pax_global_header00006660000000000000000000000064141732753060014522gustar00rootroot0000000000000052 comment=3ca1ee243896ac19af22584c83fb0ad2d8703d58 OSCAR-code-v1.3.1/000077500000000000000000000000001417327530600134715ustar00rootroot00000000000000OSCAR-code-v1.3.1/.gitignore000066400000000000000000000007251417327530600154650ustar00rootroot00000000000000# C++ objects and libs *.slo *.lo *.o *.a *.la *.lai *.so *.dll *.dylib *.qm build/* git_info.h # Qt-es /.qmake.cache /.qmake.stash *.pro.user *.pro.user.* *.qbs.user *.qbs.user.* *.moc moc_*.cpp qrc_*.cpp ui_*.h Makefile* *-build-* *build-oscar-* # QtCreator *.autosave *.swp .DS_Store #QtCtreator Qml *.qmlproject.user *.qmlproject.user.* #Remnants of editors etc. .vscode *.bak *~ #Doxygen output does not go in repository doxydoc #SourceTrail files *.jsonOSCAR-code-v1.3.1/Building/000077500000000000000000000000001417327530600152265ustar00rootroot00000000000000OSCAR-code-v1.3.1/Building/Icons/000077500000000000000000000000001417327530600163015ustar00rootroot00000000000000OSCAR-code-v1.3.1/Building/Icons/CopyIcons.bat000066400000000000000000000004271417327530600207020ustar00rootroot00000000000000@echo off setlocal set destdir=..\..\oscar\icons copy logo.ico %destdir%\logo.ico copy oscar.icns %destdir%\oscar.icns copy full-220.png %destdir%\logo-lg.png copy full-100.png %destdir%\logo-lm.png copy full-64.png %destdir%\logo-md.png copy wave-24.png %destdir%\logo-sm.png OSCAR-code-v1.3.1/Building/Icons/Empty-1079.png000066400000000000000000002071341417327530600205120ustar00rootroot00000000000000PNG  IHDR77M pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxgW09ZsNr81,L»,.9Dqg-+gi4350S]}Kh$ꧺ}sbI$IJZI$Ig$I$) 7$I$IRnH$IX3ܐ$I$If!I$IbpC$I$Ś$I$I5 I$Ik$I$) 7$I$IRnH$IX3ܐ$I$If!I$IbpC$I$Ś$I$I5 I$Ik$I$) 7$I$IRnH$IX3ܐ$I$If!I$IbpC$I$Ś$I$I5 I$Ik$I$) 7$I$IRnH$IX3ܐ$I$If!I$IbpC$I$Ś$I$I5 I$Ik$I$)Җ@$=*yXajmG7=ϻ=Œ$ IWY` AA i}u:  v 3IŢU$)\w%hm2spˊcjӗtO #,`Wzvw <lv.1k!$ IF} H԰b,P4̀,h fTS v犟߳FY%%k # "Mv Bv ڇuav]}3ܐ$U -f[L\cGlsNj'XҲ`dž_=!HKIRi>nHb,MXL$1sl%$-;S|C==;9qУ t0nH⢆`D;fb"bG#P it,eσ3;vK'IiȽ7fc,"ؙdpn%܂uR4pgg1H0q~1 X3Hw 7$I%6`  =wƘM0c ;dMsFx)]~$6vk`[I^$)LI%$s‹A"jIƐ_ NlN0c.6Z.Iҳ1ܐ$)@ Ș KcI5A!3 2$'1ܐhh"Xb288H4b,|߭ٞec3ge l=+[(27aJc#K%&y`&`Rc__dQ[3u>4;rA3ҍG$N#Ax1 8DrHMOz%g;,6ȚOm;־g"7(=L őo&NÔBO^=-Uwdi$i7 7$)T N&4 YAX+˖pc O`|u{_{6=}| }aDiaDiAA*3nw9?EĠ/1P|ϳ,g]g40igqʜ'$O_֥IfVX0mfN+!Hs|,1ܐQWOml$P,My{d˶ ҟ-П-?"`Nz_`,z]yauiRij èzǨ]ÒB*< J4cjh7N)NBԦ$W~rI5`cbvLIҎ<4j`pqpҔ Cg t */}ʳ}hno H ~wy'[EcU&2l K&֒N4-5iեFl^NK#nHklzp,p$F!wg=g(W\@+:X};\!cÙ`;>g{XbZI&7)jbF&6J'hNTbrc-U2: x`JT 7$〹۶nc O@my6taEwgCw, oJujvҞ51#[~NQۘaRcC&r¬&&5J%iJ2>MkM͆%x`V6\"nH3V>G,9qI ʲkCǎ;B4Z3<%m2L*AsM1./i/lnHД4pCR&18`a;h lcm׭ }< =rQnh+lfZbr[ OY:L2Zc=B0V&kfslERlnHTtv.;98Բ-96f*0/r>]Xy tb.%HΠ#7<ۣ:M6T$mק踞 -3ܐTIL'_vAC"{ (|έ6}yHs{cHz6.k  iyͼh~3ӚNKZZkR4Vp&q "pCR%&G:`F@G=Crgo㾍7%U'4ȐvdJPݘ 0 k!JRNZ#` ƐCRnH*W)Pc:S zjZ}} <|ܼ냞]feHh1c sTqւfrD$Ԧ4פhqf>Z#pOЀ I%g!d`?xv.=^=X`0_m3W=622Iw-.g:s0T-)_r H'lI%c!\TÁ3Ӏ9e 2r Emyxc2$ɮaG$pڜFp8fTQNRIP^{߀{ fs,nH$A3IRPE37sEsÌ/ݺ+䁍0P$lj!ý;NdJ;j#ttg  "I0ܐGi\',;9  O?_ +a ːTIvQYl.Z2ɰ& x `VÛIpCRV`vXdY^@@6_PG۝d];fv7f8kA3=h,lx2Jځ?Wzrx!iTnH `Ysy6_$_,R,‡Ɑ4$'/ `^{} H%d _R[6k14 7$EYII4N&[g/)O_޴[;;`Ǟϊ@EӘ\rp0#T 9 7l#մ4FᆤH6c :쟱 >c7񃻶 I Ÿ&䈩u|1ORn`YoH$ ᆤiN^ 4gg+'+Y>Hax TKeRIfVqV>yE`ϭ',}z 6ܐ)`*p<ғFXnv{\ ԰4@2*`Bc4ѓ&1!cqv* AȱʲHګ] I%/  k]{&B|@Cb󁛠hU* go'Mb|} Dr\,pCRL"5j0xok3++z-I dR jI꫒>3'Z`,7:"i^_ 7$(N!q4PYg73T?Ҡ]G$ gR 3IjR>ˎic:r p#Aq5'c!) I`.pJ`LȖܲ<]e I /)Tqf>tD*T#B>a$= ImғW q:ȗn5Otї?G6[ZhHRڱw"A]&ɘ4S29Y:LY9']nH-@|>/wm8jHLP>ü\9R?"h@EnHy-۹^,RO>uFۇԓ?7k!IMEP$NRbjscsJAq-[vD*ᆤ!hzp0B\H'G;:xc<A瑄$i 7NgLj0)IiSHU[ kw*ᆤpA_Pc,XAue!Wd"%IHP_bΘjTŭ\|J q R1ܐ/^"hZQtf~ۇX>@>tى$)$b+`Bc װ`\ ?y2Tn~**ᆤ8`}6*}͛Y1=iMR HJglBtu%jRxՄt3"h:C*tB's Voݹ?|D7TD<#3.'T 7$'#1R[ʭ[xt mgs`IHbr_\tmç1gl5'iuћ!w IepC 9 ' `F<Ο-ܵA, IR\-B&Vs\˛غGЏ FRy1ܐ\w@[6s^XdžYgiHȎC29C-={܉[JepCM!hz 0tA[M/HhH\T8`R-OMlrW'vN.&]ܱk}d$Idǒ|k9gA3'\*G Cp A/ IGr._9?Ю<_m nmg떁`ى B%I 0XS4qĴz^y@+3[o |Hc!U%@~jwcW<Ƀ۠?U)H\A_j6csʜ&Y}9n~ x'/RejN!Xr$0\ՏuU=\PIvpQۯf4pr|!O vV ŃTaۏj \_K1ړ.ET fiFU9=ܴ߿ զl*IRdui.;cg4pfjg *?!h@*) 72 iirzp+;mu/?ok[h0Ԑ$)Rvi 76Sȱ}` K%2ܐ8PPNluwo>j$IK9nf'n,AONOT:RUS?o;ۇ*߮gބ=]cr|I73)c]k[:C* )fg(+=k6p#V*ŧ}!œ H?hMS,skR4פКjRt gy?kO7:}5I$v^&H#hH'OY 5tY~ gqH3ܐ⩞ xpcocw l7otʫ˺]qO ( ;!E"`LmɿҔ\T_JbZZkbS͜{uT|^?'y9yʟEx ءk0O`P\IAP].6r$iS8}^L+G`؟ClpC3%96fy| ߺs ?yKPTË;L@I꫒T ‡4Y @*kOɟ玩k,:?w yxs?]áG_@p@ޗg(_wPnv )2!ISY2)1v-ChpCNQmͱcoޱܴ9q4PT>lfw ZgK53-aESuLUjbl]# W(lxm{i˱sۃ/J7ך|</?!k}@:.)sԔ%9@O$\^2ܐ# L^ 8$?O`38 8_# >k3$5$Z9tJ/^S꬗BwǺ>xeۆxts?yrEr޽ oZ=| =11/Q  U~5^Teo-v8SCAv*jxfFU* 'n%[9jZRdݺWts˚>OGmmP纤P(P/NSu:n\ xrg!$5"]>&>q35v3RIZkS3gŭOn*=NXˣ[ w!y>|o;m2>fIRBe8bQin 7IlN5@,z.7[ݼlBOړ'QfÌL2 ibF_B:iT6<<||arER9av# U|(? ܌T=f!#cq} "OrɯVp`RdjLip&^+'n8x"}_?Mzϓ/ g7>.]@U:ch #Fݿ2ܐ½怩7 EULz'֤8vF.nU8 *:ՃA9P ?I>)\v2ē{c(p9i>knH&y3C~~6ltI^MfR 8bZ̅KZZmq}97u;r^r'7Аq3p+7-N )-Bb:amWS˶PCeXZx9rZ=DKm8(v+ٔpC-E;۾ʶ<14@ ! K&sб S 2)(*a0_`(_/wngց H%\/ʹI@SU9a":ab?<3ϦpCqӁ^ 4ơu|έlQ,{lGQ(r̼f>|DZO&6:Cl\|~pW;G;t|EUI6eUsX<6fGz>n Ճ| ,k`([ka0UIv^{8&4[@CT=C:se x&:#4WyИM|%3cN<n磑*ᆴo {-\۳vί$" XdS3 iZkGStԓcSOηJ7UATFU$LKNi q:LU" iT/'Xdϱ;cr֨ hhGTbFkݲ/ƞٿoow++Rbi՜7#NG E`'SpCscc~]soeвcFȱ [xm7iU4פs-9/߲ U)["IgϞ¡r_%X"U i>hӁm|Mܹ7V-W<-ռqV&6d=HQCVt s޷mKR9sE _dބ^sX>zҤ| n7T 7w,38aöAH ![`>r$0wL5c!imβ};6eljgs(rEӜ2? >>$wn ^͡҉\5.G ce!=;7^kb+W"6Nĉ39pR-ӚX}6|v:6KViEq-0}\ k;/M|XIT2ܐ7W<e* zw.^ Ģ#;wn/w@u5b0"',n-1Emn*2]GwluOp8YQ-@:[j_NdFKw0[ |83P;x'b_?o¦A5`Eۯs6sւf`!IY +zaE73aȡ(^r 1/T::<+ Us`'47۷6 C58zA3g/h܅-0PC}Nzt;<ݐC7g:s~m*`'O3P^ ||ﮭ\gE\#r*0cr-iuEvNx?=;^''N>՟y74 ~Ck\w" 6s6s&߯źH^Ǻ^.k˺n(*aN͇)Bc<ʼn*DM޻oݱ\!5RN׈"01Å[x.\j]$iܾ޷`ݦ~Ȥ\h~qfI9GkS<Ņ*T{ȷN`I^sxmDFme]lȡ~.RϞk3!.OpC@j|W<ɥ^@oFiq+@ 9|J=?eOIu?gt;/fց[+R-?/e|}7#p( 7TΒ3~lw_\28mZN:UEBAO,T ^&8xRp4pKdr 84{n>ye(q-P]y\r8^źHR.n\4vtTҗMr(m=i* pI`ame?YNoPb+W$=g7&[I<5ܽV#!*P_W|;)j 7Tnj ͈nr]G˂PYS,B̶ϞƘuzb ]~`k?EA>WZh^ Sn.>F;Jd=eO߸ܰ 2.C\$GNK'rւfk"I1lwl} X7܇gNG1-=V |)* 7T.ZK"` }<'hx)'r&t4k"I1ѿ犇;ypc,JEA5yc#{M"pC`,*?~}==O04 b'_.aSҹYVcM$)6vgyoVq~l ZKrĆ 0lK&F(ˀ4n2-Q=Ȏ_e3ڠK{PDm5\|X>tDk"Ie=kXu8Tz5)n~01G/.'p 7gtmD ueu]G"-uiRǕϥ7T>q mP?0To]13V;4( L! !.rn_ UJH*{yL5 sW_6ћ3Pe |i6jpl 77I=ˉpq2z;" )LOGS3n$vʒQE5_<2H~6\d=a $ ,!zȾܳpM#hA[C;,$ ^ܰ*8t&-\ " Gl(;TlmX ~/E$?^:quik"IzW.gko8TJ"'kׯ[7W9ObXv$!IzVC"r%}lt E&4WK${F @Pn 7qI`i(`8ۏr^L:`BC?vGw5IR\/_U3Y8&L5 7'p RS#²ANcrj̤ ^wXCt*W;YC-f(~]Ggin 7aKoD! &-$iݾ38μW ?פ<|bfVE1X 3dڏ 8·+lJ揫7/dl}6}}[/k~rJ&HW⥋[xxOx4Z 7E'܈ݾ~O YSJS4r"}t9*Tb\`7in(j_N"eWoa0EJ'kq 6$I _(;9?\}z@m:ΝC$?m)4 E,`T"l˸.glIȒ)u\LkɐJAS4z |緫}QjIzxESvhß? tx4 7Ӂg(x;[ )ЉhIYIRhsE)}8"Tg |)Q;>sYOFᆢ`*e, *p-x \+.isJZIRoGEԛ_ojvy*{;mrԎ- H0PM"6%F__ze[Rwu?O!IJ/ǵ˻yޤ*ɥLm.^g*  MzxoV&s~-|%3Zm=$I-g}?1:qVB2s8z<3DI;B]ڂ(J$+_37jG8p p'JpCP|w墟,gնAXY!W/EKZiq$)6vg'O@M K|f  WΉeKrDn̗nĊv5|pp܌23IRLdEn\éyrE(E /ivԎnY+{(rm}m(4%@&J߭k7nx8aQ _=:j$I.O]WvFPsxY<'HD) }TqUk Пҥy$=EKZy9n~3,F.1ExǻrT5ǀ`'IOyʺ,E(E_ GƇ:+9^ut| &I x񭛡&N*=& fpB=p)оH`c]]Υ(QW 0_wy$I6]'Ol!xOFC&yV;:`:O9uY36F>y> ň`]'L cwI3E*p۪/^rט}{7H|x8 nh>ApDƧoG~`#@[ϛn=$I/`\O.W0(9&x`e;hw=A2ޘ`gȼ{~ͼUQ7l\zTcC$S6I%^rBۅ42I[Ͷlks[-f= kWd[+ 6 _5{/i-$IAhD ۗ3${E㚫8bj}h\ x*Dp1q>*ûyOlA+l$i,غ4ޕ5H'ۘZaS"q[pnrV])Bs+9 6.WWζ$>ś'6@IE` u܏gEbPE2H'_GnZzbQ/rV~9B.U:JPD;! Gx R7 9"lܾ6؈|l!IRNWΝ1Ր+XE~Q6# |*v2ศ8RlD^)kY IJ|i,Ryglk4(rE.JVv E&ٱʓSY\`F& fs?CCnqy"Ow.$,'1l7skX*+zq8sC% \qP(2!IR$Llp3 ~QIrc͛*,`'rn٤Op@z\˕\v+5.]䦌$)"&7e[mёI߯'lv%`g/`n?[_G:!S6Ҋ0߰$E ׿aZ 84:jR+qEO[Lt.twF`rv~>]#͵)~Lo$IQ5ްIU`=t뻳QOd**c-jWw^[yoVl@Cuϝ5çY I"n֘jn|7d|C"/N$"ҊE][yʧie >{T^w8!IRL[޸?ki$/~\7,Y .O)Z)A ?)>b~1J$xVxx!IR,_w_235:wJK._XA0EQ8Gpr h<ޯ8iN#?y,!IRLtI sTR;z~tO{W@_ST&IQ8l|`#&fVͳ$;ngL!4(H%xoV}G2oœR~ 7*[ppqXg&ZkSEB2qٱRB"/rW—xFʏFe;06 scAJL<%Ԥ,$Ie䟏;mШ>=RlpgnT)Q8_w{VX7g\]ZHTf ҉2-b5*Zۯ\]|#~1ܨLMs3h'߼}+@PB2J&ۛs:buV~hg{" Í |^a璺g+5^Hϛ$I*ou${|OqBWb%/z3R 7*>y^ <+1pB?{IT!S\6ը K} L0ܨ,9їw(ۛS_K$IdVk{1QBgy RbNr$QNc6hk D%IPp`bhN|R 8³o! \ T`^\`#~9z[I*kkph?n|<>XhHbp2?|іݸ͙߳q+cڸpq$ItI+} t8GJ|'$ 7_Ezt| L6"XdZS@T$T71Ki 5 ſ\Y{W{BpUoHK0PWb6o_=ِ$I$S9vNJ#ygnw(&_Z/Ė*0˟ֱ+[CHC)f 7t>K} JnZ .o<{LNh-$Is=|6 C#(qO(=x1(O5[J} ߺs+GFf-$I :aV~|Y T[W֖(Awn>e,AH(:.G;TBc6$Ik3|g\N;MJyRyY>Ɩ ~`'yFH|Ԥ=_$i1?T4w >zzo,T 4{2"Te`=<kh\ y~tTg-$I҈ZA F5B۟Ϳ-#z2p#RˁQ֝ojVm}6"/['N $iD}I6jx\v>%dDF-"&5?; WmS$I;{j^$yn^S#|7sN4ުKy}vTtb7ڌK$uirtT(+ה^ MܽxM)b}oj`837Y I4ΜħN6H%{UdSVOF4oO m<~ %R(r>}Q$IR8>t"8b74r|\xW`1AtxBbzI*A|ꆍ\~G}6rl$I!38dF74wC)x*zJ%'z""vc by&Q27Wڔg$|pD$)T UI|tjS`[ĵJuuk=ѺQV%;T7k 6b!_dfS$I*7!oIksSvO9ᬧ2܈%LНRĿ^U-i~t, !IJc'O%-FH:ſ\Iiz$<`suRj7n\,*k!IJ/HM|JuSw| 0+ff/_5.GBf5&Z I )rtj\ެI\Juz"Jp#KJu9^.E4?zQ$IR:9)$ JwTӣ[KuG|R> E_P" R9g#.db-k$I'ǂ=@@bhd䊬l-I'՞n5 R@{_}Il !I"!mo0TK~TBR@%yVt-J+tEެ6P伅-,n$)>t"'ryFHnxl;YU#x;p'4 7`ףKuׯ+I\c!$IRlyLrzL3Y v@%`M U:<}qlI gZI+ UILOӈ)uK5!h)pg!|޹F$R yUk'NyMB$8ivc.ZW k}_)Fo>8)d<'g~v~plH]&ɟ^7BH3^!z%$f'y򍴢#^Kiuyϖ;J̼À$II.2Oo}߻kk)> ,b "<_,/'gXiMٳZI{h%B#t=Wa W(3W|N%ARuOtsͲ힉x^W;$I*~ MUj`g!{#KScJ1xP?~qrFNh!$IR٨JWqܽ'K18{#PpV?YNP3#ڐ$IMBhd$ް 7a:JDU\pg!fut-5) !I/E&mWtb zFFo+\e8g#^3I>"J5.O_>uPե Ew J5`soγW\8 Q%IR`fNFȖ]C>]5`wR ~߆~~q_g!f5Wq !I^*/Eohd|-l p>AQÍV/.\e |B_j6$IRŘ֜{/~nyoVbq0(%@])MY1Yk)B$UW8mB#laN ÍҨ.(=C= qS(򅳧$Iϩ}_./_({gant)?5,I~LsV[ ITMp{7K4x3_ A97wp͢V,S'[IT>qd)렑?-Ű 2QFIj+Rp6_9hM[ ITZks]cR }˻[-sH$Y]`YaOtk- zGBk]ChE-B$Y4V3l.+N{;ȵjOFK0Eh{U탞̤$Iǫ-YcI=83 7F!;^G۶@;9`Z=tkI6Ո<~~6n^uqO}f1R ߯aiz-ٳYIpؔ:޺tE %3o 7FׁJ15˺#Cg.nn$ImwM/l{أC3m+;BmlzPtIQS'39c!o {ڰGm^wƾKGg=pY1֘9B$i}I>Y1w fmo%-xP h%I4ytǻQ>rͺG\hwƾI ,H:k#5 w$IҨ99E|;iO 37c-/бT 6$IBsւffuF>,\ 3{ n۷BYqTI 7$I/~7}[̅D>7^C0k9?~z_cK\Ւ$IazXk!o=npp^o6!P\8 yn*IT ?hwj Ci.=TJAhvֆ$IR)<&Y9~Pgpc/_?ՏtZ-$IR?9{CvW {yN%c 5{ݯW:k#Ʀ6e8rj$I*#ֳh|>Ym69d xz{n.Aޡ}GOd$I"kpN:{&QX8%`4?/bcb:MG$Ip掩'>@MYnYa?E~q6clI(3C>{`&Í=sp`؃7(ZJ$'L$IrFTY+vc?Yg2}cSîY{gGOo`fo$IQUw>7=K3nび-]M།8ڐ$I731c!Ov ̱Oe{Z ?ڭ~5T%9gA$I󜽡}}9XV 7vϱ0/X{qHکJ$).Xغ^+׮spPgw2xa@_뎭V?roqWIHK%|_˺rpp {y}YgmuLp $IR]Vgohd E>uaY\jw2x'9 [B[X8+1Ž%Ib* '?+~G/d>ʰ{0oc,Jp& !I=iK,- s_Ír8 A{6mG'rކ$IR\4Vx1mB{|/6M|/ 7O-A+`}WYeN$I1S';{Cε}t; 5Un<ÀCZo昛9YUB$)fZk\^|P'h 7K p>0)Ao_ˣ[~&[IS@] 6}O7x.=G]KXIZVñm a{;9nL xγY}X;{Q+5i/+I8S'C;ʐZe.'=/V(ӦXI;aVZ졦w )EkJM%p s\~@G0gD5>Aj-$IR̥ >~;hEy0\Q57x*d%A[;gaE$I*>h >j/%+kJJ.SG :~-3E>zH$LKo!:\CFrT$ÍR)0lwFMhbj !ITF>z$+ǯ $pcYg2#YI239ñ-N~x0G^[600c]/OlqW,#(IT6^sͲ0<Tb 7i+4n61LhXI2tV-ui Oݰ1'K+Ԇmζzʗwf$Iؿ2t^rBn58 ?ArlN{?~$I]G';}y~@gCK:n+-]RVOKt$Ie}ӔHp& }79$+4#HBste|CK'YI N=χ5\-pIոÍ$peW69 -$IR8rZ=sX핡\=!RaKS*=ܘܳϫ 2ڌ$I*??;_mK#NWn|o)zExq"ITI.9d,U~B[RORWf 8hk|7n HD^BH$UtsYs{+B%3'{Ec7$I8>qN^In戓*}ZqpDLA$>)UB{} 5RÍ6\B\ џ㦕^eC 7$I*ջn'oho|PgoJ,Mp0 9kO3ʹ^$bTl۶9d`I%ԵÍpր"|6+c$I*XcU[핍YkJpq%ԵÍq37Bs^s^;nu$IppiV~0G<+18X6-uUIf$E$ҽtI+ [C 7& ʽv5&Á .2q& I$+k2 mO 4VBwwz喑7>"H$ ?Ѷz+Bg'9?qWnH'BH$ y㪙Ҕ+9-aʹn$#a 3X`uWm8`bE$ISA{zC[28YIF3̍˟@ I$=%#|_s3ʹnOKRE^eRmH$i&7fK_eS/ Rp 0)6Ț!2QI2-`%IL2"hc}_ ˵n sO^['SV$ICƑJZ-=9Vt֧ xgֲR.#Swx XI$=GLm+y0;\X)!@cXݼl8P$ICϦsPNc+!h!h&߲+[M%F$Iz2"h,o sfrc%9KJ99c[J$ \g! E#ay\9ֱCfou2sIJ9pIE$I ҉A{7%SJKRB[T_7$p CI$酽dQ I3k/ܷ?&g[ =88=XYFՑJ$I*i~1=; ) 7b0 .rIJ9{AE$Inȉ.Mў+?=!/sQ,!%) wBpA$IԹ~9sCa7 h+sq!/IiUWdI$` B$I*)=r[[Ž^[N+p0 9a߾c+Yj!I=AwM^}aN+kQKKRyV2s<J$iϝE =[C}s>e\Í)ax>2XI$tC[?/sRr 7f5?23-7Lv$Iy [,X{_ޡd˥vnEKBMa='U$I{G_jmawbԭÍYa?=Xfδ߆$IĆ Z-s=+cÍÁM h$I'd7텻ևϱ 8VnF 8 kaKRL"GNm$I':zKSǶX}(᪁sˡnn9l+3cTM$Io6W\#Eg4r[5ئ,.I)7pֆ$IFƹ -د s38b4pH'꒔2t\J$id\rxiSqP.F 8F(>w&2d I$jjߍ68׫\yqax>2t¬F I5.՞Г%[m=pbU.ф]MaS]"Iw==)_u98׫$A| 4դhNYI$7C [q 1,YF޺ ?Y$IGL' Rýl&h,Kn, k<^ee蔹ې$I(x%)~='{^cepcul`]%I4z.>hEY1f>pf\kp#ISJhq2"H$iԜ6Ho戋 B%^`e&I5$IE[hqKX[98 ,=886Vu;*CZI$SM{澍a:9H1|L="H$i]"h *5\#pA\j9ͫ\0]$IET{eYWC:9ܘ `">U\$I}cLjX?=j1pc01ZG`grI%$IBr}7X*V v5Hޝy25"H$)4.cGB޵s ptjp怷F:dKR$I35wC{o@6iEqQ\Ía Yߕ*SpI$6`|]Bh%'m smpc.05[MvekфZ IP-e }iOÍ'fMEn$ya{%Sm$IdQEwC_ÍT8c/nդ](I𝽰"h 8'!a V(ޜWR_ZGI$:IkmBhu ). 8+Nc1\6lQNd$ITL4Dǩ6q 7&9/ *c$I*wC{ a7!N[QOܺ+-l3ܐ$IRiȾ3 t=x,-ܘ [Q^$IJcl]ƪnw}37f"ƜǺ>y;nN[$IJyi =b!F&~tOWOsI$1-v[j1).SQC6y۫0"H$.\bSQ?<pGǥ.q 7e^9elLK$ՐuGqKYa џg0W)c/n$I*T2* ݶlPqKFx6&D$ITzim ,C]n$)a)sM$IJ 큐wLơ.q 7 -^5e,%ITzŢna #j6ppjXSD%I )kBa7'5K׽NemɄZ ImW>pPӰ߆FĆE$IRd"h=i O-C!YwoF;lJE$IRd9"hF`5E!ܘL lhoj)s[V$IqZjmˇ9\=pxkpcnX{3,$I"&ަM|ޡBX%QI[↕^)Pdr=7$I-tZ+׭ s%Q/IلؼmB)s-un%I9eNEnagύ}Ƈ9-{J\M&a$I9'2[9pMpc_z)M6$IR2Φm+F#05z1Í\li$ILLBh o+g606n^R6LT$ItФ:ݒ͇:' ̋r=nB[CڽB*}Ð$IR46"h E6u:{pc/Lƕ6Ӛ!IhV`vkp 6 sC$It:vSȭDQ7l(_g0Q0ř$I5nyp@Í!F9ܘARX璔JPWeiI$Eۜ14Y5!n|7}ۼ2*E$IR9"h5ܨ&EW[mV37$Im'̴vO1,"wzc.ޜWF7"H$)ҖcvSP!"L0cQ &5-$I"mfkME3ԥ)SZ h{_\*Ac=7$I}ckܪ{nnTpc:P`Vf4XI$Eތ*ҟ uiJkTpc\ݿq+B$Inh7eYwÍjxu^e)$IXtErCÍVe)Y*3]"I3"h,k szB^in syI$I11)-P{n4GQ 7&g(ON)I$ɴE 5"ycvKlj-5ې$IR|LovivC ~_Q 7Ƈ9X{KR*EmƝR$I-( jw{r I$ɋX?p "yi&9vwJ['I[(Ta=貔J1$I#M-{x{õEQ 7Za؟-x%Tđ$IR_#H63AWo$IΦ 뺲aWK;])Ӏݵϫ,`$I+'nzA0!^#j0C^!c$I+"݈Rf$V$ImA/h{eDQ 7Zam`+I` I$˱]ZV[59v(GN$I)G퀢nL9`6D%h I$L"mi 9ܢ=(q?=g1H$IqPzA!\p)Ya I$IwDeBoڲܼg%q$Ibj  uuĨ=)RBۙ:i$IKMzA0sYsh n_A](Ixo37jҡާ(=0* IJ$)zAn `r"rm04ܐ$I5$)WYl7TEGiƆ9``II$Ivz!`I"w#*8#W=g~fI$)TTϯ?=O-!x!Q 7[ffL]"H$)&7VY=\R(=r׺>fxnI$,W sYz_aZ V$I16_)rt0Q7$/Ӱܐ$IR|Mkq^}C]0J= F017tnH$IE.nqeOÍcB16Z$I}X"z~CPggأn$Ixi_U*,l0W $I&z[zCmpcLEaexI$I3gLEwCÍcBM}>+P|I$Ԧ*絵/6' FiϚY)|$IY Hu6R G!uέ{}W'YI$13-w)ua4)|$IwP eC"_ph s5ۇ|WlXK$IZ(Kn44 M`g$I[v*WO`lT{Í`b拾H$IKS<7>=@>2&9`37$I$T[}"H" ; S$IRL(2Qβԓ'I$)掩zN!7a.m;l$I[Ӛ3Ai jQ ̋cOV҉nNl$I[iistưam3QI$I1tfEPD&SO$IRl58sCz62B]T$IJ{O#=R@S Eϸ$I:mNEE)ÍF`b޿q3.I$IR)eQ$e)IgpI$I53K8vP)$I71c)e1=Cn+I$I)e@w#46\[{sA$Iemop $YA$Ie9A)#F .pI$in(*=(ؓ[<ے$Ib/gJx"D%I$߼qAzRNܒ$ITXk)uύdR$I/FzR-{=ے$Ibo g JnԄ9غ!϶$I;m[JOZ4ܔ$ITSAzi(4I$ISE}YnH$I*o]*h&9{nT*wˑ$IRא.Jn }F]<`$IT6j2~y'TF{n(DZI$ 7Lv_k U6$J$d~vUʆ0L8sy%ITNf.Jn4p g)I$<1ܐTF+6$IT& PzRmT6%I$SNz*"pI$IdϷS$-*Ag"H$lk EBB4hCYI$'Y=ǽO&]` I$pՆ9cQyܥl( k0$ITNl(rꜦ0[]pc!/K6FnH$IT490Pyp}E$ITT*6rS 7Tr6w-$I Q{ 7Bz&g=# I$rJMmW1@XNP3_e$I$0S)^;wc[R$IT*!v򲔁|] [DŽbZctGso"H$="?Nq\cFŒmA$IoekE7D0A$I<ܭ?a!&"pC%e)$IGUYʨ [3*/:!I$x$m&E 7Š$IDce"z q]phsD܉"zƢ}A$IRQPp}pV^DhM[I$IE&Tk'+NVm$I0wLe5El(9}k-$I0wtEmb VùʛJ$I*jC65WBmzqv^ I$(>1F 7z08;K$I*|1ÍP$xhJ$.EPwJ춓 7B1z&ݒ$I [mE:Z|US}Y[e$I$̮s5QCTS$I$PF 7BM.Gs<"H$I*h0{Í-8}Y:3n,I"pvE 7bi$I & >sc 7Iys:wL$IRᱧPp,ea@}ve,$IX s("F7怵qzVwZI$Iia9ʵi_%mY+$ITx.qeaZ3Hr3[c9ֵnH$<"0"r-nt ,z-E$Ig^PJQG>ÍN~[zy$ITxPo;QG{nUEOĂ=A$IRAXp(#0{Ť=OsJ$$J/u!d\|a6+@',$I ʣ>.yΩsQGÍB\ʸ,EϷc"H$lIYw0[z;DKSN]Ӵ$I MΟۖ㦇n8s% qUyyV9sC$Iō K[y"FUYhcQIÍ~/XțϪ \I$I/=l))O9\ pkpc3<k+^ zw[I$>w+eGYFHBC0t$̪H$pd(eFU9\K!$F1i'$I1cdƆBIÍ ^ zWwZI$=Jއ_1:,DE6*l$I*;븤b0}Dh37P(ܘ=ҫAcC&I$-])P#,+D!`mXnSQI$E_M;@>Ӷ .Q* q[Y`^ I{rKE(asFL(@GX?ΫA;Іn I{b?+eMsR(=@hOck`sa]1E$Iї@Sq0[X(uB s2ר6c$If*1cdh=7R(<"vz枴E$IR=͞*fmCR(lCe«B3αˀC$I=kW sNBtoD! HB3̫Bm**Ird9&Rm|"'1]* V$Iޗ%m6}jpcsSUk;H$)R%)M sT@*_兡zlsE$IRdes#DXZHJBMEܧ+C;L$IeDMj(sfBOTf`[XYaة-a%IQOmG\:lRu55=Jƺ0,Oohz"H$)nRBpLxuhYe$II 7#TvnC-(D%#1n{d_$I)P15"(m`!:gpl$I^-Ijk& 76- =+D;ғKC$InP h Qh5R -7 I,ݐ$IRCt0[Y5R `  ];-$I"{pLBQ_U$nzq$Ip$c1Wp=X(0JƽRSZI$E@&LhBS[ ymOMv+M:$I9?60 NQz69&TyhbpKS$I!MiP޴_c=TuԅPSUI J$)JRyaw_)j2?^땢t$IRt<"8B,,ZE-L$oطѫE/&$II7]XZ `JCW^ΔE$IRd<"cՅ9ܪBU0lLx=ަ$IG9\,o.Ef#GNҋmA$IycύQQctM2:[nQ 7[F^m+](Ix`}E(!GX^ ujX`#UT/AP$I'ZWJNjXpc(kV/'MK%IG75.o)E~怓ʼ^c$I{-BH&bLi(kN(D7܀Fh^V;*@z"H$)tk\R*^6*6,K) Q76}a vܴ:$vXI$n]{E(oگ1Vb]Í_8fZ;En'{D$I!&$r|QXLrNRplXg3'I$ɭ}cJI(3>~-T(['pr( I"%)9Ri!M0}7Ү[6]$I:wE2\_ uZ6+O/鱍dvސ$Iиp1!VOB] -XN<%6.ŸgMu$I^`[wBO1.rRB 75+P^l$IV_)s갆R*-pc xNQn I6Wl@KcRx0;nZW^R{_-B$I{E}cRm!k :Gzj\dE$I^yx3U"-|E -I 74 1.f IjJt\<\J-pcpgh蚤W^Ңtg-$IHWDp%pRFh sC'V{%ߘ9~i$Iv*G5\pA8^ǽS3hj.9H$iܹ"cRmMF`~ոPÍa Ffڵ/ I=r %gpRqO5XUYW^R:uv$I[m3|x͜@i`i?]Sk6 Iݲ;ME}j\/c_(5QSmA$I嚧,J90P.pc)DX4" kZ,$Ivm+l&Zj+n^CX)ZrX`Ueq&:K~v I]v.PZKUYh[Jօn vM 'T{uj\DE$I.ߢ'9#@_;^ǿ`_:v[j4uБ$IzIo%sFiIrڰ.,zzh(^G<*..M$IK-F'Ψ#MR T.pc#pX%b0̫T…A$I/U6-:?8eR#.zJnbSQs횭)v-$I^{-BU[Ɯѕa .*Nj5,&9%sxȹ N 7o$IzA;Stg,D1^1*וzً%ܸ#f<]rMA$I/OA̾~3G sǀKn`i]ժ]cH$in\a̬L WcĪGF(wh95pu$IN="lo41n<<`9d$vCpC$I;ӓʲ3e!Huew82!/Cldu5I=%hI$IN-:GSK<*Y@ax=5zƢ$Iz?jI6׎戫 >/8Q\]V$Iv gnѵe;6!(paΰ;qfe lҮYʖ~ !I$Y6txX4r_uv| b 7ځa*A58Ƣ$I ܻ E`#Dž9j`ymn;HE-A$I6aSڦ9fln,|}76 `c$I7-w΢j#6< lnl&h,Dɍ律k1~=Ƣ$I?Ú6mOK[<o޿wvٿj#q$IR)iY(9xA#1 ?__ӣ{#]]ҝ澵]B$]~"9v|Cf埯Íma I5T}'i?{u$I*aEI5LnUF+G"|MÄ8⤙ˮytBH$ [R;'H8\N9X@\4_9n$D+YIt٢Vo4V%8uvD;Wa vZ*.MѮ$$I*EWo8sDb}"o9c+}7in7m!$IJyQqI ʿbn k|&ƕ;n$I%dMiZ,GN<黀UVE=`8b ɸk.XL&痛$IR8m#CQ9\/s9X.MhͰpS$I*.E`r o0ʿb`g4wv] {KS$IJA:M|P{mK+p_H8Lȸ4E$ݽ-`@uY[ K+pc pGl{BH$sf@ۿ<67򻯔48yDa-S$If Y6ǟ<5QoVX/ 8}n0/IbpS60:搽)pc1.M˴[6XBH$U-tZ29|洰G]eL)Bl* ͓&O-1ސ$I*d?gE(`'i`b]YCfVK5?Dk^94[Wt5`/ITV29}҄G] R 7n kD ^;=L޶BH$4d,DgT9ڧ6!J1&&-?;m2vG }ڒ$In*^?D,Q7X/00(gdm64-p:$IRhaE(PFwh{TÍ=a'7[Wgo߾:H$u+m! Q:ǯ_Nڠ`WS! :lh-+]$Pmt17l/!n [S6QQ\1}7$I 5OYBN ;P<:k_FX44_?adsϚ.ִ XI[1 ea;xR7fos(nd۷9{C$)ΙI)?:uRأf?aCԟ kD<ecQ\=[$I"OY4._>:a쒢!RR0ql,֟򓻷XIku]) Qh29gmd?SF?pS㔆rP;OyI۳E$)~t&H$ЌMW {e?2Ys/7Ƣm=iH$ItC'cmeLz0YzDXT$)rnZA&/ ʹcv#'?|<45X2vۆ~_O$IGwmKR J&/OB߁5gx%}Dh%|uA$)"ܻBi+yþa8Í@p[UɌѕV^ɍ=ܲ/PI(8&HXUP9~|||0< X=eqx7$Iwn$UF=מc' y;w4n[խB$) Na ,pՄ%Nn_1goH$O :3m{<d e!pG~I< /ha?)$IʋL.?Zkji?|~&0Pajv_Y/ݰ:H$M:ZBgs|Jx| }#g ^;(pw~1T9M{ k7;{C$) ua(9xہ#)W8k#T; RW$xV^{ޗ$I5]tHP4V%8}6x3Í|ĉ$bH$I0?~5-{#R[c:.Í87'VYy_ݻn#$I6;R,kbJC9>ib怋pnf@~3߹$I[օ=eb>3sn ^=axʬƺ-ЏBo$IC-񣻶Xqjs𨰇O{gWcnE$Ib|"d,85C/~Ívp~ To$I"97nę|Ru?<K[IȍE{(+=YI!-%biQ@n욅@W ۬H{ZhXI!9pT8qLi({>V? 7vͣaRmG29ސ$I[}_n! @]yfR>^\/Í] `]h!brfֵXIӻq3I&?'3K fpf  T&\c!$I¯k!8lr5'̬ypc-怱ifctI=twFeD,i]wo#pcevMy"A[Řמi9κx$I?Yb4xpcB$i7|_k^4T&1o0} <g샟7nYi!$IvA{z,D[Y/l!n&Au࿮Zm!$Iv'UQrgS}=JIÍ=xN$0Hk8{C{lus??k$IzY._b!"DGe|8p'!z 7O<{:?TW{nH$O_?P=yR>N#VQ{ܱ{X栕8?=u7Z|B$i':2\b"\˒u{n p}؃壨p[II$=h/Qgq1版裎%c)pcMV'9{C{7;BH$= 1mDV:˟ϜL}{ 7ರ+FS^Ӟ^1.{UB$iƖt=m2K֯~̱{ XqsDgoh/xސ$Io6")c1z!`㺈3{W='KYO{(w-fI$?=̦6gFV*˥oNU~nI>^/ҔMaZSOp\Y/ݸ:H6@Y#p|DDx6ĖD"ݳu[ IT~6ZR"j9N'_56_Gpp'pn @{S g$f Q>:-0Ís'pc؃jJ-ܧs1X?Maz:ް_cǕqE%IRq]f?/³oNY.ë`F9}*ŧCs̝$I*h:S| ڈok43aiS$IwV6()s{f(Nz<޸)KR2{._ŀ{Kֹk}Wy6F\ܗz,giH4d$Iʻ\NyfVLÍYN&yGLiT&Xe!$IReA3;""|xn>a ٳQ 7V| 3jkH\:S$Og\BDE|T(Q\(? y l>nM$X@F瑐̭RgCCF/} LN}) !IBhK/zBD2|L=gdN)18h {d13ѻlwl$)Tp9ig#ͦ,%n_ ]>?mn=L,hܹeMC$wmfMۀtsȁlod )J`q'1n\Hljugy_9H$ sh4ds~@#:lt>7-&ÍX\$\M|k$IVoJ:˹ӨLmei'LdBCgACGe$I,`o1&ϣX|`FJF~Oֈ5T&ma[{/Wr{XI4,*2:ο x.SCvc@'nDK>77<r~Qh/`}ork!I[/]gm[*Oĩ}$p#z瑇ac18}P|ĸeE'?g$ICm}\_ͯlgH~۾ Í(X_ƯO.'M,mv7.IdM]<3Q_瑬4{RQ \LOs>xhN6ދAkw^ZH[7p}Q/]̑< `'DnDc*sۧCX\C)㱵ݼ咕B$푕-0ȫ4k&GM\>KzDV`JP6)eq.$i}*:z"| |Q>>Ovdm?sGcƸ*hh$|M&O^FaH5q{Th$|붍ܾZH}e'?c3',Ff}$~+ ÍL\4/i婳 844bӛSWeK%IOYȧT7:fr<Ír?| Lbˆ hh$b,^e I_E{l#?29f7gL"w?hWnn|| ^W3@tCC<Ε47X IUOq}[ǔ樫N3aR}yf-AM ?5 R@o>?s|SlG$I%lMZ}ͧ)fv}'EOt௼iwb384t|uܽZHT>{:%_3|5p4I0(L-76ׯb' t?װzHTbP|Y#GhnIӣp=Bff1ɳc]>}: $T<\ *5/?9- G22(\}"ݖ8uS8rfS4t\D+߽}$ek x:}>Q8VOFa[ؚӨ0eHksk!IRmY1 ΚƜѕ>^g_=1SnOiyʼѕ|qqPM/Z ˜(IRbqy۾C6S&17gC{)PZ~ 8KmY$L5t_cj˸9hB$<y"xTHp20&-_=:24bc.]Δ$H9"X;tTT00(wWxaB}?>u2ePJXwzZ{3C77|K6!o~\P0(MO'u:x$g0S4q.z`߻c){HT_ݶ >.u\Q8| {n?sd^2}(ʝ!$IR]7G){C\G23frekہd ΚJ}e3!~s%%+Vk!IRԙ %9f`G"l@0lhn-O:#cݥW_#z$Ik XE{O? W6r.8sjTzmhneˋ?պ{^,ay'Z I".{pvgm-cj;sԺ(Q;MZOF< ל= eZ1 =ESOzHa.iׯhrP]oq"lh)S?+\pT*L5b;?,-b%IP#G*b|򈱜8> .p򓦸M<%o?>eVxXg!j-=uC0\|X~IQ8p'IόF`JOgB -qrR?I`v_Kc|1Q96ώwϛ?<ƪgCC/wwn&.J|ds|<ɝpKY4ӧD:>Q 7J}O'>? g&jͿqF~|fr$E]9gV|̓c*fDp(,*2~)r*61=$Iyn~F<1?T' ܐBcQ:6y}X^;a|F~y!IRֶpg#4g'PjJFi=|H Ws&<#l/ݸj1$I A_:ǫ~4&O/cg,:k>Kë4n, *1n|φ@&nX:-$Iè/c3e1B6=<-E72P2(=ߘJzF4,9x l$ 3/Y D ԖGqn+p9poyۣ+m+'O9o ˛-$ICMe"l?/#Q9^࿀1Pn4a&&pE u8%j1$i| \tpgc8lRumgCyfQ zoܙσ-sQShxblj󗰶i$Kkl-ȩQ\l( 7J G@^]Op}X:/OXI?h׬P ds9~z5 R$n-AIY ~Qj>bjSWECtV>u:RD gsy(՝jH|[߲ -/UA.%A hibtg{m7FV0oL5$ilLW#qB2Cs9}nC!FOęx$GBOk6!IKڕ?/ccFXr@?3E*X|`W)Z@y yY6Ұ}:'떴[I^@Sw'6tle0ظ9s(:C}$EhPudJC99X">k[k.$ZzҜ~r]IB2\x,~(V-G)ERQ;>9+sN?>5\1Voׯk7lTz3<|dE6}L>xdԞIRIZ x"r:xtxV4|1ַ{6YIRk𦋗s2dsO烇ґu\IRԹ[vf#2 \x!5|12,@,Ʊj$$ue8t{`FjF?%|ܗOڥ[zK}WE`>1|਱Гh? cdЕk$tgxVrmuUisvt|0PAp^H`ie>#aK_vCN&tғu$$t dy?VqP a w]|D`nsF7pLdKW6S+땣9 SW"=VG`# |XR!]/i|ȸ2}TN\<+r5RDTz|aJe!ez;bd]q+pA/:fZ-{>çS Jֲ`c5$|5maIe=?y=dTԎn` O =7+x|+%-84,YcM}4V&ol5$6ksգMP aHe5߿a*gxT칡1 pzџa ~y(N!$i=k-,iwFX_~^3>ro Þ(2gnhwO}0O+eHJ,7͍+H詵DTpZg]K 6Bӗa)j(:gnhO L?p[ܶL oʧKeҙCpՓm|M{X^9zH"ZѓAar~ S52jG ظbb/Q84e2Pxz0:)h%I%GyE+{#Y:da*##7Kf%௞, C p0Kx?V.(3PHRY |4};H?wnW k Ɨ# |bd6gDzyWpm“A*o2015Ik"Iʋmi{h_)3> o# |8ߓbeCQ e@/JFy5rn  ^hY5€C-| PkL?tG3|'KE}; $C@$K ULQue+FS&Godnh(N!"ǂM=ҕ,g-+*I3('IF `KKÒ10Ojl_, ݗ=7u\esПo62̚HT*] (1o8|Tp=k', WD倮YƻtA SO7:f!їE|PFX9fM"tE, Y|û6;6ٛv77WL̟I¦߰l`l'Vwb(KŞ, $ޱ_޻殔rCD*g5Uͨ$iiW֧ڠơA,Mϣ"7_&Tr 74'GD :uS'3>]X΁dž_*9[?0'G 'LpCí`jܧm+;x%+ip(_wi(e wS$,7/M, :59s!Q6_ S)3P5l pκd%} yF+Z,Iz ?c?m9[#_?a_8f\0 ~'LpCay4PpvrJSYϐ'\},j+$Gdr9ֵ8Y~?"oisxxY!ϖdpؗ,i㭗7m|ޢ(Kƹ}9jj ku29gQIf,>IϘ4xn(dGfF頮z]׃,₳Qy/+I%h Gwn7oF1wl|`6k#;,$gLz,\`dŭU&ۖuIU}$?iNp9vFF18aF]wDxƤ\Æx{fX:\LKT8|qCJ9%?Ǎ^3 x'&!pCy˛Ig6X}˸2܂HRMy :w8DtK|X;x*9 76/9m}8%T|q|DȦǟU-yHx1wtEˀlH/|l< | bb\o?/cKGʀCP1sdc T._\Eg'ְesSţGmy"KQ zҤg(Kp|.JA[_9XDSW(멄>xh~~8$@9%,CW-oJce"Gs@'Mڅd ExGu9O}(rPYgڈr|4^5ƚHRv>rtȺ5ORY~橼壨t+@'N5WpDj .jd3hܠkyF}Tk"I?/ =8#4t8w|hM2sfmtz]g | H;ٗ[ SUJ9*L/;'O&17-^˪~ٜFRYLa(O|`gHpCQuӕ 89 ctmM̢F ohfs 5BכˏN>R}=쫁'N}iw CGJ@ݳo])EO獭=Ǎ&'xh 6؟ܷ526lOZ~Hy=c(;pr?m+x@YEK&G}uyc*qy~DB2q%+TpmBRYʫ<WEy"'JOw 7eqP{xW,n_Iz \!`҈r\8154{pl }޷_n L|Xɓ"'h2(yљr(b29k8aF?dk"IClis?j 7ڑ vV3W6Y|c9"Gkv"IC޵]<;l dUY9c^Ѿ~GDT1P!_MQ<'7AUҟ(z9HgXkqxj"I{༇-ܾ\*Rr@of7&qq @ihn@ЃQ<|M- EQ&,Sˑ?ǍgjǓX޾{VwwY_0j,/W#^ |`!fBt }(\SOKk/[Ѩ*x#gqܴZ$k"I/?okX~Mʚ2r4NY7^|ד'  Ex b }=i*r@*K~ͩ/`)C@ixnGd?Wn՝A)W֖}x~#xہ#[VT;}])p Jޤs&Fs@@#7=3P|xOTf.o+ԔySh?ˤq6SsJ˃9&_Ά-}Pw|8:w8Ϝ~ X ab0!pvTpSgsoׯ~]E]: YLy 27h]$G6ts٢VnY#:'I`г;ś+uynXL~ #{6˸e `1ub5oڿgs"lNq<=4쫑_Yʪ|q|1L+]\ gb2aޑ&~pfVl A*9O/ 3xz"mNscܵ+i VݜWMCYǧ](G~n~w{0Pi$h2q"e-\X T&á‘@kyA#9nz-e!4-ܵ4C*J G]ȩp=K}(V 3ꬋHkIsV\%4e W9/Ø|ȱ| r䛁_pCŪ$a`FnFSKSaUxC9S8ebR o,oJHKo?:m0*CH FGʑ? ԓ(EᆊόA^h3zjwZ `Q7=&p>2?˚xhCo5Ԉ,FCF'P85H1Po!\}`tyKsFXlv*D];38db53GVXIY}kda 7<  1:Nn* =RnTF!Q?Џ\ڞr:L3Pu݇q;H6naYs-hGP#:T$yہ#3gT|/l~DJcRr4Aql[f]eqpesOLY;`;t43GVp̴Zk#i<'{ p}s|Z-gW i('R. R DSzqI{Ee**t2bl=y"sFWpj$џaі>lwof (sc䤲TU'9c^:i*o.fm^oDJfR4`ۮ3υ<ܸmOB7ؗ~8<) ;%+^چ-+:8&MBl5fN==yR!}p?K8 QmsV nz9|LQQoFs,4=Kl >C*lRWSI3a\]Y!U5~ɔ J| "0-`S ܱt*͜K* }M3_[ wYJZko-},k۷mb`IۦGV618`|5o}~BAɔ pCF~5ܴ *>wYȧ˾c*W[fo,o/kgltOå'ѕ >4)5Lj(]? 8.pP!O՛6^N),#y#PS`jC9e{RiI+>vFXY #3gT?z8lt5_oiOT 7gD @yrz2l졵+,M3,LTNaSS蚤707?.M24^إByw|_p&)S㶲R~.t dȲlG` p9eA~Tę;:yc* l_c E*:ҋ;4 /˛.^R{Ur7d1~t%9d$ٚLYG nj+oV2 ;H*2KC @EX͕ZUR'lɣ+x>1YAE2FY"F¤CK-,[ÃMd gh8'άgNgdU-lJpC5#> A?}%kHeUO_O` >xhO ~tͿOLLVs`ߟ gx=D5IY|Ru!>VOzFf!*>Q@ >uF~tf^l/1NYϙ7CGň KzL678*5K9&nY̐aQD1.K&K-xF`!Q@l e/=68cΘJNG̑bAeA={hW=΃뺂?q3s %dJpC3s F5o _~}i_QqѼqF+^Vڥ\D+~־ NxR1 <j |߮RnH{|G,Drlབྷb.K/v 1T2QSkc8eVQYrv4KPWrP]'MG#j /JpC+esp} ,>ǮZK*k!Dlp8L/O-nq^._uKÌ 9ҹuG8di*ee{/pj 6e!$p 2S S=Y| n{⮡v d"Fy"ieDe#FpZ$"ɭ}\gf dORYgg,L33{TUeB}5ۀRnHC#N0s#T* ;3,k5,}qϨ; 1(ǨH;Qis9vzGOV M6~Vuq6vHess ddϼoUBQ΍ͬQԕ' a-@'W4&Yz-ۛ xFݾc<88 ܧp̴:f^UܰUmrs^fG{}&q;gr:FW' 9h~ hnHC 8Yv]YZ{Ӽo+*) b,QYO1)1IL/ =sq+lasg =,},=,&?/],b|2ŵ7OnHg*)sxk+œ[8t" 4v ;XqԖȪ$'L㨩S>֮X| gkHRxFo#2_ç]ڍA?wR?n?qqHPP,NU2N,jԖ'8xBy UAKes,k6jvnk Ö4tgHeswg$ υv;Hj~y>?9+] | Hy% )\}8N+Զ>n^oXO+=tԓ+ݎaǎ>b'\w?vpzMYfkV$eTuE=??4,+TTRl :v /r;P35FUpޛr*SR=ђ 7 ISB|mE_tT62>h BDQ4"[Tx $5̾cQƅRdh/'̓뺟}eۺ 7daޗ v"H?ؾ+I9硆)Ԉ%)9af/X;,p=`!OK"| 8tT*H;v EK> 2AET${Ohq՗ycsv<gY-4jj|Փ8fZ-M Fָɖ 7o\ȷ;R,7oOKUYE*~Ͻر0ޠBQ5jP;'N鵼jJ ɢ~m~\<ɖ_R$4o>Cv'/ްk*J1+5I~V˱Sk,\x hK RGP*_ h>Z_|4m3$饤O*GT#rZNQE`=[2`'\Ґ>LnHS8B1эM4o郚[JPcԸJtWk)..<钆]>TqY;l7m4T% 9$Im }FGIUfvC[_Ob!E[}.ytBOƐCTZr@:5Y'r^7BanH[# dlq+n :WrHX@6ܩ|c8tb5L-W{5p.p ɗ pTo^[ /{ %0M~)Q!$Hdw>8~Nȡ9aF]1߀+anHg6_ ,62`!I*l۷sNCFqU>Vm"5.!!I3ܐ ^It4Y,// m7?Lg'BVT8RYP7w4yc*С!/7= nH$6bzazkSmlqH=㒤,&UF0oL%8tt1;fky$TBJѸxa oŭe`$I۹&c6qZްoc1 }H xT%Xrd+qfd1rHB@dm̪ U#M)WFBfpC*>+nG60M7 dn#+INۗrLS)1O>ƪD1~N*]$ETT^C5nis=#{x`}7=/;HR&(O)?g΃F«gB5$ET_Nf lIsXt/8$B&GcC9OaޘJ>1SY x=oIpC* x 0X_m暧YMD%+]]9fZ-9rlT`p+P$`nH/^GUBX%`c۝a!Iz4r0}T2wL%1T> o@oIpC*]/>/{[aIkxz[KV$ds0V2eD9_8zL.Jո`Ӿ9$: ?wohO[6^4֓~9CQ AMu]I3QcK2}H* I@ Y~/*JE-\t:Ri 9$ ɲ8[Y|cاԪoIpCҎN&Q8vu/ݸ} tThgiĒ1+L+cq|11+?&T 7$1ÁHqm,϶]}_8Ci{sx8ckʘ=7k䣇)ժ|gQ$; I/xA?ÁJhCo*ˏ?ړa[OL: 1gsHR#,$8uv_:vUeRLx7Ra!T>TRbOos׭ɭtge+\PI mkE2F]E}9uN_>n{8Peq 4 p%Tdi$K߽mY̦A$i x~$-N tLq% I{i8Jhgܕ⻷o%lL1͑5萤3FUpZ~זYgdlI$9' 9` vF.\#W/X q(-_=<_ '`%n1ܐ4y&]i9+7nŭi H*ʲ8\^6?~$>#$@Iz ICB4e.?=9FYdsd?’>2l#L% >F֕qw(Vq?+ ,..!82lHK?nHb%a=}[x`]7)H ;$aFHƘ3|$>b\fc 4^k`vπ B4Y> BxTh992 M|q+[[;vHl.1vTM31H)O"MB d!),q 8bY'/%'x`}7}gu$bOIy1dAe֗5FCy"Ne2FϨ`?@ʲHҞ3ܐPEʻwډttܿ9H wV}fF:q_6W̌dB 4n%)}0ܐ|8_T@ l5mP|࿐=ӳCvGgff3qt%YLJ^1i'Tũ-['WQ$ICpa!)*ey琶 a}kV^Iwk*8E\>D,Tę36AE"FMyƪk. FSC4 7$EIK0 ; 29Z{t dM7<½kmdJg~,Ԗ1sd9G_9Dq*4T&מ[`ד.JҰ3ܐEqUo&Y3ɱ;MgLufn_Isg<,L*geA"FE]#鵼qF8U#r^.=Ec!)F p%s:SfKgIgsp7.kgmtY3;xH^&gd9isPNE2FCe *1DW؂HR^nH*+` #:uڝg K&{mew}Aߎ 9 `c+9dB5'ΨT$bT[SƘge wH`!>Bq[ε,C?ckwmizSYR.i5,KG dOn?21 &'LՔcU$Q`lM۲, z!IyH0ܐT-j˰+Xғʒ~Y;vxK];8$ =]}6FvW&h[`ˆ2NYcxjҒUI&d`X tZI IEYF|tleԙ7M@x,Óz}e'[CZbҖJ#T$7) U;E" &ח3:Ae2n <\N$)LA1x#j޲ T4kh0ɑeyKybK/``'GUqq9I&s&XUg̨ *|bMgB]DU }NnpZ$ X XI* T`2a4Mi6wh0Αme˛X f~y8CQc})Y HPcQ-։T&L+cFCX h,$ I`Pl۾S˦YR-[Rܻml= Dt$CCeQfl{,0F61i5qeT$b$1FV%PWFMYi#*i#غ XZI*lJL%+3Ӏc*BVԝ7MKot&G_:Sfyz[/ݩ'DlO}m?oyAj_#GSA HH#I2#Zˀ[mchHRQ1ܐTfo(ZO@usIg! Ϯ~ w֑Fsl aEz/_5IL+crFT%8zjsV@2 yc*5du=|CT 7$)plB&(@KX5;M*47M >_E[_,K^{gc   n"CTre"ϹhcQSgA(I5Ie=*Ie2$jUHT}3n%h("f!Iw A#9ytteϰ- akW?G_*gw֗`ɶ~{r;_7S0$YRe ~>c=ITJ 7$I/@ൃ#<u; JL XKd, 3-$nHv;8 8 5FL`F%;tK,w$iU I^(#<%X2`9K=0gۃ f` LK]c!I CyfG=<II9hf^,wl*I$)Lcx~Q حeǠCQ, n XM0c`$Ibп{vwuMG#-RV`,'IpC$*=gG-AшV$]@/Ax̳ ;s<$I FpCTI ;sN ?F qgvsAp&+:yvxm0v .rM@$)B 7$In{=1`\ @& =@jy& y&4LZ<6\t,=Wgv0w$nHJAgϝ2O d 3BƧCiI`yHz@дs{`EЬsϟm}e .$I*2$=c{Q6;9γÐ:`k fk`)R1+wХ m(C0kkVm!Y* #Y;-$I*a$'yxfL|'?gv2ؘS$K;ދNv>؃~AxbF$)Lܐ$I$I-n $I$IR!3ܐ$I$IpC$I$4 I$IT 7$I$IRA3ܐ$I$IpC$I$4 I$IT 7$I$IRA3ܐ$I$IpC$I  X&75 `MnkrX&75 `MnkrX&75 `MnkrXZ LIENDB`OSCAR-code-v1.3.1/Building/Icons/Empty-16.png000066400000000000000000000064771417327530600203470ustar00rootroot00000000000000PNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FjIDATxڄKHa?3^g0[]q!Rd]6EBnFAmAd [)jQI-,LV椎3:3NF*yY9$ !P10R`+\jJ)4"-MypXe\CDRıQ;HHxyT5f0>⳾t1it^kXLY^OCw3T<`wMpB`$nݝ^l&?뫈LIENDB`OSCAR-code-v1.3.1/Building/Icons/Empty-24.png000066400000000000000000000072661417327530600203430ustar00rootroot00000000000000PNG  IHDRw= pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxڜ[lTE3{g/ Eŀ\b|PI QÃ5CUx4>HD ј`$BRjڲJ[ZvsKZC?}3(*ӍPhDV@5PCmnO2˫Z9gNa F-&u3"{F5D/z6G 0nM8CcKwg?(XaŜyD#vm /je_e(7rB=x BmlKiv?XZP=Lɖz'4vQ'c4$zn`<Dےf{3ضVʫf6jIENDB`OSCAR-code-v1.3.1/Building/Icons/Full-100.png000066400000000000000000000162171417327530600202160ustar00rootroot00000000000000PNG  IHDRddpT pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATx{tTս?g1̄! Lo`zqU*,*V\X *Ihk냪իGuMA"P$ĄB&&䝙s8s3<9]Lfݿ-H(LM0J(F %daPp}$ H1@ӹ<@')hN6:2`f3t2%$JG>T 9,r ^l`o_>%Dnxm1{t"`fN5;vth Es  c>sJe 2cLRFb%ϘhQF$$I{XQ`d@(n^5g|AIm \64 |d4PIͼ4s%e)Bk9[G̤ љk0{yP kI񞛾o0ߕɈKlBGhQ/%pnҩn=K/! ņyh78KU]EEȋtZ(M9CbO^_I(:Ȫ#ӧ*ץK( |#M]Zn1 sv+ 1"%Zy4O⒱mf"EZK|pxuq5dĖ, h!.4.J˰EKTgQ[`ܯ'q`Ty^?n!ZKvJyObhrA)& G:=%qğOMy%M;(!i-!Byn⋓$j몠N qFj(,wӬ,̱;cvLMƎ1KzJȭz&/T/n!*vKNNʗeǰjg-I*^֌4؈ʺ^O IтT{kj(ZAт I1~b3SXj͈%eDz^Br>&7xEm;y;QP B#g'g!㎋^Q˽<b8biꢦviVzUr)~$bZWi#iTzegU-ŇFJ[p$P0?@eJcu!+> aT)LSKQ~DK(.k|Y_ZLhJ`~t$2EOU k}c /˦ⲓj#Rֈ)~jΑU; AޗhZ35U#0߼$v[R.E iLhDQu}1&Z,i0Bqo**% KՆ-Z׵[D5Q%>Ȑa9[D5^R>;%dd[Ca)~ LJ|{Yт ?c:557+A5%Z>Ro9 x+ί~\nu4d2¦]O;-"[2(\~3}TRBIu_e+j).k`~_lU.}P[aiJSqijJbDtZ /}.ѓGwLfg Rr1R-1W!䀽{{/ʘ*a/:ӬŢWkč΀//Vy/~?3gWߕ6:RFÄO8[Iju{%x3tRyZ{++.UKkh3zlHܕqzQزec9 UYYH袋8xfPlݺio\'< i8CsQ[[kH6LRRRО >`)9{cPdv5mT)֭c$ /]%=.|\X|4+ 0aېz ‡Ql5}1uTi9D]kuIiBlЧGNvl>-co؏$Ik6{M'O@G|u޶68}ZԝoL~ E[S~νTlg΄W^BCsK3P+nj -z}i~;`ړ4QQB>v:]c4_ƲeJn#}D xn(-5kqlܣk֐6q"ޠ7!ŋj6Ȝ<9?"#MO=,]Jڟ_bC> Յˈyp' IL!] f3yyy!C;g.QcE0inn`ӦMdK8^2T7:$a"kX7;͎}aUn3NCMze6g"e(ViIH@ yþT{BEoGՕpzG\9ޡ5^4l/:~Ab(<䓺hU]&d O<~ޱYORM{2j}ȃoDoZ݈+x"K4~QDy/G\AZ_z 4Fyօ$D/r\$O۰aCX `W$ `^HNWM^Fnߊgsv.F'e bko1$%?j9Rf *J^.VQ!/Bht{N@\:I OqBݪK***Zb`XzOiSW 0X3&%u! <#u] 33S|bfVU5HP\|7D7n4=CN$Ich+o]͟uc#Ha\u[nHDs/imbt#kx>4R>?iXe8S@FV2"\GU##ExþӀf-gnpHH"e[)pLwd'LIENDB`OSCAR-code-v1.3.1/Building/Icons/Full-1024.png000066400000000000000000002731451417327530600203110ustar00rootroot00000000000000PNG  IHDR+ pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FkIDATxy|Tdd#B $,q!Z]PEk[m.X믥b{[ۊmエVkm-]lѠZb@B 2!$L2XT993y=]3g}D-@  @ @ @ @ @0O&%V%es$$$$Y쏕4StI$Mc Ò:MYK./eIJj q\H*`7;KRx;kݒr 4؟8%oI/ {!Eg#;#!,)8FۥUqꋏ̏ ޳O8f^95(~` +f1cH(pTY#}Mҁo?&D1߷_,CvA} rD~d0?2x3/fna$ n@R޸ a5 |Lh@P F{؁Ƞ30O;]lxA8tL7<H8>GW +$~pk# tPZj@8 ;O ICe=A8С `OҔ7 c#ÁW.`@@HR|.E IW i2}^E  VSs# u^IfJlH' Ey_ 43: rcv0ya{)QR@J`Dѧ#IO4@8 ":JH i("3F?2P'ߗ!ۥ\#2 a 9]ҒcBvGUtf˒vKZ9@@sH=y3 CBC.9U)i.? Z`c,^ PW0<?ox`P8!E"Rz`P/ýjᾰ"vv` L ofx3^\gi, #feU] IUk\^VfK.)ߓ!E`pbm-xy8`@|^`^0< ugZ}"opHpDpD{ҁ> kk{Pz k{G"b os9P1Y*ɔ&)ߓ1n1pT KL+:%mԪh_%@ϐtl~$"OQG^#!=2׎?ѡA و7YPvKӋd,Wcݚ31WU>xp)'+6IOK4,ikg@HC[Ryg*ڑd:?:潽sm}= gBāi23\y*v(WB[L deK@#IHE @0:oOs'pD]mk]!=W{^LvklR\,eg4&-ۑ  :iLI5wҀp(@0퇂z_{@7#a8q0ǭUQќ\W,deu'dKF +XՑ@r^ tuS%M"9i_zBC۞ =G>u`0pLM*^jfi ˊ.-NI#0ށ!pW}jߧ>0*r3U*G4G3J@ǭL[愎@W g˦OaamlVK['ڌ} J25el|]T[d8nvH/^I_'IH*kDkW_ϼ֫M_W7kK*vk|~ +u^E&zUM(#E6~=@ֿJPҙrKE#Ã_i؜73C TSՂ)+UeQ|L H*IIO;\+7͖ ztgP~D϶]=`Hcݚ^]?Gҹ>l@0^mOGSkg?"וej8.©y4>?K֝%Щ謀[1 @6V\IgH:Kһ$X헌DCڭvvC<`73CSf뼊\͛s&4/S ]Ѿ/Iڜ0؋[sF~4jA=R@O==)  t<]<@U䩢УL=*iq'@8ѠE+i,/ k틝z|g۫mr{t40Ks&|ͯ.dGGÀdFQtng ~[־ɀ%ec?_JR{ѯ6юPp |[3N k,?2+ݒݮoE*%tanTsTGy NYS.ҵS (<Egq"@@ 9Q_a*;5ߩ?ݮgh4ٙ.)ѧKj^o@@|~KM{sO` Uytq܅^pdV%=,/WY&HzS6ͿC?A=ˠ,,;ӥY%^}R}XԄK$@@iIzyr{I?Q'CVsKT_KůCIzYI!^@-1mZrB 1|^1m>s~.,HvI-~+A@S:("]u_a{?8^EG,.cNZI @@+i*#{@ڵ{PKQէCg+'+C @@8~?OgKNw.'+C h Y#םe;$m+E@@8aᡈZqP?ߩnN|L>T[OWL{2c;%]C 6sc%%gw?j?i{G?/,KM7.)WMiJ2c% `|I(G=5@@L*gsKTQIF I_"i?788;j=zP`,KWkiVWŹ B`K^~P 9@]RϣO-giXَl![Ew* 9ϓ4EIW(I[uOsy7&ۭ>wx5!'AFEw A s$O%:A}jlA^detAe>wx]X/mHZ9A@{k^"{%IV'ϵzMM;i}]\wV1Y޵r3f`TXE^iZ]`?fcok8? @<*5 Mmvu2y -=H_hoj `G\+7S}8E7<7};A}{- S|cy.gF>unN);e֏ )^v pETo}mzx+KK79_-,y0G푴^I/VnQ}P$p͓t%-Thz~>G(< 타9幺rO9Y$#׮_ 4URITn]m48fzO♅cOKjA@@Eӭ6~WKNlI,D|NЦ(<0el'}g6G J7ps+}P)~B/ʡ EctEmJKIZpօ'i(?HDm>|?uZqvS$XҟHnP_E"ڗ:0 eKEs'%}O&T.>,Cj4w76`s& N{f*/H)mA@@8VE_.=S_kHK;1O_h#ofƒ6 d@)#> )7?㑭}ѽ7ք,eZD&$7S.|EKO,+;;0co.=G=r$!0.܊[eiS3ATm8($MA@@Jt˔`X_yt~~ yr24iL欱eCq" Җ>=WCSTyt %[ZT9yISZ Sl:П8&KJr4īE 4wR|^7Icΐ~mѭ^ f uIu幺jՔzz["i$=ZjHE.Oҹ>*^ n]V;2@Pr3UY9Pey6ىށ`X[Јstք͛kk2؇%BMWONq%xJ›n%I/$/hnI%-Iʼn~ݾM!fehvYΝ+OUt߇;CڼG{U!&.ict5m):l7IZ()aMݺAv3M1(Ԝ\[(s( [l hck@O0(З.%ۆ%b67\L{ z( fenbt챪-˥(H -m.m٭il=):;p̴_ytr[MwM%|Z4@.EA{|gֿҥ'Z^ 5gO[̄}.k ˴Mݺ;uX ]:m=cOiRaEN" ێۻWU*JqnV/jڊ%I.*r2ᾰ޷CMLcŰl-׵ªD>!mգ p=s+OӸ܄MP IoVn~*N`Mq%CSxic᳋Ƨ͘Ho h+]S8VvK__4Q7_D}ۧ%}NfNPI:WrI+AOݣkժ~Ο9VKiBA${^|Hڥ׎039>G|h*}%c/E@aETugvP4uae;XKf$/'E4c;^j p W[Xw"% A@@\FR}GK4K;}}nn:Dy!fK~)cl_]o"IcI` )O|iu9266 C?;ep˥OW &h|~Bv+.$E\җ$}B zܡޭNY.]r}r)˥ ôC~a{UEٺjWoǒ6)(oԫ.vuOl2oWB14nK@m gt}efg/9 YՒqNA@@]>ΧG <lO~DUy(m =<tsU.O쵐_Jk旨.` Pa]fZz)MQK13՛'{x2T>0:n>f&s3xU.QogI$Hgk$}^ҔD|_ة߁&< -Q ܡAmOŀe/U)I%0bS"3>+imЯG(Ü;)O]_9e9iZz'izC4ě8xf\h&(0Î#旭qtB} M~uǓtצ,y33LҧMvV @/2UM҇$zs5}! <b}ua3)Z=@1`{oT_ORQΨHed/Jzh3!^>qʠ ,/;O{k*_^S hH’-~dj7[UʕvQּWq־I1`99Y+gӒVVn% \T*$}YG4`XW|7m.8ܯy~< a{qI6F?J F^tk&*K@rA"?$oo{zguF/SokR"N@J<WݫG^,,eJQ~qMNʓkt%,鏮YwI@B2˒1wcmZaCgLRfr/u|] ݾ\+QuIVnn ?k^(My.-鿯qtl`Xw<_7".g.<]B~)4$ F. 咾$zGQ;RicWwOl 킀}#pΤ<*,[=%ikJ@xLMIWMܾXncʿm[_6YLʥҚ3y/A, K?ZR% GmixHz@0%?߮'oKEΉ̱|{)t鶅'9 ; / [֫K~Uzo7n}2'ڭ/qyb >RW\>yHG\+7*} kf.]>P^U=H {b eMTk3oFVn~y :IJZ"iTݯom PX9}|\Y*(ĩC[=!"5 ȇmGݷϒne81V5>2 iOi~ k#%y;&sQ H@0ئo=qb %3]UQa8liIOҨbnlg#.}v~dr2 Ƈڰb 2\җ>A~~J!#i|a(Qt~b.^Ԕ7{V1TyL7NiI5c~]}N£z6I?4ͥ ;/fu/vKKQm"+åokyM)  gwMTO$׃/vһpߨve#1Pw "itzjm&R H@0f~R'@Rd>;oCߧ'@tK7If֫<.Пo8?N7N׌ CںTíGFmJYR* ڃ I >Ql?~Ew6F?=K>b,.˷/ϟ\ ItϷͷhOBOI$>{h>n .'+C?|piY&t^:"b )-[۪ظ_X =vDXD;$%雒 CWS %:mNChIǗJg)ItP`7fֹ?z x33_WNc6siϗ{fR (kh%DR=%P~}PZssJvQ)׭?8E7~r@g:zI+PjHKCRE;u.V^\giXzz|L]>0һ_O HCHDҭh㠮[1Yj^>K|G.n=izک*f6ռWeĽHCHs )V}.Y׾tΚC1k|3dbT{uogx%}I @7/UI+$wSP p*MF|^]7Uw/Mv{uIw#fIc9s/q f-nRGx>s>:esgNEU ?}UO::I @$n?A'7*k\p^ӿ]6P{x h鮪ȣ'>6C-,0M(Ÿl^[dKZI 1*I.c[z5?MW,kdxUY8&b`w^"%$ )3?f?++LXmY^t]W[L1`HDzЯ_RёӨ({46w )֪)yU{ih"Z͇F,}BI߽C70BA->\[nE=[6X ,R $PD7>K}6$OyHbZTfK?J\]E1q-ӟK1ш\OAOY)BjnI3%5HZY֯{;(EeM3u:Om M8o'I7Vn~j&|@NLI?}doa5%^3jٜb=| %'ih;%}@LI%/vRIzόB=}LdR @ՖjˊuΤk 9U;SL dOgQ @׏| MT=k@q Vm`oEr3TmY^t h=gj('Z 0q*K]o\H9׭4S=b aBndxg!GӨ&;;ejqP j何 ?[w7w'黑Tp[laoQ;giy`M,nইw-~$}5P磚 w?[ QL Z~^RrƧ>1cW9%#>ic %of_íjɿ.,.i`ezKgifb a!j+ITJv4lg/c׭?9SgR $DW014]J%A;,cNo2<^PB;bl9 qhwL&Pw:$ G;[KOJ)YZQ$ľ#';4ͥ ]%>?9ߊ.V_V[捧GY\w/HUߎxqN 8>xv, DB})s%IʍAŷu LyY>u.@z-_?> Կe__$HCS0A /viS!@>!ߛ#) %)/ho^bo[1og M10*HD7>K{+ޱrvR.PW%i)ZSH   3US]0ujsm}>VHxύu?KIE $׭>1SJ0:]t[;^%)Y҇98/?BZppD'əIQ:;ƯN? EJR;F LŷO. x!ӟrov?ϸHCݥTH?O2I?[7SH trpO`~KlگPED. {m}Z҂k|(?H;د{k{ I?4RE,3%&-m_QUk8BYO>[钾iH%A^*$}]yF ú旭eo93Jzک8CM( #zP0/HCJm]`iz (Cf`a]>0[& @B%$x/wƛfDcs2gjf?~T۶CA}]Z " us"0XI Ew36U|ܬ CS@TyǦ++?6jIi;* Y{=_>BZ%=WO1Hڲ\=ޫG^г$i+*$}^R;?F6t2]6c H| e$}*+b4UIZ)ic[zwSD z챺ma9 !ϯ[OhHiܮξp<呆 "n;C^Q Y͂|}܈,5>ݽB n;;9t5 N5/Qt{^whϣG?:BdK*)ЖV?ϡ3E?IZ.)U|7R;N:B n_kjϡ ;ϓL zzw gR RWLgPD~M;3\iK cٳTb\V\4^L?Vp5wR@\}a]_pc%I7[.p{n}$$02 4K!vW/Cc]nXgHN-v|~)mn=ə|]J?dݿ`XܽTun,l׏ @\"҇ݪƛogHH_qO-Ɠϯ2`iej tCt?lg4}.h1.I^4^N?젾Ƨ;5B .O:R?uyS|"Z+LR`hwf@\mlg\րH[z_)M.9M?E3qG"yܱ~ic .u`XBb`SLϢ0`Ϡnzx#F=Gҗ" uH4ԕKEqVb(Le:"b`c>[\6G>s0CI yI_RxO_?Be)?^H6KȺOHz5v |"ZPEG_A8ɲ9e #;,~Y,8QwM/0lۡ~}1w$]J `sY{^UOV ɬZe4q!mO $i?u_>="ZP]yV-dj N)~0h("-w: /)i%K`"ZP^Vv3y;te4qGu9ĺqIZ*U@X6X>B_ڗF+HCiT6i iXF[ u͙E4&kVBؘ "}r. =toE|THX?a`|^~xd\tF?vwP?C/t#$nI$}FRcZд"Z{5i XHWUEj ֿܥ?{aYneWX[Sĺk@Xc @ƧR?#F-oJZhݿuUo,H!$UhV `A}z 5 n'ۏPD+a]RժK m1@FzB t Noi՗Yԧ-%Lo+WWQ2״R$} I#a]}N hQ>nh<Ӌ᳋) iЧ[Cț" uSAEc/5mzvIweN*4c`̺-J$1Hm9>-qM%L5 H7v:zH'VI4r[Y۸Lz bR[;WP+yò$ic*$yteFlgQ.I{eN+f?݊y:r>!?|HO:jIVP=$I7K24O|ݖYEZ4B~j5 #=z I" u>ha]V hQ vS `XUG~0fK{ýJ$i+0wHqLg;;'9ĭƧ+f( my`mFBrI6}]]5I2īkN/`V)m?b 볿MCciH OFwѽТ23\5 bQ+qޫY c~r@OV.m>+ϥ ak|bB fCkw3n.PWE X?YTAvV{ w,1;;uM2P>HVt‰VM1@nsU!_k2z$ͥzH $,)A7=K=![$GMB.K{epۿOP[+}?^X2DnO>2 -LA'P9R{~pyE@La}iý%L-P>IuF[n}dn?^*CcW@tg*?f@Y~:'C|r 駛i(b谉.ziO3rܵjx6ɹ4cBX4uwX rX,h8ҹNYSk6>J,n|~>G_.K'Pv>u.݁^8g0fR+eOUD!@ZZ2Hi=,Gϗq?Om%c2 =,˨KF:;C g3 Sߛ+8hA, s%_ym⊙>)Hk73NS.0rHE $-T1ΐ0ų ݶw0 h3481rՑR,ZDҝO0zbXD=NlYE'wN8 ;pC%]J<_=! 㒾7'lN1pJ9>C,!ާ7.|ٚ8&B—QϟPpЙ4$9'<2e2)VN3[B3@8KRxU KZ2P./O!pR n'nCOۥwC:ptg+/ZqNٶ^r(hjIS9T'/] E?@|^>|v1I #FgFHZNRb_XK{vQ^t0E전S+]9HCD*G .7rkm[8-qr@!g 4.ZųhZB$S83t9e~zcD"W H:w[{ƚ 4ΐȜIy2fXVG3@ܳzl[CfGꪨ@BE|.)gë|bMf?)>9w< EpbCe$]Gm N/Yc%R(s(%0MsK(N: ZHCO#P[66w\8@sr)>y.NAmOCfINe 6Tf>pfJ2);8P82VR=#HyΏ[z=Hlě"f $Í4)1DyY Mpbk_ԞC.^æHo䀟Ui%^k:R}g 'Ȁ^ 9ĢN1iiՁTf n]}BXIJq'>7rxE]+zUl͙G!,r"6U$!r'3E|#U㲕2|^3} :6r}NVXxݖzBCTf.i>\bzW)@O3c=S5XS_S.pCz!b ̔(/Zb'U9I!,Y>zN ~'U#8$Ub6l1`U_zxz|g Ө(_Ч+U[bfk 6D71exVYA2\n(ecܒj 20/ήCY;zp!=! SbZvt `C #_= =M4zvwM/8t&$M"O([-ĉl=TW0K I@Rf,b1 '?^1r; ҋ{23\b&vUSʒNU8ѣ -מF^f}:eSn48B6CXIc L{!Q6uͮv2zbh;Nҥi24ec(T`wePE!=!=>.#Hcd`ۇ!mi򎲱(?#\:m E[ E^#\IXxvv4q%p}ȗWsb_dcET`JEqmiS$H:7?̵r u+ &_dcxf?cn?9_O>J1CzWssc,IHS'fD|fxg)M$,UppHCS}28S;bV>yL֫@_?%O`X׮@(_(ڿu,Gҿ-tdOxB-6JcrIItM:3%} V Wn!;CژplW IþVmYjrTUڲ@\{2t4}ߑ&^5vku@b~ua)yi \TU?l_A<ܯ)cc @bg*Qt @̃ [Mi232()m $~2wmj=iAujrypg-zXIPҷLIb ;ow +7dznK@Դ{TH5>-SL]zEϽ=!ҥ6̔T{ ڭ~ tצvݵ]rbxX&L,x{|XI bxXW4.UJ2;Cjl>掴xosm}a_+ֻU_Ӫ*q$vqaU~B'l0t^tz4Z"X(s)4ڭek 6 U߻ ĒY>z9C]i f?ۛ0ƣ̌ ;f~ր5.bF[ }]!C]i]<@EiCUw'nU-[W {&`eE91K!Yݓӡ.i1 P%iJ__gxicq~nK@+IB[e*/'S'j}ߵ) [*ĸgqEZ17s5v|®`XmESǨB t /S%SO oc?o' ֊{tO? .|}OQ-UmY)`ր鹶>sqfRA?9/CvzM)i7;؝ׁMZa9ILUG+jżR;CZ;Zߣu[ZwQ~uڸlzbu!m0pzM0P疁wr imu v nݶL?SK,1?^zq_8Sw/R/7v[˛;wREǂk>%M9`#\P\gLpY]?ؒ#ԪEukٜb\0Ow HK ުy`$GL`sc?zw\YSާw NSeyV-*v׭UղFKfaݰ֯՛y"VP ŒLIc]bwɗm5Mݪㅤx=hJ'3fJ0/Ӷ.!0Q͖D{HVӍӵlNqZ>[뮛|VSŖvCz %yZtbNpuw:_%6Z֫;^sI2,GM7NWmYMk\Zƀ$eo3@.r2U+/wx֫IZH*C[a@hZ" v׫~yK lfxu_O@x{U<6hܡ5)J_t- p=ji @cu{,fLIoya(,r;tCևg/M7H]ݡ@ݒo蠑C&3yۏpHqEa;=Hcdiك~&QA$b4y$m}M,̛V${*'@+M|xkuxWT:85ȓT9&8ǚ;4v'u/^276쓿3D0s(կp3%%rIcv8ŹrUZi߫WHϛ]Ջ+8!Nu'N]%1 ]XI#@Ύ~v< ->J%OSRnNkg(i[h\ZEӿ4 ?sck7[`&o `FNS)F=S̯*PV2O V}ۓ:ma Z,?s= I$9܇"=`Gn ΐ-!`aY.K>sm}IyrjQ9'B7 ɞ kS(>&Ux]$"ȝj i`p2Ί{1 &Y?k^X 0̈28r''ǜY.f[^%s6 5 IG0S/xOt6M4)/ӫGx'8DJ R3.)fD6f`Mdhw @_=2%X_6dK{wC<*N}d7`\J~.H<":uP*3w`wC,QqnfJ$$U<4K"f$`GL0cql.C;8n+L=4Lc35&OZ_[6gn]'?+VcsGJ&[K[!O`XT<*ʖVmYjrUU)4v_@%.F֒T[#׭S _{^oW1QI p[5:lj@0es+}[w):Wz4!b ^ xÿ_]1@L`OM;kr̯ȹscmox]x}V2يBt!j:± I0efe8?y֪ CY@W0 lrl_y҃7Mڰcݖݡ@k k^SZYm~(ݟ'H8ԱkTG_nP@;KIA&Jh,boT6[ T-/6Um71ૺ]yߎ4Wtڀc'8D>6g-鿹dMΣx9k(䤿I@XP;%وB[+.ϋn)Z3شӺOZUuGpN]nXׂ5vj>Un]1O2؝u9F~Nߗߣk%1HGsd 5!?y3ZSQU]Zz-݊{R2m:o hvG9pnfV)l'Ӗ\֖^̿nk %|Mnj> |,0{,ӊI URZH)VegHmw4zSVmؗ7KxȌ`io}k|Z<-+҂5t|ov耑/p+k3xXwnk@͇L  vӟ@$C+1}n>q $| W/C;8f/Il,")bT>'H?hTj>ÃB[w/RӍӓ>^6X/;W0iOWojׂ5F}_]*)SUG+敪ey]>K':@0$zƴdg}nY^I襲jþQó|3\MrIʧr;Mf9\o; ?ytS#x4r̂5t(ͯ.7NWӍ-7e,WK3ӭ~@R:{^aٟ]𨶪'8|甿 ;,O*L޲7OBϛUR@nY?,G-7X&ހN7<0*nU¨QS"AYv %m:4O$G*Ck׭g{;Fu/?)X;9OV&;P[)~MjJ֖KƦ襺O4.KbzozS.^mO_8vˌeKL.,|^]75gdRӆʵyBs+]m uiot耑/+i"5T( @Lvqr*cOH39M׌.Lv)v|֫oV9rgJu2HoKzԴmقUEgLγxA#\k0p22;͋zb i㝔WxjU>·k ;-`8~;AӾBM^@8u.I{`hI q;L~[f<^?qOLt#82m-,s?,f @Y ^mDGS> 굮 D8͙@㼙2Tw]>@~m 5׉F<N,cuwbt Jgf΂H3-YeY$;ѐ#=Ut=FLup;PfftNެT> ƮV ]:m AHC2K,=OL3'cNTO_@ZpN3׉3Q*m04QIj*m_wSQu>7}0ז]#[067S`pfR-F^9ӝx͹7vH:oQsˉb,SJyiu=9@hvNeۿJ__>aߙaf`%Mf? y0"0CZ"6tW ۴jբ2z =f;C?_@`ri>ٯpc&H7#e\VXZ>@;ahf8ucΐ'ͯ. H<7]D:x0}HqTi1qC3r/ϝub.݆ E$y2< M+&{HT>"R=$Q7Eϙj7=Pjj>~~u)>7wKT13PN{T)ʡ7 33vo ߽p R'op+-jڰ/kg?J=toVHL8̾}?[ Rs}d;/I*(gC#ԚJHMgon[XvlK>Zmx7o<' > nWzAcmYΨfjPnb+`欇HW; 5>?Rg`8F8+/n:ײmOw^`u\/E׸lZws XyjQ)YAY:`8K|:Yx3g{^YRrۘ4L rR;v $?fH@$adVAJ`X& ZOԩ~NqגS̹=瀀5Vf՛}'xkWf`@lI*w7$`}azON_׭Ջ4'[YA8&c *J70f!BQ2֩[|T熙ҬpM^;*</.1/HX$ H\~3pf;T'Ch yA- O|^zcmiw";fRiTSW$[Yad.bfC%G ~ )-ɶvxITȅia7tjX`ͶyrH5ceۿՋ'%g5Mڟ&3͚`'{VzgdӸ-V^\[1O Sg6)yjQyE٦/.]vhlHB[M7N7=!ݵތ}ҏfMNVmؗ' rsMR6$j̘?38Y #Ҁ-y:?ፙN=5[ƗU3gPZ9UJMԖrq;' iZχ;Wp*CC\'9qaDN^0}c\[k폵ւ-RYx$BSkI\n ǔ'V`756M t~uVYTHGv $#b N ΍vjoHۂzݦAoDW0>jr`JT;Fb?bكƫLf5J 8x}ܡU%zVh⊄ʹ {q\i1)2/f@Viuh5>cTx Do HW:K ]fajzXY3sf7HƲAgHܑ͇EZ62U'CGCi mP2$C؎TX~bIۺ-Slͺam|쒁h=iinf8ߩ+zqGϼgK[#e3d¿^5Uc󡄇3 lN#yAYzd Sx6Gn]nM7[*0;Lbِjۿ[&-yagb+k-e9;FfFzu `f6XB7;S;7e^%ZTԩѩ԰lN_׭Uʓ9eRLeyy™{|9oOyaRɬt_Br:vúu-XԛTj\qiUJf9%` |^ N+]:3H RI[Hu[)fg8:ݪ<] æj%"b?wN6Yk fNNu ;Ty23dY23\2!;:b g([%uE kmIyyZXqiUJ?0;m@iVcSżRӯ]V׋@7ξ߻nIXq}Iz0J~t߀[tD`|^֫:ѩmaYʗ$Y eI)?![*Hѝ; 3#KR.TOhB`TR>' Vkv4!@}ۓ8MN_ϓo(tI;Rڲ\ݒ&'kT ؑR(T’eT>ɨyRwD}O/95~Oكm{_NԸ*KfTĺêE;[dW k*<v}nK ]Yd oxkϟe63mmj'yd2{Us`斲yZ1d8협>춐Jھ 8Z1.3Z"Vir56M٦+>:$DN[r:;C0s @kk|_]`Z?k{Rޜ2fo!=iW$#& ɥrY>l)Z^Z%)Sh frh/8eY= nK0VG4`=)XX8Ȭ)F>ԑ-m)-dӲ9ZwTu~Vw.6t7}Zt8fh9u>RUy" - X6'RXf]sużR,Q13bGʵ^ kS$!`2e>UA YR:ݥIm023 Z=|n[X65|ݝ76̵bkN ;O2v{CL 8v!Fn#=Ej _*}tOڲ7LQmjVK[vvRӜa5Hu[ܧ%u`^1? dO8kE2v@r ; -mjj=jOK[Rw pi3v4qotԭ^\az{צv-S̵8. =N`WbmͦVI馶,W+敪oudVmү3gvǘ5ҖSۣgz{`e2L:ilHӦJӾF€dpMGy# 훥&z YƐ*h;3=>:ȀԸ) @*TmV/gB/۔w0sBUD:%LFx(fX@J~;,m LA)ֵѳr,5iɎnf._X0%aP2wmjUZU" rKʊ`tQ'cfY\w/n?#_ᩩ/ǜ xwx`cj:N yKʒsXIXY4w൥x:ViFN W,)ql8&`8 n3A";/7or^0i~uMkw߉ձVYjܘxբrgAZy["gN ~87 e) `VDIuMM֣tc7hfOM;Kic>+b~[Dy[TAm3f$F|þ>maYy 0{F2 YOSkwW>!f7sP}yTZu[ˇA$NpNHΐJZhh t}- K:#Tw@ZpOsGZ =VӍS~3tp$=yMF k175yRh6L &zKqCU ͟fm6.vi,I30ٜ& ɕܮȓxV?u6N'uZ\)>E"%'b~+V?u mRTȔA;^>_z$cҩn[hRXs-Җ/'S9Yʌ}<1(bvlN.ɬ篫U;ZG붅exNk(GD9Ik%C=' l紧3h(;ߊ ƛ~+C+f YqVjþl5,' iW܃œVyLO[v',Lu'GV-Uv` @SQSoŮݍWU3i`gP$O, #޴Y&'w'FW dL]gXnP}uNnK sS֬1JJcO/O̙FVlh-.H=P H9ne]Jv <8E`(ސ>4.JI*l' i;0E3x ̞>+L6wonX7mm$g?VbVY+d̢mzxOeQ ݦ?P$0.WR6(ެ()7 UE-H ז7m7}@ y[_)|pكP/c"F]T33NfnU>eIyՆ}<@҅D.)o1!M %Ijuxʃq:wG)-j : imo xOV%G-}9ضdllm>r2S#Sorɝ3񪪤fwmjgjܰekq?62x_:xNFzݖiDWhv` uȶ>OҷLM3̜q[NƵhl  t8}^wܰ֟!@2)VӍ-EiNV2uMI IDSkjEHt`d?II? |>1yϤ:`Zdm HҴldf(3#2nRȕ3,7e{s-JSunK kcu2˭NvNN;CZ֯l;e9 Y^Qud Vrd]6v ȣLbCR+B >7!w/Jixb`󥥭7'@)_R{ܰ#]B׀es%whl0s 'ŠS͜%?Ɋ{h w%#jӎesug7@0k:yYzbgt|T ֲ~SgX~FU]>t=Jtd_u 1c/Oab3n'f6'yь,ayFt4?FXGoX\W+M8sOsjűrZUu N_]k,7}q/ ּb⣁+wxٱFz$:-~O67zݺsqEBϫ:`wX2g}dz %r4? Kn-i!5،Ò~GXRyg*ݒ)ϵ[{k.V-,E㉷;sm}x6K>niU=X_m ˒3wB}uNG1g/EpfB;Cg@:hT;*'ӟb6vkՆ.SUU^g QjւT[5ΐZzڭ+ժE妾X1Xǝ,cȀYwyjY^Un"h4wU<Z%u[d̎H`I€ L9 W`X-m}  ܽqkGM c$ml>V/HJzS{{;b^9 Ka=)j!Ԟff*9I~ B$JF4IM ]kI1ͯ;뿶}b0}Z[[qȔS KLZC֧ѩZB*߻dyXԮ[zT^V-*O`W+ֿy{;CZ逩 [wX\5#;$3maV\0631@Kn-\ o98[>:]1Kt/,᳭d0 kSR>T7WUEٯcuگ&MMGd:s2 >=OiK8N^``ywwB қTWA/.z媈XQDb(") =@H雭?dI6ٝy{9ٝ939)-b]L"UY>z(]F _:d׷򾺢W JZj~]: !;QLIax9] Ք%r/ G]6w@r.Wb`7NG2-o,2O1k%R*(ZuwUh "{S 4~t*{0XSn8% bg1_ x)5:ybc 09m*f+A֜.nC|H}#^&ʰBnVMヱQXSZ:ç@s](RU+tyϋ+H PC$%{l@gRvA[yuP`Zk8x8y.SDLpP Aꌎ}NѪڕ`Xz4[ )wkpspyS1N5Uv;$@QzH{VnFyV\=\1d7Ep4C_CH +aᘿ#:-ضsbR|pmIɉWj(fZZ]Z =Q3J$q"V=2{ h7#„z  q>¯DdEg0Gd+#@?ւGKhIa/+zK+ +9i=1=0V; "eG{$ 9caD!h'{l @ .Ҥv'M(5W) wߝZ]iOx!Ba^)/Xye*w_% ipwC1ȍg. 1~~ToviVY,c4െ9E<(qPxpf] ZĠVY55UKz5!ѩSAH{ZveV_o7<6 ܵQ.][Ե7~BwD,W ,.՞_- Ьv#bP<:i$woz2\r洞v[rնg-hU['V 52//m՚Ia cn?AX|%n_yfx4CwH?bg ;! L1Y%yo;i;jً|g'n*/Cb}0"1:weV]ݩLͯ`AVy n]!m}U4Ĭ~mx|)kws0OTd!z*vS)(m\ MB2,p/D/ʍHͯjm*WwiW5ĒrJ /AAQxsT \XܴdN>_{5?N_gfI6IpŸZ|P5;AAjx)P W+DS.rru 2  2JNC(}Z2;aQFy< p!  &xdP>lFKvk(#^%%AAqCBV" CUݓ:+)   S q8)"H/G p& eee˃jL&C@@ T*IA"<ܷd \0?ATTT 33???DGGۛ 4ɔ#558|0ꫫKLL̙3nݺNȩ{|,1YH!rF#*++a6>>>PP((ph:gϞűcp){t:<ӈѡCR!#G~E`2qߘ6mz쉏>#{2H nRXC'n6Aee%`XT* ///Ȩ# ***pA9s/^hBբs:t()̱c0n8pVVV7xо}{_;w&vQFǦ`ԩx0qD 0Ռ%5ԃfbڨ`+,/\p%%%'1c !!EfÆ 8p-[^{L&1eC޽{qwǞ={cǎŊ+0l0RqCߏѣG{ Z<:===~'|g %EIڽPiUNpE |zDKm6̘1Ν1;v<_0n/_9stjbڴi(++ÓO> r7{ypf/^ĤI駟#%`ܸqߎ#(([nŲep X,D)nڴ /K u2Vr^ +4|\/;#~}g>n7_].@Mp N_~7r9­J)MRoH/wމ<** }G} &Wk@]]~7DDD ))IE߾}QRR™[cǎņ  N޽{cʔ)P*8p cǎa$:/&թpxX ]L+Jv{6d ɬ0.zow͝;0 D⺬Z s̓P,^7nĚ5kpmq1rH۷Ip8y$߱cGY; #Gb۶mXl' 3gIwy܈> }GW^y/"ovO}<g-O4:H,3#fFz7k}=,y`` z뭛hGa̘1`vy%CFF-Zđ6\.qw& /.]JvS~wNRTT/^l#Foȏ;FsMYcҥPw2dVXJřc=)Pi,Kz\RA!Mg8pK7|ӮY[o[v 5jKRbZ7̙3,y׮]oZ8f,]z+;̙?f=z3gf{зo&}/nݺq˗/޽{In͛c|֬YB3i$,X0߹s'YI,6j!6{+%@9Dp_Lmxg88<裂>k׮X2ٌO?, si|Gy>}бcG%KK.,yUU,YzRuVNnv^0cƌ&fll,֭[ SNEQQ)M Õ+WrZ~-J2k,̞=E 0}t*Kz+aF@.9;{z%̞={8m֢b d3gļyܮ)NŋInjڵkQZZʒ0Y"u)S| i{n^m݆m6b k׎%Inw}m۶qSLVuw?Nc=3ߏ LvGTC`':r8A1>4ݔ #VR1{lC׮]?<x?#Inž={8!x9քSO'd0{lN>8!M=ٍo߾=>|~~xS̚5 Ǐ82d  Ì3hy8DːSABJ;N \ZT s 7qUT?{9 {[dM(>>>Xh^֭hA08x ΗT/^̩RTTcǢBlذ| G>vX9f̘l8}4ozW@K %ʈzr9ņA`` ||I3`|4ٌŋwޡ8haѤ><==ѷo_-_?w0{ŦM8y%(( .Dtt4hɡjѣ#-7K?!rErn])D{_8BBB:r9q|>}EB/ʎܹs ތ]">>շl6c,G*!nqwo0a"##9+Y.{᫶{؆"p"zvۀ 䥠Pϲ Xrlׯ?~2!n222 N GT*1brvZf~%DRRR~\._N4S]]֮]K#݋;wd-\j^`E E'; ǐY^OJp:{E"rp"ȯ4L._sZg)J|-cƌ5kPcj*>fϞÇsoi`$@}}=oe>}oѣGV@uu5y*8)x K:wuW\àA8Ϭ,r2D3PC.sbuv Jtqp#}]@p-xx̑;vE"f|Gl2 ݻwwx|Q48}KT*ѧOV~F_|ZHiN;*ݻdꫯh .h Xf^Zj#u7oFYYgQ۸qK0j(Nvٳgip$9jsm5kt‘'^{NĴڛ2j X} u5 RxxxΥK.a˖-4PDBݼXR b4[azC^^KV[[Vs" Ξ=?G,^ld2 2={lk8;?hD^… Y2OOOL2S_%ѣT0Xz5+GeX2F#Gk;vdrss9/+ Budؠ as߿UԨQ5jG|r3goe* ;gE|||_S9mƒo߾UrC.gW6͔$b.]đu 'Onk믿ΑP [Ha) Kd]B=h15cɒ%8qK֣G|WlK2n8dGɓ'iD믿2 ~;:w2rHfCUՊ?#7L\BPPFTii)*|'y`` oM֠m۶ѣGj*0w9\< ;=,Vr^l L5kV/. 4#?~8 ٹs'gy)2c DGGd^^^8(**ž={X2BYf9횞x h4X,ؾ};Ҋ'NСC,Y||Aiii8rKz]:/AXȗ<m IUbmxރrqg L0*Ŝ0{lԚ 42|ZZlTGT̟?Sd29}'DXXK닌 41m4V=rtץRϳ"ML&8@&"Μ9ÑEEE;tkGf*++qA8GDd.H^\Ś+f&r M2i.oMDlڴ#Zg)nYYYVK.;vO `L>p?8 @N&qPUU7|% ArrK\?^4PVg&%H99&kq@P!@BJ@CH-[X hxnO>$bbbX\ZΊXm0yd0.P]'ߨOr]@3 ,Yvv6,X@'vq$&&bĉ.sYzz:h)2$NJH JbkمΓ̖s Ux5n [ѣ!^{5=n[nq;8u Њ3g5 f͚P>___,l"DgӣGCԩSX~= "AA0@7]t Ьwc]8z(g699ݺussD̛7eee4]:pK}Q5i^^^,Ymm-;F?Nⴖ\g1%aرyyy9upqN:ʼn6kӦ ^u΀׏%3Lt "AA)B6TíIɭ%K`ӦM,Bmƛ O q,++㴔#\s"%%3\3X.xwi]Yf%tq6!!!4^lDf߾}rת89N Q~vOף ƒ -fq*@Ϟ=rO"00u .(['C۶m]Z===q>}EYj+@.c֬Y.S\1!!!YAAX,|駬yBDfVY%??̤rxjro@3 DNsp2Ǐǎ;8rWܝ]@Cк&oO>.z j? |4.Ȏ;8- n0kD4.FFF'"(88K_7__ʼn"g99>KQ~*n"lp?Ycǎ.yAAAbo9Pv\{rj(䭷8:tϯ+3<Òcڵ4.ƚ5k8zDEEup":m۶Ѡ6Qt.bv\mpgLVԻY+{.Y@ZZ'di 555?rٮ%!!SΝu痌… y+ r wt9-[B;WbРA=z4G޸a?uTgH{(jw@+$/v@@l!½ b,OV+{jb>,h"7n?ԩK_70x'XQ&7oAu!{N /ٯ%""SH.-- [lu>Cd.[1uui2.sNʴTC@٘R;r `mlPVŃ>R Ȓf$v!},zS6_~ͷo^AWسg vZVT\.ĉ]x=ٳgyӲ݉9;D 9 l yq8q֭cI&nh4fd.¾}8;;vD.]D@ eeeh3|lR]q{xx`ܹ|VL>2*9^TPJ"v7oN_TQ'Npg'&&fwcȐ!Jf[nI"߿1tP@kLEE+W06l(:]vfW^ᤗTWW#>>^4`6s3H*u4Z9]"+`Xl@q{8.\ia`49}Z-к||\NǙ_eee Eػw/'ԨQqd2N6"L\m۶e2z)NWo߾Ç4q D_ )r`w~ nbÙ":u ~)KaÆzb&‰'8P܃ZtP(2Jee%8rWn-i/yyyɘfO.c颺$ 0%+**D7'5ZtJ@ 2+8IGP+T edd%kӦ w.{jtFغu+v͒u5v<,YYY;`'|r=z#}qWԪũS >$+EEg7FFG, 7>|ٿ$$$T*&L 4rry$%%>:vpq_ى;v^2c Ѥ\Kxx8Gv TP B xvC7^9x7x |U]V˩\__[ۀh=|y\\/ܟh;jNov=z芛o߾,Y]]i_#G|{\>~2LTzWJF^^^ `9A= 6E~ nNيZ{rBv3f>r9edų)//a֭ƌ#{=+--ťKhĪUsNa^au2hxEGG~F#ﻳqƘ,^(U+9A%{5ʋ~ nléB|!o߾=ڷo/{yסj˗/Itc#{,f3:^YرcE^ZV+'Z&ŋ,RdgĄFIKKCII \!%H>>;F=t{f ȋ;_J`MM <ȑ{{{~"##YFL^>Nll,'ZC X,N[6BOOOh'" """8r8O?$/뮻8u&v؁'O`n:J&rp;ӥY_~%KT*EUZ9\N#_5MVl6xn23#2r$<<\Wa ^{D~zN$F]k׮eRRRPUUEnJ)@x(۹W `9I Qk*]",Y6mc (/gGjᆪ F1grtڕ#7ͼjDaZÑ5 s >|;gFXXkСhӦ KVWWG&$R$I Z"&9/r`wLw/E;,"fq1NpQhǏ(6+ #䂂p뭷:\xiii4譈h-b*{+J٭?0 'D.SVR)2ԞbwԑTz!X9͆.ŋ9!F>T߫`6GLAAΜ9Ò 'Ns^AoEON+KKw ZJ&bc8}7o+4ةI, ֒ps,$$O=-11J%AP((^K\(2 @4&6bˋS1bPl+˗s;SAǎ9r2Ztpwtc1D ~ Kh[ew(ŋ9̥R\qE&>\L֢*vI*Sܹk֬ȥ℡0 9FJÑ|!B`89\A_ Z3̮_d*C=$uv1hqz-n&Fj}*++9郑#GJr99&hmLD4A]n̮*R``D/dKLrH=9Z `%v4&T**^vu  LiZd|>%ӊw2dKVRRN8,\x%STJ1eIr7@_g!%H ApOAw֗V9 R;@Ε%Y]]1V $ ^LJcQbpR}YYp<Y6m$ ԙ8~8vM HOOg|At]2猽^V {  v1 Ƌ~1rLg.Xuu5oDI\.Yu둝MÑ#G8c"{x+ ȥTaNzFVfŋcGaRBPP cB?vJ?@:/;SbT7[`%%%fF#lڵ++_˖-,[%c=&{0ͼrA\%Bllo.QISUU%{mH\~@j mb&9$9C) :B t(}͜I8}… ,ߎ=zHW^s$3 T(GJV#h9G;Qh8[Z*(++0/LJ4ZorcZ%E FLH*} h03ȱGGG*FrbCc|{-Tvkl 8ރ¨U+S\rZZ`I 2r555X2O]v$hgn^m&yfq @NNo D1f?~#;W^3KA1ސcS"฽:DC?ҫEu˖-x$W- h<]8̘1CRW;vIЂaΝ,YXX "ELiÃLZc߾},Y0|p}݇]r|56 oR"OH?)@Z <(iqw娫 n6ɍ "Z@ksiZ%>$\*}]l7n꼏lAvލBv0#<.]H^xL222h"+ !ꨅZ3u@v BDGkS)_eeeeɼ1uTɍKpp0TX,Xh+GSN%** ? z+RRR~c;Vٺu+9B8u;\T\\L|Vؽ.)3iM  ƌ ?~S@ڵܸsr+++K(//Ǟ={X84_<)%4Z'OvN:֘g4ZCٳ,Y׮]%!48LTl=FHLpsLrIҜty1)  d ђZͻ!ZG}c[VIb`-))Vm-@yy9fϞ͑[)yH-HKKâE8CSNZ9veV$Dh/y rt&I ރ@buL/^Ď;X9jwQ-F#]0T@͛YӧOw+=0 ÛWK4\=z%]wE!?v NbqwHm6oKIzG@RY E쎭E^xZ\gϞrbH:=xm6ydz9d`***p rBZZ9HD?_|Dxh < $Lъk\_~=5… V?-nOaa!,X.鉙3gJ=sLܹs8qK7|*D4]v… yll[­u ADk9\`/=_M[ו~#衇Oա&ٱcY <쳒o3r8^EҮ];5J d8 ǎ9sHU*oɏ>99949)@2ICj&<(Ƈf؝Y:*eL~Zg <,^&)..ƛo1ļyHS\.-VZZJmlƆ py< @%X 1cx+S'pQ7,yǎ; @y8^OaP72jG.yP`D<9ΕpϩJoW^a8bA f}݇'Ov+$]< |<&G3Z}ܸqׁ'ƕ<?n %%% 8;vtޟD@:D*)XP7j.&9EP!aq l\ڀPWW[n,}||p}Iүoa'q8֭[TgEPP83L l6~w<補hcnQbp궐q)u'V&ÇcΜ9c-&&?"##IQ Μ9"<裼٣pB?E\x Z(JMKCqq1*+qi曨v Ƅ|2ӟ:$VGlAŋٿ?nbi॒!)LPYjFs~ 5fdշ -- >s8pw[x衇~QWW///v<Ü+ϛ/]rjʼnq1;x{lN0x2njyRndƠ0lZ0 6JP[S}UggfEtPuܻ7j8WЮ0L//xh4+Ph)*ZE_ n[r@lzd;Տ?bPFcxݻ> 5vS I0>QA o'INɵg[Ȑ i}EE~7`ڵؿ?qO0qD M&ATD FzIKÉcǐ>hV/С[rydcv(0adW(().`P[[ZTVWc_gZzWFpp1JXjjQ( Jq^juófxjm@@ii5;#"fk֩!UU(((@eM J+*P(rjEnn. 558y8U\(rXm6X֫΄+s8>f`}8ŭTEE@FFn]  <}]}}C :"^W}1 P] X2V/+lS_[v6ѠGa R*+>O\XoxP?9:@P!a2PS[ LZ 땝R 6]cc>JJCݔ90GP 9P`&={`pB̅b6 cH*0ƆիqJ%M{P1 2LJ%<=:8M&J `\600jЫ4*##qKϞm柹T]?hG1rE#ZǑS^_}AA# *5@@MFqfd2xH+Fe_!` ``AÎI=v􍋃FF]ѮOJ+͝cKÎYhk^xeܰ01u42wn\`2m1snف6[_{n%%@YfJV /OOhzlH CՊ3 +~ Lq!<8}+88 /:sm6#nABıRLhl3u|rJ<06r[oK/=zs(@e QrЬ =.ꍈJ!P`Rj_ݙ(1 <@Aqq m]@HӰc{{Zȸn6f3ٰk(鰘L Z7/,DjmܾxyaX⌿?6UI.'Pwy^]C-:J??XfXm6h4>>>JHQP*!T>M8(8bi-R F#fϝ?Tv1 J aac@Ѡi'Ni`٠VӁ:ۇ׵꠬`˖-x'[TTF ???3:@ (M*qUVᩧaaa1n8>'܈ &[kr bv͜ Ҋ 4;,6VJ%0y20y2,55;y2oۆ{  ŰV@&CXxy{CT"m[ 82ߟfC//c),/Ǐ燇nHJ͘7nߎ yZ-nvxxz L1'?06  S_+Y-ްQٌ&5!AGCk1LC!;o><(n4x0p-иMb͘1c RE'm/%jaJ8V{K*N1-Od 1OO̘7c#&NZbNT dQ_{%7 v͎} VvbP_}~-:>{CPp6Lƙh]wkjLff&z!~ӧO'%2F];`mjw 'rqL>Ս/1bAG㩔aHSS?9 ,A֜(Ç"!]8hPCa.Ռ61)X~=G~cȐ!8q"P(h Al߾[l}||cѤ$Wx[b233Y`,ZSLu0ш,QT^m97iIO(/%pF0)Fz{< +9pSN={6:D4*̙3# ĦMЯ_?R*jXg,^#޽;ND.%E,],Ytt4 6I;&jyIk7ƝHϡc]$ihvWpJOJ RDrr2N2ʌSN:u%7|Cƿ_VUTQK_5Y3 wΩNB9y$Ν˒rwd@̄(q)0)Fy-SHI 4B6 ӧOǾ}X3rfT$z뭤 BT.ȑ#xY2j5z-ҠF,XV|'~dd$^{5|u7DzIk=xX[_%?OJ uV8p#WѥKR,O4vwېϼ瑓#%`4qwrvu놡CҀbҥXt)#ΰaG)"6N`|2?p#NA@{:kiW4'=d2xyy/]^z EEE,yv0rHTCLg@C[L_Ddd$ >zh?\fû JEqE1k,< C#5 M ]_g䔓9r܈ {="P@SWW wu>C=z#9 rW^8ڷoロ׿ѣGZꫨfߟEK[o+2e ZCM6E T7rS!ҏ?P@c2!=}4~7cǎHJJuAl 6oތSNq( B4ysAU;|Xn #'bYu%d2Ǝ :{(_h2<]P?`i=8)U( pH[b8yqqq4.(ѣI-0`K4nlذKH4s~Va:hI `X[AvW*I#=d]<ԝZ)l( uܹ KxboߞU_"";;%իh ]gِq >ݺu$E~~>-Z6P xq r#FRa&͑^J-@ix_~%JJJX#Gb̘14.n:۶mÞ={8'xBfxb\t#_h4 )k^`lvX+PxU˅g`/Lr@3@pO-U( wk\f ~݉&88?8 ,f3-Z\[nTp/:u*:uD )J%wW_}]޼y !8SbSIsB'wB4V~Xx1k,TTTv FH4o{r<#%XWy}8[/  hoooc9gO>ÿʼn\<(֪ iBIoB%;;{/ Y$K 0K,󈊊rQ8aLHCiYj4G-9 g er|g;{<3y=0w\8[n%KIItps'NDΝiDDhh(Gv7ꫯuVG'>r7ęW_}/^{>rRi=9,'͑d 94cz x{{sdwZ͆m۶!+( k";DЦMۗ%]&۶mO< 3 s5uY)S`ԨQ4`"f#wVܹs믿r_DLLL8(_ DwaO^%$BF YAAvMxϊ}^2o3ho_Z\.w,[ ۶m'N///0Y|'}Aa{@f`1i<,1Җ6h<<axb;k ~TUwzGyhغu+g{1b)h8tV+KK/C-/N<^Pt&9g9BXp6#@P@rNqK.acwޜPrBsؤbɒ%LÇ#>>h?3tȑ%ѳY|8t]g[`ܸq-dJz Ikp'{$r\s m-lق,227% `6m6Ng]駟&ql9F;3Eb8դ^ϙ3ݻwouEconLe>#͑F!C6ޤ5ʣ%7=g!߫W/ٓD4'O_,NCVVRRb/{4 Ə8M)--ɓ'9[ojע 2m.PPsLrI9 `sG3`q,7ԩS胐S?(ЖÑu3gΤ*-ͯio9tPL<D-];Kb߹+Z#NoMM D4h:us0HgsdP*-}zi=U 9pOJ P1@ǣ0h }QQT*vď&MD#ڶm6/:'44f͢fZFpӴf qV@Պl<**žvŅJ1 ~4X@#@k!( sP1@;/^WT&!e@^^NÐ!Ch`$baE&ϟǚ5kX8_!͞={i&%Kxg zF*y~9Zg؄4P4Ggܭd,ip$d9/rח Lرc(++cBCCD!͙3gPZ^tCmq1& '&94GDPBIc*XVvcSTxh`fc61g׳JEg`@JJ GBE& Ǒ%&&"!!EoWf* RHA15e~&͑@Ri@QҠZF#GrfSTTLVnpp0^xRlsZ2-Q^^SNqڵq".tJгO&9$iJSDi*( >-ѱcGRl9Z՜qqqx'H9D9r~YP(bIpK'E iBP}Ic/($%~BKBՒrf/P(?;GGGhv}0 ׈#0eʔZdicE,LN @V94aSI{p8kb᫖RK@O?%1c<<"͑"Q8ݙUؕYEpaz=oRl8 hZqƑrYgΜfcGDS_~)G BG-?9K*EO(IkD_,l񈉉!&77,B@d$9擓J1ΦNJ!Mmm-9H32#v "N 䄹CIk/"\y]__ϒvmԢh1L&!\pۯ0|pR|:i$ WOQA] v .< `9 bu:*|j =Yr%z=K&F!M!¤q[@h |up#8pFOvq&$j1gy!͑@l -9bu^nГw0 (*j4 kJyy9BJCP(gFAHH)h6 00U<xi`V'`Srlr)` I.FAA?;2 hOS !X,JVCVrfӸ>АݝhrJ)Hs+d{pOzS1@QѣyXXL&CI1Ca4YKGQVVƑyyy9H\_)2)'I{+)rAT \̻OG[L2 PzOaɒ%"Zs!ͦfr9t:þ#܈*٥)6ir1vpbč Oyv1t:<==I9DZ\mےrfSSSÉ0Q&ͦ/fBBB:;>8@"L մO)p{j丣F7Ź)p^Af%DBB)h6B``þ]!F'H7Hs=LrJ-BΡbčZ |hH9Dyϒuؑ"Lic*M9)r:mڴq7]p_%LrJiRAP1JhL(ס# CȞ~iV&ܗK.#vAKCrJHsJECQd2As o$ܛ*H8ݻwB@߾} ll68$G9 6ѓ](?Hz^'\ O53[|մHCQ'77.]".Ad$aӦM̜9C4j/y>YSk0e EM DZ# E8Ġ]vdggUդl6dJ"PWW~c/y=<<0d|>Dv]zஎ:w?9$؝\CQ(qʟlш%`0pL P6!s"Ld2o hk۶m,Yxx8NޕYE\áV]2!͑@pރևF\AKQNFyy9K JE!͆ PT.lZ 95&T*BBBH9D)**t+pHSxWeT;_I<_0)=rH 4D $nE8*ÃC8%2e |}}I9D1L0,Yee%!33%n H\CGIsnUA-BQO]]'"""H9C૖=aήA4/TTbڴi!rf]V-!(Sc`e'p~Apq]( 1ͬW#礗N#ѣJ#FbfSQQŋxᇛٴqMqk0-;'$ ^avOC-CQ͛qAgϞ'ͦ@XX)h6ǎT*"ͦ y_6kT V`6A4t AQ95:vPRT*[?={%0a)h>0h۶-)pR(,F1X=m'Bp;.A`1@%(+E+[V\x#l7!''E??##brS /_xG0-p\ij~-vgV OBe'͑ o9 SBQ -/^ĩS8]TfCnnn}dThRg-,)--egr׏FX>bafwsEƶôMp[C@wH2203{[렰˗/o?pLV#((&9ZK.!/CBL2ˮb԰x2u0q#Q\\"]RR232$$ ,hg  {N<+jA5ʲ@0)E~`85FB7v$UUUݓx*h˳wgPVVƒy{{HKKCFFF|9-LM&՗p#jjjX;dΝ* zjgUy@LrR*Gv!'NU,}@KW=""jC4krR f̘8R… -K.EI ztt49܌blx饗X%d2bbbU]6:#81 r9ܞmIyu0XcBb߾}y`` )h6& y.]Z%b  80ŋM wAV󤺺}o73W-EmhO!:xr3A[#)T12رwf@!޽{q ܹ3)h6?dOq#Z-BBZ:??Z`ZF-^xZ&9e i?\nFY[."LY{0zhRl6n 6m:t@!QTހKLL&w\Է@IFFL&CϞ=ӤەYl˅P?zD:R1z:'wH(hgR;K.aӦM?$_76AgwyanHKعs'jkkj?o|(cRg"^J;]"EmvsWqc( q_ؿ?K}rl=oߎ`!!!֭)ͨrg]'Or[oGGG n:V BVmgʬ*R #yh2|D#&DžHKܘMgE/QÑ 6 ƍ#-h0qDуfm~GN=z **N4"NZAeee+R]~!,( `! W&qlr¿FKč0cXpy<44E{.b1f@BBr9)=z4 kٳ)p}ǁ9p&9ʍXE}s .y7=r7Z5;غu+KaÆrG;vލ_~fݐZL&}^II 96mڐfc6n:]j̝;I7Uw%xcD8;R')) 'erpYk׮h۶-)h6OfzGg1ww\o5=rѤ() z#9YBB )͆m݆DR6l<ڝ |'ۤB] /ESexGBlrI(/" '_ksHKKŋ9rjF8J?έJL&***8Ν;S!ì.0{9Eo`tEa!aq=P?r!'524Gܐ -aԩ NqD<4fw^ڵ%I9DYbˑr6M9YYYfyd2$$$ 6) <'Hi|L#4R/;fJDҎ$qcV-Ej~ۻI#;;#ҥ zEh6gΜAMM Kֹs&- 1ϟh#GZ|,b 88X |pET0xrAQB^'h:5hH T 9HV+ Q55:&f A3 lСCxyyR M ٯRWl6 .p:V?^v]1$Gi_ h6r(άzIb*ҖUVq4 r\9Ƿ~˙_PրpGGts&O1O<) PWW`:t9Zij}v=rͧIQ n#qsܡ@c{ǩݽ{w̚5&_B^ί#GB ֮]˩/P(Fts͒}駜tƩmڴt-: 0ah'/9q mfJ7RGP[cp9<::111 EFFrrKLLDll,)ԍX,L}Nii)gG@޽I~jkk1}///hZ?v]gcX[_eč (|pYF߲2AZhuѩ1g`4 A^T@5d'4G) ݡ6l={ C\7ԀRlڴ#mR Bzo޼:Y۶m~FˋI8/?LoϣW0hJ^P?9V!4C~#|)fn㪰?gd!e—Dž uVg&%%r ѣNۻw/rssozDZe7j ۷oO}vcʍv]=1VibD+a6?vSa`Y$&&d'O͛:?;;S&DB-_mRRLB%c|)KDر,Y۶mѧOR,qzΜt慁[N۶mѱcGwjA D0XЪ8I$zX.F;;jIsMٝYhvm,Y^^~嗛k{B!pw$!*[XXxNx뭷8@/Aa|Nm58vG>qDݛK\eP*, n 㛃3f@۶moYF:ZJwQ0xoLdSBXC$Ѻ𨐓;˳X,|9شi+;** sΥ B\owҥKyOƶmX2NG j7dfaذaTŃ>Ȋjlغu avɑ۾TJbށN6O‰B ! <݇B#]t 5ĉOOO Uz^ _^zᡇ"OWj;|0Y[oG&,8N&??? 랳sNIIIv/ٕYl;,64xp7K \r‡ॢa$+_+sDDGW1*+z@V;AԩTXXg^Rk=)qmx`ZyX,K$OOON$l39p}?{>)9GE Sp7Lr p2 (V9.ǷH.((-vbb0{lR$Fu>=zSOY;'E;D@@K0 <Ȓ_[neɒ0hР~`DaaVps@LrJoJpl$ݙU@(]V&^!Ǐst:4^ :d;w߿? XN\0!gUbŚ5k8KݻwG׮]Iu Dz㻲+`>}8)*=h-,f܁!BO0IN$ pM^ }11{hSiiiȟ~iVF3qC{1׏%GFFϝ;UTK&})*јW_},٢Ew^/x 6  v* o&TDYV8pqqqXr%g~;]UL ~B r&9Z~o@!TV1cư6p᫽o€o'` 8vZa֭"tzbͱ: 4ƍcE.1 1c`8nF6l,ٴi0g΍7":[BO3xB rSX I#B&LZ۵kV^ 9s&23پD5 ,٦M0o<<l }l z-ڑjZ̛72@7BO?Exx 4i}]SZoϣN$K# $|L$ ".GpDywwax) 1{F΃矱pBN:|GT6m֋/Y2J?F">>wqM4iC #3x`HHHmqcaʕ7tDeE9H*88x =I{ǾlD8l0[~VވE'av9RgtDN<#8q"mvck.rǣ>ݘɓ'o!MbسgrrrXv[n+n;K7… ضmVX:xyyaXpMϝl:'%:ubV `:fדL>p{bc6>?\B%")3:>C8FékB΂ 957[P "]| Yͮ&v]V#ݵWUUҥKF`` &AA$܈nO`!e8 Xw[`S 9" >|@yդeCHDZ:ߞ'kAǎ' &2,2D[5)TOLrw~r^Ng @6֓" xZݙU'H P =Au qQQ C@An`HN"yH(&t =sLr 8X#UT,RAAQ迓QBf$E$ q=e5g @nvgVAA+ ("E8^d|44 O8`9AV  =a?Gj~-)  $`Y'‚Q{ {q r^$ T  $yIN!- s䔓A @>AA $ވ)k3IA!!Rk)I<;O"K KLr Ae/b8e i-g+0\RAAH'aPŷ1) r1S~pTyqj44HbR= ;X~)wGGW-z* :NXv vʰpI n_yAIͯū;ILBBF堼D#6!@?=9CԚ1tRAA\ E2JcX[`S>&- :r,P"m0R ![.!%AA.oQa"ZcVজ;"i T\IGfg( AAX~)n \xH-U'@4&9zi(LɊ+AO; ATveVV{'NV#zj9?AAN, 9/1HCDH/ÔkHANCo`7HHB@SkOS@Er:,!) /)=M J <)  ZY[.Q++o@1d*GhmR@xX[0ih.5 p O"RD+>Hh$|LZ$@*Ss߹=#HDX{ =AA 5~"E>J;=#&w&9U"AYN\k$'W-'%MXrAA@o`YJplnjFU'@,zRN ӛ.bRAA4ip, xdXvWEהө?A5`S>M蹓[HDRkIAфw3zRDkQԹI?AŜB]rG4:{&a0[YrAV-ȋCXZ?T NSfܧmxxh*M*72 &b֖KVA߈j:9WvAXVwǀzD3VAu,k%G5$, - \h {:0\  k%㿕wGG6T#'@&9 Qht $%MtޝY3I&Ҧm%-]ݠ X Ep=rGQpEEqRP ZJSJ6mڄlef~LR*$$uOi,'c!$I/#BvL}k?͠( vc @C 9,$IFw!Ÿ̟TpM*@c/t+(b%r? ݼ[-$i[[ֵX:;wXIh zcϚ[ʿ1"jH~>F !Ie}/ 'caf[? \i/``>3k8{^EԐ|-$i򸿑S7Ա& (`=>^rU[D - $I487o%ed@([/ͼ*7TvBK $I_7ZaoC|57 2D~\Ԑݺ~@4[ֵXh?wv <:@A;֖q; +<4,Il}u WT&zߤ7B9$M/+@ٰfS|c!$_%/#fo>j%e ܔ{d>q4+xICIͿ+Ӧf:nA}ke  Wdk_]EGUXHeŦ=qN4$o!vL>yF7v wXMH/O~9>B*+ge}SŐ$UZjv 1̊!nKl% Spp_&GPWʊ$ܸ@Sk'nì(믛xfH8xx> OQʎ$'cohQru#d!F3tqeCxͿ 9ήηͥ ʎ$޺U$rW _Lʴ?u?Hq=mv 7E`,lu#WߵBHlǩeQ>{V-W,<]A}VSҡ>tcߺO63e5w7q %I6|n\1X@5gC}x@e׭q76yL$QeG HC7t7?{l]Pf!U? _CuߴUgp^f ZMH /y_ !Lj] ߹ @ݜͬn$MXksM=oL.p&e:DYK=LEy30piIpFVntI$M0ֵp*L4XM@L-<'V5аe] }%4Q\N#diM!?y^=4!:K;!}3 OʗΝr v$@ƻx7lpra%|,V'뭦d\4f<.tO /Hvk;u_|#eU| u1 !ڀ+m%`-pY!;UqYvka\ Iu7})\LfE3b?𡠾f)h|2yNtO m,a#l1$ij'X-\f!_>w:?{:Eь[{m%o5,V.ȹf"kLol埾 IkY:VYn1$i5eŭOHS!_9w:Bz!.s9؂jĵ$&nm !ĸCm?iz(g\Sk2vY o~~QU\w ܤR^\Ƚ#y3X0`(ø34.  ti8rjܲ_xo o?=~2?䰡6-ӿd 1ht>v4"!cW΢] I/e}So4ή.Iaސڈ=_24f:Ƨ_Umo3k!{tW7gܴ=-@How,a#4u[o~W\e2F_"Ň|~(w}iW @Ҙvk;W('UsSTPm'=@2 \5a@~&c$S͇;/iQ3J\F jx $$@9s+.>Tf:ν[yn2L-Z,({ 팩,6)o [U .vgg:Ɩ}=\gX˂*0Wd )\wnu2#NᲥUL-P7 rw:cgg:Ʈ>9~>NR.*+8: @NUGO9K4w d0.zI8*1:{|=n A4JVkvեs(c''3RPk!f+k  bL=O i}C+41|}iVû2B8eca[66[XyV.^2u-N¡w/ĻO)Y\7\ou d Ų1+lk/YMuIƍW.90+%RnZ7c8jJ< <\x *o@(p:O?ZK+eaVYΊ#]" M]Z1 /ćNfI̫Ɛ-f+l _8_|}yy qgVyG X<ЂH#q/olv; :if1z4Κ[J0)Q/hH:?C'wCwnO0ʓ_4 [ ƨX0+O4/C_n d_@HY`PN}궸Mr$N]) MǶP(|535F4dc; {!]/dcgEU.l'g } +qQT" jP߰*@zTgc?'۬ d`ῠ>.pPM_M?i5.qM8뛺w7Hax ,RΙΩJȏdo]@#a)lme}SՄhZ/ii'XL;?Ԕ)5ke ŵ)lokg>č5 _ÝDZXsٳj^L]]6~޵ Nmp=qFYс0i_x¢cӋΘՙKd` _9[ā5GK20P ƘOp՝۹sS(mKOvk{5,}^9Il 47 -\}NΒ n*xZ .k'0/ʓpŒl Cǭ$ K >tvn}d?=(e-0Ћtڭo8{^)+Vc*=k%hK Tgk{iw;xN,e)E(P^04=u"ް:5ZIgA<@^ƽ]4Yd) =}` i'XE^vk{ܤO/Kmiw,NbVyVہtI!@9p9,- ز޻ބHX4-FyAgS@=}e* Gs*N+нZSVZ&FP}P0%[޵k;,4fGHuXEk煀`%H/ ^dkܻi禇pc IR3dQ%_XAmi^6N_nҒ Wekwlj?eSj%IزB|L%/lpCI  6wW{x/~OXhI҄&zn_-ɂvGI29gBxL67O{wo%ISbyQ%[ʒi1¡ _x dZ];7LѰǷZb.IƲP&Ɗ#yR-$? l7m_I2АMb/IÎ.~>:z[4&EBc*9yV1 2M9oh~k_I2PVB",duY@?ɺ]@3h7C46GX_lOߠ^ "@jP |xPͱ nmgUC FA$)G"gsJQ~|*oxeGH2D%zqk$ ]402 _ZS8 j' dT\ Ms|{] ?}b?CWE Qiځx;H2R0?[+K.n߰[c_w$C #6,dYMurH2&\sܻ/߷> /I+ʣr^#cTE {o @ %@p}ww|f66IxM - X45ƻӋ8:6|zI2P.Q`>A4M]|i{'= M4T"qX o]\ɑ1VKOwdks?I2A!hjc8?y?/$Ss*\8(U:Ϳ |eP? I2И FdT u?_llcOW? gHRΊ兘T9%;SJ兆K~ d،${V>IPKxѕ~a9"䇇 |Q@ 4ÀZJ-$):<;M<Ϯ>z=A@FT(0SxV.ECDwm?oc4 dq\\  |ϾD]ܵ[ŸwvWH)S[DZrNSBY~p0_>63d \8 8L}=|a/@ >WgE#D+|u%tBA('8oA+G)?~H2z%7 | xr$ΟmkbwHL̲|TF9`~cv$H/|ѓ^lCs_mn-4dwG/$U3*ck8k^) $@zB#vrKyN~~8/ evENɳ9w~(0H2􏃀 /-ܻwҰ݆Ƨ9,ęŜZW 8o @`4xrEܵyw:3@ҘcX4gqQm7w1H2?$A<.{i=$¼M/0W.":OxDk $@: B| xM]4⮧HRΘZlj3?af1En'~I2 (Ds7<_on]@F 3X4b,¼h=~`,pM$HUMzVE"Fqf3zxlw7ON>_$IY7Sb̫ͧh~0wJH24\+Wz.<g^gͦVMk T",Qĉ382,R@i~x>`=}A}C$H#j!D`7穖g{[/O{H[8ӋXR;,l*oxW@ \UX6F9u&ٴ'ζ^ܻ, MTr"RQfVE|\xHOM$H,|=sm}<Ւ?`wHUq~cXRęL+ɣ$eQs7d i|E(fo.$H(:xv k$= g[k/{gW{OXfЛHRPn aU&F)hj.Σ P^&?"_׻d I9SSMғHOO=gӞ8{:ߓHO_2Eo &?"/PP 180 &0$|ɏ_ h"}Tw $@ƈ 9K7A_"EKW?Ixf_{ٲ~5Oo"p "!!J# cEC,.(| "ahP0nJCzZ6d I~+'Kӟ/=`W{;hl;Rt֝?AGODzI~`lY8" (S(0BI4Ĝ| ,R08JÄ. ]@3h# $@ ƫd zIR|Oz&AkЁ`JqYQ"}0~`oo/I IJ9p8P@fٗ$Hr2SpOR@ٗ$Hƛ= 4?q|6$IƈOhї$HZ`9/1PhށTM@+Nݗ$@Up Lzs~J 篧yov[-$I |^Iz~'Nٗ$@4BNz%u^4طKd I d`)p,!5"I߱ovz~hc/I$IV  ~/Dzރ~?ᅍ$@\P:(w{ig ? E㎖~{yJt{~x^r9owVhO$I&"3F['$I$I$Mdn$I$I$I$I2$I$I$I$@$I$H$I$I$Id I$I $I$I2$I$I$I$@$I$H$I$I$Id I$I $I$I$I$I$I$@$I$H$I$I$Id I$I $I$I$I$I2$I$H$Id I$I $I$I$I$I2$I$I$I$@$I$H$I$I$I $I$I$I$I2$I$I$I$@$I$H$I$I$Id I$I$I$I2$I$I$I$@$I$H$I$I$Id I$Ip,&IENDB`OSCAR-code-v1.3.1/Building/Icons/Full-1079.png000066400000000000000000003715401417327530600203210ustar00rootroot00000000000000PNG  IHDR77M pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxu_wf ,\[(Z--o(T8w qOvGg?,  Yslϑ<e!"""""""V@DDDDDDDLᆈ q5"""""""j 7DDDDDDDn)WS!"""""""pCDDDDDDD\Mᆈ q5"""""""j 7DDDDDDDn)WS!"""""""pCDDDDDDD\Mᆈ q5"""""""j 7DDDDDDDn)WS!"""""""pCDDDDDDD\Mᆈ q5"""""""j 7DDDDDDDn)WS!"""""""pCDDDDDDD\MᆈO% >ߕ#tڀ=b̠pCDD$syv^37;@!?vW!P]#E"ɟ, }s'""")pCDD$a` a{1^?A{E @W0#ŀ&;`;2|_GNp߷6ww"""2 &˲TgPɯD'!|(j_'Ç+Fvs KK\zɯÒc0\ݚ: 4@Kg;| DDDd(UpaB3 <}`,vp^ܧ;{ ؆=Dv DÐ(vhccc2}S!""l*J;(`eߙubۀVcCv AZyw2z .I""i 7DDD/׎.س-jyp1`% 6؈e :/1k])l{c{3+ʀb""nьt,e{14{Gk;=E /ʱm=(&cϾ(K{lЋp4gyta >ڒy0;."" ?""Ikl-g[T%\` ^Mua KZ6ahLAz.,ED$5 7DD|؁E5v10w5{b}jУ;SDD 7DD-B30g^ `#P"ce)_Ggv ;EpCDD ;ž1{g`JJؾ 1DeG{&j cЁxlN7"""CT!"")V}jvCgc}KV/gdX؁Gb;Y-[܊|$""$`1;$v"1ɯ۰g{l6KDDvFᆈZd`X0a <ɯۀ;Ĵ5-nSL0bBKKDdŀ0^`vߎWm?T""Gᆈ U%gdLŞ^b4DDFr0 ~eq˱&JD$)]T{ ?2\IDR(L%pݻc-hZJ%"Ynd*0{FFv!"gtD.`=_^Ƣ]YDD\Nᆈ aǘ3c0̨2r[^`%vvM@$"> 7DD$؛aF.2HD2=;xAڍEDnd2Y'scÌ<"WÎGgtlaL""GᆈH曄D`0(J*VyDD>Ph;6/&I9""{f'`J$"2$ H[>cKW#": 7DD2Rس3fKMJUؽ:_8Hᆈ{ n: 8S qZ'Ќݟ1uc5UyDDF (.b8FP򈈤N+-Sء 򈈌<""nz0r0RI{;4OO$UyDDF ST`OrXK\0U1$Sc/UiVQCRaQ!" Lw=1=:"z?S?f+fZD#w^j L( @ykPSfV=sdaRXʫH<""GᆈH`؁<;@o4 ʖ0}17h]{]m@">&0G{F4 =};ˢ^߳TT-9U9zʇi^;XݧIم}""d2pvQd;FK2'$$,u[WF% #b # x{F2 .sLkNAaIE1Í'cI$JgU* bliQ5*\(uy۶N1El+Zi03AD8fs֮M} D7q ߯=5; # èzeԎaiQ y>r! QE y0x{(fzEHٶa/UY-fbK""h>ɀcJ{bQb&ݑ}QiK޿ EMc&e#7㬊 g˯N#<~~K^j ~cPR*GAл"Η+gt=#҈HS!"2rr{fLI㱷o鏚4imqE8@=t $x9Lo4Agܞi_dhavG `a8 A+89c\Q/|>AY\y> h#&ڂ\~Y{J 7DD'{"`>v1KI1;&ͽqo7-Maz# ڻc d`1,ăfZHj ^YupYeA,/) 0ϔ3+s{ T<DV 'waiFAd""CS LH*Kz?Ag8NK2on`U[e&hon߭Ia7Z(7H=H(x[Բsy"GqGywĶ ݣ cС҈HS!"늀 Zݴ3JCOHHhnyQvEh 0]f^H$L$g{ .|x X`}|A/A/c +{;ױt4"n|PsmUc#;} A4aݼ0@cO$R%ݾݩ ݷc18#fڿ)3oL&RS#P$elBZ,Ǟ2ЎHQ!"~^AKN$clIMX$L6ִE5b'0GKHDa%.;y~fTP_=ʒ- P%SO n)$"vnEDNᆈv@5vsyN*-{b '%,zbٵ0_ǀCϣgwr&&oQWDDpCDȇTPe'GbE8nff;7ɓ{ lcߎE 8sN1g.}' |z]ˁ{DZbQpCD {}L壅&i3z6[E_Ol{v̎E^ӊ82N5#w/{rCDF q/P T,~]KX$, ˂o=Vw5lO0CDdw^TMg0{6K.Q a J 7 gux  a${t1 Sk.5~O-⻇PSql&vbU٭Z""bPc Bwܿk^jaɖ~z" qk_|~A!/)|*Qq]Tb!VEDvUᆈ8 LǞqB ^/n⭦z&1{:""i~ACaQ\q`%>)`4"an<@=p"p60{F]懏5># #&DuYHvH}A/3o.&?@?7z(Vl`:խ~B3?A@X̴[+^{~>j 3/"ǟAG n)R, [HOo7:hODXXx *Lrb>_EV%tn@ڡ y۹,'?{rfKW8=䫯 E`Z>␗EfTpł ƊDCǀao%۩HvS!"#ŏ$ `PwWemGyP!""Ðl4{)S[IZ^`)v!"YHᆈ}j| " C]1~t#K3M/erig*yeV0ZpCDc8,4fXͦ(k„ {vGNDD!eT^bZyVCM?*TVEpCDb&p(ldef6wDymm=1вI/evU5Kͪ&h"OᆈqiO걛f}+XͦvU!""i^iT٧6i9|kQ5Y;l !nȮ^~r!vl ZXaY_ ϖKxpu7+Z¼hheAJ<>}+(8!Yn:w@Opͼǒ4%gi5KCDD2`oIn\NUu x52:V$c(nP?gE'+[#}8{n J7q=DGHv\dUNj s0WKODDD>HAÑS8p\O. ۗE.v`"pC$;c/AUX6zy`@/~ndLִExvc/K?!B %C||nJ33-x {ǿMtpC$3aҸF ~uMosvi;bA% sܴ"r2gh{G`7 4pC$Þ4Nn:9~ ^ OPCDD$ y>rPML 96 HondrP.e3ʒ}v|-jhC4!&dJanѩI"NeF[#ܸ[_nK 5$Kڲބ gyϤz߫Hv!54! eF\ { YI"68$oP 1^iV 5$SXvHλw? r|w<((ҩt~MNko7:\^Dm0p|#ˈfVa4Cq 7D);̸8)gcyKʿk2˺d:+= ) AioA/y;<^+r(y14NRiu!k"M@̢;xkH]Hܤw041;YA[B#O) ?Y7ˁ[Dᆈ Ʉ/yk{?{ @!/!Pk%9>Y Uv2:睿iE|]_VmFִYsG8b&У?fқ HD&}Qh<98 #6!ǘ~yXfWݓC-pCD.N̄/κ^ߞmojH\lfXAqrDe=Ӣ<+ rX,~IUC(k3E^G[QJך~8Ew$i s/z q^‚8cksrqkP컁kwTYfq(r`Kinq[Ë-&;$`x{ ؁Ec/L .-a^K[&hcYЖ = b&Ѹeã)Q 9)WnjǸ"ׇb]dѡpC${c8?L4a=S{!B IAakR`,P S+BL) qd7hKܦ^ֵG}'cl=#%1&)7Q[d>E.;"2n'P[3l@:<ݳ(|!2 cË 7(*ϼ1,ϴE~Nml`eK'75$菚FMqws!2rb,/T18@݁M Q!~B\nacl 6js?=-#=[z9~A/9>{GC'`|>+B+n2l:<-a_C@@ܤ' ŭOk}z >BR{p""THIpuY^~2low& < &ٻ6Sf06W-ou=:# c&I8nO${@$<|xfU|Klb`Q! nAcsw~Ԑa^&o^@rfFkP]I:T+I[/l=<-t , ޹uQ*㚓Q@So{Wdn9TGՕEwcX K35d0ߟ 3^%9^8eV;ۯMf'K5Lܴ-" Ď;DC.>79/XEQЃם?ъ JHxu]<0× 3B?O.dNYϣ2IfksܵtфE\aȇ[zcX4+4,shnS!qIXmpmYϞoE^CB?O*9%6@ő[v'{H0-&$t&sqxi|vXBc lTdon8CM?_m|؃a1$e|NU',UqD>B_η;-; '0-;Хχ[Vu~-pڵ w@p"(f7̝hI|ěL^E6I%AGd: |҆>q띍DV疏O n 8LnjIN"(N6y[c~*ִ1!$(z8iF1W~uynIϧZ2̰,y{Wv5l&׀E xS6_W#*n Wx-ĥiwI$nye *B\0SgS_cϝSE\4Leq.b+w0жܒ<1\o=/kwSP!2>W]%-|wىseZ`YQ%sw ",b k#n\NGw̾hfd`zeG.JE.T^h Q \UUf njDdB1y\w9]cSIGI4a֟oKZ~iv5^Lǀ€/曋vq`5>{ 7DF8@>.l[icӽl6hN)[T<^O M&\j{[zZ"΂~dfUivЭ)9^e(UV9통25jd+ӂ )ceW!AaЋO7>"c n5-k_ic^zC2P9%qn :_5" 7DFJ.ppP{zYöXAuy1&ee!s,#3( y{T# 3'-Z׽ƺ-}lGAdEeZyߜP n:y d;"W| 8(wV{5ma1S&aA$]PʩQ#?@C$[FM:LZyζr= %$o ,VȵNp;9E?HQ!2GDޥ'7NSo,m/KZH"{[oOun:uπ d#"C> evϮq˭3MjkFֈ5/ +9{R ^Ɨk.i7Ϊ0W>;^{YeQWn:&oF d"*p0U5Xr? hFaZ,^T2"D]QW!/Ck4639^%,Aj{L[μxrpCd~ Z;W?[O%4[#M')9|?J:ܸblq9 HXL q^e|\/_N.n36Bn:ݴepb&!5̬ Q_4W4Dd5X}&Z%gs5C\,nQI\s8 \)2>b"MG `/A0p\7}L5,3%L+GMj#")MX8-/گUAך0|__XiKr_:V$c)peŸEwllj k۾L?9^|h L(`]Qlu=\r"n3W)4mEJA(J|8(rIwy'|Vtٳ5|J52ŠU9Y0> H$xqWV겛jɊM,曋9h+H^۾ALpCdq:.Z{6sߊ.mWLή/+RIΫ l skZWȫf55.*kW.G;HR!n^L+0_=->M(MfN,+wl.S]DֶGxisOn/KZ0cS_qIns*8= xH(Dv9MC? LtI&F;Khjo ' are9Y90Nd|QrłJ/tY/pP2 [` '#_lg`ƈ35SiK8lR+Bd;d>n}U{r{̨㜽"W,SY )z |nzot@У5ʙ"ŢYŜ4 Y\E$;=7ϥmkj,.f+jfk@q3>KkF~| Ma_$ fs"O+bNB t^\3;~ 9$%,[Vq_ .СR!Y K؍C[V~x0T[_̄ӊ8nZ'L/fnB YmWtr._ѥC_81|*>_E Y'S!Y(| 6j+OiwWnq&rޣ.""`iC?wɃyiU2oRw!/7_jmnH.ԤEM}/߿X_\P.aA4A]u.'L/)4XuWq˛,Ʌ|iAj""2 nZ}xdM7 9$} J\}\UgMU {`$Jv'ݾp_L,nBiE̯Pu~{Vvĺ~2]ɯNy{Q| 7$yÀ/Ǥɮh sm[ ͒ۿMaѤ~tTsi7a LHIe*/WWMZx9XEҒ T^`1}`t?y-CqEJ$)\W9*V]DDR쇏5nZc7v騤8{ǎI|/РIR!'{ߪ.μa}ZZq WWGiOuISk#\qf^OC{~U?I uL8I' 7$%m-o &k-A,VĿ^kKZx8x=z/K1|Jҷg&߀ )J i`qic?޲6ڍC]L ,ZQS uK}X[w͢t0ऽL) Yb7&nH&(>Hۏ{" X7%NO \'aQc\~{8fVT;7z#^kBIqỷ̮I׳ 5hj 7`*P'177]ڵ]L !۫o.VMDD2̓{Y?Yzq`K`nu} (S!nV \ݵٟ';ϟj䷏o272-s}]Փ7""Ǐ5r+l>_5y8>ʸ h$UnyZr1lrƍxi]tS*`_:XdMQιe=o6 W!3 u|~Jr3> D5` 7m<\H`㹍,4u,U9e!""SwlWۈ)_ϼ},˂x)$hYdnnH 80O6vDq[ &77H!{iK\ͼ^<,bKpl.HhR!jvqi:ַ:8 D 6\łAt|l#""}[?ι7Uݚ))3 n_;6Nm yC#%#Iᆤq_:Z[四DMvx t;f;RaS!T8=K&Ipbrx&UI1ⵆ*:{b׬BqHr‰)n)( k@qo_h4SU!>o2kTq%[ͼr4P 8޳11R`Jv qJrWI_j7R7s|ua5{""j[#\\3xt+\F_2`2~t:38 m+I8! Wt;Oݹ;[36\/7+- ""ZܱOg]Id%KǧәE?n~ۉ]v&~BݻANeTR]<7wm3z' qtf[ ɮR!m^vb_o W=΋1b2g-%ӀHzr}/{d+O-m+,y-U\xl::$}[|pMtEy>9m+ç(""&ؿ.,[",xaS}1 JS@J> -sG[|퇷'lB$AaϚKTy>۝Vnђ|l=iq 8jGJ>  'ۉ~\ `+No}!>ST:A/O(8:4QF`> rf@J> iҪ %wl5 " Y̯cQ81ؿ. o%:Pt={<ΟZiE:fAB3e4( ̸m`CG48e)a a mRD,%,3U 㰿b-Q3Y4ϝLAЛ곱3;{qH >hˤI6(pӢ2!"" <ɩWԇ2 \5M,!0[=nxCpBM1λuRLeAhc HS1,CF v%oypCvf6@}:@6ȊN!,S1HH[牋QWP!#›t8Ӂϐ&3etNQޫ;N&7K-BDD$|ma5iX674ʁu 7S!xH{VvZ1Lߜ0'"""1xSS[CFe_^j垕>=D!ndp,pqz`ugg}:l %Ŵx >o95&""rTfU2b97o} O 5"AF |(It}z#FWrqu&NDD$K}nʨ TFj4ndsdr:,?5^2DDDĒo}aŅduκi]Oc N%{5Qv# 6 f@TDD$Kͨ P1dV'{9gj4MFv0#@O擷ou= 6 fr9"""Ymnu7[CFr3nGXIiTEE\JFv |5M}j~]3 n+9mVj!""">+OCFqh@KF+H>IH?h\Fڳ,fů"""޷2 V4 pޭR}o|0GN 72[$$MiӂOܼN5u]S>"""\= !d$L n|ږʳ(ր!d_V9 %i?#J}YP3&P]2 ?XNNC(\lK\x]Zސ" :}O.P-DDDM* Mo2`[WnX{O4 p#3Dr+׽Ҧ` L#f+U H&sS!W1dx ^ݒʳ\k@CFf:$n;7hX{I (]tʬb~vx2{;LY |V 722"'r-hMn$φ/.iKn/etJ\@Ff aR}"gg/r7&1c9uVj!"""-3ѵ̪*2w[\tT$ǤxIŇJ1 d^)oW W3-/k T ?מ6<$y[\lS*,IsTmouǷ)p OGȧwldL/Gk98k 9k ߐT,CiFҧ4pg*OgOn%m'װgqПOϼy!#x7;6MIhf_@J1;F9!#ÀuM\LS`,% n&Ƨ6[CHQ\b\TZHZ(f)~߹ KSuGh RO;SRuqz-Eq5 :d3-$E7ϒbr|:bʼnӋUR-DDD$}ma5&jy\kSu@:'d-jqdBDDD\Pme=sRܩ:R@Fz2O:yjpك&"""*ם6AɈ1?z!Uj;T\Ic /޻DBd\RB5C'Eˀ+hՆTHScrpBN]\E}6\$ """Z]8Bb3 D)II~iI08$8?;|z~9ߘpYk}e T:\ (8B RqeqWjW)rqcUqsBFnn&7Sq h>sí ;8;U'Yӥp57QDDD$su"##莍jr.PQpH%HgTw,~u|%9>BDDDZI;ϩ:ꙦT`xR|:⽛Ѭ 1 ̯P!DDDDS8pr !#p ?rkm r5#Gs|'u֮׼ԂQL△'SUDDD0gLzSqbs&ap7#{S]Y%*&` ׽ҖR#Dsu> 3Zc1ߟ0NDDDDv+ _$ydk؃2B72ap#iWۢ(.4o|>',V!DDDDv O'WsQb^og[O#F`3HѬ/޷mL>NȇĞ̭S!d,tJ=8R#0| 7F<R0׼>!ܺ<EDDD> j.*#`iY;muȹ'ўæpctI8l&iz3rq """ ҅j.*fR6{cF`xn=3SqGtЊ.pcfsne?9_62L<%[>r 4{cX 0zF+UQW\Y""""4/UsQ>૩q0E0t 7FqW>[ }\EL"""" RB^owȕ%.nnb ~x#47Z6DDDDzO/ Q5> /%7&kFwS#ũ8wn/Qp ODDDD줙X_h*X#k{>r5p`72jgm:}-a~\x5u4kCDDDdDqP!3ݛRqA S1\/޷Y+\j̫U!DDDDiޘ\N߳T[ʰh Nv"p?q9U';B>CM>N6DDDDFVGg:>{|mzƏl-K?s& +~uy*-+5{C Jl7[$ō֬ cǒ2<>O @ݠpcdTm 7SAU5U XvT]pcdTgwkliȴC(c.2<>/ݻDw3c>@䓷oЬ ,sBDDDd}ސau ݎ|0F5 7{ֆ!yxio""""2>@%* ~dGNSw1B9ɶ7Ɨ pdou=~vNa]rM޸i>)_[G +Yp ,V2ސs4`U):8K`hֆpCDDDIwlA^j%w| H 7{Fc zavgRҖ""""N:eV1ssT nz.S?1}>pso=۬ 1'jWT ,þD݁< P 78#οm؞55kCDDD$eϚ\B/]ow:}rU)"pHNUx8ADDD$c!û6[ހg p!âhR]xp~B~cYR!dX6yiCzO%P GEMzSwZ"?<^7dw_oG V?`HTn":_tFqSV""""iԗUN38S4aqvU.KDDDDN;D!ƪpcA?uF,޵ ZI#GL.8BȰܽƢくpcוG8]3 kfgB8EDDD Sw$krpc ,vk S6 5kCDDD$-ZDM_aK-Nʿ]S ;}_iS],?iE*H2ߞ2_EDDDҚcǡ2qUn|1Nm?Yn6:*I{g.D7db7!CEv 7>sLv;;껙iqł*ADDDe~|ސayyK?К*n|}>7lPfPbbI@q)Q!d '9(+x?8ctEy|]r,!JDDD~xHh Ƣ^v݅_pC $nOUDDDD\n|jCM=[5%5W~ <`܄[猈kس&Gq9k=^KZ<4lw Nn%M.w"ADDD$Cg)^>}!2/8kJ8'KpfNNѲdpp%{B6EN= DzFg yJEDDDηASeH N3[އ}<kT,4l.EB䭆~N.,5%[ÍJ\ 1 =z6g)VP !C5 8,Y]\bΆfcaN0a4+g)UDDDDXAuy* ɶ]R up{c^G@\L`RDDDDeZ"Ce5/8yc?Í=N6h x_%)""""%{ ^p4Lf۳+&,\ݭgo8lr """"||+4ĈSL,MɶpN=s3ȧ痫""""V[=i.'8-8~zfE*0̖~N7:L ',VDDDDdB^BȐmNLc^bDS EDDD\<ѦcəXl7: S˂Zb$""""y* ɺ+.:fC18޽\dh Xp^^crUm]!:fC1{#[I8%)*|-VdH~[N9%dzQ$űEdWs(h,ƣ2˚<\pt0Í<-}zffY%"""" 5ؿN$XXE8ֶuZaV".!Z"zi5p#KR~:)qsJTeG1;tpu@e&/Íy8$ٍzFfÀ9U9*첀`Z"oC[–gR29plN_2ӒL2<~""""۾]Sd<L_98$/٘aOєB}M/R!dGwߜhFpcp|_ sRADDDDv[a^m !5=Nn o.SÍ9xJCѸL3oJМ8XE/X?pp. 0'wIy& x5""""24ޯL}*?ڝMKR2a~cUmqtE:\8!ix`{E},GЧJDDDDglQ !ѥ)3nnr`M=1zZiY""""22N^"n}y=2)Ҫ%)z5qG"mN5ˤp 6=2equ!z zcN.eR1ņu&PWP!DDDDdDc,EDDDDF ӊUٽ]!^nxm8Ǐ5Ǚ00U!R(nj4Lז ˭Nq6., s*9N-}ze9U1\}1{FӖ(ݑa{Ik_C5m˂ Ϡ"~ y{4"""k-/9{YXtk L`?I;R2귱:iMX4tG鍚nG^I7Fc?wo$A#O7?~^$)eA(z(xS'Wǀksyq>(]sp"nUNe 623zg: 4;ƪ0+¬j1in賗jyO_wð~ sw)Ӣ~olf(',{gˢrL5>j ]Բ5~Js|T,""":o2["q &88))1zEϰ 52"]}qZbl>VY}vXMvQЖcNAN4wEiev;Q_b;T!d-6ኁn v :~ 62uYsvl%Ck[vdVQ १?5sS(,q,VuEqڬb# &yGn} Q`|ndE2wiѐ\rr.r -;Za^\hW=Ȃ)\23OЧWDDDd莝ZaaeYk9yfS<؍urs1۩lꙕ͸'?zV\e7 (HAϮLk 8zJc !""",GM!Վ9*ss1v`67f"נ*n{&ͽ1[ƍwfs=C'>]v*G0k5̥YO<R'CDDDva k*%[]EPn[ {zFeqn#L\j?}p_4t0 0 k}9<=+E$ns$;;NeMܸic!7񈈈.;mV -Y 5(:yF7KRb&׽v4~!A1(1:⠗N4O|␗w 'Xhw~jc/]8˶%L p"3, 0 :KVV6pr̜wV8IDDD\)!?j23;rE#9uX¢[k2AݹSJ̴$6~x=Pco-0<y P y,X ]?q|fw+xjCOe]Gpܢ? X1 ]*nXE{;un+[Íz`S{|]R 6*u5yhu?z|o굗80k 5{TE4=kRWdoz":wE'X҆~"qe\ȑGr.=u<3+C| Bv㾴p5ucaSuL`GLv6фESo+7֣<50 P| BiQ/U{]&ֶG$,N6yx}k_Z×iSg(ܐzc+NǍF|xuI*s -|>@$j~̤ ?8l ',Nzh2.ڻWusM/Rdl:}߽7[Ə8u0ӂVXympփ[]D<{5 &۩X~,>65AS zT&:.x Z׮╆>-k) z(˺# >n 7걧87}I:lRaڟoof?,ٍ,* yYٜgl~x,E!/ x)׮ =z 6PٝSqSmnq`7uplFE(mϭ'br }v#΂`Wfs^e5_8W>7ᴲ+^DDD㔙%*<\jp#wIxasAlzezo7џzfYTz:0`RiG/3&ݶ)N{^iin9yhMK7[Mso|􃍄sJy38's;_[~iA%w_8ptG̴m[r}QÛy ÍN앭긑7t33-w3_g3QӉrՉ o"~1S y)L, 8p&zmPDDD0wCv]ǀ7p7~`Sʱׯ暗Z`c4s^qy\oyYicp|Tƕpt$n{E"""Te&hQڸ&6Y2ܚܴ8-]Qf-XK$fzW3 a0$ßʸҠ38&ߺWu(""N{[pK]n5m=s2Xi(oquY&a:l`s'sT}!~0,5'tÀݲ-a HU«n{=ǹ.n 7&:ugN;iVqJf˛£js9vj^Y_C$A hq kFDD$[y=c *5Q' P7sFT62Խ~di!Av+:<\jpېTwX=gܸUP8o^9[\_;{IÊdlrz1HEn788k`iֽg}j;VtϡcJQj :TYJ]^u[4"""Y昩*2(bHkn7`om=[2ܸg[yKQu-k +)q.HͭQSQe 'O%ܨw`}QHBϖ pBor2(aq>D<ΟWƸrgo&|-D!ˮ',S3^7[: =zd:bLjx|]{xܴ_ RgGbh5ʈ( z9sN)81q/m d.3Nqv $l^rӲv=Q2\q>\v&ڣڈ|*&5#sU8Ph$DDD Tew/rp1L^>Oϒ ewo`1|* P`ޘl5̿^W*""-MT!nCg&V5 ';gI-f ~Z?:| ! P=֠jsTTvvG7(Ʀs==ܨp#菙zd%l-}|롭uie! 8eVw>$5KS a[^@""" !Q!dlq֗5 (s`m3$ -f1w}qg}QR\^kw~|jMʾiݑ lh>Z-,@])A Չ56-ݖ(ḹcP['o2;؞R$k=kry~Gs7׸iǻ3 ;]cO;caFE\q@i1~=D$mY_pCvIܴhQ5;A`J:# Ǜ>A/"`omOns>Ha5T7o>p"Q_m;fXnvo5yy9L#j75 {7xWX/1!fU02Ğ5L* 2"DEOQMvsֶE᭦6tFiB_1?^;BIWD^첷{qeC'aQ\"DŽ 3+CL1$Ȥ Sʴh6:Éa=g܌ow)08$gsOkI :>|; : cmRS'oplE FX.yzc/.qpn M%0hv ցewo r B fVP 1~il ^^NjhC>{}1|ӂ aNX2:qBU{L#8:#7fv>wqTgV7r{0!B:B 7@  7ɖdUYZkId!8F3s;DP :Q A~:,74]iZQTG8/ xIq^ߌi)3HIPqV}aAS˧%SJPxHZsv˃>PdsHgo^@E'l(*0)|Z2`  pۋwNذ~KSJ~ 9X*Ό}N5&+Myz!UG)La Xz_3#n;gk0B;&bFUX m78hRո~f LJŠb#OzJ(}>IS1>J9=xތ U9!}@j8,ɤٺ^^bM.b%:LJ5}xP6UYM d2,:=G-xP7 +3cI^d[xʊ#uA_@Xxŧ=H O,h-xO678! E$bGt08JK-ߞN\R[b]IVĘJ*n䐸1&jEdN]ԞҒT PYR`j'^LN\g(b3wX*]6?،WѪlI)m8iqP‹x VF Q㟵]ܹ07N|Z*3<;mJ6YIt761:`K.v蔀l:jŦ*+\ci<Ixf?Ur$FG6{I@%ػ74")Mx!/EjFp Z%-^fjor`U,>>Շɋ+l5Q+.*cdn|Z<~]^B >TwyjU/x9@)z<vl>a )GSgFI)^z&Sqcoon[ԓ#< SuTPE Ņ'X`hγZaPUY4QzQQg?M5eMN`GhC2T) o,SVed*J>~{}-0 rm H!h+'P iN?{76GMz٧%3SҵQ7!M[0v/7aq~Z @QQ@dQ@5¼! W~>8bf3SpӃo,\)rh g}^?UaNuů'AEfv !*+nZQy_HҰЩXFY=n?~CH %(ƫ8nksE4ˉ1nxdU1> HZ%+_:҃gmy?\E_IYz|hIΟ.g[xg lP"àDNYZeoRcfQ< Z$?NNĉDJ<~#fWNĠaZ;nhvX~8\D<NhT~qڜ'a)G 4J5 %h츼<@p1g8w&=!NL<pDMro$n{7l'S UD^Z\VEiZ0Kw XegW;x ֠^7S[0'[/͍7wuI*Z\4Qtʎ_^,RIt"moވ4׎կ/ (#2q\7Zzl9;vυ|T VX-alfgÕ8^>0M޼6~Y9=qOAF"I3@Y0i(CcopӶщ>oG;ӏ NT.~V7."r(XhDu'ЩXja.G4dW$auI^?֋_}چ7bn{}B-*1{&5b4Eπ`E'S`9z#sI*neh1+KGe8!"NOLEOxqKu[?&[N% ྕٸft{6Jq5}|=8ߍ"/@dW"פ3̸rZ2KEiP237WlVvc_ V7@ټC5q4{i Vy{buvJ)!vVJ&l 23.b^>q+0@M܎G.xn*cb MS",> @կǁhHrjw-r Si S͔6aoc\3ib,+(S1+ pFQ-amK/KTRN5(0_daTyD2d'|<-}><oE>kR@aK+OH@s(bG'b^ S?5t.` 'Kk]B=E`H*nQܐc _!Ve!A0hE,/̦90C[8&mWq }%)lT`q;3f؟'M!!XrwNU &k&Sޭ6&Ϥo/ñ×aJ&h4==<#llL:+UL Pwk)VKQa[&ɔBmm(q,BːbNJII'W৭QXGB3 )J j|R0莩1Wlsa`yNq* |1>cyUw/KsWwG`9o{pHbnj;:yRJ˒ M$OVtSҬ3241Q-i74mH[)eWބ|֭>6RRp$:ɽxv_׸ _8||uQƘ1qޯa'㛐pUiV*N~&awe{X?5(=?W< [D7=^TDQ~ʤJP^_^+7*r3 dbL{WMw]f W Dq_4Ʉ-hMwNz=~a˄JT|sFLh|ma:}s.GJJIx-WGwC;jìSQꙿL,Ih _OoIyL9čiR^PY|^B-=K#[ˋν7o"\mjTI{^Lx$kRYFd*9cUqލ6.ꄳ|'ҘoK\kO$P9XrI@~xCS}t3u!B6WBGea :q'k&ŷ'fH} O ,T5vU!Ԋċ!=ʩtII$2?h /wUǵ/&2UE?nc~%%#s(¦>N*{իY\gP9+ZSQ( ^i#s[ASte 3vy_%@ &'}[Yb/Kğ\ҥ/gEH1A!ӊ% D=7:+LF*FDu8:b<<?0;|63Z[NsOBpT,5aPB+bI*;nqt.E =$54QSRv Sg% b(V/Z=~ɔ=ȿ*?[AmpJQkZhy<.^Hܸ&fo7%j~.?L c\Ĉ$n ,Z8&IsTJIn/Hy "0H6FFyy||ڎ ~8 &x=v]sdR6K$vLj!JlpɖwGH.ih @\_.&")/Hq66MRQȔ. Fj_A 4].BvD ps"*n,56@S'+./7y03,C pBD#ёƎY%R'D^oVBmas`H:@de@%q#@C)7xHI)%tECFd5~rxo,Z\|uQEm 9G8G{jpU/^ylD],/e{+/HڳգV9RC i|gG#?0R MR Ty9#M| "9'2QL0#B ?]v?}<>o\,y|0}pzMDOJw!tf맚|r,KN$짨8jwG^=lF L @ 9}naCqۼ,xNj PsQeF;9 iVjRRL;5iAN.q [ I .|=uB.Β,pmdS0n$"`q6@Aܐ4OgwF|r/(6!gj{c<0r6ˋݫ Z< [hQo#549|&@}%A/ˋ4N)uVrUs{ ^lꞟGI\ɷL,#dH˳G;, ACQpz8C r JiGJAbjzN; 5wbz%,6~n353̸$ 3(N t8őf~kdjMD@hcNe`Zzd#-Q(uO@VG-g#+yN7J!8/e42xAC @73l)/ Bg~['f54/SW2bFfb]Q`l>PpS2/h0,I4-$/?ߕUIxbo'Mh\1čNh/LsTXhu~%x qkGl/%vmp`o?Kqd:/ Fup?CEʼnL}1gRIrOb`Cm. A}F'@q?lJzCzhmw ™PЫU,&jp 36M#"7B↤;:5$d ynȗCmnYl/bNȃc;A@ X쪳c>2/,L 95RB)p p94_-Yydo6ttxR (^kɝWC |72cX@bV0P+f\=ÌBsbDªn$ሦ t'ssOl3̢_#àWfᗟ!BBn<WM/ - *V"shIXv6:NUƓ33ugid53S%ˈFs/xXxA*@Ō9:*6buqVOJB"n/lAC [ iP$IMr]4nzX`iɒ\xʊc8/}ԊູxpM.ehwxD*LȍD wc˨†eElSd p1Z2./ en* @/4A#xmP?!W(Hxj~Eyz䙤qx)YzOc@#V, i{轉E6&*OBEO6aa6fxgmMR+ndH4i.\73at ZEj4K![>aƭ%ތ DX9 <?kF+ې"LU7>hu.c+=.o9d+aaTxbI^~Nn1[k(%blU 方x꺢lTwbb/)%%pxG_RuEq[D\'FuH).zY;9lcG0z 7) /7>{0Y\7{P q[ewU6T &EA>>Շn]/2y%S}Ņܵ =SacM6챉N7R'y-h(A }/xڟIMEm 7<{lj2JYOů/Eqmy .~EN[lK^\@ o7(b/zCl_hd7kvآ,]_ RqdqZGBS׬Qd_S)A]F&x(GVZ%5sC1|{&*qinuViZI*% e+lX+`X<32-uiDUJ)sǑ.oRᾕْ_:҃Cql;VJiN'摠Ka:'Lfx-'"s %OFz<{C ~<{iJP0q3a[8x$Ud> Aﷶi{vYhVx~N@k*yZdatf?ZH'>N_ooHw}V6(DSKy1a(aG_GoCNWb`|N^o-ӉM'mh%/tǫUV|iNhאjZ?!v͓x38U4|q'n4DѫA⏗c?N"W:S]kaz¾*i&!DsC2v5:!dDV7Mѝ/J|inj̴YEAWMOs/F}aʹd, ĴGS"$*;DNe~Dާn)b۔fj>&'%WO6MܴsOkb-}>mb#}Y_4 YЫbReeT4Mo,s7=eeQ:Jҵ;v68Dl7ic2yn=?/y T6R-0Zr%U,?χ^-~ia3הc)T &aiK nj W$E0,e1ߎz/Ш09MK`prUnjrL] |Pc-5f3J;'lLFWdYWW/b ,*{p 'ܘ6ibY$(ÑiGyQKpT$kkddOǡ͡aw8&B &l:'qC/1a@o"{̛XpT@7v;D?߄FC  ǺA %$^,/4RDw#Eqs?('٨faT6pcy*ziwcw j V`Q21jGiGEwxH8\=NqlJR&L)Y1:0`[/MSo` %n$ȕ VHdF2Ś GtFS@SRxڄkwEE.bǢ|) K3PgS}ľc6@*EP쐐n+h]JLnW1ZܘMbzqL=mR'E_zv}OVgSGL%ȀuuuN@54_I*9!3~O`L'8O\S?#[;LF'vmc20 cGr,?/bZJV ):qxj?+ AOQ.2ҚI( zi!5gojp6b+<0F%Z+JX'd)zi~tS ƒc2ƖOYܓcwLD]/LjWcNA]ǽ7|#CQ__S@QMJ}I%1㢐qXN攢pL3f5U4ir)-ē{Tur__sXd}/YDNK5L ;֋jzNFi (VꡍV7uwwJ_JI<6:A$,#JOQ$JK94Q|Ưە%6D6+~SLdjྍ͢_&Ճ HzxsSZ ЫY,.0DOx$芆:'wgүbrCy MJWZCf*9sֶ:蕝Kϙa.bi-%lW.UMNIbyi֒U.h3ǛT]"-fg'nu $KQV`K= Ə?j5e'30)5'}wfd3^xs(^ti\O>j4l#w`hO$ Gǰ~f tEq{j ?X N@Y^tln4 AX/liNLe<ى+{mVG[!";NQ`IR9e^}7 Z AD0àc{Zi!šz 7`6(ĵUuaQ봞_*= 4P nl[E x,Ob\O\SLD拳S(F?lV"̿tG\3Ì L;b(s<{y fuam4!O7M.ʎ65g 7E]"E4lT q]YO>jAGDMDn{8.̉}F3?m* ǿ/IG'~ʩT0jY{EVSQxh/M 24vAz8+5`>4,m1h+f99?×yܷ&k%!qH6iMsRQ%y?mPE_|Ҋ>]^@~),‰NoB7Psrt.M$œ$ǧR7IeYoWe;$ElT{ؽs9UvJR4 7X/p(YI)$n xJ3~:z1.7*xbO瘪 L0Xe31Q8AKcV"qir!6"> BH29U\qtOl}cŮ2O&U7~&I7{m4`".1i$;˳A$ ]l4MtEU,~:mqdYFgBU1im~d{diN[XUoEט6Z\QmQ)nđ;č*9 j,OE>t*g!@%g'qH|\ɳ uPFTSƄ.ltJbsT0td@}d#,̈́f gHs݅-TvZNx9i&ה Of77b/IQY7K.O?j RF%߿Q‰*^?ӌ&yWχn/~$& eSM**N>jG!Dv`˳A$ YFYft>=>tS mѫ ؀:0bM5c'U ~uIQ4h}~dc`l'n?u,x8J}4Z m}cc/-5a~>!#Y#nT̏\F(2:3s)1ͷcv{<{ IiSJ1]7U*݇ߊX|ݦ1T X02'GԛXgN?YL`MpWK 7$M;n(']-lŹ9W0‹up#G `3'E_ (߀n/XM,q6'xh/2//@IoOtzJOZXL5n~,yƦ10 9"o;bblЬsI4!A3Ms̈H͓P.~:,z;)~Άyitg)ؘ:>yϽHlsX[YN?8_oH9˯7`'1pt3L"><{U0\UEF$S>"vq#7d.n4 Cǯ>mAY-$PH(6O<ܬU՛'Em{͍h*zy-fbmmGf<'.¯?kCyOynof;؍Wןaʟ4o*ZGǽO{Ĩ2"WKB8(6/w^X)LVXu^VH4mUnDr թDBLJ|tgiTj>ЍKNn?o݈hd@b5E\(&h*p/^[czoӢSڭ =r|#B ىK]1&.w %^]z^@An˾*p4 znSL#{z5J>S45]_^' UO;08ŤoNvy㫚%)("'sXybf,\<) 7̖&}p_ O#sQnwEA+r&Q,|mQrM!> ,x8k^ ;UF p 5ˋP)bsW1pkw?v⢧O;fj~^_<6}5EX^drl̓B/6<]?i=.с?WgZQeq;ևKsSE~e\ջt*_YTqC2~nw۾1K!?6WY^x2$,q$$nDZB=jo,#W~^,^{}Xt5e{t ?O9/^?8o2Ef5X&vKD Vs1?50?G,Dj ^y$!gcpxd[*&v7RnjEǘ1+Wo{gE:ދg~|㬔(;-BUGzq秵cB -6\&\nGȾMLj`frH (A(ܽ$_Q ̊:M-Z|dS zJ KQݯLÏWg#Y6;9/=^N#xG- >jqcO`scI(K2bjX{N#W>cb'Y>O'OgNt9p < x|rT\;r>N/>n<%/",DXcgC%^b?+%0mƅ~}iTJiVT15"aJnӳA$63:,&wPR2;8~ʘ=Uх?k{g p4|tT\VjASDK>gjp۫8՘O؀_^,ˌU .b]Yx9痁1щo݈9;>nŞxr;pOGZщNn\ +//we"à%_pd&gh13KGoZ/;'zEV{QAj9_ _˺=T,C/1g7: TRĖ:;*&/M7n(%]hKс6|kq 6s DmVg x\Pxb\;ÌtCL6Om6^<܃jԄ/N9ڎ}-3cb, 2&CMJ28̀ELp/auauP3Ɗ"#.YZd"oPXzv4:cxxL7౫ 0_+ؽ<~Q˸u&%AAyX/~yq* E^޿:OVH`zy' "B U77V2zE)zc?}] 4;5&1;؍[ى.?8mFT Fkܲ<?0ũѢzővOƓ/ چ 8;7NE^Ȗ:;ځC.bX' ݤ£Wf(cKbqߊ,8%(pHH!áCU/^BbЈB&uJg-D;>?{s{yؽ.1>x9(J"+MFIy)R7񗗤B#ũNl>$ǻN#Nxs$E]_ )J pk&a^- YNj$%\=d~[;-QH0rtځ RqYi2+̹?+ Nvym6l~\zEaiھȎ'7 ׇ^[ۮNLK`Y ?|S9j;iCCwfL=r(㥛J0?WFhQGaT+Ӎh<sFpx\P</ ec# x9>(d8#f0ͥ ~xin)Zh$V =GX?^?uk}]T/'7LxDŽf9i5wX΁^G-f<6t>N| Oss \_&qW]XĪ5qr,3͢} 2vy oJ%'= 9"]9_A _%O_vӜOUhl.`ٔj$q qC"l>|zڎԂMH4y!$vhJH+ nLg萗B^ &b̧vN/<\]W@9#j\37_^zd#Ϗ}Ԃw[*B6Gs<fV [%F蔉S` a[]T f$řXRh4*hqӍOqݍx#jN@gh4U4Ym fj0oWs†'vs;q;X'4vFpt%2!7kP%"ܳ4JMQ_gRLI*dRLҌ -> BRViR]d9=?yn G#/ף4=ya+܁nD;hrp{8t ;ȱF|νgء>>xZSٸn32Iq3(6q+;~Dt|eA:ck+ QS1 /}9SM ,6bV8Z]xwҨXPEgTf2 y|rڎ}M3{|V[kRȑH?c^ nn0Ѯ iPg޷W;>?73|G%-珖!isٍg~ެU)&ẙf|Ċgw7"*l>&Gnx!$j*5ad*6bAYF*8yz)x B<Ģ0.)O-sSq$&YZhTйEhD'~AS%3SpddļqJo }ιII.~,netxbC&c7p^;9+ ^÷չRB,$w$n!}KJkbtv7:pL,%˨ӒqA4*+>CwǠvN {OÊB#.ڄ>?z~.+5ὓ6ZeEC!q6(p4\3݌ELMRqny o/(rXYjpdjŒ d&JÊ]qf}Я+$iwE&/e(ˡ*+UQl>8ph%+,< t M5QY)Ħ>TB/w?5 XVdIJBfQS5H,rS5l%8\i\Š?i0;[/ l 8ia=Az&p7yؑ5H1Qd -7}':ϗKxs_tCAܞ?ZdrvYC#-8MR!7IKn ';={8f$ڀp/oC5_Ò`œiZEo37N5 U *%gEIgE@UǙ3,2) QM_辠b2i SuVB]AFgRͽl:puyk 7Ap3:cj*yάUpǿjV$UXNt\9}<޳9-?*?B;&Ky$na1 $d;&\=.)&ō.T4;qⁿ<9Rĸ1L/f@ɢ4_ ff0;G IY:Y)8fx}8jqōzOz30[w` a 0gjQ,2uӈGeBsˍ8xPgpG-n4z|Ia}mf52(N`f(IѠ$E q3x{6g .7N3+n}^$i:g "fa#Ho.leFɩ h@$}^.f=IB#OJ:~t{Q܄Gӏ&}N^v0CrCyr\9+a+QBAi%C՘E1(ȆL3'r^@+f&[p!lfFIJ4i:%UH+)*Gab Vw<ōO+WDBAALnrkh,>n%˨BQa}^M6?ō&n?&@+9qy&X:-)LHn~ z% E~*OSd6mnPҦ&PLh&MIA<+6u&a&33m]@jϡwb2\?@bC[ -   Bbi7OeE ;T 8^QO,,-0$&OvK  "QM4ӿѪXU**BP#AA6Z;~TRT<1*EAAD|08M\7M )`v68   k\ `)NG@ADBp8A㨩Amm-xHMMEII ˱|rT b+\ Z@L*`i=Jw^eR@ee%=ZL& KNωRWWW_}ĉ'p8,x5an ]wҨ"`Ւ>XIqcj6|UK&~^>_}T*PP`YjD7PSSzh4b֬Y(,,B#"q8z(y۷mmm8.4 t:,^wy'.jj,z-tuuAP@#55 a8Nl޼ ;p#998ov؁~---˃BF <JSNZ[[/~_8}4~ߢh4px<>}:-Z4668|0AhJ͛ގ ^z)Ș.7$$ֻʱ>`}!UR]칃ݨRZ qFe!Dرcx N43h4Fee%Z[[TJ &Doo/?㥗^qHOOGRRRh0L(,;)) fx<(--`$ qصk~ߡN `̙XnfϞdtvvtBP 996 {Ü9shcIF<سgRxd|;W_9ʠT*QWWF$455a߾}ԩS)EEso  ̟?a߾}g0PWWn,] FI d )r[KF8!af'>&q8㾕ti\.~aTTTl6Cp@ ~[Q^^$$%%!++ gφjE{{;<   FYfQj1*x衇 t:AANN~ߠ|.33 ,@ee%R, rtRوoyɷnڵko|cߑ TTT_χ^Bbᩧ'|s͆n ]7%ZƼypA444"T*=\Sx)R^_*l#ŊSȌ:b,⣏> ;v8馛pׇN3X?w qxh“O>cǎQGoo/FcHXS(sx衇zjxOf.^xP8V<0!al6 4<Ʉ<8~8_D8ǎK/&l8,[ wu׸~ZOS =xgr8rmh,R]fD}^JGÇ_ yAɓ'_9O}6l & >(m,s駟>d`\s .]:Q\\_={6\.WhQh3g <'@ggg(dyTal vmp:Q!z(@(\u [Vi҈⮻† III8p׿VO<JeHDx<(,,ejj*( FObӦMDOƦMD_~DѸ[1}PXa`4qi<88p` `n_җ&ӲpB{8.q+H $p1oADMj^h\.|Xj՘N7M\z饰lFk?&K@TBCCCD|@jK͙3wvCQDj O< A8 W@׿~z/ZcYf~!mFO˲ 6L_q;ClQDx뭷(E*9:&&Iy9(-eXHW8{:+XVV -\.qwRTƊV}݇ЂJP@`֭KN߅^/_/~a"NCKK z)Z>[l n7x#233'Mgy< ǂ ॗ^1HW^Aeee^/nFt,;\rI{0 j5ytttP'D1$5;uhN]R^ШfialS#D^{ ]]]\sAP(p=-Cff&BТ^P@7߄AQ7Dz N3k(UJ`***P[[:Xk֬lfΜ/<``0>;3Ťe4sf$П/ ~;/^ZSW_} x饗CQwqy{ Y4de;X Zj4TUU^H`{ー"yjKss=!3ǁoLP~yyhڈկ~6`tcfQD$)R^n'c&ghiat{"TVVba&s#r F{Z6β,~?|AtvvRG$(NƟ… #10Xp!̙Ejϣ:#iii;¼n71s̈_rK`` 8pI0N<} nL p\Xd /^,5g͚ٳgANm۶?NI /rcٲe]h4/zh4bػwowk(==]k`C7jkkR5R^čHyyz!tsNtuuP\.\}0#<1!0xC3ofX#2^x!V^=$zc߾}غu+uNى;w?׋ٳg#77W뗖K/ 2j5e8p`Ϟ=CԩS1c I!-- Wj8q_$E'7|sXX(J\a^/Jo69[lAccc#u5W]uju0 xyPb:]),7Jy9;G[h) @NpW^V |>L> .K.… =~8%@~i׆G~~^)))ED8p555Iq·~&|>ddd`٢ PVV6\8vuR;>X~1m4̛7o<{n466RGIK$7(rcu*$ܘ7B}/EnsHJbRpM7#A[oQ9ǎCsssh1 Ӊs2 k ;!g. uR</QWW6׹\.\y0͒݇Rĵ^;d\z^:9cPYYJI%I:{ ^s5a,ˢwΒ*ب t*ɶ^$nH&b, (]i\4 VtRaٲe *t:رIq̛oRpWKz .ܹsO>6:*Ny饗vCO>SL $`Μ9(++ ZwG}q{~ٸ+%rJ7FO>ң$5l"cP+\+7F Ko1=0?N'*++C(ePPP }h4\wua *eaSG)ؼysةUW]YfIz/6lP*hkkqoRGm 0 yVR;w3jjje˖PJ@󑔔{X~GA]]uX=T/)IXčs`T2: >zzzB))@III뮻$52?1ROX4O?4Bc###wy7cٲe $QTi !NgXaJUVE֬Y1<>8caQC]vYTK/b,xyf4 x$2S4R^'$@d(9TJkT:::χ嚻n]eeeߏ`urBZFSSpmٲ%vcˋ=i4,]^7gZ555N#0 v;̙¨WJJ ,Y2yoo/u\ >3П9N\xᅘ?~T-??M6AgRF !! Pӛ@ a[az-ՅJ2 $whW\24cY>RS}t"4Aqˣz__~yv#C8cZ!q8h4~Gʕ+]VbgSNRByNWI JuօEjy"'ϸ ӥ^7ƆCZ%EnCz΁ شiSXB~~>f̘***Nj5gD駟@BŌ3`׌3PTTV1@Tbƍd'<7|3Lpݸ hѢܹs}:fΜ){\|9 CX:ǎ;EdE'4 H&n16$(M@ ELѣhjjV >_$+9+W3kiiɓ'#Gb 嗾ZjZY'4Z-rOc 4 7N>MC"bPSDf{^1QIoE7$,+3"7`Ct5uuuφ?kV~n09991cC={ I}}=ve۱dɒПE}1y2JJJN(U*d 92 ͆/8QkYhD^χ۷S'0. ۶m EMvrsseuK.F '˲p8"":Ȱ5.鞶 @L{Aܰb$@ KEa^y啰`0`ŊXK. 2Ncݻw0 J .Rѣtj|p\a/55UQkƠFݻIQA/m @ZZL4iHyvR;w#t(,KyXi9.*3J̅aZGS`֭a .DVVlsŊa'FeB7oLl޼9o^/J jժR,ˢǎNQN'}ݰ#ǃ/&Mս2 UVƮ'/Bٳe5y6jzHzVšCE$x64gJyC$n.MR^P5b(T]lV~\)))̙3}J%:Dl߾G 9 Vn-7Ϧ yyya,bԑ1ʖ-[p'J%*YŋD^~M<0q!ļyªD) twwbP@`Rd1S^^-R^PrP]6y6oVؿ{^,YD&2⻡Rֆ>vGÔ)Sdwjz-w^k ߀)SdyCXEkk+uh e˖!†lzs2ސ8$ύDEb3^$nc &kVCX$p PYYݻwD` 7 SLR -T*:::N?~UUUa^W6^/q)JjRǏ/Ŵ0MR.:5áCR<M&гDnnn8---ԩb/Y,e퐸i>!1 T@7߄c粲pOOuh m6,:5HIYll9'''A r1aҟJS )ORP__GȟÇb?AvPIl9Vg` 666RO ˲"X.3; i."8H`S6r7oeyHNNԡ1CEEEB m.**BIIll6#+++󡺺:5ضm[P @^^d}a>P:5ؾ};8 A㐖kދ Z*8c 5Km#qCҸZz+aZOpm.A+de$:d̛7o;r SWW|򐹨@VVVT( ("@J<ǃyh4޳a4Cˁ Wd;nTTT?׋rfE]"ʲ,zzzp)񓸑bI+&&qcXH39'[G/1,픗 nXĀ… ;,/^<$߼|7bm۶¯yNeaK՘b͛7{HEիWσ Ðc QUU0q8Z*&̙HKK ؼ^/ lP}Dz*zT\čHh*Ho1,'3v۷oSLY{yy9 C؉chp@v 3m|:u*&O,_zuXt˲X,hjj΍8G}g>%%%;wnLM1-[¢^/֬Yyш0RI1mJ` ٳLEb6Py%*O40B"nxݐ #%gk~0N8yFp 1 SNERRRhŲ,V+?N[洵ĉ'Z-֭[30 Bfˮ.djő#GB~C @VDŽ` ʆTjmmN1<2uԘ9\́sb6?5B`)iU!I$)֌!Zl֭[!BةQVV3PXX!io.:|6mb9͛V JM˙JX,@iiiL=0n,Y #"ESS^xlKlFNNNwc׮]Me`,Ju!D&n,4MKo1,=@B~4^/3^( ̘1#ԒaTVV9۶m0ܹscna;4gTBl߾=$HAXlIZ'O:ZرN3`ʕ+cYRSSQXX& >8!&V2gMD.L@_č68D,0ؖ -9l̙3ca7}efÇJ _FR!%%%l~TTTPGӉ!%̙s,]4,zCP|_d qرc3gbڴi1Lg(2J56 @d+%B ߲e ^oh1q222bHo!-S***J F1RԩSâA@{{;uL9z(*<0 7ξLHGGG0113„ Z*`Nfc$n$!MC`ѐX~~{ PZZ+,]4I'鍍Z4e عs999(..Aȋ;w<ٌEFLt2a.HJJŋc;BBF{IH4 ,Ts,INF7f)/SReq'677a;󑝝TRRf0 8)2Dl޼**4g~L4 1\III0a5q8uuD,Nh9p$Kj&` s{ *7ru޽aV|R ^z8z( ng477q* ,[,f  l* y CjjjP__6xe]6H,Q\\h4hll)2rɓaڀ;9q0}tdeeAeׇٚL4)i1 [N&$ћB LAm60y3 ??a^O-3xg} ٳch6AL.3*++ c޼y1\s ؔJ%c20AwwwhJs̉.NP@'CP#$ +3Kyu@ۑ*QNIo 1"b*ֆ'O{<\r%1{jPPPWհX,!OR%`14iZVUUQˌCmčԘ~.R9D\j1v5瑒Ss0p8$&Ʊ75Brt$7qd1K#!1;v aFff+V:RR*}`Zl.c9jL&t:ݐ`JB466y Z"<߀U 3gΌy!JP`֬Ya/,RzhvHh$7=m%7q @\d5-Ĉt:,%cԩ1lۇZ갊D xRDfff?[JJʐR2hkk En^U*1cfsH`Yv*׋0!#===f+ fu`TlwQ#$\],čFa7k)7=[(Am8yPWWG[F۷/%$,b…aiPJ<7d \jL6-.6g,&fѥMMM>SLs:a{ Ac\I=Kf R@I7>}K{<ϙ3iiiaTNfl>~EEE1c&<7dpiyqÌ*is)?<\.א>>}z\<ߴiӠjCJBcc#jkk'@DgX)]Xn+9~Ep:466JÇvژ5"`#0^pRKwrUX"i#NxnowL~^o d(¹/..yTI2Ƞi#k.+f&g&!!P={H{v@fΜ3qܹ#v188( ܃ݎ^祿6ML&{7&Den`]Կw"𙺀Z+ t(#Nޘ\XTU5f8Nf͚e n8萎f2SpMu/^(ᠥE@u^#rg<6RU;w7ANEez#GUU^oL}Nӿcg@?&KfZY> o7'=AU/>W?~?7S[bw&Z;vb/2?0F׆'DUU);vW0k֬˖-38N0T¤;g% 8{zz'>딗Wa0eʔ\lzY #E ,Yui,^8>g^^ӧO'?>N&NJJݓbl$TNhavL#6 yiܭ$Nj]ulqlTWW~+V<쳉qW&gۏ{KEff`t:IOO7 r8TWW' @pBXN~~N{{t0MǢTUeʔ)1|? 'Y,;'B DN**Nbk}tvv200``rvvqWgKKKS04M#++ v22{,/dɒrjt&̲4 &i0Aȱb+kaXJl"ٚd;8E~I^; I ҭ,"ytqK[,7@99ZYIMMɁѷE__ipocXxX]}}}@QRRbscڴiwᠹRX&>3gd6mEQzj<~L -+,+ x#/0hV]Vxɂ>9ll6b|>1=a ӎ]IJJvIHH >>4KG.F/^wŊcNrnt0zԘn'SU3gd3>>izJB+oLS&8TC]Th,ݳ63K&<${YW{ &PyKfOM*ӧO'999>kqq1EEEUr͆h߾}|r())ϛhLAy9|X^rr2W|JEQddƊKJ3"912r7Jk{34773)a8N bV\iĻ\.Y #Guui3mڴ"%%S<^a ]yM;TUedddgz ..N޽{q>*1y ʱ&楙Vs+-feagLM(qR|DnJ0رcXM$===&,??vuk8pZӪ`Ŋ1m`b2qe48p`Dpmڵ$$lj*KR𨬬4?M2YSEh[,L \Ȳ@Jۀn¤7.ΐtv5Go\QJKKM@  bvD97𨨨```t$ +++d2rkM(..T(09tЈ|3f̘Tut:ٷo)e2JrdّNXHnT*lNv$“{7Fgg'@%Kd_EV[477XlpD 𨩩vdARRSL<_c$ nZZZ7o^Lngcv:$cPJVZoGLF+ޘ,qRg4w\̚5KVX2:x-م1f9|ZAKK)5رLm6===۷O:ŶoN;X4Y`AL;.y_FiЯ3kcw͵7c#byRx'u^SSc:o)))̜9sL,E|>L>/r:A@޽۔1mڴ n::b]GKzׯGӴe1bŊ96xUw@* J^XhZYӓK(L:;vq7''mcW-ahR2[`p0 l6s̉t:MZڵ4 .,/\ʹ(JPOOw6%u+2E顥E:(E51z32-M4Tb=FCp趪O.Hok{=vGG{1M~? .ӉnH_؆ z>9sxK.`T#D;v iF~~>]tQLIHH07l6),~zS!]d1ُ%p8hkkR:(k`'O-4XhnT{*lY~"vI**N{ݻweZ!2 #̞=۴jieґ-i[l  ޽peӟ}֬Y#Vͽ^/1,zyM?Ê+b~puVQU^x|> .$33sR։r(UuNgWVX6$^FeV_2qR^ZtmyMKKcŊ1V~_vnXl֭:t(s0 9ckn޽{cXdl۶t$0EQa={w^S wbqiP*!ƥ)Hl7g1L#,p-@<򈩾u]nsUWMq8- 1-Ρpl'c>%QIEggᔤZ~F]w݅ dHOO!o>ycSem߾-[Vͽ^/7xc_A< 8oׯ[f K,4PTT4EQرctSNmĺyC8 F-L"wf3{{"_xmFRRRpv K&mv hL­:A&஻rdffr5Lzlrv$hrgWIu$cٲeރP]]-dwKĂt+0Y,l$;0ܿ?> Uvv6I7>`۶mҁCu֙V}>]v999RRRp8#IB裏w^pE5iptR~er"---'?ӟ5wcjpqb^f)|\sF{Q,%'N靪L͏cMWo|>>M*:bb)7VOOwu\\y啓.VZEffipoزet6^{5~RRR~IkǠv$t v~sw먪ի馛ez9ˊ\Y\OBHpZ qIQ z23&CFSȾ8,8;0Rmm.)hFUUoٰacʔ)|ߞt+ut̡*~_vpL0#<Oذ@ Gex=شi{CHVv65u ?)))RH* Z4<@_gppSb?N=mgIeiv[xM;ђ=ZI8ҦA4nnpbpp:>z!<yyy 7_.ufv nf?u>***xGٰaiiivt]׾5V\)&><28]___߽lݺA 6ά@d''wE54$x? Uz ( ׋E!r͆6Q;4fS0A4:{zh]}}4>LQuBMu5vFƁm(b)HH`FF -Fnn.G?c"O};86>Aϔ>Jm>z~?n=shʑ/_;w2;`!(+C:w  FĮoeY'X?Y5ڂNTp'[x<z{{illwe˖-ttt̔)Sm~pYgo}"i @KN҉=8>==(6UU7yTR UfGvm#tyօ3f͛ofɢEPY] 8Qcj̍)ѭ0A]u^>sx##A]kajv8ߩ )6kQ?{JUUT7  @QA_MM$Wv8`ٲS6\.<~?uؽ͆]QHII뼶w/8㌡@?(vؔqfeɅ%Z=V7g8BqR[ƶup``~lGaىieSSmm߷vkjv̭\.GDQ 1~dn-9yRMuvx|>:{zP埓̣& 464:**5~Pl6v;)))z 뤦d0h&jmm0}G_jxp;94QXH~}}Ytjk't꨺ C`F7.EZFg&|B>PKH no > tq ) ׿ѲtPg_~_}C% M\oEG,[UhIu@7"W_" qJ^.33GΝ<$N7 *$&}}Cgm6v;8SSI$j;BJF323Yt)k=s8#z&MM!64P|ԠTW7buyЊp$NLm6HL%ߏkbmoG L+AC7άNLD.}gj{;@ꤪs;+V𹉺|TX8|ugC\^^#l;EQ>o#.qqq8HOO',瓞ٳ/X8--<[^Nrj* NU1l6Ќ딖Oҋ/⊋#/- tϢEXz5K.Ůi W/&}yUŽ?6u e|$ 2-|y>$窪`۶믓C EAQ΋36ƹ`bFMvtap88sJVLoh@yEx챨ت ".liaq{;& RoRס%3.N:VćA f#fcёsN-%8T@qM p NN$RQPT֯Z}UbX$ JBo-ZĻ6 ^UU}Dï^ !>[+/L߹~0td8' 8\.lv;an'Ȥr))6gYGסa Y&si/?b;2yp5Ût];ٷw/III0)4 ݎ$-7ԄOg0mZ37\qq̚9\sc1VZEPNdOG:M[iUqL#)8PbVvnqQydgɃ[74`({Q)QIU pyy=p|rO(CmL%ƖW_5Ll7|pّ5JLaۦMx_) 0Ġ9味m+.dRRRHMM%'7Tf͜k2Jޒ}d:v7J)_xi~RӆF<(>GxyOpPQz<^ ]'GQ[6ӧ'0tpqq1h>miii$l؆'L/Oڌ--d|h;+! y` &sINj*v GJJ %Ŵi(6Lf͚fy3ÓQEڸc>UUO.tl6M'>^ʊ+lC܈U`7"[%U I:Um'ÀO'9ݳφ?7^{'xd>ëFyS` PPXHnV 3fܮlʕC&gߏ`PEϊIhCOy ީwT#?ȕ]ױd̙$`ۙQREd.s搝Enn.i`o i[$,#߇NHOw>ֿ. $\  )))d 999̜9Lf ngQ}1:0u@}~}Q0 Qǃ\pYg1k,f2܏&))i(7ݎfppoo|8QN܈Y:0nML?2W̊Q'Ӏ*znqiӠp//dRRS7~0n1}yb"El$) ~A!pq}{;IKӰ &u}9[ZZʟm[rLH@WUzzzP9e L:ia8Zt8av Ӊf@V/'F s!INIaltwut:Yv-\r K.%777 8;H&b<}Y}Ql6[ؗrWTv11x ~t]ZR>1_;v].j͉M˲* <48JHÒokKpC;:O8ju˗sC].ޣÓH9{5uq]wQ[[KrRA^fΚŊ+XhEEE?u*qqqC03L(6mf)Sp-pڵO-bBs=(U~o},p}#殻"(x^^/_[LyFkw[:L:8ݲ&C nD}@9Ȋ>4۞٥8jFwc$_ٲe hjj"%%EQ*\uU\|̟?@/zٱcG0a`|k_cո 1nHII &[nӟ&"4<q QyNKK IIIC96<n7wq]wݸabgYYfRIp# ޵*഑`׫o8)jR"?Z[[INNN ַ_Obb)eϞ=>>nhTiw%iIVIi6Hp#*̦gvK%Dm֭cݺu$''} 0m4*ATUq<}޽;|xry뭷rѧŜ9s?,q=y;(;;{{A4:::sTUUqbF0P__+W[n!99~gZˀoe-)7KPfUa͑^{R6tݦaQ;JJ0 2|ij*\.W0( ~;vDgٸq#Jp`?<_t)]w]p' vil6z{{Mh/e$~]V;Bo֤(Ks=tuuo@QUWUO]ML4%ؼ4OֺF۲FNl7xLvoDMsF1:tȴupp/}K]V7F4UU9|p}{,8l6&NFBUUv=y^/^̍7(G"؉nEi&(;nF.]:!el<,DcӓIpZ6nIp# ݚb$Rf OvI=DXpcƌᠥ;VIvs=̳1.oQ4<ـ?R[nq>OrJi(\F[ 駟6vEvs-),BOv}z^Νg? ^{[Y;Y;ڬ*M9'FaOTDl6ӑhoc`` jHOOoUHsS}3Zp}W,azӧ/|Ak"yGp8>9sDH`y`(qד3!eR1&=9ӓ*<2Q %y%)l[Z@3$h9yhuV>jxpM71gidR> UUUC}>~{T^(ˣ>J[[)ׁn_jp'?l:ƥ*|#r92͈9HKRhޜ&`U,((n9"&N]] '\s5RA"v /` l |dBBOee%֭3v=^{-gyT?477k000 70ǡv6ycfK ,q0F{pÇIE'QNݴ]MK + pwxǡohBL^?z C~?x㍒@9J@Եy7M^/yyy\uUZQbLV9M:1ts%)b4h8}.o6III欳ꫯ ![o}vS~.R͛'%|>_T>_6oLBBB0oR?_=ӦML2@ zpz0f>/~rցU]>(Ā[VfYYb;dX nq$$i)4iƓO>aa`z)v2c'Qϝ vCz+WDf_hhhi(|__L||i~?s}}}7W-%hZoo/sv(Kaa%64s~,n /oЯKnJ)v>$"c'Q lDI=bTդ",ڶ0WTT/j:{%pEI2^~e6n0jy5ׄdR=۷nӥD<@}}}0:o3gNx;x2N%k%cn=X nxr+ \]$Gׯ^p$<ѳ**=@ <U#!<@\\6 EQ|deeqKjhh'99:588g-ǡDȽ3 IA4/ȷZQ*~H,.c K|kmM-SӖv0#)O?4o&Qꪫ={4ݘLMǡ妛nbԩRI"|Iz{{Gwӧs-Hо6m^ ---xe˖q 7HE1MF$tT%8v#---\x,[L10$;Rr۔t][n mCΎFtXaVZzD/T?Rp%HnqD3}}}Ã/~QyIKMӸu=\~UKaрc9rOnݎ($_cyLrwވoCòe˸ꪫBVf-v޳v;ζtV HfpnL.WH?Ip#cWETwɆ 4%]hW\q4b4mT24kd_}լZJ0쇊z۴i7o?0tM!I(xbSpndˆacQQQ n]w]H23&l٢%CGĿ,ϒb6+V(~ q8@ (| S pPUUE |>{1\.W~ 8T(**"77<_WP%j>sL9Cshi<䓦<gڵ!-]ɷCM&F CE-+ѽtxJMFFx_|> xnxK c+L6|a~7'͕Ƌj< v `7Hbb4\''n;0 |>C$FHOyiVYTHvpbe\.HrT Ưvp>}:UKݻl===TUεǂ;Ͷ4~Tjb5QlE;ho>hzݻ{Vn7k׮]hpl۶t4rSXX(3I/~?7n4x&!|/@nsWKÈ۸q#]IHHફʙ$N'۶m w^5˗KLz@ @EE.?$~V\i|W*z ~KߤO2϶l9қTU{JbHX릊@ Ν;Mg}^/wgq4da)ӦM̜9yIL>Ɓp𑀅 JLɡO[[a/N4~ 8{z2.K5R'^϶lUa_=+\$F=KeX9ʂYt!L:\$*===|>:,iIal޼ٴj:$''Ku@ ҥKC^s k[Y)V1,P(mdW*"v;Ֆ7 `˖-$ja&EQL۷&VQQDȵ%bŊ&8Coo/oipp7===[#Ic4r,UJ͟\/,uR)e{Ccbvy7M@ @II ӧOFdq4jƍ`ëV!ofڥp88svTPLmmmi6/UGĿ]6ol aI`^:#!yS,^j~t&Cpc+Ӫ~|TdW+zKELn Ӫpv% ۲̜93fH#Ÿ+Vub kwd-I(+>>>,yzqݦUg_$v$Sj.* љ CV8#KUyxWT"a󍸥Bu/_. .\HNNNXi۷ođT2 =F\+**bɒ%@1.;v젭s8]ޫ[RbO>R`uKm|e{>1?]zGvuJ%X`ڴiaݻ王bԩSINNiFCCi H$qp0 ׎3!B9iMQ/|Ѯ8춪\e&Ơ]R!rʰonnn674f͚% @X{/JX0J顴ԔzjVlذ>:G(t^#f:>K͏d nT`]aNMg. nZ$Kinn6kFJJ @":@p"*YYYXB*GcH刐t+eŊ!9p?!Yƅ3R, OĂЁVsd"杪>ZT Y_UUٸqiXhQD$MUU֭[gkFAAIIIRA">ӑ<0HNNk%*++z =m6s y/#);aYmx^j~llnʬ*s˲wM2')NKÀDi2~l3L w٥1ydW'>ٍKZZZLDUU%33ӧK刐+++c```Ī)SrDAKKXcR9$U4222B~ RJ3G.xR*&cp xǪ \1G3lڱҜ6+-hnn6eB\Y.BrX޼yrD H<6 *" ح<$cI*8EfSzoTJ6;"h^^T޽{M;TU#24锝 k!=^rZT*tq^Jퟞܨ6ZY/GXP]]mZ52 ̙##Boı(O~~$s!zY `ܹBJii鶲-..Y{Zia&½[.Df+‘ai'!ہ,*GSĸ&(ֆ?3 cğ /xmĪ̙3G*GX"'%%ErnKTVVrnAJJ ӧO:+GRMqvo.{az:;;MIII!K'Ѻx<vla|>͛;6 ppHj?t$OKX|% K2@6o1r4~&7"у*:0o޼yqqq\hjj%&,u}k|lwH(HӬ. 4@hl7zqoŸ>J]D(dzSNqcӿ*bavS0p8r,JXjS p0s -G (9U~yyXrmIx ҜY$5/ٽZZZ8|i"nj3rDi7TUxIf-BoI5bGMooopp;wn6Tx0٩\4;K7} XzO.Η)b won-7"Qgg'|v}VBO__MMMc)v]n 6 㟃@ 3ʒ !WQQ1bdrr~ dq8~+-`H`G:e.FNcifSTD:x v=8X{5Ccc㈳˖-!59",s!|>_0)**"??zW*;Zpt ҜV Nxo'78~A+^M"VvQU)@XEQ50p:#&Bfׄ%4M󙂻H\\܄sϖd"z$81,GJ,IN`Y9$8e[>DUU~)qdggRXb޽UKUU:u*yyyR9":;;QUuLawpYY)aL6m xb$ܰ$kAvmXJ {)R\5?]j^XᷛZAz۷ϴB: .ƈdAZZT={v}P4233%!,Py"!f_[*;J'عӬ.lpZ$UpB /]qhܽYvoD C__i`iڄ q"rtaTU5LJJJrDUTTvM &,h]e-~v..˧R֒Ɖa茔eƳljԼ;~O@voDcӑ0HJJ"!A~5{ x,_\*FXt$@uӿ Jf`` Λ7oB~~@wDQ 1JZZ8N9gewT9'aU.x<-yyy̟?_*G\MM PEKΞ={LK.25UU%55uBx,&EDW$,W7J ͥ Nj;/F\ +C_ׂOkw­ǔLtƊxruuutww1aBLUUMMMaI刐`۶m݁@˗OfI$%'eYV'; <V5Rb\T~U*" `Ν̛7](//7>+-"֯_5ˋgɒ%R9"亻 .0 _}ދvUmTtp( fz8 n{le߿084~^3n vjkkGl...^.˴'nUKǢEbaS2@ E$Ğ={4mĻw"ˮ(qd$Z]!-2>j,N, YRb\:@voBvIooip8X`4$wAӊb(Q[[Lhv}V-8^M4Onn$(b |BMMic޼yjE4dpdq8 <$->2{46 vzTıȻwvT֭[MFRPP A]]===! 2FL O. Bb nl6GC>'"ʼn***L0 '$vfY8:;4^gR% ge (Qj^KWۤ"QYYUsGkk)9s&iii"vE 0 KJJ$߆B{ܔocxr)5cZ[[C=\__OUU)Xzi>;;@]Z/K 7Fxvy%(b?h/ aFf͚o/[u]4q)%%%E@sΐ^ۼyi皦i2m4|A__=}vz{{t]'33UV[i xa92s ; nW 8,b|:*^T1Pjii.{a^cZ4KJpMP]]Usۏ633S*_(JHlذasaԩIvF3 pfX/H 7F _m)Vټy3i0c rv-2:%[2Thll4t]gڵV M:lZ80Hʚ5kNc_:ҀN7٭?d Q5B& FlڴijrqF󗖦QPP… r%#)6!GDl83CU'~IOOigRBBk׮=NtYhlܺ2 ( `4BdD1640*."B[/_.GD {nSpj*S"^/;vLy…dggK CeǎZٳ),,<͛rkS7W#F[dFĐب:tNzttw}TDmݺW0HNNfɒ%R9"ihh02N'sTLWMXfMȯ#6>lwMXz5qqqsw} ~U#fJH i! ke.{Z?(uB7n4m L6MmK{C  ??Kӄ/6mdJ:uYR"KmŊs{/|f(;@VNZzԵ 3Hb|;^F8`Z!|Zx Xď<;v3ΐ+`IBğG7 GRfϞMQQTJL}to7 ȼ5tl\v#P:O!Hpc ]B HvoQ`s %u۶mϔnsyI删Đ俨1us9G>??={rYgɑa?|~5kָfOɮ|em.%q(}4B^AbxΓw"B`#1{ldɒa^u9sT0IMM۷ot$%>>>[*\3~?;wnn-9"#\\U ]+"7ߩvYYhƯ/+bTv{ڵkđ38$ -[FI3gSNJ&ȹqFӮ5UUɑர|C@0HLL䦛nvc+zCvM3N L1zkV/˳b&&77P;OˉιEcci+`%G\A*-',o~?%%%̝;w?=tr#g(}%LCV 1n=~f^}3욦g!M֭[ c6EI刐us=W*G\WWeee'>ŋv/o]L*XwQ7N.i me6.4'½[i|z$ a?1|gʺy亞;##cBszM5ŪU.\SMXdT***F\pBɷ!N8fa ysrdee|Mk#bt.SahJ&l{C s&(رg+T1wo ؔ֬Y#*Fp+?AMM |/}pLY(Ǐxy nL-aƷ]b o]2MؿHerr+BYYو#QEEEL>]*USRڵkNbb" .rX`l6v9U^^Nwww00gNe˖ެc[[D S%zICD nL 7C7ڬ,4%ί.+SH:i?f3ptp`bv;Ԍޝ~ '+pPXXh}e:̛7o\uk#btd1Sr0?HSIL;'dԾ:[2-eee $%%qgKc\8p`6fb TqBvl6hmm6bŊqqc3@={m|>f̘1Uٵ/-Wdz!7& Se?vP:hl6ݽ{i\UUONQQ)1jv{ڵ@  ARR-J'X;O\3 &WSU:XtuuQ[[kV48q=Ю lDD;0\ůVTbu_]Kzi|Iv;TWWy^G vX1y&ޮzݻw&6H8Yfd p'QZZj iFNNs̑J'u!0LAؿ?}}}EQʷ֡>6Kl#t4 S](_B`y_/7i ^?+{cc|Wic9466d{HL_3}p E2~?-$AL.s%!!aDPUݻwZ TdqR9ƔvΝ*̘1cLϢylD E|FVQ`47l^oGIvԾ?Eۯ5b#7Xݻta$''t"\..]:b?UUU&a1}6qs gn:d iƒ%K$8s墾4WΚ51= {}xPb(9Ν1o$!3{y) ! |;&7w~qҥXkpcm܁@) .k;5|$`뤦J qJ.ӧ۶~6SpMيZp!N3dtww.W}}q.\pLǫt&pT(:?׬.څ, r:EƯ6x۱RYYiZ5|YfL[䔝m;v6FTܳgqmdggKJLL3 cԫ=iYYYK1NF;Ƈ~h0 8T?vaP IkkrHCHpC,O`pUE`HtC_[^n( ٦(|2 ($ٹizVǀl87.tMQ2% 1~vWhIII,^ؔ@Q3F~9s-bT\.qsvrtvv8J5UK1y͞=۔#..*N }۶m#*OZff&CQ__wѪ(--=t᝝Tz1"n 0'|9l8roX~//+DrzWc.(MgO9m۶mĪ_LbbtqJNt!0رc)n 泌ŋKQYtv:;;|'{aꫯUU|ɷ!#NS͛7u$.Q=Vٵi4K v8w7.^>-IqE/om;Z[[;߭6%23 V^-}FJNNs1 u]U΃U˄|>.ҤrŨjz<<41|uYL2E*Vڊ+L .8W `ƍUU5k.ޭw"9NTq`!.! qr] ް3 ӭ)N<6m6qYzn^l6MGS\.w>ۃ @uI;5AF$qfV!nPԿǴ 8-шO휩z.*ұ}O̞= bL̙3"磱w5Z93BŘ%''vl6~?}++ӧOϗcf+W`wCUU~aۃ}6PPP^:7 .F$u>(Mtx1$!=i°7Wylqo KxjowL|˗N7ٷo)]tLEHII!--mĪ剂]vV(~?W^y 2|M󺺺X~^x \veZ5Xp9p8;Kgytݺ5jw%F6`Vv$6-8|{Ic( _|)Njf 6w_Ъܹse׆70KMKEee% Ykk+7n$---_}>s̑#yō RZZ @OOo&IIIQTT4|WU׷C;3TkG~ JkHpCN>yi4geN^:cܹL:մjp8X~^|Ec\|c mٲe̘1#8lx<yScݺu466 \^G?Q ~;`a%e(qڮZLb|<455OnvZ11?mnQÀ3 G t g7#`5Iv`D&—?Au֬Yc:ώ;L n7/~|ϟ/}A[rr2]v&%%qС`4^zrs9Rdddpxyhhh{n7WK. -33kf{p۶m7G 뮻?u l_#HZW?7+7{.8;wϓmu &4#:l6*SBQ~?7n w۶mδBxKM̈́/rNOO?8qF8`>n)SHvUW8N;@}}}W]1 niT믿l^o_o;v'Oo m,DG 0\)$1 7a>Iiqzx`k;oWGG(..K/e``EQ0 8^|E \GO,KJJӖυ^Abb"*?0w})׋f…ĄkȤ %%u{a(B?_|1+V&--}s`2[NNNFUU>sUWgVv-R3dSy<<&?я£ XLsiqV $83NZTTī;۱픕K/sm(Boo/_GL0l64Mcwwq͓R>Lbb"t:q\vt],~ӟ!&&… 룼|>z#33/}K|s;eߪ HFT|9[7Dh X|AUQu+=Ex4e3YmFF ر#p8۷ݻw'v9s&wqG0+33fnLx`(FGG^x!_җWr 1;w.۷o͆hF__o~3$G8b_ 7"K5p/aH~oJ/@Q³$B³8#<6a]$nA&7 nD x x-_'e$KQW.B1Fx@bW@Wpp?ɽVi!ta>ʝ"Š PN(풺B!Ą87٩9S$duVHp# 8,75,lk Tt5B!8}>^ŀWlPxbR|_I" nDC4[3(Oq??Q%u!BwQ78q.33CRD F:1C NOXPZ7uWK]!b\}nS+%Vnn~g#D7" ˝^^(SpxnlB!Ęt }9V~/Y~}w487"[3g/; D^ G N|X ;դ2BqJ{[6pȴ%|38;|r:ىp?WQ[" a(NOM)bbUs.BqJ=W61~ VLM ӼAE7> pQ>r]|4 nDA`g8 OsNj@膘 .?hB!ϗpmiJX)v4 R]~:l4KÈѐF<(څ|)r{8v~v씺B!&=~B\N ".'~lF%oח2ji/׳v@B!xG O/-ّ4wFc!NJ$ɓ8 Un{0^!b)6EY|)4o'"JkCL-_<++Sı+hvW+H!,6 @\~ ő4}0b$ ].\pNJ8{fOi㵲n& !bjàȎ LᾫEt쒆! h b$.&Pc.BuMk䧻xSɎxJÈFBVURacˊH l?BbHp#v,~894܈p=M2Ԣ =EL,Ƕ$B5C! )IoוDIÈ"97bύ_dK,#Wz \8%;B~/9l<1cO챘02=/)q>5xe狏WtYԅBe\PE `fv~9"0H`CL0 nĞVW@Eଢd</g^͎FԅB%:7?UC[ZK7kGJnF{@4h܈Mۀ1t-,A>k13*#u!BD8U7Vީ]V3 'ɓ.)DHPFzx0^yiN p{V>B^~Z$.O~#""#BE2%p8\gk(` x\+BDC>y8ea88 ]iJm:Kf5 QL<Y{AԇBA|~Wˮ5҂Hxx5 nľ7:!%KK[=|C49$BD6Rg)ܾ:__^)T+##B>5*yOolm. vi 1X_kAe%B i0$SEH} %-Vkx<_b"D?|_߂! !", pmrkΉfDKHpcx0_TWEN&~EB!m & l8|ca</&' nL.:CwL?·s(/s5D5{5RB!DyU9Ks@*byN*XD3btEb <΂x^LPui>Ǫ8B!BǫKEX̮o,$1ӹ+_("L$1f@Rbn߻x*8Ie!>i~e> Hy"%`a$Ibzذq|\)BB. p!{o6T]p֎u6>3 #Cl0c({|d>YLR]ZDR#fBxiMV y==9nF@[ ff'9(K 7B} 8Se]RB!8=U7^g'6Ku޼m.9q HKLj:p?8^m.NǿT3{>B1zfo7R="i|6Jzaя~$0i@ 0XaWx`P h!&0P7Ȭ8K!/6Y/~q.WM'|hFDvnFWp?ȷ%"To=^?_{H}!6r_+iK`*x s l`W!"k/U  xqvf$!{)4vڋzWC!8AqPV9xvaz$=Y=Ei$C|qc(CNG+rEB]TǓ{丨Bq.ʕbpȴxYܸ$# xS q-%6Ϻ[ :)BŮP_koɱQ!bXG2enWせfrH#|x]ID2ySc? OWVPh덭x]ԇBI׫bS6,`Sn]IO67i$qBL:> lX6 l<ٙ\>'-ҞwGQAvn 0t4%X9kZ^=u`L"DUU?H[cҤNBL ~T k)a#_-&!S 8hѷia\UDU)~nx|#l ˡ(ͨw t$/U!}$7MlIp=Y\nƕOWQ77'^\qX Ao,1=f=ʍ[ p 7 Lu=c mYA?%>85QŽ [ FD4ԕOWBpCcL>ѝUMiI.ACcɚ| ;2nN?{|=uόcdE؍[.p?ܲQ?y~37Ό "26w^4ǗRŨ|tk[|5<"j/!1^$"lV HQ!{ Q[66ՇЕU ŅpIR>""{â$޺!{ѐG%ܺ[1ƟuP)ܐ S 斍zvU;ܵ愥C 0]:U"""+mԊvθmi @qEӫ'Gu+?I!S! ӭ3dXVs]hO*`"mOFUZ,"";kMZ\f~fZ?ӥwsjWIS! L\%IiPͯNU/W3,؜3eC9s*n=s$рW@n\'+\|mЕe)A]} 8 U/gxxI_zp-lM'ߜ9PEiW]:p"Gᆸ>#å_ [ בw\ gSO1q77&hN"Y/CcD{=Tmt>6À_rۆ5ve5^7 8č,,F9|DHw/'" 6w6N ˒ֲo]۷x-L1E?Nu5&v3_wFV>S¬Qq~vPhOY\&^Z59~,!n?{nj-: ˀWtDUӀ뀓ݺ_K2UQq/~>9FQu^gW6S1YܧJO`(w)/;ӃõCSYq\h<''q>_ED iZxhq D4ed\q`>`5S? ̊(KtE>uϺu_]s[8"q7ہк('O( 5E!F[$DZe u[WIø`Jb~ X(?nH>7p[7psGum0h]ֆ1NT Krywa O`v3$)(&4CpZgzLہHQ!bs7o/^On$dxC.9$S9nl)N,ӸH^ڕOsxxIs@C}5V&TxbU?Oc6}pɭo6r [X)a 9F+}+=Ǘj\Dv/yfhDk:ml _;X-K:"CrLqqw'WyfՇC#cCb̰8Ld8ǏS!"Ҙr;ͼ{7A18|<8Ϭ*9|d [n~,A8 7$Ub*8.\{V\ۚgoOsuE%!e1nDWq8ǔh\D՚Y_ ;k^y/srEMuo^Gʖon~ 9Dd)ܐ|U |"0z3hlNV'rLYsbNF7) YZs;HPP,6_S+^_w wa捼&<,V)rTEZZ#T)1<[_۪PmR6Ua>732"{ q9yCW4'㛸~Y*VŊY$VG`Z%Ǎ-ac+#7+S4VjD׹l26/\Ꝧ]:"pC ɘ5ǧ}CZ]BK[FЉwY,Ĕ >3bLjlDdykSMI#-P=6 Wf bBg [_7:"pC LLq7sr[MB~MSif .QGklD{[zYnB -AQqΚR4*(сq/Rh`R0SV\kqC7Xfkx]ʂMEm?>n#?8FMqPc#"Y{ba}-Ye]'cS rڤ2~t`&yĻ[qĝ䧌 ixE䫇P 0,LHHiNdҙeYSۖ3Sά8$/ؐ67Ũ)Q]=ugltfԞF 4zu8!S_E[>lO耊x ~zaoz^gUK\dZP@{P ̨f\Uhx~؁-:6/o/me.3$ٞa;C~&D19kJxؠ*} 7Dv |wglfߺ7wXW$elpU\y jDDqhKY'-V460ou ~M|08q%^܃1hHP!k~sü_~hBCBE.-25.ڿKfPRSeeE@gڢ3m9-sSz8gbu;ͤgj("yGȇ;ӇcEݜqJִdJ]xcِuT+5TB~6͗MwfeS?5U~{k02ʙu|:/Z{`VF@nJy4#\vH '+Hޙ)} [66&f k7#y5X |'zCD MN~RYi#r솀/ʊ8vL)g[#Dĥx̻[oa .˅dmwt*n9c$wAHR!0TN҆glD̻V65Zd7o |>|fhG9s 8qWyokh2#mO23 QvhWE( 7 ρTpCo1_LSxi;R˛Rxr눊|>"ASQ3"J9jt 3G5VolZʋ;yfU;3dlT!mMuE'.ϸ%ဗ&? |X tpEDHߊ7C=u@c"˯_级74/m? }D~ʢgbU¸Kkxqu/kgukC2k:L^xIY>p,G.:r =І5DpCυVg{m;6-YλgsA4u0E>g*;!ǐ#6,%441ylКHdlDƶs˚w-pemr ~h-u!op/Z`1n=#e}g&9te H_vsX~ba? =*]#L2<+0omNdk I[A}gYEw&7}Z i?9X^\h]Q!w㼸+SN3?xty ~"}w`nP>a?phG,, 3kT k"L)bڠ"ǽWvؕ54%-Hfm:6X=! 2d,M7NeQx{u+4TDdn}A`Wi5-)z[b8ԏCd-ػi)>ѐiZZT$`U1r(V MY^]ISbi-YCGڢ#iړ/]sdȿc9m1jxߞ6c -|$ݠpCTa-kK:m68w=7=@%!A6==NT!XvXdjג6lNЙ8Оm{G2I,{BB>reİ7:)Q&TGa pdtEdw(_Q`&^݉ IZη@3k:+2z>|~JՋC~rŃ#Tł2bR~8}%~}]M,`t,tǺ36]Rl4` WxJ +(p#ZW䚼xm.xآ-" 7Do$pSb/@*.lOm2騈{A}EMmkDX[v"*;7.m}k@Z]D 45 yby6vs!B)liuQ=s3q|g_sft[n[ pp1pX>]o7N~VHX 9DD8@քǖCjY.5 ~pCTx}gi{N4t*<) l#\vp 38rT<a@J_D `Sa6w̜e^ CDD[}8ub93Ę=$v>pp?j*"Lᆈ>;ԙ}AV#i""m=˹|fzq26bB05DD vOgaenk뺸&:Zh Y )NbRMKgTqo#JzȀ )ry|ڱ?kdκ.$눋8P#k3jhsV0&%3yTk5y xxSѩ' LS98iʊ1_a?#Lq8W^[(#xg()a ^3|Wwpߔ`$]\5BAO5a&DXi=PF ܫ'x S\昌ϑ;287'X 8:s>Ț*I5QTFĤ2|pM!Dx h֓CDJH:p,0(wmkK!I6eiʊHTBL2"wf1}pF"4Zc"u 7D = p1p P;-5Oo⽭,mLҚȚO>Us#j8P 04̸ǎ)Gh嘾7Q!"@1ML?}H! /ȒV6gXۚ&Mj%!jANWe0<\hVaV@yWO7 7DAO〡q?ִY͆4I '먚CDkrUh!%!Ÿ/W_ $|pCDvH!@rz.ieYC I9DDܩ9G<짶8O*Cj uT2`K-=QD$)8(36׿X?6ӒhHd6T!"2|>b!? CKC4UGQ_#67o""Bᆈ;EgK)@_Ґ[n཭ݴ,3mv0(?HGI$0'M(Y<2 \ 7DD ]wOc0xkԊv~&V5H$6B%%>C~JNW2G' 4/7 #"Jᆈ|T+1!( Tq [Q R$6鬃QED"hD~*S7c" X@3Q证4$"R "?NƬ,񳛹}A3-V!"/L}| . qʄ2=ǰvhHDDr"1N(UveKg?GSߑ!m; :DD>b}>TE5:53(lca ܢpCDzuqǼU8ubOb5zQX܅ 8,l"_0N„'òҖ ;^^ɯ^]ud S!"f@DŽ(;oVg _0ƄYj͇ @p\ɬ嘰'7rߢsvKَj"]7~D~y}0$! 5DD#R!"ĀEp dƲޅV^E#C}ȀXD]¹sJ~D>zw6aV>  d4,""{NᆈPY]Cv!uڤs}m+wof}S}CDʌ?SqrvhEA?ބvӻ*4Ѕ"""&Cᆈ P .G3mәk[F#ִA߶""öʌ A?C?KfTypO,'k>1<aH_\(;@PCZ-:raǫk;^ZAWKDȮDzŒ ?q2.?"E%] pF+ 7DM@-+lLu솴ҝ3m^[]6NF&7jP*Rxz12)˸Z~( )(4^{n>p;fՓN5 n1+LNjX>Е#eַ}^ϭ5 ) ~s+hDFOUFֆ 3kt' ) ( PQbxxhpCDܮ:ppdmmdm[lmkNCgTuVdQ!~;:22̉8yBD>ʢ*J"#0Ø^hpCD<~!CCW]iϮj5!e"Z 1,ɰ(8fL) )VUFK`lL?YP!"^{„#Yd'jXF*밵+CCWMv6^YŢnҐLu*BLQ p  ) 1\!  <4V (B?0Sqp=+lБIf̊-^Yɚ44e̪ :~ݟK=~H_4r?dm={LQYbhiQa.", 0$DqϨ]cn}h-oS!"f,fdHLcRqU-)tgiZɬ9_sq -6K8:2p/AOʏ^ߤ\؎q ͐yTVʊFHG0`T.2F5 (+ 7DSznŽV4J۬oK:&dm|V^@k{&f$S!Bo="۫"W|~~Q(̑&V T`\ EL!""yHᆈqc"p&1 {cWmfr7suҚhYd\[Gf[=A@gYH3K"{XXSkBS]e]Q$Q RR1HlLߌ'0MBaV@ LBAG^ԑ=iӑXjCڙ}?Hfn5`iC\C+f;7gn{Dz?gj;^:~l{WzEm+:`Hg )*fPG07CKM@Q3TZT^H^K1MA`V9@CD(pGmiSr?+( Ӵ%M1E"W=НiJl 7Rݽ{S"˂͉M2;ٝoze|?@i5!n0㪶zB4jiyJmbi'I HaR!"TqLNTwh2|w뻷ƶ 7td|@K+wi]q;## euK0L`5^FpCDdO18ӯCAHjocuFDDzS!"Yiec4 :DDT*xXapCDoYfh`$ :DDf LxبaݥpCDd/a#(zML5 ŷ  L30H#) f`1;""" w$p &4,"glLeN6b thxDD/)XcŽ1@S!"5]@Xx5Lᆈ{ 0WQ ThV1=3/ 7DDxҲfSJ b@=:cm"""Bᆈ7D0HcuT`MaL5ٌZ xS1O#""npCDě8 3ue,&舡CDilV R!"Tu F@ ;D٘ʌf`2ur"""yގ1 ;qhD d4V4, 0vxXᆈH cTC*LeGH~WOe4D""/nII ̊,C0KCCkl̲)LU&W 7DDL9Lߎj`0PYz֟R 3$ф /^^0H!Q!""d?:L1(Tw pCDDܳJr_C9(Ǭ"2_^ODDD q9 3]]qLQ[ $@7&hbcc?}/  I""~LsҞw1t0& Tb?ej.") .Ër TP""".pCDD' =z `Ld[21=@l A"@PCIY6iLhv혩#_V0yLDDD<@ᆈ@0S`!r?~$5>K=&fzH6$fz=E'YfvYn5{} .DDD mzP׽}R T wF`[H.h벘fY^՘PG=*=AqU[0"""{{F`۔.*4h %z1|=„Ɯ"""h^Ȟ{}~]Wān;1Ň 4@DDDDDDDLᆈx 4"""""""i 7DDDDDDDn)OS!"""""""pCDDDDDDDa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F#@IDATxy|SU߷ItK7hKK( ~t틎/QG72:Rtfp;38W@+#KeJ%MӤqoM$7,>}Ms=~)v5YYs5k)8@zEӨP㲢2 QdhC\X7 Ҭu[/ WJ. X |- L6']|W쪺*|ypx;a'X+ u _|WUM"l|] ppq0~w%5s-P0޽=U"TJ`y [.xx1<s+ zU*diC&LDl)`ԙ4[lhB]v$Š6 F, ;3k(6І\W|1*4[l6:6l`g adOTSx`Se'׍AZt0fxψ3TU +vU5jb a'SOו[0YĽՉ)Hl*mi ha ޛj$n~' ;;l4E!R#~z/^ژ/w&$}p҂MeCUJM zÖ+|]&p\fIZ|6קhԞ 8zE.M4N"Mz@@Wlr/US:|ƥ8g GP+MR{^dl2كOO<&Ru:~Ŵ ~z?!ڢr9(A&Ԗq n3gcjq bC:of#[x`; yeb`@PT+?L@Z)8g/1ӭiiCu}g6x_{_ x 1ߞ5fn_yF&eMI\+ I̙I]jߦu)׆^\k4{lwn)= V)7_X⤲Y'F܌ ~C_a`W#ņxXV>\gMD[. qco= F3<%hq _iNb-t<K|.{!UR#͎l*&[ #Wd?x.x%&H;_50_ ]~ufHJa.zAtU,\{ q9s~u]riYJLI@JݥF}%/oMUŽPÒ"Ŀ46>% 5~u⺴.5pҩ|a+\;cGgFuq;Zq69ID,(8_lb~|5b v.q qzJ%0gl<^'k0ʌteG_s eBQO9Mޡ?~ Q&=b F򲣙Ȍ'_/4Y<%kg1΁ϙqH]75I>kަ2cNJeY1qbңl<^ψw Dkc`?58.@P7Tw Һe򲣙+i2mU5RMZm6yѭ8\j0ݐr)W֖ۇ{;>?ӖsŎCy@dS\  Wgq^,qΉw ubXV4*OI^voL(w ҲxJO'iU̝IhUXJ %Fe,Β4[S9\BɞRP-2X%@+q@ZE-R;(5ц/(┓wґ#؟y=-G(?Х14E5v`#ȵŮ/diw^X.=I2oNJezm~C9Wl`ƃ(š]_(}ܹ}3rru4K=z"Y<%ݭfqHcꊺF#Ůh;/%#/;e?hsJ'ɾ??[n;J|}eGwѝ2])\mۥ).+ EFs\-ro m*s jsL479"zrW{u۽ 9ctI=mަ2[sI"Eϯ/vZ|liGpntDbNDyGCjg/W n\r-~DGDХF[WαdG[]G] Em*sr a$zAZ%;t|q]1< ?r] ?L+7c?̦`@-)~PT'kǿ8~~bh?XXҭ" xs񚎗Fg4K8tXee%[laɊڻw/_}z^q[vbŊ{_DZ7N0m' Iס O,˗/gي:|0M@5kiӦQ[[o䜘sBp/iʎfnM.\ȑ#Yz56m/C ?g,Zﶖ,Yw… &??^y9r$=P&Ɯ?P #(ScϹ曝>6l6lGZADpu@Ņp]CVfcϞ=̜9>}KMM^UHtn] W]o㧴>DqS1bgϞ96l ((TH]BBNիW@FF繹44$7`49.Fyw*Y4 /3pi@;!OYEݻl_2##D;tۖ >uV-% X{! wbmL(CoWXknԣGf3fhׯ_붪ٳ};99Z~{cUBP"TejqQQQn:>>p|喖zj_1!!ZMe`ۛ4@hh(>+;;ÿ{c,Ppztʑ C<n).agnZZaaaDFFzra42dH"uuu^3,(({XX2455>gRSKr) 9d\FuXe0K4y}o2"+5lɟ,Y"${׵Q}9sKO'"}Itu6~VCL;"[:MÜ9uCA7RDV\?@h(%vQQ^NCS[p,2G"۞=*en|<֪*Kcc#~=|q3RjLD<b_ԭEE6h18u43嗡P|!`vX;=p%'H-VjMVJhvdz陞WM ֢11Cm-ՉWSͷkp!aasRzF#츢5,aVoPg{e4573t0dtV;_!! !!-P,({\C቉<׮E;`DEV+7.NZxL牉գ{yNODCӝN0~1v Ea-ӧNm_C&'.Fspw3'qc=mVKJRv3zy`T7芄"Zp_\gy&LpF#^ hCBD@|^y k316`vOf)W_Xf&VBp [^p|io/[ [c#q| !E_FF{_‚PڭoA~$?'ٟoh48-Z*F'>୷򹭐'õ>'x綢u9֭[ǟg,|`u\#At9Mm9__WW裏b>?[R9pWZ.DT,?vlf 8`ڵt:IjqMa_cN[ .P8}9q//t~%q͌5?(,rnFh4NmO 6̯\%\iiZ=~ Sqy}H{H U vU9e {|'_oj=W[aaa;#GՖJrR'F}2zO b[(%- dUjH7$KzJaco^YY&Ç+Wcc#eeeU/7@ ?8} jM7mnKFY/߿Fa:z(fhjMVC?;ɥ( J%iHVxkⱬ cbԩS}m(`***rTP ]T- g L|wSZ[[JjRRR~bqnEEE~juvo>l)7&:%| Ao0##7<3Jj,|ѣ>N8A^^m566:U7nm93gˣDCQ|ߝ1`/Fn6؍"<QQQaZimmpI[R҂db̘1~hfQWWLj#nKVjX,L&ƍǜJ}ng靎#  Ao\ OQ :գ*!%%۷{IjZlB\xZ&!!m۶9qٴi~zknp|퇷zI azz{kEnłjeϞ=_JJJ{;PF#;v;dt3fϳ?X%4W=?ou~k.ĂZy{i4+۸6&3GۇQ x'߿? G[n!''+W/ϕN>ͤIHII/D?eoV~8Y=ohý/c |K ,Z3.K;1?X%ٙof]2RxqQL>oM6KpNAlg |'˞]*vAo(^E:zd bíAZ?u EWe D?]ai)6LŊ22(f+w3R2~~50'9<# AohlvU0{\r@p!9p2_kmO_}cwQ ?p(P{UR@I4g|J@D*M xk>ȯ?ș_<" Oa R149< +1nʍk1x:?/p$T+ kslG e&+=jOMfsc^3q?[ްb-PB?a*׋X}S2Ko)?'U8+_uzC5 6`ІPh-J\Eּ<)n{Xײ_eGu b@K;%IЖNԷ2rQ!?BIemˎqݻ~Gy#*E  l* 'H'W\CW7.>VL0<=оE >-yͤ|] 0"ҩϬ=[+j`X^K6 SMm?}C nu zŠكo) f>[.B_}cW;&U{yks9jZH 棻3.O!F.l>xЁx9'f#!RMn;hw|k|ha&Vf,~=.gFu5#f[K&oԁ `p7,ZJ7];LFM0bBS%m6[1qĹ& ԷX)#S3ewzAoX}_r@֑%/a( Y{ YhTH^"5{" %W Τ v0%WdF1g|FųقްRSxZPȤ b -ܐDy z1bnp3oW kֲ`k_/5Έ!]Sڍ; 3/Tu$ Y8YcfMa-wU^1` U1.+)b%7,[WB;dZzHZ N4rJ axJ9͊rԊXO⾼5`$TKǓɈ'eK`pZa]jdg8xDQu fv$E W3,%KӐ!rK *`7q_) .]".vׂ[X*vF70 pIDdȄxJ!&bBS8 zta@(!%"w1n@]U5].tѵJ] IENDB`OSCAR-code-v1.3.1/Building/Icons/Full-150.png000066400000000000000000000254251417327530600202240ustar00rootroot00000000000000PNG  IHDR<q pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F @IDATxy|UU՝F: H !hJ(s y#J:Ie CY: twR]^=U;!O uVos( Ji)%%JK(Q"XDD`EVoRks!n ;\ItZ @MOco=Y܆L`r  Y @ L@fW8 "Rb.= `*FX B$US638ŪO( BON 9[4{`Ѵh t,Ya·Y=Ah3GFD2dj<~O}|-$JVL?=2_OE]maSN  Vw*)CaCY v JdT+2wI4EȘ@݇w AMKt:/j:4Də^Sؤg(r :&-̕ubE\2SسGҽ *3y)_z)a㡃 |6a(Xnz zM_2BMh>I,q\}.=a}F?JPg;?% bǠ@P]yN6>x>' `Jh EnKkrIK/m;*9@X᳡p?28[7)hb oցgCS(XŘ+-?qFB b`M|7~AlpFȽVe12k'`4XQp]q iD0cdb$Tg-p"5>!>ٞ+FŸ gR'n<#c+PM6|A" 5,ZƌF:֚L VaR{(1 z t;Lp|f(Bz$-&! `5` \r@A|:Lk* _.)ql) !=Ɖn0&$edBz`1㬴@@0 ~364OA-g R⋡Ǔi=9[7Q+B>'WSSY_`>ZXWnVUYqP*$X<.VNjM-[NA3K$9ۮn4 h rNVe!Q*}lZp*q`03ШH*b,VGR` <ƪ[rNc2@J ; 3u(3! x&C? m;%p ۞+%ZpdXX&.\6:R TP9fRznSXz8k&}$XO[ GkPjIr#H'np T-V&%ZwL#"IN&B4I 7XC&<ı/Kv;V֭@m; µ~P"trNChp eFVx\,2|>m;\рhT+2<~wu^e[g԰vR+IX(ŠP>&\\6ws 3@"܆Lp2qF]gF.H!QEx&Ci<5svԍh=$lnC&vKq4m:P\*`@I#Hƒ/`C7$&j"BGjװ!XUYq> JZj1lK! ry)8#6ȹ߾\H}V*l{r=$$b(d/q~@2~}fJgXm}bRkyOf|KIQ2|P(R`IhU:W9mgTHx|[Ke-7kb?TjAzl/_,LQ|ӣ#"I[p`E瓎€t .øfVKiτ] nxW~ T.fhŊ V?,CjP |-+V,f t%KuжVy=gKv.b--*!Ye.K"jΐ:@!n*W*þU _-b֤ܴmZAjY #3>ԡZ!3!✭,!cP$D@-\ f5kq7NKval1!Jd;$m;kiueۭMfBWI6.MxW{,[Υ@-g 9/ū+[WepoQC;R8g݇' VW(`j ؄P(e>̀Rne-jYNdq2g͒grWyZHYE1H,J(XrR=1mޅ } l egUV%^n/3G= T-Ppf ,O339]6=&{:/>X,"2qn:X۬ʊskAb,`UV17=#L9:-R-wPe.;Le9[dOZΫZFcgsjBܶ9m5jﶜto3B{Kk= ,b9۾=p"-PWut+Ŝ~1V;Pi}:7=Yow3Bg8-Ժ\TJ9C2x eM;gbgkFԸ+KeV֚xOvZ5sJs]g݂~ΫrtYY-w3Bg9[$wRi뤜sNgYU:D5.&h ~pLg[sB0+d\SjEik:@܎_~z Jg xzj=B`Y&[]ߴ/@{e$jrƗDOяݷp8-ZYgtVkyL#|ZXY,}Sh8Ƶ]\ڭbgt0m)t0׎ƷW;Y"!jnCfDOMW^S \Cs_k!!K!ԁXj~~>K'̙3Xf _Nb̟?7o&ڮF / ҥK=dmpRhDOqϓ={`ǎt^ubgk׮ĉxb\vHx7K/A&_~?0VX+W6hIMBj'n0QyfdggcƍhmmL䌬[k̙֬3u֡Cp>F &`ӦM8~8N:%|r̚5 GƂ j*Ѷ [HXEq'>s05k 77:Nϟ?шɓ'ƍD]kt:N8ŋo=7mW֬Y(--Euu_Yj%jPO m]&XŮ]s6x`dff"??_sssd?);v 55zĉQ^^2햗aL67L~si"ʗ/uV3JR`(nrVrq#99{/{8wOw|Uɓ܌mٳ?GZtvv#G"$ıi(J\xW]wԩSqU~8 GAVVgiiih40 {R³$28G kڊBLĤx, F;0w\ :*Wv°a~ ^^r>ܵ\TB&{#K |>L?#22:EaȐ!߀vttFr큽n0`*++a4n6pw.BCCFK>6}r }J˜1cS[F#;B, + $&&zVTX ĀqOgII eo]\\6*,, --=Lݨ'_}DF]܌k׮!%% [駟0n8 p~ƷÈ#~8|%4=r!u+ 7OWHipei&feyuY4y;u =~'$, Np8z9͛uuX[X|Pc*CMϗ% v\lƆQ|pajkwidە0HETm-pK@F7Mm- f0%%mX_!hDb[:.\<2ZrVֆOٳ eޞBJ\g*Ν?T ^ ,5 $%ܚq45EEn;v̧BiS"a8`[~$z+M~o#gPt}7CFX5#N*P(ܦ2;DGGʗiдXn$1\$X% /. yV*bԩ#H:VQTTd$>.a˖-Xv-v]͛7wmMZpf@U ).hGֿيT*uY1 W7mٲχL]apiVt9$\䋊PUUYnp`xWѝVi8T|;v HMM`@QQC-_K<.*\,?PeqzA| # V_ߗ sYΞ=qq_ 0~nrr2[ViooJȑ#}~VRR`5:x!J2]%/b„ u m 6;ibYzX:t%|2∷[WWVXxC{[H>ܔJ,4ZE9Bϟ"FڭP( ƍ^ Pv_nBYg>ni(K Te0luybW}~cƌ!jllPQQ?:eYt$fqAs{--``[z4 R0~x";v 'N$nkk cǎaȐ!nWXB҂pdffTkAE$[XPL%JP(0 j5VY#LDɄǏ{- G啓'O(,kXPPP8P%hHް Jjyd2d2P3g; 744c;`+3VEDD@7?V5jJFl?IO`;h a]x5 o$0E}}=N:EZ[ˬ5g( P՘4i^_9dHWHvYz? E~rQrmΆTgIRt=Y[k*?a~ߦW:3_]U@@e o7JFnm>" >PZ2;n(U Ew6w'X0.P[-3"\ntOJb+ d4(+Su|_  xK>`{zWS:.s\ez+2@ES$`"\u߆IϚ&|x`~G=gj xrQ}oդg;^K=5P3` ^$ݨ0"NJ6A%Td U,Z0q/\Wc%T:à U0t&.e^U֫:_3%m7TuT ^] l=+{]ܵa pgwALs47 h T;^a,7k+N߳du1{al6pC7ͅm~M3ZSu|K=8$ݰ?@Dk'm+´SΓC12vZ}TqO>[E>||3``_GO]t'Xs`Zw4fPI_CK_.46A#:-O0!pI S#Lg`ިh*q*uko67&!.`0?rPv()d)LCVr8fF;Wľ&XM ~n~;ezvcB%\06F!CR21N:8p&6vrܙ6]t,Ul$i ,ecO oG@ eѷcVvVO-\1, jt@Os@`j-+Cn]nIQx"߆½0@\OXV5|n?;AR500@̷aEK,is,w\=QeaQ;})R H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F2IDATx}yxEǜ9' H]DYDEEp[XAQBAT%x `D 0# ӿ?f&&L yA'=]UOQ<σ@c\L-#OL\_\E @>EP C E#A't׿:过x[\rMJ%_`2hP\!W!"" bqY_ ` pRa;]$Knϡ|$ `̶W[µ283+]$k ctl!N8`^XB8 p;ږ#"_u.H3wM7ēG"  µ?%&>ȆDB{R7+3b13e3py)G=P'8x<ڦWѬNE;bt9\MjfyXy&Z9,VouP|(Pmh]ě`|pmF/q#y`hG(7Qg9 cUt,-Fc0a4 E07cPl/j,Ԛ9>W)75V58@Qmx+0~.nEp#Ơ57F#UNa\r3AqG_S 4~GQUZgs10Z-G;xWUv 9pBX 1H=k=8e!(Z=:_L}QFhqPrWuRi Z)G]+<hs_bN ]{$$8DTROry] _(`o%kV:Dg0 {26@*$ǣkּ\z=MHݴqO-:DT͗=B8 HMPuwZoM&Dxe~{F`Px>}: SÙ/>86fh=! @x}mk0P݉pP76h'z?FP+!"guYw4%V(˻ndB8ķLxψ6W>7A"P}u s̸:^ai P&][\<*n',knLc8MJ@l8k){Gl5 +}oΤU҅:r p8k}quh^O8">TcCbT1Ctt/btH'~KSlB]H ;OHU seC Ha T̙:zW=e9I!2Eo:*%ܜ`O@ OևcPʙF4!\S`7mՓq(MD=$]pb9EEcyB65Rv0H gp ܙ}D k9C.Lp֣,=94=M^.BK -^+&{D72Dq}J;RPs,MP4Ia[7!l 1GN|ٞ=9 D٭q]G|bgIafڿ2WpEGP/-ACVJ>STp]Ytsf@m/IRe46WRNu0]/ƖjȔmPPQ-6O݊-,"xNU?m넋!=04<$~] JzI!aoO9H&Tң, 5^[%\qRI{t ӻRB9mpIu7'[AW4XZ3bsik #1u7HpR"L DlD&A0mpt:_Lbn/ޔĄ)#B东T'AJ&?h[ UѿyKgKnNH N%Rsˈג QJ?T <;nLkp->3N){:T \QՑ#ӮBRA:%Ui؄#Ո{iC%s7vRʆ$25%]}cxI=&fbu}FK9ڇF;t#gKCpK5X=Ek,!pwr<*{UQ=EBX-QB=TwMRCuKH%$\P \x)Gy[ 4Q(4^rI*R#U1&x)G70IρdfD+ bH)G7OLsy%FT8,-zIəƐTͶ)=n{M HUZzoOybSc472Ē.-ҡe)˙+JGՠ%-%*j$=&g(4!gQ7cIn)VMidf.4q: F3F*E~R*9R 0e]Q :&`rRlDP܄E8VMJ!.tA=`w(w}J8M.R5-nǙ%qrNd$rK;;(PJ=^ZBdH`0&`P^Ԟ*Ah !RŨJ x'%7IQݞlV4~ؤZ"D͸8N$cKLsc="%\Kc$=:h?'է3@NRhbx~WbĘ zOb!fդ.-5@U&NĪI)ͮKPmDZLi i阩2{K8_*/4jbgBt J&, !ZYTiit3t'߇۞FG-8HM8 dϦCk"|ər>l3[P%/uZ'7TΑj* J O VaMM4x%x %Ы)$ݤ$g ռ^m*ORU'$WAӰ@^ :=:B.m~.k3=ҧyrN7 N@!.*x:JZr坯pw^|mƚe&*e\P{fdjWDWյ-N# &!d/kފfIM6w?T;_AR)dR◄r\EʥU9ڗZm5 4<9А<^6@]&Wd k#~OƏ7!kj*ZI6{C_vܔuE 1!昏-o)Aڛ;_^qIy#eKYHʹ= uǀ!B+2o9'k1xe ~rwbfo(6ƒ҆?zդf+|Ø{%Ԃ<,-I|V0ք;Zpj~l9!9 P'l)7|$ J7jb`ObKeT5!;A6fwOgF㺉@Ct %zoШJ3{sN6Y, 9IVylURϟºKhFXVӸPn( ljA6pV8{>GEbrC翹ӸV]7[ j²?j9^ fΑi:+ +>^UiwA\EJ]._?Eޓp|(#8{S(1Nrp^9@U8(~Ѡ_T)eވ~(1P~vNs=H4s4& deeaΝMCaصkb}9s&͛7+~vv6RSSѭ[7lٲE*& WVZ#ef;v,fΜkomׯ9k˖-]w6n܈ѣGcܸq/dm?//cǎ/wy_%v3r(o͏U)+XdQ)/~3gDvv6xGaa!zxey+aXt)Νl;=z-"K}]m>'NDJJ .KFnӔ)SPRRN:ՌO'Ȱȱ")NT;XZZ>ddd`͚5x'PZ*O>;w.ছn¢E0a8d"_k&Lɓ1rHYQ]]~s0`&N(fSVȇA258[c!ӑ `ƌ1bz_SS>7nl3<Z-[b`M_~=v;^}U0o<?[lA^^~g12^Y\ltkbii)6o,h'YGEqq_r%7o˗/ǦM$/2͘1#mXuǎ8w{y*..xY̜%vA8dyoiٲe( C4?$== TXx1~i&MӒNlTTT`͚53gJKKqSOGHNN~ᇱo>QvNNe\uoxK_v-jkk/{;/bȑP IIIXp$l6޽k̙Պܠ۫rrƉpr|嗰xǼ^3l0d+WDj\s fȮ]裏kEvv$/]4Mcʔ)^aY=zڵkn].fUkW/' uaРA>9r$"""_gٌ<[>8p *++qZ믿oy]zz:$ywYYY~9Or08Rfki9 #'v8ټy3^xi}$-::z&I'|}xݐ!C``[ y9rSNmxQ$Z\6\+pgkl4^E3(dH駟лz%$$`Ϟ=p8pIvm-^;l0,|Q~zw3(+ zZF+'r\;xTsz<1tPTWWB 0k{?f&{ll,E7|ӧOTUU>8k*Wj'rheYkx@ZZ_Ł8Q3>V+JKK1zhOJJ™3gD}^0 9-ۇtȀFjqiPtC"9 U1bfCmmh}=tVkuB/M6;{}tsةS', |Y}p {LK~eաcǎ f#** :tdQ /^D~^hJ0~'%T*,p[ V9 w ޢ:t0ml݊$hZџŋݻw54Q]] łn[n*&X\S%Յ ;vJ(<˗/σ"6)* VxEEE)m.Oj1p@qȑVYR+F*iZCmp_|z=)nljرc>}z@qI٥K8~NL)K/aڴi&!!n3(t.`0nԓr.emܸ.] h^b ;}݇&[XXX#z)<`CR(ԉupEQ3l??<.\{g,|4 TZAc@QP_BTǎ\;y5CmU;_x#ݻ7oFAc0-:tx}""(/)aXPQY X,|E`FΗ.zq<}\|=GbHj*ŝ҄(E.:{` > tٵjfĿ.L+kQq(~Ǚ35Q)PCTd$&lU _D K3$2 "~7QVV?pArZfCF]S.MMLy?>}&[+VC0)-WWWW.?l^`oa»7Ӿ[aJ8q"{&4b ,ޅ8tiߏa߾}ؽ{$K)0-zǍ}  z4T*jȥx%+&/<3a^$Jd.2 GE_DDD/zذaxGmJ"v؁;w=~K8*ʧ:u*RRRpM7I_Вk6 W9WK015T$\qUTBL)ZhGC9)$\tt4=*w1a^j&kɯ۷cxaڴik]ڄHn] eXbժU'<<. ʟ9jjjrJgY6+^z% 8tPUqm۶; eP 5­̇VRum,f͚%xf3,MYYY~ F5^mݺgA&ރ]V`,QD*AN3'Dyi"..Nj999{%m?>>l,pndkHy믿"##Jm^?P,}.Z)ʖ5~FC:СC0L={ ,,L_5 d繹iTPP.]{7J**|{2V<, (..̙3%7`NN i6  >G Nv$Ǐcܸq7i&Oy@~I8|IgΜQN:QF)~~~>Vk3M[$ Nr/,`ݷoZ䪔/ϒK7_(//Ǚ3g$ kB7S%vH!&BM5fNAC]N=zT3܄w^P%ۡ$VBm du/t^߿ų$]Ljjp8%\~~>t"IQ]]nݺ5tUJu-)#6⡸*z~%FKI;wB+ڇÇ7J$Nl3+_cV[[˗/,iFzԔ\%::ZQ7[t)Yk /%)gZ7m0G)`Z!Ks8|0#K<;v@]]&OSuR m Fʝ8q{dg7\|,6s\}ղ_VV˗/Chӧq9 ތt+V@zzzC%.̐E辋ۊz999իl}(IJJJp N&Rm64?3\`pUW&e=UL0n?mp\tr<75558t͛'[#""dʿHHH@Ϟ=ei[njkb֭m6[`qi3Om$rSb rn֭[l+WB} XsdϞ=kdkm5᪪>A DFFeن>YVp"&{`dLpTM~:PIxxxUfͲzΝkb`YvvwQK,t4M7lYzu;P#~VEO.܄1s:uBuuuC'Nu.HKKE6??KjGb$`Z#͜&Ԅ[ (.Ð Q^^g#FhϣgϞk)]wuf4iC(p/+rwZŋtr` | g"{37v]֋U`Bʄۻ"ݿ8R#E6bCz$VW:Bp0 snžlr`{Ӗ\lmB"CC'AaَҰ}v g*/OY?̫lWܤ؄)|}Zo3ᇨ[sa%VUR;TnD.g. -R#Hkkd U¹I7DIyˢaovDs|9|tU#=OvE…PԜ=j@vh[ג?/V+=*BpI\Vl擃DXaoτAM5{[6tۦUtAEaˏ؋Ķu]AU] {^ւyd8ن)RLJU&m%n0;6U|Y}쨬2)~w<61BΜ6pvćc|x5#^˧h0V2"!/le;)7SGKfUI 5VL EtnS۪(X\8gզVІtoU4PZgs&*8x4Ńe41Z:1xҊr15stCSU3ASPӞm>0v pPM9C Pc4S@{P <hncg>,=ଞ51HR 01qI*4 ^`3%y$pވ18EC@ m3 ^,yKgoNci] B0B81piN3nԿ>#G>}OHF'5NxqK[@<<ɾ$!\(H>(Fu @oጙm'.T_cEpJ;]gyupm۽H.tN2\Q @RsKi.mo/A@&ghMIENDB`OSCAR-code-v1.3.1/Building/Icons/Full-256.png000066400000000000000000000517101417327530600202270ustar00rootroot00000000000000PNG  IHDR\rf pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FHIDATxw|TeSI%$B fDA(+k q-?Wm]t-`tE -@@&3%{'3%~^;wfs>99EgРA 64hA4hР 4РAF4h@ hРMAC@ `rwН-A# 2 w4>/!@sI9HY88H(JŎ?$T  ZA $݀| sI8 # ihĠ;B}>7ug-Oqഃ(4hС> !YVrth4hri=5!ZA:[Z@b 楇?8 AplQVHQu#gVY6[6[kR`E;m6A l6Budf2lfd :HHl'gR edik-]#î+,V-'md3g6[mbڐR;!H$.4Au [d # Zlqh u4"@I7:lKK{h8RR϶pg(nl# c:" t1P=A 3 ء,@#)';ơ_Fٺ Y}WS 4Z;F`#}bLMQkǖkMf]F~4~ ._ ٔ[ENi=u3֨ב=ùgDg=6.` edk/LpvW˲|{;NU_@mUsx@ l :s㠨fJQܣFLh,ppc7z++mEl?Y٢0q=WN{A-V-BR bΘ.qA :L]2F_#t*~{u9U5j0t[wo22Okpm^z40!a ~>OSZۨIJlxO|^4~¢\=!Ux֞bש5^ Cy.48ʓc?>\|Ȭm^ {}}]ʃ(hׯC=x_U#D;:̻V$Ǿ;Iy&A^ҕ.Gy׺B1Àiw'#©ݙ>U |ܦ@;$DZޟ 9gwA dSRnǞɠ#)"Fzw$*H\` -6-7SVH~EUfk-Vw\gZ`VҋVOz j(uB°s{rfsfU=GL~&Һ3*9! Mu uUӱ.#%fj;ȑ >`wYULTL]Nݽ LXr"u̔~\ѧCB 2v٘[Ŧ*T<G[U/8|%KG]MAY.*؞a:3cSU-h +@9sh'&&$RU]FfFꘀ(zسDqTU"cd̾8Nvu^N.ϰ`E;>!-3Z6u5Pc'h%'xc2:W3g8v0|6j7MBl/#.L`~l{s[qsY&/=BF '%kޟxB(fߙv1郢ĞSP<?=mW}{u|qFOJvςNK.u*WXŒd 1)ȵ"ߺ}hǎ?{ AT }GJe4 < UmXvL}Kh*orhZKOOPΜm?w"/ONfK;Q1%r|Ս$okZ ۼTα"c؜[E6}h*<]5Zm\AdW3TKv 3id5c޿gd,GKv}04!O T9+f]NU#?nD[ƨ"u]^[vgѵ4ITMʆhЩk;+ܑ|ۼH<G{ń?3HGqyՂ=9! V׺_DC'wuVMWqN6A<4Fnl|s~< 2G-5V8oo+|,ÓB| UW9>S/ޚ]uIg4(}¿3XLkNpzuo j^ KFv7b[wRz2%[v"vU9m6t<'jv}|%}ÖR#pW_M)&}"j/k'#B/U3NI> BX}w0,}y˓ׯ<,I|]m|)&ݩL8^nfB/SWPwAau#W/;"8 k*"+yf6D^!7a/"!>HQ>FuwՄjLϮ+`)4y+"NohU|>$ub/{K"R/#_;c6 Rp߅Oe:(R(m^IykMz}Ɏbޓ \]o^Ӎɡo'yEPmW tp\JW60sg&oIy5wKQ5yb%-G~sj/| 9ߔB@MCD>U2l$rhz?uuȯa\5&HENzK=.q.m?b }_ݪEi;.a%vMrkp7 \I=~sAH x6/ݠjpT+{Nײ),xm9&0!%\c/7$uUkR8xzko0WLma\c@e߅ۋ9H[EkdQ[_ t2ۼn$ W Clj^j |%X Zum^z<0]hBJjeq=õ_C?VX8otț4I?<@I[<5 tUp`'$m^z0pvMZhw\5,w ނ^^E[W&&DxA3m^z 0ۋ9( LFj+WߴKnY=*]N"Ñ?4]DNN\  H <`ۼJd+,l?Y-͹4^[Q OTG&+.',60n6&'*؞IGYOlnSRHwh͉mrT `|N)گBR,'s8.^X?tI!A2X[$E@vVG MFokekFdafnHm A.prP =PIYS 5Z.R;~;Q-K'bCZYd`pu/tFp\.\:-D V0}:EQll"5fZqKAZ^~ZqJg5?f@bx6[sڅbio=ZrH^_Ad̓ձ,U"$Sߝ A3ɮu)GK׬dž22kD}a=6<%čiQDgӰh^rFql9.X^\ H=39bD#b9"$ !t `QX8<ލ5[^]PCmU^[ڜJ&,fY^ m/fYL(GeԠ8"C0/&ȍ!ef<.ڗ0*1D9"FPjEIcž2V+c4/S5fWpCfޖoT@/!}NM9ZWPSM,BC3&^P170|Z ሠ?B* q\^7>Eۋk7E|M!猉g|NLDXwj shU:".H ʊZWLz!]RO}a)߉ߔBTXgmN&-Fp,h"Мwzӊ}eŸ&?sViAZ(DNj\7(`kGKE`69c♿`zo>)b_Y{[~nzR7EI(1RMM>ݼ,%U2DFnY=8tfLS]pN K9"g3GĊ:m/XXV+c|lj5MJQ~og,^1&5ܘŜ1~Y+7;MJ6XW1㣢 ]sXSIC4ks*]}8׬̯a{}~m>sĻ ¨Ѩ#pp^o؝͐UT'GITL:%٬ͩlr, q|NՉ#b&khtFM i | io:#:o[zR鉡XgǹZVka"A˹Z@Bx'*̊E3=L=/ U˥'<1[\U] DDO;mN%QQoixG_ D~خk&O6}>@(0Oci,䣭S$/ 7.$ 7v! ,|&qjˍZ[/=)Dx`[ @LwyX;I=i|?ի]ƴ(vܟ7 ]8v[Cn8@:? 8}Ar'ǟ@0<.[؎h{_ܔ(W5'۴zc;a5Ru7E -%j-dbk%QPUojH_@9(DžmPZkGK=޵9.Ǣ/LLj?$*&w5DX8;鉡kN TODni{+ߏlQaD'N8kBH&qhiŒm<]W{}푶E {,h- mbk_@[|b-%1D'&6m_4hrNT9t^2Dmy\RS椋!žUpּ01I sy{'cwIaՂ~i]88^G@ @k:j\c@hi`?!j ¤F-GK}s[kE[dTq)O 5ʄAƺ9*CSH%PLD[mbmN%ks*Y~SJ -M8W3@qB-|&* 8\,D"P΢ÆC%SkU3{wjhY [-cQsd9N_EOwrt+rQ rg{zT&$@Y J. *V X׿na,[̧nBxݙ>bpsb*x񷒱/,EoJ ; zo,?8Y l]E=:uZavpO2=/) g!_w1' 7- 8@Y1 ސ%#6;ᛵ_Nxﰨ--!Nܚ@Y+|ZQ&,n|u'91|f @/LLU>;d !NdjqZ"jzj-gY^aDs7+ ˹}:$&wĭU:@K 3kF4rYQHTQj- 3#_]nݑs.Zf<.#Xlxg,ZK%՚$ ˂')Ѧ0m̭blp;6u36)Bh{1e7Ey]Z%ZyOs[8cZO-猉'%Ԭ!HK); wSO6{NO!N.+fx?sdqBpMer6VF`yˋ/o9cߞT7WZ. wm}sWӁC))3G jULIȝҲ3s^ UӓBuGz tq~f|yww+{.ͺYu[MF_3P8C^1ߤY| y -d~45*gt13&+k$vֱQ__$Oـqa.b|8[ypi@l"隒[߿_Uzڵ+vD׮]9qߧS@k RpPk D+"֭[ eСL&ƍILL_'--w}W={6<ƍcٲeOY`>C[ndggK fv T=^Z3o4%O>N+q]]F/'++ѣG3tPŏ;˸9y$?VXvkƪUXt)555>|[q]>?nիW3p@Kc['1ՈE{*`0ꫯ߿?k׮{ 8P}WGyӧs)EwQFq5޽{Yx1k֬Ql\'NdȐ!\M%5y"j=pc~LT\\̒%K]߾};YYYk<֭[ٱc|M믾*\r%+_"ٳ2Q_׮]8p ***"//_|-;/lİ`X_^N.@%/@2Daa5 z){Nd˹r饗3|vA}|筷 -"zO? -[d?O\\{vKLŋr2ˁB@~1A6+u]lڴIg۵ksMdf3=O> 4raǎMښ;\r%. hFʴ%VgxBG( ֣O?7x}#F`<իxZ_zrJY4GZSk~!gp~{JߡTW1o(δ8oPUoiS L& ڷ~+s 2ģ 믲^ 115ɓ'c٨|\/2#Gtjjj8}Z~<ń.2hCNO9+**{رcTUUI< ̙3dz0a%%%H:rN8 <ʔ).GR`۶mp {7Xu"[^*IA9-/ T:0zOY<+ػwKA[nt\N3<{;fffm 7 P@,@ɀIJ(|ǭǸ%bcmsrr$}W^yp<+j/J!uoQQQ~4!,[`=d2a0ػwƠ{_xZL"cBfbm̆9?ȗd1W_o (ǁ#C>l׮]7)Z)qF^ رc%%%<#^i2Ht&@Θl ܥ8@D>;??YwiӦѽ{wtw vٻoM4iӦoN(-3 {whmGnQvpN] #9Am{\>[l6Yx1Ge_HG2!?m7O< 2?pbbǣӁHId8ftA˷|Æ2x1. j!lp.1r47DGa-\" }9޽{9q0gϒG^^ΜOU7os|ErwA!vYCώ?g\{%\Sl=! "؛Ӻ̟m%' 'ˀCLəyv;w&(0&/2f ~Xv.] jdbO6d=z<.yXF@YIV wgChJ3xAAtIM3+4tPΜfF#mz:!!\ е+Ownr:|KEE\5z4Ǝ=/sWoGItAGq8\kU .ahB )bdNg!wf&=; -뭇F'.T!N%޽;zcg;wJO~a>9g+ঀ w8q۷ogڵ̕`(g.Y6ɬ<3Υt]=F[?8@{)p,YN:ѩS'nbbbxg$к6fBBBZ/DDDxlF hVeJ? i:%D]Óȑ#,^~Iu.dg֞K:$yhؼy3gϞ~3<8%6/ty?> b׈@.% tڗ_~9ݺuw_HHn˂2m4* m9rcbb܆Nr Js6@=S<a( xYR]voF^^Ϭڴ}6Mfm -[PUU%{C6W_~]F4AڧU~xgzgNJJJNK1)&22ңDuu[Φ,BFNsټy3 ~9u:I-//m}9q40.ʛKwHf\.L5nm۶m󺨧?dղ$N՘6n(ZkPN!Bw22V.RvW ӧQWWfkQdA[V:urujE+ -a5D0knjmx&;2V2an9 ]8ӦMYm6h+-[`Z}*6V\#k}\VUt~w `5/72ƸP#쒆}xAA("S8-[AkI_Aj/K 0@Uڹs'ˢUzg22 UANt8%ᑋ|uKl_n1|>@˗/'99٣&rbժUrzq!:EW[ `cˋ^KQUα2U6LAA=O?S)aX/W|[$E'&]>O_@p_; B?3OI{׬S-prgtUGgϞl6"l`.=m;`KTtgZd\\!CT5.gU_ʍK2{mIqV]F'$]F~&6Mp">\Ur#x۞Mj:tHw\y zt ;eg.Aw Q/6qصksh9{j\;vPɄspg]FO-/>g`E/q&I0`ӦM>ĈgÆ MBJ@̛믿zꫯtʠ+3*\1]5T~tMny=Ȩ疡ѨN8!ZXXW_س={VP֭[HbQT$2*j*qYnBedzp`351 4: 9f[%?~뮻NQAkYy'''TLI\9sժ葩0{kuYK{ u)J$M-/ƄGBz" C'ƠA{.貨_{5 B|MBCCIOOWl\.+WUεvu|}`ou' 22-؃\ wvG-hB|"v-ŋ+eWXThSM+BR䓴Ss}Jj_ ,oy19"kR#UA}իWUxiRZZJMM wv.aZVK85faE̚5/u_j\O^roʱtgͰ|F :_f-gB\ ZQQQ^P&ӮJZWff&|DjjjֹϬ6Ԩױn2h˵K=4%j)РbUA/% [y端ˁ]OJLLlVC>`+,,ɴdɒykn|"q.'USm(22+8xuJWB g֝XpMa`wh])aOqR^nwl6~G~u7kse5V #F%^0E?KP# ?rk`?X(~Aun>ɓ'Q ky@9s(>}6WVVRWWǼyWAAASB|=it{.$!lj Hɠ !ػw~@ѣGvߖ-[tMqbؾ};>kgWCdBB&<őHygS<1:=* %(22 m8:xd]y\$pg"Z'%gs=Ǽy{T $%%TJJJxU1&Oȑ#xwP VZŤI{T;<09#uğ|llۼ1v}dԫb1vAgޟã{z){98qdMԀf̘AZZ,Ҽ%ĤI]Pð7$@=UOz0~y#^oin/%Y _܋X]gﯸ3K0.amR+;t_}%9T ,H9Sc @ٔJ>7>Rݯ~/b{nv'Kj&'i' ׼jI>ޑB`/.#3'G`R P?`j\zD;ykk$=Go]+Q"짮? .GkZt϶9Rۦ< <* $2ؠIPԎہ>NGdl/ w.#3 m8@j5 H@6¿~DӺsr|<<$@I~5%/†U&uPTP><@L22o#aw4P~?*S(nd[y"MƻŌ|S߭:prPE8\~I|ުc#I$|q su7C% NXs뼩Mpz}J*'C $uF-<+?Kٷ`r2 71;l5}m^zU`]ɕ7N)]}q&c=,I%_Nƻ0`VYU5j +dcw >xw_97'?Y6 YwaodI"QJ"&Emo7Ђ ~ip<2@u>ƮSR:'ó $Ң(}rXw(p@ ߆Bx{[1=o/,$ M2,NU6H/LLfŌOw4W@I_@7W= .\Ӎc4%ܻ82CCn9=ZHA}UgXK;^5yA톽-`wl-ybO +5"hOg_X+,̣ݪK229;o񰝁ǁ{1Ü9.}D%pgzGJ;lg O;%t@\?їNbf'{"bM(+A4ԧώOdhBHcef9i$.^Lⳍ& CnxB@tT2il/-C;#*C {KyskyfYsxROuwK"϶9=o + ODCuRhE2O'f ncef>SmE/7=KBe ;4/0xX@Əd3[(WF2,!Xp*VeӱHô|zS Jjx5Lz u.F@@$08>=HO͙T { jtDY\O1oKoA\2hvsu:@<dыqLFTn $8@Oh  3{z)6[f&F-n^=!}:^ s\SP`dw.Q_=[U;v;h >1 n]/l:N)j |F._{\=pY+h{&a*ErDSS#[Dp+05G܆NW5hR0LFE̸RZQ|<_#&SV\k(3xCOŞ6Zqj 3#W!h/zfsWJR|RFw)לs\pXk'(fiZ0 .Gqv/wB(<tb=y_Ni=on-=h/6/aф< \ǀ{}͢FRƞm/7&wT f-\u5"I}#/ sOt@I톽4xzUSR^ALɡLɟDd IaoF0p=5EƎ>]¶*cU 2]ø~`$t"$55.#Lq2}〮U;rsVUTGni=g6^S0uĄ K0F24!5 jBx|;Mx S{g946Z6[-xEuȯz*T--6:4 Ш'4@ODQt `p|0cD(&c;؀r۠r;!\~8pLM]o͇FNU5PVkBQuu6j5[mRob0[l]m60v=8.졁z D3l[d QFud:<oAݰOdr=(qA`%ATA(L8|Rwgr|9[_{5hG y5C+8`jwtRp$ KxFXUg, tM@LC7tuh "{ɳ NWE9lp] a?+qv =4kA64hA4hР 4РAF4h@ hРA# 4ņX@sIENDB`OSCAR-code-v1.3.1/Building/Icons/Full-32.png000066400000000000000000000103701417327530600201340ustar00rootroot00000000000000PNG  IHDR szz pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F#IDATxڬl[?~vR0=:GmiBn[C*"u?D%R'5$*( S[B(kc(qMqwgi#=s|=$`J,o]!LFƉiP YSda]56Ĵ@&<;Ypy"CqLXd3e2 U ;2?rs?7BaHBr * JOymǷ FR8&Ü2P`a.JP`_}\g'<&2:?rKڃJj2[pFiZA2+$Y5 >twƦMZLf۱mzCF(CZp2L1 'hdETXO_p"='R˶cWwdX 'z*`S4~ k7>vT{f[/萞v+C)] Hp=5t&rPmj4Blt\-ߒAHQ/-zSӼ@D;<+A^ pQ_,gGf6b?|)EYgBq^"&IɬWje2Y3>QTUөOL*_'I+ r}j UUp 먪J02FɓB;$ SF|8aOrgX UU{OԾ}hlvq ZeGUUTUMznNO:̎ ӃY]`<[uhiԩR 'q]t@aOOhǀ>WWTUlْ\`.G?lyy٠K|/kM`6ެWDO1Xy={ŜFVKvhĢ2 x)MMa闉$t7>%whgf>9X6\-\Bb>O&ѹݎ(rx4McvvR}\$!QXĠw;FDe8:8$IpY No::xמBOfλSI0ܖn%o~c_jH&]{j- \v6W]_&r~wEh::}wbG] l2L)a3ե֒(_sWhE[b*caU зdjs`Wk0Ϲڢ H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F IDATxڼ{t?oMҦMzh!P"tFEDl7q /G[륬 )֚LWu>6hyld7@oqT HJa^?jy n 1׷QȒ  D(1W@dP37 ߨH !c'I- 7@ ZWl$yOL[#EIäStrO捺cT2JR*A^t[XzkЊXhŚm`?.L,#5-HM yIC_td%3sa 9Xs8\HucЧlyȂI$Ng`F_88\32H DS&8IS*UMlyBSX݊*j)G|9kSlŬ NΡZzqNhAHCRGD&*:4kk0ot'`)4S^^czypUHI:mvK&HZFyU3;塺чE&_{"nv`)) M_; Bc /Rkԇ'[dˤln>YI BHFՍ>Jf2otnoG{pҙ>8*Cu5kP# ! w‡֚df^\TA;͘tJ2 &aҩ֋n>&,f-f^?vsB+F@usֱ 2p^$RJ6tQƚ҂!&fQQQ!;omQ0u-1_;g+/@V瓫l6ٶO\^~>(IĤS&s``FBpP!%p6"/lݻal6eͲuQVVÛ8q"+W"' E 9z3-,oVRqsF yˑ?R;nȾXÒ@~TII2 [wP"jׯG?lX7ƍ0f,/{׮AVVBe~x#ct`= 89FWMV;@DtWk,Meп?h(]RqllSk1x 67Ci)< iX+ < Mʜ|OYvj|#il Bz07}:֬aȭvN²+>rdm4V`ѝh6u*K/g[cPXfhoO2euQn_EW)@SFKMIk v$67Ys_.XZ$5bP8Ý*"jbbVZZZ )))nV5UiZ4z}!%rd- ;*~X_ 񊋋t!^"++Kv \"Vf@g\XqxW#RI7FnŻw%(wz㮾wVǤ#Q [;ވw43p˧5͌[wBvft1&Z-Zml4zQ*h4lIb~ZN>?0Y;wK`)໫OR>COl;CnfFQVxN'+<=7h!}W*~lEy)2)lph`u全RkP``w~(n 9pz,Yx_퍩Uk*xKSP~>^ߘE;IEa<w>[{L/=s΅ ,k 'QP&ez |Qz}UM$x6XH*T)g!+㊺z[*M߸yzxIKI)韞Id$kt']r=G~!}xU;^Ik' =% jlu[5 ~b1֌^Lx]];N\u-u,J&&瀫9u)>t R`bXVK7M~ I)J^p]Fu-~.(d4^'0(#ZITv{+EQcEq(U(kQOXtF=u @20 ΉNڭN 7a#ӰyOIENDB`OSCAR-code-v1.3.1/Building/Icons/Full-512.png000066400000000000000000001252271417327530600202270ustar00rootroot00000000000000PNG  IHDRx pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxwxTU?I!P %4AiRĊ{ .F+Vւ₊*HwB{)L2wjy?#vEg4A!A  AA  "AA  AAD  @A  AA  "AA  AAD  @A  @A_@4I\fD@C4o8.."Ap0 2 lw=,}mHEh6g]f1jf`T5@]^)AADS;t@0y / c  LOg62D(=@OA af =8ɝ/h6L;`SP"ASTĔ.4n`+/^A k̻ޘRA F7` S\L!D8s.[gY;T]Hm??5چzG H h ?: mADz?M#b)e4xpC/CܴF;(`yj mFZ_MCkՍm+k`EmF[շRRߊmFZ Ftpc3637= a _GL?ÓBgtJ(`00lvsy 8& j4A\ͭ? 9*LȮFvo$4ȯhfCk[UsO]TA#"HOhɫ?. =Bߘ@& #, z\lnxLg;z[DZvoף<& mԽ.tM|?Az₉ 3#3`NNx67x1eeT@ /naFji'*7kQ=7RZXp/~;:`&3%=AvĐ ,vsE"1f?S_/ձkHe5Mr~:Bb`|0#pH4A^#FL"ڍ~ϯ囼װQ#$%shfwT(u(<& h-zcL۲mxVWTzqѐf<^/@ G t?[{[boێ5_?)0gꑱ=*Z /D"o7A@0SQ0uMbgTP.QT_b&X%@ xƘw1}l4r\ίEbzzE2)5cb9=c"sf@ AF?SGӁx/Gx2>UfBcxR( ΉS[cA x`Jߛm6ps7-m;X#|+`p4wLLO[W`K @pFgyʦR6jzH `Zz$==SBQ9rLY[j}AJSs0R^TJNa=b 省Q,Bj<'{fQ B@ 8m㦎{n)_JP}A !cJ2 anVj,~A?߂s)*Z b[u0 .bIJS-'"wiўF^Wk9IA똒=r]| |(B@#lb sbo5~[ĆC2؂)46{&C|)@ @ /%4d:G=˥^C1(D16.[a6sחGXW#-.`@\0uYq|Kg~e"oC@9X$-n &DuYq<6+{U`.;縌b&[a_}+ <7 x@G)J%lTC`.;AFYmL8Lg7`j/FnTnO32#yTzF-ˁ׀u9dEPԤZLNGW4sVL"At:+5=>c"5~>ϯh /N#y@3fCs*S.H1B`FF$oM%1i!px XΑl"p2YmW4O*\7.z@GbM}C Hx!~uh4NGuPdl0RFuC+ #mFh3ih1*m6fb嵙xu?u9kddE𞉔 \{U6iylufzH`b0^#%*:6kZ(mxM +9ZGI]+ ZF*$CmqLϟٷ*o?,A~ 0;꣼sDA0bB JaFhCiWH^y{JGj4PJ]x?=+?t7oЍ'ټw* ǹwQWx $%*EqZHzIjWĶzVף ԵRЊQ4W32eWd0S@ @'M0n}o1ͫRT"{ՙ ܁MB?!28vr jro5m Zx BkE) @{'L2pp pf.?ɠZ*)I\qJ,);{wd0rwrO%K%II,ә9 |)Q u%SbY+O%9?zF%p)\08R#ZR>i -!4=BY5/) 4oHA"\3IK?}ojT5NK$F033;&&0<)N' Y{rz55-\33#Y53o .;GEN49T7+Gu>1A\qJ đ$KeT7"@yr?~}Iv-Ja 0b[so: !ܟkGq۩􊒳|ƃkyok0W#:ءSf#q]vNp:/ٯcwIO]xgeFr="I44żf7ױTѷ܍ : ~.@|~'0$!;&&rXr/m_p{SYG_^bsDT`#ﱹ<* 8p g$ ]% rDbBxt1u̗u_Sr?m>ݧ%:坭4J#r޻,їDO3}͜>vYЬ #ͳw(#hW?"%ҡۀ;|߽>g9nj:_ M 橳{3-#R&4kj8 gT9wG;Ou9>wCD3vGT6ꃃSECyL.Fp9:)2Ȉ`u)TBU_ {?SI${_RuuFsarZLT6_D 8GRxnqHxxʗ-Sap߿ 5;>с>?J&qW53Y>k`8Oq: _;? 7pl4pޛ.Vc{`lLx $FI8Ou9DP E~h/rY\?&MH ŏu\j$kQFAt= M vs77Rƾ[{SBa4+9oG>KQG-'[QYeE`i/gͥ%l bA,G#vd,~Renն6;b"G7%r{F/c tp޻$Mj 3֢z {|A*e|- #hLo.gS;4gYsC?]._P5ɑlu_*F|n_/M5{ΔQ?S h{^` 4vޯ3…b8|)LM*~vfd, A2vR.y7ޗ^ [v[/YC~.K'@N Qz ;p{Ciz?S)˿`g9M 440wL gzF$¡Ҥvo "D/W^hJL,#?'?b{ƿ/0v\TϠvPX 3Ÿ&&v/'riIbB$@P -md<MFkn[pX~u&OK& t`t9P*<%,xѸ8k{a7MI cM9o%|s}$Z v'6.J㟌]in#ݥA1A& I!vȬ?`V#ÞIA]ǥ3G5Y.3 ߀k5kfƥC 3MApIi{826E@*DE}YRDK6Z7ܡ1ǁ2!IB^ES"?Þv~掬y2;wcStB~@ ACKo':SI+eD8jC0(Np5Z)k3SdB<0 [{^KjpgagcWsJc'sT'ީɼ97MpU1 +'?S$r4cuFxhv/&;AWk׉ 芼&eK/gePHq C q1: OO ~? z澛gKF`J!'&Ol.̙ݓ BwpZpe(=%ն+>^mh\ @ L{a7-(33z=%I& t#1kS ?(\+@8a*s=QLK^  :XoHKn(<x8G7(}Me /U $IQUD 缱ʫ"~3AB XN4Sbx2EtJSQٕ0WDH^ F F=CզZH2d RZʈgݿ罹M}"2A%"`J`l)^)0] WOD螡|wxks/o*Uzy2xu HKsI"HAmO k]Wܼ{K*Na3?ztҙ[ڐ Z933fH09}{^2ߗ`0 (* _wk2. !*)I46^׵rr_ `7\&\`jps~YA#x~N. ?o^+kF@t-QY=pV|0_?޽"9_S9%1DZy^qZf4ozw`*h;+Y[1q)O  \1 D\ `7gX~HCEt\` kg?AKӤ@PT7JjZCaw45A1q 0zsx;Jä Z`\Qr?*^;tFx^ 4 đ#H$Zی\񁢍pqqjS_<1MM̑!5(ZPA\ª tT)]wa (*pG5r{Q< @!N-S\!j,,jdaڃ5ϯԄ<#=Q=C<}nd N#uZTT6GWZ澫??<{~_y"Ap9z1%YnXbQM.VrUSHI Hb'{_)4"\\Dۺ@NgΓݿ  d?P\ 5P`p X?33"H!\2+3,39GYL&pffjC|tE BJ뼽S<g)OinMO|<} xԘ@E@Ė~:(lnp`` Ԅ$O 2>'s#J. S WdHyA(z.#q?HUKg m4#sfL)#Y.IjEF{[0z+ϟ䤺yA:/tx2Vx./u8+8 َjsQ=[ 3_AB^#OZc\"s\x꣚L 7 ^IjL SR C+0J@Ll]_g47t'I A8rK\ \/z3k\[IȐyAZF&/6H-Jmv6V6x{s&'n/'wj8qwåWڦ㧃S$ De ڱj_F"NdY 59yF'+*P"ATäUp{Z#x EkPZߪ3oA=<4[O)e{E` {f_^DžEM LN hǶc7ܤꁫE?E|,RuJbx<= 20yE)dfx\QA}Ȍ$:Xۣ&@O9<})J.\[q(yjAP%7I (kezR_cQPoJ%*$5&/:[@Q`qqVdw}n(4;Y %:XOVr D;^WZΰuQe秃J O2`{+8Vk Wh E/\I`yZAP5׌%P/]L--J֏9`vkvHyRASӥMp{^Wh\k4[W4SP٬ rhyJANOAhǮFZl.50H#,sK `tPAfOwW)~"E+vWjvrd/gf hryonVt 0@\TOF\2DAS&@{6SrY20WpD?Au9r/3TJCB@Y!*}UI)I!t9Ȑl'EH&ٺhsQfK OY h{C |W$ՔC TL?hzB%@m2"YڳXmF[34+XRQqTYdcJv&[i=?_ )+iܞ7)8B`8@]rMO!HlA4E$͹=VIX0 EoD8%QA>#E}eM, Zp(cA|kF i6Zܠ&Ow&غ蛼jMO #N@P- !@?[S '<_A4OjL 2fVPT Ƞy @AߦzMOa }_L~ekjȯl&V5(~!D3"9Ԙ F$0"Yʃ /2o8o5*,lՊK6ڦ ?! ]ͫa NE,;X;a0%-)i"6QPL. .ђ7k~\:W6cJ\n)j`KQOo8NT97*)iR7\Q2f{yBpY>Q۸rOoލ)KsX[ywPh2^-ot ɼQ1,Z#:XOfl˚d0 j^.~.l4Q7>4ЏPV6xjqRe^aOI}twV !ZdF Vex 'Q< ͫB+.-cњR޹z65+l悷09-( hFKd c-OgnN>P Lu'o(E[oW侅isy{ p>K`S?sH ;XÈgwT  pfXhhicv}s@f5>Ek2]vkGrX{C捊ujȿk]j+C \n_yXV A=d|_P~x>J.<W,f~E3#!z 獊%a7-١?8ˊ!*gVdXpHQ-╨k|⦇gך][3 ;ѓ_o1e"A7\#<"`T[}J7<,Џ$7\[ԗU'*X`==:#C|`;p""@9e״(}~Wk $,е}̷(FY{Cn K dE{$&@ItpPdڦU"_(yyzq=zo ^R+@Tʨn^{6#PdUD/争hQ^ϵk}Ӓ&nJZÁS邞 x"|O;E evG)g}b)>ʗDT% QX6I Ez&'n7X{30/($:X(YgGA+emx;@o%_Cu>q,_̜8EgڲF:\!qњ"APb VE7m7w E[(GRsѯsoWѦ|ތ]/ 0QlfMD Бx Ek:T`ũ^?>δ/ !2@$%Qܕ(pOovo.w(LSҼPF:8(G/ %K;.G Y>w,`Rʻ~n[ς1Ą " K)%Qp.7<;Snɪ)6<O|B+yMܽ6=#,~o đ#ks{scjfϫgRBw4 `ah3@m] 93F `\c6w?<9DUڈP^1|g@^y/r¾@5w*d8NJ";v6A`Ls0=Z*< '(?^a7[8)*@ es.)+.rP<7y*5G'$jF8q.XPL~v wu>]~~یGyƫ.V4+2y1\ (veyN?}`Zn;tO zBEk:g k0/59Vt@;s>PMNa؀tNز]fNa=3^G=#=N؀)ܵ9CRNh?n(oU_>†þM qW;@<0l򙛜igk~ECu-(sWVҜ<ws{ByOψ A[D@ nY)|py:3^g}l,aR*ZwiER|VZیu(>UFٺ]>s{Y`7G<- Oq8a I: NJb$nZ^ЩGS]}vUB^yu'zizfǩX<}'[gcgxsfZY8)!@5/2P2STVz!z`*^%{K}|Phpg ttT82_9}J 0G9\^텓NVB~?l,m7:WbB ͬP/k^Uܡ1~6fԶ75rG` J{* W2捊هQ/+:4&j8^w ;]/f|<4+%w8__ׯ w]}ڡtoXz,޹QQ[C(?+설z S}cN}ޜA> 9"8C`L[XܫV0%ܵ}py:\2=#M;MruPHyQ@bWb :&#$`z8n9uvBVtD*[\q7.M]^^Ԃ1W4.w1!z6qG*Z<P5J;Tx`y{KW@C5-2(V#~.uWez8z]``k{m*A *@Gi'{g"hyQw; m\Zcl]Gʇؙ?9irtareJt;{Ցm ] qC7cB^Qs;9ܡ1]g%\xoTs"L4h-@ 8mno"m]1샐SXa gDt$vGm5gC|{o蜞R Žc+z]s聂 j}F}竅 JRCdc)3^uAJrݱTfPmeGKW г0ɮ4UWܴ@Q`lVJ(\fki%Zɠ)Kw{Vcu->uc+x@HGvEnUs>Z:%+$ KtU_H)'災g* \n"ܡ1l M$@q.(Zi*  so>˯hv:ҞRY=CJZ} đ#n7K6_zJHxU4y,SkL]S31O{Je+B.=p@_?1=3ҮstF^' PjrVE F)rp{n:Ns#FR[҉bJZ vACLm%-U,a1.1&?݁zJ҂P,h]XTX8).<wlhmEG~(_ل  5 '%y$Rl,%+%f8p_<JZh`]3}&~/b.y7ϡڳ_iWaDKs5)) 6)\>U  ߸P,Yq_俞v/onwbo%::Q=&A ̜E).pla9hVz=^KY;yJ C]dqgXh޿E'*:`Lc=1qyBeGnn@~E3[09-e‚1q,XgE%K r2h|ItTs<tt{;9}x|Y}%K 8Fw`lݻaA],Uric,kQ,#4SFxU8Z p1ܰ[7.z nx;[Gŕuw`.qq]]P@7{+`]ԭQ/݂bĶ`mRuWrfWvrnpR9'{X J~/Ks3z!k\Y)VCR,S[Rc.+ zr o,@@QqV<RǾ3LhJ *;Ȣ3zzZ8)w&bݿw!{wZ\߂;:yK1 {btU@Sa#MQZo{'L1W4i sh(͛vבA^6ouTLlwx^?jO> 04@ԈjԃEuQsb7G~SgRRsL}jri:2sƸ}1Is^[B-K']:w*l_l-lgzFõJԘ]GzػSW.,jDKS0}FQ], H d"ن:Q@^_=7$۞]# '{s 11<=#r L !+l@e6uQz_zB^Uȇ*1a/e^`޲|_sh:ˍEۑCc*): 6sWԿjW{ܝ{;n*=0o@M4QQE2z9㑪h06}ڃ5L}yOڇ?jZ`"^І)%uޘ[]W5Xw%b› ;)wx'KJ w{Tcn 1wXC;~a[:1r|Y2xߎoTR4mV7ȣ938e,Zs)!jFrD捊4//&%^Lό+#zLdcR,qVc 9k|s؏,#0= ?N!"}pJ斱bWe9-زK66/ˏ JDA{'ŗ'. V"b8X{"Z(j!"} #`_{y5n LN`JZ8sG蟼klszR]y-W2NW E w|llLDtoQ` 4Փ_L~E3MA@Rc3"9ɡ x=y֪Y(]xtL $5&cw 8E2w|JKGU4 @haI!J.+qgJ(vJLAhڵk曹Yj Hlڴn4222/J.;v0GZ\W_Oxx8ƍ###CNX~==1dz)epN:5k~lK/1k,>cBC%=.W\q -77ٳgpBz!$CiQotI瀶TҶmۘ2e f⦛n+dĈɓؿ?[lwa̘1|2@ퟃVOn5_~9\r VbҤITTH;Y _vZ8M7?;qF혪v:bmopC W :.QX@mQ=z4֭`رݛZ׿ʪ܎%KO?֬YCpp0~[2Phd|w;0k,%fkp6nɓIOO_CpB`*$:ğ@vٛ@ 2PFmwJ3sLf޽\z x%7:P 65=§ Fs}|gu]zꩿ&55?g/lMz=ÇFjqWxzTUUy7:_?!PЗ뮻{ɒ%^ƪU7|:FהWi1U8f@>tOW~nv eTw}G@@y>y+**^Sɓ'`N5^}k׮;&%%Yt'Q*QmCW RZM}Yviݩu>-z?ݻ}>>Xg-//A^NX|ŋ6Lm  x[| `>ߕi6cR}޹y /y 73XhGh1N}mn_:ۮ'saʔ)^7sL8 fYXX믿Z"tM %LW m't6.o!PjL LfYƺ_p!4~ao/2_X¶c ~ik\5.)!]FYg'|/b={]m/4h&M뵓&Mbܸqk44hHY9r1o<Xrj~{y|&(I/ME\-\,QT68E"_~à)( ׷ߕ͵zL> l2jkk5=^W;8z?hia{:'>뮳֟xU}1 %RzscW j%Ňn?/JDqm,.xҥ/jYuwmݡ{lTr=.R4_Q^L?9 UYUVWl?NxwwD=zW^y0gY؋Nkk+: ^zԬ8sдؾ}Xș֣իWSUUZjw644'W4kCjJBe6xwٲe45Z;Sx=z*lzZ&7o| ?fZ|V-K%_ `ŊlnW_}7w\[oe6ggƌcYs>{q"""g_5r?V#r^xU\_^,@d̀xut(ںh|0 ts=KtpBB3L4P4KQ>}0qDPu/iiVR!C߿KrlRUUŏ?4JKKcԨQ.yt-+ڃڢW"Oϋ])m]ڌ}~ t8Srr2}8ۭޒ.ekbM>|jp,!W``4ٿfk˖-s}SSSIHHЬ(W;c{uowPжpJZsK-[fuN:e;k,>35sڟѻrnu}=ˮ$%%YŪrhw饗}cbbL_o{)3"\6Є`ͅuٖ3DE;vanmeӦMW~)`r?u ֿO~*pFMHH~W/KLZZKG:^lܸk׎t0$f 3 EI=|)nCYnhqHmK+So>m*9ޞ(kzVz߿CLio9?ts̮]غT .p\?NJEIiKjnA>2/_Nss3v{'&&2l0J s[:tȫ]JϬK&Lp[(WVVjᷥ؛tzcw'JQU-ή15/ReZhl|M`Knѣr ?j"/`Сǻ-ƲAe-kn_Q-q..Zr޽^;|N>Gj8ZԠPRoj3ʽuɕXO=T|e󣱱QlY 3 º:G>|ːM=Y#={X[B2=֎ޜ:dh×OQ5 c(\׷=_l?ܒRd>#Qgpm:[{f̘aO?Z.sg 8Ç$~k}-{܁͂wt0&%e t(lduWxwjS+TS%pϜZQj܁}m~K#wa)TWWZTPP`c`)n.,8p@i&*+Mߝ90ZM,^@o>ʱ ?TY6stK G<3-D `w^]].#,mr,%[ci 2cw LWuK KPơ*uCZL ~6q`Afq=Cuy&f9wbd6UرXY63R^:$$X FerСn<7Yri!#55` 4ܳgkƍV;{țJOx.׌TV@Wi}%hիGQjpgh׫W/,YOy\/egݍŀzAS%0)),-[PVV5cQRMBd]| |8 lٳ=8hgn۷>ӂڂ& kL\rG>3>>:TlS *`y-xs-G%vR1O =u>~TM5*qF}B9wcJ^üݿ;rL{OgD/qS |@@sgKo{w/ikI?R+VHu]l˱I@@jMyaYhG?}`u{u]nq҄gWT.;[Z{)%Įci&~if͚e]ݙe;#;;0E{rLFU>~8?0ƍs{G&u?c Zի_|WI~o~nkn$IAf9 XRR¹kޣ#,,̺@oݺUcw[[(۷Ϛ3sLgw5j Ǜy7+Gl{+_O @d)6\<^ד ]%psq6l`ذaֶ/Gϲn=Ȼo:f\v\si>|8?8@O7wvjXnky9>;LwHm?n~@V=xic)=UKۥ*" FBz 5`v'+ w`kk3.9 9tDqcW_q`OУ̥PPRՎ}ߓ ew"877y\Ci)}CC!.=r>бcLHsϥr@ ;[T6K*hh%&ےO}F8~8| yyyj?>ꬳx_b;jNwjkM \zZZq8vCŔWT?@cݿ* f,7-d'әnaU  $,*|7 Xoe}T??HL4?TTp4/£G)@r7o@m-yf @p^VweLUXyWDDp֮]$?$|t89:T46bg޽Lҙԋu: 1c$*>̞9#"]3 d|l(,d_}=-Vmmг4axxoZi@Kc#iil.Z4nݖ)y Nshm3rWWjW7zr^|Va@+dyY@(0np.8O?G9X*;2$ܽ?sQ__'4tmkipLwoc֭- r8ՙj`pdJ`Pq;@F@>WƈH 0+.6g϶ )ɴ [j@Ųg?S<@ȇ33 暉2}i[[M\yd0@|v7\;ZZZs(*j{[8(4:nFh3_?bbh8e0 Hss3))) "$xs3׾&R4bw7 Xv-^t~;gvkj~-?%)PݔIPT7[2O %9' ??ȑ#1 DĐdZ[M tcɐH·E` ػZMJf~pٶ+9e wy' f1p 1vD #gO C 9t`[vJnZ׮Xkn_=Ϛgɓ6mJLBIgqr"# PB;[U71s<^\r W_uFoJjX [_{5͛6i cvW'.`ً) _u~==yg,>Lff&-az=yY :gݻz?aa ՋSGͺ:psvhM[Jm-Lhy5q%CXxyVSO?YkJss57]93߶f?^h ߏ G+nB%`fHmncYE7 _fmC%D:n---1n-6XFK;H-[p3׫vRU9qٲe\~5`Fmm-UXޔ[ouj+ _J7Y!@`==##7$B@:mi7pfK/ÉmmWsmmm\~/TkҬ%m-u]Iyy9_|i1>wyGcllT;s|W_=W^yeB**WWnfLSy!C&cnno7ް,Xӧ'`׮]VJOOvtG{^{MkE{/k ,Џ(o{_l]tj* `4ݲobNLLਁnlWA^,wŠȤg}/#Gjjmqu^{#GLql{^pBpm>^8ج {Fx,,wqs OҾX@kuy L%~akkX5cl˵ԣW;n ^h40\EII ӟbn1QQ.op{RX;?΁Z,`ƍLn1chb^`Æ 'DxxfvʷvU=C6WRgAzfYj-ٓ`Yt`ŮJ_W^JFѺ# hKhwӳgO\555qwM'0yOFK):?%ۺe(^/;\ng;wAnhh裏/h -.{h2>[K,q&1۬WR[+z~?%"KF$GPpuF::w…S%]@xbhw1ӻ.F^^/`HJ+bL-[g}]wݥ8WbleOކoFLM@c wimAe-b۬5w2+u]GKK ,YDeO9^GQfMc.u9{𓭋.c+KsT}g]yyy<;֚s%222:t(SuV-[5\5\X_n:.\HFF&+55*Q# X(?_URQαˇ @CqqqVWm-Zt$(ssraXvgxJ:h4r]wzi Ku΀r<〩 7 8_\~\4$A5_.יuvg͚ŨQ4{-lg7rssYjW^yuקE,Δ4_GK4gK89qF8`#$ԜlLIRr]vZt<[];2SlLJ/h=F5LVxxUWW;|G&:R;NZ Rwz[\D{>0J%QŠ+0a>1^ʐTKZT1ѣJ+r_OSW ,s^.3$Q *U',G+{ٸq5~K/{]Y}>|0Ǐ`>|8RIqǎ޽@#G۶mcSs+?JQ)=vi!ڥ·`O>ĺX]p>s cSvOط~ko{ /8ɓ'+w7팫Fosxo.Uo`2hmm4,l?iM2KldJ @^zŝʸq4?^IIIN~w 8PZGMR*t9ǁ38!Zj;Uz~2w#X ҪljNs >1^6ڶmUӻBK[FZ͌#7n-#]r SOuG1bO[g%ڟkG-x/>5^xc+a7NU/c~E11fpfYn@ۦ}zѣ}b9裏GT#G?u~q_LJUT3 o v` 뢃 Kl j9h_ ^~m'!++Knܼ7o 11.櫬Y0U8q*{ˁgu+:jdXx=LuYwyN񕝰%Йc_#GLkOs,)Ҭ%Eb?g)JEU@(:X06? .o+--%&&V8ܹݙRUUe ;v  \TwONVrY^5| uzSkP[k+aϞ=dYcbGcx-#8Z.;  "`3m$y-Ffve9%?[vϬw67ٳixm`Z~7m.:g>8駟@gj k׮Lz-bBBBä+W KZtqMn]vE(| k/-O-E f~ꩧUp1ZZZ| ,%RRR{蝼u| @MK/Q[7W` "Zt>!`Eb=o/@Slsf~/M=^RNN.$ÿ bT0i-C)"Hg(<?ol~uѼX4@_|[挜gR$IqˀR_2vaM1S9}Ӓ +Av^%t9ϕ\{ޥE/=۫W//7[ F T@nn.`V)5l7ӣFy2MOyU6Oj(wmΐ`Uw<7nhW_YwQQQ2 6H̄***L0!}*Z_=6oqWL]Ȅ򄚩j4Upgr7M֒Ǐ' @u$6@_: %D(x\ӫ9p MyjqU{ %?Bh?hQ֡ pFf$ )N+^m۶YSF!7TUUЯ_?;v1Q˔S\\ (KR)I!\08Zɥ;u9z0~ZP$@ EEE3fȀ`Æ 0a ,%`*_[KUrY }ۺo' )o 33ܽLR->>>}W8^deed*::3gZAJ;nUS -J%^2LR `4i%455YSzmee-֭`ڴi>h`̙^~ .E x ޒBsk}r_GY~%tz]e5Tje^xeT}xV/~~~v?غ:t<,[ ={2i$-""s9'ǠW/k[eiig&PYu9k=Gg\lwG`bW2O8jg2MIjLWϢ"nnڴifL -Xf {.>9^---+..Fs9>[0ɞ SK@ٳN+sՈJ.->VrsSļe^sss-m}՝ ʻӭYzok2'NTt%./Gvb@ZIK.V- 5} luQF Ywk4/8WLlO>*|]j2oI 3ϔEo[l[Whٟ&$0(^QF~TG`2/@<'qJҎ@"JӴ,Ǐˮ֮bz„ 1Q\L2ӸeTyRDO[ɥ"Z|d1xڳ:߻w/>eصk;wYŗͦM8xU02 ֿvx͛ٽ{7cǎMokq^E*" @z8߸/UP%xڼy`JMM>Pظqu:,M/H%Vhu^E4`PbqGZ;Y?#vir0uX2n8Y'g">^z)XM-Y$'c >\ =o\anHYa̘1ֿw@[%-;Z__~VjժK3ILL$, Q߉˗[ݜ9s4K{?Da uatKJT@eG[PP/oI7=zEhMw0`III8୷o߾2Xƫ}pcFFFZ?qvoz(*~*~^UrN'QP쑀aÆ\#GNk/eÝ}---0\.m\ET7kI*kf҈OYXtg$++Kn7K_JJiԩSSA)K2dK$2;z&_{5t>L^Kӈ Q5;.,^-J.ĖvJMM-[~MVW\3 ۭێ;_ _e]_g[hƍuUW cVi?_Ѭ׌ev(%6Pۀ%mqEk6^'33={P\\ ?l @_"7^z)`+p[PĠyGb$裏Ze]&n֭[ǺuxEUot K/LUzr࿾6'Ծ ^Ʌ_\OVj4(`X~iȚ_?'9s& LnR{vʔ)VgUXp!`Jꫭr⎁=c)SxNJJ=X!j>:|oݨ-ZH߿h C7*~W6Vʙ|R[[˾}9ۯtb x׭)+Vv3f =555lݺHtt4k׮ %55+WZ'n&|@ՑŜJ.䚣 ^FfQz6:-$|5Œ3ꫯd;ꫯyXntne|,w<s6@t#3|\ΒFAP9wV%_G(=Kޓ|fV\x8&EȬȄvKP FW~pP 2O"w6Px B@PT5.AP)_K}KMK,ťċ?sR<֣|rǓNS$Fm&^u!ܯIY-p.;g" H+_]_@JXgT_|t Y3Б ' 3Ǥ\ x=}g~J/o^?]Js`I 삅_R.XKC{F鏃RG>>Rra\?/_ Jn#~`r;y c +}Ip.;^[^%etBk@A2zu:~y b$NsD" SH||ei1A2[:Ƕ/ainlP_frJRˀ?s`/_(8A%3 d>]D t3ꙿ,_uk2/R.; "4KJֳ t: xBD t:xp~&!]v压pFݝ= ;'&ʬu |rA0N[UnL ~$OKD:nL.%<:c- xR<I?5?pFO6M]vνrEρ^QP+)ꋻWnNMid9pqT/*}͖(ץkexZu}rLiKGrEEǔ`=n c nb}3̿fu/"L$U3.KG4@ה56~U)! !O#'ߕG.e쓻."8׼za*#{`:od0N?<3% pН"`xJ_{ )5G\=vKek2qt=//D/՝wP{[˙.\TϨvWEy~:/e,w]/k[4؞^>͆C{|;2Ё7;uli S"yNB1"OE<:XO]È V?3wV2=ԫB?[4qy6`ExX}1:XO߆!"@-mF.|;W|'7炷OjL ;n€8r$_jqMֳσ aVsvJ\\A2!oP?<]5Qa "?"T%*}M9n6qꋻ 9ڃ5lMfJ eu: Y{Oր~y 1v;kι[qqx/fOb#Dϑ h'$,«9e2*&ʌpmƿXߗosS$h'-mF8 j\TO'OU<-u77ҿ[O"Dt;2(A95-w*{>gwIU[Hٽ}QL~ " vyt6#/&[ro U=XCyp_LJChkvLu? WE!{^<6^Aն0}Lyy Ne?8ԗr@e~iYx oo0[up g^8\Na!~n^Q !t KsHyh+/R)? 9U>)C2b5|uP'㾩=D =X_??BzU~=_ۏuS{z|5 P'(E`=;Z¨vqEVV'I S9 r+ۗGpzYOM3 g1^]v|Ep< ̰c_#Γ#ì~"l4bZW虽c^- <\08kȌ s0Sc<1Z[ͳwT_H_ !P+"Dxb%7vz{s?.E\F 8 '?c߫\;*.LEgqEΉ `WMQM j p%}r !th'~8zNJ29##‘ٺ윷vEd .}Y狽U2 =oِ@~E3/r[߸kVH>< v+9W]v>-k"cҝ ڗ7rDuLN9}I(c=X6㝕=~~qvO '(y`=\wˑsD%uE(oo:o^JG^~X?"}e|S*+ 똔gbTP*p[ i5 ʑ`*]vV_"Dt~jS܆^8:[']* ~.`E~U#zꅩPS-|EI <v=kd)D x_W6}A&cV\H=T0kCo0U >^U"=Iψ捊eF$1rJY_dOIv ݺ2_+"DxۄL 8Tn{XD뙞ɥb;4FCl.gվjRJ'sFF$o_Jbxo~">)-GOyOwWq{y/`v1_$׎ctx\E~E3WroR;9"w/McrZoш)n`$D:X9`qywkx/\032#9>k[Ū|_KY}O oy _ @" D;9je$0g(#{05-Ҩ(|kUҨT={fd,ϟׇ0[Zh>] @B`2"0xx}1(Xȫl0C; .l4=%M*i V3SyT2z96{d/P4?YV#sϗ)PAzzG':p$0*% ME~_PK~y{˚l0ɧw]1$!μM-2UA6D%@q%`!h=Ǿ&y4DȌ &_Ghz?F':6#z?#-ai5ӱim3RlXm+k[0hu幒^ߩcVWLžQDhS g!['(o0S!@B?OݛO[Ŕ9􈌪 @(WLG3{qXgߪX "|O`a߾ _̂ͫBAӤDr۩\?:PW=]v3 a`:p b0̏yc״@ P>1\-f_ @;@$ppzWki/P21Dʷovsv๕$2!S-WyZیwS)x]%@b2cө \08^Q.X)ﱎD"DtG\W l)H/ot W U#b쎏(~4.xN "DU 8':vƮF8vTrVĀYqň\:1G윭.|> "D]wcXw|ΞFWg@p zCCX0&rG5b X9Rn @G  9˚xgK9o+gOI#R}XpPfDpG2m#JL_Y+-B P.%Zrom-#uǠ`.;S#Ν>^e3(7[ [@0SP gh3WbW%K'1(!)?qÈ һ}ǀ͞Dv1)\/lhickqd́jv6R,@˄10!)i?aĄ=?wپA{c &VbվjvoB5?:HbhR032#9w8よ{kyW e1 :F8ZŽc |M7QZ׊z~:zF0(>iLN #u9?{%F @5b S@O -#j[[WSȖzkZirnOz'/.qJZ Bqo^TG׼DЊwhOMv4--mF##,PGbx=L`'7:uUͽE"D8ă1Jhj5Rdhu3˚(lfsQu&PFms6Wৃ@:CȀ Ȍ Ot DbLi{{*mO @SK1N %]ICK mT7QTBAeֵ&uմPFHkV#mF#F#dO#P#Oy_ >~avIfcK w7F` D@GuwߙA tJY ) ,Dx'Fl Fk Ap('LBw? *#"S` 0 E8M&qӹ.ߊA x089o!F@Se=?fˎ^DfB,AfN(UͻfS ^y_^q/"A ,B̂R8߂]) Dz۶6i3qߛ0 xez `6  h IA  AA  "AA  AAD  @A  AA  "AA  AAD  @A  @A  AA3in*IENDB`OSCAR-code-v1.3.1/Building/Icons/Full-64.png000066400000000000000000000154501417327530600201450ustar00rootroot00000000000000PNG  IHDR@@iq pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FSIDATx̛{xU?$MҖZ\**iuԩR>hێ3YvA6P:Йf˺ thq)Ѣ Zz)&iwis)")w~o=#|>.1z@ 8xGsbQ$3Y4$9dhҧBt$ J ?Io`5_E 7cU;N| sf3c051h(5ˏ\>`~jsÜ_ETn@Rs5]uymp_0(5%>e}[@>+f6|FC{T= Ʋ&``U*gOˎUe'v~ 0C@%|^xĿz5hTS^^͚TDvV[Wl1HvUwHSg ./K)kR XRIrL&ZthpS=8ރA CMǒb+D @*oz?T @2'ă1Ɋ#pvh9M5 D!*<ˠ0J烁pV-f-oacយf4iQˎkfge;>V4$xׁ0b$N/WNN3)aǷvb=!yt/xa(PBEYI/U>MfŌQfjR!L)lgF5X+D j<rq:5IFݨ|T6.7iOȱ%͔7@ u+D^?Lc^6JbҮLaff taF`2mD-F` p-e'3M|x2&Mе-vjρ.) 㛲mR,?jU|x*B-5RZP+\_%xH*W={@`&S%ICr\N%z:\*m1DN:ݲ\+L  #7Ii[;/"5*m<8Ck}Ic5}u"h =vuj}9lqbp~y.5i~cv՝H`%N~u'(\HE9fVKH}ʢ̐wFR{b0Bgp8wȾN` 0O_e_{Ŏ`)o E;ą}{Rs (<|u~vΆCJu+Pd6~r6R^z4xCO?%Hj-;NtϬ&u3YKխ)Ha?PXJʛYQ|po3|JT 7pQdRrk RD|>BjO䅿JR߷QZbJ(͇aYr(ESjpY[,1Q KXie;.vZBILG69ک2cd h)AjKٚz0fq IH2L:eEݐDIţYvC~/kewdόŤW&8է&`s9P5pzVj00aO@|fwÅQƴJY5$Ek P)yIS&oL6jŎte1ʪVQZNie;uOf++EIyWx(,)Jʛɿ,V;(SP 20t{GvyFVaI1Ppe}g:Ef?!Lnol7!W { k RU4h (oI%=3W-v.ockؿh*F ۪ȿ,VtpN!%&bZrĐW;4&!*4GVBӯ`k(lôJPQn=L^^v :rÇ"//O>{x{Q(>!&C=X`[neA[p!eee\2pUUU[={p /^LEEWfs'E_ 8 jC?̙͛INNfǎ Xt)nҥl޼J?O<+ŋi$'Z2-@D6|O^y 60KӅ8tzCZ%]2Y hlrko9yrĎO/իYr%SLXpikma\\ϛy H8AB]V&ލuXɓ!"<zzzD^wEIAa!ᄍ o91e#Ay8!UΕo#z=Sɸ8Z7 !uF%'w-`3~1P^Q1> .o煒kA 8Oǿ {n.L6%<ߊadzypT_?4}UH=0@guUR"[ j1a#D|A%,ZIGnj!ؾAf%ʶs=t&nL//{́`:7 ) Ӷ>5H@6l) & G+V xQ֎_P Iz/  .W ={ iͥjq~uGX&DLO6u] vU,qo/#{&4:V;xo8$FEX=I1~c&!JArh7Gtpy3[+H=;Cb i[Ųq_q2Uq oܥѫ(Xmϖׅ4x[ d"m8r?kʼn{ƯU \3_ǁ@`5 rmno$cv}zx'4wyB=1ZFEL6+{R7Ujk<`p.$:"9J#=Q{:t:8%O"GIҐ#+A'a'&L[>'9.O%g; VqҙjҧVݨ0#_ْVqָOjJ1(n7@Jt}|}^#ZIENDB`OSCAR-code-v1.3.1/Building/Icons/OSCAR.icns000066400000000000000000010351611417327530600200350ustar00rootroot00000000000000icns:qic12PNG  IHDR@@iqsRGBDeXIfMM*i@@FQBIDATx[ pU>_BBBH $PV *Uъ-REP֥ZP2ݴZF*B[*F R;!CB}Ow}Ky9s)_f2O}#IQa(`EkB`Yŀ|L$P&mR. Tx}$[B+T&x G\ )iW Qr*Ujڥ hR J7&ʸ@1 f7<ȔjI9W%NJ꥾ Be*-uU?DޣO3&߼!\& x '⍯fMb}ԑD |& ?>{WMڠ,21v1X8H|QWs2` o#pZyewͬ։''q68 I P.5-Y$'%Y:`'4c$Cg)\ZO eb-f$bLPIt>]HɨWɹ4@w=o,AKU=Bo`h}_'#W 0Qa.'&4zΡr[B 0G'dU 0 ~â\[xRp[GiNU_3LVM_qT~ei.xöxyf6f1u0/]+n"i&8<}Xl9_ ;¶NIU. 0`=/+bJj@h|b$B m)eɡn%OĀgw돕ˊ9UV:d$2\c0Vꀹ$܄ZK(X\y"nN<'+a~ȳ 0FH-yRNh3 ɩcV~lش(z\绯 rAY /,b,&|vǀ+3f1xEk5u-{`\-a(8X*U#Ps;+яNǯ.q/hP 㶖vYY kӪ7XF{Lz%'/$pyyK@ʈ$]@'[`fO$)镮?~9nv9ri:gu\9%RbN2u#f$f_иXeUsqRU]'MKEW$3#C]Pi @~r@M `fAz /3 XJ >#\ӧ\Gu))G0ΩQ[JGZi2^wÆ sJN 졉TndBȪDSVAʫZph8=67CUUU}_ _@Xbb{C͋vXM+C b.02Q_Y{_O-6fVNQjH" Y2ᡎ89 : tkD̀T-֚iq ibi?FfPnM߷Juuݥst= g4іE!%?r͑Rj# ZL Tk$I=<ά|7`!z4mTW%Nf `&`fwc bٳG _ pSFMp9IXշha7igp,՜ӭ-<cMgϞҌ36?M8Ȝ?/pp%>ew/pwD&F') ,; [e@g餵W`(_sRa緾WwA% dGpBNFtsK| Um@+7$lIi@$  2K,&n [I c+ù#&FdG7RIENDB`ic07&bPNG  IHDR>asRGBDeXIfMM*iHw%IDATx]xUŶ^Bǐދ("ՋWrԋ^?A}b/ SU@@"H z5wΜg]NBX߷2f5k֬)'*==][.O嬁+pA|}%Ł`4 ʇOO(c@W(8SXXL&Vdž2 ~\߉p/ 0BT6٢kB2:֟pD)@ Ffydz`M`+ [9U Ȝ_\\L)`5d%/tfdz] HƇlݳ7ç.Ȳ]' dI9w!]V=%Rۇ"Nh)R JZ\+q JJ1Rad!ZHuzIl#\)(p95-j=ٛ]9ت."M!m@7#(?g Ac'd:lHlSV6-s(ZPB+mh84ӁA؂0!W G5O~+/ˋɃJttA _ӀS!. rZ|0K  a1o,pa[v|k,>N`ou@?y? a&5<BD\#(H)|ہ@×PVȱnhyf6n!W Q8Q ݳPէwAH)T0Jc JbZ\QXD*[ S/gy9r悜1'aBd1! 罖 2uO#o14{>ñKK˪R#>ZJaΞOOw-d1| {! CVvbs!Lq|@W@`=ju;J5JJ2"mgdC١ jI&J$^K"ȴ 0hx漴z{;7cei^8I^8s<3}q ;T5F y~ Bcq@ (3>@mKO]oz7޺(4uJ]JsqD29[m0IUyB0_h:GћU8&oc0tiY9V(fx4y~9)`ut'F~B0I hȨ& &|}1cdAdඕxJS8X\g^aTѪpFU UDGoSĩZ}Ee翚 (%<$YX])Hg۫u!n )% ¼ E;v㢯Ѱ,{PdxM924P,"9!`cz5Bp:M i$x_5E&χ'Ow~Y4Ѩ k$Ȝ>T%p3̋,އZ\WrD'w&MA\?>pFdJw.ݺ(N{yiuuG<<1_Uexm'nPvɵ+ ! f;6+酿Vו#| TQ^5!"#bE3W+@w Cg!:Jˠbח~X 9Jc@&t~8?V=/o[6ew7y ;GMvZ̧ ʈFݧԭ0 _>A&2E-rOu<2eA;Qz`'t$  0 P{GHA,7 cv>HBH{6-- lt_n#([H踫rG^ CD:[j-H|=~E˷v=npLu*g$ X*j>* U7Y}$HX#]ib |!g[*i5%HL_EWc@ߪ9NpY]i:١Z~npĎ՝kq>(+<3+M'q=xe@.6`ȌL!Zyg7rЃ=~7SQt^6,H=}ֿ`^Ƙ1V<Ȱٙ #xH0$jM䀺thy{UZ{vހk/b{wEX!CgB;=Zp.ٞ d]/v*H6bkN fpw(vi-Ά`Ffy8 )_[a4Wm,N#\9]vg<}Q zlu8I+I[lȮ*zY*FHPq`$|y[v+Gmt*-23^8ЅOOkyj k+l9 @[E,l.;RBL 0fѾFp zS3Du N(4` [V h[ ӲyN{(cKG2*?PϓYEok]L!?oUt`-7f| .xn[(^OhMNV MbkV%tEg|22U,F|5`tJ(VC{iv4U.Q,u *1摌^1Z^fmXYV\'T_V0J h uᵢAH<dRC]45ƅ.ۗ13H*-AU1Ԥ0磿Wl de+ #wMN@%D g cJ!"р!ZzUX<t置x0NC8V<V>M!1pҠO +A2^!)@,7KNԙJ 2_גSmO>yEAJ]?͙t魗>[^9lj(VNUkag8# п)LYwDaJ(x\.7VFu/=H)K<#37?fKЏ]+^1_eKUW0nH"2-LBZ.,7!(Xv+_Ҁ C xGth oVW q1̥hd8F N"mR!*~4t:z|Yi+koPwQٝ0>]|=_1KCe Wj*^4lšd `9TWt (jT81:=sZݰԙ)`9ta{. FSӐdAP6N0Z+W\•:$8 1٧VPW1cD2) ZJ,יjNºхS9O `b?* c%ꕭ0KaJTzfEѽ8|O&(qޑAVBW]|coLi* DeRNZ8*.D5q2zAbB'`ꆡ.=p?Qõ%Eꕭ{8PwDsKQ dbQG9ߠBN?#Uez,HU~T&:愍![cd͚5ҢEl/ϗ.]J'?ᅲ!C]]/]T&L /+=8:w2 vjƥO_RcR={H套^I&~׮]2`?~ :QZskʈ#D4wrOA4hw-lK"Vx sVi YX A{nQSѣG5\#'O{OΝ:KFoy;֘1c[n?qa~/Kիe݆`$2~?܋m۶Ɇ x?̞=[NprIy衇>>}Zv ԍ5Ѩ95 uTe|w%!!A3ݻKѢEeŎfڪU+)P ԩ#͚59s8E_*Q+W$/Ӣ65mTf̘, s / 'Ceִ_r#ga0!!˗/7ZNVZ[.i;wίIonmϟM6I߾}7iD- h9{3i*U60-谓|FbͯtwË? |R\9!uh޼CUk&|Ve˖[ꏲ-tl:hB8Zq삨+V @Gx[.Z 1W (`}aU⬞z|5kժUB'/HŃg8S-dKZQ',X Bf^إe1H;,=W KWa|$vEiܸq]UaTvV0]uUBL Nh?*&&04B8p@^WTɰ/j BY<ǵT m 1pMնA,8{QߙDo8J*kԨ@7T4 U]LKqQ==~-S2Ac-CIE>l\fPe_h9b #a%իge;u+7v nPpqM W.gf!>_ӉUFƍ*e2 .՞Bahj(iNs *}{ҼϩH"Q@xe:h>y:%E@۰m?ܼ"-ԍ9 '^CHS"җMd=aO>]1*Նd8 G9a+-% ~S1W_ ֣?qZU1!6r} iSSSW^U`081p8aQVB9%K l#ʕ2l0fXL>Qt3!ѫ2}c?KZZ,+Y(dz8Hs(V#E*#^Oyu<~'~-@GTciqD97B_֭e)̏J|XIP*&zaI/R0(\hPQ{OVQ'9ԩ  @FӠ$i=В%e1\0L,h7M8v%u$.8) DGNsB@a.GyHa# a/\J.:JlG, @[ƾN)K-̠|@@˱( &kєiy=ǃ"-` *2b]? 0 !3M3o-u ‡g sZVVpx">ꙢnB+ x;T68+@i(xT`+!^5`7C?`|-25u3}IJ f9>ZkcyvCqU|c9ĞD@'_ݠ5{14~VbO%5ME:#L{a*2+ְaC{XMt.pq%HNqevgB .`3p򪂯[@{5QALB &['Dg Lpn3}nMou(fi-\P8 O-Lʯ9^?5T\<@Z:,GTRcq:T +ZM3O:`8kp*`wnIN j׮mr-_J'P`!eB\عvP4l?{<''<+OƥKh6C_jNҠ8_k!=-iq$APB RiYRToNV$4\x]B(Zׯ7&Jbh) h0ٚ)p TwGqv*[G8]&\v봸h$R"-2)-Ȃ{J׊ڽ݄Ѭ+Z\؝(ÍF2)ͩ^B'tuZ\i|psgh+3o_O՘G>A2OC o [(#pҾ}{Z-Vaڶm[״(Jl߾(m2FJV|q k2݌jpe"e+ R3{.Nh馛$>Z ._s kX. EaJoXeVcHTXShv,\gt +pH%vCBĊn!  ӸȄqC# dwbdv{]SeR|#k iFmldW U<;xs܎U>\`BsݠrڨwNC:gVr9dQ\8m#9T3BHS=3 &^<:|y"Db8&}֭kbs 0߹sg3qDGyrHN w@sl/@_H[)gv±@k_(PP( `7o ~rgth>ic6mF~/g^ss'-v'2rkZQu, `:"[}g?o9"E$`YjoO&&:rduNaGƭw_BS눀EdGeИ=IJ+Opayyax"G:{?۫xqXSX|ИmbW0$OPbKyWW|K X.':;d~ YX"pжxFoL'r!IfxdLwkJ+'.7H8HkR\ƒ>ۧFu֘ϡCwj߈k` PٝQ&H)5BTŁ{~Z-@9$|xcUM[\q@aadE8t{kݙ$3GTߟ]-_)"@b>!KjM*~"0p;_NtpyK;Nm5צ}?ΜeLܝӈ|V1 qM#8iy:c1 ;ǹFJ.#fꖋUOW;1_;.cmsd]|0ݓӶ D8 V>NN9z?u|uGO`#'xA=Tn2([@ߊ1@cW>:-4҅厺qr24'0::olȾ =ye}zOj|޽l/ː9μ 4V%pwPﯶ8^DZv! 竖y-IX-풓CN݂M;{ǔk=Q|_TA(@A{".šGњv|ÓkREq&3_Gs3'^A˄j68̛K.P(1qQ m̯ m=סoRC70z-SU< 31Uk:nA 7kPTgS[p8zGL…RT5A]Z>yE;T.w̚r3srR'`% 2L.nM H-b ùB;O49A>y'@} [a"r@cIaw, NwҀ+7S<4tnwSx.5@5<{) E~Zv +Yud|}2ڄX/Ժʗ<9SL?0W+ȅ_R eSv_X!G~k Td8Hc.Wwt{3cM=ۀIB/@ | #cv䓣@bIrhIc*ځB , 休\u9~Ȗ !@fRv~\DdgAΞ"Sy3ʞ?]uMIENDB`ic13SPNG  IHDR\rfsRGBDeXIfMM*igI@IDATx]^# EP"`^{Wbr-ݫ(`WPzrevy|m6LfI23믿D2 L .jQ )1d(@ Y3 d kJ02P?~V@FLU=@6QSl {qz9TBU**Xi"*0W=CE(Z6B2óWCh4d'3)h&J& AJy$6"@XVx]1 "P*3Ngu!4C %Bsidd  Dd c$I̎BCm9/.Y,W23 Qd2bF#<{"ӧqDZRΙ8d? "@"")[9egBc `g'!L*KaP oP_XqJ ?83Byaֿ[[ĚM-Ċ [Zg؀uҥ )ڈw*-7U._Z+]JT)_FTPZTPFTB_bQrYQx9?)@0W=.%J4U tLJ}W#DnK$/(fbњ-bΪMbF_ν t@Q́ DE*D熕DDEsՑd!p0  AAh2 :\7B-.(~V|*1~hf}3 BenݛVh^EWQTÌ"{O~E f@BAl8Ծ7B/ZsMI+SV6y6 2 vM wj]k3|h0a$`&@ğ"!@`ʒ 3VioR|;W7VNs5qn:)Tۖt@!x0("c}L!>#}_}5ˍܟL])^}=oѝ )@ U uqk*|F@AIAz@8ϳӿ5nxbg8nu9=B듰pvg>/n˃ dd|Y9r}z-J1<+жnEq> ęBWpygBN1&H^?E( 9i DQ zU-}>- Dxg0YJY ApZg<ۼu YD(@yAw\wC ng@`<$)@(iw;?{ef&gL܅=WX/"<&@AxL _w/Kĕ+ouM%H7h!;ӡRӑa`U`ȑ+p.i}10T띄p1xau3SuU(SJ4Q^ԁ9m:E-\W)+:a#NUl̩P֛n* m3,dzaGaqnXzJ+lbr ܇4#cWA@ z_!}8[^I cv*z5*4$6 lbJ㬵⛂5b bbMb=XxĖcJNTp(`4/Nd 9 Gŵ>g@FMîiWCئ9]M+pe~@y?!N.-#eA)\;ػdgtٹaJ#4wx˙{Va%pg=8j2|A0icg %:G# G~yi?TǨ~.5Š>c&,rc;obY&@EBf<{"\Ⱥ/`xd:.#/sDVX\\EơID춢'8 Xp-ig?'NKōS/{64x~@:n2# _?q6@S@$Cc_jsۮ-\:GUrwh*ftb)*[/ޛHEUˊuٓ LIYtb,m:9_Ў o=4aEǿ7JmBfialƢ *,1 zF=*HS~zэhx9=^A(@Fp95 i/ /8@PPکi{V8Ӝ#mkN_id?ӢVyɭČ: :H+RqgpHGE-8YJm`Iʏ"ckU^t+sNx}FjͲw.AU[sZ^LXPF Ti;?ŨsX:ypS1Y Am.ϟ>X}MCd?Эܣh҇vR[8kwm| &DvƑ.h/.G/l)m|b>uj\ v yZxV}qݽ~elLjj՝ťzn22t~)? +r 7j<#'hfE"W\ k:)b̞mLn6N%(w>K_M<ǿ6-F_^{ދs)Gy+{OA- @%hߞ\jt_juA %|QEL*GM Ӭz%;j@5rJ<.C;w`&AI}wxD E2 goJ<^iAα3q Ӏsj:?^`;SD5Ҿiy'jJQ9O4;hykT*"Pϯ?bqF t1Q2$L?DmR@[/.zG va,(@U!m4!m{;?Q@ pgvoK/׽Ie)*QL2zMN @g=:>QO[h2iO`]u[#(⬶ag~LL`&\}!y@b R4DG ;tQ7R KLS hfidD @E 9Z_Z:J( Irry @~脸7kE@mx8'|BMӗm2$K~'J~OM#?3p8nAw ?[ (O= =Q p@}ID2TA_~<[QPQB,"T i,uāgl *{jhkzQNs< P iʸث1 "@Z7wզȿIU#"/(+ P .(y@G| @E8ߢ`>ZfoPL -襂( "+9 a/s=5J +ɷ {&1Ϭ@S݁>ٚ5R qp NG8nSzշE\ P ܆v%N %*R itSz}>7NjG1YEG)xZNסB9}ht/ϦCc&Q4-ۇbFe"c@3Q:OycX k:K4/f6 ҇^A5J$ o` HА g\_J8oVN{d0V  R>Q|\/d٣q:΍dJ]h.m'0;6hjx>PeQ l @ ܓ뷊.St5}^ @ЖZ.q9AF)ЯMԸ{E 6@YH`P( ,fy wjAyeP)prtNpYN{ 7QuёqFeZ2)p]_6a3,_Ҡ |]}𥳐sЧp,[CL @t0FfiaKk+A-/thAM2ȞuK2s$j@r 2 |WWX҅qrt|N \4ڰ30 (2iX&R=fIjd()@]cI!y Nwˢox*4D<;qQε*ʱ9>/< H?JMG6O,H2`"0VJޭnA@{d1~i'luJ\4f{ǎ ]@%6btXjf%'~[֌}r}Ł[" r O݂<_`qewn)kB;\)pE~A.9~D5N-1gLI,iŒ݌vϧ6l3Vӟ׳8g$lB'% oeb=~0Ւ_'o&dnl;L=*$0q+g tN:p%Ԝ!H;̹vKc:Y (7sT_ŗ[٧U,Ftn [l oliKvw0v2~G}c jH8BƖӴ^qϑ[s-NƮu~"x2RC3BG*?3~jgEčN}V͑_AVRNt? t vjZ1Y/&~f{5tظ%^H c@_K8QK pA@C' ӗoҡ@ՔgY? 52h^cG 57>q=n77ȯV8ߛ W*Aڑoy5XWu%nu~rDs4+:+HgNLB|W͗LK(X&f:9˔2uճ(ײJ/:V.&z8ۿ^K` L^GZ҅qD{=\GN&@aX!^; ;v@iggͻ>ĝx:K|\7x%#b '6q@f3J*a -e=ܮ0+o V B]^W@b5C8!1K3޵5VnAs㥖A2>su!|w9R9xQ0sg^nHWY6pSoF;`rMh:ܢú'Eq 2)́FJBڢfΔgNl4v#1q6pp6&'@$vvd u8 ց3E:`#Ր=`eNv 8=RoZr27[*ZA0ו̔,~٣lS*hAWjqj#]< *jZd}v.LNߓ9jobwVgGؙSr4A;;pv,ߠ?S:mcg-fR_FݻjMWXN9 !g<B^G B;g2'ȯ˓6b2 ,o_ƹ]9;H|U;v;1-t˔]+Kҕ`f]NIoyeVXd05y1ל7uf~47 e- Jmĕo NRQ^۾Z}vf\\nerT}W)]^k+*#t]s/ -Z`Ò.̈UVr$++ߑ._jc$89[Ur=kȺXdN@Xi>weZc7F͗u֞Y-aErW",@,cEnߊ_ Y>KU֛]z;n= ^M5HbWd bƗIy* G`i*cB5v^fȲ$T KYUnU%{h% 2C]~^Q1Ϡ3KYjbyL.vyYUڴ~FjNEkrf P`9꺊r;n/}8,+ ;:T(Bx!+hu_}u{ XWJ%՝u{&]ڄ6֭:U(e a^R`@M5SWQw nS H-^Nu# GDF딗3G8衖+4u:pwlK;IK?~5'9^;e޽|CxV:]R ^סyљ2-tVse~O9~rz0F[]!{iOmO42VeVִG8EgT!bdXkT0#%/@9|yuxHFX_ e 8j;AntrK!v(}uFsH05 vpP5/~ᖈϵsV%cIk3M t߭ꦉ\O9UbNu||UG1,ћlw&qQsUཚLM<)>ӛgKdݨ/㵬F&e~-2fj%af-Q~̅758nd;L9121B# с]鈷n#\]`f~eywu8FIG `ɺ-b;B;5od:;Gbɩ1%"zz˫NSpu&vf-W3 en ޏoJ/Ic!Q*ڬEiJ6Lbwa ͩi})묻:mN}/Z&9tK%o3қ.NWNZ- X3`y~M6.T"FNvy~+ݻd:V\$吊/$;ݴ[M &-.!5ip/SN39wnѲiYfj h7YS^fᑚӱ#ӱܲqzJT:]ږsYCriU|% fIO6oUDrĨ&+fdvj 9ArT5lcdȤJnt"yNk]hnb^tPG[Jf|m@c\։nJ T~X(; L'SsL)?uCS0q'எnI$[NH[]󴣉,98& %u*k xa4fzDu[t Knt1!.s:)0ե3.8 bvk{7 w)tYٶPjxZ:x.ZRpĵ ē&aSàΩ#gud}v#oش032He*s|NZ,l4>*lG𠌾۬ rп&UwsVj5m ulc7{QqdʨN%vR2TGgLBz :5:WFCi廕%ۍP9פĆ-z9/( ]l/8]݈eƋҰzښsTӎ9z rOGlf1Τ^nZySNsÑߋ(ߧnjW>ߓnͪoxl/8־},r'vKGޮ{whj'Qw1u^f4wIzc??4pvL@6|)9/gQQ23?9E%`~SRL2E'^YHZ{eđ̉ ZH\``N`,Uy16337 qN (i,iTqDFZٲG( ۮA13Rׁ٠iT"Zp|OCDNqq4#|g>Nve݈#d94#aF*ɸ 'gd@:/[A?^Wn䷐mƜ$ 5n /8r!7Ԍn?{d#N՜?q?q:+F^6nv<603/&-tNwt3#s^8ݙX{.ss ))gVD`vp++N^!3ȩ)qbbƯNè9YNz\`ί{#pc&X xJU^gR?袋D2e/ڷoo4͛tAF!/q馛Q͋Ȑ0?p#EJ!~i1|p߿ҥ8w6n8~vąg]3ijэpoQZWPZO JAH'q}għ~CQFtI$G~aN>${sH?i$cm.˓>X 0Dm&yK_}9r/-ˆ #_/yiqzyiOU)'jW*뵌HMIx1q-kzꉫZpK&73nTjd3f/(8@4jȘVIfuy穏"?3^{%Zli)O>3/,͊0!kIdMwcW_äU_8_X~ŭު6/r"ǎ+/_n+k[ ŋYf rP=$w5,(|gp,ocBgdp`3ƻ1,iㆈbWp 7|-GykpD!?hV|yL%qifu)y-Ͼ;4a?;E]dLPN6$%, Yf' 5 Xˬ]]LXlQNFGu{_kȸ~wqcqr9mڴI|'otīss6. #52)7z0lX6J =`ئϝza(F¶_uv>qٳ7,{^++•W^OV{.@Κ@'sz'39kr:(;6%tJNۺZ0э^9BKpjT T2MEidh}[P⏪,/>(B)-ˠ-܀LNi_5bwTnf,,XS':r/U@Eܨz`+WiӦ,^*q狂zu47o~k<‘vҥFBD+V4 pyBPU^{ר% p$rtKM5z9"~V#2ij&?"SFve@M\8nxn̘1M$^u4!P%p%p iӦ(AJO rY!d V즫=E|T!-ZZPة͝tk Ih{.Uv<N*yqQvȻ ԉ$7E4.:;̇I`C%o! /"X3߳.T<[z$ "2>B-Z0g̗d켒ʁS%]eZ2E#K6w_rx,d/ `;`SVup1OJj4ySj2o^Qfu7=FD|F\ 'p3EA1!HCu`혎R5^pU/Y`G760=8؈g^u( jB``%m#0ۃND{Gk 7>y!x(XէM{}V\"|/R=7E s k2B2!63mq.pJ3nzjW#e/9 d%:ӑA*H7 ڨhiי|w`Xid.|q8MB)BSpcM? +ܬ*Q0 vG}2:v~ͻ~$[Q34F܊WsZңE8i^oP+]߸ 3E(\ةFy)%br%Z' sp\gqD'pr ƆL>g8sq 8ѫ_Ny=tNPyZԅ^]*ƭОWͩ,;($` wn4Y2 )~0XyWc%NkZrI:[zi93p%A:sl'm^Y d0Jf"gfTQרC4;q7fx'DW5ŧ>'[P8spYB?rye-?;¿_P@Mjtƥ@<QH봋{oثO 3: 5)Hq.Idi4b[_Q.%~w4wgb&{ X.lDsw@Sv#ՄlT~x.AbcwO@a<4np:u~8CH^ٝ&F1^sp?~0@'w'0sI!5yD5mq::8q6O\8?Y;?=ӆhiJSP'Z[-?R#0 2U^H(eQק_>˻<;0 /đ74-7hH*D%[R?Z 0(w\)ȁN]rqweL@( !O[PD,jkk0 =*R l|{曵WO(gI7h} iΤ(D/.cݰ0kcF:LS' ȫ!Fp?7vƎk:- klnՕց)6:UY4v2}-?g ^ qLXi6/*gIu8HCV<ۓvkQ=o1j}B_DvӍ쒹܊^r?٢Jȵ w4lj?o fpsuW~( ݍ󌾴 Y<=(M@wㄴi&$|A֢m3}"rQJaЪMg@ .Uzc<_!,4|޳=Iդ3C}x,}ݠsHar԰'}k\D,;E}QjNc&>YWL[hB(u6J08YMr+n/K@*@ߠLa>qd3? }bl'q "5 Tӿ(7RSFzYgU*iÄ:*yMCAG9,+),*In'c/Ԝ3x\+q& $_R[8 |5:r֣-~ΝժvLm-!9a!.3c05)Iv`n"kvpΰ"׏01GD?4d)&`Y0L 3 Йs/{;4%-e׭@:4!<`aaȍEQ+Hn-%YGSl>CM5:{ tnd5DSYzIr:+h^=m.YI;Y@IzM81Q'q\Pq?x>џ i4xĖL҇bt@Y%7+pH>{B;$Û OUz\IDATv#2jl ,_DkAZ}a9tQą ij<$#>ݩf;T);܁Hz /7M@ه[oe$ ٘e<󌁎[8KJՈiX KUUFCvW܃|'W#,(@z>9Ita-Aѣ;O[: uKD:1PH1߆@&ͫ%}J6H;)^OL Zש _=tʺ/#E %Bj[t$w q L v(ņ-w]etXM?.EeE?^ɗw5qdr+E!2a> X&JJM/E"9 71.q@ p#M=$<᲎iJlh\7"O5k~rW@{xN& h_2BF|;|3ez`򍂎M2H/i/ǥڑnS,X:("5 OpH Qr`wS"ex;$̏~9ٍ#eh~u#oAK9HiLaHnTS+%fH/" %A2⊏sߙ)6G/6= ԙjpFC0EQPޫpH3ڨѩppWȖ}2^C&}s@Rpaa 3'` {d*qsSuRAWTR\ ԔH筢-|S&N޶wT逸}&_[' ΄DYjEmpL4l*5q?@Z CfAU ^-#mF4? ;S,w`OHJ)# %h3\nA8< KQM*g%f3GfQQU|WweBǧ]J0~ T. \PO6X !'o#qnuP!,v[ ݏژ˰Ol+f#~:?7 (Q _8Mpkś{рDp.˄˵H.q @籨Cx—VuGdmauDZN'*;,a6k%Z.N+*˖Uu)5J?bj1t M㜅{LǚZ 29"܎HmUS$;?`$Xq+v/Y*]uJ6 deNUl'ېhkoe u@OZɢ )#w,oFˋ0z g4~) pwZ0'&M^J_,-N:=}QC禞bP>#RQ'"܂`%kOK6s`ȠQf?/sgWnGb w!P@(JkCզYtI-!qs+i*Qp Nu<> Cl-@L |oxr hZx^ze}"/s'nIϋ^m |D25Z2qWł>zOZl)q|b0%[{ a0F"-3RHbF#\Qs׊ϥ<8)РjY }^)^to32 `ԑQexžc<ӗm)"q>22ډP֯]3k;A8 C Q>PspLjjLe(M:/QWnp?:~ٸ^d 7r_)(<"f\afiLVJ/5A;xtxnE -@3J M2@L.E8'px+~ [m5I;L}8p֓+Gisٖ0# *g pf ?-p^4x,65W WcO%.$ld>@)h'J 2JӀ\AGO܊< 9;yZÜ{s;UG_0CkN@8 @LT۠"km.X/MZ)&ƒ (3 bMêON%y,7QOӗ(5d /Y1# >g+F(;;m"|]ngA;,.) RFh@fxw?;;=Uv!H P c|*( PvPL!%O "OF&( @rK!4BP !3zd'_ a$2(B@>Ս_\>D^_vHPGIuBMAH)0p A JAEkp螣8;8 tSxvl^g!LCXA(_5SF(.(͒e(`@g(a@ Yu3 )135J2P>xV݌f d LFFU70S#QQ c%g(`ņXC!IENDB`ic08SPNG  IHDR\rfsRGBDeXIfMM*igI@IDATx]^# EP"`^{Wbr-ݫ(`WPzrevy|m6LfI23믿D2 L .jQ )1d(@ Y3 d kJ02P?~V@FLU=@6QSl {qz9TBU**Xi"*0W=CE(Z6B2óWCh4d'3)h&J& AJy$6"@XVx]1 "P*3Ngu!4C %Bsidd  Dd c$I̎BCm9/.Y,W23 Qd2bF#<{"ӧqDZRΙ8d? "@"")[9egBc `g'!L*KaP oP_XqJ ?83Byaֿ[[ĚM-Ċ [Zg؀uҥ )ڈw*-7U._Z+]JT)_FTPZTPFTB_bQrYQx9?)@0W=.%J4U tLJ}W#DnK$/(fbњ-bΪMbF_ν t@Q́ DE*D熕DDEsՑd!p0  AAh2 :\7B-.(~V|*1~hf}3 BenݛVh^EWQTÌ"{O~E f@BAl8Ծ7B/ZsMI+SV6y6 2 vM wj]k3|h0a$`&@ğ"!@`ʒ 3VioR|;W7VNs5qn:)Tۖt@!x0("c}L!>#}_}5ˍܟL])^}=oѝ )@ U uqk*|F@AIAz@8ϳӿ5nxbg8nu9=B듰pvg>/n˃ dd|Y9r}z-J1<+жnEq> ęBWpygBN1&H^?E( 9i DQ zU-}>- Dxg0YJY ApZg<ۼu YD(@yAw\wC ng@`<$)@(iw;?{ef&gL܅=WX/"<&@AxL _w/Kĕ+ouM%H7h!;ӡRӑa`U`ȑ+p.i}10T띄p1xau3SuU(SJ4Q^ԁ9m:E-\W)+:a#NUl̩P֛n* m3,dzaGaqnXzJ+lbr ܇4#cWA@ z_!}8[^I cv*z5*4$6 lbJ㬵⛂5b bbMb=XxĖcJNTp(`4/Nd 9 Gŵ>g@FMîiWCئ9]M+pe~@y?!N.-#eA)\;ػdgtٹaJ#4wx˙{Va%pg=8j2|A0icg %:G# G~yi?TǨ~.5Š>c&,rc;obY&@EBf<{"\Ⱥ/`xd:.#/sDVX\\EơID춢'8 Xp-ig?'NKōS/{64x~@:n2# _?q6@S@$Cc_jsۮ-\:GUrwh*ftb)*[/ޛHEUˊuٓ LIYtb,m:9_Ў o=4aEǿ7JmBfialƢ *,1 zF=*HS~zэhx9=^A(@Fp95 i/ /8@PPکi{V8Ӝ#mkN_id?ӢVyɭČ: :H+RqgpHGE-8YJm`Iʏ"ckU^t+sNx}FjͲw.AU[sZ^LXPF Ti;?ŨsX:ypS1Y Am.ϟ>X}MCd?Эܣh҇vR[8kwm| &DvƑ.h/.G/l)m|b>uj\ v yZxV}qݽ~elLjj՝ťzn22t~)? +r 7j<#'hfE"W\ k:)b̞mLn6N%(w>K_M<ǿ6-F_^{ދs)Gy+{OA- @%hߞ\jt_juA %|QEL*GM Ӭz%;j@5rJ<.C;w`&AI}wxD E2 goJ<^iAα3q Ӏsj:?^`;SD5Ҿiy'jJQ9O4;hykT*"Pϯ?bqF t1Q2$L?DmR@[/.zG va,(@U!m4!m{;?Q@ pgvoK/׽Ie)*QL2zMN @g=:>QO[h2iO`]u[#(⬶ag~LL`&\}!y@b R4DG ;tQ7R KLS hfidD @E 9Z_Z:J( Irry @~脸7kE@mx8'|BMӗm2$K~'J~OM#?3p8nAw ?[ (O= =Q p@}ID2TA_~<[QPQB,"T i,uāgl *{jhkzQNs< P iʸث1 "@Z7wզȿIU#"/(+ P .(y@G| @E8ߢ`>ZfoPL -襂( "+9 a/s=5J +ɷ {&1Ϭ@S݁>ٚ5R qp NG8nSzշE\ P ܆v%N %*R itSz}>7NjG1YEG)xZNסB9}ht/ϦCc&Q4-ۇbFe"c@3Q:OycX k:K4/f6 ҇^A5J$ o` HА g\_J8oVN{d0V  R>Q|\/d٣q:΍dJ]h.m'0;6hjx>PeQ l @ ܓ뷊.St5}^ @ЖZ.q9AF)ЯMԸ{E 6@YH`P( ,fy wjAyeP)prtNpYN{ 7QuёqFeZ2)p]_6a3,_Ҡ |]}𥳐sЧp,[CL @t0FfiaKk+A-/thAM2ȞuK2s$j@r 2 |WWX҅qrt|N \4ڰ30 (2iX&R=fIjd()@]cI!y Nwˢox*4D<;qQε*ʱ9>/< H?JMG6O,H2`"0VJޭnA@{d1~i'luJ\4f{ǎ ]@%6btXjf%'~[֌}r}Ł[" r O݂<_`qewn)kB;\)pE~A.9~D5N-1gLI,iŒ݌vϧ6l3Vӟ׳8g$lB'% oeb=~0Ւ_'o&dnl;L=*$0q+g tN:p%Ԝ!H;̹vKc:Y (7sT_ŗ[٧U,Ftn [l oliKvw0v2~G}c jH8BƖӴ^qϑ[s-NƮu~"x2RC3BG*?3~jgEčN}V͑_AVRNt? t vjZ1Y/&~f{5tظ%^H c@_K8QK pA@C' ӗoҡ@ՔgY? 52h^cG 57>q=n77ȯV8ߛ W*Aڑoy5XWu%nu~rDs4+:+HgNLB|W͗LK(X&f:9˔2uճ(ײJ/:V.&z8ۿ^K` L^GZ҅qD{=\GN&@aX!^; ;v@iggͻ>ĝx:K|\7x%#b '6q@f3J*a -e=ܮ0+o V B]^W@b5C8!1K3޵5VnAs㥖A2>su!|w9R9xQ0sg^nHWY6pSoF;`rMh:ܢú'Eq 2)́FJBڢfΔgNl4v#1q6pp6&'@$vvd u8 ց3E:`#Ր=`eNv 8=RoZr27[*ZA0ו̔,~٣lS*hAWjqj#]< *jZd}v.LNߓ9jobwVgGؙSr4A;;pv,ߠ?S:mcg-fR_FݻjMWXN9 !g<B^G B;g2'ȯ˓6b2 ,o_ƹ]9;H|U;v;1-t˔]+Kҕ`f]NIoyeVXd05y1ל7uf~47 e- Jmĕo NRQ^۾Z}vf\\nerT}W)]^k+*#t]s/ -Z`Ò.̈UVr$++ߑ._jc$89[Ur=kȺXdN@Xi>weZc7F͗u֞Y-aErW",@,cEnߊ_ Y>KU֛]z;n= ^M5HbWd bƗIy* G`i*cB5v^fȲ$T KYUnU%{h% 2C]~^Q1Ϡ3KYjbyL.vyYUڴ~FjNEkrf P`9꺊r;n/}8,+ ;:T(Bx!+hu_}u{ XWJ%՝u{&]ڄ6֭:U(e a^R`@M5SWQw nS H-^Nu# GDF딗3G8衖+4u:pwlK;IK?~5'9^;e޽|CxV:]R ^סyљ2-tVse~O9~rz0F[]!{iOmO42VeVִG8EgT!bdXkT0#%/@9|yuxHFX_ e 8j;AntrK!v(}uFsH05 vpP5/~ᖈϵsV%cIk3M t߭ꦉ\O9UbNu||UG1,ћlw&qQsUཚLM<)>ӛgKdݨ/㵬F&e~-2fj%af-Q~̅758nd;L9121B# с]鈷n#\]`f~eywu8FIG `ɺ-b;B;5od:;Gbɩ1%"zz˫NSpu&vf-W3 en ޏoJ/Ic!Q*ڬEiJ6Lbwa ͩi})묻:mN}/Z&9tK%o3қ.NWNZ- X3`y~M6.T"FNvy~+ݻd:V\$吊/$;ݴ[M &-.!5ip/SN39wnѲiYfj h7YS^fᑚӱ#ӱܲqzJT:]ږsYCriU|% fIO6oUDrĨ&+fdvj 9ArT5lcdȤJnt"yNk]hnb^tPG[Jf|m@c\։nJ T~X(; L'SsL)?uCS0q'எnI$[NH[]󴣉,98& %u*k xa4fzDu[t Knt1!.s:)0ե3.8 bvk{7 w)tYٶPjxZ:x.ZRpĵ ē&aSàΩ#gud}v#oش032He*s|NZ,l4>*lG𠌾۬ rп&UwsVj5m ulc7{QqdʨN%vR2TGgLBz :5:WFCi廕%ۍP9פĆ-z9/( ]l/8]݈eƋҰzښsTӎ9z rOGlf1Τ^nZySNsÑߋ(ߧnjW>ߓnͪoxl/8־},r'vKGޮ{whj'Qw1u^f4wIzc??4pvL@6|)9/gQQ23?9E%`~SRL2E'^YHZ{eđ̉ ZH\``N`,Uy16337 qN (i,iTqDFZٲG( ۮA13Rׁ٠iT"Zp|OCDNqq4#|g>Nve݈#d94#aF*ɸ 'gd@:/[A?^Wn䷐mƜ$ 5n /8r!7Ԍn?{d#N՜?q?q:+F^6nv<603/&-tNwt3#s^8ݙX{.ss ))gVD`vp++N^!3ȩ)qbbƯNè9YNz\`ί{#pc&X xJU^gR?袋D2e/ڷoo4͛tAF!/q馛Q͋Ȑ0?p#EJ!~i1|p߿ҥ8w6n8~vąg]3ijэpoQZWPZO JAH'q}għ~CQFtI$G~aN>${sH?i$cm.˓>X 0Dm&yK_}9r/-ˆ #_/yiqzyiOU)'jW*뵌HMIx1q-kzꉫZpK&73nTjd3f/(8@4jȘVIfuy穏"?3^{%Zli)O>3/,͊0!kIdMwcW_äU_8_X~ŭު6/r"ǎ+/_n+k[ ŋYf rP=$w5,(|gp,ocBgdp`3ƻ1,iㆈbWp 7|-GykpD!?hV|yL%qifu)y-Ͼ;4a?;E]dLPN6$%, Yf' 5 Xˬ]]LXlQNFGu{_kȸ~wqcqr9mڴI|'otīss6. #52)7z0lX6J =`ئϝza(F¶_uv>qٳ7,{^++•W^OV{.@Κ@'sz'39kr:(;6%tJNۺZ0э^9BKpjT T2MEidh}[P⏪,/>(B)-ˠ-܀LNi_5bwTnf,,XS':r/U@Eܨz`+WiӦ,^*q狂zu47o~k<‘vҥFBD+V4 pyBPU^{ר% p$rtKM5z9"~V#2ij&?"SFve@M\8nxn̘1M$^u4!P%p%p iӦ(AJO rY!d V즫=E|T!-ZZPة͝tk Ih{.Uv<N*yqQvȻ ԉ$7E4.:;̇I`C%o! /"X3߳.T<[z$ "2>B-Z0g̗d켒ʁS%]eZ2E#K6w_rx,d/ `;`SVup1OJj4ySj2o^Qfu7=FD|F\ 'p3EA1!HCu`혎R5^pU/Y`G760=8؈g^u( jB``%m#0ۃND{Gk 7>y!x(XէM{}V\"|/R=7E s k2B2!63mq.pJ3nzjW#e/9 d%:ӑA*H7 ڨhiי|w`Xid.|q8MB)BSpcM? +ܬ*Q0 vG}2:v~ͻ~$[Q34F܊WsZңE8i^oP+]߸ 3E(\ةFy)%br%Z' sp\gqD'pr ƆL>g8sq 8ѫ_Ny=tNPyZԅ^]*ƭОWͩ,;($` wn4Y2 )~0XyWc%NkZrI:[zi93p%A:sl'm^Y d0Jf"gfTQרC4;q7fx'DW5ŧ>'[P8spYB?rye-?;¿_P@Mjtƥ@<QH봋{oثO 3: 5)Hq.Idi4b[_Q.%~w4wgb&{ X.lDsw@Sv#ՄlT~x.AbcwO@a<4np:u~8CH^ٝ&F1^sp?~0@'w'0sI!5yD5mq::8q6O\8?Y;?=ӆhiJSP'Z[-?R#0 2U^H(eQק_>˻<;0 /đ74-7hH*D%[R?Z 0(w\)ȁN]rqweL@( !O[PD,jkk0 =*R l|{曵WO(gI7h} iΤ(D/.cݰ0kcF:LS' ȫ!Fp?7vƎk:- klnՕց)6:UY4v2}-?g ^ qLXi6/*gIu8HCV<ۓvkQ=o1j}B_DvӍ쒹܊^r?٢Jȵ w4lj?o fpsuW~( ݍ󌾴 Y<=(M@wㄴi&$|A֢m3}"rQJaЪMg@ .Uzc<_!,4|޳=Iդ3C}x,}ݠsHar԰'}k\D,;E}QjNc&>YWL[hB(u6J08YMr+n/K@*@ߠLa>qd3? }bl'q "5 Tӿ(7RSFzYgU*iÄ:*yMCAG9,+),*In'c/Ԝ3x\+q& $_R[8 |5:r֣-~ΝժvLm-!9a!.3c05)Iv`n"kvpΰ"׏01GD?4d)&`Y0L 3 Йs/{;4%-e׭@:4!<`aaȍEQ+Hn-%YGSl>CM5:{ tnd5DSYzIr:+h^=m.YI;Y@IzM81Q'q\Pq?x>џ i4xĖL҇bt@Y%7+pH>{B;$Û OUz\IDATv#2jl ,_DkAZ}a9tQą ij<$#>ݩf;T);܁Hz /7M@ه[oe$ ٘e<󌁎[8KJՈiX KUUFCvW܃|'W#,(@z>9Ita-Aѣ;O[: uKD:1PH1߆@&ͫ%}J6H;)^OL Zש _=tʺ/#E %Bj[t$w q L v(ņ-w]etXM?.EeE?^ɗw5qdr+E!2a> X&JJM/E"9 71.q@ p#M=$<᲎iJlh\7"O5k~rW@{xN& h_2BF|;|3ez`򍂎M2H/i/ǥڑnS,X:("5 OpH Qr`wS"ex;$̏~9ٍ#eh~u#oAK9HiLaHnTS+%fH/" %A2⊏sߙ)6G/6= ԙjpFC0EQPޫpH3ڨѩppWȖ}2^C&}s@Rpaa 3'` {d*qsSuRAWTR\ ԔH筢-|S&N޶wT逸}&_[' ΄DYjEmpL4l*5q?@Z CfAU ^-#mF4? ;S,w`OHJ)# %h3\nA8< KQM*g%f3GfQQU|WweBǧ]J0~ T. \PO6X !'o#qnuP!,v[ ݏژ˰Ol+f#~:?7 (Q _8Mpkś{рDp.˄˵H.q @籨Cx—VuGdmauDZN'*;,a6k%Z.N+*˖Uu)5J?bj1t M㜅{LǚZ 29"܎HmUS$;?`$Xq+v/Y*]uJ6 deNUl'ېhkoe u@OZɢ )#w,oFˋ0z g4~) pwZ0'&M^J_,-N:=}QC禞bP>#RQ'"܂`%kOK6s`ȠQf?/sgWnGb w!P@(JkCզYtI-!qs+i*Qp Nu<> Cl-@L |oxr hZx^ze}"/s'nIϋ^m |D25Z2qWł>zOZl)q|b0%[{ a0F"-3RHbF#\Qs׊ϥ<8)РjY }^)^to32 `ԑQexžc<ӗm)"q>22ډP֯]3k;A8 C Q>PspLjjLe(M:/QWnp?:~ٸ^d 7r_)(<"f\afiLVJ/5A;xtxnE -@3J M2@L.E8'px+~ [m5I;L}8p֓+Gisٖ0# *g pf ?-p^4x,65W WcO%.$ld>@)h'J 2JӀ\AGO܊< 9;yZÜ{s;UG_0CkN@8 @LT۠"km.X/MZ)&ƒ (3 bMêON%y,7QOӗ(5d /Y1# >g+F(;;m"|]ngA;,.) RFh@fxw?;;=Uv!H P c|*( PvPL!%O "OF&( @rK!4BP !3zd'_ a$2(B@>Ս_\>D^_vHPGIuBMAH)0p A JAEkp螣8;8 tSxvl^g!LCXA(_5SF(.(͒e(`@g(a@ Yu3 )135J2P>xV݌f d LFFU70S#QQ c%g(`ņXC!IENDB`ic04ARGB >0Y+H2 ^* ^%h MMZ##Z =^= $==== ======== == ==m3= =wm!b ! O!R E z {̉zzz~zz=^=zy====zyz===={yzqq=q=qq=q={yz==z|z==z|z= =uzzy}z ҁ ۃӅ=^=========qq=q=qq=q====== =с̀ic14PNG  IHDRxsRGBDeXIfMM*i @IDATx]տ.һt,*-=D&&gKQ[4FcXb  R{٥?]fΛwڛrw7s9sέo>0#0BbˣeF` ?#0@ ` CfF`Xg`FH!,pyȌ#0 0#0)DN:`F`F`F I!3#0,30#R8)Y`Ar)QFȻ>Q?򯡴#b2QT"PF݌RJ`(զ&{Td&ߜRPB=wQrvRԉ}>ٔ CPo6 =n`J7;w]J)תScmJ;q+P#I!픖R߸{Hk)a+%&F/X I(u%0U}նg*4(A` )A85+eF@l`-(I&`U)45MN< JS)A)@ 01fX0`EߎTmԀ?TL@6`(ͧ4soh ƀ@`J/,wy@ L'g'VXAgJ`kpZD,L@t32;PC}߅RϖgJ6v P`%h ,<XHޜ~?J)գABtJs)ͣZ)yQdG+|;QF#%k# ;J+w@`9*#/jjJ?dg\v'#*V@|vŻ rTRtNOk]{c tH*!9U/jU(*tR.!M; ڃPPF@3d>DJ +|4V=ĄvL{bbp'm-PcƵ]WoC@pAVR$}>~,`YE!1)˷-;b wz8 dM@Ph:5"T(կ"z4&ԫ,k]CԠ2TYD=9? oEz{`XYď'>kuwسiLL64GAѱaUQj $BxnIԁi9v5^Lwy==ܠ‡^J`GPN HVoIL_Y$_]&VV+Ht8J[UVH(\PS iGi"fAl~lӧ6 0! {3}a%\meg[ŷKLuLF M*K( N^װC@ϗS L: kXp  zaO =?^E|ZED{6#SaӚgӪbt☎E:9QZC~Hi%mD?ǜ[$%?]hxeYx+3?0FlY'_ܲ8{=1S\bǔ>'@!}#A4`@,zaբZ?>nw;s6dj|&K`;~ki`x*Xf"TO_R75o@0FqAU%[J)!((J8=/EzC<э@ ۼ8G=qAajpM0,$Q |p,qzS]JX#T= i |^wk% YPK\28>C"N ځ)"rL)FO~C'}NTRg_'ޘI,pL@.@^Mep͠&FX;>THPB%8{#)â?Pg8cp LBxVW\M@kO V J Gi6%&FlNγ?)G3)߭i+)AmqMAt&B&Q爃TS` Ɛקv5`F}Glq 1>K-Duzߠ"%,L`M(e=҉j(ſ]C~ۘkO&?iF'T)4R LdgDRK܌oHuJwƏퟬo>eb҄@L]دm4 bp#|(}M5B4ϾM,eB1,p뤕)kŚϯW gdrEҶ1EFeJ,`~$ Qc+S)GWΜOB21@v`bQJoSc\U` Vע,JP{wH11w`+#qi}>B-J_#@'Weh܋9~A%__}Z,ܰ×F(@j !#[" >AinVVEŪY}oPƫ"z)a6J j;ȨoXLPDF8/Ǵ˕CqJH(R gs .bzЃ>_D x&0kY*#yF+`!;OT C')=MB * @`?}~sє<[n).{}xo.L@$@L >N@ @H(L1 ZR#)a%O Ňx 3@pHAS z$l N$LX@ @wB~_H{E3#A`xS D4(N{ 4,$W#=LGW0X,ޥ0L tʕD^E!*яڕ+Ň7ΙoV]4gX̯(}1{Sy gld:by$v>򣲴@ڟOBKrJmT K//ग़ԗ3]K),ZMi5ֳI5Q[С0HM &ͻ-Ċj\Hی$(lOx!GFTݔXI<I#K [-&x߇η KB&%ъ]W!!F_zFՌRE1Ʉ3/Cfv61myDφbN Dy*\uD C=G\4(a/Ws^' /ִjoQ'_ZG޺_(ھJb%I|O|ZG[ !BrE zf{q cxi-J ckJ'Pr=BBTLe)moI/ck'ϤM;gd{."EBX`= аz"ЛOJ}nz`?._/| AžAM3j IߓeaeL;h^LyiM1J`#?oR8`&Jo `BL.A* ~D<E-2۬8nupaEN^+^U"b .^'-{w[(>?H?I/@g؇C0Q Nns c-uĽV?B^=մ]?Spxr:͆W7ExsMSQz_# Y%Җ u*~Zܠ ;SLBF1XyUϝVԽR[)DB0h" %#4q[LJr [)"05y}_0K=ԝOWga ز @< P¯PhL` @gC?G>=0J5)R(R_ttsуLF< 3։3@>zqfnI[&Y00P@XcF<+o/~@޸*7gxY1yk_H bQPS|zqg^!m%J@QJX9z? 7Ûb|4P n ץy_? s:Ɨ)*hQ0Oo'FwXL!J8]̝` 9dzӵ (ߕo>YaDˬ;So*.?J*&F 6 7b>(Lg=CpUX Tn˧')hKclJQg3sŬpM0 cU|Lz (-(rUb bfR;]E 'j>rQ6EXH4&^JүA{d hRUw\+qmxE`"ø%E^hQҎꇠ>|YƬvtGure]e! ^-R_PG,ۊmuym$ߜ,wzN cRVA g )K!x#[KB9l \"8 l$!Ae'!`*@K40FO׭*㞞'oI2]Hab&F J|h8bE¹ob y O <>8i]ݣGQBԫ>b/WVϞV`@FxF[SEfD`]gIt&OC ,$"NH-Ez`a~)OĿ3W%%c9Xʏg+(бikI xllׯ^A! EK>-+B"k&gwJZ1q̿ 袎31㪃GEʮS x[@n;/|P\L.T@ 8Vg@ PD(د?4c'lhտQ(~g<vFw7c߭@),g!@Y(+`6JZM~C#v!E}ѪN ӄ*#n/>NUW'wgV8tۜ ZF~2 &JK/1D2!j%FvUbatFƛzz!UYA1bhL;X& )oP+oF嶢"1[G4_]ETgOg`E*Uv]3R8Zw~:`1KaBJB`}%ޙ| ky” 3qF`i qDc:K)s뻦wA>0 d!@5&`VkKF|J̿pNуL@}#PO/$ق(> [wmHR/"`,(b, pI̿ V\u%7eaAMK:K0#P;Ej:B}^&`>m hWL:ՀBn~Żl;W.B>|lG /$;)*D/NK0 M: .,XLI?5uNzUĢ?$k]\H/wh!>ET6J-X$,hvp.zW#& SXLs屺C}B!f]Cԩ{ISGٶE%U&.r^t2彙$ h,d2=H(2K'cS)׿b_c Qz-YN$PҕƉLH!7&DS(MʟMC( #7wcYNa`a@Ou"$YNu> _z~?$e'$%/1ۇ7HL#7;mXsM\}tϏ:/?ZP0T,(+kjmb k&nʑb2c͘"p={j[0K@FT%Pun`3?IG)玑-MGQ[oݻp^GQ{؁=4.K6tzwȼ,|}7܅߸ \70R&1wv-&R<>>v..N}am7aНzb #_JnwʒeDKʞ?M4_`B@p:Wco{tSv^433.0L5`1` _) my v% dwY31@(WY̿' Sm5JoC WR'ߐ.!)4b?S2"fHL#{p!ABqŇ1Dp}t-u_Y0M9= 8BJ.~XG1cO5fWO$ iB `! |3s< {@1ⲱJ!߉~O˶`(FuQBN;z?0QCT`SXjyBHTpQMd/X]ڴ,w/>ǽFYaZmMf :ȟ,T))[~vXA>4kWdL#} |IE0\@F\=C;8 z&nPRě?m"񧺕+ɿ*8HG7.>ϴue1=@bZɆ%R"yTR3Wp[:rn`\"pHdʆe/sgy?]&bV c)B ~I9/N jǐ:N`A 4t̎|xrp jsQJ :mn`CK:΄ ?^.~ZlsIҵT 1~II.*Q_=ip?ʮ-0 T=ln 3Ku6NgRRR٪DLamYM Q0@w.c#Sre ̼l37 d'Y8S#$L }jJX޾abd#0&ك0$WwJM\B _7L骳15U`Ҁs4HP]7b3\"~MRR!b%L#$_8N¬<}J8I5%4࡟TG֔2&r+ \$7ow5%%Y%.PM&FH3&f*UEO(b+J b T @%k,t_Es,b@(7|B5T0WOITD 4aGP8g^?*AxZ{V!oFH)@57I٨Ն|OzNymj5#Wb@d s;KxdCxЃ`bҎG}`H'J-,+D 4At/yBi]I3G2#/,*W\1VX_[e0zxLcQϓH@?| L+평`3xP2e"SG= gS`?%_)Tb1m 5r'F \N^OХNƤon^A`X㠟*goT<Tr'F 721#`5ř{ƍ0}gգSAQK@G ~ÿO :S}WS#'[|bJW*'v3MPRk9(̅R*V(L# ?m5S&8%.hRtJXUba:|5F7mr`L$..%Mi"OH5O &y/OF|}ekg|tX NB; ۾h[%̹ߌ#/#H؏ʁ:]ų4Kl-梅ED:lo2rf{*KPM~khG|K)HJA䉍@?h~bl3,;5>sgF mkwE%Je [XWcEG7ySdw12'ܦnrF`\#E>\ pe~ghv @ C, FTGZ4y<#@-^ YM,ݴ3iy'#/XQX}E>6U1#PǷX `~,xSi e= *JmBTLq]#_#$ h Ya4hh^lJưDZ p-U [OA9F%wl)*CR@hȏH PJJW&p_-dbF @ p.kM?+w-˅Ѵ!Wd zo9q#;EYs0IDo(7[wt< .WA/D lVIvt?<#0%@ p\qY\Ksz)xJ'X'qr3@Y>( XB_,R ԟx[AF@oDd~QHȟ#)3#<EQI"p^cH9$$u;qTVl),cҨNJ6KڴxD Ws"Ʃ3 md@/,hxF&5KH9sVEjF3# C X '\|)toa_p OɊ1#d#;9ehON]ҳBa䉔@4*9 OW8eu*"!%iF>ͪ [$Xy1NgBXG<7mSXg7XNwHW†ʙ~gJ1ZSaes;2IDpɎK w+\Sm͋n<dF ,~}XcQ  Mb߾|hgd4. 8['Z%m01#%;v|N>Qp 9Y}SX?7ˉN3)E TBeK+~Iho@)JUʃNt&;0 0yvsƪbqmsrM$cKp\-m=QٷyuѤf~}bF +7 E;&b 왘J6?(+r&W;>1\`$Y21#s䞄a*i5&[(WcƜ 4=i,_qS?[;0? 3w!Rg)EϭiH>9ϥNdqȯ(Ngx пeuQ*{7T986]ʉ_O] ,:yڕw@0##0x[̓45P2C!N㛶bXЃ0Ӻ3F ^0i:po\MS<z erCb<,s]#й&D`َ  ] ?g+@:FjV(j'FqWF ݞsJY*.Q:iHEj"{WS1'nɂ-e~gP)B{J/?Ur}1ȁd-0@Ma\U$v#2=5{7T8ck))_s $ND>4C  ?w`Up2P)no(S(a iND~-:(fռ;0_ԫ,ՂpR<09;ptnvX8؂)ܸCL7?pT]Yf(hM F 5٦duNu >])MǏ ), [_ndzmND`?qxIaag&C2wF H,{"ZX[nk(%FasӒE)*1~Fa 3 CT$ ߷!n`E`d:6 9e cai`aK~N?8N?CH+sI &4 OS_ rtsC9`r:4"_,ڪ" 0Fl$0{SU_NS=,z 6C\ja$A%J01@١va4@hV6K-a:ԝ [>ZZ͏={ }H{n,#`1CwC(7b0 VOM0_/ÀF}Ұ/VEY`Wg`X gh^:H`J altA8(>lm-xQ'CacB٫k, )kfq7l j|`@%z/ڦ|7L^-*\RmRwT`XLjZÅ+r6G5 h3F }1#7 Z>4P`~`*0iWv3V4lSggEP TJ0t`=ۛQ ĄhY ; * !p@K,"h!p<2 r.(45p6;eu*B8t2},j&@g9/#q0Σ3l=ټcXQluq=hJ' Zχ N1`a %ÔxDm޸?[rAq RcZ1hf~c3dqըb! pcoH/b8#@G:IU[wcJF7ynƻli-o֯c'NJ(|e2n2@`;#9 3l%Wk? ?y5n V#`8 =T\1JG^ o*F "{+1KN6Zlq+ X!=ymVQK44=g%NJA`1%z9ˮO|/:K'c!z tj~ JhFpT^hLC< qmr8#|y1e -+0#5-ӦѪߪu+C1˱At"Npf1}EZ49uEx08JZݦt޸UC4h 0PP!*_Ƌ 1߼??9S{` Q#;i@WNA*ϯ-jW&N3 Ԃ3R@5(ج ]%f U5q[4'z8@v[ =WG0μo> c3VhPTWa Yah]':O:EY+{l&k 4*WA27VׇhwpbX àzXy]"KUTfS<\:ᙋ\r!wlUiclO! 7J\';">ѱ#h%pcv\>9sˍ(Kgl ~MWGnKLoBҝ.վ\2Gƴ1^xYۭ죚xQGd `$4Gf!a>sodcĪKP5?9iUZ ̶M9ϻXç^!(+0sWMA }_1]z*nKԊ1MR*=i/,p\a{)!~ٵ#Gh}0_52x#k]B]&Y-Q⫪s#!TgtVWԯøVe-'1_FnSP[. bne d^bm -#av}rODϋBƎ <)ܘ(ctXVedoYP{4s`e s{m0'8e۠W>Q.5)SE۔wa^] Ne7wQiR@f-I-S5_T3ngi@IDAT-Ue>+ ɘz1[ 5rl_En.8׿cDK`+r~Lc'ti̓b@@ &ԹSeȑ IgY"?aR8{/p+fX V+cU ;1puոncW.ed ϥ@Gqi<*ٱ[PmLЧ.m1h^"7z4rtL`n]@mU11; [/L/zmJ~e/R#neUmK(UKwI+t4y_mw׾լ&y4=&5yժ $2^@#ۓ\;~$u2%uf"W1c3&s9?׫f4m 6פ{6]ݲ*!8@N Rq lPG];xϕ;TU?sȥ~L(sEkU 7,3)AbegYK߹t2\ zaϏv h^ g4׏6e~kʛʖLj{B=:[8OW!VOjzzsn*տYjWJ}6 ̤ӯ)9tzc2 8ni= @a>QȣOu9FuM3s+&gRGx$k ?g67FBYYi[GJ(WiX#H%?4[NN)hP`­N/x#7x.(Ad2"qr?V0LuU>\alr[2yMm Otbf[n1$HgVfcTghx`USЌp{<|$Ӻ \1f:as90Ό&Njksu:mũO:BUIxwYKZ)ri[]~?H'ԷTX8aXQŗ+xc:$(Wy6#߱M0nVc+nc|3~xThcb~RdI_y~ i(o/]W|֪j+$xu08K>@醊Ͽ, PO+ǯ3 7Qg,v0 <!ԴwSW\b @| dϝ{?R$8fj=~|Z =ƆPU;3{=w4eoGةW벊/e@q$̽ #}?ӆBo4GKJIt @J|V#{ZKI>ӊVmۥWksu4 V:䲰WAHͪ/@TaR-VnjX:ԔKCHTUd^8` Ɔ%2q /+`'Zkwն}3; .0mEnU?U<߫! hյBrp$i(8xa4*{ LB/s#̧<0,M^6ƍm/q&!~N VWȯ׎Yk~_bXJU\JT TCR~l LNmUynQhƏ܏gfeL(iUX}^A)ߐ8#"UVo[ FuR /I 2SZ:#L[ ́.3D~}o- 7-YI"\P˗YY-^;Q<":@"[pt5QDtCJ=‹LJƋ P/Ӫ,gHa eWDGxҝ1-[y,7 9nQ:sePYeli; xkaH>Nнe_j%q o50'^'uk,e7n sWi+o!謬u|UhH:P8Gߪy+;T+|Z8Or\ JیEuŀ;u9g"**CJVUN+ J5XZF|13*T`:RW !$(UbEr6SPw4Dg;6yT[gي:|mԳj-G7?# AӲ`$k XE p%((ҵ[藗`>q.g/TU頶 Jwr`V{XyљX Xt[}:A`|XHWq\)\Bji˷O!pnU鹚[>^klupmtIU\tAFK+c6]&uձjȧ3Xt!:B`mkԴұȾ%([Hk,ltÚ+Qk]տdfƺLmN/mݕkw[1OcElfnsZWsdφN_ewԡ-L̓r؄BZ.EC0{"yu^iܴ{!Em[5KV_FX.w8rf|w/LhW 4݌7ed1M/ _dt/?jD@ۭGD#Ɖ~Bk'^X:/Qd悇 ^κN ѭO'?lqEڂH3^̫2~ :u*ȱeԵmTXEXg2> 5[ws!ر2_oTb@]Vu1gJ]A%[=:=-Эl3( V§OX,umװ0W-. f YZ!~Yt}z}A}VTSĿK S &`F 3-V>Ѫle:ˊ=\a:v.}ѩn>Wv$q#t?ǯwˆ-E=7$stG0Y kD3"^x=i<٘<8 r?3Vg$Ϭ lYig^Ë +k\`HO0~l/` \#s^oO0#U*-uRm#iPhea@ .w'/v-eoa@A(A0~xF?էSlv02$RY=QHj7?a+{[$ygkcpe hpıWv/+M]}E,О3eWs-/'7Z6$4ב( b(ƄO΢q閣M}(sjB%#l z79BKbbۀO7BT}i%b/h8]FYIfk`W1^E a0 4,Jݨu/FÂ2(L<(&>7|h5)˶sgt1h_YBRaSQ2a1ԽqU17}: Lv5G a4^+)TfTpy6z1296~{2>e|Fa4yH,Pg^ve1-7pHllso \'^ԩTh,ʿ!H!LX5+/bI U:VTnVr.O0@:u}v*pAS{s~k]e9mO/@W-QԯQǘQ_[F'BǒDa|$aEPjP#TXc!}~aXC T:ي?۸~Q߰8!$$h96'1Ql}$ħW拹gk%u*EiR~Kuݍ^QZ`Z0ƃ0p 0}$^l 7^R]@at'"L{o>Ҽ h@G_>BM@vZF@Zj-jpfж[\> X5WnmWp~FN TKPRO89ѨQzfv)F%>@dGyD1Bkzuw*ダ^O_xqg)lȑ;AbEWA/ .-OqlmW9c 1d|饗:K`hu)߽+,^z%1o<?^ 0@|iqwÆ +egq8ӌryjn>RߢE /Awygx@+W 8/ac{ /8xJ(Eb_|߿4iџVZ[Z\{gT:[!G!-Zd0Uq?OT~ 8V}я'x¸6eC ;πZɓ'fv?,?!&)~ԥ"5*;a[Vl'@>I;i%H[wDVrcxvxQ7bΜ91uw}~i,3nJ O>DnZuQ^ڠs9X)s(]\ ^~eرcŞ=1:0+V-<쳢YfeZ,=X]w%^2ct |K` 6 3suKlyq`5/ Nru֤I1~xQ}``%_i n j/`8S^xao1f )Hvto иF4V&_v<1L#`,rWZ4,+_ aiɒ%8e6<A@$徶 KJl\!~$xF.]EN\東* 1[|:<Sꇟ@<݃BR[oذk*LjooU:dDS!;4Ӱ!GJГO>ilu-m ~Cӥ?(n:1a9˿~{\>S`xH LCʦ6"$iO6͘O$3DG}Th,"\z xWXiK{_~ U9I2!4@7|74搜\J@J@6<8n_IQح[7OCߡ41\y~q%T~I'z[ m8)ET!vpZ@-U:c&s?s~A9uZMذ*jҩjT?E<`#tBaKI# ڍ7yxpM{Y$kI&WhM ~iJX8įS@g:!j8eIEŖ7.= pªD& ϠA@eR/w}W=I$#Ev]tɗm|WJZm۶8D‘ω 5t¸e%Ybb3WE;m%mv)ѮDY _9Yq0ZL [XFu::7t&ma@VZ IUPA`1+qKCF?p"ydv+H~ VGqD )L۷=d˖U) Mj=??UfT\g~U#RMuU-m :9[A=gPi^`XI ւ Gq&-i޼6Av NieP#¶I !A1 ~Ic`…~Wﹾ =W p( "@ICBKp VnkBL0OCĊ6(C*8,XP :(dQޣ}X ҈1kvs8_~C;&?Ch>+͂Nv bЅ %W#R 4a—iP9Ǘ:*ddq>L1Fyȑx&1AWiѢEq dp#?C`VY" 2FÅUnH- GYUZ:: z BXAZԩ.هvj7JZ/Wg_߱G*}x%[-]۠@q$]K}P(~uT[Ak0:-1[_Q.{hxoٖl`OX@O X|>%!*b:G{I_(/ZT mAW4ƀ80Hm=ٻ7#cI/WO% 'Y7Dn5y?ן/O ?}|#Iu|Y2$A B,[FB2¸OVGA*YծnC@5=+,ctR6֭[' \&qH nAMQVfc u˗/7m/FY\dCKk J7Q(Zl@H+xPPP"._>O4sU?xqAzL3- Ɓr k׮A4QNA`dDX yyyFQP!=V5+;[}V#ob %qT}Dɒ[S!f A/5e@33grnIථ\>]バ!O㉀ r'y{.HjjnO@'|ۖrh$XC&?o%aSGlq" kyy :ԨZǍ.p s"UAg&L0:Ϫ-낆IB7D<*1Os }c? {. i (IEacz{/L @hoٲe?pGhCٞuW+488׷~kTٹsg!Ff}Fe4q^}YlsBkjEϦn%`Qnp&@J~acz{d?ӃFʸmxDnmӦM(xh^Rk SrB!to @ le%4nhq&tą,w%$8\4uhvv #l(LD K14:P1+u XJs1JefmΰXR F ROř _\߇wmt|\}\E/YD<-<+}$*riQ7/ܫi̜0~)C!`44K)v3qUWU.59t?Zg_R뮻.&Kې&p˶]wU[n)K_GMC馛Jqƅe)QyQh,-1t 7L ϟdC@$U%_| CߨiĈOV*:+(6nk0Bf#l}ufsw8?.s2Ge^(]><4\xHz ee> 7YC|)pxxqbQ \/VS.#ZX@ `K|4p < bCnQZi)ܯx}BWV=؇}7k^Z?N+\V'h;8腝K-Og3}I t 1# w=k="-70ĂJA\ %+Jj:5 ,k~M 31: WD k0GS PBpc#|?2bj7]B}:mOspJXoO 5Kxk$3(rkFvzI@1%[9SkVdh%P;oAT`'j)@ҖTCuIQdvYr9U"9bxNńYOѿ̞r&zFQ0" 9K3zefzNuLWuuWU^ r]x|fRE=1kPC{x 9~! #=WP9`۫~4WSVn/^~*vp 9$;5jy낡 ڤt.ܜrF=`ƷK%'e"txn y={R?ĵ O \HHtj`Q\Ɖek㤡S6_4o#1S{똄i.ve\l8qwˉy|=ʅ`jAOR0mIls"m-^9qDG,KQ6~o"E%Bn֙1>w0S|nary\U9X@w( 32lQ'3=ȅ H<ą| vaAyPeʼnuqɺx!˻n]veuO҇yy*zu8vkH3~gRAвRdL(4*;D;d}pu *!6Bo eH*֦>ED/wWZW^Q :iSVp #w[Ri`fa QNn]M? M+gM G"`n6њqdHe ~=[9 ",W^y:GLpN㉀iTJ?C*pRT)aÆgǩ')6z!uWo4^|IuܕyE(ʸ`RE|O$mPy~?vX"-`9:Mm3Pq#MySaw(Bo(^r[跩b11>v՜_Aϸ:?^xaTgv5i\yR\ ] lM\l hEivdo믿ʅ/O[]&M ^Ha%.64鸤"w]ϱ@^''.Kz饗bV0u:1̊eCu6)2b#?u梎5܊"yEef=;!-0gspl0\X4ƠYx2v^В}d

C:CB'i_TL&Qwdn-՗x]ɖđw~/A0t#=p t姓NMp9f U|[iRPfi'~K מ&<#p;?IRk'')m;5yS&FZmno9-Nc=9s1A_:!w}Na$cLH_"d0d.@Ң^((_`ݺu ovTJ&xn_;g]a?Ҥ`ʔ?F^D__?kIGl=m/k 4a)Y.;wh$Ƣ~ꈇA#/*(YX5ktQG<$Ǥ|}cNV)5̄>sRV܏v /RCe)LP2_LQ\jQvm^~ȑhЮ/SMa%];vi3^c{̹ΤӔWU!?4ZݾNX6|Zf*"gQ|fۘÏa؏C@$Fa+iQv" TaItǪw}~LN&ҘJhoob7/IrhVIw^ [#PG!;8߽XzKRT3FnH{HkʼnR{]qD\j8{6vs1|<+E^qRQc%D{Nu?a*w7rKzI0so;0#x's kTj|3S+ym%}s/?D 饿dRYi֭`w;c&F$uEm/f~J2@  t@וcB1@+XY7js=7b?^ܸqc\ZtZzso~֛qϘ1C͞eʷxc՜9~>]6wv(T>weR{L LPKp(x4#Gvndտ[[˹jx'G,._5cǎŸc(I"^[%?z*gJ jf{th}б Qi2%]R x[0V/_9-yD^᧟~rnRJ:l*$/8yv$5Jhx8twDwnoܸjӦqU~u]JuF7,K U׭<hW[AT@4谄Ō+Q N/h|ۭ,߫Wڭԩc-ge͉ @ʕdҭݡ׼z[1yǭPI0(IB"Ta PjUu=l&Cv1zl1kͦx5,E6)(x%"aٿ+V9CI]JHI2Tz pe%N=ʀ8Hd¡⠾_&h0<ADδi:u#c߾}_ԑ*i*Ɨ`}k-tM?z-(% ]t1-v6żA92IdL^n]eAK6;n8׳gO2Ww(T#u|1 k` hg@/cǝvꩧDxyeVL{m7Z"tLMѼyB&n,uI#ӿɝfSI)irXdv5(IlDA܈;4pr9sS,ve00RB޼yN!/ otk&]ځBkށeB =9:M4ImݺZP%2&Dg Eb>Tȥ$}? e]v3}nɗ(Q }mqމٱoS&8?`֬YCEgVѺ'J6" {נAn_({mU\h_p)1ٮICKi`Ze"Nnmr1F ʲ-@)S 0}.Уmb#@ |/عcƌq С%?+dVmBgcblT3'1&پI@a`[LM?;ų^>O(;4*hU&n 7Ta| K({UG.D?﹔ɨgBh+ 7d>\V[naڴiʼ7V/6lP'>7@P`0i۶2K+La*W+(#hkFeg |7dU/:HBdO&ڵQGUOFL x\YС0K ŴB݀ށDԠp&iH@ڳ[@Upb :/w4xsk gQnYGZ4efD=}$tlg_(gjD|޸HuY?93rk'I8^B' 0 ԗ&2iB~}t}]۶9hXM_8[j񋷣ԙ qIw'ObJ_Ν;@?(TRC{Is24〘4L$?e<0>h/(0nfBX`jV3+RɔDM6u7k:۸g2k`$*hj)f=~$mKh;۪U8Ѫ uݻ+.jB0 (X1w $)soDnnjӦM팵8 C%4ib$,^E~kʖ-t^t)2{lLwZjgajhT4 EsC RLx:mѾ6b@ *1L?'eJ}с4* 0hEGd)Uz??Ո', 8u467|8*m*_g jz5_U .tz3צ2۶ݡ_+D+D?|rtRxP%K7ӤN:)m~:G]4k[2HF2!Ȃ:ǀ|4!Q t+K4FigJڡ 5gZrꩧ2: IƎ[د_?[|W6M>(,z9~/VGD ?cg!cpX4BO7/Baw@9i9w:t|~\nР:##kaO>䰜|wMSbv#x<~bcUXM=vtY>~@ d1PJjo[c T9aLl2(ֻ9g1 ;qo)DbuhDET+p 1! X#f_Ck`I%R&c%!4hX=t^g|CUA2u}_M? 4gϘ{šy\C߿zо̚5KQ'/{!yi2 _"MweսqEbʧvn~ ƱW_9cBs ۳׬Y= C#5k6?ޭxzkn[aVkzQmk3 7O L@}&U18Mc?ӐK:gۡ.9A= !G"& ]8Tƍ7nzՓ"3(3 iAYPzq?f:H+wt;n>nܹsՒ%KAUXĚuggΜts΢ g4'7I)%_F)Sٳg;YvX"50?6/}0}S @AT8"h3Iq;R xј'.h:/ Lb4 .iN<Ĉ_v4ײJ>Ș曟0d_?AfLj]:B( @:tf~RuUzڟoZ+ɼ[nų; @-'pP~7/e@hH㥵r!ų~W)^?踘QiEr N, .IH.}t;Ҏ6lؠ"Q$%X_7L"pH7k.*U{n5g%`).& /Aߊe)(>cB}3cW)mߏh&RN@DŊ_ 8[)ċ!mIw? liF>߱$äp7qXQ-vUQu듾Uy61ěRM}Q*#1@EQ37Z^#9! CԡZ8&hm.BA6 ?Cfëh:񘍦Hq'F{wv\c0#4eJ^CZ1c!^ba.@?|@Q׮]>Kp)'JPeDgT֭Uz,8p:x7Mz4^ʑ=-[T,&5g.ͿE1l7Wub}?D ,wrL[eˊZpRԨr6RY[|-h}%*b6t4(#}u.S:sy_~Ժk-XT8TtҊuy623S pucf;_l* ԦM^ r gu-Z8/hDxziˁԊ}7`qw4.)gbX0w7$#kV h0ut6*&u9@wVFt6~Ie?ۋPHSq(Hi!͈mܿz2&]@52l?SuGb}xqX*S͋zq:u0M/E%AX_~pJ\ _C֤13~ 0@g'`(djwE7pjKVUM iIayetaq|*<D\jc3,+.4 ks=BOm#Fps=ٲ}S=$$#_w^+++K]z d^Ja_zߴZY e_t)lHLFӁy&UTpGAZfMRSO=\3r GjNRW"w5wriI^tEW]uJ(wygSnS\buĿNhNoWH6HQ}}T$/mKͮh}TS HԒru06?ҭ/F(3Z:T=裑5 #!C3f81xB (2ĊL%O/"h7EոKZ6RwL}"M7\YZQCwTAޥ1T<7uRO{vi+AƝ7?uCJGfLf,䦃L-G :a vfd4iv㇧eyr GW6tD1mU. @RSkO(&v>O \PIykJ".]5su@*q/͋C54]{L/~G/ 0?O"ψ)U\(`!`ǿTUYt>zAoH&Tluߤ*`ǿ.$ +l$iiwia n1-9&1N34Y&oNYAAW-d ?) ! 5 ulk*+ x|k.T׆LбԸT?#pZ۽wk)o(1w ΁` _^ʔ{47eHq[ E7#*Dn篣|3ڻZ~뎍HoHcI 4R!\_LQGD`Ӯ}Ks̐+ `=kcEt @'*2b+ UW]ε\ Lb%p *$A>gmE5/J[0j7bS(^_2/..MkU %B"e+";XO_li5Vs5<`dpG:v}x*>#ģNkL3/D\3H_ 0jF55VyQղmk@ @< *Yvz7\[G`lQ |C 㑸}4⌦G#7OH7 8ŀ$QZ ([HÍ+"/Ad\}@dw!N06$:5s:-{#: >k` 90:ѧ\2!,UoFiٙ0˭v]p?Y)u,Axmhj6Aܖ.0ސQXO b1`B?]U hX%۵\P <:w} ` ]sk(*5F`/d@`⁗{|܇DNѕ igjnZ 25v J߱׮s/W8a1>?0V,R1 Ш8jb @!@g?S ;֪* i] } LU1xIx Pu37mRpB!KQ@(_H߸U=N*!6Pt D!C@/- }vk_0IjT䲲F/(ZZhBP)p4*MM> AzE-eET0p ҏ׭H ` S UKX 'ؤE/U6)6nhKdOKs Ss*)UԫA$- TzS ZF޲,wykn*ԟX/l?$Sf?*NyבZ6`}?ms yCs jŭKowJiA s*e;@tbO7y'E1y0$8x-˨eC" ˷:K_MB@ \7'1 lyn_iHDKØ߼-_Uzߗ]u:y(Sɠ;;wo/lOpnQSj=9mO<(2-UJ R ^D8OPvhz{2V)@ =n2PejtSlN;0OѯGc}Sr|#uc|R2NWa__ad0@h5?D{q'K?vHʭ F&Xa2r|2Q\nzUj{y6JšGH`E4?a2Υ}xN+<|k:5xG]r('5R' _# ԣH6/k:bHP ̷֞G0P ċF_Bmru,E!@ k:pgB@Q?W[W?mYh'~Ѵ̵o0UĖx!y_>=R9eՈpv3j 6T{ەν(R=#EH7b^+ `0EoM ^R3}_֪+F-Qp'daܻYeMUNj$9?'V#gҕDjhQ&~&#( J\Fpţpn텖nڭ?7[l.,^[]S| xκts7+Ղv􉭪7ijϊU,Zj_΅_  j|x4$1/rIkqPW4C}&STHc4#}\ޘ1b\غJFt{;i^n0\63^@*^t7q ޜYc}B?ಮUxn]_`*ZOkԢ бzxQ^/{Ek3^BOAz1g`8<J dkK4.AS™LX{A~jw_B5ݤmt7M p2?05 HAsY4G 샪 ]G{^_طwLuQO;pMn<~(/ 'CF"]/Q.l2xL/:gd(>ozjǬo*-(խYuHW[~AOaLP2~c5^J}$@i@xx&u; w`O3z@tm(ҁd mY_ 2wdDUT<.>h'moEm?%䂀Dt(v<5\"i]K2jS@Bp""ƩQϰE;mƂG'}kn>ҳ0]?]?>) 0X2`C#уSXPf!@. ;5(`WムwgmrYkv#_N5pD;8g_vqۄl4FCjoVX(^r_YUrCrU0~T._WPCCΚ]j֚zzĈ3)N{dן~U GL񟙎:P 73(L م4ZV5VVի-:Cy5EԦ4[ O{sRwS?x)uZSuZ ? `[thЅfvc<( r" KR"**@t\LiG $QqU{!=*{@,?sNumݽOںW޺GQĨAQKӨJYbx$@Tή?u07H#1`\.&}q/7 @bԁ4ɓXEP-hEr,i1>%>x3 6 @]Gcb H%HpHbڗ A]tH@!}p͟-S"H77DIԃS'7VYT$ pEARlvc)m0DHH_<Q-uGF~" +Dv D:T>]L xG~<:!@56˒PT@.>BڧnLuB m;W]z; p> @.c[C !ڶuˈ} %7 ~DBvinԐu囁v4 Y0~|{'a҇u? Cn ๟ר{^8b'f:ayσ)_]?!=dx Pa哲4(t2RU#kej6 ABΔNi]U]׳N"CFHOcx- FB>A@xP=<(r4!aG-Q E>}@CxrxmuIZVd ?V0nI~,N0{`?AJHQ;でXJRL!A~"o]׳MI$pᏹ/.(>KS\$\##@)H7"GJ#c=uZ)GD(h /}RC XSC8zD Q}0އF`#F{ |8c#Ɋ@4zjN5ԹPmkdgo 3XO [0zoDÅH!% u@st} \HPPZ*EDjmbҫXE*5aAO@`i@)ZQϡv9GN_@J އ52iYYӡ:EdEj\DB} &zMD {=&:`@H)J޶W}6wzgzӲmj}x YU[#_,WYTOqXQk`?;xL(&!DHd RF Mһ8r !ɏ0БI+#QW -3N8 6!mV3WBo+yա^9u o\I*~"}:i?wa"&  0I@Ŕ\ )HØph| XCQ|Z~Z p27gJfz+' i/E9,?ޘr i>Gk VC4niHM y1V}̅ppPWw}RZ2pS^a7߭QE(UPaG&\o+臷^p4WO!fBTx:R a;e>縀a ߷; hq>ǝqxyҐW,[JխZbao_jUdT3?zO-/SE0qf`J1!f\z1}rY_eK;{Cs^{hS#?Hf\?bj+IL*Lp\;#]DkzHR}O&0mڭ6haÎ}j 9UyXGG]XyӦx6d*EdJX̱S';tu+c9NrBFbڎD.slF$ LR( g"3::xNH$lv3}4)&!A c cB'<870qhmsg"FGC0JYf#G֠چrwi+"dx ^>[*R9u#@LT[؂ /A D0*k(!QEjHOa}/FF|YHӐA Bpj BjTIsG\s1>@JaJuym11%m1By|/T)S|OzsIvAH$Ȥѐ؊f*4Kl LBY$(L'.ܵFڃDEHlkdBM`hI[mG.Hd(= àW ]#dJPm{R(>ޅsw^z$.?! O+A@b!`;X0#0BbˣeF` ?#0@ ` CfF`Xg`FH!,pyȌ#0 0#0)DN:`F`F`F I!3#0,30#R8)Y`Ar)QFȻ>Q?򯡴#b2QT"PF݌RJ`(զ&{Td&ߜRPB=wQrvRԉ}>ٔ CPo6 =n`J7;w]J)תScmJ;q+P#I!픖R߸{Hk)a+%&F/X I(u%0U}նg*4(A` )A85+eF@l`-(I&`U)45MN< JS)A)@ 01fX0`EߎTmԀ?TL@6`(ͧ4soh ƀ@`J/,wy@ L'g'VXAgJ`kpZD,L@t32;PC}߅RϖgJ6v P`%h ,<XHޜ~?J)գABtJs)ͣZ)yQdG+|;QF#%k# ;J+w@`9*#/jjJ?dg\v'#*V@|vŻ rTRtNOk]{c tH*!9U/jU(*tR.!M; ڃPPF@3d>DJ +|4V=ĄvL{bbp'm-PcƵ]WoC@pAVR$}>~,`YE!1)˷-;b wz8 dM@Ph:5"T(կ"z4&ԫ,k]CԠ2TYD=9? oEz{`XYď'>kuwسiLL64GAѱaUQj $BxnIԁi9v5^Lwy==ܠ‡^J`GPN HVoIL_Y$_]&VV+Ht8J[UVH(\PS iGi"fAl~lӧ6 0! {3}a%\meg[ŷKLuLF M*K( N^װC@ϗS L: kXp  zaO =?^E|ZED{6#SaӚgӪbt☎E:9QZC~Hi%mD?ǜ[$%?]hxeYx+3?0FlY'_ܲ8{=1S\bǔ>'@!}#A4`@,zaբZ?>nw;s6dj|&K`;~ki`x*Xf"TO_R75o@0FqAU%[J)!((J8=/EzC<э@ ۼ8G=qAajpM0,$Q |p,qzS]JX#T= i |^wk% YPK\28>C"N ځ)"rL)FO~C'}NTRg_'ޘI,pL@.@^Mep͠&FX;>THPB%8{#)â?Pg8cp LBxVW\M@kO V J Gi6%&FlNγ?)G3)߭i+)AmqMAt&B&Q爃TS` Ɛקv5`F}Glq 1>K-Duzߠ"%,L`M(e=҉j(ſ]C~ۘkO&?iF'T)4R LdgDRK܌oHuJwƏퟬo>eb҄@L]دm4 bp#|(}M5B4ϾM,eB1,p뤕)kŚϯW gdrEҶ1EFeJ,`~$ Qc+S)GWΜOB21@v`bQJoSc\U` Vע,JP{wH11w`+#qi}>B-J_#@'Weh܋9~A%__}Z,ܰ×F(@j !#[" >AinVVEŪY}oPƫ"z)a6J j;ȨoXLPDF8/Ǵ˕CqJH(R gs .bzЃ>_D x&0kY*#yF+`!;OT C')=MB * @`?}~sє<[n).{}xo.L@$@L >N@ @H(L1 ZR#)a%O Ňx 3@pHAS z$l N$LX@ @wB~_H{E3#A`xS D4(N{ 4,$W#=LGW0X,ޥ0L tʕD^E!*яڕ+Ň7ΙoV]4gX̯(}1{Sy gld:by$v>򣲴@ڟOBKrJmT K//ग़ԗ3]K),ZMi5ֳI5Q[С0HM &ͻ-Ċj\Hی$(lOx!GFTݔXI<I#K [-&x߇η KB&%ъ]W!!F_zFՌRE1Ʉ3/Cfv61myDφbN Dy*\uD C=G\4(a/Ws^' /ִjoQ'_ZG޺_(ھJb%I|O|ZG[ !BrE zf{q cxi-J ckJ'Pr=BBTLe)moI/ck'ϤM;gd{."EBX`= аz"ЛOJ}nz`?._/| AžAM3j IߓeaeL;h^LyiM1J`#?oR8`&Jo `BL.A* ~D<E-2۬8nupaEN^+^U"b .^'-{w[(>?H?I/@g؇C0Q Nns c-uĽV?B^=մ]?Spxr:͆W7ExsMSQz_# Y%Җ u*~Zܠ ;SLBF1XyUϝVԽR[)DB0h" %#4q[LJr [)"05y}_0K=ԝOWga ز @< P¯PhL` @gC?G>=0J5)R(R_ttsуLF< 3։3@>zqfnI[&Y00P@XcF<+o/~@޸*7gxY1yk_H bQPS|zqg^!m%J@QJX9z? 7Ûb|4P n ץy_? s:Ɨ)*hQ0Oo'FwXL!J8]̝` 9dzӵ (ߕo>YaDˬ;So*.?J*&F 6 7b>(Lg=CpUX Tn˧')hKclJQg3sŬpM0 cU|Lz (-(rUb bfR;]E 'j>rQ6EXH4&^JүA{d hRUw\+qmxE`"ø%E^hQҎꇠ>|YƬvtGure]e! ^-R_PG,ۊmuym$ߜ,wzN cRVA g )K!x#[KB9l \"8 l$!Ae'!`*@K40FO׭*㞞'oI2]Hab&F J|h8bE¹ob y O <>8i]ݣGQBԫ>b/WVϞV`@FxF[SEfD`]gIt&OC ,$"NH-Ez`a~)OĿ3W%%c9Xʏg+(бikI xllׯ^A! EK>-+B"k&gwJZ1q̿ 袎31㪃GEʮS x[@n;/|P\L.T@ 8Vg@ PD(د?4c'lhտQ(~g<vFw7c߭@),g!@Y(+`6JZM~C#v!E}ѪN ӄ*#n/>NUW'wgV8tۜ ZF~2 &JK/1D2!j%FvUbatFƛzz!UYA1bhL;X& )oP+oF嶢"1[G4_]ETgOg`E*Uv]3R8Zw~:`1KaBJB`}%ޙ| ky” 3qF`i qDc:K)s뻦wA>0 d!@5&`VkKF|J̿pNуL@}#PO/$ق(> [wmHR/"`,(b, pI̿ V\u%7eaAMK:K0#P;Ej:B}^&`>m hWL:ՀBn~Żl;W.B>|lG /$;)*D/NK0 M: .,XLI?5uNzUĢ?$k]\H/wh!>ET6J-X$,hvp.zW#& SXLs屺C}B!f]Cԩ{ISGٶE%U&.r^t2彙$ h,d2=H(2K'cS)׿b_c Qz-YN$PҕƉLH!7&DS(MʟMC( #7wcYNa`a@Ou"$YNu> _z~?$e'$%/1ۇ7HL#7;mXsM\}tϏ:/?ZP0T,(+kjmb k&nʑb2c͘"p={j[0K@FT%Pun`3?IG)玑-MGQ[oݻp^GQ{؁=4.K6tzwȼ,|}7܅߸ \70R&1wv-&R<>>v..N}am7aНzb #_JnwʒeDKʞ?M4_`B@p:Wco{tSv^433.0L5`1` _) my v% dwY31@(WY̿' Sm5JoC WR'ߐ.!)4b?S2"fHL#{p!ABqŇ1Dp}t-u_Y0M9= 8BJ.~XG1cO5fWO$ iB `! |3s< {@1ⲱJ!߉~O˶`(FuQBN;z?0QCT`SXjyBHTpQMd/X]ڴ,w/>ǽFYaZmMf :ȟ,T))[~vXA>4kWdL#} |IE0\@F\=C;8 z&nPRě?m"񧺕+ɿ*8HG7.>ϴue1=@bZɆ%R"yTR3Wp[:rn`\"pHdʆe/sgy?]&bV c)B ~I9/N jǐ:N`A 4t̎|xrp jsQJ :mn`CK:΄ ?^.~ZlsIҵT 1~II.*Q_=ip?ʮ-0 T=ln 3Ku6NgRRR٪DLamYM Q0@w.c#Sre ̼l37 d'Y8S#$L }jJX޾abd#0&ك0$WwJM\B _7L骳15U`Ҁs4HP]7b3\"~MRR!b%L#$_8N¬<}J8I5%4࡟TG֔2&r+ \$7ow5%%Y%.PM&FH3&f*UEO(b+J b T @%k,t_Es,b@(7|B5T0WOITD 4aGP8g^?*AxZ{V!oFH)@57I٨Ն|OzNymj5#Wb@d s;KxdCxЃ`bҎG}`H'J-,+D 4At/yBi]I3G2#/,*W\1VX_[e0zxLcQϓH@?| L+평`3xP2e"SG= gS`?%_)Tb1m 5r'F \N^OХNƤon^A`X㠟*goT<Tr'F 721#`5ř{ƍ0}gգSAQK@G ~ÿO :S}WS#'[|bJW*'v3MPRk9(̅R*V(L# ?m5S&8%.hRtJXUba:|5F7mr`L$..%Mi"OH5O &y/OF|}ekg|tX NB; ۾h[%̹ߌ#/#H؏ʁ:]ų4Kl-梅ED:lo2rf{*KPM~khG|K)HJA䉍@?h~bl3,;5>sgF mkwE%Je [XWcEG7ySdw12'ܦnrF`\#E>\ pe~ghv @ C, FTGZ4y<#@-^ YM,ݴ3iy'#/XQX}E>6U1#PǷX `~,xSi e= *JmBTLq]#_#$ h Ya4hh^lJưDZ p-U [OA9F%wl)*CR@hȏH PJJW&p_-dbF @ p.kM?+w-˅Ѵ!Wd zo9q#;EYs0IDo(7[wt< .WA/D lVIvt?<#0%@ p\qY\Ksz)xJ'X'qr3@Y>( XB_,R ԟx[AF@oDd~QHȟ#)3#<EQI"p^cH9$$u;qTVl),cҨNJ6KڴxD Ws"Ʃ3 md@/,hxF&5KH9sVEjF3# C X '\|)toa_p OɊ1#d#;9ehON]ҳBa䉔@4*9 OW8eu*"!%iF>ͪ [$Xy1NgBXG<7mSXg7XNwHW†ʙ~gJ1ZSaes;2IDpɎK w+\Sm͋n<dF ,~}XcQ  Mb߾|hgd4. 8['Z%m01#%;v|N>Qp 9Y}SX?7ˉN3)E TBeK+~Iho@)JUʃNt&;0 0yvsƪbqmsrM$cKp\-m=QٷyuѤf~}bF +7 E;&b 왘J6?(+r&W;>1\`$Y21#s䞄a*i5&[(WcƜ 4=i,_qS?[;0? 3w!Rg)EϭiH>9ϥNdqȯ(Ngx пeuQ*{7T986]ʉ_O] ,:yڕw@0##0x[̓45P2C!N㛶bXЃ0Ӻ3F ^0i:po\MS<z erCb<,s]#й&D`َ  ] ?g+@:FjV(j'FqWF ݞsJY*.Q:iHEj"{WS1'nɂ-e~gP)B{J/?Ur}1ȁd-0@Ma\U$v#2=5{7T8ck))_s $ND>4C  ?w`Up2P)no(S(a iND~-:(fռ;0_ԫ,ՂpR<09;ptnvX8؂)ܸCL7?pT]Yf(hM F 5٦duNu >])MǏ ), [_ndzmND`?qxIaag&C2wF H,{"ZX[nk(%FasӒE)*1~Fa 3 CT$ ߷!n`E`d:6 9e cai`aK~N?8N?CH+sI &4 OS_ rtsC9`r:4"_,ڪ" 0Fl$0{SU_NS=,z 6C\ja$A%J01@١va4@hV6K-a:ԝ [>ZZ͏={ }H{n,#`1CwC(7b0 VOM0_/ÀF}Ұ/VEY`Wg`X gh^:H`J altA8(>lm-xQ'CacB٫k, )kfq7l j|`@%z/ڦ|7L^-*\RmRwT`XLjZÅ+r6G5 h3F }1#7 Z>4P`~`*0iWv3V4lSggEP TJ0t`=ۛQ ĄhY ; * !p@K,"h!p<2 r.(45p6;eu*B8t2},j&@g9/#q0Σ3l=ټcXQluq=hJ' Zχ N1`a %ÔxDm޸?[rAq RcZ1hf~c3dqըb! pcoH/b8#@G:IU[wcJF7ynƻli-o֯c'NJ(|e2n2@`;#9 3l%Wk? ?y5n V#`8 =T\1JG^ o*F "{+1KN6Zlq+ X!=ymVQK44=g%NJA`1%z9ˮO|/:K'c!z tj~ JhFpT^hLC< qmr8#|y1e -+0#5-ӦѪߪu+C1˱At"Npf1}EZ49uEx08JZݦt޸UC4h 0PP!*_Ƌ 1߼??9S{` Q#;i@WNA*ϯ-jW&N3 Ԃ3R@5(ج ]%f U5q[4'z8@v[ =WG0μo> c3VhPTWa Yah]':O:EY+{l&k 4*WA27VׇhwpbX àzXy]"KUTfS<\:ᙋ\r!wlUiclO! 7J\';">ѱ#h%pcv\>9sˍ(Kgl ~MWGnKLoBҝ.վ\2Gƴ1^xYۭ죚xQGd `$4Gf!a>sodcĪKP5?9iUZ ̶M9ϻXç^!(+0sWMA }_1]z*nKԊ1MR*=i/,p\a{)!~ٵ#Gh}0_52x#k]B]&Y-Q⫪s#!TgtVWԯøVe-'1_FnSP[. bne d^bm -#av}rODϋBƎ <)ܘ(ctXVedoYP{4s`e s{m0'8e۠W>Q.5)SE۔wa^] Ne7wQiR@f-I-S5_T3ngi@IDAT-Ue>+ ɘz1[ 5rl_En.8׿cDK`+r~Lc'ti̓b@@ &ԹSeȑ IgY"?aR8{/p+fX V+cU ;1puոncW.ed ϥ@Gqi<*ٱ[PmLЧ.m1h^"7z4rtL`n]@mU11; [/L/zmJ~e/R#neUmK(UKwI+t4y_mw׾լ&y4=&5yժ $2^@#ۓ\;~$u2%uf"W1c3&s9?׫f4m 6פ{6]ݲ*!8@N Rq lPG];xϕ;TU?sȥ~L(sEkU 7,3)AbegYK߹t2\ zaϏv h^ g4׏6e~kʛʖLj{B=:[8OW!VOjzzsn*տYjWJ}6 ̤ӯ)9tzc2 8ni= @a>QȣOu9FuM3s+&gRGx$k ?g67FBYYi[GJ(WiX#H%?4[NN)hP`­N/x#7x.(Ad2"qr?V0LuU>\alr[2yMm Otbf[n1$HgVfcTghx`USЌp{<|$Ӻ \1f:as90Ό&Njksu:mũO:BUIxwYKZ)ri[]~?H'ԷTX8aXQŗ+xc:$(Wy6#߱M0nVc+nc|3~xThcb~RdI_y~ i(o/]W|֪j+$xu08K>@醊Ͽ, PO+ǯ3 7Qg,v0 <!ԴwSW\b @| dϝ{?R$8fj=~|Z =ƆPU;3{=w4eoGةW벊/e@q$̽ #}?ӆBo4GKJIt @J|V#{ZKI>ӊVmۥWksu4 V:䲰WAHͪ/@TaR-VnjX:ԔKCHTUd^8` Ɔ%2q /+`'Zkwն}3; .0mEnU?U<߫! hյBrp$i(8xa4*{ LB/s#̧<0,M^6ƍm/q&!~N VWȯ׎Yk~_bXJU\JT TCR~l LNmUynQhƏ܏gfeL(iUX}^A)ߐ8#"UVo[ FuR /I 2SZ:#L[ ́.3D~}o- 7-YI"\P˗YY-^;Q<":@"[pt5QDtCJ=‹LJƋ P/Ӫ,gHa eWDGxҝ1-[y,7 9nQ:sePYeli; xkaH>Nнe_j%q o50'^'uk,e7n sWi+o!謬u|UhH:P8Gߪy+;T+|Z8Or\ JیEuŀ;u9g"**CJVUN+ J5XZF|13*T`:RW !$(UbEr6SPw4Dg;6yT[gي:|mԳj-G7?# AӲ`$k XE p%((ҵ[藗`>q.g/TU頶 Jwr`V{XyљX Xt[}:A`|XHWq\)\Bji˷O!pnU鹚[>^klupmtIU\tAFK+c6]&uձjȧ3Xt!:B`mkԴұȾ%([Hk,ltÚ+Qk]տdfƺLmN/mݕkw[1OcElfnsZWsdφN_ewԡ-L̓r؄BZ.EC0{"yu^iܴ{!Em[5KV_FX.w8rf|w/LhW 4݌7ed1M/ _dt/?jD@ۭGD#Ɖ~Bk'^X:/Qd悇 ^κN ѭO'?lqEڂH3^̫2~ :u*ȱeԵmTXEXg2> 5[ws!ر2_oTb@]Vu1gJ]A%[=:=-Эl3( V§OX,umװ0W-. f YZ!~Yt}z}A}VTSĿK S &`F 3-V>Ѫle:ˊ=\a:v.}ѩn>Wv$q#t?ǯwˆ-E=7$stG0Y kD3"^x=i<٘<8 r?3Vg$Ϭ lYig^Ë +k\`HO0~l/` \#s^oO0#U*-uRm#iPhea@ .w'/v-eoa@A(A0~xF?էSlv02$RY=QHj7?a+{[$ygkcpe hpıWv/+M]}E,О3eWs-/'7Z6$4ב( b(ƄO΢q閣M}(sjB%#l z79BKbbۀO7BT}i%b/h8]FYIfk`W1^E a0 4,Jݨu/FÂ2(L<(&>7|h5)˶sgt1h_YBRaSQ2a1ԽqU17}: Lv5G a4^+)TfTpy6z1296~{2>e|Fa4yH,Pg^ve1-7pHllso \'^ԩTh,ʿ!H!LX5+/bI U:VTnVr.O0@:u}v*pAS{s~k]e9mO/@W-QԯQǘQ_[F'BǒDa|$aEPjP#TXc!}~aXC T:ي?۸~Q߰8!$$h96'1Ql}$ħW拹gk%u*EiR~Kuݍ^QZ`Z0ƃ0p 0}$^l 7^R]@at'"L{o>Ҽ h@G_>BM@vZF@Zj-jpfж[\> X5WnmWp~FN TKPRO89ѨQzfv)F%>@dGyD1Bkzuw*ダ^O_xqg)lȑ;AbEWA/ .-OqlmW9c 1d|饗:K`hu)߽+,^z%1o<?^ 0@|iqwÆ +egq8ӌryjn>RߢE /Awygx@+W 8/ac{ /8xJ(Eb_|߿4iџVZ[Z\{gT:[!G!-Zd0Uq?OT~ 8V}я'x¸6eC ;πZɓ'fv?,?!&)~ԥ"5*;a[Vl'@>I;i%H[wDVrcxvxQ7bΜ91uw}~i,3nJ O>DnZuQ^ڠs9X)s(]\ ^~eرcŞ=1:0+V-<쳢YfeZ,=X]w%^2ct |K` 6 3suKlyq`5/ Nru֤I1~xQ}``%_i n j/`8S^xao1f )Hvto иF4V&_v<1L#`,rWZ4,+_ aiɒ%8e6<A@$徶 KJl\!~$xF.]EN\東* 1[|:<Sꇟ@<݃BR[oذk*LjooU:dDS!;4Ӱ!GJГO>ilu-m ~Cӥ?(n:1a9˿~{\>S`xH LCʦ6"$iO6͘O$3DG}Th,"\z xWXiK{_~ U9I2!4@7|74搜\J@J@6<8n_IQح[7OCߡ41\y~q%T~I'z[ m8)ET!vpZ@-U:c&s?s~A9uZMذ*jҩjT?E<`#tBaKI# ڍ7yxpM{Y$kI&WhM ~iJX8įS@g:!j8eIEŖ7.= pªD& ϠA@eR/w}W=I$#Ev]tɗm|WJZm۶8D‘ω 5t¸e%Ybb3WE;m%mv)ѮDY _9Yq0ZL [XFu::7t&ma@VZ IUPA`1+qKCF?p"ydv+H~ VGqD )L۷=d˖U) Mj=??UfT\g~U#RMuU-m :9[A=gPi^`XI ւ Gq&-i޼6Av NieP#¶I !A1 ~Ic`…~Wﹾ =W p( "@ICBKp VnkBL0OCĊ6(C*8,XP :(dQޣ}X ҈1kvs8_~C;&?Ch>+͂Nv bЅ %W#R 4a—iP9Ǘ:*ddq>L1Fyȑx&1AWiѢEq dp#?C`VY" 2FÅUnH- GYUZ:: z BXAZԩ.هvj7JZ/Wg_߱G*}x%[-]۠@q$]K}P(~uT[Ak0:-1[_Q.{hxoٖl`OX@O X|>%!*b:G{I_(/ZT mAW4ƀ80Hm=ٻ7#cI/WO% 'Y7Dn5y?ן/O ?}|#Iu|Y2$A B,[FB2¸OVGA*YծnC@5=+,ctR6֭[' \&qH nAMQVfc u˗/7m/FY\dCKk J7Q(Zl@H+xPPP"._>O4sU?xqAzL3- Ɓr k׮A4QNA`dDX yyyFQP!=V5+;[}V#ob %qT}Dɒ[S!f A/5e@33grnIථ\>]バ!O㉀ r'y{.HjjnO@'|ۖrh$XC&?o%aSGlq" kyy :ԨZǍ.p s"UAg&L0:Ϫ-낆IB7D<*1Os }c? {. i (IEacz{/L @hoٲe?pGhCٞuW+488׷~kTٹsg!Ff}Fe4q^}YlsBkjEϦn%`Qnp&@J~acz{d?ӃFʸmxDnmӦM(xh^Rk SrB!to @ le%4nhq&tą,w%$8\4uhvv #l(LD K14:P1+u XJs1JefmΰXR F ROř _\߇wmt|\}\E/YD<-<+}$*riQ7/ܫi̜0~)C!`44K)v3qUWU.59t?Zg_R뮻.&Kې&p˶]wU[n)K_GMC馛Jqƅe)QyQh,-1t 7L ϟdC@$U%_| CߨiĈOV*:+(6nk0Bf#l}ufsw8?.s2Ge^(]><4\xHz ee> 7YC|)pxxqbQ \/VS.#ZX@ `K|4p < bCnQZi)ܯx}BWV=؇}7k^Z?N+\V'h;8腝K-Og3}I t 1# w=k="-70ĂJA\ %+Jj:5 ,k~M 31: WD k0GS PBpc#|?2bj7]B}:mOspJXoO 5Kxk$3(rkFvzI@1%[9SkVdh%P;oAT`'j)@ҖTCuIQdvYr9U"9bxNńYOѿ̞r&zFQ0" 9K3zefzNuLWuuWU^ r]x|fRE=1kPC{x 9~! #=WP9`۫~4WSVn/^~*vp 9$;5jy낡 ڤt.ܜrF=`ƷK%'e"txn y={R?ĵ O \HHtj`Q\Ɖek㤡S6_4o#1S{똄i.ve\l8qwˉy|=ʅ`jAOR0mIls"m-^9qDG,KQ6~o"E%Bn֙1>w0S|nary\U9X@w( 32lQ'3=ȅ H<ą| vaAyPeʼnuqɺx!˻n]veuO҇yy*zu8vkH3~gRAвRdL(4*;D;d}pu *!6Bo eH*֦>ED/wWZW^Q :iSVp #w[Ri`fa QNn]M? M+gM G"`n6њqdHe ~=[9 ",W^y:GLpN㉀iTJ?C*pRT)aÆgǩ')6z!uWo4^|IuܕyE(ʸ`RE|O$mPy~?vX"-`9:Mm3Pq#MySaw(Bo(^r[跩b11>v՜_Aϸ:?^xaTgv5i\yR\ ] lM\l hEivdo믿ʅ/O[]&M ^Ha%.64鸤"w]ϱ@^''.Kz饗bV0u:1̊eCu6)2b#?u梎5܊"yEef=;!-0gspl0\X4ƠYx2v^В}d

C:CB'i_TL&Qwdn-՗x]ɖđw~/A0t#=p t姓NMp9f U|[iRPfi'~K מ&<#p;?IRk'')m;5yS&FZmno9-Nc=9s1A_:!w}Na$cLH_"d0d.@Ң^((_`ݺu ovTJ&xn_;g]a?Ҥ`ʔ?F^D__?kIGl=m/k 4a)Y.;wh$Ƣ~ꈇA#/*(YX5ktQG<$Ǥ|}cNV)5̄>sRV܏v /RCe)LP2_LQ\jQvm^~ȑhЮ/SMa%];vi3^c{̹ΤӔWU!?4ZݾNX6|Zf*"gQ|fۘÏa؏C@$Fa+iQv" TaItǪw}~LN&ҘJhoob7/IrhVIw^ [#PG!;8߽XzKRT3FnH{HkʼnR{]qD\j8{6vs1|<+E^qRQc%D{Nu?a*w7rKzI0so;0#x's kTj|3S+ym%}s/?D 饿dRYi֭`w;c&F$uEm/f~J2@  t@וcB1@+XY7js=7b?^ܸqc\ZtZzso~֛qϘ1C͞eʷxc՜9~>]6wv(T>weR{L LPKp(x4#Gvndտ[[˹jx'G,._5cǎŸc(I"^[%?z*gJ jf{th}б Qi2%]R x[0V/_9-yD^᧟~rnRJ:l*$/8yv$5Jhx8twDwnoܸjӦqU~u]JuF7,K U׭<hW[AT@4谄Ō+Q N/h|ۭ,߫Wڭԩc-ge͉ @ʕdҭݡ׼z[1yǭPI0(IB"Ta PjUu=l&Cv1zl1kͦx5,E6)(x%"aٿ+V9CI]JHI2Tz pe%N=ʀ8Hd¡⠾_&h0<ADδi:u#c߾}_ԑ*i*Ɨ`}k-tM?z-(% ]t1-v6żA92IdL^n]eAK6;n8׳gO2Ww(T#u|1 k` hg@/cǝvꩧDxyeVL{m7Z"tLMѼyB&n,uI#ӿɝfSI)irXdv5(IlDA܈;4pr9sS,ve00RB޼yN!/ otk&]ځBkށeB =9:M4ImݺZP%2&Dg Eb>Tȥ$}? e]v3}nɗ(Q }mqމٱoS&8?`֬YCEgVѺ'J6" {נAn_({mU\h_p)1ٮICKi`Ze"Nnmr1F ʲ-@)S 0}.Уmb#@ |/عcƌq С%?+dVmBgcblT3'1&پI@a`[LM?;ų^>O(;4*hU&n 7Ta| K({UG.D?﹔ɨgBh+ 7d>\V[naڴiʼ7V/6lP'>7@P`0i۶2K+La*W+(#hkFeg |7dU/:HBdO&ڵQGUOFL x\YС0K ŴB݀ށDԠp&iH@ڳ[@Upb :/w4xsk gQnYGZ4efD=}$tlg_(gjD|޸HuY?93rk'I8^B' 0 ԗ&2iB~}t}]۶9hXM_8[j񋷣ԙ qIw'ObJ_Ν;@?(TRC{Is24〘4L$?e<0>h/(0nfBX`jV3+RɔDM6u7k:۸g2k`$*hj)f=~$mKh;۪U8Ѫ uݻ+.jB0 (X1w $)soDnnjӦM팵8 C%4ib$,^E~kʖ-t^t)2{lLwZjgajhT4 EsC RLx:mѾ6b@ *1L?'eJ}с4* 0hEGd)Uz??Ո', 8u467|8*m*_g jz5_U .tz3צ2۶ݡ_+D+D?|rtRxP%K7ӤN:)m~:G]4k[2HF2!Ȃ:ǀ|4!Q t+K4FigJڡ 5gZrꩧ2: IƎ[د_?[|W6M>(,z9~/VGD ?cg!cpX4BO7/Baw@9i9w:t|~\nР:##kaO>䰜|wMSbv#x<~bcUXM=vtY>~@ d1PJjo[c T9aLl2(ֻ9g1 ;qo)DbuhDET+p 1! X#f_Ck`I%R&c%!4hX=t^g|CUA2u}_M? 4gϘ{šy\C߿zо̚5KQ'/{!yi2 _"MweսqEbʧvn~ ƱW_9cBs ۳׬Y= C#5k6?ޭxzkn[aVkzQmk3 7O L@}&U18Mc?ӐK:gۡ.9A= !G"& ]8Tƍ7nzՓ"3(3 iAYPzq?f:H+wt;n>nܹsՒ%KAUXĚuggΜts΢ g4'7I)%_F)Sٳg;YvX"50?6/}0}S @AT8"h3Iq;R xј'.h:/ Lb4 .iN<Ĉ_v4ײJ>Ș曟0d_?AfLj]:B( @:tf~RuUzڟoZ+ɼ[nų; @-'pP~7/e@hH㥵r!ų~W)^?踘QiEr N, .IH.}t;Ҏ6lؠ"Q$%X_7L"pH7k.*U{n5g%`).& /Aߊe)(>cB}3cW)mߏh&RN@DŊ_ 8[)ċ!mIw? liF>߱$äp7qXQ-vUQu듾Uy61ěRM}Q*#1@EQ37Z^#9! CԡZ8&hm.BA6 ?Cfëh:񘍦Hq'F{wv\c0#4eJ^CZ1c!^ba.@?|@Q׮]>Kp)'JPeDgT֭Uz,8p:x7Mz4^ʑ=-[T,&5g.ͿE1l7Wub}?D ,wrL[eˊZpRԨr6RY[|-h}%*b6t4(#}u.S:sy_~Ժk-XT8TtҊuy623S pucf;_l* ԦM^ r gu-Z8/hDxziˁԊ}7`qw4.)gbX0w7$#kV h0ut6*&u9@wVFt6~Ie?ۋPHSq(Hi!͈mܿz2&]@52l?SuGb}xqX*S͋zq:u0M/E%AX_~pJ\ _C֤13~ 0@g'`(djwE7pjKVUM iIayetaq|*<D\jc3,+.4 ks=BOm#Fps=ٲ}S=$$#_w^+++K]z d^Ja_zߴZY e_t)lHLFӁy&UTpGAZfMRSO=\3r GjNRW"w5wriI^tEW]uJ(wygSnS\buĿNhNoWH6HQ}}T$/mKͮh}TS HԒru06?ҭ/F(3Z:T=裑5 #!C3f81xB (2ĊL%O/"h7EոKZ6RwL}"M7\YZQCwTAޥ1T<7uRO{vi+AƝ7?uCJGfLf,䦃L-G :a vfd4iv㇧eyr GW6tD1mU. @RSkO(&v>O \PIykJ".]5su@*q/͋C54]{L/~G/ 0?O"ψ)U\(`!`ǿTUYt>zAoH&Tluߤ*`ǿ.$ +l$iiwia n1-9&1N34Y&oNYAAW-d ?) ! 5 ulk*+ x|k.T׆LбԸT?#pZ۽wk)o(1w ΁` _^ʔ{47eHq[ E7#*Dn篣|3ڻZ~뎍HoHcI 4R!\_LQGD`Ӯ}Ks̐+ `=kcEt @'*2b+ UW]ε\ Lb%p *$A>gmE5/J[0j7bS(^_2/..MkU %B"e+";XO_li5Vs5<`dpG:v}x*>#ģNkL3/D\3H_ 0jF55VyQղmk@ @< *Yvz7\[G`lQ |C 㑸}4⌦G#7OH7 8ŀ$QZ ([HÍ+"/Ad\}@dw!N06$:5s:-{#: >k` 90:ѧ\2!,UoFiٙ0˭v]p?Y)u,Axmhj6Aܖ.0ސQXO b1`B?]U hX%۵\P <:w} ` ]sk(*5F`/d@`⁗{|܇DNѕ igjnZ 25v J߱׮s/W8a1>?0V,R1 Ш8jb @!@g?S ;֪* i] } LU1xIx Pu37mRpB!KQ@(_H߸U=N*!6Pt D!C@/- }vk_0IjT䲲F/(ZZhBP)p4*MM> AzE-eET0p ҏ׭H ` S UKX 'ؤE/U6)6nhKdOKs Ss*)UԫA$- TzS ZF޲,wykn*ԟX/l?$Sf?*NyבZ6`}?ms yCs jŭKowJiA s*e;@tbO7y'E1y0$8x-˨eC" ˷:K_MB@ \7'1 lyn_iHDKØ߼-_Uzߗ]u:y(Sɠ;;wo/lOpnQSj=9mO<(2-UJ R ^D8OPvhz{2V)@ =n2PejtSlN;0OѯGc}Sr|#uc|R2NWa__ad0@h5?D{q'K?vHʭ F&Xa2r|2Q\nzUj{y6JšGH`E4?a2Υ}xN+<|k:5xG]r('5R' _# ԣH6/k:bHP ̷֞G0P ċF_Bmru,E!@ k:pgB@Q?W[W?mYh'~Ѵ̵o0UĖx!y_>=R9eՈpv3j 6T{ەν(R=#EH7b^+ `0EoM ^R3}_֪+F-Qp'daܻYeMUNj$9?'V#gҕDjhQ&~&#( J\Fpţpn텖nڭ?7[l.,^[]S| xκts7+Ղv􉭪7ijϊU,Zj_΅_  j|x4$1/rIkqPW4C}&STHc4#}\ޘ1b\غJFt{;i^n0\63^@*^t7q ޜYc}B?ಮUxn]_`*ZOkԢ бzxQ^/{Ek3^BOAz1g`8<J dkK4.AS™LX{A~jw_B5ݤmt7M p2?05 HAsY4G 샪 ]G{^_طwLuQO;pMn<~(/ 'CF"]/Q.l2xL/:gd(>ozjǬo*-(խYuHW[~AOaLP2~c5^J}$@i@xx&u; w`O3z@tm(ҁd mY_ 2wdDUT<.>h'moEm?%䂀Dt(v<5\"i]K2jS@Bp""ƩQϰE;mƂG'}kn>ҳ0]?]?>) 0X2`C#уSXPf!@. ;5(`WムwgmrYkv#_N5pD;8g_vqۄl4FCjoVX(^r_YUrCrU0~T._WPCCΚ]j֚zzĈ3)N{dן~U GL񟙎:P 73(L م4ZV5VVի-:Cy5EԦ4[ O{sRwS?x)uZSuZ ? `[thЅfvc<( r" KR"**@t\LiG $QqU{!=*{@,?sNumݽOںW޺GQĨAQKӨJYbx$@Tή?u07H#1`\.&}q/7 @bԁ4ɓXEP-hEr,i1>%>x3 6 @]Gcb H%HpHbڗ A]tH@!}p͟-S"H77DIԃS'7VYT$ pEARlvc)m0DHH_<Q-uGF~" +Dv D:T>]L xG~<:!@56˒PT@.>BڧnLuB m;W]z; p> @.c[C !ڶuˈ} %7 ~DBvinԐu囁v4 Y0~|{'a҇u? Cn ๟ר{^8b'f:ayσ)_]?!=dx Pa哲4(t2RU#kej6 ABΔNi]U]׳N"CFHOcx- FB>A@xP=<(r4!aG-Q E>}@CxrxmuIZVd ?V0nI~,N0{`?AJHQ;でXJRL!A~"o]׳MI$pᏹ/.(>KS\$\##@)H7"GJ#c=uZ)GD(h /}RC XSC8zD Q}0އF`#F{ |8c#Ɋ@4zjN5ԹPmkdgo 3XO [0zoDÅH!% u@st} \HPPZ*EDjmbҫXE*5aAO@`i@)ZQϡv9GN_@J އ52iYYӡ:EdEj\DB} &zMD {=&:`@H)J޶W}6wzgzӲmj}x YU[#_,WYTOqXQk`?;xL(&!DHd RF Mһ8r !ɏ0БI+#QW -3N8 6!mV3WBo+yա^9u o\I*~"}:i?wa"&  0I@Ŕ\ )HØph| XCQ|Z~Z p27gJfz+' i/E9,?ޘr i>Gk VC4niHM y1V}̅ppPWw}RZ2pS^a7߭QE(UPaG&\o+臷^p4WO!fBTx:R a;e>縀a ߷; hq>ǝqxyҐW,[JխZbao_jUdT3?zO-/SE0qf`J1!f\z1}rY_eK;{Cs^{hS#?Hf\?bj+IL*Lp\;#]DkzHR}O&0mڭ6haÎ}j 9UyXGG]XyӦx6d*EdJX̱S';tu+c9NrBFbڎD.slF$ LR( g"3::xNH$lv3}4)&!A c cB'<870qhmsg"FGC0JYf#G֠چrwi+"dx ^>[*R9u#@LT[؂ /A D0*k(!QEjHOa}/FF|YHӐA Bpj BjTIsG\s1>@JaJuym11%m1By|/T)S|OzsIvAH$Ȥѐ؊f*4Kl LBY$(L'.ܵFڃDEHlkdBM`hI[mG.Hd(= àW ]#dJPm{R(>ޅsw^z$.?! O+A@b!`;X"====T P d#̆81~S/"F Õvcaq Ͻğmcelܜfp|ztp{srz|~vzʂzyzzzzzʀzz|zzzπz===܁zz====܀z|zz====z{Āz====܁zz====zzq=q=q=q=q=zz======zz====zĀz==΂==z{z====zz====рzz====zz{zӀ==̓ ==zzz====zz{zzźzzzz}z{zzzz{z ȸ ͂Ɋˀπ݂րց===Հ====܀ր============݀q=q=q=q=q=݀======ӂ====΂==΂======Ѐ====ހӁ======̓ ==؀Ӏ====܉։͂ ic10,PNG  IHDR+sRGBDeXIfMM*i@IDATxle eeA:" XPQ(Eb'M,)%oDMb%.Ks% g673W ~AX#Rۆ`M$/P@p wl ۀڎfmpoz@H9HlEk5vPf~,B @@ =%C3 6з m~Q KL`^!RJ`^/H v-jP@?\}4HW (hzāej q@! @=H @ 4Qy]6o+B$ (P/uCq`n%" ( $F 1NĂ w7լۨ11IV,TCfj@ F"@[Q-ތ>@#xL`g€9%4P1t .ҥ' 4~vkmlo~K57S֩~?+?}j [4qmq/5 ɅƺcgK<7j$?& ij'o#Xh@%Q  ;wy 4 `_>cloPkF ml_ylR#6>Tk +6Պ Y;*a9%/اv?W:eFG1kij~f+&VR[€`@ !!I3 xA@g6o8طf Ynn6]alA ~^A,6)pvL^A$5E`?-(-/^{Sg,,?t_אTG|iX ۂj,2Ke7R!b c3c &iZK,։ ;"6&qo z cJgT3a` lP@J-s</˽-Yzm a`PZ;[mqEk7WUem Iۀ_KUo-'A ?7h$Gڕ "*TѤvu]1H[mj( &>4A0v'jLTcBp4 !xBd@ xN}ZVfNuv`ӳ!Ӄ mPo{y7Ekt~mfm}e ߊ.46*4/Ȯ i_T+@pxIӟr"VhTc/C`D<#J2 ,] k-Ej߾ 7]>NUm2{[Yύ~lm-ݛn9ۦPL$ػ]T*,%D[%jBl>6OB `p J P@; B7Wf3`H,%tPᏟW%l/yk63uO_l;AsmER Z"K+Of!(PO@&Ny#fmj9oKm d:\j|w+r@U[*觫zK lEAA"4% (@qS 4tgjèߎ[i{not8_ٲ-S~$!#P%YCŁwBmsjW/d IMR NU]!+ub@mP1@ U뤖ӄWǟr_3kgg@[r}nP+un"{ҕ-jn+H{Tc7&ea )NvJ fͱ ZTU;o'@fpO -u+9![֭=ʷ aiґ4Hk9Zjl/|kdx6g|+>%@ 0-KmTݳ=?*Mv:A&ٲ{9SZ׀ $ j嗀S;>O6]EE!q2Ki b zZA,ꓯ ?3o!_;A z4'ҥXNjD[)(;\Ij}fbtVPPw@F@q@ ؔZs5O~_ϴյ?k`C 86>vo/Ԯh[@: iت,`b䰞*CRB %)pe>%l?ٞ}ۯo?NT}3Wl* l#P>ȯ:0D vR:, *`^V {! Ϝ!$L_mw=ՎP+W UKkߌ5u2u)KCITi@ eMdEb[OЅz'7`ڸT @w A 2"4-oZ##D-TN\)TQq&ٴE d5n$mu_"6P\m:lBY{jOU ԟ@qS Ѐ@\BA-%`P9%Ow3Ϋ)K7h #͹ e8ppZG D'5!55&% (m )N@_ov\5;/acu"h !"`oG3TV/}ߎ[~ ʆ2@q^:,j&?ٻ4JԻU S+@ @H;@*o Vr:_'ϯ]A  FSV{V%5@@H@tПI PQg-aՆ-'RlQ d 0}2nY EA4Z B@)H NEHQ 5hCJLTȾyo0i%^"Ib@hH:q߿DHC*HD<}+{#8Yf۩X^\ykJ!@+OJŎ1 Oh/;<"k"A@&v |>`m \_f} O|\`|2_99 K: <{rFҽ辅SjD3@@&v  f͹_oᶷz_V2 1 N(ק\vo嫟z!im 8R  2@|6;1\@>T~v yu< Y~Km r#{eHbR>Md}]S9d @ > ."Nn?H16RͷE78/_;@ rlܿ&rӐ62P6>9Kj}&  w!@|6;1\D>DHjP+U%|p\\y}j_'S@@0!`ﶅr!xl2)MLVNM> ##M@ #OHqj窵U%8yܪ38/B@2_=ճTv!5N HKԫM@ !MoR;Em/5_6"+o,k@vFK< 'TJkσ9 |E>`x &0@|6;1\D>0[k3QY!jL3r7K7h@Mkn-T(Ejw NF\@@&v &2mjg>55PgozcL^_ @I R "qPF&v $HS'js6z 3W@P!`6!؟jT@ߍ{\}D&v #Gswکjh/^(SX^ | [ˑ=Jia/ݫf6}p~@H lbwb(PlM9DfV߳_keK J޿]\+Y"ٞ8OdN960ОD 'M@ iN@EoOT+T40i\ЕJ;-wj/黳m @ eG&v 4">_ܰE{n<Պ4BU3@1UZWٺřmׄUbϏ ˂59diUL\@Jӎ='<\8eJ'`ˁ^QG]}Ip*qY!gCEOdj.tm |'P3Wu~VJ޳TI26aJl׋˧dS$3zV=?:jO{tyvI >"lbwb(9}rjWu~D}X16o}[KZ,.׏E"rL\QF`"=-,u{f V{\}^ad@ > .BJ@rڙj#!"ЭEyD'b ecd@o@|6;1\@ݬ;՚y~B dCV$'`GKզ@ɱ} _3"- r7kj?90.T䰮%Ҭchm5=#C=H!5lbwb(H1kUy͌J2rzRݦPt(woZ?=jN-!s,|aJy{66!w@8\3\?5ϩJ^%/A%@&v sfNz/#`3m𛓨:'@ ā'7* eݚS'vxo"gCE 9kj;حYѽDFCw+vV'`*Mȧ"NG@ G3G$/۳Wܪȕ6{K >Olbwb(uf* KC_T^(IҜי B azVɫSWsTj*;'\qrJ_O]MԆܭf.Q1w%@|6;1\DYqO0!K.?Py8`D/ەvȵG/O*-T(Oݮ& @|d Pp}`bQmo-[\r0Etե]&},Vp_:-k6 g27m'W.̣s8}@F%=M@ >Uf?@mjH^''}\Q$D~DݳyeҺ8Q"tn<8~iCˤ#(W{©]SYx6R+jke^ > .|"rzڥjmՒ6tɿ#/K:/2HOvj"LR)/a?=Zg N͋S~\nV6IDh J0˂5SY ʆ@\tKHwҴ i88K:so [lbwb(؎>\jWmǿ#l_Q~Mrh Q3c\y\dTfPcږȓ;ˠb/Zs@/(I;  > .MvZ[½ʆjK d|]suf L`* iu8K@~A亃ۈmK2|/wȮ!'ⳉAࢎ>T:jg%e4y3֎"@t L_QnЭO~~ rڵD9BZ$[ɚ~V> @|6;1\(}~R;!Y vl:Kg!@D`-r{ KfePצuSlK/t؁bI[r8|HB; >^CijB5o˅#3n{Gw=(ī'vNw w"`'LptdO 'nA՟r'1@|6;19F\6m|l:K*tL2{ iLCQ%`Bf_# ߁{_ `~z'[5?!ٌH >]ߪcx߬Kob~t((/L6Pq@^jox}B0#im)#:Ɉ^5^30瀜,G  PDBS\v2jfiY* @/&\yfJ _$D ?4\sPdrB@B  ~!gC }`x‚*TY:2yYrŠVr6,@so\#W:O>Nѯy_$2oP{A[N: Dqw@|6;1+ƪ%O,SfD G;sޅө@L8驙`"%&ر,mn}jqT^@|6;1{ O2jIv?׈1BVr߱d'@;_&c_-6qt;JxԮOuLl >plbwb(2B{?C}`Xu$ @yQ|h;9 D@Hwe`hR:SBN+i>]W0]"# /ԖT-)ؖ>4KEvFrRf q՜BH&l1}MzTZfg1/kl> 1&K2A w2>ٟ-Oz; e}Swwy'*QQU@ TI*9yVw<):pj [kD~B ٴEqѤcY ~_^zߖ8rͿsK G~sD{^2KlH@ҹX\qD;):z@]!=8EOT*;T, 2lH=[FQ퉧I2iv-ڵ6mJЇA[-FK\EJK]2E4s;2$I @ yeYI]jHn |e!R~:yɴ{7MSL&@ <N@Ss}_>]*FwV".%l"5I @/-`e}ڃ[K!LXS4yqkm>a#r mQ>a݅%>7K6T3wG0TӻIWiJ A}}d`&fu8陵kluGHERfR>|$)QtUSV#V+ߜO`:ӈNҭ]o$5 .y,-~![!IPjau&  vzT6`~a-2 N{pj2'<142B 3}knV35U%2O]IKn=@ "`^8<{nRjV~2{UV\I9~(|]L_!P7BtZۆər&']@\'g낀J@Hd{Ȉ^LRuղN7m=tՈnO~%vڥj]۬S_%u.l`mjAٍ=;ȋ:#D ϝ@cn._]GU4ɈЈpXGQTyZ4n~P%Yj6?NoX)h 5G-џ~ I*;gɤfіشF?E*5$ UCh]~z?Iaa?.R  )|!*|#JD_P~1-OF{G!@).1u;5 [o+w^2I8?1:dJ1!@  k'ϞpT`2I=n{k ק,D؞,A7o?gR ];I/ߘT=H&z>ygGS W@N]V{T`& &n9$`~o}kqc;ѝ@pΌ>b_Ӗ1\gݢ0[ه! !@`e}?~Mb@ P"N OeH9-d6쯗ӗ. @ /ऽ*A6mK5GJǽ^-Д@QgD*ߡzٿ3:ҵYn8+H @ ?Nm? c_-_VvuQuhU~:x9{ݤ,cM Z:,>쀀??KlwOqH@IDATaߠe &$S.APD@ nJ$+6IN R2 @ 90wRD%/}M$9C\}_f !b ìs?N+pD9)5v;8IF\@@J s)=SZ & neFILtZ Ad7̿ gpԟ|$Wd@0v\Q=J}ɟLG`odn_w$& '=~@Z-l5[((-*r @0_unydMVK{'5D8Oߡ&=Z%/i. 4_o Og`JΝGO dɄІ+?EUU-|PߥIL@'O \ R_=]帀FndVӒ f?VT!/L^AF>>ݴ;u_/B$~j^6VO[ÿ;ofٞbuG d"1 {65mzrzbwׄw>n3 ] Di}B-r<ڲN =9}mq L|l/ .eOO\)??m ^D { )p?+eSG .] "# 9;&sz y?dn3Hޥ,tK0"A4[Hvh5׃6]2R6H]?Ԉ @@^hkt&g˷C㗹PMhl HOQ~Ɯ?." a{u6 ՕyYAI9J`KM,7ܴrcuͶ 0)}Uhպ8MkO_.w4>o/15 f_ni  pO`93db8tC/i"~7Y@~覬_P`ilU !@ mg^ii!*uz=)nOS?mتI f) IJ aȘ!@ = y='"@zv`jZ|RVpC-i"~5Y_sq(N#~t/WԺvoB 0C].;kU%k6nquaN?Z߾V6EjoX#?yRF>erm%غ@@o@QiU^Jn7Tߧ8LG@PgT}Ș \;R<匟_%G=2i2@`d2HhS ;Dtwß:}?P0@Soo]zjoRk Tɠ|#7ou>'̿Ϝ2@Ћ2cnlU.BwM{ڹHK 'wb*jR~rT1w=ԑ  8'`"z:#ŷ6oc.,퇉_Qwē3 >Q(6o9-,S=МG|6ϓ. Q!@؞+'n<:-ngp \?n9nL7ő&A 嫋{KNDF4@@\&|Z7)3wI#`fg?;MbM;}o&1i2@f҇H3-bsO|j:uu$s sˋ&<# @`*;gw&cB|}\~Dn~W} ~&Dw׭ևG&vcwHrIF*%7 i#{\CK }UkhrvBB>K }.jX^ivz !|~W3P#@@ ([M;6m#&+oqN? dV}XoTB*Wl4)}&p. !@1#{ɃU~N XQ|i2L>}z C:fC<۲Scn.qZR+A B 1{7?EVQ~R~DpC.C dHGՌXlK68MF| 4c><sAd@ ۿ\0XpGWUn I!G3t_QTͱY)^I a"Ӹ4tnH Y^>գ+ _x't x3TUhm.Qs‚*yjZ0 +@j ?GGU@lսW:Wq`O-BG9B VB8;fqK $Щ,W.*V 4~h'u'@8[L ply;8HC@A'PA#:~eL[чet_$]T @ xQ=Z >1^)CƿnaR@TVhֶIv_?[$ q$pNrț @J,?Kqt>N9i 'B| x29թoiEwRsB+- Z_0%B ) HU'`[qC8)GHx?,syLs -v|_t,9@2:6ɈЈ<0n?kkK&"~j vL/R+qyu*'I Aenv  8#:KQΨE;-5rse-NA8tJ-y"EU3 [g&hXpt)ɳ@  8!O3w鸤<{Zb4ڼi2D *b @D#{1h'p˛ drǧ٘ĜBL ĝ`X5GbyDd t/ϗz/ $K*HW (UR犝 ؗ,!hAGI4q˫[f9I+6snHl^bsJ @hZ%b+@r+5NV)#ˢ,?_5g iүmaS" @Kv+@Odl0?Yn X‡*<,li /pκUbwE8C;I@%(xK4sb<8nӆ׫9N ^Qx *?[!k<$@ #P%_ $BWʜU0 Kp8@\$ϒuYqۗyZY$eA 1ǩ#F47԰{T;XeިEɔMZ xNrEb[/OWRM =`uL85s K (%8IM }J DGtbatU˯|e,v6G#_c;!`{hF$6-KEa0NJ!@ ` T4͕[u4l)-t;nV1B@ *bf#ğ9rz{dZG ݮ} ر;4wt5 +@N[IU"7cn@,? I׃Ş'$ 78IB܀ ,OFt\C @ $"%`/zqNԋg }%ӈ9&>GtKffG'^f hۦPg3#U2~~UbuNnWRAG*'XkTs%$ls9IB܀ P$*T @;&ph|ӎ3HؼFyvӶk9MD|o x u9;IB܀ CWu7`@@|eYb (ukSW'>VԣHOpea j-vA/̖[|eӻL*慭Z (;EO5[l5`c_X/h^FEGyDfu1N|kyN7`9#K^y @ px. 5)K7C/sZǡ`D7Υfxi u=c竃Jv @Ms!mɌ\"Ao̗u@6GXW 5_p[jiaHH;qeҒ ;Ga!P%WJ3> |Һ87; 0ofwpgwi nBIe>G-Q6*ұIS @01,H1w*BEtN~E Uʛ.uv_o?9sfיSeeTIf @/8lfd]B w@Hשѱ y wAmK FUȌ-ɳVnr6grNt@HgoGbV$ tnm'{7 @%зMh[:wbxE+I䣃W=4~|8{]%4Z=H'֔@nNt+#xpR|c"Qؗ,V'3N^_"pS7jw( !Cr_)ӞQxvB;nR˦6Iwk6;IBܠ Ԉ\It⦈y[H@2J=eFch\s Ʀ Eu-ږ'ޚF>6DL VMeԞ @~}8~Iyo[L8[0P$ak @ zDU2-t5G~v jKN {{3!@?}[0||pW 2gq*'=4&/$ qS@ҸH@"p(aDPӹ[/VH3_Gh4βqJwo{׽6o=Fh*Z2®J xD,?Kң& <0nYI3m/N7qJ:4aj?r,k6;IB(P%LuG @N\^o-Nc'N8@|jardD\a:JeûȀeC @*;oHYn}s,^W!+R' 8%nfd+wHmښp|"@^v#aHN @ X.msRP2E?;qjۑpJmv6o{"Q~{*~m7B 0 0E 텲U2 ng~LB d5F @@\0UQs흽r|ёV ?n./un/#{ّ@ =?gd:eyq#Mz_fQ :/LK OP@%pNV < x:bUӸɡ:fG%r&; %$MI\9Is$@қCm][ eCm{ p\()hC$5s+qY&M RYʆ @)'p־-N +/OY%sVmUM]IvO=]TX#P+e6ėN(k,7 _ !@[e@#UٲFtTOa'jqx fF }InA @@*VMPrt޸I-iNwv%Of׌Z$J}ƍ䮣:" mڅ6T%VgMp@26Iz![g_gmέrdB*A ܧd@wc K NVR .u3RU^Cmkv-ڊ$@ |cwێL^Aޭ\[>3[Ļ@HXgoc6;:ireF6 @ яUգ\"3@Ⱦ<>\td c{5< 6@ ~sxt6u3ȌY#$qKi΢lgq;`%uO)x(wE,-#zJF d3j)b};u3oƍn'Ll˸HHH'bd1}*I=^]g3@1@U{xX|gە2bD$@$@$@$S -{]t/׹ y)9R4N^{52d7<^kb=+$  _7-~S@$֚~Sz-$l⏯v迏JLj'v^/+$   P tIJg`T*t:uֹ y)u9%˗s_ssdq36HHH L?k#-3@-G`}ex(~gRyz^,[+R#"ULj@Y T! #p{-xׁJ@8dB@x+.]s.+Kgt:J6HHH&){ͷ_>sP2 ?AfH~Nvu;OSǴ86HHH%0O[zv=KLo)u.[^ FUlvHlqX5;9@]?a" 6HHH 3r.h?S̵f1 j)?[Ν;N/&6HHH 8m>VL$Pŏ~S3Q  {[ &\zx1ٯF$@$@$@$1w^^q >oٯs9vhP5~߹򘲧V+!";ΙLF-%  5<.n HӧeL6jUƄ*P%A_9}Ycąsƕ`;HHHA[_Z6g\>:^"yWSZfȊhP6UT9 yL#tWۇ.?<ک(u$@$@$@!] jV:]8Q(0&υVtۯ߻yt 0HHHr@Κ*ƴriEW{fi[F߲2 U>Ε.r桯fgs<&SPaHHH@ 0[ \>_f^sARP1 剳] էN4 D$@$@$x@aOKpV4HWR?,_"_- t _z7~%G?F$  3;j=^-3d"(QyUW=Գg@hs]Ƶ7]y3?y 8OibdIHHH |w=M?CC:/z^ j4҆mJyڛNrAl 3_C-e)?n0ITca؊RO+c&tOlK+[gkHHHH @/\8tIn=$Zղ߬c!f%HHH |8og"~r߯%_|taIR|3I+zZ6HHHG`ɌN1ښx<8$v Î@@V}%i{V6HHHJ୧Nko}dQ@Pplb5˷gɗ mMʊ~v&    ,٘H:hc ) PkPWz&5`l- @. L$P#/*9ZYy9F>Y=z uijN,.;emcHHHHJ }1JSD`H?/>.Qj [eWwfq5jl- @^ \ruU}bpX 7j ,\gwuxwVö !@3:pxJ>1.EyFѧ| ᭋ'夷& A4c ?ߺ 4ŋ")8:LSzd@&l<[M$@$@$@9%@9xn# OGJ[.B@Uևod:|l7 @N ,3Ntro)F%eSh~V:*E=v '   \(Ze!7,_v|)!_Yrߗ]W-j\xHHH\'S1ʪ}Fo@#-Tּ y@\jQ~W5+9H- )1vpX$   P&@3eT?A~N's(y,Vޣ:[BϱM$@$@$@ ,N$PA`X&-s@C—*1I@ &xHHHH%oc4XM);q%),owg!K @g[{ˮ r5Z}z8ihU@漮74[g=VBh@'OM-U !dF>5Υ&pXJo IE*/I(}ز{@lqCϚ*&    \2L$PJ`h䈸_nkEyȚW@=ep?ϳy(q2}BHHHJ\HQ:~wݫIdʫ`WΌ[ڣ$pDښlD$@$@$@$\kr@`>!2٪CȧϷΎؽ5|gLTrcZRD6f- |J$@$@$@ N+~H o;$΄wg; $C4* Uo^լ(-(ZHHHH G-`3Ŏ ^b@9_rjå'wbJ6l8Գ_<;Pxω]EĶ¥/Qx-8\y @ಅ]wo +Cڼ_\TK)359/S$i>լ(}fJBgπe8[AxP,@9_w~BA0Y@X!IFג Μ)Ǝn1211Ef^ _\k\ ?v~;ȏAQ.@jDh @HHH ,'8u5{xrw;&iRz|Q&JJδҔ;Wc5[(_HkP (2dв9c`@)#=a3HHHjx)(' Sy< ˕P,Qػ6S_+:[Pm|_+U_f_-o"X:€}  -KN۾ y\>)&e-q͖7'?\F@e} ;.G5huiLg$@$@$`<=s\غL,[?9I23Uչҳh@%#L8"3 ,ܷNh? `E3e:$@$@$NJzpOVճ^G luXmj\֬?F@>}h0P+g~YLD \M,u "7,ET74"a^lyвzbos ,sGh@Rd&   Lh'OiwalQHsiu+gڱB@|*+S%vKÏo+x&s R؂z^uk!fiŶKB gJD$@$@$W0F6On@!1 K ]&#;tDyS+yHP?OU|.U'zfp1zyyHp$66i.  8JDA6Ex=aWR@>u@s9M92G_!{,h@%3m,t 킍# wGЁ A&4EU"QYC̓GF!Ud?Zt( {F-?G.% GFIW'`ȫ)9's=[qKOŲ<   2gSvN\h:*M6!΅ .G<:S<8^AdA$@$@$P <\6I>(7{wWJ: E_K;Zl(-;.)fZuVL$@$@$@lZYzaeMaď>ˎ6htߧ7Km{ٳ;qZV՗*l5˹zC`9l X"pމth 7<)5QSno:PPט*+MlnMk <_D勺f9 <~̐Tl¼pPrD9RJ\YJf) lj֦Qrx}_~&+zh#W]|P,q6dTƜHH&(&H[j>P bwV3$Dhkug P`[5"P];<$zϷ5IHH ,ڮ'HO˨L)H! 6)#bphę`6m{v}3]JПF 3;swv:eHAFQgKU7_67ڍ 찊 ^9|؎֮Z5~y% @MΥ@M89=>qX}3BCj)]eqU}Yqe^'dyYMQ:dXXŋHHH.Ϣ@]@9< ޾a՞#T̾ Q mTJwVL$5#dz uzO>+NI^H/!  ̠ܝ7$^8 ;4H! tf35`Nt+2߲)=~hcMxn߀3@EAT{x2Г2),NXPKmphx8*lT<s>Ӌn/u.Ƃ0`Ro\W8lPgA}b 2TZl'^ΛtLVX K`oYH-ן,._1u\5jJ!IҡזZbv.cAxj{So\\BQNsIdHf=s9'`uH؅$j "_{Khk;:33%Y$xSw5X@e1>`Wx]aE0&GS v[pI3&dO&3ĽkOo,KvD]:]k_gDPvH ȢɝbX{XeIvf<6xk" NײB(nO.>bʛ:/ˋo$s2Zp9$5DGծm䙾5.ý(51ͮ,`&ܓ|iX{~  hj`0)$ )#'i+nhNvI[z*!̚+K߼)׬=_Xc? rUCFۡ5t pڛ{i*gá ^ [=41I_ym6ԩ$=nSycF7Q1ԿYb11sEq50Xyqq/O:u8KDƇV ^,JzX'Wjc弯 YUd0txpO.lgan0[⫗ yp )8mza7wP۔03?KP`ROUL!H3TAte,ڕLjd34ێVL\_G`Þhc---mu b1^IǸec*yO10|ik/Q2O!^/=pƽ3 I:vqkxmP7P0T@JAP:"WUV9"yZHّ*wKRn7.4i;k6f #'ٓ X˂sQsi85A@#GhvJ0I2_T grLG `3dYB884jl Gb7Z!04rD>4R0b!@c憘U;^.DL&&QK>8ScO=;+^{X:ƒ2eYm&}]0Wl=U8:\0kw&ykqԫyn`r\iC #zlWk2Ahvco*.H "`V$iJF 6(e,QLh3Ǜ3,mQc*Au\ׯIB0Kr\ՎcKj~ Z *)u*<W-}mA6v'_03Eג2~|7z!gfv9_RmL@D_LN1*.)& )uvV6+xOzO u>\Y,;i^h'(ʋ m. 5Mo^H{yAKvXlrz@+rP:E`400k?N $clu(.SſO*쎇_O{z;gJYNϙrI0I8yJ;(v|hEIWC2֏)2T[th"ŵ1K-@j'tPl}Tgi`vU"|PQH^A!ɤD+&S\-2_>|i HY5iPL{ u2){-߄JIwےA*v(V'`V4N/K(Ṫd1u[AHd'Z&(rM]2{7%(R1\3M2JG[)c&L00k<>5k-t5Q v=G?ϋ+~;B-M}?L}F̆tocR廘d6luN3ύË2%I3mpJ; xrwJXK&I)jYZmjZXNmq8Uk4 %8KC *!aQhᚭt~y\%"(D={f2B4viΛeU^W5J ]\yJ-ޏ^3Z;?⮫"~f3@s:?Ò}n7Nnu2:eW%"ÄYcѤDu4%I~-ҩ5R|')?liv#4%wJmXHR>S0fZuTY`kME3*.އ8k0IGcJd.U?yjQR+ <]k}8|`ģ!OPj0i(O*<',n)29Y_mZ55n˰XKk&QHݮ)#Ȍ䪥+LDL1JK=Y玶x̝Ԧ@[^2fBX`@պT2O5x+tZ(H$$$h4pώ*\u\juPP^4mu_qF ʼF[d}qc2WkdpX٠3̵N> _9 Qx8䂍b4Y佬PV%h UUҵk+nI+ +qbUjlRo_uz#s,73i `?Ʒ]^JjT7%$3 ȣI$gW84es<+V_(BR0w8> DST2g(4vMNQzª?E^{Q)qfƧn+ϙ{InRSb#z0b{^ Lhi-@t?I&l|'HCZgaJ0lM4&X>596rEӚ+5iS43/5TN^ٿmyTكY3cG7|YkeslO%TWc6SKhu4F75 TJk.W{|d"-{š1.)a$|c$lA{(vCpVwrDmJ0L~e˫^&MZ|\ UŃ 6M)l(&hKXO+ @ @\pa>l0لE@CjV߳n5^:dJsDS#W/HMUWVVx^U#hu.4AޝIOL)h\~ C;"~KLxĎZC͡D>㷪3NxOsԔ0~{MZiix$?MY}O󻒔%ώ!/kȮ)ޭ}烩*)?cg42!u4m;MR`ɗ7 L@gԔHWD>l`_ϗ¤ 1t-3:3MLͲٔKT>A`!'ޤQjǩJ*LR5HSm 'lٱpɟ bM _U`\i^ۉV®+kNteH#BaT;`m"5)&[,SaӚѽ8Sz0C 0 dM"-~P i.CcFx#/̀0uؕL_yxXfFb];. SKSKL6iχKӕ4VIFJ1`?̙Mu X`2|` m(}@߁4i^2S 34^iҚv=)Giki\M&,ӼiНn;?[9>'b> 0ЦB؄-<`(\Kx BHL־/Sܳ13[v\-Z:YSt$j:tbҿ A};kWWF*+u%!􁁜xsc8Qw?zrs|̓S8@ʳI zi' ҈ϨM0F6Khݱ-^T \0 Etq  VQp(bo CK.(ߪgZZp fxL[(3cw?ڊUp@xi B{ATz<:s`wUY9_o_LXA:L:XJba A:&.+ł;T~Ӌ Uαza`*9n;yNm{}(7/:^9˝ ˎ@^/~csvƚC#@{GԴv¦݆{h7A|,&3ۨ<Fa\m^Q#SS&}egZ{;C& pLZ6 h65dZ e6*mCn<>/OZB}zɗ7<]g^+ `[`E֕W?bҡ[ش5 *KjcH "S^-zhu PPOÌiF-)1B,¡ `r7ڸ/d j? !% TC}#푡1m^׹lA ~wnlP1LIO#kKY>]%=~; l.0LN{,M=^4#XLOl95o9pL6Dpfתp-#GК}UV6yfv++uFs3 T'a{- B<,ymGkq߼FU:B[X5ݍIF}JI ,ftt,LS*`M8#5h!+5)q+=RyVj:TmU;KOvWU)y!P4l4:m:כq A.BN!,a.I`\glK:E&MjcqU vI>r>'I#si˲wm#<&m70!zTvm"8 Alr:NטQSf iV^YjQauǿ1cs'%S[&f¼/ ۬N`Њߪe]i;.հBMWi'5sL ^xŇR|QkR'I  ^q#"ItFe`)+av_(=FZ #N]T>@99հv7&&U#&8yEhv @&OsTWPJCi+M麟$lϤ!z7YT+hS;ur'0 I⑾H W4Ι4_X676`ڋfN+c8G2a"IP^$` Y1x}V)k!cЫ䋉|&l4WIǢ&&L&eqdX6 N'~b|H,vW_:Qx숚~PSs|vaL2 ?Schc $@P`-K@`x?JMSUu#+^q-b>(e9r[V{ze&?J^L?Di9 +ڏ! H@4ݵSڈ=ZtBer(sk ;vR*M-4*ɅQq=|~8|-D&%iFՆ~}י]UZ$bCz{:I_Aձ|t1_6HCzZ-ݴ$Vx@@-:ɏj+at`gRUXM4t6ۤq3^(T`fc{S!M\L~W{d3'L%"M[̊O- ku5*5(0AmKh*Z3G PX鵌%@%U>9?>lIh\mh@fSԗdA<??Mक़0YR\6p=M b"Y^vGovhTc/IesƦK4+jp,_@ha:cq*&]xoRkA5Pt;j-28>uL8nrS4J7r,t}<;GĮlLzkf֮v%4k48ZkIc-Gξ6j&Ce8)vfWcʯC4· \>rDh#fvOVn&D˳&VYmsW/̶?7~&K2{6.Pc("Ul;䛀wEtXiE jɉcҪ) IyA JWv#f z:7*K32GVX .mӇFvIi툥MPZ;Q]D$ѧ(tZسZN3)0)dZ ] 8]Y>#o$*j>( E.PˬV,v^9/O0T˧ZX~+466i2T>3jV%# } WNؤmI~$ӛ3Hq{L2. ٯ?ߓ ؠ:8"YyTF pB צ3)UGoi+#`<0r\5n]5TMk4;QVS6&Չ:\OW`#@yp4-$u5,`^ԿǜGyp=n|ԃM"J١9\xXdVɧݲz'vذѷ{EٔڦT T}:jjm}Q酅do֮xF,0q7Y 8I'!08vf@3ѯWwBP,)YVIX|xZcqPʀ0~OsΙj !b-]T9Vbw=,`0CPNt [HCb?q2TVL",W }TvNUjH>x`7 Fi|cU;jzxVWYzM˓( LܲR+VA͇Ztc4Rin/LV~MaT_8g 1XMg^$ZGR*JwYoQlrw6>'z6XRv3ޅ3t|հhjY184e[&[YKeV"7kڭ3~6h3%ǯW#ҥ^s.n [A_VÒ;x/6W9x`OigZyj} &&w*k^J[@IDATm{Vs+L:a>1Mj UIsVZm6g Z8 !0Y@ r"Nӌ\GF(P H QT>8CbasMW`Bq;O%LFu?wD>7oMST؄Uw]E58$LuQ׆I "pt9bT_}tJXٻDzX+Ab!= C7Vˋv 8 66Wa!v{P@k*J:appŖ;nv``R;$Ӹ}V>wlx]rң`> m8Y|A2Kqċ RqSYհ+ϙV>l}`R;5.~'zlw_Q%mv,d8v cjGv_;ªucq A@@@%Bg?Ւ :a`aKZm3I .ibX۸Il٦l pcs! 㰸l2k6'2ּ l,7Rz)vwv9:֪Q[Z6|?BO@ H[zu6cN&gl`V/K]@li4 tx}On-MI9!|un7A{՝W3l`S8wW,MTCB mI1 qQ\Em&mpˆFn!e~eLqԣݭ5lML ;1˯u~?N=8 $2P8~KGVx^>FŇ)szw1ƃ!b3(҇q/S xMNZ6gNyn8irNlXŜs®v?v1XGC`Y] !}]*N8E1la]!^cPÿIUK8EXu(S̗e?&\UEBuv[qpzm!E>N{/uAXB(Z:PZ&r}^W^FڟM %|;hI碨Fޟt= qoD{d!~ lUFy$Xq,Xd!|ڲiNۣ}d®?#,Ӝ[ /C.&Wkh'3_m3oAi \wcW6ݘc5WEar|Zz Z"hloapA#K@!N=ȋ 3M~uh4>l؝ƩY>G|郪v;X`cTؕ"{qra86!LDٶ#TJ!xZf~=>a1FhƩ }>*찯tv*^G`GP~-aj>Y pq3/t6<봩Q^܏Qd:=sMbjF77&-j%wdž- ?"`Ԏ?xu"TF&]pŃTa'w?EA- 7uܤ K}Å]_,;LlLk'뗉sѽ[5aTۏ',qSV>ܻ(½tFR{6aԠ0ߤ7F/_h-Nv`bY |IďdH< BK@ߖ/ྦྷ}qxp{_LYgdos%aعXqKO]φhfT~+,G&Xł&Tv<%+ƒ?l]} 6CT::jPBc=ۍ:l-AJdB->mԋH!+ϝfI`C j#6nr4d&`8~hՎxjfQ⨰M(st'wg)͋p-hFvj&rIIVBm\@)/BL8ڜy|7B!$h7^&TяbS,4jL00ūz߫L]L1'ʵVbK}BIW^Yh1*L#d)N(Ck9a;/ S%6hL*94w\_m2XȢ?u”RIU"CqܨV &[M|F}^[OTϑO$cQ(,jLC+2,fɶ@> 4Twމc DQ|S@zyTs݇9v|ۻTսh6ph#P )A>i0Mv`Y c-Z\wn>ևƚR ͒Ey[] @[IQkZQnҌVٚXe,*7;}a9kPc)bպVmT6(A,MsI^e8 >E/&T+W뎎*Lv!{窋f:;'gv9a2oR;ZY$| H8^U6ksP)y'oHzNTA-d 0Wnf Lji-.օ8D"-cۘbt Lث7nEz9 (t@{<z)ϪM+ʡɰ py[!# Sg-AĔ?BB SS$+_ωy9҆ ңc~\|?-h``2GIӪ2kn\1X< _vȪo| c-RX,F mO$@.}kpl`P>(|lD6 #5#hGX p$X ΠL ;3+&:Pv*'Z8v ]B,!Uwa7GWĢ/h ;T1;e})ɋuaW|$@8h;[O{XFROCT *NXA( to12³uox!3X%PPcJVfk_KY W4COP>ǣS` RaO-U>@d+$I&D{J~؏Aֆ/?#yHHHHUcZhq\N| ˎ tysF@~xv-;H$@$@$@$@jfI۔`qZni1׹ tlPOC1\+HHHH2"9IV@> iө~,̿|&͜@F     7 ̓4͞*XMPraFe    Ț|Xi^}(6QO>/]otxG=   ' ݻw[$M}2-㙽ba1a%)J ޽{Ŗ-[ľ} 0a8رc@ {nϞ=bÆ '7;S?Aq駋%KtoH#P)Ng Vq)0HgPƍmmm%؟-˛?c?gqB'N(OD̚5K\pb… uo~q뭷(OXb83Eq9g d[mӪ`}z C>eozӛij>[Q Ե?O|[n)̵<@U`u{]AC{ꪫG?Q|i@I~ B`eBsVf`WɧMﰀUF`xDv{_/z{{v_ŕW^)~ǃ ӟT\qé~^*nqE̳'\ o(a̞=_}}"YoڴI\}WZp_ 0}Y!w0iF^+p:g@3yx1 P\s5YuĻ.5 p48Ԃ-vl {Ys)ho9sɎ;J.6|E(9$n>J W^׋UV |5 N.̙=$2EkH3!fܜ h4A%:묂"@ sQ)x\Ц}wm/~B3\gNcՄ1FjyMK!~Uo/x57qOdA5'y{ ۶ >򑏈}c5%Go,_k׮ַk^󚊾C(u%gxz衪E~q`zyW D> x/Oh~,?\0q׊뿮8~zGG/E$@=anPOȨ@4@ܔUX-v! ,<]\2h)5띁lO2"pF {۷dm$v `qQ(y ᳰ3 E{f?Qx-;ieЎߺ;Eag8>b͚5Y >U TCy(A5h˖-+؅};Yr%x W>bwBVB ̿N8$ |i[@$?m"wQ/+MXx٢SnΕmY2<&oV zz!}]4-8'ƒpq5_B%ZP}@C\+.sL~$J#tIE~Yƒ3Tkᜭ<=ӟta~![U`.{e/{YD\e˖C@D@a/ oʏ\L44c0Jn?N%K3ǷTNf&@e ;0 $y(x8N9B.|F|k_+=O$JPW}]2@q??v֊} ,)> mciҥױ >L,D7?ӻ/c r[wƍ+pq7C`/̀uTlOl׊nno+䃲WjtY5pFа'Jʀ?XR{{{F1 8T To'.h'[J&»{q?~ |pWnE1R'O~0kP"Ftv-<=.صkWI'/H"ZEybZ,|&\lܹ: Ieq#JýJH!&NRQgB#{KH s&I~S?ѻnV=N)3G`7*c8?n~WW]zWؙo:A¥r;N@źњ.|_rY___a>ۃd`@|7wG?UNB%x?wIZiƌmщ D/<;n,Y'Lh;xX}hX$@1 4I 婘U9{oTiv+{wT-}zI10$A|ذa8կ.~̘1k--A T?Ҭe@. Lfx64P x^uC&SN ȡ?g}tgv,8{oD(OwqG By~ghC}k_a /({ժU[ڊ<-ܷM`SO=U$q\Z\V( ȊL0NJ9s=?묳Loy[ٳK@{ `2Cg͐uToS$ƞbp}Aؤ:Oz8GDo馊}مyʼn\py\0q{J)Π88XE;cE~B&+YIOnp n(xB[9Cg]TU6iP!TZW¸z#g_xC)_,qΆLtմ6mRV"3,WZRH/ע:DUal}:~r hH݅eq]2}(>$@fײW^sj6#>wHbʃ1gb[Yyv6JmCk{5;w.ij=dH^ o({챔ja1YYG+N>Xi|,^g:(#-K%gXrJ *(Lr~zNpAO3w'(&s{vggLWuuիTzgM> ,$C 0k~$75j U[~!&3ΰ^#o $@Mw+k%O(ph|WRWuK`#p:8 6#Y={bM>%;<_|O-JD`c%+?祗^j9E4 B=䓦РA"B'୽_~̓8!oi|ߓO=>}zy[kJuӦMUVV^m|a*  BaGlT]P ^ U.RiJ_~9P:ZSiȑoB"0o޼ =h<?{ n$>#&DŽr| $D#5o<-6q 'p h${1g2"5NUj׮]yj鼼Jm&!oڪ]w&TKQN'VrʏK/ #FYfn1Ŭljiƌ4{lc\k6؇DxtN.8hm/|CKy Lp)@jbQ'0/v\^J[bSD-GQFj; 'B ʹqD ]/>]Sv+H@XmG$qlq:4hifxߏkxS6`?./e@U[oHJ7 ~NKH?0n_}qtM8a7Nh iLC;03ѠAB #|y+ E`8Ykn{EW`Wu<Ȯz 0g^˾N sъ+Ndjݺ%/v^qjժt4/mx<mQ=jԨAW]u1+#ט*AS_V%$(IzRalZ$: 9^naoBE`΃tH<7nx;ӴA:@.`бcGKKX^[H~aGfs=į~+&`ϻtRc\r`-[(YYY9n4¼2eJF!*իW.B5;iD@ #Ajԩ,N]H==Şex{.utRR6L HuO?mqYRώ3Jӟbhj|`9U2dH"K$B}8alV<Ǐ'l87pE(O!mh,)|[op2v2-W\AXHgpB[PP`b| I"091_1BLj?3͚sZ|96h-a˗?n̒"0qDE4pp\N //BKdG`} D]!MGРw `?GTC>EnH<d^O ×r @#9Ĉo-X`;x`:SW\pT}`'x݇C5kƳ2 -zbv\S!Idm"jtdQUs̉4oҾ 'ʡ #'1uHױS'0AGor-96 -۵k-If'xT{v y3gZl FP vF©3T-7Gݨ(85-` 1)wqeiWnIa~%LM/؅ B6mAyGx4٥KP2nx㦰neu`)إe.]~Pt5H FxW=gr L8NЄf/[`׮]ASOhpɒ%%#`nZ@2'|r<&2BDKx7Rڵ'0fBrUkoڵB ڬR pMVՠkwGם1P;WC/p0]iر4k,7z d(17&L0f5i6 뼼<\v C #AI&Ƭ@\eSC &b|' ⨂վ}{0`@?G`Z%Ob &p8Y%ǥfXIXs&yh-oYZB#m3'3@*eݴP<[Y]͙@@(>[য়~"YSy0WZ T+e5A`}3}ENZjH(xI<SFiӦƬ@]w$sP_3--[9[M`6I"[=|ԫW/ߴi?/)U?Sm9YJP{wࡏ^ &iw"ٺ"P_ҁV|naf-B*s= fYfpBc10xفI'LDsL%q"K/dNϞ=m]=A=_~'2]Ax![NAyJ *甦5;Pct 6`"pGbG B74谛'tM7p:ײi r"K|צd* ~6̘1H"9T%D/ : 8 5 >`. SR@2"p[>"PB7036 9w6t3dc(q]i=tEAwN1+p /FBBc\gߢ-P֭3Yc!QG]r7@觚{an3Ց飏>2 "OM@@!p* N"KwW-B5(:t | GD?6Ճ9 2 X8'СCycʕo`Ӭ.g5$M̥?cz68i޼yr221__}1K3|2s4 3^Mm,ʤ rTvn ZVgB4f䖮|(:Yf;c*xf/~a j//lώ1"FI&YN̡ݦM-qclBS|jV ؜cC$y `Ft#wm BCfbn>px v;: 60sh3V6["5|TRjA z2yjرcrxH$4iig˧="dj@xsPەa:>ҭS{IEŋ[ѪUZ򃚁~u9AԨ> N;4j֬Ys!$KLc3gj+뭷Njc"疂ɀ;a9l|/ZڸqcK^3a_~sn6SPs׷o_M8t޲O99V]#s6₎rZXr%a#VCR(T?t6 #իW[g5N8t"d!)֭̎[-~ڵkG{nfKu3j_re#^ kї_~i Zyyy<&*e$P W~}Vh-Nfz_VCP(V4ծx3߆N.@`wM'Y]9ϐ!4g}x؝7oޜ=#1qqz`!`g_c*ɖT8$1lTMxI#F2}մ\Lȶmfjf8ӑ-rϟO6mұ;yʊk[u]/Yx 0XV@? LfݻiԩqDl`nݺ Ϊҙgx1W Um LPWò$b eMΪA2ÝV1ԩcyhժU" DQʕ+[袋,~&ƏOgG‰hJuaC[peطt,B:)I]i~K?k1 % c}6Afnde~QI&7<j(@b&~pFYW{!vBG0g'B i-vdp`x۪aoZ!gHg8Xrni7!aRnv@c]vwNy\%ECNjy]sO):2'@IDAT.DX9dI[fFOnj& `%].}ҙO8,,,tAg՟:<9 LN%@2AMCiE~#X>%Vʢ2m,q a;nGξ a.:N_onjժik߈0*tZ2|At P]}L$;Ul5_JM )RJv2AF5jXo1I`^bbRPCI"8]Ą†Tp-#7OkX6 /s}B8VifoՌʼnΪlu.z>TW!|gNh@NhՑ? eK.55_uf) ={X^lM<ٔ' DŽhũا =2z-]wh=v}"4%y"tXcUԹPm3f 8Ao۶)O& j¯N8);w/99 T]b'LLO P Z,:‹І >3uAtכtJ`C x7Yr`?kg}6xY%;NrlO<5K46fvsLNNBwބz#Yy\E`r*ԮEَFm`oC D S(uxbE{V9]QI,Nt%γ)q<`^2eYC%tcwl`Ŋ4e `sbZfM;'3J_R6&Q5J۩}QV a 7p|[Oa !a0 Uetww饗RVY}~20n:7oNNh<v1 Cv!xjI 5Ǒw-ؙٙ;xעԤ"%;;&AO7-Zdb':g:cB"n.]N}Xa ~c@&f{/ gF'X hDG(}ySe'əJ%3MѤ*L7l i;_*HeBc0uTZ`A S?H/eK3;nc1WmTOXf5 L0 0޼p(zM 08[#>rss-5k~R0Y=Q/_\LqmO1tPM@4 5$b@8'BeV2N;He `Euo\l;%:DwҾPL7l1ѢE B4#$G{ aNu_>*Fmʓq-[pj3Pbf5f̘ainY iaQbAWb8L+jW#ǧ`i;8Nl',@ M?lbv8LcCyDoIj gGI&"^N>@v8]UcF?`̒kw]FYz;!ji K9s&mڴx89W h3L]ML 3m?<X'?A;L% c)N/~y)ZpUWœZ&:M.~6vǕw-;Ze~ő1x;w}xZS}!楁H6gvaL$D4!Q' t=]pwpH2eCW,L cIN~v).n~C`vF=j՛4ԚqR/٠otԳh@K6g_v>aPz*(TM$ -9#AyXΗ|i>ϩcpN0=_wk[C"VvJ8oG\ f@zN N60>"*C-g[Lv1lb1I?gh*;{Xƪ~&~'8qmI1chŦL۷7霰ڙGy|}ͪfSn@6FLQrIp#zAڱ_O?8hѢE( 7pN NKӧO751Q)OD:u,+gzKAH 06mMK!M2zNI.i&pS wЁFq r-l)<ѵ^6bAZWqTN u,z`P#UU,,٘y'|ҽKបgy7 6믿ޔsN);:3O:^}Ukؘa撪/0i0իW/(,UoiXqr-,@FsF]T}a6f0NGaߎwg皽;z0Fm 9?&B^^$#L& .ZJݺuCASOa/Y<[={W\y[$V#pB?~Oo<-̟?k2V` 'd.A!r\)lhEQś3ϐщraۤ Is`Zb 699֞@LO)K+W?lNjnY4i-YP's!|MDȊ}i;  ).ʆhB7#ղ+F,ؼvjիWzH#ՠ"gO-!?Gh:^j&%K>kD #득ُ\_3LHv=.:n'llUGUL[ $#% tAj׮m1)Sԩ9 wɫq N==X:5la-Fo#ص^w$;Jwie8S%6F;o@ g)xƍiС7aHG}`_کU˔{*H0sP_5HO<``-[ҙgi ^svF= PG5]uT~"BeC[49r#LvB&`'o=qWtB gGx ` ?GԶ8tT "1bEp6cC;jI'u]G*aYÀ{{ʓ@¥;L1FJ 9MK^Rw!SH}G:a}ĄD%?VU+W sٰ yf:ލ̪NrCG68 +VmI[TY5\@1 ]Fm)ˣnU=3~tOPΌ3L`E$!$?H00<_pƬP]QD7:9T*8H?`Uu:?9A} anuS5f9A+Y陜y;̈,qWX*m֬mdE R%]I +eD[19:X3q$ Ep> +ީOl}]|IZ1+!]jʷv[KC ҬYL!Dޟ'2KBp h2SÆ C]\)0Xls@3:JI7wQާޓ0 +أ[V;tD{6T?C oW]ur-0g "!yiŦJa_ڔe8-"ҳ>Vz6Xܵ%`= ;~-=Ybذa0e@xfjZ*L]M/2fyTc- f\85/7Dvu@u I5kÍPu dNbqEuvvvq9cۼy3I-o GE-2UXJF0vsϵ@/L(%  |gԪU+z',d}v O"8AI2='DpHO*DpO(s#ɅuZ?ԩSPNb (L T?At饗³O+p8vNJz|&w}s=;6na'Tv BuY*~!@H,fLq$$ ,tS nUTL$Phѕ#3K$enD6yZ 33ƎkY,VP!i _α8+;̙)X8B&a'N {@Wa%Um͛ǴxN.uo"!Tl!V|>Gx?;a!C駟.")f}T,pAO+ct0?P LGo9v:6W{5j6-.>h@TBqE4<_~IթCR6~AtҺu;Aǥmڴ9)WvI}i4hKB@#'̶s8ҿoNKmK䤵؅۝2!G`]tjRŕQP:˕+G>MpH26h;v숅>MZr|BSH֭k̖kAX͛G6lK< gewy%+ޑњqn)2ehN~ϝ;7dnk*ի_#X7*sO=E; dqҩ_X0~_>qؑelJC.a- ;ZK (ٌ#peiJ@?;oMLр>G'o 9jg_ٝC$g qaqlD퉐B#p^|j!]8e#S4t$B@Oo39dqI~BO?ɾ4yd۪6u 'YX^K~1IvNL-[,ΰ<`wa;ؔ a}`/>, 68F"m=zoI-=a˟af3~ "oKAEs ' )>xb |B e' @v0n3o1 aߛsho08jkAXpDž _O_=5Кçc'l!^Ջ q2ox*X“:#!z;}׊|g=D=x9a^43/qs0:ڤo"lFmϟ#1浼Ȟ׼8gx3ul̳Yȴ-&LԱcGzeey\OJcX4ϯ#/00DӆF~szor}kxbJ›1=5]|VqM,lfNz*Mw^ }bbDZJ?ޘ_O`ƨo!N$1ޱ?ol"'NySoR%ƙ 9 Ҭ]7cex~o_ϏLcMa09@<ϧx ރӬUgO/9} 7b[' _z)W9} `c4O5k>K#|0p^}f =Vy<_%Z$x b3^}$c@z\cB#U(\hBoVZm'!F`b3a^ 哱Ӧ~hKrzKaQǂފq6U%~CavU’e?78AJp"}1a^l9tC\9buy?a[ #~56LXag&Hls 86,v2y5k|,埄9s.^R: holRpo\N޿ysËLF6;~{t1ZߊCH pdgx+40ox.YoЪ*Keyg,~Isyq,ŋt|3- 7mM0-c"{ݛ1Y89v?ߕlln8aӄ7;팍qI5+ތwkx`a;qgaɞ.8X' ̟M%r?{/cBT X;YʊTb bHI+'aNx~x0{W U  ̿O{+Y:$E- $ZfjTZװ K7w IO{"P ޟXmSF,x'#FGu# B|16d!6rE&q}˛Ƭx]w]TOψUN )[I II +8ě2E/SMJA'ؠ8ƪ^i^ܖlIA7<6KR̯y3œ 'wbKZ3FGoؘaR-5Yo63ww\zL ⼶k݆_:v9|?gxmq:0&CV;LTWX7 c~wu3)¼C*ܧ{Xo_*`Y4[oYzh5mx47vmRE.`Єe'==ŲFc]: m{HЯy=u?`C"=p'vРyq,6(EC|Rx/Lryw{1fc>)9,ҙ9*fd^fa!wATAI56z|v;equmܘ6'LM;Y.Xc`?9M۱ubNlP}1)~wFїh']}5>\dž:=rĉbĢ c'%0= ,KdfIӭ$S[6f0K}lr,Veۼj)9= -k#)l .'|<$k_ alL5/6k6ͫ6q"ɋPc^pNgk6yhk~`~~HxGwn:|p3 ^糦,O*FP3~ě2P]?&t*`/_W1#3lP(Ky`lxO(9`v8˳ f|#T3R-^k RҀ a}_@ !О7]곶shJFE0bfN /1Y=-zSCz V[Nﯼ2/w?썸=D"U*fA>%v[{lLaw8 NԒ5},?m.VM<žj֝/ZS UgkH81袋l7x7žEzMg[ e*૛,~, Uwp!n;4I겦/L}1f{-b! |\׋El+JHpQ|zfcMuY[uyӕx3@8ŸPZ8! HKs72x zU˩W^4 !A X*L_~-Ig@3'+B v_iׄҏ~(#J0щck|7bIRsrԥ+4={>#d)ЁqSXSQnZ8\.ytwleoUj; a,]>u#`هD_q4__%i^%4yZx _38Ô41qtF9LqC8o~_ɓ^< . U<sԱ_<-@L<GeyQ#~[?3Jܤ…^Hs1U3߲7ބB` 9o WJh󊤤 BmDQh;{J@$QH lM=+-I=ݩC4Yަ( 3sD6sTN EDL NgZT5qo|G0i݆PF', #}6Gv_5}wcE˻If-4ihMz{eAUVqCjv'y晑wY\\wD?QLwSzJ Gmv ldDIPȣ"¦/PMBC0;TTOʰǒc.Wc,}&sȑtxXww9B*b ګI+WƄKPZj1oJ$fiӦVlT @ ͛ga"z)jѢ)_A spHTXXHsUSbU4R/eYK V\6JZ));P2;ǫӫA5=c ,j~G]vʤ`nAT5kЖ-[@ Bs5 jԨS d'B0S%| &l  TU`085s8 N?Q*Uٮ'k̂~lxw6~Bus4ֶ3(Txuw1m5\cZP 'ϰU2r jEuz8r NfٸO]:HJ԰ґ}Z͡Xl#F0fɵ &tM <ӨsΦd? @2Nus &.l`&,|a|E\x80 e6mj w>3B^v6#D3gaNr7K8ü+p-!Gt5\֭E_Q xwh<)3))C NӊTDn?QW)"5Rbrxno.~LD<|1<6m"6V顇=zْ\!YJRUuň%:mܸx \B?[r!8D`twJC߱cGI4a)B"PB:ǝC|#%q1UZnNQ bLO?hҤI1?6" ,]>) ²uemRO`Rӧǜ#d-[?SժU3 a)!0Bx?U_n]977$W娥(Rc?jNrC@7~hر4eKM6| p7f[snꖲ"Uz]|_xᅖvQ޽GjPŸQh!<| /PLP?+-lxnl59\k駟&SWH:&@HUмysX,ٯ_b_|ARڵkK.$ݔ) `f20  4]m쪉BhqZg.7aY)v XtTʢy"vWʉ@Ga !~jaUVl!3==z4͙e63LN͘%H&6v4;PCXwy'(p 6 D(̑#GLL]_'g))C Ki~R(rcɭN'Y+8-*"EXtq<aÆ{Qƍr*WNfZ%o,%ݻw](j(2QFѺuXO B@<Öp8 ~-\fN e8tDWD9S 鍀j׮Fh/?dJhm`"KDlĖ/_nI&oPpF'Ȅ"83 TXv-={1W\qux^[jC-]9S7i9`3CXZԫa*ųRH8*}G_M6œO8C?"N_}ěnIT-H[ BiV%Az{&0^9ǑbRn$:VX-v$Rj:-."ߟF][oB^:x,\#26bW6=YqgDI /hqwWS֭Q z'_xBR/I^޺N[}{:RGh$_NP7}! f頫hOF۷o7uΊ66eJBpoB? B/!|Nc^{ꩧh"-|p\|y {LnyS Qwv0Ϧpeu:4ueZ)1"xFShas po>׿$\ FziƍƬ_#d)>?~߈^u*f%'c3Oի69 ԓpNDHP۝aBH XX9өE|vڳlYWꈾtlҤIE|˖-ca/ H g=HP4xߵˬE %=2JA&.\H;v43 %?pr=PzιG?<}g+UD&,WRڥrj~`\~Hȃ83Bڇ]x@@~c~-I߭C lTQ?=j(駟8hSiӦT89Vq Cz޽ijZLm۶Q Z*Y j|c(b耻2E-RYشfwEq*Yp>ڵk6B}3H*ozݖ-[fe7LFBH6 '4qJι^s~=h8~1qB:!pkj*T6.*dP ;Fg"H(`sX$xݮ'0fuHl:챚5nܘ *$=H8FD:YLB&M2=Ν;L鿗h.8me*$S4m4M׮]ڵkg2>k] o֠|qAy\ " `DhQI3"W^ybHB#Y5c{eM8I7OҞw~ Q&'pRժUżDE4љ̛7Iߘ"@IDAT d2K:t`ZZ{e%Aj*|@M 'eNj`].hx? vojqСCI׬Y3c=#ݻwg'i[zA[R `b"2i& #U?hG^~ _tB(JŴ6]PE`Ε 3@N6mذ?_"S,I#ШQ#kt^K~kq )h0ƪGv˦M,ͥ3…qL9aԫWϒU{dz)Um@qǥ+D`˕YDɁ/bKpIʕ+y睗|)>b ?r"z oƍe]fʓ _}}G^",p:cHǪԱp;9НҘ9(m @Qx; 9iNaAZp I3+V(&12 8g۲ehm֔' A `yfӣmڴ޽{Lh_NKt~{]sD`?9Q{%73@oG*:=CONzo;)EaÆMlҥMy"%5jXšZ I h0ԢE B?2N/(YH+Ǹy@ʺC@x2V4*}:D C Gl/6fggS߾}AEF xh7V^ތ%f Sɒ #`|Μ9g6mjc)d' \Ԯ2 Og(/E]"j4\֭md.j-f3@Mc}1aЪU4r!MTUTɨ˰b~ڵ7tS>{ia-4jժqK' ,~uJ\V?B!ҝ?$AWfZ=qMr'<5e}?cTXtſ~:Jgy^ׯ_#LLT8}AZ3!X mPBANXsH="H<\*唢v\yLܲ bp"FQ=>[ Dotl8hREBK50!%,HFݻ_rYSYlF6]NrQ^&w mo\6_2sCHPoذ1KxWLs !/8p,ig˧ 0U*_ޕʷxiQ/MtU*TP) O \ɗ@Mk.r TRB\xXB;Wm۶:3lT@xIv +[,ծ];ժyAvA< 80!A U WKN1­D`}Yl0}`EA@6TCK.O %x8VUm24?h` %#amZH D02e\4ޟgƛD׬"5wu?;09P13"(bpY e_D˭ߟ%6P$#9&NH7o61߾}))S@شVHH+WZ4Lʕ+G~hʳ<P4i-[RMy^%^ɫѯPM !)<"(;6_hbYR.6c3A}MH/>Cڽ{CRNN)O@2=4pZ۶mdg vpVbEKY"6<_"`mJ#pIm-Wk$h\C? ';QbHG >\ =UYp-;|饗L&&Pt`]x 8p^kё#GL&M\ `bN:Ddh~v)xA=wL:Gi3#$Cyʖ;͵ ;Z93RaX S.@HCiݺu&5kfm)$Tg8 8JfI"ؿ? e*7  TzTZ55;4qp"lmGk}1NQLp1W?ڥXl'M?"Zv! RE46g Hjy^ 8+7o^qb'ׂSz,L$QMꊝwVZf`E<߱N.լPr+ȏZGg&&_PP`ʓ W={¹n]@DRA[VVYlήjS$dkG#k% ́$Nt ΰ?#)wE6qo nřc!8!^/qX @-`MS ,nrssŨp%pbݨQ#-#L T WSw:& > |K <ؔJ)b ~~?wuZzSN]ka$/ğS>gwZ\E =_~h{nʔ)B (6Fƍ ZLFYf,BNrw+[:WJ,eN5D j K #73@7hEE(J*/B@kךiժhD^00)_UdB4TАkР'="BDv2tm*T?<[C+J +gzL;yZ+D 8õc 3M4T$#pZ"xA[_A`ʕ&|׮]M[tz4LTp+hC+`ZT7/ SO7HY@jv@oq~Z2?89 J^7uK:ZJPED"0qD!&vꩇdyF'Ϸa!"󬿿^-u@=_Ѷۊ_^)`:Jn jW:CHC@PIogqTK/iA X@ݻTN'֭kʓ ,|B tm%[<'Gvy{n> Z/9uB= NH?bq8G_D[T;+A@A@@ҜBNTPP@M65fɵ ˗/͛7[v!A U `†HeʔAn^~yɛCz&OkE*0T)A&Bv!A UBHI#A OM6muf8iE:Y[&^CenA@ )Y$ZL"~[27vii !A NnkРATbEc\ I!'2=&bbbDI"l2:|ixOUP83DR%=k=B8QHc'֝Cʑ @gUpQN3#-;oT-N/H0k|oWp p{AiD`PtzkeO%ӔAY=}.iD@"dB'h*z'h"lռHTT)H=G`ƌ& v!}v>|Iͥk6e0RAu6Yڥ:t[_FĻJEGV6(/U8 UK ZѣGTԱcGjҤ1Kv]Zj%U<$-&P^=c1B`˖-'45ke]O&?<͝Qfpw1(ϸ:!QCD!A -ZDVZ9BG%?g ?h "͙BFZ~ђ%K,T3O(xtj5j\7H! ? rjK$8! ZGs&)|l̙caÆҠdd_vT_~}KdG|Ĩ @n ˩FyjkR S@+e*u-?;;NϠ $om11:t(5n8ժyMv~?O85KDdD$zz:|A hTW#]= *e<\?LHqgNJuHNuQ]#vV?cI&Y*Z%O2$vڴiRdq 0т2V!OL T;{DX~eMTja *'~v0"o\PP G8j-q4qDSUTf͚$!$_Mf2=uI'$!$~h0ԶI&T'h@~~>.kZvŴr$ףz}A-O[ҍC!?俟6ѳ[ɛӪ\H0D`ݺu.ׯ%_2kZl*)/Xs Ad*~ho$ر#UP!)<'p?5 pwg] S۸#nAtpX񖓟 ׆ۻwzդ+iq)A53jwRU?llg;K"]`AEEb{;1D%]#* DłP v)[s7M3޾{)l;>eʔrs3 .h"n(ښJ#6￟p F؉YɄ |V}3~ZԑUFyCgZ [Fy& crT+NΥ9p:50~ _xL=dȐKxrB)O6ˠDMo߾A$҈IKΟz["x  g)Zd9tEp&VByص-9Z^ /U)$0eQ-R /as14rȰnF B5FEw7L.uo~VyyyXĴ$Ԭ`˖->]sX;YoW{3ԕ#@r*JΡPVaŲP}wM?SXCEEE$^C@ۈMSmJYT|G=,4p PϚ<*:t&AV$&H!Э n8[KFZ_$)g0{Aۺ? jDE{Hd>k۶b>;j Wڰa-]4ۇ]P% D@U 58nܸUތF8L@8>u . r'm455&38;Wxd?:`4&}MZB qp/JB LԳ9zKwq$!ڐ@ V\2tV;3HL@ V_g}6i`ztֺX:(HO ƪx8 4e!̃/Ztw^\* 2O}tV 6ہ F$[X?ĉÚ@h0$8p؜i*aĔNqS^$ >i ٳgS]"P5/'0WlyߋGS\cb0`I󆍞t&@[hNrJ /i[|Du a颋.m0Fr2{y.>f̘st]B'KqT5UUUѶmf޲ 9|)P*3P%k3!@yTuy&;Փd{+V}uîXhm.r_jy}āim0/$8SMO< 06@aaaVh)"ك8l1%+E9U1B/E2viP<RNUR.q,ϮG!H99qV# g'ha٤ݻSzzPݺukq.ٮrHvp"1oiٲeW/UƖB:Cj' pJ!0fst{Q<*Ʉ 3/,ueF˄4iRPUyOMM  !@|4y:*++  B7ZQٸq#-4u)>{1c6'!8$`:IGN_FW Q,|fA=(I ਆٛ teY&{v~k m=N@~_N6m%*H J@G*_/!*mgDӓO>f}{qTʴo>hq"SLb/b&Æ23ڋhzͥI-w @ @4 ,YV*++îシ HQI:4.# 6ԁ^[~mTz+K U}r/HJb:zs~0 1]nJu!1Ku 3Ʋj(oa/ZpV~$hX\UTmA0Z9mUy!DŻ<ރO6-j-%KJJM A@3IT֭ w#Գgϰ_G@ u% пL7okoV0鿮Sc= Sj$'QiGT@Yg~[3{PTyF +;vD/P9K<{%jk_͝;^y_/; $ NF#G 4Ho,4Ej7 >hD;IԄk|ILI89e_ؑAFksF^ )qޠ2O /fS@PWvx4HEEEZ-Qwk$lBNK=1 D ޣ?xXMb6mCଳ΢#<2&PtByyyA3 ѝ5krʠ t~IyGPU>u \2I+UG !x8Mjg׿Ԅl t I9mhM9۔)SeeeQ*aqU"G.w1{`Xe\p`})(|tGt5$fHkӆ}w xTVlgի[ԩSʅ} + & >iW_wp֭[TcczϥݢڰīC/)#-v"䉱R!_{AggH ď}"]v$INТI@+T[@ Y_$H"ϏT}:-]40t MI·7jZN'OW=ZDU%P6S_~487vth. Js*CN{(KT@YY(>'[q45kHT͖w?AqEU3l"H9Z {XÿD^{X$!֭bz|My5j 4(:.xGA@LZE ^y$B֒Ob`0 W/X<<4o#c1D @BQS*HB| @k9ӓl"Fy kmm>[T^^NƉ t:+g-"Zp!}AexߚٳEscS$aUX!&MHsʵh !*.<2v t`.0jY8LII>8l#9. -?Dt$6H '0pX"k$օ^X A ,L B1`A=PH H8L$" nsI6M|)%rX4 % :5(JS{x8TrJO20jYq3SLplaÌ0Є $N -' ^CLbxfɈ;3h4S* g'i&UHI4"/s5v&lI8p9D?~Z :lFm:imn\P;G{-(YIW/LJo5-.ܠ!yڵ}Ar"2rH@fffćޖ"MTWW8 L[#@(#F+l(@^zTO>kB@LLBLf$)eZ@P8m-!FwH>r_04-GtHYJ0'tޝƌz )hȃ5lBG-<8 I$BPΚ5|IU߫e~t @ 駟NbD-p7E&%Æ ^ 9]0lRpաttWLe S+e @`f\ %JnU;ul8} b'{'&9Y䙸9G}_,{1@@TEKv~Qk׮F,?~|'v8aK޽{ H@LBLL.P-â <8D%my/:>Cㆶ!dN g,#} &mذb Z" :(lЖ.]z8{|7%>ӟDL{%e"LMF+ &}O^K Q#н0&[B۸yu#Gv!˱>*:>tR9T@YSMāVBBp9xVGNmE0/h5"  24O||I鿄H 0~ӧOUڇ~MNgo喖w # fr? ̋3I~_H: M>뮠uGvO ))򗿐ů |}%eeeA?`A $b;D1 0 (@y6^i{νH  ') |Ξ=@,mx1wI" D eC@EC xr&&'͆94ZgbA115l#G \S\w}6r EI-'eƎK7ԲooVV{ ZNмw@: ~QLᓔo'Bzj3f+t5אLbs;bĈKxO/0M2g[{y9g!C9[ ,o=1"@hEzMw@h9|@b&Q$JJJ/?묳  $beu7O;Zfwʿ"Omۯ0ɞ3 8@<x0s)#-޷D}E<3$h<ӁZ% LN;VM@@GIP83ݴiH'㿣HV蘛L_ *d{B@@%E!B6'a?a"NhShS @uwSL9l?lB-4mh]|?٠`Μ9>'b/vibРAbx466d٘ER׎qT *Q%CkL'%Kx?qr {)3m{[P'%oP&0az5M}C/UL$xi{F٠]uUuJwr{6g Pl~ $ >MB>pBK]eGU-Bfْ tEP{hӰg+ Z' 3.FiIgWZSow1^8;q!I $ =8#%ŷQ鼁 _{T*A@;" G?9+ݑ'UIqJ7u#0D$j=z lz3} B=<'U7MZL@{r鏃 T <6Y`  # %<0葓ʨK^j糸xJ(M8pY =OrVڝ=628c *rP ?_0͗H"Ŵ*jq_[:/ӪP&<H   ' faJd|+ofՉ p?Y)8뀒4:r8R° jNx {/HRN=Rf@Cv#+<>x~ FwTJ(2pnufUaҬ$oDd)"k.!p O~^4 Av0*NcSP!   鷢V+&GqqZA~O>$tG.5(+חTY*QҵG{}Y3Kj7mE3&Q;w6JuP.q H   n ֬)N(xgy5Fn  Vo69*锛L]ReAl-}Sy N߯8| 1 fk+cwQUe]H`W5-.  $0cVx`iþ -PY4ƙ_tPT^ <#!(*5?U6+4 7,]HGN.DF&o|tPA @Ld 0"}Q"^$~SyhPYj5Dՠ   4*ZB(L@X@C'h* tC鼆$W9*Rz{L'JW 'p̵o?@@@@q':~E2<>6k:=DA( Ȼ=R\֮F ߻YJMd*n;TGvtvo͎[TCyqt:曹j(VnC#UmVKy    23`n%?>N;yiuT.YH;No5<9+LoC/݁)M0&X~|:x 1t#}4sV;UcWS#[˼r78K?yVEOSmdƺ{mJwyjD'6zagJMOV ?ilÞs)[@/~aŀm98۩9OY.[ۿ6SB `),[3~H`a] P& XTHXH3)\( &!08GϝY'yAGco‚ GA'u#gv#6Y~CHƲ릮H #B]5: '$'- ]ZNojLtdC(\v~`"  M>#[kgjZ66z xam>GQCseZzՐBRC g`50'п$<#9Kkg۹8lC=/Km [\Y֣``5v~X 7# -m_KtNoqVr[Y x^Zmu2f^x;ztu'Y$8W>MZgVR=qk׊H/7<9WqVNG&29TP% &}X(P@lJY57qmnڎYAm+^ώim@ |BZz|xJ|h:JV~<4"8zi]7u3'Q?f($[Y+wcȖc&_;j-bp=ZС(!6PU@@"0ާ5eQUCT)q2{6㴅p>|?+)x,D@ #nr봴~ap v}$xT  `>-.B2@z܎tn\B8_k#^'f޽^g` V`y<p2@k᧮h!pZlzat%ek:  oVN SԐo%[Qʡ{@=kLɍh)1^bnJ" mcUJ@WlTwARrڄ  `-C߼u7zJ9Udkς7j&/l6"{ ՔN=˹Hٳ^v~@[6xꑭL@U^  q{#3%iO1f<4掞1s8kŶ]ؙrRa8ťm-  hąoAlͿ }VL9'Їc;JMP lem ӫ IBS!h#SmbupT|vX7ab] ( @ JPf6,5%T`ygzZLyQ@@Z' Tc٬j&ҹ?6ӿ<OЃڀ m N>.强"^~7!>l@9`; H  -{eNzGW .tu/q_B{ ZC`@TkHtt1%VkߨB@ku'`_D"0#1ZiV;zj%ƴ+ ]2ab.TyJs:Vkר|~vO`'ײE4ZƑ$04qF{vv@X)>h$U7F5vٞp$ΝN%[7ӆzJG u@ '_Q+&0G67f㖃yws@ @d.n Vd9nVD#o4H `-IcY xY"t7vd?B% 0$/w'dٟ[%ӗFӵ6z 9]ŦV4 `C~uOg~z X6F瓰h!-@?B@`f_n Ghm}S.Ô Bپ9IFA@ /pDQc$k_nc?$*-1m<6[Yr˷ss^vK k^-MB"6 L A`^4  `l?Gi JD}Kϵrdcxq ,Zɵ^op\pNnFx ?3{OPsOx\d%[!QE nA4ftV v&a =B`2pt !p=䲹8:Zf)oTѪ:x#!A1\*d?~*H8g"[ʿf~o{ {A`p˴lS4gM&P #ޚc5Nh h$ b=:vU-75nڎ.fSLMOa8lt.:@Գ r[|;KH-l۵[wu74 XG@6?$tsB`o~۰3&wb,JI+p+??@`%}4!jm : K{o_[?^M;Q ؑFcO㧭! gd-qw|bpI7 9\ @D[oo|&[/8drڰ]L@ `T Vke{K9M\JKupKXB}@`{z* @3` _w# 4j M!`F' YkЩe_B:"#5q鿉[[Kk[;VPoOZcW|]taj XH$j=i1%TdF@PMҹsǘ6sy*1KuXGQa2pq9&wrq+ikƋA@O@c(=@@H'ukKd[y!6UB6!Ma@`"h JƖa6p9қP2 B>AO @"ڼ^"jFR<;ˣkkvxn:҉s @;R7͖,c n/*>ˋDA}v/B0 `(! $tCAeC ~yz *zPqxq ,ZSo! f2g@Ww!T1V" t9I2jh@`3oejɯH#Ʒ{Ez *zPy| @IbgL]1PF7k6:刃lRU=' y ؀`7=$&EOb83TD\ A .C`団nonbjzU' 2w|k_b 8Lh& jZ6#ٓ@|\/S{.GHB; 6~F98 ɯkhܿWSND %ZOZ֯Fz0~Ϊ^@ l{A <$i'1gIM@gQV a RXN0^-^!/+j; ȩs. Q!7 *6H# -H3e)%ar9䇭gTӳ7$@3ET %(& `x@W)jw&  /%ĚD秱6$3-QC =B`Tf7}4gқ7eZ[˘(HbphxH`nuϮZ8BSd=/r~=khm8\XF @, P no'95 t~kS# &"̀in% *)_ b9~/S2FPwl} قD9yVr;99洵i78w]*jڅ8A lRS I[: O;/5`iV,긑9Oĩ8XGY@B˄٠.M|vժt;+ 6 `k}S}jGuȀfW ;vv_'wS_h؏%o[z^[q8݊C`%QH$)W~ӵKb" 5`&t#Mn(-oHxˑ4W6i,s e t$3@`&m⛩8hĤ7>m)+i:hjhn*AtpGAc%Mƒ c߉|ɀ|ih;ꘓn)U\F-hӁ HfL7l2.0?޶C}wI$&ke@/UC얕O`uRhhLJNE_lib< 0][f+DU({ `FrA$*(*۽D_Ne/+jwwY ;zH!BHӋl"v BLvx VA1͵[(9r95԰1D&8c& HыOg,.-mIݲTV7m&9MfǺi?XѣX @+Aㆨ3P57[0_8~dmKޮ$}N D s&K ЖS$Ae4::&ߩ?:BES Vle!ق0f|,uIįрB' ^l:з8 I@Nl%<^R  r'ci|3@+C?S'Ȃ.<8䜨'~UCԫI T$*}@rZ֚}9ٗ R9c-eiV]2 .OtT$v$qPx6@``8,n/|5R1G 6h٦&gZzin &H `4-3(cFSܾ_۰ϛ{*?#3Ֆ 3{eӘy42j38kH  tCOC 7 5i˶vY6D; Ih IA% I&߿߷okhC= CI٬#8?'@@@oM4  @"<κhi$xuPquoSr@f$~П-Mǯ/%l%&߇1=s蔃lɞuL8͹Fv  ~ 8s^c\XH@O}6{֫m `>?׍xyl?p#/awHDd>?ɛ\V15r[9:ES  @eW! #:fA@O轜㜢נsggWL{@/h@@Ә>|CY*"u_ w@Z<9܉.I4_\GO6BPFΥ8񛉜 <~*x0`g,9W`KZˡl--KS   "ЯdΙ:^s|Fs  d^!c톞X ,*Out/پKϦ؊R\V?scqmHgq~ޗ﷼  0yB2|?H7| ȥԯ޳[SO~n-؋@\6ԯ$FuϦx?/?^D66[ (N塧5{ ^v3 8le?})Jx]OJ5g ?ۅ@!XA+'-WQ q8xκ4CsX#`5ւZڱ!1$'^QtV 8L׬? @ XA>!hR"bp3 9UJݾH/YK VkPy7$_TW7gog[+ F(ۅ@#8TH0I`?wp>AkEVǷ[ `H   6itA<߉l{?pp>@4P@A56!6K4o|H5~a7}ʂg]4iF  #xVIc5d㿓9=?m*Æ@ʂI=i:@D1 6[P.p; |a @@ HSyU6 e/Ս}$  `6&J_BfFЁXG N @@b%a:&Ӆo֬gh^!ʀA#&1CUM!DL2%iҶ{h/9i;FUiq>O{[ڄJy9** CB eA$4uh  MaY&Dvi+*x@>Aɥ%Tcc??T6 + 0x+A |m832 |=:*Δ@@@ ;{63Ra!w^A&[VkkT5З+_ТFڽ!bvA@ ٘OQ*|\N=xoc?AW簯  }@C`xt?eWp>a;k|j;5 y#g&ѝ2邾Md#羛$.0ђB9 @7ۃ Fn3bxC#C-F hH  n$ w~<:su`<70%?wWMC,| `0 <PLf a4ea-=={A@+F_  `6m iXe/_d[Q705rr O^A&[Vk[4!Qdn'My Mf,@pgi2%~;&R.ı߷?gm3 *L( ` KBu 1>pgP&߾^O.h7Xv  QHM# wTL:k M;y/ZӖmIH[Sv,4/!*@TOiݳ}mZb㲕c?y J( `CUmIr y`9_I M/`٦&wFـq9/J.yt*oO'0qt2Th, ГmAD4aK圏ٔZzZbv-S7}k~2rP[ߦCNS59K2?/}mfzD@ I 0 4?ƿg !x\ΦMD^RGsi6D0<:p$SEdݗS+&3s^!D@ @:?)@W"P~PZXH `: nv0 >G tMά?4wɢaVpK h0F6 P)O”`e;>3lE,mk :>Nږ~/l)穜sh6A,| `0 <PM 9IA 9 ;"0sVrvnv$a@$N |OQ* Ƞ|5~H"Q53@`e> P%FyBT?/Wn簂[s  +S /I޼SFW!ɦ1i]}p+4@LI; x~?y+eǁX( 7֋Cj$78&{ATcx4֖Kη3=4־5B@(EVchj'_p:Ot$9[ E3@߭iKPU  PHCX('X96OG@$* `̠ PԳ'1^- $k|]]/@@Tb/j}Sga* (M#9(j+99s K @` \-MCx?D }8[&a]3+Pu' @%>{ۥAߓ_p> xLMίpM6~E@Pp @9N J@(~ks>sy-I=`FZyO 0uq-}kx) & >Ž:~ ׳] e%[=M<_?X= 'V cwf q,~Ƀ9'r,~HvVwe끎Aڴ(4+I=$/_/|QCQ˅@`f 8?de$|6EٲmZf+X]f.J!eAb) q4)?՗O[ev2'YzM6~,^! @b'@ um]q1"\/@" Ź2Ζ5[v/[v/u;'6}X67@ `` ` Vvr?em(~96?E UD\ͻK"0`g$Pc]{jP` ı+ї,|*! _NS*~e&P.(m<87fe 0+Zh32Fg #_3@" @`}_?Y oE># D`G6ۧ86OoswpQ*oө#;K@v:jjb33fPF @A`PA~x€Me`c.Z' ~P@"4 ^Aqۏ~o˲ios8TS~z%Ha{FN8S~~aV,@ D@~% >s퀾;p nǁ n`a@]>?k5 K`7C>Jx 56IS~/Ul6;þHs w8md,.A| @# |A;qNl$&" ynZu-Z ,i Ĝ` F^h n 1 kA'?#9CnONy@myo#g}k9yk~Elx]xL@ &y硜;sl$BF#дK`ݶ>%0Dk@߱vp="l00'QJbo#߁C{b%/d͗ߋ**: )lsj /.'0t,€8w\9S=M*UwЪ>m,ϾEc_ D N?7rjÛ~/`.y))7wmw gr7G!ax bxq15Kj#%$ND` y8\Q' XM vO)Nl  ٯF?-Il峭a[}/r/|՗}S~.Z!% f,@Jy^p.8$vm@Ͷ]|sWF Qs jv;vmA&_eo%^7V/Ld>?3Y6m(_y՜p-?|$  `6&@L \X!r/w_Jԁ75,Wtր va k"Q56y9|/!gaϻ~~I~)~_KRC K5tg@`i đ`^Wm@K&= ⌰ j9T&jvu\<ˉ}]I\V׎l/jIpzr^ihǻhr_y=8#DCp{+} @`ߵ@Geh`@4 D3@4 } E{@M f9PF(lg̓|M"4rN]h ك [9yL㍺8Փr/{I}an :IDAT"|ǜd_D߫lAK7PC?]c 邀@`P@@ @@@B J޷ )"(;% heMI5#H2KXp n|!W sHݻ O Dw芃Cɉ)U29Sx|5Q/L}'|B/y wO dd/0;QǗ>a6>!aYNpJ?C@o  ` ,NA@@BՏ8b6 1` j$! O+g Z63b~0omv\6"?װ#Cwd>^ZP1L`$bKM4"x~XEOcMbfS|1(ءKS~#r9k"\ЉL?i9WjswieC5f v#S$- Q;yS!H"y$8P 'm)~&FlؗK)x 佄= $qH\:/"HLwBx݊b7/'1VC!{S ck32$&_>ʦ~#*_s^̹sg3$w  +1!Y^%7'د<DM@%h/,|ɫ9/   Z@@b& p 'q fi@NC7;| |   z   +Hs.\9 IN%6βq_y)οpo{  f @@ V Ds9vAg$ l.p*8O}_h  @`E@@t! N sٯ5v"pX*NITwį~G{/   8a0F-Dk) $+9,ֲ-,)$y/DX  eCO"Dg7pqQ,I{O"c{9Ʌ@gzҞ\$]@@@CF   `?qlž @@@%@h"._`L@@@@@@@EA@@@@@@@@@@@@@ "c       ~       x`1E<Ș"@xXdL@@@@@@ o@@@@@@<@,2      7       S)oǎ `w"aY/ @@ @ 0Ov" @l @ H @ @ @`d' @ @ @ @@@<ى @ @@ @ 0Ov" @l @ H @ @ @`d' @ @ @ @@@<ى @ @@ @ 0Ov" @l @ H @ @ @`d' @ @ @ @@@<ى @ @@ @ 0Ov" @l @ H @ @ @`d' @ @ @ @@ pIENDB`ic11\PNG  IHDR szzsRGBDeXIfMM*i  bIDATX W LG;7g))`RJU*ڦih+jRl5mŠbMֶXM41iMƶ*" TTyz;w?-$I0 0wVM!D;9ar^x3ۂhMwhNC'ӄW_4OO>U}h92d_~Gzh;ji@)ڦ>s5ҪvܢwTQ۟-z/Q) zlNWs匩XfjI}p:?oXnX@D7Sr>o@=; p[.w'#lX~}r pG(g˹hx9> ql==Z3]gT755]T{Y1JKmvw !5#1XYC7s6<%Μ6D!//EEEX`Ftaϝ3THd"x1[vVC<V^YY[bѢqHtww \UU%0- <I~Кku1^iӦ!))I^s~KKB32õI/(? ljS2'22Rd޹s'JJJ4"3xgua@#ϊ dqq1l&h__``{,811Qt̜8ePoi栍['?psM2)pŋ6o y=Hw#Un6|8/|QʋmSN3VF ;ppG) hgve c_vh  >#ZHQ"rC`BkjDOzy)4T:sF b@k@E Gz>Wc>& ]={quyGRa *$ 1O|lJOgʋfo߾(A+l 6tc<.DU}|\WUOylxU b~Jfzg (#Ag\Zz 0{.|\ e Dq+**P^^StA~/v!οwo/jP= `w,Fn7ۅ9deeiԝ@*wV AYqrALLk)|J-&_d~.V .3]V `k@5P#,D~eM9 Z,q$:*gYadײ°:G[tDUqJY݄W^׋{QБUJew5bsR|tr; ;s뀃m|S݅+DSZTk|vڬ_Tż\*V>k@ Q`Q-yѮR ZҚ4o&huSJ2vwgEؖpu9+W| Լ4'xCDe(пB,0f`9)d ]EM /I+'0txH@eYIENDB`info>bplist00 X$versionY$archiverT$topX$objects_NSKeyedArchiver Troot U$null WNS.keysZNS.objectsV$classTname_assetcatalog-referenceTicon  !"Z$classnameX$classes\NSDictionary!#XNSObject$)27ILQS[ahp{$OSCAR-code-v1.3.1/Building/Icons/Oscar-100.png000066400000000000000000000230641417327530600203610ustar00rootroot00000000000000PNG  IHDRddpT pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F_IDATxy|3{f&wSEPAx!TZ$ *E$b ϲ1.?ԢTlN_߁ 1]t+$[GekE'h 'QUC"RccjiL<< 4 DOn`S|Ac{H[&Ql" Cl>)k'dsJ#JcKN@l\L%A }5GŎh8n[2$V"rqxFݥs 'Ӣ'l_,_'N"N cɵS2ie7j@X3(P)M⋦ ߂dL-s_chD~ܯsۂZmЮh"ÞjT.;!ieN*daa͍f~/A t)9 ɘ,"Nos᪀kG ?Y׫<_,獀4  ̙Ŝ ˲(ӟx/V]嘐La'g2klqсupT2xMJG⇯UKwXQAR^r|)#Z7O[cKc"**wcu ȷt0,OC{Rl"wM/i8__s_{{V. FqB&/\1,^=ur./pUDUS} N^9 R8Zh.YMeSgEflq* `x} ɑ=ւY[DAQ(ͶޏF22=C?Ĺ:zn04[KY+4} Q(c SXyN ӁF`~u- 8ɹ& ;>񻕍`dy3 9ai[y % a-|\㋞)U{I- l6b^Hm#ˠH2যTo< 0s9)3Ľ 7ˌĢmnV錞At B RXSrs9^ L(s&൅= t \  UTN3לLVmS+wKnweS zfCڦQ9khrtŸ,F8%IzP(.vZXDnEJ܇o@@aiĕgp>nw(\0Ey,"ۚݜG{B(-M̘.*-AHpTpZbF?@/ x: )ZCx22<ư,&Q`~4fO)fTYUP9Z ]7w@@^dX#FZ0 x2M]= wH`9܉E+8 k (ЊCY$;?@<1&c:m7AUL`:$wALs'rq(*̆V{A"j/;-?jkfAVT²UPtKT{$l)&rfN_nx[{CqHH?҆q-=C*f\vBU6ka]XBQ:>1T3N3f@Dگԛ_^ϴai\0ń2ԩ4aESq-4 ٺBpxV a%@3Jn9p5Ҏ&ۑD8]B-n'RQY_ڨ컵Wʯ.(a΄|6Ջ¸anS˜{K{A^KvyFAUUGDe548"'gAPt4ud1y)"?@Z bQB C8"/]9jj|NV";>ImnNWHW g޹?kwq[lSZ?׼ZɍS輙:سs6]X܅U8,"*7T(zbՔ[j|t\ B2i&[$L/괰 7@k4˨>&7=^g\I[)ON~bqXܪkxykUnRl b6 7"GQ;'?n!a"/3\!ЪF!3İLf0wr~I74%@nQv<#Y iM*'eNh >,)4jV )pł1ŽT}-s3Z! N`>pu/˜?}I7lS27ϓJhQ|L&6'VݔpMȊJ`3 =qz(k㬷m.[QϼŵkUnm,S[rff:%cU{#s7,LЧ!"$YS.=acDV]w?()Y605 n  T *E.'dwԕTj% -~%ꓖ\Ъj[cppɺj/^oY;.1Y@BwNLX_AB!).sO%m͡uc,r3mk "+,u|{;;yf~~^t0 i3\k@^܎3A/p̗@m͝n2[Ddq(eg7 Q(˳nxXP$Тʇ߼_OX9[ZX\b=e Mym w1d\[EW`HW7  \>!9b՟'}e@QƹD]SqDlheCw)6tqIL%pa#5N/8n $ꑸFoVkRO]64Co漢ʀm&ni籏}uԵW!_,^ XKЦq)>%)Yܵ7OZ7>-{MN>T]ޣ;R}Q Q>v905Ye?.=ӄ6u'p8"^na%e~I[{;J= [TdƗ9yhwmpgzs(]Xu{0R";n-o('XwlPT޾fy]3! mq"틪yHI"1Pk9õG!y2_˲/}d&;YxMEEsr߃RR pUæ$PHK12=gj rŋT= Uead9bz2agXn~V5&0Gԑ0w/OZ4kK 6ȣpXDm9ŔM[ި w&Kc28tdoOkhsKΞRU*-MˇrzIlr-p%P>ϡcx*2К\J%[ɉ0}%+MƉ\zB!y+ckY@VIj=TFN0ͧrVyZ|pxm#D("zZUvH{k;vxƇ-sR(5nF(JpP'2mA(|m#m~ @4mI6A[*/nI3vp7wP"3sLT&!<]{.H "=":^f"JcW*޶_6He:C2^I&kLM$n&ifdQvfZ)vYː-lZ^uO,`IOQ9k Xo -ic m.cG9qu6cg=k!jZ3}h|[c( dCk̙֜3KljU*Qt-qcf@%߼o'IENDB`OSCAR-code-v1.3.1/Building/Icons/Oscar-1079.png000066400000000000000000002720101417327530600204560ustar00rootroot00000000000000PNG  IHDR77M pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_Fi3IDATxu]wוqLܵIj[B" ]_Xv,..J)-P/uod2+Ǚ4Gfνs?9~ $I$I%$I$Iqf!I$IbpC$I$Ś$I$I5 I$Ik$I$) 7$I$IRnH$IX3ܐ$I$If!I$IbpC$I$Ś$I$I5 I$Ik$I$) 7$I$IRnH$IX3ܐ$I$If!I$IbpC$I$Ś$I$I5 I$Ik$I$) 7$I$IRnH$IX3ܐ$I$If!I$IbpC$I$Ś$I$I5 I$Ik$I$) 7$I$IRnH$IX3ܐ$I$If!I$Ib-m $I ^䟍iVk{@"S,IRi0ܐ$t%>3gn&.<*8^8]6 AE[C d];IT$a=H#^$\=LUX9ۢr/ƿq;; p6;"g]j0ao?o9?$I{{U$)Z;)hu4sPˊg;r̳tO =,`lWww<l:~v.>k$!$ Is0H̰bm,P4΀ \Q?7Ψ&?Cg J1Kd@d{2Hxl{I"pC]dGPz֟/h% Cv̮>b40mYD:Í c;B9379*I*ɋ4 Iv|.>ν.g[LE% =..QJ%/}<7c?K8 IR}AcgV& ^C8㢁p{~l_pǖ?~ËFIR^nHJ3y~$ /6ÎE8b?A7aXN8c,GЯN#οKT ? IR)} ^ɎТpŸ?& 0)&m:%-g|Mt ^XJ sh!I4a`10ĘˎY3 4o{vM {ztwcǓGac$IQ0ܐ$E5 3/AFyYgvl=v͖N4 7$IED\#pg c bHaeL' Ï'vcп#IҞ]@nH l,zn1p6(vlɚbnj I~ 6k#p[I^$)JI%$ËArIƐJ_0 FlI8cN6X.I1ܐ$I@+00ȘEcIaQAfdH.Gxd~nn$ܙa&i$ IIs 0&>H}pvG7o=Adž?Y*I*?=@{L gjT.1& 4$im_O8ãpvǣFK%IpC`K3c`1aQA̤2I* 2aaǀu`RIRi1ܐ$]Lf'QChHR>c,'qK +$Ŝ$ijs{fl3&C$U# }raDYaDYAA*snw"bPܗy%<̐TƝ’uLln8+k $3[h^jR6cgxl4%4m D?c)mm`2y2yAD&Iu=ݖacO8/C7^'5!@<_ml`dagμP~I}J2ѕLjbFK%K70>Me:ACUƪ*\i-C;v.]4J$j` 08 ؗpVКmϱ/`.,7-u}וTJC};g|dU)j*P5; i*SI+KRbrG-%q#p7H*!\)KN\vR:3s `. _ҶAVo`㖁0H&~wIDD3z5TOk GWrMXbrc%jST[A؟ӲH; IڡOt1Დyـ]e֟# Xוڧ:Nַg`08={&!T|g{l!kLhY,jaZ*R S wyJfN̈́MI;~K#) 7$*^ӁSgiLF@6`SO7F&5ܰ{nSd$fc6JE04̴2jP38hr-ɰGKM:, &Ia!\Sٱd 226dԓw0`.5\{fS? Ď]JG#;4ۣ*ㄙ›[IdHcl2T59rj=Ψ}IԤԤhqv0n! 8n0Td 7$a1pvI=5j,֟c[l\{&n]?BwfhyN2$i$mݱ}G}FUr&qh+$Ԥ4Uhvf^Z3pG؀ Ig!T>1Xz=5k @. 3}&z|Ol {eT$ݵDRy9K&UI掯yM5͕TUhIg6 }p9$ᆤRQNNfY=;6G膾p+G{eHÎ\RI&Y 1Lk*"I}ezۀ+[IQ3ܐgIf%gnh dyŒoܾ+ }П$nj!䃡,TY4[I&*&t(p aAW$)(M\xe''z |@6x:̸nZ0Ȱ_rsߎh6sQOiwţU VtHQ$cy2|XkǷӕdx.I3;ru >70?vT|| m_+;=94" 7$A h^\,/~]䂀 Ou-<[iI 3$iwT@:1ռq(8 H%T$ p=#md`!inH*fI&8hY_.ëo{ڕZc Ik ΘE: H% :^@?p6^MK#i8nH*&`p,pp.PkYݺݳǷӟdHRoI\;˫o6k D@nH*2Iˁz6|^k:3|ܸem䇖.I&X"dZK%,l';y }n.~+,_% 9VZI{k!$LU!@aء!o_v=' 4$)6܄G+S &4Vp&>}֥-N(O]-!Uj"ik 7$$tPpCm|ܵـlkI7PJPNRW9|q?℻dZIj!)B)`.p"l#: 5N<<}pvR3^JPW:),WAGn& 96b 7$E! .(m߯_mz9'z_6А0_NPbjs%mǎOe^-d F1ܐ4&.=q&}m}Y2|b@C j+M34%㨩(۠#7|Hڙᆤ8 hor \`;[;$Y  2Ik]sVqfqrJ~A؀'$0ܐ4 sX| ui'k ;KW_v@C H'hN1}rĔr aq=[vDʛᆤRA$X` PY~6VT}9a瑄$i/ 5NW$P ND>b\ |0T 7$ j Gkes>Vu 3<$/IlFU1W.l G[5Oʏᆤ8x%0YhY훸NVmdi[?pvFe'W*J揭fޘj>{&4VS% \l! I{bp( l6[7}6֕ .;$tUEj\)syeՄt? l:C*}vT<' ܽҶ0HhHL@<2`lSOeޘ>yx֗E3'T 7$z'o"1\y|lcS@IHbr@6O&!j5f5ǎGяc!8V.';yls?녁\h8KCWA0cs~XFזl^~?6Jᆤ28x%J{o޶p^ַ Ҩp$l1&qZ^#|o6:kݸuT2 7$=xLfJNXoe]d{,$2~j8drn#ǃ>3ܐ] ph`b?]kz.X^U&m\%I*'ۗ&yM,WMSuTupa`O) 7$J Y>@;s|<ƖJa O}K%'j)u\_ [Jo <d}Hc!iiE@^ܿ\VAeʥ'$\ؗ$'iȩu8cl_;+pC*OKPƕNur득\z"I.j>JZ4szNX05*>x0ܐ5\)~vOO ghT\z"IҞ f̚Rf֨*.ܿ${# 7 w$MZ9jZ=gktZTbi~6 UJnEwWll mU$!G]9zGOٍr,~X ;-X{\}Y&O˧OIuLoNw wTy gqHRK3 4 xds߾c3?FC C?'HT"ImeXԤaxQi 1?i`E{ds?ꦭ7}{̱;K@P豭?G_&`6xfؑMJqC!ǒ}G?Mf\CM9BpIFTjgk3 Xuw?TjAF)T$L=`,1U[͜۸SM}uU7˶sࣽ?K_&O&>yLid0}[SXAN}b`%" ; 8%)5,Z1uKxV9io4AFH& Ët$MU)Wxb-GMgޘj&5UX;=x|so]6w0O`l;sO&E%x\IeK}`inHŧ؏pƫT̺ oWսPtjϳLMT$iJQNR[HrVLmnk:n ˻ڗw0/k `. v\Sm\ܵ'ŷvQ* CI8mᄙ Ge?p#vnYM{_\> n2.Ge: ch99 #F]2ܐ}ofArJ~qOێG&ؓ?DdZS5FY% #0ϑ K9i'C/l8r{c4p)i׮5 7TQlQw%nXŶ%>d'Cs>M-UG 9H?{ ?<и .}L^ǀ#\ | _K_wnHh% 6yk:3'Xoz!Cύ$;aSH$$ RqԆŒ xj߽c3W='3$R 8vf׽yn\?QWt{Ff!Gxm$dW<@6=+ 3VsѼbQ3GUL@:Sؽ s| Oo{wlڧ:%-V2)8Ka!g)p0@,]=NzR+@zz.Z<0@C*\wf~V;3^ҙ*?[Ms[˲~re*wz62ܐFF3p`!ᶯ/)?yG6ӗ{FPBH¢u\|.:h4dTwJR1 ]ٽmT>7? hLccW{6g 7a77|6_򣻷/Ko@#p&>yx\GE*AM:IU'7}<}<<6~6[ӽ#pJ]5I&7Vpkgp\M̎u}tzB4|RP> D;J<~j|ryL "AcU7>R3 mm<RIHt@֦9osԸ=v=4\jfq{t9.bCWfZvyܰHpC;=~&p _ɦm;痓|y:Y_&m}$=C@Y6vgm-=Y :|HZqdR_|0T92ܐL%*e(?{ <6@6l`J}x+?ƪZ*͵$풭Y6tgy/c#cS1._.)-U9o=-NG u`'SpC}o&:o)waВ}F>ͼV[͔JSG[1,]7oLʔ-\@UU&3&qĺypUa!igWq:_޷޺J8[eП2W f6U`lTl\挫OUpߥ`!gl T_u2[ m#֟LqXZQU+Ҷ^n C9͡4ԥ9qf=g*b)}8.# ɪnWrf&ŇqB S*z me]67KViEq3uL5'iG/MM|XIT2ܐ^3Y@S\v;6sciSR/G>{掩i4V TRq>Yˏ6uɊ&@N_'belpе#D" D_|b ZՏmcن^՗L$tFsx+sTq")vY[}[}[S6V|`SkyÁ1rÝTT 7gJĢ kzlid,;oɵ:κHpnZL3ir(>2yjkӼ)hwb>Rb!PC4]noʽ˺[jssy-,lሩJO{_M}|j?ˡ80FZ9ivc^7* Rh4~`ctxEߺc3_eTzX2™';p揭.வ=\`O2P|gJ;p4<6T * 7$ |x#P<ΏlkK@.l朅͜:n*<ݰtUwp8Y.twǖgZs`.`'Pqfrw4^Ĥ޸߶[kާ37q&g!I[M˻iyx#aȡb6LZxߑ8uN/S~ |h* 7T}P>D8=߹߰>lC68c^gof|Ǖup۸m*~9fMG݇-)7x*CU뀏sp?ܳțk[6yME-\(Xk]$iܻ/Ovr߾S*VCT>~ sG<ōM=p11`Bl`s 2=Y].Sr&N94[I\åsS]sigؓíU yےq,T_hl_ܓ81P9pWQ~wm[ׯC5b+rT7Tpf^XIwlY*R.Tq^ f9l^zΜTG{ ݞ<Ņd=o[Gś7p.Cf5Z$Kh'qNm2P^jgL.+ OpC`o@2y.,\18dR=q OIqo+z|7.bÖ[WL fl]Qog_1P n%>yPlhk8vfqD6UZI/mÞ=L9Rch[ǻo E݇o뇎\*J*U)t߀`oXoX %) ]y7bijx?Aـ31Qi"IEꩭ|ܳu[^q>?},٧뎡8 QygwG)Zn7g7m Q6*aS>ɚHRL}[]g]/=YH%,V ixL)ޞ]~yT, 7T*Zz{y+xxew8T j8eN#8s5q9(  k7._?`64\@cm'򍳦ښHRm?}:0P oj| )֣> 7w gl|h.փl6٫ׄ]]/ VGc[I*17-C^-tfš橪Nq;aEp~ pCq6x'ab=ȵxzqQ>6Aj qƍ$]s +:p/$T9rZ=uŹq`B1P%I36>bX1^Ɲ˺қX LoCGGZI*:yݥyhc=Y^&Ϛ{o8mˀAO pCq>"6n]?_JgS[c'Jh\ yq#I׮c]gl.pG@}4޴x -5Eymx5 IXD)E oC//#lAk}5.$ ^eܴޜ*s!Iz^.[vЛ[N>`\S%K?d1^x p )F $0"pN1`gǹkUMb&L0>͟8}w5IR~Yot9d^Kp*s?'. jnKQN* p⏟`uSTc&Lţ˧Y In{+=md 8TG>٣Rt5fມKgin 7T,ƃ ՇFMuE.s,$iݹ~$}μWkIBTcxpgJ#pCj`ht>eKV03؈掩▷gt]6X7kt &H-xxgOy4R 7T%Qts&>5 |J>GNLeʫOIxWzACDe*%]!MEv*6'EZw@NlPNppz 6$I# Xו̟/偵= &kgM-C ?) 7 Iiq-':'ـEj\A*ᅦ$idu][+QjIyXe?{4 7T,NxG|1^oq2'NM:e=$Iܷ=}9gq(ZTU$x⩓zdx8ځO?,  `,0tKSJ"*]܇I'=qjr.^'e*~6)*V&1|)vt~ iu\ݼ+ytFl 8qf~ihܷS~]pV"24cɌzyb; wR py@݅޾ ~5~m|)爱Lh&me k Ԧ-"VJ$yM\vtOz7 7Tu)`㮵=\e:`?| .XBC%IEnCW[~T5Sp_8؎A() E8!|M|㶍,2`W82S9$D&pnN \h@.ط߾zfݽ{' 7b;oܾv=81nL5WaLXkmr5? 5>T //QlGw N`'JpCQ><;wn[G[wƥ(qЛ5#KsZ!I' [7B]5O^1,x nd(vA0mX`s36b'O{ˤJ!I* [z\hoͲpXI4҆2;l b`(`L{\ŷnn"?{3s#Zir$\`;bgJ7ϚZLG,COvF>trƧUrd䵳x~J{$I|ڵhԸ]FеǗO\LG xOvE곟UH9Ob>rO.n4=o:-Mڞ(7çԳ-<}U=g* Zk$IC.}/ܸ{WtyM y,^o, ^\ ;K nm_*UMЗ%$=ZS9znf-F.1O˓rTg=Az>.KѰ<>rrz6(z}Yp8}T&5VZIǔJXKO]aQi$$f}m/ [k9ja$=)a&f\4>׵_l#Z˧QE$I//筗䗷o;hd_Ν3ڟM m]F 8pg 6>s:뚵.E)vAcs 6$IU5I4wDTi$T$yrC*we$ĠoN]p ግtPq=ӚpƆb`}ǎL$=g*6>玕]_սF1A۶Ad8zz=E!@p'H`=7aQ4xF>u*b<8z<{$i/ln9{]lހC#tlqz#8X 8;ͧWnG._iQ솂{lHwF*RIl&v! $,bk&94ۀ͞foWli7VlA.OΟn-$IFGODG6ӛ5ȨHrn4Urb8)@3p= *_urP?zFdf3$I#`zFצyΌFF:ɟJT9V *On]L,qy'I`e^8ZH4®{aOmꇴIПq3b<৸PY2ЮHAݲc~8A.[b 8{a W~$)"}_[]4H$oQTUz)Awa 6b!} 6$I)sYS: y ᗀ 䇏ցb8 {pnhW >],t^E/pF~k!IR>o= me|6߭`E`1;;VzrʋRbwڨ(Zm_~A8+z}'{X I lCW~nsFF&ϱshJhr^* IH]. 8nglHT7Tp2~P'[72XYB K"OL0I_mVpc 6]>`*nx+$IEbbc7\< 842*|Ok}[a:OLy0Y0/߭:§lQ `bs%7e 6$I*6*-RiQM]˻hADNQ6xpP1L6w5CڧkQ &^7o$XMm䦷cBS%O#!<ƺL1'pR0:;*-| 6b*WN!j-$IEnƨ*n~p8pDЈ֣%!EcQ>.9'Olg%&"SB/GJ{o?1c RW[$IdFK%!ŰŅJϲ^sɲBB㞍N|%QN:`,AI[+qnꇤFr2{t$I1./J00>1CPÍ5Bț~[u@?g5X If7 0Jm }f`'$^ 7JS5BOf4[$IzIΨo YJpn>x՚BT+@'$> 7J[B#׿sbPYL J$/l gO3J%#$i7}qqd$I{lBC?>ou.II'|喍<gyY:fF ~pyH|T=_$i:3T4w >}:m(T4y2b JB zC yի!0={*L$iXz)gQ x]r!OFq3܈*Mu[!iCX I4>}^{D F5w< }dۺ=p#NUȃ_r̓B$15wFIeq=.>Ny'oU<+࿮_U>b!S|I16ͷΞJҝ4ru!k<ɻxW/AWr)J\ q9mn$I#괹|I0`Q P{Wvk <% |hd i>Q?f}@}hOu9$IG5Oe_k,,$ 8.2 p.>qyl$IX<ƻo|%= <Evc by Q07-Wg$s||x揭$)RIyTjR`᐀5[ uk<uxMإwb`sO7nTlB.`ɼ&)$I*#%!gaRׯr2Yd0awC yzZVnFL4ץ3,$I*Ϝ05C&$ol}>DÍx7 ~rO߿y#hPnaB@{_}6by-$I$?=oߧisqúBMZYwi`b!>xjr9Aj9eN$IsʜFV|FY[NGFq3 uzl?k}6b"_4BH2^&~ق$f y񍴊G ,swy!cUŀ$II.},*\a^~rϖB ] |Y=FGb\_.#kXiI3&[I{GOE-BtW?/Ӂ q>|taqav6DޯkE$| Uަhxg.|eڹYI;Q<x/b3f5XIT2*\v,wOѰս[ymCދ7"aQ*W2I%sֆ$I*=nyMB#p#`Qo@MDo[Õvxb}G\$$T[ B =W3(fm>:slK]E/&$tMW{kPßJ`T#p^Calzbv!$I*q0ܓW.cOyFFa"?>Y)MBHJ&3 pw̖ȿM61({5pB\`X3!IƔ ~r U` ?,c/՞aQ8SB [6}г3gA,$I*+?5BOmMQN ͨ.-݃yu|).I9͂m2vy!>TxFa Hbso)9B#[5ZH4l5>zhXܿ.Hc<p#zՄMDgb?<΍˺ 7X&ZIT>wD)bzCknDpp k;/^9s -5ik!IZKM?nS4,V?X /Íh5ob^5ڈD";k!$I6q aІ/޴lk* zFtk m{fF3K/I]/UI$D~q ޖ,B }pg`xń7"wo\+XIUgN yj/%l+Sx+p#:P/{u1#[gMu$IQ[ewQށo [3tațG8k#5.G9wA$Iz U)w4jlD=r 6ʓ0 4pWm7<_=c$IҋxoJ^ ?dk〓={pc-&!%Z?ޱ!~S8f[_K$WLt[uUw#o=-J3 1AO&ϗOb$Ivjy6^l<1Y,.䯏mF ݻ]$i}ITTxk[wsڞGnށ7#0ؘl"K<_qֆ$InUKN^"G {5O39K?6s:a~V[I1g|IMOuqިGnފwq{pcdo" ~[߰Ҟ6$I>m2 \T{dJ 2{u,Oxdjzpz!׿'Ycf76H$s4spp\TW# q 6|֍r&S8kC$iX|㬩3^J'x 1*O3z.^Z1uO$I{iZ?`[j=WtG= Vn5|"yny3!I4\zR)ou goTWB +Vo౴xj=M$IdRc?k5=Q{ 27@!uV\뒔8|Y$IK/LMUBhި #\?|jHlѾj9zz$If|q6^umzÁVn V5@!qV6td<q!E$i}o;jMԣ6opc%#Ӣ/Ϳ_ᬍjmY B$i} ^Xg1{c gmDwn!{($IQ=cxB{)O]6Q'Y]cwk"d;Ç^ Igmјޒ$Iq8s;h~zw#& xi{g4J v:|>`!I51c74Laњc_ƞKgF=p[oݹڈ9pC$)J)w;ݹlO8{%nQ6ׯ9_4j$I6 %nzԣ,33 `ԝ囷lrC>qU$~vt'?kwj݁x/(Í=\0{6&T IB8pB-L+z\HG /pcs/5uX9S-$IRǜNkFD4"T +c+8lr$I*&ױ`l^Y6E9d xzo6agmS$IR;i/uڭV 7v1Q+V;o#>Y$IMLh+߹ssCfYg25@}mV?+9BH$m;VD9h=V 7vQˣWf 1ǑS$IR:w~kSB{,ׯrp2Pkw0xi5)@_-V?rpWIJ&ڙSuKrppNzЏu-gms $IRѻ`Q -^>QY \lw0x'̙ ["7[8 O$I1PJX핟y C Í20:}ut ~S Nh!$IbOw)^؝eQ9p aRE=Fܽ백T!I U)}d O-Ƣi/Ƌ! 7"]|w#X8|qA$)f>wDgohܽhg Ís0pvԃ~/lsGW3BH$LKMmc\ohVpT5=<;yE$I8idݵP{(߾cSg[xÍpAԃ~ok|bau$I5se{mOCʽϕ!<5aYՏ3Pe%Ig~DȹX\{~GqcQ;˽ޅ=X_ב 4?yu$Icg3js7}H$ڊ$4Bh=M(<0(K;9Q笍TUB$| n =g•epcˣ[kCXI3f6XޭQX\m!Qxo.xaNY$I*Eydj vigCTc 7BiP¤+2a6JQU$I%hɌzkB{&_iC#Nc 7BsxۜYd/="H$;q"8yC{=dSM.M1kpxH$/gOgdKS(]S0|-K$ Z,$IRN'y%k]`{Ý0t[9ܘ?E@Wt =:S{I&Tl|MQ88\ʛ,ߧF9__Km$I!kTi!G^K[o6)]S5h""}YnY嫹Da$IRz To썓()n,Nrp6J4SL%I;Kek{ޱ%&ʡn$ƨl2(wE$I*c )Rg!G6teݮ)ʡn!;՗U\ 㬃$IR{KS;7G9ip_(HDV&Irw=O{۷GnLzM՘"[W0 ˓zK -$Ix~.W֞Yוa ԟf`iJ-D[B~ I$>zxid<-!O/r 7rZ_%"Lp& !I$挩bRcʭn ;Q,p# j<:|ՖXI$=;kGL.)Kn4܈%lgmH$Y.:h )צh=吧r=)ؗ\6_%bmH$Y&6T3|v([@ H`BT>u탾ZKDmEɍn+Ihh=uQ /ZK1 8,?zl\:Nr XI$!},oOc#ReNr>+"H$y5U8tr7D91Zr 7 Q vn2y_%ĩ$Iz1d6홛VDTt20XF3a3|MBKU$\b$IqQA{dY@5b!889|]RJɩsV$I/.LpZ ݖuD9ѥXr7ވOtПuIJ)9oQE$IKA{7E%,SJJ KR"[T_7$p4CI$饽bA3I3krK̫k$I+=" ] u+p#TF5ݴ%)%&&[I$1֥)m[{6pUYPR 7KǶ+1kTM$Iw&7UT-"]rH)ԭClcW~c9kC$IMAvD9dVJFXI?$0:.{%)%h<$Ifo=xEnH7DR 7R}7"[7j+Aې$Ip{~-A푍hsJ%ܘV`$I4Fצ[=3dusJ%8g]/ټRYK6ogRTJ0I$Enh7ݵ6U-@-k %%;JԦJ IȜpEnxQqQ\Íf`n޹~jDH$):ivK`LdMEVâ, XיUN)$INMEi ]_=5cl\ÍaY6Jւq5A$IZ2þ(K̚1Hs,kTN$I"-AQ7 hS}nrE"Tv$Iw&ݲc0T8,5ε$I^cU.ȑ]B%pzpc6Nnt0"H$ jc{iSmnLr>+3"H$ ^n E9ܸ8&nFp_A%l~$I cMc!nl(lv_5%, 7J$٧BhxtST%pư mY_5%f$I*MU)jBu=Q7+5K1׽r%mѸ ISmˮ|#ġ&q 7` A$IEyMA졍QWԢ8D8k(uO$I*meQh$\MQnL$62O j)qS[V$Ic56.ڛr:bI\Q 3{ 童-^o$IT4Ilz] .L+!ܘMl$nZ+&6sC$INkW%QKpc&6/䁭PJ\slI$8"h](^JcU=JJ\uE"H$;pCnE`5ݟb7&D9Ό7f$I*>'TTlH7h&s==ܘDF67URfTYI$$5I ]&/~;bFG5ح}M6$IRq:`BE."s==ܘ D7 )C$IٍA$ Í=y3ћWLLi$IME;23nsfah{nH$8<)]qkI\b7 l0=Q I$ƪ ](Eo[4"\rZJOK$LcuNV_e`Tm"H$6"hh4ܨ&k-E|\Qn[*Sܐ$IRq;vMEkhT3z7:m(sFW[I$%]3r4XkQt`(mQ&4-$IJRIj$`UGKS&k)5ODzdsT!I7V_pnLjnѲpz I7"he"]Ru(pcL=WDp+XI$.Ү(Uu(;wQ&]"I8{AEЮAG;ZQ{(ppYJdE7 htH$UeK D9\UnLrm}9_$IĆ wL.Fs 8Px`\TuRl$IRLizI]+c DvmP&!I(gc16z\R.j*)E$Ij]d[;N)^;ܶWBpY$I勚-vɟryEyWdLrmR.6;O$I1:mT-ܘ].K)sNK$I1rZ]GE9\k1֠`teʄ{H$)nl]v ~Q63,5+F9q> IJVM vfw8UŶkJt^_edɌzPdyz޸ l{t "QJTT 50L&E$I*N'lj!zQWM+bAt*(#@6do}/}tyE7AQڜ;e//=m;^DDR H$,^J7WrPK[Hۧ`Utnn .l&ZNާF"M˻}/7䂀\> C<=+i Np͓aQ@:8`B-h`ɌzfؔV$Ek~kEKFbTb 7ZQm`I6oӍܴwq>s\`. B!F1M;C.G?dG6sCTMdz=g̒4K$Z/:EQ(6euatm\X7/?Пӟ ;Rb쎝7`00 ` ֞,+;T807Q_iI4ZlSg!-*pc0)39/'W2z_=k=#}&Œ! d29lr۪^>vMœjxa2'$Iː6ԝzQ?g oZ<&/pcn'/wnu}thˑh[Ď'T.ޛue}U/*8{~38l uj$iW؄^\=%ms:F"\~ қwnǶcM=Yrgh$0x1pJ.s]ˉ+vWtsɃ\]:;nS/j%9]Yzsurڜ&Vۊ$Irxǡ_lXn)̍rRWesnsm/뻳 yTQ<l|}q^VbMB2Om2\Tr|V6U$IlqA//ҥK.KyDHc[v>W[7qjÅ$ ˇT$4ƪ$O{s<;uydsws K0H%ǔxIMH%xl}+Nr{x{$I.glEKNGnfTu(/#D>]kz훸}uOlgHFxI>Ly&Lɵ:I Lk:EE2ASuF{[TE2Ay6ǖ,\@gG7~\ݫ{+WT3; HC&Ck{ ~|]2f6$I/j=9/끉bynӢpChCwܱ#BÌԦ9v^#lIuUZ.Mn6?J?OZU1CgN>@GMY6dxr7,G;mCd4 X"A+_S[p|rJTNJ^?'H (n4Jˎl` Lo[8pb-S*Ta^%M369nFk}ܸYLE$ghҍ}7p^>|8^U$=9 Č`"Q,30Uo_frefݲܱ-d}m5Rq4EY4yg[ʤ &5Vdz=goţN.yp+7CM*$}9xp+OC#Z}JXZ펏zQ}HyjW_<\,X@ET-j3r3,c#?7#j@8Gr\J4Urz^޴Ð8 H$xtm/nk2| QS.IĆJpC/l 鲔40r^ef>뮵|M\p;==pHݤ!Z9a3GNgbC¤lu>Ɵ:wKIڻ?6}o=5i_$ 'rB/s|)\t0ԉ9mNqx+~YOrIgsjܼ;c/cQ؟uh޸[.P#|VNX65?9߹c3|4TueK>H$I/*E2,\OL1=bL[Jd3* ߼}r5<-ܾt$lW>o5R||eHFweQⱵXa IZcu"EU:N̺[4HZZJ)m)PPZBww]wqc66Y{ۗJseyscDa{+`]0KqݲT-Ǔ_`1J|w~ R,q--OޮE ""de2A'ڂﮇFȠ,uoaY7Wo" Z0nϮhR-f< Sy MgJ؍6xpߢf""$1gn\|w6yS\ ~Q#^,,l<AzrRLbEXZ ""J9i& wrݵNn80n@c];I`bCTR!8$ _-vy %|t=,Q24N0{z(tur#@ݬܠ8jq #_T6i%`gY@DDF=l 5's+jۯ~\0:`Ff[YEMvQ3vy 醤8D #]åq G.rX}N!,o@,lB+ڨNh/{n&͚g;V7SNwXhl Jj6 \4֮\׶cm( ?,4 `^{Rf2># nYPEvR#3pxB }gF6yQ莴DDD$9 _ ,$+7z ofS/nĿW'ҭc2Q[v:˃@DDfWf0'd'Ur][,L ʸ?L(Ì[g Gj:N4@ fx쉈9 Q_$ @输a.D'( n=bleVOjF /݄VOXF \ڌ/*KeDdaY "GtQe97n2Hϒqq# pDVxq?xK6?fc3$ "8od&@ocgxKGtcU-k4(O7SxTv,uS΃1ʰχcs0./NI;"=%7,y((;۴$J5GjC*m'zAS܆InJxWopN,?: xGO0|% J HvVxcwl"["JGWzփ6uc45"_@U$N(Vè A"{^IBkӻ??8,̲~M+J#xbvߨ:DuW'0˴48RS͑8QaFCk%@pw[65yE͚3i5@`1(N{gW7H xcop7x<,6v?;Q lf*ˁ݇/+ '(]xwW7nYP9VlNbpx>^c:bF!pxlԢTL+JU|#!~KTR`C0l(ڵhx7/hl ?YB)i%7Iq;wPƟ8 nkAye+ _Y6^X1(;\tX9\ߣ#;oDZot]a/i臷➅M@DDWVI7;1ݸXUEp;[(f|j݉pox~p?x. MV\^|} ؽZָ9orD6ыVoKk#3x脿C&meFDvw,- olwa\[pӻuh#zDQ .\9/*C[/~Y߁O( [YЎ>.̶ஹ,hr?22~U] (hvpǍV,qa9%8O@;;1ۣQ/xyG%fnىnv;&7tq _dWVw8WY5H_iɷ=FlkE17qCݾ2LYHظOALzD0c+[VsRV77F]༧Kq 5^JƑf62As;5})̴TniȺ/|a:z#΃^m~D"@P9uAA aƔ4i?-~lnpI \ 0$y]]m4`[ptn[PsF)VzjƦĭ2Hafc~f",+׫Z"Q0†ɅpX/&U{P DFAw7^>T F0HWɍ"?DZݢf> HHNPn;Ԅ" VxpN:݉Uƞ sq;ΪfFI(!dːPB7en wv%ZmEk5K%ĭn vuGd%1J\Y9`@Voyg B=M!VTE>Sxp~)؉@Jt)LDA^~ .kǙ6UaCŀTf~*I.Lue"*en,qc8V}!Z,X|mk[ GJ9XCGx;xsS7wnАͪHn~q3[ъ'ddXp)yldH5"mӖ7Y/Gd[1 _.6wabIe6-_VO+ZQ "-H-.2 +0eZPmS([PD]wy`y݁0:[W nxpg7t`mGd3Y&^fS97i#3qL\4֎BT#2FXYijI5sO?<?8%5 ăK[Qg2=\^|>ߍbB)Ln4ڐ|d6]Z/*3ܿ/nF/`k+k܈$Wϗwy,lR}-w.x02iFUh8ӌ)l< O.oVpTw}?xO~Aε:xqBƑ0pYݏ׶uiZjcHTk:hζ^ۥyw}܈GW<1J6~uvft *,ZugOtMϪ%}Ǎxikv6C0D-99*珶љpH5‘bDJ zXMFwuP^G)p /^-]D+xL]H3ara 4c_>e4Āڈ/OV͖i5bb~*W FeۯU`S-\6ށt[abxC, Xo0-k`GfA3wgyhY ^ ;.,wOECeU *e؉~P& pjib%7{k5XQR#ȶຳ pX;FXnB&4r8,TK9΃=rKK-q4\28Ms?7̈́'86 _xn/=&8n}ޗ:׈4Mnd(9`M{n$3;B2]" ?5_];Z}V$a/pA'6_qy)evN-KW^'<^i܆<{" joen.,mEN>V˚ MO7arA*s]/ ܜ7osw&ty&j{бv[=3!_ۊ96틨Wa+IK[:٪ k3IKKW&ܐyضV Nl f\ʷRz`c# tn7%,+*ݓsu8jĥј^a.9) b=j #"|ph4&Wcln .gǣ)ـ+݅a(" br5#q*|D~?l&6IƙRxʱBo'??a"l> #o@6~6%؉%O:ڽgAW.M)K7OJsa#wOp'hFWs~K94%/ӌ4]'?,i:@ua8ʖfuêҩv }=1k"KMqM,uMԲ'״k^j5&T7Q;" ~pZ^\5q)q~ڄHX6 nW;)9IG`3+l W6'[fz{;<GФngT>w>vv*#(% tUfU`51O}T,`[υ6w8M/c' FB<e\=5W4 #2^֍k?q_Y#2PRmLnѥ.2sR+JԨ ( %O-C) yo*1^y.@^z菏Ư?j5 ?!+X/(IiOK`er`RUc`rkUEFV0G[cO+R?Q9A)) ͗ qLn NjX_R/NJxq9_Պ?.m,%6j1/'e# Ée/" yRN)Mnjb&]l(z8W@Fm)F]r,ˠsVw(I7oR77S.KrҡDMovΦڣƒ]4"3%QaN.{jm~^}Mp;.œ\z1R`${_,~aO7yd勠;qV&Y}>1$ x~z gB~v{kPT%kн (9c ?29qkD#>NNa.E%#3q9EI{} GD點={_d>e3>?Ea [|(N-L#)=2PgH[{k5Ct;X!ָx^֥q#OP?[QǺKLέLn/$cG2L)I?ղ{ BtuP\VEܺ~gW߆^)] E6MUpu@j-ɹ{'309'!mwb(fJb3?@W0f\}7nl,FBt8&7P b:+LnU[?R&hkvCyleEBr&謧HcO׿QI魳 Niړw~4JOWQ@ҟZ}=VY4"J]<(I˜6+;\[ܺXL ӎЊq#v7{cmu^?<5'B-'HvOO{c+[wYo絔nn2=gGOwHV֪[zZ+dU]ReQ!4m~}l3<&7%fVTse56$@X&o{⟮{zum1Ml@~xZv&>~9ԢZ͏78cRN ʲ"jPm[5n\G#dУn:ZhtQRS%.ݔ(󉄒Ƃ]1;GiEl(gNohYy0d!O0׵XJ3-}$Gj+V/O~ԵqϜ;B[br  QB Y :\&7(ic|ߤΒS]k95F ׺=p^;#ňsT=3WW&} ^҅j=,<]PJUnC Z=!Iyt[fYjM^O| u\ZEpY %1kS>䥛b6i%unɲ'Pl*Q6yȵY[k;U}61c~wGH1KwV_7z-=nJo-v_BYaS526>%&Wlrcz1TY Z3+bscUe.$*F|@&F lAc~q3z'?9)Aݓ R!x+OvCՓt쥭P2H>K?nRlLnP0KQmHnL=[ZJﴱ9v;k[jx<*'KRuA=9*7jUN̩=ck7W=Ӳqż9 ʲFOߝ J*mؼE_&Yг->TŲZ8;n2Xԉ.z/gVڸ;}'GW+0㮹1)(LV1QɗtQͦevn^ ɘnD 45 O_Y iKuELnP] V%蚞J'nB0KqCpsiڡ6zn H}w9<02U\֩DLg(UVp,!osLUl0AI;[[f'3oQl^J+ R97$8{p?w;e?dK1TmHq}.|frVlClzں[nu8QBڍ51q,<{uRML:{Z =)zLnPRrӊ%-IٱVb**i*"+p#Xz]lڍ6vh2Y!57@wx+畕0MlaYACOYN)jiOjO~d_PVI*oI5JPn^y& -xc Ì zP _ҌkG+HD~b#ANrY84 YBixB(: ҈+ ^:JL(Plet3~6dӸEoaP]tv'2dXX3ˣKXN*N=*WzߖSRǿ>kWd3U TZ@RwXդh]loOmdz; GB2QU[gqgy3 ]H:QqXFbQ r#??zQ^Jj0wd&^ڥo] V?>݃#2FL)J̲hcj7]OD,aLuzx dbUk9,IzuTMUҞ ,ӍtbmMzv CݓU8krr.(nJF2?Di }f}\#>,Ie7Y7tfnuT#Q ZXY)F䦛pL\0&3vz#٦=i|^*&v`~*QvrG͆-a) >Uؼ?8aloce+kD'&wMj ^%+@XB2t3_1)(4J2]1AII AЯg=:h&V0zwZ2BGMv(p^E?63.Fe꾱:wt XoӋtzgK˄1҆61_K7K@zj#z'\0v D 64H?}&(}Hl&7&:08 YVsDI/F&7^0[Hզ&хnh4Hw^1>@Lvه-><Sp~R.§]P*d;y&+zJƳE*/HFC; fy'Wl:s(AHVGgK#ʡFX-׊#;2ӋPiF* {jo}tDZYSh׭X}8ꛙ̩XG?2Jt|a$~ڬ>7ӌ%bʪO.ICW̙%hD[Ouvas3 0\*=~}u' L&MM>c]{?6 n8=_+UKnlm%z8^Jm0%x dRdZH3xAS8Ulf} xSCVպ{0eO A?&f"03_8qQFt҆\F RSÁXVQ #$A$O߭9Aϝi*n\ݕRVթmrA4ܢV*'\LlPɷ }o.jy}nVOcܨN)g`^Ո/,QB h jromSD-}2HՖki8Л th@L΂դ͌$ӌG..ma@WqB2 l:Feܕ)qi {N\QpI9<6 U{ˎMnVqY-%ڸDfWpyLd:1'qFd ng7$' 杝P>$s4ev ~3h%G2D6q@$j2~eK4Uow'frASJr9E(ζ"zD'tP$Vz$i@ɦ'RLw^ҥyɅRy,,F _\Z{..CUA* GXa Bsdlg]7T!?>=_֯}1$T eCObjڴ-,0jhhpO8%< }qLq%hr˟{?O *U\fssQD>ےn,V?D4; ރg6t;=mD:HYe1k= y?kPwISq$ w/lD? Il3bѸr"$ :7a&?Q}k0Yљ?2ڍ%n<h ѐ)oIݟ4aw2THeF2 n}rDf6JVtP[ť#u2l*r^ߨnf /,9 ^t4vpF<|Q/xJXU>tJnth>Տ3ʆƧFY^ڥib i90|%cd|xw[}X^A}/\$wC>׃u ^Uwu}-{dn,=lٞ,ܥj?% `RK$`|S Sz% V|kz.n.t3E f6`9E <@G E|Wpz^lZQ*"n`}hp-7HWnWV6¶13s#u# 8h6; ՑKnlPq琬tӐ^tf>^҉-~Uۊkc5˜HMbͺlLUr#-&F9] E|υWHI\4Ύ9x zbZg+jtPfo 83 xsږ)f11ÞUM`hKS![=O*Z"(j㗖c?wo an<_?)'8%yBu٥WL ƃN6k(lѼy%qrl\;e,vccD'=C7١|G+ UaS Z-žK[8>5kkd:=qhl&rҸ5%B_r1Z^|MN3(Q5x /kijq*%Vtɀy21oT&h՞?7ɋ\!txpB Tv%éZrFP Jl؀O8~^EDڢN)3Eet-nu[Aqi '9%}k #,ݠln 7v{wW7~ wIQ-N8ȶbDZ=!|>k@;Zg>w(z<4ױF] %eo]/ `d#Ry +/0㮹E5Ixhy <5E)\j_˲(Ila;T$7YQ2^NCNVhRzOB Uu)cz":u_"%<ߞ)yp ;Fv=7JVtaN-L󻃜4ie͚eMf~EPe /_4UKVzpY~Ictw%sM*PoNjٙlk?OǸ!׷lVKz-m";,g=5z#Ʌ0͔zB<qe]& Y1JTeoRe^6.N+k=xnM{= t|㤜n*ci#.uʊ}o /-U^Nŭ FgFq0$A{ gg̈́9Vn#,ŸW"=Şj㗔 H͌]Ut+;eQlF̀ZL81ӊԫ܀Af_ܟ1*-72Rzl5kP>Ǵ5Z> e+@'7bǥ͚45H+Ff TURk)QЏ ۝Г٥8<=_;tE%XHzkj uӅ6ެ8h #be{nБdTfhuh vwӫM)y<*uV!RTx둛Nbn/7kj@vwS7  u+mvY;>jD}O,(Gzɍ,ilg>'֋ڳ*lW-=qdTm Rq+ߤ"/݄fľuJ2tE_zœ#Yw|0aDv R{r]Un]*Da5 ^r{: rm&I2!A)!힡g5&Y;2AE3pmV^ 8$.gݬˆҢnϐvK1Iͼb!o;2vw;$r#@oWLnzifE+&84<( pMK7qW.cR7ʢZ.fuX{YF3 jƺSUX^F<;URL{'injb{ >tRQ&= _i?|Hw.J`'9U^Ê,1?/j߳¸8vBq^Y)êZ7^䍄Cz4s,|Zh9iI֓O)LUK{-\/½6iq\LCߤTTn$g=tai96UM[)YTPz78†"Kk/Ł*ҬXpOImO^? s:[i?r1fwEUn_ՆMKLYa;;`S'j h=?uB _aYNP`3Sϱ0i8v-?r6~apM~ICVWF 'M6wkdf ľ%&.LQT[00[oz/M)ń'(㞅 TVB17|D7N.Q_ȔiDן֨[ga=#řf]xi|JҤ,ís,Ln4pJQ U{xڼo5$cզM ^4Px*+%8Tݟ4׎H]E2 UHrcn8=78qvՔlRۑVZN/S҄' sNòce_T㻭K (d^=Ce) Qb)hw<*"oU pL2@.{:Dcr~c 3f?@ZO'ne+ xⲄmaۑuNu?wF} mfw~ sUfz[q/KOwÆ`}\XL=!{};F8R4#}u%RLޞzB}hJ-HIҟ.(mim;&ғ@d=CЧ2LHsBȄeOmtaZ2W#3u7&аt~rbp~ mMABޫC(3f ݜ/x 0Dń_ME7XiS" rr^5g9ȬHfrX qqaCK7<0&A‹N'\F z|*(җGbCb}oLBm: -&j¦N|a<~^~qϝ7B<׊LNcruQGΙߚS2_.>VʒvFd Mn4CLNnDxXx%1VA}zq=/ N"l m v Sx󝛶&_^iB eY5?>-VAo0ny, ?læ:~ Tw#'ִE Vpˬ,Ƥ8cs}؏xv}>O[ WPƮ ~v}BVm$+r5Ln Ѕ)&Vnбq,1'5:[yA=F$,f=aU5B¦O\KI9Is\ 3'*'aʼT`kWuωk􂒗$UY®C&7Fhύ9)^u@,2o XÉ8,kUb#$#'ӌ_]+&8".[Of6&Vx_gұ)$}4i"XTgIye<_OjE]GG_TF4t 󰂟L1i\uVɲwvQ;9-r?t¸]m:7Ytl3ͤpDQIVܔ! W"f4’nmh5Jͼb\2-[ H}w~܈$1~hY n|~uP??^R)m :d1`ɮn;nχ[-u-KE^ߙs&8oE074$.* ,FoUq͔,\51KDO uk]X_ @v .JBa_g\2{)eiI@ݡr6dEݍý&Lc'eᷥij~tvw n/'J+֞bDwN:R0.]Rوl+ 7881VnǧSa2$aY ;UmXH3>!#[f2uhlUᐌ>j:;í']Xڂ4#=ΝGlj<ʶcvP ̈́uluc><%l#76yދn,qc_MAP;+'8`Wy=4E4&+=7<v1!_w]*U_3AKq3{Ö$OqL Mm*H:wA"(ӌZ8&+ /omЅ袩ٸ<\8ƞ0a#Dϧ/j+[vJ.kԢ4ͶLm a\='Z#+k.Rm%n2ыcޞC>R࣭]hkƖt̩aؼLT2jqQ bKtxco2()& Mcq7vbA'h&v݆cp'$cI[qⴑ3qΨL|ϥ=hެT՞!Y콜A樘X>&7p 8_O bgUmZ>a6&`N'pbD揶mEVz(ez'.ԁOvD'n;IK7a,<љ$dB+Wm8mL&Ii(ʹ$ӌŒ]! `sjg@0=':A HK53s>qЄ \F-VwE'%Pv?^lF_G0ÌB 6og@0]!tt>I&52-C#23l8*S4]!~*Ph DN'N'wqY0WH@{O7]7dLl$)HlɍMpYnA}jhۜara l[zɇ5N|GgbNe@ÂJaYjo-]R>4iRi ʩ#2p~5'qux}$`>Vpi&L.N4L/JCE9i&&:(hr D=M^,9Ɓ7`C d,LwNMc= }7W;mpx1 6EM, pq0Q#ID{(`d~*N/Oǭu zу$mcM[lj]>\"~c.)8oT&ݣ~r$NvQ3<(N{:|hOR%v:HsB3 y3w"ސ?i[P;A5(G+" Ue*6 mfnB2:}ChqŇwvc~ Gw50PbV(SsGdge3xiKy~dDcj6 ?7'G'3Jo=w u>O72~#=O/Oǯ)wG `7S6w M]oI^gNv>B265Vu><0w`d5:,V;(;3mլV'}b|<مʊu ^qC ~6?8-cck;}sD^HQm0ɍ+/E rz"Loб~E{r.>;xxY+V{n{2QPSJpri"'G --b0鏮muet"X{bT/1ȵbzIn<s==ZmM|K?F?on(EV7*/A͜4>i7u`oO &کY<&yB{]COv'ù2tG!T#,F ci 6{nG ʟ;|$/K'`d6qoӻ^g*%9Z~RNRw5b}~D+n|eZ6~|Zl;y|Má6 !lc&VKÂrODFQif8Q%|Sg ^ti=C[SA+~;HZ$fBUE؎KAՀqyL93>Ͽ;]mcm~9s'(>AE%i84RyTi|~fhm}"_te ت Ry2,Ϸ׵pJIMo-n%=?$crchKQM|ly1&bO'smo㷟6aM:чr&"Jt Aph ܃I ĕh9VKusp۶ŋ{?mK[#;E$9z+, KPiIc~ x}[>-jܫz4cTN :-7ޭã77C \5'8fM’0M|t;9W|``lޘVs|kPuue~ccoNѶJKۏ7w} b{icp~o\6v=%  G#=uME'5!? +jxxY >k AS3"fFQS h0>ߜG=8D .$.v &YpL&v v}[bvJh-(0kӲqә Axr>z䥛ېc~ {dm R)eoY|%xwg7},yR7^άH_I .mG5[rTc^%烅/$v ș6 KfoB V;(aAv _?)LBeC݂G..ō]yco'??FqmOWf !{r(jD̈́)fJ6 z2!?<8mŗxMjmŕ$qO*X\>k"JB3J^7@Mh'Ij7ﷺ*mU9 .|߅6O(ژSVoؓ5ϡ%:!sf˜\+~pj>. I̲K˰3Oi۝ *qtbO72ۊy D G>J;LhdZȴp8<5'3={K'6uΊ: ;ݥAc~صo3bEchsGfhǰ|hml8,Ƙ(Fyg0:'E9ԓ rX?U^|aɰ?{_fOPF3xL<;Qϭ)ύK&gukwUDV WDɉhu\ 8Q)q ު3&7G4$)>$8, $837:Սwvv;7D'#z~:I$!b@ـTKRqVE65Lh<YVT8,G'"J&kL4M;^㤧@'b>s"ƃU&0,]Ŧ&æ35]VC[+ a- 6 ޷Z02ˊZ, N*bxQޛ88A$9ɇgPbf"[q2exq-v%drh'0X}3x4X wݶmo8 ﴢT8R>ԎH/$t8Vk6 *b9U6W i e%SJҏW""""ζd,k1^c{ZD ^-t\'""""rYJ)_ ܈aM0lFG 1DDDD B]!r+%7jl9, gGj"""""קensB ([""""T(ڍdf1 ,l`aLN"4IxfHSK] BkM9P#rqAw+ !K&96x2MEmC(` H3-0, EQ"M&Mr1"۝ i=7,!7$j'Bܨ*jOJtB s#icuAH"s,7F#}N3̮tkBDDDD9pd(6vH5OzMn4*rt :G""""o}_% ř"G8QB}@K2̼{""""ncAH&2Ep.$H3Q@ wCnCqNqrYJ\*rfD$='7E 62W{""""; $+&8Dw@(QbF{!fUڸc P3 !4)( =#Gh>H9QMEsL0c ,̉8MI V%mtx!F+"cE'/ndHl&4fV Cɍ N+NhnFlmJtZEvRq(:(\HDDDDm2@%j0F 6vr 2s""""ɝRAN y&Qu(bE 6&7ME$wĨs$pZ~ &8ޓ-c ̼dHu w @$:|#~a{r# MESxq j3DDDDsgXY,& Fe18-"YΫN("؉T9ˆlO8Cr@`SQ:1 xjM@DDDDZUfD#Qɍ=6lZQl*JxA """"ug\-D e<$7l9`^PuK|H=[} BH1.lSN$h3Q >A"\ʫ!ai HSJ]i9^DMH8uqX;2SwTuSJS9Dg$7j1v6 ADDDDpo& G {Dg$7@`SѹVT 'RA""""wwv3 n|v56&r<%`6ux%Ixeq """DA!]1!rOC}VuYSTխ] \}wAH`F5={Pl^qԯO= T ωnA(j^ xJn5صS4Z Q u6wdj!hS6q4Wn KDDDD $$E^246pb>nPm(vp1 ̞bdq=,HZ\8&WkS7(63.g9;!؋!fr@8Ѱ 3 3 D X,qE vjY:Fv~Q3@DDDDö3XŀKD L6ޒ-6"+ Ѱlc3Dv%eSyTf ~u#` hX^ $O9ܒdm<&7j*קe ycA """aYDe4H$aK7hI ɍD 14@Ϯ`hXu8QMA\Kz)`I@nW"kk@ѐ2!MT??+_p+$$2v(NH `w7ADDDDC.>K&*Aצ cC~jW# ?>""""\ BUi(lMJ#xMnl]`_n6MAahH>gDuíNkrc'OE j6jI ck""""j6MI$1! -rGpichPZ=aٛ>!Q.rIJ3gC-J/dhPgȄ7!txNnE`ŕQEPd hc3Ddp8IXsrFQ ȳxuҀVn$\l"\Hɍuܑ0e L]; a =|>L Sn<  !R Ry xtQ7,MQM3 DY #1Hڮ3孝 `Dsrql6P|Ĩ)ђ{{9A bC """"|w& 9B:?R%7m,*(aYDcJ6%"""Jv@5NH4#.g9#%Zrj^9N",!"""Jfm$l#deH@6-KSh:=au3DDDDI췑($م"?FX`N)IG3xIƁ(} $KQjڪm|SN. bLI4`l?,3DDDDIA;@$`&{|*r9VY4(Ekۺ%\¨!INRWw̢Ayik'@DDD Ĺ, ά F~%FQ R؊V(dlo3qWf%u%Krc7.M1%GW0DDDD ]P=> 8gD!A$7d;DyvѠ b ?ֵ3q90m#?8$kl57AQ""" ıT*=s)XML)LFmD ̺E"""D֎n"|gFg",t9F~)Mw[3RBDDD\ 3YWV#3$]趰B\<3Wri Q"XV%)v䦙D4HtH- BH`t&264y"""8v3WsԸQ*Eh-pbp'  """g\ ıc(0R ?4$k!p өHyѠ|w~"""x>jq,EG  %crc OD fư N8ޅM Qa TyD)x@?tɘX MZP4Zv&"""G/oeUX_/Q:j#3cHFQUfYm3lA3ś7v0q$ۂz6nFx52yn )O"""8΃6wGa]RE + w;3L7|\B͏Ost"""x6Sl:̸bC , !g-lŀS6B% 7;; B< +x2y)/`XLA0>YAƍg """ҹM>8\f|ejaWXF2'7VosјLX-u4(g!7+[פ痈U/̆Hϴ!j0Au3rXGl!"""ҵ׷;8ausE]R(F=k"}NҠB2\@Ԓj7!"Ddun#[LnDKֈK`634HF acQ""""po"7)|6xя=ΰ6h5 IF+VHO_H\W" tq48j/=&7X-<7W8;(c>g*sS63&7ptN F04hx/@kUq%%Z2<UxF"w7h>7 B018; QAPXAC֍hV{ۿ/EwU`XP/r494 *7& ]R<??@eu:Ao8#f3 ,mEDDD^ׁ&'Zu+$k#/ gCtiJA-Eޠ3p%6FVmRXugaL-FB܈Рzg&LăK["""JZ؉NVm82L%Z},恈LnĆVfXxp~)a Q b,"""JNw|Ī xJZ^^h`r#vV@ꍛ*@Ӑy}nADDDI-]n3zQ9bCO<ɍh=oz( XVf,(a`H2ӌxR_i+9xW?>=tVo0 ;%nľVG!_ZLw%D|ar#^ |;)b l; """JxbA=6(IYI9Z}偈?co)D PЙ :{Ì%mCsW![LwxG!>1{.D7"eՕZ}7;d90ND7wgHyhw O-mnƂZ+ g^1 2Z wxdn!w*q݌Voq rm*ůޯO_" 揵㇧i "q0v=!+#@ ⱕ ťMM><tEJ,x##q0!rhs$s Qbc """+ԲjCgLF O\^v˄xG"0!F #P7U, ňor>nFDDDqda DQp,\2֡'p/?FbarCР⡋J}M ^v/,㻯&NytCFkXxx 9zb%n0DDD{ovדt}ca3;x$bm.4xKU0 ɀorAODDD: {uUOO εj\~ãC T4x|~ .ga[]7u2DDD[,l^a'wk d/x#q1!FY)(W;X4fEaO5o#pDbcrC<gl=pn .$|\BDDD(ީ[7 9i&>Ε$Рzsnat of H7vV. ՓZnx< ,B(VQL~~tx#iNQO$>Eqfa?_H$&7Ѧ6ϨHǬJ [([` Hsl@+@QcAhHLnhWU2M1ޮng#?H;ߨa Buu*,Z~΃R%tIIIHeާ{?,YzHvg|߯WL {ٙ{;Tʓcnz18icG"iIC߿RIz59{fމד"(H?xg:28O &5F5"""W51M$?z8;դ=.Hs w S^/~k$l|e*c;UpԔRݽ2DZܤp=wW5ڞ"K|ﱵ PܷEAMq'$OmKZܥp=7q /fi EDDd`Vtz_NQTKR.+H{N1߲"qyh?RqX<}jK!i]E n<}d'R(d;y`}kc\~{W5t\yPW҂ LxGB +: "<t^ôqxڠt-t쫠pÍeLrCO߾R&%"""ejگ~3NIqȗΑ( 7ܪsNs}kJ& W)-] ~ ͅl_X{뻵%";1t~,ӂH/,or謹 X\Uü]|Jͅl-Qf=lUw[~Ƀ_"[n;4lQI]#i{􏀗5lXE{OBN14oMHH-pꀛt|NatMH{u_ףvɓxkEKm!>tHGoC#n<ܑorI#=EM[WsYC[dZPo37_-#q|rB-toSZ4zpNVLۯ)?|]y\:?-SG3uH~G4W-|a!O o~VA/mCDDD>OYڎN16c&{$ˁKF-|!'߼ k=E˯^+;4""" /W6AЧH ĥj=.|E>фi.q\0FtX|w󹈈HqS+an(EndxP=E\o?zj wG0W(5nd.`N:yQG[g7q4"""#r[!۔} -{4k1}605yzO\Ə)>Ffi\51t}M'\wpWtբ+Gfz$+-=r䫂COEO՝ uɓoj;JD-~w0ΙZU"_ԌH[-߾6\Mx;yYòƈCDD$n#ӤtI|cj~6}F,{-|Yz\1io~b.ܧZS<,'M*HDD$WZwZa1Ǘp#0u@F,+9t g1Eڞ"'-博 85QEpZX{)cGa4-\-l m=p 9]đ䇔K? ݓfͅHKYK۝r\dI#_Nhz?wjad[)|"MS&Vlz Xސ~T笻2U$d7tp$ʄw><,0*0-]8֥|Jݥt {3i4""".f9:j#lw)Fa4G0ndFZ`~p.4LH{~Jqvhwm/Ձ_!)g;LU'rhb-_0_s# 3P4GGzI??,n9u4gZAPB"""u]EіLOğx8c7Li{JaIZ 'sY[4"""#3i+J:c*C+ 0?B3٧X3K{^ DŚN6Z(z^끫5@qnjk K?@Oa"""n)qw,n9R-p>< ñ˴22J^lOIۇ-{{W QwߍKٚiZLmEIÅ{WsC0xP+#MF{E%mC=r(JyXN'7ZRoͩz^U\{0 i-}U#~k)z_'Oϛ@iO!n²u%""NpSyN'ᒈrQ2vL: 7r[q{ OƧDw__ۄ|CDD$-6q:5 UnN #pVFREFx44$5)l+ȵol\뻸 6`tyMvpl>L!2 7rGp=1|uBVC\F{^!""BkbpHA_<{Bǁoƞn}t cZЊȀl˞gnd@$?U\<{~.!%7)-69ógjȀY?~f=d HaPdXMqL!/[ ] 7rO tP3@֊Ȁ&kK" r=+XD.La5sG@Q$Mnx3<~vHOF[7/ayST!""ώ{%-kD0I2(ؐ4Rap5) qy p='u ګx7;݇iT/>f 7rW{t0e #)"20<bLe k[U6+""fnVjqϟȾn:FJl (m+YYgޅ;@L7d^i>DDD>CcW#o_ ] 6R%lq8M#[ EW^k $ / 2Z<>9_}k5w'8Y ~6D,8k,T榑m^HRu*%}uK@Wy[K^>eDD$E,{9o.S2^%A~Y2ppB͉ʼnԵWJ$9vrUF 4٪KzM|K,0psU_5t'221߽\ǹ|HZrJ^UFʮMșc8r|F ܐlM 3kN2@< 뺨͉Θ-iBRvGs2iRC %s)4 |=8ݺESmس@^Hٜ*3 6Rvw2w.w^.j$]bj!) p㱵LoDx{fyx5:J1s^`#6x`ɣl\ @ 7dk\őV,nj07 8d y/L-ԜHyw}<ח*Hxj{Z(tܐmqp#0 p˛:LRr as|j~UHfxV~J]١TZ<4Fmt0[QBI6PlU@>0t8fR):[ۥ-*2yy~~3=bъH1k6R1 asҮxl-.bIQ!k*p1™⿫;!CR$nđNR&9k3e{ uuXKn@9p?߿*8$b6E>{{ 'ODDd;u~:S딱vcg,n.bIQ!|=PA:{s3Mq "6w5SQִCyqQ+84%xa!~DBlV_\Q$(ܐidTA? [GCWB囒#1X}8DDd$lu)M1cCဗ}k xnfL/-*2J0qnK+94w[ 8$]WTy 4\]1痷sei*PSÁvnjp(+`nH*`5Kx~y;'ݳI1 J)XDD>5bW:Z#aW4_*i$)ܐTS&7E O/m{VB>qYc) yD,a]k\΂ C*|{(_7%Z-Z{3I(xbI+_g= KgcG-"k;ˈ%U~].jD>3pCNn4uͻŚ[HD[T|^~5\}0MH,!y:./;`8|(.n}p:A &)7 7$M?)-$agC04ߞ&DD$K]y?rxGrN[.p.6y*"pCz| .:"7Dׅ86* PA/?گ=t&DD$u9%j)HE_a΅;02`@"}ͬpCҬ M1=z2 8>cCa^E?`6:p"[Oᆸ0w6Ʈg=gow0J.AC!sDDe[eUKvlZf<={SD\ElZdap\-U4H~kfns>H-XLT߾6]}x"Oᆸ9 L4=ōkqρvRAWiNDDҦ(?iiՔk@ү ;{V1<XG!&bzp\;hT6Fr2WED^^Z\L5R/f yQ=Rw_E1MDE(ܐL[x7~F{Fv aSYQ\1}0 ׼lmyyeN6=$'Jȡ8#nƜxЮ_ 7$L8ƍ[wqëCeӈBQO㉈{u=卼-Q>me{WcMG!zCD D;r;|kL3^" 6&.kooԼfՇC2G܆as9G 9D$4v'f^[s ~^  p[[ n," F s׾+iW6qz~]QI;v`4H""ԝࡅ-{f7ײFRNDgYcw7tn}{B TdN/ׁ+P WQ! \9OCsQ:.V2@w0\ΡcuH>cCIQZ=5̢Vj}{+mW9S+97sv`*6B Gb#kx`A -mqC'r jA&qTϤFD̜,kpfݸHW/sNet Wd& @R}nH. 8p@s^mSe;ӄ)e=1!Y~3k}l7yy 5tܺ,ɥ,s9*6-{)ܐ\3ٲZzۻ2$Ȥ*mY/=(/hwi1MB>6Q[͡C3i]?)ܐ\U lk3m*EQl=ƗrYd\EBYX7kHȧF7r˦ !2ָZ)9nHN?#>عu DŜd \w)bPa:eE$X,o1TUfIHǦ ;qA[# G)a7w 7D8 htT& 66pH=eob)Wͤ05!˛F,̟] OC[O1U%^2$5:0z5hAE2 h7Ι0rsGGIv), rN\w!%A DNswM 6EsY<Uid2"ďpw $"I;capČeMKgBUm Z*PY=HlL=sF 4v=p<;NI >ps1}؞Ղd6"5*P y-QskAf1 <=8gj%c+B}TkۊHF;3Ngemͬe4;cUa~q`N\ix%^ *n|R:9֥̫!LHې!*tAT) y) 4?".8hX,o3{U ^m̨<00!cQꀿ`>,pCyoeʠZBCJ]h[6*CK9LOAKIϏ1Θ(j9MSfmA˄0w}™z\NC: 7D>a>#y_Xw`ukxQy܅7 Aak9ڽ!WM"#@wܦ'a)M6M$W}42Pi5xL|wh~9ED /6sA@(S}CyxA 1K^c9`; %gcC>>%"@Բ&7GiV+j Uhd qۉ(˸m O)nlr@Q zΆnm.|B!ڽ'a׃_{Enq[ +ܘ 4QSVjȠ zq#WHB?"9^|S̉K9{j^|^vt|e;b('q;THyPEѴj~uȐL| \\ 7Di dP=7,={O^;5bLyDŽڽ"9cCK6Ȣ6f4oJ>L]ċԇ`֊"f<; 1\V=>K?"ƘîC C)a/+ɥm<8 Y$kty'dL u[KU$nlO)p!GlD1*fT{ܪR_m!Uac59un-<%قj,@~/%z25X xS!"vݦpCd1}82:h#km"[IⅡ]I;B:R2M=<& CqaH;:m n9C s~FHR!Nm*GdCw{VҢ6xZd+o <<}椕}k 9a2UHu_$/ldeF2'mUgq]G0QfiA[QDr Tn|M%IZ,kre4Պ|B~Sݑ2,đ9`Tj 5W2-jUqC4ll{VrH̦,ȳgclE/&\Z\Q!ҿC3́y?ـVTK0m aC$cL-`Ei_VuvVF;D6фC4at^s5)aÃQET39hj }{S!LS2m1ܷK 9"{c&Aʎpa1( gڨ"9n5jm6<֤[/+aå {WSS \< XtkE/">LGߵ8|@}orK~KaЄy~U~L 341sY9’/꠾3N܂MGԢ'^\x:-F† 2OQ(^ \4jE(X%W?c3hr w}sPR{z) z> U!&T<_tؕ4u'YQSItlzC dȧXs9p|2Q 4 T 7D8^&>Θ(?xb//h1U!2G5mZCqG8`}) uH>qaBYM8M= ZIS܍t,6u$H1MvhX|xtI)Ȑ/b9c1rx>=]38CUρPl"S924ڶs6tsѓkY[$zo~{B>^%y>^^<)S`㨉%6˚A O.nེn:c ڣqۡvdx/j$1,0+~ 8Zh 7DR+ L82SĢ/kGϬ'љH6⊤]߰oՇÏdzAK^r٠B?!߇]*5o11;n>ȇ~gmM lZl4_tMH܃ />*1ئR"2&IUY[)`ZDn_-p"SL|фüM/[Vl)uzo3:'9|nswY>ŢݶЕ`sז-b eN"H?Jz(jx\}0SnC℥p3w`mpC$}cˀ]2AF[g7r[dΧl}-H/G >BOT SwM84&+.>uΈ۪] ~*b+TJB?PYC R35Tk4hEd[)I//08bv+qdbLؑ1ʥϬgN]#])sA6j gχO6-".yb|4ck+i(" sI,V3md!.oXxX~R!: ޙ`^\Μ=\ZM(CFEM̔<UMF0xxiE)q]3L0.kcxnt' ϧCDDr[̆EyMMԡy>$_@]D w+NɆtϼfZ? ݖB-0Ƥ1E|ojjK|mbօw" 7D2nׁL01֚Nn`:rHsÄB߳Cda6>ǁ_DRAHaSf~33trFSO!d ;yÁ;pԄREh"b 7D28_#uFmn fi9CDD2[q>ܵk8jb 6bB{05DDRNH_N4g}noyMt ""96D-*Bsœ;7mxS(У'IHF+.NdFfE44D W+.""`BȡiE8c'f^ 4I "npC${cmᅭ뺘|JG)X†0acip'N.e[1[Pד@DFH8ɶ7k}wmbnf邘e*9t ޭ'Ъ0-ftE 4ϗ͏< <ԓADHHvaN5)62{}7=nrOc*5><S;£͖Pc "f 7D[ s`l6>7j`^_I}S>m CiI0*{V1* 3x" n#0 oXPƨ)yeEDD_A/+CLg\ou:Evi(""CHe}eUkfnnӕHVs(p4Ȥ0œs9*ʕYhfI!"HH)TsLJqYϻyoSMqpmYu a4&V]kKU4`>p0h֓CD2 ܵC`P?3KYcqCDҖ\c;`>(0: ?Vîsi&"/`5!"NHgg7Ź|%Z__• 1,-Ƌ{ N#_G5*fSg߼\SKکl""}1 x^+BLUȕ fPa@~p3DDc' 7DfKQP7@r8%WDļAx<y- r2~!Or0}4%""ޢpCDzu[Br]q=%e;D$'_/{{Uqʎ{%6aB٠"@ϽQ!"ٚ2)'G,N_gIc+a0ue/"W^ %kF~[ë׽KK{0 / Q!"9.v"&8(մLNV=뢭#>mS'qLȑ@|(BEaƣ{Ek;DcŽ+ [KvS!".e;P]b"z sD&L!""_ I/g5-$l,X[yg}ު})"i 2>XSy!{5lĜ|r65-""NᆈuÜrp&OM8$lbʎ]sY~[p eʠǢ#v{o5tMLHnaQ|ڋHo!e>B' 1^P!"z!ۡ+81ݼVwmvxCrkI2İ)&ê:8G|B>A/ TϺ1 B^6`H:onHnaBZ`:( M8lЕ'n\ƛkXXC{[ bPqca1z! QELO(,GuA@Dz X <飱Ui&Aᆈd"Lб m`/ i_͝f;Kw&n9hԷ&$lsڒ\.ݏI. 1>ǯ|T^blEw煠Cqǰ >~05lAQ1@ᆈdL18 PixMcw51ڢ1^X({hhASB/:VC$mE! cb (N6V4Y<Ҩ1MHfP!"٬ nS;AcWMqZ#Cvxie;c,Mc=瑼 ějqFV**KLΧ,S"OeHQ 1\` (<,Z5-""Gᆈ]jP5GR#j[;㼹խ16hnS!?}&ydCK陋C6ǒ 0K -24~<|| . P2,9uu[_@ED$)\3ee4p$?1ʖ(] Zz4X$,H¼_= =tqyh0ٗ?AoG$^؎so@<S^n*+Q!2(SYg k^y(* 7D$v_& {-oY#ph$Hؐ[xg}1}@odv`Z +}*/WO~#JVkrpPa,`!0rHR!"b샩 =t4AZ`f{cWmƓ73vhYd#fo!~i| D?p=g]~χkQcQC %Z@e?\q{|>4}~B^S6$?OR@E ,>-6E^rSi[y!Yh,4}s,""Eᆈ;-G~5g9d}{ 45FNVmQȽ{Sw?+ޙM!,mWsg(.dDUA!zmi B!s4j}yzr6X 4aD45""Iᆈ;S18Sݡ|J#N3|?Ka_O3S8)0cǨ!ezŘm&a5@pCDd[1 _Ո 2aKGS#""})~GbNZm VjZDD(_G;b=EAińk Z 7DD8t GAvX <Ԉ6]|+p^`W`0ӌ4 O'.""]ncg`Pi,ccg,Ŝp-C#""IᆈHz ;FcB݁jLHeBL ! 0""Nl_ 55"R X̘ HJ(q`4#AM 5ߟI."" 7DD2C8ӌt_G&(fIfkF`>2cGDDBᆈHfӯc֕1#"lLv`"Q]M pp c(0 (P!"Tf4k1`kH&P!"~1ap(|"q ƖM:1dzĄ"""wpCD$}1[X*!@Ï DD$@OWoeƆs4E""-n䦉 ̉,C0KC$ؘcY~+1Us1u&V 7DDLdLߎJ`0P9z֛  $ф /ookDD$(ϲ5cP ;x '+0c&X 2bŒ.M2""cgLQQ*=((Ս9M|4wh/ I ^Lso1t0&c/fi."Q .s_ 0 .DDD\Dᆈd72-`L)dKҌ`Khacvn„Nopю:|ŖLz&"""@ᆈ[[W a B&&\g ALEi00CɿG0C4 ,:1:dEqOHQ!""EoH|4 )&\ 7-"=}OK`mea&:|- „1ѠLtؘ}j pCDDdy[Z#eˌS;U#iIuh(C 薏ɯ01| W,"">'W.ĄnlSCDDDRI"""""""Ѽd 7DDDDDDD$)pCDDDDDDD2 h 7DDDDDDD$)pCDDDDDDD2 h 7DDDDDDD$)pCDDDDDDD2 h 7DDDDDDD$)pCDDDDDDD2 h 7DDDDDDD$)pCDDDDDDD2 h 7DDDDDDD$)pCDDDDDDD2 h 7DDDDDDD$)pCDDDDDDD2 h 7DDDDDDD$)pCDDDDDDD2 h 7DDDDDDD$)pCDDDDDDD2e<IENDB`OSCAR-code-v1.3.1/Building/Icons/Photoshop Notes.xlsm000066400000000000000000000270451417327530600222520ustar00rootroot00000000000000PK!K[Content_Types].xml (TN0 #U!#Lx,hi6hlLpiո~m۸K1,"*!Fk@q;>?0l⍔kh!2FJFWy9\I<: 1R8&-o,濎*Fg"*_Fj0A4 ]`L Ը"&ˌiDl ˙i[Wg°/ ]gWۼ'nGJ.['BZ-BXA4CQ:W S`3Q/(p7:'=]BsisV.3!<"~&8 e@:F^?À-jPK!U0#L _rels/.rels (MO0 HݐBKwAH!T~I$ݿ'TG~ BAT; m އ;=p#+~Ux i42^!xD \tvZ]OֵhA[Y T^rZPث 9XuI rΎv>V*:6 )ىJZ}l\ 8<O]!c)‹w&8ؾ/:]$tz7#øtT!z|KBp<>|lkߴ`}]s J ƝYKUÎ ~c.=]V)Ȓ,RIj!mͽ˓#a传dRfz3+;[&Ȓ>CnKtg~MvםFZ5۫σ2=8=Gs/eKsx-4jRUjƱn7d5MLm\:9nM9zgPK!>xl/_rels/workbook.xml.rels (RMK0 0wvt/"Uɴ)&!3~*]XK/oyv5+zl;obG s>,8(%"D҆4j0u2jsMY˴S쭂 )fCy I< y!+EfMyk K5=|t G)s墙UtB),fPK!E xl/worksheets/sheet1.xmlZmH~љ1fr{/D%+K.߯WMvFᩪ|oelHIۉ݃iTu|XN2?MeZ߫]x8TsWǑeU.W\egckn-׶VgSx(6,I"9Nt0j+-Onq.)#xYΝFCQ{x+ ?}$R%eQ-1fhIdz%cغrnHΠ?l8crSz<<=y;G)xt`YVFn&;Mk:WVF?KiR1 VE2N%\CÃW`5eWoN7i_^FivyBJB!p0I3vAobٺ7P)9Uu}fr|=_{7|,*ϳHg>|H;&%RqOej@9C Uǘ-Έe4~ÌĄ`^zHΔ0Y>8\Rf0Y`'JPQgJ5[ l AAQnә90P̂MSspT $ENgA-49 U)K eRVR&9@>M~ET> X` X.R8'`t!: %Ȇdf&0o HJskP- ?zhKj)+F{k*jw\AjݣHP`,EPP@Rѩ ]CK KjODPV5Kتh% rP)4S|Җ{\hتH`(ik(Xs W4S- E$kFDno8EsUs\]A~|)4Fy^7.>^j4Ӗ|;ʏř#(p_rZ_Aѣ& &k*ҊUn{xƻ3k[iŦ[rبWllTiY~meQ;=s.k+(RVrZmV!J^;UUmzΚ~3粶r_iіl4ѩԳKu,+y}u;teGI AB,u>9x4'O !A:fe+O+KhԻMh3Gp:a dAy)y)={9A| HEԌ.ZF3s dA(cv&,p:j?;#Z"O !A ^BQ/. $$Ȃ QQRiѮ\MKwvg҅;jK4V@\Çfgנ_[K^PkPDIr.jwd A)Q RSLX"7Z2>R$I O(9%o&`T) JU>#02]`XRxbL+7 /={=_*Kn%SSՏ__7'Ŀ˗:/}}O!c&a?0BĒ@v^[ uXsXa3W"`J+U`ek)r+emgoqx(ߤDJ]8TzM5)0IYgz|]p+~o`_=|j QkekZAj|&O3!ŻBw}ь0Q'j"5,ܔ#-q&?'2ڏ ZCeLTx3&cu+ЭNxNg x)\CJZ=ޭ~TwY(aLfQuQ_B^g^ٙXtXPꗡZFq 0mxEAAfc ΙFz3Pb/3 tSٺqyjuiE-#t00,;͖Yƺ2Obr3kE"'&&S;nj*#4kx#[SvInwaD:\N1{-_- 4m+W>Z@+qt;x2#iQNSp$½:7XX/+r1w`h׼9#:Pvd5O+Oٚ.<O7sig*t; CԲ*nN-rk.yJ}0-2MYNÊQ۴3, O6muF8='?ȝZu@,JܼfwX{IsE8#Qaw֓5m?(б\mg[&Fs=IdO (z8 w4VSa셨oG/б+Z7WǗ>ҊH1zϮ!2|'[PXoZ8veE(g-N8D˕ժH;6=R"gpuc#˓-Ó@?Y{ޫbN3gSD~ETD#/o5В ʙ͠ذr|U |',||dCY9feY1{9M/,caYe,_8bߖ#f8IbpyJT#mH]- ,cc(J{gq+?cច05TX0m+څ=}_\)Oq{ {ub ڿfѾJ#p+* S1^Q9N r $'ς& D}sʐ PdJ/pط8ӫ&V4~Z> zһ d%( }γF] aP gdqlarݐbkˋGi`Ɵ?NPK!;m2KB#xl/worksheets/_rels/sheet1.xml.rels0ECx{օ CS7"Ubۗ{ep6<f,Ժch{-A8 -Iy0Ьjm_/N,}W:=RY}H9EbAwk}m PK!nX:('xl/printerSettings/printerSettings1.bin``P`ecd(cHe(b(fHdٙ@0qff vG;##+| $<%@2r5D3,Mv UbHz!R7>]P$f|A a.@f/>tTQ# !^ ~Fh   /Fyq-˴W$1FI0) "o2?,uR@ؤ ں(HҊ_ǻ?'_/M8yνz}w/btJESyh0!0!0!0!0!0!0!0!0CPK!HedocProps/core.xml (_K0C{s m*ŁD-$w[ICۛ[>枓_ι$[E}6e-s!@rʟ#X*9j 9jeq{1EXE -x$ a*G{kذ=jNZPze_t8`)諑NHF:pZyW&NQVNS6g8MMp#~~vgiӡm'n5v-߷k1 BYyO7+TaԏMg.0!6aHIPdc?PK!aI docProps/app.xml (Ao0  9P bHWagMc$׏z{xDI:_Pkx]~ 1@%F_|RdrGDKVRm3`96elg6ڗɫp 5ԗ(UO qwL շ9bl~`_.jk5x%̰qiՃ txmWmJ&;ԌOHY?-lc9kE/G!`aqj6&;90N8ہo:s7^O'{d‘SÅg|Hxk^y>TdN> =o2!dݚ0c xl/_rels/workbook.xml.relsPK-!E  xl/worksheets/sheet1.xmlPK-!N xl/theme/theme1.xmlPK-!vo >xl/styles.xmlPK-!MR xl/sharedStrings.xmlPK-!;m2KB#*!xl/worksheets/_rels/sheet1.xml.relsPK-!nX:(',"xl/printerSettings/printerSettings1.binPK-!He%docProps/core.xmlPK-!aI *(docProps/app.xmlPK &*OSCAR-code-v1.3.1/Building/Icons/README000066400000000000000000000035771417327530600171750ustar00rootroot00000000000000All icon and other PNG files are derived from a Photoshop file. The Photoshop file has multiple layers, which need to be enabled or disabled when producing the various PNG files. A full description of which layers need to be adjusted is in Photshop Notes.xlsm, an Excel worksheet. Smaller icons require manual editing for best appearance. For Windows----------- Windows .ico file should contain 16x16, 32x32, 48x48, and 256x256 images. We also include 24x24 for Windows XP compatibility. A .ico file can be built at the website icoconvert.com. For macOS ----------- macOS naming conventions for icons: icon_64x64.png (for standard resolution) icon_64x64@2x.png (for hi-res Retina icons) The macOS hi-res icons are double the size of the standard icons. ex. the icon_128x128@2x is actually a 256x256 image. Mac automatically selects the correct one based on the type of display. macOS defaults to using pt (point) instead of px (pixel) for image size. Set the image DPI to 72 px for the original image and it should not cause any sizing issues if using px. Mac has a native command line tool, "iconutil", for creating *.icns file. Create the images, listed below, in a folder named oscar-icons.iconset (note the extension on a folder) For more information check "man iconutil" or the Mac Developer documentation at: https://developer.apple.com/library/archive/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html macOS .icns file should contain: (Icon size in px) icon_16x16.png icon_16x16@2x.png (32x32 image) icon_32x32.png icon_32x32@2x.png (64x64 image) icon_128x128.png icon_128x128@2x.png (256x256 image) icon_256x256.png icon_256x256@2x.png (512x512 image) icon_512x512.png icon_512x512@2x.png (1024x1024 image) The macOS *.icns file can also be edited on Windows using the Greenfish Icon Editor.OSCAR-code-v1.3.1/Building/Icons/Wave-100.png000066400000000000000000000227451417327530600202210ustar00rootroot00000000000000PNG  IHDRddpT pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxyxU?=MnBH@d PY,JqvFC[NXiU: mZJ+uPEeEV([X!$n㜛\.熛'O=,]}EQ.8!iQ{O't@E;+@ˀ X ` /| T\,/.\$Jn&k t(}S^XlѸ `:p'p  ~Y駦GU9楮ӏ+I@$@@E$/Ht3v3vifLU+g})A PWbwn>uSH l(B`I(L7QLY~S )+Hj<hrppz$bvTus>Y<p6JAd$ (Hq)~E:׌Ln5x.i@FkjjڼEayVA' (p|wR&S Cl^ h1fΑ~w_BRTQ%)gH *W$E}6ͣ<8csJB8 < Tx.xNmg[E:pf1@)"(`M51n$BQ dkIIۯsCO}N(QpOMvU☗iE ȃ)[uW9( Y_d'CG4qb$CxaG# ?CC]Wfp83D2I/:|WT3̆8Q#qPZȭI.@UwMMT_#JdqrA[Ow'M9܎ T+krYt}>~ ,)Ӕ0MwiZ<`5D~!Q`,GAq.}R͓jX8q^sYI"Y'_ ?YK4U6P!1*We/_4?mh-=㋒YbJ2-O2؀LšX_%"oNpǿ:.:oUHXuW(pENod4  5j}XD-V ',(26Eq|W ( -":kYAmd+JSW7pпmuժ Ęd? f޷?+xF}{\R`NX')ܳf/2 Yr[% xSmU={]\@Q#C̉dz4ɱ趺O&er jbEd+QL"n+&%@뢃!+$'xLt(;^*yd1&׻{z?K ^=ьFd9d;,>y#x~p!L`v>>CTI f×<~m.lymi?<_n㓹gR&&t_Y%q(xP2h M)rEjÒd{S2SY'3+z,Nzzq ?̲lk~#Sc'Nqtl߾ s( ߟ\Mx݀wl<Ɂܡ9|yꩧxY~pB֮]ʕ+y/KEVl/) 7yE]f$S{}cժUwv |`.::|c6Qi͚591c9s&ż{G~~>ƍ=p8ַK/~{\~Ѹ3t9G=֭cܹyl߾˕p0k׮宻:oj?ޯkf7 ws<2C tz%6@ &˳[W[l!--I&]ii)v;v$;wb61w'55I&%Y&emopA6*o' 3M1m݆amҤI޽;ာ\n!}Mg}֯kE)ѣdM=FH JvqE,Q8̙3477=9SNDfR677ٳ#SZZJKK GY^Rܿ9 u/Rn}CVȏa]ڵcǒYt:inN6[RXXHVVVs HII_1j FLS > CRm۶2@mtcZq8bURSSimmM uuursscrAYn/q"3 CNz=K|؊ 1cg*:R__OZZ1Φ|WP@#Zfa;v!C躎ᠻ;a:7;;{ ϰO;TW - Vw|;IJJKKEQ/h`BСC\veMMM^EfٍB/N}<՞jjj?~l6nwB%ǎLD|HԯeX j^ǒI~=|Mrrr>|>%))!"kƍXֳ&2  Hjߌ[!@Wκu<㈢/NkWR?_ٿ-<)ȀWA E,"18Rn7sa HTTTz#+] +;{8㹀4;,8V [e9Ǽ{n&O40ܢ(g8p. s뫶GнǁHM3dԿR.Ht$%%Uǎӵ_JJ 6w^&O|&!:׽ąZ2 )F˒U8Lh|w:0v$$ŜN>q%ɔd['FeZ pWV Sͦk/D S~(L)HMX S Q y{@ѣ DGl6G͖ N>#H9#{c'X5,QRx/8T^^>j>pjSԑ"+wpU$@uL"JjrF5)HA꭫W߃ۥ#!/30^oU0y '$gHt:)))t@ˣYKW֛W |]QvOχ[ζ OaaMkkkU(vi ݲfPW 08 1t{[;/CW>fӕu!r2Q%򳣪;v'k+zX pPS դkoMS96SmƄ Fb婟5wdĉQ-Utw-ɽjp0^@:α /´!d#P^^5\3"4B 2{᪫ۉ)>2wwc\ubD怍sgbψ#$ͧjr<Ϡ)}ꊚP_ػoK1YjQdrRue~%!ߢ#uݿ?މ$ɄөCQPP@zzz_?Jg M^NˡowX* [M`Gtɇ'YA[[:aÆ ݼy3'NR(ӖM2V;Ŝ\c k= X_(9u.jL2(S__|߈x[9ZԈ̍r= PuP,6~,iE:⠲2>+ѣ)'Nŋ3bĈORX!H2>L5_v1F+U:ꫯ&++ 6$|<q7sqz衈罶QJd8,x۰ eMFԸn?c{NW޻[$IxވF_}0 ﯨ{RD!AX0 ek mn 1Ë~Vo uy`<zA޲Ln_VPE#kt*8Ϋ#W p, kf>⭅fZ&/ 7N{/1:VWj=+iÃi\IN4 ]dȷy[5VWORdh KP8+1 .&Mj~9qgԂZj?n#|NPpdxծ+qyn+ 39_sE鎐E,ni[َ^l++kg h']ozŠ+ y?<@@m &+ w]M*(z>T}{&e^T`o7Oyewx0z|}sG2 tؙ|8kΰF]Hj ԹeuO[Xu6o ?>_Wj wNd`ZNձdG#RH )&$sۘtLqG?:5u)ot7`_#o|P{Nʢ=7[ą%;xqW3Tk(nSKN12kD*dq(;־F.`9jӃh&&'6:_9֍TP b_sDn;KJ62(J:&=$ut pt$H#P;_CKvdtH}G}F3JM&$#(J)L.HVM |A4)lހB]Sm^CE.?-]>O>/qBfI1[ 䤘(H3q/Y|_x$7VӞԪ O- `6ǮqvjHS }рR:jqTov<\?j&~=5EJ ] ՜L j;chpCB fhIWC _Qq[ҖIIENDB`OSCAR-code-v1.3.1/Building/Icons/Wave-1079.png000066400000000000000000003330371417327530600203200ustar00rootroot00000000000000PNG  IHDR77M pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FJIDATxw\g}>[mZmVee]۸%PбCLB()$i@BBJӛnVfgfwJrvvf.]m99;Ϟsbn >DD$)|q"r""n(- =DD$nH.K(1 =&Q!""ipCDDrŋڑD$$wrQ!""ipCDDaҒb`- 2D3ma%-""(bAFoMe3YE ""n0"^ aF\qBa/8\ȡ""yLᆈ 3<-3uq`gv(C 7DDd>"v!""˛ Yv""ˋ CDĄn rf\sOCD$(?皝ZfC؃fu""A3DD2O:DDr :3 :sV Q!"b. 4DDAAH)} 4DDMAS!"hȲOESSŐ|6?<\Y""h@C7_c31fg٨ATf=/3Ԗ:h(MOQUdgE{Jc9l4#`mt ?""pCD$jTy1‰ z Æc<5^|8ÍX<A| "lV 7B9bUg,K eB*SU|RpD^X%k)WYDD^>""ّ 4^,k* 1&f9v֟DsP Tp3L Ki~xYeʜs)sq;V9)tXsBܟK] C>WAtl 7DD2G2?Z /F DL čQ_P@7`jTp,lVr]r[_0rE;2pCDdq* 4^41q#c fb tN8:bt&tp@+Fd9ZeR_`e+ܴV)qȎnIF˦#N6r(eEᆈG @ʒ}Qp;c Ͳh_0XC?D^6łnE}tSY8NFU=dU2x+!"yOᆈ˓\vr5pj UQ@ ]d2gb6FD{fEE*+tX/uPW"Z+TͥNe\_YlD՟CD Wlg.P,F F11fS8f84XB \S䴲.."V6 T)PI pCDl5T Ǚ1qt,O;}t Ƶɭ(Z\TᦩIƊ";e~.'h6 ""s\vrP,8/l01;#a ͲxX1D5\PH{]ƥvJ]VJx,ll1%"%givdB$pؠs"t8047WDdRmu\TLJ7M.* llp۵%kB 断 Y4KcM"<;OOsh$Ā//urq֖M]"{nkZբ"q;7 YN4Kc c f̌ pA1-/Q`Z⚋J-5l:l ;i!"9Mᆈ,x@$N <= hwSaHD㤣׷rUc1N+.+NNŽE8 < *"Zu5, ̘F  <`8UD$;֖vV V0}%j@*"9Dᆈ pPcJr~A( 5x;Oyy+_am㕍ż~mj*feBfv0spCDErk7E*7 B1p,#~|Cj)"yוrzW7[pۭlljPr$"E 7Dғ[jD N}vn Yޚ9U<+HsAnH~ 680[Nk<_@m-%gk%_[l3{n 7$?ej|<;]~Q}3H^+qV[Tqݺ2 %+\W,p#U!Tݶ={n橾x"""ˋfaM7/=*i[/YMG%U<+HsAnyeY<廇86BAEDDzM7K*bn 7|xs,sus Zp,}gqi. 1lxCqch鉈RruS 7_eԕ:KRgqi. 1l a/?::c=bZ{"""rAlV *䦍 W~Z"ٺGW,p#U!=`:KPvDo|fC#A]|4?+VsI}N<|=~]ugqi. xp;>_q?ɩ.j)wTqRebs^]qYua1 7\Pe-p'~_.??8& tEDD2hU]ّ;nW!K|Ϯ",Y\F pCrolbih^}~yǗwO01PCDD$* %iW6ۗC]EX̳4TMCg"I{hJ;䘪B;EׯpyCQ>}iGO,gqi. MCg"O𭃓|}/\/_֖q骼 9WPI}gqi. MCgOܷO٬,W亵eO#??ܹgHWYp/",Y\F pC337[IoљgQ/_=NHUWV5el-ȇ/) r瞇ue*bn 7$`#sm|Q=ۓ|.Hi85+">fqU<+HsAnHk ?''FDDDYSYSBC_fqbU<+HsAnHfVcIPxⳏk6@9AS`gNZ[ >CAd .wSaz]\UՎ6R_0Yr*bn 7dilW "|)|.-xaB-,x 9G烕AI7B\PD$h.WpI}Qj|4) W,p#U!K3''4xhB 1승`bnD2XaE& > AD}VWqRfC8|U<+HsAnHue,a/}zI M]rSTno8$(1}>rJ>|JK~)!/_EX̘p#U!ZKoۻNcxJ\66S>rN]QQgqi. 5 ?SPYr{bh)Q!9+9c. 4,765%;mXsj6* 7,p#U!7lPX?I?қJYۛ1QrǮSsvmYm|&֭pᶛrJ7j6g{W*ܐ?xw¤5bF@d?1*ih)Ѭ kS̎]]~@6 ||uV,a1 7\P҃V1p9s;nH_;_P\VMao(ήS~vuv\R}{ \Tb1R-SY *bn 7Z}ohn[.ܖKLnl(y ;D.zS#M^O,w\6 *bn 7`p<7OCWۖ 2nlqT ;8e)zvʟr%;_SoӏܹgxfP,p#U!gR5 l֯ߟ/~O b˶?>:? mqJnkҎ'"USP'+%>jjvR1ϳgqi.|\j;ce(&|>~y§Hi鉈m)+fp&լ,2Enh *bn 7e`1eO k2;\ɍmCDrޮ.?྽*䅍| xS4UQ,p#UNv3^OCDLKV$l|krÑ!*bn 7z^ךt#'*bn 72pjG.^R!"⺧"РB12ϼ_"O ˝{ga1 7\Pf46\gI xyh{s ԤPCDeR!|FǮOfE=O _G֞ST|n 7r}| *u|6;BeGs!"r"!r2>z..ls}bVQ,p#U~gCF{oFsl'"xTf 7r}C>#,*bn 7rq(>4`/ވ.^)s۸:r!"r-MuS9[n; -˝{sgqi.\ *w ST0/>e^ȕ+:CqCrڛ7׭b wb p%~Q,p#U+iz}'xX.OHnvT!uU.vK3*STga1 7\P0(28\>`'|SD拻厫ED$hX1"M׶bdK",Y\F p#B#p9lS|/ED$騘=7ގJJ\9$|={u,",Y\F p#F]5Ohy@KPDDG8$}jU5TsisOVZgTE'A+yr8؈ u_T'>rJM^IG7q5*䤿d?A/ϥN߃HwA5s#;;@;&W3KSp&{o`Gs!"brSnn-UԶykrv'i{QA37~'F9lLc] 6]ԲCm 6DDDS](25sx4Dǿa,1#'_J_fpHP!4/`CL;~ܧfr[j }m!"nlM|ʕ*@/Ǧ+yA 7t;;*w_ c!Z'׶sfV6 ""yq  3Q?wy Fsr&:b*3?<:̬(H(fnƝ@NB<`c2E3dor!"LqJ$9_W ـ;;d 1[&Gp,=;_9 fb!""%~&␜ߎr'ruʛO(LS!9o^w@Nz0i}:3^IG7q!9'pI\;5^pH)ܐvz@S3h16Oό낙Ton_=7""5*dM0jp嗎缹vj5+pCr=5^Mh[m*,p. x_bg[uB)ܐIh>\kt 8 e"%.o-허m<4i9dV޿BCDDLkkm!j-7/#tZ\ڹq}Ll <^B.^ơ""A}8$bF?I?_|j,Nk 8<)ܐ%s:ظ%KO(0]75""}8JKŌ>o"NK$gqq>>1CDzSwh,[k &1x?A/<8K lbgKR!i_b?A/QC36̢e7kGY^pV F n^{h*NJེ:RnHZr_ +0[PyY&rk{%n_`CDDemPmO?z'W*Z") 7$vvޓK?&`(0[+WEDD.Ƚ75) >^"p_8UD\C Dn7qM0 """wMMu$38pGuUnH|ʕsa/vqM0 """su|U?W%C>A? bޗV]Q!t%rNo"`4lm 8$so̓Gp*;;,o 7e;火r圞 r'l %|T!7?xn4 SYnrQ'ɡ-_xN012 """KJ}hڅLXHpݽ'8:ʅYL(ѕYnK3-rN'&\ "@&q  Z[÷ԥCVt;d0'^8xpC^Tbgk*uurj* d[j &BDD$p;5#?g*pC^J; rdgP7$'&lŖvݾƊd\2(aYbڗ cR!/X ӟqh$c 6DDDokm!nW!K+{&ھIː 9ΎFȑ1 """CdGp*꿱(ܐ$vvTn̅ v⿞12{ojR!""CpH\w dXfn\8yú8&Q涱l-T1DDDrɄ`wr<vP5VW%)ܐ337k#Oq#OoU1"""Md / 3}*Q Iɵx}0&՛lɄ'fA?z]pC8=UH_? eMM^BBxE*,>6wy;;uE !xpK.H %vo(cW*1%|&B;S}3>z6)[y1#_;p bWrnDDDLJD?L^[ 7}6>Te\od.Il-TS!Kml&ƫX;pcg\8?:: c'n_GDDDmuv̔h[۝Өae*l|y85 c.""""ykuhҹ_3Ӹmwn,_9gx?~/<~嫈H&޽U,8>iX?Lخ+?n,%l oG0i| hVi|[xUS !K"3ljƳ PL.و ^D ]x϶J䪕*2װ@%1r헏g46 7\q˜ĦIY&|8q]X DEDD w_[{Ň,|tL뢘ȃH DEDD=XU-b%Qnl<4{}.{f(&r5hL<ya"zYڝ<ń4始r@ u(bNҬBȒ9)hy(3|Xs题Dƃ^BvcG;Ȓ^ƲܙѪa 7H.mut.φ\Vm/KD<؛ShdQg{Ljlbs},ų :)1cDS're9>e<^٨>"""8{ %.2 nIfFN [n;r`:́GSߣ>"""xMN|E*lw|+nܼ>'Q~{= ꢘoiV I%j0*KG|5{K-&vvJ.}6<8a"r%7yTI ǐ1fg;;Ԩ.G)0\qǏuALd%sM*sVHT6OJmIIʶx?A(>fr6i9,}EN=nHz=ߟGyr?xa] Z"""oZBH}}e] G %vvT QgN肘ȖZ-G̹[+UIh<{D"kΎE9x{6Od<أ+a"EN+S۾Hfs}k TIg3Y[R#nLbgG#^ k "7XBD54dm㾛(V I;~GT֖odM0&Qrf!yguALd{s w\RZ[C,OfmyU7r siޓS JHʽ75"""U!K_ߟ>-hFPagm d|QnI] HV{t_"2f5lnPao#`\WDEDDDrmH n^֖4} 7L:Y{Jo?bH.Z[]רM:}sS:;;uKFKpޜs蛎{u1LFQDDD$W}mۛKTI^Sٙi^|6CYp#]\[6'ĵl-riD˦BHq}:tGFK`n9lgπ}5͂H.k*wu"= fN55۳u#c!QuM-[k Uy7yaGz}'Dz2@v(Q{<F1'w\UBi{sꛎOeoP;;[lH??6B7a2܄ǭ"""b;uԗ:TIã!~tl:+e~ 72K2+{&89օ0׬ UsuI,RKFf]5|臽&T_v."""wlP!$-#|l^ 72$[;F ] ͫ)VsQI~=Vi!sѱ Eh.Q!DDDDm|5*t('phF)ȀΎZdi7~zҧ_""""rǕ+Y­BHz&LwH#K, xC6߻99&ftUcfma i' dК& 7Z~Vӡ8>]y1;Knm !iE3}J/USNon`s6msMNBDDD%|ͫ)tF/O?מne Q`i37k#㺧"|I}!"""r4;諪UI=eȟ(X"gm do)b"jJZY""""Ꭻ-qh1#?Ӈ 7N;pK6#<7+`R:ADDD;-""".w\UMS:x?7fO|TP'ǦuLJ6DDDD篬V!d }>x~nOfmn4iüL?xEDDDk)vh?:el&îZUOFzdm/N?0W6PY~%׿aˀ/ʬ #}[7OFݼEDDDmԗj,'d~gףƢ/+>MYː?+`R*bGs !"""۬"Ȣb >ۑLVEσknAg"F6%45*\+UYO?5/0"+U [ސ4?Ӈ}n\e5؟ZEɰ%lW3wI?AogoԠma_ YQCWJᆈH6{S vj2`;daN3Qqa2kc6j'FT}{϶J`/Nޠخʿ0*0kc!"obVk=EDDDt?&fd Áh&ާʿ0" |10h*w""""YT][+TYp/2}vV?75u>?2L(YfkTߩWdάnWM`ha&7#ê5zQ!DDDDr@S[{C'3ήDEI1FCir5!woGX4G(x*L4OfH7f@DDD$4;aGE ѱLRE_r͙>=0Ѭ 3]ۿ;Z"Ȣão!Ƣp)^O6fm)"""v4Y dq?!Xn<7dp\7-l-T!DDDDr,^[ X )xiNL_&wǕj$*"""4{C}>*U\ρICafen1ސ xF7S^td>Unl󨑨 h.aKm !2XwB_\pU2SbɩݴXs"D&i 7^M>DB7;51=\z">ѝSUy/ A}QQMJ51!ۦihP&gު+x!M3}Џ_6<*Iq✘3f򐫀^w 2k bN&qjIy6nmT!eh^wg+^ECD M0;""W7aljbD"0Hdcxx^"x/5 n`QRRrRDL%H  388gjjp8L4MqxAAAX,<a6nHUUPD,?q !$HO7!ϫdU>ܰmS!L* 222}}}0<u^n9 LLL0>>N(" nVuBv;Z)6nHcccj H.J$b19z(O>$G`0`Zrs8XTaX~3 #5v&g$g}b1V^ 71 ƍm;bGZF;kn)^d~xpC#066Çygygbjjj}A y3FS$ |>LOO399,x<ga8p׋O6-n$&ctbXp88b~Ǐflڴ/z]1S!,|CM:]pcNc Od>RKRL0 bp)z)>LWWWih&=jB }<'FbLMM1>>g||mF[[%%%vܸ>=Ok.=. EAAN35K0TdߌxhZZ fxDLNN299jJ۱j*l6pHӶX?=>M"\KSnx#oڽ|p`p>xgTS<ӉHaD>%u3 @N'h4u# bXf~ݺu466fda 3Opa~?#n6 333XVJJJRGr0\$Z,B###D"0l۶fpSR\`G\ݜ;cuFxhPeaJnK.gϞ=<:tiffflru?XV Xp$~,# 9v|>^/XZRD6A?wGСCSTTDqqq@,#*L]]]jK$0aI$mq(b``L@ _LiiYr"nAr(d,}jc@: 󉇆 o\p, <<;vah;2leff&։Dp8VO"iچa0;;,^h4 Qn.da{n{1O vvQ]]͖-[Kضm---Lee%DQfffzfɞY &@@ #HPPP8wT (֌n d9X37 7yBKRr_$abb㏳gϞԺvɷɥ%ɷ)**bS7PX,N3L%0<>ի)--e޽>|8Jr[r m˕G?ӄB!N'eErmUzA)l&b)?׭-!w,/p#KRf"<ӧ;hII~̰~~iMt8p8 X Nee%UUU^P(ZK?~D"AQQpir0شiź@"8:tGyRcaP$!Hb jkkٶm7ofTVVRPPʕ>B?pjGca8Nb###:tՊᠣ:]4IMNnG*\<Td/'v2*++Yf W+VPVV5%M2payN:M57`ǏcVEss3eeeEDd)LOOsQx veeeaRKh)hee%mmmlذvRY7v;b )++c޽LMMSb0>/Tjvri\t[{ `!Oed|[v,' Dn䃻c NUHNqyRBmj%zZձe˖Ժ6ZZZRJKKxyB37rSrmy__O>$O>$CCscӢan }<-[pW@CCCPUUj& 044D HMNM6,leffib6mVo)E$a8{Ӄᠬln'x=~8%%%e˖TQ+XTH7NގLrrrjc+Sw7 ]x3qvݻ|$ R&7p`0իy+_ɫ^*Sӥ_.ŒZ]ZZ  299 333^`# a`)..B]HYXR۾=z4ΟFvjZ[[B$xl011꿑{ɥ/Hx5 HPe4e9_;k*OqR" ٷob1VkjF)]$ajj ֭[GGGi;J6mD8jbSSSLNNRXXHAA5a011ÇYr%eee孩,Occc<38pχbI͎Hɭ_(UUU\r%SQQI\veLOOOk$ cpIO<fQRR" nlp B[<*3Íˁf͠>LDsRrwÇ(**Z`Wrw֭[/i q%`0 }100fv%nD[Ӎ7ꢊ9y$wԩSX,S]l6l---Kz^d`` 5.'d(]TTD,K5_.))/I#r!r `X&Íekr 72$%n$)}zĖʝ*DI;|0Gaxx˕qaFB!+V`Æ l޼%sQSS֭[B!|>pߟj\~bۉD"twwcZ BLOOfV\,"/K ```g}*'ǜh4J H펲uVV^p8LN599I__SSSPR2w{H$lq:t JKKnHZ[HIW3 7جY,,p#KR~`&bӛ'v4kIJ8pa(EEE &זa*++immK/%|PVz, >ɓ'SSĝNg-jAAa033Cgg'׼JMett~CR;$w%9sƊ+Kټys*XȄ.2fggyG9vX,kKrv8uٳիWsEi;O~ܧByymck5,+VpdO(^/ B[)))7Dqqqwi*--eݺu~刺պ`VfKB;v*n7eeeuMNnѶrA~u̬҇L x;|u}d|I<=F$Ч6OQrFN Mzr9 %%%lݺW^:c7əK/l$՚ž}TSbya~{Ç366F$b򼊊 .2.RVX- l޼7RZZJ,#Sgr)۽{7Ǐf"Cs9:X"̘>yD6rĉB!6-59H`FjZsmm-^z)7nTfɺuinnncFWjPZ)**iNx<۷nxj9JW<'c٨&\.W[T;vfxX, eu"sa|I Yqt(72: 7L /Ij,OQ"3Ǐȑ#h_[vTUTTe˖TCS0F v2HNv8b1NydGm䊞z);a,OڀUUUYONxyDFn111A$nn̿D"\.hhhדH$Xz5\r k֬J<_{ ̭1w\#w^&&&M!Lb1brrp8n?kxXPP@CCV >Ǔ b꽑H$(((v3==Mgg'ǎchh(ՀT$[ !-n$xo&J9etIʱ~*-I SSS;vSNS3}KJQQRZZs_OFKK lٲJBv;VOoo/g޽CdztvvՅ[1&0;;Kqq1ׯ5Kqq1mmm\zԤgٰlbTzJ%4E.ԧ~u-a=jP|!f9a||Ǐ344b9km9c؟ 7%KR2Ғ|%)!1==$XT7hNEEvsqޙ7WfÆ ][ =v~᰾IDYFFFcjéjl6[ַ>EEEձj*4MJx^N:ő#G7dȅƁnܠp<2$Si 7L4" 099I(b,Z=mkRQQ,fllp8`N8A__PGF3 x s蒔_iN|I" `rr>8xCXz5uuu y5.vYv-֭a)-Ob*_|2ϕɣ-a5X *e7l;vrTs7ʊ :::hoot󒊊XnDj\_nXÜ|fr1FFFR$ǂ3l6ʨ"ojPSS֭[inn 5'<匿PL x^V7*]NҧEՠ>yL6?{nN8Frݸab1l6444vZb6k׮M5u:D":NN'333HD38D,8z( %A<jRXXHMM ]ts8444DMM D}>i\n3޸LFj8է0ϼQ6)322Boo/ӸnR D-KjwEMM 5556eee^^tkZq|ɗ\`jjav=&%vʨiF/bb ^p\lXV(> B%FGKSρLn5yw#ÍWc31gcWhփ Xֳ>FQB%%%YnnZ[[ٶmFg'qxgMHLNN2<<333X[&P(% 6`rss36mbʕ眹 }raFGGMهIoGs L.on[%)~XZo#[}}}LNNbXp:QZZJss3uuu$ V\ɺuR_[<_@/gݎr)\$ΦH$`bHMMM y b ."V\n?gCr\t8v&&&dhhHdnȅG3v/7}7-xSB"K&&&8|0izO>藕B]]݂fgX(++cڵRSS&iN8Aww7^WL"y0>stwwSEdSh4J< 6ܜWq1LKK +WrFbgpѣ0;;o*Q!`$qܳؖF Q}*Etuu1220Me;jrQZZJee%EEEy7<{spFSkGGGS[%9%wIN}n$ L3y<VZESSp8p:B!FGGezzAAdmcKm !ۙ}}7I#徽cٓH$R`Imm-\͖ӰN'---l޼sH17 #ѣIs@.=1|>q[k i8U9f)Oh5杆2'Gz{{X,\=kfQZZJCCyKʹP^^t`rehhNLPcQ}.FPL#塎:풒 p'NOs) r-IO(,,҂yBAA[l6vH$CCCD\$9rl6ۂpbhnhll6/wHy1.+{윻I%Cd1227d܎A۾hU}kWeuiC֒LLLLʼn'pvZH$̗JCCòb͚5XbAk;b1&''%h $ ^/SSSxjٙ6"Q^^,L0(//Զ%gA(z1 CdfnȅF027ToW}L:/uuj&i9rÇ388H0j\$ȕaÆԬ夺M6؈N!IɛdsT]n>O?Φ>s䲴J֯_Occc^/{!:ڨMfݤB###La9?1#LCFv9k&F 䫭Zi###9rA`n 7 ꨬ,===D"l6ۂͩ`Ŋ[[R__Í2YesIFGG;H1 x ;F0Lx'NرcaZ%%87~zu#HCMM 555zp8044z"96NNNɓ'Zk#" (**bS\>Qgw+WJn7xQ=^/NbhhH$4ElH}u)`^R띓7Í+Wj]GYY&TdjjJ[ŠD"A8fll כncgRQQwxD՚O6+WL K1oiiEwMI>@ 311A,SP$aKww7{C$*NI6lVuSkb 4=׸8;;CCCLOOkL̆ 7XF_UY5;Ɉ~_PL?իC&cFFFL-GINJl6 (++ݮ3P]]MYYYj}O6 0BxԶɾi|Y?lnj)//WJKKD"q̍dxFLMM155ڙK$SSoNf4ܨjndNFm귑6!Cb?~1l6Ykˣ(PhhhH+XBmm-N3uc>kdbccc8qnE$tvv2>>3_rEEmmmlܸUVt:n*֬YCuu5V~p0  X,zQSSÖ-[hiiQl6S[[KII9{o1ɢYN8Agg'.|f.Lt:ǃ8klaZ|1::W2jkmfy{˟]p#32oc,1Ci~tf$݌%%o Ht:^0X^`Z`ժUx<]S[,8P(U<0btuu M3N.#^łzƿnCEEY[Mf1;;HH&uft5A`/')tK,Hρ#Hsu$F֭[GKK 7^bPWWGii9wZv(x^fffT<,BtvvsIxarrK6MuuuTWW5n (țRkÁ裏fg+d8Жr̍"Tߤf|b=ndL2ɰ*BԂF5αXh4JYYk֬azKrrÑjw撓0255uu"4`0NbzzӉ:x 7JKKSex Jq1 1118@`uY{PБsr*Ue{9݌@7ORZQ`K-lj'fzzwbTWWeZ[[8e*..fŊaR7߭V+6X,F 2==E2,044DOO9{m9BMMTYYIKK +W<)e)hχ%hLҲ9_128L7*XZkOX~孵U)eMMMqQz؞/9۠FVX/SII TUUf$A8&TT$ 1==M"HmKzXň㔖@ee x懾ŮM"H 8$+ x>͠- 7V}&g:-IYJa0<<& ֕'ז'QXVnTWWS^^8kFh @`0C$'N`||ଐwC`ʕPYY<PVVFQQыzI~^Eɨjl/@&Wpc lz:,uzaff&y |xkl!.5M,//cmm a0x3`gg[[[T*0Msжm\.K.aff&0 &.n֞t: ]1}({@iRv$t8 mNR8(a^?>9,d0'R d>Id: ~M`&JR"ja{{JnwhR b 055n Rx;666P.ٮGB)]?y_Qx,< >.D,g2`}}=H wbJJ,Ο?L&''G#cqqbqHCW۶QհZl!dzJ%x"07BD"Ǎ7099IqFNFF&vvvXFB Mɻ?>u,+70k8&2YFV*ƾF"WUd|P$P( H󼡓JguvQ`˰ 9F*J`_;6$IB$AXb_H:,2 < * w{1نarbN,+y r))Iܸ 3οfQ4 o\LI㘛 4_@" &&&6EB-nullleY2jZP)01==`J eLLLˁnbsnRT*q$T8 k ;eqJLEO#d*$4\.Rlq}{ǁmH$F. 4 bbqhj`7|ZEbO1a*2 À{*7!F=N1==bH$uPeZbkk F" wa;w¼3dz|oDƫWv uqw2y<.^I.貌t:bBX,}$h`uu5 u]elnnASR<nܸY=H$0??)q(hsr_FTHB781 {<¼`ȥ>$dc`Za wSR/G?FA:.666rLFE\FZEWTabbW\ +㚘fJH%IJ EQ`&*+7Hpb yW0/wc@!=6qrf>MӦu]lmmayyR P u#Y,.."2OiLNN"N[! z0Bq".^D"v(( fD"g;AS<σeY4%= iSR6" a\(P(*c"`~~mvFviR .e+ݦ!7|Jƀ,Uonn(JCy˲8,.]K.ajjj'y ,#JX,bnDUUXR44jW^77LbzzHX~R)̠P( w?Iq0 q(nP 92ˋP^ýAh&z)&ؒr))fsR0h$zLLL0hL:42LP1XM#چZVVVG!bJyd2)d=0y ,Cd@lVӐayluCM-)n2f4=d"L 7a`{{kkkh6C155Wbbb䒼&&&L&Iaa!uej۶.2L0Tb-_!t]Xw ]xU 8uq#T3QѲ;\GLERA\d~Y4r9LOO3)ɼ ˫g#IR3!!<666n{EF4E&?~kݠjJBq MEɻ\ Ub'ʍDmg 9Ax8DVmCɼ} P(`jj h!cg<xrP1 uDNJbfHRPepZ-[Iya^˗/=d[UX \\C""͢P( }/J(Xr",BMΟ_qi7B7]|:Uf 'rh4 l!dihD]LmJR f1'gk^7Q&APسncL|e9[H$]L"丹;C/_*n8Q~j&z>jǁy9 'LVVVn6q r0C'Nc~~b1pߝ=lB8˗/QׇlUr9 d2nCm^Deo!3!aA .| 5XzFhf ]aSR.TU y zDLCZ,~ ?uP q]R kkkAq}ض H$099L&êX5M;p8hZhZ""'uqøDoߖdiz/P*PtFȟam&`(ygM+@=SRQ(BL,C>&^D{iARim"ʑ8$zC?(n#0/ǗM>c@{K[QTvPgc`SBDmeؾ0 TU4M zh&jRhL&yy!N177\.8N !nej5D 0pƻD-%4V,9:+׆ygg/_D\ !j$Ql( fffJxh4yh4D\3A?R`yyv@Nj,ōwc:̠ulXldneass^B zx۶8fff077Ǒa/T*|>l6h4l.*˰ 9"b}}/_D*7¢! ajj bq' O܈b~q!$,Q¼A%}Hq8 :1bn1d$*6̦i¶mi;wu IǑH$ʍFŠQa4M&3VׇC!nq.RQ(@4r9r9R@}eY, Z-Ra }CeDF4Eqc.1&QQVj ˲5Sp5LOO3'D"@P8p:.&Gضfz~eY<\ΝCP`{ *2L`pDD_Q!DFmj5$t>e;59"_u8FI?.5D L%!Jt*Zǁ,Cy7`jj .\@Xd2B$ILOO#BD^| NFTeчjjP *7$&''i|D"$ bQm3si²,znVѵC]"FEqc0/$~\1ʍ_yj2k,#cjj H b:jY*,B\FRA[b|6$IfqHUUv$I $lؖBFr?Ĉq! ;s|IRq\.c}}F#Hw'tKQiLLL)&FdH$ IҞj{۶"BaERA^ip]wrCpvh6AI=!d/Dv;{3~Vn?4d2"@k0Z7v1̱rQx;üj0&$4dme"bfxp躎T*d2H$Y_Eņy0MZ ;;;,e؄E@=}$I #D:#n % 4X$;Cq$QFQ87ƅ_/0rjVuLO+%IuN9ib199d2 UUAq@F`6!%P ˲vaPUSb1r9$ImHdh"% `vb0Fh|S0Fm%F;;;t:AwPy4MC,cn;棄]SߣXyENS ihhZo]C 9>9;9Gҿf4_slKit: /.ѿ$8vm*|1&a¶}M)Kch0rF"8Lӄ8A>BTv]lmmT*K!8mFvm)BTZ-HeÑH$099bd2T 333D4wDx wp"!a38Xv)9JFc` nRo.$IB$$ `up1hKyy0 vl-%pSNK7jsCBUUR)qh|nZ-`kkzDd6 Ǘ0/Qu>mòBcXF4 Df#H2t n{vFV28ڂaCBitlIQDa}'2 r"|0e[hj?r@)<)ecl|ȉnG!ipCz0vpFȸc&2JRЦ3"D|>yd2nDm:Umjq,91)n쨈I<)ڥ>N_.* VVVPTl.))d\lŋ窪*ABVCрeY7#,˂y8MOOrc?id2}HSoBqrc2 CءR?2&1b}?۶Q*N Cxc XUUa&Jjѻ W\KhڡޖQ_-H:6Lӄy/d^c:4ġ;2 nwoL&199 aSg8j*+7ȉqe[=my("n ]-ߊoP,e4CM"E+~Flll( rX%Ӡ(rf}G{w,<ض ˲TH,(S7@j޶IܫӡA[vT*t:83?E"r9E$ l&е6* +78* vQ185M"rE9 oP"ܒQPRFs#2DȡxVjz>!N E'2b&''y2 ߀<7\ׅeY,dq[[[X]]E݆i{ ,)L"M&s011q!STUe%9Qf81jV}TRa]$ 9 ¶mضVMӐfQ,*7b1d2r9`ӶڋN8yjvvv`ƾ`mۆyrE:fFwد}k<+a^.9J(<y~wOUU8$!HP(`bbd}8|+MӠ*zö!oj(*'1;1p#xw~Q,!įKE u Sa^ܡ8?p]j5-WkMӂM0mdYF*B6 *7{I88Nuh׋FHRF9`ԑ$i*BF{m]ÊwM777+4aQi(dY椔SCc{)&!=DޠggDx>b hϣKF]H1P$:&еh#ͱ]=[vD}d0==\.ǓӲ?O&8** Z2(bh\.:.B}7Ir9;#mki:]7ub=8/Ncvv|m)%L 9y, JR Z-7G1`rrHxzdINs-2RB87 ü`'ɼp],yTUE4 Y2Oў˲jna&<۷}(t:B@ύSC޶.צIH|4Ê02R(QGA8jd#%E80`۬:IN`S" IQ6 M2hXX/~I!'8yNZH"2'-t:u4=C$AuD"d^l!pџ3RӣzfHsM<%!JuhʠylAVCۥ691&\_ُN^h((˰,OeqdY_"v$zYATZ֞JH$x<MSibH$b4m*G|BnZFZAN/cU4"(t]4rcJ1P(`nnt;%X,H$lɼ0 i2'cQ*7PǑdXqZaYF:FXD.C, P|za;&91&P|7Ȇu vBނypں8$IB64"(,$dY _qjl6aYG3I}n{b+br47N $!HT$ N$ VlɟeXnF=p?hynQt=ۃ uaYV ~r"8`Cڡy% F8O8| rhZ0=^da¦(}fffP( Y*lFRA\a K6jz`,rAύ!&I4MC$4rܝ1jOCmXYYA^-c:$RwuT x<(ޝ+tt[NтMj!qxS&ĩ)?L =NC?I< `[bQ:1|0SREA<G:f!LjM+G6L&C38l!+M`666 48BH_ rGGƂW; !xZt:J$x,#"ϣX,R I.jZ1X!6ōSt'ٍ(PUib{{RD9(9xOcUAƗ&%uFjik;9>tJIjj6Ѧ {/"]ǡiwJqi²=%I0MiW Q]1 g ~ڄeY 64M/ZU(2 4S0_DN vX[[C\a}xn4MC>g)GQuI軜! dcӖhO0MR [[[0 #(`6G<gNͲ,XqUB愌mcggjp0 LD$!X,R8DQiB CUU IP=SkB>Id2`͠!ڒDJ*^~n ]ׇN}߇i$ qfffxʉbrH&Ơvj%B†!%nڬxo'nV!^$s"ѓe|lK9(L&bT*> u]4Mt:N gNeض8eSSS8<3Yۚ'w!znrrr]$'lmp „h)^PUt333q)F5%'>z۶aF0"h4x)VVVh4.4M x;!{I4B\.cssaSR\ׅyD"d2d2{DrzCiB"ixJRp?z>4MC*B>G.3b"}qNxUB9yNjwxH`f6klll4MD"=I(d2d@N}. rjp=FL&LNN"pMӶmX)R2B06?1F$jDلyCm)ʲ,8d2 _d( N ,h45X54IHB `mm ˨jCb**Lӄ8HӘ#$!!M/Ǐٳg$ h,:4M$!`qq׮]"qss"OYZ>jھHTQdY?l#l^Gm!NP4srBz q=mH4!2vce=dlF<41==MseBȉ9AN(8m&o\&[|ڂ(PUuT^~u]doo~?_85jayyϞ=HT5 ҥKOqU$=d`E9inOAN-%Ĕh[L"ٳgxA4iC8\ׅ(vnܸL&@a.ڕǁ8%g۶W^F~:>SLOO3g]y5N9)R:2xn0MxNoSj5$ D ,D"XXXG}۷ocbb7"2TUeY(JT*(LBN;F>Çh4{,,>CܹsFg5W|qCT8pC =0 ?ݻJLG𦷼^> x"P2/24MCJPOYn˗xe }r9czzCg6O 5a {$o6+ݷ]2 wlV૯ǏtLJQ Z4>c|(ѯ0M;;;(ˬ gf]|)Bζ1??UQömz }?ٳghZ$iH0 À(8iH$C1v`rLPvOٽ1'𔒜JqR/Z-blӳ, fd.]ݻwQ(1@irz7ƀAԱ%#QBvq}|t:SA$m"$ d.\k|gLpwX,ˁi0 cO !zo{ +WO?ŭ[d1ZYFN 8 ƙ$I??/XYYyH${JQVaYG( ,d~]Tk zz䟜Z?~?PDQB(f֭[{ܺu lvhD,! LpZ !mMƆr<8^Z⫯_ܻwV @O! (|vv} \pƘ1`+(J0M30#dg۶a&ױ/ot:d2WԶmȲd2˗/㷿-sss6!# BqSjQ#<|~-^~N&䢭,D"y|'qeR)0 4 4 t:`0!v˗'O^#MbeYhf8w>3|ᇘ2!rAtCl2]ׅmh6hZX__6ݻcyy,Cuqh8N߻LLY\\č7wA> B čp0`&MI/AՑ(:=z޽{@B"D. J Yp5|;\x˽ !!.kMSt"!ҲS<fR Ϟ=^xjMj5(]׃^r)ɻ8p]p>3ܼy/_Q9}( !'|Sw]KxWV#b{k "B{lua; D_qΟ?0q]<C\>6BN6!!/ʍXg1jnccckkk>dIe À|;j*Q*jJn WkȺ'|l;ϟo~#LMMQ%Q`b: OUP]q`* ih:H>Y,Br y=,u]4;4^;xG@$8ư  ?\,TO. D&{?}>`&:LÀw4nj<فmh4t: Mӂn~!arr}>qy/} ǡȾI/I,{cx\5QPv4av6v u!2.F0nT.\.c}}rZ BQUc1$Gua{|N١H&J7G>MWđbbH9Bo]$ ,{6?_r{^ϰcxL~=7چiBK/U(W*,+R(2l6`t:7{{}#74`4i۰'=jJz۶Ql6ѨvQ*PTPz/Jσo u$wσw] yܼyw'wb~~~:H(h2MU{t";C`GSJpYQcXz\Vua.tjaɽԯvh׻Nzra@Ql6qv{_DzE=_M@úh$K8WO>171\%fwy "Ln U@V e,$MnFj D"i61$*> ?{$ p](لǁZlBQjj[גp IEӁjAskdBvD 2Nԭwo_|Fo0I};m8FxʊNFVNR uXjzV3mUUŐJ$' MQ033W'|su_6& v>$0 V}Z>I{I%N'c({|7~ϊMC6־e*aT*% i67qö,Z Va nC0ԓ(>|IZ5]i~Ņܟ#0 vDu)ÅV ;{6UfaU\P*ۦ Pב1 X *oa ao6jpWVD"=|wAׁhկxo)JJaZhT*(h;J,IPe@޼VxH?a6B~rBdhR $woZ_ u=8Me!cHKWE,ziY7W'B$/{+ǽy!n ƾVd^T ˽u_N8P^y5zn UITׅ8H+΂͞}8B\zO}MUUP^0yp},# [*!nr7(> Y}^6Qylj˰m7yGmYwp<&'2$EQI>k4~bdYWXYZ/ryJ{OY?U,LeF Zᄑ,=av^(koB~&L: `}ōP-~!4߽RA4hU$sIUJo:hLfDbI3,:l:ysg/>o(rW6~Z+7c41n#7lt@`ryb_#]$$a%IXw:oknޚ _U&Z:Zuf؎Wɘ`HTI(P'z%]'o~$a9*M7_ ){2ކ/UY1v{+vHq0 X;o#>"7~{01,,xGrKKH`)R4JNkl?:C-ޖxQejRM7hI0/բ0'- $}SBϋC}/8B}B@ (P5 1x}BdCux,T*x<F9=F}9\f$2'F}CBsS'>ތShMEQǑLDo}ʯ%qio\޿[֛,=%aKyXm<OOr?!xN-3Xf' \xqn7 p<0;ۻ {b/$ ag{[x!* VtNXaAӠ245*Jn`OX:xx5Q4h֛F]G2B<@<0i8 33OO#@Op4WM`K9ׯlPDhjH}mNÞ&'Qzk1lϖÇSB$D"֫}~0F_TZ- L"L*r,r<߇ ۵,t,+StTU?FdIB, N#{dEA2D&A*m q$qHwX ,# "J!NGwd&GIQ.4ǁy>"e}w8qd2;!<}Fuj" j.LUj6<σ(b'Phdt##@堥s꺎bBM!("Ȟ 8BF. BNͦv zR(/jLYF*F*=?G*B:F6C"LuDyD2hD m|gG'eg a_6$m0vt:(NLŋǰ'4<O*9'z ۽D"h)ׯp͛8|Ei==MC:F**\}σ? D"Cz↢/UBX x_Hy79!]}-so߾Hiڐ?8}@U:\ǁ$uLLDHHEA",zqz_HO 5,7֧kqdWBF |o7<|/jO*GyC㩅7u@4hDdHrf4mX@σi((D"fO X,D_xlpEQz'ɱ/=QO7޴{]$ݻ?Ox9VV`6"MӠ}<σi9"ˈD 1;3$I$@tUmtX Hg8Z"ޕ>3q03] 7X/[_EA< hqdzߚu{d2T2$AAYW'O/:3 V.bҶ7j}qð,tmy@Dрl^|-ܻwݻ{}-6Hx$Aoǁa, CO 'AY$A<G2D.E:A&,RF7 NC}O.ZX% OI\^|eV'E݆$IH& xyIueq]h|>I\r.\˗199BD"TB(n2u]G"D"P,=~ߤ Pd:zsE}Kx91$σޟ/i>SѣjŋW_ŋX_7F"D Bۅ8Pɾ!A|PF{e3dY^A1s{Jl%Iy3 Mb>!䘨w?ÇV=Ѷ&{qnalF&sp ,,,… D1үq~d![79!Zm?=?%!,܇)%4ra脲EFGXZZ—_~o? T,D/m۰\BD尰Y,,,`bbbLD«BFmsp]/^g|7hZIg4 @` LW^G}> (͒O k3P+k(nrB7w2t],--᫯_,//hZr˂(¹s簰K.an~SSHR)Pt'2mcyy}>|mLilF݆m}SSSr ~_ƍtR`5dz PW79^fN9t:o_W3LNN"zJ:+70:AƊ: !Գ O9!'$muD";w~)ܹ>͈B!tb&^zӧvFC0 Qb}׮]VslČږB!0{1K$1:UK`Y4^7~:&''SK>|/^ZSQ$I뺐e0 @6ŵk_?G:9S(V7ߟO3+d&Smxq0l}hEQJjvD;w͛ *;!Fxgp(| z;E BY:nX,۷owܹl6Ka9Rcަ' BNV*޽{jJ<7@$a@e;wwQ,DBQ ?0 |7x1L @g>LӄyFXXX_|_טa |>½^@q]c9 9?jD>|\ERǏ~i-˲`Y#ϟZPBކ1bR(nB9)%nŋwVVV`&TUE, n Q(p |裏fHB V,_!(g{{_5k@Qh|fF|6d~! ?(Ls>y+MƎ 2۶ 4%~BLå\.~w}RUUAM²,Z-h;wpmz%䈤R)brr+fفmӴ8mr=n MӂPxmt]ضX,x]v \k!9$9E);e%\\$qP8ׯ_X^^H&PUuжmxUU1==_W)#dpy@=梃y^cee7N/_*LӄiAD,XXX͛7q%i ̣{7N$9N 4 dlD˲ M8* vvvl6, O<~/^hRl\ >˗//ݻw199BN4E>G:2<07 TU4n7ڨT*Qb:ʇ~78w+Xpk**7zTi >#brrt@wm^3;RMu/R 4 DѡlWy\E6'|<HȻ&Ʋ}6iJZųgϰ 9躎ܽ{nB*bXp1ȝt`+ݙN'ؖB@$R) R)HZ$(unj`&wFgϞdzgtFSJ1 nq |Opur9)pKqGaii r8g>@&9XuQyK-Lq$t`p,!CD"$ ~`>X%pS5W^ p!(أ IRPA?lɓ'P.Rxcx.LD,í['`rrgI|ӡ|?j?qgBQehaیG0 <}?#vvvy^PA#>ul6w?<{ !gMܻw?Pk5 iZЎrͱ`Fd],d(whE Rl:}<<~:?W^q$=d9Jܹsq.]t˯ !nvx%~'  Y Àiĕ+WpULMMˮrFygBƍ29j5<}O<A$ D IrYq%|駸rB9ʹm7N@2PQIzAk!2ȣmBNjѣGh4PU >l4H$pm|駘)%!Ǒ3' % ܿϞ=au}`Z똟ǧ~7n 0dȆyG1'53 MyvfٕX2RW^agg$!Cexg( <._ . 0z( O^|r YQU)|2]Ih g2LkcpG>p:; A d w]70hc?`ii jC kj}>KFYA1 It]G,C,iAF : MqtH$?"4MBax6q8k*-9Uw@ < JoQTunH$+Wƍf !DŽ(bHRHӈFux_N'0$)#ܹs/Mh\MOG1DٮD]בNH$L,}%|駟-w]7-O$x" | $!H4E.CP@"eYp]cF3bɣGp=,--AӴ=ӢlۆiPU333y&n߾iNaH{6QIVn*3J& ?8XBD"_,f( O(˲P.ŋEQ=’yt:e}v0!^/ Un1t:X^^ƣG0Ꞷ;QFqUܼy !ܜ 8Qb{0-e|@*jA?r}lllmȲ<[.6777h618NwR,qm\~L$zMF5cBdYAc>!i4?J"wvhׯ_C4N3&k|wف(b3 t}>3ST\m3$I)B,PxWNsHN?j1ccmm ߢh@u$°жmd2;wΝ; )VVVwayyytqy ޽[n(X̅ES>8Zr?0xKbe3mh6׿?F$ \EՆ7)I2 ._O?W^E,c !nMx677ǃP@UӸu>C3t2-}ccq-;a^)d7d!jٳgx677a6t]?\uի8w(˯ !gJɓ'h6e~Bɶ=ڵky&&&&@B-&ü(ǂ.edY3_~z_iDF>ǧ~?<-',m0$I (Hs|( !oa*y($ōHIvso B;;;;x1֠i4M*&b}ϲ0MiuS R3q:Sq] ?ƭ[;HXS ه-NL!2KO0MhtQ9B$>cܾ}LA$dDb( d2eyp!24MeYATa ZoO>8{*1D;നO>/^(BH!D#<8yqc3̋<N 2C}=oPu… l}%=$Ipndׯ_Fg7X}<|:r)n<4}X2!㍪d2fFClbkk ;;;m&VnHq`Y,ܹ۷o#N3!J~SSv{GoctݠchKI$x"(#?mZhx6Tq{SAϘ$LrzA c,ˈbd2A`ب{fMMeYXZZ½{ xx<\*կ~k׮!J&$dEMӠj}O<_W<{ H&XXX133Mw|^ կcRBkM.t3 DDdILU> чh,a@U=Eo$Ir2 mہG~K1=JQ_z~4 ht(^=OQLMMaaa0\d"ȏqr в8מr]Lm7u8yq`' ",%{n` m'oƐ$)XY/_ǫWѯ,1m<$!qy38X[[ÇUU<ᵑJ۷oc~~ĥX 9Ж8X d?q~[R*VVVP It]8Lӄ(( 4M!nmt:Mf(<&''YHpe,F |rrt>}'O`kk aIF&-g"O9+Z-,//cii reA=Bm4MdYܾ}nzH/\ uXOCLF,T+b^8XB;Ps@]"W,n޼+WrVxu…  x̓qwB%*GAܨyl6 !G<XYY3l3<> s8!rõ5x51,d2pBB3q&ڀp4e~CmKbOكp<2Bގ(X[[Cр(FC^¶m(\.)LNNBUU`rW"۶ׯ_ZBeCcbbC UdJ* 4eVzƢ<`r5[S!cYVWWod8/ҥK|2< B bDMrR _*P|md2p5ZU̍hJVX\Onrt\.X]]anr$ImhۈD"X\\Ĺs[NEgn8t:lmmT*q  ᵑfqEAc>Oyf=B!\|IrO)m{{<2lޓxmD"֭[8"nk+9 B6Jvvv-Sr>\pSR!g۶Q.ZLڽn ܹsXXX@*Ń˃a^i(.y,'4%hZDZ C7Ap]DܓBȩT.JRPɦJ6qfqU;wl7 ˜oü->"n4yL=do !PױJ-h|333f-')4)T R,ٟG;T !7ehnB4 L\drB>v[[[V>"2 RͶ:hz3B5,MS Sj;}aBHB\Fل3qB:(!gX,bbd2 M><[lMIk`\zIQxvY1gB7,81ʍ'a^PW"@v;aG|GFDՂeY$i)blXA `bbd2AՖGl6d<#}qd2A3##Xa^0SM&rw7TUE<G43r=2MR ji%؃$I*4yBX T h.6 trM|9۶JpyLMMQ=Zi3cȕ"D(V¼Q>)@TxA:N(0M3w(H$H&L 9#(H$MӂgDaY&恕l„5Jܹsb[1q1_Hy5N*EfL@MExL&1??I(c%mhZt:m Un EQŐfq `۶a,,D2/. L&"9FՂi-E!JH$B9i;~̥0/+u~PE&`6@ uaYX/V ;;;hZA FD"0$F^&*\n↪H& q"7~|i(Vn:6\* d(ː$)p'8LlG!I2Q.`^<G<G$a n]ƎP6?ͱLBojJh$tˑC@!B777+T*H]ׇOTn$IN Y,?RUUB 7abbl(ZN>Ky8~Müż' B uQ.Q*PaYdYJ+7055drhZEӦ{=i !TDA];p5La^dO 9/qK_zM{Qy(DEA>ǹsB`ggۨj0M$rDl6 .`ff1VA3LW]~5Br Kxl6R۶P/ cvvdm)3EA\Fل8~wicbb1RcKʸRa^i(<uy)ͧ [S*VWWQT` 7t]G6"t]g2O9S4 loohiT _\.),#zCqQ![kW2B,\397XH8>&l6lۆwH9hk x3%EQqiiJqjrc)̽jLMq#4#{2FRv Y5s]X SSS( {BvjV 6=Ma#qlj7|wc6F=3Иpb 9283$n$#Ie<$r Bș<ض 0BUUDT1S d{|b65c0/v!Gq] L9똦fFXD")rB 0 t:ضgjX]E$A6E&3>Nٖ2VWüwb6381_]N! B>möm8.\ׅ8D"(d2-'9|GZVP&xS011L&G&1A/΅6Lm+n:1__ "%nrFEՂap`5H#48 Bș\A4X& Vip?No0YSODCrA jJN#EA$A<G,C4e2O9s8˲`Y<Z8XɖJ#0FmFU}bJ.2:r0lK!l#ʯ+ lJ7ת"#L"r&i6odHLx1rX_}BèMüdBSDn89ضNL'Ry(drhZ@\a{*7D%,u}Hzx|ŶqAZƙFWB7nOst9rv, Ne= 2b42_yBYlbkkk!ث( 84Rt]zxP.Bx3 @-$C(!gVRVqޠF4E4=r<vOmz,c`"ōcc`LJy9}wg)v_0.o81rvi6F|4$I30 J! ?!%+<5:8Cu$IdYz3ueƄ|Y(WnlxfRd^,ҩijt:$iO.b1LOO#s,!Lb:Lӄ,umd;^6[Iߝeōa^p*ISQr8_fk !g˲v?Gb1LMM!r}fR NBӴ=# E5M Hr||qT4˝) ` &Ƈ34%CqGEFۅCSQH儐3$Ihل,V;N:ܓgv6'2+H`BTD1 0;=gO)&PAL AP]D`]؜fvwfzRn=;af?k_G<5SSyDvr%[m=?f⵫mhSŢA Tb1LMM!: ȧ&i'DDT*ajj H&aƖbbÁ~nKGrY\w_w4Zn0\On5|>h4h4T*b8c<d2񠵵TQfFd2ܐGcˣ`- 5m,"4A66Z ~' p E\ɟ*:!nM!jT HX lr2ܨyzpR4E$A2DX^/JF& zjl0clJn?\D-ؠv.cA886f#?̛fn >QÐ$ SSS8pBR1{`D0RyoלGZI7e~3wqDQ #Cs߸w扨aHI8p-zmzFMvs%[ sKJtZ*ysɢb Ɛ0::D"t:LiG `6x`63'"8Bx9ĘMT$Ilp: {khKRAE!4@1nt qk R)LNNbll dz~vQQ,aZ˩Dp$IB**Qw*L&vƮ?슳MU+}٨uB1`R{2ni_TB*B4E6u[nzv;.`||H$0 3F^W6X,lAUz ඔf=uQ ME6~*GǾDW,))G?J%J%zffi]:F$2Ly%lCΕlJ9)љ Q5BCiOXɃXSFžDږL& Y 7\* "v;nQ|t|:nFD^^VūKP"4S[9j2{(& ݷ#"iT,áC˫6f#OIq\T"j8z:Kpd2vtr"$K-)@i CJA'ƭ)DڕdFH$P,TL f3 zzznQEdlvpC!o㶔zl8zx?etBl*JM$X,"ϣX,WgT>Wp8 7|HD(GRn-zUoUpy%0򧍎+9f!40NǑdf}_zl6x N FH$ D!l %IŢEx?}l&.[;ZIѦA}c,ƔJ%ibأ?$ (`0npbai"j,$!LbrrSSS( -yGEn,^---\Vc1)u<Z#)TwNl@E Ҙbh4Q$I X,r0L,5|>L&\.Rt̿'o3+7jG$'42Հ~Śm 7^ mH Y" )JH$D"d2&/ER . \~MD Kކ[UmBCc<"4u6%jp:XtbB_|:DQ,7=VύRݎnDp$IB$0gL@$ $Ad2ffq l=y0"4O_ؖ #M񌧡sM8{<\L'ND"By+N+?00qDԈDQ$(,JҬ+7ʿ$u"FdǑfV |>~?Gؓ׭aWp4C]'E h4r`0s HBqLNN"HX,N[Vd2  2쭱 n8EClڞ#L]ͭ)trfTh4b{m̶ [g' 5Ht:ILLL`jj B`J6y^KK zn6UA 6Rk-)Í=JgH'i;t]!"DQlnїy$ R)rzN~&F qpz~%kpCѾA?`0g!4F~PY,~8 io6ykJe -(:%fVP +y:l©)D?bBՊ`0ÕDp$IB<qGzfL&k,LAݒ"?،ur booXO%NM!R|=?ږb0p8`0OD y? "Hy0}%$I00܎Rc\Ѹv:(AFRt*lH Y"d2H&H(-YVnv.&#I&&&Ju^Պ@ Іֈ:M5@}7\f~zG'7dX x\z~Cl6jh4DpiPEEX,|>nf8)1fAتݒh;PimVtIaA.$!L"" y$l%^ǃvx< S, !-Ep8fgRp^czߋk'6Enpk wĸ5HEDQDP@6E6-?WN$ R :. x;6N_TpM%~1`R4toH=2 L&Q(ʽ5f .AbdbAD '"! `0:-E\.r~XFhHgwڠMSoInRZڹNG$IHӈO!G("ϳxDpCʍx<>m=JAEww7|>Í⤔Wt*y^FHbk.qN nG,C(B<?ץR R Fv{ˉ!J%N{nl6~8NrX :\Dߒ4F (#'NJ<+2 R8KoT(0xQ׆MޖbٸM8)|E nI9QQ`+X :~I *+7O*znQ)Jd2)):nƔznL&F6҈gEaǢOnI(o +ys`: "r1Gb\pF"di^!ᖔ<T>5OJ|ƭ)D{H&'Gz#S*166T*pD6 ~!6m,V?'!aS`<7:iwn #%D$ L㘚*OM4P^N+7'"j4\D\nJ6X,Q*rÕl51ukJ.VyG.q$,]{<Lp+7,O ̺Z&" pK 5IJL&dӣ*RN[n(ʰ6oqt>{q|Vakuf<AN&Y"dy^1@~7 X,0L\MD y?L$@<փlz>---\Q#X^̩ncgj'm)uow- yH `jj H&r< I)D4-h#JTNI9k$ d2nT]Xn%OUÍ^7|:y\ABI ajj z~ƖQ!"Z[[rnQ^obX&Y^yd⪍b6aMECndg׈FJ,r9$ qc<EFVRd2'h9(E*n_WXDXlcć_h#8k;?+uí)tJ: @kaV$r9ܘm[$Ilp\|'!:iIk40< (155U/K CQŐJP,gNߏ^~k+7Ƿ_h#QbՏךmX7wxVd Rt:]^ GAs=8$IB,xyF%q^nG0j<!###NO Iv?fHRVU:PڈeEl eX<`VgV=جkE 7Hm066VnER HPDKHT^ڊ@ Pt iir98-0Gb+$ z. . &|J. 6V9q豄[Sh`4Bjr9aLMMt/"r\rJџPF\.8NͼLZ^166)JRMT*l6n3nIi 6o;CѾ[YkJ~GќܱiE ( H&d/jύ|>?mlrYX(p\`7_`T/("bttDv?Z|xnGnVl$:n(5MaIJ" A@$!LUN-0 p8xI (yH6f 7ݚp\jl,Jp bhhD☍Jl6 ~?V+GD 'chhSSSt6V$ v@---l&Z#\} /Fsׯ֔>3Мqk IL&1 Jh4h4w_+7\. QyH$'l$ֆ86 $g5M]}nM9~wќ({ƣ(01G ÁV\. D011l6;~($IBP@Xd {)^xnaM3nM9 $ T 4$IN+ܐ ^/v˫:ATB40))BId|>FnhCe|U 7ޚkw A>! !+ښb0v r~f199p8d2b^?kثPbyB^e2u8u~tM*5]gsN8́X>1 "1F\.AfD0"&''qLLL ϗWA(fCkk+8%F6 $YJr#>?n(5wʭ)Ds699Cajjj^(BEF8N|>x<di$I155Af]!ڰll\IAV-XbQQ?a秩ޚpf?G Q._$:Ya$iSRdBR mmm,5|> "Hd2h4N-$v;ZZZt:Eyl eX U]Jl$:[S>xWoܭ7ND~XO$rǜ"O\"*J9|>I!̸JbRӉ6k1AyLb[a׌ᆢ[S޻.I8 G6E(dy˰"$Iŋ9%|dVV4H)LLL`rr\FqְW"Vsܒe}+}!6]}پ`p"hNY h4DcNI l":&^_C$ D"D"$ EtY{IχnNInI6Q (}gXi5hg)z%/ŷwpY4XJ&&&p!b1bC$HΩDtR. VmC+DQD,Py^e0Sy(ߏN8N^m%mP8ӌeVfm֔:`5#?͖P,q$ID"d2??]\z9%d2! ܔS^6(N {g[&  ^ܒ]6[RUQ5ӝ[S] <'Yc(bó6Γ(p8hmmE )VDt|^`NsZ8D"p(v}ߣmKڔ>d-5GWNMn9XXc+ D(B:hP( y*cbEnP,P붔RI #J͸VNIlp8%EF2Ъf7ݚgRq4g K4x<1/q^OO|> GD'M^%SX4!"#ϗT1BR n0lZ ܒ]<'!j9P|kʻK 2XVd!(Jd2$dy*ѽ4))@-B 0AVz=P|kW^:WmUe @N]Ӝ(՚hnj (J<\.W7f ?JCqDpDQJۍ6prN7VCઍc<OJ?kaiζ20`!fV+ ^/t:ݴ jL&1>>>`CӉ T(D011|>tnGKK ZZZvUJOc4_ϰpxL~ӍXÁNAt: UCz<G$ ¬TS~?/^ LD P( #"`0̘%`Dkds(< 1Q|FZ 7CJk\6y8`86*WnP7<vrrDXSt:ZZZdDԐ$8$DQ,wEBkk+l6 Wg4^!X mG%KYua6t:*mI)J5;$I0 hoo'DPJ4c`ⴕr+o1thmmF%E{lFnz^p_gY4EfvOsw0WoPM|>BpND"C:NqrT DP"(gr@-^mmm۹zF<+Kڔ^!9+_;|<?xNUy o\I*QB Hn#`F#n7QÑޑLMMʕlXN͆v,Z2Q]JAbkƿ컴:6yp#cll LfZ#ʽ(bpp!5bH$qdY iʰWޒ•l~[q 1L`Cp+~BYC 7 <=^.IR""b&'' DQnG aAD}c9ҼSnDfIFd$ ^V#`>4񩗴)}ApF1ܘnǕ<-bi^?5T<$dq^'XIryDAUT,DP(fT*nFa|RhT 7  6=ǎӜq!^yIDDH$ʣ^n'7""Ed2p8H$|>?mҕ fT*e[}Ciɻ,Z> jC 7fz ą: WoP7DQD*b B9Đ_$*L&~tvvaj~/t(Jrr5]$cbRDQ,oWɌF#~?Z[[aXxhS\9%8pF jCQ 7fxc~3^ͽ4?7>@jG!B)/D"+7QF1111cD t: z;^!Jbyj-Í B!ia= V+\.\.׌4Cil eX ٛz>dWU 7Ti*yO_ƪӼp)A~$ Bdx+74&''199l6;c!rQÁ@ ^DT("ϗWc򉂐j(`9{$IlŬ"6ՖkOaOM~+Ʊ|jҼqUp~RQj@H$ǑL&S1/ $njbQR 00 5W(H$H$P,a0.r,mkkc#*c#Qm1ze҇WÍ 9拫7h&BaZ`qZ~R)IDQD"@4=üwz= zzzxxAhv;n7ԴɐWa i+mzr HT[x,oAV_y 7o847ha>S#T*!`jj T |#AlF @GGN'/(-Fgg'A@>1ZISSSbI)+|zl$g4q˫>l\Q7 7OƢpi KV@@V:{ȏEnw!DlSp󡥥R*v; n7A8 9=zrIȫFd+tlZ  YqgUupĞ%4o7>6K<;:: P~962l)J(Jv#`* , L&SsV|? G*:n,~ܖREI;{x~ V~nQ./">l6;c*px$IXdIQ5XV l6OGP&''199YnQG?,Jfr5 8R0贑TJA HDUrӃNl* N^nȁl5yaLNNP(`0f8NXVUrǦ0ǿj kU|k-> -yJUtM< Wo|9i_: 2 122l6[㋢T*aajj (V$ & Xx1</ UbA0u冼]N{4 BՎ/(X cccJ ^Fp6zk2Q 7D񱰂|VV捫7h><[~.&*? 00 ϱn^_,H|>H$)dYJ ^ X?zU 74J7'4'6 >N MӡX,"H LVuuXD(bش yIx>GT./xAݻx|rc[ZZ`xh0;b,XX}ZXV/ߍs(pCI>pnysS,C_(ZNDFQ,a0Ǔ_&$IhCkk+?$X,.oE$sbxUX P(X,Ӧ+J^/  4gEܹ)Bä.ǡ#^Z{I%wI; :~*Iwc!NfC[[ L&ӌ `0@Eb1#w|Ed0bⴕjEGGz{{8jprjX.1ej>T*P(i#-h"z`*9m VFq%3}<ƁWoIq:Xx1`X}B(0(F/X|P(ߏH$RUNI`jh#y6Dž"* G:J+IJf3tRn^eEDnEwǡ<n4. Zd, |> Ì)R,2 b><:]v!>)OKm6~?~?l6?$00Lӂb1D"000x<>U7z8N`ɒ%\Vwl #Y5L z: ~eрc6;7hG^)1[c& fz~A(G(B6dX,|q"ZVfyZ!z=L&"0<ĂMMMahhf03S\.ZZZ %)p---CG3 "8$I СCޣ$t@ݱ)< bN~Q=U 7f9%p^V@,O$G8J%R)abbb^7#"LT*T~B*Pl6x< "e^Ntwwbch|R)D"q8mj|p#pjTǿ[CsÍđorE}nޠY8Tf', n7^oa2;+ExCCCG*BT*?wz=ZZZ FD5'olaQ}QEiqdc՞qD"$iFBZ PK8q՚  /ꍷ4'tpf! "pL 742#H Lk[JP$ʫ6/@ܖBD(fN./J%iLLLGR\.T*|>_q}Wzn~X,^ u3ڥ:bO 9b)\)b1yti˯))$br*)d2!td2M7U#UdtzNR5>3>sq[=-X`Dz㻯[oBlHp4,l^u:hDXD$$r缹\cccF\^ v^ @DNW7h P( L"L)8x L&a0SRD\.ttt~0FfU-NC\V?&^ͤn79ӂp4,jEkk+~?t:݌ `0P(`bb 7<"HylT˅`0XBD$\^&d2tW>G> Q111b8cleUl ~ 5{VS&`F[쁁7h8nCh*oK{n$9MKoONNbttH095k y.nl0sNdBXx98FFFPn"aXj*[ 76ԭi_S A^ͨ.7<=vWo|G 6 V8t)0meljTNrO>!"F#:;;l2xEDuxp`X=7bn41::j(Z*ch~x"6 ˖-ڵky?\ Pt`z[ ZiF`4Cy Wm{pn fzӹz1iz=f3f&r~y[$IrFB<)lΝ;gP7n7:::ʍrӃrQyϒ (JH&G:>ncѩ)<زe "ȴ{ne,nvdjC=n{zB_xnPVo|50r- Tf0rp8`4FHEŰo> "N[*f188-[`ΝH&X,VHl6 NnY3&)tbٲen/EQmۆAd2Y|?4LrCު( U,סU/է9Ǹp8$l*BNM) ޽{188p8RT7*݅BzXt)\./ ՍlF0DKK \.Wy%D^!hv޽{fgFQ crrl ΣÍl6^v\z)^\."aUꍾKa7¬jE O mmmp\.7cbA> ߏh4zo:ƞ={CCCr屲Go}CEaŊ 7:l6ۋ@ 0(:T {޽{1555ߗ000۷#iSR& ===X|yLswǦ02, 9uݎr0ܠzNV$iMc4x}z(a2 "&'' Ǐ n{wpCN^/|>_yk Q=y<|_ }E J!Ϻ-P(D.b)W^䰷p:U,Ǣǡn`A8էG mpUVaɒ%0L3VnFJ%LMM! axxx_~PEؽ{7a`4MrEn]]]hooba "R`0N; ˗/l(dPԴwcccصkyr"ItR,_ {Maq]ƃ+VeE`ϫpfWէCˊ,vcٲeXhL&Ӵ?b||ӦHR$<@^!IRy8Bj8N,^]]]X,`C%=99Y1+"FGG1::x?({gs9F܂ P޽^Ql0ܠ=å[ Z/>P  `$!ɔUf\Xr(ؿ?vڅD"TQw>+Vի9ӕ'|>(fX,bhh[nŎ; /੧֭[H$Pn Z*`2҂իWcŊ &qB޻.uus  :9O}r^_~pyhE uYXz5,ˌzrbaܹB" 6oތ]v!NjV RL&Qb ]===0ԧ gy&VXшt:=m^bqڵ ckyDle!(Xv-VZ-)1ĹVuZzNGy^t~@w\է8'pcիWcl( 3>}7݋ݻwc߾}رcDP*`6˯=T bXreyn]K.h,rHcaf3dy{J(l4~X*H$ "j*`[h2*u,}lph++}>?ӂܣV+/^\^Mt:MBKRyoC#cظq#6n܈8x |a_׆5kpYg'"ղXd ֮]nl6yR) r!{ GfMNQd qꩧSOec<<>U*KqF{ݶB #?'v*}oNij">!۽(O+?u8p=`Ϟ=d2ZӖ_χ^fDjFmmmXd |>R0r ̓ \A@XD4E"nǢEdd29Ɲ,,?/-06 9e1ܷ=BZ[[qg`ժUr;I`0`ZسgvڅP(T"o_bd2Yuxc|[NDсuaݺu*NfEzV6gT*!!ɠP(@3_b\v}ZQ:y>F?kE( 7J+;4"-"eMtbŊX|9 r\h4XW9ܶR90#BE,_s:::Xh" ۍSN9>֭[E# HWr+8LX d2x<8Spbݺu,eX~/- bcCW3dBP(HBP*$I*ODty~:.",_f%"MX,hkkDGG:::088ݻwcbbr(JNK,Aoo/N?t,[[Q(qc|U?{E=ȟ< B%*hx;6:F,F392SOEP$FFFǑL&a /. mmmzXz5֬YիWrDy6 ]]]X,@OO(B"rfsyFoo/+#8Um> 4nGi$IB?I7wP#XVD-(xiܞB(Bسgw^bll l<-@n---Xl:::l2tvvDprN199IdYXV|>|>A8r"͡4BESu<gՆtټxÕG\A==>{I-pxG0dBGGF111D"Q`2  χvx^|X"Fc6zt:vQa4, *Wu13޷.@Uqf!HQ;"_z> nGiZ 7T@z+{4UG:(2Ɓ oX{~(͋z<o߲էgE~zBDDD`Quiwq5u}y^pC%= XE^ WQmlh F?{ (pCeѰ[¨gsQ/>P ""0g!T/šK_nG!*RѰ>ya+/UU?DDDT܎o=͇wY> } SѰ7f^<4' 74Fq]މA /UG&X""&ˊuSEm> ٠9cMOC;sz U9 $X""&um̗X:tw_ާl<0 ?ãa^n J1*DDDMs4 QGm#~{z 6h>nh׿xߋ8 AU5,?B5,Dv|t>*+BpCzI)T]Ci\k6%""jWܱbIb!Ĩ{V!>^/&Mzc7.慠(>a]y^rk]}5=`ާ` 79͋׭*P}e=B5Oそq>~a+NާR08?'xc/\f=/U(Ixbd ""j0)|C,DfާQT 7׿ STuyQKnߍpT}(FݜfXSyB(UÍQ)/yF\q^A\MFtx ^ B SweͼTu ;piݗ6ۢ,D8zlA#_ P0h ޞ| 9j=q!H7?ab x}e٠jcx=k_Wj `!4&4U[}6z.&^6 F Su/_Š%"" I^l^G߼[ ly^ H SK0bPՉ% ^l؟`14?MiNuE7>zANeO}ܛD5pqu{ǢX7% o> iH~\?|[JguaQ% J SKŠfDq*u0o˖87^gyUn4#S>nq'/j%KT#..lNFDD&^`8ˎ޳B "'n48!}Rn)ZF^ }^|x"""R Qpяv!Y:X1w-WN( )Fs:Ǣ#/7F:~""$uwpŨ. ?^  *M@X`vn^ w ""xhWInPM1 NF!1hB_FޞR<'W.wbP| |,Qq>aLzOoZ/p2 )FsyC<_e ڝAG񍿎DDD JUwa!WN'+6@pa{ת[ 7v>8 ADDXVą?u` ۿٝ6`FQe~3~ETS{nn ""XV["/uۗ.UxU^n4Wx~| ~wCiF^w^D2E~EjgUzbфlO m<[ /ԺdADDTܾO &Y:x9Vd7MJS&'ޫԠĒĀ>06$X:+:ɋr:>`ԀFu;àcQ$E 8֧'p GM//VMA^R'yq˫x%Gr,Ѽbsz Q_|i.iWqx R MN߃[[| TSŒKoߍ'S,u0w3B[O&WR%$L=oYuM L$\]x""9e? QX z|bGDDD؊p.[ğ޽BM ;~^R;4Թ0 aA:$}.k9:8A򝿏d!D^ҍ߿}Ni Gl0ܠ3;} BxP p ADDM#[ 6ን Y_!-aAT1>U0rD,)䩃I^ bQCeE6'l(U+xH}B_M:5 7踎lOy/Tc] s"5 8Ų"֬߆dPէx;lU :!5xߋU](PUچ}ADD %v6 6ɋp[l1ܠ [5^RX 쌳DD"V}kFl(wkw `A 5^/]!}X8ADD/oo(kwJ~` :i7Tp+2h!E }>!48F]|Emj:% ]B_:nМ(^!j4 7h!~ v5Hׄ:x`~5b|_GY *mW۩mp?«D͛&adAKxsf1H5>|y# ל_ CuQ8˳DDD|{;wPXۄm7ǢW۩1ؠpHE15 {W26Y݁wY ""RӇxwbxPإKx?֨1x 6I0ܠTq+Hx "wkܾ\9+V{V1 6Y0ܠj7j8wn}m7 E^Fp-X""/m[@QJ z25yY0ܠ:2A{~s k=8T7w?omÎ,ADDUOK 6vld0lPaAU'|*^\šqw㗛,-Xh+܋>;b(̨pU>nEfpjb j9G_W0:ʋ~|聃ȋ#i;l\4^3\(cAM̑rN^WP]}o9ޭQ8ȑku65 jz 7>`\-⍧xyq^`m6nS!"Kqz<&Z5l`A  dD,KpZT_wTB>7Nj]5jZ0 ps܄Ukp O7۰}Th{;.ר/Wkq^D1 Ewr^^gRr~ yR6u "j~tg~wD.Lzz|vīEtb 7px^Ђn*mG)=w*""['FY:p}+qi^0w2 :y 7Hp  8H]~|=_{UDDZg&[7c16+6jۮSB_ZD'Z {x+`1Dž%qُw*""eE4~}5ZFoaA4w|[#UQkqQg<=/Γ޼>=b=[޼[BNLz~u7~^չXp넾~^1yKJc^Y9n>~EM5tܿ"_IGrX| V":Z87oaQgZ통j6`AT 7HeU+ܼBZ~%b)h0DzonG>"{kXvjkd `A%')15A'w.cA̋c7b0ʭTDDb7b'ԕY/WgoUi,+FTGP%EX 2۟@4߿j:hSXF`pC"3ܨ ,8S>H軴͡4s/P xi>|V,X":!$2{Eu,ÂhĹv,Y$$i_g "Gjѵp=ZZp ~"hEf; 0ڇȋGM\܅wDZ?mMb?Wihj-%Z:,_PhwAk 7(2 -Q_'^8ţb7n\{.q¤gA_?x`GѸ8yyVkWkq Xw3P 7x3LQo{ o~6gN k=8D*%<1/Dq"iN<Ѻ. })hi1Fʇt:n VohgMi0w,/5~3;Ï7`MAQD [2g?8ƒ 4I//_ҦS6˻n ᆖoj>4ė6FP*"ax^a`ATsŒcY~g wlⴓFs"~vlJs}`pC"3MS}8ë8^r.Xe~3uWbeDU$$Q?ak㶫SZ;<4wAk 7(2ÍFi|CocxDGY7 kxZAhn% {9ܿ#۟d>qQ+.iZPX/īHxgpC"3hpPWr?Cn<| W@/Y~J(I4MwcLF#;Պ߿}i(U㹟E{7 pnt0n{qŋxJ7ZbA5< fEܷ-wȞ)d %Y :|u=,O? np -E{7 pov<^2E$: ~oX+ݸdSKes(_7/Dp~ pYVM $UyE{7 pozU9g yC7m!W?\jŕxr.YdAa Fx|~%L![fsZn)xb#^,޻n(Pd~[fW0ksxo$/"6D k==hs%WkPYw3P 7Fm*Ю8q $ڬx2.X6-X$J#<+Jcxpݙ6﫻qaC_Bqx,޻n(Pdx36(q7odQ"%X :,eNnVc0Pp '9ɄN,`3+3Zl*co RyE{7 poܦ2a ITN#^eiVt3Wy4hCi<}(gR9E(Q`ahN,>}q>sqLzͦAzgpC"3hr_Z.oEb"sDjgƩV Zn;z&а  2"Lbs(Z{f@nпng&h<>pAawHc0YuW}x,zX͡4bY<N!+١ % {\A9݊_:Z2DIW"hEfAoN9-~ n axtu0tfY.3mX [yq䎬xl_L) u8͊_֎zL*䙝E{7 pfa;.__{b_}b q5W,X9%Nxʅ"l}7—gneR9n  T8 |Cxa8g5#.ko/]I+,޻n(Pd4Wq'0C~Q+>Bei_fPs8n -z%["y!"" h4 ?Z{f@nog&IJ">!yDDԜ&p~ >~ak#~ylJ"hEfAչ:\ :keE!|9"""j|To#~ylJ"hEfAս6*p7[\ADD NbrPlJDyf@nPUQXV'06rQCxR'n}/zFpADTn Ոtqxk񑱕bYxx?l.x,F2p >Q߻n(PdTcGF~ZB|]O?2;ADDnzI>pn 7(2 Rt =h*WE<+b XK3e&qC "R{7 pԨ[Ud}zDDT_-qWtv[#lJDxf@nP4VNaDq>|DfDn I#oUm? xfV,؅e_ "{7 pѷ`4l ᧛ȋ™.[iQjv߻n(PdU8|Ma_BDD4g]n>~a+? [OD 7(2 R* r>0Jq^̅^ B+q 5{7 pTqy|!ܿ#\<""o=,4 'Qw3P 7HŚ,m1| DDMlpZ0 F~f@n4C?JCi|#x|5s3 f@nF4S?Y,+➭Q & "j0>Y;Y&TbADpC"3 i~6ܟF}SADaf-m栕C "jn iԑ~pfcYmO 4;6\Ӭ%(~"jn i\I+?zvfo""9Պ_؊xiѲv5{7 pD3a >=wĐcR"qsxifQ=5n 5Jf¸I{9OxN|P%1<3X"Rڬxr^ڃuv\qADTn Ѭn^ 9fKSEbADT} 7H|t' IlHDt~.rv+.[y2c>'?+XQ1 "^G,ɛʉ)lH)lY&1v.u%Nml`1Oq;p֓?1 ":DNQwT?c@OLax"{v5ڬpg.t0B%IҘ "cADZ$oY9@ 8ee޲6si@ 8HyLXjmV\ĉ {Q='q"DDUpSVj`(q JbdYT(`3`UXҥNێ.7o5UaADT} 7TNYj*+$0?ϑ4>ž2B̀UA ֶXҥ.cG7Z*Y 75P@$ >xc(a ^3V-qq.Y@ 7^TiƬbADTu 7Qq5Gl؟_'p0Ǯ,yh~N#aUЂ;xq6#{d(NNQ1 f`-CQŒm,4Od0DLp0訛bI“:48cD!MZoceqq&*pP@ "cADͪ28 @'mE5F $vs=,i'|P} Z"`+f@=1TJn ,ۄUqR 7ѿ? Op yl`,QHl SB "`I/V+BDDPnQ9V!j5[`!sDDD;F0\,ռ7nc(A7 " 3!$)&Hݦlǐ}YfQk; H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F+IDATx|OSQ?mB[2%jb41}1E10^D6c Htqb" `1i vo9=I$$Y@}Vx ukr6j2 ^. IQ=T< w~5G`HP6 %n_lafp*+y&P %#3Wr @&oeՑvvv,T*[gx[᤿˲H$XEX 25= cWW*W_wjiTϙ=D8 9٫$O76%*(vjcsSHDrYF<)ϣO 1mW @vJ\/еN&uLMH&qR(Ue_񤆦֏7xA=8h"N}`W41zt3y^#!>jGBU`ؾ*:#_&{(7N5a :pp<ſMt$4IENDB`OSCAR-code-v1.3.1/Building/Icons/Wave-24.png000066400000000000000000000071231417327530600201370ustar00rootroot00000000000000PNG  IHDRw= pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F~IDATxڜ]hUfwi65AS+ڱ$/ Z|6E.>/P-}EP ]2)Ȳ|MvƇ;n̝s{ιgp& WML Խǀ";+0Uh8LSvqΙC%:+m;\k -ld"7LHgɨpr Cr%!SqƏ1p k\*9/Uq"&^9p`Dd 9gcvxcON\˯&x@'@/P8u˛:{I^t/ .hPj&!M뛏K$zU|bj685Co-.]ױ,ز,t]Gz}TUNCuFGGOulأĂ^?plooIyXUS֫ jvPۓL;oCC06:Z΅֞@W ٬0R 8yY/w_8}CkD> :a=r"f"XW/?ϭVbnf~ˍT%+ Dt Y-+23omNi8`[8>1۶YoND~! ӖĶm à0,]AxpwK·?8~l)%_K< زv`S$slunL*0{<&_!/m+5])1|_W D0)Dˬ\Ҫ!MWvuUkz” =H?4< qA#(LõE+N[IUWf U/IENDB`OSCAR-code-v1.3.1/Building/Icons/Wave-32.png000066400000000000000000000104361417327530600201370ustar00rootroot00000000000000PNG  IHDR szz pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIIDATxڬl[?=/v:qMHtRkђ ̏@Gh 24M޺_BU5** j+luR*bn$ )i@n8u?^l'q=w9rbύ$b I \ sJQ, LDYGUf]UN;SWd sE9>ԗ0ς(%d*nZU -?Mc{k<>%JB)๸ 0E~fcgG V+X.AV;ѯ>8wl%`1P D0@H\ORM^6L๳F@'dw;[e~-{W>OzO{ ρ7|d&_[Ρ{(7J'ykh:O~n;ν (4Lxx>sqyqb\Q/8 lVEP\Ѥ\Ig5k,x+.^Ǵ7*٢n(¤ <66*Â1me5[e%\TX]ϵ(,$A`ǭU>ww]^]/T:āH狽{r$yTFl[fGa9@kkkQfy'' DSIg,jgtxT,٠\&L枎0?+esk.䅏~@Ԙ|@CGp~c2)P[_f`,O 5ybbVy!od~nֳ?TwUY)P]6F Fg80z5L%RfXn1\iLnjH,c;pa\;7txHIENDB`OSCAR-code-v1.3.1/Building/Icons/Wave-48.png000066400000000000000000000134051417327530600201450ustar00rootroot00000000000000PNG  IHDR00W pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F 0IDATxԚ{p\}?~XVJB~IX 9A41ql3$m 6%q;L_4B &$CS qcc$eYϕZ?ξ$e[49}Jay(Yf`qU N@R@z6=6`--MbʮN˥+ E=n,9pOcQ:G FtF)bzY*b¡PWlJM~'u.L&2,Q1c0- H"h*Y/ z,&mlpSk\:}Q`|<{mq[e$$c8-1FHC&LĆJxl*C?fgq_ 2$6ͥ_@s#/8xm`!XMl^ RMO|g|6l'a]_X3/*C CMZ&E8vW|RkQ>;>^op﮳c\+`*ln[WV姁- o756E0Iz||UټPV֕vFRT;9wl -xUPcIf|pGVTT^{šXT_Am#Xh40߫œϤظͦ;BLx$KщapKfp_(zn$EjJqH1]<0 (2_<1ǾbRs|?ell;wrYv9GxeΟ?7λ=? 83F'r-7)Hp!~aٱc9.|>[l)-+#H$;LS(lxTPWhM ף*]]]Foছnرc߼#j-`X,+P  ]34|" .Ԕ{_^^Ι3gfpJJJZ9Xt:=55QN+|0#2T`e!G: RY`νfQ[[/JKD"c|xP@ 0}v("/;TpX;-Pwj((.SdсbEvx<>+M&ώ/N9R)w&l( [\hs1g$ ,(KfӉ$I3D0 lf6FӮ[RA+$@3 LKɩyfIcDx&ye8pusb!/D4ٛ6֤n]/k }>~kKKXy17 a94 HP&^p^4XВI5x@QĞ3 uv'n  B6$ ,eↁE\[;vckPQUE`B($Y!<(YY2H?ȝK j)MU7/e4Tuu_~<Kkj&D٤#)P\,o㬩a?/Z΁QBi2%vp,`.h"ebhBDسgenO1,i98ݛ7O3&ڤ.T8U!Uu$1!Hy]]]^zE2)ʅ'Ns E9qR+NLFZd̰) ƈj=EAQ$RWW77YPQ0L|&fqMH3KqB;WQC*찚83{u8ttt\'.R-+8#PRal, <~hjbR66$IXVv}$!2#=ZPt ?u2w]YN'Gg=$1>>3wHuEɭ/; Q]Ѳ# YY]ӻ-BL_{΅5$JF 444@:VNvv ,&4 NR4#Fy'~ɓj,˘f)ݮ`cQQXeyM'~DOLèʻG9-rew^B]w]k6 ťÍ_3N}U\1QPd%sZCXjehll$r0E1>(;g.1wLſRGsjFg}Kxi9#fv5N8Ԗ"l}Į i##TyvOP倞0?u_nlVp(y-4־0>sJ,ovfLֆp] wi32v΋񯴌vIqq-rsoZM?r5_i mOvETXהg4LR/2BF/URn?\"SǣvRn_ roSXݘL3-Qȿ"$Uv)'%߭G#L[ OgXkܥb7h)h ^p' "NdU~J_fKToƩCu }$SPd , .O♖Y%8{y|ťs6Wr\`H@8-Cqǒ Ett1RTBʒb 7WٹES-G+%*hC1P"D]A3w]-Ik`PQ S@= \_[3δN{IENDB`OSCAR-code-v1.3.1/Building/Icons/Wave-64.png000066400000000000000000000155531417327530600201510ustar00rootroot00000000000000PNG  IHDR@@iq pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxyT՝?oh *AFE#NQH$1'1dg8ɘ1шHdZ0Eh77*UoKN9u[{|+h_r og ;0 L&Y0GA@?4ȯ j`ȋJ[Hp! 6HYfnx8 &j\^`#PHM|kqGmww_R *ѯGpE2d[7Ɯ3,ȢP*LkYOz9 , Btڠ4"1+3|4ڐD!}؀oI]l=.ް>Y92;EW* L\|āQPªއYdA-K0?,(o9Ts[8TէO,OTP5v9l,M8BCi0IJOu]Ԁ(1T=WtKSOGa{44UxPE'n^:wL(2s>J+z}y/4z ,v;)lgm  <%7VX%}!e3<{%_V oU>ԯ 2Slp6Zd `K':R@8JHn&$kmۍx E#[SK~ԫ/pǏ. WD06SR`T=3->ֿ^Ce,Ra] K` t%}G 2=6͠x jZ'̽Us5,Z_# 3( rMju,,{Ogwzì~ S5ҒM,.H뿺˗/yf֭[Ǯ]Pe\wZ3gΌyqVZuܹsqtvu$En,t\8Xpmrsq׎?΢Eb5sQzIII!###vp_\`OHX8tׯP3K@aZohhY}IȠeL1o޼xSWT4]3vZ!ZCD( ٗziMפYN0$33/HKKQl&f͚w=++ =mxJ@PzCa$c_؈4fP}>M %%5b1"n|O=)@>2v&4 $`5MhV-ںIN6؇YM6% *QF;,BVmm-EEEJ(4N}}=SL1i,Q$/bd!4 ȟBFx 2g\հXp (ѓ'af0`pľ6iC͠@o?rsU ș 8`mېf/)!j;Ě7hpG_ZJu]kﻏ4#au@dI!1?Tpi&/`χfNѩi|'d=6XL`ZZZ8'|dMz[5T$[a7ਕdb o&[f;wdʕW&TԄJ\6IdLP:V,l2L޽{6mc@Kp9A$9W؏AHt3_;M D,I@ &,`ʕE!XgrM7= ![Hշ\ S͉}H0 ,Ȳ|Ell #Qmmm̘1#6wX(Ch* Sͺ#aEE}AjcQ0C  ===֝+o$Ţr=a @P$3sRA (j)nX[CC6[|B/HUYV8xK؍\t[[۸_Q`Xcq|C"N#iɉ5ZW4 Q?&Mށj:iPd='cjtZ.0R'lsS>R)dy<8/ksH(55yt=\!}+I5SeH@ǧ ۀg4SS ºJY32-LMeEWOP'@E&/>}13+rY7?I6"oW{b$g`+Xd'Nvr9TcHQt Adobe Photoshop CS4 Windows 2019-04-12T06:32:10-04:00 2019-05-13T20:20:35-07:00 2019-05-13T20:20:35-07:00 application/vnd.adobe.photoshop xmp.iid:2D9573E2F675E911B63EB3098D527AD1 xmp.did:7F4C1A3F915CE9118CC9D95869862682 xmp.did:1748646B995CE9118CC9D95869862682 created xmp.iid:1748646B995CE9118CC9D95869862682 2019-04-12T06:32:10-04:00 Adobe Photoshop CS4 Windows saved xmp.iid:2EE0426D6661E9119BD8F58C76039487 2019-04-17T19:13:35-04:00 Adobe Photoshop CS4 Windows / saved xmp.iid:F544A21CAD63E91198CAD6AA02B9FE24 2019-04-20T16:44:37-04:00 Adobe Photoshop CS4 Windows / saved xmp.iid:F644A21CAD63E91198CAD6AA02B9FE24 2019-04-20T16:44:37-04:00 Adobe Photoshop CS4 Windows / saved xmp.iid:B76817787673E911A939D3167B55D360 2019-05-10T19:01:27-04:00 Adobe Photoshop CS4 Windows / saved xmp.iid:4F6A79F47773E911A939D3167B55D360 2019-05-10T19:12:43-04:00 Adobe Photoshop CS4 Windows / saved xmp.iid:506A79F47773E911A939D3167B55D360 2019-05-10T19:12:56-04:00 Adobe Photoshop CS4 Windows / saved xmp.iid:F9D0C2A1AA73E911AD49A7E34B69E7CD 2019-05-10T22:15:58-07:00 Adobe Photoshop CS4 Windows / saved xmp.iid:CB1F84120474E911BC13A94EBA51DCE0 2019-05-11T08:53:12-07:00 Adobe Photoshop CS4 Windows / saved xmp.iid:BC6F968D8174E911B09A891F012348ED 2019-05-11T23:45:38-07:00 Adobe Photoshop CS4 Windows / saved xmp.iid:2885AD483F75E911A4C4A48B61F04FBF 2019-05-12T22:29:33-07:00 Adobe Photoshop CS4 Windows / saved xmp.iid:2D9573E2F675E911B63EB3098D527AD1 2019-05-13T20:20:35-07:00 Adobe Photoshop CS4 Windows / 1 720000/10000 720000/10000 2 256,257,258,259,262,274,277,284,530,531,282,283,296,301,318,319,529,532,306,270,271,272,305,315,33432;C67C26CE0F22F93EC0DA66A680ACA80B 1079 1079 1 36864,40960,40961,37121,37122,40962,40963,37510,40964,36867,36868,33434,33437,34850,34852,34855,34856,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37396,41483,41484,41486,41487,41488,41492,41493,41495,41728,41729,41730,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,42016,0,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,26,27,28,30;F1D697A1ACBB51B6BEACDDBFAD797532 3 sRGB IEC61966-2.1 2019-05-10T22:04:47-07:00 File final w white glow.psd opened Open E:\OSCAR\OSCAR-code\Icons\final w white glow.psd Hide layer “SCAR centered” Hide layer “O” Show layer “O” Hide layer “O” Show layer “O” Hide layer “SCAR” Show layer “SCAR” Hide layer “O” Show layer “O” Hide layer “O” Show layer “O” Select layer “O” Without Make Visible Hide current layer Show current layer Hide current layer Show current layer Hide layer “half wave” Hide layer “O” Show layer “O” Hide layer “O” Show layer “O” Hide layer “O” Show layer “O” Hide layer “O centered” Nudge Move current layer To: 3 pixels, 0 pixels Select layer “SCAR” Without Make Visible Nudge Move current layer To: 5 pixels, 0 pixels Select layer “O” Without Make Visible Nudge Move current layer To: 3 pixels, 0 pixels Select layer “SCAR” Without Make Visible Nudge 2019-05-10T22:07:10-07:00 File E:\OSCAR\OSCAR-code\Icons\final w white glow-v2.psd saved Move current layer To: 2 pixels, 0 pixels Save As: Photoshop With Maximize Compatibility In: E:\OSCAR\OSCAR-code\Icons\final w white glow-v2.psd Image Size Image Size Width: 100 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Select previous history state Nudge Move current layer To: 3 pixels, 0 pixels Select layer “O” Without Make Visible Nudge 2019-05-10T22:09:44-07:00 File E:\OSCAR\OSCAR-code\Icons\final w white glow-v2.psd saved Move current layer To: 3 pixels, 0 pixels Save Image Size Image Size Width: 100 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Select layer “O centered” Without Make Visible Select previous history state 2019-05-10T22:15:58-07:00 File final w white glow-v2.psd closed 2019-05-10T22:15:58-07:00 File E:\OSCAR\OSCAR-code\Icons\final w white glow-v2.psd saved 2019-05-11T00:24:28-07:00 File final w white glow-v2.psd opened Open E:\OSCAR\OSCAR-code\Icons\final w white glow-v2.psd Image Size Image Size Width: 64 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Select previous document Select previous document Select previous document Select previous history state Select layer “SCAR” Modification: Add Without Make Visible Nudge Move current layer To: 2 pixels, 0 pixels Image Size Image Size Width: 64 pixels With Scale Styles With Constrain Proportions Interpolation: nearest neighbor Select previous history state Image Size Image Size Width: 64 pixels With Scale Styles With Constrain Proportions Interpolation: bilinear Undo Image Size Image Size Width: 64 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Undo Image Size Image Size Width: 100 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Undo Image Size Image Size Width: 64 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Undo Free Transform Transform current layer Center: center Translate: 1.8 pixels, 0 pixels Width: 96% Image Size Image Size Width: 64 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-11T06:43:51-07:00 File E:\OSCAR\OSCAR-code\Icons\Full-64.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Icons\Full-64.png With Copy Select previous history state Image Size Image Size Width: 100 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-11T07:20:18-07:00 File E:\OSCAR\OSCAR-code\Icons\Full-100.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Icons\Full-100.png With Copy Select previous history state Image Size Image Size Width: 128 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-11T07:21:09-07:00 File E:\OSCAR\OSCAR-code\Icons\Full-128.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Icons\Full-128.png With Copy Select previous history state Image Size Image Size Width: 48 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-11T07:24:51-07:00 File E:\OSCAR\OSCAR-code\Icons\Full-48.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Icons\Full-48.png With Copy Select previous history state Select previous history state Duplicate Layer Duplicate current layer 2 Select layer “SCAR copy” Without Make Visible Layer Properties Set current layer To: layer Name: “SCAR - smaller” Select layer “O copy” Without Make Visible Layer Properties Set current layer To: layer Name: “O copy - smaller” Select layer “SCAR” Without Make Visible Layer Properties Set current layer To: layer Name: “SCAR - Full” Hide layer “O” Show layer “O” Select layer “O” Without Make Visible Layer Properties Set current layer To: layer Name: “O - Full” Hide current layer Hide layer “SCAR - Full” Show layer “SCAR - Full” Show current layer Hide layer “SCAR - smaller” Show layer “SCAR - smaller” Select layer “SCAR - smaller” Without Make Visible Select layer “O copy - smaller” Modification: Add Without Make Visible Undo Undo Hide layer “SCAR - Full” Hide layer “O - Full” Show layer “O - Full” Hide layer “O copy - smaller” Show layer “O copy - smaller” Hide layer “O - Full” Free Transform Transform current layer Center: center Translate: 0.5 pixels, 0 pixels Width: 98.4% Show layer “SCAR - Full” Select layer “SCAR - smaller” Without Make Visible Select layer “O copy - smaller” Without Make Visible Show layer “O - Full” Hide current layer Show current layer Hide layer “O - Full” Show layer “O - Full” Hide layer “O - Full” Hide layer “SCAR - smaller” Show layer “SCAR - smaller” Hide layer “SCAR - smaller” Hide layer “SCAR - Full” Show layer “SCAR - Full” Show layer “SCAR - smaller” Hide layer “SCAR - Full” Select layer “O centered” Without Make Visible Hide layer “O” Show layer “O” Hide layer “O” Show layer “O” Hide layer “O” Show layer “O” Hide layer “O” Show layer “O” Hide layer “O” Show layer “O” Hide layer “Layer 1” Show layer “Layer 1” Hide layer “full wave” Show layer “full wave” Show layer “half wave” Select layer “half wave” Without Make Visible Set Foreground Color To: RGB color Red: 64 Green: 64 Blue: 64 Exchange Swatches Paint Bucket Fill From: 697 pixels, 391 pixels Tolerance: 55 Using: foreground color Without Contiguous Hide current layer Hide layer “full wave” Show layer “full wave” Image Size Image Size Width: 48 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-11T08:37:15-07:00 File E:\OSCAR\OSCAR-code\Icons\Full-48.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Icons\Full-48.png With Copy Select previous history state Image Size Image Size Width: 64 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Select previous history state Select layer “O copy - smaller” Without Make Visible Select layer “SCAR - smaller” Modification: Add Continuous Without Make Visible Free Transform Transform current layer Center: center Translate: 1 pixels, 0 pixels Width: 98.5% Image Size Image Size Width: 48 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-11T08:47:25-07:00 File E:\OSCAR\OSCAR-code\Icons\Full-48.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Icons\Full-48.png With Copy Select previous history state Image Size Image Size Width: 128 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Show layer “O - Full” Show layer “SCAR - Full” Hide layer “O copy - smaller” Hide layer “SCAR - smaller” 2019-05-11T08:49:16-07:00 File E:\OSCAR\OSCAR-code\Icons\Full-128.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Icons\Full-128.png With Copy Select previous history state Image Size Image Size Width: 220 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Show layer “SCAR - Full” Hide layer “SCAR - Full” Show layer “SCAR - Full” Show layer “O - Full” Hide layer “SCAR - smaller” Hide layer “O copy - smaller” 2019-05-11T08:50:46-07:00 File E:\OSCAR\OSCAR-code\Icons\Full-220.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Icons\Full-220.png With Copy Select previous history state Image Size Image Size Width: 32 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Show layer “SCAR - Full” Show layer “O - Full” Hide layer “O copy - smaller” Hide layer “SCAR - smaller” Show layer “SCAR - smaller” Show layer “O copy - smaller” Hide layer “SCAR - Full” Hide layer “O - Full” 2019-05-11T08:52:54-07:00 File E:\OSCAR\OSCAR-code\Icons\Full-32.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Icons\Full-32.png With Copy Select previous history state 2019-05-11T08:53:12-07:00 File final w white glow-v2.psd closed 2019-05-11T08:53:12-07:00 File E:\OSCAR\OSCAR-code\Icons\final w white glow-v2.psd saved 2019-05-11T23:38:19-07:00 File final w white glow-v2.psd opened Open E:\OSCAR\OSCAR-code\Building\Icons\final w white glow-v2.psd Select layer “full wave” Without Make Visible Set Foreground Color To: RGB color Red: 63.004 Green: 63.004 Blue: 63.004 Set Foreground Color To: RGB color Red: 63.004 Green: 63.004 Blue: 0 Set Foreground Color To: RGB color Red: 63.004 Green: 0 Blue: 0 Set Foreground Color To: RGB color Red: 0 Green: 0 Blue: 0 Paint Bucket Fill From: 632 pixels, 607 pixels Tolerance: 55 Using: foreground color Without Contiguous Select previous history state Duplicate Layer Duplicate current layer Name: “full wave black” 2 Hide layer “full wave” Paint Bucket Fill From: 577 pixels, 643 pixels Tolerance: 55 Using: foreground color Without Contiguous 2019-05-11T23:40:37-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-128.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-128.png With Copy Image Size Image Size Width: 128 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-11T23:41:36-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-128.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-128.png With Copy Select previous history state Image Size Image Size Width: 64 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-11T23:43:11-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-64.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-64.png With Copy Select previous history state Image Size Image Size Width: 48 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-11T23:44:08-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-48.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-48.png With Copy Select previous history state Image Size Image Size Width: 32 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-11T23:45:23-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-32.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-32.png With Copy Select previous history state 2019-05-11T23:45:38-07:00 File final w white glow-v2.psd closed 2019-05-11T23:45:38-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\final w white glow-v2.psd saved 2019-05-12T22:21:06-07:00 File final w white glow-v2.psd opened Open E:\OSCAR\OSCAR-code\Building\Icons\final w white glow-v2.psd Select layer “O” Without Make Visible Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 37 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Image Size Image Size Width: 48 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-12T22:23:47-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\testglow-48.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\testglow-48.png With Copy Select previous history state Open E:\OSCAR\OSCAR-code\Building\Icons\final w white glow-v2.psd Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 34 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Image Size Image Size Width: 48 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-12T22:27:01-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\test34glow-48.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\test34glow-48.png With Copy Select previous history state 2019-05-12T22:29:33-07:00 File final w white glow-v2.psd closed 2019-05-12T22:29:33-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\final w white glow-v2.psd saved 2019-05-13T16:06:15-07:00 File final w white glow-v2.psd opened Open E:\OSCAR\OSCAR-code\Building\Icons\final w white glow-v2.psd Select layer “O” Without Make Visible Layer Properties Set current layer To: layer Name: “O filler” Hide layer “Layer 1” Show layer “Layer 1” Select layer “O” Without Make Visible Layer Properties Set current layer To: layer Name: “Big O” Layer Properties Set current layer To: layer Name: “Big O - always on” Select layer “O filler” Without Make Visible Layer Properties Set current layer To: layer Name: “O filler - always on” Select layer “O copy - smaller” Without Make Visible Layer Properties Set current layer To: layer Name: “O - smaller” Layer Order Move layer “O - Full” To: layer 7 Without Adjust Selection Select layer “full wave” Without Make Visible Delete Layer Delete current layer Select layer “full wave black” Without Make Visible Layer Properties Set current layer To: layer Name: “full wave” Select layer “SCAR - smaller” Without Make Visible Layer Properties Set current layer To: layer Name: “SCAR - small” Select layer “O - smaller” Without Make Visible Layer Properties Set current layer To: layer Name: “O - small” Select layer “SCAR - Full” Without Make Visible Layer Properties Set current layer To: layer Name: “SCAR - large” Select layer “O - Full” Without Make Visible Layer Properties Set current layer To: layer Name: “O - large” Select layer “Big O - always on” Without Make Visible Hide layer “full wave” Show layer “half wave” Hide layer “O - small” Hide layer “SCAR - small” Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 44 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Select layer “half wave” Without Make Visible Set Foreground Color To: RGB color Red: 0 Green: 0 Blue: 0 Image Size Image Size Width: 16 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Select layer “Big O - always on” Without Make Visible Select pencil Reset Swatches Reset Swatches Exchange Swatches Pencil Undo Pencil Pencil Pencil Pencil Select pencil Pencil Pencil Pencil Pencil Pencil Pencil Undo Select previous history state Select pencil Select brush “Hard Round 1 pixel” Pencil Pencil Exchange Swatches Pencil Pencil Undo Select previous history state Select layer “half wave” Without Make Visible Pencil Pencil Pencil Pencil Undo Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Select healing brush Select pencil Pencil Set Foreground Color To: RGB color Red: 229 Green: 229 Blue: 229 Pencil 2019-05-13T17:21:31-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Wave-16.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Wave-16.png With Copy Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select All Layers Select All Layers current layer Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Undo Select previous history state Undo Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 37 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Image Size Image Size Width: 32 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Select pencil Reset Swatches Pencil Select layer “half wave” Without Make Visible Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Select pencil Pencil Pencil Pencil Pencil Pencil Exchange Swatches Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Select pencil Pencil Pencil Set Foreground Color To: HSB color Hue: 0° Saturation: 79.216 Brightness: 100 Select pencil Pencil Pencil Pencil Select previous history state Select previous history state Select previous history state Pencil Pencil Pencil Pencil Pencil Pencil 2019-05-13T17:30:50-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Wave-32.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Wave-32.png With Copy Select pencil Pencil Pencil Pencil Pencil Pencil 2019-05-13T17:33:46-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Wave-32.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Wave-32.png With Copy Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select previous history state Select history state -29 Hide layer “half wave” Show layer “full wave” Show layer “SCAR - large” Show layer “O - small” Image Size Image Size Width: 32 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Exchange Swatches Undo Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 32 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Image Size Image Size Width: 48 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Exchange Swatches Select pencil Select layer “full wave” Without Make Visible Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Set Foreground Color To: HSB color Hue: 0° Saturation: 37.647 Brightness: 100 Select pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Exchange Swatches Select pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Undo Pencil Pencil Pencil Pencil Pencil Undo Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Undo Pencil Pencil Pencil Pencil Pencil Undo Pencil Pencil Pencil Reset Swatches Exchange Swatches Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil 2019-05-13T19:14:07-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-48.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-48.png With Copy Select history state -117 Set Foreground Color To: RGB color Red: 0 Green: 0 Blue: 0 Set Foreground Color To: RGB color Red: 255 Green: 61.004 Blue: 61.004 Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 35 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Image Size Image Size Width: 48 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Select pencil Pencil Pencil Pencil Pencil Select layer “full wave” Without Make Visible Pencil Undo Select next document Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Exchange Swatches Pencil Undo Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Undo Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Magic Wand Add To Selection From: 14 pixels, 27 pixels Tolerance: 32 Magic Wand Add To Selection From: 11 pixels, 27 pixels Tolerance: 32 Without Contiguous Paint Bucket Fill From: 14 pixels, 33 pixels Tolerance: 55 Using: foreground color Without Contiguous Deselect Set Selection To: none 2019-05-13T19:25:41-07:00 File Full-48.png closed Close 2019-05-13T19:26:03-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-48.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-48.png With Copy Select history state -54 Select previous history state Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 30 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Image Size Image Size Width: 64 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Select layer “full wave” Without Make Visible Magic Wand Add To Selection From: 18 pixels, 37 pixels Tolerance: 32 Without Contiguous Deselect Set Selection To: none Exchange Swatches Select pencil Select next document Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Exchange Swatches Select next document Pencil Pencil Pencil Pencil Pencil Undo Redo Undo Select previous history state Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Undo Pencil Pencil Pencil Pencil Pencil Pencil Pencil Undo Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Undo Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Pencil Undo Pencil Pencil Pencil Pencil Set Foreground Color To: RGB color Red: 229 Green: 229 Blue: 229 Pencil Pencil Pencil Set Foreground Color To: RGB color Red: 60 Green: 60 Blue: 60 Pencil Pencil Pencil Set Foreground Color To: RGB color Red: 243 Green: 243 Blue: 243 Pencil Set Foreground Color To: RGB color Red: 60 Green: 60 Blue: 60 Pencil Set Foreground Color To: RGB color Red: 243 Green: 243 Blue: 243 Pencil Set Foreground Color To: RGB color Red: 60 Green: 60 Blue: 60 Pencil Set Foreground Color To: RGB color Red: 255 Green: 255 Blue: 255 Pencil Pencil Pencil Set Foreground Color To: RGB color Red: 60 Green: 60 Blue: 60 Pencil Pencil Set Foreground Color To: RGB color Red: 243 Green: 243 Blue: 243 Pencil Pencil Set Foreground Color To: RGB color Red: 60 Green: 60 Blue: 60 Pencil Pencil 2019-05-13T19:35:15-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-64.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-64.png With Copy Select history state -79 Select previous history state Select previous history state Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 25 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Disable layer effects Hide layer styles of current layer Enable layer effects Show layer styles of current layer Show layer “O - large” Hide layer “SCAR - large” Hide layer “O - small” Show layer “SCAR - large” Show layer “SCAR - small” Hide layer “SCAR - small” Image Size Image Size Width: 128 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-13T20:09:53-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-128.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-128.png With Copy Select previous history state Image Size Image Size Width: 256 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper Undo Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 20 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Image Size Image Size Width: 256 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-13T20:13:10-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-256.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-256.png With Copy Select previous history state Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 12 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Image Size Image Size Width: 512 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-13T20:14:38-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-512.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-512.png With Copy Select previous history state Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 20 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Disable layer effects Hide layer styles of current layer Enable layer effects Show layer styles of current layer Image Size Image Size Width: 512 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-13T20:16:34-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-512.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-512.png With Copy Select previous history state Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 10 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Outer Glow Set Layer Styles of current layer To: layer styles Scale: 100% Outer Glow: outer glow With Enabled Mode: screen Color: RGB color Red: 255 Green: 255 Blue: 255 Opacity: 100% Technique: precise Spread: 100 pixels Size: 12 pixels Noise: 0% Jitter: 0% With Anti-alias Contour: Contour Name: “Linear” Range: 50% Image Size Image Size Width: 1024 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-13T20:18:03-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-1024.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-1024.png With Copy Select previous history state Disable layer effects Hide layer styles of current layer Enable layer effects Show layer styles of current layer Disable layer effects Hide outer glow of current layer Disable layer effects Hide layer styles of current layer Image Size Image Size Width: 100 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-13T20:19:03-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-100.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-100.png With Copy Select previous history state Image Size Image Size Width: 220 pixels With Scale Styles With Constrain Proportions Interpolation: bicubic sharper 2019-05-13T20:19:41-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\Full-220.png saved Save As: PNG Format Interlace: None Filter: Adaptive In: E:\OSCAR\OSCAR-code\Building\Icons\Full-220.png With Copy Select previous history state Layer Order Move layer “O centered” To: layer 9 Without Adjust Selection Select layer “SCAR centered” Without Make Visible Layer Properties Set current layer To: layer Name: “SCAR centered - not used” Select layer “O centered” Without Make Visible Layer Properties Set current layer To: layer Name: “O centered - not used” 2019-05-13T20:20:35-07:00 File final w white glow-v2.psd closed 2019-05-13T20:20:35-07:00 File E:\OSCAR\OSCAR-code\Building\Icons\final w white glow-v2.psd saved 8BIMHH8BIM&?8BIM Transparency8BIM Transparency8BIMd8BIM5d8BIM8BIM x8BIM8BIM 8BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM8BIM8BIM0 8BIM-8BIM@@8BIM8BIM_77final w white glow-v277nullboundsObjcRct1Top longLeftlongBtomlong7Rghtlong7slicesVlLsObjcslicesliceIDlonggroupIDlongoriginenum ESliceOrigin autoGeneratedTypeenum ESliceTypeImg boundsObjcRct1Top longLeftlongBtomlong7Rghtlong7urlTEXTnullTEXTMsgeTEXTaltTagTEXTcellTextIsHTMLboolcellTextTEXT horzAlignenumESliceHorzAligndefault vertAlignenumESliceVertAligndefault bgColorTypeenumESliceBGColorTypeNone topOutsetlong leftOutsetlong bottomOutsetlong rightOutsetlong8BIM( ?8BIM H HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km8BIM!8BIM ,JFIFHH Adobe_CMAdobed            " ?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?TI%)$IJI$RYG_C̷$[sfis˛{裂ɬ`k95^k׿VG]srY]sºS;k` K߬QHuImWjrGᙿzlT>)|zKIJnouGމCr}L7s'6/%CS!_S6>w`z^$:o"u6ڝlc~oVٲI%)$IOTI%)$5czSN/rZx{\Tm)䐄˖g3@7NYے53?Z:V.ezcZ],,muw>Ǚs9nZY?iΣǨ>h?ϻ_OK[/2.ԋO bDvl,>`vXZi=G|W]On6M뵽=I[ՅW7IJH1;Tx~"r_d^fЫq{6+?'?Z*RU3T8}co9w/6ۨe?Yvn^C- I:<1W%yoIVRsqj{=x8ޚ^@p-pt B~E쮌pN+m_+>#D>>b /:[-5ysqn.Aѷ )yw{=a-{s\4s^\Uܶ,=]&>fk.}%}R3_MoC !Zv\?Yi?6|>. (*I$,TPisx h}kލvD!j{%{&\8'9\=RޯmβC^v7?^H{`Z2sd䎐N՞='Kq(8H;IR?>Eu]U[k4kZZ՗c*2,.IpOMOmk,s9r>inKq k?TI*$I)I$JRI$WաC&7khξ7CρHk~a}-ۋ6;gnkS?X9_tGO wQ>鶖#uuc5?3!p7dUPϘ?? 1f`w'ds9˴xΒ4f Lww AЍ jU~'7 ,~lwy]5{LmkǞ7}g5y/"7kOt8Mmf6=Xw_cYZ]gs2{SKk]uַ3> 9%Zrώ>6=KY.ꃡ2ŗd٭uG?Cb :u̳MON9q MpE Fv\>:N,sEnˬc},w轿Ү,d]!GS,rZDї cadȪ7 בmFRARJ4]Uv{k$pn7P v]% - RӆwAMW}Q=U2͹!4e=X}$# C "L cF H z{wlyer2ٖ&uu~?XWllݷ5=zRÞU`OF?UXnu{X~|>ֽmx{u ξGcא}oO;Ƞ]?\t3b6|Eۖs voλH.*K7[~s5GK}ȹ#-:? KkϚWnqKrAnGVR=cKCXKL$Βy߀T@Zؓu!9RwJok:gl3'wVwo:~3}kڣԍ\+t^Ee4=-5Oجd! /;qi.{7փִ5IgZf-8kz8nV5Lȑ)qzя6aOc~:nS Yi^VG{nJ~ػ. l2W7f'̊,YjmE of֟V%u_S1O s0,/K.!HHg3p`fZ>5`^tVlc#kIWK%YeQXwdv06k[z)%pk]q4>0F?B;c^[~nWռdT>XٹMO"c&RÊ1/ї}Y%<+@p6YJ9:ǂ^qߑ~T*ɚf~? }`[ztܲI6c8~SmYf6exK?>l0łzs+l}_[o>яc2JOKk53E/5ۜcIgڬ ׷&=WYE8x>v[p `[_LG}2/OUew}z~meW صk4@ΝY}[feYw\,n;^EwrYZ]Ҿ]V -ӽ,^/MIH?7׋ˌ"@|x5~gt[ӦoRa!f^}MUeX2r*UsZZ_x}95̚w4L9}g>V)d~(@ې67oF}a!b\bDK8+~VCWv@o}4UUǨs}Z,Ⱦ_ QeeP̡gE߭zb7`rj|N:~>Ui}atց>rw7gZ#dQB[Gu gb)[hh/kVX={ np/ca{sk,__ [+ 9tyuti I8V?=B< nGLU]_U1(:}u _@;?XF|wdWVY/QլWQ{.-w1s7(/&fEWv=Uqxx8nS 1+i{\wvwZŽS%E]LՂ9aX1t?G}SǀWcWoW%8^~%+Wᑬ%U_q=vm zBO9& ~vEv_k7|>>^U}ccVjKlAc!6C;;#%<; USEhٖ赗$5}PgJyA`ﵣ;댱x_aA!+Ɍ㜠wǔN-:>b㰐,nꭍ~FMb}yyϡ9ӋNcu6 6.k)e %$ſiޛu%Ma5}QoE}-7l^V5VQ1eYv07{u- Ҧ+"=7zU}ooZ~*#e&GO`-&$Բ33=ǩ mϳu1쾚gYOzB뷦Vn!崱v-?~c(gݟNvvMV;SƾeپmޏikҮ=_d1O~Zdj\r6Tޛ-l%J>`q~2ci۟ȆdbO?7x\1 3&\oRz7W}7v=TkRg=]&c""7&25@Ytx~ru79 #5* ܣaߝN? i V:?2nBm/1"rd'yN__`z*ve?ꚙAūZUC[xh%s{%?:w^?o!'T;"(깮ek7DOu>wLϿ]C?joWԱn# qA4IyW?¯9#Px[q &?<7˜9H kֿcFk"*Ge ]:Ϫ] LVyZgae}9pqW3F~~!vP=c kЏfhu̳K1p4c5u9UnMyyTTkmU421CZ֏֩,];P-m#5sVӷ;#\Aޟeo*mOgZLֵ h kD5@RcIᴺ>uY8%T9l$`ۍԜwnK -ۇc?wOſI%9?YtUWs62rmw)-z:S69u,gGCAlQB}}%ak ѧMۿԫ@^.P'G ta *KwׯHy , Gi65??@r?.ow'}e-wD׊?lC_#~מ]5k[ 7EWRN32q5uu7w+լ_VyoMW9_b˩LΫyѭ1G[wk}YWsréPx}ɧjn?xŹYʸj1L~vGEccHqo/JRyd~)ǥP]U1DI%RI$TI%)$IJC9?Z@֡U{w.uO<wvVr?UcٲhThנ$Ý!_['#S ?`_YX`b6\Cg}f'ݧK/RIK%|h|ƯY,0vT<_k#6 os^Z˦Zw&91K\n%/uLjuoꮂiEmh0Ud˓'#/6> /ParagraphRun << /DefaultRunData << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> /RunArray [ << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> ] /RunLengthArray [ 2 ] /IsJoinable 1 >> /StyleRun << /DefaultRunData << /StyleSheet << /StyleSheetData << >> >> >> /RunArray [ << /StyleSheet << /StyleSheetData << /Font 1 /FontSize 245.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 1555.19995 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> /StrokeColor << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst false /YUnderline 1 /OutlineWidth .01 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> >> ] /RunLengthArray [ 2 ] /IsJoinable 2 >> /GridInfo << /GridIsOn false /ShowGrid false /GridSize 18.0 /GridLeading 22.0 /GridColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /GridLeadingFillColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /AlignLineHeightToGridFlags false >> /AntiAlias 4 /UseFractionalGlyphWidths true /Rendered << /Version 1 /Shapes << /WritingDirection 0 /Children [ << /ShapeType 0 /Procession 0 /Lines << /WritingDirection 0 /Children [ ] >> /Cookie << /Photoshop << /ShapeType 0 /PointBase [ 0.0 0.0 ] /Base << /ShapeType 0 /TransformPoint0 [ 1.0 0.0 ] /TransformPoint1 [ 0.0 1.0 ] /TransformPoint2 [ 0.0 0.0 ] >> >> >> >> ] >> >> >> /ResourceDict << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (BookAntiqua-BoldItalic) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> /DocumentResources << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (BookAntiqua-BoldItalic) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> >>warp warpStyleenum warpStylewarpNone warpValuedoubwarpPerspectivedoubwarpPerspectiveOtherdoub warpRotateenumOrntHrzn8BIMluni O - large8BIMlyid 8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMshmdH8BIMcust4metadata layerTimedoubA6~hJZ8BIMfxrp@g`@y`I1f"IVVV8BIMnorm&l( SCAR - large8BIMTySh%$??]-ܒ#@.p@r[s2TxLrTxt TEXTSCAR textGriddingenum textGriddingNoneOrntenumOrntHrznAntAenumAnntantiAliasSharp TextIndexlong EngineDatatdta#V << /EngineDict << /Editor << /Text (SCAR ) >> /ParagraphRun << /DefaultRunData << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> /RunArray [ << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> ] /RunLengthArray [ 5 ] /IsJoinable 1 >> /StyleRun << /DefaultRunData << /StyleSheet << /StyleSheetData << >> >> >> /RunArray [ << /StyleSheet << /StyleSheetData << /Font 1 /FontSize 185.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 270.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> /StrokeColor << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst false /YUnderline 1 /OutlineWidth .01 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> >> ] /RunLengthArray [ 5 ] /IsJoinable 2 >> /GridInfo << /GridIsOn false /ShowGrid false /GridSize 18.0 /GridLeading 22.0 /GridColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /GridLeadingFillColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /AlignLineHeightToGridFlags false >> /AntiAlias 4 /UseFractionalGlyphWidths true /Rendered << /Version 1 /Shapes << /WritingDirection 0 /Children [ << /ShapeType 0 /Procession 0 /Lines << /WritingDirection 0 /Children [ ] >> /Cookie << /Photoshop << /ShapeType 0 /PointBase [ 0.0 0.0 ] /Base << /ShapeType 0 /TransformPoint0 [ 1.0 0.0 ] /TransformPoint1 [ 0.0 1.0 ] /TransformPoint2 [ 0.0 0.0 ] >> >> >> >> ] >> >> >> /ResourceDict << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (BookAntiqua-BoldItalic) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> /DocumentResources << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (BookAntiqua-BoldItalic) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> >>warp warpStyleenum warpStylewarpNone warpValuedoubwarpPerspectivedoubwarpPerspectiveOtherdoub warpRotateenumOrntHrzn8BIMluni SCAR - large8BIMlyid8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMshmdH8BIMcust4metadata layerTimedoubA6~f@8BIMfxrp@@xG48BIMnorm &\( O - small8BIMTySh%?H.?]-ܒ#@rY"d@3ːi2TxLrTxt TEXTO textGriddingenum textGriddingNoneOrntenumOrntHrznAntAenumAnntantiAliasSharp TextIndexlong EngineDatatdta#U << /EngineDict << /Editor << /Text (O ) >> /ParagraphRun << /DefaultRunData << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> /RunArray [ << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> ] /RunLengthArray [ 2 ] /IsJoinable 1 >> /StyleRun << /DefaultRunData << /StyleSheet << /StyleSheetData << >> >> >> /RunArray [ << /StyleSheet << /StyleSheetData << /Font 1 /FontSize 245.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 1555.19995 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> /StrokeColor << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst false /YUnderline 1 /OutlineWidth .01 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> >> ] /RunLengthArray [ 2 ] /IsJoinable 2 >> /GridInfo << /GridIsOn false /ShowGrid false /GridSize 18.0 /GridLeading 22.0 /GridColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /GridLeadingFillColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /AlignLineHeightToGridFlags false >> /AntiAlias 4 /UseFractionalGlyphWidths true /Rendered << /Version 1 /Shapes << /WritingDirection 0 /Children [ << /ShapeType 0 /Procession 0 /Lines << /WritingDirection 0 /Children [ ] >> /Cookie << /Photoshop << /ShapeType 0 /PointBase [ 0.0 0.0 ] /Base << /ShapeType 0 /TransformPoint0 [ 1.0 0.0 ] /TransformPoint1 [ 0.0 1.0 ] /TransformPoint2 [ 0.0 0.0 ] >> >> >> >> ] >> >> >> /ResourceDict << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (BookAntiqua-BoldItalic) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> /DocumentResources << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (BookAntiqua-BoldItalic) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> >>warp warpStyleenum warpStylewarpNone warpValuedoubwarpPerspectivedoubwarpPerspectiveOtherdoub warpRotateenumOrntHrzn8BIMluni O - small8BIMlyid8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMshmdH8BIMcust4metadata layerTimedoubA6~dP8BIMfxrp@hH@y`I1] >>>8BIMnorm &l( SCAR - small8BIMTySh%$?H.?]-ܒ#@s۾(@r[s2TxLrTxt TEXTSCAR textGriddingenum textGriddingNoneOrntenumOrntHrznAntAenumAnntantiAliasSharp TextIndexlong EngineDatatdta#V << /EngineDict << /Editor << /Text (SCAR ) >> /ParagraphRun << /DefaultRunData << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> /RunArray [ << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> ] /RunLengthArray [ 5 ] /IsJoinable 1 >> /StyleRun << /DefaultRunData << /StyleSheet << /StyleSheetData << >> >> >> /RunArray [ << /StyleSheet << /StyleSheetData << /Font 1 /FontSize 185.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 270.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> /StrokeColor << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst false /YUnderline 1 /OutlineWidth .01 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> >> ] /RunLengthArray [ 5 ] /IsJoinable 2 >> /GridInfo << /GridIsOn false /ShowGrid false /GridSize 18.0 /GridLeading 22.0 /GridColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /GridLeadingFillColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /AlignLineHeightToGridFlags false >> /AntiAlias 4 /UseFractionalGlyphWidths true /Rendered << /Version 1 /Shapes << /WritingDirection 0 /Children [ << /ShapeType 0 /Procession 0 /Lines << /WritingDirection 0 /Children [ ] >> /Cookie << /Photoshop << /ShapeType 0 /PointBase [ 0.0 0.0 ] /Base << /ShapeType 0 /TransformPoint0 [ 1.0 0.0 ] /TransformPoint1 [ 0.0 1.0 ] /TransformPoint2 [ 0.0 0.0 ] >> >> >> >> ] >> >> >> /ResourceDict << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (BookAntiqua-BoldItalic) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> /DocumentResources << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (BookAntiqua-BoldItalic) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> >>warp warpStyleenum warpStylewarpNone warpValuedoubwarpPerspectivedoubwarpPerspectiveOtherdoub warpRotateenumOrntHrzn8BIMluni SCAR - small8BIMlyid 8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMshmdH8BIMcust4metadata layerTimedoubA6~b$8BIMfxrp@IJ@xGs~8BIMnorm &(O centered - not used8BIMTySh%??]-ܒ#@rG,#Or@3ːi2TxLrTxt TEXTO textGriddingenum textGriddingNoneOrntenumOrntHrznAntAenumAnntantiAliasSharp TextIndexlong EngineDatatdta#U << /EngineDict << /Editor << /Text (O ) >> /ParagraphRun << /DefaultRunData << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> /RunArray [ << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> ] /RunLengthArray [ 2 ] /IsJoinable 1 >> /StyleRun << /DefaultRunData << /StyleSheet << /StyleSheetData << >> >> >> /RunArray [ << /StyleSheet << /StyleSheetData << /Font 1 /FontSize 245.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 1555.19995 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> /StrokeColor << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst false /YUnderline 1 /OutlineWidth .01 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> >> ] /RunLengthArray [ 2 ] /IsJoinable 2 >> /GridInfo << /GridIsOn false /ShowGrid false /GridSize 18.0 /GridLeading 22.0 /GridColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /GridLeadingFillColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /AlignLineHeightToGridFlags false >> /AntiAlias 4 /UseFractionalGlyphWidths true /Rendered << /Version 1 /Shapes << /WritingDirection 0 /Children [ << /ShapeType 0 /Procession 0 /Lines << /WritingDirection 0 /Children [ ] >> /Cookie << /Photoshop << /ShapeType 0 /PointBase [ 0.0 0.0 ] /Base << /ShapeType 0 /TransformPoint0 [ 1.0 0.0 ] /TransformPoint1 [ 0.0 1.0 ] /TransformPoint2 [ 0.0 0.0 ] >> >> >> >> ] >> >> >> /ResourceDict << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (BookAntiqua-BoldItalic) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> /DocumentResources << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (BookAntiqua-BoldItalic) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> >>warp warpStyleenum warpStylewarpNone warpValuedoubwarpPerspectivedoubwarpPerspectiveOtherdoub warpRotateenumOrntHrzn8BIMluni0O centered - not used8BIMlyid8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMshmdH8BIMcust4metadata layerTimedoubA688BIMfxrp@g@}PI}oa">VVV8BIMnorm &(SCAR centered - not used8BIMTySh%$??]-ܒ#@.p@b[s2TxLrTxt TEXTSCAR textGriddingenum textGriddingNoneOrntenumOrntHrznAntAenumAnntantiAliasSharp TextIndexlong EngineDatatdta#V << /EngineDict << /Editor << /Text (SCAR ) >> /ParagraphRun << /DefaultRunData << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> /RunArray [ << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> ] /RunLengthArray [ 5 ] /IsJoinable 1 >> /StyleRun << /DefaultRunData << /StyleSheet << /StyleSheetData << >> >> >> /RunArray [ << /StyleSheet << /StyleSheetData << /Font 1 /FontSize 185.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 270.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> /StrokeColor << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst false /YUnderline 1 /OutlineWidth .01 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> >> ] /RunLengthArray [ 5 ] /IsJoinable 2 >> /GridInfo << /GridIsOn false /ShowGrid false /GridSize 18.0 /GridLeading 22.0 /GridColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /GridLeadingFillColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /AlignLineHeightToGridFlags false >> /AntiAlias 4 /UseFractionalGlyphWidths true /Rendered << /Version 1 /Shapes << /WritingDirection 0 /Children [ << /ShapeType 0 /Procession 0 /Lines << /WritingDirection 0 /Children [ ] >> /Cookie << /Photoshop << /ShapeType 0 /PointBase [ 0.0 0.0 ] /Base << /ShapeType 0 /TransformPoint0 [ 1.0 0.0 ] /TransformPoint1 [ 0.0 1.0 ] /TransformPoint2 [ 0.0 0.0 ] >> >> >> >> ] >> >> >> /ResourceDict << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (BookAntiqua-BoldItalic) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> /DocumentResources << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (BookAntiqua-BoldItalic) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> >>warp warpStyleenum warpStylewarpNone warpValuedoubwarpPerspectivedoubwarpPerspectiveOtherdoub warpRotateenumOrntHrzn8BIMluni4SCAR centered - not used8BIMlyid8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMshmdH8BIMcust4metadata layerTimedoubA68BIMfxrp@@|GA9FJJJ8BIMnorm%(O filler - always on8BIMTySh$8??]-ܒ#@@2TxLrTxt TEXTO textGriddingenum textGriddingNoneOrntenumOrntHrznAntAenumAnntantiAliasSharp TextIndexlong EngineDatatdta"q << /EngineDict << /Editor << /Text (O ) >> /ParagraphRun << /DefaultRunData << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> /RunArray [ << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> ] /RunLengthArray [ 2 ] /IsJoinable 1 >> /StyleRun << /DefaultRunData << /StyleSheet << /StyleSheetData << >> >> >> /RunArray [ << /StyleSheet << /StyleSheetData << /Font 0 /FontSize 1296.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 294.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> /StrokeColor << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst false /YUnderline 1 /OutlineWidth .01 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> >> ] /RunLengthArray [ 2 ] /IsJoinable 2 >> /GridInfo << /GridIsOn false /ShowGrid false /GridSize 18.0 /GridLeading 22.0 /GridColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /GridLeadingFillColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /AlignLineHeightToGridFlags false >> /AntiAlias 4 /UseFractionalGlyphWidths true /Rendered << /Version 1 /Shapes << /WritingDirection 0 /Children [ << /ShapeType 0 /Procession 0 /Lines << /WritingDirection 0 /Children [ ] >> /Cookie << /Photoshop << /ShapeType 0 /PointBase [ 0.0 0.0 ] /Base << /ShapeType 0 /TransformPoint0 [ 1.0 0.0 ] /TransformPoint1 [ 0.0 1.0 ] /TransformPoint2 [ 0.0 0.0 ] >> >> >> >> ] >> >> >> /ResourceDict << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 1 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> /DocumentResources << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 1 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 /CharacterDirection 0 /HindiNumbers false /Kashida 1 /DiacriticPos 2 >> >> ] /FontSet [ << /Name (BookAntiqua) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> >>warp warpStyleenum warpStylewarpNone warpValuedoubwarpPerspectivedoubwarpPerspectiveOtherdoub warpRotateenumOrntHrzn8BIMluni,O filler - always on8BIMlyid8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMshmdH8BIMcust4metadata layerTimedoubA6~-l8BIMfxrp@>@,B|=3GA<7400.,*)((((&&&&&&&%"##!#$##""!"!! !  6MKF?=:8877421*112210/../....,++*,,-,,***(*+)*,)**)+*)***((%))((&(%&(&'('(((((''&%'&'((&'''''(((''''&&&&%&&&&&''(%&%&%''%%&&&$%%%&%%&&%&&$&%$&%&&%%%%#%#%&$&%%%#&%#%%$&#&#%%$%#%#%$$%$&"&"&"&#%#$$##$$#%"&"&$$%#$##$$"&###!"!!#""!#"#%"$#"$#$"#"####$####$#"$#"#$"#"$#"""#!!! # !!#!!"#""##"""###"##"""""""##""##"""#""" """"""""""" """"" "!!!!!!       !!!!!!!!!! """"""""""""" """"""" """""""""#""""#"""#"""#"#""""#""#"""%"""$"""#""###"##"##""!"# "!"!!"##"%""$#"####"$###$##%""#!#!!$!" #"!$""#%##$$$#%"$$###$"$"$###$$%$#%"&"%##%$%$$$%%#$$$%&$%#%$$%$$$%$&$&%#%%%$$#&$%$%$%&%%$&&&&#%#%$&%&'%&$$&%&%$&&&&%$&'%%&$&'&%&&'&&'&'&&&%%$&&%%&%&''%'''''('(('&&%&''(((('%'&()((())))('))(&'('*+*****,,*++,,*+++*,--.,..-/.0/101111+2565578;>BHL7  !!!""""###%#"%%&%$%%&''(')+,.026;?H0@ %:DdnydY:/-77A_}_U7# !44GZZmdZZ>44 (1:UUpyɫyygUU:1  &/@QYrr rrYQ@/  $,DLdk íkdLD, ")GOe{ {eOG)" 'DK`x x`KD' ,@SZ 뻣fS@, ";M` ī`M;)  1‡ϕ1ăʇϕՇϕy͕ە~ˈ}fƈu<;@ˆ7?ш+܈ΔΉϔ}߄ȉϔoϔ@Éϔ1„ΉĔ܊WъAƔKDΔ'ɊϔΔҔυۋɋjOËϋٓc͓Fѓ/͌Ɠm G߆͌ɓϓ΍Гct'R3э̒mՇŎ̒>ĎwێXʒr5яВD ߒˈՏՑw|ԐΑ9m‘ =Аϑ`ّΑ"‘UԑӐx6А8Ԓ؊NJɒ͐ːPi ?ē ۏؓɏUДWǏ+ʔG ٌؕȎnȕŽ:I ܖŎrƖ&{ٍLݗǍN̗Í ڍz՘s>&ҘnjƙČJr،1Ӌ`ƐƋǚIҋe՛ݑٛϊ~`0"ÜNjƜ7x˝ˋ9~ˋ*מq-۞ȋ/Όxvן#$ތkԠ_¡R֖ѡƍ AĢӍ4΢Ѝvqģ"%ӣЎ٘Oӎ :ޥ͏)T̎YԦ ˚Ŧӏr$ΧƏӛɧS=ܩȐD2ƪWK ~ SŬ&̬:Mr.Ʈ ܮ|{د¯̢n°+ɱBӱ/ֲ3)̥8_ ɴ<0˵Aԧkն(ٷ-1ŷթ%dŸ  Ĺ$ ëúwDެ»ch¼J$½2Gۯz]Q'c185:fõ @7ȶD̷SJѸ(Oq׹J/{> û6C ,? Gs 7Y?"m*Ln F&i5Xy.Bx\ &3x[ D(ISq!^1O'ni."`9Q`   J23l,pjEÿɾ"ORټQĻUiùAv / U"z' ,033X7|<@E+IANQ1t58<@CF Jڥ"`إ$zեեӦ   q hɥQÂ˥9˃ܥ-"@Då)ׇ˥ۈZ ߉?#ĥ#&٥) -ä}юQՏ6ِ  r ЃFz* Ɔ VtXJ ;ɋ)<ƚݍ!$e#ʐEҒ+ sٔsRFCɡ.1tLu/^Ӧq>>"X\˫14l6vԯۂ<H ߊ uֳN6߇Ffٷ݃!0m5&fZٽ?bٺrطII }AϿح!F;Ͽj "" W8\ 1 V $ " ;<@RD$i[ 2|a( <g+Gl/`r2'cc.)ii)<d,@bC^GߓZ7ΏK991}}&(ݧaqʔN]蹂-Hײa 1Ǘ=hּhQ˕69ݶpfу;>ڳMnn5΀5GG yK^BbFV YY MݝMQс26f& +zښK  _o0  DуD  WӕW .kk.Bٝ`$ $G͡e*)>yߑ‡[, &6oӕӨoD*&8X㫂X8& .@i[<. #2Fnٱ|aA2 &6Gg贚M<1 "5ASy 긫`M;" %9FZ~ ߻ZM@% '6D`l ᾦl`D=' )@Geee^G@)  ,4LTkk íkdLL,, / HQYrrƻ}rjQQ7/  11LUUpyy yy^UU11( +44>ZmwZG4@``@ (>82.-,*'('&$$"$##$"!""!!    !! !""""#$##&&%'(&)*-06<&&.j r8lagw||z'$4}Ax<s7n2i7P?9966/0+&   R7 ͪ toki%"" Ѱık*|P3bv>+ OO"k^'`+|Ka#ZR# s00 g}8 /*/Te.dz)zԲMܱ6plh+Svk og$_<9ѨD4˧K[ iiHaܤXP|I0ǡBi=# Zܞ {K8"FÛzX>Κi Wm \ʗI>!% JrȔm'ߓ{^ qkÑvbpZOB1ݎo{ȍD-Ōxہ4*yJY׊B$N9ωޅ0 BȆR<2zɈ·--̉Kp'zȊ#hQX Du V<MN~6J݄͎@[Ǐ(IMLL9G{eʐ8U‘ރRUMLJ˓8zPOLKDLvLLNFÔۄRqLHLLLILLLLLLLLItZJRr]DOL%LLLLLчLLLVL?LO:ՈE0މ}OҔ&nN^KN@ {GӓŌK`IGˍFߎ’k6XGď,>̐͑ILM‘>_ZQѓ;`Y$_Ŏg%fۍVy2Eڌߙo;$Ě؋lC 4Ӝej%Z7ވN> ‡D~)¡KچȢGQУNM HT 8C ܧނZ"8Áiت]1%g֬W?1</BT2 .´1ԵMwaqH(g 2c \, y^ ii F0#9j@GG,N TeE9<h/!#Po] {9IYH Q$ 55w 'WLx&e+[`,T^{  D9[bJ#``C¾7޽ik'o͹ @ D!& + 06!$$)6-D2J7O6U ["_$djtpvvn|bkAg6l f;>c 1s↙;PC ꇓJMm++Ix#RW ZuM'}22 !p$ /ɉ=Rx%-}9  =]'RҐ@.hWGm2QL\Y'nܙn'@uޛu@D{YH^LtOxS~WЧ;ө>|볂y۰hF̲ݭY#ɦIsPd|ҭ*AlYը. ]w M"Q%U),,`x $|7<i Wm  Er  5u^My:g}R !$Dll2$!  $(*\ppI*&!  &+/0uݜ Ɋu0/+"  !&156Cz̬ zC631&!  ##559::}}͑}}::955## ''-:@MM@:-'' #*<@<*# (?NE@;752/-+($#"'))((((&&'%&%$####$$$$$$##""!" "! !          ! !!! """"$#####$$$$$&$%&&&'(((((*("%&),,-147<FH:"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyxvutsrstuxzzzzzzzzzxwutsy}stuvxxyzzzzzzzzzyxwvut}tuvwxxzzzzzzzzzyxvvu}ݾuvxzzzzzzzzzywvvuu}лwuvvwxyzzzzzzzzzyxwv }ĺxvvwwyyzzzzzzzzzyxw | ͷ|wxyzzzzzzzzz yxwwz⵪zwwxyyzzzzzzzzzyx{ೞ|xyzzzzzzzzzyxy~ ǥ|xxyyzzzzzzzzzy|Ţ~zyzzzzzzzzzy} ۷~|yyzzzzzzzzzy|ž{yzzzzzzzzz{}˜}{zzzzzzzz|}}ə}|zzzzzzzz{ﻍ{{zzzzzzzzy۬yyzzzzzzzzxwvˆvwyzzzzzzzzyxv}םuvxzzzzzzzzvtާ{twzzzzzzzzvszzsvzzzzzzzzvrxxruyzzzzzzzzxqoެwqvzzzzzzzzxsn۟npvzzzzzzzzuo~ђnrxzzzzzzzzxqm}ntyzzzzzzzzun|ڥtowzzzzzzzzyrlʄlsyzzzzzzzzxnrעrnwzzzzzzzzvlzłlszzzzzzzskΟkpyzzzzzzzypkҰrnwzzzzzzzxprԽltzzzzzzzxnyŒlszzzzzzzxnxșlqyzzzzzzzxoxĨrqyzzzzzzzxoxƥroxzzzzzzzxpxïxpxzzzzzzzyrxǮxpxzzzzzzzyrsīxpxzzzzzzzusxsyzzzzzzzwptsyzzzzzzzyqttzzzzzzzysy챗qvzzzzzzzvu쨌rwzzzzzzzxsأsyzzzzzzztžyuzzzzzzzwvvvzzzzzzzyuuxzzzzzzzw}ۗvyzzzzzzzyvzwzzzzzzzwwyzzzzzzzyxzzzzzzzx~yyzzzzzzzyxzzzzzzzy}ņ{yzzzzzzzyzzzzzzz{́{zzzzzzz~~zzzzzzz}}{zzzzzzzzzzzzzzyyzzzzzzzyxzzzzzzzwuzzzzzzu{xzzzzzzyruzzzzzzwqyzzzzzzsvzzzzzypqzzzzzwunxzzzzzv}vzzzzzsqzzzzznnyzzzzzymswzzzzzxsڃszzzzzwzߟozzzzzsmyzzzzzsrxzzzzzoywzzzzznрszzzzzyoԗpzzzzzyrozzzzzysnyzzzzzyssxzzzzzxsxxzzzzzx~~wzzzzzx~ńuzzzzzx}ˎuzzzzzx}ڍtzzzzzx}ٕszzzzzx}tzzzzzy}tzzzzzy}uzzzzzy}uzzzzzxzzzzzxxzzzzzxxzzzzzyyzzzzzxyzzzzzyyzzzzzyzzzzzyzzzzz}zzzzz|zzzzz{}zzzzz{zzzzzyzzzzzxzzzzzvwzzzzzwxzzzzzxxzzzzzyyzzzzzywyzzzzzxwzzzzzspzzzzzvqzzzzzyszzzzzswzzzzzoxzzzzzt{yzzzzzwtzzzzzypzzzzzuszzzzztwzzzzzxyzzzzzyszzzzzqqzzzzzvuzzzzzyxzzzzztyyzzzzzvuzzzzzyuzzzzzuxzzzzzu~yzzzzzywzzzzzvzzzzzvyzzzzzyzzzzzvzzzzzxyzzzzzzzzzzyyzzzzzyyzzzzz~zzzzz{zzzzzyzzzzz{ڻzzzzz{zzzzz|zzzzz|zzzzzzzzzzܸzzzzzxzzzzzxyzzzzzzzzzz|vzzzzzyǵyzzzzzyzzzzzx״vzzzzzzzzzzvqzzzzzxzzzzzs}zzzzzytzzzzzwyzzzzzywzzzzzvxzzzzzxzzzzz{tzzzzzxzzzzz{qzzzzzxyzzzzz{tzzzzzxwzzzzzzzzzzxvzzzzzzzzzzyuzzzzz{yzzzzzxzzzzzxzzzzzwzzzzzxyzzzzz{zzzzzxyzzzzz}zzzzzy̦yzzzzzzzzzzyzzzzz|~zzzzzzzzzz}zzzzzzzzzz֣{zzzzz{zzzzzzzzzzzzzzzyzzzzzzzzzz~xzzzzzzzzzzxyzzzzzzzzzzyٟxzzzzz{zzzzzyzzzzzxxzzzzzzzzzzŝszzzzztzzzzzvzzzzzyԜzzzzzyxzzzzz|zzzzzxyzzzzz~yzzzzzzzzzzxvzzzzz{zzzzzxzzzzzx|zzzzzzzzzzvzzzzzxzzzzzyzzzzz|zzzzzyzzzzz|xzzzzzzzzzzזzzzzzzzzzzzzzzzǕyzzzzzy}zzzzzzzzzzzzzzz~zzzzz{zzzzzzzzzzȓzzzzzzzzzzyzzzzzzzzzzڒzzzzzyyzzzzzzzzzzzzzzzߑyzzzzzxzzzzzzzzzzxzzzzzܐzzzzzxzzzzzyzzzzzzzzzz؏zzzzzxzzzzztzzzzzzzzzzʎzzzzzwzzzzzzvzzzzzzzzzzzzxzzzzz~zzzzzyzzzzzzzzzzwzzzzzzzzzzzzyzzzz~zzzzzzzzʂzzzz܋yzzzz~zzzz{zzzzЃzzzzzzzz̊}zzzzzzzz̄zzzz{zzzz{zzzzzzzz҉ƅzzzzzzzzyzzzzzzzzzzzzzzzzyzzzzΈzzzzzzzzӇzzzzxzzzzvzzzzzzzzzzzz͇zzzzyzzzz|zzzzzzzzvzzzzzzzzyzzzz{zzzzΆzzzz܆zzzzzzzzyzzzz{zzzzwzzzzzzzzԋzzzzzzzząyzzzzӅzzzzمzzzzzzzz݌zzzzzzzzyzzzzzzzz}zzzzzzzz֍zzzzzzzzzzzz҄{zzzz҄zzzz҄zzzzЎzzzzzzzzzzzzzzzzwzzzzzzzzzzzzʏzzzzwzzzzzzzzzzzzxzzzzzzzzzzzzzzzz͐zzzzʃzzzzԃzzzz҃zzzz҃xzzzz҃zzzz҃zzzz҃zzzz҃zzzzЃӑzzzzكzzzzzzzzzzzzzzzzxzzzzzzzzzzzzzzzzzzzzԒzzzz֒zzzzzzzzzzzzzzzzzzzzzzzz{zzzzzzzzzzzzzzzzӃГzzzz҃ғzzzz҃ѓzzzz҃zzzz҃zzzz҃zzzz҃zzzz҃zzzz҃zzzz҃zzzz҃vzzzzփzzzzzzzzzzzzzzzzzzzzДzzzzӔzzzzҔzzzzϔzzzzzzzzwzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz΄zzzz҄{zzzzӄzzzzτzzzzzzzzzzzzzzzzzzzzzzzzyzzzzzzzzzzzzzzzzzzzzzzzzzzzzԅzzzz҅Еzzzzօҕzzzzҕzzzzҕzzzzҕzzzzҕzzzz{ҕzzzzҕzzzzҕzzzzҕzzzzҕzzzz܆ҕzzzzӆוzzzzƆzzzzzzzzzzzzzzzzvzzzzzzzzzzzzzzzzzzzzчzzzzLJzzzzzzzzzzzzzzzzxzzzzvzzzzzzzzzzzzΈzzzzzzzzzzzzzzzzxzzzzzzzzzzzzzzzz҉zzzzzzzzzzzzӔzzzz{ҔzzzzҔzzzzՔzzzz؊zzzzʊzzzzzzzzzzzz{zzzzzzzzyzzzzދzzzzzzzzzzzzzzzzxzzzzzzzzՓzzzzӓzzzzȓzzzzzzzzwzzzzzzzzzzzzčwzzzzzzzzzzzzxzzzzzzzzҎߒzzzzђzzzz˒zzzzvzzzzzzzzzzzzxzzzzzzzzwzzzzzzzzzzzzĐёzzzzőzzzzzzzzzzzzߑ{zzzzzzzzzzzz|zzzz֐zzzzےՐzzzzzzzzzzzz{zzzzyzzzzƓzzzzzzzz}܏zzzzyÏzzzzؔzzzzzzzz{zzzzxyzzzzەzzzzzzzzŎzzzzwzzzzޖzzzzvzzzzzzzzwzzzzחԍzzzzzzzzzzzzx|zzzzϘyzzzzzzzz|ʌzzzzyzzzzřzzzzvzzzzxzzzzzzzz̋zzzz~zzzzzzzzÛzzzzzzzzҊzzzzzzzzzzzz}{zzzzzzzzÝ؉zzzzzzzzzzzzҞ{zzzzzzzz|шzzzzyןzzzzzzzz|yzzzzy۠zzzzzzzz}zzzzyߡzzzzyzzzz}ˆzzzzyzzzz~zzzz}xzzzzxӅzzzzzzzzxzzzzyۤxzzzzքzzzzyzzzzyԥzzzzxzzzztσzzzzŦzzzz{zzzzxyzzzzĂzzzz{zzzzyڨxzzzz߁zzzzwzzzzɩ~zzzzzzzzzyzzzzzzzzzzyzzzzzzҫzzzzzzzzzzzz{|zzzzzzzzzzz}zzzzzЭzzzzzzzzzzzzzzzzzzzz{|zzzzzyyzzzzzzzzzzx۰|zzzzzyzzzzzyzzzzz|zzzzz{yzzzzzyzzzzzyzzzzzwѳyzzzzzzzzzzuߴtzzzzzzzzzztzzzzzvzzzzzxzzzzzy|zzzzzvxzzzzzyzzzzz~yzzzzzxȸyzzzzz}zzzzzx͹vzzzzz~zzzzzxкyzzzzzyzzzzzxջzzzzzvzzzzzyټzzzzz}zzzzzyܽyzzzzzzzzzzy׾yzzzzzzzzzzѿyzzzzzzzzzzzzzzzzzzzzzzzzz{zzzzzyzzzzz~zzzzzyyzzzzzzzzzzyyzzzzz{zzzzzyxzzzzzyzzzzzwzzzzzvzzzzzxzzzzzxzzzzzxzzzzzyyzzzzzwwzzzzzyyzzzzztwzzzzzyzzzzzwvzzzzzuyzzzzzyrzzzzzszzzzzszzzzzwzzzzzvtzzzzzyzzzzzvvzzzzzt|zzzzzyxzzzzzsvzzzzz~yzzzzzxyzzzzzsyzzzzzuzzzzzyzzzzzuwzzzzz|zzzzzyzzzzzxxzzzzzzzzzzzzzzz|zzzzzzzzzz~zzzzz}zzzzz|zzzzzzzzzz|zzzzzyzzzzzxyzzzzzxxzzzzzzzzzzxxzzzzzvzzzzzvyzzzzz}vzzzzzxzzzzzuyzzzzzuvzzzzzy~uzzzzzxyzzzzztvzzzzzqvzzzzzsyzzzzzywzzzzzwrzzzzzsszzzzzpyzzzzztvzzzzzyrpzzzzzyszzzzzwyzzzzzsvzzzzzrrzzzzzouzzzzzvyzzzzzyuwzzzzzyuzzzzzxszzzzzxyzzzzzxzzzzzwyzzzzzwyzzzzzxxzzzzzyyzzzzz{zzzzz}zzzzzzzzzz{zzzzzy|zzzzzŀzzzzzyށyzzzzzzyyzzzzzzyxzzzzzzxwzzzzzzwwzzzzzzvxzzzzzzvwzzzzzzuwzzzzzztvzzzzzztvzzzzzzvuzzzzzzvuyzzzzzzu}tyzzzzzzx~tyzzzzzzxstyzzzzzzyssyzzzzzzyosyzzzzzzpoyzzzzzzrozzzzzzsozzzzzzwyozzzzzzxrٚszzzzzzymہszzzzzzpԁwzzzzzztrwzzzzzzwrsyzzzzzzymmyzzzzzzqozzzzzzu|szzzzzzxm}wzzzzzzquxzzzzzzupyzzzzzzypszzzzzzsvzzzzzzwzzyzzzzzztuzzzzzzwwzzzzzzywyzzzzzzyyzzzzzzzzzzzz|}zzzzzz|~zzzzzz|zzzzzzy}ہzzzzzzy~yzzzzzzy|ӄyzzzzzzzxxzzzzzzzxyyyzzzzzzzzw횂wzzzzzzzzyvxxzzzzzzzzwz嘀wzzzzzzzzyvҎuxzzzzzzzzxtʛzvzzzzzzzzwv騅tyzzzzzzzzuyœuwzzzzzzzzysЕǢytyzzzzzzzzwrڗڨryzzzzzzzzvpqwzzzzzzzzuttuzzzzzzzzyssssyzzzzzzzzyrsʫxpyzzzzzzzzyrrԷ~pxzzzzzzzzyrrֹnwzzzzzzzzyqrؼnvzzzzzzzzyqrڿmvzzzzzzzzyqlˆmvzzzzzzzzslպmvzzzzzzzzslؽynvzzzzzzzzvnyְynxzzzzzzzzxorԨrpxzzzzzzzzyslѠlryzzzzzzzzvm{ǃmszzzzzzzzxqlޮsnwzzzzzzzzsm|Ԑmryzzzzzzzzxqm}ovzzzzzzzzupuђnsxzzzzzzzzytovpvzzzzzzzzyro츀puyzzzzzzzzxsqЌquyzzzzzzzzwsrӍrtxzzzzzzzzwusՏsuyzzzzzzzzxvuבuvyzzzzzzzzywwˆvwyzzzzzzzzyx一yyzzzzzzzz{ӗ{{zzzzzzzz|}}౅}|{zzzzzzzz{}Ⳉ~|zzzzzzzzy|䶊}zyzzzzzzzzy| ۫~zyyzzzzzzzzy{~繘}yzzzzzzzzyx|޼{xyzzzzzzzzyx~೔|xyzzzzzzzzy xwwz ⵠywwxyyzzzzzzzzyxw| ·zwxyzzzzzzzzzywwv  ĺvwyzzzzzzzzzz yxwwvuuwƻ wuvvwwxyzzzzzzzzzzxvuuvxzzzzzzzzzzxvutvvtuvvxzzzzzzzzzzxvutuvxzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz6D@;600.,&'&# #%''&&&&&&%$%$""#"#"#$##"""!!! !  ! !"""!""""""$$$$$%%&&&'((('&##$%'&*-.18@D2́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́̅˲̝̤̫̑́́́́̓̑́́́́̑́́́́̑́́́́ ̱̑́́́́ ̶̑́́́́ ̻̑́́́́ ̑́́́́̑́́́́ ͔̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́́́́́ӕ́́́́˚́́́́˞́́́́ˢ́́́́˥́́́́ʨ́́́́ʫ́́́́ʮ́́́́˱́́́́˴́́́́ʶ́́́́˹́́́́ɻ́́́́˾́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́̂́́́̄́́́̅ʁ́́̆ɂ́́̈˄́́̉ʅ́́̊Ȇ́́̋LJ́́̌ˉ́́̎Ɋ́́̏ȋ́́̐nj́́̑ˎ́́̒ˏ́́̓ɐ́́̔ȑ́́̕Ȓ̖́́Ǔ̗́́˕̙́́˖́́̚˗̛́́ʘ̜́́ʙ̝́́ʚ̞́́ɛ̟́́ʜ̠́́ʝ̡́́ʞ̡́́ʟ̡́́ˠ̣́́ˡ̤́́ˢ̤́́ע̧̦֣́́́́դ̧́́ԥ̨́́Ҧ̩́́Χ̪́́Ψ̪̫̬̭́́ͩ́́́́́́ˬ̮́́˭̯́́ˮ̰́́˯̱́́ӯ̱́́˰̲́́˱̳́́Ȳ̴́́ɳ̴́́ɴ̵́́˵̶́́˶̷̸́́́́ɷ̸́́ȸ̹́́ɹ̺́́˺̻́́κ̺́́ɻ̼́́ɼ́́̽ʽ́́̽˾́́̾́́̿ʿ́́̿́́́́́́́́́́́́́́́́ݿ́́Ͼֽ́́́́́́́́́́һֺ́́́́́́́́́́ϸַ́́́́́́́́ݶ́́́́́́́́́́́́́́Բܱ́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́ߩ́́́́ب֧́́́́́́́́Ц́́́́Ύ́́́́́ۤ́́́́ѣ́́́́́́́́́́֡́́́́Π́́́́́́ӟ́́́́́́ޞ́́́́Ν́́́́́́ל́́́́́́ڛ́́́́Κ́́ޚ͙́́́́́́́́́́Ϙ́́́́́́Ή́ߗ͖́́́́́́ؖ́́́́́́̕ӕ́́́́́́Δ́́́́́́̓́́ԓ́́́́́́̒́́ߒ́́́́́́ё́́́́́́́́А́́́́́́́́Ϗ́́ޏ́́́́́́ώ́́ڎ́́́́́́́́ҍ́́́́́́́́̌́́Ռˁ́Ё́́́͋́׋Ԃ́́́΃́̊ۃ́ъ́ފ́̈́́ل́́̉́Ӊ́ۉ؅́́́́̈ֆ́Έ́و́ˇ́ч́݇́́ˇ́чʈ́؇ֈ͉́́́́́Չ́ʆ́Ն́ۆ́́Ԋ́́́́́˅Ӌ́҅́܅́ޅ́ˌ́ό́ڌ́́́́̄́̄΍́̈́ݍ́؄́ۄ́܄́́́׎́ێ́́́́́̃ˏُ́̃́̃ޏ́̃́˃́ԃ́ڃ́ۃː́ۃА́ۃא́ڃ́́́́́ˑ́ϑ́ב́ݑ́́́́́́́˒́В́ܒ́ݒ͓́́́́́́́́́́ۓ́ۓ́ۓ́́́́́́́́́́́ʔ́ה́ܔ́܃۔́ۃٔ́ۃ́ۃ́ۃ́ۃ́ڃ́у́˃́̃́̃́̃́̃́́́́́́́́́ѕ́ܕ́ە́ڄە́ۄە́ڄە́҄ە́̄ە́̄ە́̄ە́ە́ە́ە́ڕ́́́́ޅ́ۅ́܅́҅́̅́̅́́́́́́́߆ٕ́܆ە́ӆە́ˆە́̆ە́ە́ە́ە́ە́ە́݇ە́؇ە́Їܕ́ˇו́ʕ́́́́́ڈ́ֈ́ˈ́̈́́́́́܉́ԉ́̉́́́́ܔ́ߊ۔́ъ۔́̊ٔ́ϔ́́́́ڋ́ΰ́́́́ی́ьߓ́ړ́ړ́ғ́˓́ڍ́ύ́́́́́׎́ʎޒ́ڒ́Ւ́˒́؏́ˏ́́́́ې́̐ۑ́ۑّ́͑́́́͑́́́ߒސ́ђܐ́̒ϐ́ː͓́́ؓ́́́ߏ́ޔӏ́ϔ́́́ߕ́Е́́ӎ́ʎ́і́́́ޗ́ϗ֍͍́́́ژ́́́͘׌́әˌ́́́́Κً́΋́́כ́́́Ԋ́́́́͜ѝމ́щ́́מ́́͞܈́؟Έ́́́͟ڠ́͠ԇ́́ۡ́́͡ۆ́ܢΆ́͢ˆۣ́́܅́΅́˅݄́ۤ́́τ́ץ˄́́ڃ́Φ́́ާ́ӂ́˂́ר́́́́ѩ́́́́ߪ́́́́́́ԫ́́́́ݬ́́ͬ́́́́ҭ́́́́ٮ́́́́́́Я́́́́հ́́́́ݱ́́́́́́в́́́́ӳִ́́́́́́́́۵́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́ξ́́́́Ͽ̸̸̷̶̶̵̵̴̨̧̧̢̢̡̛̼̼̺̻̺̹̹̳̳̲̱̱̰̰̯̮̮̭̬̫̫̪̩̩̦̥̥̤̤̣̠̟̟̞̝̜̜̙̘̘̗̖́́́́́́́́́́́́́́́́́́̿́́̾́́̾́́̾́́̽́́̽́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́̔́́̓́́̓́́̒́́̑́́̐́́̏́́̎́́̍́́̍̚̕ɿ́́̌́́̋н́́̊Լ́́̉́́̈˻́́̆́́̅Ϲ́́̅и́́̄ٷ́́̃ڶ́́̂Ͷ́́́έ́́ϴ́́́ѳ́́́˳́́́˲́́́˱́́́˰́́́˯́́́ˮ́́́ʭ́́́ʬ́́́ʫ́́́ʪ́́́ʩ́́́ʨ́́́ɧ́́́ɦ́́́ɥ́́́Ȥ́́́ȣ́́́Ȣ́́́ȡ́́́ɠ́́́ɟ́́́˞́́́˝́́́ɛ́́́ǚ́́́ș́́́ɘ́́́˗́́́˖́́́Ȕ́́́ɓ́́́˒́́́́́́ʏ́́́ˎ́́́Ό́́́́́́Ӊ͈́́́́́́·́́́͆́́́τ́́́܂΃́́́с́́́ˁ̼̹́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́̿́́́́́́́́˽̶́́́́˹̲́́́́˶̮́́́́˲̩́́́́׭̨̡̤̝̘̖́́́́ͬ́́́́ͩ́́́́ͥ́́́́́́́́́́́́͡͝ ˛́́́́̒˗́́́́̎˒́́́́̈ ̑́́́́̂ ̑́́́́́̑́́́́́̑́́́́́̑́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́т&/:9;CNKRSROQPPNRMQPPNNKJNJNNNNILLLPKLJNMOLLHNMOMNILLOMNNIJKGHIKKHKKMHJKHIJIHGKILIHFIGHMKJIKKKLKJHNSbqynVPOMPMPLNONKMFJIKKKILNLMLKHJJFKFKLJKHJJJONNONONOOLLLLMOONOOOMNMLLLLJKMLNMNMNLIKNLMMLJJKFHHIIHKLLKKKILJLJFDDFIJIIKKONNNMHNKKHE?:75155)!Ϧ蹎ފ ǧ ߲тyڋzήxxy~|}w}Քĵ~ƕ~|w|~Ƹx{Ә{~ܺz{wzÚԻxΛ|~Ԝܽ{~wxxܝzϾ|y؞{ٿw׿z}ٟ{}Ѡzw~zx|~١||z١{~xۡy١z~zyxyy}ڥz{yxwˬϭzϯϲߛxϽʏԚزͦyӒϒлétnmķѶεѫitssnЪإΨtzuvvpƦ ߷pyýɫ xuvvoؐlzx}bmmnvw|}zrqu muq|t  skmmntvy޹k  Uajkkjnڳs lmmlrn{} | ~|w||zz~՝yǦyyxٚx|x|x~|yףx|~|˗ٖӠ}yɝy}ט{|ٖywӔ|Γyxڒwzwxȑxё{yԏُy|̿޿~{ؾ{y|ۍͽx|όȋz޻պ{}{wlj~xވzɷ˶Åxz~z߄{zw{~}Ȱw|ρչĽ۹ɩ&/:9;CNKRSROQPPNRMQPPNNKJNJNNNNILLLPKLJNMOLLHNMOMNILLOMNNIJKGHIKKHKKMHJKHIJIHGKILIHFIGHMKJIKKKLKJHNQb~kVPOMPMPLNONKMFJIKKKILNLMLKHJJFKFKLJKHJJJONNONONOOLLLLMOONOOOMNMLLLLJKMLNMNMNLIKNLMMLJJKFHHIIHKLLKKKILJLJFDDFIJIIKKONNNMHNKKHE?:75155)!Ϧ蹎ފ ǧ ߲тyڋzήxxy~|}w}Քĵ~ƕ~|w|~Ƹx{Ә{~ܺz{wzÚԻxΛ|~Ԝܽ{~wxxܝzϾ|y؞{ٿw׿z}ٟ{}Ѡzw~zx|~١||z١{~xۡy١z~zyxyy}ڥz{yxwˬϭzϯϲߛxϺʏԚز֮ھ}ϒĥimؼ3,0*=ׅ{BCHHEE7,FBѶԻѮػl$.(߀\U-#   )RD??=  #'W X^X3'XWWϬy3+--&Q|{}qQC"%  ZK=; '(/   *Oqj\[\\^׭b$,Ӯ \PA!#   ,ttM96%-31   3>=?af.+--%Xӊy~ܧ_  "'*(% "WXurnF6  +64#(   +AAB>412Rgpb%-)t  />967Zkh-ks u)#$%%-.o}w ߰g#'?>Gb~ wyyx~~~ |~|w||zz~՝yǦyyxٚx|x|x~|yףx|~|˗ٖӠ}yɝy}ט{|ٖywӔ|Γyxڒwzwxȑxё{yԏُy|̿޿~{ؾ{y|ۍͽx|όȋz޻պ{}{wlj~xވzɷ˶Åxz~z߄{zw{~}Ȱw|ρչĽ۹ɩ&/:9;CNKRSROQPPNRMQPPNNKJNJNNNNILLLPKLJNMOLLHNMOMNILLOMNNIJKGHIKKHKKMHJKHIJIHGKILIHFIGHMKJIKKKLKJHNQb~kVPOMPMPLNONKMFJIKKKILNLMLKHJJFKFKLJKHJJJONNONONOOLLLLMOONOOOMNMLLLLJKMLNMNMNLIKNLMMLJJKFHHIIHKLLKKKILJLJFDDFIJIIKKONNNMHNKKHE?:75155)!Ϧ蹎ފ ǧ ߲тyڋzήxxy~|}w}Քĵ~ƕ~|w|~Ƹx{Ә{~ܺz{wzÚԻxΛ|~Ԝܽ{~wxxܝzϾ|y؞{ٿw׿z}ٟ{}Ѡzw~zx|~١||z١{~xۡy١z~zyxyy}ڥz{yxwˬϭzϯϲߛxϺʏԚز֮ھ}ϒĥimؼ3,0*=ׅ{BCHHEE7,FBѶԻѮػl$.(߀\U-#   )RD??=  #'W X^X3'XWWϬy3+--&Q|{}qQC"%  ZK=; '(/   *Oqj\[\\^׭b$,Ӯ \PA!#   ,ttM96%-31   3>=?af.+--%Xӊy~ܧ_  "'*(% "WXurnF6  +64#(   +AAB>412Rgpb%-)t  />967Zkh-ks u)#$%%-.o}w ߰g#'?>Gb~ wyyx~~~ |~|w||zz~՝yǦyyxٚx|x|x~|yףx|~|˗ٖӠ}yɝy}ט{|ٖywӔ|Γyxڒwzwxȑxё{yԏُy|̿޿~{ؾ{y|ۍͽx|όȋz޻պ{}{wlj~xވzɷ˶Åxz~z߄{zw{~}Ȱw|ρչĽ۹ɩ 517*/(,(2/5:@BD=@?:;7;9;:9;:;:94879568:=<<:::;<8<9<;97=;:73:9=9798:68987<?>==;><9=9;<>:;989A>9889<:9<:9=<>?98:8:::6598;99:<<;:89789;9:::89::;<:9:999::989;::88897897888778<:;<:978999::;87:::9:8><A>HBcgz~xruqiFA;;:6:;;9<:99:8:899:666787757989899:77999887:9<;;<99::99;99:;;77::::979::99:7879:999769:;;987989:::;9967879::;9::77989<::9:::;:99::9:::88998:;<;=><;:8998:;;=;;;898<=?=:99:9:<::889::;979;:;:55569876588:9:9569;::866779<;8988<;::79;<:76:<<<:569::8=:877843-+)$"%%''-*- ˸ Ư}~ֹʭ~ˢ~฀⸋ܧЮݦٸԡ{םê |ȓΣ}Զ~zՕ Ȑ֐|࿅۝}̔|ƀ͞~}̓~Č֔Ë{˒yzҊҌݝ}Ё|؍ܗ|ݢ͍ӋٜŁ֏Ɓ̀ז}~۞ɉԊؑ|ƂɁ}~ٍۚ}zՓЌڠ~֏}ޞ߄~ΊyԒԐݓՊՔ|ۢ֙܇z~ڛݡ{Ѓٚߥy~ٌؚٛуݣߦ{|}ڠ͍}ӓՁ̅|۠{ǁ~ۢy۟͊yz}Ӆ~ݣ֗Ɂ~֘}י~Ђ؅Ԅهܩʅѐ|ӕԗߪz}|{΀{ϋڌӖ݋~܊~zӁن}ѐɆ|۠}ɄЏ܌~xˁ~ۈɇʀ؛͌|~ӕʀ֛zzߓߌ{ɀ΍zҒЏ~ґ~Ӓӓxӕܣݗ{y{|؛z|ߏ{{ɀ؂Ғߎzܢ|zzȃ͋{}׈yۇ˂ךߙ~Ӗyݙ{|ޙכā̈ɀڢ}~тߪyxߪӖߪ~łܣyڟۣ{̋χۆɂہ|՗ߛׁ|ۢݥ߄֚۠܅zБڟ}ѓڟޣy~ڞՖ|ݤי˅|י~Ӑ؛ЌڞωyՄٜϊzdžڡֈ܎֘ɀߐyͅڌχӂًً̃x́׈ýՆy~ڝڌ؇{Ƅÿ́Ք{ݝ{ʁ~՝܊Ύ׋Н҆yҝυzҝՔ΄{ҝЏ݇΄{ҝÀ΄{ҝ΄{ҝ|΄{ҝ֖΄{ҝȅυ{ҝ̂}֝~|۝zցˁyā֖؍{ѐہ{͉؃ׂȆؖЎֆyٖӑxޏyzĀzˇޔԈ֓Ӓէzȿް˒ݰzڣؾ潓ĕí{֭ڵϬռ ȫ־Ƕx ɶ Źs κ ɷx uorj ν~soqqv˹njt|}~yv Ĵskllmofeuп xrqrmfdfeecdfilopmkihgeahr 満ȸԶ ȷҴuw{ |}~~~~~~kotuttuz ~|}}|{z㵄g^ilkc]˾۽~}}~}|~}|{z{~ٜ۬ܰ ˤ~a]`ababehmiwԏ[͢vzxxt xҦdkwՒÎ s șcx|{ ~؛嶅{ ໆq ؛߭lpz{xؚ Ϣ |ؚЖ yؚ|љ }ؚؔ؜ؚؚxؚߝ~טחx~Փ̇ʈיޛҊ̊ܨԌъx{|ͨ~ΆƪՑŪ֕~ؕŪ֐֑ƪ݌~֐ƬކȁՐƬ͊ݔՐƭє}ՐƮ͊ՐƮؙՐƮЁՐưِՐư{Րưۓ̓ՏưԌƲݘǀԋƲڑڙƴƴ}Έʃƴςחߔƴ}xƶ|ӈޓƶ؎{|ݟۑƶݣ|ܒƷ|ܓܒƸ·|ަܒƸ֑|ܒƺ|ޒܒƺȀ|ܒƺג{x{ܒƺx|ۑǼyَʼyَʽʄӌ͇ېɾߦx{{́{֏̓zט҇z|̂}ޑzxx~}ܝɀ~Վɀ~ɀ~yɀ~ςޡɀ~ɀ~yɀ}ސɀɀ̀Ўɀިɀ͉ɀ|ɀߒɀˀɀݧɀֈʅɀɀՙ{ɀǁ߾ߍɀ|ɀɀڟɀςΌxyzߩʁԖ}ޕǂ~ݏؖ|΁~ܤzyЏݨyʇy~y{y}نy|yyғzy}֚ݸyˉyƁyy|yߏy}yyݦ́yל}yˈyyyۡy΍yy}yՇy~yߧyΌݦy΍zy}yyy΀yyڠy֚Ђy̋y{ݣyךyԗy|yyy}yyyݥ{yёyyyًyyyy|ۢyƂyyyۊyւ~yyyؠ|ẙ}zy}ył؞y͍yАydžyyxyy~yyۡ~yٟy͍yٞy{łxz݉Ɂxߋ{ާyߖ{΍Ї}ԏ}|}՘|Āɀɀ؃ɀʩ|ɀڃݩߧɀƄɀܡɀɅɀɀȧɀ܈ڧ{ɀˋɀɀ͋ɀ}yåxފ֥̃xђّŀԓ|ǣףyґ|Ϗ|֑||{ȡ|݊ҡzғyyґy{yyßy|џyz|ܟyߤyyϐyАyyxۈ}ȝވԝьxܝ˅ޣܡxɇߣ̋xޤz}ڃؑ}}՛ъߤۜƃޞݟzޞ{ܛz{|҉ԍ۟ӈx֏Ń٘ė{҉Ηߓܗܞ׍сłޟсłؒz{{zz˄ЂِЎ۞ȅÓ~ܿԓޓ}~ؑؕzx{Ձݒнגю΁|—ӅûxՏܓϪٻ،Ӈς̹ݍݓ羕~دץɭ⽢~̸׿z µ 517*/(,(2/5:@BD=@?:;7;9;:9;:;:94879568:=<<:::;<8<9<;97=;:73:9=9798:68987<?>==;><9=9;<>:;989A>9889<:9<:9=<>?98:8:::6598;99:<<;:89789;9:::89::;<:9:999::989;::88897897888778<:;<:978999::;87:::9:8><A>KB^`ul`IA;;:6:;;9<:99:8:899:666787757989899:77999887:9<;;<99::99;99:;;77::::979::99:7879:999769:;;987989:::;9967879::;9::77989<::9:::;:99::9:::88998:;<;=><;:8998:;;=;;;898<=?=:99:9:<::889::;979;:;:55569876588:9:9569;::866779<;8988<;::79;<:76:<<<:569::8=:877843-+)$"%%''-*- ˸ Ư}~ֹʭ~ˢ~฀⸋ܧЮݦٸԡ{םê |ȓΣ}Զ~zՕ Ȑ֐|࿅۝}̔|ƀ͞~}̓~Č֔Ë{˒yzҊҌݝ}Ё|؍ܗ|ݢ͍ӋٜŁ֏Ɓ̀ז}~۞ɉԊؑ|ƂɁ}~ٍۚ}zՓЌڠ~֏}ޞ߄~ΊyԒԐݓՊՔ|ۢ֙܇z~ڛݡ{Ѓٚߥy~ٌؚٛуݣߦ{|}ڠ͍}ӓՁ̅|۠{ǁ~ۢy۟͊yz}Ӆ~ݣ֗Ɂ~֘}י~Ђ؅Ԅهܩʅѐ|ӕԗߪz}|{΀{ϋڌӖ݋~܊~zӁن}ѐɆ|۠}ɄЏ܌~xˁ~ۈɇʀ؛͌|~ӕʀ֛zzߓߌ{ɀ΍zҒЏ~ґ~Ӓӓxӕܣݗ{y{|؛z|ߏ{{ɀ؂Ғߎzܢ|zzȃ͋{}׈yۇ˂ךߙ~Ӗyݙ{|ޙכā̈ɀڢ}~тߪyxߪӖߪ~łܣyڟۣ{̋χۆɂہ|՗ߛׁ|ۢݥ߄֚۠܅zБڟ}ѓڟޣy~ڞՖ|ݤי˅|י~Ӑ؛ЌڞωyՄٜϊzdžڡֈ܎֘ɀߐyͅڌχӂًً̃x́׈ýՆy~ڝڌ؇{Ƅÿ́Ք{ݝ{ʁ~՝܊Ύ׋Н҆yҝυzҝՔ΄{ҝЏ݇΄{ҝÀ΄{ҝ΄{ҝ|΄{ҝ֖΄{ҝȅυ{ҝ̂}֝~|۝zցˁyā֖؍{ѐہ{͉؃ׂȆؖЎֆyٖӑxޏyzĀzˇޔԈ֓Ӓէzȿް˒ݰzڣؾ潓ĕí{֭ڵΫռ ȫ־ ʺ{̸ Źǻzɸ ȵv ɺɾ{nqp`OJIIJKKLI?3/}ƶѭؒ?()+,,&!+=ntsrsrtuwy{|~zsdUFB9'}{˷yklm_MFEEGHC:2,++'! "&)+)%" *Jjxuww~zN /BMIGGHI@5/010/026;;5.,+ ,*' Ժ\ƳiQJGA7'  >`{}ypkklpl\H;635575&  '/2432479:.     ^\j~vqqrrwvdK,.WprtsrsrtjTDBDDCDFKQSPNMOK=22/"  *7?=830013688 78;==8(#BXZ]\[[XWimM,  !#&''&$#  .JZU[iuyumeddeh_L:4001132+    +/212.$!%)"   -?IONMNMKE?988969HZipsqq pqvzynN-$.10("Pʚ   ,:9;9AP]^]] \^bgmg]A! ( #)/48BOg}ޘĴř%&'& %#(/;DIC@>>??>;FXltzyxx}|Y+'IkzxwxwĮkD'$''())().001 {xի '19AEVgrsts rqpm|ȕOҨ |ŒzLPMNS`nüڮ} ؙ嶄y ƕxט߭ |wؚϢ yؚЖ yؚ|љ }ؚؔ؜ؚؚxؚߝ~טחx~Փ̇ʈיޛҊ̊ܨԌъx{|ͨ~ΆƪՑŪ֕~ؕŪ֐֑ƪ݌~֐ƬކȁՐƬ͊ݔՐƭє}ՐƮ͊ՐƮؙՐƮЁՐưِՐư{Րưۓ̓ՏưԌƲݘǀԋƲڑڙƴƴ}Έʃƴςחߔƴ}xƶ|ӈޓƶ؎{|ݟۑƶݣ|ܒƷ|ܓܒƸ·|ަܒƸ֑|ܒƺ|ޒܒƺȀ|ܒƺג{x{ܒƺx|ۑǼyَʼyَʽʄӌ͇ېɾߦx{{́{֏̓zט҇z|̂}ޑzxx~}ܝɀ~Վɀ~ɀ~yɀ~ςޡɀ~ɀ~yɀ}ސɀɀ̀Ўɀިɀ͉ɀ|ɀߒɀˀɀݧɀֈʅɀɀՙ{ɀǁ߾ߍɀ|ɀɀڟɀςΌxyzߩʁԖ}ޕǂ~ݏؖ|΁~ܤzyЏݨyʇy~y{y}نy|yyғzy}֚ݸyˉyƁyy|yߏy}yyݦ́yל}yˈyyyۡy΍yy}yՇy~yߧyΌݦy΍zy}yyy΀yyڠy֚Ђy̋y{ݣyךyԗy|yyy}yyyݥ{yёyyyًyyyy|ۢyƂyyyۊyւ~yyyؠ|ẙ}zy}ył؞y͍yАydžyyxyy~yyۡ~yٟy͍yٞy{łxz݉Ɂxߋ{ާyߖ{΍Ї}ԏ}|}՘|Āɀɀ؃ɀʩ|ɀڃݩߧɀƄɀܡɀɅɀɀȧɀ܈ڧ{ɀˋɀɀ͋ɀ}yåxފ֥̃xђّŀԓ|ǣףyґ|Ϗ|֑||{ȡ|݊ҡzғyyґy{yyßy|џyz|ܟyߤyyϐyАyyxۈ}ȝވԝьxܝ˅ޣܡxɇߣ̋xޤz}ڃؑ}}՛ъߤۜƃޞݟzޞ{ܛz{|҉ԍ۟ӈx֏Ń٘ė{҉Ηߓܗܞ׍сłޟсłؒz{{zz˄ЂِЎ۞ȅÓ~ܿԓޓ}~ؑؕzx{Ձݒнגю΁|—ӅûxՏܓϪٻ،Ӈς̹ݍݓ羕~دץɭ⽢~̸׿z µ 517*/(,(2/5:@BD=@?:;7;9;:9;:;:94879568:=<<:::;<8<9<;97=;:73:9=9798:68987<?>==;><9=9;<>:;989A>9889<:9<:9=<>?98:8:::6598;99:<<;:89789;9:::89::;<:9:999::989;::88897897888778<:;<:978999::;87:::9:8><A>KB^`ul`IA;;:6:;;9<:99:8:899:666787757989899:77999887:9<;;<99::99;99:;;77::::979::99:7879:999769:;;987989:::;9967879::;9::77989<::9:::;:99::9:::88998:;<;=><;:8998:;;=;;;898<=?=:99:9:<::889::;979;:;:55569876588:9:9569;::866779<;8988<;::79;<:76:<<<:569::8=:877843-+)$"%%''-*- ˸ Ư}~ֹʭ~ˢ~฀⸋ܧЮݦٸԡ{םê |ȓΣ}Զ~zՕ Ȑ֐|࿅۝}̔|ƀ͞~}̓~Č֔Ë{˒yzҊҌݝ}Ё|؍ܗ|ݢ͍ӋٜŁ֏Ɓ̀ז}~۞ɉԊؑ|ƂɁ}~ٍۚ}zՓЌڠ~֏}ޞ߄~ΊyԒԐݓՊՔ|ۢ֙܇z~ڛݡ{Ѓٚߥy~ٌؚٛуݣߦ{|}ڠ͍}ӓՁ̅|۠{ǁ~ۢy۟͊yz}Ӆ~ݣ֗Ɂ~֘}י~Ђ؅Ԅهܩʅѐ|ӕԗߪz}|{΀{ϋڌӖ݋~܊~zӁن}ѐɆ|۠}ɄЏ܌~xˁ~ۈɇʀ؛͌|~ӕʀ֛zzߓߌ{ɀ΍zҒЏ~ґ~Ӓӓxӕܣݗ{y{|؛z|ߏ{{ɀ؂Ғߎzܢ|zzȃ͋{}׈yۇ˂ךߙ~Ӗyݙ{|ޙכā̈ɀڢ}~тߪyxߪӖߪ~łܣyڟۣ{̋χۆɂہ|՗ߛׁ|ۢݥ߄֚۠܅zБڟ}ѓڟޣy~ڞՖ|ݤי˅|י~Ӑ؛ЌڞωyՄٜϊzdžڡֈ܎֘ɀߐyͅڌχӂًً̃x́׈ýՆy~ڝڌ؇{Ƅÿ́Ք{ݝ{ʁ~՝܊Ύ׋Н҆yҝυzҝՔ΄{ҝЏ݇΄{ҝÀ΄{ҝ΄{ҝ|΄{ҝ֖΄{ҝȅυ{ҝ̂}֝~|۝zցˁyā֖؍{ѐہ{͉؃ׂȆؖЎֆyٖӑxޏyzĀzˇޔԈ֓Ӓէzȿް˒ݰzڣؾ潓ĕí{֭ڵΫռ ȫ־ ʺ{̸ Źǻzɸ ȵv ɺɾ{nqp`OJIIJKKLI?3/}ƶѭؒ?()+,,&!+=ntsrsrtuwy{|~zsdUFB9'}{˷yklm_MFEEGHC:2,++'! "&)+)%" *Jjxuww~zN /BMIGGHI@5/010/026;;5.,+ ,*' Ժ\ƳiQJGA7'  >`{}ypkklpl\H;635575&  '/2432479:.     ^\j~vqqrrwvdK,.WprtsrsrtjTDBDDCDFKQSPNMOK=22/"  *7?=830013688 78;==8(#BXZ]\[[XWimM,  !#&''&$#  .JZU[iuyumeddeh_L:4001132+    +/212.$!%)"   -?IONMNMKE?988969HZipsqq pqvzynN-$.10("Pʚ   ,:9;9AP]^]] \^bgmg]A! ( #)/48BOg}ޘĴř%&'& %#(/;DIC@>>??>;FXltzyxx}|Y+'IkzxwxwĮkD'$''())().001 {xի '19AEVgrsts rqpm|ȕOҨ |ŒzLPMNS`nüڮ} ؙ嶄y ƕxט߭ |wؚϢ yؚЖ yؚ|љ }ؚؔ؜ؚؚxؚߝ~טחx~Փ̇ʈיޛҊ̊ܨԌъx{|ͨ~ΆƪՑŪ֕~ؕŪ֐֑ƪ݌~֐ƬކȁՐƬ͊ݔՐƭє}ՐƮ͊ՐƮؙՐƮЁՐưِՐư{Րưۓ̓ՏưԌƲݘǀԋƲڑڙƴƴ}Έʃƴςחߔƴ}xƶ|ӈޓƶ؎{|ݟۑƶݣ|ܒƷ|ܓܒƸ·|ަܒƸ֑|ܒƺ|ޒܒƺȀ|ܒƺג{x{ܒƺx|ۑǼyَʼyَʽʄӌ͇ېɾߦx{{́{֏̓zט҇z|̂}ޑzxx~}ܝɀ~Վɀ~ɀ~yɀ~ςޡɀ~ɀ~yɀ}ސɀɀ̀Ўɀިɀ͉ɀ|ɀߒɀˀɀݧɀֈʅɀɀՙ{ɀǁ߾ߍɀ|ɀɀڟɀςΌxyzߩʁԖ}ޕǂ~ݏؖ|΁~ܤzyЏݨyʇy~y{y}نy|yyғzy}֚ݸyˉyƁyy|yߏy}yyݦ́yל}yˈyyyۡy΍yy}yՇy~yߧyΌݦy΍zy}yyy΀yyڠy֚Ђy̋y{ݣyךyԗy|yyy}yyyݥ{yёyyyًyyyy|ۢyƂyyyۊyւ~yyyؠ|ẙ}zy}ył؞y͍yАydžyyxyy~yyۡ~yٟy͍yٞy{łxz݉Ɂxߋ{ާyߖ{΍Ї}ԏ}|}՘|Āɀɀ؃ɀʩ|ɀڃݩߧɀƄɀܡɀɅɀɀȧɀ܈ڧ{ɀˋɀɀ͋ɀ}yåxފ֥̃xђّŀԓ|ǣףyґ|Ϗ|֑||{ȡ|݊ҡzғyyґy{yyßy|џyz|ܟyߤyyϐyАyyxۈ}ȝވԝьxܝ˅ޣܡxɇߣ̋xޤz}ڃؑ}}՛ъߤۜƃޞݟzޞ{ܛz{|҉ԍ۟ӈx֏Ń٘ė{҉Ηߓܗܞ׍сłޟсłؒz{{zz˄ЂِЎ۞ȅÓ~ܿԓޓ}~ؑؕzx{Ձݒнגю΁|—ӅûxՏܓϪٻ،Ӈς̹ݍݓ羕~دץɭ⽢~̸׿z µ  0@@@@p@@Р` @P```@@pp     ` @`0P @pPP p  @ Ppp```P00P00`0`0p`0`0`0```p0л0@P``0  @`0`p 00е@` @@p`@00@```@ P  @@ pP @@` 0p@PPp` p0 0p@p@00@P@@@@@@ @P`Чp@@p@ @P@@@p00pP@ @@@@p `@0` `0 `@p0@p @@`@`@@ @`@@@@ @`p@0`@`@00 @`P @`0 `P``0@P`p `@ p@@ @00pP000```0`0 `  0P0000``p@@@  0  `P@00P@pP @p@p@P @ @@0@  P`оp p  pАP@P0``00@p`@ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzź́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́05N4//0/3-HHTJHHHGGEA?@@FB;CFILLEDKGCA??::9;8:9<9=><=:=<?A>>C;;<:9<;89:;83101224./9;988:88;86648A>=CABCB?=CKLLMJJKMLOOSRUUHIA]::8;:'1 0`@ @Pp@ Pp0@p` @ `P0`@`P0@ Pp `АP`@0```@p `@`@  @`@p@ @P@P` @ P0`0РP@p  p@00P `Ѐ@ ` ```@p@`@```0@@p@0`@PP`@`@0@``p PP``00`0p`0`p@0 0`0PPP`@00@0p`` @`P`00`@@ P```p``p ` @Pp`@@ @P@` @p0 `p@ @@0 0`p @`@@ p@@@0@@``@P @@@p`@@@p0@@``0P@@@p@@@P@00`0Pp@@@@pP @@0  `p@ P pЩ@0@ @@ 0 @p p0`p@p`Ь@P@0`00 p`@  0 @@@`pPPP@P0` PP @@@p@ @` `P@ ppЀ0Њ@0@@0` p0P` @``@p00@ ` @@` Pp0Љ `pp ` p P @@@@PP`P@P ``P@P@`@`@PP  @@`P `@ @ @00@ `p`@@p 0p 0@P@0@pP`@@`@@p0@P@ @ `@P p@p0P@0 pp@0` `P ` 0P @`0`@p @@@0` P@ppppP00p `@PP0@0@P@@ p@@@pP@ pP@ `` `@@p00@00PpPPpp``@0p@0PP`0@@@@@@@  @@PPp@00P@ЙP@ 0 ``@ PP@p@P @@@@@` @0P @@p@p@@P@`p@  00@`@``pp`@ 0@ `@@P@  @ 0@ @@Pp0`P `0P`0@` 00p@@P@0p P 00pP` @`` P `P@p`P` @0``0p0 `P @Pp0p@@0``  `p ``@@p@@p@`0````P@P @@ pp@`@`@@0@`@@pp@`@0 p`@@0p@@`@`@``@@`@00@`P 0 P@ 0P00P@`p@p@@@p@p@PP0Pఀ@0PP  @`@  zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz ̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣̣́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́ "@Pp@0@P @p@0p`0`0Ppp0@@p@@ `@P@@p@P@0@@P 0P00``0`@`@ `  `000@`00`P000p`P@@ PP0`@p@ P `pP @ @@ P`P`@ p`@@ ``Ь@p@@ @@@pPp Ъ000` @ @p@`@ @@@@0p`0@P0@@@` P@@Pp@@Ф`@p@@@@@@@ @P@p@ @@P@0@@ @` @@`@P@00PP`P`0 @`@ppP@0 p0@P` 0`@` @0@  ``@P```@@ 0`0@P0 00@000000@`P0 @0 `P``  ``p  p0`0 P@p@0@`p0Ы``@`` pp  pPPР` @pp@@PP@zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzź́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́-$I31,.+.+CBMLHFEFEA=@@<DDAGGGDFB?<@@@@<26768679997959=<>;:<;87<86969:867-121,10./66265426688669??>A>?>8ABAEJJGIJJMKLMJMOUGH@c64566$. P`@ @p`@0pАPPఀ@0`Pp @@@PP0p0`0`P @@p@@@p @p  0@0Ъ0P  @Pp P@@p@P0ఀ`@p`p@`@p 0p@А@@p`  P@`pp P@` `Pp@@0`P`P@PP@@`00 0`p ` 0p```0  000 p``0`@p`@` `@p@ ` `@@PPP @ pp`0p@ p@ @`p@ @@`0P@  0 @@@@@0ppP@``@` @pPP@@0@@`@P` @ 0Pp @`P00``@@@@@pP00@0`@`0p`` Pp@@0 00P 0P`Њ@P`@p@@ ` @@ @Љ @0@@ @0@@` p@p0 p@`@ p @P` 00p`@``0 @`pP  `00p p@@@@` p`@p0``p@PP@@0``@00p``P @`P  @`0` 0 @p`P@@ p0PP@  @ p`p@0@@p00P@@PpP@@ PP p@0`@  `@0@`@`@ p0`@``` p``@P0@ P@00 p@P@ p P@@@p@@@`0p`` `@ @p`@@` 0@p@ P0``@@P@p@p @@@`` `` @ P@@ PPp`@@@0` @`p @` @p@p`О0` @P0`0@@p p0@@p  0P @@p` @ @P```@@@0@ @@@@0p @P P `P@`p0@` p@0 ``@0 p`P `@`0`PP@p0` P`P 0@@@ 0`` @@@@p`` P@@p@```Pp @0 ``p0@ `@pP0P```@`@`@ @` P0@pp@`00 p@@P``` @0P@`аP@ @`p@@`@00Ѐ@@0@`@@Pp@@`@`@p@```P@@@p``@@@@P@0`@``PP@PP@`@ `@p@`@@АP0p`  @`@@`@0zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzź̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̩̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄́́̄  0@@@@p@@Р` @P```@@pp     ` @`0P @pPP p  @ Ppp```P00P00`0`0p`0`0`0```p0л0@P``0  @`0`p 00е@` @@p`@00@```@ P  @@ pP @@` 0p@PPp` p0 0p@p@00@P@@@@@@ @P`Чp@@p@ @P@@@p00pP@ @@@@p `@0` `0 `@p0@p @@`@`@@ @`@@@@ @`p@0`@`@00 @`P @`0 `P``0@P`p `@ p@@ @00pP000```0`0 `  0P0000``p@@@  0  `P@00P@pP @p@p@P @ @@0@  P`оp p  pАP@P0``00@p`@ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzź́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́05N4//0/3-HHTJHHHGGEA?@@FB;CFILLEDKGCA??::9;8:9<9=><=:=<?A>>C;;<:9<;89:;83101224./9;988:88;86648A>=CABCB?=CKLLMJJKMLOOSRUUHIA^68698'1 0`@ @Pp@ Pp0@p` @ `P0`@`P0@ Pp `АP`@0```@p `@`@  @`@p@ @P@P` @ P0`0РP@p  p@00P `Ѐ@ ` ```@p@`@```0@@p@0`@PP`@`@0@``p PP``00`0p`0`p@0 0`0PPP`@00@0p`` @`P`00`@@ P```p``p ` @Pp`@@ @P@` @p0 `p@ @@0 0`p @`@@ p@@@0@@``@P @@@p`@@@p0@@``0P@@@p@@@P@00`0Pp@@@@pP @@0  `p@ P pЩ@0@ @@ 0 @p p0`p@p`Ь@P@0`00 p`@  0 @@@`pPPP@P0` PP @@@p@ @` `P@ ppЀ0Њ@0@@0` p0P` @``@p00@ ` @@` Pp0Љ `pp ` p P @@@@PP`P@P ``P@P@`@`@PP  @@`P `@ @ @00@ `p`@@p 0p 0@P@0@pP`@@`@@p0@P@ @ `@P p@p0P@0 pp@0` `P ` 0P @`0`@p @@@0` P@ppppP00p `@PP0@0@P@@ p@@@pP@ pP@ `` `@@p00@00PpPPpp``@0p@0PP`0@@@@@@@  @@PPp@00P@ЙP@ 0 ``@ PP@p@P @@@@@` @0P @@p@p@@P@`p@  00@`@``pp`@ 0@ `@@P@  @ 0@ @@Pp0`P `0P`0@` 00p@@P@0p P 00pP` @`` P `P@p`P` @0``0p0 `P @Pp0p@@0``  `p ``@@p@@p@`0````P@P @@ pp@`@`@@0@`@@pp@`@@0 p`@@0p@@`@`@``@@`@00@`P 0 P@ 0P00P@`p@p@@@p@p@PP0Pఀ@0PP  @`@  zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz ̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞̞́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́,.*'&&$"""!" !  7820--,+**))()(()(('('((''&&(%&&&&%&%$$##&&'$&&&%$%&%$&%%%#$#%$$#$#$"%%%$$$$%$##$#$####""##$%&%%%#$#""$%%""#$%$"#"$$$"#$$####$$"#%##$#"%"##"%#"%#"####$"##"$"$###""#"#"##"#"""#""#"#"""#""""""""#""""#"#""#""""""""#""""!!  !!!"""""""#""""""""""""""""""""""""""" !!   !!""" """  """"" """ " """" """"" """" """ """   !!!!         !!!!!!! """ """""" " "" " "" """ " "" """"" " " " """ " """" """"" """ """" "" ""!!!  !!!""""""""" """"""" """"!!!  "!!""""#""""""""""""""$"#"#"#"#""$"#"##""""$"""#"###"#"""###$"$"$##"$##"#"#"$""$"#"#####"#$"#$"##$"#####"#####%#"##$$"#$$$$"#%###%##$$$#$$##%$! !#$#$$%%####$$%$#"""$&%%$#%$"!"%$%$$$$$%%$$###$&&&$%%&%&'$%&&&&&&%&&(&&'(&('''(((('())))****,+/040  !!""""$$'*+.@ @`@00@P@ @@p `@@@@p@0@pp@0 @pp@  @pp@ @`а`@@``@0P`0@pРp@@лఀ@@pжఀ@0`бР`0@@ PША`  ``  ``  `` PИАP0pp0``0Ѝp0PP`` pЃp 0p 0p 0p 0p p``PP00p pPP  ``  PPp00``p  0@@````     ` `p@@p@ P@@0@p@pP@00@P ``@  @P@P@ @pp p@0PPа@  P Рp@@p`p@0``0P @p@@ `p@Pp@А@P0`PpЀ0 p@@` PP0pО`PP `  P`ppP` 0``pp `Э @0 pp`0@0`P` 0@`@  0``п pP@P@ P p0 ``0й @p @0 0P0  p  @ 00P@`pp@`0Э `0`P`  @0P``0@ PP`0У0@p0` p  0`0 Н00@P`P``pp`0 Ж`0Е00P0 @@` P` Ђ```00`p0Е`ЌЖ@Ж0Ж0`Ж@pЖ0`ЖГ`Д@00````0p`0``00@0000000000`00@`00`@``P`0`P00@pp`Э@@0000``@`0г@@0PPp0@0и `@@0PPp000`P п`p0 `P p@0@ pPп 00Ppp  @@@p p@к p@ @@`@@P0 PP0 pp`@ @@p p@@@ @ @ `@ `@ @@@ @@ @`0`@@@ P @ ` @  ` @ P p  @`0 С@@`@ 0`ПP``` PPP0  МPppp0`Щ`@ P P``PP ` PP pP ` `pp @ 0`Еp`0 ` ``P0ГP0@pP@p`P 00``0 P@P` PP `p@0` `0pp@0И Ѝ``Pp0@p@ Ж ``0P0pЂ0pp@@@@0ГP@P0p@ВЉ@@P@p@@P0 P @`0p@Џ0P@`P @ @@ІPp @p@` @0@p@pp@0 p@@@p@` @ @p@@@`@pP@@P@@@ @@@@``@ ЇА@@`@ 0@@@P@@ppP@@@0@@@В@@0@@@@@`p@@@@@ @@@@@ `@@@@@@@@@@@@@@0@@@@@@@@@@@@@@`@@@@ @@@@@@@ @@@@@@@@@p@@@Е@@@@@@`@@`@@@@@@@@@@`0@@@@@@Ѕ@@@@@@@`@P@0@0 Ѓ@@p@@Ї@@@P@@@@@0@@@p@P@@Pp@@``@@0@pP0 @@@@0@ PP0Ќ0PГ@` @0@@@`p`0p@ `0P00 @`PP @0 @Pp @@` 0` `Вp0@@ @p`00p@p0 `@``` 0 `ЎP ` @0`p0 `pP0@0p P  PP0p00Pp@pP@PЋВ`0@PpН`Г ЋpP@`Д`PP P`0Ѝ0P@ `P` 0PpPP`0 `p `ЙpХ00 p `P0@Џ ```P `p 0Н@pp Оp`Pp @`П@`p 0`Pp  @PpP` 0аp`@p ` pp` pp0` Цд`@0 @ ``p `p0`Ъ0`@`0 `0 @`Э```p`@P 0 @``@@` p@P@p ``@ `@@``@ @ 0@@@@` ``@оP`@0@0 p 0 p0pP @P@@p 0@0pPP@Pл  `P `@и 0@Pp0`0 0pp@ P p P @p `pp00Э 0p@`P 0P` 0P``0Pp 0` `00PP00`0`0P`0м0```@0`00 `0P`ж@0`0`0p`00```0P`0000000P@`Э``Ь`ЫpЪЩШ0ЧPЦ` Х`0Ф`P Ф``0`0`0@  `000`00`0P``0 Ф``0```@` Ф0@0@p 0У`P`@@ У0pP`@`0Тp`@P` 00@`00`P`Ж 0З`P  P`@0``0@`` О 00pppp`P0P@00Ф@ p@0@`@P0@`@`p``0P` Ю@  `@@@0вp`0PP`@ ж0`@`0@P`@0PPp 00 p  pppp  pм  p  pйPЀ0 pж0P0 p `` P0Ьp@ `  @ `PPP0`Р`0@@P 0`И0p`0@pP Р@  Pp@P0@PP@@P `@00@` @@@@`P@P@` `0`@```````@@ ``P`0@0`  ``@0 ``00`` 00`P `@p `p `p  pp  pp  p``P`Ѐ0@` p0PP p` @p0@p0@Р` @P0`Рp0@ఀ@ Pఀ@ PР@@Ы`00Pб`@0PP@@``@@P@@Pp@@@@pP@0@`@@ @PP@zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzź́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́́́́̑́8BIMPatt8BIMTxt2n /DocumentResources << /FontSet << /Resources [ << /Resource << /StreamTag /CoolTypeFont /Identifier << /Name (BookAntiqua) /Type 1 >> >> >> << /Resource << /StreamTag /CoolTypeFont /Identifier << /Name (BookAntiqua-BoldItalic) /Type 1 >> >> >> << /Resource << /StreamTag /CoolTypeFont /Identifier << /Name (MyriadPro-Regular) /Type 0 >> >> >> << /Resource << /StreamTag /CoolTypeFont /Identifier << /Name (AdobeInvisFont) /Type 0 >> >> >> ] >> /MojiKumiCodeToClassSet << /Resources [ << /Resource << /Name () /Members << /ClassMappings [ << /R (55) /C 1 >> << /R (77) /C 1 >> << /R (99) /C 1 >> << /R (;;) /C 1 >> << /R (==) /C 1 >> << /R (??) /C 1 >> << /R (AA) /C 1 >> << /R (CC) /C 1 >> << /R () /C 1 >> << /R (;;) /C 1 >> << /R ([[) /C 1 >> << /R (  ) /C 1 >> << /R (00) /C 1 >> << /R (0 0 ) /C 1 >> << /R (0 0 ) /C 1 >> << /R (00) /C 1 >> << /R (00) /C 1 >> << /R (00) /C 1 >> << /R (66) /C 2 >> << /R (88) /C 2 >> << /R (::) /C 2 >> << /R (<<) /C 2 >> << /R (>>) /C 2 >> << /R (@@) /C 2 >> << /R (BB) /C 2 >> << /R (DD) /C 2 >> << /R ( ) /C 2 >> << /R (==) /C 2 >> << /R (]]) /C 2 >> << /R (  ) /C 2 >> << /R (0 0 ) /C 2 >> << /R (0 0 ) /C 2 >> << /R (0 0 ) /C 2 >> << /R (00) /C 2 >> << /R (00) /C 2 >> << /R (00) /C 2 >> << /R () /C 3 >> << /R (^^) /C 3 >> << /R (0A0A) /C 3 >> << /R (0C0C) /C 3 >> << /R (0E0E) /C 3 >> << /R (0G0G) /C 3 >> << /R (0I0I) /C 3 >> << /R (0c0c) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R () /C 4 >> << /R () /C 4 >> << /R () /C 5 >> << /R (00) /C 5 >> << /R (  ) /C 5 >> << /R () /C 6 >> << /R (00) /C 6 >> << /R ( ) /C 7 >> << /R (00) /C 7 >> << /R (  ) /C 8 >> << /R ( % &) /C 8 >> << /R () /C 9 >> << /R () /C 9 >> << /R () /C 9 >> << /R () /C 10 >> << /R () /C 10 >> << /R () /C 10 >> << /R ( 0 0) /C 10 >> << /R ( 2 4) /C 10 >> << /R (00) /C 11 >> << /R (0B0B) /C 12 >> << /R (0D0D) /C 12 >> << /R (0F0F) /C 12 >> << /R (0H0H) /C 12 >> << /R (0J0b) /C 12 >> << /R (0d0) /C 12 >> << /R (00) /C 12 >> << /R (00) /C 12 >> << /R (00) /C 12 >> << /R () /C 13 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (2 2C) /C 14 >> << /R (22) /C 14 >> << /R (22) /C 14 >> << /R (33W) /C 14 >> << /R (3q3v) /C 14 >> << /R (33) /C 14 >> << /R (N) /C 14 >> << /R (09) /C 15 >> << /R (!~) /C 16 >> << /R () /C 16 >> << /R (  ) /C 16 >> << /R (  ) /C 16 >> ] >> >> >> ] /DisplayList [ << /Resource 0 >> ] >> /MojiKumiTableSet << /Resources [ << /Resource << /Name (Photoshop6MojiKumiSet4) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 17 ] /Data << /B << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 17 ] /Data << /B << /R [ .25 .25 .25 ] >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .75 .75 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 17 ] /Data << /B << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 17 ] /Data << /B << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 17 /Elements [ << /P [ 17 1 ] /Data << /A << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 18 /Elements [ << /P [ 18 1 ] /Data << /A << /R [ .5 .5 .5 ] >> >> >> ] >> ] >> >> /PredefinedTag 2 >> >> >> << /Resource << /Name (Photoshop6MojiKumiSet3) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 17 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .75 .75 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 17 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 17 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> ] >> >> /PredefinedTag 4 >> >> >> << /Resource << /Name (Photoshop6MojiKumiSet2) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .75 .75 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> ] >> >> /PredefinedTag 3 >> >> >> << /Resource << /Name (Photoshop6MojiKumiSet1) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> ] >> >> /PredefinedTag 1 >> >> >> << /Resource << /Name (YakumonoHankaku) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> ] >> >> /PredefinedTag 1 >> >> >> << /Resource << /Name (GyomatsuYakumonoHankaku) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .75 .75 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> ] >> >> /PredefinedTag 3 >> >> >> << /Resource << /Name (GyomatsuYakumonoZenkaku) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 17 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .75 .75 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 17 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 17 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> ] >> >> /PredefinedTag 4 >> >> >> << /Resource << /Name (YakumonoZenkaku) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 17 ] /Data << /B << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 17 ] /Data << /B << /R [ .25 .25 .25 ] >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .75 .75 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 17 ] /Data << /B << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 17 ] /Data << /B << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 17 /Elements [ << /P [ 17 1 ] /Data << /A << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 18 /Elements [ << /P [ 18 1 ] /Data << /A << /R [ .5 .5 .5 ] >> >> >> ] >> ] >> >> /PredefinedTag 2 >> >> >> ] /DisplayList [ << /Resource 0 >> << /Resource 1 >> << /Resource 2 >> << /Resource 3 >> << /Resource 4 >> << /Resource 5 >> << /Resource 6 >> << /Resource 7 >> ] >> /KinsokuSet << /Resources [ << /Resource << /Name (None) /Data << /NoStart () /NoEnd () /Keep () /Hanging () /PredefinedTag 0 >> >> >> << /Resource << /Name (PhotoshopKinsokuHard) /Data << /NoStart (!\),.:;?]}    0!! 0000 0 0 0000A0C0E0G0I0c000000000000000000000000 =]) /NoEnd (\([{  00 0 0000 ;[) /Keep (  % &) /Hanging (00 ) /PredefinedTag 1 >> >> >> << /Resource << /Name (PhotoshopKinsokuSoft) /Data << /NoStart (  0000 0 0 00000000 =]) /NoEnd (  00 0 000;[) /Keep (  % &) /Hanging (00 ) /PredefinedTag 2 >> >> >> << /Resource << /Name (Hard) /Data << /NoStart (!\),.:;?]}    0!! 0000 0 0 0000A0C0E0G0I0c000000000000000000000000 =]) /NoEnd (\([{  00 0 0000 ;[) /Keep (  % &) /Hanging (00 ) /PredefinedTag 1 >> >> >> << /Resource << /Name (Soft) /Data << /NoStart (  0000 0 0 00000000 =]) /NoEnd (  00 0 000;[) /Keep (  % &) /Hanging (00 ) /PredefinedTag 2 >> >> >> ] /DisplayList [ << /Resource 0 >> << /Resource 1 >> << /Resource 2 >> << /Resource 3 >> << /Resource 4 >> ] >> /StyleSheetSet << /Resources [ << /Resource << /Name (Normal RGB) /Features << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 1 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures false /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms false /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /JapaneseAlternateFeature 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /Blend << /StreamTag /SimpleBlender >> /FillFlag true /StrokeFlag false /FillFirst true /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> >> >> ] /DisplayList [ << /Resource 0 >> ] >> /ParagraphSheetSet << /Resources [ << /Resource << /Name (Normal RGB) /Features << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /DropCaps 1 /AutoLeading 1.2 /LeadingType 0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 0 /Zone 36.0 /HyphenateCapitalized true /HyphenationPreference .5 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /SingleWordJustification 6 /Hanging false /AutoTCY 0 /KeepTogether true /BurasagariType 0 /KinsokuOrder 0 /Kinsoku /nil /KurikaeshiMojiShori false /MojiKumiTable /nil /EveryLineComposer false /TabStops << >> /DefaultTabWidth 36.0 /DefaultStyle << >> /ParagraphDirection 0 /JustificationMethod 0 /ComposerEngine 0 >> >> >> ] /DisplayList [ << /Resource 0 >> ] >> /TextFrameSet << /Resources [ << /Resource << /Bezier << /Points [ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ] >> /Data << /Type 0 /LineOrientation 0 /TextOnPathTRange [ -1.0 -1.0 ] /RowGutter 0.0 /ColumnGutter 0.0 /FirstBaselineAlignment << /Flag 1 /Min 0.0 >> /PathData << /Spacing -1 >> >> >> >> << /Resource << /Bezier << /Points [ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ] >> /Data << /Type 0 /LineOrientation 0 /TextOnPathTRange [ -1.0 -1.0 ] /RowGutter 0.0 /ColumnGutter 0.0 /FirstBaselineAlignment << /Flag 1 /Min 0.0 >> /PathData << /Spacing -1 >> >> >> >> << /Resource << /Bezier << /Points [ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ] >> /Data << /Type 0 /LineOrientation 0 /TextOnPathTRange [ -1.0 -1.0 ] /RowGutter 0.0 /ColumnGutter 0.0 /FirstBaselineAlignment << /Flag 1 /Min 0.0 >> /PathData << /Spacing -1 >> >> >> >> << /Resource << /Bezier << /Points [ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ] >> /Data << /Type 0 /LineOrientation 0 /TextOnPathTRange [ -1.0 -1.0 ] /RowGutter 0.0 /ColumnGutter 0.0 /FirstBaselineAlignment << /Flag 1 /Min 0.0 >> /PathData << /Spacing -1 >> >> >> >> << /Resource << /Bezier << /Points [ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ] >> /Data << /Type 0 /LineOrientation 0 /TextOnPathTRange [ -1.0 -1.0 ] /RowGutter 0.0 /ColumnGutter 0.0 /FirstBaselineAlignment << /Flag 1 /Min 0.0 >> /PathData << /Spacing -1 >> >> >> >> << /Resource << /Bezier << /Points [ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ] >> /Data << /Type 0 /LineOrientation 0 /TextOnPathTRange [ -1.0 -1.0 ] /RowGutter 0.0 /ColumnGutter 0.0 /FirstBaselineAlignment << /Flag 1 /Min 0.0 >> /PathData << /Spacing -1 >> >> >> >> << /Resource << /Bezier << /Points [ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ] >> /Data << /Type 0 /LineOrientation 0 /TextOnPathTRange [ -1.0 -1.0 ] /RowGutter 0.0 /ColumnGutter 0.0 /FirstBaselineAlignment << /Flag 1 /Min 0.0 >> /PathData << /Spacing -1 >> >> >> >> ] >> >> /DocumentObjects << /DocumentSettings << /HiddenGlyphFont << /AlternateGlyphFont 3 /WhitespaceCharacterMapping [ << /WhitespaceCharacter ( ) /AlternateCharacter (1) >> << /WhitespaceCharacter ( ) /AlternateCharacter (6) >> << /WhitespaceCharacter ( ) /AlternateCharacter (0) >> << /WhitespaceCharacter ( \)) /AlternateCharacter (5) >> << /WhitespaceCharacter () /AlternateCharacter (5) >> << /WhitespaceCharacter (0) /AlternateCharacter (1) >> << /WhitespaceCharacter () /AlternateCharacter (3) >> ] >> /NormalStyleSheet 0 /NormalParagraphSheet 0 /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 /UseSmartQuotes true /SmartQuoteSets [ << /Language 0 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 1 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 2 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 3 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 4 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 5 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 6 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( 9) /CloseSingleQuote ( :) >> << /Language 7 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 8 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 9 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 10 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 11 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 12 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 13 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 14 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 15 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 16 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 17 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 18 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 19 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 20 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 21 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 22 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 23 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 24 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 25 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( 9) /CloseSingleQuote ( :) >> << /Language 26 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 27 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 28 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 29 /OpenDoubleQuote (0) /CloseDoubleQuote (0) >> << /Language 30 /OpenDoubleQuote (0 ) /CloseDoubleQuote (0 ) >> << /Language 31 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 32 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 33 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 34 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 35 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 36 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 37 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 38 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 39 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote (<) /CloseSingleQuote (>) >> << /Language 40 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 41 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote (<) /CloseSingleQuote (>) >> << /Language 42 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 43 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 44 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( 9) /CloseSingleQuote ( :) >> << /Language 45 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> ] >> /TextObjects [ << /Model << /Text (SCAR ) /ParagraphRun << /RunArray [ << /RunData << /ParagraphSheet << /Name () /Features << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /DropCaps 1 /AutoLeading 1.2 /LeadingType 0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 0 /Zone 36.0 /HyphenateCapitalized true /HyphenationPreference .5 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /SingleWordJustification 6 /Hanging false /AutoTCY 1 /KeepTogether true /BurasagariType 0 /KinsokuOrder 0 /Kinsoku /nil /KurikaeshiMojiShori false /MojiKumiTable /nil /EveryLineComposer false /TabStops << >> /DefaultTabWidth 36.0 /DefaultStyle << /Font 0 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 1 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures false /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms false /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /FillFlag true /StrokeFlag false /FillFirst true /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> /ParagraphDirection 0 /JustificationMethod 0 /ComposerEngine 0 >> /Parent 0 >> >> /Length 5 >> ] >> /StyleRun << /RunArray [ << /RunData << /StyleSheet << /Name () /Parent 0 /Features << /Font 1 /FontSize 185.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 270.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 0 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures true /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms true /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /JapaneseAlternateFeature 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> >> /Blend << /StreamTag /SimpleBlender >> /FillFlag true /StrokeFlag false /FillFirst false /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth .01 /MiterLimit .01 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> >> >> /Length 5 >> ] >> /KernRun << /RunArray [ << /RunData << >> /Length 5 >> ] >> /AlternateGlyphRun << /RunArray [ << /RunData << >> /Length 5 >> ] >> /StorySheet << /AntiAlias 4 /UseFractionalGlyphWidths true >> >> /View << /Frames [ << /Resource 0 >> ] /RenderedData << /RunArray [ << /RunData << /LineCount 1 >> /Length 5 >> ] >> /Strikes [ << /StreamTag /PathSelectGroupCharacter /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 0 /Children [ << /StreamTag /FrameStrike /Frame 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /LineStrike /Baseline 0.0 /Leading 222.00002 /EMHeight 185.0 /DHeight 134.8629 /SelectionAscent -155.49248 /SelectionDescent 62.87109 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -155.49248 0.0 62.87109 ] /ChildProcession 1 /Children [ << /StreamTag /Segment /Mapping << /CharacterCount 5 /GlyphCount 0 /WRValid false >> /FirstCharacterIndexInSegment 0 /Transform << /Origin [ -248.41309 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /GlyphStrike /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -155.49248 496.82617 62.87109 ] /Glyphs [ 54 38 36 53 3 ] /GlyphAdjustments << /Data [ << >> ] /RunLengths [ 5 ] >> /VisualBounds [ -248.41309 -155.49248 248.41309 62.87109 ] /RenderedBounds [ -248.41309 -155.49248 248.41309 62.87109 ] /Invalidation [ -248.41309 -155.49248 333.69781 62.87109 ] /ShadowStylesRun << /Data [ << /Index 0 /Font 1 /Scale [ 1.0 1.0 ] /Orientation 0 /BaselineDirection 2 /BaselineShift 0.0 /KernType 0 /EmbeddingLevel 0 /ComplementaryFontIndex 0 >> ] /RunLengths [ 5 ] >> /EndsInCR true /SelectionAscent -155.49248 /SelectionDescent 62.87109 /MainDir 0 >> ] >> ] >> ] >> ] >> ] >> ] >> ] >> /OpticalAlignment false >> << /Model << /Text (O ) /ParagraphRun << /RunArray [ << /RunData << /ParagraphSheet << /Name () /Features << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /DropCaps 1 /AutoLeading 1.2 /LeadingType 0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 0 /Zone 36.0 /HyphenateCapitalized true /HyphenationPreference .5 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /SingleWordJustification 6 /Hanging false /AutoTCY 1 /KeepTogether true /BurasagariType 0 /KinsokuOrder 0 /Kinsoku /nil /KurikaeshiMojiShori false /MojiKumiTable /nil /EveryLineComposer false /TabStops << >> /DefaultTabWidth 36.0 /DefaultStyle << /Font 0 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 1 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures false /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms false /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /FillFlag true /StrokeFlag false /FillFirst true /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> /ParagraphDirection 0 /JustificationMethod 0 /ComposerEngine 0 >> /Parent 0 >> >> /Length 2 >> ] >> /StyleRun << /RunArray [ << /RunData << /StyleSheet << /Name () /Parent 0 /Features << /Font 1 /FontSize 245.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 1555.19995 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 0 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures true /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms true /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /JapaneseAlternateFeature 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> >> /Blend << /StreamTag /SimpleBlender >> /FillFlag true /StrokeFlag false /FillFirst false /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth .01 /MiterLimit .01 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> >> >> /Length 2 >> ] >> /KernRun << /RunArray [ << /RunData << >> /Length 2 >> ] >> /AlternateGlyphRun << /RunArray [ << /RunData << >> /Length 2 >> ] >> /StorySheet << /AntiAlias 4 /UseFractionalGlyphWidths true >> >> /View << /Frames [ << /Resource 1 >> ] /RenderedData << /RunArray [ << /RunData << /LineCount 1 >> /Length 2 >> ] >> /Strikes [ << /StreamTag /PathSelectGroupCharacter /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 0 /Children [ << /StreamTag /FrameStrike /Frame 1 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /LineStrike /Baseline 0.0 /Leading 294.0 /EMHeight 245.0 /DHeight 178.60222 /SelectionAscent -205.92247 /SelectionDescent 83.26172 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -205.92247 0.0 83.26172 ] /ChildProcession 1 /Children [ << /StreamTag /Segment /Mapping << /CharacterCount 2 /GlyphCount 0 /WRValid false >> /FirstCharacterIndexInSegment 0 /Transform << /Origin [ -102.04346 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /GlyphStrike /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -205.92247 204.08691 83.26172 ] /Glyphs [ 50 3 ] /GlyphAdjustments << /Data [ << >> ] /RunLengths [ 2 ] >> /VisualBounds [ -102.04346 -205.92247 102.04346 83.26172 ] /RenderedBounds [ -102.04346 -205.92247 102.04346 83.26172 ] /Invalidation [ -102.04346 -205.92247 214.9881 83.26172 ] /ShadowStylesRun << /Data [ << /Index 0 /Font 1 /Scale [ 1.0 1.0 ] /Orientation 0 /BaselineDirection 2 /BaselineShift 0.0 /KernType 0 /EmbeddingLevel 0 /ComplementaryFontIndex 0 >> ] /RunLengths [ 2 ] >> /EndsInCR true /SelectionAscent -205.92247 /SelectionDescent 83.26172 /MainDir 0 >> ] >> ] >> ] >> ] >> ] >> ] >> ] >> /OpticalAlignment false >> << /Model << /Text (O ) /ParagraphRun << /RunArray [ << /RunData << /ParagraphSheet << /Name () /Features << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /DropCaps 1 /AutoLeading 1.2 /LeadingType 0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 0 /Zone 36.0 /HyphenateCapitalized true /HyphenationPreference .5 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /SingleWordJustification 6 /Hanging false /AutoTCY 1 /KeepTogether true /BurasagariType 0 /KinsokuOrder 0 /Kinsoku /nil /KurikaeshiMojiShori false /MojiKumiTable /nil /EveryLineComposer false /TabStops << >> /DefaultTabWidth 36.0 /DefaultStyle << /Font 0 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 1 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures false /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms false /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /FillFlag true /StrokeFlag false /FillFirst true /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> /ParagraphDirection 0 /JustificationMethod 0 /ComposerEngine 0 >> /Parent 0 >> >> /Length 2 >> ] >> /StyleRun << /RunArray [ << /RunData << /StyleSheet << /Name () /Parent 0 /Features << /Font 0 /FontSize 1296.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 294.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 0 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures true /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms true /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /JapaneseAlternateFeature 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> >> /Blend << /StreamTag /SimpleBlender >> /FillFlag true /StrokeFlag false /FillFirst false /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth .01 /MiterLimit .01 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> >> >> /Length 2 >> ] >> /KernRun << /RunArray [ << /RunData << >> /Length 2 >> ] >> /AlternateGlyphRun << /RunArray [ << /RunData << >> /Length 2 >> ] >> /StorySheet << /AntiAlias 4 /UseFractionalGlyphWidths true >> >> /View << /Frames [ << /Resource 2 >> ] /RenderedData << /RunArray [ << /RunData << /LineCount 1 >> /Length 2 >> ] >> /Strikes [ << /StreamTag /PathSelectGroupCharacter /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 0 /Children [ << /StreamTag /FrameStrike /Frame 2 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /LineStrike /Baseline 0.0 /Leading 1555.20007 /EMHeight 1296.0 /DHeight 943.48389 /SelectionAscent -1095.75439 /SelectionDescent 403.10156 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -1095.75439 0.0 403.10156 ] /ChildProcession 1 /Children [ << /StreamTag /Segment /Mapping << /CharacterCount 2 /GlyphCount 0 /WRValid false >> /FirstCharacterIndexInSegment 0 /Transform << /Origin [ -509.41406 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /GlyphStrike /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -1095.75439 1018.82813 403.10156 ] /Glyphs [ 50 3 ] /GlyphAdjustments << /Data [ << >> ] /RunLengths [ 2 ] >> /VisualBounds [ -509.41406 -1095.75439 509.41406 403.10156 ] /RenderedBounds [ -509.41406 -1095.75439 509.41406 403.10156 ] /Invalidation [ -509.41406 -1095.75439 1106.86816 403.10156 ] /ShadowStylesRun << /Data [ << /Index 0 /Font 0 /Scale [ 1.0 1.0 ] /Orientation 0 /BaselineDirection 2 /BaselineShift 0.0 /KernType 0 /EmbeddingLevel 0 /ComplementaryFontIndex 0 >> ] /RunLengths [ 2 ] >> /EndsInCR true /SelectionAscent -1095.75439 /SelectionDescent 403.10156 /MainDir 0 >> ] >> ] >> ] >> ] >> ] >> ] >> ] >> /OpticalAlignment false >> << /Model << /Text (O ) /ParagraphRun << /RunArray [ << /RunData << /ParagraphSheet << /Name () /Features << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /DropCaps 1 /AutoLeading 1.2 /LeadingType 0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 0 /Zone 36.0 /HyphenateCapitalized true /HyphenationPreference .5 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /SingleWordJustification 6 /Hanging false /AutoTCY 1 /KeepTogether true /BurasagariType 0 /KinsokuOrder 0 /Kinsoku /nil /KurikaeshiMojiShori false /MojiKumiTable /nil /EveryLineComposer false /TabStops << >> /DefaultTabWidth 36.0 /DefaultStyle << /Font 0 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 1 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures false /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms false /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /FillFlag true /StrokeFlag false /FillFirst true /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> /ParagraphDirection 0 /JustificationMethod 0 /ComposerEngine 0 >> /Parent 0 >> >> /Length 2 >> ] >> /StyleRun << /RunArray [ << /RunData << /StyleSheet << /Name () /Parent 0 /Features << /Font 1 /FontSize 245.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 1555.19995 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 0 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures true /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms true /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /JapaneseAlternateFeature 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> >> /Blend << /StreamTag /SimpleBlender >> /FillFlag true /StrokeFlag false /FillFirst false /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth .01 /MiterLimit .01 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> >> >> /Length 2 >> ] >> /KernRun << /RunArray [ << /RunData << >> /Length 2 >> ] >> /AlternateGlyphRun << /RunArray [ << /RunData << >> /Length 2 >> ] >> /StorySheet << /AntiAlias 4 /UseFractionalGlyphWidths true >> >> /View << /Frames [ << /Resource 3 >> ] /RenderedData << /RunArray [ << /RunData << /LineCount 1 >> /Length 2 >> ] >> /Strikes [ << /StreamTag /PathSelectGroupCharacter /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 0 /Children [ << /StreamTag /FrameStrike /Frame 3 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /LineStrike /Baseline 0.0 /Leading 294.0 /EMHeight 245.0 /DHeight 178.60222 /SelectionAscent -205.92247 /SelectionDescent 83.26172 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -205.92247 0.0 83.26172 ] /ChildProcession 1 /Children [ << /StreamTag /Segment /Mapping << /CharacterCount 2 /GlyphCount 0 /WRValid false >> /FirstCharacterIndexInSegment 0 /Transform << /Origin [ -102.04346 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /GlyphStrike /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -205.92247 204.08691 83.26172 ] /Glyphs [ 50 3 ] /GlyphAdjustments << /Data [ << >> ] /RunLengths [ 2 ] >> /VisualBounds [ -102.04346 -205.92247 102.04346 83.26172 ] /RenderedBounds [ -102.04346 -205.92247 102.04346 83.26172 ] /Invalidation [ -102.04346 -205.92247 214.9881 83.26172 ] /ShadowStylesRun << /Data [ << /Index 0 /Font 1 /Scale [ 1.0 1.0 ] /Orientation 0 /BaselineDirection 2 /BaselineShift 0.0 /KernType 0 /EmbeddingLevel 0 /ComplementaryFontIndex 0 >> ] /RunLengths [ 2 ] >> /EndsInCR true /SelectionAscent -205.92247 /SelectionDescent 83.26172 /MainDir 0 >> ] >> ] >> ] >> ] >> ] >> ] >> ] >> /OpticalAlignment false >> << /Model << /Text (SCAR ) /ParagraphRun << /RunArray [ << /RunData << /ParagraphSheet << /Name () /Features << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /DropCaps 1 /AutoLeading 1.2 /LeadingType 0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 0 /Zone 36.0 /HyphenateCapitalized true /HyphenationPreference .5 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /SingleWordJustification 6 /Hanging false /AutoTCY 1 /KeepTogether true /BurasagariType 0 /KinsokuOrder 0 /Kinsoku /nil /KurikaeshiMojiShori false /MojiKumiTable /nil /EveryLineComposer false /TabStops << >> /DefaultTabWidth 36.0 /DefaultStyle << /Font 0 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 1 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures false /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms false /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /FillFlag true /StrokeFlag false /FillFirst true /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> /ParagraphDirection 0 /JustificationMethod 0 /ComposerEngine 0 >> /Parent 0 >> >> /Length 5 >> ] >> /StyleRun << /RunArray [ << /RunData << /StyleSheet << /Name () /Parent 0 /Features << /Font 1 /FontSize 185.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 270.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 0 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures true /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms true /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /JapaneseAlternateFeature 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> >> /Blend << /StreamTag /SimpleBlender >> /FillFlag true /StrokeFlag false /FillFirst false /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth .01 /MiterLimit .01 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> >> >> /Length 5 >> ] >> /KernRun << /RunArray [ << /RunData << >> /Length 5 >> ] >> /AlternateGlyphRun << /RunArray [ << /RunData << >> /Length 5 >> ] >> /StorySheet << /AntiAlias 4 /UseFractionalGlyphWidths true >> >> /View << /Frames [ << /Resource 4 >> ] /RenderedData << /RunArray [ << /RunData << /LineCount 1 >> /Length 5 >> ] >> /Strikes [ << /StreamTag /PathSelectGroupCharacter /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 0 /Children [ << /StreamTag /FrameStrike /Frame 4 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /LineStrike /Baseline 0.0 /Leading 222.00002 /EMHeight 185.0 /DHeight 134.8629 /SelectionAscent -155.49248 /SelectionDescent 62.87109 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -155.49248 0.0 62.87109 ] /ChildProcession 1 /Children [ << /StreamTag /Segment /Mapping << /CharacterCount 5 /GlyphCount 0 /WRValid false >> /FirstCharacterIndexInSegment 0 /Transform << /Origin [ -248.41309 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /GlyphStrike /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -155.49248 496.82617 62.87109 ] /Glyphs [ 54 38 36 53 3 ] /GlyphAdjustments << /Data [ << >> ] /RunLengths [ 5 ] >> /VisualBounds [ -248.41309 -155.49248 248.41309 62.87109 ] /RenderedBounds [ -248.41309 -155.49248 248.41309 62.87109 ] /Invalidation [ -248.41309 -155.49248 333.69781 62.87109 ] /ShadowStylesRun << /Data [ << /Index 0 /Font 1 /Scale [ 1.0 1.0 ] /Orientation 0 /BaselineDirection 2 /BaselineShift 0.0 /KernType 0 /EmbeddingLevel 0 /ComplementaryFontIndex 0 >> ] /RunLengths [ 5 ] >> /EndsInCR true /SelectionAscent -155.49248 /SelectionDescent 62.87109 /MainDir 0 >> ] >> ] >> ] >> ] >> ] >> ] >> ] >> /OpticalAlignment false >> << /Model << /Text (O ) /ParagraphRun << /RunArray [ << /RunData << /ParagraphSheet << /Name () /Features << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /DropCaps 1 /AutoLeading 1.2 /LeadingType 0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 0 /Zone 36.0 /HyphenateCapitalized true /HyphenationPreference .5 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /SingleWordJustification 6 /Hanging false /AutoTCY 1 /KeepTogether true /BurasagariType 0 /KinsokuOrder 0 /Kinsoku /nil /KurikaeshiMojiShori false /MojiKumiTable /nil /EveryLineComposer false /TabStops << >> /DefaultTabWidth 36.0 /DefaultStyle << /Font 0 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 1 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures false /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms false /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /FillFlag true /StrokeFlag false /FillFirst true /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> /ParagraphDirection 0 /JustificationMethod 0 /ComposerEngine 0 >> /Parent 0 >> >> /Length 2 >> ] >> /StyleRun << /RunArray [ << /RunData << /StyleSheet << /Name () /Parent 0 /Features << /Font 1 /FontSize 245.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 1555.19995 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 0 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures true /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms true /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /JapaneseAlternateFeature 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> >> /Blend << /StreamTag /SimpleBlender >> /FillFlag true /StrokeFlag false /FillFirst false /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth .01 /MiterLimit .01 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> >> >> /Length 2 >> ] >> /KernRun << /RunArray [ << /RunData << >> /Length 2 >> ] >> /AlternateGlyphRun << /RunArray [ << /RunData << >> /Length 2 >> ] >> /StorySheet << /AntiAlias 4 /UseFractionalGlyphWidths true >> >> /View << /Frames [ << /Resource 5 >> ] /RenderedData << /RunArray [ << /RunData << /LineCount 1 >> /Length 2 >> ] >> /Strikes [ << /StreamTag /PathSelectGroupCharacter /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 0 /Children [ << /StreamTag /FrameStrike /Frame 5 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /LineStrike /Baseline 0.0 /Leading 294.0 /EMHeight 245.0 /DHeight 178.60222 /SelectionAscent -205.92247 /SelectionDescent 83.26172 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -205.92247 0.0 83.26172 ] /ChildProcession 1 /Children [ << /StreamTag /Segment /Mapping << /CharacterCount 2 /GlyphCount 0 /WRValid false >> /FirstCharacterIndexInSegment 0 /Transform << /Origin [ -102.04346 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /GlyphStrike /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -205.92247 204.08691 83.26172 ] /Glyphs [ 50 3 ] /GlyphAdjustments << /Data [ << >> ] /RunLengths [ 2 ] >> /VisualBounds [ -102.04346 -205.92247 102.04346 83.26172 ] /RenderedBounds [ -102.04346 -205.92247 102.04346 83.26172 ] /Invalidation [ -102.04346 -205.92247 214.9881 83.26172 ] /ShadowStylesRun << /Data [ << /Index 0 /Font 1 /Scale [ 1.0 1.0 ] /Orientation 0 /BaselineDirection 2 /BaselineShift 0.0 /KernType 0 /EmbeddingLevel 0 /ComplementaryFontIndex 0 >> ] /RunLengths [ 2 ] >> /EndsInCR true /SelectionAscent -205.92247 /SelectionDescent 83.26172 /MainDir 0 >> ] >> ] >> ] >> ] >> ] >> ] >> ] >> /OpticalAlignment false >> << /Model << /Text (SCAR ) /ParagraphRun << /RunArray [ << /RunData << /ParagraphSheet << /Name () /Features << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /DropCaps 1 /AutoLeading 1.2 /LeadingType 0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 0 /Zone 36.0 /HyphenateCapitalized true /HyphenationPreference .5 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /SingleWordJustification 6 /Hanging false /AutoTCY 1 /KeepTogether true /BurasagariType 0 /KinsokuOrder 0 /Kinsoku /nil /KurikaeshiMojiShori false /MojiKumiTable /nil /EveryLineComposer false /TabStops << >> /DefaultTabWidth 36.0 /DefaultStyle << /Font 0 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 1 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures false /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms false /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /FillFlag true /StrokeFlag false /FillFirst true /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> /ParagraphDirection 0 /JustificationMethod 0 /ComposerEngine 0 >> /Parent 0 >> >> /Length 5 >> ] >> /StyleRun << /RunArray [ << /RunData << /StyleSheet << /Name () /Parent 0 /Features << /Font 1 /FontSize 185.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 270.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 0 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures true /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms true /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /JapaneseAlternateFeature 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 .47842 .79999 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 1.0 0.0 0.0 ] >> >> /Blend << /StreamTag /SimpleBlender >> /FillFlag true /StrokeFlag false /FillFirst false /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth .01 /MiterLimit .01 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> >> >> /Length 5 >> ] >> /KernRun << /RunArray [ << /RunData << >> /Length 5 >> ] >> /AlternateGlyphRun << /RunArray [ << /RunData << >> /Length 5 >> ] >> /StorySheet << /AntiAlias 4 /UseFractionalGlyphWidths true >> >> /View << /Frames [ << /Resource 6 >> ] /RenderedData << /RunArray [ << /RunData << /LineCount 1 >> /Length 5 >> ] >> /Strikes [ << /StreamTag /PathSelectGroupCharacter /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 0 /Children [ << /StreamTag /FrameStrike /Frame 6 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /LineStrike /Baseline 0.0 /Leading 222.00002 /EMHeight 185.0 /DHeight 134.8629 /SelectionAscent -155.49248 /SelectionDescent 62.87109 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -155.49248 0.0 62.87109 ] /ChildProcession 1 /Children [ << /StreamTag /Segment /Mapping << /CharacterCount 5 /GlyphCount 0 /WRValid false >> /FirstCharacterIndexInSegment 0 /Transform << /Origin [ -248.41309 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /GlyphStrike /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -155.49248 496.82617 62.87109 ] /Glyphs [ 54 38 36 53 3 ] /GlyphAdjustments << /Data [ << >> ] /RunLengths [ 5 ] >> /VisualBounds [ -248.41309 -155.49248 248.41309 62.87109 ] /RenderedBounds [ -248.41309 -155.49248 248.41309 62.87109 ] /Invalidation [ -248.41309 -155.49248 333.69781 62.87109 ] /ShadowStylesRun << /Data [ << /Index 0 /Font 1 /Scale [ 1.0 1.0 ] /Orientation 0 /BaselineDirection 2 /BaselineShift 0.0 /KernType 0 /EmbeddingLevel 0 /ComplementaryFontIndex 0 >> ] /RunLengths [ 5 ] >> /EndsInCR true /SelectionAscent -155.49248 /SelectionDescent 62.87109 /MainDir 0 >> ] >> ] >> ] >> ] >> ] >> ] >> ] >> /OpticalAlignment false >> ] /OriginalNormalStyleFeatures << /Font 2 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 1 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures false /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms false /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /JapaneseAlternateFeature 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /Blend << /StreamTag /SimpleBlender >> /FillFlag true /StrokeFlag false /FillFirst true /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] /Kashidas 0 /DirOverride 0 /DigitSet 0 /DiacVPos 4 /DiacXOffset 0.0 /DiacYOffset 0.0 /OverlapSwash false /JustificationAlternates false /StretchedAlternates false /FillVisibleFlag true /StrokeVisibleFlag true >> /OriginalNormalParagraphFeatures << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /DropCaps 1 /AutoLeading 1.2 /LeadingType 0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 0 /Zone 36.0 /HyphenateCapitalized true /HyphenationPreference .5 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /SingleWordJustification 6 /Hanging false /AutoTCY 0 /KeepTogether true /BurasagariType 0 /KinsokuOrder 0 /Kinsoku /nil /KurikaeshiMojiShori false /MojiKumiTable /nil /EveryLineComposer false /TabStops << >> /DefaultTabWidth 36.0 /DefaultStyle << >> /ParagraphDirection 0 /JustificationMethod 0 /ComposerEngine 0 >> >>8BIMFMsk 23IC<7400.,*)(('(&&&&&&&%" "!! !!!!   9:43//.-.,,-**(**))(('((()((*%'''(''&%&$&'&'(&&''&'&'%'%&&%&%&&'$%$%#&&&%%%%&%%&&&&$$##$$%%&&&%&&%%$$$%#%$$$#%%###$#$$$"%$" !####%%##%$#$$"$%$$%$#%"#" #!""%#######"$#$#######$##"$"$"$"$"$######""""#"#"$#####""#"#$"#!!! !"!!"!#""$""$"$"""""""""""""""#"""#"#"##""!!%71.*--,-*))<:85566534543322/.111231002:X]v\TUXVYTpp|rpllpmkgebfjgagjlprkjsmeeec``_a\bab_edbc^cddfcbg__`\[`_\]`c`]ZYYWVWSR_a`_]^^]^\^[Z_fa_gddggfdlsssttsvxwzy{tzq__\`_GR--3 """-:ANKMU`]debacbbbfacbdbb_^`Zb```[```d]\^bac^^\`ac_b]``]aba\]^W\Y[]X][_Z\[\Z[\[[]]`]\Z[Y\a^[\^]]\^]Zbguiccaeadabcb]aZ^]___[`b`a_^[\\V]X]^\_[]^^dbccbcbcc`aa`accbecdacaa`a`^aaabbcaca^bcacdbaab^_^^_]`ab_`^]`^_`\[Z]_a_```ebcbb]c``\ZUNLKDII>3+####"#####%#"##$$"#$$$$"#%###%#$$$$$&&$#%%#"$$$#$$%%####$$%$#"""$&%%$#%%$$#%%%%%$&%&%%%###%&&&%%%&%&'$%&&&&&&%&'(&&((')(()*****,+)))***,-+0060!""!"! !!""!"##$$$&(*.23IB<7400.,*)(('(&&&&&&&%" "!! !!!!   9:43//.-.,,-**(**))(('((()((*%'''(''&%&$&'&'(&&''&'&'%'%&&%&%&&'$%$%#&&&%%%%&%%&&&&$$##$$%%&&&%&&%%$$$%#%$$$#%%###$#$$$"%$" !####%%##%$#$$"$%$$%$#%"#" #!""%#######"$#$#######$##"$"$"$"$"$######""""#"#"$#####""#"#$"#!!! !"!!"!#""$""$"$"""""""""""""""#"""#"#"##""!!%71.*--,-*))<:85566534543322/.111231002:X]v\TUXVYTpp|rpllpmkgebfjgagjlprkjsmeeec``_a\bab_edbc^cddfcbg__`\[`_\]`c`]ZYYWVWSR_a`_]^^]^\^[Z_fa_gddggfdlsssttsvxwzy{tzq__\`_GR--3 """-:ANKMU`]debacbbbfacbdbb_^`Zb```[```d]\^bac^^\`ac_b]``]a`a\]^W\Y[]X][_Z\[\Z[\[[]]`]\Z[Y\a^[\^]]\^]Zbeu~iccaeadabcb]aZ^]___[`b`a_^[\\V]X]^\_[]^^dbccbcbcc`aa`accbecdacaa`a`^aaabbcaca^bcacdbaab^_^^_]`ab_`^]`^_`\[Z]_a_```ebcbb]c``\ZUNLKDII>3+####"#####%#"##$$"#$$$$"#%###%#$$$$$&&##%%#"$$$#$$%%####$$%$#"""$&%%$#%%$$#%%%%%$&%%%%%###%&&&%%%&%&'$%&&&&&&%&'(&&((')(()****),+)))***,-+0060!""!"! !!""!"##$$$&(*.23GB;720.,,*)(('(&&&&$$$# !     9:42//.-,,++()((*))(('(('('(*%'&''&'&$%$&&&'(&&''&'&'%&%&&%&%%%%$%#%#&&&%%%%&$$%%%%#$##$$%%&&%%&&%$##$####$$#%$###$#$$$"#$" !###"%%"#%##$$"$%##%#"%"#" #!"!$"#"##"#"$#$###"#"##"""#"$"$"$"#"###""""""#"""##""##""#"#""#!!! !" !"!"""$""#"#""""""""""""""""""""""""""" !%61.*--,,*))<:85266534543320/,1112010027X]v\SUXVYTpp|rpljnmkgebfjgagjlprkjsmeeea``_a\`ab_edbc^cbdfcbg__`\[`_\]`c`]ZYYWVWSR_a`_[^^]^\^[Z_fa_gddggfdlsssttsvxwzy}{tzq__\`_GR--3 """-:ALKMU`]debaabbbfacbdbb_^^Z````[^``d]\^bac^\Z`_c_b[``]_`a\]^W\W[]X][_ZZY\Z[Z[[[[`]ZZ[Y\a^[Z^]]Z^]Zbeu~iccaead`bcb]_Z^]___[`b`a_^[\\V]X]^\^[]^^cbccbcbcc````accbecdacaa`a`^`aabbcaca^bcacdbaab\_\^^]`ab_`^]`^__\[Z]_a_```ebcbb]c``\ZUNLKDIH>3+####"###"#$""#"$$"#$$#$""$###$#"#$#$%$##$%#""#$#"#$%####$$%$#"""$&%%$#%%$##%%%%%$#%%$$$##!%&&&%%%&%&'$%&&&&&&%&&(&&((')(()****)*))'(***+-+0060 ! "! !!""!"##$$$&(*.23IC<7400.,*)(('(&&&&&&&%" "!! !!!!      &%&%%$$%%&%&&&##%!"!! $! $$! !!$$%$%#$""#&$$$%$$$$ $$$$$$ $$$$$#$$  $$$##$$####"$$#"$#! !"###$#"$ "$#$#$## "! #!""%#"##"##"$"$"####" "$$$$##""""#"#"$"####""#"$#!!!!"!"#"$"$"#""""""""""""""#"""##"#""!!!  ! !!#"""""""""""""""#""#"""""" """""""""""" "" "" """"""""""" ""  """""""""""""  """""""""""" """"""""   """"" "" " """ """"" "" """" """""" "" """""""""""""" """" """""""#" "#"" "$#$%$$##%%##%$%&$#%$%%&$%%%$$&&$#%%#"$$$#$$%%###$%%%$#"##%&&'&%%&&$%&&'&&&&&&$$$#!#%&&&%%%&&&'&'''''(('''*(()*)***+,,,,+--''(()(+++/020!""!"! !!""!"##$$$&(*.2߸rC *wժ}) jiQW  I܌@9ܳ˒,,˵}""ηpvvû_ |ǽdʿfkXs  ?_o?=ք /??o_??@ܐ?OϿ??e?_߿o?p1?_߿?/V?oϯ_?$`/O߯O/Y/_o?Oϟ_/B?oO1n?ߟo/n?o/./oo/?\__z /ϭߏN+Nߪobg_Ϗ._ߟNIM_O_OP6OOߘ/L+_ϕߏt/_Z4_ϐ?)/ p@_?7߉oH/X?_+i_o{1_P_+I_? !_?Uw__0L__?_x}??KӂO?'?oX?Nև''Ɉ_ ??Pw%oT/?*َ \/o 5//?n/CfoOEH OO ҙ .>֚^ ʛ.k_GN=-;ow-{!O._    .?Oo_ / x _ }ϻo Ϻ Э.!ݮ/-߯//.?/:__c__g____Զ _׷"O"$/\,/bb/e/xϩ ּϨϧ8r_3_L/+z]ϡ_] =^-=4-d Ϝϯ=^,-^N -H =6 $a_e+ < , OYoO *>$ _/_6 .1 }?< _H?H ?3/ bOOoP s-o!|_R n,~"\ 8; v\;Ge  }}*# ZR 8#(:m I[W V8]Y]9V \]F]\IK [M0<d7= <  ?"t]]9z9\a [ JY< ;(f9W'&(%Q 9 U{%ZYZ{k 7$69A Zk[Q4!KXz N E>V|8RBC.!= Xc9  iƮ:O C Oׯ+]S!s U6f>ȱeKVNgR/eҳQMh PdKT@q,#Mr2aTJO#<ʷnm"'-\_??/??O _??_UJO/_?_E&_O!}LO_t4_ƹoz__/9m_w)9_σp_?O?/??_8sh_ߏ//5 O_o;OO+»lm/r)oO//I/o?( o]5(/ Q_?߆^s__2_HO?o__// _/o'_&dwP]49mo߄߈_/_υ't_?ky2φM $/O_߉.D/߸ e?oϟO??oo??/?O&yOo?OO/?oj3?O_?_o??o_?/??oO_o_/?/oϊ?/O_?/OL???_"3|?O/O_OH?_?_o #)o?O/`oo?_oc_?///_?_Oo?o?_?_ߟ_/??/ߟO%z_???_o?o?O/YD9__?O/o;_OO/Oo_?O_//_O_?O/??_?'/o_/_?oU=_/_/_O_O!'o?o _O??O__?_/o?^/o?O?0/_?/?O?:Z??_/?_o/l9oO_/?/??/ /?__?O__//???/?oO?__oO?/?7sO_?jWD_O???>"/o??o/C?oOߏ#?oOo//߾?O/ǿ/_O_o?' "?_?_??__PrC??/_ok?`/?߲o?o3__O?Ϡ__??_//o?/ߐ _o??o?O?_??ϟ?_?_?_Oʾ_?ϵ_?'a^?/__`>|d?/?/Ϸ??/g?D??o??O//@;_?_o??_/;#/o_/Α#OO?O O///O/?o_o_o/?/_/o/OoϜ????_3OO/o/_` OO//OߟOa??_O￯OOD??O?O@???/o=?_oo?O??oߒ?/o?O_?_??/o_?__?/?___??????o?w?oO/o?_:_?????___?///^_??/o/l_?_߼O__OOo?_?/_r_ߏO/@_O_OO??̓?_Oߏ__?@_?ߟ_??o<_ߟO?o_o!b/_?߷?L???/??#?_?_?oؿ/O_o????o???o% ???oQ_?o?/?`_O??___??O] OO_h/?o?_Oo?oo/?o?̻_/_/?O_ϛ?oO???O_/?ߔo__/_ߜ?/O_??o//__o??h?_ooߝo____O?__/__/????\o__o?????_/?o/_/__?O?OO/o9 O_???w//O_o?x/__???//_/o_??//_O//_v_///__/?_?o?/_O???O??oooo///?????/x/__o/_?/%^/?//_OQb/O_O/O_?/___5_o/_?Oߟo/o/_o_/__?OO?O?o_O_/_ߟO/__?_/_ /_o?o???o_o?Oo??_O___?_??o?O_//?oo?oO_4?OOo??_?O??/??O_B?_?????_n?O?Ooo?___O__???bV///_/??/??O?/??O?/?O?/?Oo??/H0oOo???Oߏ?/oϯo??/?_߿??/??_??o?/?ϼϿ_??/?o߶5 B[^V-Ϧ蹎ފC ǧ? ߲?@тyڋALzήE\Qxx y~ӽ| }0wHjN}Քĵ~.ƕ`?~_>_!^|wa<||~ƸGpZx'{Ә {~ܺ*z{wJtzy*MÚԻ>xԓNΛIվ|a~P~Ԝ`"ܽW{0 ~4wNExxܝ lTzϾۻ|y؞@{ٿxkw׿!>^DzY^&=u}ٟQȑ;9{I{ԸU}^ѠzUOw5O~ _Ґ;|zx|y۶~~١||a0z١{~.**xۡbKy١Ƶz~WՏ!z_ô'STp(#&ygxyijy}.H0{qVֲ+ڥ =29zߍp|}c-\Ű{<:4xiy x$w_fFUˬ?ڮϭzϯߋEϲߛ%4xϽʏfԚزͦyӒO9ϒлétnmķ_ѶεѫitssnЪإΨtzuvvpƦ ߷py0Oɫ xuvvoؐlzx}bmmnvw|}zrqu muq|t _4 skmmntvy޹k /! Uajkkjns lmmlr};n{}I | ~( |w_ |/>|zz&<~՝yǦyO2<Oyxٚxߌ|x7_|x~oP|yףx/|~V_|˗#vٖ?_Ӡ}ύ3>y_O X#ɝύ{OOyJ/ώHO5_}oK#ט/y%O"_ us{|?e_ٖ/ `yw_ _{oY!Ӕ5O|/)1oΓ/?y / x/ jO #oڒ/rwzwc<x/a/ȑoxё_?7_k_ t_O {: y_ԏ"]ُy|̿/:8޿_Z~s \{ؾ{y_!<|ۍA&ͽx|lό _O' _ȋz_cq޻ ?b&պ{_}d{w ]lj~@Z_xX?ވ?#zZ?h`ɷ?Od^?˶/T  OÅPxz#~z߄d>?{zw{`_|O~$w? }_C$QoȰolw%~?o|ρչX4ĽO۹ڻhɩ? R?=oոϩz/Y!_)Ć]߬; ty=D5߯e۱2DtaByIJ%,UʬuW' V/A{10WħzLOsۻLʔ.i5R%H ϡϠE2gbB@# 6=$PJl GΘ̗ ɖh>*?sYY4KIGExDGyB6nM8K:J(ISuQC'o@3uyzORD++AJW[R gGJFjP-T}S`LHR`QP9]NZ NK]wX$ .;/]"߈1j-I #]I$ߍk] JE;ߔ+Z<9Foout  7!6u(#RT#[T$\%u)(%wte&y|Y d ZZ I D°DP,Cn-7WH`/J9 Y߃"o"ܿMZN݆#?_Nl"}aϵ)%3҃#;߲$Յ$t%v׈%t"ae-߭y?P /dx$o,zߜA.=1{ߞO-xϟPp`Qy>R.uϞR1á*oZ&@N{߯vA"OwϒqA"mn(]q￯qP( IӚI$)^kϲϿk^)$/˘+$*kkttkk*$.%KllK%fI- a_^(\3dCccg'B(iD'gH6KVIH7uKK-9y,S;.ߊߏ!,ގ @>QO!zhMԅ:.uddтA Np/ Dm-O~k0Ol2.jJJzh/ =?af.+--%Xӊy~Ŋzīzzܧ_  "'*(% "WXurnnF6  +64#(   +AAB>412Rgpb%-)t zzz />9677Zkh-ks u)#$%%-.o}w zzz߰g#'?>Gb~ wyyx~zzz~~ފzzz |~ŋzzz~|wzzz|zzz|zzzzz~՝ދzzzyz|zzǦyzzzyxzzzٚxz|zz|x͌zzz|x~zzz|yףxz~zz|~z{zz|˗ތzzzzzzٖzzzӠz~zz}zzzyōzzzzzzzzzɝzzzŎzzzzzzyz|zzzzzŎz|zzzzzzzzޏz|zz}zzzטzzzzzz͏zzzzzzzzz{zz{zz|zzzzٖzzzzzzzzzzzzzzywzzzzzzzzzzzz|zzzzzzzzzzӔzzzzzzzz|zzzzzzzzzz|zzΓzzzzyzzzzzzzzxzzzzzzzzzzzzڒzzzzwzzz||zzwzzzzxzz|zzzzzzȑzz|zzxёzzzzzz|zzzzzzzzzzzzzzzz{zz{zzzzzzzzzzzzzzzzyzz|zzԏzzzzُzzzzy|̿zzzz޿zzzzzzzzzz}zz~zz|zzzzzz{ؾzzzz{yzzzz|zzzzۍzzzzͽzz|zzx|zzzzzzzzόzzzzzzzzzzzzȋzzzzz޻zz|zzzzzzzz|zzպ{zz|zz}zz|zz{wzzzzljzz|zz~zzzzxzz||zzzzzzވzz{zzzzzz|zzzzz|zzzzzzzzɷzz|zzzzzzzzzz}zzzzzzzz˶zzzzzzzzzzzzzzzÅzzzzzxzzzzzz~zzzzzz߄zzzzz{zzzzzzw{zzzzzzz|zzz~zzzzzzz}zzz}zzzzzzzzzzzzzzzzzzzzȰzz|zzzzwzzzzzzzz}zzz|zzϽzzzρչzzzzĽzz|zz۹zz}zzɩzz˺zzzz؃zzzzzzzz}}zzzzηzzzzzzzzzzͫzz~zzzz޴zzzzӈzz|zzzzŮzz}zz|zz}Ɋzz|zz԰zzzz}zzzzzz|zzzzzz~|zzzz|zzzz«zzƵzz}zz|ݶzzʐzzzzzz|zz}}zzzz}ђzzzzɦzz{ٺzzzz~|zzzzzzzzzzzzzz}zz|zz}̗zzȿzzzz|zzğzz|zzŞ|zzʽzzšzz|zzzzzzzzzz|zzzzȝzzzzzz۸zz}zz|zz}}zzzz}̠zzzzڕzzzzɔzz|zz“zz|zz|zz|DzzzŤzz}ϱzzzz||zzzz||zzzz|}zz}zz|}zz֨zzzzzzzzzzzzzzzzzzzz~zzzz}zzzzʮzzzzïzzzzzz~z‚zz}톤zzz~zzz~zzz}z}~zzz}}zz~z~}zzzѷzzzɸzzzȹzz⊤zȺzz}~zǻzz|zzzzzz~z}zz劤z}zz~zzz~zzzzzzᇣzzz||zzzz}zz~툣zzz|zzzzzz兢zzzz}}zzz}zz݃|z~zzzzzzzz|dž|zzz}zzzzzzzz}zzzzzЋzzzzz|zz~zzzބzz}zzzzz~zzzݒ~zzzzzzzzzz}zzzzz~~zzzzzęˁzz~zzzӛzzzzzԝzz}zzzԟzzzzzԡ~zz}zzzԣ~zz~zzz֥zzzzzΧzzzzzzz}zzzzzzzzzzzzz߰zz}zzz˲ѝzzzzzzzzzzڷ쭅zz~վ}zzzϠzzzzzzzܻzzz٣zzށzzzzz޸zzzʣzz}zzzܬzzzzz弅zz}}zzz澌zzzzzzz԰zzzzzzzz㯅zz}zzzˤzz~ͫzzzz渍zz~zz˜zzzẓzz~zzͤzz~zz޸~zz؊zz̥zz~zzzz雚zzָzzz~ԅzzֵzzz~zzzޡֵzzzzzzͲzzzԐzzzzzzԖzzzzzzє~zzzzzꠦzzzzzݑzzzzz҉zzzzzzzzzzzzzzz~zzzzz~zzzzzÄ~zzzz҂~zzzz~zzzz~zzzz~~zzzzzzzzǃzzzzڏzzzzzzzz}zzzz}Ńzzzzޖzzzz}~zzzz̉zzzz~}zzzzˈzzzz~zzzz~͑zzzzzzzzޥ~zzzz͙zzzz͒zzzzŠzzzzŋzzzzċzzzz~˚zzzzʣ~zzzzٰzzzz~鿘zzzzЮzzzzzzzzմzzzԳzzzӲzzzӲ~zzzzzzѰzzzɯzzȮzzѯzzȮzzǮzzԿzzƯzzŰzzշzz ͯzzzz} ͩ̐ ̅ ́ ́ ́ ́ ́ ́Ϳ́Ϲ́ϴ́ѯ́Ϋ́̿Ӧ̺́Ѣ̶́ў̲́њ̮́Җ̪́ג̦́я̣́؋̠́҈̜́΅̙̖́͂́́́̓́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑́́̑Ϳ́́̑ν́́̑޺́́̑͹́́̑̾Ϸ̆Ҳ̼̑ϵ̓ ̺̑ϳ̸̝̑ұ̶̵̥̫̑֯̑ݭ̷̲̳̱̼̯̑߫̑ͪ̑Ҩ̮̑٦̬̫̑̑ϣ̩̑ա̧̑ޟҔ̦Ξܗ̤՜қ̢͛՞̡љܡ̠͘ܤ̞ϖܧ̛̝͕۪ϓխ͒̚Ұ̘̑ϳ̗̑ص̑̕Ҹ̔̑غ̒̑Ͻ̑̑տ̴̶̸̢̧̖̘̜̞̠̤̦̩̫̬̮̰̱̳̹̺̼̐̑̎̒̍̔̋̊̉̇̆̅̃̂́́́́́́́́́́́́́́́́̽́̿́́́́́́́́́́̚ρՂ߃΅цه߿Ί΋֌ͿߍΑҒ֓֔ߴߕͷ߳ߖͶ߲͵߱ʹ߰г߯Ҳ߮ϜӱۭϝӰϞկϟϠϡϢϣϤϥϩѨߤ֧ߣۦߪޫΣج̿Ңޞխ̾֡՝Ѯ̽ϯ̼̻̽͟Оߚ̺՝ՙ߲̺ܜճ̹Ҵ̸͛ε̷К̷ؖי̶ߘ߷̵ո̴Ηؓι̴Ֆ̳ޕ̲ܻ̲ΖҼ̱ӕϽ̰ߕ̰߾̯͖տ̮̮̭̬֕Е̬ڕ̨̨̫̪͕̪̩͕֕ҕ̧̧̦ѕ̥̥̤Ӕ̢̤̣͕Ք̢̡̡͔ה̡̡̢̢̧̧̧̨̨̛̛̛̛̠̠̟̖̞̖̞̗̝̗̝̘̜̘̜̙̙̙̙̜̘̜̘̝̗̝̖̞̖̞̟̟̠̠̣̣̤̤̤̥̥̦̦̩̩̪̪̪̫̫̫̬̬̭̭̭̮̮̮̯̯̯̰̰̱̱̱̲̔̔̔̓̓̓̒̒̑̑̐̐̏̏̎̎̎̍̍̌̌̋̋̋̊̊̉̉̉̈̈̇̇̇̆̆̆̅̅̄̄̄̃̃̃̂̂̕̚̚̚̚̕̕ت̲̲̲̂́́׫̳̳̳́́́լ̴̴̴́́́׭̵̵̵́́́ܮ̶́ή̶̶̶́́߯́ү̷̷̷́́́װ̸́Ͱ̸̸́́۱̸́б̹̹̹́́́ղ̹́Ͳ̺̺̺́́́ҳ̺̻̻́́́ݴ̻́Ѵ̻̼̼́́́۵̼́е̼̼́́̽́ݶ̽́Ӷ̽́Ͷ̽́̽́̾́շ̾ρη̾݁̾̾ڸ̿҂Ѹ̿ܽ̿ϸ̿ҳ̿ϮՃ׹ߩϹߦ΄ׄ׺к̺ՅۻѻԆͻ߆߼χּڇѼ̼߷նԈ҂߈ֽѽͽ߄߮ԉم۾߆վϾԊ;ҰۿӋֿۋпοټܱ̿ٺ߸ΌϸԌތύԍݍю׎͏ҏ׏ҿАܽԐ߱ܐձϠٿֿпղҟοٳ͑ҞґߞԑߑھٝؾӾϾ̜Ͼ՜ВҒ̛֒՛ߒߛڽٽؽн̚Ͻ̚Ͻٚϓνٚѓѓדټټ̙һټ̙ټ̙Ϲؼ̙Ӽһ̙͔ϼ̙ٷєϼ̙єϼ̙єϼ̙Ԕμ̙ܔߔܚٚ̚ߛқ՜ϝЕҞѕѕѕܿѕѕѕѕѕѕѕѕѕԕܕնٹ̾ڕѕѕѕѕ̿ѕѕμѕϼѕϼѕϼѕϼϦѕм蹎ފѕټ ǧѕټ ߲Еټټтyڋۼzήxxy~ӽ̿|ο}ϿֿwڿϽϽ}ՔĵϽ~սƕ̾ٽ~ϾؽӾ߽|wؾ|~Ƹ۔xє{Әє{є̽̿єϽ;̿~ܺԽϾ̿z{w۽Ͼ̿zԾ̿ÚԻؾ̿x̼ܾ̾μ̾ΛҼ̾ۼ̾|̾~Ԝ̾ܽޓ̻Ͽ̾{֓λп̽~ѓһֿ̽wѓܻڿ̽xxܝΓ̽zϾ̽|y̺̼к̼؞ٺ̼{ٿ̼w׿̼̹̼zޒй̻Ԓع̻ђ̻}ٟВ̸̻̻и̺ظ̺̺{̺}ͷ̺Ѡzݑҷ̹ёܷ̹wё̶̹̹~϶̹ض̸̸zx|̸~١||͵̸z١{~ՐԵ̸xۡѐ̷y١̷z~̴̷Ӵ̷zߴ̶̶ݏ̶̳ԏӳ̶yϏ̵xy̵Ͳ̵y}ղ̵̴ݎ̴Ԏα̴ڥΎر̴̴z̳Ѱ̳̳ލ̲{؍̲ͯύ̲֯y̲x̱Ӯ̱wߌ̱ˬٌ̱ϭϭ̰zϯڭ̰ϲߛ̰xϺʏ̯ͬԚز֮ھ}܋׬̯ϒĥimؼ3,0*=ׅ{BCHHEE7,FBҋ̯ѶԻѮػl$.(߀\U-#   )RD??=  #'W X^X3'XWW̯ͫϬy3+--&Q|{}qQC"%  ZK=; '(/   *Oqj\[\\^׭b$,̮֫Ӯ \PA!#   ,ttM96%-31   3>=?af.+--%Xӊy~̮ܧ_  "'*(% "WXurnnF6  +64#(   +AAB>412Rgpb%-)t ߊ̮ />9677Zkh-ks u)#$%%-.o}w ՋӪ̭߰g#'?>Gb~ wyyx~̭~~̭ |~̭|wߋ̬|Ջ̬|zz̬~՝̫y̫Ǧy܌̫yxό̪ٚx̪|x̪|x~̪|yףxՍ̩|~̩|˗ٍ̨̨̩ٖӠύ̨}̧y̧܎̦ώ̦ɝ̦̥܎̥yϏ̥̤̤܏̤̣̣}̣טՏ̢̢̡ߐ̡Ґ̡{̠̐|̠ٖ̟̟̟̑̑̒̒yw̞̞̝̝̝̓̓̔̔̔Ӕ̜̜̕̕|̛̛̛̖̖̗Γ̗̚y̘̘̙̚x̙̙̙̘̘̚ڒ̘̚wz̛̗w̛̗x̜̖̜̖ȑ̝̕xё̝̞̞̟̟̠̔̔̓̓̓̕{̡̡̢̠̒̒̑̑y̢̐ԏُ̣̣̐̏y|̤̿̏޿̤̥̦̎̎̍~̧̦̍̌{ؾ̧̌{y̨̋|̨̋ۍ̩̊ͽ̪̊x|̪̫̉̉ό̫̬̬̈̇̇ȋz̭̆޻̮̮̯̆̅̅պ{̰̄}̰̄{w̱̃lj̱̃~̲̂x̳̳́́ވ̴́ź̵̵̶́́ɷ̶̷̸̸́́́́˶̹̺̺́́́Ǻ̻xź̼~̼́z߄̽́{̾́zw{̾́̿́~́́}́́́́Ȱ́wӾ́́|́ρչ޼ցĽѻ͂۹ɩσܹ٧؄ͅ޶ӆߪԵ߇ΈЬ٭ڲ׉͊Я֋ٰ͌ٮѭӍ͎ճ޴ԏݪ͐٩ַ֑͒ݸܓϔۤգ׼Ӗ۽͗ژֿЙ͚ٜٛμכќԻ͝ۺڞџ͠η϶ٵԢٴϣͤޥޏ֦Ͱێѧͯۍͨͮڌ̿Эڋ̾Ьڊڪ̽Ыى׫̼ЪۈѬ̻Щۇ̺ͭѨۆ̹ͮѧۅ̸Ѧ̷݄ѥ̶ܰѤٱ̵ФԲ̳ͤѳ̲ͤѴ̱ͥ͵̰Ͷ̯ͷ̭ܤ̬ڤ̫դ̪Ҥ̩Τ̧ܼ̦ڽ̥ۤھ̣Ҥ̢ܿΤ̡̟ۣ̞ѣ̛̜ߣ̚ң̘̗ܢ̕Ѣ̔̒Ӣ̵̶̷̢̧̤̥̦̩̪̫̬̮̯̰̲̳̹̺̻̻̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̑̽̑̾̑̑̑̑̑̑̑̑̑̑̑̾̑п̸̑Ӽ̵̑ӹ̲̑Ӷ̯̑ѳ̫̑ΰ̨̑Ӭ̤͑Ω̠ؒϥΔ̜ϡߕ̘Νҗ̓Ҙ͙̏ӓٚ̉̑М̃̑́̑۟́̑͞ա́̑У́̑Ύ́̑ۦ́́̑ը́́̑Ҫ́́̑Ь́́̑ή́́̑Ͱ́́̑Ͳ́́̑ʹ́́̒Ͷ́́̔͸̖́́ͺ̙́́μ̛́́Ͼ̶̢̧̝̟̤̩̬̮̱̳̹̻́́́́́́́́́́́́́́́́́́́́́́́́́́́́̾́́́́́́́́́́́́́́́́́́́́́́΃́Ն́Պ́֎́Γ́З́Μ́Ρ̅Φ̊ҫ̐ұ̖Է̜Ծ̴̣̫̿ @ %:DdnêydY:/-77A_}_U7# !44GZZm˼dZZ>44 (1:UUpyyygUU:1  &/@QYrr ŵrrYQ@/  $,DLdk «kdLD, ")GOe{ ܿ{eOG)" 'DK`x ŝx`KD' ,@SZ պfS@, ";M` `M;)  1"I nQ%ǫ bdѬ]ƭ=9o(~ɰ7'0sвD-Q [ i W>ܶ5ҷRs9ƹD4ʺ=={ \ͼWY*˽5DѾ1ߊ ݿG̿߾ gT{ھ\qq߽߽߽n۽۽܎ڽP'qkkޏ8n߼ߐ̿߼߼b߼q߼n߼y5߼p߼ޑޑľܼߑܾۼۼۼۼۼ ۼiۼߒoۼwۼޒۼےۼޒ½ۼۼۼۼۼּW׼p߼ߓpH߼ߓpq߼pp߼ړpm߼ۓp߼ؓp߼p߼p߼pü߼p޼߼ppppppߔpߔpߔppߔsܔ`۔#3۔tڔppؽp۽p۽pܽpؽpڽp߽p߽p߽ppppqpp۾p۾pp۾pp׾rpؾAp߾p߾ߕpߕpߕpߕpߕpߕpߕp߿ߕpڿߕqؿߕlտߕo>߿ޕl?nemxpio`ڔ۔Ͽ۔۔|۔eהѽߔߔߔ޾ߔþqs]Eսվ?ޓړtݓcԓ*ߓߓ?˿]$f?ْْؒ?ߒ¹h7CݑĸڑՑ+nߑ<YِĶhܐ$ؐ$ސյ9o)޴׏؏<ܳ`;֎Ѳَߎ)@±jD:˯j?ۮ%r BƬGcϫqYުwzo}hiA?*'gi(!zF*vc#\ + f#x$}*Jee>{}#z;~L|w9tvG0%#Ova<!OpS'^(Py'7H{nJV4G!0 ZM(6ھ\Nˁ#%cJ„*^ކֵ;?Tʉݮۯ$gS͋!޲ڍ!5u؏ eΑ;Xg5ѣږ 0a0qp3uɛ0iڹ13$6aGgբuݳx9۲{oٱ~˦;N{ɫ4estuزvqF 71090Xhijkld00dG0zmpqXg8<܃=9ۆn?ڋgv?vސAݒ{)ޔB?c43ua60U FfG0Q[ C@@P0 xz  S0 `!|#$’UXK<}_+R,4SԡSvXy({BҨ}Vx}zz{sWJ xy\l0F0f  pp@0 ia00bd 00`P `@y `{ `t  uu  pp !qacSfޅ6Hh+zъ;[\.}n,PІ? RҊ찁?Tώs2 TѕeDx൅DX×T1hęV2j޶XY߫ӝuB >iӠwV ?kطŞkM SzپƠzSOnӦaI JpԾVJJPkJ; JJ`JJ,pp OSCAR-code-v1.3.1/Building/Icons/logo.ico000066400000000000000000001062411417327530600177410ustar00rootroot00000000000000 hV   F00 % HD(  CCݥR}zzz΀ ΀ ؘE΀ zч˜˜˜˜ۢOyӌ!ӌ!zbǚ ӌ!mz֕3======wum΀ zʙ======ۺz|z΀ ======z|zτqqqq===qq===qqqq===qq==={yzΈ ============{yyӍ$============zy΀ρ ======Ӫz ީZzӍ#Ӎ#zީZчzۢMۢMz~w҈%{h˜˜˜̟҉ z -߫^Ҋ*zzzz߫^u$sv(0 Ԏ%zzzzzԎ%Ԏ%ςrzzzzz}~Ԏ%AԎ%Ѕzz iƒ֕5z}Ԏ%6Ԏ%Ԏ%zzٝEٳ{tԎ% zzpzσԎ%r=߬`zzݦU======z~ {zz============zzԎ%Ԏ%zzؘ9============ٛ?z|zzzk==================߭azzԎ%zzzְ============澃zzzzzz==================ر{zzzzzzqq===qqqq===qqqqqq===qqqq===qqqqqqԭyzzzzzz============rzzzzzz============ܤPzzzԎ%zzկ==================΀ zzԎ%}zh======ܻzzzԎ%z}Ԏ%zzז6ӎ$zцݥQzz{шzӍ'ڞFzz{ۺٝEu|em}zzЄAԎ%ӌ7r|zzzzznσC <Ԏ%Ԏ%zzzzzԎ%5?@/( @ Xzȕv߬cީaq״kݥS֓/{zzzzzч"ٝF֯=֔1zzzzzzzzzzzz΀~Czzzzzז8澄漁҉zzчCdzzzzӋ#Ҩ}z{Ц͠zzzzܤP΀ zzЦ*{zzzݦT΀ zz؛>zzzӌ"============zzρ zzzzӪ============ςzzdzީZzzzӌ"============pzz{Ѕzzz============Ѧzzzڹ{zzz============zzzՑ, čzzz΀ ============zzz ׳bzzzӍ%============zzzzݨXzzzϒ:==================zzzzqݨXzzzҚJqq===qqqqqq===qqqqqqqq===qqqqqqqq===qqqqqq===zzzzdqzzzܥR============zzzzečzzzז6============ܻzzzzrܼ|zzч============zzz{čՓ/zzz============֓1zzzЅݾϣzzz===============ܻzzzz߬`\<|zzς֔2zzzzuʙzzzwwzzzz֔27Q֕3zzzzzzzzτyzzݦUݦUzzzzz˜g9Ԏ$vzzzwʙfzzzzzzJ߫\srzzzzzzzzzzzzz|~ӫ{ܻٜBfp |zzzzztp{΀ n$>ĎڟR΁)mcel΀ז6eѧ@Oϣ彄qlvčְ1+??(0` p[_ײŏxg߭acrϣhbrؚ=ӊ zzzzzzzЅ֕3ݨW4ɘӌ#zzzzzzzzzzzzzzz΀ ܢMFXЦЄzzzzzzw΀֕5yďʙǕ徂ٛBЅxzzzט9bڟGzzzzzzwٝMޫf{zz{rbцzzzzzx֔<ٝKzzzՑ,RЄzzzzzvďxzzρ&Ҋzzzzzwծ̜yzz~ݦUzzzzzwҥkzzz΀ ]zzzzzzȖי=zzzӍ$WЅzzzzzf============zzzziW͟zzzzzσ============ݬczzzzчzzzzz========================ȯzzzzӎ&zzzzzz========================ҋ zzzz]ީZzzzzzח8========================szzzz֔1Єzzzzzߴu===========================ͱzzzzz$zzzzzz=================================ZI1zzzzzÌǓzzzzz{========================zzzzzז6߬azzzzzЄ========================~zzzzσAڟFzzzzz֔1========================ςzzzzzӪՒ-zzzzzݧU===GA9===PE5цzzzzz潁Ԑ*zzzzzrܻ֕3zz֕3ʙݦU۪`ұӌ"fw‘֠NʙzʙsЉσчzzzzzfԐ*zzzzzǓσ֕3z֕3σܻfӪσσݦUݦUцzzzzzۢNՑ-zzzzzʚzʙfzڝDσzڝDڝDӌ"σσЄzzzzzۢL֖5zzzzz̞zʙӪzӌ"ӌ"ܻݦUwݦUӌ"wݦU΀ zzzzzܣOݧVzzzzzəzzݦUʙܻݦUwʙӌ"wݦUwӌ"|zzzzzިYzzzzzzÌσӌ"zʙݦUڝDfڝDڝDfӪݦUݦUwڝDzzzzzz{zzzzziӪzfڝDzȕzzzzzzѧzzzzzט9ܻ֕3zzσʙۡKzzzzz~f֓0zzzzσчzzzzzԏ(3zzzzvyzzzzzlzzzzx|ݥNzzzzzz?߬`zzzzρxzzzzzԎ' zzzzq˗ݧQzzzzzzۺtwzzzzwqzzzzzՒ.*Ҋzzzs؜FuzzzzzzAρ zzzklwzzzzznܻzzzzdުh|vzzzzzܤQXܻzzzzfҌ3xlwzzzzzٜB~Ӌ!zzznjٞPԲs|izzzzzzwxfzzzyje΁ٜErʛ˛ďyۢPщ#ngxzzzzzρӪ PܻڝDzzzzupbbddbbotzzzzzzzwh*zzzzzzzzzzzzzzzzӌ"=:tӍ$zzzzzzzؚ=t`*MAu???PNG  IHDR\rf IDATxwxTe?wfIo$-ttQQ,(Vņm]*UAAPW, (4Az H/grdf2$3w|s{ 0` 0` 0` 0` 0`C0cA`+!@(;@=`rw PS n; t@a@S;ޞb۝uNb( < 2#I`{A ҁ=)Nat rAS1`!qRUԑ:nl 2)PIw) "je 1VAJ ~p p!eVq-RUsv$$@LޱVƤshv bJǁAXeV%Dt>ߡj^1 dP]/sl1 $pApىߥ>6oAnb|}8ovjE:\v:z@ dan`tGZA!# 3k'x9Bўb^ߒڌfC߸ nډ;G)WT_80 J^G{yz)vVa WiAۙb[ɕ`N&iNGss8r ^m1CyDƥ{KAm)@7oNSUR5 7>S϶", o#e<$ưu/ʬ;shOxْ(ñ([;x`u6%6CڡVݸipDxAt<;=̢%UwDyqrWnɓw;rp~$0 J:>m< j D0J j8ғ )ȿo59ggn%Rߎ=Y )2Nzu $:B|.Z6=7 A {n鏫܉YYYˊ%k| V>YvA e?Jx+j yuG kT Yŵݧo"X<; u?ͩ_n_)cҒ#T7W/Ɲ#c n94M"@B;TM8ey̡jݎqbZ$_%z/W.#ȍ. U48!SS:QX{?mXvl=sly.p5`m`ܘO]7ğ~ِRĺ6Wc~Ks@h?G~rݘYEbɍƮ2vVr4z3wD#@n9Q7FͻΉcU i}~Ci\3]cO>p/]Elxnsg3ú+.^\7RSh= :sS%&º;z*PRmc}Jф/MʃK.m`p^2 (-%5{:3d^Cu#kA7Kc@H {]Ooɔ~Qr$D&ݝ/'侑|^d#W´Gui(~ 6m q(VEu%ܿ*[K ; jؙ[ɨ9hArd 0U˓N29m*yHC1$} 4DzrdE1%g>k/< UG$PRm#=row^:+ G3p~8Rc; l֙&pđ$c_!=g8~=ϫw?* 6G}X |}'=)'DQ< gǀrvo^a$4C;0 l4g].;NVJ,E1lQWRGvEW#l 7N՘.|[':48vAtd-G|c?ʐ3Fr8݌fI(FhB1 >Eפq+qNrn I5RNYwPjކDX37C-ϟ xe7MW}JJ xNo-dKF24 x 37 x)9YzHz*:G{nRWc5 /^эhKv<28Zj< +Uz6iD.qrʛ4x'UmNnbmF @N xV7 b$2>^j0}X']e-C`4jv;׀_0d݄?F 8^sp&LG"oδ kTVs⌕koZLg_ՑY\2jN`Th…: 0Ёɺ˟LJEp/"M +ր_=:$PfqYo{}ZM͌ah_qW")3*-5-8X\'X)OyR/XOt]-}Z"NJ5nm9Z4j H`kjb1 O0Vt1:ȇ{$cnׂnZP㱒Z2Th. 5(GIC>; iZQDQ("RokrTFT-Cafn,W `( yšRU'!o@yܯ'ꨮ+\a|B:.ąZ *̠07^U@ETA4 G?@oUtz;&arqo3UI b5`<"v lNĵ:95pίd~5`>;/,q }l:R(𺓰Ň{.Su)G~kV|O1s*e}f#6<5uцDeqh m:86,(HzOB%QcJf[E#d<0$!@jWK*Ƣ(& /E;s+K}[Q%3^ l-`k(GU48&C LU+[ TU6f8&ۗ0:]pXIA/%{Ye7AǤ>l?YDH.^݉]JtXYڌ2CZ:(Z4ZeN#qt'mv5 6 qXmHJjؐPB~7p|QYkGjd`U)~]hQ s7XͿwxRץG+(1w)IAz7R樳d3E~#Qժ?U}k8>8*|X=2NR`\f²|O1q{=!P):7@"Uuv +m?MǖOSbr']ݺuJ6afHy)qg\G֜9cbfQ!me-}hlmϩ䒷lmviuw\@Ǝx]M?ZOh(Į?5hu1#KfmFYcQj{F0gweGH{Sh^=2c5)%rJnÓB*u|4~* K]5՜8^Z)&d=uq:`tr @&@o+`iks(#:,+ol&5rv4jon7vB%i.dJg MO>ڪI*nrqtSoxV)d(x K>"0BM}pSFqͭ7<)Dx`[ @Nwqk'±0KO܍h2v_?vңv_?iZ-ywErWc I3uR/EQL'HVB8Pn[5txnjիX\e5'ڴzc8f;ao5m vң%іU6{2^Ҝ@(K55'3rL JNWCCi>Ưy-Xxf8}fBR!6 15)) O XsOVD6游(jm"yZG=qBPe]*Iqո],1VuMi[$ _L)˟1<)/->ЖuМ̚.Jij _|V# xW!j[ubW#kN0?<׾jӚg&$5xZ#@)&9S"Wh..K`~5(e=ZBlxB!ju B0kHiÓBjw-SR.廋},Uם#G w;/f\'l+{ϒ܆|$Sĉ:i[:uZavpO26=/)~Sh W!_w9' 7- 8@[1 ސmVA@vYٝXq/Uf/y-su'ng w͌>lL[zKn|uʑ'91|f2r&^O=,;^4sXRp< u9$jI!6ѢF߫1xdbYai\{תCrI8,@ON pGZn ]% StjᮼԈ{s7bk[,`3nHRQlxmGqAݑk.7f8cSR-o5hxR߳䊫ll&WkZ4. &Sԣ bEo}f9z LOk#WlK8xeσKvss7rCv IDAT7sq]zO-gBjIC>WSRw{d,LHM?9=(Gޛ?%E6Wά&sb%׍뷐"@kl䑣GhEj N3=ww@EM/pp9qHzĽ]8IJxt$liysqO iUټsf4.Jm! j8EX 8F":o[[{ݐ$PjM61W tq[N3jA2pcVvKmxۖ\L@LF C??XĴw. isUʗZp}==CdJ XLr?@RB5|5qڂg@]`e6`Y >LRţ~)ZkJk.-\}Yݫqtڕ[oUWLPP]vOKxLH@zA Bh T$޼y3 2ժ"X~=ˤ[ob\3g䡇⢋.wޡO>M7?ŋ2dݺuÊ޳JaJ#Ri5)?w%s/f3!Ch~YPP^M7ĉ'?]w5׎;x饗Xr%K,Cqwj:ks뭷zj @ZZJBnJW#b@vOYSZ~f3Vo߾] 邾x?z!Mɓ'5e=\Jٽ{7 .d͚5k„ iw}dž oREg[B}7Uק~ڠglTQ I 9`6K(zQ j؛v Gfʔ) 6Y`` \p5se۶mԨ~r-|Ͳ<lڴIq=ӫW/d6Ypﯕ8,$ ha? @ҡ'6HSؗ_7oW_}ka՞_~ᩧrMjUjkkꫯ?6̭Zضm[~ ߅_4 mNDJ0=O?ʸy j]4i(R]]^x9>|8: ?^5&L)"!"@ t7G`ii)s=deeQ^^?0{l%PXXHee*))<]?ydGJ`˖-\{]{y5XJݣ5 '.tƌ]饗:~;}:Y ezQ1h͊<{::n߾oc( j*R/~%Ar-`ƪq%->cqq>ϼy':ڳ9R($ꫯ68}GQQMHo*EHFk;] 1А!C\׮]i]څSRZZظ9 8ޠwޜ9sFq<ޕJMMo;[O 0 \+B_C"+p| }%^]1, :usݺuB9u/33Q)h{Q#{^}N[j o: bI'D$N'h$lv z}éU9\]z)ikgddBdwqIIIBBBBJP9eu=¤^ޯpG@PąZ Զ)q/ݻw矞讶b wYŢ(1} 6Q3}X,>ӧ</7Ȅ#. O޽{(9r!DE9A";wHhaKK;8m4?\&W[-GE`|p^$#C5>_~IOݮLDh&"fm(,,䡇.A +/ןutյ]ud[0L Du w~ϳ0SN%%%Ν;{}٬.{w 0uT ک%px 0@m82ԏ5TnsVvHzGqD0< 0k,233Y>ݣ[HM>.ѣ3>Lf:dzzO?Mvv6+~Ht:L/*QFC~~uJ~X)~ArDQSPXY) WgrDyEzfe߲`|<Ȓŋ9whS^s[l&>"ʢ"P]k#Ƕ"Y{? `2Am-TV:~;#./f3Alxl6q#5f>ȩ\vw|2k@kDc@!NqmL I}xƅKxwCT9Cfv6ٜ<}tڸ=|Eqʰ}/ݢwe|2:|{16WdfpB +N..YI!2 `)SGqo3{6:Hqa!H;qPv6::E/fvHNv\ 1|+NՊn'4x0ݻáCu@. | d a'5H[_E GoUU쩩OZneFz+c/w#G@TDelO]ڳ'pь>};~yIp_M%s1;nGyyd nvDDx8pdAQtXxsK/oa;Vxulg)G&fMCbZ-cgn%CbI&u C%%. 4 N/p`^شIxW(I-1bAAv̙^ya̙< >\jaDR  x-kNyMYC9,%%KΎ;}fW`m>  b`2<.V2i$fZ/Z9U(QP HZV]] w]p!?8f:=W^'$<<\17ٺu+k׮UefG@_};wlќ`%Jm DF=8VRX"""<:o;'P{@@رc iQe]ȑ#硯l5\Ð!CZ4gQ[O "e zAng~‘}'Ev Ov#GpB{U׵]Y{rO9xe5`ƍݻ6=zz'xqLs?.c\Rk tͻFJu)Pf _|1ݺuSw_HHn˂UUU1uT.r j>sTm/6t9sp}IJ3Ϩ6@"ܭ 8`ڠh XI*춟l.]3L&6ᅬ(u[{ӦMfMr5yW6]RJ-o:i@aa!ٲﻺRnLIb򨦿?QQQ)TQ%![_ƍIHHy/K_@H@LH̀%; 6LnVX^Z,ζ-[x]HNNv{jzjUMjLׯ5pXbT:AД'e6j{9E+R-wo9|0SLQ}\.QQQ|%]v@PSx|ɬ=~C8˄߿300PvZTO:UgEQئM>i+u.exUW>..;_vl6Kvڙ[ɱZUjA-ҰmtAN3@O2@V~J㧟~">>r^?п]kǎӥKUJ/w3޴VZY xκ1_ܔڽ>-f>̧(Jcٲe$''{UM\R6C-?4-H^JKȉ|`tA?WjkkG}$l6/|[$MKzʟ? qDU#u>IKլS/pjWtAtGgΜl6l $w7?鷐h. (T\ p5bvEA gSRЋ{-ZT(ᆱI]k z…{٥˗7ԓ4o޼$D>Is<+#гg& .v{"iRTTDee%{֘0v;/䚘1cƌ&ґDž-Eg5v *)-` M|BBB8\ -jr'h9cVɴk doh/ѯ_&_4IM~b;%ˠUmJѽĉ@Yq ]UoB+Z pvÇ9zEjؤG}4WXXXiѢE DC-Rsm)8ĉ` <ǂ jcC2xQFQR8EoOqwyMp\x -ȑ#G7pw5LQcO, ?jk.-@h#5meyp8q V>|xCE Wٳ5WZZZC|}YYr~ ' |KpAp2_ZSW­gN~.)YDԻE1x\dӱ3Dɍ= j'(]T_{urt9N>?Eq _=dj.2y{9u?> ks*(N-OmvاwQRB0б?)" ` <.8:7lLj9a7tj'{"Ÿ9؝g״ 8I?80aFt|^c LG~@ܮ~PVAT {k),&JmkN_?6!,;*ΐ@ [M 䖡Xrmw9?"BZ{3AoE? /5r; ̹O$Oa4_`hIl`CA~"$ W2.[ݮQ'CB T%6.[r??)cE5..!Οׁ8H`wrc,P24!&'D+pק(PP5wٗ#Š8B{(@ows>n3").3O7J9sA#ϵ-b/=Ájƽug3Eyn{V: KmםT<|_OO#)"@zfwp@(B텦}p廋s&`ij0G֜}%ٔ=ϵ{e7/>+_kDp0ی2&.97bwyH'Gv(pwm'V{H a}.%IZd=Sy{9XP.[T {Gpv/*eEߒϱZk6 ~e7r_rw6^.MsyY,&KB*e}f9+} 5ј: O,_KB4^6Uw&AyV; ;sD28!A]=ɡ9w*6d}R51-%v'>N+AY #>3mgtZq6ƱZ.㽝8Y:Z$@@&E2O$֓` 4@ "$@u7~㳃dpĆXQ88 |Uþ7@K`&p'=l"r*yog![NTpX4bkdq̀(& $x Gq41ME+h~=UgJٟ_MfQ ^SbC-t s0SD1$!5 Diqxc<tMN20uN+jdpl˩$jTڨ5H5 h1`"2L@:0K0}H GKG7ie@{%8 u6zNQ\eF~E"UuvکScSkȝ"X-&,&@af̈́YMt -*` &G: Xc@G$?8g|v:X b$p6AS35HmNVKX| ț w(m# ?J;{SK;j E I`t|Pr8y.t2Ty g>8qH4%,N0Qt{.(u z<^ހ 0` 0` 0` 0` 0`@;_,D8IENDB`OSCAR-code-v1.3.1/Building/Linux/000077500000000000000000000000001417327530600163255ustar00rootroot00000000000000OSCAR-code-v1.3.1/Building/Linux/BUILD_Linux.md000066400000000000000000000023531417327530600206700ustar00rootroot00000000000000You will need either qtcreator or qttools5-dev-tools and a C++ compiler and linker Also, you need the packages qt5-default, libqt5serialport5, libqt5serialport5-dev and libqt5opengl5-dev Oscar can be built without its Help module with Qt5.7 SDK (for Debian Stretch, for example) The Help module (The Help texts are incomplete, and language support is incomplete) requires Qt5.9 SDK, additionally, the QT modules libqt5help5, qttools5-dev, and qttools5-dev-tools are required. Linux also needs libudev-dev and zlib1g-dev The current pre-built downloads use the distribution-supplied version of Qt. Shadow building is recommneded to avoid cluttering up the git source code folder. The following does not use the QT creator package, and assumes the directoy structure shown. After installing the required packages: $ mkdir OSCAR $ cd OSCAR $ git clone https://gitlab.com/pholy/OSCAR-code.git $ mkdir build $ cd build $ qmake ../OSCAR-code/OSCAR_QT.pro. $ make After successful compilation, you can execute in place with ./oscar/OSCAR, or build an installable package with one of the mkXXXX.sh scripts. It is recommended to create a Packages folder within OSCAR and copy the scripts and associated files to it to avoid cluttering up the git source tree. OSCAR-code-v1.3.1/Building/Linux/OSCAR-test.desktop000066400000000000000000000004501417327530600215430ustar00rootroot00000000000000[Desktop Entry] Version=1.1 Name=OSCAR-test GenericName=CPAP Reporter TryExec=OSCAR-test Exec=OSCAR-test Terminal=false Icon=/usr/share/icons/hicolor/48x48/apps/OSCAR-test.png Type=Application Categories=Science;Education; StartupNotify=true X-GNOME-UsesNotifications=true Name[en_CA]=OSCAR-test OSCAR-code-v1.3.1/Building/Linux/OSCAR-test.png000066400000000000000000000064501417327530600206640ustar00rootroot00000000000000PNG  IHDR00WgAMA a cHRMz%u0`:o_FbKGD pHYs  tIMEpm IDAThy\]?ޛ7vwvj:%AH8)E"$$8R* ?I ($ 9 1]V$Ŋni=ǜ;ykx3; Wt~_w/~&K/_<VPV<`Ǧ`oX"-23!92*R (6%.eMN'+bJb Y׀gj )VKGOHȃ}ڎĊ!{&3'WL*d2Wp+?4~ydHx5qMSQJVoJ7S"S]~ w ?)ЄE;*FBl)yl dqxؿDoiQq`Aٻ687&B^`Ry_g/zA8 B!)yp[X~*`W<<|Sod:ϞZh:U̗#iN%+H y^?^1{s4ͭ~}HÙd] 39\Λ8ܻߍD:Z@Nxo%U~_6X)ZGfK^)p)kRϙH W6:OaW<ư.{{V-!RЧ+T,'ex`[ymm>V+XDn퐯8T4y*K`5j}I,?Q֋kWRR%_ghƟ}G2lyVMp.i~v7% fU~0[dTEIU82Psc%-Nk^0zNjLz62pG;t ͙U]q?%!U9}Բ w2Ļ'>'!wO`I_LT*Z=nuMs5,G*)%JZUX r*+++4h@Z*M,/"(vLuxa,£qrU/N̶$^X^;Q,br>xlA`>tVczzE*cIɑG)U잞&a+ o849vXkV8kτdmrC x]\$>9 SSk)1^fR)"ccoڬullm-'DA IJ>Y[o\kJSX\gS^Ќ:8tx<4l"H[(!XZZbxxwH$Ѓ7:ӈ2PhYΎkÓ9i&RJ|>_,L$ ut0{<΋;  mՋE^8))%d>TU@>GQ^oW]T4M`OREAXk> RgM&ի /%H,JCCCubH,Ct@UUF_5!>?f* |9pn/3QlaxTUett"8x image/svg+xml OSCAR-code-v1.3.1/Building/Linux/OSCAR.desktop000066400000000000000000000004211417327530600205640ustar00rootroot00000000000000[Desktop Entry] Version=1.1 Name=OSCAR GenericName=CPAP Reporter TryExec=OSCAR Exec=OSCAR Terminal=false Icon=/usr/share/icons/hicolor/48x48/apps/OSCAR.png Type=Application Categories=Education;Science; StartupNotify=true X-GNOME-UsesNotifications=true Name[en_CA]=OSCAR OSCAR-code-v1.3.1/Building/Linux/OSCAR.png000066400000000000000000000064501417327530600177070ustar00rootroot00000000000000PNG  IHDR00WgAMA a cHRMz%u0`:o_FbKGD pHYs  tIMEpm IDAThy\]?ޛ7vwvj:%AH8)E"$$8R* ?I ($ 9 1]V$Ŋni=ǜ;ykx3; Wt~_w/~&K/_<VPV<`Ǧ`oX"-23!92*R (6%.eMN'+bJb Y׀gj )VKGOHȃ}ڎĊ!{&3'WL*d2Wp+?4~ydHx5qMSQJVoJ7S"S]~ w ?)ЄE;*FBl)yl dqxؿDoiQq`Aٻ687&B^`Ry_g/zA8 B!)yp[X~*`W<<|Sod:ϞZh:U̗#iN%+H y^?^1{s4ͭ~}HÙd] 39\Λ8ܻߍD:Z@Nxo%U~_6X)ZGfK^)p)kRϙH W6:OaW<ư.{{V-!RЧ+T,'ex`[ymm>V+XDn퐯8T4y*K`5j}I,?Q֋kWRR%_ghƟ}G2lyVMp.i~v7% fU~0[dTEIU82Psc%-Nk^0zNjLz62pG;t ͙U]q?%!U9}Բ w2Ļ'>'!wO`I_LT*Z=nuMs5,G*)%JZUX r*+++4h@Z*M,/"(vLuxa,£qrU/N̶$^X^;Q,br>xlA`>tVczzE*cIɑG)U잞&a+ o849vXkV8kτdmrC x]\$>9 SSk)1^fR)"ccoڬullm-'DA IJ>Y[o\kJSX\gS^Ќ:8tx<4l"H[(!XZZbxxwH$Ѓ7:ӈ2PhYΎkÓ9i&RJ|>_,L$ ut0{<΋;  mՋE^8))%d>TU@>GQ^oW]T4M`OREAXk> RgM&ի /%H,JCCCubH,Ct@UUF_5!>?f* |9pn/3QlaxTUett"8x image/svg+xml OSCAR-code-v1.3.1/Building/Linux/README.first000066400000000000000000000030271417327530600203350ustar00rootroot00000000000000The files that are in Building/Linux are for creating an installable package, and assume you have already compiled and linked OSCAR using qmake and make, or QtCreator. The packages, once built, can be installed using 'dpkg i' or 'gdebi' - with either su or sudo. If you use sudo, you will get a desktop icon installed, which must be double-clicked and trusted to see the actual icon. The packaged file cannot be installed using apt-get, or aptitude, because those programs install from the distribution repository, but not a plain package file. The package can be installed with apt or apt -F if the file name is preceded by ./ to force filename recognition. The packaging scripts assume the following folder structure: /home/username/OSCAR/ /home/username/OSCAR/build/ /home/username/OSCAR/OSCAR-code/ /home/username/OSCAR/OSCAR-code/oscar ...etc The fpm program used in the mkXxxx scripts can be found at https://fpm.readthedocs.io/en/latest/intro.html Read the installation notes there, installation is sort of convoluted, then once it is installed type 'fpm -help' to see all the options. The code to put a desktop icon in the Desktop folder, regardless of language, was contributed by UnToutSeul05. Getting a menu item istalled is largely due to the efforts of CrimsonNape. The mkDebian9.sh and mkUbuntu.sh scripts have been consolidated into mkDistDeb.sh and are now depreciated and will be removed. mkDistDeb.sh has code to query which packages are available for certail libraries. Finally, the mkRedHat.sh script has not been tested. OSCAR-code-v1.3.1/Building/Linux/clean_rm-NN-test1.sh000077500000000000000000000002331417327530600220110ustar00rootroot00000000000000#! /bin/bash set -e # # delete all the folder not deleted by the purge command # at the beginning, there was # application name appli_name="OSCAR_beta" OSCAR-code-v1.3.1/Building/Linux/clean_rm-NN-test2.sh000077500000000000000000000001561417327530600220160ustar00rootroot00000000000000 # # delete all the folder not deleted by the purge command # now, application name appli_name="OSCAR-test" OSCAR-code-v1.3.1/Building/Linux/clean_rm-NN.sh000077500000000000000000000001671417327530600207610ustar00rootroot00000000000000#! /bin/bash set -e # # delete all the folder not deleted by the purge command # application name appli_name="OSCAR" OSCAR-code-v1.3.1/Building/Linux/clean_rm-common-NN.sh000077500000000000000000000004241417327530600222430ustar00rootroot00000000000000 # begin common script shell # delete all the folder not deleted by the purge command echo "appli_name=${appli_name}" list=$(find /usr -name ${appli_name} 2>/dev/null) for folder in $list do echo "to delete : '$folder'" rm -r $folder done # end common script shell OSCAR-code-v1.3.1/Building/Linux/clean_rm-test.xx000077500000000000000000000010251417327530600214440ustar00rootroot00000000000000#! /bin/bash set -e #we need to rm files that have been copied via script rep="/usr/share/icons/OSCAR_beta" if [ -d "$rep" ]; then rm -r $rep fi rep="/usr/local/share/icons/OSCAR-test" if [ -d "$rep" ]; then rm -r $rep fi rep="/usr/share/doc/OSCAR_beta" if [ -d "$rep" ]; then rm -r $rep fi rep="/usr/local/share/doc/OSCAR-test" if [ -d "$rep" ]; then rm -r $rep fi rep="/usr/share/OSCAR_beta" if [ -d "$rep" ]; then rm -r $rep fi rep="/usr/local/share/OSCAR-test" if [ -d "$rep" ]; then rm -r $rep fi OSCAR-code-v1.3.1/Building/Linux/clean_rm.xx000077500000000000000000000004301417327530600204660ustar00rootroot00000000000000#! /bin/bash set -e #we need to rm files that have been copied via script rep="/usr/share/icons/OSCAR" if [ -d "$rep" ]; then rm -r $rep fi rep="/usr/share/doc/OSCAR" if [ -d "$rep" ]; then rm -r $rep fi rep="/usr/share/OSCAR" if [ -d "$rep" ]; then rm -r $rep fi OSCAR-code-v1.3.1/Building/Linux/copyright000066400000000000000000000020071417327530600202570ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: OSCAR Files: * Copyright: 2011-2018 Mark Watkins 2019-2020 The OSCAR Team License: GPL-3+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA . On Debian systems, the full text of the GNU General Public License version 3 can be found in the file `/usr/share/common-licenses/GPL-3'. OSCAR-code-v1.3.1/Building/Linux/ln_usrbin-NN-test.sh000077500000000000000000000002451417327530600221460ustar00rootroot00000000000000#! /bin/bash set -e # # modify by untoutseul05 to search local name for Desktop Folder # the package now suits the fhs # application name appli_name="OSCAR-test" OSCAR-code-v1.3.1/Building/Linux/ln_usrbin-NN.sh000077500000000000000000000002401417327530600211640ustar00rootroot00000000000000#! /bin/bash set -e # # modify by untoutseul05 to search local name for Desktop Folder # the package now suits the fhs # application name appli_name="OSCAR" OSCAR-code-v1.3.1/Building/Linux/ln_usrbin-common-NN.sh000077500000000000000000000024771417327530600224700ustar00rootroot00000000000000# begin common script # # modify by untoutseul05 to search local name for Desktop Folder # the package now suits the fhs # application name echo "appli_name='$appli_name', SUDO_USER='$SUDO_USER'" if [ ! -z "$SUDO_USER" ]; then # find real name of the Desktop folder (Bureau for xubuntu french version) desktop_folder_name="/home/$SUDO_USER/Desktop" # if doesn't exist, try to find it translated name tmp_dir="" if [ ! -d "$desktop_folder_name" ]; then tmp_dir=`cat /home/$SUDO_USER/.config/user-dirs.dirs | grep XDG_DESKTOP_DIR | awk -F= '{print $2}' | awk -F\" '{print $2}' | awk -F\/ '{print $2}'` fi # don't overwrite if translated name or doesn't exist if [ -n "$tmp_dir" ]; then # calculate the full folder tmp_dir_full="/home/${SUDO_USER}/${tmp_dir}" if [ -d "$tmp_dir_full" ]; then desktop_folder_name=$tmp_dir_full fi fi # info : /usr/share/applications/${appli_name}.desktop # copy icon file to the Desktop folder (even if it has been translated) file_from="/usr/share/applications/${appli_name}.desktop" file_to="$desktop_folder_name/${appli_name}.desktop" cp $file_from $file_to if [ -f "$file_to" ]; then chown $SUDO_USER:$SUDO_USER $file_to chmod a+x $file_to fi fi # end common script OSCAR-code-v1.3.1/Building/Linux/ln_usrbin-test.xx000077500000000000000000000046101417327530600216620ustar00rootroot00000000000000#! /bin/bash set -e # # modify by untoutseul05 to search local name for Desktop Folder # the package now suits the fhs # application name appli_name="OSCAR-test" # binary copy flag copy_flag=0 #### Select the binary file for the goog version of QT (5.7 ou 5.9) ####echo "test the version of qt5 core version" ### ###if [ -f "/etc/redhat-release" ]; then ### # pour mageia (red hat) ### echo "distribution : mageia (red hat)" ### Qt5_core=$(yum search -q qt5core5 | awk '{print $1}' | grep lib | sort -u) ### MajorVer_no=$(yum info $Qt5_core | grep -i "version" | awk '{print $3}' | awk -F. '{print $1}') ### MinorVer_no=$(yum info $Qt5_core | grep -i "version" | awk '{print $3}' | awk -F. '{print $2}') ### ###elif [ -f "/etc/lsb-release" ] || [ -f "/etc/debian_version" ]; then ### # pour debian #### echo "distribution : debian" ### Qt5_core=$(dpkg -l | awk '{print $2}'| grep qt5 | grep core | awk -F: '{print $1}') ### Major_ver=$(dpkg -l | grep $Qt5_core | awk '{print $3}' | awk -F. '{print $1}') ### Minor_ver=$(dpkg -l | grep $Qt5_core | awk '{print $3}' | awk -F. '{print $2}') ###else ### echo "unknown distribution " ### exit ###fi ### ####echo "Qt5_core = '$Qt5_core'" ####echo "Major_ver = $Major_ver" ####echo "Minor_ver = $Minor_ver" ##if [ -x /usr/bin/update-menus ]; then update-menus; fi if [ ! -z "$SUDO_USER" ]; then # find real name of the Desktop folder (Bureau for xubuntu french version) desktop_folder_name="/home/$SUDO_USER/Desktop" # si doesn't exist, try to find it translated name tmp_dir="" if [ ! -d "$desktop_folder_name" ]; then tmp_dir=`cat /home/$SUDO_USER/.config/user-dirs.dirs | grep XDG_DESKTOP_DIR | awk -F= '{print $2}' | awk -F\" '{print $2}' | awk -F\/ '{print $2}'` fi # don't overwrite if translated name or doesn't exist if [ -n "$tmp_dir" ]; then # calculate the full folder tmp_dir_full="/home/${SUDO_USER}/${tmp_dir}" if [ -d "$tmp_dir_full" ]; then desktop_folder_name=$tmp_dir_full fi fi # info : /usr/share/applications/${appli_name}.desktop # copy icon file to the Desktop folder (even if it has been translated) file_from="/usr/share/applications/${appli_name}.desktop" file_to="$desktop_folder_name/${appli_name}.desktop" cp $file_from $file_to chown $SUDO_USER:$SUDO_USER $file_to chmod a+x $file_to fi OSCAR-code-v1.3.1/Building/Linux/ln_usrbin.xx000077500000000000000000000046601417327530600207120ustar00rootroot00000000000000#! /bin/bash set -e # # modify by untoutseul05 to search local name for Desktop Folder # the package now suits the fhs # application name appli_name="OSCAR" # binary copy flag copy_flag=0 #### Select the binary file for the goog version of QT (5.7 ou 5.9) ###echo "test the version of qt5 core version" ### ###if [ -f "/etc/redhat-release" ]; then ### # pour mageia (red hat) ### echo "distribution : mageia (red hat)" ### nom_core=$(yum search -q qt5core5 | awk '{print $1}' | grep lib | sort -u) ### no_vermaj=$(yum info $nom_core | grep -i "version" | awk '{print $3}' | awk -F. '{print $1}') ### no_vermin=$(yum info $nom_core | grep -i "version" | awk '{print $3}' | awk -F. '{print $2}') ### ###elif [ -f "/etc/lsb-release" ] || [ -f "/etc/debian_version" ]; then ### # pour debian ### echo "distribution : debian" ### nom_core=$(dpkg -l | awk '{print $2}'| grep qt5 | grep core | awk -F: '{print $1}') ### no_vermaj=$(dpkg -l | grep $nom_core | awk '{print $3}' | awk -F. '{print $1}') ### no_vermin=$(dpkg -l | grep $nom_core | awk '{print $3}' | awk -F. '{print $2}') ###else ### echo "unknown distribution " ### exit ###fi ### ###echo "QT5_core = '$nom_core'" ###echo "Major ver = $no_vermaj" ###echo "Minor ver = $no_vermin" ##if [ -x /usr/bin/update-menus ]; then update-menus; fi if [ X_$SUDO_USER != "X_" ]; then # find real name of the Desktop folder (Bureau for xubuntu french version) desktop_folder_name="/home/$SUDO_USER/Desktop" # if doesn't exist, try to find it translated name tmp_dir="" if [ ! -d "$desktop_folder_name" ]; then tmp_dir=`cat /home/$SUDO_USER/.config/user-dirs.dirs | grep XDG_DESKTOP_DIR | awk -F= '{print $2}' | awk -F\" '{print $2}' | awk -F\/ '{print $2}'` fi # don't overwrite if translated name or doesn't exist if [ -n "$tmp_dir" ]; then # calculate the full folder tmp_dir_full="/home/${SUDO_USER}/${tmp_dir}" if [ -d "$tmp_dir_full" ]; then desktop_folder_name=$tmp_dir_full fi fi # info : /usr/share/applications/${appli_name}.desktop # copy icon file to the Desktop folder (even if it has been translated) file_from="/usr/share/applications/${appli_name}.desktop" file_to="$desktop_folder_name/${appli_name}.desktop" cp $file_from $file_to if [ -f "$file_to" ]; then chown $SUDO_USER:$SUDO_USER $file_to chmod a+x $file_to fi fi OSCAR-code-v1.3.1/Building/Linux/mkDistDeb.xx000077500000000000000000000145231417327530600205640ustar00rootroot00000000000000#! /bin/bash # First parameter names distribution and release number # eg Debian10 Ubuntu18 RasPiOS # function getPkg () { unset PKGNAME unset PKGVERS while read stat pkg ver other ; do if [[ ${stat} == "ii" ]] ; then PKGNAME=`awk -F: '{print $1}' <<< ${pkg}` PKGVERS=`awk -F. '{print $1 "." $2}' <<< ${ver}` break fi ; done <<< $(dpkg -l | grep $1) } ITERATION=$1 if [ -z ${ITERATION} ]; then ITERATION="1" fi SRC=/home/$USER/OSCAR/OSCAR-code/oscar VERSION=`awk '/#define VERSION / { gsub(/"/, "", $3); print $3 }' ${SRC}/VERSION` if [[ ${VERSION} == *-* ]]; then # Use ~ for prerelease information so that it sorts correctly compared to release # versions. See https://www.debian.org/doc/debian-policy/ch-controlfields.html#version IFS="-" read -r VERSION PRERELEASE <<< ${VERSION} if [[ ${PRERELEASE} == *rc* ]]; then RC=1 fi VERSION="${VERSION}~${PRERELEASE}" fi GIT_REVISION=`awk '/#define GIT_REVISION / { gsub(/"/, "", $3); print $3 }' ${SRC}/git_info.h` echo Version: ${VERSION} # application name appli_name="OSCAR" package_name="oscar" pre_inst="tst_user.sh" # build folder (absolute path is better) build_folder="/home/$USER/OSCAR/build" if [[ -n ${PRERELEASE} && -z ${RC} ]] ; then appli_name=${appli_name}-test package_name=${package_name}-test post_inst="ln_usrbin-test.sh" pre_rem="rm_usrbin-test.sh" post_rem="clean_rm-test.sh" else post_inst="ln_usrbin.sh" pre_rem="rm_usrbin.sh" post_rem="clean_rm.sh" fi # temporary folder (absolute path is better) temp_folder="/home/$USER/tmp_deb_${appli_name}/" # destination folder in the .deb file dest_folder="/usr/" # the .deb file mustn't exist archi_tmp=$(lscpu | grep -i architecture | awk -F: {'print $2'} | tr -d " ") if [ "$archi_tmp" = "x86_64" ];then archi="amd64" else archi="unknown" fi deb_file="${appli_name}_${VERSION}-${ITERATION}_$archi.deb" # if deb file exists, fatal error if [ -f "./$deb_file" ]; then echo "destination file (./$deb_file) exists. fatal error" exit fi # retrieve packages version for the dependencies getPkg libqt5core qtver=$PKGVERS getPkg libdouble dblPkg=$PKGNAME echo "QT version " $qtver echo "DblConv package " $dblPkg # clean folders need to create the package if [ -d "${temp_folder}" ]; then rm -r ${temp_folder} fi mkdir ${temp_folder} if [ ! -d "${temp_folder}" ]; then echo "Folder (${temp_folder}) not created : fatal error." exit fi chmod 0755 ${temp_folder} # save current value of umask (for u=g and not g=o) current_value=$(umask) umask 022 mkdir ${temp_folder}/bin mkdir ${temp_folder}/share mkdir ${temp_folder}/share/${appli_name} mkdir ${temp_folder}/share/doc share_doc_folder="${temp_folder}/share/doc/${package_name}" mkdir ${share_doc_folder} mkdir ${temp_folder}/share/icons mkdir ${temp_folder}/share/icons/hicolor mkdir ${temp_folder}/share/icons/hicolor/48x48 mkdir ${temp_folder}/share/icons/hicolor/48x48/apps mkdir ${temp_folder}/share/icons/hicolor/scalable mkdir ${temp_folder}/share/icons/hicolor/scalable/apps mkdir ${temp_folder}/share/applications # must delete debug symbol in OSCAR binary file # --- V1 strip -s -o ${temp_folder}/bin/${appli_name} ${build_folder}/oscar/OSCAR #old code : cp ${build_folder}/oscar/OSCAR ${temp_folder}/bin # 2>/dev/null : errors does not appear : we don't care about them cp -r ${build_folder}/oscar/Help ${temp_folder}/share/${appli_name} 2>/dev/null cp -r ${build_folder}/oscar/Html ${temp_folder}/share/${appli_name} 2>/dev/null cp -r ${build_folder}/oscar/Translations ${temp_folder}/share/${appli_name} 2>/dev/null cp ./${appli_name}.png ${temp_folder}/share/icons/hicolor/48x48/apps/${appli_name}.png cp ./${appli_name}.svg ${temp_folder}/share/icons/hicolor/scalable/apps/${appli_name}.svg cp ./${appli_name}.desktop ${temp_folder}/share/applications/${appli_name}.desktop #echo "Copyright 2019-2020 oscar-team.org " > $share_doc_folder/copyright #echo "Licensed under /usr/share/common-licenses/GPL-3" >> $share_doc_folder/copyright cp ./copyright $share_doc_folder/copyright changelog_file="./changelog" #automatic changelog as a bad name # need to generate one and say fpm to use it instead of create one # it seems that it needs both of them... # creation of the Debian changelog echo "$appli_name (${VERSION}-${ITERATION}) whatever; urgency=medium" > $changelog_file echo "" >> $changelog_file echo " * Package created with FPM." >> $changelog_file echo "" >> $changelog_file echo " * See the Release Notes under Help/About menu" >> $changelog_file echo "" >> $changelog_file echo -n " -- oscar-team.org " >> $changelog_file date -Iminutes >> $changelog_file cp $changelog_file $share_doc_folder/changelog gzip --best $share_doc_folder/changelog description='Open Source CPAP Analysis Reporter\nProvides graphical and statistical display of the CPAP stored data' # trick for dummies : need to use echo -e to take care of \n (cariage return to slip description and extra one description=$(echo -e $description) # restore umask value umask $current_value # create the .deb file (litian test show juste a warning with a man that doesn't exists : don't care about that) fpm --input-type dir --output-type deb \ --prefix ${dest_folder} \ --before-install ${pre_inst} \ --after-install ${post_inst} \ --before-remove ${pre_rem} \ --after-remove ${post_rem} \ --name ${appli_name} --version ${VERSION} --iteration ${ITERATION} \ --category misc \ --deb-priority optional \ --maintainer " -- oscar-team.org " \ --license GPL3+ \ --vendor oscar-team.org \ --description "$description" \ --url https://sleepfiles.com/OSCAR \ --deb-no-default-config-files \ --depends $dblPkg \ --depends libpcre16-3 \ --depends qttranslations5-l10n \ --depends "libqt5core5a >= 5.9" \ --depends libqt5serialport5 \ --depends libqt5xml5 \ --depends libqt5network5 \ --depends libqt5gui5 \ --depends libqt5widgets5 \ --depends libqt5opengl5 \ --depends libqt5printsupport5 \ --depends libglu1-mesa \ --depends libgl1 \ --depends libc6 \ --no-deb-generate-changes \ -C ${temp_folder} \ . OSCAR-code-v1.3.1/Building/Linux/mkOSDistDeb-NN.sh000077500000000000000000000203671417327530600213150ustar00rootroot00000000000000#! /bin/bash # No parameter is not required # This script will identify the distribution and release version # function gene_script () { # generate script shell from 2 files # clean_rm if [ -f "clean_rm-result-NN.sh" ]; then rm clean_rm-result-NN.sh fi cat clean_rm-NN.sh clean_rm-common-NN.sh > clean_rm-result-NN.sh chmod +x clean_rm-result-NN.sh if [ -f "clean_rm-result-NN-test.sh" ]; then rm clean_rm-result-NN-test.sh fi cat clean_rm-NN-test1.sh clean_rm-common-NN.sh clean_rm-NN-test2.sh clean_rm-common-NN.sh > clean_rm-result-NN-test.sh chmod +x clean_rm-result-NN-test.sh # ln_usrbin if [ -f "ln_usrbin-result-NN.sh" ]; then rm ln_usrbin-result-NN.sh fi cat ln_usrbin-NN.sh ln_usrbin-common-NN.sh > ln_usrbin-result-NN.sh chmod +x ln_usrbin-result-NN.sh if [ -f "ln_usrbin-result-NN-test.sh" ]; then rm ln_usrbin-result-NN-test.sh fi cat ln_usrbin-NN-test.sh ln_usrbin-common-NN.sh > ln_usrbin-result-NN-test.sh chmod +x ln_usrbin-result-NN-test.sh # rm_usrbin if [ -f "rm_usrbin-result-NN.sh" ]; then rm rm_usrbin-result-NN.sh fi cat rm_usrbin-NN.sh rm_usrbin-common-NN.sh > rm_usrbin-result-NN.sh chmod +x rm_usrbin-result-NN.sh if [ -f "rm_usrbin-result-NN-test.sh" ]; then rm rm_usrbin-result-NN-test.sh fi cat rm_usrbin-NN-test.sh rm_usrbin-common-NN.sh > rm_usrbin-result-NN-test.sh chmod +x rm_usrbin-result-NN-test.sh } function getOS () { rel=$(lsb_release -r | awk '{print $2}') os=$(lsb_release -i | awk '{print $3}') tmp2=${os:0:3} echo "tmp2 = '$tmp2'" if [ "$tmp2" = "Ubu" ] ; then OSNAME=$os${rel:0:2} elif [ "$tmp2" = "Deb" ];then OSNAME=$os$rel elif [ "$tmp2" = "Ras" ];then OSNAME="RasPiOS" else OSNAME="unknown" fi } function getPkg () { unset PKGNAME unset PKGVERS while read stat pkg ver other ; do if [[ ${stat} == "ii" ]] ; then PKGNAME=`awk -F: '{print $1}' <<< ${pkg}` PKGVERS=`awk -F. '{print $1 "." $2}' <<< ${ver}` break fi ; done <<< $(dpkg -l | grep $1) } # generate the script from sources gene_script ITERATION=$1 if [ -z ${ITERATION} ]; then ITERATION="1" fi SRC=/home/$USER/OSCAR/OSCAR-code/oscar VERSION=`awk '/#define VERSION / { gsub(/"/, "", $3); print $3 }' ${SRC}/VERSION` if [[ ${VERSION} == *-* ]]; then # Use ~ for prerelease information so that it sorts correctly compared to release # versions. See https://www.debian.org/doc/debian-policy/ch-controlfields.html#version IFS="-" read -r VERSION PRERELEASE <<< ${VERSION} if [[ ${PRERELEASE} == *rc* ]]; then RC=1 fi VERSION="${VERSION}~${PRERELEASE}" fi GIT_REVISION=`awk '/#define GIT_REVISION / { gsub(/"/, "", $3); print $3 }' ${SRC}/git_info.h` echo Version: ${VERSION} # application name appli_name="OSCAR" package_name="oscar" pre_inst="tst_user.sh" # build folder (absolute path is better) build_folder="/home/$USER/OSCAR/build" if [[ -n ${PRERELEASE} && -z ${RC} ]] ; then appli_name=${appli_name}-test package_name=${package_name}-test post_inst="ln_usrbin-result-NN-test.sh" pre_rem="rm_usrbin-result-NN-test.sh" post_rem="clean_rm-result-NN-test.sh" else post_inst="ln_usrbin-result-NN.sh" pre_rem="rm_usrbin-result-NN.sh" post_rem="clean_rm-result-NN.sh" fi # temporary folder (absolute path is better) temp_folder="/home/$USER/tmp_deb_${appli_name}/" # destination folder in the .deb file dest_folder="/usr/" # the .deb file mustn't exist archi_tmp=$(lscpu | grep -i architecture | awk -F: {'print $2'} | tr -d " ") if [ "$archi_tmp" = "x86_64" ];then archi="amd64" else archi="unknown" fi # detection of the OS with version for Ubuntu getOS echo "osname='$OSNAME'" deb_file="${appli_name}_${VERSION}-${OSNAME}_$archi.deb" # if deb file exists, fatal error if [ -f "./$deb_file" ]; then echo "destination file (./$deb_file) exists. fatal error" exit fi # retrieve packages version for the dependencies getPkg libqt5core qtver=$PKGVERS getPkg libdouble dblPkg=$PKGNAME echo "QT version " $qtver echo "DblConv package " $dblPkg # clean folders need to create the package if [ -d "${temp_folder}" ]; then rm -r ${temp_folder} fi mkdir ${temp_folder} if [ ! -d "${temp_folder}" ]; then echo "Folder (${temp_folder}) not created : fatal error." exit fi chmod 0755 ${temp_folder} # save current value of umask (for u=g and not g=o) current_value=$(umask) umask 022 mkdir ${temp_folder}/bin mkdir ${temp_folder}/share mkdir ${temp_folder}/share/${appli_name} mkdir ${temp_folder}/share/doc share_doc_folder="${temp_folder}/share/doc/${package_name}" mkdir ${share_doc_folder} mkdir ${temp_folder}/share/icons mkdir ${temp_folder}/share/icons/hicolor mkdir ${temp_folder}/share/icons/hicolor/48x48 mkdir ${temp_folder}/share/icons/hicolor/48x48/apps mkdir ${temp_folder}/share/icons/hicolor/scalable mkdir ${temp_folder}/share/icons/hicolor/scalable/apps mkdir ${temp_folder}/share/applications # must delete debug symbol in OSCAR binary file strip -s -o ${temp_folder}/bin/${appli_name} ${build_folder}/oscar/OSCAR # 2>/dev/null : errors does not appear : we don't care about them cp -r ${build_folder}/oscar/Help ${temp_folder}/share/${appli_name} 2>/dev/null cp -r ${build_folder}/oscar/Html ${temp_folder}/share/${appli_name} 2>/dev/null cp -r ${build_folder}/oscar/Translations ${temp_folder}/share/${appli_name} 2>/dev/null cp ./${appli_name}.png ${temp_folder}/share/icons/hicolor/48x48/apps/${appli_name}.png cp ./${appli_name}.svg ${temp_folder}/share/icons/hicolor/scalable/apps/${appli_name}.svg cp ./${appli_name}.desktop ${temp_folder}/share/applications/${appli_name}.desktop #echo "Copyright 2019-2020 oscar-team.org " > $share_doc_folder/copyright #echo "Licensed under /usr/share/common-licenses/GPL-3" >> $share_doc_folder/copyright cp ./copyright $share_doc_folder/copyright changelog_file="./changelog" #automatic changelog as a bad name # need to generate one and say fpm to use it instead of create one # it seems that it needs both of them... # creation of the Debian changelog echo "$appli_name (${VERSION}-${ITERATION}) whatever; urgency=medium" > $changelog_file echo "" >> $changelog_file echo " * Package created with FPM." >> $changelog_file echo "" >> $changelog_file echo " * See the Release Notes under Help/About menu" >> $changelog_file echo "" >> $changelog_file echo -n " -- oscar-team.org " >> $changelog_file date -Iminutes >> $changelog_file cp $changelog_file $share_doc_folder/changelog gzip --best $share_doc_folder/changelog description='Open Source CPAP Analysis Reporter\nProvides graphical and statistical display of the CPAP stored data' # trick for dummies : need to use echo -e to take care of \n (cariage return to slipt description and extra one) description=$(echo -e $description) echo "appli (replaces) : '${package_name}'" # restore umask value umask $current_value # create the .deb file (litian test show juste a warning with a man that doesn't exists : don't care about that) fpm --input-type dir --output-type deb \ --prefix ${dest_folder} \ --before-install ${pre_inst} \ --after-install ${post_inst} \ --before-remove ${pre_rem} \ --after-remove ${post_rem} \ --name ${package_name} --version ${VERSION} --iteration ${OSNAME} \ --category misc \ --deb-priority optional \ --maintainer " -- oscar-team.org " \ --license GPL3+ \ --vendor oscar-team.org \ --description "$description" \ --url https://sleepfiles.com/OSCAR \ --replaces "${package_name} ( << ${VERSION})" \ --deb-no-default-config-files \ --depends $dblPkg \ --depends libpcre16-3 \ --depends qttranslations5-l10n \ --depends "libqt5core5a >= 5.9" \ --depends libqt5serialport5 \ --depends libqt5xml5 \ --depends libqt5network5 \ --depends libqt5gui5 \ --depends libqt5widgets5 \ --depends libqt5opengl5 \ --depends libqt5printsupport5 \ --depends libglu1-mesa \ --depends libgl1 \ --depends libc6 \ --no-deb-generate-changes \ -C ${temp_folder} \ . # Suppress the *result* files : if not, it can make trouble with git rm *result* OSCAR-code-v1.3.1/Building/Linux/mkOSDistDeb.xx000077500000000000000000000154671417327530600210360ustar00rootroot00000000000000#! /bin/bash # No parameter is not required # This script will identify the distribution and release version # function getOS () { rel=$(lsb_release -r | awk '{print $2}') os=$(lsb_release -i | awk '{print $3}') tmp2=${os:0:3} echo "tmp2 = '$tmp2'" if [ "$tmp2" = "Ubu" ] ; then OSNAME=$os${rel:0:2} elif [ "$tmp2" = "Deb" ];then OSNAME=$os$rel elif [ "$tmp2" = "Ras" ];then OSNAME="RasPiOS" else OSNAME="unknown" fi } function getPkg () { unset PKGNAME unset PKGVERS while read stat pkg ver other ; do if [[ ${stat} == "ii" ]] ; then PKGNAME=`awk -F: '{print $1}' <<< ${pkg}` PKGVERS=`awk -F. '{print $1 "." $2}' <<< ${ver}` break fi ; done <<< $(dpkg -l | grep $1) } ITERATION=$1 if [ -z ${ITERATION} ]; then ITERATION="1" fi SRC=/home/$USER/OSCAR/OSCAR-code/oscar VERSION=`awk '/#define VERSION / { gsub(/"/, "", $3); print $3 }' ${SRC}/VERSION` if [[ ${VERSION} == *-* ]]; then # Use ~ for prerelease information so that it sorts correctly compared to release # versions. See https://www.debian.org/doc/debian-policy/ch-controlfields.html#version IFS="-" read -r VERSION PRERELEASE <<< ${VERSION} if [[ ${PRERELEASE} == *rc* ]]; then RC=1 fi VERSION="${VERSION}~${PRERELEASE}" fi GIT_REVISION=`awk '/#define GIT_REVISION / { gsub(/"/, "", $3); print $3 }' ${SRC}/git_info.h` echo Version: ${VERSION} # application name appli_name="OSCAR" package_name="oscar" pre_inst="tst_user.sh" # build folder (absolute path is better) build_folder="/home/$USER/OSCAR/build" if [[ -n ${PRERELEASE} && -z ${RC} ]] ; then appli_name=${appli_name}-test package_name=${package_name}-test post_inst="ln_usrbin-test.sh" pre_rem="rm_usrbin-test.sh" post_rem="clean_rm-test.sh" else post_inst="ln_usrbin.sh" pre_rem="rm_usrbin.sh" post_rem="clean_rm.sh" fi # temporary folder (absolute path is better) temp_folder="/home/$USER/tmp_deb_${appli_name}/" # destination folder in the .deb file dest_folder="/usr/" # the .deb file mustn't exist archi_tmp=$(lscpu | grep -i architecture | awk -F: {'print $2'} | tr -d " ") if [ "$archi_tmp" = "x86_64" ];then archi="amd64" else archi="unknown" fi # detection of the OS with version for Ubuntu getOS echo "osname='$OSNAME'" #deb_file="${appli_name}_${VERSION}-${ITERATION}_$archi.deb" deb_file="${appli_name}_${VERSION}-${OSNAME}_$archi.deb" # if deb file exists, fatal error if [ -f "./$deb_file" ]; then echo "destination file (./$deb_file) exists. fatal error" exit fi # retrieve packages version for the dependencies getPkg libqt5core qtver=$PKGVERS getPkg libdouble dblPkg=$PKGNAME echo "QT version " $qtver echo "DblConv package " $dblPkg # clean folders need to create the package if [ -d "${temp_folder}" ]; then rm -r ${temp_folder} fi mkdir ${temp_folder} if [ ! -d "${temp_folder}" ]; then echo "Folder (${temp_folder}) not created : fatal error." exit fi chmod 0755 ${temp_folder} # save current value of umask (for u=g and not g=o) current_value=$(umask) umask 022 mkdir ${temp_folder}/bin mkdir ${temp_folder}/share mkdir ${temp_folder}/share/${appli_name} mkdir ${temp_folder}/share/doc share_doc_folder="${temp_folder}/share/doc/${package_name}" mkdir ${share_doc_folder} mkdir ${temp_folder}/share/icons mkdir ${temp_folder}/share/icons/hicolor mkdir ${temp_folder}/share/icons/hicolor/48x48 mkdir ${temp_folder}/share/icons/hicolor/48x48/apps mkdir ${temp_folder}/share/icons/hicolor/scalable mkdir ${temp_folder}/share/icons/hicolor/scalable/apps mkdir ${temp_folder}/share/applications # must delete debug symbol in OSCAR binary file # --- V1 strip -s -o ${temp_folder}/bin/${appli_name} ${build_folder}/oscar/OSCAR #old code : cp ${build_folder}/oscar/OSCAR ${temp_folder}/bin # 2>/dev/null : errors does not appear : we don't care about them cp -r ${build_folder}/oscar/Help ${temp_folder}/share/${appli_name} 2>/dev/null cp -r ${build_folder}/oscar/Html ${temp_folder}/share/${appli_name} 2>/dev/null cp -r ${build_folder}/oscar/Translations ${temp_folder}/share/${appli_name} 2>/dev/null cp ./${appli_name}.png ${temp_folder}/share/icons/hicolor/48x48/apps/${appli_name}.png cp ./${appli_name}.svg ${temp_folder}/share/icons/hicolor/scalable/apps/${appli_name}.svg cp ./${appli_name}.desktop ${temp_folder}/share/applications/${appli_name}.desktop #echo "Copyright 2019-2020 oscar-team.org " > $share_doc_folder/copyright #echo "Licensed under /usr/share/common-licenses/GPL-3" >> $share_doc_folder/copyright cp ./copyright $share_doc_folder/copyright changelog_file="./changelog" #automatic changelog as a bad name # need to generate one and say fpm to use it instead of create one # it seems that it needs both of them... # creation of the Debian changelog echo "$appli_name (${VERSION}-${ITERATION}) whatever; urgency=medium" > $changelog_file echo "" >> $changelog_file echo " * Package created with FPM." >> $changelog_file echo "" >> $changelog_file echo " * See the Release Notes under Help/About menu" >> $changelog_file echo "" >> $changelog_file echo -n " -- oscar-team.org " >> $changelog_file date -Iminutes >> $changelog_file cp $changelog_file $share_doc_folder/changelog gzip --best $share_doc_folder/changelog description='Open Source CPAP Analysis Reporter\nProvides graphical and statistical display of the CPAP stored data' # trick for dummies : need to use echo -e to take care of \n (cariage return to slip description and extra one description=$(echo -e $description) # restore umask value umask $current_value # create the .deb file (litian test show juste a warning with a man that doesn't exists : don't care about that) fpm --input-type dir --output-type deb \ --prefix ${dest_folder} \ --before-install ${pre_inst} \ --after-install ${post_inst} \ --before-remove ${pre_rem} \ --after-remove ${post_rem} \ --name ${appli_name} --version ${VERSION} --iteration ${OSNAME} \ --category misc \ --deb-priority optional \ --maintainer " -- oscar-team.org " \ --license GPL3+ \ --vendor oscar-team.org \ --description "$description" \ --url https://sleepfiles.com/OSCAR \ --deb-no-default-config-files \ --depends $dblPkg \ --depends libpcre16-3 \ --depends qttranslations5-l10n \ --depends "libqt5core5a >= 5.9" \ --depends libqt5serialport5 \ --depends libqt5xml5 \ --depends libqt5network5 \ --depends libqt5gui5 \ --depends libqt5widgets5 \ --depends libqt5opengl5 \ --depends libqt5printsupport5 \ --depends libglu1-mesa \ --depends libgl1 \ --depends libc6 \ --no-deb-generate-changes \ -C ${temp_folder} \ . OSCAR-code-v1.3.1/Building/Linux/mkRedHat.sh000077500000000000000000000077101417327530600203700ustar00rootroot00000000000000#! /bin/bash # First parameter is version number - (ex: 1.0.0) # Second parametr is build or release status - (ex: beta or release) # # application name appli_name="OSCAR" # build folder (absolute path is better) build_folder="/home/$USER/OSCAR/build" # temporary folder (absolute path is better) temp_folder="/home/$USER/tmp_RH_${appli_name}/" chmod 775 $temp_folder # destination folder in the .rpm file dest_folder="/usr/" if [ -z "$1" ]; then echo "1st parameter must be the version number (x.y.z)" exit fi if [ "$2" != "beta" ] && [ "$2" != "release" ]; then echo "2nd parameter must 'beta' or 'release'." exit fi # the .rpm file mustn't exist (or fpm must have -f parameter to force the creation) archi=$(lscpu | grep -i architecture | awk -F: {'print $2'} | tr -d " ") rpm_file="${appli_name}-$1-$2.$archi.rpm" # if rpm file exists, fatal error if [ -f "./$rpm_file" ]; then echo "destination file (./$rpm_file) exists. fatal error" exit fi # clean folders need to create the package if [ -d "${temp_folder}" ]; then rm -r ${temp_folder} fi mkdir ${temp_folder} if [ ! -d "${temp_folder}" ]; then echo "Folder (${temp_folder}) not created : fatal error." exit fi chmod 0755 ${temp_folder} # save current value of umask (for u=g and not g=o) current_value=$(umask) umask 022 mkdir ${temp_folder}/bin mkdir ${temp_folder}/share mkdir ${temp_folder}/share/${appli_name} mkdir ${temp_folder}/share/doc share_doc_folder="${temp_folder}/share/doc/${appli_name}" mkdir ${share_doc_folder} mkdir ${temp_folder}/share/icons mkdir ${temp_folder}/share/icons/${appli_name} mkdir ${temp_folder}/share/applications # must delete debug symbol in OSCAR binary file # -- V1 : strip -s -o ${temp_folder}/bin/${appli_name} ${build_folder}/oscar/OSCAR #old code : cp ${build_folder}/oscar/OSCAR ${temp_folder}/bin # for QT5.7 # strip -s -o ${temp_folder}/bin/oscar.qt5.7 ./oscar.qt5.7 # for QT5.9 # strip -s -o ${temp_folder}/bin/oscar.qt5.9 ./oscar.qt5.9 # 2>/dev/null : errors does not appear : we don't care about them cp -r ${build_folder}/oscar/Help ${temp_folder}/share/${appli_name} 2>/dev/null cp -r ${build_folder}/oscar/Html ${temp_folder}/share/${appli_name} 2>/dev/null cp -r ${build_folder}/oscar/Translations ${temp_folder}/share/${appli_name} 2>/dev/null cp ./OSCAR.png ${temp_folder}/share/icons/${appli_name}/${appli_name}.png cp ./OSCAR.desktop ${temp_folder}/share/applications/${appli_name}.desktop echo "Copyright 20192020 oscar-team.org " > $share_doc_folder/copyright changelog_file="$share_doc_folder/changelog" #automatic changelog as a bad name # need to generate one and say fpm to use it instead of create one # it seems that it needs both of them... # creation of the changelog.Debian.gz (for rpm package ? ) echo "$appli_name ($1-$2) whatever; urgency=medium" > $changelog_file echo "" >> $changelog_file echo " * Package created with FPM." >> $changelog_file echo "" >> $changelog_file echo " -- oscar-team.org " >> $changelog_file gzip --best $changelog_file description='Open Source CPAP Analysis Reporter\n' # trick for dummies : need to use echo -e to take care of \n (cariage return to slip description and extra one description=$(echo -e $description) # restore umask value umask $current_value # create the .rpm file (litian test show juste a warning with a man that doesn't exists : don't care about that) # pour 32bit : i686 au lieu de x86_64 fpm --input-type dir --output-type rpm \ --prefix ${dest_folder} \ --after-install ln_usrbin.sh \ --before-remove rm_usrbin.sh \ --after-remove clean_rm.sh \ --name ${appli_name} --version $1 --iteration $2 \ --category misc \ --maintainer " -- oscar-team.org " \ --license GPL-v3 \ --vendor oscar-team.org \ --description "$description" \ --url https://sleepfiles.com/OSCAR \ -C ${temp_folder} \ . OSCAR-code-v1.3.1/Building/Linux/rm_usrbin-NN-test.sh000077500000000000000000000001341417327530600221500ustar00rootroot00000000000000#! /bin/bash # # no error is permitted set -e # application name appli_name="OSCAR-test" OSCAR-code-v1.3.1/Building/Linux/rm_usrbin-NN.sh000077500000000000000000000001311417327530600211700ustar00rootroot00000000000000#! /bin/bash # # no error is permitted set -e # application name appli_name="OSCAR" OSCAR-code-v1.3.1/Building/Linux/rm_usrbin-common-NN.sh000077500000000000000000000043701417327530600224670ustar00rootroot00000000000000 if [ ! -z "$SUDO_USER" ]; then # find real name of the Desktop folder (Bureau for xubuntu french version) desktop_folder_name="/home/$SUDO_USER/Desktop" # si doesn't exist, try to find it translated name tmp_dir="" if [ ! -d "$desktop_folder_name" ]; then tmp_dir=`cat /home/$SUDO_USER/.config/user-dirs.dirs | grep XDG_DESKTOP_DIR | awk -F= '{print $2}' | awk -F\" '{print $2}' | awk -F\/ '{print $2}'` fi # don't overwrite if tmp_dir empty or doesn't exist if [ -n "$tmp_dir" ]; then # calculate the full folder tmp_dir_full="/home/${SUDO_USER}/${tmp_dir}" if [ -d "$tmp_dir_full" ]; then desktop_folder_name=$tmp_dir_full fi fi file="$desktop_folder_name/${appli_name}.desktop" if [ -f "$file" ]; then rm $file fi fi # clean destination folders (bin + shortcut) + move from /opt/OSCAR (if exists) if [ -d "/opt/${appli_name}" ]; then # --- old folder exists : move files to the new folder --- # Help folder folder_from="/opt/${appli_name}/Help" folder_to="/usr/share/${appli_name}" if [ -d "$folder_from" ]; then mkdir -p $folder_to mv $folder_from $folder_to fi # Html folder folder_from="/opt/${appli_name}/Html" folder_to="/usr/share/${appli_name}" if [ -d "$folder_from" ]; then mkdir -p $folder_to mv $folder_from $folder_to fi # Translations folder folder_from="/opt/${appli_name}/Translations" folder_to="/usr/share/${appli_name}" if [ -d "$folder_from" ]; then mkdir -p $folder_to mv $folder_from $folder_to fi # icon file : OSCAR.png file="/opt/${appli_name}/OSCAR.png" folder_to="/usr/share/icons/${appli_name}" if [ -f "$file" ]; then mkdir -p $folder_to mv $file ${folder_to}/${appli_name}.png fi # shortcut file : OSCAR.desktop file="/opt/${appli_name}/OSCAR.desktop" folder_to="/usr/share/applications/${appli_name}" if [ -f "$file" ]; then mkdir -p $folder_to mv $file ${folder_to}/${appli_name}.desktop fi # folder /opt can be deleted rm -R /opt/${appli_name} fi # clean the destination folder file="/usr/bin/${appli_name}" if [ -f "$file" ]; then rm $file fi OSCAR-code-v1.3.1/Building/Linux/rm_usrbin-test.xx000077500000000000000000000020231417327530600216630ustar00rootroot00000000000000#! /bin/bash # # no error is permitted set -e # application name appli_name="OSCAR-test" if [ ! -z "$SUDO_USER" ]; then # find real name of the Desktop folder (Bureau for xubuntu french version) desktop_folder_name="/home/$SUDO_USER/Desktop" # si doesn't exist, try to find it translated name tmp_dir="" if [ ! -d "$desktop_folder_name" ]; then tmp_dir=`cat /home/$SUDO_USER/.config/user-dirs.dirs | grep XDG_DESKTOP_DIR | awk -F= '{print $2}' | awk -F\" '{print $2}' | awk -F\/ '{print $2}'` fi # don't overwrite if tmp_dir empty or doesn't exist if [ -n "$tmp_dir" ]; then # calculate the full folder tmp_dir_full="/home/${SUDO_USER}/${tmp_dir}" if [ -d "$tmp_dir_full" ]; then desktop_folder_name=$tmp_dir_full fi fi file="$desktop_folder_name/${appli_name}.desktop" if [ -f "$file" ]; then rm $file fi fi # clean the destination folder file="/usr/local/bin/${appli_name}" if [ -f "$file" ]; then rm $file fi OSCAR-code-v1.3.1/Building/Linux/rm_usrbin.xx000077500000000000000000000051421417327530600207130ustar00rootroot00000000000000#! /bin/bash # # no error is permitted set -e # application name appli_name="OSCAR" if [ X_$SUDO_USER != "X_" ]; then # find real name of the Desktop folder (Bureau for xubuntu french version) desktop_folder_name="/home/$SUDO_USER/Desktop" # si doesn't exist, try to find it translated name tmp_dir="" if [ ! -d "$desktop_folder_name" ]; then tmp_dir=`cat /home/$SUDO_USER/.config/user-dirs.dirs | grep XDG_DESKTOP_DIR | awk -F= '{print $2}' | awk -F\" '{print $2}' | awk -F\/ '{print $2}'` fi # don't overwrite if tmp_dir empty or doesn't exist if [ -n "$tmp_dir" ]; then # calculate the full folder tmp_dir_full="/home/${SUDO_USER}/${tmp_dir}" if [ -d "$tmp_dir_full" ]; then desktop_folder_name=$tmp_dir_full fi fi file="$desktop_folder_name/${appli_name}.desktop" if [ -f "$file" ]; then rm $file fi fi # clean destination folders (bin + shortcut) + move from /opt/OSCAR (if exists) if [ -d "/opt/OSCAR" ]; then # --- old folder exists : move files to the new folder --- # Help folder folder_from="/opt/OSCAR/Help" folder_to="/usr/share/${appli_name}" if [ -d "$folder_from" ]; then mkdir -p $folder_to mv $folder_from $folder_to fi # Html folder folder_from="/opt/OSCAR/Html" folder_to="/usr/share/${appli_name}" if [ -d "$folder_from" ]; then mkdir -p $folder_to mv $folder_from $folder_to fi # Translations folder folder_from="/opt/OSCAR/Translations" folder_to="/usr/share/${appli_name}" if [ -d "$folder_from" ]; then mkdir -p $folder_to mv $folder_from $folder_to fi # icon file : OSCAR.png file="/opt/OSCAR/OSCAR.png" folder_to="/usr/share/icons/${appli_name}" if [ -f "$file" ]; then mkdir -p $folder_to mv $file ${folder_to}/${appli_name}.png fi # shortcut file : OSCAR.desktop file="/opt/OSCAR/OSCAR.desktop" folder_to="/usr/share/applications/${appli_name}" if [ -f "$file" ]; then mkdir -p $folder_to mv $file ${folder_to}/${appli_name}.desktop fi # what is the purpose of saving the binary file ? # binary file : OSCAR (in /tmp + minimize the name [oscar instead of OSCAR]) #file="/opt/OSCAR/OSCAR" #folder_to="/tmp/${appli_name}" #if [ -f "$file" ]; then # mkdir -p $folder_to # mv $file ${folder_to}/${appli_name} #fi # folder /opt can be deleted rm -R /opt/OSCAR fi # clean the destination folder file="/usr/bin/${appli_name}" if [ -f "$file" ]; then rm $file fi OSCAR-code-v1.3.1/Building/Linux/rpm/000077500000000000000000000000001417327530600171235ustar00rootroot00000000000000OSCAR-code-v1.3.1/Building/Linux/rpm/.gitignore000066400000000000000000000001001417327530600211020ustar00rootroot00000000000000.ccache # This is ignored in a parent directory !Makefile* *~ OSCAR-code-v1.3.1/Building/Linux/rpm/Makefile000066400000000000000000000011531417327530600205630ustar00rootroot00000000000000# Front end for second-stage Makefile include Makefile.common # Second-stage make, which does the actual work. MAKE2 := $(MAKE) -f Makefile-stage2 default: build %: $(SPEC) $(MAKE2) $@ # Build the spec with a valid version. VERSION_FILE := VERSION VERSION_SOURCE := $(TOP)/oscar/VERSION VERSION := $(shell egrep -e '\#define\s+VERSION\s+' '$(VERSION_SOURCE)' \ | sed -e 's/^.* //g' \ | tr -d '"' \ | tr -- - . \ ) SPEC=$(NAME).spec $(SPEC): $(SPEC).raw Makefile sed -e 's/__VERSION__/$(VERSION)/g' $< > $@ TO_CLEAN += $(SPEC) build: $(SPEC) $(MAKE2) clean:: $(SPEC) $(MAKE2) $@ rm -f $(TO_CLEAN) OSCAR-code-v1.3.1/Building/Linux/rpm/Makefile-stage2000066400000000000000000000007061417327530600217510ustar00rootroot00000000000000# # Makefile for Most Packages # AUTO_TARBALL=1 include generic-rpm.make # Build a copy of the Git repository so it can be built into a tarball TOP := $(shell git rev-parse --show-toplevel) WHEREAMI = $(shell cd $(PWD) && pwd) EXCLUDE := $(echo "$(WHEREAMI)" | sed -e 's|^$(TOP)/||') GIT_DIR := $(notdir $(TOP)) $(GIT_DIR): mkdir -p "$@" (cd $(TOP) && tar cf - --exclude Building/Linux/rpm .) \ | (cd "$@" && tar xpf -) TO_CLEAN += $(GIT_DIR) OSCAR-code-v1.3.1/Building/Linux/rpm/Makefile.common000066400000000000000000000002721417327530600220530ustar00rootroot00000000000000# # Common Makefile values # NAME := OSCAR TOP := $(shell git rev-parse --show-toplevel) WHEREAMI := $(shell cd $(PWD) && pwd) EXCLUDE := $(echo "$(WHEREAMI)" | sed -e 's|^$(TOP)/||') OSCAR-code-v1.3.1/Building/Linux/rpm/OSCAR.spec.raw000066400000000000000000000036061417327530600214430ustar00rootroot00000000000000# # RPM Spec for OSCAR # %define short OSCAR %define long %{short}-code Name: OSCAR Version: __VERSION__ Release: 1%{?dist} Summary: Open-Source CPAP Analyser and Reporter BuildArch: %(uname -m) License: GNU GPL v3 Vendor: Phil Olynyk Group: Unspecified Source0: %{long}-%{version}.tar.gz Provides: %{name} = %{version}-%{release} # RPM requires definitions be at least three characters. Go figure. %define q_t qt5 %define qmake qmake-%{q_t} %define icons %{_datadir}/icons/hicolor Requires: %{q_t} BuildRequires: dnf-utils BuildRequires: %{q_t}-devel %description OSCAR is a cross-platform, open-source sleep tracking program for reviewing CPAP and oximetry data, which are devices used in the treatment of Sleep Disorders like Obstructive Sleep Apnea. # Don't build a debug package. %define debug_package %{nil} %prep %setup -q -n %{long}-%{version} %build %{qmake} OSCAR_QT.pro make -j %install # Binary install -D -m 555 ./oscar/%{short} "${RPM_BUILD_ROOT}/%{_bindir}/%{short}" # Icons for SIZE in 32 48 64 128 256 512 1024 do echo "SIZE $SIZE" install -D -m 555 \ "./Building/Icons/Full-${SIZE}.png" \ "${RPM_BUILD_ROOT}/%{icons}/${SIZE}x${SIZE}/apps/%{name}.png" done # Desktop install -D -m 555 "Building/Linux/%{short}.desktop" \ "${RPM_BUILD_ROOT}/%{_datadir}/applications/%{short}.desktop" %post touch --no-create %{icons} &>/dev/null || true update-desktop-database &> /dev/null || true if [ $1 -eq 0 ] ; then touch --no-create %{icons} &>/dev/null gtk-update-icon-cache %{icons} &>/dev/null || true fi %postun update-desktop-database &> /dev/null || true if [ $1 -eq 0 ] ; then touch --no-create %{icons} &>/dev/null gtk-update-icon-cache %{icons} &>/dev/null || true fi %posttrans gtk-update-icon-cache %{icons} &>/dev/null || true %files %defattr(-,root,root,-) %license COPYING %{_bindir}/* %{icons}/*/apps/* %{_datadir}/applications/* OSCAR-code-v1.3.1/Building/Linux/rpm/README.md000066400000000000000000000006001417327530600203760ustar00rootroot00000000000000# RPM Builder for Linux The Makefile in this directory builds OSCAR as an RPM. ## Build Process * Clone this repository * `cd Building/Linux/rpm` * `sudo make` Build by-products can be removed with `sudo make clean`. ## Assumptions `git rev-parse` is used to determine the top-level directory, ergo this tree must have been cloned from GitLab and `git` must be installed. OSCAR-code-v1.3.1/Building/Linux/rpm/generic-rpm.make000066400000000000000000000112741417327530600221770ustar00rootroot00000000000000# # Generic Makefile for RPMs # # Original source: https://github.com/perfsonar/pscheduler/tree/master/make-generic-rpm # Licensed Apache Software License Version 2.0 # # To use this file, create a Makefile containing the following: # # inclulde make/generic-rpm.make # # Targets (Shortcuts in Parentheses): # # build (b) - Build the RPM. This is the default target. # clean (c) - Remove build by-products # install (i) - Install the RPM forcibly. Must be run as a user # that can do this. # rpmdump (r) - Dump contents of built RPMs. # # Other useful shortcut targets: # # cb - Clean and build # cbr - Clean, build and rpmdump # cbi - Clean, build and install # cbic - Clean, build and install and forced re-clean # cbrc - Clean, build, rpmdump and forced re-clean # # # To construct a tarball of your sources automatically: # # - Name a subdirectory with the name of the product (e.g, foomatic) # - Specify a tarball in the source (e.g., Source0: foomatic-1.3.tar.gz) # - Set AUTO_TARBALL=1 in your makefile # # NOTE: The version number in the spec may not contain hyphens. # # # NO USER-SERVICEABLE PARTS BELOW THIS LINE # default: build # # Spec file and things derived from it # # Do this in a POSIX-y way, which precludes mindepth. SPEC := $(shell find . -name '*.spec' | sed -e '/^\.\/[^/]*$$/!d; s/^\.\///') ifeq "$(words $(SPEC))" "0" $(error No spec in this directory) endif ifneq "$(words $(SPEC))" "1" $(error This directory contains more than one spec file) endif NAME := $(shell echo "$(SPEC)" | sed -e 's/\.spec$$//') VERSION := $(shell rpmspec -P "$(SPEC)" | awk '$$1 == "Version:" { print $$2 }') SOURCE_FILES := $(shell spectool -S $(SPEC) | awk '{ print $$2 }') PATCH_FILES := $(shell spectool -P $(SPEC) | awk '{ print $$2 }') # # Automagic source tarball construction # ifdef AUTO_TARBALL ifeq "$(words $(SOURCE_FILES))" "0" $(error No need to set AUTO_TARBALL with no sources in spec file) endif ifeq "$(shell [ $(words $(SOURCE_FILES)) -gt 1 ]; echo $$?)" "0" $(error Cannot automatically build a tarball from multiple sources) endif ifneq "$(findstring -, $(VERSION))" "" $(error The version number in the spec may not contain hyphens.) endif TARBALL_SOURCE=$(shell echo $(SOURCE_FILES) | sed -e 's/-[^-]*\.tar\.gz$$//') TARBALL_NAME=$(TARBALL_SOURCE)-$(VERSION) TARBALL=$(TARBALL_NAME).tar.gz ALL_TARBALLS=$(TARBALL_SOURCE)-*.tar.gz $(TARBALL): $(TARBALL_SOURCE) cp -r $(TARBALL_SOURCE) $(TARBALL_NAME) tar czf $@ $(TARBALL_NAME) rm -rf $(TARBALL_NAME) BUILD_DEPS += $(TARBALL) TO_CLEAN += $(TARBALL) $(TARBALL_NAME) $(ALL_TARBALLS) endif # # RPM Build Directory # BUILD_DIR=./rpmbuild BUILD_BUILD=$(BUILD_DIR)/BUILD BUILD_RPMS=$(BUILD_DIR)/RPMS BUILD_SOURCES=$(BUILD_DIR)/SOURCES BUILD_SPECS=$(BUILD_DIR)/SPECS BUILD_SRPMS=$(BUILD_DIR)/SRPMS BUILD_SUBS=\ $(BUILD_BUILD) \ $(BUILD_RPMS) \ $(BUILD_SOURCES) \ $(BUILD_SPECS) \ $(BUILD_SRPMS) \ $(BUILD_DIR): $(SPEC) $(SOURCE_FILES) rm -rf $@ mkdir -p $(BUILD_SUBS) cp $(SPEC) $(BUILD_SPECS) ifneq "$(words $(SOURCE_FILES))" "0" cp $(SOURCE_FILES) $(BUILD_SOURCES) endif ifneq "$(words $(PATCH_FILES))" "0" cp $(PATCH_FILES) $(BUILD_SOURCES) endif TO_CLEAN += $(BUILD_DIR) BUILD_ROOT=./BUILD-ROOT $(BUILD_ROOT): mkdir -p $@ TO_CLEAN += $(BUILD_ROOT) RPM=rpm RPMBUILD=rpmbuild # # Useful Targets # build:: $(BUILD_DEPS) $(BUILD_DIR) $(BUILD_ROOT) set -o pipefail \ && dnf builddep $(SPEC) \ && HOME=$(shell pwd) \ $(RPMBUILD) -ba \ --buildroot $(shell cd $(BUILD_ROOT) && pwd) \ $(SPEC) 2>&1 \ | tee build.log find $(BUILD_DIR) -name '*.rpm' | xargs -I{} cp {} . TO_CLEAN += build.log TO_CLEAN += *.rpm srpm:: $(BUILD_DEPS) $(BUILD_DIR) HOME=$(shell pwd) rpmbuild -v -bs $(RPMBUILD_OPTS) $(SPEC) find $(BUILD_DIR) -name '*.src.rpm' | xargs -I{} cp {} . TO_CLEAN += *.src.rpm rpmdump:: @if [ -d "$(BUILD_RPMS)" ] ; then \ for RPM in `find $(BUILD_RPMS) -name '*.rpm'` ; do \ echo `basename $${RPM}`: ; \ rpm -qpl $$RPM 2>&1 | sed -e 's/^/\t/' ; \ echo ; \ done ; \ else \ echo "RPMs are not built." ; \ false ; \ fi install:: find $(BUILD_RPMS) -name '*.rpm' | xargs $(RPM) -Uvh --force clean:: rm -rf $(TO_CLEAN) find . -name '*~' | xargs rm -rf find . -depth -name Makefile \ -exec /bin/sh -c \ '[ "{}" != "./Makefile" ] && make -C `dirname {}` clean' \; # Placeholder for running unit tests. test:: @true # # Convenient shorthands # b: build c: clean i: install r: rpmdump cb: c b cbr: c b r cbi: c b i # CBI with forced clean afterward cbic: cbi $(MAKE) clean # CBR with forced clean afterward cbrc: cbr $(MAKE) clean OSCAR-code-v1.3.1/Building/Linux/tst_user.sh000077500000000000000000000004501417327530600205330ustar00rootroot00000000000000#! /bin/bash set -e # #test USER (must be root) vs SUDO_USER (must be other than root but not empty) if [ "$USER" != "root" ] || [ "$SUDO_USER" = "root" ] || [ -z "$SUDO_USER" ]; then echo "Installation must be done as normal user with sudo command. fatal error" exit fi # do nothing OSCAR-code-v1.3.1/Building/MacOS/000077500000000000000000000000001417327530600161705ustar00rootroot00000000000000OSCAR-code-v1.3.1/Building/MacOS/BUILD-mac.md000066400000000000000000000104561417327530600201150ustar00rootroot00000000000000# OSCAR Build Instructions for Mac ## Prerequisites - [Qt 5.12.8] (the current LTS release as of OSCAR 1.1.0) - [macOS 10.12 Sierra] or higher for building (required by Qt 5.12) - Command-Line Tools for Xcode 9.2, and optionally [Xcode] itself - Xcode 9.2 is the last version that runs on macOS 10.12 - Xcode 10.1 is the last version that runs on macOS 10.13 - Xcode 10.3 is the latest version that runs on macOS 10.14 NOTE: Official builds are currently made with [macOS 10.14 Mojave] and Command-Line Tools for [Xcode] 10.3. ## Setup 1. Install Mac OS X 10.12.6 Sierra (or later) and apply all updates. * Optionally create a "build" user. 2. (Optional) Install Xcode 9.2 (or later, if using a newer version of macOS), approx. 7GB: 1. Open Xcode_9.2.xip to expand it with Archive Utility. This will take a while. 2. Delete the .xip archive. 3. Move Xcode.app into /Applications. 4. Launch Xcode.app and agree to the license. 5. Uncheck "Show this window..." and close the window. 6. Xcode > Quit 3. Install the command-line developer tools, approx. 0.6GB: 1. Launch Terminal.app and run: xcode-select --install 2. Click "Install". 3. Click "Agree". This will download and install the latest version of the Command-Line Tools for Xcode for your version of macOS, without requiring a developer account. _Alternatively, the command-line tools installer .dmg can be downloaded from the [Xcode] download site, but you will need a (free) developer account and will need to pick the appropriate download for your version of macOS._ 4. Install Qt (as "build" user, if created), approx. 3GB: 1. Mount qt-opensource-mac-x64-5.12.8.dmg 2. Launch qt-opensource-mac-x64-5.12.8 3. Next, Skip, Continue, (optionally change the installation directory), Continue * Qt is entirely self-contained and can be installed anywhere. It defaults to ~/Qt5.12.8. * If you only have the command-line tools installed, the Qt installer will complain that "You need to install Xcode and set up Xcode command line tools." Simply click OK. 4. Expand Qt 5.12.8 and select "macOS", Continue 5. Select "I have read and agree..." and Continue, Install 6. Uncheck "Launch Qt Creator", Done 7. Eject qt-opensource-mac-x64-5.12.8 ## Build 1. Build OSCAR: git clone https://gitlab.com/pholy/OSCAR-code.git cd OSCAR-code mkdir build cd build ~/Qt5.12.8/5.12.8/clang_64/bin/qmake ../oscar/oscar.pro make The application is in OSCAR.app. 2. (Optional) Package for distribution: make dist-mac The dmg is at OSCAR.dmg. ## (Optional) Using Qt Creator 1. Launch Qt Creator where you installed Qt above, by default ~/Qt5.12.8/Qt Creator.app. 2. File > Open File or Project... and select ~/OSCAR-code/oscar/oscar.pro (or wherever you cloned it above), then click "Configure Project". 3. Configure building: 1. Click on "Projects" in the left panel. 2. Under **Build Settings**, in the "Edit build configuration" drop-down menu, select "Release". 3. Click to expand "Details" for the **qmake** build step. 4. Uncheck "Enable Qt Quick Compiler", click "No" to defer recompiling. 4. Configure packaging for distribution: 1. Click "Clone..." to the right of the "Edit build configuration" drop-down menu. 2. Name the new configuration "Deploy". 3. Click to expand "Details" for the **Make** build step. 4. Set the Make arguments for the Make step to "dist-mac". 5. To build OSCAR, select "Release" from the "oscar" button in the left panel. Then select Build > Build Project "oscar". The application is in OSCAR.app. 6. To build OSCAR and package for distribution, select "Deploy" from the "oscar" button in the left panel. Then select Build > Build Project "oscar". The dmg is at OSCAR.dmg. * Progress in "Compile Output" will pause for several seconds while "Creating .dmg". This is normal. [Qt 5.12.8]: http://download.qt.io/archive/qt/5.12/5.12.8/qt-opensource-mac-x64-5.12.8.dmg [macOS 10.14 Mojave]: https://apps.apple.com/us/app/macos-mojave/id1398502828?ls=1&mt=12 [macOS 10.13 High Sierra]: https://apps.apple.com/us/app/macos-high-sierra/id1246284741?ls=1&mt=12 [macOS 10.12 Sierra]: https://apps.apple.com/us/app/macos-sierra/id1127487414?ls=1&mt=12 [Xcode]: https://developer.apple.com/download/more/ OSCAR-code-v1.3.1/Building/MacOS/Info.plist.in000066400000000000000000000017171417327530600205530ustar00rootroot00000000000000 CFBundleExecutable ${EXECUTABLE_NAME} CFBundleGetInfoString ${QMAKE_FULL_VERSION} CFBundleIconFile ${ASSETCATALOG_COMPILER_APPICON_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundlePackageType APPL CFBundleShortVersionString ${QMAKE_FULL_VERSION} CFBundleSignature ${QMAKE_PKGINFO_TYPEINFO} LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSPrincipalClass NSApplication NSSupportsAutomaticGraphicsSwitching NSRequiresAquaSystemAppearance OSCAR-code-v1.3.1/Building/MacOS/README.rtfd/000077500000000000000000000000001417327530600200635ustar00rootroot00000000000000OSCAR-code-v1.3.1/Building/MacOS/README.rtfd/Oscar-mac-10-catalina-fix-permissions.jpg000066400000000000000000001757521417327530600275400ustar00rootroot00000000000000JFIFHHExifMM*JR(iZHH 8Photoshop 3.08BIM8BIM%ُ B~ " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC C  1 ?(((2(p@u>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pu:nK@ EPEPEPEP((-M-QK2Pj35^)7yRy2|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q K}7ϣϠ =o&7io՞%x585TY*PdQ-Q@Q@(1Pf꜒b$Abpqq3Ŧv 8RTyY O⋷A3Ky>MqVˁ4ڀPT#z>!_=X>mmmjGaREA*_Ս@"Q*_Տ@"Q*_Տ@"Q*_ՏrAfbUFK֐5%M*_o_ -G>OFOhxK!c RTkǨԿ/ZϴPR} DPqeFqU}{?I*_o_ -E>OWH^3T6e%N=3Կ/Z?/KzQDieI=Rx>O /KzKRE`ch_KRERTkǪ/{?g; Kãz.ph[RTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZhhcRTkǨԿ/ZjHE̢'R lLtI$"5?/KzKRE`chOKRERTkǪ?}{?RTkǨԿ/Z^|;G>@?v$Կ/Z?/Kz׿'>O ?/KzKRE`chOKRERTkǪ?}{?RTkǨԿ/Z^|;G>@?v$Կ/Z??Kz׿'>O 5/Kz)haZFJ٠ @斣SRPEPJWsS15WYK;.es>0LJu._;{3LJ5/A5];w.#N;!5_%}_ K#cHȏ&I%$lgC5K} f;Z[w<FqykOK."hah"Iefb;cؚTZCl"(P}n(:#͓C^Z\x+ x7Te,(GzĂ%a_;Gy־eC[1c@W/e_2A&G-$b^&\/p "H.Ā~xV4x Q{o ygqT}Oҙ*u ^tS=Wd)X(K-VCNgC"#x5x@,웿cRoBޛ$iIy|,pprnXّ?cEK@<) :kI;:M"P(/B/??~I_"!ogzAG0(]5C{FS>+ t㽤$w?wyƽ>h?664zRrk?> #&҄7rŨ0]h:Yx[Sm9Υue-򢸍bX䐅V$(=+cno,rBg~DF<^UP7akhG]_$PȓWBo2=o4Z%tx``Bͺ0 mXHQ6\j K1,B_л|~1ڸ:̓ꌞ`[Ln" y1koᗊo<;[":日/S*hǁu2[6O;Je>ZYx{O}<| -#;p8 1@W"חricy`qmɻtjX;֢z[mj5M=bU/4Itr,2+?մX4_ 6{4DKsnp¨M=|&X‚ܱE?חw{q#b\i4Æ3lA 8&>3ͣ͠ ?;ޏ;޳<<3ͣ͠ ?;ޏ;޳<<3ͣ͠ ?;ޏ;޳<<\jDVLmu~HnBdvlveUzqD%';nO֫o=6c Z-321W|Ys[ KUմ4J9 Gs$XeT%U +Η~7 ~ws߭csvKW"x䙚]A]x_Y<7 ΥK b9>x E.m䶍"RGo25WVf=;^$]톙i vo !.ۆ D/Ӭ-]Οs,m#CLbU;Y wʹ8 xS]c獬W!բg[1b1>wRL[gkVQC",#0RTsuas@=`Ļ TY-^%)*obWo=믶A$3"daG=3V}^Wȯ R%q$ K(߅bF49ˣV:]#KkD2J@8u>`Cpp `Cpp `Cpp `Cpp `CpR0 F(G4 <+/6ߟ%Gmu`;'{Bqלu`|x˟vСOl׋O׵ zuɚЍLjSSUahQEn\RBc@w sr5r/_p~6Կ_WG%LJ5/A5FAkoI 66ֿ-m/4Z_h'ͣͭoB5 [ Mk_hk[kG!&2|<#Z$FAkoI -тOB+|`tc>qYk_?ֿ-m/4 WM#S_V=2O SL7fZ_h#Z$+txwi3,Wsc͓ yM?XZm5ֿ-m/4Z_hS@o FmS:ȢYb̹r1t_B5 [ Mk_ϴԮ۸vcJI#GB:?m՗FAkoIk@𰵏k5𰵏k5eZ_h#Z$,cV\ޥw]}K mmkZ_h#Z$OGZk_?ֿ-m/4ַ!&B5 [ MdoO֯kX$J+r0 TOb0O!&B5 [ Mjmm՗FAkoIk@k5k5eZ_h#Z$,-_6G,-_6Yk_?ֿ-m/4 WM#Q WM#V_!&B5 [ Mjmm՗FAkoIk@k5k5eZ_h#Z$,-_6G,-_6Yk_?ֿ-m/4 WM#Q WM#V_!&B5 [ Mjmm՗FAkoIk@k5`jtw^Z(u@NX!K@[#Z$FAkoIzlx(k,+4k_.6pvh3&slcoD8܅n+کjQE5>]VtNJВ ;RQw%~zKM{mxWS{KZ(((((((((((((((((((((((((((((((((((((((((/6ZN=iIҳg~uBh~?#i<9^jĄ+ʶ45' 3=|xWI ~ 2[hxJ5].M2%%`#uhTyaX Mo7?QMo7?Wh߁5J7[HtM^4iф0&?>(D?'L<3aۏvE3bg2:m0ɭ&[>ɭ&[j[hιh#I[[pŪq 4fe(3]&~8mSO-- 5=@ۣ7,@ɭ&[>ɭ&[~|MĿ׉wሼ7=oF" S[]BG"A wM{ypkh2ꤩޢ&@mǫP+/ZgJGHhֹYYZgy;H.w J_k zk zyi4zW/t_x{J˺}"%%^ݘL%ݵBzL~eV^i6k[Ay;!$nHpH}[M}[Mầǯ~x:zOaQ_O*W7IHC){z0cI74M~K zR}dG/`fVSԀuds-ds-dh|#;j6__n.,QF7$!_,Tp #;Ϋ xW% [EeʲxX7a|/n atcm~oB+Ӫ?Tt}[M}[MѾ>־|ɭ&['oXx_&VKa{;d/iCwmGY0Ygûho"Դ,AuvYK(+p%ixYLnnK|iv'cYYXyC-Y%ɜȬC+<f`GL4b[CKq D:K<_&@mǫ^qĚƓ{;?/,kggs'|% m{d8(c3<>9/RIge Z1gIx?_i/#Yv_/|- gE5}Bg.\%Kuvtڪ|4rQ^m%2z?0qveNqM.%}Z={Kݧ%Q %k??ݎ^6mjdӤKhQ2WqpnOTK8u; V95J'Id)4F=^yp M^+41Jk.u揪᤼@gU^F~?*S!>oi&\h7αÿjDPkNoZ=ڄzU1_{+wuٞYJf=E[U'7~5nVqFtv?K;LIx?#@:N=v|Uiz΋ZN=iIҳgzrC]}J5<_-/k+u"ާ^& Ԕ3û7vYzߋҀ3袊(((((((((++]ޫZGU6]. '{fL{K)v=o2 jX.%hfIAMcz?)P_v~<1fLJχ?i7KN9̓ o?ْSX  ¨{Mq_nahg9_=fIAMcz?)Po?z>!|=O>Kh: &;ᱷyWJdMOW$jrO'5~?ڎ}4tiZOE2$ov2kK麶xvK='DԦGLF̡m&<+,sڤ*wzd?kyǨ̓ o?!t}w(E=bE5\.43bn! ̟tFdY'0sx)My|6Q+ i lPӿ$[=Gd?kyǨG ^x-CRDҗG]<:ݪuy=~U! ?gOᦡ #\'&<#>gk,K [m%I'>z 4$[=Gd?kyǨJkxGd?kyǩJe$$Rd ,|><q־iwflǟ3ٝ.8D<6ns>d?kyǨ̓ o?r:쳦j Q |څޯG/"G$WzlbH^;Y$WqUu??L~h> t2]3Oˠ6Eխr[^gnG ͜$O/ TfIAMcz?)P~v-|N-N;/>]In/.7ùf9/O<%Im3Mmh1:|M٥(f噆~a#̓ o?ْSXAS/V>| /최}<:i&WMߏsW~׷dYqmu akC2O k5Ձh~4|5♵=KKYKSN={|5f\~!7 u!9%Qni^Kyx l5&E_kҿ Q><_xwZ=m[^[}D\q@6 6`+d-;H/ k\# ^\^vqI뽭3*JjkY'tR{,&̧1ݷ]ϕ:?M/W5.MZy-)l/$[T]2L&N.x/ hKB3xF<vR^7Ro攟fXf_u\QޗhIUӬuk[%R5>}必5_W[fVr !~%`й/ &d.K;I,~`1woK=ZKTK^6ߣOҽzR_KSV~{~_bܱKPy1KmEܢ8 6 䪃^ 5 g4LF8ԯҊ+.SX+kw}ݽ7g;ì"Zw{-ƳKsT ʶ֪HQ_Z/9OHAVaW'$IZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊(ںgAWh?@ںgAWh?@ںgAWh?@ںgAWh?@ںgAWh?@ںgAWh?@ںgAWh?@ںgAWh?@ںgAWh?@ںgAWh?@ںgAWh?@ںgAWh?@ںgAWh?@ںgAWjR: au) 8(J*)n tzn`Ρ}E-U?,?GhSB~a4hX?ƀ-U?,?GhSB~a4hX?ƀ-U?,?GhSB~a4hX?ƀ-U?,?GhSB~a4hX?ƀ-U?,?GhSB~a4hX?ƀ-U?,?GhSB~a4hX?ƀ-U?,?GhSB~a4hX?ƀ-U?,?GhSB~a4hX?ƀ-U?,?GhSB~a4hX?ƀ-U?,?GhSB~a4hX?ƀ-U?,?GhT_ؓq_@2(oS_7MEPxF׶ZֶVOg{j?kiEv]\ŗjPUujUuhqҊJ(5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊3/t^O#-|o{x;}Wŷ֧i6vn}̦8'>ƒ=l-R=l//#^:_i{kykmtA^(=Y r~4>iqk~:l; ;DQ'F zZԡ4?Vϕym鞻dP5*K|.qfkKPٚʼn~?>j]ӌ50Y;]r2ddWQ_<{UzEǟbaH\ wVڏyYc h,=7-xPu]C/<;ߖkXS{Ωs6U6_]6mydlc 8T57Z54iaCyk6O2 1{?Ŗ:j[H]C5-W:h#˥qppexI7#T\ִ h˅tYo.[ۡW!A$*)'8v hI Fã#A[}_G/jDŽmܶOrhevA>!$ xG#xSaU,4{z\mbidInY/vdXٝT(Tv"Ѿ6Ǻ΃wxmMK4.i95P+qXȻ6>0|MwwEfK[dsSB mv( ( ( ( ( u]El0-(nߍxŧݟs׳k->u2Xr*, >TQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE`1hаǭ_F-_7өh(ʼw#?QЭk[N+'3 ֵ;{.tb˵t(f*J*8E5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊? ȳ׍ZcJ4Nf8mosq ̸B#EђA澖:Gx赯9~-u/%(gH.Hq_ntFX:UM]5ٮө:rS&kF xT}Vzs[KiJ}HYc<۶NϢ!|s_y{;<kvⷸ4FAkiӴKZ7wmj=:'?p) |2<ݻpCOl-M>A51A U 3j|ᯇt|k֣+hU#~- "8uD6wucZV+4{Yfzr #~nDyc$*h+=OOuiO內n {)c\I"W^;k,k/rx i)N7У,pciɊhjJj1} O+6xیڶ+⟌W.o ~8,08%aO*i ?2x2Hu=ޡ$*M>b$f71*ږer{ I>_ktgo ]1d[; ^XW'jFňRQ3M־"x?_ռW?x;IP]FR{ub^;}F[vFi u MT-|Zo#.: ʲ60J:}ħ k_t5erƺO;d[xEj9Pt"~+ h|/{|-p.<9fogh/e͜pE HM}E|gZeķҾxT񧅛_knu u%ۨ\&l i?|S4Ož隯5[%k- GݒFK2RE~_h)n~&_/W":畭@4#E%[™qX WjƚZ^K5<i vEvfS2>+ό|iK4$ vj>"ԕ#1=ŧMb8AF7mw mǎKE> O^ uÉ}GngU:䥺+~WO.Ci:Oq]ip%o,<s+Jpp_kZ}NHPڱ77ꨐsl7 ܂ ׆?JԵ!kk$7}qq Q6d_0oM}h*|Iu(U`=@5_w_:L#ºT_^R=n4Ҟcs!+>)ƻNdfxji:֓o=Ȉ\5qj-kYݙv 4 ?v},5]Pӣ^n?0[ԡ{1G֑`[|[:G|W[;iŤX,ʰ=YCpYH%s_|9^iKZ};Ӕ.e;•P0+M/%ƻߍo i^ׄfb#H;Sd2۠t`_q _[ɾ 7)h[k2NRQRA~#Ѽ]Z:Y1wQiaAk [ɩip\]ۀۢ bHC2cq~2-]O@|kK6 IV[^I7e[mL@p־)ox{Z?Iomb)}cesuzƨLRn;s@~[xG/7mrjZd7v6袼26 1Os3_M*{ f{NoCʺ6Y \HּYi6>'uށkCo=yj5{Kf0Ҙ,9|@>+uxSU-" z^.VS/3@qMh{͌p64SFr)"=@tQEQEco5lǪ[B4nqMC@5Q@U?^ڏkZwQY>;Vuvۥs]@1UժQUՠ J((5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊? ȳ׍Zݬ XZqK¾ -HA47E1Td"{3jœ;%՝8<|]hsI6ߢGpX~t|s[v:k+C<ӳ.eS^8$k4:MGS4k*A0!dV 9Ƽ.*"R|Z6wWN2p⤯歿2/]oHY{n>՚cŠ(((((((((}Ei[5?/J^1igO?}] ˯?PT_/"T h#\O wdT2"W7GVD~v;(q7|79F}ffrkEF|.99o+7bFcz+ s}AXyw`g|W? h]AtxZ/=BSn471tp;W¹_~W7xo/,tHL0_jRvrnubH ,B%z+ s}AXyyqr+\V/o?G+  pAs}AXo+7?z+ s}AXytFw`g|W¹_~W7`g8hrxҽbF¹_~< c =k?\V/o?G+ π (0a^ #?\V/o?@}q@s}AXo+7?bF¹_~<|AዯAms%w+ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊Z~i̇ijgګž xOqOg,7PFiP/Π?3i_--׍:Hq/+ƋI7v:´ׂ |&9dXd+HeTRklxߕ4kM]hֶk/?}f>8{hmt)]jʱ$̻0r ҾOgQFa (l'ҡؑleV2&1ڂ}+FUޱ ,nngkpAQmѕ_8ɯ~zUΟuxͫAn5]DyJFHŸCZm!$=5d|0b3*Q{y(EoZV~xGSA*XP{y?5ּGHm&>{kKk]K:~$O UsڽgF{{eWe$6Gl0k׼+}"[LdGFPik_8~MIE)~}_MφÑjnyF<#i[-utnf\%9ی`GmX|<С(=qhfsN,SD1r\} wZ=m @jsyMka&?n$]~l _~kזTksm{ukw\s 5P!)\99>poxƟc}hv6u"m/M@.acu4WIf~ÿ>"/ojV:(_I7l[Z~{g0˽{ †%lmf<=x^ !|?k7[_:A+<`Qi~h,_i3GGuq5ԖVa0XimGy`|u? i }q5c@hYjt[4^Y*6IQWxGÏkii]Py!IAe^@Q@Q@Q@Q@Q@Q@cҶkt_7oƼb_W_9~5v@_/"_EA@,%J>>|XO 1Ë[_ ^/ igҼOӯ_uLԜd0G?.P|6'n6>w1YE|Nt[coSNm|SKGF..yQEW\Fmſ < N@73`<XzN8{k8eBVq{srEo+;1 &qeG)ӷ/ٵ]3|?*n o{ϞeόWiEэ䃲B;nkмMBԱka4jg&ԂSUlBL}7AM{N`\Y} +׎S_=ҬOtFQ Sd %a# YKGSKu#iv݉$|Փ˴Hv2+@?|BL)5o \&dT% RJ%eWt//xKǺ,^$Ncs3"^i s t8e 2O ɿ^/*{h;Vuvۥs]@1UժQUՠ J((5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊? gI|-Rטxg:"[Lwevq]h?,mַhKsWІ#̊xEchz~_\N%ltk"4?I@SFsҔWElUjф*͵އ5 3JPIݴAkmv#5 TQ\HQEQEQEQEQEQEQEQEQEV0-+fm;׎#{~gw ߍxŧݟskh^#{#,s,2R'*_W_92>eT=|(ŗ^Oh=|Ceu=Ob"rBZ+JUgJj7i-S]ZPN^wGľ7*I|9hleEk ?/qR;jzlU,E%ZhҢ+((.a`eM;G"RFQՕry.QT5 ?Te7vK*o9r2:A&!>TgIs@QPq5D\a)w p Cj6Mږs &b4^*ܓEPEPEPEPEPEPEPEPEPEPEPFn_DM^[^A5ymco5lDz[B4nqMC@5Q@U?^ڏkZwQY>;Vuvۥs]@1UժQUՠ J((5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊.?"3ţh?ox2-Ty\>4,I,ڟ-.>g{v\ ~'6xX=|M1kǥnkJ=:;asPR'.XHBI#u[81n26}y  xowm_ĺwGKAp |[' 摝K`w( A> պ^x']o=;2HK2;π'#'hզݭ# C~o@c-W~zrab`9#Ggd  l0?1Kgkx<İ>uq_ YwADvoOkpt5ԱF6@ʂf[h6-b'H-x 5Y1/H-&i bMuZw%z ݟse|*{?PP/ igҾ%J2 (>y7N]^m~/]j6zݭjZ[ɛCchi8Fb[[]Ma#U J̬+Ib ]a{ۓg~5?Yd1էO74cOMv늸scSݭ$N5/n-K)甀 @3}JX۩[LiMKK=nIJlnGHL!/*œ|#jx;wyOٟ_ٴ,<)&G FCuPQF;;Z-BtɴJ/le.O$Bɱibq;ns>$o_.Y ww? ~xkw Jڬm-^3̛xёeJ*0o¿:?ŚGt+n1(MJMD.&HăgbŘ>ExJuC&=ޒ;JoDn @\4c|Tl ~𕞥vGVuꍧoun' \AJ9#עk xB=KFK:kDY&X^2GGvp733I'=eiik"Q¢"(=~,x,;>+X I}i2.C2Cogs9 Ѓ*~^6/(KKKg9U>Ĩkx$ 3_.>Yɫn&dX̳Pe| 4+Tt?h~jvve6狈0zĿ|Ox[$ۖk:ZdOT﮴Јxd`|KT[Vm_K>)x;"hdwg4~k]Fbf(6~#k DJu;6k eh~?vȥ>o#….\Ł$u5M;Uڇ%'״M VFGIk 2H H,wV[]ՖhWTN6w5>Llq91 nk ~xL|EmTwkyB#teyc*_4+> 5;- BךeبnXptA'^[5+k&fIYE4ad#1+HnSQ4XPP0((((((((߈ 艫kԾ#7O&-l~ƭ_F-_7өh(ʼw#?QЭk[N+'3 ֵ;{.tb˵t(f*J*8E5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊\di>Z Ev~!VѴ,ke ,@gPp8~BԼQ?ѣoYZHPY4qN0''u #xkVy̅؝t$gV 5Ib% σVZ"+wkmmi$6k/#a2׷>R"6#$~|VA]ik+sm Meep=[;* G5}&8$M;/7i]Mt[K%a{y=QlȂ?;` B EQEQEQEQEQEQEW륷 zywmC@+G-;?_W_9Ͼ}g2W}G=f]̿Q~AX_=+BZ((S'Tu#6ӵx!sX]5C;mV!h[ *8;$_?"[L@חeƳ\]N؂D Bp*ze}@aDqJ˂56}LFcԳ1$MiQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWש|Fn_DM^[@lDz[B5[6?VЍ[oS_7MEPxF׶ZֶVOg{j?kiEv]\ŗjPUujUuhqҊJ(5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊? ȳׅZ!k 4RD5L-4n%l8;Nx>I.-|I!,vKٔ0!JSQB=ȇy(!k|\w#i#G,OT#T(€G3oMi\ZkpvhqmpXВ 3++5VN2RWAERQEQEQEQEQEQEQEQEW|G_[55W륷 x&JO?}]>o#D^igu2G* >eTbBZ+;/ igҾ(((((((((((((((((((= ?K7t"j+f=jٱeh5} :q:j(<3 ֵ|w#?QЭk[N(]=J,WOmҀ6bT@P:Q@5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊Oo& ?+^ּgOd&R._o^M}X|Xޞ?Oz|-j8 .b;NJ㙬~@Cm+ F!DK85_*Uѽ[K)aDBwʞce8Wy,!lG&[C$iiz7,w"Z;H? Ǿ q{ Zǎg!+LXOe5~⯄xx;^KwE˂:s Cd־Uw~$>$Zy'L|7:_dB$j 0>|D+P־'ᵏo4qy 4BG`u2BEI#+3Sy6 qg+_sTXI*kE&C6#Q/ſ|>_9)6O{mC3$|3  3]' ^o-3hтXNDTH|`7>|CF| Gټ%i}Oݪ^:F–$/3LD?Q[ĚK%'o%d($$96>-񕟈rdbSib$kmw <5A Qmت2 I2 ի>xQ|[{M>km;VvZltrDVmoMf%4WxA%^>uĚd߇u ]SN.-PH%$0<ԯ<x/Z䚽\֬֝qv CI){E%̅QEQEQEQEW륷 zywmC@+G-;?_W_9Ͼ}g2W}G=f]̿Q>AX_=+BZ((((((((((((((((((((~#7O&-R ? ٱekl~MCNo(*mGBm;+ZӺWOmҹ.t (P5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊Z9%|YJ]ꪣ$/]RMGIԤ>X.h])W` ~xn4ºTR(d}>zbZIgH;,tRI=I&*i]ơi~"]iGT"2.6#pF9N[quMtU *(U.rT3rm D*T6Z..TyWir/|۵;E dRّO): iP~>>UK )v$p:ۤRV ( haE7dpXzx5%QEQEQEQEQEQEQEW|G_[55W륷 x&JO?}]>o#D^igu2}G*X>A^,YKO?e|e!-?zWєQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWש|Fn_DM^[@mYDz[B5[V_VЍZoS_7MEPxF׶ZֶVOg{j?kiEv]\ŗjPUujUuhqҊJ(5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊? xgI'|-R1/H_"+)A\+s-+]Ȏ ,A:ߎ3 kZɦh<d 0Ĭ Rk(I7&qqb?r|!7CCdžoikS↕xCmN׾ kYT‹M(RQ gvZeՅ2%1ʯ$ "A*JBT85wNZfkV1j5s\[JC WBUF85~ 4rq ^%_.5U2|nϫ1=Mz_h@ȏuy*yPXxU$qTGJnpRgGEr|z A"|n(둕`ddr+4 ( +z%bd4-no-mze1$m"C '9)炴+Qt-p:ƛ7;*r(~<+ċ{xMuK\>JId۸ z((z\_^ޤϪrTVΑ+JG" `:+Ş<;oz_$a%㍦YT1ddE@Q@Q@ywmC^^]E}o[P7NdAz9iYL6v@_'"Ջ?Uŗ_FWv_G}@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@{A5ymzo?DV՗{/տ#Xeoku5} tQEyWg{j?kiEdF׶ZֶPoeں{nYvۥlWVEWV'(t?5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊mVޞ?-7_xSVїNF:O "83I]{ ~Z8\}< /4Q<`fg;w.ztȭ!+&jBLtm^|ZU*pX` Rww6[ZX^j"4fXFq$|q\JEE1>2S'Ow|9-jC[,ƫo@f`fUR\~k~|Cwv֡lssi2UH Măi$WIvیcbSh<|_e?G6'BiSO"M"+}^h.o"߻g{n;vu-B_)[jsʤ6qմP淌o~0U&MMkky2DO$en&?UҼsK;yxj OC׵\28$W;Y;^T:@ x°VZ7|.5Y5dĖq59-p'b~2-m>Ox6? fSoṸhƊ.XX%dTEs76y{Fcn8Ǧ+ǀ4Cd. [Iӥy4{lnȅICDe<  |+R/IxG_`_\i햙r't򦉣] #dFXT?7> u_RC_VxhE Y!0\3ð?y#>{ xf)#y-ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊д;z^}%D$x:FU_){;|_ +c{,mַha ߮ߝ'xcw?0MBE<9swuy'uTm4ETEPEQEQEQEQEQEQEQEQEQEW|G_[55W륷 x&JO?}]>o#D^igu2}G*X>A^,YKO?e|e!-?zWєQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWש|Fn_DM^[@mYDz[B5[V_VЍZoS_7MEPxF׶ZֶVOg{j?kiEv]\ŗjPUujUuhqҊJ(5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊 rVؒN%^8yk^z_ QBڦrU 2z+~-^u ))xe:Zd ş5Eo}f'K`m\[Gq,.29^@FM~ 7yy[DPPiKrn#-#yJH*ֲֶͧdݵvK'[ڝ-;Y[% 3 (9E}x Y;j:zb_x_>[aqWRMSƞ$мK ֋vu5O$\@a* 6|s ׇŇ|97cD$BFFκſ NUXAº/8&oWǓwܵ:4hI2$`hPN"85&YOJ}jB5#>Y+*ʥ%)K'_|.h |xωmloGt6S|k̓+A_ |g|-.$UΒ8jE8< >:?~\x‰ E̐j_ma֦MB.1"1*7E~lDωƓ^/fyX_k&Vn$7#Q'r8VVeҏ}E|msmc`dUzu2}G*X_=+BZ((((((((((((((((((((~#7O&-R ? ڲek~MCNo(*mGBm;+ZӺWOmҹ.t (P5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊54[0z5ժq?YcQ_e;㿆VIl |OgkN0 #X }?b:X#>,PO("Ciq{) n"|%j&Z÷RH ݤՄnT][nWmml-l$HB(Ptv88YcSrEF7,#Fya>>H([⟂ |g_k> Z]CQuw6aM,m UY))G(Xf_xat/'mn 5(. юuif DDzQ^W>6<3iul6+wHF-3Y>ki $V֚}m,,.tRL6v (X'I$A(m~ 7hlQ[uHmB ђKsgë2!bsI~_ǧ#h6Ϥؾg5Ee,< CHc@{W_&&vipA4Fus HBL$X7]%PēKo{wmpq$ s̒(1"<|͟O<_ ͯeSj\ av͸2UHyǴQEQEQEQEQEW륷 zywmC@+G-;?_W_9Ͼ}g2W}G=f̟QV.OEW _G}_9KO?eQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEyo?D_W[V_VЍb՗{/տ#@i4QE^;Vu?^ڏkZwQ@jW1eں{n]Z]ZtҊ5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊? ȳ׍Zݬ/ ȳ׍Zݠ(((((((((((?tСQ."]-(h~}g2W}G=y&JO?}] ۯ?U|bBZ+;/ igҾ(((((((((((((((((((= ?K7t"j+j=Zڲeh5} :q:j(<3 ֵ|w#?QЭk[N(]=J,WOmҀ6bT@P:Q@5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊 <msosXb]+ g=p|q "(OGt"\s8piG',}&16℩< 4jzk3pֈYIiE|Ñ^ijPyZ 2XL.S$ P 1C! LJt?wyGgw||-].5S5K{HnRyH견bE,5gI L=iu{j#:vdD;M|/[>O#l4h4awv̷/mf8ͽ ߏzF+^CR'u#60> ]HV9U| |DρeI<;.b6 2[4E4@_~7lQs 챬ϝyY\gm};ƋZw|BKw%ih6nn#.Pp!r "|SXm+]4Eau66IjP\I#NєlY.(F4};QypZXc 2ZmfvwE,W ]Wvoލe:lVZxяWʨ|~=k l#T񽯍.fŬ y4۶Y"oԛ!HJƙ.y>=LoNq}o?rsv7M|' 2_m&6QX-˩p^FP#=?!EPEPEP^]E}o[Pרח|G_[4 rӾ+m>uZw%z ݟsm|b>dUze!-?zWѕ_FPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPFn_DM^[^A5ymeo-mYDz[B4jqMC@5Q@U?^ڏkZwQY>;Vuvۥs]@1UժQUՠ J((5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊w!/|4ak:h,p4[(wX*)F kwujxѰ>&ΧXl + ȳ׍Zݠ?O(W?m ǜ%xQ`G%͜6~Q@7|ŭ#\go9-(k/#)#Wy W4Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ytgE40 Ҙn1}Ei@?;mHuImanitdsߴz~ߍxŧݟsm|b>dUze!-?zWѕ_FPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPFn_DM^[^A5ymeo-mYDz[B4jqMC@5Q@U?^ڏkZwQY>;Vuvۥs]@1UժQUՠ J((5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊 Hx[JV۳3JI$m ^\%a<w1; ~5k/ Lxr3zS###X qu *MYۧd) ^[I^IIQh<ʻ`3ƅUMn /!iCPHdl푔rEywx^1ɺեo4 ]>Hinu{@q(&PY@#iH# jVO&Y[A{gUQ{A I%|]B\֚W]ҬuRJw0pr"qax7⎃Ix4 AusGٌ .@ * LmρsZ]14({p"V* U#d"1[ڗ_hmwZ^jW6,?-d YH yʊZN5B[}C:szڧ|>3OuB0];Gy2Fy:j+>6bx-0x$C۾Ϳ۷9AZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊o" $~r_, ~zW֞1/qdK]6r'j&a~Tp3}|9uz9Ӄk-jFIzqSm׏ύs)wǕ/\?bܾG~&'k+Sl{=3ږ-6QD<2:,!m7MG]wZΪKVG+FiYGT`1" }_qMh*O%МBtrJ+5ͣI.^"⟇./ o}e>nMQ.u$r@#N6eA?V|<ڞfTWS&o,-ĞhR&"q_WϟV|.~x~:<Ϋ$Ӡ eH-nn$7m _%o'/~8<[ĿfL7;; P1k >$ū;-%{EyBI/-m.X[̨䃸*,?#:u;^ 贠 ~5v^zͺ>A^]̟Q@OQC#5~𭭴pI\( 5 ,ٙt#"!Aw NH?4 hF}fg6$ #$\.Ak(((((((((((((((((~ŭ/_F-S_7өh(ʼw#?QЭk[N+'3 ֵ;{.tb˵t(f*J*8E5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊/ AۯsZ<?_7)-2W _0v[]_~b|Z5Km|}OQx?"+%?K`O?eDAPAu(QS#B\Q_=Ķ)@P?hWWE-1_?0'OGKgLt qE(QS#B\Q_=Ķ)@P?hWWE-1_?0'OGKgLt qE(QS#B\Q_=Ķ)@P?hW?/JX+jt Ol4-˼e+ߞ_k[pm%J]ϭ;~5v^zhͺ>A^]̟Q@ow]D#k6 ȸ< ׮~q.m嶓ʌa]=_YGmqj7n{s&74f {ᱟJ&]j _"T˟۝͛r3 rO_5+gx/!T7qe9ߜc>lRt^s)ä*Hj?tVpA"w]QB _:?֮j!;Vuvۥs]@1UժQUՠ J((5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊HEw-d ˾628wBhQriw R#HAzWt?? ⿱xXRQ^ƞ?qSVJ)L%M;E;41˧M,?*@hϥUHS/֏suƙVX8_'/̏ VϥƚjI9 lch dfl$9| ƿY?3w,w$ȌqQΣkt]W0s_DPĖ7zb g2"}ÈxTl(J;?ŘWmVhV.`0ҩ]o$^Ojݴx..-:kiZP\K4l\_h漯U/MRF"/,.%pmBr2  y5wº%Ťzo ,VwTeiZm`YxP+1fI#D̉" OEoi,*ƋJi9J)igK_zM%mp|*qvI~JnO2?6K~#9,$@8׏>|Hc=ntdE7b \ t0־Tc_rC(%?)pHEaw 3tqi~'`SeԷ\%9ϩl] ҅Tϧ3^ז}n⣓POKߕIB7}z({zZ3׼N5#Z>mRYەI۸|94? k$ծ[_šX67$dòĒڻ wZi:޽='J;hTlkXؤK ۜ^m?C< Þ!ӡ획ٱ-r]1B*nɫ#PM{J7ja+;9w}/uSz&ݓ䮭֕zy4W߇ tkm't(o:Ӵ{y{h֨14bvhUB ŠY8^ jRhT>ac6}2i{řPZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊OU\ *]c=OOj2?_¿"~#]Y|S]ՌOh\HR\%^.W7QhTQJ#g~JkK?f~1[)f`udſ0j]x _a?c>} ү^ꚶW,7Sc;KgFR̮]S՗3^m8nQ)C0@a:Q^ሹ8ۓ@f^T= %R@ IEQE)%f9'M$tPx(Ve9RAu ( ( ( ڼ+]5?/ڼ+]5?/NP*xM#zP>׌Z}G={?oƼb_W_93nOEW_'"EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP[V_VЍb՗{/տ#@i4QE^;Vu?^ڏkZwQ@jW1eں{n]Z]ZtҊ5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FYzߋҀ3袊-| ZKhd־h׺F]LI#b''Jt/_];bc/8]n~G??%:Jt/_]_ۙO7 >9@~)пGSJ(s2R0Ϩ+Jt/_G ?_#ҿƺ (̿"?p? # zW?%:񮂊?3//'DB4N=+k'K7 >9@~)пGSJ(s2R0Ϩ+Jt/_G ?_#ҿƺ (̿"?p? # zW?%:񮂊?3//'DB4N=+k'K7 >9@~)пGSJ(s2R0Ϩ+Jt/_G ?_#ҿƺ (̿"?p? # zWc^ŗG:N϶(T]Y$y&|NeGYIog&l֖'J ?$g?{?qVAc2Ms<[Pnh-?nQ@eg_?k5EayƏ3[5>s<[Pnh-?nQ@eg_?k5EayƏ3[5>s<[Pnh-?nQ@eg_?k5EayƏ3[5>s<[Pnh-?nQ@eg_?k5EayƏ3[5>s<[Pnh-?nQ@eg_?k5EayƏ3[5>s<[P.֭f(V63槢 ku5} tQEyWg{j?kiEdF׶ZֶPoeں{nYvۥlWVEWV'(t?5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ~_FU.p @Uϰ\z\z€)W>q >q E\ǪG(ǪG(s}} tUϰ\z\z€)W>q >q E\ǪG(ǪG(s}} tUϰ\z\z€)W>q >q E\ǪG(ǪG(s}} tUϰ\z\z€)W>q >q E\ǪG(ǪG(s}} tUϰ\z\z€)W>q >q E\ǪG(ǪG(s}} tUϰ\z\z€)W>q >q E\ǪG(ǪG(s}} tUϰ\z\z€)W>q >q E\ǪG(ǪG(s}} tUϰ\z\z€)W>q >q E\ǪG(ǪG(s}} tUϰ\z\z€)W>q >q E\ǪG(ǪG(s}} tUϰ\z\z€)W>q >q MCWq 39PI?PQ@U?^ڏkZwQY>;Vuvۥs]@1UժQUՠ J((5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (<3 ֵ|w#?QЭk[N(]=J,WOmҀ6bT@P:Q@5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& (+\Ej2r@.TR*c<><]\O 톝uK1[)k8Nߗ}6F7|ǃfLUfN޶2QjxQEQOtpmC.Ȃ h9+ĽEVY`)eg?ʢ,}%UF+8ݝv++;8` EAE฿Mo-K&38$8^w㯊? k߇u|zqi YLd5ԌAa#<͸ϠU9p.]Bg,M ѓ >(*mGBm;+ZӺWOmҹ.t (P5>ZN=iIҳgzFpEv} r7y']_׽W9״ x(((JoZYc? J=L{N|A:ƟN\-m'Tؖ6쮳_ں&c F/!Z-Xqjs[7֡&W?zs\`hƥh&%9 h;@]À][a]cEb[T];MuseFN1}̀~nOa_uWe8m'mukψf,,Ԭ{4v< $xmk(x%.}nj.Ojwڔ-tuf$klF,@'$WrGѴ3*nYXeYOx x;IKдHuh(6*A#i5~)tVJӮ-:uo{h,VrX# ?3pRqTiAoG=KI5H2G.9):! Yj%LJnIT[eb  8),;6K};L#`iaT,A+A 4s)PIu<ѳ=UqA`:יZfx2M Ny(YTaIqRWw;Mk=: X(cXмUeǹ95SM+#LH4Ų9I"Wp(nG[iãTlr%/4j'8̠ ouOZqEwwk:A"$3$`^{V\m)#O&O\ع67OlATW T8 Pk>4<hz\Z&Ln/lRl`c*ƗTn;!G]ha,QI*8FCmר\k:] ^6@:m4?|9&"JmUվ61]qקG-kkY-"I<"B"C;*S+ͩpoӕa9\y@̜!WEhp5CZFm m܉<x?6gKsiWݕ%ͭđ+mo-,ihK]ou|QO}4 bɢH4F ×14nUxƺ}otM`Y[x[K!6ʗK紉+ >B ag<5Gn.n%%9Ƞ<ެh=v6Ӌ2Dzlhĵ·>"е}z[aFl kSha9hҶv\\֖Ogs~O6ѝ圕ۿ"4-QnWRӭ.Qr'$󢈳"ITI#>ƘEqma&HK"Q!N<.mMiWU_)#$H`t0!z 5GSEliIj K'kg5cg0-N|/.R75-ſH [F͘ݽ6t|レMOJVj٦m+B 7su5Ox@|'w\݋2-> +V#@ Dav+,h^c^c 閒i.kk4]$1F*1Fd)'i۴gg;ak#8R:lfM6'H+⹟x Ac4:rq-̑ky).Hl<[OkH𽥭ܷb8L,+{$p3ct@0ԼoѴb-!HR:O}Ȓ y}"go wq^=𽧆-<)C;b|K$J x qZ-b')p(RPLO̩CQ*R˒˸y^7qwL$ZM[jEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPRR?BGw~nOEu)&']:ض(˼T ƫqU uA@yp:ZN}>1%dYc*{֥a֒*܂HUu|: ? ??v8Kfh|CS+ũ[T͍}eȉq-͹s&X)݃ /wr:Eo;MJK(4M48B$aOLOaһK!&$1߸:D0X!$r8,{t/E~;Vdg{j?livvCt++PUujUuhqҊJ(5>Y:n0@5_ rր%G蟍%՞ssP7=za%fHt\e{U'Lv )>)h?sɕLޢ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@n|T (SA+Lҏ@?M2>-|R=N#j_ h_sur/\w?Xl=j?EK;J|]JJnܖdgrFG"q]-1Giwb]SVP'UG@OQFII9$Qcemq]U8l'J m1[p]U񊲣IEP(Y֮0f2L=YD颠> nm3+ Dֹ@wqeXX{Wg՛%y̚wUm7ڽjM'ozӽ?pپfW=(fQ]p8zPGowҏ@oپgJ?pپfW=(fQ]p8zPGowҏ@oپgJ?pپfW=(fQ]p8zPGowҏ@oپgJ?pپfW=(fQ]p8zPGowҏ@oپgJ?pپfW=(fQ]p8zPGowҏ@oپgJ?pپfW=(fQ]p8zPGowҏ@oپgJ?pپfW=(fQ]p8zPGowҏ@oپgJ?pپfW=(fQ]p8zPGowҏ@oپgJ?pپfW=(fQ]p8zPGowҏ@o]Qf}ڏWSaGهra>zQa@}u?f}zP-j>]Oهf}ڏWSaGهra>zQa@}u?f}zP-j>]Oهf}ڏWSaGهra>zQa@}u?f}zP-j>]Oهf}ڏWSaGهra>zQa@}u?f}zP-j>]Oهf}ڏWSaGهra>zQa@}u?f}zP-j>]Oهf}ڏWSaGهra>zQa@}u?f}zP-j>]Oهf}ڏWSaGهra>zQa@}u?f}zP-j>]Oهf}ڏWSaGهra>zQa@}u?f}zP-j>]Oهf}ڏWSaGهra>zQa@}u?f}zP-jwGtfj=(^*ُJO1vZNS,X LU^*ԠPEQE((FV+UҴLM1 )yT(U}(^FjJP{o}=(^FjJP{o}=(^FjJP{o}=(^FjJP{o}=(^FjJP{o}=(^FjJP{o}=(^FjJP{o}=(^FjJP{o}=(^FjJP{o}(^Fj"U]F#]AO R4-?PEPEP((((( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0( Z(0)p( ( ( ( ( (?OSCAR-code-v1.3.1/Building/MacOS/README.rtfd/Oscar-mac-4-click.jpg000066400000000000000000001175411417327530600236270ustar00rootroot00000000000000JFIFHHExifMM*JR(iZHHn8Photoshop 3.08BIM8BIM%ُ B~n" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC C  / ?(((2(p@u>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pn@p((((((-M-QK2Pj35^)7yRy2|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>47oG@[wD€4pozTZ *P aj j}Q@Q@(1Pf꜒b$Abpqg0\Liֳݬd*"T!^FEb 'sSoBS\Gko9cLvKL-B|G@"Wþ(զv'OFOz G$A'q#W|0-&yY.]qt$UqAW0:y/C -kI۪n7ۙ'uq>"O_=MRTkǫu"(%%bF8_eԐ+Ʃ nŀg^6ӎks 2UVVWۙeWdxx1xg ySڿ Կ/Z? Կ/Z~_+T ZYdhkŒGa 0Hs,x.iFa>U'dm.O Կ/Z? Կ/Zޗ4B-oc[r*HqAW޻x81P-S=L0e +t_j_ -j_ -si>v$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ީ6ґր:H5/KzH5/Kzt][B."he|uYTlQH.1bXn2rO^4sRTkǨRTkǫu"DŽ5k"([{xA<ə>}H㎝hCz=/[lhm^)+=Ĉ^XtϢ Կ/Z? Կ/ZEiZ n]k~#-TZȑp\GqcCn] *{N!^hwYNaw/9Lo4rGc{$_=G$_=_?}>OZw ?K:յznKˋhas#*#$V@F&/ZM惬IW .") D 0E{$_=G$_=^Cg;rOE]CǡJymb% 0I#τhGqi3#{[aCڀ= KRE KRE"⟃#n೩۶X1xNnw3xwzޓYkn'~Hc}e,gz$_=G$_=^5|Nnu5Cka,חWL73mTٝ'ҷ%45.=6wx-Bh8'IRTkǨRTkǫ9Iu92:U:}RTkǨRTkǫOi KRE KREw>}ހ:/H5/KzH5/Kzߴz Կ/Z? Կ/Z~G}RTkǨRTkǫOi KRE KREw>}ހ:/H5/KzH5/Kzߴz Կ/Z? Կ/Z~G}RTkǨRTkǫOi KRE KREw>}ހ:/H5/KzH5/Kzߴz Կ/Z? Կ/Z~G}RTkǨRTkǫOiKREj#arEk>}ހ:;z_^dmǫOix y-F{E%mD 3ΪI d+`09V壺lsFѾ~ ݼ I*(Gի.93W}@(JWsS15WYK;.es>0LJu._ z<9״ /U:\@_x^tHܥŜp~MXb`g<NRu͵ެ`݈/6o 34H0 }I Z Szu`}b ߢs(82uayr[-RnVZIkI ^H]5ȥΨ-%%h%D<67X3} Z SzuOOg 8NJO)K$։'nL RIl;>ľm626+mqi+şv Szt©ֿ=mn!>oF\of_?! t2qpmZ OR𽎡j{PZ8m߂@bdbGa^zT_s]u[.<. Cge2 <VW{[lG}Szt©ֿ=m=COiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z Os~-t9%$f8UifD'W©ֿ=m亂w ]6sC &Ό=7D>-34pKXj@#VuGXԵ+NX;iC*e *ȧYr0 ^ a=to/$֍[1Y4OOW J1#!njBI>e-x:O{}Bw6KkyvG8/ݸY] mfXx{F'=NmP:wMpVi&> b`Wu[.U:\R37ÿ< m/M-ZO7p񰿕^ܭĭ2[H1dpIud>t7my4rI="H&Szt©ֿ=mwPi g4C/!dg 7ܾEzGoj uGnԐ2P\ wѐYdT_s]u[.>7{j>.7ƚ?d&-bqtBc~FyS!6z%kojZM݌se5㐣.fSzt©ֿ=mþ΍3Zl$@Դ{3\[o+lDH 'hEyUÏyKm, ״V>mho#ʆ+![fu;vb1տFT_s]u[.>O"<;$0Mh&O ?`G#Q:bv#O uX ]EYA=SXU=CPۡi·kN߶T_s]u[.8p^ _c Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}޻gúo?**k.K\gÚo{E8eij\CV(ڭ t?n\RBc@w sr5r/_p~6Կ_%LJ5/A5eQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEGgχtA|ys7Ezt6r6OҺ{fPFYКezRWҝ@5>]VtNJВ ;RQw%~zKM}s_x9״ h((((((((((((((((M;L}cķXZ?3QYӧ:PnEZRI-#nnm{RcI"(,p6!Ķ8i}3Cijl;thDS$Sÿ >$mג ܓJ$k,e)h,-f1 kUxw qX˳WO olZamBOn߁Gfx52=#gVߵdl\z#$.?h5s~_BtH`Oָc35LJ3f13?;]iWxjsH(}acV.>?$f_z e&*Wq_זWuDpL9`j;{IWh6(GpF5X sdm%[' 6qtc/K7۪+?o I$ _g}2n>JƟJm͞/p2Lgʣ9UȯxG0˓8C~q[Ng=r(>((((((((((((((((+o>|9״_k4A-ҺS>ӠhJ͆c +O->?5>ZN=iIҳg~uBh&i /6&mpB zqCyG6佷L钝'hd??'C˦~XS~sfpk{_?RG$24RGBUXpAA\ZOդ иx䍊:V "Ch/xxN&lK"^oQu@.>^Nݲ,kԴ{Iv>z_ϜAb2Wz]U_  u)b $,8~ _r8"H?g/xJ>MsL1=䏀5ph'`hk˺ SIe^fOQ_Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@|uS^"ů< ;E?uևW`xt ҏfZQ +O->?5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((Yb'gXK; 2I'k_[>4ii6ۡ8b?!<Wܟό&-dy1v1_uNYEcޟ'g麐)/^ªDM~|}ïYi2 Z7HC1e_=" 2[iڔ<窙jGpkb5|=>)~I~o:%QrZKn|d̲Kߎ]>{5xWĚEImZ|*z:69J&+8ԃTΜJn;o'ЬiGҀ,>5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((|Bڏ,tܘt,{.k|^V:o_u yumaWaV-JE_-.-s\EfKh}i}+[AklxP$$4hҾ*<[y߆ooEMu~Xv v j_׋o,ϕ6å AlhCȣa5/췡å+ԕWv"#s=(SLNgYs>h 욊m׭~xh>Xʤݧ&Skm;[mÞ1bBibֳ8+yH9pG%^iqyls̏q,oT㓚~͉&ݗTB Mpsۊ5yn;h*iJ-$;%t>4Tf8Hѓqdovk~-x+NB-Nk{/S10YDao$9@.^oa3lDQ'h3ʅOK.]k3gS-쵻Zi{Ǫ^G~>;ѫ,\}IHI6hȭ^aR+Jk M%XV CFVu0T -8ߦ]/uy,µZT[Jϭm}=~|qokO/WםuFn.FA޼#Q[M>bzϬj25ܶӺno)oͭi^nϬh?j'Iog֠akEHTFx #Hkce$KGu`h;p?z 'B0{^oyt|o-xfu8-ytD0_AC2E~!+7fQE|0QEQEQEQEQEQEQEQEQEQEQEQEQEQEWoLh_dWoLh@tCO+@ J͂@VLZ}5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((c?7d2sux٬kZR\RZt`e"iwIMUu8Ӗpp;7:?.j 4yTO94cV8;W~cU0e^=X_νyZxHxJk G!ȃqfƚq)ΧUM{v[Q٦$jM٦VڮƷ?JWd&mM9mlvEy7oR|+~?F7vs4$2ZM$bG ƒ w45hA>n]O.wx.o N6:PJ84Z# J;"|= OEm;QXFNp;-J1>gR1dZ{uNZu*Ɯ Ekw+j6ZŸ c"dÔu#ϸ9^&5ݼQ,0L=+S]&MCEJg"vPS`8ێخ'VQiHoE5kD\ ,vFZRGېBw_'S[*r哾kocRŒyY}/_Ƌx'ZtoTtmb;40Vӕyfmr(ǔ|3Ǟy&710EyXJ"5D E8UPp⾔MS–2VK/⹵3]\\2,k#O$95[||Ioizn0IpxNsCF'X1N7wOKw}.IsWnxKWcž%c_|4Qۿ,DI굹q 'tGՆ5]i7\1 ?B֝mo %i$•x_NFuC?F4I|VIGV|;Ⓤ9ڵyefV|S]`~O<ܤ}gVXYh 8L-'1rd&gۀ Wɕ"q{z?g`'VW~Sv?<=lRO9o& W~yLRhm_ϼ$(4((((((((((((((+ȷ״_+ȷ״_Z :W!t ҏfZQ +O->?5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((t~.ib^1B7G^5_a{o(w}hr:Ilc.Ǖ!XT_5*6omzKULZiM8մt"k$VYi[,<)<''q(Y6}q"kG^kx"/rfvd- 19SCkG!W/+37^(Nprwl}Mz]y<r_U-'?6vqLj,n麭Dj3"K+He/g -s? [E|U.3`vZt-dgz%>zj)m>$x؟]Ej*3r-vȴQWKxSJ` 9T7Iq$;Iس1 @P^4EJ7Sl7z ӻ.XwM4`MFY[5Emk) `B>YӮk?-ORFԙcmw%6T1GFȠ Oi[zb=I#G`*x 5,;N3>ȼ)6 VX }~x[l.nnFcfQ w'co ZκorC# &YGdIW=YkҾ *YX."k;q*P+*p~bB&v5~_x=Ag,,gs&Xs^_i_?@֪Zd3n'3Ix^,$Zω46o1Y7;IL0R Fj}K6. 7TUu$Io<ʺ|rHJfX1>q^a_Ҝ=t2*uO~&ƾkd= gk쁢Ӭtn?k.v"ku˭vfl0!Ez& rfe}7?y2zP^_(>((((((((((((((+ȷ״_+ȷ״_Z :W!t ҏfZQ +O->?5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((񏏾 4mmPFFIgz$Lꣻ__?~?M%[t}\݉Q:<<{U8K.? ;$o3e?<6Lf<䛵pNY>C{mz7¿7 ak7Ijq}F3e3_gY/TX5|7gyj^s Ԟ[^LbxףBۺn=^;yXVw4CtmowD"wUa+GZmK_HP?<.-JМe(_\iZύ/,X.#{=̬{OxXn F h5X\K7b q\*%h5lam ` E+N(W哝C+J?4cl=@_KWh~tzo'}4tP1g]<F߲i9Y8>@/k=?i ks>qieiŧ5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((+ /On|9)mn[(;wRFARX֤$'OJTk+Jy(Ƴrꬿrҹhi~'Is㏇)y'ĶKyZW~t8e{)P媷]u:O2q\^._.^ ]gԙ`2g#u1ԡ4/͐ߥn6jUwv()`{? ?嚮勿͖n"s ||oYn]TN_ʵ>v=K?M ̓ ) o4QO`74rK#I%$[GMdi%r/ıGc?31M~4>!;Gˇ@͵c: `1o |^r^NiŸh-(迖+n#>x({ 6Pu'Q_ٮ#1ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((((((((+#\4_i7s)2 ; u誄 )A٢g.WO>)m<^?ԅchI'PVoo3'ڡ ;z ]h˸2å ֨Y9^%н7FVRUpA|Rav2b+Rt=dm4K1sM Msv$ hdI:utizR!5V~NoĂ8M@V``F<3p__ hiiGz.t>HФ#=+xK _6zz7~Oxw:O<k,%}CQ[I<!66znr}7O ЅOwh02'6{>-+5:_r[9 C^ ӆ>>߂1;e&J(Vu&Tmz)R(*tQ[%AEVfEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_E3E}_E3Ez֟W_iҹ ?ӥt0V}+6 ҏYZ}1i5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((((((((((((((((((((((((((+ȷ״_+ȷ״_Z :W!t ҏfZQ +O->?5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((* -乜%, <1>&#o*Qe9wE^/9Y:݅${@tiL.qe2Pp-#$%>*~E[xEqm;p˴:K{3/W)iv74Rymdwhԁ zZo75Bk-ܼ[>F9}z::ȡІVt"EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_E3E}_E3Ez֟W_iҹ ?ӥt0V}+6 ҏYZ}1i5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((('?h*^c|%v7\̎=R6~tw0吨(rE  QEQEQEW7?A5W7?A4siF)A 0~XIg|ŏgFkQ'$Wx?5>ZN=iIҳgzrC]}J5<_-/k_-/k((((+_)6~.>iږM%SIa :~(jzAZ/m[ۗdP1bORM~Zy:އo^ ץKelXD% ȪQycqk!#ii&LIa's79&Ioo+V?fk74V/=i-wK4^}ddaȶIlWƟ xy,bKZpT}[((((o+'ko+'hC^ {[j+;c :vjcxvZmFQK$oQ ]O{ /Ȭ m{זhwKxPSj]ZO ϓG#]hqxjjWo*B$/$|і9H⚛o*t5QQ2+{׎[k:3@jp HqBH\ l, Z<}M0spT*< r#8Kf_14ya48ЍuUѤ׵t%Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@|o_AE|o_AZA]}J4NZQ+J>eiŧ5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((#xi [:]D8P,[kvoRMsڽk>:5[xecog Kp<=mWk%ƫKS(( ( ( ( a"^&Ja"^&:3׉JQ sg'5퇥x<u5cv Q4q!Y/e*J*68>k5S6w5F[iA-QE+VڎITflCZŁGG&JmcrgG#F^׿W7#F^׿PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_E3E}_E3Ez֟W_iҹ ?ӥt0V}+6 ҏYZ}1i5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((OMi@P)5ѭc+vPkw%8)_huqK~zٱxŌ[mo+ L㌚,-- EomC`+??ÿ~1-4O xrkBktncmUc\Gݠ)ɯ& ( ( ( ( <^ԕFIkIME ܬ0A(tb yw_3اWfh.,X,5.-L`r>e:9~x:iY4 1;&k| K[JVŵ\êj$M y$Hxq:`+|(-^_7%=su |He9'C3@_+_.Q  >c/NgxOY֢Y~ضR[%'VYK2)&o;\+_.Q  | .qmz 2Z + G֋ʵw]*ݠ((((((((((((((((((((((((7/zg{E"7/zg{E?ӥrA]}J`(VlJb5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((w ¿ mcųo{ylv7zS135M@|* IduT0:ڊjr'jש1ЃЌWQ};^iw/ih/ p~ZFF{Wʺsx_PYԵh;]]]٘lo{[)X!)@@q\d K↳usHˍ[Ht Ur-.pR& V:*|C֡kY SV};@apjZy,x$9>o|N4Of5n|=s[6ͯĊȩ#3.YAPH˫{+YnEm,3_~y;Q|nzᛛK;yl`Koi6fX\J.H j|_/-αkcg>ng-e&5f*361p>w#<-ZMq8$ -K ItV(\Cr2(&.WxMMĬ5]ZR#km:KXbڥZ 㡯| ~k>;h΍v&P%};ĐI s7e%Sg6e%+uO xcKj.t*k<P6yD Q@Q@Q@Q@ 4t EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_E3E}_E3Ez֟W_iҹ ?ӥt0V}+6 ҏYZ}1i5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((((((((((((4Pi (Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@|o_AE|o_AZA]}J4NZQ+J>eiŧ5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((((((((((((4Pi (Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@|o_AE|o_AZA]}J4NZQ+J>eiŧ5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((G1(lWWoJ.Ziʐ>͹ g, w CcK4imb1/,)/;@hUW KM;C\iwq,l!$ؘ+]QEQEQEQEU-GR{=Z;;d2M</VfbԚ3W}]'Η7bF6EieE@{1k&5)9w^/$rjaUB-J[?ۋq xE9yҶx{` Vf Dn1qlߕ}L|8_x )/0Is ^QeKOţg>ӵ/)]:qugڛw9#ۗ5 6,@C㒠WpuSu榾_2}RjǡfVnZrE}NTQE|aEPEPEPEPY7ޝaiwrsެ#>QP=q֯HtRT7sz9V8LhIk>oM\pv'+|Rbz緿orWׁdr cUt'O9]SG (l(((Ue?k4[NInTU_Y_XVWPCЃhiI=(Š((((((((((((7/zg{E"7/zg{E?ӥrA]}J`(VlJb5>ZN=iIҳgzrC]}J5<_-/k_-/k((((+K _ ־~m]Juܖݻt!^.\%]⿉ k"Thǚ3C\_v~ k#My{C8HZ'~KcGݰs@92hosKw8k..bԵŚTRR *Ύ eic8+[x##ۍGč?Z[hr!#]& M ec8cHaEHBªp%|jrho;ӼGJZ5Sݕd,m1ו= ZI"duKH/mFߊ5lz_"[}IXq^|nY__/ jhѴe5Q8*y2;PQEQEQEV~w icc \O)G`3@hWWuƋ#ᆗ1]yPpMg%| ԞFE֛57,da?%|e|.{Pխ Y_T L^hlyO1v6 DBo췢kkkO53 4-Ktl7{L]|Oo9?<[8mF</<|o?ƿ7Pƾ/z3.@2&UpMH6i:݃o-cTVU+ S}:)-wM/[hG][7JiY](ViKiQEiQEQ_wǯJ5: .+kcR6R%$2OS]-gP7Zq'OO*Gçдcq^#8zQNe~Q_Ɖ~X̽9A\<9aGg-|'W76'Ĺo-O5'fIeX"m J*vխʯ|=lvew:+Ӽ Xz6s;c?sߗ|6dS^3'Uz>!1Y5u_]G߱ȒF) u/M#Z>eӞ\}յ]ZJC :Z6y*ϣ<1X< _^(B(-yqom Y;@)?5loצzXKpTI|U2;V~cgM(2AmɠFǔᑽqϭ|^/5X&Y'z*ʾZK-m#}g(Ϭk}372yfw5R2{<}D_} [HY1G-m#}gkoΏ?&_r#+-v Љ]>ϤsHcfEG-MPzI?~KUx%Q_"}QEQEQEQEQEQEQEQEQEWoLh_dWoLh@tCO+@ J͂@VLZ}5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((fqj7ļxwHZXX>9v\Dds@w/A]UoR=KW-g4F?+9Ir#ޜߖjo +4ЮN<5uxlLNwRܰʢ ^9wòY%taIpGF%02T4W3?V_"ZOn-I!B2ē 2+mgi>.Dl:$$cX0hEPEPEP_޼okec"8j̯k?'+8{i$iYIKuMߍ'cɟCZk_ <9u./YM^۶ˈ.8\s.(0NGן"|׼O/W# ;x猱a#eՆA?'?ojOj|A1AsieP0eʷ˕`kO'_O `XVo%KMp7ŜN? gƥۻ)b츷ʬQMɵ0FOxsTox,e+.z:+.N݅_Fs+-3y, }h'ˆ(˗@1lv 泫jw/o-Ko6v1gm@$Eԃ+GtΔetO\vl.2[lڳբwm+Ie~MOUl&Cӵ1v uPW,P;q^a_x[ռaucI⋭%ƃlo}w$FrT.[5|=yGTk&{ *p:E>xº7+,ںXAn$S9)!UbXa=8Y_-U7nT N 9'u+|֊6~j?zc'3W췩K~M_1_9&f>|3.,vӼW^1MK%J_]C bAjߩQE8AEPa,^;R|G?T(W/0Plju.K-U5b` $2x3~o>բ5xWV |Yn|#.>ՐwW5x5|Zy+^yU0A`Mc#xݾ5jũ8&eMY^U߁oN<ܾ{c9M^Z SֆV6jn#x'=;~@fF k1m^}N܏ u2k%Zuk8$B;ӮoZeرzrOJ+1%mͫ6]ٸ%QQI'(:}? _.yC5Wݤx?WW~3X?|H&oHc d ]VbvzW-,I8 >c_}ާI%*b8 dp>lNpwG{=msգ? VM&3iˆv~at{忘|ȃn#sA[{t'CUbXwP&Uu<QXMnZ{Bگխ|ad_W߰Z^I~F>ۉ(p(((((((((+ȷ״_+ȷ״_Z :W!t ҏfZQ +O->?5>ZN=iIҳgzrC]}J5<_-/k_-/k((((:|?]|N֗w\ KM!E'?3ڼ( y{ jdj&XOk9zʺ!?KO>eު~ۧj/.?<g*)5*GW׼W~7-lC]Iln̑luXZڒ4XU*;SҀ?9 [?΁a~N`q}/#c-#ybO5WVSjoTOW~˿|qsO֚}}4js,6i ĎZO, 6lbH@Q@Q@Q@~;A<6KJqm>876LQ}|i'OE:ޘQ8Hhrz ݴ|C*Ωb+;A2}GxO7ȫaVWw5t˪%Sw>o;Xn#fXOHw3N0yZƭ^<4iSH4q&:ၶF\'16V)R$xSeObE~|&/5?|?y=5l4\XJdݽ`(V n8cTY%zuRkVgx/4%ҩMһwvTۿFߊtw^]6; ˩"Y2 [ ;Tv@')ZG|{'-PF"R/$R5v P hIKc@YA1-h9¶ u Q[ĐB#B">?o9&U-)5hZvy<,$anMS'|]}kzHt.o:hђgMno*ܪ 'ú?tNasa#Df=qg23_~nA0sT-湕,([oG25q[ZR|g}A_}LFMIR!ھ#_|ey6UqcS{6c%K ]~C1DpXsM_eݏMĿu`h}+{_^ _ áz-@f摇VbrI'Cþ|'hۨT0p16ob3ݢwl;=H+O (|.}cQEQEQEQEQEQEQEQEQEQEWoLh_dWoLh@tCO+@ J͂@VLZ}5>ZN=iIҳgzrC]}J5<_-/k_-/k(((+4kxLt B;[aYr+bdg%F;Y7 }/N?_Kj?ᓿe#iZfY%~K *8DUPQ@Q@Q@Q@Q@~ӟwag-$";mDn#c`榃o ^sUQ{KȘ[t?$VRqXkkΝ:Sc o\P_[G-K N+ul#H|QKNfiɻ*w3/c冒-~`cdtKnA^ WIl@mcDQ<#<X%x}mm'I}3NOm|<$U]׵[i6r=)͏0Wn0 4^O$<֧eiŧ5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((()".HʜkڮKú?a[ųE;E*cRR)pN )P7^[xڷr¶ZLX!ӵ?&j%%3h*겶vpH89vY#r6ӵs} ~Xj*W/ ^7y&R-"{*sVq=/hu|xKP%=_FH{XN*F3J˽@ZQ__|#Z֏㟇!񻮳d^xy&#Yyc n;{6V-2O}ɠW ,:_=P$0A7䁊t8D'qޝEQEQEQEQEPh@QE ( ( ( +o\n^u;KI5<H g<\c~0_e[<|I M$VSF" ؉9u5K=K'l.]ʸw>-x\6>:w}aOn Vlb D=?Me[ϊmiA>ŵ7ZDž3*20$@?A(){ ZtIxsY}.kkRV($,MseC>(~-msE>H/ž$W+$&>Y .~Gq,pȎ>UXT6z #|9CoCxM:ıiyt|"a$>DwRK4nV(c x7 gGz +CW-.;#$HY|P6`-+><|OԴQC>Եs:gԳXĵW_p\WUQEQEQEQEQEQEQEQEQEQE[?/W[?/Piu+ :PCiGҳ`(P@5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((i~cq][\F4N0+ <'׀> ZM+A_ޤHRG͞8]Sï-'7ƠŮƙa )S8#j6<;kІJ;8*@I05 x_WĚ= Uֱ\~UCVsJ~VqVt#[NҳOCm+#<1˷z@BHcZ((((((4Pi (Q@Q@Q@#Ҵu9Qi%n֗%fci&fummkg) pCh LhHF7( O %o84ildb4o/0"Uc((((((((((( "ޙ^"ȯ "ޙ^"=kO+\W_iҀ:+J>iGҀ,>5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((((((((((((4Pi ))iQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWoLh_dWoLh@tCO+@ J͂@VLZ}5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((((((((((((((((((((((((((+ȷ״_+ȷ״_Z :W!t ҏfZQ +O->?5>ZN=iIҳgzr7+ Cy/]_ߎS{K ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( .+j)7Ez :W%d ߂YV}(O5>Y:j~0]\]+|h|tNj_/k 'i_Z#Fee9((((((((((((((((((((((((((((((((((((((+So{E#FF 31u$K?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMxo ҏgħdo גgzxzJK N[daq4V~О%daqv*Ӈ㟉%NY8^猼_a6V5M,wpH7.T@9R/6impD0UVm1uPcdtcZEҺuiB*v eR/JZ(ZE* "t%fh9۫\g={\gK|Vhs@ĖQh=mɵJoGrfv#Һ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)J,k-eˋRҜ-`%[ڶE;TgGoա8+*NJdq⭢ЩS*RHzuQE((}*25! j2l0S1*nj O&6RlPG'=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?HFP&{`)vP!bmTj@Z/(((((((MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM Z(0)h ( ( ( (?OSCAR-code-v1.3.1/Building/MacOS/README.rtfd/Oscar-mac-5-drag.jpg000066400000000000000000001322311417327530600234510ustar00rootroot00000000000000JFIFHHExifMM*JR(iZHHn8Photoshop 3.08BIM8BIM%ُ B~n" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC C  / ?(((2(p@u>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pn@p((((((-M-QK2Pj35^)7yRy2|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>47oG@[wD€4pozTZ *P aj j}Q@Q@(1Pf꜒b$Abpqg0\Liֳݬd*"T!^FEb 'sSoBS\Gko9cLvKL-B|G@"Wþ(զv'OFOz G$A'q#W|0-&yY.]qt$UqAW0:y/C -kI۪n7ۙ'uq>"O_=MRTkǫu"(%%bF8_eԐ+Ʃ nŀg^6ӎks 2UVVWۙeWdxx1xg ySڿ Կ/Z? Կ/Z~_+T ZYdhkŒGa 0Hs,x.iFa>U'dm.O Կ/Z? Կ/Zޗ4B-oc[r*HqAW޻x81P-S=L0e +t_j_ -j_ -si>v$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ީ6ґր:H5/KzH5/Kzt][B."he|uYTlQH.1bXn2rO^4sRTkǨRTkǫu"DŽ5k"([{xA<ə>}H㎝hCz=/[lhm^)+=Ĉ^XtϢ Կ/Z? Կ/ZEiZ n]k~#-TZȑp\GqcCn] *{N!^hwYNaw/9Lo4rGc{$_=G$_=_?}>OZw ?K:յznKˋhas#*#$V@F&/ZM惬IW .") D 0E{$_=G$_=^Cg;rOE]CǡJymb% 0I#τhGqi3#{[aCڀ= KRE KRE"⟃#n೩۶X1xNnw3xwzޓYkn'~Hc}e,gz$_=G$_=^5|Nnu5Cka,חWL73mTٝ'ҷ%45.=6wx-Bh8'IRTkǨRTkǫ9Iu92:U:}RTkǨRTkǫOi KRE KREw>}ހ:/H5/KzH5/Kzߴz Կ/Z? Կ/Z~G}RTkǨRTkǫOi KRE KREw>}ހ:/H5/KzH5/Kzߴz Կ/Z? Կ/Z~G}RTkǨRTkǫOi KRE KREw>}ހ:/H5/KzH5/Kzߴz Կ/Z? Կ/Z~G}RTkǨRTkǫOiKREj#arEk>}ހ:;z_^dmǫOix y-F{E%mD 3ΪI d+`09V壺lsFѾ~ ݼ I*(Gի.93W}@(JWsS15WYK;.es>0LJu._ z<9״ /U:\@_x^tHܥŜp~MXb`g<NRu͵ެ`݈/6o 34H0 }I Z Szu`}b ߢs(82uayr[-RnVZIkI ^H]5ȥΨ-%%h%D<67X3} Z SzuOOg 8NJO)K$։'nL RIl;>ľm626+mqi+şv Szt©ֿ=mn!>oF\of_?! t2qpmZ OR𽎡j{PZ8m߂@bdbGa^zT_s]u[.<. Cge2 <VW{[lG}Szt©ֿ=m=COiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z OiNAo?% Z Os~-t9%$f8UifD'W©ֿ=m亂w ]6sC &Ό=7D>-34pKXj@#VuGXԵ+NX;iC*e *ȧYr0 ^ a=to/$֍[1Y4OOW J1#!njBI>e-x:O{}Bw6KkyvG8/ݸY] mfXx{F'=NmP:wMpVi&> b`Wu[.U:\R37ÿ< m/M-ZO7p񰿕^ܭĭ2[H1dpIud>t7my4rI="H&Szt©ֿ=mwPi g4C/!dg 7ܾEzGoj uGnԐ2P\ wѐYdT_s]u[.>7{j>.7ƚ?d&-bqtBc~FyS!6z%kojZM݌se5㐣.fSzt©ֿ=mþ΍3Zl$@Դ{3\[o+lDH 'hEyUÏyKm, ״V>mho#ʆ+![fu;vb1տFT_s]u[.>O"<;$0Mh&O ?`G#Q:bv#O uX ]EYA=SXU=CPۡi·kN߶T_s]u[.8p^ _c Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}ޏc Z Szt}޻gúo?**k.K\gÚo{E8eij\CV(ڭ t?n\RBc@w sr5r/_p~6Կ_%LJ5/A5eQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEGgχtA|ys7Ezt6r6OҺ{fPFYКezRWҝ@5>]VtNJВ ;RQw%~zKM}s_x9״ h((((((((((((((((M;L}cķXZ?3QYӧ:PnEZRI-#nnm{RcI"(,p6!Ķ8i}3Cijl;thDS$Sÿ >$mג ܓJ$k,e)h,-f1 kUxw qX˳WO olZamBOn߁Gfx52=#gVߵdl\z#$.?h5s~_BtH`Oָc35LJ3f13?;]iWxjsH(}acV.>?$f_z e&*Wq_זWuDpL9`j;{IWh6(GpF5X sdm%[' 6qtc/K7۪+?o I$ _g}2n>JƟJm͞/p2Lgʣ9UȯxG0˓8C~q[Ng=r(>((((((((((((((((+o>|9״_k4A-ҺS>ӠhJ͆c +O->?5>ZN=iIҳg~uBh&i /6&mpB zqCyG6佷L钝'hd??'C˦~XS~sfpk{_?RG$24RGBUXpAA\ZOդ иx䍊:V "Ch/xxN&lK"^oQu@.>^Nݲ,kԴ{Iv>z_ϜAb2Wz]U_  u)b $,8~ _r8"H?g/xJ>MsL1=䏀5ph'`hk˺ SIe^fOQ_Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@|uS^"ů< ;E?uևW`xt ҏfZQ +O->?5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((Yb'gXK; 2I'k_[>4ii6ۡ8b?!<Wܟό&-dy1v1_uNYEcޟ'g麐)/^ªDM~|}ïYi2 Z7HC1e_=" 2[iڔ<窙jGpkb5|=>)~I~o:%QrZKn|d̲Kߎ]>{5xWĚEImZ|*z:69J&+8ԃTΜJn;o'ЬiGҀ,>5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((|Bڏ,tܘt,{.k|^V:o_u yumaWaV-JE_-.-s\EfKh}i}+[AklxP$$4hҾ*<[y߆ooEMu~Xv v j_׋o,ϕ6å AlhCȣa5/췡å+ԕWv"#s=(SLNgYs>h 욊m׭~xh>Xʤݧ&Skm;[mÞ1bBibֳ8+yH9pG%^iqyls̏q,oT㓚~͉&ݗTB Mpsۊ5yn;h*iJ-$;%t>4Tf8Hѓqdovk~-x+NB-Nk{/S10YDao$9@.^oa3lDQ'h3ʅOK.]k3gS-쵻Zi{Ǫ^G~>;ѫ,\}IHI6hȭ^aR+Jk M%XV CFVu0T -8ߦ]/uy,µZT[Jϭm}=~|qokO/WםuFn.FA޼#Q[M>bzϬj25ܶӺno)oͭi^nϬh?j'Iog֠akEHTFx #Hkce$KGu`h;p?z 'B0{^oyt|o-xfu8-ytD0_AC2E~!+7fQE|0QEQEQEQEQEQEQEQEQEQEQEQEQEQEWoLh_dWoLh@tCO+@ J͂@VLZ}5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((c?7d2sux٬kZR\RZt`e"iwIMUu8Ӗpp;7:?.j 4yTO94cV8;W~cU0e^=X_νyZxHxJk G!ȃqfƚq)ΧUM{v[Q٦$jM٦VڮƷ?JWd&mM9mlvEy7oR|+~?F7vs4$2ZM$bG ƒ w45hA>n]O.wx.o N6:PJ84Z# J;"|= OEm;QXFNp;-J1>gR1dZ{uNZu*Ɯ Ekw+j6ZŸ c"dÔu#ϸ9^&5ݼQ,0L=+S]&MCEJg"vPS`8ێخ'VQiHoE5kD\ ,vFZRGېBw_'S[*r哾kocRŒyY}/_Ƌx'ZtoTtmb;40Vӕyfmr(ǔ|3Ǟy&710EyXJ"5D E8UPp⾔MS–2VK/⹵3]\\2,k#O$95[||Ioizn0IpxNsCF'X1N7wOKw}.IsWnxKWcž%c_|4Qۿ,DI굹q 'tGՆ5]i7\1 ?B֝mo %i$•x_NFuC?F4I|VIGV|;Ⓤ9ڵyefV|S]`~O<ܤ}gVXYh 8L-'1rd&gۀ Wɕ"q{z?g`'VW~Sv?<=lRO9o& W~yLRhm_ϼ$(4((((((((((((((+ȷ״_+ȷ״_Z :W!t ҏfZQ +O->?5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((t~.ib^1B7G^5_a{o(w}hr:Ilc.Ǖ!XT_5*6omzKULZiM8մt"k$VYi[,<)<''q(Y6}q"kG^kx"/rfvd- 19SCkG!W/+37^(Nprwl}Mz]y<r_U-'?6vqLj,n麭Dj3"K+He/g -s? [E|U.3`vZt-dgz%>zj)m>$x؟]Ej*3r-vȴQWKxSJ` 9T7Iq$;Iس1 @P^4EJ7Sl7z ӻ.XwM4`MFY[5Emk) `B>YӮk?-ORFԙcmw%6T1GFȠ Oi[zb=I#G`*x 5,;N3>ȼ)6 VX }~x[l.nnFcfQ w'co ZκorC# &YGdIW=YkҾ *YX."k;q*P+*p~bB&v5~_x=Ag,,gs&Xs^_i_?@֪Zd3n'3Ix^,$Zω46o1Y7;IL0R Fj}K6. 7TUu$Io<ʺ|rHJfX1>q^a_Ҝ=t2*uO~&ƾkd= gk쁢Ӭtn?k.v"ku˭vfl0!Ez& rfe}7?y2zP^_(>((((((((((((((+ȷ״_+ȷ״_Z :W!t ҏfZQ +O->?5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((񏏾 4mmPFFIgz$Lꣻ__?~?M%[t}\݉Q:<<{U8K.? ;$o3e?<6Lf<䛵pNY>C{mz7¿7 ak7Ijq}F3e3_gY/TX5|7gyj^s Ԟ[^LbxףBۺn=^;yXVw4CtmowD"wUa+GZmK_HP?<.-JМe(_\iZύ/,X.#{=̬{OxXn F h5X\K7b q\*%h5lam ` E+N(W哝C+J?4cl=@_KWh~tzo'}4tP1g]<F߲i9Y8>@/k=?i ks>qieiŧ5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((+ /On|9)mn[(;wRFARX֤$'OJTk+Jy(Ƴrꬿrҹhi~'Is㏇)y'ĶKyZW~t8e{)P媷]u:O2q\^._.^ ]gԙ`2g#u1ԡ4/͐ߥn6jUwv()`{? ?嚮勿͖n"s ||oYn]TN_ʵ>v=K?M ̓ ) o4QO`74rK#I%$[GMdi%r/ıGc?31M~4>!;Gˇ@͵c: `1o |^r^NiŸh-(迖+n#>x({ 6Pu'Q_ٮ#1ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((((((((+#\4_i7s)2 ; u誄 )A٢g.WO>)m<^?ԅchI'PVoo3'ڡ ;z ]h˸2å ֨Y9^%н7FVRUpA|Rav2b+Rt=dm4K1sM Msv$ hdI:utizR!5V~NoĂ8M@V``F<3p__ hiiGz.t>HФ#=+xK _6zz7~Oxw:O<k,%}CQ[I<!66znr}7O ЅOwh02'6{>-+5:_r[9 C^ ӆ>>߂1;e&J(Vu&Tmz)R(*tQ[%AEVfEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_E3E}_E3Ez֟W_iҹ ?ӥt0V}+6 ҏYZ}1i5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((((((((((((((((((((((((((+ȷ״_+ȷ״_Z :W!t ҏfZQ +O->?5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((* -乜%, <1>&#o*Qe9wE^/9Y:݅${@tiL.qe2Pp-#$%>*~E[xEqm;p˴:K{3/W)iv74Rymdwhԁ zZo75Bk-ܼ[>F9}z::ȡІVt"EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_E3E}_E3Ez֟W_iҹ ?ӥt0V}+6 ҏYZ}1i5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((('?h*^c|%v7\̎=R6~tw0吨(rE  QEQEQEW7?A5W7?A4siF)A 0~XIg|ŏgFkQ'$Wx?5>ZN=iIҳgzrC]}J5<_-/k_-/k((((+_)6~.>iږM%SIa :~(jzAZ/m[ۗdP1bORM~Zy:އo^ ץKelXD% ȪQycqk!#ii&LIa's79&Ioo+V?fk74V/=i-wK4^}ddaȶIlWƟ xy,bKZpT}[((((o+'ko+'hC^ {[j+;c :vjcxvZmFQK$oQ ]O{ /Ȭ m{זhwKxPSj]ZO ϓG#]hqxjjWo*B$/$|і9H⚛o*t5QQ2+{׎[k:3@jp HqBH\ l, Z<}M0spT*< r#8Kf_14ya48ЍuUѤ׵t%Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@|o_AE|o_AZA]}J4NZQ+J>eiŧ5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((#xi [:]D8P,[kvoRMsڽk>:5[xecog Kp<=mWk%ƫKS(( ( ( ( a"^&Ja"^&:3׉JQ sg'5퇥x<u5cv Q4q!Y/e*J*68>k5S6w5F[iA-QE+VڎITflCZŁGG&JmcrgG#F^׿W7#F^׿PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_E3E}_E3Ez֟W_iҹ ?ӥt0V}+6 ҏYZ}1i5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((OMi@P)5ѭc+vPkw%8)_huqK~zٱxŌ[mo+ L㌚,-- EomC`+??ÿ~1-4O xrkBktncmUc\Gݠ)ɯ& ( ( ( ( <^ԕFIkIME ܬ0A(tb yw_3اWfh.,X,5.-L`r>e:9~x:iY4 1;&k| K[JVŵ\êj$M y$Hxq:`+|(-^_7%=su |He9'C3@_+_.Q  >c/NgxOY֢Y~ضR[%'VYK2)&o;\+_.Q  | .qmz 2Z + G֋ʵw]*ݠ((((((((((((((((((((((((7/zg{E"7/zg{E?ӥrA]}J`(VlJb5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((w ¿ mcųo{ylv7zS135M@|* IduT0:ڊjr'jש1ЃЌWQ};^iw/ih/ p~ZFF{Wʺsx_PYԵh;]]]٘lo{[)X!)@@q\d K↳usHˍ[Ht Ur-.pR& V:*|C֡kY SV};@apjZy,x$9>o|N4Of5n|=s[6ͯĊȩ#3.YAPH˫{+YnEm,3_~y;Q|nzᛛK;yl`Koi6fX\J.H j|_/-αkcg>ng-e&5f*361p>w#<-ZMq8$ -K ItV(\Cr2(&.WxMMĬ5]ZR#km:KXbڥZ 㡯| ~k>;h΍v&P%};ĐI s7e%Sg6e%+uO xcKj.t*k<P6yD Q@Q@Q@Q@ 4t EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_E3E}_E3Ez֟W_iҹ ?ӥt0V}+6 ҏYZ}1i5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((((((((((((4Pi (Q@Q@Q@Q@S @5Š/] Vu4bhpiQEQEQEQEQEQEQEQEQEQEWoLh_dWoLh@tCO+@ J͂@VLZ}5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((((((((((((4Pi (Q@Q@Q@! Rq8Pho ;W$rMej:Ρ\iegOss3e7).| >K{~'nݿ *r{1W 2cI~Gq72:Jx:O~OG|i&fhp_\N^SsO|.[^̿@¾|5u1?Jje)bjUӌT~\׿>viXl6YIkz=Q=5O*ư'w}K D=ۼ16I%_fISFkŸ h \>`U$=v a:+o#]C!R~Vv8Ukk'mFeV w ^!EPEPEPEPEPEPEPEPEP_E3E}_E3Ez֟W_iҹ ?ӥt0V}+6 ҏYZ}1i5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((G1(lWWoJ.Ziʐ>͹ g, w CcK4imb1/,)/;@hUW KM;C\iwq,l!$ؘ+]QEQEQEsZα5({X^z~?ej:_I=?Q 5?oi>}<6vVdq1 _xu᫉4iw-F1\X0J3ʃ׵pct/VxyeE5S0}%v_I=?K -u_M~q[|figƕg/1<FG\f* n "HPnaep1 򯩏OB2WS_3%f .xa+?̩i~My&I=?Q冋t|IM1^S9tPyu}.$ҵ4@vM@C㒢s^55ꗛV= <5+rfK+ߒw>M*#p?6() 60sm3qqS-;<3Wh(((oMm4|VF(ljLJNXX^S/<}}k>kE\pv'{=<w><iZG1=`s<##k nU:x{pY8 ٮhQErQEU{epsW rb*rGſG+۽M>hYi\ԑ(u8h2׶~t +&dث,l$Oj-:!ov6cᏁƧsk;k_YY,,0$c+"nэr3᧓d42\5J;RZGv,]<1`ҧIuq[oyy5cWxkVk6K[]"~[<2-#\,֮r^h/_?[*IG{YK}{4tc2GZ`l8( ( ( ( ( ( ( ( ( -+-(ִNiu(YV}(O5>ZN=iIҳgzrC]}J5<_-/k_-/k((((+K _ ־~m]Juܖݻt!^.\%]⿉ k"Thǚ3C\_v~ k#My{C8HZ'~KcGݰs@92hosKw8k..bԵŚTRR *Ύ eic8+[x##ۍGč?Z[hr!#]& M ec8cHaEHBªp%|jrho;ӼGJZ5Sݕd,m1ו= ZI"duKH/mFߊ5lz_"[}IXq^|nY__/ jhѴe5Q8*y2;PQEQEQEx?ͼV:pz}/sws!C @1>WIo99ᮙ)Ĭچ+vQd?pIS7̩`)s=_dox=sEݽ>b?Wm| n#-䥶<}}௃e[=ơ2Kw#UWon?-_k/W$Ia`Xgm&W E~&x\7~4W}tR"7z_UmMon+nŠ(ُ3M&Kia[mNXmY1Q*};*<7;n7QtkqoX9WR09FqJ3]m [W𲾩g s p-~b2l0^-oDe}s8jfh[Ms]i̖v}6/fM[>f#|DO~K9VhdGC#_f5|%Uc὾K/],QENKQ)?*RO2P¾:omU.|ѡ`sϗr9WgnYc1]q :χs)sOߺO2 ?; CeKe4ߢ~/8izI/ş +_hwS"RfBA_/~~<"^EjzoK7)o./%|,ĐTeoqPH 9WD}$i4!jWlfImK(Ub"rOxURJ3;iŭlalEwNVNZ]5%(m,-|x\5YҗC6o5ݜzlw9v41 'kZe5u?}.1$J툋.q!VN͌ h+I,Mx2bQOPG48&Lt්y&Buw5M[mjmիsxÊe$^;~Eִ_@}]jwz}eaE2gf:35%Oai-w 2W3<1dqW-_döѴ@$]֐,ĝN +.t+{&Y|?ܤ<|+TG;X!pPJQ^hKm9\\&읹fV}W|<3\thneX*rffOH |$:gW_wOxӡN &AB/\8=8q5^nm>rbZ5㑜8W qu"vKE?ZN=iIҳgzrC]}J5<_-/k_-/k(((((fqj7ļxwHZXX>9v\Dds@w/A]UoR=KW-g4F?+9Ir#ޜߖjo +4ЮN<5uxlLNwRܰʢ ^9wòY%taIpGF%02T4W3?V_"ZOn-I!B2ē 2+mgi>.Dl:$$cX0hEPEPEP5/~:mmlk}v:|Caj.?ZI&|bg}Khʿ]f%TTݿ(Rqȩ;: ?ծ×RŚڥlNic1B2}y(~m{ARzn-29P'x)6]XdM|"xC? ֤6,D6Xۉ!\|V~~ $EigRTT ylYϔ..p||Hm[f\K;.pn[dJ$ȥkku.e7di_5}_oSH\j^+߰inˋy\]Uܛ[d5OF!V[{-.aB 3[ .9mQ|>\w>"0jւ{~:wyf-;<3Wk (?K^*BzxY==w6{5!1|DzxZ뢲W:?&VzC溛MZ>9H]=xT,F\{PwWׄjdzWGk7).t9OoY#W>ZN&^ZyIע|5yKuSWh{n_a Z'OSZ^-l>vG[o|(Tf6To;2f4%7]W~k>t:sլ6 >+N~~- F U@2X'I+1%mͫ6] %qQI'*rKQ9??W9|f2֖я:k|9/;]۹-gUs zc^>=ʼ}je[7%YR7J-z?^o?uD3}M2ccdoV8ؓmTj~ftd~O&9〻=k/xFÞdPC6žo ˟*^,Afi7  i-UUׂ;6' %m$~; t[?gJ7Un'ڏI{?Hf:o*-thV=㪉@+9Ͷw%ųJߙ) !޹>!p$rAG}[I'Z0&eeD M9Aޫ^#J $`Op7zd?Vx|Tf?Ug6]aqxsSٯG=Q?.nlt]*_"5 cn簼gG$9"CK-4}2HR0Em #B('VlW@Fyd{?h|'hu?H[idscp8$;Y^]bqAoeݑf\ ,^6jW.KV~kwkG1nc얶RK*-aB9S]JN1pY~g_KUGx,vj<ȴb \+F??wW ?>!q|MǾ-ǒצ{IFI#3=_%KFAFjqjШ% 'Ȗ'0,sy2V­`ҜZ);ͧE./x/fyzTMӃVq7Iroo28U@I0QEQEQEQEQEQEQEQEQE[?/W[?/Piu+ :PCiGҳ`(P@5>ZN=iIҳgzrC]}J5<_-/k_-/k((((:|?]|N֗w\ KM!E'?3ڼ( y{ jdj&XOk9zʺ!?KO>eު~ۧj/.?<g*)5*GW׼W~7-lC]Iln̑luXZڒ4XU*;SҀ?9 [?΁a~N`q}/#c-#ybO5WVSjoTOW~˿|qsO֚}}4js,6i ĎZO, 6lbH@Q@Q@Q@_4?gq1ϯlj_?x] ,јnHx2z ݴ|C*Ωb+;A2}GxO7ȫaVWw5t_;.Mޤ}aڈna<  !@8~kxHѧGYM"?ӤVP[ˆr_/>N=[C*HH"UO8au=ľתeFղsuc)v݀)X.o 8}9R9fIIJMZiӫJ6SJ-SnM~)z^tړx/.5g%l( Q k6KQA IJ mHp\@5'/ ;d~Ssy]4|{O!uE!tFH"Qy6qr2\?~ >H.I# K1B$O?7oi*PӡUEXvtM~er mʦI$8ጊmRSNQ[>wOUi4e—|/ mgORB]w˹e,ǰ|>gCoimVv[_{>Zic[[iQܲU*ȏ'J~%faѺVK\.;R+kc]Zwyf'EPo|G_x[[(|I6X #9@_Y8+Xx^XtR_enbiw [xg SZ)-7 c)[kI/We?<ڞП헇~ gú?Bu 9"3qq\g_~nA03j{|;5\2;vM>W7cY-]ϐ-/wsWR-ҼQ '}'+>z5}EZP8gl==Oǽ~} b|v#QcAr7wb%S_[Ek]jn3?U|EC[#ǒI9={և<7xOIC-cP` c'Il0g5WF? W]; \.EG;J.QעEQ_}U-G<1Wjr~#msUq^.Aá=kp~u¿^ ՟"EmJo dpF9KVӼ5Nؿě M(K]{O ~m00]G2韵>E+O!pLY$lz7Ң~jW)7ڜFLF,ﲆin>lI9(q]r* u}ZTr[o7J SSuʅhM:[JFGy'ǯ?ǟ `E'6־$5vw` |.^5_xFIFc$O|yL>s +JM촲Ii~>G>qmZ_$?O՚X@l,܇FGz=~9xCS~f힟 '\73E5ƇyGO)gڹuqk-N y֍gm8o $z]5EW}QEQEQEQEQEQEQEQEWoLh_dWoLh@tCO+@ J͂@VLZ}5>ZN=iIҳgzrC]}J5<_-/k_-/k(((+4kxLt B;[aYr+bdg%F;Y7 }/N?_Kj?ᓿe#iZfY%~K *8DUPQ@Q@Q@Q@\ڳ#RA WKe?k kho~ľ4;Axn"+ML"= ,>|`hn樠$0t?$ўN:~T:dtjS'&?J~Þ%b0X_ƒBֳ.wMvM|g_bʮ"پM4Us?C<}m ..}8ճP"ֽFoۯ5/9y&x-Q^M_Iqj2R~ϿtEŏ40tKvV8wՓ=hߋl QT牡52ܒGę$;>4B5 xi-0? hm5ܺfdHiaٲ Z]6n==*?|qxy|Gakx~d{k >9D_# 71|57/]i[y 焼17ڗ.v.P=N1srW߾vrzXS&* _k=ĖNe4L 8+gE_5i O ht ydD`+_B6OftoQ_?<b[)JrVMmoomg({:BQw]}=ƾ,O4):yW9_؀dPEGEQT6QP˰W?jO`ӵݾ(x((((((((+ȷ״_+ȷ״_Z :W!t ҏfZQ +O->?5>ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((kh' ʛʡγE~nS|9Ǻςt˭w-rb qn% WCp+)m>^`y %iR+z^vT5y.llI"|΋(<wǾ-y|NW4߈eՂ 伱0]Hrn?XGxS J*T%7&N2rvj7Q;%wouϯ*x4$׽ͽG712C71]#: t/+?3|"JihK{mWIa5㟴>jSė^?E]YW1if326TYe-(,viխxdcJ-=pԫUx5VNVDֶGxwm- 3~)" dˑ נg?|ɰ3s9/CRq^KGy.+-Sd\SbKEហQEQEQEPh@QE ( ( 6x[z}Hػȶp;yW!Tjp񯋥B{JQOѴ\uiRԫq$yc弚콋(p1ǩLWK<+k[yX^]ZZ5ˤڰb93 Ӓ:G Es׾>4/m.mpE!')wuMTݔR1\QMn|Y_ K b\ͧ]~M6_wnz RH],0UpA ~ֿ eCpQ.%#b|ez_4]OfXFml(6ø힟Px(n $QDW͉1e< \yV8I/ԍSѪގ6^W|K10n1opvwtJ{Y-Fe_޼7<ᯅ/\t“!Ic(>Р-njCӾ5/igO}R[O?IfG7t*A;#!N2r^{(9ڻ[WsҩRIIEeܔ]I^ͤ߶-/uvIgoF.٦%w):_#p##"-FePg5i>8=}⣣>O B`kz՝ ;LZĊ F@,Z߄3meO\I.yso,2ʻSV2S1[1uz=wGQ_!EPEPEPEPEPEPEPEPEP_E3E}_E3Ez֟W_iҹ ?ӥt0V}+6 ҏYZ}1i5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((T+~IT' {K[ |18o~Ou/xHqY}NH0A=)#=kS QN nM9Z~W&9Jn)aګj+}ϓsfOF h큫]Xj쮮tMklJ&&6*J [h˟<q۵'!Pۗ5K_zkt?4o#xZ%y>ǪjV2o.rпQ\?hhi usj5@4Y^?y3a5+EnnW'/_fmt2^i(_}w)!!Y|;{& m©EyfM<.yZ+v63Sw'ݿSQbKJ (MŠ(((E)QEQEW5? A?t6IPeK^qtVkNHզ$^b*SH:sWOG?_tX|Ukgkd*hHrA$^ʿˍKi-7pkz {oGm0,Xj+5qeM>w篩&nvȄkHKD,:܏.]HwdĬa?dٷ[47;MCTbXyo Vb_/*Ffm }Exkb9ۛ -ߓKzyWy~.ZN=iIҳgzrC]}J5<_-/k_-/k((((((((((((((((((((4Pi ))iQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWoLh_dWoLh@tCO+@ J͂@VLZ}5>ZN=iIҳgzrC]}J5<_-/k_-/k(((((((((((((((((((((((((((((((((((((((+ȷ״_+ȷ״_Z :W!t ҏfZQ +O->?5>ZN=iIҳgzr7+ Cy/]_ߎS{K ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( .+j)7Ez :W%d ߂YV}(O5>Y:j~0]\]+|h|tNj_/k 'i_Z#Fee9((((((((((((((((((((((((((((((((((((((+So{E#FF 31u$K?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMo 7omr^M}(>?~&&-4Mo דgJ?z4߉I ~&&-5ҏ@ 7omrG4߉I y7q8PMxo ҏgħdo גgzxzJK N[daq4V~О%daqv*Ӈ㟉%NY8^猼_a6V5M,wpH7.T@9R/6impD0UVm1uPcdtcZEҺuiB*v eR/JZ(ZE* "t%fh9۫\g={\gK|Vhs@ĖQh=mɵJoGrfv#Һ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)GGue}zQ]_h-ra}zWWZ>@Ga)J,k-eˋRҜ-`%[ڶE;TgGoա8+*NJdq⭢ЩS*RHzuQE((}*25! j2l0S1*nj O&6RlPG'=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?y#Ҵ6Oҍ $zQJ=?J6OҀ3G=+C`(=?JGH `(?HFP&{`)vP!bmTj@Z/(((((((MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM Z(0)h ( ( ( (?OSCAR-code-v1.3.1/Building/MacOS/README.rtfd/Oscar-mac-6-right-click-open.jpg000066400000000000000000002777161417327530600257160ustar00rootroot00000000000000JFIFHHExifMM*JR(iZHHb/8Photoshop 3.08BIM8BIM%ُ B~/b" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC C  7 ?(Bq@ IL'֚Z$I% {ѻޫy0zªyyoppoo[(*G@ 7 ªyyoppoo[(*G@ 7 ªyyoppoo[(*G@ 7 ªyyoppoo[(*G@ 7 ªyyoppoo[(*G@ 7 ªyyoppoo[(*G@ 7 ªyyoppoo[(*G@ 7 ªyyoppoo[(*G@ 7 ªyyoppoo[(*G@ 7 ªyyoppoo[(*G@ 7 ªyyoppoo[(*G@ 7 ng(-4U)KP@QM N((Bq@OaL'BTo%djwyת#0Z,lƇј ?Qbs*dFA'/^.ͩjwGUYc7Xb1TG^g9kY2$U컾^/S_د-)x~Iي0m$n:XgZ~}q"B,ǢS[A*i]ɻ$kpxMŧ{;/_ؤJ^IDKI>h6hGce'Weۯ'k'r֋VHfg fO$WE}? ҿ/W^%_ ji ڎ<sb㪖H#]Wxcw4+&F Y$Tu<J`֖Tz=rN&x<=hʤwiH4A_خ[_Oۯ'k=cJG$WE}u=mF:H4A_خ[_Oۯ'hJG$WE}u=mF:H4A_خ[_Oۯ'hJG$WE}u=mF:H4A_خ[_Oۯ'hJG$WE}u=mF:H4A_خ[_Oۯ'hJG$WE}u=mF:H4A_خ[_Oۯ'hJG$WE}u=mF:H4A_خ[_Oۯ'hJG$WE}u=mF:H4A_خ[_Oۯ'hJG$WE}u=mF:H4A_خ[_Oۯ'hJG$WE})燆$"" Fۻ?.H@ 2'=2ԒdY/JMt1ӻҬz$WE}? ҿ/WswwM #I,UY&%ihzYa|r*$ +~i_+m5xE[V{u}9'7w" ;H4A_د;mi1@.+n1s,Vq Ɨw\P=*/tCEg#ϙ3s' _X/a)Uԃ9c}a^ϤKIndCp!ui -R؟#yECT5RmkJ/VˏBH+^/*cUDU@ 2.~*VUm]?y QEsrYɯy['zShkFT<Ѽ?66.o4o5Oͣ͠ ShhFT<Ѽ?66.o4o5Oͣ͠ ShhFT<Ѽ?66.o4o5Oͣ͠ ShhFT<Ѽ?66.o4o5Oͣ͠ ShhsumLW[xu 3!f*U9b{s67e3C{K9^0 VȘd9Pƾoh$( XD_@xA|dчpA#W|V_~0u VV|=uz$0kR=iv> #fSd׵>?ÍEQdQ^%PUvgr\/:߇o>&J=z?.%tW!nda<.8O?*]bkײON񄷺Sߌ5o j'TSL{j;+$^KSo, >1ZԴ{k:.}WYDUym B8 s4eQ_7(o/?oDrM~'z>׾0ͥknu] PmNi_k504LbS$by9Tjc[x2mV-m+xo%M&!,F.Z7hl2"i Iu.?lfiw|'F4g1A$#0k-|9̠hGcܖ`O?x57F}>º|BkѝQso,pI$Um8kúIeO\DVL,2c;rN2IWwc`46ʽpv} _)?Je~(N:xO?|^oa xZ[iٯ R@&q>Wirg[j^qg2*^ph>ee^}}ꏙG@zeeex[<#k3mAAx"g\ekT-n]KXv-$ӱwb}xj`'[oRi+̠KPݳOvyw&[G%q dW7(=`Ckm?٣n=!?C_1~孕 RxxjJ]pO>ck |1v$s$bd[Ki2J !RT c TfK,kOV/⟳wb`3ۏu[#m]|>ћRm43A.#tuRXUO3?/٢aGG'>gygǙ~vv|֤ơ!iVB{oov U@$ \,0Cl?Ʋ)c3uןO~8{Q m[Y{,5yZx̒+-.] neq O5>Qm4{[Bei"$ٙ*$9&MJE׳돵i8e@h1rK{>UWx$10u$/U`AAig1lK>%gH<1(/#E嘅$|d ?O?<}k}['l}>Z/_w|쟳Dkw>n'͍~/@Dx~-{C[o-,3?Օ+ 㯟pbI#0j _a^I?sho,<1!onHXɨ\b"(cgRd(8?4w@ S W*q|;X#wvp˲U}dW=H0xek=q%A\߄H6HVUf8g#yy3}ߢ\i߻>m;3#=e5GcXmfKlMc'WOO1~돰jZ|I'jS_c)l5+\8K7f@UUC^B yDUV[K ܨ22~|V W򦐇*όyۃ]o#bݽ eRG%ĉ,AeR5g q*-;K%L֏3Iiz}D1ĈU0IzkO.5Ad(5-#D{Dm;+ _-ivx 25pzͲ(Q ^Rk7H,t!1oPja^X*@NHWE|dnZFwW2$$ >}yvY~eP#ܪ۔&<` <{wˡ@-'<9#E1cu8eoAK_͇0 zə̆w@m5oJԗO$6 ~Sk3;sY@m/,c.dvB;`w‚qV;\𥕼 7S؆Pb_vڞ@/$ (~>P\`CX#5/Y6rA\ P7ޏ7ިy{>ee^}}ꏙG@z?QY<Mr>] oO>dlϞl8=9sJRR.H ;yC'g x??@/|ze%B.W`ŗ|Ssϛڼ{Hm#bG)&QtӀ7ۆ4-R`f,?&?2k"^1#J=>C<ur#J0B7`rgMMK֞4(n%Tp7g׭xgn3Ggn3@ ?5T_UyνNd"+.įC8?m m lyyǙGXm m lyyǙGXm m lyyOi;Rڎwicx>j|/Ktb ,bC:Hz_1JHJgn|q¤ݨZEN|K"_{f}_nݿ>F/;luO}~X}^J2#Yd ga7jֿQ}(Ρ@mkb>/O_AmUK"X8ҍn/%KV8)5HX i7 g$5[0mkb~@}kb:F?g;?ľ NӵM. x-A(ƉXU ]_ûNa[mkVy4ln+PwI*|ŠBnG>kZo ?7ZjH  B˸pqMr~wX5~(uoQΞ:Ɛ|6R]٥fb2Dcu.}( t=#DO>i&ySylEd%r8ȭG/, SK&9aX%FVVb2 5Nivz_ͥݦy=d)e$r hoSj0=2 v%* ŝ 9f$ԒI扼Wەjd0FcʰExG /_( w^С4#xmX G"Ux큁92$kє~#W~@}kb_]{VCZM)[[Duo=FBήFVm߇VVK)55柶k]B}R7v& T#oQQ>1@o4xuo,ϑgDIBϖ}xW 7\?UxG /_((FiN/!i"',ne$3ONiڌZniOʂBd  qboVY+D7נ{>Өjv7Vv4$iUr,@GoD{|;&&7Sm2[ !*5V% zLiKH͍LaTH fj,Ҵ2\zuηs=2$*_ɷWإ& ooP#ڗEΚ ; 7\?UxG /_(]ٖ+X 4g1JPXp h𾻧S x,Fsc?q8Q>1FG /{g&^ޟqu߅qsk-~$գ)uEL1\|WoQQ>1@mxVž=܉Ğmը f'$J>3ϰɯߨZE~@}kb6F-KS`O 1ITE@pFF EjwK +\f #LG /_(xOzDYCwcf{bI$׍꺺ze޵z~%֙y y%GQfbI8>7e+-?}j[\Gcr/o&IQ>aa9%zKZDZld󬯴ky1Kn*AW>8m;qק_(oP{I&m9%$e{`rwAիoX__h [kap08^IQ>1FG /z|k:D>T^Bysbv 3ϰɯ.u-NPw<Ǹ%Qg3td0&8?ڏ:: oGmDhLutKVK4-F sH>r*d zb;L~=m\t mDxҨWEPnR^mUIh&3|x5tbh3^M]r^$i]r@SEx ?giR~{fZiQOdbkhn. a-" +?]4j#HI-~Б]\}g#gB"N+cƥr>9:yo:tY1C]^uwG^bY5t-&UӬ;5K{VYX0̦PS*yV?xZ..G6xiQ#\Hg`?wW6m>DQ^qA]Y>Ⱥ3I,ǧƱ2K@~C.Ώe",b),2$8J:( ( ( ( ؼؿサ_EWE\E@Q@Q@|+NxZIVyjꢜ$5R;?gX_玵jZ7>5VQ.kkUi^6SYL\?:<]2DWxcQ?os-$m5?xJ;k;O]O{衏#HƬ̀ hZ{4r7 )Gt~_h>-Ş>":ԟGxC×$%K{ RN]>nw¾{Ӵ:7ğ?o<$|~"7&=k6AkZV/QcRa1Xqk?Vt{K85;PK[Kk{&RE,n : F(NI5{ߛ~󊌿zZnRU4\tdKCK4?6d{:Ѷ7%{so k_ 4_ ^-mMZd+W,-_f_+(.X. DVI[_ j;©Ax+^*[- )u[qq: efyq~ԥ{#^g >*es/&ӕk/$:)vU,uRQon8_},Ih^Vn~D&M-5?ڟ#[X_us\I~+J $xhմ_}^-˪kzM'P,淐ɤkZK+-upm+=4s6&ܛˢW֟.%ݤ~G7T) >֯|C<}S]> Cgey=G ѯٴS_2~\S(tQ_ ӷž}u.O4}׻G[Y×ݾ/jI7C÷7Xd%(|o;~)j? :xcⶫ[Zܾ<;uhZmγyMuiw9adi"Ia}QE* ҄"T~zU9~s_sizE#4' ? UW^'TZ5ΧpmJյ]s4QݕYb'$O ~:t9"}br=RInuit̡6ca4{.QYΕ8vkdI³ڕַlTw}^Olt[s>UAoX}/[0J.k7AXAs$/90#&+6+*=_rQEQExqxzmOS<-[Bn&KgT"PFv L*ϖJV?9U~|7O ;z׃u4Ρ{H"f£nLJœ|M_ I zu65ɥ87SԢOu3EgvJI~׫qmޡ.YFIlߍk 쏄|?h^]`jP:T~ԕ 2,p<=-cKW_O jwԴ6i/>vo***yq+I<[M?ŧYIS#ܭ.((( "? AsEԃ|B<WOO!*4@=+\WYkPFQ@(nRZ*qҹ{k\4#i]r^w#w?#@UV<:o>sY^s3 İ.{[Rjy\WpH{a2ysp h͕ S|4VL n֖{y+dD1k3GmmxvYeHOT BI'Zo]?<Um#85[(gD{H#h`*;Sio;Q{{i O2ېV@dDe%ǘV|E<5x(MHЖb~C `Fp3_k&*:Td2(zCrfm)"7zs]'#-X_Cu,3LjH5`p+߅ 5[ CzͶ52Jd1hZǁ|JY?ZwWvp=ܾTɵaG55oJM/Vlޝ>s8׮s4Og.+eŬ<חQj"f/o䨌5 6|o>)ϊ l%4TXɨMexO8eP۝7L-B5)l{ȂgnL' o|D׺5KEE}~fz]V\&tv/'w?Wf.V6q\gIIaScNcv φ//ê-Kۺ\v0(D a5^ii:56#%tTDT@lݣ%wU}袊c ( د-m7m7 (9kyFi\n۝/LX?ӗE*?){P} 9_N?"7/zQռY~DQa1>f?E|?iIt_ a gRny? fY7^-Mo95.|oYv]QP;{B)O!/aG' 03jv[|e+ʆA> +P #ixȢ*kإ#asϖǏWMs/ߑ~\'' ?8/W/l_h:i8L/q5du܏,=6־}CtӨxVJ6\@O==eOc^v{ySkx4]2.3si{,-KOd/ɳN?"z:);~ \ WʟTuQEQEQES$"B0U>U?M?{@ښOΏM?{@ښOΏM?{@qJ8Vf8PEPEPEPEPEG$ōIɨ|雕(@@hGGF@ߥruu 2uQEQEQEQETm4jJr:hu'?@QP}S|0OEA}OC=upJڝ@Q@Q@Q@Q@-O7lL#6BW^~k)Z812O~Aq[GRJZ6}oE|~Ժ,V'A(|X/w?<~9Qe${zS8$8ԌLȰ(("qBzt5 hA|WO s#I⮟J ?urZjmz ڇTa*袊nRZ*qҹ{k\4j] ׮j] TQEu YjK^}$t2{q\ cSdž_b5ԵEXB8C _30xfQUko :5-_4^<ʤKr3118=R|^in)"tkKHdKUn#prRWVH#>)o:φS-7—7V֏;+6k.b&MXvy*Nީ-$ݒ4hd'Ev75GBne ose$Pd8x˅\ c㗈t ޛEa K6._E$nnįHA'j55g}v/wwuo×MO(ok^Miݥ;a[K; pw9ߊ[wG uѴ-B-";,"c ǹ'Bj-Gs.^u?3Z+ƾ2I=YпGѥȀJUXAs~<4k-sH7ya-C་$Nѩ?2rQ^Wi(oD8__+k x_7+鶚\A`iBK S38* @:54 Zm:a5ū^ $FX!Gة}./׸{>/;ⷆ5[ ķKaqoGVIAPA)N_=!yhn?ݏtUn?ݏtTQE^4_ׇN1ߊ5O4x4= p"uО$lOͬA!&@x77;=pG,5t.7eSS߻>%w2+ũ&5NSNvr3sQ/k'æ!qݜw=ى$5onwK{ZVV嵵s6&wT M}xa$jI_iq4P Fcb}RT5qXa(A G݊Jzs{v{pHK[ޓns(>[#FAȠFAȯϝ>~ξ١L`UEÇ|s$MЃW&XM+]^ N}xe՟먢#(*ѐ[Ja6(mPlag<[E+=+4χ/AeqqYXhGx4IJ v߱ߎ4mB Tt:LAhB) B`ϴqk{ioxy_SlHY=x犥+82<ֆab㉣8RZq},ouF_> HĺDR&K w\~WLݍY-o+3ogJ|GY$#AEWiEPEPU.|J@V>ޭ\|O&oA?sM+)YG3xvy%Τ͙T 0zH(tXŤ ye'q@,rXYIH5=_]5MS|Ws;0$Gq7mBZѵ]:Y-c8empA~aKZJ2j^7_Mu?sT89rn[|7{IJSލFUKۋ{_VxWow|Cts$+ |BOh 1m;OQ+= {h<=8==H AòO4ܷS֭̕u٧ѯ ?biէ+;Y=[:7Yi<~PQ7!'H A}7y% BP'WYN~KQJ}W|uY2XP-[I>hݶRZ]٦~`{(;΂(O8(((k76wye-1>S l +׵iFԍ8_y&7<?/˦j-Y’D6FhɈ pd_AWoÚ&67`Ku"ʬIO@vU~T$}'KO1ML-I^fo3z%ϊ[z]Ưy$z ]mSZZh;/'dwp|Yh/"^|[ɗ sHe]4Ww ?ks|5Y5)TOu8h*L0rOŁŗվn>K#Vg-ci&(;$כ_|yմ5yq"yG?#^~3^K%? tfS#P|GVg%~HYr95+V]cǴiTt44eA|+#4LO2~g :-CKFYXwr+㻟 [!V~UjpU--줞?> -O0ɪ+s[M$Y;lVMƴ1uU ϥݯx/Z)8U$UJrOl|QN`g:Ў_'@N=;5{8Bu>t>W^2?HrVcEWQEdO!1ORB/\cО |?(>!?*cP|C_0"Q_UԵ[u[\I$'G$j 9'/PPqX ߉?!G?ϳ߻6C[q뢮wC[q뢠(2"uО ~5޽şԹ_(ݯR/_ ΘOZߨI:bP'<q s+~3_ٴ۟mvO/ؕG$ZtඝFTGGvݞ>:ukm47–:d5g[rC#@ '-xJ ΐJmVB<0 _Ozg7Vա$Y ~ǢwnIzr5um󝿊>ҵM.z>F\Nk+sy3e]ysZe|8H 7qZ\,mn' X*  yz)X]Z8vLv踪QɮůZ\B|T(DOjb; gt8i->WKז+$Ҷ>UR~_|,˨],MʹV=Jy->Ry:=Q9l`t"tV-cImcXC=&OKnyZtα!b$ WpGч/=^mmfMSe55Kkm'{{kg4kq6t< _HR06W ݃H<SW._o+okBy!M'Mx?Fp')m*k;j*-~P~XhBk />TaclFӆѓ$eɀSH^:^#?ztPKϵ%/U_r3FS"YmD^/և_3X7 . @ l >+l(xWiAFm<=;+,0H?3t̕O~Nvfܟ WeQ+Y]ݒ3Ǿ% |'*6đr/Y&|U;O.|TԾ$vKmm^%Iba19#CV:_JMum%iCiw[|^~!ORQEѴ[eJ{P1,q5 ROyպuZ'w[f*nhRsm%5$bT-ꭿx#SmjŚ [حU=K8p\4yufP2K/ kU!%ydEe%XmSI`Ě淨7gy.rp^x.Bm^IR0 R죹G-m[}.&̩b\SQqvW[?cf?ÿYx쮤[kQ1 vm<"rϖ9G_>9|Aa KվjhEwovX+wM?XFb9\)8 .կݶ^Z(,7H3YOr?K^?xG-8A+;eߥmk?;-lIa=Aly vG *_TӴ/m'2/| 13(rUr}6_\x_t>bMJh} l6cz{õ +?x{FEl[lx匕t,G~ONk/!3HN0E`W/z /.:w5 6S !tqƹ)cq\?i[ɩjzBypNX@s_NdWO>/*:6cn]czEXoҼ\~ i[PgB=_)Z𿇟NJu[854hPmredx Hy_2߇+Wҥ̬4&F<2:)?/exr-<k h zާOa[I:F\[C"Jφ4`ՌB%6Fŵv,~H`ou[Whi&?{(`YaX(@Tk-4?zu?:Mk `ĸb=Oxzo,-99M# =^/d9'Ia]Ke{ꑖ` șp>PGETQEQE~DFJ1i܁7O3)?B=UKT93H}CSFɺɜtϩM~3ܰ dbx}bSpn§O)UbgG䕾/,Vȡ;zVFߩ@@@郚+K9++YUFgG̠Mf-3qi˕\w{^/xyeuC7jMG纵j]i  ?^⹯l=8\2G"_Ą ]S!ԫe7}vz'>' 5dn|6$(`ڿ]W a귶I͏kԼNzN'#tMB(aFa濛a:_ ݽ:a)1\_S*((ȟB/\cО G?_=H:?P|C_0C10rAPŧ{$K;'}I?UD>΁+4 u7ᇂ masF.w+#  I"gb7g'}yoK:fvѴsQHOԊTX[]V yXtKr3Œc'9%g'f޽}qi>>[i3Ƙ_DC0!I Rw̥mot7.6)_Rl{'6hf1\@H̽;Idj餵%ȫr6o)g~#xK^𶛤aIhMy$@_j$20G|6@ӵ ~}ou] 8#y8Z<kǭk~HY;7o⏉RJ[@}-NPi--P.D N02HW}/nK 34MqQp`GwV9r3զg}i#[4o5^B6#²% O xBֵk]hް5yn/wol3İ*yr4_הZ7|7{}z/2=GF"8٥ M~xJEJ4cp\G^_\z,|N^#KjPeR]r:0.+?ois %vwX #"]vMYZ}4qDd#4rBO9criiFJ13""_QS%mVgu67VC?p5ѼKiyy"[ ϑcx& $.>Wa ;N#LJGOP?Qw{N˚I!>_Y>ݯ^k5VcKWrߚE%u=$fS)G[ZPqwQ[>˭_~6|jzMGVXn&;<]s4  #<xᄶK[9c & &ɭ2Q .FG"%W43JRWIF-JfUMфU'vW=7[][>˭_2(^KRh~џ Ywhc%wKT?'ڢ_xk W M}9#YdVwU 1מ벍SNr?_O#J{ʩQyߧ{KP_JHdm h$}H0:` }?uqEPxSX-?^;%/Dmݻq~?(M}iߛ׿a.a 4pڛ|)^76?AWR2<;&/-V>.1 ~nleu=JHՆa-u"HakOj D󵘌o8s VY|1 esupmRĔM_ڦRzW6Ry,WͻT]oskFʏgChMe/?v;_̾6Qae?Gu/Ļ?/*%"4mяk^w?=^k_nT"H^vсwQo/*2^Yl,OQNOW R,oئM9JF))^W&lmJO'DvQsa7LYP $s\|+OKIX369#$byɲ< "*=+Fy5~#x{ɲicc}wRRؿaܕ]w6ӷKr鯇5ϋ,V<)"_N3W¼Gno^H7$ z㌌F;h|v6;r],$k ?^἞yv P+ɶݶ/ע+s ( ( ak* qZT(ǟ\OvVx})*F yf9bz^3=koNm\_O3[Uf&S$Q,$Np7öz/4 \jڮ6ko#F6DI+/h|c?U+IBc:6A>R/hn+%z2*|̫c)_8zm6YےɴVw+o8EMs@Y ;dLT~p{ oPey\5Ą=Tjd 9M^zGIF [>-!;y#[}Lj2HĠ@+S;r?~L~?5-g֞ x],4͐PT~ץM6DTf ( ((?m_~ 64^n-5GyaS!"Qbld HVD`U:dB2`^`mK\/~0|W6DžOէӣG͐ZN>Y/Kj+vބoP T$RzwmiڕwVcthUd`#"tGGk:,s6Ңfsk'Owuی<b˛W5^V~8E|Uim,i'QO5-bIUnk<]kViN)V=x6]_H^мT1ZGP9' px\*N^鵿tS s, 3{iE+Yۢmzv߹ڏZ-]jUwƞcI,@t<kx;G]g KlJ''޿F4. :EKO5?Ex:hxgcg'$]OGtQEIAEPD j9"? A#I⮟J4jmz AWUEnRZ*qҹ{k\4j] ׮j] ¶@¶@P¶@¶@x4jh$0ya[9;S)(v0;?K44#?e#?eWkgwӣ$k%ѝq%LrsF2d']_zlG5 Ư)`-cv:RmuMOVVþ;헊'z!x'2J*2*2AGTNI_ ?Q ?W[xOExf&[mTFCp=3V4hayb6:y7 PS#eI9q_#?e#?ez#?e#?ez#?e#?ezo}#h6g;b'?޺*t?޺*(/R/_ 됯\ּ?/a >& bl"inglqƽI;$9%9(A]]II#t lG,,<3u>=F_$0f,>.r" T]BJ8Vj#P~jJ4mH[P_P?=}O<{9N-~\%Jmuk椟|˟<,L~l@-fk^rω@J?55??_Yv?ԿُT|ϛE,qqݾs>WMM%woʐ [+UaM7wڇuڻ%2H{fB*Y1AoU!v#>Įqzh?n ( ( ( (3N4rm2,FOԂDU 1(<O|M' QʙWϚg3gOVH.B*[1b }EDNW*5%0=ºdZFi*#B[QVHQEQEQES]E(=AQ@8Y~ e 7|=m=]y0(GIVYuth l"$-д`dX -~ q [Z sp~FǫiEm hXbU2dc.~Ěj(7ᇌ5xJ'}+h4i(j-Q@(nRZ*qҹ{k\4j] ׮j] TQE|Fqm4jQG 5hbc_J- RO&pfs[GH"? `rW"ռ ~^&y de=n<>)!O~u[#o|cj67]HԔAww#m6z׷h@ӭ4dm 3cƪ kZ^k߃O?;A6%x\4)Aas$6$Xfezl/jnB;`0Yj_iiԶIdJ )5Kfƥ޻.ngZ|ws=>(<2u j1XLTn *a|!,uMPi|گV,Ů|%Gyo_~QT{O6hZy=~>niw5<4$ k"v+8hٗ2A|?NtilLem^IdgTA`}QѴ`6gٰKS4jM@2Pҧdi_ݾ( ( د-m7m7 ( [%f?$icCZ_T׀~Ԟ ,🅭u @i~w"fH(p+8J:9 VcVm쒚~HeXt$mM(ߎ5 W=6v6:[%N3E ZkN?-Iu SI=֖Im@g!5%TܧwFkv~'ĿOvQtMP+]ɓʔ*hܨ8;&?x/i4~kh2۬q71 _xꕧR#w'uY{2\\N29\ iҦQ%gIړi>_q&'Gi, ro{n,駱e=%d ؔ cu~%I~OX-R[Y Hr$ڮOGTԵ⿄G75H,-b5d @e!!pFG?ڟW 7Msn_]S{{=.9R& fyvF|yR"*?b`QroZMK{ܙXGV2TMbNROrV #box &kmX;zB###'?.~0׺g1Qr9&MIx4icM)e,*R7RIU)8V6yuXZM "{yd}pN|c}oS 𶅯Ckq>~i5Z\ۘog953Px~QU29`t/ Emi& h!l魶Y_^jvvV^j&6%In +3+Bp8W;|:}N=k}BZ;.gXX01$F`3es~Փ2ԗ_kG׺1ռ⮥;f.Kmgi6"[l =8][: MPx~ os ptMua-Wr|rW& Vq^Ԭus>,keuua,Gtb't, F xGQTPۨkH[OR&تwZ4OuN־~{]I"|K|ScFU?Zj.vERy6rwʽ?{u7XƧȴ ǫ*[ok#HH{HT:x/I˦ 4D2I{(* r+ŵ١u:M^W{v,RNK8E]_34O[o4|UuH_$Ƥ$HζPcm~KhލJm>Kj"F7yL L$G|*a{ZR %=mY+,BcјUA99iK[w2}?ǿO9bOj7~URk=Q--e7"4aȸa(s-׍4m.YPUյ lb%HDu2"`G6?>)?:<:UE_iZ\ݢ#kp17`ne͵/׷m+C75AXX׭nuG6u]W6?jّQb]Wx_ǰD=*?HK㕶~EP (((yߎڏ_/ӭ<:6߶\_j0wesys[H}B滟 ~6VN.^}5뛹-4O2MjX©rc}7kF|Rkj%4LUӮ.bd]Č%ʕ ط|k^85_ZEcgJ@v㙗2ø|ߺ]pu)|m [o}|_۷Cm-΋o E%{5jz{A^9:C+P]WO𷄗&I,XpX۵}ܗ,W7ê֕axK.Wïo$K)$f(b+! s2KrPҐR߳_{/ wT<=_lӬjV5 ocFD1Gv#߳7-wM9}}u{vK[wnK(<F ^w4cu)( FGc4Ofáf+⾡ڇNZg FI1{9m I4|%_^,] 62yv[Df!u2KrW| oٷh^=ЎokVu0Uإݔo#p$m涽6עJ/WٮdI}Z^][OߨȠ(+}gOxÐ[\V,|Wjn KT]ψOҵ[CN o\iam nxnZ:m2Q_uƵ^iWv٭lF/0EHuP1_?65_k\ri~&k{vllฉ"?mkX$dg .> ǻе_².5I- adڠM#>yC~Xng5{/{9]ZPo_ḓewgm] /㏌G#_Rz1^CFn&M6HǙcm#rv>K?hxG -]./x53]Y\O7Is tHJ\Czѭw?4J/uC} kZ%Q6вG.g]}ǁK7u+;<;{/#Wk-b$gʚ?-Ԩ\ _[/Û˵ޱ5V]yZKџ/|5 /GOqKHt-6R?ϦcmĢ nlВ z_Q?ZD>O>(,4 k}9^)kfn^Fd{eO^R:wL+{95P0)UU[ݜ?l^I|'}ydr&KU K`޴޺; }K M-,i_>^O hn5^#XN΂ Qԋ0Hʋ̌7?{7zN1g5) BKI{T~m|#xJJ#wkާB,p\rxYZ) 2><{o xC僚H?j%#ͧIJFuo$nE;u<[=\];2y*[r+~T|kroz|aq/ 2}NKmMxZRu& -'ݖWk^x{EMv= ŶZ,z&PVw0}Icc%XmAχ5׸zڝp:K%ƟyMf1 w`?9韲O :m<qmC~miEܴf*BiG+TII]$]QڦM{(J*.$hMtOG)<ҵr7_;w숫Krua'ŝKz_ ./u}B}b]'SK6Iw+z30~ /g\:3 o!;%Kp=Kt/ xEi5kXt  b]w Wp5&fd1jBڄ#>YJ=ZfNɽ9t-~W[EVEEԃ'C@'J'}+kOtWi+W[kP=^ZAWEPnRZ*qҹ{k\4j] ׮j] TQEq|54V[>+d[X]ݬPH*o ?)=+|1&&qiE53[NEm%Ē=ĩ2꠶3r=vJN`=J5*ğ ߆~Un#Oh e{eX2*at=sO4nJ::))"O ӯK` [InTd ⼏Ux%Xh0@7p8G~ㆢ#Rux !葐C72/_7ovRoI~@ ~x[@m6MO_W 9[Obn54˫Csw 2⹷+ymr[4l78k;\W}~xėuO׆>']h?ڑF o<+2$ *o6Zw5[Jյ_M᳷/t[Icjב,D.ޮW >zZ-|2r_kҊ(0>z諝z(((k^/0G%m>|d>;9F]g?mNh? [x_4ՠhKD̲mݻczȰ((((((((((((((((("qBzt5 hA|WO s#I⮟J ?urZjmz ڇTa*袊nRZ*qҹ{k\4j] ׮j] TQEy?:ڬmlKEuhA9z}7ﴍ[M^WItY4 ypT˿Y;`x^ljoR[,9*"] IR>=~G#(ݴ} DYh}nZ+8B,V0 zJx+gGaʊmFD%JB䓀q]=힥i 񆯥GL)ttbU-E@S6V8:wB=^O:ǃ#hTU]A/Uے'`&+*v2rqдV]ޮw{t#]'0ƆMm²«poge<3xBO),I@pG8NDzދ{xj+KvEKO9!~ 9?kR*KV?K%k^_;hS?&|wOZ4K$MJM ۶=X1 X8v׫ O|e|?i4 RR@60d@ RWI?}Ϳkvqz}?yS7=޹xbTɂh:m<| Tg]0~ޢqm_/Ko^xrm_]𮵦wMjsjW~:MHkg24jo* n<)?FkU}%ܒQG`Z,aZo.GcK6UTa@U'i' ]#φ> Ï~.𗂭[LѮ3$I _=># a˕ekJ(} jQEQEQEQEQEQEj9F]pG ѳPX}ZakEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPD j9"? A#I⮟J4jmz AWUEnRZ*qҹ{k\4j] ׮j] TQE~yзlj- v֖0m%,ۤe@%v^3)9xOn#ҪCg"4H<⥰8t~ j~3žfcAwhX!2Qf!|7.0r>5~~_:̀yx<팈ǣ| rO7\еֺlv$ރnv=XxH-b熬<7g~nK٥ϸ@ σ WᧅΟs*\jW}X~ixrO=,<~OmlNEwar7n`>Z4?E)i>z5_Mr×1C~HI4-sg寐#!Ye#8Mr_O5XCjZҦZwI$;p#ԠJܞGm&v{qmoެj񠷶@~>f8\Fӳ{m[Z_\|~!|D|cዽ4;R_43XՎ\#rck(>I3cվ|S⅝pksFnz1Dhft9Yf#O[<]Z\]Ew eZ D.Ǘ-x#ᕇmSRjږ\f0Y kgF% <jNR^K[]?%=.(PW|O@yhn?ݏtUn?ݏtTQEU=GPl.5=FU'i_ %W+>7Y\ <[ofHEܰC(@vWQ,nȐ܈d, ^)+ׇ4=SТYNor7(T={xOxq5[uxܣp t,TTiTlgn^;_=C7?s)ru73YHĽGnV=6@\wJemrx\uk 6h٘]E[  l=+ͨBZ4 'o\8[|bB3ͨBZ>ͨBZѯ!o xO.>,Nlɥi (aXa""l@zoٵKGٵK^k.K1tlMy}>]J$h R#G"UsI⏍ <#h$|G6Caay23 A& DY+أ-ǣ}P}PO [x%Ӭa6E$ڔ*HOhO[kPxnĚDZԭ}K*}HK׺G\6? h6? k|1þ,fF jci-O&%ʪJ22=K~.𧌬Sh&ndgP.FٵKGٵKZ4PwٵKGٵKZ4PwٵKGٵKZ4PwٵKGٵKZ4PwٵKGٵKZ4PwٵKGٵKZ4PwٵKGٵKZ4PwٵKGٵKZ4PwٵKGٵKZ4PwٵK_~ߵg_:W}j:F;?lj]DRKR" Ỳ Ɵ(m*Ȓhvj N0spUO1}Vnkk<3ejֺ֏~?ltLê0?4r/Fe<|+­6NhX zpa*kX7t/oM7/SP5k8o.=†F `Ej\c6ʱv&X\B}wO;(<Т((n%Cܾ8o+םf1ae[QNF}U)5(ׄ\x+j#OGmf1q!GrqǽyuJWZtZTrw$;y>m }w87qP]ޯ=`b&zܚޙte2[%u\js\-\eK5 PI;r2q\]'] ]5H,+y%Z6 v3xWc0ϭb[mf߷Ecj8rC(HNùԢoƾڟ7Ғo>(m/Ya]ŷʈA]%}~MHy\p}77?AJ4.x5 I;-rwnq$2y2 rUpWh5}%ɟ ZvvN-bNǠ=ǟ}emGuiC,QΎV[B>?e|+hKܔeֵɼ\Hkyn`C*M${;kk3w\k 뗟[Z2z"9jcM姐88ÅxҊROC6~tCSnyiN iv2P#+_A-x—{sk2 YNU_F0o$?\ux[ړ=*( (2''CQ!1OR>OOtW1?(>!?*@W[kW%נ z F->(nRZ*qҹ{k\4j] ׮j] TQEQEQEQEQEQEQEQE=}_Zۏco]sۏco]QEGSOnx7WH*Gt-K~Ͼ(t3,j$ِ kZ:Hyj>%a2_k +x i;w"y` _|\³M@.k[y4ғ[O1yd;@?گhNKt/g=m^|`NúE&Yæ} `8ʔ C W|#XoxWš<3>r CM} J dKIaYOʤ澭ו0z[ (Q@Q@Q@Q@Q@Q@Q@Q@~=@-'N$jJ Y_W%|WwRjnu_<\)ܒ6sfIUW<7a2V?5;'=JE 'MVf_QcԺf+>9v Ů+Nrp'Ue`?+/7E]Mi[?zc.}vHsSUofJEK^1Q@Q@s#03*?ܗ? nj/tS-"`18\$qXi紓_yP,$idwAL7d. WbX!! PĂ !i} ;mfݜj@+6M|!C :itft 4,& *$Pd?h;O_vf6ohe>lRZX] {^cO?^-K}Pa>moH7 jSG]fmF?Quko;vwF!a#8`G{~Ix\v&v)nT`$n)cPj-:mjì5q**B)+~xs< y -Yot_#8򙅾74ЙGχ>&(fJ)[YFY]EHLc_/9,]룙vf*)Vmv=(("qBzt5܉  Ff<~i_<7_V*76NI h|G%?]>5xJ'}+h4i(j-Q@(nRZ*qҹ{k\4j] ׮j] Ulj5oG$l[OEs"I.Ŝ,@"AGjSVXtXfG4Fd|ڪZkm5 ~-'O&P&%&! Q l;H2HB9Ӫfyll줇O4[h-A J80+k=:; q2@»#/"/ xc|>s[٨"nP )tep)98k/2l/9Ⱦ$jjw頻xwJV<{c+dR$.NkӾ6^j6'n{k+{xYX$ GRHF*[EsӭӃ]K+:4c<_LsILG[ t_4]p6m`'P0eO|KqI[e&> ?|:ukcKAh_*v빈ʁy{9+G_Ƣ'2i륵Ϙ٤0#nqqK~_Iw~s7ƿ? |>qCOtMSK'զ=,4Ej*]U6g2 Yǃo]x[S5F]2l/H3Gd3 ({~Jx7.o.fH't8e,KB"A_ <64:tF&e企"eH*vInNoK;}^U_M>6'^ ƏFf]_R׆diRi[Z@ߺMm_D@]ទ-ĺڋ"9Kw%UKcQ.4Xt'bX/`,d)db\#$W_) t"6Э4G'|Vg^F?/?ÞXb%v55붟'";&i(6_OƩ<^j5+hjm%:TvJE*;_Xt{g G@gh.R(twe] I;`u峑_? gY%Okۻ淳V-4w`j \}Yۯ7j(aEPEPEPEPEPEPEPEP_P~2]BhԤ|!dIo8v1,-qW>UAlhgV-jU)4cKß_.t_~t&}:D}[+H ϕ<=ԣ#; _^NlMN^V4R9<ŕ©h׭k:d uV|cuousg wq\%o9#2g ׏~ϑKxgO%힑rvGm rG4\2r#dGeʓPN wFվ=z~nl{ x@ՐzC ez_#"&?izvQ-p3=ħ'?3KllY&敶Aak/rNhv)B^4iֹcO$ixiRWTFI c\5-G9U kgի*EWfQEQE|7X}Xq؏}?OI*616l.:4Ȋ@f80pk;й>O_j-^<#maY)|Y+RGD_7=uimV09E W,AS)d\G='WHYIGZ$;S/•>&rǫ 6#SE8Py m fo(0IX ׮/xK>KNb+M2+0l9\sV{K;+4UGI@Iw5ֺR=/ jIIY"68Q=!g!wW|zO&a]M}P <[0l{)sk׼+xU.Α_^ȗuMB:=ܨZAeBիvJK#˨[4b@8 +nk:kqock c'jLOrI9'$cY< VjFrgєQEAQExAz%\{LN Pqтw+zb'E\E@Q@Q@9~|7ZKn5[$2\6k[.bG$!y#~ޟe-b~NҮ.nEqe-3%nuO#u )4KӴa۰%/ ?<=?n5&=.qwdMe[k%:edgFR~;:wZZxSSo>)*}Gc dbr0ď|-xBM\sq{ l⹺ rx [_\z]}zy+EN{[ ?DQ[9&TL2JJF#i`ҫa@uuul!^:揩j:vcMi.r8Uƌ=g$YMԖ?ږ"$2 EmFko| <9.XMh\w˧J["'Ē6@@L-x5kvv4[^}{o},qGGĝs&OHմ nM22[j2^=aʂDhlOI⏋c3֡xwGzcOX˨Ϩ^Ds̄$#nݎHr>|dk~ ޗ^_uU,}#{u%"g~?~/x^">>~)x淋縵TuXB&? o}/Nϗmקּx_O|,&Ŀ]kI(L^d)2a6<]ȯdUo|b5 |4~ib$`k"{R=dI".9ȨĞԾ-<7N\iM7#oK֟UoFx/þ2,l5%dZL>"MGy;2v *vI%${z/q-{MN>?|I? _hVk;XȺ̪az ( ( ( ( oy ۼL#KJ:+,5uxr7 sGsCxQ|; &`?_o岽Z)NK 'Nc[NS1ҦE?pO=5ӯ;][7?Ϡ/~OʍRr$~$‡xBl"Pgx tO"ŨHyK#|XpJ#'{ԆQEQEq.V` k ɤZ /-|4ѫ@#I⮟J4jmz AWUEnRZ*qҹ{k\4j] ׮j] TQEp~/Jxx5m>bdSBN|+3ʐC#Aen3Lox?ly\Aah Hצ^#M"OQiR_$0݌Ā"OgYЯ?h_4I$I#Oss95}d  P4k{ 32_+3N95[D-95hLLIHHrpH=+V8[߅ 5[ CzͶ52Jd1h]۾ӥK"o#ey .+j; xkR:MP󭣓pŸ$sSm'MgArOxwJAK[(Eh6ylKrT,;o}b/ tqgzl`MpRފ @Q_|) H<]ݴ2yèwIeVH"(z%7gi*xrCmIHcYcJ!TdP0f#q~pev9_8]]X崯/B.Chf_]0Ͻ7* w19o5MkXРmk)f7 T",W*pd |UtOhxZ]Er=.x-O"Xff@wyO|1Ӽ)ǂ|A^^] )ym#iavcxmx۵ k:_ڿGȴsWLϬvu6k]iG-мmnvvTլ om]aH `Ϳ?=o oM4CĚv5խNJ#&I1 Yd/%TѵL+_ 5;NK!Mүu&[S#E?a*R>lv'?>aW#i^$V+. `|(W=[ Q%.dϮ<%~*j]A:+&vFVG Ue`AG_? cx2cq5m5ۨfR6דKdX 1` |X~Yx >mei"1,|anbגKoծ3治o]?F+ >$Ѽ-g? s}2[THp#x7BF$C2~=GyD1+頤 b+8o;Wyxz~}f<#®]CdD8Tgi?.JҔ_KTKѴk7_I{7tmm+ƅ 2mٷ6s.ɪ=_⾶Sk" ù`B7e<#]Vh I52q]}PHsMp$ռ9k߇iz>}w4_"idE hAM5ui_[ĵJҵ|k^zv<,K}_i'Tl.4FiQuaS8$/c"7B|5FiV36˭G,u7[H$Tn5fǺ>1|U]6V^*E;^ ݕ_~~<|l LZ,vi%Ή-߹mXJWO~{X~(7=!xA<| _PQI4ӫ-Hcݿ7nݻv1q ~gW4}V}S#n-}B̼k0 %7 In|}=;4ѢH Wa/mim6_s玻V>'u1ꖭc.onmKL0d[f,>8 Y]wMoi[ضFOѥ燧Si}>TmImotxQՌʪ f`6GqFVkWih}|p0@<%n:5zVuHZ[M32cy P<Ūۛ[9#lВJ72xc ;Ŀ|1x3fm46{L2HI$&XmӋ2I{̾s0]aa_eZCǩ-sK?7ˈ+p^B6wm,z=z5G6-sרc~1eٺ} QIHYS7)OQSمQ@Q@pG ѳWw\&#?lpVdX}ZQEQEQEQE88Yk ioH5n,!d 6HmшS,Iaz:o/tZ%j_R-uXo5+?jZgk XUtQ7D &JB z7}sY_TԞ^x{Tҵ 2ky HGO:d$FN^3n6iQEdPQEQEQEWC^狵{{ ԥҵ =*ݥv+{>!x ~"xǚe6|E?m.켧-k .x_I5MkzuϥQiGg$Il{VROuC6vwgמ;~#*}AWwLj5#&g->qxn:%gqs~ 2|Y-ľ6Ƨj[tux^k "am:Lyhm# kxWkSjj4Ivswӊ+ٷsFqigKe:!?*@W[kW%נ z F->(nRZ*qҹ{k\4j] ׮j] TQEy4 \~׍|BF9u}P.ZwvhuBɴyj'pyM!7[_PS_owo`W5-,OĚtE/N}}uM !{艫y֑a \[\%1y]#lܐ {4_-F;Vԥ^QO*i.<؞ 4; ~xbiZz 4#ԠExHcbQ"ʕ99i;W->'55o$Tk/^(t$ʳjvcMbA,2͛ز`'1x5~,ޒ;:bZ2q,$v_zx_EwM}p1وU *a/,%]ϥ>^ziL =#)17E MIZkvy$]Ew<n~2i-θ51o73b<6U7{kHO[{}J-1[oE(K<F vrio? %ETr1.뻏iں]#ݥtS g#;qV=&m'▚۱{}@[gG$uF!+s>/ZC:ܤ~lUV@ۢtcv'X~Kz4[gFc9XpѮл_ɟ[_ +ox++}<d XjE+<#t6wRFA*jׁ|ylt t}L[o{samkjo#^y{%R@#RX ER*M[})GPz2 *@@|㟌!x~_oxOש~o-o5apmCp *v2V.[⏌[om vP֢Χr5 _j4x[;I|M&- 1}- ("5g8k]/[Ԫ:K?]ݵV?@> cx_tQUD$.FkFQs416ʌ++^fkt|C |Ok5ڴh7zucwsi񔵜b4-?w7躯,mCľSg_۽k\K/dhsMSmE]&֚KO^[EyM>g_P_$!τu} 7_Pt ;Śuu U=nu+^MVR,b(9IV_j|_xsW׼5Z\Kmwɦx6^_+d-uj9(o~lX|泾[]#kwnzWGS|σ5'-^*A MB{V]O-oxR9U!2DZ4{=/Qg'<}'xsPI/mn^)ú %ܒApNqOKӾof|ѥZZ((wK_,x_FM'j.V` k ɤZOtW1?(>!?*@W[kW%נ z F->(nRZ*qҹ{k\4j] ׮j] TQEQEQEQEQEQEQEQE=}_Zۏco]sۏco]QEQEQE kE4C'Ѱw\&# l5^3_60x~#Ʀ4H$+LK;#٫>|ԬB'/u/4v.ffxvW/ {kǯ+}:NOٿqx6Z5ޯi-n~dyUI$##!1a[mN5j(Sw躢LtkYRQycP`F;?vo:%uMnk.i!he)hw2|+޲GMxo~)oËuGÚtz`cVxy | 4wf?Yߕ}~>IWXյkM}iZ]֩o_vuhd;lx|BaPɢ73^uHmjv_Ϧ5徴SWQYJM]\1]5C?E΍d4[o owG/洒7︬Aa!p$`\u_zm6<#I&Mh).5:edlڑ$JJ—ʱ{yǏ2_\xZ4ݞL_2v#6pݡ2 nN*]#wzV>uŴq  OO$CR3.G+S袊QEQEQEQEQEQEQEj9F]pG ѳPX}ZakEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPk? @XO[կ] grIo?(>!?*cP|C_0&3i-ӵ;mBv,![/#B[ky,͑X_w5ʹ33`CuR/PHɥ/n1^h0މnq$62d2%Y|]մ}AunF>1䳄Kn.ODAUՆZ߄k=Vkn5Kh$Y!~]UE_ |4+-V& ".d%%ۋz?Ws?^uE^lIU.2@"S[ZvhL mgg K$rI$y rݶoe}Š(0((((>z諝z(զx,$8ق8A٠ tw;8XZ(>s|AV3#iXc3]&jks ̲ikkd`Sy^zx?cX99'qyH:Y/E_^mQO<6Iss" (I#%<$+ͤkxLm7N ˱wo K<2I&D"@e`GQ:+_/MxĚnaMvW),+}tpS0H#<|fM/7ҳI@%z-z]zǃ5hZ{LI푵[`r+ռGh2A6jIeb3,Msr o8Qq@4W ω|sM3!ȭDS$e$Gz;SfJ Ӯ&VV`b6c$O]j^[kZ%76$hەtue#Ѡ(((MCG#^٫otm#y)3N;[PR)+ N*Ҵ++^z⎂f ?ُ⇇4W( Jٮ\vǙ{xQQC4w$xPá 2RזմgAEP0(:rhoI1Kj::"Ȝ!ys6jj_jcml*Z;hv}0yvǻ18+.k=3Mn&{?G^sZ?{KqIGG^Fx7>*T> tKi՗Y mi$k&.h+Xf8$}~2z8&4meB+UGBkqg*}/ƷSxVdg3$,Ųg3Y|FhצGFW1j1k,|4jmz AWUEnRZ*qҹ{k\4j] ׮j] TQEVӭ&'6iqqFs;$+_ C oeqJ r,r!''kSO-Ԉ8_7~w]$R[I˹mی'|qUbqMZ}?Sr%ɧc1bhM6*q%e_k&*:Td2*?!i{>Rߜ!oZƭ?7i {KeRn%r a61@U..# ԥt]~זG` 5ZLRԮ"wX(gwv!UTI$95ݣxO|5{]z7wS[\ԓLͳ d"&ڵ>7ρ# A^!+4ŸB$5ۺݙ>ҵK ZV}mMochhY]C+)ʲApAWy[赭囏c:S炗tQEAQEQEW[ݽsxwK_1v]svy3hz諝z(z?h/1W)2zVʖ2r@m˽A-\7?,dX텬6EV|2u>WʒZ/~2[YBw\JBZ2\zZڳZ}dU\#?3ux0CS2<@ r[Kalvkq§׸Zez3%ۍWzmbc++{Vχj|tȌ<ᇙY RKU5ُ%ckG?Y5xC)xDHki oUX+`EF^iZg= &j^<[x5/n[O h[v, 00X!hm8p+67W'4[; 4fRKP#9$P $huo ~vO<|\߆$UoKMGKěZ Fnt۽c)JBe)^oX4WlAqcůCk1ڌy}# 07vSӟ|>,-4sI,1K{.bRH|qk/<<ݮf'ʹK6*M܈IU&0v [Z[|KAo³]fFtSb^}xKI"609|~[cP^W2Ć ebTAeKAWz>{MF,m>vAig AJI8H 2IR:vX\iz7WQS\F,n0)Ah_ {c俉?q^6.ɣ`H9|sSg&-G¿yYb+vCNXAqh:RL8Ÿ|1p 6S.bU-'铎OIYAV=Oei ]>>j |N㜶y84?;O룖_OYt>NG5BlmM3ALF8qkau|;毮;mKtmmB]%`5 ڏLjSڷ4[n6Q L 9+Ѵ*[ɴk95 wxMqpʨe*-pO_vЦ_>!|.4fVi{zڢ{jd(gty[X*9u:>xo7#[oT%=/Q#HYM"G#Ҽ!- xtMN)K[Ht_>dVE,~9<+B!ti/n lyI lcON^[wdݿMo[hzw4-;z<~Mi܆h?QZQCmRIYQHaEPEP_ЅjZb1Z8sw_pW_I&[ I_˞8 }> Ț~9KC.>iD`დUEj_FJ}y<)I!5r]=r ?k W3O~AEVHQEW|K4v2:ź)0Ī}s_+6Cf7VDh޾'Rw_>/ 2qr'6|TJO9uW%:ӭ'ev嶫uePMM\;+RO=P}V-1")WBFHݵF'+eu;dCi~#[0KoffnFO;Mamk6֒"]"2%GowsrC8^W-}gKv Ҿ-x]鲾7[E.˰~5\W}3v-1N5|ڋƿxrjpGi:jɝX&QԅSDind[u)Y;';Ӝ<ҼhEioW,IU=mN .a聿mW?~hN^|C[;E(`3,+ؔ6,8JXޮQEqEPEPEPEPEPEPEPEPk? @XO[կ] grIo?(>!?*cP|C_0lQƒZ)NP.OCҩ+evx=jmG-rH%m .=x(kK[㗈5qkmMhtQ?ZֵJm>*/_+ߎ0W~!|<֡kFm[:=zN|׶*$Q#b&bQKnhnoh/|~uuuvG^kȲyҴѰdAJSt ~U요>Z] /`DnY"*0 ʝromF KCR!ֵ52)iLDT}Oݰo[cA%<aͪ]'+5Ӭ3Kd2 K>Q?k}Ş*lI't}J=fpkhghQz^l~|/{o^žYЯq h'mvuc}V-t/Mueb $fxԝg'bC$1'庿K[r_}QQE# ( ( ( ( ( ( ( (<''$QjS 1,dKoz txw;IO|)s2\h[>F{/| "KϚ)ųĺ-q[KH(s낊((((((((3 /-|4ѫ_SAZ%[Eָ#2|-i;,yZV_{={F}SGO-T G⏈_'cMC:ꗧHx྾&8Wt0 fp@#E5 U^i~']AgnCQҞY'5s7&nTBFijZ~UJn{y+vh :ͩglo'Nym$!E YVTc5/ G OZIʺj+HmIYHۗ򶁞ÿ-᳣[\7Nu{u:q_Zg,nPn&)HQWj~|>meq77 ޵IZi_E)auD ^lpVI8 ޕiz~=DnAnY,W]I"I @Ic<3?ǖ7W k5mx+j\ DT2v4잿祝K$vzO{C'égemJ6zL5 H5@򌂌U?Qגx' o,N.*)O8q}I<׭֒岷wET((z諝z((b-ٷ҇Yc*c 05N%L660iszx8C;w 5 z^k=&:ܹI >$֦2p/-~ eW5o[-Y\ /<=kuS,5wouך\\L;I\N W{BtqsN,G :Cfw= ?V_um?zEgkio[Oh@[(:Z4Pw= ?V_um?zEgkio[Oh@[(:Z4Pw= ?V_um?zEgkio[Oh@[(:Z4Pw= ?V_u¾4,ןmdYhJD7}Wە纥34ji lߖ僮]rf8gNگ#8u}?O5ZA4GDfvG|@A?8.4?}/[xgMvK*{0@룤ӂM(,(+7Y]CJ~V "?GR֕# G4wBi5f~j> ?ݤ:]<6΂養IWz5UPͷrzuA(O,a"BО rrO$俲w«bWne)n%0&bDa]'gtXD[ ֿ}(^gRzI5'}zc8C*|+⭧Kv^ L6Oil,?'|oCZm&%7y3o^Ƌuu"I}: {);$G*1I猼UgWN nheu\KK%~>c/!sx5ynd?&( d/\&9'2Lec=k oL hU6v/D("|-< ؿI[XkpVS[(6kkiC㏈o]OQgd&- mǢ%1mq--Eyi['%{Z;g}? wu7_QEiZ'|0|gs=WXM=uu4P$ Mղ<JO|zSx49Pκ&om$7- $ ϵ2q%u,|gx4lFd}J+@a2/ihYjl:$vƽ '[me#dxG- 5xiI?( okS\c=s$Q9B!J K?KOowoV-Y{ngм_a^iMn>ynZSB9:=?] ս:Jk~sc`),WhQ9'|m*OI*9S]Ե_w <,W$Pmŗ 1 F'x/4E&?y(cܓUWWyxL IE˛mkQ#t/-!|N[w_Wvx7ĺڭiu{B-rQq<32ĉ|7Gj^m% J"Jܤ2H "WF4[=WA|K .tm/Z[;[S3X4!L9(euo~zqaxK>*0wyZ|;yiZVkxӡ$1F˘Kk ;`Oxwp|r-Ozm<9&z֙pA;]/2yrۤuڷSgK'ψu?Z^ĺ|e+4 u7,qΊۛ5?_¾${ZgAWwK'T8Ė-[+-OLjtO7_|I6ωo&|f+ܙ^A)+ݟxY s)OY.d"28`1PTsOÝ'79>+ĺ$?-!@I/KA9'͢/QEHQ@Q@Q@Q@Q@pG ѳWw_~ߴޑCNo~m^QÖcTIg$CV2K۹9%d/+Em+^퉥x?|={ ~DMO^uMrnNҮP}6GrRagJpk'j ((((u;GGti"[IdB2$$N "20FAWMxź/)!#ɮҿ}6/NO1m19yGaΜMЈދSz+CJ摥ih>~ػGaw /$Mz==pj6mom?'S?`d_kY4kxRPztāex1*_n1:uf-ٵŠy]( 2hb_;|"+]-o_k+xgehO[[bw˧&X;e<҅m~˞'WgsCƞ%o?:-ٵ%imQO;o#7E_=ou%ӧO (aEPEPEPևE\E@Q@Q@Q^ %>8կ5kC/i֯|U%sJ¦Ф"z~ x~1񭆥qu?"Kv=>K{K+ksm;nȌOvG4*(z[QMhܞG|)T~u=>ꚍk Oٌ;yֲgMD+(6qƩ0+fQ[?SUQWo ئvtXp `1_UE4K޺n{$.u>@gR|[sxQW SN i*ƫmkl +D33L1 vztxOaɻ&Ē[VV_rI?s~hӵ֍T,4څ6 4=,@ϵHkש`is'( QUK$ =E[ vz?FkףG^$i9>ފjڋ@Kf&Cpm(;an3;\߿?s~EghӵN?Z ʪY x em.s ) "#8k׭ο.KHu;{95 ŭO*D (^Brp G^vzѢ3k׭(;G^vzѢ3k׭(;G^vzѢ3k׭(;G^oko"]- bi$x$[k$PȨ빁MCG#^٨}ց+^3v%![AERQEQEQEi5o}u{m5_YիJDЙRXı7 5 "MӼ1{8V_dS,%ÉIiemK"єT)_2]>`[n<7xw:ȴGR\hF[ln$71O4fy*ZLJe\0Sk|hn,FZ>F,E/(mIM}]VZޖ-C_?Exg,ѼEajڴ~&Uyu,1)%BnB:)`U z sNյnT`y%,ǹ7Z+-w ( ( ( ( ( ( ( ( ( (8+Y\DždF}Ok? @XO[ՠ @|WQZD?xax @]eA\+(j-Q@(nV^hs@9fpkq\hԗyG.װj)kεu7C@AQ_?ZN״K4ӢԌ "n  ː돏Z☉Q"?ր=^4iWƦ`\.U<@ d׍iKD[_iZZm3ݣ ,}V񖊺?#I3Yv2ݞ$xƫ8}r+(I/[4ogY_\wWw{z[RGY:A ך⷇<hWVږjQ=Y[@fE¤jH':韴毥ivzo"KxA-B#gzᮏ8vi[0Ws!KFGө10N}9M.jϪm}]6Ovq Љ ݔWz z }E|' Љ Љ ݔWz z }Yp_z f~!q4# l5>7Oq}4nnD|nM~>sӧ<}y@|oOKм1(iσuԆθyex6ʉ,~~ȯ-5ou{[gPۣk!Yh^kvs]jzg=yV^+%0K6.P̣bUW[BWž/a8yVվ`2v+S 3-Nu:otKK7lm`dl17sASZxwOwQFv7EBf!r\$v]Z={_E7/!xf? #l'1i3 \s%_̶V~׺~5Ԗqڗp0=ēI+N>W<{Zdz#4ǑM@le k\q]g6q_]\iWruӬ]I%wqqPK4]5lߴ]@XbMzU+6H #yIXna߰gjߴǎ5MWİx[PM;ŭg|9XuWV:26oZR@$p)W/%t/@ =΄Ys`!i $7M)&_] Xm뿑?p6~vw%6x zunAgZ~[]X=7{]}Sxctu]&Om8;[* 2 ῁sjgQ:sjZ֧tsL%$ ooԥk|/m¯Iҭ!K4vҢix2J',R0? &.O񖛡nMq,Yk [&H4+},->|$Vqrcۯᦞ{=|m4ח㮿|G|U/t^mF4/48Ft!W}Zћ{G]; T'J O~Ğͭ剺k{˩lM&Z32CcN }D/QE!Q@Q@Q@pG ѳWw_!k{]mE2Z6f:D+T%QξvF%7V2gsK"1[gsY~:ɟ8r*cPX=zea;U> B~exO'I⮛L^swu57R4>0 7@; 6>vVuVÀk^ b5⫋U"WրEP#ZNi(.q\kk2 pg50ds(+^W frxbx:9<~Ǔ`O`}+.t޼V,_ytVm8WUJ>N>gJCd@y}(>?yq8WdGO4ҏ^?gJ?zO4d@y}(>?yq8WdGO4ҏ^?gJ?zO4d@y}(>?yq:yҽK'ϺҾSֿ>Zs__i?"|A7nLWC|*C|*֍yω~0|)n|_hڊ"ַ̨*Ðq uȫGuȫT7 JMwzJΉwc2\Ḃ *J<ޠ VVh VVh VVh VVh VVh VVh VVh VVh VVh VVh VVh V=j8bE0}i ?|V|g ɛO;zU]򏖴?٠<>gJ?hf<8Q}+٣'GqCfh?ҽ'?٠<>gJ?hf<8Q}+٣'GqCfh?ҽ'?٠<>gJ?hf<8Q}+٣'GqCfh?ҽ'?٠<>gJ?hf<8Q}+٣'GqCfhƜz&sһJ4@l:N+rǑtqix?vv 6]8N#TVzy,cm4aXZWee1n٧JׁY6(c^1VGjv6ӨoZLyFE]jnkxxsX@mwe\֝^qgՋ={POg|;֬K{XHQ /!X# 3!v~_x.{y>;j+udwEg*VQVZ54sEo+eX ֓$ab&$nGXg Ԭ'EѴScX[i,m!H#2rnw%$iQEQEQEQEQEQEQEQEQE/M|g%ɋeg3"{M'-id]ݞ6ҵ?GJhf?$w$w0fkGJ?GJhf?$w$w0fkGJ?GJhf?$w$w0fkGJ?GJhf?$w$w0fkGJ?GJhf?$w$w0fkGJ?GJhf?$w$w0fkGJ?GJhf?$w$w0fkGJ?GJhf?$w$v=?J㿳һOҏOҀ8Gpcc;8zQ=+((gJ~=?J>=?J㿳һOҏOҀ8Gpcc;8zVM6+=?JȾOycFu%>W mWPE|eq׋,YnlDW w(yq*+/3QM62ijNJ+Lj%5 rCn|]cBGp#:÷wj:ķWlŌ1/Gp Wq׉T8r<"Tk(ݤi])NHEnuMk~mWLʜ;m{IÓ!*N^G%_3F^@s^1 ^]Jgd2HnGA837jCB\M5i5Sfçjmil%PC/fhu:WÓ[^\%汩ȓE((0 K Ey ݾY}9zq$ZskUv**El}Dw_nQ8e=Uxʰgkσ=6q_D9JH#Ege|C8UFqJ-&wM5+=Y?2j՚w{&H%Yd2#F8;P';-WM۬BU=AТ`prʩS^ǕǕt{߽3,F+,mi^wKzt>|`\8$GV! xhxrW\G y$utW𧄹2ʦ|;O$̷:D`!#8;$Bĺt;F6d8d=U>3?F@? & 8+c`(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣɭ `(?ɣz~l@B)!WvR *xmJ-<Jp_Zh< R@Q@Q@(((hh(hN(hN(hN(hN(hN(k">C[5z ӄ6$HբՒ&)Q~9\2AҦ`kd8";J+?:MW_jY ɿ PyEp<?g?oTQ\,uƨwW 37]?fx3&+5@gMW_jY ɿ PyEp<?g?oTQ\,uƨwW 37]?fx3&+5@gMW_jY ɿ PyEp<?g?oTQ\,uƨwW 37]?fx3&+5@gMW_j/uƨPLğpFS³q {\ qPKBH|F ,@` }B6n6Өm:n6Өm:n6Өm:n6Өm:n6Өm:n6Өm:n6Өm:n6Өm:ML \ (((((((((((((((((((((eM¦9MCN\e׆F'mzDȠ>LғD^4i@?sD^4i@?sD^4i@?sD^4i@?sD^4i@?sD^4i@?sD^4i@?sD^4i@?sD^4i@?sD^4i@?sD^4i@?sD^4i@?sLҽiGҀ+>Yq@jwyת#0pQIdq]Ɨu^eր:+٣J{?k_GZf+ٮK~khҺ}?f/}J{?hҺ}ր:+٣J{?k_GZf+ٮK~khҺ}?f/}J{?hҺ}ր:+٣J{?k_GZf+ٮK~khҺ}?f/}J{?hҺ}ր:+٣J{?k_GZf+ٮK~khҺ}?f/}J{?hҺ}ր:+٣J{?k_GZf+ٮK~khҺ}?f/}J{?hҺ}ր:+٣J{?k_GZf+ٮK~khҺ}?f/}J{?hҺ}ր:+٣J{?k_GZ?gkgBY|~3}`y:_[~Ϊ'k@Ē+ 3iE&hQZQ) -QE=)i@5NJVi1Xc5nL\b]r^y_o=].'UfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^ExoqmjQvZhi)6oxv [Lz]r:Vu58\:X5u d٭([}D:TQE5>=(53U+.v {5MCx :⋂4#^^⩱'UQEQEQEsm3rQ78,I($4m RI]: L rJ}9TAΈ2ֆva&LY4C ( + _~"to4wգ]*+vO5ek8UZQJ:W"ӾtR w+j̐mi$\4~b9_E.vonS[.*U}xS_Mo?`6x.tEvK1FJ(][k{GѼ!P |76̚ cl-ᴽ㮩iշeoVZjNϖZ;6ɷD=7 CO_ &Mr>Eu hKfbM8~X>%[Wc VzmSGFc:vYg> +eF RJ2H_kI實_K{Z+W$UY>*=:.^ѥ%s%*b_/l*T}%㟈6 Axn%ūMJk xYͶ-rH n-.Tp$ H#Gm4i${  ~2Qeig%m{VHlvmM]6<]ԭ5qC;FڤF\DKywW};TN2Z[_5zۺٔW"itxxÓxNZS^&m)ົ JW%ʩY_07|Ke&j:%ZBBm$j3)RZM{s3m$vmo>nNxO%3Nj:h>Z \]JNZ8"~ ޏ6|wNuNB 孮 ;e2FĊAp|dW+f_+wGؔWwxKs{k7:]^CH>d"s;v8}ᾥ}+_XZ v^yiw푢x#oˮ|>/Ki;^$z­} ̋"/PeInm'>Y<%$ EPC͔J ㅟ;r9fPr`n/mU6J&5o:+o?+|1kQwto%V2^-✮p9o۷ռ8~_Yxm*[uh繁H7)q唁^y?iCS_,Px z޺<)5幁d]Y|ۤ q &5'EM)+_c/_ۿ$ÚDڄ0ʛ븆"]LeBݙ][Sm>gl4޻iE|>|@YA:wmE vW7 !]H 67Aه|'Zi } w)yml$|%źLcn*Aө*R^VR_$8vz+ONh>/`%6/tEk?<$~x V杨gƞ'4gB^^ZH.fX$Sꤌwm>vRėoOҚAnὺ6YUcqm%v^zE^o]eq#G8C fx1zE:yt4?ZF5UG\bjȤ x+6E(Qoouk>kGb- ^ =KU̺%kFK吤i*7<)=PQE!p.4x5יk7W ۰8LMw/Pcnխ+fh)GJ*UQEKQ7J/CYWI{M9r5jLyk3@a?Ww\Wo[%w}%@Q@5cR@MW#5mOy/<^.­ЏO\J;=C_Isq}/Q]_[ֳFn2zm#4ɓԒk~<>k\.DبF.$t Wt# hjΥgn6Z@-!%P1vJ+ l#¤sB|SڿLj z{iۛIApO@+ѫ2#T%ӏsQ"#G+0E  zxmXȬ W^[%;\x04ԵEVPW~2~#>|T,GZC/lɠ6`P[K3x+L n|gk7gcY`XK ሳc#%FXtC;oB|et0?!nGG'fTjS?/ދQɯLd~ /Wݵůiwi-^K|\yήnc4_H񏇼Q7tX[ V@bX\. r'g;_z/ 4"%ǓY$xþ&.fѡ9<ǍdT1e95sRrz6wRNI;.3RM_{+ѦSz>>/{+OI|Y˩,"͆\-Ķvz|W{!DlF݄,WߊBMI}7U5 QMͽqv^'¨M5ھ^I>?vsk~3]f]\˩lT0[ yby:&ṅvw^׊lu9|so{uoӀ7EYT4ĊYX#p DaxOn/ol7v%$J/Ecφ]WxMh4qY.#[̍G6}pĿx·7y42[o5 w[n\Ew4ʛV1pG5s:\[Y3ZՂcqO$aE7S߫~۾Ɨ-ioU}c1xmcҧׅ4 ,2ֺVa29o ¿.vV̿kRkvw/WӴM(hZ%\\bŦ8 T/>pUͦj>!)bhܺg zqy~? M.{t2y5gWZL:͵֕џ?}>^%1Džkz'CZ\YxRk{,)+42X9c^Q?i3kx,I4$-%B.pHldηw;o7ӝ.‘EyB) y~g7z/|kcY.|f341'C.!r)byo/> G}skAֱewfg& uuqʒGdP]kIՕF~evmcA**~Vbu}ݵ_c!Dq=}3"D~ړW%q7I&1V@GIt5a!ico)n5{}c:gmULhdu caௌ ſ`OZL )tjB1sپ0N~O;׏m&Qy% GK H$kV."K&;Qk+hR֒%R9&ٲ4.Sjtzv'eWR?h7-xU<=Si)tcDʱ 9'2:4O8?e3ˑd;qm?ֻMxG֖O|'8&HݒfaeRʅBj~<ƶh%խvVwS_Y&7x0@$;z[_FMYV~_w#=wuO'~/m]wSx–{gZ֛?xGI|x­J-N-GJyM0ƶSdF6E!A0>wK Z_חBzIo+d#> k|_ۗ ^^o> {kg`щ0{_1~Бy}|3zZ?ϱ-rrzzf-ÿ|AOiiEƫ "w[fHF2 \=*M^?-4j[XXttR޲7!مB_WG푯;s1ԧy6cXίrit /mޢIVT }Nmr-tltvKBchr?f}_auSum<hVjbMKQO:fV\X9 <5|w^|O}]G)lcY!-*٭h"(Dw&ARmO|=סQvkm٭ϩgc@6c^ fCp."EWmۤEBsj2KKx[ƙ|H>Lխuxmtn.욓ʯoǕhd۴vZ];^#6JgٮgSD|pOOzIggkZCC0S,A\-+úߏuxc[7K.mKL%Ӣt@8DQ v\~J+n^VW_d$fo]g bߏú}NRԓHA AnDJŴ?Pk(-༜OHPFeu3^q E?/$IY~Qۧ7kk]Q@yO&۬^^IYп cWimҽ O8vV܄-z ބ(A{TP'AS:(KQ7J-d\tyk"sǃ\.޻k3@KʾZWw\W4QE?}kQњ}ic$pV&X`=vl59aIvo0K#/Od'}AWKƳn(I))[CM5s~6 <9rƍ!y@t$|4}Oz?'=m?ױc4\[ M4"LkmX6T~\k?Zg!N0sݡ Hui1Nue[>M\ydۑE(E(#O? ~ğTFeHԄoq:F2D3‘+$D.0UP_#{i#w%xwL{hJ Wş"|7K8]$@=E|c~^-}+On;k(>MƠ<:/>%G牼SuG¾"[kں秊䷒KLBv dZ.!otZ[?@b#\R ]q#VDsM.|y_޿z>|y~;@ ^YMa*2+Jȭo,$m+?oK)/|si:SJnuInV'24!$X?tJ֥1vQjZַQVzw+Y+{>~,;~ xރqizWמ%-ݼsLEp̪Y#–a;oj R!7ƾu7{WēOp }&vn&_>ܛyLl&tJY_.x7,A=/Þ|4kX_rFl EO.U-PtǗf&RKR?0c'|A_W[߃w tKD]L=ɉvOA#m۵#^!7o4;>]9=KDK(-n :#dhwFȃ) [άRT8?U>|%O_<#=0=Ě-$j3\\^ةDkW'/־*4}2KÛkNZ_˨<ӄ4B!_ll ~Y5̥/o%ɽ(^]ԹIiv?2?dO?NW𞋬^|5ޏ{2$g7oqcM Sw<>1ulHާ7TX֭a|GU_{]M{K;O`hZvXJ̌e G1~xcK{<[x"gT-uC MOM/"8&45c*@Jy+ٶ?c6\_ĚMG ۽-,/qϙ#rST~,K;SSѢiZ\tRml#hcMmo7ǹT_wτvz.m?~kEJk&+HgЀ7Ow|I'V^շ#Hz~h6dM,LmcYa8OEM5fKM$D̾䯍_Y^ k7-έ 5ׇHAups%s =Ο$O?,IOx&4-iwiƓL$9rbp?a~3[^u|//[/;7So)??5 ^)]e[}/N".=\|Vr%kEƫ]䬚߈u֒ ,j5y.$F\j0S*_ovo~Cy-d]:6|59_-W6h#i[#]t{I-BKv6H{iu_^<<"7M/Ϛ/ܑ.Em ;ETg((K%J{??]Oρ>(xG퇅?{o$ӽڕvѱ{lZyXLy H(hV-=_QE ((((+~$k:rv3+o@zWiyޔzW@oAX6}oA@)TP'AS:(KQ7J-d\tyk"rt w5N~.Urk O}5@Q@_Ľ~,ۈXc5zW{)xcayf@J5gv7o1qM; ߁5_"Ey`э)$+ Of~_ [-<ŀUO =+u:'y")ᕕ GQ^ u/44Y%m3?Þ+BxFeQ"kw+ VЌӲcV=_~|!hRZivzDvI$ҹog!kͮbV@DO'NX$mNanrFd'&&?ɣdceVI4ܢN!_ R~\+/Oh(ejz,nu5mR[--슨rӴW(d'aesٵ o'þ-PM/PE&bm ; 65]S"M%Oz&[ߟA^I jWW_>O%>x dx`t|0cai=q|PgㆺͥΑEcqEm$7S2Ck,FuԬ*'z扤EkZeym(s[!HwWF /zn麏_{ok7v^0܋whRYDg٣[K{YWxYiooO OAU|g㋟j3^-l쯴hז"Qi.rZ/V +<'s߇[GQ΍uo_]-S$24ٟR=ľ"z¶{V&"y'ivpٕg ]SH4/x>(DH5+4 6i#kv[! Er#;mRQiJ}WڶWſ<[~2jI It=v-c}vu ռA?Kᘼ?4o`"n& -؞7b&;Do?f_VT<.'Gk-k;4N=VIbX2$]vWSȍ x FN+ [ĺjػ5K@K2ei3bmvkMl۲[ڮRjjrm{7 45}+Q}62,57Y$gKsw r2My4<_{]j }soww}(mVOVjW>`yv7|C6/:ǁ5ɮlfk7ϐo!W dʝN~h-,[[ho6By Fj9JKIyO^NZ.[A$]Mlw5_(_쉄.tBs'ipA|mм9OyGS ]4G[rh^\iC i4='ı{g,2>@7#n$暇;=ufi?p \jv6eJOC%np@ē9N/yozֺjY[n?~Wꑉ~~<j^?[-} ,[+J!{i ([o{mn]\ѭ+R㵴M]LM\*OI !o+7k^?6Z-bu\jjt&1b`'&5i2k!PRxOTIf sqwaq,s44nfw`.qN[__M_oݕ?.ܪ|yAxOWdMom n-\Knw"xJy>n6&OSOItRm{s~s% 9mdF&:)x ϊZEa mi]g%-ޠĶB;X>c+>OmMLj5;][Qu_o6co$3YM.pFT@6mʾzmVU5Qvj߆l. Z6kztQ^ZljbYO*Eow- #V6kI4ۭTguoGgneWk ݴFn iK[ZW^ɮn-չHc]|n^kwvh}[ӕ&zz+½N=]ACi:6;Z2ϭXGipC2!iT123]]ě}kយ—Oaii)i=Ր2_j0[#mr~O3j!G+(+kK&tiko^K\I &E.R0#yϯ?ǃYZCiF-ʫ"=|nXc%r_{~Z%6Zg➵/I.xSPAmn%ޮq$1\8V;0B?j s:yIJx^mbkiM4-V G^m/P׵kdݴ/ n$I7,IvS <Kw~(&m]tNֻӵT\iC%rF%2 Ooh~UYvKՓy[|rm}.ui to61_z/d#Ӡ wrwii'@ʔ##R/[+۝[Kմ7:,yAM2u(RgYWٽBl{? 7K-—ZޝEoujI(ZKt-;/~K rP4hvZ 6W n̴{,GDX mzT5y^?\ח̝9_{~:w'ǝ!|? :PMMSP;~v%hYS-p?ǿ _W>H֯ o\4SiDA2B#ѡ,: |/baӧ|=_9ыjHek&6uֹ/|UyujOn/[e50F-lJB H\B$HAzyy! xek}yi:彺wĶFcYL24r*X^_ᯄ==&kZݿ"1kvkU5{<0@ lk.T+QEHŠ((_}>%kAWހtz&5zWAWi@A[V A[Pt: T@(KQ7J-d\tyk"rt w5N~.Urk O}5@Q@s9Y2GEqxK@%@?]ëmLGEigW䐹x +?mk=8_^E|3r ]ibqX3[gK6RJWE&s+uzpg%Uo]Kqo 2ym$+9a ,^E> 0hqQףQM\VZq}_QE|I][Iӵh;'V K}B;N\@2Y#+;>#f^Z{KUc!?n$ddco21Yp><ǫk_Eiqa3̖` Fa"xsM˥>gvg >O/>n9Kc;]iYY47o=n|yb4\鶺նegei<,/L"DsZ\ϸ'&/+\_ͧ}^Ey^VM|?/wזv'$e}wiaY{y93RYUo|qa5rV>NҌ[붧BݝW%+VI}:FI5jAҮ&-$lhch nTZ 뺿:|Alz4o(Go/̚6U 唀Sr?oB$OשE,sFB$r(ee9VS #{KK[ Xlla5bPUP V*ERQEQEQEQEQEQEQEQEQEQEQEQEQEQEW|Kփ\zyĿ h?4DJ? M?h+z +z NZ: hQEKQ7J-d\tyk"rt w5N~.Urk O}5@Q@Q@Q@Q@Q@Q@8EҠҵV[KT6fQd7,?/+7b:dڥWki'D`q8@9,Fh_|)hv O{{CwuNd`dA⶯SZN6zow0%,F"'{("WY.}m`Ȱ3$*DRɬ?x~Imj$x]v4\ iUV[ ss\s,ۇG bl1CP6-tsrbE6mI]REf$p+GwD -~oؼO+;.5hbdi^Es$۵ďXރ:)8 ɤΞdMEF[ m2H'nAEsv*&G4oٽ46ZѴ*2fZDԶԭb[^TXb; q 4쿫V;z+F;mgr{˘#Fh@A_kgH& 4z nt[-&8IrK379Ԇ\ [|_X4ZƲݬ)|F1EQmΚœĞ4J!ŤPn98b(gG!:*%<TJv&+oxb eլVM[ģ%Mۑ@9*?h:c~pn`ۣL:ĺRn 5lݥ*W{/ ;};tݣo %"-K;,BȲ%I19]e[maږ߫%۝ٷrFNY<۩[wXtO.s>7#Cd9昬Ͷ)-n@b , qrTu־dΥg'|w=owi ѡ{K[m ( Q)(TV#r-O sXhR-WS-J?x]-31J~E_zǭHJ[WhI:\KI [qBM=},ZMΩ~g3)68V_0JfnJ_%]g>{f_wVWФķ!MWqPr/]EfUIH0c hv# dm5xXӼK#|[e}Zo$pMQH4U :ľ$T兎n5#m]=!Kk+!&&w%Ԓ+YI;y*vE%"5[{ṅQb̳w3d| ҆mkįV^_ . o^t>YikL#+l9 4nBsIu_ۋkKaw^6 DLnZ,Er*QyQ V5Q8%ng>"4'Mw>kCIdF=cw6d\~&Y٬?u..qqmpMl7cuBlPI/_ڟ>މGJ_-<@.nu;Bȭ,YX8^T~_/ xkhu𾛳L6N-pgCn̐7'_;%BQk|~OwٟDxw7o+mڔ[n0GUw*HgI-^zz|w^M"<$'H6&A`oƑsP5:] VT| :K:'"^&e+gx:ikk% wo#^Ki$*1Z\#f9"te F+<1xCK%K9&GBaYJFܱH9b@BHeFRlo7dS8$kVR~m-rJJ'M$gk z1sޏ &ړRfoe.'nk"RSrm[ml~ETQEW|Kփ\zyĿ h?4DJ? M?h+z +z NZ: hQEKQ7J-d\tyk"rt w5N~.Urk O}5@Q@Q@Q@Q@Q@Q@?KY|HWSj:h$bUKy>ҍ M/P#q χ $k=>ak,L6uE2⾳:/m&-wROI*~ǟ >mڴz_Mf}sP ;$HEFأxP۫$1j~? 3x]N jt &(&4YlLiNpqT䟬8.%I~ z+߱N汯]k',jz-dw{7(c#.G%M?·:^j据xZmoZ˴+gq岙 +!vT%۴Ri857#K+l}-usKZW42Mst뗷ΕQlR|xN? ,>wmi է}7z\otӊrۘ GtPnK<ߞZ.U%>4|Z}|5hW>!:~Gv)synhGV,ARE5s|y/k~4O Z3L_x8Xyb@$+%ʾ݊:j~wϓtFKw*.&]+8Ci ln2b!,ؙ3:e[k6wOkS,@u]Z{Y @V9آ$ 20?EQUz/ &OO{(,业@{Y'*|(r*FQEQEQEQEQEQEQEQEQEQE5+o^^A/CZrv++4;Һ½O ނl ނ4S֠NZuQ@KQ7J-d\tyk"rt wTyy\jgh]rk(NӠ-Y<:WvuI#pv & 5-?Iu !y.{ۛ3g @;<+UFd9 Fk<:K8ҎH~VϹ#w\t|J/uSxj.c(t綁dUrK4E+tp(b%|ik'4SJq<}\D*э %+4޷N&f7M6EǙ8՟N(b6%#ӺS5ZC ( ( ( ( ( ( ( ( ( ((w k'ކ $URGž[{P7K:|񶱡6 GKac,h,@)A:|Wo6? _$xWg1_twX{2AGM2-{hѦVI??Nڗ%?wk&oDڔғFvae WI-Kݴ[Wve~뢲OMl?&F|7@̟!i;ꖾ.wڂh'Eƪ:;C%[<bH>|"_?}ZL$X4Džu7a\E'p7}i}4y_qj|o6? G#>UG:8"GI>M-;[Cdl<$hA>d皫[~0kĠ?hzNA#l"U _)N1  s2_k/!?ޯ 2'WpߝM~J\jr{r=:w_FֵV(;I$h`g,7ddA?">ۛ^(nKkIu%ܯdI$Xb#XSrQ\wzQO)nV[웖e Jt{/SKkY)x2:x;x|/W|=״k!Ao 2Ien>+]%ft'.JN_ hemKmV-J8."3ij\ኛv,SɴWF&yGh9;(dZh{7ZuyUd.UfnǔN1 }N1 ~)d<-#I--pDrwOhx.9m`Mt4/A3[͡[YhQrA.>c 7_hqդַIs4k]PRqov =,۲V[p җZ6bvF87tY> *uրEPKQ7J-d\ty{Mzu1ֻSyGJ?*ZnjĮ?h(QRH<3)h&,\AxW)䭮bOq4'걘e@6iec$Hrz8W( ( ( ( ( ( ( ( ( ( (!.L}`ӭK 8]>~o$i,g {ᱟJw%, ş6w%, +Cu/ V|)u+dAbNkNw%, ş6_g=IԴM++,OFA p7Ѧ~Ҟ 6QxMFOz8Qc9SU<"}stPd-3:,5_/ɡ8`b5vv~a^E$gYп]$3_BGZwA\+tҀ:JނG(A: j*uQEKQR^qִ:/z%]k#@OCsi<1^xVq} a!e~o#pB21\A:,ղYASI_g[?(GP^a?*4~APoqŚf:B'cc4GOZ ogm+3<Z ogm(vr8#4fGh{?i1AّqvrZ ogm+2?/Gd_?Gi1Ȁ|Z ogm(vr8#4fGh{?i1AّqvrZ ogm+2?/Gd_?Gi1Ȁ|Z ogm(vr8#4fGh{?i1AّqvrZ ogm+2?/Gd_?Gi1Ȁ|Z ogm(vr8#4fGh{?i1Aّqo ]zVro-Ā䁪ۅ$w+¿?#4fGhx|S`8Ȁ|Z ogm(vr8#4fGh{?i1AّqvrZ ogm+2?/Gd_?Gi1Ȁ|ӆ~e 8|OjzxB?̚d^L?@WÖ*Q2Ch-GOʀ>Ox*O~ { μMX[m5{_8}y=B$^G1ʽKۀ(w CM1-F+nYmD(Q%J(?5>Yg̵J&AVk sp4څs\6g^yk\sxI[8^wgysigKO:O4hϥgҽ3'?٠3>eJhf<,Q}+?٣'?GYLfhϥgҽ3'?٠3>eJhf<,Q}+?٣'?GYLfhϥgҽ3'?٠3>eJhf<,Q}+?٣'?GYLfhϥgҽ3'?٠3>eJhf<,Q}+?٣'?GYLfhϥgҽ3'?٠3>eJhf<,Q}+?٣'?N_5_?ҍ'ZatjzO4#tjܶzq@,+ӭq* ; c,(J b 8ɳ8,b4ֵJh(ŠtQE=)hXUgZEB@SGǸ5șŚnms+jeMi~scͦ{Wdj&j4ji?צ6=*JCٯJݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fi?פff@rO5:i_נ 4gR@4zXV 7M8zU@6miqZYjCm+nlPc$iWSjusOZ((FFjZB3@YjǚFj2$9oնPTϽ{Uft=(6cҘltjgs_b⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳ŗ8YJ)Ecҧ[P;VЀzS"3ڭ8WDU2@$XJJ-"Hh4((((4}L+SM\7fjI_/ړ˫{}6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moU|ڍ[˧l}h&]8/:(((((((((((((((((((((((((((((((((((((OSCAR-code-v1.3.1/Building/MacOS/README.rtfd/Oscar-mac-7b-catalina-permissions.jpg000066400000000000000000000420351417327530600270270ustar00rootroot00000000000000JFIFHHExifMM*JR(iZHH 8Photoshop 3.08BIM8BIM%ُ B~ " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC C  " ?(((2(p@u>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pn@€E&E-QEQEQEQE((-M-QK2Pj35^)7yRy2|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ko>>4>L(Kxo&jU- @Hih(((1Pf꜒b$Abpqg0\Liֳݬd*"T!^FEb 'sSoBS\Gko9cLvKL-B|G@"W/z>@)/Zo$:_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>C*_GrEo>}ހ:a-C/Z|G|:ES>}ހ;{Ldkv<{ I?lh!Q;"ʆ9P[dG97OMJJD~x+^=$ Տ~7_5U[5:((JWsS15WYK;.es>0LJu._ z<9״ B [ UTOi_? ֿ-m/TS>}ޭZ_/Z%POz^AkoJk@>G}!z*B [ UTOi_? ֿ-m/TS>}ޭZ_/Z%POz^AkoJk@>G}!z*B [ UTOi_? ֿ-m/TS>}ޭZ_/Z%POz^AkoJk@>G}!z*B [ UTOi_? ֿ-m/TS>}ޭZ_/Z%POz^AkoJk@>G}!z*B [ UTOi_? ֿ-m/TS>}ޭZ_/Z%POz^AkoJk@>G}!z*B [ UTOi_? ֿ-m/TS>}ޭZ_/Z%POz^AkoJk@>G}!z*B [ UTOi_? ֿ-m/TS>oEω-yk *? ֿ-m/UVI]VtNJВ ;RQw%~zKM{mxWS{KZ((6|FK{rH!y+ֵkm){E)|G=HsQC]IЎO)ib9$A-dyM;W5yn;822yOͪZŷyar+Ǡ⹅fG ȀjfCˁvyǡ֪t"Sl[ԠB+H c=  ^}YEqZYf&QEfj|j>3.ml>7wL{@T$ xg{wQo }vئ bsx͇b> |U᷎m/SE׮Z|rĠ^Tb$33@YxwoW9-oXӵ^5lwg\#\ns!w6kxciT;~+Vkx"(v~ ~xi?<7VDk}B K"ˎ8Vς< mK>Fl6fm 6<ύ[??g [GqqzUi>Y/t-cQ?f]h|$P@Q̿fFoŏZAyN,䰺iG'&7@`l?N4߄_ t}" Knk^ǩCilG}ܪ}qv5j~ Gk:k5w6pR+hbNFRѯKsRImŲJ)! 7¯jǍ^ZmӴ}Ե^EL-q?{]i:&n<*I[Ko3J @Y-b `H4as KN:owm<ٻx@'ߍ(=>-7EFH$[)ԓyěnqҼ/ϊ%w?xC:ݔ/=k7 ySģ=85{o jZޟmGH4*Iwf']&e2D$SpVT~vѡoinuG!"vbI sMS} n9˿Egk E,5o@K͹#=k֯/^t}<gR2byfb5b >E9<4TܧtB!5O,J toZ[_[ okGgٔPWd 4[)ڄ񃁊o7^zUoxZBijc;~}nw ǥi+]C'­_Xm\^ivаxbTH*+<;nOִ;&XMuoZ\`:$nB8~#\[?/⏇|?idOl3Nƛ L(U $ 7՗Q3oi[.vS kmD& 3/dW['MZO#W2|ݘ?5u[inm*Y'}&ѭbG#iʀ[ho^&?b=[Ɠ_áxo >}.,ILo<eRVHbr+Ǭ~,|HG⎛Z~ ^핕[`==}zrHlA4N֐*Ҥl4Yk`C]\ ysI窨 r߫GEю?>پͷgcgٍS ~Mtw%{ 2ٞG1ƥg;$f>7_~Xᯈ7i%3E j1%k-Ud~a7ܥs?wHGԭuN`yI~-H fSuh oMt_xKC4\ZAv H0a$S_ ?]GFcߒzeold#eF?z(tkmfӗQC0G&:6bYHY%-kg!}햑gY]]I]E)8e+!K" ~ΗJ]6YI(K 1n|i ЮKeh]$x~[: T@ 0mO Mimom.]'ODyWw&-]|-vwz߉m485}B(#.">ZK0Q#g /lW-qU޻'."PK{G1\ȌW@ QEQEQEWxѱm ڽ6'?QЭhzWef+J,:sZ=J֊/-L:T SQE5>ZN=iIҳg~uBhMFHQuiVr9s!"19'KNǖ?4)oP._XV_(/܌Lco\4|~&~?b|[`2cA8a؂U s]ů 47qq<Ki۬Q]RcbUx >I f?6[msv𷭼GG:S;ࠚY2p&~)Ÿ 0|{2j.D8bLئ>!Ӿx H0|5Y}EE6 kk jgtY8M.y2(>Xo/zQ~*_<'GƒM'Px]DV1wr;n`QlrF6-MkCƱ"FӞ),&xAX(] q BPT3U|Knj|a]\RӭH$  6W89T txDjMad5!Μw w}Yٞq@˟>0 6֗/['ГPI1kܱ_tRʊn9;~4h_uVU&O _}ݚ jMq| F9ExȬrqW krޓp#KaTI|Gv-ET '+)~O:>ω`Co~N:UmCg-WIu?wZn~uqKǵ`\4eE|doxKvk?xFOf]WrvqZ$ .58ZN=iIҳgzrC]}J5<_-/k+u"ާ^& (57"ӟIC_uun~UQv&Q?-&y }a( $}E~>mxv4S<0G.X 0¾}?pS`awȎx>Tr9Oc׎]< r+$FHXB=ĺiGOeM9 'N#=k g}}sƚ+}'6#2y_ܨ%dv_m׷d>RCZָdn@MQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE^;V^;Vuv#N+@=JيVTujqҠZt(5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& (57o:ic%pFW Kܨb ~Q@?S_f"i܏_P?;~6_SN粜OqIyra)NH鎵5g1pXU3Wk[Bm)z)$kOZLMCR$ZIXͩ_Cө]zX{j>/ѯ/)yTlzp^~x}Ho5Ð~º]f~surc{LHQ|]ETQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE^;V^;Vuv#N+@=JيVTujqҠZt(5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& (fQgw`2&R^S׉|;#F#!<;{ g[ӠgK>`\ PnU ~\QEQEQEQEQEQEQEQEQEQECq2[[q'܉&&[x{\&՜0R[L#mEj1F(cW?$PUƯAoH_6@TV#m"F5#EeQZjG?#mEj1F(cW?$PU|1F؟O8|X涻UI1Hspp4=S;S[iaI$i#iZCu' (_F5#EjGʢ#m1F(*cW?$QƯAoH +W_6G#m"2u='Vl$(U!hYcy2H^;V^;Vuv#N+@=JيVTujqҠZt(5>ZN=iIҳfz롮FuFu${K5 _׻Ri,R^M'u uN X崓ɺq,c!\v#%Xzk)mΗox_=zM,g|iaVYX̦(+aYGuw}\~R6IǹXqUk/R7Z4] 妠—2BneY$;𲢶83[:wXgo5_.YyuUn̓@ǒM6XF[?]QEQEQEQEQEQEQEQEVV MCg jR.A,ke :+$쬥 otXۺ9S8Sst |}///co]x}Bt^Jv5(fΨ ā\'*|JӶΥmjxJѬw/.i˴lV'|s࿀uoxU;[6ķzYkGhd[T/o!x>chS}K]7T:}ōk{Y⸎W>Y>[' [e5o@ݏ.Zww:tsxtBqkM @Qa NkmR"f>m:mvdE&#k;*՗'U.4GΚohT I#xn.}EdIiUo#z+?0_G'> JuSl7WVw:$Q* O~͚?]5+A,9)`itb絼0(b!!'N|CuV fM3!OFmROmvw-CsF)w2@6~9^ C{h>~!g{XnCL.%o2$[YT(;Q$ŇdQGI CjFTFĵ?|ִoUFovo$ԃ " q(>~]ca@젰[DRI+Vv,ǒI$dzR_ƏMj Cu궠M&6[51ji2 C/At bYaHԕe A ?ȯׅjjψ|aLҵ;]B-dL76UsNV_3 ֽR#>'?QЭhWN++C]A@-Jي-VTujqҠZt(5>hl igL(Z.k@CsRi^^9;gA"e@*k}SOh/!x%%PH QEQEQEQEQEQEQEQEQEQEQEQE4qIE>gyQq!OET__SyQq!G(T__QE>gyQq!OGQ@4E:gyQq!OET__SyQq!Hbq!RQ@n%ͦk:_N0F|5KmSR y'V&Kh#.^S#jioV᎓CygIsOapZ 5XuSrVwԗzeA2|u+GBzv72HFnylpw%4P>EWg{jzyo.!cL73J1K#X&sO^X'J,ץteێ+V1@֧*J(nW~J+E0W\yuӧus@eeaAq麦?X RX%#]sN2NMzZ`}G5h$常FiFbX@qRZcWUgmq@QcZ1Y1ԀVbB'ZQRPEP(&Y֭XPTYf4\x~9⻹dMkY5z4yYXPU[MCm?ڡ:wyyI]Ӈ'w4fQ]w4g@oپgGw4fQ]w4g@oپgGw4fQ]w4g@oپgGw4fQ]w4g@oپgGw4fQ]w4g@oپgGw4fQ]w4g@oپgGw4fQ]w4g@oپgGw4fQ]w4g@oپgGw4fQ]w4g@oپgGw4fQ]w4g@oپgK=(o*tӽwH~;P#է;WHz+,v kGؽ0Ҁ9{Q/j=(0c^}ںJ>=(bҏJ>Gؽ0Ҁ9{Q/j=(0c^}ںJ>=(bҏJ>Gؽ0Ҁ9{Q/j=(0c^}ںJ>=(bҏJ>Gؽ0Ҁ9{Q/j=(0c^}ںJ>=(bҏJ>Gؽ0Ҁ9{Q/j=(0c^}ںJ>=(bҏJ=)ںAl=)~([1VڶŸD;U]Xju qbӕ*`*RO((((0IE@V)ViZcjnRyCmSU]F{Q{Uݴm ^WW]F{Q{Uݴm ^WW]F{Q{Uݴm ^WW]F{Q{Uݴm ^WW]F{Q{Uݴm ^WW]F{Q{Uݴm ^WW]F{Q{Uݴm ^WW]F{Q{Uݴm ^WW]F{Q{Uݴm ^WW]F{Q{Uݴm ^WPh@E!W6 ~] 8Photoshop 3.08BIM8BIM%ُ B~tICC_PROFILEdapplmntrRGB XYZ 6acspAPPLAPPL-appldescPbdscmJcprt#wtpt$rXYZ8gXYZLbXYZ`rTRCt aarg vcgt 0ndin >chad,mmod<(bTRCt gTRCt aabg aagg descDisplaymluc# hrHR koKR nbNO id huHUcsCZdaDK nlNL(fiFI .itITroRO :esESFarVukUAjheILxzhTW~viVNskSKzhCNruRU frFR0mshiINcaESFthTH esXLFdeDE enUS ptBRplPLelGR svSE"trTR 4ptPTjaJP >Zaslon¤ tSkjermLayarKijelzQMonitorSkrmOnbekend beeldschermNyttAfiajPantalla4'4) 'D9168A?;59ژoy:VhHin thDisplejf>y:Vh-:@0=Moniteur de type inconnuPaparan ! ? 8 M * M 2 G- 2DisplayBildskrmEkran000000textCopyright Apple Inc., 2019XYZ QXYZ tK=XYZ Zst3XYZ (*curv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtndin6HW K'P T9333333sf32 B&nmmodunkn7" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC C  " ?((("u:fIހ$IPT[KEEQPT[KEEQPT[KEEQPT[KEEQPT[KEEQPT[KEEQPT[KEEQPT[KEEQPT[KEEQPT[KEEQPT[KEEQހ%FzfKEQEQEQE(i=)8HN*"!jazRݶ+>Yq@jwyת#0pQIdq]Ɨu^eր:+٣J{?k_GZf+ٮK~khҺ}?f/}J{?hҺ}ր:+٣J{?k_GZf+ٮK~khҺ}?f/}J{?hҺ}ր:+٣J{?k_GZf+ٮK~khҺ}?f/}J{?hҺ}ր:+٣J{?k_GZf+ٮK~khҺ}?f/}J{?hҺ}ր:+٣J{?k_GZf+ٮK~khҺ}?f/}J{?hҺ}ր:+٣J{?k_GZf+ٮK~khҺ}?f/}J{?hҺ}ր:+٣J{?k_GZ?gkgBY|~3}`y:_[~Ϊ'k@Ē+ 3iE&hQZQ) -QE=)i@5NJVi1Xc5nL\b]r^y_o=].'UfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^Exoqmپ%=PfQ_o=^ExoqmjQvZhi)6oxv [Lz]r:Vu58\:X5u d٭([}D:TQE5>=(53U+.v {5MCx :⋂4#^^⩱'UQEQEQEsm3rQ78,I($4m RI]: L rJ}9TAΈ2ֆva&LY4C ( + _~"to4wգ]*+vO5ek8UZQJ:W"ӾtR w+j̐mi$\4~b9_E.vonS[.*U}xS_Mo?`6x.tEvK1FJ(][k{GѼ!P |76̚ cl-ᴽ㮩iշeoVZjNϖZ;6ɷD=7 CO_ &Mr>Eu hKfbM8~X>%[Wc VzmSGFc:vYg> +eF RJ2H_kI實_K{Z+W$UY>*=:.^ѥ%s%*b_/l*T}%㟈6 Axn ,tsqo|Msr%b% Fr3[#/>ڷ<1? {Qylr̟*' U _kQr[Vw>آKľntMfJ^hq0ݘ0 +2[ck?x՟y''>!ӗPҾD{mKh#£`Rg:oxj˕0 KoGٔWΟi|d t ӭIbW7ZM 9fV!dVk.ռ5XxM?iS\h:\7AÆ* qU:~z>MwNKeWc+o~Zȼ{mi-SNўMRQ㸚x܁ ; x:y]=E 'OV UQ>/)) Sz=mV#+_>%'=z!>hO46V%{5TC fV9b-x9ĺ[k &iJп5ȓ"[1\ᶜһIn_GW+t>+zEohjRxZ\#4o F/>N|rO崺|=kGwA g{JT~('<Iߥ'evi/n M}]t<;6ú-42-f~״B+~ۿ|#xYӼIkZiiD%6,Rj YYOߚuuo3:+?ګ· ]s& xat [\U5cl| y T~YV^)E׃a(̳/Qv(iW'p'e/$oO}(5+Vm+ {WԬgy8t Hcu-Rվ6?F_W:ׁ{m.khgH`dH,6s+,wkE 9v[RkQ%ʊc'_Ώ,<L]%pB%/GKĐ)Bxr͡kcԭ.58AkevE5[A142c~ykm$1OvW{#n.t 4Ř;"^NꑗknTG#w{r$ 6ުnhז^ ީkhmb aX"N$ j-IuI~JN=_u_B>~i&,9:ZNiq/-&g2;v.\G|6~RG|5^) W/lPubX9Exw)U5WE4yv(W|cI]yf{y l+δzWqbv6Z¶jوtRZuQ@KQ7J/CYWI{M9r5jLyk3@a?Ww\Wo[%w}%@Q@5cR@MW#5mOy/<^.­ЏO\J;=C_Isq}/Q]_[ֳFn2zm#4ɓԒk~<>k\.DبF.$t Wt# hjΥgn6Z@-!%P1vJ+ l#¤sB|SڿLj z{iۛIApO@+ѫ2#T%ӏsQ"#G+0E  zxmXȬ W^[%;\x04ԵEVPW~2~#>|T,GZC/lɠ6`P?#kXjwVl[D+B?ζmt##w?_<] ;q]jJmp) e>p&5ܻw.f/[nkOO=K< q/ _YzlZ yǞ(;69;3M~ht{xNUΥd )%L/U@ݷ*~wďÖ.V {qgkoy9eu/HBc|G_ű Ay)V2p?6+Ni)JW4ԾV|4W5wW|_Wğ xHSԟh>EwC~_|*NZTc^ShFH ]#ļ;]|:>SE隇ZErSHImZ6['gU=x*w!֕oE#PXZq ]d'Ftmnӹ%5$}6/o#+ƿMe?q>&IH)US`؊03w_Σ_|n'<Vmt4X᧐G$sre2[lhp> t>,ö׿--짚6.Gc9+ʼ1l|1ß~~jZ&jzX,\EJ~RJ9I:U;vNKG-t숧7NTkwE3KpzRx3#FFk-33Fǜ+8#2qi.O5k]o^].(f&qG6 Bf뷚/"区6˒dMղ? 51ݱ]֑x:4YJ̱.αJWX*`HN\U'vw-|}NӺVꟼφ.dO^< ޻qz1m3 l;̤/>|;WG5O|Db6߄_:('M_E !wej> xBŗILK2hU)%7IK^6ҼWV~~ Ym?N[:r#kkWFQg6a4M[o|*_~;mwI+O]8~ʑm>%qbӴ=@Í!tdtߴBzד|MY񖑯趶(kjk>4ʬsqCY`I1񝎝NKof.0ۨ{k!0 "+A~qunyetzS~+l]]n űH*\S Եm_2jQyaςEDPt25|7<_ xY5}V$Kɶ)$D 3U#a݇!sM~ _5̖ N&ٮ3QX.~\ q1ZhZ_gt77ɴmOο7_t_|T7gKYMOӿ_m4D|IʧVn>մ~(tO-+>{ E|w2-ɰn Eiijӭ}ww''7ykfgˡd_e|B|Exg5AGW@yNIL1JQ@D/^߃_#yĒd}Jߝm gSxP1g|6>T,'&U[|RB$nH˕–.;@*Uf?=y_GousTQ?fk>ektO6Х&wM?I(?n+zy'sgBWހ,iһ^qJ-<Z+r+z S@N(?KQ7J-d\tyk"sǃ\.޻k3@KʾZWw\W4QE?}kQњ}ic$pV&X`=vl59aIvo0K#/Od'}AWKƳn(I))[CM5s~6 <9rƍ!y@t$|4}Oz?'=m?ױc4\[ M4"LkmX6T~\k?Zg!N0sݡ Hui1Nue[>M\ydۑE(E(#O? ~ğTFeHԄoq:F2D3‘+$D.0UP_#{i#w%xwL{hJ Wş"|7K8]$@=E|c~^-}+On;k(>MƠ<:ozTڎxw5u'NYfͶVݥehQ@rٮOg_M~S|?_0#>;=ׇ߲~xS _m7-l ȾpV7G#~^?9>?\ZŖsZA~ّs=B(q#dEu{yYV'i%}ޤ^ΜiD_$#__~$5? ^hFvd.e&I=yvԈrHP y3֭N_]Kl2[/ILsy8MxG](rtfR#XUd4qV_o_S]x{Z#KG>"E&T~dy!hvOg 8|!j7?uDyXfn[}< ~[BN/I4ʿR)IFQ._oO_Zχ>":oGwmoyyP@]~RW t>5GN}˦s \3K*ت32ɞuF\?,|?sv?_hz|l\+9RV Ҫ r.Gz:_O~ ^x&+; U5g-P7:w=ԥ&*Ȫ@hĘ֊NMV_~r{r?g^^gqjzLږkX彚ycD]U3^1~_o`1J*fgٕۤM4ѧ$ w~[K|U{k}/(k2xz}"Ss};7!dq3=K>T}_mCR.H&6ө0',l<ڊwW[_iS頠UY?T?~џxWj>([e !R3PB E$O#0l^е x4Ax òk87-mc5 Ec' s$AsjulCCwy@Wm5E_3de*OVoP*K ( ( ( ( ( ( ( '΅\zyį ?ZwA^wzei[V A[Pt: T@(KQ7J-d\tyk"rt w5N~.Urk O}5@Q@_Ľ~,ۈXc5zW{)xcayf@J5gv7o1qM; ߁5_"Ey`э)$+ Of~_ [-<ŀUO =+u:'y")ᕕ GQ^ u/44Y%m3?Þ+BxFeQ"kw+ VЌӲcV=_~|!hRZivzDvI$ҹog!kͮbV@DO'NX$mNanrFd'&&?ɣdceVI4ܢN!_ R~\+o|+v'U{s^;~)ڳ u\uk7+KId[)v]* țY^+vul,I.țGIyݭg $z= OѬ|+zrW6+s_4RElfGhaYY"({GLw~(Y:,j@7S@&XfYRH(1n5;s;.}/.jo+]t![jKmEK =ŵ&m2Zld r%cs`s?h_ < W:]_iKkWrc-RȱVxinsǟ|cZIk-J eivF(`pDe&L8>oƿ,SIߊ|=^4J]E ؚB&xp E!(r61i[ѻG%.ku֎v}yNe|.Դx4oӯU{cOo`!FE3B+ܠKB okF\Lu)N1iZ=r_I<84\[#J6x{b𮁢'*}M2t4/mx,u/mrIt4K41,zY'Em;ZմӨjzj,è_ZCs$_F|bW ]߽ۤ{kv% w{7Sӿj\}Fh6~Gj6 <=)O5h܂XcL8{F..ygci:, [. ZU'S3{wC?_xJGYiT]D`@r#d*WI˿Wў9f{gk|TF,NGq*LJV}[?}~vw-r~Wc&Au^^2iidזI7lox孮ƎqIJFU~$|Dƍ&y L)vy&@^7ά ⼯sM7^!W4hQ$]W͛/2ɧʭx%8jl)|>)x{Pu3\Kcw`S[Цi{. V59 "pS>+O/㿕m8_${MoXx*N{wIėຕo%W`*66wQQesh ΗyZŬbִ8ڮl\ x2Y/,7d#4/uEWu[ٻEyͥz]+sj_c"ռBb j6g@[-HJGop<).|$TgͩJ o=nHxTMa8!k%/"d;^>|1óxKQ׵MUԬ`_.rS"\;{*%?,,|)⩵ιiP=WGY.$h2;F-$Q[~뵖 ?|77< hׯFy4]B+ !/bBG)Lu/F㹾y/WMŪ5[G-\MQ#UN SfoejK MCYxiRo.u MVqQiOM}RR6^Ik[|Hͫx#ecswKŤwYZ ],v $j4V~Һ,?eҦcMֲCaq'۾at.0rmZ~~ Zw.u]jFҿ[xn,)!le:,tt^5/ڶZv&&Ij>>A #v,zlֽ]4V4wM>G w:Dv41#9 kx˗@U#.L,'G= OW3ŭME4ZOO|w;Ut!xRPP7J߅Ծ6k4Ay]R\ؖ)QWd`2TFO),:~$^Xnm#wZZ[%[,XJ-{D׋s.ẻ){i}t>'YKM[2h"[-^\ZۤP/2"f`+e #6YJ5CPa4f[!;nCG%hIN~[I,[ºP)7Im|#6v(hG<Ώ<@5#IP\ᫀnGS]rȌGl|?RH!hwoC|m[4&d̄ɛnFE{Fnh~[u[T@\&v 08'|W"x/va:2Z;fvO HҢ銚I%ϽvvnO켬 )(+>%kAW޽~_}WAWi^wtz&4Y`hAS@N(?KQ7J-d\tyk"rt w5N~.Urk O}5@Q@s9Y2GEqxK@%@?]ëmLGEigW䐹x +?mk=8_^E|3r ]ibqX3[gK6RJWE&s+uzpg%Uo]Kqo 2ym$+9a ,^E> 0hqQףQM\VZq}_QE|ϊt :KҮ<9i.w5Tz<W)2¥]=/R_ Ϣk^#񇅭HWSeY$L-' }M x#L{?Y~=׮a/M/̶=\8F<#"ߺ/huxNޱͥ%ر],oxMįpo<{ʗ6a<7mJ6P鰛kx1[gɶM61.Nsz7tȵ;uhQ47DE*F+3ZTוI/uɻy9Ipl$߂u# |[̞9tO.> :e̷zeΦfk˄6y hVfE>P^.~~˫ZiC%Qg.T, иi6"xoS_G/{m.Ѵ(IHǂmA5+.,#yq"$@v05]6K_7f'̻t&ͦ%W՟%ůk4M_ĩ{h:; ky ioZKmZP6\[I #~L֮j3koɵH))c97kexs,$,> oA障:մ6z^Y:[h%6#TMGk_L \6htXM8ɝPIw.hZ_o-"dk+_}M9tݣx5I5H!i/-_*H{=$`GGA>7IO7nuY[bIđj -Gk .nD /~o |HѮ4'k-ݤqm'8bJa\mpp8gW:%%zut?Nn[V]L6ڼqIueui}Vo{I>ӋuG~!x ?4[mz#Z} <%pǎNj*{FOM|o}q{{m윓Kϕ.[]uvxgj˼N.][нƘ~"D-2\Ky>?jK.l/;>3/:U=gRl12^B#W/x;@]K6跋[ 2 U!G4RBeUA j97ASn'{t'Ih fFVb;4﫿m薟w_ڞU/~|L#ҬOz{O4q3#E c!|3rzğ/-?LQILMEY !x=։MƵ'H&ͪ򼠠v1] )d=;OHe5bB;ī0(+sN5K;GvDnXϊ>!|x:dzsh +]5m*9[2"N!ԟWxWYv R!5G02Hmzs?'<jͣ$L6iq`#o(I&cg>=V2$j v4{$&WkК9:vZJ_[Iu&5{ۡwBMJ#bឫ+Jd}{oO:\}8Ttbl1wzE_Kx.OЬCx?ìM hzQ4m嘤e*cFSÓ7>~M}j2Yx.ʛ952~wSsYi^𮉢ioYuVגIjHfRpH:^r57Jwv-{y)i1gUl K yRM76lYm$+34rCZ3Q񟈵-gT7>>|=:nڷ31]Wtoo:wC4#8TU7~JK(]nowhX1?k6rse<%Xċ@ ,k|n%?,eP2iӪ4ZnմKo1v H~%]xU<5 σO1nao }K[Ǻȏe&F2Hz~4⟉SUzi-Q4!^/p!)%ß/c7yZV&YTrJOJO}z5ެ;Ru{oVeΙCXk\k)k{3An%h"{H'UX 28dߕoeik1MVڝѿ[_i\JpRDnd0/¿!𵧁hZ,Hm`1[Ō>5k2<r[6WYCibٌWC:[K7o1j.4o ]&h2Ciq#}hĉ% Xe:k5+ǺӭNTdx٠U#+ow{~]*[>^]读ӛ~͚ſfxfòڧn-Z9.f{MNo ŕ&,(/h!7Ww$+U "V F7Ff+k<} $֑-yuAqi=C"2 4z nt[-&8IrK379Ԇ\ [|_X4ZƲݬ)|F1EQmΚœĞ4J!ŤPn98b(gG!:*%<TJv&+oxb eլVM[ģ%Mۑ@9*?h:c~pn`ۣL:ĺRn 5lݥ*W{/ ;};tݣo %"-K;,BȲ%I19]e[maږ߫%۝ٷrFNY<۩[wXtO.s>7#Cd9昬Ͷ)-n@b , qrTu־dΥg'|w=owi ѡ{K[m ( Q)(TV#r-O sXhR-WS-J?x]-31J~E_zǭHJ[WhI:\KI [qBM=},ZMΩ~g3)68V_0JfnJ_%]g>{f_wVWФķ!MWqPr/]EfUIH0c hv# dm5xXӼK#|[e}Zo$pMQH4U :ľ$T兎n5#m]=!Kk+!&&w%Ԓ+YI;y*vE%"5[{ṅQb̳w3d| ҆mkįV^_ . o^t>YikL#+l9 4nBsIu_ۋkKaw^6 DLnZ,Er*QyQ V5Q8%ng>"4'Mw>kCIdF=cw6d\~&Y٬?u..qqmpMl7cuBlPI/_ڟ>މGJ_-<@.nu;Bȭ,YX8^T~_/ xkhu𾛳L6N-pgCn̐7'_;%BQk|~OwٟDxw7o+mڔ[n0GUw*HgI-^zz|w^M"<$'H6&A`oƑsP5:] VT| :K:'"^&e+gx:ikk% wo#^Ki$*1Z\#f9"te F+<1xCK%K9&GBaYJFܱH9b@BHeFRlo7dS8$kVR~m-rJJ'M$gk z1sޏ &ړRfoe.'nk"RSrm[ml~ETQEW|Kփ\zyĿ h?4DJ? M?h+z +z NZ: hQEKQ7J-d\tyk"rt w5N~.Urk O}5@Q@Q@Q@Q@Q@Q@?KY|HWSj:h$bUKy>ҍ M/P#q χ $k=>ak,L6uE2⾳:/m&-wROI*~ǟ >mڴz_Mf}sP ;$HEFأxP۫$1j~? 3x]N jt &(&4YlLiNpqT䟬8.%I~ z+߱N汯]k',jz-dw{7(c#.G%M?·:^j据xZmoZ˴+gq岙 +!vT%۴Ri857#K+l}-usKZW42Mst뗷ΕQlR|xN? ,>wmi է}7z\otӊrۘ GtPnK<ߞZ.U%>4|Z}|5hW>!:~Gv)synhGV,ARE5s|y/k~4O Z3L_x8Xyb@$+%ʾ݊:j~wϓtFKw*.&]+8Ci ln2b!,ؙ3:e[k6wOkS,@u]Z{Y @V9آ$ 20?EQUz/ &OO{(,业@{Y'*|(r*FQEQEQEQEQEQEQEQEQEQE5+o^^A/CZrv++4;Һ½O ނl ނ4S֠NZuQ@KQ7J-d\tyk"rt wTyy\jgh]rk(NӠ-Y<:WvuI#pv & 5-?Iu !y.{ۛ3g @;<+UFd9 Fk<:K8ҎH~VϹ#w\t|J/uSxj.c(t綁dUrK4E+tp(b%|ik'4SJq<}\D*э %+4޷N&f7M6EǙ8՟N(b6%#ӺS5ZC ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( )5kS?캕K'̱o3ġAp~5W ]+Ʌ~XxKKX~,D}kW?Oi~_}!ͨQ )`#~WmOn~od * Rֲߛ(ƺF.OǛW|Kփ\zyį _4DJ+Ϡ+Ϡ(E: j*uQEKQ7J-d\ty{Mzu1ֻSyGJ?*ZnjĮ?h(QRH<3)h&,\AxW)䭮bOq4'걘e@6iec$Hrz8W( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( $x^?ZSYjs_< dRq_Wpf|9'-NJ5tywGp8ct՛M?e o<5G1 ~_|w>K!G$&~Ym } o<5_Q?pKg1 _w5E8C!G$&~Ym } o<5_Q?pKgwcxS`Ҽ7_aw^-zWWfKR9'ʢw>_/ɡ8`b5vv~a^E$gYп]$3_BGZwA\+tҀ:JނG(A: j*uQEKQR^qִ:/z%]k#@OCsi<1^xVq} a!e~o#pB21\A:,ղYASI_g[?(GP^a?*4~APoqŚf:B'cc4GOZ ogm+3<Z ogm(vr8#4fGh{?i1AّqvrZ ogm+2?/Gd_?Gi1Ȁ|Z ogm(vr8#4fGh{?i1AّqvrZ ogm+2?/Gd_?Gi1Ȁ|Z ogm(vr8#4fGh{?i1AّqvrZ ogm+2?/Gd_?Gi1Ȁ|Z ogm(vr8#4fGh{?i1AّqvrZ ogm+2?/Gd_?Gi1Ȁ|Z ogm(vr8#4fGh{?i1AّqvrZ ogm+2?/Gd_?Gi1Ȁ|ӆ~e 8|OjzxB?̚d^L?@WÖ*Q2Ch-GOʀ>Ox*O~ { μMX[m5{_8}y=B$^G1ʽKۀ(w CM1-F+nYmD(Q%J(?5>Yg̵J&AVk sp4څs\6g^yk\sxI[8^wgysigKO:O4hϥgҽ3'?٠3>eJhf<,Q}+?٣'?GYLfhϥgҽ3'?٠3>eJhf<,Q}+?٣'?GYLfhϥgҽ3'?٠3>eJhf<,Q}+?٣'?GYLfhϥgҽ3'?٠3>eJhf<,Q}+?٣'?GYLfhϥgҽ3'?٠3>eJhf<,Q}+?٣'?N_5_?ҍ'ZatjzO4#tjܶzq@,+ӭq* ; c,(J b 8ɳ8,b4ֵJh(ŠtQE=)hXUgZEB@SGǸ5șŚnms+jeMi~scͦ{Wdj&j4ji?צ6=*JCٯJݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fkhݠ6fi?פff@rO5:i_נ 4gR@4zXV 7M8zU@6miqZYjCm+nlPc$iWSjusOZ((FFjZB3@YjǚFj2$9oնPTϽ{Uft=(6cҘltjgs_b⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳~=(⏳ŗ8YJ)Ecҧ[P;VЀzS"3ڭ8WDU2@$XJJ-"Hh4((((4}L+SM\7fjI_/ړ˫{}6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moUj6P_..moU|ڍ[˧l}h&]8/:(((((((((((((((((((((((((((((((((((((OSCAR-code-v1.3.1/Building/MacOS/README.rtfd/Oscar-mac-9-catalina-open-1.jpg000066400000000000000000000612361417327530600254170ustar00rootroot00000000000000JFIFHHExifMM*JR(iZHH58Photoshop 3.08BIM8BIM%ُ B~5" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC C  " ?(((2(p@u>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pnu>f7Pn@€E&E-QEQEQEQE((-M-QK2Pj35^)7yRy2|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ky|<`>>4G+7ϣϠ /0Q Ko>>4>L(Kxo&jU- @Hih(((1Pf꜒b$Abpqg0\Liֳݬd*"T!^FEb 'sSoBS\Gko9cLvKL-B|G@"W/z>@)/Zo$:_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>A*_A*_ޏE @"Q @"W;z>@$_=G$_=\}Ot_j_ -j_ -si>C*_GrEo>}ހ:a-C/Z|G|:ES>}ހ;{Ldkv<{ I?lh!Q;"ʆ9P[dG97OMJJD~x+^=$ Տ~7_5U[5:((JWsS15WYK;.es>0LJu._ z<9״ B [ UTOi_? ֿ-m/TS>}ޭZ_/Z%POz^AkoJk@>G}!z*B [ UTOi_? ֿ-m/TS>}ޭZ_/Z%POz^AkoJk@>G}!z*B [ UTOi_? ֿ-m/TS>}ޭZ_/Z%POz^AkoJk@>G}!z*B [ UTOi_? ֿ-m/TS>}ޭZ_/Z%POz^AkoJk@>G}!z*B [ UTOi_? ֿ-m/TS>}ޭZ_/Z%POz^AkoJk@>G}!z*B [ UTOi_? ֿ-m/TS>}ޭZ_/Z%POz^AkoJk@>G}!z*B [ UTOi_? ֿ-m/TS>oEω-yk *? ֿ-m/UVI^Z4",(]ܡ# g](((\^ϦAu=H,K @w(a Rӵ pȲe^I< QEQEQEQTKNխVJ݋(E2J >b.EQEQEW I6xEЭ뻯9s6m[P_g6q]=XɜW[h(J3Y5jFhj%h(5>]VtNJВ ;RQw%~zKM{mxWS{KZ((:rj ,RZS9>D 7(6#eIdm*'nb^{WkڣXC z rkoeknfYr~.bUrG;n/mZ#<k |/˧_w{8ǹa[BNX_-o৏6|gum&7m=i .:z#▅Aڀrɸqz^'Uu} [,juon45%ᵻY1[\H|ͻZ^>t<1՚bhݦ}(_v_^*sZmO :啑P8sȬ#:U[@χDb8~# 'ٶy[Cs=hVOg\^exoOK"XO٬浗k$v$I31T9Pm/٫QᮅccP^x1SC 9,.%gH *c2R b txⶣxI4tӼ/{cxoGYNg7`kR(oks'R0ľlWP-@Uno}8{-v Ə~߲jOwù|ipF"4_ /Q煴O #̈cutXK+`0r|+szgOPxrIM,\ʘ a$'-¾eiqowNZUdjzEͺ,^X03I݌эnE8<=ޯgKTMR[/ -I%sSt %. ~? HXڍ:$F4 60#y_/bdxDcgn<)6|WMS׌}KB%"v\k(wȁ+.`0WBOßpj>t_ tK;{6 ^4Op8Mg/z.::鶒LS&g2U>hm̀sOӟ2vYiIj2vVV Ρ}ozN /'%{'ğ|E>y#𕿃u;MF&DY|-y&1Sp>Ծ;|Im-c5g6K{ȥq/j[o/R: Zj:ݾaZiz}rI6mWhr~R(~_g^:U/4j:ItKO}dxNWͪO :BYTq |1mlumROC&wPHK%Rh ~x{^,Ю㴴IZ]Zkp$x%dظkRx4^#q}], [,bny8'vH]QEQE^4lxAmCBM.lj+ZްnYJһK#ּG5oҵ SQE5>ZN=iIҳg~uBh/G BxDaod$]:r5+9k?JCKs걏?k7DO[ʣUf}B[ۖ,Ynx3S+ݎVA,?μCtc'`*?SJD-[~os|?$0%?Vڝ)n-s5'GZz^??풪Makq^6a`~}bxK+8?k(M/_E%SV'(Aʚ{>ED9 2?}cizWG$}}f8 [4ʔNnVkFQEt~{~wŏ|1gx BѼ ? jvvyH<΅# 1% ||1_WD^K\-]ku%@Lgѹ[I~1w4nN%l`ܞSJS=~R9O­oFӼ;x7V2Kk0 )$r{燊?j/=^k"x;Jd gyi>+y3Gr&IUWc_'> úeyhBi''PnY\85cjZ^MiOg 8Z!0yiG:_^%m+jf+,7Uݼ,yl\v ~hmCfk2O*ܜ SzT_ i WOo%^l2 Oy@!0hž2__ٛ.X-ѡ\jXGg7`%75zƯ;73h^1GMѭ/fԤ mDdd `~Y6ZM$Xdt`UGZ>+?}=70{:2;K #7 t?j/#ǖ1k.xcKt֜EİŧI=lh_x xL +xz~&on$2&OWmn$h"ҭR9%wQLA1+JVP<1^]jZ\_Oq-xŤ;FZGcw#Gω,uO i⛝2X,QI=ŴDDV E,NKeҼW^ G~_bh|OZAj"ݭXEJUH>cX7o&n.?O/$RvBd3HvpQ_ß'<xgGnK+)y*pshڭ&[xfҬQyn|H A^'7[xON.>-Nkq "F2@/Df*8'O*_ K-6"XP?FbV0pZDy 6֏g?& d|-\7|5Qִ|?mI>7Hы]卤TVv7q6B~϶:5hxbVx[Pkb1,E^Eg%'^[?-뇹]~s nN3gиnGkO_uY-b{ }ۢ੕UXɠM|Uk'BV}]o =RMGTXrck~5xe[&um/Ll֎t-BWL6}YH,ZN=iIҳgzrC]}J5<_-/k+u"ާ^& (!_&2e =Xo @}Yk==;WxğUޣ2D|W$I%9$/^ Ҩ>пy$H _k7_ެYayo}kY&qgK'ֺxJ溚IoH̭ }<8 a#6QuF-.n~ETn^jyq"kL[8*xJ&Go3L@'uͱ4c]Vik{kcaEyĢ n Hp_AF0ᇣKǍDbAw_&Pe&q__GN9x4k+ʚrz?w^MkMi-%@+(>.wjZ8?4mZkJ=Mspk#]B<1|J,u/ixM:wzXKid_\Op5کgl* b?OItI1쏷A=ww=ou 1O%@;?Pjq9ap]cĕ4Cm_KO/7;|??ڀ>?4RtMoOa6ՓZ`,j$HWݘBFHj85rlON.?j8mojzWZ>*2vJ5)fΓ! IčwYL .v tGKCƓ_ .i./nnuإהeme%xž:#OORdT43ɦܢ.gDW~4>,oEV>UƙjOIMԗrOkfҤݤdmA$2)\5MwF-o/u{-!$yd < ȍBN+N7IQdWA~-key#TI>e|Wc&)|_WV>_k.&RSh洎{)RmeA_*7$}VGi$̮XuʮrB#Fz S]ѴK[^ HtI/Y63r#P-ӊƉsG|k◻-4VF?;崇G;-key#TI>e>uH" $m$,PFI#MkWs<%x^'M{kkQX^"(QڈVv f^\>&in[^H!cɦv22}717:WM56X$HH r_5o eT^I6yEcx7D熎5XІubX&_ŞYM?`MģoͿF'*` (((((3 ֽV3 ր5{.iEv][1V5Jي.N:T SQE5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& (<:qŤ{]ݕn!8#GNxwxIzҾ?8cRiZn5svzO[ޗ<_rWz_ҌJ? p_G$wr#oWz_?]~+J0=(1 r#aKO+ؼVXZt[Vg. U׶1^K^wʰ6}[m[,vyASr (O$4x'J ![XfmIVRTa0v 2磋wiKH|YEԭ) Nؓ̒# F+̾(%&iؤ~)L>[mIf,oJܠlx|K/j'z'O4}3EXo4X'o2(IYč-e€{&cfuycxW:z4jIn'*YX 7׊.dd;vy11N _ Z@>#ftiP-)s;VR`VQEQEQEQEQEQEQEQEWxF׶ZתוxF׶ZWoeڸ;˵t+f*ƶ[1PթJjqҀ (?5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( +ZZ+ZӺWuvۥlX+f*8P-N:PEP5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ( ( ( ( ( (8_{,mmjita؂$195,":V7C_+! r9E^]ize5GX{H8短/fnm O63ӎkzYX.d]J0y&L b}0kf}Z;a^ۢ0JLnۋ1 iMEŕJ+8J;MD{]F ݬF9+pzJ/aZXg Sl985 ` 3%is8sK?B6XߪW Dp85iZ[ pjؠBv7Fdir20ZF1WSY_Ebai#F G^쬤gi-c##9dRYBxi=;S$c%s">`Pkj6VL%l,on/8glBm#+Nķ0j"Kjg6n\BdǘX)l p=o^{.MxJy 1MͤiZzqր8CPʚтUǞ8`,nxp,(i;)#+R2h\۾Bo+Iҭ]䶳 %R gѣi j+cl-4"$ٽJMeJ˨eLm/< VSjZՙPC7ٴ{{ã 39G,I1Ӣ޽mv-F61闱m8ipx,=a46(Z;H'dX)0W,tnmB[a̒13[xpV<sVo4 igH=r9HR6JpNFƗ]%դ+?HՁ|rNz㊲C",O)VU**@TGuJƮl3פе-F!#U1 $?.>p5,ĶqՀ2OF+dc$Ts@f-KD-m$nW9;5Ĭŋ9X2Ǖb$KSOgo"M'"J鹁拍'J;y+1$Jp1P5s ص8Yd8̛2 P3Q 5 n|L~kQ1֐LcS xI413HHK#|Hrv_n7o濖ݢ[Qm;3A¨g\ZxZ RJ| YAQ8rװ@IiV51=I`2sS[Y¶$1.vjFI'q9u'iVܗtbHڌ{ϽYVViRk90~FzEJC9aCEH \P:jz|RkyI.JWBUQGsjc79[BJ#2`z9N{Kh`c8Y_ ֬,Q$*"ɍ2{t:]cQlL^Țqb[d&Q2ɏMޕ~KWq `}w{6r5: z;Hd;zN-mm6zcڀ9uMSN[ȗ-*ഓ26B?xߌ> ƚ i?|ߐ+FxġB'*19QAi1$.dR5PA wEuK˛Xwoq*UF#lc6:sֺE(cCeBUr b8@QEQEWxF׶ZתוxF׶ZWoeڸ;˵t+f*ƶ[1PթJjqҀ (?5>ZN=iIҳgzrC]}J5<_-/k+u"ާ^& ( ( ( ( ( (??i-;oW:K~-+$W}hg{M/P^\'}\CbQ?ixgau\&fOYkˇǪi[A9E~ch_өľ>և%|bCSv붞ŪM>#X4jߴ?|T_!񆯣;DY}$l^[K'(z(↿տi=7=;Ɩ5<7m)_\h:-ϖPۭDJf[`\ugMZ.VVY5o dq- TX±0*vX/G_؍3}f$Qog [$̒>®v,f$M~[iZw_Y^xrƺWWk^|cwnaY-iBFq$n![ O㶝]O{+haլK WC.',a؈ R(};V+~a[CB:0COY% [;U&?0I[ڜx[ÇŇǑ@(r2?'א .q<5E~gx^ǯ;kԍnt:"]RUlwpi%4,u1gCU>xݠ%MYZ Bs:,d4 OPψ| [kgZN=iIҳgzrC]}C\Oȷ״ |{K(Ut&pH-ޣ<'+#¾$5SM;om`Kv8#0]-|%km&Vg7YX1?0  ;ogMt Zx7 ,4-[,MqCKp#7{o(y-c!;dN R9#ؾ C/dO/R(((((((X 6-.X3ץ-u9?m9O/ ?O|{3vmE[m$Uci֖6Cr5[mRm؞#6_'#?eR=|/J1e]wNe%uc,<z}kxf/!A[9IʒI{'獗IbxxT^ ~׾|1m.4/_HQlnӬ$2(E{2; v ߏW[qEǩkR\6CA,(̊ȲѩB+) ?eR=؞#6_'#7ψZ#|OmkO xnOSssyæ%k*aXDO/Hs*gz?aI@ii<嶮噘9O/ /.mz%y'獗IbxxT^_&o> i:whѴ}+1-ơ=^yXڣ8Wdl@bxxTG'獗I?>>?tU5]b-5[CkE{qj1$zmIqq&+sj)vfbId]y8U{ +McK_[`B<Ƃf'q;8Җq`M\^;V~;Vuv+N lJVUmҶb SQE5>ZN=i=gMހ9ֻ kht?Կ_׼WxԿ_׹EP{ VsJnld1Ï:ۖ*ǒ3IzVfq? jz Wjs"HٮH0#Wax m@iԵFV۹SDcBy!rI=&QEQEQEQEQEQEQEQE:Ö"[L ear8QsUyQϿӭn$?,(J|Y'7%O )وdKkPF-r*ۂ   ?? o mbM/Œx2x?jk9I1gVֳypDdTOۦO6mkO.n;ۘdX K<8E@8Ɵ!=?<@ZN7zjnBc(0%)+[~J< [ u/k+"7TH !.y aq?<G!=_Z|'uMf_v-QArcҼD|z ^k:Ԛƫ=w/lu_Y;4)o 6w*Ŀ31$C'@{(;x€;&"fԇEBX'پ-\+0߷n~\^aw9] h淤OKoM^$ix޷Z;_8*AOC'@{(;x€1lg];E𥷅7_i"CWgki+9n<HJKnx*-o%c̎Iw,r)1ٯdĔP9??  xf~|4Zi[IpUxlXQ*ʠ,B'+;xC'@{(mݴ^+\%ܼ`%TöZE=yڡciZm!V!S?]UK kMw"6pk.IdF?O׉Zm/UkB-W;xr5SivwZcӿt(_$?t˼_gugߴW_ iF"&힕YqJ  12;q+峀C-7kym\K2UQ89v>sMYuՎ<j6Ż]4j$,Aٽ~ @mGBS/ω?+jJl ]@-⬛~\Zt((5>Y>aڴov+s7ր<дUFIck-n`İ΋,nGR=9'RYcxeXA5O -PkdЙ B&C3\(HcțGĨRm)|O^suY,|RW?@V_&m2 s⟦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&Q s⧦W?@ZQ_%&R4ȀFC\ 'V\, xc9thI#Z9HّՕAc#A+L4/+^Ək$Q[Y vCP©QG""/\w?Zp|A':(YU}^_ aź582gy$RW!>cl} s•it1-&q䌏pEYѴw.]ɨj U'DT $@$Ilvkҹ(mێ+Z:΀Vb-/L:TKREP=)hJ* @ k3Xf8k3-s+Ѯ`xv9<9}n,+kjYl=iUjӧzgwzyI@q8WGopҏ]oپgJ?wپfP}(>fQ@q8WGopҏ]oپgJ?wپfP}(>fQ@q8WGopҏ]oپgJ?wپfP}(>fQ@q8WGopҏ]oپgJ?wپfP}(>fQ@q8WGopҏ]oپgJ?wپfP})7ڀ8UӈVc]xS@6շmeӊ܋OZXP;;Lcꬭ)xcPHq-*8[GPV#Z%\Q@(RSTS((*$dTL; j9dMkj@TyYX{Wu%T{<jܛ!Q@1ӽ?waqҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qҏ]}qcNOj=)ڀ94j;WDcҭ%;P<6կ1]pb#VI#"bVTS{T(ÁKEQE(ޝEBEBVFV(yiPKAPq[&?jiP!jOJP Sl=)>=+oyB1>=(0(zQJ0ҶG=(ҏJP OJ>=+oyCҀ1>=(0(zQJ0ҶG=(ҏJP OJ>=+oyCҀ1>=(0(zQJ0ҶG=(ҏJP OJ>=+oyCҀ1>=(0(zQJ0ҶG=(ҏJP OJ>=+oyCҀ1>=(0(zQJ0ҶG=(ҏJP OJ>=+oyB1/lyBD($[`/*# + *Ȏ(%JV @IGJ((((((ijJ(-ݵ=Mc Fj o}| 0( m(6Qڧ Fj o}| 0( m(6Qڧ Fj o}| 0( m(6Qڧ Fj o}| 0( m(6Qڧ Fj o}| 0( m(6Qڧ Fj o.j m.ڛm8->@(((((((((((((((((((((((((((((((((((((OSCAR-code-v1.3.1/Building/MacOS/README.rtfd/TXT.rtf000066400000000000000000000172371417327530600212710ustar00rootroot00000000000000{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf600 {\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;\red28\green28\blue28;\red255\green255\blue255;\red9\green47\blue157; \red10\green0\blue109;\red0\green0\blue0;} {\*\expandedcolortbl;;\cssrgb\c14510\c14510\c14510;\cssrgb\c100000\c100000\c100000;\cssrgb\c2353\c27059\c67843; \cssrgb\c4314\c0\c50196;\csgenericrgb\c0\c0\c0;} {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid1\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1} {\list\listtemplateid2\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}.}{\leveltext\leveltemplateid101\'02\'00.;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{upper-alpha\}.}{\leveltext\leveltemplateid102\'02\'01.;}{\levelnumbers\'01;}\fi-360\li1440\lin1440 }{\listname ;}\listid2} {\list\listtemplateid3\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid201\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid3}} {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}{\listoverride\listid3\listoverridecount0\ls3}} \margl1440\margr1440\vieww19380\viewh16080\viewkind0 \deftab720 \pard\pardeftab720\partightenfactor0 \f0\b\fs33\fsmilli16800 \cf2 \cb3 \expnd0\expndtw0\kerning0 System requirements\ \cf0 \cb1 \ \pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 \ls1\ilvl0 \f1\b0\fs28 \cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext \uc0\u8226 }\expnd0\expndtw0\kerning0 MacOSX 10.12 or later.\cb1 \ \pard\pardeftab720\partightenfactor0 \f0\b\fs33\fsmilli16800 \cf2 \cb3 \ \ Installation\ \pard\pardeftab720\partightenfactor0 \f1\b0\fs28 \cf2 \cb1 \ \pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 \ls2\ilvl0\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext 1. }\expnd0\expndtw0\kerning0 To install OSCAR, you need to copy it to the Applications folder on your computer. To do so:\uc0\u8232 \cb1 \ \pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0 \ls2\ilvl1\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext A. }\expnd0\expndtw0\kerning0 Click on the OSCAR icon and drag it onto the Applications folder icon. Note the green "+" cursor indicating that it will be copied to this location when you unclick:\uc0\u8232 \cf4 \cb1 {{\NeXTGraphic Oscar-mac-4-click.jpg \width15040 \height12440 \noorient \appleattachmentpadding0 \appleembedtype0 \appleaqc }}\cf2 \uc0\u8232 \cf4 {{\NeXTGraphic Oscar-mac-5-drag.jpg \width15040 \height12440 \noorient \appleattachmentpadding0 \appleembedtype0 \appleaqc }}\cf2 \uc0\u8232 \ \pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 \ls2\ilvl0\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext 2. }\expnd0\expndtw0\kerning0 To launch OSCAR for the first time, you will need to grant it permission, otherwise you will receive an error that it "can't be opened because it is from an unidentified developer." To grant OSCAR permission to run:\uc0\u8232 \cb1 \ \pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0 \ls2\ilvl1\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext A. }Double-click the Applications folder icon and find the new OSCAR icon in the Applications folder.\uc0\u8232 \ {\listtext B. }\expnd0\expndtw0\kerning0 Hold down the "control" key on your keyboard and click on the new OSCAR icon in the Applications folder. Select "open" from the menu that will appear: \cf4 \cb1 {{\NeXTGraphic Oscar-mac-6-right-click-open.jpg \width17320 \height11180 \noorient \appleattachmentpadding0 \appleembedtype0 \appleaqc }}\cf2 \uc0\u8232 \ \ls2\ilvl1\cb3 \kerning1\expnd0\expndtw0 {\listtext C. }\expnd0\expndtw0\kerning0 A window will appear advising you that "OSCAR is from an unidentified developer" and asking if you want to run it. Click "Open" to grant it permission: \uc0\u8232 \cf5 \cb1 {{\NeXTGraphic Oscar-mac-7-open-confirm-with-cursor.jpg \width10640 \height6220 \noorient \appleattachmentpadding0 \appleembedtype0 \appleaqc }}\cf2 \uc0\u8232 \u8232 \cb3 On macOS Catalina or later, you may first get a window without an "Open" button, so you may need to attempt this twice. You will only need to do this the first time you run OSCAR after installing any new version.\uc0\u8232 \ \pard\tx220\tx720\tx1440\pardeftab720\li720\fi-720\partightenfactor0 \ls2\ilvl0\cf2 \cb1 \kerning1\expnd0\expndtw0 {\listtext 3. } \f0\b \cb3 \expnd0\expndtw0\kerning0 On macOS Catalina or later \f1\b0 , the first time you import CPAP data from an SD card you will see the following prompt:\uc0\u8232 \cf4 \cb1 {{\NeXTGraphic Oscar-mac-7b-catalina-permissions.jpg \width10640 \height5360 \noorient \appleattachmentpadding0 \appleembedtype0 \appleaqc }}\uc0\u8232 \cf2 \cb3 Click "OK" to grant OSCAR permission to read your SD cards.\cb1 \ \pard\pardeftab720\partightenfactor0 \f0\b\fs33\fsmilli16800 \cf2 \cb3 \ \ Troubleshooting\ \cf0 \cb1 \ \pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 \ls3\ilvl0 \f1\b0\fs28 \cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext \uc0\u8226 } \f0\b \expnd0\expndtw0\kerning0 Help, I'm getting the following error! \f1\b0 \uc0\u8232 \cf5 \cb1 {{\NeXTGraphic Oscar-mac-8-open-error.jpg \width10640 \height6220 \noorient \appleattachmentpadding0 \appleembedtype0 \appleaqc }}\cf2 \uc0\u8232 \u8232 \cf6 \cb3 This can happen after installing a new version of OSCAR. Open the Applications folder as shown in step 1 above and then see step 2 above to grant OSCAR permission to run.\uc0\u8232 \ \ls3\ilvl0\cf2 \kerning1\expnd0\expndtw0 {\listtext \uc0\u8226 } \f0\b \expnd0\expndtw0\kerning0 Help, I'm running macOS Catalina or later and I'm getting the following warning about malware when I try to open OSCAR! \f1\b0 \cf4 \cb1 {{\NeXTGraphic Oscar-mac-9-catalina-open-1.jpg \width10640 \height6180 \noorient \appleattachmentpadding0 \appleembedtype0 \appleaqc }}\cf2 \uc0\u8232 \u8232 \cb3 This is simply an extra step in Catalina and later versions of macOS. Click "Cancel" and then try step 2 again to grant OSCAR permission to run.\uc0\u8232 \cb1 \ \ls3\ilvl0\cb3 \kerning1\expnd0\expndtw0 {\listtext \uc0\u8226 } \f0\b \expnd0\expndtw0\kerning0 Help, I'm running macOS Catalina or later and OSCAR won't import anything from my SD card! \f1\b0 \cb1 \uc0\u8232 \u8232 \cb3 You may have accidentally clicked "Don't Allow" at the prompt shown in step 3 above. To fix this, open \f0\b System Preferences \f1\b0 , select \f0\b Security & Privacy \f1\b0 , and select the \f0\b Privacy \f1\b0 tab. Scroll down and select \f0\b Files & Folders \f1\b0 from the list on the left and then check the \f0\b Removable Volumes \f1\b0 box under OSCAR as shown: \cf4 \cb1 {{\NeXTGraphic Oscar-mac-10-catalina-fix-permissions.jpg \width15600 \height13700 \noorient \appleattachmentpadding0 \appleembedtype0 \appleaqc }}\cf2 \uc0\u8232 \u8232 \cb3 You may first need to click on the lock in the bottom-left corner and enter your password in order to make this change.\uc0\u8232 \ \ls3\ilvl0\kerning1\expnd0\expndtw0 {\listtext \uc0\u8226 } \f0\b I still have questions! \f1\b0 \uc0\u8232 \u8232 \expnd0\expndtw0\kerning0 The most up-to-date version of these instructions can be found at {\field{\*\fldinst{HYPERLINK "http://www.apneaboard.com/wiki/index.php/OSCAR_Installation:_Apple_Mac"}}{\fldrslt http://www.apneaboard.com/wiki/index.php/OSCAR_Installation:_Apple_Mac}}.\ }OSCAR-code-v1.3.1/Building/MacOS/background.png000066400000000000000000000526611417327530600210270ustar00rootroot00000000000000PNG  IHDRJ cHRMz%u0`:o_FULIDATxyT՝眪^ifߗfQA@Q#5&.Ĭ&$NQk&3h"g2$:$bhԈ ʾ 4=ǽU]t7T#yWC-oWs=,      @@@@@@f9@enYmgѥ ]bbρ*I5kUta19 OI%K*[ֳo I{6H*\I9ph(XIc$ ^G}&I{ISi푴I҇ޖ_ d&MSXx=嘴`E;^*>@8n>&$IGwϦe$=]ifJ:Ka5-օ>W.PXIKu V$}^ Wve1IK%=! lOaElb>+ӒA@]UIVaU IIzQҽA@]EQvscF{#59Y#~T'yG_ k+ު@;#K:E~K@gYIe3fDq?udwwGg~LL#Jj6ϭ.k̛[*PWNtI?Th:s?[t2Իo€}{g~PWƖ 7w߽l_{bI?U G&Q)t&5eMiы[` vΪ_I<[#INpԎ~#?1ԟ{ߧ>f JCIS$鶶яV_?^1Ic{U׍7?9wM-6 \m7o8Ձ+y;gX_Q!I!MaczO];N <2g~(CǷ' zd1ЍgHnLҨt=>3azt\Y@tQcGd_2s%}3 ]s>ytOpxv_xCٜY@ HӞ0۱= Q;} ОQ}uN`slFOt)g^ɃI v43uhi@ӹrs/M8 'tY@{&q|ir00-a,4=#տqZržpm?nCtY IqN5hN+) Z1ŭtm1͙9@q]}b~+͸^YCRX] ;k=gwWkދ[u߫;%Isf3WѬQ=RYB Ooԝg !6g~̌԰4V .@(˘y^ݩk]'I*镧./ѤoBM\u}@) bzc  `z՝CQ=3=\{"gǶy^ܚ'Iw5D%Zty/nUM5p^u>~@_PpվzU^y7{|q@6~S Wadhd=١{P[ߋ\\ӽ^'IC3#K .nt&7V {%+S5{Nyzϫ%a? `>o *T : MMY@0T57Ǯ`rT1 7>)UƤߧpU;fM+=xk@];ā&,MVK+͆d+ t.H&CTK'ʓcASAjK `K77{ήyt]*^}bz>LVd9]?fӫ00gaլ Von\ڧ^jR\oeKL OV5CA;iPa`ymФ3s@ê;+z]xk@]WL? 1f/ntmrn5ӝg W)[\ ިI LWZz6vӪuL\lG~Fl@V )7Z:aPtbI %&κx@27#5*=Fz+.;^{4><|Y `F-68E+^fT VK+シKSldq;/ 乘&,2 .,'S MrDf k %ޜ=֧GK`}U^:u*5Z|Y=Ww4ڶ&7f= @0 tmCEkڪ[r5mҜ5W^\OTK+{qܾ AjM.W/Fdh xk@]W*;چռI_1kTF7>ѦIw"iؓ=a;DӇk.h*$7|sfw6/;Ƨ>/԰zaE9>9XpKma W{&W7 gѦn"CX&C@J`W]3냃y/nMUgdZ5{u bzW WW>LcƧ7ۅ${^lLƪn05a[xE@8zd֨Zqj۲Muw^ܪn|zf/үkvGލOoҥW7XtnpUZHaU s |^g Zq`v߫;`yia-]( WpJ+M*`zCIʍe㺌W4݌7-ΑL#pnt)d٦ -\O W5:oj[-[25V5f켼x߫;Rl4 []ECרpₘv:~[Oga^~@}D=ʇdjtxı1!<ڮ ^Ǥ8@bj2u}խ_73}K[nʕ+3z+V3뮌w򗿬~zwt 5 JRe&'!{L>y}[oմitW?qt7o߮{ַt9_{m뢜@uU*CUZߊ u]7oN׿uF~nMr?|͛7Ovjkk}ܟ0aNGz饗dɒvgQ~~.bM8Qg5gΜV2|   е+=VjQo}cׯ$[nт TQQѮ믫Fr$OT<o=CEE/^:uM7ݤ{キӧ?/b]V7onqj5 RҾLl6#@?x<^S:Hd**$V`_{L\pO8Q۶mS"Ѷg}VƍSnnn1kU\\7|Mݸqw;N;4}m:sN/fΜymcǎU]]ݚs!-KZ whUVV˗k6h A۷{/]T_~y1BV|G4jԨFo?~6lЦ{ѣF}mӎ;Z|ћ-/csJ+fPoz衢n3hڶm[ommݫ &4z!CZ"=Xp ֯_?mܸQ55_H|bEaaa})!f` 6dժU:|l4Wo͚5 @^\\ܪ0U/ԖjĈVTTxEEE->s21[&i'o@ {%גV~i̙w﮽-ZDeؙg6ynTHk7n_yF^ye*ݿݲcnu͵6{n~ [̬ $9|s&wъŋs.=ضMw'Rv,IKh`izn&\^pض@{o-[knm߶M .-\ؚ5c;ܚ -/Wo(Gݮ|^BWVUU/K/н{Ƅ^VVI{sU믫 'GUUZa>7a6LJ&lsZA۷֭73۽[>οB34y e… 2q'khUaJ9RLɑ Znn]#hkz{7^SOUq>;e.j) \Uٽj{z{)'G_ӟT_W54h`]v shEgkY\,%6Cٸq>w5UTT={7FsbvmߥF+[o_gWI6@*I2q&7ؗ`|y>TzK9ݚήFRY,nݤ#wr[95@!kIjOE`Ing}c_XF~3<]vzcKEF~ec =2Zz=~L~,&Vp?Oon<_7Jtܹ:S-PPPhzJzjŴ*য়~ZGuT=&//n>dצMtץ5@LU@kۧں}m-Zݻk̘1z.555`uu}~:n,޽{_~yk9Cw\olު=Ɖ_@ ofV???_ݺu7nٲe0kSVV\>}w:ȑ#{eeVZqƵ;X6_|N>6F s%i{nOMBV999vXb&Mp+7|SSNmd֭S^^zɐrM<9 R@ ILU2zݖ}] 0 ݱcJKKէOI[*sՒVr@ I)/)ksj۶m*..y 7nI'!277W`: >6=nVri?]td#w׺u4lذ}wO{RT4PKƘzs^u/?܎;dQll0p˖-Z\R#GLu{jz(/g4F႐iIZPg_NmݻwڵlڴI6lА!C} 굁I_ sKZJ ܟɃ X[[*YkתU4eʔ|%-Ҵi2rܲTP[hn&M n߾}***ҤIF/}o@Д%VQAAbV^hyyyr饗^҉':4-/Yfep… 5`ckf-( @ML5eͮ^S^^1Zt>}jڵKׯW<W3,;];$u]UUUg}V&L=տ fCLOlО&/+**R֓O>FI}Ν;dɒU$iȐ!Պ,1ڵkV^ӧOlhᜤ9TF{&Lnn ^xA۷o ';vXz'tWdÆ _JguVƎURR9scѪDO=ޞwR@R/)=xmW B.2p KTXXykh^ziZzꪌ믿^;vе^۞JIe 5L9] ~˲޵Ke{Urʌ-HW]]zyiӶX.KVwL d1*$Lғ<ު@aU' 6:[[[Zo}fS[$ޖqNȽHϭ.3_ZۋG&̺״ik?;&- h >R<.;@_}l_ڴ43 6HKh~&{{ݽKv%} GA@Ik3y:%+ ]2U[>K3#(*u^ymsv=UT4M^:J[5״_on*󎶗'Z;[&j@[!3}Кk7l(Ϫ!{?hsYm[W:[a]YT{Ѧ}{ > Lp^Wy#t|}s ! LҲ8o%w-kKkܼ{^}SV6#?@KGGxMi涅D5pOUglµ:>p6Iz#p^?Ŗܵ/^_i zvվ~C|С @k͗T) b>j3uzIwߧѽ:?T?U>fΪ<}+i//@}߲#?V;1>g\}5em}~H-wLE@I&nI;?kz?zI#>x~yGU[K$'A@I}(QN贑'zAdcV{מDOU@@eQ'ס9hΘ#4sd#пBϯݯokv^SIf |0鳒.GȬwȁinwAܜ1bČS/z/_뼩Ix-\ol*m[b?Iz8?+%HIWߨ:m`tn4P7%ϲJҋy 2[$4{$= @g (ls$v-p?JZɯ+2FbfޑPZe>T*r]Q{_  1I9ϧ\(q3גH8I% _RoI( V•ʵ6+\ @F@,c9@@@@@@@       @@@@@@@     2.)hcLU"iU#Tԃ@P<|\u˚ ΫDҜF_ȩ&]Ւf7q@qE<[2I&n+UY@kQ̒L3/`P"(FCcv;Y %<""@R+z[E @$c`fqh$55N$Z9=usDs+{.n5>dLh-?d vAd vA vAM ,C%55/ vA-a涋c fV97]0˰ fv,2@]Hn/ vA ,2,R, ?@ᯩ ` e05U,@@r`D@$0,2 YW+\@B%4q 4 f`)i6V`2Ijz`Gx9 9|ߟh-*Ј~h8 E=5cQt9qNOuuZsig@WAv וϾ% d?-]Zg& d=m1>7 [uֱ& @6i(7)_[' d47u|xf @x+ XM@l{˰s dS?_@@Oy_ߛ| @p/m߯{-~ O.Oϟ* h _N=9o# @sw#@l2nd}w=mb [,2j{}!ŋ߽5X!o~=so Ⱬ?___<-T֞2> [{7Ǯ!M.ޣ]C |g(7)g2 d[/8M;.^61-v~?o& @XtӥϾi^!! ]~sߗ [<ܓlc@& t[L5> [3*Ch@&?xW׍Mn87)YfySm,܋fiw>z @X|nK57\yn-~[i krϭ٥_<-ۻktSl@lS.ˮ!M~p.Vx @6SǸ9Mq?x> |T7u╛i.w/Xdz{-!Ţf;zˆ]xۧ<FYx9 9棝KJm_2VY}5zsc2rl/VWk+*SQꞟrM=P Ɲڼ\:e`Z ˔י ׊-n>_ӎ"'郭ڶBg3LXy6)W:oڥԿ{N9rڰCKkoE>vvWiOE. ^2%KגiՈm)-Mspא6.^ Kz& ~%mHKݜ*׻[K+6ɢ'uM)0+\\k?6ripA,c$N=80 A]X U@;'\xh8ؤ]bppYUBQ#I$jiȣ_;c @*waRIgFK>`m_Z@G*i{) jK[ bK- :YʪZ_,W}u۠G8˦^Ljc5f)טՒ&ռ\sHhKL ꟢ `pp4Dl<|/[`mQL,ؼl`<=~d+ȡa4@ ͭȽ4 mUp t :u+} sR$|tIp8+P^uz5_X#{˫gQ!P?8osG_~W%-XiFgV rT7!oxecl^7u?N8ekq^9yYDȘ&ƅ{M1줁6_~֘usw#܋fk@rf7qj|5rq?ycdQ꫕d h30`W#ӯldmo1R,1Vu mt>qk|P(O䓑3i%(\Z~z%:~icbW|{a{ 7?^<ßvPϻ vz ܦa !ۭXqENߣP,esLɑL*Ba* Z).Ŭdb&Hƚzk?ǒ1 iddclLGnjb1)frQJٰ$)/c$gLT wpi^^N>c^kj况}Onڴ֚Gi[.w3~Z?;Z͵ax1r{U,\+7Ǣ721+k%ŔDU Kl,ltX083V$Ȅ ?ʑ8 FpjS8TlVRitV'Tc=[ME7]f[Y+#1&vL/ߧ]EVkaGi'dl cLz/k}x](Č5.Y)fb+fb)[ṘbF1R)ܽ#rJd[@a>-J8n#D B)xɿ]Hĺ0KZQŹG>WV ]VO?.]î! f5җ S.P 5^&S\a=*p&*eWDEjdO?K>= |G+G0)p>x:8j Ƈ+󼴽"E}njyanՕյʦ^sݓm5x ;=Sf Ud|P?*Y pJٸ0_LxM0$JɹyQȋY.,yG;w|:](%LH VA; AVNNQL=z*צao*k cYT \p, FNX~>Fn\~^KqwAnA h>v2G)+H6bR[\q+ >ڽÇ;a_= >rF!`.&fz(+j=[f*&B௯֖yɥ I@t~NQWeEFCQhxG!*X۴`%>;yH&+9ԛ At€EM}" > vA]6%Cc4E\ &W72N3`8.epϞ*MV%=€ª38pwa=k MkA\5a&d L*M6 tceD*ՇƇdL8XX86kxyef>OC`=`2zc.atptR;zwM]Y0{g$iK08DWQӋL2d! oR{zD8,%%%%+znKnFAu{:.UK &AD4?0Y*GUB_uw/Fa50x}{BϩYo&ٲlj6 ]C|xfVTV^*vAg Ÿ ] 8DT]Qra.B^x.=@]\‡BOy(%VR+Q¯9 jrpZptia6}ppk'v%gLPTfzV^a}w=!` ^tAshN|`ZY#/zo䢡Ym&gdb>BN%AEѐpۇ ¿ͤ.NRIVˆCƚpeLvrrF:IQihWN~F[)N>l] zWw9av֪gg !ؼ1ٴ8d͗S=lK+u14]ZIf )/F^JJ R̆sl[2gàmν 7u^'7z>6 |5ᬼN"Nu{. & ޘheIx o$m ʭQ4Q L%o΄kaypG>ⴁ#]]65~a%{bW 'C]ҝj~oҥNH%0a3 axއb sar|QuPVasV޼r>Y#kl1Qeιh?hчO! &kf6~8iM0P&Z$mTMmL84'i=\7^A[G 8`fvuO}_>eG禎 &.cNfuEt] pqspq(مk%Sui ʄ{뭗ccZkLpl4$MP?o҂$5& = б~Vka415TVOTꂒaZPU-^>}l@\>þ@a 0L4OpQy '%Cdl8TlMn,jɇp69vH^OV {0y(s*gg*d4uЫɖ2u}6+[8^Z{L&/!U+Z|{ 9jKenTf>yw10+DTR))gl*h>ZdEτSl2}ʋpuPd ;lrƅRLORdE} ~J?S09- diͥ'WEЁ:sX1{קOu3u|{i`4[aů%4 ~,@>061yto8$*cS!0åVڰtr}W[Ȧy\ܡTSiECĊF Ʌ&ѼAa\4/FNV|7zO^'7 _US;a0HxHckvZ_ɞYd_ BN~~n͝y伓uA/9к(0E'Tl*I3좩QM'FG>*6 MK &7i F |S;$à,&WGOV&þsu[!i{ ׸:x >SH7o*ݻ4?LRXϋ5қ \+gl8 pO_.Lj6E!ۺ0'A%\PeM,% f*vEsjKՕ$mul[ɄӫzU9iM/ܽpKyK˪jur޲=kvp)խ~Ď 0E?/c=2x^&9žJ֦x z6Z6FEC|xZX% s\ؼ0u U7'GMr`_ϧѼ?9)V&jzL&󑹱t_fǾx\n9; 9de/Y$9/Y/D.ƈvHΗK#cJ]r֤t %K BBRUbcJb΅QE*_M Y]D'EEO!JĬ>6_csM,)߻mOVD>vӷ?~ v*eUF_6Ag˱5A`{+KνW(Y#-9IhgE]lMAhdmxǢ>|& -d߿harOL%B #u>l3]͋TSsc:s|FWo|p =A ?9c%=/O?*~7J dd@9d}vX*\5nvu|Uc?(FrQ3阢U܁Jp+d?at P64Q#hB@RÍL@_2&wxŻh1qvoJTg? 핓G+_ӄ(5eaYͻ@ ƧFQ@mذ9ٴ97Zbl& 8bi6a L/0*j`r`6p1R܆ۺLG\,Ni{G` qKR'}y>j\UuD<{3;v`|m\ya~gOOLC]*]#zJ.uAj"6;mlnTus?$o\T?v| f&+㣊G!0KeSQ"شnz?Xl~ǟ 3FC̈́e ]i6\L,vݰ6j cFQa yk-l}[$ONG+Z bpVqXʻ^>fNuNNco%%\n_0ɸhN{ V?4_猨߾+Ap^`6:w> `'1w9 %Yq.KJr6&d9k K +>-0'VLqԑ9lhS!0cQ JD?2qXNX3rƆy9od 9&#Uɡ_aZiB5vkmadK?I@o=@ 9i 4ֹ31LZCĢ+a313p6LȚ0 FV&F6Z$PƬb1+c +kUp2+lXmbV.021%F5VM7 iF߲~}ケqCgi@{ gSϏŬ3ɾyD?EBhh8}޺f? WG6to7) ºbԕI-bIV^ta 5=z1`M$o=s;#c5>T?8fH& w{^,XM(|M*-g=TVQ+[pVֿ_􋒓_-^b26G4V9992z=9='vI /> %USrN>H)dt[AlYqoNUSZo{O5ӰKkD*IpisM_}6ODy)-{)moc-p* rq{R7v+{tۖ# 'YkϘH>6iӒn~ 茌1 4WdaŌ&~= ]靗OH>prAO^<V\^֫)GΝaV^<'^-=+N(W2Sko|0̌>TYC>:.8 IKѪ0P |:@5 hx_ll>duv]y,WNԥ6^u5O$:FFWß$ys.5/9^>N^s {H>15w_M*K''`WYa?atJШ8OU'Cs Rj~(žK.^~$ʪ@CFw;7]2VY2Yd_27!?%R?@;T$4w$u?4{*#5>V. F!^ASeq=t摉 y7&Q,u>k*Xb^4@'DM:LG9BFRMm Tڎ8x<)%=G0^?8f 8x⊚DVRm7dw 9- ?d1r )aS yX*󊖯۵/+~o>-ɳtj 3VF,d \#pRS?|+v5F'(#j>1Y?otٓ/Gþ- UO ۽xՁRv1V'SȒSv^ aV4ޅOъ_p s4唞TX߃˲e9?{^pi\8p+_sߙʯ;u_g˾KӏdwaR;|!IU>8Gŀ-8"(]~~Wt߿`??aF''D; }T/2jTk] lM;sũ~A?gkW/FеNG)NX 9Xb_ @8mO'O]o{b۞8>^QFg @72s/Hx8Pg_2zed# @<3ⲹ3^R?v/Pu"]ڄ3=*>[UvEVfIypd%OM\{~ZPpkey|iG|õLokϘH^bo>jDI{CS%Y!`B~lOCef޹~I% @@W7?/8z Ydr9C`K܋fH]ڷxR5 C; \QQ @so_ל6QT 9g CSǎLC?@fOsOdw@Sη\q6z}+] ߱}u'_cn36 t"{vd%/SwugO" @Ww/gӵgL$h7acw?z~猉I M 3F@nv tafK:@8L|㯯!Vsh QZlcw@i{O  @Ww=O=سCE 8Gf/r$=?ٛ=џ?]ݔO8O=]m,3 do<TjE3 _CJ< @6/أ,=cg ΞD@={l} K, :GUӏWNG-*!'ک@t|{E3G# @W7%ߣ3΃!`hyO,!t@\5{da:%*Jv\# @6Wi u9 ՝OYcz?]?~Ξ8_{DNE p3|^s8< @6v-ϟXBeLtWn6ٔjS#A $ :?V@@@@@@       @@@@@@     0m3@DIENDB`OSCAR-code-v1.3.1/Building/MacOS/create_dmg000077500000000000000000000076671417327530600202300ustar00rootroot00000000000000#!/bin/bash # Usage: create_dmg target_name file1 [file2...] STAGING_DIR="./Staging" # Extract the target name TARGET="$1" shift # Look for the .app in the files to be added to the .dmg APP="" BACKGROUND_IMG="" for src in "$@" do [[ "$src" == *.app ]] && APP="$src" [[ "$src" == *background.png ]] && BACKGROUND_IMG="$src" done if [[ ${APP} != "" ]]; then if [[ -z "$QT_BIN" ]]; then echo "Error: QT_BIN must be defined" exit 1 fi # Get the version from the application bundle. VERSION=`/usr/libexec/PlistBuddy -c "Print CFBundleGetInfoString" ${APP}/Contents/Info.plist` echo ${APP} is version ${VERSION} # If it's a prerelease version, include the git revision. if [[ ${VERSION} == *-* ]]; then GIT_REVISION=`/usr/libexec/PlistBuddy -c "Print GitRevision" ${APP}/Contents/Info.plist 2>/dev/null` if [[ ${GIT_REVISION} != "" ]]; then VERSION=${VERSION}-${GIT_REVISION} fi # TODO: possibly add -no-strip to macdeployqt for prerelease versions fi # Create a deployable application bundle (if it hasn't been already been done). # Edit: do it every time so that the application gets stripped, just suppress the spurious warnings. #if [[ ! -d "${APP}/Contents/Frameworks/QtCore.framework" ]]; then echo "${QT_BIN}"/macdeployqt "${APP}" "${QT_BIN}"/macdeployqt "${APP}" 2>/dev/null || exit #fi fi mkdir "${STAGING_DIR}" || exit for src in "$@" do echo "Copying ${src}" cp -a "$src" "${STAGING_DIR}/." done VOL_NAME="${TARGET} ${VERSION} Installer" echo "Creating .dmg" if [[ ${BACKGROUND_IMG} == "" ]]; then hdiutil create -srcfolder "${STAGING_DIR}" -volname "${VOL_NAME}" -fs HFS+ -fsargs "-c c=64,a=16,e=16" -format UDZO -imagekey zlib-level=9 -o "${TARGET}-${VERSION}.dmg" -ov else # Arrange the files needed for a custom Finder display. pushd "${STAGING_DIR}" > /dev/null # Add a link to /Applications echo "Add link to /Applications" ln -s /Applications # Add a hidden .background folder and move the png to it echo "Move background.png into position" mkdir .background mv background.png .background/background.png popd > /dev/null # Create a temporary image then mount it so we can tell Finder how to display it. TARGET_TMP="${TARGET}-${VERSION}-tmp.dmg" hdiutil create -srcfolder "${STAGING_DIR}" -volname "${VOL_NAME}" -fs HFS+ -fsargs "-c c=64,a=16,e=16" -format UDRW "${TARGET_TMP}" -ov DEVICE=$(hdiutil attach "${TARGET_TMP}" | egrep '^/dev/' | sed 1q | awk '{print $1}') sleep 2 # tell Finder to resize the window, set the background, # change the icon size, place the icons in the right position, etc. echo ' tell application "Finder" tell disk "'${VOL_NAME}'" open set current view of container window to icon view set toolbar visible of container window to false set statusbar visible of container window to false set the bounds of container window to {400, 100, 1040, 610} set viewOptions to the icon view options of container window set arrangement of viewOptions to not arranged set icon size of viewOptions to 72 set background picture of viewOptions to file ".background:background.png" set position of item "'${APP}'" of container window to {210, 350} set position of item "Applications" of container window to {430, 350} set position of item "README.rtfd" of container window to {210, 220} close open update without registering applications delay 2 end tell end tell ' | osascript sync # unmount it hdiutil detach "${DEVICE}" # now make the final image for distribution echo "Creating compressed image" hdiutil convert "${TARGET_TMP}" -format UDZO -imagekey zlib-level=9 -o "${TARGET}-${VERSION}.dmg" -ov rm -rf "${TARGET_TMP}" fi rm -rf "${STAGING_DIR}" OSCAR-code-v1.3.1/Building/MacOS/finalize_plist000077500000000000000000000017351417327530600211400ustar00rootroot00000000000000#!/bin/bash SRC=$1 PLIST=$2 if [[ ! -d ${SRC} ]]; then echo "${SRC} is not a directory!" exit 128 fi if [[ ! -f ${PLIST} ]]; then echo "${PLIST} does not exist!" exit 128 fi # We have to do this here because qmake truncates the version and because its template # doesn't rebuild Info.plist when VERSION changes. VERSION=`awk '/#define VERSION / { gsub(/"/, "", $3); print $3 }' ${SRC}/VERSION` || exit /usr/libexec/PlistBuddy -c "Set CFBundleGetInfoString ${VERSION}" ${PLIST} /usr/libexec/PlistBuddy -c "Set CFBundleShortVersionString ${VERSION}" ${PLIST} GIT_REVISION=`awk '/#define GIT_REVISION / { gsub(/"/, "", $3); print $3 }' ${SRC}/git_info.h` if [[ ${GIT_REVISION} != "" ]]; then /usr/libexec/PlistBuddy -c "Add GitRevision string ${GIT_REVISION}" ${PLIST} 2>/dev/null if [[ $? == 1 ]]; then /usr/libexec/PlistBuddy -c "Set GitRevision ${GIT_REVISION}" ${PLIST} fi fi # This is where we would set CFBundleVersion if needed in the future. OSCAR-code-v1.3.1/Building/Windows/000077500000000000000000000000001417327530600166605ustar00rootroot00000000000000OSCAR-code-v1.3.1/Building/Windows/BUILD-WIN.md000066400000000000000000000156111417327530600205000ustar00rootroot00000000000000Creating OSCAR development environment on Windows, compiling, and building installers ===================================================================================== This document is intended to be a brief description of how to install the necessary components to build OSCAR and create installers for Windows 32-bit and 64-bit versions. On my computers, I have QT installed in E:\\QT and the OSCAR code base in E:\\oscar\\oscar-code. On another computer, they are on the F: drive. All references in the deploy.bat file are relative, so it should run with Oscar-code installed at any location. **Required Programs** The following programs and files are required to create Windows installers: - Inno Setup 6.0.3 from . Download and install innosetup-qsp-6.0.3.exe. - GIT for windows, from . GIT for Windows adds itself to your path. - QT Open Source edition from . I use the latest patch version in the 5.12 LTS series -- version 5.12.8 at the date this was last updated. More recent versions in the 5.12 series should also work. **Installing Inno Setup 6** Inno Setup 6.0.3 is found on . Download and install innosetup-qsp-6.0.3.exe. The deployment batch file assumes that Inno Setup is installed into its default location: C:\\Program Files (x86)\\Inno Setup 6. If you put it somewhere else, you will have to change the batch file. Run the installer, accepting options to install inno script studio (for possible future use) and install Inno Setup Preprocessor. Encryption support is not needed, so do not select it. **Installing GIT for Windows** Go to and click on the Download button. Run the installer, which presents lots of options: - Select whichever editor you desire. - Select “Use Git from the command line and also from 3rd-party software.” - Select “Use the OpenSSL library.” - Select “Checkout Windows-style, commit Unix-style line endings.” - Select “Use Windows’ default console window.” I find the Windows default console to be satisfactory on Windows 10. - Select "Enable symbolic links" - Leave other extra options as they default (enable file system caching, enable Git credential manager). GIT for Windows adds itself to your path. Create SSH key and upload to GitLab--See https://docs.gitlab.com/ce/ssh/README.html. **Installing QT** Go to QT at and download the Open Source edition of the Windows online installer, qt-unified-windows-x86-3.1.1-online.exe. Run the installer: - Logon with your QT account or create an account if needed. - Click Next to download meta information (this takes a while). - Choose your installation directory (I picked E:\\Qt, but there are no dependencies on where QT is located) - Select components: - In QT 5.12.*x*: - MinGW 7.3.0 32-bit - MinGW 7.3.0 64-bit - Sources - QT Debug Information Files - In Developer and Designer Tools: - QT Creator 4.11.2 CDB Debug - Debugging Tools for Windows - MinGW 7.3.0 32-bit - MinGW 7.3.0 64-bit And complete the installation (this also takes a while). **Getting Started Developing Oscar in QT Creator** In browser, log into your account at gitlab.com. Select the Oscar project at https://gitlab.com/pholy/OSCAR-code. Clone a copy of the repository to a location on your computer. Start QT. There are two QT Oscar project files: OSCAR_QT.pro in the Oscar-code directory, and Oscar.pro in the Oscar-code\\oscar directory. You may use *either* project file. Both will create a running version of Oscar. I find building with Oscar.pro in the Oscar-code\\oscar directory to be very slightly faster, but the difference is negligible. QT it will ask you to select your kits and configure them. Select both MinGW 7.3.0 32-bit and 64-bit kits. Click on Projects in the left panel to show your active project (“oscar”) and **Build & Run** settings. Click on the **Build** line for either 32-bit or 64-bit. In the Build settings in the center panel, select “Release” rather than the default “Debug” in the pull-down at the top of the Build Settings. Click on Details for the qmake build step. By default, “Enable Qt Quick Compiler” is checked. Remove that check – errors result if it is on. QT will ask if you want to recompile everything now. Don’t, as there is more to do before compiling. Make this same change for the Release build for both 32-bit and 64-bit kits. If you want to use the QT Creator Debug tools, select the Build Debug pull-down and disable the QT Quick Compiler there as well. With these changes, you can build, debug, and run Oscar from QT Creator. However, to run Oscar from a Windows shortcut, not in the QT environment, you must create a deployment directory that contains all files required by Oscar. Creating an installer also requires an additional step. A deploy.bat file performs both functions. It creates a release directory and an installer. You can include this deployment file in QT Creator in one of two ways. You can include it as part of QT Creator's build process, or you can do this as a separate deployment step. To include deployment as part of the Release build process, add a custom process step to the configuration. Be sure to select “Release” rather than the default “Debug” in the pull-down at the top of the Build Settings. Create a custom process step as the final build step. Put the fully qualified path for deploy.bat in the command field. Don’t touch “working directory.” Any string you care to place in the “arguments” field will be appended to the installer executable file name. Do the same for both 32-bit and 64-bit Build settings. Now you should be able to build the OSCAR project from the QT Build menu. To make 32-bit or 64-bit builds, just make sure the correct Build item is selected in the Build & Run section on the left. If you prefer to run deploy.bat as a separate deployment step, select Run under the kit name in the Build & Run section. Under Run Settings, select Add Deploy Step. Now create a custom process step just as described earlier. Menu item Build/Deploy will now run this deployment script. **Compiling and building from the command line** If you prefer to build from the command line and not use QT Creator, a batch script buildall.bat will build and create installers for both 32-bit and 64-bit versions of Windows. This script has some hard-coded paths, so will need to be modified for your system configuration. This batch file is not well tested, as I prefer to build from QT Creator. **The Deploy.BAT file** The deployment batch file creates two folders inside the shadow build folder: Release – everything needed to run OSCAR. You can run OSCAR from this directory just by clicking on it. Installer – contains an installer exe file that will install this product. OSCAR-code-v1.3.1/Building/Windows/BuildInstall.iss000066400000000000000000000130111417327530600217620ustar00rootroot00000000000000; Script originally generated by the Inno Script Studio Wizard, then manually modified. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! ; ; See DEPLOY.BAT for documentation on how this is used to build OSCAR installer #define MyGitRevision "unreleased" #define MyReleaseStatus "" #define MyVersionNumbers "0.0.0.0" #include "buildinfo.iss" #define MyAppPublisher "The OSCAR Team" #define MyAppExeName "OSCAR.exe" #define MyAppName "OSCAR" [Setup] SetupLogging=yes ; NOTE: The value of AppId uniquely identifies this application. ; Do not use the same AppId value in installers for other applications. ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) ; Now using separate AppID for Win32 and Win64 and for test builds -- GTS 4/6/2019 #if MyPlatform == "Win64" ArchitecturesAllowed=x64 ArchitecturesInstallIn64BitMode=x64 #if MyReleaseStatus == "r" || MyReleaseStatus == "rc" AppId={{FC6F08E6-69BF-4469-ADE3-78199288D305} #define MyGroupName "OSCAR" #define MyDirName "OSCAR" #else AppId={{C5E23210-4BC5-499D-A0E4-81192748D322} #define MyGroupName "OSCAR (test)" #define MyDirName "OSCAR-test" #endif ; DefaultDirName={%PROGRAMFILES|{pf}}\OSCAR ; above doesn't work. Always returns x86 directory #else // 32-bit #if MyReleaseStatus == "r" || MyReleaseStatus == "rc" AppId={{4F3EB81B-1866-4124-B388-5FB5DA34FFDD} #define MyGroupName "OSCAR 32-bit" #define MyDirName "OSCAR" #else AppId={{B0382AB3-ECB4-4F9D-ABB1-F6EF73D4E3DB} #define MyGroupName "OSCAR 32-bit (test)" #define MyDirName "OSCAR-test" #endif ; DefaultDirName={%PROGRAMFILES(X86)|{pf}}\OSCAR #endif AppName={#MyAppName} AppVersion={#MyAppVersion}-{#MyPlatform}-{#MyGitRevision}{#MySuffix} AppVerName={#MyAppName} {#MyAppVersion}-{#MyPlatform}-{#MyGitRevision}{#MySuffix} AppPublisher={#MyAppPublisher} AppCopyright=Copyright 2019-2020 {#MyAppPublisher} ; **** AppCopyright=Copyright {GetDateTimeString('yyyy', #0, #0)} {%MyAppPublisher} DefaultDirName={pf}\{#MyDirName} DefaultGroupName={#MyGroupName} OutputDir=.\Installer #if MyReleaseStatus == "r" OutputBaseFilename={#MyAppName}-{#MyAppVersion}-{#MyPlatform}{#MySuffix} #else OutputBaseFilename={#MyAppName}-{#MyAppVersion}-{#MyPlatform}-{#MyGitRevision}{#MySuffix} #endif SetupIconFile=setup.ico Compression=lzma SolidCompression=yes VersionInfoCompany={#MyAppPublisher} VersionInfoProductName={#MyAppName} UninstallDisplayName={#MyGroupName} UninstallDisplayIcon={app}\{#MyAppExeName} VersionInfoVersion={#MyVersionNumbers} VersionInfoProductTextVersion={#MyAppVersion} [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" Name: "armenian"; MessagesFile: "compiler:Languages\Armenian.isl" Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" Name: "catalan"; MessagesFile: "compiler:Languages\Catalan.isl" Name: "corsican"; MessagesFile: "compiler:Languages\Corsican.isl" Name: "czech"; MessagesFile: "compiler:Languages\Czech.isl" Name: "danish"; MessagesFile: "compiler:Languages\Danish.isl" Name: "dutch"; MessagesFile: "compiler:Languages\Dutch.isl" Name: "finnish"; MessagesFile: "compiler:Languages\Finnish.isl" Name: "french"; MessagesFile: "compiler:Languages\French.isl" Name: "german"; MessagesFile: "compiler:Languages\German.isl" Name: "hebrew"; MessagesFile: "compiler:Languages\Hebrew.isl" Name: "icelandic"; MessagesFile: "compiler:Languages\Icelandic.isl" Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl" Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" Name: "norwegian"; MessagesFile: "compiler:Languages\Norwegian.isl" Name: "polish"; MessagesFile: "compiler:Languages\Polish.isl" Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl" Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" Name: "slovenian"; MessagesFile: "compiler:Languages\Slovenian.isl" Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl" Name: "ukrainian"; MessagesFile: "compiler:Languages\Ukrainian.isl" [InstallDelete] Type: files; Name: "{app}\Html\release_notes*.html" Type: files; Name: "{app}\Html\credits*.html" Type: files; Name: "{app}\Html\about*.html" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1 [Files] Source: ".\Release\OSCAR.exe"; DestDir: "{app}"; Flags: ignoreversion Source: ".\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] Name: "{group}\{#MyGroupName}"; Filename: "{app}\{#MyAppExeName}" Name: "{group}\{cm:UninstallProgram,{#MyGroupName}}"; Filename: "{uninstallexe}" Name: "{commondesktop}\{#MyGroupName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon ; Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon [Messages] ConfirmUninstall=Are you sure you want to completely remove %1 and all of its components? (Data folder and settings will not be removed.) [Run] Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent OSCAR-code-v1.3.1/Building/Windows/Setup.ico000066400000000000000000000236261417327530600204650ustar00rootroot00000000000000(fh   00h00( ʦ  wzwz xwwxwx"wxzx"ryxzx"w'wzx1wwzzxxxzxyw'xzxyywwxwzwwwwzwwwwxxxwwwwww( @ʦ """)))UUUMMMBBB999|PP3f3333f333ff3fffff3f3f̙f3333f3333333333f3333333f3f33ff3f3f3f3333f3333333f3̙33333f333ff3ffffff3f33f3ff3f3f3ffff3fffffffff3fffffff3f̙ffff3ff333f3ff33fff33f3ff̙3f3f3333f333ff3fffff̙̙3̙f̙̙̙3f̙3f3f3333f333ff3fffff3f3f̙3ffffffffff!___wwwww ##YY#_7wM#_ \# ]    #   wƾw  ( @wwpwpxwxwxxp'x""'p""j""x*"xx*#x袹x蛛xxpxnnnnwnnwwwpwwwwwwwwxwwwwwxpwxxwwwwwwwwwpwp???a( @ʦ """)))UUUMMMBBB999|PP3f3333f333ff3fffff3f3f̙f3333f3333333333f3333333f3f33ff3f3f3f3333f3333333f3̙33333f333ff3ffffff3f33f3ff3f3f3ffff3fffffffff3fffffff3f̙ffff3ff333f3ff33fff33f3ff̙3f3f3333f333ff3fffff̙̙3̙f̙̙̙3f̙3f3f3333f333ff3fffff3f3f̙3ffffffffff!___wwwMwMwwwś澿MY7777Y8_87S___7_`_7`e_7Mee`Y懇b]b]]]ٿ]]]]x]UݾŶ翸徿޽޸Mٿƾٸٿ濿??a(0`xwxwwwwxwwwwx~~wwwwxwwwx~~ww""(xww""""~~~ww"""(ww"""(z~~ww**"(wzxww"hwwwxww**"wwwwwwxww"ww**"xwwwww*xxwwwwﹹw~~xwwww鹹~~~xwwwwwwz~~wwwwwwwzxwwwzwwnwwwwwwwnnnnnnnhwpww戈wwwwwhnnnhwwwwwxxxwpwwxwxxpxwwwxww??08?(0` ʦ """)))UUUMMMBBB999|PP3f3333f333ff3fffff3f3f̙f3333f3333333333f3333333f3f33ff3f3f3f3333f3333333f3̙33333f333ff3ffffff3f33f3ff3f3f3ffff3fffffffff3fffffff3f̙ffff3ff333f3ff33fff33f3ff̙3f3f3333f333ff3fffff̙̙3̙f̙̙̙3f̙3f3f3333f333ff3fffff3f3f̙3ffffffffff!___wwwwwwwwwwwww wwww w 翿w w###澸 ## ##{#渿#####7777_{####77777777####8888777v###____777 ###____87Y ###````87{###````_7v῿ ###````_7v   ###````_7v $#  燇`[  $ 燇]$  燃]]]]V  ]]]]]]V #]]]]]]V # ]]]]]]翸 VV wݶ 濸嶶 渿#嶶 # #ุ#߸#ุ##߸# #ٸ#翸# ߾ # ޾ٸ# ww#w     ## ###w  ????OSCAR-code-v1.3.1/Building/Windows/Use Legacy Graphics.reg000066400000000000000000000003441417327530600230220ustar00rootroot00000000000000Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Software\OSCAR_Team\OSCAR] "GFXEngine"=dword:00000002OSCAR-code-v1.3.1/Building/Windows/buildall.bat000066400000000000000000000030611417327530600211400ustar00rootroot00000000000000setlocal :::@echo off ::: You must set these paths to your QT configuration set qtpath=E:\Qt set qtVersion=5.12.8 ::: ::: Build 32- and 64-bit versions of OSCAR for Windows. ::: Includes code to build BrokenGL (LegacyGFX) versions, but that option is not currently used ::: Uses Timer 4.0 - Command Line Timer - www.Gammadyne.com - to show time it takes to compile. This could be removed. timer /nologo :::call :buildone 32 brokengl call :buildone 64 call :buildone 32 :::call :buildone 64 brokengl timer /s /nologo goto :eof ::: Subroutine to build one version :buildone setlocal timer /nologo set QTDIR=%qtpath%\%qtversion%\mingw73_%1 echo QTDIR is %qtdir% set path=%qtpath%\Tools\mingw730_%1\bin;%qtpath%\%qtversion%\mingw73_%1\bin;%qtpath%\Tools\mingw730_%1\bin;%PATH% set savedir=%cd% : Construct name of our build directory set dirname=build-oscar-win_%1_bit if "%2"=="brokengl" ( set dirname=%dirname%-LegacyGFX set extraparams=DEFINES+=BrokenGL ) echo Build directory is %dirname% set basedir=..\.. if exist %basedir%\%dirname%\nul rmdir /s /q %basedir%\%dirname% mkdir %basedir%\%dirname% cd %basedir%\%dirname% %qtpath%\%qtversion%\mingw73_%1\bin\qmake.exe ..\oscar\OSCAR.pro -spec win32-g++ %extraparams% >qmake.log 2>&1 && %qtpath%/Tools/mingw730_%1/bin/mingw32-make.exe qmake_all >>qmake.log 2>&1 mingw32-make.exe -j8 >make.log 2>&1 || goto :makefail call ..\Building\Windows\deploy.bat timer /s /nologo echo === MAKE %1 %2 SUCCESSFUL === cd %savedir% endlocal exit /b :makefail endlocal timer /s /nologo echo *** MAKE %1 %2 FAILED *** pause exit /bOSCAR-code-v1.3.1/Building/Windows/deploy.bat000066400000000000000000000103241417327530600206440ustar00rootroot00000000000000::: ::: Build Release and Installer subdirectories of the current shadow build directory. ::: The Release directory contains everything needed to run OSCAR. ::: The Installer directory contains a single executable -- the OSCAR installer. ::: ::: DEPLOY.BAT should be run as the last step of a QT Build Release kit. ::: QT Shadow build should be specified (this is the QT default). ::: A command line parameter is optional. Any text on the command line will be appended ::: to the file name of the installer. ::: ::: Requirements: ::: Inno Setup 6 - http://www.jrsoftware.org/isinfo.php, installed to default Program Files (x86) location ::: gawk - somewhere in the PATH or in Git for Windows installed in its default lolcation ::: ::: Deploy.bat resides in .../OSCAR-code/Nuilding/Windows, along with ::: buildinstall.iss -- script for Inno Setup to create installer ::: getBuildInfo.awk -- gawk script for extracting version fields from various files ::: setup.ico -- Icon to be used for the installer. ::: ::: When building a release version in QT, QT will start this batch file which will prepare ::: additional files, run WinDeployQt, and create an installer. ::: @echo off setlocal ::: toolDir is where the Windows install/deploy tools are located set toolDir=%~dp0 :::echo tooldir is %toolDir% ::: Set shadowBuildDir and sourceDir for oscar_qt.pro vs oscar\oscar.pro projects if exist ..\oscar-code\oscar\oscar.pro ( ::: oscar_QT.pro set sourceDir=..\oscar-code\oscar set shadowBuildDir=%cd%\oscar ) else ( ::: oscar\oscar.pro set sourceDir=..\oscar set shadowBuildDir=%cd% ) echo sourceDir is %sourceDir% echo shadowBuildDir is %shadowBuildDir% ::: Now copy the base installation control file copy %toolDir%buildInstall.iss %shadowBuildDir% || exit 45 copy %toolDir%setup.ico %shadowBuildDir% || exit 46 :::copy %toolDir%use*.reg %shadowBuildDir% || exit 47 ::: ::: If gawk.exe is in the PATH, use it. If not, add Git mingw tools (awk) to path. They cannot ::: be added to global path as they break CMD.exe (find etc.) where /q gawk.exe || set PATH=%PATH%;%ProgramW6432%\Git\usr\bin ::: Create and copy installer control files ::: Create file with all version numbers etc. ready for use by buildinstall.iss ::: Extract the version info from various header files using gawk and ::: #define fields for each data item in buildinfo.iss which will be ::: used by the installer script. where /q gawk.exe || set PATH=%PATH%;%ProgramW6432%\Git\usr\bin echo ; This script auto-generated by DEPLOY.BAT >%shadowBuildDir%\buildinfo.iss gawk -f %toolDir%getBuildInfo.awk %sourcedir%\VERSION >>%shadowBuildDir%\buildInfo.iss || exit 60 gawk -f %toolDir%getBuildInfo.awk %sourcedir%\git_info.h >>%shadowBuildDir%\buildInfo.iss || exit 62 echo %shadowBuildDir% | gawk -f %toolDir%getBuildInfo.awk >>%shadowBuildDir%\buildInfo.iss || exit 63 echo #define MySuffix "%1" >>%shadowBuildDir%\buildinfo.iss || exit 64 :::echo #define MySourceDir "%sourcedir%" >>%shadowBuildDir%\buildinfo.iss ::: Create Release directory and subdirectories if exist %shadowBuildDir%\Release\*.* rmdir /s /q %shadowBuildDir%\Release mkdir %shadowBuildDir%\Release cd %shadowBuildDir%\Release copy %shadowBuildDir%\oscar.exe . || exit 71 :::copy %shadowBuildDir%\use*.reg . || exit 72 ::: Now in Release subdirectory ::: If QT created a help directory, copy it. But it might not have if "helpless" option was set if exist Help\*.* rmdir /s /q Help mkdir Help if exist ..\help\*.qch copy ..\help Help if exist Html\*.* rmdir /s /q Html mkdir Html copy ..\html html || exit 80 if exist Translations\*.* rmdir /s /q Translations mkdir Translations copy ..\translations Translations || exit 84 ::: Run deployment tool windeployqt.exe --release --force --compiler-runtime --no-translations --verbose 1 OSCAR.exe || exit 87 ::: Clean up unwanted translation files :::For unknown reasons, Win64 build copies the .ts files into the release directory, while Win32 does not if exist Translations\*.ts del /q Translations\*.ts ::: Create installer cd .. :::"%ProgramFiles(x86)%\Inno Setup 6\compil32" /cc BuildInstall.iss "%ProgramFiles(x86)%\Inno Setup 6\iscc" /Q BuildInstall.iss OSCAR-code-v1.3.1/Building/Windows/getBuildInfo.awk000066400000000000000000000025331417327530600217420ustar00rootroot00000000000000# # awk script to extract build identification from build_number.h, git_info.h, and version.h # for use by Inno Setup in building installation file for OSCAR. See DEPLOY.BAT for information. # # Usage: gawk -f getBuildInfo.awk build_number.h >buildInfo.iss # gawk -f getBuildInfo.awk git_info.h >>buildInfo.iss # gawk -f getBuildInfo.awk version.h >>buildInfo.iss # echo %cd% | gawk -f %sourcedir%getBuildInfo.awk >>buildInfo.iss /#define GIT_BRANCH / { print "#define MyGitBranch", $3 } /#define GIT_REVISION / { print "#define MyGitRevision", $3 } /#define GIT_TAG / { print "#define MyGitTag", $3 } /#define VERSION / { version = $3 print "#define MyAppVersion", version split(version, v, "[.-]") status = v[4] ? v[4] : "r" print "#define MyReleaseStatus \"" status "\"" split("alpha beta gamma rc r", parts, " ") for (i=1; i <= length(parts); i++) dict[parts[i]]=i build = dict[status] print "#define MyBuildNumber \"" (build * 100) "\"" # strip any trailing quote (only happens for a release build) sub("\"","",v[3]) # v[1] already includes a leading quote mark print "#define MyVersionNumbers " v[1] "." v[2] "." v[3] "." (build * 100) "\"" } /32.*bit/ { print "#define MyPlatform \"Win32\"" } /64.*bit/ { print "#define MyPlatform \"Win64\"" } OSCAR-code-v1.3.1/CONTRIBUTING.md000066400000000000000000000307471417327530600157350ustar00rootroot00000000000000## Using Branches with git Our practice is to do all development in branches rather than master. ### Why Use Branches? Setting aside the religious wars that can arise over any development methodology, here are several advantages to doing all development in branches rather than master: - master is always in a consistent state (and passes whatever tests your workflow requires). This means: - Anyone can branch from master at any time to start work on a feature or fix. - When someone is working on a feature or fix, they can update their branch with the current state of master (i.e. merge master into their branch) at any time before submitting a merge request. - Merge conflicts, when they arise, are much easier to manage. For example: - Merge conflicts don't stop everyone in their tracks and block pushing work-in-progress to the repo. Instead, since everyone is working in their own branch, merge conflicts can deferred to a time of each author's choosing. - Merge conflicts are addressed within a branch by the branch author, who will best know how to resolve conflicts. - Merge conflicts are resolved before a branch is merged back into master, - Work-in-progress in its own branch can safely be pushed to the repo for review or testing by others. - Branches let you follow a merge-request workflow that lets you document your changes and enforce your tests. - Branches let you follow the same workflow for both core development and outside contributions. ### How Do I Develop in a Branch? 0. Create your own fork of the repo and configure it to stay up-to-date with the upstream repo. * Go to https://gitlab.com/pholy/OSCAR-code and click on **Fork** in the top right of the project page. * In your fork's sidebar, go to **Settings > Repository** then click on **Expand** for "Mirroring repositories". * Enter "https://gitlab.com/pholy/OSCAR-code.git" for the repository **URL**, make sure the mirror is set to **Pull** and then click **Mirror repository**. 1. Create a branch to work on your feature or bugfix: git clone https://gitlab.com/my-repo/OSCAR-code.git cd OSCAR-code git checkout -b my-branch **Note:** Because OSCAR includes the branch name in its [version string](https://semver.org/spec/v2.0.0.html), **the branch name must contain only alphanumeric characters or "-"** ([0-9a-zA-Z-]). 2. Write your code, committing to your branch as you go, using `git add` and `git commit -a`. 3. Test your code. See the instructions on how to build the project. It should build successfully, without warnings, and your feature should work as intended. 4. Bring your branch up to date with the current master: while you've been working, the "master" branch might have advanced past where it was when you first created your branch from it. Before you try to reincorporate your branch back into the "master" branch, you need to make sure your branch has incorporated any intervening changes in master: git checkout master git pull git checkout my-branch git merge master Use `git stash` and `git stash pop` later if needed to hide some changes. 5. If there are any merge conflicts, resolve them and then build and test again. See below for details on resolving conflicts. 6. Push your branch up to gitlab: ``` git push -u origin my-branch ``` Note that `git push` by itself won't push a branch that doesn't exist upstream, so you need to do this the first time you push your branch. After that (if you need to push additional changes later), you can just use `git push`. 7. Create a merge request describing your proposed change, linking to any issues that it might address, and attaching your branch. * On gitlab, create a new merge request from your branch into the upstream's master branch. The "git push" above will helpfully provide you with a direct URL to start this process. (Make sure you're logged into gitlab or you'll get a 404 error.) Otherwise you can do it manually by going to **Merge Requests > New Merge Request** and selecting your branch; upstream master is the default target. * Fill in the **Title** and **Description** summarizing your proposed change. You don't need to go into exhaustive detail, since all of your commits and their comments will be attached to the request. * Check the **Delete source branch when merge request is accepted** box (optional but recommended). * Do **NOT** check "Squash commits": this will make your local copy think that the branch was never merged. (If you really want to squash commits, you'll need to use "-D" to delete your branch later.) * Click **Submit merge request**. * You may need to repeat steps 2-6 a few times, if changes are needed before your request is accepted, or 4-6 if other changes are merged into master before your request is accepted. 8. An upstream developer will eventually accept the merge request, which merges your branch into master and updates any linked issues. 9. Once the branch has been merged, you can delete your local branch. Assuming you checked the "Delete source branch..." option above (which will only delete the branch on gitlab): get checkout master git pull git fetch --prune git branch -d my-branch If you have commit access to the repo, you *can* theoretically skip steps 7 and 8 and just merge your branch into master directly, but this is discouraged, as it may circumvent automated tests or other workflow requirements. But if you absolutely can't bear to deal with creating a merge request in a particular situation, at least do your development in a branch rather than master! ## Handling Merge Conflicts People dread merge conflicts, but they're not actually all that frequent: most conflicts can be automatically resolved by git. On the other hand, this means that the conflicts that do arise require more attention, and the infrequency can lead to unfamiliarity with the process. The cheat sheet for resolving merge conflicts is: * Address each file with a conflict, then `git add` it. * Check `git status` to see if there are any conflicts remaining. * `git commit` when you've addressed all conflicts. * Build and test the new code. So what does it mean to address a file with a conflict? Most merge conflicts fall into one of two categories: 1. A file was deleted in one branch and modified in the other. 2. Some lines in a file were changed in both branches, and the changes were different. File deletions are easy: If the file should remain, `git add` your copy back into your branch. If the file should be deleted, `git rm` it from your branch. The rest of this discussion will focus on conflicting changes in a file. ### What Just Happened? In the case that files were changed in both branches, most of the fear and confusion comes from the fact that *even a failed merge modifies the files in your working directory,* leaving you with code that won't compile or run. So the first important command to remember is `git merge --abort`. If you have a failed merge, and you just want to revert your attempt to merge, `git merge --abort` will revert your working directory back to your previous commit. (And if you get really stuck, you can `git reset --hard` to revert to your previous commit, but this will also throw away any changes in your working directory.) But eventually you'll need to deal with the merge conflict. So the next step is to understand the state of your working directory after a failed merge: * Files that were able to be merged successfully have been modified and marked ready for commit. * Files with conflicting edits have been merged as much as possible, and conflicts have been marked as described below. * **No change has yet been committed to the repository.** Read that last point again and breathe a sigh of relief. While this default result may seem surprising and scary, it is in fact very practical: it gets you as close to a resolved merge as it can, and provides you with much of the information you need to resolve the remaining conflicts. Once you resolve the conflicts, you mark the resolved files ready for commit, and then you'll be able to commit the merge. ### What Do I Do? git will tell you which files had conflict when `git merge` fails. But you can ask it again with `git status`, which will list "Unmerged paths" that still need resolving. This is especially handy when you have multiple files to resolve, so you can check your progress and see if you've gotten them all. Now let's examine what a merge conflict looks like by default. Here are the contents of a sample file with a merge conflict: preceding merged text <<<<<<< HEAD First file in master, modified by second-branch twice. ======= First file in master, modified by first-branch. >>>>>>> master following merged text Everything outside of a "<<<<<<<" and ">>>>>>>" was successfully merged. (Note that there may be multiple conflicts in a single file, so search for "<<<<" to make sure you've found them all.) Everything above the "=======" is the text in HEAD (your branch). Everything below it belongs to master. So let's resolve the conflict: preceding merged text First file in master, modified by first-branch once and second-branch twice. following merged text **In short, there's nothing magic about the conflict information git inserted into your file, it's just there to show you the two versions that are conflicting. All you have to do is replace the <<<<< through >>>>> lines with the text that should appear there.** Once you've fixed the file, you can use `git diff` to make sure the changes are what you expected. (This will also help you catch any conflicts you missed.) For example: - First file in master, modified by second-branch twice. -First file in master, modified by first-branch. ++First file in master, modified by first-branch once and second-branch twice. Once you're satisfied, you need to mark the file ready to commit, using `git add`. Again, `git status` will tell you if there are any more files with unresolved conflicts. (It also helpfully points you to `git add` to mark a file as resolved.) And once all your conflicts are resolved, you complete the merge with `git commit`. > **NOTE** the one gotcha in this process: if you start `git commit` and change your mind, and simply quit your editor as you normally would to abort a commit, **it will still commit** -- because your commit message isn't empty! (It gets pre-filled with a default merge comment.) While it's annoying to have a commit you didn't intend, the easiest course of action is just to fix anything with another commit. But *if* you can overcome your muscle memory before quitting the editor, you can delete the default merge comment and save it, and then git will abort the commit. ### Getting More Information About a Conflict In the above example, git's default output provided all the information we needed to figure out what the merged text should be. But sometimes we're not sure. Thankfully, git can tell us more. The first command to know is: `git config merge.conflictstyle diff3` This tells git to mark merge conflicts with not just the conflicting text in your branch and in master, but also what that text originally was before your branch or master changed it. So, in this example, you would instead see: preceding merged text <<<<<<< HEAD First file in master, modified by second-branch twice. ||||||| merged common ancestors First file in master. ======= First file in master, modified by first-branch. >>>>>>> master following merged text In complicated conflicts, seeing the original state of the code can be incredibly helpful. Note that this setting needs to be configured before `git merge` tries to merge the conflicting files and inserts the conflict information into the file. But never fear: you can simply set do the `git config` above, `git merge --abort` and then attempt your `git merge` again to see this full context. And you might want to add the `--global` flag to git config so that you always see this on your projects. The second command to know (which isn't as easy to remember) is: `git log --merge -p [filename]` This shows you all the commits and changes to the file that caused it to diverge. It's quite a bit more information than you usually need, but in those few instances you need more this is often just what you wanted to see. And if you need to see *everything*, you can use `git show :1:filename` to see the entirety of the ancestor file, `:2:` to see the entirety of the file in your branch before the merge conflict, and `:3:` to see the entirety of the file as it currently exists in master. OSCAR-code-v1.3.1/COPYING000066400000000000000000001045131417327530600145300ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . OSCAR-code-v1.3.1/Doxyfile000066400000000000000000002166531417327530600152140ustar00rootroot00000000000000# Doxyfile 1.7.4 # # Required to run: # Doxygen (http://www.doxygen.nl/) # GraphViz (https://www.graphviz.org/) # # Usage: # 1) put GraphViz bin directory in your PATH # 2) make oscar-code the current directory # 3) Run doxygen with no parameters. Output will be placed # in doxygen directory. (.gitignore excludes doxygen directory from git.) # # # # # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" "). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = OSCAR # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = 1.1.x # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer # a quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = Open_Source_CPAP_Analysis_Reporter # With the PROJECT_LOGO tag one can specify an logo or icon that is # included in the documentation. The maximum height of the logo should not # exceed 55 pixels and the maximum width should not exceed 200 pixels. # Doxygen will copy the logo to the output directory. #PROJECT_LOGO = ./docs/logo.png PROJECT_LOGO = ./oscar/icons/logo-lm.png # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = ./doxydoc # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = YES # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, # Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English # messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, # Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, # Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful if your file system # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = YES # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this # tag. The format is ext=language, where ext is a file extension, and language # is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, # C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make # doxygen treat .inc files as Fortran files (default is PHP), and .f files as C # (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions # you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. EXTENSION_MAPPING = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also makes the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and # unions are shown inside the group in which they are included (e.g. using # @ingroup) instead of on a separate page (for HTML and Man pages) or # section (for LaTeX and RTF). INLINE_GROUPED_CLASSES = NO # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penalty. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will roughly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols SYMBOL_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespaces are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen # will list include files with double quotes in the documentation # rather than with sharp brackets. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = YES # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen # will sort the (brief and detailed) documentation of class members so that # constructors and destructors are listed first. If set to NO (the default) # the constructors will appear in the respective orders defined by # SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. # This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO # and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to # do proper type resolution of all parameters of a function it will reject a # match between the prototype and the implementation of a member function even # if there is only one candidate or it is obvious which candidate to choose # by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen # will still accept a match between prototype and implementation in such cases. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or macro consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and macros in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. # This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. The create the layout file # that represents doxygen's defaults, run doxygen with the -l option. # You can optionally specify a file name after the option, if omitted # DoxygenLayout.xml will be used as the name of the layout file. LAYOUT_FILE = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # The WARN_NO_PARAMDOC option can be enabled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. #INPUT = ./ ./Graphs ./SleepLib ./SleepLib/loader_plugins INPUT = ./oscar ./oscar/Graphs ./oscar/SleepLib ./oscar/SleepLib/loader_plugins # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh # *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py # *.f90 *.f *.for *.vhd *.vhdl FILE_PATTERNS = # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = */quazip/* */qextserialport/* */fonts/* */docs/* */icons/* *git* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. # If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. # Doxygen will compare the file name with each pattern and apply the # filter if there is a match. # The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty or if # non of the patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) # and it is also possible to disable source filtering for a specific pattern # using *.ext= (so without naming a filter). This option only has effect when # FILTER_SOURCE_FILES is enabled. FILTER_SOURCE_PATTERNS = #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. # Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. Note that when using a custom header you are responsible # for the proper inclusion of any scripts and style sheets that doxygen # needs, which is dependent on the configuration options used. # It is adviced to generate a default header using "doxygen -w html # header.html footer.html stylesheet.css YourConfigFile" and then modify # that header. Note that the header is subject to change so you typically # have to redo this when upgrading to a newer version of doxygen or when changing the value of configuration settings such as GENERATE_TREEVIEW! HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # If the HTML_TIMESTAMP tag is set to YES then the generated HTML # documentation will contain the timesstamp. HTML_TIMESTAMP = NO # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that # the files will be copied as-is; there are no commands or markers available. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. # Doxygen will adjust the colors in the stylesheet and background images # according to this color. Hue is specified as an angle on a colorwheel, # see http://en.wikipedia.org/wiki/Hue for more information. # For instance the value 0 represents red, 60 is yellow, 120 is green, # 180 is cyan, 240 is blue, 300 purple, and 360 is red again. # The allowed range is 0 to 359. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of # the colors in the HTML output. For a value of 0 the output will use # grayscales only. A value of 255 will produce the most vivid colors. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to # the luminance component of the colors in the HTML output. Values below # 100 gradually make the output lighter, whereas values above 100 make # the output darker. The value divided by 100 is the actual gamma applied, # so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, # and 100 does not change the gamma. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. HTML_TIMESTAMP = YES # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. DOCSET_PUBLISHER_NAME = The OSCAR Team # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated # that can be used as input for Qt's qhelpgenerator to generate a # Qt Compressed Help (.qch) of the generated HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to # add. For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see # # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's # filter section matches. # # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files # will be generated, which together with the HTML files, form an Eclipse help # plugin. To install this plugin and make it available under the help contents # menu in Eclipse, the contents of the directory containing the HTML and XML # files needs to be copied into the plugins directory of eclipse. The name of # the directory within the plugins directory should be the same as # the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before # the help appears. GENERATE_ECLIPSEHELP = NO # A unique identifier for the eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have # this name. ECLIPSE_DOC_ID = org.doxygen.Project # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values # (range [0,1..20]) that doxygen will group on one line in the generated HTML # documentation. Note that a value of 0 will completely suppress the enum # values from appearing in the overview section. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to YES, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). # Windows users are probably better off using the HTML help feature. GENERATE_TREEVIEW = YES # By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, # and Class Hierarchy pages using a tree view instead of an ordered list. USE_INLINE_TREES = YES # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open # links to external symbols imported via tag files in a separate window. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are # not supported properly for IE 6.0, but are supported on all modern browsers. # Note that when changing this option you need to delete any form_*.png files # in the HTML output before the changes have effect. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax # (see http://www.mathjax.org) which uses client side Javascript for the # rendering instead of using prerendered bitmaps. Use this if you do not # have LaTeX installed or if you want to formulas look prettier in the HTML # output. When enabled you also need to install MathJax separately and # configure the path to it using the MATHJAX_RELPATH option. USE_MATHJAX = NO # When MathJax is enabled you need to specify the location relative to the # HTML output directory using the MATHJAX_RELPATH option. The destination # directory should contain the MathJax.js script. For instance, if the mathjax # directory is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the # mathjax.org site, so you can quickly see the result without installing # MathJax, but it is strongly recommended to install a local copy of MathJax # before deployment. MATHJAX_RELPATH = http://www.mathjax.org/mathjax # When the SEARCHENGINE tag is enabled doxygen will generate a search box # for the HTML output. The underlying search engine uses javascript # and DHTML and should work on any modern browser. Note that when using # HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets # (GENERATE_DOCSET) there is already a search function so this one should # typically be disabled. For large projects the javascript based search engine # can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a PHP enabled web server instead of at the web client # using Javascript. Doxygen will generate the search PHP script and index # file to put on the web server. The advantage of the server # based approach is that it scales better to large projects and allows # full text search. The disadvantages are that it is more difficult to setup # and does not have live searching capabilities. SERVER_BASED_SEARCH = NO #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. # Note that when enabling USE_PDFLATEX this option is only used for # generating bitmaps for formulas in the HTML output, but not in the # Makefile that is written to the output directory. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4 # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for # the generated latex document. The footer should contain everything after # the last chapter. If it is left blank doxygen will generate a # standard footer. Notice: only use this tag if you know what you are doing! LATEX_FOOTER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # If LATEX_SOURCE_CODE is set to YES then doxygen will include # source code with syntax highlighting in the LaTeX output. # Note that which sources are shown also depends on other settings # such as SOURCE_BROWSER. LATEX_SOURCE_CODE = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. # This is useful # if you want to understand what is going on. # On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # pointed to by INCLUDE_PATH will be searched when a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition that # overrules the definition found in the source code. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all references to function-like macros # that are alone on a line, have an all uppercase name, and do not end with a # semicolon, because these will confuse the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option also works with HAVE_DOT disabled, but it is recommended to # install and use dot, since it yields more powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is # allowed to run in parallel. When set to 0 (the default) doxygen will # base this on the number of processors available in the system. You can set it # explicitly to a value larger than 0 to get control over the balance # between CPU load and processing speed. DOT_NUM_THREADS = 0 # By default doxygen will write a font called Helvetica to the output # directory and reference it in all dot files that doxygen generates. # When you want a differently looking font you can specify the font name # using DOT_FONTNAME. You need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = Helvetica # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = YES # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = YES # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will generate a graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are svg, png, jpg, or gif. # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the # \mscfile command). MSCFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = YES # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES OSCAR-code-v1.3.1/Htmldocs/000077500000000000000000000000001417327530600152465ustar00rootroot00000000000000OSCAR-code-v1.3.1/Htmldocs/about-af.html000066400000000000000000000057351417327530600176440ustar00rootroot00000000000000 about_af

Welkom by OSCAR,
die Open Source CPAP Analise Verslaggewer


Hierdie sagteware is ontwerp om u te help om die data wat deur u CPAP en verwante toerusting geproduseer word, te hersien.

Vrywilligers het dit in baie tale vertaal. Klik op die Help-oortjie om die vertoonde taal te verander.

Enige Chromium-gebaseerde blaaier soos Google Chrome of Microsoft Edge Chromium bied tans die beste outomatiese vertaalopsies .
U vind uitgebreide hulp in die OSCAR Wiki. Maak http://www.apneaboard.com/wiki/index.php/OSCAR_Help oop.

Besoek http://www.apneaboard.com of 'n plaaslike apnee-forum.

LEES ASSEBLIEF SORGVULDIG


OSCAR is NIE 'n plaasvervanger vir bekwame mediese voorligting van u dokter nie.

Weens die gebrek aan dokumentasie wat vervaardigers oor lêerformate vrygestel het, kan die akkuraatheid van die data wat in OSCAR vertoon word, geensins gewaarborg word nie.

Alle verslae wat gegenereer word, is slegs vir PERSOONLIKE GEBRUIK en is bedoel om so akkuraat as moontlik te wees.

OSCAR-verslae is gebaseer op data wat deur die CPAP-masjien gerapporteer is. Die goedkeuring van hierdie data vir voldoening of vir ander doeleindes is onderhewig aan die goedkeuringsbevoegdheid van die beoordelingsagentskap.

Die gebruik van hierdie sagteware is op u eie risiko. Die skrywers sal nie aanspreeklik gehou word vir enigiets wat verband hou met die gebruik of misbruik van hierdie sagteware nie.

OSCAR is gratis (soos in kostelose) sagteware wat vrygestel word onder die GNU Public License v3, en kom sonder enige waarborg, en sonder ENIGE aansprake op geskiktheid vir enige doel.

OSCAR is © 2019-2022 Die OSCAR-span: 'n groep vrywillige ontwikkelaars van die Apnea-gemeenskap en lede van verskeie forums en verskillende nasionaliteite..

OSCAR is 'n afgeleide van die SleepyHead-program wat outeursreg het © 2011-2018 Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-ar.html000066400000000000000000000066621417327530600176600ustar00rootroot00000000000000 about_ar

مرحبًا بكم في OSCAR ،
مراسل تحليل CPAP مفتوح المصدر


تم تصميم هذا البرنامج لمساعدتك في مراجعة البيانات التي تنتجها CPAP والمعدات ذات الصلة.

ترجمها المتطوعون إلى العديد من اللغات. انقر فوق علامة التبويب "تعليمات" لتغيير اللغة المعروضة.

يوفر أي متصفح قائم على Chromium مثل Google Chrome أو Microsoft Edge Chromium حاليًا أفضل خيارات الترجمة التلقائية.
ستجد مساعدة واسعة في ويكي OSCAR. افتح http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

للأسئلة ، قم بزيارة http://www.apneaboard.com أو منتدى انقطاع النفس المحلي.

يرجى قراءة بعناية



OSCAR
ليس بديلاً عن التوجيه الطبي المختص من طبيبك.

نظرًا لعدم وجود وثائق صادرة عن الشركات المصنعة بشأن تنسيقات الملفات ، لا يمكن ضمان دقة البيانات المعروضة في OSCAR بأي شكل من الأشكال.

جميع التقارير التي تم إنشاؤها للاستخدام الشخصي فقط ومخصصة لتكون دقيقة قدر الإمكان.

تستند تقارير OSCAR إلى البيانات التي تم الإبلاغ عنها بواسطة جهاز CPAP. يخضع قبول هذه البيانات للامتثال أو لأغراض أخرى لتقدير موافقة وكالة المراجعة.

استخدام هذا البرنامج على مسؤوليتك الخاصة. لن يكون المؤلفون مسؤولين عن أي شيء يتعلق باستخدام أو إساءة استخدام هذا البرنامج.

OSCAR هو برنامج مجاني (كما هو الحال في الحرية) ، تم إصداره بموجب ترخيص GNU Public v3 ، ويأتي بدون أي ضمان ، ودون أي مطالبات باللياقة لأي غرض.

OSCAR هو © 2012-2020 فريق OSCAR: مجموعة من المطورين المتطوعين من مجتمع Apnea وأعضاء المنتديات المتعددة والجنسيات المختلفة..

OSCAR هو أحد مشتقات برنامج SleepyHead الذي يتمتع بحقوق الطبع والنشر © 2011-2018 Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-bg.html000066400000000000000000000077371417327530600176520ustar00rootroot00000000000000 about_bg

Добре дошли в OSCAR,
отворения източник за анализ на CPAP анализ


Този софтуер е създаден да ви помогне да прегледате данните, получени от вашия CPAP и свързаното с него оборудване.

Доброволците го превеждаха на много езици. Щракнете върху раздела Помощ, за да промените показания език.

Всеки браузър, базиран на Chromium, като Google Chrome или Microsoft Edge Chromium , в момента предлага най-добрите опции за автоматичен превод .
Ще намерите обширна помощ в OSCAR Wiki. Отворете http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

За въпроси посетете http://www.apneaboard.com или местен форум за апнея.

МОЛЯ, ЧЕТЕТЕ ВНИМАНИЕ


OSCAR НЕ замества компетентните медицински указания от Вашия лекар.

Поради липсата на документация, издадена от производителите по отношение на формати на файлове, точността на данните, показани в OSCAR, по никакъв начин не може да бъде гарантирана.

Всички генерирани отчети са САМО ЗА ЛИЧНО УПОТРЕБА и са предназначени да бъдат възможно най-точни.

Отчетите за OSCAR се основават на данни, докладвани от CPAP машината. Приемането на тези данни за съответствие или други цели подлежи на преценка на одобряващата агенция за преразглеждане.

Използването на този софтуер е изцяло на ваш собствен риск. Авторите няма да носят отговорност за нищо, свързано с използването или злоупотребата с този софтуер.

OSCAR е безплатен (както на свобода) софтуер, пуснат под GNU Public License v3 и се предлага без гаранция и без никакви претенции за годност за всякакви цели.

OSCAR е © 2019-2022 Екипът на OSCAR: група доброволчески разработчици от общността на Apnea и членове на множество форуми и различни националности.

OSCAR е производно на програмата SleepyHead, която е с авторско право © 2011-2018 Марк Уоткинс.
OSCAR-code-v1.3.1/Htmldocs/about-da.html000066400000000000000000000055221417327530600176340ustar00rootroot00000000000000 about-da

Velkommen til OSCAR,
Open Source CPAP Analyse Reporter


Denne software er designet til at hjælpe dig med at gennemgå de data, der er produceret af dit CPAP og relateret udstyr.

Frivillige oversatte det til mange sprog. Klik på fanen Hjælp for at ændre det viste sprog.

Enhver Chromium-baseret browser som Google Chrome eller Microsoft Edge Chromium tilbyder i øjeblikket de bedste automatiske oversættelsesindstillinger .
Du finder omfattende hjælp i OSCAR Wiki. Åbn http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

For spørgsmål kan du besøge http://www.apneaboard.com eller et lokalt apnøforum.


LÆS VENLIGST



OSCAR er IKKE en erstatning for kompetent medicinsk vejledning fra din læge.

På grund af den manglende dokumentation, der er frigivet af producenterne om filformater, kan nøjagtigheden af ​​data, der vises i OSCAR, på ingen måde garanteres.

Alle genererede rapporter er kun til PERSONLIG BRUG og er beregnet til at være så nøjagtige som muligt.

OSCAR-rapporter er baseret på data rapporteret af CPAP-maskinen. Accept af disse data med henblik på overholdelse eller andre formål er underlagt godkendelsesbeføjelsen fra det vurderingsorgan.

Brug af denne software er helt på din egen risiko. Forfatterne hæfter ikke for noget, der er relateret til brug eller misbrug af denne software.

OSCAR er gratis (som i frihed) software, frigivet under GNU Public License v3, og leveres uden garanti og uden NOEN krav på egnethed til noget formål.

OSCAR er © 2019-2022 OSCAR-teamet: en gruppe frivillige udviklere fra Apnea Community og medlemmer af flere fora og forskellige nationaliteter.

OSCAR er et derivat af SleepyHead-programmet, der er copyright © 2011-2018 Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-de.html000066400000000000000000000062161417327530600176410ustar00rootroot00000000000000 about_de

Willkommen bei OSCAR,
der Open Source CPAP Analysis Reporter


Diese Software wurde entwickelt, um Sie bei der Überprüfung der von Ihrem CPAP und verwandten Geräten erzeugten Daten zu unterstützen.

Freiwillige Mitarbeiter haben sie in viele Sprachen übersetzt. Um Übersetzungsfehler oder Vorschläge zu melden, senden Sie bitte eine E-Mail an Steffen Reitz ( steffen.reitz@t-online.de), der die deutsche Übersetzung erstellt hat.

Jeder Chromium-basierte Browser wie Google Chrome oder Microsoft Edge Chromium bietet derzeit die besten Optionen für die automatische Übersetzung.
Umfangreiche Hilfe finden Sie im OSCAR-Wiki. Öffnen Sie http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

Bei Fragen besuchen Sie http://www.apneaboard.com oder ein lokales Apnoe-Forum.

BITTE AUFMERKSAM LESEN


OSCAR ist KEIN Ersatz für eine kompetente medizinische Beratung durch Ihren Arzt.

Aufgrund der fehlenden Dokumentation der Hersteller zu Dateiformaten kann die Richtigkeit der in OSCAR angezeigten Daten in keiner Weise garantiert werden.

Alle erstellten Berichte sind NUR FÜR DEN PERSÖNLICHEN GEBRAUCH bestimmt und sollen so genau wie möglich sein.

OSCAR-Berichte basieren auf Daten, die vom CPAP-Gerät gemeldet wurden. Die Annahme dieser Daten zur Einhaltung der Vorschriften oder anderen Zwecken liegt im Ermessen der prüfenden Behörde.

Die Verwendung dieser Software erfolgt auf eigenes Risiko. Die Autoren haften nicht für irgendetwas im Zusammenhang mit der Verwendung oder dem Missbrauch dieser Software.

OSCAR ist freie (wie in Freiheit) Software, die unter der GNU Public License v3 veröffentlicht wurde und ohne Garantie und ohne jeglichen Anspruch auf Eignung für irgendeinen Zweck geliefert wird.

OSCAR ist © 2019-2022 Das OSCAR-Team: eine Gruppe freiwilliger Entwickler aus der Apnoe-Community und Mitglieder mehrerer Foren und verschiedener Nationalitäten.

OSCAR ist eine Ableitung des SleepyHead-Programms, das dem Copyright © 2011-2018 Mark Watkins unterliegt.
OSCAR-code-v1.3.1/Htmldocs/about-el.html000066400000000000000000000106321417327530600176460ustar00rootroot00000000000000 about-el

Καλώς ήρθατε στο OSCAR,
τον Αναλυτή ανάλυσης CPAP ανοιχτού κώδικα


Αυτό το λογισμικό έχει σχεδιαστεί για να σας βοηθήσει στην αναθεώρηση των δεδομένων που παράγονται από τις συσκευές CPAP και τον σχετικό εξοπλισμό τους.

Οι εθελοντές το μεταφράζουν σε πολλές γλώσσες. Κάντε κλικ στην καρτέλα Βοήθεια για να αλλάξετε την εμφανιζόμενη γλώσσα.

Κάθε πρόγραμμα περιήγησης που βασίζεται στο Chromium, όπως το Google Chrome ή το Microsoft Edge Chromium , προσφέρει τις καλύτερες επιλογές αυτόματης μετάφρασης .
Θα βρείτε εκτεταμένη βοήθεια στο Wiki του OSCAR. Ανοίξτε http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

Για ερωτήσεις επισκεφθείτε το http://www.apneaboard.com ή ένα τοπικό φόρουμ άπνοιας.

ΠΑΡΑΚΑΛΩ ΔΙΑΒΑΣΤΕ ΠΡΟΣΕΚΤΙΚΑ


Το OSCAR ΔΕΝ είναι υποκατάστατο της αρμόδιας ιατρικής καθοδήγησης από τον γιατρό σας.

Λόγω της έλλειψης τεκμηρίωσης που απελευθερώνεται από τους κατασκευαστές σχετικά με τις μορφές αρχείων, η ακρίβεια των δεδομένων που εμφανίζονται στο OSCAR δεν μπορεί με κανένα τρόπο να διασφαλιστεί.

Όλες οι αναφορές που παράγονται είναι ΜΟΝΟ για ΠΡΟΣΩΠΙΚΗ ΧΡΗΣΗ και προορίζονται να είναι όσο το δυνατόν ακριβείς.

Οι αναφορές OSCAR βασίζονται σε δεδομένα που αναφέρθηκαν από τη μηχανή CPAP. Η αποδοχή αυτών των δεδομένων για λόγους συμμόρφωσης ή για άλλους σκοπούς εξαρτάται από τη διακριτική ευχέρεια έγκρισης του οργανισμού ελέγχου.
Η χρήση αυτού του λογισμικού είναι εξ ολοκλήρου με δική σας ευθύνη. Οι συγγραφείς δεν θα θεωρηθούν υπεύθυνοι για οτιδήποτε σχετίζεται με τη χρήση ή την κακή χρήση αυτού του λογισμικού.

Το OSCAR είναι δωρεάν λογισμικό που κυκλοφορεί υπό τη GNU Public License v3 και δεν συνοδεύεται από καμία εγγύηση και χωρίς αξιώσεις για καταλληλότητα για οποιοδήποτε σκοπό.

OSCAR είναι © 2019-2022 Η Ομάδα OSCAR: μια ομάδα εθελοντών προγραμματιστών από την Κοινότητα Apnea και μέλη πολλών φόρουμ και διαφόρων εθνικοτήτων.
Το OSCAR είναι ένα παράγωγο του προγράμματος SleepyHead που προστατεύεται από πνευματικά δικαιώματα © 2011-2018 Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-es.html000066400000000000000000000061671417327530600176650ustar00rootroot00000000000000 about_es

Bienvenidos a OSCAR,
el reportero de análisis de CPAP de código abierto

Este software ha sido diseñado para ayudarle a revisar los datos producidos por sus dispositivos CPAP y equipos relacionados.

Voluntarios lo tradujeron a muchos idiomas. Para reportar cualquier error de traducción y/o sugerencia, siéntase libre de enviar un correo electrónico a Perchas: anuncio.venta@yahoo.es, quien hizo la traducción. Haga clic en la pestaña de Ayuda para cambiar el idioma mostrado.

Cualquier navegador basado en Chromium como Google Chrome o Microsoft Edge Chromium actualmente ofrece las mejores opciones de traducción automática .
Encontrará una amplia ayuda en la Wiki de OSCAR. Abra http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

Si tiene preguntas, visite http://www.apneaboard.com o un foro local sobre apnea.

POR FAVOR LEA DETENIDAMENTE


OSCAR no es un sustituto de la orientación médica competente de su médico.

Debido a la falta de documentación publicada por los fabricantes en relación con los formatos de archivo, la precisión de los datos mostrados en OSCAR no puede garantizarse de ninguna manera.

Todos los informes generados son para USO PERSONAL ÚNICAMENTE, y están pensados para ser lo más precisos posible.

Los informes del OSCAR se basan en los datos reportados por la máquina CPAP. La aceptación de estos datos para el cumplimiento u otros propósitos está sujeta a la discreción de aprobación de la agencia revisora.

El uso de este software es totalmente bajo su propio riesgo. Los autores no serán responsables de nada relacionado con el uso o el mal uso de este software.

OSCAR es un software libre (como en la libertad), publicado bajo la Licencia Pública GNU v3, y viene sin ninguna garantía, y sin NINGÚN reclamo de aptitud para ningún propósito.

OSCAR es © 2019-2022 El Equipo OSCAR: un grupo de desarrolladores voluntarios de la Comunidad Apnea y miembros de múltiples foros y diversas nacionalidades.

OSCAR es un derivado del programa SleepyHead que tiene el copyright ©2011-2018 de Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-fi.html000066400000000000000000000054641417327530600176530ustar00rootroot00000000000000 about-fi

Tervetuloa OSCAR-ohjelmaan,
avoimen lähdekoodin CPAP analyysin raportoijaan

Tämä ohjelma on suunniteltu avustamaan käyttämäsi CPAP-laitteen ja siihen liittyvien laitteiden näyttämiä tietoja.

Vapaaehtoiset käänsivät sen monille kielille. Napsauta Apua -välilehteä muuttaaksesi näytettävää kieltä.

Kaikki Chromium-pohjaiset selaimet, kuten Google Chrome tai Microsoft Edge Chromium , tarjoavat parhaita automaattisia käännösvaihtoehtoja .
Löydät laajan avun OSCARin Wikistä. Avaa http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

Kysymyksiä voit esittää http://www.apneaboard.com tai paikallisella uniapnea-foorumilla.

OLE HYVÄ JA LUE HUOLELLISESTI


OSCAR EI KORVAA lääkärin pätevää lääketieteellistä ohjausta.

Laitevalmistajien puutteellisen dokumentaation takia näytetettyjä tietoja Oscarissa ei taata millään tavalla.

Kaikki luodut raportit ovat VAIN HENKILÖKOHTAISTA KÄYTTÖÄ VARTEN. Niiden tarkoitus on olla mahdollisimman tarkkoja.

OSCARin raportit perustuvat CPAP-laitteen antamiin tietoihin. Näiden tietojen hyväksyminen vaatimustenmukaisuutta tai muita tarkoituksia varten edellyttää tarkastavan viranomaisen hyväksyntää.

Tämän ohjelman käyttö on täysin omalla vastuullasi. Ohjelman omistajat ja tekijät eivät ole vastuussa mistään, joka liittyy tämän ohjelman käyttöön tai väärinkäyttöön.

OSCAR on vapaa ohjelmisto, julkaistu GNU Public Lisenssin v3 alaisena. Ohjelma tulee ilman takuuta ja ilman MITÄÄN takeita tai vaatimuksia kunnosta.

OSCAR on © 2019-2022 OSCAR-tiimi: ryhmä Apnea-yhteisön vapaaehtoiskehittäjiä ja useiden foorumien ja eri kansallisuuksien jäseniä.

OSCAR on johdannainen SleepyHead-ohjelmasta, jonka tekijänoikeudet ovat ©2011-2018 Mark Watkinsilla.

OSCAR-code-v1.3.1/Htmldocs/about-fr.html000066400000000000000000000060701417327530600176560ustar00rootroot00000000000000 about_fr

Bienvenue dans OSCAR,
l’outil d'analyse PPC Open Source

Ce logiciel a été conçu pour vous aider à examiner les données produites par votre appareil à PPC et les équipements connexes.

Des bénévoles l'ont traduit en plusieurs langues. Cliquez sur l'onglet Aide pour changer la langue affichée.

Tout navigateur basé sur Chromium comme Google Chrome ou Microsoft Edge Chromium offre actuellement les meilleures options de traduction automatique .
Vous trouverez une aide complète dans le Wiki OSCAR. Ouvrez http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

Pour toutes questions, visitez http://www.apneaboard.com ou un forum local sur l'apnée du sommeil.

À LIRE ATTENTIVEMENT S'IL VOUS PLAÎT


OSCAR ne remplace pas les conseils médicaux compétents de votre médecin.

En raison du manque de documentation publiée par les fabricants concernant les formats de fichiers, l'exactitude des données affichées dans OSCAR ne peut en aucun cas être garantie.

Tous les rapports générés sont destinés à un USAGE PERSONNEL UNIQUEMENT et essayent d'être aussi précis que possible.

Les rapports OSCAR sont basés sur les données rapportées par la machine à PPC. L'acceptation de ces données à des fins de conformité ou à d'autres fins est soumise à la discrétion d'approbation des cabinet médicaux d'examen.

L'utilisation de ce logiciel est entièrement à vos risques et périls. Les auteurs ne seront pas tenus responsables de tout ce qui concerne l'utilisation ou la mauvaise utilisation de ce logiciel.

OSCAR est un logiciel libre, publié sous la licence publique GNU v3, et est livré sans garantie, et sans aucune prétention de répondre à quelque fin que ce soit.

OSCAR est © 2019-2022 L'équipe OSCAR se compose d’un groupe de développeurs bénévoles de la communauté de l'apnée du sommeil, des membres de plusieurs forums et de différentes nationalités.

OSCAR est un dérivé du programme SleepyHead qui est protégé par copyright © 2011-2018 Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-he.html000066400000000000000000000063001417327530600176370ustar00rootroot00000000000000 about-he

ברוך הבא ל- OSCAR,
כתב CPAP ניתוח קוד פתוח

תוכנה זו תוכננה לסייע לך בבדיקת הנתונים המיוצרים על ידי מכשירי CPAP שלך וציוד נלווה.

מתנדבים תרגמו את זה לשפות רבות. לחץ על לשונית העזרה כדי לשנות את השפה המוצגת.

כל דפדפן מבוסס כרום כמו Google Chrome או Microsoft Edge Chromium מציע כרגע את אפשרויות התרגום האוטומטיות הטובות ביותר .

תוכלו למצוא עזרה רחבה בוויקי OSCAR. פתח את http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

לשאלות בקר בכתובת http://www.apneaboard.com או בפורום דום נשימה מקומי.

אנא קרא בעיון


OSCAR אינו מהווה תחליף לליווי רפואי מוסמך מהרופא שלך.

בשל היעדר תיעוד שפרסמו היצרנים בנוגע לפורמטים של קבצים, לא ניתן להבטיח את דיוק הנתונים המוצגים ב- OSCAR בשום דרך.

כל הדוחות שנוצרו מיועדים לשימוש אישי בלבד ונועדו להיות מדויקים ככל האפשר.

דוחות OSCAR מבוססים על נתונים המדווחים על ידי מכונת CPAP. קבלת נתונים אלה לציות או למטרות אחרות כפופה לשיקול דעתה של הסוכנות הבודקת.
השימוש בתוכנה זו הנו על אחריותך בלבד. המחברים לא יהיו אחראים לכל דבר הקשור לשימוש או שימוש לרעה בתוכנה זו.

OSCAR היא תוכנה חינמית (כמו בחופש), המשוחררת תחת GNU Public License v3, ואינה מגיעה ללא אחריות וללא כל טענה לכשירות לכל מטרה שהיא.

OSCAR הוא © 2019-2020 צוות OSCAR: קבוצה של מפתחים מתנדבים מקהילת Apnea וחברים בפורומים מרובים ובלאומים שונים.

OSCAR היא נגזרת של תוכנית SleepyHead שזכויות יוצרים © 2011-2018, מארק ווטקינס.
OSCAR-code-v1.3.1/Htmldocs/about-hu.html000066400000000000000000000060661417327530600176700ustar00rootroot00000000000000 about-hu

Üdvözöljük az OSCAR-ban,
a nyílt forrású CPAP elemző riporter

Ezt a szoftvert úgy fejlesztették ki, hogy segítsen a CPAP-eszközök és a kapcsolódó berendezések által előállított adatok áttekintésében.

Az önkéntesek sok nyelvre fordították. Kattintson a Súgó fülre a megjelenített nyelv megváltoztatásához.

Bármely Chromium alapú böngésző, például a Google Chrome vagy a Microsoft Edge Chromium jelenleg a legjobb automatikus fordítási lehetőségeket kínálja.
Kiterjedt segítséget talál az OSCAR Wikiben. Nyissa meg a http://www.apneaboard.com/wiki/index.php/OSCAR_Help webhelyet.

Ha kérdése van, látogasson el a http://www.apneaboard.com webhelyre vagy egy helyi apnoe fórumra.

KÉRJÜK, OLVASSA EL FIGYELMESEN


Az OSCAR NEM helyettesíti orvosának az illetékes orvosi útmutatásait.

A gyártók által a fájlformátumokkal kapcsolatban kiadott dokumentáció hiánya miatt az OSCAR-ban megjelenített adatok pontosságát semmilyen módon nem garantálhatjuk.

Az összes generált jelentés CSAK SZEMÉLYES HASZNÁLATRA van, és a lehető legpontosabbnak tűnik.

Az OSCAR jelentések a CPAP gép által szolgáltatott adatokon alapulnak. Ezen adatok elfogadása megfelelőség vagy más célok érdekében a felülvizsgálatot végző ügynökség jóváhagyási jogkörébe tartozik.
A szoftver használata kizárólag a saját felelősségére történik. A szerzők nem vállalnak felelősséget a szoftver használatával vagy visszaélésszerű használatáért.

Az OSCAR egy ingyenes (a szabadsághoz hasonlóan) szoftver, amelyet a GNU Public License v3 alatt adtak ki, garancia nélkül és bármilyen célra való alkalmasságra vonatkozóan.

Az OSCAR © 2019-2022 Az OSCAR csapata: az Apnea közösség önkéntes fejlesztőinek csoportja, valamint több fórum és különböző nemzetiségű tagok.

Az OSCAR a SleepyHead program származékos terméke, amelynek szerzői jogi védelem alatt áll. © 2011-2018, Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-it.html000066400000000000000000000057341417327530600176710ustar00rootroot00000000000000 about-it

Benvenuto in OSCAR,
il reporter di analisi CPAP open source


Questo software è stato progettato per assistere l'utente nella revisione dei dati prodotti dal CPAP e dalle apparecchiature correlate.

I volontari lo hanno tradotto in molte lingue. Fare clic sulla scheda Guida per cambiare la lingua visualizzata.

Qualsiasi browser basato su Chromium come Google Chrome o Microsoft Edge Chromium offre attualmente le migliori opzioni di traduzione automatica .
Troverai un ampio aiuto nel Wiki OSCAR. Apri http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

Per domande visitare http://www.apneaboard.com o un forum di apnea locale.

SI PREGA DI LEGGERE CON ATTENZIONE


OSCAR NON sostituisce la consulenza medica competente del proprio medico.

A causa della mancanza di documentazione rilasciata dai produttori in merito ai formati di file, l'accuratezza dei dati visualizzati in OSCAR non può in alcun modo essere garantita.

Tutti i report generati sono SOLO PER USO PERSONALE e devono essere il più precisi possibile.

I report OSCAR si basano sui dati segnalati dalla macchina CPAP. L'accettazione di questi dati per conformità o altri scopi è soggetta alla discrezione di approvazione dell'agenzia di revisione.

L'uso di questo software è interamente a tuo rischio. Gli autori non saranno ritenuti responsabili per qualsiasi cosa relativa all'uso o all'uso improprio di questo software.

OSCAR è un software gratuito (come in libertà), rilasciato sotto licenza GNU Public License v3 e non è coperto da alcuna garanzia e senza QUALSIASI pretesa di idoneità per qualsiasi scopo.

OSCAR è © 2019-2022 Il team OSCAR: un gruppo di sviluppatori volontari della comunità Apnea e membri di più forum e nazionalità diverse.

OSCAR è un derivato del programma SleepyHead che è protetto da copyright © 2011-2018 Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-nl.html000066400000000000000000000067701417327530600176670ustar00rootroot00000000000000 about-nl

Welkom bij OSCAR,
dè Open Source CPAP-Analyse Rapporteur

Deze software is ontworpen om u te helpen bij het beoordelen van de gegevens
die zijn geproduceerd door uw CPAP en aanverwante apparatuur.

Vrijwilligers vertaalden het in vele talen. De Nederlandse versie is door The OSCAR Team gemaakt.
Stuur een e-mail naar arie.klerk@gmail.com voor vragen of opmerkingen over deze vertaling.

Klik op het tabblad Help om de weergegeven taal te wijzigen.

Elke op Chromium gebaseerde browser zoals Google Chrome of Microsoft Edge Chromium biedt momenteel de beste automatische vertaalopties .

U vindt uitgebreide hulp in de OSCAR Wiki. Open http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

Ga voor vragen naar de Amerikaanse ApneaBoard of de Nederlandse sites:
Het Apneuforum of de FaceBook-groep van de ApneuVereniging.

LEES DIT ZORGVULDIG


OSCAR is GEEN vervanging voor bekwame medische begeleiding door uw arts.

Vanwege het gebrek aan documentatie die door fabrikanten is vrijgegeven met betrekking tot bestandsindelingen, kan de nauwkeurigheid van de in OSCAR weergegeven gegevens op geen enkele manier worden gegarandeerd.

Alle gegenereerde rapporten zijn UITSLUITEND voor PERSOONLIJK GEBRUIK en zijn bedoeld om zo nauwkeurig mogelijk te zijn.

OSCAR-rapporten zijn gebaseerd op gegevens die zijn gerapporteerd door de CPAP-machine. Acceptatie van deze gegevens voor naleving of andere doeleinden is onderworpen aan de goedkeuring van het beoordelingsbureau.

Het gebruik van deze software is geheel op eigen risico. De auteurs kunnen niet aansprakelijk worden gehouden voor iets dat verband houdt met het gebruik of misbruik van deze software.

OSCAR is vrije (zoals in vrijheid) software, vrijgegeven onder de GNU Public License v3, en wordt geleverd zonder garantie en zonder enige aanspraak op fitness voor welk doel dan ook.

OSCAR is © 2019-2022 Het OSCAR-team: een groep vrijwillige ontwikkelaars van de Apneu-gemeenschap en leden van meerdere forums en verschillende nationaliteiten.

OSCAR is een afgeleide van het SleepyHead-programma waarop copyright © 2011-2018 Mark Watkins rust.
OSCAR-code-v1.3.1/Htmldocs/about-no.html000066400000000000000000000061321417327530600176620ustar00rootroot00000000000000 about_no

Velkommen til OSCAR,
Open Source CPAP Analyse Reporter

Denne programvaren er designet for å hjelpe deg med å gjennomgå dataene som produseres av CPAP-maskiner og relatert utstyr.

Frivillige oversatte det til mange språk. Klikk på Hjelp-fanen for å endre språket som vises.

Enhver Chromium-basert nettleser som Google Chrome eller Microsoft Edge Chromium tilbyr for tiden de beste automatiske oversettelsesalternativene.
Du finner omfattende hjelp i OSCAR Wiki. Åpne http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

For spørsmål besøk http://www.apneaboard.com eller et lokalt apnéforum.
Facebook Søvnapnegruppe (lukket for personvern) finner du her: https://www.facebook.com/groups/1428781900666778 /

VENNLIGST LES NØYE


OSCAR er IKKE en erstatning for kompetent medisinsk veiledning fra legen din.

På grunn av mangelen på dokumentasjon utgitt av produsenter angående filformater, kan ikke nøyaktigheten til data som vises i OSCAR på noen måte garanteres.

Alle rapporter som genereres er KUN PERSONLIG BRUK og er ment å være så nøyaktige som mulig.

OSCAR-rapporter er basert på data rapportert av CPAP-maskinen. Aksept av disse dataene for samsvar eller andre formål er underlagt godkjenning fra byrået som utfører revisjonen.

Bruk av denne programvaren er helt på egen risiko. Forfatterne vil ikke bli holdt ansvarlig for noe relatert til bruk eller misbruk av denne programvaren.

OSCAR er gratis (som i frihet) programvare, utgitt under GNU Public License v3, og kommer uten garanti og uten NOEN krav på egnethet til noe formål.

OSCAR er © 2019-2022 OSCAR Team: en gruppe frivillige utviklere fra Apnea Community og medlemmer av flere fora og forskjellige nasjonaliteter.

OSCAR er et derivat av SleepyHead-programmet som er copyright © 2011-2018 Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-ph.html000066400000000000000000000062371417327530600176630ustar00rootroot00000000000000 about-ph

Maligayang pagdating sa OSCAR,
ang Open Source CPAP Analysis Reporter

Ang software na ito ay idinisenyo upang matulungan ka sa pagsuri ng data na ginawa ng iyong mga aparato ng CPAP at mga kaugnay na kagamitan.

Isinalin ito ng mga boluntaryo sa maraming wika. I-click ang tab na Tulong upang baguhin ang ipinakitang wika.

Ang anumang browser na nakabase sa Chromium tulad ng Google Chrome o Microsoft Edge Chromium ay kasalukuyang nag-aalok ng pinakamahusay na awtomatikong pagpipilian sa pagsasalin.
Makakakita ka ng malawak na tulong sa OSCAR Wiki. Buksan ang http://www.apneaboard.com/wiki/index.php/OSCAR_help.

Para sa mga katanungan bisitahin ang http://www.apneaboard.com o isang lokal na forum ng apnea.

MANGYARING MABASA NG MABUTI


Ang OSCAR ay HINDI isang kapalit para sa karampatang medikal na gabay mula sa iyong Doktor.

Dahil sa kakulangan ng dokumentasyon na inilabas ng mga tagagawa tungkol sa mga format ng file, ang kawastuhan ng data na ipinapakita sa OSCAR ay hindi maaaring garantisado.

Ang lahat ng mga ulat na nabuo ay para sa PERSONAL GAMIT LAMANG at inilaan na maging tumpak hangga't maaari.

Ang mga ulat ng OSCAR ay batay sa data na naiulat ng makina ng CPAP. Ang pagtanggap ng data na ito para sa pagsunod o iba pang mga layunin ay napapailalim sa pagpapasya sa pag-apruba ng ahensya ng pagsusuri.
Ang paggamit ng software na ito ay ganap na nasa iyong sariling peligro. Ang mga may-akda ay hindi gaganapin mananagot para sa anumang bagay na may kaugnayan sa paggamit o maling paggamit ng software na ito.

Ang OSCAR ay libre (tulad ng sa kalayaan) na software, na inilabas sa ilalim ng GNU Public Lisensya v3, at walang warranty, at walang ANUMANG nagsasabing fitness para sa anumang layunin.

Ang OSCAR ay © 2019-2022 Ang OSCAR Team: isang pangkat ng mga nag-develop ng boluntaryo mula sa Apnea Community at mga miyembro ng maraming mga forum at iba't ibang nasyonalidad.

Ang OSCAR ay isang hinango sa programa ng SleepyHead na may copyright © 2011-2018, Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-pl.html000066400000000000000000000074671417327530600176750ustar00rootroot00000000000000 about-pl

Witamy w OSCAR,
Open Source CPAP Analysis Reporter


To oprogramowanie zostało zaprojektowane, aby pomóc Ci w przeglądaniu danych generowanych przez twoje urządzenia CPAP i powiązany sprzęt.

Wolontariusze przetłumaczyli go na wiele języków. Kliknij kartę Pomoc, aby zmienić wyświetlany język.
W razie potrzeby skontaktuj się z tłumaczem - Wiesław Mrotek aka refurbished, oscar-cpap-pl@wp.pl).
Pomoc w języku polskim znajdziesz również na Facebooku:
http://www.facebook.com/groups/bezdechsenny/?ref=bookmarks
oraz na blogu:
https://www.cpapblog.pl/

Każda przeglądarka oparta na Chromium, taka jak Google Chrome lub Microsoft Edge Chromium , oferuje obecnie najlepsze opcje automatycznego tłumaczenia .
Obszerną pomoc znajdziesz na Wiki OSCAR. Otwórz http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

W przypadku pytań odwiedź stronę http://www.apneaboard.com lub lokalne forum dotyczące bezdechu.
FaceBook - https://www.facebook.com/groups/285098231956516/ 
CPAP-Blog - https://www.cpapblog.pl/2019/04/analiza-danych-sleepyhead-OSCAR.html

PROSZĘ CZYTAĆ UWAŻNIE


OSCAR NIE jest substytutem dla kompetentnych wskazówek medycznych od twojego lekarza.

Z powodu braku opublikowanej przez producentów dokumentacji dotyczącej formatów plików, nie można w żaden sposób zagwarantować dokładności danych wyświetlanych w OSCAR.

Wszystkie generowane raporty są WYŁĄCZNIE DO UŻYTKU OSOBISTEGO i mają być jak najbardziej dokładne.

Raporty OSCAR są oparte na danych zgłaszanych przez urządzenie CPAP. Akceptacja tych danych do celów zgodności lub w innych celach podlega zatwierdzeniu przez agencję recenzującą.

Korzystanie z tego oprogramowania odbywa się wyłącznie na własne ryzyko. Autorzy nie ponoszą odpowiedzialności za nic związanego z użytkowaniem lub niewłaściwym użytkowaniem tego oprogramowania.

OSCAR to darmowe oprogramowanie, wydane na licencji GNU Public License v3, i nie ma żadnej gwarancji i ŻADNYCH roszczeń do przydatności do jakiegokolwiek celu.

OSCAR jest © 2019-2022 Zespół OSCAR: grupa programistów-wolontariuszy ze społeczności bezdechu oraz członkowie wielu forów i różnych narodowości.

OSCAR jest pochodną programu SleepyHead, który jest chroniony prawem autorskim © 2011-2018 Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-pt.html000066400000000000000000000056771417327530600177060ustar00rootroot00000000000000 about-pt

Bem-vindo à OSCAR,
o Open Source CPAP Analysis Reporter

Este software foi projetado para ajudá-lo a revisar os dados produzidos pelos seus dispositivos CPAP e equipamentos relacionados.

Os voluntários o traduziram para vários idiomas. Clique na guia Ajuda para alterar o idioma exibido.

Atualmente, qualquer navegador baseado no Chromium, como o Google Chrome ou o Microsoft Edge Chromium, oferece as melhores opções de tradução automática.
Você encontrará ampla ajuda no Wiki da OSCAR. Abra http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

Para perguntas, visite http://www.apneaboard.com ou um fórum local de apneia.

POR FAVOR, LEIA ATENTAMENTE


O OSCAR NÃO substitui a orientação médica competente do seu médico.

Devido à falta de documentação divulgada pelos fabricantes sobre os formatos de arquivo, a precisão dos dados exibidos na OSCAR não pode de forma alguma ser garantida.

Todos os relatórios gerados são APENAS PARA USO PESSOAL e devem ser o mais precisos possível.

Os relatórios OSCAR são baseados em dados relatados pela máquina CPAP. A aceitação desses dados para conformidade ou outros fins está sujeita ao critério de aprovação da agência revisora.

O uso deste software é inteiramente por sua conta e risco. Os autores não serão responsáveis ​​por nada relacionado ao uso ou uso indevido deste software.

O OSCAR é um software gratuito (como na liberdade), lançado sob a Licença Pública GNU v3, e vem sem garantia e sem QUALQUER reivindicação de adequação a qualquer finalidade.

O OSCAR é © 2019-2022 A Equipe OSCAR: um grupo de desenvolvedores voluntários da Comunidade de Apneia e membros de vários fóruns e várias nacionalidades.

OSCAR é um derivado do programa SleepyHead, com direitos autorais © 2011-2018 Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-pt_BR.html000066400000000000000000000062151417327530600202560ustar00rootroot00000000000000 about-pt_BR

Bem-vindo à OSCAR,
a Open Source CPAP Analysis Reporter

Este software foi projetado para ajudá-lo a revisar os dados produzidos por suas máquinas CPAP e equipamentos relacionados.

Os voluntários o traduziram para vários idiomas. Para informar qualquer erro de tradução e/ou sugestões, sinta-se a vontade para enviar um email para ristraus@hotmail.com, que fez a tradução. Clique na guia Ajuda para alterar o idioma exibido.

Atualmente, qualquer navegador baseado no Chromium, como o Google Chrome ou o Microsoft Edge Chromium, oferece as melhores opções de tradução automática.
Você encontrará ampla ajuda no Wiki da OSCAR. Abra http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

Para perguntas, visite http://www.apneaboard.com ou um fórum local de apneia.

POR FAVOR, LEIA ATENTAMENTE


O OSCAR NÃO substitui a orientação médica competente do seu médico.

Devido à falta de documentação divulgada pelos fabricantes sobre os formatos de arquivo, a precisão dos dados exibidos na OSCAR não pode de forma alguma ser garantida.

Todos os relatórios gerados são APENAS PARA USO PESSOAL e devem ser o mais precisos possível.

Os relatórios OSCAR são baseados em dados relatados pela máquina CPAP. A aceitação desses dados para conformidade ou outros fins está sujeita ao critério de aprovação da agência revisora.

O uso deste software é inteiramente por sua conta e risco. Os autores não serão responsáveis ​​por nada relacionado ao uso ou uso indevido deste software.


O OSCAR é um software gratuito (como na liberdade), lançado sob a Licença Pública GNU v3, e vem sem garantia e sem QUALQUER reivindicação de adequação a qualquer finalidade.

O OSCAR é © 2019-2022 A Equipe OSCAR: um grupo de desenvolvedores voluntários da Comunidade da Apneia e membros de vários fóruns e várias nacionalidades.

OSCAR é um derivado do programa SleepyHead, com direitos autorais © 2011-2018 Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-ro.html000066400000000000000000000056331417327530600176730ustar00rootroot00000000000000 about-ro

Bine ați venit la OSCAR,
Reporter de analiză CPAP Open Source

Acest software a fost conceput pentru a vă ajuta să revizuiți datele produse de dispozitivele dvs. CPAP și echipamentele aferente.

Voluntarii au tradus-o în mai multe limbi. Faceți clic pe fila Ajutor pentru a schimba limba afișată.

Orice browser bazat pe Chromium, cum ar fi Google Chrome sau Microsoft Edge Chromium , oferă în prezent cele mai bune opțiuni de traducere automată .
Vei găsi ajutor extins în Wiki OSCAR. Deschideți http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

Pentru întrebări vizitați http://www.apneaboard.com sau un forum local de apnee.

VĂ RUGĂM CITIȚI CU ATENȚIE


OSCAR NU ESTE un substitut pentru îndrumarea medicală competentă din partea medicului dumneavoastră.

Din cauza lipsei documentației lansate de producători cu privire la formatele de fișiere, acuratețea datelor afișate în OSCAR nu poate fi în niciun fel garantată.

Toate rapoartele generate sunt DOAR PENTRU UTILIZARE PERSONALĂ și sunt destinate să fie cât mai precise.

Rapoartele OSCAR se bazează pe datele raportate de aparatul CPAP. Acceptarea acestor date în conformitate cu alte scopuri este supusă discreției agenției de examinare.
Utilizarea acestui software este pe riscul dvs. Autorii nu vor fi responsabili pentru nimic legat de utilizarea sau utilizarea greșită a acestui software.

OSCAR este un software gratuit (ca și în libertate), lansat sub licența publică GNU v3 și nu are nicio garanție și fără NICIUNE pretenții de fitness pentru niciun scop.

OSCAR este © 2019-2022 Echipa OSCAR: un grup de dezvoltatori voluntari din comunitatea Apnea și membri ai mai multor forumuri și diferite naționalități.

OSCAR este un derivat al programului SleepyHead care este protejat de drepturi de autor © 2011-2018, Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-sv.html000066400000000000000000000063261417327530600177030ustar00rootroot00000000000000 about-sv

Välkommen till OSCAR,
Open Source CPAP Analyst Reporter

Denna programvara har utformats för att hjälpa dig att granska informationen som produceras av dina CPAP-enheter och relaterad utrustning.

Volontärer har översatt det till många språk. Klicka på fliken Hjälp för att ändra det visade språket.

Alla Chromium-baserade webbläsare som Google Chrome eller Microsoft Edge Chromium erbjuder för närvarande de bästa automatiska översättningsalternativen .
Du hittar omfattande hjälp i OSCAR Wiki. Öppna http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

För frågor besök http://www.apneaboard.com eller något av Sveriges olika apnéforum.
https://www.facebook.com/groups/OSCAR.Sverige/
https://www.facebook.com/groups/370842319659576/

VÄNLIGEN LÄS NOGGRANT


OSCAR kan INTE ersätta kompetent medicinsk rådgivning från din läkare.

På grund av bristen på dokumentation som släppts av tillverkarna om filformat kan inte noggrannheten för data som visas i OSCAR på något sätt garanteras.

Alla rapporter som genereras är för PERSONLIG ANVÄNDNING och är avsedda att vara så korrekta som möjligt.

OSCAR-rapporter är baserade på data rapporterade av CPAP-maskinen. Godkännande av denna information för compliance eller för andra ändamål är upp till varje beslutsfattares ansvar.

Användningen av denna programvara sker helt och hållet på egen risk. Författarna kommer inte att hållas ansvariga för något relaterat till användning eller missbruk av denna programvara.

OSCAR är gratis (som i frihet) mjukvara, släppt under GNU Public License v3, och levereras utan garanti och utan några krav på lämplighet för något syfte.

OSCAR är © 2019-2022 OSCAR-teamet: en grupp volontärutvecklare från Apnea Community och medlemmar i flera forum och olika nationaliteter.

OSCAR är en vidareutveckling av SleepyHead-programmet som har copyright © 2011-2018 Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/about-th.html000066400000000000000000000113211417327530600176550ustar00rootroot00000000000000 about

ยินดีต้อนรับสู่ OSCAR,
โปรแกรมรายงานการวิเคราะห์ CPAP ของโอเพ่นซอร์ส

ซอฟต์แวร์นี้ได้รับการออกแบบมาเพื่อช่วยคุณในการตรวจสอบข้อมูลที่ผลิตโดยอุปกรณ์ CPAP และอุปกรณ์ที่เกี่ยวข้อง

อาสาสมัครแปลเป็นหลายภาษา คลิกแท็บวิธีใช้เพื่อเปลี่ยนภาษาที่แสดง

เบราว์เซอร์ที่ทำงานด้วย Chromium ใด ๆ เช่น Google Chrome หรือ Microsoft Edge Chromium นำเสนอตัวเลือกการแปลอัตโนมัติที่ดีที่สุด
คุณจะพบกับความช่วยเหลือมากมายใน OSCAR Wiki เปิด http://www.apneaboard.com/wiki/index.php/OSCAR_Help

สำหรับคำถามเยี่ยมชม http://www.apneaboard.com หรือฟอรั่มหยุดหายใจขณะในท้องถิ่น

โปรดอ่านอย่างละเอียด


ออสการ์ไม่ได้ใช้แทนคำแนะนำทางการแพทย์จากแพทย์ของคุณ

เนื่องจากไม่มีเอกสารประกอบที่ออกโดยผู้ผลิตเกี่ยวกับรูปแบบไฟล์ความถูกต้องของข้อมูลที่แสดงใน OSCAR จึงไม่สามารถรับประกันได้

รายงานทั้งหมดที่สร้างขึ้นมีไว้สำหรับการใช้งานส่วนบุคคลเท่านั้นและมีจุดประสงค์เพื่อความถูกต้องแม่นยำที่สุดเท่าที่จะเป็นไปได้

รายงาน OSCAR อ้างอิงจากข้อมูลที่รายงานโดยเครื่อง CPAP การยอมรับข้อมูลนี้เพื่อการปฏิบัติตามหรือวัตถุประสงค์อื่น ๆ ขึ้นอยู่กับดุลยพินิจการอนุมัติของหน่วยงานตรวจสอบ
การใช้ซอฟต์แวร์นี้ถือเป็นความเสี่ยงของคุณเอง ผู้เขียนจะไม่รับผิดชอบต่อสิ่งที่เกี่ยวข้องกับการใช้หรือการใช้ซอฟต์แวร์นี้ในทางที่ผิด

OSCAR เป็นซอฟต์แวร์ฟรี (ตามอิสระ) เผยแพร่ภายใต้ GNU Public License v3 และไม่มีการรับประกันใด ๆ

ออสการ์คือ© 2019-2022 ทีมออสการ์: กลุ่มนักพัฒนาอาสาสมัครจากชุมชน Apnea และสมาชิกของฟอรัมหลายแห่งและหลายเชื้อชาติ

ออสการ์เป็นอนุพันธ์ของโปรแกรม SleepyHead ซึ่งเป็นลิขสิทธิ์© 2011-2018, Mark Watkins
OSCAR-code-v1.3.1/Htmldocs/about-tr.html000066400000000000000000000064071417327530600177000ustar00rootroot00000000000000 about

OSCAR'a hoş geldiniz,
the Open Source CPAP Analysis Reporter
(Açık Kaynak Kodlu CPAP Analizi Raporlayıcısı)

Bu yazılım CPAP cihazınız ve ilgili diğer donanımlar tarafından üretilen verileri gözden geçirmeniz için size yardımcı olmak için tasarlanmıştır.

Gönüllüler tarafından birçok dile tercüme edilmiştir. Kullanılan dili değiştirmek için Yardım sekmesine tıklayınız.

Google Chrome veya Microsoft Edge Chromium gibi Chromium tabanlı herhangi bir gezgin şu anda mevcut en iyi otomatik tercüme seçeneklerini sunmaktadır.
OSCAR Wiki'sinden kapsamlı bir yardım seçeneği mevcuttur. http://www.apneaboard.com/wiki/index.php/OSCAR_Help adresine gidin.

Sorularınız için http://www.apneaboard.com forumuna veya yerel bir apne forumuna baş vurabilirsiniz.

LÜTFEN DİKKATLE OKUYUN


OSCAR doktorunuzun sağlayacağı tıbbi reberliğin yerini alabilecek bir alternatif DEĞİLDİR

Cihaz üreticilerinin dosya formatları hakkında yeterli miktarda bilgilendirme yapmamaları sebebiyle, OSCAR tarafından gösterilen verilerin doğruluğu hiçbir şekilde garanti edilemez.

Oluşturulan tüm raporlar KİŞİSEL KULLANIM İÇİNDİR ve mümkün olabildiği kadar doğru olmaları amaçlanmıştır.

OSCAR raporları CPAP makinesi tarafından bildirilen verilere dayanmaktadır. Bu verilerin cihaz kullanımına uyum veya başka amaçlarla kullanılabilirliği, ilgili makamların onay verip vermemesine bağlıdır.
Bu yazılımı kullanmanın doğuracağı risklerin sorumluluğu tamamıyla size aittir. Yazarlar hiçbir şekilde bu yazılımın kullanım veya kötüye kullanımından doğabilecek sonuçlardan sorumlu değildir.

OSCAR ücretsiz bir yazılım olup GNU Public License v3 başlığı altında kullanıma sunulmuştur, herhangi bir garanti sunmamaktadır ve belirli bir amaca uygun olduğuna dair bir iddası yoktur.

OSCAR © 2019-2022 OSCAR Takımı: Apne Topluluğunun parçası olan, çeşitli forumlardan ve farklı milliyetlerden bir grup gönüllü geliştirici tarafından hazırlanmıştır.

OSCAR telif hakkı © 2011-2018 Mark Watkins'e ait olan SleepyHead yazılımından türetilmiştir.
OSCAR-code-v1.3.1/Htmldocs/about-zh.html000066400000000000000000000047721417327530600176770ustar00rootroot00000000000000 about_zh

欢迎使用OSCAR,
开源CPAP分析报告器

该软件旨在帮助您检查由CPAP设备和相关设备产生的数据。

志愿者将其翻译成多种语言。单击“帮助”选项卡以更改显示的语言。

目前,任何基于Chromium的浏览器(例如 Google ChromeMicrosoft Edge Chromium )都可提供最佳的 自动翻译选项
您将在OSCAR Wiki中找到广泛的帮助。打开 http://www.apneaboard.com/wiki/index.php/OSCAR_Help

如有疑问,请访问 http://www.apneaboard.com或 当地的呼吸暂停论坛。

请仔细阅读


OSCAR不 能代替您的医生的有效医疗指导。

由于缺少制造商发布的有关文件格式的文档,因此不能以任何方式保证OSCAR中显示的数据的准确性。

生成的所有报告 仅供个人使用,目的是尽可能准确。

OSCAR报告基于CPAP机器报告的数据。出于合规性或其他目的而接受此数据受审核机构的批准决定。

使用此软件的风险完全由您自己承担。作者不承担与使用或滥用本软件有关的任何责任。

OSCAR是根据GNU Public License v3发布的免费软件(如自由软件),没有任何担保,也没有任何适用于任何目的的声明。

OSCAR©©2019-2022 OSCAR团队:由Apnea社区的志愿者开发人员组成,是多个论坛和不同国籍的成员。

OSCAR是SleepyHead程序的派生工具,其版权©2011-2018 Mark Watkins。
OSCAR-code-v1.3.1/Htmldocs/about.html000066400000000000000000000052701417327530600172520ustar00rootroot00000000000000 about

Welcome to OSCAR,
the Open Source CPAP Analysis Reporter

This software has been designed to assist you in reviewing the data produced by your CPAP devices and related equipment.

Volunteers translated it into many languages. Click the Help tab to change the displayed language.

Any Chromium-based browser like Google Chrome or Microsoft Edge Chromium currently offers the best automatic translation options.
You will find extensive help in the OSCAR Wiki. Open http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

For questions visit http://www.apneaboard.com or a local apnea forum.

PLEASE READ CAREFULLY


OSCAR is NOT a substitute for competent medical guidance from your Doctor.

Due to the lack of documentation released by manufacturers regarding file formats, the accuracy of data displayed in OSCAR can not in any way be guaranteed.

All reports generated are for PERSONAL USE ONLY and are intended to be as accurate as possible.

OSCAR reports are based on data reported by the CPAP machine. Acceptance of this data for compliance or other purposes is subject to the approval discretion of the reviewing agency.
The use of this software is entirely at your own risk. The authors will not be held liable for anything related to the use or misuse of this software.

OSCAR is free (as in freedom) software, released under the GNU Public License v3, and comes with no warranty, and without ANY claims to fitness for any purpose.

OSCAR is © 2019-2022 The OSCAR Team: a group of volunteer developers from the Apnea Community and members of multiple forums and various nationalities.

OSCAR is a derivative of the SleepyHead program which is copyright © 2011-2018, Mark Watkins.
OSCAR-code-v1.3.1/Htmldocs/credits.html000066400000000000000000000153741417327530600176030ustar00rootroot00000000000000 credits

This page in other languages:
http://www.apneaboard.com/wiki/index.php/OSCAR_Credits

Credits


OSCAR is a derivative of the SleepyHead program written by Mark Watkins, during the years 2011 to 2018. The current project is the combined effort of people from CPAPtalk.com, ApneaBoard.com, and other volunteers, starting in 2019.

OpenSource Libraries
OSCAR uses the OpenSource version of the Qt cross-platform toolkit available from http://qt.io which itself draws from many smaller open source libraries. You can read the individual licensing for many of these components that are used under the hood of OSCAR at https://doc.qt.io/qt-5/licenses-used-in-qt.html

Data formats
The CPAP device data formats are mostly undocumented. Getting them working in OSCAR involved a lot of investigation, together with a lot of SD card data samples, many patient users willing to put up with crashes and data issues, and plenty of help from fellow developers out there who shared in the workload of decoding data formats.Thanks to all of you who have helped in the fight to protect our right to keep our own data open and accessible!

The OSCAR team currently consists of the following persons (with their ApneaBoard names):

Fred Bonjour (Gideon) : Project Manager & Lead Tester, Phil Olynyk (pholynyk) : Lead Developer, Arie Klerk (A KLERK): Translations Team Coordinator

Developers
Phil Olynyk (pholynyk) (Lead Developer), GuyScharf, sawinglogz

Reporters
AlanE, BrandonA, Crimson Nape, foxfire, Heyns, jeremieb, jaswilliams, palerider, patl

Testers
Fred Bonjour (Gideon) (Lead Tester), Beej, DeepBreathing, Fastlane, GuyScharf, JJJ, LookingForward, Pollcat, Ruth Catrin, SarcasticDave94, unidee

Advisors
aviB, SkepticDoc, Sleeprider, SleepyProgrammer, srlevine1, LunaFerret, harre, mdhamptom, mitchcampbell, rtannerf

Translators
Arie Klerk (A KLERK) (Translations Team Coordinator, Dutch), 1st.qwerty (Polish), C Chan (Trad.Chinese), Caniss (Swedish), delta (Romanian), dolceitalia (Italian), drmaestro (Turkish), drol (French), FaureCourtet (French), GregK (Russian, Hebrew), hearsay73 (Filipino), Heyns (Afrikaans), Hypoxic (Greek), jaswilliams (British), johanh (Finnish), k2boys (Korean), Lazer1234 (Swedish), Mac_Sheepcounter (German), mazingas65 (Italian), Ppja (Spanish), pstrjds (Bulgarian), refurbished (Polish), Ristraus (Brazilian Portugese), rlabs (Russian), ShaunBlake (British), steffenreitz (German), tolnaiz (Hungarian), unidee (Finnish), untoutseul05 (French), yrnkrn (Hebrew), N-A-N (Thai).
Thank you all very much for your continuous effort!

OSCAR is always looking for help: programmers, testers, or translators. If you are interested, please PM 'Gideon' on the Apnea Board Forum.


A special mention to the ApneaBoard for providing a development forum for OSCAR and for providing the primary download site for OSCAR at https://sleepfiles.com/OSCAR.

Also acknowledging ApneaBoard for their support of software for CPAP users for many years.


Third-Party Libraries:

Botan
Copyright (C) 1999-2020 The Botan Authors
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

miniz
Copyright 2013-2014 RAD Game Tools and Valve Software
Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
All Rights Reserved.

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.

OSCAR-code-v1.3.1/Htmldocs/release_notes.html000066400000000000000000000704001417327530600207650ustar00rootroot00000000000000 release_notes

This page in other languages:
http://www.apneaboard.com/wiki/index.php/OSCAR_Release_Notes

Changes and fixes in OSCAR v1.3.1
Portions of OSCAR are © 2019-2022 by The OSCAR Team

  • [new] Additional Philips Respironics devices tested and fully supported:
    • REMstar Plus (System One 60 Series) (261P) provides only limited information
    • REMstar Pro (System One 60 Series) (460PBT)
    • DreamStation CPAP Pro with Auto-Trial (401X150)
  • [new] Choose AS11 data instead of AS10 data if both are present on the SD card.
  • [new] Add support for new settings codes in recently manufactured 700X110 DreamStations.
  • [new] Add 95% flow limitation to Therapy Efficacy section on Statistics page.
  • [new] Add date range option to Statistics page.
  • [new] Improve appearance and operation of event types and graphs comboboxes on Daily and Overview pages.
  • [new] Skip first 20 seconds of TiVol, RR, and MinVent in ResMed loader.
  • [new] Add ResMed 39423 Canadian Autoset to tested list.
  • [new] Show channel description when hovering over index name on Daily page.
  • [new] Add full support for Wellue CheckMe O2 Max.
  • [fix] Fix pressure values for AutoForHer mode on AS1x.
  • [fix] Fix missing oximetry and motion waveforms on Overview pages.
  • [fix] Fix rare problem of minimum pressure shown as zero on Overview and Statistics pages.
  • [fix] Update Statistics page after editing profile.
  • [fix] Correct selection of CPAP and AutoForHer modes for ResMed AS11 modes.
  • {fix} Skip first 10 seconds of pressure data for ResMed grapths and statistics.
  • [fix] Correct SleepStyle machines sometimes identified as Icon machines.
  • [fix] Improve event flag position in flow graph for DV6 machines.
  • [fix] --datadir option now allows fully qualified paths on Mac and Linux.
  • [fix] Correct value display on exact data points, and near session boundaries with drift.
  • [fix] Link to translations in Credits and Release Notes html is now translated to selected language.
  • [fix] Continue to import DreamStation 1 data even if there is (unsupported) DreamStation 2 data on the SD card.
  • [fix] Automatic Oximetry Cleanup no longer drops samples or supplemental oximeter channels such as movement.
  • [fix] Replace BRICK! message with better wording.
  • [fix] Add footer to first page of printed Statistics report.
  • [fix] Work around a Qt bug on Fedora 35 that caused hangs when selecting files.
  • [fix] Update copyright notices in html files.

Changes and fixes in OSCAR v1.3.0
Portions of OSCAR are © 2019-2021 by The OSCAR Team

  • [new] Add preliminary support for ResMed AirSense 11 CPAP machines.
  • [new] Add support for Fisher & Paykel SleepStyle CPAP machines.
  • [new] Add support for DeVilbiss BLUE (DV6x) CPAP machines.
  • [new] Support both Intel/AMD and ARM Chromebooks using Linux beta.
  • [new] Additional Philips Respironics devices tested and fully supported:
    • DreamStation Go Auto (500G120)
    • DreamStation Auto CPAP with A-Flex (500X140)
    • DreamStation Auto BiPAP (700X130)
    • DreamStation BiPAP AVAPS 30 (1130X200)
  • [new] Add support for DreamStation Go humidifier Target Time setting.
  • [new] Detect DreamStation 2 cards, which are still unsupported.
  • {new} Correct the mode names for BiLevel ResMed machines.
  • [new] Add support for additional Viatom/Wellue filename conventions.
  • [new] Add support for unreadably low SpO2 samples on Viatom/Wellue oximeters.
  • [new] Improve Somnopose import options.
  • [new] Multi-file import for non-CPAP loaders (Somnopose, Viatom, Zeo, Dreem)
  • [new] Purge Current Selected Day allows purge of each machine type separately
  • [new] Weight, BMI and Zombie history appear on Overview page
  • [new] Add Turkish signal names to RedMed loader.
  • [new] Add Bulgarian translation; update other languages.
  • [new] Add Traditional Chinese to languages available.
  • [fix] Fix AVAPS pressure settings.
  • [fix] Check for Updates no longer shows test versions to release users.
  • [fix] Rx pressures shown correctly in Profile dialog.
  • [fix] Resolve empty CPAP data card zips on macOS Big Sur.
  • [fix] Always prompt for SD card location when zipping SD card.
  • [fix] Add script to identify platform in mkDistDeb.sh (Linux building).
  • [fix] Correct calculation of average leak rate on Welcome page.
  • [fix] Correct installation of non-English Release Notes on Windows.
  • [fix] About/Credits page now offers Google translations to other languages.
  • [fix] User first and last name in Profile window may now have UTF-8 (non-ASCII) characters.
  • [fix] Fix crash and other problems when disabling an oximeter session on Daily page when a bookmark was present.
  • [fix] Fix rare problem of OSCAR crashing with unusual Journal file.
  • [fix] ResMed loader no longer rejects data from an earlier timezone or DST.
  • [fix] Newly entered notes no longer lost when importing new day or purging oximetry data.
  • [fix] Purge currently selected day no longer deletes bookmarks for that day.
  • [fix] Remove warning from Chromebook when importing from previously used local folder.
  • [fix] Update link to Contec drivers.
  • [fix] Fix display problems for short duration events.
  • [fix] Statistics headings will now be 99.5% or Max, depending on machine type and preference settings.
  • [fix] Mark exported Journal backup file as UTF-8.
  • [fix] Improve error message when unable to access OSCAR database.
  • [fix] Stop skipping the first 40 seconds of ResMed low-rate pressure data.
  • [fix] Correct Total Time and AHI in CSV Export when non-CPAP devices are used.
  • [fix] Fix value display and bookmark behavior with clock drift.
  • [fix] Ignore old sessions should not impact existing data.
  • [fix] Fix ocasional misordering of indexes on the Daily page.
  • [fix] Add Unclassified Apneas to the Statistics page.
  • [fix] Fix rare error on Welcome page comparing leak rate to 7-day average.
  • [fix] Fix some Y2K issues in oximetry importers.

Changes and fixes in OSCAR v1.2.0
Portions of OSCAR are © 2019-2020 by The OSCAR Team

  • [new] Support for Chromebooks using Linux beta - (Intel/AMD only)
  • [new] Support for Raspberry Pi OS
  • [new] Additional Philips Respironics devices tested and fully supported:
    • REMstar Pro (System One) (452P, 450P V1)
    • BiPAP Pro (System One) (650P)
    • REMstar Plus (System One 60 Series) (261CA)
    • REMstar Pro (System One 60 Series) (462P)
    • DreamStation CPAP Pro (400X120)
    • DreamStation BiPAP autoSV (900X150)
    • Dorma 500 Auto (System One 60 Series) (501V)
  • [new] OSCAR checks automatically for new releases.
  • [new] Privacy feature allows suppressing profile info in Statistics page and printed reports.
  • [new] Add option to print reports in black and white (monochrome).
  • [new] Hold down the Shift key while selecting a range on a chart to measure the duration without zooming.
  • [new] Allow second (or more) import on same day for ResMed data.
  • [fix] Improve support of rare events and update warnings in Philips Respironics 950P, 1030X, and 50-series.
  • [fix] Profile page and Daily records block now show most recently used machine when a profile has multiple machines.
  • [fix] Improve warning on Daily page when settings are missing.
  • [fix] No longer zoom charts to maximum when clicking on chart with popup menu present.
  • [fix] Correct page margins problem when printing Statistics page on Mac.
  • [fix] Adjust font size and column widths on Statistics page.
  • [fix] Improve language in various error messages.
  • [fix] Date at right end of Overview page reflects actual data, not any future notes.
  • [fix] Limit SD card scan to mounted vfat volumes.
  • [fix] Correct Statistics page calculations when CPAP and Oximetry use do not overlap.
  • [fix] Hours used on Welcome and Daily page are now hours of CPAP machine use.
  • [fix] Popout graphs now limited to desktop height and multiple popout graphs work better.
  • [fix] Overview tooltips now list chart components in same order as displayed in chart.
  • [fix] Overview graphs now show last day on charts for timezones near GMT.
  • [fix] Welcome page calculation of days since last import fixed.
  • [fix] Oximetry import from file now remembers last directory imported from.
  • [fix] Correct pressure reported on Welcome page for VAuto-S machines.
  • [fix] Improve error checking for file system problems.
  • [fix] ResMed machines now show the Essentials setting.
  • [fix] Support migration from both SleepyHead and OSCAR.
  • [fix] Correct 95th percentile computations on Statistics page when some days were summary-only.
  • [fix] Improve some prompts and tooltip messages.
  • [fix] Empty directories in Profiles directory are not shown in Profiles list.
  • [fix] Show correct copyright when installing Debian version.
  • [fix] Translations updated; Korean translation added.
  • [fix] Rebuilding CPAP data for ResMed machine with compression enabled no longer results in settings loss.

Changes and fixes in OSCAR v1.1.1
Portions of OSCAR are © 2019-2020 by The OSCAR Team

  • [new] Additional Philips Respironics devices tested and fully supported:
    • BiPAP S/T (C Series) (1061401)
    • REMstar Pro (System One 60 Series) (461CA)
    • DreamStation CPAP Pro (400X130)
    • DreamStation BiPAP Pro (600X150)
    • DreamStation Auto BiPAP (700X120, 700X150)
    • Note: Ventilator alarms are not currently imported.
  • [new] Add the "peak flow" channel reported by pre-DreamStation ventilators.
  • [new] Automatically detect and resolve graphics-related crashes on Windows.
  • [new] Support AVAPS in the Overview pressure chart.
  • [new] Added Italian and Turkish translations.
  • [fix] Fix missing bars in the Overview pressure chart for Philips Respironics devices.
  • [fix] Add missing Philips Respironics pressure channels to CSV export.
  • [fix] Fix zero Philips Respironics AHI in CSV session export.
  • [fix] Add support for the Bi-Flex lock setting on pre-DreamStation ventilators.
  • [fix] Fix the pressure waveform scale for the BiPAP autoSV Advanced 30 (960T)
  • [fix] Add support for rise time mode on DreamStation BiPAP devices (600X-700X).
  • [fix] Remove the ramp time and pressure settings when the ramp is disabled on pre-DreamStation devices.
  • [fix] Improve import of Philips Respironics oximetry data.
  • [fix] Fix VS2 index shown on the Daily page for Philips Respironics machines.
  • [fix] Fix occasional failure to save imported Viatom data.
  • [fix] Fix a recurring database upgrade prompt.
  • [fix] Fix an occasional crash when importing Resmed data.
  • [fix] Add bounds error checking to Resmed input processing.
  • [fix] Fix STR.edf file backup to avoid loss of settings data.
  • [fix] Fix Backup Journal to format XML properly.
  • [fix] In flow rate graph, don't show duration for events with no duration.
  • [fix] Fix pressure settings in Overview graph reported as a huge negative number.

Changes and fixes in OSCAR v1.1.0
Portions of OSCAR are © 2019-2020 by The OSCAR Team

  • [new] Additional Philips Respironics devices tested and fully supported:
    • DreamStation Auto CPAP (500X180)
  • [new] Add support for DreamStation humidifier passover and error modes.
  • [fix] Fix an rc-1 regression in pressure relief display on the Statistics page.
  • [fix] Fix the minutes-at-pressure graph for Philips Respironics devices.

Changes and fixes in OSCAR v1.1.0-rc-1
Portions of OSCAR are © 2019-2020 by The OSCAR Team

  • [fix] AHI and Usage bars in overview graphs now show the correct height when oximetry usage is significantly longer than CPAP usage.
  • [fix] Improved import of Philips Respironics settings. In particular:
    • The settings for PC, S, and S/T modes are now displayed correctly.
    • AVAPS settings are now displayed correctly, including target tidal volume.
    • Backup breath settings are now imported and displayed.
    • The settings for CPAP-Check and Auto-Trial modes are now displayed correctly.
  • [fix] Fix the reported duration for extremely long sessions on DreamStation CPAP and BiPAP devices
  • [new] Additional Philips Respironics devices tested and fully supported:
    • DreamStation Auto CPAP (500X120)
  • [fix] Fix regression in Welcome page pressure display
  • [fix] Several crashes have been fixed.

Changes and fixes in OSCAR v1.1.0-beta-2
Portions of OSCAR are © 2019-2020 by The OSCAR Team

  • [new] Add preliminary support for Viatom/Wellue pulse oximeters
  • [new] Add support for Dreem hypnograms
  • [new] Extensive reorganization of the ResMed loader to facilitate understanding and future improvements
  • [new] Additional Philips Respironics devices tested and fully supported:
    • DreamStation Auto CPAP (500X130)
  • [new] Add a menu option to create a zip from an SD card
  • [new] Ask where to save screenshots
  • [new] Alert users when unexpected Philips Respironics data is encountered during import
  • [fix] Add support for CPAP mode on DreamStation BiPAP S/T and AVAPS
  • [fix] Pinch-to-zoom now works as expected
  • [fix] The Philips Respironics loader now respects the "ignore old sessions" preference
  • [fix] Improved handling of discontinuous Philips Respironics data
  • [fix] Improved import of Philips Respironics flex and humidification settings
  • [fix] Fix incorrect display of tube diameter on some Philips Respironics devices
  • [fix] Fix missing "Bi-Flex" label for bi-level DreamStations
  • [fix] Fix crashes in ZEO loader
  • [fix] Fix several memory leaks

Changes and fixes in OSCAR v1.1.0-beta-1
Portions of OSCAR are © 2019 by The OSCAR Team

  • [new] Extensive overhaul of the Philips Respironics System One importer, resolving all previously reported issues.
  • [new] The following Philips Respironics devices are now tested and fully supported:
    • REMstar Plus (System One) (251P),
    • REMstar Pro (System One) (450P, 451P),
    • REMstar Auto (System One) (550P, 551P),
    • BiPAP Auto (System One) (750P),
    • BiPAP AutoSV Advanced System One (950P, 951P),
    • REMstar Pro (System One 60 Series) (460P, 461P),
    • REMstar Auto (System One 60 Series) (560P, 561P, 562P, 560PBT),
    • BiPAP Pro (System One 60 Series) (660P),
    • BiPAP Auto (System One 60 Series) (760P),
    • BiPAP autoSV Advanced (System One 60 Series) (960P, 961P),
    • BiPAP autoSV Advanced 30 (System One 60 Series) (960T),
    • BiPAP S/T 30 (System One 60 Series) (1061T),
    • BiPAP AVAPS 30 (System One 60 Series) (1160P),
    • DreamStation CPAP (200X110),
    • DreamStation CPAP Pro (400X110, 400X150),
    • DreamStation Go (400G110),
    • DreamStation Auto CPAP (500X110, 500X150),
    • DreamStation Go Auto (500G110, 502G150),
    • DreamStation BiPAP Pro (600X110),
    • DreamStation Auto BiPAP (700X110),
    • DreamStation BiPAP autoSV (900X110, 900X120),
    • DreamStation BiPAP S/T 30 (1030X110),
    • DreamStation BiPAP S/T 30 with AAM (1030X150),
    • DreamStation BiPAP AVAPS 30 (1130X110),
    • DreamStation BiPAP AVAPS 30 AE (1131X150)
    • Note: The settings for PC, S, and S/T modes are still displayed incorrectly.
  • [new] Update translation files and add new languages
  • [new] Allow user to reset graph order on the Daily page to Standard (for CPAP and APAP) or Advanced order (for ASV and AVAPS modes)
  • [new] Add preference setting to include the serial number on machine settings list
  • [fix] Place date, time, and Oscar version information in report footers
  • [fix] Update identification of ResMed S9 devices on the Welcome page
  • [fix] Correct formatting of event number in the Daily Events tab
  • [fix] Correct timezone offset for SomnoPose imports
  • [fix] Show a progress bar when setting the Overview range to a large number of days
  • [fix] Make session bars on the Daily page clearer by using a better color
  • [fix] Improve list of devices on the Statistics page
  • [fix] Report Pressure when IPAP data is missing
  • [fix] Implement Refresh button on the Profile page

Changes and fixes in OSCAR v1.1.0-testing-4
Portions of OSCAR are © 2019 by The OSCAR Team

  • [new] Windows installers support Oscar, Oscar 32-bit, Oscar (test) and Oscar 32-bit (test)
  • [fix] Release builds use a Settings key of OSCAR, Test builds use OSCAR-test, and Branch builds use OSCAR-branch. Default data directories are similarly named.
  • [fix] Overview chart problems when both CPAP and Oximetry data were included fixed
  • [fix] Translations updated for all languages. Spanish (Mexico) and Norwegian languages added
  • [fix] All languages now available on Mac
  • [fix] Date bar on the bottom of the Daily graph now in the local time when no line cursor displayed, and formatting improved
  • [fix] 100% zoom now works on the Overview page
  • [fix] View/Reset Graphs now enables all graphs and all event flags
  • [fix] Calendar date now formatted per national settings
  • [fix] Toggle buttons on the Daily page's session list and event and chart pulldowns changed to checkboxes

Changes and fixes in OSCAR v1.1.0-testing-3
NOTE: Translations have NOT yet been updated for these changes
Portions of OSCAR are © 2019 by The OSCAR Team

  • [new] DreamStation BiPAP S/T and AVAPS ventilators (1030X and 1130X) are now supported. The settings aren't yet displayed correctly, but therapy events and graphs should now display properly.
  • [new] View/Reset Graphs organizes graphs in their original order
  • [fix] Format dates for the national region as reported by the operating system
  • [fix] Improve progress bar for the Statistics cache update
  • [fix] Correct calculations of seven-day AHI and leak rate on the Welcome page
  • [fix] Clarify AHI and hours labels on Records tab of the right sidebar
  • [fix] Correct import error resulting in invalid elapsed times and impossibly high AHI values

Changes and fixes in OSCAR v1.1.0-testing-2
NOTE: Translations have NOT yet been updated for these changes
Portions of OSCAR are © 2019 by The OSCAR Team

  • [new] Show progress bar when the Statistics page has to refresh the cache with more than 180 days in it
  • [new] Romanian language is supported
  • [fix] Show commit id in the title bar for testing and beta builds
  • [fix] Show BMI label and calculated value only when both weight and height are non-zero
  • [fix] Improve BMI display in Daily/Notes
  • [fix] Correct profile dialog to show height is measured in cm, not meters, when units system is metric
  • [fix] Always show event indices on the Daily page even when some event flags are turned off

Changes and fixes in OSCAR v1.1.0-testing-1
NOTE: Translations have NOT yet been updated for these changes
Portions of OSCAR are © 2019 by The OSCAR Team

  • [new] Offer migration if a non-default directory is selected on first use
  • [new] Hold the SHIFT key down when starting OSCAR to use Software Graphics Engine
  • [new] Support for DreamStation BiPAP autoSV (900X) should now be complete
  • [new] Improved DreamStation support
  • [new] Improve oximeter import for CM550D+
  • [new] Disable Dark Mode on Mac
  • [new] Show hours/day constituting compliance on the Statistics page
  • [new] Better order of graphs on the Daily page for newly created profiles
  • [new] Help/System Information shows info about OSCAR, OS, and data location
  • [new] Move pie chart option from Preferences dialog to View menu
  • [new] Hide pie chart when capturing screen
  • [new] The --datadir option now allows fully qualified name on Windows
  • [fix] Fix some oximeter import issues
  • [fix] Use local time rather than UTC in oximeter import
  • [fix] Improve screen capture on Mac
  • [fix] Fix crashes in CPAP data rebuild, purging of a machine
  • [fix] Prevent crash if taking a screenshot before a profile is opened
  • [fix] Increase mask vent ranges
  • [fix] Correct session bar if no sessions are present
  • [fix] Correct months shown on the Statistics page monthly view
  • [fix] Compute compliance on the Statistics page based on total days, not days used
  • [fix] Paginate the Statistics report when printing
  • [fix] Fix "phantom date" (12/31/1969) on some ResMed imports
  • [fix] Default font substituted when a specified font is not valid
  • [fix] Change Preferences measurement units choice to Metric or English
  • [fix] Improve display of cmH2O numbers
  • [fix] Show graphics engine in title bar correctly
  • [fix] Adjust the size of the Preferences dialog to fit smaller screens
  • [fix] Label climate control as manual or auto correctly on ResMed import
  • [fix] Move less useful information from title bar to Help/System Information
  • [fix] Change "Prescription Settings" to "Machine Settings" message
  • [fix] Improve icons, especially smaller ones
  • [fix] Correct Mac menu issues
  • [fix] Improve messages in debug pane
  • [fix] Re-organize build instructions and other cleanup

Changes and fixes in OSCAR v1.0.1-r-1
Portions of OSCAR are © 2019 by The OSCAR Team

  • Disable multitasking to avoid a crash during pre-loading of summaries

Changes and fixes in OSCAR v1.0.0-beta-9
Portions of OSCAR are © 2019 by The OSCAR Team

  • Picked up the latest translation files
  • Changed logo to big O with text
  • Clean up odds and ends

Changes and fixes in OSCAR v1.0.0-beta-8
Portions of OSCAR are © 2019 by The OSCAR Team

  • Picked up the latest translation files
  • Fixed filename creation for about OSCAR docs
  • Removed display of I:E ratio from the Daily page
  • Changed some Mac build parameters

Changes and fixes in OSCAR v1.0.0-beta-7
Portions of OSCAR are © 2019 by The OSCAR Team

  • Use the Qt language files for button text
  • Use "Maximize" (with frame & menu) instead of "FullScreen"
  • Change Mac target platform to OS-X 10.12 Sierra

Changes and fixes in OSCAR v1.0.0-beta-6
Portions of OSCAR are © 2019 by The OSCAR Team

  • Added a progress bar during file migration
  • Raise the main window upon opening it

Changes and fixes in OSCAR v1.0.0-beta-5
Portions of OSCAR are © 2019 by The OSCAR Team

  • Moved the Help Index to a user-writable location
  • Clarified folder selection and Migration messages

Changes and fixes in OSCAR v1.0.0-beta-4
Portions of OSCAR are © 2019 by The OSCAR Team

  • Added code to migrate an old data directory to a new data directory
  • Removed "-d" startup option and "Change directory" file menu item. Use the "--datadir foldername" startup option to create or change the data directory.
  • Various fixes to tab and button visibility depending on profile and machine data presence.

Changes and fixes in OSCAR v1.0.0-beta-3
Portions of OSCAR are © 2019 by The OSCAR Team and members of the apnea community

  • The Donation and Update features have been removed
  • The Glossary, FAQ, and Wiki features are not yet implemented
  • The Daily page left panel will resize to a smaller size.
  • Oximeter import reports file errors in a message box
  • Fixed date and time problems with Oximeter import
  • Added support for new CMS50D+ firmware version 4.6
  • Reformatted the 'No Data' display on the Statistics page
  • Got the Help browser working - just stubs for now
  • Small fixes to build and debug problems

SleepyHead v1.1.0-unstable-0

  • Released under the GNU General Public License version 3
  • Copyright © 2011-2018 Mark Watkins
OSCAR-code-v1.3.1/OSCAR_QT.pro000066400000000000000000000006031417327530600154650ustar00rootroot00000000000000lessThan(QT_MAJOR_VERSION,5) { error("You need Qt 5.7 or newer to build OSCAR"); } if (equals(QT_MAJOR_VERSION,5)) { lessThan(QT_MINOR_VERSION,9) { message("You need Qt 5.9 to build OSCAR with Help Pages") } lessThan(QT_MINOR_VERSION,7) { error("You need Qt 5.7 or newer to build OSCAR"); } } TEMPLATE = subdirs SUBDIRS += oscar CONFIG += ordered OSCAR-code-v1.3.1/README000066400000000000000000000040571417327530600143570ustar00rootroot00000000000000OpenSource CPAP Analysis Reporter - OSCAR OSCAR is a derivative of SleepyHead version 1.1.0, created when that was abandoned by Mark Watkins. SleepyHead was a cross platform, opensource sleep tracking program for reviewing CPAP and Oximetry data, which are devices used in the treatment of Sleep Disorders like Obstructive Sleep Apnea. It was released under the GPL version 3 license. See the file COPYING for those details. SleepyHead was written by Mark Watkins (aka Jedimark), an Australian software developer afflicted with sleep apnea. SleepyHead was copyright (C) 2011-2018 by Mark Watkins Portions of OSCAR are copyright (c) 2019-2022 The OSCAR Team Minimum Requirements: ------------------- All systems need a C++ compiler and linker and the QT platform download. Windows Building: See Building/Windows/BUILD-WIN.md ----------------- MacOS Building: See Building/MacOS/BUILD-mac.md --------------- Linux Building: See Building/Linux/BUILD-Linux.md --------------- Software Licensing Information ------------------------------ OSCAR is released under the GNU GPL v3 License. Please see below for a note on giving correct attribution in redistribution of derivatives. It is built using Qt SDK (Open Source Edition), available from https://qt.io. Redistribution of derivatives ( a note added by Mark Watkins ) ----------------------------- Mark Watkins created this software to help lessen the exploitation of others. Seeing his work being used to exploit others is incredibly un-motivational, and incredibly disrespectful of all the work he put into this project. If you plan on reselling any derivatives of SleepyHead, I specifically request that you give due credit and link back, mentioning clearly in your advertising material, software installer and about screens that your derivative "is based on the free and open-source software SleepyHead available from http://sleepyhead.jedimark.net, developed and copyright by Mark Watkins (C) 2011-2018." It is not enough to reference that your derivative "is based on GPL software". OSCAR-code-v1.3.1/Translations/000077500000000000000000000000001417327530600161525ustar00rootroot00000000000000OSCAR-code-v1.3.1/Translations/Afrikaans.af.ts000066400000000000000000014714071417327530600210240ustar00rootroot00000000000000 AboutDialog &About R&akende Release Notes Vrystelling Notas Credits Erkenning GPL License GPL Lisensie Close Maak Toe Show data folder Vertoon data vouer About OSCAR %1 Ranende OSCAR %1 Sorry, could not locate About file. Jammer, kon nie Rakende lêer opspoor nie. Sorry, could not locate Credits file. Jammer, kon nie Krediete lêer opspoor nie. Sorry, could not locate Release Notes. Jammer, kon nie Vrystellingnotas opspoor nie. OSCAR %1 OSCAR %1 Important: Belangrik: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Omdat hierdie 'n voor-vrystelling weergawe is, word dit aanbeveel <b>dat u 'n kopie van u data maak</b> voordat u voortgaan. Om later te probeer terugrol, kan verkeerdloop. To see if the license text is available in your language, see %1. Om te sien of die lisensie beskikbaar is in u taal, sien %1. CMS50F37Loader Could not find the oximeter file: Kon nie die oximeter lêer vind nie: Could not open the oximeter file: Kon nie die oximeter lêer oopmaak nie: CMS50Loader Could not get data transmission from oximeter. Nie in staat om data van die oximeter te ontvang nie. Please ensure you select 'upload' from the oximeter devices menu. Kies asb "upload" in dei oximeter toestelle kieskaart. Could not find the oximeter file: Kon nie die oximeter lêer vind nie: Could not open the oximeter file: Kon nie die oximeter lêer oopmaak nie: CheckUpdates Checking for newer OSCAR versions Besig om te soek vir nuwer OSCAR weergawes Daily Go to the previous day Gaan na vorige dag Show or hide the calender Wys of verwyder kalender Go to the next day Gaan na die volgende dag Go to the most recent day with data records Gaan na mees onlangse dag met data inskrywings Events Gebeurtenisse View Size Kyk grootte Notes Notas Journal Joernaal B B u u Color Kleur Small Klein Medium Middelmatig Big Groot Zombie Zombie I'm feeling ... Ek voel ... Weight Massa If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Indien lengte groter as nul is in die Voorkeure, sal die keuse van massa hier die LiggaamsMassaIndeks (BMI) waarde vertoon Awesome Uitstekend B.M.I. B.M.I. Bookmarks Boekmerke Add Bookmark Voeg Boekmerk by Starts Begin Remove Bookmark Verwyder Boekmerk Flags Merkers Graphs Grafiek Show/hide available graphs. Wys/verberg beskikbare grafieke. Details Details Breakdown Afbraak events gebeurtenisse UF1 UF1 UF2 UF2 Time at Pressure Tyd teen Druk No %1 events are recorded this day Geen %1 gebeurtenisse hierdie dag opgeneem nie %1 event %1 gebeurtenis %1 events %1 gebeurtenisse Session Start Times Sessie Begintye Session End Times Sessie Eindtye Session Information Sessie Inligting Oximetry Sessions Oximeter Sessies Duration Tydsduur Click to %1 this session. Kliek om die sessie te %1. disable afskakel enable in staat te stel %1 Session #%2 %1 Sessie #%2 %1h %2m %3s %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Neem kennis:</b> Alle verstellings hieronder is gebaseer op die aanname dat niks verander het van die vorige dae af nie. (Mode and Pressure settings missing; yesterday's shown.) (Mode en Druk instellings nie teenwoordig nie; vorige dag se waardes vertoon.) 99.5% 99.5% 10 of 10 Event Types 10 van 10 Gebeurtenis Tipes This CPAP machine does NOT record detailed data Hierdie CPCP masjien stoor NIE detail data nie No data is available for this day. Geen data is beskikbaar vir hierdie dag nie. This bookmark is in a currently disabled area.. Hierdie boekmerk is in 'n huidige afgeskakelde area.. 10 of 10 Graphs 10 van 10 Grafieke CPAP Sessions CPAP Sessies Sleep Stage Sessions Slaapvlak Sessies Position Sensor Sessions Posisie Sensor Sessies Unknown Session Onbekende Sessie Machine Settings Masjien Instellings Model %1 - %2 Model %1 - %2 PAP Mode: %1 PAP Mode: %1 This day just contains summary data, only limited information is available. Hierdie dag bevat opsommende data, slegs beperkte informasie is beskikbaar. Total ramp time Totale stygtyd Time outside of ramp Tyd buite styg Start Begin End Einde Unable to display Pie Chart on this system Kan nie die Sirkelgrafiek op hierdie stelsel vertoon nie Sorry, this machine only provides compliance data. Jammer, hierdie masjien verskaf slegs voldoenigsdata. "Nothing's here!" "Hier is niks!" Oximeter Information Oximeter Informasie SpO2 Desaturations Baie slegte vertaling!!! SpO2 Desaturasies Pulse Change events Polsslag Verandering Gebeurtenisse SpO2 Baseline Used SpO2 Basislyn Gebruik Statistics Staistieke Total time in apnea Totale tyd in apneë Time over leak redline Tyd oor lek rooilyn Event Breakdown Gebeurtenis Afbraak Sessions all off! Sessies is almal af! Sessions exist for this day but are switched off. Sessies bestaan vir hierdie dag maar is afgeskakel. Impossibly short session Onmoontlike kort sessie Zero hours?? Nul ure?? BRICK :( Moontlik verkeerde vertaling BAKSTEEN :( Complain to your Equipment Provider! Bring dit onder die aandag van die verskaffer van u toerusting! Pick a Colour Kies 'n Kleur Bookmark at %1 Boekmerk by %1 ExportCSV Export as CSV Stoor as CSV Dates: Datums: Resolution: Resolusie: Details Details Sessions Sessies Daily Daagliks Filename: Lêernaam: Cancel Kanselleer Export Voer uit Start: Begin: End: Einde: Quick Range: Vinnige gedeelte: Most Recent Day Mees Onlangse Dag Last Week Laasweek Last Fortnight Laaste Twee Weke Last Month Laaste Maand Last 6 Months Laaste 6 Maande Last Year Laaste Jaar Everything Alles Custom Eiedoelige Details_ Details_ Sessions_ Sessies_ Summary_ Opsomming_ Select file to export to Kies lêer om na uit te voer CSV Files (*.csv) CSV Lêers (*.csv) DateTime DatumTyd Session Sessie Event Gebeurtenis Data/Duration Data/Tydsduur Date Datum Session Count Sessie Aantal Start Begin End Einde Total Time Totale Tyd AHI AHI Count Aantal FPIconLoader Import Error Intrek Fout This Machine Record cannot be imported in this profile. Hierdie Masjien Rekord kan nie in hierdie profiel ingetrek word nie. The Day records overlap with already existing content. Die Dag rekords oorvleuel bestaande inhoud. Help Hide this message Verberg hierdie boodskap Search Topic: Soek Onderwerp: Help Files are not yet available for %1 and will display in %2. Hulp boodskappe is nog nie beskikbaar vir %1 en sal vertoon in %2. Help files do not appear to be present. Dit lyk nie of hulplêers bskikbaar is nie. HelpEngine did not set up correctly Dei Hulpengine het nie reg opgestel nie HelpEngine could not register documentation correctly. Die Hulpengine het nie reg geregistreer nie. Contents Inhoud Index Indeks Search Soek No documentation available Please wait a bit.. Indexing still in progress Wag asseblief... Indeksering is in proses No Nee %1 result(s) for "%2" %1 resultaat(e) vir "%2" clear MD300W1Loader Could not find the oximeter file: Kon nie die oximeter lêer vind nie: Could not open the oximeter file: Kon nie die oximeter lêer oopmaak nie: MainWindow &Statistics &Statistieke Report Mode Verslag Mode Standard Standaard Monthly Maandelikse Date Range Datum Bestek Statistics Statistieke Daily Daagliks Overview Oorsig Oximetry Oximetrie Import Intrek Help Help &File &Lêer &View &Vertoon &Reset Graphs He&rstel Grafieke &Help &Help Troubleshooting Foutsporing &Data &Data &Advanced &Gevorderd Show Daily view Vertoon Daaglikse skerm Show Overview view Vertoon Oorsig &Maximize Toggle &Maksimiseer Skakel Maximize window Maksimiseer Venster Reset Graph &Heights Herstel Grafiek &Hoogte Reset sizes of graphs Herstel groottes van grafieke Show Right Sidebar Vertoon Regter Sidebar Show Statistics view Vertoon Statistieke Import &Dreem Data Voer &Dreem Data In Purge Current Selected Day Vee Huidige Gekose Dag Uit &CPAP &CPAP &Oximetry &Oximetrie &Sleep Stage &Slaap Fase &Position &Posisie &All except Notes &Alles behalwe Notas All including &Notes Alles insluitend &Notas Show &Line Cursor Vertoon &Lyn Merker Show Daily Left Sidebar Vertoon Daaglikse Linker Sidebar Show Daily Calendar Vertoon Daaglikse Kalender Create zip of CPAP data card Maak zip van CPAP data kaart Create zip of OSCAR diagnostic logs Skep zip van OSCAR disgnostiese lyste Create zip of all OSCAR data Maak zip van alle OSCAR data Report an Issue Rapporteer 'n Probleem System Information Stelsel Informasie Show &Pie Chart Vertoon &Pie Grafiek Show Pie Chart on Daily page Vertoon Pie Grafiek op Daaglikse bladsy Standard graph order, good for CPAP, APAP, Bi-Level Standaard grafiek volgorde, goed vir CPAP, APAP, Bi-Level Advanced Gevorderd Advanced graph order, good for ASV, AVAPS Gevorderde grafiek volgorde, goed vir ASV, AVAPS Show Personal Data Vertoon Persoonlike Data Check For &Updates Soek Vir &Opdaterings Rebuild CPAP Data Herbou CPAP Data &Preferences &Voorkeure &Profiles &Profiele &About OSCAR &Rakende OSCAR Show Performance Information Vertoon Verrigting Informasie CSV Export Wizard CSV Uitvoer Helper Export for Review Uitvoer vir Hersiening E&xit E&xit Exit Gaan Uit View &Daily Besigtig &Daagliks View &Overview Vertoon &Oorsig View &Welcome Use &AntiAliasing Gebruik &AntiAliasering Show Debug Pane Vertoon Ontfouting Paneel Take &Screenshot Neem &Skermskoot O&ximetry Wizard O&ximetrie Helper Print &Report D&ruk Verslag &Edit Profile V&erander Profiel Import &Viatom/Wellue Data Laai t &Viatom/Wellue Data Daily Calendar Daaglikse Kalender Backup &Journal Rugsteun &Joernaal Online Users &Guide Aanlyn &Gebruikersgids &Frequently Asked Questions Gereelde &Vrae &Automatic Oximetry Cleanup &Outomatiese Oximetry Skoonmaak Change &User Verander &Gebruiker Purge &Current Selected Day Wis &Huidige Gekose Day Right &Sidebar Regterkantste &Kantlyn Daily Sidebar Daaglikse Kantlyn View S&tatistics Vertoon &Statistieke Navigation Navigasie Bookmarks Boekmerke Records Rekords Exp&ort Data V&oer Data Uit Profiles Profiele Purge Oximetry Data Wis Oximetrie Data Purge ALL Machine Data Vernietig ALLE Masjien Data &Import CPAP Card Data Voer CPAP Kaart Data &In View Statistics Vertoon Statistieke Import &ZEO Data Voer &ZEO Data In Import RemStar &MSeries Data Voer RemStar &MReeks Data In Sleep Disorder Terms &Glossary Slaapafwyking &Oorsig van Terme Change &Language Verander &Taal Change &Data Folder Verander &Data Vouer Import &Somnopose Data Voer &Somnopose Data In Current Days Huidige Dae Welcome Welkom &About &Rakende Please wait, importing from backup folder(s)... Wag asseblief, besig om data van rugsteun vouer af te laai... Import Problem Invoer Probleem Please insert your CPAP data card... Sit asseblief u CPAP data kaart in... Access to Import has been blocked while recalculations are in progress. Toegang tot Invoer is geblok terwyl berekeninge uitgevoer word. CPAP Data Located CPAP Data Gestoor Import Reminder Invoer Herinnering Importing Data Voer Data In The User's Guide will open in your default browser Die Gebruikersgids sal oopmaak in u bestek browser The Glossary will open in your default browser There was a problem opening %1 Data File: %2 Daar was 'nprobleem om %1 Data Lêer: %2 oop te maak %1 Data Import of %2 file(s) complete %1 Data Invoer van %2 (s) lêers voltooi %1 Import Partial Success %1 Invoer Gedeeltelik Suksesvol %1 Data Import complete %1 Data Invoer voltooi Export review is not yet implemented Uitvoer oorsig is nog nie geimplementeer nie Reporting issues is not yet implemented Rapportering is nog nie geimplementeer nie If you can read this, the restart command didn't work. You will have to do it yourself manually. Indien u hierdie kan lees, het die herbegin opdrag nie gewerk nie. U sal dit self handrolies moet uitvoer. Are you sure you want to rebuild all CPAP data for the following machine: Is u seker dat u al die CPAP data vir die volgende masjien wil herbou: Please note, that this could result in loss of data if OSCAR's backups have been disabled. Neem asseblief kennis dat dit kan lei tot verlies in data as OSCAR se rugsteun afgeskakel is. For some reason, OSCAR does not have any backups for the following machine: Vir een of ander rede het OSCAR geen rugsteun vir die volgende masjien nie: You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> U is op die punt om OSCAR se databasis vir die volgende masjien <font size=+2>te vernietig</font>:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: 'n Lêer toegang fout verhoed dat die proses uitgevoer word; u sal self die lêer moet uitvee: No help is available. Geen hulp is beskikbaar nie. %1's Journal %1 se Joernaal Choose where to save journal Kies waar om u joernaal te stoor XML Files (*.xml) XML Lêers (*.xml) Help Browser Hulp Leser Loading profile "%1" Laai profiel "%1" %1 (Profile: %2) %1 (Profiel: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Onthou asseblief om die hoogste lêer of skyf letter vir u data kaart te kies en nie 'n vouer daarbinne nie. Find your CPAP data card Vind U CPAP data kaart Please open a profile first. Maak asb eers 'n profiel oop. Check for updates not implemented Soek vir opdaterings wat nie implementeer is nie Choose where to save screenshot Kies waar om die skermskoot te stoor Image files (*.png) Beeld lêers (*.png) Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Gegewe dat u <i>u <b>eie</b> rugsteun vir al u CPAP data gemaak het</i>, kan u steeds hierdie aksie voltooi, maar u sal handmatig van u rugsteun af die data moet herstel. Are you really sure you want to do this? Is u regtig seker dat u dit wil doen? Because there are no internal backups to rebuild from, you will have to restore from your own. Omdat daar nie interne rugsteun is om vanaf te herbou nie, sal u moet herskep van u eie af. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Wil u invoer vanaf u eie rugsteun? (u sal geen data sigbaar hê vir hierdie masjien totdat u dit doen nie) Note as a precaution, the backup folder will be left in place. Neem kennis dat as 'n voorkomende maatreël, die rugsteun vouer in plek gelaat word. OSCAR does not have any backups for this machine! OSCAR het geen rugsteun vir hierdie masjien nie! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Behalwe as u <i>u <b>eie</b> rugsteun van AL u data vir hierdie masjien gemaak het</i>, <font size=+2>sal u hierdie masjien se data <b>permanent verloor</b>!</font> Are you <b>absolutely sure</b> you want to proceed? Is u <b>doodseker</b> dat u wil voortgaan? Are you sure you want to delete oximetry data for %1 Is u seker dat u die oximeter data wil uitvee vir %1 <b>Please be aware you can not undo this operation!</b> <b>Wees asseblief bewus dat u nie hierdie aksie kan omkeer nie!</b> Select the day with valid oximetry data in daily view first. Kies eers die dag met geldige oximeter data in die daaglikse vertoon. Would you like to zip this card? Wil u graag hierdie kaart zip? Choose where to save zip Kies waar om zip te stoor ZIP files (*.zip) ZIP lêers (*.zip) Creating zip... Skep zip... Calculating size... OSCAR Information OSCAR Informasie Imported %1 CPAP session(s) from %2 %1 CPAP sessie(s) ingevoer van %2 Import Success Suksesvolle Invoer Already up to date with CPAP data at %1 Alreeds op datum met CPAP data by %1 Up to date Op datum Couldn't find any valid Machine Data at %1 Kon geen geldige Masjien Data vind by %1 nie Choose a folder Kies 'n vouer No profile has been selected for Import. Geen profiel is gekies vir Invoer nie. Import is already running in the background. Reeds besig om in die agtergrond in te voer. A %1 file structure for a %2 was located at: 'n %1 vouer struktuur vir 'n %2 is gevind by: A %1 file structure was located at: 'n %1 vouer struktuur is gevind by: Would you like to import from this location? Wil u invoer van hierdie ligging af? Specify Spesifiseer Access to Preferences has been blocked until recalculation completes. Toegang tot Verrigting is geblok todat die herberekening voltooi is. There was an error saving screenshot to file "%1" Daar was 'n fout tydens stoor van die skermskoot na lêer "%1" Screenshot saved to file "%1" Skermskoot gestoor na lêer "%1" The FAQ is not yet implemented Die FAQ is nog nie geimplementeer nie There was a problem opening MSeries block File: Daar was 'n probleem met die oopmaak van MSeries blok Lêer: MSeries Import complete MSeries Invoer voltooi MinMaxWidget Auto-Fit Auto Pas Defaults Verstekwaardes Override Oorskryf The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Die Y-As skalering mode, 'Auto-Pas' vir outomatiese skalering, 'Verstek' vir waardes per vervaardiger en "Oorskryf' om u eie te kies. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Die Minimum Y-as waarde. Hierdie kan 'n negatiewe getal wees as u so verkies. The Maximum Y-Axis value.. Must be greater than Minimum to work. Die Maksimum Y-As waarde. Moet groter wees as die Minimum om te werk. Scaling Mode Skalering Mode This button resets the Min and Max to match the Auto-Fit Hierdie herstel die Min en Maks om soos die Auto Pas te wees NewProfile Edit User Profile Redigeer Gebruiker Profiel I agree to all the conditions above. Ek stem saam met al die voorwaardes hierbo. User Information Gebruikersinligting User Name Naam van Gebruiker Password Protect Profile Beskerm Profiel met Wagwoord Password Wagwoord ...twice... ...tweekeer... Locale Settings Ligging Opstellings Country Land TimeZone Tydzone about:blank about:blank Very weak password protection and not recommended if security is required. Baie swak wagwoordbeskerming en word nie aanbeveel as sekuriteit benodig word nie. DST Zone DST Zone Personal Information (for reports) Persoonlike Inligting (vir verslae) First Name Naam Last Name Van It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Dis aanvaarbaar om hierdie te los, maar u benaderde ouderdom word benodig om die akkuraatheid van sekere berekeninge te verbeter. D.O.B. Geb.dat. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Biologiese (geboorte) geslag word somtyds benodig om die akkuraatheid van 'n paar berekeninge te verbeter, maar voel vry om dit oop te los.</p></body></html> Gender Geslag Male Manlik Female Vroulik Height Lengte Metric English Engels Contact Information Kontak Inligting Address Adres Email Epos Phone Telefoon CPAP Treatment Information CPAP Behandeling Inligting Date Diagnosed Datum Gediagnoseer Untreated AHI Onbehandelde AHI CPAP Mode CPAP Mode CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure RX Druk Doctors / Clinic Information Dokter / Kliniek Inligting Doctors Name Dokter se Naam Practice Name Praktyk Naam Patient ID Pasiënt ID &Cancel &Kanselleer &Back &Terug &Next &Volgende Select Country Kies Land This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Hierdie sagteware is ontwerp om u te help om die data deur u CPAP masjiene en verwante toerusting te hersien. PLEASE READ CAREFULLY LEES ASSEBLIEF NOUKEURIG Accuracy of any data displayed is not and can not be guaranteed. Akkuraatheid van enige data vertoon is nie en kan nie gewaarborg word nie. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Enige verslae gegenereer is vir PERSOONLIKE GEBRUIK ALLENLIK en nie OP ENIGE MANIER geskik vir voldoening of mediese diagnose doeleindes nie. Use of this software is entirely at your own risk. Gebruik van hierdie sagteware is geheel en al op u eie risiko. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR is kopiereg &copy;2011-2018 Mark Watkins en gedeeltes &copy;2019-2022 Die OSCAR Span Welcome to the Open Source CPAP Analysis Reporter Welkom by die Open Source CPAP Analysis Reporter OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR is gratis vrygestel onder die <a href='qrc:/COPYING'>GNU Public License v3</a>, het geen waarborg nie, en sonder enige stelling van geskiktheid vir enige doel. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR is slegs bedoel om data te besigtig en nie as 'n vervanging vir bevoegde mediese leidingvan u dokter af nie. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Die outeurs sal nie verantwoordelik gehou word vir <u>enine iets</u> rakende die gebruik of miskruik van hierdie sagteware nie. Please provide a username for this profile Verskaf asseblief 'n gebruikersnaam vir hierdie profiel Passwords don't match Wagwoorde is nie dieselfde nie Profile Changes Profiel Veranderings Accept and save this information? Aanvaar en stoor hierdie inligting? &Finish &Maak klaar &Close this window &Maak hierdie venster toe Overview Range: Bereik: Last Week Laaste Week Last Two Weeks Laaste Twee Weke Last Month Laaste Maand Last Two Months Laaste Twee Maande Last Three Months Laaste Drie Maande Last 6 Months Laaste 6 Maande Last Year Laaste Jaar Everything Alles Custom Eie Snapshot Skermskoot Start: Begin: End: Einde: Reset view to selected date range Herstel vertoon na gekose data bereik Toggle Graph Visibility Skakel Grafiek Sigbaarheid Drop down to see list of graphs to switch on/off. Breek af om lys van grafieke te sien om aan/af te skakel. Graphs Grafiek Respiratory Disturbance Index Respiratoriese Versteuring Indeks Apnea Hypopnea Index Apneë Hypopneë Indeks Usage Gebruik Usage (hours) Gebruik (ure) Session Times Sessie Tye Total Time in Apnea Totale Tyd in Apneë Total Time in Apnea (Minutes) Totale Tyd in Apneë (Minute) Body Mass Index Liggaam Massa Indeks How you felt (0-10) Hoe u gevoel het (0-10) 10 of 10 Charts 10 van 10 Grafieke Show all graphs Vertoon alle grafieke Hide all graphs Steek alle grafieke weg OximeterImport Oximeter Import Wizard Oximeter Invoer Helper Skip this page next time. Gaan volgende keer verby hierdie bladsy. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Neem asseblief kennis: </span><span style=" font-style:italic;">Kies eers u korrekte oksimeter tipe in die kiskaart hieronder.</span></p></body></html> Where would you like to import from? Vanwaar vir u invoer? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">Kies EERS u oksimeter uit hierdie groepe:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. CMS50E/F gebruikers, wanneer direk ingetrek, moet asseblief nie oplaai kies op die toerustingvoordat OSCAR daarvoor vra nie. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Indien gekies, sal OSCAR outomaties u CMS50 se interne klok herstel na u rekenaar se interne klok toe.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Hierdie opsie sal die ingevoerde sessie op u oximeter uitvee nadat dit ingevoer is. </p><p>Gebruik met oorleg, omdat u nie die data sal kan terugkry indien iets verkeerd gaan voordat OSCAR u sessie gestoor het nie.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Hierdei opsie laat u toe om in te voer (deur kabel) vanaf u oximeter se interne opnames.</p><p>Nadat u hierdie opsie gekies het, sal ou Contec oximeters vereis dat u die toestel se kieslys gebruik om die oplaaiproses te begin.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Indien u nie omgee om aan 'n werkende rekenaar vas te wees oornag nie, verskaf hierdie opsie 'n bruikbare plethysomogram grafiek, wat 'n aanduiding gee van hartritme, sowel as die normale oximeter lesings.</p></body></html> Record attached to computer overnight (provides plethysomogram) Neem op terwyl gekoppel aan rekenaar oornag (verskaf die plethysomogram) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Hierdie opsie laat u toe om in te voer vanaf data lêers wat deur u Oximeter se sagteware, soos SpO2Review, geskep is.</p></body></html> Import from a datafile saved by another program, like SpO2Review Voer in van 'n data lêer af wat deur 'n ander program gestoor is, soos SpO2Review Please connect your oximeter device Koppel asseblief u oximeter toestel If you can read this, you likely have your oximeter type set wrong in preferences. Indien u hierdie sien, het u waarskynlik verkeerde oximeter in u instellings opgestel. Please connect your oximeter device, turn it on, and enter the menu Koppel asseblief u oximeter toestel, skakel dit aan en gaan in die kieslys in Press Start to commence recording Druk Begin om op te neem Show Live Graphs Vertoon Deurlopende Grafieke Duration Tydsduur Pulse Rate Polstempo Multiple Sessions Detected Veelvoudige Sessies Bespeur Start Time Begintyd Details Details Import Completed. When did the recording start? Invoer voltooi. Wanneer is die opname begin? Oximeter Starting time Oximeter Begintyd I want to use the time reported by my oximeter's built in clock. Ek wil die tyd gebruik wat gerapporteer word deur my oximeter se ingeboude klok. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Ek het hierdie opname begin op (of naby) dieselfde tyd as 'n sessie op my CPAP masjien. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Nota: Sinkronisasie met 'n CPAP sessie begintyd sal altyd meer akkuraat wees.</p></body></html> Choose CPAP session to sync to: Kies CPAP sessie om mee te sinkroniseer: You can manually adjust the time here if required: U ma handmatig die tyd hier wysig indien nodig: HH:mm:ssap HH:mm:ssap &Cancel &Kanselleer &Information Page &Informasie Bladsy CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Stel toestel datum/tyd <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Merk om die toestel identifisering tydens die volgende invoer op te dateer, wat nuttig is vir diegene wat meer as een oximeter het.</p></body></html> Set device identifier Stel toestel identifikasie Erase session after successful upload Vee sessie uit na suksesvolle oplaai Import directly from a recording on a device Voer direk in vanaf 'n opname op 'n toestel <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Herinnering vir CPAP gebruikers: </span><span style=" color:#fb0000;">Het u onthou om u CPAP sessies eers in te voer?<br/></span>Indien u vergeet, sal u nie 'n geldige tyd hê om hierdie oximeter sessie mee te sinkroniseer nie.<br/>Probeer altyd om albei op dieselfde tyd te begin om 'n goeie sinkronisasie tussen toestelle te verseker.</p></body></html> Please choose which one you want to import into OSCAR Kies asseblief watter een u in OSCAR wil intrek Day recording (normally would have) started Dag opname (sou normaalweg) begin <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR benodig 'n begintyd om te weet waar om die oximeter sessie te stoor.</p><p>Kies een van die volgende keuses:</p></body></html> &Retry &Probeer weer &Choose Session &Kies Sessie &End Recording &Beeindig Opname &Sync and Save &Sinkroniseer en Stoor &Save and Finish &Stoor en Maak Klaar &Start &Begin Scanning for compatible oximeters Soek vir versoenbare oximeters Could not detect any connected oximeter devices. Kon nie enige gekoppelde oximeter toestelle opspoor nie. Connecting to %1 Oximeter Koppel na %1 Oximeter Renaming this oximeter from '%1' to '%2' Herbenoem hiereie oximeter van '%1' na '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Oximeetr naam verskil... As u net een het en dit deel tussen profiele, stel die naam dieselfde op beide profiele. "%1", session %2 "%1", sessie %2 Nothing to import Niks om in te voer nie Your oximeter did not have any valid sessions. U oximeter het geen geldige sessies gehad nie. Close Maak toe Waiting for %1 to start Wag vir %1 om te begin Waiting for the device to start the upload process... Wag vir die toestel om die invoer proses te begin... Select upload option on %1 Kies die oplaai opsie op %1 You need to tell your oximeter to begin sending data to the computer. U moet die oximeter vertel om data te begin stuur na die rekenaar toe. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Koppel asb u oximeter, gaan in sy kieskaart in en kies om op te laai om data oordrag te begin... %1 device is uploading data... %1 toestel isbesig om dat op te laai... Please wait until oximeter upload process completes. Do not unplug your oximeter. Wag asseblief totdat dei oximeter oplaai proses voltooi is. Moenie die oximeter ontkoppel nie. Oximeter import completed.. Oximeter invoer voltooi.. Select a valid oximetry data file Kies 'n geldige oximeter dat lêer Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Oximetrie Lêers (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Geen Oksimetrie module kon die gegewe lêer ontleed nie: Live Oximetry Mode Lewendige Oksimetrie Mode Live Oximetry Stopped Lewendige Oksimetrie Gestop Live Oximetry import has been stopped Lewendige Oksimetrie invoer is gestop Oximeter Session %1 Oksimeter Sessie %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR gee u een van die vermoë om oximetrie data teenoor CPAP sessie data te volg, wat waardevolle insig kan gee in die effektiwiteit van CPAP behandeling. Dit sal ook allenstaande werk met u pols oximeter, wat u sooende toelaat om u data wat opgeneem is te stoor, na te gaan en te ondersoek. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR is tans versoenbaar met Contec CMS50D+, CMS50E, CMS50F en CMS50I seriale oximeters.<br/>(Nota: Direkte invoering van bluetooth modelle is <span style=" font-weight:600;">waarskynlik</span> nog nie moontlik nie) If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Indien u probeer om oksimetrie en CPAP data te sinkroniseer, maak asb seker dat u die CPAP sessies reeds ingevoer het voordat u voortgaan! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. U moet seker maak dat die korrekte device drivers gelaai is vir OSCAR om u oximeter op te spoor en data daarvan af te kan lees (bv UART of USB). Vir meer informasie hieroor, %1sien%2. Oximeter not detected Oximeter nie bespeur nie Couldn't access oximeter Kon nie toegang tot oximeter kry nie Starting up... Begin nou... If you can still read this after a few seconds, cancel and try again Indien u steeds hierdie kan lees na 'n paas sekondes, kanselleer en probeer weer Live Import Stopped Lewendige Invoer Gestop %1 session(s) on %2, starting at %3 %1 sessie(s) op %2, beginnende by %3 No CPAP data available on %1 Geen CPAP data beskikbaar op %1 nie Recording... Neem op... Finger not detected Vinger nie bespeur nie I want to use the time my computer recorded for this live oximetry session. Ek wil die tyd wat my rekenaar opgeneem het gebruik vir hierdie reële-tyd oximeter sessie. I need to set the time manually, because my oximeter doesn't have an internal clock. Ek het nodig om die tyd handmatig te stel, omdat my oximeter nie beskik oor 'n interne klok nie. Something went wrong getting session data Iets het fout gegaan tydens sessie data invordering Welcome to the Oximeter Import Wizard Elkom by die Oximeter Invoer Helper Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Pols Oximeters is mediese toestelle wat gebruik word om bloed suurstofvlak versadiging te meet. Gedurende verlengde Apneë gebeurtenisse en abnormale asemhalingspatrone kan bloed suurstof versadigingsvlakke noemenswaardig afneem en kan dui op kwessies wat mediese aandag benodig. You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. U mag dalk wil weet dat ander maatskappye, soos Polux, eenvoudig die Contec CMS50 onder 'n nuwe naam verpak, soos die Polux PO-200, PO-300, PO-400. Hierdie behoort ook te werk. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Dit kan ook lees vanaf ChoiceMMed MD300W1 oximeter .dat lêers. Please remember: Onthou asseblief: Important Notes: Belangrike Notas: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Contec CMS50D+ toestelle het nie 'n interne klok nie en noteer gevolglik nie 'n begintyd nie. Indien u nie 'n CPAP sessie het waaraan 'n opname gekoppel kan word nie, sal u 'n begintyd handmatig moet verskaf nadat die invoer proses voltooi is. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Selfs vir toestelle met 'n interne klok, word dit steeds aanbeveel om die gewoonte aan te kweek om oximeter opnames te begin op dieselfde tyd as CPAP sessies, omdat CPAP interne tydhouding oor tyd kan dryf en nie almal kan maklik herstel word nie. Oximetry Date Datum d/MM/yy h:mm:ss AP d/MM/yy h:mm:ss AP R&eset H&erstel Pulse Pols &Open .spo/R File Maak .spo/R Lêer &Oop Serial &Import Seriale &Invoer &Start Live &Begin Lewendig Serial Port Seriale Poort &Rescan Ports Herlees Poo&rte PreferencesDialog Preferences Voorkeure &Import &Invoer Combine Close Sessions Kombineer Naby Sessies Minutes Minute Multiple sessions closer together than this value will be kept on the same day. Veelvuldige sessien nader aan mekaar as hierdie waarde sal op dieselfde dag gehou word. Ignore Short Sessions Ingoreer Kort Sessies <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessies korter as hierdie sal nie vertoon word nie<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Dag Verdeling Tyd Sessions starting before this time will go to the previous calendar day. Sessies wat voor hierdie tyd begin sal na die vorige kalenderdag toe gaan. Session Storage Options Sessie Stoor Opsies Changing SD Backup compression options doesn't automatically recompress backup data. Deur SD Backup samepersing keuses te verander, word die vorige data nie outomaries saamepers nie. Compress SD Card Backups (slower first import, but makes backups smaller) Pers SD kaart rugsteun saam (stadiger eerste invoer, maar maak rugsteun kleiner) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Beskou dae met gebruik onder hierdie waarde as"onvoldoening". 4 ure word gewoonlik as voldoening beskou. hours ure Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Aktiveer / deaktiveer verbeterings vir eksperimentele gebeurtenisse. Dit laat grensgebeurtenisse opspoor, en op sommige van die masjiene word dit gemis. Hierdie opsie moet geaktiveer word voor data invoer, anders word 'n opruiming vereis. Flow Restriction Vloei Beperking Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Persentasie beperkings in lugvloei vanaf die mediaanwaarde. 'n waarde van 20% werk goed vir die opsporing van apneë. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Eie merking is 'n eksperimentele metode om gebeurtenisse wat deur die masjien gemis word, op te spoor. Hulle is <span style=" text-decoration: underline;">nie</span> ingesluit in die AHI nie.</p></body></html> Duration of airflow restriction Tydsduur van lugvloei beperking s s Event Duration Geburtenis Tydsduur Allow duplicates near machine events. Laat herhaling in die omgewing van masjien gebeurtenisse toe. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Pas die hoeveelheid data aan wat oorweeg word vir elke punt in die AHI/Uur grafiek. Verstekwaarde is 60 minute. Dit word sterk aanbeveel om dit op hierdie waarde te los. minutes minute Reset the counter to zero at beginning of each (time) window. Stel die teller terug na nul aan die begin van elke (tyd) venster. Zero Reset Zero Herstel CPAP Clock Drift CPAP Klok Dryf Do not import sessions older than: Moenie sessies ouer as hierdie invoer nie: Sessions older than this date will not be imported Sessies ouer as hierdie datum sal nie ingevoer word nie dd MMMM yyyy dd MMMM yyyy User definable threshold considered large leak Gebruikers gedefinieerde drempel wat as groot lek beskou word Whether to show the leak redline in the leak graph Welke die rooi lek lyn op die lek grafiek vertoon moet word Search Soek &Oximetry &Oximetrie Show in Event Breakdown Piechart Vertoon in Gebeurtenis Afbraak Sirkelkaart Resync Machine Detected Events (Experimental) Hersinkroniseer Masjien Bespeurde Gebeurtenisse (Eksperimenteel) Percentage drop in oxygen saturation Persentasie afname in suurstofversadiging Pulse Pols Sudden change in Pulse Rate of at least this amount Skielike verandering in Polstempo van ten minste hierdie hoeveelheid bpm bpm Minimum duration of drop in oxygen saturation Minimum tydsduur van daling in suurstofversadiging Minimum duration of pulse change event. Minimum duur van polsverandering gebeurtenis. Small chunks of oximetry data under this amount will be discarded. Klein stukkies oximetrie data onder hierdie hoeveelheid sal ignoreer word. &General Al&gemeen Changes to the following settings needs a restart, but not a recalc. Wysigings aan die volgende instellings het 'n herbegin nodig, maar nie 'n herberekening nie. Preferred Calculation Methods Voorkeur Berekening Metodes Middle Calculations Middelberekenings Upper Percentile Boonste Persentiel Session Splitting Settings Sessie Verdeling Instellings <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Hierdie instelling moet met omsigtigheid gebruik word ...</span> Om dit af te skakel kom met gevolge wat die akkuraatheid van die opsomming van enkele dae betref, aangesien sekere berekeninge net behoorlik werk indien opsomming sessies wat uit individuele dagrekords kom, bymekaar gehou word. </p><p><span style=" font-weight:600;">ResMed gebruikers:</span> Net omdat dit natuurlik vir u en my lyk dat die middel-van-die-dag sessie herbegin moet word in die vorige dag, beteken nie ResMed se data stem saam met ons nie. Die STF.edf opsomming indeks formaat het ernstige swakpunte wat maak dat dit nie 'n goeie idee is nie.</p><p>Hierdie opsie bestaan om diegene wat nie omgee nie, gerus te stel en toe te laat om dit &quot;reg te stel&quot; ongeag die gevolge, maat weet dat dit met 'n koste gepaard gaan. As u u SD-kaart elke aand inhou en minstens een keer per week invoer, sal u dit nie baie dikwels probleme sien nie.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Moenie Opsommingsdae Skei Nie (Waarskuwing: lees die tooltip!) Memory and Startup Options Geheue en Aanvangsopsies Pre-Load all summary data at startup Laai alle opsommende data vooraf <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Hierdie instelling hou die gebruik van golfvorm- en gebeurtenisdata in gebruik om die herhalende dae te bespoedig.</p><p>Dit is nie regtig 'n noodsaaklike opsie nie, aangesien u bedryfstelsel ook voorheen-gebruikte lêers cache.</p><p>Die aanbeveling is om dit af te skakel, tensy u rekenaar 'n groot klomp geheue het.</p></body></html> Keep Waveform/Event data in memory Hou Golfvorm/Gebeurtenis data in geheue <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Beperk enige onbelangrike bevestigingsdialoë tydens invoer.</p></body></html> Import without asking for confirmation Voer in sonder om te vra vir bevestiging General CPAP and Related Settings Algemene CPAP en Verwante Stellings Enable Unknown Events Channels Aktiveer Onbekende Gebeurtenis Kanale AHI Apnea Hypopnea Index AHI RDI Respiratory Disturbance Index RDI AHI/Hour Graph Time Window AHI/Uur Grafiek Tyd Venster Preferred major event index Gewenste groot gebeurtenis indeks Compliance defined as Voldoening gedefinieer as Flag leaks over threshold Merk lekke oor drempel Seconds Sekondes <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Nota: Dit is nie bedoel vir tydsone regstellings nie! Maak seker dat u bedryfstelsel klok en tydsone korrek ingestel is.</p></body></html> Hours Ure Your masks vent rate at 20 cmH2O pressure U masker es lek tempo teen 20 cmH2O druk Your masks vent rate at 4 cmH2O pressure For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Vir konsekwentheid moet ResMed-gebruikers 95% hier gebruik, aangesien dit die enigste waarde is wat op slegs opsommende dae beskikbaar is. Median is recommended for ResMed users. Mediaan word aanbeveel vir ResMed gebruikers. Median Mediaan Weighted Average Geweegde Gemiddeld Normal Average Normale Gemiddeld True Maximum Ware Maksimum 99% Percentile 99% Persentiel Maximum Calcs Maksimum Berekeninge General Settings Algemene Instellings Daily view navigation buttons will skip over days without data records Daaglikse vertoon navigasieknoppies sal oor dae gaan wat nie data rekords het nie Skip over Empty Days Gaan oor Dae Sonder Rekords Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Laat gebruik van verskeie CPU kerne toe waar beskikbaar om prestasie te verbeter. Hoofsaaklik van toepassing op die invoerder. Enable Multithreading Aktiveer Multi-Verwerking Bypass the login screen and load the most recent User Profile Gaan verby die aanmeldskerm en laai die mees onlangse gebruikersprofiel Create SD Card Backups during Import (Turn this off at your own peril!) Skep SD Kaart Rugsteun tydens Invoer (Skakel hierdie af op u eie risiko!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Ware maksimum is die maksimum van die datastel.</p><p>99ste persentiel filter die seldsame uitskieters uit.</p></body></html> Combined Count divided by Total Hours Gekombineerde Telling gedeel deur Totale Ure Time Weighted average of Indice Tyd-geweegde gemiddeld van Indekse Standard average of indice Standaard gemiddeld van indekse Custom CPAP User Event Flagging Aangepaste CPAP Gebruikers Gebeurtenis Merking Events Gebeurtenisse Reset &Defaults Herstel &Verstekwaardes <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Waarskuwing: </span>Net omdat u kan, beteken nie noodwendig dis 'n goeie idee nie.</p></body></html> Waveforms Golfvorms Flag rapid changes in oximetry stats Merk vinnige veranderinge in oximetrie waardes Other oximetry options Ander oximetrie opsies Discard segments under Ignoreer segmente onder Flag Pulse Rate Above Merk Polstempo Boven Flag Pulse Rate Below Merk Polstempo Onder Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Pers ResMed (EDF) rugsteun saam om skyfspasie te stoor. Gerugsteunde EDF-lêers word gestoor in die .gz-formaat, wat algemeen op Mac en Linux platforms voorkom. OSCAR kan direk invoer vanaf hierdie saamgeperste rugsteun. Om dit met ResScan te gebruik, sal die .gz-lêers eers ontplooi moet word. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Die volgende opsies beïnvloed die hoeveelheid skyfspasie wat OSCAR gebruik, en het 'n effek op hoe lank invoer duur. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Dit maak dat OSCAR se data ongeveer die helfte soveel ruimte in beslag neem. Maar dit maak invoer en verandering van dae in OSCAR langer. As jy 'n nuwe rekenaar het met 'n vinnige stoorspasie, is dit 'n goeie opsie. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Pers Sessie Data Saam (maak OSCAR data kleiner, maar dagverandering stadiger) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Dit behou 'n rugsteun van SD-kaart data vir ResMed masjiene, ResMed S9 reeks masjiene verwyder hoë resolusie data ouer as 7 dae, en grafiekdata ouer as 30 dae. OSCAR kan 'n afskrif van hierdie data behou as u ooit weer moet installeer. (Hoogs aanbeveel, tensy u kort is op skyfspasie of nie omgee vir die grafiekdata nie) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Maak aanvang van OSCAR 'n bietjie stadiger, deur al die opsomming data vooraf te laai, wat vinniger oorsig verskaf sowel as 'n paar ander berekeninge later aan. </p><p>As u 'n groot hoeveelheid data het, kan dit die moeite werd wees om dit af te skakel, maar as u gewoonlik <span style=" font-style:italic;">alles</span> wil sien in die oorsig, sal al die data in elk geval gelaai moet word. </p><p>Neem kennis dat hierdie stelling nie golfvorm en gebeurtenis data affekteer nie, wat altyd gelaai word soos benodig.</p></body></html> This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Hierdie eksperimentele opsie poog om OSCAR se gebeurtenismerkerstelsel te gebruik om masjien opgespoor gebeurtenis posisionering te verbeter. Show flags for machine detected events that haven't been identified yet. Wys merkers vir masjien opgespoor gebeurtenisse wat nog nie geïdentifiseer is nie. Show Remove Card reminder notification on OSCAR shutdown Vertoon Verwyder Kaart kennisgewing tydens OSCAR uitgaan Check for new version every Kyk vir nuwe weergawe elke days. dae. Last Checked For Updates: Laaste Gekyk Vir Opdaterings: TextLabel TextLabel I want to be notified of test versions. (Advanced users only please.) Ek wil in kennis gestel word van toetsweergawes. (Slegs gevorderde gebruikers asseblief.) &Appearance &Voorkoms Graph Settings Grafiek Instellings <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Watter tab om oop te maak wanner 'n profiel gelaai word. (Nota: Die verstekwaarde is Profiel as OSCAR gestel is om nie 'n profiel oop te maak as dit begin nie)</p></body></html> Bar Tops ???? Bar Tops Line Chart Overview Linecharts Oorsigtelike Lyngrafieke <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Dit maak scrolling makliker wanneer ingezoom is op sensitiewe tweerigting-TouchPads</p><p>50ms is die aanbeveelde waarde.</p></body></html> How long you want the tooltips to stay visible. Hoe lank moet die tooltips sigbaar bly. Scroll Dampening Scroll Demping Tooltip Timeout Tooltip Timeout Default display height of graphs in pixels Verstek vertoon hoogte van grafieke in pixels Graph Tooltips Grafiek Tooltips The visual method of displaying waveform overlay flags. Die viduele metode om golfvorm oorleg merkers te vertoon. Standard Bars Standaard Bars Top Markers Boonste Merkers Graph Height Grafiek Hoogte Auto-Launch CPAP Importer after opening profile Outo-Laai CPAP Invoerder nadat profiel oopgemaak is Automatically load last used profile on start-up Laai outomaties laaste profiel <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Genereer 'n waarskuwing wanneer data ingevoer word van enige masjien model wat nog nie getoets is deur OSCAR ontwikkelaars nie.</p></body></html> Warn when importing data from an untested machine Waarsku my wanneer data ingevoer word vanaf 'n ongetoetsde maskien <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>Genereer 'n waarskuwing wanneer data ingevoer word wat op een of ander wyse verskillend is van wat verwag word deur die OSCAR ontwikkelaars.</p></body></html> Warn when previously unseen data is encountered Waarsku my wanneer onbekende data teëgekom word This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Hierdie berekening vereis dat Total Lek data verskaf word deur die CPAP masjien. (Bv. PRS1, maar nie ResMed nie, wat reeds hierdie het) Die onbedoelde lekberekeninge wat hier gebruik word, is lineêr, hulle modelleer nie die maskerventilasiekurwe nie. As u 'n paar verskillende maskers gebruik, kies eerder gemiddelde waardes. Dit behoort steeds naby genoeg te wees. Calculate Unintentional Leaks When Not Present Bereken Onbedoekde Lekke Indien Nie Teenwoordig Nie 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Nota: 'n Lineêre berekeningsmetode word gebruik. Verandering van die waardes vereis 'n herberekening. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Nota: </span>As gevolg van opsommende ontwerpbeperkings, ondersteun ResMed-masjiene nie die verandering van hierdie instellings nie.</p></body></html> Oximetry Settings Oximetrie Instellings Always save screenshots in the OSCAR Data folder Stoor altyd skermskote in die OSCAR data vouer Check For Updates Soek vir Opdaterings You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. U gebruik 'n toetsweergawe van OSCAR. Toetsweergawes soek outomaties vir opdaterings ten minste elke sewe dae. U kan hierdie tyd korter stel as sewe dae. Automatically check for updates Soek outomaties vir opdaterings How often OSCAR should check for updates. Hoe gereeld OSCAR moet soek vir opdaterings. If you are interested in helping test new features and bugfixes early, click here. Indien u belangstel om nuwe funksies en foutregstellings te help toets, kliek hier. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Indien u wil help om vroeë weergawes van OSCAR te help toets, sien asseblief die Wiki blasy oor toetsing van OSCAR. Ons verwelkom almal wat OSCAR wil help toets, ontwikkel, en wil help met vertalings van bestaande of nuwe tale. https://www.sleepfiles.com/OSCAR On Opening Tydens Oopmaak Profile Profiel Welcome Welkom Daily Daaglikse Statistics Statistieke Switch Tabs Verander Tabs No change Geen verandering nie After Import Na Invoering Overlay Flags Oorleg Merkers Line Thickness Lyndikte The pixel thickness of line plots Die lyndikte van plots Other Visual Settings Ander Visuele Stellings Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Anti-Aliasering maak dat grafieke gladder vertoon. Sekere grafieke lyk beter met hierdie opsie aangeskakel. Hierdie affekteer ook gedrukte verslae. Probeer dit en kyk of u daarvan hou. Use Anti-Aliasing Gebruik Anti-Aliasering Makes certain plots look more "square waved". Laat sekere plots meer reghoekig lyk. Square Wave Plots Reghoekige Plots Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Pixmap caching is 'n garfiese versnellingstegbiek. Dit mag probleme veroorsaak met lettertipe tekening in die grafiese vertoon area op u platvorm. Use Pixmap Caching Gebruik Pixmap Caching <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Hierdie kenmerke is onlangs gesnoei. Hulle sal later terugkom. </p></body></html> Animations && Fancy Stuff Animasies && Fênsie Dinge Whether to allow changing yAxis scales by double clicking on yAxis labels Welke verandering van Y-As skalering toegelaat word deur dubbel-kliek op Y-as waardes Allow YAxis Scaling Laat Y-As Skalering Toe Whether to include machine serial number on machine settings changes report Sluit masjien serienommer op die masjien instellings verslag in Include Serial Number Sluit Serienommer in Graphics Engine (Requires Restart) Grafiese Engine (Benodig Uitgaan) l/min L/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Saamgestelde Indekse</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <html><head/><body><p>Merk SpO<span style=" vertical-align:sub;">2</span> desaturaties Onder</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.84158pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 dataingevoer van SpO2Review (from .spoR files) of die seriale invoer metode het </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">nie</span><span style=" font-family:'Sans'; font-size:10pt;"> die korrekte tydstempel om te sinkroniseer nie.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Lewendige beskouings mode (deur 'n seriale kabel te gebruik) is een amnier om akkurate sinkronisasie op CMS50 oximeters te verkry, maar maak nie voorsiening vir CPAP klok dryf nie.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Indien u u Oximeter se opname tyd begin by </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">presies </span><span style=" font-family:'Sans'; font-size:10pt;">dieselfde tyd wat u u CPAP masjien begin, kan u wel sinkronisasie bewerk. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Die seriale invoer proses gebruik die begintyd van die vorige nag se eerste CPCP sessie. (Onthou om CPAP data eerste in te voer!)</span></p></body></html> Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Probeer om dit te verander van die verstekinstelling (Desktop OpenGL) as u probleme ondervind met OSCAR se grafieke. Print reports in black and white, which can be more legible on non-color printers Druk verslae in swart en wit, wat meer leesbaar kan wees op nie-kleur drukkers Print reports in black and white (monochrome) Druk verslae in swart en wit (monochroom) Fonts (Application wide settings) Lettertipes (Programwye instellings) Font Lettertipe Size Grootte Bold Bold Italic Kursief Application Program Graph Text Grafiek Teks Graph Titles Grafiek Titels Big Text Groot Teks Details Details &Cancel &Kanselleer &Ok &Ok Name Naam Color Kleur Flag Type Merker Tipe Label Label CPAP Events CPAP Gebeurtenisse Oximeter Events Oximeter Gebeurtenisse Positional Events Posisionele Gebeurtenisse Sleep Stage Events Slaap Fase Gebeurtenisse Unknown Events Onbekende Gebeurtenisse Double click to change the descriptive name this channel. Dubbel kliek om die beskrywende naam van hierdie kanaal te verander. Double click to change the default color for this channel plot/flag/data. Dubbelkliek om die verstek kleur vir hierdie kanaal se plot/merker/data te verander. Overview Oorsig <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Neem Asseblief Kennis:</b> OSCAR se se gevorderde sessie skeiding vermoëns is nie moontlik met <b>ResMed</b> masjiene nie weens 'n beperking in die manier waarop hul instellings en opsommingsdata gestoor word, en daarom is hulle vir hierdie profiel gedeaktiveer.</p><p>Op ResMed masjiene, sal dae <b>skei op middagete</b> soos in ResMed se kommersiële sagteware.</p> Double click to change the descriptive name the '%1' channel. Dubbelkliek om die beskrywende naam van die '%1'-kanaal te verander. Whether this flag has a dedicated overview chart. Of hierdie vlag 'n toegewyde oorsigkaart het. Here you can change the type of flag shown for this event Hier kan u die tipe merker wat vir hierdie gebeurtenis gewys word, verander This is the short-form label to indicate this channel on screen. Dit is die kortvormetiket om hierdie kanaal op die skerm aan te dui. This is a description of what this channel does. Dit is 'n beskrywing van wat hierdie kanaal doen. Lower Onderste Upper Boonste CPAP Waveforms CPAP Golfvorms Oximeter Waveforms Oximeter Golfvorms Positional Waveforms Posisionele Golfvorms Sleep Stage Waveforms Slaap Fase Golfvorms Whether a breakdown of this waveform displays in overview. Of 'n afbraak van hierdie golfvorm in die oorsig vertoon. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Hier kan u die <b>onderste</b> drempel stel wat gebruik word vir sekere berekenings op die %1 golfvorm Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Hier kan u die <b>boonste</b> drempel stel wat gebruik word vir sekere berekenings op die %1 golfvorm Data Processing Required Data Verwerking Benodig A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? 'n Data her- / dekompressie-proses is nodig om hierdie veranderinge toe te pas. Hierdie operasie kan 'n paar minute neem om te voltooi. Is u seker u wil hierdie veranderinge wil maak? Data Reindex Required Data Her-indeks Benodig A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? 'n Data her-indeksproses is nodig om hierdie veranderinge toe te pas. Hierdie operasie kan 'n paar minute neem om te voltooi. Is u seker u wil hierdie veranderinge wil maak? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. As u hierdie data ooit weer moet invoer (of dit nou in OSCAR of ResScan is), sal hierdie data nie terugkom nie. If you need to conserve disk space, please remember to carry out manual backups. As u skyfspasie moet behou, onthou asb om handmatige rugsteun uit te voer. Are you sure you want to disable these backups? Is u seker u wil hierdie backups afskakel? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Om rugsteun af te skakel, is nie 'n goeie idee nie, want OSCAR benodig dit om die databasis te herbou indien foute gevind word. Are you really sure you want to do this? Is u regtig seker dat u dit wil doen? Flag Merker Minor Flag Klein Merker Span Mate Always Minor Altyd Klein No CPAP machines detected Geen CPAP masjien bespeur nie Will you be using a ResMed brand machine? Never Nooit %1 %2 %1 %2 Restart Required Herbegin Vereis One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Een of meer van die veranderinge wat u gemaak het, sal vereis dat hierdie program herbegin word, sodat hierdie veranderinge in werking tree. Wil u dit nou doen? This may not be a good idea Dit mag dalk nie 'n goeie idee wees nie ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9-toestelle vee gereeld sekere data uit u SD-kaart ouer as 7 en 30 dae (afhangende van die resolusie). ProfileSelector Filter: Filter: Reset filter to see all profiles Herstel filter om alle profiele te sien Version Weergawe &Open Profile &Open Profiel &Edit Profile &Wysig Profiel &New Profile &Nuwe Profiel Profile: None Profiel: Geen Please select or create a profile... Kies of skep asseblief 'n profiel... Destroy Profile Vernietig Profiel Profile Profiel Ventilator Brand Ventilator Maak Ventilator Model Ventilator Model Other Data Ander Data Last Imported Laaste Ingevoer Name Naam You must create a profile U moet 'n profiel skep Enter Password for %1 Verskaf Wagwoord vir %1 You entered an incorrect password U het 'n verkeerde wagwoord verskaf Forgot your password? Het u u wagwoord vergeet? Ask on the forums how to reset it, it's actually pretty easy. Vra op die forums hoe om dit te herstel, dit is baie maklik. Select a profile first Kies eers 'n profiel The selected profile does not appear to contain any data and cannot be removed by OSCAR Die gekose profiel bevat blykbaar geen data nie en kan nie deur OSCAR verwyder word nie If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Indien u probeer uitvee omdat u die wagwoord vergeet het, moet u dit of herstel of die profiel vouer handrolies uitvee. You are about to destroy profile '<b>%1</b>'. U is op die punt om die profiel te vernietig '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Dink noukeurig, aangesien dit die profiel met alle <b>rugsteun data</b> gestoor onder<br/>%2. onherroeplik sal uitvee. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Skryf die woord <b>DELETE</b> hieronder (presies soos vertoon) om te bevestig. DELETE DELETE Sorry Jammer You need to enter DELETE in capital letters. U het nodig om DELETE in hoofletters in te sleutel. There was an error deleting the profile directory, you need to manually remove it. Daar was 'n probleem met die uitvee van die profiel stoorplek, u sal dit handmatig moet verwyder. Profile '%1' was succesfully deleted Profiel '%1' is suksesvol uitgevee Bytes Grepe KB KB MB MB GB GB TB TB PB PB Summaries: Opsommings: Events: Gebeurtenisse: Backups: Rugsteun: Hide disk usage information Versteek skyfgebruiksinligting Show disk usage information Wys skyfgebruiksinligting Name: %1, %2 Naam: %1, %2 Phone: %1 Tel: %1 Email: <a href='mailto:%1'>%1</a> Epos: <a href='mailto:%1'>%1</a> Address: Adres: No profile information given Geen profiel informasie verskaf Profile: %1 Profiel: %1 ProgressDialog Abort Gaan uit QObject No Data Geen Data Events Gebeurtenisse Duration Tydsduur (% %1 in events) (% %1 in gebeurtenisse) Jan Jan Feb F11 Mar Maart Apr Apr May Mei Jun Jun Jul Jul Aug Aug Sep Sep Oct Okt Nov Nov Dec Des ft vt lb lb oz oz Kg kg cmH2O cmH2O Med. Med. Min: %1 Min: %1 Min: Min: Max: Maks: Max: %1 Maks: %1 %1 (%2 days): %1 (%2 dae): %1 (%2 day): %1 (%2 dag): % in %1 % in %1 Hours Ure Min %1 Min %1 Hours: %1 Ure: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 lae gebruik, %2 geen gebruik, uit %3 dae (%4% voldoen.) Lengte: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Sessies: %1 / %2 / %3 Lengte: %4 / %5 / %6 Langste: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Lengte: %3 Begin: %2 Mask On Masker Op Mask Off Masker Af %1 Length: %3 Start: %2 %1 Lengte: %3 Begin: %2 TTIA: TTIA: TTIA: %1 TTIA: %1 Minutes Minute Seconds Sekondes h h m m s s ms ms Events/hr Gebeurtenisse/uur Hz Hz bpm bpm Litres Liters ml ml Breaths/min Asemteue/min ? ? Severity (0-1) Erns (0-1) Degrees Grade Error Fout Warning Waarskuwing Information Informasie Busy Besig Please Note Neem Asseblief Kennis Graphs Switched Off Grafieke Afgeskakel Sessions Switched Off Sessies Afgeskakel &Yes &Ja &No &Nee &Cancel &Kanselleer &Destroy &Vernietig &Save &Stoor BMI BMI Weight Massa Zombie Zombie Pulse Rate Polstempo Plethy **??? Plethy Pressure Druk Daily Daagliks Profile Profiel Overview Oorsig Oximetry Oximetrie Oximeter Oximeter Event Flags Gebeurtenis Merkers Default Verstek CPAP CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP Min EPAP Min EPAP Max EPAP Maks EPAP IPAP IPAP Min IPAP Min IPAP Max IPAP Maks IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Bevogtiger H H OA OA A A CA CA FL FL SA SA LE LE EP EP VS VS VS2 VS2 RERA RERA PP PP P P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF1 UF2 UF2 UF3 UF3 PS PS AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI EPI PB PB IE IE Insp. Time Inasemtyd Exp. Time Uitasemtyd Resp. Event *??? Resp. Gebeurtenis Flow Limitation *??? Vloeibeperking Flow Limit Vloeilimiet SensAwake SensAwake Pat. Trig. Breath ???? Pat. Trig. Breath Tgt. Min. Vent ?????? Tgt. Min. Vent Target Vent. ???? Target Vent. Minute Vent. ???? Minuut vent. Tidal Volume *??? Gety Volume Resp. Rate Resp. Tempo Snore Snork Leak Lek Leaks Lekke Large Leak Groot Lek LL LL Total Leaks Totale Lekke Unintentional Leaks *? Onbedoelde Lekke MaskPressure Maskerdruk Flow Rate Vloeitempo Sleep Stage Slaap Stadium Usage Gebruik Sessions Sessies Pr. Relief *? Druk Verligting No Data Available Geen Data Beskikbaar Nie App key: App key: Operating system: Operating system: Built with Qt %1 on %2 Gebou met Qt %1 op %2 Graphics Engine: Grafiese Engine: Graphics Engine type: Grafiese Engine tipe: Software Engine Sagteware Engine ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Desktop OpenGL m m cm cm in inch Only Settings and Compliance Data Available Selgs Instelling en Voldoeningsdata Beskikbaar Summary Data Only Slegs Omsommende Data Bookmarks Boekmerke Mode Mode Model Model Brand Maak Serial Serie Series Reeks Machine Masjien Channel Kanaal Settings Instellings Inclination **??? Depends on use. Can also be "geneigdheid" or "hoek" Gradiënt Orientation Oriëntasie Motion Beweging Name Naam DOB GebDat Phone Foon Address Adres Email Epos Patient ID Pasiënt ID Date Datum Bedtime Slaaptyd Wake-up Opstaantyd Mask Time Masker Tyd Unknown Onbekend None Geen Ready Gereed First Eerste Last Laaste Start Begin End Einde On Aan Off Af Yes Ja No Nee Min Min Max Maks Med Med Average Gemiddeld Median Mediaan Avg Gem W-Avg W-Gem Your %1 %2 (%3) generated data that OSCAR has never seen before. U %1 %2 (%3) het data gegenereer wat OSCAR nog nooit vantevore gesien het nie. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Die ingevoerde data mag moontlik nie heeltemal akkuraat wees nie, dus wil die ontwikkelaars graag 'n ZIP weergawe van hierdie masjien se SD kaart en ooreenstemmende doktersverslag of PDF analises kry om seker te maak dat OSCAR die data korrek hanteer. Non Data Capable Machine Masjien het Geen Data Vermoë Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. U %1 CPAP masjien (Model %2) is ongelukkig nie in staat om data te verskaf nie. Your %1 CPAP machine (Model %2) has not been tested yet. U %1 CPAP masjien (Model %2) is nog nie getoets nie. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Dit lyk soortgelyk genoeg aan ander masjiene dat dit moontlik mag werk, maar die ontwikkelaars sal graag 'n zip weergawe van hierdie masjien se SD kaart wil kry om te verseker dat dit werk met OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. Jammer, u %1 CPAP masjien (%2) is nog nie ondersteun nie. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Die ontwikkelaars benodig 'n ZIP weergawe van hierdie masjien se SD kaart en ooreenstemmende doktersverslag of PDF analises om dit te laat werk met OSCAR. Getting Ready... Maak Gereed... Machine Unsupported Masjie nie ge-ondersteun nie I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Ongelukkig kan OSCAR slegs gebruiksure en basiese instellings van hierdie toestel rapporteer. Scanning Files... Skandeer Lêers... Importing Sessions... Sessie Invoer... Finishing up... Voltooiing... Untested Data Ongetoetste Data Machine Untested Ongetoetse Masjien CPAP-Check CPAP-Toets AutoCPAP AutoCPAP Auto-Trial Outo-Nagaan AutoBiLevel OutoBiLevel S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Flex Flex Lock Flex Sluit Whether Flex settings are available to you. Of Flex instellings beskikbaar is vir u. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Tyd wat dit neem om oor te gaan van EPAP na IPAP, hoe hoër die syfer hoe stadiger die oorgang Rise Time Lock Stygtyd Sluit Whether Rise Time settings are available to you. Of Stygtyd instellings beskikbaar is vir u. Rise Lock Styg Sluit Passover Verdamping Target Time Teiken Tyd PRS1 Humidifier Target Time PRS1 Bevogtiger Teiken Tyd Hum. Tgt Time Bevogt. Teiken Tyd Mask Resistance Setting Masker Weestand Instelling Mask Resist. Masker Weest. Hose Diam. Pyp Dia. 15mm 15 mm Tubing Type Lock Pyptipe Sluiting Whether tubing type settings are available to you. Of pyp tipe instellings beskikbaar is vir u. Tube Lock Pyp Sluit Mask Resistance Lock Masker Weerstand Sluit Whether mask resistance settings are available to you. Of masker weerstand instellings beskikbaar is vir u. Mask Res. Lock Masker Res. Sluit Whether or not machine shows AHI via built-in display. Of u masjien AHI vertoon op die ingeboude skerm. Ramp Type Helling Tipe Type of ramp curve to use. Tipe helling kurwe om te gebruik. Linear Lineêr SmartRamp SlimHelling Ramp+ Ramp+ Backup Breath Mode Rugsteun Asem Mode The kind of backup breath rate in use: none (off), automatic, or fixed Die tipe rugsteun asemtempo in gebruik: (geen (af), outomaties of vas Breath Rate Asem Tempo Fixed Vaste Fixed Backup Breath BPM Vaste Rugsteun Asem BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Minimum Asem per minuut (BPM) waaronder 'n gemete asemingestel sal word Breath BPM Asem BPM Timed Inspiration Gemete Inspirasie The time that a timed breath will provide IPAP before transitioning to EPAP Die tyd wat 'n gemete asem sal IPAP verskaf voor oorskakeling na EPAP Timed Insp. Gemete Insp. Auto-Trial Duration Outo Toets Tydperk The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Die aantal dae in die Outo CPAP toets periode, waarna die masjien sal terugkeer na CPAP Auto-Trial Dur. Outo-Toets Tyd. EZ-Start EZ-Begin Whether or not EZ-Start is enabled OF EZ-Begin geaktiveer is Variable Breathing Veranderlike Asemhaling UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend ONBEVESTIG: Moontlike veranderende asemhaling, wat periodes van hoë afwyking is van die piek inspirasie vloei tendens Peak Flow Piek Vloei Peak flow during a 2-minute interval Piek vloei gedurende a'n 2 minute interval 22mm 22 mm Backing Up Files... Rugsteun lêers... model %1 model %1 DreamStation 2 DreamStation 2 unknown model onbekende model Flex Mode Flex Mode PRS1 pressure relief mode. PRS1 drukverligting mode. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time Stygtyd Bi-Flex Bi-Flex Flex Level Flex Vlak PRS1 pressure relief setting. PRS1 drukverligting instelling. Humidifier Status Bevogtiger Status PRS1 humidifier connected? Is die PRS1 bevogtiger gekoppel? Disconnected Ontkoppel Connected Gekoppel Humidification Mode Bevogtiger Mode PRS1 Humidification Mode PRS1 Bevogtiger Mode Humid. Mode Bevog. Mode Fixed (Classic) Vas (Kassiek) Adaptive (System One) Aanpas (System One) Heated Tube Verhitte Pyp Tube Temperature Pyp Temperatuur PRS1 Heated Tube Temperature PRS1 Verhitte Pyp Temperatuur Tube Temp. Pyp Temp. PRS1 Humidifier Setting PRS1 Bevogtiger Stelling Hose Diameter Pyp Deursneë Diameter of primary CPAP hose Deursneë van primêre CPAP pyp 12mm 12mm Auto On Outo Aan A few breaths automatically starts machine 'n Paar asemteue begin outomaties die masjien Auto Off Outo af Machine automatically switches off Masjien skakel outomaties af Mask Alert Masker Waarskuwing Whether or not machine allows Mask checking. Of die masjien toelaat dat die Masker nagegaan word. Show AHI Vertoon AHI Breathing Not Detected Asemhaling Nie Waargeneem Nie A period during a session where the machine could not detect flow. 'n Periode gedurende 'n sessie waartydens die masjien die vloei kon waarneem nie. BND BND Timed Breath Gemete Asemhaling Machine Initiated Breath Masjien Heinisieerde Asemhaling TB TB Windows User Windows Gebruiker Using Gebruik , found SleepyHead - , het SleepyHead gevind - You must run the OSCAR Migration Tool U moet die OSCAR Migrasie Hulpmiddel gebruik <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>U ou masjien data moet hergenereer word gegewe dat die rugsteun funksie nie afgeskakel was tydens vorige data intrek sessies nie.</i> Launching Windows Explorer failed Kon nie Windows Explorer oopmaak nie Could not find explorer.exe in path to launch Windows Explorer. Kon nie explorer.exe vind om Windows Explorer oop te maak nie. <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR behou 'n rugsteun van u toestel se data kaart vir hierdie doel.</b> OSCAR does not yet have any automatic card backups stored for this device. OSCAR het nog geen outomatiese kaart rugsteun vir hierdie toestel nie. This means you will need to import this machine data again afterwards from your own backups or data card. Dit beteken dat u die masjien se data agterna weer sal moet intrek van u eie rugsteun of data kaart af. Important: Belangrik: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. As u bekommerd is, kies Nee om uit te gaan en rugsteun u profiel handrolies, voordat u OSCAR weer begin. Are you ready to upgrade, so you can run the new version of OSCAR? Is u gereed om te opgradeer, sodat u die nuwe weergawe van OSCAR kan begin? Sorry, the purge operation failed, which means this version of OSCAR can't start. Jammer, die uitvee van die profiel was onsuksesvol, wat beteken dat hierdie weergawe van OSCAR nie kan begin nie. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Wil u outomatiese rugsteun aanskakel, sodat wanneer 'n nuwe weergawe van OSCAR nodig het om dit te doen, dit hiervandaan kan herbou? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR sal nou die invoer helper begin dat u u %1 data kan invoer. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: OSCAR sal nou stop, waarna dit sal probeer om u rekenaar se file manager te begin dat u handrolies u profiel kan rugsteun: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Gebruik u file manager om 'n afskrif te maak van u profiel directory. Daarna kan u OSCAR weer begin om die opgraderingsproses te voltooi. Machine Database Changes Masjien Databasis Veranderinge OSCAR %1 needs to upgrade its database for %2 %3 %4 OSCAR %1 het nodig om die databasis op te dateer vir %2 %3 %4 Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Wanneer u opgradeer, <font size=+1>KAN U NIE</font> meer hierdie profiel gebruik met die vorige weergawe nie. The machine data folder needs to be removed manually. The masjien se data vouer moet handmatig deur uself verwyder word. This folder currently resides at the following location: Hierdie lêer is tans teenwoordig in die volgende plek: Rebuilding from %1 Backup Herbou van %1 Rugsteun Therapy Pressure Terapie Druk Inspiratory Pressure Inaseming Druk Lower Inspiratory Pressure Laer Inaseming Druk Higher Inspiratory Pressure Hoër Inaseming Druk Expiratory Pressure Uitaseming Druk Lower Expiratory Pressure Laer Uitaseming Druk Higher Expiratory Pressure Hoër Uitaseming Druk Pressure Support Druk Ondersteuning PS Min PS Min Pressure Support Minimum Druk Ondersteuning Minimum PS Max PS Maks Pressure Support Maximum Druk Ondersteuning Maksimum Min Pressure Min Druk Minimum Therapy Pressure Minimum Terapie Druk Max Pressure Maks Druk Maximum Therapy Pressure Maksimum Terapie Druk Ramp Time Helling Tyd Ramp Delay Period Helling Vertraging Periode Ramp Pressure Helling Druk Starting Ramp Pressure Begin Helling Druk Ramp Event Helling Gebeurtenis Ramp Helling Vibratory Snore (VS2) Vibratory Snore (VS2) A ResMed data item: Trigger Cycle Event 'n ResMed data item: Sneller Siklus Gebeurtenis Mask On Time Masker Op Tyd Time started according to str.edf Tyd begin volgens str.edf Summary Only Opsomming Alleenlik An apnea where the airway is open 'n Apneë waar die lugweg oop is An apnea caused by airway obstruction 'n Apneë veroorsaak deur lugweg obstruksie Hypopnea Hypopneë A partially obstructed airway 'n Gedeeltelik-geblokte lugweg Unclassified Apnea Ongeklassifiseerde Apneë UA UA Vibratory Snore Vibrerende Snork A vibratory snore 'n Vibrerende Snork A vibratory snore as detcted by a System One machine 'n Vibrerende snork soos waargeneem deur 'n System One masjien Pressure Pulse Druk Puls A pulse of pressure 'pinged' to detect a closed airway. 'n Druk puls wat gebruik word om 'n gelote lugweg waar te neem. A large mask leak affecting machine performance. 'n Groot maskerlek wat masjienverrigting beinvloed. Non Responding Event Nie-reaksie Gebeurtenis A type of respiratory event that won't respond to a pressure increase. 'n Tipe respiratoriese gebeurtenis wat nie op 'n toename in druk reageer nie. Expiratory Puff Uitgaande Teug Intellipap event where you breathe out your mouth. Intellipap gebeurtenis waar u deur u mond uitasem. SensAwake feature will reduce pressure when waking is detected. SensAwake funksie sal druk verminder wanneer die pasiënt wakker word. User Flag #1 Gebruikersvlaggie #1 User Flag #2 Gebruikersvlaggie #2 User Flag #3 Gebruikersvlaggie #3 Heart rate in beats per minute Harttempo in slae per minuut Blood-oxygen saturation percentage Bloedsuurstof versadiging persentasie Plethysomogram Plethysomogram An optical Photo-plethysomogram showing heart rhythm 'n Optiese Foto-plethysomogram wat hart ritme wys Pulse Change Polsverandering A sudden (user definable) change in heart rate 'n Skielike (gebruiker definieerbare) verandering in hartklop SpO2 Drop SpO2 Afname A sudden (user definable) drop in blood oxygen saturation 'n Skielike (gebruiker definieerbare) afname in bloedsuurstof versadiging SD SD Breathing flow rate waveform Asemhaling vloeitempo golfvorm L/min L/min Mask Pressure Masker Druk Amount of air displaced per breath Hoeveelheid lug verplaas per asemteug Graph displaying snore volume Grafiek vertoon snork volume Minute Ventilation Minuut Ventilasie Amount of air displaced per minute Hoeveelheid lug verplaas per minuut Respiratory Rate Respiratoriese Tempo Rate of breaths per minute Tempo van asemteue per minuut Patient Triggered Breaths Pasiënt-gesnellerde Asemteue Percentage of breaths triggered by patient Persentasie asemteue gesneller deur pasiënt Pat. Trig. Breaths Pat. Trig. Breaths Leak Rate Lek Tempo Rate of detected mask leakage Tempo van bespeurde maskerlek I:E Ratio I:E Verhouding Ratio between Inspiratory and Expiratory time Verhouding tussen Inasem en Uitasem tyd ratio verhouding Pressure Min Druk Min Pressure Max Druk Maks Pressure Set Druk Gestel Pressure Setting Druk Stelling IPAP Set IPAP Gestel IPAP Setting IPAP Stelling EPAP Set EPAP Gestel EPAP Setting EPAP Stelling Cheyne Stokes Respiration Cheyne Stokes Respirasie An abnormal period of Cheyne Stokes Respiration 'n Abnormale periode van Cheyne Stokes Respirasie CSR CSR Periodic Breathing Ongereëlde Asemhaling An abnormal period of Periodic Breathing 'n Abnormale tydperk van Ongereëlde Asemhaling Clear Airway Oop Lugweg Obstructive Obstruktiewe An apnea that couldn't be determined as Central or Obstructive. 'n Apneë wat nie bepaal kon word as Sentraal of Obstruktief nie. A restriction in breathing from normal, causing a flattening of the flow waveform. 'n Beperking in asemhaling wat afplatting van die vloi golfvorm veroorsaak. Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Respiratory Effort Related Arousal: 'n Beperking in asemhaling wat of veroorsaak dat die pasiënt wakker word of wat slaapversteuring tot gevolg het. Leak Flag Lek Vlag LF LF A user definable event detected by OSCAR's flow waveform processor. 'n Gebruiker definieerbare gebeurtenis waargeneem deru OSCAR se vloei golfvorm prosesseerder. Perfusion Index Perfusie Indeks A relative assessment of the pulse strength at the monitoring site 'n Relatiewe beoordeling van die polssterkte op die moniteringsplek Perf. Index % Perf. Indeks % Mask Pressure (High frequency) Masker Druk (Hoë frekwensie) Expiratory Time Uitasem Tyd Time taken to breathe out Tyd wat dit neem om uit te asem Inspiratory Time Inasem Tyd Time taken to breathe in Tyd wat dit neem om in te asem Respiratory Event Respiratoriese Gebeurtenis Graph showing severity of flow limitations Grafiek wat die omvang van vloeibeperkings toon Flow Limit. Vloei Lim. Target Minute Ventilation Minuut Ventilasie Teiken Maximum Leak Maksimum Lek The maximum rate of mask leakage Die maksimum tempo van maskerlek Max Leaks Maks Lek Apnea Hypopnea Index Apneë Hypopneë Indeks Graph showing running AHI for the past hour Grafiek wat die lopende AHI vir die laaste uur wys Total Leak Rate Totale Lek Tempo Detected mask leakage including natural Mask leakages Waargenome maskerlek, insluitend natuurlike Maskerlekkasies Median Leak Rate Median Lek Tempo Median rate of detected mask leakage Median tempo van waargenome maskerlek Median Leaks Median Lek Respiratory Disturbance Index Respiratoriese Versteuring Indeks Graph showing running RDI for the past hour Grafiek wat RDI vir die afgelope uur vertoon Sleep position in degrees Slaap posisie in grade Upright angle in degrees Regop hoek in grade Movement Beweging Movement detector Beweging sensor CPAP Session contains summary data only CPAP Sessie bevat slegs opsommende data PAP Mode PAP Mode Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Kon nie Channels.xml verwerk nie, OSCAR kan nie voortgaan nie en stop nou. Apnea Apneë An apnea reportred by your CPAP machine. 'n Apneë wat deur u CPAP masjien gerapporteer is. PAP Device Mode PAP Toestel Mode APAP (Variable) APAP (Veranderbaar) ASV (Fixed EPAP) ASV (Vaste EPAP) ASV (Variable EPAP) ASV (Veranderbare EPAP) Height Lengte Physical Height Fisiese Lengte Notes Notas Bookmark Notes Boekmerk Notas Body Mass Index Ligaams Massa Indeks How you feel (0 = like crap, 10 = unstoppable) Hoe u voel (0 = sleg, 10 = wonderlik) Bookmark Start Boekmerk Begin Bookmark End Boekmerk Einde Last Updated Laaste Opgedateer Journal Notes Joernaal Notas Journal Joernaal 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=Wakker 2=REM 3=Ligte Slaap 4=Diep Slaap Brain Wave Breingolf BrainWave Breingolf Awakenings Ontwakings Number of Awakenings Aantal Ontwakings Morning Feel Oggend Gevoel How you felt in the morning Hoe u gevoel het in die oggend Time Awake Tyd Wakker Time spent awake Tyd wakker gespandeer Time In REM Sleep Tyd In REM Slaap Time spent in REM Sleep Tyd gespandeer in REM Slaap Time in REM Sleep Tyd in REM Slaap Time In Light Sleep Tyd in Ligte Slaap Time spent in light sleep Tyd gespandeer in ligte slaap Time in Light Sleep Tyd in Ligte Slaap Time In Deep Sleep Tyd In Diep Slaap Time spent in deep sleep Tyd gespandeer in diep slaap Time in Deep Sleep Tyd in Diep Slaap Time to Sleep Tyd om te Slaap Time taken to get to sleep Tyd geneem om te slaap Zeo ZQ Zeo ZQ Zeo sleep quality measurement Zeo slaap kwaliteit meting ZEO ZQ ZEO ZQ Debugging channel #1 Ontfouting kanaal #1 Test #1 Toets #1 For internal use only Slegs vir interne gebruik Debugging channel #2 Ontfouting kanaal #2 Test #2 Toets #2 Zero Zero Upper Threshold Boonste Drempel Lower Threshold Onderste Drempel As you did not select a data folder, OSCAR will exit. Omdat u nie 'n data lêer gekies het nie, sal OSCAR nou uitgaan. or CANCEL to skip migration. of CANCEL om migrasie oor te slaan. Choose the SleepyHead or OSCAR data folder to migrate Kies die SleepyHead- of OSCAR-datamap om te migreer The folder you chose does not contain valid SleepyHead or OSCAR data. Die gekose vouer bevat nie geldige SleepyHead- of OSCAR-data nie. You cannot use this folder: U kan nie hierdie vouer gebruik nie: Migrating Besig om te migreer files lêers from vanaf to na OSCAR crashed due to an incompatibility with your graphics hardware. OSCAR het gestop na 'n versoenbaarheidsprobleem met u grafiese hardeware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. Om hierdie op te los, skakel OSCAR oor na 'n statiger maar meer aanpasbare metode van vertoon. OSCAR will set up a folder for your data. OSCAR sal 'n vouer skep vir u data. If you have been using SleepyHead or an older version of OSCAR, As u SleepyHead of 'n ouer weergawe van OSCAR gebruik, OSCAR can copy your old data to this folder later. kan OSCAR u ou data later na hierdie vouer kopieer. Migrate SleepyHead or OSCAR Data? Migreer die SleepyHead- of OSCAR-data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Op die volgende skerm sal OSCAR u vra om 'n lêergids met SleepyHead- of OSCAR-data te kies Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Klik op [OK] om na die volgende skerm te gaan of op [Nee] as u geen SleepyHead- of OSCAR-data wil gebruik nie. We suggest you use this folder: Dit word voorgestel dat u die volgende vouer gebruik: Click Ok to accept this, or No if you want to use a different folder. Kies OK om hierdie te aanvaar, of No indien u 'n verskillende vouer wil gebruik. Choose or create a new folder for OSCAR data Kies of skep 'n nuwe vouer vir OSCAR data Next time you run OSCAR, you will be asked again. Die volgende keer wat u OSCAR gebruik, sal u weer gevra word. The folder you chose is not empty, nor does it already contain valid OSCAR data. Die lêer wat u gekies het, is nie leeg nie, en bevat ook nie geldige OSCAR data nie. Data directory: Data directory: Unable to create the OSCAR data folder at Kan nie die datalêer skep nie by Unable to write to OSCAR data directory Kan nie skry nie na die OSCAR data lêer Error code Foutkode OSCAR cannot continue and is exiting. OSCAR kan nie voorgaan nie. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Kan nie die ontfoutingslys opdateer nie. U kan steeds die ontfoutingsblad gebruik (Help/Troubleshooting/Show Debug Pane) maar die ontvoutingslys sal nie na die skr=yf geskryf word nie. Version "%1" is invalid, cannot continue! Weergawe "%1" is ongeldig, kan nie voortgaan nie! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Die weergawe van OSCAR wat u gebruik (%1) is OUER as die een wat gebruik is om hierdie data te genereer (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? Dit is moontlik dat hierdie sal lei tot data korrupsie, is u seker dat u wil voortgaan? Question Vraag Exiting Gaan uit Are you sure you want to use this folder? Is u seker dat u hierdie vouer wil gebruik? Don't forget to place your datacard back in your CPAP machine Moenie vergeet om u data kaart terug te sit in u CPAP masjien nie OSCAR Reminder OSCAR Herinnering You can only work with one instance of an individual OSCAR profile at a time. U kan slegs met een instansie van 'n individuele OSCAR profiel werk op 'n keer. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Indien u gebruik maak van cloud berging, maak seker dat OSCAR toegemaak is en dat sinkronisasie afgehandel is op die rekenaar voordat u voortgaan. Loading profile "%1"... Laai profiel "%1"... Chromebook file system detected, but no removable device found Chromebook-lêerstelsel bespeur, maar geen verwyderbare toestel gevind nie You must share your SD card with Linux using the ChromeOS Files program U moet u SD-kaart met Linux deel deur gebruik te maak van die ChromeOS Files-program Recompressing Session Files Pers Sessie Lêers Saam Please select a location for your zip other than the data card itself! KIes asseblief 'n plek vir u zip anders as die data kaart self! Unable to create zip! Kan nie die zip skep nie! Are you sure you want to reset all your channel colors and settings to defaults? Is u seker dat u alle kanaal kleure en instellings wil terugstel na die verstekwaardes? Are you sure you want to reset all your waveform channel colors and settings to defaults? Is u seker u wil al u golfvormkanaalkleure en -instellings na standaardinstellings herstel? There are no graphs visible to print Daar is geen grafieke sigbaar om te druk nie Would you like to show bookmarked areas in this report? Wil u graag boekmerk areas in hierdie verslag wys? Printing %1 Report Druk %1 Verslag %1 Report %1 Verslag : %1 hours, %2 minutes, %3 seconds : %1 ure, %2 minute, %3 sekondes RDI %1 RDI %1 AHI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 UAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 AI=%1 AI=%1 Reporting from %1 to %2 Verslaggewing van %1 tot %2 Entire Day's Flow Waveform Volle Dag se Vloei Golfvorm Current Selection Huidige Keuse Entire Day Volle Dag Page %1 of %2 Bladsy %1 van %2 Days: %1 Dae: %1 Low Usage Days: %1 Lae Gebruik Dae: %1 (%1% compliant, defined as > %2 hours) (%1% voldoening, gedefinieer as > %2 ure) (Sess: %1) (Sess: %1) Bedtime: %1 Bedtyd: %1 Waketime: %1 Wakkerwordtyd: %1 (Summary Only) (Slegs Opsomming) There is a lockfile already present for this profile '%1', claimed on '%2'. Daar is 'n uitsluiting reeds teenwoordig vir hierdie profiel '%1', aangevra op '%2'. Fixed Bi-Level Vaste Bi-Level Auto Bi-Level (Fixed PS) Outo Bi-Level (Vaste PS) Auto Bi-Level (Variable PS) Outo Bi-Level (Veranderbare PS) 99.5% 99.5% varies verander n/a NVT Fixed %1 (%2) Vaste %1 (%2) Min %1 Max %2 (%3) Min %1 Maks %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %1 oor %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EPAP %1 Maks IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Mees onlangse Oximetrie data: <a onclick='alert("daily=%2");'>%1</a> (last night) (laasnag) (1 day ago) (1 dag gelede) (%2 days ago) (%2 dae gelede) No oximetry data has been imported yet. Geen oximetrie data is nog ingevoer ne. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings SmartFlex Settings ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Somnopose Software Zeo Zeo Personal Sleep Coach Personal Sleep Coach Database Outdated Please Rebuild CPAP Data Databasis Verouderd Herbou Asseblief Die CPAP Data (%2 min, %3 sec) (%2 min, %3 s) (%3 sec) (%3 s) Pop out Graph Pop Grafiek Uit The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Die opspring venster is vol. U moet die bestaande opspring venster weer vang, uitvee en dan hierdie grafiek weer laat opspring. Your machine doesn't record data to graph in Daily View U masjien verskaf nie data om te vertoon in die Daaglikse Vertoon nie There is no data to graph Daar is geen data om te vertoon nie d MMM yyyy [ %1 - %2 ] d MMM yyyy [ %1 - %2 ] Hide All Events Versteek Alle Gebeurtenisse Show All Events Vertoon Alle Gebeurtenisse Unpin %1 Graph Ontpin%1 Grafiek Popout %1 Graph Popout %1 Grafiek Pin %1 Graph Pin %1 Grafiek Plots Disabled Plots Afgeskakel Duration %1:%2:%3 Tydsduur %1:%2:%3 AHI %1 AHI %1 Relief: %1 Verligting: %1 Hours: %1h, %2m, %3s Ure: %1h, %2m, %3s Machine Information Masjien Inligting Journal Data Joernaal Data OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR het 'n ou Joernaal lêer gevind, maar dit lyk of die naam verander is: OSCAR will not touch this folder, and will create a new one instead. OSCAR sal nie aan die lêer raak nie en sal eerder 'n nuwe een skep. Please be careful when playing in OSCAR's profile folders :-P Wees asseblief versigtig wanneer u met OSCAR se profiel lêers speel :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. OSCAR kon nie 'n joernaal objek rekord in u profiel vind nie, maar het veelvuldige Joernaal data vouers gevind. OSCAR kon nie 'n joernaal objek rekord in u profiel vind nie, maar het veelvuldige Joernaal data vouers gevind. OSCAR picked only the first one of these, and will use it in future: OSCARhet slegs die eerste hiervan gekies en sal dit in die toekoms gebruik: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Indien u ou data weg is, verskuif die inhoud van al die ander Journal_XXXXXXX vouers handmatig na hierdie een toe. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Backing up files... Stoor lêers... Reading data files... Lees data lêers... SmartFlex Mode SmartFlex Mode Intellipap pressure relief mode. Intellipap drukverligtingsmode. Ramp Only Slegs Helling Full Time Voltyds SmartFlex Level SmartFlex Vlak Intellipap pressure relief level. Intellipap drukverligting vlak. Snoring event. Snork gebeurtenis. SN SN Locating STR.edf File(s)... Soek na STR.edf Lêer(s)... Cataloguing EDF Files... Katalogisering van EDF-lêers ... Queueing Import Tasks... Invoeropdragte in afwagting ... Finishing Up... Voltooiing ... CPAP Mode CPAP Mode VPAPauto VPAPauto ASVAuto ASVAuto iVAPS iVAPS PAC PAC Auto for Her Auto for Her EPR EPR ResMed Exhale Pressure Relief ResMed Uitasem Druk Verligting Patient??? Pasiënt??? EPR Level EPR Vlak Exhale Pressure Relief Level Uitasem Druk Verligting Vlak Response Reaksie Patient View Pasiënt Aansig SmartStart SmartStart Machine auto starts by breathing Masien begin outomaties deur asemhaling Smart Start Smart Start Humid. Status Humid. Status Humidifier Enabled Status Humidifier Enabled Status Humid. Level Humid. Level Humidity Level Bevogtiger Vlak Temperature Temperatuur ClimateLine Temperature ClimateLine Temperatuur Temp. Enable Temp. Enable ClimateLine Temperature Enable ClimateLine Temperature Enable Temperature Enable Temperature Enable AB Filter AB Filter Antibacterial Filter Antibakteriese Filter Pt. Access Pt. Access Essentials Noodsaaklikhede Plus Plus Climate Control Manual Of handleiding??? Handrolies Soft Sagte Standard Standaard BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T SmartStop SlimStop Machine auto stops by breathing Masjien outo stop deur asem te haal Smart Stop Slim Stop Simple Eenvoudige Advanced Gevorderde Your ResMed CPAP machine (Model %1) has not been tested yet. U ResMed CPAP masjien (Model %1) is nog nie getoets nie. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Dit lyk soortgelyk genoeg aan ander masjiene dat dit moontlik mag werk, maar die ontwikkelaars sal graag 'n zip weergawe van hierdie masjien se SD kaart wil kry om te verseker dat dit werk met OSCAR. Parsing STR.edf records... Verwerk STR.edf rekords... Auto Outo Mask Masker ResMed Mask Setting ResMed Masker Stelling Pillows Kussings Full Face Volgesig Nasal Neus Ramp Enable Helling Aktiveer Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Snapshot %1 Snapshot %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... Laai %1 data vir %2... Scanning Files Lêers skandeer Migrating Summary File Location Migreer Opsomming Lêer Plek Loading Summaries.xml.gz Laai Summaries.xml.gz Loading Summary Data Laai Opsomming Data Please Wait... Wag Asseblief... Updating Statistics cache Opdateer Statistieke Usage Statistics Gebruiks Statistieke %1 Charts %1 Grafieke %1 of %2 Charts %1 van %2 Grafieke Loading summaries Laai Opsommings Dreem Dreem Your Viatom device generated data that OSCAR has never seen before. U Viatom toesetl het data genereer wat OSCAR nog nie vantevore gesien het nie. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Die ingevoerde data mag dalk nie heeltemal akkuraat wees nie, daarom sal die ontwikkelaars graag 'n afskrif van u Viatom lêers wil kry om seker te maak dat OSCAR die data reg hanteer. Viatom Viatom Viatom Software Viatom Sagteware New versions file improperly formed Nuwe weergawe se lêer onsuksesvol gevorm A more recent version of OSCAR is available 'n Nuwer weergawe van OSCAR is beskikbaar release weergawe test version toetsweergawe You are running the latest %1 of OSCAR U gebruik die nuutste t %1 van OSCAR You are running OSCAR %1 U gebruik OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 is beskikbaar <a href='%2'>hier</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Informasie aangaande 'n nuwer toets weergawe %1 is beskikbaar by <a href='%2'>%2</a> Check for OSCAR Updates Kyk vir OSCAR Opdaterings Unable to check for updates. Please try again later. Kan nie vir opdaterings kyk nie. Probeer asseblief weer later. SensAwake level SensAware vlak Expiratory Relief Uitasem Verligting Expiratory Relief Level Uitasem Vrligting Vlak Humidity Humiditeit SleepStyle SlaapStyl This page in other languages: Hierdie bladsy in ander tale: %1 Graphs %1 Grafieke %1 of %2 Graphs %1 van %2 Grafieke %1 Event Types %1 Gebeurtenis Tipes %1 of %2 Event Types %1 of %2 Gebeurtenis Tipes Report about:blank ***??? about:blank SessionBar %1h %2m %1h %2m No Sessions Present Geen Sessies Teenwoordig nie SleepStyleLoader Import Error Intrek Fout This Machine Record cannot be imported in this profile. Hierdie Masjien Rekord kan nie in hierdie profiel ingetrek word nie. The Day records overlap with already existing content. Die Dag rekords oorvleuel met bestaande inhoud. Statistics CPAP Statistics CPAP Statistieke CPAP Usage CPAP Gebruik Average Hours per Night Gemiddelde Ure per Nag Therapy Efficacy Terapie Doeltreffendheid Leak Statistics Lek Statistieke Pressure Statistics Druk Statistieke Oximeter Statistics Oximeter Statistieke Blood Oxygen Saturation Bloed Suurstofversadiging Pulse Rate Polstempo %1 Median %1 Mediaan Average %1 Gemiddelde %1 Min %1 Min %1 Max %1 Maks %1 %1 Index %1 Indeks % of time in %1 % tyd in %1 % of time above %1 threshold % tyd bo die %1 drempelwaarde % of time below %1 threshold % tyd onder die %1 drempelwaarde Name: %1, %2 Naam: %1, %2 DOB: %1 Geb: %1 Phone: %1 Tel: %1 Email: %1 Epos: %1 Address: Adres: OSCAR is free open-source CPAP report software OSCAR is gratis oop-bronkode CPAP verslaggewing sagteware Oscar has no data to report :( OSCAR het geen data om te rapporteer nie :( Days Used: %1 Dae Gebruik: %1 Low Use Days: %1 Lae Gebruik Dae: %1 Compliance: %1% Voldoening: %1% Days AHI of 5 or greater: %1 Dae AHI van 5 of hoër: %1 Best AHI Beste AHI Date: %1 AHI: %2 Datum: %1 AHI: %2 Worst AHI Slegste AHI Best Flow Limitation Beste Vloeibeperking Date: %1 FL: %2 Datum: %1 FL: %2 Worst Flow Limtation Slegste Vloeibeperking No Flow Limitation on record Geen Vloeibeperking op rekord Worst Large Leaks Slegste Groot Lekke Date: %1 Leak: %2% Datum: %1 Lek: %2% No Large Leaks on record Geen Groot Lekke op rekord Worst CSR Slegste CSR Date: %1 CSR: %2% Datum: %1 CSR: %2% No CSR on record Geen CSR op rekord Worst PB Slegste PB Date: %1 PB: %2% Datum: %1 PB: %2% No PB on record Geen PB op rekord Want more information? Benodig u meer inligting? OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR benodig alle opsommende data ingelaai om die beste/slegste data vir individuele dae te kan bereken. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Aktiveer asseblief die vooraf laai van opsommings in die kieslys om te verseker dat hierdie data beskikbaar is. Best RX Setting Beste RX Stelling Date: %1 - %2 Datum: %1 - %2 AHI: %1 AHI: %1 Total Hours: %1 Totale Ure: %1 Worst RX Setting Slegste RX Stelling Most Recent Mees Onlangse Compliance (%1 hrs/day) Voldoening (%1 ure/dag) Changes to Machine Settings Veranderinge aan Masjien Instellings No data found?!? Geen Data Gevind nie?!? Last Week Laaste Week Last 30 Days Laaste 30 Dae Last 6 Months Laaste 6 Maande Last Year Laasdte Jaar Last Session Laaste Sessie Details Details No %1 data available. Geen %1 data beskikbaar. %1 day of %2 Data on %3 %1 dag van %2 Data op %3 %1 days of %2 Data, between %3 and %4 %1 dae van %2 Data, tussen %3 en %4 Days Dae This report was prepared on %1 by OSCAR %2 Hierdie verslag is voorberei op %1 deur OSCAR %2 Pressure Relief Druk Verligting Pressure Settings Druk Instellings Machine Information Masjien Inligting First Use Eerste Gebruik Last Use Laaste Gebruik Welcome Welcome to the Open Source CPAP Analysis Reporter Welkom by die Open Source CPAP Analysis Reporter What would you like to do? Wat wil u doen? CPAP Importer CPAP Invoerder Oximetry Wizard Oximetrie Helper Daily View Daaglikse Vertoon Overview Oorsig Statistics Statistieke <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Waarskuwing: </span><span style=" color:#ff0000;">ResMed S9 SD Kaartes moet gesluit wees </span><span style=" font-weight:600; color:#ff0000;">voordat dit in u rekenaar geplaas word.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Sommige stelsels skryf indekslêers na die kaart toe sonder om te vra, wat u kaart onleesbaar kan maak vir u CPAP masjien.</span></p></body></html> It would be a good idea to check File->Preferences first, Dit is 'n goeie idee om eers na File->Preferences te kyk, as there are some options that affect import. want daar is keuses wat invoering affekteer. Note that some preferences are forced when a ResMed machine is detected Let daarop dat sommige voorkeure afgedwing word wanneer 'n ResMed-masjien bespeur word First import can take a few minutes. Die eerste invoer kan 'n paar minute neem. The last time you used your %1... Die laaste keer wat u u %1 gebruik het... last night laasnag %2 days ago %2 dae gelede was %1 (on %2) was %1 (op %2) %1 hours, %2 minutes and %3 seconds %1 ure, %2 minute en %3 sekondes Your machine was on for %1. U masjien was aan vir %1. <font color = red>You only had the mask on for %1.</font> <font color = red>U het slegs die masker opgehad vir %1.</font> under onder over bo reasonably close to redelik naby aan equal to gelyk aan You had an AHI of %1, which is %2 your %3 day average of %4. U het 'n AHI gehad van %1, wat %2 u %3 dag gemiddeld van %4 is. Your CPAP machine used a constant %1 %2 of air U CPAP masjien het 'n konstante %1 %2 lug gebruik Your pressure was under %1 %2 for %3% of the time. U druk was onder %1 %2 vir %3% van die tyd. Your machine used a constant %1-%2 %3 of air. U masjien het 'n konstante %1-%2 %3 lug gebruik. Your EPAP pressure fixed at %1 %2. U EPAP druk was vas op %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. U IPAP druk was onder %1 %2 vir %3% van die tyd. Your EPAP pressure was under %1 %2 for %3% of the time. U EPAP druk was onder %1 %2 vir %3% van die tyd. Your machine was under %1-%2 %3 for %4% of the time. U masjien was onder %1-%2 %3 vir %4% van die tyd. 1 day ago 1 dag gelede Your average leaks were %1 %2, which is %3 your %4 day average of %5. U gemiddelde lekke was %1 %2, wat %3 u %4 dag gemiddeld van %5 is. No CPAP data has been imported yet. Geen CPAP data is nog ingevoer nie. gGraph %1 days %1 dae gGraphView 100% zoom level 100% zoom vlak Restore X-axis zoom to 100% to view entire selected period. Herstel X-as zoom na 100% om die hele gekose periode te sien. Restore X-axis zoom to 100% to view entire day's data. Herstel X-as zoom na 100% om die hele dag se data te sien. Reset Graph Layout Herstel Grafiek Uitleg Resets all graphs to a uniform height and default order. Herstel alle grafieke na univorme hoogte en verstek volgorde. Y-Axis Y-As Plots Plots CPAP Overlays CPAP Oorlê Oximeter Overlays Oximeter Oorlê Dotted Lines Stippellyne Double click title to pin / unpin Click and drag to reorder graphs Dubbelkliek die titel om te pin / ontpin Kliek en sleep om grafieke te herrangskik Remove Clone Verwyder Kloon Clone %1 Graph Kloon %1 Grafiek OSCAR-code-v1.3.1/Translations/Arabic.ar.ts000066400000000000000000015326641417327530600203250ustar00rootroot00000000000000 AboutDialog &About حول Release Notes ملاحظات الإصدار Credits قروض GPL License رخصة GPL Close قريب Show data folder عرض مجلد البيانات About OSCAR %1 حول OSCAR %1 Sorry, could not locate About file. عذرا ، لا يمكن تحديد موقع ملف. Sorry, could not locate Credits file. عذرًا ، تعذر تحديد موقع ملف الائتمانات. Sorry, could not locate Release Notes. عفوا، لا يمكن تحديد موقع ملاحظات الإصدار. OSCAR %1 OSCAR %1 Important: مهم: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. نظرًا لأن هذا الإصدار ما قبل النشر ، يوصى <b>بعمل نسخة احتياطية من مجلد البيانات يدويًا</b> قبل المتابعة ، لأن محاولة الاستعادة لاحقًا قد تؤدي إلى كسر الأمور. To see if the license text is available in your language, see %1. لمعرفة ما إذا كان نص الترخيص متاحًا بلغتك ، راجع %1. CMS50F37Loader Could not find the oximeter file: تعذر العثور على ملف مقياس التأكسج: Could not open the oximeter file: لا يمكن فتح ملف مقياس التأكسج: CMS50Loader Could not get data transmission from oximeter. نعةزر عن عدم امكان نقل البيانات من جهاز التاكسج. Please ensure you select 'upload' from the oximeter devices menu. يرجى التأكد من اختيار "تحميل" من قائمة الأجهزة مقياس التأكسج. Could not find the oximeter file: تعذر العثور على ملف مقياس التأكسج: Could not open the oximeter file: لا يمكن فتح ملف مقياس التأكسج: CheckUpdates Checking for newer OSCAR versions Daily Go to the previous day انتقل إلى اليوم السابق Show or hide the calender إظهار أو إخفاء التقويم Go to the next day انتقل إلى اليوم التالي Go to the most recent day with data records انتقل إلى أحدث يوم مع سجلات البيانات Events أحداث View Size عرض الحجم Notes عرض الحجم Journal مجلة B u Color اللون Small صغير Medium متوسط Big كبير Zombie الاموات الاحياء I'm feeling ... أنا أشعر ... Weight وزن If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value إذا كان الارتفاع أكبر من الصفر في مربع حوار التفضيلات ، فإن تحديد الوزن هنا سيظهر قيمة مؤشر كتلة الجسم (BMI) Awesome ممتاز B.M.I. Bookmarks إشارات مرجعية Add Bookmark اضافة للمفضلة Starts يبدأ Remove Bookmark إزالة المرجعية Flags أعلام Graphs الرسوم البيانية Show/hide available graphs. إظهار / إخفاء الرسوم البيانية المتاحة. Breakdown انفصال events أحداث UF1 UF1 UF2 UF2 Time at Pressure الوقت في الضغط No %1 events are recorded this day لم يتم تسجيل أحداث %1 هذا اليوم %1 event حدث %1 %1 events أحداث %1 Session Start Times أوقات بدء الجلسة Session End Times أوقات نهاية الجلسة Session Information معلومات الجلسة Oximetry Sessions جلسات قياس التأكسج Duration المدة الزمنية (Mode and Pressure settings missing; yesterday's shown.) This bookmark is in a currently disabled area.. هذه الإشارة المرجعية موجودة في منطقة معطلة حاليًا .. CPAP Sessions جلسات CPAP Details تفاصيل Sleep Stage Sessions جلسات مرحلة النوم Position Sensor Sessions جلسات استشعار الموقف Unknown Session جلسة غير معروفة Machine Settings إعدادات الجهاز Model %1 - %2 النموذج %1 - %2 PAP Mode: %1 وضع PAP: %1 99.5% 90% {99.5%?} This day just contains summary data, only limited information is available. هذا اليوم يحتوي فقط على بيانات موجزة ، تتوفر معلومات محدودة فقط. Total ramp time إجمالي الوقت المنحدر Time outside of ramp الوقت خارج المنحدر Start بداية End النهاية Unable to display Pie Chart on this system غير قادر على عرض مخطط دائري على هذا النظام 10 of 10 Event Types This CPAP machine does NOT record detailed data Sorry, this machine only provides compliance data. عذرًا ، لا يوفر هذا الجهاز سوى بيانات التوافق. "Nothing's here!" "لا يوجد شيء هنا!" No data is available for this day. لا توجد بيانات متاحة لهذا اليوم. 10 of 10 Graphs Oximeter Information معلومات مقياس التأكسج Click to %1 this session. انقر فوق %1 هذه الجلسة. disable تعطيل enable ممكن %1 Session #%2 %1 الجلسة #%2 %1h %2m %3s %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b> يرجى ملاحظة: </ b> تستند جميع الإعدادات الموضحة أدناه إلى افتراضات أنه لم يتغير شيء منذ الأيام السابقة. SpO2 Desaturations التشوهات SpO2 Pulse Change events أحداث تغيير النبض SpO2 Baseline Used خط الأساس SPO2 المستخدمة Statistics الإحصاء Total time in apnea الوقت الإجمالي في انقطاع النفس Time over leak redline الوقت على تسرب الخط الأحمر Event Breakdown انهيار الحدث Sessions all off! جلسات جميع قبالة! Sessions exist for this day but are switched off. توجد جلسات لهذا اليوم ولكن تم إيقافها. Impossibly short session جلسة قصيرة مستحيلة Zero hours?? ساعات الصفر؟ BRICK :( قالب طوب :( Complain to your Equipment Provider! شكوى إلى مزود المعدات الخاص بك! Pick a Colour اختيار اللون Bookmark at %1 إشارة مرجعية في %1 ExportCSV Export as CSV تصدير بتنسيق CSV Dates: تواريخ: Resolution: الدقة: Details تفاصيل Sessions جلسات Daily اليومي Filename: اسم الملف: Cancel إلغاء Export تصدير Start: بداية: End: النهاية: Quick Range: المدى السريع: Most Recent Day معظم الأيام الأخيرة Last Week الاسبوع الماضى Last Fortnight آخر أسبوعين Last Month الشهر الماضي Last 6 Months آخر 6 أشهر Last Year العام الماضي Everything كل شىء Custom مخصص Details_ تفاصيل_ Sessions_ جلسات_ Summary_ ملخص_ Select file to export to حدد ملف للتصدير إليه CSV Files (*.csv) ملفات CSV (* .csv) DateTime التاريخ والوقت Session جلسة Event حدث Data/Duration البيانات / المدة Date تاريخ Session Count عدد الجلسة Start بداية End النهاية Total Time الوقت الكلي AHI Count عد FPIconLoader Import Error خطأ في الاستيراد This Machine Record cannot be imported in this profile. لا يمكن استيراد سجل الجهاز في ملف التعريف هذا. The Day records overlap with already existing content. تتداخل سجلات اليوم مع المحتوى الموجود بالفعل. Help Hide this message إخفاء هذه الرسالة Search Topic: موضوع البحث: Help Files are not yet available for %1 and will display in %2. ملفات المساعدة ليست متاحة بعد لـ %1 وسيتم عرضها في %2. Help files do not appear to be present. لا يبدو أن ملفات المساعدة موجودة. HelpEngine did not set up correctly مساعدة المحرك لم يتم إعداد بشكل صحيح HelpEngine could not register documentation correctly. لم تتمكن HelpEngine من تسجيل الوثائق بشكل صحيح. Contents محتويات Index فهرس Search بحث No documentation available لا توجد وثائق متاحة Please wait a bit.. Indexing still in progress الرجاء الانتظار قليلاً .. لا تزال الفهرسة جارية No لا %1 result(s) for "%2" نتيجة (نتائج) %1 لـ "%2" clear واضح MD300W1Loader Could not find the oximeter file: تعذر العثور على ملف مقياس التأكسج: Could not open the oximeter file: لا يمكن فتح ملف مقياس التأكسج: MainWindow &Statistics الإحصاء Report Mode وضع التقرير Standard اساسي Monthly شهريا Date Range نطاق الموعد Statistics الإحصاء Daily اليومي Overview نظرة عامة Oximetry التأكسج Import استيراد Help مساعدة &File ملف &View رأي &Reset Graphs إعادة تعيين الرسوم البيانية &Help مساعدة Troubleshooting استكشاف الأخطاء وإصلاحها &Data البيانات &Advanced المتقدمة Purge ALL Machine Data تطهير جميع بيانات الجهاز Rebuild CPAP Data إعادة بناء بيانات CPAP &Import CPAP Card Data استيراد بيانات بطاقة CPAP Show Daily view عرض عرض يومي Show Overview view عرض عرض نظرة عامة &Maximize Toggle تكبير الحد الأقصى Maximize window تكبير النافذة Reset Graph &Heights إعادة تعيين الرسم البياني مرتفعات Reset sizes of graphs إعادة تعيين أحجام الرسوم البيانية Show Right Sidebar إظهار الشريط الجانبي الأيمن Show Statistics view عرض الاحصائيات الرأي Import &Dreem Data استيراد بيانات دريم Purge Current Selected Day &CPAP &Oximetry التأكسج &Sleep Stage &Position &All except Notes All including &Notes Show &Line Cursor عرض وخط المؤشرمشاهدة والخط المؤشر Show Daily Left Sidebar عرض الشريط الجانبي الأيسر اليومي Show Daily Calendar عرض التقويم اليومي Create zip of CPAP data card إنشاء ملف مضغوط لبطاقة بيانات CPAP Create zip of OSCAR diagnostic logs Create zip of all OSCAR data إنشاء ملف مضغوط لجميع بيانات OSCAR Report an Issue بلغ عن خطأ System Information معلومات النظام Show &Pie Chart عرض ومخطط دائري Show Pie Chart on Daily page إظهار مخطط دائري على الصفحة اليومية Standard graph order, good for CPAP, APAP, Bi-Level ترتيب الرسم البياني القياسي ، جيد ل CPAP ، APAP ، ثنائية المستوى Advanced المتقدمة Advanced graph order, good for ASV, AVAPS ترتيب الرسم البياني المتقدم ، جيد ل ASV ، AVAPS Show Personal Data Check For &Updates &Preferences والتفضيلات &Profiles مظهر &About OSCAR حول OSCAR Show Performance Information عرض معلومات الأداء CSV Export Wizard معالج تصدير CSV Export for Review تصدير للمراجعة E&xit خروج Exit خروج View &Daily عرض و يوميا View &Overview عرض ونظرة عامة View &Welcome عرض ومرحبا بكم Use &AntiAliasing استخدام ومكافحة التعرج Show Debug Pane إظهار جزء التصحيح Take &Screenshot خد لقطة للشاشة O&ximetry Wizard معالج التأكسج Print &Report اطبع تقرير &Edit Profile تعديل الملف الشخصي Import &Viatom/Wellue Data Daily Calendar التقويم اليومي Backup &Journal النسخ الاحتياطي ومجلة Online Users &Guide دليل المستخدمين عبر الإنترنت &Frequently Asked Questions أسئلة مكررة &Automatic Oximetry Cleanup التلقائي تنظيف مقياس التأكسج Change &User التغيير والمستخدم Purge &Current Selected Day تطهير واليوم المحدد الحالي Right &Sidebar الشريط الجانبي الأيمن Daily Sidebar الشريط الجانبي اليومي View S&tatistics عرض الاحصائيات Navigation التنقل Bookmarks إشارات مرجعية Records تسجيل Exp&ort Data تصدير البيانات Profiles مظهر Purge Oximetry Data تطهير بيانات قياس التأكسج View Statistics عرض الاحصائيات Import &ZEO Data استيراد وبيانات ZEO Import RemStar &MSeries Data استيراد RemStar و MSeries البيانات Sleep Disorder Terms &Glossary شروط اضطراب النوم والمسرد Change &Language تغيير اللغة Change &Data Folder التغيير ومجلد البيانات Import &Somnopose Data استيراد و Somnopose البيانات Current Days الأيام الحالية Welcome أهلا بك &About حول Please wait, importing from backup folder(s)... الرجاء الانتظار ، الاستيراد من مجلد (مجلدات) النسخ الاحتياطي ... Import Problem مشكلة الاستيراد Please insert your CPAP data card... الرجاء إدخال بطاقة بيانات CPAP ... Access to Import has been blocked while recalculations are in progress. تم حظر الوصول إلى الاستيراد أثناء إعادة الحساب. CPAP Data Located بيانات CPAP تقع Import Reminder استيراد تذكير Importing Data استيراد البيانات Please note, that this could result in loss of data if OSCAR's backups have been disabled. يرجى ملاحظة أن هذا قد يؤدي إلى فقدان البيانات إذا تم تعطيل النسخ الاحتياطية لـ OSCAR. There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete Export review is not yet implemented لم يتم تنفيذ مراجعة التصدير بعد Would you like to zip this card? هل تريد ضغط هذه البطاقة؟ Choose where to save zip اختر مكان حفظ الملف المضغوط ZIP files (*.zip) ملفات ZIP (* .zip) Creating zip... جارٍ إنشاء ملف مضغوط ... Calculating size... حساب الحجم ... Reporting issues is not yet implemented لم يتم تنفيذ مشكلات الإبلاغ بعد If you can read this, the restart command didn't work. You will have to do it yourself manually. إذا كنت تستطيع قراءة هذا ، فلن يعمل أمر إعادة التشغيل. سيكون عليك القيام بذلك بنفسك يدويًا. Are you sure you want to rebuild all CPAP data for the following machine: هل تريد بالتأكيد إعادة إنشاء جميع بيانات CPAP للجهاز التالي: For some reason, OSCAR does not have any backups for the following machine: لسبب ما ، لا يحتوي OSCAR على أي نسخ احتياطية للجهاز التالي: You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> أنت على وشك <font size = + 2> طمس </font> قاعدة بيانات الجهاز الخاصة بـ OSCAR للجهاز التالي: </ p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: تسبب خطأ إذن الملف في فشل عملية التطهير؛ سيكون عليك حذف المجلد التالي يدويًا: No help is available. لا يوجد مساعدة متاحة. %1's Journal مجلة %1 Choose where to save journal اختر مكان حفظ دفتر اليومية XML Files (*.xml) ملفات XML (* .xml) Help Browser مساعدة المتصفح Loading profile "%1" تحميل ملف التعريف "%1" %1 (Profile: %2) %1 (ملف تعريف: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. يرجى تذكر تحديد المجلد الجذر أو حرف محرك الأقراص لبطاقة البيانات ، وليس مجلدًا بداخلها. Find your CPAP data card Please open a profile first. يرجى فتح ملف تعريف أولا. Check for updates not implemented Choose where to save screenshot اختر مكان حفظ لقطة الشاشة Image files (*.png) ملفات الصور (* .png) Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. شريطة أن تكون قد قمت بعمل <i> نسخ احتياطي <b> خاصة بك </ b> لجميع بيانات CPAP الخاصة بك </ i> ، فلا يزال بإمكانك إكمال هذه العملية ، لكن سيتعين عليك الاستعادة من النسخ الاحتياطية يدويًا. Are you really sure you want to do this? هل أنت متأكد أنك تريد فعل ذلك؟ Because there are no internal backups to rebuild from, you will have to restore from your own. نظرًا لعدم وجود نسخ احتياطية داخلية لإعادة الإنشاء منها ، سيتعين عليك الاستعادة من جهازك. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) هل ترغب في الاستيراد من النسخ الاحتياطية الخاصة بك الآن؟ (لن يكون لديك بيانات مرئية لهذا الجهاز حتى تقوم بذلك) Note as a precaution, the backup folder will be left in place. لاحظ كإجراء وقائي ، سيتم ترك مجلد النسخ الاحتياطي في مكانه. OSCAR does not have any backups for this machine! ليس لدى OSCAR أي نسخ احتياطية لهذا الجهاز! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> ما لم تقم بعمل نسخ احتياطية خاصة بك لجميع بياناتك لهذا الجهاز ، فستفقد بيانات هذا الجهاز بشكل دائم! Are you <b>absolutely sure</b> you want to proceed? هل أنت <b> متأكد تمامًا </ b> أنك تريد المتابعة؟ Are you sure you want to delete oximetry data for %1 هل أنت متأكد من أنك تريد حذف بيانات مقياس التأكسج لـ %1 <b>Please be aware you can not undo this operation!</b> <b> يرجى العلم بأنه لا يمكنك التراجع عن هذه العملية! </b> Select the day with valid oximetry data in daily view first. حدد اليوم مع بيانات oximetry صالحة في العرض اليومي أولاً. Imported %1 CPAP session(s) from %2 تم استيراد جلسة (جلسات) CPAP %1 من %2 Import Success استيراد النجاح Already up to date with CPAP data at %1 بالفعل حتى الآن مع بيانات CPAP في %1 Up to date حتى الآن Couldn't find any valid Machine Data at %1 تعذر العثور على أي بيانات صالحة عن الجهاز على %1 Choose a folder اختيار مجلد No profile has been selected for Import. لم يتم اختيار ملف تعريف للاستيراد. Import is already running in the background. الاستيراد قيد التشغيل بالفعل في الخلفية. A %1 file structure for a %2 was located at: توجد بنية ملف %1 لـ %2 على: A %1 file structure was located at: تم تحديد بنية مل %1 على: Would you like to import from this location? هل ترغب في الاستيراد من هذا الموقع؟ Specify تحديد Access to Preferences has been blocked until recalculation completes. تم حظر الوصول إلى التفضيلات حتى تكتمل عملية إعادة الحساب. There was an error saving screenshot to file "%1" حدث خطأ أثناء حفظ لقطة الشاشة لملف "%1" Screenshot saved to file "%1" تم حفظ لقطة الشاشة في الملف "%1" The User's Guide will open in your default browser سيتم فتح دليل المستخدم في المستعرض الافتراضي الخاص بك The FAQ is not yet implemented لم يتم تنفيذ التعليمات There was a problem opening MSeries block File: حدثت مشكلة أثناء فتح ملف :MSeries block MSeries Import complete MSeries استيراد كاملة The Glossary will open in your default browser سيتم فتح المسرد في المستعرض الافتراضي الخاص بك OSCAR Information معلومات OSCAR MinMaxWidget Auto-Fit لصناعة السيارات في صالح Defaults التخلف Override تجاوز The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. وضع التحجيم Y-Axis ، "الاحتواء التلقائي" للتحجيم التلقائي ، "الإعدادات الافتراضية" للإعدادات وفقًا للشركة المصنعة ، و "التجاوز" لاختيار إعداداتك. The Minimum Y-Axis value.. Note this can be a negative number if you wish. الحد الأدنى لقيمة المحور ص .. لاحظ أن هذا يمكن أن يكون رقمًا سالبًا إذا كنت ترغب في ذلك. The Maximum Y-Axis value.. Must be greater than Minimum to work. القيمة القصوى لمحور Y .. يجب أن تكون أكبر من الحد الأدنى للعمل. Scaling Mode وضع التحجيم This button resets the Min and Max to match the Auto-Fit يعيد هذا الزر ضبط Min و Max ليتناسب مع Auto-Fit NewProfile Edit User Profile تحرير ملف تعريف المستخدم I agree to all the conditions above. أوافق على جميع الشروط المذكورة أعلاه. User Information معلومات المستخدم User Name اسم المستخدم Password Protect Profile كلمة السر حماية الملف الشخصي Password كلمه السر ...twice... ...مرتين... Locale Settings الإعدادات المحلية Country بلد TimeZone وحدة زمنية about:blank Very weak password protection and not recommended if security is required. DST Zone التوقيت الصيفي Personal Information (for reports) المعلومات الشخصية (للتقارير) First Name الاسم الاول Last Name الكنية It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. من المقبول تمامًا تخطي ذلك أو تخطي ذلك ، ولكن سنك القاسي ضروري لتعزيز دقة بعض الحسابات. D.O.B. D.O.B. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body> <p> يلزم في بعض الأحيان الجنس (الولادة) البيولوجي لتعزيز دقة بعض الحسابات ، ولا تتردد في ترك هذا فارغًا وتخطي أيًا منها. </ p> </body> </ HTML> Gender جنس Male الذكر Female أنثى Height ارتفاع Metric قياس English الإنجليزية Contact Information معلومات الاتصال Address عنوان Email البريد الإلكتروني Phone هاتف CPAP Treatment Information معلومات علاج CPAP Date Diagnosed تاريخ التشخيص Untreated AHI غير المعالجة AHI CPAP Mode وضع CPAP CPAP CPAP APAP APAP Bi-Level مستوى ثنائي ASV ASV RX Pressure ضغط آر إكس Doctors / Clinic Information معلومات الأطباء / العيادة Doctors Name اسم الطبيب Practice Name اسم الممارسة Patient ID رقم المريض &Cancel إلغاء &Back عودة &Next التالى Select Country حدد الدولة Welcome to the Open Source CPAP Analysis Reporter مرحبًا بكم في مراسل تحليل CPAP مفتوح المصدر (OSCAR) This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. تم تصميم هذا البرنامج لمساعدتك في مراجعة البيانات التي تنتجها أجهزة CPAP والأجهزة ذات الصلة. PLEASE READ CAREFULLY يرجى قراءة بعناية Accuracy of any data displayed is not and can not be guaranteed. دقة أي من البيانات المعروضة ليست ولا يمكن ضمانه. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. أي تقارير يتم إنشاؤها مخصصة للاستخدام الشخصي فقط ، وليست مناسبة بأي حال من الأحوال للامتثال أو لأغراض التشخيص الطبي. Use of this software is entirely at your own risk. استخدام هذا البرنامج بالكامل على مسؤوليتك الخاصة. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. تم إصدار OSCAR بحرية بموجب <a href='qrc:/COPYING'> GNU Public License v3 </a> ، ولا يأتي مع أي ضمان ، وبدون أي مطالبات بالرشاقة لأي غرض من الأغراض. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. الغرض من OSCAR هو مجرد عارض للبيانات ، وبالتأكيد ليس بديلاً عن التوجيه الطبي المختص من طبيبك. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. لن يتحمل المؤلفون مسؤولية <u> أي شيء </ u> يتعلق باستخدام أو سوء استخدام هذا البرنامج. Please provide a username for this profile يرجى تقديم اسم مستخدم لملف التعريف هذا Passwords don't match كلمات المرور غير متطابقة Profile Changes التغييرات الشخصية Accept and save this information? قبول وحفظ هذه المعلومات؟ &Finish إنهاء &Close this window أغلق هذه النافذة Overview Range: نطاق: Last Week الاسبوع الماضى Last Two Weeks الاسبوعين الماضيين Last Month الشهر الماضي Last Two Months آخر شهرين Last Three Months آخر ثلاثة أشهر Last 6 Months آخر 6 أشهر Last Year العام الماضي Everything كل شىء Custom مخصص Snapshot Start: بداية: End: النهاية: Reset view to selected date range إعادة تعيين العرض إلى نطاق التاريخ المحدد Toggle Graph Visibility تبديل رؤية الرسم البياني Drop down to see list of graphs to switch on/off. المنسدلة لرؤية قائمة من الرسوم البيانية لتشغيل / إيقاف. Graphs الرسوم البيانية Respiratory Disturbance Index تنفسي إزعاج فهرس Apnea Hypopnea Index توقف التنفس أثناء ضعف التنفس فهرس Usage استعمال Usage (hours) استعمال (ساعات) Session Times أوقات الجلسة Total Time in Apnea الوقت الإجمالي في انقطاع النفس Total Time in Apnea (Minutes) الوقت الإجمالي في انقطاع النفس (الدقائق) Body Mass Index الجسم كتلة فهرس How you felt (0-10) كيف شعرت (0-10) 10 of 10 Charts Show all graphs عرض كل الرسوم البيانية Hide all graphs إخفاء جميع الرسوم البيانية OximeterImport Oximeter Import Wizard مقياس التأكسج استيراد معالج Skip this page next time. تخطي هذه الصفحة في المرة القادمة. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? من أين تريد الاستيراد من؟ <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. مستخدمو CMS50E / F ، عند الاستيراد مباشرة ، يرجى عدم تحديد تحميل على جهازك حتى يطالبك OSCAR. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>إذا تم تمكينه ، فسيقوم OSCAR تلقائيًا بإعادة ضبط الساعة الداخلية لـ CMS50 باستخدام الوقت الحالي لأجهزة الكمبيوتر.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>هنا يمكنك إدخال اسم مكون من 7 أحرف لهذا التأكسج.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>هذا الخيار سوف يمحو الجلسة المستوردة من مقياس التأكسج الخاص بك بعد اكتمال الاستيراد. </p> <p> استخدم بحذر ، لأنه إذا حدث خطأ ما قبل حفظ OSCAR لجلستك ، فلن تتمكن من استعادته.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>يسمح لك هذا الخيار باستيراد (عبر الكابل) من التسجيلات الداخلية لمقاييس التأكسج. بعد تحديد هذا الخيار ، سيتطلب منك مقياس التأكسج القديم في Contec استخدام قائمة الجهاز لبدء التحميل.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>إذا كنت لا تمانع في أن تكون مرتبطًا بجهاز كمبيوتر يعمل بين عشية وضحاها ، فإن هذا الخيار يوفر رسمًا بيانيًا تخطيطيًا plethysomogram مفيدًا ، والذي يعطي إشارة إلى إيقاع القلب ، أعلى قراءات التأكسج الطبيعية.</p></body></html> Record attached to computer overnight (provides plethysomogram) سجل مرفق بجهاز الكمبيوتر طوال الليل (يوفر plethysomnogram) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>يسمح لك هذا الخيار بالاستيراد من ملفات البيانات التي تم إنشاؤها بواسطة البرنامج الذي يأتي مع مقياس التأكسج النبضي ، مثل SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review استيراد من ملف بيانات تم حفظه بواسطة برنامج آخر ، مثل SpO2Review Please connect your oximeter device يرجى توصيل جهاز قياس التأكسج If you can read this, you likely have your oximeter type set wrong in preferences. إذا كنت تستطيع قراءة هذا ، فمن المحتمل أن يكون لديك نوع مقياس التأكسج مضبوطًا في التفضيلات. Please connect your oximeter device, turn it on, and enter the menu يرجى توصيل جهاز قياس التأكسج ، وتشغيله ، وإدخال القائمة Press Start to commence recording اضغط على "ابدأ" لبدء التسجيل Show Live Graphs عرض الرسوم البيانية الحية Duration المدة الزمنية Pulse Rate معدل النبض Multiple Sessions Detected تم الكشف عن جلسات متعددة Start Time وقت البدء Details تفاصيل Import Completed. When did the recording start? اكتمل الاستيراد. متى بدأ التسجيل؟ Oximeter Starting time مقياس التأكسج وقت البدء I want to use the time reported by my oximeter's built in clock. أرغب في استخدام الوقت الذي تم الإبلاغ عنه بواسطة مقياس التأكسج الخاص بي المدمج في الساعة. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. لقد بدأت في تسجيل مقياس التأكسج هذا (أو بالقرب منه) في نفس الوقت كجلسة على جهاز CPAP الخاص بي. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>ملاحظة: ستكون المزامنة مع وقت بدء جلسة CPAP أكثر دقة دائمًا.</p></body></html> Choose CPAP session to sync to: اختر جلسة CPAP للمزامنة مع: You can manually adjust the time here if required: يمكنك ضبط الوقت هنا يدويًا إذا لزم الأمر: HH:mm:ssap &Cancel إلغاء &Information Page صفحة المعلومات CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 Set device date/time ضبط تاريخ الجهاز / الوقت <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>تحقق لتمكين تحديث معرف الجهاز التالي للاستيراد ، وهو أمر مفيد لأولئك الذين لديهم أجهزة تأكسج متعددة.</p></body></html> Set device identifier ضبط معرف الجهاز Erase session after successful upload محو الجلسة بعد نجاح التحميل Import directly from a recording on a device استيراد مباشرة من تسجيل على جهاز <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">تذكير لمستخدمي CPAP:</span><span style=" color:#fb0000;">هل تذكر أن تستورد جلسات CPAP أولاً؟<br/></span>إذا نسيت ، فلن يكون لديك وقت صالح لمزامنة جلسة التأكسج هذه مع.<br/>لضمان مزامنة جيدة بين الأجهزة ، حاول دائمًا تشغيل كليهما في نفس الوقت.</p></body></html> Please choose which one you want to import into OSCAR يرجى اختيار أي واحد تريد استيراده إلى OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body>يحتاج OSCAR إلى وقت بدء لمعرفة مكان حفظ جلسة التأكسج هذه إلى.<p></p><p>اختيار واحد من الخيارات التالية:</p></body></html> &Retry وإعادة المحاولة &Choose Session اختيار الجلسة &End Recording &Sync and Save وتسجيل نهاية &Save and Finish حفظ وإنهاء &Start بداية Scanning for compatible oximeters Could not detect any connected oximeter devices. المسح الضوئي لمقاييس التأكسج المتوافقة. Connecting to %1 Oximeter الاتصال بـ %1 Oximeter Renaming this oximeter from '%1' to '%2' إعادة تسمية مقياس التأكسج من '%1' إلى '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. يختلف اسم Oximeter .. إذا كان لديك واحدًا فقط وتقوم بمشاركته بين ملفات التعريف ، فاضبط الاسم على كل من ملفي التعريف. "%1", session %2 "%1" ، الجلسة %2 Nothing to import لا شيء للاستيراد Your oximeter did not have any valid sessions. لم يكن لديك مقياس التأكسج أي جلسات صالحة. Close قريب Waiting for %1 to start في انتظار %1 لبدء Waiting for the device to start the upload process... في انتظار أن يبدأ الجهاز عملية التحميل ... Select upload option on %1 حدد خيار التحميل على %1 You need to tell your oximeter to begin sending data to the computer. تحتاج إلى إخبار مقياس التأكسج لديك لبدء إرسال البيانات إلى الكمبيوتر. Please connect your oximeter, enter it's menu and select upload to commence data transfer... يرجى توصيل مقياس التأكسج ، وإدخاله في القائمة وتحديد التحميل لبدء نقل البيانات ... %1 device is uploading data... يقوم %1 جهاز بتحميل البيانات ... Please wait until oximeter upload process completes. Do not unplug your oximeter. الرجاء الانتظار حتى تكتمل عملية تحميل مقياس التأكسج. لا افصل مقياس التأكسج. Oximeter import completed.. اكتمال استيراد مقياس التأكسج .. Select a valid oximetry data file حدد ملف بيانات قياس صالح Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) ملفات قياس التأكسج (* .spo * .spor * .spo2 * .SpO2 * .dat) No Oximetry module could parse the given file: لا توجد وحدة لقياس التأكسج يمكنها تحليل الملف المحدد: Live Oximetry Mode وضع قياس التأكسج الحي Live Oximetry Stopped Oximetry لايف متوقف Live Oximetry import has been stopped تم إيقاف استيراد مقياس التأكسج المباشر Oximeter Session %1 جلسة التأكسج %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. يمنحك OSCAR القدرة على تتبع بيانات Oximetry جنبًا إلى جنب مع بيانات جلسة CPAP ، والتي يمكن أن تعطي نظرة ثاقبة حول فعالية علاج CPAP. سيعمل أيضًا بشكل مستقل مع مقياس تأكسج نبضك ، مما يتيح لك تخزين وتتبع ومراجعة البيانات المسجلة. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! إذا كنت تحاول مزامنة قياس التأكسج وبيانات CPAP ، فيرجى التأكد من استيراد جلسات CPAP أولاً قبل المتابعة! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. لكي يتمكن OSCAR من تحديد موقع وقراءة مباشرة من جهاز Oximeter ، تحتاج إلى التأكد من تثبيت برامج تشغيل الجهاز الصحيحة (على سبيل المثال ، USB إلى Serial UART) على جهاز الكمبيوتر الخاص بك. لمزيد من المعلومات حول هذا ، %1 انقر هنا %2. Oximeter not detected مقياس التأكسج لم يتم الكشف عنه Couldn't access oximeter لا يمكن الوصول إلى مقياس التأكسج Starting up... بدء... If you can still read this after a few seconds, cancel and try again إذا كان لا يزال بإمكانك قراءة هذا بعد بضع ثوانٍ ، فقم بالإلغاء والمحاولة مرة أخرى Live Import Stopped استيراد لايف متوقف %1 session(s) on %2, starting at %3 %1 جلسة (جلسات) على %2 ، تبدأ من %3 No CPAP data available on %1 لا تتوفر بيانات CPAP على %1 Recording... تسجيل... Finger not detected الاصبع لم يتم اكتشافه I want to use the time my computer recorded for this live oximetry session. أريد استخدام الوقت الذي سجله جهاز الكمبيوتر الخاص بي لجلسة قياس التأكسج المباشرة هذه. I need to set the time manually, because my oximeter doesn't have an internal clock. أحتاج إلى ضبط الوقت يدويًا ، لأن مقياس التأكسج لا يحتوي على ساعة داخلية. Something went wrong getting session data حدث خطأ ما في الحصول على بيانات الجلسة Welcome to the Oximeter Import Wizard مرحبًا بك في معالج استيراد Oximeter Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. مقياس تأكسج النبض عبارة عن أجهزة طبية تستخدم لقياس تشبع الأكسجين في الدم. خلال أحداث انقطاع النفس الموسعة وأنماط التنفس غير الطبيعية ، يمكن أن تنخفض مستويات تشبع الأكسجين في الدم بشكل كبير ، ويمكن أن تشير إلى المشكلات التي تحتاج إلى عناية طبية. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR متوافق حاليًا مع مقياس التأكسج التسلسلي لـ Contec CMS50D + و CMS50E و CMS50F و CMS50I.<br/>(ملاحظة: الاستيراد المباشر من طرز البلوتوث هو<span style=" font-weight:600;">على الاغلب لا</span>ممكن بعد) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. قد ترغب في ملاحظة أن شركات أخرى ، مثل Pulox ، تقوم ببساطة بإعادة تكوين Contec CMS50 تحت أسماء جديدة ، مثل Pulox PO-200 ، PO-300 ، PO-400. هذه يجب أن تعمل أيضا. It also can read from ChoiceMMed MD300W1 oximeter .dat files. كما يمكن قراءتها من ChoiceMMed MD300W1 oximeter .dat الملفات. Please remember: أرجوك تذكر: Important Notes: ملاحظات هامة: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. لا تملك أجهزة + Contec CMS50D ساعة داخلية ، ولا تسجل وقت بدء. إذا لم يكن لديك جلسة CPAP لربط التسجيل بها ، فسيتعين عليك إدخال وقت البدء يدويًا بعد اكتمال عملية الاستيراد. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. حتى بالنسبة للأجهزة المزودة بساعة داخلية ، لا يزال من المستحسن الدخول في عادة بدء تشغيل سجلات مقياس التأكسج في نفس وقت جلسات CPAP ، لأن الساعات الداخلية لـ CPAP تميل إلى الانجراف بمرور الوقت ، ولا يمكن إعادة تعيينها جميعها بسهولة. Oximetry Date تاريخ d/MM/yy h:mm:ss AP R&eset إعادة تعيين Pulse نبض &Open .spo/R File افتح. spo/R ملف Serial &Import استيراد المسلسل &Start Live بدء لايف Serial Port منفذ تسلسلي &Rescan Ports منافذ إعادة التفتيش PreferencesDialog Preferences تفضيلات &Import استيراد Combine Close Sessions الجمع بين جلسات مغلقة Minutes الدقائق Multiple sessions closer together than this value will be kept on the same day. سيتم الاحتفاظ بجلسات متعددة أقرب من هذه القيمة في نفس اليوم. Ignore Short Sessions تجاهل الجلسات القصيرة <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">لن يتم عرض الجلسات الأقصر من المدة<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time يوم سبليت الوقت Sessions starting before this time will go to the previous calendar day. ستذهب الجلسات التي تبدأ قبل هذا الوقت إلى اليوم الميلادي السابق. Session Storage Options خيارات تخزين الجلسة Changing SD Backup compression options doesn't automatically recompress backup data. لا يؤدي تغيير خيارات ضغط نسخ احتياطي SD إلى إعادة ضغط بيانات النسخ الاحتياطي تلقائيًا. Compress SD Card Backups (slower first import, but makes backups smaller) ضغط النسخ الاحتياطي لبطاقة SD (الاستيراد الأول أبطأ ، ولكن يجعل النسخ الاحتياطية أصغر) &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. تعتبر الأيام مع تحت هذا الاستخدام بأنها "غير مكتملة". عادة ما تعتبر 4 ساعات متوافقة. hours ساعات Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. تمكين / تعطيل تحسينات الإبلاغ عن الحدث التجريبي. انها تسمح للكشف عن أحداث الشريط الحدودي ، وبعض آلة غاب. يجب تمكين هذا الخيار قبل الاستيراد ، وإلا فالتطهير مطلوب. Flow Restriction تقييد التدفق Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. النسبة المئوية للقيود المفروضة على تدفق الهواء من القيمة المتوسطة. قيمة 20 ٪ تعمل بشكل جيد للكشف عن انقطاع النفس. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">وضع علامة مخصصة هي طريقة تجريبية للكشف عن الأحداث التي فاتتها الآلة. هم انهم<span style=" text-decoration: underline;">ليس</span> المدرجة في AHI.</p></body></html> Duration of airflow restriction مدة تقييد تدفق الهواء s s Event Duration مدة الحدث Allow duplicates near machine events. السماح بالتكرارات بالقرب من أحداث الجهاز. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. يضبط مقدار البيانات التي تم النظر فيها لكل نقطة في الرسم البياني AHI / Hour. الافتراضات إلى 60 دقيقة .. نوصي بشدة تركها عند هذه القيمة. minutes الدقائق Reset the counter to zero at beginning of each (time) window. إعادة تعيين العداد إلى صفر في بداية كل نافذة (الوقت). Zero Reset إعادة تعيين صفر CPAP Clock Drift الانجراف CPAP على مدار الساعة Do not import sessions older than: لا تستورد جلسات أقدم من: Sessions older than this date will not be imported لن يتم استيراد الجلسات الأقدم من هذا التاريخ dd MMMM yyyy User definable threshold considered large leak عتبة تعريف المستخدم تعتبر تسرب كبير Whether to show the leak redline in the leak graph سواء لإظهار الخط الأحمر للتسرب في الرسم البياني للتسرب Search بحث &Oximetry التأكسج Show in Event Breakdown Piechart تظهر في مخطط انهيار مخطط الأحداث Resync Machine Detected Events (Experimental) الأحداث التي تم اكتشافها في آلة إعادة المزامنة (تجريبية) Percentage drop in oxygen saturation انخفاض النسبة المئوية في تشبع الأكسجين Pulse نبض Sudden change in Pulse Rate of at least this amount التغيير المفاجئ في معدل النبض لا يقل عن هذا المبلغ bpm نبضة في الدقيقة Minimum duration of drop in oxygen saturation الحد الأدنى لمدة انخفاض في تشبع الأكسجين Minimum duration of pulse change event. الحد الأدنى لمدة الحدث تغيير النبض. Small chunks of oximetry data under this amount will be discarded. سيتم تجاهل أجزاء صغيرة من بيانات قياس التأكسج تحت هذا المبلغ. &General جنرال لواء Changes to the following settings needs a restart, but not a recalc. التغييرات في الإعدادات التالية تحتاج إلى إعادة تشغيل ، ولكن ليس إعادة حساب. Preferred Calculation Methods طرق الحساب المفضلة Middle Calculations الحسابات الوسطى Upper Percentile النسبة المئوية العليا Session Splitting Settings إعدادات تقسيم الجلسة <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">يجب استخدام هذا الإعداد بحذر ...</span>يأتي إيقاف تشغيله مع عواقب تتضمن دقة ملخص أيام فقط ، حيث تعمل بعض الحسابات فقط بشكل صحيح بشرط أن يتم الاحتفاظ فقط بجلسات الملخص التي جاءت من سجلات اليوم الفردية.</p><p><span style=" font-weight:600;">المستخدمين ResMed:</span>لمجرد أنه يبدو من الطبيعي لك ولأنه يجب أن تتم إعادة تشغيل جلسة 12 ظهراً في اليوم السابق ، فهذا لا يعني أن بيانات ResMed تتفق معنا. يحتوي تنسيق فهرس الملخص STF.edf على نقاط ضعف خطيرة تجعل القيام بذلك ليس فكرة جيدة.</p><p>يوجد هذا الخيار لتهدئة أولئك الذين لا يهتمون ويريدون رؤية هذا "ثابت" بغض النظر عن التكاليف ، ولكن معرفة أنه يأتي مع تكلفة. إذا احتفظت ببطاقة SD الخاصة بك في كل ليلة ، وقمت بالاستيراد مرة واحدة على الأقل في الأسبوع ، فلن تظهر لك مشاكل في كثير من الأحيان.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) لا تنقسم أيام الملخص (تحذير: اقرأ تلميح الأدوات!) Memory and Startup Options خيارات الذاكرة وبدء التشغيل Pre-Load all summary data at startup قبل تحميل جميع البيانات الموجزة عند بدء التشغيل <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>يحتفظ هذا الإعداد ببيانات الطول الموجي والحدث في الذاكرة بعد استخدامها لتسريع أيام الزيارة. هذا ليس حقًا خيارًا ضروريًا ، لأن نظام التشغيل الخاص بك يقوم بتخزين الملفات المستخدمة مسبقًا أيضًا. التوصية هي إيقاف تشغيله ، ما لم يكن الكمبيوتر يحتوي على طن من الذاكرة.</p></body></html> Keep Waveform/Event data in memory الحفاظ على بيانات الموجي / الحدث في الذاكرة <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>يخفض من أي مربعات حوار تأكيد غير مهم أثناء الاستيراد.</p></body></html> Import without asking for confirmation استيراد دون طلب التأكيد General CPAP and Related Settings CPAP العامة والإعدادات ذات الصلة Enable Unknown Events Channels تمكين قنوات أحداث غير معروفة AHI Apnea Hypopnea Index AHI RDI Respiratory Disturbance Index RDI AHI/Hour Graph Time Window AHI / ساعة الرسم البياني الوقت النافذة Preferred major event index مؤشر الحدث الرئيسي المفضل Compliance defined as الامتثال كما هو محدد Flag leaks over threshold تسرب العلم فوق العتبة Seconds ثواني <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>ملاحظة: هذا غير مخصص للتصحيحات الزمنية! تأكد من ضبط ساعة ونظام التشغيل الخاصين بك بشكل صحيح.</p></body></html> Hours ساعات Your masks vent rate at 20 cmH2O pressure معدل تنفيس الأقنعة عند ضغط 20 cmH2O Your masks vent rate at 4 cmH2O pressure تنفيس الأقنعة الخاصة بك عند ضغط 4 cmH2O For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. للتناسق ، يجب على مستخدمي ResMed استخدام 95 ٪ هنا ، لأن هذه هي القيمة الوحيدة المتاحة في الأيام الموجزة فقط. Median is recommended for ResMed users. ينصح الوسيط لمستخدمي ResMed. Median الوسيط Weighted Average متوسط الوزن Normal Average متوسط عادي True Maximum الحد الأقصى الحقيقي 99% Percentile 99 ٪ في المئة Maximum Calcs الحسابات القصوى General Settings الاعدادات العامة Daily view navigation buttons will skip over days without data records ستتخطى أزرار التنقل في العرض اليومي على مدار أيام بدون سجلات بيانات Skip over Empty Days تخطي أيام فارغة Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. السماح باستخدام نوى وحدة المعالجة المركزية المتعددة حيثما كان ذلك متاحًا لتحسين الأداء. يؤثر بشكل رئيسي على المستورد. Enable Multithreading تمكين تعدد العمليات Bypass the login screen and load the most recent User Profile تجاوز شاشة تسجيل الدخول وتحميل أحدث ملف تعريف المستخدم Create SD Card Backups during Import (Turn this off at your own peril!) إنشاء نسخ احتياطية لبطاقة SD أثناء الاستيراد (قم بإيقاف تشغيل هذا على مسؤوليتك الخاصة!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>الحد الأقصى الحقيقي هو الحد الأقصى لمجموعة البيانات. 99 في المئة مرشحات خارج أندر القيم المتطرفة.</p></body></html> Combined Count divided by Total Hours الجمع بين عدد مقسوما على إجمالي ساعات Time Weighted average of Indice متوسط الوقت المرجح للمؤشر Standard average of indice متوسط قياسي من indice Custom CPAP User Event Flagging عرف حدث المستخدم CPAP Events أحداث Reset &Defaults إعادة تعيين الإعدادات الافتراضية <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">تحذير:</span>فقط لأنك تستطيع ، لا يعني أنها ممارسة جيدة.</p></body></html> Waveforms الطول الموجي Flag rapid changes in oximetry stats علم التغيرات السريعة في احصائيات قياس التأكسج Other oximetry options خيارات قياس التأكسج الأخرى Discard segments under تجاهل شرائح تحت Flag Pulse Rate Above علم معدل النبض أعلاه Flag Pulse Rate Below معدل نبض العلم أدناه Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. ضغط النسخ الاحتياطية ResMed (EDF) لتوفير مساحة على القرص. يتم تخزين ملفات EDF التي تم نسخها احتياطيًا بتنسيق .gz ، وهو أمر شائع على منصات Mac و Linux .. يمكن استيراد OSCAR من دليل النسخ الاحتياطي المضغوط هذا بشكل أصلي .. لاستخدامه مع ResScan سيتطلب ملفات .gz أن تكون غير مضغوطة أولاً .. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. تؤثر الخيارات التالية على مقدار مساحة القرص التي يستخدمها OSCAR ، ويكون لها تأثير على المدة التي يستغرقها الاستيراد. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. هذا يجعل بيانات OSCAR تأخذ حوالي نصف مساحة كبيرة. لكنه يجعل الاستيراد وتغيير اليوم يستغرق وقتًا أطول .. إذا كنت تمتلك كمبيوترًا جديدًا به قرص صلب صغير ، فهذا خيار جيد. Compress Session Data (makes OSCAR data smaller, but day changing slower.) ضغط بيانات الجلسة (يجعل بيانات OSCAR أصغر ، لكن تغيير اليوم يكون أبطأ.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) هذا يحتفظ بنسخة احتياطية من بيانات بطاقة SD لأجهزة ResMed ، تحذف ماكينات سلسلة ResMed S9 بيانات عالية الدقة يزيد عمرها عن 7 أيام ، وبيانات الرسم البياني الأقدم من 30 يومًا .. يمكن لـ OSCAR الاحتفاظ بنسخة من هذه البيانات إذا احتجت إلى إعادة التثبيت. (موصى به للغاية ، إلا إذا كانت لديك مساحة قصيرة على القرص أو لا تهتم ببيانات الرسم البياني) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>يجعل تشغيل OSCAR أبطأ قليلاً ، عن طريق التحميل المسبق لجميع بيانات الملخص مقدمًا ، مما يسرع من استعراض النظرة العامة وبعض الحسابات الأخرى لاحقًا. إذا كان لديك كمية كبيرة من البيانات ، فقد يكون من المفيد إيقاف تشغيل هذا الخيار ، ولكن إذا كنت ترغب عادةً في العرض<span style=" font-style:italic;">كل شىء</span>في نظرة عامة ، لا يزال يتعين تحميل جميع البيانات الموجزة على أي حال. </p> <p> لاحظ أن هذا الإعداد لا يؤثر على شكل الموجة وبيانات الأحداث ، والتي يتم تحميلها دائمًا حسب الحاجة.</p></body></html> This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. يحاول هذا الخيار التجريبي استخدام نظام الإبلاغ عن الأحداث الخاص بـ OSCAR لتحسين وضع الحدث الذي تم اكتشافه بواسطة الجهاز. Show flags for machine detected events that haven't been identified yet. إظهار علامات الأحداث التي تم اكتشافها بواسطة الجهاز والتي لم يتم تحديدها بعد. Show Remove Card reminder notification on OSCAR shutdown إظهار إزالة إشعار تذكير البطاقة عند إيقاف تشغيل OSCAR Check for new version every تحقق من وجود نسخة جديدة كل days. أيام. Last Checked For Updates: آخر فحص للتحقق من وجود تحديثات: TextLabel تسمية النص I want to be notified of test versions. (Advanced users only please.) &Appearance مظهر خارجي Graph Settings إعدادات الرسم البياني <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>أي علامة تبويب لفتح على تحميل ملف التعريف. (ملاحظة: سيتم التعيين على ملف التعريف إذا تم ضبط OSCAR على عدم فتح ملف تعريف عند بدء التشغيل)</p></body></html> Bar Tops شريط بلايز Line Chart خط الرسم البياني Overview Linecharts نظرة عامة على المخططات الخطية Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. حاول تغيير هذا من الإعداد الافتراضي (Desktop OpenGL) إذا كنت تواجه مشاكل في تقديم الرسوم البيانية الخاصة بـ OSCAR. <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>هذا يجعل التمرير عند التكبير أسهل على لوحات اللمس الحساسة ثنائية الاتجاه. ينصح 50ms القيمة.</p></body></html> How long you want the tooltips to stay visible. إلى متى تريد أن تظل تلميحات الأدوات مرئية. Scroll Dampening التمرير الملطف Tooltip Timeout تلميح الأدوات Default display height of graphs in pixels الارتفاع الافتراضي لعرض الرسوم البيانية بالبكسل Graph Tooltips الرسم البياني تلميحات The visual method of displaying waveform overlay flags. الطريقة البصرية لعرض أعلام تراكب الموجي. Standard Bars أشرطة القياسية Top Markers أعلى علامات Graph Height الرسم البياني الارتفاع Auto-Launch CPAP Importer after opening profile التشغيل التلقائي للمستورد CPAP بعد فتح ملف التعريف Automatically load last used profile on start-up تحميل آخر ملف تعريف مستخدم تلقائيًا عند بدء التشغيل <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>قم بتوفير تنبيه عند استيراد البيانات من أي طراز آلة لم يتم اختباره من قبل مطوري OSCAR.</p></body></html> Warn when importing data from an untested machine التحذير عند استيراد البيانات من جهاز لم يتم اختباره <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>قدم تنبيهًا عند استيراد البيانات التي تختلف بطريقة أو بأخرى عن أي شيء سبق لمطوري OSCAR رؤيته.</p></body></html> Warn when previously unseen data is encountered التحذير عند مواجهة بيانات لم يتم رؤيتها من قبل This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. يتطلب هذا الحساب توفير بيانات إجمالي التسريبات بواسطة جهاز CPAP. (على سبيل المثال ، PRS1 ، ولكن ليس ResMed ، التي لديها بالفعل) حسابات التسرب غير المقصود المستخدمة هنا خطية ، فهي لا تصمم منحنى تنفيس القناع. إذا كنت تستخدم بعض الأقنعة المختلفة ، فاختر متوسط القيم بدلاً من ذلك. يجب أن تظل قريبة بما فيه الكفاية. Calculate Unintentional Leaks When Not Present حساب التسريبات غير المقصودة عندما لا تكون موجودة 4 cmH2O 4 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. ملاحظة: يتم استخدام طريقة الحساب الخطي. يتطلب تغيير هذه القيم إعادة حساب. l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">ملحوظة:</span>نظرًا لقيود التصميم الموجزة ، لا تدعم أجهزة ResMed تغيير هذه الإعدادات.</p></body></html> Oximetry Settings إعدادات قياس التأكسج <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Always save screenshots in the OSCAR Data folder احفظ لقطات الشاشة دائمًا في مجلد بيانات OSCAR Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR On Opening في الافتتاح Profile الملف الشخصي Welcome أهلا بك Daily اليومي Statistics الإحصاء Switch Tabs تبديل علامات التبويب No change لا تغيير After Import بعد الاستيراد Overlay Flags تراكب الأعلام Line Thickness سمك الخط The pixel thickness of line plots سماكة البيكسل للخط Other Visual Settings الإعدادات البصرية الأخرى Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. تطبيق مكافحة التعرج ينطبق على تجانس مؤامرات الرسم البياني .. بعض المؤامرات تبدو أكثر جاذبية مع هذا على. هذا يؤثر أيضًا على التقارير المطبوعة. جرب هذا وانظر إذا ما كان يعجبك. Use Anti-Aliasing استخدام مكافحة التعرج Makes certain plots look more "square waved". يجعل بعض المؤامرات تبدو أكثر "لوح مربع". Square Wave Plots مؤامرات موجة مربعة Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Pixmap التخزين المؤقت هو تقنية تسريع الرسومات. قد يسبب مشاكل في رسم الخطوط في منطقة عرض الرسم البياني على النظام الأساسي الخاص بك. Use Pixmap Caching استخدام Pixmap التخزين المؤقت <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>هذه الميزات تم تشذيبها مؤخرًا. سوف يعودون لاحقا. </p></body></html> Animations && Fancy Stuff الرسوم المتحركة والأشياء الهوى Whether to allow changing yAxis scales by double clicking on yAxis labels ما إذا كنت تريد تغيير مقاييس yAxis بالنقر المزدوج على تسميات المحور y Allow YAxis Scaling السماح لتوسيع نطاق المحور ص Whether to include machine serial number on machine settings changes report سواء لتضمين الرقم التسلسلي للجهاز في تقرير تغييرات إعدادات الجهاز Include Serial Number تشمل الرقم التسلسلي Graphics Engine (Requires Restart) محرك الرسومات (يتطلب إعادة التشغيل) Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Fonts (Application wide settings) الخطوط (إعدادات التطبيق الواسعة) Font الخط Size بحجم Bold بالخط العريض Italic مائل Application تطبيق Graph Text نص الرسم البياني Graph Titles عناوين الرسم البياني Big Text نص كبير Details تفاصيل &Cancel إلغاء &Ok حسنا Name اسم Color اللون Flag Type نوع العلم Label ضع الكلمة المناسبة CPAP Events أحداث CPAP Oximeter Events أحداث مقياس التأكسج Positional Events الأحداث الموقفية Sleep Stage Events أحداث مرحلة النوم Unknown Events أحداث غير معروفة Double click to change the descriptive name this channel. انقر نقرًا مزدوجًا لتغيير الاسم الوصفي لهذه القناة. Double click to change the default color for this channel plot/flag/data. انقر مرتين لتغيير اللون الافتراضي لهذه القناة مؤامرة / العلم / البيانات. Overview نظرة عامة <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>يرجى الملاحظة:</b>قدرات تقسيم جلسة OSCAR المتقدمة غير ممكنة<b>ResMed</b>آلات بسبب وجود قيود في طريقة تخزين إعداداتها وبيانات الملخص ، وبالتالي تم تعطيلها لهذا الملف الشخصي.</p><p>على أجهزة ResMed ، سيتم تقسيم الأيام عند الظهر كما هو الحال في برنامج ResMed التجاري.</p> Double click to change the descriptive name the '%1' channel. انقر نقرًا مزدوجًا لتغيير الاسم الوصفي للقناة "%1". Whether this flag has a dedicated overview chart. ما إذا كان هذا العلم لديه مخطط نظرة عامة مخصصة. Here you can change the type of flag shown for this event هنا يمكنك تغيير نوع العلم الموضح لهذا الحدث This is the short-form label to indicate this channel on screen. هذا هو التسمية القصيرة للإشارة إلى هذه القناة على الشاشة. This is a description of what this channel does. هذا وصف لما تفعله هذه القناة. Lower خفض Upper أعلى CPAP Waveforms CPAP الموجي Oximeter Waveforms الموجي مقياس التأكسج Positional Waveforms الموجي الموضعي Sleep Stage Waveforms مرحلة النوم الطول الموجي Whether a breakdown of this waveform displays in overview. ما إذا كان انهيار لهذا الشكل الموجي يعرض في نظرة عامة. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform هنا يمكنك تعيين الحد الأدنى <b> الأدنى </b> المستخدم لحسابات معينة على شكل الموجة %1 Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform هنا يمكنك تعيين الحد الأعلى المستخدم لحسابات معينة على شكل الموجة %1 Data Processing Required معالجة البيانات المطلوبة A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? مطلوب إجراء إعادة / إلغاء ضغط البيانات لتطبيق هذه التغييرات. قد تستغرق هذه العملية بضع دقائق حتى تكتمل. هل أنت متأكد أنك تريد إجراء هذه التغييرات؟ Data Reindex Required إعادة فهرسة البيانات المطلوبة A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? يلزم إجراء إعادة فهرسة البيانات لتطبيق هذه التغييرات. قد تستغرق هذه العملية بضع دقائق حتى تكتمل. هل أنت متأكد أنك تريد إجراء هذه التغييرات؟ Restart Required إعادة التشغيل المطلوبة One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? يتطلب إجراء واحد أو أكثر من التغييرات التي أجريتها إعادة تشغيل هذا التطبيق ، حتى تدخل هذه التغييرات حيز التنفيذ. هل ترغب في القيام بذلك الآن؟ If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. إذا احتجت إلى إعادة استيراد هذه البيانات مرة أخرى (سواء في OSCAR أو ResScan) ، فلن تعود هذه البيانات. If you need to conserve disk space, please remember to carry out manual backups. إذا كنت بحاجة إلى الحفاظ على مساحة القرص ، يرجى تذكر القيام بنسخ احتياطية يدوية. Are you sure you want to disable these backups? هل أنت متأكد من أنك تريد تعطيل هذه النسخ الاحتياطية؟ Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. لا يعد إيقاف تشغيل النسخ الاحتياطية فكرة جيدة ، لأن OSCAR يحتاج إلى إعادة إنشاء قاعدة البيانات في حالة العثور على أخطاء. Are you really sure you want to do this? هل أنت متأكد أنك تريد فعل ذلك؟ %1 %2 %1 %2 Flag علم Minor Flag العلم الصغرى Span امتداد Always Minor دائما الصغرى No CPAP machines detected لم يتم الكشف عن آلات CPAP Will you be using a ResMed brand machine? هل ستستخدم آلة العلامة التجارية ResMed؟ Never أبدا This may not be a good idea قد لا تكون هذه فكرة جيدة ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). تقوم أجهزة ResMed S9 بحذف بيانات معينة بشكل روتيني من بطاقة SD الخاصة بك التي تزيد مدتها عن 7 و 30 يومًا (حسب الدقة). ProfileSelector Filter: منقي: Reset filter to see all profiles إعادة تعيين مرشح لرؤية جميع ملامح Version الإصدار &Open Profile فتح الملف الشخصي &Edit Profile تعديل الملف الشخصي &New Profile الملف الشخصي الجديد Profile: None الملف الشخصي: لا شيء Please select or create a profile... الرجاء تحديد أو إنشاء ملف تعريف ... Destroy Profile تدمير الملف الشخصي Profile الملف الشخصي Ventilator Brand جهاز التنفس الصناعي Ventilator Model نموذج التنفس الصناعي Other Data بيانات أخرى Last Imported آخر مستورد Name اسم You must create a profile يجب عليك إنشاء ملف تعريف Enter Password for %1 أدخل كلمة المرور لـ %1 You entered an incorrect password لقد أدخلت كلمة مرور غير صحيحة Forgot your password? نسيت رقمك السري؟ Ask on the forums how to reset it, it's actually pretty easy. اسأل في المنتديات عن كيفية إعادة تعيينه ، إنه في الواقع سهل للغاية. Select a profile first حدد ملف تعريف أولاً The selected profile does not appear to contain any data and cannot be removed by OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. إذا كنت تحاول الحذف لأنك نسيت كلمة المرور ، فعليك إما إعادة تعيينها أو حذف مجلد ملف التعريف يدويًا. You are about to destroy profile '<b>%1</b>'. أنت على وشك تدمير الملف الشخصي '%1'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. فكر جيدًا ، لأن هذا سيؤدي إلى حذف ملف التعريف إلى جانب كل <b> بيانات النسخ الاحتياطي </b> المخزنة تحت %2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. أدخل الكلمة DELETE أدناه (تمامًا كما هو موضح) للتأكيد. DELETE Sorry آسف You need to enter DELETE in capital letters. تحتاج إلى إدخال DELETE بحروف كبيرة. There was an error deleting the profile directory, you need to manually remove it. حدث خطأ أثناء حذف دليل ملف التعريف ، تحتاج إلى إزالته يدويًا. Profile '%1' was succesfully deleted تم حذف الملف الشخصي '%1' بنجاح Bytes بايت KB KB MB MB GB GB TB TB PB PB Summaries: الملخصات: Events: الأحداث: Backups: النسخ الاحتياطي: Hide disk usage information إخفاء معلومات استخدام القرص Show disk usage information إظهار معلومات استخدام القرص Name: %1, %2 الاسم: %1 ،%2 Phone: %1 الهاتف: %1 Email: <a href='mailto:%1'>%1</a> البريد الإلكتروني: <a href='mailto:%1'>%1</a> Address: عنوان: No profile information given لم يتم تقديم معلومات الملف الشخصي Profile: %1 الملف الشخصي: %1 ProgressDialog Abort إحباط QObject No Data لايوجد بيانات Events أحداث Duration أحداث (% %1 in events) (٪ %1 في الأحداث) Jan كانون الثاني Feb شهر فبراير Mar مارس Apr أبريل May قد Jun يونيو Jul يوليو Aug أغسطس Sep سبتمبر Oct اكتوبر Nov شهر نوفمبر Dec ديسمبر ft قدم lb رطل oz أوقية Kg كلغ cmH2O cmH2O Med. الوسيط Min: %1 الحد الأدنى: %1 Min: الحد الأدنى: Max: أقصى: Max: %1 الحد الأقصى: %1 %1 (%2 days): %1 (%2 يوما): %1 (%2 day): %1 (%2 يوم): % in %1 ٪ في %1 Hours ساعات Min %1 الحد الأدنى %1 Hours: %1 الساعات: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 انخفاض الاستخدام، %2 لا فائدة ، خارج %3 أيام (%4٪ متوافق.) الطول: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 الجلسات: %1 / %2 / %3 الطول: %4 / %5 / %6 الأطول:%7 / %8 / %9 %1 Length: %3 Start: %2 %1 لمدة: %3 البداية: %2 Mask On قناع على Mask Off قناع قبالة %1 Length: %3 Start: %2 %1 المدة:%3 البداية:%2 TTIA: TTIA: TTIA: %1 Minutes الدقائق Seconds ثواني h h m m s s ms ms Events/hr الأحداث / ساعة Hz Hz bpm Litres ليتر ml مل Breaths/min الأنفاس / دقيقة ? ? Severity (0-1) درجة الخطورة (0-1) Degrees درجات Error خطأ Warning تحذير Information معلومات Busy مشغول Please Note يرجى الملاحظة Graphs Switched Off تم إيقاف تشغيل الرسوم البيانية Sessions Switched Off تم إغلاق الجلسات &Yes نعم &No لا &Cancel إلغاء &Destroy هدم &Save حفظ BMI BMI Weight وزن Zombie الاموات الاحياء Pulse Rate معدل النبض Plethy تخطيط التحجم Pressure الضغط Daily اليومي Profile الملف الشخصي Overview نظرة عامة Oximetry التأكسج Oximeter مقياس التأكسج Event Flags أعلام الحدث Default إفتراضي CPAP BiPAP Bi-Level Bi-Level EPAP EPAP Min EPAP Max EPAP IPAP IPAP Min IPAP Max IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier المرطب H H OA OA A A CA CA FL FL SA SA LE LE EP EP VS VS VS2 VS2 RERA RERA PP PP P P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF1 UF2 UF2 UF3 UF3 PS PS AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI EPI PB PB IE IE Insp. Time المفتش. زمن Exp. Time إكسب. زمن Resp. Event التركيب. حدث Flow Limitation الحد من التدفق Flow Limit الحد من التدفق SensAwake Pat. Trig. Breath تربيتة. علم حساب المثلثات. نفس Tgt. Min. Vent الهدف مين. منفس Target Vent. الهدف تنفيس. Minute Vent. تنفيس دقيقة. Tidal Volume حجم المد والجزر Resp. Rate التركيب. معدل Snore شخير Leak تسرب Leaks تسرب Large Leak تسرب كبير LL LL Total Leaks إجمالي التسريبات Unintentional Leaks تسرب غير مقصود MaskPressure قناع الضغط Flow Rate معدل المد و الجزر Sleep Stage مرحلة النوم Usage استعمال Sessions جلسات Pr. Relief العلاقات العامة. ارتياح No Data Available لا تتوافر بيانات App key: مفتاح التطبيق: Operating system: نظام التشغيل: Built with Qt %1 on %2 تم إنشاؤه باستخدام Qt %1 على %2 Graphics Engine: محرك الرسومات: Graphics Engine type: نوع محرك الرسومات: Software Engine محرك البرمجيات ANGLE / OpenGLES Desktop OpenGL m m cm cm in Only Settings and Compliance Data Available Summary Data Only Bookmarks إشارات مرجعية Mode الوضع Model نموذج Brand علامة تجارية Serial مسلسل Series سلسلة Machine آلة Channel قناة Settings الإعدادات Inclination ميل Orientation اتجاه Motion اقتراح Name اسم DOB تاريخ الميلاد Phone هاتف Address عنوان Email البريد الإلكتروني Patient ID رقم المريض Date تاريخ Bedtime وقت النوم Wake-up استيقظ Mask Time قناع الوقت Unknown مجهول None لا شيء Ready جاهز First أول Last الاخير Start بداية End النهاية On على Off إيقاف Yes نعم No لا Min دقيقة Max ماكس Med ميد Average معدل Median الوسيط Avg متوسط W-Avg متوسط الوزن Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine آلة غير قادرة على البيانات Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... يستعد... Machine Unsupported الجهاز غير مدعوم I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. نأسف للإبلاغ بأن OSCAR يمكنه فقط تتبع ساعات الاستخدام والإعدادات الأساسية جدًا لهذا الجهاز. Scanning Files... جارٍ فحص الملفات ... Importing Sessions... استيراد الجلسات ... Finishing up... الانتهاء من ... Machine Untested آلة لم تختبر Flex Lock فليكس لوك Whether Flex settings are available to you. ما إذا كانت إعدادات Flex متاحة لك. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition مقدار الوقت المستغرق للانتقال من EPAP إلى IPAP ، كلما زاد الرقم كلما كان الانتقال أبطأ Rise Time Lock ارتفاع قفل الوقت Whether Rise Time settings are available to you. ما إذا كانت إعدادات "وقت الارتفاع" متاحة لك. Rise Lock Rise Lock Mask Resistance Setting إعداد مقاومة القناع Mask Resist. Mask Resist. Hose Diam. Hose Diam. 15mm 15 mm 22mm 22 mm Backing Up Files... النسخ الاحتياطي للملفات ... Untested Data البيانات غير المختبرة model %1 DreamStation 2 unknown model CPAP-Check تحقق CPAP AutoCPAP AutoCPAP Auto-Trial Auto-Trial AutoBiLevel AutoBiLevel S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Mode وضع فليكس PRS1 pressure relief mode. وضع تخفيف الضغط PRS1. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time وقت الشروق Bi-Flex Bi-Flex Flex Flex Level فليكس المستوى PRS1 pressure relief setting. إعداد تخفيف الضغط PRS1. Passover Target Time PRS1 Humidifier Target Time Hum. Tgt Time Tubing Type Lock قفل نوع الأنابيب Whether tubing type settings are available to you. ما إذا كانت إعدادات نوع الأنابيب متاحة لك. Tube Lock قفل الأنبوب Mask Resistance Lock قفل قناع المقاومة Whether mask resistance settings are available to you. ما إذا كانت إعدادات مقاومة القناع متاحة لك. Mask Res. Lock Mask Res. Lock Whether or not machine shows AHI via built-in display. ما إذا كان الجهاز يعرض AHI أم لا عبر شاشة مدمجة. Ramp Type نوع المنحدر Type of ramp curve to use. نوع منحنى المنحدر للاستخدام. Linear خطي SmartRamp SmartRamp Ramp+ Backup Breath Mode وضع التنفس الاحتياطي The kind of backup breath rate in use: none (off), automatic, or fixed نوع معدل التنفس الاحتياطي المستخدم: بلا (إيقاف) أو تلقائي أو ثابت Breath Rate معدل التنفس Fixed ثابت Fixed Backup Breath BPM التنفس الاحتياطي الثابت BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated الحد الأدنى من الأنفاس في الدقيقة (BPM) التي سيتم بعدها بدء التنفس الموقوت Breath BPM التنفس BPM Timed Inspiration إلهام موقوت The time that a timed breath will provide IPAP before transitioning to EPAP الوقت الذي يوفر فيه التنفس الموقوت IPAP قبل الانتقال إلى EPAP Timed Insp. Timed Insp. Auto-Trial Duration مدة المحاكمة التلقائية The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP عدد الأيام في الفترة التجريبية Auto-CPAP ، وبعدها يعود الجهاز إلى CPAP Auto-Trial Dur. Auto-Trial Dur. EZ-Start EZ-Start Whether or not EZ-Start is enabled سواء تم تمكين EZ-Start أم لا Variable Breathing تنفس متغير UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend غير مؤكد: تنفس متغير محتمل ، وهي فترات انحراف كبير عن ذروة اتجاه التدفق الشهيق Peak Flow Peak flow during a 2-minute interval Humidifier Status حالة المرطب PRS1 humidifier connected? PRS1 المرطب متصلة؟ Disconnected انقطع الاتصال Connected متصل Humidification Mode وضع الترطيب PRS1 Humidification Mode وضع الترطيب PRS1 Humid. Mode Humid. Mode Fixed (Classic) ثابت (كلاسيكي) Adaptive (System One) التكيف (النظام الأول) Heated Tube أنبوب ساخن Tube Temperature درجة حرارة الأنبوب PRS1 Heated Tube Temperature PRS1 درجة حرارة الأنبوب المسخن Tube Temp. Tube Temp. PRS1 Humidifier Setting إعداد مرطب PRS1 Hose Diameter قطر خرطوم Diameter of primary CPAP hose قطر أو خرطوم CPAP الأساسي 12mm 12mm Auto On تشغيل تلقائي A few breaths automatically starts machine وهناك عدد قليل من الأنفاس يبدأ الجهاز تلقائيا Auto Off إيقاف السيارات Machine automatically switches off الجهاز يغلق تلقائيا Mask Alert تنبيه قناع Whether or not machine allows Mask checking. أم لا يسمح الجهاز فحص القناع. Show AHI عرض AHI Breathing Not Detected التنفس لم يتم كشفه A period during a session where the machine could not detect flow. فترة خلال الجلسة حيث لم يتمكن الجهاز من اكتشاف التدفق. BND BND Timed Breath توقيت التنفس Machine Initiated Breath آلة بدأت التنفس TB TB Windows User مستخدم ويندوز Using استخدام , found SleepyHead - ، وجدت SleepyHead - You must run the OSCAR Migration Tool يجب عليك تشغيل أداة ترحيل OSCAR <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>يجب إعادة إنشاء بيانات الجهاز القديم شريطة ألا يتم تعطيل ميزة النسخ الاحتياطي هذه في التفضيلات أثناء عملية استيراد بيانات سابقة.</i> Launching Windows Explorer failed فشل بدء تشغيل مستكشف Windows Could not find explorer.exe in path to launch Windows Explorer. تعذر العثور على explorer.exe في الطريق لبدء تشغيل مستكشف Windows. OSCAR %1 needs to upgrade its database for %2 %3 %4 يحتاج OSCAR %1 إلى ترقية قاعدة بياناته لـ %2 %3 %4 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>يحتفظ OSCAR بنسخة احتياطية من بطاقة بيانات أجهزتك التي يستخدمها لهذا الغرض.</b> OSCAR does not yet have any automatic card backups stored for this device. لا يوجد لدى الأداة OSCAR أي نسخ احتياطية للبطاقات تلقائيًا مخزنة لهذا الجهاز. This means you will need to import this machine data again afterwards from your own backups or data card. هذا يعني أنك ستحتاج إلى استيراد بيانات الجهاز هذه مرة أخرى بعد ذلك من النسخ الاحتياطية أو بطاقة البيانات الخاصة بك. Important: هام: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. إذا كنت مهتمًا ، فانقر فوق "لا" للخروج من النسخة الاحتياطية ونسخها احتياطيًا يدويًا ، قبل بدء تشغيل OSCAR مرة أخرى. Are you ready to upgrade, so you can run the new version of OSCAR? هل أنت مستعد للترقية ، فهل يمكنك تشغيل الإصدار الجديد أو OSCAR؟ Sorry, the purge operation failed, which means this version of OSCAR can't start. عذرًا ، فشلت عملية التطهير ، مما يعني أنه لا يمكن بدء تشغيل هذا الإصدار أو OSCAR. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? هل ترغب في التبديل إلى النسخ الاحتياطية التلقائية ، لذا في المرة القادمة التي يحتاج فيها إصدار جديد أو OSCAR إلى القيام بذلك ، يمكنه إعادة البناء من هذه؟ OSCAR will now start the import wizard so you can reinstall your %1 data. يبدأ OSCAR الآن معالج الاستيراد حتى تتمكن من إعادة تثبيت بيانات %1 الخاصة بك. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: سيتم الآن الخروج من الأداة OSCAR ، ثم (محاولة) بدء تشغيل مدير ملفات أجهزة الكمبيوتر حتى تتمكن من نسخ ملف التعريف الخاص بك يدويًا إلى أعلى: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. استخدم مدير الملفات لديك لإنشاء نسخة أو دليل ملف التعريف الخاص بك ، ثم بعد ذلك ، أعد تشغيل OSCAR وإكمال عملية الترقية. Machine Database Changes تغييرات قاعدة بيانات الجهاز Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. بمجرد الترقية ، لا يمكنك <font size = + 1> </font> استخدام هذا الملف الشخصي مع الإصدار السابق بعد الآن. The machine data folder needs to be removed manually. يجب إزالة مجلد بيانات الجهاز يدويًا. This folder currently resides at the following location: يوجد هذا المجلد حاليًا في الموقع التالي: Rebuilding from %1 Backup إعادة بناء من %1 النسخ الاحتياطي Therapy Pressure ضغط العلاج Inspiratory Pressure ضغط ملهمة Lower Inspiratory Pressure انخفاض ضغط الشهيق Higher Inspiratory Pressure ارتفاع ضغط إلهامي Expiratory Pressure ضغط الزفير Lower Expiratory Pressure انخفاض ضغط الزفير Higher Expiratory Pressure ارتفاع ضغط الزفير Pressure Support دعم الضغط PS Min Pressure Support Minimum دعم الضغط الحد الأدنى PS Max Pressure Support Maximum دعم الضغط الأقصى Min Pressure دقيقة الضغط Minimum Therapy Pressure الحد الأدنى من ضغط العلاج Max Pressure أقصى ضغط Maximum Therapy Pressure أقصى ضغط العلاج Ramp Time الوقت منحدر Ramp Delay Period منحدر فترة التأخير Ramp Pressure منحدر الضغط Starting Ramp Pressure بدءا منحدر الضغط Ramp Event حدث منحدر Ramp المنحدر Vibratory Snore (VS2) الشخير الاهتزازي (VS2) A ResMed data item: Trigger Cycle Event عنصر بيانات ResMed: حدث دورة الزناد Mask On Time قناع في الوقت المحدد Time started according to str.edf بدأ الوقت وفقًا str.edf Summary Only ملخص فقط An apnea where the airway is open توقف التنفس أثناء فتح مجرى الهواء An apnea caused by airway obstruction انقطاع النفس الناجم عن انسداد مجرى الهواء Hypopnea ضعف التنفس A partially obstructed airway مجرى الهواء عرقلة جزئيا Unclassified Apnea انقطاع النفس غير المصنف UA UA Vibratory Snore الشخير الاهتزازي A vibratory snore شخير اهتزازي A vibratory snore as detcted by a System One machine شخير اهتزازي كما تم اكتشافه بواسطة جهاز System One Pressure Pulse نبض الضغط A pulse of pressure 'pinged' to detect a closed airway. نبضة ضغط "تتعرض لضغوط" لاكتشاف مجرى الهواء المغلق. A large mask leak affecting machine performance. تسرب كبير للقناع يؤثر على أداء الماكينة. Non Responding Event حدث غير مستجيب A type of respiratory event that won't respond to a pressure increase. وهناك نوع من الحدث التنفسي الذي لن يستجيب لزيادة الضغط. Expiratory Puff الزفير النفخة Intellipap event where you breathe out your mouth. حدث Intellipap حيث تتنفس فمك. SensAwake feature will reduce pressure when waking is detected. ميزة SensAwake ستقلل الضغط عند اكتشاف الاستيقاظ. User Flag #1 علم المستخدم رقم 1 User Flag #2 علم المستخدم رقم 2 User Flag #3 علم المستخدم رقم 3 Heart rate in beats per minute معدل ضربات القلب في يدق في الدقيقة الواحدة Blood-oxygen saturation percentage نسبة تشبع الدم بالأكسجين Plethysomogram تخطيط التحجم An optical Photo-plethysomogram showing heart rhythm رسم ضوئي بصري ضوئي يظهر إيقاع القلب Pulse Change تغيير النبض A sudden (user definable) change in heart rate تغيير مفاجئ (يمكن تعريف المستخدم) في معدل ضربات القلب SpO2 Drop SpO2 إسقاط A sudden (user definable) drop in blood oxygen saturation انخفاض مفاجئ (يمكن تعريف المستخدم) في تشبع الأكسجين في الدم SD SD Breathing flow rate waveform معدل تدفق التنفس الموجي L/min لتر / دقيقة Mask Pressure قناع الضغط Amount of air displaced per breath كمية الهواء النازحة في التنفس Graph displaying snore volume الرسم البياني عرض حجم الشخير Minute Ventilation دقيقة حداد Amount of air displaced per minute كمية الهواء النازحة في الدقيقة Respiratory Rate معدل التنفس Rate of breaths per minute معدل التنفس في الدقيقة Patient Triggered Breaths أثار المريض أنفاسه Percentage of breaths triggered by patient نسبة الأنفاس الناتجة عن المريض Pat. Trig. Breaths تربيتة. علم حساب المثلثات. الأنفاس Leak Rate معدل التسرب Rate of detected mask leakage معدل تسرب قناع الكشف I:E Ratio أنا: ه نسبة Ratio between Inspiratory and Expiratory time النسبة بين الوقت الملهم والزفير ratio نسبة Pressure Min الضغط دقيقة Pressure Max الضغط ماكس Pressure Set مجموعة الضغط Pressure Setting وضع الضغط IPAP Set مجموعة IPAP IPAP Setting إعداد IPAP EPAP Set مجموعة EPAP EPAP Setting إعداد EPAP Cheyne Stokes Respiration شايان ستوكس التنفس An abnormal period of Cheyne Stokes Respiration فترة غير طبيعية من شايان ستوكس تنفس CSR CSR Periodic Breathing التنفس الدوري An abnormal period of Periodic Breathing فترة غير طبيعية من التنفس الدوري Clear Airway واضح مجرى الهواء Obstructive المعوق An apnea that couldn't be determined as Central or Obstructive. انقطاع النفس الذي لا يمكن تحديده على أنه مركزي أو معيق. A restriction in breathing from normal, causing a flattening of the flow waveform. وجود قيود في التنفس من المعتاد ، مما تسبب في تسطيح الموجي التدفق. Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. الجهد التنفسي المتعلق بالإثارة: وجود قيود في التنفس تسبب إما إيقاظًا أو اضطرابًا في النوم. Leak Flag تسرب العلم LF LF A user definable event detected by OSCAR's flow waveform processor. حدث يمكن تعريفه من قِبل المستخدم تم اكتشافه بواسطة معالج شكل الموجة OSCAR. Perfusion Index مؤشر الارواء A relative assessment of the pulse strength at the monitoring site تقييم نسبي لقوة النبض في موقع المراقبة Perf. Index % الأداء. فهرس ٪ Mask Pressure (High frequency) ضغط القناع (تردد عالي) Expiratory Time وقت الزفير Time taken to breathe out الوقت المستغرق في التنفس Inspiratory Time وقت ملهمة Time taken to breathe in الوقت الذي يستغرقه التنفس Respiratory Event الحدث التنفسي Graph showing severity of flow limitations رسم بياني يوضح شدة قيود التدفق Flow Limit. الحد من التدفق. Target Minute Ventilation الهدف دقيقة التهوية Maximum Leak أقصى تسرب The maximum rate of mask leakage الحد الأقصى لمعدل تسرب القناع Max Leaks ماكس تسرب Apnea Hypopnea Index تشغيل مؤشر توقف التنفس أثناء التنفس Graph showing running AHI for the past hour رسم بياني يظهر أنه يركض AHI للساعة الماضية Total Leak Rate معدل التسرب الكلي Detected mask leakage including natural Mask leakages كشف تسرب القناع بما في ذلك تسرب القناع الطبيعي Median Leak Rate معدل التسرب المتوسط Median rate of detected mask leakage متوسط معدل تسرب القناع المكتشف Median Leaks تسرب متوسط Respiratory Disturbance Index تشغيل مؤشر اضطراب الجهاز التنفسي Graph showing running RDI for the past hour رسم بياني يوضح تشغيل RDI للساعة الماضية Sleep position in degrees موقف النوم بالدرجات Upright angle in degrees زاوية تستقيم بالدرجات Movement حركة Movement detector كاشف الحركة CPAP Session contains summary data only تحتوي جلسة CPAP على ملخص البيانات فقط PAP Mode وضع PAP Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Apnea An apnea reportred by your CPAP machine. PAP Device Mode وضع جهاز PAP APAP (Variable) APAP (متغير) ASV (Fixed EPAP) ASV (ثابت EPAP) ASV (Variable EPAP) ASV (متغير EPAP) Height ارتفاع Physical Height الارتفاع البدني Notes ملاحظات Bookmark Notes إشارة مرجعية ملاحظات Body Mass Index مؤشر كتلة الجسم How you feel (0 = like crap, 10 = unstoppable) ما هو شعورك (0 = مثل حماقة ، 10 = لا يمكن وقفها) Bookmark Start إشارة مرجعية ابدأ Bookmark End المرجعية نهاية Last Updated آخر تحديث Journal Notes مجلة مذكرات Journal مجلة 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1 = استيقظ 2 = REM 3 = نوم خفيف 4 = نوم عميق Brain Wave موجة الدماغ BrainWave الفكره الراءعه Awakenings الاستيقاظ Number of Awakenings عدد الصحوات Morning Feel يشعر الصباح How you felt in the morning كيف شعرت في الصباح Time Awake الوقت مستيقظا Time spent awake الوقت الذي يقضيه مستيقظا Time In REM Sleep الوقت في REM النوم Time spent in REM Sleep الوقت الذي يقضيه في REM Sleep Time in REM Sleep الوقت في REM النوم Time In Light Sleep الوقت في النوم الخفيف Time spent in light sleep الوقت الذي يقضيه في النوم الخفيف Time in Light Sleep الوقت في النوم الخفيف Time In Deep Sleep الوقت في النوم العميق Time spent in deep sleep الوقت الذي يقضيه في النوم العميق Time in Deep Sleep الوقت في النوم العميق Time to Sleep وقت النوم Time taken to get to sleep الوقت المستغرق للوصول إلى النوم Zeo ZQ Zeo sleep quality measurement قياس جودة نوم النوم ZEO ZQ Debugging channel #1 قناة التصحيح # 1 Test #1 اختبار # 1 For internal use only Debugging channel #2 قناة التصحيح # 2 Test #2 اختبار # 2 Zero صفر Upper Threshold العتبة العليا Lower Threshold العتبة السفلى As you did not select a data folder, OSCAR will exit. نظرًا لأنك لم تحدد مجلد بيانات ، فسيتم إنهاء OSCAR. or CANCEL to skip migration. أو إلغاء لتخطي الهجرة. Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. You cannot use this folder: لا يمكنك استخدام هذا المجلد: Migrating ترحيل files ملفات from من عند to إلى OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. سيقوم OSCAR بإعداد مجلد لبياناتك. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. We suggest you use this folder: نقترح عليك استخدام هذا المجلد: Click Ok to accept this, or No if you want to use a different folder. انقر فوق موافق لقبول هذا ، أو لا إذا كنت تريد استخدام مجلد مختلف. Choose or create a new folder for OSCAR data اختر أو أنشئ مجلدًا جديدًا لبيانات OSCAR Next time you run OSCAR, you will be asked again. في المرة القادمة التي تقوم فيها بتشغيل OSCAR ، سيتم سؤالك مرة أخرى. The folder you chose is not empty, nor does it already contain valid OSCAR data. المجلد الذي اخترته ليس فارغًا ، كما أنه لا يحتوي بالفعل على بيانات OSCAR صالحة. Data directory: دليل البيانات: Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! الإصدار "%1" غير صالح ، لا يمكن المتابعة! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). إصدار OSCAR الذي تقوم بتشغيله (%1) هو أقدم من الإصدار المستخدم لإنشاء هذه البيانات (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? من المحتمل أن يؤدي هذا إلى تلف البيانات ، هل أنت متأكد من رغبتك في القيام بذلك؟ Question سؤال Exiting الخروج Are you sure you want to use this folder? هل أنت متأكد أنك تريد استخدام هذا المجلد؟ Don't forget to place your datacard back in your CPAP machine لا تنسَ أن تضع بطاقة بياناتك في جهاز CPAP OSCAR Reminder أوسكار تذكير You can only work with one instance of an individual OSCAR profile at a time. يمكنك العمل فقط مع مثيل واحد لملف تعريف OSCAR فردي في كل مرة. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. إذا كنت تستخدم التخزين السحابي ، فتأكد من إغلاق OSCAR وإكمال المزامنة أولاً على الكمبيوتر الآخر قبل المتابعة. Loading profile "%1"... جارٍ تحميل ملف التعريف "%1" ... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! يُرجى تحديد موقع لرمزك البريدي بخلاف بطاقة البيانات نفسها! Unable to create zip! تعذر إنشاء ملف مضغوط! Are you sure you want to reset all your channel colors and settings to defaults? هل تريد بالتأكيد إعادة تعيين جميع ألوان قناتك وإعداداتها إلى الإعدادات الافتراضية؟ Are you sure you want to reset all your waveform channel colors and settings to defaults? هل أنت متأكد من رغبتك في إعادة تعيين كل الألوان والإعدادات لقناة الموجي إلى الإعدادات الافتراضية؟ There are no graphs visible to print لا توجد رسوم بيانية مرئية للطباعة Would you like to show bookmarked areas in this report? هل ترغب في إظهار المناطق التي تمت الإشارة إليها في هذا التقرير؟ Printing %1 Report طباعة تقرير %1 %1 Report %1 تقرير : %1 hours, %2 minutes, %3 seconds : %1 ساعات، %2 دقيقة، %3 ثواني RDI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/CSR=%4% UAI=%1 UAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 الإبلاغ من %1 إلى %2 Entire Day's Flow Waveform كامل يوم الموجة الموجي Current Selection الإختيار الحالي Entire Day يوم كامل Page %1 of %2 صفحة %1 من %2 Days: %1 الأيام: %1 Low Usage Days: %1 أيام الاستخدام المنخفض: %1 (%1% compliant, defined as > %2 hours) (%1٪ متوافق ، معرّف كـ > %2 ساعات) (Sess: %1) (الاختبار: %1) Bedtime: %1 وقت النوم: %1 Waketime: %1 وقت التشغيل: %1 (Summary Only) (الملخص فقط) There is a lockfile already present for this profile '%1', claimed on '%2'. يوجد ملف تعريف موجود بالفعل لهذا الملف الشخصي '%1' ، تمت المطالبة به على '%2'. Fixed Bi-Level ثابت ثنائي المستوى Auto Bi-Level (Fixed PS) المستوى الثنائي التلقائي (PS الثابت) Auto Bi-Level (Variable PS) ثنائية المستوى التلقائي (متغير PS) 99.5% 90% {99.5%?} varies n/a n/a Fixed %1 (%2) ثابت %1 (%2) Min %1 Max %2 (%3) الحد الأدنى %1 الحد الأقصى %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %1 على %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1 IPAP %2-%3 (%4) {1-%2 ?} {3-%4 ?} {5)?} Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> أحدث بيانات قياس التأكسج: <a onclick='alert("daily=%2");'>%1</a> (last night) (اخر مساء) (1 day ago) (%2 days ago) No oximetry data has been imported yet. لم يتم استيراد بيانات قياس التأكسج حتى الآن. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings إعدادات SmartFlex ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Somnopose البرمجيات Zeo Zeo Personal Sleep Coach مدرب النوم الشخصي Database Outdated Please Rebuild CPAP Data قاعدة البيانات القديمة يرجى إعادة بناء بيانات CPAP (%2 min, %3 sec) (%3 sec) Pop out Graph المنبثقة الرسم البياني The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View There is no data to graph لا توجد بيانات للرسم البياني d MMM yyyy [ %1 - %2 ] d MMM yyyy [ %1 - %2 ] Hide All Events إخفاء جميع الأحداث Show All Events عرض جميع الأحداث Unpin %1 Graph إلغاء تثبيت الرسم البياني %1 Popout %1 Graph المنبثقة %1 الرسم البياني Pin %1 Graph دبوس %1 الرسم البياني Plots Disabled مؤامرات المعوقين Duration %1:%2:%3 المدة الزمنية %1:%2:%3 AHI %1 Relief: %1 ارتياح: %1 Hours: %1h, %2m, %3s ساعات العمل: %1 س ، %2 م ، %3 ث Machine Information معلومات الجهاز Journal Data مجلة البيانات OSCAR found an old Journal folder, but it looks like it's been renamed: عثر OSCAR على مجلد "دفتر يومية" قديم ، لكن يبدو أنه تمت إعادة تسميته: OSCAR will not touch this folder, and will create a new one instead. لن تلمس الأداة OSCAR هذا المجلد ، وستقوم بإنشاء مجلد جديد بدلاً منه. Please be careful when playing in OSCAR's profile folders :-P يرجى توخي الحذر عند اللعب في مجلدات ملف تعريف OSCAR :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. لسبب ما ، لم يتمكن OSCAR من العثور على سجل كائن دفتر اليومية في ملف التعريف الخاص بك ، ولكنه عثر على مجلدات بيانات دفتر يومية متعددة. OSCAR picked only the first one of these, and will use it in future: اختار OSCAR أول واحد فقط من هذه ، وسوف يستخدمه في المستقبل: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. إذا كانت بياناتك القديمة مفقودة ، انسخ محتويات جميع مجلدات Journal_XXXXXXX الأخرى إلى هذا المجلد يدويًا. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Backing up files... Reading data files... SmartFlex Mode وضع SmartFlex Intellipap pressure relief mode. وضع تخفيف الضغط Intellipap. Ramp Only منحدر فقط Full Time وقت كامل SmartFlex Level مستوى SmartFlex Intellipap pressure relief level. Intellipap مستوى تخفيف الضغط. Snoring event. SN Locating STR.edf File(s)... تحديد موقع ملف (ملفات) STR.edf ... Cataloguing EDF Files... فهرسة ملفات EDF ... Queueing Import Tasks... انتظار استيراد مهام ... Finishing Up... الانتهاء من ... CPAP Mode وضع CPAP VPAPauto VPAPauto ASVAuto ASVAuto iVAPS PAC Auto for Her السيارات لها EPR ResMed Exhale Pressure Relief ResMed الزفير تخفيف الضغط Patient??? صبور؟؟؟ EPR Level مستوى EPR Exhale Pressure Relief Level الزفير تخفيف الضغط المستوى Response Patient View SmartStart Machine auto starts by breathing يبدأ تشغيل الآلة عن طريق التنفس Smart Start البداية الذكية Humid. Status رطب. الحالة Humidifier Enabled Status تمكين حالة المرطب Humid. Level رطب. مستوى Humidity Level مستوى الرطوبة Temperature درجة الحرارة ClimateLine Temperature درجة حرارة خط المناخ Temp. Enable مؤقت. ممكن ClimateLine Temperature Enable درجة حرارة خط التكييف ممكنة Temperature Enable تمكين درجة الحرارة AB Filter تصفية AB Antibacterial Filter مرشح مضاد للجراثيم Pt. Access حزب العمال. التمكن من Essentials Plus Climate Control التحكم في المناخ Manual كتيب Soft Standard اساسي BiPAP-T BiPAP-S BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Advanced المتقدمة Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... جارٍ تحليل سجلات STR.edf ... Auto تلقاءي Mask قناع ResMed Mask Setting إعداد قناع ResMed Pillows الوسائد Full Face كامل الوجه Nasal أنفي Ramp Enable منحدر تمكين Weinmann SOMNOsoft2 Snapshot %1 لقطة %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... جارٍ تحميل بيانات %1 لـ %2 ... Scanning Files مسح الملفات Migrating Summary File Location ترحيل ملف ملخص الموقع Loading Summaries.xml.gz تحميل Summaries.xml.gz Loading Summary Data تحميل ملخص البيانات Please Wait... ارجوك انتظر... Updating Statistics cache تحديث الاحصائيات المخبأ Usage Statistics إحصائيات الاستخدام %1 Charts %1 of %2 Charts Loading summaries تحميل الملخصات Dreem Dreem Your Viatom device generated data that OSCAR has never seen before. أنتج جهاز Viatom بيانات لم يشاهدها OSCAR من قبل. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. قد لا تكون البيانات المستوردة دقيقة تمامًا ، لذلك يرغب المطورون في الحصول على نسخة من ملفات Viatom للتأكد من أن OSCAR يتعامل مع البيانات بشكل صحيح. Viatom Viatom Viatom Software برنامج Viatom New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to check for updates. Please try again later. SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank SessionBar %1h %2m %1h %2m No Sessions Present لا توجد جلسات حالية SleepStyleLoader Import Error خطأ في الاستيراد This Machine Record cannot be imported in this profile. لا يمكن استيراد سجل الجهاز في ملف التعريف هذا. The Day records overlap with already existing content. تتداخل سجلات اليوم مع المحتوى الموجود بالفعل. Statistics CPAP Statistics إحصائيات CPAP CPAP Usage استخدام CPAP Average Hours per Night متوسط ساعات الليلة Therapy Efficacy فعالية العلاج Leak Statistics تسرب الاحصائيات Pressure Statistics إحصائيات الضغط Oximeter Statistics إحصائيات مقياس التأكسج Blood Oxygen Saturation تشبع الأكسجين في الدم Pulse Rate معدل النبض %1 Median %1 الوسيط Average %1 معدل %1 Min %1 دقيقة %1 Max %1 ماكس %1 %1 Index %1 فهرس % of time in %1 ٪ من الوقت في %1 % of time above %1 threshold ٪ من الوقت أعلاه %1 عتبة % of time below %1 threshold ٪ من الوقت أدناه %1 عتبة Name: %1, %2 اسم: %1, %2 DOB: %1 تاريخ الميلاد: %1 Phone: %1 هاتف: %1 Email: %1 البريد الإلكتروني: %1 Address: عنوان: This report was prepared on %1 by OSCAR %2 تم تحضير هذا التقرير في %1 بواسطة OSCAR %2 Days Used: %1 الأيام المستخدمة: %1 Low Use Days: %1 انخفاض استخدام أيام: %1 Compliance: %1% الالتزام: %1% Days AHI of 5 or greater: %1 أيام AHI من 5 أو أكبر: %1 Best AHI أفضل AHI Date: %1 AHI: %2 تاريخ: %1 AHI: %2 Worst AHI أسوأ AHI Best Flow Limitation أفضل الحد من التدفق Date: %1 FL: %2 تاريخ: %1 FL: %2 Worst Flow Limtation أسوأ قيود التدفق No Flow Limitation on record لا توجد قيود على التدفق Worst Large Leaks أسوأ التسريبات الكبيرة Date: %1 Leak: %2% تاريخ: %1 تسرب: %2% No Large Leaks on record لا تسريبات كبيرة على الاطلاق Worst CSR أسوأ CSR Date: %1 CSR: %2% تاريخ: %1 CSR: %2% No CSR on record لا CSR في السجل Worst PB أسوأ PB Date: %1 PB: %2% تاريخ: %1 PB: %2% No PB on record لا PB على السجل Want more information? تريد المزيد من المعلومات؟ OSCAR needs all summary data loaded to calculate best/worst data for individual days. يحتاج OSCAR إلى جميع البيانات الموجزة المحملة لحساب أفضل / أسوأ البيانات للأيام الفردية. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. يرجى تمكين خانة الاختيار ملخص التحميل المسبق في التفضيلات للتأكد من توفر هذه البيانات. Best RX Setting أفضل إعداد RX Date: %1 - %2 تاريخ: %1 - %2 AHI: %1 Total Hours: %1 مجموع الساعات: %1 Worst RX Setting إعداد RX أسوأ Most Recent الأحدث Compliance (%1 hrs/day) الامتثال (%1 ساعة / يوم) OSCAR is free open-source CPAP report software OSCAR هو برنامج تقرير CPAP مفتوح المصدر مجاني Changes to Machine Settings التغييرات في إعدادات الجهاز No data found?!? لاتوجد بيانات؟!؟ Oscar has no data to report :( أوسكار ليس لديه بيانات للإبلاغ عنها :( Last Week الاسبوع الماضى Last 30 Days آخر 30 يوم Last 6 Months آخر 6 أشهر Last Year العام الماضي Last Session أخر موسم Details تفاصيل No %1 data available. لا توجد بيانات %1 متاحة. %1 day of %2 Data on %3 %1 يوم من %2 بيانات على %3 %1 days of %2 Data, between %3 and %4 %1 يوم من %2 بيانات ، بين %3 و %4 Days أيام Pressure Relief انتهاء الضغط Pressure Settings إعدادات الضغط Machine Information معلومات الجهاز First Use اول استخدام Last Use الاستخدام الأخير Welcome Welcome to the Open Source CPAP Analysis Reporter مرحبًا بكم في مراسل تحليل CPAP مفتوح مستخرج What would you like to do? ماذا تريد ان تفعل؟ CPAP Importer مستورد CPAP Oximetry Wizard معالج التأكسج Daily View عرض يومي Overview نظرة عامة Statistics الإحصاء <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">تحذير: </span><span style=" color:#ff0000;">بطاقات ResMed S9 SD تحتاج إلى أن تكون مؤمنة </span><span style=" font-weight:600; color:#ff0000;">قبل الإدراج في جهاز الكمبيوتر الخاص بك.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>تكتب بعض أنظمة التشغيل ملفات الفهرس إلى البطاقة دون أن تطلب ، مما قد يجعل بطاقتك غير قابلة للقراءة بواسطة جهاز cpap.</span></p></body></html> It would be a good idea to check File->Preferences first, سيكون من الجيد التحقق من ملف> تفضيلات أولاً ، as there are some options that affect import. لأن هناك بعض الخيارات التي تؤثر على الاستيراد. Note that some preferences are forced when a ResMed machine is detected لاحظ أنه يتم فرض بعض التفضيلات عند اكتشاف جهاز ResMed First import can take a few minutes. يمكن أن يستغرق الاستيراد الأول بضع دقائق. The last time you used your %1... آخر مرة استخدمت فيها %1 ... last night اخر مساء %2 days ago %2 منذ أيام was %1 (on %2) كان %1 (في %2) %1 hours, %2 minutes and %3 seconds %1 ساعات ،%2 دقيقة و %3 ثواني Your machine was on for %1. تم تشغيل الجهاز لـ %1. <font color = red>You only had the mask on for %1.</font> ''<font color = red>كان لديك القناع فقط على%1.</font> under تحت over على reasonably close to قريبة إلى حد معقول equal to يساوي You had an AHI of %1, which is %2 your %3 day average of %4. كان لديك AHI من %1 ، وهو %2 الخاص بك متوسط %3 أيام %4. Your CPAP machine used a constant %1 %2 of air استخدم جهاز CPAP ثابتًا %1 %2 من الهواء Your pressure was under %1 %2 for %3% of the time. كان ضغطك أقل من %1 %2 لـ %3٪ من الوقت. Your machine used a constant %1-%2 %3 of air. يستخدم الجهاز ثابتًا من %1 - %2 %3 من الهواء. Your EPAP pressure fixed at %1 %2. ضغط EPAP ثابت عند %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. كان ضغط IPAP الخاص بك أقل من %1 %2 لـ %3٪ من الوقت. Your EPAP pressure was under %1 %2 for %3% of the time. كان ضغط EPAP أقل من %1 %2 لـ %3٪ من الوقت. Your machine was under %1-%2 %3 for %4% of the time. كان جهازك أقل من %1 - %2 %3 لـ %4٪ من الوقت. 1 day ago Your average leaks were %1 %2, which is %3 your %4 day average of %5. متوسط التسريبات الخاص بك هو %1 %2، وهو %3 الخاص بك ، متوسط %4 أيام ،%5. No CPAP data has been imported yet. لم يتم استيراد بيانات CPAP حتى الآن. gGraph %1 days %1 أيام gGraphView 100% zoom level 100 ٪ مستوى التكبير Restore X-axis zoom to 100% to view entire selected period. استعادة تكبير المحور X إلى 100٪ لعرض الفترة المحددة بأكملها. Restore X-axis zoom to 100% to view entire day's data. قم باستعادة تكبير المحور X إلى 100٪ لعرض بيانات اليوم بأكمله. Reset Graph Layout إعادة تعيين الرسم البياني تخطيط Resets all graphs to a uniform height and default order. إعادة تعيين جميع الرسوم البيانية إلى ارتفاع موحد وترتيب افتراضي. Y-Axis Y-المحور Plots فجأة CPAP Overlays تراكب CPAP Oximeter Overlays مقياس التأكسج التراكبات Dotted Lines خطوط منقطة Double click title to pin / unpin Click and drag to reorder graphs انقر نقرًا مزدوجًا فوق عنوان الرقم السري / إلغاء التثبيت انقر واسحب لإعادة ترتيب الرسوم البيانية Remove Clone إزالة استنساخ Clone %1 Graph استنساخ %1 رسم بياني OSCAR-code-v1.3.1/Translations/Bulgarian.bg.ts000066400000000000000000015166541417327530600210370ustar00rootroot00000000000000 AboutDialog &About За &приложение Release Notes Бележки по изданието Credits Заслуги GPL License As a whole this actually should read "Общ публичен лиценз на ГНУ", but that is a bit long on a tab. I think it would be acceptable to just say "license GPL" and then the explanation is written in the tab. The Bulgarian wikipedia page for the GPL gives a couple of translation options. лиценз GPL Close Затваряне Show data folder Покажи папката на данните About OSCAR %1 За предложение OSCAR %1 Sorry, could not locate About file. За съжаление, файлът „За приложение“ не се намери. Sorry, could not locate Credits file. За съжаление, файлът „Заслуги“ не се намери. Sorry, could not locate Release Notes. За съжаление, Бележки по изданието не се намери. OSCAR %1 OSCAR %1 Important: Важно: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Тъй като това е предварително издание, препоръчано е <b>да направите ръчно архивиране на своята папка с данни</b> преди да продължите, защото е възможно при по-късен опит за връщане назад нещата да се повредят. To see if the license text is available in your language, see %1. За да проверите дали съществува превода на лиценз на Вашия език, вижте %1. CMS50F37Loader Could not find the oximeter file: Файлът на оксиметъра не се намери: Could not open the oximeter file: Не може да се отвори файлът на оксиметъра: CMS50Loader Could not get data transmission from oximeter. Не се получават данни от оксиметъра. Please ensure you select 'upload' from the oximeter devices menu. Моля уверете се че сте избрали 'upload' от менюто на оксиметъра. Could not find the oximeter file: Файлът на оксиметъра не се намери: Could not open the oximeter file: Не може да се отвори файлът на оксиметъра: CheckUpdates Checking for newer OSCAR versions Проверяваме за нова версия на OSCAR Daily Go to the previous day Отиди на предишен ден Show or hide the calender Покажи или скрий календар Go to the next day Отиди на следващ ден Go to the most recent day with data records Отиди на последния ден, за който има запис Events Събития View Size Размер на изглед Notes Бележки Journal Журнал Small Малък Medium Среден Big Голям I'm feeling ... Чувствам се ... If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Ако в настройки ръст е над нула, задаване на тегло тук ще се покажи стойността на индекса на телесната маса (ИТМ) Flags Флагове Graphs Графики Show/hide available graphs. Покажи или скрий достъпни графики. Color Цвят u B Zombie Зомби Weight Тегло Awesome Чудесно B.M.I. Bookmarks Отметки Add Bookmark Добави отметка Starts Начало Remove Bookmark Премахни отметка Breakdown Класифициране events събития No %1 events are recorded this day Няма %1 събития записани за този ден %1 event %1 събитие %1 events %1 събития UF1 UF1 UF2 UF2 Session Start Times Начала на сесии Session End Times Край на сесии Duration Продължителност Position Sensor Sessions Позиционен сензор сесии Unknown Session Непознати сесии Total time in apnea Общо време в апнея Time over leak redline Общо време с теч на въздух над граница Event Breakdown Класифициране на събития Unable to display Pie Chart on this system Sessions all off! Всички сесии са изключени! Sessions exist for this day but are switched off. Съществуват сесии за този ден, но са изключени. Impossibly short session Невъзможно къса сесия Zero hours?? Нула часа?? BRICK :( Тухла :( Sorry, this machine only provides compliance data. За съжаление, тази машина предоставя само данни за съответствие. Complain to your Equipment Provider! Оплачете се на този който ви го е препоръчал/продал! Statistics Статистики Details Детайли Time at Pressure Време с налягане Click to %1 this session. Натисни за да се %1 тази сесия. disable изключи enable включи %1 Session #%2 %1 Сесия #%2 %1h %2m %3s %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Моля, Забележете:</b>Всичките настройки, които са показани надолу, се основават на предположения, че нищо не се е променило от предишните дни. Oximeter Information Информация за оксиметър SpO2 Desaturations SpO2 десатурации Pulse Change events Събития промяна на пулс SpO2 Baseline Used Изплзвана базова линия SpO2 (Mode and Pressure settings missing; yesterday's shown.) (липсват настройките на Режима и Налягането; показват се вчерашните.) 99.5% 99,5% Start Начало End Край 10 of 10 Event Types This CPAP machine does NOT record detailed data This bookmark is in a currently disabled area.. Тази отметка се намира в зона, която в момента е деактивирана.. 10 of 10 Graphs Machine Settings Настройки на апарата Session Information Информация за сесия CPAP Sessions CPAP сесии Oximetry Sessions Оксиметрични сесии Sleep Stage Sessions Сесии фази на сън Model %1 - %2 Модел %1 - %2 PAP Mode: %1 PAP оперативен режим: %1 This day just contains summary data, only limited information is available. Този ден съдържа само обобщаващи данни, а наличната информация е ограничена. Total ramp time Общо рампинг време Time outside of ramp Време извън рампинг "Nothing's here!" "Няма нищо тук!" No data is available for this day. Няма налични данни за този ден. Pick a Colour Избери цвят Bookmark at %1 Отметка в %1 ExportCSV Export as CSV Експорт като CSV Dates: Дати: Resolution: Детайлност: Details Детайли Sessions Сесии Daily Дневна Filename: Име на файл: Cancel Отказ Export Експорт Start: Начало: End: Край: Quick Range: Бърз период: Most Recent Day Последен ден Last Week Последна седмица Last Fortnight Последни 2 седмици Last Month Последен месец Last 6 Months Последни 6 месеца Last Year Последна година Everything Всичко Custom По избор Details_ Details_ Sessions_ Sessions_ Summary_ Summary_ Select file to export to Избиране на файл за експорт CSV Files (*.csv) CSV файлове (*.csv) DateTime ДатаВреме Session Сесия Event Събитие Data/Duration Дата/Продължителност Date Дата Session Count Брой сесии Start Начало End Край Total Time Време общо AHI AHI Count Брой FPIconLoader Import Error Грешка при импорт This Machine Record cannot be imported in this profile. Записите от този апарат не могат да бъдат импортирани в този профил. The Day records overlap with already existing content. Дневните записи се застъпват с вече съществуващи такива. Help Hide this message Скрий това съобщение Search Topic: Темата за търсене Help Files are not yet available for %1 and will display in %2. Файловете за помощ все още не са налични за %1 и ще се покажат в %2. Help files do not appear to be present. Файловете за помощ не изглеждат налични. HelpEngine did not set up correctly Помощ (HelpEngine) не е настроен правилно HelpEngine could not register documentation correctly. Помощ (HelpEngine) не можа да регистрира правилно документацията. Contents Съдържание Index Индекс Search Търсене No documentation available Няма налична документация Please wait a bit.. Indexing still in progress Моля, изчакайте малко .. Индексирането продължава No Не %1 result(s) for "%2" %1 резултат(и) за "%2" clear изчисти MD300W1Loader Could not find the oximeter file: Файлът на оксиметър не се намери: Could not open the oximeter file: Не може да се отвори файлът на оксиметър: MainWindow &Statistics &Статистики Report Mode Режим на отчет Standard Стандартен Monthly Месечен Date Range Период от време Statistics Статистики Daily Дневна Overview Общ преглед Oximetry Оксиметрия Import Импорт Help Помощ &File &Файл &View &Преглед &Reset Graphs &Възстановяване на графики &Help &Помощ Troubleshooting Отстраняване на неизправности &Data &Данни &Advanced &Операции за напреднали Show Daily view Покажи ежедневния изглед Show Overview view Покажи изглед за преглед &Maximize Toggle Превключването на &максимизиране Maximize window Максимизирай прозорец Reset Graph &Heights Възстановяване на височините на &графиката Reset sizes of graphs Възстановяване на размерите на графиките Show Right Sidebar Покажи дясната странична лента Show Statistics view Покажи изглед на Статистика Import &Dreem Data Импортирай данни от &Dreem Show &Line Cursor Покажи стрелка за &линя Show Daily Left Sidebar Show Daily Calendar Покажи ежедневния календар Create zip of CPAP data card Създай цип от карта с данни на CPAP Create zip of OSCAR diagnostic logs Създай цип от диагностични журнали на OSCAR Create zip of all OSCAR data Създай цип от всички данни на OSCAR Report an Issue Съобщи за проблем System Information Информация за система Show &Pie Chart Покажи &Кръгова диаграма Show Pie Chart on Daily page Покажи кръгова диаграма в ежедневната страница Standard graph order, good for CPAP, APAP, Bi-Level Стандартен графичен ред, подходящ за CPAP, APAP, Bi-Level Advanced Разширено Advanced graph order, good for ASV, AVAPS Разширено графичен ред, подходящ за ASV, AVAPS Show Personal Data Покажи лични данни Check For &Updates Провери за &обновления Purge Current Selected Day &CPAP &CPAP &Oximetry &Оксиметрия &Sleep Stage Фаза на &сън &Position &Позиция &All except Notes &Всички с изключение на бележки All including &Notes Всички, включително &бележки Rebuild CPAP Data Презареждане на CPAP данни &Preferences &Настройки &Profiles &Профили &About OSCAR &За приложение OSCAR Show Performance Information Показване на информацията за работата CSV Export Wizard Помощник за CSV експорт Export for Review Експорт за преглед E&xit И&зход Exit Изход View &Daily Преглед по &дни View &Overview Преглед &общ поглед View &Welcome Преглед &начална страница Use &AntiAliasing Използване на заглаждане (&AntiAliasing) Show Debug Pane Показване на Debug прозорец Take &Screenshot Направи &снимка на екрана O&ximetry Wizard Помощник за настройка на &оксиметър Print &Report &Печат на отчет &Edit Profile &Редактиране на профил Import &Viatom/Wellue Data Импортирай данни на &Viatom/Wellvue Daily Calendar Календар по дни Backup &Journal Архивиране на &журнал Online Users &Guide Онлайн &Наръчник за употреба &Frequently Asked Questions &Често задавани въпроси &Automatic Oximetry Cleanup &Автоматично почистване на оксиметрия Change &User Промени &потребител Purge &Current Selected Day Изтрий избрания &текущ ден Right &Sidebar Десен &прозорец Daily Sidebar Прозорец с дни View S&tatistics Преглед &статистики Navigation Навигация Bookmarks Отметки Records Записи Exp&ort Data &Експорт на данни Profiles Профили Purge Oximetry Data Purge ALL Machine Data Изтрий ВСИЧКИ данни за машината &Import CPAP Card Data &Импортирай данни от картата CPAP View Statistics Преглед статистики Import &ZEO Data Импорт данни от &ZEO Import RemStar &MSeries Data Импорт данни от RemStar &MSeries Sleep Disorder Terms &Glossary &Речник на термини относно разстройство на съня Change &Language Промени &език Change &Data Folder Промени папката за &данни Import &Somnopose Data Импорт на данни от &Somnopose Current Days Текущи дни Welcome Добре дошли &About &Относно Access to Import has been blocked while recalculations are in progress. Достъп до импорт функцията е блокиран докато се извършват преизчисления. Importing Data Импорт на данни Please wait, importing from backup folder(s)... Моля изчакайте, извършва се импорт от архивната папка... Import Problem Импорт проблем Couldn't find any valid Machine Data at %1 Не могат да се открият валидни апаратни данни от %1 Please insert your CPAP data card... Моля поставете вашата карта със CPAP данни... CPAP Data Located Данни за CPAP са открити Import Reminder Напомнянe при импорт Find your CPAP data card Намери картата с данни за CPAP The User's Guide will open in your default browser Ръководството на потребителя ще се отвори в браузъра по подразбиране Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Ако сте правили <i>свой <b>собствен</b> бекъп на абсолютно всички CPAP данни досега</i> ще можете да завършите тази операция, но ще е необходимо да извършите възстановяването от бекъп ръчно. Are you really sure you want to do this? Сигурни ли сте че желаете да направите това? Note as a precaution, the backup folder will be left in place. За всеки случай бекъп папката ще бъде оставена. Are you <b>absolutely sure</b> you want to proceed? <b>Абсолютно ли сте сигурни</b> че желаете да продължите? The Glossary will open in your default browser Глосарият ще се отвори в браузъра по подразбиране There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete %1's Journal Журналът на %1 Choose where to save journal Изберете къде да запишете журналът XML Files (*.xml) XML файлове (*.xml) Because there are no internal backups to rebuild from, you will have to restore from your own. Не е открит наличен вътрешен бекъп от който да се извърши възстановяването, ще трябва да възстановите от собствен бекъп. Help Browser Please open a profile first. The FAQ is not yet implemented If you can read this, the restart command didn't work. You will have to do it yourself manually. Are you sure you want to rebuild all CPAP data for the following machine: No profile has been selected for Import. %1 (Profile: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Check for updates not implemented Choose where to save screenshot Image files (*.png) Please note, that this could result in loss of data if OSCAR's backups have been disabled. For some reason, OSCAR does not have any backups for the following machine: Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Желаете ли да извършите импорт от свой собствен бекъп? (няма да виждате никакви данни за този апарат докато не го направите) OSCAR does not have any backups for this machine! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: No help is available. Are you sure you want to delete oximetry data for %1 Сигурни ли сте че желаете да изтриете оксиметричните данни за %1 <b>Please be aware you can not undo this operation!</b> <b>Моля имайте предвид, че тази операция не може да бъде отменена по-късно!</b> Select the day with valid oximetry data in daily view first. Първо изберете ден с валидни оксметрични дани в дневния изглед. Export review is not yet implemented Would you like to zip this card? Choose where to save zip ZIP files (*.zip) Creating zip... Calculating size... Reporting issues is not yet implemented OSCAR Information Loading profile "%1" Imported %1 CPAP session(s) from %2 Импортирани %1 CPAP сесия(и) от %2 Import Success Успешен импорт Already up to date with CPAP data at %1 CPAP данните са си актуални и се намират тук %1 Up to date Актуален Choose a folder Избери папка Import is already running in the background. Импортирането вече работи във фонов режим. A %1 file structure for a %2 was located at: Файловата структура %1 за %2 беше открита тук: A %1 file structure was located at: Файловата структура %1 беше открита тук: Would you like to import from this location? Желаете ли да извършите импорт от това място? Specify Посочи Access to Preferences has been blocked until recalculation completes. Достъпът до конфигурацията е блокиран, докато приключат преизчисленията. There was an error saving screenshot to file "%1" Има проблем със запазването на снимка на екрана във файл "%1" Screenshot saved to file "%1" Снимката на екрана е записана във файл "%1" There was a problem opening MSeries block File: Има проблем с отварянето на файл с данни от MSeries: MSeries Import complete Импорта от MSeries приключи MinMaxWidget Auto-Fit Автоматичен мащаб Defaults По подразбиране Override Ръчно указване The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Режим на ординатната ос, 'Автоматичен мащаб' за автоматично скалиране на изображенията, 'По подразбиране' за настройки според производителя и 'Ръчно указване' за да изберете ваш собствен изглед. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Минимална стойност на ординатната ос.. Тя може да бъде и отрицателно число ако желаете. The Maximum Y-Axis value.. Must be greater than Minimum to work. Максимална стойност на ординатната ос.. Трябва да бъде повече от минималната за да работи. Scaling Mode Режим на мащабиране This button resets the Min and Max to match the Auto-Fit Този бутон възстановява Мин и Макс стойности до Автоматичен мащаб NewProfile Edit User Profile Редактиране на профил I agree to all the conditions above. Съгласен съм с условията. User Information Информация за потребителя User Name Потребителско име Password Protect Profile Защити профила с парола Password Парола ...twice... ...още веднъж... Locale Settings Настройки за локализация Country Държава TimeZone Часова зона about:blank about:blank Very weak password protection and not recommended if security is required. Много слаба защита с парола и не се препоръчва, ако се изисква сигурност. DST Zone Лятно часово време Personal Information (for reports) Лична информация (за отчети) First Name Име Last Name Фамилия It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Не е страшно да се пренебрегнеш или да пропуснеш това, но приблизителната ти възраст е необходима, за да се повиши точността на някои изчисления. D.O.B. Роден на. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Понякога биологичен (рожден) пол е необходим, за да се повиши точността на някои изчисления, но не се колебай да оставиш това поле празно и да пропуснеш някое от тях.</p></body></html> Gender Пол Male Мъж Female Жена Height Височина Metric English Contact Information Информация за контакт Address Адрес Email E-мейл Phone Телефон CPAP Treatment Information Инфромация за CPAP терапия Date Diagnosed Дата на диагностициране Untreated AHI Нетретиран AHI CPAP Mode CPAP режим CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure Предписано налягане Doctors / Clinic Information Информация за доктор/клиника Doctors Name Име на доктор Practice Name Практика Patient ID Пациент ID &Cancel &Отказ &Back &Назад &Next Н&апред Select Country Изберете държава This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Този софтуер е проектиран с цел да помага за визуализиране на данните от вашия CPAP апарат и подобно оборудване. PLEASE READ CAREFULLY МОЛЯ ПРОЧЕТЕТЕ ВНИМАТЕЛНО Accuracy of any data displayed is not and can not be guaranteed. Точността на всички визуализирани данни не е и не може да бъде гарантирана. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Всички генерирани отчети са само за лична употреба а не за определяне дали се спазва терапията или за диагностични цели. Use of this software is entirely at your own risk. Използвайте на своя отговорност. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team Welcome to the Open Source CPAP Analysis Reporter OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Please provide a username for this profile Моля посочете потребителско име за този профил Passwords don't match Паролите не съвпадат Profile Changes Промени по профил Accept and save this information? Приемане и съхраняване на тази информация? &Finish &Край &Close this window &Затвори този прозорец Overview Range: Период: Last Week Последна седмица Last Two Weeks Последни две седмици Last Month Последен месец Last Two Months Последни два месеца Last Three Months Последни три месеца Last 6 Months Последни 6 месеца Last Year Последна година Everything Всичко Custom По избор Snapshot Моментална снимка Start: Начало: End: Край: Reset view to selected date range Анулиране на изгледа до избрания период от време Toggle Graph Visibility Превключване на визуализация на графики Drop down to see list of graphs to switch on/off. Избиране кои графики да се показват и кои не. Graphs Графики Respiratory Disturbance Index Индекс на Дихателното Разстройство Apnea Hypopnea Index Апнея Хипопнея Индекс Usage Употреба Usage (hours) Употреба (часове) Session Times Време сесии Total Time in Apnea Общо време в апнея Total Time in Apnea (Minutes) Общо време в апнея (минути) Body Mass Index Индекс на телесната маса How you felt (0-10) Как се чувствате (0-10) 10 of 10 Charts Show all graphs Показване на всички графики Hide all graphs Скриване на всички графики OximeterImport Oximeter Import Wizard Помощник за импорт от оксиметър Skip this page next time. Не показвай повече тази страница. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? Откъде желаете да правите импорт? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. Потребители на CMS50E/F, когато импортирате директно, моля, не избирайте качване на устройството ви, докато OSCAR не ви подкани за това. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Ако нямате против да бъдете закачени към работещ компютър през ноща, тази опция предоставя полезна плетизмограма, която дава индикация на сърдечния ритъм, наложена върху стандартната графика за кислородна сатурация.</p></body></html> Record attached to computer overnight (provides plethysomogram) Запис докато сте свързани към компютър през ноща (представлява плетизмограма) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Тази опция ви позволява да направите импорт от файл с данни създаден от софтуера, с който е дошъл вашият пулсоксиметър, например SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Импорт от файл с данни, записани от друга програма, например SpO2Review Please connect your oximeter device Моля свържете вашия оксиметър If you can read this, you likely have your oximeter type set wrong in preferences. Ако четете това най-вероятно сте посочили грешен тип за оксиметър в настройките. Please connect your oximeter device, turn it on, and enter the menu Press Start to commence recording Натиснете Старт за да започне запис Show Live Graphs Покажи графики в реално време Duration Продължителност Pulse Rate Пулс Multiple Sessions Detected Налични са повече от една сесия Start Time Начално време Details Детайли Import Completed. When did the recording start? Импорта приключи. Кога е започнал записът? Oximeter Starting time Време на стартиране на оксиметъра I want to use the time reported by my oximeter's built in clock. Желая да използвам времето подадено от вградения часовник на моя оксиметър. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Стартирах този оксиметричен запис в същото време (или почти същото), в което стартирах и CPAP сесия на апарата. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Забележка: Синхронизирането с началото на CPAP сесия винаги ще бъде по-точно.</p></body></html> Choose CPAP session to sync to: Избери CPAP сесия, към която да се синхронизира: You can manually adjust the time here if required: От тук може да се извърши ръчна настройка на времето ако има нужда: HH:mm:ssap ЧЧ:мм:ссAP &Cancel &Отказ &Information Page &Информационна страница CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Синхронизация на дата/час <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Изберете тук за да включите маркиране на импорта с идентификатор, което е полезна функция за хора, които биха ползвали повече от един оксиметър.</p></body></html> Set device identifier Указване на идентификатор Erase session after successful upload Изтриване на сесията след успешен импорт Import directly from a recording on a device Импорт директно от запис на устройство <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Напомняне за CPAP потребители: </span><span style=" color:#fb0000;">Нали не забравихте да импортирате първо вашата CPAP сесия?<br/></span>Ако сте забравили няма да разполагате с валидно време, към което да синхронизирате тази оксиметрична сесия.<br/>За да си гарантирате добра синхронизация най-добре винаги се опитвайте да стартирате едновременно вашия оксиметър и CPAP апарат.</p></body></html> Please choose which one you want to import into OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> &Retry &Опитай отново &Choose Session &Избери сесия &End Recording &Прекрати записа &Sync and Save &Сихронизирай и запиши &Save and Finish &Запиши и прекрати &Start &Старт Scanning for compatible oximeters Сканиране за съвместими оксиметри Could not detect any connected oximeter devices. Не е открит нито един свързан оксиметър. Connecting to %1 Oximeter Свързване към оксиметър %1 Renaming this oximeter from '%1' to '%2' Преименуване на този оксиметър от '%1' на '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Името на оксиметъра е различно.. Ако разполагате само с един оксиметър и го споделяте между различни профили моля укажете едно и също име за него в различните профили. "%1", session %2 "%1", сесия %2 Nothing to import Няма нищо за импорт Your oximeter did not have any valid sessions. Вашият оксиметър няма валидни сесии. Close Затваряне Waiting for %1 to start Изчаква се %1 да стартира Waiting for the device to start the upload process... Изчаква се устройството да стартира процеса по качване на данни... Select upload option on %1 Избери вариант за качване на %1 You need to tell your oximeter to begin sending data to the computer. Необходимо е да укажете на вашия оксиметър да качва сесията към компютър. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Моля свържете вашия оксиметър, влезте в неговото меню и изберете да започне да качва сесията си... %1 device is uploading data... Устройство %1 качва данни... Please wait until oximeter upload process completes. Do not unplug your oximeter. Моля изчакайте докато процесът по качване на данни от оксиметъра приключи. Не го изключвайте. Oximeter import completed.. Импорта от оксиметъра приключи.. Select a valid oximetry data file Избери валиден файл с оксиметрични данни Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Live Oximetry Mode Live Oximetry Stopped Live Oximetry import has been stopped Oximeter Session %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Oximeter not detected Не е открит оксиметър Couldn't access oximeter Оксиметърът не е достъпен Starting up... Стартиране... If you can still read this after a few seconds, cancel and try again Ако все още четете това след няколко секунди откажете действието и опитайте отново Live Import Stopped Импортването на данни в реално време е прекратено %1 session(s) on %2, starting at %3 %1 сесия(и) на %2, с начало %3 No CPAP data available on %1 Няма CPAP данни за %1 Recording... Запис... Finger not detected Не е открит пръст I want to use the time my computer recorded for this live oximetry session. Искам да използвам времето, което моя компютър е записало за тази оксиметрична сесия в реално време. I need to set the time manually, because my oximeter doesn't have an internal clock. Желая да укажа времето ръчно, понеже моят оксиметър няма вътрешен часовник. Something went wrong getting session data Получи се някаква грешка в процеса на получаване на данни от сесията Welcome to the Oximeter Import Wizard Добре дошли в помощника за импорт от оксиметър Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Пулсоксиметрите са медицински устройства, които се използват за измерване на кислородната сатурация в кръвта. По време на продължителна апнея и абнормални дихателни събития нивата на кислородната сатурация в кръвта могат да паднат до ниски стойности и да индикират за проблеми, които се нуждаят от медицинска помощ. You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Също така е хубаво да се знае че някои фирми като Pulox просто ребрандират Contec CMS50 оксиметри под нови имена като например Pulox PO-200, PO-300, PO-400. Те също би трябвало да работят безпроблемно. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Също така могат да се четат оксиметрични сесии от .dat файлове на ChoiceMMed MD300W1. Please remember: Моля запомнете: Important Notes: Важни бележки: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Устройствата Contec CMS50D+ нямат вграден часовник и не записват начален час. Ако не разполагате със CPAP сесия, към която да желаете да синхронизирате е необходимо да въведете начален час след като приключи импортирането на сесията. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Дори за устройства с вграден часовник се препоръчва да си изградите навик да стартирате запис на оксиметъра в същото време, в което стартирате и CPAP сесията. Много често вградените часовници в CPAP апаратите с времето изостават/избързват и не всички разполагат лесна възможност за сверяване на часовник. Oximetry Date Дата d/MM/yy h:mm:ss AP д/ММ/ГГ ч:мм:сс AP R&eset А&нулиране Pulse Пулс &Open .spo/R File &Отвори .spo/R Serial &Import Импорт от &сериен порт &Start Live &В реално време Serial Port Сериен порт &Rescan Ports &Сканиране на портове PreferencesDialog Preferences Настройки &Import &Импорт Combine Close Sessions Комбиниране на затворени сесии Minutes минути Multiple sessions closer together than this value will be kept on the same day. Множество сесии по-близки като време от тази стойност ще бъдат запазвани в един и същ ден. Ignore Short Sessions Игнориране на кратки сесии <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Сесии с продължителност по-кратка от тази няма да бъдат визуализирани<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Време за разделяне по дни Sessions starting before this time will go to the previous calendar day. Сесии започнати преди това време ще бъдат зачислени към предишния календарен ден. Session Storage Options Опции за съхранение на сесиите Create SD Card Backups during Import (Turn this off at your own peril!) Създаване на архивно копие на SD картата по време на импортиране (Изключете това само на ваша отговорност!) Compress SD Card Backups (slower first import, but makes backups smaller) Компресиране на архивните копия на SD картата (първото импортиране е по-бавно, но прави архивите по-малки) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Дните, в които апаратът е използван по-малко от тези часове, да се броят за дни с неспазване на терапията. Обикновено 4 часа се считат за достатъчни за спазване на терапия. hours часа Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Включване/изключване на експериментално подобрение при маркиране на събития. Това позволява откриването на гранични събития и такива, които апаратът може би е пропуснал. Тази настройка трябва да е включена преди импортиране, в противен случай е небходимо изтриване на данните. Flow Restriction Дихателнo ограничение Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Процент на дихателното ограничение от средната стойност. Стойност 20% е подходяща за откриване на апнеи. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Потребителското маркиране е експериментален метод за откриване на събития пропуснати от апарата. Те <span style=" text-decoration: underline;">НЕ СА</span> включени в AHI.</p></body></html> Duration of airflow restriction Продължителност на дихателното ограничение s сек Event Duration Продължителност на събитие Allow duplicates near machine events. Позволяване на дублиращи се близки събития. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Настройка на количеството данни, които се считат за всяка точка в графиката AHI/час. По подразбиране това е 60 минути.. Силно препоръчително е да се остави тази стойност. minutes минути Reset the counter to zero at beginning of each (time) window. Нулиране на брояча в началото на всеки (времеви) прозорец. Zero Reset Нулиране CPAP Clock Drift Отместване на часовника на CPAP Do not import sessions older than: Да не се прави импорт на сесии по-стари от: Sessions older than this date will not be imported Сесии по-стари от тази дата няма да бъдат импортирани dd MMMM yyyy дд мммм гггг Show in Event Breakdown Piechart Показване графика с разбивка на събитията Resync Machine Detected Events (Experimental) Синхронизация на откритите събития от апарата (Експериментално) User definable threshold considered large leak Потребителски указана граница, която да се счита за голям теч Whether to show the leak redline in the leak graph Да се показва ли червена линия в графиката с течове Search Търсене &Oximetry &Оксиметрия Percentage drop in oxygen saturation Процентен спад в кислородна сатурация Pulse Пулс Sudden change in Pulse Rate of at least this amount Внезапна промяна в честотата на пулс поне от поне такъв порядък bpm удара в минута Minimum duration of drop in oxygen saturation Минимална продължителност на спад в кислородната сатурация Minimum duration of pulse change event. Минимална продължителност на събитието промяна в пулс. Small chunks of oximetry data under this amount will be discarded. Малки откъси оксиметрични данни под тази бройка ще бъдат игнорирани. &General &Основно General Settings Основни настройки Daily view navigation buttons will skip over days without data records Навигационните бутони за дневен преглед ще прескачат дните, за които няма данни Skip over Empty Days Прескачане на празни дни Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Позволяване използването на многоядрени процесори, когато е възможно за да се подобри производителността. Предимно се усеща при импортиране. Enable Multithreading Включване на многонишковост Bypass the login screen and load the most recent User Profile Прескачане на екрана за вход и зареждане на последния използван потребителски профил <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Тези функции бяха отстранени наскоро. Ще се появят обратно по-късно. </p></body></html> Changes to the following settings needs a restart, but not a recalc. Промени в следните настройки ще изискват рестарт, а не преизчисления. Preferred Calculation Methods Предпочитан метод за калкулация Middle Calculations Средни изчисления Upper Percentile Горни проценти For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. За консистенция потребителите на ResMed апарати трябва да използват 95% тук, тъй като това е единствената налична стойност за дните само с обща информация. Median is recommended for ResMed users. Използването на 'средно' е подходящо за ResMed апарати. Median Средно Weighted Average Средно по тежест Normal Average Нормално средно True Maximum Реален максимум 99% Percentile 99% процента Maximum Calcs Максимум изчисления Session Splitting Settings Настройки за разделяне на сесии <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Тази опция трябва да се използва внимателно...</span> Изключването й носи последствия като например точността на дните само с общи данни. Това е така, тъй като някои изчисления работят коректно само когато са налични едновременно сесиите с общи данни и индивидуалните дни. </p><p><span style=" font-weight:600;">За потребителите на ResMed:</span> Само защото изглежда естествено рестарта на сесията в 12 часът на обяд да бъде в предишния ден не означава че данните на ResMed мислят също като вас. Индекс форматът STF.edf има сериозна слабост, която прави това лоша идея.</p><p>Тази възможност съществува само за спокойствие на тези, които не се притесняват от това и желаят да виждат това &quot;фиксирано&quot; без значение цената, която се плаща. Не го забравяйте. Ако държите SD картата си всяка нощ в CPAP апарата и правите импорт на данните поне веднъж седмично то би трябвало да нямате чести проблеми с това.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Не разделяй дните с обща информация (Внимание: прочетете подсказката!) Memory and Startup Options Настройки за стартиране и памет Pre-Load all summary data at startup Предварително зареждане на обща информация при старт <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Тази настройка пази данните за вълните и събитията в паметта след употреба с цел ускоряване на зареждането при повторен достъп.</p><p>Това не е необходимо тъй като операционната система от своя страна също кешира скоро използваните файлове.</p><p>Препоръчително е да се изключи, освен ако компютърът ви не разполага с много излишна памет.</p></body></html> Keep Waveform/Event data in memory Запазване на данните за вълни/събития в паметта <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Спестява питането за маловажни въпроси при импортиране.</p></body></html> Import without asking for confirmation Импортиране без потвърждение General CPAP and Related Settings Общи CPAP настройки Enable Unknown Events Channels Включване на канала за непознати събития AHI Apnea Hypopnea Index Апнея Хипопнея Индекс AHI RDI Respiratory Disturbance Index Индекс на дихателно разстройство RDI AHI/Hour Graph Time Window AHI/час графичен времеви прозорец Preferred major event index Предпочитан основен индекс Compliance defined as Дефинирано спазване Flag leaks over threshold Маркиране на течове над Seconds секунди <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Забележка: Целта на това поле не е да се използва за корекция на часовата зона! Моля убедете се че часовникът и часовата зона са коректно указани във вашата операционна система.</p></body></html> Hours Часове <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Реалният максимум е максималният от данните.</p><p>99ти перцентил изчиства редките отклонения.</p></body></html> Combined Count divided by Total Hours Комбиниран брой разделен на общия брой часове Time Weighted average of Indice Усреднена тежест на индекс Standard average of indice Стандартна тежест на индекс Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Компресирай резервните копия на ResMed (EDF), за да спестиш място на диска. Резервните копия на EDF файлове се съхраняват във формат .gz, който е обикновенен формат в Мак ОС и в Линукс OSCAR може да импортира от тази компресирина папка за резервни копия (.gz файл).. За да я използва с ResScan, първо е необходимо да декромпесираш .gz файловете.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Следните опции влияят на количеството дисково пространство, което OSCAR използва, и на времето, което отнема импортирането. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Compress Session Data (makes OSCAR data smaller, but day changing slower.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> Custom CPAP User Event Flagging Персонален избор за маркиране на CPAP събития This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Show Remove Card reminder notification on OSCAR shutdown Check for new version every Проверка за нова версия всеки days. дни. Last Checked For Updates: Последна проверка за обновления: TextLabel I want to be notified of test versions. (Advanced users only please.) &Appearance &Изглед Graph Settings Графични настройки <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> Bar Tops Връх на стълб Line Chart Линейна графика Overview Linecharts Общ изглед линейни графики <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Това прави по-лесно скролирането при увеличение когато се използва чувствителен двупосочен TouchPad</p><p>50мс е препоръчителната стойност.</p></body></html> Scroll Dampening Плавност на скролиране Overlay Flags Overlay маркери Line Thickness Дебелина на линия The pixel thickness of line plots Дебелина на пиксела в линейни графики Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Pixmap кеширането е техника за графично ускорение. Може да причини проблеми с изрисуването на шрифтове в графиките. Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Fonts (Application wide settings) Шрифтове (за тази програма) The visual method of displaying waveform overlay flags. Визуален метод за показване на overlay вълни на маркери. Standard Bars Стандартни стълбове Graph Height Височина на графика Default display height of graphs in pixels Стандартна височина на графиките в пиксели How long you want the tooltips to stay visible. Колко дълго желаете да седят показани подсказките. Events Събития Reset &Defaults &Възстановяване на настройки по подразбиране <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Внимание: </span>Това че можете да го направите не означава че е добра практика.</p></body></html> Waveforms Вълни Flag rapid changes in oximetry stats Маркиране на рязки промени в данните от оксиметър Other oximetry options Други оксиметрични настройки Discard segments under Игнорирай сегменти под Flag Pulse Rate Above Маркиране при пулс над Flag Pulse Rate Below Маркиране при пулс под This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Това изчисление изисква данните за общите течове да бъдат предоставени от CPAP апарата. (Напр., PRS1, но не ResMed, които вече имат тези данни) Изчисленията за неумишлените течове използвани тук са линейни, те не взимат предвид кривата на вентилация на конкретния модел на маската. Ако използвате няколко различни маски изберете усреднена стойност - разликите би трябвало да са пренебрежително малки. Calculate Unintentional Leaks When Not Present Ръчно изчисляване за неумишлените течове 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Бележка: Използва се метод на линейни изчисления. Промяната на тези стойности изискват преизчисляване. Show flags for machine detected events that haven't been identified yet. Показване на флагове за събития, открити от апарата, които не са идентифицирани засега. Tooltip Timeout Време на показване на подсказка Graph Tooltips Подсказки в графики Top Markers Топ маркери Changing SD Backup compression options doesn't automatically recompress backup data. Auto-Launch CPAP Importer after opening profile Automatically load last used profile on start-up <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered Your masks vent rate at 20 cmH2O pressure Your masks vent rate at 4 cmH2O pressure l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Always save screenshots in the OSCAR Data folder Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR On Opening Profile Профил Welcome Добре дошли Daily Дневна Statistics Статистики Switch Tabs No change After Import Other Visual Settings Други визауални настройки Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Заглаждането на ръбове прави графиките по-плавни.. Някои графики изглеждат по-добре, когато е включена тази опция. Това засяга и отпечатаните репорти. Опитайте опцията за да прецените дали Ви допада. Use Anti-Aliasing Използване на заглаждане на ръбове Makes certain plots look more "square waved". Прави някои графики да изглеждат с по "квадратни вълни". Square Wave Plots Графики с "квадратни вълни" Use Pixmap Caching Използване на Pixmap кеширане Animations && Fancy Stuff Анимации && красоти Whether to allow changing yAxis scales by double clicking on yAxis labels Да се позволи ли смяна на мащаба по абциса/ордината чрез двойно кликане върху етикетите Allow YAxis Scaling Позволяване на мащабиране по ос/абциса Whether to include machine serial number on machine settings changes report Include Serial Number Graphics Engine (Requires Restart) Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Font Шрифт Size Размер Bold Удебелен Italic Курсив Application Приложение Graph Text Текст към графики Graph Titles Заглавие към графики Big Text Голям текст Details Детайли &Cancel &Отказ &Ok O&K Name Име Color Цвят Flag Type Тип маркер Label Етикет CPAP Events CPAP събития Oximeter Events Оксиметрични събития Positional Events Позиционни събития Sleep Stage Events Събития относно фази на сън Unknown Events Непознати събития Double click to change the descriptive name this channel. Направете двойно кликане с мишката за да смените описателното име на този канал. Double click to change the default color for this channel plot/flag/data. Направете двойно кликане с мишката за да смените цвят за графика/маркер/данни в този канал. <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> %1 %2 %1 %2 Overview Общ преглед Double click to change the descriptive name the '%1' channel. Направете двойно кликане с мишката за да смените описателното име за канал '%1'. Whether this flag has a dedicated overview chart. Дали този флаг да има собствена графика. Here you can change the type of flag shown for this event От тук можете да промените какъв тип маркер да се показва за това събитие This is the short-form label to indicate this channel on screen. Това е кратък етикет, който ще се показва за този канал на екрана. This is a description of what this channel does. Това е описание какво прави този канал. Lower Долна Upper Горна CPAP Waveforms CPAP вълна Oximeter Waveforms Оксиметрична вълна Positional Waveforms Позиционна вълна Sleep Stage Waveforms Вълна за фази на съня Whether a breakdown of this waveform displays in overview. Дали разбивката на тази вълна да се показва в преглед. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Тук можете да укажете <b>долна</b> граница, която да се използва за калкулация на %1 вълната Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Тук можете да укажете <b>горна<b> граница, която да се използва за калкулация на %1 вълната Data Processing Required A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Data Reindex Required Необходимо е реиндексиране на данните A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? За да влезнат в сила тези промени е необходимо да се извърши реиндексиране. Тази операция може да отнеме няколко минути. Желаете ли да се извършат тези промени? Restart Required Необходим е рестарт If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. If you need to conserve disk space, please remember to carry out manual backups. Are you sure you want to disable these backups? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Are you really sure you want to do this? Сигурни ли сте че желаете да направите това? Flag Флаг Minor Flag Минимален маркер Span Разделяне Always Minor Постоянен маркер No CPAP machines detected Will you be using a ResMed brand machine? Never One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? This may not be a good idea Това може би не е добра идея ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Апаратите ResMed S9 рутинно изтриват някои данни от SD картата (по-стари от 7 и 30 дни, в зависимост от резолюцията). ProfileSelector Filter: Филтър: Reset filter to see all profiles Възстанови филтъра, за да видиш всички профили Version Версия &Open Profile &Edit Profile &Редактиране на профил &New Profile Profile: None Please select or create a profile... Destroy Profile Profile Профил Ventilator Brand Ventilator Model Other Data Last Imported Name Име You must create a profile Enter Password for %1 Въведете парола за %1 You entered an incorrect password Въвели сте грешна парола Forgot your password? Ask on the forums how to reset it, it's actually pretty easy. Select a profile first The selected profile does not appear to contain any data and cannot be removed by OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. You are about to destroy profile '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. DELETE Sorry Съжалявам You need to enter DELETE in capital letters. Необходимо е да изпишете DELETE с главни букви. There was an error deleting the profile directory, you need to manually remove it. Получи се грешка при опит за изтриване на директорията на профила, налага се да го изтриете ръчно. Profile '%1' was succesfully deleted Профил '%1' бе изтрит успешно Bytes KB KB MB MB GB GB TB TB PB PB Summaries: Events: Backups: Hide disk usage information Show disk usage information Name: %1, %2 Име: %1, %2 Phone: %1 Телефон: %1 Email: <a href='mailto:%1'>%1</a> Address: Адрес: No profile information given Profile: %1 ProgressDialog Abort Прекрати QObject No Data Няма данни On Вкл Off Изкл ft ft lb lb oz oz Kg кг cmH2O cmH2O Med. Сред. Min: %1 Мин: %1 Min: Мин: Max: Макс: Max: %1 Макс: %1 %1 (%2 days): %1 (%2 дни): %1 (%2 day): %1 (%2 ден): % in %1 % в %1 Hours Часове Min %1 Мин %1 Hours: %1 Часове: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 слаб употреба, %2 без употреба, от общо %3 дни (%4% спазване на терапията.) Продължителност: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Сесии: %1 / %2 / %3 Продължителност: %4 / %5 / %6 Най-продължителна: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Продължителност: %3 Начало: %2 Mask On С маска Mask Off Без маска %1 Length: %3 Start: %2 %1 Продължителност: %3 Начало: %2 TTIA: TTIA: TTIA: %1 TTIA: %1 bpm удара в минута ? ? Severity (0-1) Тежест (0-1) Error Грешка Warning Внимание Please Note Моля обърнете внимание Graphs Switched Off Графиките са изключени Sessions Switched Off Сесиите са изключени &Yes &Да &No &Не &Cancel &Отказ &Destroy &Унищожи &Save &Запис BMI BMI Weight Тегло Zombie Зомби Pulse Rate Пулс Plethy Плетизмограма Pressure Налягане Daily Дневна Profile Профил Overview Общ преглед Oximetry Оксиметрия Oximeter Оксиметър Event Flags Флагове събития Default По подразбиране CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP Min EPAP Мин EPAP Max EPAP Макс EPAP IPAP IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Овлажнител H H OA OA A A CA CA FL FL LE LE EP EP VS VS VS2 VS2 RERA RERA PP PP P P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF2 UF3 UF3 PS PS AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI EPI Min IPAP Мин IPAP App key: Operating system: Операционна система: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Software Engine ANGLE / OpenGLES Desktop OpenGL m m cm cm in Minutes минути Seconds секунди h ч m м s с ms мс Events/hr събития/час Hz Hz Litres литра ml ml Breaths/min Вдишвания/min Degrees Градуса Information Информация Busy Зает Only Settings and Compliance Data Available Summary Data Only Max IPAP Макс IPAP SA SA PB PB IE IE Insp. Time Време вдишване Exp. Time Време издишване Resp. Event Респ. събитие Flow Limitation Ограничения на дебита Flow Limit Ограничение дебит SensAwake Събуждания Pat. Trig. Breath Вдишв. иниц. от пациент Tgt. Min. Vent Целева мин. белодр. вмест Target Vent. Целева белодр. вмест. Minute Vent. Дихателен обем в минута. Tidal Volume Дихателен обем Resp. Rate Респираторна честота Snore Хъркане Leak Теч Leaks Течове Total Leaks Общи течове Unintentional Leaks Неумишлени течове MaskPressure Налягане в маска Flow Rate Дебит Sleep Stage Фаза на сън Usage Употреба Sessions Сесии Pr. Relief Облекчение на налягане No Data Available Няма данни Bookmarks Отметки Mode Режим Model Модел Brand Марка Serial Сериен номер Series Серия Machine Апарат Channel Канал Settings Настройки Motion Name Име DOB Дата на раждане Phone Телефон Address Адрес Email E-мейл Patient ID Пациент ID Date Дата Bedtime Лягане Wake-up Ставане Mask Time Време с маска Unknown Непознат None Няма Ready Готов First Първи Last Последен Start Начало End Край Yes Да No Не Min Мин Max Макс Med Средно Average Средно Median Средно Avg Средно W-Avg Усреднено Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine Апарат неспособен да записва данни Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Machine Unsupported Апарата не се поддържа I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Scanning Files... Importing Sessions... Finishing up... Untested Data Machine Untested CPAP-Check AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS Flex Flex Lock Whether Flex settings are available to you. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Passover Target Time PRS1 Humidifier Target Time Hum. Tgt Time Mask Resistance Setting Mask Resist. Hose Diam. 15mm 15мм Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Type of ramp curve to use. Linear SmartRamp Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Fixed Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Breath BPM Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled Variable Breathing UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend Peak Flow Peak flow during a 2-minute interval 22mm 22мм Backing Up Files... model %1 DreamStation 2 unknown model Flex Mode Flex режим PRS1 pressure relief mode. Режим облекчение на налягането на PRS1. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time Време на събуждане Bi-Flex Bi-Flex Flex Level Степен на гъвкавост PRS1 pressure relief setting. Настройка на облекчение на налягането на PRS1. Humidifier Status Статус овлажнител PRS1 humidifier connected? Свързан ли е овлажнителят на PRS1? Disconnected Несвързан Connected Свързан Humidification Mode PRS1 Humidification Mode Humid. Mode Fixed (Classic) Adaptive (System One) Heated Tube Tube Temperature PRS1 Heated Tube Temperature Tube Temp. PRS1 Humidifier Setting Hose Diameter Диаметър на маркуч Diameter of primary CPAP hose Диаметър на основния CPAP маркуч 12mm 12mm Auto On Автоматично включване A few breaths automatically starts machine Няколко вдишвания автоматично включват апарата Auto Off Автоматично изключване Machine automatically switches off Апаратът автоматично се изключва Mask Alert Предупреждение за маска Whether or not machine allows Mask checking. Дали апарата позволява проверка за маска. Show AHI Покажи AHI Breathing Not Detected A period during a session where the machine could not detect flow. BND BND Timed Breath Периодично дишане Machine Initiated Breath Дишане инициирано от апарата TB TB Windows User Уиндоус потребител Using , found SleepyHead - You must run the OSCAR Migration Tool <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Данните от Вашият стар апарат трябва да бъдат регенерирани в случай че тази опция за архивиране не е била изключена в настройките преди да направите предишен импорт на данни.</i> Launching Windows Explorer failed Неуспешно стартиране на Windows Explorer Could not find explorer.exe in path to launch Windows Explorer. explorer.exe не бе открит в дефинираните пътища на Уиндоус и не може да бъде стартиран Windows Explorer. <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> OSCAR does not yet have any automatic card backups stored for this device. This means you will need to import this machine data again afterwards from your own backups or data card. Това означава че ще е необходимо да направите отново импорт на данните за този апарат по-късно. Important: Важно: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Are you ready to upgrade, so you can run the new version of OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Machine Database Changes Промяна в базата данни за апарат OSCAR %1 needs to upgrade its database for %2 %3 %4 Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. The machine data folder needs to be removed manually. Тази папка с данни от апарата трябва да бъде премахната ръчно. This folder currently resides at the following location: Тази папка в момента се намира тук: Rebuilding from %1 Backup Презареждане от архив %1 or CANCEL to skip migration. You cannot use this folder: Migrating files from to OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. We suggest you use this folder: Click Ok to accept this, or No if you want to use a different folder. Next time you run OSCAR, you will be asked again. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Choose or create a new folder for OSCAR data Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. As you did not select a data folder, OSCAR will exit. The folder you chose is not empty, nor does it already contain valid OSCAR data. Data directory: It is likely that doing this will cause data corruption, are you sure you want to do this? Много е вероятно това да причини повреда на данни, сигурни ли сте че искате да продължите? Question Въпрос Exiting Изход Are you sure you want to use this folder? Сигурни ли сте, че желаете да използвате тази папка? Don't forget to place your datacard back in your CPAP machine Не забравяйте да поставите обратно вашата SD карта в CPAP апарата OSCAR Reminder You can only work with one instance of an individual OSCAR profile at a time. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Loading profile "%1"... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Unable to create zip! Are you sure you want to reset all your channel colors and settings to defaults? Сигурни ли сте че желаете да възстановите всички цветове на канали и настройки към стойностите им по подразбиране? Are you sure you want to reset all your waveform channel colors and settings to defaults? Сигурни ли сте че желаете да възстановите фабричните настройки за цветовете на каналите на вълните? There are no graphs visible to print Няма видими графики за печат Would you like to show bookmarked areas in this report? Желаете ли да се покажат областите с отметки в този репорт? Printing %1 Report Печатане на репорт %1 %1 Report %1 Отчет : %1 hours, %2 minutes, %3 seconds : %1 часа, %2 минути, %3 секунди RDI %1 RDI %1 AHI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 UAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 Репорт от %1 до %2 Entire Day's Flow Waveform Вълна на дебита за целия ден Current Selection Текуща селекция Entire Day Цял ден Page %1 of %2 Страница %1 от %2 Jan Яну Feb Фев Mar Мар Apr Апр May Май Jun Юни Jul Юли Aug Авг Sep Сеп Oct Окт Nov Ное Dec Дек Events Събития Duration Продължителност (% %1 in events) (% %1 в събития) Therapy Pressure Терапевтично налягане Inspiratory Pressure Налягане при вдишване Lower Inspiratory Pressure Ниско налягане при вдишване Higher Inspiratory Pressure Високо налягане при вдишване Expiratory Pressure Налягане при издишване Lower Expiratory Pressure Ниско налягане при издишване Higher Expiratory Pressure Високо налягане при издишване Pressure Support Поддръжка налягане PS Min PS мин Pressure Support Minimum Минимално поддържано налягане PS Max PS макс Pressure Support Maximum Максимално поддържано налягане Min Pressure Мин. налягане Minimum Therapy Pressure Минимално терапевтично налягане Max Pressure Макс. налягане Maximum Therapy Pressure Максимално терапевтично налягане Ramp Time Време за рампинг Ramp Delay Period Закъсненителен рампинг период Ramp Pressure Рампинг налягане Starting Ramp Pressure Начално рампинг налягане Ramp Event Рампинг събитие Ramp Рампинг An abnormal period of Cheyne Stokes Respiration Абнормален период на Чейн-Стоксово дишане An apnea that couldn't be determined as Central or Obstructive. A restriction in breathing from normal, causing a flattening of the flow waveform. Vibratory Snore (VS2) Вибраторно хъркане (VS2) A ResMed data item: Trigger Cycle Event Mask On Time Време с маска Time started according to str.edf Начално време според str.edf Summary Only Само обща информация An apnea where the airway is open Апнея, при която горните дихателни пътища са отворени An apnea caused by airway obstruction Апнея причинена поради обструкция на горните дихателни пътища Hypopnea Хипопнея A partially obstructed airway Частична обструкция на горните дихателни пътища Unclassified Apnea Некласифицирана апнея UA UA Vibratory Snore Вибраторно хъркане A vibratory snore Вибраторно хъркане A vibratory snore as detcted by a System One machine Вибраторно хъркане открито от апарат System One Pressure Pulse Пулсиращо налягане A pulse of pressure 'pinged' to detect a closed airway. Пулсиращо налягане, което 'ping-ва' за да засече обструкция в дихателния път. Large Leak Големи течове A large mask leak affecting machine performance. Голям теч от маската влияещ на способноста на апарата да спазва терапията. LL LL Non Responding Event Нереагиращо събитие A type of respiratory event that won't respond to a pressure increase. Тип респираторно събитие, което не реагира на увеличение на налягането. Expiratory Puff Издишване от уста Intellipap event where you breathe out your mouth. Събитието на Intellipap, когато се издишва през уста. SensAwake feature will reduce pressure when waking is detected. Функцията SensAwake ще намали налягането, когато засече събуждане. User Flag #1 Потребителски маркер #1 User Flag #2 Потребителски маркер #2 User Flag #3 Потребителски маркер #3 Heart rate in beats per minute Сърдечен пулс в удари за минута Blood-oxygen saturation percentage Процент кислородна сатурация Plethysomogram Плетизмограма An optical Photo-plethysomogram showing heart rhythm Оптична фото-плетизмограма показваща сърдечен ритъм Pulse Change Промяна в пулс A sudden (user definable) change in heart rate Внезапна (може да се укаже от потребител) промяна в сърдечния ритъм SpO2 Drop SpO2 спад A sudden (user definable) drop in blood oxygen saturation Внезапен (може да се укаже от потребител) спад в кислородната сатурация SD SD Breathing flow rate waveform Вълна на дебита на дишане L/min л/мин Mask Pressure Налягане в маска Amount of air displaced per breath Количество въздух изтласквано при дишане Graph displaying snore volume Графика показваща обема на хъркане Minute Ventilation Белодробна вместимост Amount of air displaced per minute Количество въздух вдишан за минута Respiratory Rate Респираторна честота Rate of breaths per minute Честота на дишанията в минута Patient Triggered Breaths Вдишвания инициирани от пациента Percentage of breaths triggered by patient Процент вдишвания инициирани от пациента Pat. Trig. Breaths Вдишв. иниц. от пациент Leak Rate Теч на въздух Rate of detected mask leakage Дебит на засечен теч от маска I:E Ratio В:И съотношение Ratio between Inspiratory and Expiratory time Съотношение между времето на вдишване и издишване ratio съотношение Pressure Min Налягане мин Pressure Max Налягане макс Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting Cheyne Stokes Respiration Чейн-Стоксово дишане CSR CSR Periodic Breathing Периодично вдишване An abnormal period of Periodic Breathing Абнормален период на периодично вдишване Clear Airway Отворен дихателен път Obstructive Обструкция Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Респираторно усилие свързано с араузал (RERA): Ограничение в дишането, което причинява събуждане или смущения на съня. Leak Flag Маркер за теч LF LF A user definable event detected by OSCAR's flow waveform processor. Perfusion Index Перфузионен индекс A relative assessment of the pulse strength at the monitoring site Относително преценяване на силата на пулса в мястото на следене Perf. Index % Перф. индекс % Mask Pressure (High frequency) Expiratory Time Време издишване Time taken to breathe out Време за издишване Inspiratory Time Време вдишване Time taken to breathe in Време за вдишване Respiratory Event Респираторно събитие Graph showing severity of flow limitations Графика показваща степента на тежест при обструкция на дишането Flow Limit. Ограничение дебит. Target Minute Ventilation Целева белодробна вместимост Maximum Leak Максимум теч The maximum rate of mask leakage Максимум дебит на теч от маска Max Leaks Макс теч Apnea Hypopnea Index Апнея Хипопнея Индекс Graph showing running AHI for the past hour Графика показваща AHI за последния час Total Leak Rate Общ дебит на теч Detected mask leakage including natural Mask leakages Засеченият теч от маска включително умишленият теч Median Leak Rate Среден дебит на теч Median rate of detected mask leakage Средния дебит на засечения теч от маската Median Leaks Средни течове Respiratory Disturbance Index Индекс на Дихателното Разстройство Graph showing running RDI for the past hour Графика показваща RDI за последния час Movement Movement detector CPAP Session contains summary data only CPAP сесията съдържа само обща информация PAP Mode PAP режим Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Apnea An apnea reportred by your CPAP machine. PAP Device Mode PAP режим на апарат APAP (Variable) APAP (променлив) ASV (Fixed EPAP) ASV (фиксиран EPAP) ASV (Variable EPAP) ASV (променлив EPAP) Height Височина Physical Height Ръст Notes Бележки Bookmark Notes Бележки за отметки Body Mass Index Индекс на телесната маса How you feel (0 = like crap, 10 = unstoppable) Как се чувствате (0 = отвратително, 10 = прекрасно) Bookmark Start Старт отметка Bookmark End Край отметка Last Updated Последно обновяване Journal Notes Журнални бележки Journal Журнал 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=Будно 2=РЕМ фаза 3=Лек сън 4=Дълбок сън Brain Wave Мозъчна вълна BrainWave Мозъчна вълна Awakenings Събуждания Number of Awakenings Брой на събуждания Morning Feel Усещане сутрин How you felt in the morning Как се чувствате на сутринта Time Awake Будно време Time spent awake Време прекарано в будно състояние Time In REM Sleep Време в РЕМ фаза Time spent in REM Sleep Време прекарано в РЕМ фаза Time in REM Sleep Време в РЕМ фаза Time In Light Sleep Време в лек сън Time spent in light sleep Време прекарано в лек сън Time in Light Sleep Време в лек сън Time In Deep Sleep Време в дълбок сън Time spent in deep sleep Време прекарано в дълбок сън Time in Deep Sleep Време в дълбок сън Time to Sleep Време за заспиване Time taken to get to sleep Време необходимо за заспиване Zeo ZQ Zeo ZQ Zeo sleep quality measurement Zeo измерено качество на сън ZEO ZQ ZEO ZQ Debugging channel #1 Test #1 For internal use only Debugging channel #2 Test #2 Zero Нула Upper Threshold Горна граница Lower Threshold Долна граница Orientation Ориентация Sleep position in degrees Позиция на спящия в градуси Inclination Наклон Upright angle in degrees Ъгъл на изправяне в градуси Days: %1 Дни: %1 Low Usage Days: %1 Дни с малко използване: %1 (%1% compliant, defined as > %2 hours) (%1% спазване, дефинирано като > %2 часа) (Sess: %1) (Сесия: %1) Bedtime: %1 Лягане %1 Waketime: %1 Ставане %1 (Summary Only) (само резюме) There is a lockfile already present for this profile '%1', claimed on '%2'. Вече съществува lockfile за този профил '%1', предявен на '%2'. Fixed Bi-Level Фиксиран Bi-Level Auto Bi-Level (Fixed PS) Автоматичен Bi-Level (фиксиран PS) Auto Bi-Level (Variable PS) Автоматичен Bi-Level (променлив PS) 99.5% 90% {99.5%?} varies n/a Fixed %1 (%2) Фиксиран %1 (%2) Min %1 Max %2 (%3) Мин %1 Макс %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %1 над %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Мин EPAP %1 Макс IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2 %3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1 %2 IPAP %3 %4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> (last night) (снощи) (1 day ago) (%2 days ago) No oximetry data has been imported yet. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings SmartFlex настройки ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Somnopose софтуер Zeo Zeo Personal Sleep Coach Personal Sleep Coach Locating STR.edf File(s)... Cataloguing EDF Files... Queueing Import Tasks... Finishing Up... CPAP Mode CPAP режим VPAPauto VPAPauto ASVAuto ASVAuto iVAPS PAC Auto for Her Auto for Her EPR EPR ResMed Exhale Pressure Relief ResMed облекчение на налягането при издишане Patient??? Пациент??? EPR Level EPR степен Exhale Pressure Relief Level Степен на облекчение на налягането при издишане Response Patient View SmartStart SmartStart Machine auto starts by breathing Автоматичен старт на апарата чрез дишане Smart Start Умен старт Humid. Status Статус овлажнител Humidifier Enabled Status Вкл/изкл. статус на овлажнител Humid. Level Степен на овлажн Humidity Level Степен на овлажняване Temperature Температура ClimateLine Temperature Темпеартура на ClimateLine Temp. Enable Вкл. температура ClimateLine Temperature Enable Включена температура на ClimateLine Temperature Enable Включена температура AB Filter AB филтър Antibacterial Filter Антибактериален филтър Pt. Access Пац. достъп Essentials Plus Climate Control Климат контрол Manual Ръчен Soft Standard Стандартен BiPAP-T BiPAP-S BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Advanced Разширено Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... Auto Автоматичен Mask Маска ResMed Mask Setting Настройки маска ResMed Pillows Възглавнички Full Face Фул фейс Nasal Назална Ramp Enable Включен рампинг Database Outdated Please Rebuild CPAP Data Базата данни не е актуална Моля презаредете CPAP данните си (%2 min, %3 sec) (%2 мин, %3 сек) (%3 sec) (%3 сек) Pop out Graph The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View There is no data to graph d MMM yyyy [ %1 - %2 ] d MMM yyyy [ %1 - %2 ] Hide All Events Скрий всички събития Show All Events Покажи всички събития Unpin %1 Graph Откачи графика %1 Popout %1 Graph Pin %1 Graph Закачи графика %1 Plots Disabled Графиките са изключени Duration %1:%2:%3 Продължителност %1 %2 %3 AHI %1 AHI %1 Relief: %1 Облекчение: %1 Hours: %1h, %2m, %3s Време: %1ч, %2м, %3с Machine Information Информация за апарата Journal Data Журнални данни OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR will not touch this folder, and will create a new one instead. Please be careful when playing in OSCAR's profile folders :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. OSCAR picked only the first one of these, and will use it in future: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Ако старите Ви данни липсват копирайте съдържанието на всички останали Journal_XXXXXXX папки в тази ръчно. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Backing up files... Reading data files... SmartFlex Mode Режим SmartFlex Intellipap pressure relief mode. Режим на облекчение на налягането при Intellipap. Ramp Only Само по време на рампинг Full Time През цялото време SmartFlex Level SmartFlex степен Intellipap pressure relief level. Степен на облекчение на налягането при Intellipap. Snoring event. SN Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Snapshot %1 Снимка %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... Scanning Files Migrating Summary File Location Loading Summaries.xml.gz Loading Summary Data Please Wait... Updating Statistics cache Usage Statistics Статистики за употреба %1 Charts %1 of %2 Charts Loading summaries Dreem Your Viatom device generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Viatom Viatom Software New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to check for updates. Please try again later. SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank about:blank SessionBar %1h %2m %1h %2m No Sessions Present Няма налични сесии SleepStyleLoader Import Error Грешка при импорт This Machine Record cannot be imported in this profile. Записите от този апарат не могат да бъдат импортирани в този профил. The Day records overlap with already existing content. Дневните записи се застъпват с вече съществуващи такива. Statistics Details Детайли Most Recent Последен ден Therapy Efficacy Ефикасност на терапията Last 30 Days Последни 30 дни Last Year Последна година Average %1 Средно %1 CPAP Statistics CPAP Статистики CPAP Usage Употреба на CPAP Average Hours per Night Часове средно за нощ Compliance (%1 hrs/day) Leak Statistics Статистики за изтекъл въздух Pressure Statistics Статистики за налягане Oximeter Statistics Статистики за оксиметър Blood Oxygen Saturation Кислородна сатурация Pulse Rate Пулс %1 Median %1 медиана Min %1 Мин %1 Max %1 Макс %1 %1 Index %1 индекс % of time in %1 % от времето в %1 % of time above %1 threshold % от времето над праг %1 % of time below %1 threshold % от времето под праг %1 Name: %1, %2 Име: %1, %2 DOB: %1 Дата на раждане: %1 Phone: %1 Телефон: %1 Email: %1 E-мейл: %1 Address: Адрес: This report was prepared on %1 by OSCAR %2 Oscar has no data to report :( Days Used: %1 Дни употреба: %1 Low Use Days: %1 Дни с малко използване: %1 Compliance: %1% Спазване: %1% Days AHI of 5 or greater: %1 Дни с AHI 5 или по-голям: %1 Best AHI Най-добро AHI Date: %1 AHI: %2 Дата: %1 AHI: %2 Worst AHI Най-лошо AHI Best Flow Limitation Най-добро ограничение на дебита Date: %1 FL: %2 Дата: %1 FL: %2 Worst Flow Limtation Най-лошо ограничение на дебита No Flow Limitation on record Без запис за ограничение на дебита Worst Large Leaks Най-лоши големи течове Date: %1 Leak: %2% Дата: %1 Течове: %2% No Large Leaks on record Без запис за големи течове Worst CSR Най-лошо CSR Date: %1 CSR: %2% Дата: %1 CSR: %2% No CSR on record Без запис зa CSR Worst PB Най-лошо PB Date: %1 PB: %2% Дата: %1 PB: %2% No PB on record Без запис за PB Want more information? Желаете повече информация? OSCAR needs all summary data loaded to calculate best/worst data for individual days. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Моля включете опцията за предварително зареждане на обща информация при старт в настройките за да се осигури наличността на тези данни. Best RX Setting Най-добри настройки Date: %1 - %2 Дати: %1 - %2 AHI: %1 Total Hours: %1 Worst RX Setting Най-лоши настройки Last Week Последна седмица Changes to Machine Settings No data found?!? Last 6 Months Последни 6 месеца Last Session Последна сесия No %1 data available. Няма %1 данни на разположение. %1 day of %2 Data on %3 %1 ден от %2 данни за %3 %1 days of %2 Data, between %3 and %4 %1 дни от %2 данни, между %3 и %4 OSCAR is free open-source CPAP report software Days Дни Pressure Relief Облекчение на налягане Pressure Settings Настройки налягане Machine Information Информация за апарата First Use Първо използване Last Use Последно използване Welcome Welcome to the Open Source CPAP Analysis Reporter What would you like to do? Какво желаете да направите? CPAP Importer Oximetry Wizard Daily View Overview Общ преглед Statistics Статистики <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> It would be a good idea to check File->Preferences first, as there are some options that affect import. Note that some preferences are forced when a ResMed machine is detected First import can take a few minutes. Първият импорт може да отнеме няколко минути. The last time you used your %1... Последният път, когато сте използвали %1 апарата си... last night снощи %2 days ago преди %2 дни was %1 (on %2) е било %1 (в %2) %1 hours, %2 minutes and %3 seconds %1 часа, %2 минути и %3 секунди Your machine was on for %1. <font color = red>You only had the mask on for %1.</font> <font color = red>Използвали сте маската само %1.</font> under over reasonably close to equal to You had an AHI of %1, which is %2 your %3 day average of %4. Your CPAP machine used a constant %1 %2 of air Your pressure was under %1 %2 for %3% of the time. Налягането ви е било под %1 %2 за %3% от времето. Your machine used a constant %1-%2 %3 of air. Your EPAP pressure fixed at %1 %2. Вашето EPAP налягане фиксирано на %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Вашето IPAP налягане е било под %1 %2 за %3% от времето. Your EPAP pressure was under %1 %2 for %3% of the time. Вашето EPAP налягане е било под %1 %2 за %3% от времето. Your machine was under %1-%2 %3 for %4% of the time. Вашият апарат е бил под %1-%2 %3 за %4% от времето. 1 day ago Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. Няма импортирани CPAP данни засега. gGraph %1 days gGraphView 100% zoom level 100% мащаб Restore X-axis zoom to 100% to view entire selected period. Restore X-axis zoom to 100% to view entire day's data. Reset Graph Layout Възстановяване на изгледа на диаграмите Resets all graphs to a uniform height and default order. Възстановяване на стандартна височина и подредба на диаграмите. Y-Axis ординатна ос Plots Диаграми CPAP Overlays CPAP слоеве Oximeter Overlays Оксиметър слоеве Dotted Lines Пунктирани линии Double click title to pin / unpin Click and drag to reorder graphs Remove Clone Премахни клонинг Clone %1 Graph Клониране на %1 графиката OSCAR-code-v1.3.1/Translations/Chinese.zh_CN.ts000066400000000000000000014205601417327530600211100ustar00rootroot00000000000000 AboutDialog &About &关于 Release Notes 版本注释 Credits 信任 GPL License GPL许可证 Close 关闭 Show data folder 显示数据文件夹 Sorry, could not locate About file. 抱歉,无法找到相关文件. Sorry, could not locate Credits file. 抱歉,无法找到信用证书. OSCAR %1 Important: 重要提示: To see if the license text is available in your language, see %1. 若要查看许可证书是否可用,请参阅%1. Sorry, could not locate Release Notes. 抱歉,无法找到版本注释. As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. About OSCAR %1 CMS50F37Loader Could not find the oximeter file: 抱歉,无法找到血氧计文件: Could not open the oximeter file: 抱歉,无法打开血氧计文件: CMS50Loader Could not get data transmission from oximeter. 无法传输血氧计数据. Please ensure you select 'upload' from the oximeter devices menu. 请确认已在血氧计菜单中选择'上传'操作. Could not find the oximeter file: 抱歉,无法找到血氧计文件: Could not open the oximeter file: 抱歉,无法打开血氧计文件: CheckUpdates Checking for newer OSCAR versions Daily B u 线 Big Color 颜色 Notes 备注 Small Journal 日志 Position Sensor Sessions 位置传感器会话 Add Bookmark 添加标记 Remove Bookmark 删除标记 Pick a Colour 选择一种颜色 Complain to your Equipment Provider! 向设备供应商投诉! Session Information 会话信息 Sessions all off! 关闭所有会话! %1 event %1 事件 Go to the most recent day with data records 跳转到最近一天的数据记录 Machine Settings 设置 B.M.I. 体重指数. Sleep Stage Sessions 睡眠阶段会话 Oximeter Information 血氧仪信息 Events 事件 CPAP Sessions CPAP会话 Medium Starts 开始 Weight 体重 Zombie 极差 Bookmarks 标记簇 %1 events %1 事件 events 事件 BRICK :( 崩溃 :( Event Breakdown 事件分类 SpO2 Desaturations 血氧饱和度下降 Awesome 极好 Pulse Change events 脉搏变化 SpO2 Baseline Used 血氧饱和度基准 Zero hours?? 零时?? Go to the previous day 跳转到前一天 Bookmark at %1 在%1添加标记 Statistics 统计 Breakdown 分类 Unknown Session 未知会话 Sessions exist for this day but are switched off. 会话存在,但是已被关闭。 Duration 时长 View Size 显示尺寸 Impossibly short session 不可用会话 No %1 events are recorded this day 当前日期无 %1 事件记录 Show or hide the calender 显示或者隐藏日历 Go to the next day 下一天 Session Start Times 会话开始次数 Session End Times 会话结束次数 Time over leak redline 漏气时长超限 UF1 UF1 UF2 UF2 Total time in apnea 呼吸暂停总时间 Total ramp time 斜坡升压总时间 Time outside of ramp 斜坡升压之外的时间 Flags 标记 Graphs 图表 "Nothing's here!" "无's这里!" Oximetry Sessions 血氧测定法 Model %1 - %2 模式 %1 - %2 This day just contains summary data, only limited information is available. 此日仅有概要数据,仅含有少量可用信息。 I'm feeling ... 我感觉... Show/hide available graphs. 显示/隐藏可用图表。 Details 详情 Time at Pressure 压力时间 Click to %1 this session. 点击到%1会话. disable 禁用 enable 启用 %1 Session #%2 %1h %2m %3s %1h %2m %3s PAP Mode: %1 PAP模式: %1 Start 开始 End 结束 Unable to display Pie Chart on this system 无法在此系统上显示饼图 10 of 10 Event Types This CPAP machine does NOT record detailed data Sorry, this machine only provides compliance data. 抱歉,此设备仅提供相容数据。 No data is available for this day. 10 of 10 Graphs If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. This bookmark is in a currently disabled area.. (Mode and Pressure settings missing; yesterday's shown.) 99.5% 90% {99.5%?} ExportCSV AHI AHI End 结束 Date 日期 End: 结束: Quick Range: 快速变化范围: Daily 日常 Event 事件 Start 开始 Last Fortnight 最后两周 Most Recent Day 最近一天 Count 计数 Filename: 文件名称: Select file to export to 选择文件导出到 Resolution: 分辨率: Cancel 取消 Dates: 日期: Custom 自定义 Export 导出 Start: 开始: Data/Duration 数据/时长 CSV Files (*.csv) CSV文件(*.ccsv) Last Month 上个月 Last 6 Months 过去六个月 Total Time 总时长 DateTime 日期时间 Session Count 会话计数 Session 会话 Everything 所有 Last Week 上周 Last Year 去年 Export as CSV 导出为CSV格式数据 Sessions_ 会话_ Details 详情 Summary_ 概要_ Details_ 详情_ Sessions 会话 FPIconLoader Import Error 导入出错 This Machine Record cannot be imported in this profile. 无法在此配置文件中导入此设备的记录。 The Day records overlap with already existing content. 本日的数据已覆盖已存储的内容. Help Search Topic: 搜索主题: Contents 目录 Index 索引 Search 搜索 Hide this message 隐藏此信息 Help Files are not yet available for %1 and will display in %2. 帮助文件尚不可用于%1并将显示在%2。 HelpEngine did not set up correctly 帮助引擎未正确设置 HelpEngine could not register documentation correctly. 帮助引擎无法正确注册文档。 No %1 result(s) for "%2" %1 结果 "%2" clear 清除 Help files do not appear to be present. 帮助文件不存在。 No documentation available Please wait a bit.. Indexing still in progress 无可用文件 MD300W1Loader Could not find the oximeter file: 抱歉,无法找到血氧计文件: Could not open the oximeter file: 抱歉,无法打开血氧计文件: MainWindow Help 帮助 &Data &数据 &File &文档 &Help &帮助 &View &查看 E&xit &退出 Daily 日常 Import &ZEO Data 导入&ZEO数据 MSeries Import complete M系列呼吸机数据导入完成 There was an error saving screenshot to file "%1" 错误信息截屏存储在 "%1"文档中 Importing Data 正在导入数据 Online Users &Guide 在线&指南 View &Welcome 查看&欢迎 There was a problem opening MSeries block File: 打开M系列呼吸机文件出错: &About &关于 View &Daily 查看&日常 View &Overview 查看&概述 Access to Preferences has been blocked until recalculation completes. 重新计算完成之前,已阻止访问首选项。。 Import RemStar &MSeries Data 导入瑞斯迈&M系列呼吸机数据 Change &User 更改&用户 View S&tatistics 查看&统计 Monthly 每月 Change &Language 更改&语言 Import 导入 Please wait, importing from backup folder(s)... 请稍等,正在由备份文件夹导入... Right &Sidebar 右&侧边栏 View Statistics 查看统计信息 CPAP Data Located CPAP数据已定位 Access to Import has been blocked while recalculations are in progress. 导入数据访问被阻止,重新计算进行中。 Sleep Disorder Terms &Glossary 睡眠障碍术语&术语表 Use &AntiAliasing 使用&图形保真 Report Mode 报告模式 &Profiles &配置文件 Standard 标准 Statistics 统计 &Statistics &统计 &Advanced &高级 Print &Report 打印&报告 Take &Screenshot &截屏 Overview 总览 Show Debug Pane 显示调试面板 &Edit Profile &编辑配置文件 Import Reminder 导入提示 Welcome 欢迎使用 Import &Somnopose Data 导入&睡眠姿势数据 Screenshot saved to file "%1" 截屏存储于 "%1" &Preferences &参数设置 &Frequently Asked Questions &常见问题 Oximetry 血氧测定 Change &Data Folder 更改&数据文件夹 Date Range 日期范围 O&ximetry Wizard &血氧仪安装向导 Purge &Current Selected Day 清除&当前所选日期的数据 Current Days 当前天数 Import Problem 导入错误 Choose a folder 选择一个文件夹 Are you sure you want to delete oximetry data for %1 确定清除%1内的血氧仪数据吗 <b>Please be aware you can not undo this operation!</b> <b>请注意,您无法撤消此操作!</b> Select the day with valid oximetry data in daily view first. 请先在每日视图中选择有效血氧仪数据的日期. Rebuild CPAP Data 重建数据 Exit 退出 &Automatic Oximetry Cleanup &血氧仪数据自动清理 Please insert your CPAP data card... 请插入CPAP数据卡... Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. 如果已经为所有CPAP数据进行了<i>备份 <b>,</b>仍然可以完成此操作</i>,但必须手动从备份中还原。 Are you really sure you want to do this? 确定进行此操作? Because there are no internal backups to rebuild from, you will have to restore from your own. 由于没有可用的内部备份可供重建使用,请自行从备份中还原。 Would you like to import from your own backups now? (you will have no data visible for this machine until you do) 您希望立即从备份导入吗?(完成导入,才能有数据显示) A %1 file structure for a %2 was located at: %1文件结构的%2位置在: A %1 file structure was located at: %1 文件结构的位置在: Would you like to import from this location? 从这里导入吗? Specify 指定 Navigation 导航 Bookmarks 标记簇 Records 存档 Daily Sidebar 每日侧边栏 Daily Calendar 日历 Imported %1 CPAP session(s) from %2 导入 %1呼吸机会话来自 %2 Import Success 导入成功 Already up to date with CPAP data at %1 已更新CPAP数据位于 %1 Up to date 最新 Couldn't find any valid Machine Data at %1 此处没有有效的呼吸机数据 %1 Note as a precaution, the backup folder will be left in place. 请注意:请将备份文件夹保留在合适的位置。 Are you <b>absolutely sure</b> you want to proceed? 您 <b>确定</b> 要继续吗? Exp&ort Data 导&出数据 Backup &Journal 备份&日志 %1's Journal %1'的日志 Choose where to save journal 选择存储日志的位置 XML Files (*.xml) XML Files (*.xml) Show Performance Information 显示性能信息 CSV Export Wizard CSV导出向导 Export for Review 导出查看 Import is already running in the background. 已在后台执行导入操作. Profiles 配置文件 Purge Oximetry Data 清除血氧测定数据 Help Browser 帮助浏览器 Loading profile "%1" 加载配置文件"%1" Please open a profile first. 请先打开配置文件. &About OSCAR &关于OSCAR Report an Issue 报告问题 The FAQ is not yet implemented FAQ尚未实施 If you can read this, the restart command didn't work. You will have to do it yourself manually. 重启命令不起作用,需要手动重启。 Are you sure you want to rebuild all CPAP data for the following machine: 确定要重建以下设备的所有CPAP数据吗: For some reason, OSCAR does not have any backups for the following machine: 由于某种原因,OSCAR没有以下设备的任何备份: You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> 您将要<font size=+2>删除以下设备的</font>OSCAR数据库:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: 文件权限错误导致清除过程失败; 您必须手动删除以下文件夹: No help is available. 没有可用的帮助。 Export review is not yet implemented 导出检查不可用 Reporting issues is not yet implemented 报告问题不可用 Please note, that this could result in loss of data if OSCAR's backups have been disabled. 请注意,如果禁用了OSCAR's备份,这可能会导致数据丢失。 &Maximize Toggle No profile has been selected for Import. The User's Guide will open in your default browser The Glossary will open in your default browser Show Daily view Show Overview view Maximize window Reset sizes of graphs Show Right Sidebar Show Statistics view Show &Line Cursor Show Daily Left Sidebar Show Daily Calendar System Information Show &Pie Chart Show Pie Chart on Daily page OSCAR Information &Reset Graphs Reset Graph &Heights Standard graph order, good for CPAP, APAP, Bi-Level Advanced Advanced graph order, good for ASV, AVAPS Troubleshooting Purge ALL Machine Data &Import CPAP Card Data Import &Dreem Data Create zip of CPAP data card Create zip of all OSCAR data %1 (Profile: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Choose where to save screenshot Image files (*.png) OSCAR does not have any backups for this machine! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Would you like to zip this card? Choose where to save zip ZIP files (*.zip) Creating zip... Calculating size... Show Personal Data Create zip of OSCAR diagnostic logs Check For &Updates Check for updates not implemented Import &Viatom/Wellue Data Purge Current Selected Day &CPAP &CPAP &Oximetry &血氧测量 &Sleep Stage &Position &All except Notes All including &Notes Find your CPAP data card There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete MinMaxWidget Auto-Fit 自适应 Defaults 默认 Override 覆盖 The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Y轴缩放模式:“自适应”意味着自动适应大小,“默认”意味着使用制造商的出厂值,“覆盖”意味着自定义. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Y轴最小值,此值可为负。 The Maximum Y-Axis value.. Must be greater than Minimum to work. Y轴最大值,必须大于最小值方可正常工作. Scaling Mode 缩放模式 This button resets the Min and Max to match the Auto-Fit 此按钮将按自适应模式重置最大最小值 NewProfile ASV 适应性支持同期模式 APAP 全自动正压通气 CPAP 持续气道正压通气 Male &Back &上一步 &Next &下一步 TimeZone 时区 Email 电子邮件 Phone 电话 Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. 所有生成的报告仅限个人使用,不能用于医疗诊断。 &Close this window &关闭窗口 Edit User Profile 编辑用户信息 CPAP Treatment Information 呼吸机治疗信息 Password Protect Profile 密码保护 Accuracy of any data displayed is not and can not be guaranteed. 不保证任何显示数据的准确性。 D.O.B. D.O.B. Female Gender 性别 Height 身高 Contact Information 联系方式 Locale Settings 归属地设置 CPAP Mode CPAP模式 Select Country 选择国家 PLEASE READ CAREFULLY 请认真阅读 Untreated AHI 未治疗时的AHI Address 地址 I agree to all the conditions above. 同意以上条款。 DST Zone DST时区 RX Pressure 释放压力 Password 密码 Use of this software is entirely at your own risk. 后果自负。 Passwords don't match 密码不匹配 First Name 名字 Last Name 姓氏 Country 国家 &Cancel &取消 &Finish &完成 Bi-Level 双水平 Profile Changes 配置文件更改 Personal Information (for reports) 个人信息 User Name 用户名 This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. 这款软件用于协助读取用于治疗睡眠障碍的各种CPAP的数据. User Information 用户信息 ...twice... ...两次... Doctors Name 医生姓名 Doctors / Clinic Information 医生/诊所信息 Practice Name 患者姓名 Date Diagnosed 诊断日期 Accept and save this information? 接收并保存此信息? Patient ID 患者编号 Please provide a username for this profile 请输入用户名 about:blank 关于:空白 It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. 可以跳过这一步,但提供大概年龄数据可以提高计算的准确性。 <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>可以留空或者跳过这一步,但提供出生日期和性别可以提高计算的准确性。</p></body></html> OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR已根据<a href='qrc:/COPYING'>GNU公共许可证免费发布v3版本</a>,没有任何担保,也没有任何针对任何目的的适用性声明。 OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR 仅仅作为一个数据读取显示软件,不能替代医生提供有效的医疗指导。 The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. 该软件作者对<u>任何人</u> 使用或误用本软件不承担任何责任。 Welcome to the Open Source CPAP Analysis Reporter 欢迎使用开源CPAP分析报告 Metric English OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team Very weak password protection and not recommended if security is required. Overview End: 结束: Usage 使用 Respiratory Disturbance Index 呼吸 紊乱 指数 10 of 10 Charts Show all graphs 显示所有图表 Reset view to selected date range 将视图重置为所选日期范围 Drop down to see list of graphs to switch on/off. 下拉以查看要打开/关闭的图表列表。 Usage (hours) 使用 (小时) Last Three Months 前三个月 Custom 自定义 How you felt (0-10) 感觉如何 (0-10) Graphs 图表 Range: 范围: Start: 开始: Last Month 上个月 Apnea Hypopnea Index 暂停 低通气 指数 Last 6 Months 前六个月 Body Mass Index 身体 重量 指数 Session Times 会话时间 Last Two Weeks 前两周 Everything 所有 Last Week 上周 Last Year 去年 Toggle Graph Visibility 切换视图 Hide all graphs 隐藏所有图表 Last Two Months 前两个月 Total Time in Apnea 呼吸暂停总时间 Total Time in Apnea (Minutes) 呼吸暂停总时间 (分钟) Snapshot OximeterImport Oximeter Import Wizard 血氧仪导入向导 Skip this page next time. 下次跳过此页面。 <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? 从何处导入数据? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>如果你不介意整晚连入电脑,可以生成容积图,可以直观的展现心率,显示在常规的血氧读数顶端.</p></body></html> Record attached to computer overnight (provides plethysomogram) 整晚连入电脑记录(提供体描仪) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>开启这个功能则允许由数据文件夹导入入SpO2Review这样的脉搏血氧仪记录的读数.</p></body></html> Import from a datafile saved by another program, like SpO2Review 导入其他程序创建的数据文件,例如SpO2Review所创建的文件 Please connect your oximeter device 请连接血氧仪 Press Start to commence recording 开始记录 Show Live Graphs 显示实时图表 Duration 时长 Pulse Rate 脉搏 Multiple Sessions Detected 检测到多重会话 Details 详情 Import Completed. When did the recording start? 导入完成.何时开始记录? Oximeter Starting time 血氧仪开启时间 I want to use the time reported by my oximeter's built in clock. 使用血氧仪的时间作为系统时钟. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. 开启血氧仪记录的时间和开启CPAP的时间一致(或相近). <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>注意:同步CPAP会话的起始时间往往更加准确.</p></body></html> Choose CPAP session to sync to: 选择CPAP会话同步于: You can manually adjust the time here if required: 如果有需要,可以在此手动调整时间: HH:mm:ssap 小时:分钟:秒 &Cancel &取消 &Information Page &信息页 &Retry &再试一次 &Choose Session &选择会话 &End Recording &停止记录 &Sync and Save &同步并存储 &Save and Finish &存储并结束 &Start &开始 Scanning for compatible oximeters 正在扫描所兼容的血氧仪 Could not detect any connected oximeter devices. 没有连接血氧仪设备. Connecting to %1 Oximeter 正在与%1血氧仪连接 Select upload option on %1 在%1选择上传选项 %1 device is uploading data... %1设备正在上传数据... Please wait until oximeter upload process completes. Do not unplug your oximeter. 请等待血氧仪上传数据结束,期间不要拔出血氧仪. Oximeter import completed.. 血氧仪数据导入完成.. Select a valid oximetry data file 选择一个可用的血氧仪数据文件 Oximeter not detected 未检测到血氧仪 Couldn't access oximeter 无法与血氧仪连通 Starting up... 开始... If you can still read this after a few seconds, cancel and try again 如果在几秒钟后仍然可以阅读此内容,请取消并重试 Live Import Stopped 实时导入已停止 %1 session(s) on %2, starting at %3 %1 会话 %2, 开始时间是 %3 No CPAP data available on %1 在%1中没有可用的CPAP数据 Recording... 正在存储... Finger not detected 没有检测到手指 I want to use the time my computer recorded for this live oximetry session. 希望使用电脑的时间作为实时血氧会话的时间. I need to set the time manually, because my oximeter doesn't have an internal clock. 血氧仪没有内置时钟,需要手动设置。 Something went wrong getting session data 获取会话数据时出错 Start Time 开始时间 "%1", session %2 "%1", %2会话 Waiting for %1 to start 等待 %1 开始 Waiting for the device to start the upload process... 正在等待设备开始上传数据... CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time 设置日期/时间 <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>请激活血氧仪标识符,以区分多个血氧仪</p></body></html> Set device identifier 设置设备标识符 Erase session after successful upload 上传成功后删除会话 Import directly from a recording on a device 直接由磁盘导入 <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">提醒呼吸机用户: </span><span style=" color:#fb0000;">请先将呼吸机数据导入<br/></span>否则血氧仪数据将无法与呼吸机数据在时间轴上同步.<br/>为了保证一致性,请同时启动呼吸机以及血氧仪.</p></body></html> If you can read this, you likely have your oximeter type set wrong in preferences. 如果您看到此处提示,请重新设置血氧仪类型. Renaming this oximeter from '%1' to '%2' 正在为血氧仪从 '%1' 改名到 '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. 血氧仪名称不同...如果您仅有一个血氧仪而且与不用的用户公用,请将其名称统一. Nothing to import 没有可导入的数据 Your oximeter did not have any valid sessions. 血氧仪会话无效. Close 关闭 You need to tell your oximeter to begin sending data to the computer. 请在血氧仪操作开始上传数据到电脑. Please connect your oximeter, enter it's menu and select upload to commence data transfer... 请连接血氧仪,点击菜单选择数据上传... Welcome to the Oximeter Import Wizard 欢迎使用血氧仪数据导入向导 Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. 脉动血氧仪是一款用于测量血样饱和度的医疗设备,在呼吸暂停以及低通气事件发生时,血氧饱和度大幅降低会引起一系列的健康问题,需要引起重视。 You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. 你可能注意到,其他的公司,比如Pulox, Pulox PO-200, PO-300, PO-400.也可以使用. It also can read from ChoiceMMed MD300W1 oximeter .dat files. 还可以读取ChoiceMMed MD300W1血氧仪的 .dat文件. Please remember: 请谨记: Important Notes: 重要提示: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Contec CMS50D+没有内部时钟,所以不能够记录开始时间。如果呼吸机数据与其无法同步,请在导入完成后手动输入开始时间. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. 即使对于含有内部时钟的血氧仪,仍然建议养成血氧仪与呼吸机同时开启记录的习惯,因为呼吸机的内部时钟会存在漂移现象,而且不易复位. Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) 血氧仪文件 (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: 血氧仪无法解析所选文件: Live Oximetry Mode 实时血氧测量模式 Live Oximetry Stopped 实时血氧测量已停止 Live Oximetry import has been stopped 实时血氧测量导入已停止 Oximeter Session %1 血氧仪会话 %1 If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! 如果您尝试同步血氧测定和CPAP数据,请确保在继续之前先导入您的CPAP会话! CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. CMS50E/F用户,在直接导入时,请不要在设备上选择上传,直到OSCAR提示您为止。 <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>如果启用,OSCAR将使用您的计算机当前时间自动重置CMS50的内部时钟。</p></body></html> Please choose which one you want to import into OSCAR 请选择哪个要导入OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR需要一个开始时间来知道将血氧仪会话保存到哪里。</p><p>选择以下选项之一:</p></body></html> OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR能够在CPAP会话数据的同时跟踪血氧测定数据,从而对CPAP治疗的有效性提供有价值的见解。它也将与脉搏血氧计独立工作,允许存储,跟踪和审查记录数据。 For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. 为了使OSCAR能够直接从血氧计设备上定位和读取,需要确保在计算机上安装了正确的设备驱动程序(如USB转串行UART)。有关更多信息%1,请点击这里%2. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR目前可以兼容Contec CMS50D+、CMS50E、CMS50F和CMS50I系列血氧计。<br/>(请注意:直接从蓝牙模式导入是不可行的 <span style=" font-weight:600;"></span> ) CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> Please connect your oximeter device, turn it on, and enter the menu Oximetry Date 日期 Pulse 脉搏 &Open .spo/R File &打开 SPO/R 文件 R&eset 重&置 Serial &Import 序列号&导入 Serial Port 产口 d/MM/yy h:mm:ss AP 日/月/年 小时:分钟:秒 &Start Live &开始 &Rescan Ports &扫描端口 PreferencesDialog s s &Ok &好的 bpm bpm Graph Height 图表高度 Font 字体 Size 大小 &CPAP &CPAP General Settings 通用设置 <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>在使用双向触摸板放大时,滚动显示更容易</p><p>50ms是推荐值.</p></body></html> Event Duration 事件区间 Pulse 脉搏 days. 天. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">会话将会比这个稍短并且不会显示<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Ignore Short Sessions 忽略短时会话 Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. 气流限值的中值百分比 20%的气流限值有利于检测呼吸暂停。 Sessions starting before this time will go to the previous calendar day. 在此之前开始一段会话将会计入上一天. Session Storage Options 会话存储选项 Graph Titles 图表标题 Zero Reset 归零 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">自定义标记是一个检测被机器忽略的事件实验方法。它们<span style=" text-decoration: underline;">不</span>包含于 AHI.</p></body></html> Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. 激活/禁用(实验性)突出事件标记。 允许检测边缘事件以及设备遗漏事件 这个选项必须在导入前激活,否则需要清除缓存。 Flow Restriction 气流限制 Minimum duration of drop in oxygen saturation 血氧饱和下降的最小区间 Overview Linecharts 线形图概览 Whether to allow changing yAxis scales by double clicking on yAxis labels 是否允许以双击Y轴来进行Y轴的缩放 Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. 像素映射缓存是图形加速技术,或许会导致在您的操作系统上的字体显示异常. Bypass the login screen and load the most recent User Profile 跳过用户登录界面,登录常用用户 Data Reindex Required 重建数据索引 Scroll Dampening 滚动抑制 hours 小时 Standard Bars 标准导航条 99% Percentile 99%百分位数 Small chunks of oximetry data under this amount will be discarded. 少量的血氧测定数据将被丢弃。 Reset the counter to zero at beginning of each (time) window. 在每个窗口打开时将计数器归零。 minutes 分钟 Minutes 分钟 Graph Settings 图形设置 Bold 突出显示 Minimum duration of pulse change event. 脉搏改变事件的最小区间。 Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. 图形保真技术可以使得图表显示更加圆润 当这一功能启用时,特定的图块会突出显示 在打印报告中也会体现出来 可以进行尝试。 Median is recommended for ResMed users. 建议瑞斯迈用户选择中值。 Italic 意大利 Enable Multithreading 启用多线程 This may not be a good idea 不正确的应用 Weighted Average 平均体重 Median 中间值 Sudden change in Pulse Rate of at least this amount 脉搏突然改变的最小值 Search 查询 Middle Calculations 中值计算 Skip over Empty Days 跳过无数据的日期 Allow duplicates near machine events. 允许多重记录趋近机器事件数据。 The visual method of displaying waveform overlay flags. 将视窗显示的波形的标记进行叠加。 Upper Percentile 增大 Restart Required 重启请求 True Maximum 真极大值 For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. 为了保持一致,ResMed的用户需要设置95%, 它将作为唯一值出现在汇总界面内。 Graph Text 图表文字 Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. 允许使用多核CPU以提高性能 提高导入性能。 Line Chart 线形图 How long you want the tooltips to stay visible. 设置工具提示可见时间长度。 Multiple sessions closer together than this value will be kept on the same day. 缩小多个会话间距可以使其显示在同一天. Duration of airflow restriction 气流限制的持续时间 Bar Tops 任务条置顶 Other Visual Settings 其他显示设置 Day Split Time 时段 Big Text 大字体 <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>此项功能已被取消,但会在后续版本内加入. </p></body></html> ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9设备会定期从SD卡内删除7天和30天以内的数据(取决于分辨率)。 Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. 注意使用时间低于4个小时的日期。 Daily view navigation buttons will skip over days without data records 点击日常查看导航按钮将跳过没有数据记录的日期 Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. 为AHI/小时图表的每一个点调节数据量 默认值到60分钟,建议使用这一默认值。 &Cancel &取消 Last Checked For Updates: 上次的更新: Details 详情 Use Anti-Aliasing 使用图形保真技术显示 Animations && Fancy Stuff 动画 && 爱好 &Import &导入 Changes to the following settings needs a restart, but not a recalc. 更改如下设置需要重启,但不需要重新估算。 &Appearance &外观 The pixel thickness of line plots 线条图的像素厚度 Combine Close Sessions 关闭所有会话 Allow YAxis Scaling 允许Y轴缩放 Use Pixmap Caching 使用像素映射缓存 Check for new version every 检查是否有新版本 Maximum Calcs 最大估算值 Tooltip Timeout 工具提示超时 Preferences 参数设置 Default display height of graphs in pixels 使用默认项目显示图标高度 Overlay Flags 叠加标记 Makes certain plots look more "square waved". 在特定区块显示更多的方波。 Percentage drop in oxygen saturation 血氧饱和百分比下降 &General &通用 Compress SD Card Backups (slower first import, but makes backups smaller) 压缩备份SD卡数据(节省空间但导入速度变慢) Normal Average 正常体重 A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? 为了更改数据设置将重建索引,这将花费几分钟的时间。 确定要更改数据吗? Preferred Calculation Methods 首选计算方法 Graph Tooltips 图形工具提示 &Oximetry &血氧测量 CPAP Clock Drift CPAP时钟漂移 Square Wave Plots 方波图 TextLabel 文本标签 Application 应用 Line Thickness 线宽 Do not import sessions older than: 请不要导入早于如下日期的会话: Sessions older than this date will not be imported 将不会导入早于此日期的会话 dd MMMM yyyy 天天 月月月月 年年年年 User definable threshold considered large leak 用户自定义大量漏气数值 Whether to show the leak redline in the leak graph 是否在漏气图表中显示漏气限值红线 Are you really sure you want to do this? 确定进行此操作? Show in Event Breakdown Piechart 在事件分类饼图中显示 Resync Machine Detected Events (Experimental) 重新同步呼吸机检测到的事件(试验性功能) Create SD Card Backups during Import (Turn this off at your own peril!) 在导入过程中创建SD卡备份(请自行关闭此功能!) Reset &Defaults 恢复&默认设置 <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">警告: </span>这仅仅是提示您可以这么做,但这不是个好建议.</p></body></html> Show flags for machine detected events that haven't been identified yet. 显示已标记但仍未被识别的事件. Waveforms 波形 Name 姓名 Color 颜色 Label 标签 Events 事件 Flag rapid changes in oximetry stats 血氧仪统计数据中标记快速改变 Other oximetry options 其他血氧仪选项 Discard segments under 删除偏低的数据 Flag Pulse Rate Above 心率标记高 Flag Pulse Rate Below 心率标记低 Flag 标记 Minor Flag 次要标记 Span 范围 Always Minor 保持小 Flag Type 标记类型 CPAP Events CPAP 事件 Oximeter Events 血氧仪事件 Positional Events 位置事件 Sleep Stage Events 睡眠阶段事件 Unknown Events 未知事件 Double click to change the descriptive name this channel. 双击改变这个通道的描述。 Double click to change the default color for this channel plot/flag/data. 双击改变这个区块/标记/数据的默认颜色. Here you can change the type of flag shown for this event 在这里可以改变事件显示的标记类型 This is the short-form label to indicate this channel on screen. 这是将在屏幕所显示的此通道的简短描述标签. This is a description of what this channel does. 这里显示的是这个通道的作用. Lower 更低 Upper 更高 CPAP Waveforms CPAP波形 Oximeter Waveforms 血氧仪波形 Positional Waveforms 位置波形 Sleep Stage Waveforms 睡眠阶段波形 Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform 在这里可以为%1波形设置<b>更低的</b> 阈值来进行某些计算 Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform 在这里可以为%1波形设置<b>更高的</b> 阈值来进行某些计算 Top Markers 置顶标志 Session Splitting Settings 会话拆分设置 <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">设置前请注意...</span>关闭这一选项的后果就是影响汇总报告的准确性,因为某些计算只在某一天的数据保存在一起的时候才能正常工作 . </p><p><span style=" font-weight:600;">瑞思迈用户:</span> 正午12点之前属于前一天,这对你我来说感觉很自然,但不代表瑞思迈也这么认为。STF.edf功能很弱,并不适合来实现这一功能。.</p><p>这一选项存在的意义在于安抚那些不在意看到什么,只是想看到 &quot;固定的数据&quot;不管成本,但是这始终是有代价的.如果你每天都记录数据,并且每周导入电脑一次,不会经常遇到这个报错 .</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) 不可分割(警告:请阅读工工具提示信息) Memory and Startup Options 存储与启动选项 Pre-Load all summary data at startup 启动时预加载所有汇总数据 <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>这一设置将波形以及事件数据保存在内存中以便于提升再次访问的速度.</p><p>这不是一项必须打开的设置,因为操作系统会缓存加载过的数据.</p><p>建议保持这一设置呈关闭状态,除非内存非常大.</p></body></html> Keep Waveform/Event data in memory 保持波形/事件数据在内存中 <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>在导入期间减少任何不重要的确认对话框。</p></body></html> Import without asking for confirmation 无需确认直接导入 General CPAP and Related Settings 通用呼吸机以及相关设置 Enable Unknown Events Channels 启用位置事件通道 AHI Apnea Hypopnea Index 呼吸暂停低通气指数 AHI RDI Respiratory Disturbance Index 呼吸紊乱指数 RDI AHI/Hour Graph Time Window AHI/小时 图形时间窗 Preferred major event index 首选主要事件索引 Compliance defined as 符合性定义为 Flag leaks over threshold 漏气超阈值标志 Seconds <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>注意: 不能够进行时区自动矫正,请确保您操作系统时间以及时区设置正确.</p></body></html> Hours 小时 <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>真极大值是数据设置的最大值.</p><p>滤除百分之九十九的异常值.</p></body></html> Combined Count divided by Total Hours 合并计数除以总小时数 Time Weighted average of Indice 时间加权平均值指数 Standard average of indice 标准平均值 Custom CPAP User Event Flagging 自定义呼吸机用户事件 Fonts (Application wide settings) 字体 Overview 总览 Double click to change the descriptive name the '%1' channel. 双击更改 '%1通道的描述信息. Whether this flag has a dedicated overview chart. 此标志是否有专用的概览图表. Whether a breakdown of this waveform displays in overview. 是否显示此波形的细分概览。 This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. 这一计算需要呼吸机记录的总漏气量) 非意识漏气量的计算是线性的,因为没有面罩排气曲线可参考. 如果你佩戴不同的面罩,请选择平均值,值应足够接近. Calculate Unintentional Leaks When Not Present 计算非意识漏气量 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. 注意:默认选用线性计算法。如果更改数据需重新计算. %1 %2 %1 %2 Auto-Launch CPAP Importer after opening profile 打开配置文件后自动启动CPAP导入程序 Automatically load last used profile on start-up 在启动时自动加载上次使用的配置文件 l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">由于总结设计限制,ResMed机器不支持更改这些设置。</p></body></html> Oximetry Settings 血氧饱和度设置 <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> I want to be notified of test versions. (Advanced users only please.) On Opening 开启状态 Profile 配置文件 Welcome 欢迎使用 Daily 日常 Statistics 统计 Switch Tabs 切换标签 No change 无更改 After Import 导入后 Never 从不 Data Processing Required 需要数据处理 A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? 应用这些更改需要数据重新/解压缩过程。此操作可能需要几分钟才能完成。 确实要进行这些更改吗? Graphics Engine (Requires Restart) 图形引擎 One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? 您所做的一个或多个更改将要求重新启动此应用程序,以便这些更改生效。 确定进行此操作吗? Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. 压缩Rresmed(EDF)备份以节省磁盘空间。 备份的EDF文件以.gz格式存储, 这在Mac和Mac上很常见 Linux平台.. OSCAR可以本地从此压缩备份目录导入.. 要将其与ResScan一起使用,首先需要解压缩.gz文件。 The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. 以下选项会影响OSCAR使用的磁盘空间量,并影响导入的时间。 This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. 这使得OSCAR的数据占用了大约一半的空间。 但它使导入和日期变化需要更长的时间.. 建议使用带有小型固态硬盘的计算。 Compress Session Data (makes OSCAR data smaller, but day changing slower.) 压缩会话数据(使OSCAR数据量变小,但日期变化较慢。) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) 保留ResMed设备SD卡数据的备份, ResMed S9系列设备删除超过7天的高分辨率数据, 以及超过30天的图表数据。 如果您需要重新安装,OSCAR可以保留此数据的副本。 (强烈推荐,除非你的磁盘空间不足或者不关心图形数据) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>通过预先加载所有摘要数据,可以使启动OSCAR的速度稍慢一些,这样可以加快浏览概述和稍后的其他一些计算。</p><p>如果有大量数据,建议关闭此项<span style=" font-style:italic;">everything</span> 总而言之,仍然必须加载所有摘要数据。</p><p>注意:该设置不会影响波形和事件数据,根据需要进行加载。</p></body></html> This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Show Remove Card reminder notification on OSCAR shutdown OSCAR关闭时显示删除卡提醒通知 <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>加载配置文件时要打开哪个选项卡。(注意:如果OSCAR设置为在启动时不打开配置文件,它将默认为配置文件)</p></body></html> Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. 如果OSCAR出现图形渲染问题,请尝试从默认设置(桌面OpenGL)更改此设置。 <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>请注意:</b>OSCAR的高级会话分割功能由于其设置和摘要数据的存储方式的限制而无法用于ResMed设备,因此它们已针对该配置文件被禁用。</p><p>在ResMed设备上,日期将在中午分开,和在ResMed的商业软件的设置相同。</p> If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. 如果您需要再次重新导入此数据(无论是在OSCAR还是ResScan中),此数据将不会再返回。 If you need to conserve disk space, please remember to carry out manual backups. 如果需要节省磁盘空间,请手动备份。 Are you sure you want to disable these backups? 确实要禁用这些备份吗? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. 不建议关闭备份,因为如果发现错误,OSCAR需要这些备份来重建数据库。 Changing SD Backup compression options doesn't automatically recompress backup data. Your masks vent rate at 20 cmH2O pressure Your masks vent rate at 4 cmH2O pressure Whether to include machine serial number on machine settings changes report Include Serial Number <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered Always save screenshots in the OSCAR Data folder No CPAP machines detected Will you be using a ResMed brand machine? Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) ProfileSelector Filter: 筛选: Version 版本 &Open Profile &打开配置文件 &Edit Profile &编辑配置文件 &New Profile &新建配置文件 Profile: None 配置文件:无 Please select or create a profile... 请选择或创建一个配置文件... Destroy Profile 删除配置文件 Profile 配置文件 Ventilator Brand 呼吸机品牌 Ventilator Model 呼吸机型号 Other Data 其他参数 Last Imported 最新导入 Name 姓名 Enter Password for %1 键入密码 %1 You entered an incorrect password 密码不正确 Forgot your password? 忘记密码? Ask on the forums how to reset it, it's actually pretty easy. 在论坛上询问如何重置。 Select a profile first 首先选择配置文件 If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. 如果由于忘记密码而试图删除,则需要重置密码或手动删除配置文件文件夹。 You are about to destroy profile '<b>%1</b>'. 你将要删除配置文件'<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. 请注意:这将不可避免地删除配置文件以及存储在%2下的所有备份数据。 Enter the word <b>DELETE</b> below (exactly as shown) to confirm. 如下图所示,请确认输入信息:DELETE。 DELETE 删除 Sorry 抱歉 You need to enter DELETE in capital letters. 需要输入大写字母 DELETE. There was an error deleting the profile directory, you need to manually remove it. 配置文件目录出错,请手动移除. Profile '%1' was succesfully deleted 配置文件 '%1'已成功删除 Hide disk usage information 隐藏磁盘使用信息 Show disk usage information 显示磁盘使用信息 Name: %1, %2 名字: %1, %2 Phone: %1 电话号码:%1 Email: <a href='mailto:%1'>%1</a> 发件人:<a href='发送到:%1'>%1</a> Address: 地址: No profile information given 未提供配置文件信息 Profile: %1 配置文件: %1 Bytes 字节 KB KB MB MB GB GB TB TB PB PB Summaries: 摘要: Events: 事件: Backups: 备份: You must create a profile Reset filter to see all profiles The selected profile does not appear to contain any data and cannot be removed by OSCAR ProgressDialog Abort 退出 QObject A 未分类 H 低通气 P 压力 AI 呼吸暂停指数 CA 中枢性 EP 呼气压力 FL 气流受限 HI 低通气指数 IE 呼吸 LE 漏气率 LL 大量漏气 Kg 公斤 O2 氧气 OA 阻塞性 NR 未响应事件 PB 周期性呼吸 PC 混合面罩 in PP 最高压力 PS 压力 On 开启 RE 呼吸作用 SA 呼吸暂停 SD SD UA 未知暂停 VS 鼾声指数 ft 英尺 lb oz 盎司 AHI 呼吸暂停低通气指数 ASV 适应性支持通气模式 BMI 体重指数 CAI 中枢性暂停指数 Apr 四月 Aug 八月 Avg 平均 DOB 生日 EPI 呼气压力指数 Dec 十二月 FLI 气流受限指数 End 结束 Feb 二月 Jan 一月 Jul 七月 Jun 六月 NRI 未响应事件指数 Mar 三月 Max 最大 May 五月 Med 中间值 Min 最小 Nov 十一月 Oct 十月 Off 关闭 RDI 呼吸紊乱指数 REI 呼吸作用指数 UAI 未知暂停指数 UF1 UF1 UF2 UF2 UF3 UF3 Sep 九月 VS2 鼾声指数2 bpm 次每分钟 APAP 全自动正压通气 CPAP 持续气道正压通气 Min EPAP 呼气压力最小值 EPAP 呼气压力 Date 日期 Min IPAP 吸气压力最小值 IPAP 吸气压力 Last 最后一次 Leak 漏气率 Mode 模式 Name 姓名 None RERA 呼吸努力相关性觉醒 Resp. Event 呼吸时间 Inclination 侧卧 Therapy Pressure 治疗压力 BiPAP 双水平气道正压通气 Brand 品牌 Daily 日常 Email 电子邮件 Error 错误 First 第一次 Ramp Pressure 压力上升 L/min 升/分钟 Hours 小时 Leaks 漏气率 Model 型式 Phone 电话号码 Ready 就绪 W-Avg W-Avg Snore 鼾声 Start 开始 Usage 使用 Respiratory Disturbance Index 呼吸紊乱指数 cmH2O 厘米水柱 Pressure Support 压力支持 Hypopnea 低通气 ratio 比率 Tidal Volume 呼吸容量 Entire Day 整天 Heart rate in beats per minute 心脏每分钟的跳动次数 A large mask leak affecting machine performance. 大量漏气影响呼吸机性能. Pat. Trig. Breath 患者触发呼吸 Ramp Delay Period 斜坡升压期间 Pulse Change 脉搏变化 Sleep Stage 睡眠阶段 Minute Vent. 分钟通气率. SpO2 Drop 血氧饱和度降低 SensAwake feature will reduce pressure when waking is detected. 觉醒侦测功能会在侦测到醒来时降低呼吸机的压力. Upright angle in degrees 垂直 Higher Expiratory Pressure 更高的呼气压力 NRI=%1 LKI=%2 EPI=%3 未响应事件指数=%1 漏气指数=%2 呼气压力指数=%3 A vibratory snore 一次振动打鼾 Vibratory Snore 振动打鼾 Lower Inspiratory Pressure 更低的吸气压力 Resp. Rate 呼吸速率 Insp. Time 吸气时间 Exp. Time 呼气时间 Machine 机器 A sudden (user definable) drop in blood oxygen saturation 血氧饱和度突然降低 There are no graphs visible to print 无可打印图表 Target Vent. 目标通气率. Sleep position in degrees 睡眠体位角度 Ramp Time 斜坡升压时间 Unintentional Leaks 无意识漏气量 Would you like to show bookmarked areas in this report? 是否希望在报告中显示标记区域? Apnea Hypopnea Index 呼吸暂停低通气指数 Patient Triggered Breaths 患者出发的呼吸 Events 事件 (% %1 in events) (% %1 事件) No Data 无数据 Page %1 of %2 页码 %1 到 %2 Median 中值 PS Max 压力支持最大压力 PS Min 最小压力 Flow Limit. 气流限制. Detected mask leakage including natural Mask leakages 包含自然漏气在内的面罩漏气率 Plethy 足够的 SensAwake 觉醒 ST/ASV 自发/定时 ASV Median Leaks 漏气率中值 %1 Report %1报告 Pr. Relief 压力释放 Serial 串号 AHI %1 呼吸暂停低通气指数(AHI)%1 Weight 体重 Orientation 定位 Event Flags 呼吸事件 Zombie 呆瓜 Bookmarks 标记簇 An apnea where the airway is open 气道开放情况下的呼吸暂停 Flow Limitation 气流受限 RDI %1 呼吸紊乱指数(RDI) %1 Flow Rate 气流速率 Time taken to breathe out 呼气时间 An optical Photo-plethysomogram showing heart rhythm 光学探测显示心率 I:E Ratio 呼吸比率 Amount of air displaced per breath 每次呼吸气量 Pat. Trig. Breaths 患者触发呼吸率 Address 地址 Leak Rate 漏气率 Reporting from %1 to %2 正在生成由 %1 到 %2 的报告 Inspiratory Pressure 吸气压力 A pulse of pressure 'pinged' to detect a closed airway. 通过压力脉冲'砰'可以侦测到气道关闭. Non Responding Event 未响应事件 Median Leak Rate 漏气率中值 Rate of breaths per minute 每分钟呼吸的次数 Graph displaying snore volume 图形显示鼾声指数 Max EPAP 呼气压力最大值 Max IPAP 吸气压力最大值 Bedtime 睡眠时间 Pressure 压力 Average 平均 Target Minute Ventilation 目标分钟通气率 Amount of air displaced per minute 每分钟的换气量 Percentage of breaths triggered by patient 患者出发的呼吸百分比 Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine 没有使用机器的数据 Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Plethysomogram 体积描述术 Unclassified Apnea 未定义的呼吸暂停 Starting Ramp Pressure 开始斜坡升压 Intellipap event where you breathe out your mouth. Intellipap侦测到的嘴部呼吸事件. Flow Limit 气流受限 UAI=%1 未知暂停指数=%1 Pulse Rate 脉搏 Graph showing running AHI for the past hour 同行显示过去一个小时的AHI Graph showing running RDI for the past hour 图形显示过去一个小时的RDI Mask Time 面罩使用时间 Channel 通道 Max Leaks 最大漏气率 User Flag #1 用户标记#1 User Flag #2 用户标记#2 User Flag #3 用户标记#3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% 呼吸作用指数=%1 鼾声指数=%2 气流受限指数=%3 周期性呼吸/潮湿呼吸=%4%% Median rate of detected mask leakage 面罩漏气率的中间值 Mask Pressure 面罩压力 A vibratory snore as detcted by a System One machine 振动打鼾可被System One侦测到 Respiratory Event 呼吸事件 A type of respiratory event that won't respond to a pressure increase. 未导致压力上升的呼吸事件. Windows User Windows用户 Question 问题 Higher Inspiratory Pressure 更高的吸气压力 Bi-Level 双水平 Unknown 未知 Duration 时长 Sessions 会话 Settings 设置 Overview 总览 Entire Day's Flow Waveform 全天气流波形 Exiting 正在退出 Pressure Support Maximum 压力支持最大值 Graph showing severity of flow limitations 图形显示气流限制的严重程度 : %1 hours, %2 minutes, %3 seconds :%1 小时, %2 分钟, %3 秒 A partially obstructed airway 气道部分阻塞 Pressure Support Minimum 压力支持最小值 Large Leak 大量漏气 Wake-up Warning 警告 Min Pressure 最小压力 Total Leak Rate 总漏气率 Max Pressure 最大压力 MaskPressure 面罩压力 Total Leaks 总漏气量 Minute Ventilation 分钟通气率 Rate of detected mask leakage 面罩漏气率 Breathing flow rate waveform 呼吸流量波形 Lower Expiratory Pressure 更低的呼气压力 AI=%1 HI=%2 CAI=%3 暂停指数=%1 低通气指数=%2 中枢性暂停指数=%3 Time taken to breathe in 吸气时间 Maximum Therapy Pressure 最大治疗压力 Current Selection 当前选择 Blood-oxygen saturation percentage 血氧饱和百分比 Inspiratory Time 吸气时间 Respiratory Rate 呼吸频率 Printing %1 Report 正在打印%1报告 Expiratory Time 呼气时间 Expiratory Puff 嘴部呼吸 Maximum Leak 最大漏气率 Ratio between Inspiratory and Expiratory time 呼气和吸气时间的比率 Minimum Therapy Pressure 最小治疗压力 A sudden (user definable) change in heart rate 心率突变 Oximetry 血氧测定 Oximeter 血氧仪 The maximum rate of mask leakage 面罩的最大漏气率 Machine Database Changes 数据库更改 Expiratory Pressure 呼气压力 Tgt. Min. Vent 目标 分钟 通气 Pressure Pulse 压力脉冲 Humidifier 湿度 Patient ID 患者编号 An apnea caused by airway obstruction 气道阻塞状态下的呼吸暂停 Days: %1 天数:%1 Low Usage Days: %1 低使用天数:%1 (%1% compliant, defined as > %2 hours) (%1% 依从性, 定义为 > %2 小时) (Sess: %1) (会话:%1) Bedtime: %1 睡眠时间:%1 Waketime: %1 觉醒时间:%1 Minutes 分钟 Seconds Events/hr 事件/小时 Hz Hz Litres ml 毫升 Breaths/min 呼吸次数/分钟 Degrees Information 消息 Busy Please Note 请留言 Only Settings and Compliance Data Available Summary Data Only &Yes &是 &No &不 &Cancel &取消 &Destroy &删除 &Save &保存 No Data Available 无可用数据 Launching Windows Explorer failed 启动视窗浏览器失败 Could not find explorer.exe in path to launch Windows Explorer. 未找到视窗浏览器的可执行文件. <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> This means you will need to import this machine data again afterwards from your own backups or data card. 这意味着您需要自行由您的存档或者数据卡中导入数据. Important: 重要提示: The machine data folder needs to be removed manually. 数据文件夹需要手动移除. This folder currently resides at the following location: 本地文档位置: Rebuilding from %1 Backup 由%1备份重建中 Vibratory Snore (VS2) 呼吸 频率 (呼吸次数/分钟) Mask On Time 面具使用时间 Time started according to str.edf 计时参照str.edf Summary Only 仅有概要信息 Are you sure you want to use this folder? 确认选择这个文件夹吗? There is a lockfile already present for this profile '%1', claimed on '%2'. There is a lockfile already present for this profile '%1', claimed on '%2'. ? ? Severity (0-1) 严重程度 (0-1) Fixed Bi-Level 固定双水平 Auto Bi-Level (Fixed PS) 自动双水平 (last night) (昨晚) Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 Somnopose Somnopose Somnopose Software Somnopose Software Zeo Zeo Personal Sleep Coach 个人睡眠教练 Ramp Event 斜坡启动事件 Ramp 斜坡启动 Database Outdated Please Rebuild CPAP Data 数据库过期 请重建呼吸机数据 Series 系列 Yes 是的 No Auto Bi-Level (Variable PS) 全自动双水平(压力可变) Fixed %1 (%2) 固定 %1 (%2) Min %1 Max %2 (%3) 最小 %1 最大%2(%3) EPAP %1 IPAP %2 (%3) 呼气压力 %1 吸气压力%2 (%3) PS %1 over %2-%3 (%4) 压力 %1 超过 %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) 最小呼气压力%1 最大吸气压力%2 压力 %3-%4 (%5) EPAP %1-%2 IPAP %3-%4 (%5) 呼气压力 %1 吸气压力%2 (%3) {1-%2 ?} {3-%4 ?} {5)?} SmartFlex Mode SmartFlex模式 Intellipap pressure relief mode. Intellipa压力释放模式. Ramp Only 仅斜坡升压 Full Time 全部时间 SmartFlex Level SmartFlex 级别 Intellipap pressure relief level. Intellipap 压力释放水平. SmartFlex Settings SmartFlex设置 15mm 15mm 22mm 22mm Flex Mode Flex模式 PRS1 pressure relief mode. PRS1 压力释放模式. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex Rise Time 上升时间 Bi-Flex Bi-Flex Flex Level Flex Level PRS1 pressure relief setting. PRS1 压力释放设置. Humidifier Status 加湿器状态 PRS1 humidifier connected? PRS1 加湿器是否连接? Disconnected 断开 Connected 连接 Hose Diameter 管径 Diameter of primary CPAP hose 呼吸机主管内径 Auto On 自动打开 A few breaths automatically starts machine 自动打开机器在几次呼吸后 Auto Off 自动关闭 Machine automatically switches off 呼吸机自动关闭 Mask Alert 面罩报警 Whether or not machine allows Mask checking. 是否允许呼吸机进行面罩检查. Show AHI 显示AHI Timed Breath 短时间的呼吸 Machine Initiated Breath 呼吸触发机器开启 TB TB EPR EPR ResMed Exhale Pressure Relief 瑞思迈呼气压力释放 Patient??? 病患??? EPR Level 呼气压力释放水平 Exhale Pressure Relief Level 呼气压力释放水平 EPR: 呼气压力释放: Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Pressure Min 最小压力 Pressure Max 最大压力 Leak Flag 漏气标志 LF 漏气标志 CPAP Session contains summary data only 仅含有概要数据 PAP Mode 正压通气模式 PAP Device Mode 正压通气模式 ASV (Fixed EPAP) ASV模式 (固定呼气压力) ASV (Variable EPAP) ASV模式 (可变呼气压力) Are you sure you want to reset all your channel colors and settings to defaults? 确定将所有通道颜色恢复默认设置吗? Duration %1:%2:%3 时长 %1:%2:%3 AHI %1 AHI %1 Hide All Events 隐藏所有事件 Show All Events 显示所有事件 Unpin %1 Graph 解除锁定%1图表 Pin %1 Graph 锁定%1图表 Plots Disabled 禁用区块 (Summary Only) (摘要) Relief: %1 压力释放: %1 Hours: %1h, %2m, %3s 小时数:%1小时.%2分钟,%3秒 Machine Information 机器信息 Graphs Switched Off 关闭图表 Sessions Switched Off 关闭会话 Journal Data 日志 If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. 如果您过往的数据已经丢失,请手动将所有的 Journal_XXXXXXX 文件夹内的文件拷贝到这里. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Perfusion Index 灌注指数 A relative assessment of the pulse strength at the monitoring site 脉搏的强度的相关评估 Perf. Index % 灌注指数 % APAP (Variable) APAP(自动) Zero 0 Upper Threshold 增加 Lower Threshold 降低 Snapshot %1 快照 %1 (%2 min, %3 sec) (%2 分, %3 秒) (%3 sec) (%3 秒) Med. 中间值. Min: %1 最小:%1 Min: 最小: Max: 最大: Max: %1 最大:%1 %1 (%2 days): %1 (%2 天): %1 (%2 day): %1 (%2 天): % in %1 % in %1 Min %1 最小 %1 Hours: %1 小时:%1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 很少使用, %2 不使用, 超过 %3 天 (%4% 兼容.) 长度: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 会话: %1 / %2 / %3 长度: %4 / %5 / %6 最长: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 长度: %3 开始: %2 Mask On 面罩开启 Mask Off 面罩关闭 %1 Length: %3 Start: %2 %1 长度: %3 开始: %2 TTIA: 呼吸暂停总时间: TTIA: %1 呼吸暂停总时间: %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Machine Unsupported 不支持的机型 CPAP Mode CPAP模式 VPAPauto VPAP全自动 ASVAuto ASV全自动 Auto for Her Auto for Her Cheyne Stokes Respiration 潮式呼吸 CSR CSR Clear Airway 开放气道 Obstructive 阻塞性 Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. 呼吸努力指数与觉醒有关:呼吸限制会导致觉醒或者睡眠障碍. It is likely that doing this will cause data corruption, are you sure you want to do this? 此操作会损坏数据,是否继续? Don't forget to place your datacard back in your CPAP machine 请拔出内存卡,插入呼吸机 Are you sure you want to reset all your waveform channel colors and settings to defaults? 确定将所有的波形通道颜色重置为默认值吗? Default 默认 AVAPS AVAPS An abnormal period of Cheyne Stokes Respiration 潮式呼吸的不正常时期 Periodic Breathing 周期性呼吸 An abnormal period of Periodic Breathing 周期性呼吸的不正常时期 SmartStart 自启动 Machine auto starts by breathing 呼吸触发启动 Smart Start 自启动 Humid. Status 湿化器状态 Humidifier Enabled Status 湿化器已启用 Humid. Level 湿度 Humidity Level 湿度 Temperature 温度 ClimateLine Temperature 加热管路温度 Temp. Enable 温度启用 ClimateLine Temperature Enable 加热管路温度启用 Temperature Enable 温度测量启用 AB Filter 抗菌过滤棉 Antibacterial Filter 抗菌过滤棉 Pt. Access 患者通道 Climate Control 恒温控制 Manual 手动 Auto 自动 Mask 面罩 ResMed Mask Setting ResMed面罩设置 Pillows 鼻枕 Full Face 全脸 Nasal 鼻罩 Ramp Enable 斜坡升压启动 h 小时 m 分钟 s 秒s ms 毫秒 Height 身高 Physical Height 身高 Notes 备注 Bookmark Notes 标记备注 Body Mass Index 体重指数 How you feel (0 = like crap, 10 = unstoppable) 体感(0-无效,10=喜欢到停不下来) Bookmark Start 标记开始 Bookmark End 标记结束 Last Updated 最后更新 Journal Notes 日志备注 Journal 日志日记 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=醒 2=动眼睡眠 3=浅睡眠 4=深睡眠 Brain Wave 脑波 BrainWave 脑波 Awakenings 觉醒 Number of Awakenings 觉醒次数 Morning Feel 晨起感觉 How you felt in the morning 早上醒来的感觉 Time Awake 清醒时间 Time spent awake 清醒时长 Time In REM Sleep 动眼睡眠时长 Time spent in REM Sleep 动眼睡眠时长 Time in REM Sleep 动眼睡眠时长 Time In Light Sleep 浅睡眠时长 Time spent in light sleep 浅睡眠时长 Time in Light Sleep 浅睡眠时长 Time In Deep Sleep 深睡眠时长 Time spent in deep sleep 深睡眠时长 Time in Deep Sleep 深睡眠时长 Time to Sleep 睡眠时长 Time taken to get to sleep 入睡时长 Zeo ZQ ZEO 睡商 Zeo sleep quality measurement ZEO睡眠质量监测 ZEO ZQ ZEP睡商 Pop out Graph 弹出图表 Your machine doesn't record data to graph in Daily View Popout %1 Graph 弹出图表%1 m m cm cm Profile 配置文件 Getting Ready... 准备就绪... Scanning Files... 扫描文件... Importing Sessions... 导入会话... Finishing up... 整理中... Breathing Not Detected 呼吸未被检测到 A period during a session where the machine could not detect flow. 机器无法检测流量的会话期间。 BND BND Locating STR.edf File(s)... 正在查找str.edf文件... Cataloguing EDF Files... 正在给EDF文件编辑目录... Queueing Import Tasks... 正在排队导入任务... Finishing Up... 整理中... Loading %1 data for %2... 正在为%2加载%1数据... Scanning Files 正在扫描文件 Migrating Summary File Location 正在迁移摘要文件位置 Loading Summaries.xml.gz 加载摘要.xml.gz文件 Loading Summary Data 正在加载摘要数据 Please Wait... 请稍候... Loading profile "%1"... 正在加载配置文件"%1"... Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> 最新血氧测定数据:<a onclick='alert("daily=%2");'>%1</a> No oximetry data has been imported yet. 尚未导入血氧测定数据。 Software Engine 软件引擎 ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL 桌面OpenGL There is no data to graph 没有数据可供图表 OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR找到先前的日志文件夹,已被重命名: OSCAR will not touch this folder, and will create a new one instead. OSCAR不会更改此文件夹,将会创建一个新文件夹。 Please be careful when playing in OSCAR's profile folders :-P 请谨慎在OSCAR配置文件夹中操作:-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. 出于某种原因,Oscar在您的配置文件中找不到日志对象记录,但找到了多个日志数据文件夹。 OSCAR picked only the first one of these, and will use it in future: OSCAR将会使用其中的第一个: I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. OSCAR只能跟踪该机器的使用时间和基本的设置。 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR保留了设备数据卡的备份</b> OSCAR does not yet have any automatic card backups stored for this device. OSCAR尚未为此设备存储任何备份。 If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. 请单击“否”退出,并在再次启动OSCAR之前手动备份您的配置文件。 Are you ready to upgrade, so you can run the new version of OSCAR? 准备升级,是否运行新版本的OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. 抱歉,清除操作失败,此版本的OSCAR无法启动。 Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? 是否要打开自动备份,新版的OSCAR可以从这些版本重建? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR现在将启动导入向导,以便您可以重新安装%1数据。 OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Oscar现在将退出,然后(尝试)启动计算机文件管理器,以便手动备份配置文件: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. 使用文件管理器复制配置文件目录,然后重新启动oscar并完成升级过程。 A user definable event detected by OSCAR's flow waveform processor. 由OSCAR的流量波形处理器检测到的用户自定义事件。 As you did not select a data folder, OSCAR will exit. 由于没有选择数据文件夹,OSCAR将退出。 The folder you chose is not empty, nor does it already contain valid OSCAR data. 您选择的文件夹不是空的,也不包含有效的OSCAR数据。 OSCAR Reminder OSCAR提醒 You can only work with one instance of an individual OSCAR profile at a time. 一次只能处理单个OSCAR配置文件的一个实例。 If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. 如果正在使用云存储,请确保OSCAR已关闭,并且在继续操作之前已在另一台计算机上完成同步。 You must run the OSCAR Migration Tool 必须运行OSCAR迁移工具 Using , found SleepyHead - An apnea that couldn't be determined as Central or Obstructive. A restriction in breathing from normal, causing a flattening of the flow waveform. or CANCEL to skip migration. You cannot use this folder: Migrating files from to OSCAR will set up a folder for your data. We suggest you use this folder: Click Ok to accept this, or No if you want to use a different folder. Choose or create a new folder for OSCAR data Next time you run OSCAR, you will be asked again. App key: Operating system: Graphics Engine: Graphics Engine type: Machine Untested Data directory: Updating Statistics cache Usage Statistics 使用统计 d MMM yyyy [ %1 - %2 ] d MMM yyyy [ %1 - %2 ] EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting %1 Charts %1 of %2 Charts Loading summaries Built with Qt %1 on %2 Motion n/a Dreem Untested Data P-Flex P-Flex Humidification Mode PRS1 Humidification Mode Humid. Mode Fixed (Classic) Adaptive (System One) Heated Tube Tube Temperature PRS1 Heated Tube Temperature Tube Temp. PRS1 Humidifier Setting 12mm 12mm Your Viatom device generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Viatom Viatom Software OSCAR %1 needs to upgrade its database for %2 %3 %4 Movement Movement detector Version "%1" is invalid, cannot continue! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Please select a location for your zip other than the data card itself! Unable to create zip! Parsing STR.edf records... Mask Pressure (High frequency) A ResMed data item: Trigger Cycle Event Backing Up Files... Debugging channel #1 Test #1 Debugging channel #2 Test #2 EPAP %1 IPAP %2-%3 (%4) 呼气压力 %1 吸气压力%2 (%3) {1 ?} {2-%3 ?} {4)?} CPAP-Check AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS Flex Lock Whether Flex settings are available to you. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Mask Resistance Setting Mask Resist. Hose Diam. Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Type of ramp curve to use. Linear SmartRamp Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Fixed Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Breath BPM Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled Variable Breathing UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Passover Peak Flow Peak flow during a 2-minute interval Recompressing Session Files OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. (1 day ago) (%2 days ago) New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to create the OSCAR data folder at The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Essentials Plus Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. For internal use only Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Flex Unable to check for updates. Please try again later. Target Time PRS1 Humidifier Target Time Hum. Tgt Time 99.5% 90% {99.5%?} varies Backing up files... Reading data files... Snoring event. SN model %1 DreamStation 2 unknown model iVAPS Soft Standard 标准 BiPAP-T BiPAP-S BiPAP-S/T PAC SmartStop Machine auto stops by breathing Smart Stop Simple Advanced Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Humidity SleepStyle Apnea An apnea reportred by your CPAP machine. AI=%1 SensAwake level Expiratory Relief Expiratory Relief Level Response Patient View This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank 关于:空白 SessionBar No Sessions Present 没有会话 %1h %2m %1% %2m SleepStyleLoader Import Error 导入出错 This Machine Record cannot be imported in this profile. 无法在此配置文件中导入此设备的记录。 The Day records overlap with already existing content. 本日的数据已覆盖已存储的内容. Statistics Days 天数 Oximeter Statistics 血氧仪统计 CPAP Usage CPAP使用情况 Blood Oxygen Saturation 血氧饱和度 % of time in %1 % 在 %1 时间中 Last 30 Days 过去三十天 %1 Index %1 指数 %1 day of %2 Data on %3 %1 天在 %2 中的数据在 %3 Max %1 最大 %1 %1 Median %1 中值 Min %1 最小 %1 Most Recent 最近 Pressure Settings 压力设置 Pressure Statistics 压力统计 Last 6 Months 过去六个月 Average %1 平均 %1 No %1 data available. %1 数据可用. Last Use 最后一次 Pulse Rate 脉搏 First Use 首次 Last Week 上周 Last Year 去年 Details 详情 %1 days of %2 Data, between %3 and %4 %1 天的在 %2中的数据,在%3 和 %4 之间 Last Session 上一个会话 Machine Information 机器信息 CPAP Statistics CPAP统计 Leak Statistics 漏气统计 Average Hours per Night 平均每晚的小时数 % of time above %1 threshold % 的时间高于 %1 阈值 % of time below %1 threshold % 的时间低于 %1 阈值 Pressure Relief 压力释放 Therapy Efficacy 疗效 Name: %1, %2 名字: %1, %2 DOB: %1 生日:%1 Phone: %1 电话号码:%1 Email: %1 电子邮箱: %1 Address: 地址: Days Used: %1 天数:%1 Low Use Days: %1 低使用天数:%1 Compliance: %1% 依从: %1% Days AHI of 5 or greater: %1 AHI大于5的天数: %1 Best AHI 最低AHI Date: %1 AHI: %2 日期: %1 AHI: %2 Worst AHI 最高的AHI Best Flow Limitation 最好的流量限值 Date: %1 FL: %2 日期: %1 FL: %2 Worst Flow Limtation 最差的流量限值 No Flow Limitation on record 无流量限值记录 Worst Large Leaks 最大漏气量 Date: %1 Leak: %2% 日期: %1 Leak: %2% No Large Leaks on record 无大量漏气记录 Worst CSR 最差的潮式呼吸 Date: %1 CSR: %2% 日期: %1 CSR: %2% No CSR on record 无潮式呼吸记录 Worst PB 最差周期性呼吸 Date: %1 PB: %2% 日期: %1 PB: %2% No PB on record 无周期性呼吸数据 Want more information? 更多信息? OSCAR needs all summary data loaded to calculate best/worst data for individual days. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. 请在属性选单中选中预调取汇总信息选项. Best RX Setting 最佳治疗方案设定 Date: %1 - %2 Date: %1 - %2 Worst RX Setting 最差治疗方案设定 OSCAR is free open-source CPAP report software Oscar has no data to report :( Compliance (%1 hrs/day) Changes to Machine Settings No data found?!? AHI: %1 Total Hours: %1 This report was prepared on %1 by OSCAR %2 Welcome What would you like to do? 确定? CPAP Importer CPAP导入器 Oximetry Wizard 血氧测量向导 Daily View 每日视图 Overview 总览 Statistics 统计 First import can take a few minutes. 第一次导入将会花费数分钟. The last time you used your %1... 上一次您使用您的%1... last night 上一晚 %2 days ago %2 天以前 was %1 (on %2) 是 %1 (在 %2) %1 hours, %2 minutes and %3 seconds %1 小时, %2分钟 %3 秒 Your machine was on for %1. 计算机已启用 %1。 <font color = red>You only had the mask on for %1.</font> <font color = red>带呼吸面罩 %1.</font> under 低于 over 高于 reasonably close to 合理地接近于 equal to 等于 You had an AHI of %1, which is %2 your %3 day average of %4. Your machine was under %1-%2 %3 for %4% of the time. 压力低于 %1-%2 %3 ,持续时间%4% . Your average leaks were %1 %2, which is %3 your %4 day average of %5. 平均泄漏为%1 %2,即%3您的%5天的平均泄漏为%4。 No CPAP data has been imported yet. 未导入呼吸机数据. It would be a good idea to check File->Preferences first, 建议首选项为检查文件 as there are some options that affect import. 因为有些选项会影响导入. Note that some preferences are forced when a ResMed machine is detected 请注意,在检测到ResMed设备时会强制执行某些首选项 Welcome to the Open Source CPAP Analysis Reporter 欢迎使用开源CPAP分析报告 Your CPAP machine used a constant %1 %2 of air Your pressure was under %1 %2 for %3% of the time. 压力低于 %1 %2 ,持续时间%3%. Your machine used a constant %1-%2 %3 of air. Your EPAP pressure fixed at %1 %2. 呼气压力固定于 %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. 吸气压力低于 %1 %2 ,持续时间 %3%. Your EPAP pressure was under %1 %2 for %3% of the time. 呼气压力低于 %1 %2 ,持续时间 %3%. <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> 1 day ago gGraph %1 days %1天 gGraphView 100% zoom level 100% 缩放级别 Reset Graph Layout 重置图表结构 Plots 区块 CPAP Overlays CPAP 覆盖 Oximeter Overlays 血氧仪 覆盖 Dotted Lines 虚线 Resets all graphs to a uniform height and default order. 重置所有图标到统一的高度以及默认顺序. Y-Axis Y轴 Remove Clone 删除复制项 Clone %1 Graph 复制 %1 图表 Double click title to pin / unpin Click and drag to reorder graphs Restore X-axis zoom to 100% to view entire selected period. Restore X-axis zoom to 100% to view entire day's data. OSCAR-code-v1.3.1/Translations/Chinese.zh_TW.ts000066400000000000000000014233631417327530600211460ustar00rootroot00000000000000 AboutDialog Sorry, could not locate About file. 歹勢,找不到相關檔案。 Close 關閉 &About &關於 GPL License GPL授權 About OSCAR %1 Sorry, could not locate Credits file. 歹勢,找不到此志工名單。 OSCAR %1 Important: 重要提示: Credits 志工名單 Sorry, could not locate Release Notes. 歹勢,找不到版本說明。 As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. To see if the license text is available in your language, see %1. 授權說明%1。 Release Notes 版本說明 Show data folder 顯示數據資料夾 CMS50F37Loader Could not find the oximeter file: 歹勢,找不到血氧儀檔案: Could not open the oximeter file: 歹勢,無法開啟血氧儀檔案: CMS50Loader Could not get data transmission from oximeter. 無法傳輸血氧儀資料。 Please ensure you select 'upload' from the oximeter devices menu. 請確認已在血氧儀選單中選取'上传'操作. Could not find the oximeter file: 歹勢,找不到血氧儀檔案: Could not open the oximeter file: 歹勢,無法開啟血氧儀檔案: CheckUpdates Checking for newer OSCAR versions Daily B u Big End 結束 99.5% 99.5% Oximetry Sessions 血氧飽和度監測時段 Color 顏色 Flags 記號 Notes 附註 Small Start 開始 PAP Mode: %1 PAP 模式: %1 I'm feeling ... 我目前感覺... Journal 日記 Total time in apnea 睡眠呼吸中止總計時間 Position Sensor Sessions 位置感測器監控時段 Add Bookmark 加入書籤 Remove Bookmark 移除書籤 Pick a Colour 挑一顏色 Complain to your Equipment Provider! 請找出產品序號,即刻聯繫您的設備供應商! Session Information 療程資訊 Sessions all off! 所有療程結束! %1 event %1 重點事件 Go to the most recent day with data records 移至最近一天的數據資料 Machine Settings 機器處方設定值 Sorry, this machine only provides compliance data. 歹勢,此機器僅提供醫囑數據資料。 B.M.I. 身體質量指數 Sleep Stage Sessions 睡眠狀態療程 Oximeter Information 血氧飽和濃度器資訊 Events 重點事件 Graphs 圖表 CPAP Sessions PAP 療程 Medium Starts 開始 Weight 體重 Zombie 遲鈍 If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Bookmarks 書籤集 Session End Times 療程結束次數 enable 啟用 %1 events %1 重點事件 events 重點事件 BRICK :( 崩潰 Orz Event Breakdown 重點事件解析 UF1 UF2 Click to %1 this session. 點擊以 %1 這個療程. %1 Session #%2 %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. SpO2 Desaturations 血氧飽和度降低次數 (Mode and Pressure settings missing; yesterday's shown.) 10 of 10 Event Types This CPAP machine does NOT record detailed data "Nothing's here!" "這裡啥都沒有!" 10 of 10 Graphs Awesome 美賣,讚喔 Pulse Change events 脈搏變化重點事件 SpO2 Baseline Used 血氧飽和度採用基準 Zero hours?? 零小時?? Go to the previous day 移至前一天 Details 詳細說明 Time over leak redline 漏氣超標的計時 disable 停用 No data is available for this day. This bookmark is in a currently disabled area.. Bookmark at %1 把%1加入書籤 Statistics 統計值 Breakdown 解析 Unknown Session 不明療程 Sessions exist for this day but are switched off. 此日存有療程,但已被切換為關閉狀態。 Model %1 - %2 模式 %1 - %2 Duration 持續時間 View Size 檢視尺寸大小 Impossibly short session 療程太短無法採用 Show/hide available graphs. 顯示或隱藏可用的圖表. No %1 events are recorded this day 此日期無%1重點事件記錄 Show or hide the calender 顯示或隱藏日曆 Time outside of ramp 斜線升壓的除外時間 Unable to display Pie Chart on this system 無法在此系統上顯示圓形圖 Total ramp time 斜線升壓總時間 This day just contains summary data, only limited information is available. 此日期只包含摘要數據資料,而且僅有少量可用資訊。 Time at Pressure 壓力時間 Go to the next day 移至次日 Session Start Times 療程啟動次數 ExportCSV End 結束 Date 日期 End: 結束: Quick Range: 快速變化範圍: Daily 日常 Event 事件 Start 开始 Last Fortnight 最近兩週 Most Recent Day 最近一天 Count 計數 Filename: 檔案名稱: Select file to export to 選取檔案匯出到 Resolution: 分辨率: Cancel 取消 Dates: 日期: Custom 自訂 Export 匯出 Start: 开始: Data/Duration 資料/時長 CSV Files (*.csv) CSV檔案(*.ccsv) Last Month 上個月 Last 6 Months 最近六個月 Total Time 總時長 DateTime 日期時間 Session Count 療程計數 AHI 呼吸中止指數 Session 療程 Everything 所有 Last Week 上週 Last Year 去年 Export as CSV 匯出為CSV格式 Sessions_ 療程_ Details 詳細資料 Summary_ 概要_ Details_ 詳細資料_ Sessions 療程 FPIconLoader This Machine Record cannot be imported in this profile. 無法在此個人檔案中匯入此设备的记录。 Import Error 匯入出错 The Day records overlap with already existing content. 本日的資料已覆蓋已儲存的内容. Help No Index 索引 clear 清除 HelpEngine did not set up correctly 帮助引擎未正确設定 Help files do not appear to be present. 帮助檔案不存在。 HelpEngine could not register documentation correctly. 帮助引擎無法正确注册檔案。 Help Files are not yet available for %1 and will display in %2. 帮助檔案尚不可用於%1並將顯示在%2。 Hide this message 隐藏此資訊 Please wait a bit.. Indexing still in progress 無可用檔案 Search 搜索 Contents 目录 No documentation available %1 result(s) for "%2" %1 结果 "%2" Search Topic: 搜索主题: MD300W1Loader Could not find the oximeter file: 歹勢,找不到血氧儀檔案: Could not open the oximeter file: 歹勢,無法開啟血氧儀檔案: MainWindow Exit 退出 Help 帮助 Please insert your CPAP data card... 請插入CPAP資料卡... Daily Calendar 日历 &Data &資料 &File &檔案 &Help &帮助 &View &查看 E&xit &退出 Daily 日常 Loading profile "%1" 載入個人檔案"%1" Import &ZEO Data 匯入&ZEO資料 MSeries Import complete M系列PAP資料匯入完成 There was an error saving screenshot to file "%1" 錯誤資訊截圖儲存在 "%1"檔案中 %1 (Profile: %2) Choose a folder 選取一個資料夾 No profile has been selected for Import. A %1 file structure for a %2 was located at: %1檔案配置的%2位置在: Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Find your CPAP data card Importing Data 正在匯入資料 Online Users &Guide 在線&指南 View &Welcome 查看&欢迎 Show Right Sidebar Show Statistics view Import &Dreem Data Import &Viatom/Wellue Data Show &Line Cursor Show Daily Left Sidebar Show Daily Calendar Show Performance Information 顯示性能資訊 There was a problem opening MSeries block File: 開啟M系列PAP檔案出错: Current Days 当前天数 &About &关於 View &Daily 查看&日常 View &Overview 查看&概述 Access to Preferences has been blocked until recalculation completes. 重新計算完成之前,已阻止存取首選项。 Import RemStar &MSeries Data 匯入瑞斯迈&M系列PAP資料 For some reason, OSCAR does not have any backups for the following machine: 由於某種原因,OSCAR没有以下设备的任何備份: Daily Sidebar 每日侧边栏 Note as a precaution, the backup folder will be left in place. 請注意:請將備份資料夾保留在合适的位置。 A file permission error casued the purge process to fail; you will have to delete the following folder manually: 檔案权限錯誤導致清除过程失败; 您必須手動删除以下資料夾: Change &User 變更&使用者 %1's Journal %1'的日誌 Import Problem 匯入錯誤 <b>Please be aware you can not undo this operation!</b> <b>請注意,您無法撤消此操作!</b> View S&tatistics 查看&統計值 Monthly 每月 Change &Language 變更&语言 &About OSCAR &关於OSCAR Import 匯入 Because there are no internal backups to rebuild from, you will have to restore from your own. 由於没有可用的内部備份可供重建使用,請自行從備份中还原。 Would you like to import from your own backups now? (you will have no data visible for this machine until you do) 您希望立即從備份匯入吗?(完成匯入,才能有資料顯示) Please wait, importing from backup folder(s)... 請稍等,正在由備份資料夾匯入... Check for updates not implemented Choose where to save screenshot Image files (*.png) The User's Guide will open in your default browser Please note, that this could result in loss of data if OSCAR's backups have been disabled. 請注意,如果停用了OSCAR's備份,這可能會導致資料丢失。 OSCAR does not have any backups for this machine! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> The Glossary will open in your default browser There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete Are you sure you want to delete oximetry data for %1 確定清除%1内的血氧儀資料吗 OSCAR Information O&ximetry Wizard &血氧儀安裝小幫手 Bookmarks 標記簇 Right &Sidebar 右&侧边栏 Rebuild CPAP Data 重建資料 The FAQ is not yet implemented FAQ尚未施行 XML Files (*.xml) Export review is not yet implemented 匯出检查不可用 Are you sure you want to rebuild all CPAP data for the following machine: 確定要重建以下设备的所有CPAP資料吗: Report an Issue 報告问题 Date Range 日期範圍 View Statistics 查看統計值資訊 CPAP Data Located CPAP資料已定位 Access to Import has been blocked while recalculations are in progress. 匯入資料存取被阻止,重新計算進行中。 Sleep Disorder Terms &Glossary 睡眠障碍术语&术语表 Are you really sure you want to do this? 確定進行此操作? Select the day with valid oximetry data in daily view first. 請先在每日视图中選取有效血氧儀資料的日期. Purge Oximetry Data 清除血氧测定資料 Records 記錄 Use &AntiAliasing 使用&圖形保真 Would you like to import from this location? 從此匯入吗? Report Mode 報告模式 &Profiles &個人檔案 Profiles 個人檔案 Create zip of CPAP data card Create zip of OSCAR diagnostic logs Create zip of all OSCAR data CSV Export Wizard CSV匯出小幫手 &Automatic Oximetry Cleanup &血氧儀資料自動清理 Import is already running in the background. 已在后台执行匯入操作. Specify 指定 Standard 標準 No help is available. 没有可用的帮助。 Statistics 統計值 Up to date 最新 Please open a profile first. 請先開啟個人檔案. &Statistics &統計值 Backup &Journal 備份&日誌 Imported %1 CPAP session(s) from %2 已匯入 %1 PAP 療程,來源機器為 %2 Would you like to zip this card? Choose where to save zip ZIP files (*.zip) Creating zip... Calculating size... Reporting issues is not yet implemented 報告问题不可用 Purge &Current Selected Day 清除&当前所选日期的資料 Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. 如果已经為所有CPAP資料進行了<i>備份 <b>,</b>仍然可以完成此操作</i>,但必須手動從備份中还原。 &Advanced &進階 Print &Report 列印&報告 Couldn't find any valid Machine Data at %1 此处没有有效的PAP資料 %1 Export for Review 匯出查看 Take &Screenshot &截圖 Overview 總覽 &Reset Graphs Troubleshooting Purge ALL Machine Data &Import CPAP Card Data Show Daily view Show Overview view &Maximize Toggle Maximize window Show Debug Pane 顯示调试面板 Reset Graph &Heights Reset sizes of graphs &Edit Profile &編輯個人檔案 &Sleep Stage &Position &All except Notes All including &Notes Import Reminder 匯入提示 Help Browser 帮助浏览器 If you can read this, the restart command didn't work. You will have to do it yourself manually. 重启命令不起作用,需要手動重启。 Exp&ort Data 导&出資料 Welcome 欢迎使用 Import &Somnopose Data 匯入&睡眠姿势資料 Screenshot saved to file "%1" 截圖儲存於 "%1" &Preferences &参数設定 You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> 您將要<font size=+2>删除以下设备的</font>OSCAR資料库:</p> Are you <b>absolutely sure</b> you want to proceed? 您 <b>確定</b> 要繼續吗? Import Success 匯入成功 Choose where to save journal 選取儲存日誌的位置 &Frequently Asked Questions &常见问题 Oximetry 血氧测定 System Information Show &Pie Chart Show Pie Chart on Daily page Standard graph order, good for CPAP, APAP, Bi-Level Advanced Advanced graph order, good for ASV, AVAPS Show Personal Data Check For &Updates Purge Current Selected Day &CPAP &Oximetry &血氧测量 A %1 file structure was located at: %1 檔案配置的位置在: Change &Data Folder 變更&資料資料夾 Navigation 导航 Already up to date with CPAP data at %1 已更新CPAP資料位於 %1 MinMaxWidget Scaling Mode 缩放模式 The Maximum Y-Axis value.. Must be greater than Minimum to work. Y轴最大值,必須大於最小值方可正常工作. This button resets the Min and Max to match the Auto-Fit 此按钮將按自适应模式重新設定最大最小值 The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Y轴缩放模式:“自适应”意味着自動适应大小,“預設”意味着使用制造商的出厂值,“覆蓋”意味着自訂. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Y轴最小值,此值可為负。 Defaults 預設 Auto-Fit 自适应 Override 覆蓋 NewProfile ASV 适应性支持同期模式 APAP 全自動正压通氣 CPAP 持续氣道正压通氣 Male Very weak password protection and not recommended if security is required. D.O.B. Metric English &Back &上一步 &Next &下一步 TimeZone 時区 Email 电子邮件 Phone 电话 Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. 所有生成的報告仅限個人使用,不能用於医疗诊断。 OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team &Close this window &關閉窗口 Edit User Profile 編輯使用者資訊 The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. 该應用程式作者对<u>任何人</u> 使用或误用本應用程式不承担任何责任。 Please provide a username for this profile 請输入使用者名 CPAP Treatment Information PAP治療資訊 Password Protect Profile 密碼保护 OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR 仅仅作為一個資料读取顯示應用程式,不能替代医生提供有效的医疗指导。 Welcome to the Open Source CPAP Analysis Reporter 欢迎使用开源CPAP分析報告 OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR已根据<a href='qrc:/COPYING'>GNU公共许可证免费发布v3版本</a>,没有任何担保,也没有任何针对任何目的的适用性声明。 Accuracy of any data displayed is not and can not be guaranteed. 不保证任何顯示資料的准确性。 Female Gender 性别 Height 身高 Contact Information 联系方式 Locale Settings 归属地設定 CPAP Mode CPAP模式 Select Country 選取国家 PLEASE READ CAREFULLY 請认真阅读 Untreated AHI 未治療時的AHI Address 地址 I agree to all the conditions above. 同意以上条款。 DST Zone DST時区 about:blank 关於:空白 RX Pressure 释放壓力 Password 密碼 Use of this software is entirely at your own risk. 后果自负。 Passwords don't match 密碼不匹配 First Name 名字 Last Name 姓氏 Country 国家 &Cancel &取消 &Finish &完成 Bi-Level 双水平 Profile Changes 個人檔案變更 Personal Information (for reports) 個人資訊 User Name 使用者名 <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>可以留空或者跳过這一步,但提供出生日期和性别可以提高計算的准确性。</p></body></html> This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. 這款應用程式用於协助读取用於治療睡眠障碍的各种CPAP的資料. User Information 使用者資訊 ...twice... ...两次... Doctors Name 医生姓名 It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. 可以跳过這一步,但提供大概年龄資料可以提高計算的准确性。 Doctors / Clinic Information 医生/诊所資訊 Practice Name 患者姓名 Date Diagnosed 诊断日期 Accept and save this information? 接收並保存此資訊? Patient ID 患者编号 Overview End: 結束: Usage 使用數據 Respiratory Disturbance Index 呼吸 紊乱 指數 10 of 10 Charts Show all graphs 顯示所有圖表 Reset view to selected date range 將视图重新設定為所选日期範圍 Total Time in Apnea 呼吸中止總時間 Drop down to see list of graphs to switch on/off. 下拉以查看要開啟/關閉的圖表列表。 Usage (hours) 使用 (小時) Last Three Months 前三個月 Total Time in Apnea (Minutes) 呼吸中止總時間 (分鐘) Custom 自訂 How you felt (0-10) 感觉如何 (0-10) Graphs 圖表 Range: 範圍: Start: 开始: Last Month 上個月 Apnea Hypopnea Index 中止 低通氣 指數 Last 6 Months 前六個月 Body Mass Index 身体 重量 指數 Session Times 療程次數 Last Two Weeks 前兩週 Everything 所有 Last Week 上週 Last Year 去年 Snapshot Toggle Graph Visibility 切换视图 Hide all graphs 隐藏所有圖表 Last Two Months 前两個月 OximeterImport <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>請激活血氧儀标识符,以区分多個血氧儀</p></body></html> Live Oximetry import has been stopped 实時血氧测量匯入已停止 Press Start to commence recording 开始记录 Close 關閉 No CPAP data available on %1 在%1中没有可用的CPAP資料 It also can read from ChoiceMMed MD300W1 oximeter .dat files. 还可以读取ChoiceMMed MD300W1血氧儀的 .dat檔案. Please wait until oximeter upload process completes. Do not unplug your oximeter. 請等待血氧儀上传資料結束,期間不要拔出血氧儀. Finger not detected 没有檢測到手指 You need to tell your oximeter to begin sending data to the computer. 請在血氧儀操作开始上传資料到电脑. No Oximetry module could parse the given file: 血氧儀無法解析所选檔案: Renaming this oximeter from '%1' to '%2' 正在為血氧儀從 '%1' 改名到 '%2' CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>开启這個功能则允许由資料資料夾匯入入SpO2Review這样的脈搏血氧儀记录的读数.</p></body></html> Oximeter import completed.. 血氧儀資料匯入完成.. &Retry &再试一次 &Start &开始 You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. 你可能注意到,其他的公司,比如Pulox, Pulox PO-200, PO-300, PO-400.也可以使用. %1 session(s) on %2, starting at %3 %1 療程 %2, 開始時間為 %3 I need to set the time manually, because my oximeter doesn't have an internal clock. 血氧儀没有内置時鐘,需要手動設定。 You can manually adjust the time here if required: 如果有需要,可以在此手動调整時間: OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR能够在CPAP療程資料的同時跟踪血氧测定資料,從而对CPAP治療的有效性提供有价值的见解。它也將與脈搏血氧儀独立工作,允许儲存,跟踪和审查记录資料。 Oximeter Session %1 血氧儀療程 %1 Couldn't access oximeter 無法與血氧儀连通 Please choose which one you want to import into OSCAR 請選取哪個要匯入OSCAR Please connect your oximeter device 請连接血氧儀 Important Notes: 重要提示: Starting up... 开始... Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Contec CMS50D+没有内部時鐘,所以不能够记录开始時間。如果PAP資料與其無法同步,請在匯入完成后手動输入开始時間. HH:mm:ssap 小時:分鐘:秒 Import directly from a recording on a device 直接由磁盘匯入 Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. 血氧儀名稱不同...如果您仅有一個血氧儀而且與不用的使用者公用,請將其名稱统一. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. 即使对於含有内部時鐘的血氧儀,仍然建議养成血氧儀與PAP同時开启记录的习惯,因為PAP的内部時鐘會存在漂移现象,而且不易复位. Please connect your oximeter device, turn it on, and enter the menu &Information Page &資訊页 I want to use the time reported by my oximeter's built in clock. 使用血氧儀的時間作為系统時鐘. Live Oximetry Stopped 实時血氧测量已停止 Waiting for %1 to start 等待 %1 开始 OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR目前可以兼容Contec CMS50D+、CMS50E、CMS50F和CMS50I系列血氧儀。<br/>(請注意:直接從蓝牙模式匯入是不可行的 <span style=" font-weight:600;"></span> ) <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">給 PAP 使用者的提示: </span><span style=" color:#fb0000;">請務必先行匯入呼吸器資料<br/></span>否則血氧儀資料將無法與呼吸器資料在時間軸上同步。<br/>請同時啟動兩台機器,以確保資料的同步完整。</p></body></html> Select a valid oximetry data file 選取一個可用的血氧儀資料檔案 %1 device is uploading data... %1设备正在上传資料... &End Recording &停止记录 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. CMS50E/F使用者,在直接匯入時,請不要在设备上選取上传,直到OSCAR提示您為止。 &Choose Session &選取療程 Nothing to import 没有可匯入的資料 Select upload option on %1 在%1選取上传選項 Waiting for the device to start the upload process... 正在等待设备开始上传資料... Could not detect any connected oximeter devices. 没有连接血氧儀设备. Oximeter Import Wizard 血氧儀匯入小幫手 <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> Day recording (normally would have) started &Save and Finish &儲存並結束 Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. 脉動血氧儀是一款用於测量血样飽和度的医疗设备,在呼吸中止以及低通氣事件发生時,血氧飽和度大幅降低會引起一系列的健康问题,需要引起重视。 For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. 為了使OSCAR能够直接從血氧儀设备上定位和读取,需要确保在計算机上安装了正确的设备驱動程序(如USB转串行UART)。有关更多資訊%1,請點擊此%2. Start Time 开始時間 <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR需要一個开始時間来知道將血氧儀療程保存到哪里。</p><p>選取以下選項之一:</p></body></html> Pulse Rate 脈搏 <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>注意:同步CPAP療程的起始時間往往更加准确.</p></body></html> Set device date/time 設定日期/時間 Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) 血氧儀檔案 (*.spo *.spor *.spo2 *.SpO2 *.dat) Import Completed. When did the recording start? 匯入完成.何時开始记录? Import from a datafile saved by another program, like SpO2Review 匯入其他程序創建的資料檔案,例如SpO2Review所創建的檔案 Multiple Sessions Detected 檢測到多重療程 I started this oximeter recording at (or near) the same time as a session on my CPAP machine. 开启血氧儀记录的時間和开启CPAP的時間一致(或相近). Record attached to computer overnight (provides plethysomogram) 整晚连入电脑记录(提供体描仪) Erase session after successful upload 上传成功后删除療程 Live Oximetry Mode 实時血氧测量模式 &Cancel &取消 Set device identifier 設定设备标识符 Details 詳細資料 <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>如果啟用,OSCAR將使用您的計算机当前時間自動重新設定CMS50的内部時鐘。</p></body></html> Oximeter not detected 未檢測到血氧儀 Please remember: 請谨记: Where would you like to import from? 從何处匯入資料? If you can read this, you likely have your oximeter type set wrong in preferences. 如果您看到此处提示,請重新設定血氧儀類型. I want to use the time my computer recorded for this live oximetry session. 希望使用电脑的時間作為实時血氧療程的時間. Scanning for compatible oximeters 正在扫描所兼容的血氧儀 Please connect your oximeter, enter it's menu and select upload to commence data transfer... 請连接血氧儀,點擊選單選取資料上传... Choose CPAP session to sync to: 選取CPAP療程同步於: Duration 時長 Welcome to the Oximeter Import Wizard 欢迎使用血氧儀資料匯入小幫手 <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>如果你不介意整晚连入电脑,可以生成容积图,可以直观的展现心率,顯示在常规的血氧读数顶端.</p></body></html> If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! 如果您嘗試同步血氧测定和CPAP資料,請确保在繼續之前先匯入您的CPAP療程! "%1", session %2 "%1", %2療程 Show Live Graphs 顯示实時圖表 Live Import Stopped 实時匯入已停止 Oximeter Starting time 血氧儀开启時間 Skip this page next time. 下次跳过此页面。 If you can still read this after a few seconds, cancel and try again 如果在几秒鐘后仍然可以阅读此内容,請取消並重试 &Sync and Save &同步並儲存 Your oximeter did not have any valid sessions. 血氧儀療程無效. Something went wrong getting session data 获取療程資料時出错 Connecting to %1 Oximeter 正在與%1血氧儀连接 Recording... 正在儲存... Oximetry Date 日期 Pulse 脈搏 &Open .spo/R File &開啟 SPO/R 檔案 R&eset 重&置 Serial &Import 序列号&匯入 Serial Port 产口 d/MM/yy h:mm:ss AP 日/月/年 小時:分鐘:秒 &Start Live &开始 &Rescan Ports &扫描端口 PreferencesDialog &Ok &好的 Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. 不建議關閉備份,因為如果发现錯誤,OSCAR需要這些備份来重建資料库。 Graph Height 圖表高度 Flag 標記 Font 字体 Name 姓名 Size 大小 Span 範圍 General Settings 通用設定 <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>在使用双向触摸板放大時,滚動顯示更容易</p><p>50ms是推荐值.</p></body></html> Color 颜色 Daily 日常 Event Duration 事件区間 Hours 小時 Label 标签 Lower 更低 Never 從不 Oximetry Settings 血氧飽和度設定 Pulse 脈搏 Graphics Engine (Requires Restart) 圖形引擎 Upper 更高 days. 天. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">療程將會比這個稍短並且不會顯示<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform 在此可以為%1波形設定<b>更高的</b> 閥值来進行某些計算 After Import 匯入后 Ignore Short Sessions 忽略短時療程 Sleep Stage Waveforms 睡眠階段波形 Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. 氣流限值的中值百分比 20%的氣流限值有利於檢測呼吸中止。 Sessions starting before this time will go to the previous calendar day. 在此之前开始一段療程將會计入上一天. Session Storage Options 療程儲存選項 Show flags for machine detected events that haven't been identified yet. 顯示已標記但仍未被识别的事件. Graph Titles 圖表标题 Zero Reset 归零 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">自訂標記是一個檢測被機器忽略的事件实验方法。它们<span style=" text-decoration: underline;">不</span>包含於 AHI.</p></body></html> A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? 应用這些變更需要資料重新/解压缩过程。此操作可能需要几分鐘才能完成。 确实要進行這些變更吗? Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. 激活/停用(实验性)突出事件標記。 允许檢測边缘事件以及设备遗漏事件 這個選項必須在匯入前激活,否则需要清除缓存。 Flow Restriction 氣流限制 Enable Unknown Events Channels 啟用位置事件通道 Minimum duration of drop in oxygen saturation 血氧飽和下降的最小区間 Overview Linecharts 線形图概览 Whether to allow changing yAxis scales by double clicking on yAxis labels 是否允许以双击Y轴来進行Y轴的缩放 Always Minor 保持小 No CPAP machines detected Will you be using a ResMed brand machine? Unknown Events 未知事件 %1 %2 Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. 像素映射缓存是圖形加速技术,或许會導致在您的操作系统上的字体顯示异常. Reset &Defaults 恢复&預設設定 Bypass the login screen and load the most recent User Profile 跳过使用者登录界面,登录常用使用者 Data Reindex Required 重建資料索引 <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>請注意:</b>OSCAR的進階療程分割功能由於其設定和摘要資料的儲存方式的限制而無法用於ResMed设备,因此它们已针对该個人檔案被停用。</p><p>在ResMed设备上,日期將在中午分开,和在ResMed的商业應用程式的設定相同。</p> Scroll Dampening 滚動抑制 Are you sure you want to disable these backups? 确实要停用這些備份吗? Flag leaks over threshold 漏氣超閥值标志 hours 小時 Double click to change the descriptive name this channel. 双击變更這個通道的描述。 Sessions older than this date will not be imported 將不會匯入早於此日期的療程 Standard Bars 標準导航条 99% Percentile 99%百分位数 Memory and Startup Options 儲存與啟動選項 <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>在匯入期間减少任何不重要的确认对话框。</p></body></html> Small chunks of oximetry data under this amount will be discarded. 少量的血氧测定資料將被丢弃。 Oximeter Waveforms 血氧儀波形 User definable threshold considered large leak 使用者自訂大量漏氣数值 Reset the counter to zero at beginning of each (time) window. 在每個窗口開啟時將计数器归零。 Compliance defined as 符合性定义為 <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>通过預先載入所有摘要資料,可以使啟動OSCAR的速度稍慢一些,這样可以加快浏览概述和稍后的其他一些計算。</p><p>如果有大量資料,建議關閉此项<span style=" font-style:italic;">everything</span> 總而言之,仍然必須載入所有摘要資料。</p><p>注意:该設定不會影响波形和事件資料,根据需要進行載入。</p></body></html> Here you can change the type of flag shown for this event 在此可以變更事件顯示的標記類型 Top Markers 置顶标志 One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? 您所做的一個或多個變更將要求重新啟動此应用程序,以便這些變更生效。 確定進行此操作吗? minutes 分鐘 Minutes 分鐘 Create SD Card Backups during Import (Turn this off at your own peril!) 在匯入过程中創建SD卡備份(請自行關閉此功能!) Graph Settings 圖形設定 This is the short-form label to indicate this channel on screen. 這是將在屏幕所顯示的此通道的简短描述标签. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. 以下選項會影响OSCAR使用的磁盘空間量,並影响匯入的時間。 CPAP Events CPAP 事件 Bold 突出顯示 <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>載入個人檔案時要開啟哪個選項卡。(注意:如果OSCAR設定為在啟動時不開啟個人檔案,它將預設為個人檔案)</p></body></html> Minimum duration of pulse change event. 脈搏變更事件的最小区間。 Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. 圖形保真技术可以使得圖表顯示更加圆润 当這一功能啟用時,特定的图块會突出顯示 在列印報告中也會体现出来 可以進行嘗試。 Sleep Stage Events 睡眠階段事件 Events 事件 <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">設定前請注意...</span>關閉這一選項的后果就是影响彙總報告的准确性,因為某些計算只在某一天的資料保存在一起的時候才能正常工作 . </p><p><span style=" font-weight:600;">瑞思迈使用者:</span> 正午12点之前属於前一天,這对你我来说感觉很自然,但不代表瑞思迈也這么认為。STF.edf功能很弱,並不适合来实现這一功能。.</p><p>這一選項存在的意义在於安抚那些不在意看到什么,只是想看到 &quot;固定的資料&quot;不管成本,但是這始终是有代价的.如果你每天都记录資料,並且每周匯入电脑一次,不會经常遇到這個报错 .</p></body></html> Median is recommended for ResMed users. 建議瑞斯迈使用者選取中值。 Oximeter Events 血氧儀事件 Italic 意大利 Enable Multithreading 啟用多線程 This may not be a good idea 不正确的应用 Weighted Average 平均体重 Median 中間值 Flag rapid changes in oximetry stats 血氧儀統計值資料中標記快速變更 Sudden change in Pulse Rate of at least this amount 脈搏突然變更的最小值 Search 查询 Resync Machine Detected Events (Experimental) 重新同步PAP檢測到的事件(试验性功能) Time Weighted average of Indice 時間加权平均值指數 Middle Calculations 中值計算 Skip over Empty Days 跳过無資料的日期 Allow duplicates near machine events. 允许多重记录趋近機器事件資料。 The visual method of displaying waveform overlay flags. 將视窗顯示的波形的標記進行叠加。 Upper Percentile 增大 Restart Required 重启請求 Whether to show the leak redline in the leak graph 是否在漏氣圖表中顯示漏氣限值红線 If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. 如果您需要再次重新匯入此資料(無论是在OSCAR还是ResScan中),此資料將不會再返回。 True Maximum 真极大值 Minor Flag 次要標記 Data Processing Required 需要資料处理 For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. 為了保持一致,ResMed的使用者需要設定95%, 它將作為唯一值出现在彙總界面内。 On Opening 开启状态 Pre-Load all summary data at startup 啟動時預載入所有彙總資料 Show Remove Card reminder notification on OSCAR shutdown OSCAR關閉時顯示删除卡提醒通知 No change 無變更 Graph Text 圖表文字 Double click to change the default color for this channel plot/flag/data. 双击變更這個區塊/標記/資料的預設颜色. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">由於總结设计限制,ResMed機器不支持變更這些設定。</p></body></html> AHI/Hour Graph Time Window AHI/小時 圖形時間窗 Changing SD Backup compression options doesn't automatically recompress backup data. Import without asking for confirmation 無需确认直接匯入 <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered &CPAP Your masks vent rate at 20 cmH2O pressure Your masks vent rate at 4 cmH2O pressure 4 cmH2O 20 cmH2O s 秒s l/min <html><head/><body><p>Cumulative Indices</p></body></html> This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. AHI Apnea Hypopnea Index 呼吸中止指數 RDI Respiratory Disturbance Index 呼吸紊乱指數 bpm Discard segments under 删除偏低的資料 Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. 允许使用多核CPU以提高性能 提高匯入性能。 Always save screenshots in the OSCAR Data folder Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Line Chart 線形图 dd MMMM yyyy 天天 月月月月 年年年年 <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>真极大值是資料設定的最大值.</p><p>滤除百分之九十九的异常值.</p></body></html> Profile 個人檔案 Flag Type 標記類型 Calculate Unintentional Leaks When Not Present 計算非意識漏氣量 Automatically load last used profile on start-up 在啟動時自動載入上次使用的個人檔案 How long you want the tooltips to stay visible. 設定工具提示可见時間长度。 Double click to change the descriptive name the '%1' channel. 双击變更 '%1通道的描述資訊. Multiple sessions closer together than this value will be kept on the same day. 缩小多個療程間距可以使其顯示在同一天. Are you really sure you want to do this? 確定進行此操作? Duration of airflow restriction 氣流限制的持续時間 Bar Tops 任務条置顶 This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. 這一計算需要PAP记录的總漏氣量) 非意識漏氣量的計算是線性的,因為没有面罩排氣曲線可参考. 如果你佩戴不同的面罩,請選取平均值,值应足够接近. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. 這使得OSCAR的資料占用了大约一半的空間。 但它使匯入和日期變化需要更长的時間.. 建議使用带有小型固态硬盘的計算。 Session Splitting Settings 療程拆分設定 <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>注意: 不能够進行時区自動矫正,請确保您操作系统時間以及時区設定正确.</p></body></html> Other Visual Settings 其他顯示設定 Day Split Time 時段 CPAP Waveforms CPAP波形 Compress Session Data (makes OSCAR data smaller, but day changing slower.) 压缩療程資料(使OSCAR資料量变小,但日期變化较慢。) Big Text 大字体 <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>此项功能已被取消,但會在后续版本内加入. </p></body></html> Note: A linear calculation method is used. Changing these values requires a recalculation. 注意:預設选用線性計算法。如果變更資料需重新計算. ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9设备會定期從SD卡内删除7天和30天以内的資料(取决於分辨率)。 Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. 注意使用時間低於4個小時的日期。 Do not import sessions older than: 請不要匯入早於如下日期的療程: Daily view navigation buttons will skip over days without data records 點擊日常查看导航按钮將跳过没有資料记录的日期 Flag Pulse Rate Above 心率標記高 Flag Pulse Rate Below 心率標記低 Seconds Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. 為AHI/小時圖表的每一個点调节資料量 預設值到60分鐘,建議使用這一預設值。 Show in Event Breakdown Piechart 在事件分类饼图中顯示 Other oximetry options 其他血氧儀選項 Switch Tabs 切换标签 This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) 保留ResMed设备SD卡資料的備份, ResMed S9系列设备删除超过7天的高分辨率資料, 以及超过30天的圖表資料。 如果您需要重新安装,OSCAR可以保留此資料的副本。 (强烈推荐,除非你的磁盘空間不足或者不关心圖形資料) &Cancel &取消 Don't Split Summary Days (Warning: read the tooltip!) 不可分割(警告:請阅读工工具提示資訊) Last Checked For Updates: 上次的更新: Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. 压缩Rresmed(EDF)備份以节省磁盘空間。 備份的EDF檔案以.gz格式儲存, 這在Mac和Mac上很常见 Linux平台.. OSCAR可以本地從此压缩備份目录匯入.. 要將其與ResScan一起使用,首先需要解压缩.gz檔案。 Details 詳細資料 Use Anti-Aliasing 使用圖形保真技术顯示 Animations && Fancy Stuff 動画 && 爱好 &Import &匯入 Statistics 統計值 <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>這一設定將波形以及事件資料保存在内存中以便於提升再次存取的速度.</p><p>這不是一项必須開啟的設定,因為操作系统會缓存載入过的資料.</p><p>建議保持這一設定呈關閉状态,除非内存非常大.</p></body></html> Changes to the following settings needs a restart, but not a recalc. 變更如下設定需要重启,但不需要重新估算。 &Appearance &外观 The pixel thickness of line plots 線条图的像素厚度 Whether this flag has a dedicated overview chart. 此标志是否有专用的概览圖表. This is a description of what this channel does. 此顯示的是這個通道的作用. Combine Close Sessions 關閉所有療程 Custom CPAP User Event Flagging 自訂PAP使用者事件 <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">警告: </span>這仅仅是提示您可以這么做,但這不是個好建議.</p></body></html> I want to be notified of test versions. (Advanced users only please.) Allow YAxis Scaling 允许Y轴缩放 Whether to include machine serial number on machine settings changes report Include Serial Number Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Fonts (Application wide settings) 字体 Use Pixmap Caching 使用像素映射缓存 Check for new version every 检查是否有新版本 Waveforms 波形 Maximum Calcs 最大估算值 Overview 總覽 Tooltip Timeout 工具提示超時 Preferences 参数設定 General CPAP and Related Settings 通用PAP以及相关設定 Default display height of graphs in pixels 使用預設项目顯示图标高度 Overlay Flags 叠加標記 Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. 如果OSCAR出现圖形渲染问题,請嘗試從預設設定(桌面OpenGL)變更此設定。 Makes certain plots look more "square waved". 在特定區塊顯示更多的方波。 Welcome 欢迎使用 Percentage drop in oxygen saturation 血氧飽和百分比下降 &General &通用 Standard average of indice 標準平均值 If you need to conserve disk space, please remember to carry out manual backups. 如果需要节省磁盘空間,請手動備份。 Compress SD Card Backups (slower first import, but makes backups smaller) 压缩備份SD卡資料(节省空間但匯入速度变慢) Keep Waveform/Event data in memory 保持波形/事件資料在内存中 Whether a breakdown of this waveform displays in overview. 是否顯示此波形的细分概览。 Normal Average 正常体重 Positional Waveforms 位置波形 A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? 為了變更資料設定將重建索引,這將花费几分鐘的時間。 確定要變更資料吗? Positional Events 位置事件 Preferred Calculation Methods 首選計算方法 Combined Count divided by Total Hours 合並计数除以總小時数 Graph Tooltips 圖形工具提示 &Oximetry &血氧测量 CPAP Clock Drift CPAP時鐘漂移 Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform 在此可以為%1波形設定<b>更低的</b> 閥值来進行某些計算 Auto-Launch CPAP Importer after opening profile 開啟個人檔案后自動啟動CPAP匯入程序 Square Wave Plots 方波图 TextLabel 文本标签 Preferred major event index 首選主要事件索引 Application 应用 Line Thickness 線宽 ProfileSelector Name 姓名 Bytes 位元 Sorry 歹勢 Profile: %1 個人檔案: %1 Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. 請注意:這將不可避免地删除個人檔案以及儲存在%2下的所有備份資料。 You must create a profile The selected profile does not appear to contain any data and cannot be removed by OSCAR KB MB GB TB PB 周期性呼吸 Email: <a href='mailto:%1'>%1</a> 電郵: <a href=‘mailto:%1’>%1</a> Profile '%1' was succesfully deleted 個人檔案 '%1'已成功删除 Summaries: 摘要: DELETE 删除 Forgot your password? 忘记密碼? &New Profile &新建個人檔案 There was an error deleting the profile directory, you need to manually remove it. 個人檔案目录出错,請手動移除. Show disk usage information 顯示磁盘使用資訊 Phone: %1 电话号码:%1 Enter Password for %1 輸入密碼 %1 Last Imported 最新匯入 Profile 個人檔案 Backups: 備份: Name: %1, %2 名字: %1, %2 Please select or create a profile... 請選取或創建一個個人檔案... You entered an incorrect password 密碼不正确 Hide disk usage information 隐藏磁盘使用資訊 No profile information given 未提供個人檔案資訊 Address: 地址: Ask on the forums how to reset it, it's actually pretty easy. 在论坛上询问如何重新設定。 Select a profile first 首先選取個人檔案 Other Data 其他参数 Version 版本 Events: 事件: &Open Profile &開啟個人檔案 Filter: 筛选: Reset filter to see all profiles Destroy Profile 删除個人檔案 &Edit Profile &編輯個人檔案 If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. 如果由於忘记密碼而试图删除,则需要重新設定密碼或手動删除個人檔案資料夾。 Enter the word <b>DELETE</b> below (exactly as shown) to confirm. 如下图所示,請確認输入資訊:DELETE。 Ventilator Brand PAP品牌 Profile: None 個人檔案:無 Ventilator Model PAP型号 You are about to destroy profile '<b>%1</b>'. 你將要删除個人檔案'<b>%1</b>'. You need to enter DELETE in capital letters. 需要输入大写字母 DELETE. ProgressDialog Abort 退出 QObject A 未分类 H 低通氣 P 壓力 h 小時 Built with Qt %1 on %2 Operating system: Graphics Engine: Graphics Engine type: App key: ANGLE / OpenGLES m cm m 分鐘 s 秒s Hz ? AVAPS UF1 UF2 UF3 AI 呼吸中止 CA 中枢性 EP 呼氣壓力 FL 氣流受限 in HI 低通氣指數 CSR IE 呼吸 LE 漏氣率 LF 漏氣标志 LL 大量漏氣 Kg 公斤 O2 氧氣 OA 阻塞性 NR 未影響事件 PB 周期性呼吸 PC 混合面罩 No PP 最高壓力 Only Settings and Compliance Data Available Summary Data Only PS 壓力 Motion On 开启 RE 呼吸作用 SA 呼吸中止 UA 未知中止 VS 打鼾指數 ft 英尺 lb ml 毫升 ms 毫秒 oz 盎司 &No &不 AHI 呼吸中止指數 ASV 适应性支持通氣模式 BMI 体重指數 CAI 中枢性中止指數 Apr 四月 Aug 八月 W-Avg Avg 平均 % in %1 DOB 生日 EPI 呼氣壓力指數 Dec 十二月 FLI 氣流受限指數 End 結束 Feb 二月 Jan 一月 Jul 七月 Jun 六月 NRI 未影響事件指數 Mar 三月 Max 最大 May 五月 Med 中間值 Min 最小 Nov 十一月 Oct 十月 Off 關閉 RDI 呼吸紊乱指數 REI 呼吸作用指數 UAI 未知中止指數 Sep 九月 VS2 打鼾指數2 Yes 是的 bpm 次每分鐘 Brain Wave 脑波 &Yes &是 APAP 全自動正压通氣 CPAP 持续氣道正压通氣 Auto 自動 Busy Min EPAP 呼氣壓力最小值 EPAP 呼氣壓力 Date 日期 Min IPAP 吸氣壓力最小值 IPAP 吸氣壓力 Last 最近一次 Leak 漏氣率 Mask 面罩 Med. 中間值. Mode 模式 Name 姓名 None RERA 呼吸努力相关性觉醒 Ramp 斜坡啟動 Zero 0 Resp. Event 呼吸時間 Inclination 侧卧 Launching Windows Explorer failed 啟動视窗浏览器失败 OSCAR %1 needs to upgrade its database for %2 %3 %4 <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? 是否要開啟自動備份,新版的OSCAR可以從這些版本重建? Hose Diameter 管径 &Save &保存 99.5% 90% {99.5%?} varies n/a EPAP %1 PS %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) 呼氣壓力 %1 吸氣壓力 %2 %3 (%4) {1-%2 ?} {3-%4 ?} {5)?} Therapy Pressure 治療壓力 BiPAP 双水平氣道正压通氣 Brand 品牌 ResMed S9 EPR: 呼氣壓力释放: Daily 日常 Email 电子邮件 Error 錯誤 First 第一次 Ramp Pressure 壓力上升 L/min 升/分鐘 Hours 小時 Leaks 漏氣率 Max: 最大: Min: 最小: Model 型式 Nasal 鼻罩 Notes 備註 Phone 电话号码 Ready 就緒 TTIA: 呼吸中止總時間: Snore 打鼾 Start 开始 Usage 使用 Respiratory Disturbance Index 呼吸紊乱指數 cmH2O 厘米水柱 Pressure Support 壓力支持 Bedtime: %1 睡眠時間:%1 Hypopnea 低通氣 ratio 比率 Tidal Volume 呼吸容量 Getting Ready... 準備就緒... AI=%1 Entire Day 整天 Intellipap pressure relief mode. Intellipa壓力释放模式. Personal Sleep Coach 個人睡眠教练 Zeo Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> 最新血氧测定資料:<a onclick='alert("daily=%2");'>%1</a> (1 day ago) (%2 days ago) Scanning Files 正在扫描檔案 Clear Airway 开放氣道 Heart rate in beats per minute 心臟每分鐘的跳動次数 A large mask leak affecting machine performance. 大量漏氣影响PAP性能. Time spent awake 清醒時長 Temp. Enable 温度啟用 Timed Breath 短時間的呼吸 Pop out Graph 弹出圖表 The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View d MMM yyyy [ %1 - %2 ] Using , found SleepyHead - You must run the OSCAR Migration Tool 必須執行OSCAR移轉工具 Mask On Time 面具使用時間 Choose the SleepyHead or OSCAR data folder to migrate or CANCEL to skip migration. The folder you chose does not contain valid SleepyHead or OSCAR data. You cannot use this folder: Migrating files from to OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. We suggest you use this folder: Click Ok to accept this, or No if you want to use a different folder. Choose or create a new folder for OSCAR data Next time you run OSCAR, you will be asked again. Data directory: Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? 此操作會损坏資料,是否繼續? There is a lockfile already present for this profile '%1', claimed on '%2'. Loading profile "%1"... 正在載入個人檔案"%1"... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Unable to create zip! Breathing Not Detected 呼吸未被檢測到 There is no data to graph 没有資料可供圖表 Journal 日誌 Locating STR.edf File(s)... 正在查找str.edf檔案... Pat. Trig. Breath 患者触发呼吸 (Summary Only) (摘要) Ramp Delay Period 斜坡升压期間 Sessions Switched Off 關閉療程 Awakenings 觉醒 This folder currently resides at the following location: 本地檔案位置: Morning Feel 晨起感觉 Pulse Change 脈搏變化 Disconnected 断开 Sleep Stage 睡眠階段 Minute Vent. 分鐘通氣率. SpO2 Drop 血氧飽和度降低 Ramp Event 斜坡啟動事件 SensAwake feature will reduce pressure when waking is detected. 觉醒偵測功能會在偵測到醒来時降低PAP的壓力. Show All Events 顯示所有事件 Upright angle in degrees 垂直 Importing Sessions... 匯入療程... Higher Expiratory Pressure 更高的呼氣壓力 NRI=%1 LKI=%2 EPI=%3 未影響事件指數=%1 漏氣指數=%2 呼氣壓力指數=%3 If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. 請单击“否”退出,並在再次啟動OSCAR之前手動備份您的個人檔案。 A vibratory snore 一次振動打鼾 Vibratory Snore 振動打鼾 As you did not select a data folder, OSCAR will exit. 由於没有選取資料資料夾,OSCAR將退出。 Lower Inspiratory Pressure 更低的吸氣壓力 Humidifier Enabled Status 湿化器已啟用 Essentials Plus Full Face 全臉 Backing up files... Reading data files... Full Time 全部時間 SmartFlex Level SmartFlex 级别 Snoring event. SN Journal Data 日誌 (%1% compliant, defined as > %2 hours) (%1% 依從性, 定义為 > %2 小時) Resp. Rate 呼吸速率 Insp. Time 吸氣時間 Exp. Time 呼氣時間 OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Oscar现在將退出,然后(嘗試)啟動計算机檔案管理器,以便手動備份個人檔案: ClimateLine Temperature 加热管路温度 Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Machine Unsupported 不支持的机型 Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. A relative assessment of the pulse strength at the monitoring site 脈搏的强度的相关评估 Machine 機器 Mask On 面罩开启 Max: %1 最大:%1 Sorry, the purge operation failed, which means this version of OSCAR can't start. 歹勢,清除操作失败,此版本的OSCAR無法啟動。 A sudden (user definable) drop in blood oxygen saturation 血氧飽和度突然降低 Time spent in deep sleep 深層睡眠時長 There are no graphs visible to print 無可列印圖表 OSCAR picked only the first one of these, and will use it in future: OSCAR將會使用其中的第一個: Target Vent. 目標通氣率. Sleep position in degrees 睡眠体位角度 Plots Disabled 停用區塊 Min: %1 最小:%1 Minutes 分鐘 Periodic Breathing 周期性呼吸 Popout %1 Graph 弹出圖表%1 Ramp Only 仅斜坡升压 Ramp Time 斜坡升压時間 For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. 出於某種原因,Oscar在您的個人檔案中找不到日誌对象记录,但找到了多個日誌資料資料夾。 PRS1 pressure relief mode. PRS1 壓力释放模式. An abnormal period of Periodic Breathing 周期性呼吸的不正常時期 ResMed Mask Setting ResMed面罩設定 ResMed Exhale Pressure Relief 瑞思迈呼氣壓力释放 iVAPS Auto for Her EPR EPR Level 呼氣壓力释放水平 Response Soft SmartStop Machine auto stops by breathing Smart Stop Patient View Simple Advanced BiPAP-T BiPAP-S BiPAP-S/T PAC Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... Unintentional Leaks 無意識漏氣量 Would you like to show bookmarked areas in this report? 是否希望在報告中顯示標記区域? VPAPauto VPAP全自動 Apnea Hypopnea Index 呼吸中止指數 Physical Height 身高 Pt. Access 患者通道 ASV (Fixed EPAP) ASV模式 (固定呼氣壓力) Patient Triggered Breaths 患者出发的呼吸 This means you will need to import this machine data again afterwards from your own backups or data card. 這意味着您需要自行由您的記錄或者資料卡中匯入資料. Events 事件 Humid. Level 湿度 AB Filter 抗菌過濾棉 Height 身高 Ramp Enable 斜坡升压啟動 (% %1 in events) (% %1 事件) Lower Threshold 降低 No Data 無資料 Zeo sleep quality measurement ZEO睡眠质量监测 Page %1 of %2 页码 %1 到 %2 Litres Manual 手動 Median 中值 Fixed %1 (%2) 固定 %1 (%2) Min %1 最小 %1 Could not find explorer.exe in path to launch Windows Explorer. 未找到视窗浏览器的可执行檔案. Machine automatically switches off PAP自動關閉 Connected 连接 Low Usage Days: %1 低使用天数:%1 PS Max 壓力支持最大壓力 PS Min 最小壓力 Database Outdated Please Rebuild CPAP Data 資料库过期 請重建PAP資料 Flow Limit. 氣流限制. Detected mask leakage including natural Mask leakages 包含自然漏氣在内的面罩漏氣率 Plethy 足够的 SensAwake 觉醒 ST/ASV 自发/定時 ASV Median Leaks 漏氣率中值 %1 Report %1報告 Pr. Relief 壓力释放 Graphs Switched Off 關閉圖表 Serial 串号 Series 系列 (last night) (昨晚) AHI %1 呼吸中止指數(AHI)%1 Weight 体重 ZEO ZQ ZEP睡商 PRS1 pressure relief setting. PRS1 壓力释放設定. Orientation 定位 Smart Start 自啟動 Event Flags 呼吸事件 A few breaths automatically starts machine 自動開啟機器在几次呼吸后 Zeo ZQ ZEO 睡商 Migrating Summary File Location 正在移轉摘要檔案位置 Zombie 呆瓜 Bookmarks 標記簇 PAP Mode 正压通氣模式 CPAP Mode CPAP模式 Time taken to get to sleep 入睡時長 DeVilbiss Intellipap SmartFlex Settings SmartFlex設定 An apnea where the airway is open 氣道开放情况下的呼吸中止 Flow Limitation 氣流受限 Pin %1 Graph 標示%1圖表 Unpin %1 Graph 解除標示%1圖表 Queueing Import Tasks... 正在排队匯入任務... Hours: %1h, %2m, %3s 小時数:%1小時.%2分鐘,%3秒 OSCAR does not yet have any automatic card backups stored for this device. OSCAR尚未為此设备儲存任何備份。 %1 Length: %3 Start: %2 %1 长度: %3 开始: %2 RDI %1 呼吸紊乱指數(RDI) %1 ASVAuto ASV全自動 PS %1 over %2-%3 (%4) 壓力 %1 超过 %2-%3 (%4) Flow Rate 氣流速率 Time taken to breathe out 呼氣時間 Important: 重要提示: Machine auto starts by breathing 呼吸触发啟動 An optical Photo-plethysomogram showing heart rhythm 光学探测顯示心率 Loading %1 data for %2... 正在為%2載入%1資料... Pillows 鼻枕 %1 Length: %3 Start: %2 %1 长度: %3 开始: %2 Time Awake 清醒時間 How you felt in the morning 早上醒来的感觉 I:E Ratio 呼吸比率 Amount of air displaced per breath 每次呼吸氣量 Pat. Trig. Breaths 患者触发呼吸率 Humidity Level 湿度 Profile 個人檔案 Address 地址 Leak Flag 漏氣标志 Leak Rate 漏氣率 Loading Summaries.xml.gz 載入摘要.xml.gz檔案 ClimateLine Temperature Enable 加热管路温度啟用 Severity (0-1) 嚴重程度 (0-1) Reporting from %1 to %2 正在生成由 %1 到 %2 的報告 Are you sure you want to reset all your channel colors and settings to defaults? 確定將所有通道颜色恢复預設設定吗? BrainWave 脑波 Inspiratory Pressure 吸氣壓力 Whether or not machine allows Mask checking. 是否允许PAP進行面罩检查. Number of Awakenings 觉醒次数 A pulse of pressure 'pinged' to detect a closed airway. 通过壓力脉冲'砰'可以偵測到氣道關閉. Intellipap pressure relief level. Intellipap 壓力释放水平. Non Responding Event 未回應事件 Median Leak Rate 漏氣率中值 (%3 sec) (%3 秒) Rate of breaths per minute 每分鐘呼吸的次数 Are you ready to upgrade, so you can run the new version of OSCAR? 準備升级,是否執行新版本的OSCAR? Updating Statistics cache Usage Statistics 使用統計值 Perfusion Index 灌注指數 Graph displaying snore volume 圖形顯示打鼾指數 Mask Off 面罩關閉 Max EPAP 呼氣壓力最大值 Max IPAP 吸氣壓力最大值 Bedtime 睡眠時間 EPAP %1 IPAP %2 (%3) 呼氣壓力 %1 吸氣壓力%2 (%3) Pressure 壓力 Auto On 自動開啟 Average 平均 Target Minute Ventilation 目標分鐘通氣率 Amount of air displaced per minute 每分鐘的换氣量 TTIA: %1 呼吸中止總時間: %1 Percentage of breaths triggered by patient 患者出发的呼吸百分比 Non Data Capable Machine 没有使用機器的資料 Days: %1 天数:%1 Plethysomogram 体积描述术 Unclassified Apnea 未分類的呼吸中止 A user definable event detected by OSCAR's flow waveform processor. 由OSCAR的流量波形處理器檢測到的使用者自訂事件。 Software Engine 應用程式引擎 Auto Bi-Level (Fixed PS) 自動双水平 Please Note 請留言 Starting Ramp Pressure 开始斜坡升压 Last Updated 最近更新 Intellipap event where you breathe out your mouth. Intellipap偵測到的嘴部呼吸事件. ASV (Variable EPAP) ASV模式 (可变呼氣壓力) Exhale Pressure Relief Level 呼氣壓力释放水平 Flow Limit 氣流受限 UAI=%1 未知中止指數=%1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 很少使用, %2 不使用, 超过 %3 天 (%4% 兼容.) 长度: %5 / %6 / %7 Loading Summary Data 正在載入摘要資料 Information 消息 Pulse Rate 脈搏 Rise Time 上升時間 Cheyne Stokes Respiration 潮式呼吸 SmartStart 自啟動 Graph showing running AHI for the past hour 同行顯示最近一個小時的AHI Graph showing running RDI for the past hour 圖形顯示最近一個小時的RDI Temperature Enable 温度测量啟用 Seconds %1 (%2 days): %1 (%2 天): Desktop OpenGL 桌面OpenGL Snapshot %1 快照 %1 Mask Time 面罩使用時間 How you feel (0 = like crap, 10 = unstoppable) 体感(0-無效,10=喜欢到停不下来) Time in REM Sleep 眼動睡眠時長 Channel 通道 Time In Deep Sleep 深層睡眠時長 Time in Deep Sleep 深層睡眠時長 Obstructive 阻塞性 Pressure Max 最大壓力 Pressure Min 最小壓力 Diameter of primary CPAP hose PAP主管内径 Max Leaks 最大漏氣率 Time to Sleep 睡眠時長 Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. 呼吸努力指數與觉醒有关:呼吸限制會導致觉醒或者睡眠障碍. Humid. Status 湿化器状态 (Sess: %1) (療程:%1) Climate Control 恒温控制 Perf. Index % 灌注指數 % Standard 標準 If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. 如果您过往的資料已经丢失,請手動將所有的 Journal_XXXXXXX 資料夾内的檔案拷贝到此. &Cancel &取消 Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) 最小呼氣壓力%1 最大吸氣壓力%2 壓力 %3-%4 (%5) Default 預設 Breaths/min 呼吸次数/分鐘 Degrees &Destroy &删除 User Flag #1 使用者標記#1 User Flag #2 使用者標記#2 User Flag #3 使用者標記#3 OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR现在將啟動匯入小幫手,以便您可以重新安装%1資料。 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% 呼吸作用指數=%1 打鼾指數=%2 氣流受限指數=%3 周期性呼吸/潮湿呼吸=%4%% Median rate of detected mask leakage 面罩漏氣率的中間值 Bookmark Notes 標記備註 Bookmark Start 標記开始 PAP Device Mode 正压通氣模式 Mask Pressure 面罩壓力 A vibratory snore as detcted by a System One machine 振動打鼾可被System One偵測到 No oximetry data has been imported yet. 尚未匯入血氧测定資料。 Please be careful when playing in OSCAR's profile folders :-P 請谨慎在OSCAR個人資料夾中操作:-P 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=醒 2=眼動睡眠 3=淺層睡眠 4=深層睡眠 Respiratory Event 呼吸事件 Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting An abnormal period of Cheyne Stokes Respiration 潮式呼吸的不正常時期 An apnea that couldn't be determined as Central or Obstructive. Apnea An apnea reportred by your CPAP machine. A restriction in breathing from normal, causing a flattening of the flow waveform. A type of respiratory event that won't respond to a pressure increase. 未導致壓力上升的呼吸事件. Debugging channel #1 For internal use only Test #1 Debugging channel #2 Test #2 Antibacterial Filter 抗菌過濾棉 Windows User Windows使用者 Cataloguing EDF Files... 正在给EDF檔案編輯目录... Question 问题 Time spent in light sleep 淺層睡眠時長 Waketime: %1 觉醒時間:%1 Time In REM Sleep 眼動睡眠時長 Higher Inspiratory Pressure 更高的吸氣壓力 Don't forget to place your datacard back in your CPAP machine 請拔出内存卡,插入PAP The machine data folder needs to be removed manually. 數據資料夾需要手動移除. Summary Only 仅有概要資訊 Bookmark End 標記結束 Bi-Level 双水平 Unknown 未知 Finishing Up... 整理中... Events/hr 事件/小時 PRS1 humidifier connected? PRS1 加湿器是否连接? CPAP Session contains summary data only 仅含有概要資料 Finishing up... 整理中... Duration 時長 Scanning Files... 扫描檔案... Hours: %1 小時:%1 Flex Mode Flex模式 Sessions 療程 A period during a session where the machine could not detect flow. 機器無法檢測流量的療程期間。 Auto Off 自動關閉 EPAP %1 IPAP %2-%3 (%4) 呼氣壓力 %1 吸氣壓力 %2 %3 (%4) Settings 設定 Overview 總覽 The folder you chose is not empty, nor does it already contain valid OSCAR data. 您選取的資料夾不是空的,也不包含有效的OSCAR資料。 Temperature 温度 Entire Day's Flow Waveform 全天氣流波形 Exiting 正在退出 Time in Light Sleep 淺層睡眠時長 Time In Light Sleep 淺層睡眠時長 Fixed Bi-Level 固定双水平 Machine Information 機器資訊 Pressure Support Maximum 壓力支持最大值 Graph showing severity of flow limitations 圖形顯示氣流限制的嚴重程度 : %1 hours, %2 minutes, %3 seconds :%1 小時, %2 分鐘, %3 秒 Auto Bi-Level (Variable PS) 全自動双水平(壓力可变) Mask Alert 面罩报警 OSCAR Reminder OSCAR提醒 OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR找到先前的日誌資料夾,已被重命名: A partially obstructed airway 氣道部分阻塞 Pressure Support Minimum 壓力支持最小值 Large Leak 大量漏氣 Time started according to str.edf 依據 str.edf 計時 Wake-up Warning 警告 Min Pressure 最小壓力 Total Leak Rate 總漏氣率 Max Pressure 最大壓力 MaskPressure 面罩壓力 Duration %1:%2:%3 時長 %1:%2:%3 AHI %1 Upper Threshold 增加 OSCAR will not touch this folder, and will create a new one instead. OSCAR不會變更此資料夾,將會創建一個新資料夾。 Total Leaks 總漏氣量 Minute Ventilation 分鐘通氣率 Rate of detected mask leakage 面罩漏氣率 Breathing flow rate waveform 呼吸流量波形 Lower Expiratory Pressure 更低的呼氣壓力 SD Mask Pressure (High frequency) A ResMed data item: Trigger Cycle Event Movement Movement detector Time spent in REM Sleep 眼動睡眠時長 Min %1 Max %2 (%3) 最小 %1 最大%2(%3) If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. 如果正在使用云儲存,請确保OSCAR已關閉,並且在繼續操作之前已在另一台計算机上完成同步。 AI=%1 HI=%2 CAI=%3 中止指數=%1 低通氣指數=%2 中枢性中止指數=%3 Time taken to breathe in 吸氣時間 Maximum Therapy Pressure 最大治療壓力 Are you sure you want to use this folder? 确认選取這個資料夾吗? Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. 使用檔案管理器复制個人檔案目录,然后重新啟動oscar並完成升级过程。 I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. OSCAR只能跟踪该機器的使用時間和基本的設定。 %1 (%2 day): %1 (%2 天): Current Selection 当前選取 Blood-oxygen saturation percentage 血氧飽和百分比 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR保留了设备資料卡的備份</b> Inspiratory Time 吸氣時間 Respiratory Rate 呼吸频率 Hide All Events 隐藏所有事件 Printing %1 Report 正在列印%1報告 Expiratory Time 呼氣時間 Expiratory Puff 嘴部呼吸 Maximum Leak 最大漏氣率 Ratio between Inspiratory and Expiratory time 呼氣和吸氣時間的比率 APAP (Variable) APAP(自動) Minimum Therapy Pressure 最小治療壓力 A sudden (user definable) change in heart rate 心率突变 Body Mass Index 体重指數 Oximetry 血氧测定 Oximeter 血氧儀 No Data Available 無可用資料 The maximum rate of mask leakage 面罩的最大漏氣率 Backing Up Files... Untested Data model %1 DreamStation 2 unknown model Machine Untested CPAP-Check AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS C-Flex C-Flex+ A-Flex P-Flex Bi-Flex Flex Flex Level Flex Lock Whether Flex settings are available to you. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Humidifier Status 加湿器状态 Humidification Mode PRS1 Humidification Mode Humid. Mode Fixed (Classic) Adaptive (System One) Heated Tube Passover Tube Temperature PRS1 Heated Tube Temperature Tube Temp. PRS1 Humidifier Setting Target Time PRS1 Humidifier Target Time Hum. Tgt Time Mask Resistance Setting Mask Resist. Hose Diam. 22mm 15mm 12mm Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Type of ramp curve to use. Linear SmartRamp Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Fixed Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Breath BPM Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled Variable Breathing UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend BND Machine Initiated Breath 呼吸触发機器开启 TB Peak Flow Peak flow during a 2-minute interval Machine Database Changes 資料库變更 SmartFlex Mode SmartFlex模式 Journal Notes 日誌備註 (%2 min, %3 sec) (%2 分, %3 秒) You can only work with one instance of an individual OSCAR profile at a time. 一次只能处理单個OSCAR個人檔案的一個实例。 Expiratory Pressure 呼氣壓力 Show AHI 顯示AHI Tgt. Min. Vent 目標 分鐘 通氣 Rebuilding from %1 Backup 由%1備份重建中 Are you sure you want to reset all your waveform channel colors and settings to defaults? 確定將所有的波形通道颜色重新設定為預設值吗? Pressure Pulse 壓力脉冲 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 療程: %1 / %2 / %3 长度: %4 / %5 / %6 最长: %7 / %8 / %9 Humidifier 湿度 Relief: %1 壓力释放: %1 Patient ID 患者编号 Patient??? 病患??? An apnea caused by airway obstruction 氣道阻塞状态下的呼吸中止 Vibratory Snore (VS2) 震動式打鼾 (VS2) Please Wait... 請稍候... CMS50D+ CMS50E/F Contec CMS50 CMS50F3.7 CMS50F Dreem Fisher & Paykel ICON ChoiceMMed MD300 Respironics M-Series Philips Respironics System One SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle Somnopose Somnopose Software Your Viatom device generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Viatom Viatom Software Weinmann SOMNOsoft2 New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to check for updates. Please try again later. %1 Charts %1 of %2 Charts Loading summaries This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank 关於:空白 SessionBar %1h %2m %1% %2m No Sessions Present 没有療程 SleepStyleLoader This Machine Record cannot be imported in this profile. 無法在此個人檔案中匯入此设备的记录。 Import Error 匯入出错 The Day records overlap with already existing content. 本日的資料已覆蓋已儲存的内容. Statistics Days 天数 Worst Flow Limtation 最差的流量限值 Worst Large Leaks 最大漏氣量 Oximeter Statistics 血氧儀統計值 Date: %1 Leak: %2% 日期: %1 Leak: %2% CPAP Usage CPAP使用情况 Blood Oxygen Saturation 血氧飽和度 No PB on record 無周期性呼吸資料 % of time in %1 % 在 %1 時間中 Last 30 Days 最近三十天 Want more information? 更多資訊? Days Used: %1 天数:%1 %1 Index %1 指數 Date: %1 - %2 AHI: %1 Total Hours: %1 Worst RX Setting 最差治療方案设定 Best RX Setting 最佳治療方案设定 %1 day of %2 Data on %3 %1 天在 %2 中的資料在 %3 Date: %1 CSR: %2% 日期: %1 CSR: %2% % of time above %1 threshold % 的時間高於 %1 閥值 Therapy Efficacy 疗效 % of time below %1 threshold % 的時間低於 %1 閥值 Max %1 最大 %1 Compliance (%1 hrs/day) %1 Median %1 中值 Min %1 最小 %1 This report was prepared on %1 by OSCAR %2 OSCAR is free open-source CPAP report software Changes to Machine Settings No data found?!? Oscar has no data to report :( Most Recent 最近 OSCAR needs all summary data loaded to calculate best/worst data for individual days. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. 請在属性选单中选中預调取彙總資訊選項. Pressure Settings 壓力設定 Phone: %1 电话号码:%1 Worst PB 最差周期性呼吸 Pressure Statistics 壓力統計值 Name: %1, %2 名字: %1, %2 Last 6 Months 最近六個月 Email: %1 电子邮箱: %1 Average %1 平均 %1 No %1 data available. %1 資料可用. Last Use 最近一次 Pressure Relief 壓力释放 DOB: %1 生日:%1 Pulse Rate 脈搏 First Use 首次 Worst CSR 最差的潮式呼吸 Worst AHI 最高的AHI Last Week 上週 Last Year 去年 Best Flow Limitation 最好的流量限值 Address: 地址: Details 詳細資料 No Flow Limitation on record 無流量限值记录 %1 days of %2 Data, between %3 and %4 %1 天的在 %2中的資料,在%3 和 %4 之間 No Large Leaks on record 無大量漏氣记录 Date: %1 PB: %2% 日期: %1 PB: %2% Best AHI 最低AHI Last Session 上一個療程 Date: %1 AHI: %2 日期: %1 AHI: %2 Machine Information 機器資訊 CPAP Statistics CPAP統計值 Compliance: %1% 依從: %1% Date: %1 FL: %2 日期: %1 FL: %2 Days AHI of 5 or greater: %1 AHI大於5的天数: %1 Low Use Days: %1 低使用天数:%1 Leak Statistics 漏氣統計值 No CSR on record 無潮式呼吸记录 Average Hours per Night 平均每晚的小時数 Welcome over 高於 under 低於 Your CPAP machine used a constant %1 %2 of air 您的呼吸器使用固定%1 %2加壓空氣 Your average leaks were %1 %2, which is %3 your %4 day average of %5. 平均漏氣為 %1 %2,即 %3 您的 %5 天 %4 平均值。 <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">注意:</span><span style=" color:#ff0000;">請確認 PAP 專用記憶卡的覆寫保護已開啟</span><span style=" font-weight:600; color:#ff0000;">特別是在插入其它電腦裝置之前&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>有些作業系統會在偵測到連接媒體時,自動寫入索引檔案且無預設提示通知,此類型系統寫入動作可能導致呼吸器將無法辨識讀取記憶卡</span></p></body></html> Welcome to the Open Source CPAP Analysis Reporter 歡迎使用開放資源 CPAP 解析彙整程式 Your EPAP pressure fixed at %1 %2. 呼氣壓力固定於 %1 %2。 No CPAP data has been imported yet. 尚未匯入呼吸器資料。 Daily View 每日概況 Oximetry Wizard 血氧測定儀小幫手 last night 昨晚 What would you like to do? 您打算從何處著手? was %1 (on %2) 為 %1 (於 %2) as there are some options that affect import. 有些至關重要的偏好選項會影響資料匯入. Your machine was on for %1. 呼吸器使用時間為 %1。 You had an AHI of %1, which is %2 your %3 day average of %4. 您的 AHI 相等於%1, 即 %2 您的 %3 天 %4 的平均值。 <font color = red>You only had the mask on for %1.</font> <font color = red>您有戴著呼吸罩使用機器的計時只有 %1.</font> First import can take a few minutes. 首次記錄導入需耗時數分鐘。 equal to 等於 It would be a good idea to check File->Preferences first, 開始的第一步,先檢查 檔案 --> 偏好選項, %2 days ago %2 天前 1 day ago 1 天前 Statistics 統計數據 Your machine used a constant %1-%2 %3 of air. 您的呼吸機使用固定 %1 %2 %3 加壓空氣。 CPAP Importer CPAP 導入器 Your pressure was under %1 %2 for %3% of the time. 壓力低於 %1 %2,持續時間%3%. Overview 綜合概況 Your machine was under %1-%2 %3 for %4% of the time. 呼吸器使用時數低於 %1-%2 %3 ,持續時間%4% 。 reasonably close to 合理地近似 Your EPAP pressure was under %1 %2 for %3% of the time. 呼氣壓力低於 %1 %2,持續時間 %3%。 Your IPAP pressure was under %1 %2 for %3% of the time. 吸氣壓力低於 %1 %2,持續時間 %3%。 Note that some preferences are forced when a ResMed machine is detected 請注意,在偵測到 ResMed 設備時某些偏好選項會直接套用 %1 hours, %2 minutes and %3 seconds %1 小時,%2分 %3 秒 The last time you used your %1... 您上次使用 %1... gGraph %1 days %1天 gGraphView Clone %1 Graph 复制 %1 圖表 Oximeter Overlays 血氧儀 覆蓋 Restore X-axis zoom to 100% to view entire selected period. Restore X-axis zoom to 100% to view entire day's data. Plots 區塊 Resets all graphs to a uniform height and default order. 重新設定所有图标到统一的高度以及預設顺序. Double click title to pin / unpin Click and drag to reorder graphs Remove Clone 移除複製 Dotted Lines 虛線 CPAP Overlays CPAP 覆蓋 Y-Axis Y轴 Reset Graph Layout 重新設定圖表配置 100% zoom level 100% 缩放级别 OSCAR-code-v1.3.1/Translations/Dansk.da.ts000066400000000000000000013700711417327530600201560ustar00rootroot00000000000000 AboutDialog &About Release Notes Credits GPL License Close Show data folder About OSCAR %1 Sorry, could not locate About file. Sorry, could not locate Credits file. Sorry, could not locate Release Notes. OSCAR %1 Important: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. To see if the license text is available in your language, see %1. CMS50F37Loader Could not find the oximeter file: Could not open the oximeter file: CMS50Loader Could not get data transmission from oximeter. Please ensure you select 'upload' from the oximeter devices menu. Could not find the oximeter file: Could not open the oximeter file: CheckUpdates Checking for newer OSCAR versions Daily Go to the previous day Show or hide the calender Go to the next day Go to the most recent day with data records Events Εκδηλώσεις View Size Notes Σημειώσεις Journal Ημερολόγιο διάφορων πράξεων B u Color Χρώμα Small Medium Big Zombie Βρυκόλακας I'm feeling ... Weight Βάρος If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Awesome B.M.I. Bookmarks Σελιδοδείκτες Add Bookmark Starts Remove Bookmark Flags Graphs Γραφικες Παράστασης Show/hide available graphs. Breakdown events UF1 UF1 UF2 UF2 Time at Pressure No %1 events are recorded this day %1 event %1 events Session Start Times Session End Times Session Information Oximetry Sessions Duration Διάρκεια (Mode and Pressure settings missing; yesterday's shown.) This bookmark is in a currently disabled area.. CPAP Sessions Sleep Stage Sessions Position Sensor Sessions Unknown Session Machine Settings Model %1 - %2 PAP Mode: %1 99.5% This day just contains summary data, only limited information is available. Total ramp time Time outside of ramp Start End Unable to display Pie Chart on this system 10 of 10 Event Types This CPAP machine does NOT record detailed data BRICK :( Sorry, this machine only provides compliance data. "Nothing's here!" No data is available for this day. 10 of 10 Graphs Oximeter Information Details Click to %1 this session. disable enable %1 Session #%2 %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. SpO2 Desaturations Pulse Change events SpO2 Baseline Used Statistics Στατιστικά Total time in apnea Time over leak redline Event Breakdown Sessions all off! Sessions exist for this day but are switched off. Impossibly short session Zero hours?? Complain to your Equipment Provider! Pick a Colour Bookmark at %1 ExportCSV Export as CSV Dates: Resolution: Details Sessions Daily Filename: Cancel Export Start: End: Quick Range: Most Recent Day Last Week Last Fortnight Last Month Last 6 Months Last Year Everything Custom Details_ Sessions_ Summary_ Select file to export to CSV Files (*.csv) DateTime Session Event Data/Duration Date Session Count Start End Total Time AHI Count FPIconLoader Import Error This Machine Record cannot be imported in this profile. The Day records overlap with already existing content. Help Hide this message Search Topic: Help Files are not yet available for %1 and will display in %2. Help files do not appear to be present. HelpEngine did not set up correctly HelpEngine could not register documentation correctly. Contents Index Search No documentation available Please wait a bit.. Indexing still in progress No %1 result(s) for "%2" clear MD300W1Loader Could not find the oximeter file: Could not open the oximeter file: MainWindow &Statistics Report Mode Standard Monthly Date Range Statistics Στατιστικά Daily Overview Oximetry Import Help &File &View &Reset Graphs &Help Troubleshooting &Data &Advanced Purge ALL Machine Data Rebuild CPAP Data &Import CPAP Card Data Show Daily view Show Overview view &Maximize Toggle Maximize window Reset Graph &Heights Reset sizes of graphs Show Right Sidebar Show Statistics view Import &Dreem Data Show &Line Cursor Show Daily Left Sidebar Show Daily Calendar Create zip of CPAP data card Create zip of OSCAR diagnostic logs Create zip of all OSCAR data Report an Issue System Information Show &Pie Chart Show Pie Chart on Daily page Standard graph order, good for CPAP, APAP, Bi-Level Advanced Advanced graph order, good for ASV, AVAPS Show Personal Data Check For &Updates Purge Current Selected Day &CPAP &Oximetry &Sleep Stage &Position &All except Notes All including &Notes &Preferences &Profiles &About OSCAR Show Performance Information CSV Export Wizard Export for Review E&xit Exit View &Daily View &Overview View &Welcome Use &AntiAliasing Show Debug Pane Take &Screenshot O&ximetry Wizard Print &Report &Edit Profile Import &Viatom/Wellue Data Daily Calendar Backup &Journal Online Users &Guide &Frequently Asked Questions &Automatic Oximetry Cleanup Change &User Purge &Current Selected Day Right &Sidebar Daily Sidebar View S&tatistics Navigation Bookmarks Σελιδοδείκτες Records Exp&ort Data Profiles Purge Oximetry Data View Statistics Import &ZEO Data Import RemStar &MSeries Data Sleep Disorder Terms &Glossary Change &Language Change &Data Folder Import &Somnopose Data Current Days Welcome &About Please wait, importing from backup folder(s)... Import Problem Please insert your CPAP data card... Access to Import has been blocked while recalculations are in progress. CPAP Data Located Import Reminder Find your CPAP data card Importing Data Choose where to save screenshot Image files (*.png) The User's Guide will open in your default browser The FAQ is not yet implemented If you can read this, the restart command didn't work. You will have to do it yourself manually. Are you sure you want to rebuild all CPAP data for the following machine: For some reason, OSCAR does not have any backups for the following machine: OSCAR does not have any backups for this machine! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: No help is available. %1's Journal Choose where to save journal XML Files (*.xml) Export review is not yet implemented Would you like to zip this card? Choose where to save zip ZIP files (*.zip) Creating zip... Calculating size... Reporting issues is not yet implemented Help Browser %1 (Profile: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Please open a profile first. Check for updates not implemented Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Are you really sure you want to do this? Because there are no internal backups to rebuild from, you will have to restore from your own. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Note as a precaution, the backup folder will be left in place. Are you <b>absolutely sure</b> you want to proceed? The Glossary will open in your default browser There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete Are you sure you want to delete oximetry data for %1 <b>Please be aware you can not undo this operation!</b> Select the day with valid oximetry data in daily view first. Loading profile "%1" Imported %1 CPAP session(s) from %2 Import Success Already up to date with CPAP data at %1 Up to date Couldn't find any valid Machine Data at %1 Choose a folder No profile has been selected for Import. Import is already running in the background. A %1 file structure for a %2 was located at: A %1 file structure was located at: Would you like to import from this location? Specify Access to Preferences has been blocked until recalculation completes. There was an error saving screenshot to file "%1" Screenshot saved to file "%1" Please note, that this could result in loss of data if OSCAR's backups have been disabled. There was a problem opening MSeries block File: MSeries Import complete OSCAR Information MinMaxWidget Auto-Fit Defaults Override The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. The Minimum Y-Axis value.. Note this can be a negative number if you wish. The Maximum Y-Axis value.. Must be greater than Minimum to work. Scaling Mode This button resets the Min and Max to match the Auto-Fit NewProfile Edit User Profile I agree to all the conditions above. User Information User Name Password Protect Profile Password ...twice... Locale Settings Country TimeZone about:blank Very weak password protection and not recommended if security is required. DST Zone Personal Information (for reports) First Name Last Name It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. D.O.B. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> Gender Male Female Height Metric English Contact Information Address Email Phone CPAP Treatment Information Date Diagnosed Untreated AHI CPAP Mode CPAP APAP Bi-Level ASV RX Pressure Doctors / Clinic Information Doctors Name Practice Name Patient ID &Cancel &Back &Next Select Country Welcome to the Open Source CPAP Analysis Reporter This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. PLEASE READ CAREFULLY Accuracy of any data displayed is not and can not be guaranteed. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Use of this software is entirely at your own risk. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Please provide a username for this profile Passwords don't match Profile Changes Accept and save this information? &Finish &Close this window Overview Range: Last Week Last Two Weeks Last Month Last Two Months Last Three Months Last 6 Months Last Year Everything Custom Snapshot Start: End: Reset view to selected date range Toggle Graph Visibility Drop down to see list of graphs to switch on/off. Graphs Γραφικες Παράστασης Respiratory Disturbance Index Apnea Hypopnea Index Usage Usage (hours) Session Times Total Time in Apnea Total Time in Apnea (Minutes) Body Mass Index How you felt (0-10) 10 of 10 Charts Show all graphs Εμφάνιση όλων των γραφικoν παραστάσεον Hide all graphs Απόκρυψη όλων των γραφικoν παραστάσεον OximeterImport Oximeter Import Wizard Skip this page next time. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> Record attached to computer overnight (provides plethysomogram) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Please connect your oximeter device If you can read this, you likely have your oximeter type set wrong in preferences. Please connect your oximeter device, turn it on, and enter the menu Press Start to commence recording Show Live Graphs Duration Διάρκεια Pulse Rate Multiple Sessions Detected Start Time Details Import Completed. When did the recording start? Oximeter Starting time I want to use the time reported by my oximeter's built in clock. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> Choose CPAP session to sync to: You can manually adjust the time here if required: HH:mm:ssap &Cancel &Information Page CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 Set device date/time <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> Set device identifier Erase session after successful upload Import directly from a recording on a device <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> Please choose which one you want to import into OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> &Retry &Choose Session &End Recording &Sync and Save &Save and Finish &Start Scanning for compatible oximeters Could not detect any connected oximeter devices. Connecting to %1 Oximeter Renaming this oximeter from '%1' to '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. "%1", session %2 Nothing to import Your oximeter did not have any valid sessions. Close Waiting for %1 to start Waiting for the device to start the upload process... Select upload option on %1 You need to tell your oximeter to begin sending data to the computer. Please connect your oximeter, enter it's menu and select upload to commence data transfer... %1 device is uploading data... Please wait until oximeter upload process completes. Do not unplug your oximeter. Oximeter import completed.. Select a valid oximetry data file Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Live Oximetry Mode Live Oximetry Stopped Live Oximetry import has been stopped Oximeter Session %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Oximeter not detected Couldn't access oximeter Starting up... If you can still read this after a few seconds, cancel and try again Live Import Stopped %1 session(s) on %2, starting at %3 No CPAP data available on %1 Recording... Finger not detected I want to use the time my computer recorded for this live oximetry session. I need to set the time manually, because my oximeter doesn't have an internal clock. Something went wrong getting session data Welcome to the Oximeter Import Wizard Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Please remember: Important Notes: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Oximetry Date d/MM/yy h:mm:ss AP R&eset Pulse &Open .spo/R File Serial &Import &Start Live Serial Port &Rescan Ports PreferencesDialog Preferences &Import Combine Close Sessions Minutes Multiple sessions closer together than this value will be kept on the same day. Ignore Short Sessions <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Sessions starting before this time will go to the previous calendar day. Session Storage Options Compress SD Card Backups (slower first import, but makes backups smaller) &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. hours Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Flow Restriction Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> Duration of airflow restriction s Event Duration Allow duplicates near machine events. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. minutes Reset the counter to zero at beginning of each (time) window. Zero Reset CPAP Clock Drift Do not import sessions older than: Sessions older than this date will not be imported dd MMMM yyyy User definable threshold considered large leak Whether to show the leak redline in the leak graph Search &Oximetry Show in Event Breakdown Piechart Resync Machine Detected Events (Experimental) Percentage drop in oxygen saturation Pulse Sudden change in Pulse Rate of at least this amount bpm Minimum duration of drop in oxygen saturation Minimum duration of pulse change event. Small chunks of oximetry data under this amount will be discarded. &General Changes to the following settings needs a restart, but not a recalc. Preferred Calculation Methods Middle Calculations Upper Percentile Session Splitting Settings <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Memory and Startup Options Pre-Load all summary data at startup <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> Keep Waveform/Event data in memory <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> Import without asking for confirmation General CPAP and Related Settings Enable Unknown Events Channels AHI Apnea Hypopnea Index RDI Respiratory Disturbance Index AHI/Hour Graph Time Window Preferred major event index Compliance defined as Flag leaks over threshold Seconds <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> Hours For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Median is recommended for ResMed users. Median Weighted Average Normal Average True Maximum 99% Percentile Maximum Calcs General Settings Daily view navigation buttons will skip over days without data records Skip over Empty Days Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Enable Multithreading Bypass the login screen and load the most recent User Profile Create SD Card Backups during Import (Turn this off at your own peril!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> Combined Count divided by Total Hours Time Weighted average of Indice Standard average of indice Custom CPAP User Event Flagging Events Εκδηλώσεις Reset &Defaults <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> Waveforms Flag rapid changes in oximetry stats Other oximetry options Discard segments under Flag Pulse Rate Above Flag Pulse Rate Below Changing SD Backup compression options doesn't automatically recompress backup data. Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Compress Session Data (makes OSCAR data smaller, but day changing slower.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Calculate Unintentional Leaks When Not Present 4 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Show flags for machine detected events that haven't been identified yet. Show Remove Card reminder notification on OSCAR shutdown Check for new version every days. Last Checked For Updates: TextLabel I want to be notified of test versions. (Advanced users only please.) &Appearance Graph Settings <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> Bar Tops Line Chart Overview Linecharts Whether to include machine serial number on machine settings changes report Include Serial Number Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> How long you want the tooltips to stay visible. Scroll Dampening Tooltip Timeout Default display height of graphs in pixels Graph Tooltips The visual method of displaying waveform overlay flags. Standard Bars Top Markers Graph Height Auto-Launch CPAP Importer after opening profile Automatically load last used profile on start-up <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered Your masks vent rate at 20 cmH2O pressure Your masks vent rate at 4 cmH2O pressure l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Always save screenshots in the OSCAR Data folder Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR On Opening Profile Welcome Daily Statistics Στατιστικά Switch Tabs No change After Import Overlay Flags Line Thickness The pixel thickness of line plots Other Visual Settings Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Use Anti-Aliasing Makes certain plots look more "square waved". Square Wave Plots Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Use Pixmap Caching <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> Animations && Fancy Stuff Whether to allow changing yAxis scales by double clicking on yAxis labels Allow YAxis Scaling Graphics Engine (Requires Restart) Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Fonts (Application wide settings) Font Size Bold Italic Application Graph Text Graph Titles Big Text Details &Cancel &Ok Name Color Χρώμα Flag Type Label CPAP Events Oximeter Events Positional Events Sleep Stage Events Unknown Events Double click to change the descriptive name this channel. Double click to change the default color for this channel plot/flag/data. <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> %1 %2 %1 %2 Overview Double click to change the descriptive name the '%1' channel. Whether this flag has a dedicated overview chart. Here you can change the type of flag shown for this event This is the short-form label to indicate this channel on screen. This is a description of what this channel does. Lower Upper CPAP Waveforms Oximeter Waveforms Positional Waveforms Sleep Stage Waveforms Whether a breakdown of this waveform displays in overview. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Data Processing Required A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Data Reindex Required A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Restart Required One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. If you need to conserve disk space, please remember to carry out manual backups. Are you sure you want to disable these backups? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Are you really sure you want to do this? Flag Minor Flag Span Always Minor No CPAP machines detected Will you be using a ResMed brand machine? Never This may not be a good idea ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ProfileSelector Filter: Reset filter to see all profiles Version &Open Profile &Edit Profile &New Profile Profile: None Please select or create a profile... Destroy Profile Profile Ventilator Brand Ventilator Model Other Data Last Imported Name You must create a profile Enter Password for %1 You entered an incorrect password Forgot your password? Ask on the forums how to reset it, it's actually pretty easy. Select a profile first The selected profile does not appear to contain any data and cannot be removed by OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. You are about to destroy profile '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. DELETE Sorry You need to enter DELETE in capital letters. There was an error deleting the profile directory, you need to manually remove it. Profile '%1' was succesfully deleted Bytes KB MB GB TB PB Summaries: Events: Backups: Hide disk usage information Show disk usage information Name: %1, %2 Phone: %1 Email: <a href='mailto:%1'>%1</a> Address: No profile information given Profile: %1 ProgressDialog Abort QObject No Data Events Εκδηλώσεις Duration Διάρκεια (% %1 in events) Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ft lb oz Kg cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours Min %1 Hours: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 %1 Length: %3 Start: %2 Mask On Mask Off %1 Length: %3 Start: %2 TTIA: TTIA: %1 Minutes Seconds h m s ms Events/hr Hz bpm Litres ml Breaths/min ? Severity (0-1) Degrees Error Warning Information Busy Please Note Graphs Switched Off Sessions Switched Off &Yes &No &Cancel &Destroy &Save BMI Weight Βάρος Zombie Βρυκόλακας Pulse Rate Plethy Pressure Daily Profile Overview Oximetry Oximeter Event Flags Default CPAP BiPAP Bi-Level EPAP Min EPAP Max EPAP IPAP Min IPAP Max IPAP APAP ASV AVAPS ST/ASV Humidifier H OA A CA FL SA LE EP VS VS2 RERA PP P RE NR NRI O2 PC UF1 UF1 UF2 UF2 UF3 UF3 PS AHI RDI AI HI UAI CAI FLI REI EPI PB IE Insp. Time Exp. Time Resp. Event Flow Limitation Flow Limit SensAwake Pat. Trig. Breath Tgt. Min. Vent Target Vent. Minute Vent. Tidal Volume Resp. Rate Snore Leak Leaks Large Leak LL Total Leaks Unintentional Leaks MaskPressure Flow Rate Sleep Stage Usage Sessions Pr. Relief No Data Available App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Software Engine ANGLE / OpenGLES Desktop OpenGL m cm in Only Settings and Compliance Data Available Summary Data Only Bookmarks Σελιδοδείκτες Mode Model Brand Serial Series Machine Channel Settings Inclination Orientation Motion Name DOB Phone Address Email Patient ID Date Bedtime Wake-up Mask Time Unknown None Ready First Last Start End On Off Yes No Min Max Med Average Median Avg W-Avg Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Machine Unsupported I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Scanning Files... Importing Sessions... Finishing up... Machine Untested Flex Lock Whether Flex settings are available to you. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Mask Resistance Setting Mask Resist. Hose Diam. 15mm 22mm Backing Up Files... Untested Data model %1 DreamStation 2 unknown model CPAP-Check AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS Flex Mode PRS1 pressure relief mode. C-Flex C-Flex+ A-Flex P-Flex Rise Time Bi-Flex Flex Flex Level PRS1 pressure relief setting. Passover Target Time PRS1 Humidifier Target Time Hum. Tgt Time Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Type of ramp curve to use. Linear SmartRamp Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Fixed Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Breath BPM Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled Variable Breathing UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend Peak Flow Peak flow during a 2-minute interval Humidifier Status PRS1 humidifier connected? Disconnected Connected Humidification Mode PRS1 Humidification Mode Humid. Mode Fixed (Classic) Adaptive (System One) Heated Tube Tube Temperature PRS1 Heated Tube Temperature Tube Temp. PRS1 Humidifier Setting Hose Diameter Diameter of primary CPAP hose 12mm Auto On A few breaths automatically starts machine Auto Off Machine automatically switches off Mask Alert Whether or not machine allows Mask checking. Show AHI Breathing Not Detected A period during a session where the machine could not detect flow. BND Timed Breath Machine Initiated Breath TB Windows User Using , found SleepyHead - You must run the OSCAR Migration Tool <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> Launching Windows Explorer failed Could not find explorer.exe in path to launch Windows Explorer. OSCAR %1 needs to upgrade its database for %2 %3 %4 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> OSCAR does not yet have any automatic card backups stored for this device. This means you will need to import this machine data again afterwards from your own backups or data card. Important: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Are you ready to upgrade, so you can run the new version of OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Machine Database Changes Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. The machine data folder needs to be removed manually. This folder currently resides at the following location: Rebuilding from %1 Backup Therapy Pressure Inspiratory Pressure Lower Inspiratory Pressure Higher Inspiratory Pressure Expiratory Pressure Lower Expiratory Pressure Higher Expiratory Pressure Pressure Support PS Min Pressure Support Minimum PS Max Pressure Support Maximum Min Pressure Minimum Therapy Pressure Max Pressure Maximum Therapy Pressure Ramp Time Ramp Delay Period Ramp Pressure Starting Ramp Pressure Ramp Event Ramp Vibratory Snore (VS2) A ResMed data item: Trigger Cycle Event Mask On Time Time started according to str.edf Summary Only An apnea where the airway is open An apnea caused by airway obstruction Hypopnea A partially obstructed airway Unclassified Apnea UA Vibratory Snore A vibratory snore A vibratory snore as detcted by a System One machine Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A large mask leak affecting machine performance. Non Responding Event A type of respiratory event that won't respond to a pressure increase. Expiratory Puff Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. User Flag #1 User Flag #2 User Flag #3 Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm Pulse Change A sudden (user definable) change in heart rate SpO2 Drop A sudden (user definable) drop in blood oxygen saturation SD Breathing flow rate waveform L/min Mask Pressure Amount of air displaced per breath Graph displaying snore volume Minute Ventilation Amount of air displaced per minute Respiratory Rate Rate of breaths per minute Patient Triggered Breaths Percentage of breaths triggered by patient Pat. Trig. Breaths Leak Rate Rate of detected mask leakage I:E Ratio Ratio between Inspiratory and Expiratory time ratio Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting Cheyne Stokes Respiration An abnormal period of Cheyne Stokes Respiration CSR Periodic Breathing An abnormal period of Periodic Breathing Clear Airway Obstructive An apnea that couldn't be determined as Central or Obstructive. A restriction in breathing from normal, causing a flattening of the flow waveform. Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Leak Flag LF A user definable event detected by OSCAR's flow waveform processor. Perfusion Index A relative assessment of the pulse strength at the monitoring site Perf. Index % Mask Pressure (High frequency) Expiratory Time Time taken to breathe out Inspiratory Time Time taken to breathe in Respiratory Event Graph showing severity of flow limitations Flow Limit. Target Minute Ventilation Maximum Leak The maximum rate of mask leakage Max Leaks Apnea Hypopnea Index Graph showing running AHI for the past hour Total Leak Rate Detected mask leakage including natural Mask leakages Median Leak Rate Median rate of detected mask leakage Median Leaks Respiratory Disturbance Index Graph showing running RDI for the past hour Sleep position in degrees Upright angle in degrees Movement Movement detector CPAP Session contains summary data only PAP Mode Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Apnea An apnea reportred by your CPAP machine. PAP Device Mode APAP (Variable) ASV (Fixed EPAP) ASV (Variable EPAP) Height Physical Height Notes Σημειώσεις Bookmark Notes Body Mass Index How you feel (0 = like crap, 10 = unstoppable) Bookmark Start Bookmark End Last Updated Journal Notes Journal Ημερολόγιο διάφορων πράξεων 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep Brain Wave BrainWave Awakenings Number of Awakenings Morning Feel How you felt in the morning Time Awake Time spent awake Time In REM Sleep Time spent in REM Sleep Time in REM Sleep Time In Light Sleep Time spent in light sleep Time in Light Sleep Time In Deep Sleep Time spent in deep sleep Time in Deep Sleep Time to Sleep Time taken to get to sleep Zeo ZQ Zeo sleep quality measurement ZEO ZQ Debugging channel #1 Test #1 For internal use only Debugging channel #2 Test #2 Zero Upper Threshold Lower Threshold As you did not select a data folder, OSCAR will exit. or CANCEL to skip migration. Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. You cannot use this folder: Migrating files from to OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. We suggest you use this folder: Click Ok to accept this, or No if you want to use a different folder. Choose or create a new folder for OSCAR data Next time you run OSCAR, you will be asked again. The folder you chose is not empty, nor does it already contain valid OSCAR data. Data directory: Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? Question Exiting Are you sure you want to use this folder? Don't forget to place your datacard back in your CPAP machine OSCAR Reminder You can only work with one instance of an individual OSCAR profile at a time. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Loading profile "%1"... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Unable to create zip! Are you sure you want to reset all your channel colors and settings to defaults? Are you sure you want to reset all your waveform channel colors and settings to defaults? There are no graphs visible to print Would you like to show bookmarked areas in this report? Printing %1 Report %1 Report : %1 hours, %2 minutes, %3 seconds RDI %1 AHI %1 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 Entire Day's Flow Waveform Current Selection Entire Day Page %1 of %2 Days: %1 Low Usage Days: %1 (%1% compliant, defined as > %2 hours) (Sess: %1) Bedtime: %1 Waketime: %1 (Summary Only) There is a lockfile already present for this profile '%1', claimed on '%2'. Fixed Bi-Level Auto Bi-Level (Fixed PS) Auto Bi-Level (Variable PS) varies 99.5% n/a Fixed %1 (%2) Min %1 Max %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> (last night) (1 day ago) (%2 days ago) No oximetry data has been imported yet. Contec CMS50 Fisher & Paykel ICON DeVilbiss Intellipap SmartFlex Settings ChoiceMMed MD300 Respironics M-Series Philips Respironics System One ResMed S9 EPR: Somnopose Somnopose Software Zeo Personal Sleep Coach Database Outdated Please Rebuild CPAP Data (%2 min, %3 sec) (%3 sec) Pop out Graph The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View There is no data to graph d MMM yyyy [ %1 - %2 ] Hide All Events Show All Events Unpin %1 Graph Popout %1 Graph Pin %1 Graph Plots Disabled Duration %1:%2:%3 AHI %1 Relief: %1 Hours: %1h, %2m, %3s Machine Information Journal Data OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR will not touch this folder, and will create a new one instead. Please be careful when playing in OSCAR's profile folders :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. OSCAR picked only the first one of these, and will use it in future: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. CMS50F3.7 CMS50F Backing up files... Reading data files... SmartFlex Mode Intellipap pressure relief mode. Ramp Only Full Time SmartFlex Level Intellipap pressure relief level. Snoring event. SN Locating STR.edf File(s)... Cataloguing EDF Files... Queueing Import Tasks... Finishing Up... CPAP Mode VPAPauto ASVAuto iVAPS PAC Auto for Her EPR ResMed Exhale Pressure Relief Patient??? EPR Level Exhale Pressure Relief Level Response Patient View SmartStart Machine auto starts by breathing Smart Start Humid. Status Humidifier Enabled Status Humid. Level Humidity Level Temperature ClimateLine Temperature Temp. Enable ClimateLine Temperature Enable Temperature Enable AB Filter Antibacterial Filter Pt. Access Essentials Plus Climate Control Manual Soft Standard BiPAP-T BiPAP-S BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Advanced Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... Auto Mask ResMed Mask Setting Pillows Full Face Nasal Ramp Enable Weinmann SOMNOsoft2 Snapshot %1 CMS50D+ CMS50E/F Loading %1 data for %2... Scanning Files Migrating Summary File Location Loading Summaries.xml.gz Loading Summary Data Please Wait... %1 Charts %1 of %2 Charts Loading summaries Updating Statistics cache Usage Statistics Dreem Your Viatom device generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Viatom Viatom Software New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to check for updates. Please try again later. SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank SessionBar %1h %2m No Sessions Present SleepStyleLoader Import Error This Machine Record cannot be imported in this profile. The Day records overlap with already existing content. Statistics CPAP Statistics CPAP Usage Average Hours per Night Therapy Efficacy Leak Statistics Pressure Statistics Oximeter Statistics Blood Oxygen Saturation Pulse Rate %1 Median Average %1 Min %1 Max %1 %1 Index % of time in %1 % of time above %1 threshold % of time below %1 threshold Name: %1, %2 DOB: %1 Phone: %1 Email: %1 Address: This report was prepared on %1 by OSCAR %2 Days Used: %1 Low Use Days: %1 Compliance: %1% Days AHI of 5 or greater: %1 Best AHI Date: %1 AHI: %2 Worst AHI Best Flow Limitation Date: %1 FL: %2 Worst Flow Limtation No Flow Limitation on record Worst Large Leaks Date: %1 Leak: %2% No Large Leaks on record Worst CSR Date: %1 CSR: %2% No CSR on record Worst PB Date: %1 PB: %2% No PB on record Want more information? OSCAR needs all summary data loaded to calculate best/worst data for individual days. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Best RX Setting Date: %1 - %2 AHI: %1 Total Hours: %1 Worst RX Setting Most Recent Compliance (%1 hrs/day) OSCAR is free open-source CPAP report software Changes to Machine Settings No data found?!? Oscar has no data to report :( Last Week Last 30 Days Last 6 Months Last Year Last Session Details No %1 data available. %1 day of %2 Data on %3 %1 days of %2 Data, between %3 and %4 Days Pressure Relief Pressure Settings Machine Information First Use Last Use Welcome Welcome to the Open Source CPAP Analysis Reporter What would you like to do? CPAP Importer Oximetry Wizard Daily View Overview Statistics Στατιστικά <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> It would be a good idea to check File->Preferences first, as there are some options that affect import. Note that some preferences are forced when a ResMed machine is detected First import can take a few minutes. The last time you used your %1... last night %2 days ago was %1 (on %2) %1 hours, %2 minutes and %3 seconds Your machine was on for %1. <font color = red>You only had the mask on for %1.</font> under over reasonably close to equal to You had an AHI of %1, which is %2 your %3 day average of %4. Your CPAP machine used a constant %1 %2 of air Your pressure was under %1 %2 for %3% of the time. Your machine used a constant %1-%2 %3 of air. Your EPAP pressure fixed at %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Your EPAP pressure was under %1 %2 for %3% of the time. Your machine was under %1-%2 %3 for %4% of the time. 1 day ago Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. gGraph %1 days gGraphView 100% zoom level Restore X-axis zoom to 100% to view entire selected period. Restore X-axis zoom to 100% to view entire day's data. Reset Graph Layout Resets all graphs to a uniform height and default order. Y-Axis Plots CPAP Overlays Oximeter Overlays Dotted Lines Double click title to pin / unpin Click and drag to reorder graphs Remove Clone Clone %1 Graph OSCAR-code-v1.3.1/Translations/Deutsch.de.ts000066400000000000000000015021051417327530600205140ustar00rootroot00000000000000 AboutDialog Sorry, could not locate About file. Leider konnte die Über OSCAR-Datei nicht gefunden werden. Close Beenden &About &Über GPL License GPL Lizenz Sorry, could not locate Credits file. Entschuldigung, die Credits-Datei konnte nicht gefunden werden. Important: Wichtig: Credits Verdienst To see if the license text is available in your language, see %1. Informationen dazu, ob der Lizenztext in Ihrer Sprache verfügbar ist, finden Sie unter %1. Release Notes Versionshinweise Show data folder Datenordner anzeigen Sorry, could not locate Release Notes. Sorry, konnte die Release Notes nicht finden. As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Da es sich um eine Vorabversion handelt, wird empfohlen, dass Sie <b>Ihre Daten manuell sichern</b> bevor Sie fortfahren, denn der Versuch die Daten später zurückzuholen, kann scheitern. About OSCAR %1 Über OSCAR %1 OSCAR %1 OSCAR %1 CMS50F37Loader Could not find the oximeter file: Die Oxymeter-Datei konnte nicht gefunden werden: Could not open the oximeter file: Die Oxymeter-Datei konnte nicht geöffnet werden: CMS50Loader Could not get data transmission from oximeter. Keine Datenübertragung vom Oxymeter möglich. Please ensure you select 'upload' from the oximeter devices menu. Bitte stellen Sie sicher, dass Sie den'Upload' aus dem Menü des Oxymeter Gerätes auswählt haben. Beim PO400 geht das automatisch. Could not find the oximeter file: Die Oxymeter-Datei konnte nicht gefunden werden: Could not open the oximeter file: Die Oxymeter-Datei konnte nicht geöffnet werden: CheckUpdates Checking for newer OSCAR versions Prüfung auf neuere OSCAR-Versionen Daily B B u u Big Groß End Ende UF1 UF1 UF2 UF2 Oximetry Sessions Oxymeter Sitzung Color Farbe Flags Flags Notes Aufzeichnungen Small Klein Start Start PAP Mode: %1 PAP Modus: %1 I'm feeling ... Ich fühle mich ... Journal Journal Total time in apnea Gesamtzeit des Apnoe Position Sensor Sessions Position Sensor Sitzungen Add Bookmark Neues Lesezeichen Remove Bookmark Lesezeichen entfernen Pick a Colour Wählen Sie eine Farbe Complain to your Equipment Provider! Beschweren Sie sich bei Ihren Anbieter! Session Information Sitzungsinformationen Sessions all off! Alle Sitzungen schließen! %1 event %1 Ereignis Go to the most recent day with data records Zum letzten Tag mit Datensätzen Machine Settings Geräteeinstellungen Sorry, this machine only provides compliance data. Tut mir leid, dieses Gerät liefert nur Compliance-Daten. B.M.I. B.M.I. Sleep Stage Sessions Schlafstadium Sitzungen Oximeter Information Oxymeter Informationen Events Ereignisse Graphs Diagramme CPAP Sessions CPAP Sitzung Medium Mittel Starts Startet Weight Gewicht Zombie Nicht gut Bookmarks Lesezeichen Session End Times Sitzungsendzeit enable aktivieren %1 events %1 Ereignisse events Ereignisse BRICK :( BLOCK :( Event Breakdown Ereignis Pannen Click to %1 this session. Klicken Sie auf %1 dieser Sitzung. SpO2 Desaturations SpO2 Entsättigungen 10 of 10 Event Types 10 von 10 Event-Typen This CPAP machine does NOT record detailed data Dieses CPAP-Gerät zeichnet KEINE detaillierten Daten auf "Nothing's here!" "Hier ist nichts!" 10 of 10 Graphs 10 von 10 Grafiken %1h %2m %3s %1h %2m %3s Awesome Sehr gut Pulse Change events Pulsereignis ändern SpO2 Baseline Used SpO2-Baseline verwendet Zero hours?? Null-Stunden?? Go to the previous day Zum vorherigen Tag Details Details Time over leak redline Zeit über Leck rote Linie disable deaktivieren Bookmark at %1 Lesezeichen bei %1 Statistics Statistiken Breakdown Aufschlüsselung Unknown Session Unbekannte Sitzung Sessions exist for this day but are switched off. Sitzungen existieren heute, sind aber ausgeschaltet. Model %1 - %2 Model %1 - %2 Duration Dauer View Size Größen Impossibly short session Sehr kurze Sitzung %1 Session #%2 %1 Sitzung #%2 Show/hide available graphs. Verfügbare Diagramme anzeigen/ausblenden. No %1 events are recorded this day Keine %1 Ereignisse werden an diesem Tag aufgezeichnet Show or hide the calender Ein/Aus Kalender Time outside of ramp Außerhalb der Rampenzeit Unable to display Pie Chart on this system Das Kreisdiagramm kann auf diesem System nicht angezeigt werden Total ramp time Gesamte Rampenzeit This day just contains summary data, only limited information is available. Dieser Tag enthält nur zusammenfassende Daten. Es stehen nur begrenzte Informationen zur Verfügung. Time at Pressure Zeit in Druck Go to the next day Gehen Sie auf den nächsten Tag Session Start Times Sitzungsstartzeit No data is available for this day. Für diesen Tag sind keine Daten verfügbar. If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Wenn die Größe im Einstellungsdialog größer als Null ist, zeigt die Gewichtseinstellung hier den Wert des Body Mass Index (BMI) an <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Bitte beachten Sie:</b> Alle nachfolgend dargestellten Einstellungen basieren auf der Annahme, dass sich gegenüber den Vortagen nichts geändert hat. This bookmark is in a currently disabled area.. Dieses Lesezeichen befindet sich in einem derzeit deaktivierten Bereich. (Mode and Pressure settings missing; yesterday's shown.) (Modus- und Druckeinstellungen fehlen; die von gestern werden gezeigt.) 99.5% 99.5% ExportCSV AHI AHI End Ende Date Datum End: Ende: Quick Range: Zeitspanne: Daily Täglich Event Ereignis Start Start Last Fortnight Letzten Vierzehn Tage Most Recent Day Neuste Tag Count Land Filename: Dateiname: Select file to export to Wählen Sie die Datei, um zu Exportieren Resolution: Auflösung: Cancel Aufheben Dates: Termine: Custom In Gebrauch Export Export Start: Start: Data/Duration Daten/Dauer CSV Files (*.csv) CSV Dateien (*.csv) Last Month Letzter Monat Last 6 Months Letzten 6 Monate Total Time Gesamte Zeit DateTime Datum-Zeit Session Count Sitzungs Anzahl Session Sitzung Everything Alles Last Week Letzte Woche Last Year Letztes Jahr Export as CSV Export in CSV Datei Sessions_ Sitzungen_ Details Details Summary_ Zusammenfassung_ Details_ Details_ Sessions Sitzungen FPIconLoader This Machine Record cannot be imported in this profile. Dieser Geräte-Datensatz kann in diesem Profil nicht importiert werden. Import Error Import Fehler The Day records overlap with already existing content. Die Aufzeichnungen dieses Tages überschneiden sich mit bereits vorhandenen Inhalt. Help No Nein Index Index clear klar HelpEngine did not set up correctly Hilfe Anwendung wurde nicht richtig eingerichtet Help files do not appear to be present. Hilfedateien scheinen nicht vorhanden zu sein. HelpEngine could not register documentation correctly. Die Hilfe Anwendung konnte die Dokumentation nicht korrekt registrieren. Help Files are not yet available for %1 and will display in %2. Hilfedateien sind noch nicht verfügbar für %1 und werden angezeigt in %2. Hide this message Verberge diese Nachricht Please wait a bit.. Indexing still in progress Bitte warten Sie etwas.. Die Indizierung läuft noch Search Suche Contents Inhalt %1 result(s) for "%2" %1 Resultat(e) für "%2" No documentation available Keine Dokumentation verfügbar Search Topic: Suchthema: MD300W1Loader Could not find the oximeter file: Die Oxymeter-Datei konnte nicht gefunden werden: Could not open the oximeter file: Die Oxymeter-Datei konnte nicht geöffnet werden: MainWindow Exit Beenden Help Hilfe Please insert your CPAP data card... Bitte benutzen Sie Ihre CPAP-Datenkarte... Daily Calendar Kalender täglich &Data &Daten &File &Datei &Help &Hilfe &View &Ansicht E&xit &Schließen Daily Täglich Loading profile "%1" Profil laden "%1" Import &ZEO Data Import &ZEO Daten MSeries Import complete M-Serie komplett Importiert There was an error saving screenshot to file "%1" Es gab einen Fehler beim Speichern des Screenshot in eine Datei "%1" Choose a folder Wählen Sie einen Ordner A %1 file structure for a %2 was located at: Eine%1 Dateistruktur für eine %2 wurde in: Importing Data Importieren von Daten Online Users &Guide Online &Handbuch View &Welcome &Willkommensansicht Show Performance Information Anzeige der Performance- Informationen There was a problem opening MSeries block File: Es gab ein Problem beim Öffnen einer M-Serie Block-Datei: Current Days Aktueller Tag &About &Über View &Daily &Tagesansicht View &Overview &Übersichtsansicht Access to Preferences has been blocked until recalculation completes. Zugriff auf Einstellungen wurde blockiert, bis die Neuberechnung abgeschlossen ist. Import RemStar &MSeries Data Import REMSTAR &M-Serie Daten For some reason, OSCAR does not have any backups for the following machine: Aus irgendeinem Grund verfügt OSCAR über keine Sicherungen für das folgende Gerät: Daily Sidebar Randleiste täglich Note as a precaution, the backup folder will be left in place. Als Vorsichtsmaßnahme werden die Backup Ordner an Ort und Stelle belassen. A file permission error casued the purge process to fail; you will have to delete the following folder manually: Ein Fehler bei der Dateiberechtigung hat dazu geführt, dass der Bereinigungsprozess fehlgeschlagen ist. Sie müssen den folgenden Ordner manuell löschen: Change &User &Benutzer ändern %1's Journal %1's Journal Import Problem Importproblem <b>Please be aware you can not undo this operation!</b> <b>Bitte beachten Sie, dass Sie diesen Vorgang nicht rückgängig machen können!</b> View S&tatistics Statistik &anzeigen Monthly Monatlich Change &Language &Sprache auswählen &About OSCAR &Über OSCAR Import Import Because there are no internal backups to rebuild from, you will have to restore from your own. Es existiert keine interne Datensicherung. Sie müssen Ihre eigene verwenden. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Möchten Sie jetzt Ihr eigenes Backup importieren? (Es wird nichts angezeigt bevor Sie nicht Ihre Daten einspielen) Please wait, importing from backup folder(s)... Bitte warten, Import von Backup-Ordner (n)... Are you sure you want to delete oximetry data for %1 Sind Sie sicher, dass Sie die Oxymetriedaten löschen möchten %1 O&ximetry Wizard O&xymetrie Assistent Bookmarks Lesezeichen Right &Sidebar &Seitenleiste rechts Rebuild CPAP Data Wiederherstellung der CPAP Daten XML Files (*.xml) XML Datei (*.xml) The FAQ is not yet implemented Die FAQ ist noch nicht implementiert Export review is not yet implemented Die Exportprüfung ist noch nicht implementiert Are you sure you want to rebuild all CPAP data for the following machine: Sind Sie sicher, dass Sie alle CPAP-Daten für das folgende Gerät neu erstellen möchten?: Report an Issue Ein Problem melden Date Range Datumsbereich View Statistics Statistiken anzeigen CPAP Data Located CPAP-Daten liegen an Access to Import has been blocked while recalculations are in progress. Der Zugang zu Import wurde blockiert, während Neuberechnungen im Gange sind. Sleep Disorder Terms &Glossary Schlafstörungen Nutzungswörterbuch &Wörterverzeichnis Are you really sure you want to do this? Sind Sie wirklich sicher, dass Sie das tun wollen? Select the day with valid oximetry data in daily view first. Wählen Sie zuerst den Tag mit gültigen Oximetriedaten in der Tagesansicht aus. Purge Oximetry Data Oxymetriedaten löschen Records Zusammenfassung Use &AntiAliasing Verwenden Sie &Antialiasing Would you like to import from this location? Möchten Sie von diesem Ort impoertieren? Report Mode Report Modus &Profiles &Profile Profiles Profile CSV Export Wizard CSV-Export-Assistent &Automatic Oximetry Cleanup &Automatische Bereinigung der Oxymetrie Import is already running in the background. Import läuft bereits im Hintergrund. Specify einzeln Ausführen Standard Standard No help is available. Es ist keine Hilfe verfügbar. Statistics Statistiken Up to date Neuster Stand Please open a profile first. Bitte öffnen Sie zuerst ein Profil. &Statistics &Statistiken Backup &Journal Sicherungskopie &Journal Imported %1 CPAP session(s) from %2 Importiert %1 CPAP-Sitzung(en) von %2 Reporting issues is not yet implemented Berichterstattungsprobleme sind noch nicht implementiert Purge &Current Selected Day Bereinigen, &Aktualisieren des aktuell ausgewählten Tages Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Vorausgesetzt, Sie haben <i><b>eigene </b> Backups für ALLE Ihre CPAP-Daten</i>, die Sie noch vervollständigen können erstellt. Aber Sie müssen die Backups manuell wiederherstellen. &Advanced &Fortgeschrittene Print &Report &Drucken Couldn't find any valid Machine Data at %1 Hier konnten keine gültigen Gerätedaten gefunden werden %1 Export for Review Export für Bewertung Take &Screenshot &Bildschirmverwaltung Overview Übersicht Show Debug Pane Debug-Fenster anzeigen &Edit Profile &Profil bearbeiten Import Reminder Import Erinnerung Help Browser Hilfe Browser If you can read this, the restart command didn't work. You will have to do it yourself manually. Wenn Sie dies lesen können, hat der Neustartbefehl nicht funktioniert. Sie müssen es manuell tun. Exp&ort Data Exp&ort Daten Welcome Willkommen Import &Somnopose Data Import &CSV Daten Screenshot saved to file "%1" Screenshot Datei gespeichert "%1" &Preferences &Einstellungen You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Du bist dabei <font size=+2>zu löschen</font> OSCAR-Gerätedatenbank für folgende Geräte:</p> Are you <b>absolutely sure</b> you want to proceed? Sind Sie <b>absolut sicher</b> das Sie fortfahren möchten? Import Success Erfolgreicher Import Choose where to save journal Wählen, wo das Blatt gespeichert werden soll &Frequently Asked Questions &Häufig gestellte Fragen Oximetry Oxymetrie A %1 file structure was located at: Eine%1 Dateistruktur befindet sich unter: Change &Data Folder &Datenordner ändern Navigation Navigation Already up to date with CPAP data at %1 Bereits aktuelle CPAP Daten %1 No profile has been selected for Import. Es wurde kein Profil für den Import ausgewählt. Please note, that this could result in loss of data if OSCAR's backups have been disabled. Bitte beachten Sie, dass dies zu Datenverlust führen kann, wenn die Backups von OSCAR deaktiviert wurden. &Maximize Toggle &Maximieren des Umschalters The User's Guide will open in your default browser Das Benutzerhandbuch wird in Ihrem Standardbrowser geöffnet The Glossary will open in your default browser Das Glossar wird in Ihrem Standardbrowser geöffnet Show Daily view Tagesansicht anzeigen Show Overview view Übersichtsansicht anzeigen Maximize window Fenster maximieren Reset sizes of graphs Größen von Diagrammen zurücksetzen Show Right Sidebar Rechte Seitenleiste anzeigen Show Statistics view Statistikansicht anzeigen Show &Line Cursor Zeigt &Cursorlinie an Show Daily Left Sidebar Tägliche Ansicht in linker Sidebar anzeigen Show Daily Calendar Tageskalender anzeigen System Information Systeminformationen Show &Pie Chart Torten -&Diagramm anzeigen Show Pie Chart on Daily page Tortendiagramm auf der Tagesseite anzeigen OSCAR Information OSCAR Information &Reset Graphs &Grafiken zurücksetzen Reset Graph &Heights Graph &Höhen zurücksetzen Standard graph order, good for CPAP, APAP, Bi-Level Standard-Graphenanordnung, gut für CPAP, APAP, Bi-Level Advanced Erweitert Advanced graph order, good for ASV, AVAPS Erweiterte Graphenanordnung, gut für ASV, AVAPS Troubleshooting Fehlerbehebung Purge ALL Machine Data Alle Gerätedaten bereinigen &Import CPAP Card Data &CPAP-Kartendaten importieren Import &Dreem Data Import &Dreem Daten Create zip of CPAP data card Zip der CPAP-Datenkarte erstellen Create zip of all OSCAR data Zip von allen OSCAR-Daten erstellen %1 (Profile: %2) %1 (Profil: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Bitte denken Sie daran, den Stammordner oder Laufwerksbuchstaben Ihrer Datenkarte zu wählen und nicht einen Ordner darin. Choose where to save screenshot Wählen Sie, wo der Screenshot gespeichert werden soll Image files (*.png) Bilddateien (*.png) OSCAR does not have any backups for this machine! OSCAR hat keie Backup für dieses Gerät! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Wenn Sie nicht <i>eigene <b>own</b> Backups für ALLE Ihre Daten für dieses Gerät</i>, <font size=+2>werden Sie die Daten dieses Geräts<b>permanent</b>!</verlieren> Would you like to zip this card? Möchten Sie diese Karte verschließen? Choose where to save zip Wählen Sie, wo die Zip-Datei gespeichert werden soll ZIP files (*.zip) ZIP Dateien (*.zip) Creating zip... Zip erstellen... Calculating size... Größe berechnen... Show Personal Data Persönliche Daten anzeigen Create zip of OSCAR diagnostic logs Zip von OSCAR-Diagnoseprotokollen erstellen Check For &Updates Nach &Updates suchen Check for updates not implemented Prüfung auf nicht implementierte Updates Import &Viatom/Wellue Data Import &Viatom/Wellendaten Purge Current Selected Day Aktuellen ausgewählten Tag bereinigen &CPAP &CPAP &Oximetry &Oxymetrie &Sleep Stage &Schlafphase &Position &Position &All except Notes &Alle außer Anmerkungen All including &Notes Alle einschließlich &Notizen Find your CPAP data card Finden Sie Ihre CPAP-Datenkarte There was a problem opening %1 Data File: %2 Es gab ein Problem beim Öffnen von %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Datenimport von %2 Datei(en) abgeschlossen %1 Import Partial Success %1 Teilweise erfolgreicher Import %1 Data Import complete %1 Datenimport abgeschlossen MinMaxWidget Scaling Mode Skalierungsmodus The Maximum Y-Axis value.. Must be greater than Minimum to work. Um damit zu arbeiten, muss der max. Y-Achsen Wert größer sein als der minimale Wert. This button resets the Min and Max to match the Auto-Fit Diese Schaltfläche setzt die Automatische Anpassung für Min. und Max The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Der Y-Achsen Skalierungsmodus "Automatische Anpassung", für die automatische Skalierung, sind Vorgaben vom Hersteller. Die "Übersteuerung" können Sie selbst wählen. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Wenn Sie es wünschen kann der Y-Achsen-Mindestwert eine negative Zahl sein. Defaults Standardwerte Auto-Fit Automatische Anpassung Override Übersteuerung NewProfile ASV ASV APAP APAP CPAP CPAP Male Männlich &Back &Zurück &Next &Weiter TimeZone Zeitzone Email E-mail Phone Telefon Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Alle Berichte die erzeugt werden, sind nur zum PERSÖNLICHEN GEBRAUCH und in keiner Weise für die Einhaltung medizinischer Diagnosezwecke geeignet. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR ist urheberrechtlich geschützt &copy;2011-2018 Mark Watkins und Teile &copy;2019-2022 Das OSCAR-Team &Close this window &Fenster schließen Edit User Profile Benutzerprofil bearbeiten The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Die Autoren haften nicht für <u>irgendetwas</u> im Zusammenhang mit der Verwendung oder dem Missbrauch dieser Software. Please provide a username for this profile Bitte geben Sie einen Benutzernamen für dieses Profil an CPAP Treatment Information CPAP-Behandlungsinformationen Password Protect Profile Profilpasswort OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR ist lediglich als Datenanzeige gedacht und ersetzt keinesfalls die kompetente medizinische Anleitung Ihres Arztes. OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR wurde frei veröffentlicht unter <a href='qrc:/COPYING'>GNU Public License v3</a>, und kommt ohne Gewähr und ohne irgendwelche Ansprüche auf Eignung für irgendeinen Zweck. Accuracy of any data displayed is not and can not be guaranteed. Für die Korrektheit der Daten kann nicht garantiert werden. D.O.B. Geb. Dat. Female Weiblich Gender Geschlecht Height Größe Contact Information Kontaktinformationen Locale Settings Ländereinstellungen CPAP Mode CPAP Modus Select Country Land wählen PLEASE READ CAREFULLY BITTE LESEN SIE Untreated AHI Unbehandelter AHI Address Adresse I agree to all the conditions above. Ich bin mit allen oben genannten Bedingungen einverstanden. DST Zone Automatische Sommerzeit about:blank Leere Seite RX Pressure RX Druck Password Passwort Use of this software is entirely at your own risk. Die Nutzung der Software erfolgt auf eigene Gefahr. Passwords don't match Passwörter stimmen nicht überein First Name Vorname Last Name Nachname Country Land &Cancel &Schließen &Finish &Ende Bi-Level Bi-Level Profile Changes Profiländerungen Personal Information (for reports) Persönliche Informationen (für Berichte) User Name Benutzername <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Manchmal ist ein biologisches (Geburts-) Geschlecht erforderlich, um die Genauigkeit einiger Berechnungen zu verbessern. Lassen Sie dieses Feld leer können Sie alles überspringen.</p></body></html> This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Diese Software wird entwickelt, um Sie bei der Überprüfung der von Ihrem CPAP-Geräte und zugehörige Ausrüstung erzeugten Daten zu unterstützen. User Information Benutzerinformationen ...twice... ...wiederholen... Doctors Name Name des Arztes It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Dies ist jedoch erforderlich, um die Genauigkeit bestimmter Berechnungen zu verbessern. Doctors / Clinic Information Name des Arztes in der Klinik Practice Name Name der Praxis Date Diagnosed Diagnosedatum Accept and save this information? Akzeptieren und speichern Sie diese Informationen? Patient ID Patient-ID Welcome to the Open Source CPAP Analysis Reporter Willkommen beim Open Source CPAP Analysis Reporter Metric Metrisch English Englisch Very weak password protection and not recommended if security is required. Sehr schwacher Passwortschutz und nicht empfehlenswert, wenn Sicherheit erforderlich ist. Overview End: Ende: Usage Verwendung Respiratory Disturbance Index Atem- Störung Index 10 of 10 Charts 10 von 10 Diagrammen Show all graphs Alle Diagramme zeigen Reset view to selected date range Ansicht auf den ausgewählten Datumsbereich zurücksetzen Total Time in Apnea Gesamtzeit im Apnoe Drop down to see list of graphs to switch on/off. Drop-Down-Liste, Diagramme, Ein/Ausschalten. Usage (hours) Verwendung (Stunden) Last Three Months Letzten 3 Monate Total Time in Apnea (Minutes) Gesamtzeit im Apnoe (Minuten) Custom Gebrauch How you felt (0-10) Wie fühlen Sie sich? (0-10) Graphs Diagramme Range: Zeitraum: Start: Start: Last Month Letzter Monat Apnea Hypopnea Index Apnoe Hypopnoe Index Last 6 Months Letzten 6 Monate Body Mass Index Body Masse Index Session Times Anwendungszeit Last Two Weeks Letzten zwei Wochen Everything Alles Last Week Letzte Woche Last Year Letztes Jahr Toggle Graph Visibility Umschalten Sichtbarkeit Diagramm Hide all graphs Alle Diagramme zeigen Last Two Months Letzten 2 Monate Snapshot Schnappschuss OximeterImport <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Wer mehrere verschiedene Oxymeter benutzt, muss die Aktualisierung der Geräteerkennung ermöglichen.</p></body></html> Live Oximetry import has been stopped Der Live-Oxymetrie-Import wurde gestoppt Press Start to commence recording Drücken Sie Start, um die Aufnahme zu beginnen Close Beenden No CPAP data available on %1 Keine CPAP Daten auf%1 verfügbar It also can read from ChoiceMMed MD300W1 oximeter .dat files. Es kann auch von Choicemmed MD300W1 Oxymeter DAT-Dateien gelesen werden. Please wait until oximeter upload process completes. Do not unplug your oximeter. Bitte warten Sie, bis der Oxymeter Upload-Vorgang abgeschlossen ist. Das Oxymeter nicht trennen. Finger not detected Kein Fingerchlip angeschlossen You need to tell your oximeter to begin sending data to the computer. Sie müssen im Menü Ihres Oxymeters den Upload starten. No Oximetry module could parse the given file: Kein Oxymetriemodul kann die angegebene Datei analysieren: Renaming this oximeter from '%1' to '%2' Dieses Oxymeter umbenennen aus '%1' to '%2' <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Diese Option ermöglicht den Import von Dateien, welche durch Software von SpO2 Review erzeugt wurde.</p></body></html> Oximeter import completed.. Oxymeterdatenimport abgeschlossen.. &Retry &Wiederholen &Start &Start You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Sie werden darauf hingewiesen, dass andere Unternehmen, wie Pulox identisch mit dem CMS50 sind. (wie der Pulox PO-200, PO-300, PO-400). Diese sollten auch funktionieren. %1 session(s) on %2, starting at %3 %1 Sitzung(en) an %2, Starten ab %3 I need to set the time manually, because my oximeter doesn't have an internal clock. Ich muss die Zeit manuell einstellen, denn mein Oxymeter hat keine eigebaute Uhr. You can manually adjust the time here if required: Falls erforderlich, können Sie hier die Zeit manuell einstellen: OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. Mit OSCAR können Sie neben CPAP-Sitzungsdaten auch Oxymetriedaten nachverfolgen, wodurch Sie wertvolle Einblicke in die Wirksamkeit der CPAP-Behandlung erhalten. Es funktioniert auch eigenständig mit Ihrem Pulsoxymeter, sodass Sie Ihre aufgezeichneten Daten speichern, verfolgen und überprüfen können. Oximeter Session %1 Oxymetersitzung %1 Couldn't access oximeter Konnte nicht auf das Oxymeter zugreifen Please choose which one you want to import into OSCAR Bitte wählen Sie aus, welche Sie in OSCAR importieren möchten Please connect your oximeter device Bitte verbinden Sie Ihr Oxymeter-Gerät Important Notes: Wichtige Hinweise: Starting up... Sarten Sie... Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Contec CMS50D + Geräte verfügen über keine interne Uhr und notieren keine Startzeit. Hier müssen Sie die Startzeit manuell eingeben, nachdem der Importvorgang abgeschlossen ist. HH:mm:ssap HH:mm:ssap Import directly from a recording on a device Importieren direkt aus einer Aufzeichnung auf einem Gerät Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Oxymeter Name ist anders. Wenn Sie mit mehreren Profilen arbeiten setzen Sie den Namen auf allen Profilen gleich. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Auch bei Geräten mit einer internen Uhr wird empfohlen die CPAP-Sitzung gleichzeitig mit der Oxymetrie-Sitzung zu beginnen. Manche CPAP Geräte neigen mit der Zeit dazu ungenaue Zeitdaten zu liefern. &Information Page &Informationsseite I want to use the time reported by my oximeter's built in clock. Ich will die Zeit von meiner im Oxymeter eingebauten Uhr verwenden. Live Oximetry Stopped Live-Oxymetrie gestoppt Waiting for %1 to start Warten auf %1 zu starten <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Erinnerung für CPAP Benutzer: </span><span style=" color:#fb0000;">Haben Sie daran gedacht erst Ihre CPAP Daten zu importieren?<br/></span>Bitte versuchen Sie immer die CPAP Sitzung gleichzeitig mir der Oxymetrie Sitzung zu starten.</p></body></html> Select a valid oximetry data file Wählen Sie eine gültige Oxymetriedatendatei aus %1 device is uploading data... %1 Gerät Hochladen von Daten... &End Recording &Aufnahme Beenden CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. CMS50E / F-Benutzer: Wenn Sie direkt importieren, wählen Sie bitte keinen Upload auf Ihrem Gerät aus, bis Sie von OSCAR dazu aufgefordert werden. &Choose Session &Wählen Sie die Sitzung Nothing to import Nichts zu importieren Select upload option on %1 Wählen Sie eine Uploadfunktion %1 Waiting for the device to start the upload process... Warten auf das Gerät, um den Upload-Vorgang zu starten... Could not detect any connected oximeter devices. Konnte keine angeschlossenen Oxymeter Geräte erkennen. Oximeter Import Wizard Oxymeter Import-Assistent <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Hinweis: </span><span style=" font-style:italic; ">Wählen Sie zuerst Ihren richtigen Oximetertyp aus dem Pulldown-Menü unten aus.</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">Wählen Sie ZUERST Ihr Oximeter aus diesen Gruppen aus:</span></p></ body></html> Day recording (normally would have) started Tagesaufzeichnung (hätte normalerweise) begonnen &Save and Finish &Speichern und Beenden Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Pulsoxymeter sind medizinische Geräte und werden zur Feststellung der Sauerstoffsättigung im Blut benutzt. Bei längeren Apnea Ereignissen und abnormalen Atemmustern, kann die Blutsauerstoffsättigung deutlich sinken. Dann sollten Sie Ihren Arzt informieren. For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Damit OSCAR direkt von Ihrem Oxymeter-Gerät aus suchen und lesen kann, müssen Sie sicherstellen, dass die richtigen Gerätetreiber (z. B. USB zu Serial UART) auf Ihrem Computer installiert sind. Weitere Informationen dazu, %1klick hier%2. Start Time Startzeit <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR benötigt eine Startzeit, um zu wissen, wo die Oxymetriesitzung gespeichert werden soll.</p><p>Wählen Sie eine der folgenden Optionen:</p></body></html> Pulse Rate Pulsrate <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Hinweis: Synchronisieren Sie die CPAP-Sitzungs- Startzeit. Danach wird das Ergebnis immer genauer sein.</p></body></html> Set device date/time Datum/Uhrzeit im Gerät gesetzt Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Oxymetriedateien (*.spo *.spor *.spo2 *.SpO2 *.dat) Import Completed. When did the recording start? Import abgeschlossen. Wann hat die Aufnahme zu starten? Import from a datafile saved by another program, like SpO2Review Import aus einer Datendatei von einem anderen Programm, welche von SpO2Review gespeichert wurden Multiple Sessions Detected Mehrere Sitzungen erkannt I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Ich begann diese Oxymeter Aufnahme zur gleichen Zeit (oder nahe der Zeit) wie eine Session auf meinem CPAP-Gerät. Record attached to computer overnight (provides plethysomogram) Waren Sie über Nacht an den PC angeschlossen benutzen Sie das (Plethysonogramm) Erase session after successful upload Löschen der Sitzung nach erfolgreichem Upload Live Oximetry Mode Live-Oxymetriemodus &Cancel &Schließen Set device identifier Geräteidentifizierung Details Details <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Wenn aktiviert, setzt OSCAR die interne Uhr des CMS50 automatisch auf die aktuelle Uhrzeit Ihres Computers zurück.</p></body></html> Oximeter not detected Kein Oxymeter angeschlossen Please remember: Bitte beachten Sie: Where would you like to import from? Von wo wollen Sie importieren? If you can read this, you likely have your oximeter type set wrong in preferences. Wenn Sie das lesen, haben Sie nicht das richtige Oxymeter in den Einstellungen gewählt. I want to use the time my computer recorded for this live oximetry session. Ich möchte die Zeit von meinem Computer für diese Live-Oxymetrie-Sitzung benutzen. Scanning for compatible oximeters Scannen von kompatieblen Oxymetern Please connect your oximeter, enter it's menu and select upload to commence data transfer... Bitte schließen Sie Ihr Oxymeter an und wählen Sie im Menü Upload mit Datenübertragung beginnen... Choose CPAP session to sync to: Wählen Sie aus um die CPAP-Sitzung zu synchronisieren: Duration Dauer Welcome to the Oximeter Import Wizard Herzlich Willkommen beim Oxymeter-Import-Assistenten <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Sollten Sie das Pulsoxymeter über Nacht an einem PC betreiben, kann diese Option sehr nützlich sein um Herzrhythmusstörungen zu erkennen.</p></body></html> If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Wenn Sie versuchen, Oxymetrie- und CPAP-Daten zu synchronisieren, stellen Sie sicher, dass Sie Ihre CPAP-Sitzungen zuerst importiert haben, bevor Sie fortfahren! "%1", session %2 "%1", Sitzung %2 Show Live Graphs Live-Diagramme anzeigen Live Import Stopped Live Import stoppen Oximeter Starting time Oxymeter Startzeit Skip this page next time. Überspringen Sie diese Seite das nächste Mal. If you can still read this after a few seconds, cancel and try again Sollte der Vorgang zu lange dauern, starten Sie Ihn nach ein paar Sekunden erneut CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 &Sync and Save &Sync und Speichern Your oximeter did not have any valid sessions. Ihre Oxymeter hatten keine gültigen Sitzungen. Something went wrong getting session data Ein Fehler ist immer wenn Sitzungs-Daten nicht übereinstimmen Connecting to %1 Oximeter Anschließen an ein %1 Oxymeter Recording... Aufnahme... ChoiceMMed MD300W1 ChoiceMMed MD300W1 OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR ist derzeit kompatibel mit den seriellen Oximetern Contec CMS50D+, CMS50E, CMS50F und CMS50I.<br/>(Hinweis: Der direkte Import aus Bluetooth-Modellen ist möglich. <span style=" font-weight:600;">wahrscheinlich noch nicht</span> möglich) CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>Hier können Sie einen 7-stelligen Namen für dieses Oximeter eingeben.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Diese Option löscht die importierte Sitzung aus Ihrem Oximeter, nachdem der Import abgeschlossen ist. </p><p>Seien Sie vorsichtig, denn wenn etwas schief geht, bevor OSCAR Ihre Sitzung speichert, können Sie es nicht zurückbekommen.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Mit dieser Option können Sie (per Kabel) interne Aufzeichnungen aus dem Oximeter importieren.</p><p>Nachdem Sie diese Option ausgewählt haben, müssen Sie bei alten Contec-Oximetern das Menü des Geräts verwenden, um den Upload zu starten.</p></body></html> Please connect your oximeter device, turn it on, and enter the menu Bitte schließen Sie Ihr Oximetergerät an, schalten Sie es ein und öffnen Sie das Menü Oximetry Date Datum Pulse Puls &Open .spo/R File &Öffnen .spo/R Datei R&eset R&eset Serial &Import Serien &Import Serial Port Serien Port d/MM/yy h:mm:ss AP d/MM/yy h:mm:ss AP &Start Live &Start-Live &Rescan Ports &Ports neu scannen PreferencesDialog s s &Ok &OK AHI Apnea Hypopnea Index AHI RDI Respiratory Disturbance Index RDI Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Das Abschalten von Sicherungen ist keine gute Idee, da OSCAR diese benötigt, um die Datenbank neu zu erstellen, falls Fehler gefunden werden. bpm bpm Graph Height Diagrammhöhe Flag Flag Font Schriftart Name Name Size Größe Span Spannweite %1 %2 %1 %2 &CPAP &CPAP General Settings Allgemeine Einstellungen <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p> Dies erleichtert das Scrollen beim Vergrößern auf empfindlichen bidirektionalen TouchPads</p><p>50ms ist der empfohlenen Wert.</p></body></html> Color Farbe Daily Täglich Event Duration Sitzungsdauer Hours Stunden Label Aufschrift Lower untere Never Niemals Oximetry Settings Oxymetrieeinstellungen Pulse Puls Graphics Engine (Requires Restart) Grafikanwendung (neu starten) Upper obere days. tägl. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sitzungen die kürzer als Diese sind werden nicht angezeigt<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Hier können Sie einstellen <b>obere</b> Schwelle für bestimmte Berechnungen welche die Wellenform verwendet %1 After Import Nach dem Import Ignore Short Sessions Ignorieren von kurzen Sitzungen Sleep Stage Waveforms Schlafstadium-Wellenform Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Prozentualer Anteil der Einschränkung des Luftstroms aus dem Medianwert. Ein Wert von 20% eignet sich gut zum Nachweis von Apnoen. Sessions starting before this time will go to the previous calendar day. Sitzungen vor dieser Zeit werden auf den voangegangenen Tag genommen. Session Storage Options Sitzungs Speicher Optonen Show flags for machine detected events that haven't been identified yet. Erfasste Ereignisse vom Gerät, die noch nicht identifiziert wurden. Graph Titles Diagrammtitel Zero Reset Nullsetzung <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Individuelle Markierungen ist ein experimentelles Verfahren zum Nachweis von Ereignissen, die von dem Gerät ausgehen. Sie sind <span style=" text-decoration: underline;">nicht</span> im AHI enthalten.</p></body></html> A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Ein Daten-Dekomprimierungsvorgang ist erforderlich, um diese Änderungen anzuwenden. Dieser Vorgang kann einige Minuten dauern. Möchten Sie diese Änderungen wirklich vornehmen? Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Aktivieren/Deaktivieren experimenteller Sitzungmarkierungen von Verbesserungen. Es wird erlaubt, Grenzlinien-Ereignisse und einige die das Geräte verpasst hat anzuzeigen. Diese Option muss vor dem Import aktiviert werden, da sonst eine Reinigung erforderlich ist. Flow Restriction Durchflussbegrenzung Enable Unknown Events Channels Aktivieren Sie die Kanäle für unbekannte Ereignisse Minimum duration of drop in oxygen saturation Mindestdauer des Abfalls der Sauerstoffsättigung I want to be notified of test versions. (Advanced users only please.) Ich möchte über Testversionen informiert werden. (Bitte nur fortgeschrittene Benutzer.) Overview Linecharts Übersicht Liniendiagramme Whether to allow changing yAxis scales by double clicking on yAxis labels Ob sich ändernde yAchse Skalen durch Doppelklick auf yAchse Etiketten ermöglichen Always Minor immer Klein Unknown Events Unbekannte Ereignisse Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Pixmap-Caching ist eine Grafikbeschleunigungstechnik, welche zu Problemen mit der Anzeige von Schrift in dem Grafik-Anzeigebereich auf Ihrer Plattform führen kann. Reset &Defaults Auf &Standardwerte zurücksetzen Bypass the login screen and load the most recent User Profile Umgehen Sie den Login-Bildschirm und laden Sie das neueste Benutzerprofil Data Reindex Required Erforderliche Daten indizieren <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Bitte beachten Sie:</b> Mit OSCAR sind die erweiterten Funktionen zur Sitzungsaufteilung nicht möglich <b>ResMed</b> Geräte aufgrund einer Einschränkung in der Art und Weise, wie ihre Einstellungen und Zusammenfassungsdaten gespeichert werden, und daher für dieses Profil deaktiviert wurden.</p><p>Auf ResMed-Geräten wird es Tage dauern <b>mittags getrennt</b> wie in der kommerziellen Software von ResMed.</p> Scroll Dampening Bildlauf Dämpfung Are you sure you want to disable these backups? Möchten Sie diese Sicherungen wirklich deaktivieren? Flag leaks over threshold Leck-Flag über dem Schwellenwert 20 cmH2O 20 cmH2O hours Stunden Double click to change the descriptive name this channel. Klicken Sie doppelt auf den beschreibenden Namen um diesen Kanal zu ändern. Sessions older than this date will not be imported Sitzungen, die älter als dieses Datum sind werden nicht importiert Standard Bars Standardbalken 99% Percentile 99% Prozentuale Memory and Startup Options Speicher und Startoptionen <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>verkürzt sich auf irgendwelche unwichtigen Bestätigungsdialoge beim Import.</p></body></html> Small chunks of oximetry data under this amount will be discarded. Kleine Abschnitte von Oxymetriedaten unter diesem Betrag, werden verworfen. Oximeter Waveforms Oxymeter Wellenformen User definable threshold considered large leak Frei definierbare Schwelle als großes Leck Reset the counter to zero at beginning of each (time) window. Setzen Sie den Zähler zu Beginn eines jeden (Zeit-) Abschnitte s auf Null. Compliance defined as Therapietreue definiert als <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Startet OSCAR etwas langsamer, indem alle Übersichtsdaten vorab geladen werden, wodurch das Durchsuchen von Übersichten und einige andere Berechnungen später beschleunigt wird. </p><p>Wenn Sie über eine große Datenmenge verfügen, kann es sich lohnen, diese Option deaktiviert zu lassen, wenn Sie sie jedoch normalerweise anzeigen möchten <span style=" font-style:italic;">alles</span> in der Übersicht müssen alle zusammenfassenden Daten trotzdem noch geladen werden. </p><p>Beachten Sie, dass sich diese Einstellung nicht auf Wellenform- und Ereignisdaten auswirkt, die bei Bedarf immer nach Bedarf geladen werden.</p></body></html> Here you can change the type of flag shown for this event Hier können Sie die Art der Markierung für das gezeigte Ereigniss ändern Top Markers Obere Markierung One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Für eine oder mehrere der von Ihnen vorgenommenen Änderungen muss diese Anwendung neu gestartet werden, damit die Änderungen wirksam werden. Möchten Sie das jetzt tun? minutes minuten Minutes Minuten Create SD Card Backups during Import (Turn this off at your own peril!) Erstellen Sie ein SD-Karten Backup während des Imports (Deaktivieren Sie dieses auf eigene Gefahr!) Graph Settings Diagramm-Einstellungen This is the short-form label to indicate this channel on screen. Das ist das Kurzform-Label, um diesen Kanal auf dem Bildschirm anzuzeigen. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Die folgenden Optionen wirken sich auf den von OSCAR verwendeten Festplattenspeicherplatz aus und wirken sich auch darauf aus, wie lange der Import dauert. CPAP Events CPAP Ereignisse Bold Fett <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Welche Registerkarte wird beim Laden eines Profils geöffnet? (Hinweis: Standardmäßig wird ein Profil festgelegt, wenn OSCAR beim Starten nicht zum Öffnen eines Profils konfiguriert ist.)</p></body></html> Minimum duration of pulse change event. Mindestdauer von Pulswechsel-Ereignissen. Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Anti-Aliasing gilt für eine Verbesserung der grphischen Darstellung.. Bestimmte Darstellungen sehen besser aus wenn Sie das aktivieren. Dies wirkt sich auch auf gedruckte Berichte Berichte aus. Probieren Sie es aus und sehen, ob es Ihnen gefällt. Sleep Stage Events Schlafstadium Ereignisse Events Ereignisse <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Diese Einstellung sollte mit Vorsicht angewendet werden...</span> Das Ausschalten hat Konsequenzen auf die Genauigkeit der Zusammenfassung von Tagen, da bestimmte Berechnungen nur ordnungsgemäß auf die Zusammenfassung von Sitzungen funktionieren. </p><p><span style=" font-weight:600;">ResMed Anwender:</span> Nur weil es so scheint, dass die 12 Uhr-Sitzung der Neustart in den vorherigen Tag sein sollte, bedeutet es nicht, dass die ResMed Daten mit uns übereinstimmen. Das STF.edf Zusammenfassung Indexformat hat gravierende Schwächen. Es ist keine gute Idee, es zu tun.</p><p>Diese Option gibt es für diejenigen, denen es egal ist ob Sie es sehen oder nicht &quot;feststehend&quot; Wenn Sie die SD-Karte in jeder Nacht benutzen, und den Import mindestens einmal pro Woche erledigen, werden Sie sehen, dass es nicht sehr oft zu Problemen kommt.</p></body></html> Median is recommended for ResMed users. Medianwert ist für ResMed Benutzer empfohlen. Oximeter Events Oxymeter Ereignisse Italic Kursiv Enable Multithreading aktivieren Sie Multithreading This may not be a good idea Das ist keine gute Idee Weighted Average Gewichteter Durchschnitt Median Medianwert Flag rapid changes in oximetry stats Flag bei raschen Veränderungen in der Oxymetrie-Statistik Sudden change in Pulse Rate of at least this amount Plötzliche Änderung in Pulsfrequenz von mindestens diesen Betrag Search Suche Resync Machine Detected Events (Experimental) Nach der Resynchronisierung des Gerätes erfasste Ereignisse (experimentell) Time Weighted average of Indice Zeit Gewichteter Durchschnitt der Indice Middle Calculations Mittel Berechnungen Skip over Empty Days Leere Tage überspringen Allow duplicates near machine events. Gerät für Duplikate von Ereignissen zulassen. The visual method of displaying waveform overlay flags. Das visuelle Verfahren zur Darstellung von Wellenüberlagerungsansichten. Upper Percentile Obere Prozentuale Restart Required Neustart erforderlich Whether to show the leak redline in the leak graph Ob die rote Linie im Leck Graphen angezeigt werden soll If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Wenn Sie diese Daten jemals erneut importieren müssen (ob in OSCAR oder ResScan), werden diese Daten nicht zurückgegeben. True Maximum Echte Maximum Minor Flag Kleinere Flag Data Processing Required Datenverarbeitung erforderlich For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Für Konsistenz sollten ResMed Nutzer hier 95% verwenden, denn dies ist der einzige Wert in der Tageszusammenfassung der lieferbar ist. 4 cmH2O 4 cmH2O On Opening Beim Öffnen Pre-Load all summary data at startup Übersichtsdaten beim Start vorladen Show Remove Card reminder notification on OSCAR shutdown Show Reminder Card Reminder-Benachrichtigung beim Herunterfahren von OSCAR No change Keine Änderung Graph Text Diagrammtext Double click to change the default color for this channel plot/flag/data. Klicken Sie doppelt auf die Standardfarbe für diese Kanal Parzelle/Markierung/Daten ändern. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Hinweis: </span>Aufgrund zusammenfassender Entwurfseinschränkungen unterstützen ResMed-Geräte das Ändern dieser Einstellungen nicht.</p></body></html> AHI/Hour Graph Time Window AHI/Stunde Diagramm Zeitfenster Import without asking for confirmation Import ohne weitere Bestätigung l/min l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Kumulative Indizes</p></body></html> Discard segments under Untere Segmente verwerfen <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Entsättigungen unten</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { Leerzeichen: pre-wrap; } </style></head><body style=" Schriftartfamilie:'Segoe UI'; Schriftgröße:9pt; Schriftstärke:400; Schriftart:normal;"> <p style=" Rand-oben:0px; Rand-unten:0px; Rand-links:0px; Rand-rechts:0px; -qt-block-einzug:0; text-einzug:0px;"><span style= " font-family:'Sans'; font-size:10pt; font-weight:600;">Oximetrie- und CPAP-Daten synchronisieren</span></p> <p align="justify" style="-qt-paragraph-type:empty; Rand-oben:0px; Rand-unten:0px; Rand-links:0px; Rand-rechts:0px; -qt-block-einzug: 0; Texteinzug:0px; Schriftfamilie:'Sans'; Schriftgröße:10pt;"><br /></p> <p align="justify" style=" Rand-oben:0px; Rand-unten:0px; Rand-links:0px; Rand-rechts:0px; -qt-block-einzug:0; text-einzug:0px;" ><span style=" font-family:'Sans'; font-size:10pt;">CMS50-Daten, die aus SpO2Review (aus .spoR-Dateien) oder der seriellen Importmethode importiert wurden, tun </span><span style=" font- Familie:'Sans'; Schriftgröße:10pt; Schriftstärke:600; Textdekoration: underline;">not</span><span style=" font-family:'Sans'; Schriftgröße:10pt; "> haben den korrekten Zeitstempel, der für die Synchronisierung benötigt wird.</span></p> <p align="justify" style="-qt-paragraph-type:empty; Rand-oben:0px; Rand-unten:0px; Rand-links:0px; Rand-rechts:0px; -qt-block-einzug: 0; Texteinzug:0px; Schriftfamilie:'Sans'; Schriftgröße:10pt;"><br /></p> <p align="justify" style=" Rand-oben:0px; Rand-unten:0px; Rand-links:0px; Rand-rechts:0px; -qt-block-einzug:0; text-einzug:0px;" ><span style=" font-family:'Sans'; font-size:10pt;">Der Live-Ansichtsmodus (unter Verwendung eines seriellen Kabels) ist eine Möglichkeit, eine genaue Synchronisierung auf CMS50-Oximetern zu erreichen, zählt jedoch nicht zur CPAP-Uhr Abweichung.</span></p> <p align="justify" style="-qt-paragraph-type:empty; Rand-oben:0px; Rand-unten:0px; Rand-links:0px; Rand-rechts:0px; -qt-block-einzug: 0; Texteinzug:0px; Schriftfamilie:'Sans'; Schriftgröße:10pt;"><br /></p> <p align="justify" style=" Rand-oben:0px; Rand-unten:0px; Rand-links:0px; Rand-rechts:0px; -qt-block-einzug:0; text-einzug:0px;" ><span style=" font-family:'Sans'; font-size:10pt;">Wenn Sie den Aufzeichnungsmodus Ihres Oximeters bei </span><span style=" font-family:'Sans'; font-size starten :10pt; font-style:italic;">genau </span><span style=" font-family:'Sans'; font-size:10pt;">das gleiche Mal, wenn Sie Ihr CPAP-Gerät starten, können Sie jetzt auch Synchronisierung erreichen. </span></p> <p align="justify" style="-qt-paragraph-type:empty; Rand-oben:0px; Rand-unten:0px; Rand-links:0px; Rand-rechts:0px; -qt-block-einzug: 0; Texteinzug:0px; Schriftfamilie:'Sans'; Schriftgröße:10pt;"><br /></p> <p align="justify" style=" Rand-oben:0px; Rand-unten:0px; Rand-links:0px; Rand-rechts:0px; -qt-block-einzug:0; text-einzug:0px;" ><span style=" font-family:'Sans'; font-size:10pt;">Der serielle Importvorgang nimmt die Startzeit der ersten CPAP-Sitzung von letzter Nacht. (Denken Sie daran, zuerst Ihre CPAP-Daten zu importieren!)</span></p></body></html> Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Erlaubt die Verwendung von mehreren CPU-Kernen, wenn verfügbar, um die Leistung zu verbessern. Vor allem wirkt sich das auf den Import von Daten aus. Line Chart Liniendiagramm dd MMMM yyyy dd MMMM yyyy <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>True Maximum liegt das Maximum des Datensatzes.</p><p>99. Perzentile filtert den seltensten Ausreißer heraus.</p></body></html> Profile Profile Flag Type Flag-Typ Calculate Unintentional Leaks When Not Present Berechnung unbeabsichtigter Lecks, wenn nicht vorhanden Automatically load last used profile on start-up Das zuletzt verwendete Profil wird beim Start automatisch geladen How long you want the tooltips to stay visible. Wie lange sollen die Tooltips sichtbar bleiben. Double click to change the descriptive name the '%1' channel. Klicken Sie doppelt auf den beschreibenden Namen des '%1' Kanal zu wechseln. Multiple sessions closer together than this value will be kept on the same day. Mehrere Sitzungen, die näher als dieser Wert liegen, werden am selben Tag gehalten. Are you really sure you want to do this? Sind Sie wirklich sicher, dass Sie das tun wollen? Duration of airflow restriction Dauer der Behinderung des Luftstroms Bar Tops Balkendiagramme This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Diese Berechnung erfordert Gesamt Lecks Daten,welche durch die CPAP-Geräte zur Verfügung gestellt werden. (ZB PRS1, aber nicht ResMed, weil diese bereits vorhanden sind) Die hier verwendeten unbeabsichtigten Leckberechnungen sind liniar. Diese beziehen sich auf die Maske nicht auf die Entlüftungskurve. Wenn Sie verschiedene Masken verwenden, wählen Sie die Mittelwerte. Es sollte dann immer noch genau genug sein. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Dadurch benötigen OSCAR-Daten etwa die Hälfte des Speicherplatzes. Import und Tageswechsel dauern jedoch länger. Wenn Sie einen neuen Computer mit einer kleinen Solid-State-Diskette haben, ist dies eine gute Option. Session Splitting Settings Sitzung Splitting-Einstellungen <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Hinweis: Dies ist nicht für Zeitzone Korrekturen bestimmt! Stellen Sie sicher, dass Ihre Betriebssystem Uhr und Zeitzone richtig eingestellt ist.</p></body></html> Other Visual Settings Andere Visuelle Einstellungen Day Split Time Tages Zwischenzeit CPAP Waveforms CPAP Wellenform Compress Session Data (makes OSCAR data smaller, but day changing slower.) Sitzungsdaten komprimieren (OSCAR-Daten werden dadurch kleiner, jedoch die Tagesansicht wird langsamer.) Big Text Großer Text <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p> Diese Eigenschaften sind vor kurzem eingestellt worden. Sie werden später wieder zu benutzen sein. </p></body></html> Note: A linear calculation method is used. Changing these values requires a recalculation. Hinweis:Hier wird ein lineares Berechnungsverfahren verwendet. Das Ändern dieser Werte erfordert eine Neuberechnung. ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9 Geräte löschen routinemäßig bestimmte Daten von der SD-Karte, die älter als 7 und 30 Tagen (je nach Auflösung) sind. Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Tage mit der Benutzung des Gerätes mit unter 4 Stunden ist "nicht konform".mehr als 4 Stnden sind in Ordnung. Do not import sessions older than: Keine älteren als diese Sitzung importieren: Daily view navigation buttons will skip over days without data records Tagesansicht Navigationstasten wird die Tage ohne Datensätze überspringen Flag Pulse Rate Above Flag bei Pulsrate über Flag Pulse Rate Below Flag bei Pulsrate unter Seconds Sekunden Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Stellt die Datenmenge für jeden Punkt in dem AHI/ Stunde Diagramm bereit. Standardwerte auf 60 Minuten.. Sehr zu empfehlen. Show in Event Breakdown Piechart Die Ereignispannen als Kreisdiagramm anzeigen Other oximetry options Andere Oxymetrie Optionen Switch Tabs Tabs wechseln This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Dadurch wird eine Sicherung der SD-Kartendaten für ResMed-Geräte beibehalten. Geräte der Serie ResMed S9 löschen hochauflösende Daten, die älter als 7 Tage sind und Diagrammdaten welche älter als 30 Tage sind. OSCAR kann eine Kopie dieser Daten aufbewahren, wenn Sie eine Neuinstallation durchführen müssen. (Sehr empfehlenswert, es sei denn, Sie haben nicht genügend Speicherplatz oder kümmern sich nicht um die Diagrammdaten) &Cancel &Abbrechen Don't Split Summary Days (Warning: read the tooltip!) Übersicht der Tage nicht spalten (Warnung: Lesen Sie die Tooltipps!) Last Checked For Updates: Letzte Kontrolle Updates: Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Komprimieren Sie ResMed (EDF) -Sicherungen, um Speicherplatz zu sparen. Gesicherte EDF-Dateien werden im .gz-Format gespeichert. Das ist auf Mac- und Linux-Plattformen üblich. OSCAR kann nativ aus diesem komprimierten Sicherungsverzeichnis importieren. Um es mit ResScan zu verwenden, müssen die .gz-Dateien zuerst dekomprimiert werden. Details Details Use Anti-Aliasing Verwenden Sie Anti-Aliasing Animations && Fancy Stuff Animationen && gutes Material &Import &Importieren Statistics Statistiken <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Diese Einstellung hält Wellenform und Ereignisdaten im Speicher. Bei einem neuen Besuch des Programms werden Sie eine erhebliche Beschleunigung bemerken.</p><p>Das ist nicht wirklich eine notwendige Option.</p><p>Empfohlen wird, diese Option ausgeschaltet zu lassen.</p></body></html> Changes to the following settings needs a restart, but not a recalc. Änderungen an den folgenden Einstellungen benötigt einen Neustart, aber keine Neuberechnung. &Appearance &Erscheinungsbild The pixel thickness of line plots Die Pixeldicke von Liniendiagrammen Whether this flag has a dedicated overview chart. Egal, diese Markierung hat eine eigene Übersichtskarte. This is a description of what this channel does. Dies ist eine Beschreibung der Funktion dieses Kanals. Combine Close Sessions Kombinieren Schließen Sitzung Custom CPAP User Event Flagging Benutzerdefinierte CPAP Benutzerereignis Flag <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Warnung: </span>Nur weil Sie auf die Standardwerte zurücksetzen können, bedeutet Dies nicht immer, dass das gut ist.</p></body></html> Allow YAxis Scaling Erlauben Sie YAxis Skalierung Fonts (Application wide settings) Schriften (Application Größeneinstellungen) Use Pixmap Caching Verwenden Pixmap Zwischenspeicherung This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Bei dieser experimentellen Option wird versucht, das Ereigniserkennungssystem von OSCAR zu verwenden, um die Positionierung der von Geräten erkannten Ereignissen zu verbessern. Check for new version every Alle auf neue Version prüfen Waveforms Wellenformen Maximum Calcs Maximale Berechnungen Overview Überblick Tooltip Timeout Kurzinfo Zeitüberschreitung Preferences Einstellungen General CPAP and Related Settings Allgemeine CPAP und verwandte Einstellungen Default display height of graphs in pixels Standardanzeige Höhe von Diagrammen in Pixel Overlay Flags Überlagerungs-Flag Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Ändern Sie die Standardeinstellung (Desktop OpenGL), wenn Sie Probleme mit der Darstellung der OSCAR-Diagramme haben. Makes certain plots look more "square waved". Macht bestimmte Abschnitte vom Aussehen her "schwenkbar". Welcome Willkommen Percentage drop in oxygen saturation Prozentualer Abfall der Sauerstoffsättigung &General &Allgemein Standard average of indice Standarddurchschnitt von indice If you need to conserve disk space, please remember to carry out manual backups. Wenn Sie Speicherplatz sparen müssen, denken Sie daran, manuelle Sicherungen durchzuführen. Compress SD Card Backups (slower first import, but makes backups smaller) Komprimieren der Backups auf der SD-Karte (langsamer beim ersten Import, aber macht Backups kleiner) Keep Waveform/Event data in memory Halten Sie die Wellenform / Sitzungsdaten im Speicher Whether a breakdown of this waveform displays in overview. Ob eine Aufschlüsselung dieser Wellenform im Überblick gezeigt werden soll. Normal Average Normaler Durchschnitt Positional Waveforms Positions-Wellenform A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Ein Datenindexierungsvorgang ist erforderlich, um diese Änderungen anzuwenden. Dieser Vorgang kann einige Minuten dauern. Sind Sie sicher, dass Sie diese Änderungen vornehmen wollen? Positional Events Positions Ereignisse Preferred Calculation Methods Bevorzugte Berechnungsmethoden Combined Count divided by Total Hours Kombinierte Anzahl geteilt durch die Gesamtstunden Graph Tooltips Diagramm Tooltips &Oximetry &Oxymetrie CPAP Clock Drift CPAP Wecker benutzen Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Hier können Sie einstellen <b>unteren</b> Schwelle für bestimmte Berechnungen welche die Wellenform verwendet %1 Auto-Launch CPAP Importer after opening profile CPAP-Importeur nach dem Öffnen des Profils automatisch starten Square Wave Plots Quadratwelle-Anschläge TextLabel Textlabel Preferred major event index Index für bevorzugte wichtige Ereignisse Application Anwendung Line Thickness Linienstärke Changing SD Backup compression options doesn't automatically recompress backup data. Das Ändern der SD Backup-Komprimierungsoptionen führt nicht automatisch zu einer Neukomprimierung der Backup-Daten. Your masks vent rate at 20 cmH2O pressure Ihre Maskenlüftungsrate bei 20 cmH2O Druck Your masks vent rate at 4 cmH2O pressure Ihre Maskenlüftungsrate bei 4 cmH2O Druck Whether to include machine serial number on machine settings changes report Ob die Seriennummer des Gerätes in den Bericht über Änderungen der Geräteeinstellungen aufgenommen werden soll Include Serial Number Seriennummer einbeziehen <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Geben Sie eine Warnmeldung aus, wenn Sie Daten von einem Rechnermodell importieren, das noch nicht von den OSCAR-Entwicklern getestet wurde.</p></body></html> Warn when importing data from an untested machine Warnung beim Importieren von Daten von einem ungetesteten Rechner <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>Geben Sie beim Importieren von Daten eine Warnung aus, die sich irgendwie von allem unterscheidet, was die OSCAR-Entwickler zuvor gesehen haben.</p></body></html> Warn when previously unseen data is encountered Warnen Sie, wenn Sie auf bisher ungesehene Daten stoßen Always save screenshots in the OSCAR Data folder Screenshots immer im OSCAR-Datenordner speichern No CPAP machines detected Keine CPAP-Geräte erkannt Will you be using a ResMed brand machine? Werden Sie ein Gerät der Marke ResMed verwenden? Check For Updates Nach Updates suchen You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Sie verwenden eine Testversion von OSCAR. Testversionen suchen mindestens alle sieben Tage automatisch nach Updates. Sie können das Intervall auf weniger als sieben Tage einstellen. Automatically check for updates Automatisch nach Updates suchen How often OSCAR should check for updates. Wie oft OSCAR nach Aktualisierungen suchen sollte. If you are interested in helping test new features and bugfixes early, click here. Wenn Sie daran interessiert sind, neue Funktionen und Fehlerbehebungen frühzeitig zu testen, klicken Sie hier. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Wenn Sie helfen möchten, frühe Versionen von OSCAR zu testen, lesen Sie bitte die Wiki-Seite über das Testen von OSCAR. Wir heißen alle willkommen, die OSCAR testen, an der Entwicklung von OSCAR mitwirken und bei Übersetzungen in bestehende oder neue Sprachen helfen möchten. https://www.sleepfiles.com/OSCAR Print reports in black and white, which can be more legible on non-color printers Berichte in Schwarzweiß drucken, die auf Nicht-Farbdruckern besser lesbar sein können Print reports in black and white (monochrome) Berichte in Schwarzweiß (monochrom) drucken ProfileSelector GB GB KB KB MB MB PB PB TB TB Name Name Bytes Bytes Sorry Entschuldigung Profile: %1 Profile: %1 Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Denken Sie sorgfältig nach, da dadurch das Profil mit allen unwiederbringlich gelöscht wird <b>Backup-Daten</b> gespeichert unter<br/>%2. Email: <a href='mailto:%1'>%1</a> Email: <a href='mailto:%1'>%1</a> Profile '%1' was succesfully deleted Profil '%1' wurde erfolgreich gelöscht Summaries: Zusammenfassungen: DELETE DELETE Forgot your password? Haben Sie Ihr Passwort vergessen? &New Profile &Neues Profil There was an error deleting the profile directory, you need to manually remove it. Es gab einen Fehler beim Löschen des Profil-Verzeichnisses. Sie müssen es manuell entfernen. Show disk usage information Informationen zur Festplattennutzung anzeigen Phone: %1 Telefon: %1 Enter Password for %1 Passwort eingeben für %1 Last Imported Letzter Import Profile Profile Backups: Sicherungen: Name: %1, %2 Name: %1, %2 Please select or create a profile... Bitte wählen oder erstellen Sie ein Profil... You entered an incorrect password Sie haben ein falsches Passwort eingegeben Hide disk usage information Informationen zur Festplattennutzung ausblenden No profile information given Keine Profilinformationen angegeben Address: Adresse: Ask on the forums how to reset it, it's actually pretty easy. Fragen Sie in den Foren nach, wie Sie es zurücksetzen können. Es ist eigentlich ziemlich einfach. Select a profile first Wählen Sie zuerst ein Profil aus Other Data Andere Daten Version Version Events: Sitzungen: &Open Profile &Profil öffnen Filter: Filter: Destroy Profile Profil löschen &Edit Profile &Profil bearbeiten If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Wenn Sie versuchen, das Profil zu löschen, weil Sie das Kennwort vergessen haben, müssen Sie es entweder zurücksetzen oder den Profilordner manuell löschen. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Geben Sie das Wort ein <b>DELETE</b> unten (genau wie gezeigt) zur Bestätigung. Ventilator Brand Geräte-Marke Profile: None Profil: Keine Ventilator Model Geräte-Model You are about to destroy profile '<b>%1</b>'. Sie sind dabei, Ihr Profil zu zerstören '<b>%1</b>'. You need to enter DELETE in capital letters. Sie müssen DELETE in Großbuchstaben eingeben. You must create a profile Sie müssen ein Profil erstellen Reset filter to see all profiles Filter zurücksetzen, um alle Profile zu sehen The selected profile does not appear to contain any data and cannot be removed by OSCAR Das ausgewählte Profil scheint keine Daten zu enthalten und kann von OSCAR nicht entfernt werden ProgressDialog Abort Abbrechen QObject ? ? Only Settings and Compliance Data Available Nur Einstellungen und Compliance-Daten verfügbar Summary Data Only Nur zusammenfassende Daten A A H H P P h h m m s s m m Your machine doesn't record data to graph in Daily View Ihr Gerät zeichnet in der Tagesansicht keine Daten auf, um sie grafisch darzustellen AI AI CA CA EP EP FL FL HI HI IE IE Hz Hz LE LE LF LF LL LL Kg Kg O2 O2 OA OA NR NR PB PB PC PC No Nein PP PP PS PS On An RE RE S9 S9 SA SA SD SD TB TB UA UA VS VS ft ft lb lb ml ml ms ms oz oz cm cm &No &Nein AHI AHI ASV ASV BMI BMI BND BND CAI CAI Apr Apr CSR CSR Aug Aug Avg Gem DOB Geburtsdatum EPI EPI EPR EPR Dec Dez FLI FLI End Ende Feb Feb Jan Jan Jul Jul Jun Jun NRI NRI Mar März Max Max May Mai Med Med Min Min Nov Nov Oct Okt Off Aus RDI RDI REI REI UAI UAI in in UF1 UF1 UF2 UF2 UF3 UF3 Sep Sep VS2 VS2 Yes Ja Zeo Zeo bpm bpm Brain Wave Gehirn Wellen &Yes &Ja 15mm 15mm 22mm 22mm APAP APAP CPAP CPAP Auto Auto Busy Beschäftigt Min EPAP Min EPAP EPAP EPAP Date Datum ICON ICON Min IPAP Min IPAP IPAP IPAP Last Letzte Verwendung Leak Leck Mask Maske Med. Med. Mode Modus Name Name None Keiner RERA RERA Ramp Rampe Zero Null Resp. Event Resp. Ereignis Inclination Neigung Launching Windows Explorer failed Das Starten vom Windows Explorer ist gescheitert Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Möchten Sie die automatischen Sicherungen einschalten, so dass das nächste Mal, wenn eine neue Version von OSCAR erforderlich ist, diese neu erstellt werden kann? Hose Diameter Schlauchdurchmesser &Save &Speichern AVAPS AVAPS CMS50 CMS50 Therapy Pressure Therapiedruck BiPAP BiPAP Brand Marke EPR: EPR: Daily Täglich Email Email Error Fehler First Erste Verwendung Ramp Pressure Rampen Druck L/min L/min Hours Stunden MD300 MD300 Leaks Lecks Max: Max: Min: Min: Model Model Nasal Nase Notes Aufzeichnungen Phone Telefon Ready Bereit TTIA: TTIA: W-Avg W-Durchschnitt Snore Schnarchen Start Start Usage Verwendung Respiratory Disturbance Index Atem-Störungs-Verzeichnis cmH2O cmH2O Pressure Support Druckunterstützung Bedtime: %1 Schlafenszeit: %1 Hypopnea Hypopnoe ratio Verhältnis Tidal Volume AZV Getting Ready... Fertig werden... Entire Day Ganzer Tag Intellipap pressure relief mode. Intellipap Druckmodus. Personal Sleep Coach Persönlichen Schlaftrainer Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Neueste Oxymetriedaten: <a onclick='alert("daily=%2");'>%1</a> Scanning Files Scanne Dateien Clear Airway Centraler Apnoe Respironics Respironics Heart rate in beats per minute Die Herzfrequenz in Schlägen pro Minute A large mask leak affecting machine performance. Eine zu große Maske beeinflußt die Geräteleistung. Somnopose Software Somnopose Software Time spent awake Zeit im Wachliegen Temp. Enable Temp. aktivieren Timed Breath Zeitüberschreitung Atem Pop out Graph grafische Darstellung Mask On Time Masken-Einschaltzeit It is likely that doing this will cause data corruption, are you sure you want to do this? Es ist wahrscheinlich, dass dabei eine Datenbeschädigung auftreten kann. Sind Sie sicher, dass Sie das tun wollen? Loading profile "%1"... Profil laden "%1"... Breathing Not Detected Atmung nicht erkannt There is no data to graph Es gibt keine Daten zum Darstellen Journal Journal Locating STR.edf File(s)... Suche nach STR.edf-Datei (en)... Pat. Trig. Breath Pat. Trig. Atem (Summary Only) (Nur Zusammenfassung) Ramp Delay Period Rampen-Verzögerungszeit Sessions Switched Off Ereignisse abgemeldet Awakenings Erwachen This folder currently resides at the following location: Dieser Ordner befindet sich derzeit an der folgenden Position: Morning Feel Morgen erwartet Sie Pulse Change Impulsänderung Disconnected Getrennt Sleep Stage Schlafstadium Minute Vent. Minuten Vent. SpO2 Drop SpO2-Abfall Ramp Event Rampenereignis SensAwake feature will reduce pressure when waking is detected. SensAwake reduziert den Druck beim Erkennen des Wachzusdtandes. Show All Events Alle Ereignisse anzeigen CMS50E/F CMS50E/F Upright angle in degrees Bis rechten Winkel in Grad Importing Sessions... Importiere Sitzung... Higher Expiratory Pressure Höherer Expirationsdruck NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 M-Series M-Serie If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Wenn Sie Bedenken haben, klicken Sie auf Nein, um den Vorgang zu beenden, und sichern Sie Ihr Profil manuell, bevor Sie OSCAR erneut starten. A vibratory snore Eine Schnarchvibration Vibratory Snore Schnarchvibration As you did not select a data folder, OSCAR will exit. Da Sie keinen Datenordner ausgewählt haben, wird OSCAR beendet. Lower Inspiratory Pressure Niedrigster Inspirationsdruck Humidifier Enabled Status Befeuchtungsstatus aktiviert Full Face Mund-Nase-Maske Full Time Volle Zeit SmartFlex Level Smartflex Ebene Journal Data Journal Daten (%1% compliant, defined as > %2 hours) (%1% konform, definiert als > %2 Stunden) Resp. Rate Resp. Rate Insp. Time Einatmungszeit Exp. Time Ausatmungszeit OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: OSCAR wird nun beendet und (versucht) den Dateimanager Ihres Computers zu starten, damit Sie Ihr Profil manuell sichern können: ClimateLine Temperature Schlauchtemperatur Philips Respironics Philips Respironics Your %1 %2 (%3) generated data that OSCAR has never seen before. Ihre %1 %2 (%3) haben Daten generiert, die OSCAR noch nie zuvor gesehen hat. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Die importierten Daten sind möglicherweise nicht ganz korrekt, daher möchten die Entwickler eine ZIP-Kopie der SD-Karte dieses Geräts und die entsprechenden PDF-Berichte des Klinikers, um sicherzustellen, dass OSCAR die Daten korrekt verarbeitet. Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Ihr %1 CPAP-Gerät (Modell %2) ist leider kein datenfähiges Modell. Your %1 CPAP machine (Model %2) has not been tested yet. Ihr %1 CPAP-Gerät (Modell %2) wurde noch nicht getestet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Es scheint anderen Computern ähnlich genug zu sein, dass es funktionieren könnte, aber die Entwickler möchten eine ZIP-Kopie der SD-Karte dieses Computers und passende .pdf-Berichte für den Kliniker, um sicherzustellen, dass es mit OSCAR funktioniert. Machine Unsupported Gerät wird nicht unterstützt Sorry, your %1 CPAP machine (%2) is not supported yet. Ihr %1 CPAP-Gerät (%2) wird leider noch nicht unterstützt. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Die Entwickler benötigen eine ZIP-Kopie der SD-Karte dieses Geräts und die entsprechenden PDF-Berichte des Klinikers, damit es mit OSCAR funktioniert. SOMNOsoft2 SOMNOsoft2 A relative assessment of the pulse strength at the monitoring site Eine relative Bewertung der Pulsstärke an der Messstelle Machine Gerät Mask On Maske auf Max: %1 Max: %1 Sorry, the purge operation failed, which means this version of OSCAR can't start. Die Bereinigungsoperation ist fehlgeschlagen. Das bedeutet, dass diese Version von OSCAR nicht gestartet werden kann. A sudden (user definable) drop in blood oxygen saturation Ein plötzlicher (frei definierbarer) Abfall der Blutsauerstoffsättigung Time spent in deep sleep Zeit im Tiefschlaf There are no graphs visible to print Keine Diagramme zum Drucken OSCAR picked only the first one of these, and will use it in future: OSCAR hat nur das Erste ausgewählt und wird es zukünftig verwenden: Target Vent. Ziel Vent. Sleep position in degrees Schlafposition in Grad Plots Disabled Diagramme deaktiviert Min: %1 Min: %1 Minutes Minuten Periodic Breathing periodische Atmung Popout %1 Graph Ausschalten %1 Grafik Ramp Only Nur Rampe Ramp Time Rampenzeit For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. Aus irgendeinem Grund konnte OSCAR keinen Journalobjektdatensatz in Ihrem Profil finden. Es wurden jedoch mehrere Journaldatenordner gefunden. PRS1 pressure relief mode. PRS1 Druckentlastungsmodus. An abnormal period of Periodic Breathing Eine abnormale Periode der periodischen Atmung ResMed Mask Setting ResMed Maskeneinstellung ResMed Exhale Pressure Relief ResMed Ausatmungsdruckentlastung A-Flex A-Flex EPR Level EPR Ebene Unintentional Leaks Unbeabsichtigte Lecks Would you like to show bookmarked areas in this report? Möchten Sie die Lesezeichenbereiche in diesem Bericht anzeigen? Somnopose Somnopose AHI %1 AHI %1 C-Flex C-Flex VPAPauto VPAPauto Apnea Hypopnea Index Apnoe-Hypopnoe-Index Physical Height Physische Größe Pt. Access Pt. Zugriff CMS50F CMS50F ASV (Fixed EPAP) ASV (Fest-EPAP) Patient Triggered Breaths Durch Patienten ausgelöste Atemzüge This means you will need to import this machine data again afterwards from your own backups or data card. Das heißt, Sie müssen diese Gerätedaten danach wieder von Ihren eigenen Backups oder Datenkarte importieren. Contec Contec Events Ereignisse Humid. Level Befeuchtungsstärke AB Filter AB Filter Height Höhe Ramp Enable Rampe aktivieren (% %1 in events) (% %1 der Ereignisse) Lower Threshold untere Schwelle No Data Keine Daten Zeo sleep quality measurement Schlafqualitätsmessung Page %1 of %2 Seite %1 von %2 Litres Liter Manual Handbuch Median Median Fixed %1 (%2) Fest %1 (%2) Min %1 Min %1 Could not find explorer.exe in path to launch Windows Explorer. Explorer.exe nicht im PATH. Windows-Explorer kann nicht gestartet werden. Machine automatically switches off Gerät schaltet sich automatisch aus Connected Angeschlossen Low Usage Days: %1 Tage mit geringer Nutzung: %1 PS Max PS Max PS Min PS Min Database Outdated Please Rebuild CPAP Data veraltete Datenbank CPAP Daten wiederherstellen Flow Limit. Fließgrenze. Detected mask leakage including natural Mask leakages Erkannte Masken Lecks einschließlich der natürlichen Maskenlecks Plethy Plethy (Ein Gerät bestimmen und Registrieren der Variationen in der Größe oder des Volumens eines Schenkels, in Arm oder Bein, und der Änderung der Blutmenge im Glied.) SensAwake Druckverminderungstechnologie während des Wachwerdens ST/ASV ST/ASV Median Leaks Median Lecks %1 Report %1 Bericht ResMed ResMed Pr. Relief Druckentlastung Graphs Switched Off Diagramme ausgeblendet Serial Serien Series Serie (last night) (letzte Nacht) AHI %1 AHI %1 Weight Gewicht ZEO ZQ Schlafqualitätsmessung PRS1 pressure relief setting. PRS1 Druckentlastungseinstellung. Orientation Orientierung Smart Start Smart Start Event Flags Ereignis-Flag A few breaths automatically starts machine Nach ein paar Atemzügen startet das Gerät automatisch Zeo ZQ Schlafqualitätsmessung Migrating Summary File Location Ändern des Speicherorts der Zusammenfassungsdatei Zombie Mir geht es C-Flex+ C-Flex+ Bookmarks Lesezeichen PAP Mode PAP Modus CPAP Mode CPAP Modus Time taken to get to sleep Einschlafzeit SmartFlex Settings SmartFlex Einstellungen An apnea where the airway is open Atemaussetzer obwohl Atemwege offen sind Flow Limitation Flusslimitierung Pin %1 Graph Einheften Graph %1 Unpin %1 Graph Loslösen Graph %1 Queueing Import Tasks... Warteschlangenimportaufgaben... Hours: %1h, %2m, %3s Stunden: %1h, %2m, %3s OSCAR does not yet have any automatic card backups stored for this device. Für OSCAR sind noch keine automatischen Kartensicherungen für dieses Gerät gespeichert. %1 Length: %3 Start: %2 %1 Länge: %3 Start: %2 CMS50F3.7 CMS50F3.7 RDI %1 RDI %1 ASVAuto ASVAuto PS %1 over %2-%3 (%4) PS %1 über %2-%3 (%4) Flow Rate Fließrate Time taken to breathe out Ausatmungszeit Important: Wichtig: Machine auto starts by breathing Wenn Sie atmen beginnt das Gerät zu arbeiten ANGLE / OpenGLES ANGLE / OpenGLES An optical Photo-plethysomogram showing heart rhythm Eine optische Darstellung vom Herzrhythmus Loading %1 data for %2... Lade %1 Daten für %2... Pillows Kissen %1 Length: %3 Start: %2 %1 Länge: %3 Start: %2 Time Awake Aufwachzeit How you felt in the morning Wie fühlten Sie sich am Morgen I:E Ratio I: E-Verhältnis Amount of air displaced per breath Pro Atemzug verdrängte Luftmenge Pat. Trig. Breaths Patientenatemverursachte Atemzüge % in %1 % in %1 Humidity Level Feuchtigkeitsgrad Profile Profil Address Adresse Leak Flag Leck Flag Leak Rate Leckrate Loading Summaries.xml.gz Zusammenfassungsdaten xml.gz werden geladen ClimateLine Temperature Enable Schlauchtemperatur einschalten Severity (0-1) Schwere (0-1) Reporting from %1 to %2 Berichterstattung vom %1 bis %2 Are you sure you want to reset all your channel colors and settings to defaults? Sind Sie sicher, dass Sie alle Kanal-Farben und Einstellungen auf Standardwerte zurücksetzen wollen? BrainWave Gehirnwellen Inspiratory Pressure Einatmungsdruck Whether or not machine allows Mask checking. Ob das Gerät Maskenprüfung erlaubt. Number of Awakenings Anzahl Aufwachereignisse A pulse of pressure 'pinged' to detect a closed airway. Ein Druckimpuls um geschlossene Atemwege zu detektieren. Intellipap pressure relief level. Intellipap Druckentlastungsniveau. CMS50D+ CMS50D+ Non Responding Event Kein Ereignis registriert Median Leak Rate Median Leckrate (%3 sec) (%3 sek) Rate of breaths per minute Atemzüge pro Minute Are you ready to upgrade, so you can run the new version of OSCAR? Sind Sie bereit für ein Upgrade, damit eine neue Version von OSCAR ausgeführt wird? Perfusion Index Perfusionen-Index Graph displaying snore volume Graphische Anzeige Schnarchvolumen Mask Off Maske ab Max EPAP Max EPAP Max IPAP Max IPAP Bedtime Schlafenszeit Bi-Flex Bi-Flex EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) Pressure Druck Auto On Automatisch ein Average Durchschnitt Target Minute Ventilation Zielminutenvolumen Amount of air displaced per minute Atemminutenvolumen TTIA: %1 TTIA: %1 Percentage of breaths triggered by patient Prozentualer Anteil der vom Patienten ausgelösten Atemzüge Non Data Capable Machine Gerät nicht datenfähig Days: %1 Tage: %1 Plethysomogram Plethysomogramm Unclassified Apnea Apnoe ohne Zuordnung A user definable event detected by OSCAR's flow waveform processor. Ein vom Benutzer definierbares Ereignis, das vom Flow-Wave-Prozessor von OSCAR erkannt wird. Software Engine Software Auto Bi-Level (Fixed PS) Auto Bi-Level (Feste PS) Please Note Bitte warten Sie Starting Ramp Pressure Anlaufdruck Last Updated Letzte Aktualisierung Intellipap event where you breathe out your mouth. Intellipap Ereignis, bei dem Sie durch den Mund ausatmen. ASV (Variable EPAP) ASV (Variables EPAP) Exhale Pressure Relief Level Ausatemdruckentlastungs-Niveau Flow Limit Fließgrenze UAI=%1 UAI=%1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 geringer Nutzung, %2 keine Verwendung, von %3 Tage (%4% konform.) Länge: %5 / %6 / %7 Loading Summary Data Zusammenfassungsdaten laden Information Information Pulse Rate Pulsrate Rise Time Anstiegszeit Cheyne Stokes Respiration Cheyne-Stokes-Atmung SmartStart SmartStart Graph showing running AHI for the past hour Anzeige des AHI der letzten Stunde Graph showing running RDI for the past hour Anzeige des RDI der letzten Stunde Temperature Enable Temperatur aktivieren Seconds Sekunden %1 (%2 days): %1 (%2 Tage): Desktop OpenGL Desktop OpenGL Snapshot %1 Schnappschuss %1 Mask Time Maskenzeit How you feel (0 = like crap, 10 = unstoppable) Wie fühlen Sie sich (0 = nicht gut, 10 = hervorragend) Time in REM Sleep Zeit im Traum/REM-Schlaf Channel Kanal Auto for Her Auto für Sie Time In Deep Sleep Zeit im Tiefschlaf Time in Deep Sleep Zeit im Tiefschlaf Obstructive Obstruktive Apnoe Pressure Max Maximaler Druck Pressure Min Minimaler Druck Diameter of primary CPAP hose Durchmesser des primären CPAP Schlauchs Max Leaks Max Lecks Flex Level Flex-Ebene Time to Sleep Einschlafzeit Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Respiratory Effort Related Arousal: Atembehinderungen, die entweder Erwachen oder Schlafstörung verursachen. Humid. Status Feucht. Status (Sess: %1) (Sitzung: %1) Climate Control Klimakontrolle Perf. Index % Perfusionen-Index % If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Wenn ihre alten Daten fehlen, kopieren Sie den Inhalt aller andere Journal_XXXXXXX Ordner in diesenl. &Cancel &Abrechen Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) System One System One Default Standard Breaths/min Atmungen/min Degrees Grad &Destroy &Vernichten There is a lockfile already present for this profile '%1', claimed on '%2'. Es ist bereits eine Sperrdatei für dieses Profil vorhanden '%1', beansprucht am '%2'. User Flag #1 Benutzer Flag #1 User Flag #2 Benutzer Flag #2 User Flag #3 Benutzer Flag #3 OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR startet nun den Importassistenten, damit Sie Ihr %1 Daten neu installieren kann. REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% Median rate of detected mask leakage Median Rate der bemerkten Masken Lecks Bookmark Notes Lesezeichen-Notizen Bookmark Start Beginn Lesezeichen PAP Device Mode PAP Gerätemodus Mask Pressure Maskendruck A vibratory snore as detcted by a System One machine Eine Schnarchvibration die durch ein System One endeckt wurde No oximetry data has been imported yet. Es wurden noch keine Oxymetriedaten importiert. Please be careful when playing in OSCAR's profile folders :-P Seien Sie vorsichtig, wenn Sie mit dem OSCAR-Profilordnern spielen :-P 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1 = Wach 2 = Traum 3 = Leichter Schlaf 4 = Tiefschlaf Respiratory Event Atemereignis An abnormal period of Cheyne Stokes Respiration Eine abnormale Periode von Cheyne-Stokes-Atmung A type of respiratory event that won't respond to a pressure increase. Ein Atemereignis, das nicht auf Druckanstieg reagiert. Antibacterial Filter Antibakterienfilter Windows User Windows-Benutzer Cataloguing EDF Files... EDF-Dateien werden katalogisiert... Question Frage Time spent in light sleep Zeit in Leichtschlaf Waketime: %1 Aufwachzeit: %1 Time In REM Sleep Zeit in Traum/REM-Schlaf Higher Inspiratory Pressure Obererr Inspirationsdruck Weinmann Weinmann Don't forget to place your datacard back in your CPAP machine Vergessen Sie nicht, Ihre Datenkarte wieder in Ihr CPAP Gerät zu stecken The machine data folder needs to be removed manually. Der Gerätedaten-Ordner muss manuell entfernt werden. Summary Only Nur Zusammenfassung Bookmark End Ende Lesezeichen Bi-Level Bi Ebene Intellipap Intellipap <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i> Ihre alten Gerätedaten sollten erneuert werden, sofern die Backup-Funktion während eines frühern Datenimports nicht deaktiviert wurde.</ i> Unknown Unbekannt Finishing Up... Beenden... Events/hr Ereignisse/Stunde PRS1 humidifier connected? PRS1 Luftbefeuchter angeschlossen? CPAP Session contains summary data only CPAP Sitzung enthält nur Übersichtsdaten Finishing up... Beenden... Fisher & Paykel Fisher & Paykel Duration Dauer Scanning Files... Scanne Dateien... Hours: %1 Stunden: %1 Flex Mode Flex-Modus Sessions Sitzungen A period during a session where the machine could not detect flow. Periode innerhalb einer Sitzung, während das Gerät kein Fluss erkennen kann. Auto Off Automatisch aus Settings Einstellungen Overview Überblick The folder you chose is not empty, nor does it already contain valid OSCAR data. Der von Ihnen gewählte Ordner ist weder leer noch enthält er gültige OSCAR-Daten. Temperature Temperatur Entire Day's Flow Waveform Fluss-Wellenform des ganzen Tages Exiting Verlassen DeVilbiss DeVilbiss Time in Light Sleep Zeit in Leichtschlaf Time In Light Sleep Zeit in Leichtschlaf Fixed Bi-Level Bi-Level fix Machine Information Geräte-Informationen Pressure Support Maximum Druckunterstützungs-Maximum Graph showing severity of flow limitations Graphische Darstellung der Schwere der Flussbegrenzung : %1 hours, %2 minutes, %3 seconds : %1 Stunden, %2 Minuten, %3 Sekunden Auto Bi-Level (Variable PS) Bi-Level (Variable PS) Mask Alert Maskenalarm OSCAR Reminder OSCAR-Erinnerung OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR hat einen alten Journalordner gefunden, der jedoch umbenannt wurde: A partially obstructed airway Teilweise behinderte Atemwege Pressure Support Minimum Druckunterstützungs-Minimum Large Leak Großes Leck Time started according to str.edf Startzeit laut str.edf Wake-up Aufwachzeit Warning Warnung Min Pressure Mindestdruck Total Leak Rate Gesamtleckrate Max Pressure Größter Druck MaskPressure Maskendruck Duration %1:%2:%3 Dauer %1:%2:%3 Upper Threshold obere Schwelle OSCAR will not touch this folder, and will create a new one instead. OSCAR benutzt diesen Ordner nicht und erstellt stattdessen einen neuen. Total Leaks Anzahl der Lecks Minute Ventilation Minutenvolumen Rate of detected mask leakage Anzahl erkannter Maskenlecks Breathing flow rate waveform Atemflussrate-Wellenform Lower Expiratory Pressure Niedriger Ausatemdruck Time spent in REM Sleep Zeit in REM-Schlaf Min %1 Max %2 (%3) Min %1 Max %2 (%3) If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Wenn Sie Cloud-Speicher verwenden, stellen Sie sicher, dass OSCAR geschlossen ist und die Synchronisierung zuerst auf dem anderen Computer abgeschlossen ist, bevor Sie fortfahren. AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 Time taken to breathe in Einatmungszeit Maximum Therapy Pressure Maximaler Therapiedruck Are you sure you want to use this folder? Sind Sie sicher, dass Sie diesen Ordner nutzen möchten? Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Verwenden Sie Ihren Dateimanager, um eine Kopie Ihres Profilverzeichnisses zu erstellen. Starten Sie anschließend OSCAR erneut, und schließen Sie den Aktualisierungsvorgang ab. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Leider kann OSCAR für dieses Gerät nur Betriebsstunden und grundlegende Einstellungen auslesen. %1 (%2 day): %1 (%2 Tag): Current Selection Aktuelle Auswahl Blood-oxygen saturation percentage Blutsauerstoffsättigung in Prozent <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR unterhält eine Sicherung Ihrer Gerätedatenkarte, die es für diesen Zweck verwendet.</b> Inspiratory Time Einatemzeit Respiratory Rate Atemfrequenz Hide All Events Alle Ereignisse verbergen Printing %1 Report Bericht %1 Drucken Expiratory Time Ausatemzeit Expiratory Puff Ausatem-Hauch Maximum Leak Maximales Leck Ratio between Inspiratory and Expiratory time Verhältnis Ein-/Ausatemzeit APAP (Variable) APAP (Variabel) Minimum Therapy Pressure Kleinster Theraphiedruck A sudden (user definable) change in heart rate Eine plötzliche (frei definierbare) Veränderung der Herzfrequenz Body Mass Index BMI Oximetry Oxymetrie Oximeter Oxymeter No Data Available Keine Daten verfügbar The maximum rate of mask leakage Der Höchstsatz der Maskenlecks Humidifier Status Luftbefeuchter-Status Machine Initiated Breath Gerät-initiierter Zugang Machine Database Changes Gerätedatenbankänderungen SmartFlex Mode Smart-Flex-Modus Journal Notes Journal-Notizen (%2 min, %3 sec) (%2 min, %3 sek) You can only work with one instance of an individual OSCAR profile at a time. Sie kann nur mit einer einzigen Instanz eines OSCAR-Profils arbeiten. Expiratory Pressure Ausatmungsdruck Show AHI Zeige AHI Tgt. Min. Vent Ziel-Minutenventilation Rebuilding from %1 Backup Wiederherstellung vom%1 Backup Are you sure you want to reset all your waveform channel colors and settings to defaults? Sind Sie sicher, dass Sie alle Ihre Wellenform-Kanal-Farben und Einstellungen auf die Standardwerte zurücksetzen wollen? Pressure Pulse Druckimpuls ChoiceMMed ChoiceMMed Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Sitzungen: %1 / %2 / %3 Länge: %4 / %5 / %6 Längste: %7 / %8 / %9 Humidifier Luftbefeuchter Relief: %1 Relief: %1 Patient ID Patienten-ID Patient??? Patient??? An apnea caused by airway obstruction Eine Apnoe durch Obstruktion der Atemwege verursacht Vibratory Snore (VS2) Vibrations-Schnarchen (VS2) Please Wait... Bitte warten... Using Verwendung von , found SleepyHead - , gefunden SleepyHead - You must run the OSCAR Migration Tool Sie müssen das OSCAR-Migrationswerkzeug ausführen An apnea that couldn't be determined as Central or Obstructive. Eine Apnoe, die nicht als zentral oder obstruktiv bestimmt werden konnte. A restriction in breathing from normal, causing a flattening of the flow waveform. Eine Einschränkung der Atmung aus dem Normalzustand, die zu einer Verflachung der Strömungswellenform führt. or CANCEL to skip migration. oder CANCEL, um die Migration zu überspringen. You cannot use this folder: Sie können diesen Ordner nicht verwenden: Choose or create a new folder for OSCAR data Auswählen oder Erstellen eines neuen Ordners für OSCAR-Daten Migrating Migration files Dateien from von to zu OSCAR will set up a folder for your data. OSCAR richtet einen Ordner für Ihre Daten ein. We suggest you use this folder: Wir empfehlen Ihnen, diesen Ordner zu verwenden: Click Ok to accept this, or No if you want to use a different folder. Klicken Sie auf Ok, um dies zu akzeptieren, oder auf Nein, wenn Sie einen anderen Ordner verwenden möchten. Next time you run OSCAR, you will be asked again. Wenn Sie OSCAR das nächste Mal ausführen, werden Sie erneut gefragt. App key: App-Taste: Operating system: Betriebssystem: Graphics Engine: Grafik-Modul: Graphics Engine type: Typ der Grafik-Engine: Machine Untested Geräteüberprüfung Data directory: Datenverzeichnis: Updating Statistics cache Aktualisieren des Statistik-Cache Usage Statistics Statistik der Anwendung d MMM yyyy [ %1 - %2 ] d MMM yyyy [ %1 - %2 ] EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) Pressure Set Eingestellter Druck Pressure Setting Druckeinstellung IPAP Set IPAP-Set IPAP Setting IPAP-Einstellung EPAP Set EPAP Set EPAP Setting EPAP Einstellungen %1 Charts %1 Diagramme %1 of %2 Charts %1 von %2 Diagrammen Loading summaries Laden von Zusammenfassungen Built with Qt %1 on %2 Gebaut mit Qt %1 on %2 Motion Antrag n/a n/a Dreem Dreem Untested Data Ungeprüfte Daten P-Flex P-Flex Humidification Mode Befeuchtungsmodus PRS1 Humidification Mode PRS1 Befeuchtungsmodus Humid. Mode Feucht. Modus Fixed (Classic) Fixiert (klassisch) Adaptive (System One) Anpassungsfähig an (System One) Heated Tube Beheizte Schläuche Tube Temperature Schlauchtemperatur PRS1 Heated Tube Temperature PRS1 Temperatur des beheizten Schlauches Tube Temp. Schlauch-Temp. PRS1 Humidifier Setting PRS1-Luftbefeuchter-Einstellung 12mm 12mm Your Viatom device generated data that OSCAR has never seen before. Ihr Viatom-Gerät generierte Daten, die OSCAR noch nie zuvor gesehen hat. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Die importierten Daten sind möglicherweise nicht ganz korrekt, weshalb die Entwickler eine Kopie Ihrer Viatom-Dateien wünschen, um sicherzustellen, dass OSCAR die Daten korrekt verarbeitet. Viatom Viatom Viatom Software Viatom-Software OSCAR %1 needs to upgrade its database for %2 %3 %4 OSCAR %1 muss seine Datenbank für %2 %3 %4 sichern Movement Bewegung Movement detector Bewegungsmelder Version "%1" is invalid, cannot continue! Version "%1 ist ungültig, kann nicht fortgesetzt werden! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Die Version von OSCAR, die Sie betreiben (%1) ist ÄLTER als derjenige, der zur Erstellung dieser Daten verwendet wurde (%2). Please select a location for your zip other than the data card itself! Bitte wählen Sie einen anderen Ort für Ihren Zip als die Datenkarte selbst! Unable to create zip! Zip kann nicht erstellt werden! Parsing STR.edf records... Analysieren von STR.edf-Einträgen... Mask Pressure (High frequency) Maskendruck (Hochfrequenz) A ResMed data item: Trigger Cycle Event Ein ResMed-Datenelement: Zyklus-Ereignis auslösen Backing Up Files... Sichern von Dateien... Debugging channel #1 Debugging-Kanal #1 Test #1 Test #1 Debugging channel #2 Debugging-Kanal #2 Test #2 Test #2 EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) CPAP-Check CPAP-Check AutoCPAP AutoCPAP Auto-Trial Auto-Versuch AutoBiLevel AutoBiLevel S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Lock Flex-Verschluss Whether Flex settings are available to you. Ob Ihnen Flex-Einstellungen zur Verfügung stehen. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Zeitaufwand für den Übergang von EPAP zu IPAP, je höher die Zahl, desto langsamer der Übergang Rise Time Lock Zeitschloss für den Aufstieg Whether Rise Time settings are available to you. Ob Ihnen die Einstellungen der Anstiegszeit zur Verfügung stehen. Rise Lock Aufstiegshilfe Mask Resistance Setting Einstellung des Maskenwiderstands Mask Resist. Maske Widerstand. Hose Diam. Schlauch Diam. Tubing Type Lock Schlauchtyp-Sperre Whether tubing type settings are available to you. Ob Ihnen die Einstellungen für den Schlauchtyp zur Verfügung stehen. Tube Lock Rohrschloss Mask Resistance Lock Masken-Widerstandsschloss Whether mask resistance settings are available to you. Ob Ihnen Maskenwiderstandseinstellungen zur Verfügung stehen. Mask Res. Lock Maske Res. Sperre Whether or not machine shows AHI via built-in display. Ob der Geräte AHI über das eingebaute Display anzeigt werden soll oder nicht. Ramp Type Rampentyp Type of ramp curve to use. Art der zu verwendenden Rampenkurve. Linear Linear SmartRamp Intelligente Rampe Ramp+ Rampe+ Backup Breath Mode Sicherungs-Atemmodus The kind of backup breath rate in use: none (off), automatic, or fixed Die Art der verwendeten Backup-Atemfrequenz: keine (ausgeschaltet), automatisch oder fest Breath Rate Atemfrequenz Fixed Festgelegt Fixed Backup Breath BPM Festgelegte Sicherung des BPM-Atems Minimum breaths per minute (BPM) below which a timed breath will be initiated Minimale Atemzüge pro Minute (BPM), unterhalb derer ein zeitgesteuerter Atemzug eingeleitet wird Breath BPM Atmung BPM Timed Inspiration Zeitliche Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Die Zeit, die ein zeitgesteuerter Atemzug IPAP vor dem Übergang zu EPAP liefert Timed Insp. Zeitgesteuerte Insp. Auto-Trial Duration Dauer der automatischen Prüfung The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Die Anzahl der Tage in der Auto-CPAP-Testperiode, nach denen das Gerät wieder zu CPAP zurückkehrt Auto-Trial Dur. Auto-Versuch Dur. EZ-Start EZ-Start Whether or not EZ-Start is enabled Ob EZ-Start aktiviert ist oder nicht Variable Breathing Variable Atmung UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend UNBESTÄTIGT: Möglicherweise variable Atmung, d.h. Perioden mit hoher Abweichung vom Spitzenwert des inspiratorischen Flusses Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Sobald Sie ein Upgrade durchführen, können Sie dieses Profil nicht mehr mit der vorherigen Version verwenden. Passover Befeuchter, bei denen die Luft nur die Wasseroberfläche überströmt Peak Flow Spitzenfluss Peak flow during a 2-minute interval Spitzenfluss während eines 2-Minuten-Intervalls Recompressing Session Files Sitzungsdateien neu komprimieren OSCAR crashed due to an incompatibility with your graphics hardware. OSCAR stürzte aufgrund einer Inkompatibilität mit Ihrer Grafikhardware ab. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. Um dieses Problem zu lösen, ist OSCAR zu einer langsameren, aber kompatibleren Zeichenmethode zurückgekehrt. Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Konnte Channels.xml nicht parsen, OSCAR kann nicht weitermachen und wird beendet. (1 day ago) (vor 1 Tag) (%2 days ago) (%2 Tage zuvor) New versions file improperly formed Neue Versionen Datei unsachgemäß gebildet A more recent version of OSCAR is available Eine neuere Version von OSCAR ist verfügbar release Veröffentlichung test version Testversion You are running the latest %1 of OSCAR Sie verwenden die neueste %1 von OSCAR You are running OSCAR %1 Sie verwenden OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 ist verfügbar <a href='%2'>hier</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Informationen über die neuere Testversion %1 sind unter <a href='%2'>%2</a> verfügbar Check for OSCAR Updates Nach OSCAR-Updates suchen Unable to create the OSCAR data folder at Der OSCAR-Datenordner konnte nicht erstellt werden unter The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Das Popup-Fenster ist voll. Sie sollten die vorhandenen Popup-Fenster, löschen Sie es, und öffnen Sie dann dieses Diagramm erneut. Essentials Grundlagen Plus Plus Unable to write to OSCAR data directory OSCAR-Datenverzeichnis kann nicht beschrieben werden Error code Fehlercode OSCAR cannot continue and is exiting. OSCAR kann nicht weitergeführt werden und wird geschlossen. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Kann nicht in das Debug-Protokoll schreiben. Sie können immer noch das Debug-Fenster (Hilfe/Fehlerbehebung/Debug-Fenster anzeigen) verwenden, aber das Debug-Protokoll wird nicht auf die Festplatte geschrieben. For internal use only Nur für den internen Gebrauch Choose the SleepyHead or OSCAR data folder to migrate Wählen Sie den zu migrierenden SleepyHead- oder OSCAR-Datenordner The folder you chose does not contain valid SleepyHead or OSCAR data. Der von Ihnen gewählte Ordner enthält keine gültigen SleepyHead- oder OSCAR-Daten. If you have been using SleepyHead or an older version of OSCAR, Wenn Sie SleepyHead oder eine ältere Version von OSCAR verwendet haben, OSCAR can copy your old data to this folder later. OSCAR kann Ihre alten Daten später in diesen Ordner kopieren. Migrate SleepyHead or OSCAR Data? SleepyHead- oder OSCAR-Daten migrieren? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Auf dem nächsten Bildschirm werden Sie von OSCAR aufgefordert, einen Ordner mit SleepyHead- oder OSCAR-Daten auszuwählen Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Klicken Sie auf [OK], um zum nächsten Bildschirm zu gelangen, oder auf [Nein], wenn Sie keine SleepyHead- oder OSCAR-Daten verwenden möchten. Chromebook file system detected, but no removable device found Chromebook-Dateisystem erkannt, aber kein Wechseldatenträger gefunden You must share your SD card with Linux using the ChromeOS Files program Sie müssen Ihre SD-Karte unter Linux mit dem Programm ChromeOS Files freigeben Flex Flex Unable to check for updates. Please try again later. Suche nach Updates nicht möglich. Bitte versuchen Sie es später noch einmal. 99.5% 99.5% varies variiert EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1-%2 IPAP %3-%4 (%5) Backing up files... Sichern von Dateien... Reading data files... Lesen von Datendateien... Snoring event. Schnarch-Ereignis. SN SN model %1 Modell %1 DreamStation 2 DreamStation 2 unknown model unbekanntes Modell Target Time Zielzeit PRS1 Humidifier Target Time PRS1 Luftbefeuchter Zielzeit Hum. Tgt Time Brummen. Tgt Zeit iVAPS iVAPS Soft Weich Standard Standard BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T PAC PAC SmartStop Intelligenter Stop Machine auto stops by breathing Gerät stoppt automatisch beim Atmen Smart Stop Intelligenter Stop Simple Einfach Advanced Fortgeschrittene Your ResMed CPAP machine (Model %1) has not been tested yet. Ihr ResMed CPAP-Gerät (Modell %1) wurde noch nicht getestet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Er scheint mit anderen Rechnern so ähnlich zu sein, dass er funktionieren könnte, aber die Entwickler hätten gerne eine .zip-Kopie der SD-Karte dieses Rechners, um sicherzustellen, dass er mit OSCAR funktioniert. Humidity Luftfeuchtigkeit SleepStyle Schlafstil Apnea Apnoe An apnea reportred by your CPAP machine. Eine Apnoe, die von Ihrem CPAP-Gerät gemeldet wird. AI=%1 AI=%1 Response Antwort Patient View Patient Ansicht SensAwake level Sinneswahrnehmungslevel Expiratory Relief Druckentlastung beim Ausatmen Expiratory Relief Level Ausatemdruckentlastungs-Niveau This page in other languages: Diese Seite in anderen Sprachen: %1 Graphs %1 Grafiken %1 of %2 Graphs %1 von %2 Grafiken %1 Event Types %1 Ereignistypen %1 of %2 Event Types %1 von %2 Ereignistypen Report about:blank Leere Seite SessionBar %1h %2m %1h %2m No Sessions Present Gegenwärtig keine Sitzung SleepStyleLoader Import Error Import Fehler This Machine Record cannot be imported in this profile. Dieser Geräte-Datensatz kann in diesem Profil nicht importiert werden. The Day records overlap with already existing content. Die Aufzeichnungen dieses Tages überschneiden sich mit bereits vorhandenen Inhalt. Statistics Days Tage Worst Flow Limtation Schlechteste Flusslimitierung Worst Large Leaks Die schlechtesten großen Lecks Oximeter Statistics Oxymetrie-Statistik Date: %1 Leak: %2% Datum: %1 Leck: %2% CPAP Usage CPAP-Nutzung Blood Oxygen Saturation Blutsauerstoffsättigung Date: %1 - %2 Datum: %1 - %2 No PB on record Kein PB aufgezeichnet % of time in %1 % der Zeit in %1 Last 30 Days Die letzten 30 Tage Want more information? Möchten Sie weitere Informationen? Days Used: %1 Tage verwendet: %1 %1 Index %1 Index Worst RX Setting Schlechteste RX Einstellungen Best RX Setting Beste RX Einstellungen %1 day of %2 Data on %3 %1 Tage %2 Daten über %3 Date: %1 CSR: %2% Datum: %1 CSR: %2% % of time above %1 threshold % der Zeit über %1 Schwelle Therapy Efficacy Therapie Effizienz % of time below %1 threshold % der Zeit unter %1 Schwelle Max %1 Max %1 %1 Median %1 Medianwert Min %1 Min %1 Most Recent Der letzte Tag Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Bitte aktivieren Sie das Vor-Laden der Zusammenfassungen Checkbox in den Einstellungen, um sicherzustellen, dass diese Daten verfügbar sind. Pressure Settings Druckeinstellungen Phone: %1 Telefon: %1 Worst PB Schlechtesten PB Pressure Statistics Druck-Statistik Name: %1, %2 Name: %1, %2 Last 6 Months Die letzten 6 Monate Email: %1 E-mail: %1 Average %1 Durchschnittliche(r) %1 No %1 data available. Keine %1 Daten verfügbar. Last Use Zuletzt verwendet Pressure Relief Druckentlastung DOB: %1 Geb.-Datum: %1 Pulse Rate Pulsrate First Use Erste Verwendung Worst CSR Schlechtester CSR Worst AHI Schlechtester AHI Last Week Letzte Woche Last Year Letztes Jahr Best Flow Limitation Beste Flusslimitierung Address: Adresse: Details Details No Flow Limitation on record Keine eingeschränkte Durchflussbegrenzung %1 days of %2 Data, between %3 and %4 %1 Tage %2 Daten, zwischen %3 und %4 No Large Leaks on record Keine großen Lecks aufgenommen Date: %1 PB: %2% Daten: %1 PB: %2% Best AHI Bester AHI Last Session Die letzte Sitzung Date: %1 AHI: %2 Datum: %1 AHI: %2 Machine Information Geräte-Informationen CPAP Statistics CPAP-Statistik Compliance: %1% Therapietreue: %1% OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR benötigt alle geladenen Übersichtsdaten, um die besten/schlechtesten Daten für einzelne Tage zu berechnen. Date: %1 FL: %2 Datum: %1 FL: %2 Days AHI of 5 or greater: %1 Tage mit einem AHI von 5 oder mehr als: %1 Low Use Days: %1 Tage mit geringer Nutzung: %1 Leak Statistics Leck-Statistik No CSR on record Kein CSR aufgenommen Average Hours per Night Durchschnittliche Stunden pro Nacht OSCAR is free open-source CPAP report software OSCAR ist eine kostenlose Open-Source CPAP-Berichtssoftware Oscar has no data to report :( Oscar hat keine Daten zu melden :( Compliance (%1 hrs/day) Einhaltung (%1 Std./Tag) Changes to Machine Settings Änderungen an den Geräteeinstellungen No data found?!? Keine Daten gefunden?!?? AHI: %1 AHI: %1 Total Hours: %1 Total Stunden: %1 This report was prepared on %1 by OSCAR %2 Dieser Bericht wurde am %1 von OSCAR %2 erstellt Welcome over über under unter Your average leaks were %1 %2, which is %3 your %4 day average of %5. Der durchschnittliche Leckwert war %1 %2, was %3 Ihrem %4 Tagesdurchschnitt von %5 liegt. No CPAP data has been imported yet. Es wurden noch keine CPAP-Daten importiert. Daily View Tagesansicht Oximetry Wizard Oxymetrie-Assistent last night letzte Nacht What would you like to do? Was möchten Sie tun? was %1 (on %2) war %1 (am %2) as there are some options that affect import. da es einige Optionen gibt, die den Import beeinflussen. Your machine was on for %1. Ihr Gerät lief während %1. You had an AHI of %1, which is %2 your %3 day average of %4. Sie hatten einen AHI von %1. Das liegt %2 Ihrem %3 Tagesdurchschnitt von %4. <font color = red>You only had the mask on for %1.</font> <font color = red>Sie benutzten die Maske nur %1.</font> First import can take a few minutes. Der erste Import kann ein paar Minuten dauern. equal to gleich It would be a good idea to check File->Preferences first, Es empfiehlt sich, zuerst die Datei-> Preferences zu überprüfen, %2 days ago vor %2 Tagen Statistics Statistiken CPAP Importer CPAP-Importeur Overview Überblick Your machine was under %1-%2 %3 for %4% of the time. Ihr Gerät war unter %1-%2 %3 für %4% diese Zeit. reasonably close to ziemlich nahe an Note that some preferences are forced when a ResMed machine is detected Beachten Sie, dass einige Einstellungen erzwungen werden, wenn ein ResMed-Gerät erkannt wird %1 hours, %2 minutes and %3 seconds %1 Stunden, %2 Minuten und %3 Sekunden The last time you used your %1... Die letzte Verwendung von %1... Welcome to the Open Source CPAP Analysis Reporter Willkommen beim Open Source CPAP Analysis Reporter Your CPAP machine used a constant %1 %2 of air Ihr CPAP-Gerät hat verwendete konstant %1 %2 Luft Your pressure was under %1 %2 for %3% of the time. Ihr Druck lag unter %1 %2 für %3% dieser Zeit. Your machine used a constant %1-%2 %3 of air. Ihr Gerät verwendete konstant %1-%2 %3 Luft. Your EPAP pressure fixed at %1 %2. Ihr EPAP Druck fixiert auf %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Ihr IPAP- Druck war unter %1 %2 für %3% dieser Zeit. Your EPAP pressure was under %1 %2 for %3% of the time. Ihre EPAP- Druck war unter %1 %2 für %3% dieser Zeit. <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Achtung: </span><span style=" color:#ff0000;">ResMed S9 SDCards müssen gesperrt werden </span><span style=" font-weight:600; color:#ff0000;">vor dem Einsetzen in Ihren Computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Einige Betriebssysteme schreiben ungefragt Indexdateien auf die Karte, was Ihre Karte für Ihr CPAP-Gerät unlesbar machen kann.</span></p></body></html> 1 day ago vor 1 Tag gGraph %1 days %1 Tage gGraphView Clone %1 Graph Klone Grafik %1 Oximeter Overlays Oxymeter-Überlagerung Plots Plots Resets all graphs to a uniform height and default order. Setzen Sie alle auf einheitliche Höhe und Standardreihenfolge. Remove Clone Klon entfernen Dotted Lines gestrichelte Linien CPAP Overlays CPAP-Überlagerung Y-Axis Y-Achse Reset Graph Layout Zurücksetzen von Grafiklayout 100% zoom level 100% Zoom-Stufe Double click title to pin / unpin Click and drag to reorder graphs Doppelklicken Sie auf den Titel, umm anheften/entfernen Klicken und ziehen Sie, um Diagramme neu zu ordnen Restore X-axis zoom to 100% to view entire selected period. Stellen Sie den X-Achsen-Zoom auf 100% zurück, um den gesamten ausgewählten Zeitraum zu betrachten. Restore X-axis zoom to 100% to view entire day's data. Stellen Sie den X-Achsen-Zoom auf 100% wieder her, um die Daten des gesamten Tages anzuzeigen. OSCAR-code-v1.3.1/Translations/English.en_UK.ts000066400000000000000000013701511417327530600211230ustar00rootroot00000000000000 AboutDialog &About Release Notes Credits GPL License Close Show data folder About OSCAR %1 Sorry, could not locate About file. Sorry, could not locate Credits file. Sorry, could not locate Release Notes. OSCAR %1 Important: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. To see if the license text is available in your language, see %1. CMS50F37Loader Could not find the oximeter file: Could not open the oximeter file: CMS50Loader Could not get data transmission from oximeter. Please ensure you select 'upload' from the oximeter devices menu. Could not find the oximeter file: Could not open the oximeter file: CheckUpdates Checking for newer OSCAR versions Daily Go to the previous day Show or hide the calender Go to the next day Go to the most recent day with data records Events View Size Notes Journal B u Color Colour Small Medium Big Zombie I'm feeling ... Weight If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value If height is greater than zero in Preferences Dialogue, setting weight here will show Body Mass Index (BMI) value Awesome B.M.I. Bookmarks Add Bookmark Starts Remove Bookmark Flags Graphs Show/hide available graphs. Breakdown events UF1 UF2 Time at Pressure No %1 events are recorded this day %1 event %1 events Session Start Times Session End Times Session Information Oximetry Sessions Duration (Mode and Pressure settings missing; yesterday's shown.) This bookmark is in a currently disabled area.. CPAP Sessions Details Sleep Stage Sessions Position Sensor Sessions Unknown Session Machine Settings Model %1 - %2 PAP Mode: %1 This day just contains summary data, only limited information is available. Total ramp time Time outside of ramp Start End Unable to display Pie Chart on this system 10 of 10 Event Types This CPAP machine does NOT record detailed data Sorry, this machine only provides compliance data. "Nothing's here!" No data is available for this day. 10 of 10 Graphs Oximeter Information Click to %1 this session. disable enable %1 Session #%2 %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. SpO2 Desaturations Pulse Change events SpO2 Baseline Used Statistics 99.5% Total time in apnea Total time in apnoea Time over leak redline Event Breakdown Sessions all off! Sessions exist for this day but are switched off. Impossibly short session Zero hours?? BRICK :( Complain to your Equipment Provider! Pick a Colour Bookmark at %1 ExportCSV Export as CSV Dates: Resolution: Details Sessions Daily Filename: Cancel Export Start: End: Quick Range: Most Recent Day Last Week Last Fortnight Last Month Last 6 Months Last Year Everything Custom Details_ Sessions_ Summary_ Select file to export to CSV Files (*.csv) DateTime Session Event Data/Duration Date Session Count Start End Total Time AHI AHI Count FPIconLoader Import Error This Machine Record cannot be imported in this profile. The Day records overlap with already existing content. Help Hide this message Search Topic: Help Files are not yet available for %1 and will display in %2. Help files do not appear to be present. HelpEngine did not set up correctly HelpEngine could not register documentation correctly. Contents Index Search No documentation available Please wait a bit.. Indexing still in progress No %1 result(s) for "%2" clear MD300W1Loader Could not find the oximeter file: Could not open the oximeter file: MainWindow &Statistics Report Mode Standard Monthly Date Range Statistics Daily Overview Oximetry Import Help &File &View &Reset Graphs &Help Troubleshooting &Data &Advanced Purge ALL Machine Data Rebuild CPAP Data &Import CPAP Card Data Show Daily view Show Overview view &Maximize Toggle Maximize window Reset Graph &Heights Reset sizes of graphs Show Right Sidebar Show Statistics view Import &Dreem Data Show &Line Cursor Show Daily Left Sidebar Show Daily Calendar Create zip of CPAP data card Create zip of OSCAR diagnostic logs Create zip of all OSCAR data Report an Issue System Information Show &Pie Chart Show Pie Chart on Daily page Standard graph order, good for CPAP, APAP, Bi-Level Advanced Advanced graph order, good for ASV, AVAPS Show Personal Data Check For &Updates Purge Current Selected Day &CPAP &Oximetry &Sleep Stage &Position &All except Notes All including &Notes &Preferences &Profiles &About OSCAR Show Performance Information CSV Export Wizard Export for Review E&xit Exit View &Daily View &Overview View &Welcome Use &AntiAliasing Show Debug Pane Take &Screenshot O&ximetry Wizard Print &Report &Edit Profile Import &Viatom/Wellue Data Daily Calendar Backup &Journal Online Users &Guide &Frequently Asked Questions &Automatic Oximetry Cleanup Change &User Purge &Current Selected Day Right &Sidebar Daily Sidebar View S&tatistics Navigation Bookmarks Records Exp&ort Data Profiles Purge Oximetry Data View Statistics Import &ZEO Data Import RemStar &MSeries Data Sleep Disorder Terms &Glossary Change &Language Change &Data Folder Import &Somnopose Data Current Days Welcome &About Please wait, importing from backup folder(s)... Import Problem Please insert your CPAP data card... Access to Import has been blocked while recalculations are in progress. CPAP Data Located Import Reminder Find your CPAP data card Importing Data Choose where to save screenshot Image files (*.png) The User's Guide will open in your default browser The FAQ is not yet implemented If you can read this, the restart command didn't work. You will have to do it yourself manually. Are you sure you want to rebuild all CPAP data for the following machine: For some reason, OSCAR does not have any backups for the following machine: OSCAR does not have any backups for this machine! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: No help is available. %1's Journal Choose where to save journal XML Files (*.xml) Export review is not yet implemented Would you like to zip this card? Choose where to save zip ZIP files (*.zip) Creating zip... Calculating size... Reporting issues is not yet implemented OSCAR Information Help Browser %1 (Profile: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Please open a profile first. Check for updates not implemented Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Are you really sure you want to do this? Because there are no internal backups to rebuild from, you will have to restore from your own. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Note as a precaution, the backup folder will be left in place. Are you <b>absolutely sure</b> you want to proceed? The Glossary will open in your default browser There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete Are you sure you want to delete oximetry data for %1 <b>Please be aware you can not undo this operation!</b> Select the day with valid oximetry data in daily view first. Loading profile "%1" Imported %1 CPAP session(s) from %2 Import Success Already up to date with CPAP data at %1 Up to date Couldn't find any valid Machine Data at %1 Choose a folder No profile has been selected for Import. Import is already running in the background. A %1 file structure for a %2 was located at: A %1 file structure was located at: Would you like to import from this location? Specify Access to Preferences has been blocked until recalculation completes. There was an error saving screenshot to file "%1" Screenshot saved to file "%1" Please note, that this could result in loss of data if OSCAR's backups have been disabled. There was a problem opening MSeries block File: MSeries Import complete MinMaxWidget Auto-Fit Defaults Override The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. The Minimum Y-Axis value.. Note this can be a negative number if you wish. The Maximum Y-Axis value.. Must be greater than Minimum to work. Scaling Mode This button resets the Min and Max to match the Auto-Fit NewProfile Edit User Profile I agree to all the conditions above. User Information User Name Password Protect Profile Password ...twice... Locale Settings Country TimeZone about:blank Very weak password protection and not recommended if security is required. DST Zone Personal Information (for reports) First Name Last Name It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. D.O.B. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> Gender Male Female Height Metric English Contact Information Address Email Phone CPAP Treatment Information Date Diagnosed Untreated AHI CPAP Mode CPAP APAP Bi-Level ASV RX Pressure Doctors / Clinic Information Doctors Name Practice Name Patient ID &Cancel &Back &Next Select Country Welcome to the Open Source CPAP Analysis Reporter This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. PLEASE READ CAREFULLY Accuracy of any data displayed is not and can not be guaranteed. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Use of this software is entirely at your own risk. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Please provide a username for this profile Passwords don't match Profile Changes Accept and save this information? &Finish &Close this window Overview Range: Last Week Last Two Weeks Last Month Last Two Months Last Three Months Last 6 Months Last Year Everything Custom Snapshot Start: End: Reset view to selected date range Toggle Graph Visibility Drop down to see list of graphs to switch on/off. Graphs Respiratory Disturbance Index Apnea Hypopnea Index Apnoea Hypopnea Index Usage Usage (hours) Session Times Total Time in Apnea Total Time in Apnoea Total Time in Apnea (Minutes) Total Time in Apnoea (Minutes) Body Mass Index How you felt (0-10) 10 of 10 Charts Show all graphs Hide all graphs OximeterImport Oximeter Import Wizard Skip this page next time. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> Record attached to computer overnight (provides plethysomogram) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Please connect your oximeter device If you can read this, you likely have your oximeter type set wrong in preferences. Please connect your oximeter device, turn it on, and enter the menu Press Start to commence recording Show Live Graphs Duration Pulse Rate Multiple Sessions Detected Start Time Details Import Completed. When did the recording start? Oximeter Starting time I want to use the time reported by my oximeter's built in clock. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> Choose CPAP session to sync to: You can manually adjust the time here if required: HH:mm:ssap &Cancel &Information Page CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 Set device date/time <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> Set device identifier Erase session after successful upload Import directly from a recording on a device <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> Please choose which one you want to import into OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> &Retry &Choose Session &End Recording &Sync and Save &Save and Finish &Start Scanning for compatible oximeters Could not detect any connected oximeter devices. Connecting to %1 Oximeter Renaming this oximeter from '%1' to '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. "%1", session %2 Nothing to import Your oximeter did not have any valid sessions. Close Waiting for %1 to start Waiting for the device to start the upload process... Select upload option on %1 You need to tell your oximeter to begin sending data to the computer. Please connect your oximeter, enter it's menu and select upload to commence data transfer... %1 device is uploading data... Please wait until oximeter upload process completes. Do not unplug your oximeter. Oximeter import completed.. Select a valid oximetry data file Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Live Oximetry Mode Live Oximetry Stopped Live Oximetry import has been stopped Oximeter Session %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Oximeter not detected Couldn't access oximeter Starting up... If you can still read this after a few seconds, cancel and try again Live Import Stopped %1 session(s) on %2, starting at %3 No CPAP data available on %1 Recording... Finger not detected I want to use the time my computer recorded for this live oximetry session. I need to set the time manually, because my oximeter doesn't have an internal clock. Something went wrong getting session data Welcome to the Oximeter Import Wizard Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnoea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Please remember: Important Notes: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Oximetry Date d/MM/yy h:mm:ss AP R&eset Pulse &Open .spo/R File Serial &Import &Start Live Serial Port &Rescan Ports PreferencesDialog Preferences &Import Combine Close Sessions Minutes Multiple sessions closer together than this value will be kept on the same day. Ignore Short Sessions <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Sessions starting before this time will go to the previous calendar day. Session Storage Options Compress SD Card Backups (slower first import, but makes backups smaller) &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. hours Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Flow Restriction Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apnoeas. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> Duration of airflow restriction s Event Duration Allow duplicates near machine events. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. minutes Reset the counter to zero at beginning of each (time) window. Zero Reset CPAP Clock Drift Do not import sessions older than: Sessions older than this date will not be imported dd MMMM yyyy User definable threshold considered large leak Whether to show the leak redline in the leak graph Search &Oximetry Show in Event Breakdown Piechart Resync Machine Detected Events (Experimental) Percentage drop in oxygen saturation Pulse Sudden change in Pulse Rate of at least this amount bpm Minimum duration of drop in oxygen saturation Minimum duration of pulse change event. Small chunks of oximetry data under this amount will be discarded. &General Changes to the following settings needs a restart, but not a recalc. Preferred Calculation Methods Middle Calculations Upper Percentile Session Splitting Settings <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Memory and Startup Options Pre-Load all summary data at startup <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> Keep Waveform/Event data in memory <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Cuts down on any unimportant confirmation dialogues during import.</p></body></html> Import without asking for confirmation This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Calculate Unintentional Leaks When Not Present Note: A linear calculation method is used. Changing these values requires a recalculation. General CPAP and Related Settings Enable Unknown Events Channels AHI Apnea Hypopnea Index AHI RDI Respiratory Disturbance Index AHI/Hour Graph Time Window Preferred major event index Compliance defined as Flag leaks over threshold Seconds <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> Hours For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Median is recommended for ResMed users. Median Weighted Average Normal Average True Maximum 99% Percentile Maximum Calcs General Settings Daily view navigation buttons will skip over days without data records Skip over Empty Days Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Enable Multithreading Bypass the login screen and load the most recent User Profile Create SD Card Backups during Import (Turn this off at your own peril!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> Combined Count divided by Total Hours Time Weighted average of Indice Standard average of indice Custom CPAP User Event Flagging Events Reset &Defaults <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> Waveforms Flag rapid changes in oximetry stats Other oximetry options Discard segments under Flag Pulse Rate Above Flag Pulse Rate Below Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Compress Session Data (makes OSCAR data smaller, but day changing slower.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> 4 cmH2O 20 cmH2O This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Show flags for machine detected events that haven't been identified yet. Show Remove Card reminder notification on OSCAR shutdown Check for new version every days. Last Checked For Updates: TextLabel I want to be notified of test versions. (Advanced users only please.) &Appearance Graph Settings <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> Bar Tops Line Chart Overview Linecharts Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> How long you want the tooltips to stay visible. Scroll Dampening Tooltip Timeout Default display height of graphs in pixels Graph Tooltips The visual method of displaying waveform overlay flags. Standard Bars Top Markers Graph Height Changing SD Backup compression options doesn't automatically recompress backup data. Auto-Launch CPAP Importer after opening profile Automatically load last used profile on start-up <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered Your masks vent rate at 20 cmH2O pressure Your masks vent rate at 4 cmH2O pressure l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Always save screenshots in the OSCAR Data folder Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR On Opening Profile Welcome Daily Statistics Switch Tabs No change After Import Overlay Flags Line Thickness The pixel thickness of line plots Other Visual Settings Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Use Anti-Aliasing Makes certain plots look more "square waved". Square Wave Plots Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Use Pixmap Caching <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> Animations && Fancy Stuff Whether to allow changing yAxis scales by double clicking on yAxis labels Allow YAxis Scaling Whether to include machine serial number on machine settings changes report Include Serial Number Graphics Engine (Requires Restart) Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Fonts (Application wide settings) Font Size Bold Italic Application Graph Text Graph Titles Big Text Details &Cancel &Ok Name Color Colour Flag Type Label CPAP Events Oximeter Events Positional Events Sleep Stage Events Unknown Events Double click to change the descriptive name this channel. Double click to change the default color for this channel plot/flag/data. Double click to change the default colour for this channel plot/flag/data. Overview <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> Double click to change the descriptive name the '%1' channel. Whether this flag has a dedicated overview chart. Here you can change the type of flag shown for this event This is the short-form label to indicate this channel on screen. This is a description of what this channel does. Lower Upper CPAP Waveforms Oximeter Waveforms Positional Waveforms Sleep Stage Waveforms Whether a breakdown of this waveform displays in overview. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Data Processing Required A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Data Reindex Required A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Restart Required One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. If you need to conserve disk space, please remember to carry out manual backups. Are you sure you want to disable these backups? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Are you really sure you want to do this? %1 %2 Flag Minor Flag Span Always Minor No CPAP machines detected Will you be using a ResMed brand machine? Never This may not be a good idea ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ProfileSelector Filter: Reset filter to see all profiles Version &Open Profile &Edit Profile &New Profile Profile: None Please select or create a profile... Destroy Profile Profile Ventilator Brand Ventilator Model Other Data Last Imported Name You must create a profile Enter Password for %1 You entered an incorrect password Forgot your password? Ask on the forums how to reset it, it's actually pretty easy. Select a profile first The selected profile does not appear to contain any data and cannot be removed by OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. You are about to destroy profile '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. DELETE Sorry You need to enter DELETE in capital letters. There was an error deleting the profile directory, you need to manually remove it. Profile '%1' was succesfully deleted Bytes KB MB GB TB PB Summaries: Events: Backups: Hide disk usage information Show disk usage information Name: %1, %2 Phone: %1 Email: <a href='mailto:%1'>%1</a> Address: No profile information given Profile: %1 ProgressDialog Abort QObject No Data Events Duration (% %1 in events) Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ft lb oz Kg cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours Min %1 Hours: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 %1 Length: %3 Start: %2 Mask On Mask Off %1 Length: %3 Start: %2 TTIA: TTIA: %1 Minutes Seconds h m s ms Events/hr Hz bpm Litres ml Breaths/min ? Severity (0-1) Degrees Error Warning Information Busy Please Note Graphs Switched Off Sessions Switched Off &Yes &No &Cancel &Destroy &Save BMI Weight Zombie Pulse Rate Plethy Pressure Daily Profile Overview Oximetry Oximeter Event Flags Default CPAP BiPAP Bi-Level EPAP Min EPAP Max EPAP IPAP Min IPAP Max IPAP APAP ASV AVAPS ST/ASV Humidifier H OA A CA FL SA LE EP VS VS2 RERA PP P RE NR NRI O2 PC UF1 UF2 UF3 PS AHI AHI RDI AI HI UAI CAI FLI REI EPI PB IE Insp. Time Exp. Time Resp. Event Flow Limitation Flow Limit SensAwake Pat. Trig. Breath Tgt. Min. Vent Target Vent. Minute Vent. Tidal Volume Resp. Rate Snore Leak Leaks Large Leak LL Total Leaks Unintentional Leaks MaskPressure Flow Rate Sleep Stage Usage Sessions Pr. Relief No Data Available App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Software Engine ANGLE / OpenGLES Desktop OpenGL m cm in Only Settings and Compliance Data Available Summary Data Only Bookmarks Mode Model Brand Serial Series Machine Channel Settings Inclination Orientation Motion Name DOB Phone Address Email Patient ID Date Bedtime Wake-up Mask Time Unknown None Ready First Last Start End On Off Yes No Min Max Med Average Median Avg W-Avg Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Machine Unsupported I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Scanning Files... Importing Sessions... Finishing up... Machine Untested Flex Lock Whether Flex settings are available to you. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Mask Resistance Setting Mask Resist. Hose Diam. 15mm 22mm Backing Up Files... Untested Data model %1 DreamStation 2 unknown model CPAP-Check AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS Flex Mode PRS1 pressure relief mode. C-Flex C-Flex+ A-Flex P-Flex Rise Time Bi-Flex Flex Flex Level PRS1 pressure relief setting. Passover Target Time PRS1 Humidifier Target Time Hum. Tgt Time Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Type of ramp curve to use. Linear SmartRamp Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Fixed Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Breath BPM Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled Variable Breathing UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend Peak Flow Peak flow during a 2-minute interval Humidifier Status PRS1 humidifier connected? Disconnected Connected Humidification Mode PRS1 Humidification Mode Humid. Mode Fixed (Classic) Adaptive (System One) Heated Tube Tube Temperature PRS1 Heated Tube Temperature Tube Temp. PRS1 Humidifier Setting Hose Diameter Diameter of primary CPAP hose 12mm Auto On A few breaths automatically starts machine Auto Off Machine automatically switches off Mask Alert Whether or not machine allows Mask checking. Show AHI Breathing Not Detected A period during a session where the machine could not detect flow. BND Timed Breath Machine Initiated Breath TB Windows User Using , found SleepyHead - You must run the OSCAR Migration Tool <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> Launching Windows Explorer failed Could not find explorer.exe in path to launch Windows Explorer. OSCAR %1 needs to upgrade its database for %2 %3 %4 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> OSCAR does not yet have any automatic card backups stored for this device. This means you will need to import this machine data again afterwards from your own backups or data card. Important: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Are you ready to upgrade, so you can run the new version of OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Machine Database Changes Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. The machine data folder needs to be removed manually. This folder currently resides at the following location: Rebuilding from %1 Backup Therapy Pressure Inspiratory Pressure Lower Inspiratory Pressure Higher Inspiratory Pressure Expiratory Pressure Lower Expiratory Pressure Higher Expiratory Pressure Pressure Support PS Min Pressure Support Minimum PS Max Pressure Support Maximum Min Pressure Minimum Therapy Pressure Max Pressure Maximum Therapy Pressure Ramp Time Ramp Delay Period Ramp Pressure Starting Ramp Pressure Ramp Event Ramp An abnormal period of Cheyne Stokes Respiration An apnea that couldn't be determined as Central or Obstructive. An apnoea that couldn't be determined as Central or Obstructive. A restriction in breathing from normal, causing a flattening of the flow waveform. Vibratory Snore (VS2) A ResMed data item: Trigger Cycle Event Mask On Time Time started according to str.edf Summary Only An apnea where the airway is open An apnoea where the airway is open An apnea caused by airway obstruction An apnoea caused by airway obstruction Hypopnea Hypopnoea A partially obstructed airway Unclassified Apnea Unclassified Apnoea UA Vibratory Snore A vibratory snore A vibratory snore as detcted by a System One machine Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A large mask leak affecting machine performance. Non Responding Event A type of respiratory event that won't respond to a pressure increase. Expiratory Puff Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. User Flag #1 User Flag #2 User Flag #3 Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm Pulse Change A sudden (user definable) change in heart rate SpO2 Drop A sudden (user definable) drop in blood oxygen saturation SD Breathing flow rate waveform L/min Mask Pressure Amount of air displaced per breath Graph displaying snore volume Minute Ventilation Amount of air displaced per minute Respiratory Rate Rate of breaths per minute Patient Triggered Breaths Percentage of breaths triggered by patient Pat. Trig. Breaths Leak Rate Rate of detected mask leakage I:E Ratio Ratio between Inspiratory and Expiratory time ratio Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting Cheyne Stokes Respiration CSR Periodic Breathing An abnormal period of Periodic Breathing Clear Airway Obstructive Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Leak Flag LF A user definable event detected by OSCAR's flow waveform processor. Perfusion Index A relative assessment of the pulse strength at the monitoring site Perf. Index % Mask Pressure (High frequency) Expiratory Time Time taken to breathe out Inspiratory Time Time taken to breathe in Respiratory Event Graph showing severity of flow limitations Flow Limit. Target Minute Ventilation Maximum Leak The maximum rate of mask leakage Max Leaks Apnea Hypopnea Index Apnoea Hypopnea Index Graph showing running AHI for the past hour Total Leak Rate Detected mask leakage including natural Mask leakages Median Leak Rate Median rate of detected mask leakage Median Leaks Respiratory Disturbance Index Graph showing running RDI for the past hour Sleep position in degrees Upright angle in degrees Movement Movement detector CPAP Session contains summary data only PAP Mode Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Apnea An apnea reportred by your CPAP machine. PAP Device Mode APAP (Variable) ASV (Fixed EPAP) ASV (Variable EPAP) Height Physical Height Notes Bookmark Notes Body Mass Index How you feel (0 = like crap, 10 = unstoppable) Bookmark Start Bookmark End Last Updated Journal Notes Journal 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep Brain Wave BrainWave Awakenings Number of Awakenings Morning Feel How you felt in the morning Time Awake Time spent awake Time In REM Sleep Time spent in REM Sleep Time in REM Sleep Time In Light Sleep Time spent in light sleep Time in Light Sleep Time In Deep Sleep Time spent in deep sleep Time in Deep Sleep Time to Sleep Time taken to get to sleep Zeo ZQ Zeo sleep quality measurement ZEO ZQ Debugging channel #1 Test #1 For internal use only Debugging channel #2 Test #2 Zero Upper Threshold Lower Threshold As you did not select a data folder, OSCAR will exit. or CANCEL to skip migration. Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. You cannot use this folder: Migrating files from to OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. We suggest you use this folder: Click Ok to accept this, or No if you want to use a different folder. Choose or create a new folder for OSCAR data Next time you run OSCAR, you will be asked again. The folder you chose is not empty, nor does it already contain valid OSCAR data. Data directory: Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? Question Exiting Are you sure you want to use this folder? Don't forget to place your datacard back in your CPAP machine OSCAR Reminder You can only work with one instance of an individual OSCAR profile at a time. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Loading profile "%1"... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Unable to create zip! Are you sure you want to reset all your channel colors and settings to defaults? Are you sure you want to reset all your channel colours and settings to defaults? Are you sure you want to reset all your waveform channel colors and settings to defaults? There are no graphs visible to print Would you like to show bookmarked areas in this report? Printing %1 Report %1 Report : %1 hours, %2 minutes, %3 seconds RDI %1 AHI %1 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 Entire Day's Flow Waveform Current Selection Entire Day Page %1 of %2 Days: %1 Low Usage Days: %1 (%1% compliant, defined as > %2 hours) (Sess: %1) Bedtime: %1 Waketime: %1 (Summary Only) There is a lockfile already present for this profile '%1', claimed on '%2'. Fixed Bi-Level Auto Bi-Level (Fixed PS) Auto Bi-Level (Variable PS) 99.5% varies n/a Fixed %1 (%2) Min %1 Max %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> (last night) (1 day ago) (%2 days ago) No oximetry data has been imported yet. Contec CMS50 Fisher & Paykel ICON DeVilbiss Intellipap SmartFlex Settings ChoiceMMed MD300 Respironics M-Series Philips Respironics System One ResMed S9 EPR: Somnopose Somnopose Software Zeo Personal Sleep Coach Database Outdated Please Rebuild CPAP Data (%2 min, %3 sec) (%3 sec) Pop out Graph The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View There is no data to graph d MMM yyyy [ %1 - %2 ] Hide All Events Show All Events Unpin %1 Graph Popout %1 Graph Pin %1 Graph Plots Disabled Duration %1:%2:%3 AHI %1 Relief: %1 Hours: %1h, %2m, %3s Machine Information Journal Data OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR will not touch this folder, and will create a new one instead. Please be careful when playing in OSCAR's profile folders :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. OSCAR picked only the first one of these, and will use it in future: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. CMS50F3.7 CMS50F Backing up files... Reading data files... SmartFlex Mode Intellipap pressure relief mode. Ramp Only Full Time SmartFlex Level Intellipap pressure relief level. Snoring event. SN Locating STR.edf File(s)... Cataloguing EDF Files... Queueing Import Tasks... Finishing Up... CPAP Mode VPAPauto ASVAuto iVAPS PAC Auto for Her EPR ResMed Exhale Pressure Relief Patient??? EPR Level Exhale Pressure Relief Level Response Patient View SmartStart Machine auto starts by breathing Smart Start Humid. Status Humidifier Enabled Status Humid. Level Humidity Level Temperature ClimateLine Temperature Temp. Enable ClimateLine Temperature Enable Temperature Enable AB Filter Antibacterial Filter Pt. Access Essentials Plus Climate Control Manual Soft Standard BiPAP-T BiPAP-S BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Advanced Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... Auto Mask ResMed Mask Setting Pillows Full Face Nasal Ramp Enable Weinmann SOMNOsoft2 Snapshot %1 CMS50D+ CMS50E/F Loading %1 data for %2... Scanning Files Migrating Summary File Location Loading Summaries.xml.gz Loading Summary Data Please Wait... Updating Statistics cache Usage Statistics %1 Charts %1 of %2 Charts Loading summaries Dreem Your Viatom device generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Viatom Viatom Software New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to check for updates. Please try again later. SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank SessionBar %1h %2m No Sessions Present SleepStyleLoader Import Error This Machine Record cannot be imported in this profile. The Day records overlap with already existing content. Statistics CPAP Statistics CPAP Usage Average Hours per Night Therapy Efficacy Leak Statistics Pressure Statistics Oximeter Statistics Blood Oxygen Saturation Pulse Rate %1 Median Average %1 Min %1 Max %1 %1 Index % of time in %1 % of time above %1 threshold % of time below %1 threshold Name: %1, %2 DOB: %1 Phone: %1 Email: %1 Address: This report was prepared on %1 by OSCAR %2 Days Used: %1 Low Use Days: %1 Compliance: %1% Days AHI of 5 or greater: %1 Best AHI Date: %1 AHI: %2 Worst AHI Best Flow Limitation Date: %1 FL: %2 Worst Flow Limtation No Flow Limitation on record Worst Large Leaks Date: %1 Leak: %2% No Large Leaks on record Worst CSR Date: %1 CSR: %2% No CSR on record Worst PB Date: %1 PB: %2% No PB on record Want more information? OSCAR needs all summary data loaded to calculate best/worst data for individual days. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Best RX Setting Date: %1 - %2 AHI: %1 Total Hours: %1 Worst RX Setting Most Recent Compliance (%1 hrs/day) OSCAR is free open-source CPAP report software Changes to Machine Settings No data found?!? Oscar has no data to report :( Last Week Last 30 Days Last 6 Months Last Year Last Session Details No %1 data available. %1 day of %2 Data on %3 %1 days of %2 Data, between %3 and %4 Days Pressure Relief Pressure Settings Machine Information First Use Last Use Welcome Welcome to the Open Source CPAP Analysis Reporter What would you like to do? CPAP Importer Oximetry Wizard Daily View Overview Statistics <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> It would be a good idea to check File->Preferences first, as there are some options that affect import. Note that some preferences are forced when a ResMed machine is detected First import can take a few minutes. The last time you used your %1... last night %2 days ago was %1 (on %2) %1 hours, %2 minutes and %3 seconds Your machine was on for %1. <font color = red>You only had the mask on for %1.</font> under over reasonably close to equal to You had an AHI of %1, which is %2 your %3 day average of %4. Your CPAP machine used a constant %1 %2 of air Your pressure was under %1 %2 for %3% of the time. Your machine used a constant %1-%2 %3 of air. Your EPAP pressure fixed at %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Your EPAP pressure was under %1 %2 for %3% of the time. Your machine was under %1-%2 %3 for %4% of the time. 1 day ago Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. gGraph %1 days gGraphView 100% zoom level Restore X-axis zoom to 100% to view entire selected period. Restore X-axis zoom to 100% to view entire day's data. Reset Graph Layout Resets all graphs to a uniform height and default order. Y-Axis Plots CPAP Overlays Oximeter Overlays Dotted Lines Double click title to pin / unpin Click and drag to reorder graphs Remove Clone Clone %1 Graph OSCAR-code-v1.3.1/Translations/Espaniol.es.ts000066400000000000000000014610471417327530600207170ustar00rootroot00000000000000 AboutDialog &About &Acerca de Release Notes Notas de la Versión Credits Créditos GPL License Licencia GPL Close Cerrar Show data folder Mostrar carpeta datos About OSCAR %1 Sorry, could not locate About file. Lo siento, no he podido localizar el archivo. Sorry, could not locate Credits file. Lo siento, no he podido localizar los créditos. Sorry, could not locate Release Notes. Lo siento, no he podido localizar notas de la versión. OSCAR %1 As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Important: Importante: To see if the license text is available in your language, see %1. Para ver sí el texto de la licencia está disponible en su idioma, vea %1. CMS50F37Loader Could not find the oximeter file: No he podido encontrar el archivo del oxímetro: Could not open the oximeter file: No se pudo abrir el archivo del oxímetro: CMS50Loader Could not get data transmission from oximeter. No se recibió transmisión de datos alguna desde el oxímetro. Please ensure you select 'upload' from the oximeter devices menu. Need to know how is 'upload' translated Por favor asegúrese de seleccionar 'upload' o 'descargar' desde el menú de Dispositivos de Oximetría. Could not find the oximeter file: No se pudo encontrar el archivo del oxímetro: Could not open the oximeter file: No se pudo abrir el archivo del oxímetro: CheckUpdates Checking for newer OSCAR versions Daily Go to the previous day Ir al día anterior Show or hide the calender Mostrar u ocultar el calendario Go to the next day Ir al día siguiente Go to the most recent day with data records Ir al día más reciente con datos registrados Events Eventos View Size Ver tamaño Notes Notas Journal or agenda Diario Small Pequeño Medium Mediano Big Grande I'm feeling ... Me siento ... If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Flags Indicadores Graphs Gráficos Show/hide available graphs. Mostrar/ocultar los gráficos disponibles. Color Color u u B B Zombie Zombi Weight Peso Awesome sorprendentes B.M.I. Índice de masa corporal I.M.C. Bookmarks Marcadores Add Bookmark Añadir Marcador Starts ¿inicios? Comienza Remove Bookmark Eliminar Marcador Breakdown desarrollo, descomponer, desarrollar, DESGLOSAR Desglose events eventos No %1 events are recorded this day . al final No hay eventos %1 registrados éste día %1 event Evento %1 %1 events Eventos %1 UF1 UF1 UF2 UF2 Session Start Times Hora de inicio de sesión Session End Times Hora de fin de sesión Duration Duración Position Sensor Sessions Sesiones de Posición del Sensor Unknown Session Sesión Desconocida Time over leak redline mmm Tiempo de fuga sobre la línea roja Event Breakdown Desglose de eventos Unable to display Pie Chart on this system No se puede mostrar el Gráfico Tarta en este sistema Sessions all off! ¡Todas las sesiones deshabilitadas! Sessions exist for this day but are switched off. Existen sesiones para este día pero fueron deshabilitadas. Impossibly short session Una sesión demasiado corta Zero hours?? ¿Cero horas? BRICK :( Ladrillo :( Sorry, this machine only provides compliance data. Lo sentimos, esta máquina sólo proporciona datos de cumplimiento. Complain to your Equipment Provider! ¡Quejese al Proveedor de su Equipo! Statistics Estadísticas Details Detalles Time at Pressure Tiempo a Presión Click to %1 this session. Haga clic en %1 en ésta sesión. disable desactivar enable activar %1 Session #%2 %1 Sesión #%2 %1h %2m %3s %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. Oximeter Information Información del Oxímetro SpO2 Desaturations Desaturaciones SpO2 Pulse Change events Eventos de cambio de pulso SpO2 Baseline Used Línea basal de SpO2 utilizada (Mode and Pressure settings missing; yesterday's shown.) 99.5% 90% {99.5%?} Start Inicio End Final 10 of 10 Event Types This CPAP machine does NOT record detailed data This bookmark is in a currently disabled area.. 10 of 10 Graphs Machine Settings Ajustes de la máquina Session Information Información de sesión CPAP Sessions Sesiones CPAP Oximetry Sessions Sesiones del Oxímetro Sleep Stage Sessions Sesiones de Etapas del Sueño Model %1 - %2 Modelo %1 - %2 PAP Mode: %1 Modo PAP: %1 This day just contains summary data, only limited information is available. Éste día sólo contiene datos resumidos, sólo se dispone de información limitada. Total time in apnea Tiempo total en apnea Total ramp time Tiempo total en rampa Time outside of ramp Tiempo fuera de rampa "Nothing's here!" "¡Aquí no hay nada!" No data is available for this day. No hay datos disponibles para éste día. Pick a Colour Escoja un color Bookmark at %1 Marcador en %1 ExportCSV Export as CSV Exportar como CSV Dates: Fechas: Resolution: Resolución: Details Detalles Sessions Sesiones Daily Diario Filename: Nombre del archivo: Cancel Cancelar Export Exportar Start: Inicio: End: Fin: Quick Range: Intérvalo rápido: Most Recent Day Último Día Last Week Última Semana Last Fortnight Última Quincena Last Month Último Mes Last 6 Months Último Semestre Last Year Último Año Everything Todo Custom Personalizado Details_ Detalles_ Sessions_ Sesiones_ Summary_ Resumen_ Select file to export to Seleccione archivo a exportar CSV Files (*.csv) Archivos CSV (*.csv) DateTime Fecha /Hora Session Sesión Event Evento Data/Duration Datos/Duración Date Fecha Session Count ¿conteo de sesiones? Recuento de sesiones Start Inicio End Fin Total Time Tiempo total AHI IAH Count Conteo FPIconLoader Import Error Error de Importación This Machine Record cannot be imported in this profile. Este registro de máquina no puede ser importado a este perfi. The Day records overlap with already existing content. Estos registros diarios se traslapan con contenido previamente existente. Help Hide this message Ocultar este mensaje Search Topic: Búsqueda Tema: Help Files are not yet available for %1 and will display in %2. Los archivos de ayuda aún no están disponibles para %1 y se mostrarán en %2. Help files do not appear to be present. Los archivos de ayuda no parecen estar presentes. HelpEngine did not set up correctly El sistema de ayuda no se ha configurado correctamente HelpEngine could not register documentation correctly. El sistema de ayuda no pudo registrar la documentación correctamente. Contents Contenido Index Indice Search Buscar No documentation available No hay documentación disponible Please wait a bit.. Indexing still in progress Por favor, espera un poco.. Indexación en curso No no %1 result(s) for "%2" %1 resultado(s) for "%2" clear ¿despejado, lúcido, limpido, despejarse, clarificarse? despejada MD300W1Loader Could not find the oximeter file: No he podido encontrar el archivo del oxímetro: Could not open the oximeter file: No se pudo abrir el archivo del oxímetro: MainWindow &Statistics &Estadísticas &stadísticas Report Mode Modo de informe Standard Estándar Monthly Mensual Date Range Intérvalo de fechas Statistics Estadísticas Daily mmm Vista por día Overview Vista general Oximetry Oximetría Import Importar Help Ayuda &File &Archivo &View &Vista &Reset Graphs &Help A&yuda Troubleshooting &Data &Datos &Advanced &Avanzado &Maximize Toggle Activar &Maximizado Import &Viatom/Wellue Data Report an Issue Informar de un problema Rebuild CPAP Data Restablecer Datos de CPAP &Preferences &Preferencias &Profiles &Perfiles E&xit &Salir Exit Salir View &Daily Ver Vista por &Día View &Overview Ver Vista &General View &Welcome Ver &Bienvenida Use &AntiAliasing Usar &AntiAliasing Show Debug Pane Mostrar panel de depuración Take &Screenshot &Capturar Pantalla O&ximetry Wizard Asistente de O&ximetría Print &Report Imprimir &Reporte &Edit Profile &Editar perfil Show &Line Cursor Show Daily Left Sidebar Show Daily Calendar Show Performance Information Mostrar información de rendimiento Create zip of CPAP data card Create zip of all OSCAR data CSV Export Wizard Asistente de exportación CSV Export for Review Exportar para visualización Exportar para revisión Daily Calendar Calendario diario Backup &Journal espero no se contradiga Respaldar &Diario Online Users &Guide &Guía del Usuario (en línea) &About OSCAR &Acerca de OSCAR &Frequently Asked Questions Preguntas &Frecuentes &Automatic Oximetry Cleanup &Limpieza Automática de Oximetría Change &User Cambiar &Usuario Purge &Current Selected Day &Purgar día actualmente seleccionado Right &Sidebar Acti&var panel derecho Daily Sidebar Barra lateral diaria View S&tatistics Ver Es&tadísticas Navigation Navegación Bookmarks Marcadores Records Registros Exp&ort Data Exp&ortar datos Profiles Perfiles Purge Oximetry Data Purga de datos de oximetría Purge ALL Machine Data &Import CPAP Card Data Show Daily view Show Overview view Maximize window Reset Graph &Heights Reset sizes of graphs Show Right Sidebar View Statistics Show Statistics view Import &ZEO Data Importar Datos de &ZEO Import &Dreem Data Import RemStar &MSeries Data Importar Datos de REMstar y Serie &M Sleep Disorder Terms &Glossary Glosario de &Términos de Transtornos del Sueño Change &Language Cambiar &Idioma Change &Data Folder Cambiar &Directorio de Datos Import &Somnopose Data Importar Datos de &Somnopose Current Days Días Actuales Create zip of OSCAR diagnostic logs System Information Show &Pie Chart Show Pie Chart on Daily page Standard graph order, good for CPAP, APAP, Bi-Level Advanced Advanced graph order, good for ASV, AVAPS Show Personal Data Check For &Updates Purge Current Selected Day &CPAP &CPAP &Oximetry &Oximetría &Sleep Stage &Position &All except Notes All including &Notes Welcome Bienvenida &About &Acerca de Access to Import has been blocked while recalculations are in progress. Se ha bloqueado el acceso a Importar mientras hay recalculaciones en progreso. Importing Data Importando Datos Please wait, importing from backup folder(s)... Por favor espere, importando desde el(los) directorio(s) de respaldo... Import Problem Problema de Importación Help Browser ¿Navegador de ayuda? Búsqueda de ayuda Loading profile "%1" Cargar perfil "%1" Couldn't find any valid Machine Data at %1 No se pudieron encontrar datos de máquina válidos en %1 Please insert your CPAP data card... Por favor inserte la tarjeta de datos del CPAP... Import is already running in the background. La importación ya está corriendo en segundo plano. CPAP Data Located Datos de CPAP encontrados Import Reminder Importar Recordatorio Please note, that this could result in loss of data if OSCAR's backups have been disabled. Tenga en cuenta que esto podría resultar la pérdida de datos sí las copias de seguridad de OSCAR se han desactivado. There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete Export review is not yet implemented Todavía no se ha implementado la revisión de exportación Would you like to zip this card? Choose where to save zip ZIP files (*.zip) Creating zip... Calculating size... Reporting issues is not yet implemented Aún no se han implementado los problemas de presentación de informes Please open a profile first. Por favor, abra primero un perfil. %1's Journal Diario de %1 Choose where to save journal Elija dónde guardar el diario XML Files (*.xml) Archivos XML (*.xml) Access to Preferences has been blocked until recalculation completes. El acceso a Preferencias ha sido bloqueado hasta que se complete la recalculación. The FAQ is not yet implemented FAQ aún no está implementado If you can read this, the restart command didn't work. You will have to do it yourself manually. Si puede leer esto, el comando de reinicio no funcionó. Tendrá que hacerlo usted mismo manualmente. Are you sure you want to rebuild all CPAP data for the following machine: ¿Está seguro de que desea reconstruir todos los datos de CPAP para el siguiente equipo?: For some reason, OSCAR does not have any backups for the following machine: Por alguna razón, OSCAR no tiene ninguna copia de seguridad para el siguiente equipo: You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Está a punto de <font size=+2>borrar la base de datos</font> de la máquina OSCAR para la siguiente máquina:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: Un error de permiso de archivo hizo que el proceso de purga fallara; tendrá que borrar la siguiente carpeta manualmente: No help is available. No hay ayuda disponible. Are you sure you want to delete oximetry data for %1 Está usted seguro de borrar los datos de oximetría para %1 <b>Please be aware you can not undo this operation!</b> <b>¡Tenga en cuenta que no puede deshacer esta operación!</b> Select the day with valid oximetry data in daily view first. Seleccione primero un día con datos de oximetría válidos en la Vista por Día. Imported %1 CPAP session(s) from %2 Se importó(aron) %1 sesión(es) de CPAP desde %2 Import Success Importación Exitosa Already up to date with CPAP data at %1 Datos de CPAP al corriente en %1 Up to date Al corriente Choose a folder Elija un directorio No profile has been selected for Import. No se ha seleccionado ningún perfil para Importar. A %1 file structure for a %2 was located at: Una estructura de archivo %1 para un %2 fue encontrada en: A %1 file structure was located at: Una estructura de archivo %1 fue encontrada en: Would you like to import from this location? ¿Desea usted importar desde esta ubicación? %1 (Profile: %2) Specify Especifique Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Find your CPAP data card Check for updates not implemented Choose where to save screenshot Image files (*.png) There was an error saving screenshot to file "%1" Hubo un error al guardar la captura de pantalla en el archivo "%1" Screenshot saved to file "%1" Captura de pantalla guardada en el archivo "%1" The User's Guide will open in your default browser La Guía del usuario se abrirá en su navegador predeterminado Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Suponiendo que usted ha hecho <i>sus <b>propios</b> respaldos para TODOS sus datos de CPAP</i>, aún puede completar esta operación pero tendrá que restaurar desde sus respaldos manualmente. Are you really sure you want to do this? ¿Está seguro de que quiere hacer ésto? Because there are no internal backups to rebuild from, you will have to restore from your own. Debido a que no hay respaldos internos desde donde reestablecer, tendrá que restaurar usted mismo. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) ¿Le gustaría importar desde sus propios respaldos ahora? (No habrá datos visibles para esta máquina hasta que así lo haga) Note as a precaution, the backup folder will be left in place. Tenga en cuenta que, por precaución, la carpeta de copia de seguridad se dejará en su sitio. OSCAR does not have any backups for this machine! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Are you <b>absolutely sure</b> you want to proceed? ¿Está usted totalmente seguro que desea continuar? OSCAR Information There was a problem opening MSeries block File: Hubo un problema abriendo el Archivo de Bloques del SerieM: MSeries Import complete Importación de SerieM completa The Glossary will open in your default browser El Glosario se abrirá en su navegador predeterminado MinMaxWidget Auto-Fit Ajuste automático Defaults Predeterminados Override Anular The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. El modo de escalado del eje Y, 'Auto-Fit' para el escalado automático, 'Defaults' para los ajustes según el fabricante, y 'Override' para elegir el suyo propio. The Minimum Y-Axis value.. Note this can be a negative number if you wish. El valor mínimo del eje Y... Tenga en cuenta que este puede ser un número negativo si lo desea. The Maximum Y-Axis value.. Must be greater than Minimum to work. El valor máximo del eje Y... Debe ser mayor que el Mínimo para trabajar. Scaling Mode Modo de escalado This button resets the Min and Max to match the Auto-Fit Este botón reajusta el Mínimo y el Máximo para que coincidan con el Ajuste Automático NewProfile Edit User Profile Editar Perfil de Usuario I agree to all the conditions above. Acepto todas las condiciones arriba mencionadas. User Information Información de Usuario User Name Nombre de Usuario Password Protect Profile Perfil Protegido por Contraseña Password Contraseña ...twice... ...otra vez... Locale Settings Configuración Local Country País TimeZone Zona Horaria about:blank acerca de: blank Very weak password protection and not recommended if security is required. DST Zone Zona de Horario de Verano Personal Information (for reports) Información Personal (para los reportes) First Name Nombre Last Name Apellidos It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Está totalmente bien mentir o saltarse esto, pero su edad aproximada es necesaria para mejorar la precisión de ciertos cálculos. D.O.B. Fecha de Nacimiento. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>El género biológico (nacimiento) a veces es necesario para mejorar la precisión de unos pocos cálculos, no dude en dejar éste espacio en blanco y omitir cualquiera de ellos.</p></body></html> Gender Sexo Male Masculino Female Femenino Height Altura Metric Métrico English Inglés Contact Information Información de Contacto Address Domicilio Email Correo Electrónico Phone Teléfono CPAP Treatment Information Información del Tratamiento CPAP Date Diagnosed Fecha de Diagnóstico Untreated AHI IAH sin tratar CPAP Mode Modo de CPAP CPAP APAP APAP Bi-Level Bi-Nivel ASV ASV: Ventilación Servoasistida Adaptativa RX Pressure Presión Diagnosticada Doctors / Clinic Information Información del Médico/Clínica Doctors Name Nombre del Médico Practice Name Nombre de la clínica Patient ID ID de paciente &Cancel &Cancelar &Back &Anterior &Next &Siguiente Select Country Seleccione País This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Este software ha sido creado para asisitirlo a revisar los datos producidos por las máquinas de CPAP y otros equipos relacionados. PLEASE READ CAREFULLY POR FAVOR LEA CUIDADOSAMENTE Accuracy of any data displayed is not and can not be guaranteed. La exactitud de los datos mostrados no está y no puede ser garantizada. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Cualquier reporte generado es EXCLUSIVAMENTE PARA USO PERSONAL y no es adecuado DE NINGÚN MODO para fines de apego al tratamiento o diagnóstico médico. Use of this software is entirely at your own risk. El uso de este software es completamente bajo su propio riesgo. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team Welcome to the Open Source CPAP Analysis Reporter Bienvenido al Reporter de Análisis de CPAP de Código Abierto OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR ha sido publicado libremente bajo la <a href='qrc:/COPYING'>Licencia Pública GNU v3</a>, y viene sin garantía, y sin NINGÚN reclamo de idoneidad para ningún propósito. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR está concebido simplemente como un visor de datos, y definitivamente no como un sustituto de la orientación médica competente de su médico. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Los autores no serán responsables de <u>cualquier cosa </u> relacionada con el uso o mal uso de este software. Please provide a username for this profile Por favor proporcione un nombre de usuario para este perfiĺ Passwords don't match Las contraseñas no coinciden Profile Changes Cambio al perfil Accept and save this information? ¿Aceptar y guardar esta información? &Finish &Finalizar &Close this window &Cerrar esta Ventana Overview Range: Intérvalo: Last Week Última Semana Last Two Weeks Última Quincena Last Month Último Mes Last Two Months Último Bimestre Last Three Months Último Trimestre Last 6 Months Último Semestre Last Year Último Año Everything Todo Custom Personalizado Snapshot Start: Inicio: End: Fin: Reset view to selected date range Reinicializar vista al intérvalo seleccionado Toggle Graph Visibility Activar Visibilidad del Gráfico Drop down to see list of graphs to switch on/off. Expanda esta lista para mostrar u ocultar los gráficos disponibles. Graphs Gráficos Respiratory Disturbance Index Índice de Perturbación Respiratoria Apnea Hypopnea Index Índice de Apnea- Hipoapnea Usage Uso Usage (hours) Uso (horas) Session Times Horarios de la sesión Total Time in Apnea Tiempo Total en Apnea Total Time in Apnea (Minutes) Tiempo Total en Apnea (Minutos) Body Mass Index Índice de Masa Corporaĺ How you felt (0-10) ¿Cómo se sintió? (0-10) 10 of 10 Charts Show all graphs Mostrar todos los gráficos Hide all graphs Ocultar todos los gráficos OximeterImport Oximeter Import Wizard Asistente para la Importación de Datos desde el Oxímetro Skip this page next time. Saltar esta pantalla la próxima vez. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? ¿Desde dónde le gustaría importar? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. Los usuarios de CMS50E/F, cuando importen directamente, no seleccionen cargar en su dispositivo hasta que OSCAR se lo pida. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Sí está habilitado, OSCAR reajustará automáticamente el reloj interno de su CMS50 usando la hora actual de su computadora.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Sí no le molesta estar delante de una computadora funcionando toda la noche, esta opción le proporcionará una útil gráfica de pletismografía, la cual indica el ritmo cardiaco además de las lecturas normales de oximetría.</p></body></html> Record attached to computer overnight (provides plethysomogram) Grabación adjunta a la computadora durante la noche (proporciona una pletismografía) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Esta opción le permitirá importar datos desde archivos creados por el software de acompañamiento del oxímetro de pulso, tales como SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Desde un archivo de datos guardado por otro programa, como SpO2Review Please connect your oximeter device Por favor conecte su Oxímetro Please connect your oximeter device, turn it on, and enter the menu Press Start to commence recording Presione Inicio para comenzar el registro Show Live Graphs Mostrar Gráficos en Vivo Duration Duración Pulse Rate Frecuencia de Pulso Multiple Sessions Detected Múltiples sesiones detectadas Start Time Hora de inicio Details Detalles Import Completed. When did the recording start? Importación Completada. ¿Cuándó comenzó la grabación? Oximeter Starting time Hora de inicio del Oxímetro I want to use the time reported by my oximeter's built in clock. Quiero usar la hora registrada por el reloj interno de mi oxímetro. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Inicié esta grabación del oxímetro al mismo (o casi) tiempo de una sesión de mi máquina CPAP. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Nota: Sincronizar con el inicio de una sesión CPAP será siempre más preciso.</p></body></html> Choose CPAP session to sync to: Elija con cuál sesión CPAP sincronizar: You can manually adjust the time here if required: Puede ajustar manualmente la hora si es necesario: HH:mm:ssap ap? HH:mm:ssap &Cancel &Cancelar &Information Page &Hoja de información CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 Elección MMed MD300W1 Set device date/time Ajustar fecha/hora del aparato <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Compruebe para habilitar la actualización del identificador del dispositivo para la próxima importación, lo que es útil para aquellos que tienen varios oxímetros por ahí.</p></body></html> Set device identifier Ajustar el identificador del dispositivo Erase session after successful upload Borrar sesión después de una carga exitosa Import directly from a recording on a device Importación directa desde una grabación en un dispositivo <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">¿Recordaste importar tus sesiones de CPAP primero?<br/></span>Si lo olvida, no tendrá un tiempo válido para sincronizar esta sesión de oximetría.<br/>Para asegurar una buena sincronización entre los dispositivos, intente siempre iniciar ambos al mismo tiempo.</p></body></html> If you can read this, you likely have your oximeter type set wrong in preferences. Si puede leer esto, es probable que su tipo de oxímetro esté mal configurado en las preferencias. Please choose which one you want to import into OSCAR Por favor, elija el que desea importar a OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR necesita una hora de inicio para saber dónde guardar esta sesión de oximetría.</p><p>Seleccione una de las siguientes opciones::</p></body></html> &Retry &Reintentar &Choose Session &Escoger Sesión &End Recording &Finalizar Registro &Sync and Save &Sincronizar y Guardar &Save and Finish &Guardar y Terminar &Start &Iniciar Scanning for compatible oximeters Buscando oxímetros compatibles Could not detect any connected oximeter devices. No se pudieron detectar oxímetros conectados. Connecting to %1 Oximeter Conectando al oxímetro %1 Renaming this oximeter from '%1' to '%2' Renombrar este oxímetro de '%1' a '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. El nombre del Oxímetro es diferente... Si sólo tienes uno y lo compartes entre perfiles, establece el mismo nombre en ambos perfiles. "%1", session %2 "%1", sesión %2 Nothing to import Nada que importar Your oximeter did not have any valid sessions. Su oxímetro no tenía ninguna sesión válida. Close Cerrar Waiting for %1 to start Esperando que %1 comience Waiting for the device to start the upload process... Esperando a que el dispositivo inicie el proceso de carga.... Select upload option on %1 Seleccione ĺa opción adecuada para realizar la descarga en %1 You need to tell your oximeter to begin sending data to the computer. Necesita decirle a su oxímetro que empiece a enviar datos a la computadora. Please connect your oximeter, enter it's menu and select upload to commence data transfer... O bien enciendalo, vaya al menu setting Por favor, conecte su Oxímetro, entre en su menú y seleccione cargar para iniciar la transferencia de datos.... %1 device is uploading data... El dispositivo %1 está descargando información... Please wait until oximeter upload process completes. Do not unplug your oximeter. Por favor espere hasta que la descarga desde el oxímetro finalice. No lo desconecte. Oximeter import completed.. Importación desde el Oxímetro completada. Select a valid oximetry data file Seleccione un archivo válido con datos de oximetría Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Archivos del Oxímetro (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Ningún módulo de oximetría podría analizar el archivo dado: Live Oximetry Mode Modo de oximetría en vivo Live Oximetry Stopped La oximetría en vivo se detuvo Live Oximetry import has been stopped Se ha detenido la importación de oximetría en vivo Oximeter Session %1 Sesión de Oxímetro %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR le ofrece la posibilidad de realizar un seguimiento de los datos de oximetría junto con los datos de las sesiones de CPAP, lo que puede proporcionarle una valiosa información sobre la eficacia del tratamiento con CPAP. También funcionará de forma autónoma con su oxímetro de pulso, lo que le permitirá almacenar, rastrear y revisar los datos grabados. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Sí está intentando sincronizar la oximetría y los datos de CPAP, asegúrese de haber importado sus sesiones de CPAP antes de continuar! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Para que OSCAR pueda localizar y leer directamente desde su dispositivo Oximeter, necesita asegurarse de que los controladores de dispositivo correctos (por ejemplo, USB a Serial UART) han sido instalados en su computadora. Para más información sobre esto, %1click aqui%2. Oximeter not detected Oxímetro no detectado Couldn't access oximeter No se pudo acceder al oxímetro Starting up... Iniciando... If you can still read this after a few seconds, cancel and try again Sí aún puede leer esto después de algunos segundos, cancele e inténtelo de nuevo Live Import Stopped Importación en vivo detenida %1 session(s) on %2, starting at %3 %1 sesión(es) en %2, comenzando en %3 No CPAP data available on %1 No hay datos de CPAP disponibles en %1 Recording... Registrando... Finger not detected Dedo no detectado I want to use the time my computer recorded for this live oximetry session. Quiero usar la hora registrada por mi computadora para esta sesión de oximetría en vivo. I need to set the time manually, because my oximeter doesn't have an internal clock. Necesito configurar la hora manualmente porque mi oxímetro no tiene reloj interno. Something went wrong getting session data Algo salió mal al obtener los datos de la sesión Welcome to the Oximeter Import Wizard Bienvenido al Asistente de Importación de Oxímetros Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Los oxímetros de pulso son dispositivos médicos utilizados para medir la saturación de oxígeno en la sangre. Durante eventos de apnea prolongada y patrones de respiración anormales, los niveles de saturación de oxígeno en la sangre pueden disminuir significativamente y pueden indicar problemas que requieren atención médica. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR es actualmente compatible con los oxímetros seriales Contec CMS50D+, CMS50E, CMS50F y CMS50I.<br/>(Nota: La importación directa desde los modelos bluetooth es <span style=" font-weight:600;">probablemente no</span> es posible) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Es posible que desee tener en cuenta, otras empresas, como Pulox, simplemente volver a clasificar a Contec CMS50 bajo nuevos nombres, como el Pulox PO-200, PO-300, PO-400. Estos también deberían funcionar. It also can read from ChoiceMMed MD300W1 oximeter .dat files. También puede leer de los archivos.dat del oxímetro Choice MMed MD300W1. Please remember: Por favor, recuerde: Important Notes: Notas importantes: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Los dispositivos Contec CMS50D+ no tienen un reloj interno y no registran la hora de inicio. Si no tiene una sesión de CPAP a la que vincular una grabación, tendrá que introducir la hora de inicio manualmente una vez finalizado el proceso de importación. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Incluso para los dispositivos con reloj interno, se recomienda acostumbrarse a iniciar los registros del oxímetro al mismo tiempo que las sesiones de CPAP, ya que los relojes internos de CPAP tienden a ir a la deriva con el tiempo, y no todos pueden ser reajustados fácilmente. Oximetry Date Fecha d/MM/yy h:mm:ss AP ¿ap? d/MM/aa h:mm:ss AP R&eset R&einicializar Pulse Pulso &Open .spo/R File &Abrir archivo .spo/R Serial &Import &Importación en serie &Start Live Iniciar en &vivo Serial Port Puerto Serie &Rescan Ports Volver a &buscar puertos PreferencesDialog Preferences Preferencias &Import &Importar Combine Close Sessions Combinar Sesiones Cerradas Minutes Minutos Multiple sessions closer together than this value will be kept on the same day. Sesiones múltiples más cercanas que este valor serán mantenidas en el mimsmo día. Ignore Short Sessions Ignorar sesiones cortas <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sesiones de duración menor a esta no serán mostradas<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Hora de cambio de día Sessions starting before this time will go to the previous calendar day. Sesiones comenzadas antes de esta hora van al día calendario previo. Session Storage Options Opciones de almacenamiento de sesión Create SD Card Backups during Import (Turn this off at your own peril!) Cree copias de seguridad de tarjetas SD durante la importación (¡apáguelo por su cuenta y riesgo!) Compress SD Card Backups (slower first import, but makes backups smaller) Comprimir Respaldos de tarjeta SD (primera importación más lenta, respaldos más pequeños) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Considerar días con menos uso que este como incumplidos. Cuatro horas son generalmente consideradas como en cumplimiento. hours horas Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Habilitar/deshabilitar mejoras experimentales de detección de eventos. Esto permite detectar eventos limítrofes, y algunos que la máquina pasó por alto. Esta opción debe habilitarse antes de importar, de otro modo es requerida una purga. Flow Restriction Restricción de flujo Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Porcentaje de restricción de fluje respecto al valor medio. Un valor de 20% funciona bien para detectar apneas. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">La deteccióon personalizada es un método experimental para detectar eventos errados por la máquina. Estos <span style=" text-decoration: underline;">no</span> están incluidos en el IAH.</p></body></html> Duration of airflow restriction Duración de la restricción de flujo s s Event Duration Duración del evento Allow duplicates near machine events. Permitir duplicados cercanos a eventos de la máquina. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Ajusta la cantidad de datos considerados para cada punto en el gráfico IAH/hora. 60 minutos por defecto. Se recomienda dejar este valor. minutes minutos Reset the counter to zero at beginning of each (time) window. Reinicializa el contador a cero al inicio de cada ventana de tiempo. Zero Reset Reinicializar a cero CPAP Clock Drift Deriva del reloj del CPAP Do not import sessions older than: No importar sesiones más antiguas que: Sessions older than this date will not be imported Las sesiones más antiguas que esta fecha no serán importadas dd MMMM yyyy dd MMMM aaaa Show in Event Breakdown Piechart Mostra gráfico de pastel con el desglose de eventos Resync Machine Detected Events (Experimental) Resincronizar los eventos detectados por la máquina (EXPERIMENTAL) User definable threshold considered large leak Umbral definible por el usuario para la consideración de fugas grandes Whether to show the leak redline in the leak graph Mostrar/Ocultar la línea roja en el gráfico de fugas Search Buscar &Oximetry &Oximetría Percentage drop in oxygen saturation Porcentaje de caída en la saturación de oxígeno Pulse Pulso Sudden change in Pulse Rate of at least this amount Cambio repentino en el pulso de al menos esta cantidad bpm ppm Minimum duration of drop in oxygen saturation Duración mínima de la caída en la saturación de oxígeno Minimum duration of pulse change event. Duración mínima del evento de cambio en el pulso. Small chunks of oximetry data under this amount will be discarded. Fragmentos pequeños de datos de oximetría menores a esta cantidad serán descartados. &General General Settings Configuración General Daily view navigation buttons will skip over days without data records Los botones de navegación en la vista diaria se saltarán los días sin datos Skip over Empty Days Saltar días vacíos Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Permite el uso de múltiples núcleos de CPU cuando estén disponibles para mejorar el rendimiento. Afecta principalmente al importador. Enable Multithreading Habilitar Multithreading Bypass the login screen and load the most recent User Profile Saltar la pantalla de inicio de sesión y cargar el perfil de usuario más reciente <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Éstas características han sido podadas. Volverán después.</p></body></html> Changes to the following settings needs a restart, but not a recalc. Cambios a los siguientes ajustes requieren un reinicio pero no una recalculación. Preferred Calculation Methods Métodos de cálculo preferidos Middle Calculations ¡? Cálculo del centro Upper Percentile Percentil superior For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Para consistencia, los usuarios de ResMed deben usar 95% aquí, ya que es el único valor disponible en los días de sólo resumen. Median is recommended for ResMed users. Se recomienda Mediana para los usuarios de ResMed. Median Mediana Weighted Average Promedio Ponderado Normal Average Promedio True Maximum Máximo Verdadero 99% Percentile Percentil 99% Maximum Calcs Cálculo de máximo Session Splitting Settings Parámetros de partición de sesión <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Este ajuste debe utilizarse con precaución....</span> Desactivarlo tiene consecuencias que implican la precisión de los días de sólo resumen, ya que ciertos cálculos sólo funcionan correctamente si se mantienen juntas las sesiones de sólo resumen que proceden de los registros de días individuales. </p><p><span style=" font-weight:600;">Usuarios de ResMed:</span> El hecho de que nos parezca natural que el reinicio de la sesión de las 12 del mediodía sea el día anterior no significa que los datos de ResMed coincidan con los nuestros. El formato del índice STF.edf tiene serias debilidades que hacen que hacer esto no sea una buena idea.</p><p>Esta opción existe para pacificar a los que no les importa y quieren ver esto &quot;fijo&quot; sin importar los costos, pero que sepas que tiene un costo. Si mantienes tu tarjeta SD todas las noches y la importas al menos una vez a la semana, no verás problemas con esto muy a menudo.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) No dividir los días de resumen (Advertencia: ¡lea el consejo de la herramienta!) Memory and Startup Options Opciones de memoria e inicio Pre-Load all summary data at startup Precargar los datos de resumen al inicio <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Esta configuración mantiene los datos de forma de onda y de evento en la memoria después del uso para acelerar los días de visita.</p><p>Esta opción no es realmente necesaria, ya que su sistema operativo también almacena en caché los archivos utilizados anteriormente.</p><p>Se recomienda dejarlo apagado, a menos que su ordenador tenga una tonelada de memoria.</p></body></html> Keep Waveform/Event data in memory Mantenga los datos de forma de onda/evento en la memoria <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Reduce cualquier diálogo de confirmación sin importancia durante la importación.</p></body></html> Import without asking for confirmation Importar sin pedir confirmación General CPAP and Related Settings CPAP General y Ajustes Relacionados Enable Unknown Events Channels Habilitar canales de eventos desconocidos AHI Apnea Hypopnea Index IAH RDI Respiratory Disturbance Index IPR AHI/Hour Graph Time Window Ventana de tiempo del gráfico IAH/Hora Preferred major event index Lista de eventos importantes preferidos Compliance defined as Cumplimiento definido como Flag leaks over threshold Fugas por encima del umbral Seconds Segundos <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Nota: ¡Ésto no es para correcciones de zona horaria! Asegúrese de que el reloj y la zona horaria de su sistema operativo estén configurados correctamente.</p></body></html> Hours Horas <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>El máximo real es el máximo del conjunto de datos.</p><p>El percentil 99% filtra los valores atípicos más raros.</p></body></html> Combined Count divided by Total Hours Recuento combinado dividido por el total de horas Time Weighted average of Indice Promedio ponderado en el tiempo del Índice Standard average of indice Promedio estándar del índice Changing SD Backup compression options doesn't automatically recompress backup data. Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Comprima las copias de seguridad de ResMed (EDF) para ahorrar espacio en disco. Los archivos EDF respaldados se almacenan en el formato.gz, que es común en las plataformas Mac y Linux.. OSCAR puede importar desde este directorio de backup comprimido de forma nativa... Para usarlo con ResScan se requiere que los archivos.gz se descompriman primero... The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Las siguientes opciones afectan a la cantidad de espacio en disco que utiliza OSCAR y tienen un efecto sobre el tiempo que tarda la importación. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Esto hace que los datos de OSCAR ocupen la mitad de espacio. Pero hace que la importación y el cambio de día lleven más tiempo... Si tiene un equipo nuevo con un pequeño disco de estado sólido, esta es una buena opción. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Comprimir datos de sesión (hace que los datos de OSCAR sean más pequeños, pero el día cambia más lentamente.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Esto mantiene una copia de seguridad de los datos de la tarjeta SD en los equipos de ResMed, Los equipos de la serie S9 de ResMed eliminan los datos de alta resolución de más de 7 días, y datos gráficos de más de 30 días.. OSCAR puede guardar una copia de estos datos si alguna vez necesita reinstalarlos. (Altamente recomendado, a menos que te falte espacio en disco o no te preocupes por los datos del gráfico) Traducción realizada con el traductor www.DeepL.com/Translator <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Hace que el inicio de OSCAR sea un poco más lento, al pre-cargar todos los datos de resumen por adelantado, lo que acelera la navegación general y algunos otros cálculos más adelante.. </p><p>Sí tiene una gran cantidad de datos, puede que valga la pena mantenerla desactivada, pero sí lo que le gusta es ver <span style=" font-style:italic;">siempre</span> en el resumen, todos los datos totalizados deben cargarse de todos modos. </p><p>Tenga en cuenta que este ajuste no afecta a la forma de onda ni a los datos de eventos, que siempre se cargan según sea necesario.</p></body></html> <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered Custom CPAP User Event Flagging Marcaje personalizado de eventos del usuario de CPAP This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Esta opción experimental intenta utilizar el sistema de señalización de eventos de OSCAR para mejorar el posicionamiento de eventos detectados por la máquina. Show Remove Card reminder notification on OSCAR shutdown Mostrar notificación de de retirada de tarjeta en el apagado de OSCAR Always save screenshots in the OSCAR Data folder Check for new version every Buscar por una nueva versión cada days. días. Last Checked For Updates: Ultima búsqueda de actualizaciones: TextLabel Etiqueta de Texto I want to be notified of test versions. (Advanced users only please.) &Appearance A&pariencia Graph Settings Ajustes de gráficos <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Qué pestaña abrir al cargar un perfil. (Nota: Por defecto será Perfil si OSCAR está configurado para no abrir un perfil al iniciar el programa).</p></body></html> Bar Tops ? Barras Line Chart Líneas Overview Linecharts Gráficos de vista general <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Ésto hace que el deplazamiento mientras se hace zoom sea más fácil en Touchpads bidireccionales sensibles.</p><p>El valor recomendado son 50 ms.</p></body></html> Scroll Dampening Atenuación del desplazamiento Overlay Flags Sobreponer indicadores Line Thickness Grosor de línea The pixel thickness of line plots Grosor del pixel en gráficos de línea Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. El caché Pixmap es una técnica de aceleración de gráficos. Puede causar problemas con el uso de fuentes durante el despliegue de gráficos en tu plataforma. Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Intente cambiar esta opción desde la configuración predeterminada (Desktop OpenGL) si experimenta problemas de renderizado con los gráficos de OSCAR. Fonts (Application wide settings) Fuentes (ajustes de ancho de aplicación) The visual method of displaying waveform overlay flags. Método visual para mostrar los indicadores sobrepuestos de la forma de onda. Standard Bars Barras estándar Graph Height Altura del gráfico Default display height of graphs in pixels Altura por defecto para el despliegue de gráficos en pixeles How long you want the tooltips to stay visible. Que tanto tiempo permanece visible la ventana de ayuda contextual. Events Eventos Reset &Defaults Resetear &valores predeterminados <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Advertencia: </span>Sólo porque usted pueda, no significa que sea una buena práctica.</p></body></html> Waveforms Formas de onda Flag rapid changes in oximetry stats Indicar cambios en estad. de oximetría Other oximetry options Otras opciones Oximetría Discard segments under Descartar segmentos en Flag Pulse Rate Above Indic. Pulso Encima de Flag Pulse Rate Below Indicar Latido Pulso Debajo de This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Este cálculo requiere que los datos de fugas totales sean proporcionados por la CPAP. (Por ejemplo, PRS1, pero no ResMed, que ya los tiene) Los cálculos de fugas involuntarias utilizados aquí son lineales, no modelan la curva de ventilación de la máscara. Si utiliza unas cuantas máscaras diferentes, escoja en su lugar valores promedio. Todavía debería estar lo suficientemente cerca. Calculate Unintentional Leaks When Not Present Calcule las fugas involuntarias cuando no estén presentes 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Nota: Se utiliza un método de cálculo lineal. La modificación de estos valores requiere un nuevo cálculo. Show flags for machine detected events that haven't been identified yet. Muestra los indicadores de eventos detectados por la máquina que aún no se han identificado. Tooltip Timeout Visibilidad del menú de ayuda contextual Graph Tooltips Ayuda contextual del gráfico Top Markers Marcadores superiores Auto-Launch CPAP Importer after opening profile Importador de CPAP de inicio automático después de abrir el perfil Automatically load last used profile on start-up Cargar automáticamente el último perfil utilizado en la puesta en marcha Your masks vent rate at 20 cmH2O pressure La tasa de ventilación de sus máscaras a una presión de 20 cmH2O Your masks vent rate at 4 cmH2O pressure La tasa de ventilación de sus máscaras a una presión de 4 cmH2O <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Debido a las limitaciones de diseño resumidas, los equipos de ResMed no admiten el cambio de estos ajustes.</p></body></html> Oximetry Settings Ajustes de Oximetría Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR On Opening En la Apertura Profile Perfil Welcome Bienvenida Daily Diariamente Statistics Estadísticas Switch Tabs Cambiar pestañas No change Sin cambios After Import Después de la importación Other Visual Settings Otras configuraciones visuales Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Anti-Aliasing aplica suavizado a los gráficos... Ciertas parcelas se ven más atractivas con esto puesto. Ésto también afecta a los informes impresos. Pruébelo y vea si le gusta. Use Anti-Aliasing Usar Anti-Aliasing Makes certain plots look more "square waved". Hace que algunos gráficos luzcan más "cuadrados". Square Wave Plots Gráficos de onda cuadrada Use Pixmap Caching Usar caché Pixmap Animations && Fancy Stuff elegantes, no coquetas Animaciones y cosas coquetas Whether to allow changing yAxis scales by double clicking on yAxis labels Permitir cambiar la escala del eje Y haciendo dobli clic en sus etiquetas Allow YAxis Scaling Permitir escalado del eje Y Graphics Engine (Requires Restart) Gráfico (requiere reiniciar) l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Whether to include machine serial number on machine settings changes report Include Serial Number Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Font Fuente Size Tamaño Bold Negritas Italic Itálica Application Aplicación Graph Text Texto del Gráfico Graph Titles Títulos del gráfico Big Text Texto grande Details Detalles &Cancel &Cancelar &Ok &Aceptar Flag Marcador Minor Flag Indicador Menor Span Extensión Always Minor Siempre Menor No CPAP machines detected Will you be using a ResMed brand machine? Never Nunca Name Nombre Color Color Flag Type Tipo de indicador Label Etiqueta CPAP Events Eventos CPAP Oximeter Events Eventos de Oximetría Positional Events Eventos Posicionales Sleep Stage Events Eventos Etapa del Sueño Unknown Events Eventos Desconocidos Double click to change the descriptive name this channel. Haga doble clic para cambiar el nombre descriptivo de este canal. Double click to change the default color for this channel plot/flag/data. Haga doble clic para cambiar el color predeterminado de este gráfico de canal/indicador/datos. %1 %2 %1 %2 Overview Vista general Double click to change the descriptive name the '%1' channel. Haga doble clic para cambiar el nombre descriptivo del canal `%1'. Whether this flag has a dedicated overview chart. Si esta indicación tiene un gráfico de resumen dedicado. Here you can change the type of flag shown for this event Aquí puede cambiar el tipo de indicador que se muestra para este evento This is the short-form label to indicate this channel on screen. Esta es la etiqueta de forma abreviada para indicar este canal en la pantalla. This is a description of what this channel does. Ésta es una descripción de lo que hace este canal. Lower Inferior Upper Superior CPAP Waveforms Formas de onda CPAP Oximeter Waveforms Formas de Onda Oxímetro Positional Waveforms Formas de Onda Posicionales Sleep Stage Waveforms Formas de onda de la etapa de sueño Whether a breakdown of this waveform displays in overview. Sí un desglose de esta forma de onda se muestra en resumen. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Aquí puede ajustar el umbral <b>b>bajo</b> utilizado para ciertos cálculos en la forma de onda %1 Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Aquí puede ajustar el umbral <b>superior</b> utilizado para ciertos cálculos en la forma de onda %1 Data Processing Required Procesamiento de Datos Requerido A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Para aplicar estos cambios se requiere un procedimiento de re/descompresión de datos. Esta operación puede tardar un par de minutos en completarse. ¿Estás seguro de que quieres hacer estos cambios? Data Reindex Required Se requiere reindizar datos A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Se requiere un procedimiento de reindexación de datos para aplicar estos cambios. Esta operación puede tardar un par de minutos en completarse. ¿Está seguro que quiere realizar estos cambios? Restart Required Reinicio Requerido If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Sí alguna vez necesita volver a importar estos datos (ya sea en OSCAR o en ResScan), estos datos no volverán a aparecer. If you need to conserve disk space, please remember to carry out manual backups. Sí necesita conservar espacio en disco, por favor recuerde realizar copias de seguridad manuales. Are you sure you want to disable these backups? ¿Estás seguro de que quieres desactivar estas copias de seguridad? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Desactivar las copias de seguridad no es una buena idea, porque OSCAR las necesita para reconstruir la base de datos si se encuentran errores. Are you really sure you want to do this? ¿Está verdaderamente seguro de querer realizar esto? This may not be a good idea Esto podría no ser una buena idea <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Please Note:</b> Las capacidades avanzadas de división de sesiones de OSCAR no son posibles con <b>ResMed</b> debido a una limitación en la forma en que se almacenan sus ajustes y datos de resumen, y por lo tanto han sido desactivados para este perfil.</p><p>En las máquinas de ResMed, los días <b>dividirán al mediodía.</b> como en el software comercial de ResMed.</p> One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Uno o más de los cambios que ha hecho requerirá que esta aplicación se reinicie para que estos cambios entren en vigor. ¿Le gustaría hacer esto ahora? ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Las máquinas ResMed S9 rutinariamente eliminan ciertos datos de la tarjeta SD si son anteriores a 7 y 30 días (depende de la resolución). ProfileSelector Filter: Filtro: Reset filter to see all profiles Version Versión &Open Profile &Abrir Perfil &Edit Profile &Editar perfil &New Profile &Nuevo Perfil Profile: None Perfil: Ninguno Please select or create a profile... Por favor, seleccione o cree un perfil.... Destroy Profile Destruir Perfil Profile Perfil Ventilator Brand Marca del Respirador Ventilator Model Modelo del Respirador Other Data Otros datos Last Imported Última Import. Name Nombre You must create a profile Debe crear un perfil Enter Password for %1 Ingrese contraseña para %1 You entered an incorrect password Ha ingresado una contraseña incorrecta Forgot your password? ¿Olvidó su contraseña? Ask on the forums how to reset it, it's actually pretty easy. Pregunta en los foros cómo restablecerlo, es bastante fácil. Select a profile first Seleccione primero un perfil The selected profile does not appear to contain any data and cannot be removed by OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Sí está intentando eliminar porque ha olvidado la contraseña, debe restablecerla o eliminar la carpeta del perfil manualmente. You are about to destroy profile '<b>%1</b>'. Está a punto de destruir el perfil '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Piense cuidadosamente, ya que ésto borrará irremediablemente el perfil junto con todos <b>los datos de copia de seguridad</b> almacenados bajo<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Introduzca la palabra <b>ELIMINAR</b> abajo (exactamente como se muestra) para confirmar. DELETE ELIMINAR Sorry Lo sentimos You need to enter DELETE in capital letters. Necesita escribir ELIMINAR en mayúsculas. There was an error deleting the profile directory, you need to manually remove it. Hubo un error al eliminar el directorio con el perfil, necesitará removerlo manualmente. Profile '%1' was succesfully deleted El perfil '%1' fue eliminado con éxito Bytes Bytes KB KB MB MB GB GB TB TB PB RP Summaries: Sumarios: Events: Eventos: Backups: Copias: Hide disk usage information Oculttar información sobre uso del disco Show disk usage information Mostrar información sobre el uso del disco Name: %1, %2 Nombre: %1, %2 Phone: %1 Teléfono: %1 Email: <a href='mailto:%1'>%1</a> Email: <a href='mailto:%1'>%1</a> Address: Domicilio: No profile information given No se ha facilitado información del perfil Profile: %1 Perfil: %1 ProgressDialog Abort Abortar QObject No Data Sin datos On Activado Off Desactivado ft pie(s) lb libra(s) oz onzas Kg Kg cmH2O cmH2O Med. Med. Min: %1 Min: %1 Min: Min: Max: Max: Max: %1 Max: %1 %1 (%2 days): %1 (%2 días): %1 (%2 day): %1 (%2 día): % in %1 % en %1 Hours Horas Min %1 Mín %1 Hours: %1 Horas: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 uso bajo, %2 ningún uso, fuera de %3 días (%4% cumplimiento.) Duración: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Sesiones: %1 / %2 / %3 Duración: %4 / %5 / %6 Más largo:%7 / %8 / %9 %1 Length: %3 Start: %2 %1 Longitud: %3 Inicio: %2 Mask On Poner Mascarilla Mask Off Quitar la mascarilla %1 Length: %3 Start: %2 %1 Longitud: %3 Inicio: %2 TTIA: TTIA: TTIA: %1 TTIA: %1 bpm pulsaciones por minuto/ ¿latidos por minuto? ppm ? Severity (0-1) Severidad (0-1) Error Error Warning Advertencia Please Note Por favor considere Graphs Switched Off Gráficos desactivados Sessions Switched Off Sesiones desactivadas &Yes &Sí &No &Cancel &Cancelar &Destroy &Destruir &Save &Guardad BMI índice de masa corporal IMC Weight Peso Zombie Zombi Pulse Rate Frecuencia de Pulso Plethy Pleti Pressure Presión Daily Vista por día Profile Perfil Overview Vista general Oximetry Oximetría Oximeter Oxímetro Event Flags Indic. Eventos Default Predeterminado CPAP CPAP BiPAP BiPAP Bi-Level Bi-Nivel EPAP Min EPAP Max EPAP IPAP APAP ASV AVAPS ST/ASV Humidifier Humidificador H OA AO A CA AC FL LF LE evento de fuga EF EP Resoplido RS VS ronquido vibratorio RV VS2 RV2 RERA PP P RE NR NRI O2 PC CP UF1 UF1 UF2 UF2 UF3 UF3 PS SP AHI IAH RDI IPR AI IA HI IH UAI IANC CAI Índice de vía aérea despejada IVAD FLI ILF REI IRE EPI IRS Min IPAP Built with Qt %1 on %2 Operating system: Graphics Engine: Graphics Engine type: App key: Software Engine Motor de software ANGLE / OpenGLES Desktop OpenGL Escritorio OpenGL m m cm in Minutes Minutos Seconds Segundos h h m m s s ms ms Events/hr Eventos/hora Hz Hz Litres Litros ml Breaths/min Inspiraciones/min Degrees Grados Information Información Busy Ocupado Only Settings and Compliance Data Available Summary Data Only Max IPAP SA PB RP IE Insp. Time Tiempo Insp. Exp. Time Tiempo Exp. Resp. Event Evento Resp. Flow Limitation Limitación de flujo Flow Limit Límite de flujo SensAwake Pat. Trig. Breath Pat. Trig. Breath Tgt. Min. Vent Vent. Min. Objetivo Target Vent. Vent. Objetivo. Minute Vent. Vent. Minuto Tidal Volume Volumen corriente Resp. Rate Frec. Resp. Snore Ronquido Leak Fuga Leaks Fugas Total Leaks Fugas totales Unintentional Leaks Fugas accidentales MaskPressure PresiónMascarilla Flow Rate Tasa de flujo Sleep Stage Estado del sueño Usage Uso Sessions Sesiones Pr. Relief Alivio de Presión No Data Available Sin información disponible Bookmarks Marcadores Mode Modo Model Modelo Brand Marca Serial # de Serie Series Serie Machine Máquina Channel Canal Settings Ajustes Motion Name Nombre DOB Fecha de Nacimiento Phone Teléfono Address Domicilio Email Correo Electrónico Patient ID ID de paciente Date Fecha Bedtime Hora de dormir Wake-up Hora de levantarse Mask Time Tiempo de mascarilla Unknown Desconocido None Ninguno Ready Listo First Primero Last Último Start Inicio End Final Yes No no Min Max Med Mediana Average Promedio Median Mediana Avg Prom. W-Avg Prom-Pond. Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine Máquina sin capacidad de registro Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Preparándose.... Machine Unsupported Máquina no soportada I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Lamento informar que OSCAR sólo puede rastrear horas de uso y ajustes muy básicos para esta máquina. Scanning Files... Escaneo de archivos.... Importing Sessions... Importacion de Sesiones... Finishing up... Terminado... Untested Data Machine Untested CPAP-Check AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS Flex Flex Lock Whether Flex settings are available to you. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Humidification Mode PRS1 Humidification Mode Humid. Mode Fixed (Classic) Adaptive (System One) Heated Tube Passover Tube Temperature PRS1 Heated Tube Temperature Tube Temp. Target Time PRS1 Humidifier Target Time Hum. Tgt Time Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Type of ramp curve to use. Linear SmartRamp Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Fixed Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Breath BPM Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled Variable Breathing UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend Peak Flow Peak flow during a 2-minute interval PRS1 Humidifier Setting Mask Resistance Setting Mask Resist. Hose Diam. 15mm 22mm Backing Up Files... model %1 DreamStation 2 unknown model Flex Mode Modo Flex PRS1 pressure relief mode. Modo de alivio de presión PRS1. C-Flex C-Flex+ A-Flex P-Flex Rise Time Tiempo de ascenso Bi-Flex Flex Level Nivel de Flex PRS1 pressure relief setting. Ajuste de alivio de presión PRS1. Humidifier Status Estado del humidificador PRS1 humidifier connected? ¿Está el humidificador PRS1 conectado? Disconnected Desconectado Connected Conectado Hose Diameter Diámetro de manguera Diameter of primary CPAP hose Diámetro de la manguera primaria del CPAP 12mm Auto On Encendido automático A few breaths automatically starts machine Unas cuantas inspiraciones activan la máquina automáticamente Auto Off Apagado automático Machine automatically switches off La máquina se apaga automáticamente Mask Alert Alerta de mascarilla Whether or not machine allows Mask checking. Define si se permite o no el chequeo de mascarilla. Show AHI Mostrar IAH Breathing Not Detected Respiración No Detectada A period during a session where the machine could not detect flow. Un período durante una sesión en el que la máquina no puede detectar el flujo. BND Timed Breath Inspiración temporizada Machine Initiated Breath Respiración iniciada por la máquina TB TB: ¿inspiración temporizada? IT Windows User Usuario de Windows Using Usando , found SleepyHead - , cargando SleepyHead - You must run the OSCAR Migration Tool Debe ejecutar la herramienta de migración OSCAR <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Los datos antiguos de su máquina deben ser regenerados, suponiendo que esta característica no haya sido deshabilitada en Preferencias durante una importación de datos previa.</i> Launching Windows Explorer failed Error al iniciar el Explorador de Windows Could not find explorer.exe in path to launch Windows Explorer. No se halló explorer.exe en el 'path' para lanzar el Explorador de Windows. <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR mantiene una copia de seguridad de la tarjeta de datos de sus dispositivos que utiliza para éste propósito..</b> OSCAR does not yet have any automatic card backups stored for this device. OSCAR no tiene todavía ninguna copia de seguridad automática de la tarjeta almacenada para este dispositivo. This means you will need to import this machine data again afterwards from your own backups or data card. Esto significa que tendrá que importar los datos de esta máquina nuevamente después, desde sus propios respaldos de sa tarjeta de datos. Important: Importante: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Sí le preocupa, haga clic en No para salir, y haga una copia de seguridad de su perfil manualmente, antes de volver a iniciar OSCAR. Are you ready to upgrade, so you can run the new version of OSCAR? ¿Estás listo para actualizar, así que puedes ejecutar la nueva versión de OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Lo sentimos, la operación de purga falló, lo que significa que esta versión de OSCAR no puede iniciarse. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? ¿Le gustaría activar las copias de seguridad automáticas, para que la próxima vez que una nueva versión de OSCAR necesite hacerlo, pueda reconstruir a partir de ellas? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR iniciará el asistente de importación para que pueda reinstalar sus %1 datos. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: OSCAR saldrá ahora, y luego (intentará) iniciar el administrador de archivos de su computadora para que pueda hacer una copia de seguridad manual de su perfil: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Utilice su gestor de archivos para hacer una copia del directorio de su perfil y, a continuación, reinicie OSCAR y complete el proceso de actualización. Machine Database Changes Cambios en la base de datos de la máquina OSCAR %1 needs to upgrade its database for %2 %3 %4 Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. The machine data folder needs to be removed manually. El directorio de datos de la máquina debe ser removido manualmente. This folder currently resides at the following location: Este directorio reside actualmente en la siguiente ubicación: Rebuilding from %1 Backup Reconstruyendo desde el respaldo de %1 or CANCEL to skip migration. o CANCELE para saltar la migración. You cannot use this folder: No puede utilizar esta carpeta: Migrating Migrar files archivos from a partir de to otras definiciones: a,hasta,hacia para OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. OSCAR creará una carpeta para sus datos. We suggest you use this folder: Le sugerimos que utilice esta folder: Click Ok to accept this, or No if you want to use a different folder. Haga clic en Aceptar para aceptarlo, o en No si desea utilizar una carpeta diferente. Next time you run OSCAR, you will be asked again. La próxima vez que ejecute OSCAR, se le preguntará de nuevo. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Choose or create a new folder for OSCAR data Elegir o crear una nueva carpeta para los datos OSCAR Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. As you did not select a data folder, OSCAR will exit. Como no seleccionó una carpeta de datos, OSCAR saldrá. The folder you chose is not empty, nor does it already contain valid OSCAR data. La carpeta que ha elegido no está vacía ni contiene datos válidos de OSCAR. Data directory: It is likely that doing this will cause data corruption, are you sure you want to do this? Es probable que hacer esto cause corrupción de datos, ¿está seguro de que quiere hacerlo? Question Pregunta Exiting Saliendo Are you sure you want to use this folder? ¿Está seguro que quiere usar este directorio? Don't forget to place your datacard back in your CPAP machine No olvide volver a colocar su tarjeta de datos en su máquina CPAP OSCAR Reminder Recordatorio OSCAR You can only work with one instance of an individual OSCAR profile at a time. Sólo se puede trabajar con una instancia de un perfil OSCAR individual a la vez. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Si utiliza almacenamiento en nube, asegúrese que OSCAR esté cerrado y de que la sincronización se haya completado primero en el otro equipo antes de continuar. Loading profile "%1"... Cargando perfil "%1"... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Unable to create zip! Are you sure you want to reset all your channel colors and settings to defaults? ¿Está seguro de que quiere reinicializar los ajustes y colores del canal a sus valores por defecto? Are you sure you want to reset all your waveform channel colors and settings to defaults? ¿Está seguro de que desea restablecer todos los colores y ajustes de su canal de forma de onda a los valores predeterminados? There are no graphs visible to print No hay graficos visibles para imprimir Would you like to show bookmarked areas in this report? ¿Le gustaría mostrar las áreas con marcadores en este reporte? Printing %1 Report Imprimiendo reporte %1 %1 Report Reporte %1 : %1 hours, %2 minutes, %3 seconds : %1 horas, %2 minutos, %3 segundos RDI %1 IPR %1 AHI %1 IAH %1 AI=%1 HI=%2 CAI=%3 IA=%1 IH=%2 IAC=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% IRE=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 IANC=%1 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 Reportando desde %1 hasta %2 Entire Day's Flow Waveform Forma de onda del día completo Current Selection Selección actual Entire Day Día completo Page %1 of %2 Página %1 de %2 Jan Ene Feb Feb Mar Mar Apr Abr May Jun Jul Aug Ago Sep Oct Oct Nov Dec Dic Events Eventos Duration Duración (% %1 in events) (%-%1 en eventos) Therapy Pressure Presión Terapéutica Inspiratory Pressure Presión Inspiratoria Lower Inspiratory Pressure Presión inspiratoria inferior Higher Inspiratory Pressure Presión inspiratoria superior Expiratory Pressure Presión Espiratoria Lower Expiratory Pressure Presión espiratoria inferior Higher Expiratory Pressure Presión espiratoria superior Pressure Support Soporte de presión PS Min SP Min Pressure Support Minimum Soporte de presión mínimo PS Max SP Máx Pressure Support Maximum Soporte de presión máximo Min Pressure Presión Mín Minimum Therapy Pressure Presión de terapia mínima Max Pressure Presión Máxima Maximum Therapy Pressure Máxima terapia de presión Ramp Time Tiempo de rampa Ramp Delay Period Periodo de retraso de rampa Ramp Pressure Presión de rampa Starting Ramp Pressure Presión inicial de rampa Ramp Event Evento de Rampa Ramp Rampa Vibratory Snore (VS2) Ronquido vibratorio (VS2) Mask On Time Mascarilla a tiempo Time started according to str.edf Hora de inicio según str.edf Summary Only Sólo resumen An apnea where the airway is open Una apnea mientras la vía aérea se encuentra abierta An apnea caused by airway obstruction Una apnea provocada por obstrucción de la vía aérea Hypopnea Hipoapnea A partially obstructed airway Una vía aérea parcialmente obstruida Unclassified Apnea Apnea no clasificada UA ANC Vibratory Snore Ronquido vibratorio A vibratory snore Un ronquido vibratorio A vibratory snore as detcted by a System One machine Un ronquido vibratorio detectado por la máquina System One Pressure Pulse Pulso de presión A pulse of pressure 'pinged' to detect a closed airway. Un Pulso de Presión 'lanzado' para detectar una vía aérea cerrada. Large Leak Fuga Grande A large mask leak affecting machine performance. Una gran fuga en la mascarilla que afecta al rendimiento de la máquina. LL LL Non Responding Event Evento de no respuesta A type of respiratory event that won't respond to a pressure increase. Un tipo de evento respiratorio que no responde a los incrementos de presión. Expiratory Puff Soplo espiratorio Intellipap event where you breathe out your mouth. Evento de intellipap en el cual se espira por la boca. SensAwake feature will reduce pressure when waking is detected. Característica de SensAwake con la cual se reduce la presión cuando se detecta el despertar. User Flag #1 Indicador de usuario #1 User Flag #2 Indicador de usuario #2 User Flag #3 Indicador de usuario #3 Heart rate in beats per minute Frecuencia cardiaca en latidos por minuto Blood-oxygen saturation percentage Porcentaje de saturación de oxígeno en sangre Plethysomogram Pletismograma An optical Photo-plethysomogram showing heart rhythm Un foto-pletismograma óptico mostrando el ritmo cardiaco Pulse Change Cambio de Pulso A sudden (user definable) change in heart rate Un repentino (definible por el usuario) cambio en el ritmo cardiaco SpO2 Drop Caída de SpO2 A sudden (user definable) drop in blood oxygen saturation Una repentina (definible por el usuario) caída en la saturación de oxígeno en sangre SD ??????????????????? Breathing flow rate waveform Forma de onda de flujo respiratorio L/min L/min Mask Pressure Presión de mascarilla Amount of air displaced per breath Volumen de aire desplazado por inspiración Graph displaying snore volume Gráfico mastrando el volumen de los ronquidos Minute Ventilation Ventilaciónes Minuto Amount of air displaced per minute Cantidad de aire desplazado por minuto Respiratory Rate Frecuencia respiratoria Rate of breaths per minute Tasa de inspiraciones por minuto Patient Triggered Breaths Inpiraciones iniciadas por el paciente Percentage of breaths triggered by patient Porcentaje de inpiraciones iniciadas por el paciente Pat. Trig. Breaths Insp. inic. x paciente Leak Rate Tasa de fuga Rate of detected mask leakage Tasa de fugas de mascarilla detectadas I:E Ratio Tasa I:E Ratio between Inspiratory and Expiratory time Relación entre el tiempo de inspiración y espiración ratio tasa Pressure Min Presión Mínima Pressure Max Presión Máxima Cheyne Stokes Respiration Respiración Cheyne Stokes An abnormal period of Cheyne Stokes Respiration Un período anormal de respiración Cheyne Stokes CSR Periodic Breathing Respiración Periódica An abnormal period of Periodic Breathing Un período anormal de Respiración Periódica Clear Airway Vía respiratoria despejada Vía Resp. Libre Obstructive Obstructiva Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Despertar relacionado con el esfuerzo respiratorio: Una restricción en la respiración que causa ya sea un despertar o una alteración del sueño. Leak Flag Indicador de fuga LF BF A user definable event detected by OSCAR's flow waveform processor. Un suceso definible por el usuario detectado por el procesador de forma de onda de flujo de OSCAR. Perfusion Index Índice de perfusión A relative assessment of the pulse strength at the monitoring site Una evaluación relativa de la fuerza del pulso en el lugar de monitorización Perf. Index % Índice perf. % Expiratory Time Tiempo espiración Time taken to breathe out El tiempo que tarda espirar Inspiratory Time Tiempo inspiratorio Time taken to breathe in El tiempo que toma inspirar Respiratory Event Evento respiratorio Graph showing severity of flow limitations Gráfico que muestra la severidad de las limitaciones de flujo Flow Limit. Límit. de Flujo Target Minute Ventilation Ventilación minuto objetivo An apnea that couldn't be determined as Central or Obstructive. Una apnea que no se pudo determinar como Central u Obstructiva. Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting Apnea An apnea reportred by your CPAP machine. A restriction in breathing from normal, causing a flattening of the flow waveform. Una restricción en la respiración desde lo normal, causando un aplanamiento de la forma de onda del flujo. Mask Pressure (High frequency) A ResMed data item: Trigger Cycle Event Maximum Leak Fuga máxima The maximum rate of mask leakage La tasa máxima de fuga de la mascarilla Max Leaks Fugas máximas Apnea Hypopnea Index Índice Apnea-Hipoapnea Graph showing running AHI for the past hour Gráfico que muestra la IAH corriente de la última hora Total Leak Rate Tasa de fuga total Detected mask leakage including natural Mask leakages Fuga de mascarilla detectada, incluyendo fugas naturales de la mascarilla Median Leak Rate Mediana de tasa de fuga Median rate of detected mask leakage Mediana de la tasa de fugas detectadas de mascarilla Median Leaks Fugas Medianas Respiratory Disturbance Index Índice de perturbación respiratoria Graph showing running RDI for the past hour Gráfico que muestra el IPR corriente de la última hora Movement Movement detector CPAP Session contains summary data only La sesión de CPAP sólo contiene un resumen PAP Mode Modo PAP PAP Device Mode Modo del dispositivo PAP APAP (Variable) ASV (Fixed EPAP) ASV (EPAP fijo) ASV (Variable EPAP) ASV (EPAP variable) Height Altura Physical Height Altura Física Notes Notas Bookmark Notes Notas Marcador Body Mass Index Índice de Masa Corporal How you feel (0 = like crap, 10 = unstoppable) Cómo te sientes (0 = like basura, 10 = imparable) Bookmark Start Marcador Inicio Bookmark End Marcador final Last Updated Últiima Actualización Journal Notes Notas de diario Journal Diario 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=Despierto 2=REM 3=Sueño Ligero 4=Sueño Profundo Brain Wave Onda Cerebral BrainWave OndaCerebral Awakenings Despertares Number of Awakenings Número de Despertares Morning Feel Sensación Matutina How you felt in the morning Cómo te sentiste por la mañana Time Awake Tiempo Despierto Time spent awake Tiempo de permanencia despierto Time In REM Sleep Tiempo en el sueño REM Time spent in REM Sleep Tiempo de permanencia en el sueño REM Time in REM Sleep Tiempo en el sueño REM Time In Light Sleep Tiempo en sueño Ligero Time spent in light sleep Tiempo de permanencia en un sueño ligero Time in Light Sleep Tiempo en sueño Ligero Time In Deep Sleep Tiempo en sueño profundo Time spent in deep sleep Tiempo de permanencia en sueño profundo Time in Deep Sleep Tiempo en sueño profundo Time to Sleep Tiempo para Dormir Time taken to get to sleep Tiempo necesario para dormir Zeo ZQ Zeo ZQ Zeo sleep quality measurement Medición de la calidad del sueño Zeo ZEO ZQ ZEO ZQ Debugging channel #1 Test #1 For internal use only Debugging channel #2 Test #2 Zero Cero Upper Threshold Umbral Superior Lower Threshold Umbral inferior Orientation Orientación Sleep position in degrees Posición de dormir en grados Inclination Inclinación Upright angle in degrees ?????????? Ángulo vertical en grados Days: %1 Días: %1 Low Usage Days: %1 Días de uso bajo: %1 (%1% compliant, defined as > %2 hours) (%1% cumplido, definido como > %2 horas) (Sess: %1) (Ses: %1) Bedtime: %1 Hora de dormir: %1 Waketime: %1 Hora de despertar: %1 (Summary Only) (Sólo resumen) There is a lockfile already present for this profile '%1', claimed on '%2'. Ya hay un archivo de cierre (lockfile) presente para el perfil '%1', reclamado en '%2'. Fixed Bi-Level Bi-nivel fijo Auto Bi-Level (Fixed PS) Bi-nivel automático (PS fijo) Auto Bi-Level (Variable PS) Bi-nivel automático (PS variable) 99.5% 90% {99.5%?} varies n/a Fixed %1 (%2) %1 Fijo (%2) Min %1 Max %2 (%3) Mín %1 Máx %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) ps?? PS %1 sobre %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) ps?? EPAP mín %1 IPAP máx %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Datos de oximetría más recientes: <a onclick='alert("daily=%2");'>%1</a> (last night) (anoche) (1 day ago) (%2 days ago) No oximetry data has been imported yet. Aún no se han importado datos de oximetría. Contec CMS50 Fisher & Paykel ICON DeVilbiss Intellipap SmartFlex Settings Configuración de SmartFlex ChoiceMMed ElijaMMed MD300 Respironics M-Series Serie-M Philips Respironics System One ResMed S9 EPR: Somnopose Somnopose Software Software Somnopose Zeo Zeo Personal Sleep Coach Consejero de sueño personal Locating STR.edf File(s)... Localización archivos STR.edf.... Cataloguing EDF Files... Catalogación de archivos EDF.... Queueing Import Tasks... Cola de tareas de importación.... Finishing Up... Terminando... CPAP Mode Modo de CPAP VPAPauto Auto VPAP ASVAuto iVAPS PAC Auto for Her EPR ResMed Exhale Pressure Relief Alivio de presión de exhalación de ResMed Patient??? ¿paciente? EPR Level Nivel EPR Exhale Pressure Relief Level Alivio de presión de exhalación Response Patient View SmartStart Machine auto starts by breathing La máquina arranca automáticamente al respirar Smart Start Humid. Status Estado Humid. Humidifier Enabled Status Estado Humidificador Activado Humid. Level Nivel Humid. Humidity Level Nivel de Humedad Temperature Temperatura ClimateLine Temperature Temperatura ClimateLine Temp. Enable Temp. Habilitada ClimateLine Temperature Enable Habilitación de temperatura ClimateLine Temperature Enable Temeperatura Habilitada AB Filter Antibacterial Filter Filtro Antibacterias Pt. Access Pt. Acceso Essentials Plus Climate Control Control de Climatización Manual Manual Soft Standard Estándar BiPAP-T BiPAP-S BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Advanced Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... Auto Mask Máscarilla ResMed Mask Setting Ajuste Mascarilla ResMed Pillows Almohadas Full Face Cara completa Nasal Nasal Ramp Enable Rampa Habilitada Database Outdated Please Rebuild CPAP Data Base de datos obsoleta Por favor recompile datos del CPAP (%2 min, %3 sec) (%3 sec) Snapshot %1 Instantánea %1 Pop out Graph Gráfico Desplegable The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View There is no data to graph No hay datos para graficar d MMM yyyy [ %1 - %2 ] Hide All Events Ocultar todos los eventos Show All Events Mostrar todos los eventos Unpin %1 Graph pin? Soltar gráfico %1 Popout %1 Graph Gráfico %1 Desplegable Pin %1 Graph Fijar gráfico %1 Plots Disabled Gráficos deshabilitados Duration %1:%2:%3 Duración %1:%2:%3 AHI %1 IAH %1 Relief: %1 Alivio: %1 Hours: %1h, %2m, %3s Horas: %1h, %2m, %3s Machine Information Información de la máquina Journal Data Datos de diario OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR encontró una vieja carpeta de Diario, pero parece que ha sido renombrada: OSCAR will not touch this folder, and will create a new one instead. Esta carpeta no será tocada por OSCAR y se creará una nueva. Please be careful when playing in OSCAR's profile folders :-P Por favor, tenga cuidado al jugar en las carpetas de perfil de OSCAR :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. Por alguna razón, OSCAR no pudo encontrar un registro de objeto de diario en su perfil, pero sí encontró varias carpetas de datos de diario. OSCAR picked only the first one of these, and will use it in future: OSCAR escogió sólo el primero de ellos, y lo utilizará en el futuro. If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Si Sí faltan los datos antiguos, copie manualmente el contenido de todas las demás carpetas del Diario_XXXXXXXXX a éste. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Backing up files... Reading data files... SmartFlex Mode Modo SmartFlex Intellipap pressure relief mode. Modo de alivio de presión Intellipap. Ramp Only Sólo rampa Full Time Tiempo completo SmartFlex Level Nivel SmartFlex Intellipap pressure relief level. Nivel de alivio de presión Intellipap. Snoring event. SN Weinmann SOMNOsoft2 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... Cargando %1 datos para %2... Scanning Files Escaneo de archivos.... Migrating Summary File Location Ubicación del archivo de resumen de la migración Loading Summaries.xml.gz Cargando Resúmenes.xml.gz Loading Summary Data Cargar datos totalizados Please Wait... Por favor Espere... Dreem Your Viatom device generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Viatom Viatom Software %1 Charts %1 of %2 Charts Loading summaries Updating Statistics cache Usage Statistics Estadísticas de uso New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to check for updates. Please try again later. SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank about:blank SessionBar %1h %2m %1h %2m No Sessions Present No hay sesiones presentes SleepStyleLoader Import Error Error de Importación This Machine Record cannot be imported in this profile. Este registro de máquina no puede ser importado a este perfi. The Day records overlap with already existing content. Estos registros diarios se traslapan con contenido previamente existente. Statistics Details Detalles Most Recent Más reciente Therapy Efficacy Eficacia de la terapia Last 30 Days Últimos 30 días Last Year Último Año Average %1 Promedio %1 CPAP Statistics Estadísticas de CPAP CPAP Usage Uso de CPAP Average Hours per Night Promedio de horas por noche Compliance (%1 hrs/day) Leak Statistics Estadísticas de fuga Pressure Statistics Estadísticas de presión Oximeter Statistics Estadísticas del oxímetro Blood Oxygen Saturation Saturación de oxígeno en sangre Pulse Rate Frecuencia de Pulso %1 Median %1 Mediana Min %1 Mín %1 Max %1 Max %1 %1 Index Índice %1 % of time in %1 % del tiempo en %1 % of time above %1 threshold % del tiempo por encima del umbral de %1 % of time below %1 threshold % del tiempo por debajo del umbral de %1 Name: %1, %2 Nombre: %1, %2 DOB: %1 Fecha de Nacimiento: %1 Phone: %1 Teléfono: %1 Email: %1 Correo Electrónico: %1 Address: Domicilio: Oscar has no data to report :( Oscar no tiene datos que reportar :( Days Used: %1 Días de uso. %1 Low Use Days: %1 Días de uso reducido: %1 Compliance: %1% Cumplimiento: %1% Days AHI of 5 or greater: %1 Días de 5 o más IAH %1 Best AHI Mejor IAH Date: %1 AHI: %2 Fecha: %1 IAH: %2 Worst AHI Peor IAH Best Flow Limitation Mejor Limitación de Flujo Date: %1 FL: %2 Fecha:%1 FL: %2 Worst Flow Limtation Peor lLmitación de Flujo No Flow Limitation on record No hay registro de Limitación de Flujo Worst Large Leaks Las peores Fugas Grandes Date: %1 Leak: %2% Fecha: %1 Fuga: %2% No Large Leaks on record No hay grandes fugas en los registros Worst CSR Peor CSR Date: %1 CSR: %2% Fecha: %1 CSR: %2% No CSR on record No hay registro de CSR Worst PB Peor RP Respiración Periodica Peor PB Date: %1 PB: %2% RP, Respiración Periodica Fecha: %1 PB: %2% No PB on record RP Respiracion Periodica No hay registro de PB Want more information? ¿Desea más información? OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR necesita todos los datos de resumen cargados para calcular los mejores/peores datos para días individuales. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Por favor, active la casilla de verificación Resúmenes previos a la carga en las preferencias para asegurarse de que estos datos están disponibles. Best RX Setting Mejor Prescripción Date: %1 - %2 Fecha: %1 - %2 AHI: %1 Total Hours: %1 Worst RX Setting Peor Prescripción Last Week Última Semana This report was prepared on %1 by OSCAR %2 Changes to Machine Settings No data found?!? Last 6 Months Últimos 6 meses Last Session Última sesión No %1 data available. No hay datos %1 disponibles. %1 day of %2 Data on %3 %1 día(s) de datos %2 en %3 %1 days of %2 Data, between %3 and %4 %1 día(s) de datos %2 entre %3 y %4 OSCAR is free open-source CPAP report software OSCAR es un software gratuito de código abierto para informes de CPAP Days Días Pressure Relief Alivio de Presión Pressure Settings Ajustes de presión Machine Information Información de la máquina First Use Primer Uso Last Use Último Uso Welcome Welcome to the Open Source CPAP Analysis Reporter Traducido al Español por Perchas Bienvenido al Reporter de Análisis CPAP de Código Abierto Perchas Tradujo al Español What would you like to do? ¿Qué puedo hacer por usted? CPAP Importer Importar CPAP Oximetry Wizard Asist. Oximetría Daily View Vista Diaria Overview Vista General Statistics Estadísticas <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> It would be a good idea to check File->Preferences first, Sería una buena idea revisar Archivo->Preferencias primero, as there are some options that affect import. ya que hay algunas opciones que afectan a la importación. Note that some preferences are forced when a ResMed machine is detected Tenga en cuenta que algunas preferencias son obligatorias cuando se detecta un equipo de ResMed First import can take a few minutes. La primera importación puede tardar unos minutos. The last time you used your %1... La última vez que usó su %1... last night anoche %2 days ago hace %2 días was %1 (on %2) fue %1 (el %2) %1 hours, %2 minutes and %3 seconds %1 horas, %2 minutos, %3 segundos Your machine was on for %1. Su máquina estuvo funcionando durante %1. <font color = red>You only had the mask on for %1.</font> <font color = red>Sólo tuvo la mascarilla puesta por %1.</font> under debajo over encima reasonably close to razonablemente cerca de equal to igual a You had an AHI of %1, which is %2 your %3 day average of %4. Tuvo un IAH de %1,que está por %2 de su promedio de %3 dias de %4. Your CPAP machine used a constant %1 %2 of air Su máquina CPAP usó una constante %1 %2 de aire Your pressure was under %1 %2 for %3% of the time. Su presión estuvo debajo de %1 %2 por el %3% del tiempo. Your machine used a constant %1-%2 %3 of air. Su máquina usó una constante %1-%2 %3 de aire. Your EPAP pressure fixed at %1 %2. Su presión de EPAP se fijó en %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Su presión de IPAP estuvo debajo de %1 %2 por %3% del tiempo. Your EPAP pressure was under %1 %2 for %3% of the time. Su presión de EPAP estuvo debajo de %1 %2 por %3% del tiempo. Your machine was under %1-%2 %3 for %4% of the time. Su máquina estuvo debajo de %1-%2 %3 por %4% del tiempo. 1 day ago Your average leaks were %1 %2, which is %3 your %4 day average of %5. Las fugas promedio fueron %1 %2, que está por %3 a su promedio de %4 días de %5. No CPAP data has been imported yet. No se han importado datos de CPAP todavía. gGraph %1 days %1 días gGraphView 100% zoom level 100% nivel de zoom Restore X-axis zoom to 100% to view entire selected period. Restore X-axis zoom to 100% to view entire day's data. Reset Graph Layout Reinicio de disposición del gráfico Resets all graphs to a uniform height and default order. Restablece todos los gráficos a una altura uniforme y a un orden predeterminado. Y-Axis Eje-Y Plots Gráficos CPAP Overlays Superposición CPAP Oximeter Overlays Superposición Oxímetro Dotted Lines Líneas de puntos Double click title to pin / unpin Click and drag to reorder graphs Doble clic en el título para pin / sin pin Haga clic y arrastre para reordenar las gráficas Remove Clone Eliminar Clon Clone %1 Graph Clone %1 Gráfico OSCAR-code-v1.3.1/Translations/Espaniol.es_MX.ts000066400000000000000000014160161417327530600213170ustar00rootroot00000000000000 AboutDialog &About &Acerca de Release Notes Notas de la Versión Credits GPL License Close Show data folder About OSCAR %1 Sorry, could not locate About file. Sorry, could not locate Credits file. Sorry, could not locate Release Notes. OSCAR %1 OSCAR %1 Important: Importante: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. To see if the license text is available in your language, see %1. CMS50F37Loader Could not find the oximeter file: Could not open the oximeter file: CMS50Loader Could not get data transmission from oximeter. No se recibió transmisión de datos alguna desde el oxímetro. Please ensure you select 'upload' from the oximeter devices menu. Need to know how is 'upload' translated Por favor asegúrese de seleccionar 'upload' o 'descargar' desde el menú de Dispositivos de Oximetría. Could not find the oximeter file: Could not open the oximeter file: CheckUpdates Checking for newer OSCAR versions Daily Go to the previous day Ir al día anterior Show or hide the calender Mostrar u ocultar el calendario Go to the next day Ir al día siguiente Go to the most recent day with data records Ir al día más reciente con datos registrados Events Eventos View Size Ver tamaño Notes Notas Journal or agenda Diario Small Pequeño Medium Mediano Big Grande I'm feeling ... If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Flags Banderas Identificadores Graphs Gráficos Show/hide available graphs. Color Color u u B B Zombie Zombi Weight Peso Awesome Increíble B.M.I. Índice de masa corporal I.M.C. Bookmarks Marcadores Add Bookmark Añadir Marcador Starts ¿inicios? Inicia Remove Bookmark Eliminar Marcador Breakdown desarrollo, descomponer, desarrollar, DESGLOSAR Desglose events eventos No %1 events are recorded this day . al final No hay eventos %1 registrados este día %1 event Evento %1 %1 events Eventos %1 UF1 UF1 UF2 UF2 Session Start Times Hora de inicio de sesión Session End Times Hora de fin de sesión Duration Duración Position Sensor Sessions Sesiones del sensor de posición Unknown Session Sesión Desconocida Time over leak redline mmm Límite de tiempo sobre fuga Event Breakdown Desglose de eventos Unable to display Pie Chart on this system Sessions all off! ¡Todas las sesionos deshabilitadas! Sessions exist for this day but are switched off. Existen sesiones para este día pero fueron deshabilitadas. Impossibly short session Sesión imposiblemente corta Zero hours?? ¿Cero horas? BRICK :( Ladrillo :( Sorry, this machine only provides compliance data. Complain to your Equipment Provider! ¡Quéjesete con el proveedor de su equipo! Statistics Estadísticas Details Time at Pressure Click to %1 this session. disable enable %1 Session #%2 %1 Sesión #%2 %1h %2m %3s %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. Oximeter Information Información del Oxímetro SpO2 Desaturations Desaturaciones SpO2 Pulse Change events Eventos de cambio de pulso SpO2 Baseline Used Línea basal de SpO2 usada (Mode and Pressure settings missing; yesterday's shown.) 99.5% 90% {99.5%?} Start Inicio End Fin 10 of 10 Event Types This CPAP machine does NOT record detailed data This bookmark is in a currently disabled area.. 10 of 10 Graphs Machine Settings Ajustes de la máquina Session Information Información de sesión CPAP Sessions Sesiones CPAP Oximetry Sessions Sleep Stage Sessions Sesiones de Etapas del Sueño Model %1 - %2 PAP Mode: %1 This day just contains summary data, only limited information is available. Total time in apnea Tiempo total en apnea Total ramp time Tiempo total en rampa Time outside of ramp Tiempo fuera de la dampa "Nothing's here!" "¡Aquí no hay nada!" No data is available for this day. Pick a Colour Escoja un color Bookmark at %1 Marcador en %1 ExportCSV Export as CSV Exportar como CSV Dates: Fechas: Resolution: Resolución: Details Detallado Sessions Sesiones Daily Diario Filename: Nombre del archivo: Cancel Cancelar Export Exportar Start: Inicio: End: Fin: Quick Range: Intérvalo rápido: Most Recent Day Último Día Last Week Última Semana Last Fortnight Última Quincena Last Month Último Mes Last 6 Months Último Semestre Last Year Último Año Everything Todo Custom Personalizado Details_ Detalles_ Sessions_ Sesiones_ Summary_ Resumen_ Select file to export to Seleccione archivo a exportar CSV Files (*.csv) Archivos CSV (*.csv) DateTime FechaHora Session Sesión Event Evento Data/Duration Datos/Duración Date Fecha Session Count ¿conteo de sesiones? Conteo de sesión Start Inicio End Fin Total Time Tiempo total AHI IAH Count Conteo FPIconLoader Import Error Error de Importación This Machine Record cannot be imported in this profile. Este registro de máquina no puede ser importado a este perfi. The Day records overlap with already existing content. Estos registros diarios se traslapan con contenido previamente existente. Help Hide this message Search Topic: Help Files are not yet available for %1 and will display in %2. Help files do not appear to be present. HelpEngine did not set up correctly HelpEngine could not register documentation correctly. Contents Index Search Buscar No documentation available Please wait a bit.. Indexing still in progress No no %1 result(s) for "%2" clear MD300W1Loader Could not find the oximeter file: Could not open the oximeter file: MainWindow &Statistics E&stadísticas Report Mode Modo de reporte Standard Estándar Monthly Mensual Date Range Intérvalo de fechas Statistics Estadísticas Daily mmm Vista por día Overview Vista general Oximetry Oximetría Import Importar Help Ayuda &File &Archivo &View &Vista &Reset Graphs &Help A&yuda Troubleshooting &Data &Datos &Advanced &Avanzado Show Daily view Show Overview view &Maximize Toggle Maximize window Reset Graph &Heights Reset sizes of graphs Show Right Sidebar Show Statistics view Import &Dreem Data Show &Line Cursor Show Daily Left Sidebar Show Daily Calendar Create zip of CPAP data card Create zip of OSCAR diagnostic logs Create zip of all OSCAR data Report an Issue System Information Show &Pie Chart Show Pie Chart on Daily page Standard graph order, good for CPAP, APAP, Bi-Level Advanced Advanced graph order, good for ASV, AVAPS Show Personal Data Check For &Updates Purge Current Selected Day &CPAP &CPAP &Oximetry &Oximetría &Sleep Stage &Position &All except Notes All including &Notes Rebuild CPAP Data Restablecer Datos de CPAP &Preferences &Preferencias &Profiles &Perfiles E&xit &Salir Exit Salir View &Daily Ver Vista por &Día View &Overview Ver Vista &General View &Welcome Ver &Bienvenida Use &AntiAliasing Usar &AntiAliasing Show Debug Pane Mostrar panel de depuración Take &Screenshot &Capturar Pantalla O&ximetry Wizard Asistente de O&ximetría Print &Report Imprimir &Reporte &Edit Profile &Editar perfil Import &Viatom/Wellue Data Show Performance Information Mostrar información de desempeño CSV Export Wizard Asistente de exportación CSV Export for Review Exportar para visualización Exportar para revisión Daily Calendar Calendario diario Backup &Journal espero no se contradiga Respaldar &diario Online Users &Guide &Guía del Usuario (en línea) &About OSCAR &Frequently Asked Questions Preguntas &Frecuentes &Automatic Oximetry Cleanup &Limpieza Automática de Oximetría Change &User Cambiar &Usuario Purge &Current Selected Day &Purgar día actualmente seleccionado Right &Sidebar Acti&var panel derecho Daily Sidebar Barra lateral diaria View S&tatistics Ver Es&tadísticas Navigation Navegación Bookmarks Marcadores Records Registros Exp&ort Data Exp&ortar datos Profiles Purge Oximetry Data Purge ALL Machine Data &Import CPAP Card Data View Statistics Import &ZEO Data Importar Datos de &ZEO Import RemStar &MSeries Data Importar Datos de REMstar y Serie &M Sleep Disorder Terms &Glossary Glosario de &Términos de Transtornos del Sueño Change &Language Cambiar &Idioma Change &Data Folder Cambiar &Directorio de Datos Import &Somnopose Data Importar Datos de &Somnopose Current Days Días Actuales Welcome Bienvenida &About &Acerca de Access to Import has been blocked while recalculations are in progress. Se ha bloqueado el acceso a Importar mientras hay recalculaciones en progreso. Importing Data Importando Datos Please wait, importing from backup folder(s)... Por favor espere, importando desde el(los) directorio(s) de respaldo... Import Problem Problema de Importación Help Browser Loading profile "%1" Couldn't find any valid Machine Data at %1 No se pudieron encontrar datos de máquina válidos en %1 Please insert your CPAP data card... Por favor inserte la tarjeta de datos de CPAP... Import is already running in the background. La importación ya está corriendo en segundo plano. CPAP Data Located Datos de CPAP encontrados Import Reminder Importar Recordatorio Please note, that this could result in loss of data if OSCAR's backups have been disabled. There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete Export review is not yet implemented Reporting issues is not yet implemented Please open a profile first. %1's Journal Diario de %1 Choose where to save journal Elija dónde guardar el diario XML Files (*.xml) Archivos XML (*.xml) Access to Preferences has been blocked until recalculation completes. El acceso a Preferencias ha sido bloqueado hasta que se complete la recalculación. %1 (Profile: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Choose where to save screenshot Image files (*.png) The FAQ is not yet implemented If you can read this, the restart command didn't work. You will have to do it yourself manually. Are you sure you want to rebuild all CPAP data for the following machine: For some reason, OSCAR does not have any backups for the following machine: OSCAR does not have any backups for this machine! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: No help is available. Are you sure you want to delete oximetry data for %1 Está usted seguro de borrar los datos de oximetría para %1 <b>Please be aware you can not undo this operation!</b> <b>¡Por favor esté consciente de que esta operación no puede ser deshecha!</b> Select the day with valid oximetry data in daily view first. Seleccione primero un día con datos de oximetría válidos en la Vista por Día. Imported %1 CPAP session(s) from %2 Se importó(aron) %1 sesión(es) de CPAP desde %2 Import Success Importación Exitosa Already up to date with CPAP data at %1 Datos de CPAP al corriente en %1 Up to date Al corriente Choose a folder Elija un directorio No profile has been selected for Import. A %1 file structure for a %2 was located at: Una estructura de archivo %1 para un %2 fue encontrada en: A %1 file structure was located at: Una estructura de archivo %1 fue encontrada en: Would you like to import from this location? ¿Desea usted importar desde esta ubicación? Specify Especifique Find your CPAP data card Check for updates not implemented There was an error saving screenshot to file "%1" Hubo un error al guardar la captura de pantalla en el archivo "%1" Screenshot saved to file "%1" Captura de pantalla guardada en el archivo "%1" The User's Guide will open in your default browser Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Suponiendo que usted ha hecho <i>sus <b>propios</b> respaldos para TODOS sus datos de CPAP</i>, aún puede completar esta operación pero tendrá que restaurar desde sus respaldos manualmente. Are you really sure you want to do this? ¿Está en verdad seguro de querer realizar esto? Because there are no internal backups to rebuild from, you will have to restore from your own. Debido a que no hay respaldos internos desde donde reestablecer, tendrá que restaurar usted mismo. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) ¿Le gustaría importar desde sus propios respaldos ahora? (No habrá datos visibles para esta máquina hasta que así lo haga) Note as a precaution, the backup folder will be left in place. Are you <b>absolutely sure</b> you want to proceed? There was a problem opening MSeries block File: Hubo un problema abriendo el Archivo de Bloques del SerieM: MSeries Import complete Importación de SerieM completa The Glossary will open in your default browser Would you like to zip this card? Choose where to save zip ZIP files (*.zip) Creating zip... Calculating size... OSCAR Information MinMaxWidget Auto-Fit Defaults Override The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. The Minimum Y-Axis value.. Note this can be a negative number if you wish. The Maximum Y-Axis value.. Must be greater than Minimum to work. Scaling Mode This button resets the Min and Max to match the Auto-Fit NewProfile Edit User Profile Editar Perfil de Usuario I agree to all the conditions above. Acepto todas las condiciones arriba mencionadas. User Information Información de Usuario User Name Nombre de Usuario Password Protect Profile Perfil Protegido por Contraseña Password Contraseña ...twice... ...otra vez... Locale Settings Configuración Local Country País TimeZone Zona Horaria about:blank about:blank Very weak password protection and not recommended if security is required. DST Zone Zona de Horario de Verano Personal Information (for reports) Información Personal (para los reportes) First Name Nombre Last Name Apellidos It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. D.O.B. Fecha de Nacimiento <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> Gender Sexo Male Masculino Female Femenino Height Altura Metric English Contact Information Información de Contacto Address Domicilio Email Correo Electrónico Phone Teléfono CPAP Treatment Information Información del Tratamiento CPAP Date Diagnosed Fecha de Diagnóstico Untreated AHI IAH sin tratar CPAP Mode Modo de CPAP CPAP CPAP APAP APAP Bi-Level Bi-Nivel ASV ASV RX Pressure Presión Diagnosticada Doctors / Clinic Information Información del Médico/Clínica Doctors Name Nombre del Médico Practice Name Especialidad Patient ID ID de paciente &Cancel &Cancelar &Back &Anterior &Next &Siguiente Select Country Seleccione País This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Este software ha sido creado para asisitirlo a revisar los datos producidos por las máquinas de CPAP y otros equipos relacionados. PLEASE READ CAREFULLY POR FAVOR LEA CUIDADOSAMENTE Accuracy of any data displayed is not and can not be guaranteed. La exactitud de los datos mostrados no está y no puede ser garantizada. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Cualquier reporte generado es EXCLUSIVAMENTE PARA USO PERSONAL y no es adecuado DE NINGÚN MODO para fines de apego al tratamiento o diagnóstico médico. Use of this software is entirely at your own risk. El uso de este software es completamente bajo su propio riesgo. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team Welcome to the Open Source CPAP Analysis Reporter OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Please provide a username for this profile Por favor proporcione un nombre de usuario para este perfiĺ Passwords don't match Las contraseñas no coinciden Profile Changes Cambios al Perfil Accept and save this information? ¿Aceptar y guardar esta información? &Finish &Finalizar &Close this window &Cerrar esta Ventana Overview Range: Intérvalo: Last Week Última Semana Last Two Weeks Última Quincena Last Month Último Mes Last Two Months Último Bimestre Last Three Months Último Trimestre Last 6 Months Último Semestre Last Year Último Año Everything Todo Custom Personalizado Snapshot Start: Inicio: End: Fin: Reset view to selected date range Reinicializar vista al intérvalo seleccionado Toggle Graph Visibility Activar Visibilidad del Gráfico Drop down to see list of graphs to switch on/off. Expanda esta lista para mostrar u ocultar los gráficos disponibles. Graphs Gráficos Respiratory Disturbance Index Índice de Perturbación Respiratoria Apnea Hypopnea Index Índice de Apnea- Hipoapnea Usage Uso Usage (hours) Uso (horas) Session Times Horarios de la sesión Total Time in Apnea Total Time in Apnea (Minutes) Body Mass Index Índice de Masa Corporaĺ How you felt (0-10) ¿Cómo se sintió? (0-10) 10 of 10 Charts Show all graphs Mostrar todos los gráficos Hide all graphs Ocultar todos los gráficos OximeterImport Oximeter Import Wizard Asistente para la Importación de Datos desde el Oxímetro Skip this page next time. Saltar esta pantalla la próxima vez. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? ¿Desde dónde le gustaría importar? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Si no le molesta estar delante de una computadora funcionando toda la noche, esta opción le proporcionará una útil gráfica de pletismografía, la cual indica el ritmo cardiaco además de las lecturas normales de oximetría.</p></body></html> Record attached to computer overnight (provides plethysomogram) Desde un registro procesado de un día para otro (proporciona pletismografía) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Esta opción le permitirá importar datos desde archivos creados por el software de acompañamiento del oxímetro de pulso, tales como SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Desde un archivo de datos guardado por otro programa, como SpO2Review Please connect your oximeter device Por favor conecte su oxímetro Please connect your oximeter device, turn it on, and enter the menu Press Start to commence recording Presione Inicio para comenzar el registro Show Live Graphs Mostrar Gráficos en Vivo Duration Duración Pulse Rate Frecuencia de Pulso Multiple Sessions Detected Múltiples sesiones detectadas Start Time Details Detalles Import Completed. When did the recording start? Importación Completada. ¿Cuándó comenzó la grabación? Oximeter Starting time Hora de inicio del oxímetro I want to use the time reported by my oximeter's built in clock. Quiero usar la hora registrada por el reloj interno de mi oxímetro. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Inicié esta grabación del oxímetro al mismo (o casi) tiempo de una sesión de mi máquina CPAP. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Nota: Sincronizar con el inicio de una sesión CPAP será siempre más preciso.</p></body></html> Choose CPAP session to sync to: Elija con cuál sesión CPAP sincronizar: You can manually adjust the time here if required: Puede ajustar manualmente la hora si es necesario: HH:mm:ssap ap? HH:mm:ssap &Cancel &Cancelar &Information Page &Hoja de información CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 Set device date/time <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> Set device identifier Erase session after successful upload Import directly from a recording on a device <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> If you can read this, you likely have your oximeter type set wrong in preferences. Please choose which one you want to import into OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> &Retry &Choose Session &Escoger Sesión &End Recording &Finalizar Registro &Sync and Save &Sincronizar y Guardar &Save and Finish &Guardar y Terminar &Start &Iniciar Scanning for compatible oximeters Buscanda oxímetros compatibles Could not detect any connected oximeter devices. No se pudieron detectar oxímetros conectados. Connecting to %1 Oximeter Conectando al oxímetro %1 Renaming this oximeter from '%1' to '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. "%1", session %2 Nothing to import Your oximeter did not have any valid sessions. Close Waiting for %1 to start Waiting for the device to start the upload process... Select upload option on %1 Seleccione ĺa opción adecuada para realizar la descarga en %1 You need to tell your oximeter to begin sending data to the computer. Please connect your oximeter, enter it's menu and select upload to commence data transfer... %1 device is uploading data... El dispositivo %1 está descargando información... Please wait until oximeter upload process completes. Do not unplug your oximeter. Por favor espere hasta que la descarga desde el oxímetro finalice. No lo desconecte. Oximeter import completed.. Importación desde el oxímetro completada. Select a valid oximetry data file Seleccione un archivo válido con datos de oximetría Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Live Oximetry Mode Live Oximetry Stopped Live Oximetry import has been stopped Oximeter Session %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Oximeter not detected Oxímetro no detectado Couldn't access oximeter No se pudo acceder al oxímetro Starting up... Iniciando... If you can still read this after a few seconds, cancel and try again Si aún puede leer esto después de asgunos segundos, cancele e inténtelo de nuevo Live Import Stopped Importación en vivo detenida %1 session(s) on %2, starting at %3 %1 sesión(es) en %2, comenzando en %3 No CPAP data available on %1 No hay datos de CPAP disponibles en %1 Recording... Registrando... Finger not detected Dedo no detectado I want to use the time my computer recorded for this live oximetry session. Quiero usar la hora registrada por mi computadora para esta sesión de oximetría en vivo. I need to set the time manually, because my oximeter doesn't have an internal clock. Necesito configurar la hora manualmente porque mi oxímetro no tiene reloj interno. Something went wrong getting session data Algo salió mal al obtener los datos de la sesión Welcome to the Oximeter Import Wizard Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Please remember: Important Notes: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Oximetry Date Fecha d/MM/yy h:mm:ss AP ¿ap? d/MM/aa h:mm:ss AP R&eset R&einicializar Pulse Pulso &Open .spo/R File &Abrir archivo .spo/R Serial &Import &Importación en serie &Start Live Iniciar en &vivo Serial Port Puerto Serie &Rescan Ports Volver a &buscar puertos PreferencesDialog Preferences Preferencias &Import &Importar Combine Close Sessions Combinar Sesiones Cerradas Minutes Minutos Multiple sessions closer together than this value will be kept on the same day. Sesiones múltiples más cercanas que este valor serán mantenidas en el mimsmo día. Ignore Short Sessions Ignorar sesiones cortas <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sesiones de duración menor a esta no serán mostradas<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Hora de cambio de día Sessions starting before this time will go to the previous calendar day. Sesiones comenzadas antes de esta hora van al día calendario previo. Session Storage Options Opciones de almacenamiento de sesión Create SD Card Backups during Import (Turn this off at your own peril!) Compress SD Card Backups (slower first import, but makes backups smaller) Comprimir Respaldos de tarjeta SD (primera importación más lenta, respaldos más pequeños) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Considerar días con menos uso que este como incumplidos. Cuatro horas son generalmente consideradas como en cumplimiento. hours horas Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Habilitar/deshabilitar mejoras experimentales de detección de eventos. Esto permite detectar eventos limítrofes, y algunos que la máquina pasó por alto. Esta opción debe habilitarse antes de importar, de otro modo es requerida una purga. Flow Restriction Restricción de flujo Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Porcentaje de restricción de fluje respecto al valor medio. Un valor de 20% funciona bien para detectar apneas. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">La deteccióon personalizada es un método experimental para detectar eventos errados por la máquina. Estos <span style=" text-decoration: underline;">no</span> están incluidos en el IAH.</p></body></html> Duration of airflow restriction Duración de la restricción de flujo s s Event Duration Duración del evento Allow duplicates near machine events. Permitir duplicados cercanos a eventos de la máquina. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Ajusta la cantidad de datos considerados para cada punto en el gráfico IAH/hora. 60 minutos por defecto. Se recomienda dejar este valor. minutes minutos Reset the counter to zero at beginning of each (time) window. Reinicializa el contador a cero al inicio de cada ventana de tiempo. Zero Reset Reinicializar a cero CPAP Clock Drift Deriva del reloj del CPAP Do not import sessions older than: No importar sesiones más antiguas que: Sessions older than this date will not be imported Las sesiones más antiguas que esta fecha no serán importadas dd MMMM yyyy dd MMMM aaaa Show in Event Breakdown Piechart Mostra gráfico de pastel con el desglose de eventos Resync Machine Detected Events (Experimental) Resincronizar los eventos detectados por la máquina (EXPERIMENTAL) User definable threshold considered large leak Umbral definible por el usuario para la consideración de fugas grandes Whether to show the leak redline in the leak graph Mostrar/Ocultar la línea roja en el gráfico de fugas Search Buscar &Oximetry &Oximetría Percentage drop in oxygen saturation Porcentaje de caída en la saturación de oxígeno Pulse Pulso Sudden change in Pulse Rate of at least this amount Cambio repentino en el pulso de al menos esta cantidad bpm ppm Minimum duration of drop in oxygen saturation Duración mínima de la caída en la saturación de oxígeno Minimum duration of pulse change event. Duración mínima del evento de cambio en el pulso. Small chunks of oximetry data under this amount will be discarded. Fragmentos pequeños de datos de oximetría menores a esta cantidad serán descartados. &General General Settings Configuración General Daily view navigation buttons will skip over days without data records Los botones de navegación en la vista diaria se saltarán los días sin datos Skip over Empty Days Saltar días vacíos Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Permitir el uso de núcleos múltiples del CPU de ser posible para mejorar el desempeño. Afecta principalmente al importador. Enable Multithreading Habilitar Multithreading Bypass the login screen and load the most recent User Profile Saltar la pantalla de inicio de sesión y cargar el perfil de usuario más reciente <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Estas características han sido podadas. Volverán después.</p></body></html> Changes to the following settings needs a restart, but not a recalc. Cambios a los siguientes ajustes requieren un reinicio pero no una recalculación. Preferred Calculation Methods Métodos de cálculo preferidos Middle Calculations ¡? Cálculo del centro Upper Percentile Percentil superior For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Para consistencia, los usuarios de ResMed deben usar 95% aquí, ya que es el único valor disponible en los días de sólo resumen. Median is recommended for ResMed users. Se recomienda Mediana para los usuarios de ResMed. Median Mediana Weighted Average Promedio Ponderado Normal Average Promedio True Maximum Máximo Verdadero 99% Percentile Percentil 99% Maximum Calcs Cálculo de máximo Session Splitting Settings <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Memory and Startup Options Pre-Load all summary data at startup <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> Keep Waveform/Event data in memory <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> Import without asking for confirmation General CPAP and Related Settings Enable Unknown Events Channels AHI Apnea Hypopnea Index IAH RDI Respiratory Disturbance Index IPR AHI/Hour Graph Time Window Preferred major event index Compliance defined as Flag leaks over threshold Seconds Segundos <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> Hours Horas <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> Combined Count divided by Total Hours Time Weighted average of Indice Standard average of indice Changing SD Backup compression options doesn't automatically recompress backup data. Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Compress Session Data (makes OSCAR data smaller, but day changing slower.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered Custom CPAP User Event Flagging This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Show Remove Card reminder notification on OSCAR shutdown Always save screenshots in the OSCAR Data folder Check for new version every Buscar por una nueva versión cada days. días. Last Checked For Updates: Ultima búsqueda de actualizaciones: TextLabel Etiqueta de Texto I want to be notified of test versions. (Advanced users only please.) &Appearance A&pariencia Graph Settings Ajustes de gráficos <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> Bar Tops ? Barras Line Chart Líneas Overview Linecharts Gráficos de vista general <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Esto hace que el deplazamiento mientras se hace zoom sea más fácil en Touchpads bidireccionales sensibles.</p><p>El valor recomendado son 50 ms.</p></body></html> Scroll Dampening Atenuación del desplazamiento Overlay Flags Sobreponer indicadores Line Thickness Grosor de línea The pixel thickness of line plots Grosor del pixel en gráficos de línea Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. El caché Pixmap es una técnica de aceleración de gráficos. Puede causar problemas con el uso de fuentes durante el despliegue de gráficos en tu plataforma. Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Fonts (Application wide settings) The visual method of displaying waveform overlay flags. Método visual para mostrar los indicadores sobrepuestos de la forma de onda. Standard Bars Barras estándar Graph Height Altura del gráfico Default display height of graphs in pixels Altura por defecto para el despliegue de gráficos en pixeles How long you want the tooltips to stay visible. Que tanto tiempo permanece visible la ventana de ayuda contextual. Events Eventos Reset &Defaults <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> Waveforms Flag rapid changes in oximetry stats Other oximetry options Discard segments under Flag Pulse Rate Above Flag Pulse Rate Below This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Calculate Unintentional Leaks When Not Present 4 cmH2O 4 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Show flags for machine detected events that haven't been identified yet. Tooltip Timeout Visibilidad del menú de ayuda contextual Graph Tooltips Ayuda contextual del gráfico Top Markers Auto-Launch CPAP Importer after opening profile Automatically load last used profile on start-up Your masks vent rate at 20 cmH2O pressure Your masks vent rate at 4 cmH2O pressure l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR On Opening Profile Perfil Welcome Bienvenida Daily Statistics Estadísticas Switch Tabs No change After Import Other Visual Settings Otras configuraciones visuales Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. El Anti-Aliasing suaviza la líneas en los gráficos. Algunos gráficos lucen más atractivos con esta opción activada. También afectará a los reportes impresos. Pruébela y vea si le agrada. Use Anti-Aliasing Usar Anti-Aliasing Makes certain plots look more "square waved". Hace que algunos gráficos luzcan más "cuadrados". Square Wave Plots Gráficos de onda cuadrada Use Pixmap Caching Usar caché Pixmap Animations && Fancy Stuff elegantes, no coquetas Animaciones y cosas coquetas Whether to allow changing yAxis scales by double clicking on yAxis labels Permitir cambiar la escala del eje Y haciendo dobli clic en sus etiquetas Allow YAxis Scaling Permitir escalado del eje Y Whether to include machine serial number on machine settings changes report Include Serial Number Graphics Engine (Requires Restart) Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Font Fuente Size Tamaño Bold Negritas Italic Itálica Application Aplicación Graph Text Texto del Gráfico Graph Titles Títulos del gráfico Big Text Texto grande Details Detalles &Cancel &Cancelar &Ok &Aceptar Flag Minor Flag Span Always Minor No CPAP machines detected Will you be using a ResMed brand machine? Never Name Nombre Color Color Flag Type Label CPAP Events Oximeter Events Positional Events Sleep Stage Events Unknown Events Double click to change the descriptive name this channel. Double click to change the default color for this channel plot/flag/data. %1 %2 %1 %2 Overview Vista general Double click to change the descriptive name the '%1' channel. Whether this flag has a dedicated overview chart. Here you can change the type of flag shown for this event This is the short-form label to indicate this channel on screen. This is a description of what this channel does. Lower Upper CPAP Waveforms Oximeter Waveforms Positional Waveforms Sleep Stage Waveforms Whether a breakdown of this waveform displays in overview. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Data Processing Required A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Data Reindex Required Se requiere reindizar datos A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Se requiere reindizar los datos para aplicar estos cambios. Esta operación tomará un par de minutos para completarse. ¿Está seguro que quiere realizar estos cambios? Restart Required Reinicio Requerido If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. If you need to conserve disk space, please remember to carry out manual backups. Are you sure you want to disable these backups? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Are you really sure you want to do this? ¿Está verdaderamente seguro de querer realizar esto? This may not be a good idea Esto podría no ser una buena idea <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Las máquinas ResMed S9 rutinariamente eliminan ciertos datos de la tarjeta SD si son anteriores a 7 y 30 días (depende de la resolución). ProfileSelector Filter: Reset filter to see all profiles Version Versión &Open Profile &Edit Profile &Editar perfil &New Profile Profile: None Please select or create a profile... Destroy Profile Profile Perfil Ventilator Brand Ventilator Model Other Data Last Imported Name Nombre You must create a profile Enter Password for %1 Ingrese contraseña para %1 You entered an incorrect password Ha ingresado una contraseña incorrecta Forgot your password? Ask on the forums how to reset it, it's actually pretty easy. Select a profile first The selected profile does not appear to contain any data and cannot be removed by OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. You are about to destroy profile '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. DELETE Sorry Lo sentimos You need to enter DELETE in capital letters. Necesita escribir DELETE en mayúsculas. There was an error deleting the profile directory, you need to manually remove it. Hubo un error al eliminar el directorio con el perfil, necesitará removerlo manualmente. Profile '%1' was succesfully deleted El perfil '%1' fue eliminado con éxito Bytes KB KB MB MB GB GB TB TB IT PB RP Summaries: Events: Backups: Hide disk usage information Show disk usage information Name: %1, %2 Nombre: %1, %2 Phone: %1 Teléfono: %1 Email: <a href='mailto:%1'>%1</a> Address: Domicilio: No profile information given Profile: %1 ProgressDialog Abort QObject No Data Sin datos On Activado Off Desactivado ft pie(s) lb libra(s) oz Kg kg cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours Horas Min %1 Mín %1 Hours: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 %1 Length: %3 Start: %2 Mask On Mask Off %1 Length: %3 Start: %2 TTIA: TTIA: %1 bpm pulsaciones por minuto/ ¿latidos por minuto? ppm ? ? Severity (0-1) Severidad (0-1) Error Warning Advertencia Please Note Por favor considere Graphs Switched Off Gráficos desactivados Sessions Switched Off Sesiones desactivadas &Yes &Sí &No &Cancel &Cancelar &Destroy &Destruir &Save &Guardad BMI índice de masa corporal IMC Weight Peso Zombie Zombi Pulse Rate Frecuencia de Pulso Plethy Pleti Pressure Presión Daily Vista por día Profile Perfil Overview Vista general Oximetry Oximetría Oximeter Oxímetro Event Flags Indicadores de eventos Default CPAP CPAP BiPAP BiPAP Bi-Level Bi-Nivel EPAP Min EPAP Max EPAP IPAP APAP ASV AVAPS ST/ASV Humidifier Humidificador H H OA AO A A CA AC FL LF LE evento de fuga EF EP Resoplido RS VS ronquido vibratorio RV VS2 RV2 RERA PP PP P P RE RE NR NR NRI O2 O2 PC CP UF1 UF1 UF2 UF2 UF3 UF3 PS SP AHI IAH RDI IPR AI IA HI IH UAI IANC CAI Índice de vía aérea despejada IVAD FLI ILF REI IRE EPI IRS Min IPAP App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Software Engine ANGLE / OpenGLES Desktop OpenGL m m cm in Minutes Minutos Seconds Segundos h h m m s s ms Events/hr Eventos/hora Hz Hz Litres Litros ml Breaths/min Inspiraciones/min Degrees Grados Information Información Busy Ocupado Only Settings and Compliance Data Available Summary Data Only Max IPAP SA SA PB RP IE IE Insp. Time Tiempo Insp. Exp. Time Tiempo Exp. Resp. Event Evento Resp. Flow Limitation Limitación de flujo Flow Limit Límite de flujo SensAwake Pat. Trig. Breath Insp. Inic. p/el U. Tgt. Min. Vent Vent. Min. Objetivo Target Vent. Vent. Objetivo. Minute Vent. Vent. Minuto Tidal Volume Volumen corriente Resp. Rate Frec. Resp. Snore Ronquido Leak Fuga Leaks Fugas Total Leaks Fugas totales Unintentional Leaks Fugas accidentales MaskPressure PresiónMascarilla Flow Rate Tasa de flujo Sleep Stage Estado del sueño Usage Uso Sessions Sesiones Pr. Relief Alivio de Presión No Data Available Sin información disponible Bookmarks Marcadores Mode Modo Model Modelo Brand Marca Serial # de Serie Series Serie Machine Máquina Channel Canal Settings Ajustes Motion Name Nombre DOB Fecha de Nacimiento Phone Teléfono Address Domicilio Email Correo Electrónico Patient ID ID de paciente Date Fecha Bedtime Hora de dormir Wake-up Hora de levantarse Mask Time Tiempo de mascarilla Unknown Desconocido None Ninguno Ready Listo First Primero Last Último Start Inicio End Fin Yes No no Min Max Med Mediana Average Promedio Median Mediana Avg Prom. W-Avg Prom. Pond. Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine Máquina sin capacidad de registro Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Machine Unsupported I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Scanning Files... Importing Sessions... Finishing up... Machine Untested Flex Lock Whether Flex settings are available to you. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Mask Resistance Setting Mask Resist. Hose Diam. 15mm 15mm 22mm 22mm Backing Up Files... Untested Data model %1 DreamStation 2 unknown model CPAP-Check AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS Flex Mode Modo Flex PRS1 pressure relief mode. Modo de alivio de presión PRS1. C-Flex C-Flex+ A-Flex P-Flex Rise Time Tiempo de ascenso Bi-Flex Flex Flex Level Nivel de Flex PRS1 pressure relief setting. Ajuste de alivio de presión PRS1. Passover Target Time PRS1 Humidifier Target Time Hum. Tgt Time Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Type of ramp curve to use. Linear SmartRamp Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Fixed Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Breath BPM Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled Variable Breathing UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend Peak Flow Peak flow during a 2-minute interval Humidifier Status Estado del humidificador PRS1 humidifier connected? ¿Está el humidificador PRS1 conectado? Disconnected Desconectado Connected Conectado Humidification Mode PRS1 Humidification Mode Humid. Mode Fixed (Classic) Adaptive (System One) Heated Tube Tube Temperature PRS1 Heated Tube Temperature Tube Temp. PRS1 Humidifier Setting Hose Diameter Diámetro de manguera Diameter of primary CPAP hose Diámetro de la manguera primaria del CPAP 12mm 12mm Auto On Encendido automático A few breaths automatically starts machine Unas cuantas inspiraciones activan la máquina automáticamente Auto Off Apagado automático Machine automatically switches off La máquina se apaga automáticamente Mask Alert Alerta de mascarilla Whether or not machine allows Mask checking. Define si se permite o no el chequeo de mascarilla. Show AHI Mostrar IAH Breathing Not Detected A period during a session where the machine could not detect flow. BND Timed Breath Inspiración temporizada Machine Initiated Breath Respiración iniciada por la máquina TB ¿inspiración temporizada? TB IT Windows User Usuario de Windows Using , found SleepyHead - You must run the OSCAR Migration Tool <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Los datos antiguos de su máquina deben ser regenerados, suponienda que esta característica no haya sido deshabilitada en Preferencias durante una importación de datos previa.</i> Launching Windows Explorer failed Falló el lanzamiento del Explorador de Windows Could not find explorer.exe in path to launch Windows Explorer. No se halló explorer.exe en el 'path' para lanzar el Explorador de Windows. <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> OSCAR does not yet have any automatic card backups stored for this device. This means you will need to import this machine data again afterwards from your own backups or data card. Esto significa que tendrá que importar los datos de esta máquina nuevamente después, desde sus propios respaldos de sa tarjeta de datos. Important: Importante: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Are you ready to upgrade, so you can run the new version of OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Machine Database Changes Cambios a la base de datos de la máquina OSCAR %1 needs to upgrade its database for %2 %3 %4 Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. The machine data folder needs to be removed manually. El directorio de datos de la máquina debe ser removido manualmente. This folder currently resides at the following location: Este directorio reside actualmente en la siguiente ubicación: Rebuilding from %1 Backup Reconstruyendo desde el respaldo de %1 or CANCEL to skip migration. You cannot use this folder: Migrating files from to OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. We suggest you use this folder: Click Ok to accept this, or No if you want to use a different folder. Next time you run OSCAR, you will be asked again. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Choose or create a new folder for OSCAR data Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. As you did not select a data folder, OSCAR will exit. The folder you chose is not empty, nor does it already contain valid OSCAR data. Data directory: It is likely that doing this will cause data corruption, are you sure you want to do this? Question Pregunta Exiting Saliendo Are you sure you want to use this folder? ¿Está seguro que quiere usar este directorio? Don't forget to place your datacard back in your CPAP machine OSCAR Reminder You can only work with one instance of an individual OSCAR profile at a time. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Loading profile "%1"... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Unable to create zip! Are you sure you want to reset all your channel colors and settings to defaults? ¿Está seguro de que quiere reinicializar los ajustes y colores del canal a sus valores por defecto? Are you sure you want to reset all your waveform channel colors and settings to defaults? There are no graphs visible to print No hay graficos visibles para imprimir Would you like to show bookmarked areas in this report? ¿Le gustaría mostrar las áreas con marcadores en este reporte? Printing %1 Report Imprimiendo reporte %1 %1 Report Reporte %1 : %1 hours, %2 minutes, %3 seconds : %1 horas, %2 minutos, %3 segundos RDI %1 IPR %1 AHI %1 IAH %1 AI=%1 HI=%2 CAI=%3 IA=%1 IH=%2 IAC=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% IRE=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 IANC=%1 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 Reportando desde %1 hasta %2 Entire Day's Flow Waveform Forma de onda del día completo Current Selection Selección actual Entire Day Día completo Page %1 of %2 Página %1 de %2 Jan Ene Feb Mar Apr Abr May Jun Jul Aug Ago Sep Oct Nov Dec Dic Events Eventos Duration Duración (% %1 in events) (%-%1 en eventos) Therapy Pressure Presión Terapéutica Inspiratory Pressure Presión Inspiratoria Lower Inspiratory Pressure Presión inspiratoria inferior Higher Inspiratory Pressure Presión inspiratoria superior Expiratory Pressure Presión Expiratoria Lower Expiratory Pressure Presión expiratoria inferior Higher Expiratory Pressure Presión expiratoria superior Pressure Support Soporte de presión PS Min SP Min Pressure Support Minimum Soporte de presión mínimo PS Max SP Máx Pressure Support Maximum Soporte de presión máximo Min Pressure Presión Mín Minimum Therapy Pressure Presión de terapia mínima Max Pressure Presión Máxima Maximum Therapy Pressure Máxima terapia de presión Ramp Time Tiempo de rampa Ramp Delay Period Periodo de retraso de rampa Ramp Pressure Presión de rampa Starting Ramp Pressure Presión inicial de rampa Ramp Event Evento de Rampa Ramp Rampa Vibratory Snore (VS2) Ronquido vibratorio (VS2) Mask On Time Mascarilla a tiempo Time started according to str.edf Hora de inicio según str.edf Summary Only Sólo resumen An apnea where the airway is open Una apnea mientras la vía aérea se encuentra abierta An apnea caused by airway obstruction Una apnea provocada por obstrucción de la vía aérea Hypopnea Hipoapnea A partially obstructed airway Una vía aérea parcialmente obstruida Unclassified Apnea Apnea no clasificada UA ANC Vibratory Snore Ronquido vibratorio A vibratory snore Un ronquido vibratorio A vibratory snore as detcted by a System One machine Un ronquido vibratorio detectado por la máquina System One Pressure Pulse Pulso de presión A pulse of pressure 'pinged' to detect a closed airway. Un pulso de presión 'lanzado' para detectar una vía aérea cerrada. Large Leak Fuga Grande A large mask leak affecting machine performance. Una fuga grande en la mascarilla que afecta el desempeño de la máquina. LL LL Non Responding Event Evento de no respuesta A type of respiratory event that won't respond to a pressure increase. Un tipo de evento respiratorio que no responde a los incrementos de presión. Expiratory Puff Soplo expiratorio Intellipap event where you breathe out your mouth. Evento de intellipap en el cual se espira por la boca. SensAwake feature will reduce pressure when waking is detected. Característica de SensAwake con la cual se reduce la presión cuando se detecta el despertar. User Flag #1 Bandera de usuario #1 User Flag #2 Bandera de usuario #2 User Flag #3 Bandera de usuario #3 Heart rate in beats per minute Frecuencia cardiaca en latidos por minuto Blood-oxygen saturation percentage Porcentaje de saturación de oxígeno en sangre Plethysomogram Pletismograma An optical Photo-plethysomogram showing heart rhythm Un foto-pletismograma óptico mostrando el ritmo cardiaco Pulse Change Cambio de Pulso A sudden (user definable) change in heart rate Un repentino (definible por el usuario) cambio en el ritmo cardiaco SpO2 Drop Caída de SpO2 A sudden (user definable) drop in blood oxygen saturation Una repentina (definible por el usuario) caída en la saturación de oxígeno en sangre SD ??????????????????? SD Breathing flow rate waveform Forma de onda de flujo respiratorio L/min L/min Mask Pressure Presión de mascarilla Amount of air displaced per breath Volumen de aire desplazado por inspiración Graph displaying snore volume Gráfico mastrando el volumen de los ronquidos Minute Ventilation Ventilaciót minuto Amount of air displaced per minute Cantidad de aire desplazado por minuto Respiratory Rate Frecuencia respiratoria Rate of breaths per minute Tasa de inspiraciones por minuto Patient Triggered Breaths Inpiraciones iniciadas por el paciente Percentage of breaths triggered by patient Porcentaje de inpiraciones iniciadas por el paciente Pat. Trig. Breaths Insp. inic. x paciente Leak Rate Tasa de fuga Rate of detected mask leakage Tasa de fugas de mascarilla detectadas I:E Ratio Tasa I:E Ratio between Inspiratory and Expiratory time Relación entre el tiempo de inspiración y espiración ratio tasa Pressure Min Presión mínima Pressure Max Presión Máxima Cheyne Stokes Respiration An abnormal period of Cheyne Stokes Respiration CSR Periodic Breathing An abnormal period of Periodic Breathing Clear Airway Obstructive Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Leak Flag Bandera de fuga LF BF A user definable event detected by OSCAR's flow waveform processor. Perfusion Index Índice de perfusión A relative assessment of the pulse strength at the monitoring site Una evaluación relativa de la fuerza del pulso en el lugar de monitorización Perf. Index % Índice perf. % Expiratory Time Tiempo espiratorio Time taken to breathe out El tiempo que tarda espirar Inspiratory Time Tiempo inspiratorio Time taken to breathe in El tiempo que toma inspirar Respiratory Event Evento respiratorio Graph showing severity of flow limitations Gráfico que muestra la severidad de las limitaciones de flujo Flow Limit. Límit. de Flujo Target Minute Ventilation Ventilación minuto objetivo An apnea that couldn't be determined as Central or Obstructive. Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting Apnea An apnea reportred by your CPAP machine. A restriction in breathing from normal, causing a flattening of the flow waveform. Mask Pressure (High frequency) A ResMed data item: Trigger Cycle Event Maximum Leak Fuga máxima The maximum rate of mask leakage La tasa máxima de fuga de la mascarilla Max Leaks Fugas máximas Apnea Hypopnea Index Índice de apnea-hipoapnea Graph showing running AHI for the past hour Gráfico que muestra la IAH corriente de la última hora Total Leak Rate Tasa de fuga total Detected mask leakage including natural Mask leakages Fuga de mascarilla detectada, incluyendo fugas naturales de la mascarilla Median Leak Rate Mediana de tasa de fuga Median rate of detected mask leakage Mediana de la tasa de fugas detectadas de mascarilla Median Leaks Fugas Medianas Respiratory Disturbance Index Índice de perturbación respiratoria Graph showing running RDI for the past hour Gráfico que muestra el IPR corriente de la última hora Movement Movement detector CPAP Session contains summary data only La sesión de CPAP sólo contiene un resumen PAP Mode Modo PAP PAP Device Mode Modo del dispositivo PAP APAP (Variable) ASV (Fixed EPAP) ASV (EPAP fijo) ASV (Variable EPAP) ASV (EPAP variable) Height Altura Physical Height Notes Notas Bookmark Notes Body Mass Index How you feel (0 = like crap, 10 = unstoppable) Bookmark Start Bookmark End Last Updated Journal Notes Journal Diario 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep Brain Wave BrainWave Awakenings Number of Awakenings Morning Feel How you felt in the morning Time Awake Time spent awake Time In REM Sleep Time spent in REM Sleep Time in REM Sleep Time In Light Sleep Time spent in light sleep Time in Light Sleep Time In Deep Sleep Time spent in deep sleep Time in Deep Sleep Time to Sleep Time taken to get to sleep Zeo ZQ Zeo sleep quality measurement ZEO ZQ Debugging channel #1 Test #1 For internal use only Debugging channel #2 Test #2 Zero Cero Upper Threshold Umbral superior Lower Threshold Umbral inferior Orientation Orientación Sleep position in degrees Posición de dormir en grados Inclination Inclinación Upright angle in degrees ?????????? Ángulo vertical en grados Days: %1 Días: %1 Low Usage Days: %1 Días de bajo uso: %1 (%1% compliant, defined as > %2 hours) (%1% cumplido, definido como > %2 horas) (Sess: %1) (Ses: %1) Bedtime: %1 Hora de dormir: %1 Waketime: %1 Hora de despertar: %1 (Summary Only) (Sólo resumen) There is a lockfile already present for this profile '%1', claimed on '%2'. Ya hay un archivo de cierre (lockfile) presente para el perfil '%1', reclamado en '%2'. Fixed Bi-Level Bi-nivel fijo Auto Bi-Level (Fixed PS) Bi-nivel automático (PS fijo) Auto Bi-Level (Variable PS) Bi-nivel automático (PS variable) 99.5% 90% {99.5%?} varies n/a Fixed %1 (%2) %1 Fijo (%2) Min %1 Max %2 (%3) Mín %1 Máx %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) ps?? PS %1 sobre %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) ps?? EPAP mín %1 IPAP máx %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> (last night) (anoche) (1 day ago) (%2 days ago) No oximetry data has been imported yet. Contec CMS50 Fisher & Paykel ICON DeVilbiss Intellipap SmartFlex Settings Configuración de SmartFlex ChoiceMMed MD300 Respironics M-Series Serie-M Philips Respironics System One ResMed S9 S9 EPR: Somnopose Somnopose Software Software Somnopose Zeo Personal Sleep Coach Consejero de sueño personal Locating STR.edf File(s)... Cataloguing EDF Files... Queueing Import Tasks... Finishing Up... CPAP Mode Modo de CPAP VPAPauto ASVAuto iVAPS PAC Auto for Her EPR ResMed Exhale Pressure Relief Alivio de presión de exhalación de ResMed Patient??? ¿paciente? EPR Level Nivel EPR Exhale Pressure Relief Level Alivio de presión de exhalación Response Patient View SmartStart Machine auto starts by breathing Smart Start Humid. Status Humidifier Enabled Status Humid. Level Humidity Level Temperature ClimateLine Temperature Temp. Enable ClimateLine Temperature Enable Temperature Enable AB Filter Antibacterial Filter Pt. Access Essentials Plus Climate Control Manual Soft Standard Estándar BiPAP-T BiPAP-S BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Advanced Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... Auto Mask ResMed Mask Setting Pillows Full Face Nasal Ramp Enable Database Outdated Please Rebuild CPAP Data Base de datos obsoleta Por favor recompile los datos del CPAP (%2 min, %3 sec) (%3 sec) Snapshot %1 Instantánea %1 Pop out Graph The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View There is no data to graph d MMM yyyy [ %1 - %2 ] Hide All Events Ocultar todos los eventos Show All Events Mostrar todos los eventos Unpin %1 Graph pin? Soltar gráfico %1 Popout %1 Graph Pin %1 Graph Fijar gráfico %1 Plots Disabled Gráficos deshabilitados Duration %1:%2:%3 Duración %1:%2:%3 AHI %1 IAH %1 Relief: %1 Alivio: %1 Hours: %1h, %2m, %3s Horas: %1h, %2m, %3s Machine Information Información de la máquina Journal Data Datos de diario OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR will not touch this folder, and will create a new one instead. Please be careful when playing in OSCAR's profile folders :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. OSCAR picked only the first one of these, and will use it in future: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Si sus antiguos datos no están presentes, copie los contenidos desde todos los otros directorios Journal_XXXXXXX manualmente hacia este. CMS50F3.7 CMS50F Backing up files... Reading data files... SmartFlex Mode Modo SmartFlex Intellipap pressure relief mode. Modo de alivio de presión Intellipap. Ramp Only Sólo rampa Full Time Tiempo completo SmartFlex Level Nivel SmartFlex Intellipap pressure relief level. Nivel de alivio de presión Intellipap. Snoring event. SN Weinmann SOMNOsoft2 CMS50D+ CMS50E/F Loading %1 data for %2... Scanning Files Migrating Summary File Location Loading Summaries.xml.gz Loading Summary Data Please Wait... %1 Charts %1 of %2 Charts Loading summaries Updating Statistics cache Usage Statistics Estadísticas de uso Dreem Your Viatom device generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Viatom Viatom Software New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to check for updates. Please try again later. SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank about:blank SessionBar %1h %2m %1h %2m No Sessions Present No hay sesiones presentes SleepStyleLoader Import Error Error de Importación This Machine Record cannot be imported in this profile. Este registro de máquina no puede ser importado a este perfi. The Day records overlap with already existing content. Estos registros diarios se traslapan con contenido previamente existente. Statistics Details Detallado Detalles Most Recent Más reciente Más recientes Therapy Efficacy This report was prepared on %1 by OSCAR %2 Last 30 Days Último Mes Últimos 30 días Last Year Último Año Average %1 Promedio %1 CPAP Statistics Estadísticas de CPAP CPAP Usage Uso de CPAP Average Hours per Night Promedio de horas por noche Compliance (%1 hrs/day) Leak Statistics Estadísticas de fuga Pressure Statistics Estadísticas de presión Oximeter Statistics Estadísticas del oxímetro Blood Oxygen Saturation Saturación de oxígeno en sangre Pulse Rate Frecuencia de Pulso %1 Median %1 Mediana Min %1 Mín %1 Max %1 Max %1 %1 Index Índice %1 % of time in %1 % del tiempo en %1 % of time above %1 threshold % del tiempo por encima del umbral de %1 % of time below %1 threshold % del tiempo por debajo del umbral de %1 Name: %1, %2 Nombre: %1, %2 DOB: %1 Fecha de Nacimiento: %1 Phone: %1 Teléfono: %1 Email: %1 Correo Electrónico: %1 Address: Domicilio: Oscar has no data to report :( Days Used: %1 Low Use Days: %1 Compliance: %1% Days AHI of 5 or greater: %1 Best AHI Date: %1 AHI: %2 Worst AHI Best Flow Limitation Date: %1 FL: %2 Worst Flow Limtation No Flow Limitation on record Worst Large Leaks Date: %1 Leak: %2% No Large Leaks on record Worst CSR Date: %1 CSR: %2% No CSR on record Worst PB Date: %1 PB: %2% No PB on record Want more information? OSCAR needs all summary data loaded to calculate best/worst data for individual days. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Best RX Setting Mejor Prescripción Date: %1 - %2 AHI: %1 Total Hours: %1 Worst RX Setting Peor Prescripción Last Week Última Semana Changes to Machine Settings No data found?!? Last 6 Months Último Semestre últimos 6 meses Last Session Última sesión No %1 data available. No hay datos %1 disponibles. %1 day of %2 Data on %3 %1 día(s) de datos %2 en %3 %1 days of %2 Data, between %3 and %4 %1 día(s) de datos %2 entre %3 y %4 OSCAR is free open-source CPAP report software Days Días Pressure Relief Pressure Settings Ajustes de presión Machine Information Información de la máquina First Use Primer Uso Last Use Último Uso Welcome Welcome to the Open Source CPAP Analysis Reporter What would you like to do? ¿Qué puedo hacer por usted? CPAP Importer Oximetry Wizard Daily View Overview Vista general Statistics Estadísticas <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> It would be a good idea to check File->Preferences first, as there are some options that affect import. Note that some preferences are forced when a ResMed machine is detected First import can take a few minutes. La primera importación podría tomar varios minutos. The last time you used your %1... La última vez que usó su %1... last night anoche %2 days ago hace %2 días was %1 (on %2) fue %1 (en %2) %1 hours, %2 minutes and %3 seconds %1 horas, %2 minutos, %3 segundos Your machine was on for %1. <font color = red>You only had the mask on for %1.</font> <font color = red>Sólo tuvo la mascarilla puesta por %1.</font> under over reasonably close to equal to You had an AHI of %1, which is %2 your %3 day average of %4. Your CPAP machine used a constant %1 %2 of air Your pressure was under %1 %2 for %3% of the time. Su presión estuvo bajo %1 %2 por %3 del tiempo. Your machine used a constant %1-%2 %3 of air. Your EPAP pressure fixed at %1 %2. La presión de EPAP se fijó en %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Su presión de IPAP estuvo debajo de %1 %2 por %3% del tiempo. Your EPAP pressure was under %1 %2 for %3% of the time. Su presión de EPAP estuvo debajo de %1 %2 por %3% del tiempo. Your machine was under %1-%2 %3 for %4% of the time. Su máquina estuvo por debajo de %1-%2 %3 por %4 del tiempo. 1 day ago Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. No se han importado datos de CPAP todavía. gGraph %1 days gGraphView 100% zoom level Restore X-axis zoom to 100% to view entire selected period. Restore X-axis zoom to 100% to view entire day's data. Reset Graph Layout Resets all graphs to a uniform height and default order. Y-Axis Plots CPAP Overlays Oximeter Overlays Dotted Lines Double click title to pin / unpin Click and drag to reorder graphs Remove Clone Clone %1 Graph OSCAR-code-v1.3.1/Translations/Filipino.ph.ts000066400000000000000000014071641417327530600207160ustar00rootroot00000000000000 AboutDialog &About &Tungkol Dito Release Notes Tala ng mga Bersyons Credits Credits GPL License GPL License Close Close Show data folder Ipakita ang folder ng data About OSCAR %1 Sorry, could not locate About file. Paumanhin, hindi mahanap ang file ng Tungkol Sa OSCAR. Sorry, could not locate Credits file. Paumanhin, hindi mahanap ang file ng Mga Pagkilala. Sorry, could not locate Release Notes. Paumanhin, hindi mahanap ang file ng Tala ng mga Bersyons. OSCAR %1 Important: Mahalaga: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. To see if the license text is available in your language, see %1. Upang makita kung magagamit ang teksto ng lisensya sa iyong wika, tingnan ang %1. CMS50F37Loader Could not find the oximeter file: Hindi mahanap ang oximeter file: Could not open the oximeter file: Hindi mabuksa ang oximeter file: CMS50Loader Could not get data transmission from oximeter. Hindi malipat ang data galing sa oximeter. Please ensure you select 'upload' from the oximeter devices menu. Pakusiguro na i-select ang upload sa oximeter devices menu. Could not find the oximeter file: Hindi mahanap ang oximeter file: Could not open the oximeter file: Hindi mabuksan ang oximeter file: CheckUpdates Checking for newer OSCAR versions Daily Go to the previous day Pumunta sa nakalipas na araw Show or hide the calender Ipakita o itago ang calendaryo Go to the next day Pumunta sa sunod na araw Go to the most recent day with data records Pumunta sa pinaka bago ng araw na may data Events Mga Pangyayari View Size i-adjust kung gaano kalaki ang nakikita Notes Mga Tala Journal Talaarawan B B u u Color Kulay Small Maliit Medium Katamtaman Big Malaki Zombie Zombie ang pakiramdam I'm feeling ... Ang pakiramdam ko ay ... Weight Timbang If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Awesome Napakabuti B.M.I. B.M.I. Bookmarks Mga Bookmark Add Bookmark Magdagdag ng Bookmark Starts Starts Remove Bookmark Alisin ang Bookmark Flags Flags Graphs Mga Talaguhitan Show/hide available graphs. Ipakita/itago ang talaguhitan. Breakdown Ang Pagsusuri events Mga pangyayari UF1 UF1 UF2 UF2 Time at Pressure Oras sa Pressure No %1 events are recorded this day Walang %1 mga pangyayari na itanala sa araw na ito %1 event %1 ng pangyayari %1 events %1 ng mga pangyayari Session Start Times Session Start Times Session End Times Session End Times Session Information Impormasyon sa Session Oximetry Sessions Mga session ng Oximeter Duration Duration (Mode and Pressure settings missing; yesterday's shown.) This bookmark is in a currently disabled area.. CPAP Sessions CPAP Sessions Sleep Stage Sessions Sleep Stage Sessions Position Sensor Sessions Position Sensor Sessions Unknown Session Unkown Session Machine Settings Settings ng aparato Model %1 - %2 Modelo %1- %2 PAP Mode: %1 PAP Mode:%1 99.5% This day just contains summary data, only limited information is available. Ang data sa araw na ito ay limitado. Total ramp time Total Ramp Time Time outside of ramp Oras sa labas ng Ramp Start Start End End Unable to display Pie Chart on this system Hindi ma display ang Pie Chart sa system nato 10 of 10 Event Types This CPAP machine does NOT record detailed data Sorry, this machine only provides compliance data. Paumanhin, ang aparato na to ay nagpapakita lang ng compliance data. "Nothing's here!" "Walang nandito!" No data is available for this day. Walang makukuhang data sa araw na ito. 10 of 10 Graphs Oximeter Information Oximeter Information Details Details Click to %1 this session. Pakiclick para %1 ang session na iito. disable disable enable enable %1 Session #%2 %1Session #%2 %1h %2m %3s %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. SpO2 Desaturations Sp02 Desaturations Pulse Change events Mga pangyayari na may pagbabago sa Pulso SpO2 Baseline Used Ginamitan ng Sp02 Baseline Statistics Statistics Total time in apnea Kabuuang oras sa Apnea Time over leak redline Oras na lumagpas sa leak redline Event Breakdown Event Breakdown Sessions all off! Naka off ang Sessions! Sessions exist for this day but are switched off. Merong Sessions sa araw na to pero naka off. Impossibly short session Imposible ng pagka ikli ng sessions Zero hours?? Sero na oras?? BRICK :( BRICK (Ang aparato mo ay hindi nagbibigay ng data) :( Complain to your Equipment Provider! Magreklamo sa nagbenta sayo ng aparato! Pick a Colour Pumili ng Kulay Bookmark at %1 Bookmark sa %1 ExportCSV Export as CSV i-export bilang CSV Dates: Mga Petsa: Resolution: Resolusyon: Details Mga Detalye Sessions Sessions Daily Daily Filename: Filename: Cancel Cancel Export i-Export Start: Start: End: End: Quick Range: Quick Range: Most Recent Day Pinakabagong Araw Last Week Nakaraang Linggo Last Fortnight Nakaraang dalawang Linggo Last Month Nakaraang Buwan Last 6 Months Nakaraang anim na Buwan Last Year Nakaraang Taon Everything Everything Custom Custom Details_ Details_ Sessions_ Sessions_ Summary_ Summary_ Select file to export to Piliin ang File na i-export sa CSV CSV Files (*.csv) CSV Files (*.csv) DateTime Petsa at Oras Session Session Event Event Data/Duration Data/Duration Date Date Session Count Session Count Start Start End End Total Time Total Time AHI AHI Count Bilang FPIconLoader Import Error May mali sa pag Import This Machine Record cannot be imported in this profile. Ang datos ng aparato na ito ay hindi pwede ma import. The Day records overlap with already existing content. Pinapatungan ng data na pangaraw-araw ang umiiral na laman. Help Hide this message Itago ang Mensaheng ito Search Topic: Paksang Hinahanap: Help Files are not yet available for %1 and will display in %2. Wala pang Help Files para dito sa %1 at ipapakita sa %2. Help files do not appear to be present. Walang Help Files para dito. HelpEngine did not set up correctly Ang Help Engine ay hindi na setup ng maayos HelpEngine could not register documentation correctly. Contents Ang Nilalaman Index Talatuntunan Search Hanapin No documentation available Walang Dokumento na magagamit Please wait a bit.. Indexing still in progress Pakiantay ng sandali..ginagawa pa ang talatuntunan No Hindi %1 result(s) for "%2" %1 resulta para sa "%2" clear Burahin MD300W1Loader Could not find the oximeter file: Hindi mahanap ang oximeter file: Could not open the oximeter file: Hindi mabuksan ang oximeter file: MainWindow &Statistics &Statistics Report Mode Report Mode Standard Standard Monthly Buwanan Date Range Date Range Statistics Statistics Daily Daily Overview Overview Oximetry Oximetry Import i-Import Help Tulong &File &File &View &Tingnan &Help &Tulong Troubleshooting &Data &Data &Advanced &Advanced Purge ALL Machine Data Rebuild CPAP Data Bumuo muli ng CPAP Data &Import CPAP Card Data Show Daily view Show Overview view &Maximize Toggle &Maximize Toggle Maximize window Reset Graph &Heights Reset sizes of graphs Show Right Sidebar Show Statistics view Import &Dreem Data Show &Line Cursor Show Daily Left Sidebar Show Daily Calendar Create zip of CPAP data card Create zip of OSCAR diagnostic logs Create zip of all OSCAR data Report an Issue i-Report ang Issue System Information Show &Pie Chart Show Pie Chart on Daily page Standard graph order, good for CPAP, APAP, Bi-Level Advanced Advanced graph order, good for ASV, AVAPS Show Personal Data Check For &Updates Purge Current Selected Day &CPAP &Oximetry &Sleep Stage &Position &All except Notes All including &Notes &Reset Graphs &Preferences &Preferences &Profiles &Profiles &About OSCAR &Tungkol sa OSCAR Show Performance Information Show Performance Information CSV Export Wizard CSV Export Wizard Export for Review i-Export para ma Review E&xit E&xit Exit Exit View &Daily View &Daily View &Overview View &Overview View &Welcome View &Welcome Use &AntiAliasing Use&AntiAiasing Show Debug Pane Show Debug Pane Take &Screenshot Kumuha &Screenshot O&ximetry Wizard O&ximetry Wizard Print &Report i-Print &i-Report &Edit Profile &i-Edit ang Profile Import &Viatom/Wellue Data Daily Calendar Daily Calendar Backup &Journal Backup &Journal Online Users &Guide Online Users &Guide &Frequently Asked Questions &Mga Tanong na Maaring Meron Ka &Automatic Oximetry Cleanup &Automatic Oximetry Cleanup Change &User Palitan &User Purge &Current Selected Day Purge&Current Selected Day Right &Sidebar Kanan &Sidebar Daily Sidebar Daily SIdebar View S&tatistics View S&tatistics Navigation Navigation Bookmarks Bookmarks Records Records Exp&ort Data i-Exp&ort ang Data Profiles Profiles Purge Oximetry Data Purge Oximetry Data View Statistics View Statistics Import &ZEO Data i-Import ang &ZEO Data Import RemStar &MSeries Data i-Import ang Remstar &MSeries Data Sleep Disorder Terms &Glossary Sleep Disorder Terms &Glossary Change &Language Ibahin &Language Change &Data Folder Ibahin &Folder ng Data Import &Somnopose Data i-Import &Somnopose Data Current Days Kasalukuyang mga Araw Welcome Welcome &About &Tungkol sa Please wait, importing from backup folder(s)... Pakiantay, ini-Import pa galing sa backup folder(s)... Import Problem May problema sa pag import Please insert your CPAP data card... Paki pasok ang CPAP data card... Access to Import has been blocked while recalculations are in progress. Pinagbabawal ang pag Import habang ginagawa ang recalculation. CPAP Data Located CPAP Data Located Import Reminder Paalala sa pag Import Find your CPAP data card Importing Data Importing Data Choose where to save screenshot Image files (*.png) The User's Guide will open in your default browser Bubukas ang User's Guide sa iyong default browser The FAQ is not yet implemented Ang FAQ ay hindi pa maitutupad If you can read this, the restart command didn't work. You will have to do it yourself manually. Kung nababasa nyo ito, ibig sabihin hindi gumana ang restart command. Kelangan nyo gawin manually. Are you sure you want to rebuild all CPAP data for the following machine: Sigurado ka ba na gusto mo i-rebuild lahat ng CPAP data para sa: For some reason, OSCAR does not have any backups for the following machine: Paumanhin, walang backup si OSCAR nito: OSCAR does not have any backups for this machine! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Babala: Binabago mo ang <font size=+2>obliterate</font> OSCAR's machine database para sa :</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: Hindi natuloy ang purge dahil sa file permission error; kelangan mo i-delete manually ang folder na ito: No help is available. Walang tulong para dito. %1's Journal %1's Journal Choose where to save journal Piliin kung saan gusto i-save ang journal XML Files (*.xml) XML Files (*.xml) Export review is not yet implemented Hindi pa maipapatupad ang export review Would you like to zip this card? Choose where to save zip ZIP files (*.zip) Creating zip... Calculating size... Reporting issues is not yet implemented Hindi pa maipapatupad ang reporting issues Help Browser Help Browser %1 (Profile: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Please open a profile first. Kelangan mo muna magbukas ng Profile. Check for updates not implemented Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Kung sigurado kang na backup <i>mo <b>na</b> ang LAHAT ALL ng CPAP data</i>, pwede mo pa rin ito ipagpatuloy, ngunit kelangan mo na i-restore ang backups manually. Are you really sure you want to do this? Sigurado ka ba talaga na gusto mo itong gawin? Because there are no internal backups to rebuild from, you will have to restore from your own. Dahil wala kang internal backups, kelang mo i-restore ito sa sarili mong backup file. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Gusto mo ba na mag import sa sarili mong backup file? (Wala kang makikitang data para sa aparato mo hanggang gawin mo ito) Note as a precaution, the backup folder will be left in place. Para sigurado, ang backup folder ay matitira. Are you <b>absolutely sure</b> you want to proceed? Siguradong <b>sigurado</b> ka ba na gusto mo ito ituloy? The Glossary will open in your default browser Bubukas ang Glossary sa default browser There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete Are you sure you want to delete oximetry data for %1 Sigurado ka ba na gusto mo i-delete ang oximetry data para sa %1 <b>Please be aware you can not undo this operation!</b> <b>Tandaan walang balikan kung gagawim mo ito!</b> Select the day with valid oximetry data in daily view first. Piliin mun ang araw na may valid oximetry data sa daily view. Loading profile "%1" Loading profile "%1" Imported %1 CPAP session(s) from %2 Imported %1 CPAP session(s) galing sa %2 Import Success Import Success Already up to date with CPAP data at %1 Up to date na ang CPAP data sa %1 Up to date Up to date Couldn't find any valid Machine Data at %1 Walang mahanap na valid Machine Data sa %1 Choose a folder Pumili ng folder No profile has been selected for Import. Walang profile na napili para i-Import. Import is already running in the background. Tumatakbo na ang Import sa background. A %1 file structure for a %2 was located at: Ang %1 file structure para sa %2 ay nasa: A %1 file structure was located at: Ang %1 file structure as nasa: Would you like to import from this location? Gusto mo bang mag import galing dito? Specify Paki specify Access to Preferences has been blocked until recalculation completes. Hindi ma access ang Preferences hangat matapos ang recalculation. There was an error saving screenshot to file "%1" May error sa pag save ng screenshot sa file "%1" Screenshot saved to file "%1" Ang screenshot ay na save sa file %1 Please note, that this could result in loss of data if OSCAR's backups have been disabled. Tandaan, na baka ma wala ang data mo pag ang OSCAR backups ay naka off. There was a problem opening MSeries block File: May error sa pag bukas ng MSeries block File: MSeries Import complete Kumpleto na ang pag import sa MSeries OSCAR Information MinMaxWidget Auto-Fit Auto-Fit Defaults Defaults Override Override The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Ang Y-Axis scaling mode, 'Auto-Fit' para automatic scaling, 'Defaults' para sa manufacturer settings, at 'Override' para pumili ng sarili. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Pwedeng negative number ang Minimum Y-Axis value kung gusto mo. The Maximum Y-Axis value.. Must be greater than Minimum to work. Para gumana kelangan na mas malaki ang Maximum Y-Axis value kesa Minimum. Scaling Mode Scaling Mode This button resets the Min and Max to match the Auto-Fit Ang button na ito ay pag reset ng Min at Max para ito ay maging parehas sa Auto-Fit NewProfile Edit User Profile i-Edit ang User Profile I agree to all the conditions above. Sumang-ayon sa lahat ng kondisyones sa itaas. User Information User Information User Name User Name Password Protect Profile Password Protect Profile Password Password ...twice... ...dalawang beses... Locale Settings Settings ng Lokasyon Country Country TimeZone TimeZone about:blank about:blank Very weak password protection and not recommended if security is required. DST Zone DST Zone Personal Information (for reports) Personal Information (para sa reports) First Name Pangalan Last Name Apelyido It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Ok lang kung hindi mo sabihin ang totoo mong edad , pero eto ay maka epekto sa kaisaktuhan ng ibang kalkulasyon. D.O.B. D.O.B. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Ang tunay na kasarian ay kinakailangan para sa ibang kalkulasyon pero kung nais mo, pwede mo itong iwanang blangko.</p></body></html> Gender Kasarian Male Lalaki Female Babae Height Kataasan Metric Metric English English Contact Information Contact Informations Address Address Email Email Phone Phone CPAP Treatment Information CPAP Treatment Information Date Diagnosed Date Diagnosed Untreated AHI Untreated AHI CPAP Mode CPAP Mode CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure RX Pressure Doctors / Clinic Information Doctors / Clinic Information Doctors Name Practice Name Practice ng Doctor Patient ID Patient ID &Cancel &Cancel &Back &Back &Next &Next Select Country Select Country Welcome to the Open Source CPAP Analysis Reporter Welcome to the Open Source CPAP Analysis Reporter This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Ang software na ito ay ginawa para makita mo ang data galing sa CPAP machine at mga kaugnay na equipment. PLEASE READ CAREFULLY PAKIBASA NG MABUTI Accuracy of any data displayed is not and can not be guaranteed. Hindi ma garantiya na ang mga data na pinapakita ay tama. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Ang mga reports dito ay para sa PERSONAL USE LAMANG, at HINDI SA KAHIT ANONG PARAAN pwede gamitin para sa compliance o medical diagnostic purposes. Use of this software is entirely at your own risk. Sa pag gamit mo sa sotware na ito , tinatanggap mo na walang pananagutan ang mga gumawa ng software na ito kung sakaling may mangyari na hindi kanais-nais. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. Libre ang OSCAR dahil napapailalim ito sa <a href='qrc:/COPYING'>GNU Public License v3</a>, wala itong warranty, at walang garantiya sa anumang panukala. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. Ang ukol ng OSCAR ay isang data viewer lamang, hindi ito kapalit sa patnubay na galing sa iyong Doctor. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Ang mga gumawa nito ay walang pananagutan sa <u>kahit anumang bagay</u> kaugnay sa paggamit or maling paggamit nito. Please provide a username for this profile Gumawa ng username para sa profile na ito Passwords don't match Hindi nag tugma ang mga Passwords Profile Changes Profile Changes Accept and save this information? Tanggapin at i-save ang impormasyon? &Finish &Finish &Close this window &Isara itong window Overview Range: Range: Last Week Nakaraang Linggo Last Two Weeks Nakaraang Dalawang Linggo Last Month Nakaraang Buwan Last Two Months Nakaraang Dalawang Buwan Last Three Months Nakaraang Tatlong Buwan Last 6 Months Nakaraang Anim na Buwan Last Year Nakaraang Taon Everything Lahat Custom Custom Snapshot Start: Start: End: End: Reset view to selected date range i-Reset ang view sa piniling date range Toggle Graph Visibility Toggle Graph Visibility Drop down to see list of graphs to switch on/off. Drop Down para makita ang graphs na pwedeng i- on/off. Graphs Graphs Respiratory Disturbance Index Respiratory Disturbance Index Apnea Hypopnea Index Apnea Hypopnea Index Usage Usage Usage (hours) Usage (hours) Session Times Session Times Total Time in Apnea Kabuuan Oras sa Apnea Total Time in Apnea (Minutes) Kabuuan Oras sa Apnea (Minuto) Body Mass Index Body Mass Index How you felt (0-10) Pakiramdam mo (0-10) 10 of 10 Charts Show all graphs Ipakita lahat ng graphs Hide all graphs Itago lahat ng graphs OximeterImport Oximeter Import Wizard Oximeter Import Wizard Skip this page next time. Sa susunod, laktawan mo na ang page na ito. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? Saan galing mo gusto mag import? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. CMS50E/F users, pag nag import kayo, wag piliin ang "upload on your device" hanggang sinabi ni OSCAR. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Kung naka enable, automatic na i-reset ni OSCAR ang oras sa internal clock ng CMS50 batay sa oras ng iyong computer.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Pag ok lang sayo na naka connect ka sa umaandar na computer buong gabi, ang option na ito ay magbigay sayo ng plethysomogram graph, na isa pang indikasyon sa heart rythm maliban pa sa oximetry readings.</p></body></html> Record attached to computer overnight (provides plethysomogram) Ang record ay naka connect sa computer buong gabi (nagbibgay ng plethysomogram) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Pahintulutan ka ng option na ito na i-import ang data files na galing sa software ng iyong Pulse Oximeter, gaya ng SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review i-Import galing sa datafile na naka save sa ibang program, gaya ng SpO2Review Please connect your oximeter device Paki-connect ang iyong oximeter device If you can read this, you likely have your oximeter type set wrong in preferences. Kung nakikita mo ito, malamang mali ang pagka set ng oximiter type sa preferences. Please connect your oximeter device, turn it on, and enter the menu Press Start to commence recording Pindutin ang Start para magumpisa ang recording Show Live Graphs Show Live Graphs Duration Gaano katagal Pulse Rate Pulse Rate Multiple Sessions Detected Multiple Sessions Detected Start Time Oras sa Pagsimula Details Mga Detalye Import Completed. When did the recording start? Import Completed. Kelang nag umpisa ang recording? Oximeter Starting time Oximeter Starting time I want to use the time reported by my oximeter's built in clock. Gusto ko gamitin ang oras na ginagamit ng internal clock ng oximeter. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Inumpisahan ko ang oximeter recording sa parehong oras ng session ko sa CPAP (o malapit dun). <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Paalala: pag sync sa oras ng CPAP session starting time ay mas accurate.</p></body></html> Choose CPAP session to sync to: Piliin ang CPAP session na gusto mo i-sync: You can manually adjust the time here if required: Pwede mo baguhin ang oras pag kelangan: HH:mm:ssap HH:mm:ssap &Cancel &Cancel &Information Page &Information Page CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Set device date/time <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Piliin para ma enable ang "device identifier next import", magagamit mo ito pag marami kang oximeters.</p></body></html> Set device identifier Set device identifier Erase session after successful upload Pagkatapos ng upload i-erase ang session Import directly from a recording on a device i-Import galing sa recordng na nasa device <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Paalala sa CPAP users: </span><span style=" color:#fb0000;">Naalala mo bang i-import ang CPAP sessions mo?<br/></span>Kung makalimutan mo ito, maging mali ang oras sa pag sync sa oximetry session nato.<br/>Para masiguro na tama ang pag sync ng devices, subukan na isabay ang pag start ng sync ng dalawa.</p></body></html> Please choose which one you want to import into OSCAR Paki pili kung alin ang gusto mo i-import kay OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>Kelangan ni OSCAR ng starting time para alam nya kung saan i-save ang oximetry session nato.</p><p>Pumili ng isa sa mga options nato:</p></body></html> &Retry &Retry &Choose Session &Choose Session &End Recording &End Recording &Sync and Save &Sync and Save &Save and Finish &Save and Finish &Start &Start Scanning for compatible oximeters Scanning para sa compatible na oximeters Could not detect any connected oximeter devices. Walang ma detect na naka connect na oximter devices. Connecting to %1 Oximeter Connecting to %1 Oximeter Renaming this oximeter from '%1' to '%2' Iibahin ang pangalan ng oximter nato galing '%1' sa '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Ang pangalan ng oximeter ay iba.. kung isa lang ang oximeter mo pero ginagamit ito sa ibat ibang profiles, paki pareho ang pangalan sa mga profiles. "%1", session %2 "%1", session %2 Nothing to import Walang i-import Your oximeter did not have any valid sessions. Walang valid na session sa oximeter. Close Close Waiting for %1 to start Inaantay na mag umpisa ang %1 Waiting for the device to start the upload process... Inaantay na magumpisa ang upload process... Select upload option on %1 Piliin ang upload option sa %1 You need to tell your oximeter to begin sending data to the computer. Kelangan mo sabihin sa oximeter na magpadala ng data sa computer. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Paki connect ang iyong oximeter, pumunta sa menu at piliin ang upload para mag umpisa ang data transfer... %1 device is uploading data... %1 device ay nag u-upload ng data... Please wait until oximeter upload process completes. Do not unplug your oximeter. Paki antay hanggang matapos ang upload process. Wag bunutin ang oximeter. Oximeter import completed.. Kumpleto na ang import ng oximeter.. Select a valid oximetry data file Pumili ng valid na oximetery data file Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Hindi ma analyze ng oximetry module ang file: Live Oximetry Mode Live Oximetry Mode Live Oximetry Stopped Live Oximetry Stopped Live Oximetry import has been stopped Huminto ang live oximetry import Oximeter Session %1 Oximeter Session %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. Kaya ni OSCAR i-track ng sabay ang Oximetry data at CPAP session data at eto ay maaring magbibigay syo ng kaalaman kung matagumpay ba ang CPAP treatment mo. Pwede mo din gamitin sa Pulse Oximeter lamang para i-track at i-review ang recorded data. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Kung sinusubukan mong i-sync ang oximetry and CPAP data, pakisigurado muna na tapos na ang pag import ng CPAP sessions bago tumuloy! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Para mabasa at mahanap ni OSCAR ang iyong Oximeter device, kelangan mo siguraduhin na ang mga device drivers ay tama (eg. USB to Serial UART) naka install sa iyong computer. Para sa kadagdagang impormasyon, %1pumunta dito%2. Oximeter not detected Ang oximeter ay hindi ma detect Couldn't access oximeter Ang oximeter ay hindi ma access Starting up... Starting up... If you can still read this after a few seconds, cancel and try again Kung nakikita mo pa rin ito, paki cancel at paki ulit muli Live Import Stopped Live Import Stopped %1 session(s) on %2, starting at %3 %1 session(s) on %2, starting at %3 No CPAP data available on %1 Walang CPAP data available sa %1 Recording... Recording... Finger not detected Hindi ma detect ang daliri I want to use the time my computer recorded for this live oximetry session. Gusto ko gamitin ang na record ng computer ko para sa live oximetry session. I need to set the time manually, because my oximeter doesn't have an internal clock. Kelangan ko i-set manually ang oras dahil ang oximeter ko ay walang internal clock. Something went wrong getting session data May error sa pagkuha ng session data Welcome to the Oximeter Import Wizard Welcome to the Oximeter Import Wizard Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Ang Pulse Oximeters ay medical devices na ginagamit na panukat ng blood oxygen saturation. Pag nakaranas ka ng Apnea events at abnormal breathing patterns, posibleng bumaba masyado ang blood oxygen saturation levels at eto ay indikasyon na baka kelangan kayo magpatingin sa doktor. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) Ang OSCAR ay pwedeng gamitin sa Contec CMS50D+, CMS50E, CMS50F at CMS50I serial oximeters.<br/>(Tandaan: ang pag import gamit ng bluetooth <span style=" font-weight:600;">ay hindi</span> pa pwede) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Tandaan na ang ibang mga kompanya kagaya sa Pulox ay pinapalitan lamang ang Contec CMS50's ng bagong pangalan kagaya ng Pulox PO-200, PO-300, PO-400. Etong mga ito ay compatible din. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Pwedeng basahin ang ChoiceMMed MD300W1 oximeter .dat files. Please remember: Paki tandaan: Important Notes: Important Notes: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Ang Contec CMS50D+ devices ay walang internal clock, at hindi ni-rerecord ang starting time. Kung wala kang CPAP session na pwedeng i-link sa recording , kelangan mo i-enter manually ang start time pagkatapos ng import process. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Kahit sa mga devices na may internal clock, nirerekomenda pa rin namin na ugaliin mo ang pag sabay sa pag record ng oximeter at CPAP sessions dahil ang CPAP internal clocks ay nagbabago sa habang panahon at mahirap ito i-reset. Oximetry Date Date d/MM/yy h:mm:ss AP R&eset Pulse &Open .spo/R File Serial &Import &Start Live Serial Port &Rescan Ports PreferencesDialog Preferences &Import Combine Close Sessions Minutes Multiple sessions closer together than this value will be kept on the same day. Ignore Short Sessions <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Sessions starting before this time will go to the previous calendar day. Session Storage Options Compress SD Card Backups (slower first import, but makes backups smaller) &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. hours Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Flow Restriction Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> Duration of airflow restriction s Event Duration Allow duplicates near machine events. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. minutes Reset the counter to zero at beginning of each (time) window. Zero Reset CPAP Clock Drift Do not import sessions older than: Sessions older than this date will not be imported dd MMMM yyyy User definable threshold considered large leak Whether to show the leak redline in the leak graph Search Hanapin &Oximetry Show in Event Breakdown Piechart Resync Machine Detected Events (Experimental) Percentage drop in oxygen saturation Pulse Sudden change in Pulse Rate of at least this amount bpm Minimum duration of drop in oxygen saturation Minimum duration of pulse change event. Small chunks of oximetry data under this amount will be discarded. &General Changes to the following settings needs a restart, but not a recalc. Preferred Calculation Methods Middle Calculations Upper Percentile Session Splitting Settings <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Memory and Startup Options Pre-Load all summary data at startup <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> Keep Waveform/Event data in memory <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> Import without asking for confirmation General CPAP and Related Settings Enable Unknown Events Channels AHI Apnea Hypopnea Index AHI RDI Respiratory Disturbance Index AHI/Hour Graph Time Window Preferred major event index Compliance defined as Flag leaks over threshold Seconds <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> Hours For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Median is recommended for ResMed users. Median Weighted Average Normal Average True Maximum 99% Percentile Maximum Calcs General Settings Daily view navigation buttons will skip over days without data records Skip over Empty Days Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Enable Multithreading Bypass the login screen and load the most recent User Profile Create SD Card Backups during Import (Turn this off at your own peril!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> Combined Count divided by Total Hours Time Weighted average of Indice Standard average of indice Custom CPAP User Event Flagging Events Εκδηλώσεις Reset &Defaults <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> Waveforms Flag rapid changes in oximetry stats Other oximetry options Discard segments under Flag Pulse Rate Above Flag Pulse Rate Below Changing SD Backup compression options doesn't automatically recompress backup data. Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Compress Session Data (makes OSCAR data smaller, but day changing slower.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Calculate Unintentional Leaks When Not Present 4 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Show flags for machine detected events that haven't been identified yet. Show Remove Card reminder notification on OSCAR shutdown Check for new version every days. Last Checked For Updates: TextLabel I want to be notified of test versions. (Advanced users only please.) &Appearance Graph Settings <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> Bar Tops Line Chart Overview Linecharts Whether to include machine serial number on machine settings changes report Include Serial Number Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> How long you want the tooltips to stay visible. Scroll Dampening Tooltip Timeout Default display height of graphs in pixels Graph Tooltips The visual method of displaying waveform overlay flags. Standard Bars Top Markers Graph Height Auto-Launch CPAP Importer after opening profile Automatically load last used profile on start-up <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered Your masks vent rate at 20 cmH2O pressure Your masks vent rate at 4 cmH2O pressure l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Always save screenshots in the OSCAR Data folder Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR On Opening Profile Profile Welcome Welcome Daily Daily Statistics Στατιστικά Switch Tabs No change After Import Overlay Flags Line Thickness The pixel thickness of line plots Other Visual Settings Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Use Anti-Aliasing Makes certain plots look more "square waved". Square Wave Plots Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Use Pixmap Caching <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> Animations && Fancy Stuff Whether to allow changing yAxis scales by double clicking on yAxis labels Allow YAxis Scaling Graphics Engine (Requires Restart) Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Fonts (Application wide settings) Font Size Bold Italic Application Graph Text Graph Titles Big Text Details &Cancel &Cancel &Ok Name Color Χρώμα Flag Type Label CPAP Events Oximeter Events Positional Events Sleep Stage Events Unknown Events Double click to change the descriptive name this channel. Double click to change the default color for this channel plot/flag/data. <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> %1 %2 %1 %2 Overview Overview Double click to change the descriptive name the '%1' channel. Whether this flag has a dedicated overview chart. Here you can change the type of flag shown for this event This is the short-form label to indicate this channel on screen. This is a description of what this channel does. Lower Upper CPAP Waveforms Oximeter Waveforms Positional Waveforms Sleep Stage Waveforms Whether a breakdown of this waveform displays in overview. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Data Processing Required A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Data Reindex Required A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Restart Required One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. If you need to conserve disk space, please remember to carry out manual backups. Are you sure you want to disable these backups? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Are you really sure you want to do this? Sigurado ka ba talaga na gusto mo itong gawin? Flag Minor Flag Span Always Minor No CPAP machines detected Will you be using a ResMed brand machine? Never This may not be a good idea ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ProfileSelector Filter: Reset filter to see all profiles Version &Open Profile &Edit Profile &i-Edit ang Profile &New Profile Profile: None Please select or create a profile... Destroy Profile Profile Profile Ventilator Brand Ventilator Model Other Data Last Imported Name You must create a profile Enter Password for %1 You entered an incorrect password Forgot your password? Ask on the forums how to reset it, it's actually pretty easy. Select a profile first The selected profile does not appear to contain any data and cannot be removed by OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. You are about to destroy profile '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. DELETE Sorry You need to enter DELETE in capital letters. There was an error deleting the profile directory, you need to manually remove it. Profile '%1' was succesfully deleted Bytes KB MB GB TB PB Summaries: Events: Backups: Hide disk usage information Show disk usage information Name: %1, %2 Phone: %1 Email: <a href='mailto:%1'>%1</a> Address: No profile information given Profile: %1 ProgressDialog Abort Abort QObject No Data Events Εκδηλώσεις Duration Διάρκεια (% %1 in events) Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ft lb oz Kg cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours Min %1 Hours: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 %1 Length: %3 Start: %2 Mask On Mask Off %1 Length: %3 Start: %2 TTIA: TTIA: %1 Minutes Seconds h m s ms Events/hr Hz bpm Litres ml Breaths/min ? Severity (0-1) Degrees Error Warning Information Busy Please Note Graphs Switched Off Sessions Switched Off &Yes &No &Cancel &Cancel &Destroy &Save BMI Weight Βάρος Zombie Βρυκόλακας Pulse Rate Pulse Rate Plethy Pressure Daily Daily Profile Profile Overview Overview Oximetry Oximetry Oximeter Event Flags Default CPAP CPAP BiPAP Bi-Level Bi-Level EPAP Min EPAP Max EPAP IPAP Min IPAP Max IPAP APAP APAP ASV ASV AVAPS ST/ASV Humidifier H OA A CA FL SA LE EP VS VS2 RERA PP P RE NR NRI O2 PC UF1 UF1 UF2 UF2 UF3 UF3 PS AHI AHI RDI AI HI UAI CAI FLI REI EPI PB IE Insp. Time Exp. Time Resp. Event Flow Limitation Flow Limit SensAwake Pat. Trig. Breath Tgt. Min. Vent Target Vent. Minute Vent. Tidal Volume Resp. Rate Snore Leak Leaks Large Leak LL Total Leaks Unintentional Leaks MaskPressure Flow Rate Sleep Stage Usage Usage Sessions Sessions Pr. Relief No Data Available App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Software Engine ANGLE / OpenGLES Desktop OpenGL m cm in Only Settings and Compliance Data Available Summary Data Only Bookmarks Σελιδοδείκτες Mode Model Brand Serial Series Machine Channel Settings Inclination Orientation Motion Name DOB Phone Phone Address Address Email Email Patient ID Patient ID Date Date Bedtime Wake-up Mask Time Unknown None Ready First Last Start Start End End On Off Yes No Hindi Min Max Med Average Median Avg W-Avg Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Machine Unsupported I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Scanning Files... Importing Sessions... Finishing up... Untested Data Machine Untested CPAP-Check AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS Flex Flex Lock Whether Flex settings are available to you. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Humidification Mode PRS1 Humidification Mode Humid. Mode Fixed (Classic) Adaptive (System One) Heated Tube Passover Tube Temperature PRS1 Heated Tube Temperature Tube Temp. Target Time PRS1 Humidifier Target Time Hum. Tgt Time Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Type of ramp curve to use. Linear SmartRamp Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Fixed Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Breath BPM Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled Variable Breathing UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend Peak Flow Peak flow during a 2-minute interval PRS1 Humidifier Setting Mask Resistance Setting Mask Resist. Hose Diam. 15mm 22mm Backing Up Files... model %1 DreamStation 2 unknown model Flex Mode PRS1 pressure relief mode. C-Flex C-Flex+ A-Flex P-Flex Rise Time Bi-Flex Flex Level PRS1 pressure relief setting. Humidifier Status PRS1 humidifier connected? Disconnected Connected Hose Diameter Diameter of primary CPAP hose 12mm Auto On A few breaths automatically starts machine Auto Off Machine automatically switches off Mask Alert Whether or not machine allows Mask checking. Show AHI Breathing Not Detected A period during a session where the machine could not detect flow. BND Timed Breath Machine Initiated Breath TB Windows User Using , found SleepyHead - You must run the OSCAR Migration Tool <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> Launching Windows Explorer failed Could not find explorer.exe in path to launch Windows Explorer. OSCAR %1 needs to upgrade its database for %2 %3 %4 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> OSCAR does not yet have any automatic card backups stored for this device. This means you will need to import this machine data again afterwards from your own backups or data card. Important: Mahalaga: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Are you ready to upgrade, so you can run the new version of OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Machine Database Changes Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. The machine data folder needs to be removed manually. This folder currently resides at the following location: Rebuilding from %1 Backup Therapy Pressure Inspiratory Pressure Lower Inspiratory Pressure Higher Inspiratory Pressure Expiratory Pressure Lower Expiratory Pressure Higher Expiratory Pressure Pressure Support PS Min Pressure Support Minimum PS Max Pressure Support Maximum Min Pressure Minimum Therapy Pressure Max Pressure Maximum Therapy Pressure Ramp Time Ramp Delay Period Ramp Pressure Starting Ramp Pressure Ramp Event Ramp Vibratory Snore (VS2) A ResMed data item: Trigger Cycle Event Mask On Time Time started according to str.edf Summary Only An apnea where the airway is open An apnea caused by airway obstruction Hypopnea A partially obstructed airway Unclassified Apnea UA Vibratory Snore A vibratory snore A vibratory snore as detcted by a System One machine Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A large mask leak affecting machine performance. Non Responding Event A type of respiratory event that won't respond to a pressure increase. Expiratory Puff Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. User Flag #1 User Flag #2 User Flag #3 Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm Pulse Change A sudden (user definable) change in heart rate SpO2 Drop A sudden (user definable) drop in blood oxygen saturation SD Breathing flow rate waveform L/min Mask Pressure Amount of air displaced per breath Graph displaying snore volume Minute Ventilation Amount of air displaced per minute Respiratory Rate Rate of breaths per minute Patient Triggered Breaths Percentage of breaths triggered by patient Pat. Trig. Breaths Leak Rate Rate of detected mask leakage I:E Ratio Ratio between Inspiratory and Expiratory time ratio Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting Cheyne Stokes Respiration An abnormal period of Cheyne Stokes Respiration CSR Periodic Breathing An abnormal period of Periodic Breathing Clear Airway Obstructive An apnea that couldn't be determined as Central or Obstructive. A restriction in breathing from normal, causing a flattening of the flow waveform. Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Leak Flag LF A user definable event detected by OSCAR's flow waveform processor. Perfusion Index A relative assessment of the pulse strength at the monitoring site Perf. Index % Mask Pressure (High frequency) Expiratory Time Time taken to breathe out Inspiratory Time Time taken to breathe in Respiratory Event Graph showing severity of flow limitations Flow Limit. Target Minute Ventilation Maximum Leak The maximum rate of mask leakage Max Leaks Apnea Hypopnea Index Graph showing running AHI for the past hour Total Leak Rate Detected mask leakage including natural Mask leakages Median Leak Rate Median rate of detected mask leakage Median Leaks Respiratory Disturbance Index Graph showing running RDI for the past hour Sleep position in degrees Upright angle in degrees Movement Movement detector CPAP Session contains summary data only PAP Mode Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Apnea An apnea reportred by your CPAP machine. PAP Device Mode APAP (Variable) ASV (Fixed EPAP) ASV (Variable EPAP) Height Kataasan Physical Height Notes Σημειώσεις Bookmark Notes Body Mass Index How you feel (0 = like crap, 10 = unstoppable) Bookmark Start Bookmark End Last Updated Journal Notes Journal Ημερολόγιο διάφορων πράξεων 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep Brain Wave BrainWave Awakenings Number of Awakenings Morning Feel How you felt in the morning Time Awake Time spent awake Time In REM Sleep Time spent in REM Sleep Time in REM Sleep Time In Light Sleep Time spent in light sleep Time in Light Sleep Time In Deep Sleep Time spent in deep sleep Time in Deep Sleep Time to Sleep Time taken to get to sleep Zeo ZQ Zeo sleep quality measurement ZEO ZQ Debugging channel #1 Test #1 For internal use only Debugging channel #2 Test #2 Zero Upper Threshold Lower Threshold As you did not select a data folder, OSCAR will exit. or CANCEL to skip migration. Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. You cannot use this folder: Migrating files from to OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. We suggest you use this folder: Click Ok to accept this, or No if you want to use a different folder. Choose or create a new folder for OSCAR data Next time you run OSCAR, you will be asked again. The folder you chose is not empty, nor does it already contain valid OSCAR data. Data directory: Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? Question Exiting Are you sure you want to use this folder? Don't forget to place your datacard back in your CPAP machine OSCAR Reminder You can only work with one instance of an individual OSCAR profile at a time. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Loading profile "%1"... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Unable to create zip! Are you sure you want to reset all your channel colors and settings to defaults? Are you sure you want to reset all your waveform channel colors and settings to defaults? There are no graphs visible to print Would you like to show bookmarked areas in this report? Printing %1 Report %1 Report : %1 hours, %2 minutes, %3 seconds RDI %1 AHI %1 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 Entire Day's Flow Waveform Current Selection Entire Day Page %1 of %2 Days: %1 Low Usage Days: %1 (%1% compliant, defined as > %2 hours) (Sess: %1) Bedtime: %1 Waketime: %1 (Summary Only) There is a lockfile already present for this profile '%1', claimed on '%2'. Fixed Bi-Level Auto Bi-Level (Fixed PS) Auto Bi-Level (Variable PS) varies 99.5% n/a Fixed %1 (%2) Min %1 Max %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> (last night) (1 day ago) (%2 days ago) No oximetry data has been imported yet. Contec CMS50 Fisher & Paykel ICON DeVilbiss Intellipap SmartFlex Settings ChoiceMMed MD300 Respironics M-Series Philips Respironics System One ResMed S9 EPR: Somnopose Somnopose Software Zeo Personal Sleep Coach Database Outdated Please Rebuild CPAP Data (%2 min, %3 sec) (%3 sec) Pop out Graph The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View There is no data to graph d MMM yyyy [ %1 - %2 ] Hide All Events Show All Events Unpin %1 Graph Popout %1 Graph Pin %1 Graph Plots Disabled Duration %1:%2:%3 AHI %1 Relief: %1 Hours: %1h, %2m, %3s Machine Information Journal Data OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR will not touch this folder, and will create a new one instead. Please be careful when playing in OSCAR's profile folders :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. OSCAR picked only the first one of these, and will use it in future: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. CMS50F3.7 CMS50F Backing up files... Reading data files... SmartFlex Mode Intellipap pressure relief mode. Ramp Only Full Time SmartFlex Level Intellipap pressure relief level. Snoring event. SN Locating STR.edf File(s)... Cataloguing EDF Files... Queueing Import Tasks... Finishing Up... CPAP Mode CPAP Mode VPAPauto ASVAuto iVAPS PAC Auto for Her EPR ResMed Exhale Pressure Relief Patient??? EPR Level Exhale Pressure Relief Level Response Patient View SmartStart Machine auto starts by breathing Smart Start Humid. Status Humidifier Enabled Status Humid. Level Humidity Level Temperature ClimateLine Temperature Temp. Enable ClimateLine Temperature Enable Temperature Enable AB Filter Antibacterial Filter Pt. Access Essentials Plus Climate Control Manual Soft Standard Standard BiPAP-T BiPAP-S BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Advanced Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... Auto Mask ResMed Mask Setting Pillows Full Face Nasal Ramp Enable Weinmann SOMNOsoft2 Snapshot %1 CMS50D+ CMS50E/F Loading %1 data for %2... Scanning Files Migrating Summary File Location Loading Summaries.xml.gz Loading Summary Data Please Wait... %1 Charts %1 of %2 Charts Loading summaries Updating Statistics cache Usage Statistics Dreem Your Viatom device generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Viatom Viatom Software New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to check for updates. Please try again later. SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank about:blank SessionBar %1h %2m No Sessions Present SleepStyleLoader Import Error May mali sa pag Import This Machine Record cannot be imported in this profile. Ang datos ng aparato na ito ay hindi pwede ma import. The Day records overlap with already existing content. Pinapatungan ng data na pangaraw-araw ang umiiral na laman. Statistics CPAP Statistics CPAP Usage Average Hours per Night Therapy Efficacy Leak Statistics Pressure Statistics Oximeter Statistics Blood Oxygen Saturation Pulse Rate Pulse Rate %1 Median Average %1 Min %1 Max %1 %1 Index % of time in %1 % of time above %1 threshold % of time below %1 threshold Name: %1, %2 DOB: %1 Phone: %1 Email: %1 Address: Days Used: %1 Low Use Days: %1 Compliance: %1% Days AHI of 5 or greater: %1 Best AHI Date: %1 AHI: %2 Worst AHI Best Flow Limitation Date: %1 FL: %2 Worst Flow Limtation No Flow Limitation on record Worst Large Leaks Date: %1 Leak: %2% No Large Leaks on record Worst CSR Date: %1 CSR: %2% No CSR on record Worst PB Date: %1 PB: %2% No PB on record Want more information? OSCAR needs all summary data loaded to calculate best/worst data for individual days. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Best RX Setting Date: %1 - %2 AHI: %1 Total Hours: %1 Worst RX Setting Most Recent Compliance (%1 hrs/day) This report was prepared on %1 by OSCAR %2 OSCAR is free open-source CPAP report software Changes to Machine Settings No data found?!? Oscar has no data to report :( Last Week Nakaraang Linggo Last 30 Days Last 6 Months Last Year Nakaraang Taon Last Session Details No %1 data available. %1 day of %2 Data on %3 %1 days of %2 Data, between %3 and %4 Days Pressure Relief Pressure Settings Machine Information First Use Last Use Welcome Welcome to the Open Source CPAP Analysis Reporter Welcome to the Open Source CPAP Analysis Reporter What would you like to do? CPAP Importer Oximetry Wizard Daily View Overview Overview Statistics Στατιστικά <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> It would be a good idea to check File->Preferences first, as there are some options that affect import. Note that some preferences are forced when a ResMed machine is detected First import can take a few minutes. The last time you used your %1... last night %2 days ago was %1 (on %2) %1 hours, %2 minutes and %3 seconds Your machine was on for %1. <font color = red>You only had the mask on for %1.</font> under over reasonably close to equal to You had an AHI of %1, which is %2 your %3 day average of %4. Your CPAP machine used a constant %1 %2 of air Your pressure was under %1 %2 for %3% of the time. Your machine used a constant %1-%2 %3 of air. Your EPAP pressure fixed at %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Your EPAP pressure was under %1 %2 for %3% of the time. Your machine was under %1-%2 %3 for %4% of the time. 1 day ago Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. gGraph %1 days %1 mga Araw gGraphView 100% zoom level 100% zoom level Restore X-axis zoom to 100% to view entire selected period. Restore X-axis zoom to 100% to view entire day's data. Reset Graph Layout i-Reset ang Graph Layout Resets all graphs to a uniform height and default order. i-Reset lahat ng graphs sa magkaparehong sukat at default order. Y-Axis Y-Axis Plots Plots CPAP Overlays CPAP Overlays Oximeter Overlays Oximeter Overlays Dotted Lines Double click title to pin / unpin Click and drag to reorder graphs Remove Clone Clone %1 Graph OSCAR-code-v1.3.1/Translations/Francais.fr.ts000066400000000000000000015057651417327530600207010ustar00rootroot00000000000000 AboutDialog &About &À propos Release Notes Notes de publication Credits Remerciements GPL License Licence GPL Close Fermer Show data folder Afficher le répertoire des données Sorry, could not locate About file. Désolé, je ne trouve pas À propos. Sorry, could not locate Credits file. Désolé, je ne trouve pas Remerciements. Important: Important : To see if the license text is available in your language, see %1. Pour voir si le texte de la licence est disponible dans votre langue, voyez %1. Sorry, could not locate Release Notes. Désolé, je ne trouve pas les Notes de publication. As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Comme c'est une préversion il est recommandé de <b>sauvegarder vos données à la main</b> avant de continuer. Tenter un retour en arrière plus tard pourrait endommager les données. About OSCAR %1 À propos d'OSCAR %1 OSCAR %1 OSCAR %1 CMS50F37Loader Could not find the oximeter file: Fichiers d'oxymétrie introuvables : Could not open the oximeter file: Impossible d'ouvrir les fichiers d'oxymétrie : CMS50Loader Could not get data transmission from oximeter. Impossible d'obtenir des données de l'oxymètre. Please ensure you select 'upload' from the oximeter devices menu. Merci de vérifier que vous avez sélectionné 'upload' dans les menus de votre oxymètre. Could not find the oximeter file: Fichiers d'oxymétrie introuvables : Could not open the oximeter file: Impossible d'ouvrir les fichiers d'oxymétrie : CheckUpdates Checking for newer OSCAR versions Recherche de nouvelles versions d'OSCAR Daily B Gras u Souligné Big Grand UF1 UF1 UF2 UF2 Color Couleur Flags Marques Notes Notes Small Petit Journal Journal Total time in apnea Temps total en apnée Position Sensor Sessions Session des capteurs de position Add Bookmark Ajouter un favori Remove Bookmark Enlever un favori Pick a Colour Choisir une couleur Complain to your Equipment Provider! Plaignez-vous à votre fournisseur d'équipement ! Session Information Informations de session Sessions all off! Toutes les sessions sont désactivées ! %1 event %1 évènement Go to the most recent day with data records Aller au jour le plus récent avec des données Machine Settings Réglages de l'appareil B.M.I. I.M.C. Sleep Stage Sessions Sessions du sommeil Oximeter Information Informations de l'oxymètre Events Évènements Graphs Graphiques CPAP Sessions Sessions PPC Medium Moyen Starts Début Weight Poids Zombie Zombie Bookmarks Favoris Session End Times Fin de session %1 events %1 évènements events évènements BRICK :( PLANTAGE :( Event Breakdown Répartition des évènements SpO2 Desaturations Désaturations SpO₂ "Nothing's here!" "Rien ici !" Awesome Bien Pulse Change events Changements du pouls SpO2 Baseline Used Ligne de base du SpO₂ Zero hours?? Zéro heure ?!? Go to the previous day Aller au jour précédent Time over leak redline Fuites au-dessus ligne rouge Bookmark at %1 Favori à %1 Statistics Statistiques Breakdown Arrêt Unknown Session Session inconnue Sessions exist for this day but are switched off. Des sessions existent pour ce jour mais sont désactivées. Duration Durée View Size Taille de la vue Impossibly short session Session trop courte No %1 events are recorded this day Aucun évènement %1 disponible pour ce jour Show or hide the calender Afficher ou masquer le calendrier Time outside of ramp Durée hors rampe Total ramp time Durée totale de la rampe Go to the next day Aller au jour suivant Session Start Times Début de session Oximetry Sessions Sessions d'oxymétrie Model %1 - %2 Modèle %1 - %2 This day just contains summary data, only limited information is available. Jour avec informations limitées, seulement le résumé. I'm feeling ... Je me sens... Show/hide available graphs. Affiche ou cache les graphiques. Details Détails Time at Pressure Durée à cette pression Click to %1 this session. Cliquez pour %1 cette session. disable désactivé enable activé %1 Session #%2 %1 Session #%2 %1h %2m %3s %1h %2m %3s PAP Mode: %1 Mode PAP : %1 99.5% 99.5% Start Début End Fin Unable to display Pie Chart on this system Impossible d'afficher des graphiques sur ce système 10 of 10 Event Types Types d'évenement 10/10 This CPAP machine does NOT record detailed data L'appareil PPC NE contient AUCUNE donnée détaillée Sorry, this machine only provides compliance data. Désolé, votre appareil ne fournit que des données d'observance. 10 of 10 Graphs Graphique 10/10 If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Si la taille est supérieure à zéro dans les préférences, rentrer le poids ici affichera l'indice de masse corporelle (I.M.C.) No data is available for this day. Aucune donnée disponible pour ce jour. <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Veuillez noter :</b>les réglages affichés ci-dessous sont basés sur la supposition que rien n'a changé depuis les jours précédents. This bookmark is in a currently disabled area.. Ce favori est actuellement en zone désactivée.. (Mode and Pressure settings missing; yesterday's shown.) (Paramètres de mode et pression manquants. Ceux d'hier sont affichés.) ExportCSV AHI IAH End Fin Date Date End: Fin : Quick Range: Choix rapide : Daily Quotidien Event Évènements Start Début Last Fortnight Quatre derniers jours Most Recent Day Jour le plus récent Count Occurrence Filename: Nom de fichier : Select file to export to Choisir le fichier pour export Resolution: Résolution : Cancel Annuler Dates: Dates : Custom Personnalisé Export Exporter Start: Début : Data/Duration Date/Durée CSV Files (*.csv) Fichiers CSV (*.csv) Last Month Mois dernier Last 6 Months 6 derniers mois Total Time Temps total DateTime Date et heure Session Count Nb sessions Session Session Everything Tout Last Week Semaine dernière Last Year Dernière année Export as CSV Export en CSV Sessions_ Sessions_ Details Détails Summary_ Résumé_ Details_ Détails_ Sessions Sessions FPIconLoader This Machine Record cannot be imported in this profile. Import impossible des données de cet appareil dans ce profil. Import Error Erreur d'import The Day records overlap with already existing content. Les enregistrements du jour se chevauchent avec le contenu déjà existant. Help Search Topic: Sujet à rechercher : Contents Sommaire Index Index Search Rechercher Hide this message Cacher ce message Help Files are not yet available for %1 and will display in %2. Aide non disponible en %1 et sera affichée en %2. HelpEngine did not set up correctly Problème à l'ouverture du système d'aide HelpEngine could not register documentation correctly. Problème à l'enregistrement du système d'aide. No Non %1 result(s) for "%2" %1 résultat(s) pour "%2" clear effacer Help files do not appear to be present. Problème à l'ouverture du fichier d'aide. No documentation available Aucune documentation disponible Please wait a bit.. Indexing still in progress Patientez, indexation en cours MD300W1Loader Could not find the oximeter file: Fichiers d'oxymétrie introuvables : Could not open the oximeter file: Impossible d'ouvrir les fichiers d'oxymétrie : MainWindow Exit Quitter Help Aide Please insert your CPAP data card... Insérez la carte de données PPC svp... Daily Calendar Calendrier onglet Quotidien &Data &Données &File &Fichier &Help &Aide &View &Vues E&xit &Quitter Daily Quotidien Import &ZEO Data Importer des données &ZEO MSeries Import complete Import du fichier terminé There was an error saving screenshot to file "%1" Erreur en enregistrant la copie d'écran "%1" Choose a folder Choisissez un répertoire A %1 file structure for a %2 was located at: Une structure de fichier %1 pour un %2 a été située à : Importing Data Import en cours Online Users &Guide &Guide de l'utilisateur en ligne View &Welcome Vue &Bienvenue Show Performance Information Afficher les informations de performance There was a problem opening MSeries block File: Problème à l'ouverture du fichier MSeries : Current Days Jours courants &About &À propos View &Daily Afficher la vue &Quotidien View &Overview Afficher la vue &Aperçus Access to Preferences has been blocked until recalculation completes. Les Préférences sont bloquées pendant le recalcul. Import RemStar &MSeries Data Importer des données RemStar &MSeries Daily Sidebar Barre latérale onglet Quotidien Note as a precaution, the backup folder will be left in place. Par mesure de précaution, le dossier de sauvegarde sera laissé en place. Change &User &Changer de profil utilisateur %1's Journal %1's Journal Import Problem Problème d'import <b>Please be aware you can not undo this operation!</b> <b>Attention ! Cette opération ne peut être annulée !</b> View S&tatistics Afficher la vue S&tatistiques Monthly Mensuel Change &Language Changer de &langue Import Import Because there are no internal backups to rebuild from, you will have to restore from your own. Comme il n'y a pas de sauvegardes internes, vous devrez restaurer à partir de votre propre sauvegarde. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Voulez-vous importer vos propres sauvegardes maintenant ? (vous n'aurez pas de données visibles pour cet appareil jusqu'à ce que vous le fassiez) Please wait, importing from backup folder(s)... Patientez, importation de(s) dossier(s) de sauvegarde... Are you sure you want to delete oximetry data for %1 Voulez-vous effacer les données de l'oxymètre pour %1 O&ximetry Wizard Assistant d'o&xymétrie Bookmarks Favoris Right &Sidebar &Barre latérale droite Rebuild CPAP Data Reconstruire les données PPC XML Files (*.xml) Fichiers XML (*.xml) Date Range Période View Statistics Voir les statistiques CPAP Data Located Données PPC trouvées Access to Import has been blocked while recalculations are in progress. Accès à l'importation bloqué pendant les recalculs en cours. Sleep Disorder Terms &Glossary &Glossaire des termes des troubles du sommeil Are you really sure you want to do this? Êtes-vous vraiment sûr de vouloir faire cela ? Select the day with valid oximetry data in daily view first. Sélectionnez d'abord un jour avec des données valides dans la vue journalière. Records Enregistrements Use &AntiAliasing Utiliser l'&anti-aliasing Would you like to import from this location? Voulez-vous importer de cet emplacement ? Report Mode Type de rapport &Profiles &Profils utilisateurs CSV Export Wizard Assistant d'export en CSV &Automatic Oximetry Cleanup Nettoyage &automatique de l'oxymétrie Import is already running in the background. L'import est déjà lancé en tâche de fond. Specify Parcourir Standard Standard Statistics Statistiques Up to date À jour &Statistics &Statistiques Backup &Journal Sauvegarde du &journal Imported %1 CPAP session(s) from %2 %1 session(s) importée(s) de %2 Purge &Current Selected Day Purger le jour &courant sélectionné Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Vu que vous avez fait vos <i> <b> propres </b> sauvegardes pour l'ensemble de vos données PPC </i>, vous pouvez toujours effectuer cette opération, mais vous aurez à restaurer manuellement à partir de vos sauvegardes. &Advanced &Avancé Print &Report Imprimer &rapport Couldn't find any valid Machine Data at %1 Impossible de trouver des données de l'appareil valides à %1 Export for Review Export pour relecture Take &Screenshot &Faire une copie d'écran Overview Aperçus Show Debug Pane Afficher le panneau de debug &Edit Profile &Modifier le profil utilisateur Import Reminder Rappel d'import Exp&ort Data Exp&ort des données Welcome Bienvenue Import &Somnopose Data Importer des données &Somnopose Screenshot saved to file "%1" Copie d'écran "%1" enregistrée &Preferences &Préférences Are you <b>absolutely sure</b> you want to proceed? Êtes-vous <b> absolument sûr</b> de vouloir continuer ? Import Success Import réussi Choose where to save journal Choisissez où sauvegarder le journal &Frequently Asked Questions Questions &fréquentes Oximetry Oxymétrie A %1 file structure was located at: Une structure de fichier %1 a été trouvée à : Change &Data Folder Changer de répertoire des &données Navigation Navigation Already up to date with CPAP data at %1 Déjà à jour avec les données ici : %1 Profiles Profils Purge Oximetry Data Purger les données d'oxymétrie Help Browser Aide Loading profile "%1" Chargement du profil "%1" Please open a profile first. Sélectionnez le profil utilisateur. &About OSCAR &À propos d'OSCAR Report an Issue Rapporter un problème The FAQ is not yet implemented Désolé, fonction non encore implémentée If you can read this, the restart command didn't work. You will have to do it yourself manually. Veuillez redémarrer manuellement. Are you sure you want to rebuild all CPAP data for the following machine: Êtes-vous sûr de vouloir reconstruire toutes les données de PPC pour l'appareil suivant : For some reason, OSCAR does not have any backups for the following machine: Pour une raison quelconque, OSCAR n'a pas de sauvegardes internes pour l'appareil suivant : You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Vous êtes sur le point de <font size=+2>détruire</font> les données d'OSCAR pour l'appareil suivant :</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: Une erreur d'autorisation de fichier a planté le processus de purge, vous devrez supprimer manuellement le dossier suivant : No help is available. Aucune aide disponible. There was a problem opening %1 Data File: %2 Un problème est survenu lors de l'ouverture %1 du fichier de données :%2 %1 Data Import of %2 file(s) complete %1 Import de données de %2 fichier(s) terminé %1 Import Partial Success %1 Import partiellement réussi %1 Data Import complete %1 Import de données terminé Export review is not yet implemented Désolé, fonction non encore implémentée Reporting issues is not yet implemented Désolé, fonction non encore implémentée Please note, that this could result in loss of data if OSCAR's backups have been disabled. SVP, notez que cela pourrait entraîner la perte de données graphiques quand les sauvegardes internes d'OSCAR ont été désactivées. No profile has been selected for Import. Aucun profil sélectionné pour l'import. Show Daily view Afficher la vue quotidienne Show Overview view Afficher la vue globale &Maximize Toggle &Plein écran Maximize window Maximiser la fenêtre Reset Graph &Heights Réinitialiser la &hauteur des graphiques Reset sizes of graphs Réinitialiser la taille des graphiques Show Right Sidebar Afficher la barre latérale droite Show Statistics view Afficher Statistiques Import &Viatom/Wellue Data Import de données &Viatom/Wellue Show &Line Cursor Infos du curseur &ligne Show Daily Left Sidebar Barre latérale onglet Quotidien Show Daily Calendar Calendrier onglet Quotidien System Information Informations système Show &Pie Chart Afficher Gra&phique sectoriel Show Pie Chart on Daily page Graphique sectoriel onglet Quotidien Purge Current Selected Day Purger le jour sélectionné &CPAP &PPC &Oximetry &Oxymétrie &Sleep Stage &Stade de sommeil &Position &Position &All except Notes &Tout sauf les Notes All including &Notes Tout y compris les &Notes &Reset Graphs &Réinitialiser les graphiques The User's Guide will open in your default browser Le guide utilisateur sera ouvert dans le navigateur par défaut The Glossary will open in your default browser Le glossaire sera ouvert dans le navigateur par défaut OSCAR Information Informations sur OSCAR Standard graph order, good for CPAP, APAP, Bi-Level Ordre standard des graphiques, adapté pour CPAP, APAP, Bi-Level Advanced Avancé Advanced graph order, good for ASV, AVAPS Ordre des graph avancé, bon pour ASV, AVAPS Troubleshooting Dépannage Purge ALL Machine Data Purger TOUTES les données de l'appareil &Import CPAP Card Data &Importer les données PPC depuis la carte SD Import &Dreem Data Importer les données depuis &Dreem Create zip of CPAP data card Créer un fichier zip des données de la carte SD Create zip of all OSCAR data Créer un fichier zip de toutes les données d'OSCAR %1 (Profile: %2) %1 (Profil : %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Sélectionner le dossier racine ou la lettre de lecteur de votre carte de données, et non pas un dossier à l’intérieur. Find your CPAP data card Trouver votre carte de données PPC Choose where to save screenshot Choisir où enregistrer la capture d’écran Image files (*.png) Fichiers image (*.png) OSCAR does not have any backups for this machine! OSCAR n’a pas de sauvegarde pour cet appareil ! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Si vous n'avez pas effectué <i>vos <b>propres</b> sauvegardes de TOUTES les données pour cet appareil</i>, <font size=+2>vous allez les perdre de façon <b>définitive</b>!</font> Would you like to zip this card? Souhaitez-vous compresser cette carte ? Choose where to save zip Choisissez où enregistrer le fichier compressé ZIP files (*.zip) Fichiers ZIP (*.zip) Creating zip... Création du fichier ZIP... Calculating size... Calcul de la taille... Show Personal Data Afficher les données personnelles dans Statistiques Create zip of OSCAR diagnostic logs Créer un fichier zip des journaux de diagnostic d'OSCAR Check For &Updates Rechercher des &mises à jour Check for updates not implemented La fonction de vérification de mise à jour n'est pas activée MinMaxWidget Scaling Mode Type d'échelle The Maximum Y-Axis value.. Must be greater than Minimum to work. Valeur Y maximum... doit être supérieure à la valeur minimum. This button resets the Min and Max to match the Auto-Fit Bouton de remise à zéro aux valeurs automatiques The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Échelle des Y, 'Automatique' pour échelle automatique, 'Par défaut' pour les réglages constructeur et 'Personnalisé' pour choisir par vous-même. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Valeur minimum en Y... Les valeurs négatives sont possibles. Defaults Par défaut Auto-Fit Automatique Override Personnalisé NewProfile ASV ASV APAP APAP CPAP PPC Male Homme &Back &Précédent &Next &Suivant TimeZone Fuseau horaire Email Courriel Phone Téléphone Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Les rapports générés ne le sont QUE pour usage PERSONNEL et ne peuvent être utilisés pour aucun diagnostic médical. &Close this window &Fermer la fenêtre Edit User Profile Modifier le profil utilisateur Please provide a username for this profile Merci de donner un nom à ce profil CPAP Treatment Information Informations sur le traitement PPC Password Protect Profile Profil protégé par mot de passe Accuracy of any data displayed is not and can not be guaranteed. La précision des données n'est pas et ne peut pas être garantie. D.O.B. Né le. Female Femme Gender Sexe Height Taille Contact Information Coordonnées Locale Settings Réglages locaux CPAP Mode Mode PPC Select Country Choisissez le pays PLEASE READ CAREFULLY MERCI DE LIRE ATTENTIVEMENT Untreated AHI IAH non traité Address Adresse I agree to all the conditions above. Je suis d'accord avec toutes ces conditions. DST Zone Zone heure d'été/hiver about:blank au sujet : blanc RX Pressure Pression RX Password Mot de passe Use of this software is entirely at your own risk. Vous utilisez ce logiciel à vos risques et périls. Passwords don't match Non correspondance des mots de passe First Name Prénom Last Name Nom Country Pays &Cancel &Annuler &Finish &Fin Bi-Level Bi-Level Profile Changes Modification du profil Personal Information (for reports) Informations personnelles (pour les rapports) User Name Nom d'utilisateur This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Ce logiciel est conçu pour vous aider à visualiser les données de votre appareil respiratoire à Pression Positive Continue (PPC) et équipements en rapport. User Information Informations utilisateur ...twice... ...et de deux... Doctors Name Nom du médecin Doctors / Clinic Information Docteur / Informations sur la clinique Practice Name Spécialité Date Diagnosed Date de diagnostic Accept and save this information? Accepter et sauvegarder ? Patient ID Identifiant du patient It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Votre âge approximatif n'est pas obligatoire mais améliorera la précision des calculs. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>L'indication de votre sexe n'est pas obligatoire mais améliorera la précision des calculs.</p></body></html> OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR est sous licence <a href='qrc:/COPYING'>GNU Licence Publique V3</a> Il est fourni sans aucune garantie et sans prétention de correspondre à un usage quelconque. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR est un outil de visualisation de données et ne doit pas se substituer à la compétence de votre médecin. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. L'auteur n'est <u> en aucun cas </u> responsable de l' utilisation faite de ce logiciel. Welcome to the Open Source CPAP Analysis Reporter Bienvenue dans O.S.C.A.R. (Open Source CPAP Analysis Reporter) Metric Métrique English Anglais Very weak password protection and not recommended if security is required. Protection par mot de passe très faible et non recommandée si la sécurité est requise. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR est copyright &copy;2011-2018 Mark Watkins et partiellement &copy;2019-2022 The OSCAR Team Overview End: Fin : Usage Utilisation Respiratory Disturbance Index Index des troubles respiratoires 10 of 10 Charts Graphique 10 de 10 Show all graphs Afficher les graphiques Reset view to selected date range Réinitialiser à la durée choisie Total Time in Apnea Temps total en apnée Drop down to see list of graphs to switch on/off. Dérouler pour voir la liste des graphiques à activer. Usage (hours) Utilisation (heures) Last Three Months 3 derniers mois Total Time in Apnea (Minutes) Temps total en apnée (minutes) Custom Personnalisé How you felt (0-10) Sensation (0-10) Graphs Graphiques Range: Durée : Start: Début : Last Month Mois dernier Apnea Hypopnea Index Index Apnée Hypopnée Last 6 Months 6 derniers mois Body Mass Index Indice de masse corporelle Session Times Durée session Last Two Weeks 2 dernières semaines Everything Tout Last Week Semaine dernière Last Year Année dernière Snapshot Copie d'écran Toggle Graph Visibility Activer les graphiques Hide all graphs Cacher les graphiques Last Two Months 2 derniers mois OximeterImport <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Cocher pour mettre à jour l'identifiant du matériel au prochain import, utile pour ceux ayant plusieurs oxymètres.</p></body></html> Press Start to commence recording Appuyez sur Démarrer pour commencer à enregistrer Close Fermer No CPAP data available on %1 Pas de données PPC disponibles sur %1 It also can read from ChoiceMMed MD300W1 oximeter .dat files. OSCAR lit aussi les fichiers .dat du ChoiceMMed MD300W1. Please wait until oximeter upload process completes. Do not unplug your oximeter. Merci d'attendre la fin du transfert. Ne pas le débrancher pendant ce temps. Finger not detected Doigt non détecté You need to tell your oximeter to begin sending data to the computer. Vous devez demander à l'oxymètre de transmettre des données à l'ordinateur. Renaming this oximeter from '%1' to '%2' Renommer cet oxymètre de '%1' en '%2' <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Cette option vous permet d'importer des données créées par le logiciel de votre oxymètre, comme SpO2Review.</p></body></html> Oximeter import completed.. Import terminé.. &Retry &Recommencer &Start &Débuter You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Information : d'autres entreprises comme Pulox rebadgent les Contec CMS50 sous les noms suivants : Pulox PO-200, PO-300, PO-400 qui devraient donc fonctionner. %1 session(s) on %2, starting at %3 %1 session(s) sur %2, démarrage à %3 I need to set the time manually, because my oximeter doesn't have an internal clock. Réglage manuel du temps en l'absence d'horloge interne sur l'oxymètre. You can manually adjust the time here if required: Vous pouvez ajuster l'heure manuellement : Couldn't access oximeter Impossible d'accéder à l'oxymètre Please connect your oximeter device Connectez votre matériel d'oxymétrie SVP Important Notes: Informations importantes : Starting up... Démarrage... Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Le Contec CMS50D+ n'a pas d'horloge interne et donc, n'enregistre pas l'horaire de départ. Si vous n'avez pas de session de PPC à lier avec, vous allez devoir entrer l'horaire de départ manuellement après import. HH:mm:ssap HH:mm:ss AP Import directly from a recording on a device Import direct d'un matériel d'enregistrement Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Le nom de l'oxymètre est différent. Si vous n'en avez qu'un et que vous l'utilisez pour différents profils, utilisez le même nom. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Même avec les appareils avec horloge interne, il est recommandé de prendre l'habitude de démarrer l'enregistrement de l'oxymétrie et de la PPC en même temps, car les horloges internes des appareils à PPC décalent et tous ne peuvent être remis à zéro facilement. &Information Page Page d'&informations I want to use the time reported by my oximeter's built in clock. Utiliser l'heure de l'horloge interne de l'oxymètre. Waiting for %1 to start Attente de %1 pour démarrer <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Mémo pour les utilisateurs de PPC : </span><span style=" color:#fb0000;"> avez-vous importé votre session PPC en premier ?<br/></span>Si vous l'oubliez, vos données ne seront pas correctement synchronisées.<br/>Pour assurer une bonne synchronisation des deux appareils, démarrez les toujours en même temps</p></body></html> Select a valid oximetry data file Sélectionnez un fichier de données valides %1 device is uploading data... %1 transmet des données... &End Recording T&erminer l'enregistrement &Choose Session &Choisir la session Nothing to import Rien à importer Select upload option on %1 Sélectionner l'option Transmettre sur %1 Waiting for the device to start the upload process... En attente du matériel pour démarrer le téléchargement... Could not detect any connected oximeter devices. Aucun appareil détecté. Oximeter Import Wizard Assistant d'import pour oxymètre <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Veuillez noter : </span><span style=" font-style:italic;">Sélectionnez d'abord le type d'oxymètre correct dans le menu déroulant ci-dessous.</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">TOUT D'ABORD Sélectionnez votre oxymètre parmi ces groupes :</span></p></body></html> Day recording (normally would have) started L'enregistrement devrait normalement avoir commencé &Save and Finish &Sauvegarder et terminer Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Les oxymètres sont des appareils médicaux pour mesurer la saturation en oxygène du sang. Pendant de longues apnées ou lors de respirations anormales, la saturation du sang en oxygène peut baisser significativement et indiquer un besoin de consultation médicale. Start Time Heure de début Pulse Rate Fréquence des pulsations <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Note : synchroniser avec l'horaire de démarrage de la session de PPC sera toujours plus précis.</p></body></html> Set device date/time Régler la date et l'heure du matériel Import Completed. When did the recording start? Import terminé. Quand l'enregistrement a-t-il commencé ? Import from a datafile saved by another program, like SpO2Review Import d'un fichier de données externes, comme ceux de SpO2Review Multiple Sessions Detected Plusieurs sessions détectées I started this oximeter recording at (or near) the same time as a session on my CPAP machine. J'ai démarré l'oxymètre en même temps que la session de l'appareil à Pression Positive Continue. Record attached to computer overnight (provides plethysomogram) Enregistrement rattaché à l'ordinateur toute la nuit (fournit un pléthysmogramme) Erase session after successful upload Effacer la session après un import réussi &Cancel &Annuler Set device identifier Ajouter l'identifiant de l'appareil Details Détails Oximeter not detected Oxymètre non détecté Please remember: Veuillez retenir que : Where would you like to import from? D'où voulez-vous importer ? If you can read this, you likely have your oximeter type set wrong in preferences. Erreur de type d'oxymètre dans les préférences. I want to use the time my computer recorded for this live oximetry session. Utilisation de l'horaire d'enregistrement de l'ordinateur pour cette session d'oxymétrie temps réel. Scanning for compatible oximeters Recherche d'un oxymètre compatible Please connect your oximeter, enter it's menu and select upload to commence data transfer... Connectez votre oxymètre et sélectionnez envoi de données dans les menus... Choose CPAP session to sync to: Choisir la session PPC avec laquelle synchroniser : Duration Durée Welcome to the Oximeter Import Wizard Bienvenue dans l'assistant d'import d'oxymétrie <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Si cela ne vous gêne pas d'être relié à un ordinateur toute la nuit, cette option fournit un graphique pléthysmogramme, qui indique le rythme cardiaque, en complément des informations d'oxymétrie normales.</p></body></html> "%1", session %2 "%1", session %2 Show Live Graphs Afficher les graphiques en temps réel Live Import Stopped Import temps réel stoppé Oximeter Starting time Heure de démarrage de l'oxymètre Skip this page next time. Ne plus voir cette page. If you can still read this after a few seconds, cancel and try again Si vous pouvez lire ceci après quelques secondes, annulez et recommencez CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 &Sync and Save &Synchroniser et sauvegarder Your oximeter did not have any valid sessions. Votre oxymètre n'a pas de session valide. Something went wrong getting session data Erreur à la récupération des données de cette session Connecting to %1 Oximeter Connexion à l'oxymètre %1 Recording... Enregistrement en cours... ChoiceMMed MD300W1 ChoiceMMed MD300W1 Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Fichiers d'oxymétrie (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Impossible de traiter le fichier : Live Oximetry Mode Mode oxymétrie temps réel Live Oximetry Stopped Oxymétrie temps réel stoppée Live Oximetry import has been stopped Import temps réel de l'oxymétrie stoppé Oximeter Session %1 Session d'oxymétrie %1 If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Si vous voulez synchroniser les données de PPC et d'oxymétrie, surtout importez celles de PPC en premier ! CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. Utilisateurs de CMS50E/F, veuillez attendre qu'OSCAR vous le demande avant de lancer l'import. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Si coché, OSCAR synchronisera automatiquement l'horloge interne de votre CMS50 avec l'ordinateur .</p></body></html> Please choose which one you want to import into OSCAR Merci de sélectionner celle que vous voulez importer <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR a besoin d'un horaire de départ pour savoir où sauver sa session d'oxymétrie.</p><p>Choisissez une des options suivantes :</p></body></html> OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR vous permet de suivre la saturation en oxygène pendant l'utilisation d'une appareil à Pression Positive Continue, ce qui donne une visibilité sur l'efficacité du traitement. Cela fonctionne aussi avec votre oxymètre seul et vous permet de stocker, suivre et revoir les données. For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Afin qu'OSCAR puisse utiliser votre oxymètre, veuillez auparavant installer les logiciels gestionnaires de votre matériel (ex USB vers série), pour plus d'informations à ce sujet, %1 cliquez ici %2. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR est compatible avec les oxymètres Contec CMS50D+,CMS50E/F et CMS50I.<br/>(Note : l'import en bluetooth n'est <span style=" font-wright:600;">probablement pas </span> encore possible) CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>Entrez le nom de 7 caractères de votre oxymètre.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Cette option effacera la session importée de votre oxymètre en fin d'import. </p><p>À utiliser avec précaution, parce que si quelque chose va mal avant qu'OSCAR sauvegarde votre session, vous ne pourrez pas la récupérer</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Cette option vous permet d'importer (via câble) les enregistrements internes de votre oxymètre.</p><p>Après avoir choisi cette option,les anciens oxymètres Contec vous demanderons d'utiliser le menu de l'appareil pour lancer le téléchargement.</p></body></html> Please connect your oximeter device, turn it on, and enter the menu Connectez votre oxymètre, allumez-le et entrez dans le menu Oximetry Date Date Pulse Pulsations &Open .spo/R File &Ouvrir Fic.spo/R R&eset R&emettre à zéro Serial &Import &Importer Série Serial Port Port série d/MM/yy h:mm:ss AP d/MM/yy h:mm:ss AP &Start Live &Début mesures &Rescan Ports &Rescanner les ports PreferencesDialog s s &Ok &Ok AHI Apnea Hypopnea Index IAH RDI Respiratory Disturbance Index IDR bpm bpm Graph Height Hauteur des graphiques Flag Marque Font Polices Name Nom Size Taille Span Envergure %1 %2 %1 %2 &CPAP &PPC General Settings Réglages généraux <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head><body><p>Permet de faire défiler plus facilement avec les touchpads bidirectionnels en mode zoom</p><p>50 ms est une valeur recommandée.</p></body></html> Color Couleur Event Duration Durée d'évènement Hours Heures Label Libellé Lower Plus bas Pulse Pouls Upper Plus haut days. Jours. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Les sessions plus courtes que cela ne seront pas affichées<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Ici vous pouvez indiquer le seuil <b>supérieur</B> utilisé pour les calculs des courbes de %1 Ignore Short Sessions Ignorer les sessions plus courtes que Sleep Stage Waveforms Courbe de période de sommeil Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. % de restriction de flux à partir de la valeur median. Une valeur de 20% est adéquate pour détecter les apnées. Sessions starting before this time will go to the previous calendar day. Les sessions démarrées avant cette heure iront dans le jour précédent. Session Storage Options Options de stockage des sessions Show flags for machine detected events that haven't been identified yet. Afficher les marqueurs d'évènements détectés mais non identifiés. Graph Titles Titres des graphiques Zero Reset Remettre à zéro <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Le marquage personnalisé est une méthode expérimentale de détection des évènements ratés par l'appareil. Ils ne sont <span style=" text-decoration: underline;">pas</span> pris en compte dans l'IAH (Index des apnées et hypopnées).</p></body></html> Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Active/désactive le marquage amélioré expérimental. Permet de détecter des évènements ratés par certaines appareils. Option à activer avant import, sinon une purge est nécessaire. Flow Restriction Restriction de flux Enable Unknown Events Channels Autoriser les canaux d'évènements inconnus l/min l/min <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> Désaturations ci-dessous Minimum duration of drop in oxygen saturation Durée minimum de perte en saturation d'oxygène <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Synchronisation des données d'oxymétrie et de CPAP</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Les données CMS50 importées de SpO2Review à partir de fichiers .spoR ou par la méthode d'importation en série n'</span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;">ont pas l'horodatage correct nécessaire à la synchronisation.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Le mode d'affichage en direct (à l'aide d'un câble série) est un moyen d'obtenir une synchronisation précise sur les oxymètres CMS50, mais ne compense pas la dérive d'horloge CPAP.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Si vous démarrez le mode d'enregistrement de votre oxymètre </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">en même temps que vous démarrez votre appareil CPAP, vous pourrez désormais effectuer la synchronisation.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Le processus d'importation en série prend l'heure de début de la première session CPAP de la nuit précédente. (N'oubliez pas d'importer d'abord vos données CPAP !)</span></p></body></html> Overview Linecharts Type de graphiques Whether to allow changing yAxis scales by double clicking on yAxis labels Autoriser de changer l'axe des y en double-cliquant sur l'intitulé Always Minor Toujours inférieur Unknown Events Évènements inconnus Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Le cache des pixels est une technique d'accélération graphique qui peut poser des soucis à l'affichage des caractères sur votre plateforme. Reset &Defaults Remettre aux valeurs par &défaut Bypass the login screen and load the most recent User Profile Pas de choix d'utilisateur, choisir le plus récent Data Reindex Required Réindexation des données nécessaire Scroll Dampening Défilement adouci Flag leaks over threshold Afficher les fuites supérieures à 20 cmH2O 20 cmH₂O hours Heures Double click to change the descriptive name this channel. Double-cliquez pour changer la description de ce canal. Sessions older than this date will not be imported Les sessions antérieures à cette date ne seront pas importées Standard Bars Barres standard 99% Percentile 99% pour cent Memory and Startup Options Options mémoire et démarrage <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Ne montre pas les boîtes de dialogue non importantes durant l'import.</p></body></html> Small chunks of oximetry data under this amount will be discarded. Les données d'oxymétrie sous cette valeur seront ignorées. Oximeter Waveforms Courbes d'oxymétrie User definable threshold considered large leak Seuil personnalisé des fuites importantes Reset the counter to zero at beginning of each (time) window. RAZ du compteur à chaque début de fenêtre de temps. Compliance defined as Conformité d'observance choisie Here you can change the type of flag shown for this event Ici vous pouvez changer le type de marques affichées pour cet évènement Top Markers Marqueurs hauts minutes minutes Minutes Minutes Create SD Card Backups during Import (Turn this off at your own peril!) Créer des sauvegardes de la carte SD pendant l'importation (désactivation de cette option à vos risques et périls !) Graph Settings Réglages du graphique This is the short-form label to indicate this channel on screen. Libellé court pour ce canal sur l'écran. CPAP Events Évènements PPC Bold Gras Minimum duration of pulse change event. Durée minimum du changement de pulsations. Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. L'anti-aliasing lisse le tracé des graphiques... Certains tracés sont plus agréables avec cette option. Affecte aussi les impressions. À essayer pour voir. Sleep Stage Events Évènements de période de sommeil Events Évènements <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Ce paramètre doit être utilisé avec précaution... </span> Le désactiver aura des conséquences sur la précision des jours avec résumé seulement, car certains calculs ne fonctionneront correctement que si les sessions journalières sont groupées. </p><p><span style=" font-weight:600;"> Utilisateurs ResMed :</span> bien qu'il semble logique qu'un démarrage avant midi compte pour la journée précédente, ça ne signifie pas que nous soyons d'accord avec la métode ResMed. Le format STF.edf, index de synthèse présente des faiblesses graves qui font que ce n'est pas une bonne idée de faire comme cela. </p><p>Cette option existe pour ceux qui ne se soucient pas de voir ceci &quot;corrigé &quot;. Si vous gardez la carte SD dans l'appareil tous les soirs et faites l'importation une fois par semaine, vous ne rencontrerez pas très souvent ce problème.</p></body></html> Median is recommended for ResMed users. Moyenne est recommandé pour les appareils ResMed. Oximeter Events Évènements de l'oxymètre Italic Italique Enable Multithreading Autoriser la parallélisation This may not be a good idea Cela n'est peut-être pas une bonne idée Weighted Average Moyenne pondérée Median Moyenne Flag rapid changes in oximetry stats Indiquer les changements rapides dans les statistiques d'oxymétrie Sudden change in Pulse Rate of at least this amount Changement soudain de fréquence cardiaque d'au moins ce montant Search Rechercher Resync Machine Detected Events (Experimental) Resynchronisation des évènements détectés par l'appareil (expérimental) Time Weighted average of Indice Moyenne pondérée de l'indice Middle Calculations Calcul de la moyenne Skip over Empty Days Ne pas prendre en compte les jours sans mesure Allow duplicates near machine events. Autoriser la duplication des évènements proches. The visual method of displaying waveform overlay flags. Méthode visuelle d'affichage des marques sur les graphiques. Upper Percentile Pourcentage haut Restart Required Redémarrage nécessaire Whether to show the leak redline in the leak graph Afficher la ligne rouge des fuites dans le graphique True Maximum Maximum réel Minor Flag Marque secondaire For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Par soucis de cohérence, les utilisateurs de ResMed devraient utiliser 95% ici pour une meilleure visualisation car c'est la seule valeur disponible dans ce cas. 4 cmH2O 4 cmH₂O Pre-Load all summary data at startup Précharger les données de synthèse au démarrage Graph Text Texte des graphiques Double click to change the default color for this channel plot/flag/data. Double-cliquez pour changer la couleur par défaut des points/marques/données de ce canal. AHI/Hour Graph Time Window Affichage IAH/Heure Import without asking for confirmation Importer sans confirmation Discard segments under Passer les mesures inférieures à Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Autorise la parallélisation pour les processeurs multicœurs pour améliorer les performances. Surtout pour l'import. Line Chart Courbes dd MMMM yyyy dd MMMM yyyy <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Le véritable maximum est le maximum de l'ensemble des données.</p><p> 99% filtre les valeurs aberrantes les plus rares. </p></body></html> Flag Type Type de marqueur Calculate Unintentional Leaks When Not Present Calculer les fuites involontaires si non existant How long you want the tooltips to stay visible. Durée d'affichage des info-bulles. Double click to change the descriptive name the '%1' channel. Double-cliquez pour changer le nom du canal '%1'. Multiple sessions closer together than this value will be kept on the same day. Les sessions plus rapprochées que cette valeur seront sur le même jour. Are you really sure you want to do this? Êtes-vous vraiment sûr de vouloir faire cela ? Duration of airflow restriction Durée de restriction de flux d'air Bar Tops Graphiques à barres This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Ce calcul nécessite que le total des fuites soit fourni par l'appareil à PPC (i.e. PRS1 mais pas ResMed, qui les inclut déjà). Le calcul des fuites involontaires est linéaire, ça concerne les fuites du masque et non la courbe de ventilation. Si vous utilisez des masques différents, utilisez plutôt la valeur moyenne, ce sera assez précis. Session Splitting Settings Réglage du découpage des sessions <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Note : n'est pas destiné aux corrections de fuseau horaire ! Assurez-vous que l'horloge PPC et le fuseau horaire du système d'exploitation sont correctement synchronisés.</p></body></html> Other Visual Settings Autres réglages visuels Day Split Time Heure de séparation des jours CPAP Waveforms Courbes PPC Big Text Grand texte <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <htlm><head/><body><p>Fonctionnalités récemment désactivées. Elles reviendront plus tard</p></body></html> Note: A linear calculation method is used. Changing these values requires a recalculation. Note : une méthode de calcul linéaire est utilisée. Changer ces valeurs nécessite un recalcul. ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Les appareils ResMed S9 effacent régulièrement les données de plus de 7 ou 30 jours de la carte SD (selon la résolution). Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Considérer les jours sous cette valeur comme non conformes. 4 heures est considéré conforme. Do not import sessions older than: Ne pas importer de sessions antérieures au : Daily view navigation buttons will skip over days without data records Le bouton Quotidien passe les jours sans données Flag Pulse Rate Above Marquer les pulsations en dessous de Flag Pulse Rate Below Marquer les pulsations au-dessus de Seconds secondes Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Ajuste le nombre de données pour chaque point du graphique IAH/Heure. 60 min. par défaut. Il est hautement recommandé de le laisser à cette valeur. Show in Event Breakdown Piechart Afficher la répartition des évènements dans le camembert Other oximetry options Autres options d'oxymétrie &Cancel &Annuler Don't Split Summary Days (Warning: read the tooltip!) Ne pas séparer les jours résumés (Attention lire les astuces) Last Checked For Updates: Dernière vérification de disponibilité de mise à jour : Details Détails Use Anti-Aliasing Utiliser l'anti-aliasing Animations && Fancy Stuff Animation et effets &Import &Import <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Ce paramètre conserve les données de forme d'ondes et d'évènements en mémoire pour accélérer l'accès lors des prochaines utilisations.</p><p>Cette option n'est pas vraiment nécessaire, car le système d'exploitation garde en cache les fichiers déjà lus.</p><p>Il est recommandé de le laisser décoché, sauf si votre ordinateur dispose d'une grande quantité de mémoire...</p></body></html> Changes to the following settings needs a restart, but not a recalc. Un changement des réglages ci-dessous nécessitera un redémarrage. &Appearance &Apparence The pixel thickness of line plots Épaisseur de la ligne en pixel Whether this flag has a dedicated overview chart. Graphique d'aperçu général dédié pour cet objet. This is a description of what this channel does. Description de ce que fait le canal. Combine Close Sessions Fermeture combinée de sessions Custom CPAP User Event Flagging Comptage d'évènements personnalisés <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Attention : </span>Réinitialiser les paramètres usine est possible mais ce n'est peut-être pas la bonne méthode</p></body></html> Allow YAxis Scaling Autoriser la mise à l'échelle de l'axe Y Fonts (Application wide settings) Polices (paramètres étendus) Use Pixmap Caching Utiliser le cache des pixels Check for new version every Vérifier les nouvelles versions tous les Waveforms Ondes Maximum Calcs Calculs maximum Overview Aperçus Tooltip Timeout Durée d'affichage des info-bulles Preferences Préférences General CPAP and Related Settings Réglages généraux de PPC Default display height of graphs in pixels Hauteur d'affichage par défaut des graphiques (pixels) Overlay Flags Marques de dépassement Makes certain plots look more "square waved". Rendre certains tracés plus "carrés". Percentage drop in oxygen saturation % perdus lors de la saturation d'oxygène &General &Général Standard average of indice Moyenne simple de l'indice Compress SD Card Backups (slower first import, but makes backups smaller) Compression de la sauvegarde de la carte SD Keep Waveform/Event data in memory Garder les ondes/évènements en mémoire Whether a breakdown of this waveform displays in overview. Affiche la ventilation de cette forme d'onde dans l'aperçu. Normal Average Moyenne simple Positional Waveforms Courbe de position A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Il faudra réindexer les données ce qui prendra quelques minutes. Êtes-vous sûr de vouloir le faire ? Positional Events Évènements de position Preferred Calculation Methods Choix de la méthode de calcul Combined Count divided by Total Hours Nombre combiné divisé par nombre d'heures Graph Tooltips Info-bulles du graphique &Oximetry &Oxymétrie CPAP Clock Drift Décalage d'horloge de PPC Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Ici vous pouvez indiquer le seuil <b>inférieur</B> utilisé pour les calculs des courbes de %1 Square Wave Plots Points carrés TextLabel Libellé Preferred major event index Évènement majeur préféré Application Application Line Thickness Épaisseur des lignes Auto-Launch CPAP Importer after opening profile Lancement automatique de l'import après ouverture du profil utilisateur Automatically load last used profile on start-up Ouverture automatique du dernier profil utilisé au lancement <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note : </span>Les appareils ResMEd ne prennent pas en compte ces réglages du fait de leur conception.</p></body></html> Oximetry Settings Réglages de l'oxymétrie I want to be notified of test versions. (Advanced users only please.) Je veux être averti lors de la sortie d'une version de test (Seulement pour les utilisateurs confirmés) On Opening À l'ouverture Profile Profil Welcome Bienvenue Daily Quotidien Statistics Statistiques Switch Tabs Changer d'onglet No change Pas de changement After Import Après import Never Jamais Data Processing Required Traitement des données nécessaire A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Il faudra re/décompresser les données, ce qui prendra quelques minutes Êtes-vous sûr de vouloir le faire ? Graphics Engine (Requires Restart) Moteur graphique (nécessite un redémarrage) One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Ces modifications nécessitent un redémarrage de l'application pour être effectives. Voulez-vous le faire maintenant ? Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Compresse la sauvegarde des appareils ResMed (fichiers EDF en .gz). Format courant sous Linux et Mac. OSCAR peut importer de ce répertoire de sauvegarde compressé en mode natif. Pour l'utiliser avec ResScan, il faudra d'abord décompresser les fichiers *.gz.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Options affectant l'espace disque utilisé et la durée de l'import. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Réduit de moitié l'occupation des données. Mais prendra plus de temps pour l'import et les modifications. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Compresser les données de sessions This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Garde un copie de la carte SD des appareils ResMed. Les appareils ResMed effacent les données précises après 7 jours, et les graphiques de plus de 30 jours... OSCAR peut garder ces données au cas vous devriez réinstaller (Hautement recommandé, à moins que vous n'ayez pas de place disque ou que les graphiques ne vous intéressent pas) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Rend le démarrage d'OSCAR un peu plus lent, en préchargeant toutes les informations de synthèse. En contrepartie, la navigation et les calculs seront améliorés. </p><p>Si vous avez beaucoup de données vous pouvez ignorer cette option, car si vous voulez voir <span style=" font-style:italic;">tous les aperçus</span>, il faudra tout charger quand même. </p><p>N'affecte pas les données d'évènements qui sont chargées à la demande.</p></body></html> <html><head/><body><p>Cumulative Indices</p></body></html> Indices cumulés This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Cette option expérimentale tente d'utiliser le système de repérage d'OSCAR pour améliorer la détection d'évènements de position. Show Remove Card reminder notification on OSCAR shutdown Rappel de retrait de la carte SD à l'arrêt d'OSCAR <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Choix de l'onglet à ouvrir au chargement d'un profil. (Note : l'onglet Profil sera affiché automatiquement si OSCAR est réglé pour ne pas ouvrir de profil au lancement)</p></body></html> Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Si vous rencontrez des problèmes d'affichage des graphiques, essayez de changer le réglage par défaut (Desktop OpenGL). <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Note :</b> Le découpage de session n'est pas possible avec les appareils <b>ResMed</b> du fait de la façon dont ils sauvegardent les données et sera de ce fait désactivé.</p><p>Sur les appareils ResMed, les jours <b> débutent à midi </b> comme dans leur logiciel commercial ResScan.</p> If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Si vous avez besoin un jour de réimporter ces données (dans OSCAR ou ResScan) ces données auront disparu. If you need to conserve disk space, please remember to carry out manual backups. Si vous avez besoin de conserver de l'espace disque, n'oubliez pas de faire des sauvegardes manuelles. Are you sure you want to disable these backups? Êtes-vous sûr de vouloir désactiver ces sauvegardes ? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Désactiver les sauvegardes automatiques n'est pas une bonne idée, car OSCAR en aura besoin pour reconstruire sa base de données si des erreurs apparaissent. Changing SD Backup compression options doesn't automatically recompress backup data. Changer les options de compression de la sauvegarde sur carte SD ne recompresse pas les données automatiquement. Your masks vent rate at 20 cmH2O pressure Ventilation du masque à 20 cmH₂O de pression Your masks vent rate at 4 cmH2O pressure Ventilation du masque à 4 cmH₂O de pression Whether to include machine serial number on machine settings changes report Inclure ou non le n° de série de l'appareil sur le rapport des changements de réglage de l'appareil Include Serial Number Inclure le numéro de série <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Alerter lors de l’importation de données de tout modèle d'appareil qui n’a pas encore été testé par les développeurs d'OSCAR.</p></body></html> Warn when importing data from an untested machine Avertir lors de l’importation de données à partir d’un appareil non testé <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>Alerter lors de l’importation de données différentes de tout ce qui a déjà été vu par les développeurs d'OSCAR.</p></body></html> Warn when previously unseen data is encountered Avertir lorsque des données inédites sont rencontrées Always save screenshots in the OSCAR Data folder Enregistrez toujours les captures d’écran dans le dossier de données d'OSCAR No CPAP machines detected Pas d'appareil PPC détecté Will you be using a ResMed brand machine? Utiliserez-vous un appareil Resmed ? Check For Updates Vérification de disponibilté de mise à jour You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Vous utilisez une version de test d'OSCAR. Les versions de test vérifient la disponibilité de mise à jour au moins une fois par semaine. Vous pouvez réduire cet intervalle. Automatically check for updates Rechercher automatiquement les mises à jour How often OSCAR should check for updates. Intervalle de recherche de mise à jour. If you are interested in helping test new features and bugfixes early, click here. Si tester les nouvelles fonctionnalités et corrections vous intéresse, cliquer ici. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Si vous voulez aider à tester les préversions d'OSCAR, veuillez consulter le wiki sur le sujet. Nous acceptons toutes les bonnes volontés : testeurs, développeurs, traducteurs que ce soit dans une langue déjà disponible ou dans une nouvelle langue. https://www.sleepfiles.com/OSCAR Print reports in black and white, which can be more legible on non-color printers Impression de rapports en noir et blanc (plus lisible sur les imprimantes monochrome) Print reports in black and white (monochrome) Impression des rapports en noir et blanc (monochrome) ProfileSelector Filter: Filtre : Version Version &Open Profile &Ouverture du profil &Edit Profile &Modifier le profil &New Profile &Nouveau profil utilisateur Profile: None Profil : aucun Please select or create a profile... Sélectionnez ou créez le profil utilisateur... Destroy Profile Effacer le profil Profile Profil Ventilator Brand Marque du ventilateur Ventilator Model Modèle du ventilateur Other Data Autres données Last Imported Dernier import Name Nom Enter Password for %1 Mot de passe pour %1 You entered an incorrect password Mot de passe incorrect Forgot your password? Mot de passe oublié ? Ask on the forums how to reset it, it's actually pretty easy. Demandez sur les forums comment le réinitialiser. C'est facile. Select a profile first Sélectionnez d'abord un profil If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Si vous tentez de supprimer parce que vous avez oublié le mot de passe, vous devez l'effacer à la main. You are about to destroy profile '<b>%1</b>'. Vous allez détruire le profil '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Attention cela va définitivement effacer le profil et toutes les <b>données de sauvegarde<b> présentes dans <br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Entrez le mot DELETE ci-dessous pour confirmer. DELETE DELETE Sorry Désolé You need to enter DELETE in capital letters. Vous devez saisir DELETE en lettres majuscules. There was an error deleting the profile directory, you need to manually remove it. Erreur lors de l'effacement du répertoire du profil. Vous devez l'effacer à la main. Profile '%1' was succesfully deleted Profil '%1' effacé avec succès Hide disk usage information Cacher les informations d'utilisation disque Show disk usage information Afficher les informations d'utilisation disque Name: %1, %2 Nom : %1, %2 Phone: %1 Téléphone : %1 Email: <a href='mailto:%1'>%1</a> Email : <a href='mailto:%1'>%1</a> Address: Adresse : No profile information given Pas d'information de profil Profile: %1 Profil : %1 Bytes Octets KB Ko MB Mo GB Go TB To PB Po Summaries: Résumés : Events: Évènements : Backups: Sauvegardes : You must create a profile Vous devez créer un profil Reset filter to see all profiles Réinitialise le filtre pour voir tous les profils The selected profile does not appear to contain any data and cannot be removed by OSCAR Le profil sélectionné semble ne pas contenir de données et ne peut pas être supprimé par OSCAR ProgressDialog Abort Annuler QObject ? ? Only Settings and Compliance Data Available Les seules données disponibles sont les paramètres et l'observance Summary Data Only Seul le résumé A A H H P P AI IA CA AC EP EP FL FL HI HI IE IE Hz Hz LE LE LF LF LL LL Kg Kg O2 O₂ OA AO NR NR PB PB PC PC No Non PP PP PS PS On RE RE S9 S9 SA SA SD SD TB TB UA NC VS VS ft ft lb lb ml ml oz oz &No &Non AHI IAH ASV ASV BMI IMC CAI IAC Apr Avr CSR RCS Aug Aoû Avg Moy. DOB DdN EPI EPI EPR Expiration Plus Relax EPR Dec Déc FLI FLI End Fin Feb Fév Jan Jan Jul Jui Jun Jun NRI NRI Mar Mar Max maxi May Mai Med moy Min mini Nov Nov Oct Oct Off Désactivé RDI IDR REI REI UAI INC in en UF1 UF1 UF2 UF2 UF3 UF3 Sep Sep VS2 VS2 Yes Oui Zeo Zeo bpm bpm 99.5% 99.5% varies varie EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1-%2 IPAP %3-%4 (%5) &Yes &Oui 15mm 15 mm 22mm 22 mm APAP APAP CPAP PPC Auto Auto Busy Occupé Min EPAP EPAP mini EPAP EPAP Date Date ICON ICON Min IPAP IPAP mini IPAP IPAP Last Dernier Leak Fuite Mask Masque Med. Moy. Mode Mode Name Nom None Aucun RERA effort expiratoire éveillant RERA Ramp Rampe Zero Zéro Resp. Event Évènement respiratoire Inclination Inclinaison Launching Windows Explorer failed Échec au lancement de Windows Explorer Hose Diameter Diamètre du tuyau &Save &Sauvegarder AVAPS AVAPS CMS50 CMS50 Therapy Pressure Pression de la thérapie BiPAP BiPAP Brand Marque EPR: EPR : Daily Quotidien Email Courriel Error Erreur First Premier Ramp Pressure Pression de la rampe L/min l/min Hours Durée MD300 MD300 Leaks Fuites Max: Maxi : Min: Mini : Model Modèle Nasal Nasal Phone Téléphone Ready Prêt TTIA: TTIA : W-Avg moy. ponderée Snore Ronflement Start Début Usage Utilisation Respiratory Disturbance Index Index des troubles respiratoires cmH2O cmH₂O Pressure Support Pression supportée Bedtime: %1 Coucher : %1 Hypopnea Hypopnées (H) ratio ratio Tidal Volume Volume courant AI=%1 IA=%1 Entire Day Jour entier Intellipap pressure relief mode. Mode dépression Intellipap. Personal Sleep Coach Coach personnel de sommeil Clear Airway Apnées centrales (AC) Respironics Respironics Heart rate in beats per minute Pouls en battements par minute A large mask leak affecting machine performance. Grosses fuites affectant les performances de l'appareil. Somnopose Software Logiciel Somnopose Temp. Enable Temp. activée Timed Breath Respiration chronométrée Mask On Time Durée d'utilisation du masque It is likely that doing this will cause data corruption, are you sure you want to do this? Il y a un fort risque de perte de données, êtes-vous sûr de vouloir continuer ? Pat. Trig. Breath Resp. activée par le patient (Summary Only) (Résumé seulement) Ramp Delay Period Durée de la rampe Sessions Switched Off Sessions désactivées This folder currently resides at the following location: Emplacement actuel de ce répertoire : Pulse Change Changement de pulsations Disconnected Déconnecté Sleep Stage Phase du sommeil Minute Vent. Vent. minute SpO2 Drop Baisse de SpO₂ Ramp Event Évènement de rampe SensAwake feature will reduce pressure when waking is detected. La fonctionnalitié SensAwake réduira la pression quand l'éveil est détecté. Show All Events Afficher les évènements CMS50E/F CMS50E/F Upright angle in degrees Position assise à inclinée en degrés Higher Expiratory Pressure Pression d'expiration maxi NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 M-Series M-Series A vibratory snore Ronflement vibratoire Vibratory Snore Ronflement vibratoire Lower Inspiratory Pressure Pression d'inspiration la plus basse Humidifier Enabled Status État de l'humidificateur activé Full Face Facial Backing up files... Sauvegarde des fichiers ... Reading data files... Lecture des fichiers de données ... Full Time Temps complet SmartFlex Level Niveau de SmartFlex Snoring event. Ronflement SN SN Journal Data Données du journal (%1% compliant, defined as > %2 hours) (%1% conforme, définie comme > %2 heures) Resp. Rate Taux de respiration Insp. Time Durée inspiration Exp. Time Durée expiration ClimateLine Temperature Température ClimateLine Philips Respironics Philips Respironics Your %1 %2 (%3) generated data that OSCAR has never seen before. OSCAR ne connait pas votre %1 %2 (%3). The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Vos données importées semblent incorrectes, les developpeurs aurait besoin d'une copie de la carte zip de votre machine afin de valider les rapports. Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Votre machine %1 (Modèle %2) n'est malheureusement pas compatible. Your %1 CPAP machine (Model %2) has not been tested yet. Votre machine PPC %1 (Modèle %2) n'a pas encore été testée. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Cela semble assez similaire à d'autres machines et devrait fonctionner, mais les développeurs aimeraient une copie .zip de la carte SD de cette machine afin de valider la génération des rapports. Machine Unsupported Appareil non supporté Sorry, your %1 CPAP machine (%2) is not supported yet. Votre machine PPC %1 (Modèle %2) n'est,pour le moment, pas supportée. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Les developpeurs aurient besoin d'une copie de la carte zip et des relevés cliniques afin d'adapter OSCAR. SOMNOsoft2 SOMNOsoft2 A relative assessment of the pulse strength at the monitoring site Évaluation relative de la force d'impulsion au niveau du site de surveillance Machine Appareil Mask On Avec masque Max: %1 Maxi : %1 A sudden (user definable) drop in blood oxygen saturation Baisse soudaine d'oxygénation du sang (définissable par l'utilisateur) There are no graphs visible to print Pas de graphique à imprimer Target Vent. Vent. cible Sleep position in degrees Position du sommeil en degrés Plots Disabled Points désactivés Min: %1 Mini : %1 Minutes Minutes Periodic Breathing Respiration périodique Ramp Only Rampe seulement Ramp Time Durée de la rampe PRS1 pressure relief mode. Mode de dépression PRS1. An abnormal period of Periodic Breathing Période anormale de respiration périodique ResMed Mask Setting Réglage du masque ResMed ResMed Exhale Pressure Relief Dépression d'expiration ResMed A-Flex A-Flex EPR Level Expiration Plus Relax Niveau de l'EPR Unintentional Leaks Fuites involontaires Would you like to show bookmarked areas in this report? Voulez-vous afficher les zones favorites dans ce rapport ? Somnopose Somnopose AHI %1 IAH : %1 C-Flex C-Flex VPAPauto VPAP Auto Apnea Hypopnea Index Index Apnées Hypopnées Pt. Access Accès patient CMS50F CMS50F ASV (Fixed EPAP) ASV (EPAP fixe) Patient Triggered Breaths Respirations activées par le patient This means you will need to import this machine data again afterwards from your own backups or data card. Ce qui signifie que vous allez devoir réimporter les données à partir de vos propres sauvegardes. Contec Contec Events Évènements Humid. Level Niv. humidité AB Filter Filtre AB Ramp Enable Rampe active (% %1 in events) (% %1 en évènements) Lower Threshold Seuil le plus bas No Data Pas de données Page %1 of %2 Page %1 sur %2 Litres Litres Manual Manuel Median Moyenne Fixed %1 (%2) Fixé %1 (%2) Min %1 mini %1 Could not find explorer.exe in path to launch Windows Explorer. Windows Explorer n'a pas été trouvé dans le chemin indiqué. Machine automatically switches off Arrêt automatique de l'appareil Connected Connecté Low Usage Days: %1 Jours de faible usage : %1 PS Max PS maxi PS Min PS mini Database Outdated Please Rebuild CPAP Data Base de données périmée Merci de reconstruire les données de PPC Flow Limit. Limitation du flux Detected mask leakage including natural Mask leakages Fuites détectées incluant les fuites naturelles du masque Plethy Pléthy SensAwake SensAwake ST/ASV ST/ASV Median Leaks Fuites moyennes %1 Report Rapport %1 ResMed ResMed Pr. Relief Dépression Graphs Switched Off Graphiques désactivés Serial Numéro de série Series Séries (last night) (la nuit dernière) AHI %1 IAH %1 Weight Poids PRS1 pressure relief setting. Réglage de dépression PRS1. Orientation Orientation Smart Start SmartStart Event Flags Évènements A few breaths automatically starts machine Mise en marche par respiration Zombie Zombie C-Flex+ C-Flex+ Bookmarks Favoris PAP Mode Mode PAP CPAP Mode Mode PPC SmartFlex Settings Réglages SmartFlex An apnea where the airway is open Apnée avec passage de l'air ouvert Flow Limitation Limitation du débit Pin %1 Graph Attacher le graphique %1 Unpin %1 Graph Détacher le graphique %1 Hours: %1h, %2m, %3s Heures : %1h, %2m, %3s %1 Length: %3 Start: %2 %1 Longueur : %3 Début : %2 CMS50F3.7 CMS50F3.7 RDI %1 IRD %1 ASVAuto ASV Auto PS %1 over %2-%3 (%4) PS %1 sur %2-%3 (%4) Flow Rate Débit Time taken to breathe out Temps pris pour expirer Important: Important : Machine auto starts by breathing Démarrage auto par respiration An optical Photo-plethysomogram showing heart rhythm Photopléthysmogramme optique montrant le rythme cardiaque Pillows Coussinets %1 Length: %3 Start: %2 %1 Longueur : %3 Début : %2 I:E Ratio Ratio Inspiration/Expiration Amount of air displaced per breath Quantité de l'air déplacé par respiration Pat. Trig. Breaths Resp. activées par le patient % in %1 % en %1 Humidity Level Niveau humidité Address Adresse Leak Flag Marqueur de fuite Leak Rate Vitesse de fuite ClimateLine Temperature Enable Température ClimateLine active Severity (0-1) Gravité (0-1) Reporting from %1 to %2 Rapport du %1 au %2 Are you sure you want to reset all your channel colors and settings to defaults? Voulez-vous vraiment réinitialiser les préférences des graphiques (couleur des canaux et réglages) aux valeurs par défaut ? Inspiratory Pressure Pression d'inspiration Whether or not machine allows Mask checking. Selon que l'appareil permet la vérification du masque. A pulse of pressure 'pinged' to detect a closed airway. Impulsion de pression envoyée pour détecter une obstruction. Intellipap pressure relief level. Niveau de dépression Intellipap. CMS50D+ CMS50D+ Non Responding Event Évènement ne répondant pas Median Leak Rate Volume moyen de fuite (%3 sec) (%3 sec) Rate of breaths per minute Respirations par minute Usage Statistics Statistiques d'utilisation Perfusion Index Index de perfusion Graph displaying snore volume Niveau du ronflement Mask Off Sans masque Max EPAP EPAP maxi Max IPAP IPAP maxi Bedtime Heure du coucher Bi-Flex Bi-Flex EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) Pressure Pression Auto On Auto On Average Moyenne Target Minute Ventilation Ventilation cible par minute Amount of air displaced per minute Quantité de l'air déplacé par minute TTIA: %1 TTIA : %1 Percentage of breaths triggered by patient Pourcentage de respirations activées par le patient Non Data Capable Machine Appareil sans données Days: %1 Jours : %1 Plethysomogram Pléthysmogramme Unclassified Apnea Apnées non classifiées (NC) Auto Bi-Level (Fixed PS) Auto Bi-Level (PS fixe) Please Note Merci de noter que Starting Ramp Pressure Pression de départ de la rampe Apnea Apnée An apnea reportred by your CPAP machine. Apnée signalée par votre appareil CPAP. Intellipap event where you breathe out your mouth. Évènement Intellipap d'expiration par la bouche. ASV (Variable EPAP) ASV (EPAP variable) Exhale Pressure Relief Level Niveau de dépression d'expiration Flow Limit Limitation du flux UAI=%1 INC = %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 faible utilisation, %2 pas d'utilisation, sur %3 jours (%4% compatible) Durée : %5 / %6 / %7 Information Information Pulse Rate Pouls Rise Time Montée temporisée Cheyne Stokes Respiration Respiration de Cheyne-Stokes SmartStart SmartStart Graph showing running AHI for the past hour IAH pour les heures précédentes Graph showing running RDI for the past hour Graphique des troubles respiratoires pour les dernières heures Temperature Enable Niv. Temp. activé Seconds secondes %1 (%2 days): %1 (%2 jours): Snapshot %1 Copie écran %1 Mask Time Utilisation du masque Channel Canal Auto for Her Auto pour Elle Obstructive Apnées obstructives (AO) Pressure Max Pression maxi Pressure Min Pression mini Diameter of primary CPAP hose Diamètre du tuyau principal de PPC Max Leaks Fuites maxi Flex Level Niveau Flex Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Respiratory Effort Related Arousal (RERA) : gêne respiratoire qui cause un réveil ou un trouble du sommeil. Humid. Status État humidif. (Sess: %1) (Sess : %1) Climate Control Climate Control est un système intelligent qui contrôle l'humidificateur et le circuit respiratoire ResMed Climate Control Perf. Index % % Index de perfusion If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Si vos anciennes données manquent, copier le contenu de tous les autres répertoires de journaux_XXXX manuellement dans celui-ci. &Cancel &Annuler Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP mini %1 IPAP maxi %2 PS %3-%4 (%5) System One System One Default Par défaut Breaths/min Resp./min Degrees Degrés &Destroy &Détruire There is a lockfile already present for this profile '%1', claimed on '%2'. Il y a un fichier de verrouillage déjà présent pour ce profil '%1', demandé sur '%2'. User Flag #1 Évènement utilisateur #1 User Flag #2 Évènement utilisateur #2 User Flag #3 Évènement utilisateur #3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/RCS=%4%% Median rate of detected mask leakage Volume moyen des fuites du masque PAP Device Mode Type matériel PAP Mask Pressure Pression du masque A vibratory snore as detcted by a System One machine Ronflement vibratoire détecté par un appareil SystemOne Respiratory Event Évènement respiratoire An abnormal period of Cheyne Stokes Respiration Période anormale de respiration périodique/Cheyne-Stokes A type of respiratory event that won't respond to a pressure increase. Évènement respiratoire ne répondant pas à l'augmentation de la pression. Antibacterial Filter Filtre antibactérien Windows User Utilisateur Windows Question Question Waketime: %1 Réveil : %1 Higher Inspiratory Pressure Pression d'inspiration mini Weinmann Weinmann Don't forget to place your datacard back in your CPAP machine N'oubliez pas de remettre la carte SD dans votre appareil The machine data folder needs to be removed manually. Le répertroire de données de l'appareil doit être effacé manuellement. Summary Only Résumé seulement Bi-Level Bi-Level Intellipap Intellipap <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Vos anciennes données seront restaurées si la sauvegarde n'a pas été désactivée dans les préférences d'import des données</i> Unknown Inconnue Events/hr Évènements/Heure PRS1 humidifier connected? Humidificateur PRS1 connecté ? CPAP Session contains summary data only Session PPC avec résumé seulement Fisher & Paykel Fisher & Paykel Duration Durée Hours: %1 Heures : %1 Flex Mode Mode Flex Sessions Sessions Auto Off Auto Off Settings Réglages Overview Aperçus Temperature Température Entire Day's Flow Waveform Flux du jour entier Exiting Arrêt en cours DeVilbiss DeVilbiss Fixed Bi-Level Bi-Level fixe Machine Information Informations de l'appareil Pressure Support Maximum Pression maximum supportée Graph showing severity of flow limitations Limitation de flux : %1 hours, %2 minutes, %3 seconds : %1 heure(s); %2 minute(s), %3 seconde(s) Auto Bi-Level (Variable PS) Bi-Level Auto (Pres. variable) Mask Alert Alerte du masque A partially obstructed airway Passage de l'air partiellement obstrué Pressure Support Minimum Pression minimum supportée Large Leak Grosses fuites (LL) Time started according to str.edf Heure de départ selon str.edf Wake-up Réveil Warning Alerte Min Pressure Pression mini Total Leak Rate Total des fuites Max Pressure Pression maxi MaskPressure Pression du masque Duration %1:%2:%3 Durée %1:%2:%3 Upper Threshold Seuil le plus haut Total Leaks Total fuites Minute Ventilation Ventilation par minute Rate of detected mask leakage Vitesse de fuite du masque Breathing flow rate waveform Courbe du flux respiratoire Lower Expiratory Pressure Pression d'expiration la plus basse Min %1 Max %2 (%3) Mini %1 Maxi %2 (%3) AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 Time taken to breathe in Temps pris pour inspirer Maximum Therapy Pressure Pression de thérapie maximum Are you sure you want to use this folder? Êtes-vous sûr de vouloir utiliser ce répertoire ? %1 (%2 day): %1 (%2 jour) : Current Selection Sélection courante Blood-oxygen saturation percentage % de saturation du sang en oxygène Inspiratory Time Temps d'inspiration Respiratory Rate Vitesse respiratoire Hide All Events Cacher les évènements Printing %1 Report Impression du rapport %1 Expiratory Time Temps d'expiration Expiratory Puff Bouffée expiratoire Maximum Leak Fuite maximum Ratio between Inspiratory and Expiratory time Ratio entre temps d'inspiration et d'expiration APAP (Variable) APAP (Variable) Minimum Therapy Pressure Pression minimum de thérapie A sudden (user definable) change in heart rate Changement soudain de pouls (définissable par l'utilisateur) Oximetry Oxymétrie Oximeter Oxymètre No Data Available Aucune donnée disponible The maximum rate of mask leakage Vitesse maximum de fuite du masque Humidifier Status État de l'humidificateur Machine Initiated Breath Respiration provoquée par l'appareil Machine Database Changes La base de données de l'appareil a changé SmartFlex Mode Mode SmartFlex (%2 min, %3 sec) (%2 min, %3 sec) Expiratory Pressure Pression d'expiration Show AHI Afficher l'IAH Tgt. Min. Vent Vent. act. min Rebuilding from %1 Backup Reconstruction de la sauvegarde de %1 Are you sure you want to reset all your waveform channel colors and settings to defaults? Êtes-vous sûr de vouloir réinitialiser tous vos réglages ? Pressure Pulse Impulsion de pression ChoiceMMed ChoiceMMed Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Sessions : %1/ %2 / %3 Longueur :%4 / %5 / %6 Plus long : %7 / %8 / %9 Humidifier Humidificateur Relief: %1 Réduction : %1 Patient ID Identifiant du patient Patient??? Patient ??? An apnea caused by airway obstruction Apnée causée par une obstruction du passage de l'air Vibratory Snore (VS2) Ronflement vibratoire (VS2) h h m m s s ms ms Height Taille Physical Height Taille Notes Notes Bookmark Notes Favoris Body Mass Index Indice de masse corporelle How you feel (0 = like crap, 10 = unstoppable) Comment vous sentez-vous (0=un vrai débri; 10=inarrêtable) Bookmark Start Début des Favoris Bookmark End Fin des Favoris Last Updated Dernière mise à jour Journal Notes Journal Journal Journal 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=Éveil 2=Endormissement 3=Sommeil léger 4=Sommeil lourd Brain Wave Ondes cérébrales BrainWave Ondes cérébrales Awakenings Réveil en cours Number of Awakenings Nombre d'éveils Morning Feel Sensations du matin How you felt in the morning Comment vous sentiez-vous le matin Time Awake Durée d'éveil Time spent awake Durée passée en éveil Time In REM Sleep Durée en endormissement Time spent in REM Sleep Durée passée en endormissement Time in REM Sleep Durée en endormissement Time In Light Sleep Durée en sommeil léger Time spent in light sleep Durée passée en sommeil léger Time in Light Sleep Durée en sommeil léger Time In Deep Sleep Durée en sommeil profond Time spent in deep sleep Durée passée en sommeil profond Time in Deep Sleep Durée en sommeil profond Time to Sleep Durée d'endormissement Time taken to get to sleep Durée d'endormissement Zeo ZQ Zeo ZQ Zeo sleep quality measurement Mesure de qualité d'endormissement ZEO ZEO ZQ ZEO ZQ Pop out Graph Graphique Your machine doesn't record data to graph in Daily View L'appareil n'enregistre acune donnée qui puisse générer la vue quotitienne Popout %1 Graph Graphique %1 m m cm cm Profile Profil Getting Ready... Préparation... Scanning Files... Lecture des fichiers... Importing Sessions... Import des sessions... Finishing up... Finalisation... Breathing Not Detected Respiration non détectée A period during a session where the machine could not detect flow. Période pendant une session sans flux détectée. BND BND iVAPS iVAPS Soft Doux Standard Standard SmartStop Smart Stop Machine auto stops by breathing Arrêt auto par respiration Smart Stop Smart Stop Response Réponse Patient View Vue patient Simple Simple Advanced Avancé BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T PAC PAC Your ResMed CPAP machine (Model %1) has not been tested yet. Votre machine PPC Resmed (Modèle %1) n'a pas encore été testée. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Cela semble assez similaire à d'autres machines et devrait fonctionner, mais les développeurs aimeraient une copie .zip de la carte SD de cette machine pour s'assurer qu'elle fonctionne avec OSCAR. Locating STR.edf File(s)... Localisation des fichiers STR.edf... Cataloguing EDF Files... Catalogage des fichiers EDF... Queueing Import Tasks... Mise en liste de tâche d'import... Finishing Up... Finalisation... Loading %1 data for %2... Chargement des données %1 pour %2... Scanning Files Lecture des fichiers Migrating Summary File Location Déplacement de fichiers récapitulatifs Loading Summaries.xml.gz Chargement de Summaries.xml.gz Loading Summary Data Chargement des données Please Wait... Veuillez patienter... Loading profile "%1"... Chargement du profil "%1"... Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Données d'oxymétrie les plus récentes : <a onclick='alert("daily=%2");'>%1</a> No oximetry data has been imported yet. Pas de données d'oxymétrie importées pour le moment. Software Engine Moteur logiciel ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Desktop OpenGL There is no data to graph Pas de données pour les graphiques OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR a trouvé un vieux répertoire du journal, mais il semble avoir été renommé : OSCAR will not touch this folder, and will create a new one instead. OSCAR ne va pas toucher à ce répertoire et va en créer un nouveau. Please be careful when playing in OSCAR's profile folders :-P Merci d'être prudent en jouant avec les répertoires de profils :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. OSCAR n'a pas trouvé d'enregistrement de journal dans votre profil, mais plusieurs répertoires de journaux. OSCAR picked only the first one of these, and will use it in future: OSCAR n'a sélectionné que le premier et continuera de l'utiliser : I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Désolé, OSCAR ne peut suivre que les heures d'utilisation et quelques informations de base pour cet appareil. <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR garde une copie de la carte qu'il utilise dans cette optique</b> OSCAR does not yet have any automatic card backups stored for this device. OSCAR ne fait pas de sauvegarde automatique de la carte SD pour ce matériel. If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Si cela vous pose un souci, cliquez sur Non pour sortir, sauvegardez le profil manuellement avant de relancer OSCAR. Are you ready to upgrade, so you can run the new version of OSCAR? Êtes-vous prêt pour mettre à jour afin d'utiliser la nouvelle version d'OSCAR ? Sorry, the purge operation failed, which means this version of OSCAR can't start. Désolé la purge à échoué. Cette version d'OSCAR ne peut démarrer. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Voulez-vous passer en sauvegarde automatique, ainsi la prochaine fois qu'une nouvelle version d'OSCAR doit le faire, elle pourra s'en servir ? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR va lancer l'assistant d'import pour réinstaller les données de votre %1. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: OSCAR va quitter, lancez ensuite votre gestionnaire de fichiers pour faire une copie de votre profil : Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Utilisez votre gestionnaire de fichiers pour faire une copie de votre répertoire du profil puis relancez OSCAR pour terminer la mise à jour. A user definable event detected by OSCAR's flow waveform processor. Évènement définissable par l'utilisateur détecté par le moteur d'analyse de flux d'OSCAR. As you did not select a data folder, OSCAR will exit. Comme vous n'avez pas sélectionné de répertoire OSCAR va quitter. The folder you chose is not empty, nor does it already contain valid OSCAR data. Le répertoire n'est pas vide et ne contient pas de données d'OSCAR valides. OSCAR Reminder Rappel d'OSCAR You can only work with one instance of an individual OSCAR profile at a time. Vous ne pouvez travailler qu'avec un seul profil OSCAR à la fois. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Si vous utilisez un stockage en cloud, assurez-vous que OSCAR est fermé et que la synchronisation est terminée sur l'autre ordinateur avant de poursuivre. You must run the OSCAR Migration Tool Vous devez utiliser l'outil de migration vers OSCAR Using Utilisation , found SleepyHead - , SleepyHead trouvé - An apnea that couldn't be determined as Central or Obstructive. Apnée ne pouvant être détérminée comme Centrale ou Obstructive. A restriction in breathing from normal, causing a flattening of the flow waveform. Restriction respiratoire anormale causant un applatissement de l'onde de flux. or CANCEL to skip migration. ou choisir ANNULER pour ne pas migrer. You cannot use this folder: Vous ne pouvez pas utiliser ce répertoire : Choose or create a new folder for OSCAR data Choisissez ou créez un nouveau répertoire pour les données d'OSCAR App key: Clef de l'application : Operating system: Système d'exploitation : Graphics Engine: Moteur graphique : Graphics Engine type: Type de moteur graphique : Machine Untested Appareil non testé Migrating En cours de migration files fichiers from de to à OSCAR will set up a folder for your data. OSCAR va préparer un répertoire pour vos données. We suggest you use this folder: Nous vous suggérons d'utiliser ce répertoire : Click Ok to accept this, or No if you want to use a different folder. Cliquez sur Oui pour accepter ou Non pour utiliser un autre répertoire. Next time you run OSCAR, you will be asked again. La prochaine fois qu'OSCAR sera lancé, cette question sera posée. Data directory: Répertoire : Updating Statistics cache Mise à jour du cache des statistiques d MMM yyyy [ %1 - %2 ] d MMM yyyy [ %1 - %2 ] EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) Pressure Set Ajustement de la pression Pressure Setting Réglage de pression IPAP Set Réglages IPAP IPAP Setting Réglages IPAP EPAP Set configuration EPAP EPAP Setting Réglages EPAP %1 Charts Graphique %1 %1 of %2 Charts Graphique %1 de %2 Loading summaries Chargement des résumés Built with Qt %1 on %2 Construit avec Qt %1 le %2 Motion Mouvement n/a n/a Dreem Dreem Untested Data Données non testées P-Flex P-Flex Humidification Mode Mode d'humidification PRS1 Humidification Mode Mode d'humidification PRS1 Humid. Mode Mode humid. Fixed (Classic) Fixé (classique) Adaptive (System One) Adaptatif (System One) Heated Tube Tuyau chauffant Tube Temperature Température du tuyau Température du tuyau PRS1 Heated Tube Temperature Température tuyau chauffant PRS1 Tube Temp. Temp. tuyau PRS1 Humidifier Setting Réglage de l’humidificateur PRS1 12mm 12 mm Parsing STR.edf records... Analyse des enregistrements STR.edf... Your Viatom device generated data that OSCAR has never seen before. Votre appareil Viatom a généré des données que OSCAR n’a jamais vues auparavant. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Les données importées peuvent ne pas être totalement précises. Les développeurs souhaitent donc une copie de vos fichiers Viatom pour qu'OSCAR gère correctement ces données. Viatom Viatom Viatom Software Logiciel Viatom OSCAR %1 needs to upgrade its database for %2 %3 %4 OSCAR %1 doit mettre à niveau sa base de données pour %2 %3 %4 Mask Pressure (High frequency) Pression du masque (haute fréquence) A ResMed data item: Trigger Cycle Event Élément de données Resmed : événement du cycle de déclenchement Movement Mouvement Movement detector Détecteur de mouvement Version "%1" is invalid, cannot continue! La version "%1" est invalide, impossible de continuer ! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). La version d'OSCAR que vous utilisez (%1) est PLUS ANCIENNE que celle utilisée pour créer ces données (%2). Please select a location for your zip other than the data card itself! Pour votre fichier zip, sélectionnez un emplacement différent de la carte de données ! Unable to create zip! Impossible de créer un fichier zip ! EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) Backing Up Files... Sauvegarde des fichiers... model %1 modèle %1 DreamStation 2 DreamStation 2 unknown model modèle inconnu CPAP-Check Mode CPAP-Check des appareils System One de Philips Respironics CPAP-Check AutoCPAP Mode AutoCPAP des appareils System One de Philips Respironics AutoCPAP Auto-Trial Mode Auto-Trial des appareils System One de Philips Respironics Auto-Trial AutoBiLevel Le système AutoBilevel modifie automatiquement la pression d’inspiration et d’expiration en fonction des besoins du patient AutoBiLevel S S S/T S/T S/T - AVAPS mode ventilatoire S/T - AVAPS PC - AVAPS fonction Philips Respironics PC - AVAPS Flex Lock fonction Philips Respironics Verrou Flex Whether Flex settings are available to you. Si les paramètres Flex sont disponibles. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Temps nécessaire pour passer d'EPAP à IPAP (plus le nombre est élevé, plus la transition est lente) Rise Time Lock Fonction Philips Respironics Verrouillage Pente Whether Rise Time settings are available to you. Si les paramètres Rise Time sont disponibles. Rise Lock Fonction Philips Respironics Rise Lock Passover Humidificateur Philips Respironics Passover Target Time Durée cible PRS1 Humidifier Target Time Durée cible de l’humidificateur PRS1 Hum. Tgt Time Hum. Durée cible Mask Resistance Setting Réglage résistance du masque Mask Resist. Résist. masque Hose Diam. Diamètre tuyau Tubing Type Lock Type de verrou du tuyau Whether tubing type settings are available to you. Si les paramètres Circuit sont disponibles. Tube Lock Verrouillage tuyau Mask Resistance Lock Verrouillage résistance masque Whether mask resistance settings are available to you. Si les paramètres Résistance masque sont disponibles. Mask Res. Lock Verrou type de masque Whether or not machine shows AHI via built-in display. Selon que l'écran de l'appareil affiche ou non l'IAH. Ramp Type Type de rampe Type of ramp curve to use. Type de courbe de rampe à utiliser. Linear Linéaire SmartRamp SmartRamp Ramp+ Ramp+ Backup Breath Mode Mode Fréquence respiratoire de secours The kind of backup breath rate in use: none (off), automatic, or fixed Type de fréquence respiratoire de secours utilisée : aucun (désactivé), automatique, ou fixe Breath Rate Fréquence respiratoire Fixed Fixe Fixed Backup Breath BPM Fréquence de respiration par minute (RPM) dans le mode de ventilation contrôlée Minimum breaths per minute (BPM) below which a timed breath will be initiated Respiration minimale par minute (RPM) en dessous de laquelle une respiration chronométrée sera amorcée Breath BPM Réglage de la fréquence respiratoire (RPM) Timed Inspiration Inspiration chronométrée The time that a timed breath will provide IPAP before transitioning to EPAP Durée d’une respiration chronométrée IPAP avant la transition vers EPAP Timed Insp. Insp. chronométrée Auto-Trial Duration Durée de l'Auto-Trial The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Nombre de jours de la période d'essai Auto-CPAP, après lequel la machine reviendra au CPAP Auto-Trial Dur. Durée Auto-Test EZ-Start fonction EZ-Start de Philips Respironics EZ-Start Whether or not EZ-Start is enabled Si EZ-Start activé ou non Variable Breathing Respiration variable UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend NON CONFIRMÉ : respiration peut-être variable, qui contient des écarts élevés par rapport à la tendance du débit inspiratoire de pointe Peak Flow Pic de débit Peak flow during a 2-minute interval Pic de débit pendant un intervalle de 2 minutes Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Une fois la mise à niveau effectuée, vous <font size=+1> ne pourrez plus</font> utiliser ce profil avec la version précédente. Debugging channel #1 Canal de débogage n° 1 Test #1 Test n ° 1 Debugging channel #2 Canal de débogage n° 2 Test #2 Test n ° 2 Recompressing Session Files Recompression des fichiers de session OSCAR crashed due to an incompatibility with your graphics hardware. Plantage d'OSCAR en raison d'incompatibilité avec votre matériel graphique. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. Pour résoudre ce problème, OSCAR est revenu à une méthode de dessin plus lente mais plus compatible. Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Ne peut analyser Channels.xml. OSCAR ne peut continuer et va s'arrêter. (1 day ago) (il y a 1 jour) (%2 days ago) (il y a %2 jours) New versions file improperly formed Nouvelle version invalide A more recent version of OSCAR is available Une version plus récente d'OSCAR est disponible release version test version version de test You are running the latest %1 of OSCAR Vous exécutez la dernière version %1 d'OSCAR You are running OSCAR %1 Vous exécutez OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 est disponible <a href='%2'>ici</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Des informations sur la préversion %1 sont disponibles ici : <a href='%2'>%2</a> Check for OSCAR Updates Vérification de disponibilté de mise à jour Unable to create the OSCAR data folder at Impossible de créer le répertoire pour les données d'OSCAR The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. La fenêtre contextuelle est saturée. Pour capturer la fenêtre contextuelle actuelle, supprimez-la, puis affichez à nouveau ce graphique. Essentials Paramètres de base Plus Plus For internal use only Pour usage interne uniquement Choose the SleepyHead or OSCAR data folder to migrate Choisissez le dossier de données SleepyHead ou OSCAR à migrer The folder you chose does not contain valid SleepyHead or OSCAR data. Le dossier que vous avez choisi ne contient pas de données SleepyHead ou OSCAR valides. If you have been using SleepyHead or an older version of OSCAR, Si vous avez utilisé SleepyHead ou une ancienne version d'OSCAR, OSCAR can copy your old data to this folder later. OSCAR peut copier vos anciennes données dans ce dossier ultérieurement. Migrate SleepyHead or OSCAR Data? Migrer les données SleepyHead ou OSCAR ? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Sur l'écran suivant, OSCAR vous demandera de sélectionner un dossier contenant des données SleepyHead ou OSCAR Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Cliquez sur [OUI] pour passer à l'écran suivant ou sur [Non] si vous ne souhaitez pas utiliser de données SleepyHead ou OSCAR. Unable to write to OSCAR data directory Impossible d’écrire dans le répertoire de données OSCAR Error code Code d'erreur OSCAR cannot continue and is exiting. OSCAR ne peut pas continuer et va fermer. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Impossible d'écrire dans le journal de débogage. Vous pouvez toujours utiliser le volet de débogage (Aide / Dépannage / Afficher le volet de débogage) mais le journal de débogage ne sera pas écrit sur le disque. Chromebook file system detected, but no removable device found Système de fichiers Chromebook détecté, mais aucun appareil amovible n'a été trouvé You must share your SD card with Linux using the ChromeOS Files program Vous devez partager votre carte SD avec Linux à l'aide du programme ChromeOS Files Flex Flex Unable to check for updates. Please try again later. Impossible de vérifier les mises à jour. Veuillez réessayer plus tard. SensAwake level Niveau SensAwake Expiratory Relief Décharge pression expiratoire Expiratory Relief Level Niveau de décharge pression expiratoire Humidity Humidité SleepStyle Mode sommeil This page in other languages: Cette page dans d'autres langues : %1 Graphs Graphiques %1 %1 of %2 Graphs Graphiques %1 de %2 %1 Event Types Types d'événements %1 %1 of %2 Event Types Types d'événements %1 de %2 Report about:blank au sujet : blanc SessionBar No Sessions Present Pas de sessions présentes %1h %2m %1h %2m SleepStyleLoader Import Error Erreur d'import This Machine Record cannot be imported in this profile. Import impossible des données depuis cet appareil dans ce profil. The Day records overlap with already existing content. Les enregistrements du jour se chevauchent avec le contenu déjà existant. Statistics Days Jours Oximeter Statistics Statistiques de l'oxymètre CPAP Usage Utilisation de la PPC Blood Oxygen Saturation Saturation en oxygène du sang % of time in %1 % du temps en %1 Last 30 Days Mois dernier %1 Index Index %1 %1 day of %2 Data on %3 %1 jour sur %2 de données sur %3 % of time above %1 threshold % du temps au-dessus de la limite de %1 Therapy Efficacy Efficacité de la thérapie % of time below %1 threshold % du temps au-dessous de la limite de %1 Max %1 %1 maxi %1 Median %1 Moyen Min %1 %1 mini Most Recent Le plus récent Pressure Settings Réglages de la pression Pressure Statistics Statistiques de pression Last 6 Months 6 derniers mois Average %1 Moyenne %1 No %1 data available. Pas de données %1 disponibles. Last Use Dernière utilisation Pressure Relief Allègement de la pression Pulse Rate Pouls First Use Première utilisation Last Week Semaine dernière Last Year Année dernière Details Détails %1 days of %2 Data, between %3 and %4 %1 jours de %2, entre %3 et %4 Last Session Dernière session Machine Information Informations de l'appareil CPAP Statistics Statistiques PPC Leak Statistics Statistiques des fuites Average Hours per Night Moyenne d'utilisation par nuit Name: %1, %2 Nom : %1, %2 DOB: %1 Date de naissance : %1 Phone: %1 Téléphone : %1 Email: %1 Email : %1 Address: Adresse : Days Used: %1 Jours d'utilisation : %1 Low Use Days: %1 Jours de faible usage : %1 Compliance: %1% Observance : %1% Days AHI of 5 or greater: %1 Jours avec l'IAH à 5 ou plus : %1 Best AHI Meilleur IAH Date: %1 AHI: %2 Date : %1 IHA : %2 Worst AHI Pire IAH Best Flow Limitation Meilleure limitation de flux Date: %1 FL: %2 Date : %1 FL : %2 Worst Flow Limtation Pire limitation de flux No Flow Limitation on record Pas de limitation de flux enregistrée Worst Large Leaks Pire fuites importantes Date: %1 Leak: %2% Date : %1 Fuite : %2% No Large Leaks on record Pas de fuite importante enregistrée Worst CSR Pire RCS Date: %1 CSR: %2% Date : %1 RCS : %2% No CSR on record Pas de RCS (Resp. Cheyne-Stokes) enregistrée Worst PB Pire RP (Resp. Per.) Date: %1 PB: %2% Date : %1 RP : %2% No PB on record Pas de RP enregistrée Want more information? Plus d'informations ? OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR a besoin de charger toutes les informations de synthèse pour calculer les meilleures/pires données journalières. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Cochez le préchargement des informations de synthèse dans les préférences pour que ces données soient disponibles. Best RX Setting Meilleurs réglages RX Date: %1 - %2 Date : %1 - %2 Worst RX Setting Pires réglages RX OSCAR is free open-source CPAP report software OSCAR est un logiciel libre de création de rapports de PPC (Pression Positive Continue) Oscar has no data to report :( Pas de données pour les rapports :( Compliance (%1 hrs/day) Observance (%1 heures/jour) Changes to Machine Settings Changements de réglages de l'appareil No data found?!? Aucune donnée disponible !? AHI: %1 IAH : %1 Total Hours: %1 Total Heures : %1 This report was prepared on %1 by OSCAR %2 Ce rapport a été rédigé le %1 par OSCAR %2 Welcome What would you like to do? Que souhaitez-vous faire ? CPAP Importer Import de PPC Oximetry Wizard Assistant d'oxymétrie Daily View Vue quotidienne Overview Aperçus Statistics Statistiques First import can take a few minutes. Le premier import peut prendre quelques minutes. The last time you used your %1... Dernière utilisation de l'appareil %1... last night la nuit dernière %2 days ago il y a %2 jours was %1 (on %2) %1 (%2) %1 hours, %2 minutes and %3 seconds %1 heure(s), %2 minute(s) et %3 seconde(s) Your machine was on for %1. L'appareil a fonctionné pendant %1. <font color = red>You only had the mask on for %1.</font> <font color = red>Vous n'avez porté le masque que %1.</font> under inférieur à over supérieur à reasonably close to assez proche de equal to égal à You had an AHI of %1, which is %2 your %3 day average of %4. IAH de %1 (%2 %4, moyenne des %3 jours précédents). Your machine was under %1-%2 %3 for %4% of the time. L'appreil était en dessous de %1-%2 %3 pendant %4% du temps. Your average leaks were %1 %2, which is %3 your %4 day average of %5. Fuites moyennes de %1 %2 (%3 %5, moyenne des %4 jours précédents). No CPAP data has been imported yet. Pas de données de PPC importées pour le moment. It would be a good idea to check File->Preferences first, Il est préférable de vérifier Fichier → Préférences avant toute chose, as there are some options that affect import. car il y a des options qui affectent l'import. Note that some preferences are forced when a ResMed machine is detected Certaines préférences sont forcées avec les appareils ResMed Welcome to the Open Source CPAP Analysis Reporter Bienvenue dans O.S.C.A.R. (Open Source CPAP Analysis Reporter) Your CPAP machine used a constant %1 %2 of air L'appareil PPC a utilisé une pression d'air constante de %1 %2 Your pressure was under %1 %2 for %3% of the time. La pression a été inférieure à %1 %2 pendant %3% du temps. Your machine used a constant %1-%2 %3 of air. L'appareil a utilisé une pression d'air constante de %1 %2 %3. Your EPAP pressure fixed at %1 %2. La pression EPAP s'est fixée %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Pression IPAP en dessous de %1 %2 pendant %3% du temps. Your EPAP pressure was under %1 %2 for %3% of the time. Pression EPAP en dessous de %1 %2 pendant %3% du temps. <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Attention: </span><span style=" color:#ff0000;">La carte SD de l'appareil ResMed S9 doit être verrouillée </span><span style=" font-weight:600; color:#ff0000;">avant d'être insérée dans votre ordinateur.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Certains systèmes d'exploitation écrivent des fichiers sur la carte sans le demander et peuvent rendre la carte inutilisable par votre appareil.</span></p></body></html> 1 day ago Il y a 1 jour gGraph %1 days %1 jours gGraphView Clone %1 Graph Cloner le graphique %1 Oximeter Overlays Dépassement de l'oxymètre Plots Points Resets all graphs to a uniform height and default order. Réinitialiser la hauteur et l'ordre de tous les graphiques. Remove Clone Enlever les clones Dotted Lines Lignes en pointillé CPAP Overlays Dépassement PPC Y-Axis Axe Y Reset Graph Layout Réinitialiser la disposition des graphiques 100% zoom level Zoom à 100% Double click title to pin / unpin Click and drag to reorder graphs Double-clic sur le titre pour épingler/décrocher Cliquer et glisser pour réorganiser les graphiques Restore X-axis zoom to 100% to view entire selected period. Réinitiliser le zoom des X à 100% pour afficher la période choisie. Restore X-axis zoom to 100% to view entire day's data. Réinitialiser le zoom des X à 100% pour afficher une journée complète. OSCAR-code-v1.3.1/Translations/Greek.el.ts000066400000000000000000016206541417327530600201740ustar00rootroot00000000000000 AboutDialog &About &Σχετικά με Release Notes Σημειώσεις έκδοσης Credits Πιστώσεις GPL License Άδεια GPL Close Κλείσε Show data folder Εμφάνιση φακέλου δεδομένων About OSCAR %1 Σχετικά με το OSCAR %1 Sorry, could not locate About file. Λυπούμαστε, δεν ήταν δυνατή η εύρεση του αρχείου Σχετικά με το αρχείο. Sorry, could not locate Credits file. Δυστυχώς, δεν ήταν δυνατή η εύρεση του αρχείου Credits. Sorry, could not locate Release Notes. Λυπούμαστε, δεν εντοπίσατε τις Σημειώσεις έκδοσης. OSCAR %1 OSCAR %1 Important: Σπουδαίος: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Δεδομένου ότι πρόκειται για έκδοση που εκδόθηκε πριν από την έκδοση, συνιστάται <b> να δημιουργήσετε αντίγραφο ασφαλείας του φακέλου δεδομένων σας με μη αυτόματο τρόπο </b> προτού προχωρήσετε, επειδή η προσπάθεια επαναφοράς αργότερα ενδέχεται να σπάσει τα πράγματα. To see if the license text is available in your language, see %1. Για να δείτε αν το κείμενο της άδειας χρήσης είναι διαθέσιμο στη γλώσσα σας, ανατρέξτε στην ενότητα %1. CMS50F37Loader Could not find the oximeter file: Δεν ήταν δυνατή η εύρεση του αρχείου οξύμετρου: Could not open the oximeter file: Το αρχείο του οξυμέτρου δεν ήταν δυνατό να ανοίξει: CMS50Loader Could not get data transmission from oximeter. Δεν ήταν δυνατή η λήψη δεδομένων από τον.οξύμετρο. Please ensure you select 'upload' from the oximeter devices menu. Παρακαλώ βεβαιωθείτε ότι έχετε επιλέξει «Ανέβασμα» από το μενού συσκευών οξύμετρο. Could not find the oximeter file: Δεν ήταν δυνατή η εύρεση του αρχείου οξύμετρου: Could not open the oximeter file: Το αρχείο του οξυμέτρου δεν ήταν δυνατό να ανοίξει: CheckUpdates Checking for newer OSCAR versions Daily Go to the previous day Φόρμα Show or hide the calender Εμφάνιση ή απόκρυψη του ημερολογίου Go to the next day Μετάβαση στην επόμενη μέρα Go to the most recent day with data records Μετάβαση στην πιο πρόσφατη ημέρα με τα αρχεία δεδομένων Events Εκδηλώσεις View Size Δείτε Μέγεθος Notes Σημειώσεις Journal Ημερολόγιο διάφορων πράξεων B Τ u υ Color Χρώμα Small Μικρό Medium Μεσαίο Big Μεγάλο Zombie Βρυκόλακας I'm feeling ... Νιώθω ... Weight Βάρος If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Εάν το ύψος είναι μεγαλύτερο από το μηδέν στο παράθυρο Προτιμήσεων, η ρύθμιση του βάρους θα εμφανίσει την τιμή του Δείκτη Μάζας Σώματος (BMI) Awesome Φοβερό B.M.I. Δ Μ Σ. Bookmarks Σελιδοδείκτες Add Bookmark Προσθήκη σελιδοδείκτη Starts Αρχή Remove Bookmark Κατάργηση σελιδοδείκτη Flags Σημάδι Graphs Γραφικες Παράστασης Show/hide available graphs. Εμφάνιση / απόκρυψη διαθέσιμων γραφημάτων. Breakdown Ανάλυση events Περιστατικα UF1 UF1 UF2 UF2 Time at Pressure Χρόνος στην πίεση No %1 events are recorded this day Αριθμός %1 Περιστατικα που καταγράφικαν αυτή τη μέρα %1 event %1 Περιστατικο %1 events %1 Περιστατικα Session Start Times Ώρες έναρξης Συνεδρίας Session End Times Ώρες Λήξης Συνεδρίας Session Information Πληροφορίες συνεδρίας Oximetry Sessions Οξυμετρικές συνεδρίες Duration Διάρκεια (Mode and Pressure settings missing; yesterday's shown.) This bookmark is in a currently disabled area.. Αυτός ο σελιδοδείκτης βρίσκεται σε μια περιοχή με ειδικές ανάγκες. CPAP Sessions Συνεδρίες CPAP Sleep Stage Sessions Συνεδρίες Στάδιο Ύπνου Position Sensor Sessions Συνεδρίες θέσης αισθητήρα Unknown Session άγνωστη Συνεδρία Machine Settings Ρυθμίσεις μηχανής Model %1 - %2 Μοντέλο %1 - %2 PAP Mode: %1 Λειτουργία PAP: %1 99.5% 90% {99.5%?} This day just contains summary data, only limited information is available. Αυτή η μέρα περιέχει μόνο σύνοψη δεδομένων, υπάρχουν μόνο περιορισμένες πληροφορίες. Total ramp time Συνολικός χρόνος ράμπας Time outside of ramp Χρόνος εκτός ράμπας Start Αρχή End Τέλος Unable to display Pie Chart on this system Δεν είναι δυνατή η εμφάνιση του Πίνακα πίτας σε αυτό το σύστημα 10 of 10 Event Types This CPAP machine does NOT record detailed data Sorry, this machine only provides compliance data. Λυπούμαστε, αυτό το μηχάνημα παρέχει μόνο δεδομένα συμμόρφωσης. "Nothing's here!" "Τίποτα δεν είναι εδώ!" No data is available for this day. Δεν υπάρχουν διαθέσιμα δεδομένα για αυτήν την ημέρα. 10 of 10 Graphs Oximeter Information πληροφορίες Οξύμετρο Details Λεπτομέριες Click to %1 this session. Κάντε κλικ στο %1 αυτή την περίοδο σύνδεσης. disable καθιστώ ανίκανο enable επιτρέπω %1 Session #%2 %1 Συνεδρίαση #%2 %1h %2m %3s %1ώ %2λ %3δ <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b> Σημείωση: </b> Όλες οι παρακάτω ρυθμίσεις βασίζονται σε υποθέσεις που δεν έχουν αλλάξει από τις προηγούμενες ημέρες. SpO2 Desaturations Αποκορεσμούς SpO2 Pulse Change events Παλμική Αλλαγή γεγονότων SpO2 Baseline Used Χρησιμοποιείται Βάση SpO2 Statistics Στατιστικά Total time in apnea Συνολικός χρόνος άπνοιας Time over leak redline Χρόνο πάνω από διαρροή κόκκινη υπογράμμιση Event Breakdown Κατανομή εκδήλωσης Sessions all off! Συνεδρίες όλοι σβηστοί! Sessions exist for this day but are switched off. Υπάρχουν συνεδρίες για αυτήν την ημέρα, αλλά είναι απενεργοποιημένες. Impossibly short session Ανέφικτα σύντομη συνεδρίαση Zero hours?? Μηδέν ώρες ?? BRICK :( ΤΟΥΒΛΟΥ :( Complain to your Equipment Provider! Παραπονεθείτε στον Πάροχο του εξοπλισμό σας! Pick a Colour Διαλέξτε χρώμα Bookmark at %1 Αποθηκεύετε με σελιδοδείκτη στο %1 ExportCSV Export as CSV Εξαγωγή ως CSV Dates: Ημερομηνίες: Resolution: Ανάλυση: Details Λεπτομέριες Sessions Συνεδρίες Daily Καθημερινά Filename: Ονομα αρχείου: Cancel Ματαίωση Export Εξαγωγή Start: Αρχή: End: Τέλος: Quick Range: Γρήγορη εμβέλεια: Most Recent Day Η πιο πρόσφατη ημέρα Last Week Την προηγούμενη εβδομάδα Last Fortnight Τελευταίο Δεκαπενθήμερο Last Month Τον προηγούμενο μήνα Last 6 Months Τελευταίοι 6 μήνες Last Year Πέρυσι Everything Τα παντα Custom Εθιμο Details_ Λεπτομέριες_ Sessions_ Συνεδρίες_ Summary_ Περίληψη_ Select file to export to Επιλέξτε το αρχείο προς εξαγωγή στο CSV Files (*.csv) Αρχεία CSV (* .csv) DateTime Ημερομηνία ώρα Session Συνεδρίαση Event Εκδήλωση Data/Duration Δεδομένα / Διάρκεια Date Ημερομηνία Session Count Καταμέτρηση συνόδων Start Αρχή End Τέλος Total Time Συνολικός χρόνος AHI AHI Count μετρώ FPIconLoader Import Error Σφάλμα εισαγωγής This Machine Record cannot be imported in this profile. Αυτή η εγγραφή μηχάνημα δεν μπορεί να εισαχθεί σε αυτό το προφίλ. The Day records overlap with already existing content. Οι εγγραφές Ημέρας επικαλύπτονται με ήδη υπάρχον περιεχόμενο. Help Hide this message Απόκρυψη αυτού του μηνύματος Search Topic: Θέμα αναζήτησης: Help Files are not yet available for %1 and will display in %2. Τα Βοήθεια αρχείων δεν είναι ακόμη διαθέσιμα για το %1 και θα εμφανίζονται στο %2. Help files do not appear to be present. Τα αρχεία βοήθειας δεν φαίνεται να είναι παρόντα. HelpEngine did not set up correctly Η μηχανή βοήθειας δεν έχει ρυθμιστεί σωστά HelpEngine could not register documentation correctly. Η μηχανή βοήθειας δεν μπορούσε να εγγράψει σωστά την τεκμηρίωση. Contents Περιεχόμενα Index Δείκτης Search Αναζήτηση No documentation available Δεν υπάρχει διαθέσιμη τεκμηρίωση Please wait a bit.. Indexing still in progress Περιμένετε λίγο .. Η ευρετηρίαση είναι ακόμα σε εξέλιξη No Οχι %1 result(s) for "%2" %1 αποτελέσματα για το "%2" clear Σαφή MD300W1Loader Could not find the oximeter file: Δεν ήταν δυνατή η εύρεση του αρχείου οξύμετρου: Could not open the oximeter file: Το αρχείο του οξυμέτρου δεν ήταν δυνατό να ανοίξει: MainWindow &Statistics &Στατιστική Report Mode Λειτουργία αναφοράς Standard Πρότυπο Monthly Μηνιαίο Date Range Εύρος ημερομηνιών Statistics Στατιστική Daily Καθημερινά Overview Συνολική εικόνα Oximetry Οξυμετρία Import Εισαγωγή Help Βοήθεια &File &Αρχείο &View &Θέα &Reset Graphs &Επαναφορά γραφημάτων &Help &Βοήθεια Troubleshooting Αντιμετώπιση προβλημάτων &Data &Δεδομένα &Advanced &Προχωρημένος Purge ALL Machine Data Καθαρίστε όλα τα δεδομένα μηχανής Rebuild CPAP Data Επαναδημιουργία δεδομένων CPAP &Import CPAP Card Data &Εισαγωγή δεδομένων κάρτας CPAP Show Daily view Εμφάνιση ημερήσιας προβολής Show Overview view Εμφάνιση προβολής "Επισκόπηση" &Maximize Toggle &Μεγιστοποιήστε την εναλλαγή Maximize window Μεγιστοποίηση παραθύρου Reset Graph &Heights Επαναφορά γραφήματος &ύψους Reset sizes of graphs Επαναφορά μεγεθών γραφημάτων Show Right Sidebar Εμφάνιση δεξιάς πλευρικής γραμμής Show Statistics view Εμφάνιση προβολής στατιστικών στοιχείων Import &Dreem Data Εισαγωγή &Dreem Δεδομένα Purge Current Selected Day &CPAP &CPAP &Oximetry &Οξυμετρία &Sleep Stage &Position &All except Notes All including &Notes Show &Line Cursor Εμφάνιση δρομέα &γραμμής Show Daily Left Sidebar Εμφάνιση ημερήσια αριστερή πλευρική γραμμή Show Daily Calendar Εμφάνιση ημερήσιου ημερολογίου Create zip of CPAP data card Δημιουργία zip της κάρτας δεδομένων CPAP Create zip of OSCAR diagnostic logs Create zip of all OSCAR data Δημιουργία φερμουάρ για όλα τα δεδομένα του OSCAR Report an Issue Αναφέρετε ένα πρόβλημα System Information Πληροφορίες συστήματος Show &Pie Chart Εμφάνιση &πίνακα πίτας Show Pie Chart on Daily page Εμφάνιση πίνακα πίτας στην ημερήσια σελίδα Standard graph order, good for CPAP, APAP, Bi-Level Τυπική σειρά γραφημάτων, καλή για CPAP, APAP, Bi-Level Advanced Προχωρημένος Advanced graph order, good for ASV, AVAPS Προηγμένη σειρά γραφημάτων, καλή για ASV, AVAPS Show Personal Data Check For &Updates &Preferences &Προτιμήσεις &Profiles &Προφίλ &About OSCAR &Σχετικά με το OSCAR Show Performance Information Εμφάνιση πληροφοριών απόδοσης CSV Export Wizard Οδηγός εξαγωγής CSV Export for Review Εξαγωγή για αναθεώρηση E&xit Ε&ξοδος Exit Εξοδος View &Daily Προβολή &Καθημερινά View &Overview Προβολή &επισκόπηση View &Welcome Προβολή &Καλώς ορίσατε Use &AntiAliasing Χρήση &αντισύλληψη Show Debug Pane Εμφάνιση παραθύρου εντοπισμού σφαλμάτων Take &Screenshot Πραγματοποιήστε &λήψη της οθόνης O&ximetry Wizard Οδηγός O &οξυμετρίας Print &Report Εκτύπωση &Αναφορά &Edit Profile &Επεξεργασία προφίλ Import &Viatom/Wellue Data Daily Calendar Καθημερινό ημερολόγιο Backup &Journal Backup &Εφημερίδα Online Users &Guide Online Χρήστες &Οδηγός &Frequently Asked Questions &Συχνές Ερωτήσεις &Automatic Oximetry Cleanup &Καθαρισμός αυτόματης οξυμετρίας Change &User Αλλαγή &Χρήστη Purge &Current Selected Day Εκκαθάριση &τρέχουσας επιλεγμένης ημέρας Right &Sidebar Δεξιά πλευρική &γραμμή Daily Sidebar Ημερήσια πλευρική γραμμή View S&tatistics Προβολή σ&τατιστικών στοιχείων Navigation Πλοήγηση Bookmarks Σελιδοδείκτες Records Εγγραφές Exp&ort Data Εξαγωγή &δεδομένων Profiles Προφίλ Purge Oximetry Data Δεδομένα καθαρισμού οξυμετρίας View Statistics Προβολή στατιστικών στοιχείων Import &ZEO Data Εισαγωγή δεδομένων &ZEO Import RemStar &MSeries Data Εισαγωγή δεδομένων RemStar &MSeries Sleep Disorder Terms &Glossary Όροι διαταραχής ύπνου &Γλωσσάρι Change &Language Αλλαξε &γλώσσα Change &Data Folder Αλλαγή του φακέλου &δεδομένων Import &Somnopose Data Εισαγωγή δεδομένων &Somnopose Current Days Τρέχουσες ημέρες Welcome καλως ΗΡΘΑΤΕ &About &Σχετικά με Please wait, importing from backup folder(s)... Περιμένετε, εισάγοντας από το φάκελο ή τα αντίγραφα ασφαλείας ... Import Problem Πρόβλημα εισαγωγής Please insert your CPAP data card... Εισαγάγετε την κάρτα δεδομένων CPAP ... Access to Import has been blocked while recalculations are in progress. Η πρόσβαση στην εισαγωγή έχει αποκλειστεί κατά τη διάρκεια της επανεκτίμησης. CPAP Data Located Τα δεδομένα CPAP βρίσκονται Import Reminder Υπενθύμιση εισαγωγής Find your CPAP data card Importing Data Εισαγωγή δεδομένων Choose where to save screenshot Επιλέξτε από πού να αποθηκεύσετε το στιγμιότυπο οθόνης Image files (*.png) Αρχεία εικόνας (*.png) The User's Guide will open in your default browser Ο οδηγός χρήσης θα ανοίξει στο προεπιλεγμένο πρόγραμμα περιήγησης The FAQ is not yet implemented Οι συνήθεις ερωτήσεις δεν έχουν ακόμη εφαρμοστεί If you can read this, the restart command didn't work. You will have to do it yourself manually. Εάν μπορείτε να διαβάσετε αυτό, η εντολή επανεκκίνησης δεν λειτούργησε. Θα πρέπει να το κάνετε μόνοι σας με το χέρι. Are you sure you want to rebuild all CPAP data for the following machine: Είστε βέβαιοι ότι θέλετε να επαναδημιουργήσετε όλα τα δεδομένα CPAP για το ακόλουθο μηχάνημα: For some reason, OSCAR does not have any backups for the following machine: Για κάποιο λόγο, το OSCAR δεν διαθέτει αντίγραφα ασφαλείας για το ακόλουθο μηχάνημα: OSCAR does not have any backups for this machine! Το OSCAR δεν διαθέτει αντίγραφα ασφαλείας για αυτό το μηχάνημα! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Εάν δεν έχετε κάνει <i>τα αντίγραφα <b>δικά σας </b>για ΟΛΑ τα δεδομένα σας για αυτό το μηχάνημα </i>, <font size=+2>θα χάσετε μόνιμα τα στοιχεία του μηχανήματος <b> μόνιμα </b>! </font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Πρόκειται να μετακινηθείτε <font size= +2> κατάργηση </font> της μηχανής OSCAR για το ακόλουθο μηχάνημα: </p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: Ένα σφάλμα δικαιωμάτων αρχείου προκάλεσε την αποτυχία της διαδικασίας εκκαθάρισης. θα πρέπει να διαγράψετε το παρακάτω φάκελο με μη αυτόματο τρόπο: No help is available. Δεν υπάρχει βοήθεια. %1's Journal Εφημερίδα της %1 Choose where to save journal Επιλέξτε πού να αποθηκεύσετε το ημερολόγιο XML Files (*.xml) Αρχεία XML (* .xml) Export review is not yet implemented Η αναθεώρηση εξαγωγής δεν έχει ακόμη εφαρμοστεί Would you like to zip this card? Θα θέλατε να παραλάβετε αυτήν την κάρτα; Choose where to save zip Επιλέξτε πού να αποθηκεύσετε το φερμουάρ ZIP files (*.zip) Αρχεία ZIP (* .zip) Creating zip... Δημιουργία φερμουάρ ... Calculating size... Υπολογισμός μεγέθους ... Reporting issues is not yet implemented Τα θέματα αναφοράς δεν έχουν ακόμη εφαρμοστεί OSCAR Information Πληροφορίες OSCAR Help Browser Βοήθεια του προγράμματος περιήγησης %1 (Profile: %2) %1 (Προφίλ: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Θυμηθείτε να επιλέξετε τον ριζικό φάκελο ή το γράμμα μονάδας δίσκου της κάρτας δεδομένων σας και όχι έναν φάκελο μέσα σε αυτό. Please open a profile first. Ανοίξτε πρώτα ένα προφίλ. Check for updates not implemented Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Με την προϋπόθεση ότι έχετε κάνει τα <i> αντίγραφα ασφαλείας <b> δικά σας </b> για ΟΛΑ τα δεδομένα CPAP </i>, μπορείτε να ολοκληρώσετε αυτήν την ενέργεια, αλλά θα πρέπει να επαναφέρετε από τα αντίγραφα ασφαλείας σας με μη αυτόματο τρόπο. Are you really sure you want to do this? Είστε σίγουροι ότι θέλετε να το κάνετε αυτό; Because there are no internal backups to rebuild from, you will have to restore from your own. Επειδή δεν υπάρχουν εσωτερικά αντίγραφα ασφαλείας για την αποκατάσταση, θα πρέπει να αποκαταστήσετε από τη δική σας. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Θα θέλατε να εισαγάγετε από τα δικά σας αντίγραφα ασφαλείας τώρα; (δεν θα έχετε ορατά δεδομένα για αυτό το μηχάνημα μέχρι να το κάνετε) Note as a precaution, the backup folder will be left in place. Σημειώστε προληπτικά ότι ο φάκελος δημιουργίας αντιγράφων ασφαλείας θα παραμείνει στη θέση του. Are you <b>absolutely sure</b> you want to proceed? Είστε <b> απολύτως σίγουροι </b> που θέλετε να συνεχίσετε; The Glossary will open in your default browser Το Γλωσσάριο θα ανοίξει στο προεπιλεγμένο πρόγραμμα περιήγησης There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete Are you sure you want to delete oximetry data for %1 Είστε βέβαιοι ότι θέλετε να διαγράψετε δεδομένα oximetry για %1 <b>Please be aware you can not undo this operation!</b> <b>Λάβετε υπόψη ότι δεν μπορείτε να αναιρέσετε αυτήν την ενέργεια! </b> Select the day with valid oximetry data in daily view first. Επιλέξτε την ημέρα με έγκυρα δεδομένα οξυμετρίας σε ημερήσια προβολή πρώτα. Loading profile "%1" Το προφίλ φόρτωσης "%1" Imported %1 CPAP session(s) from %2 Εισαγόμενες %1 συνεδρίες CPAP από %2 Import Success Εισαγωγή επιτυχίας Already up to date with CPAP data at %1 Ήδη ενημερωμένο με δεδομένα CPAP στο %1 Up to date Ενημερωμένος Couldn't find any valid Machine Data at %1 Δεν ήταν δυνατή η εύρεση έγκυρων δεδομένων μηχανών στη διεύθυνση %1 Choose a folder Επιλέξτε ένα φάκελο No profile has been selected for Import. Δεν έχει επιλεγεί προφίλ για την εισαγωγή. Import is already running in the background. Η εισαγωγή εκτελείται ήδη στο παρασκήνιο. A %1 file structure for a %2 was located at: Μια δομή αρχείου %1 για ένα %2 βρέθηκε στο: A %1 file structure was located at: Μια δομή αρχείου %1 βρισκόταν στη διεύθυνση: Would you like to import from this location? Θα θέλατε να εισαγάγετε από αυτήν την τοποθεσία; Specify Προσδιορίζω Access to Preferences has been blocked until recalculation completes. Η πρόσβαση στις προτιμήσεις έχει αποκλειστεί μέχρι να ολοκληρωθεί ο επανυπολογισμός. There was an error saving screenshot to file "%1" Παρουσιάστηκε σφάλμα κατά την αποθήκευση του στιγμιότυπου οθόνης στο αρχείο "%1" Screenshot saved to file "%1" Το στιγμιότυπο οθόνης αποθηκεύτηκε στο αρχείο "%1" Please note, that this could result in loss of data if OSCAR's backups have been disabled. Σημειώστε ότι αυτό θα μπορούσε να οδηγήσει σε απώλεια δεδομένων αν τα αντίγραφα ασφαλείας του OSCAR έχουν απενεργοποιηθεί. There was a problem opening MSeries block File: Παρουσιάστηκε πρόβλημα κατά το άνοιγμα του αρχείου MSeries Αρχείο: MSeries Import complete Η εισαγωγή του MSeries ολοκληρώθηκε MinMaxWidget Auto-Fit Αυτόματη προσαρμογή Defaults Προεπιλογές Override Καταπατώ The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Ο τρόπος κλιμάκωσης του άξονα Υ, το 'Auto-Fit' για αυτόματη κλιμάκωση, 'Defaults' για τις ρυθμίσεις σύμφωνα με τον κατασκευαστή και 'Override' για να επιλέξετε το δικό σας. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Η ελάχιστη τιμή αξόνων Y. Σημειώστε ότι αυτό μπορεί να είναι ένας αρνητικός αριθμός εάν το επιθυμείτε. The Maximum Y-Axis value.. Must be greater than Minimum to work. Η μέγιστη τιμή αξόνων Y .. Πρέπει να είναι μεγαλύτερη από το ελάχιστο για εργασία. Scaling Mode Λειτουργία κλιμάκωσης This button resets the Min and Max to match the Auto-Fit Αυτό το κουμπί επαναφέρει τα Min και Max για να ταιριάζει με το Auto-Fit NewProfile Edit User Profile Επεξεργασία προφίλ χρήστη I agree to all the conditions above. Συμφωνώ με όλες τις παραπάνω προϋποθέσεις. User Information Πληροφορίες χρήστη User Name Ονομα χρήστη Password Protect Profile Προστασία με προφίλ με κωδικό πρόσβασης Password Κωδικός πρόσβασης ...twice... ...εις διπλούν... Locale Settings Ρυθμίσεις τοπικού συστήματος Country Χώρα TimeZone Ζώνη ώρας about:blank about:blank Very weak password protection and not recommended if security is required. DST Zone Ζώνη DST Personal Information (for reports) Προσωπικές πληροφορίες (για αναφορές) First Name Ονομα Last Name Επίθετο It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Είναι τελείως εντάξει για να περάσετε ή να παραλείψετε αυτό, αλλά η ογκώδης ηλικία σας είναι απαραίτητη για την ενίσχυση της ακρίβειας ορισμένων υπολογισμών. D.O.B. Ημερομηνια γεννησης. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Το βιολογικό (γέννηση) φύλο χρειάζεται μερικές φορές για να αυξήσει την ακρίβεια κάποιων υπολογισμών, μη διστάσετε να αφήσετε αυτό το κενό και να παραλείψετε οποιοδήποτε από αυτά.</p></body></html> Gender Γένος Male Αρσενικός Female Θηλυκός Height Υψος Metric Μετρικός English Αγγλικά Contact Information Στοιχεία επικοινωνίας Address Διεύθυνση Email διεύθυνση ηλεκτρονικού ταχυδρομείου Phone Τηλέφωνο CPAP Treatment Information Πληροφορίες επεξεργασίας CPAP Date Diagnosed Ημερομηνία διάγνωσης Untreated AHI Μη επεξεργασμένο AHI CPAP Mode Λειτουργία CPAP CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure Rx Πίεση Doctors / Clinic Information Πληροφορίες ιατρών / κλινικών Doctors Name Όνομα Ιατρού Practice Name Όνομα πρακτικής Patient ID Αναγνωριστικό ασθενούς &Cancel &Ματαίωση &Back &Πίσω &Next &Επόμενο Select Country Επιλέξτε χώρα Welcome to the Open Source CPAP Analysis Reporter Καλώς ήλθατε στον Αναλυτή ανάλυσης CPAP ανοιχτού κώδικα This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Αυτό το λογισμικό έχει σχεδιαστεί για να σας βοηθήσει στην αναθεώρηση των δεδομένων που παράγονται από τις μηχανές σας CPAP και τον σχετικό εξοπλισμό. PLEASE READ CAREFULLY ΠΑΡΑΚΑΛΩ ΔΙΑΒΑΣΤΕ ΠΡΟΣΕΚΤΙΚΑ Accuracy of any data displayed is not and can not be guaranteed. Η ακρίβεια των εμφανιζόμενων δεδομένων δεν είναι και δεν μπορεί να διασφαλιστεί. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Οποιεσδήποτε αναφορές δημιουργούνται μόνο για ΠΡΟΣΩΠΙΚΗ ΧΡΗΣΗ και ΔΕΝ ΚΑΝΕΝΑΣ ΤΡΟΠΟΠΟΙΗΜΕΝΟΙ για συμμόρφωση ή ιατρικούς διαγνωστικούς σκοπούς. Use of this software is entirely at your own risk. Η χρήση αυτού του λογισμικού είναι εξ ολοκλήρου με δική σας ευθύνη. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. Το OSCAR κυκλοφόρησε ελεύθερα κάτω από το<a href='qrc:/COPYING'>GNU Public License v3</a>, και δεν συνοδεύεται από καμία εγγύηση και χωρίς αξιώσεις για καταλληλότητα για οποιοδήποτε σκοπό. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. Το OSCAR προορίζεται απλώς ως θεατής δεδομένων και σίγουρα δεν υποκαθιστά την αρμόδια ιατρική καθοδήγηση από τον γιατρό σας. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Οι συγγραφείς δεν θα θεωρηθούν υπεύθυνοι για <u> τίποτα </u> που σχετίζονται με τη χρήση ή την κακή χρήση αυτού του λογισμικού. Please provide a username for this profile Καταχωρίστε ένα όνομα χρήστη για αυτό το προφίλ Passwords don't match Οι κωδικοί πρόσβασης δεν ταιριάζουν Profile Changes Προφίλ αλλαγών Accept and save this information? Αποδεχτείτε και αποθηκεύστε αυτές τις πληροφορίες; &Finish &Φινίρισμα &Close this window &Κλείστε αυτό το παράθυρο Overview Range: Εύρος: Last Week Την προηγούμενη εβδομάδα Last Two Weeks Τελευταίες δύο εβδομάδες Last Month Τον προηγούμενο μήνα Last Two Months Τελευταίους δύο μήνες Last Three Months Τελευταίοι τρεις μήνες Last 6 Months Τελευταίοι 6 μήνες Last Year Πέρυσι Everything Τα παντα Custom Επιλέγω Snapshot Start: Αρχή: End: Τέλος: Reset view to selected date range Επαναφορά προβολής σε επιλεγμένο εύρος ημερομηνιών Toggle Graph Visibility Εναλλαγή ορατότητας γραφήματος Drop down to see list of graphs to switch on/off. Πατήστε κάτω για να δείτε τη λίστα γραφημάτων για ενεργοποίηση / απενεργοποίηση. Graphs Γραφικές παραστάσεις Respiratory Disturbance Index Αναπνευστικός Διατάραξη Δείκτης Apnea Hypopnea Index Άπνοια Υπόπνοια Δείκτης Usage Χρήση Usage (hours) Χρήση (ώρες) Session Times Ώρες συνεδρίας Total Time in Apnea Συνολικός χρόνος στην άπνοια Total Time in Apnea (Minutes) Συνολικός χρόνος στην άπνοια (Λεπτά) Body Mass Index Σώμα Μάζα Δείκτης How you felt (0-10) Πως αισθάνθηκες (0-10) 10 of 10 Charts Show all graphs Εμφάνιση όλων των γραφημάτων Hide all graphs Απόκρυψη όλων των γραφημάτων OximeterImport Oximeter Import Wizard Οδηγός εισαγωγής οξύμετρου Skip this page next time. Περάστε αυτή τη σελίδα την επόμενη φορά. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? Από πού θέλετε να εισάγετε; <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. Οι χρήστες CMS50E / F, κατά την άμεση εισαγωγή, παρακαλώ μην επιλέξετε μεταφόρτωση στη συσκευή σας μέχρι να σας ζητηθεί από το OSCAR. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Εάν είναι ενεργοποιημένη, το OSCAR θα επαναφέρει αυτόματα το εσωτερικό ρολόι του CMS50 χρησιμοποιώντας τους τρέχοντες χρόνους των υπολογιστών σας.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>Εδώ μπορείτε να εισάγετε ένα όνομα 7 χαρακτήρων για αυτό το οξύμετρο.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Αυτή η επιλογή θα διαγράψει την εισαγωγή από το οξύμετρο σας μετά την ολοκλήρωση της εισαγωγής. </p><p>Χρησιμοποιήστε με προσοχή, γιατί αν κάτι πάει στραβά πριν η OSCAR αποθηκεύσει την συνεδρία σας, δεν μπορείτε να την πάρετε πίσω.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Αυτή η επιλογή σας επιτρέπει να εισάγετε (μέσω καλωδίου) από τις εσωτερικές εγγραφές των oximeters σας.</p><p>Μετά την επιλογή αυτής της επιλογής, τα παλαιά οξυμετρικά Contec θα σας ζητήσουν να χρησιμοποιήσετε το μενού της συσκευής για να ξεκινήσετε τη μεταφόρτωση.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Αν δεν σας πειράζει η ύπαρξη ενός συνημμένου σε έναν τρέχοντα υπολογιστή κατά τη διάρκεια της νύχτας, αυτή η επιλογή παρέχει ένα χρήσιμο γράφημα πλεισιοσόγραμμα, το οποίο δίνει ένδειξη για τον καρδιακό ρυθμό, πάνω από τις κανονικές μετρήσεις οξυμετρίας.</p></body></html> Record attached to computer overnight (provides plethysomogram) Εγγραφή συνδεδεμένη στον υπολογιστή κατά τη διάρκεια της νύχτας (παρέχει πλεισιοσόγραμμα) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Αυτή η επιλογή σας επιτρέπει να εισάγετε από αρχεία δεδομένων που δημιουργήθηκαν από το λογισμικό που συνοδεύει το παλμικό οξύμετρο σας, όπως το SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Εισαγωγή από αρχείο δεδομένων αποθηκευμένο από άλλο πρόγραμμα, όπως το SpO2Review Please connect your oximeter device Συνδέστε τη συσκευή οξυμέτρου If you can read this, you likely have your oximeter type set wrong in preferences. Εάν μπορείτε να διαβάσετε αυτό το θέμα, πιθανότατα ο τύπος οξυμέτρου σας έχει ρυθμιστεί λανθασμένα στις προτιμήσεις. Please connect your oximeter device, turn it on, and enter the menu Συνδέστε τη συσκευή οξυμέτρου, ενεργοποιήστε την και εισέλθετε στο μενού Press Start to commence recording Πατήστε Έναρξη για να ξεκινήσετε την εγγραφή Show Live Graphs Εμφάνιση ζωντανών γραφημάτων Duration Διάρκεια Pulse Rate Καρδιακός σφυγμός Multiple Sessions Detected Εντοπίστηκαν πολλαπλές περιόδους σύνδεσης Start Time Ωρα έναρξης Details Λεπτομέριες Import Completed. When did the recording start? Η εισαγωγή ολοκληρώθηκε. Πότε ξεκίνησε η εγγραφή; Oximeter Starting time Χρόνος έναρξης του οξύμετρου I want to use the time reported by my oximeter's built in clock. Θέλω να χρησιμοποιήσω το χρόνο που αναφέρθηκε από το ενσωματωμένο ρολόι του οξυγονομέτρου μου. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Ξεκίνησα αυτήν την καταγραφή οξυμέτρου στο (ή κοντά) την ίδια ώρα με μια συνεδρία στη μηχανή CPAP μου. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Σημείωση: Ο συγχρονισμός με την ώρα έναρξης της περιόδου σύνδεσης CPAP θα είναι πάντα ακριβέστερος.</p></body></html> Choose CPAP session to sync to: Επιλέξτε την περίοδο λειτουργίας CPAP για συγχρονισμό σε: You can manually adjust the time here if required: Μπορείτε να ρυθμίσετε με μη αυτόματο τρόπο την ώρα εδώ, εάν απαιτείται: HH:mm:ssap HH:mm:ssap &Cancel &Ματαίωση &Information Page &Σελίδα πληροφοριών CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Ρύθμιση ημερομηνίας / ώρας συσκευής <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Ελέγξτε για να ενεργοποιήσετε την ενημέρωση της επόμενης εισαγωγής του αναγνωριστικού της συσκευής, η οποία είναι χρήσιμη για όσους διαθέτουν πολλαπλά οξυμετρικά όργανα.</p></body></html> Set device identifier Ορίστε αναγνωριστικό συσκευής Erase session after successful upload Διαγραφή περιόδου μετά την επιτυχή μεταφόρτωση Import directly from a recording on a device Εισαγάγετε απευθείας από μια εγγραφή σε μια συσκευή <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Υπενθύμιση για τους χρήστες CPAP: </span><span style=" color:#fb0000;">Θυμήσατε να εισαγάγετε πρώτα τις συνεδρίες CPAP;<br/></span>Εάν ξεχάσετε, δεν θα έχετε έγκυρο χρόνο για να συγχρονίσετε αυτή την περίοδο οξυμετρίας.<br/>Για να εξασφαλίσετε καλό συγχρονισμό μεταξύ συσκευών, προσπαθήστε πάντα να ξεκινήσετε και τις δύο ταυτόχρονα.</p></body></html> Please choose which one you want to import into OSCAR Επιλέξτε ποια θέλετε να εισαγάγετε στο OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>Το OSCAR χρειάζεται χρόνο έναρξης για να μάθει πού να αποθηκεύσει αυτή τη συνεδρία οξυμετρίας. </P> <p> Επιλέξτε μία από τις παρακάτω επιλογές:</p></body></html> &Retry &Προσπαθησε ξανα &Choose Session &Επιλογή συνόδου &End Recording &Τέλος εγγραφής &Sync and Save &Sync και Αποθήκευση &Save and Finish &Αποθήκευση και Τερματισμός &Start &Αρχή Scanning for compatible oximeters Σάρωση για συμβατά οξύμετρα Could not detect any connected oximeter devices. Δεν ήταν δυνατή η ανίχνευση συνδεδεμένων συσκευών οξύμετρου. Connecting to %1 Oximeter Σύνδεση στο %1 οξύμετρο Renaming this oximeter from '%1' to '%2' Μετονομάστε αυτό το οξύμετρο από '%1' σε '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Το όνομα του οξυμέτρου είναι διαφορετικό. Εάν έχετε μόνο ένα και το μοιράζεστε μεταξύ των προφίλ, ορίστε το όνομα στην ίδια και στα δύο προφίλ. "%1", session %2 "%1", περίοδος %2 Nothing to import Τίποτα για εισαγωγή Your oximeter did not have any valid sessions. Το οξύμετρο σας δεν έχει έγκυρες περιόδους σύνδεσης. Close Κλείσε Waiting for %1 to start Αναμονή για την εκκίνηση του %1 Waiting for the device to start the upload process... Αναμονή της συσκευής να ξεκινήσει τη διαδικασία μεταφόρτωσης ... Select upload option on %1 Επιλέξτε επιλογή μεταφόρτωσης στο %1 You need to tell your oximeter to begin sending data to the computer. Πρέπει να πείτε στο οξύμετρο σας να αρχίσει να στέλνει δεδομένα στον υπολογιστή. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Συνδέστε το οξύμετρο σας, εισάγετε το μενού του και επιλέξτε μεταφόρτωση δεδομένων για να ξεκινήσετε τη μεταφορά δεδομένων ... %1 device is uploading data... Η συσκευή %1 μεταφορτώνει δεδομένα ... Please wait until oximeter upload process completes. Do not unplug your oximeter. Περιμένετε μέχρι να ολοκληρωθεί η διαδικασία φόρτωσης του οξυμέτρου. Μην αποσυνδέετε το οξύμετρο. Oximeter import completed.. Η εισαγωγή του οξυμέτρου ολοκληρώθηκε .. Select a valid oximetry data file Επιλέξτε ένα έγκυρο αρχείο δεδομένων οξυμετρίας Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Αρχεία οξυμετρίας (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Καμία μονάδα Οξυμετρίας δεν θα μπορούσε να αναλύσει το δεδομένο αρχείο: Live Oximetry Mode Λειτουργία ζωντανής οξυμετρίας Live Oximetry Stopped Η ζωντανή οξυμετρία σταμάτησε Live Oximetry import has been stopped Η εισαγωγή ζωντανής οξυμετρίας έχει σταματήσει Oximeter Session %1 Οξυμετρική συνεδρία %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. Το OSCAR σάς δίνει τη δυνατότητα να παρακολουθείτε τα δεδομένα οξυμετρίας παράλληλα με τα δεδομένα της συνεδρίας CPAP, τα οποία μπορούν να δώσουν πολύτιμη εικόνα της αποτελεσματικότητας της θεραπείας με CPAP. Θα λειτουργεί επίσης αυτόνομα με το παλμικό οξύμετρο σας, επιτρέποντάς σας να αποθηκεύετε, να παρακολουθείτε και να ελέγχετε τα καταγεγραμμένα δεδομένα σας. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Αν προσπαθείτε να συγχρονίσετε δεδομένα οξυμετρίας και CPAP, βεβαιωθείτε ότι έχετε εισαγάγει πρώτα τις συνεδρίες CPAP πριν προχωρήσετε! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Για να μπορεί ο OSCAR να εντοπίσει και να διαβαστεί απευθείας από τη συσκευή του οξυμέτρου, πρέπει να βεβαιωθείτε ότι έχουν εγκατασταθεί στον υπολογιστή σας τα σωστά προγράμματα οδήγησης συσκευών (π.χ. USB to Serial UART). Για περισσότερες πληροφορίες σχετικά με αυτό, %1 κάντε κλικ εδώ %2. Oximeter not detected Δεν ανιχνεύθηκε οξύμετρο Couldn't access oximeter Δεν ήταν δυνατή η πρόσβαση στο οξύμετρο Starting up... Ξεκινώντας... If you can still read this after a few seconds, cancel and try again Αν εξακολουθείτε να το διαβάσετε μετά από μερικά δευτερόλεπτα, ακυρώστε και δοκιμάστε ξανά Live Import Stopped Η Ζωντανή Εισαγωγή σταμάτησε %1 session(s) on %2, starting at %3 %1 συνεδρία (ες) στο %2, ξεκινώντας από %3 No CPAP data available on %1 Δεν υπάρχουν διαθέσιμα δεδομένα CPAP στο %1 Recording... Εγγραφή... Finger not detected Δεν εντοπίστηκε δάκτυλο I want to use the time my computer recorded for this live oximetry session. Θέλω να χρησιμοποιήσω την ώρα που κατέγραψε ο υπολογιστής μου για αυτή τη συνεδρία ζωντανής οξυμετρίας. I need to set the time manually, because my oximeter doesn't have an internal clock. Πρέπει να ρυθμίσω την ώρα χειροκίνητα, επειδή το οξύμετρο μου δεν διαθέτει εσωτερικό ρολόι. Something went wrong getting session data Κάτι πήγε στραβά με την απόκτηση δεδομένων συνόδου Welcome to the Oximeter Import Wizard Καλώς ήλθατε στον Οδηγό εισαγωγής οξύμετρου Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Τα παλμικά οξύμετρα είναι ιατρικές συσκευές που χρησιμοποιούνται για τη μέτρηση του κορεσμού οξυγόνου στο αίμα. Κατά τη διάρκεια εκτεταμένων περιπτώσεων άπνοιας και μη φυσιολογικών ρυθμών αναπνοής, τα επίπεδα κορεσμού οξυγόνου στο αίμα μπορούν να μειωθούν σημαντικά και μπορεί να υποδεικνύουν ζητήματα που χρειάζονται ιατρική φροντίδα. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) Το OSCAR είναι συμβατό με τα σειριακά οξύμετρα Contec CMS50D +, CMS50E, CMS50F και CMS50I. <br/> (Σημείωση: Η άμεση εισαγωγή από τα μοντέλα bluetooth είναι πιθανή ακόμα και </span> <span style = "font-weight: ) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Μπορεί να θέλετε να σημειώσετε ότι άλλες εταιρείες, όπως το Pulox, απλά αναδιαμορφώνουν το Contec CMS50 με νέα ονόματα, όπως τα Pulox PO-200, PO-300, PO-400. Αυτά θα πρέπει επίσης να λειτουργούν. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Μπορεί επίσης να διαβάσει από τα αρχεία .dat του οξυμέτρου ChoiceMMed MD300W1. Please remember: Παρακαλώ θυμηθείτε: Important Notes: Σημαντικές σημειώσεις: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Οι συσκευές Contec CMS50D + δεν διαθέτουν εσωτερικό ρολόι και δεν καταγράφουν χρόνο έναρξης. Αν δεν έχετε μια περίοδο λειτουργίας CPAP για να συνδέσετε μια εγγραφή, θα πρέπει να εισαγάγετε την ώρα έναρξης με το χέρι μετά την ολοκλήρωση της διαδικασίας εισαγωγής. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Ακόμα και για συσκευές με εσωτερικό ρολόι, συνιστάται ακόμη να έχετε τη συνήθεια να εκκινείτε αρχεία οξυμέτρου ταυτόχρονα με τις συνεδρίες CPAP, επειδή τα εσωτερικά ρολόγια CPAP τείνουν να μετακινούνται με την πάροδο του χρόνου και δεν μπορούν να επαναληφθούν εύκολα όλα. Oximetry Date Ημερομηνία d/MM/yy h:mm:ss AP d/MM/yy h:mm:ss AP R&eset &Επαναφορά Pulse Σφυγμός &Open .spo/R File &Άνοιγμα αρχείου .spo/R Serial &Import &Σειριακή εισαγωγή &Start Live &Ξεκινήστε τη λειτουργία Live Serial Port Σειριακή θύρα &Rescan Ports &Επανασυνδέστε τις θύρες PreferencesDialog Preferences Προτιμήσεις &Import &Εισαγωγή Combine Close Sessions Συνδυάστε Κλείσιμο Συνεδρίων Minutes λεπτά Multiple sessions closer together than this value will be kept on the same day. Πολλές συνεδρίες πιο κοντά από αυτήν την τιμή θα διατηρούνται την ίδια ημέρα. Ignore Short Sessions Αγνοήστε σύντομες περιόδους σύνδεσης <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Οι συνεδρίες μικρότερης διάρκειας από αυτή δεν θα εμφανίζονται<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Ημέρα χωριστής ώρας Sessions starting before this time will go to the previous calendar day. Οι περιόδους λειτουργίας που ξεκινούν πριν από αυτή τη φορά θα μεταβούν στην προηγούμενη ημερολογιακή ημέρα. Session Storage Options Επιλογές αποθήκευσης συνόδων Compress SD Card Backups (slower first import, but makes backups smaller) Συμπίεση αντιγράφων κάρτας SD (πιο αργή εισαγωγή, αλλά μικρότερο αντίγραφο ασφαλείας) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Λάβετε υπόψη τις ημέρες με τη χρήση αυτή ως "μη συμβατές". 4 ώρες συνήθως θεωρούνται συμμορφούμενες. hours ώρες Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Ενεργοποίηση / απενεργοποίηση βελτιώσεων σήμανσης πειραματικών συμβάντων. Επιτρέπει την ανίχνευση οριακών συμβάντων και μερικά χάνονται από το μηχάνημα. Αυτή η επιλογή πρέπει να είναι ενεργοποιημένη πριν από την εισαγωγή, διαφορετικά απαιτείται καθαρισμός. Flow Restriction Περιορισμός ροής Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Ποσοστό περιορισμού στην ροή αέρα από τη μέση τιμή. Μια τιμή 20% λειτουργεί καλά για την ανίχνευση των άπνοιων. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Η προσαρμοσμένη επισήμανση είναι μια πειραματική μέθοδος ανίχνευσης συμβάντων που χάθηκαν από το μηχάνημα. Αυτοί είναι<span style=" text-decoration: underline;">δεν</span>περιλαμβάνονται στο AHI.</p></body></html> Duration of airflow restriction Διάρκεια περιορισμού ροής αέρα s s Event Duration Διάρκεια συμβάντος Allow duplicates near machine events. Αφήστε διπλότυπα κοντά στα συμβάντα του μηχανήματος. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Ρυθμίζει την ποσότητα δεδομένων που λαμβάνεται υπόψη για κάθε σημείο στο γράφημα AHI / Hour. Προεπιλογή έως 60 λεπτά .. Συνιστάται ιδιαίτερα να μείνει σε αυτήν την τιμή. minutes λεπτά Reset the counter to zero at beginning of each (time) window. Επαναφέρετε τον μετρητή στο μηδέν στην αρχή του κάθε παραθύρου (χρόνου). Zero Reset Μηδενική επαναφορά CPAP Clock Drift CPAP Ρολόγια Do not import sessions older than: Μην εισάγετε περιόδους μεγαλύτερες από: Sessions older than this date will not be imported Δεν θα εισαχθούν περιόδους μεγαλύτερης αυτής της ημερομηνίας dd MMMM yyyy dd MMMM yyyy User definable threshold considered large leak Το κατώφλι που ορίζεται από το χρήστη θεωρείται μεγάλη διαρροή Whether to show the leak redline in the leak graph Είτε πρόκειται να εμφανιστεί η κόκκινη γραμμή διαρροής στο γράφημα διαρροών Search Αναζήτηση &Oximetry &Οξυμετρία Show in Event Breakdown Piechart Εμφάνιση στην κατανομή συμβάντων Piechart Resync Machine Detected Events (Experimental) Επαναλαμβανόμενα συμβάντα ανίχνευσης μηχανής (πειραματικό) Percentage drop in oxygen saturation Ποσοστό πτώσης του κορεσμού οξυγόνου Pulse Σφυγμός Sudden change in Pulse Rate of at least this amount Ξαφνική αλλαγή του ρυθμού παλμού τουλάχιστον αυτού του ποσού bpm bpm Minimum duration of drop in oxygen saturation Ελάχιστη διάρκεια πτώσης του κορεσμού οξυγόνου Minimum duration of pulse change event. Ελάχιστη διάρκεια συμβάντος αλλαγής παλμού. Small chunks of oximetry data under this amount will be discarded. Μικρά τεμάχια δεδομένων οξυμετρίας κάτω από αυτό το ποσό θα απορριφθούν. &General &Γενικός Changes to the following settings needs a restart, but not a recalc. Αλλαγές στις ακόλουθες ρυθμίσεις απαιτούν επανεκκίνηση, αλλά όχι επανάληψη. Preferred Calculation Methods Προτιμώμενες μέθοδοι υπολογισμού Middle Calculations Μέσοι υπολογισμοί Upper Percentile Ανώτερο εκατοστημόριο Session Splitting Settings Ρυθμίσεις διαίρεσης περιόδου σύνδεσης <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Αυτή η ρύθμιση θα πρέπει να χρησιμοποιείται με προσοχή ...</span> Η απενεργοποίησή του έρχεται με συνέπειες που αφορούν την ακρίβεια των περιλήψεων μόνο ημερών, δεδομένου ότι ορισμένοι υπολογισμοί λειτουργούν μόνο σωστά υπό την προϋπόθεση ότι συνοψίζονται μόνο οι συνεδρίες που προέρχονται από αρχεία μεμονωμένων ημερών διατηρούνται μαζί.</p><p><span style=" font-weight:600;">Χρήστες ResMed:</span> Ακριβώς επειδή φαίνεται φυσικό για εσάς και εγώ ότι η επανεκκίνηση της περιόδου 12 μεσημέρι θα πρέπει να είναι την προηγούμενη ημέρα, δεν σημαίνει ότι τα δεδομένα του ResMed συμφωνούν μαζί μας. Η μορφή του δείκτη περίληψης STF.edf έχει σοβαρές αδυναμίες που κάνουν αυτό να μην είναι καλή ιδέα.</p><p>Αυτή η επιλογή υπάρχει για την ειρήνευση εκείνων που δεν ενδιαφέρονται και θέλουν να δουν αυτό &quot;σταθερό&quot; ανεξάρτητα από το κόστος, αλλά ξέρουν ότι έρχεται με ένα κόστος. Εάν κρατήσετε την κάρτα SD σας κάθε βράδυ και εισαγάγετε τουλάχιστον μία φορά την εβδομάδα, δεν θα δείτε προβλήματα με αυτό πολύ συχνά.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Μην διαχωρίσετε τις συνοπτικές ημέρες (Προειδοποίηση: διαβάστε την επεξήγηση εργαλείου!) Memory and Startup Options Επιλογές μνήμης και εκκίνησης Pre-Load all summary data at startup Προκαταλάβετε όλα τα συνοπτικά δεδομένα κατά την εκκίνηση <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Αυτή η ρύθμιση διατηρεί τα δεδομένα κυματομορφής και συμβάντων στη μνήμη μετά τη χρήση για να επιταχύνει την αναθεώρηση των ημερών. Αυτό δεν είναι πραγματικά μια απαραίτητη επιλογή, καθώς το λειτουργικό σας σύστημα αποθηκεύει προσωρινά τα αρχεία που χρησιμοποιούσατε. Η σύσταση είναι να την αφήσετε απενεργοποιημένη, εκτός εάν ο υπολογιστής σας έχει τόνο μνήμης.</p></body></html> Keep Waveform/Event data in memory Διατηρήστε δεδομένα κυματομορφής / συμβάντων στη μνήμη <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Μειώνει οποιουσδήποτε ασήμαντους διαλόγους επιβεβαίωσης κατά την εισαγωγή.</p></body></html> Import without asking for confirmation Εισαγωγή χωρίς να ζητήσετε επιβεβαίωση General CPAP and Related Settings Γενικά CPAP και συναφείς ρυθμίσεις Enable Unknown Events Channels Ενεργοποίηση καναλιών Άγνωστων συμβάντων AHI Apnea Hypopnea Index AHI RDI Respiratory Disturbance Index RDI AHI/Hour Graph Time Window Παράθυρο χρόνου χρονικού διαγράμματος AHI / ώρας Preferred major event index Προτιμώμενος δείκτης μεγάλων γεγονότων Compliance defined as Η συμμόρφωση ορίζεται ως Flag leaks over threshold Σημαίνει διαρροές πάνω από το όριο Seconds Δευτερόλεπτα <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Σημείωση: Αυτό δεν προορίζεται για διορθώσεις ζωνών ώρας! Βεβαιωθείτε ότι το ρολόι και η ζώνη ώρας του λειτουργικού σας συστήματος έχουν ρυθμιστεί σωστά.</p></body></html> Hours Ωρες For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Για λόγους συνέπειας, οι χρήστες της ResMed θα πρέπει να χρησιμοποιούν το 95% εδώ, καθώς αυτή είναι η μόνη διαθέσιμη τιμή σε ημερήσιες μόνο περιόδους. Median is recommended for ResMed users. Το Median συνιστάται για τους χρήστες του ResMed. Median Διάμεσος Weighted Average Σταθμισμένος μέσος όρος Normal Average Κανονική Μέση True Maximum Αληθές Μέγιστο 99% Percentile 99% εκατοστημόριο Maximum Calcs Μέγιστα Calcs General Settings Γενικές Ρυθμίσεις Daily view navigation buttons will skip over days without data records Τα κουμπιά πλοήγησης ημερήσιας προβολής θα παραλείψουν τις ημέρες χωρίς την εγγραφή δεδομένων Skip over Empty Days Παράλειψη στις ημέρες άδειες Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Επιτρέψτε τη χρήση πολλών πυρήνων CPU, όταν υπάρχουν, για να βελτιώσετε την απόδοση. Επηρεάζει κυρίως τον εισαγωγέα. Enable Multithreading Ενεργοποίηση της πολλαπλής επεξεργασίας Bypass the login screen and load the most recent User Profile Καταργήστε την οθόνη σύνδεσης και φορτώστε το πιο πρόσφατο προφίλ χρήστη Create SD Card Backups during Import (Turn this off at your own peril!) Δημιουργήστε αντίγραφα ασφαλείας καρτών SD κατά τη διάρκεια της εισαγωγής (Απενεργοποιήστε αυτό το δικό σας κίνδυνο!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Το πραγματικό μέγιστο είναι το μέγιστο του συνόλου δεδομένων. Το 99ο εκατοστημόριο φιλτράρει τα σπανιότερα άσχημα.</p></body></html> Combined Count divided by Total Hours Συνδυασμένη μέτρηση διαιρούμενη με Συνολικές ώρες Time Weighted average of Indice Χρόνος σταθμισμένος μέσος όρος του δείκτη Standard average of indice Τυπικός μέσος όρος δείκτη Custom CPAP User Event Flagging Προσαρμοσμένη καταγραφή συμβάντων χρήστη CPAP Events Εκδηλώσεις Reset &Defaults Επαναφορά &προεπιλογών <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Προειδοποίηση: </span>Ακριβώς επειδή μπορείτε, δεν σημαίνει ότι είναι καλή πρακτική.</p></body></html> Waveforms Κυματομορφές Flag rapid changes in oximetry stats Επισημάνετε ταχείες αλλαγές στα στατιστικά στοιχεία οξυμετρίας Other oximetry options Άλλες επιλογές οξυμετρίας Discard segments under Καταργήστε τμήματα κάτω από Flag Pulse Rate Above Σημαία παλμού σημαίας παραπάνω Flag Pulse Rate Below Ρυθμός παλμού σημαιών παρακάτω Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Συμπιέστε τα αντίγραφα ασφαλείας του ResMed (EDF) για να εξοικονομήσετε χώρο στο δίσκο. Τα υποστηριζόμενα αρχεία EDF αποθηκεύονται στη μορφή .gz, η οποία είναι κοινή στις πλατφόρμες Mac & Linux .. Το OSCAR μπορεί να εισάγει εγγενώς από αυτόν τον συμπιεσμένο κατάλογο backup. Για να το χρησιμοποιήσετε με το ResScan, θα χρειαστεί πρώτα να αποσυμπιεστούν τα αρχεία .gz. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Οι ακόλουθες επιλογές επηρεάζουν το μέγεθος του χώρου που χρησιμοποιεί το OSCAR στο δίσκο και επηρεάζουν τον χρόνο που απαιτείται για την εισαγωγή. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Αυτό καθιστά τα δεδομένα του OSCAR περίπου μισό. Αλλά κάνει την εισαγωγή και την αλλαγή ημέρας να διαρκέσει περισσότερο. Αν έχετε νέο υπολογιστή με μικρό δίσκο στερεάς κατάστασης, αυτή είναι μια καλή επιλογή. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Συμπίεση δεδομένων περιόδου σύνδεσης (καθιστά τα δεδομένα του OSCAR μικρότερα, αλλά η αλλαγή ημέρας είναι πιο αργή.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Αυτό διατηρεί ένα αντίγραφο ασφαλείας των δεδομένων καρτών SD για τις μηχανές ResMed, Οι μηχανές ResMed της σειράς S9 διαγράφουν δεδομένα υψηλής ανάλυσης μεγαλύτερα από 7 ημέρες, και δεδομένα γραφημάτων παλαιότερα από 30 ημέρες .. Το OSCAR μπορεί να διατηρήσει ένα αντίγραφο αυτών των δεδομένων αν χρειαστεί ποτέ να επανεγκαταστήσετε. (Συνιστάται εξαιρετικά, εκτός εάν ο μικρός χώρος στο δίσκο σας ή δεν ενδιαφέρεται για τα δεδομένα γραφήματος) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Κάνει την εκκίνηση του OSCAR λίγο πιο αργή, προφορτώνοντας εκ των προτέρων όλα τα συνοπτικά δεδομένα, γεγονός που επιταχύνει την περιήγηση σε γενικές γραμμές και μερικούς άλλους υπολογισμούς αργότερα. </p><p>IΑν έχετε μεγάλο όγκο δεδομένων, ίσως αξίζει τον κόπο να το απενεργοποιήσετε, αλλά αν θέλετε να βλέπετε <span style=" font-style:italic;">τα πάντα</span> στην επισκόπηση, όλα τα συνοπτικά δεδομένα πρέπει να φορτωθούν ούτως ή άλλως. </p> <p> Σημειώστε ότι αυτή η ρύθμιση δεν επηρεάζει δεδομένα κυματομορφής και συμβάντων, τα οποία είναι πάντα φορτωμένα με τη ζήτηση ανάλογα με τις ανάγκες.</p></body></html> This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Αυτός ο υπολογισμός απαιτεί να παρέχονται δεδομένα συνολικής διαρροής από τη μηχανή CPAP. (Π.χ. PRS1, αλλά όχι ResMed, η οποία έχει ήδη αυτά) Οι υπολογισμοί αθέλητης διαρροής που χρησιμοποιούνται εδώ είναι γραμμικοί, δεν μοντελοποιούν την καμπύλη εξαερισμού της μάσκας. Εάν χρησιμοποιείτε μερικές διαφορετικές μάσκες, επιλέξτε αντί για μέσες τιμές. Θα πρέπει να είναι αρκετά κοντά. Calculate Unintentional Leaks When Not Present Υπολογίστε αθέλητες διαρροές όταν δεν υπάρχουν 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Σημείωση: Χρησιμοποιείται μια μέθοδος γραμμικής υπολογισμού. Η αλλαγή αυτών των τιμών απαιτεί επανυπολογισμό. This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Αυτή η πειραματική επιλογή προσπαθεί να χρησιμοποιήσει το σύστημα εντοπισμού συμβάντων OSCAR για τη βελτίωση της τοποθέτησης συμβάντων που ανιχνεύθηκε από το μηχάνημα. Show flags for machine detected events that haven't been identified yet. Εμφανίστε σημαίες για συμβάντα που ανιχνεύθηκαν από μηχάνημα και δεν έχουν εντοπιστεί ακόμα. Show Remove Card reminder notification on OSCAR shutdown Εμφάνιση ειδοποίησης υπενθύμισης κατάργησης κάρτας στο τερματισμό λειτουργίας του OSCAR Check for new version every Ελέγξτε για κάθε νέα έκδοση days. ημέρες. Last Checked For Updates: Τελευταία Έλεγχος για ενημερώσεις: TextLabel Ετικέτα κειμένου I want to be notified of test versions. (Advanced users only please.) &Appearance &Εμφάνιση Graph Settings Ρυθμίσεις γραφήματος <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Ποια καρτέλα ανοίγει κατά τη φόρτωση ενός προφίλ. (Σημείωση: Το προεπιλεγμένο προφίλ θα είναι εάν το OSCAR δεν έχει ανοίξει ένα προφίλ κατά την εκκίνηση)</p></body></html> Bar Tops διάγραμμα ράβδων Line Chart Γράφημα γραμμής Overview Linecharts Επισκόπηση Linecharts Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Δοκιμάστε να την αλλάξετε από την προεπιλεγμένη ρύθμιση (Desktop OpenGL) αν αντιμετωπίζετε προβλήματα εμφάνισης με τα γραφήματα του OSCAR. <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Αυτό κάνει την κύλιση όταν διευρύνεται ευκολότερα στις ευαίσθητες επισημάνσεις διπλής κατεύθυνσης. 50ms είναι η συνιστώμενη τιμή.</p></body></html> How long you want the tooltips to stay visible. Πόσο καιρό θέλετε οι συμβουλές εργαλείων να παραμένουν ορατές. Scroll Dampening Μετακινηθείτε Tooltip Timeout Εργαλείο λήξης χρόνου Default display height of graphs in pixels Προεπιλεγμένο ύψος προβολής γραφικών σε εικονοστοιχεία Graph Tooltips Γραφήματα εργαλείων γραφημάτων The visual method of displaying waveform overlay flags. Η οπτική μέθοδος εμφάνισης σημαιών επικάλυψης κυματομορφής. Standard Bars ιστογράμματα Top Markers Κορυφαίοι δείκτες Graph Height Ύψος γραφικού Changing SD Backup compression options doesn't automatically recompress backup data. Η αλλαγή των επιλογών συμπίεσης SD Backup δεν επαναφέρει αυτόματα τα δεδομένα δημιουργίας αντιγράφων ασφαλείας. Auto-Launch CPAP Importer after opening profile Αυτόματη εκκίνηση του εισαγωγέα CPAP μετά το άνοιγμα του προφίλ Automatically load last used profile on start-up Αυτόματη φόρτωση του τελευταίου χρησιμοποιούμενου προφίλ κατά την εκκίνηση <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Παρέχετε μια ειδοποίηση κατά την εισαγωγή δεδομένων από οποιοδήποτε μοντέλο μηχανής που δεν έχει δοκιμαστεί ακόμα από προγραμματιστές του OSCAR.</p></body></html> Warn when importing data from an untested machine Προειδοποίηση κατά την εισαγωγή δεδομένων από μη ελεγμένο μηχάνημα <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>Παρέχετε μια ειδοποίηση κατά την εισαγωγή δεδομένων που είναι κάπως διαφορετικά από οτιδήποτε είχε παρατηρηθεί προηγουμένως από τους προγραμματιστές του OSCAR.</p></body></html> Warn when previously unseen data is encountered Προειδοποιήστε όταν έχουν εντοπιστεί δεδομένα που δεν έχουν καταγραφεί προηγουμένως Your masks vent rate at 20 cmH2O pressure Η μάσκα εξαερισμού ρυθμίζεται σε πίεση 20 cmH2O Your masks vent rate at 4 cmH2O pressure Η μάσκα σας εξαερισμού σε πίεση 4 cmH2O <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Σημείωση: </span>Λόγω περιορισμένων περιορισμών σχεδιασμού, τα μηχανήματα ResMed δεν υποστηρίζουν την αλλαγή αυτών των ρυθμίσεων.</p></body></html> Oximetry Settings Ρυθμίσεις οξυμετρίας Always save screenshots in the OSCAR Data folder Πάντα να αποθηκεύετε στιγμιότυπα οθόνης στο φάκελο OSCAR Data Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR On Opening Στο άνοιγμα Profile Προφίλ Welcome καλως ΗΡΘΑΤΕ Daily Καθημερινά Statistics Στατιστική Switch Tabs Μεταβείτε στις καρτέλες No change Καμία αλλαγή After Import Μετά την εισαγωγή Overlay Flags Σημαίες επικάλυψης Line Thickness Πάχος γραμμής The pixel thickness of line plots Το πάχος εικονοστοιχείων των διαγραμμάτων γραμμής Other Visual Settings Άλλες οπτικές ρυθμίσεις Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Το Anti-Aliasing εφαρμόζει εξομάλυνση στα γραφήματα γραφικών. Ορισμένα οικόπεδα φαίνονται πιο ελκυστικά με αυτό. Αυτό επηρεάζει επίσης τις εκτυπώσεις. Δοκιμάστε το και δείτε αν σας αρέσει. Use Anti-Aliasing Χρησιμοποιήστε το Anti-Aliasing Makes certain plots look more "square waved". Κάνει ορισμένα οικόπεδα να φαίνονται πιο "τετράγωνα κύματα". Square Wave Plots Τετράγωνο κύμα οικόπεδα Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Η προσωρινή αποθήκευση Pixmap είναι μια τεχνική επιτάχυνσης γραφικών. Μπορεί να προκαλέσει προβλήματα με την κατάρτιση γραμματοσειρών στην περιοχή εμφάνισης γραφικών στην πλατφόρμα σας. Use Pixmap Caching Χρήση της προσωρινής αποθήκευσης Pixmap <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html> <head/> <body> <p> Αυτά τα χαρακτηριστικά έχουν πρόσφατα κλαδευτεί. Θα επιστρέψουν αργότερα. </p> </body> </html> Animations && Fancy Stuff Κινούμενα Σχέδια και Γεγονότα Fancy Whether to allow changing yAxis scales by double clicking on yAxis labels Είτε πρόκειται να επιτρέψετε την αλλαγή των κλιμάκων άξονα y κάνοντας διπλό κλικ στις ετικέτες x Axis Allow YAxis Scaling Αφήστε την κλίμακα άξονα Y Whether to include machine serial number on machine settings changes report Το αν θα συμπεριληφθεί ο σειριακός αριθμός του μηχανήματος στις αλλαγές στις ρυθμίσεις του μηχανήματος Include Serial Number Συμπεριλάβετε τον σειριακό αριθμό Graphics Engine (Requires Restart) Μηχανή γραφικών (απαιτείται επανεκκίνηση) l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Fonts (Application wide settings) Γραμματοσειρές (ρυθμίσεις ευρείας εφαρμογής) Font Γραμματοσειρά Size Μέγεθος Bold Τολμηρός Italic Πλαίσιο Application Εφαρμογή Graph Text Γραφικό κείμενο Graph Titles Τίτλοι γραφημάτων Big Text Μεγάλο κείμενο Details Λεπτομέριες &Cancel &Ματαίωση &Ok &Εντάξει Name Ονομα Color Χρώμα Flag Type Τύπος σημαίας Label Επιγραφή CPAP Events Εκδηλώσεις CPAP Oximeter Events Οξύμετρο Γεγονότα Positional Events Θέματα γεγονότων Sleep Stage Events Εκδηλώσεις ύπνου Unknown Events Άγνωστα συμβάντα Double click to change the descriptive name this channel. Κάντε διπλό κλικ για να αλλάξετε το περιγραφικό όνομα αυτού του καναλιού. Double click to change the default color for this channel plot/flag/data. Κάντε διπλό κλικ για να αλλάξετε το προεπιλεγμένο χρώμα για αυτό το διάγραμμα / σημαία / δεδομένα του καναλιού. <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Παρακαλώ σημειώστε: </b> Οι προηγμένες δυνατότητες διάσπασης συνεδριών του OSCAR δεν είναι δυνατές με τα μηχανήματα <b> ResMed </b> λόγω περιορισμών στον τρόπο αποθήκευσης των ρυθμίσεων και των συνοπτικών δεδομένων τους έχει απενεργοποιηθεί για αυτό το προφίλ. </p> <p> Στις μηχανές ResMed, οι ημέρες <b> θα διαχωριστούν το μεσημέρι </b> όπως στο εμπορικό λογισμικό της ResMed. </p> %1 %2 %1 %2 Overview ΣΦΑΙΡΙΚΗ ΕΙΚΟΝΑ Double click to change the descriptive name the '%1' channel. Κάντε διπλό κλικ για να αλλάξετε το περιγραφικό όνομα το κανάλι '%1'. Whether this flag has a dedicated overview chart. Είτε αυτή η σημαία έχει ειδικό χάρτη επισκόπησης. Here you can change the type of flag shown for this event Εδώ μπορείτε να αλλάξετε τον τύπο της σημαίας που εμφανίζεται για αυτό το συμβάν This is the short-form label to indicate this channel on screen. Αυτή είναι η ετικέτα μικρής μορφής για την ένδειξη αυτού του καναλιού στην οθόνη. This is a description of what this channel does. Αυτή είναι μια περιγραφή του τι κάνει αυτό το κανάλι. Lower χαμηλότερος Upper Ανώτερος CPAP Waveforms CPAP κυματομορφές Oximeter Waveforms Ομομετρικά κύματα Positional Waveforms Θέση κυματομορφών Sleep Stage Waveforms Κυματομορφές φάσης ύπνου Whether a breakdown of this waveform displays in overview. Είτε υπάρχει ανάλυση της κυματομορφής αυτής σε επισκόπηση. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Εδώ μπορείτε να ορίσετε το κατώτατο όριο <b> χαμηλότερο </b> που χρησιμοποιείται για ορισμένους υπολογισμούς στην κυματομορφή %1 Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Εδώ μπορείτε να ορίσετε το κατώτατο όριο <b> άνω </b> που χρησιμοποιείται για ορισμένους υπολογισμούς στην κυματομορφή %1 Data Processing Required Απαιτείται επεξεργασία δεδομένων A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Για την εφαρμογή αυτών των αλλαγών απαιτείται διαδικασία επανάληψης / αποσυμπίεσης δεδομένων. Η ενέργεια αυτή μπορεί να διαρκέσει μερικά λεπτά για να ολοκληρωθεί. Είστε βέβαιοι ότι θέλετε να κάνετε αυτές τις αλλαγές; Data Reindex Required Απαιτείται αναδημιουργία δεδομένων A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Απαιτείται μια διαδικασία reindexing δεδομένων για την εφαρμογή αυτών των αλλαγών. Η ενέργεια αυτή μπορεί να διαρκέσει μερικά λεπτά για να ολοκληρωθεί. Είστε βέβαιοι ότι θέλετε να κάνετε αυτές τις αλλαγές; Restart Required Απαιτείται επανεκκίνηση One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Μία ή περισσότερες από τις αλλαγές που κάνατε θα απαιτήσουν την επανεκκίνηση αυτής της εφαρμογής, για να τεθούν σε ισχύ αυτές οι αλλαγές. Θα θέλατε να το κάνετε τώρα; If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Αν ποτέ χρειαστεί να επανεισάγετε ξανά αυτά τα δεδομένα (είτε στο OSCAR είτε στο ResScan), αυτά τα δεδομένα δεν θα επανέλθουν. If you need to conserve disk space, please remember to carry out manual backups. Εάν θέλετε να εξοικονομήσετε χώρο στο δίσκο, παρακαλούμε να θυμάστε να κάνετε χειροκίνητα αντίγραφα ασφαλείας. Are you sure you want to disable these backups? Είστε βέβαιοι ότι θέλετε να απενεργοποιήσετε αυτά τα αντίγραφα ασφαλείας; Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Η απενεργοποίηση των αντιγράφων ασφαλείας δεν είναι καλή ιδέα, επειδή το OSCAR τις χρειάζεται για την ανασύσταση της βάσης δεδομένων αν εντοπιστούν σφάλματα. Are you really sure you want to do this? Είστε σίγουροι ότι θέλετε να το κάνετε αυτό; Flag Σημαία Minor Flag Μικρή σημαία Span Σπιθαμή Always Minor Πάντα Μικρά No CPAP machines detected Δεν εντοπίστηκαν μηχανές CPAP Will you be using a ResMed brand machine? Θα χρησιμοποιείτε μια μηχανή μάρκας ResMed; Never Ποτέ This may not be a good idea Αυτό μπορεί να μην είναι μια καλή ιδέα ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Οι μηχανές ResMed S9 διαγράφουν συστηματικά ορισμένα δεδομένα από την κάρτα SD παλαιότερα των 7 και 30 ημερών (ανάλογα με την ανάλυση). ProfileSelector Filter: Φίλτρο: Reset filter to see all profiles Επαναφέρετε το φίλτρο για να δείτε όλα τα προφίλ Version Εκδοχή &Open Profile &Ανοίξτε το προφίλ &Edit Profile &Επεξεργασία προφίλ &New Profile &Νέο προφίλ Profile: None Προφίλ: Κανένα Please select or create a profile... Επιλέξτε ή δημιουργήστε ένα προφίλ ... Destroy Profile Καταστρέψτε το προφίλ Profile Προφίλ Ventilator Brand Φίλτρο ανεμιστήρα Ventilator Model Μοντέλο ανεμιστήρων Other Data Άλλα δεδομένα Last Imported Τελευταία εισαγωγή Name Ονομα You must create a profile Πρέπει να δημιουργήσετε ένα προφίλ Enter Password for %1 Εισαγάγετε τον κωδικό πρόσβασης για %1 You entered an incorrect password Εισαγάγετε έναν εσφαλμένο κωδικό πρόσβασης Forgot your password? Ξεχάσατε τον κωδικό σας; Ask on the forums how to reset it, it's actually pretty easy. Ρωτήστε στα φόρουμ πώς να το επαναφέρετε, είναι πραγματικά πολύ εύκολο. Select a profile first Επιλέξτε πρώτα ένα προφίλ The selected profile does not appear to contain any data and cannot be removed by OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Αν προσπαθείτε να διαγράψετε επειδή ξεχάσατε τον κωδικό πρόσβασης, πρέπει είτε να το επαναφέρετε είτε να διαγράψετε το φάκελο προφίλ με μη αυτόματο τρόπο. You are about to destroy profile '<b>%1</b>'. Πρόκειται να καταστρέψετε το προφίλ '<b> %1 </b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Σκεφτείτε προσεκτικά, καθώς αυτό θα διαγράψει ανεπανόρθωτα το προφίλ μαζί με όλα τα <b>δεδομένα αντιγραφής</b> που είναι αποθηκευμένα κάτω από το <br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Εισαγάγετε τη λέξη <b>ΔΙΑΓΡΑΦΩ</b> παρακάτω (ακριβώς όπως φαίνεται) για επιβεβαίωση. DELETE ΔΙΑΓΡΑΦΩ Sorry Συγνώμη You need to enter DELETE in capital letters. Πρέπει να εισάγετε ΔΙΑΓΡΑΦΩ με κεφαλαία γράμματα. There was an error deleting the profile directory, you need to manually remove it. Παρουσιάστηκε σφάλμα κατά τη διαγραφή του καταλόγου προφίλ, πρέπει να το καταργήσετε με μη αυτόματο τρόπο. Profile '%1' was succesfully deleted Το προφίλ '%1' διαγράφηκε με επιτυχία Bytes Bytes KB KB MB MB GB GB TB TB PB PB Summaries: Σύνοψη: Events: Εκδηλώσεις: Backups: Δημιουργία αντιγράφων ασφαλείας: Hide disk usage information Απόκρυψη πληροφοριών χρήσης δίσκου Show disk usage information Εμφάνιση πληροφοριών χρήσης δίσκου Name: %1, %2 Ονομα: %1, %2 Phone: %1 Τηλέφωνο: %1 Email: <a href='mailto:%1'>%1</a> ΗΛΕΚΤΡΟΝΙΚΗ ΔΙΕΥΘΥΝΣΗ: <a href='mailto:%1'>%1</a> Address: Διεύθυνση: No profile information given Δεν έχουν δοθεί πληροφορίες προφίλ Profile: %1 Προφίλ: %1 ProgressDialog Abort Αποβάλλω QObject No Data Χωρίς δεδομένα Events Εκδηλώσεις Duration Διάρκεια (% %1 in events) (% %1 σε εκδηλώσεις) Jan Ιανουάριο Feb Φεβρουάριος Mar Μάρτιος Apr Απρ May Μάης Jun Ιούνιος Jul Ιουλ Aug Αυγ Sep Σεπ Oct Οκτ Nov Νοέμβριος Dec Δεκ ft ft lb lb oz oz Kg kg cmH2O cmH2O Med. Διάμεσος Min: %1 Ελάχιστη: %1 Min: Ελάχιστη: Max: Μέγιστη: Max: %1 Μέγιστη: %1 %1 (%2 days): %1 (%2 ημέρες): %1 (%2 day): %1 (%2 ημέρα): % in %1 % σε %1 Hours Ωρες Min %1 Ελάχιστο %1 Hours: %1 Ωρες: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 χαμηλή χρήση, %2 καμία χρήση, εκτός %3 ημέρες (%4% υποχωρητικός). Μήκος: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Συνεδρίες: %1 / %2 / %3 Μήκος: %4 / %5 / %6 Μακρύτερα: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Μήκος: %3 Αρχή: %2 Mask On Μάσκα ενεργοποιημένο Mask Off Μάσκα απενεργοποιημένη %1 Length: %3 Start: %2 %1 Μήκος: %3 Αρχή: %2 TTIA: TTIA: TTIA: %1 TTIA: %1 Minutes Λεπτά Seconds Δευτερόλεπτα h h m m s s ms ms Events/hr Εκδηλώσεις / ώρα Hz Hz bpm bpm Litres Λίτρα ml ml Breaths/min Αναπνοές / λεπτό ? ? Severity (0-1) Σοβαρότητα (0-1) Degrees Βαθμοί Error Λάθος Warning Προειδοποίηση Information Πληροφορίες Busy Απασχολημένος Please Note Παρακαλώ σημειώστε Graphs Switched Off Τα γραφήματα είναι απενεργοποιημένα Sessions Switched Off Οι περίοδοι σύνδεσης απενεργοποιήθηκαν &Yes &Ναί &No &Οχι &Cancel &Ματαίωση &Destroy &Καταστρέφω &Save &Αποθηκεύσετε BMI BMI Weight Βάρος Zombie Βρυκόλακας Pulse Rate Καρδιακός σφυγμός Plethy Πλήθος Pressure Πίεση Daily Καθημερινά Profile Προφίλ Overview επισκόπηση Oximetry Οξυμετρία Oximeter Οξύμετρο Event Flags Σημαίες συμβάντων Default Προκαθορισμένο CPAP CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP Min EPAP Ελάχιστο EPAP Max EPAP Μέγιστη EPAP IPAP IPAP Min IPAP Ελάχιστο IPAP Max IPAP Μέγιστη IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Υγραντήρας H H OA OA A A CA CA FL FL SA SA LE LE EP EP VS VS VS2 VS2 RERA RERA PP PP P P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF1 UF2 UF2 UF3 UF3 PS PS AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI EPI PB PB IE IE Insp. Time Ώρα εισπνοής Exp. Time Χρόνος λήξης Resp. Event Αναπνευστικό συμβάν Flow Limitation Περιορισμός ροής Flow Limit Όριο ροής SensAwake SensAwake Pat. Trig. Breath Ελαφρό κτύπημα. Κομψός. Αναπνοή Tgt. Min. Vent Στόχευση λεπτών εξαερισμού Target Vent. Στόχευση εξαερισμού. Minute Vent. Λεπτό άνεμο. Tidal Volume Παλιρροιακός Όγκος Resp. Rate Ρυθμός αναπνοής Snore Ροχαλίζω Leak Διαρροή Leaks Διαρροές Large Leak Μεγάλη διαρροή LL LL Total Leaks Συνολικές διαρροές Unintentional Leaks Μη σκόπιμες διαρροές MaskPressure Μάσκα Πίεση Flow Rate Ρυθμός ροής Sleep Stage Φάση ύπνου Usage Χρήση Sessions Συνεδρίες Pr. Relief Ανακούφιση πίεσης No Data Available Δεν υπάρχουν διαθέσιμα δεδομένα App key: Κλειδί εφαρμογής: Operating system: Λειτουργικό σύστημα: Built with Qt %1 on %2 Κατασκευάστηκε με Qt %1 στο %2 Graphics Engine: Μηχανή γραφικών: Graphics Engine type: Γραφικά Τύπος κινητήρα: Software Engine Μηχανή Λογισμικού ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Desktop OpenGL m m cm cm in Only Settings and Compliance Data Available Summary Data Only Bookmarks Σελιδοδείκτες Mode Τρόπος Model Μοντέλο Brand Μάρκα Serial Αύξων αριθμός Series Σειρά Machine Μηχανή Channel Κανάλι Settings Ρυθμίσεις Inclination Κλίση Orientation Προσανατολισμός Motion Κίνηση Name Ονομα DOB Ημερομηνια γεννησης Phone Τηλέφωνο Address Διεύθυνση Email διεύθυνση ηλεκτρονικού ταχυδρομείου Patient ID Αναγνωριστικό ασθενούς Date Ημερομηνία Bedtime Ωρα ύπνου Wake-up Ξύπνα Mask Time Μάσκα Ώρα Unknown Αγνωστος None Κανένας Ready Ετοιμος First Πρώτα Last τελευταίος Start Αρχή End Τέλος On Επί Off Μακριά από Yes Ναί No Οχι Min Ελάχιστο Max ανώτατο όριο Med Μεσαίο Average Μέση τιμή Median Διάμεσος Avg Μέγ W-Avg Σταθμισμένος μέσος όρος Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine Μηχανή μη ικανή για δεδομένα Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Ετοιμάζομαι... Machine Unsupported Μη υποστηριζόμενη μηχανή I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Λυπούμαστε, το μηχάνημά σας Philips Respironics CPAP (Μοντέλο% 1) δεν υποστηρίζεται ακόμη. Λυπούμαστε που αναφέρετε ότι το OSCAR μπορεί να παρακολουθεί μόνο τις ώρες χρήσης και τις πολύ βασικές ρυθμίσεις για αυτό το μηχάνημα. Scanning Files... Σάρωση αρχείων ... Importing Sessions... Εισαγωγή περιόδων σύνδεσης ... Finishing up... Τελειώνω... Machine Untested Μη ελεγχθείσα μηχανή Flex Lock Flex Lock Whether Flex settings are available to you. Είτε υπάρχουν ρυθμίσεις Flex για εσάς. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Το χρονικό διάστημα που απαιτείται για τη μετάβαση από το EPAP στο IPAP, τόσο μεγαλύτερος είναι ο αριθμός, τόσο πιο αργή είναι η μετάβαση Rise Time Lock Αύξηση χρόνου κλειδώματος Whether Rise Time settings are available to you. Εάν οι ρυθμίσεις ώρας ανόδου είναι διαθέσιμες σε εσάς. Rise Lock Rise Lock Mask Resistance Setting Ρύθμιση αντίστασης μάσκας Mask Resist. Mask Resist. Hose Diam. Διάμετρος σωλήνα. 15mm 15 mm 22mm 22 mm Backing Up Files... Δημιουργία αντιγράφων ασφαλείας αρχείων ... Untested Data Μη ελεγμένα δεδομένα model %1 DreamStation 2 unknown model CPAP-Check CPAP-Check AutoCPAP AutoCPAP Auto-Trial Auto-Trial AutoBiLevel AutoBiLevel S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Mode Λειτουργία Flex PRS1 pressure relief mode. Λειτουργία ανακούφισης πίεσης PRS1. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time Αύξηση χρόνου Bi-Flex Bi-Flex Flex Flex Level Επίπεδο Flex PRS1 pressure relief setting. Ρύθμιση πίεσης PRS1. Passover Target Time PRS1 Humidifier Target Time Hum. Tgt Time Tubing Type Lock Κλείδωμα τύπου σωλήνα Whether tubing type settings are available to you. Είτε οι ρυθμίσεις τύπου σωλήνωσης είναι διαθέσιμες σε εσάς. Tube Lock Κλείδωμα σωλήνα Mask Resistance Lock Κλείδωμα αντίστασης μάσκας Whether mask resistance settings are available to you. Εάν έχετε στη διάθεσή σας ρυθμίσεις αντοχής στη μάσκα. Mask Res. Lock Κλείδωμα αντίστασης μάσκας Whether or not machine shows AHI via built-in display. Είτε το μηχάνημα δείχνει AHI μέσω ενσωματωμένης οθόνης. Ramp Type Τύπος ράμπας Type of ramp curve to use. Τύπος καμπύλης ράμπας για χρήση. Linear Γραμμικός SmartRamp SmartRamp Ramp+ Backup Breath Mode Λειτουργία αναπνοής δημιουργίας αντιγράφων ασφαλείας The kind of backup breath rate in use: none (off), automatic, or fixed Το είδος της εφεδρικής αναπνοής κατά τη χρήση: καμία (εκτός λειτουργίας), αυτόματη ή σταθερή Breath Rate Ποσοστό αναπνοής Fixed Σταθερός Fixed Backup Breath BPM Σταθερή εφεδρική αναπνοή BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Οι ελάχιστες αναπνοές ανά λεπτό (BPM) κάτω από τις οποίες θα ξεκινήσει μια χρονική αναπνοή Breath BPM Breath BPM Timed Inspiration Προσωρινή Έμπνευση The time that a timed breath will provide IPAP before transitioning to EPAP Ο χρόνος που μια χρονική αναπνοή θα παρέχει IPAP πριν από τη μετάβαση στην EPAP Timed Insp. Προσωρινή Έμπνευση Auto-Trial Duration Διάρκεια αυτόματης δοκιμής The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Ο αριθμός ημερών στη δοκιμαστική περίοδο Auto-CPAP, μετά την οποία το μηχάνημα θα επανέλθει στην CPAP Auto-Trial Dur. Διάρκεια αυτόματης δοκιμής EZ-Start EZ-Start Whether or not EZ-Start is enabled Είτε ενεργοποιείται το EZ-Start είτε όχι Variable Breathing Μεταβλητή αναπνοή UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend UNCONFIRMED: Πιθανώς μεταβλητή αναπνοή, που είναι περιόδους υψηλής απόκλισης από την κορυφαία τάση εισπνοής ροής Peak Flow Peak flow during a 2-minute interval Humidifier Status Κατάσταση υγραντήρα PRS1 humidifier connected? Έχει συνδεθεί ο υγραντήρας PRS1; Disconnected Ασύνδετος Connected Συνδεδεμένος Humidification Mode Λειτουργία υγρασίας PRS1 Humidification Mode Λειτουργία υγρασίας PRS1 Humid. Mode Λειτουργία υγρασίας Fixed (Classic) Σταθερό (κλασικό) Adaptive (System One) Προσαρμοστικό (Σύστημα Ένα) Heated Tube Θερμαινόμενος σωλήνας Tube Temperature Θερμοκρασία σωλήνα PRS1 Heated Tube Temperature Θερμοκρασία θερμαινόμενου σωλήνα PRS1 Tube Temp. Θερμοκρασία σωλήνα PRS1 Humidifier Setting Ρύθμιση υγραντήρα PRS1 Hose Diameter Διάμετρος σωλήνα Diameter of primary CPAP hose Διάμετρος του κύριου εύκαμπτου σωλήνα CPAP 12mm 12mm Auto On Auto On A few breaths automatically starts machine Μερικές αναπνοές ξεκινούν αυτόματα μηχανή Auto Off Auto Off Machine automatically switches off Το μηχάνημα απενεργοποιείται αυτόματα Mask Alert Προειδοποίηση μάσκας Whether or not machine allows Mask checking. Εάν το μηχάνημα επιτρέπει τον έλεγχο της μάσκας. Show AHI Εμφάνιση AHI Breathing Not Detected Η αναπνοή δεν εντοπίστηκε A period during a session where the machine could not detect flow. Μια περίοδος κατά τη διάρκεια μιας περιόδου λειτουργίας όπου το μηχάνημα δεν μπόρεσε να εντοπίσει ροή. BND BND Timed Breath Χρονική αναπνοή Machine Initiated Breath Μηχανική διέγερση της αναπνοής TB TB Windows User Χρήστης των Windows Using Χρησιμοποιώντας , found SleepyHead - , βρέθηκε SleepyHead - You must run the OSCAR Migration Tool Πρέπει να εκτελέσετε το Εργαλείο μετεγκατάστασης OSCAR <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i> Τα παλιά δεδομένα του μηχανήματος θα πρέπει να αναγεννηθούν, υπό την προϋπόθεση ότι αυτή η δυνατότητα δημιουργίας αντιγράφων ασφαλείας δεν έχει απενεργοποιηθεί στις προτιμήσεις κατά τη διάρκεια προηγούμενης εισαγωγής δεδομένων. </i> Launching Windows Explorer failed Η εκκίνηση της Εξερεύνησης των Windows απέτυχε Could not find explorer.exe in path to launch Windows Explorer. Δεν βρέθηκε το explorer.exe στη διαδρομή για την εκκίνηση της Εξερεύνησης των Windows. OSCAR %1 needs to upgrade its database for %2 %3 %4 Το OSCAR%1 πρέπει να αναβαθμίσει τη βάση δεδομένων του για %2 %3 %4 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b> Το OSCAR διατηρεί ένα αντίγραφο ασφαλείας της κάρτας δεδομένων των συσκευών που χρησιμοποιεί για το σκοπό αυτό. </b> OSCAR does not yet have any automatic card backups stored for this device. Το OSCAR δεν διαθέτει ακόμη αυτόματα αποθηκευμένα αντίγραφα ασφαλείας για αυτήν τη συσκευή. This means you will need to import this machine data again afterwards from your own backups or data card. Αυτό σημαίνει ότι στη συνέχεια θα χρειαστεί να εισαγάγετε αυτό το μηχάνημα από τα δικά σας αντίγραφα ασφαλείας ή την κάρτα δεδομένων. Important: Σπουδαίος: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Εάν ανησυχείτε, κάντε κλικ στο Όχι για να τερματίσετε και να δημιουργήσετε αντίγραφο ασφαλείας του προφίλ σας με μη αυτόματο τρόπο πριν ξεκινήσετε ξανά το OSCAR. Are you ready to upgrade, so you can run the new version of OSCAR? Είστε έτοιμοι για αναβάθμιση, ώστε να μπορείτε να εκτελέσετε τη νέα έκδοση του OSCAR; Sorry, the purge operation failed, which means this version of OSCAR can't start. Λυπούμαστε, η διαδικασία καθαρισμού απέτυχε, πράγμα που σημαίνει ότι αυτή η έκδοση του OSCAR δεν μπορεί να ξεκινήσει. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Θα θέλατε να ενεργοποιήσετε τα αυτόματα αντίγραφα ασφαλείας, έτσι ώστε την επόμενη φορά που χρειάζεται μια νέα έκδοση του OSCAR, μπορεί να ξαναδοκιμάσει από αυτά; OSCAR will now start the import wizard so you can reinstall your %1 data. Το OSCAR θα ξεκινήσει τώρα τον οδηγό εισαγωγής ώστε να μπορέσετε να εγκαταστήσετε ξανά τα δεδομένα σας %1. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Το OSCAR θα τερματίσει τώρα και, στη συνέχεια, θα προσπαθήσει να ξεκινήσει τον διαχειριστή αρχείων των υπολογιστών σας, ώστε να μπορέσετε να στηρίξετε το προφίλ σας με μη αυτόματο τρόπο: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Χρησιμοποιήστε το διαχειριστή αρχείων για να δημιουργήσετε ένα αντίγραφο του καταλόγου προφίλ σας, στη συνέχεια, στη συνέχεια, κάντε επανεκκίνηση του OSCAR και ολοκληρώστε τη διαδικασία αναβάθμισης. Machine Database Changes Αλλαγές βάσεων δεδομένων μηχανών Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Μόλις αναβαθμίσετε, <font size=+1> δεν μπορείτε </font> να χρησιμοποιήσετε πάλι αυτό το προφίλ με την προηγούμενη έκδοση. The machine data folder needs to be removed manually. Ο φάκελος δεδομένων του μηχανήματος πρέπει να αφαιρεθεί με μη αυτόματο τρόπο. This folder currently resides at the following location: Αυτός ο φάκελος βρίσκεται αυτή τη στιγμή στην ακόλουθη τοποθεσία: Rebuilding from %1 Backup Επανακατασκευή από %1 δημιουργία αντιγράφων ασφαλείας Therapy Pressure Πίεση Θεραπείας Inspiratory Pressure Εισπνευστική πίεση Lower Inspiratory Pressure Κάτω εισπνευστική πίεση Higher Inspiratory Pressure Υψηλότερη εισπνευστική πίεση Expiratory Pressure Εκπνευστική Πίεση Lower Expiratory Pressure Κάτω εκπνευστική πίεση Higher Expiratory Pressure Υψηλότερη αναπνευστική πίεση Pressure Support Υποστήριξη πίεσης PS Min PS Min Pressure Support Minimum Ελάχιστη υποστήριξη πίεσης PS Max PS Max Pressure Support Maximum Μέγιστη υποστήριξη πίεσης Min Pressure Πίεση μου Minimum Therapy Pressure Ελάχιστη πίεση θεραπείας Max Pressure Μέγιστη πίεση Maximum Therapy Pressure Μέγιστη πίεση θεραπείας Ramp Time Ώρα ράμπας Ramp Delay Period Περίοδος καθυστέρησης Ramp Ramp Pressure Πίεση ράμπας Starting Ramp Pressure Έναρξη πίεσης ράμπας Ramp Event Γεγονός Ramp Ramp Αναβαθμίδα Vibratory Snore (VS2) Δονητική φλύκταινα (VS2) A ResMed data item: Trigger Cycle Event Ένα στοιχείο δεδομένων ResMed: Event Cycle Trigger Mask On Time Μάσκα σε ώρα Time started according to str.edf Ο χρόνος ξεκίνησε σύμφωνα με το str.edf Summary Only Περίληψη μόνο An apnea where the airway is open Μια άπνοια όπου ο αεραγωγός είναι ανοιχτός An apnea caused by airway obstruction Άπνοια προκαλούμενη από απόφραξη των αεραγωγών Hypopnea Υπόπνοια A partially obstructed airway Ένας μερικώς παρεμποδισμένος αεραγωγός Unclassified Apnea Μη ταξινομημένη άπνοια UA UA Vibratory Snore Δονητικό φίδι A vibratory snore Ένα δονητικό ροχαλητό A vibratory snore as detcted by a System One machine Ένα δονητικό ροχαλητό όπως ανιχνεύεται από ένα μηχάνημα System One Pressure Pulse Πίεση πίεσης A pulse of pressure 'pinged' to detect a closed airway. Ένας παλμός πίεσης 'pinged' για να ανιχνεύσει έναν κλειστό αεραγωγό. A large mask leak affecting machine performance. Μια μεγάλη διαρροή μάσκας που επηρεάζει την απόδοση της μηχανής. Non Responding Event Μη ανταποκρινόμενο συμβάν A type of respiratory event that won't respond to a pressure increase. Ένας τύπος αναπνευστικού συμβάντος που δεν ανταποκρίνεται σε αύξηση της πίεσης. Expiratory Puff Εκπνευσμένη ριπή Intellipap event where you breathe out your mouth. Event Intellipap όπου εκπνέετε το στόμα σας. SensAwake feature will reduce pressure when waking is detected. Η λειτουργία SensAwake θα μειώσει την πίεση κατά την ανίχνευση ξυπνητηριού. User Flag #1 Σημαία χρήστη #1 User Flag #2 Σημαία χρήστη #2 User Flag #3 Σημαία χρήστη #3 Heart rate in beats per minute Καρδιακός ρυθμός σε παλμούς ανά λεπτό Blood-oxygen saturation percentage Ποσοστό κορεσμού οξυγόνου-οξυγόνου Plethysomogram Πλεισματολογία An optical Photo-plethysomogram showing heart rhythm Ένα οπτικό φωτοφραγματογράφημα που δείχνει καρδιακό ρυθμό Pulse Change Αλλαγή παλμού A sudden (user definable) change in heart rate Μια ξαφνική (καθορίσιμη από το χρήστη) αλλαγή στον καρδιακό ρυθμό SpO2 Drop SpO2 Drop A sudden (user definable) drop in blood oxygen saturation Μια ξαφνική (καθορίσιμη από το χρήστη) πτώση του κορεσμού οξυγόνου αίματος SD SD Breathing flow rate waveform Κοιλιακή μορφή ρυθμού ροής αναπνοής L/min L/min Mask Pressure Μάσκα Πίεση Amount of air displaced per breath Ποσότητα αέρα που μετατοπίζεται ανά αναπνοή Graph displaying snore volume Γραφή που εμφανίζει όγκο ροχαλητού Minute Ventilation Εξαερισμός λεπτών Amount of air displaced per minute Ποσότητα αέρα που μετατοπίζεται ανά λεπτό Respiratory Rate Ρυθμός αναπνοής Rate of breaths per minute Ρυθμός αναπνοών ανά λεπτό Patient Triggered Breaths Διαταραχές της αναπνοής του ασθενούς Percentage of breaths triggered by patient Ποσοστό αναπνοών που προκαλούνται από τον ασθενή Pat. Trig. Breaths Διαταραχές της αναπνοής του ασθενούς Leak Rate Ποσοστό διαρροών Rate of detected mask leakage Ποσοστό ανίχνευσης διαρροής μάσκας I:E Ratio Ι:Ε Αναλογία Ratio between Inspiratory and Expiratory time Αναλογία μεταξύ του χρόνου εισπνοής και της εκπνοής ratio αναλογία Pressure Min Πίεση Ελάχ Pressure Max Μέγιστη πίεση Pressure Set Ρύθμιση πίεσης Pressure Setting Ρύθμιση πίεσης IPAP Set Ρύθμιση IPAP IPAP Setting Ρύθμιση IPAP EPAP Set Ρύθμιση EPAP EPAP Setting Ρύθμιση EPAP Cheyne Stokes Respiration Cheyne Stokes Αναπνοή An abnormal period of Cheyne Stokes Respiration Μια ανώμαλη περίοδος αναπνοής του Cheyne Stokes CSR CSR Periodic Breathing Περιοδική αναπνοή An abnormal period of Periodic Breathing Μία ανώμαλη περίοδος περιοδικής αναπνοής Clear Airway Άνοιγμα των αεραγωγών Obstructive Κωλυσιεργικός An apnea that couldn't be determined as Central or Obstructive. Μια άπνοια που δεν μπορούσε να προσδιοριστεί ως Κεντρική ή Αποφρακτική. A restriction in breathing from normal, causing a flattening of the flow waveform. Ένας περιορισμός στην αναπνοή από το φυσιολογικό, προκαλώντας μια ισοπέδωση της κυματομορφής ροής. Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Δύσπνοια που σχετίζεται με την αναπνευστική προσπάθεια: Περιορισμός στην αναπνοή που προκαλεί είτε διαταραχή αφύπνισης είτε ύπνου. Leak Flag Διαρροή Σημαία LF LF A user definable event detected by OSCAR's flow waveform processor. Ένα συμβάν καθορισμένο από το χρήστη ανιχνεύεται από τον επεξεργαστή κυματομορφής ροής του OSCAR. Perfusion Index Δείκτης διάχυσης A relative assessment of the pulse strength at the monitoring site Σχετική εκτίμηση της αντοχής των παλμών στη θέση παρακολούθησης Perf. Index % Δείκτης διάχυσης % Mask Pressure (High frequency) Πίεση μάσκας (υψηλή συχνότητα) Expiratory Time Χρόνος εκπνοής Time taken to breathe out Χρόνος που απαιτείται για να αναπνέει Inspiratory Time Εμπνευσμένος χρόνος Time taken to breathe in Χρόνος που απαιτείται για να εισπνεύσουμε Respiratory Event Αναπνευστικό συμβάν Graph showing severity of flow limitations Γράφημα που δείχνει τη σοβαρότητα των περιορισμών ροής Flow Limit. Όριο ροής. Target Minute Ventilation Στόχευση λεπτού εξαερισμού Maximum Leak Μέγιστη διαρροή The maximum rate of mask leakage Το μέγιστο ποσοστό διαρροής μάσκας Max Leaks Μέγιστη διαρροή Apnea Hypopnea Index Δείκτης Υπερπνοίας Άπνοιας Graph showing running AHI for the past hour Γράφημα που δείχνει την τρέχουσα AHI για την τελευταία ώρα Total Leak Rate Συνολικό ποσοστό διαρροής Detected mask leakage including natural Mask leakages Ανίχνευση διαρροής μάσκας, συμπεριλαμβανομένων φυσικών διαρροών μάσκας Median Leak Rate Διάμεσος ρυθμός διαρροής Median rate of detected mask leakage Μέσος ρυθμός ανίχνευσης διαρροής μάσκας Median Leaks Μέσες διαρροές Respiratory Disturbance Index Δείκτης Αναπνευστικής Διαταραχής Graph showing running RDI for the past hour Γράφημα που δείχνει την τρέχουσα RDI για την τελευταία ώρα Sleep position in degrees Θέση ύπνου σε μοίρες Upright angle in degrees Όρθια γωνία σε μοίρες Movement Κίνηση Movement detector Ανιχνευτής κίνησης CPAP Session contains summary data only Η περίοδος CPAP περιέχει μόνο σύνοψη δεδομένων PAP Mode Λειτουργία PAP Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Apnea An apnea reportred by your CPAP machine. PAP Device Mode Λειτουργία συσκευής PAP APAP (Variable) APAP (μεταβλητή) ASV (Fixed EPAP) ASV (Σταθερό EPAP) ASV (Variable EPAP) ASV (μεταβλητή EPAP) Height Υψος Physical Height Φυσικό Ύψος Notes Σημειώσεις Bookmark Notes Σημειώσεις σημειώσεων Body Mass Index Δείκτη μάζας σώματος How you feel (0 = like crap, 10 = unstoppable) Πώς νιώθεις (0 = σαν χάλια, 10 = ασταμάτητη) Bookmark Start Σημειώστε την έναρξη Bookmark End Σελιδοδείκτης Τέλος Last Updated Τελευταία ενημέρωση Journal Notes Σημειώσεις περιοδικών Journal Ημερολόγιο διάφορων πράξεων 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1 = Ξυπνήστε 2 = REM 3 = Ελαφρύς ύπνος 4 = Deep Sleep Brain Wave Κύμα του εγκεφάλου BrainWave BrainWave Awakenings Αφυπνίσεις Number of Awakenings Αριθμός Αφυπνίσεων Morning Feel Πρωινή αίσθηση How you felt in the morning Πώς νιώσατε το πρωί Time Awake Ώρα Ξυπνήστε Time spent awake Ώρα ξοδευμένος Time In REM Sleep Ώρα σε ύπνο REM Time spent in REM Sleep Χρόνος που δαπανάται στο REM Sleep Time in REM Sleep Ώρα σε ύπνο REM Time In Light Sleep Χρόνος στην ανοιχτή νύχτα Time spent in light sleep Χρόνος που ξοδεύεται σε ανοιχτό ύπνο Time in Light Sleep Ώρα στην Ελαφριά ύπνο Time In Deep Sleep Χρόνος σε βαθύ ύπνο Time spent in deep sleep Ο χρόνος που περνάει σε βαθύ ύπνο Time in Deep Sleep Ώρα σε βαθιά ύπνο Time to Sleep Ωρα για ύπνο Time taken to get to sleep Ώρα για να κοιμηθείς Zeo ZQ Zeo ZQ Zeo sleep quality measurement Μέτρηση ποιότητας ύπνου Zeo ZEO ZQ ZEO ZQ Debugging channel #1 Κανάλι εντοπισμού σφαλμάτων # 1 Test #1 Δοκιμή # 1 For internal use only Debugging channel #2 Κανάλι εντοπισμού σφαλμάτων # 2 Test #2 Δοκιμή # 2 Zero Μηδέν Upper Threshold Ανώτερο όριο Lower Threshold Κάτω όριο As you did not select a data folder, OSCAR will exit. Καθώς δεν επιλέξατε φάκελο δεδομένων, το OSCAR θα εξέλθει. or CANCEL to skip migration. ή ΑΚΥΡΩΣΗ για να παρακάμψετε τη μετάβαση. Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. You cannot use this folder: Δεν μπορείτε να χρησιμοποιήσετε αυτόν το φάκελο: Migrating Μετεγκατάσταση files αρχεία from από to προς το OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. Το OSCAR θα δημιουργήσει ένα φάκελο για τα δεδομένα σας. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. We suggest you use this folder: Προτείνουμε να χρησιμοποιήσετε αυτόν το φάκελο: Click Ok to accept this, or No if you want to use a different folder. Κάντε κλικ στο OK για να το αποδεχτείτε ή στο No (Όχι) εάν θέλετε να χρησιμοποιήσετε διαφορετικό φάκελο. Choose or create a new folder for OSCAR data Επιλέξτε ή δημιουργήστε ένα νέο φάκελο για δεδομένα OSCAR Next time you run OSCAR, you will be asked again. Την επόμενη φορά που θα εκτελέσετε το OSCAR, θα σας ζητηθεί ξανά. The folder you chose is not empty, nor does it already contain valid OSCAR data. Την επόμενη φορά που θα εκτελέσετε το OSCAR, θα σας ζητηθεί ξανά. Data directory: Κατάλογος δεδομένων: Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! Η έκδοση "%1" δεν είναι έγκυρη, δεν μπορεί να συνεχιστεί! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Η έκδοση του OSCAR που εκτελείτε (%1) είναι παλαιότερη από αυτή που χρησιμοποιήθηκε για τη δημιουργία αυτών των δεδομένων (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? Είναι πιθανό ότι αυτό θα προκαλέσει καταστροφή δεδομένων, είστε σίγουροι ότι θέλετε να το κάνετε αυτό; Question Ερώτηση Exiting Έξοδος Are you sure you want to use this folder? Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε αυτόν τον φάκελο; Don't forget to place your datacard back in your CPAP machine Μην ξεχάσετε να τοποθετήσετε το datacard σας πίσω στο μηχάνημα CPAP OSCAR Reminder Υπενθύμιση OSCAR You can only work with one instance of an individual OSCAR profile at a time. Μπορείτε να εργαστείτε μόνο με μια εμφάνιση ενός μεμονωμένου προφίλ OSCAR τη φορά. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Εάν χρησιμοποιείτε χώρο αποθήκευσης στο νέφος, βεβαιωθείτε ότι το OSCAR είναι κλειστό και ότι ο συγχρονισμός έχει ολοκληρωθεί πρώτα στον άλλο υπολογιστή πριν προχωρήσετε. Loading profile "%1"... Το προφίλ φόρτωσης "%1" ... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Επιλέξτε μια τοποθεσία για το φερμουάρ σας εκτός από την ίδια την κάρτα δεδομένων! Unable to create zip! Δεν είναι δυνατή η δημιουργία φερμουάρ! Are you sure you want to reset all your channel colors and settings to defaults? Είστε βέβαιοι ότι θέλετε να επαναφέρετε όλα τα χρώματα και τις ρυθμίσεις των καναλιών σας στις προεπιλογές; Are you sure you want to reset all your waveform channel colors and settings to defaults? Είστε βέβαιοι ότι θέλετε να επαναφέρετε όλα τα χρώματα καναλιών κυματομορφών και τις ρυθμίσεις σας στις προεπιλογές; There are no graphs visible to print Δεν υπάρχουν γραφήματα ορατά για εκτύπωση Would you like to show bookmarked areas in this report? Θα θέλατε να εμφανίσετε σελιδοδείκτες σε αυτήν την αναφορά; Printing %1 Report Εκτύπωση αναφοράς %1 %1 Report %1 Αναφορά : %1 hours, %2 minutes, %3 seconds : %1 ώρες, %2 λεπτά, %3 δευτερόλεπτα RDI %1 RDI %1 AHI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 UAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 Αναφορά από %1 σε %2 Entire Day's Flow Waveform Ολόκληρη η ροή της ημέρας Current Selection Τρέχουσα επιλογή Entire Day Ολόκληρη μέρα Page %1 of %2 Σελίδα %1 του %2 Days: %1 Ημέρες: %1 Low Usage Days: %1 Χαμηλές ημέρες χρήσης: %1 (%1% compliant, defined as > %2 hours) (%1% συμβατό, ορίζεται ως >%2 ώρες) (Sess: %1) (Sess: %1) Bedtime: %1 Ώρα για ύπνο: %1 Waketime: %1 Διάρκεια: %1 (Summary Only) (Μόνο περίληψη) There is a lockfile already present for this profile '%1', claimed on '%2'. Υπάρχει ήδη κλειδωμένο αρχείο κλειδώματος για αυτό το προφίλ '%1', το οποίο αξιώνεται στο '%2'. Fixed Bi-Level Σταθερό επίπεδο δύο επιπέδων Auto Bi-Level (Fixed PS) Auto Bi-Level (Σταθερό PS) Auto Bi-Level (Variable PS) Auto Bi-επίπεδο (μεταβλητό PS) 99.5% 90% {99.5%?} varies n/a n/a Fixed %1 (%2) Σταθερό %1 (%2) Min %1 Max %2 (%3) Ελάχιστο %1 Μέγιστο %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %1 πάνω από %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Ελάχιστο EPAP %1 Μέγιστο IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1 IPAP %2-%3 (%4) {1-%2 ?} {3-%4 ?} {5)?} Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Τα πιο πρόσφατα δεδομένα οξυμετρίας: <a onclick='alert("daily=%2");'>%1</a> (last night) (την προηγούμενη νύχτα) (1 day ago) (%2 days ago) No oximetry data has been imported yet. Δεν έχουν εισαχθεί ακόμα δεδομένα οξυμετρίας. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings Ρυθμίσεις SmartFlex ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Λογισμικό Somnopose Zeo Zeo Personal Sleep Coach Προσωπικός προπονητής ύπνου Database Outdated Please Rebuild CPAP Data Η βάση δεδομένων είναι ξεπερασμένη Παρακαλούμε ξαναχτίστε τα δεδομένα CPAP (%2 min, %3 sec) (%2 λεπτά, %3 δευτερόλεπτα) (%3 sec) (%3 δευτερόλεπτα) Pop out Graph Αναδύστε το γράφημα The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View There is no data to graph Δεν υπάρχουν δεδομένα στο γράφημα d MMM yyyy [ %1 - %2 ] d MMM yyyy [ %1 - %2 ] Hide All Events Απόκρυψη όλων των συμβάντων Show All Events Εμφάνιση όλων των συμβάντων Unpin %1 Graph Απελευθερώστε το %1 γράφημα Popout %1 Graph Αναφορά %1 Γραφή Pin %1 Graph Pin %1 Γράφημα Plots Disabled Τα οικόπεδα είναι απενεργοποιημένα Duration %1:%2:%3 Διάρκεια %1:%2:%3 AHI %1 AHI %1 Relief: %1 Ανακούφιση: %1 Hours: %1h, %2m, %3s Ωρες: %1h, %2m, %3s Machine Information Πληροφορίες μηχανής Journal Data Δεδομένα περιοδικών OSCAR found an old Journal folder, but it looks like it's been renamed: Το OSCAR βρήκε ένα παλιό φάκελο του περιοδικού, αλλά μοιάζει να έχει μετονομαστεί: OSCAR will not touch this folder, and will create a new one instead. Το OSCAR δεν θα αγγίξει αυτόν το φάκελο και θα δημιουργήσει ένα νέο. Please be careful when playing in OSCAR's profile folders :-P Να είστε προσεκτικοί κατά την αναπαραγωγή στους φακέλους προφίλ του OSCAR :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. Για κάποιο λόγο, το OSCAR δεν μπόρεσε να βρει μια εγγραφή αντικειμένου περιοδικού στο προφίλ σας, αλλά βρήκε πολλούς φακέλους δεδομένων περιοδικών. OSCAR picked only the first one of these, and will use it in future: Το OSCAR επέλεξε μόνο το πρώτο από αυτά και θα το χρησιμοποιήσει στο μέλλον: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Εάν τα παλιά σας δεδομένα λείπουν, αντιγράψτε το περιεχόμενο όλων των άλλων φακέλων Journal_XXXXXXX σε αυτό το μη αυτόματο. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Backing up files... Reading data files... SmartFlex Mode Λειτουργία SmartFlex Intellipap pressure relief mode. Λειτουργία ανακούφισης πίεσης Intellipap. Ramp Only Μόνο Ramp Full Time Πλήρης απασχόληση SmartFlex Level Επίπεδο SmartFlex Intellipap pressure relief level. Επίπεδο ανακούφισης πίεσης Intellipap. Snoring event. SN Locating STR.edf File(s)... Εντοπισμός αρχείων STR.edf ... Cataloguing EDF Files... Καταλογογράφηση αρχείων EDF ... Queueing Import Tasks... Εργασίες εισαγωγής ουράς ... Finishing Up... Τελειώνω... CPAP Mode Λειτουργία CPAP VPAPauto VPAPauto ASVAuto ASVAuto iVAPS PAC Auto for Her Αυτόματα για αυτήν EPR EPR ResMed Exhale Pressure Relief ResMed Έκπλυση πίεσης Patient??? Υπομονετικος??? EPR Level Επίπεδο EPR Exhale Pressure Relief Level Επίπεδο ανακούφισης πίεσης εκπνοής Response Patient View SmartStart SmartStart Machine auto starts by breathing Η αυτόματη μηχανή ξεκινά με αναπνοή Smart Start Έξυπνη εκκίνηση Humid. Status Κατάσταση υγραντήρα Humidifier Enabled Status Κατάσταση ενεργοποιημένου υγραντήρα Humid. Level Επίπεδο υγραντήρα Humidity Level Επίπεδο υγρασίας Temperature Θερμοκρασία ClimateLine Temperature Θερμοκρασία κλιματικής γραμμής Temp. Enable Θερμοκρασία. επιτρέπω ClimateLine Temperature Enable Ενεργοποίηση θερμοκρασίας ClimateLine Temperature Enable Ενεργοποίηση θερμοκρασίας AB Filter Φίλτρο ΑΒ Antibacterial Filter Αντιβακτηριακό φίλτρο Pt. Access Πρόσβαση ασθενούς Essentials Plus Climate Control Ελεγχος του κλίματος Manual Εγχειρίδιο Soft Standard Πρότυπο BiPAP-T BiPAP-S BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Advanced Προχωρημένος Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... Ανάλυση αρχείων STR.edf ... Auto Αυτο Mask Μάσκα ResMed Mask Setting Ρύθμιση μάσκας ResMed Pillows Μαξιλάρια Full Face ΟΛΟΚΛΗΡΟ ΠΡΟΣΩΠΟ Nasal Ρινικός Ramp Enable Ενεργοποίηση ράμπας Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Snapshot %1 Στιγμιότυπο %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... Φόρτωση δεδομένων %1 για %2 ... Scanning Files Σάρωση αρχείων Migrating Summary File Location Μετεγκατάσταση τοποθεσίας συνοπτικού αρχείου Loading Summaries.xml.gz Φόρτωση περιλήψεων.xml.gz Loading Summary Data Φόρτωση δεδομένων περίληψης Please Wait... Παρακαλώ περιμένετε... Updating Statistics cache Ενημέρωση της προσωρινής μνήμης στατιστικών στοιχείων Usage Statistics Στατιστικά χρήσης %1 Charts %1 of %2 Charts Loading summaries Φόρτωση περιλήψεων Dreem Dreem Your Viatom device generated data that OSCAR has never seen before. Η συσκευή σας Viatom δημιούργησε δεδομένα που ο OSCAR δεν έχει δει ποτέ πριν. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Τα εισαγόμενα δεδομένα ενδέχεται να μην είναι απολύτως ακριβή, οπότε οι προγραμματιστές θα ήθελαν ένα αντίγραφο των αρχείων Viatom να βεβαιωθεί ότι η OSCAR χειρίζεται σωστά τα δεδομένα. Viatom Viatom Viatom Software Λογισμικό Viatom New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to check for updates. Please try again later. SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank about:blank SessionBar %1h %2m %1h %2m No Sessions Present Δεν υπάρχουν Συνεδρίες SleepStyleLoader Import Error Σφάλμα εισαγωγής This Machine Record cannot be imported in this profile. Αυτή η εγγραφή μηχάνημα δεν μπορεί να εισαχθεί σε αυτό το προφίλ. The Day records overlap with already existing content. Οι εγγραφές Ημέρας επικαλύπτονται με ήδη υπάρχον περιεχόμενο. Statistics CPAP Statistics Στατιστικά CPAP CPAP Usage Χρήση CPAP Average Hours per Night Μέσες ώρες ανά νύχτα Therapy Efficacy Αποτελεσματικότητα θεραπείας Leak Statistics Στατιστικά διαρροών Pressure Statistics Στατιστικές πίεσης Oximeter Statistics Στατιστικά Οξύμετρο Blood Oxygen Saturation Κορεσμός οξυγόνου αίματος Pulse Rate Καρδιακός σφυγμός %1 Median %1 διάμεσος Average %1 Μέσο %1 Min %1 Ελάχιστο %1 Max %1 Μέγιστο %1 %1 Index %1 Ευρετήριο % of time in %1 % του χρόνου στο %1 % of time above %1 threshold % του χρόνου πάνω από το %1 όριο % of time below %1 threshold % του χρόνου κάτω από το %1 όριο Name: %1, %2 Ονομα: %1, %2 DOB: %1 Ημερομηνια γεννησης: %1 Phone: %1 Τηλέφωνο: %1 Email: %1 ΗΛΕΚΤΡΟΝΙΚΗ ΔΙΕΥΘΥΝΣΗ: %1 Address: Διεύθυνση: This report was prepared on %1 by OSCAR %2 Αυτή η αναφορά ετοιμάστηκε για %1 από OSCAR %2 Days Used: %1 Ημέρες που χρησιμοποιήθηκαν: %1 Low Use Days: %1 Ημέρες χαμηλής χρήσης: %1 Compliance: %1% Συμμόρφωση: %1% Days AHI of 5 or greater: %1 Ημέρες AHI 5 ή μεγαλύτερες: %1 Best AHI Καλύτερο AHI Date: %1 AHI: %2 Ημερομηνία: %1 Τύπος: %2 Worst AHI Χειρότερη AHI Best Flow Limitation Καλύτερος περιορισμός ροής Date: %1 FL: %2 Ημερομηνία: %1 FL: %2 Worst Flow Limtation Περιορισμός χειρότερης ροής No Flow Limitation on record Δεν υπάρχει Περιορισμός ροής Worst Large Leaks Χειρότερες μεγάλες διαρροές Date: %1 Leak: %2% Ημερομηνία: %1 Διαρροή: %2% No Large Leaks on record Δεν υπάρχουν μεγάλες διαρροές στο αρχείο Worst CSR Χειρότερη CSR Date: %1 CSR: %2% Ημερομηνία: %1 CSR: %2% No CSR on record Δεν υπάρχει CSR στην εγγραφή Worst PB Χειρότερη PB Date: %1 PB: %2% Ημερομηνία: %1 PB: %2% No PB on record Δεν υπάρχει αρχείο PB Want more information? Θέλετε περισσότερες πληροφορίες; OSCAR needs all summary data loaded to calculate best/worst data for individual days. Το OSCAR χρειάζεται όλα τα συγκεντρωτικά δεδομένα που έχουν φορτωθεί για να υπολογίσουν τα καλύτερα / χειρότερα δεδομένα για μεμονωμένες ημέρες. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Ενεργοποιήστε το πλαίσιο ελέγχου Pre-Load Summaries στις προτιμήσεις για να βεβαιωθείτε ότι τα δεδομένα αυτά είναι διαθέσιμα. Best RX Setting Καλύτερη ρύθμιση Rx Date: %1 - %2 Ημερομηνία: %1 - %2 AHI: %1 AHI: %1 Total Hours: %1 Συνολικές ώρες: %1 Worst RX Setting Χειρότερη ρύθμιση Rx Most Recent Πρόσφατα Compliance (%1 hrs/day) Συμμόρφωση (%1 ώρα/ημέρα) OSCAR is free open-source CPAP report software Το OSCAR είναι δωρεάν λογισμικό αναφορών CPAP ανοιχτού κώδικα Changes to Machine Settings Αλλαγές στις Ρυθμίσεις μηχανής No data found?!? Δε βρέθηκαν δεδομένα?!? Oscar has no data to report :( Το Όσκαρ δεν έχει δεδομένα για να αναφέρει :( Last Week Την προηγούμενη εβδομάδα Last 30 Days Τελευταίες 30 ημέρες Last 6 Months Τελευταίοι 6 μήνες Last Year Πέρυσι Last Session Τελευταία σύνοδος Details Λεπτομέριες No %1 data available. Δεν υπάρχουν διαθέσιμα δεδομέναv%1. %1 day of %2 Data on %3 %1 ημέρα %2 Δεδομένα στο %3 %1 days of %2 Data, between %3 and %4 %1 ημέρες %2 Δεδομένα, μεταξύ %3 και %4 Days Ημέρες Pressure Relief Ανακούφιση πίεσης Pressure Settings Ρυθμίσεις πίεσης Machine Information Πληροφορίες μηχανής First Use Πρώτη χρήση Last Use Τελευταία χρήση Welcome Welcome to the Open Source CPAP Analysis Reporter Καλώς ήλθατε στον Αναλυτή Δεδομένων CPAP ανοιχτού κώδικα What would you like to do? Τι θα θέλατε να κάνετε; CPAP Importer Εισαγωγή δεδομένων CPAP Oximetry Wizard Οδηγός οξυμετρίας Daily View Καθημερινή προβολή Overview Συνολική εικόνα Statistics Στατιστικά <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Προειδοποίηση: </span><span style=" color:#ff0000;">Οι κάρτες SD ResMed S9 πρέπει να κλειδωθούν </span><span style=" font-weight:600; color:#ff0000;">πριν τις εισαγάγετε στον υπολογιστή σας.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Ορισμένα λειτουργικά συστήματα γράφουν αρχεία ευρετηρίου στην κάρτα χωρίς ερώτηση, το οποίο μπορεί να καταστήσει την κάρτα σας δυσανάγνωστη από το μηχάνημα cpap.</span></p></body></html> It would be a good idea to check File->Preferences first, Θα ήταν καλή ιδέα να ελέγξετε πρώτα το Αρχείο-> Προτιμήσεις, as there are some options that affect import. καθώς υπάρχουν ορισμένες επιλογές που επηρεάζουν την εισαγωγή. Note that some preferences are forced when a ResMed machine is detected Σημειώστε ότι ορισμένες προτιμήσεις είναι προεπιλεγμένες όταν ανιχνεύεται ένα μηχάνημα ResMed First import can take a few minutes. Η πρώτη εισαγωγή μπορεί να διαρκέσει μερικά λεπτά. The last time you used your %1... Την τελευταία φορά που χρησιμοποιήσατε το %1 σας ... last night την προηγούμενη νύχτα %2 days ago πριν %2 μέρες was %1 (on %2) ήταν %1 (την %2) %1 hours, %2 minutes and %3 seconds %1 ώρες, %2 λεπτά και %3 δευτερόλεπτα Your machine was on for %1. Το μηχάνημά σας ήταν ενεργοποιημένο για %1. <font color = red>You only had the mask on for %1.</font> <font color = red>Φορέσατε τη μάσκα μόνο για %1.</font> under κάτω από over πάνω από reasonably close to σχετικά κοντά equal to ίσος με You had an AHI of %1, which is %2 your %3 day average of %4. Είχατε Δείκτη Απνοιών %1, ο οποίος είναι %2 τον μέσο όρο των %3 ημερών που είναι %4. Your CPAP machine used a constant %1 %2 of air Το μηχάνημα CPAP χρησιμοποίησε σταθερά %1 %2 αέρα Your pressure was under %1 %2 for %3% of the time. Η πίεσή σας ήταν κάτω από %1 %2 για το %3% του χρόνου. Your machine used a constant %1-%2 %3 of air. Το μηχάνημά σας χρησιμοποίησε σταθερά %1-%2 %3 αέρα. Your EPAP pressure fixed at %1 %2. Η πίεσή σας EPAP καθορίστηκε στο %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Η πίεση IPAP ήταν κάτω από %1 %2 για το %3% του χρόνου. Your EPAP pressure was under %1 %2 for %3% of the time. Η πίεση σας EPAP ήταν κάτω από %1 %2 για το %3% του χρόνου. Your machine was under %1-%2 %3 for %4% of the time. Το μηχάνημά σας ήταν κάτω από %1-%2 %3 για το %4% του χρόνου. 1 day ago χθες Your average leaks were %1 %2, which is %3 your %4 day average of %5. Ο μέσος όρος της διαρροής αέρα ήταν %1 %2, ο οποίος είναι %3 τον μέσο όρο των %4 ημερών που είναι %5. No CPAP data has been imported yet. Δεν έχουν εισαχθεί ακόμα δεδομένα CPAP. gGraph %1 days %1 ημέρες gGraphView 100% zoom level 100% επίπεδο ζουμ Restore X-axis zoom to 100% to view entire selected period. Επαναφέρετε το ζουμ του άξονα Χ σε 100% για να δείτε ολόκληρη την επιλεγμένη περίοδο. Restore X-axis zoom to 100% to view entire day's data. Επαναφέρετε το ζουμ του άξονα Χ σε 100% για να δείτε τα δεδομένα ολόκληρης της ημέρας. Reset Graph Layout Επαναφορά διάταξης γραφήματος Resets all graphs to a uniform height and default order. Επαναφέρει όλες τις γραφικές παραστάσεις σε ομοιόμορφο ύψος και προεπιλεγμένη σειρά. Y-Axis Άξονας Υ Plots σύρω CPAP Overlays Επικάλυψη CPAP Oximeter Overlays Επικάλυψη οξυμέτρου Dotted Lines Γραμμωμένες Γραμμές Double click title to pin / unpin Click and drag to reorder graphs Κάντε διπλό κλικ στον τίτλο για να ενεργοποιήσετε / αποσυνδέσετε Κάντε κλικ και σύρετε για να αναδιατάξετε τα γραφήματα Remove Clone Αφαιρέστε τον κλώνο Clone %1 Graph Κλώνος %1 Γράφημα OSCAR-code-v1.3.1/Translations/Hebrew.he.ts000066400000000000000000013702371417327530600203460ustar00rootroot00000000000000 AboutDialog &About &אודות Release Notes הערות שחרור Credits GPL License Close Show data folder About OSCAR %1 Sorry, could not locate About file. Sorry, could not locate Credits file. Sorry, could not locate Release Notes. OSCAR %1 Important: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. To see if the license text is available in your language, see %1. CMS50F37Loader Could not find the oximeter file: Could not open the oximeter file: CMS50Loader Could not get data transmission from oximeter. Please ensure you select 'upload' from the oximeter devices menu. Could not find the oximeter file: Could not open the oximeter file: CheckUpdates Checking for newer OSCAR versions Daily Go to the previous day אל היום הקודם Show or hide the calender הראה או הסתר את לוח השנה Go to the next day אל היום הבא Go to the most recent day with data records אל היום האחרון שיש בו נתונים Details פרטים Events אירועים View Size גודל צפיה Notes הערות Journal יומן Small קטן Medium בינוני Big גדול I'm feeling ... If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Flags Graphs גרפים Show/hide available graphs. Color צבע u B Zombie זומבי Weight משקל Awesome מדהים B.M.I. Bookmarks סימניות Add Bookmark הוסף סימניה Starts מתחיל Remove Bookmark הסר סימניה Breakdown סיווג events אירועים No %1 events are recorded this day אין אירוע %1 שהוקלטו היום %1 event אירוע %1 %1 events אירועי %1 <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. Event Breakdown סיווג האירועים Unable to display Pie Chart on this system Sessions all off! כל השימושים מכובים! Sessions exist for this day but are switched off. קיימים שימושים ליום זה אבל הם כובו. Impossibly short session שימוש קצר מדי Zero hours?? אפס שעות?? BRICK :( לבנה:( Complain to your Equipment Provider! התלונן לספק הציוד שלך! Statistics סטטיסטיקה Oximeter Information מידע אוקסימטר SpO2 Desaturations ירידות בריווי חמצן Pulse Change events אירועי שינוי דופק SpO2 Baseline Used נתון בסיס ריווי חמצן Machine Settings הגדרות המכשיר UF1 UF2 Time at Pressure Session Start Times Session End Times Session Information נתוני שימוש Position Sensor Sessions Unknown Session Duration Click to %1 this session. disable enable %1 Session #%2 %1h %2m %3s (Mode and Pressure settings missing; yesterday's shown.) 99.5% 10 of 10 Event Types This CPAP machine does NOT record detailed data This bookmark is in a currently disabled area.. 10 of 10 Graphs Model %1 - %2 PAP Mode: %1 This day just contains summary data, only limited information is available. Total time in apnea Time over leak redline Total ramp time Time outside of ramp Start התחלה End סוף Sorry, this machine only provides compliance data. "Nothing's here!" CPAP Sessions שימושי סיפאפ Oximetry Sessions שימושי אוקסימטריה Sleep Stage Sessions שימושי שלב שינה No data is available for this day. Pick a Colour בחר צבע Bookmark at %1 סימניה ב %1 ExportCSV Export as CSV ייצא כ CSV Dates: תאריכים: Resolution: החלטה: Details פרטים Sessions שימושים Daily יומי Filename: שם קובץ: Cancel בטל Export ייצא Start: התחלה: End: סיום: Quick Range: תחום מהיר: Most Recent Day היום האחרון Last Week שבוע אחרון Last Fortnight שבועיים אחרונים Last Month חודש אחרון Last 6 Months ששה חודשים אחרונים Last Year שנה אחרונה Everything הכל Custom מותאם Details_ Sessions_ Summary_ Select file to export to בחר קובץ ליצוא CSV Files (*.csv) DateTime Session Event Data/Duration Date Session Count Start End Total Time AHI Count FPIconLoader Import Error This Machine Record cannot be imported in this profile. The Day records overlap with already existing content. Help Hide this message Search Topic: Help Files are not yet available for %1 and will display in %2. Help files do not appear to be present. HelpEngine did not set up correctly HelpEngine could not register documentation correctly. Contents Index Search No documentation available Please wait a bit.. Indexing still in progress No %1 result(s) for "%2" clear MD300W1Loader Could not find the oximeter file: Could not open the oximeter file: MainWindow &Statistics &סטטיסטיקה Report Mode Standard Monthly Date Range Navigation Profiles Statistics סטטיסטיקה Daily יומי Overview מבט על Oximetry אוקסימטריה Import יבוא Help עזרה Records &Reset Graphs Troubleshooting Purge Oximetry Data Rebuild CPAP Data Exit Show Daily view Show Overview view &About OSCAR &Maximize Toggle Maximize window Reset Graph &Heights Reset sizes of graphs O&ximetry Wizard &Automatic Oximetry Cleanup Purge Current Selected Day &CPAP &Oximetry &Sleep Stage &Position &All except Notes All including &Notes Purge &Current Selected Day Show Right Sidebar View S&tatistics View Statistics Show Statistics view Import &Dreem Data Change &Language Change &Data Folder Import &Somnopose Data Current Days Show &Line Cursor Create zip of CPAP data card Create zip of OSCAR diagnostic logs Create zip of all OSCAR data System Information Show &Pie Chart Show Pie Chart on Daily page Standard graph order, good for CPAP, APAP, Bi-Level Advanced Advanced graph order, good for ASV, AVAPS Show Personal Data Check For &Updates Daily Sidebar Purge ALL Machine Data &Import CPAP Card Data Import &Viatom/Wellue Data Show Daily Left Sidebar Daily Calendar Show Daily Calendar Backup &Journal Show Performance Information CSV Export Wizard Export for Review Report an Issue &File &קובץ Exp&ort Data &View &מבט &Help &עזרה &Data &נתונים &Advanced &מתקדם &Preferences &העדפות &Profiles &פרופילים E&xit &יציאה View &Daily ראה מבט &יומי View &Overview ראה מבט &על View &Welcome ראה &מסך ברוך הבא Use &AntiAliasing השתמש בהחלקת &גופנים Show Debug Pane הראה חלון דיבוג Take &Screenshot &תמונת מסך Print &Report &הדפס דו"ח &Edit Profile &ערוך פרופיל Online Users &Guide &מדריך למשתמש מקוון &Frequently Asked Questions &שאלות נפוצות Change &User &החלף משתמש Right &Sidebar תצוגת צד &ימני Import &ZEO Data יבא נתוני &ZEO Import RemStar &MSeries Data יבא נתוני &RemStar MSeries Sleep Disorder Terms &Glossary מונחון &בעיות שינה Importing Data מייבא נתונים Welcome ברוך הבא &About &אודות Access to Import has been blocked while recalculations are in progress. גישה ליבוא נחסמה בזמן שחישובים מתבצעים. Access to Preferences has been blocked until recalculation completes. גישה להגדרות נחסמה עד אשר החישוב מחדש יסתיים. Help Browser Loading profile "%1" %1 (Profile: %2) Imported %1 CPAP session(s) from %2 Import Success Already up to date with CPAP data at %1 Up to date Couldn't find any valid Machine Data at %1 Import Problem Please wait, importing from backup folder(s)... Please insert your CPAP data card... Choose a folder No profile has been selected for Import. Import is already running in the background. A %1 file structure for a %2 was located at: A %1 file structure was located at: CPAP Data Located There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete Would you like to import from this location? Specify Import Reminder Please open a profile first. There was an error saving screenshot to file "%1" Screenshot saved to file "%1" The Glossary will open in your default browser Please note, that this could result in loss of data if OSCAR's backups have been disabled. The FAQ is not yet implemented The User's Guide will open in your default browser Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Find your CPAP data card Choose where to save screenshot Image files (*.png) If you can read this, the restart command didn't work. You will have to do it yourself manually. Are you sure you want to rebuild all CPAP data for the following machine: For some reason, OSCAR does not have any backups for the following machine: Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Are you really sure you want to do this? Because there are no internal backups to rebuild from, you will have to restore from your own. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) OSCAR does not have any backups for this machine! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Note as a precaution, the backup folder will be left in place. Check for updates not implemented Are you <b>absolutely sure</b> you want to proceed? A file permission error casued the purge process to fail; you will have to delete the following folder manually: No help is available. There was a problem opening MSeries block File: הייתה שגיאה בפתיחת קובץ הבלוק של MSeries: MSeries Import complete יבוא MSeries הושלם Are you sure you want to delete oximetry data for %1 <b>Please be aware you can not undo this operation!</b> Select the day with valid oximetry data in daily view first. %1's Journal Choose where to save journal XML Files (*.xml) Export review is not yet implemented Would you like to zip this card? Choose where to save zip ZIP files (*.zip) Creating zip... Calculating size... Reporting issues is not yet implemented OSCAR Information Bookmarks סימניות MinMaxWidget Auto-Fit Defaults Override The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. The Minimum Y-Axis value.. Note this can be a negative number if you wish. The Maximum Y-Axis value.. Must be greater than Minimum to work. Scaling Mode This button resets the Min and Max to match the Auto-Fit NewProfile Edit User Profile ערוך פרופיל משתמש I agree to all the conditions above. אני מסכים לכל התנאים. User Information מידע משתמש User Name שם משתמש Password Protect Profile הגנת סיסמה לפרופיל Password סיסמה ...twice... ...שוב... Locale Settings הגדרות איזוריות Country מדינה TimeZone איזור זמן about:blank Very weak password protection and not recommended if security is required. DST Zone איזור DST Personal Information (for reports) מידע אישי (לדוחות) First Name שם פרטי Last Name שם משפחה It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. D.O.B. תאריך לידה <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> Gender מגדר Male זכר Female נקבה Height גובה Metric English Contact Information מידע ליצירת קשר Address כתובת Email מייל Phone טלפון CPAP Treatment Information מידע טיפול סיפאפ Date Diagnosed תאריך אבחנה Untreated AHI AHI ללא טיפול CPAP Mode אופן עבודה סיפאפ CPAP לחץ קבוע APAP לחץ משתנה Bi-Level שתי רמות ASV RX Pressure לחץ מרשם Doctors / Clinic Information מידע רופא\מרפאה Doctors Name שם הרופא Practice Name שם המרפאה Patient ID שם הפציינט &Cancel &בטל &Back &קודם &Next &בא Select Country בחר מדינה Welcome to the Open Source CPAP Analysis Reporter This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. PLEASE READ CAREFULLY OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. Accuracy of any data displayed is not and can not be guaranteed. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Use of this software is entirely at your own risk. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team Please provide a username for this profile Passwords don't match הססמאות לא תואמות Profile Changes שינויי פרופיל Accept and save this information? קבל ושמור מידע זה? &Finish &סיים &Close this window Overview Range: תחום: Last Week שבוע אחרון Last Two Weeks שבועיים אחרונים Last Month חודש אחרון Last Two Months חודשיים אחרונים Last Three Months שלושה חודשים אחרונים Last 6 Months ששה חודשים אחרונים Last Year שנה אחרונה Everything הכל Custom מותאם Snapshot Start: התחלה: End: סיום: Reset view to selected date range אפס תחום לטווח התאריכים הנבחר Toggle Graph Visibility החלף נראות גרף Drop down to see list of graphs to switch on/off. משוך לראות רשימת גרפים להדליק\לכבות. Graphs גרפים Apnea Hypopnea Index אינדקס דום נשימה Usage שימוש Usage (hours) שימוש (שעות) Session Times זמני שימושים Total Time in Apnea Total Time in Apnea (Minutes) Respiratory Disturbance Index אינדקס הפרעות נשימה Body Mass Index מדד מסת גוף How you felt (0-10) איך הרגשת (0-10) 10 of 10 Charts Show all graphs הראה את כל הגרפים Hide all graphs הסתר כל הגרפים OximeterImport Oximeter Import Wizard Skip this page next time. Where would you like to import from? CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> Set device date/time <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> Set device identifier Erase session after successful upload CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> Import directly from a recording on a device <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> Record attached to computer overnight (provides plethysomogram) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> Please connect your oximeter device If you can read this, you likely have your oximeter type set wrong in preferences. Please connect your oximeter device, turn it on, and enter the menu Press Start to commence recording Show Live Graphs Duration Day recording (normally would have) started Pulse Rate דופק Multiple Sessions Detected Please choose which one you want to import into OSCAR Start Time Details פרטים Import Completed. When did the recording start? Oximeter Starting time I want to use the time reported by my oximeter's built in clock. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> Choose CPAP session to sync to: You can manually adjust the time here if required: HH:mm:ssap &Information Page &Cancel &בטל &Retry &Choose Session &End Recording &Sync and Save &Save and Finish &Start &התחל Scanning for compatible oximeters Could not detect any connected oximeter devices. Connecting to %1 Oximeter Renaming this oximeter from '%1' to '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. "%1", session %2 Nothing to import Your oximeter did not have any valid sessions. Close Waiting for %1 to start Waiting for the device to start the upload process... Select upload option on %1 You need to tell your oximeter to begin sending data to the computer. Please connect your oximeter, enter it's menu and select upload to commence data transfer... %1 device is uploading data... Please wait until oximeter upload process completes. Do not unplug your oximeter. Oximeter import completed.. Select a valid oximetry data file Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Oximeter not detected Couldn't access oximeter Live Oximetry Mode Starting up... If you can still read this after a few seconds, cancel and try again Live Import Stopped Live Oximetry Stopped Live Oximetry import has been stopped %1 session(s) on %2, starting at %3 No CPAP data available on %1 Recording... Finger not detected I want to use the time my computer recorded for this live oximetry session. I need to set the time manually, because my oximeter doesn't have an internal clock. Something went wrong getting session data Oximeter Session %1 Welcome to the Oximeter Import Wizard Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Please remember: If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Important Notes: For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Oximetry Date תאריך d/MM/yy h:mm:ss AP R&eset &אפס Pulse דופק &Open .spo/R File &פתח קובץ .spo/R Serial &Import &יבוא טורי &Start Live &התחל חי Serial Port ממשק טורי &Rescan Ports &סרוק ממשקים PreferencesDialog Preferences &Import Combine Close Sessions Minutes Multiple sessions closer together than this value will be kept on the same day. Ignore Short Sessions <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Sessions starting before this time will go to the previous calendar day. Session Storage Options Compress SD Card Backups (slower first import, but makes backups smaller) &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. hours Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Flow Restriction Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> Duration of airflow restriction s Event Duration Allow duplicates near machine events. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. minutes Reset the counter to zero at beginning of each (time) window. Zero Reset CPAP Clock Drift Search &Oximetry Percentage drop in oxygen saturation Pulse דופק Sudden change in Pulse Rate of at least this amount bpm Minimum duration of drop in oxygen saturation Minimum duration of pulse change event. Small chunks of oximetry data under this amount will be discarded. &General General Settings Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> Daily view navigation buttons will skip over days without data records Skip over Empty Days Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Enable Multithreading Bypass the login screen and load the most recent User Profile Changes to the following settings needs a restart, but not a recalc. Preferred Calculation Methods Middle Calculations Upper Percentile For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Median is recommended for ResMed users. Median חציון Weighted Average Normal Average True Maximum 99% Percentile Maximum Calcs Your masks vent rate at 20 cmH2O pressure Your masks vent rate at 4 cmH2O pressure Check for new version every days. Last Checked For Updates: TextLabel &Appearance Overlay Flags The visual method of displaying waveform overlay flags. Standard Bars Graph Height Default display height of graphs in pixels How long you want the tooltips to stay visible. Session Splitting Settings <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Changing SD Backup compression options doesn't automatically recompress backup data. Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Compress Session Data (makes OSCAR data smaller, but day changing slower.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Create SD Card Backups during Import (Turn this off at your own peril!) Do not import sessions older than: Sessions older than this date will not be imported dd MMMM yyyy Memory and Startup Options Auto-Launch CPAP Importer after opening profile <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> Keep Waveform/Event data in memory <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> Pre-Load all summary data at startup Automatically load last used profile on start-up <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> Import without asking for confirmation <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> Hours שעות Seconds This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Calculate Unintentional Leaks When Not Present 4 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Custom CPAP User Event Flagging l/min <html><head/><body><p>Cumulative Indices</p></body></html> This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Resync Machine Detected Events (Experimental) Show in Event Breakdown Piechart General CPAP and Related Settings Show flags for machine detected events that haven't been identified yet. Enable Unknown Events Channels AHI/Hour Graph Time Window Compliance defined as Whether to show the leak redline in the leak graph Flag leaks over threshold Preferred major event index User definable threshold considered large leak AHI Apnea Hypopnea Index RDI Respiratory Disturbance Index <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> Combined Count divided by Total Hours Time Weighted average of Indice Standard average of indice <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> Oximetry Settings Other oximetry options Discard segments under Flag Pulse Rate Below Flag Pulse Rate Above Flag rapid changes in oximetry stats Events אירועים Reset &Defaults <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> Waveforms Show Remove Card reminder notification on OSCAR shutdown Always save screenshots in the OSCAR Data folder Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Graph Settings On Opening <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> Profile פרופיל Welcome ברוך הבא Daily יומי Overview מבט על Statistics סטטיסטיקה Switch Tabs No change After Import Line Thickness Top Markers The pixel thickness of line plots Tooltip Timeout Scroll Dampening Graph Tooltips Bar Tops Line Chart <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> Overview Linecharts Other Visual Settings Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Use Anti-Aliasing Makes certain plots look more "square waved". Square Wave Plots Whether to allow changing yAxis scales by double clicking on yAxis labels Allow YAxis Scaling Whether to include machine serial number on machine settings changes report Include Serial Number Graphics Engine (Requires Restart) Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Fonts (Application wide settings) Use Pixmap Caching <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> I want to be notified of test versions. (Advanced users only please.) Animations && Fancy Stuff Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Font Size גודל Bold Italic Application Graph Text Graph Titles Big Text Details פרטים &Cancel &בטל &Ok Flag Minor Flag Span Always Minor No CPAP machines detected Will you be using a ResMed brand machine? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> Never Name שם Color צבע Flag Type Label CPAP Events Oximeter Events Positional Events Sleep Stage Events Unknown Events Double click to change the descriptive name the '%1' channel. Double click to change the default color for this channel plot/flag/data. Whether this flag has a dedicated overview chart. Here you can change the type of flag shown for this event This is the short-form label to indicate this channel on screen. This is a description of what this channel does. Lower Upper CPAP Waveforms Oximeter Waveforms Positional Waveforms Sleep Stage Waveforms Double click to change the descriptive name this channel. Whether a breakdown of this waveform displays in overview. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Data Processing Required A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Data Reindex Required A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Restart Required נחוץ אתחול One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. If you need to conserve disk space, please remember to carry out manual backups. Are you sure you want to disable these backups? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Are you really sure you want to do this? %1 %2 This may not be a good idea ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ProfileSelector Filter: Reset filter to see all profiles Version גרסה &Open Profile &Edit Profile &ערוך פרופיל &New Profile Profile: None Please select or create a profile... Destroy Profile Profile פרופיל Ventilator Brand Ventilator Model Other Data Last Imported Name שם You must create a profile Enter Password for %1 הזן סיסמה עבור %1 You entered an incorrect password Forgot your password? Ask on the forums how to reset it, it's actually pretty easy. Select a profile first The selected profile does not appear to contain any data and cannot be removed by OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. You are about to destroy profile '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. DELETE Sorry You need to enter DELETE in capital letters. There was an error deleting the profile directory, you need to manually remove it. Profile '%1' was succesfully deleted Bytes KB MB GB TB PB Summaries: Events: Backups: Hide disk usage information Show disk usage information Name: %1, %2 Phone: %1 Email: <a href='mailto:%1'>%1</a> Address: No profile information given Profile: %1 ProgressDialog Abort QObject Days: %1 Low Usage Days: %1 (%1% compliant, defined as > %2 hours) (Sess: %1) Bedtime: %1 Waketime: %1 (Summary Only) No Data אין נתונים ft רגל lb ליברה oz אונקיה Kg ק"ג cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours שעות Min %1 Hours: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 %1 Length: %3 Start: %2 Mask On Mask Off %1 Length: %3 Start: %2 TTIA: TTIA: %1 bpm Error שגיאה Warning אזהרה Only Settings and Compliance Data Available Summary Data Only Min IPAP Max IPAP IPAP מקסימלי On דלוק Off כבוי BMI App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Software Engine ANGLE / OpenGLES Desktop OpenGL m cm in Minutes Seconds h m s ms Events/hr Hz L/min Litres ml Breaths/min ? ratio Severity (0-1) Degrees Question שאלה Information מידע Busy Please Note No Data Available Graphs Switched Off Sessions Switched Off &Yes &No &Cancel &בטל &Destroy &Save Weight משקל Zombie זומבי Pulse Rate דופק Plethy אין מילה כזאת Profile פרופיל Oximeter אוקסימטר Default CPAP סיפאפ BiPAP ביפאפ Bi-Level בי-לבל EPAP Min EPAP EPAP מינימלי Max EPAP IPAP APAP ASV AVAPS ST/ASV Humidifier מעשיר לחות H OA A CA FL SA LE EP VS VS2 RERA PP P RE NR NRI O2 PC UF1 UF2 UF3 PS AHI RDI AI HI UAI CAI FLI REI EPI PB IE Insp. Time משך נשימה Exp. Time משך נשיפה Resp. Event אירוע נשימתי Flow Limitation הגבלת זרימה Flow Limit מגבלת זרימה SensAwake Pat. Trig. Breath תבנית נשימה פציינט Tgt. Min. Vent יעד אורור מינימלי Target Vent. יעד אוורור Minute Vent. https://he.wikipedia.org/wiki/%D7%A4%D7%99%D7%96%D7%99%D7%95%D7%9C%D7%95%D7%92%D7%99%D7%94_%D7%A9%D7%9C_%D7%9E%D7%A2%D7%A8%D7%9B%D7%AA_%D7%94%D7%A0%D7%A9%D7%99%D7%9E%D7%94 אוורור דקתי Tidal Volume https://he.wikipedia.org/wiki/%D7%A4%D7%99%D7%96%D7%99%D7%95%D7%9C%D7%95%D7%92%D7%99%D7%94_%D7%A9%D7%9C_%D7%9E%D7%A2%D7%A8%D7%9B%D7%AA_%D7%94%D7%A0%D7%A9%D7%99%D7%9E%D7%94 נפח חלופי Resp. Rate קצב נשימה Snore נחירה Leak דליפה Leaks דליפות Large Leak LL Total Leaks סה"כ דליפות Unintentional Leaks דליפות לא מכוונות MaskPressure Flow Rate קצב זרימה Sleep Stage שלב שינה Usage שימוש Sessions שימושים Pr. Relief הפחתת לחץ Bookmarks סימניות Mode מצב Model מודל Brand יצרן Serial מספר סידורי Series Machine מכונה Channel ערוץ Settings הגדרות Inclination Orientation Motion Name שם DOB Phone טלפון Address כתובת Email מייל Patient ID מספר מזהה Date תאריך Bedtime זמן שינה Wake-up זמן קימה Mask Time זמן מסכה Unknown לא ידוע None כלום Ready מוכן First ראשון Last אחרון Start התחלה End סוף Yes No Min מינימום Max מקסימום Med חציון Average ממוצע Median חציון Avg ממוצע W-Avg ממוצע משוקלל Pressure לחץ Daily יומי Overview מבט על Oximetry אוקסימטריה Event Flags דגלי אירועים Windows User משתמש חלונות Using , found SleepyHead - You must run the OSCAR Migration Tool Launching Windows Explorer failed Could not find explorer.exe in path to launch Windows Explorer. OSCAR %1 needs to upgrade its database for %2 %3 %4 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> OSCAR does not yet have any automatic card backups stored for this device. This means you will need to import this machine data again afterwards from your own backups or data card. Important: Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Are you ready to upgrade, so you can run the new version of OSCAR? Machine Database Changes שינויים בבסיס הנתונים של המכונה Sorry, the purge operation failed, which means this version of OSCAR can't start. The machine data folder needs to be removed manually. This folder currently resides at the following location: Rebuilding from %1 Backup Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Database Outdated Please Rebuild CPAP Data (%2 min, %3 sec) (%3 sec) Snapshot %1 Pop out Graph The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View There is no data to graph d MMM yyyy [ %1 - %2 ] Hide All Events Show All Events Unpin %1 Graph Popout %1 Graph Pin %1 Graph Plots Disabled Duration %1:%2:%3 AHI %1 Duration Events אירועים (% %1 in events) Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Relief: %1 Hours: %1h, %2m, %3s Machine Information מידע מכונה 99.5% varies n/a Fixed %1 (%2) Min %1 Max %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) Journal Data OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR will not touch this folder, and will create a new one instead. Please be careful when playing in OSCAR's profile folders :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. OSCAR picked only the first one of these, and will use it in future: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. CMS50D+ CMS50E/F Contec CMS50 CMS50F3.7 CMS50F Fisher & Paykel ICON Backing up files... Reading data files... SmartFlex Mode Intellipap pressure relief mode. Ramp Only Full Time SmartFlex Level Intellipap pressure relief level. Snoring event. SN DeVilbiss Intellipap SmartFlex Settings ChoiceMMed MD300 Respironics M-Series System One Getting Ready... Non Data Capable Machine Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Machine Unsupported Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Scanning Files... Importing Sessions... Finishing up... Machine Untested Flex Lock Whether Flex settings are available to you. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Mask Resistance Setting Mask Resist. Hose Diam. 15mm 22mm Backing Up Files... Untested Data model %1 DreamStation 2 unknown model Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. CPAP-Check AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS Flex Mode PRS1 pressure relief mode. C-Flex C-Flex+ A-Flex P-Flex Rise Time Bi-Flex Flex Flex Level PRS1 pressure relief setting. Passover Target Time PRS1 Humidifier Target Time Hum. Tgt Time Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Type of ramp curve to use. Linear SmartRamp Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Fixed Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Breath BPM Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled Variable Breathing UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend Peak Flow Peak flow during a 2-minute interval Humidifier Status PRS1 humidifier connected? Disconnected Connected Humidification Mode PRS1 Humidification Mode Humid. Mode Fixed (Classic) Adaptive (System One) Heated Tube Tube Temperature PRS1 Heated Tube Temperature Tube Temp. PRS1 Humidifier Setting Hose Diameter Diameter of primary CPAP hose 12mm Auto On A few breaths automatically starts machine Auto Off Machine automatically switches off Mask Alert Whether or not machine allows Mask checking. Show AHI Breathing Not Detected A period during a session where the machine could not detect flow. BND Timed Breath Machine Initiated Breath TB Philips Respironics Locating STR.edf File(s)... Cataloguing EDF Files... Queueing Import Tasks... Finishing Up... CPAP Mode אופן עבודה סיפאפ VPAPauto ASVAuto iVAPS PAC Auto for Her EPR ResMed Exhale Pressure Relief Patient??? EPR Level Exhale Pressure Relief Level Response Patient View SmartStart Machine auto starts by breathing Smart Start Humid. Status Humidifier Enabled Status Humid. Level Humidity Level Temperature ClimateLine Temperature Temp. Enable ClimateLine Temperature Enable Temperature Enable AB Filter Antibacterial Filter Pt. Access Essentials Plus Climate Control Manual Soft Standard BiPAP-T BiPAP-S BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Advanced Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... Auto Mask ResMed Mask Setting Pillows Full Face Nasal Ramp Ramp Enable ResMed S9 EPR: Somnopose Somnopose Software Weinmann SOMNOsoft2 Zeo Personal Sleep Coach Loading %1 data for %2... Scanning Files Migrating Summary File Location Loading Summaries.xml.gz Loading Summary Data Please Wait... Therapy Pressure Inspiratory Pressure Lower Inspiratory Pressure Higher Inspiratory Pressure Expiratory Pressure Lower Expiratory Pressure Higher Expiratory Pressure Pressure Support PS Min PS מינימלי Pressure Support Minimum PS Max PS מקסימלי Pressure Support Maximum Min Pressure Minimum Therapy Pressure Pressure Min Max Pressure Maximum Therapy Pressure Pressure Max Ramp Time Ramp Delay Period Ramp Pressure Starting Ramp Pressure Ramp Event Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting Cheyne Stokes Respiration An abnormal period of Cheyne Stokes Respiration CSR Periodic Breathing An abnormal period of Periodic Breathing Clear Airway נתיב אוויר חופשי An apnea where the airway is open Obstructive חסימתי An apnea caused by airway obstruction Hypopnea דום נשימה חלקי (היפופניאה) A partially obstructed airway Unclassified Apnea UA An apnea that couldn't be determined as Central or Obstructive. A restriction in breathing from normal, causing a flattening of the flow waveform. Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Vibratory Snore A vibratory snore Vibratory Snore (VS2) A vibratory snore as detcted by a System One machine Leak Flag A large mask leak affecting machine performance. LF Non Responding Event A type of respiratory event that won't respond to a pressure increase. Expiratory Puff Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. User Flag #1 A user definable event detected by OSCAR's flow waveform processor. User Flag #2 User Flag #3 Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm Perfusion Index A relative assessment of the pulse strength at the monitoring site Perf. Index % Pulse Change A sudden (user definable) change in heart rate SpO2 Drop A sudden (user definable) drop in blood oxygen saturation SD Breathing flow rate waveform Mask Pressure Mask Pressure (High frequency) A ResMed data item: Trigger Cycle Event Amount of air displaced per breath Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Apnea An apnea reportred by your CPAP machine. Graph displaying snore volume Minute Ventilation Amount of air displaced per minute Respiratory Rate Rate of breaths per minute Patient Triggered Breaths Percentage of breaths triggered by patient Pat. Trig. Breaths Leak Rate Rate of detected mask leakage I:E Ratio Ratio between Inspiratory and Expiratory time Expiratory Time Time taken to breathe out Inspiratory Time Time taken to breathe in Respiratory Event Graph showing severity of flow limitations Flow Limit. Target Minute Ventilation Maximum Leak The maximum rate of mask leakage Max Leaks Apnea Hypopnea Index Graph showing running AHI for the past hour Total Leak Rate Detected mask leakage including natural Mask leakages Median Leak Rate Median rate of detected mask leakage Median Leaks Respiratory Disturbance Index Graph showing running RDI for the past hour Sleep position in degrees Upright angle in degrees Movement Movement detector Mask On Time Time started according to str.edf Summary Only CPAP Session contains summary data only PAP Mode PAP Device Mode APAP (Variable) Fixed Bi-Level Auto Bi-Level (Fixed PS) Auto Bi-Level (Variable PS) ASV (Fixed EPAP) ASV (Variable EPAP) Height גובה Physical Height Notes הערות Bookmark Notes Body Mass Index How you feel (0 = like crap, 10 = unstoppable) Bookmark Start Bookmark End Last Updated Journal Notes Journal יומן 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep Brain Wave BrainWave Awakenings Number of Awakenings Morning Feel How you felt in the morning Time Awake Time spent awake Time In REM Sleep Time spent in REM Sleep Time in REM Sleep Time In Light Sleep Time spent in light sleep Time in Light Sleep Time In Deep Sleep Time spent in deep sleep Time in Deep Sleep Time to Sleep Time taken to get to sleep Zeo ZQ Zeo sleep quality measurement ZEO ZQ Debugging channel #1 Test #1 For internal use only Debugging channel #2 Test #2 Zero Upper Threshold Lower Threshold Exiting As you did not select a data folder, OSCAR will exit. or CANCEL to skip migration. Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. You cannot use this folder: Migrating files from to OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. We suggest you use this folder: Click Ok to accept this, or No if you want to use a different folder. Choose or create a new folder for OSCAR data Next time you run OSCAR, you will be asked again. The folder you chose is not empty, nor does it already contain valid OSCAR data. Are you sure you want to use this folder? Data directory: Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? Don't forget to place your datacard back in your CPAP machine OSCAR Reminder There is a lockfile already present for this profile '%1', claimed on '%2'. You can only work with one instance of an individual OSCAR profile at a time. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Loading profile "%1"... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Unable to create zip! Are you sure you want to reset all your channel colors and settings to defaults? Are you sure you want to reset all your waveform channel colors and settings to defaults? There are no graphs visible to print אין גרפים נראים להדפסה Would you like to show bookmarked areas in this report? האם ברצונך להראות אזורים מסומנים בדו"ח הזה? Printing %1 Report מדפיס דו"ח %1 %1 Report דו"ח %1 : %1 hours, %2 minutes, %3 seconds : %1 שעות, %2 דקות, %3 שניות RDI %1 AHI %1 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 מדווח מ %1 עד %2 Entire Day's Flow Waveform צורת גל ליום שלם Current Selection Entire Day יום שלם Page %1 of %2 דף %1 מתוך %2 Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> (last night) (1 day ago) (%2 days ago) No oximetry data has been imported yet. Updating Statistics cache Usage Statistics %1 Charts %1 of %2 Charts Loading summaries Dreem Your Viatom device generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Viatom Viatom Software New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to check for updates. Please try again later. SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank SessionBar %1h %2m No Sessions Present SleepStyleLoader Import Error This Machine Record cannot be imported in this profile. The Day records overlap with already existing content. Statistics CPAP Statistics CPAP Usage Average Hours per Night Therapy Efficacy Leak Statistics Pressure Statistics Oximeter Statistics Blood Oxygen Saturation Pulse Rate דופק %1 Median Average %1 ממוצע %1 Min %1 Max %1 %1 Index % of time in %1 % of time above %1 threshold % of time below %1 threshold Name: %1, %2 DOB: %1 Phone: %1 Email: %1 Address: This report was prepared on %1 by OSCAR %2 Machine Information מידע מכונה First Use שימוש ראשון Last Use שימוש אחרון Days ימים Pressure Relief Pressure Settings Compliance (%1 hrs/day) OSCAR is free open-source CPAP report software Changes to Machine Settings No data found?!? Oscar has no data to report :( Most Recent אחרון Last Week שבוע אחרון Last 30 Days 30 ימים אחרונים Last 6 Months ששה חודשים אחרונים Last Year שנה אחרונה Last Session Details פרטים No %1 data available. %1 day of %2 Data on %3 %1 days of %2 Data, between %3 and %4 Days Used: %1 Low Use Days: %1 Compliance: %1% Days AHI of 5 or greater: %1 Best AHI Date: %1 AHI: %2 Worst AHI Best Flow Limitation Date: %1 FL: %2 Worst Flow Limtation No Flow Limitation on record Worst Large Leaks Date: %1 Leak: %2% No Large Leaks on record Worst CSR Date: %1 CSR: %2% No CSR on record Worst PB Date: %1 PB: %2% No PB on record Want more information? OSCAR needs all summary data loaded to calculate best/worst data for individual days. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Best RX Setting הגדרות המרשם הטובות ביותר Date: %1 - %2 AHI: %1 Total Hours: %1 Worst RX Setting הגדרות המרשם הגרועות ביותר Welcome Welcome to the Open Source CPAP Analysis Reporter What would you like to do? CPAP Importer Oximetry Wizard Daily View Overview מבט על Statistics סטטיסטיקה <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> It would be a good idea to check File->Preferences first, as there are some options that affect import. Note that some preferences are forced when a ResMed machine is detected First import can take a few minutes. יבוא ראשון יכול לקחת כמה דקות. The last time you used your %1... last night %2 days ago was %1 (on %2) %1 hours, %2 minutes and %3 seconds Your machine was on for %1. <font color = red>You only had the mask on for %1.</font> under over reasonably close to equal to You had an AHI of %1, which is %2 your %3 day average of %4. Your CPAP machine used a constant %1 %2 of air Your pressure was under %1 %2 for %3% of the time. Your machine used a constant %1-%2 %3 of air. Your EPAP pressure fixed at %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Your EPAP pressure was under %1 %2 for %3% of the time. Your machine was under %1-%2 %3 for %4% of the time. 1 day ago Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. gGraph %1 days gGraphView 100% zoom level Restore X-axis zoom to 100% to view entire selected period. Restore X-axis zoom to 100% to view entire day's data. Reset Graph Layout Resets all graphs to a uniform height and default order. Y-Axis Plots CPAP Overlays Oximeter Overlays Dotted Lines Double click title to pin / unpin Click and drag to reorder graphs Remove Clone Clone %1 Graph OSCAR-code-v1.3.1/Translations/Italiano.it.ts000066400000000000000000015101311417327530600206770ustar00rootroot00000000000000 AboutDialog &About &Informazioni Release Notes Informazioni sulla Versione Credits Riconoscimenti GPL License Licenza GPL Close Chiudi Show data folder Mostra Cartella Dati About OSCAR %1 info %1 Sorry, could not locate About file. Spiacente, non trovo il file Informazioni. Sorry, could not locate Credits file. Spiacente, non trovo il file Riconoscimenti. Sorry, could not locate Release Notes. Spiacente, non è stato possibile individuare le Note di rilascio. OSCAR %1 OSCAR %1 Important: Importante: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Poiché questa è una versione pre-release, si consiglia di eseguire <b>il backup manuale della cartella dati</b> prima di procedere, perché il tentativo di tornare indietro più tardi potrebbe eliminare i file. To see if the license text is available in your language, see %1. Per verificare se il testo della licenza sia disponibile nella vostra lingua, andate su %1. CMS50F37Loader Could not find the oximeter file: Is it file as in data file here? Non trovo il file dati dell'ossimetro: Could not open the oximeter file: Is it file as in data file here? Non posso aprire il file dati dell'ossimetro: CMS50Loader Could not get data transmission from oximeter. Impossibile ottenere dati dall'ossimetro. Please ensure you select 'upload' from the oximeter devices menu. Per favore, assicuratevi di selezionare 'invio file' dal menu del vostro ossimetro. Could not find the oximeter file: Non trovo il file dati dell'ossimetro: Could not open the oximeter file: Non posso aprire il file dati dell'ossimetro: CheckUpdates Checking for newer OSCAR versions Controllo delle nuove versioni di OSCAR Daily Go to the previous day Vai al giorno precedente Show or hide the calender Mostra o nascondi il calendario Go to the next day Vai al giorno dopo Go to the most recent day with data records Vai al giorno più recente con record di dati Events Eventi View Size Vedi Dimensione Notes Appunti Journal Diario B B u u Color Colore Small Piccolo Medium Medio Big Grande Zombie Zombi I'm feeling ... Mi sento un ... Weight Peso If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Se l'altezza è maggiore di zero nella finestra delle preferenze, impostare il peso qui mostrerà il valore BMI (Body Mass Index) Awesome Impressionante B.M.I. B.M.I. Bookmarks Segnalibro Add Bookmark Aggiungi Segnalibro Starts Inizia Remove Bookmark Rimuovi Segnalibro Flags Segnale Graphs Grafici Show/hide available graphs. Mostra/nascondi i grafici disponibili. Breakdown Interruzione events eventi UF1 UF1 UF2 UF2 Time at Pressure Tempo pressione No %1 events are recorded this day Nessun evento %1 viene registrato in questo giorno %1 event %1 evento %1 events %1 eventi Session Start Times Inizio Sessione Session End Times Fine Sessione Session Information Informazioni Sessione CPAP Sessions CPAP Sessione Sleep Stage Sessions Sessione Fase Di Sonno Position Sensor Sessions Sessioni del sensore di posizione Unknown Session Non conosco sessione Duration Durata Machine Settings Impostazioni macchina <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. b>Nota:/b> Tutte le impostazioni mostrate di seguito sono basate sull'ipotesi che non sia cambiato nulla rispetto ai giorni precedenti. Oximeter Information Informazioni sull'ossimetro SpO2 Desaturations SpO2 Desaturazioni Pulse Change events Eventi cambio pulsazioni SpO2 Baseline Used Linea di base SpO2 utilizzata (Mode and Pressure settings missing; yesterday's shown.) (Mancano le impostazioni di Modalità e Pressione; quelle di ieri sono mostrate.) Statistics Statistiche 99.5% 99,5% 10 of 10 Event Types 10 di 10 Tipi di Eventi This CPAP machine does NOT record detailed data Questa macchina CPAP NON registra dati dettagliati This bookmark is in a currently disabled area.. Questo segnalibro si trova in un'area attualmente disabilitata.. 10 of 10 Graphs 10 di 10 Grafici Oximetry Sessions Sessioni di ossimetria Details Dettagli Click to %1 this session. Fai clic su %1 questa sessione. disable Disabilita enable Abilita %1 Session #%2 %1 Sessione #%2 %1h %2m %3s %1h %2m %3s Model %1 - %2 Modello %1 - %2 PAP Mode: %1 Modalità PAP: %1 This day just contains summary data, only limited information is available. Questo giorno contiene solo i dati di sintesi, solo le informazioni limitate sono disponibili. Total time in apnea Tempo totale in apnea Time over leak redline Tempo su perdita fuori limite Total ramp time Tempo totale di rampa Time outside of ramp Tempo al di fuori della rampa Start Inizio End Fine Sorry, this machine only provides compliance data. Siamo spiacenti, questa macchina fornisce solo dati di conformità. Event Breakdown eventi Interruzione Unable to display Pie Chart on this system Impossibile visualizzare il grafico a torta su questo sistema Sessions all off! Le sessioni sono tutte spente! Sessions exist for this day but are switched off. Le sessioni esistono per questo giorno, ma sono spente. Impossibly short session Sessione incredibilmente breve Zero hours?? Zero ore?? BRICK :( BLOCCO! ;C Complain to your Equipment Provider! Lamentatevi con il vostro fornitore di attrezzature! "Nothing's here!" "Non c'è niente qui!" No data is available for this day. Non sono disponibili dati per questo giorno. Pick a Colour Scegli un colore Bookmark at %1 Segnalibro a %1 ExportCSV Export as CSV Esporta come CSV Dates: Date: Resolution: Risoluzione: Details Dettagli Sessions Sessione Daily Giornaliero Filename: Nome del file: Cancel Cancella Export Esporta Start: Avvia: End: Fine: Quick Range: Portata rapida: Most Recent Day Giorno più recente Last Week Ultima settimana Last Fortnight Ultime due settimane Last Month Ultimo mese Last 6 Months Ultimi 6 mesi Last Year Ultimo anno Everything Tutto Custom Personalizzato Details_ Dettagli Sessions_ Sessioni_ Summary_ Sommario_ Select file to export to Seleziona il file da esportare CSV Files (*.csv) File CSV (*.csv) DateTime Data Ora Session Sessione Event Evento Data/Duration Data/Durata Date Data Session Count numero di sessioni Start Avvio End Fine Total Time Tempo Totale AHI AHI Count Contare FPIconLoader Import Error Errore nell' Importare i Dati This Machine Record cannot be imported in this profile. Need more context about the meaning of "Machine Record" in the app Non è possibile importare i dati del dispositivo nel profilo. The Day records overlap with already existing content. More context needed about the meaning of Day Records in the app; Is the overlap partial or total? I dati giornalieri coincidono con contenuti già presenti. Help Hide this message Nascondi questo messaggio Search Topic: Cerca Argomento: Help Files are not yet available for %1 and will display in %2. I file di aiuto non sono ancora disponibili per%1 e verranno mostrati in %2. Help files do not appear to be present. I file di aiuto non sembrano essere presenti. HelpEngine did not set up correctly HelpEngine non è settato correttamente HelpEngine could not register documentation correctly. HelpEngine non ha registrato la documentazione correttamente. Contents Contenuto Index Indice Search Ricerca No documentation available Documentazione non disponibile Please wait a bit.. Indexing still in progress Attendi per favore..Creazione indice in corso No No %1 result(s) for "%2" %1 risultato(i) per %2 clear Chiaro MD300W1Loader Could not find the oximeter file: Impossibile trovare il file dell'ossimetro: Could not open the oximeter file: Impossibile aprire il file dell'ossimetro: MainWindow Welcome Benvenuto &Statistics &Statistiche Report Mode Modalità rapporto Standard Normale Monthly Mensile Date Range Intervallo di date Navigation Navigazione Profiles Profili Statistics Statistiche Daily Giornaliero Overview Panoramica Oximetry Ossimetria Import Importa Help Aiuto Bookmarks Segnalibri Records Registro &File &File Exp&ort Data Esp&orta Dati &View &Vista &Reset Graphs &Resetta Grafici &Help &Aiuto Troubleshooting Risoluzione dei problemi &Data &Dati &Advanced &Avanzate Purge Oximetry Data Elimina Dati di ossimetria Rebuild CPAP Data Ricostruire i dati CPAP &About OSCAR &Disclaimer OSCAR &Maximize Toggle &Massimizza Reset Graph &Heights Ripristina &Azzera i grafici Import &Viatom/Wellue Data Importazione Dati &Viatom/Wellue Show &Line Cursor Mostra il cursore &linea Show Daily Left Sidebar Mostra barra laterale sinistra giornaliera Show Daily Calendar Mostra il calendario giornaliero Report an Issue Segnalare un problema Standard graph order, good for CPAP, APAP, Bi-Level Ordine standard dei grafici, buono per CPAP, APAP, Bi-Level Advanced Avanzate Advanced graph order, good for ASV, AVAPS Ordine avanzato del grafico, buono per ASV, AVAPS Purge Current Selected Day Eliminazione del giorno corrente selezionato &CPAP &CPAP &Oximetry &Ossimetria &Sleep Stage &Fase del sonno &Position &Posizione &All except Notes &Tutto tranne le note All including &Notes Tutto incluso &Note &Preferences &Preferenze Purge ALL Machine Data Elimina tutti i dati della macchina &Import CPAP Card Data &Importa dati CPAP dalla scheda SD &Profiles &Profili Exit Uscita View &Daily Visualizza &Giornaliero Show Daily view Mostra vista Giornaliera View &Overview Visualizza &Panoramica Show Overview view Mostra vista Panoramica View &Welcome Visualizza &Benvenuto Use &AntiAliasing Use &AntiAliasing Maximize window Massimizzare la finestra Show Debug Pane Mostra riquadro di debug Reset sizes of graphs Reimposta le dimensioni dei grafici Take &Screenshot Fare &Screenshot O&ximetry Wizard Procedura guidata o&ssimetria Print &Report Stampa &Report &Edit Profile &Modifica profilo Online Users &Guide Guida &utenti online &Frequently Asked Questions &Domande frequenti &Automatic Oximetry Cleanup &Pulizia automatica dell'ossimetria Change &User Cambia &utente Purge &Current Selected Day Spurgo &giorno corrente selezionato Right &Sidebar Destra &barra laterale Show Right Sidebar Mostra barra laterale destra View S&tatistics Visualizza S&tatistiche View Statistics Visualizza statistiche Show Statistics view Mostra vista Statistiche Import &ZEO Data Importa dati &ZEO Import &Dreem Data Importa dati &Dreem Import RemStar &MSeries Data Importa dati Remstar &Mseries Sleep Disorder Terms &Glossary &Glossario dei Termini sui disturbi del sonno Change &Language Cambia &Lingua Change &Data Folder Cambia cartella &dati Import &Somnopose Data Importa &Somnopose Dati Current Days Giorno Corrente Create zip of CPAP data card Crea zip della scheda dati CPAP Create zip of OSCAR diagnostic logs Creare uno zip dei log diagnostici di OSCAR Create zip of all OSCAR data Crea zip di tutti i dati OSCAR System Information Informazioni di Sistema Show &Pie Chart Mostra &grafico torta Show Pie Chart on Daily page Mostra grafico delle torte sulla pagina giornaliera Show Personal Data Mostrare i dati personali Check For &Updates Controlla gli &Aggiornamenti Daily Sidebar Barra laterale giornaliera Daily Calendar Calendario giornaliero Backup &Journal Backup &Giornaliero Show Performance Information Mostra informazioni sulle prestazioni CSV Export Wizard Procedura guidata di esportazione CSV Export for Review Anteprima Esportazione &About &Informazioni E&xit U&scita Imported %1 CPAP session(s) from %2 Sessione(i) CPAP importata %1 da %2 Import Success Importato con successo Already up to date with CPAP data at %1 Già aggiornato con i dati CPAP a %1 Up to date Aggiornato Couldn't find any valid Machine Data at %1 Impossibile trovare dati macchina validi all'indirizzo %1 Import Problem Problema di importazione Please wait, importing from backup folder(s)... Attendere, l'importazione dalla cartella di backup (s)... Please insert your CPAP data card... Inserisci la tua scheda dati CPAP... Choose a folder Scegliere una cartella No profile has been selected for Import. Non è stato selezionato nessun profilo per l'importazione. Access to Import has been blocked while recalculations are in progress. Importazione è stata bloccata ricalcolo in corso. Import is already running in the background. L'importazione è già in esecuzione in background. A %1 file structure for a %2 was located at: Una struttura di file %1 per %2 si trovava a: A %1 file structure was located at: Una struttura di file %1 si trovava a: CPAP Data Located Localizzazione dei dati CPAP Would you like to import from this location? Vuoi importare da questa cartella? %1 (Profile: %2) %1 (Profilo: %2) Specify Specifiche Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Ricorda di selezionare la cartella principale o la lettera di unità della scheda dati, e non una cartella al suo interno. Check for updates not implemented Controlla gli aggiornamenti non implementati If you can read this, the restart command didn't work. You will have to do it yourself manually. Se riesci a leggere questo, il comando restart non ha funzionato. Dovrai farlo manualmente. Are you sure you want to rebuild all CPAP data for the following machine: Sei sicuro di voler ricostruire tutti i dati CPAP per la seguente macchina: For some reason, OSCAR does not have any backups for the following machine: Per qualche ragione, OSCAR non ha alcun backup per la seguente macchina: You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Stai per <dimensione carattere=+2>obliterare</font> database macchina di OSCAR per la macchina seguente:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: Un errore di autorizzazione file a causato un errore nel processo; si dovrà eliminare la seguente cartella manualmente: No help is available. Nessun aiuto è disponibile. The Glossary will open in your default browser Il Glossario si aprirà nel browser predefinito Export review is not yet implemented Il riesame delle esportazioni non è ancora stato attuato Would you like to zip this card? Vuoi chiudere questa card? Choose where to save zip Scegli dove salvare zip ZIP files (*.zip) File ZIP (*.zip) Creating zip... Creazione di zip... Calculating size... Calcolare la dimensione... Reporting issues is not yet implemented I problemi di segnalazione non sono ancora disponibili Help Browser Finestra di aiuto Loading profile "%1" Caricamento del profilo "%1" Import Reminder Importa promemoria Find your CPAP data card Trova la tua scheda dati CPAP Importing Data importazione dati Please open a profile first. Aprite prima un profilo. Access to Preferences has been blocked until recalculation completes. L'accesso alle Preferenze è stato bloccato fino al completamento dell'aggiornamento. Choose where to save screenshot Scegli dove salvare lo screenshot Image files (*.png) File immagine (*.png) There was an error saving screenshot to file "%1" C'è stato un errore durante il salvataggio dello screenshot nel file "%1" Screenshot saved to file "%1" Schermata salvata nel file "%1" The User's Guide will open in your default browser La Guida per l'utente si aprirà nel browser predefinito The FAQ is not yet implemented Le FAQ non sono ancora implementate Please note, that this could result in loss of data if OSCAR's backups have been disabled. Si prega di notare, che questo potrebbe comportare la perdita di dati se i backup di OSCAR sono stati disabilitati. Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Anche se tu hai eseguito<i>il tuo <b>personale</b> backup per TUTTI i dati CPAP</i>, è comunque possibile completare questa operazione, ma sarà necessario ripristinare manualmente i backup. Are you really sure you want to do this? Sei davvero sicuro di volerlo fare? Because there are no internal backups to rebuild from, you will have to restore from your own. Poiché non ci sono backup interni per ricostruire, si dovrà ripristinare dal proprio. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Vuoi importare dai tuoi backup ora? (non avrai dati visibili per questa macchina finché non lo fai) Note as a precaution, the backup folder will be left in place. Nota come precauzione, la cartella di backup verrà lasciata al suo posto. OSCAR does not have any backups for this machine! OSCAR non ha alcun backup per questa macchina! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> A meno che tu non abbia fatto <i>il tuo <b>personale</b> backup per TUTTI i tuoi dati per questa macchina</i>, <font size=+2>perderai i dati di questa macchina <b>permanentemente</b>! </font> Are you <b>absolutely sure</b> you want to proceed? Sei assolutamente sicuro <b></b> di voler procedere? There was a problem opening MSeries block File: C'era un problema di apertura Mseries file bloccato: MSeries Import complete Importazione Mseries completa There was a problem opening %1 Data File: %2 C'è stato un problema nell'aprire %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Importazione dati di %2 file completata %1 Import Partial Success %1 Importazione con successo parziale %1 Data Import complete %1 Importazione dati completa Are you sure you want to delete oximetry data for %1 Sei sicuro di voler eliminare i dati di ossimetria per %1 <b>Please be aware you can not undo this operation!</b> <b>Tieni presente che non puoi annullare questa operazione! </b> Select the day with valid oximetry data in daily view first. Selezionare prima il giorno con i dati ossigenometria validi nella vista giornaliera. %1's Journal %1 Diario Choose where to save journal Scegli dove salvare il diario XML Files (*.xml) File XML (*.xml) OSCAR Information Informazioni OSCAR MinMaxWidget Auto-Fit Auto-adattare Defaults Impostazioni predefinite Override Sovrascrivere The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. La modalità di ridimensionamento Y-Axis, 'Auto-adattare per il ridimensionamento automatico, 'impostazioni predefinite' per le impostazioni secondo il costruttore, e 'sovrascrivere' per scegliere la propria. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Il valore Asse Y minimo.. Nota che questo può essere un numero negativo se lo desideri. The Maximum Y-Axis value.. Must be greater than Minimum to work. Il valore Massimo dell'asse Y.. Deve essere più grande del Minimo per funzionare. Scaling Mode Modalità di ridimensionamento This button resets the Min and Max to match the Auto-Fit Questo pulsante ripristina il Min e Max per abbinare l'Auto-adattare NewProfile Edit User Profile Modifica profilo utente about:blank about:blank I agree to all the conditions above. Accetto tutte le condizioni di cui sopra. User Information Informazioni Utente User Name Nome Utente Very weak password protection and not recommended if security is required. Protezione con password molto debole e non raccomandata se è richiesta la sicurezza. Password Protect Profile Proteggi profilo con Password Password Password ...twice... ...doppio... Locale Settings impostazioni locali DST Zone Zona Ora Legale TimeZone Fuso orario Country Stato Personal Information (for reports) Informazioni personali (per le relazioni) First Name Nome Last Name Cognome It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Puoi anche saltare questa opzione, ma la tua età è necessaria per migliorare l'accuratezza di alcuni calcoli. D.O.B. Data di nascita. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Il genere biologico (nascita) è talvolta necessario per migliorare l'accuratezza di alcuni calcoli, sentitevi liberi di lasciare questo vuoto e saltarne uno qualsiasi. </p></body></html> Gender Sesso Male Maschio Female Femmina Height Altezza Metric Sistema metrico English Sistema britannico Contact Information Informazioni di contatto Address Indirizzo Email Email Phone Telefono CPAP Treatment Information Informazioni sul trattamento CPAP Date Diagnosed Data della diagnosi Untreated AHI AHI non trattata CPAP Mode Modalità CPAP Mode CPAP CPAP APAP APAP Bi-Level Bi-Level ASV Ventilazione servoadattiva (ASV) RX Pressure Pressione Prescritta Doctors / Clinic Information Medici / Informazioni cliniche Doctors Name Nome Dottore Practice Name Nome Pratica Patient ID ID Identificazione del paziente &Cancel &Cancella &Back &Indietro &Next &Prossimo Select Country Seleziona Stato Welcome to the Open Source CPAP Analysis Reporter Benvenuto all'Open Source CPAP Analysis Reporter This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Questo software è stato progettato per assistervi nella revisione dei dati prodotti dalle vostre macchine CPAP e relative apparecchiature. PLEASE READ CAREFULLY Si prega di leggere attentamente Accuracy of any data displayed is not and can not be guaranteed. La precisione dei dati visualizzati non è e non può essere garantita. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Tutti i rapporti generati sono SOLTANTO PER USO PERSONALE e IN ALCUN MODO adatti per conformità o scopi diagnostici medici. Use of this software is entirely at your own risk. L'uso di questo software è interamente a proprio rischio. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR è copyright &copy;2011-2018 Mark Watkins e parti &copy;2019-2022 The OSCAR Team OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR è stato rilasciato gratuitamente sotto la licenza pubblica <a href='qrc:/COPYING'>GNU v3</a>, e viene fornito senza garanzia e senza alcuna pretesa di idoneità per qualsiasi scopo. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR è inteso semplicemente come un visualizzatore di dati, e sicuramente non un sostituto del vostro medico. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Gli autori non saranno responsabili per <u>qualsiasi cosa</u> relativa all'uso o all'uso improprio di questo software. Please provide a username for this profile Fornire un nome utente per questo profilo Passwords don't match Le password non corrispondono Profile Changes Cambia Profilo Accept and save this information? Accettare e salvare queste informazioni? &Finish &Fine &Close this window &Chiudi questa finestra Overview Range: Gamma: Last Week La settimana scorsa Last Two Weeks Le ultime due settimane Last Month Lo scorso mese Last Two Months Ultimi due mesi Last Three Months Ultimi tre mesi Last 6 Months Ultimi 6 mesi Last Year L'anno scorso Everything Qualunque cosa Custom Personalizzato Snapshot Snapshot Start: Inizio: End: Fine: Reset view to selected date range Ripristina la vista sull'intervallo di date selezionato Toggle Graph Visibility Attiva / disattiva la visibilità del grafico Drop down to see list of graphs to switch on/off. Scorri verso il basso per visualizzare l'elenco dei grafici da attivare / disattivare. Graphs Grafici Respiratory Disturbance Index Indice Disturbo Respiratorio Apnea Hypopnea Index Indice Ipopnea Apnea (eventi per ora) Usage Uso Usage (hours) Uso (ore) Session Times Tempi di sessione Total Time in Apnea Tempo totale in apnea Total Time in Apnea (Minutes) Tempo totale in apnea (Minuti) Body Mass Index Indice Massa Corporea How you felt (0-10) Come vi siete sentiti (0-10) 10 of 10 Charts 10 di 10 Grafici Show all graphs Mostra tutti i grafici Hide all graphs Nascondi tutti i grafici OximeterImport Oximeter Import Wizard Importazione guidata ossimetro Skip this page next time. Salta questa pagina la prossima volta. Where would you like to import from? Da dove vorresti importare? CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Imposta data / ora del dispositivo <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body> <p> Seleziona per abilitare l'aggiornamento dell'identificatore del dispositivo alla prossima importazione, utile per chi ha più ossimetri in giro. </p> </body> </html> Set device identifier Imposta identificatore dispositivo CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. Gli utenti CMS50E / F, durante l'importazione diretta, non selezionare il caricamento sul dispositivo fino a quando OSCAR non lo richiede. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Notare prego: </span><span style=" font-style:italic;">Prima seleziona il tipo di ossimetro corretto dal menù a discesa sotto.</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">PRIMA Selezona il tuo Ossimetro tra questi gruppi:</span></p></body></html> <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body> <p> Se abilitato, OSCAR ripristinerà automaticamente l'orologio interno del CMS50 utilizzando l'ora corrente del computer. </p> </body> </html> Erase session after successful upload Cancella sessione dopo il caricamento riuscito Import directly from a recording on a device Importa direttamente da una registrazione su un dispositivo <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body> <p> Se non ti dispiace essere attaccato a un cardiofrequenzimetro durante la notte, questa opzione fornisce un utile grafico pletisomogramma, che fornisce un'indicazione del ritmo cardiaco, oltre alla normale ossimetria letture. </ p> </ body> </ html> Record attached to computer overnight (provides plethysomogram) Record collegato al computer durante la notte (fornisce pletisomogramma) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body> <p> Questa opzione consente di importare da file di dati creati dal software fornito con l'ossimetro, come SpO2Review. </p> </body> </html> Import from a datafile saved by another program, like SpO2Review Importa da un file di dati salvato da un altro programma, come SpO2Review <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body> <p> <span style = "font-weight: 600; font-style: italic;"> Promemoria per gli utenti CPAP: </span> <span style = "color: # fb0000 ; "> Ti sei ricordato di importare prima le sessioni CPAP? <br/> </span> Se dimentichi, non avrai un tempo valido per sincronizzare questa sessione di ossimetria. <br/> Per garantire una buona sincronizzazione tra dispositivi, cerca sempre di avviarli entrambi contemporaneamente. </p> </body> </html> Please connect your oximeter device Si prega di collegare il dispositivo ossimetro If you can read this, you likely have your oximeter type set wrong in preferences. Se riesci a leggere questo, è probabile che il tuo tipo di ossimetro sia impostato in modo errato nelle preferenze. Press Start to commence recording Premere Avvio per iniziare la registrazione Show Live Graphs Mostra grafici live Duration Durata Pulse Rate Pulsazioni Multiple Sessions Detected Rilevate più sessioni Please choose which one you want to import into OSCAR Scegli quale vuoi importare in OSCAR Day recording (normally would have) started Giorno di registrazione (normalmente dovrebbe essere) partito <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body> <p> OSCAR ha bisogno di un orario di inizio per sapere dove salvare questa sessione di ossimetria. </p> <p> Scegli una delle seguenti opzioni: </p> </body> </ html> Start Time Ora di inizio CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body> <p> Qui puoi inserire un nome di 7 caratteri per questo ossimetro. </p> </body> </html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body> <p> Questa opzione cancellerà la sessione importata dall'ossimetro dopo il completamento dell'importazione. </p> <p> Usa con cautela, perché se qualcosa va storto prima che OSCAR salvi la sessione, non puoi recuperarla. </p> </body> </html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body> <p> Questa opzione ti consente di importare (via cavo) dalle registrazioni interne dei tuoi ossimetri. </p> <p> Dopo aver selezionato questa opzione, i vecchi ossimetri Contec richiedono di utilizzare il menu del dispositivo per avviare il caricamento. </p> </body> </html> Please connect your oximeter device, turn it on, and enter the menu Connetti il ​​tuo ossimetro, accendilo ed entra nel menu Details Dettagli Import Completed. When did the recording start? Importazione completata. Quando è iniziata la registrazione? Oximeter Starting time Ossimetro Tempo di inizio I want to use the time reported by my oximeter's built in clock. Voglio usare il tempo riportato dall'orologio incorporato del mio ossimetro. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Ho iniziato questa registrazione dell'ossimetro (o quasi) contemporaneamente a una sessione sulla mia macchina CPAP. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body> <p> Nota: la sincronizzazione con l'ora di inizio della sessione CPAP sarà sempre più accurata. </p> </body> </html> Choose CPAP session to sync to: Scegli la sessione CPAP da sincronizzare con: You can manually adjust the time here if required: È possibile regolare manualmente l'ora qui, se necessario: HH:mm:ssap HH:mm:ss &Information Page &pagina informazioni &Cancel &Annulla &Retry &Riprova &Choose Session &Scegli sessione &End Recording &Termina la registrazione &Sync and Save &Sincronizza e salva &Save and Finish &Salva e termina &Start &Inizio Scanning for compatible oximeters Scansione per ossimetri compatibili Could not detect any connected oximeter devices. Impossibile rilevare alcun dispositivo ossimetro collegato. Connecting to %1 Oximeter Collegamento all'ossimetro %1 Renaming this oximeter from '%1' to '%2' Rinomina questo ossimetro da '%1' a '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Il nome dell'ossimetro è diverso. Se ne hai solo uno e lo condividi tra i profili, imposta il nome sullo stesso su entrambi i profili. "%1", session %2 "%1", sessione %2 Nothing to import Niente da importare Your oximeter did not have any valid sessions. Il tuo ossimetro non ha avuto sessioni valide. Close Chiudi Waiting for %1 to start In attesa dell'inizio di %1 Waiting for the device to start the upload process... In attesa che il dispositivo avvii il processo di caricamento ... Select upload option on %1 Seleziona l'opzione di caricamento su %1 You need to tell your oximeter to begin sending data to the computer. Devi dire al tuo ossimetro di iniziare a inviare i dati al computer. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Collega il tuo ossimetro, accedi al menu e seleziona il caricamento per iniziare il trasferimento dei dati ... %1 device is uploading data... %1 dispositivo sta caricando dati ... Please wait until oximeter upload process completes. Do not unplug your oximeter. Attendere il completamento del processo di caricamento dell'ossimetro. Non scollegare l'ossimetro. Oximeter import completed.. Importazione ossimetro completata.. Select a valid oximetry data file Seleziona un file di dati per ossimetria valido Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) File di ossimetria (* .spo * .spor * .spo2 * .SpO2 * .dat) No Oximetry module could parse the given file: Nessun modulo di ossimetria potrebbe analizzare il file indicato: Live Oximetry Mode Modalità ossimetria dal vivo Live Oximetry Stopped Ossimetria dal vivo interrotta Live Oximetry import has been stopped L'importazione di ossimetria dal vivo è stata interrotta Oximeter Session %1 Sessione ossimetro %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR ti dà la possibilità di tracciare i dati dell'ossimetria insieme ai dati della sessione CPAP, che possono fornire preziose informazioni sull'efficacia del trattamento CPAP. Funzionerà anche autonomamente con il pulsossimetro, consentendoti di archiviare, tracciare e rivedere i dati registrati. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Se stai cercando di sincronizzare i dati di ossimetria e CPAP, assicurati di aver importato le tue sessioni CPAP prima di procedere! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Affinché OSCAR sia in grado di individuare e leggere direttamente dal proprio dispositivo ossimetro, è necessario assicurarsi che sul computer siano stati installati i driver di dispositivo corretti (ad es. Da USB a UART seriale). Per ulteriori informazioni al riguardo,%1clicca qui%2. Oximeter not detected Ossimetro non rilevato Couldn't access oximeter Impossibile rilevare dispositivo ossimetro collegato Starting up... Per iniziare a... If you can still read this after a few seconds, cancel and try again Se riesci ancora a leggere questo dopo alcuni secondi, annulla e riprova Live Import Stopped Importazione live interrotta %1 session(s) on %2, starting at %3 %1 sessioni su %2, a partire da %3 No CPAP data available on %1 Nessun dato CPAP disponibile su %1 Recording... Registrazione... Finger not detected Dito non rilevato I want to use the time my computer recorded for this live oximetry session. Voglio usare il tempo registrato dal mio computer per questa sessione di ossimetria dal vivo. I need to set the time manually, because my oximeter doesn't have an internal clock. Devo impostare l'ora manualmente, perché il mio ossimetro non ha un orologio interno. Something went wrong getting session data Welcome to the Oximeter Import Wizard Benvenuti alla procedura guidata di importazione dell'ossimetro Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. I pulsossimetri sono dispositivi medici utilizzati per misurare la saturazione di ossigeno nel sangue. Durante gli eventi estesi di apnea e i modelli di respirazione anormali, i livelli di saturazione di ossigeno nel sangue possono calare in modo significativo e possono indicare problemi che richiedono cure mediche. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR è attualmente compatibile con contossimetri seriali Contec CMS50D +, CMS50E, CMS50F e CMS50I. <br/> (Nota: l'importazione diretta da modelli bluetooth è <span style = "font-weight: 600;"> probabilmente non </span> ancora possibile ) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Si consiglia di notare che altre società come Pulox, semplicemente reintegrano Contec CMS50 con nuovi nomi, come Pulox PO-200, PO-300, PO-400. Anche questi dovrebbero funzionare. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Può anche leggere dai file .dat del saturimetro ChoiceMMed MD300W1. Please remember: Per favore ricorda: Important Notes: Note importanti: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. I dispositivi Contec CMS50D + non dispongono di un orologio interno e non registrano un orario di inizio. Se non si dispone di una sessione CPAP a cui collegare una registrazione, sarà necessario inserire l'ora di inizio manualmente al termine del processo di importazione. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Anche per i dispositivi con un orologio interno, si consiglia comunque di prendere l'abitudine di avviare i record dell'ossimetro contemporaneamente alle sessioni CPAP, poiché gli orologi interni CPAP tendono a spostarsi nel tempo e non tutti possono essere ripristinati facilmente. Oximetry Date Data d/MM/yy h:mm:ss AP Maintained h in its original form g/MM/aa h:mm:ss AP R&eset R&esettare Pulse Alternative: Polso Pulsazioni &Open .spo/R File &Apri file . Spo/R Serial &Import Need context Importa &Seriale &Start Live &Avvia in Tempo Reale Serial Port Porta Seriale &Rescan Ports &Ricontrollare Porte PreferencesDialog Preferences Preferenze &Import &Importare Session Splitting Settings Impostazioni di suddivisione della sessione Combine Close Sessions Combina sessioni chiuse Minutes Minuti Multiple sessions closer together than this value will be kept on the same day. Sessioni multiple più vicine di questo valore verranno mantenute lo stesso giorno. Ignore Short Sessions Ignora sessioni brevi <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <! DOCTYPE HTML PUBLIC "- // W3C // DTD HTML 4.0 // EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html> <head> <meta name = "qrichtext" content = "1" /> <style type = "text / css"> p, li {white-space: pre-wrap; } </style> </head> <body style = "font-family: 'Cantarell'; font-size: 11pt; font-weight: 400; font-style: normal;"> <p style = "margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-block-indent: 0; text-indent: 0px;"> Sessioni di durata inferiore di ciò non verrà visualizzato <span style = "font-style: italic;">. </span> </p> <p style = "- qt-paragraph-type: empty; margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-block-indent: 0; text-indent : 0px; stile carattere: corsivo; "> </p> </body> </html> Day Split Time Tempo parziale del giorno Sessions starting before this time will go to the previous calendar day. Le sessioni che iniziano prima di questo orario andranno al giorno di calendario precedente. <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body> <p> <span style = "font-weight: 600;"> Questa impostazione deve essere usata con cautela ... </span> La disattivazione comporta conseguenze che riguardano l'accuratezza del riepilogo solo giorni, poiché alcuni calcoli funzionano correttamente solo se vengono tenute insieme solo le sessioni riepilogative provenienti dai record dei singoli giorni. </p> <p> <span style = "font-weight: 600;"> Utenti ResMed: </span> Solo perché mi sembra naturale e io che il riavvio della sessione di mezzogiorno dovrebbe avvenire il giorno precedente, lo fa non significa che i dati di ResMed siano d'accordo con noi. Il formato dell'indice di riepilogo STF.edf presenta serie debolezze che rendono questa non una buona idea. </p> <p> Questa opzione esiste per pacificare coloro che non si preoccupano e vogliono vedere questo "riparato". non importa i costi, ma sappi che ha un costo. Se mantieni la tua scheda SD ogni notte e importa almeno una volta alla settimana, non vedrai problemi con questo molto spesso. </p> </body> </html> Don't Split Summary Days (Warning: read the tooltip!) Non dividere i giorni di riepilogo (attenzione: leggi la descrizione!) Session Storage Options Opzioni di archiviazione della sessione Compress SD Card Backups (slower first import, but makes backups smaller) Comprimi i backup della scheda SD (prima importazione più lenta, ma riduce i backup) Create SD Card Backups during Import (Turn this off at your own peril!) Crea backup della scheda SD durante l'importazione (disattivala a tuo rischio e pericolo!) Do not import sessions older than: Non importare sessioni più vecchie di: Sessions older than this date will not be imported Le sessioni più vecchie di questa data non verranno importate dd MMMM yyyy gg/MM/aaaaa Memory and Startup Options Memoria e opzioni di avvio Pre-Load all summary data at startup Pre-carica tutti i dati di riepilogo all'avvio <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body> <p> Questa impostazione mantiene in memoria i dati della forma d'onda e degli eventi dopo l'uso per accelerare la rivisitazione dei giorni. </p> <p> Questa non è davvero un'opzione necessaria, poiché il sistema operativo memorizza nella cache anche i file utilizzati in precedenza. </p> <p> La raccomandazione è di lasciarlo spento, a meno che il computer non abbia una tonnellata di memoria. </p> </body> </html> Keep Waveform/Event data in memory Conserva i dati di forma d'onda / evento in memoria <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body> <p> Riduce eventuali dialoghi di conferma non importanti durante l'importazione. </p> </body> </html> Import without asking for confirmation Importa senza chiedere conferma Bypass the login screen and load the most recent User Profile Bypassa la schermata di accesso e carica il profilo utente più recente &CPAP &CPAP CPAP Clock Drift Deriva dell'orologio CPAP <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body> <p> Nota: questo non è destinato alle correzioni del fuso orario! Assicurati che l'orologio e il fuso orario del tuo sistema operativo siano impostati correttamente. </p> </body> </html> Hours Ore Seconds secondi This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Questo calcolo richiede che i dati sulle perdite totali siano forniti dalla macchina CPAP. (Ad esempio, PRS1, ma non ResMed, che ha già questi) I calcoli della perdita involontaria utilizzati qui sono lineari, non modellano la curva di sfiato della maschera. Se usi alcune maschere diverse, scegli invece valori medi. Dovrebbe essere ancora abbastanza vicino. Calculate Unintentional Leaks When Not Present Calcola perdite involontarie quando non presenti 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Nota: viene utilizzato un metodo di calcolo lineare. La modifica di questi valori richiede un ricalcolo. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Abilita / disabilita i miglioramenti della segnalazione di eventi sperimentali. Permette di rilevare eventi borderline e alcuni hanno perso la macchina. Questa opzione deve essere abilitata prima dell'importazione, altrimenti è necessario uno spurgo. Custom CPAP User Event Flagging Contrassegno evento utente CPAP personalizzato s s Resync Machine Detected Events (Experimental) Risincronizza eventi rilevati macchina (sperimentale) Flow Restriction Limitazione del flusso <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <! DOCTYPE HTML PUBLIC "- // W3C // DTD HTML 4.0 // EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html> <head> <meta name = "qrichtext" content = "1" /> <style type = "text / css"> p, li {white-space: pre-wrap; } </style> </head> <body style = "font-family: 'Sans'; font-size: 10pt; font-weight: 400; font-style: italic;"> <p style = "margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-block-indent: 0; text-indent: 0px;"> La segnalazione personalizzata è un metodo sperimentale per rilevare eventi persi dalla macchina. Sono <span style = "text-decoration: underline;"> non </span> inclusi in AHI. </p> </body> </html> Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Percentuale di restrizione nel flusso d'aria dal valore mediano. Un valore del 20% funziona bene per il rilevamento di apnee. Show in Event Breakdown Piechart Mostra in Grafico a torta suddivisione eventi Duration of airflow restriction Durata della restrizione del flusso d'aria Allow duplicates near machine events. Consenti duplicati vicino ad eventi macchina. Event Duration Durata dell'evento General CPAP and Related Settings CPAP generale e impostazioni correlate Show flags for machine detected events that haven't been identified yet. Mostra i flag per gli eventi rilevati dalla macchina che non sono stati ancora identificati. Enable Unknown Events Channels Abilita canali di eventi sconosciuti AHI/Hour Graph Time Window Finestra temporale del grafico AHI / ora Compliance defined as Conformità definita come Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Regola la quantità di dati considerati per ciascun punto nel grafico AHI / Ora. Il valore predefinito è 60 minuti. Consiglio vivamente di lasciare questo valore. minutes minuti Whether to show the leak redline in the leak graph Indica se mostrare la linea rossa delle perdite nel grafico delle perdite Flag leaks over threshold Perdite aria oltre il limite Preferred major event index Indice maggiore evento preferito Reset the counter to zero at beginning of each (time) window. Reimpostare il contatore su zero all'inizio di ciascuna finestra (orario). Zero Reset Ripristino zero User definable threshold considered large leak Soglia definibile dall'utente considerata grande perdita Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Considera i giorni con questo utilizzo come "incompleti". 4 ore sono generalmente considerate conformi. hours ore AHI Apnea Hypopnea Index AHI RDI Respiratory Disturbance Index Indice di disturbo respiratorio RDI Changes to the following settings needs a restart, but not a recalc. Le modifiche alle seguenti impostazioni richiedono un riavvio, ma non un ricalcolo. Preferred Calculation Methods Metodi di calcolo preferiti Upper Percentile Percentile superiore Maximum Calcs Calcoli massimi For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Per coerenza, gli utenti ResMed dovrebbero utilizzare qui il 95%, poiché questo è l'unico valore disponibile nei giorni di solo riepilogo. Middle Calculations Calcoli intermedi Median is recommended for ResMed users. La mediana è consigliata per gli utenti ResMed. Median Mediana Weighted Average Media Ponderata Normal Average Media Normale <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body> <p> Il massimo vero è il massimo del set di dati. </p> <p> Il 99o percentile filtra i valori anomali più rari. </p> </body> </html> True Maximum Vero Massimo 99% Percentile 99% percentile Combined Count divided by Total Hours Conteggio combinato diviso per ore totali Time Weighted average of Indice Media ponderata nel tempo di Indice Standard average of indice Media standard dell'indice Changing SD Backup compression options doesn't automatically recompress backup data. La modifica delle opzioni di compressione del backup SD non comporta la ricompressione automatica dei dati di backup. Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Comprimi i backup ResMed (EDF) per risparmiare spazio su disco. I file EDF di backup sono archiviati nel formato .gz, che è comune su piattaforme Mac e Linux .. OSCAR può importare nativamente da questa directory di backup compressa. Per utilizzarlo con ResScan è necessario prima decomprimere i file .gz. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Le seguenti opzioni influiscono sulla quantità di spazio su disco utilizzata da OSCAR e influiscono sulla durata dell'importazione. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Ciò consente ai dati di OSCAR di occupare circa la metà dello spazio. Ma richiede più tempo per importare e cambiare giorno .. Se hai un nuovo computer con un piccolo disco a stato solido, questa è una buona opzione. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Comprimi i backup della scheda SD (prima importazione più lenta, ma riduce i backup) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Ciò mantiene un backup dei dati della scheda SD per macchine ResMed, Le macchine ResMed serie S9 eliminano i dati ad alta risoluzione di oltre 7 giorni, e tracciare un grafico dei dati più vecchi di 30 giorni .. OSCAR può conservare una copia di questi dati se è necessario reinstallare. (Altamente raccomandato, a meno che tu non abbia poco spazio sul disco o non ti interessi dei dati del grafico) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body> <p> Rende un po 'più lento l'avvio di OSCAR, pre-caricando in anticipo tutti i dati di riepilogo, velocizzando la navigazione della panoramica e alcuni altri calcoli in seguito. </p> <p> Se disponi di una grande quantità di dati, potrebbe valere la pena tenerlo spento, ma se in genere ti piace visualizzare <span style = "font-style: italic;"> tutto </span> in sintesi, tutti i dati di riepilogo devono comunque essere caricati comunque. </p> <p> Nota che questa impostazione non influenza la forma d'onda e i dati degli eventi, che vengono sempre caricati come richiesto. </p> </body> </html> <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body> <p> Fornisce un avviso durante l'importazione di dati da qualsiasi modello di macchina che non è stato ancora testato dagli sviluppatori OSCAR. </p> </body> </html> Warn when importing data from an untested machine Avvisa quando si importano dati da una macchina non testata <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body> <p> Fornisce un avviso durante l'importazione di dati che è in qualche modo diverso da qualsiasi cosa vista in precedenza dagli sviluppatori OSCAR. </p> </body> </html> Warn when previously unseen data is encountered Avvisa quando vengono rilevati dati precedentemente non visti Your masks vent rate at 20 cmH2O pressure La frequenza di sfiato delle maschere a una pressione di 20 cm H2O Your masks vent rate at 4 cmH2O pressure La frequenza di sfiato delle maschere a una pressione di 4 cm H2O This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Questa opzione sperimentale tenta di utilizzare il sistema di segnalazione eventi di OSCAR per migliorare il posizionamento degli eventi rilevati dalla macchina.. &Oximetry &Ossimetria Other oximetry options Altre opzioni di ossimetria bpm bpm Small chunks of oximetry data under this amount will be discarded. Piccoli pezzi di dati di ossimetria al di sotto di questo importo verranno scartati. Discard segments under Elimina i segmenti in Flag Pulse Rate Below Frequenza delle pulsazioni qui sotto la soglia Flag Pulse Rate Above Frequenza del polso oltre la soglia Flag rapid changes in oximetry stats Contrassegna rapidi cambiamenti nelle statistiche dell'ossimetria Minimum duration of drop in oxygen saturation Durata minima della caduta nella saturazione di ossigeno Sudden change in Pulse Rate of at least this amount Improvvisa variazione della frequenza del polso di almeno questo valore Minimum duration of pulse change event. Durata minima dell'evento di cambio pulsazioni. Pulse Pulsazioni Percentage drop in oxygen saturation Caduta percentuale della saturazione di ossigeno Events Evento Search Ricerca Reset &Defaults Reimpostazione &valori iniziali <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body> <p> <span style = "font-weight: 600;"> Avvertenza: </span> Solo perché puoi, non significa che sia una buona pratica. </p> </ body> </ html> Waveforms Forme d'onda &General &Generale General Settings Impostazioni generali Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Consentire l'uso di più core della CPU ove disponibili per migliorare le prestazioni. Colpisce principalmente l'importatore. Enable Multithreading Abilita multithreading Show Remove Card reminder notification on OSCAR shutdown Mostra notifica promemoria Rimuovi scheda all'arresto di OSCAR Always save screenshots in the OSCAR Data folder Salvare sempre gli screenshot nella cartella Dati OSCAR Check for new version every Controlla la nuova versione ogni days. giorni. Last Checked For Updates: Ultimi aggiornamenti controllati: TextLabel etichetta di testo I want to be notified of test versions. (Advanced users only please.) Io desidero che mi siano notificate le versioni beta. (Solo per utenti avanzati, grazie) &Appearance &Aspetto Graph Settings Impostazioni del grafico <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body> <p> Quale scheda aprire al caricamento di un profilo. (Nota: il profilo predefinito sarà se OSCAR è impostato per non aprire un profilo all'avvio) </p> </body> </html> Graph Height Altezza del grafico Overlay Flags Sovrapposte Line Thickness Spessore della linea The visual method of displaying waveform overlay flags. Il metodo visivo di visualizzazione dei flag di sovrapposizione delle forme d'onda. Standard Bars Barre standard Top Markers I migliori marcatori The pixel thickness of line plots Lo spessore in pixel dei grafici a linee Scroll Dampening Smorzamento dello scorrimento How long you want the tooltips to stay visible. Per quanto tempo vuoi che i tooltip rimangano visibili. Whether to include machine serial number on machine settings changes report Se includere il numero di serie della macchina nel report delle modifiche alle impostazioni della macchina Include Serial Number Includi il numero di serie Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Prova a modificarlo dall'impostazione predefinita (Desktop OpenGL) se riscontri problemi di rendering con i grafici di OSCAR. Tooltip Timeout Timeout della descrizione comandi <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body> <p> Ciò semplifica lo scorrimento quando si esegue lo zoom avanti su TouchPad bidirezionali sensibili </p> <p> 50ms è un valore raccomandato. </p> </body> </html> Default display height of graphs in pixels Altezza di visualizzazione predefinita dei grafici in pixel Auto-Launch CPAP Importer after opening profile Avvia importazione CPAP automatico dopo l'apertura del profilo Automatically load last used profile on start-up Carica automaticamente l'ultimo profilo utilizzato all'avvio <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body> <p> <span style = "font-weight: 600;"> Nota: </span> A causa delle limitazioni del progetto di riepilogo, le macchine ResMed non supportano la modifica di queste impostazioni. </ p > </ body> </ html> Oximetry Settings Impostazioni ossimetria Check For Updates Controlla gli aggiornamenti You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. State usando una versione di prova di OSCAR. Le versioni di prova controllano automaticamente gli aggiornamenti almeno una volta ogni sette giorni. Puoi impostare l'intervallo a meno di sette giorni. Automatically check for updates Controlla automaticamente gli aggiornamenti How often OSCAR should check for updates. Quanto spesso OSCAR dovrebbe controllare gli aggiornamenti. If you are interested in helping test new features and bugfixes early, click here. Se sei interessato ad aiutare a testare in anticipo nuove caratteristiche e correzioni di bug, clicca qui. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Se vuoi aiutare a testare le prime versioni di OSCAR, per favore vedi la pagina Wiki sui test di OSCAR. Diamo il benvenuto a chiunque voglia testare OSCAR, aiutare a sviluppare OSCAR, e aiutare con le traduzioni in lingue esistenti o nuove. https://www.sleepfiles.com/OSCAR On Opening All'apertura Profile Profilo Welcome Benvenuto Daily Giornaliero Statistics Statistiche Switch Tabs Cambia scheda No change Nessun cambiamento After Import Dopo l'importazione Graph Tooltips Descrizioni comandi del grafico Bar Tops Bar Tops Line Chart Grafico a linee Overview Linecharts Panoramica Grafici a linee Other Visual Settings Altre impostazioni visive Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. L'antialiasing applica il livellamento ai grafici. Alcune trame sembrano più attraenti con questo. Ciò influisce anche sui rapporti stampati. Provalo e vedi se ti piace. Use Anti-Aliasing Usa l'antialiasing Makes certain plots look more "square waved". Rende alcuni grafici più "ondulati quadrati". Square Wave Plots Grafici ad onda quadra Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. La memorizzazione nella cache di Pixmap è una tecnica di accelerazione grafica. Potrebbe causare problemi con il disegno dei caratteri nell'area di visualizzazione del grafico sulla piattaforma. Use Pixmap Caching Usa la memorizzazione nella cache di Pixmap <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body> <p> Queste funzionalità sono state recentemente potate. Torneranno più tardi. </ P> </ body> </ html> Animations && Fancy Stuff Animazioni && gadget Daily view navigation buttons will skip over days without data records I pulsanti di navigazione della vista giornaliera salteranno nei giorni senza record di dati Skip over Empty Days Passa sopra i giorni vuoti Whether to allow changing yAxis scales by double clicking on yAxis labels Se consentire la modifica delle scale yAxis facendo doppio clic sulle etichette yAxis Allow YAxis Scaling Consenti ridimensionamento YAxis Graphics Engine (Requires Restart) Motore grafico (richiede il riavvio) l/min l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Indici Cumulativi</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <html><head/><body><p>Segnalazione SpO<span style=" vertical-align:sub;">2</span> Desaturazione Sotto</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Sincronizzazione Ossimetro e Dati CPAP</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">I dati iCMS50 importadi da SpO2Review (dai files .spoR) o il metodo di importazione seriale </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">non</span><span style=" font-family:'Sans'; font-size:10pt;"> hano il corretto timestamp necessario alla sincronizzazione.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Il modo di visualizzazione Live (usando il cavo seriale) è un modo per avere una accurata sincronizzazione con l'ossimetro CMS50, ma non conteggia lo scostamento con l'orologio della CPAP.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Se fai partire la registrazione del tuo Ossimetro </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">esattamente </span><span style=" font-family:'Sans'; font-size:10pt;">nello stesso istante in cui fai partire la macchina CPSP, puoi raggiungere la sincronizzazione. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Il processo di importazione seriale prende come tempo iniziale la prima sessione CPAP dell'ultima notte. (Ricorda di importare i tuoi dati CPAP prima!)</span></p></body></html> Print reports in black and white, which can be more legible on non-color printers Stampare rapporti in bianco e nero, che possono essere più leggibili su stampanti non a colori Print reports in black and white (monochrome) Stampare rapporti in bianco e nero (monocromatico) Fonts (Application wide settings) Caratteri (impostazioni a livello di applicazione) Font carattere Size Dimensione Bold Grassetto Italic Corsivo Application Applicazione Graph Text Testo grafico Graph Titles Grafico titoli Big Text Testo grande Details Dettagli &Cancel &Annulla &Ok &Ok Flag Limite Minor Flag Limite minore Span Durata Always Minor Sempre minore No CPAP machines detected Nessuna macchina CPAP rilevata Will you be using a ResMed brand machine? Utilizzerai una macchina a marchio ResMed? Never Mai %1 %2 %1 %2 Name Nome Color Colore Overview Panoramica <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p> <b> Nota: </b> le funzionalità avanzate di suddivisione della sessione di OSCAR non sono possibili con le macchine <b> ResMed </b> a causa di una limitazione nel modo in cui sono archiviate le loro impostazioni e i dati di riepilogo, e quindi hanno stato disabilitato per questo profilo. </p> <p> Su macchine ResMed, i giorni <b> verranno suddivisi a mezzogiorno </b> come nel software commerciale di ResMed. </p> Flag Type Tipo di Limite Label Etichetta CPAP Events Eventi CPAP Oximeter Events Eventi dell'ossimetro Positional Events Eventi posizionali Sleep Stage Events Eventi della fase del sonno Unknown Events Eventi sconosciuti Double click to change the descriptive name the '%1' channel. Fare doppio clic per modificare il nome descrittivo nel canale '%1'. Double click to change the default color for this channel plot/flag/data. Fare doppio clic per cambiare il colore predefinito per questo grafico / flag / dati di questo canale. Whether this flag has a dedicated overview chart. Se questo flag ha un grafico generale dedicato. Here you can change the type of flag shown for this event Qui puoi cambiare il tipo di limite mostrato per questo evento This is the short-form label to indicate this channel on screen. Questa è l'etichetta abbreviata per indicare questo canale sullo schermo. This is a description of what this channel does. Questa è una descrizione di ciò che fa questo canale. Lower Inferiore Upper Superiore CPAP Waveforms Forme d'onda CPAP Oximeter Waveforms Forme d'onda dell'ossimetro Positional Waveforms Forme d'onda posizionali Sleep Stage Waveforms Forme d'onda della fase del sonno Double click to change the descriptive name this channel. Fare doppio clic per modificare il nome descrittivo di questo canale. Whether a breakdown of this waveform displays in overview. Se una ripartizione di questa forma d'onda viene visualizzata in panoramica. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Qui puoi impostare la soglia <b> inferiore </b> utilizzata per determinati calcoli sulla forma d'onda%1 Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Qui puoi impostare la soglia <b> superiore </b> utilizzata per determinati calcoli sulla forma d'onda%1 Data Processing Required Elaborazione dei dati richiesta A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Per applicare queste modifiche è necessario un procedimento di re / decompressione dei dati. Il completamento di questa operazione potrebbe richiedere alcuni minuti. Sei sicuro di voler apportare queste modifiche? Data Reindex Required Elaborazione dei dati richiesta A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Per applicare queste modifiche è necessario un procedimento di reindicizzazione dei dati. Il completamento di questa operazione potrebbe richiedere alcuni minuti. Sei sicuro di voler apportare queste modifiche? Restart Required Riavvio richiesto One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Una o più delle modifiche apportate richiederanno il riavvio di questa applicazione per rendere effettive le modifiche. Vorresti farlo ora? This may not be a good idea Questa potrebbe non essere una buona idea ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Le macchine ResMed S9 eliminano di routine determinati dati dalla scheda SD di età superiore a 7 e 30 giorni (a seconda della risoluzione). If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Se hai mai bisogno di reimportare nuovamente questi dati (sia in OSCAR che in ResScan), questi dati non torneranno. If you need to conserve disk space, please remember to carry out manual backups. Se è necessario conservare lo spazio su disco, ricordarsi di eseguire backup manuali. Are you sure you want to disable these backups? Sei sicuro di voler disabilitare questi backup? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. La disattivazione dei backup non è una buona idea, poiché OSCAR ha bisogno di questi per ricostruire il database in caso di errori. Are you really sure you want to do this? Sei davvero sicuro di voler fare questo? ProfileSelector Filter: Filtro: Reset filter to see all profiles Reimposta filtro per vedere tutti i profili Version [versione] &Open Profile &Apri profilo &Edit Profile &Modifica Profilo &New Profile &Nuovo profilo Profile: None Nessun: Profilo Please select or create a profile... Seleziona o crea un profilo ... Destroy Profile Distruggi profilo Profile Profilo Ventilator Brand Marca del ventilatore Ventilator Model Modello di ventilazione Other Data Altri dati Last Imported Ultimo importato Name nome You must create a profile Devi creare un profilo Enter Password for %1 Inserisci la password per%1 You entered an incorrect password Hai inserito una password errata Forgot your password? Hai dimenticato la password? Ask on the forums how to reset it, it's actually pretty easy. Chiedi ai forum come ripristinarlo, in realtà è abbastanza facile. Select a profile first Seleziona prima un profilo The selected profile does not appear to contain any data and cannot be removed by OSCAR Il profilo selezionato non sembra contenere alcun dato e non può essere rimosso da OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Se stai cercando di eliminare perché hai dimenticato la password, devi reimpostarla o eliminare manualmente la cartella del profilo. You are about to destroy profile '<b>%1</b>'. Stai per distruggere il profilo '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Rifletti attentamente, poiché ciò eliminerà irrimediabilmente il profilo insieme a tutti i<b>dati di backup</b>archiviati in<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Inserisci la parola <b> ELIMINA </b> in basso (esattamente come mostrato) per confermare. DELETE ELIMINA Sorry Scusate You need to enter DELETE in capital letters. Devi inserire ELIMINA in maiuscolo. There was an error deleting the profile directory, you need to manually remove it. Si è verificato un errore durante l'eliminazione della directory del profilo, è necessario rimuoverla manualmente. Profile '%1' was succesfully deleted Il profilo '%1' è stato eliminato con successo Bytes Bytes KB KB MB MB GB GB TB TB PB PB Summaries: Sintesi: Events: eventi: Backups: backup: Hide disk usage information Nascondi le informazioni sull'utilizzo del disco Show disk usage information Mostra informazioni sull'utilizzo del disco Name: %1, %2 Nome: %1, %2 Phone: %1 Telefono: %1 Email: <a href='mailto:%1'>%1</a> Email: <a href='mailto:%1'>%1</a> Address: indirizzo: No profile information given Nessuna informazione sul profilo fornita Profile: %1 Profilo: %1 ProgressDialog Abort Annullare QObject Database Outdated Please Rebuild CPAP Data Database Obsoleto Prego Ricostrure i dati CPAP (%2 min, %3 sec) (%2 min, %3 sec) (%3 sec) (%3 sec) Snapshot %1 Istantanea %1 Pop out Graph Grafico a comparsa The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. La finestra popout è piena. Dovresti catturare l'esistente finestra popout, cancellarla e poi far apparire di nuovo questo grafico. Your machine doesn't record data to graph in Daily View La tua macchina non registra i dati da graficizzare nella Vista Giornaliera There is no data to graph Non ci sono dati da rappresentare graficamente d MMM yyyy [ %1 - %2 ] d MMM yyyy [ %1 - %2 ] Hide All Events Nascondi tutti gli eventi Show All Events Mostra tutti gli eventi Unpin %1 Graph Rimuovi il grafico %1 Popout %1 Graph Mostra grafico %1 Pin %1 Graph Pin %1 Grafico Plots Disabled Trame disabilitate Duration %1:%2:%3 Durata %1:%2:%3 AHI %1 AHI %1 Duration Durata Events Eventi (% %1 in events) (% %1 negli eventi) Med. Med. W-Avg W-Avg Avg Avg Min: %1 Min %1 Min: Min: Max: Max: Max: %1 Max %1 %1 (%2 days): %1 (%2 giorni): %1 (%2 day): %1 (%2 giorno): % in %1 % in %1 Hours Ore Min %1 Min %1 Hours: %1 Ore: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 basso utilizzo, %2 nessun utilizzo, da %3 giorni (%4% conforme.) Lunghezza: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Sessioni: %1 / %2 / %3 Lunghezza: %4 / %5 / %6 Più lunga: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Lunghezza: %3 Avvio: %2 Mask On maschera On Mask Off maschera Off %1 Length: %3 Start: %2 %1 Lunghezza: %3 Inizio: %2 TTIA: TTIA: TTIA: %1 TTIA: %1 Days: %1 Giorni:%1 Low Usage Days: %1 Giorni di utilizzo basso:%1 (%1% compliant, defined as > %2 hours) (%1% conforme, definito come>%2 ore) (Sess: %1) (Sess: %1) Bedtime: %1 Ora di andare a letto:%1 Waketime: %1 orario di veglia: %1 (Summary Only) (Solo riepilogo) No Data Nessun dato Jan Gen Feb Feb Mar Mar Apr Apr May Mag Jun Giu Jul Lug Aug Ago Sep Set Oct Ott Nov Nov Dec Dic Relief: %1 Rilievo:%1 Hours: %1h, %2m, %3s Ore: %1h, %2m, %3s Machine Information Informazioni macchina App key: Tasto app: Operating system: Sistema operativo: Built with Qt %1 on %2 Costruito con Qt %1 su %2 Graphics Engine: Motore grafico: Graphics Engine type: Tipo di motore grafico: Software Engine Software Engine ANGLE / OpenGLES ANGOLO / OpenGLES Desktop OpenGL Desktop OpenGL m m cm cm ft ft lb lb oz oz Kg Kg cmH2O cmH2O Minutes Minuti Seconds secondi h h m m s s ms ms Events/hr Eventi / hr Hz Hz bpm bpm L/min L/min Litres Litri ml ml Breaths/min Respiri/min ? ? ratio coefficiente Severity (0-1) Gravità (0-1) Degrees livelli Question Domanda Error Errore Warning avvertimento Information Informazioni Busy Occupata Please Note Notare che No Data Available Nessun dato disponibile Graphs Switched Off Grafici disattivati Only Settings and Compliance Data Available Solo Settaggi e Dati di Compliance Disponibili in in Summary Data Only Solo dati di Riepilogo Sessions Switched Off Sessioni disattivate &Yes &Sì &No &No &Cancel &Annulla &Destroy &Distruggere &Save &Salva BMI BMI Weight Peso Zombie Zombie Pulse Rate Pulsazioni Plethy Pleteo Pressure Pressione Daily Giornaliero Profile Profilo Overview Panoramica Oximetry Ossimetria Oximeter Ossimetro Event Flags Flag degli Eventi Default Predefinite CPAP CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP Min EPAP Min EPAP Max EPAP Max EPAP IPAP IPAP Min IPAP Min IPAP Max IPAP Max IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Umidificatore H I OA AO A A CA AC FL FL SA SA LE LE EP EP VS VS VS2 VS2 RERA RERA PP PP P P RE RE NR NR NRI NRI O2 O2 PC Variazione di Pulsazioni VP UF1 UF1 UF2 UF2 UF3 UF3 PS PS AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI EPI PB PB IE IE Insp. Time Insp. Tempo Exp. Time Esp. Tempo Resp. Event Resp. Evento Flow Limitation Limitazione del flusso Flow Limit Limite di flusso SensAwake Sveglio Pat. Trig. Breath Colpetto. Trig. Respiro Tgt. Min. Vent Tgt. Min. Vent Target Vent. Obiettivo Vent. Minute Vent. Ventilazione al Min. Tidal Volume Volume Corrente Resp. Rate Freq. Respiratoria Snore Russare Leak Perdita Leaks Perdite Large Leak Grande Perdita LL GP Total Leaks Perdite Totali Unintentional Leaks Perdite Involontarie MaskPressure Pressione Maschera Flow Rate Portata Sleep Stage Fase del sonno Usage Uso Sessions Sessione Pr. Relief Pr. Sollievo Bookmarks Preferiti Mode Modalità Model Modello Brand Marca Serial Marca Series Serie Machine Macchina Channel Canale Settings Impostazioni Inclination Inclinazione Orientation Orientamento Motion Movimento Name Nome DOB DOB Phone Telefono Address Indirizzo Email Email Patient ID ID Identificazione del Paziente Date Data Bedtime Ora della Nanna Wake-up Sveglia Mask Time Tempo Maschera Unknown Sconosciuto None Nessuna Ready Pronto First Primo Last Ultimo Start Inizio End Fine On On Off Off Yes Si No No Min Min Max Max Med Med Average Media Median Mediana 99.5% 99,5% varies varie n/a n/a Fixed %1 (%2) Fisso %1 (%2) Min %1 Max %2 (%3) Min %1 Max %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %1 su %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1-%2 IPAP %3-%4 (%5) Journal Data Dati del Diario OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR ha trovato una vecchia cartella Journal, ma sembra che sia stata rinominata: OSCAR will not touch this folder, and will create a new one instead. OSCAR non toccherà questa cartella e ne creerà una nuova. Please be careful when playing in OSCAR's profile folders :-P Si prega di fare attenzione quando si lavora nelle cartelle del profilo di OSCAR :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. Per qualche motivo, OSCAR non è riuscito a trovare un record di oggetto journal nel profilo, ma ha trovato più cartelle di dati journal. OSCAR picked only the first one of these, and will use it in future: OSCAR ha scelto solo il primo di questi e lo userà in futuro: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Se mancano i tuoi vecchi dati, copia il contenuto di tutte le altre cartelle Journal_XXXXXXX in questo manualmente. CMS50D+ CMS50D+ CMS50E/F CMS50E/F Contec Contec CMS50 CMS50 CMS50F3.7 CMS50F3.7 CMS50F CMS50F Fisher & Paykel Fisher & Paykel ICON ICONA Backing up files... Backup dei file... Reading data files... Lettura dei file di dati... SmartFlex Mode Modalità SmartFlex Intellipap pressure relief mode. Modalità di riduzione della pressione Intellipap. Ramp Only Solo Rampa Full Time Tempo Totale SmartFlex Level Livello SmartFlex Intellipap pressure relief level. Intellipap livello di riduzione della pressione. Snoring event. Evento di russamento. SN SN DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings Impostazioni SmartFlex ChoiceMMed Scelta MMed MD300 MD300 Respironics Respironics M-Series M-Series System One System One Getting Ready... Prepararsi... Your %1 %2 (%3) generated data that OSCAR has never seen before. La Tua %1 %2 (%3) ha generato dati che OSCAR non ha mai visto prima. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. I dati importati non possono essere totalmente accurati, così gli sviluppatori vorrebbero una .zip copia della card SD della macchina e i corrispondenti .pdf report clinici per essere sicuri che OSCAR possa processare i dati correttamente. Non Data Capable Machine Macchina senza dati Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. La Tua %1 macchina CPAP (Model %2), sfortunatamente è un modello che non ha dati trattabili. Your %1 CPAP machine (Model %2) has not been tested yet. La Tua %1 macchina CPAP (Modello %2) non è stata ancora testata. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Essa sembra simile abbastanza ad alte macchine che potrebbe funzionare, ma gli sviluppatori vorrebbero una copia .zip della scheda SD della macchina e i corrispondenti report clinici .pdf per essere sicuri che essa funzioni con OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. Ci scusiamo, la tua %1 macchina CPAP (%2) non è ancora supportata. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Gli sviluppatori necessitano di una copia .zip della scheda SD della macchina ed i corrispondenti report clinici .pdf per essere sicuri che essa funzioni con OSCAR. Machine Unsupported Macchina non supportata I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Mi dispiace segnalare che OSCAR può monitorare solo le ore di utilizzo e le impostazioni di base per questa macchina. Scanning Files... Scansione dei file ... Importing Sessions... Importazione di sessioni ... Finishing up... Terminando... Machine Untested Macchina non Testata Flex Lock Blocco Flex Whether Flex settings are available to you. Se le impostazioni Flex sono disponibili per te. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Tempo necessario per passare da EPAP a IPAP, maggiore è il numero, più lenta è la transizione Rise Time Lock Blocco del tempo di salita Whether Rise Time settings are available to you. Se le impostazioni di Rise Time sono disponibili per te. Rise Lock Blocco di salita Mask Resistance Setting Impostazione della resistenza della maschera Mask Resist. Maschera Resist. Hose Diam. Diam. Tubo 15mm 15mm 22mm 22mm Backing Up Files... Backup dei file ... Untested Data Dati non testati model %1 modello %1 DreamStation 2 DreamStation 2 unknown model modello sconosciuto Pressure Pulse Impulso di pressione A pulse of pressure 'pinged' to detect a closed airway. Un impulso di pressione "pingato" per rilevare una via aerea chiusa. CPAP-Check CPAP-Check AutoCPAP AutoCPAP Auto-Trial Auto-Trial AutoBiLevel AutoBiLevel S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Mode Modalità Flex PRS1 pressure relief mode. Modalità di riduzione della pressione PRS1. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time Ora di alzarsi Bi-Flex Bi-Flex Flex Flex Flex Level Livello flessibile PRS1 pressure relief setting. Impostazione del limitatore di pressione PRS1. Passover Passa sopra Target Time Tempo Obiettivo PRS1 Humidifier Target Time PRS1 Umidificatore Target Time Hum. Tgt Time Umid. Tgt Time Tubing Type Lock Blocco del tipo di tubo Whether tubing type settings are available to you. Se le impostazioni del tipo di tubo sono disponibili. Tube Lock Blocco del Tubo Mask Resistance Lock Blocco resistenza maschera Whether mask resistance settings are available to you. Se le impostazioni di resistenza maschera sono disponibili per te. Mask Res. Lock Maschera Res. Blocco Whether or not machine shows AHI via built-in display. Se la macchina mostra o meno AHI tramite display integrato. Ramp Type Tipo di Rampa Type of ramp curve to use. Tipo di curva di rampa da utilizzare. Linear Lineare SmartRamp Rampa intelligente Ramp+ Rampa+ Backup Breath Mode Modalità di backup del respiro The kind of backup breath rate in use: none (off), automatic, or fixed Il tipo di frequenza respiratoria di backup in uso: nessuno (spento), automatico o fisso Breath Rate Frequenza respiratoria Fixed Fisso Fixed Backup Breath BPM Risolto problema BPM Breath Backup Minimum breaths per minute (BPM) below which a timed breath will be initiated Respiri minimi al minuto (BPM) al di sotto dei quali verrà avviato un respiro a tempo Breath BPM Respiro BPM Timed Inspiration Tempo Ispirazione The time that a timed breath will provide IPAP before transitioning to EPAP Il tempo in cui un respiro a tempo fornirà IPAP prima di passare a EPAP Timed Insp. Inspiraziione Temporizzata Auto-Trial Duration Durata prova automatica The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Il numero di giorni nel periodo di prova Auto-CPAP, trascorso il quale la macchina tornerà a CPAP Auto-Trial Dur. Durata prova automatica. EZ-Start EZ-Start Whether or not EZ-Start is enabled Se EZ-Start è abilitato o meno Variable Breathing Respirazione Variabile UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend NON CONFERMATO: Possibilmente respiro variabile, che sono periodi di elevata deviazione dal picco del flusso inspiratorio Peak Flow Flusso Massimo Peak flow during a 2-minute interval Flusso di picco in un intervallo di 2 minuti Humidifier Status Stato dell'Umidificatore PRS1 humidifier connected? Umidificatore PRS1 collegato? Disconnected Disconnesso Connected Connesso Humidification Mode Modalità Umidificazione PRS1 Humidification Mode Modalità di umidificazione PRS1 Humid. Mode Modalità Umid. Fixed (Classic) Fisso (classico) Adaptive (System One) Adattivo (System One) Heated Tube Tubo Riscaldato Tube Temperature Temperatura del Tubo PRS1 Heated Tube Temperature PRS1 Temperatura del tubo riscaldato Tube Temp. Temp. Tubo PRS1 Humidifier Setting Impostazione dell'umidificatore PRS1 Hose Diameter Diametro del Tubo Diameter of primary CPAP hose Diametro del tubo CPAP primario 12mm 12mm Auto On Auto On A few breaths automatically starts machine Alcuni respiri avviano automaticamente la macchina Auto Off Auto Off Machine automatically switches off La macchina si spegne automaticamente Mask Alert Avviso Maschera Whether or not machine allows Mask checking. Se la macchina consente o meno il controllo Maschera. Show AHI Mostra AHI Breathing Not Detected Respirazione Non Rilevata A period during a session where the machine could not detect flow. Un periodo durante una sessione in cui la macchina non è stata in grado di rilevare il flusso. BND BND Timed Breath Respiro cronometrato Machine Initiated Breath Respiro iniziato dalla macchina TB TB Philips Respironics Philips Respironics Locating STR.edf File(s)... Individuazione dei file STR.edf ... Cataloguing EDF Files... Catalogazione dei file EDF ... Queueing Import Tasks... Attività di importazione in coda ... Finishing Up... Terminando... CPAP Mode CPAP Mode VPAPauto VPAPauto ASVAuto ASVAuto iVAPS iVAPS PAC PAC Auto for Her Auto per Lei EPR EPR ResMed Exhale Pressure Relief ResMed Sollievo Pressione Esalazione Patient??? Paziente??? EPR Level Livello EPR Exhale Pressure Relief Level Livello di Sollievo Pressione Esalazione Response Risposta Patient View Vista Paziente SmartStart SmartStart Machine auto starts by breathing La macchina si avvia automaticamente respirando Smart Start Smart Start Humid. Status Stato Umidif. Humidifier Enabled Status Stato abilitato umidificatore Humid. Level Umidif. Livello Humidity Level Livello di Umidità Temperature Temperatura ClimateLine Temperature ClimateLine Temperature Temp. Enable Temp. Abilitare ClimateLine Temperature Enable Abilitazione Temperatura ClimateLine Temperature Enable Abilita Temperatura AB Filter Filtro AB Antibacterial Filter Filtro Antibatterico Pt. Access Pt. Accesso Essentials Essenziali Plus Più Climate Control Controllo Climatico Manual Manuale Soft Morbido Standard Normale BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T SmartStop SmartStop Machine auto stops by breathing La macchina si ferma automaticamente interrompendo la respirazione Smart Stop Arresto Intelligente Simple Semplice Advanced Avanzato Your ResMed CPAP machine (Model %1) has not been tested yet. La tua macchina ResMed CPAP (modello %1) non è stata ancora testata. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Sembra abbastanza simile ad altre macchine che potrebbe funzionare, ma gli sviluppatori vorrebbero una copia .zip della scheda SD di questa macchina per essere sicuri che funzioni con OSCAR. Parsing STR.edf records... Analisi dei record STR.edf ... Auto Auto Mask Maschera ResMed Mask Setting Impostazione Maschera ResMed Pillows Cuscini Full Face Pieno Volto Nasal Nasale Ramp Rampa Ramp Enable Abilita Rampa ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Somnopose Software Weinmann Weinmann SOMNOsoft2 Weinmann Zeo Zeo Personal Sleep Coach Allenatore del Sonno Personale Windows User Utente Windows Using Utilizzando , found SleepyHead - , trovato SleepyHead - You must run the OSCAR Migration Tool È necessario eseguire lo strumento di migrazione OSCAR Launching Windows Explorer failed Avvio di Windows Explorer non riuscito Could not find explorer.exe in path to launch Windows Explorer. Impossibile trovare explorer.exe nel percorso per avviare Esplora risorse. <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i> I dati della vecchia macchina dovrebbero essere rigenerati a condizione che questa funzione di backup non sia stata disabilitata nelle preferenze durante un'importazione di dati precedente. </i> <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b> OSCAR mantiene un backup della scheda dati del dispositivo utilizzata a tale scopo. </b> OSCAR %1 needs to upgrade its database for %2 %3 %4 OSCAR %1 deve aggiornare il suo database per %2 %3 %4 OSCAR does not yet have any automatic card backups stored for this device. OSCAR non ha ancora alcun backup automatico delle carte memorizzato per questo dispositivo. This means you will need to import this machine data again afterwards from your own backups or data card. Ciò significa che sarà necessario importare nuovamente i dati di questa macchina in seguito dai propri backup o dalla propria scheda dati. Important: Importante: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Se sei preoccupato, fai clic su No per uscire e fai il backup manuale del tuo profilo, prima di riavviare OSCAR. Are you ready to upgrade, so you can run the new version of OSCAR? Sei pronto per l'aggiornamento, in modo da poter eseguire la nuova versione di OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Spiacenti, l'operazione di eliminazione non è riuscita, il che significa che questa versione di OSCAR non può essere avviata. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Vorresti attivare i backup automatici, quindi la prossima volta che una nuova versione di OSCAR deve farlo, può ricostruire da questi? OSCAR will now start the import wizard so you can reinstall your %1 data. Ora OSCAR avvierà la procedura guidata di importazione in modo da poter reinstallare i tuoi dati%1. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: OSCAR ora verrà chiuso, quindi (tentare di) avviare il file manager del computer in modo da poter eseguire manualmente il backup del profilo: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Utilizzare il file manager per creare una copia della directory del proprio profilo, quindi riavviare OSCAR e completare il processo di aggiornamento. Machine Database Changes Modifiche al database macchina Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Una volta eseguito l'aggiornamento, <font size = + 1> non è più possibile </font> utilizzare questo profilo con la versione precedente. The machine data folder needs to be removed manually. La cartella dei dati della macchina deve essere rimossa manualmente. This folder currently resides at the following location: Questa cartella attualmente risiede nel seguente percorso: Rebuilding from %1 Backup Ricostruzione da%1 Backup Therapy Pressure Pressione Terapeutica Inspiratory Pressure Pressione Inspiratoria Lower Inspiratory Pressure Pressione Inspiratoria Inferiore Higher Inspiratory Pressure Pressione Inspiratoria Superiore Expiratory Pressure Pressione Espiratoria Lower Expiratory Pressure Pressione Espiratoria Inferiore Higher Expiratory Pressure Pressione Espiratoria Superiore Pressure Support Pressione di Supporto PS Min PS Min Pressure Support Minimum Pressione Minima di supporto PS Max PS Max Pressure Support Maximum Pressione Massima di Supporto Min Pressure Pressione Minima Minimum Therapy Pressure Pressione Minima di Terapia Pressure Min Pressione Min Max Pressure Pressione Massima Maximum Therapy Pressure Massima Pressione Terapeutica Pressure Max Pressione Max Ramp Time Tempo di Rampa Ramp Delay Period Periodo di Ritardo Rampa Ramp Pressure Pressione di Rampa Starting Ramp Pressure Avvio della Pressione di Rampa Ramp Event Evento di Rampa Pressure Set Pressione di Settaggio Pressure Setting Impostazione della Pressione IPAP Set Set IPAP IPAP Setting Impostazione IPAP EPAP Set Set EPAP EPAP Setting Impostazione EPAP Cheyne Stokes Respiration Cheyne Stokes Respirazione An abnormal period of Cheyne Stokes Respiration Un periodo anormale di Cheyne Stokes Respiration CSR CSR Periodic Breathing Respirazione Periodica An abnormal period of Periodic Breathing Un periodo anormale di respirazione periodica Clear Airway Apnea Centrale An apnea where the airway is open Un'apnea in cui le vie aeree sono aperte Obstructive Apnea Ostruttiva An apnea caused by airway obstruction Un'apnea causata da ostruzione delle vie aeree Hypopnea Ipopnea A partially obstructed airway Una via aerea parzialmente ostruita Unclassified Apnea Apnea Non Classificata UA ANC An apnea that couldn't be determined as Central or Obstructive. Un'apnea che non può essere determinata come centrale o ostruttiva. A restriction in breathing from normal, causing a flattening of the flow waveform. Una limitazione nella respirazione dalla normalità, che causa un appiattimento della forma d'onda del flusso. Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Risveglio correlato allo sforzo respiratorio: una limitazione nella respirazione che provoca un disturbo del sonno o il risveglio. Vibratory Snore Russare Vibrante A vibratory snore Un russare vibratorio Vibratory Snore (VS2) Russamento Vibratorio (VS2) A vibratory snore as detcted by a System One machine Un russare vibratorio come rilevato da una macchina System One Leak Flag Segnale di Perdita A large mask leak affecting machine performance. Una grande perdita di maschera che influisce sulle prestazioni della macchina. (%) LF LF Non Responding Event Evento non Rispondente A type of respiratory event that won't respond to a pressure increase. Un tipo di evento respiratorio che non risponderà a un aumento della pressione. Expiratory Puff Flusso Espiratorio Intellipap event where you breathe out your mouth. Evento Intellipap in cui espiri la bocca. SensAwake feature will reduce pressure when waking is detected. La funzione SensAwake riduce la pressione quando viene rilevata la veglia. User Flag #1 Flag utente n. 1 A user definable event detected by OSCAR's flow waveform processor. Un evento definibile dall'utente rilevato dal processore di forme d'onda di flusso di OSCAR. User Flag #2 Flag utente n. 2 User Flag #3 Flag utente n. 3 Heart rate in beats per minute Frequenza cardiaca in battiti al minuto Blood-oxygen saturation percentage Percentuale di saturazione di ossigeno nel sangue Plethysomogram Pletismografia An optical Photo-plethysomogram showing heart rhythm Un foto-pletisomogramma ottico che mostra il ritmo cardiaco Perfusion Index Indice di perfusione A relative assessment of the pulse strength at the monitoring site Una valutazione relativa della forza del polso nel sito di monitoraggio Perf. Index % Perf. Indice% Pulse Change Variazione Pulsazioni A sudden (user definable) change in heart rate Un improvviso (definibile dall'utente) cambiamento nella frequenza cardiaca (eventi per ora) SpO2 Drop ODI Desat. Indice A sudden (user definable) drop in blood oxygen saturation Un calo improvviso (definibile dall'utente) nella saturazione di ossigeno nel sangue (desaturazioni per ora) SD Desaturazione ossigeno SD Breathing flow rate waveform Forma d'onda della portata del flusso respiratorio Mask Pressure Pressione Maschera Mask Pressure (High frequency) Pressione Maschera (alta frequenza) A ResMed data item: Trigger Cycle Event Un elemento dati ResMed: Trigger Cycle Event Amount of air displaced per breath Quantità di aria spostata per respiro (ml) Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Impossibile analizzare Channels.xml, OSCAR non può continuare e sta uscendo. Apnea Apnea An apnea reportred by your CPAP machine. Un'apnea registrata dalla tua macchina CPAP. Graph displaying snore volume Grafico che mostra il volume del russare Minute Ventilation Ventilazione Minuto Amount of air displaced per minute Quantità di aria spostata al minuto Respiratory Rate Frequenza Respiratoria Rate of breaths per minute Frequenza di respiri al minuto Patient Triggered Breaths Respiri del paziente Percentage of breaths triggered by patient Percentuale di respiri del paziente Pat. Trig. Breaths Colpetto. Trig. respiri Leak Rate Tasso di Perdita Rate of detected mask leakage Tasso rilevato di perdita della maschera (l al min) I:E Ratio I:E Rapporto Ratio between Inspiratory and Expiratory time Rapporto tra tempo inspiratorio ed espiratorio Expiratory Time Tempo espiratorio Time taken to breathe out Tempo impiegato per espirare Inspiratory Time Tempo inspiratorio Time taken to breathe in Tempo impiegato per inspirare Respiratory Event Evento respiratorio Graph showing severity of flow limitations Grafico che mostra la gravità dei limiti di flusso Flow Limit. Limitazione di flusso Target Minute Ventilation Ventilazione minuto target Maximum Leak Perdita Massima The maximum rate of mask leakage Il tasso massimo di perdita della maschera Max Leaks Perdite Massime Apnea Hypopnea Index Indice di Apnea Ipopnea Graph showing running AHI for the past hour Grafico che mostra AHI in esecuzione nell'ultima ora Total Leak Rate Tasso Perdita Totale Detected mask leakage including natural Mask leakages Perdita di maschera rilevata, comprese perdite di maschera naturali Median Leak Rate Mediana del Tasso di Perdita Median rate of detected mask leakage Mediana del tasso di perdita della maschera rilevata Median Leaks Mediana Perdite Respiratory Disturbance Index Indice di Disturbo Respiratorio Graph showing running RDI for the past hour Grafico che mostra RDI in esecuzione nell'ultima ora Sleep position in degrees Posizione del sonno in gradi Upright angle in degrees Angolo verticale in gradi Movement Movimento Movement detector Rilevatore di movimento Mask On Time Tempo Maschera Time started according to str.edf Il tempo è iniziato secondo str.edf Summary Only Solo riepilogo CPAP Session contains summary data only La sessione CPAP contiene solo dati di riepilogo PAP Mode Modalità PAP PAP Device Mode Modalità dispositivo PAP APAP (Variable) APAP (Variabile) Fixed Bi-Level Bi-Level Fisso Auto Bi-Level (Fixed PS) Bi-Level Automatico (PS fisso) Auto Bi-Level (Variable PS) Bi-Level automatico (PS variabile) ASV (Fixed EPAP) ASV (EPAP fisso) ASV (Variable EPAP) ASV (EPAP variabile) Height Altezza Physical Height Altezza fisica Notes Appunti Bookmark Notes Note sui Segnalibri Body Mass Index Indice di Massa Corporea BMI How you feel (0 = like crap, 10 = unstoppable) Come ti senti (0 = come una schifezza, 10 = inarrestabile) Bookmark Start Inizio Segnalibro Bookmark End Segnalibro Fine Last Updated Ultimo Aggiornamento Journal Notes Diario Giornaliero Journal Diario 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1 = Sveglia 2 = REM 3 = Sonno leggero 4 = Sonno profondo Brain Wave Onde Celebrali BrainWave Onde Cerebrali Awakenings Risvegli Number of Awakenings Numero di Risvegli Morning Feel Sentirsi al mattino How you felt in the morning Come ti sei sentito al mattino Time Awake Tempo sveglio Time spent awake Tempo trascorso sveglio Time In REM Sleep Tempo nel sonno REM Time spent in REM Sleep Tempo trascorso nel sonno REM Time in REM Sleep Tempo nel sonno REM Time In Light Sleep Tempo nel sonno leggero Time spent in light sleep Tempo trascorso nel sonno leggero Time in Light Sleep Tempo nel sonno leggero Time In Deep Sleep Tempo nel sonno profondo Time spent in deep sleep Tempo trascorso nel sonno profondo Time in Deep Sleep Tempo nel sonno profondo Time to Sleep Momento di dormire Time taken to get to sleep Tempo impiegato per addormentarsi Zeo ZQ Zeo ZQ Zeo sleep quality measurement Misurazione della qualità del sonno Zeo ZEO ZQ ZEO ZQ Debugging channel #1 Debug del canale n. 1 Test #1 Test #1 For internal use only Solo per uso interno Debugging channel #2 Debug del canale n. 2 Test #2 Test #2 Zero Zero Upper Threshold Soglia superiore Lower Threshold Soglia inferiore Exiting Uscita As you did not select a data folder, OSCAR will exit. Poiché non è stata selezionata una cartella di dati, OSCAR verrà chiuso. Next time you run OSCAR, you will be asked again. La prossima volta che avvierai OSCAR, ti verrà chiesto di nuovo. The folder you chose is not empty, nor does it already contain valid OSCAR data. La cartella che hai scelto non è vuota, né contiene già dati OSCAR validi. Are you sure you want to use this folder? Sei sicuro di voler usare questa cartella? or CANCEL to skip migration. o CANCEL per saltare la migrazione. You cannot use this folder: Non puoi usare questa cartella: Migrating Migrazione files File from a partire dal to per OSCAR crashed due to an incompatibility with your graphics hardware. OSCAR si è arrestato in modo anomalo a causa di un'incompatibilità con l'hardware grafico. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. Per risolvere questo, OSCAR è tornato a un metodo di disegno più lento ma più compatibile. OSCAR will set up a folder for your data. OSCAR imposterà una cartella per i tuoi dati. Choose the SleepyHead or OSCAR data folder to migrate Scegliere la cartella di dati SleepyHead o OSCAR da migrare The folder you chose does not contain valid SleepyHead or OSCAR data. La cartella che hai scelto non contiene dati SleepyHead o OSCAR validi. If you have been using SleepyHead or an older version of OSCAR, Se avete usato SleepyHead o una versione precedente di OSCAR, OSCAR can copy your old data to this folder later. OSCAR può copiare i vostri vecchi dati in questa cartella in seguito. We suggest you use this folder: Ti consigliamo di utilizzare questa cartella: Click Ok to accept this, or No if you want to use a different folder. Fare clic su OK per accettarlo o su No se si desidera utilizzare una cartella diversa. Choose or create a new folder for OSCAR data Scegli o crea una nuova cartella per i dati OSCAR Data directory: Directory dei dati: Migrate SleepyHead or OSCAR Data? Migrare i dati di SleepyHead o OSCAR? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Nella schermata successiva OSCAR vi chiederà di selezionare una cartella con dati SleepyHead o OSCAR Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Cliccate su [OK] per andare alla prossima schermata o su [No] se non volete usare nessun dato di SleepyHead o OSCAR. Unable to create the OSCAR data folder at Impossibile creare la cartella dei dati OSCAR in Unable to write to OSCAR data directory Impossibile scrivere nella directory dei dati OSCAR Error code Codice di errore OSCAR cannot continue and is exiting. OSCAR non può continuare e sta uscendo. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Impossibile scrivere nel registro di debug. Puoi ancora usare il pannello di debug (Help/Troubleshooting/Show Debug Pane) ma il debug log non verrà scritto su disco. Version "%1" is invalid, cannot continue! La versione "%1" non è valida, impossibile continuare! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). La versione di OSCAR che stai usando (%1) è PIÙ vecchia di quella usata per creare questi dati (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? È probabile che ciò causi il danneggiamento dei dati, sei sicuro di volerlo fare? Don't forget to place your datacard back in your CPAP machine Non dimenticare di reinserire la scheda dati nella tua macchina CPAP OSCAR Reminder Promemoria OSCAR You can only work with one instance of an individual OSCAR profile at a time. Puoi lavorare solo con un'istanza di un singolo profilo OSCAR alla volta. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Se si utilizza l'archiviazione cloud, assicurarsi che OSCAR sia chiuso e che la sincronizzazione sia stata completata prima sull'altro computer prima di procedere. Loading profile "%1"... Caricamento profilo "%1" ... Chromebook file system detected, but no removable device found Rilevato il file system del Chromebook, ma nessun dispositivo rimovibile trovato You must share your SD card with Linux using the ChromeOS Files program Devi condividere la tua scheda SD con Linux usando il programma ChromeOS Files Recompressing Session Files Ricompilare i file di sessione Please select a location for your zip other than the data card itself! Seleziona una posizione per la tua zip diversa dalla scheda dati stessa! Unable to create zip! Impossibile creare lo zip! Are you sure you want to reset all your channel colors and settings to defaults? Sei sicuro di voler ripristinare i valori predefiniti di tutti i colori e le impostazioni del tuo canale? Are you sure you want to reset all your waveform channel colors and settings to defaults? Sei sicuro di voler ripristinare i valori predefiniti di tutti i colori e le impostazioni del canale della forma d'onda? There is a lockfile already present for this profile '%1', claimed on '%2'. C'è un lockfile già presente per questo profilo '%1', rivendicato su '%2'. There are no graphs visible to print Non ci sono grafici visibili per la stampa Would you like to show bookmarked areas in this report? Desideri mostrare le aree contrassegnate in questo rapporto? Printing %1 Report Stampa la relazione %1 %1 Report %1 Rapporto : %1 hours, %2 minutes, %3 seconds %1 ore, %2 minuti, %3 secondi RDI %1 RDI %1 AHI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 UAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 AI=%1 AI=%1 Reporting from %1 to %2 Segnalazione da %1 a %2 Entire Day's Flow Waveform Forma d'onda di flusso dell'intero giorno Current Selection Selezione corrente Entire Day Tutto il giorno Page %1 of %2 Pagina %1 di %2 Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> I dati ossimetrici più recenti: <a onclick='alert("daily=%2");'>%1</a> (last night) (ieri sera) (1 day ago) (1 giorno fa) (%2 days ago) (%2 giorni fa) No oximetry data has been imported yet. Non sono ancora stati importati dati ossimetrici. Loading %1 data for %2... Caricamento %1 dati per %2... Scanning Files Scansione dei file Migrating Summary File Location Migrazione della posizione del file di riepilogo Loading Summaries.xml.gz Caricamento del riepiogo.xml.gz Loading Summary Data Caricamento dei dati di riepilogo Please Wait... Attendere prego... Updating Statistics cache Aggiornamento della cache delle statistiche Usage Statistics Statistiche di utilizzo %1 Charts %1 Grafici %1 of %2 Charts %1 di %2 Grafici Loading summaries Caricamento dei riepiloghi Dreem Dreem Your Viatom device generated data that OSCAR has never seen before. Il tuo dispositivo Viatom ha generato dati che OSCAR non ha mai visto prima. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. I dati importati potrebbero non essere del tutto precisi, quindi gli sviluppatori vorrebbero una copia dei file Viatom per assicurarsi che OSCAR gestisca correttamente i dati. Viatom Viatom Viatom Software Software Viatom New versions file improperly formed File delle nuove versioni formato in modo improprio A more recent version of OSCAR is available Una versione più recente di OSCAR è disponibile release release test version versione test You are running the latest %1 of OSCAR Tu stai usando l'ultima %1 di OSCAR You are running OSCAR %1 Tu stai usando OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 è disponibile <a href='%2'>qui</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Informazioni sulla versione di test più recente %1 sono disponibili su <a href='%2'>%2</a> Check for OSCAR Updates Controlla gli aggiornamenti OSCAR Unable to check for updates. Please try again later. Impossibile controllare gli aggiornamenti. Si prega di riprovare più tardi. SensAwake level SensAwake level Expiratory Relief Sollievo Espiratorio Expiratory Relief Level Livello Sollievo Espiratorio Humidity Umidità SleepStyle SleepStyle This page in other languages: Questa pagine in altre lingue: %1 Graphs %1 Grafico %1 of %2 Graphs %1 di %2 Grafici %1 Event Types %1 Tipi di Eventi %1 of %2 Event Types %1 di %2 Tipi di Eventi Report about:blank Needs to be kept in the original form informazioni:bianco SessionBar %1h %2m H and M have an italian equivalent (O and M) but it is not commonly used. The full words are used instead %1ore %2minuti No Sessions Present Nessuna sessione è presente SleepStyleLoader Import Error Errore nell' Importare i Dati This Machine Record cannot be imported in this profile. Non è possibile importare i dati del dispositivo nel profilo. The Day records overlap with already existing content. I dati giornalieri coincidono con contenuti già presenti. Statistics CPAP Statistics Statistiche CPAP CPAP Usage Utilizzo CPAP Average Hours per Night Ore medie a notte Therapy Efficacy Efficacia terapeutica Leak Statistics Statistiche perdite Pressure Statistics Statistiche di pressione Oximeter Statistics Statistiche dell'ossimetro Blood Oxygen Saturation Saturazione di ossigeno nel sangue Pulse Rate Frequenza Pulsazioni %1 Median %1 Mediana Average %1 Media %1 Min %1 Min %1 Max %1 Max %1 %1 Index %1 Indice % of time in %1 % del tempo in %1 % of time above %1 threshold % del tempo sopra la soglia %1 % of time below %1 threshold % del tempo inferiore alla soglia %1 Name: %1, %2 Nome: %1, %2 DOB: %1 Data di nascita: %1 Phone: %1 Telefono: %1 Email: %1 Email: %1 Address: Indirizzo: This report was prepared on %1 by OSCAR %2 Questa relazione è stata preparata su %1 da OSCAR %2 Machine Information Informazioni macchina First Use Primo utilizzo Last Use Ultimo utilizzo Days Giorni Pressure Relief Riduzione della pressione Pressure Settings Impostazioni di pressione Days Used: %1 Giorni di uso: %1 Low Use Days: %1 Giorni di utilizzo ridotti: %1 Compliance: %1% Conformità: %1% Days AHI of 5 or greater: %1 Giorni AHI di 5 o più: %1 Best AHI AHI Migliore Date: %1 AHI: %2 Data: %1 AHI: %2 Worst AHI Peggiore AHI Best Flow Limitation Limitazione del flusso ottimale Date: %1 FL: %2 Data: %1 FL: %2 Worst Flow Limtation Peggior limitazione del flusso No Flow Limitation on record Nessuna limitazione di flusso sul record Worst Large Leaks Peggiori perdite di grandi dimensioni Date: %1 Leak: %2% Data: %1 Perdita: %2% No Large Leaks on record Nessuna grande perdita registrata Worst CSR Cheyne-Stokes Respiration (CSR) Peggiore CSR Date: %1 CSR: %2% Data: %1 CSR: %2% No CSR on record Nessun CSR registrato Worst PB Peggiore PB Date: %1 PB: %2% Data: %1 PB: %2% No PB on record Nessun PB registrato Want more information? Vuoi maggiori informazioni? OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR ha bisogno di caricare tutti i dati di riepilogo per calcolare i dati migliori / peggiori per i singoli giorni. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Abilitare la casella di controllo Riepiloghi pre-caricamento nelle preferenze per assicurarsi che questi dati siano disponibili. Best RX Setting Migliore impostazione RX Date: %1 - %2 Data: %1 - %2 AHI: %1 AHI: %1 Total Hours: %1 Ore totali: %1 Worst RX Setting Peggiore impostazione RX Most Recent Piu recente Compliance (%1 hrs/day) Conformità (%1 ora/giorno) OSCAR is free open-source CPAP report software OSCAR è un software di report CPAP open source gratuito Changes to Machine Settings Modifiche alle impostazioni della macchina No data found?!? Nessun dato trovato?!? Oscar has no data to report :( Oscar non ha dati da segnalare :( Last Week Ultima settimana Last 30 Days Ultimi 30 giorni Last 6 Months Ultimi 6 mesi Last Year Ultimo anno Last Session Ultima sessione Details Dettagli No %1 data available. Nessun dato %1 disponibile. %1 day of %2 Data on %3 %1 giorno di %2 Dati su %3 %1 days of %2 Data, between %3 and %4 %1 giorni di %2 Dati, tra %3 e %4 Welcome Welcome to the Open Source CPAP Analysis Reporter Benvenuto in Open Source CPAP Reporter di analisi What would you like to do? Cosa ti piacerebbe fare? CPAP Importer Importare CPAP Oximetry Wizard Procedura guidata per ossimetria Daily View Vista Giornaliera Overview Panoramica Statistics Statistiche <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style = "font-weight: 600;"> Avvertenza: </span> <span style = "color: # ff0000;"> Le schede SD S9 ResMed devono essere bloccate </span> <span style = "font-weight : 600; color: # ff0000; "> prima di inserirlo nel computer. & Nbsp; & nbsp; & nbsp; </span> <span style =" color: # 000000; "> <br> Alcuni sistemi operativi scrivono file di indice sulla scheda senza chiedere, il che può rendere illeggibile la tua carta dalla tua macchina cpap. </span> </p> </body> </html> It would be a good idea to check File->Preferences first, Sarebbe una buona idea controllare prima File-> Preferenze, as there are some options that affect import. poiché ci sono alcune opzioni che influiscono sull'importazione. Note that some preferences are forced when a ResMed machine is detected Si noti che alcune preferenze vengono forzate quando viene rilevata una macchina ResMed First import can take a few minutes. La prima importazione può richiedere alcuni minuti. The last time you used your %1... L'ultima volta che hai utilizzato%1 ... last night notte scorsa %2 days ago %2 giorni fa was %1 (on %2) era %1 (su %2) %1 hours, %2 minutes and %3 seconds %1 hours, %2 minutes and %3 seconds Your machine was on for %1. La tua macchina era accesa per%1. <font color = red>You only had the mask on for %1.</font> <colore carattere = rosso>La maschera era attiva solo per %1. </font> under sotto over al di sopra di reasonably close to ragionevolmente vicino a equal to uguale a You had an AHI of %1, which is %2 your %3 day average of %4. Hai avuto un AHI di %1, che è %2 la tua media di %3 giorni di %4. Your CPAP machine used a constant %1 %2 of air La tua macchina CPAP ha usato una costante %1 %2 di aria Your pressure was under %1 %2 for %3% of the time. La tua pressione era sotto %1 %2 per %3% del tempo. Your machine used a constant %1-%2 %3 of air. La tua macchina ha usato una costante di %1-%2 %3 di aria. Your EPAP pressure fixed at %1 %2. La pressione EPAP è stata fissata a %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. La tua pressione IPAP è stata inferiore a %1 %2 per %3% del tempo. Your EPAP pressure was under %1 %2 for %3% of the time. La tua pressione EPAP era sotto %1 %2 per %3% del tempo. Your machine was under %1-%2 %3 for %4% of the time. La tua macchina era sotto %1-%2 %3 per %4% del tempo. 1 day ago 1 giorno fa Your average leaks were %1 %2, which is %3 your %4 day average of %5. Le perdite medie erano %1 %2, che è %3 la tua media di %4 giorni di %5. No CPAP data has been imported yet. Non sono stati ancora importati dati CPAP. gGraph %1 days %1 giorni gGraphView 100% zoom level Livello di zoom del 100% Restore X-axis zoom to 100% to view entire selected period. Ripristina lo zoom dell'asse X al 100% per visualizzare l'intero periodo selezionato. Restore X-axis zoom to 100% to view entire day's data. Ripristina lo zoom dell'asse X al 100% per visualizzare i dati dell'intera giornata. Reset Graph Layout Ripristina layout grafico Resets all graphs to a uniform height and default order. Ripristina tutti i grafici a un'altezza uniforme e un ordine predefinito. Y-Axis Asse-Y Plots Trame CPAP Overlays Sovrapposizioni CPAP Oximeter Overlays Sovrapposizioni di ossimetro Dotted Lines Linee tratteggiate Double click title to pin / unpin Click and drag to reorder graphs Fare doppio clic sul titolo per bloccare / sbloccare Fare clic e trascinare per riordinare i grafici Remove Clone Rimuovi clone Clone %1 Graph Clone %1 grafico OSCAR-code-v1.3.1/Translations/Korean.ko.ts000066400000000000000000015103731417327530600203630ustar00rootroot00000000000000 AboutDialog &About &정보 Release Notes 릴리즈 노트 Credits 도움주신분들 GPL License GPL 라이선스 Close 닫기 Show data folder 데이터 폴더 보기 About OSCAR %1 OSCAR에 대해 %1 Sorry, could not locate About file. 죄송합니다. 파일 정보를 찾을 수 없습니다. Sorry, could not locate Credits file. 죄송합니다. 크레딧 파일을 찾을 수 없습니다. Sorry, could not locate Release Notes. 죄송합니다. 릴리스 노트를 찾을 수 없습니다. OSCAR %1 오스카 %1 Important: 중요: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. 이 버전은 시험판 버전으로 추후 원복하려고 하면 문제가 발생할수 있으므로,<b>데이터 폴더를 수동으로 백업</b>하는 것이 좋습니다. To see if the license text is available in your language, see %1. 사용자 언어로 라이센스 텍스트를 사용할 수 있는지 확인하려면 %1을 참조하십시오. CMS50F37Loader Could not find the oximeter file: 산소 측정기 파일을 찾을 수 없습니다: Could not open the oximeter file: 산소 측정기 파일을 열 수 없습니다.: CMS50Loader Could not get data transmission from oximeter. 산소측정기부터 전송 데이터를 가져올 수 없습니다. Please ensure you select 'upload' from the oximeter devices menu. 산소측정기 장치 메뉴에서 '업로드'를 선택 하십시오. Could not find the oximeter file: 산소측정기 파일을 찾을 수 없습니다: Could not open the oximeter file: 산소측정기 파일을 열수 없습니다: CheckUpdates Checking for newer OSCAR versions 최신 Oscar 버전 확인 중 Daily Go to the previous day 전날로 이동 Show or hide the calender 캘린더 표시 또는 숨기기 Go to the next day 다음날로 이동 Go to the most recent day with data records 데이터 기록이 있는 가장 최근 날짜로 이동 Events 이벤트 View Size 크기 보기 Notes 메모 Journal 일지 B B u u Color 색상 Small Medium Big Zombie 무기력 I'm feeling ... 나의 느낌은 ... Weight 체중 If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value 기본 설정 대화 상자에서 키가 0보다 큰 경우 여기에서 체중을 설정하면 체질량지수(BMI) 값이 표시됩니다 Awesome 활기찬 B.M.I. B.M.I.(체질량지수) Bookmarks 북마크 Add Bookmark 북마크 추가 Starts 시작 Remove Bookmark 북마크 삭제 Flags 플래그 Graphs 그래프 Show/hide available graphs. 유효한 그래프를 표시/숨김. Breakdown 고장 events 이벤트 UF1 UF2 Time at Pressure 압력 시간 No %1 events are recorded this day 이날 %1 이벤트는 기록되지 않았습니다 %1 event %1 이벤트 %1 events %1 이벤트 Session Start Times 세션 시작 시간 Session End Times 세션 종료 시간 Session Information 세션 정보 Oximetry Sessions 산소측정기 세션 Duration 기간 (Mode and Pressure settings missing; yesterday's shown.) (모드 및 압력 설정이 누락되었습니다. 어제와 같습니다.) This bookmark is in a currently disabled area.. 이 북마크는 현재 비활성 영역에 있습니다. CPAP Sessions CPAP 세션 Details 상세 Sleep Stage Sessions 수면 단계 세션 Position Sensor Sessions 위치 센서 세션 Unknown Session 알수없는 세션 Machine Settings 기기 설정 Model %1 - %2 모델 %1 - %2 PAP Mode: %1 PAP 모드: %1 99.5% 99.5% This day just contains summary data, only limited information is available. 이날은 요약 데이터만 포함되며 제한된 정보만 사용할 수 있습니다. Total ramp time 총 ramp(압력상승) 시간 Time outside of ramp ramp(압력상승)외 시간 Start 시작 End 종료 Unable to display Pie Chart on this system 이 시스템에 원형 차트를 표시할 수 없습니다 10 of 10 Event Types 10개 이벤트 유형 중 10개 This CPAP machine does NOT record detailed data 이 CPAP 시스템은 세부 데이터를 기록하지 않습니다 Sorry, this machine only provides compliance data. 죄송합니다.이 기기는 순응 데이터만 제공합니다. "Nothing's here!" "아무것도 없습니다!" No data is available for this day. 이 날은 자료가 없습니다. 10 of 10 Graphs 그래프 10개 중 10개 Oximeter Information 산소측정기 정보 Click to %1 this session. 이 세션에서 %1을 클릭하십시오. disable 비활성 enable 활성 %1 Session #%2 %1 세션 #%2 %1h %2m %3s %1시 %2분 %3초 <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b> 참고 사항 :</b> 아래에 표시된 모든 설정은 전날 이후로 변경된 사항이 없음을 전제로 합니다. SpO2 Desaturations SpO2(혈중산소포화도) 불포화 Pulse Change events 맥박 변화 이벤트 SpO2 Baseline Used SpO2(혈중산소포화도) 기준선 사용됨 Statistics 통계 Total time in apnea 무호흡 총 시간 Time over leak redline 시간 초과 누출 임계선 Event Breakdown 이벤트 분석 Sessions all off! 세션이 모두 끝남! Sessions exist for this day but are switched off. 세션이 이날 존재하지만 꺼져 있습니다. Impossibly short session 난해한 짧은 세션 Zero hours?? 제로 시간 ?? BRICK :( 먹통! :( Complain to your Equipment Provider! 장비 공급자에게 문의 하십시오! Pick a Colour 색상 선택 Bookmark at %1 북마크 at %1 ExportCSV Export as CSV CSV로 내보내기 Dates: 날짜: Resolution: 결과: Details 상세 Sessions 세션 Daily 일간 Filename: 파일명: Cancel 취소 Export 내보내기 Start: 시작: End: 종료: Quick Range: 빠른 범위: Most Recent Day 가장 최근일 Last Week 지난주 Last Fortnight 지난2주 Last Month 지난달 Last 6 Months 지난6달 Last Year 지난해 Everything 모두 Custom 범위지정 Details_ 상세_ Sessions_ 세션_ Summary_ 요약_ Select file to export to 내보낼 파일 선택 CSV Files (*.csv) CSV 파일 (*.csv) DateTime 일시 Session 세션 Event 이벤트 Data/Duration 날짜/기간 Date 날짜 Session Count 세션 카운트 Start 시작 End 종료 Total Time 총 시간 AHI AHI(무저호흡 지수) Count 카운트 FPIconLoader Import Error 불러오기 에러 This Machine Record cannot be imported in this profile. 프로필에서 기기 기록을 가져올 수 없습니다. The Day records overlap with already existing content. 일별 기록이 이미 존재하는 내용과 겹칩니다. Help Hide this message 메시지 숨기기 Search Topic: 검색 주제: Help Files are not yet available for %1 and will display in %2. 도움말 파일은 %1에서 아직 사용할 수 없으며 %2에 표시됩니다. Help files do not appear to be present. 도움말 파일을 찾을 수 없습니다 . HelpEngine did not set up correctly HelpEngine이 정상적으로 설정되지 않았습니다 HelpEngine could not register documentation correctly. HelpEngine이 문서를 정상적으로 등록하지 못했습니다. Contents 내용 Index 목차 Search 검색 No documentation available 사용 가능한 문서가 없습니다 Please wait a bit.. Indexing still in progress 잠시 기다리십시오.. 인덱싱이 계속 진행 중입니다 No 아니요 %1 result(s) for "%2" "%2"의 결과중 %1 clear 지우기 MD300W1Loader Could not find the oximeter file: 산소측정기 파일을 찾을 수 없습니다 : Could not open the oximeter file: 산소측정기 파일을 열 수 없습니다: MainWindow &Statistics &통계 Report Mode 보고서 모드 Standard 표준 Monthly 월간 Date Range 날짜 범위 Statistics 통계 Daily 일간 Overview 개요 Oximetry 산소측정 Import 불러오기 Help 도움말 &File &파일 &View &보기 &Reset Graphs &그래프 재설정 &Help &도움말 Troubleshooting 문제 해결 &Data &데이터 &Advanced &고급설정 Purge ALL Machine Data 모든 기기 데이터 제거 Rebuild CPAP Data CPAP 데이터 재구성 &Import CPAP Card Data &CPAP 카드 데이터 가져 오기 Show Daily view 일별보기 표시 Show Overview view 개요보기 표시 &Maximize Toggle &데이터 불러오기 Maximize window 창 최대화 Reset Graph &Heights &그래프 높이 재설정 Reset sizes of graphs 그래프 크기 재설정 Show Right Sidebar 오른쪽 사이드 바 표시 Show Statistics view 통계보기 표시 Import &Dreem Data &Dreem 데이터 가져 오기 Purge Current Selected Day 현재 선택한 날짜 삭제 &CPAP &양압기 &Oximetry &산소측정기 &Sleep Stage &수면 단계 &Position &위치 &All except Notes &메모를 제외한 모든 항목 All including &Notes 모두 포함 &Notes Show &Line Cursor &줄 커서 표시 Show Daily Left Sidebar 일별 왼쪽 사이드 바 표시 Show Daily Calendar 일별 캘린더 표시 Create zip of CPAP data card CPAP 데이터 카드의 zip 생성 Create zip of OSCAR diagnostic logs OscAR 진단 로그 zip 만들기 Create zip of all OSCAR data 모든 OSCAR 데이터의 zip 생성 Report an Issue 이슈 보고 System Information 시스템 정보 Show &Pie Chart &원형 차트 표시 Show Pie Chart on Daily page 일별 페이지에 원형 차트 표시 Standard graph order, good for CPAP, APAP, Bi-Level 표준 그래프 정렬, CPAP, APAP, Bi-Level에 적합 Advanced 고급 Advanced graph order, good for ASV, AVAPS ASV, AVAPS에 적합한 고급 그래프 정렬 Show Personal Data 개인 데이터 표시 Check For &Updates &Updates 확인 &Preferences &설정 &Profiles &프로필 &About OSCAR &OSCAR 정보 Show Performance Information 실적 정보 표시 CSV Export Wizard CSV 내보내기 마법사 Export for Review 검토를 위해 내보내기 E&xit 나&가기 Exit 나가기 View &Daily 일별 &보기 View &Overview 개요&보기 View &Welcome 환영인사 &보기 Use &AntiAliasing 앤티앨리어싱 &사용 Show Debug Pane 디버그 창 표시 Take &Screenshot 스크린샷 &저장 O&ximetry Wizard 산&소측정기 마법사 Print &Report 보고서 &출력 &Edit Profile &프로필 수정 Import &Viatom/Wellue Data &Viatom/Wellue 데이터 불러오기 Daily Calendar 일간 달력 Backup &Journal 일지 &백업 Online Users &Guide 온라인 사용자 &가이드 &Frequently Asked Questions &자주 묻는 질문 &Automatic Oximetry Cleanup &자동 산소측정기 정리 Change &User 사용자 &변경 Purge &Current Selected Day 현재 &선택한 요일 제거 Right &Sidebar 오르쪽 &슬라이드바 Daily Sidebar 일별 슬라이드 View S&tatistics 통계 &보기 Navigation 네비게이션 Bookmarks 북마크 Records 기록 Exp&ort Data &데이터 내보내기 Profiles 프로필 Purge Oximetry Data 산소측정기 데이터 제거 View Statistics 통계 보기 Import &ZEO Data ZEO 데이터 &가져오기 Import RemStar &MSeries Data RemStar &MSeries 데이터 불러오기 Sleep Disorder Terms &Glossary 수면 장애 용어 &사전 Change &Language 언어 &변경 Change &Data Folder 데이터 폴더 &변경 Import &Somnopose Data Somnopose &데이터 가져오기 Current Days 현재 날짜 Welcome 환영합니다 &About &정보 Please wait, importing from backup folder(s)... 잠시 기다려 주십시오. 백업 폴더에서 가져오는 중입니다... Import Problem 불러오기 문제 Please insert your CPAP data card... CPAP 데이터 카드를 삽입 하십시오... Access to Import has been blocked while recalculations are in progress. 재계산중 가져 오기에 대한 액세스가 차단 되었습니다. CPAP Data Located CPAP 데이터 위치 Import Reminder 가져오기 알림 Find your CPAP data card CPAP 데이터 카드 찾기 Importing Data 데이터 가져 오기 Choose where to save screenshot 스크린 샷을 저장할 위치 선택 Image files (*.png) 이미지 파일 (* .png) The User's Guide will open in your default browser 사용자 가이드가 기본 브라우저에서 열립니다 The FAQ is not yet implemented FAQ가 아직 구현되지 않았습니다 If you can read this, the restart command didn't work. You will have to do it yourself manually. 이 내용을 읽을 수 있으면 재시작 명령이 작동하지 않았습니다. 수동으로 시작해야 합니다. Are you sure you want to rebuild all CPAP data for the following machine: 다음 기기에 대한 모든 CPAP 데이터를 다시 작성 하시겠습니까: For some reason, OSCAR does not have any backups for the following machine: 어떤 이유로 OSCAR에는 다음 기기에 대한 백업이 없습니다: OSCAR does not have any backups for this machine! OSCAR에는이 기기에 대한 백업이 없습니다! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> <i>이 기기의 모든 데이터를 <b></b> 백업 </i>하지 않으면 <font size = + 2>이 기기의 데이터를 <b> 영구적으로 </b 잃게 됩니다. >! </font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> 다음 기기에서 OSCAR의 기기 데이터베이스를 <font size=+2> 제거합니다 </font> : </p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: 파일 권한 오류로 인해 제거 프로세스가 실패했습니다. 다음 폴더를 수동으로 삭제해야합니다: No help is available. 도움을받을 수 없습니다. %1's Journal %1 일지 Choose where to save journal 일지 저장 위치 선택 XML Files (*.xml) XML 파일 (*.xml) Export review is not yet implemented 내보내기 리뷰가 아직 구현되지 않았습니다 Would you like to zip this card? 이 카드를 압축 하시겠습니까? Choose where to save zip zip 저장 위치 선택 ZIP files (*.zip) Creating zip... ZIP 생성 중 ... Calculating size... 크기 계산 중 ... Reporting issues is not yet implemented 이슈보고는 아직 구현되지 않았습니다 OSCAR Information OSCAR 정보 Help Browser 도움말 브라우저 %1 (Profile: %2) %1 (프로필: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. 데이터 카드의 루트 폴더 또는 드라이브 문자를 선택하고 그 안에있는 폴더는 선택하지 마십시오. Please open a profile first. 먼저 프로필을 여십시오. Check for updates not implemented 구현되지 않은 업데이트 확인 Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. 모든 CPAP 데이터</i>에 대해 <i> your ><b>own </b> 백업을 수행한 경우에도 이 작업을 완료할 수 있지만 백업에서 수동으로 복원해야 합니다. Are you really sure you want to do this? 정말이 작업을 하시겠습니까? Because there are no internal backups to rebuild from, you will have to restore from your own. 재구성할 내부 백업이 없으므로 사용자가 직접 복원해야 합니다. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) 지금 자신의 백업에서 가져 오시겠습니까? (당신이 할때까지 이 컴퓨터에 대한 데이터를 볼 수 없습니다) Note as a precaution, the backup folder will be left in place. 예방 조치로 백업 폴더는 그대로 유지됩니다. Are you <b>absolutely sure</b> you want to proceed? <b>무조건</b> 진행하기를 원하십니까? The Glossary will open in your default browser 용어집이 기본 브라우저에서 열립니다 There was a problem opening %1 Data File: %2 %1 데이터 파일을 여는 중 문제가 발생했습니다. %2 %1 Data Import of %2 file(s) complete %2중 %1 파일의 데이터 가져오기 완료 %1 Import Partial Success %1 가져오기 부분 성공 %1 Data Import complete %1 데이터 가져오기 완료 Are you sure you want to delete oximetry data for %1 %1에 대한 산소측정 데이터를 삭제 하시겠습니까 <b>Please be aware you can not undo this operation!</b> <b>이 작업을 실행 취소 할 수 없습니다.!</b> Select the day with valid oximetry data in daily view first. 먼저 일별 보기에서 유효한 산소 측정 데이터가 포함된 요일을 선택 하십시오. Loading profile "%1" 프로필 "%1"로드 중 Imported %1 CPAP session(s) from %2 %2로부터 %1 개의 CPAP 세션 가져 오기 Import Success 가져오기 성공 Already up to date with CPAP data at %1 %1에 CPAP 데이터에 대한 최신 정보 Up to date 최신 정보 Couldn't find any valid Machine Data at %1 %1에서 유효한 기기 데이터를 찾을 수 없습니다 Choose a folder 폴더 선택 No profile has been selected for Import. 가져 오기에 대한 프로파일이 선택되지 않았습니다. Import is already running in the background. 가져오기가 이미 백그라운드에서 실행 중입니다. A %1 file structure for a %2 was located at: %2에 대한 %1 파일 구조는 다음 위치에 있습니다: A %1 file structure was located at: %1 파일 구조는 다음 위치에 있습니다: Would you like to import from this location? 이 위치에서 가져 오시겠습니까? Specify 지정 Access to Preferences has been blocked until recalculation completes. 재계산 완료될 때까지 기본 설정에 대한 액세스가 차단되었습니다. There was an error saving screenshot to file "%1" 파일 "%1"에 스크린 샷을 저장하는 중 오류가 발생했습니다 Screenshot saved to file "%1" %1"파일에 스크린 샷 저장 됨 Please note, that this could result in loss of data if OSCAR's backups have been disabled. OSCAR의 백업이 비활성화 된 경우 데이터가 손실 될 수 있습니다. There was a problem opening MSeries block File: MSeries 블록 파일을 여는 중 문제가 발생했습니다: MSeries Import complete MSeries 가져 오기 완료 MinMaxWidget Auto-Fit 자동 맞춤 Defaults 기본값 Override 덮어쓰기 The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Y축 스케일링 모드, 자동 스케일링을 위한 'Auto-Fit', 제조업체별 설정의 'Defaults', 자신만의 설정을 위한 'Override'. The Minimum Y-Axis value.. Note this can be a negative number if you wish. 최소 Y축 값 원하는 경우 음수가 될 수 있습니다. The Maximum Y-Axis value.. Must be greater than Minimum to work. 최대 Y축 값. 동작하려면 최소값보다 커야 합니다. Scaling Mode 스케일링 모드 This button resets the Min and Max to match the Auto-Fit 이 버튼은 자동 맞춤과 일치 하도록 최소 및 최대를 재설정 합니다 NewProfile Edit User Profile 사용자 프로필 편집 I agree to all the conditions above. 위의 모든 조건에 동의합니다. User Information 사용자 정보 User Name 사용자 이름 Password Protect Profile 암호로 보호된 프로필 Password 비밀번호 ...twice... ...재입력... Locale Settings 지역 설정 Country 국가 TimeZone 시간대 about:blank 공백에:관하여 Very weak password protection and not recommended if security is required. 매우 취약한 암호 보호 기능이며 보안이 필요한 경우 권장되지 않습니다. DST Zone DST 구역 Personal Information (for reports) 개인 정보 (보고서 용) First Name Last Name 이름 It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. 전체를 ok하거나 건너 뛰어도 괜찮습니다. 그러나 특정 계산의 정확성을 높이려면 귀하의 나이가 필요합니다. D.O.B. 생년.월.일. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>몇 가지 계산의 정확성을 높이기 위해 생물학적 (출생) 성별이 필요한 경우도 있지만 공백으로 두고 그 중 아무 것도 건너뛰지 않아도 됩니다.</p></body></html> Gender 성별 Male 남성 Female 여성 Height Metric English 영문 Contact Information 연락처 정보 Address 주소 Email 이메일 Phone 전화 CPAP Treatment Information PAP 치료 정보 Date Diagnosed 진단 날짜 Untreated AHI 치료되지 않은 AHI CPAP Mode CPAP(고정) 모드 CPAP CPAP(고정) APAP APAP(자동) Bi-Level Bi-Level(이중형-중추성치료용) ASV ASV(지능형 인공호흡기) RX Pressure RX 압력 Doctors / Clinic Information 의사 / 클리닉 정보 Doctors Name 의사명 Practice Name 환자명 Patient ID 환자 ID &Cancel &취소 &Back &이전 &Next &다음 Select Country 국가 선택 Welcome to the Open Source CPAP Analysis Reporter 오픈 소스 CPAP 분석 리포터에 오신 것을 환영합니다 This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. 이 소프트웨어는 귀하의 CPAP 기기 및 관련 장비에 의해 생성된 데이터를 검토 하는데 도움을 주기 위해 고안 되었습니다. PLEASE READ CAREFULLY 주의 깊게 읽으십시오 Accuracy of any data displayed is not and can not be guaranteed. 표시된 모든 데이터의 정확성은 보장되지 않으며 보장할수 없습니다. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. 작성된 모든 보고서는 개인 용도로만 사용되며 순응도 체크 또는 의학적 진단 목적으로는 적합하지 않습니다. Use of this software is entirely at your own risk. 이 소프트웨어의 사용은 전적으로 귀하의 책임입니다. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR은 저작권 &copy;2011-2018 Mark Watkins 및 부분 &copy;2019-2022 The OSCAR Team OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR는 GNU Public License v3 하에 자유롭게 배포되었으며 어떠한 보증도 제공하지 않으며 어떠한 목적으로도 적합성을 주장하지 않습니다. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR는 단순히 데이터 뷰어로 사용되며 의사의 유능한 의료 지침을 대신 할 수 없습니다. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. OSCAR는 데이터보기를 사용하여 의사의 유능한 의료 기기를 사용하지 마십시오. Please provide a username for this profile 이 프로필에 대한 사용자 이름을 입력하십시오 Passwords don't match 암호가 일치하지 않습니다 Profile Changes 프로필 변경 Accept and save this information? 이 정보를 수락하고 저장 하시겠습니까? &Finish &끝 &Close this window &현재창 닫기 Overview Range: 구간: Last Week 지난주 Last Two Weeks 지난2주 Last Month 지난달 Last Two Months 지난2달 Last Three Months 지난3달 Last 6 Months 지난6달 Last Year 지난해 Everything 모두 Custom 범위지정 Snapshot 스냅샷 Start: 시작: End: 종료: Reset view to selected date range 선택한 기간으로보기 재설정 Toggle Graph Visibility 그래프 보기 전환 Drop down to see list of graphs to switch on/off. 드롭 다운하여 그래프의 목록을 보고 켜거나 끕니다. Graphs 그래프 Respiratory Disturbance Index 호흡기 방해 지수 Apnea Hypopnea Index 무호흡증 저호흡 지수 Usage 사용 Usage (hours) 사용 (시간) Session Times 세션 타임 Total Time in Apnea 무호흡의 총 시간 Total Time in Apnea (Minutes) 무호흡 총 시간 (분) Body Mass Index 신체 질량 지수 How you felt (0-10) 어땠나요? (0-10) 10 of 10 Charts 10개 차트 중 10개 Show all graphs 모든 그래프 표시 Hide all graphs 모든 그래프 숨김 OximeterImport Oximeter Import Wizard 산소측정기 가져 오기 마법사 Skip this page next time. 다음번 현재 페이지 건너뛰기. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">참고하세요: </span><span style=" font-style:italic;">먼저 아래의 풀다운 메뉴에서 올바른 산소 측정기 유형을 선택합니다.</span></p></body></html> Where would you like to import from? 어디에서 가져 오시겠습니까? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">먼저 다음 그룹에서 Oximeter를 선택하십시오:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. CMS50E / F 사용자는 직접 가져올 때 OSCAR에서 메시지를 표시 할 때까지 장치에서 업로드를 선택하지 마십시오. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>활성화 된 경우, OSCAR는 현재 컴퓨터를 사용하여 CMS50의 내부 클럭을 자동으로 재설정합니다.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html> <head /> <body> <p> 여기에이 산소 농도계의 7자 이름을 입력 할 수 있습니다. </p> </body> </html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html> <head /> <body> <p>이 옵션은 가져 오기가 완료된 후 산소 농도계에서 가져온 세션을 지웁니다. </p> <p> OSCAR가 세션을 저장하기 전에 문제가 발생하면 다시 가져올 수 없으므로주의해서 사용하십시오. </p> </body> </html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html> <head /> <body> <p>이 옵션을 사용하면 산소 농도계 내부 기록에서 케이블을 통해 가져올 수 있습니다. </p> <p>이 옵션을 선택한 후에는 이전 Contec 산소 농도계를 사용해야합니다. 업로드를 시작하는 기기의 메뉴입니다. </p> </body> </html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>실행중인 컴퓨터에 밤새 부착되는 것을 마음에 들지 않으면,이 옵션은 심장 박동의 표시를 제공하는 유용한 흉막 조영 그래프를 제공합니다.정상적인 산소 측정 판독 값의 상단.</p></body></html> Record attached to computer overnight (provides plethysomogram) 컴퓨터에 밤새 연결된 기록 (plethysomogram 제공) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>이 옵션을 사용하면 SpO2Review와 같이 맥박 산소포화도 측정기와 함께 제공된 소프트웨어로 만든 데이터 파일에서 가져올 수 있습니다..</p></body></html> Import from a datafile saved by another program, like SpO2Review SpO2Review와 같은 다른 프로그램에서 저장 한 데이터 파일에서 가져 오기 Please connect your oximeter device 산소측정기 장치를 연결하십시오 If you can read this, you likely have your oximeter type set wrong in preferences. 이것을 읽을 수 있다면 산소측정기 유형을 환경 설정에서 잘못 설정했을 가능성이 큽니다. Please connect your oximeter device, turn it on, and enter the menu 산소 농도계 장치를 연결하고 켜고 메뉴로 들어갑니다 Press Start to commence recording 시작을 눌러 기록을 시작하십시오 Show Live Graphs 라이브 그래프 표시 Duration 구간 Pulse Rate 맥박수 Multiple Sessions Detected 감지 된 여러 세션 Start Time 시작 시간 Details 상세 Import Completed. When did the recording start? 가져 오기가 완료되었습니다. 기록이 언제 시작 되었습니까? Oximeter Starting time 산소측정기 시작 시간 I want to use the time reported by my oximeter's built in clock. 산소측정기가 시계에 내장 된 시간을 보고 싶습니다. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. 나는 CPAP 머신에서 같은 시간에 (또는 그와 가까운 시간에)이 산소 농도계 기록을 시작했다. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>메모: CPAP 세션 시작 시간과의 동기화는 항상 보다 정확할 것입니다..</p></body></html> Choose CPAP session to sync to: 동기화 할 CPAP 세션을 선택하십시오: You can manually adjust the time here if required: 필요한 경우 여기에서 시간을 수동으로 조정할 수 있습니다: HH:mm:ssap HH:mm:ssap &Cancel &취소 &Information Page &정보 페이지 CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time 기기 날짜 / 시간 설정 <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>다음 가져 오기를 업데이트 할 수 있도록 설정하려면 선택합니다.</p></body></html> Set device identifier 기기 식별자 설정 Erase session after successful upload 업로드 성공 후 세션 지우기 Import directly from a recording on a device 장치의 레코딩에서 직접 가져 오기 <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">CPAP 사용자를위한 알림 : </span><span style=" color:#fb0000;">CPAP 세션을 먼저 가져 오는 것을 잊지 않으셨습니까? <br/></span>만약 잊었다면 이 산소 측정 세션을 동기화 할 수있는 유효한 시간이 없습니다.<br/>기기간에 양호한 동기화를 유지하려면 항상 동시에 두 가지를 시작하십시오.</p></body></html> Please choose which one you want to import into OSCAR OSCAR로 가져올 항목을 선택하십시오 Day recording (normally would have) started 일 녹화가 시작됨(일반적으로 시작되었음) <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> OSCAR은이 산소 측정 세션을 저장할 위치를 알기 위한 시작 시간을 필요로 합니다. 다음 옵션 중 하나를 선택 하십시오 &Retry &재시도 &Choose Session &세션 선택 &End Recording &레코딩 종료 &Sync and Save &동기화 및 저장 &Save and Finish &저장 및 마침 &Start &시작 Scanning for compatible oximeters 호환 산소측정기 스캔 Could not detect any connected oximeter devices. 연결된 산소 농도계를 감지 할 수 없습니다. Connecting to %1 Oximeter %1 산소측정기에 연결 중 Renaming this oximeter from '%1' to '%2' Renaming this oximeter from '%1' to '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. 산소측정기 이름이 다릅니다. 프로필이 하나만 있고 프로필간에 공유하는 경우 두 프로필에서 이름을 동일하게 설정하십시오. "%1", session %2 "%1", 세션 %2 Nothing to import 가져올 항목 없습니다 Your oximeter did not have any valid sessions. 산소 측정기에 유효한 세션이 없습니다. Close 닫기 Waiting for %1 to start %1이 (가) 시작될 때까지 대기 중입니다 Waiting for the device to start the upload process... 기기가 업로드 프로세스를 시작하기를 기다리는 중... Select upload option on %1 %1의 업로드 옵션 선택 You need to tell your oximeter to begin sending data to the computer. 데이터를 컴퓨터에 보내기 시작하려면 산소 측정기에 알려야 합니다. Please connect your oximeter, enter it's menu and select upload to commence data transfer... 산소측정기를 연결하고 메뉴를 입력 한 다음 업로드를 선택하여 데이터 전송을 시작하십시오.... %1 device is uploading data... %1 장치에서 데이터를 업로드하는 중입니다... Please wait until oximeter upload process completes. Do not unplug your oximeter. 산소측정기 업로드 프로세스가 완료될 때까지 기다려주십시오. 산소측정기를 분리하지 마십시오. Oximeter import completed.. 산소측정기 불러오기가 완료 되었습니다 .. Select a valid oximetry data file 유효한 산소측정기 데이터 파일을 선택하십시오 Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) 산소측정기 파일들 (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: 산소측정기 모듈이 주어진 파일을 분석 할 수 없습니다: Live Oximetry Mode 라이브 산소측정 트리 모드 Live Oximetry Stopped 활성 산소계량 정지 Live Oximetry import has been stopped 실시간 Oximetry 가져오기가 중지 되었습니다 Oximeter Session %1 산소측정기 세션 %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR는 CPAP 세션 데이터와 함께 Oximetry 데이터를 추적 할 수있는 기능을 제공하므로 CPAP 치료의 효과에 대한 중요한 정보를 얻을 수 있습니다. 또한 맥박 측정기로 독립형으로 작동하므로 기록된 데이터를 저장, 추적 및 검토 할 수 있습니다. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! 산소측정과 CPAP 데이터를 동기화하려는 경우 계속하기 전에 먼저 CPAP 세션을 가져오십시오! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. OSCAR에서 Oximeter 장치를 찾아서 직접 읽을 수 있으려면 올바른 장치 드라이버 (예 : USB - 직렬 UART)가 컴퓨터에 설치되어 있는지 확인해야합니다. 이에 대한 자세한 내용은 %1 여기를%2 클릭하십시오. Oximeter not detected 산소측정기가 감지되지 않음 Couldn't access oximeter 산소측정기 액세스 할 수 없습니다 Starting up... 시작 중... If you can still read this after a few seconds, cancel and try again 몇 초 후에도 내용을 읽을 수 없으면 취소하고 다시 시도 하십시오 Live Import Stopped 라이브 가져 오기 중지됨 %1 session(s) on %2, starting at %3 %2에서 세션(s)을 %1, %3부터 시작 No CPAP data available on %1 %1에서 사용할수 있는 CPAP 데이터가 없습니다 Recording... 기록중... Finger not detected 손가락이 감지되지 않음 I want to use the time my computer recorded for this live oximetry session. 컴퓨터가 실시간 산소포화도 측정 세션을 위해 기록한 시간을 사용하고 싶습니다. I need to set the time manually, because my oximeter doesn't have an internal clock. 산소측정기가 내부 시계를 가지고 있지 않기 때문에 수동으로 시간을 설정해야 합니다. Something went wrong getting session data 세션 데이터를 가져 오는 중 오류가 발생했습니다 Welcome to the Oximeter Import Wizard 산소측정기 가져 오기 마법사에 오신 것을 환영합니다 Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. 맥박 산소 측정기는 혈액 산소 포화도를 측정하는데 사용되는 의료 기기입니다. 연장된 무호흡 이벤트 및 비정상적인 호흡 패턴 동안, 혈중 산소 포화도가 크게 떨어질 수 있으며 의료 조치가 필요한 문제를 나타낼 수 있습니다. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR은 현재 Contec CMS50D +, CMS50E, CMS50F 및 CMS50I 직렬 산소 농도계와 호환됩니다. (참고 : 블루투스 모델에서 직접 가져 오기는 아마도 가능) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Pulox와 같은 다른 회사는 단순히 Contec CMS50을 Pulox PO-200, PO-300, PO-400과 같은 새로운 이름으로 재지정하는 것을 원할 수 있습니다. 이것들 역시 효과가 있을 것입니다. It also can read from ChoiceMMed MD300W1 oximeter .dat files. 또한 ChoiceMMed MD300W1 Oximeter .dat 파일에서도 읽을 수 있습니다. Please remember: 기억해 주세요: Important Notes: 중요 사항 : Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Contec CMS50D + 장치에는 내부 시계가 없으며 시작 시간을 기록하지 않습니다. CPAP 세션이 없으면 가져 오기 프로세스가 완료된 후 수동으로 시작 시간을 입력해야합니다. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. 내부 시계가 있는 장치의 경우에도 CPAP 세션과 동시에 시간 기록을 시작하는 습관을 유지하는 것이 좋습니다. CPAP 내부 시계는 시간이 지남에 따라 표류하는 경향이 있으며 모든 것을 쉽게 재설정 할 수는 없기 때문입니다. Oximetry Date 날짜 d/MM/yy h:mm:ss AP yy/MM/d h:mm:ss AP R&eset &리셋 Pulse 맥백 &Open .spo/R File &O열기 .spo/R 파일 Serial &Import Serial &가져오 &Start Live &라이브 시작 Serial Port 시리얼 포트 &Rescan Ports &포트 다시스캔 PreferencesDialog Preferences 환경 설정 &Import &불러오기 Combine Close Sessions 닫기 세션 결합 Minutes Multiple sessions closer together than this value will be kept on the same day. 이 값보다 더 가까운 여러 세션은 같은 날에 유지됩니다. Ignore Short Sessions 짧은 세션 무시 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">이보다 짧은 세션은 표시되지 않음<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time 일 분할 시간 Sessions starting before this time will go to the previous calendar day. 이 시간 전에 시작하는 세션은 이전 요일로 이동합니다. Session Storage Options 세션 저장 옵션 Compress SD Card Backups (slower first import, but makes backups smaller) SD 카드 백업 압축 (처음 가져 오기는 느리지만 백업 데이터를 더 작게 만듭니다) &CPAP &양압기 Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. 이 사용법을 적용한 날을 "불일치"라고 간주 하십시오. 4 시간은 일반적으로 준수로 간주됩니다. hours 시간 Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. 실험적 이벤트 플래그 기능 향상을 활성화 / 비활성화 합니다. 경계선 이벤트를 감지하고 일부 기기를 놓친다. 가져 오기 전에이 옵션을 사용하도록 설정해야 합니다. 그렇지 않으면 제거가 필요합니다. Flow Restriction 유량 제한 Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. 중간 값에서 기류 제한 비율. 20 %의 값은 무호흡을 감지하는데 적합합니다. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">사용자 정의 플래그 지정은 시스템에서 누락 된 이벤트를 탐지하는 실험적인 방법입니다. 거기엔 <span style=" text-decoration: underline;">not</span> 무호흡수치가 포합되진 않습니다.</p></body></html> Duration of airflow restriction 기류 제한 기간 s Event Duration 이벤트 기간 Allow duplicates near machine events. 머신 이벤트 근처에서 중복을 허용하십시오. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. AHI / Hour 그래프에서 각 포인트에 대해 고려되는 데이터의 양을 조정합니다. 기본값은 60 분입니다.이 값으로 남겨 두는 것이 좋습니다. minutes Reset the counter to zero at beginning of each (time) window. 각 (시간) 창의 시작 부분에서 카운터를 0으로 재설정하십시오. Zero Reset Zero 리셋 CPAP Clock Drift CPAP 시간대 이동 Do not import sessions older than: 다음보다 오래된 세션을 가져 오지 마십시오: Sessions older than this date will not be imported 이 날짜보다 오래된 세션은 가져 오지 않습니다 dd MMMM yyyy yyyy MMMM dd User definable threshold considered large leak 큰 누출로 간주되는 사용자 정의 임계값 Whether to show the leak redline in the leak graph 누출 그래프에 누출 임계선을 표시할지 여부 Search 검색 &Oximetry &산소측정기 Show in Event Breakdown Piechart 이벤트 분석 파이 차트에 표시 Resync Machine Detected Events (Experimental) 기기 재동기화 감지 이벤트(실험) Percentage drop in oxygen saturation 산소 포화도의 백분율 저하 Pulse 맥박 Sudden change in Pulse Rate of at least this amount 최소양의 맥박 속도 급격한 변화 bpm bpm(심박수) Minimum duration of drop in oxygen saturation 산소 포화의 최소 강하 지속시간 Minimum duration of pulse change event. 펄스 변경 이벤트의 최소 지속 시간. Small chunks of oximetry data under this amount will be discarded. 이 양에 따른 작은 산소 측정 데이터는 폐기될 것이다. &General &일반적 Changes to the following settings needs a restart, but not a recalc. 다음 설정을 변경하면 다시 시작해야하지만 다시 시작할 필요는 없습니다. Preferred Calculation Methods 선호하는 계산 방법 Middle Calculations 중간 계산 Upper Percentile 상단 백분위 수 Session Splitting Settings 세션 분리 설정 <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">이 설정은 주의해서 사용해야 합니다 ...</span> 개별 일 기록에서 나온 요약 세션만 함께 보관하면 특정 계산만 제대로 작동하므로 요약만 일의 정확도를 비롯한 결과가 있습니다. </p><p><span style=" font-weight:600;">ResMed users:</span> 12시 정오에 다시 시작해야하는 것이 당일에 있어야 한다고해서 ResMed의 데이터가 우리에게 동의하는것은 아닙니다. STF.edf 요약 색인 형식에는 좋은 생각이 아닌 심각한 단점이 있습니다.</p><p>이 옵션은 신경 쓰지 않는 사람들을 달래기 위해 존재합니다. &quot;fixed&quot; 아무리 비용이 들지만 비용이 있다는 것을 알고 있습니다. 매일 밤 SD 카드를 보관하고 일주일에 한 번 이상 가져 오는 경우 이 문제가 자주 발생하지 않습니다.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) 요약 일을 나누지 마십시오 (경고 : 툴팁을 읽으십시오!) Memory and Startup Options 메모리 및 시작 옵션 Pre-Load all summary data at startup 시작시 모든 요약 데이터 사전로드 <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>이 설정은 사용 후 파형 및 이벤트 데이터를 메모리에 저장하여 재 방문 날짜를 빠르게합니다.</p><p>운영 체제가 이전에 사용한 파일도 캐시하므로 실제로는 필수 옵션은 아닙니다.</p><p>권장 사항은 컴퓨터에 많은 메모리가없는 경우 스위치를 끄는 것입니다.</p></body></html> Keep Waveform/Event data in memory 파형/이벤트 데이터를 메모리에 유지 <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>가져오는 동안 중요하지 않은 확인 대화 상자 축소.</p></body></html> Import without asking for confirmation 확인 요구 없이 가져오기 This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. 이 계산을 위해서는 CPAP 기기가 전체 누출 데이터를 제공해야합니다. (예 : PRS1,하지만 ResMed는 없습니다.) 여기서 사용된 의도하지 않은 누출 계산은 선형이며 마스크 배출 곡선을 모델링하지 않습니다. 몇 가지 다른 마스크를 사용하는 경우 평균 값을 선택하십시오. 그것은 충분히 근접할 겁니다. Calculate Unintentional Leaks When Not Present 존재하지 않을 때 의도하지 않은 누출 계산 Note: A linear calculation method is used. Changing these values requires a recalculation. 참고 : 선형 계산 방법이 사용됩니다. 이 값을 변경하려면 다시 계산해야 합니다. General CPAP and Related Settings 일반 CPAP 및 관련 설정 Enable Unknown Events Channels 알수 없는 이벤트 채널 사용 AHI Apnea Hypopnea Index AHI(무저호흡지수) RDI Respiratory Disturbance Index 호흡 방해 지수 AHI/Hour Graph Time Window AHI / 시간 그래프 시간 창 Preferred major event index 선호하는 주요 이벤트 지수 Compliance defined as 순응도(시간) 정의 Flag leaks over threshold 임계값 초과 표시 Seconds <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>참고: 이것은 시간대 수정을 위한 것이 아니다! 운영 체제 시계와 표준 시간대가 올바르게 설정되어 있는지 확인하십시오.</p></body></html> Hours 시간 For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. 일관성을 위해 ResMed 사용자는 여기에서 95%를 사용해야하며, 이는 요약 전용 일에 사용할 수있는 유일한 값이기 때문입니다. Median is recommended for ResMed users. ResMed 사용자에게는 중간값이 권장됩니다. Median 중간값 Weighted Average 가중 평균 Normal Average 보통 평균 True Maximum 실제 최대값 99% Percentile 99% 퍼센트 Maximum Calcs 최대값 계산 General Settings 일반 설정 Daily view navigation buttons will skip over days without data records 일간보기 탐색버튼 클릭시 데이터 기록이 없는 날 건너뜀 Skip over Empty Days 미 사용일 건너 뛰기 Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. 성능을 향상시키기 위해 멀티 CPU 코어를 사용 허용. 주로 데이터 불러오기에 영향을 줌. Enable Multithreading 멀티 스레딩 사용 Bypass the login screen and load the most recent User Profile 로그인 화면을 건너띄고 가장 최근의 사용자 프로필 로드 Create SD Card Backups during Import (Turn this off at your own peril!) 가져오는 동안 SD 카드 백업 생성 (스스로 위험을 감수 한다면 이것을 끄세요!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>실제 최대 값은 데이터 세트의 최대 값입니다..</p><p> 99번째 백분위수는 가장 희귀한 특이치를 걸러낸다.</p></body></html> Combined Count divided by Total Hours 합계 수를 총 시간으로 나눈 값 Time Weighted average of Indice 시간 가중 평균 지수 Standard average of indice 표준 평균 지수 Custom CPAP User Event Flagging 맞춤 CPAP 사용자 이벤트 신고 Events 이벤트 Reset &Defaults 기본값 &재설정 <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;"> 경고: 설정한다고 그것이 좋은 실행이라는 것을 의미하지는 않습니다.</p></body></html> Waveforms 파형 Flag rapid changes in oximetry stats 산소측정 통계의 신속한 변화 플래그 Other oximetry options 기타 산소 측정 옵션 Discard segments under 세그먼트 삭제 Flag Pulse Rate Above 상의 맥박 비율 표시 Flag Pulse Rate Below 하의 맥박 비율 표시 Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. 디스크 공간을 절약하기 위해 ResMed (EDF) 백업 압축. 백업 된 EDF 파일은 .gz 형식으로 저장되며, Mac & 리눅스 플랫폼 .. OSCAR는이 압축 된 백업 디렉토리에서 기본적으로 가져올 수 있습니다. ResScan과 함께 사용하려면 먼저 .gz 파일을 압축 해제해야합니다. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. 다음 옵션은 OSCAR에서 사용하는 디스크 공간의 크기에 영향을 미치며 가져 오기 시간에 영향을 줍니다. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. OSCAR의 데이터는 절반 정도의 공간을 차지합니다. 하지만 가져 오기와 하루 변경 시간이 더 오래 걸립니다.. 작은 SSD디스크가 장착된 새 컴퓨터를 가지고 있다면 이 방법이 좋습니다. Compress Session Data (makes OSCAR data smaller, but day changing slower.) 세션 데이터 압축 (OSCAR 데이터를 더 작게 만들지만 하루 변경 속도가 느려짐.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) 이것은 ResMed 머신에 대한 SD 카드 데이터의 백업을 유지합니다. ResMed S9 시리즈 기기는 7 일이 지난 고해상도 데이터를 삭제합니다. 30 일이 지난 데이터를 그래프로 표시합니다 .. OSCAR는 재설치가 필요한 경우이 데이터의 사본을 보관할 수 있습니다. (디스크 공간이 부족하거나 그래프 데이터에 신경 쓰지 않는 한 적극 권장) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>모든 요약 데이터를 사전에 미리 로드하여 OSCAR를 약간 느리게 시작하게 하여 나중에 전체 탐색 및 몇 가지 다른 계산 속도를 높입니다. </p><p>많은 양의 데이터가 있는 경우 이 기능을 사용하지 않는 것이 좋습니다. 하지만 일반적으로 데이터를 보려면<span style=" font-style:italic;">모두</span> 개요에서 모든 요약 데이터는 여전히 로드 되어야 합니다. </p><p>이 설정은 파형 및 이벤트 데이터에 영향을 미치지 않으며 필요에 따라 항상 로드 되도록 요구됩니다.</p></body></html> 4 cmH2O 4 cmH2O(압력) 20 cmH2O 20 cmH2O(압력) This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. 이 실험 옵션은 OSCAR의 이벤트 플래그 시스템을 사용하여 기계 감지 이벤트 포지셔닝을 개선합니다.. Show flags for machine detected events that haven't been identified yet. 아직 식별되지 않은 머신 감지 이벤트에 대한 플래그를 표시합니다. Show Remove Card reminder notification on OSCAR shutdown OSCAR 종료시 카드 미리 알림 알림 표시 Check for new version every 매번 새 버전 확인 days. 일. Last Checked For Updates: 마지막으로 업데이트 확인 : TextLabel TextLabel I want to be notified of test versions. (Advanced users only please.) 테스트 버전에 대한 알림을 받고 싶습니다. (고급 사용자만 해당) &Appearance &외관 Graph Settings 그래프 설정 <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>프로필을로드 할 때 열리는 탭입니다. (참고 : OSCAR이 시작시 프로필을 열지 않도록 설정하면 프로필로 기본 설정됩니다.)</p></body></html> Bar Tops 바 상단 Line Chart 선형 차트 Overview Linecharts 선형 차트 개용 Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. OSCAR의 그래프에서 렌더링 문제가 발생하면 이것을 기본 설정 (Desktop OpenGL)에서 변경하십시오. <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p> 민감한 양방향 터치패드에서 쉽게 확대/축소할 때 스크롤할 수 있는 값.</p></body></html> How long you want the tooltips to stay visible. 툴팁을 얼마나 오랫동안 보길 원하십니까. Scroll Dampening 스크롤 감소 Tooltip Timeout 툴팁 타임 아웃 Default display height of graphs in pixels 그래프의 기본 표시 높이 (픽셀 단위) Graph Tooltips 그래프 툴팁 The visual method of displaying waveform overlay flags. 파형 겹침 플래그를 표시하는 시각적 방법. Standard Bars 표준 막대 Top Markers 최고 마커 Graph Height 그래프 높이 Changing SD Backup compression options doesn't automatically recompress backup data. SD 백업 압축 옵션을 변경해도 백업 데이터가 자동으로 다시 압축되지 않습니다. Auto-Launch CPAP Importer after opening profile 프로필 연후 CPAP 가져 오기 프로그램 자동 실행 Automatically load last used profile on start-up 시작시 마지막 사용한 프로필 자동 로드 <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html> <head /> <body> <p> OSCAR 개발자가 아직 테스트하지 않은 기기 모델에서 데이터를 가져올 때 경고를 제공합니다. </p> </body> </html> Warn when importing data from an untested machine 테스트되지 않은 시스템에서 데이터를 가져올 때 경고 <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html> <head /> <body> <p> 이전에 OSCAR 개발자가 본 것과 다른 데이터를 가져올 때 경고를 제공합니다. </p> </body> </html> Warn when previously unseen data is encountered 이전에 보지 못한 데이터가 발견되면 경고 Your masks vent rate at 20 cmH2O pressure 20cmH2O 압력에서 마스크 배출 속도 Your masks vent rate at 4 cmH2O pressure 4cmH2O 압력에서 마스크 배출 속도 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html> <head /> <body> <p> <span style = "font-weight : 600;"> 참고 : </span> 요약 디자인 제한으로 인해 ResMed 시스템은 이러한 설정 변경을 지원하지 않습니다. </p > </body> </html> Oximetry Settings 산소측적기 설정 Always save screenshots in the OSCAR Data folder 항상 OSCAR 데이터 폴더에 스크린 샷 저장 Check For Updates 업데이트 확인 You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. 오스카의 테스트 버전을 사용하고 있습니다. 테스트 버전은 7일에 한 번 이상 자동으로 업데이트를 확인합니다. 간격을 7일 미만으로 설정할 수 있습니다. Automatically check for updates 자동으로 업데이트 확인 How often OSCAR should check for updates. 오스카에서 업데이트를 확인하는 빈도입니다. If you are interested in helping test new features and bugfixes early, click here. 새로운 기능 및 버그 수정을 조기에 테스트하려면 여기를 클릭하십시오. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Oscar의 초기 버전을 테스트하는 데 도움이 필요하면 Oscar 테스트에 대한 Wiki 페이지를 참조하십시오. 오스카를 테스트하고, 오스카를 개발하고, 기존 또는 새로운 언어로 번역하는 것을 돕고 싶은 모든 사람들을 환영합니다. https://www.sleepfiles.com/OSCAR On Opening 열기 Profile 프로필 Welcome 환영합니다 Daily 일간 Statistics 통계 Switch Tabs 탭 전환 No change 변화 없습니다 After Import 가져온 후 Overlay Flags 오버레이 플래그(표시 중첩) Line Thickness 선 두께 The pixel thickness of line plots 선 그래프의 픽셀 두께 Other Visual Settings 기타 시각적 설정 Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. 앤티 앨리어싱은 그래프 플롯에 스무딩을 적용합니다. 어떤 플롯은 이것으로 더 매력적으로 보입니다. 이는 인쇄 된 보고서에도 영향을 미칩니다. 그것을 시도하고 귀하이 그것을 좋아하는지보십시오. Use Anti-Aliasing 앤티 앨리어싱 사용 Makes certain plots look more "square waved". 특정 플롯들을 더욱"사각파형으로" 보이게 합니다. Square Wave Plots 구형파(사각파) 그림 Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Pixmap 캐싱은 그래픽 가속 기술입니다. 플랫폼의 그래프 표시 영역에서 글꼴 그리기에 문제가 발생할 수 있습니다. Use Pixmap Caching Pixmap 캐싱 사용 <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p> 이러한 기능은 최근에 제거되었습니다. 그들은 나중에 추가 될것입니다. </p></body></html> Animations && Fancy Stuff 애니메이션 && 장식 Whether to allow changing yAxis scales by double clicking on yAxis labels x축 레이블을 두번 클릭하여 y축 배율을 변경할 수 있는지 여부 Allow YAxis Scaling Y축 스케일링 허용 Whether to include machine serial number on machine settings changes report 기기 설정 변경 보고서에 컴퓨터 일련 번호를 포함할지 여부 Include Serial Number 일련 번호 포함 Graphics Engine (Requires Restart) 그래픽 엔진 (재시작 필요) l/min l/분 <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>누적 지수</p></body></html <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> 아래의 불포함</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Oximetry와 CPAP 데이터 동기화</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">SpO2Review(.spoR 파일에서) 또는 직렬 가져오기 방법에서 가져온 CMS50 데이터 </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> 동기화에 필요한 정확한 타임스탬프가 있습니다..</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">라이브 뷰 모드(직렬 케이블 사용)는 CMS50 oximeter에서 정확한 동기화를 수행할 수 있는 한 가지 방법이지만 CPAP 클럭 드리프트에는 대응되지 않습니다.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;"> Oximeters 기록 모드를 시작하면</span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">CPAP 시스템을 시작하는 동시에 동기화도 수행할 수 있습니다.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">직렬 가져오기 프로세스는 첫 번째 CPAP 세션의 마지막 밤부터 시작 시간이 걸립니다. (CPAP 데이터를 먼저 가져오십시오!))</span></p></body></html> Print reports in black and white, which can be more legible on non-color printers 컬러 프린터가 아닌 다른 프린터에서 더 쉽게 읽을 수 있는 흑백 보고서 인쇄 Print reports in black and white (monochrome) 흑백으로 보고서 인쇄(단색) Fonts (Application wide settings) 글꼴 (응용프로그램 전체 설정) Font 폰트 Size 크기 Bold 굵게 Italic 이탤릭체 Application 응용프로그램 Graph Text 그래프 텍스트 Graph Titles 그래프 제목 Big Text 큰 글자 Details 상세 &Cancel &취소 &Ok &확인 Name 이름 Color Colour Flag Type 플래그 타입 Label 라벨 CPAP Events CPAP 이벤트 Oximeter Events 산소측정기 이벤트 Positional Events 위치 이벤트 Sleep Stage Events 수면 단계 이벤트 Unknown Events 알수없는 이벤트 Double click to change the descriptive name this channel. 이 채널의 설명 명칭을 변경하려면 더블 클릭 하십시오. Double click to change the default color for this channel plot/flag/data. Double click to change the default colour for this channel plot/flag/data. Overview 개요 <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p> <b> 참고 사항 : </b> OSCAR의 고급 세션 분할 기능은 설정 및 요약 데이터가 저장되는 방식의 제한으로 인해 <b> ResMed </b> 기기에서 불가능하므로 ResMed의 상용 소프트웨어처럼 <b> 정오에 분할 </b>됩니다.</p> Double click to change the descriptive name the '%1' channel. 설명 명칭을 '%1'채널로 변경하려면 더블 클릭하십시오. Whether this flag has a dedicated overview chart. 이 플래그에 전용 개요 차트가 있는지 여부. Here you can change the type of flag shown for this event 여기서 이벤트에 대해 표시되는 플래그의 유형을 변경할 수 있습니다 This is the short-form label to indicate this channel on screen. 이것은 이 채널을 화면에 표시하는 짧은 형식의 라벨입니다. This is a description of what this channel does. 이것은 이 채널이 하는것에 대한 설명입니다. Lower 더 낮게 Upper 높은 CPAP Waveforms CPAP 파형 Oximeter Waveforms 산소측정기 파형 Positional Waveforms 위치 파형 Sleep Stage Waveforms 수면 단계 파형 Whether a breakdown of this waveform displays in overview. 이 파형의 분석이 개요로 표시 되는지 여부. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform 여기에서 %1 파형에 대한 특정 계산에 사용 된 <b> 낮은 </b> 임계 값을 설정할 수 있습니다 Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform %1 파형에서 특정 계산에 사용 된 <b> 상위 </b> 임계 값을 설정할 수 있습니다 Data Processing Required 필요한 데이터 처리 A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? 이러한 변경 사항을 적용하려면 데이터 재구성 / 압축 해제 절차가 필요합니다. 이 작업을 완료하는 데 몇 분이 걸릴 수 있습니다. 이러한 변경을 수행 하시겠습니까? Data Reindex Required 필요한 데이터 다시 색인 A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? 이러한 변경 사항을 적용하려면 데이터 다시 색인화 절차가 필요합니다. 이 작업을 완료하는 데 몇 분이 걸릴 수 있습니다. 이러한 변경을 수행 하시겠습니까? Restart Required 재시작 필요 One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? 변경 사항 중 하나 이상을 적용하면 이러한 변경 사항을 적용하기 위해이 응용 프로그램을 다시 시작해야합니다. 지금 해보시겠습니까? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. 데이터를 OSCAR 또는 ResScan에서 다시 가져올 필요가 있는 경우 데이터는 다시 표시되지 않습니다. If you need to conserve disk space, please remember to carry out manual backups. 디스크 공간을 절약해야하는 경우 수동 백업을 수행해야합니다. Are you sure you want to disable these backups? 백업을 비활성화 하시겠습니까? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. 백업을 끄는 것은 좋은 생각이 아닙니다, OSCAR에서 오류가 발견되면 데이터베이스를 다시 작성해야 하기 때문입니다. Are you really sure you want to do this? 이 작업을 정말로하고 싶으십니까? %1 %2 Flag 플래그 Minor Flag 마이너 플래그 Span 걸침 Always Minor 항상 사소한 No CPAP machines detected 감지 된 CPAP 기계 없음 Will you be using a ResMed brand machine? ResMed 브랜드 기계를 사용 하시겠습니까? Never 결코 This may not be a good idea 이것은 좋은 생각이 아닐 수도 있습니다 ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9 기기는 해상도에 따라 7 일 및 30 일 이전의 SD 카드에서 특정 데이터를 정기적으로 삭제합니다. ProfileSelector Filter: 필터: Reset filter to see all profiles 모든 프로필을 보려면 필터 재설정 Version 버전 &Open Profile &프로필 열기 &Edit Profile &프로필 수정 &New Profile &새 프로필 Profile: None 프로필: 없습니다 Please select or create a profile... 프로필을 선택하거나 생성 하십시오... Destroy Profile 프로필 삭제 Profile 프로필 Ventilator Brand 인공 호흡기 브랜드 Ventilator Model 인공 호흡기 모델 Other Data 기타 데이터 Last Imported 마지막으로 가져온 항목 Name 이름 You must create a profile 프로필을 생성해야 합니다 Enter Password for %1 %1 비번 입력 You entered an incorrect password 잘못된 암호를 입력 했습니다 Forgot your password? 비밀번호가 기억나지 않습니까? Ask on the forums how to reset it, it's actually pretty easy. 포럼에서 재설정하는 방법에 대해 물어 보면 실제로는 매우 쉽습니다. Select a profile first 먼저 프로필을 선택 하십시오 The selected profile does not appear to contain any data and cannot be removed by OSCAR 선택한 프로필에 데이터가 없는 것 같으며 OscAR로 제거할 수 없습니다 If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. 비밀번호를 기억못해서 삭제 하려는 경우 재설정 하거나 프로필 폴더를 수동으로 삭제해야 합니다. You are about to destroy profile '<b>%1</b>'. 프로필 '<b>%1 </b>을 삭제하려고 합니다. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. 이 경우 <b> 백업 데이터 </b>와 함께 <br/>%2에 저장된 모든 프로필을 지울 수 있으므로 신중하게 고려하십시오. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. 확인하려면 아래에 <b>DELETE </b>라는 단어를 입력하십시오(표시된 대로 정확하게). DELETE 삭제 Sorry 죄송합니다 You need to enter DELETE in capital letters. 대문자로 DELETE를 입력해야 합니다. There was an error deleting the profile directory, you need to manually remove it. 프로필 디렉토리를 삭제하는 중에 오류가 발생했습니다. 프로필 디렉토리를 수동으로 제거해야 합니다. Profile '%1' was succesfully deleted 프로필 '%1' 성공적으로 삭제되었습니다 Bytes Bytes(바이트) KB KB(킬러바이트) MB MB(메가바이트) GB GB(기가바이트) TB TB(테라바이트) PB PB(주호) Summaries: 요약 : Events: 이벤트: Backups: 백업: Hide disk usage information 디스크 사용 정보 숨기기 Show disk usage information 디스크 사용 정보 표시 Name: %1, %2 이름: %1, %2 Phone: %1 전화: %1 Email: <a href='mailto:%1'>%1</a> 이메일: <a href='mailto:%1'>%1</a> Address: 주소: No profile information given 주어진 프로필 정보가 없습니다 Profile: %1 프로필 %1 ProgressDialog Abort 중단 QObject No Data 데이터 없음 Events 이벤트 Duration 기간 (% %1 in events) (이벤트에서 % %1) Jan 1월 Feb 2월 Mar 3월 Apr 4월 May 5월 Jun 6월 Jul 7월 Aug 8월 Sep 9월 Oct 10월 Nov 11월 Dec 12월 ft lb oz Kg cmH2O cmH2O(압력) Med. 중간. Min: %1 최소: %1 Min: 최소: Max: 최대: Max: %1 최대: %1 %1 (%2 days): %1 (%2 일): %1 (%2 day): %1 (%2 일): % in %1 Hours 시간 Min %1 최소 %1 Hours: %1 시간: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 낮은사용, %2 사용없음, %3 일중 (%4% 순응) 길이 : %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 세션 : %1 / %2 / %3 길이 : %4 / %5 / %6 최장시간: : %7 / %8 / %9 %1 Length: %3 Start: %2 %1 길이: %3 시작: %2 Mask On 마스크 씀 Mask Off 마스크 벗음 %1 Length: %3 Start: %2 %1 길이: %3 시작: %2 TTIA: 무호흡총시간(TTIA): TTIA: %1 무호흡총시간(TTIA): %1 Minutes Seconds h m s ms 밀리세컨 Events/hr 이벤트/시간 Hz bpm Litres ml Breaths/min 호흡/분 ? Severity (0-1) 심각도(0-1) Degrees Error 에러 Warning 경고 Information 정보 Busy 바쁨 Please Note 참고 사항 Graphs Switched Off 그래프 전환 Sessions Switched Off 세션 전환 &Yes &예 &No &아니오 &Cancel &취소 &Destroy &파괴 &Save &저장 BMI BMI(체질량지수) Weight 무게 Zombie 무기력 Pulse Rate 맥박수 Plethy 쾌적한 Pressure 압력 Daily 일간 Profile 프로필 Overview 개요 Oximetry 산소계 Oximeter 산소측정기 Event Flags 이벤트 표시 Default 디볼트 CPAP CPAP(고정) BiPAP BiPAP이중형양압기(중추용) Bi-Level Bi-Level(이중형) EPAP 호기(날숨)압력 Min EPAP 최저 EPAP(날숨압력) Max EPAP 최대 EPAP)(날숨압력) IPAP IPAP(들숨압력) Min IPAP 최소 IPAP(들숨압력) Max IPAP 최대 IPAP(들숨압력) APAP APAP(자동양압기) ASV ASV(지능형 인공호흡기) AVAPS 평균양 보장 압력 지원 ST/ASV Humidifier 가습기 H H(저호흡) OA OA(폐쇄) A CA CA(열기) FL FL(흐제) SA SA(기상시압력감소) LE EP VS 코골이 VS2 코골이2 RERA 각성(RERA) PP PP(압력변화) P RE RE(각성) NR NRI O2 PC UF1 UF2 UF3 PS 압력 AHI AHI(무저호흡지수) RDI 호흡 방해 지수 AI HI UAI CAI FLI REI EPI PB PB(주호) IE Insp. Time 들숨. 시간 Exp. Time 날숨. 시간 Resp. Event Flow Limitation 흐름 제한(FL) Flow Limit 흐름 제한(FL) SensAwake Pat. Trig. Breath Tgt. Min. Vent Tgt. Min. 벤트 Target Vent. 대상 환기구. Minute Vent. 분당 공기변위량. Tidal Volume 호흡량 Resp. Rate 분당 호흡회수 Snore 코골이 Leak 누출 Leaks 누출들 Large Leak 대량 누출(LL) LL LL(대누) Total Leaks 총 누출 Unintentional Leaks 의도하지 않은 누출 MaskPressure 마스크압력 Flow Rate 유동률 Sleep Stage 수면 단계 Usage 사용 Sessions 세션 Pr. Relief No Data Available 자료 없습니다 App key: 앱 키: Operating system: 운영 체제 : Built with Qt %1 on %2 %2에 Qt %1로 빌드 Graphics Engine: 그래픽 엔진 : Graphics Engine type: 그래픽 엔진 유형 : Software Engine 소프트웨어 엔진 ANGLE / OpenGLES Desktop OpenGL 데스크톱 OpenGL m cm in Only Settings and Compliance Data Available 설정 및 규정 준수 데이터만 사용 가능 Summary Data Only 요약 데이터만 Bookmarks 북마크 Mode 모드 Model 모델 Brand 브랜드 Serial 시리얼 Series 시리즈 Machine 기기 Channel 채널 Settings 세팅 Inclination 기울기 Orientation 방향 Motion 동작 Name 이름 DOB 생년월일 Phone 전화 Address 주소 Email 이메일 Patient ID 환자 ID Date Bedtime 취침시간 Wake-up 기상 Mask Time 마스크 시간 Unknown 알수 없는 None 없습니다 Ready 준비된 First 처음 Last 마지막 Start 시작 End 종료 On Off Yes No 아니요 Min 최소 Max 최대 Med 중간 Average 평균 Median 중간 Avg 평균 W-Avg Your %1 %2 (%3) generated data that OSCAR has never seen before. 귀하의 %1 %2(%3)이(가) 오스카에서 이전에 보지 못한 데이터를 생성했습니다. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. 가져온 데이터가 완전히 정확하지는 않을 수 있으므로 개발자는 OSCAR이 데이터를 올바르게 처리하는지 확인하기 위해 이 기계의 SD 카드의 .zip 복사본과 일치하는 임상의 .pdf 보고서를 원합니다. Non Data Capable Machine 비 데이터 가능 기기 Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. 귀하의 %1 CPAP 기계(%2 모델)는 유감스럽게도 데이터 지원 모델이 아닙니다. Your %1 CPAP machine (Model %2) has not been tested yet. 귀하의 %1 CPAP 시스템(%2 모델)이 아직 테스트되지 않았습니다. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. 동작할 수 있을 정도로 다른 컴퓨터와 비슷해 보이지만 개발자들은 OSCAR에서 동작할 수 있도록 이 기계의 SD 카드의 .zip 복사본과 일치하는 임상의 .pdf 보고서를 원합니다. Sorry, your %1 CPAP machine (%2) is not supported yet. 죄송합니다. %1 CPAP 시스템(%2)이(가) 아직 지원되지 않습니다. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. 개발자들은 이 기계의 SD 카드의 .zip 복사본과 일치하는 임상의사 .pdf 보고서가 있어야 OSCAR에서 동작할 수 있다. Getting Ready... 준비 중 ... Machine Unsupported 지원되지 않는 기계 I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. OSCAR가이 컴퓨터의 사용 시간과 매우 기본적인 설정 만 추적 할 수 있다는 사실을 알려 드려 죄송합니다. Scanning Files... 파일 스캔 중 ... Importing Sessions... 세션 가져 오는 중 ... Finishing up... 끝내는 중 ... Machine Untested 테스트되지 않은 기계 Flex Lock Flex 잠금 Whether Flex settings are available to you. Flex 설정을 사용할 수 있는지 여부. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition EPAP(날숨)에서 IPAP(들숨)로 전환하는 데 걸리는 시간이 높을수록 전환 속도가 느려집니다 Rise Time Lock 상승 시간 잠금 Whether Rise Time settings are available to you. 상승 시간 설정을 사용할 수 있는지 여부. Rise Lock 상승 잠금 Mask Resistance Setting 마스크 저항 설정 Mask Resist. 마스크 저항. Hose Diam. 호스 직경. 15mm 22mm Backing Up Files... 파일 백업 ... Untested Data 테스트되지 않은 데이터 model %1 모델 %1 DreamStation 2 unknown model 알 수 없는 모델 CPAP-Check CPAP- 체크 AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS Flex Mode Flex(압력완화) 모드 PRS1 pressure relief mode. PRS1 압력 완화 모드. C-Flex C-Flex+ A-Flex P-Flex Rise Time 상승 시간 Bi-Flex Flex Flex Level Flex(압력완화) 레벨 PRS1 pressure relief setting. PRS1 압력 완화 설정. Passover Target Time PRS1 Humidifier Target Time PRS1 가습기 목표시간 Hum. Tgt Time Tubing Type Lock 튜브 유형 잠금 Whether tubing type settings are available to you. 튜브 유형 설정을 사용할 수 있는지 여부. Tube Lock 튜브 잠금 Mask Resistance Lock 마스크 저항 잠금 Whether mask resistance settings are available to you. 마스크 저항 설정을 사용할 수 있는지 여부. Mask Res. Lock 마스크 저항. 잠금 Whether or not machine shows AHI via built-in display. 기기가 내장 디스플레이를 통해 AHI를 표시하는지 여부. Ramp Type Ramp(압력상승) 유형 Type of ramp curve to use. 사용할 ramp(압력상승) 곡선 유형입니다. Linear 선형 SmartRamp SmartRamp(스마트압력상승) Ramp+ Backup Breath Mode 백업 호흡 모드 The kind of backup breath rate in use: none (off), automatic, or fixed 사용중인 백업 호흡 속도의 종류 : 없음 (꺼짐), 자동 또는 고정 Breath Rate 호흡 수 Fixed 고정됨 Fixed Backup Breath BPM 고정 백업 호흡 BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated 시간 제한 호흡이 시작되는 분당 최소 호흡 (BPM) Breath BPM 호흡 BPM Timed Inspiration 초과 시간 The time that a timed breath will provide IPAP before transitioning to EPAP 시간 제한 호흡이 EPAP(날숨)로 전환하기 전에 IPAP(들숨)를 제공하는 시간 Timed Insp. Auto-Trial Duration 자동 평가 기간 The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-CPAP 평가판 기간의 일수입니다. 이후 컴퓨터는 CPAP로 되돌아갑니다 Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled EZ-Start 활성화 여부 Variable Breathing 가변 호흡 UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend 확인되지 않음 : 최대 흡기 흐름 추세에서 크게 벗어난 기간 인 가변 호흡 가능성 Peak Flow 최고 유량 Peak flow during a 2-minute interval 2 분 간격의 최대 유량 Humidifier Status 가습기 상태 PRS1 humidifier connected? PRS1 가습기를 연결했습니까? Disconnected 연결 끊김 Connected 연결됨 Humidification Mode 가습기 모드 PRS1 Humidification Mode PRS1 가습 모드 Humid. Mode 가습.모드 Fixed (Classic) 고정 (구형) Adaptive (System One) 자동 (System One) Heated Tube 열선 튜브 Tube Temperature 튜브 온도 PRS1 Heated Tube Temperature PRS1 가열 튜브 온도 Tube Temp. 튜브 온도. PRS1 Humidifier Setting PRS1 가습기 설정 Hose Diameter 호스 직경 Diameter of primary CPAP hose 주 CPAP 호스의 직경 12mm Auto On 자동 켜기 A few breaths automatically starts machine 몇 번의 호흡이 자동으로 기기를 시작합니다 Auto Off 자동 끔 Machine automatically switches off 기기 자동 끄기 Mask Alert 마스크 경고 Whether or not machine allows Mask checking. 기기가 마스크 검사를 허용하는지 여부. Show AHI AHI 보기 Breathing Not Detected 호흡 무감지 A period during a session where the machine could not detect flow. 시스템이 흐흠을 감지 할수 없는 세션 동안의 기간. BND BND(호흡무) Timed Breath 시측된 호흡 Machine Initiated Breath 기기 개시 호흡 TB TB(테라바이트) Windows User Windows 사용자 Using 사용 , found SleepyHead - , SleepyHead 발견 - You must run the OSCAR Migration Tool OSCAR 마이그레이션 도구를 실행해야합니다 <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>귀하의 이전 데이터를 가져오는 동안 백업 기능을 사용하지 않도록 설정하지 않은 경우 이전 시스템 데이터를 재생성 해야 함.</i> Launching Windows Explorer failed Windows 탐색기 시작 실패 Could not find explorer.exe in path to launch Windows Explorer. Windows 탐색기를 실행하기 위해 경로에서 explorer.exe를 찾을 수 없습니다. OSCAR %1 needs to upgrade its database for %2 %3 %4 SCAR %1은 (는) %2 %3 %4의 데이터베이스를 업그레이드해야합니다 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR은이 용도로 사용하는 기기 데이터 카드의 백업을 유지 관리합니다.</b> OSCAR does not yet have any automatic card backups stored for this device. OSCAR에는 아직이 장치 용으로 저장된 자동 카드 백업이 없습니다. This means you will need to import this machine data again afterwards from your own backups or data card. 나자신의 백업 또는 데이터 카드에서이 머신 데이터를 다시 가져와야합니다. Important: 중요: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. 문제가 있다면 OSCAR을 다시 시작하기 전에 아니요를 클릭하여 종료하고 프로필을 수동으로 백업하십시오. Are you ready to upgrade, so you can run the new version of OSCAR? 업그레이드 준비가되었으므로 OSCAR의 새 버전을 실행할 수 있습니까? Sorry, the purge operation failed, which means this version of OSCAR can't start. 죄송합니다. 제거 작업이 실패했습니다. 즉,이 OSCAR 버전을 시작할 수 없음을 의미합니다. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? 자동 백업을 사용 하시겠습니까? 다음 번에 OSCAR의 새로운 버전이 그렇게해야한다면, 이것들을 다시 만들 수 있습니까? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR이 이제 %1 데이터를 다시 설치할 수 있도록 가져 오기 마법사를 시작합니다. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: 이제 OSCAR이 종료되어 컴퓨터 파일 관리자를 실행 (시도)하여 수동으로 프로필을 백업 할 수 있습니다: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. 파일 관리자를 사용하여 프로필 디렉토리의 복사본을 만든 다음 나중에 OSCAR를 다시 시작하고 업그레이드 프로세스를 완료하십시오. Machine Database Changes 기기 데이터베이스 변경 Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 업그레이드하면 더 이상 이전 버전에서이 프로필을 사용할 수 <font size = + 1> 할 수 없습니다 </font>. The machine data folder needs to be removed manually. 머신 데이터 폴더는 수동으로 제거해야합니다. This folder currently resides at the following location: 이 폴더는 현재 다음 위치에 있습니다: Rebuilding from %1 Backup %1 백업에서 재 구축 Therapy Pressure 치료 압력 Inspiratory Pressure 흡기 압력 Lower Inspiratory Pressure 낮은 흡기 압력 Higher Inspiratory Pressure 더 높은 흡기 압력 Expiratory Pressure 호기 압력 Lower Expiratory Pressure 낮은 호기 압력 Higher Expiratory Pressure 높은 호기 압력 Pressure Support 보조 압력 PS Min 최저 압력 Pressure Support Minimum 최저 보조 압력 PS Max 최고 압력 Pressure Support Maximum 최대 보조 압력 Min Pressure 최소 압력 Minimum Therapy Pressure 최소 치료 압력 Max Pressure 최대 압력 Maximum Therapy Pressure 최대 치료 압력 Ramp Time Ramp(압력상승) 시간 Ramp Delay Period Ramp(압력상승) 지연 기간 Ramp Pressure Ramp(상승) 압력 Starting Ramp Pressure Ramp(압력상승) 시작 Ramp Event Ramp(압력상승) 이벤트 Ramp Ramp(압력상승) An abnormal period of Cheyne Stokes Respiration 교차성 호흡의 이상한시기 An apnea that couldn't be determined as Central or Obstructive. 중추 또는 폐쇠로 판단할 수 없는 무호흡증. A restriction in breathing from normal, causing a flattening of the flow waveform. 정상에서 호흡을 제한하여 흐름 파형을 평평하게 만듭니다. Vibratory Snore (VS2) 코골이 (VS2) A ResMed data item: Trigger Cycle Event ResMed 데이터 항목 : 트리거주기 이벤트 Mask On Time 마스크 켜기 시간 Time started according to str.edf str.edf에 따라 시간이 시작되었습니다 Summary Only 요약만 An apnea where the airway is open 기도가 열려 있는 무호흡(중추성 무호흡 증상과 비슷한 무호흡->단정할순 없습니다) An apnea caused by airway obstruction 기도 폐쇄로 인한 무호흡 Hypopnea 저호흡 A partially obstructed airway 부분적으로 폐쇄된 기도 Unclassified Apnea 분류되지 않은 무호흡 UA Vibratory Snore 코골이 A vibratory snore 코골이 A vibratory snore as detcted by a System One machine 시스템 원 기기가 감지 한 진동 코콜이 Pressure Pulse 압력변화(PP) A pulse of pressure 'pinged' to detect a closed airway. 폐쇄된 기도를 감지하기 위해 '반복된 '압력 변화. A large mask leak affecting machine performance. 기기 성능에 영향을 주는 대량 마스크 누출. Non Responding Event 응답 없는 이벤트 A type of respiratory event that won't respond to a pressure increase. 압력 증가에 반응하지 않는 호흡 이벤트 유형. Expiratory Puff 호기 펌프 Intellipap event where you breathe out your mouth. 입김을 내뿜는 Intellipap 이벤트. SensAwake feature will reduce pressure when waking is detected. SensAwake 기능은 잠에서 깨어 났을때 압력을 감소시킵니다. User Flag #1 사용자 표시 #1 User Flag #2 사용자 플래그 #2 User Flag #3 사용자 플래그 #3 Heart rate in beats per minute 분당 비트 수의 심박수 Blood-oxygen saturation percentage 혈액-산소 포화율 Plethysomogram 혈구 혈압 An optical Photo-plethysomogram showing heart rhythm 박동을 보여주는 광학적 사진-생리학 Pulse Change 펄스 변경 A sudden (user definable) change in heart rate 갑작스런 (사용자가 정의할수 있는) 심박수 변화 SpO2 Drop 혈중산소포화도 떨어짐 A sudden (user definable) drop in blood oxygen saturation 갑작스런 (사용자가 정의할수 있는) 혈중 산소 포화도 감소 SD Breathing flow rate waveform 호흡 유량 파형 L/min Mask Pressure 마스크 압력 Amount of air displaced per breath 호흡 당 이동 된 공기의 양 Graph displaying snore volume 코골이 볼륨을 나타내는 그래프 Minute Ventilation 분당 환기 Amount of air displaced per minute 1분간 폐에서 배출되는 공기량 Respiratory Rate 호흡 속도 Rate of breaths per minute 분당 호흡 수 Patient Triggered Breaths 환자 작동 호흡 Percentage of breaths triggered by patient 환자에 의해 유발된 호흡 비율 Pat. Trig. Breaths 패치. 트리거. 숨 Leak Rate 누출율 Rate of detected mask leakage 감지된 마스크 누출 비율 I:E Ratio I:E 비율 Ratio between Inspiratory and Expiratory time 흡기 시간과 호기 시간 간 비율 ratio 비율 Pressure Min 압력 최소 Pressure Max 압력 최대값 Pressure Set 압력 설정 Pressure Setting 압력 설정 IPAP Set IPAP(들숨) Set IPAP Setting IPAP(들숨) 설정 EPAP Set EPAP Setting EPAP 설정 Cheyne Stokes Respiration 교차성 호흡 CSR Periodic Breathing 주기적 호흡 An abnormal period of Periodic Breathing 주기적 호흡의 이상기 - 무호흡과 저호흡이 주기적(3회이상)으로 나타남 Clear Airway 열린기도 무호흡(CA) Obstructive 폐쇄성 Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. 호흡 노력 관련 각성 : 각성 또는 수면 장애를 유발하는 호흡 제한. Leak Flag 누출 표시 LF A user definable event detected by OSCAR's flow waveform processor. OSCAR의 흐름 파형 프로세서에 의해 검출 된 사용자 정의 가능한 이벤트. Perfusion Index 관류 지수 A relative assessment of the pulse strength at the monitoring site 모니터링 사이트에서의 맥박 강도의 상대적 평가 Perf. Index % 성능. 지수 % Mask Pressure (High frequency) 마스크 압력(고주파) Expiratory Time 만기 시간 Time taken to breathe out 날숨 걸린 시간 Inspiratory Time 흡기 시간 Time taken to breathe in 들숨 걸린 시간 Respiratory Event 호흡기 이벤트 Graph showing severity of flow limitations 흐름 제한의 심각도를 나타내는 그래프 Flow Limit. 흐름 제한. Target Minute Ventilation 목표 분간 환기 Maximum Leak 최대 누출 The maximum rate of mask leakage 최대 마스크 누출률 Max Leaks 최대 누출 Apnea Hypopnea Index 무호흡 저호흡 지수 Graph showing running AHI for the past hour 지난 1시간 동안 AHI(수면무호흡) 진행을 보여주는 그래프 Total Leak Rate 총 누출률 Detected mask leakage including natural Mask leakages 자연스런 공기 누출을 포함한 마스크 누출 감지 Median Leak Rate 중간 누출률 Median rate of detected mask leakage 검출 된 마스크 누출의 중간값 Median Leaks 중간 누출 Respiratory Disturbance Index 호흡 장애 지수 Graph showing running RDI for the past hour 지난 1 시간 동안 RDI 실행을 보여주는 그래프 Sleep position in degrees 잠자리 위치 (도) Upright angle in degrees 직각도 Movement 움직임 Movement detector 움직임 감지기 CPAP Session contains summary data only CPAP 세션에는 요약 데이터만 포함됩니다 PAP Mode Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Channels.xml을 구문 분석할 수 없습니다. OSCAR을 계속할 수 없으며 종료 중입니다. Apnea 무호흡 An apnea reportred by your CPAP machine. CPAP 기계에 의해 무호흡이 보고되었습니다. PAP Device Mode PAP 장치 모드 APAP (Variable) APAP(자동양압기) (가변) ASV (Fixed EPAP) ASV(지능형 인공호흡기) (날숨 고정) ASV (Variable EPAP) >ASV(지능형 인공호흡기) (날숨 가변) Height 신장 Physical Height 물리적 높이 Notes 메모 Bookmark Notes 북마크 메모 Body Mass Index 체질량 지수 How you feel (0 = like crap, 10 = unstoppable) 귀하의 느낌 (0 = like crap, 10 = unstoppable) Bookmark Start 북마크 시작 Bookmark End 북마크 종료 Last Updated 마지막 업데이트됨 Journal Notes 일지 메모 Journal 일지 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=깸 2=REM 3=얇은 잠 4=깊은 잠 Brain Wave 뇌파 BrainWave 뇌파 Awakenings 각성 Number of Awakenings 각성 횟수 Morning Feel 아침기분 How you felt in the morning 아침에 어떻게 느꼈나요 Time Awake 깬 시간 Time spent awake 깨어있는 시간 Time In REM Sleep REM 수면 시간 Time spent in REM Sleep REM 수면에 소비 된 시간 Time in REM Sleep REM 수면 시간 Time In Light Sleep 가벼운 수면 시간 Time spent in light sleep 가벼운 수면 시간 Time in Light Sleep 가벼운 수면 시간 Time In Deep Sleep 깊은 수면 시간 Time spent in deep sleep 깊은 수면 시간 Time in Deep Sleep 깊은 수면 시간 Time to Sleep 수면 시간 Time taken to get to sleep 잠이든 시간 Zeo ZQ Zeo sleep quality measurement Zeo 수면 품질 측정 ZEO ZQ Debugging channel #1 디버깅 채널 #1 Test #1 테스트 #1 For internal use only 내부 전용 Debugging channel #2 디버깅 채널 #2 Test #2 테스트 #2 Zero 제로 Upper Threshold 상한 임계 값 Lower Threshold 하한선 As you did not select a data folder, OSCAR will exit. 데이터 폴더를 선택하지 않으면 OSCAR이 종료됩니다. or CANCEL to skip migration. 또는 마이그레이션을 건너 뛰려면 CANCEL을 누르십시오. Choose the SleepyHead or OSCAR data folder to migrate 마이그레이션할 슬리피헤드 또는 오스카 데이터 폴더 선택 The folder you chose does not contain valid SleepyHead or OSCAR data. You cannot use this folder: 이 폴더는 사용할 수 없습니다: Migrating 마이그레이션 files 파일들 from to OSCAR crashed due to an incompatibility with your graphics hardware. 그래픽 하드웨어와의 비 호환성으로 인해 OSCAR가 충돌했습니다. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. 이 문제를 해결하기 위해 OSCAR는 느리지 만 호환성이 더 좋은 그리기 방법으로 되돌 렸습니다. OSCAR will set up a folder for your data. OSCAR는 데이터를 저장할 폴더를 설정합니다. If you have been using SleepyHead or an older version of OSCAR, 만약 당신이 SleepHead나 이전 버전의 Oscar를 사용해왔다면, OSCAR can copy your old data to this folder later. 오스카는 나중에 이전 데이터를 이 폴더에 복사할 수 있습니다. Migrate SleepyHead or OSCAR Data? 슬리피헤드 또는 오스카 데이터를 마이그레이션하시겠습니까? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data 다음 화면에서 Oscar는 SleepHead 또는 Oscar 데이터가 있는 폴더를 선택하라는 메시지를 표시합니다 Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. 다음 화면으로 이동하려면 [OK]를 클릭하고 SleepHead 또는 Oscar 데이터를 사용하지 않으려면 [No]를 클릭합니다. Unable to create the OSCAR data folder at 다음 위치에서 오스카 데이터 폴더를 만들 수 없음 Unable to write to OSCAR data directory 오스카 데이터 디렉토리에 쓸 수 없음 Error code 에러코드 OSCAR cannot continue and is exiting. OSCAR을 계속할 수 없으며 종료하고 있습니다. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. 디버그 로그에 쓸 수 없습니다. 디버그 창(도움말/문제 해결/디버그 창 표시)을 계속 사용할 수 있지만 디버그 로그는 디스크에 기록되지 않습니다. We suggest you use this folder: 이 폴더를 사용하는 것이 좋습니다: Click Ok to accept this, or No if you want to use a different folder. 다른 폴더를 사용하려면 확인을 클릭하고 그렇지 않으면 아니오를 클릭하십시오. Choose or create a new folder for OSCAR data OscAR 데이터에 대한 새 폴더 선택 또는 만들기 Next time you run OSCAR, you will be asked again. 다음에 OSCAR을 실행하면 다시 묻습니다. The folder you chose is not empty, nor does it already contain valid OSCAR data. 선택한 폴더가 비어 있지 않으며 유효한 OSCAR 데이터가 이미 포함되어 있지 않습니다. Data directory: 데이터 디렉토리 : Version "%1" is invalid, cannot continue! "%1" 버전이 잘못되었습니다. 계속할 수 없습니다! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). 실행중인 OSCAR 버전 (%1)이이 데이터를 만드는 데 사용 된 버전 (%2)보다 오래되었습니다. It is likely that doing this will cause data corruption, are you sure you want to do this? 이렇게하면 데이터가 손상 될 수 있습니다.이 작업을 수행 하시겠습니까? Question 질문 Exiting 종료중 Are you sure you want to use this folder? 이 폴더를 사용 하시겠습니까? Don't forget to place your datacard back in your CPAP machine CPAP 시스템에 데이터 카드를 다시 놓지 마세요 OSCAR Reminder OSCAR 알림 You can only work with one instance of an individual OSCAR profile at a time. 한 번에 하나의 개별 OSCAR 프로파일 인스턴스로만 작업 할 수 있습니다. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. 클라우드 저장소를 사용하는 경우 진행하기 전에 OSCAR이 닫혀 있고 동기화가 다른 컴퓨터에서 먼저 완료되었는지 확인하십시오. Loading profile "%1"... 프로필 읽는중 "%1"... Chromebook file system detected, but no removable device found 크롬북 파일 시스템이 감지되었지만 이동식 장치를 찾을 수 없음 You must share your SD card with Linux using the ChromeOS Files program ChromeOS Files 프로그램을 사용하여 Linux와 SD 카드를 공유해야 합니다 Recompressing Session Files 세션 파일 재 압축 Please select a location for your zip other than the data card itself! 데이터 카드 이외의 다른 zip 위치를 선택하십시오! Unable to create zip! zip을 만들 수 없습니다! Are you sure you want to reset all your channel colors and settings to defaults? 모든 채널 색상 및 설정을 기본값으로 재설정 하시겠습니까? Are you sure you want to reset all your waveform channel colors and settings to defaults? 모든 파형 채널 색상 및 설정을 기본값으로 재설정 하시겠습니까? There are no graphs visible to print 인쇄 할 그래프가 없습니다 Would you like to show bookmarked areas in this report? 이 보고서에 북마크 된 영역을 표시 하시겠습니까? Printing %1 Report 보고서 %1 출력중 %1 Report %1 보고서 : %1 hours, %2 minutes, %3 seconds : %1 시, %2 분, %3 초 RDI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI(무)=%1 HI(저)=%2 CAI(패쇄)=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI(각성)=%1 VSI(코골이)=%2 FLI(호흡제한)=%3 PB(주기적무호흡)/CSR(교대성무호흡)=%4%% UAI=%1 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 %1 에서 %2 보고서 출력중 Entire Day's Flow Waveform 하루 전체 흐름 파형 Current Selection 현재 선택 Entire Day 하루종일 Page %1 of %2 %2 중 %1 페이지 Days: %1 일: %1 Low Usage Days: %1 낮은 사용 일: %1 (%1% compliant, defined as > %2 hours) (%1% 순응, > %2 시간으로 정의) (Sess: %1) Bedtime: %1 취침시간: %1 Waketime: %1 기상시간: %1 (Summary Only) (요약만) There is a lockfile already present for this profile '%1', claimed on '%2'. '%2'에 대해 주장 된이 프로파일 '%1'에 대해 이미 존재하는 잠금 파일이 있습니다. Fixed Bi-Level 고정 Bi-Leve(이중형) Auto Bi-Level (Fixed PS) 자동 Bi-Level(이중형) (고정 압력) Auto Bi-Level (Variable PS) 자동 Bi-Level(이중형) (가변 압력) 99.5% 99.5% varies n/a Fixed %1 (%2) 고정 %1 (%2) Min %1 Max %2 (%3) 최소 %1 최대 %2 (%3) EPAP %1 IPAP %2 (%3) EPAP(날숨) %1 IPAP(들숨) %2 (%3) PS %1 over %2-%3 (%4) 압력 %1 초과 %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) 최소 EPAP(날숨) %1 최대 IPAP(들숨) %2 압력 %3-%4 (%5) EPAP %1 PS %2-%3 (%4) 날숨 %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP(날숨) %1 IPAP(들숨) %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP(날숨) %1-%2 IPAP(들숨) %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> 가장 최근의 산소측정 데이터: <a onclick='alert("daily=%2");'>%1</a> (last night) (지난 밤) (1 day ago) (하루 전) (%2 days ago) (%2 일전) No oximetry data has been imported yet. 산소 측정 데이터가 아직 가져 오지 않았습니다. Contec CMS50 Fisher & Paykel ICON DeVilbiss Intellipap SmartFlex Settings ChoiceMMed MD300 Respironics M-Series Philips Respironics System One ResMed S9 EPR: Somnopose Somnopose Software Zeo Personal Sleep Coach 개인 수면 코치 Database Outdated Please Rebuild CPAP Data 오래된 데이터베이스 CPAP 데이터를 다시 작성하십시오 (%2 min, %3 sec) (%2 분, %3 초) (%3 sec) (%3 초) Pop out Graph 그래프 출력 The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. 팝업 창이 가득 찼습니다. 기존 항목을 캡처해야 합니다. 팝업 창을 삭제한 다음 이 그래프를 다시 팝업합니다. Your machine doesn't record data to graph in Daily View 시스템에서 일별 보기에 그래프로 표시할 데이터를 기록하지 않습니다 There is no data to graph 그래프로 표시 할 데이터가 없습니다 d MMM yyyy [ %1 - %2 ] yyyy MMM d [ %1 - %2 ] Hide All Events 모든 이벤트 숨김 Show All Events 모든 이벤트 표시 Unpin %1 Graph %1 그래프 고정 해제 Popout %1 Graph 팝업 %1 그래프 Pin %1 Graph %1 그래프 고정 Plots Disabled 플롯 비활성 Duration %1:%2:%3 지속시간 %1:%2:%3 AHI %1 AHI(무저호흡지수) %1 Relief: %1 완화: %1 Hours: %1h, %2m, %3s 시간: %1h, %2m, %3 Machine Information 기기 정보 Journal Data 일지 데이터 OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR은 이전 저널 폴더를 찾았지만 이름이 변경된 것처럼 보입니다: OSCAR will not touch this folder, and will create a new one instead. OSCAR는이 폴더를 건드리지 않고 대신 새로운 폴더를 만듭니다. Please be careful when playing in OSCAR's profile folders :-P OSCAR의 프로필 폴더에서 재생할 때 주의하십시오 :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. 어떤 이유로 OSCAR에서는 프로필에 일지 개체 레코드를 찾을 수 없지만 여러 일지 데이터 폴더를 찾았습니다. OSCAR picked only the first one of these, and will use it in future: 오스카 (OSCAR)는 이들 중 첫 번째 것만을 선택했으며 앞으로 사용할 예정입니다: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. 이전 데이터가 누락 된 경우 다른 모든 Journal_XXXXXXX 폴더의 내용을 수동으로 여기에 복사하십시오. CMS50F3.7 CMS50F Backing up files... 파일 백업 중... Reading data files... 데이터 파일을 읽는 중... SmartFlex Mode SmartFlex(스마트압력완화) 모드 Intellipap pressure relief mode. Intellipap 압력 완화 모드. Ramp Only Ramp(압력상승)만 Full Time 전체시간 SmartFlex Level SmartFlex 레벨 Intellipap pressure relief level. Intellipap 압력 완화 수준. Snoring event. 코골기 이벤트. SN Locating STR.edf File(s)... STR.edf 파일을 찾는 중... Cataloguing EDF Files... EDF 파일 카탈로깅 ... Queueing Import Tasks... 대기열 작업 가져 오기 ... Finishing Up... 마무리 중... CPAP Mode CPAP(고정) 모드 VPAPauto ASVAuto iVAPS PAC Auto for Her Auto for Her(여성용 자동) EPR 호흡압력완화(EPR) ResMed Exhale Pressure Relief ResMed Exhale 압력 완화 Patient??? 환자??? EPR Level 호흡압력완화(EPR) 레벨 Exhale Pressure Relief Level 날숨 압력 완화 수준 Response 반응 Patient View 환자 보기 SmartStart 스마트 스타트 Machine auto starts by breathing 호흡으로 기기 자동 시동 Smart Start 스마트 스타트 Humid. Status 가습. 상태 Humidifier Enabled Status 가습기 사용 가능 상태 Humid. Level 가습 레벨 Humidity Level 습도 Temperature 온도 ClimateLine Temperature 열선 온도 Temp. Enable 온도. 활성 ClimateLine Temperature Enable ClimateLine 온도 활성화 Temperature Enable 온도 활성화 AB Filter AB Filter(향균 필터) Antibacterial Filter 항균 필터 Pt. Access Pt. 엑세스 Essentials 에센셜 Plus Climate Control 기후 제어 Manual 수동 Soft 소프트 Standard 표준 BiPAP-T BiPAP-S BiPAP-S/T SmartStop 스마트 스톱 Machine auto stops by breathing 호흡으로 기계 자동 정지 Smart Stop 스마트 스톱 Simple 간단 Advanced 고급 Your ResMed CPAP machine (Model %1) has not been tested yet. ResMed CPAP 시스템(%1 모델)이 아직 테스트되지 않았습니다. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. 다른 기계들과 충분히 비슷해 보이지만, 개발자들은 이 기계가 OSCAR와 함께 작동하는 것을 확실히 하기 위해 이 기계의 SD 카드의 .zip 복사본을 원할 것이다. Parsing STR.edf records... STR.edf 레코드를 구문 분석하는 중... Auto 자동 Mask 마스크 ResMed Mask Setting ResMed 마스크 설정 Pillows 필로우(코구멍형) Full Face 풀페이스(안면형) Nasal 나잘(코형) Ramp Enable Ramp(압력상승) 활성 Weinmann SOMNOsoft2 Snapshot %1 CMS50D+ CMS50E/F Loading %1 data for %2... %2에 대해 %1 데이터를 로드하는 중... Scanning Files 파일 검사 Migrating Summary File Location 요약 파일 위치 마이그레이션 Loading Summaries.xml.gz Summaries.xml.gz 로드 중 Loading Summary Data 요약 데이터로드 중 Please Wait... 잠시만 기다려 주세요... Updating Statistics cache 통계 캐시 업데이트 Usage Statistics 사용 통계 %1 Charts %1 차트 %1 of %2 Charts %2 차트의 %1 Loading summaries 요약 로드 중 Dreem 드림 Your Viatom device generated data that OSCAR has never seen before. Viatom 장치는 OSCAR가 이전에 본 적이없는 데이터를 생성했습니다. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. 가져온 데이터가 완전히 정확하지 않을 수 있으므로 개발자는 OSCAR가 데이터를 올바르게 처리하는지 확인하기 위해 Viatom 파일의 사본을 원합니다. Viatom Viatom Software Viatom 소프트웨어 New versions file improperly formed 새 버전 파일이 잘못 형성됨 A more recent version of OSCAR is available 오스카의 최신 버전을 사용할 수 있습니다 release 릴리즈 test version 테스트 버전 You are running the latest %1 of OSCAR 오스카의 최신 %1을 실행하고 있습니다 You are running OSCAR %1 OscAR %1을 실행하고 있습니다 OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 이용할수 있다 <a href='%2'>여기</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> 최신 테스트 버전 %1에 대한 정보는 <a href='%2'>%2</a>에서 확인할 수 있다 Check for OSCAR Updates 오스카 업데이트 확인 Unable to check for updates. Please try again later. 업데이트를 확인할 수 없습니다. 나중에 다시 시도하십시오. SensAwake level SensAwake 수준 Expiratory Relief 호기구제 Expiratory Relief Level 호기 완화 수준 Humidity 습도 SleepStyle 수면스타일 This page in other languages: 다른 언어로 된 이 페이지: %1 Graphs %1 그래프 %1 of %2 Graphs %2 그래프 중 %1개 %1 Event Types %1 이벤트 유형 %1 of %2 Event Types %2개의 이벤트 유형 중 %1개 Report about:blank about:blank SessionBar %1h %2m %1시 %2분 No Sessions Present 현재 세션이 없습니다 SleepStyleLoader Import Error 불러오기 에러 This Machine Record cannot be imported in this profile. 프로필에서 기기 기록을 가져올 수 없습니다. The Day records overlap with already existing content. 일별 기록이 이미 존재하는 내용과 겹칩니다. Statistics CPAP Statistics CPAP 통계 CPAP Usage CPAP 사용율 Average Hours per Night 평균 수면 시간 Therapy Efficacy 치료 효과 Leak Statistics 누출 통계 Pressure Statistics 압력 통계 Oximeter Statistics 산소측정기 통계 Blood Oxygen Saturation 혈중 산소 포화도 Pulse Rate 맥박수 %1 Median %1 중위수 Average %1 평균 %1 Min %1 최소 %1 Max %1 최대 %1 %1 Index %1 지수 % of time in %1 %1의 시간 % % of time above %1 threshold %1 임계 값 초과 시간 % % of time below %1 threshold %1 임계 값 미만의 시간 % Name: %1, %2 이름: %1, %2 DOB: %1 생년월일: %1 Phone: %1 전화: %1 Email: %1 이메일: %1 Address: 주소: This report was prepared on %1 by OSCAR %2 이 보고서는 Oscar %2에 의해 %1에 작성되었습니다 Days Used: %1 사용일 : %1 Low Use Days: %1 낮은 사용일: %1 Compliance: %1% 순응도: %1% Days AHI of 5 or greater: %1 AHI 5이상 일: %1 Best AHI 최상 AHI Date: %1 AHI: %2 일: %1 AHI: %2 Worst AHI 최악 AHI Best Flow Limitation 최상의 흐름 제한 Date: %1 FL: %2 일: %1 FL: %2 Worst Flow Limtation 최악의 흐름 제한 No Flow Limitation on record 기록에 유량 제한 없음 Worst Large Leaks 최악의 대형 누출 Date: %1 Leak: %2% 일: %1 Leak: %2% No Large Leaks on record 기록에 큰 누출 없음 Worst CSR 최악의 CSR Date: %1 CSR: %2% 일: %1 CSR: %2% No CSR on record 기록에 CSR 없음 Worst PB 최악의 PB Date: %1 PB: %2% 일: %1 PB: %2% No PB on record 기록에 PB 없음 Want more information? 더 많은 정보를 원하십니까? OSCAR needs all summary data loaded to calculate best/worst data for individual days. Oscar는 개별 날짜에 대한 최고/최악의 데이터를 계산하기 위해 모든 요약 데이터를 로드해야 합니다. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. 이 데이터를 사용할 수 있도록 하려면 기본 설정에서 사전 로드 요약 확인란을 활성화하십시오. Best RX Setting 최상의 RX 설정 Date: %1 - %2 일: %1 - %2 AHI: %1 Total Hours: %1 총 시간: %1 Worst RX Setting 최악의 RX 설정 Most Recent 가장최근 Compliance (%1 hrs/day) 순응도 (%1 시간/일) OSCAR is free open-source CPAP report software OSCAR는 무료 오픈 소스 CPAP 보고서 소프트웨어입니다 Changes to Machine Settings 기기 설정 변경 No data found?!? 데이터가 없습니다?!? Oscar has no data to report :( Oscar는보고 할 데이터가 없습니다:( Last Week 지난주 Last 30 Days 최근30일 Last 6 Months 최근6달 Last Year 지난해 Last Session 마지막 세션 Details 상세 No %1 data available. 사용 가능한 %1 데이터 없음. %1 day of %2 Data on %3 %2일중 %1일 %3일 데이터 %1 days of %2 Data, between %3 and %4 %2 데이터 %1일(%3요일 부터 %4요일 사이) Days Pressure Relief 압력 완화 Pressure Settings 압력 설정 Machine Information 기기 정보 First Use 첫사용 Last Use 최근사용 Welcome Welcome to the Open Source CPAP Analysis Reporter 오픈 소스 CPAP 분석 리포터 사용을 환영합니다 What would you like to do? 무엇을 하고 싶으세요? CPAP Importer CPAP 가져오기 도구 Oximetry Wizard 산소측정기 마법사 Daily View 일별보기 Overview 개요 Statistics 통계 <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">경고: </span><span style=" color:#ff0000;">당신의 컴퓨터에 삽입전에.ResMed S9 SDCards 잠금이 필요함 </span><span style=" font-weight:600; color:#ff0000;">&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>어떤 OS시스템은 사용자 확인없이 카드에 인덱스 파일을 작성하여 당신의 양압기에서 당신의 메모리카드를 읽지 못할수 있다.</span></p></body></html> It would be a good idea to check File->Preferences first, 먼저 파일->기본 설정을 확인하는 것이 좋을 것이다, as there are some options that affect import. 불러오기 영향을 미치는 몇 가지 옵션이 있기 때문에. Note that some preferences are forced when a ResMed machine is detected ResMed 시스템이 감지되면 일부 기본 설정이 강제로 적용된다는 점에 유의하십시오 First import can take a few minutes. 처음 가져오기시 몇 분이 소요될수 있습니다. The last time you used your %1... 마지막으로 %1을 사용했습니다 ... last night 지난밤 %2 days ago %2 일전 was %1 (on %2) %1 (%2) 사용함 %1 hours, %2 minutes and %3 seconds %1 시, %2 분 %3 초 Your machine was on for %1. 기기를 %1까지 사용하였습니다. <font color = red>You only had the mask on for %1.</font> <font color = red> %1동안 마스크 착용함.</font> under 미만 over 이상 reasonably close to 에 근접 equal to 와 같은 You had an AHI of %1, which is %2 your %3 day average of %4. AHI는 %1이고, %3일 평균 %4 %2입니다. Your CPAP machine used a constant %1 %2 of air 귀하의 CPAP(양압기)는 일정한 %1 %2의 공기를 사용합니다 Your pressure was under %1 %2 for %3% of the time. 귀하의 압력은 %3 % 시간 동안 %1 %2 이하였습니다. Your machine used a constant %1-%2 %3 of air. 귀하의 기기는 일정한 %1-%2 %3의 공기를 사용했다. Your EPAP pressure fixed at %1 %2. 날숨 압력이 %1 %2로 고정됨. Your IPAP pressure was under %1 %2 for %3% of the time. IPAP(들숨) 압력이 %3% 동안 %1 %2 미만이었습니다. Your EPAP pressure was under %1 %2 for %3% of the time. 날숨 압력이 %3% 동안 %1 %2 미만이었습니다. Your machine was under %1-%2 %3 for %4% of the time. 귀하의 기기는 시간의 %4% 동안 %1- %2 %3 미만이었습니다. 1 day ago 1일전 Your average leaks were %1 %2, which is %3 your %4 day average of %5. 평균 누수는 %1 %2였습니다. 이는 귀하의 %4 일 평균 %5 %3입니다. No CPAP data has been imported yet. CPAP 데이터를 아직 가져오지 못했습니다. gGraph %1 days %1 일 gGraphView 100% zoom level 100% 줌 레벨 Restore X-axis zoom to 100% to view entire selected period. 전체 선택된 기간을 보려면 100% 줌 X-축을 복원하십시오. Restore X-axis zoom to 100% to view entire day's data. 전체 요일 데이터를 보려면 100% 줌 X-축을 복원하십시오. Reset Graph Layout 그래프 레이아웃 재설정 Resets all graphs to a uniform height and default order. 모든 그래프를 일정한 높이 및 기본 순서로 재설정. Y-Axis Y-축 Plots 구성 CPAP Overlays CPAP 오버레이 Oximeter Overlays 산소측정기 오버레이 Dotted Lines 점선 Double click title to pin / unpin Click and drag to reorder graphs 고정 / 고정해제 제목 더블 클릭 그래프를 다시 정렬하려면 클릭하고 드레그 Remove Clone 복제본 제거 Clone %1 Graph 그래프 %1 복제 OSCAR-code-v1.3.1/Translations/Magyar.hu.ts000066400000000000000000014452171417327530600203730ustar00rootroot00000000000000 AboutDialog &About &Névjegy Release Notes Kiadási jegyzék Credits Köszönet GPL License GPL licenc Close Bezárás Show data folder Adat könyvtár megnyitása About OSCAR %1 Az OSCAR-ról %1 Sorry, could not locate About file. Nem sikerült a névjegy fájlt megnyitni. Sorry, could not locate Credits file. Nem sikerült a köszönet fájlt megnyitni. Sorry, could not locate Release Notes. Nem sikerült a kiadási jegyzék fájlt megnyitni. OSCAR %1 OSCAR %1 Important: Fontos: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Mivel ez egy kiadás előtti verzió, ajánlott, hogy <b>készítsen biztonsági mentést az adat könyvtáráról manuálisan</b> mielőtt tovább lépne, mert egy későbbi visszaállítás eltörhet dolgokat. To see if the license text is available in your language, see %1. Ha meg akarja nézni, hogy a licenc szövege elérhető-e az ön nyelvén, kattintson ide: %1. CMS50F37Loader Could not find the oximeter file: Az oximéter fájl nem található itt: Could not open the oximeter file: Az oximéter fájlt nem lehetett megnyitni itt: CMS50Loader Could not get data transmission from oximeter. Az adatátvitel sikertelen volt az oximéterről. Please ensure you select 'upload' from the oximeter devices menu. Győződjön meg, hogy kiválasztotta a feltöltés (upload) lehetőséget az oximéter menüjében. Could not find the oximeter file: Az oximéter fájl nem található itt: Could not open the oximeter file: Az oximéter fájlt nem lehetett megnyitni itt: CheckUpdates Checking for newer OSCAR versions Új OSCAR verziók keresése Daily Go to the previous day Előző nap Show or hide the calender Naptár összecsukása vagy nyitása Go to the next day Következő nap Go to the most recent day with data records Ugrás az utolsó napra amihez van adat Events Események View Size Méret Notes Jegyzetek Journal Napló B B u u Color Szín Small Kicsi Medium Közepes Big Nagy Zombie Zombi I'm feeling ... Így érzem magam ... Weight Súly If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Ha a magasság nagyobbra van állítva, mint nulla a beállítások ablakban, a súly beállításával elérhetővé válik a testtömeg-index (BMI) értéke Awesome Szuper B.M.I. B.M.I. Bookmarks Könyvjelzők Add Bookmark Könyvjelző hozzáadása Starts Kezdőpont Remove Bookmark Könyvjelző törlése Flags Jelölők Graphs Grafikonok Show/hide available graphs. Grafikonok ki és bekapcsolása. Breakdown Lebontás events események UF1 UF1 UF2 UF2 Time at Pressure Nyomás alatt töltött idők No %1 events are recorded this day Nem lett %1 esemény rögzítve ezen a napon %1 event %1 esemény %1 events %1 események Session Start Times Szakasz kezdő időpontok Session End Times Szakasz vége időpontok Session Information Szakasz információk Oximetry Sessions Oximetria szakaszok Duration Hossz (Mode and Pressure settings missing; yesterday's shown.) (Mód és nyomás beállítások hiányoznak; a tegnapit mutatjuk.) This bookmark is in a currently disabled area.. Ez a könyvjelző jelenleg nem látható területre esik.. CPAP Sessions CPAP szakaszok Details Részletek Sleep Stage Sessions Alvási fázis szakaszok Position Sensor Sessions Pozíciószenzor szakaszok Unknown Session Ismeretlen szakasz Machine Settings Gép beállítások Model %1 - %2 Model %1 - %2 PAP Mode: %1 PAP mód: %1 99.5% 99.5% This day just contains summary data, only limited information is available. Ez a nap csak összegző adatokat tartalmaz, az elérhető információk limitáltak. Total ramp time Teljes "rámpa" idő Time outside of ramp "Rámpán" kívüli idő Start Kezdés End Végzés Unable to display Pie Chart on this system Nem lehet a tortadiagrammot megjeleníteni ezen a rendszeren 10 of 10 Event Types 10 / 10 Esemény típus This CPAP machine does NOT record detailed data Ez a CPAP gép nem rögzít részletes adatokat Sorry, this machine only provides compliance data. Elnézést, ez a gép csak teljesítés adatokat kínál. "Nothing's here!" "Nincs itt semmi!" No data is available for this day. Nem érhető el adat ezen a napon. 10 of 10 Graphs 10 / 10 Grafikon Oximeter Information Oximéter információk Click to %1 this session. Kattintson ide, hogy %1 ezt a szakaszt. disable letiltsa enable engedélyezze %1 Session #%2 %1 szakasz #%2 %1h %2m %3s %1ó %2p %3m <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Figyelem:</b> Minden alábbi beállítás azon a feltételezésen alapul, hogy semmi nem változott az előző napokhoz képest. SpO2 Desaturations SpO2 deszaturációk Pulse Change events Pulzusszám változás események SpO2 Baseline Used SpO2 alapszint Statistics Statisztika Total time in apnea Teljes apnoé-ban töltött idők Time over leak redline Szivárgáshatár felett töltött idő Event Breakdown Esemény lebontás Sessions all off! Minden szakasz kikapcsolva! Sessions exist for this day but are switched off. Vannak szakasz adatok erre a napra, de mind ki van kapcsolva. Impossibly short session Lehetetlenül rövid szakasz Zero hours?? Nulla óra?? BRICK :( TÉGLA :( Complain to your Equipment Provider! Tegyen panaszt az eszköz kereskedőnél! Pick a Colour Válasszon színt Bookmark at %1 Könyvjelző itt: %1 ExportCSV Export as CSV Exportálás CSV-be Dates: Dátumok: Resolution: Felbontás: Details Részletek Sessions Szakaszok Daily Napi Filename: Fájlnév: Cancel Mégse Export Export Start: Kezdés: End: Vég: Quick Range: Időintervallum: Most Recent Day Utolsó nap Last Week Múlt hét Last Fortnight Utolsó két hét Last Month Utolsó hónap Last 6 Months Utolsó 6 hónap Last Year Utolsó év Everything Minden Custom Egyéni Details_ Reszletek_ Sessions_ Szakaszok_ Summary_ Osszegzes_ Select file to export to Válassza ki hova mentsük a fájlt CSV Files (*.csv) CSV fájlok (*.csv) DateTime Dátum/Idő Session Szakasz Event Esemény Data/Duration Adat/Időtartam Date Dátum Session Count Szakaszok száma Start Kezdés End Vég Total Time Teljes idő AHI AHI Count Száma FPIconLoader Import Error Import hiba This Machine Record cannot be imported in this profile. A gép által rögzített adatok nem importálhatók ebbe a profilba. The Day records overlap with already existing content. A napi mérések ütköznek a már meglévő adatokkal. Help Hide this message Ne mutassa ezt az üzenetet Search Topic: Keresés: Help Files are not yet available for %1 and will display in %2. A súgófájlok nem elérhetők %1 nyelven, így %2 nyelven fognak megjelenni. Help files do not appear to be present. Úgy látszik, hogy a súgófájlok nem elérhetők. HelpEngine did not set up correctly A súgómotor nem lett rendesen beállítva HelpEngine could not register documentation correctly. A súgómotor nem tudta regisztrálni a dokumentációt rendesen. Contents Tartalom Index Index Search Keresés No documentation available Dokumentáció nem elérhető Please wait a bit.. Indexing still in progress Várjon egy kicsit.. Indexelés folyamatban No Nem %1 result(s) for "%2" %1 találat erre: "%2" clear törlés MD300W1Loader Could not find the oximeter file: Nem található az oximéter fájl: Could not open the oximeter file: Nem megnyitható az oximéter fájl: MainWindow &Statistics &Statisztika Report Mode Riport mód Standard Általános Monthly Havi Date Range Dátum intervallum Statistics Statisztika Daily Napi Overview Áttekintés Oximetry Oximetria Import Importálás Help Súgó &File &Fájl &View &Nézet &Reset Graphs &Grafikonok alaphelyzetbe &Help &Súgó Troubleshooting Problémamegoldás &Data &Adat &Advanced &Speciális Purge ALL Machine Data Minden rögzített adat törlése Rebuild CPAP Data CPAP adatok újraépítése &Import CPAP Card Data &CPAP kártya adatok importálása Show Daily view Napi nézet mutatása Show Overview view Áttekintő nézet mutatása &Maximize Toggle &Teljesképernyő kapcsoló Maximize window Ablak teljes képernyőre méretezése Reset Graph &Heights Grafikon magasságok &vissszaállítása Reset sizes of graphs Grafikon méretek visszaállítása Show Right Sidebar Jobb oldalsáv mutatása Show Statistics view Statisztika nézet mutatása Import &Dreem Data &Dreem adatok importálása Show &Line Cursor &Vonal kurzor mutatása Show Daily Left Sidebar Napi bal sáv mutatása Show Daily Calendar Napi naptár mutatása Create zip of CPAP data card Zip fájl készítése a CPAP kártyáról Create zip of OSCAR diagnostic logs Zip fájl készítése az OSCAR diagnosztikai naplóiból Create zip of all OSCAR data Zip fájl készítése minden OSCAR adatról Report an Issue Probléma jelentése System Information Rendszer információk Show &Pie Chart &Torta diagram mutatása Show Pie Chart on Daily page Torta diagram mutatása napi bontás oldalon Standard graph order, good for CPAP, APAP, Bi-Level Normál grafikon sorrend, ajánlott CPAP APAP és Bi-Level esetén Advanced Speciális Advanced graph order, good for ASV, AVAPS Speciális grafikon sorrend, ajánlott ASV AVAPS esetén Show Personal Data Személyes adatok mutatása Check For &Updates &Frissítések keresése Purge Current Selected Day Az aktuálisan kiválasztott nap törlése &CPAP &CPAP &Oximetry &Oximetria &Sleep Stage &Alvási fázis &Position &Pozició &All except Notes &Minden kivéve a jegyzeteket All including &Notes Minden a &jegyzetekkel &Preferences &Beállítássok &Profiles &Profilok &About OSCAR &Az OSCAR-ról Show Performance Information Teljesítmény információk mutatása CSV Export Wizard CSV export varázsló Export for Review Exportálás felülvizsgálatra E&xit &Kilépés Exit Kilépés View &Daily &Napi nézet View &Overview &Összegző nézet mutatása View &Welcome &Üdvözlőképernyő mutatása Use &AntiAliasing &AntiAliasing használata Show Debug Pane Debug panel mutatása Take &Screenshot &Képernyőkép készítése O&ximetry Wizard O&ximetria varázsló Print &Report &Riport nyomtatása &Edit Profile Profil &szerkesztése Import &Viatom/Wellue Data &Viatom/Wellue adat importálása Daily Calendar Napi naptár Backup &Journal Biztonsági mentés a &naplóról Online Users &Guide Online felhasználói &kézikönyv &Frequently Asked Questions &Gyakran ismételt kérdések &Automatic Oximetry Cleanup &Automatikus oximetria takarítás Change &User &Felhasználóváltás Purge &Current Selected Day Kiválasztott &nap adatainak törlése Right &Sidebar Jobb &oldalság Daily Sidebar Napi oldalsáv View S&tatistics &Statisztika Navigation Navigáció Bookmarks Könyvjelzők Records Rekordok Exp&ort Data Adatok &Exportálása Profiles Profilok Purge Oximetry Data Oximetria adatok törlése View Statistics Statisztika Import &ZEO Data &ZEO adatok importálása Import RemStar &MSeries Data RemStar &MSeries adatok importálása Sleep Disorder Terms &Glossary Alvászavar kifejezések és &szójegyzék Change &Language Nyelv &váltása Change &Data Folder &Adatkönyvtár váltása Import &Somnopose Data &Somnopose adatok importálása Current Days Aktuális napok Welcome Üdvözlet &About &Névjegy Please wait, importing from backup folder(s)... Kérem várjon, importálás folyamatban a backup könyvtárakból... Import Problem Probléma importálása Please insert your CPAP data card... Kérem helyezze be a CPAP memóriakártyát... Access to Import has been blocked while recalculations are in progress. Az importáláshoz hozzáférés nem lehetséges amíg az újraszámolás folyamatban van. CPAP Data Located CPAP adatok megtalálva Import Reminder Emlékeztető importálása Find your CPAP data card Keresse meg a CPAP memóriakártyát Importing Data Adatok importálása Choose where to save screenshot Válassza ki, hogy hova mentsük a képernyőkéepeket Image files (*.png) Képfájlok (*.png) The User's Guide will open in your default browser A felhasználói kézikönyv az alapértelmezett böngészőben fog megnyílni The FAQ is not yet implemented GYIK még nincs implementálva If you can read this, the restart command didn't work. You will have to do it yourself manually. Ha ezt az üzenetet olvassa, az újraindítás parancs nem működött. Meg kell próbálnia manuálisan újraindítani az alkalmazást. Are you sure you want to rebuild all CPAP data for the following machine: Biztosan újra szeretné építeni az összes CPAP adatot a következő gép részére: For some reason, OSCAR does not have any backups for the following machine: Valamiért az OSCAR nem rendelkezik biztonsági mentéssel a következő géphez: OSCAR does not have any backups for this machine! Az OSCAR nem rendelkezik biztonsági mentéssel ehhez a géphez! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Ha csak nem készített <i><b>saját</b> mentést minden adatról ehhez a géphez</i>, <font size=+2> minden adatát el fogja veszíteni visszavonhatatlanul</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> <font size=+2>Kitörölni</font> készül az OSCAR gép adatbázisát a következő géphez kapcsolódóan:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: Jogosultsági hiba miatt nem lehetett a törlést végrehajtani. Saját kezüleg kell a következő könyvtárat letörölni: No help is available. Súgó nem áll rendelkezésre. %1's Journal %1 Napló Choose where to save journal Válassza ki hova mentsük a naplót XML Files (*.xml) XML fájlok (*.xml) Export review is not yet implemented Áttekintés exportálása még nincs implementálva Would you like to zip this card? Szeretne zip fájlt készíteni a kártyáról? Choose where to save zip Válassza ki hova mentsük a zip fájlt ZIP files (*.zip) Zip fájlok (*zip) Creating zip... Zip fájl létrehozása... Calculating size... Méret kiszámítása... Reporting issues is not yet implemented Problémák jelentése még nincs implementálva OSCAR Information OSCAR információk Help Browser Súgó böngésző %1 (Profile: %2) %1 (Profil: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Ne feledje, hogy a kártya gyökérkönyvtárát, vagy a meghajtó betűjelét kell kiválasztani, nem a benne lévő könyvtárat. Please open a profile first. Először nyisson egy profilt. Check for updates not implemented A frissítések automatikus keresése nincs implementálva Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Felételezve, hogy létrehozta az <i>ön <b>saját</b> biztonsági mentését az összes CPAP adatról</i>, folytathatja a műveletet, de ez után csak manuálisan tudja visszaállítani az adatokat a biztonsági mentésből. Are you really sure you want to do this? Biztosan ezt akarja tenni? Because there are no internal backups to rebuild from, you will have to restore from your own. Mivel nincs belső mentés amiből újra lehetne építeni az adatokat, önnek kell visszállítani a saját biztonsági mentéséből. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Szeretne a saját biztonsági mentéséből importálni most? (nem lesz látható semmilyen adat ehhez a géphez amíg ezt nem teszi meg) Note as a precaution, the backup folder will be left in place. Elővigyázatosságként a biztonsági mentés könyvtárhoz nem nyúlunk. Are you <b>absolutely sure</b> you want to proceed? <b>Egészen biztos</b> benne, hogy folytatni akarja? The Glossary will open in your default browser A szójegyzék az alapértelmezett böngészőben fog megnyílni There was a problem opening %1 Data File: %2 Nem sikerült megnyitni: %1 Adat fájl: %2 %1 Data Import of %2 file(s) complete %1 adat importálás %2 fájlal befejeződött %1 Import Partial Success %1 importálás részlegesen sikerült %1 Data Import complete %1 adat import befejeződött Are you sure you want to delete oximetry data for %1 Biztosan törölni akarja az oximetria adatokat erre az időpontra? %1 <b>Please be aware you can not undo this operation!</b> <b>Kérjük vegye figyelembe, hogy ezt a műveletet nem tudja visszavonni!</b> Select the day with valid oximetry data in daily view first. Először válasszon egy oximetria adattal rendelkező napot a napi nézetben. Loading profile "%1" Profil betöltése: "%1" Imported %1 CPAP session(s) from %2 Importálva %1 CPAP alvás szakasz innen: %2 Import Success Importálás sikeres Already up to date with CPAP data at %1 Minden CPAP adat friss innen: %1 Up to date Minden adat friss Couldn't find any valid Machine Data at %1 Nem található érvényes adat itt: %1 Choose a folder Válasszon egy könyvtárat No profile has been selected for Import. Nincs profil kiválasztva az importáláshoz. Import is already running in the background. Az importálás már folyamatban van a háttérben. A %1 file structure for a %2 was located at: Egy %1 fájl struktúra a %2 -hoz megtalálva itt: A %1 file structure was located at: Egy %1 fájl struktúra megtalálva itt: Would you like to import from this location? Szeretné az importálást elindítani erről a helyről? Specify Részletezés Access to Preferences has been blocked until recalculation completes. A beállításokhoz való hozzáférés letiltva az újraszámolás befejeztéig . There was an error saving screenshot to file "%1" Hiba történt a képernyőkép mentésekor erre a helyre "%1" Screenshot saved to file "%1" Képernyőkép mentve ide: "%1" Please note, that this could result in loss of data if OSCAR's backups have been disabled. Vegye figyelembe, hogy ez adatvesztéssel járhat, ha az OSCAR biztonsági mentések le vannak tiltva. There was a problem opening MSeries block File: Hiba történt az Mseries blokk fájl megnyitása közben: MSeries Import complete MSeries importálás kész MinMaxWidget Auto-Fit Automatikus méretezés Defaults Alapértelmezett Override Felülbírálás The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Az Y tengely méretezési módja. Automatikus méretezés, alapértelmezett (gyártói) vagy kézzel beállított lehet. The Minimum Y-Axis value.. Note this can be a negative number if you wish. A minimális Y tengely érték. Lehet negatív is, ha szeretné. The Maximum Y-Axis value.. Must be greater than Minimum to work. A maximális Y tengely érték. Csak a minimálisnál nagyobb értékkel működik. Scaling Mode Méretezési mód This button resets the Min and Max to match the Auto-Fit Ez a gomb visszaállítja a min és max értéket az automatikus méretezéshez NewProfile Edit User Profile Felhasználói profil szerkesztése I agree to all the conditions above. Minden fenti feltételt elfogadok. User Information Felhasználói információk User Name Felhasználónév Password Protect Profile Profil jelszóval ellátása Password Jelszó ...twice... ...mégegyszer... Locale Settings Nyelvi beállítások Country Ország TimeZone Időzóna about:blank about:blank Very weak password protection and not recommended if security is required. Ez egy nagyon gyenge titkosítás és nem ajánlott olyan esetekben ahol a biztonság igazán fontos. DST Zone DST zóna Personal Information (for reports) Személyes információk (riportokhoz) First Name Keresztnév Last Name Vezetéknév It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Nyugodtan kihagyhatja ezeket, de az életkora megadása fontos néhány számításhoz. D.O.B. Szül. nap. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Biológiai (születési) segíthet néhány számítás pontossításához, nyugodtan hagyja ki.</p></body></html> Gender Nem Male Férfi Female Height Magasság Metric Metrikus English Angolszász Contact Information Kapcsolati információk Address Cím Email E-mail Phone Telefon CPAP Treatment Information CPAP kezelési információk Date Diagnosed Diagnosztizálás időpontja Untreated AHI Kezelés előtti AHI CPAP Mode CPAP mód CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure RX nyomás Doctors / Clinic Information Orvos / Klinika információk Doctors Name Orvos neve Practice Name Praxis neve Patient ID Páciens azonosítója &Cancel &mégsem &Back &vissza &Next &következő Select Country Válasszon országot Welcome to the Open Source CPAP Analysis Reporter Üdvözli a nyílt forráskódú CPAP riport készítő This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Ez a szoftver azért készült, hogy segítse Önt abban, hogy ellenőrizni tudja a CPAP gépe által előállított adatokat. PLEASE READ CAREFULLY OLVASSA EL FIGYELMESEN Accuracy of any data displayed is not and can not be guaranteed. A megjelenített adatok pontossága nem garantált, nem is garantálható. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Bármilyen generált riport CSAK SZEMÉLYES HASZNÁLATRA KÉSZÜL, SEMMILYEN igazolásként, vagy diagnosztikai feladatokra nem alkalmas. Use of this software is entirely at your own risk. A szoftvert csak saját felelősségre használhatja. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR copyright &copy;2011-2018 Mark Watkins és részben &copy;2019-2022 az OSCAR Csapat számára OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. Az OSCAR ingyenesen elérhető a <a href='qrc:/COPYING'>GNU Általános Nyilvános Licenc v3</a> alatt és semmilyen garanciát nem vállal, és nem garantálja, hogy BÁRMIRE alkalmas. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. Az OSCAR csupán az adatok egy megjelenítője, semmiképpen sem helyettesítí a szakszerű orvosi ellátást, kezelést. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. A szerzők nem felelősek <u>semmiért</u> ami a szoftver használatából, vagy téves használatából ered. Please provide a username for this profile Kérem adjon meg egy felhasználónevet ehhez a profilhoz Passwords don't match A jelszavak nem egyeznek Profile Changes Profil változtatások Accept and save this information? Elfogadja és menti ezeket az információkat? &Finish &Befejezés &Close this window Ablak &bezárása Overview Range: Időszak: Last Week Múlt hét Last Two Weeks Elmúlt két hét Last Month Múlt hónap Last Two Months Elmúlt két hónap Last Three Months Elmúlt három hónap Last 6 Months Utolsó fél év Last Year Utolsó év Everything Minden Custom Egyéni Snapshot Pillanatkép Start: Kezdet: End: Vég: Reset view to selected date range Nézet visszaállítása a kiválasztott intervallumra Toggle Graph Visibility Grafikon be/ki Drop down to see list of graphs to switch on/off. Legördülő menü a grafikonok ki/be kapcsolásához. Graphs Grafikonok Respiratory Disturbance Index Respiratory Disturbance Index Apnea Hypopnea Index Apnoé Hypopnoé Index Usage Használat Usage (hours) Használat (órákban) Session Times Szakaszok Total Time in Apnea Teljes apnoé idő Total Time in Apnea (Minutes) Teljes Apnoé idő (Percek) Body Mass Index Testtömeg-index How you felt (0-10) Hogy érezte magát (0-10) 10 of 10 Charts 10-ből 10 diagram Show all graphs Minden grafikon mutatása Hide all graphs Minden grafikon elrejtése OximeterImport Oximeter Import Wizard Oximéter import varázsló Skip this page next time. Legközelebb hagyjuk ki ezt az oldalt. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Figyelem: </span><span style=" font-style:italic;">Először ki kell választani a megfelelő oximéter típust a legördülő menüből.</span></p></body></html> Where would you like to import from? Honnan szeretne importálni? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">ELŐSZÖR válassza ki az Ön oximéterét a következő csoportokból:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. CMS50E/F felhasználók ha közvetlen importálnak, kérem ne válasszák a feltöltés lehetőséget az eszközön, amíg az OSCAR erre nem kéri. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Ha engedélyezve van, az OSCAR automatikusan beállítja a CMS50 belső óráját a számítógép órájához. </p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>Itt megadhat egy hét karakter hosszú nevet az oximéternek.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Ez a beállítás törli az importált szakaszt az oximéterből amikor az importálás befejeződött. </p><p>Óvatosan használja, mert ha valami hiba történik azelőtt, hogy az OSCAR menteni tudná a mérést, az egész mérés el fog veszni.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Ez a beállítás lehetővé teszi az importálást (kábelen keresztül) az oximéter belső rögzítéseiből.</p><p>Miután kiválaszotta ezt a lehetőséget, a régebbi Contec oximéterek megkövetelik, hogy az eszköz menüjében kezdeményezze a feltöltést.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Ha nem zavarja, hogy egy működő számítógéphez van kötve éjszaka, ez az opció plethysmográf grafikont készít, ami tájékoztatást ad a szívritmusáról, a normál oximetria mérésen felül.</p></body></html> Record attached to computer overnight (provides plethysomogram) Rögzítés a számítógéphez kötve éjjelre (plethysmográf) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Ez az opció lehetővé teszi az olyan adatfájlok importálását, amit az eszközzel érkező szoftver állított elő, mint pl. az SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Más program által készített adatfájl importálása (pl. SpO2Review) Please connect your oximeter device Csatlakoztassa az oximéter eszközét If you can read this, you likely have your oximeter type set wrong in preferences. Ha ezt olvassa, valószínűleg rossz oximéter típust adott meg a beállításokban. Please connect your oximeter device, turn it on, and enter the menu Csatlakoztassa az oximéter eszközt, kapcsolja be és lépjen be a menübe Press Start to commence recording Nyomja meg a startot a felvétel elindításához Show Live Graphs Élő grafikonok mutatása Duration Hossz Pulse Rate Pulzusszám Multiple Sessions Detected Több szakasz érzékelve Start Time Indítási idő Details Részletek Import Completed. When did the recording start? Importálás befejezve. Mikor kezdődött a rögzítés? Oximeter Starting time Oximéter indítási idő I want to use the time reported by my oximeter's built in clock. Az oximéter óráját szeretném alapul venni. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Nagyjából akkor indítottam az oximétert amikor a CPAP gépet. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Tipp: A CPAP kezdési időhöz szinkronizálás mindig pontosabb lesz.</p></body></html> Choose CPAP session to sync to: Válassza ki a CPAP mérést amihez szinkronizálni szeretne: You can manually adjust the time here if required: Ha szükséges, manuálisan beállíthatja az időt: HH:mm:ssap HH:mm:ssap &Cancel &Mégsem &Information Page &információs lap CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Állítsuk be az eszköz dátumát/időpontját <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Pipálja ki, hogy engedélyezve legyen az eszköz azonosító feltöltése a következő importálásnál. Ez hasznos azoknak akik több Oximéterrel rendelkeznek.</p></body></html> Set device identifier Állítsuk be meg az eszköz azonosítóját Erase session after successful upload Törölje a rögzítést sikeres feltöltés után Import directly from a recording on a device Importálás közvetlenül a rögzítő eszközről <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">CPAP felhasználóknak: </span><span style=" color:#fb0000;">Nem felejtette el a CPAP méréseket importálni?<br/></span>Ha elfelejti, nem lesz mivel szinkronizálni az oximetria mérést.<br/>Mindig indítsa egyszerre a két eszközt, hogy szinkronban működjenek.</p></body></html> Please choose which one you want to import into OSCAR Válassza ki, melyiket szeretné importálni az OSCAR-ba Day recording (normally would have) started Felvétel napja (normál esetben) amikor elkezdődött <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>Az OSCAR-nak szüksége van egy kezdő időpontra, hogy tudja hova mentse ezt az oximetria szakaszt.</p><p>Válasszon az alábbi lehetőségek közül:</p></body></html> &Retry &Újra &Choose Session &Válasszon mérési szakaszt &End Recording Rögzítés &befejezése &Sync and Save &Szinkronizálás és mentés &Save and Finish &Mentés és befejezés &Start &Indítás Scanning for compatible oximeters Kompatibilis oximéterek keresése Could not detect any connected oximeter devices. Nem találtunk csatlakoztatott oximéter eszközt. Connecting to %1 Oximeter Kapcsolódás a %1 Oximéterhez Renaming this oximeter from '%1' to '%2' Oximéter átnevezése '%1'-ről '%2'-re Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Az oximéter név különbözik.. Ha csak egy van és megosztja a profilok között, állítsa be ugyanazt a nevet mindkét profilnál. "%1", session %2 "%1", szakasz %2 Nothing to import Nincs mit importálni Your oximeter did not have any valid sessions. Az oximétere nem tartalmaz értékelhető rögzítést. Close Bezárás Waiting for %1 to start Várakozás %1 készülékre az indításhoz Waiting for the device to start the upload process... Várakozás az eszközre, hogy elindulhasson a feltöltés... Select upload option on %1 Válassza a feltöltés opciót a %1 eszközön You need to tell your oximeter to begin sending data to the computer. Jelezni kell az Oximéternek hogy kezdje meg az adatok küldését a számítógép felé. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Kérem csatlakoztassa az oximétert, lépjen be a menüjébe, és válassza a feltöltést a másolás elindításához... %1 device is uploading data... %1 gép feltölti az adatokat... Please wait until oximeter upload process completes. Do not unplug your oximeter. Várja meg amíg az oximéter feltöltés befejeződik. Ne húzza ki az oximétert. Oximeter import completed.. Oximéter importálás befejeződött.. Select a valid oximetry data file Válasszon érvényes oximetria adatfájlt Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Oximetria fájlok (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Egyik oximetria modul sem tudta feldolgozni a megadott fájlt: Live Oximetry Mode Élő oximetria mód Live Oximetry Stopped Élő Oximetria megszakítva Live Oximetry import has been stopped Élő Oximetria importálás félbe lett szakítva Oximeter Session %1 Oximéter szakasz %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. Az OSCAR lehetőséget biztosít, hogy kövesse az Oximetria adatokat a CPAP szakaszokkal egyetemben, ami értékes bepillantást ad a CPAP kezelés hatékonyságáról. Csak a Pulzoximéter adatainak rögzítése is lehetséges, rögzítheti, követheti, és ellenőrizheti a rögzített adatokat. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Ha az oximetria és CPAP adatokat szeretné szinkronizálni, győződjön meg róla, hogy a CPAP rögzítést már importálta korábban! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Ahhoz hogy az OSCAR képes legyen megtalálni és olvasni az oximéter eszközét, Önnek meg kell bizonyosodnia arról hogy a megfelelő eszköz illesztőprogramok (pl. USB- Soros UART) telepítve vannak a számítógépére. További információkért %1kattintson ide%2. Oximeter not detected Oximéter nem található Couldn't access oximeter Nem sikerült hozzáférni az oximéterhez Starting up... Indítás... If you can still read this after a few seconds, cancel and try again Ha pár másodperc után is ezt látja, szakítsa meg a folyamatot és próbálja újra Live Import Stopped Élő importálás megállítva %1 session(s) on %2, starting at %3 %1 szakasz %2 napon %3 dátumtól kezdve No CPAP data available on %1 Nincs CPAP adat %1 időpontban Recording... Rögzítés... Finger not detected Ujj nem érzékelhető I want to use the time my computer recorded for this live oximetry session. A számítógép által rögzített időt szeretném használni ehhez az élőben rögzített oximetria szakaszhoz. I need to set the time manually, because my oximeter doesn't have an internal clock. Be kell állítanom az időt manuálisan, mert az oximéterem nem rendelkezik belső órával. Something went wrong getting session data Valami hiba történt a mérési adatok beszerzése közben Welcome to the Oximeter Import Wizard Üdvözli az Oximéter varázsló Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. A pulzoximéterek orvosi eszközök a vér oxigén szaturációjának mérésére. Elhúzódó apnoé események és abnormális légzési minták közben a vér oxigén szaturációja jelentősen lecsökkenhet, és orvosi felügyeletet kívánó problémát jelezhet. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) Az OSCAR jelenleg a Contec CMS50D+, CMS50E, CMS50F és CMS50I soros oximéterekkel kompatibilis.<br/> (Direkt importálás bluetooth eszközökről<span style=" font-weight:600;">valószínűleg nem</span> lehetséges jelenleg) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Jó ha tudja, más gyártók, mint pl. a Pulox egyszerűen átcimkézik a Contec CMS50-et más néven, úgy mint, Pulox-200, PO-300, PO-400. Ezek is valószínűleg működni fognak. It also can read from ChoiceMMed MD300W1 oximeter .dat files. A ChoiceMMed MD300W1 Oximéter .dat fájljait is használhatja. Please remember: Ne feledje: Important Notes: Fontos megjegyzések: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Contec CMS50D+ eszközöknek nincs belső órájuk és nem rögzítik a kezdő időpontot. Ha rendelkezik CPAP rögzítéssel amihez kapcsolható, meg kell adnia a kezdő időpontot manuálisan az importálási folyamat végén. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Még a belső órával rendelkező eszközök esetén is ajánlott az oximéter rögzítést egyszerre indítani a CPAP géppel, mivel a CPAP gépek belső órája elállítódhat idővel, és nem egyszerű beállítani. Oximetry Date Időpont d/MM/yy h:mm:ss AP yy/MM/d h:mm:ss AP R&eset &Visszaállítás Pulse Pulzus &Open .spo/R File .spo/R fájl &megnyitása Serial &Import &Importálás soros portról &Start Live &Élő monitorozás Serial Port Soros port &Rescan Ports &Portok újraszkennelése PreferencesDialog Preferences Beállítások &Import &Importálás Combine Close Sessions Közeli szakaszok kombinálása Minutes Perc Multiple sessions closer together than this value will be kept on the same day. Ennél az értéknél kisebb távolságú szakaszok egy napnak kezelendőek. Ignore Short Sessions Rövid szakaszok ignorálása <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Ennél rövidebb szakaszok nem fognak látszani<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Nap váltás időpontja Sessions starting before this time will go to the previous calendar day. Ennél korábban zajló szakaszok az előző naptári napba fognak számolódni. Session Storage Options Szakasz tárolsái beállítások Compress SD Card Backups (slower first import, but makes backups smaller) SD Kártya mentések tömörítése (lassabb az első import, de kisebbek lesznek a mentések) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Az ennél rövidebb használatot nem elégségesnek vesszük. 4 óra használat számít általában elégségesnek. hours óra Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Esemény jelzések javításának engedélyezése/tiltása (kísérleti fázis). Olyan események detektálását engedélyezi, amit a gép esetleg kihagyott mivel azok nem egyértelműek. Ezt a beállítást importálás előtt kell engedélyezni, egyébként újraimportálás szükséges. Flow Restriction Korlátozott áramlás Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Az légáramlás korlátozásának százalékos mértéke a középértékhez képest. 20% jól szokott működni az apnoék érzékelésére. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Ez egy kísérleti módszer a gép által nem detektált események megtalálására. <span style=" text-decoration: underline;">NEM</span> számolódik bele az AHI-ba.</p></body></html> Duration of airflow restriction Légáramlás korlátozás hossza s mp Event Duration Esemény időtartama Allow duplicates near machine events. Duplikációk engedélyezése a gép események közelépben. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Meghatározza az adat mennyiséget ami számításba lesz véve az AHI/óra grafikon pontjainál. Alapértelmezetten 60 perc, nagyon ajánlott ezen az értéken hagyni. minutes perc Reset the counter to zero at beginning of each (time) window. Visszaállítja a számlálót nullára minden időablak elején. Zero Reset Nulla visszaállítás CPAP Clock Drift Óra eltolása Do not import sessions older than: Ne importáljon ennél régebbi méréseket: Sessions older than this date will not be imported Az ennél régebbi rögzítések nem lesznek importálva dd MMMM yyyy yyyy MMMM dd User definable threshold considered large leak Felhasználó által válaszott küszöb, amitől nagy szivárgást regisztrálunk Whether to show the leak redline in the leak graph Mutassuk a szivárgás határvonalát a szivárgás grafikonon Search Keresés &Oximetry &Oximetria Show in Event Breakdown Piechart Esemény lebontás tortadiagram mutatása Resync Machine Detected Events (Experimental) Gép által érzékelt események újraszinkronizálása (Kísérleti) Percentage drop in oxygen saturation Százalékos csökkenés az oxigén szaturációban Pulse Pulzus Sudden change in Pulse Rate of at least this amount Pulzusszám hirtelen változása legalább ennyivel bpm bpm Minimum duration of drop in oxygen saturation Legalább ennyi ideig tartó szaturáció esés Minimum duration of pulse change event. Legalább ennyi ideig tartó pulzusszám változás esemény. Small chunks of oximetry data under this amount will be discarded. Kis méretű oximetria adatok e határ alatt figyelmen kívül lesznek hagyva. &General &Általános Changes to the following settings needs a restart, but not a recalc. Az alábbi beállítások megváltoztatása újraindítást igényel, de újrakalkulálást nem. Preferred Calculation Methods Preferált számítási módok Middle Calculations Középső számítások Upper Percentile Felső százalék Session Splitting Settings Szakasz vágási beállítások <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Ezt a beállítást mindig elővigyázatossággal használd...</span> Kikapcsolása hatással van a napi összegző értékekre, mivel néhány számítás csak akkor pontos, ha a napi rekordok együtt vannak tartva. </p><p><span style=" font-weight:600;">ResMed használók:</span> Attól még, hogy az éjféli váltás természetesnek tűnik, nem biztos, hogy a ResMed adatok is így gondolják. Az STF.edf összegző index formátum gyengeségei miatt ez rossz ötlet. </p><p> Ez az opció csak azoknak elérhető, akiknek nem számítanak ennek a beállításnak a következményei, de ettől még a következmények megvannak. Ha mindig a gépben tartod az SD kártyát, és legalább hetente importálsz, nem fogsz problémákba ütközni olyan gyakran. </p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Ne ossza fel az összegzett napokat Memory and Startup Options Memória és indítás beállítások Pre-Load all summary data at startup Összegző adatok előtöltése indításkor <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Ez a beállítás a memóriában tartja a grafikon és esemény adatokat, hogy felgyorsítsa a napok közti váltást.</p><p>Ez nem feltétlenül szükséges beállítás, mivel az operációs rendszer gyorsítótárazza a nem rég használt fájlokat.</p><p>Ajánlott kikapcsolva hagyni, hacsak a számítógépe nem rendelkezik nagyon sok memóriával.</p></body></html> Keep Waveform/Event data in memory Tartsa a görbe/esemény adatokat a memóriában <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Kikapcsol minden lényegtelen megerősítő ablakot importálás közben.</p></body></html> Import without asking for confirmation Importálás megkezdése kérdés néllkül This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Calculate Unintentional Leaks When Not Present Számolja ki a véletlen szivárgásokat, ha nincs jelen Note: A linear calculation method is used. Changing these values requires a recalculation. Figyelmeztetés: lineáris kalkulációs módszert használ. Ezek az értékek megváltoztatása újraszámítást igényel. General CPAP and Related Settings Általános CPAP és kapcsolódó beállítások Enable Unknown Events Channels Ismeretlen esemény csatornák engedélyezése AHI Apnea Hypopnea Index Apnoé Hipopnoé index RDI Respiratory Disturbance Index RDI AHI/Hour Graph Time Window AHI/óra időgrafikon ablak Preferred major event index Preferált fő esemény index Compliance defined as Megfelelőség definíciója Flag leaks over threshold Szivárgás jelölésének határa Seconds Másodperc <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p> Az időzóna korrekciókat ne itt hajtsa végre. Győződjön meg róla, hogy a számítógép órája és időzónája helyes.</p></body></html> Hours Óra For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. A rend kedvéért a ResMed felhasználóknak 95%-ot kell használnia itt, mivel ez az egy érték elérhető a csak összegzéssel rendelkező napokon. Median is recommended for ResMed users. Közép ajánlott a ResMed felhasználóknak. Median Közép Weighted Average Súlyozott átlag Normal Average Normál átlag True Maximum Valódi maximum 99% Percentile 99% Százalék Maximum Calcs Maximum számítások General Settings Általános beállítások Daily view navigation buttons will skip over days without data records Skip over Empty Days Üres napok átugrása Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Több CPU mag használatának engedélyezése a teljesítmény javítása érdekében. Főként az importálást befolyásolja. Enable Multithreading Többszálúság engedélyezése Bypass the login screen and load the most recent User Profile Hagyja ki a bejelentkező képernyőt és térjen egyből a felhasználó profilra Create SD Card Backups during Import (Turn this off at your own peril!) SD kártya mentés készítése importáláskor <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Az igazi maximum az ami az adatsorban a maximum</p><p>a 99 százalék kiszűri a legritkább kiugró értékeket.</p></body></html> Combined Count divided by Total Hours Kombinált összeg osztva az összes óraszámmal Time Weighted average of Indice Standard average of indice Custom CPAP User Event Flagging Saját CPAP felhasználói esemény jelölések Events Események Reset &Defaults &Alapértelmezések visszaállítása <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Figyelem: </span>Csak azért mert meg tudja tenni, nem biztos, hogy ez a legjobb gyakorlat.</p></body></html> Waveforms Görbék Flag rapid changes in oximetry stats Jelölje a hirtelen változásokat az oximetria adatokban Other oximetry options Egyéb oximetria beállítások Discard segments under Hagyja ki ha ennél rövidebb Flag Pulse Rate Above Magas pulzus jelölése Flag Pulse Rate Below Alacsony pulzus jelölése Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. ResMed (EDF) mentések tömörítése lemezhely takarékosság miatt. A fájlok .gz formátumban lesznek tárolva ami gyakori formátum a Mac és Linux rendszereken... Az OSCAR importálni tudja a tömörített mentéseket magától.. Ha ResScan-el akarja őket használni, először ki kell csomagolni a .gz fájlokat.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Az alábbi beállítások kihatással vannak az OSCAR lemezhasználatára, de nincs hatással az importálások hosszára. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Ez megfelezi az OSCAR által használt területet. Az importálás és a nap váltás viszont tovább fog tartani... Ha új számítógépe van ami kisebb SSD lemezt használ, ez egy jó opció Önnek. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Szakasz adatok tömorítése (az OSCAR adatok kisebbek lesznek, de a nap váltás lassulni fog.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Ez mentést készít az SD-kártya adatairól a ResMed gépeken. A ResMed S9 series gépek törlik a 7 napnál régebbi nagy felbontású adatokat, és a 30 napnál régebbi grafikonokat.. Az OSCAR megtarthat egy másolatot ezekről az adatokről, ha valamikor a jövőben újratelepítésre lesz szükség. (Nagyon ajánlott, kivéve ha kevés a lemezterülete, vagy nem érdeklik a grafikonok adatai) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Show flags for machine detected events that haven't been identified yet. Show Remove Card reminder notification on OSCAR shutdown Mutassa a memóriakártya eltávolítására vonatkozó figyelmeztetést az OSCAR bezárásakor Check for new version every Új verzió ellenőrzése days. naponta. Last Checked For Updates: Frissítések utolsó keresése: TextLabel Szövegfelirat I want to be notified of test versions. (Advanced users only please.) &Appearance &Megjelenés Graph Settings Grafikonbeállítások <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> Bar Tops Oszlopgrafikon Line Chart Vonaldiagram Overview Linecharts Áttekintés grafikon típusa Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Ha problémát észlel az OSCAR grafikonok megjelenítése során, állítson be az alapértelmezettől (Asztali OpenGL) eltérő motort. <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Ez megkönnyíti a görgetést nagyítás közben az érzékenyebb kétirányú érintőpadokon</p><p>50ms az ajánlott érték.</p></body></html> How long you want the tooltips to stay visible. Mennyi ideig maradjanak a tippek láthatók. Scroll Dampening Görgetés simítása Tooltip Timeout Tipp mutatása Default display height of graphs in pixels Grafikonok alapértelmezett magassága pixelben Graph Tooltips Grafikon tippek The visual method of displaying waveform overlay flags. Standard Bars Top Markers Fő jelölők Graph Height Grafikon magasság Changing SD Backup compression options doesn't automatically recompress backup data. Az SD mentés tömörítésbeállításának változtatása nem tömöríti újra a korábbi mentéseket. Auto-Launch CPAP Importer after opening profile CPAP importáló automatikus indítása a profil megnyitásakor Automatically load last used profile on start-up Utolsó profil automatikus betöltése indításkor <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Figyelmeztetés ha az OSCAR fejlesztők által nem tesztelt eszközről importálnak.</p></body></html> Warn when importing data from an untested machine Figyelmeztetés ha nem tesztelt gépről importálnak <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered Your masks vent rate at 20 cmH2O pressure A maszk áramlási sebessége 20 cmH2O nyomáson Your masks vent rate at 4 cmH2O pressure A maszk áramlási sebessége 4 cmH2O nyomáson <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> Oximetry Settings Oximetria beállítások Always save screenshots in the OSCAR Data folder Mindig mentse a képernyőképeket az OSCAR adat könyvtárába Check For Updates Frissítések keresése You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates Frissítések automatikus keresése How often OSCAR should check for updates. Milyen gyakran keressen az OSCAR frissítéseket. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR On Opening Megnyitáskor Profile Profil Welcome Üdvözlet Daily Napi Statistics Statisztika Switch Tabs Fülváltás No change Ne változtasson After Import Importálás után Overlay Flags Line Thickness Vonalvastagság The pixel thickness of line plots A vonalgrafikonok vonalvastagsága Other Visual Settings Egyéb vizuális beállítások Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Az anti-aliasing elsimítja a grafikonok vonalait.. Némely grafikon jobban néz ki ha ez be van kapcsolva. Ez a beállítás a nyomtatott riportokat is befolyásolja. Próbálja ki és döntse el, hogy tetszik-e. Use Anti-Aliasing Anti-Aliasing használata Makes certain plots look more "square waved". Néhány grafikont "szögletesebbé" tesz. Square Wave Plots Négyzet-hullám grafikonok Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Use Pixmap Caching Pixmap gyorsítótár használata <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> Animations && Fancy Stuff Animációk és dekorációk Whether to allow changing yAxis scales by double clicking on yAxis labels Engedjük-e változtatni az Y tengely skáláját a feliratokra történő dupla kattintásra Allow YAxis Scaling Y tengely méretezés engedélyezése Whether to include machine serial number on machine settings changes report Vegyük-e bele a riportokba a gép sorozatszámát a beállításoknál Include Serial Number Sorozatszám megjelenítése Graphics Engine (Requires Restart) Grafikus motor (újraindítást igényel) l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Print reports in black and white, which can be more legible on non-color printers A riportok fekete-fehérben nyomtatása. Javíthatja az olvashatóságot nem színes nyomtatókon Print reports in black and white (monochrome) Riportok fekete-fehérben nyomtatása (monokróm) Fonts (Application wide settings) Betűtípusok(alkalmazásszintű beállítások) Font Betűtípus Size Méret Bold Vastag Italic Dőlt Application Alkalmazás Graph Text Grafikon szöveg Graph Titles Grafikon címek Big Text Nagy betűk Details Részletek &Cancel &Mégse &Ok &Ok Name Név Color Szín Flag Type Jelölő típus Label Felirat CPAP Events CPAP események Oximeter Events Oximéter események Positional Events Pozíció események Sleep Stage Events Alvási fázis események Unknown Events Ismeretlen események Double click to change the descriptive name this channel. Ha meg akarja változtatni a nevét ennek a csatornának. Double click to change the default color for this channel plot/flag/data. Duplán kattintson hogy megváltoztassa az alapértelmezett színét ennek a csatornának. Overview Áttekintés <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> Double click to change the descriptive name the '%1' channel. Kattintson duplán hogy megváltoztathassa a %1 csatorna nevét. Whether this flag has a dedicated overview chart. Here you can change the type of flag shown for this event Itt állíthatja be milyen típusú jelölőt használjunk ennél az eseménynél This is the short-form label to indicate this channel on screen. Ez egy rövidített felirat ami ezt a csatornát jelöli a képernyőn. This is a description of what this channel does. Ez egy leírás, hogy ez a csatorna mit csinál. Lower Alsó Upper Felső CPAP Waveforms CPAP hullámok Oximeter Waveforms Oximéter görbék Positional Waveforms Pozíció görbék Sleep Stage Waveforms Alvási fázis görbék Whether a breakdown of this waveform displays in overview. Látszon-e ez a hullámgörbe az áttekintésben. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Itt beállíthatja az <b>alsó</b> határt néhány kalkulációhoz a(z) %1 görbén Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Itt tudja beállítani néhány kaluláció a <b>felső</b> határát a %1 hullámgörbének Data Processing Required Adatfeldolgozás szükséges A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? A változtatások életbe léptetéséhez az adatok újratömörítése szükséges. Ez a művelet pár percet is eltarthat. Biztos el akarja végezni ezeket a módosításokat? Data Reindex Required Adat újraindexelés szükséges A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Az adatok újraindexelése szükséges a változtatások életbe léptetéséhez. Ez a művelet több percig is eltarthat. Mindenképp el akarja végezni ezeket a módosításokat? Restart Required Újraindítás szükséges One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Egy vagy több változtatás életbe lépéséhez az alkalmazás újraindítása szükséges. Szeretné újraindítani most? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Ha valamikor újra kell importálni ezt az adatot (OSCAR-ba vagy ResScan-be) ezek az adtok már nem lesznek meg. If you need to conserve disk space, please remember to carry out manual backups. Ha takarékoskodni akkar a lemezterülettel, ne felejtsen el kézi mentéseket készíteni. Are you sure you want to disable these backups? Biztosan le akarja tiltani a biztonsági mentéseket? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. A mentések kikapcsolása nem jó ötlet, mert az OSCAR-nak szüksége van ezekre ha újra kell építeni az adatbázist valamilyen hiba miatt. Are you really sure you want to do this? Biztosan ezt akarja tenni? %1 %2 %1 %2 Flag Jelölő Minor Flag Kis jelölő Span Összevonás Always Minor Mindig apró No CPAP machines detected Nem található CPAP készülék Will you be using a ResMed brand machine? ResMed márkájú gépet fog használni? Never Soha This may not be a good idea Ez nem biztos, hogy egy jó ötlet ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). A ResMed S9 gép rutinszerűen töröl néhány 7 és 30 napnál (felbontástól függően) régebbi adatot az SD kártyáról. ProfileSelector Filter: Szűrő: Reset filter to see all profiles Szűrő visszaállítása minden profilnál Version Verzió &Open Profile Profil &megnyitása &Edit Profile Profil &Szerkesztése &New Profile &Új profil Profile: None Profil: nincs Please select or create a profile... Válasszon vagy hozzon létre új profilt... Destroy Profile Profil törlése Profile Profil Ventilator Brand Ventilátor márka Ventilator Model Ventilátor model Other Data Egyéb adat Last Imported Utolsó import Name Név You must create a profile Létre kell hozzon egy profilt Enter Password for %1 Adja meg a jelszót a %1 profilhoz You entered an incorrect password Hibás jelszót adott meg Forgot your password? Elfelejtette a jelszavát? Ask on the forums how to reset it, it's actually pretty easy. Kérdezze meg egy fórumon hogy kell visszaállítani, elég egyszerű. Select a profile first Először válasszon profilt The selected profile does not appear to contain any data and cannot be removed by OSCAR Úgy tűnik, hogy a kiválasztott profil nem tartalmaz adatot és az OSCAR nem tudja eltávolítani If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Ha azért szeretné törölni, mert elfelejtette a jelszavát, először vagy új jelszót kell beállítania, vagy manuálisan törölni a profilt. You are about to destroy profile '<b>%1</b>'. Törölni készül a következő profilt: '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Gondolja át még egyszer, mert ez a művelet visszavonhatatlanul törli a profilját minden <b>biztonsági mentéssel</b> a következő helyen<br/> %2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Írja be a TÖRLÉS szót nagybetűkkel a megerősítéshez. DELETE TÖRLÉS Sorry Sajnáljuk You need to enter DELETE in capital letters. Be kell írnia a TÖRLÉS szót így, nagy betűkkel. There was an error deleting the profile directory, you need to manually remove it. Hiba történt a profil könyvtár törlése közben, csak manuálisan fogja tudni törölni. Profile '%1' was succesfully deleted A '%1' profil sikeresen törölve Bytes Bájt KB KB MB MB GB GB TB TB PB PB Summaries: Összegzések: Events: Események: Backups: Mentések: Hide disk usage information Lemezhasználati információk elrejtése Show disk usage information Lemezhasználati információk mutatása Name: %1, %2 Név: %1, %2 Phone: %1 Telefon: %1 Email: <a href='mailto:%1'>%1</a> E-mail: <a href='mailto:%1'>%1</a> Address: Cím: No profile information given Nincs profil információ megadva Profile: %1 Profil: %1 ProgressDialog Abort Megszakítás QObject No Data Nincs adat Events Események Duration Hossz (% %1 in events) (% %1 esemény) Jan Jan Feb Feb Mar Már Apr Ápr May Máj Jun Jún Jul Júl Aug Aug Sep Szep Oct Okt Nov Nov Dec Dec ft láb lb font oz uncia Kg Kg cmH2O cmH2O Med. Közép Min: %1 Min: %1 Min: Min: Max: Max: Max: %1 Max: %1 %1 (%2 days): %1 (%2 nap): %1 (%2 day): %1 (%2 nap): % in %1 % ebben: %1 Hours Óra Min %1 Min %1 Hours: %1 Óra: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 nap kevés használat, %2 nap nem használat, az összesen %3 napból (%4% felelt meg.) Hosz: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Rögzítések: %1 / %2 / %3 Hossz: %4 / %5 / %6 Leghosszabb: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Hossz: %3 Start: %2 Mask On Maszk fel Mask Off Maszk le %1 Length: %3 Start: %2 %1 Hossz: %3 Start: %2 TTIA: TTIA: TTIA: %1 TTIA: %1 Minutes Perc Seconds Másodperc h h m m s s ms ms Events/hr Esemény/ó Hz Hz bpm Ütés/perc Litres Liter ml ml Breaths/min Légvétel/perc ? ? Severity (0-1) Súlyosság (0-1) Degrees Fok Error Hiba Warning Figyelmeztetés Information Információ Busy Elfoglalt Please Note Jegyezze meg Graphs Switched Off Grafikonok kikapcsolva Sessions Switched Off Szakaszok kikapcsolva &Yes &Igen &No &Nem &Cancel &Mégsem &Destroy &megsemmisít &Save &Mentés BMI BMI Weight Súly Zombie Zombi Pulse Rate Pulzusszám Plethy Plethy Pressure Nyomás Daily Napi Profile Profil Overview Áttekintés Oximetry Oximetria Oximeter Oximéter Event Flags Esemény jelzők Default Alapértelmezett CPAP CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP Min EPAP Min EPAP Max EPAP Max EPAP IPAP IPAP Min IPAP Min IPAP Max IPAP Max IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Párásító H H OA OA A A CA CA FL FL SA SA LE LE EP EP VS VS VS2 VS2 RERA RERA PP PP P P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF1 UF2 UF2 UF3 UF3 PS PS AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI API PB PB IE IE Insp. Time Bel. idő Exp. Time Kilégzési idő Resp. Event Kil. idő Flow Limitation Áramlás limitáció Flow Limit Áramlás limit SensAwake SensAwake Pat. Trig. Breath Páciens légzése Tgt. Min. Vent Target Vent. Minute Vent. Tidal Volume Légzőtérfogat Resp. Rate Légzésszám Snore Horkolás Leak Szivárgás Leaks Szivárgások Large Leak Nagy szivárgás LL LL Total Leaks Összes szivárgás Unintentional Leaks Akaratlan szivárgások MaskPressure Maszk nyomás Flow Rate Áramlási ráta Sleep Stage Alvási fázis Usage Használat Sessions Szakaszok Pr. Relief No Data Available Nem áll adat rendelkezésre App key: Alkalmazás kulcs: Operating system: Operációs rendszer: Built with Qt %1 on %2 Fordítva a következőn: Qt %1 (%2) Graphics Engine: Grafikus motor: Graphics Engine type: Grafikus motor típus: Software Engine Szoftveres motor ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Asztali OpenGL m m cm cm in Only Settings and Compliance Data Available Csak beállítások és teljesítés adatok elérhetőek Summary Data Only Csak összegző adatok Bookmarks Könyvjelző Mode Mód Model Model Brand Márka Serial Gyári szám Series Széria Machine Gép Channel Csatorna Settings Beállítások Inclination Hajlam Orientation Orientáció Motion Mozgás Name Név DOB Szül. idő Phone Telefon Address Cím Email Email Patient ID Páciens ID Date Dátum Bedtime Lefekvés idő Wake-up Felébredés Mask Time Maszk-idő Unknown Ismeretlen None Semmi Ready Kész First Első Last Utolsó Start Indítás End Vége On Be Off Ki Yes Igen No Nem Min Min Max Max Med Közép Average Átlag Median Középérték Avg Átl W-Avg Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine Nem adat képes gép Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. Az ön %1 CPAP gépe (%2 model) még nem lett tesztelve. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. Sajnos az ön %1 CPAP gépe (%2) még nem támogatott. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Felkészülés... Machine Unsupported Gép nem támogatott I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Scanning Files... Fájlok keresése... Importing Sessions... Mérések betöltése... Finishing up... Befejezés... Machine Untested A Gép nem lett tesztelve Flex Lock Flex zár Whether Flex settings are available to you. Elérhetők-e ön számára a FLex beállítások. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Mask Resistance Setting Mask Resist. Maszk ellenáll. Hose Diam. Cső átmérő 15mm 15mm 22mm 22mm Backing Up Files... Fájlok biztonsági mentése... Untested Data Teszteletlen adat model %1 %1 model DreamStation 2 DreamStation 2 unknown model ismeretlen model CPAP-Check CPAP-ellenőrzés AutoCPAP AutoCPAP Auto-Trial AutoBiLevel AutoBiLevel S S S/T S/T S/T - AVAPS S/T - ACAPS PC - AVAPS PC - ACAPS Flex Mode Flex mód PRS1 pressure relief mode. PRS1 nyomáskönnyítés mód C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time Bi-Flex Bi-Flex Flex Flex Flex Level Flex szint PRS1 pressure relief setting. PRS1 nyomáskönnyítés beállítás. Passover Target Time PRS1 Humidifier Target Time Hum. Tgt Time Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Rámpa típus Type of ramp curve to use. Rámpagörbe választása. Linear Lineáris SmartRamp Okos rámpa Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Légzésszám Fixed Fix Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Minimum percenkénti légzésszám (BPM) ami alatt időzített légzés kezdeményeződik Breath BPM Légzésszám (BPM) Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Időzített belég. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start EZ-Start Whether or not EZ-Start is enabled Variable Breathing Változó légzés UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend Peak Flow Áramlási csúcs Peak flow during a 2-minute interval Humidifier Status Párásító státusz PRS1 humidifier connected? Disconnected Lecsatlakoztatva Connected Kapcsolódva Humidification Mode PRS1 Humidification Mode Humid. Mode Párásító mód Fixed (Classic) Fix (classic) Adaptive (System One) Heated Tube Fűtött cső Tube Temperature PRS1 Heated Tube Temperature Tube Temp. PRS1 Humidifier Setting PRS1 Párásító beállítás Hose Diameter Cső átmérő Diameter of primary CPAP hose 12mm 12mm Auto On Automatikus bekapcsolás A few breaths automatically starts machine Auto Off Automatikus kikapcsolás Machine automatically switches off Mask Alert Maszk figyelmeztetés Whether or not machine allows Mask checking. Show AHI AHI mutatása Breathing Not Detected A period during a session where the machine could not detect flow. BND BND Timed Breath Machine Initiated Breath TB TB Windows User Using , found SleepyHead - You must run the OSCAR Migration Tool Futtatnia kell az OSCAR migrációs eszközt <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> Launching Windows Explorer failed Could not find explorer.exe in path to launch Windows Explorer. OSCAR %1 needs to upgrade its database for %2 %3 %4 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> OSCAR does not yet have any automatic card backups stored for this device. This means you will need to import this machine data again afterwards from your own backups or data card. Important: Fontos: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Are you ready to upgrade, so you can run the new version of OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Machine Database Changes Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. The machine data folder needs to be removed manually. This folder currently resides at the following location: Rebuilding from %1 Backup Therapy Pressure Inspiratory Pressure Lower Inspiratory Pressure Higher Inspiratory Pressure Expiratory Pressure Kilégzési nyomás Lower Expiratory Pressure Higher Expiratory Pressure Pressure Support PS Min PS Min Pressure Support Minimum PS Max PS Max Pressure Support Maximum Min Pressure Minimum Therapy Pressure Max Pressure Maximum Therapy Pressure Ramp Time Ramp Delay Period Ramp Pressure Starting Ramp Pressure Ramp Event Ramp An abnormal period of Cheyne Stokes Respiration An apnea that couldn't be determined as Central or Obstructive. A restriction in breathing from normal, causing a flattening of the flow waveform. Vibratory Snore (VS2) A ResMed data item: Trigger Cycle Event Mask On Time Time started according to str.edf Summary Only An apnea where the airway is open An apnea caused by airway obstruction Hypopnea A partially obstructed airway Részlegesen elzáródott légút Unclassified Apnea UA UA Vibratory Snore A vibratory snore A vibratory snore as detcted by a System One machine Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A large mask leak affecting machine performance. Non Responding Event A type of respiratory event that won't respond to a pressure increase. Expiratory Puff Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. User Flag #1 User Flag #2 User Flag #3 Heart rate in beats per minute Blood-oxygen saturation percentage Vér-oxigén szaturáció (százalék) Plethysomogram An optical Photo-plethysomogram showing heart rhythm Pulse Change A sudden (user definable) change in heart rate SpO2 Drop SpO2 esés A sudden (user definable) drop in blood oxygen saturation SD SD Breathing flow rate waveform L/min Mask Pressure Amount of air displaced per breath Graph displaying snore volume Minute Ventilation Amount of air displaced per minute Respiratory Rate Rate of breaths per minute Patient Triggered Breaths Percentage of breaths triggered by patient Pat. Trig. Breaths Leak Rate Rate of detected mask leakage I:E Ratio Ratio between Inspiratory and Expiratory time ratio arány Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting Cheyne Stokes Respiration Cheyne Stokes légzés CSR CSR Periodic Breathing An abnormal period of Periodic Breathing Clear Airway Obstructive Obstruktív Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Leak Flag LF LF A user definable event detected by OSCAR's flow waveform processor. Perfusion Index A relative assessment of the pulse strength at the monitoring site Perf. Index % Mask Pressure (High frequency) Expiratory Time Time taken to breathe out Inspiratory Time Time taken to breathe in Respiratory Event Graph showing severity of flow limitations Flow Limit. Target Minute Ventilation Maximum Leak The maximum rate of mask leakage Max Leaks Apnea Hypopnea Index Apnoé Hypopnoé index Graph showing running AHI for the past hour Total Leak Rate Detected mask leakage including natural Mask leakages Median Leak Rate Median rate of detected mask leakage Median Leaks Respiratory Disturbance Index Graph showing running RDI for the past hour Sleep position in degrees Upright angle in degrees Movement Movement detector CPAP Session contains summary data only PAP Mode PAP mód Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Apnea An apnea reportred by your CPAP machine. PAP Device Mode APAP (Variable) APAP (Változó) ASV (Fixed EPAP) ASV (fix EPAP) ASV (Variable EPAP) ASV (változó EPAP) Height Magasság Physical Height Notes Jegyzetek Bookmark Notes Body Mass Index Testtömeg-index How you feel (0 = like crap, 10 = unstoppable) Bookmark Start Bookmark End Last Updated Journal Notes Journal Napló 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep Brain Wave Agyhullám BrainWave Agyhullám Awakenings Number of Awakenings Morning Feel How you felt in the morning Time Awake Ébren töltött idő Time spent awake Time In REM Sleep Time spent in REM Sleep Time in REM Sleep Time In Light Sleep Time spent in light sleep Time in Light Sleep Time In Deep Sleep Time spent in deep sleep Time in Deep Sleep Time to Sleep Time taken to get to sleep Zeo ZQ Zeo sleep quality measurement ZEO ZQ ZEO ZQ Debugging channel #1 Test #1 Teszt #1 For internal use only Csak belső használatra Debugging channel #2 Test #2 Teszt #2 Zero Nulla Upper Threshold Lower Threshold As you did not select a data folder, OSCAR will exit. or CANCEL to skip migration. Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. You cannot use this folder: Nem használhatja ezt a könyvtárat: Migrating files fájlok from to OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. We suggest you use this folder: Click Ok to accept this, or No if you want to use a different folder. Choose or create a new folder for OSCAR data Next time you run OSCAR, you will be asked again. Amikor legközelebb futtatja az OSCART újra meg lesz kérdezve. The folder you chose is not empty, nor does it already contain valid OSCAR data. Data directory: Adat könyvtár: Unable to create the OSCAR data folder at Nem sikerült az OSCAR adat könyvtárat létrehozni itt: Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Az OSCAR nem tud tovább futni és kilép. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! "%1" verzió érvénytelen, nem lehet folytatni! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Az Ön által futtatott OSCAR verzió (%1) régebbi mint ami ezeket az adatokat létrehozta (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? Question Kérdés Exiting Kilépés Are you sure you want to use this folder? Biztos ezt a könyvtárat szeretné használni? Don't forget to place your datacard back in your CPAP machine OSCAR Reminder OSCAR emlékeztető You can only work with one instance of an individual OSCAR profile at a time. Egyszerre csak egy példány lehet nyitva egy OSCAR profilból. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Ha a fájlokhoz felhőszolgáltatást használ, bizonyosodjon meg róla, hogy az OSCAR be van zárva és a szinkronizáció befejeződött a korábbi számítógépen mielőtt folytatja. Loading profile "%1"... "%1" profil betöltése... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Válasszon egy helyet a zip fájlnak (de ne a gép memóriakártyája legyen az)! Unable to create zip! Nem sikerült létrehozni a Zip fájlt! Are you sure you want to reset all your channel colors and settings to defaults? Are you sure you want to reset all your waveform channel colors and settings to defaults? There are no graphs visible to print Nincs rajzolható grafikon Would you like to show bookmarked areas in this report? Szeretné látni a könyvjelzőzött területeket ebben a riportban? Printing %1 Report %1 riport nyomtatása %1 Report %1 Riport : %1 hours, %2 minutes, %3 seconds : %1 óra, %2 perc, %3 másodperc RDI %1 RDI %1 AHI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 UAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 Entire Day's Flow Waveform Current Selection Jelenlegi kijelölés Entire Day Egész nap Page %1 of %2 %1 / %2 oldal Days: %1 %1 nap Low Usage Days: %1 (%1% compliant, defined as > %2 hours) (%1% megfelel, kritérium: > %2 óra) (Sess: %1) (Szakasz: %1) Bedtime: %1 Lefekvés: %1 Waketime: %1 Ébredés: %1 (Summary Only) (csak összegzés) There is a lockfile already present for this profile '%1', claimed on '%2'. Fixed Bi-Level Fix Bi-Level Auto Bi-Level (Fixed PS) Auto Bi-Level (Fix PS) Auto Bi-Level (Variable PS) Auto Bi-Level (Változó PS) 99.5% 99.5% varies n/a nem ismert Fixed %1 (%2) Fix %1 (%2) Min %1 Max %2 (%3) Min %1 Max %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Legutóbbi oximetria adat: <a onclick='alert("daily=%2");'>%1</a> (last night) (előző éjszaka) (1 day ago) (%2 days ago) No oximetry data has been imported yet. Nem lett még importálva oximetria adat. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings SmartFlex beállítások ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Somnopose szoftver Zeo Zeo Personal Sleep Coach Személyes alvástréner Database Outdated Please Rebuild CPAP Data Az adatbázis elavult Kérem építtesse újra a CPAP adatokat (%2 min, %3 sec) (%2 perc, %3 másodperc) (%3 sec) (%3 másodperc) Pop out Graph The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View Az Ön gépe nem rögzít adatokat a napi nézethez There is no data to graph Nincs adat grafikon rajzoláshoz d MMM yyyy [ %1 - %2 ] yyyy MMM d [ %1 - %2 ] Hide All Events Minden esemény elrejtése Show All Events Minden esemény mutatása Unpin %1 Graph Popout %1 Graph Pin %1 Graph Plots Disabled Grafikonok tiltva Duration %1:%2:%3 Hossz: %1:%2:%3 AHI %1 AHI %1 Relief: %1 könnyítés: %1 Hours: %1h, %2m, %3s Machine Information Gép információ Journal Data Napló adatok OSCAR found an old Journal folder, but it looks like it's been renamed: Az OSCAR talált egy régi naplófájlt, de úgy látszik át lett nevezve: OSCAR will not touch this folder, and will create a new one instead. Az OSCAR nem fog ehhez a könyvtárhoz nyúlni, hanem létrehoz egy újat helyette. Please be careful when playing in OSCAR's profile folders :-P Legyen óvatos ha az OSCAR profil könyvtárában dolgozik For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. OSCAR picked only the first one of these, and will use it in future: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Backing up files... Fájlok biztonsági mentése... Reading data files... SmartFlex Mode SmartFlex mód Intellipap pressure relief mode. Intellipap nyomás könnyítés mód. Ramp Only Csak rámpa Full Time Teljes idő SmartFlex Level SmartFlex szint Intellipap pressure relief level. Snoring event. SN Locating STR.edf File(s)... STR.edf fájl(ok) keresése... Cataloguing EDF Files... EDF fájlok katalogizálása... Queueing Import Tasks... Import feladatok sorba állítása... Finishing Up... Befejezés... CPAP Mode CPAP mód VPAPauto VPAPauto ASVAuto ASVAuto iVAPS iVAPS PAC Auto for Her Auto for Her EPR EPR ResMed Exhale Pressure Relief Patient??? Páciens??? EPR Level EPR szint Exhale Pressure Relief Level Kilégzési nyomáskönnyítés szintje Response Válasz Patient View Páciens nézet SmartStart SmartStart Machine auto starts by breathing A gép automatikusan indul ha légzést érzékel Smart Start Okos indítás Humid. Status Pára állapot Humidifier Enabled Status Humid. Level Páratart. Humidity Level Páratartalom Temperature Hőmérséklet ClimateLine Temperature ClimateLine hőmérséklet Temp. Enable Hőm. engedélyezése ClimateLine Temperature Enable Temperature Enable Hőmérséklet engedélyezése AB Filter AB szűrő Antibacterial Filter Antibakteriális szűrő Pt. Access Essentials Plus Climate Control Klíma kontrol Manual Manuális Soft Standard Általános BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Egyszerű Advanced Speciális Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... STR.edf rekordok feldolgozása... Auto Auto Mask Maszk ResMed Mask Setting ResMed maszk beállítás Pillows Párnák Full Face Teljes arc Nasal Orr Ramp Enable Rámpa engedélyezése Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Snapshot %1 Pillanatkép %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... Scanning Files Fájlok keresése Migrating Summary File Location Összegzőfájl költöztetése Loading Summaries.xml.gz Summaries.xml.gz betöltése Loading Summary Data Összegzési adatok betöltése Please Wait... Kérem várjon... Updating Statistics cache Statisztika gyorsítótár frissítése Usage Statistics Használati statisztika %1 Charts %1 of %2 Charts Loading summaries Összesítések betöltése Dreem Dreem Your Viatom device generated data that OSCAR has never seen before. A Viatom eszköze által generált adat amit az OSCAR még sosem látott korábban. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Az importált adatok nem biztos, hogy teljesen pontosak, ezért a fejlesztők szeretnének kérni egy másolatot a Viatom fájljaiból, hogy meggyőződjenek arról, hogy az OSCAR megfelelően kezeli őket. Viatom Viatom Viatom Software Viatom Software New versions file improperly formed A more recent version of OSCAR is available release kiadás test version teszt verzió You are running the latest %1 of OSCAR You are running OSCAR %1 Ön az OSCAR %1 verzóját futtatja OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 elérhető <a href='%2'>itt</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates OSCAR frissítések keresése Unable to check for updates. Please try again later. Nem sikerült ellenőrizni a firssítéseket. Próbálja meg később. SensAwake level Expiratory Relief Kilégzés könnyítés Expiratory Relief Level Humidity Páratartalom SleepStyle SleepStyle This page in other languages: Ez az oldal más nyelveken: %1 Graphs %1 Grafikonok %1 of %2 Graphs %1 - %2 Grafikonok %1 Event Types %1 Esemény típusok %1 of %2 Event Types %1 - %2 Esemény típusok Report about:blank about:blank SessionBar %1h %2m %1ó %2p No Sessions Present Szakaszok nem állnak rendelkezésre SleepStyleLoader Import Error Import hiba This Machine Record cannot be imported in this profile. Ezek az adatok nem importálhatók ebbe a profilba. The Day records overlap with already existing content. A napi adatok átfedésben vannak már meglévő adatokkal. Statistics CPAP Statistics CPAP Statisztika CPAP Usage CPAP használat Average Hours per Night Átlagos használati órák éjszakánként Therapy Efficacy Terápia hatékonyság Leak Statistics Szivárgás statisztikák Pressure Statistics Nyomás statisztikák Oximeter Statistics Oximéter statisztikák Blood Oxygen Saturation Véroxigén-szaturáció Pulse Rate Pulzusszám %1 Median %1 középérték Average %1 %1 átlag Min %1 Min %1 Max %1 Max %1 %1 Index %1 index % of time in %1 % idő ebben: %1 % of time above %1 threshold % idő a %1 fölött % of time below %1 threshold % idő %1 alatt Name: %1, %2 Név: %1, %2 DOB: %1 Szül. nap: %1 Phone: %1 Telefon: %1 Email: %1 E-mail: %1 Address: Cím: This report was prepared on %1 by OSCAR %2 Ez a riport %1 napon lett előállítva az OSCAR %2 segítségével Days Used: %1 %1 napot használva Low Use Days: %1 Túl rövid használatok: %1 nap Compliance: %1% Megfelelőség: %1% Days AHI of 5 or greater: %1 Napok száma amikor az AHI 5 vagy több volt: %1 Best AHI Legjobb AHI Date: %1 AHI: %2 Dátum: %1 AHI: %2 Worst AHI Legrosszabb AHI Best Flow Limitation Legjobb áramlás limitáció Date: %1 FL: %2 Dátum: %1 FL: %2 Worst Flow Limtation Legrosszabb légáramlás korlátozás No Flow Limitation on record Nincs áramlás korlátozva a felvételen Worst Large Leaks Legrosszabb nagy szivárgások Date: %1 Leak: %2% Dátum: %1 Szivárgás: %2% No Large Leaks on record Nem voltak nagy szivárgások rögzítve Worst CSR Legrosszabb CSR Date: %1 CSR: %2% Dátum: %1 CSR: %2% No CSR on record Nincs CSR rögzítve Worst PB Legrosszabb PB Date: %1 PB: %2% Dátum: %1 PB: %2% No PB on record Nincs PB rögzítve Want more information? Szeretne több információt? OSCAR needs all summary data loaded to calculate best/worst data for individual days. Az OSCARnak be kell tölteni az összes összegző adatot, hogy kiszámolhassa a legjobb/legrosszabb adatot minden egyes napra. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Kérem engedélyezze az összegzések előre betöltését a beállításokba, hogy ez az adat biztosan elérhető legyen. Best RX Setting Legjobb RX beállítás Date: %1 - %2 Dátum: %1 - %2 AHI: %1 AHI: %1 Total Hours: %1 Összes órák: %1 Worst RX Setting Legrosszabb RX beállítás Most Recent Legutóbbi Compliance (%1 hrs/day) Megfelelőség (%1 óra/nap) OSCAR is free open-source CPAP report software Az OSCAR ingyenes és nyílt forráskódú CPAP riport szoftver Changes to Machine Settings Változások a gép beállításaiban No data found?!? Nem található adat?!? Oscar has no data to report :( Az OSCAR-nak nincs adata riporthoz :( Last Week Előző hét Last 30 Days Utolsó 30 nap Last 6 Months Utolsó 6 hónap Last Year Előző év Last Session Utolsó mérés Details Részletek No %1 data available. Nem áll rendelkezésre %1 adat. %1 day of %2 Data on %3 %1 nap %2 adat %3-ig %1 days of %2 Data, between %3 and %4 %1 napnyi %2 adat %3 és %4 között Days Nap Pressure Relief Nyomás könnyítés Pressure Settings Nyomás beállítások Machine Information Gép információk First Use Első használat Last Use Utolsó használat Welcome Welcome to the Open Source CPAP Analysis Reporter Üdvözli a nyílt forráskódú CPAP analízis jelentő What would you like to do? Mit szeretne csinálni? CPAP Importer CPAP importáló Oximetry Wizard Oximetria varázsló Daily View Napi nézet Overview Összkép nézet Statistics Statisztika <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Figyelem: </span><span style=" color:#ff0000;">ResMed S9 SD kártyák zárolva kell hogy legyenek </span><span style=" font-weight:600; color:#ff0000;">mielőtt a számítógépbe helyezi.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Néhány operációs rendszer index fájlokat készít kérdés nélkül, ami miatt a kártya olvashatatlanná válik a CPAP készüléke számára.</span></p></body></html> It would be a good idea to check File->Preferences first, Jó ötlet lenne megnézni a fájl->beállítások menüpontot, as there are some options that affect import. mivel néhány beállítás befolyásolja az importálást. Note that some preferences are forced when a ResMed machine is detected Néhány beállítás zárolva van ha ResMed készülék van érzékelve First import can take a few minutes. Az első importálás eltarthat néhány percig. The last time you used your %1... A %1 gép utolsó használata... last night előző este %2 days ago %2 napja was %1 (on %2) %1 volt (ekkor: %2) %1 hours, %2 minutes and %3 seconds %1 órát, %2 percet és %3 másodpercet Your machine was on for %1. A készüléke %1 üzemelt. <font color = red>You only had the mask on for %1.</font> <font color = red>A maszkja csak %1.volt fent.</font> under alatta van over fölötte van reasonably close to megközelíti equal to megegyezik You had an AHI of %1, which is %2 your %3 day average of %4. Az AHI értéke %1, ami %2 az ön %3 napos átlagának ami %4. Your CPAP machine used a constant %1 %2 of air A CPAP készüléke konstans %1 %2 levegővel üzemelt Your pressure was under %1 %2 for %3% of the time. A nyomás %1 %2 alatt volt %3%-ban. Your machine used a constant %1-%2 %3 of air. Az ön készüléke konstans %1-%2 %3 levegőt használt. Your EPAP pressure fixed at %1 %2. Az EPAP nyomás fixen %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Az IPAP nyomás %1 %2 alatt volt %3%-ban. Your EPAP pressure was under %1 %2 for %3% of the time. Az EPAP nyomás %1 %2 alatt volt %3%-ban. Your machine was under %1-%2 %3 for %4% of the time. A készüléke %1-%2 %3 alatt volt %4%-ban. 1 day ago 1 nappal ezelőtt Your average leaks were %1 %2, which is %3 your %4 day average of %5. Az átlagos szivárgás %1 %2 volt, ami %3 az ön %4 napi átlagának, ami %5. No CPAP data has been imported yet. Még nincs CPAP adat importálva. gGraph %1 days %1 nap gGraphView 100% zoom level 100% nagyítás Restore X-axis zoom to 100% to view entire selected period. X tengely visszaállítása a teljes periódus mutatásához. Restore X-axis zoom to 100% to view entire day's data. X tengely visszaállítása 100%-ra az egész nap láthatóságához. Reset Graph Layout Grafikon elrendezés visszaállítása Resets all graphs to a uniform height and default order. Minden grafikon visszaállítása egységes magasságra és alapértelmezett sorrendbe. Y-Axis Y tengely Plots Ábrák CPAP Overlays CPAP rétegek Oximeter Overlays Oximéter rétegek Dotted Lines Pontozott vonalak Double click title to pin / unpin Click and drag to reorder graphs Dupla kattintás a rögzítés / feloldáshoz Kattintson és húzzon a sorrend módosításához Remove Clone Klón eltávolítása Clone %1 Graph %1 grafikon klónozása OSCAR-code-v1.3.1/Translations/Nederlands.nl.ts000066400000000000000000015053561417327530600212300ustar00rootroot00000000000000 AboutDialog &About &Over Release Notes Nieuws Credits Dank GPL License GPL Licentie Close Sluiten Show data folder Toon gegevensmap About OSCAR %1 Over OSCAR %1 Sorry, could not locate About file. Sorry, kan de pagina "Over" niet vinden. Sorry, could not locate Credits file. Sorry, kan de pagina "Dank" niet vinden. Sorry, could not locate Release Notes. Sorry, kan de pagina "Nieuws" niet vinden. OSCAR %1 OSCAR %1 Important: Belangrijk: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Dit is nog geen uiteindelijke versie, dus <b> maak een backup van de map OSCAR-Data</b> voordat U verder gaat. Dan kunt U terugvallen op een vorige versie. <b>U vindt de vertaling van onderstaande lijst in de wiki van OSCAR, zie hieronder</b>. To see if the license text is available in your language, see %1. Kijk bij %1 om te zien of een vertaling van de licentie in het Nederlands beschikbaar is. CMS50F37Loader Could not find the oximeter file: Kan het oxymeter bestand niet vinden: Could not open the oximeter file: Kan het oxymeter bestand niet openen: CMS50Loader Could not get data transmission from oximeter. Er kwam geen gegevensoverdracht van de oxymeter. Please ensure you select 'upload' from the oximeter devices menu. Kies eerst 'upload' in het menu van de oxymeter. Could not find the oximeter file: Kan het oxymeter bestand niet vinden: Could not open the oximeter file: Kan het oxymeter bestand niet openen: CheckUpdates Checking for newer OSCAR versions Controleren op nieuwe OSCAR versie Daily Go to the previous day Ga naar de vorige dag Show or hide the calender Kalender aan/uit zetten Go to the next day Ga naar de volgende dag Go to the most recent day with data records WJG: compacter Ga naar de meest recente dag met gegevens Events WJG: Zou 'Apneus' niet beter zijn dan Gebeurtenissen'? Want dat is wat hier geteld wordt en het past beter op de ruimte van het tabje. AK: Nee, er zijn ook andere gebeurtenissen: snurken, RERA, enz. Misschien 'evenementen' of 'incidenten'? Incidenten View Size Onder INCIDENTEN Misschien is 'Zoomniveau' beter? Beeldgrootte Notes WJG: Is compacter, past beter op tabje In verband met de koppeling met Bladwijzers, lijkt me 'Notities' beter. Notities Journal WJG: is gebruikelijker Dagboek Small Klein Medium Medium Big Groot Color Kleur u u B B Zombie Zombie I'm feeling ... Ik voel me ... Weight Gewicht If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Als in het dialoogvenster Voorkeuren de lengte is ingevuld, zorgt invullen van uw gewicht hier voor de berekening van de Body Mass Index (BMI) Awesome Fantastisch B.M.I. zonder puntjes? B.M.I. Bookmarks Bladwijzers Add Bookmark Bladwijzer toevoegen Starts WJG: er wordt een punt in de tijd mee aangegeven vanaf wanneer je opmerkingen wilt plaatsen Vanaf Remove Bookmark Bladwijzer verwijderen Flags Markeringen Graphs Grafieken Show/hide available graphs. Toon/verberg grafieken. Breakdown Niet gezien Verdeling events incidenten UF1 Letters in de cirkelgrafiek UF1 UF2 Letters in de cirkelgrafiek UF2 Time at Pressure Tijdsduur bij Druk No %1 events are recorded this day Er zijn vandaag geen %1 incidenten geweest %1 event %1 incident %1 events %1 incidenten Total time in apnea Totale Tijd in Apneu (TTiA) Time over leak redline Tijdsduur boven de leklimiet Event Breakdown Verdeling incidenten Sessions all off! Niet gevonden Alle sessies staan uit! Sessions exist for this day but are switched off. Er zijn wel sessies, maar die staan uit. Impossibly short session Onmogelijk korte sessie Zero hours?? Nul uren??? BRICK :( Arie: Als er niets uit komt is het echt fout Volgens mij zit er een foutje in deze string: dat eerste ( hoort er niet in dacht ik... Oh, dat is een smiley ;-) BAKSTEEN :( Complain to your Equipment Provider! Klaag bij uw leverancier! Statistics Statistieken Oximeter Information Oxymeterinformatie Session Start Times Starttijden Session End Times Stoptijden Duration Tijdsduur Position Sensor Sessions Sessies met positie-sensor Unknown Session Onbekende sessie Click to %1 this session. Klik om deze sessie %1 te zetten. disable uit enable aan %1 Session #%2 %1 Sessie #%2 %1h %2m %3s %1u %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Let op:</b> Alle onderstaande instellingen zijn gebaseerd op de aanname dat er niets is veranderd. PAP Mode: %1 Soort apparaat: %1 (Mode and Pressure settings missing; yesterday's shown.) (Modus- en drukinstellingen ontbreken; de laatst bekende worden weergegeven.) 99.5% 99,5 % Total ramp time Totale aanlooptijd Time outside of ramp Tijd na aanloop Start Start End Einde Unable to display Pie Chart on this system Kan op dit systeem het taartdiagram niet tonen 10 of 10 Event Types 10 van 10 soorten incidenten This CPAP machine does NOT record detailed data Dit apparaat registreert GEEN gedetailleerde gegevens Sorry, this machine only provides compliance data. Sorry, dit apparaat geeft uitsluitend gegevens over therapietrouw. "Nothing's here!" "Er is hier niets!" This bookmark is in a currently disabled area.. Deze bladwijzer staat in een uitgeschakeld gebied.. 10 of 10 Graphs 10 van 10 grafieken SpO2 Desaturations WJG: hoofdletter D? SpO2 desaturaties Pulse Change events AK: Oei! Bedoeld worden plotselinge, kortdurende wijzigingen in de polsslag. Maar hoe maak je dat kort? Hartritme incidenten SpO2 Baseline Used WJG: hoofdletter B? SpO2 basislijn gebruikt Machine Settings Apparaatinstellingen Details Details Session Information Sessie-informatie CPAP Sessions CPAP-sessies Oximetry Sessions Oxymetrie sessies Sleep Stage Sessions Slaapfase sessies Model %1 - %2 Model %1 - %2 This day just contains summary data, only limited information is available. Van deze dag zijn alleen overzichtsgegevens beschikbaar. No data is available for this day. Geen gegevens beschikbaar.voor deze dag. Pick a Colour Kies een kleur Bookmark at %1 Bladwijzer bij %1 ExportCSV Export as CSV Exporteer naar .csv bestand Dates: Periode: Resolution: Resolutie: Details Details Sessions Sessies Daily Dagrapport Filename: Bestandsnaam: Cancel Annuleren Export Exporteren Start: Start: End: Einde: Quick Range: Snelkeuze: Most Recent Day WJG: zie ook bij Daily Meest recente dag Last Week Afgelopen week Last Fortnight Afgelopen twee weken Last Month Afgelopen maand Last 6 Months Afgelopen halfjaar Last Year AK: Bij vorig jaar denk ik nu aan 2012. Bedoeld wordt een jaar voor gisteren.... Dat is toch afgelopen jaar? Afgelopen jaar Everything Alles Custom Zelf kiezen Details_ Details_ Sessions_ WJG: Engels is meervoud AK: Wat betekent het streepje erachter? Het zit in de bestandsnaam, het streepje is een spatie Sessies_ Summary_ Overzicht_ Select file to export to Kies exportbestand CSV Files (*.csv) bestandstype CSV bestanden (*.csv) DateTime Datum-Tijd Session Sessie Event Incident Data/Duration Gegevens/duur Date Datum Session Count Aantal sessies Start Start End Einde Total Time Totale tijdsduur AHI AHI Count Aantal FPIconLoader Import Error Importfout This Machine Record cannot be imported in this profile. Deze apparaatgegevens kunnen niet in dit profiel worden geimporteerd. The Day records overlap with already existing content. De gegevens van deze dag overlappen met bestaande gegevens. Help Hide this message Verberg dit bericht Search Topic: Zoek onderwerp: Help Files are not yet available for %1 and will display in %2. Help bestanden zijn niet beschikbaar voor %1 en zullen komen in %2. Help files do not appear to be present. Er zijn blijkbaar geen help bestanden. HelpEngine did not set up correctly Help is niet goed geinstalleerd HelpEngine could not register documentation correctly. Help kon de documentatie niet vinden. Contents Inhoud Index Index Search Zoeken No documentation available Geen documentatie beschikbaar Please wait a bit.. Indexing still in progress Even wachten, ben nog bezig No Nee %1 result(s) for "%2" %1 resultaten voor "%2" clear wissen MD300W1Loader Could not find the oximeter file: Kan het oxymeter bestand niet vinden: Could not open the oximeter file: Kan het oxymeter bestand niet openen: MainWindow &Statistics &Statistieken Report Mode Soort verslag Standard Standaard layout Monthly Maand layout Date Range Tijdspanne Statistics Statistieken Daily Dagrapport Overview Overzicht Oximetry Oxymetrie Import Importeren Help Over OSCAR &File WJG: Onderstreepte letter kan geen B zijn, is al gebruikt bij Bladwijzers AK: Dan zou ik het andersom doen: B&ladwijzers &Bestand &View &Weergave &Help &Help Troubleshooting Probleemoplossen &Data &Gegevens &Advanced Ge&avanceerd Purge Oximetry Data Wis oxymetrie gegevens Purge ALL Machine Data Wis ALLE apparaatgegevens Import &Dreem Data Importeer &Dreem gegevens Import &Viatom/Wellue Data Importeer &Viatom/Wellue gegevens Create zip of OSCAR diagnostic logs Maak een zip van OSCAR's diagnostische-gegevens Show Personal Data Persoonlijke gegevens zichtbaar op rapportages Check For &Updates &Update zoeken Purge Current Selected Day Wis de huidige geselecteerde dag &CPAP &Masker en apparaat &Oximetry &Oxymetrie &Sleep Stage &Slaapfase &Position &Positie &All except Notes &Alles behalve Notities All including &Notes Alles inclusief &Notities Show Daily view Toon dagrapport Show Overview view Toon overzichtpagina &Maximize Toggle &Schermvullend aan/uit Maximize window Maak venster schermvullend Reset Graph &Heights Herstel grafiek&hoogten Reset sizes of graphs Herstel grafiekhoogten Show Right Sidebar Toon rechter zijbalk Show Statistics view Toon statistiek pagina Show &Line Cursor Toon &lijncursor Show Daily Left Sidebar Toon linker zijbalk Show Daily Calendar Toon kalender Create zip of CPAP data card Maak een zip van CPAP-gegevenskaart Create zip of all OSCAR data Maak een zip van alle OSCAR-gegevens Report an Issue Meld een probleem System Information Systeem-informatie Show &Pie Chart Toon &Taartgrafiek Show Pie Chart on Daily page Toon de taartgrafiek op de linker zijbalk Standard graph order, good for CPAP, APAP, Bi-Level Standaard grafiekvolgorde, goed voor CPAP, APAP en BiPAP Advanced Speciaal Advanced graph order, good for ASV, AVAPS Speciale grafiekvolgorde voor ASV en AVAPS &Reset Graphs &Reset alle grafieken CSV Export Wizard Wizard bestandsexport Export for Review Export voor beoordeling Rebuild CPAP Data Herstel CPAP gegevens &Preferences WJG: i is al gebruikt bij Gegevens importeren &Voorkeuren &Profiles &Profielen Exit Afsluiten O&ximetry Wizard O&xymetrie wizard &Automatic Oximetry Cleanup &Automatisch opschonen van de oxymetrie-gegevens E&xit &Afsluiten View &Daily 20/9 WJG: aangepast na compilatie &Dagrapport View &Overview &Overzichtpagina View &Welcome WJG: Om de al gebruikte W te omzeilen AK: Waar staat dat Welkomst-/Startscherm??? 20/9 WJG: Goeie vraag, waarschijnlijk dateert dat nog uit een oudere versie en heeft Mark dat niet opgeruimd? &Welkomstscherm Use &AntiAliasing Gebruik &Anti-aliasing Show Debug Pane Foutopsporingsvenster Take &Screenshot &Schermopname maken Exp&ort Data Exp&orteer gegevens Daily Calendar Dagkalender Backup &Journal &Dagboek opslaan Show Performance Information Toon informatie over prestaties Profiles Profielen &Import CPAP Card Data &Importeer CPAP-kaart gegevens &About OSCAR &Over OSCAR Print &Report &Verslag afdrukken &Edit Profile Profiel &aanpassen Online Users &Guide Online &gebruiksaanwijzing &Frequently Asked Questions &FAQ Change &User Ander &profiel Purge &Current Selected Day Wis de &huidige geselecteerde dag Current Days Huidige dagen Right &Sidebar &Rechter zijbalk aan/uit Daily Sidebar Zijbalk dagrapport View S&tatistics Bekijk S&tatistiek Navigation Navigatie Records Gegevens View Statistics Bekijk Statistiek Import &Somnopose Data Importeer &SomnoPose gegevens Import &ZEO Data Importeer &ZEO gegevens Import RemStar &MSeries Data Importeer RemStar &M-series gegevens Sleep Disorder Terms &Glossary &Woordenlijst slaapaandoeningen Change &Language Wijzig &Taal Change &Data Folder Wijzig &Gegevensmap Importing Data Gegevens importeren Bookmarks Bladwijzers Welcome Welkom &About &Over Imported %1 CPAP session(s) from %2 %1 Sessies geimporteerd van %2 Import Success Import gelukt Already up to date with CPAP data at %1 Al bijgewerkt met gegevens van %1 Up to date Reeds bijgewerkt Couldn't find any valid Machine Data at %1 Kon geen geldige gegevens vinden op %1 Access to Import has been blocked while recalculations are in progress. Tijdens een herberekening kan niet geïmporteerd worden. Import Problem Import probleem CPAP Data Located CPAP gegevens gevonden Import Reminder Import herinnering Find your CPAP data card Zoek uw CPAP-gegevenskaart Please open a profile first. Open eerst een profiel. Check for updates not implemented Update controle is niet geimplementeerd Choose where to save screenshot Kies waar u een screenshot wilt opslaan Image files (*.png) Afbeeldingsbestanden (*.png) The User's Guide will open in your default browser De gebruikershandleiding wordt geopend in uw standaardbrowser Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Mits <i>U<b> zelf </b> backups gemaakt hebt van AL UW CPAP gegevens </i>, kunt U dit nog steeds afronden, maar U zult deze back-ups handmatig moeten terugzetten. Are you really sure you want to do this? Weet U echt zeker dat U dit wilt? Note as a precaution, the backup folder will be left in place. Ter geruststelling: de backup map blijft intakt. OSCAR does not have any backups for this machine! OSCAR heeft helemaal.geen backups voor dit apparaat! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Tenzij je <i>je <b>eigen</b> backups het gemaakt van ALLE gegevens van dit apparaat</i>, <font size=+2>zul je de gegevens van dit apparaat<b>blijvend</b> kwijtraken!</font> Are you <b>absolutely sure</b> you want to proceed? Weet U <b>absoluut zeker</b> dat U wilt doorgaan? The Glossary will open in your default browser De woordenlijst wordt geopend in uw standaardbrowser %1's Journal %1's dagboek Choose where to save journal Kies waar het dagboek moet worden opgeslagen XML Files (*.xml) XML bestanden (*.xml) Because there are no internal backups to rebuild from, you will have to restore from your own. Aangezien er geen interne backups zijn om uit te herstellen, moet je dat uit je eigen backups doen. %1 (Profile: %2) %1 (Profiel: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Vergeet niet om de hoofdmap of stationsletter van uw gegevenskaart te selecteren en niet een map erin. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) WilT U nu importeren vanuit uw eigen back-ups? (U heeft geen zichtbare gegevens voor dit apparaat totdat U dit doet) You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> U staat op het punt om alle gegevens te <font size=+2>vernietigen</font> van het volgende apparaat:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: Het samenstellen is mislukt, U moet zelf de volgende map wissen: No help is available. Er is geen help bestand. Are you sure you want to delete oximetry data for %1 Weet U zeker dat U de oxymetrie-gegevens van %1 wilt wissen <b>Please be aware you can not undo this operation!</b> <b>Dit kan niet ongedaan worden gemaakt!</b> Select the day with valid oximetry data in daily view first. Selecteer eerst de dag met geldige oxymetrie-gegevens in het dagrapport. Access to Preferences has been blocked until recalculation completes. Toegang tot Voorkeuren is geblokkeerd gedurende een herberekening. Please wait, importing from backup folder(s)... Even wachten, importeren vanuit de backup-map(pen)... Help Browser Handleiding Loading profile "%1" Profiel "%1" laden Please insert your CPAP data card... Plaats uw cpap gegevenskaart... Choose a folder Kies een gegevensmap No profile has been selected for Import. Er is nog geen profiel geselcteerd om te importeren. Import is already running in the background. Op de achtergrond draait al een import. A %1 file structure for a %2 was located at: Een %1 bestandsstructuur voor een %2 is gevonden op: A %1 file structure was located at: Een %1 bestandsstructuur is gevonden op: Would you like to import from this location? Wilt U vanaf deze lokatie importeren? Specify Specificeren OSCAR Information Informatie over OSCAR Please note, that this could result in loss of data if OSCAR's backups have been disabled. Houd er rekening mee, dat dit kan leiden tot verlies van gegevens indien de interne back-ups van OSCAR op enige manier zijn uitgeschakeld of verstoord. The FAQ is not yet implemented de FAQ is nog niet geimplementeerd If you can read this, the restart command didn't work. You will have to do it yourself manually. Als U dit kunt lezen, heeft het herstartcommando niet gewerkt. U zult het handmatig moeten doen. Are you sure you want to rebuild all CPAP data for the following machine: Weet U zeker dat U alle CPAP gegevens voor het volgende apparaat wilt herstellen: For some reason, OSCAR does not have any backups for the following machine: Om een ​​of andere reden heeft OSCAR geen back-ups voor het volgende apparaat: There was a problem opening %1 Data File: %2 Er was een probleem met het openen van het %1 gegevensbestand: %2 %1 Data Import of %2 file(s) complete Import van %2 gegevensbestand(en) van %1 voltooid %1 Import Partial Success Import van %1 gegevens deels gelukt %1 Data Import complete Import van %1 gegevens voltooid Export review is not yet implemented Exporteren is nog niet geimplementeerd Would you like to zip this card? Wilt u van deze kaart een zip bestand maken? Choose where to save zip Kies waar het bestand moet worden opgeslagen ZIP files (*.zip) ZIP bestanden (*.zip) Creating zip... Maakt een zip bestand... Calculating size... Grootte berekenen... Reporting issues is not yet implemented Melden van problemen is nog niet geimplementeerd There was an error saving screenshot to file "%1" Er is iets fout gegaan bij het opslaan van een beeldschermafdruk naar het bestand "%1" Screenshot saved to file "%1" Schermafbeelding bewaard als bestand "%1" There was a problem opening MSeries block File: Er was een probleem bij het openen van het M-Series blokbestand: MSeries Import complete Import M-Series voltooid MinMaxWidget Auto-Fit Automatisch Defaults Standaard Override Instellen The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Instelling y-as: 'Automatisch' om alles te zien, 'Standaard' voor fabrieksinstelling en 'Instellen' om zelf te kiezen. The Minimum Y-Axis value.. Note this can be a negative number if you wish. De minimale waarde. Dit mag negatief zijn als U wilt. The Maximum Y-Axis value.. Must be greater than Minimum to work. De maximale waarde. Deze moet groter zijn dan de minimale waarde. Scaling Mode Schaalinstelling This button resets the Min and Max to match the Auto-Fit Deze knop reset de min en max waarden naar Automatisch NewProfile Edit User Profile Gebruikersprofiel aanpassen I agree to all the conditions above. Ik ga akkoord met alle bovengenoemde voorwaarden. User Information Gebruikersinformatie User Name Naam gebruiker Password Protect Profile Wachtwoordbeveiliging van het profiel Password Wachtwoord ...twice... ... nog eens ... Locale Settings Landinstellingen Country Land TimeZone Tijdzone about:blank about:blank Very weak password protection and not recommended if security is required. Zeer zwakke wachtwoordbeveiliging en niet aan te raden als beveiliging nodig is. DST Zone Automatische zomertijd Personal Information (for reports) Persoonlijke informatie (voor verslagen) First Name Voornaam Last Name Achternaam It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Het is OK om hier geen of een foute leeftijd in te vullen, maar deze is nodig om meer nauwkeurige berekeningen te kunnen doen. D.O.B. Geb.dat. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Het biologische (geboorte-)geslacht is soms nodig voor de nauwkeurigheid van berekeningen, U mag het best leeg laten of overslaan.</p></body></html> Gender Geslacht Male Man Female Vrouw Height Lengte Metric Metrisch English Engels Contact Information Contactinformatie Address Adres Email E-mail Phone Telefoon CPAP Treatment Information Informatie over de behandeling Date Diagnosed Datum diagnose Untreated AHI Onbehandelde AHI CPAP Mode WJG: klopt dit wel, want Bi-level en APAP zijn in feite geen CPAP-soorten, toch? Ik geef maar wat alternatieven, ook spreekt het wel voor zich en zou je ook 'Soort CPAP' kunnen laten staan. 20/9 WJG: Soort apparaat lijkt me prima! Soort apparaat CPAP CPAP APAP APAP Bi-Level Bi-level ASV Adaptieve ventilatie-instelling ASI? ASV RX Pressure Voorgeschreven druk Doctors / Clinic Information Specialist/ziekenhuis Doctors Name Specialist Practice Name WJG: zou dit niet bedoeld worden? Bij het adres wordt wel duidelijk welk ziekenhuis het is. Bij mij is de behandeling bij 'Longziekten', misschien heet dat bij een ander ziekenhuis anders? Afdeling Patient ID Patient-ID &Cancel &Annuleren &Back &Terug &Next &Volgende Select Country Kies land This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Deze software is ontworpen om U te helpen bij het analyseren van de gegevens van uw CPAP. PLEASE READ CAREFULLY GRAAG AANDACHTIG LEZEN Accuracy of any data displayed is not and can not be guaranteed. De betrouwbaarheid van de weergegeven gegevens wordt en kan nooit worden gegarandeerd. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Alle verslagen zijn voor EIGEN GEBRUIK en kunnen ONDER GEEN VOORWAARDE worden toegepast voor medische diagnostiek. Use of this software is entirely at your own risk. Het gebruik van deze software is geheel voor eigen risico. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team Het auteursrecht van OSCAR &copy;2011-2018 berust bij Mark Watkins en delen &copy;2019-2022 bij The OSCAR Team Welcome to the Open Source CPAP Analysis Reporter Welkom bij dè Open Source CPAP-Analyse Rapporteur OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR is vrijgegeven onder de <a href='qrc:/COPYING'> GNU Public License v3</ a>, en wordt geleverd zonder garantie en zonder enige aanspraak op geschiktheid voor enig doel. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR is voornamelijk bedoeld om gegevens te bekijken en absoluut geen vervanging voor de medische begeleiding door uw arts. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. De auteurs accepteren GEEN ENKELE AANSPRAKELIJKHEID voor schade, in welke vorm ook, door het gebruik of misbruik van deze software. Please provide a username for this profile Geef een gebruikersnaam voor dit profiel Passwords don't match Wachtwoorden komen niet overeen Profile Changes Profielwijzigingen Accept and save this information? Opslaan? &Finish &Einde &Close this window &Sluit dit venster Overview Range: Bereik: Last Week Afgelopen week Last Two Weeks Afgelopen twee weken Last Month Afgelopen maand Last Two Months Afgelopen twee maanden Last Three Months Afgelopen drie maanden Last 6 Months Afgelopen halfjaar Last Year Afgelopen jaar Everything Alles Custom Zelf kiezen Snapshot Momentopname Start: Start: End: Einde: Reset view to selected date range WJG: is wat preciezer, als het past Herstel naar geselecteerd datumbereik Toggle Graph Visibility Grafieken aan/uit Drop down to see list of graphs to switch on/off. Lijst met grafieken om aan/uit te zetten. Graphs Grafieken Apnea Hypopnea Index Apneu Hypopneu Index (AHI) Usage Gebruik Usage (hours) Gebruik (uren) Total Time in Apnea Totale tijd in apneu Total Time in Apnea (Minutes) Totale tijd in apneu (minuten) Session Times Sessietijden Respiratory Disturbance Index Ademhalings Verstorings Index (RDI) Body Mass Index Body Mass Index (BMI) How you felt (0-10) Hoe U zich voelde (0-10) 10 of 10 Charts 10 van 10 grafieken Show all graphs Alle grafieken zichtbaar Hide all graphs Verberg alle grafieken OximeterImport Oximeter Import Wizard Oxymeter import wizard Skip this page next time. Sla deze pagina volgende keer over. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Let op: </span><span style=" font-style:italic;">Kies eerst het juiste type van uw oxymeter uit onderstaand pull-down menu.</span></p></body></html> Where would you like to import from? Waar wilt u vandaan importeren? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">Kies EERST uw oxymeter uit deze groepen:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. CMS50E/F gebruikers die direct importeren moeten de upload pas starten als OSCAR dat aangeeft. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Indien ingeschakeld, zal OSCAR de interne klok van je CMS50 instellen op de tijd van uw computer.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>Hier kunt u met 7 karakters een naam voor deze oxymeter geven.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Deze optie wist de geïmporteerde sessie van uw oxymeter na importeren. </p><p>Wees voorzichtig, want als er iets fout gaat vóór opslaan kunt u het niet terugkrijgen.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Deze optie maakt importeren mogelijk (met USB kabel) van de gegevens.</p><p>Na selectie van deze optie moet u bij oudere Contec oxymeters de upload vanaf het apparaat starten.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Als U er geen probleem mee hebt om de hele nacht aan de computer te hangen, geeft deze optie de mogelijkheid van een live "plethysomogram"-grafiek, met indicatie van het hartritme en natuurlijk de normale zuurstofsaturatiegrafiek.</p></body></html> Record attached to computer overnight (provides plethysomogram) Opname direct naar de computer (geeft plethysomogram) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Deze optie laat gegevensbestanden importeren, die door het programma van de oxymeter zijn gemaakt, zoals SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Importeren van gegevensbestanden van een ander programma, zoals SpO2Review Please connect your oximeter device Sluit uw oxymeter aan Please connect your oximeter device, turn it on, and enter the menu Sluit uw oxymeter aan, schakel hem in en ga naar het menu Press Start to commence recording Druk op Start om de opname te beginnen Show Live Graphs Live grafieken laten zien Duration Tijdsduur Pulse Rate Hartritme Multiple Sessions Detected Meerdere sessies gevonden Start Time Starttijd Details Details Import Completed. When did the recording start? Import gelukt. Wanneer is de opname gestart? Oximeter Starting time Oxymeter starttijd I want to use the time reported by my oximeter's built in clock. Ik wil de tijd van de klok van de oxymeter gebruiken. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Ik startte de oxymeter (ongeveer) tegelijk met de CPAP. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Let op: Beide apparaten tegelijk starten is altijd nauwkeuriger.</p></body></html> Choose CPAP session to sync to: Kies de CPAP-sessie waarmee moet worden gesynchroniseerd: You can manually adjust the time here if required: U kunt hier eventueel de tijd zelf aanpassen: HH:mm:ssap uu:mm:ss AP &Cancel &Annuleren &Information Page &Informatiepagina CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Zet datum en tijd van uw apparaat gelijk <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Sla bij de volgende import de identificatie van uw apparaat op. Dat is handig als U meerdere oxymeters hebt.</p></body></html> Set device identifier Sla identificatie op Erase session after successful upload Wis sessie na succesvol importeren Import directly from a recording on a device Importeer direct een opname uit een apparaat <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Herinnering voor CPAP gebruikers: </span><span style=" color:#fb0000;">Bent U niet vergeten om eerst uw CPAP sessies te importeren?<br/></span>Als U dat vergeet, heeft U geen geldig tijdstip om de oxymetrie sessie mee te synchroniseren.<br/>Om een goede synchronisatie te waarborgen, kunt U het beste proberen om beide apparaten tegelijk te starten.</p></body></html> If you can read this, you likely have your oximeter type set wrong in preferences. Als U dit leest, heeft U waarschijnlijk in Voorkeuren het verkeerde type oxymeter ingesteld. Please choose which one you want to import into OSCAR Kies welke U in OSCAR wilt importeren Day recording (normally would have) started De dag waarop de opname (normaal gesproken) gestart is <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR heeft een starttijd nodig om de sessie op te kunnen slaan.</p><p>Kies een van de volgende opties:</p></body></html> &Retry &Opnieuw &Choose Session &Kies sessie &End Recording &Einde opname &Sync and Save &Sync en sla op &Save and Finish &Opslaan en afsluiten &Start &Start Scanning for compatible oximeters Zoeken naar een compatibele oxymeter Could not detect any connected oximeter devices. Kon geen enkele oxymeter vinden. Connecting to %1 Oximeter Verbinden met de oxymeter %1 Renaming this oximeter from '%1' to '%2' Wijzig de naam van deze oxymeter van '%1' naar '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. De naam van de oxymeter is anders dan verwacht. Als U er maar een hebt die U gebruikt met meerdere profielen, zorg dat steeds dezelfde naam wordt gebruikt. "%1", session %2 "%1", sessie %2 Nothing to import Niets te importeren Your oximeter did not have any valid sessions. Er staan geen geldige sessies op deze oxymeter. Close Sluiten Waiting for %1 to start Wacht op starten van %1 Waiting for the device to start the upload process... Wacht tot het apparaat gaat verzenden... Select upload option on %1 Kies 'upload' op de %1 %1 device is uploading data... Oxymeter %1 stuurt gegevens... Please wait until oximeter upload process completes. Do not unplug your oximeter. Even wachten tot de gegevensoverdracht klaar is. Houd de oxymeter aangesloten. Oximeter import completed.. Import geslaagd.. Select a valid oximetry data file Kies een geldig gegevensbestand Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Oxymetrie bestanden (*.spo, *.spor, *.spo2 *.dat) No Oximetry module could parse the given file: Er was geen oxymeter die het opgegeven bestand kon lezen: Live Oximetry Mode Directe oxymetrie-aansluiting Live Oximetry Stopped Directe oxymetrie gestopt Live Oximetry import has been stopped Directe oxymetrie-import is beeindigd Oximeter Session %1 Oxymetrie sessie %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR maakt het mogelijk om de gegevens van de oxymeter te vergelijken met die van de CPAP. Daarmee kunt U waardevol inzicht krijgen in de effectiviteit van de CPAP behandeling. Het werkt ook met alleen de oxymeter, U kunt gegevens opslaan, volgen en bekijken. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR is momenteel compatibel met oxymeters van Contec, type CMS50D+, CMS50E, CMS50F en CMS50I<br/>(Let op: Importeren vanuit bluetooth modellen is <span style=" font-weight:600;">waarschijnlijk nog niet</span> mogelijk) If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Als U oxymetrie en CPAP gegevens wilt synchroniseren, moet U EERST uw CPAP gegevens importeren voordat U verder gaat! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Zorg dat U de juiste drivers hebt geinstalleerd (zoals USB naar serieel UART) om uw oxymeter te kunnen uitlezen. Voor meer informatie hierover, %1klik hier%2. You need to tell your oximeter to begin sending data to the computer. U moet upload op de oxymeter starten zodat gegevens naar de computer worden gezonden. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Sluit uw oxymeter aan, ga naar het menu en selecteer upload om gegevensoverdracht te starten... Oximeter not detected Geen oxymeter gedetecteerd Couldn't access oximeter Kon geen oxymeter benaderen Starting up... Opstarten... If you can still read this after a few seconds, cancel and try again Als U dit na enkele seconden nog ziet, druk dan op 'cancel' en probeer het opnieuw Live Import Stopped Directe import beeindigd %1 session(s) on %2, starting at %3 %1 sessie(s) op %2, beginnend met %3 No CPAP data available on %1 Geen CPAP gegevens beschikbaar op %1 Recording... Opnemen... Finger not detected Geen vinger gedetecteerd I want to use the time my computer recorded for this live oximetry session. Ik wil de tijd van mijn computer gebruiken voor deze directe oxymetrie-sessie. I need to set the time manually, because my oximeter doesn't have an internal clock. Ik moet de tijd zelf instellen, want mijn oxymeter heeft geen klok. Something went wrong getting session data Er ging iets fout bij het ophalen van sessie-gegevens Welcome to the Oximeter Import Wizard Welkom bij de Oxymeter import wizard Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Pols oxymeters zijn medische apparaten die worden gebruikt om de zuurstofsaturatie in het bloed te meten. Gedurende langere apneus en bij abnormaal ademhalen kan de zuurstofsaturatie flink zakken. Dit kan een indicatie zijn voor noodzakelijke medische hulp. You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Andere bedrijven, zoals Pulox plakken hun eigen naam op de Contec CMS50, zoals de Pulox PO-200, PO-300, PO-400. Deze zouden ook met dit programma moeten werken. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Ook de .dat bestanden van de ChoiceMMed MD300W1 oxymeter kunnen worden ingelezen. Please remember: Niet vergeten: Important Notes: Belangrijke opmerkingen: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. De apparaten van Contec CMS50D hebben geen interne klok en slaan dus ook de starttijd niet op. Als U geen CPAP gegevens hebt om de opname mee te verbinden, moet U zelf de starttijd ingeven nadat het importeren is voltooid. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Zelfs voor apparaten met een interne klok wordt ook aangeraden om er een gewoonte van te maken om beide apparaten tegelijk te starten. Ook omdat de klok van de CPAP langzaam verloopt en deze niet makkelijk kan worden gelijkgezet. Oximetry Date Datum d/MM/yy h:mm:ss AP dd/MM/jj uu:mm:ss AP R&eset R&eset Pulse Pols &Open .spo/R File &Open SpO/R gegevensbestand Serial &Import Seriële &import &Start Live &Start live Serial Port Seriële poort &Rescan Ports &Herscan poorten PreferencesDialog Preferences Voorkeuren &Import &Importeer Combine Close Sessions Combineer nabije sessies Minutes Minuten Multiple sessions closer together than this value will be kept on the same day. Tijd tussen sessies die nog tot dezelfde dag gerekend moeten worden. Ignore Short Sessions Negeer korte sessies <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Kortere sessies worden niet weergegeven<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Volgende dag start om Sessions starting before this time will go to the previous calendar day. Sessies die voor dit tijdstip gestart werden, horen bij de vorige dag. Session Storage Options Opties voor sessie-opslag Compress SD Card Backups (slower first import, but makes backups smaller) Comprimeer SD-kaart back-ups (langzamere eerste import, maar minder opslagruimte nodig) &CPAP &Masker en apparaat Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. WJG: bij gebrek aan beter, maar 'niet-compliant' en 'compliant' zijn geen termen die je in Van Dale tegenkomt Als ze het maar begrijpen, klachten mogen Beschouw dagen met minder gebruik als "niet-therapietrouw". 4 uur wordt meestal als "therapietrouw" beschouwd . hours uren Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Zet experimentele incidentmarkeringen aan of uit. Dit detecteert incidenten 'op het randje' en door het apparaat gemiste incidenten. Deze optie moet worden aangezet vóór het importeren, anders eerst alles wissen. Flow Restriction AK: Inderdaad, afsluiting is 0%, hier kan je kiezen, Debietreductie Debietbeperking Stroombeperking Debietbegrenzing Doorstroombeperking Luchtstroombeperking Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Percentage van de vermindering van de luchtstroom ten opzichte van de mediane waarde. Een waarde van 20% werkt goed voor het opsporen van apneus. <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Ware maximum is het hoogste punt van de gegevens.</p><p>De 99e percentiel filtert de uitschieters uit.</p></body></html> Combined Count divided by Total Hours Totaal aantal gedeeld door aantal uren Time Weighted average of Indice Tijdgewogen gemiddelde van de index Standard average of indice Gewoon gemiddelde van de index Custom CPAP User Event Flagging Aangepaste gebruikers markering <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Aangepast markeren is een experimentele werkwijze voor het detecteren van incidenten die zijn gemist door het apparaat. Ze worden <span style=" text-decoration: underline;">niet </ span> opgenomen in de AHI.</p></body></html> Duration of airflow restriction 20/9 WJG: Vanaf hier weer verder gegaan Duur van de luchtstroombeperking s s Event Duration Tijdsduur incident Allow duplicates near machine events. 20/9 WJG: Maar ik kan deze tekst niet terugvinden op het tabblad CPAP van Preferences AK: inderdaad, vreemd Sta duplicaten toe vlak naast apparaat-incidenten. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Regelt de hoeveelheid gegevens die worden beschouwd voor elk punt in de grafiek AHI/uur. Staat standaard op 60 minuten. Sterk aanbevolen het op deze waarde te laten staan, anders is het geen AHI/uur meer. minutes min Reset the counter to zero at beginning of each (time) window. Zet de teller op nul aan het begin van elke periode. Zero Reset Telkens op nul zetten CPAP Clock Drift Correctie afwijking klok CPAP Do not import sessions older than: Importeer geen sessies ouder dan: Sessions older than this date will not be imported Sessies ouder dan deze datum worden niet geimporteerd dd MMMM yyyy dd MMMM yyyy Show in Event Breakdown Piechart Laat zien in de incidenten grafiek Resync Machine Detected Events (Experimental) Synchroniseer de door het apparaat gedetecteerde incidenten opnieuw (experimenteel) User definable threshold considered large leak Instelbare grens voor 'overmatige' lekkage (meestal 24 l/min) Whether to show the leak redline in the leak graph Of U de rode lijn in de lekgrafiek wilt zien Search Zoeken &Oximetry &Oxymetrie Line Thickness Lijndikte The pixel thickness of line plots Pixelgrootte van lijngrafieken Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. "Pixmap caching" is een grafische versnellingstechniek. Kan problemen geven bij sommige teksten in de grafische omgeving. <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Deze instellingen zijn tijdelijk uitgeschakeld. Ze komen later terug.</p></body></html> Percentage drop in oxygen saturation 20/9 WJG: Zuurstof wellicht niet echt nodig? Percentage daling van zuurstofsaturatie Pulse 209/ WJG: Als 't past Hartritme Sudden change in Pulse Rate of at least this amount Plotselinge verandering in het hartritme van tenminste deze hoeveelheid bpm 20/9 WJG: slagen per minuut per minuut Minimum duration of drop in oxygen saturation Minimale duur van de verlaging Minimum duration of pulse change event. Minimale duur van de verandering van het hartritme. Small chunks of oximetry data under this amount will be discarded. Kortdurende oxymetrie-incidenten worden verwaarloosd. &General &Algemeen General Settings Algemene instellingen Daily view navigation buttons will skip over days without data records De navigatieknoppen slaan de dagen zonder gegevens over Skip over Empty Days Sla lege dagen over Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Gebruik meerdere CPU-cores voor betere prestaties. Werkt vooral bij importeren. Enable Multithreading Multithreading inschakelen Bypass the login screen and load the most recent User Profile Sla het inlogscherm over en laad het meest recente gebruikersprofiel Changes to the following settings needs a restart, but not a recalc. Wijzigingen in de volgende instellingen werken pas na een herstart, maar er is geen herberekening nodig. Create SD Card Backups during Import (Turn this off at your own peril!) Maak tijdens importeren een backup van de SD-kaart (Uitschakelen op eigen risico!) Reset &Defaults &Standaardinstellingen herstellen <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Waarschuwing: </span>Hoewel het mogelijk is, betekent dit nog niet dat het een goede keuze is </p></body></html> Preferred Calculation Methods Voorkeur berekeningsmethoden Middle Calculations Gemiddelden Upper Percentile Bovenste percentiel Session Splitting Settings Instelling splitsen van sessies Don't Split Summary Days (Warning: read the tooltip!) Dagen met alleen overzichtgegevens niet splitsen (Waarschuwing: lees de knopinfo!) Memory and Startup Options Geheugen en startopties Pre-Load all summary data at startup Laad bij het starten alle overzichtgegevens <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Deze instelling houdt de gegevens van golfvorm en gebeurtenissen in het geheugen, zodat het bij terugkeer naar dezelfde dag sneller gaat.</p><p>Het is niet echt noodzakelijk, want de computer doet dit zelf ook al.</p><p>Aanbevolen wordt dus om het uit te laten, tenzij U gigantisch veel geheugen hebt.</p></body></html> Keep Waveform/Event data in memory Houd gegevens van golfvormen en gebeurtenissen in het geheugen <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Voorkomt onnodige vragen tijdens het importeren.</p></body></html> Import without asking for confirmation Importeer zonder bevestiging te vragen General CPAP and Related Settings Algemene instellingen omtrent de CPAP Enable Unknown Events Channels Zet de kanalen van onbekende gebeurtenissen aan AHI Apnea Hypopnea Index Keuze tussen AHI en RDI AHI RDI Respiratory Disturbance Index RDI AHI/Hour Graph Time Window AHI/uur grafiek tijdvenster Preferred major event index Voorkeur index Compliance defined as Therapietrouw bij Flag leaks over threshold Aangeven lek-limiet Seconds seconden <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Let op: Dit is niet bedoeld om de tijdzone te corrigeren! Zorg dat de klok en tijdzone van de computer goed staan.</p></body></html> Hours Uren For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. 20/9 WJG: koppelteken en extra woorje Voor consistentie moeten ResMed-gebruikers hier 95% instellen, want dit is de enige waarde die beschikbaar is op de dagen met alleen een samenvatting. Median is recommended for ResMed users. Mediaan wordt aanbevolen voor ResMed-gebruikers. Median Mediaan Weighted Average Gewogen gemiddelde Normal Average Normaal gemiddelde Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Als U problemen hebt met grafieken, probeer dan een andere dan de standaard instelling (Desktop Open GL). Fonts (Application wide settings) Lettertype (geldt voor hele programma) True Maximum Ware maximum 99% Percentile 99% percentiel Maximum Calcs Berekening maximum Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Comprimeer ResMed (EDF) back-ups om schijfruimte te besparen. Back-ups van EDF bestanden worden opgeslagen in het gz-formaat, dat veel gebruikt wordt op Mac & Linux-systemen. OSCAR kan hier rechtstreeks uit importeren, maar voor ResScan moeten de .gz-bestanden eerst uitgepakt worden. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. De volgende opties hebben effect op de gebruikte schijfruimte en op de snelheid van importeren. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. De grootte van de opslag wordt gehalveerd, maar maakt het importeren en verwerken trager. Als U een nieuwe computer met SSD hebt, is dit een goede keuze. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Comprimeer sessiegegevens (minder opslagruimte, maar tragere verwerking.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Dit zorgt voor een back-up van de SD-kaart voor ResMed-apparaten, ResMed apparaten verwijderen hoge resolutie-gegevens ouder dan 7 dagen, en grafiekgegevens die ouder zijn dan 30 dagen.. OSCAR kan een kopie van deze gegevens bewaren voor na een herinstallatie. (Sterk aanbevolen, tenzij U weinig schijfruimte hebt of niets om grafiekgegevens geeft) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Maakt het opstarten van OSCAR een beetje trager door vooraf alle overzichtgegevens te laden, maar verder loopt het programma daardoor wel sneller. </p><p>Als U erg veel gegevens hebt, kunt U dit beter uit laten, want als U <span style=" font-style:italic;"> alle overzichten</span> wilt bekijken, moeten deze gegevens toch worden opgehaald. </p><p>Let op: dit beinvloedt niet de gegevens van golfvorm en gebeurtenissen, want die moeten toch altijd worden geladen.</p></body></html> This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Deze experimentele optie probeert de incident-markeringen te gebruiken om een betere correlatie te kunnen zien. Show Remove Card reminder notification on OSCAR shutdown Toon aanwijzing om de SD-kaart te verwijderen bij afsluiten Check for new version every Controleer elke days. dagen. Last Checked For Updates: Laatste controle: TextLabel Tekstlabel I want to be notified of test versions. (Advanced users only please.) Ik wil meldingen voor testversies ontvangen (Indien aangemeld als tester) &Appearance &Uiterlijk Graph Settings Grafiekinstellingen <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Welke tab openen bij het laden van een profiel. (Let op: Gaat standaard naar Profiel als is ingesteld dat geen er profiel bij de start opent)</p></body></html> Bar Tops Staafgrafieken Line Chart Lijngrafieken Overview Linecharts Overzicht lijngrafieken <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Dit maakt scrollen makkelijker bij een tablet,</p><p> 50 ms wordt aanbevolen.</p></body></html> Scroll Dampening Scrollen dempen Overlay Flags Markeringen The visual method of displaying waveform overlay flags. De visuele methode voor het tonen van markeringen in golfvormgrafieken. Standard Bars Standaardbalken Graph Height Grafiekhoogte Default display height of graphs in pixels Standaardhoogte grafieken in pixels How long you want the tooltips to stay visible. Hoe lang de tooltips zichtbaar moeten blijven. Events Incidenten Flag rapid changes in oximetry stats Markeer snelle veranderingen in de oxymeter statistieken Other oximetry options Andere oxymeter opties Discard segments under Verwerp segmenten onder Flag Pulse Rate Above Markeer hartritme hoger dan Flag Pulse Rate Below Markeer hartritme lager dan <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Deze instelling moet voorzichtig worden gebruikt ...</span> Als u het uitschakelt, heeft dit gevolgen voor de nauwkeurigheid van dagen met slechts een samenvatting, aangezien bepaalde berekeningen alleen correct werken, mits sessies met alleen samenvattingen die afkomstig zijn van individuele dagrecords, bij elkaar worden gehouden. </p><p><span style=" font-weight:600;">ResMed gebruikers:</span> Alleen omdat het voor u en mij natuurlijk lijkt dat de herstart van de 12-uur 's middags sessie de vorige dag zou moeten zijn, wil nog niet zeggen dat de gegevens van ResMed het met ons eens zijn. Het STF.edf-overzichtsindexformaat heeft ernstige tekortkomingen waardoor dit geen goed idee is.</p><p>Deze optie bestaat om degenen die het niet schelen, hun zin te geven en dit 'vastgezet' willen zien, ongeacht de kosten, maar weten dat er kosten aan verbonden zijn. Als je je SD-kaart elke nacht in het apparaat laat en minstens één keer per week importeert, zul je hier niet vaak problemen mee zien.</p></body></html> This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Voor deze berekening zijn de gegevens van de totale lek nodig.(Van de Respironics, maar ResMed berekent dit zelf al). De berekening van de onbedoelde lekkage gebeurt lineair, niet volgens de masker-curve. Als U meerdere maskers gebruikt, neem dan gemiddelde waarden, dat is voldoende nauwkeurig. Calculate Unintentional Leaks When Not Present Bereken de onbedoelde lekkage als deze niet door het apparaat gegeven wordt 4 cmH2O 4 cm H2O 20 cmH2O 20 cm H2O Note: A linear calculation method is used. Changing these values requires a recalculation. Let op: hier wordt een lineaire benadering gebruikt. Voor verandering van deze waarden moet worden herberekend. Show flags for machine detected events that haven't been identified yet. Zet de markeringen voor de zelf gekozen incident-vlaggen aan. Tooltip Timeout Tooltip timeout Graph Tooltips Grafiek tekstballonnen Top Markers Top markeringen Changing SD Backup compression options doesn't automatically recompress backup data. Wijzigen van de instelling comprimeert niet de al opgeslagen back-ups. Auto-Launch CPAP Importer after opening profile Start automatisch met importeren na het openen van een profiel Automatically load last used profile on start-up Laadt automatisch het laatste profiel bij opstarten <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Geef een waarschuwing bij het importeren van gegevens van elk apparaatmodel dat nog niet is getest door OSCAR-ontwikkelaars.</p></body></html> Warn when importing data from an untested machine Waarschuw bij het importeren van gegevens van een niet-getest apparaat <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>Geef een waarschuwing bij het importeren van gegevens die op een of andere manier verschillen van alles wat eerder door OSCAR-ontwikkelaars is gezien.</p></body></html> Warn when previously unseen data is encountered Waarschuw wanneer eerder ongeziene gegevens worden aangetroffen Your masks vent rate at 20 cmH2O pressure De normale maskerlekkage bij 20 cm H2O druk Your masks vent rate at 4 cmH2O pressure De normale maskerlekkage bij 4 cm H2O druk <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Wegens beperkingen in het ontwerp van overzichten kan dit bij ResMed apparaten niet worden gewijzigd.</p></body></html> Oximetry Settings Oxymetrie instellingen Always save screenshots in the OSCAR Data folder Sla schermafbeeldingen altijd op in de map OSCAR-Data Check For Updates Controleer op update You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. U gebruikt een testversie van OSCAR. Testversies controleren ten minste elke zeven dagen automatisch op updates. U mag het interval instellen op minder dan zeven dagen. Automatically check for updates Controleer automatisch op updates How often OSCAR should check for updates. Hoe vaak OSCAR moet controleren op updates. If you are interested in helping test new features and bugfixes early, click here. Als u geïnteresseerd bent in het vroegtijdig testen van nieuwe functies en bugfixes, klik dan hier. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Als U wilt helpen bij het testen van vroege versies van OSCAR, raadpleeg dan de Wiki-pagina over het testen van OSCAR. We verwelkomen iedereen die OSCAR wil testen, OSCAR wil helpen ontwikkelen en wil helpen met vertalingen naar bestaande of nieuwe talen. https://www.sleepfiles.com/OSCAR On Opening Bij start Profile Profiel Welcome Welkom Daily Dagrapport Statistics Statistiek Switch Tabs Wissel tabbladen No change Zelfde After Import Na import Other Visual Settings Overige visuele instellingen Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Anti-aliasing strijkt de grafieken glad. Sommige grafieken zien er dan mooier uit. Dit is ook van invloed op afgedrukte verslagen. Probeer het en kijk of U het leuk vindt. Use Anti-Aliasing Gebruik Anti-aliasing Makes certain plots look more "square waved". Zorgt ervoor dat sommige grafieken er hoekiger uitzien. Square Wave Plots Hoekige golfgrafieken Use Pixmap Caching Gebruik Pixmap Caching Animations && Fancy Stuff Animaties en grappige dingen Whether to allow changing yAxis scales by double clicking on yAxis labels Toestaan om de automatische y-as instelling te wijzigen door dubbelklikken op een label Allow YAxis Scaling Sta automatische y-as instelling toe Whether to include machine serial number on machine settings changes report Of het serienummer van het apparaat moet worden opgenomen in het verslag met wijzigingen in de apparaat-instellingen Include Serial Number Toon serienummer Graphics Engine (Requires Restart) Grafische kaart (Herstart nodig) l/min l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <html><head/><body><p>Markeer SpO2<span style=" vertical-align:sub;">2</span> desaturaties onder</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Synchromisatie van Oxymetrie- en CPAP gegevens</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 gegevens die worden geïmporteerd uit SpO2Review (van .spoR bestanden) of met seriële import hebben </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">niet</span><span style=" font-family:'Sans'; font-size:10pt;"> de juiste tijdsaanduiding die voor synchronisatie nodig is.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">De "Live view" methode (met een seriële kabel) is een manier om een accurate synchronisatie te krijgen met CMS50 oxymeters, maar houdt geen rekening met de onstabiele klok van de CPAP.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Als u de oxymeter start op </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exact </span><span style=" font-family:'Sans'; font-size:10pt;">hetzelfde moment als de CPAP, werkt de synchronisatie wèl. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">De seriële import neemt de starttijd van de eerste sessie van de vorige nacht. (Denk er wel aan om EERST de CPAP gegevens te importeren!)</span></p></body></html> Print reports in black and white, which can be more legible on non-color printers Druk rapporten af in zwart-wit, wat leesbaarder kan zijn op niet-kleurenprinters Print reports in black and white (monochrome) Druk rapporten af in zwart-wit (monochroom) Font Lettertype Size Grootte Bold Vet Italic Cursief Application Toepassing Graph Text Grafiektekst Graph Titles Gafiektitels Big Text Grote tekst Details Details &Cancel &Annuleren &Ok &OK Waveforms Golfvormgrafiek Name Naam Color Kleur Label Label Data Reindex Required Gegevens opnieuw indexeren A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Herindexering van de gegevens is nodig. Dit kan een paar minuten duren. Weet U zeker dat U deze wijzigingen wilt doorvoeren? Restart Required Herstart vereist Flag Markering Minor Flag Kleine vlag Span Bereik Always Minor Altijd klein No CPAP machines detected Geen CPAP apparaat gedetecteerd Will you be using a ResMed brand machine? Gaat u een apparaat van ResMed inlezen? Never Nooit Flag Type Soort markering CPAP Events CPAP incidenten Oximeter Events Oxymeter incidenten Positional Events Positie incidenten Sleep Stage Events Slaapfase incidenten Unknown Events Onbekende incidenten Double click to change the descriptive name this channel. Dubbelklik om de naam van dit kanaal te wijzigen. Double click to change the default color for this channel plot/flag/data. Dubbelklik om de kleur te wijzigen van dit kanaal (grafiek/markering/gegevens). <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Let op: </b>de geavanceerde sessiesplitsingsmogelijkheden van OSCAR zijn niet mogelijk met <b>ResMed</b>-apparaten vanwege een beperking in de manier waarop de instellingen en samenvattingsgegevens worden opgeslagen, en daarom zijn ze uitgeschakeld voor dit profiel. </p><p>Op ResMed-apparaten worden dagen <b>gesplitst tussen de middag</b>, zoals in de commerciële software van ResMed.</p> %1 %2 %1 %2 Overview Overzicht Double click to change the descriptive name the '%1' channel. Dubbelklik om de beschrijving van kanaal '%1' te wijzigen. Whether this flag has a dedicated overview chart. Of deze vlag een eigen overzichtgrafiek heeft. Here you can change the type of flag shown for this event Hier kunt U het soort markering van dit incident wijzigen This is the short-form label to indicate this channel on screen. Dit is het beknopte label om dit kanaal op het scherm te tonen. This is a description of what this channel does. Dit is de beschrijving van wat dit kanaal doet. Lower Onderste Upper Bovenste CPAP Waveforms CPAP golfgrafiek Oximeter Waveforms Oxymeter grafiek Positional Waveforms Positie grafiek Sleep Stage Waveforms Slaapfase grafiek Whether a breakdown of this waveform displays in overview. Of er een verdeling van deze golfvorm wordt getoond in de overzichtpagina. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Hier kunt U de <b>onderste</b> drempel instellen van enkele berekeningen aan de %1 grafiek Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Hier kunt U de <b>bovenste</b> drempel instellen van enkele berekeningen aan de %1 grafiek Data Processing Required Gegevens opnieuw verwerken A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Voor het toepassen van deze wijzigingen is de- en hercompressie vereist. Deze bewerking kan enkele minuten duren om te voltooien. Weet U zeker dat U deze wijzigingen wilt aanbrengen? One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? De wijzigingen maken een herstart noodzakelijk. Wil tU dit nu doen? This may not be a good idea Dit lijkt me niet zo'n goed idee ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9 apparaten wissen bepaalde gegevens van uw SD kaart als ze ouder zijn dan 7 en 30 dagen (afhankelijk van de resolutie). If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Als U ooit gegevens opnieuw moet inlezen (in OSCAR of in ResScan), krijgt U deze gegevens niet terug. If you need to conserve disk space, please remember to carry out manual backups. Als U zuinig moet zijn met schijfruimte, vergeet dan niet om zelf backups te maken. Are you sure you want to disable these backups? Weet U zeker dat U deze automatische backups wilt uitschakelen? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Het is geen goed idee om da automatische backup-functie uit te schakelen, OSCAR heeft deze nodig voor het eventuele herstellen van de database. Are you really sure you want to do this? Weet U zeker dat U dit wilt? ProfileSelector Filter: Filter: Reset filter to see all profiles Herstel filter om alle profielen te zien Version Versie &Open Profile &Open profiel &Edit Profile Profiel &aanpassen &New Profile &Nieuw profiel Profile: None Profiel: geen Please select or create a profile... Selecteer of maak een profiel ... Destroy Profile Verwijder profiel Profile Profiel Ventilator Brand Merk apparaat Ventilator Model Type apparaat Other Data Andere gegevens Last Imported Laatste import Name Naam You must create a profile U moet een profiel aanmaken Enter Password for %1 Geef wachtwoord voor %1 You entered an incorrect password U hebt een verkeerd wachtwoord gegeven Forgot your password? Wachtwoord vergeten? Ask on the forums how to reset it, it's actually pretty easy. Vraag op de forums hoe U het kunt resetten, het is eigenlijk vrij eenvoudig. Select a profile first Kies eerst een profiel The selected profile does not appear to contain any data and cannot be removed by OSCAR Het geselecteerde profiel lijkt geen gegevens te bevatten en kan niet door OSCAR worden verwijderd If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Als u probeert te verwijderen omdat u het wachtwoord bent vergeten, moet u het resetten of de profielmap handmatig verwijderen. You are about to destroy profile '<b>%1</b>'. U staat op het punt het profiel "<b>%1</b>" te wissen. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Denk goed na, want hierdoor wordt het profiel onherroepelijk verwijderd, samen met alle <b>back-upgegevens</b> opgeslagen onder <br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Voer het woord <b>VERWIJDEREN</b> hieronder in (precies zoals getoond) om te bevestigen. DELETE VERWIJDEREN Sorry Sorry You need to enter DELETE in capital letters. U moet het woord VERWIJDEREN in HOOFDLETTERS intypen. There was an error deleting the profile directory, you need to manually remove it. Er ging iets mis bij het wissen van de profielmap, U moet deze zelf wissen. Profile '%1' was succesfully deleted Het profiel '%1' is succesvol gewist Bytes Bytes KB kB MB MB GB GB TB TB PB PB Summaries: Overzichten: Events: Gebeurtenissen: Backups: Backups: Hide disk usage information Verberg informatie over schijfgebruik Show disk usage information Toon informatie over schijfgebruik Name: %1, %2 Naam: %2 %1 Phone: %1 Telefoon: %1 Email: <a href='mailto:%1'>%1</a> E-mail: <a href='mailto:%1'>%1</a> Address: Adres: No profile information given Geen profielinformatie opgegeven Profile: %1 Profiel: %1 ProgressDialog Abort Afbreken QObject No Data Geen gegevens ft ft lb lb oz oz Kg kg cmH2O cmWK Med. Med. Min: %1 Min.: %1 Min: Min.: Max: Max.: Max: %1 Max.: %1 %1 (%2 days): %1.(%2 dagen): %1 (%2 day): %1 (%2 dagen): % in %1 % in %1 Hours Uren Min %1 Min. %1 Hours: %1 Uren:.%1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 kort gebruikt, %2 niet gebruikt, op %3 dagen (%4% therapietrouw.) Tijdsduur: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Min/Med/Max Sessies in zichtbare periode: %1 / %2 / %3 Tijdsduur: %4 / %5 / %6 Langste: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Tijdsduur: %3 Start: %2 Mask On Masker op Mask Off Masker af %1 Length: %3 Start: %2 %1 Tijdsduur: %3 Start: %2 TTIA: Min/Med/Max TTiA in zichtbare periode: TTIA: %1 TTiA: %1 bpm slagen per minuut Error Fout Warning Waarschuwing Please Note LET OP &Yes &Ja &No &Nee &Cancel &Annuleren &Destroy &Wissen &Save &Opslaan Min EPAP Min. EPAP Max EPAP Max. EPAP Min IPAP Min. IPAP Max IPAP Max. IPAP On Aan Off Uit BMI BMI Minutes min Seconds sec Events/hr Incidenten per uur Hz Hz Litres Liters ml ml Breaths/min Ademh./min Degrees Graden Information Informatie Busy Bezig Yes Ja Weight Gewicht App key: Naam: Operating system: Besturingssysteem: Built with Qt %1 on %2 Gemaakt met Qt %1 op %2 Graphics Engine: Grafische engine: Graphics Engine type: Soort grafische engine: Software Engine Software Engine ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Desktop OpenGL m m cm cm in inch h h m m s s ms ms Only Settings and Compliance Data Available Alleen instellingen en conformiteitsgegevens beschikbaar Summary Data Only Alleen samenvattende gegevens Zombie Zombie Pulse Rate 20/9 WJG: overal gebruiken we polsslag - moeten we daar eigenlijk niet hartslag van maken? Dat lijkt me eigenlijk beter... Toch maar niet (nog) Hartritme Plethy 20/9 WJG: Wat is dat? AK: Het kwam me bekend voor: plethy definition, meaning, English dictionary, synonym, see also 'plethora',plethoric',plenty',pleat', Reverso dictionary, English definition, English vocabulary. DAT HAD JIJ TOCH MOETEN WETEN? Plethysmos = toename http://www.apneaboard.com/forums/Thread-CMS50D--3956 Plethy Profile Profiel Oximeter oxymeter Default Standaard CPAP CPAP BiPAP BiPAP Bi-Level Bi-level EPAP EPAP IPAP IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Bevochtiger H H OA OA A A CA CA FL FL SA SA LE LE EP EP VS VS VS2 VS2 RERA RERA (RE) PP PP P P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF1 UF2 UF2 UF3 UF3 PS Ondersteuningsdruk AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI EPI PB PB IE I/E Insp. Time Inademtijd Exp. Time Uitademtijd Resp. Event Ademh.-incident Flow Limitation Luchtstroombeperking (FL) Flow Limit Luchtstroombeperking SensAwake SensAwake Pat. Trig. Breath Door patient getriggerde ademhalingen Tgt. Min. Vent Doel min. vent Target Vent. Doelvent. Minute Vent. Ademmin.vol. Tidal Volume Ademvolume Resp. Rate Ademfrequentie Snore Snurken Leak Lekkage Leaks Maskerlek Total Leaks Totale lek Unintentional Leaks Onbedoelde lek MaskPressure Maskerdruk Flow Rate Luchtstroomsterkte Sleep Stage Slaapfase Usage Gebruik Sessions Sessies Pr. Relief Drukvermindering No Data Available Geen gegevens beschikbaar Graphs Switched Off Grafieken uitgeschakeld Sessions Switched Off Sessies uitgeschakeld Bookmarks Bladwijzers Mode Beademingsmodus Model Type Brand Merk Serial Serienummer Series Serie Machine Apparaat Channel Kanaal Settings Instellingen Inclination Inclinatie Orientation Orientatie Motion Beweging Name Naam DOB Geboortedatum Phone Telefoon Address Adres Email E-mail Patient ID Patient-ID Date Datum Bedtime Gaan slapen Wake-up Opgestaan Mask Time Maskertijd Unknown Onbekend None Geen Ready Klaar First Eerste dag Last Laatste dag Start Start End Einde No Nee Min Min Max Max Med Med Average Gemiddeld Median Mediaan Avg Gem W-Avg Gew. gem Pressure Druk ? ? Severity (0-1) Ernst (0-1) Daily Dagrapport Overview Overzicht Oximetry Oxymetrie Event Flags Incident markeringen Windows User Windows-gebruiker Using Het bestand , found SleepyHead - , is nog van SleepyHead - You must run the OSCAR Migration Tool U moet de OSCAR Migratie Tool gebruiken <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Uw oude gegevens moeten worden ingelezen, als de backup-functie tenminste niet is uitgeschakeld</i> Launching Windows Explorer failed Het is niet gelukt om de Windows Verkenner te starten Could not find explorer.exe in path to launch Windows Explorer. Kan explorer.exe niet in het pad vinden. <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR maakt een backup van uw SD-kaart voor dit doel.</b> OSCAR does not yet have any automatic card backups stored for this device. OSCAR heeft nog geen automatische backup-functie voor dit apparaat. This means you will need to import this machine data again afterwards from your own backups or data card. Dat betekent dat U de gegevens van dit apparaat straks opnieuw van de kaart of uit een eigen backup moet inlezen. Important: Belangrijk: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Als U zich zorgen maakt, klik dan op Nee om af te sluiten en maak eerst een backup van uw profiel voordat U OSCAR opnieuw start. Are you ready to upgrade, so you can run the new version of OSCAR? Bent U er klaar voor om met de nieuwe versie te gaan werken? Sorry, the purge operation failed, which means this version of OSCAR can't start. Sorry, het wissen is mislukt. Dat betekent dat deze versie van OSCAR niet kan starten. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Wilt U de automatische backup-functie inschakelen, opdat OSCAR eventueel de database kan herstellen? OSCAR will now start the import wizard so you can reinstall your %1 data. Er wordt een importhulp gestart zodat U de gegevens van uw %1 kunt inlezen. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: OSCAR sluit nu af en probeert het bestandsbeheer te starten, zodat U een backup van het profiel kunt maken: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Gebruik het bestandsbeheer om een copie van het profiel te maken. Start daarna OSCAR opnieuw en maak het proces verder af. Machine Database Changes Wijzigingen in de opslag van de apparaatgegevens OSCAR %1 needs to upgrade its database for %2 %3 %4 OSCAR %1 moet de database voor %2 %3 %4 vernieuwen Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Nadat u deze upgrade heeft uitgevoerd, kunt u <font size=+1></font>dit profiel niet meer gebruiken met de vorige versie. The machine data folder needs to be removed manually. U moet zelf de map OSCAR_Data wissen. This folder currently resides at the following location: Deze map staat momenteel hier: Rebuilding from %1 Backup Herstellen vanuit backup %1 or CANCEL to skip migration. Of klik ANNULEREN om de migratie over te slaan. You cannot use this folder: U kunt deze map niet gebruiken: Migrating Ik migreer nu files bestanden from van to naar OSCAR crashed due to an incompatibility with your graphics hardware. OSCAR is gecrasht vanwege een incompatibiliteit met uw grafische hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. Om dit op te lossen, is OSCAR teruggekeerd naar een langzamer, maar meer compatibele grafische instelling. OSCAR will set up a folder for your data. OSCAR maakt een nieuwe map aan voor uw gegevens. We suggest you use this folder: We bevelen deze map aan: Click Ok to accept this, or No if you want to use a different folder. Klik op OK om dit te accepteren of Nee als u een andere map wilt gebruiken. Next time you run OSCAR, you will be asked again. De volgende keer als U OSCAR gebruikt, wordt dit u weer gevraagd. Migrate SleepyHead or OSCAR Data? Wilt u SleepyHead of OSCAR-gegevens migreren? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Op het volgende scherm zal de OSCAR u vragen om een map te selecteren met SleepyHead of OSCAR gegevens Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Klik op [OK] om naar het volgende scherm te gaan of op [Nee] als u geen SleepyHead of OSCAR-gegevens wilt gebruiken. Unable to create the OSCAR data folder at Kon geen OSCAR_Data folder maken op Unable to write to OSCAR data directory Kon geen gegevens naar de OSCAR_Data folder schrijven Error code Foutcode OSCAR cannot continue and is exiting. OSCAR kan niet verder en sluit nu af. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Kan niet naar het debug log schrijven. Maar u kunt wel het foutopsporingsvenster zichtbaar maken: Help/Probleemoplossen/Foutopsporingsvenster. U kunt de inhoud hiervan selecteren (CTRL-A), dan kopieren (CTRL-C) en vervolgens ergens plakken (CTRL-V) om het op te sturen. Version "%1" is invalid, cannot continue! Versie %1 is ongeldig, kan niet doorgaan! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). De versie van OSCAR die U gebruikt (%1) is OUDER dan degene waarmee U deze gegevens maakte (%2). Choose or create a new folder for OSCAR data Kies of maak een nieuwe map voor OSCAR_Data Choose the SleepyHead or OSCAR data folder to migrate Kies de SleepyHead of OSCAR datamap om te migreren The folder you chose does not contain valid SleepyHead or OSCAR data. De door u gekozen map bevat geen geldige SleepyHead of OSCAR gegevens. If you have been using SleepyHead or an older version of OSCAR, Als u SleepyHead of een oudere versie van OSCAR heeft gebruikt, OSCAR can copy your old data to this folder later. kan OSCAR uw oude gegevens later naar deze map kopiëren. As you did not select a data folder, OSCAR will exit. Omdat U geen map voor gegevensopslag hebt gekozen, wordt OSCAR nu afgesloten. The folder you chose is not empty, nor does it already contain valid OSCAR data. De map die U koos is niet leeg, maar bevat ook geen gegevens van OSCAR. Data directory: Gegevensmap: It is likely that doing this will cause data corruption, are you sure you want to do this? Dit geeft waarschijnlijk aanleiding tot verminkte gegevens, weet U zeker dat U dit wilt? Question Vraag Exiting Afsluiten Are you sure you want to use this folder? Weet U zeker dat U deze map wilt gebruiken? Are you sure you want to reset all your channel colors and settings to defaults? Weet U zeker dat U de kleuren en instellingen van alle grafieken wilt herstellen? Are you sure you want to reset all your waveform channel colors and settings to defaults? Weet U zeker dat U alle kleuren en instellingen wilt resetten? Getting Ready... Voorbereiden... Your %1 %2 (%3) generated data that OSCAR has never seen before. Uw %1 %2 (%3) heeft gegevens gegenereerd die OSCAR nog nooit eerder heeft gezien. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. De geïmporteerde gegevens zijn mogelijk niet helemaal nauwkeurig, dus willen de ontwikkelaars graag een .zip kopie van uw SD kaart, met bijbehorende .pdf van de rapportage om te controleren of OSCAR de gegevens correct verwerkt. Non Data Capable Machine Dit apparaat verstrekt geen gegevens Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Uw %1 CPAP (model %2) is helaas geen model dat gegevens kan verwerken. Your %1 CPAP machine (Model %2) has not been tested yet. Uw %1 CPAP (Model %2) is nog niet getest. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Hij likt veel op andere apparaten die wel werken, maar de ontwikkelaars hebben een .zip kopie van de SD-kaart en bijbehorende .pdf van de rapportage nodig om dit echt met OSCAR compatibel te maken. Sorry, your %1 CPAP machine (%2) is not supported yet. Sorry, uw %1 CPAP (%2) wordt nog niet ondersteund. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. De ontwikkelaars hebben een .zip-kopie van de SD-kaart van dit apparaat nodig en bijpassende .pdf-rapporten van de arts om hem met OSCAR te laten werken. Machine Unsupported Niet ondersteund apparaat I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Het spijt me dat OSCAR van dit apparaat alleen gebruiksuren en erg simpele instellingen kan verwerken. Scanning Files... Bestanden bekijken... Importing Sessions... Sessies importeren... Finishing up... Afronden... Untested Data Niet geteste gegevens Machine Untested Ongetest apparaat CPAP-Check CPAP-controle AutoCPAP AutoCPAP Auto-Trial Auto-Trial AutoBiLevel AutoBiLevel S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Flex Flex Lock Flex Lock Whether Flex settings are available to you. Of Flex-instellingen voor u beschikbaar zijn. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition De tijd die nodig is om over te schakelen van EPAP naar IPAP. Hoe hoger het getal, hoe langzamer de overgang Rise Time Lock Rise Time Lock Whether Rise Time settings are available to you. Of de Rise Time-instellingen voor u beschikbaar zijn. Rise Lock Vergrendeling stijgtijd Passover Koude bevochtiger Target Time Doeltijd PRS1 Humidifier Target Time Doeltijd van de PSR1 bevochtiger Hum. Tgt Time Bevocht. doeltijd Mask Resistance Setting Instelling van maskerweerstand Mask Resist. Inst. maskerweerst. Hose Diam. Slang Diam. 15mm 15 mm Tubing Type Lock Vergrendeling slangtype Whether tubing type settings are available to you. Beschikbaarheid instelling van het slangtype. Tube Lock Vergrendeling slangtype Mask Resistance Lock Vergrendeling maskerweerstand Whether mask resistance settings are available to you. Of instellingen voor maskerweerstand voor u beschikbaar zijn. Mask Res. Lock Vergrendeling maskerweerstand Whether or not machine shows AHI via built-in display. Of het apparaat al dan niet de AHI weergeeft via het ingebouwde display. Ramp Type Soort aanloop Type of ramp curve to use. Welke aanloopcurve moet worden gebruikt. Linear Lineair SmartRamp SmartRamp Ramp+ Ramp+ Backup Breath Mode bepaalt (itt bij ondesteunde spontane beademing zoals CPAP) zowel de in- als expiratie van de betrokkene Gecontroleerde beademingsmodus The kind of backup breath rate in use: none (off), automatic, or fixed Het soort gecontroleerde ademfrequentie die wordt gebruikt: geen (uit), automatisch of vast Breath Rate Ademfrequentie Fixed Vast Fixed Backup Breath BPM Vast aantal ademhalingen per minuut (BPM) voor de gecontroleerde beademingsmodus Minimum breaths per minute (BPM) below which a timed breath will be initiated Minimaal aantal ademhalingen per minuut (BPM) waaronder een gecontroleerde ademhaling wordt geïnitieerd Breath BPM Instelling ademfrequentie Timed Inspiration Gecontroleerde inademing The time that a timed breath will provide IPAP before transitioning to EPAP De tijd dat een geforceerde ademhaling IPAP levert voordat wordt overgeschakeld naar EPAP Timed Insp. Duur gecontr. inadem. Auto-Trial Duration Duur van de Auto-trial The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Het aantal dagen in de Auto-CPAP-proefperiode waarna de machine terugkeert naar CPAP Auto-Trial Dur. Duur Auto-trial. EZ-Start EZ-Start Whether or not EZ-Start is enabled Of EZ-Start al dan niet is ingeschakeld Variable Breathing Variabele ademhaling UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend ONBEVESTIGD: Dit is mogelijk "variabele ademhaling". Periodes met een grote afwijking van de hoogste inademings-stroom Peak Flow Piek-flow Peak flow during a 2-minute interval Piek-flow gedurende 2 minuten interval 22mm 22 mm Backing Up Files... Backup maken... model %1 model %1 DreamStation 2 DreamStation 2 unknown model onbekend model Flex Mode Flex modus PRS1 pressure relief mode. PRS1 drukhulp modus. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time Inst. stijgtijd Bi-Flex Bi-Flex Flex Level Flex instelling PRS1 pressure relief setting. PRS1 drukhulp instelling. Humidifier Status Status bevochtiger PRS1 humidifier connected? Is de bevochtiger aan de PRS1 aangesloten? Disconnected Losgekoppeld Connected Aangekoppeld Humidification Mode Bevochtigingsmodus PRS1 Humidification Mode PRS1-bevochtigingsmodus Humid. Mode Bevocht.modus Fixed (Classic) Vast (klassiek) Adaptive (System One) Adaptief (System One) Heated Tube Verw. slang Tube Temperature Slangtemperatuur PRS1 Heated Tube Temperature PRS1 temperatuur verwarmde slang Tube Temp. Inst. slangtemp. PRS1 Humidifier Setting PRS1 Instelling bevochtiger Hose Diameter Slangdiameter Diameter of primary CPAP hose Diameter van de belangrijkste slang 12mm 12 mm Auto On Automatische start A few breaths automatically starts machine Het apparaat start na enkele ademhalingen Auto Off Automatisch uit Machine automatically switches off Het apparaat schakelt automatisch uit Mask Alert Masker waarschuwing Whether or not machine allows Mask checking. Of controle van het masker is ingeschakeld. Show AHI Toon AHI Breathing Not Detected Geen ademhaling gedetecteerd (BND) tijdfractie A period during a session where the machine could not detect flow. Een periode tijdens een sessie waarbij het apparaat geen flow kon detecteren. BND BND Timed Breath Geforceerde ademhaling Machine Initiated Breath Door apparaat getriggerde ademhaling TB TB Don't forget to place your datacard back in your CPAP machine Vergeet niet om de SD-kaart weer in uw apparaat te steken OSCAR Reminder OSCAR herinnering You can only work with one instance of an individual OSCAR profile at a time. U mag in OSCAR maar met één profiel tegelijk open hebben. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Als U cloud-opslag gebruikt, zorg dan dat OSCAR is afgesloten en de synchronisatie is afgerond voordat U verder gaat. Loading profile "%1"... Profiel "%1" aan het laden... Chromebook file system detected, but no removable device found Chromebook-bestandssysteem gedetecteerd, maar geen verwijderbaar apparaat gevonden You must share your SD card with Linux using the ChromeOS Files program U moet uw SD-kaart delen met Linux met behulp van het programma ChromeOS Files Recompressing Session Files Sessie-bestanden hercomprimeren Please select a location for your zip other than the data card itself! Selecteer een andere locatie voor uw zip dan de datakaart zelf! Unable to create zip! Kan geen zip maken! There are no graphs visible to print Geen zichtbare grafieken om af te drukken Would you like to show bookmarked areas in this report? Wilt U gebieden met bladwijzer in dit verslag tonen? Printing %1 Report Verslag %1 afdrukken %1 Report %1 Verslag : %1 hours, %2 minutes, %3 seconds : %1 uren, %2 minuten, %3 seconden RDI %1 RDI %1 AHI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI: %1 HI: %2 CAI: %3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI: %1 VSI: %2 FLI: %3 PB/CSR: %4% UAI=%1 UAI: %1 NRI=%1 LKI=%2 EPI=%3 NRI: %1 LKI: %2 EPI: %3 AI=%1 AI: %1 Reporting from %1 to %2 Verslag van %1 tot %2 Entire Day's Flow Waveform Luchtstroomsterkte golfvorm van de hele dag Current Selection Huidige selectie Entire Day Gehele dag Page %1 of %2 Pagina %1 van %2 Jan Jan Feb Feb Mar Mrt Apr Apr May Mei Jun Jun Jul Jul Aug Aug Sep Sep Oct Okt Nov Nov Dec Dec Events Incidenten Duration Tijdsduur (% %1 in events) (%1% in incidenten) Therapy Pressure Behandeldruk Inspiratory Pressure Inademdruk Lower Inspiratory Pressure Laagste inademdruk Higher Inspiratory Pressure Hoogste inademdruk Expiratory Pressure Uitademdruk Lower Expiratory Pressure Onderste uitademdruk Higher Expiratory Pressure Bovenste uitademdruk Pressure Support Drukondersteuning PS Min Min. ondersteuningsdruk Pressure Support Minimum Minimale drukondersteuning PS Max Max. ondersteuningsdruk Pressure Support Maximum Maximale drukondersteuning Min Pressure Minimale druk Minimum Therapy Pressure Minimum behandeldruk Pressure Min Minimum druk Pressure Max Maximum druk Pressure Set Ingestelde druk Pressure Setting Drukinstelling IPAP Set Ingestelde IPAP IPAP Setting IPAP instelling EPAP Set Ingestelde EPAP EPAP Setting EPAP instelling Cheyne Stokes Respiration Cheyne Stokes Ademhaling (CSR) tijdfractie CSR CSR Periodic Breathing Periodieke ademhaling (PB) tijdfractie An abnormal period of Periodic Breathing Een abnormale tijdsduur van periodieke ademhaling Clear Airway Open luchtweg of Centrale Apneu (CA) Obstructive Obstructieve Apneu (OA) Apnea Apneu An apnea reportred by your CPAP machine. Een apneu die door het apparaat is geregistreerd. Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Ademafhankelijke activiteitsverhoging: Door ademhalingsinspanning veroorzaakte verhoogde activiteit van hersenen/lichaam waardoor de slaapdiepte vermindert. A user definable event detected by OSCAR's flow waveform processor. Door de gebruiker instelbaar incident dat door OSCAR wordt herkend. Perfusion Index Perfusie index A relative assessment of the pulse strength at the monitoring site Een relatieve benadering van de sterkte van de hartslag op de gemeten plek Perf. Index % Perf index % Mask Pressure (High frequency) Maskerdruk (Hoge resolutie) A ResMed data item: Trigger Cycle Event Een ResMed-gegevensitem: "Trigger Cycle Event" Movement Beweging Movement detector Bewegingsmelder CPAP Session contains summary data only Deze sessie bevat uitsluitend overzichtgegevens PAP Mode Soort apparaat PAP Device Mode Soort PAP APAP (Variable) APAP (variabel) ASV (Fixed EPAP) ASV (Vaste EPAP) ASV (Variable EPAP) ASV (Variabele EPAP) Height Lengte Physical Height Lichaamslengte Notes Notities Bookmark Notes Bladwijzer notities Body Mass Index Body Mass Index How you feel (0 = like crap, 10 = unstoppable) Hoe voelt U zich (0=waardeloos, 10=fantastisch) Bookmark Start Bladwijzer begin Bookmark End Bladwijzer eind Last Updated Laatst bijgewerkt Journal Notes Dagboek notities Journal Dagboek 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=Wakker 2=REM 3=Lichte slaap 4=Diepe slaap Brain Wave Hersengolf BrainWave Hersengolf Awakenings Ontwakingen Number of Awakenings Aantal keren wakker geworden Morning Feel Morgenstemming How you felt in the morning Hoe U zich 's morgens voelt Time Awake Wektijd Time spent awake Tijdsduur wakker gebleven Time In REM Sleep Tijd in REM-slaap Time spent in REM Sleep Tijdsduur in REM-slaap Time in REM Sleep Tijd in REM-slaap Time In Light Sleep Tijd in ondiepe slaap Time spent in light sleep Tijdsduur in ondiepe slaap Time in Light Sleep Tijd in ondiepe slaap Time In Deep Sleep Tijd in diepe slaap Time spent in deep sleep Tijdsduur in diepe slaap Time in Deep Sleep Tijd in diepe slaap Time to Sleep Tijd tot slapen Time taken to get to sleep Tijdsduur tot in slaap vallen Zeo ZQ Zeo ZQ Zeo sleep quality measurement Zeo slaapkwaliteit meting ZEO ZQ ZEO ZQ Debugging channel #1 Kanaal 1 debuggen Test #1 Test #1 For internal use only Voor intern gebruik Debugging channel #2 Kanaal 2 debuggen Test #2 Test #2 Zero Nul Upper Threshold Bovengrens Lower Threshold Ondergrens Max Pressure Max. druk Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Kan Channels.xml niet ontleden, OSCAR kan niet doorgaan en wordt afgesloten. Maximum Therapy Pressure Maximum behandeldruk Ramp Time Aanlooptijd Ramp Delay Period Aanloop vertraging Ramp Event Aanloop incident Ramp Aanloop An abnormal period of Cheyne Stokes Respiration Een abnormale tijdsduur van Cheyne-Stokes ademhaling An apnea that couldn't be determined as Central or Obstructive. Een apneu die niet als centraal of obstructief kon worden geclassificeerd. A restriction in breathing from normal, causing a flattening of the flow waveform. Een abnormale beperking van de ademhaling, waardoor de luchtstroomsterktegolf afvlakte. Vibratory Snore (VS2) Vibrerend snurken (VS2) Leak Flag Lekmarkering (LF) LF LF Mask On Time Tijdstip masker opgezet Time started according to str.edf Starttijd volgens het bestand str.edf Summary Only Alleen overzichtsgegevens Ramp Pressure Aanloopdruk Starting Ramp Pressure Aanloop startdruk An apnea where the airway is open Een apneu waarbij de luchtweg niet is afgesloten An apnea caused by airway obstruction Een apneu waarbij de luchtweg is afgesloten Hypopnea Hypopneu (H) A partially obstructed airway Een gedeeltelijk afgesloten luchtweg Unclassified Apnea Onbekende Apneu (UA) UA UA Vibratory Snore Vibrerend snurken (VS) A vibratory snore Een snurk A vibratory snore as detcted by a System One machine System One detecteert vibrerend snurken Pressure Pulse Drukpuls A pulse of pressure 'pinged' to detect a closed airway. Een kleine drukgolf waarmee een afgesloten luchtweg wordt gedetecteerd. Large Leak Groot lek (LL) tijdfractie A large mask leak affecting machine performance. Dusdanige lekkage dat het apparaat niet meer goed detecteert. LL LL Non Responding Event Incident zonder reactie A type of respiratory event that won't respond to a pressure increase. Een ademhalings-incident dat niet door drukverhoging wordt beinvloed. Expiratory Puff Uitademstoot Intellipap event where you breathe out your mouth. Een Intellipap incident waarbij door de mond wordt uitgeademd. SensAwake feature will reduce pressure when waking is detected. De SensAwake functie verlaagt de druk als een arousal wordt gedetecteerd. User Flag #1 Gebruikersmarkering UF1 User Flag #2 Gebruikersmarkering UF2 User Flag #3 Gebruikersmarkering UF3 Heart rate in beats per minute Pols in slagen per minuut Blood-oxygen saturation percentage Bloedzuurstof saturatie Plethysomogram Plethysomogram An optical Photo-plethysomogram showing heart rhythm Een optisch foto-plethysomogram die het hartritme laat zien Pulse Change Wijziging in hartritme (PC) A sudden (user definable) change in heart rate Een plotselinge verandering in hartritme (instelbaar) SpO2 Drop SpO2 verlaging (SD) A sudden (user definable) drop in blood oxygen saturation Een plotselinge verlaging in zuurstofsaturatie (instelbaar) SD SD Breathing flow rate waveform Golfvorm van de luchtstroomsterkte L/min l/min Mask Pressure Maskerdruk Amount of air displaced per breath Volume lucht dat per ademhaling wordt verplaatst Graph displaying snore volume Grafiek die de luidheid van snurken weergeeft Minute Ventilation Ademminuutvolume Amount of air displaced per minute Volume lucht dat per minuut wordt uitgewisseld met de omgeving Respiratory Rate Ademfrequentie Rate of breaths per minute Aantal ademhalingen per minuut Patient Triggered Breaths Patient getriggerde ademhaling Percentage of breaths triggered by patient Percentage door de patient getriggerde ademhalingen Pat. Trig. Breaths Pat. trig. ademh Leak Rate Onbedoelde lek Rate of detected mask leakage Ernst van de maskerlekkage I:E Ratio verhouding van de inspiratie- en expiratietijd (is normaal 1:2) I:E Ratio Ratio between Inspiratory and Expiratory time Verhouding tussen inadem- en uitademtijd ratio verhouding Expiratory Time Uitademtijd Time taken to breathe out Tijdsduur van het uitademen Inspiratory Time Inademtijd Time taken to breathe in Tijdsduur van het inademen Respiratory Event Ademhalingsincident Graph showing severity of flow limitations Grafiek die de ernst van de luchtstroombeperking aangeeft Flow Limit. Stroombep. Target Minute Ventilation Beoogd ademminuutvolume Maximum Leak Maximum lekkage The maximum rate of mask leakage De maximale lekstroomsterkte Max Leaks Max. lek Apnea Hypopnea Index Apneu-Hypopneu Index Graph showing running AHI for the past hour Grafiek met de voortschrijdende AHI van het afgelopen uur Total Leak Rate Totale lekstroomsterkte Detected mask leakage including natural Mask leakages Gedetecteerde maskerlekkage inclusief de bedoelde lek Median Leak Rate Mediaan van de lekstroomsterkte Median rate of detected mask leakage De mediaan van de maskerlekkage Median Leaks Mediaan lek Respiratory Disturbance Index Ademhalings verstoring index (RDI) Graph showing running RDI for the past hour Grafiek met de voorstschrijdende RDI van het afgelopen uur Sleep position in degrees Slaaphouding in graden Upright angle in degrees Zit/lig stand in graden Plots Disabled Grafieken uitgeschakeld Duration %1:%2:%3 Tijdsduur %1 %2 %3 AHI %1 AHI %1 Days: %1 Dagen: %1 Low Usage Days: %1 Korte dagen: %1 (%1% compliant, defined as > %2 hours) (%1% therapietrouw, met meer dan %2 uren) (Sess: %1) (Sessies: %1) Bedtime: %1 Naar bed: %1 Waketime: %1 Opstaan: %1 (Summary Only) (Alleen overzichtgegevens) There is a lockfile already present for this profile '%1', claimed on '%2'. Er is een blokkeervlag voor het profiel '%1', dat in gebruik is door '%2'. Fixed Bi-Level Bi-level met vaste druk Auto Bi-Level (Fixed PS) Auto Bi-level (met vaste ondersteuningsdruk) Auto Bi-Level (Variable PS) Auto Bi-level (met variabele ondersteuningsdruk) 99.5% 99,5% varies wisselend n/a nvt Fixed %1 (%2) Vaste druk %1 (%2) Min %1 Max %2 (%3) Min: %1 Max: %2 (%3) EPAP %1 IPAP %2 (%3) EPAP: %1 IPAP: %2 (%3) PS %1 over %2-%3 (%4) Ondersteuningsdruk: %1 tussen %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EPAP: %1 Max IPAP: %2 Ondersteuningsdruk: %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP: %1 Ondersteuningsdruk %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP: %1 IPAP: %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Meest recente oxymetriegegevens: <a onclick='alert("daily=%2");'>%1</a> (last night) (afgelopen nacht) (1 day ago) (één dag geleden) (%2 days ago) (%2 dagen geleden) No oximetry data has been imported yet. Er zijn nog geen oxymetriegegevens geïmporteerd. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykell ICON ICON DeVilbiss DeVilbiss Intellipap IntelliPap SmartFlex Settings Instellingen SmartFlex ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose SomnoPose Somnopose Software SomnoPose Programma Zeo Zeo Personal Sleep Coach Persoonlijke Slaap Trainer Database Outdated Please Rebuild CPAP Data Verouderde database Gaarne gegevens opnieuw inlezen (%2 min, %3 sec) (%2 min, %3 sec) (%3 sec) (%3 sec) Backing up files... Backup maken... Reading data files... Gegevensbestanden lezen... SmartFlex Mode SmartFlex instelling Intellipap pressure relief mode. IntelliPap drukhulp modus. Ramp Only Alleen tijdens aanloop Full Time Continu SmartFlex Level Instelling SmartFlex Intellipap pressure relief level. IntelliPap drukhulp instelling. Snoring event. Snurk gebeurtenis. SN SN Locating STR.edf File(s)... Lokaliseren STR.edf bestand (en) ... Cataloguing EDF Files... EDF-bestanden catalogiseren ... Queueing Import Tasks... Importtaken in de wachtrij zetten ... Finishing Up... Afronden... CPAP Mode Soort apparaat VPAPauto VPAPauto ASVAuto ASVAuto iVAPS iVAPS PAC PAC Auto for Her Auto for Her EPR EPR ResMed Exhale Pressure Relief ResMed uitademingsdrukhulp Patient??? Patient??? EPR Level EPR niveau Exhale Pressure Relief Level Niveau van uitademingsdrukhulp Response Reactie Patient View Patiënt weergave SmartStart Autostart Machine auto starts by breathing Apparaat start automatisch Smart Start Automatisch starten Humid. Status Bevocht. status Humidifier Enabled Status Is de bevochtiger.aangesloten? Humid. Level Stand bevochtiger Humidity Level Instelling bevochtiger Temperature Slangtemperatuur ClimateLine Temperature Temperatuur ClimateLine slang Temp. Enable Temp. aan ClimateLine Temperature Enable Stand ClimateLine Temperature Enable Slangverwarming AB Filter AB filter Antibacterial Filter AntiBacterieel filter Pt. Access Pat. toegang Essentials Basisinstellingen Plus Plus Climate Control Climate Control Manual Handmatig Soft Zacht Standard Standaard BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T SmartStop Autostop Machine auto stops by breathing Apparaat stopt automatisch Smart Stop Automatisch stoppen Simple Eenvoudig Advanced Geavanceerd Your ResMed CPAP machine (Model %1) has not been tested yet. Uw ResMed apparaat (Model %1) is nog niet getest. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Hij likt veel op andere apparaten die wel werken, maar de ontwikkelaars hebben een .zip kopie van de kaart en bijbehorende .pdf van de rapportage nodig om dit echt met OSCAR compatibel te maken. Parsing STR.edf records... Interpreteren STR.edf bestanden... Auto Automatisch Mask Masker ResMed Mask Setting ResMed masker instelling Pillows Neuskussens Full Face Volgelaat Nasal Neus Ramp Enable Aanloopdruk Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Pop out Graph Zwevende grafiek The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Het popout-venster is vol. U moet het bestaande popout venster verwijderen en dan deze grafiek weer vastzetten. Your machine doesn't record data to graph in Daily View Uw apparaat registreert geen gegevens voor een grafiek in Dagrapport There is no data to graph Geen gegevens om te laten zien d MMM yyyy [ %1 - %2 ] d MMM yyyy [ %1 - %2 ] Hide All Events Verberg alle incidenten Show All Events Toon alle incidenten Unpin %1 Graph %1 grafiek losmaken Popout %1 Graph Grafiek %1 zwevend maken Pin %1 Graph %1 grafiek vastzetten Relief: %1 Vermindering:%1 Hours: %1h, %2m, %3s Tijd: %1 h, %2 mm %3 s Machine Information Apparaat-informatie Journal Data Dagboek gegevens OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR vond een oude dagboek, maar het schijnt dat de naam is gewijzigd: OSCAR will not touch this folder, and will create a new one instead. OSCAR doet niets met deze map, maar zal een nieuwe maken. Please be careful when playing in OSCAR's profile folders :-P Wees voorzichtig met wijzigen van de profielmappen van OSCAR :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. Om de een of andere reden heeft OSCAR geen dagboekgegevens in uw profiel gevonden, maar wel meerdere mappen met dagboekgegevens. OSCAR picked only the first one of these, and will use it in future: OSCAR heeft de eerste gebruikt en zal deze blijven gebruiken: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Als de oude gegevens ontbreken, copieer dan alle Journal_XXXXXXX mappen naar deze map. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Snapshot %1 Momentopname %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... %1-gegevens voor%2 laden ... Scanning Files Bestanden scannen Migrating Summary File Location Samenvattingsbestand verplaatsen Loading Summaries.xml.gz Summaries.xml.gz laden Loading Summary Data Samenvatting-gegevens laden Please Wait... Even wachten ... Updating Statistics cache Bijwerken statistische gegevens Usage Statistics Gebruiks-statistieken %1 Charts %1 grafieken %1 of %2 Charts %1 van %2 grafieken Loading summaries Samenvattingen laden Your Viatom device generated data that OSCAR has never seen before. Uw Viatom-apparaat heeft gegevens gegenereerd die OSCAR nog nooit eerder heeft gezien. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. De geïmporteerde gegevens zijn mogelijk niet helemaal nauwkeurig, dus de ontwikkelaars willen graag een kopie van uw Viatom-bestanden om te controleren of OSCAR de gegevens correct verwerkt. Viatom Viatom Viatom Software Viatom Software Dreem Dreem New versions file improperly formed Bestand met nieuwe versies is onjuist ingedeeld A more recent version of OSCAR is available Er is een meer recente versie van OSCAR beschikbaar release release test version testversie You are running the latest %1 of OSCAR U gebruikt de nieuwste %1 van OSCAR You are running OSCAR %1 U gebruikt OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 is <a href='%2'>hier</a> beschikbaar. Information about more recent test version %1 is available at <a href='%2'>%2</a> Informatie over recentere testversie %1 is beschikbaar op <a href='%2'>%2</a> Check for OSCAR Updates Controleer op OSCAR-updates Unable to check for updates. Please try again later. Niet in staat om te controleren op updates. Probeer het later nog eens. SensAwake level SensAwake niveau Expiratory Relief Uitademings hulp Expiratory Relief Level Uitademings hulp instelling Humidity Bevochtiging SleepStyle SleepStyle This page in other languages: Deze pagina in andere talen: %1 Graphs %1 grafieken %1 of %2 Graphs %1 van %2 grafieken %1 Event Types %1 soorten incidenten %1 of %2 Event Types %1 van %2 soorten incidenten Report about:blank about:blank SessionBar %1h %2m %1u: %2m No Sessions Present Geen sessies gevonden SleepStyleLoader Import Error Importfout This Machine Record cannot be imported in this profile. Deze apparaatgegevens kunnen niet in dit profiel worden geimporteerd. The Day records overlap with already existing content. De gegevens van deze dag overlappen met bestaande gegevens. Statistics Details Details Most Recent Laatste ingelezen dag Last 30 Days Afgelopen 30 dagen Last Year Afgelopen jaar Average %1 Gemiddelde %1 CPAP Statistics CPAP statistiek CPAP Usage CPAP gebruik Average Hours per Night Gemiddeld aantal uren per nacht Leak Statistics Lekstatistiek Pressure Statistics Drukstatistiek Oximeter Statistics Oxymeterstatistiek Blood Oxygen Saturation Bloedzuurstof saturatie Pulse Rate Hartritme %1 Median %1 mediaan Min %1 Min. %1 Max %1 Max. %1 %1 Index %1 index % of time in %1 Percentage tijd in %1 % of time above %1 threshold Percentage tijd boven de %1 grens % of time below %1 threshold Percentage tijd onder de %1 grens Name: %1, %2 Naam: %2 %1 DOB: %1 Geboortedatum: %1 Phone: %1 Telefoon: %1 Email: %1 E-mail: %1 Address: Adres: Oscar has no data to report :( OSCAR heeft geen gegevens om te laten zien :( Days Used: %1 Dagen gebruikt: %1 Low Use Days: %1 Dagen (te) kort gebruikt: %1 Compliance: %1% Therapietrouw: %1% Days AHI of 5 or greater: %1 Dagen met AHI=5 of meer: %1 Best AHI Laagste AHI Date: %1 AHI: %2 Datum: %1 AHI: %2 Worst AHI Slechtste AHI Best Flow Limitation Laagste luchtstroombeperking Date: %1 FL: %2 Datum: %1 FL: %2 Worst Flow Limtation Slechtste stroombeperking No Flow Limitation on record Geen luchtstroombeperking gevonden Worst Large Leaks Grootste lekkage Date: %1 Leak: %2% Datum: %1 Lek: %2 No Large Leaks on record Geen grote lekkage gevonden Worst CSR Slechtste CSR Date: %1 CSR: %2% Datum: %1 CSR: %2 No CSR on record Geen CSR gevonden Worst PB Slechtste PB Date: %1 PB: %2% Datum: %1 PB: %2% No PB on record Geen PB gemeten Want more information? Wilt U meer informatie? OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR wil alle overzichtgegevens laden om de beste/slechtste van bepaalde dagen te berekenen. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Zet in Voorkeuren de keuze aan om alle gegevens vooraf te laden. Best RX Setting Beste Rx instelling Date: %1 - %2 Datum: %1 - %2 AHI: %1 AHI: %1 Total Hours: %1 Totaal aantal uren: %1 Worst RX Setting Slechtste Rx instelling Last Week Afgelopen week Compliance (%1 hrs/day) Therapietrouw: (%1 uur per dag) Changes to Machine Settings Wijzigingen in de instellingen van het apparaat No data found?!? Geen gegevens gevonden?!? Last 6 Months Afgelopen halfjaar Last Session Laatste sessie No %1 data available. Geen %1 gegevens beschikbaar. %1 day of %2 Data on %3 %1 dagen met %2 gegevens van %3 %1 days of %2 Data, between %3 and %4 %1 dagen met %2 gegevens tussen %3 en %4 Therapy Efficacy Werkzaamheid behandeling This report was prepared on %1 by OSCAR %2 Dit verslag is opgesteld op %1 door OSCAR %2 OSCAR is free open-source CPAP report software OSCAR is gratis open-source CPAP-beoordelingssoftware Days Dagen Pressure Relief Drukvermindering Pressure Settings Drukinstellingen Machine Information Apparaat informatie First Use Eerste gebruik Last Use Laatste gebruik Welcome Welcome to the Open Source CPAP Analysis Reporter Welkom bij dè "Open Source CPAP-Analyse Rapporteur" What would you like to do? <html><head/><body>Uitleg nodig? Klik op <b>Help / Online Gebruiksaanwijzing</b>. Kies op die pagina bovenaan je eigen taal.<br> Of ga naar het <a href="https://forum.apneuvereniging.nl/">ApneuForum</a> of de <a href="https://www.facebook.com/groups/apneuvereniging/" target="_blank">ApneuVereniging Facebook-groep</a> (Rechts klikken om de link te kopieren!)<br> </body></html> Wat wilt U gaan doen? CPAP Importer CPAP importeren Oximetry Wizard Oxymetrie wizard Daily View Dagrapport Overview Overzicht Statistics Statistieken <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Waarschuwing: </span><span style=" color:#ff0000;">De SD-kaart uit een ResMed S9 moet "op slot" worden gezet </span><span style=" font-weight:600; color:#ff0000;">vóórdat u deze in uw computer steekt<br/></span><span style=" color:#000000;">Bepaalde besturingssystemen schrijven een indexbestandje op de kaart, waardoor ResMed de kaart wil formatteren</span></p></body></html> It would be a good idea to check File->Preferences first, Het is een goed idee eerst Bestand -> Voorkeuren te selecteren, as there are some options that affect import. want enkele instellingen kunnen van invloed zijn op het importeren, zoals comprimeren en backup maken. Note that some preferences are forced when a ResMed machine is detected Sommige voorkeuren worden geforceerd ingesteld wanneer een ResMed-apparaat wordt gedetecteerd First import can take a few minutes. De eerste import kan enkele minuten duren. The last time you used your %1... De laatste keer dat U de %1 gebruikte ... last night afgelopen nacht %2 days ago %2 dagen geleden was %1 (on %2) was %1 (op %2) %1 hours, %2 minutes and %3 seconds %1 uren, %2 minuten en %3 seconden Your machine was on for %1. Uw apparaat stond aan gedurende %1. <font color = red>You only had the mask on for %1.</font> <font color=red>U droeg uw masker maar gedurende %1.</font> under minder dan over hoger dan reasonably close to behoorlijk dicht bij equal to gelijk aan You had an AHI of %1, which is %2 your %3 day average of %4. U had een AHI van %1, dat is %2 uw %3-daagse gemiddelde van %4. Your CPAP machine used a constant %1 %2 of air Uw CPAP blies met een constante %1 %2 luchtdruk Your pressure was under %1 %2 for %3% of the time. Uw druk was %3% van de tijd beneden %1 %2 (mediaan). Your machine used a constant %1-%2 %3 of air. Uw CPAP gebruikte een constante luchtdruk van %1-%2 %3. Your EPAP pressure fixed at %1 %2. Uw EPAP druk stond vast op %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Uw IPAP druk was beneden %1 %2 gedurende %3% van de tijd. Your EPAP pressure was under %1 %2 for %3% of the time. Uw EPAP druk was beneden %1 %2 gedurende %3% van de tijd. Your machine was under %1-%2 %3 for %4% of the time. Uw CPAP was beneden %1-%2 %3 gedurende %4% van de tijd. 1 day ago één dag geleden Your average leaks were %1 %2, which is %3 your %4 day average of %5. De gemiddelde lek was %1 %2, dat is %3 uw %4-daags gemiddelde van %5. No CPAP data has been imported yet. Er zijn nog geen CPAP gegevens geimporteerd. gGraph %1 days %1 dagen gGraphView 100% zoom level 100% zoomniveau Restore X-axis zoom to 100% to view entire selected period. Herstel het zoomniveau naar 100% om de hele geselecteerde periode te zien. Restore X-axis zoom to 100% to view entire day's data. Herstel het zoomniveau naar 100% om alle gegevens te zien. Reset Graph Layout Herstel alle grafieken Resets all graphs to a uniform height and default order. Herstelt alle grafieken naar standaard hoogte en volgorde. Y-Axis Y-as Plots Grafieken CPAP Overlays apneu-markeringen Oximeter Overlays SpO2-markeringen Dotted Lines Stippellijnen Double click title to pin / unpin Click and drag to reorder graphs Dubbelklik om dit kanaal vast te zetten Klik en sleep om grafieken te verplaatsen Remove Clone Wis kloon Clone %1 Graph Kloon grafiek %1 OSCAR-code-v1.3.1/Translations/Norsk.no.ts000066400000000000000000014244531417327530600202460ustar00rootroot00000000000000 AboutDialog &About &Om Release Notes Utgivelsesnotater Credits Ingen godt norsk ord? Heder GPL License GPL-lisens Close Lukk Show data folder Vis datamappe About OSCAR %1 Om OSCAR %1 Sorry, could not locate About file. Beklager, kunne ikke finne Om filen. Sorry, could not locate Credits file. Inget godt ord for Credits? Beklager, kunne ikke finne Heder-filen. Sorry, could not locate Release Notes. Beklager, kunne ikke finne Utgivelsesnotater. OSCAR %1 OSCAR %1 Important: Viktig: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Siden dette er en forhåndsvisningsverjon så er det anbefalt at du <b>tar backup av dint data-mappe manuelt</b> før du fortsetter, dette fordi forsøk på å rulle tilbake senere kan ødelegge ting. To see if the license text is available in your language, see %1. For å se om lisensteksten er tilgjengelig i ditt språk, se %1. CMS50F37Loader Could not find the oximeter file: Kunne ikke finne oximeterfil: Could not open the oximeter file: Kunne ikke åpne oximeterfilen: CMS50Loader Could not get data transmission from oximeter. Kunne ikke få dataoverføring fra oximeter. Please ensure you select 'upload' from the oximeter devices menu. Vennligst sørg for at du velger 'opplasting' fra oximeter-enheter i menyen. Could not find the oximeter file: Kunne ikke finne oximeter filen: Could not open the oximeter file: Kunne ikke åpne oximeter-filen: CheckUpdates Checking for newer OSCAR versions Ser etter nyere OSCAR-versjoner Daily Go to the previous day Gå til forrige dag Show or hide the calender Vis eller skjul kalenderen Go to the next day Gå til neste dag Go to the most recent day with data records Gå til nyeste dag med dataregistreringer Events Hendelser View Size Vis Størrelse Notes Notater Journal Journal B B u u Color Farge Small Liten Medium Middels Big Stor Zombie Zombie I'm feeling ... Jeg føler ... Weight Vekt If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Hvis høyde er større enn null i Innstillinger vinduet, så vil vekt her vise Body Mass Index (BMI) verdi Awesome Fantastisk B.M.I. B.M.I. Bookmarks Bokmerker Add Bookmark Legg til Bokmerke Starts Starter Remove Bookmark Fjern Bokmerke Flags Flagg Graphs Grafer Show/hide available graphs. Vis/skjul tilgjengelige grafer. Breakdown Brutt ned events hendelser UF1 UF1 UF2 UF2 Time at Pressure Tid på trykk No %1 events are recorded this day Ingen %1 hendelser er registrert denne dag %1 event %1 hendelse %1 events %1 hendelser Session Start Times Økt-start-tid Session End Times Økt slutt-tid Session Information Sessjoninformasjon Oximetry Sessions Oximetry-sessjoner Duration Varighet (Mode and Pressure settings missing; yesterday's shown.) (Innstillinger for modus og trykk mangler; gårsdagens er vist.) This bookmark is in a currently disabled area.. Dette bokmerket er foreløpig i et deaktivert område.. CPAP Sessions CPAP-sessjoner Sleep Stage Sessions SpO2 desatureringer Position Sensor Sessions Posisjonssensor sessjoner Unknown Session Ukjente sessjoner Machine Settings Maskininnstillinger Model %1 - %2 Modell %1 - %2 PAP Mode: %1 PAP-modus: %1%1 99.5% 90% {99.5%?} This day just contains summary data, only limited information is available. Denne dagen inneholder oppsummeringsdata, kun begrenset informasjon er tilgjengelig. Total ramp time Total rampetid Time outside of ramp Tid utenfor rampe Start Start End Slutt Unable to display Pie Chart on this system Kan ikke vise kakediagram på dette systemet 10 of 10 Event Types This CPAP machine does NOT record detailed data Sorry, this machine only provides compliance data. Beklager, denne maskinen tilbyr kun complicance data. "Nothing's here!" "Ingenting her!" No data is available for this day. Ingen data tilgjengelig for denne dagen. 10 of 10 Graphs Oximeter Information Oximeterinformasjon Details Detaljer Click to %1 this session. Klikk for å %1 denne sessjonen. disable Deaktiver enable Aktiver %1 Session #%2 %1 sessjon #%2 %1h %2m %3s %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Vennligst merk:</b> Alle innstillinger vist nedenfor er basert på antagelser om at ingenting har endret seg siden forrige dager. SpO2 Desaturations SpO2 desatureringer Pulse Change events Pulsendring hendelser SpO2 Baseline Used SpO2 grunnlinje brukt Statistics Statistikk Total time in apnea Total tid i apne Time over leak redline Tid over lekasjegrense Event Breakdown Hendelseroppsummering Sessions all off! Alle økter av! Sessions exist for this day but are switched off. Sessjoner eksisterer for denne dagen, men er skrudd av. Impossibly short session Umulig kort sessjon Zero hours?? Null timer?? BRICK :( ØDELAGT :( Complain to your Equipment Provider! Klag til din fabrikant av ustyret! Pick a Colour Velg en farge Bookmark at %1 Bokmerke på %1 ExportCSV Export as CSV Eksporter som CSV Dates: Datoer: Resolution: Oppløsning: Details Detaljer Sessions Økter Daily Daglig Filename: Filnavn: Cancel Avbryt Export Eksport Start: Start: End: Slutt: Quick Range: Hurtig fra til: Most Recent Day Nyligste dag Last Week Siste uke Last Fortnight Siste to uker Last Month Siste måned Last 6 Months Siste 6 måneder Last Year Siste år Everything Alt Custom Tilpasset Details_ Detaljer_ Sessions_ Økter_ Summary_ Oppsummering_ Select file to export to Velg fil å eksportere til CSV Files (*.csv) CSV-filer (*.csv) DateTime DatoTid Session Økt Event Hendelse Data/Duration Dato/Varighet Date Dato Session Count Øktantall Start Start End Slutt Total Time Total tid AHI AHI Count Antall FPIconLoader Import Error Viktig feilmelding This Machine Record cannot be imported in this profile. Dette maskinopptaket kan ikke bli importert i denne profilen. The Day records overlap with already existing content. Denne dagens opptak overlapper med allerede eksiterende innhold. Help Hide this message Skjul denne meldingen Search Topic: Søk emne: Help Files are not yet available for %1 and will display in %2. Hjelperfiler er enda ikke tilgjengelig for %1 og vil bli vist i %2. Help files do not appear to be present. Hjelperfiler ser ikke ut til å vøre tilgjengelig. HelpEngine did not set up correctly Hjelpemotor ble ikke satt opp korrekt HelpEngine could not register documentation correctly. Hjelpemotor kunne ikke registere dokumentasjon korrekt. Contents Innhold Index Register Search Søk No documentation available Ingen dokumentasjon tilgjengelig Please wait a bit.. Indexing still in progress Vennligsty vent litt.. Indeksering pågår fortsatt No Nei %1 result(s) for "%2" %1 resultat(er) for "%2" clear tø, MD300W1Loader Could not find the oximeter file: Kunne ikke finne oximeterfilen: Could not open the oximeter file: Kunne ikke åpne oximeterfilen: MainWindow &Statistics &Statistikk Report Mode Rapporteringsmodus Standard Standard Monthly Månedlig Date Range Dato fra til Statistics Statistikk Daily Daglig Overview Oversikt Oximetry Oximetry Import Importer Help Hjelp &File &Fil &View &Vis &Reset Graphs &Resett grafer &Help &Hjelp Troubleshooting Feilsøking &Data &Data &Advanced &Avansert Purge ALL Machine Data Tøm all maskindata Rebuild CPAP Data Gjenoppbygg-CPAP-data &Import CPAP Card Data &Importer CPAP kortdata Show Daily view Vis daglig visning Show Overview view Vis oversiktvisning &Maximize Toggle &Maksimer Toggle Maximize window Maksimer vindu Reset Graph &Heights Nullstille graf &Høyder Reset sizes of graphs Nullstille størrelser på grafer Show Right Sidebar Vise høyre sidebar Show Statistics view Vis statistikkvisning Import &Dreem Data Import &Dreem Data Purge Current Selected Day &CPAP &CPAP &Oximetry &Oksimetri &Sleep Stage &Position &All except Notes All including &Notes Show &Line Cursor Vis &linjemarkør Show Daily Left Sidebar Vis daglig venstre sidenar Show Daily Calendar Vis daglig kalender Create zip of CPAP data card Lag zip av CPAP datakort Create zip of OSCAR diagnostic logs Lag zip av OSCAR diagnostiske logger Create zip of all OSCAR data Lag zip av all OSCAR data Report an Issue Rapporter et problem System Information Systeminformasjon Show &Pie Chart Vis &kakediagramm Show Pie Chart on Daily page Vis kakediagramm på daglig visning Standard graph order, good for CPAP, APAP, Bi-Level Standard grafrekkefølge, bra for CPAP, APAP, Bi-Level Advanced Avansert Advanced graph order, good for ASV, AVAPS Avansert grafrekkefølge, bra for AVS, AVAPS Show Personal Data Vis Personlig Data Check For &Updates Se etter &Oppdatering &Preferences &Innstillinger &Profiles &Profiler &About OSCAR &Om OSCAR Show Performance Information Vis ytelsesinformasjon CSV Export Wizard CSV eksportveiviser Export for Review Eksporter for gjennomgang E&xit A&vslutt Exit Avslutt View &Daily Vis &daglig View &Overview Vis &oversikt View &Welcome Vis &velkommen Use &AntiAliasing Bruk &antialiasing Show Debug Pane Vis feilsøkingsrute Take &Screenshot Ta &skjermbilde O&ximetry Wizard O&ksimetriveiviser Print &Report Skriv ut &rapport &Edit Profile &Endre profil Import &Viatom/Wellue Data Daily Calendar Daglig kalender Backup &Journal Sikkerhetskopi &journal Online Users &Guide &Brukermanual på nett &Frequently Asked Questions &Ofte stilte spørsmål &Automatic Oximetry Cleanup &Automatisk opprydding av oksimetri Change &User Endre &bruker Purge &Current Selected Day Rens &valgt dag Right &Sidebar Høyre &sidebar Daily Sidebar Daglig sidebar View S&tatistics Vis s&tatistikk Navigation Navigering Bookmarks Bokmerker Records Opptak Exp&ort Data Eks&porter data Profiles Profiler Purge Oximetry Data Tøm oksimetri data View Statistics Vis statistikk Import &ZEO Data Importer &ZEO data Import RemStar &MSeries Data Importer RemStar &MSeries data Sleep Disorder Terms &Glossary Vilkår og &ordliste for søvnproblemer Change &Language Endre &språk Change &Data Folder Endre &datamappe Import &Somnopose Data Importer &Somnopose-data Current Days Aktuelle dager Welcome Velkommen &About &Om Please wait, importing from backup folder(s)... Vent, importerer fra sikkerhetskopimappe (r) ... Import Problem Importproblem Please insert your CPAP data card... Sett inn CPAP-datakortet ... Access to Import has been blocked while recalculations are in progress. Tilgang til import er blokkert mens omberegninger pågår. CPAP Data Located CPAP-data ligger Import Reminder Importer påminnelse Find your CPAP data card Importing Data Importerer data The User's Guide will open in your default browser Brukerhåndboken åpnes i standard nettleser The FAQ is not yet implemented Ofte stilte spørsmål er ikke implementert If you can read this, the restart command didn't work. You will have to do it yourself manually. Hvis du kan lese dette, fungerte ikke omstartkommandoen. Du må gjøre det selv manuelt. Are you sure you want to rebuild all CPAP data for the following machine: Er du sikker på at du vil gjenoppbygge alle CPAP-data for følgende maskin: For some reason, OSCAR does not have any backups for the following machine: Av en eller annen grunn har OSCAR ingen sikkerhetskopier for følgende maskin: You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Du er i ferd med å <font size =+2>utslette</font> OSCARs maskindatabase for følgende maskin:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: En filtillatelsesfeil gjorde at renseprosessen mislyktes; Du må slette følgende mappe manuelt: No help is available. Ingen hjelp er tilgjengelig. There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete %1's Journal %1's Journal Choose where to save journal Velg hvor du vil lagre journal XML Files (*.xml) XML-filer (*.xml) Export review is not yet implemented Eksportgjennomgang er ikke implementert ennå Would you like to zip this card? Ønsker du å zippe dette kortet? Choose where to save zip Velg hvor du ønsker å lagre zip ZIP files (*.zip) ZIP-filer (*.zip) Creating zip... Opprettet zip... Calculating size... Beregner størrelse... Reporting issues is not yet implemented Rapportering av problemer er ikke implementert ennå Help Browser Hjelp nettleser %1 (Profile: %2) %1 (Profil: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Husk å velge rotmappe eller stasjonsbokstav på datakortet, og ikke en mappe inni den. Please open a profile first. Åpne en profil først. Check for updates not implemented Se etter oppdateringer er ikke implementert Choose where to save screenshot Velg hvor du vil lagre skjermbildet Image files (*.png) Bildefiler (*.png) Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Forutsatt at du har laget <i> dine <b> egne </b> sikkerhetskopier for ALLE CPAP-dataene dine </i>, kan du fortsatt fullføre denne operasjonen, men du må gjenopprette fra sikkerhetskopiene manuelt. Are you really sure you want to do this? Er du virkelig sikker på at du vil gjøre dette? Because there are no internal backups to rebuild from, you will have to restore from your own. Fordi det ikke er noen interne sikkerhetskopier å gjenoppbygge fra, må du gjenopprette fra din egen. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Vil du importere fra dine egne sikkerhetskopier nå? (du vil ikke ha noen data synlige for denne maskinen før du gjør det) Note as a precaution, the backup folder will be left in place. Merk som en forholdsregel at sikkerhetskopimappen blir liggende igjen. OSCAR does not have any backups for this machine! OSCAR har ingen sikkerhetskopier for denne maskinen! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Om du ikke har tatt <i>din <b>egen<b> sikkerhetskopi for ALLE dine data for denne maskinen</i>, <font size=+2>så vil du miste denne maskinens data <b>permanent</b>!</font> Are you <b>absolutely sure</b> you want to proceed? Er du <b> helt sikker </b> på at du vil fortsette? The Glossary will open in your default browser Ordlisten åpnes i standard nettleser Are you sure you want to delete oximetry data for %1 Er du sikker på at du vil slette oksimetri-data for%1 <b>Please be aware you can not undo this operation!</b> <b>Vær oppmerksom på at du ikke kan angre denne operasjonen!</b> Select the day with valid oximetry data in daily view first. Velg dagen med gyldige oksimetridata i daglig visning først. Loading profile "%1" Laster profil "%1" Imported %1 CPAP session(s) from %2 Importert %1 CPAP sessjon(er) fra %2 Import Success Import vellykket Already up to date with CPAP data at %1 Allerede oppdatert med CPAP data på %1 Up to date Oppdatert Couldn't find any valid Machine Data at %1 Kunne ikke finne noe gyldig maskindata på %1 Choose a folder Velg en mappe No profile has been selected for Import. Ingen profil har blitt valgt for import. Import is already running in the background. Import kjører allerede i bakgrunnen. A %1 file structure for a %2 was located at: En %1 filstruktur for en %2 var lokalisert på: A %1 file structure was located at: En %1 filestruktur var lokalisert på: Would you like to import from this location? Vil du importere fra denne lokasjonen? Specify Spesifiser Access to Preferences has been blocked until recalculation completes. Tilgang til innstillinger har blitt blokkert inntil rekalkulering er ferdig. There was an error saving screenshot to file "%1" Det var en feil med lagring av skjermbilde til fil "%1" Screenshot saved to file "%1" Skjermbilde lagret til fil "%1" Please note, that this could result in loss of data if OSCAR's backups have been disabled. Vennligst merk, dette kan resulterte i tap av data hvis OSCARs sikkerhetskopi har blitt deaktivert. There was a problem opening MSeries block File: Det var et problem med å åpne MSeries blokkfil: MSeries Import complete MSeries Import ferdig OSCAR Information OSCAR-informasjon MinMaxWidget Auto-Fit Autotilpass Defaults Standarder Override Overstyring The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Skalermodus Y-akse, 'Autotilpass' for automatisk skalering, 'Standard' for innstillinger i henhold til produsent, og 'Overstyring' for å velge din egen. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Minimum Y-akse-verdi .. Merk at dette kan være et negativt tall hvis du ønsker det. The Maximum Y-Axis value.. Must be greater than Minimum to work. Maksimal Y-akse verdi .. Må være større enn Minimum for å fungere. Scaling Mode Skalermodus This button resets the Min and Max to match the Auto-Fit Denne knappen tilbakestiller Min og Maks til å matche Autotilpass NewProfile Edit User Profile Endre brukerprofil I agree to all the conditions above. Jeg aksepterer alle vilkår ovenfor. User Information Brukerinformasjon User Name Brukernavn Password Protect Profile Passordbeskyttet profil Password Passord ...twice... ...to ganger... Locale Settings Lokaliseringsinnstillinger Country Land TimeZone Tidssone about:blank about:blank Very weak password protection and not recommended if security is required. Svært svak passordbeskyttelse og anbefales ikke hvis sikkerhet kreves. DST Zone DST-sone Personal Information (for reports) Personlig informasjon (for rapporter) First Name Fornavn Last Name Etternavn It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Det er helt i orden å hopper over dette, men en ca alder trengs for å øke nøyaktigheten for enkelte kalkulasjoner. D.O.B. F.d. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Biologisk (fødsel) kjønn trengs noen ganger for å øke nøyaktigheten for enkelte kalkuleringen, ha gjerne disse blanke eller hopp over noen av de.</p></body></html> Gender Kjønn Male Mann Female Kvinne Height Høyde Metric Metrisk English Engelsk Contact Information Kontaktinformasjon Address Adresse Email E-post Phone Telefon CPAP Treatment Information CPAP behandlingsinformasjon Date Diagnosed Dato diagnoisert Untreated AHI Ubehandlet AHI CPAP Mode CPAP-modus CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure RX trykk Doctors / Clinic Information Doktor/klinikk-informasjon Doctors Name Doktornavn Practice Name Legekontor Patient ID PasientID &Cancel &Avbryt &Back &Tilbake &Next &Neste Select Country Velg land Welcome to the Open Source CPAP Analysis Reporter Velkommen til åpen kildekode CPAP analyse og rapportering This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Denne programvare er utviklet for å hjelpe deg med å revidere data produsert av din CPAP-maskin og relatert utstyr. PLEASE READ CAREFULLY VENNLIGST LES NØYE Accuracy of any data displayed is not and can not be guaranteed. Nøyaktigheten av data som vises er ikke, og kan ikke garanteres. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Rapporter som genereres er KUN FOR PERSONLIG BRUK og IKKE I NOEN SOM HELST GRAD egnet for bruk til medisinsk diagnose. Use of this software is entirely at your own risk. All bruk av denne programvare er helt og holdent for egen risiko. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR er copyright & copy; 2011-2018 Mark Watkins and portions & copy; 2019-2022 OSCAR Team OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR har blitt tilgjengeliggjort fritt under <a href='qrc:/COPYING'>GNU Public License v3</a> og kommer uten noen garanti og uten NOEN påstander om å være egnet til noe formål. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR er kun ment som en datavisning, og definitivt ikke en erstatning for kompetent medisinsk veiledning fra legen din. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Forfatterne vil ikke bli holdt ansvarlig for <u> noe </u> relatert til bruk eller misbruk av denne programvaren. Please provide a username for this profile Vennligst oppgi et brukernavn for denne profilen Passwords don't match Passord er ikke like Profile Changes Profilendringer Accept and save this information? Aksepterte og lagre denne informasjonen? &Finish &Ferdig &Close this window &Lukk dette vinduet Overview Range: Fra til: Last Week Siste uke Last Two Weeks Siste to uker Last Month Siste måned Last Two Months Siste to måneder Last Three Months Siste tre måneder Last 6 Months Siste 6 måneder Last Year Siste år Everything Alt Custom Tilpasset Snapshot Start: Sart: End: Slutt: Reset view to selected date range Resett visning for å velge fra til dato Toggle Graph Visibility Graf synlighet Drop down to see list of graphs to switch on/off. Velg rullemeny for å se liste over grafer å slå av/på. Graphs Grafer Respiratory Disturbance Index Respiratory Disturbance Index Apnea Hypopnea Index Apnea Hypoapnea Index Usage Bruk Usage (hours) Bruk (timer) Session Times Økt-tider Total Time in Apnea Total tid i apné Total Time in Apnea (Minutes) Total tid i apné (Minutter) Body Mass Index Body Mass Index How you felt (0-10) Hvordan du følte deg (0-10) 10 of 10 Charts Show all graphs Vis alle grafer Hide all graphs Skjul alle grafer OximeterImport Oximeter Import Wizard Oksimeter importeringsveiviser Skip this page next time. Hopp over denne siden neste gang. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? Hvor vil du importere fra? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> Record attached to computer overnight (provides plethysomogram) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Please connect your oximeter device If you can read this, you likely have your oximeter type set wrong in preferences. Please connect your oximeter device, turn it on, and enter the menu Press Start to commence recording Show Live Graphs Duration Varighet Pulse Rate Puls Multiple Sessions Detected Start Time Start tid Details Detaljer Import Completed. When did the recording start? Oximeter Starting time I want to use the time reported by my oximeter's built in clock. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> Choose CPAP session to sync to: You can manually adjust the time here if required: HH:mm:ssap &Cancel &Avbryt &Information Page CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> Set device identifier Erase session after successful upload Import directly from a recording on a device <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> Please choose which one you want to import into OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> &Retry &Choose Session &End Recording &Sync and Save &Save and Finish &Start &Start Scanning for compatible oximeters Could not detect any connected oximeter devices. Connecting to %1 Oximeter Renaming this oximeter from '%1' to '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. "%1", session %2 Nothing to import Ingenting å importere Your oximeter did not have any valid sessions. Close Lukk Waiting for %1 to start Waiting for the device to start the upload process... Select upload option on %1 You need to tell your oximeter to begin sending data to the computer. Please connect your oximeter, enter it's menu and select upload to commence data transfer... %1 device is uploading data... Please wait until oximeter upload process completes. Do not unplug your oximeter. Oximeter import completed.. Select a valid oximetry data file Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Live Oximetry Mode Live Oximetry Stopped Live Oximetry import has been stopped Oximeter Session %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Oximeter not detected Couldn't access oximeter Starting up... Starter opp... If you can still read this after a few seconds, cancel and try again Live Import Stopped %1 session(s) on %2, starting at %3 No CPAP data available on %1 Ingen CPAP data tigjengelig på %1 Recording... Tar opp... Finger not detected I want to use the time my computer recorded for this live oximetry session. I need to set the time manually, because my oximeter doesn't have an internal clock. Something went wrong getting session data Welcome to the Oximeter Import Wizard Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Please remember: Vennligst husk: Important Notes: Viktige notater: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Oximetry Date Dato d/MM/yy h:mm:ss AP d/MM/yy-h:mm:ss AP R&eset R&esett Pulse Puls &Open .spo/R File &Åpne.spo/R-fil Serial &Import Seriell &Import &Start Live &Start Direkte Serial Port Seriell port &Rescan Ports &Søk porter igjen PreferencesDialog Preferences Innstillinger &Import &Import Combine Close Sessions Kombiner nære økter Minutes Minutter Multiple sessions closer together than this value will be kept on the same day. Flere økter nærmere hverandre enn denne verdien blir holdt samme dag. Ignore Short Sessions Ignorer korte økter <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Økter i kortere varighet enn dette vil ikke vises<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Dagdeletid Sessions starting before this time will go to the previous calendar day. Økter som starter før denne tiden, går til forrige kalenderdag. Session Storage Options Alternativer for øktlagring Compress SD Card Backups (slower first import, but makes backups smaller) Komprimere SD-kort sikkerhetskopier (tregere første import, men gjør sikkerhetskopier mindre) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Betrakt dager med under denne bruken som "ufullstendig". 4 timer regnes vanligvis som i samsvar. hours timer Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Aktiver / deaktiver forbedringer for eksperimentell hendelsesflagging. Det gjør det mulig å oppdage grensehendelser, og noen savnet maskinen. Dette alternativet må være aktivert før import, ellers er det nødvendig med en rensing. Flow Restriction Strømningsbegrensning Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Prosent av begrensning i luftstrøm fra medianverdien. En verdi på 20% fungerer bra for å oppdage apné. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Egendefinert flagging er en eksperimentell metode for å oppdage hendelser som maskinen savner. De er<span style=" text-decoration: underline;">ikke</span> inkludert i AHI.</p></body></html> Duration of airflow restriction Varighet av luftstrømningsbegrensning s s Event Duration Hendelsesvarighet Allow duplicates near machine events. Tillat duplikater nær maskinhendelser. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Justerer datamengden som vurderes for hvert punkt i AHI / Time-grafen. Standardinnstillingen er 60 minutter .. Anbefaler på det sterkeste at den blir stående på denne verdien. minutes minutter Reset the counter to zero at beginning of each (time) window. Tilbakestill telleren til null i begynnelsen av hvert (tids) vindu. Zero Reset Nullstilling CPAP Clock Drift CPAP Klokkeforskyvelse Do not import sessions older than: Ikke importer økter eldre enn: Sessions older than this date will not be imported Økter eldre enn denne datoen blir ikke importert dd MMMM yyyy dd MMMM-yyyy User definable threshold considered large leak Brukerdefinerbar terskel betraktet som stor lekkasje Whether to show the leak redline in the leak graph Om du vil vise lekkasjerødlinjen i lekkasjegrafen Search Søk &Oximetry &Oksimetri Show in Event Breakdown Piechart Vis i kakediagrammet for hendelsesoppsummering Resync Machine Detected Events (Experimental) Resynkroniser maskinoppdagede hendelser (eksperimentell) Percentage drop in oxygen saturation Prosentvis fall i oksygenmetning Pulse Puls Sudden change in Pulse Rate of at least this amount Plutselig endring i pulsfrekvens på minst dette antallet bpm bpm Minimum duration of drop in oxygen saturation Minimum varighet av fall i oksygenmetning Minimum duration of pulse change event. Minimum varighet av pulsendringshendelsen. Small chunks of oximetry data under this amount will be discarded. Små biter av oksimeterdata under dette antallet vil bli forkastet. &General &Generelt Changes to the following settings needs a restart, but not a recalc. Endringer i følgende innstillinger trenger omstart, men ikke omberegning. Preferred Calculation Methods Foretrukne beregningsmetoder Middle Calculations Midtre beregninger Upper Percentile Øvre prosentil Session Splitting Settings Innstillinger for splitting av sesjoner <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Ikke del oppsummeringsdager (Advarsel: les verktøytips!) Memory and Startup Options Minne- og oppstartsalternativer Pre-Load all summary data at startup Last alle oppsummeringsdata på forhånd ved oppstart <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Denne innstillingen holder bølgeform og hendelsesdata i minnet etter bruk for å øke hastigheten på å besøke dager.</p><p>Dette er egentlig ikke et nødvendig alternativ, ettersom operativsystemet ditt cacher tidligere brukte filer også.</p><p>Anbefaling er å la den være slått av, med mindre datamaskinen har massevis av minne.</p></body></html> Keep Waveform/Event data in memory Oppbevar bølgeform / hendelsesdata i minnet <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Kutter ned på alle viktige bekreftelsesdialoger under import.</p></body></html> Import without asking for confirmation Importer uten å be om bekreftelse General CPAP and Related Settings Generelle CPAP og relaterte innstillinger Enable Unknown Events Channels Aktiver ukjente hendelseskanaler AHI Apnea Hypopnea Index AHI RDI Respiratory Disturbance Index RDI AHI/Hour Graph Time Window AHI/Time-Graf-Tid-Vindu Preferred major event index Foretrukket større hendelsesindeks Compliance defined as Overholdelse definert som Flag leaks over threshold Flagg lekkasjer over terskel Seconds Sekunder <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Merk: Dette er ikke ment for korreksjon av tidssonen! Forsikre deg om at operativsystemets klokke og tidssone er riktig innstilt.</p></body></html> Hours Timer For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. For konsistens, bør ResMed-brukere bruke 95% her, da dette er den eneste verdien som er tilgjengelig kun på sammendragsdager. Median is recommended for ResMed users. Median anbefales for ResMed-brukere. Median Median Weighted Average Vektlagt gjennomsnitt Normal Average Normal gjennomsnitt True Maximum Sant maksimum 99% Percentile 99% prosentil Maximum Calcs Maksimum beregninger General Settings Generelle innstillinger Daily view navigation buttons will skip over days without data records Navigasjonsknapper for daglig visning hopper over dager uten dataposter Skip over Empty Days Hopp over tomme dager Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Tillat bruk av flere CPU-kjerner der det er tilgjengelig for å forbedre ytelsen. Berører hovedsakelig importen. Enable Multithreading Aktiver flere tråder Bypass the login screen and load the most recent User Profile Omgå påloggingsskjermen og last inn den nyeste brukerprofilen Create SD Card Backups during Import (Turn this off at your own peril!) Opprett sikkerhetskopier av SD-kort under import (slå dette av på egen risiko!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Sann maksimum er maksimum av datasettet.</p><p>99. persentilen filtrerer ut de sjeldneste avvikerne.</p></body></html> Combined Count divided by Total Hours Kombinert antall delt på totalt antall timer Time Weighted average of Indice Tidsvektet gjennomsnitt av Indeks Standard average of indice Standard gjennomsnitt av indeks Custom CPAP User Event Flagging Tilpasset CPAP-brukerhendelflagging Events Hendelser Reset &Defaults Tilbakestill &Standardinnstillinger <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Advarsel: </span>Bare fordi du kan, betyr ikke det at det er god praksis</p></body></html> Waveforms Bølgeformer Flag rapid changes in oximetry stats Flagg raske endringer i oksimetristatistikk Other oximetry options Andre oksimetri alternativer Discard segments under Se bort fra segmenter under Flag Pulse Rate Above Flagg pulsfrekvens over Flag Pulse Rate Below Flagg pulsfrekvens nedenfor Changing SD Backup compression options doesn't automatically recompress backup data. Endring av komprimeringsalternativer for SD-sikkerhetskopiering komprimerer ikke automatisk sikkerhetskopidata. Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Komprimer ResMed (EDF) sikkerhetskopier for å spare diskplass. Sikkerhetskopierte EDF-filer lagres i .gz-format, som er vanlig på Mac- og Linux-plattformer .. OSCAR kan importere fra denne komprimerte sikkerhetskopikatalogen. For å bruke den med ResScan vil .gz-filene først være ukomprimert .. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Følgende alternativer påvirker mengden diskplass OSCAR bruker, og har en innvirkning på hvor lang tid det tar å importere. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Dette gjør at OSCARs data tar omtrent halvparten så mye plass. Men det gjør at import og dagsendring tar lengre tid .. Hvis du har en ny datamaskin med en liten solid state-disk, er dette et godt alternativ. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Komprimere øktdata (gjør OSCAR-data mindre, men dagen endres langsommere.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Dette opprettholder en sikkerhetskopi av SD-kortdata for ResMed-maskiner, ResMed S9-serie maskiner sletter data med høy oppløsning eldre enn 7 dager, og grafdata eldre enn 30 dager .. OSCAR kan beholde en kopi av disse dataene hvis du noen gang trenger å installere på nytt. (Sterkt anbefalt, med mindre du har lite diskplass eller ikke bryr deg om grafdataene) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Gjør OSCAR litt tregere ved å forhåndslaste alle oppsummeringsdataene på forhånd, noe som øker oversikten og noen få andre beregninger senere. </p><p>Hvis du har en stor mengde data, kan det være verdt å holde dette slått av, men hvis du vanligvis vil se <span style=" font-style:italic;">alt</span>oversikt må all sammendragsdata likevel lastes inn uansett. </p><p>Merk at denne innstillingen ikke påvirker bølgeform- og hendelsesdata, som alltid etterspørres etter behov.</p></body></html> <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Gi et varsel når du importerer data fra en hvilken som helst maskinemodell som ennå ikke er testet av OSCAR-utviklere.</p></body></html> Warn when importing data from an untested machine Advarsel når du importerer data fra en ikke-testet maskin <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>Gi et varsel når du importerer data som på en eller annen måte er forskjellig fra alt som OSCAR-utviklere tidligere har sett</p></body></html> Warn when previously unseen data is encountered Advarsel når det oppdages tidligere usett data This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Denne beregningen krever at Total Leaks data skal leveres av CPAP-maskinen. (F.eks. PRS1, men ikke ResMed, som allerede har disse) Utilsiktede lekkasjeberegninger som brukes her er lineære, de modellerer ikke maskeventilkurven. Hvis du bruker noen forskjellige masker, velger du gjennomsnittsverdier i stedet. Det skal fortsatt være nær nok. Calculate Unintentional Leaks When Not Present Calculate Unintentional Leaks When Not Present 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Merk: En lineær beregningsmetode brukes. Endring av disse verdiene krever en ny beregning. This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Dette eksperimentelle alternativet prøver å bruke OSCARs hendelsesflaggingssystem for å forbedre maskinoppdaget hendelsesposisjonering. Show flags for machine detected events that haven't been identified yet. Vis flagg for maskinoppdagede hendelser som ikke har blitt identifisert ennå. Show Remove Card reminder notification on OSCAR shutdown Vis påminnelse om fjerning av kort når OSCAR stenges Always save screenshots in the OSCAR Data folder Lagre alltid skjermbilder i OSCAR Data-mappen Check for new version every Se etter ny versjon hver days. dager. Last Checked For Updates: Sist sjekket for oppdateringer: TextLabel TextLabel I want to be notified of test versions. (Advanced users only please.) &Appearance &Utseende Graph Settings Grafinnstillinger <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Hvilken fane du skal åpne når du laster inn en profil. (Merk: Det vil som standard være Profil hvis OSCAR er satt til ikke å åpne en profil ved oppstart)</p></body></html> Bar Tops Bar Topper Line Chart Linjediagram Overview Linecharts Oversikt Linjediagrammer Whether to include machine serial number on machine settings changes report Om maskinens serienummer skal inkluderes i rapporten om endringer i maskininnstillinger Include Serial Number Inkluder serienummer Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Prøv å endre dette fra standardinnstillingen (Desktop OpenGL) hvis du opplever gjengivelsesproblemer med OSCARs grafer. <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Dette gjør det enklere å rulle når du zoomer inn på sensitive toveis styreputer</p><p>50ms er anbefalt verdi.</p></body></html> How long you want the tooltips to stay visible. Hvor lenge du vil at verktøytipsene skal være synlige. Scroll Dampening Rull demping Tooltip Timeout Tidsavbrudd for verktøytips Default display height of graphs in pixels Standard visningshøyde for grafer i piksler Graph Tooltips Tips om grafverktøy The visual method of displaying waveform overlay flags. Den visuelle metoden for visning av flagg for bølgeformoverlegg. Standard Bars Standard barer Top Markers Topp markører Graph Height Grafhøyde Auto-Launch CPAP Importer after opening profile Start CPAP-importør automatisk etter åpning av profilen Automatically load last used profile on start-up Last inn sist brukte profil automatisk ved oppstart Your masks vent rate at 20 cmH2O pressure Maskeventilasjonshastigheten din ved 20 cm H2O trykk Your masks vent rate at 4 cmH2O pressure Maskeventilasjonshastigheten din ved 4 cmH20 trykk l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Merk: </span>På grunn av begrensede designbegrensninger støtter ikke ResMed-maskiner endring av disse innstillingene.</p></body></html> Oximetry Settings Oximetry-innstlinnger <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Check For Updates Se etter oppdateringer You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Du bruker en testversjon av OSCAR. Testversjoner ser etter oppdateringer automatisk minst hver sjuende dag. Du kan sette intervallet til mindre enn syv dager. Automatically check for updates Se etter oppdateringer automatisk How often OSCAR should check for updates. Hvor ofte OSCAR skal se etter oppdateringer. If you are interested in helping test new features and bugfixes early, click here. Hvis du er interessert i å teste nye funksjoner og feilrettinger tidlig, klikk her. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Hvis du vil hjelpe deg med å teste tidlige versjoner av OSCAR, kan du se Wiki-siden om testing av OSCAR. Vi ønsker alle som ønsker å teste OSCAR, hjelpe med å utvikle OSCAR og hjelpe med oversettelser til eksisterende eller nye språk. https://www.sleepfiles.com/OSCAR On Opening Ved åpning Profile Profil Welcome Velkommen Daily Daglig Statistics Statistikk Switch Tabs Bytt fane No change Ingen endring After Import Etter import Overlay Flags Overleggsflagg Line Thickness Linjetykkelse The pixel thickness of line plots Pikseltykkelsen på linjeplottene Other Visual Settings Andre visuelle innstillinger Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Anti-Aliasing bruker utjevning på grafplott .. Enkelte tomter ser mer attraktive ut med dette på. Dette påvirker også trykte rapporter. Prøv det og se om du liker det. Use Anti-Aliasing Bruk anti-aliasing Makes certain plots look more "square waved". Gjør at visse plotter ser mer "firkantbølget" ut. Square Wave Plots Firkantbølgede plotter Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Pixmap caching er en grafikkakselerasjonsteknikk. Kan forårsake problemer med skrifttegning i grafvisningsområdet på plattformen. Use Pixmap Caching Bruke Pixmap-cache <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Disse funksjonene har nylig blitt beskåret. De kommer tilbake senere.</p></body></html> Animations && Fancy Stuff Animasjoner&&fjonge ting Whether to allow changing yAxis scales by double clicking on yAxis labels Om du vil tillate å endre y-akseskalaer ved å dobbeltklikke på x-akse-merkelapper Allow YAxis Scaling Tillat skalering av YAxis Graphics Engine (Requires Restart) Grafikkmotor (krever omstart) Print reports in black and white, which can be more legible on non-color printers Skriv ut rapporter i svart-hvitt, noe som kan være mer leselig på ikke-fargeskrivere Print reports in black and white (monochrome) Skriv ut rapporter i svart-hvitt (monokrom) Fonts (Application wide settings) Skrifttyper (innstillinger for hele applikasjonen) Font Skrift Size Størrelse Bold Uthevet Italic Kursiv Application Applikasjon Graph Text Graftekst Graph Titles Graftittler Big Text Stor tekst Details Detaljer &Cancel &Avbryt &Ok &Ok Name Navn Color Farge Flag Type Flaggtype Label Merkelapp CPAP Events CPAP-hendelser Oximeter Events Oksimeter-hendelser Positional Events Posisjonshendelser Sleep Stage Events Søvnstadiehendelser Unknown Events Ukjente hendelser Double click to change the descriptive name this channel. Dobbeltklikk for å endre beskrivende navn på denne kanalen. Double click to change the default color for this channel plot/flag/data. Dobbeltklikk for å endre standardfargen for dette kanalplottet / flagget / dataene. <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b> Merk:</b> OSCARs avanserte øktdelingsfunksjoner er ikke mulig med <b>ResMed</b> maskiner på grunn av en begrensning i måten deres innstillinger og sammendragsdata er lagret på, og derfor de har blitt deaktivert for denne profilen.</p><p> På ResMed-maskiner vil dager<b> deles ved middagstid</b> som i ResMeds kommersielle programvare.</p> %1 %2 %1 %2 Overview Oversikt Double click to change the descriptive name the '%1' channel. Dobbeltklikk for å endre beskrivelsesnavnet '%1'-kanalen. Whether this flag has a dedicated overview chart. Om dette flagget har et dedikert oversiktsdiagram. Here you can change the type of flag shown for this event Her kan du endre hvilken type flagg som vises for denne hendelsen This is the short-form label to indicate this channel on screen. Dette er kortformet etikett for å indikere denne kanalen på skjermen. This is a description of what this channel does. Dette er en beskrivelse av hva denne kanalen gjør. Lower Nedre Upper Øvre CPAP Waveforms CPAP bølgeformer Oximeter Waveforms Oksimeter bølgeformer Positional Waveforms Posisjonelle bølgeformer Sleep Stage Waveforms Søvnestadie bølgeformer Whether a breakdown of this waveform displays in overview. Hvorvidt en oversikt over denne kurven vises i oversikten. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Her kan du angi <b>nedre</b> terskel som brukes for visse beregninger på bølgeformen%1 Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Her kan du angi <b>øvre</b> terskel som brukes for visse beregninger på bølgeformen%1 Data Processing Required Databehandling kreves A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? For å bruke disse endringene er det nødvendig å fortsette data / dekompresjon. Denne operasjonen kan ta et par minutter å fullføre. Er du sikker på at du vil gjøre disse endringene? Data Reindex Required Data reindeksering påkrevd A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Det kreves en dataindekseringsprosedyre for å bruke disse endringene. Denne operasjonen kan ta et par minutter å fullføre. Er du sikker på at du vil gjøre disse endringene? Restart Required Start på nytt påkrevd One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? En eller flere av endringene du har gjort, krever at denne applikasjonen startes på nytt for at disse endringene skal tre i kraft. Vil du gjøre dette nå? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Hvis du noen gang trenger å importere disse dataene på nytt (enten i OSCAR eller ResScan), kommer ikke disse dataene tilbake. If you need to conserve disk space, please remember to carry out manual backups. Hvis du trenger å spare diskplass, må du huske å utføre manuelle sikkerhetskopier. Are you sure you want to disable these backups? Er du sikker på at du vil deaktivere disse sikkerhetskopiene? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Å slå av sikkerhetskopier er ikke en god ide, fordi OSCAR trenger disse for å gjenoppbygge databasen hvis feil blir funnet. Are you really sure you want to do this? Er du virkelig sikker på at du vil gjøre dette? Flag Flagg Minor Flag Mindre flagg Span Spenn Always Minor Alltid mindre No CPAP machines detected Ingen CPAP-maskiner oppdaget Will you be using a ResMed brand machine? Vil du bruke en ResMed-maskin? Never Aldri This may not be a good idea Dette er kanskje ikke en god idé ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9-maskiner sletter rutinemessig visse data fra SD-kortet ditt eldre enn 7 og 30 dager (avhengig av oppløsning). ProfileSelector Filter: Filter: Reset filter to see all profiles Resett filter for å se alle profiler Version Versjon &Open Profile &Åpne profil &Edit Profile &Endre profil &New Profile &Ny profil Profile: None Profil: Ingen Please select or create a profile... Vennligst velg eller opprett en profil... Destroy Profile Ødelegg Profil Profile Profil Ventilator Brand Ventilatormerke Ventilator Model Ventilatormodell Other Data Andre data Last Imported Sist importert Name Navn You must create a profile Du må opprette en profil Enter Password for %1 Skriv inn passord for %1 You entered an incorrect password Du skrev inn feil passord Forgot your password? Glemt ditt passord? Ask on the forums how to reset it, it's actually pretty easy. Spør på forumet om hvordan det kan endres, det er faktisk ganske lett. Select a profile first Velg profil først The selected profile does not appear to contain any data and cannot be removed by OSCAR Den valgte profilen ser ikke ut til å inneholde data og kan ikke fjernes av OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Hvis du forsøker å slette fordi du har glemt ditt passord, så må du enten resette det eller slette profilmappen manuelt. You are about to destroy profile '<b>%1</b>'. Du er i ferd med å slette profilen '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Tenk deg godt om, dette vil slette profilen sammen med all <b>backup data</b lagret under<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Skriv inn order <b>SLETT</B> under (akkurat som vist) for å bekrefte. DELETE SLETT Sorry Beklager You need to enter DELETE in capital letters. Du må skrive SLETT med store bokstaver. There was an error deleting the profile directory, you need to manually remove it. Det skjedde en feil under sletting av profilmappen, du må fjerne den manuelt. Profile '%1' was succesfully deleted Profilen '%1' ble slettet Bytes Bytes KB KB MB MB GB GB TB TB PB PB Summaries: Oppsummering: Events: Hendelser: Backups: Sikkerhetskopier: Hide disk usage information Gjem informasjon og diskforbruk Show disk usage information Vis informasjon om diskforbruk Name: %1, %2 Navn:%1,%2 Phone: %1 Telefon: %1 Email: <a href='mailto:%1'>%1</a> Epost: <a href='mailto:%1'>%1</a> Address: Adresse: No profile information given Ingen profilinformasjon oppgitt Profile: %1 Profil: %1 ProgressDialog Abort Avbryt QObject No Data Ingen data Events Hendelser Duration Varighet (% %1 in events) (% %1 i hendelser) Jan Jan Feb Feb Mar Mar Apr Apr May Mai Jun Jun Jul Jul Aug Aug Sep Sep Oct Okt Nov Nov Dec Des ft ft lb lb oz oz Kg Kg cmH2O cmH2O Med. Med. Min: %1 Min: %1 Min: Min: Max: Maks: Max: %1 Maks: %1 %1 (%2 days): %1 (%2 dager): %1 (%2 day): %1 (%2 dag): % in %1 % av %1 Hours Timer Min %1 Min %1 Hours: %1 Timer: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 %1 Length: %3 Start: %2 Mask On Maske På Mask Off Maske Av %1 Length: %3 Start: %2 TTIA: TTIA: %1 TTIA: %1 Minutes Minutter Seconds Sekunder h h m m s s ms ms Events/hr Hz Hz bpm Litres Liter ml ml Breaths/min ? ? Severity (0-1) Degrees Error Feil Warning Advarsel Information Informasjon Busy Please Note Graphs Switched Off Sessions Switched Off &Yes &Ja &No &Nei &Cancel &Avbryt &Destroy &Save &Lagre BMI BMI Weight Vekt Zombie Zombie Pulse Rate Puls Plethy Pressure Daily Daglig Profile Profil Overview Oversikt Oximetry Oximetry Oximeter Event Flags Default Standard CPAP CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP Min EPAP Min EPAP Max EPAP Maks EPAP IPAP IPAP Min IPAP Min IPAP Max IPAP Maks IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Fukter H H OA OA A A CA CA FL FL SA SA LE LE EP EP VS VS VS2 VS2 RERA RERA PP PP P P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF1 UF2 UF2 UF3 UF3 PS PS AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI EPI PB PB IE IE Insp. Time Exp. Time Resp. Event Flow Limitation Flow Limit SensAwake Pat. Trig. Breath Tgt. Min. Vent Target Vent. Minute Vent. Tidal Volume Resp. Rate Snore Leak Leaks Large Leak LL Total Leaks Unintentional Leaks MaskPressure Flow Rate Sleep Stage Usage Bruk Sessions Økter Pr. Relief No Data Available Ingen data tilgjengelig App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Software Engine ANGLE / OpenGLES Desktop OpenGL m m cm cm in Only Settings and Compliance Data Available Summary Data Only Bookmarks Bokmerker Mode Model Modell Brand Merke Serial Series Machine Maskin Channel Kanal Settings Innstillinger Inclination Orientation Motion Name Navn DOB Phone Telefon Address Adresse Email Epost Patient ID PasientID Date Dato Bedtime Wake-up Mask Time Unknown Ukjent None Ingen Ready Klar First Første Last Siste Start Start End Slutt On Off Av Yes Ja No Nei Min Min Max Maks Med Med Average Median Median Avg W-Avg Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Machine Unsupported I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Scanning Files... Importing Sessions... Finishing up... Machine Untested Flex Lock Whether Flex settings are available to you. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Mask Resistance Setting Mask Resist. Hose Diam. 15mm 22mm Backing Up Files... Untested Data model %1 DreamStation 2 unknown model CPAP-Check AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS Flex Mode PRS1 pressure relief mode. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex Rise Time Bi-Flex Bi-Flex Flex Flex Level PRS1 pressure relief setting. Passover Target Time PRS1 Humidifier Target Time Hum. Tgt Time Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Type of ramp curve to use. Linear SmartRamp Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Fixed Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Breath BPM Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled Variable Breathing UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend Peak Flow Peak flow during a 2-minute interval Humidifier Status PRS1 humidifier connected? Disconnected Connected Humidification Mode PRS1 Humidification Mode Humid. Mode Fixed (Classic) Adaptive (System One) Heated Tube Tube Temperature PRS1 Heated Tube Temperature Tube Temp. PRS1 Humidifier Setting Hose Diameter Diameter of primary CPAP hose 12mm Auto On Auto av A few breaths automatically starts machine Auto Off Auto på Machine automatically switches off Mask Alert Whether or not machine allows Mask checking. Show AHI Vis AHI Breathing Not Detected A period during a session where the machine could not detect flow. BND BND Timed Breath Machine Initiated Breath TB TB Windows User Using , found SleepyHead - You must run the OSCAR Migration Tool <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> Launching Windows Explorer failed Could not find explorer.exe in path to launch Windows Explorer. OSCAR %1 needs to upgrade its database for %2 %3 %4 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> OSCAR does not yet have any automatic card backups stored for this device. This means you will need to import this machine data again afterwards from your own backups or data card. Important: Viktig: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Are you ready to upgrade, so you can run the new version of OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Machine Database Changes Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. The machine data folder needs to be removed manually. This folder currently resides at the following location: Rebuilding from %1 Backup Therapy Pressure Inspiratory Pressure Lower Inspiratory Pressure Higher Inspiratory Pressure Expiratory Pressure Lower Expiratory Pressure Higher Expiratory Pressure Pressure Support PS Min Pressure Support Minimum PS Max Pressure Support Maximum Min Pressure Minimum Therapy Pressure Max Pressure Maximum Therapy Pressure Ramp Time Ramp Delay Period Ramp Pressure Starting Ramp Pressure Ramp Event Ramp Vibratory Snore (VS2) A ResMed data item: Trigger Cycle Event Mask On Time Time started according to str.edf Summary Only An apnea where the airway is open An apnea caused by airway obstruction Hypopnea A partially obstructed airway Unclassified Apnea UA Vibratory Snore A vibratory snore A vibratory snore as detcted by a System One machine Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A large mask leak affecting machine performance. Non Responding Event A type of respiratory event that won't respond to a pressure increase. Expiratory Puff Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. User Flag #1 User Flag #2 User Flag #3 Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm Pulse Change A sudden (user definable) change in heart rate SpO2 Drop SpO2 dropp A sudden (user definable) drop in blood oxygen saturation SD SD Breathing flow rate waveform L/min L/min Mask Pressure Amount of air displaced per breath Graph displaying snore volume Minute Ventilation Amount of air displaced per minute Respiratory Rate Rate of breaths per minute Patient Triggered Breaths Percentage of breaths triggered by patient Pat. Trig. Breaths Leak Rate Rate of detected mask leakage I:E Ratio Ratio between Inspiratory and Expiratory time ratio Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting Cheyne Stokes Respiration An abnormal period of Cheyne Stokes Respiration CSR Periodic Breathing An abnormal period of Periodic Breathing Clear Airway Obstructive An apnea that couldn't be determined as Central or Obstructive. A restriction in breathing from normal, causing a flattening of the flow waveform. Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Leak Flag LF LF A user definable event detected by OSCAR's flow waveform processor. Perfusion Index A relative assessment of the pulse strength at the monitoring site Perf. Index % Mask Pressure (High frequency) Expiratory Time Time taken to breathe out Inspiratory Time Time taken to breathe in Respiratory Event Graph showing severity of flow limitations Flow Limit. Target Minute Ventilation Maximum Leak The maximum rate of mask leakage Max Leaks Apnea Hypopnea Index Graph showing running AHI for the past hour Total Leak Rate Detected mask leakage including natural Mask leakages Median Leak Rate Median rate of detected mask leakage Median Leaks Respiratory Disturbance Index Graph showing running RDI for the past hour Sleep position in degrees Upright angle in degrees Movement Movement detector CPAP Session contains summary data only PAP Mode Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Apnea An apnea reportred by your CPAP machine. PAP Device Mode APAP (Variable) ASV (Fixed EPAP) ASV (Variable EPAP) Height Høyde Physical Height Notes Notater Bookmark Notes Body Mass Index How you feel (0 = like crap, 10 = unstoppable) Bookmark Start Bookmark End Last Updated Journal Notes Journal Journal 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep Brain Wave BrainWave Awakenings Number of Awakenings Morning Feel How you felt in the morning Time Awake Time spent awake Time In REM Sleep Time spent in REM Sleep Time in REM Sleep Time In Light Sleep Time spent in light sleep Time in Light Sleep Time In Deep Sleep Time spent in deep sleep Time in Deep Sleep Time to Sleep Time taken to get to sleep Zeo ZQ Zeo sleep quality measurement ZEO ZQ Debugging channel #1 Test #1 For internal use only Debugging channel #2 Test #2 Zero Upper Threshold Lower Threshold As you did not select a data folder, OSCAR will exit. or CANCEL to skip migration. Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. You cannot use this folder: Migrating Migrerer files filer from fra to til OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. OSCAR vil sette opp en mappe for dine data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. We suggest you use this folder: Vi foreslår at du bruker denne mappen: Click Ok to accept this, or No if you want to use a different folder. Klikk OK for å akseptere dette, eller Nei om du ønsker å bruke en annen mappe. Choose or create a new folder for OSCAR data Velg eller lag en ny mappe for OSCAR-data Next time you run OSCAR, you will be asked again. Neste gang du kjører OSCAR, så vil du bli spurt igjen. The folder you chose is not empty, nor does it already contain valid OSCAR data. Mappen du valgte er ikke tom, den inneholder helle ikke gyldig OSCAR-data. Data directory: Datamappe: Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? Question Spørsmål Exiting Are you sure you want to use this folder? Er du sikker på at du vil bruke denne mappen? Don't forget to place your datacard back in your CPAP machine OSCAR Reminder You can only work with one instance of an individual OSCAR profile at a time. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Loading profile "%1"... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Unable to create zip! Are you sure you want to reset all your channel colors and settings to defaults? Are you sure you want to reset all your waveform channel colors and settings to defaults? There are no graphs visible to print Would you like to show bookmarked areas in this report? Printing %1 Report %1 Report : %1 hours, %2 minutes, %3 seconds RDI %1 AHI %1 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 Entire Day's Flow Waveform Current Selection Entire Day Page %1 of %2 Days: %1 Low Usage Days: %1 (%1% compliant, defined as > %2 hours) (Sess: %1) Bedtime: %1 Waketime: %1 (Summary Only) There is a lockfile already present for this profile '%1', claimed on '%2'. Fixed Bi-Level Auto Bi-Level (Fixed PS) Auto Bi-Level (Variable PS) 99.5% 90% {99.5%?} varies n/a Fixed %1 (%2) Min %1 Max %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> (last night) (1 day ago) (%2 days ago) No oximetry data has been imported yet. Contec CMS50 Fisher & Paykel ICON DeVilbiss Intellipap SmartFlex Settings ChoiceMMed MD300 Respironics M-Series Philips Respironics System One ResMed ResMed S9 S9 EPR: Somnopose Somnopose Software Zeo Personal Sleep Coach Database Outdated Please Rebuild CPAP Data (%2 min, %3 sec) (%3 sec) Pop out Graph The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View There is no data to graph d MMM yyyy [ %1 - %2 ] Hide All Events Show All Events Unpin %1 Graph Popout %1 Graph Pin %1 Graph Plots Disabled Duration %1:%2:%3 AHI %1 Relief: %1 Hours: %1h, %2m, %3s Machine Information Maskininformasjon Journal Data OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR will not touch this folder, and will create a new one instead. Please be careful when playing in OSCAR's profile folders :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. OSCAR picked only the first one of these, and will use it in future: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. CMS50F3.7 CMS50F Backing up files... Reading data files... SmartFlex Mode Intellipap pressure relief mode. Ramp Only Full Time SmartFlex Level Intellipap pressure relief level. Snoring event. SN Locating STR.edf File(s)... Cataloguing EDF Files... Queueing Import Tasks... Finishing Up... CPAP Mode CPAP-modus VPAPauto ASVAuto iVAPS PAC Auto for Her EPR ResMed Exhale Pressure Relief Patient??? EPR Level Exhale Pressure Relief Level Response Patient View SmartStart Machine auto starts by breathing Smart Start Humid. Status Humidifier Enabled Status Humid. Level Humidity Level Temperature ClimateLine Temperature Temp. Enable ClimateLine Temperature Enable Temperature Enable AB Filter Antibacterial Filter Pt. Access Essentials Plus Climate Control Manual Soft Standard Standard BiPAP-T BiPAP-S BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Advanced Avansert Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... Auto Mask ResMed Mask Setting Pillows Full Face Nasal Ramp Enable Weinmann SOMNOsoft2 Snapshot %1 CMS50D+ CMS50E/F Loading %1 data for %2... Scanning Files Migrating Summary File Location Loading Summaries.xml.gz Loading Summary Data Please Wait... Vennligst vent... %1 Charts %1 of %2 Charts Loading summaries Updating Statistics cache Usage Statistics Dreem Your Viatom device generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Viatom Viatom Software New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Se etter OSCAR-oppdateringer Unable to check for updates. Please try again later. SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank about:blank SessionBar %1h %2m %1h %2m No Sessions Present Ingen-økter tilstede SleepStyleLoader Import Error Viktig feilmelding This Machine Record cannot be imported in this profile. Dette maskinopptaket kan ikke bli importert i denne profilen. The Day records overlap with already existing content. Denne dagens opptak overlapper med allerede eksiterende innhold. Statistics CPAP Statistics CPAP statistikk CPAP Usage CPAP bruk Average Hours per Night Gjennomsnittlig timer per natt Therapy Efficacy Terapieffektivitet Leak Statistics Lekkasjestatistikk Pressure Statistics Trykkstatistikk Oximeter Statistics Oksymeterstatistikk Blood Oxygen Saturation Blodoksygenmetning Pulse Rate Puls %1 Median %1 median Average %1 Gjennomsnitt %1 Min %1 Min %1 Max %1 Max %1 %1 Index %1 indeks % of time in %1 % av tid i %1 % of time above %1 threshold % av tid over %1 grense % of time below %1 threshold % av tid under %1 grense Name: %1, %2 Navn: %1, %2 DOB: %1 DOB: %1 Phone: %1 Telefon: %1 Email: %1 Epost: %1 Address: Adresse: This report was prepared on %1 by OSCAR %2 Denne rapporten ble forberedt på %1 av OSCAR %2 Days Used: %1 Dager brukt: %1 Low Use Days: %1 Dager med lav bruk: %1 Compliance: %1% Samsvar: %1% Days AHI of 5 or greater: %1 Dager med AHI på 5 eller mer: %1 Best AHI Beste AHI Date: %1 AHI: %2 Dato: %1 AHI: %2 Worst AHI Verste AHI Best Flow Limitation Beste flytbegrensning Date: %1 FL: %2 Dato %1 FL: %2 Worst Flow Limtation Verste flytbegrensning No Flow Limitation on record Ingen flytbegrensning registrert Worst Large Leaks Verste store lekkasjer Date: %1 Leak: %2% Dato: %1 Lekkasje: %2% No Large Leaks on record Ingen store lekkasjer registrert Worst CSR Verste CSR Date: %1 CSR: %2% Dato: %1 CSR: %2% No CSR on record Ingen CSR registrert Worst PB Verste PB Date: %1 PB: %2% Dato: %1 PB: %2% No PB on record Ingen PB registrert Want more information? Vil du ha mer informasjon? OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR trenger all sammendragsdata lastet for å beregne beste/verste data for individuelle dager. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Aktiver avmerkingsboksen pre-last oppsummering i preferanser for å sikre at disse dataene er tilgjengelige. Best RX Setting Beste RX innstilling Date: %1 - %2 Dato: %1 - %2 AHI: %1 AHI: %1 Total Hours: %1 Totale timer: %1 Worst RX Setting Verste RX innstilling Most Recent Senest Compliance (%1 hrs/day) Samsvar (%1 tmr/dag OSCAR is free open-source CPAP report software OSCAR er fri åpen kildekode CPAP-rapportprogramvare Changes to Machine Settings Endringer i maskininnstillinger No data found?!? Ingen data funnet?!? Oscar has no data to report :( Oscar har ingen data å rapportere :( Last Week Siste uke Last 30 Days Siste 30 dager Last 6 Months Siste 6 måneder Last Year Siste år Last Session Siste økt Details Detaljer No %1 data available. Ingen %1 data tilgjengelig. %1 day of %2 Data on %3 %1 dag av %2 data på %3 %1 days of %2 Data, between %3 and %4 %1 dager av %2 data, mellom %3 og %4 Days Dager Pressure Relief Trykkavlastning Pressure Settings Trykkinnstillinger Machine Information Maskininformasjon First Use Første bruk Last Use Siste bruk Welcome Welcome to the Open Source CPAP Analysis Reporter Velkommen til åpen kildekode CPAP analyse og rapportering What would you like to do? Hva har du lyst til å gjøre? CPAP Importer CPAP-importør Oximetry Wizard Oksimetriveiviser Daily View Daglig visning Overview Oversikt Statistics Statistikk <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SD-kort må være låst </span><span style=" font-weight:600; color:#ff0000;">før de settes inn i din datamaskin.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Noen operativsystemer skriver indeksfiler til kortet uten å spørre, noe som kan gjøre kortet ditt uleselig av cpap-maskinen din.</span></p></body></html> It would be a good idea to check File->Preferences first, Det ville være en god idé å sjekke Fil-> Innstillinger først, as there are some options that affect import. ettersom det er noen alternativer som påvirker import. Note that some preferences are forced when a ResMed machine is detected Merk at noen preferanser blir tvunget når en ResMed-maskin blir oppdaget First import can take a few minutes. Første import kan ta noen minutter. The last time you used your %1... Siste gang du brukte din %1 ... last night i går kveld %2 days ago %2 dager siden was %1 (on %2) var %1 (på %2) %1 hours, %2 minutes and %3 seconds %1 timer, %2 minutter og %3 sekunder Your machine was on for %1. Din maskin var på for %1. <font color = red>You only had the mask on for %1.</font> <font color = red>Du hadde kun masken på i %1.</font> under under over over reasonably close to rimelig nær equal to lik You had an AHI of %1, which is %2 your %3 day average of %4. Du hadde en AHI på %1, som er %2 ditt %3 dagers gjennomsnitt på %4. Your CPAP machine used a constant %1 %2 of air CPAP-maskinen din brukte konstant %1 %2 med luft Your pressure was under %1 %2 for %3% of the time. Trykket var under %1 %2 for %3 av tiden. Your machine used a constant %1-%2 %3 of air. Din maskin brukte konstant %1-%2 %3 med luft. Your EPAP pressure fixed at %1 %2. Ditt EPAP-trykk fast på %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Ditt IPAP-trykk var under %1 %2 for %3 av tiden. Your EPAP pressure was under %1 %2 for %3% of the time. Ditt EPAP-trykk var under %1 %2 for %3 av tiden. Your machine was under %1-%2 %3 for %4% of the time. Din maskin var under %1-%2 %3 for %4 av tiden. 1 day ago 1 dag siden Your average leaks were %1 %2, which is %3 your %4 day average of %5. Din gjennomsnittlige lekkasje var %1 %2. noe som er %3 av ditt %4 daglige snitt på %5. No CPAP data has been imported yet. Ingen CPAP-data er importert ennå. gGraph %1 days %1 dager gGraphView 100% zoom level 100% zoomnivå Restore X-axis zoom to 100% to view entire selected period. Gjenopprett zoom på X-aksen til 100% for å se hele valgt periode. Restore X-axis zoom to 100% to view entire day's data. Gjenopprett zoom på X-aksen til 100% for å se hele dagens data. Reset Graph Layout Tilbakestile grafutseende Resets all graphs to a uniform height and default order. Tilbakestiller alle grafer til ensartet høyde og standardrekkefølge. Y-Axis Y-akse Plots Plotter CPAP Overlays CPAP-overlegg Oximeter Overlays Oksymeteroverlegg Dotted Lines Prikkete linjer Double click title to pin / unpin Click and drag to reorder graphs Dobbeltklikk tittele for å fest /løsne Klikk og dra for å ordne grafer på nytt Remove Clone Fjern klone Clone %1 Graph Klone %1 graf OSCAR-code-v1.3.1/Translations/Polski.pl.ts000066400000000000000000014672461417327530600204210ustar00rootroot00000000000000 AboutDialog &About &O programie Release Notes Uwagi do wydania Credits Zasługi GPL License Licencja GPL Close Zamknij Show data folder Pokaż folder danych Sorry, could not locate About file. Przepraszam, nie znaleziono pliku O programie. Sorry, could not locate Credits file. Przepraszam, nie znaleziono pliku Zasługi. Important: Ważne: To see if the license text is available in your language, see %1. Aby sprawdzić, czy tekst licencji jest dostępny w Twoim języku, zobacz %1. Sorry, could not locate Release Notes. Nie mogę znaleźć notatek o wydaniu. As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Poniewaz jest to wersja rozwojowa, rekomendujemy <b>samodzielne zarchiwizowanie folderu danych</b> przed kontynuacją , ponieważ próba przywrócenia poprzedniej wersji może się nie udać. About OSCAR %1 O programie OSCAR %1 OSCAR %1 OSCAR %1 CMS50F37Loader Could not find the oximeter file: Nie można znaleźć pliku pulsoksymetru: Could not open the oximeter file: Nie można otworzyć pliku pulsoksymetru: CMS50Loader Could not get data transmission from oximeter. Nie można uzyskać przekazu danych z pulsoksymetru. Please ensure you select 'upload' from the oximeter devices menu. no Proszę sprawdzić czy wybrał "wysyłaj" w menu pulsoksymetru. Could not find the oximeter file: Nie można znaleźć pliku pulsoksymetru: Could not open the oximeter file: Nie można otworzyć pliku pulsoksymetru: CheckUpdates Checking for newer OSCAR versions Sprawdzanie nowszych wersji OSCARa Daily Go to the previous day Idź do dnia poprzedzającego Show or hide the calender Pokaż/ukryj kalendarz Go to the next day Idź do następnego dnia Go to the most recent day with data records Idź do najpóźniejszego dnia z zapisanymi danymi Events Zdarzenia View Size Pokaż rozmiar Notes Notatki Journal dziennik B B u u Color Kolor Small Mały Medium Średni Big Duży Zombie Zombie I'm feeling ... Czuję ... Weight Waga Awesome Super B.M.I. B.M.I. Bookmarks Zakładki Add Bookmark Dodaj zakładkę Starts Rozpoczyna Remove Bookmark Usuń zakładkę Flags Flagi Graphs Wykresy Show/hide available graphs. Pokaż/ukryj dostępne wykresy. Breakdown Rozkład events zdarzenia UF1 UF1 UF2 UF2 Time at Pressure Czas z ciśnieniem No %1 events are recorded this day Tego dnia zarejestrowano %1 zdarzeń %1 event %1 zdarzenie %1 events %1 zdarzeń Session Start Times Czas rozpoczęcia sesji Session End Times Czas zakończenia sesji Session Information Informacje o sesji Oximetry Sessions Sesje z pulsoksymetrem Duration Czas trwania CPAP Sessions Sesje CPAP Sleep Stage Sessions Sesje faz snu Position Sensor Sessions Sesje z czujnikiem pozycji Unknown Session Nieznana sesja Machine Settings Ustawienia aparatu Model %1 - %2 Model %1 - %2 PAP Mode: %1 Tryb PAP- %1 This day just contains summary data, only limited information is available. Ten dzień zawiera tylko dane sumaryczne, jest dostępna tylko ograniczona informacja. Total ramp time Całkowity czas rampy Time outside of ramp Czas poza rampą Start Początek End Koniec Unable to display Pie Chart on this system Nie można pokazać wykresu kołowego w tym systemie 10 of 10 Event Types 10 z 10 zdarzeń This CPAP machine does NOT record detailed data To Urządzeniea CPAP NIE rejestruje szczegółowych danych Sorry, this machine only provides compliance data. Niestety ten aparat dostarcza tylko danych o zgodności. "Nothing's here!" "Tu nic nie ma!" 10 of 10 Graphs 10 z 10 wykresów Oximeter Information Informacje pulsoksymetru Details Szczegóły Click to %1 this session. Kliknij aby %1 tę sesję. disable wyłączyć enable włączyć %1 Session #%2 %1 sesja #%2 %1h %2m %3s %1h %2m %3s SpO2 Desaturations Desaturacje SpO2 Pulse Change events Zdarzenia zmiany pulsu SpO2 Baseline Used Użyta linia podstawowa SpO2 Statistics Statystyki Total time in apnea Całkowity czas bezdechu Time over leak redline Czas powyżej ostrzegawczej linii wycieku Event Breakdown Rozkład zdarzeń Sessions all off! Wszystkie sesje wyłączone! Sessions exist for this day but are switched off. Są sesje dla tego dnia, ale są wyłączone. Impossibly short session Niemożliwie krótka sesja Zero hours?? Zero godzin?? BRICK :( CEGŁA :( Complain to your Equipment Provider! Poskarż się sprzedawcy sprzętu! Pick a Colour Wybierz kolor Bookmark at %1 Zrób zakładkę przy %1 No data is available for this day. Brak danych dla tego dnia. If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Jeśli w preferencjach wzrost jest powyżej zera, podanie wagi spowoduje wyliczenie BMI <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Uwaga</b> Wszystkie ustawienia podane ponizej są oparte na założeniu, że nic się nie zmieniło w poprzedzających dniach. This bookmark is in a currently disabled area.. Ta zakładka nie jest aktuallnie obsługiwana. (Mode and Pressure settings missing; yesterday's shown.) (Brak ustawień trybu i ciśnienia - pokazuję wczorajsze.) 99.5% 99.5% ExportCSV Export as CSV Eksportuj jako CSV Dates: Daty: Resolution: Rozdzielczość: Details Szczegóły Sessions Sesje Daily Dziennie Filename: Nazwa pliku: Cancel Skasuj Export Wyeksportuj Start: Początek: End: Koniec: Quick Range: Szybki zakres: Most Recent Day Najnowszy dzień Last Week Ostatni tydzień Last Fortnight Ostatnie dwa tygodnie Last Month Ostatni miesiąc Last 6 Months Ostatnie 6 miesięcy Last Year Ostatni rok Everything Wszystko Custom Wybór własny Details_ Szczegóły_ Sessions_ Sesje_ Summary_ Podsumowanie_ Select file to export to Wybór pliku do eksportu CSV Files (*.csv) Pliki CSV (*.csv) DateTime Data i czas Session Sesja Event Zdarzenie Data/Duration Data/Czas trwania Date Data Session Count Ilość sesji Start Początek End Koniec Total Time Czas całkowity AHI AHI Count Ilość FPIconLoader Import Error Błąd importu This Machine Record cannot be imported in this profile. Ten zapis z aparatu nie moze być zaimportowany do tego profilu. The Day records overlap with already existing content. Zapis z dnia nakłada się na istniejący zapis. Help Hide this message Ukryj tę wiadomość Search Topic: Temat wyszukiwania: Help Files are not yet available for %1 and will display in %2. Pliki pomocy dla %1 nie są dotąd dostępne i będą wyświetlone w %2. Help files do not appear to be present. Nie ma plików pomocy. HelpEngine did not set up correctly Pomoc nie jest prawidłowo ustawiona HelpEngine could not register documentation correctly. Pomoc nie może prawidłowo zarejestrować dokumentacji. Contents Zawartość Index Indeks Search Wyszukaj No documentation available Brak dostępnej dokumentacji Please wait a bit.. Indexing still in progress Poczekaj, indeksowanie w trakcie Poczekaj, indeksowanie w trakcie No Nie %1 result(s) for "%2" %1 wynik(ów) dla "%2" clear czysty MD300W1Loader Could not find the oximeter file: Nie można znaleźć pliku pulsoksymetru: Could not open the oximeter file: Nie można otworzyć pliku pulsoksymetru: Nie można otworzyć pliku pulsoksymetru: MainWindow &Statistics &Statystyki Report Mode Tryb raportu Standard Standard Monthly Miesięcznie Date Range Zakres dat Statistics Statystyki Daily Dziennie Overview Przegląd Oximetry Pulsoksymetria Import Import Help Pomoc &File &Plik &View &Widok &Help &Pomoc &Data &Dane &Advanced &Zaawansowane Report an Issue Zgłoś problem Rebuild CPAP Data Przebuduj dane CPAP &Preferences &Preferencje &Profiles &Profile Show Performance Information Pokaż informację o wydajności CSV Export Wizard Kreator ekportu CSV Export for Review Eksport do przeglądu E&xit W&yjście Exit Wyjście View &Daily Widok &Dziennie View &Overview Widok &Przegląd View &Welcome Widok &Witaj Use &AntiAliasing Użyj &AntiAliasing Show Debug Pane Pokaż okno debugowania Take &Screenshot Zrób &Zrzut ekranu O&ximetry Wizard Kreator Pulso&xymetru Print &Report Drukuj &Raport &Edit Profile &Edytuj profil Daily Calendar Kalendarz Dzienny Backup &Journal Zapisz &Dziennik Online Users &Guide &Przewodnik użytkownika online Profiles Profile Purge Oximetry Data Wyczyść dane pulsoksymetru &About OSCAR &O programie OSCAR &Frequently Asked Questions &Często zadawane pytania &Automatic Oximetry Cleanup &Automatyczne czyszczenie danych pulsoksymetrii Change &User Zmień &Użytkownika Purge &Current Selected Day Wyczyść &wybrany dzień Right &Sidebar Prawy &pasek boczny Daily Sidebar Dzienny pasek boczny View S&tatistics Pokaż s&tatystyki Navigation Nawigacja Bookmarks Zakładki Records Zapisy Exp&ort Data Wyeksp&ortuj dane View Statistics Pokaż statystyki Import &ZEO Data Importuj dane &ZEO Import RemStar &MSeries Data Importuj dane RemStar &MSeries Sleep Disorder Terms &Glossary Terminologia zaburzeń snu &Słownik Change &Language Zmień &Język Change &Data Folder Zmień folder &danych Import &Somnopose Data Importuj dane pozycji &snu Current Days Bieżące dni Welcome Witaj &About &O programie Please wait, importing from backup folder(s)... Proszę czekać, importuję z kopii zapasowej folderu (ów)... Import Problem Problem z importem Please insert your CPAP data card... Proszę włóż kartę danych CPAP... Access to Import has been blocked while recalculations are in progress. Dostęp do importu został zablokowany podczas trwających przeliczeń. CPAP Data Located Zlokalizowano dane CPAP Import Reminder Przypomnienie o imporcie Importing Data Importuję dane Help Browser Przeglądarka pomocy Loading profile "%1" Ładowanie profilu:"%1" Import is already running in the background. Import działa w tle. Please open a profile first. Proszę najpierw otworzyć profil. The FAQ is not yet implemented FAQ nie działa If you can read this, the restart command didn't work. You will have to do it yourself manually. Jeżeli to widać, restart się nie powiódł. Trzeba to zrobić ręcznie. Are you sure you want to rebuild all CPAP data for the following machine: Na pewno chcesz przebudować wszystkie dane dla : For some reason, OSCAR does not have any backups for the following machine: Z pewnych powodów OSCAR nie ma żadnych kopii zapasowych dla: You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Usuwasz <font size=+2>bezpowrotnie</font> bazę danych dla aparatu:</p> Export review is not yet implemented Podgląd eksportu chwilowo niedostępny Reporting issues is not yet implemented Zgłaszanie problemów chwilowo niedostępne %1's Journal Dziennik %1 Choose where to save journal Wybierz, gdzie zapisać dziennik XML Files (*.xml) Pliki XML (*.xml) Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Jeżeli utworzyłeś <i>swoje<b>własne</b>kopie zapasowe WSZYSTKICH swoich danych CPAP</i> nadal możesz wykonać tą czynność, ale będziesz musiał ręcznie przywrócić dane. Are you really sure you want to do this? Naprawdę chcesz to zrobić? Because there are no internal backups to rebuild from, you will have to restore from your own. Ponieważ brak wewnętrznej kopii zapasowej, musisz użyć własnej do odbudowy. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Czy chcesz importować z własnych kopii zapasowych teraz? (nie będziesz widział żadnych danych z tego aparatu dopóki nie importujesz) Note as a precaution, the backup folder will be left in place. Uwaga, folder kopii zapasowych pozostanie na swoim miejscu. Are you <b>absolutely sure</b> you want to proceed? Jesteś <b>absolutnie pewny</b>, że chcesz to zrobić? A file permission error casued the purge process to fail; you will have to delete the following folder manually: Z uwagi na prawa dostępu do pliku usuwanie nie powiodło się, musisz ręcznie usunąć folder : No help is available. Brak pomocy. Are you sure you want to delete oximetry data for %1 Jesteś pewny, że chcesz usunąć dane pulsoksymetrii dla %1 <b>Please be aware you can not undo this operation!</b> <b>UWAGA! Tej operacji nie da się cofnąć!</b> Select the day with valid oximetry data in daily view first. Najpierw należy wybrać dzień z ważnymi danymi pulsoksymetrii w widoku dziennym. Imported %1 CPAP session(s) from %2 Zaimportowano %1 sesji CPAP %2 Import Success Import zakończony pomyślnie Already up to date with CPAP data at %1 Aktualne z danymi CPAP na %1 Up to date Aktualne Couldn't find any valid Machine Data at %1 Nie mogę znaleźć odpowiednich danych w %1 Choose a folder Wybierz folder A %1 file structure for a %2 was located at: Struktura plików %1 dla %2 jest zlokalizowana w: A %1 file structure was located at: Struktura plików %1 została zlokalizowana w: Would you like to import from this location? Czy chcesz importować z tej lokalizacji? Specify Sprecyzuj Access to Preferences has been blocked until recalculation completes. Dostęp do preferencji został zablokowany do czasu zakończenia obliczeń. There was an error saving screenshot to file "%1" Błąd przy zapisywaniu zrzutu ekranu do pliku "%1" Screenshot saved to file "%1" Zrzut ekranu zapisany do pliku "%1" There was a problem opening MSeries block File: Problem z otwarciem pliku blokującego MSeries: MSeries Import complete Ukończono import MSeries Please note, that this could result in loss of data if OSCAR's backups have been disabled. Gdy kopie zapasowe są wyłączone lub zaburzone w inny sposób, może to spowodować utratę danych. No profile has been selected for Import. Nie wybrano profilu do importu. &Maximize Toggle &Przełącznik Maksymalizowania The User's Guide will open in your default browser Podręcznik użytkownika otworzy się w oknie domyślnej przeglądarki The Glossary will open in your default browser Słownik otworzy się w oknie domyślnej przeglądarki Show Daily view Pokaż widok dzienny Show Overview view Pokaż Przegląd Maximize window Maksymalizuj okno Reset sizes of graphs Zresetuj wielkość wykresów Show Right Sidebar Pokaż prawy pasek boczny Show Statistics view Pokaż Statystyki Show &Line Cursor Pokaż kursor &linii Show Daily Left Sidebar Pokaż lewy pasek boczny dla widoku dziennego Show Daily Calendar Pokaż kalendarz System Information Informacje systemowe Show &Pie Chart Pokaż wykres &ciasteczkowy Show Pie Chart on Daily page Pokaż wykres ciasteczkowy w widoku dziennym OSCAR Information OSCAR - Informacje &Reset Graphs &Resetuj wykresy Reset Graph &Heights Resetuj wykresy i &wysokości Standard graph order, good for CPAP, APAP, Bi-Level Standardowy układ wykresów, dobry dla CPAP, APAP, Bi-Level Advanced Zaawansowany Advanced graph order, good for ASV, AVAPS Zaawansowany układ wykresów, dobry dla ASV, AVAPS Troubleshooting Rozwiązywanie problemów Purge ALL Machine Data Wyczyść wszystkie dane aparatu &Import CPAP Card Data &Importuj dane karty CPAP Import &Dreem Data Importuj dane &Dreem Create zip of CPAP data card Utwórz archiwum zip danych karty CPAP Create zip of all OSCAR data Utwórz archiwum zip wszystkich danych OSCAR %1 (Profile: %2) %1 (Profile: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Proszę, pamiętaj wybrać folder podstawowy lub literę dysku karty, a nie folderu wewnętrznego. Choose where to save screenshot Wybierz, gdzie zapisać zrzut ekranu Image files (*.png) Pliki obrazu (*.png) OSCAR does not have any backups for this machine! OSCAR nie ma żadnych plików zapasowych dla tego aparatu! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Dopóki nie utworzysz <i>swoich <b>własnych </b> kopii zapasowych WSZYSTKICH danych dla tego aparatu</i>, <font size=+2>utracisz wszwszystkie dane tego aparatu <b>trwale</b>!</font> Would you like to zip this card? Czy chcesz zarchiwizować tę kartę? Choose where to save zip Wybierz, gdzie zapisać zip ZIP files (*.zip) Pliki ZIP (*.zip) Creating zip... Tworzenie archiwum... Calculating size... Obliczanie rozmiaru... Show Personal Data Pokaż dane osobiste Create zip of OSCAR diagnostic logs Utwórz zip logów diagnostycznych OSCARa Check For &Updates Sprawdź &Uaktualnienia Check for updates not implemented Sprawdzanie uaktualnień nie wprowadzone Import &Viatom/Wellue Data Import danych &Viatom/Wellue Purge Current Selected Day Wyczyść aktualnie wybrany dzień &CPAP &CPAP &Oximetry &Pulsoksymetria &Sleep Stage Faza &Snu &Position &Pozycja &All except Notes &wszystko oprócz notatek All including &Notes Wszystko włącznie z &notatkami Find your CPAP data card Znajdź kartę danych CPAP There was a problem opening %1 Data File: %2 Był problem z otwarciem %1 pliku danych:%2 %1 Data Import of %2 file(s) complete %1 import danych z %2 plik(ów zakończony %1 Import Partial Success %1 częściowe powodzenie importu %1 Data Import complete %1 import zakończony MinMaxWidget Auto-Fit Autodopasowanie Defaults Domyślne Override Nadpisanie The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Tryb skalowania osi Y. "Autodopasowanie" dla skalowania automatycznego, "Domyślne" dla ustawień odpowiednio dla producenta, "Nadpisanie" dla wyboru własnego. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Minimalna wartość osi Y. Może być liczbą ujemną jeśli tak chcesz. The Maximum Y-Axis value.. Must be greater than Minimum to work. Maksymalna wartość osi Y. Musi być większa od minimalnej. Scaling Mode Tryb skalowania This button resets the Min and Max to match the Auto-Fit Ten przycisk resetuje Min i Max do Autodopasowania NewProfile Edit User Profile Edytuj profil użytkownika I agree to all the conditions above. Zgadzam się na wszystkie powyższe warunki. User Information Informacje o użytkowniku User Name Nazwa użytkownika Password Protect Profile Profil chroniony hasłem Password Hasło ...twice... ...dwukrotnie... Locale Settings Ustawienia lokalizacji Country Kraj TimeZone Strefa czasowa about:blank about:blank DST Zone Strefa czasu letniego Personal Information (for reports) Informacje osobiste (do raportów) First Name Imię Last Name Nazwisko It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Oczywiście można to pominąć, ale przybliżony wiek jest potrzebny do precyzyjniejszych obliczeń. D.O.B. Data urodzenia. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Płeć biologiczna jest czasem potrzebna do precyzyjniejszych obliczeń, ale można to pominąć.</p></body></html> Gender Płeć Male Męska Female Żeńska Height Wzrost Contact Information Informacje kontaktowe Address Adres Email Email Phone Telefon CPAP Treatment Information Leczenie CPAP Date Diagnosed Data diagnozy Untreated AHI AHI bez leczenia CPAP Mode Tryb CPAP CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure Ciśnienie RX Doctors / Clinic Information Informacje o lekarzu/klinice Doctors Name Nazwisko lekarza Practice Name Nazwa praktyki Patient ID ID pacjenta &Cancel &Skasuj &Back &Wstecz &Next &Następny Select Country Wybierz kraj This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Ten program jest przeznaczony do pomocy w ocenie danych z aparatu CPAP i akcesoriów. PLEASE READ CAREFULLY PROSZĘ UWAŻNIE PRZECZYTAĆ Accuracy of any data displayed is not and can not be guaranteed. Dokładność przedstawionych danych nie jest gwarantowana. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Jakiekolwiek raporty są przeznaczone do UŻYTKU OSOBISTEGO i W ŻADNYM WYPADKU nie służą do celów diagnostyki medycznej. Use of this software is entirely at your own risk. Używasz tego programu wyłącznie na własne ryzyko. OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR jest udostępniany jako wolne oprogramowanie pod <a href='qrc:/COPYING'>GNU Public License v3</a>, i nie daje gwarancji ani praw do roszczeń z jakiegokolwiek powodu. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR ma służyć pomocą w przeglądaniu danych CPAP, ale nie zastępowaniu pomocy lekarskiej. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Autorzy nie ponoszą odpowiedzialności za cokolwiek mającego związek z używaniem oprogramowania. Please provide a username for this profile Proszę podać nazwę użytkownika dla tego profilu Passwords don't match Hasła nie są jednakowe Profile Changes Zmiany profilu Accept and save this information? Czy zaakceptować i zapisać informacje? &Finish &Zakończ &Close this window &Zamknij okno Welcome to the Open Source CPAP Analysis Reporter Witaj w OSCAR Metric metryczny English angielski OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team Very weak password protection and not recommended if security is required. Bardzo słaba ochrona hasłem, nie rekomendowana jeżeli wymagane jest bezpieczeństwo. Overview Range: Zakres: Last Week Ostatni tydzień Last Two Weeks Ostatnie 2 tygodnie Last Month Ostatni miesiąc Last Two Months Ostatnie dwa miesiące Last Three Months Ostatnie trzy miesiące Last 6 Months Ostatnie 6 miesięcy Last Year Ostatni rok Everything Wszystko Custom Własne Start: Początek: End: Koniec: Reset view to selected date range Zresetuj widok do wybranego zakresu dat Toggle Graph Visibility Przełącz widoczność wykresów Drop down to see list of graphs to switch on/off. Pokaż listę wykresów do włączenia/wyłączenia. Graphs Wykresy Respiratory Disturbance Index Wskaźnik Zaburzeń Oddechowych Apnea Hypopnea Index Wskaźnik Niedotlenienie Bezdech Usage Użycie Usage (hours) Użycie (godziny) Session Times Czasy sesji Total Time in Apnea Całkowity czas bezdechu Total Time in Apnea (Minutes) Całkowity czas bezdechu (minuty) Body Mass Index Indeks Masy Ciała (BMI) How you felt (0-10) Jak się czułeś (0-10) 10 of 10 Charts 10 z 10 wykresów Show all graphs Pokaż wszystkie wykresy Hide all graphs Ukryj wszystkie wykresy Snapshot migawka OximeterImport Oximeter Import Wizard Kreator importu pulsoksymetrii Skip this page next time. Następnym razem pomiń tę stronę. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Uwaga: </span><span style=" font-style:italic; ">Najpierw wybierz właściwy typ pulsoksymetru z menu rozwijanego poniżej.</span></p></body></html> Where would you like to import from? Skąd chcesz importować? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">NAJPIERW Wybierz swój pulsoksymetr z tych grup:</span></p></ body></html> CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. Użytkownicy CMS50E/F, jeśli importujecie bezpośrednio, nie wybierajcie wysyłki zanim OSCAR poprosi o to. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Jeżeli jest to włączone, OSCAR automatycznie zresetuje zegar wewnętrzny CMS50 używając bieżącego czasu komputera.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Jeśli nie przeszkadza Ci bycie podłączonym do włączonego komputera całą noc, ta opcja dostarczy wykresu pletyzmograficznego, który pokaże rytm pracy serca powyżej normalnych wskazań SpO2. </p></body></html> Record attached to computer overnight (provides plethysomogram) Zapis podłączenia do komputera przez noc(dostarcza pletyzmogram) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Ta opcja pozwala na importowanie z plików danych utworzonych przez oprogramowanie pulsoksymetru, jak np. SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Importuj z plików danych zachowanych przez inny program, np. SpO2Review Please connect your oximeter device Proszę podłączyć pulsoksymetr If you can read this, you likely have your oximeter type set wrong in preferences. Jeśli można to przeczytać, prawdopodobnie źle ustawiono typ pulsoksymetru w preferencjach. Press Start to commence recording Naciśnij Start aby rozpocząć zapis Show Live Graphs Pokaż wykresy Duration Czas trwania Pulse Rate Częstość pulsu Multiple Sessions Detected Wykryto wiele sesji Start Time Czas rozpoczęcia Details Szczegóły Import Completed. When did the recording start? Import zakończony. Jaki był czas rozpoczęcia zapisu? Oximeter Starting time Czas uruchomienia pulsoksymetru I want to use the time reported by my oximeter's built in clock. Chcę użyć czasu wewnętrznego zegara pulsoksymetru. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Wystartowałem pulsoksymetr o niemal tym samym czasie co aparat CPAP. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Uwaga: zsynchronizowanie z z rozpoczęciem sesji CPAP zawsze będzie dokładniejsze.</p></body></html> Choose CPAP session to sync to: Wybierz sesję CPAP do synchronizacji: You can manually adjust the time here if required: Tu możesz ręcznie dopasować czas, jeśli potrzebne: HH:mm:ssap HH:mm:ssap &Cancel &Skasuj &Information Page &Strona informacyjna CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Ustaw czas i datę urządzenia <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Zaznacz w celu włączenia uaktualnienia identyfikatora urządzenia przy następnym imporcie. Przydatne jak masz kilka pulsoksymetrów.</p></body></html> Set device identifier Ustaw identyfikator urządzenia Erase session after successful upload Skasuj sesję po udanym przesłaniu Import directly from a recording on a device Importuj bezpośrednio z urządzenia <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Przypomnienie dla użyszkodników CPAP: </span><span style=" color:#fb0000;">Czy pamiętałeś najpierw zaimportować sesję CPAP?<br/></span>Jeśli zapomnisz, nie będziesz miał właściwego czasu do synchronizacji tej sesji pulsoksymetru..<br/>Aby zapewnić dobrą synchronizację urządzeń, zawsze staraj się startować oba w jednym czasie.</p></body></html> Please choose which one you want to import into OSCAR Proszę wybierz, który chcesz importować do programu Day recording (normally would have) started Dzienny zapis (normalnie powinien) zaczął się <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR potrzebuje czasu rozpoczęcia aby wiedzieć, gdzie zaimportować tę sesję pulsoksymetru.</p><p>Wybierz jedną z następujących opcji:</p></body></html> &Retry &Spróbuj ponownie &Choose Session &Wybierz sesję &End Recording &Koniec zapisu &Sync and Save &Synchronizuj i zapisz &Save and Finish &Zapisz i zamknij &Start &Rozpocznij Scanning for compatible oximeters Poszukiwanie kompatybilnego pulsoksymetru Could not detect any connected oximeter devices. Nie wykryto podłączonego pulsoksymetru. Connecting to %1 Oximeter Łączenie z pulsoksymetrem %1 Renaming this oximeter from '%1' to '%2' Zmiana nazwy tego pulsoksymetru z %1' na %2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Inna nazwa pulsoksymetru. Jeżeli masz tylko jeden, i używasz go w obu profilach, ustaw jednakową nazwę w obu profilach. "%1", session %2 "%1" sesja %2 Nothing to import Nic do zaimportowania Your oximeter did not have any valid sessions. Pulsoksymetr nie zawiera ważnych sesji. Close Zamknij Waiting for %1 to start Czekam na %1 z rozpoczęciem Waiting for the device to start the upload process... Czekam aż urządzenie rozpocznie przesył danych... Select upload option on %1 Wybierz opcję przesyłu danych na %1 You need to tell your oximeter to begin sending data to the computer. Musisz powiedzieć pulsoksymetrowi żeby zaczął wysyłać dane do komputera. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Proszę podłącz pulsoksymetr, wejdź do jego menu i wybierz przesyłanie danych... %1 device is uploading data... Urządzenie %1 wysyła dane... Please wait until oximeter upload process completes. Do not unplug your oximeter. Poczekaj aż pulsoksymetr zakończy przesyłanie danych. Nie odłączaj pulsoksymetru. Oximeter import completed.. Import danych pulsoksymetru zakończony.. Select a valid oximetry data file Wybierz ważny plik danych pulsoksymetru Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Pliki pulsoksymetru (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Żaden moduł pulsoksymetrii nie mógł przeanalizować danego pliku: Live Oximetry Mode Tryb live pulsoksymetrii Live Oximetry Stopped Tryb live pulsoksymetrii zatrzymany Live Oximetry import has been stopped Import w trybie live pulsoksymetrii zatrzymany Oximeter Session %1 Sesja pulsoksymetru %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR daje możliwość śledzenia danych pulsoksymetru razem z danymi CPAP, co daje wgląd na efektywność leczenia. Można też przeglądać dane osobno. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Jeżeli próbujesz zsynchronizować dane pulsoksymetru i CPAP, upewnij się, że zaimportowałeś sesje CPAP najpierw!! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Aby OSCAR mógł prawidłowo znaleźć i zaimportować dane, potrzebujesz sterowników (np. USB to Serial UART). Szukaj ich %1 tutaj%2. Oximeter not detected Nie znaleziono pulsoksymetru Couldn't access oximeter Nie ma dostępu do pulsoksymetru Starting up... Startuję ... If you can still read this after a few seconds, cancel and try again Jeśli po paru sekundach nadal to widzisz, skasuj i spróbuj ponownie Live Import Stopped Import w trybie live pulsoksymetrii zatrzymany %1 session(s) on %2, starting at %3 %1 sesje na %2, początek o %3 No CPAP data available on %1 Brak danych CPAP na %1 Recording... Zapisuję... Finger not detected Nie wykryto palca I want to use the time my computer recorded for this live oximetry session. Chcę użyć czasu który mój komputer użył do tej sesji live pulsoksymetru. I need to set the time manually, because my oximeter doesn't have an internal clock. Potrzebuję ręcznie ustawić czas, bo mój pulsoksymetr nie ma zegara. Something went wrong getting session data Coś poszło nie tak z pozyskaniem danych sesji Welcome to the Oximeter Import Wizard Witaj w Kreatorze importu pulsoksymetrii Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Pulsoksymetry to urządzenia medyczne używane do pomiaru saturacji tlenem krwi. Podczas długotrwałego bezdechu oraz u pacjentów oddychających nieprawidłowo saturacja krwi tlenem obniża się znacznie, mogąc powodować problemy wymagające obserwacji medycznej. You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Możesz zauważyć, że niektóre firmy rebrandują pulsoksymetry,jak np. Contec CMS50xx pod nową nazwą jak np. Pulox PO-200, PO-300, PO-400. One też powinny współpracować. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Może także odczytywać pliki .dat z pulsoksymetru ChoiceMMed MD300W1. Please remember: Proszę zapamiętaj: Important Notes: Ważne notatki: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Urządzenia Contec CMS50D+ nie mają wbudowanego zegara i nie rejestrują czasu rozpoczęcia sesji. Jeżeli nie masz sesji CPAP do podpięcia zapisu, musisz ręcznie wpisać czas rozpoczęcia po zakończeniu importu. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Nawet dla urządzeń z wewnętrznym zegarem warto wykształcić nawyk rozpoczynania sesji pulsoksymetru razem z CPAP, gdyż zegary CPAP potrafią być niedokładne i nie zawsze łatwo je przestawić. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR obecnie współpracuje z modelami Contec CMS50D+, CMS50E, CMS50I.( Uwaga - bezpośrednie importowanie przez bluetooth raczej niemożliwe.) CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>Tu możesz wpisać 7-literową nazwę dla tego pulsoksymetru.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Ta opcja wykasuje importowane sesje z Twojego pulsoksymatru po ukończeniu importu </p><p>Używaj ostrożnie, ponieważ w wypadku utraty danych nie da się już ich przywrócić.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Ta opcja pozwala na import danych zapisanych w pulsoksymetrze (przez kabel usb).</p><p>Po wybraniu tej opcji starsze pulsoksymetry Contex wymagają włączenia wysyłki menu urządzenia.</p></body></html> Please connect your oximeter device, turn it on, and enter the menu Porzę podłączyć pulsoksymetr, włączyć i wejść do menu Oximetry Date Data d/MM/yy h:mm:ss AP d/MM/yy h:mm:ss AP R&eset R&esetuj Pulse Puls &Open .spo/R File &Otwórz plik .spo/R Serial &Import &Import seryjny &Start Live &Rozpocznij sesję live Serial Port Port szeregowy &Rescan Ports &Ponownie skanuj porty PreferencesDialog Preferences Preferencje &Import &Importuj Combine Close Sessions Połącz krótkie sesje Minutes Minuty Multiple sessions closer together than this value will be kept on the same day. Wielokrotne sesje bliższe siebie niż dana wielkość będą zachowane w tym samym dniu. Ignore Short Sessions Ignoruj krótkie sesje <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sesje krótsze niż te nie będą wyświetlane<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Czas podziału dnia Sessions starting before this time will go to the previous calendar day. Sesje zaczynające się przed tym czasem będą zapisane z dniem poprzedzającym. Session Storage Options Opcje zapisu sesji Compress SD Card Backups (slower first import, but makes backups smaller) Kompresuj kopie zapasowe kart SD (pierwszy import wolniej, ale kopie będą mniejsze) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Uwzględniaj dni z użyciem mniejszym jako "niewystarczające". Użycie 4-godzinne zwykle jest uważane za wystarczające. hours godziny Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Włącz/wyłącz eksperymentalne wzmocnienie oznaczanie zdarzeń. Pozwala to na wykrycie zdarzeń granicznychi takich, które przegapił aparat. Ta opcja musi być włączona przed importem, w innym przypadku wymagane jest czyszczenie danych. Flow Restriction Ograniczenia przepływu Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Procent ograniczenia przepływu z wartości mediany. Wartość 20% jest wystarczająca dla wykrycia bezdechu. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Własne "flagi" są eksperymentalną metodą wykrywania zdarzeń opuszczonych przez aparat. <span style=" text-decoration: underline;">Nie są</span> one zawarte w AHI.</p></body></html> Duration of airflow restriction Czas ograniczenia przepływu powietrza s s Event Duration Czas trwania zdarzenia Allow duplicates near machine events. Pozwalaj na duplikaty zdarzeń bliskich . Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Dopasuj wielkość danych uwzględnianych dla każdego punktu wykresu AHI/godz. Domyślnie to 60 min. Zalecamy pozostawić tą wartość. minutes min Reset the counter to zero at beginning of each (time) window. Zresetuj licznik do zera na początku każdego okienka (czasu). Zero Reset Reset zero CPAP Clock Drift Niedokładność zegara CPAP Do not import sessions older than: Nie importuj sesji starszych niż: Sessions older than this date will not be imported Sesje starsze od tej daty nie będą importowane dd MMMM yyyy dd MMMM yyyy User definable threshold considered large leak Definiowany przez użytkownika próg uważany za duży wyciek Whether to show the leak redline in the leak graph Czy i kiedy pokazać na wykresie wycieku czerwoną linię Search Wyszukaj &Oximetry &Pulsoksymetria Show in Event Breakdown Piechart Pokaż na wykresie kołowym zdarzeń Resync Machine Detected Events (Experimental) Resynchronizuj zdarzenia wykryte przez aparat (eksperymentalne) Percentage drop in oxygen saturation Procentowy spadek wysycenia tlenem Pulse Puls Sudden change in Pulse Rate of at least this amount nagła zmiana pulsu co najmniej o wielkości bpm uderzeń na minutę Minimum duration of drop in oxygen saturation Minimalny czas trwania spadku wysycenia tlenem Minimum duration of pulse change event. Minimalny czas trwania zdarzenia zmiany pulsu. Small chunks of oximetry data under this amount will be discarded. Małe ilości danych pulsoksymetru (mniejsze niż ta wartość) będą odrzucane. &General &Ogólne Changes to the following settings needs a restart, but not a recalc. Zmiana następujących ustawień wymaga restartu, ale nie przeliczania. Preferred Calculation Methods Preferowane metody obliczania Middle Calculations Obliczenia pośrednie Upper Percentile Górny percentyl Session Splitting Settings Ustawienia podziału sesji <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">To ustawienie powinno być używane ostrożnie...</span>Wyłączenie skutkuje dokładnością dla dni tylko z podsumowaniem, ponieważ określone obliczenia będą poprawne gdy będą przechowywane w tym samym miejscu. </p><p><span style=" font-weight:600;">Użytkownicy ResMed:</span> Tylko dlatego, że wydaje się naturalnym że 12-godzinne wznowienie sesji powinno nastąpić poprzedniego dnia, to nie znaczy, ze dane ResMed się z tym zgadzają. Format pliku STF.edf wskaźników podsumowania ma poważne słabości, które czynią to kiepskim pomysłem.</p><p>Ta opcja jest dla uspokojenia tych co mimo wszystko chcą to widzieć &quot;naprawione&quot; bez względu na koszty, które wystąpią. Jeżeli będziesz trzymał swoją kartę SD w aparacie każdą noc, a importował dane co tydzień, nieczęsto będziesz widział podobne problemy.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Nie dziel dni w podsumowaniu(Uwaga: czytaj wskazówki!) Memory and Startup Options Opcje pamięci i rozruchu Pre-Load all summary data at startup Ładuj dane podumowań na starcie <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>To ustawienie utrzymuje dane wykresów i zdarzeń w pamięci dla przyspieszenia przy ponownym wyświetlaniu.</p><p>To nie jest konieczne, gdyż system również przechowuje wcześniej używane pliki.</p><p>Rekomenduje się pozostawienie wyłączonym, chyba że komputer cierpi na nadmiar pamięci.</p></body></html> Keep Waveform/Event data in memory Pozostaw dane wykresów i zdarzeń w pamięci <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Wycina jakiekolwiek niepotrzebne potwierdzenia podczas importu.</p></body></html> Import without asking for confirmation Importuj bez pytania o zgodę General CPAP and Related Settings Ogólne ustawienia CPAP i pochodnych Enable Unknown Events Channels Uruchom kanał nieznanych zdarzeń AHI Apnea Hypopnea Index AHI RDI Respiratory Disturbance Index RDI - indeks zaburzeń oddychania AHI/Hour Graph Time Window Okno czasowe wykresu AHI/godzina Preferred major event index Preferowany indeks głównych zdarzeń Compliance defined as Zgodność definiowana jako Flag leaks over threshold Zaznaczaj (flaguj) przecieki powyżej wątku Seconds Sekundy <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Uwaga: To nie jest przeznaczone do korekty strefy czasowej! Upewnij się, że zegar systemowy komputera i strefa czasowa są ustawione poprawnie.</p></body></html> Hours Godziny For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Dla spójności, użytkownicy ResMed powinni używać tu 95%, ponieważ jest to jedyna wartość dostępna dla dni tylko z podsumowaniem. Median is recommended for ResMed users. Mediana jest rekomendowana dla użytkowników ResMed. Median Mediana Weighted Average Średnia ważona Normal Average Średnia arytmetyczna True Maximum Prawdziwe maksimum 99% Percentile Percentyl 99% Maximum Calcs Obliczenia maksimum General Settings Ogólne ustawienia Daily view navigation buttons will skip over days without data records Przyciski nawigacji w widoku dziennym będą przeskakiwały nad dniami bez danych Skip over Empty Days Pomijaj dni bez danych Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Zezwalaj na użycie wielu rdzeni procesora gdy to możliwe, by poprawić wydajność. Głównie wpływa na import. Enable Multithreading Włącz wielowątkowość Bypass the login screen and load the most recent User Profile Omiń ekran logowania i załaduj ostatni profil użytkownika Create SD Card Backups during Import (Turn this off at your own peril!) Utwórz kopię zapasową danych karty SD podczas importu(wyłącz na własną zgubę!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Prawdziwym maksimum jest maksimum zestawu danych.</p><p>99% percentyl filtruje najrzadsze odchylenia.</p></body></html> Combined Count divided by Total Hours Połączona liczba podzielona przez całkowite godziny Time Weighted average of Indice Średnia ważona czasu wskaźników Standard average of indice Średnia arytmetyczna wskaźników Custom CPAP User Event Flagging Własne oznakowanie zdarzeń CPAP Events Zdarzenia Reset &Defaults Przywróć &Domyślne <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Uwaga: </span>To, że można, nie znaczy, że to dobry pomysł.</p></body></html> Waveforms Wykresy Flag rapid changes in oximetry stats Zaznacz (oflaguj) szybkie zmiany w pulsoksymetrii Other oximetry options Inne opcje pulsoksymetrii Discard segments under Odrzuć segmenty ponizej Flag Pulse Rate Above Oflaguj częstość tętna powyżej Flag Pulse Rate Below Oflaguj częstość tętna poniżej Show flags for machine detected events that haven't been identified yet. Pokaż flagi zdarzeń wykrytych przez aparat które dotąd nie zostały zidentyfikowane. <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <html><head/><body><p>Oznacz SpO<span style=" vertical-align:sub;">2</span> Desaturacje poniżej</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Synchronizacja danych pulsoksymetrycznych i CPAP</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Dane CMS50 importowane z SpO2Review (z plików .spoR) lub metodą importu seryjnego</span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">nie</span><span style=" font-family:'Sans'; font-size:10pt;">mają prawidłowej sygnatury czasowej potrzebnej do synchronizacji.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Tryb podglądu na żywo (przy użyciu kabla szeregowego) jest jednym ze sposobów uzyskania dokładnej synchronizacji na pulsoksymetrach CMS 50, ale nie uwzględnia dryftu zegara CPAP.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Jeśli uruchomisz tryb rejestracji pulsoksymetru o</span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">dokładnie </span><span style=" font-family:'Sans'; font-size:10pt;">w tym samym czasie, gdy uruchamiasz maszynę CPAP, możesz teraz również osiągnąć synchronizację. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Proces importu seryjnego trwa od pierwszej sesji CPAP ostatniej nocy. (Pamiętaj, aby najpierw zaimportować dane CPAP!)</span></p></body></html> Check for new version every Sprawdzaj w poszukiwaniu nowej wersji co days. dni. Last Checked For Updates: Ostatnio sprawdzano uaktualnienia: TextLabel Etykieta tekstowa &Appearance &Wygląd Graph Settings Ustawienia wykresów Bar Tops Nagłówki pasków Line Chart Wykresy liniowe Overview Linecharts Przeglądowe wykresy liniowe <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>To ułatwia przewijanie gdy powiększone na czułych dwukierunkowych touchpadach</p><p>50ms jest wartością rekomendowaną.</p></body></html> How long you want the tooltips to stay visible. Jak długo mają być widoczne wskazówki. Scroll Dampening Tłumienie przewijania Tooltip Timeout Limit czasu podpowiedzi Default display height of graphs in pixels Domyślna wysokość wykresów w pikselach Graph Tooltips Podpowiedzi dla wykresów The visual method of displaying waveform overlay flags. Metoda wizualna wyświetlania nakładających się flag wykresów. Standard Bars Paski standardowe Top Markers Najlepsze znaczniki Graph Height Wysokość wykresów Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Kompresuj kopie zapasowa ResMed (EDF) dla oszczędzenia miejsca na dysku. Pliki EDF są spakowane w formacie *.gz, jak to wiedzą użytkownicy makówek i linuksiarze. OSCAR umie tego używać. ResScan potrzebuje rozpakowanych plików. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Następujące opcje wpływają na zajęcie miejsca na dysku przez program, a także na szybkość importu. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. To powoduje, że OSCAR zabiera o połowę mniej miejsca na dysku. Ale import i zmiana dnia zabiera więcej czasu.. Jeśli masz nowy komputer z małym dyskiem SSD to jest dobry pomysł. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Kompresuj dane sesji (folder danych mniejszy, ale wolniejsza zmiana dni) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) To obsługuje kopię zapasową danych aparatów ResMed, Aparaty ResMed S9 usuwają dokładniejsze dane starsze niż 7 dni, oraz dane wykresów starsze niz 30 dni. OSCAR może zachować te dane jeśli kiedyś będziesz reinstalował. (Rekomendowane, o ile nie masz za mało miejsca albo nie dbasz o dane wykresów) Auto-Launch CPAP Importer after opening profile Automatycznie otwieraj importowanie po otwarciu profilu <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>OSCAR startuje nieco wolniej, ładuje dane podsumowań z wyprzedzeniem, ale potem działa szybciej. </p><p>Jeśli masz dużo danych, to wyłącz, chyba, że lubisz widzieć wszystko. </p><p>Zauważ, że to ustawienie nie wpływa na dane wykresów i zdarzeń.</p></body></html> Automatically load last used profile on start-up Automatycznie otwieraj ostatnio używany profil po uruchomieniu This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Te obliczenia wymagają danych o wycieku całkowitym dostarczonych pzez aparat CPAP (np. PRS1, ale nie ResMed które już to mają) Obliczenia niezamierzonych wycieków są liniowe, nie uwzględniają krzywej wentylacji maski. Jeżeli używasz kilku różnych masek, wybierz wartosć średnią. Powinno wystarczyć. Calculate Unintentional Leaks When Not Present Wylicz niezamierzone wycieki jeśli nieobecne 4 cmH2O 4 cmH20 20 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Uwaga: Użyto liniowej metody obliczania. Zmiana tych wartości wymaga przeliczenia. l/min l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Wskaźniki skumulowane</p></body></html> This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Ta eksperymentalna opcja próbuje użyć systemu flagowania OSCAR by poprawić pozycjonowanie zdarzeń wykrytych przez aparat. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Z uwagi na ograniczenia, aparaty ResMed nie obsługują zmiany tych ustawień.</p></body></html> Oximetry Settings Ustawienia pulsoksymetru Show Remove Card reminder notification on OSCAR shutdown Pokazuj przypomnienie Usuń Kartę przy zamykaniu programu I want to be notified of test versions. (Advanced users only please.) Chcę otrzymywać powiadomienia o wersjach testowych. (Tylko dla zaawansowanych użytkowników.) On Opening Przy otwarciu <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Którą zakładkę otwierać przy ładowaniu. Uwaga - domyślnie otworzy Profile.</p></body></html> Profile Profil Welcome Witaj Daily Dziennie Statistics Statystyki Switch Tabs Przełącz zakładki No change Bez zmian After Import Po imporcie Overlay Flags Nakładające się flagi Line Thickness Grubość linii The pixel thickness of line plots Wielkość piksela wykresów liniowych Other Visual Settings Inne ustawienia wizualne Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Anti-Aliasing daje wygładzenie wykresów,,. Niektóre wykresy wyglądają lepiej. Również wydruki raportów Spróbuj i zdecyduj. Use Anti-Aliasing Użyj Anti-Aliasing Makes certain plots look more "square waved". Powoduje, że wyglad niektórych wykresów jest bardziej prostokątny. Square Wave Plots Wykresy prostokątne Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Buforowanie mapy pikseli jest techniką przyspieszania grafiki. Może spowodować problemy z wyświetlaniem czcionek na twoim systemie. Use Pixmap Caching Użyj buforowania mapy pikseli <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <head/><body><p>Te funkcje ostatnio usunięto. Wrócą później. </p></body></html> Animations && Fancy Stuff Animacje && Duperelki Whether to allow changing yAxis scales by double clicking on yAxis labels Czy pozwolić na zmianę skali osi Y przez dwuklik na etykietach osi Y Allow YAxis Scaling Pozwól na skalowanie osi Y Graphics Engine (Requires Restart) Silnik graficzny (wymaga restartu) Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Spróbuj to zmienić z ustawień domyślnych (Desktop OpenGL) jeśli doświadczasz problemów renderowania z wykresami OSCAR. Fonts (Application wide settings) Czcionki (Ustawienia dla całej aplikacji) Font Czcionka Size Rozmiar Bold Wytłuszczenie Italic Pochylenie Application Aplikacja Graph Text Tekst wykresu Graph Titles Tytuły wykresu Big Text Duży tekst Details Szczegóły &Cancel &Skasuj &Ok &Ok Name Nazwa Color Kolor Flag Type Typ flagi Label Etykieta CPAP Events Zdarzenia CPAP Oximeter Events Zdarzenia pulsoksymetru Positional Events Zdarzenia ułożenia Sleep Stage Events Zdarzenia fazy snu Unknown Events Nieznane zdarzenia Double click to change the descriptive name this channel. Dwuklik dla zmiany nazwy opisowej tego kanału. Double click to change the default color for this channel plot/flag/data. Dwuklik dla zmiany domyślnego koloru wykresu/flagi/danych tego kanału. Overview Przegląd Double click to change the descriptive name the '%1' channel. Dwuklik dla zmiany nazwy opisowej kanału '%1'. Whether this flag has a dedicated overview chart. Czy ta flaga ma dedykowaną tabelę przeglądową. Here you can change the type of flag shown for this event Tu możesz zmienić typ flagi pokazywanej dla tego zdarzenia This is the short-form label to indicate this channel on screen. To jest skrócona etykieta dla wskazania tego kanału na ekranie. This is a description of what this channel does. To jest opis działania tego kanału. Lower Niższy Upper Wyższy CPAP Waveforms Wykresy CPAP Oximeter Waveforms Wykresy pulsoksymetru Positional Waveforms Wykresy ułożenia Sleep Stage Waveforms Wykresy fazy snu Whether a breakdown of this waveform displays in overview. Czy ten wykres wyświetla się w przeglądzie. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Tu możesz ustawić <b>niższy </b> próg używany do określonych obliczeń dla wykresu %1 Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Tu możesz ustawić <b>wyzszy </b> próg używany do określonych obliczeń dla wykresu %1 Data Processing Required Wymagane przetworzenie danych A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Kompresja/dekompresja danych jest wymagana do zastosowania tych zmian. Ta operacja może zpotrwać kilka minut. Na pewno chcesz dokonać tych zmian? Data Reindex Required Wymagana reindeksacja danych A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Reindeksacja danych jest wymagana do zastosowania tych zmian. Ta operacja może zpotrwać kilka minut. Na pewno chcesz dokonać tych zmian? Restart Required Wymagany restart If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Jeśli kiedykolwiek potzrbowałbyś tych danych - nie wrócą. If you need to conserve disk space, please remember to carry out manual backups. Jeśli potrzebujesz oszczędzać miejsce na dysku, pamiętaj zrobić kopie zapasowe ręcznie. Are you sure you want to disable these backups? Jesteś pewny, że chcesz wyączyć kopie zapasowe? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Wyłączenie kopii zapasowych to słaby pomysł, OSCAR moze ich potrzebować do przebudowania bazy danych w razie błędów. Are you really sure you want to do this? Na pewno chcesz to zrobić? Flag Flaga Minor Flag Mała flaga Span Odstęp Always Minor Zawsze mniejsze <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Uwaga:</b> zaawansowane dzielenie sesji nie jest możliwe dla aparatów ResMed z uwagi na ograniczenia w przechowywaniu danych i ustawień, i dlatego w tym profilu są wyłączone.</p><p>Dni będą dzielone w południe, jak w komercyjnym oprogramowaniu.</p> Never Nigdy %1 %2 %1 %2 One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Jedna lub więcej zmian wymaga restartu, o ile mają być widoczne. Restartować teraz? This may not be a good idea To słaby pomysł ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Aparaty ResMed S9 rutynowo usuwają określone dane z karty SD starsze niż 7 i 30 dni (zależnie od rozdzielczości). Changing SD Backup compression options doesn't automatically recompress backup data. Zmiana opcji kompresji danych na karcie SD nie powoduje automatycznej rekompresji danych zapasowych. Your masks vent rate at 20 cmH2O pressure Wskaźnik wentylacji maski przy ciśnieniu 20 cmH2O Your masks vent rate at 4 cmH2O pressure Wskaźnik wentylacji maski przy ciśnieniu 4 cmH2O Whether to include machine serial number on machine settings changes report Czy zaimportować nr seryjny urządzenia w raporcie zmian ustawień urządzenia Include Serial Number Dołącz numer seryjny <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Wyświetl ostrzeżenie przy imporcie danych z aparatu nie testowanego przez deweloperów OSCARa.</p></body></html> Warn when importing data from an untested machine Ostrzegaj przed importem danych z nie testowanego aparatu <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>Wyświetl ostrzeżenie, gdy importowane dane są inne niż wcześniej widziane przez deweloperów OSCARa</p></body></html> Warn when previously unseen data is encountered Ostrzegaj, gdy wcześniej nieznane dane są zaliczane Always save screenshots in the OSCAR Data folder Zawsze zapisuj zrzuty ekranu w folderze danych OSCAR No CPAP machines detected Nie wykryto aparatu CPAP Will you be using a ResMed brand machine? Czy będziesz używał aparatu ResMed? Check For Updates Sprawdź uaktualnienia You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Używasz testowej wersji OSCARa. Wersje testowe sprawdzają aktualizacje co najmniej co siedem dni. Możesz ustawić częściej. Automatically check for updates Automatycznie sprawdzaj aktualizacje How often OSCAR should check for updates. Jak często OSCAR ma sprawdzać aktuallizacje. If you are interested in helping test new features and bugfixes early, click here. Jeśli chcesz pomóc w testowaniu nowych funkcji i poprawek, kliknij tu. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Jeśli chcesz pomóc w testowaniu wczesnych wersji OSCARa, odwiedź stronę Wiki o testowaniu OSCAR. Zapraszamy wszystkich, którzy chcieliby przetestować OSCARa, pomóc w rozwoju OSCARa i pomóc w tłumaczeniach na istniejące lub nowe języki. https://www.sleepfiles.com/OSCAR Print reports in black and white, which can be more legible on non-color printers Drukuj raporty w czerni i bieli, co może być bardziej czytelne na drukarkach innych niż kolorowe Print reports in black and white (monochrome) Drukuj raporty w czerni i bieli (monochromatycznie) ProfileSelector Filter: Filtr: Version Wersja &Open Profile &Otwórz profil &Edit Profile &Edytuj profil &New Profile &Nowy profil Profile: None Profil:Brak Please select or create a profile... Proszę wybierz lub utwórz profil... Destroy Profile Zniszcz profil Profile Profil Ventilator Brand Marka aparatu Ventilator Model Model aparatu Other Data Inne dane Last Imported Ostatnio importowane Name Nazwisko Enter Password for %1 Wprowadź hasło dla %1 You entered an incorrect password Wprowadziłeś nieprawidłowe hasło Forgot your password? Zapomniałeś hasła? Ask on the forums how to reset it, it's actually pretty easy. Zapytaj na forum jak to zresetować, to łatwe. Select a profile first Najpierw wybierz profil If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Jeżeli próbujesz usunąć bo zapomniałeś hasła, musisz zresetować albo usunąć folder profilu ręcznie. You are about to destroy profile '<b>%1</b>'. Masz zamiar usunąć profil '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Pomyśl chwilę, to nieodwracalnie usunie profil i dane <b>kopii zapasowej</b>, przechowywanych w </br>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Wprowadź słowo <b>DELETE</b> poniżej, dokładnie jak tu napisane by potwierdzić. DELETE DELETE Sorry Przepraszam You need to enter DELETE in capital letters. Musisz wpisać DELETE uzywając WIELKICH liter. There was an error deleting the profile directory, you need to manually remove it. Wystąpił błąd w usuwaniu katalogu profilu, musisz usunąć go ręcznie. Profile '%1' was succesfully deleted Profil '%1' pomyślnie usunięto Bytes Bajtów KB KB MB MB GB GB TB TB PB PB Summaries: Podsumowania: Events: Zdarzenia: Backups: Kopie zapasowe: Hide disk usage information Ukryj informację o użyciu dysku Show disk usage information Pokaż informację o użyciu dysku Name: %1, %2 Nazwisko: %1, %2 Phone: %1 Telefon: %1 Email: <a href='mailto:%1'>%1</a> Email: <a href='mailto:%1'>%1</a> Address: Adres: No profile information given Nie podano danych profilu Profile: %1 Profil: %1 You must create a profile Musisz utworzyć profil Reset filter to see all profiles Wyczyść filtry aby ujrzeć wszystkie profile The selected profile does not appear to contain any data and cannot be removed by OSCAR Wydaje się, że wybrany profil nie zawiera żadnych danych i nie może zostać usunięty przez OSCAR ProgressDialog Abort Przerwij QObject No Data Brak danych Events Zdarzenia Duration Czas trwania (% %1 in events) (% %1 w zdarzeniach) Jan Sty Feb Lut Mar Mar Apr Kwi May Maj Jun Cze Jul Lip Aug Sie Sep Wrz Oct Paź Nov Lis Dec Gru ft ft lb lb oz oz Kg kg cmH2O cmH2O Med. Med. Min: %1 Min: %1 Min: Min: Max: Max: Max: %1 Max: %1 %1 (%2 days): %1 (%2 dni): %1 (%2 day): %1 (%2 dzień): % in %1 % w %1 Hours Godziny Min %1 Min %1 Hours: %1 Godziny: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 niskie użycie, %2 bez użycia, z %3 dni (%4 zgodnych). Długość : %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Sesje: %1 / %2 / %3 Długość: %4 / %5 / %6 Najdłuższa: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Długość: %3 Start: %2 Mask On Maska założona Mask Off Maska zdjęta %1 Length: %3 Start: %2 %1 Długość: %3 Start: %2 TTIA: TTIA: TTIA: %1 TTIA: %1 Minutes minuty Seconds sekundy h h m m s s ms ms Events/hr zdarzeń/godz Hz Hz bpm uderzeń/min Litres Litry ml ml Breaths/min Oddechów/min ? ? Severity (0-1) Ciężkość (0-1) Degrees Stopni Error Błąd Warning Ostrzeżenie Information Informacja Busy Zajęty Please Note Proszę zauważ Graphs Switched Off Wykresy wyłączone Sessions Switched Off Sesje wyłączone &Yes &Tak &No &Nie &Cancel &Skasuj &Destroy &Zniszcz &Save &Zachowaj BMI BMI Weight Waga Zombie Zombie Pulse Rate Częstość pulsu Plethy Pletyzmografia Pressure Ciśnienie Daily Dziennie Profile Profil Overview Przegląd Oximetry Pulsoksymetria Oximeter Pulsoksymetr Event Flags Flagi zdarzeń Default Domyślnie CPAP CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP Min EPAP Min EPAP Max EPAP Max EPAP IPAP IPAP Min IPAP Min IPAP Max IPAP Max IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Nawilżacz H H OA OA A A CA CA FL FL SA SA LE LE EP EP VS VS VS2 VS2 RERA RERA PP PP P P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF1 UF2 UF2 UF3 UF3 PS PS AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI EPI PB PB IE IE Insp. Time Czas wdechu Exp. Time Czas wydechu Resp. Event Zdarzenie oddechowe Flow Limitation Ograniczenie przepływu Flow Limit Limit przepływu SensAwake Przebudzenie sensoryczne Pat. Trig. Breath Oddech wyw. przez pacjenta Tgt. Min. Vent Docel.went.min Target Vent. Docelowa wentylacja. Minute Vent. Wentylacja minutowa. Tidal Volume Objętość oddechowa Resp. Rate Częstość oddechów Snore Chrapanie Leak Nieszczelność Leaks Nieszczelności Large Leak Duży wyciek LL LL Total Leaks Całkowite nieszczelności Unintentional Leaks Nieszczelności niezamierzone MaskPressure Ciśnienie maski Flow Rate Przepływ Sleep Stage Faza snu Usage Użycie Sessions Sesje Pr. Relief Ulga ciśnienia No Data Available Brak dostępnych danych Software Engine Silnik oprogramowania ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Desktop OpenGL m m cm cm in cal Only Settings and Compliance Data Available Dostępne są tylko ustawienia i zgodne dane Summary Data Only Tylko dane podsumowujące Bookmarks Zakładki Mode Tryb Model Model Brand Marka Serial nr seryjny Series seria Machine Aparat Channel Kanał Settings Ustawienia Inclination Nachylenie Orientation Kierunek Name Nazwisko DOB Data urodzenia Phone telefon Address Adres Email email Patient ID ID pacjenta Date Data Bedtime do łóżka Wake-up wstał/a Mask Time czas z maską Unknown nieznany None Żaden Ready Gotowy First pierwszy Last ostatni Start Początek End Koniec On Włącz Off Wyłącz Yes Tak No Nie Min Min Max Max Med Med Average Średnio Median Mediana Avg Śrd W-Avg ŚrdWaż Your %1 %2 (%3) generated data that OSCAR has never seen before. Twój %1 %2 (%3) wygenerował dane, których OSCAR nigdy wcześniej nie widział. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Importowane dane mogą nie być w pełni dokładne, więc programiści chcieliby otrzymać kopię .zip karty SD tego urządzenia i pasujące raporty .pdf lekarza, aby upewnić się, że OSCAR obsługuje dane prawidłowo. Non Data Capable Machine Aparat nie zbierający danych Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Twoje urządzenier CPAP %1 (model %2) nie udostępnia zgodnych danych. Your %1 CPAP machine (Model %2) has not been tested yet. Twoje urządzenie CPAP %1 (model %2) nie zostało jeszcze przetestowane. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Wydaje się na tyle podobny do innych urządzeń, że może działać, ale programiści chcieliby mieć kopię .zip karty SD tego urządzenia i pasujące raporty .pdf lekarza, aby upewnić się, że działa z OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. Przepraszamy, Twoje urządzenier CPAP %1 (%2) nie jest jeszcze obsługiwane. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Deweloperzy potrzebują kopii .zip karty SD tego urządzenia i pasujących raportów .pdf lekarza, aby działało z OSCAR. Machine Unsupported Aparat nie wspierany 15mm 15mm 22mm 22mm Flex Mode Tryb Flex PRS1 pressure relief mode. PRS1 tryb ulgi ciśnienia. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex Rise Time Czas wzrostu Bi-Flex Bi-Flex Flex Level Flex Level PRS1 pressure relief setting. Ustawienia ulgi ciśnienia PRS1. Humidifier Status Status nawilżacza PRS1 humidifier connected? Czy jest podłączony nawilżacz PRS1? Disconnected Odłączony Connected Podłączony Getting Ready... Przygotowuję... I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Niestety OSCAR może śledzić tylko czas działania i podstawowe ustawienia tego aparatu. Scanning Files... Skanuję pliki... Importing Sessions... Importuję sesje... Finishing up... Kończę... Hose Diameter Średnica węża Diameter of primary CPAP hose Średnica węża podstawowego CPAP Auto On Auto włączanie A few breaths automatically starts machine Kilka oddechów automatycznie włącza aparat Auto Off Auto wyłączanie Machine automatically switches off Aparat wyłącza się automatycznie Mask Alert Alarm maski Whether or not machine allows Mask checking. Czy aparat pokazuje alarm maski. Show AHI Pokaż AHI Breathing Not Detected Nie wykryto oddechu A period during a session where the machine could not detect flow. Przez pewien czas sesji aparat nie wykrył przepływu. BND BND Timed Breath Czasowy oddech Machine Initiated Breath Oddech inicjowany przez aparat TB TB Windows User Użytkownik Windows <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Twoje stare dane z aparatu powinny być zregenerowane ponieważ ta cecha kopii zapasowej nie została wyłączona w preferencjach podczas poprzedniego importu.</i> Launching Windows Explorer failed Uruchomienie Eksploratora Windows nie powiodło się Could not find explorer.exe in path to launch Windows Explorer. Nie znaleziono explorer.exe na ścieżce uruchomienia Eksploratora Windows. <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR prowadzi kopię zapasową danych z Twoich aparatów których używa w tym celu.</b> OSCAR does not yet have any automatic card backups stored for this device. Jak dotąd OSCAR nie ma żadnych automatycznych kopii zapasowych dla tego aparatu. This means you will need to import this machine data again afterwards from your own backups or data card. To oznacza, że trzeba zaimportować dane tej maszyny ponownie z własnych kopii zapasowych lub karty pamięci. Important: Ważne: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. W razie wątpliwości kliknij przycisk Nie, aby wyjść i ręcznie wykonać kopię zapasową profilu, zanim ponownie uruchomisz program OSCAR. Are you ready to upgrade, so you can run the new version of OSCAR? Czy jesteś gotowy na aktualizację, aby uruchomić nową wersję OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Niestety, operacja czyszczenia nie powiodła się, co oznacza, że ta wersja OSCAR nie może się uruchomić. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Czy chcesz włączyć automatyczne tworzenie kopii zapasowych, aby następnym razem, gdy nowa wersja OSCAR będzie musiała to zrobić, mogła je ponownie utworzyć? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR uruchomi teraz kreator importu, aby móc ponownie zainstalować dane%1. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: OSCAR zakończy teraz, a następnie (spróbuje) uruchomić menedżera plików komputera, aby można ręcznie przywrócić swój profil: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Użyj swojego menedżera plików, aby utworzyć kopię swojego katalogu profilu, a następnie zrestartuj OSCAR i zakończ proces aktualizacji. Machine Database Changes Zmiany bazy danych aparatów The machine data folder needs to be removed manually. Folder danych aparatu musi być usunięty ręcznie. This folder currently resides at the following location: Ten folder aktualnie jest w położeniu: Rebuilding from %1 Backup Odbudowa z kopii zapasowej %1 Therapy Pressure Ciśnienie lecznicze Inspiratory Pressure Ciśnienie wdechowe Lower Inspiratory Pressure Niskie ciśnienie wdechowe Higher Inspiratory Pressure Wysokie ciśnienie wdechowe Expiratory Pressure Ciśnienie wydechowe Lower Expiratory Pressure Niskie ciśnienie wydechowe Higher Expiratory Pressure Wysokie ciśnienie wydechowe Pressure Support Wsparcie ciśnieniem PS Min PS Min Pressure Support Minimum Minimalne wsparcie ciśnieniem PS Max PS Max Pressure Support Maximum Maksymalne wsparcie ciśnieniem Min Pressure Ciśnienie min Minimum Therapy Pressure Minimalne ciśnienie lecznicze Max Pressure Ciśnienie max Maximum Therapy Pressure Maksymalne ciśnienie lecznicze Ramp Time Czas rozbiegu Ramp Delay Period Czas opóźnienia rozbiegu Ramp Pressure Ciśnienie rozbiegu Starting Ramp Pressure Początkowe ciśnienie rozbiegu Ramp Event Zdarzenie rozbiegu Ramp Rozbieg Vibratory Snore (VS2) Chrapanie z wibracją (VS2) Mask On Time Czas z maską Time started according to str.edf Czas rozpoczęcia odpowiednio do str.edf Summary Only Tylko podsumowanie An apnea where the airway is open Bezdech przy otwartych drogach oddechowych An apnea caused by airway obstruction Bezdech spowodowany obturacją dróg oddechowych Hypopnea Spłycony oddech A partially obstructed airway Częściowo zamknięte drogi oddechowe Unclassified Apnea Niesklasyfikowany bezdech UA UA Vibratory Snore Chrapanie z wibracją A vibratory snore Chrapanie z wibracją A vibratory snore as detcted by a System One machine Chrapanie z wibracją wykryte przez aparat System One Pressure Pulse Impuls ciśnienia A pulse of pressure 'pinged' to detect a closed airway. Impuls ciśnienia wysłany w celu wykrycia zamkniętych dróg oddechowych. A large mask leak affecting machine performance. Impuls ciśnienia wysłany w celu wykrycia zamkniętych dróg oddechowych. Non Responding Event Zdarzenie nie odpowiadające A type of respiratory event that won't respond to a pressure increase. Rodzaj zdarzenia oddechowego nie odpowiadającego na zwiększenie ciśnienia. Expiratory Puff Pufnięcie wydechowe Intellipap event where you breathe out your mouth. Zdarzenie intellipap gdy wydychasz przez usta. SensAwake feature will reduce pressure when waking is detected. Funkcja SensAwake zredukuje ciśnienie gdy wykryje przebudzenie. User Flag #1 Flaga użytkownika #1 User Flag #2 Flaga użytkownika #2 User Flag #3 Flaga użytkownika #3 Heart rate in beats per minute Częstość akcji serca w uderzeniach na minutę Blood-oxygen saturation percentage Wartość procentowa saturacji krwi tlenem Plethysomogram Pletyzmogram An optical Photo-plethysomogram showing heart rhythm Optyczny foto-pletyzmogram pokazujący rytm serca Pulse Change Zmiany pulsu A sudden (user definable) change in heart rate Nagła (zdefiniowana przez użytkownika) zmiana częstosci akcji serca SpO2 Drop Spadek SpO2 A sudden (user definable) drop in blood oxygen saturation Nagła (zdefiniowana przez użytkownika) zmiana saturacji krwi tlenem SD SD Breathing flow rate waveform Wykres współczynnika przepływu oddechowego L/min L/min Mask Pressure Ciśnienie maski Amount of air displaced per breath Ilość powietrza przemieszczonego na oddech Graph displaying snore volume wykres ukazujący wielkość chrapania Minute Ventilation Wentylacja minutowa Amount of air displaced per minute Ilość powietrza przemieszczonego na minutę Respiratory Rate Częstość oddechowa Rate of breaths per minute Częstość oddechów na minutę Patient Triggered Breaths Oddechy wywołane przez pacjenta Percentage of breaths triggered by patient Odsetek oddechów wywołanych przez pacjenta Pat. Trig. Breaths Pat. Trig. Breaths Leak Rate Wskaźnik wycieków Rate of detected mask leakage Wskaźnik wykrytych wycieków maski I:E Ratio I:E Ratio Ratio between Inspiratory and Expiratory time Stosunek czasu wdechu/wydechu ratio stosunek Pressure Min Ciśnienie Min Pressure Max Ciśnienie Max Cheyne Stokes Respiration Oddech Cheyne-Stokes'a CSR CSR An abnormal period of Cheyne Stokes Respiration Nienormalny okres oddechów Cheyne-Stokes'a Periodic Breathing Oddychanie okresowe An abnormal period of Periodic Breathing Nienormalny czas oddychania okresowego Clear Airway Centralne Obstructive Obturacyjne Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Pobudzenie związane z wysiłkiem oddechowym. Zaburzenie oddechowe powodujące przebudzenie lub zaburzenie snu. Leak Flag Flaga wycieku LF LF A user definable event detected by OSCAR's flow waveform processor. Definiowane przez użytkownika zdarzenie wykryte przez OSCAR. Perfusion Index Indeks perfuzji A relative assessment of the pulse strength at the monitoring site Względna osena siły pulsu na stanowisku monitorowania Perf. Index % Perf. Index % Expiratory Time Czas wydechu Time taken to breathe out Czas wydychania powietrza Inspiratory Time Czas wdechu Time taken to breathe in Czas wdychania powietrza Respiratory Event Zdarzenie oddechowe Graph showing severity of flow limitations Wykres pokazujący ciężkość ograniczeń przepływu Flow Limit. Ograniczenie przepływu. Target Minute Ventilation Docelowa wentylacja minutowa Maximum Leak Wyciek maksymalny The maximum rate of mask leakage Maksymalny wskaźnik wycieku maski Max Leaks Wycieki max Apnea Hypopnea Index Indeks bezdechów i spłycenia oddechu Graph showing running AHI for the past hour Wykres ukazujący zmiany AHI dla ostatniej godziny Total Leak Rate Wskaźnik wycieku całkowitego Detected mask leakage including natural Mask leakages Wykryty wyciek maski z naturalnym wyciekiem maski włącznie Median Leak Rate Mediana wskaźnika wycieku maski Median rate of detected mask leakage Mediana wskaźnika wykrytego wycieku maski Median Leaks Mediana wycieków Respiratory Disturbance Index Wskaźnik zaburzeń oddychania Graph showing running RDI for the past hour Wykres pokazujący bieżące RDI dla ostatniej godziny Sleep position in degrees Pozycja snu w stopniach Upright angle in degrees Kąt pochylenia w stopniach CPAP Session contains summary data only Sesje CPAP zawierające tylko dane sumaryczne PAP Mode Tryb PAP PAP Device Mode Tryb urządzenia PAP APAP (Variable) APAP (Variable) ASV (Fixed EPAP) ASV (Fixed EPAP) ASV (Variable EPAP) ASV (Variable EPAP) Height Wzrost Physical Height Wzrost fizyczny Notes Notatki Bookmark Notes Notatki zakładki Body Mass Index Wskaźnik masy ciała How you feel (0 = like crap, 10 = unstoppable) Jak się czujesz (0-gówniano; 10-zajefajnie) Bookmark Start Początek zakładki Bookmark End Koniec zakładki Last Updated Ostatnio uaktualnione Journal Notes Notatki dziennika Journal Dziennik 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=obudzony, 2=REM, 3=lekki sen, 4=głęboki sen Brain Wave Fale mózgowe BrainWave Fale mózgowe Awakenings Przebudzenia Number of Awakenings Ilość przebudzeń Morning Feel Samopoczucie o poranku How you felt in the morning Jak się czułeś rano Time Awake Czas przebudzenia Time spent awake Czas spędzony na przebudzeniu Time In REM Sleep Czas snu w fazie REM Time spent in REM Sleep Czas snu w fazie REM Time in REM Sleep Czas snu w fazie REM Time In Light Sleep Czas snu lekkiego Time spent in light sleep Czas snu lekkiego Time in Light Sleep Czas snu lekkiego Time In Deep Sleep Czas snu głębokiego Time spent in deep sleep Czas snu głębokiego Time in Deep Sleep Czas snu głębokiego Time to Sleep Czas zasypiania Time taken to get to sleep Czas poświęcony na zaśnięcie Zeo ZQ Zeo ZQ Zeo sleep quality measurement Pomiar jakości snu Zeo ZEO ZQ ZEO ZQ Zero Zero Upper Threshold Górny próg Lower Threshold Dolny próg As you did not select a data folder, OSCAR will exit. Ponieważ nie wybrałeś foldera danych, OSCAR ucieka. The folder you chose is not empty, nor does it already contain valid OSCAR data. Wybrany folder nie jest pusty ani nie zawiera prawidłowych danych OSCAR. It is likely that doing this will cause data corruption, are you sure you want to do this? Możliwe, że zrobienie tego spowoduje utratę danych, jesteś pewny, że chcesz to zrobić? Question Pytanie Exiting Wychodzenie Are you sure you want to use this folder? Jesteś pewny, że chcesz użyć tego folderu? Don't forget to place your datacard back in your CPAP machine Nie zapomnij włożyć swojej karty SD ponownie do aparatu CPAP OSCAR Reminder OSCAR przypomina You can only work with one instance of an individual OSCAR profile at a time. Możesz pracować tylko z jedną instancją pojedynczego profilu OSCAR naraz. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Jeśli korzystasz z pamięci w chmurze, upewnij się, że OSCAR jest zamknięty, a synchronizacja zakończyła się przed kontynuowaniem. Loading profile "%1"... ładuję profil "%1"... Are you sure you want to reset all your channel colors and settings to defaults? Jesteś pewny, że chcesz przywrócić domyślne kolory i ustawienia? Are you sure you want to reset all your waveform channel colors and settings to defaults? Jesteś pewny, że chcesz przywrócić domyślne kolory i ustawienia wykresów? There are no graphs visible to print Nie ma widocznych wykresów do wydruku Would you like to show bookmarked areas in this report? Chciałbyś pokazać obszary zakładek w tym raporcie? Printing %1 Report Drukuję raport %1 %1 Report Raport %1 : %1 hours, %2 minutes, %3 seconds : %1 godzin, %2 minut, %3 sekund RDI %1 RDI %1 AHI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 UAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 Reporting from %1 to %2 Raportowanie od %1 do %2 Entire Day's Flow Waveform Wykres przepływu całego dnia Current Selection Bieżący wybór Entire Day Cały dzień Page %1 of %2 Strony %1 z %2 Days: %1 Dni: %1 Low Usage Days: %1 Dni niskiego użycia: %1 (%1% compliant, defined as > %2 hours) (%1% zgodne, zdefiniowane jako > %2 godzin) (Sess: %1) (Sesja:%1) Bedtime: %1 Czas w łóżku:%1 Waketime: %1 Czas budzenia: %1 (Summary Only) (Tylko podsumowanie) There is a lockfile already present for this profile '%1', claimed on '%2'. Jest plik blokady dla tego profilu '%1', zgłoszony na '%2'. Fixed Bi-Level Fixed Bi-Level Auto Bi-Level (Fixed PS) Auto Bi-Level (Fixed PS) Auto Bi-Level (Variable PS) Auto Bi-Level (Variable PS) Fixed %1 (%2) Fixed %1 (%2) Min %1 Max %2 (%3) Min %1 Max %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %1 ponad %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Najświeższe dane pulsoksymetru: <a onclick='alert("daily=%2");'>%1</a> (last night) (ostatnia noc) No oximetry data has been imported yet. Nie zaimportowano dotąd żadnych danych pulsoksymetrii. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings Ustawienia SmartFlex ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Oprogramowanie Somnopose Zeo Zeo Personal Sleep Coach Osobisty trener snu Database Outdated Please Rebuild CPAP Data Baza danych przeterminowana Proszę przebuduj dane CPAP (%2 min, %3 sec) (%2 min, %3 sec) (%3 sec) (%3 sek) Pop out Graph Wyskakujący wykres Your machine doesn't record data to graph in Daily View Twoje urządzenie nie rejestruje danych na wykresie w widoku dziennym There is no data to graph Nie ma danych dla wykresu Hide All Events Ukryj wszystkie zdarzenia Show All Events Pokaż wszystkie zdarzenia Unpin %1 Graph Odepnij wykres %1 Popout %1 Graph Wydobądź wykres %1 Pin %1 Graph Przypnij wykres %1 Plots Disabled Wykresy wyłączone Duration %1:%2:%3 Czas trwania %1:%2:%3 AHI %1 AHI %1 Relief: %1 Ulga: %1 Hours: %1h, %2m, %3s Godziny: %1h, %2m, %3s Machine Information Informacje o aparacie Journal Data Dane dziennika OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR znalazł stary folder dziennika, ale wygląda na to, że został przemianowany: OSCAR will not touch this folder, and will create a new one instead. OSCAR nie dotknie tego folderu i zamiast tego utworzy nowy. Please be careful when playing in OSCAR's profile folders :-P Zachowaj ostrożność podczas zabawy w folderach profilu OSCAR :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. Z jakiegoś powodu OSCAR nie mógł znaleźć zapisu dziennika w swoim profilu, ale znalazł wiele folderów danych dziennika. OSCAR picked only the first one of these, and will use it in future: OSCAR wybrał tylko pierwszy z nich i użyje go w przyszłości: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Jeśli brakuje danych, skopiuj zawartość wszystkich innych folderów Journal_XXXXXXX do tego tu ręcznie. CMS50F3.7 CMS50F3.7 CMS50F CMS50F SmartFlex Mode Tryb SmartFlex Intellipap pressure relief mode. Tryb ulgi ciśnieniowej Intellipap. Ramp Only Tylko rozbieg Full Time Całkowity czas SmartFlex Level Poziom SmartFlex Intellipap pressure relief level. Poziom ulgi ciśnieniowej Intellipap. Locating STR.edf File(s)... Lokaizowanie pliku(ów) STR.edf ... Cataloguing EDF Files... Katalogowanie plików EDF... Queueing Import Tasks... Kolejkowanie zadań importu ... Finishing Up... Kończenie ... CPAP Mode Tryb CPAP VPAPauto VPAPauto ASVAuto ASVAuto PAC PAC Auto for Her Auto for Her EPR EPR ResMed Exhale Pressure Relief ResMed ulga wydechowa Patient??? Pacjent ??? EPR Level Poziom EPR Exhale Pressure Relief Level Poziom ulgi wydechowej SmartStart SmartStart Machine auto starts by breathing Aparat sam startuje przez rozpoczęcie oddychania Smart Start Smart Start Humid. Status Status nawilżacza Humidifier Enabled Status Status włączenia nawilżacza Humid. Level Poziom nawilżacza Humidity Level Poziom wilgotności Temperature Temperatura ClimateLine Temperature Temperatura ClimateLine Temp. Enable Włącz. Temp ClimateLine Temperature Enable Temperatura ClimateLine włączona Temperature Enable Włączenie temperatury AB Filter Filtr AB Antibacterial Filter Filtr antybakteryjny Pt. Access Dostęp Pt Climate Control Kontrola klimatu Manual Podręcznik Auto Auto Mask Maska ResMed Mask Setting Ustawienia maski ResMed Pillows Poduszeczki Full Face Pełnotwarzowa Nasal Nosowa Ramp Enable Włącz rozbieg Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Snapshot %1 Migawka %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... Ładuję %1 dane dla %2... Scanning Files Skanuję pliki Migrating Summary File Location Przenoszenie lokalizacji plików podsumowań Loading Summaries.xml.gz Ładowanie Summaries.xml.gz Loading Summary Data Ładowanie danych podsumowań Please Wait... Zaczekaj ... Using Używam , found SleepyHead - , znaleziono SleepyHead -Head - You must run the OSCAR Migration Tool Musisz uruchomić narzędzie migracji OSCAR'a An apnea that couldn't be determined as Central or Obstructive. Bezdech, który nie moze być określony jako centralny czy obturacyjny. A restriction in breathing from normal, causing a flattening of the flow waveform. Ograniczenie oddechu, powodujące spłaszczenie wykresu przepływu. or CANCEL to skip migration. lub Skasuj aby pominąć przenoszenie. You cannot use this folder: Nie możesz użyć tego folderu: Choose or create a new folder for OSCAR data Wybierz albo utwórz nowy folder na dane OSCARa Migrating Przenoszę files pliki from od to do OSCAR will set up a folder for your data. OSCAR przygotuje folder na Twoje dane. We suggest you use this folder: Proponujemy użycie tego folderu: Click Ok to accept this, or No if you want to use a different folder. Kliknij OK aby zaakceptować, albo Nie jeśli chcesz użyć innego folderu. Next time you run OSCAR, you will be asked again. NGdy następny raz uruchomisz OSCARa, będziesz zapytany ponownie. App key: Klucz aplikacji: Operating system: System operacyjny: Graphics Engine: Silnik graficzny: Graphics Engine type: Typ silnika graficznego: Machine Untested Urządzenie nie testowane Data directory: Folder danych: Updating Statistics cache Uaktualnienie cache statystyk Usage Statistics Statystyki użycia d MMM yyyy [ %1 - %2 ] d MMM yyyy [ %1 - %2 ] EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) Pressure Set Ustawienie ciśnienia Pressure Setting Ustawianie ciśnienia IPAP Set Ustawienie ciśnienia wdechowego (IPAP) IPAP Setting Ustawianie ciśnienia wdechowego (IPAP) EPAP Set Ustawienie ciśnienia wydechowego (EPAP) EPAP Setting Ustawianie ciśnienia wydechowego (EPAP) %1 Charts %1 Wykresów %1 of %2 Charts %1 z %2 Wykresów Loading summaries Ładowanie podsumowań Built with Qt %1 on %2 Zbudowane z użyciem Qt %1 na %2 Motion Ruch n/a n/a Dreem Dreem Untested Data Niesprawdzone dane P-Flex P-flex Humidification Mode Tryb nawilżania PRS1 Humidification Mode Tryb nawilżania aparatu PRS1 Humid. Mode Tryb nawilżania Fixed (Classic) Stały (klasyczny) Adaptive (System One) Adaptacyjny (System One) Heated Tube Podgrzewana rura Tube Temperature Temperatura rury PRS1 Heated Tube Temperature Temperatura podgrzewanej rury PRS1 Tube Temp. Temp. rury. PRS1 Humidifier Setting Ustawienie nawilżania PRS1 12mm 12mm Your Viatom device generated data that OSCAR has never seen before. Twój aparat Viatom wygenerował dane, których OSCAR jeszcze nie widział. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Dane zaimportowanie mogą nie być dokładnem dlatego deweloperzy chcieliby kopię Twoich plików Viatom, dla pewności, ze OSCAR prawidłowo obsługuje dane. Viatom Viatom Viatom Software Oprogramowanie Viatom OSCAR %1 needs to upgrade its database for %2 %3 %4 OSCAR %1 potrzebuje uaktualnić bazę danych dla %2 %3 %4 Movement Ruch Movement detector Detektor ruchu Version "%1" is invalid, cannot continue! Wersja :%1" jest nieprawidłowa, nie można kontynuować! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Wersja której używasz (%1) jest starsza od tej, w której utworzono te dane (%2). Please select a location for your zip other than the data card itself! Proszę wybierz miejsce zapisu archiwum inne niż karta z danymi! Unable to create zip! Nie mogę utworzyć archiwum! Parsing STR.edf records... Analiza zapisów STR.edf... Backing Up Files... Zapisywanie kopii zapasowej plików... Mask Pressure (High frequency) Ciśnienie w masce (Wysoka częstotliwość) A ResMed data item: Trigger Cycle Event Element danych ResMed: zdarzenie wyzwalania cyklu Debugging channel #1 Kanał debugowania #1 Test #1 Test #1 Debugging channel #2 Debugowanie kanał #2 Test #2 Test #2 EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) CPAP-Check CPAP-Check AutoCPAP AutoCPAP Auto-Trial Auto-Trial AutoBiLevel AutoBiLevel S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Lock Whether Flex settings are available to you. Czy są dostępne ustawienia Flex. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Ilość czasu zmiany EPAP do IPAP, im wyższy tym wolniejsza zmiana Rise Time Lock Blokada czasu podwyższania Whether Rise Time settings are available to you. Czy ustawienia czasu wzrostu są dostępne. Rise Lock Blokada podwyższania Mask Resistance Setting Ustawienie oporu maski Mask Resist. Opór maski. Hose Diam. Średnica węża. Tubing Type Lock Blokada typu rury Whether tubing type settings are available to you. Czy są dostępne ustawienia rodzaju rury. Tube Lock Blokada rury Mask Resistance Lock Blokada oporu maski Whether mask resistance settings are available to you. Czy są dostępne ustawienia oporu maski. Mask Res. Lock Blokada oporu maski Whether or not machine shows AHI via built-in display. Czy aparat pokazuje AHI. Ramp Type Typ rampy Type of ramp curve to use. Rodzaj krzywej ramp do użycia. Linear SmartRamp SmartRamp Ramp+ Ramp+ Backup Breath Mode Tryb oddechu zastępczego The kind of backup breath rate in use: none (off), automatic, or fixed Rodzaj oddechu zastępczego w użyciu żaden (off) automatyczny, ustalony Breath Rate Częstość oddechów Fixed Ustalone Fixed Backup Breath BPM Ustalony oddech zastępczy BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Minimalne oddechy na minutę (BPM) poniżej których inicjowany jest oddech Breath BPM Oddechy BPM Timed Inspiration Czasowy wdech The time that a timed breath will provide IPAP before transitioning to EPAP Czas gdy ustalony wdech będzie dostarczał IPAP przed przejściem w EPAP Timed Insp. Czasowy wdech. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Ilość dni okresu próbnego Auto-CPAP, po którym aparat wraca do trybu CPAP Auto-Trial Dur. Czas trwania Auto-Trial. EZ-Start EZ-Start Whether or not EZ-Start is enabled Czy jest włączony tryb EZ-Start Variable Breathing Oddychanie zmienne UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend NIEPOTWIERDZONE: Prawdopodobnie zmienne oddychanie, z okresami o wysokim odchyleniu od szczytowego trendu wdechowego Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Jak już zaktualizujesz <font size=+1>nie możesz</font> już używać tego profilu z poprzednią wersją. Passover Przepuszczenie Peak Flow Szczytowy przepływ Peak flow during a 2-minute interval Przepływ szczytowy podczas 2-minutowego interwału Recompressing Session Files Rekompresja plików sesji OSCAR crashed due to an incompatibility with your graphics hardware. OSCAR wysypał si e z powodu niekompatybilności z grafiką Twojego komputera. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. Aby to rozwiązać, OSCAR przeszedł na wolniejszą ale bardziej kompatybilną metodę rysowania. Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. (1 day ago) (1 dzień wczeniej) (%2 days ago) (%2 dni wcześniej) New versions file improperly formed Plik nowej wersji jest nieprawidłowo utworzony A more recent version of OSCAR is available Dostępna jest nowsza wersja OSCAR release wydanie test version wersja testowa You are running the latest %1 of OSCAR Używasz najnowszego %1 OSCAR You are running OSCAR %1 Używasz OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 jest dostępny <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Informacje o nowszej wersji testowej %1 są dostępne pod adresem <a href='%2'>%2</a> Check for OSCAR Updates Sprawdzanie uaktualnień OSCAR Unable to create the OSCAR data folder at Nie można utworzyć folderu danych OSCARa w The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Okno wyskakujące jest pełne. Powinieneś uchwycić istniejące wyskakujące okienko, usunąć je, a następnie otworzyć ponownie ten wykres. Essentials Elementy zasadnicze Plus Plus Unable to write to OSCAR data directory Nie można zapisać w folderze danych OSCARa Error code Kod błędu OSCAR cannot continue and is exiting. OSCAR nie może kontynuować, wyłącza się. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Nie można zapisać w dzienniku debugowania. Nadal można używać okienka debugowania (Pomoc / Rozwiązywanie problemów / Pokaż okienko debugowania), ale dziennik debugowania nie zostanie zapisany na dysku. For internal use only Tylko do użytku wewnętrznego Choose the SleepyHead or OSCAR data folder to migrate Wybierz folder danych SleepyHead lub OSCAR do migracji The folder you chose does not contain valid SleepyHead or OSCAR data. Wybrany folder nie zawiera prawidłowych danych SleepyHead ani OSCAR. If you have been using SleepyHead or an older version of OSCAR, Jeśli używasz SleepyHead lub starszej wersji OSCAR, OSCAR can copy your old data to this folder later. OSCAR może później skopiować stare dane do tego folderu. Migrate SleepyHead or OSCAR Data? Migrować dane SleepyHead lub OSCAR? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Na następnym ekranie OSCAR poprosi Cię o wybranie folderu z danymi SleepyHead lub OSCAR Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Kliknij [OK], aby przejść do następnego ekranu lub [Nie], jeśli nie chcesz używać żadnych danych SleepyHead lub OSCAR. Chromebook file system detected, but no removable device found Wykryto system plików Chromebooka, ale nie znaleziono urządzenia wymiennego You must share your SD card with Linux using the ChromeOS Files program Musisz udostępnić swoją kartę SD Linuksowi za pomocą programu Pliki systemu operacyjnego ChromeOS Flex Flex Unable to check for updates. Please try again later. Nie można sprawdzać aktualizacji. Proszę spróbować ponownie później. Target Time Czas docelowy PRS1 Humidifier Target Time Czas docelowy nawilżacza PRS1 Hum. Tgt Time Czas docel. nawilżania 99.5% 99.5% varies różne Backing up files... Zachowywanie plików... Reading data files... Odczytywanie plików danych... Snoring event. Zdarzenie chrapania. SN SN model %1 model %1 DreamStation 2 DreamStation 2 unknown model nieznany model iVAPS iVAPS Response Odpowiedź Soft Miękki Standard Standard BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T SmartStop SmartStop Machine auto stops by breathing Aparat wyłącza się sam przez oddychanie Smart Stop Smart Stop Patient View Widok pacjenta Simple Prosty Advanced Zaawansowany Your ResMed CPAP machine (Model %1) has not been tested yet. Twój aparat ResMed (model %1) nie był dotąd testowany. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Wygląda to dość podobnie do innych aparatów by mogło działać, ale deweloperzy chcieliby kopię (.zip) karty SD z tego aparatu by upewnić się, że działa w OSCAR. SensAwake level poziom czułości wybudzania Expiratory Relief ulga wydechowa Expiratory Relief Level poziom ulgi wydechowej Humidity wilgotność SleepStyle styl snu Apnea bezdech An apnea reportred by your CPAP machine. Bezdech zaraportowany przez aparat. AI=%1 AI=%1 This page in other languages: Ta strona w innych językach: %1 Graphs %1 Wykresów %1 of %2 Graphs %1 z %2 Wykresów %1 Event Types %1 Zdarzeń %1 of %2 Event Types %1 z %2 Zdarzeń Report about:blank about:blank SessionBar %1h %2m %1h %2m No Sessions Present Brak sesji SleepStyleLoader Import Error Błąd importu This Machine Record cannot be imported in this profile. Ten zapis z aparatu nie moze być zaimportowany do tego profilu. The Day records overlap with already existing content. Zapis z dnia nakłada się na istniejący zapis. Statistics CPAP Statistics Statystyki CPAP CPAP Usage Użycie CPAP Average Hours per Night Średnio godzin na noc Therapy Efficacy Efektywność leczenia Leak Statistics Statystyka wycieków Pressure Statistics Statystyka ciśnienia Oximeter Statistics Statystyki pulsoksymetru Blood Oxygen Saturation Saturacja krwi tlenem Pulse Rate Częstotliwość pulsu %1 Median Mediana %1 Average %1 Średnia %1 Min %1 Min %1 Max %1 Max %1 %1 Index Wskaźnik %1 % of time in %1 % czasu w %1 % of time above %1 threshold % czasu powyżej granicy %1 % of time below %1 threshold % czasu poniżej granicy %1 Name: %1, %2 Nazwisko: %1, %2 DOB: %1 Data urodzenia: %1 Phone: %1 Telefon: %1 Email: %1 Email: %1 Address: Adres: Days Used: %1 Dni użycia: %1 Low Use Days: %1 Dni niskiego używania: %1 Compliance: %1% Użycie zgodne z wymaganiami: %1% Days AHI of 5 or greater: %1 Dni z AHI =5 lub powyżej: %1 Best AHI Najlepsze AHI Date: %1 AHI: %2 Dnia: %1 AHI: %2 Worst AHI Najgorsze AHI Best Flow Limitation Najlepsze ograniczenia przepływu (FL) Date: %1 FL: %2 Dnia: %1 FL: %2 Worst Flow Limtation Najgorsze ograniczenia przepływu No Flow Limitation on record Brak ograniczeń przepływu w zapisie Worst Large Leaks Najgorsze duże wycieki Date: %1 Leak: %2% Dnia: %1 Wyciek: %2% No Large Leaks on record Brak dużych wycieków w zapisie Worst CSR Najgorszy oddech okresowy (CSR) Date: %1 CSR: %2% Dnia: %1 CSR: %2% No CSR on record Brak oddechów okresowych w zapisie Worst PB Najgorszy oddech okresowy Date: %1 PB: %2% Dnia: %1 PB: %2% No PB on record Brak oddechów okresowych w zapisie Want more information? Chcesz więcej informacji? OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR potrzebuje wszystkich danych podsumowań do obliczenia najlepszych/najgorszych danych dla poszczególnych dni. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Proszę zaptaszkuj w Preferencjach "załaduj podsumowania na starcie". Best RX Setting Najlepsze ustawienia Date: %1 - %2 Dnia: %1 - %2 Worst RX Setting Najgorsze ustawienia Most Recent Najnowsze Last Week Ostatni tydzień Last 30 Days Ostatnie 30 dni Last 6 Months Ostatnie 6 miesięcy Last Year Ostatni rok Last Session Ostatnia sesja Details Szczegóły No %1 data available. Brak danych %1. %1 day of %2 Data on %3 %1 dzień z danymi %2 na %3 %1 days of %2 Data, between %3 and %4 %1 dni z danymi %2, między %3 a %4 Days Dni Pressure Relief Ulga ciśnienia Pressure Settings Ustawienia ciśnienia Machine Information Informacje o aparacie First Use Pierwsze użycie Last Use Ostatnie użycie OSCAR is free open-source CPAP report software OSCAR to wolne oprogramowanie open source do raportowania CPAP Oscar has no data to report :( OSCAR nie ma danych do raportu :( Compliance (%1 hrs/day) Zgodność (%1 godz/dzień) Changes to Machine Settings Zmiany ustawień urządzenia No data found?!? Nie znaleziono danych? AHI: %1 AHI: %1 Total Hours: %1 Razem godzin: %1 This report was prepared on %1 by OSCAR %2 Ten raport został przygotowany %1 przez OSCAR %2 Welcome What would you like to do? Co chcesz zrobić? CPAP Importer Import danych CPAP Oximetry Wizard Kreator pulsoksymetru Daily View Widok dzienny Overview Przegląd Statistics Statystyki It would be a good idea to check File->Preferences first, Dobrym pomysłem jest sprawdzenie najpierw - Plik-Preferencje as there are some options that affect import. ponieważ jest tam kilka opcji wpływających na import. Note that some preferences are forced when a ResMed machine is detected Zauważ, że kilka opcji jest wymuszonych po wykryciu aparatu ResMed First import can take a few minutes. Pierwszy import trwa kilka minut. The last time you used your %1... Ostatnio użyto aparatu %1... last night ostatniej nocy %2 days ago %2 dni temu was %1 (on %2) %1 ( %2) %1 hours, %2 minutes and %3 seconds %1 godz., %2 min. i %3 sek Your machine was on for %1. Aparat działał przez %1. <font color = red>You only had the mask on for %1.</font> <font color = red>Maska była założona przez %1.</font> under poniżej over powyżej reasonably close to rozsądnie blisko equal to równe z You had an AHI of %1, which is %2 your %3 day average of %4. AHI wynosiło %1, co stanowi %2 %3 dniowej średniej wynoszącej %4. Your machine was under %1-%2 %3 for %4% of the time. Aparat był poniżej %1-%2 %3 przez %4% czasu. Your average leaks were %1 %2, which is %3 your %4 day average of %5. Średnie wycieki wynosiły %1 %2, co stanowi %3 %4 dniowej średniej %5. No CPAP data has been imported yet. Dotąd nie zaimportowano żadnych danych CPAP. Welcome to the Open Source CPAP Analysis Reporter Witaj w OSCAR Your CPAP machine used a constant %1 %2 of air Twój aparat podawał stałe ciśnienie %1 %2 Your pressure was under %1 %2 for %3% of the time. Ciśnienie było poniżej %1 %2 przez %3% czasu. Your machine used a constant %1-%2 %3 of air. Ten aparat podawał stałe %1-%2 %3 powietrza. Your EPAP pressure fixed at %1 %2. EPAP (ciśnienie na wydechu) ustaliło się na poziomie %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. IPAP (ciśnienie na wdechu) było poniżej %1 %2 przez %3% czasu. Your EPAP pressure was under %1 %2 for %3% of the time. EPAP (ciśnienie na wydechu) było poniżej %1 %2 przez %3% czasu. <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">UWAGA: </span><span style=" color:#ff0000;">Karty SD z aparatów ResMed S9 muszą być zablokowane </span><span style=" font-weight:600; color:#ff0000;">przed włożeniem do komputera.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Niektóre systemy operacyjne dopisują pliki indeksu na karcie bez pytania, co może uczynić kartę nieczytelną dla aparatu CPAP.</span></p></body></html> 1 day ago 1 dzień wcześniej gGraph %1 days %1 dni gGraphView 100% zoom level 100% powiększenie Reset Graph Layout Resetuj Układ Wykresu Resets all graphs to a uniform height and default order. Przywróć wszystkie wykresy do jednakowej wysokości i domyślnego układu. Y-Axis Oś Y Plots Wykresy CPAP Overlays Nakładki CPAP Oximeter Overlays Nakładki pulsoksymetru Dotted Lines Linie kropkowane Remove Clone Usuń klona Clone %1 Graph Wykres klona %1 Double click title to pin / unpin Click and drag to reorder graphs Kliknij dwukrotnie apy przypiąć/odpiąć Kliknij i przeciągnij aby zmienić układ wykresów Restore X-axis zoom to 100% to view entire selected period. Przywróć zoom osi X do 100% aby zobaczyć cały zaznaczony okres. Restore X-axis zoom to 100% to view entire day's data. Przywróć zoom osi X do 100% aby zobaczyć dane całego dnia. OSCAR-code-v1.3.1/Translations/Portugues.pt.ts000066400000000000000000014333651417327530600211600ustar00rootroot00000000000000 AboutDialog &About &Sobre Release Notes Notas da Versão Credits Créditos GPL License Licença GPL Close Fechar Show data folder Exibir pasta de dados About OSCAR %1 Sorry, could not locate About file. Sorry, could not locate Credits file. Sorry, could not locate Release Notes. OSCAR %1 OSCAR %1 Important: Importante: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. To see if the license text is available in your language, see %1. Para ver se o texto da licença está disponível no seu idioma, consulte%1. CMS50F37Loader Could not find the oximeter file: Could not open the oximeter file: CMS50Loader Could not get data transmission from oximeter. Impossível obter dados de transmissão do oxímetro. Please ensure you select 'upload' from the oximeter devices menu. Por favor certifique-se de selecionar 'enviar' do menu de dispositivos do oxímetro. Could not find the oximeter file: Could not open the oximeter file: CheckUpdates Checking for newer OSCAR versions Daily Go to the previous day Ir para o dia anterior Show or hide the calender Exibir ou ocultar o calendário Go to the next day Ir para o próximo dia Go to the most recent day with data records Ir para o dia mais recente com dados registrados Events Eventos View Size Ver tamanho Notes Notas Journal Diário Small Pequeno Medium Médio Big Grande I'm feeling ... If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Flags Marcadores Graphs Gráficos Show/hide available graphs. Color Cor u u B B Zombie Zumbi Weight Peso Awesome Ótimo B.M.I. I.M.C. Bookmarks Favoritos Add Bookmark Adicionar favorito Starts Inícios Remove Bookmark Remover Favorito Breakdown Discriminação events eventos No %1 events are recorded this day Nenhum evento %1 registrado neste dia %1 event evento %1 %1 events eventos %1 UF1 UF1 UF2 UF2 Session Start Times Tempos de Início de Sessão Session End Times Tempos de Término de Sessão Duration Duração Position Sensor Sessions Sessões de Sensor de Posição Details Detalhes Time at Pressure Tempo na Pressão Unknown Session Sessão Desconhecida Click to %1 this session. Clique para %1 essa sessão. disable ativar enable desativar %1 Session #%2 %1 Sessão #%2 %1h %2m %3s %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. PAP Mode: %1 (Mode and Pressure settings missing; yesterday's shown.) 99.5% 90% {99.5%?} Time over leak redline Tempo acima da linha vermelha de vazamento Event Breakdown Discriminação de Eventos Unable to display Pie Chart on this system 10 of 10 Event Types This CPAP machine does NOT record detailed data Sessions all off! Sessões todas desativadas! Sessions exist for this day but are switched off. Sessões existem para esse dia mas estão desativadas. Impossibly short session Sessão impossivelmente curta Zero hours?? Zero horas?? BRICK :( PROBLEMA :( Sorry, this machine only provides compliance data. Complain to your Equipment Provider! Reclama para seu fornecedor do equipamento! This bookmark is in a currently disabled area.. 10 of 10 Graphs Statistics Estatísticas Oximeter Information Informação do Oxímetro SpO2 Desaturations Dessaturações de SpO2 Pulse Change events Eventos de Mudança de Pulso SpO2 Baseline Used Patamar SpO2 Usado Machine Settings Configurações de Máquina Session Information Informações da Sessão CPAP Sessions Sessões CPAP Oximetry Sessions Sessões de Oxímetro Sleep Stage Sessions Sessões de Estátio de Sono Model %1 - %2 Modelo %1 - %2 This day just contains summary data, only limited information is available. Esse dia apenas contem dados sumários, apenas informação limitada está disponível. Total time in apnea Tempo total em apneia Total ramp time Tempo total de rampa Time outside of ramp Tempo fora da rampa Start Início End Fim "Nothing's here!" "Nada aqui!" No data is available for this day. Pick a Colour Escolha uma Cor Bookmark at %1 Favorito em %1 ExportCSV Export as CSV Exportar como CSV Dates: Datas: Resolution: Resolução: Details Detalhes Sessions Sessões Daily Diariamente Filename: Arquivo: Cancel Cancelar Export Exportar Start: Início: End: Fim: Quick Range: Intervalo Rápido: Most Recent Day Dira Mais Recente Last Week Última Semana Last Fortnight Última Quinzena Last Month Último Mês Last 6 Months Últimos 6 Meses Last Year Último Ano Everything Tudo Custom Personalizado Details_ Details_ Sessions_ Sessions_ Summary_ Summary_ Select file to export to Selecione arquivo para o qual exportar CSV Files (*.csv) Arquivos CSV (*.csv) DateTime DataHora Session Sessão Event Evento Data/Duration Data/Duração Date Data Session Count Contagem de Sessão Start Início End Fim Total Time Tempo Total AHI IAH Count Contagem FPIconLoader Import Error Erro de Importação This Machine Record cannot be imported in this profile. O relatório dessa máquina não pode ser importado nesse perfil. The Day records overlap with already existing content. Os registros diários sobrepõem-se com conteúdo pré-existente. Help Hide this message Esconder essa mensagem Search Topic: Tópico de Busca: Help Files are not yet available for %1 and will display in %2. Arquivos de ajuda não estão disponíveis ainda para %1 e aparecerão em %2. Help files do not appear to be present. HelpEngine did not set up correctly HelpEngine não instalou corretamente HelpEngine could not register documentation correctly. HelpEngine não conseguiu registrar a documentação corretamente. Contents Conteúdos Index Índice Search Busca No documentation available Please wait a bit.. Indexing still in progress No Não %1 result(s) for "%2" %1 resultado(s) para "%2" clear limpar MD300W1Loader Could not find the oximeter file: Could not open the oximeter file: MainWindow &Statistics E&statísticas Report Mode Modo Relatório Standard Padrão Monthly Mensal Date Range Faixa de Data Statistics Estatísticas Daily Diariamente Overview Visão Geral Oximetry Oximetria Import Importar Help Ajuda &File &Arquivo &View &Exibir &Reset Graphs &Help A&juda Troubleshooting &Data &Dados &Advanced A&vançado Purge ALL Machine Data Rebuild CPAP Data Recompilar Dados CPAP &Import CPAP Card Data Show Daily view Show Overview view &Maximize Toggle Maximize window Reset Graph &Heights Reset sizes of graphs Show Right Sidebar Show Statistics view Import &Dreem Data Show &Line Cursor Show Daily Left Sidebar Show Daily Calendar Create zip of CPAP data card Create zip of OSCAR diagnostic logs Create zip of all OSCAR data Report an Issue System Information Show &Pie Chart Show Pie Chart on Daily page Standard graph order, good for CPAP, APAP, Bi-Level Advanced Advanced graph order, good for ASV, AVAPS Show Personal Data Check For &Updates Purge Current Selected Day &CPAP &CPAP &Oximetry &Oximetria &Sleep Stage &Position &All except Notes All including &Notes &Preferences &Preferências &Profiles &Perfis &About OSCAR Show Performance Information Mostrar Informação de Desempenho CSV Export Wizard Assistente de Exportação CSV Export for Review Exportar para Revisão E&xit Sai&r Exit Sair View &Daily Mostrar &Diariamente View &Overview Ver Visã&o Geral View &Welcome Ver Boas-vi&ndas Use &AntiAliasing Usar &AntiAliasing Show Debug Pane Mostrar Painel Depurador Take &Screenshot Salvar Captura de &Tela O&ximetry Wizard Assistente de O&ximetria Print &Report Imprimir &Relatório &Edit Profile &Editar Perfil Import &Viatom/Wellue Data Daily Calendar Calendário Diário Backup &Journal Fazer Backu&p do Diário Online Users &Guide &Guia Online do Utilizador &Frequently Asked Questions Perguntas &Frequentes &Automatic Oximetry Cleanup Limpeza &Automática de Oximetria Change &User Trocar &Utilizador Purge &Current Selected Day Remover Dia S&elecionado Right &Sidebar Barra Lateral Di&reita Daily Sidebar Barra Lateral Diária View S&tatistics Ver E&statísticas Navigation Navegação Bookmarks Favoritos Records Registros Exp&ort Data Exp&ortar Dados Profiles Perfis Purge Oximetry Data Remover Dados Oximétricos View Statistics Ver Estatísticas Import &ZEO Data Importar Dados &ZEO Import RemStar &MSeries Data Importar Dados RemStar &MSeries Sleep Disorder Terms &Glossary G&lossário de Termos de Desordens do Sono Change &Language A&lterar Idioma Change &Data Folder Alterar Pasta de &Dados Import &Somnopose Data Importar Dados &Somnopose Current Days Dias Atuais Welcome Bem-Vindo &About So&bre Access to Import has been blocked while recalculations are in progress. Acesso para importar foi bloqueado enquanto cálculos estão em progresso. Importing Data Importando Dados Please wait, importing from backup folder(s)... Por favor aguarde, importando da(s) pasta(s) de backup... Import Problem Importar Problema Couldn't find any valid Machine Data at %1 Impossível encontrar qualquer Dado de Máquina valido em %1 Please insert your CPAP data card... Por favor insira seu cartão de dados do CPAP... Import is already running in the background. Importação já em execução em segundo plano. CPAP Data Located Dados do CPAP Localizados Import Reminder Lembrete de Importação Please open a profile first. Por favor abra um perfil primeiro. Check for updates not implemented Please note, that this could result in loss of data if OSCAR's backups have been disabled. You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete %1's Journal Diário de %1 Choose where to save journal Escolha onde salvar o diário XML Files (*.xml) Arquivos XML (*.xml) Access to Preferences has been blocked until recalculation completes. Acesso às preferêcias foi bloqueado até que os cálculos terminem. Are you sure you want to delete oximetry data for %1 Você tem certeza de que deseja deletar dados oximétricos para %1 <b>Please be aware you can not undo this operation!</b> <b>Por favor esteja ciente de que você não pode desfazer a operação!</b> Select the day with valid oximetry data in daily view first. Selecione o dia com dados oxímetros válidos na visualização diária primeiro. Help Browser Navegador de Ajuda Loading profile "%1" Carregando perfil "%1" Choose a folder Escolha uma pasta A %1 file structure for a %2 was located at: Uma estrutura de arquivos %1 para %2 foi localizada em: A %1 file structure was located at: Uma estrutura de arquivos %1 foi localizada em: Would you like to import from this location? Você gostaria de importar dessa localização? Specify Especifique There was an error saving screenshot to file "%1" Erro ao salvar a captura de tela para o arquivo "%1" Screenshot saved to file "%1" Captura de tela salva para o arquivo "%1' Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Contanto que você possua <i>seus<b>próprios</b> backups para TODOS os seus dados de CPAP</i>, você ainda pode completar essa operação, mas você precisará restaurar manualmente a partir dos seus backups. Are you really sure you want to do this? Tem certeza de que deseja fazer isso? Because there are no internal backups to rebuild from, you will have to restore from your own. Por não existir backups internos a partir dos quais recompilar, você terá que restaurar a partir dos seus próprios. Imported %1 CPAP session(s) from %2 Importadas %1 sessão(ões) CPAP de %2 %1 (Profile: %2) Import Success Sucesso na Importação Already up to date with CPAP data at %1 Up to date Atualizado No profile has been selected for Import. Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Find your CPAP data card Choose where to save screenshot Image files (*.png) The User's Guide will open in your default browser The FAQ is not yet implemented If you can read this, the restart command didn't work. You will have to do it yourself manually. Are you sure you want to rebuild all CPAP data for the following machine: For some reason, OSCAR does not have any backups for the following machine: Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Gostaria de importar de seus próprios backups agora? (você não terá quaisquer dados visíveis para essa máquina até fazê-lo) OSCAR does not have any backups for this machine! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> The Glossary will open in your default browser Export review is not yet implemented Would you like to zip this card? Choose where to save zip ZIP files (*.zip) Creating zip... Calculating size... Reporting issues is not yet implemented Note as a precaution, the backup folder will be left in place. Note como precaução, a pasta de backup será mantida no mesmo lugar. Are you <b>absolutely sure</b> you want to proceed? Você tem <b>absoluta certeza</b> de que deseja prosseguir? A file permission error casued the purge process to fail; you will have to delete the following folder manually: No help is available. There was a problem opening MSeries block File: Houve um problema abrindo o arquivo de bloco MSeries: MSeries Import complete Importação de MSeries completa OSCAR Information MinMaxWidget Auto-Fit Auto-Ajuste Defaults Padrões Override Substituir The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. O modo de escala do eixo-Y, 'Auto-Ajuste' para auto-escala, 'Padrões' para configurar de acordo com o fabricante, e 'Substituir' para escolher o seu próprio. The Minimum Y-Axis value.. Note this can be a negative number if you wish. O valor Mínimo de eixo-Y.. Note que esse pode ser um número negativo se você quiser. The Maximum Y-Axis value.. Must be greater than Minimum to work. O valor Máximo de eixo-Y.. Deve ser maior do que o mínimo para funcionar. Scaling Mode Modo de Escala This button resets the Min and Max to match the Auto-Fit Esse botão redefine o Min e Máx para combinar com a Auto-Escala NewProfile Edit User Profile Editar Perfil de Utilizador I agree to all the conditions above. Eu concordo com todas as condições acima. User Information Informação de Utilizador User Name Nome de Utilizador Password Protect Profile Proteger Perfil com Senha Password Senha ...twice... ...duas vezes... Locale Settings Configurações de Localidade Country País TimeZone Zona Horária about:blank about:blank Very weak password protection and not recommended if security is required. DST Zone Horário de Verão Personal Information (for reports) Informação Pessoal (para relatórios) First Name Primeiro Nome Last Name Sobrenome It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Não há problema em mentir ou pular isso, mas sua idade aproximada é necessária para melhorar a qualidade de certos cálculos. D.O.B. Data de Nasc. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Sexo biológico (nascença) é necessário algumas vezes para melhorar a precisão de alguns cálculos, sinta-se livre para deixar isso em branco e pular perguntas.</p></body></html> Gender Sexo Male Masculino Female Feminino Height Altura Metric English Contact Information Informações de Contato Address Endereço Email Email Phone Telefone CPAP Treatment Information Informação de Tratamento CPAP Date Diagnosed Data do Diagnóstico Untreated AHI IAH Não Tratado CPAP Mode Modo CPAP CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure Pressão RX Doctors / Clinic Information Informação do Médico / Clínica Doctors Name Nome do Médico Practice Name Nome da Clínica Patient ID RG do Paciente &Cancel &Cancel &Back &Voltar &Next &Próximo Select Country Selecione o País This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Esse software está sendo projetado para ajudá-lo a revisar os dados produzidos por máquinas CPAP e equipamento relacionado. PLEASE READ CAREFULLY POR FAVOR LEIA COM ATENÇÃO Accuracy of any data displayed is not and can not be guaranteed. Precisão de qualquer dado mostrado não é não pode ser garantida. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Quaisquer relatórios gerados são para USO PESSOAL SOMENTE, e DE NENHUM MODO servem para propósitos de observância ou diagnóstico médico. Use of this software is entirely at your own risk. Uso desse software é interamente por sua conta e risco. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team Welcome to the Open Source CPAP Analysis Reporter OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Please provide a username for this profile Por favor forneça um nome de utilizador para esse perfil Passwords don't match Senhas não correspondem Profile Changes Mudanças de Perfil Accept and save this information? Aceitar e salvar essa informação? &Finish &Finalizar &Close this window Fe&char essa janela Overview Range: Intervalo: Last Week Última Semana Last Two Weeks Últimas Duas Semanas Last Month Último Mês Last Two Months Últimos Dois Meses Last Three Months Últimos Três Meses Last 6 Months Últimos 6 Meses Last Year Último Ano Everything Tudo Custom Personalizado Snapshot Start: Início: End: Fim: Reset view to selected date range Redefinir visualização para a faixa de data selecionada Toggle Graph Visibility Alternar Visibilidade do Gráfico Drop down to see list of graphs to switch on/off. Desça para ver a lista de gráficos para des/ativar. Graphs Gráficos Respiratory Disturbance Index Índice Distúrbio Respiratório Apnea Hypopnea Index Índice Hipoapneia Apneia Usage Uso Usage (hours) Uso (horas) Session Times Tempos de Sessão Total Time in Apnea Tempo Total em Apneia Total Time in Apnea (Minutes) Tempo Total em Apneia (Minutos) Body Mass Index Índice Massa Corporal How you felt (0-10) Como se sentiu (0-10) 10 of 10 Charts Show all graphs Mostrar todos os gráficos Hide all graphs Esconder todos os gráficos OximeterImport Oximeter Import Wizard Assistente de Importação do Oxímetro Skip this page next time. Pular essa página da próxima vez. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? De onde você gostaria de importar? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Se você não se importa de ficar conectado a um computador ligado a noite toda, essa opção fornece um gráfico pletismográfico útil, que fornece uma indicação de rítmo cardíaco, em adição às leituras oximétricas normais.</p></body></html> Record attached to computer overnight (provides plethysomogram) Registrar conectado ao computador durante a noite (fornece pletismograma) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Essa opção permite que você importe arquivos de dado criados pelo software que acompanha o seu Oxímetro de Pulso, como o SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Importar de um arquivo de dado salvo por outro programa, como SpO2Review Please connect your oximeter device Por favor conecte seu aparelho oxímetro If you can read this, you likely have your oximeter type set wrong in preferences. Se você pode ler isso, provavelmente você configurou o tipo de oxímetro errado nas preferências. Please connect your oximeter device, turn it on, and enter the menu Press Start to commence recording Pressione Início para começar o registro Show Live Graphs Mostrar Gráficos em Tempo Real Duration Duração Pulse Rate Taxa de Pulso Multiple Sessions Detected Múltiplas Sessões Detectadas Start Time Tempo de Início Details Detalhes Import Completed. When did the recording start? Importação Completada. Quando a gravação começou? Oximeter Starting time Tempo de Início do Oxímetro I want to use the time reported by my oximeter's built in clock. Eu gostaria de usar o tempo relatado pelo relógio interno do meu oxímetro. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Eu comecei essa gravação de oxímetro ao mesmo (ou próximo) tempo que minha máquina CPAP. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Nota: Sincronizar para o tempo de início do CPAP será mais preciso sempre.</p></body></html> Choose CPAP session to sync to: Escolha uma sessão CPAP com a qual sincronizar: You can manually adjust the time here if required: Você pode ajustar manualmente o tempo aqui se necessário: HH:mm:ssap HH:mm:ss &Cancel &Cancelar &Information Page Página de &Informação CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Definir data/hora do aparelho <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Marque para ativar a atualização do identificador do aparelho na próxima importação, o que é útil para aqueles que possuem múltiplos oxímeros largados por perto.</p></body></html> Set device identifier Definir identificador do aparelho Erase session after successful upload Apagar sessão após envio concluído Import directly from a recording on a device Importar diretamente de uma gravação num aparelho <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Lembrete para utilizadores de CPAP: </span><span style=" color:#fb0000;">Você lembrou de importar as sessões do CPAP primeiro?<br/></span>Se você esqueceu, você não terá um tempo válido para o qual sincronizar essa sessão de oximetria.<br/>Para garantir uma boa sincronia entre aparelhos, sempre tente iniciar ambos ao mesmo tempo.</p></body></html> Please choose which one you want to import into OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> &Retry Tenta&r Novamente &Choose Session Es&colher Sessão &End Recording T&erminar Gravação &Sync and Save &Sincronizar e Salvar &Save and Finish &Salvar e Terminar &Start Ini&ciar Scanning for compatible oximeters Buscando por oxímetros compatíveis Could not detect any connected oximeter devices. Nenhum oxímetro conectado foi detectado. Connecting to %1 Oximeter Conectando ao Oxímetro %1 Renaming this oximeter from '%1' to '%2' Renomeando esse oxímetro de '%1' para '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. O nome do oxímetro é diferente.. Se você possui apenas um e está compartilhando-o entre perfis, defina o mesmo nome em todos os perfis. "%1", session %2 "%1", sessão %2 Nothing to import Nada para importar Your oximeter did not have any valid sessions. Seu oxímetro não tinha quaisquer sessões válidas. Close Fechar Waiting for %1 to start Aguardando %1 para começar Waiting for the device to start the upload process... Aguardando pelo dispositivo para iniciar o processo de envio... Select upload option on %1 Selecione a opção upload em %1 You need to tell your oximeter to begin sending data to the computer. Você precisa dizer ao oxímetro para começar a enviar dados para o computador. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Por favor conecte seu oxímetro, entre no menu e selecione upload para começar a transferência de dados... %1 device is uploading data... Dispositivo %1 está enviando dados... Please wait until oximeter upload process completes. Do not unplug your oximeter. Por favor aguarde até o processo de envio de dados do oxímetro terminar. Não desconecte seu oxímetro. Oximeter import completed.. Importação do oxímetro completada.. Select a valid oximetry data file Selecione um arquivo de dados válido de oxímetro Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Arquivos de Oximetria (*.so *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Nenhum módulo de oximetria pode interpretar o arquivo fornecido: Live Oximetry Mode Módo de Oximetria em Tempo Real Live Oximetry Stopped Oximetria em Tempo Real Parada Live Oximetry import has been stopped A importação de oximetria em tempo real foi parada Oximeter Session %1 Sessão de Oxímetro %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Se você está tentando sincronizar dados de oximetria e CPAP, por favor certifique de que você importou sua sessão de CPAP antes de prosseguir! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Oximeter not detected Oxímetro não detectado Couldn't access oximeter Impossível acessar oxímetro Starting up... Inicializando... If you can still read this after a few seconds, cancel and try again Se você ainda pode ler isso após alguns segundos, cancele e tente novamente Live Import Stopped Importação em Tempo Real Parada %1 session(s) on %2, starting at %3 %1 sessão(ões( em %2, começando em %3 No CPAP data available on %1 Nenhum dado de CPAP disponível em %1 Recording... Gravando... Finger not detected Dedo não detectado I want to use the time my computer recorded for this live oximetry session. Eu quero usar a hora que meu computador registrou para essa sessão de oximetria em tempo real. I need to set the time manually, because my oximeter doesn't have an internal clock. Eu preciso definir o tempo manualmente, porque meu oxímetro não possui relógio interno. Something went wrong getting session data Algo deu errado ao obter os dados da sessão Welcome to the Oximeter Import Wizard Bem-vindo ao Assistente de Importação de Oxímetro Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Oxímetros de pulso são dispositivos médicos usados para medir a saturação de oxigênio no sangue. Durante eventos extensos de apenai e padrões anormais de respiração, os níveis de saturação de oxigênio no sangue podem cair significativamente, e podem indicar problemas que precisam de atenção médica. You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Você pode desejar notar, outras companhias, como a Pulox, simplesmente rotulam o Contec CMS50 sob nomes novos, como o Pulox PO-200, PO-300, PO-400. Estes devem funcionar também. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Ele também pode ler dos arquivos .dat do ChoiceMMed MD300W1. Please remember: Por favor lembre: Important Notes: Notas importantes: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Aparelhos CMS50D+ não possuem relógio interno e não registram um tempo de inínio, se você não possui uma sessão de CPAP para sincronizar a gravação, você terá de especificar manualmente o tempo de início após completar o processo de importação. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Mesmo para aparelhos com um relógio interno, ainda é recomendado desenvolver o hábito de iniciar as gravações de oxímetro ao mesmo tempo que as sessões de CPAP, porque os relógios internos dos CPAPs tendem a desviar com o tempo, e nem todos podem ser redefinidos facilmente. Oximetry Date Data d/MM/yy h:mm:ss AP d/MM/yy HH:mm:ss R&eset R&edefinir Pulse Pulso &Open .spo/R File Abrir arquivo .sp&o/R Serial &Import &Importação Serial &Start Live Ini&ciar em Tempo Real Serial Port Porta Serial &Rescan Ports &Rebuscar Portas PreferencesDialog Preferences Preferências &Import &Importar Combine Close Sessions Combinar Sessões Fechadas Minutes Minutos Multiple sessions closer together than this value will be kept on the same day. Múltiplas sessões mais próximas do que esse valor serão mantidas no mesmo dia. Ignore Short Sessions Ignorar Sessões Curtas <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessões mais curtas do que isso em duração não serão mostradas<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Tempo de Divisão do Dia Sessions starting before this time will go to the previous calendar day. Sessões iniciando antes dessa hora irão para o dia anterior no calendário. Session Storage Options Opções de Armazenamento de Sessões Create SD Card Backups during Import (Turn this off at your own peril!) Criar backups do cartão SD durante a importação (desative isso por sua conta e risco!) Compress SD Card Backups (slower first import, but makes backups smaller) Comprimir os Backups do Cartão SD (primeira importação mais lenta, mas os backups ficam mais leves) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Considere dias com uso abaixo disso "inobservante" 4 horas é geralmente considerado observante. hours horas Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Ativar/desativar melhorias experimentais na marcação de eventos. Elas permitem detectar eventos limítrofes, e alguns que a máquina deixa passar. Essa opção deve ser ativada antes da importação, do contrário uma limpeza é necessária. Flow Restriction Restrição de Fluxo Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Porcentagem da restrição em fluxo de ar do valor médio. Um valor de 20% funciona bem para detectar apneias. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">As marcações personalizadas são um método experimental de detecção de eventos que a máquina deixa passar. Eles <span style=" text-decoration: underline;">não</span> são incluídos no AHI.</p></body></html> Duration of airflow restriction Duração da restrição de fluxo de ar s s Event Duration Duração de Evento Allow duplicates near machine events. Permitir duplicados próximos de eventos da máquina. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Ajuste a quantidade de dados considerada para cada ponto no gráfico AHI/Hora. Padrão em 60 minutos.. Altamente recomendado manter nesse valor. minutes minutos Reset the counter to zero at beginning of each (time) window. Redefinir o contador a zero no começo de cada janela (de tempo). Zero Reset Redefinir a Zero CPAP Clock Drift Deslocamento do Relógio do CPAP Do not import sessions older than: Não importar sessões mais antigas que: Sessions older than this date will not be imported Sessões mais antigas que essa data não serão importadas dd MMMM yyyy dd MMMM yyyy Show in Event Breakdown Piechart Mostrar no Gráfico Torta de Discriminação de Evento Resync Machine Detected Events (Experimental) Resincronizar Eventos Detectados pela Máquina (Experimental) User definable threshold considered large leak Limitar definível pelo utilizador considerando vazamento grande Whether to show the leak redline in the leak graph Mostrar ou não a linha vermelha de vazamento no gráfico de vazamentos Search Buscar &Oximetry &Oximetria Percentage drop in oxygen saturation Queda de porcentagem na saturação de oxigênio Pulse Pulso Sudden change in Pulse Rate of at least this amount Mudança brusca na Taxa de Pulso de pelo menos essa intensidade bpm bpm Minimum duration of drop in oxygen saturation Duração mínima da queda na saturação de oxigênio Minimum duration of pulse change event. Duração mínima do evento de mudança no pulso. Small chunks of oximetry data under this amount will be discarded. Pequenos pedaços dos dados de oximetria abaixo desse valor serão descartados. &General &Geral General Settings Configurações Gerais Daily view navigation buttons will skip over days without data records Botões de navegação na visão diária irão pular dias sem dados registrados Skip over Empty Days Pular sobre Dias Vazios Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Permitir múltiplos núcleos de CPU quando disponíveis para melhorar desempenho. Afeta principalmente a importação. Enable Multithreading Ativar Multithreading Bypass the login screen and load the most recent User Profile Ignorar a tela de login e carregar o perfil de utilizador mais recente <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Esses recursos foram retirados recentemente. Eles retornarão no futuro. </p></body></html> Changes to the following settings needs a restart, but not a recalc. Mudanças nas configurações seguintes exige um reinício, mas não uma recalculação. Preferred Calculation Methods Métodos de Cálculo Preferidos Middle Calculations Calculações de Meio Upper Percentile Percentil Superior For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Para consistência, utilizadores ResMed devem usar 95% aqui, já que esse é o único valor disponível nos dias de apenas resumo. Median is recommended for ResMed users. Mediana é recomendada para utilizadores ResMed. Median Mediana Weighted Average Média Ponderada Normal Average Média Normal True Maximum Máximo Verdadeiro 99% Percentile Percil de 99% Maximum Calcs Cálcs Máximos Session Splitting Settings Configurações de Separação de Sessão Don't Split Summary Days (Warning: read the tooltip!) Não Separar Dias Resumidos (Aviso: leia a dica de contexto!) Memory and Startup Options Opções de Memória e Inicialização Pre-Load all summary data at startup Pré-carregar todos os dados resumidos no início <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Essa opção mantem os dados de forma de onda e eventos na memória após o uso para acelerar a revisitação de dias.</p><p>Essa não é uma opção realmente necessária, já que seu sistema operativo retém arquivos previamente utilizados.</p><p>A recomendação é deixá-la desativada, a menos que seu computador possua muita memória livre.</p></body></html> Keep Waveform/Event data in memory Manter dados de forma de onda/eventos na memória <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Reduzir quaisquer diálogos de confirmação sem importância durante a importação.</p></body></html> Import without asking for confirmation Importar sem pedir confirmação General CPAP and Related Settings Configurações Gerais e Relacionadas ao CPAP Enable Unknown Events Channels Ativar Canais de Evento Desconhecidos AHI Apnea Hypopnea Index Índice de Apneia-Hipoapneia IAH RDI Respiratory Disturbance Index Índice de Distúrbio Respiratório IDR AHI/Hour Graph Time Window Janela de Tempo do Gráfico IAH/Hora Preferred major event index Índice preferido de evento principal Compliance defined as Observância definida como Flag leaks over threshold Marcar vazamentos acima do limite Seconds Segundos <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Nota: Isso não é pretendido para correções de fuso-horário! Certifique-se de que o fuso-horário e o relógio do seu sistema estão configurados corretamente.</p></body></html> Hours Horas <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Máximo verdadeiro é o máximo do conjunto de dados.</p><p>99º percentil filtra os mais raros distanciamentos.</p></body></html> Combined Count divided by Total Hours Contagem combinada divididade pelas horas totais Time Weighted average of Indice A Média Ponderada pelo tempo do Índice Standard average of indice Média padrão do índice Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Compress Session Data (makes OSCAR data smaller, but day changing slower.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> Custom CPAP User Event Flagging Marcação de Evento Personalizada pelo Utilizador de CPAP This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Show Remove Card reminder notification on OSCAR shutdown Check for new version every Buscar uma nova versão a cada days. dias. Last Checked For Updates: Última Busca Por Atualizações: TextLabel Rótulo I want to be notified of test versions. (Advanced users only please.) &Appearance &Aparência Graph Settings Configurações de Gráfico <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> Bar Tops Topo de Barra Line Chart Gráficos de Linha Overview Linecharts Visão-Geral de Gráficos de Linha <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Isso facilita a rolagem quando o zoom é mais fácil em TouchPads bidirecionais sensíveis.</p><p>50 ms é o valor recomendado.</p></body></html> Scroll Dampening Amortecimento de rolagem Overlay Flags Marcações Sobrepostas Line Thickness Espessura de Linha The pixel thickness of line plots Espessura em pixels dos traços de linha Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Cache de pixmap é uma técnica de aceleração gráfica. Pode causar problemas no desenho de fontes na área de mostragem de gráficos na sua plataforma. Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Fonts (Application wide settings) Fontes (afeta o aplicativo todo) The visual method of displaying waveform overlay flags. O método visual de mostrar marcações de sobreposição de formas de onda. Standard Bars Barras Padrão Graph Height Altura do Gráfico Default display height of graphs in pixels Altura de exibição, em pixels, padrão dos gráficos How long you want the tooltips to stay visible. Por quanto tempo você deseja que as dicas de contexto permaneçam visíveis. Events Eventos Reset &Defaults Redefinir Pa&drões <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Aviso: </span>Só porque você pode, não significa que é uma boa prática.</p></body></html> Waveforms Formas de Onda Flag rapid changes in oximetry stats Marcar mudanças bruscas nos números de oximetria Other oximetry options Outras opções de oximetria Discard segments under Descartar segmentos abaixo de Flag Pulse Rate Above Marcar Taxa de Pulso Acima de Flag Pulse Rate Below Marcar Taxa de Pulso Abaixo de <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Essa configuração deve ser usada com cautela...</span> Desativá-la traz consequências envolvendo a precisão de dias resumo-apenas, pois certos cálculos só funcionam corretamente se sessões de resumo provenientes de registros de dias individuais forem mantidas juntas. </p><p><span style=" font-weight:600;">Utilizadores ResMed:</span> Apenas porque parece natural para você e eu que a sessão reiniciada ao meio-dia deve estar no dia anterior, não significa que os dados da ResMed concordam conosco. O formato de índice de resumo STF.edf tem sérios pontos fracos que fazem com que isso não seja uma boa idéia.</p><p>Esta opção existe para apaziguar aqueles que não se importam e querem ver isso &quot;corrigido&quot; não importando os custos, mas saiba que isso tem um custo. Se você mantiver seu cartão SD inserido todas as noites, e importar pelo menos uma vez por semana, você não verá problemas com isso com grande frequência..</p></body></html> This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Este cálculo requer dados de vazamentos totais a serem fornecidos pela máquina de CPAP. (Por exemplo, PRS1, mas não a ResMed, que já tem isso) Os cálculos de vazamentos não intencionais usados aqui são lineares, eles não modelam a curva de ventilação da máscara. Se você usar algumas máscaras diferentes, escolha valores médios. E deve ficar próximo o suficiente. Calculate Unintentional Leaks When Not Present Calcular Vazamentos Não Intencionais Quando Não Presente 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Nota: Um método de cálculo linear é usado. Alterar esses valores requer um recálculo. Show flags for machine detected events that haven't been identified yet. Mostrar marcações para eventos detectados por máquina que ainda não foram identificados. Tooltip Timeout Tempo Limite de Dica de Contexto Graph Tooltips Dicas de Contexto de Gráfico Top Markers Marcadores de Topo Changing SD Backup compression options doesn't automatically recompress backup data. Auto-Launch CPAP Importer after opening profile Iniciar o importador de CPAP automaticamente depois de abrir o perfil Automatically load last used profile on start-up Carregar automaticamente o perfil usado por último ao abrir <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered Your masks vent rate at 20 cmH2O pressure Your masks vent rate at 4 cmH2O pressure l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Devido a limitações no projeto dos resumos, as máquinas da ResMed não suportam a alteração dessas opções.</p></body></html> Oximetry Settings Opções de oximetria <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Always save screenshots in the OSCAR Data folder Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR On Opening Ao Abrir Profile Perfil Welcome Bem-vindo Daily Diariamente Statistics Estatísticas Switch Tabs Alternar Abas No change Nenhuma mudança After Import Após Importação Other Visual Settings Outras Opções Visuais Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. O anti-aliasing aplica suavização aos desenhos do gráfico. Certas parcelas parecem mais atraentes com isso. Isso também afeta os relatórios impressos. Experimente e veja se você gosta. Use Anti-Aliasing Usar Anti-Aliasing Makes certain plots look more "square waved". Faz com que certos gráficos pareçam mais "quadrados ondulados". Square Wave Plots Gráficos de Onda Quadrada Use Pixmap Caching Usar Cache de Pixmap Animations && Fancy Stuff Animações e Coisas Chiques Whether to allow changing yAxis scales by double clicking on yAxis labels Permitir ou não a alteração das escalas do EixoY clicando duas vezes nos rótulos do EixoY Allow YAxis Scaling Permitir Escala do EixoY Whether to include machine serial number on machine settings changes report Include Serial Number Graphics Engine (Requires Restart) Motor Gráfico (Exige Reinício) Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Font Fonte Size Tamanho Bold Negrito Italic Itálico Application Aplicativo Graph Text Texto do Gráfico Graph Titles Títulos do Gráfico Big Text Texto Grande Details Detalhes &Cancel &Cancelar &Ok &Ok Name Nome Color Cor Flag Type Tipo de Marcação Label Rótulo CPAP Events Eventos CPAP Oximeter Events Eventos Oxímetro Positional Events Eventos Posicionais Sleep Stage Events Eventos de Estágio do Sono Unknown Events Eventos Desconhecidos Double click to change the descriptive name this channel. Clique duas vezes para alterar o nome descritivo desse canal. Double click to change the default color for this channel plot/flag/data. Clique duas vezes para alterar a cor padrão para esse canal/desenho/marcação/dado. <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> %1 %2 %1 %2 Overview Visão-Geral Double click to change the descriptive name the '%1' channel. Clique duass vezes para mudar o nome descritivo do canal '%1'. Whether this flag has a dedicated overview chart. Se esse sinalizador possui um gráfico de visão geral dedicado ou não. Here you can change the type of flag shown for this event Aqui você pode alterar o tipo de marcação mostrada para este evento This is the short-form label to indicate this channel on screen. Este é o rótulo de forma curta para indicar este canal na tela. This is a description of what this channel does. Esta é uma descrição do que esse canal faz. Lower Inferior Upper Superior CPAP Waveforms Formas de Onda de CPAP Oximeter Waveforms Formas de Onda de Oxímetro Positional Waveforms Formas de Onda Posicionais Sleep Stage Waveforms Formas de Onda de Estágio de Sono Whether a breakdown of this waveform displays in overview. Se uma discriminação dessa forma de onda é exibida na visão geral ou não. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Aqui você pode definir o limite <b>inferior</b> usado para determinados cálculos na forma de onda %1 Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Aqui você pode definir o limite <b>superior</b> usado para determinados cálculos na forma de onda %1 Data Processing Required Processamento de Dados Requerido A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Um procedimento de recuperação de dados / descompactação é necessário para aplicar essas alterações. Essa operação pode levar alguns minutos para ser concluída. Tem certeza de que deseja fazer essas alterações? Data Reindex Required Reordenação de Dados de Índice Requerida A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Um procedimento de reindexação de dados é necessário para aplicar essas alterações. Essa operação pode levar alguns minutos para ser concluída. Tem certeza de que deseja fazer essas alterações? Restart Required Reinício Requerido One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Uma ou mais das alterações feitas exigirão que esse aplicativo seja reiniciado para que essas alterações entrem em vigor. Você gostaria de fazer isso agora? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. If you need to conserve disk space, please remember to carry out manual backups. Are you sure you want to disable these backups? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Are you really sure you want to do this? Tem certeza mesmo de que deseja fazer isso? Flag Marcação Minor Flag Pequena Marcação Span Período Always Minor Sempre Pequeno No CPAP machines detected Will you be using a ResMed brand machine? Never Nunca This may not be a good idea Isso pode não ser uma boa ideia ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). As máquinas ResMed S9 apagam rotineiramente certos dados do seu cartão SD com mais de 7 e 30 dias (dependendo da resolução). ProfileSelector Filter: Filtro: Reset filter to see all profiles Version Versão &Open Profile Abr&ir Perfil &Edit Profile &Editar Perfil &New Profile &Novo Perfil Profile: None Perfil: Nenhum Please select or create a profile... Por favor selecione ou crie um perfil... Destroy Profile Destruir Perfil Profile Perfil Ventilator Brand Marca do Ventilador Ventilator Model Modelo do Ventilador Other Data Outros Dados Last Imported Importado por Último Name Nome You must create a profile Enter Password for %1 Digite a senha para %1 You entered an incorrect password Você digitou uma senha incorreta Forgot your password? Esqueceu sua senha? Ask on the forums how to reset it, it's actually pretty easy. Pergunte nos fóruns como redefiní-la, na verdade é muito fácil. Select a profile first Selecione um perfil primeiro The selected profile does not appear to contain any data and cannot be removed by OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Se você está tentando deletar porque você esqueceu a senha, você precisa ou redefiní-la ou deletar a pasta do perfil manualmente. You are about to destroy profile '<b>%1</b>'. Você está prestes a destruir o perfil '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Pense com cuidado, já que isso irá irreverssívelmente deletar o perfil bem como todos os <b>dados de backup</b> guardados sob<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Digite a palavra <b>DELETE</b> abaixo (exatamente como exibida) parar confirmar. DELETE DELETE Sorry Desculpe You need to enter DELETE in capital letters. Você precisa digitar DELETE em letras maiúsculas. There was an error deleting the profile directory, you need to manually remove it. Houve um erro deletando a pasta do perfil, você precisa removê-lo manualmente. Profile '%1' was succesfully deleted Perfil '%1' foi deletado com sucesso Bytes KB KB MB MB GB GB TB RC PB RP Summaries: Resumos: Events: Eventos: Backups: Backups: Hide disk usage information Esconder informação de uso de disco Show disk usage information Mostrar informação de uso de disco Name: %1, %2 Nome: %1, %2 Phone: %1 Telefone: %1 Email: <a href='mailto:%1'>%1</a> Email: <a href='mailto:%1'>%1</a> Address: Endereço: No profile information given Nenhuma informação de perfil fornecida Profile: %1 Perfil: %1 ProgressDialog Abort Abortar QObject No Data Nenhum Dado On Ligado Off Desligado ft ft lb lb oz oz Kg Kg cmH2O cmH2O Med. Med. Min: %1 Min: %1 Min: Min: Max: Max: Max: %1 Max: %1 %1 (%2 days): %1 (%2 dias): %1 (%2 day): %1 (%2 dia): % in %1 % em %1 Hours Horas Min %1 Mín %1 Hours: %1 Horas: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 baixo uso, %2 nenhum uso, de %3 dias (%4% obervância). Duração: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Sessões: %1 / %2 / %3 Duração: %4 / %5 / %6 Mais Longa: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Duração: %3 Início: %2 Mask On Mascara Colocada Mask Off Mascara Removida %1 Length: %3 Start: %2 %1 Duração: %3 Início: %2 TTIA: Tempo Total Em Apneia? TTIA: TTIA: %1 TTIA: %1 bpm bpm ? ? Severity (0-1) Severidade (0-1) Error Erro Warning Aviso Please Note Por Favor Note Graphs Switched Off Gráficos Desativados Sessions Switched Off Sessões Desativadas &Yes &Sim &No &Não &Cancel &Cancelar &Destroy &Destruir &Save &Salvar BMI IMC Weight Peso Zombie Zumbi Pulse Rate Taxa de Pulso Plethy Pletis Pressure Pressão Daily Diário Profile Perfil Overview Visão-Geral Oximetry Oximetria Oximeter Oxímetro Event Flags Marcações de Evento Default Padrão CPAP CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP Min EPAP EPAP Mín Max EPAP EPAP Máx IPAP IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Umidifcador H H OA OA A A CA CA FL FL LE LE EP EP VS VS VS2 VS2 RERA RERA PP PP P P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF1 UF2 UF2 UF3 UF3 PS PS AHI IAH RDI IDR AI IA HI IH UAI Índice de Apneia Indeterminada IAI CAI IAC FLI Índice de Limitação de Fluxo FLI REI Índice de Eventos Respiratórios REI EPI EPI Min IPAP IPAP Mín App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Software Engine Motor de Software ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL OpenGL Desktop m m cm cm in Minutes Minutos Seconds Segundos h h m m s s ms ms Events/hr Eventos/hr Hz Hz Litres Litros ml ml Breaths/min Respirações/min Degrees Graus Information Informação Busy Ocupado Only Settings and Compliance Data Available Summary Data Only Max IPAP IPAP Máx SA Apneia do Sono AS PB Respiração Periódica RP IE IE Insp. Time Tempo de Insp. Exp. Time Tempo de Exp. Resp. Event Evento Resp. Flow Limitation Limitação de Fluxo Flow Limit Limite de Fluxo SensAwake SensAwake Pat. Trig. Breath Resp. por Paciente Tgt. Min. Vent Vent. Mín. Alvo Target Vent. Vent Alvo Minute Vent. Vent. Minuto Tidal Volume Volume Tidal Resp. Rate Taxa de Resp. Snore Ronco Leak Vazamento Leaks Vazamentos Total Leaks Vazamentos Toais Unintentional Leaks Vazamentos Não-Intencionais MaskPressure PressãoMáscara Flow Rate Taxa de Fluxo Sleep Stage Estágio do Sono Usage Uso Sessions Sessões Pr. Relief Alívio de Pr. No Data Available Nenhum Dado Disponível Bookmarks Favoritos Mode Modo Model Modelo Brand Marca Serial Serial Series Série Machine Máquina Channel Canal Settings Configurações Motion Name Nome DOB Data Nasc. Phone Telefone Address Endereço Email Email Patient ID RG Paciente Date Data Bedtime Hora de Dormir Wake-up Acordar Mask Time Tempo de Másc Unknown Desconhecido None Nenhum Ready Pronto First Primeiro Last Último Start Início End Fim Yes Sim No Não Min Mín Max Máx Med Méd Average Média Median Mediana Avg Méd W-Avg Méd-Aco Getting Ready... Aprontando-se... Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine Máquina Sem Capacidade de Dados Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Machine Unsupported Máquina Não Suportada I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Scanning Files... Vasculhando Arquivos... Importing Sessions... Importando Sessões... Finishing up... Finalizando... Untested Data Machine Untested CPAP-Check AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS Flex Flex Lock Whether Flex settings are available to you. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Passover Target Time PRS1 Humidifier Target Time Hum. Tgt Time Mask Resistance Setting Mask Resist. Hose Diam. 15mm 15mm Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Type of ramp curve to use. Linear SmartRamp Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Fixed Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Breath BPM Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled Variable Breathing UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend Peak Flow Peak flow during a 2-minute interval 22mm 22mm Backing Up Files... model %1 DreamStation 2 unknown model Flex Mode Flex Mode PRS1 pressure relief mode. Modo PRS1 de alívio de pressão. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex Rise Time Tempo de Rampa Bi-Flex Bi-Flex Flex Level Flex Level PRS1 pressure relief setting. Configuração PRS1 alívio de pressão. Humidifier Status Estado de Umidificador PRS1 humidifier connected? Umidificador PRS1 conectado? Disconnected Disconectado Connected Conectado Humidification Mode PRS1 Humidification Mode Humid. Mode Fixed (Classic) Adaptive (System One) Heated Tube Tube Temperature PRS1 Heated Tube Temperature Tube Temp. PRS1 Humidifier Setting Hose Diameter Diâmetro da Traquéia Diameter of primary CPAP hose Diâmetro da traquéia do CPAP 12mm 12mm Auto On Auto Ligar A few breaths automatically starts machine Algumas respirações automaticamente ligam a máquina Auto Off Auto Desligar Machine automatically switches off Máquina desliga automaticamente Mask Alert Alerta de Máscara Whether or not machine allows Mask checking. Se a máquina permite verificação da máscara Show AHI Mostrar IAH Breathing Not Detected Respiração Não Detectada A period during a session where the machine could not detect flow. Um período durante a sesão onde a máquina não pôde detectar fluxo. BND Respiração Não Detectada RND Timed Breath Respiração Cronometrada Machine Initiated Breath Respiração Iniciada pela Máquina TB Respiração Cronometrada RC Windows User Utilizador Windows Using , found SleepyHead - You must run the OSCAR Migration Tool <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Seus dados de máquina antigos devem ser regenerados, desde que esse recurso de backup não tenha sido desativado nas preferências durante uma importação de dados anterior.</i> Launching Windows Explorer failed Execução do Windows Explorer falhou Could not find explorer.exe in path to launch Windows Explorer. Não foi possível encontrar o explorer.exe no caminho para iniciar o Windows Explorer. <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> OSCAR does not yet have any automatic card backups stored for this device. This means you will need to import this machine data again afterwards from your own backups or data card. Isso significa que você precisará importar os dados da máquina novamente a partir de seus próprios backups ou cartão de dados. Important: Importante: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Are you ready to upgrade, so you can run the new version of OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Machine Database Changes Alterações no Banco de Dados da Máquina OSCAR %1 needs to upgrade its database for %2 %3 %4 Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. The machine data folder needs to be removed manually. A pasta de dados da máquina precisa ser removida manualmente. This folder currently resides at the following location: Essa pasta reside atualmente na localização seguinte: Rebuilding from %1 Backup Recompilando %1 do backup or CANCEL to skip migration. You cannot use this folder: Migrating files from to OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. We suggest you use this folder: Click Ok to accept this, or No if you want to use a different folder. Next time you run OSCAR, you will be asked again. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Choose or create a new folder for OSCAR data Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. As you did not select a data folder, OSCAR will exit. The folder you chose is not empty, nor does it already contain valid OSCAR data. Data directory: It is likely that doing this will cause data corruption, are you sure you want to do this? É provável que isso cause corrupção de dados. Tem certeza de que deseja fazer isso? Question Pergunta Exiting Encerrando Are you sure you want to use this folder? Tem certeza de que deseja usar esta pasta? Don't forget to place your datacard back in your CPAP machine Não se esqueça de colocar o seu cartão de volta na sua máquina de CPAP OSCAR Reminder You can only work with one instance of an individual OSCAR profile at a time. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Loading profile "%1"... Carregando perfil "%1"... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Unable to create zip! Are you sure you want to reset all your channel colors and settings to defaults? Tem certeza de que deseja redefinir todas as suas configurações de cores e canais para os padrões? Are you sure you want to reset all your waveform channel colors and settings to defaults? Tem certeza de que deseja redefinir todas as cores e configurações do seu canal de forma de onda para os padrões? There are no graphs visible to print Não há gráficos visíveis para imprimir Would you like to show bookmarked areas in this report? Gostaria de mostrar áreas favoritadas neste relatório? Printing %1 Report Imprimindo Relatório %1 %1 Report Relatório %1 : %1 hours, %2 minutes, %3 seconds : %1 horas, %2 minutos, %3 segundos RDI %1 IDR %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 RP/RCS=%4%% UAI=%1 IAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 Relatando de %1 a %2 Entire Day's Flow Waveform Forma de Onda de Fluxo do Dia Todo Current Selection Seleção Atual Entire Day Dia Inteiro Page %1 of %2 Página %1 de %2 Jan Jan Feb Fev Mar Mar Apr Abr May ai Jun Jun Jul Jul Aug Ago Sep Set Oct Out Nov Nov Dec Dez Events Eventos Duration Duração (% %1 in events) (% %1 em eventos) Therapy Pressure Pressão da Terapia Inspiratory Pressure Pressão Inspiratória Lower Inspiratory Pressure Pressão Inferior Respiratória Higher Inspiratory Pressure Pressão Superior Respiratória Expiratory Pressure Pressão Expiratória Lower Expiratory Pressure Pressão Expiratória Inferior Higher Expiratory Pressure Pressão Expiratória Superior Pressure Support Suporte de Pressão PS Min PS Mín Pressure Support Minimum Mínima Pressão de Suporte PS Max PS Máx Pressure Support Maximum Máxima Pressão de Suporte Min Pressure Pressão Mín Minimum Therapy Pressure Mínima Pressão de Terapia Max Pressure Pressão Máx Maximum Therapy Pressure Máxima Pressão de Terapia Ramp Time Tempo de Rampa Ramp Delay Period Período de Atraso de Rampa Ramp Pressure Pressão de Rampa Starting Ramp Pressure Pressão de Rampa Inicial Ramp Event Evento de Rampa Ramp Rampa Vibratory Snore (VS2) Ronco Vibratório (VS2) A ResMed data item: Trigger Cycle Event Mask On Time Tempo com Máscara Time started according to str.edf Tempo iniciado de acordo com str.edf Summary Only Apenas Resumo An apnea where the airway is open Uma apneia em que a via aérea está aberta An apnea caused by airway obstruction Uma apneia causada por uma bostrução de via aérea Hypopnea Hipoapneia A partially obstructed airway Uma via aérea parcialmente obstruída Unclassified Apnea Apneia Indeterminada UA AI Vibratory Snore Ronco Vibratório A vibratory snore Um ronco vibratório A vibratory snore as detcted by a System One machine Um ronco vibratório como detectado por uma máquina System One Pressure Pulse Pulso de Pressão A pulse of pressure 'pinged' to detect a closed airway. Um pulseo de pressão 'pingado' para detectar uma via aérea fechada. Large Leak Grande Vazamento A large mask leak affecting machine performance. Um grande vazamento de máscara afetando o desempenho da máquina. LL GV Non Responding Event Um Evento Não Respondendo A type of respiratory event that won't respond to a pressure increase. Um tipo de evento que não irá responder a um aumento na pressão. Expiratory Puff Sopro Expiratório Intellipap event where you breathe out your mouth. Evento Intellipap no qual você expira pela boca. SensAwake feature will reduce pressure when waking is detected. Recursos SensAwake reduzirá a pressão quando caminhar é detectado. User Flag #1 Marca de Utilizador #1 User Flag #2 Marca de Utilizador #2 User Flag #3 Marca de Utilizador #3 Heart rate in beats per minute Taxa cardíaca em batimentos por minuto Blood-oxygen saturation percentage Porcentagem de saturação de oxigênio no sangue Plethysomogram Pletismograma An optical Photo-plethysomogram showing heart rhythm Um pletismograma foto-óptico mostrando o ritmo cardíaco Pulse Change Mudança no pulso A sudden (user definable) change in heart rate Uma mudança brusca (definível pelo utilizador) na taxa cardíaca SpO2 Drop Queda de SpO2 A sudden (user definable) drop in blood oxygen saturation Uma quebra brusca (definível pelo utilizador) na saturação do sangue SD QB Breathing flow rate waveform Forma de onda da taxa de fluxo respiratório L/min L/min Mask Pressure Pressão de Máscara Amount of air displaced per breath Quantidade de ar deslocado por respiração Graph displaying snore volume Gráfico mostrando o volume de ronco Minute Ventilation Ventilação por Minuto Amount of air displaced per minute Quantidade de ar deslocado por minuto Respiratory Rate Taxa Respiratória Rate of breaths per minute Taxa de respirações por minuto Patient Triggered Breaths Respirações Iniciadas pelo Paciente Percentage of breaths triggered by patient Porcentagem de respirações iniciadas pelo paciente Pat. Trig. Breaths Resp. Inic. Paciente Leak Rate Taxa de Vazamento Rate of detected mask leakage Taxa do vazamento detectado na máscara I:E Ratio Taxa I:E Ratio between Inspiratory and Expiratory time Taxa entre tempo inspiratório e expiratório ratio taxa Pressure Min Mín Pressão Pressure Max Máx Pressão Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting Cheyne Stokes Respiration Respiração Cheyne Stokes An abnormal period of Cheyne Stokes Respiration Um período anormal de respiração Cheyne Stokes CSR RCS Periodic Breathing Respiração Periódica An abnormal period of Periodic Breathing Um período anormal de respiração Clear Airway Via Aérea Livre Obstructive Obstrutiva An apnea that couldn't be determined as Central or Obstructive. A restriction in breathing from normal, causing a flattening of the flow waveform. Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Excitação Relacionada ao Esforço Respiratório: Uma restrição na respiração que causa um despertar ou distúrbio do sono. Leak Flag Marcação Vazamento LF MV A user definable event detected by OSCAR's flow waveform processor. Perfusion Index Índice de Perfusão A relative assessment of the pulse strength at the monitoring site Uma avaliação relativa da força de pulso no lugar de monitoramente Perf. Index % Índice Perf. % Mask Pressure (High frequency) Expiratory Time Tempo Expiratório Time taken to breathe out Tempo usado para expirar Inspiratory Time Tempo Inspiratório Time taken to breathe in Tempo usado para inspirar Respiratory Event Evento Respiratório Graph showing severity of flow limitations Gráfico mostrando a severidade de limitações de fluxo Flow Limit. Limite de Fluxo. Target Minute Ventilation Alvo Ventilações Minuto Maximum Leak Vazamento Máximo The maximum rate of mask leakage A taxa máxima de vazamento da máscara Max Leaks Vazamentos Máx Apnea Hypopnea Index Índice de Apneia Hipo-apneia Graph showing running AHI for the past hour Gráfico mostrando IAH na hora precedente Total Leak Rate Taxa Total de Vazamento Detected mask leakage including natural Mask leakages Vazamento de máscara detectado incluindo vazamentos naturais de máscara Median Leak Rate Taxa Mediana de Vazamento Median rate of detected mask leakage Taxa mediana de vazamento detectado na máscara Median Leaks Vazamentos Medianos Respiratory Disturbance Index Índice de Distúrbio Respiratório Graph showing running RDI for the past hour Gráfico Mostrando o IDR na hora precedente Movement Movement detector CPAP Session contains summary data only Sessão CPAP contém apenas dados resumidos PAP Mode Modo PAP Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Apnea An apnea reportred by your CPAP machine. PAP Device Mode Modo Aparelho PAP APAP (Variable) APAP (Variável) ASV (Fixed EPAP) ASV (EPAP Fixo) ASV (Variable EPAP) ASV (EPAP Variável) Height Altura Physical Height Altura Física Notes Notas Bookmark Notes Notas de Favorito Body Mass Index Índice de Massa Corporal How you feel (0 = like crap, 10 = unstoppable) Como se sente (0 = como lixo, 10 = imparável) Bookmark Start Começo do Favorito Bookmark End Fim do Favorito Last Updated Última Atualização Journal Notes Notas de Diário Journal Diário 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=Acordado 2=REM 3=Sono Leve 4=Sono Profundo Brain Wave Onda Cerebral BrainWave OndaCerebral Awakenings Despertares Number of Awakenings Número de Despertares Morning Feel Sensação Matutina How you felt in the morning Como se sentiu na manhã Time Awake Tempo Acordado Time spent awake Tempo gasto acordado Time In REM Sleep Tempo No Sono REM Time spent in REM Sleep Tempo gasto no sono REM Time in REM Sleep Tempo no Sono REM Time In Light Sleep Tempo Em Sono Leve Time spent in light sleep Tempo gasto em sono leve Time in Light Sleep Tempo em Sono Leve Time In Deep Sleep Tempo Em Sono Profundo Time spent in deep sleep Tempo gasto em sono profundo Time in Deep Sleep Tempo em Sono Profundo Time to Sleep Tempo para Dormir Time taken to get to sleep Tempo exigido para conseguir adormecer Zeo ZQ Zeo ZQ Zeo sleep quality measurement Medição Zeo de qualidade do sono ZEO ZQ ZEO ZQ Debugging channel #1 Test #1 For internal use only Debugging channel #2 Test #2 Zero Zero Upper Threshold Limite Superior Lower Threshold Limite Inferior Orientation Orientação Sleep position in degrees Posição de sono em graus Inclination Inclinação Upright angle in degrees Ângulo na vertical em graus Days: %1 Dias: %1 Low Usage Days: %1 Dias de Pouco Uso: %1 (%1% compliant, defined as > %2 hours) (%1% obervância, definida como > %2 horas) (Sess: %1) (Sess: %1) Bedtime: %1 Hora de cama: %1 Waketime: %1 Hora acordado: %1 (Summary Only) (Apenas Resumod) There is a lockfile already present for this profile '%1', claimed on '%2'. Existe um arquivo de bloqueio já presente para este perfil '%1', reivindicado em '%2'. Fixed Bi-Level Bi-Level Fixo Auto Bi-Level (Fixed PS) Auto Bi-Level (PS Fixa) Auto Bi-Level (Variable PS) Auto Bi-Level (PS Variável) 99.5% 90% {99.5%?} varies n/a Fixed %1 (%2) %1 (%2) Fixa Min %1 Max %2 (%3) Mín %1 Máx %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %1 sobre %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP Mín %1 IPAP Máx %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%6) {1 ?} {2-%3 ?} {4)?} EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2 (%3) {1 ?} {2-%3 ?} {4)?} EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1 IPAP %2 (%3) {1-%2 ?} {3-%4 ?} {5)?} Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Dados de Oximetria mais recentes: <a onclick='alert("daily=%2");'>%1</a> (last night) (noite passada) (1 day ago) (%2 days ago) No oximetry data has been imported yet. Nenum dado de oximetria foi importado ainda. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings Configurações SmartFlex ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Software Somnopose Zeo Zeo Personal Sleep Coach Personal Sleep Coach Database Outdated Please Rebuild CPAP Data Banco de Dados Desatualizado Por favor, Reconstrua os dados CPAP (%2 min, %3 sec) (%2 min, %3 seg) (%3 sec) (%3 seg) Pop out Graph Deslocar Gráfico The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View There is no data to graph d MMM yyyy [ %1 - %2 ] Hide All Events Esconder Todos Eveitos Show All Events Mostrar Todos Eventos Unpin %1 Graph Fixar Gráfico %1 Popout %1 Graph Deslocar Gráfico %1 Pin %1 Graph Fixar Gráfico %1 Plots Disabled Desenhos Desativados Duration %1:%2:%3 Duração %1:%2:%3 AHI %1 IAH %1 Relief: %1 Alívio: %1 Hours: %1h, %2m, %3s Horas: %1h, %2m, %3s Machine Information Informação de Máquina Journal Data Dados de Diário OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR will not touch this folder, and will create a new one instead. Please be careful when playing in OSCAR's profile folders :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. OSCAR picked only the first one of these, and will use it in future: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Se seus dados antigos estiverem faltando, copie o conteúdo de todas as outras pastas nomeadas Journal_XXXXXXX para esta manualmente. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Backing up files... Reading data files... SmartFlex Mode Modo SmartFlex Intellipap pressure relief mode. Modo Intellipap de alívio de pressão. Ramp Only Apenas Rampa Full Time Tempo Total SmartFlex Level Nível SmartFlex Intellipap pressure relief level. Nível de alívio de pressão Intellipap. Snoring event. SN Locating STR.edf File(s)... Localizando arquivo(s) STR.edf... Cataloguing EDF Files... Catalogando arquivos EDF... Queueing Import Tasks... Ordenando Tarefas Importantes... Finishing Up... Terminando... CPAP Mode Modo CPAP VPAPauto VPAPauto ASVAuto ASVAuto iVAPS PAC Auto for Her Auto para Ela EPR EPR ResMed Exhale Pressure Relief Alívio de Pressão ResMed Exhale Patient??? Paciente??? EPR Level Nível EPR Exhale Pressure Relief Level Alívio de Pressão de Expiração Response Patient View SmartStart SmartStart Machine auto starts by breathing Máquina liga automaticamente com a respiração Smart Start Smart Start Humid. Status Estado do Umidif. Humidifier Enabled Status Estado de Umidificador Ativo Humid. Level Nível do Umidif. Humidity Level Nível de Umidade Temperature Temperatura ClimateLine Temperature Temperatura ClimateLine Temp. Enable Temper. Ativa ClimateLine Temperature Enable Ativar Temperatura ClimateLine Temperature Enable Ativar Temperatura AB Filter Filtro AB Antibacterial Filter Filtro Antibacteriano Pt. Access Acesso Pac. Essentials Plus Climate Control Controle Climático Manual Manual Soft Standard Padrão BiPAP-T BiPAP-S BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Advanced Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... Auto Automático Mask Máscara ResMed Mask Setting Configuração de Máscara ResMed Pillows Almofadas Full Face Facial Total Nasal Nasal Ramp Enable Ativar Rampa Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Snapshot %1 Captura %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... Carregando dados %1 para %2... Scanning Files Vasculhando arquivos Migrating Summary File Location Migrando Localização de Arquivo de Resumo Loading Summaries.xml.gz Carregando Summaries.xml.gz Loading Summary Data Carregando Dados de Resumos Please Wait... Por favor Aguarde... Updating Statistics cache Usage Statistics Estatísticas de Uso %1 Charts %1 of %2 Charts Loading summaries Dreem Your Viatom device generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Viatom Viatom Software New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to check for updates. Please try again later. SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank about:blank SessionBar %1h %2m %1: %2m {1h?} No Sessions Present Nenhuma Sessão Presente SleepStyleLoader Import Error Erro de Importação This Machine Record cannot be imported in this profile. O relatório dessa máquina não pode ser importado nesse perfil. The Day records overlap with already existing content. Os registros diários sobrepõem-se com conteúdo pré-existente. Statistics Details Detalhes Most Recent Mais Recente Therapy Efficacy Eficácia da Terapia Last 30 Days Últimos 30 Dias Last Year Último Ano Average %1 Média %1 CPAP Statistics Estatísticas CPAP CPAP Usage Uso CPAP Average Hours per Night Horas Médias por Noite Compliance (%1 hrs/day) Leak Statistics Estatísticas de Vazamento Pressure Statistics Estatísticas de Pressão Oximeter Statistics Estatísticas de Oxímetro Blood Oxygen Saturation Saturação de Oxigênio Sérico Pulse Rate Taxa de Pulso %1 Median Mediana %1 Min %1 Mín %1 Max %1 Máx %1 %1 Index Índice %1 % of time in %1 % de tempo em %1 % of time above %1 threshold % acima do limite %1 % of time below %1 threshold % do tempo abaixo do limite %1 Name: %1, %2 Nome: %1, %2 DOB: %1 Data Nasc.: %1 Phone: %1 Telefone: %1 Email: %1 Email: %1 Address: Endereço: This report was prepared on %1 by OSCAR %2 Oscar has no data to report :( Days Used: %1 Dias de Uso: %1 Low Use Days: %1 Dias de Pouco Uso: %1 Compliance: %1% Observância: %1% Days AHI of 5 or greater: %1 Dias com IAH 5 ou mais: %1 Best AHI Melhor AHI Date: %1 AHI: %2 Data: %1 IAH: %2 Worst AHI Pior IAH Best Flow Limitation Melhor Limitação de Fluxo Date: %1 FL: %2 Data: %1 LF: %2 Worst Flow Limtation Pior Limitação de Fluxo No Flow Limitation on record Nenhuma Limitação de Fluxo na gravação Worst Large Leaks Pior Grande Vazamento Date: %1 Leak: %2% Data: %1 Vazamento: %2% No Large Leaks on record Nenhum Grande Vazamento na gravação Worst CSR Pior RCS Date: %1 CSR: %2% Data: %1 RCS: %2% No CSR on record Nenhuma RCS na gravação Worst PB Pior PR Date: %1 PB: %2% Data: %1 PR: %2% No PB on record Nenhum PR na gravação Want more information? Quer mais informações? OSCAR needs all summary data loaded to calculate best/worst data for individual days. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Ative a caixa de seleção Pré-Carregar Dados Resumidos nas preferências para garantir que esses dados estejam disponíveis. Best RX Setting Melhor Configuração RX Date: %1 - %2 Data: %1 - %2 AHI: %1 Total Hours: %1 Worst RX Setting Pior Configuração RX Last Week Última Semana Changes to Machine Settings No data found?!? Last 6 Months Últimos 6 Meses Last Session Última Sessão No %1 data available. Nenhum dado %1 disponível. %1 day of %2 Data on %3 %1 dia de %2 dados em %3 %1 days of %2 Data, between %3 and %4 %1 dia de %2 dados em %3 e %4 OSCAR is free open-source CPAP report software Days Dias Pressure Relief Alívio de Pressão Pressure Settings Configurações de Pressão Machine Information Informação de Máquina First Use Primeiro Uso Last Use Último Uso Welcome Welcome to the Open Source CPAP Analysis Reporter What would you like to do? O que você gostaria de fazer? CPAP Importer Importador CPAP Oximetry Wizard Assistente de Oximetria Daily View Visão Diária Overview Visão-Geral Statistics Estatísticas <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> It would be a good idea to check File->Preferences first, as there are some options that affect import. Note that some preferences are forced when a ResMed machine is detected First import can take a few minutes. A primeira importação pode levar alguns minutos. The last time you used your %1... A vez mais recente que você usou seu %1... last night noite passada %2 days ago %2 dias atrás was %1 (on %2) foi %1 (em %2) %1 hours, %2 minutes and %3 seconds %1 horas, %2 minutos e %3 segundos Your machine was on for %1. Sua máquina estava ligada para %1. <font color = red>You only had the mask on for %1.</font> <font color = red>Você só manteve a máscara em uso por %1.</font> under abaixo over acima reasonably close to razoavelmente próximo de equal to igual a You had an AHI of %1, which is %2 your %3 day average of %4. Você teve um IAH de %1, que é %2 sua média de %3 dias de %4. Your CPAP machine used a constant %1 %2 of air Your pressure was under %1 %2 for %3% of the time. Sua pressão ficou abaixo de %1 %2, %3% do tempo. Your machine used a constant %1-%2 %3 of air. Your EPAP pressure fixed at %1 %2. Sua pressão EPAP fixou em %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Sua pressão IPAP ficou abaixo de %1 %2, %3% do tempo. Your EPAP pressure was under %1 %2 for %3% of the time. Sua pressão EPAP ficou abaixo de %1 %2, %3% do tempo. Your machine was under %1-%2 %3 for %4% of the time. Sua máquina ficou abaixo de %1-%2 %3, %4% do tempo. 1 day ago Your average leaks were %1 %2, which is %3 your %4 day average of %5. Seus vazamentos médios foram %1 %2, que é %3 sua média de %4 dias de %5. No CPAP data has been imported yet. Nenhum dado CPAP foi importado ainda. gGraph %1 days gGraphView 100% zoom level 100% de aproximação Restore X-axis zoom to 100% to view entire selected period. Restore X-axis zoom to 100% to view entire day's data. Reset Graph Layout Redefinir Disposição de Gráfico Resets all graphs to a uniform height and default order. Redefine todos os gráficos para altura uniforme e ordenação padrão. Y-Axis EixoY Plots Desenhos CPAP Overlays Sobreposições CPAP Oximeter Overlays Sobreposições Oxímetro Dotted Lines Linhas Pontilhadas Double click title to pin / unpin Click and drag to reorder graphs Remove Clone Remover Clone Clone %1 Graph Clonar Gráfico %1 OSCAR-code-v1.3.1/Translations/Portugues.pt_BR.ts000066400000000000000000015175051417327530600215420ustar00rootroot00000000000000 AboutDialog &About &Sobre Release Notes Notas da Versão Credits Créditos GPL License Licença GPL Close Fechar Show data folder Exibir pasta de dados About OSCAR %1 Sobre OSCAR %1 Sorry, could not locate About file. Lamento, impossível localizar arquivo Sobre. Sorry, could not locate Credits file. Lamento, impossível localizar arquivo de Créditos. Sorry, could not locate Release Notes. Lamento, impossível localizar o arquivo das Notas de Versão. OSCAR %1 OSCAR %1 Important: Importante: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Como se trata de uma versão de pré-lançamento, é recomendável que você <b>faça um backup manual</b> da sua pasta de dados antes de proceder, porque a tentativa de retroceder mais tarde pode causar danos. To see if the license text is available in your language, see %1. Para ver se o texto da licença está disponível no seu idioma, consulte%1. CMS50F37Loader Could not find the oximeter file: Impossivel encontrar o arquivo do oximetro: Could not open the oximeter file: Impossível abrir o arquivo do oximetro: CMS50Loader Could not get data transmission from oximeter. Impossível obter dados de transmissão do oxímetro. Please ensure you select 'upload' from the oximeter devices menu. Por favor certifique-se de selecionar 'enviar' do menu de dispositivos do oxímetro. Could not find the oximeter file: Impossivel encontrar o arquivo do oxímetro: Could not open the oximeter file: Impossivel abrir o arquivo do oximetro: CheckUpdates Checking for newer OSCAR versions Buscando por novas versões do OSCAR Daily Go to the previous day Ir para o dia anterior Show or hide the calender Exibir ou ocultar o calendário Go to the next day Ir para o próximo dia Go to the most recent day with data records Ir para o dia mais recente com dados registrados Events Eventos View Size Ver tamanho Notes Notas Journal Diário Small Pequeno Medium Médio Big Grande I'm feeling ... Me sentindo ... If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Se a altura é maior que zero no Dialogo de Preferencias, configurar o peso aqui irá exibir o valor de Índice de Massa Corporea (IMC) Flags Marcadores Graphs Gráficos Show/hide available graphs. Mostrar/esconder gráficos disponíveis. Color Cor u u B B Zombie Zumbi Weight Peso Awesome Ótimo B.M.I. I.M.C. Bookmarks Favoritos Add Bookmark Adicionar favorito Starts Inícios Remove Bookmark Remover Favorito Breakdown Separação events eventos No %1 events are recorded this day Nenhum evento %1 registrado neste dia %1 event evento %1 %1 events eventos %1 UF1 UF1 UF2 UF2 Session Start Times Horários de Início de Sessão Session End Times Horários de Término de Sessão Duration Duração Position Sensor Sessions Sessões de Sensor de Posição Details Detalhes Time at Pressure Tempo sob Pressão Unknown Session Sessão Desconhecida Click to %1 this session. Clique para %1 esta sessão. disable desativar enable ativar %1 Session #%2 %1 Sessão #%2 %1h %2m %3s %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Por favor note:</b> Todas as configurações mostradas abaixo se baseiam em suposições de que nada mudou desde os dias anteriores. PAP Mode: %1 Modo PAP: %1 (Mode and Pressure settings missing; yesterday's shown.) (faltando configurações de Modo e Pressão; exibindo os de ontem) 99.5% 99.5% Time over leak redline Tempo acima da linha vermelha de vazamento Event Breakdown Separação de Eventos Unable to display Pie Chart on this system Impossível exibir o Gráfico de Pizza nesse sistema 10 of 10 Event Types 10 de 10 Tipos de Eventos This CPAP machine does NOT record detailed data Este equipamento CPAP NAO grava dados detalhados Sessions all off! Sessões todas desativadas! Sessions exist for this day but are switched off. Sessões existem para esse dia mas estão desativadas. Impossibly short session Sessão impossivelmente curta Zero hours?? Zero horas?? BRICK :( PROBLEMA :( Sorry, this machine only provides compliance data. Desculpe, esse aparelho fornece apenas dados de assiduidade. Complain to your Equipment Provider! Reclame para o seu fornecedor do aparelho! This bookmark is in a currently disabled area.. Este favorito está em uma área desativada atualmente.. 10 of 10 Graphs 10 de 10 Gráficos Statistics Estatísticas Oximeter Information Informação do Oxímetro SpO2 Desaturations Dessaturações de SpO2 Pulse Change events Eventos de Mudança de Pulso SpO2 Baseline Used Patamar SpO2 Usado Machine Settings Configurações de Aparelho Session Information Informações da Sessão CPAP Sessions Sessões CPAP Oximetry Sessions Sessões de Oxímetro Sleep Stage Sessions Sessões de Estátio de Sono Model %1 - %2 Modelo %1 - %2 This day just contains summary data, only limited information is available. Esse dia apenas contem dados resumidos, apenas informações limitadas estão disponíveis. Total time in apnea Tempo total em apnéia Total ramp time Tempo total de rampa Time outside of ramp Tempo fora da rampa Start Início End Fim "Nothing's here!" "Nada aqui!" No data is available for this day. Nenhum dado está disponível para este dia. Pick a Colour Escolha uma Cor Bookmark at %1 Favorito em %1 ExportCSV Export as CSV Exportar como CSV Dates: Datas: Resolution: Resolução: Details Detalhes Sessions Sessões Daily Diariamente Filename: Arquivo: Cancel Cancelar Export Exportar Start: Início: End: Fim: Quick Range: Intervalo Rápido: Most Recent Day Dira Mais Recente Last Week Última Semana Last Fortnight Última Quinzena Last Month Último Mês Last 6 Months Últimos 6 Meses Last Year Último Ano Everything Tudo Custom Personalizado Details_ Details_ Sessions_ Sessions_ Summary_ Summary_ Select file to export to Selecione arquivo para o qual exportar CSV Files (*.csv) Arquivos CSV (*.csv) DateTime DataHora Session Sessão Event Evento Data/Duration Data/Duração Date Data Session Count Contagem de Sessão Start Início End Fim Total Time Tempo Total AHI IAH Count Contagem FPIconLoader Import Error Erro de Importação This Machine Record cannot be imported in this profile. O registro deste aparelho não pode ser importado nesse perfil. The Day records overlap with already existing content. Os registros diários se sobrepõem com conteúdo pré-existente. Help Hide this message Ocultar essa mensagem Search Topic: Tópico de Busca: Help Files are not yet available for %1 and will display in %2. Arquivos de ajuda não estão disponíveis ainda para %1 e aparecerão em %2. Help files do not appear to be present. Arquivos de ajuda parecem não serem atuais. HelpEngine did not set up correctly HelpEngine não instalou corretamente HelpEngine could not register documentation correctly. HelpEngine não conseguiu registrar a documentação corretamente. Contents Conteúdos Index Índice Search Busca No documentation available Nenhuma documentação disponível Please wait a bit.. Indexing still in progress Por favor aguarda um pouco.. Indexação em progresso No Não %1 result(s) for "%2" %1 resultado(s) para "%2" clear limpar MD300W1Loader Could not find the oximeter file: Impossível encontrar o arquivo do oxímetro: Could not open the oximeter file: Impossível abrir o arquivo do oxímetro: MainWindow &Statistics E&statísticas Report Mode Modo Relatório Standard Padrão Monthly Mensal Date Range Faixa de Data Statistics Estatísticas Daily Diariamente Overview Visão Geral Oximetry Oximetria Import Importar Help Ajuda &File &Arquivo &View &Exibir &Reset Graphs &Restaurar Gráficos &Help A&juda Troubleshooting Solução de Problemas &Data &Dados &Advanced A&vançado Purge ALL Machine Data Apagar TODOS os dados do Aparelho Rebuild CPAP Data Recompilar Dados CPAP &Import CPAP Card Data &Importar Dados Cartão CPAP Show Daily view Mostrar visão Diária Show Overview view Mostrar visão Resumida &Maximize Toggle Alternar para &Maximizar Maximize window Maximizar janela Reset Graph &Heights Redefinir &Altura de Gráficos Reset sizes of graphs Redefinir tamanho dos gráficos Show Right Sidebar Mostrar Barra Lateral Direita Show Statistics view Mostrar visão Estatísticas Import &Dreem Data Importar Dados &Dreem Purge Current Selected Day Apagar Dia Atualmente Selecionado &CPAP &CPAP &Oximetry &Oximetria &Sleep Stage &Estágio Sono &Position &Posição &All except Notes &Todas Notas de excessão All including &Notes Todas &Notas de inclusão Show &Line Cursor Mostrar &Linha Cursor Show Daily Left Sidebar Mostrar Barra Esquerda do Diário Show Daily Calendar Mostrar Calendário Diário Create zip of CPAP data card Criar um zip dos dados do cartão do CPAP Create zip of OSCAR diagnostic logs Criar zip dos registros de diagnóstico do OSCAR Create zip of all OSCAR data Criar um zip de todos os dados do OSCAR Report an Issue Relatar um Problema System Information Informações do Sistema Show &Pie Chart Mostrar Gráfico &Pizza Show Pie Chart on Daily page Mostrar Gráfico Pizza na página do Diário Standard graph order, good for CPAP, APAP, Bi-Level Ordem padrão de gráfico, bom para CPAP, APAP, Bi-Level Advanced Avançado Advanced graph order, good for ASV, AVAPS Ordem de gráfico avançada, bom para ASV, AVAPS Show Personal Data Mostar Informações Pessoais Check For &Updates Buscar por&Atualizações &Preferences &Preferências &Profiles &Perfis &About OSCAR &Sobre OSCAR Show Performance Information Mostrar Informação de Desempenho CSV Export Wizard Assistente de Exportação CSV Export for Review Exportar para Revisão E&xit Sai&r Exit Sair View &Daily Mostrar &Diário View &Overview Ver Visã&o Geral View &Welcome Ver Boas-vi&ndas Use &AntiAliasing Usar &AntiDistroção Show Debug Pane Mostrar Painel Depurador Take &Screenshot Captura de &Tela O&ximetry Wizard Assistente de O&ximetria Print &Report Imprimir &Relatório &Edit Profile &Editar Perfil Import &Viatom/Wellue Data Importar Dados &Viatom/Wellue Daily Calendar Calendário Diário Backup &Journal Fazer Backu&p do Diário Online Users &Guide &Guia Online do Usuário &Frequently Asked Questions Perguntas &Frequentes &Automatic Oximetry Cleanup Limpeza &Automática de Oximetria Change &User Trocar &Usuário Purge &Current Selected Day Remover Dia S&elecionado Right &Sidebar Barra Lateral Di&reita Daily Sidebar Barra Lateral Diária View S&tatistics Ver E&statísticas Navigation Navegação Bookmarks Favoritos Records Registros Exp&ort Data Exp&ortar Dados Profiles Perfis Purge Oximetry Data Remover Dados Oximétricos View Statistics Ver Estatísticas Import &ZEO Data Importar Dados &ZEO Import RemStar &MSeries Data Importar Dados RemStar &MSeries Sleep Disorder Terms &Glossary G&lossário de Termos de Desordens do Sono Change &Language A&lterar Idioma Change &Data Folder Alterar Pasta de &Dados Import &Somnopose Data Importar Dados &Somnopose Current Days Dias Atuais Welcome Bem-Vindo &About So&bre Access to Import has been blocked while recalculations are in progress. Acesso para importar foi bloqueado enquanto cálculos estão em progresso. Importing Data Importando Dados Please wait, importing from backup folder(s)... Por favor aguarde, importando da(s) pasta(s) de backup... Import Problem Importar Problema Couldn't find any valid Machine Data at %1 Impossível encontrar qualquer Dado de Aparelho valido em %1 Please insert your CPAP data card... Por favor insira seu cartão de dados do CPAP... Import is already running in the background. Importação já em execução em segundo plano. CPAP Data Located Dados do CPAP Localizados Import Reminder Lembrete de Importação Please open a profile first. Por favor abra um perfil primeiro. Check for updates not implemented Bucar por atualizações não implementadas Please note, that this could result in loss of data if OSCAR's backups have been disabled. Por favor, note que pode resultar na perda de dados de gráficos se os backups do OSCAR foram desativados. You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Você está prestes a <font size=+2>desintegrar</font> o banco de dados do OSCAR para o seguinte aparelho:</p> There was a problem opening %1 Data File: %2 Houve um problema abrindo o arquino%1 : %2 %1 Data Import of %2 file(s) complete Importação de Dado(s) %1 de %2 completada %1 Import Partial Success %1 Importação Parcial com Sucesso %1 Data Import complete %1 Importação de Dados Completada %1's Journal Diário de %1 Choose where to save journal Escolha onde salvar o diário XML Files (*.xml) Arquivos XML (*.xml) Access to Preferences has been blocked until recalculation completes. Acesso às preferêcias foi bloqueado até que os cálculos terminem. Are you sure you want to delete oximetry data for %1 Você tem certeza de que deseja deletar dados oximétricos para %1 <b>Please be aware you can not undo this operation!</b> <b>Por favor esteja ciente de que você não pode desfazer a operação!</b> Select the day with valid oximetry data in daily view first. Selecione o dia com dados oxímetros válidos na visualização diária primeiro. Help Browser Navegador de Ajuda Loading profile "%1" Carregando perfil "%1" Choose a folder Escolha uma pasta A %1 file structure for a %2 was located at: Uma estrutura de arquivos %1 para %2 foi localizada em: A %1 file structure was located at: Uma estrutura de arquivos %1 foi localizada em: Would you like to import from this location? Você gostaria de importar dessa localização? Specify Especifique There was an error saving screenshot to file "%1" Erro ao salvar a captura de tela para o arquivo "%1" Screenshot saved to file "%1" Captura de tela salva para o arquivo "%1' Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Desde que você tenha feito <i>seus <b>próprios</b> backups para TODOS os seus dados de CPAP </i>, você ainda pode concluir esta operação, mas terá que restaurar manualmente a partir de seus backups. Are you really sure you want to do this? Tem certeza de que deseja fazer isso? Because there are no internal backups to rebuild from, you will have to restore from your own. Por não existirem backups internos a partir dos quais se poderia reconstruir, você terá que restaurar a partir dos seus próprios backups. Imported %1 CPAP session(s) from %2 Importadas %1 sessão(ões) CPAP de %2 %1 (Profile: %2) %1 (Perfil: %2) Import Success Sucesso na Importação Already up to date with CPAP data at %1 Já atualizado com dados do CPAP em %1 Up to date Atualizado No profile has been selected for Import. Nenhum perfil foi selec. para Importação. Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Por favor lembre-se de selecionar a pasta raiz ou a letra do driver do seu cartão de dados, e não uma pasta dentro dele. Find your CPAP data card Encontre seu cartão de dados CPAP Choose where to save screenshot Escolha onde salvar a captura de tela Image files (*.png) Arquivos de Imagem (*.png) The User's Guide will open in your default browser O Guia do Usuário será aberto no seu navegador padrão The FAQ is not yet implemented O FAQ ainda não foi implementado If you can read this, the restart command didn't work. You will have to do it yourself manually. Se você pode ler isso, o comando de reinicialização não funcionou. Você precisará fazer isso sozinho manualmente. Are you sure you want to rebuild all CPAP data for the following machine: Tem certeza que deseja reconstruir todos os dados CPAP para o seguinte aparelho: For some reason, OSCAR does not have any backups for the following machine: Por algum motivo, o OSCAR não parece possuir backups do seguinte aparelho: Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Gostaria de importar de seus próprios backups agora? (você não terá quaisquer dados visíveis para esse aparelho até fazê-lo) OSCAR does not have any backups for this machine! OSCAR não contém nenhum backup para este aparelho! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> A menos que você tenha feito <i>seu <b>próprio</b> backup para TODOS os seus dados deste aparelho</i>, <font size=+2>você perderá os dados deste aparelho <b>permanentemente</b>!</font> The Glossary will open in your default browser O Glossário será aberto no seu navegador padrão Export review is not yet implemented Revisão de exportações ainda não foi implementada Would you like to zip this card? Gostaria de zipar este cartão? Choose where to save zip Escolha aonde salvar o zip ZIP files (*.zip) Arquivos ZIP (*.zip) Creating zip... Criando zip... Calculating size... Calculando tamanho... Reporting issues is not yet implemented Relatório de problemas ainda não foi implementado Note as a precaution, the backup folder will be left in place. Note como precaução, a pasta de backup será mantida no mesmo lugar. Are you <b>absolutely sure</b> you want to proceed? Você tem <b>absoluta certeza</b> de que deseja prosseguir? A file permission error casued the purge process to fail; you will have to delete the following folder manually: Um erro de permissão fez com que o processo de limpeza falhasse; você precisará deletar a seguinte pasta manualmente: No help is available. Nenhuma ajuda está disponível. There was a problem opening MSeries block File: Houve um problema ao abrir o arquivo de bloco MSeries: MSeries Import complete Importação de MSeries completada OSCAR Information Informnações sobre o OSCAR MinMaxWidget Auto-Fit Auto-Ajuste Defaults Padrões Override Substituir The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. O modo de escala do eixo-Y, 'Auto-Escala' para dimensionamento automático, 'Padrões' para configurar de acordo com o fabricante, e 'Substituir' para escolher o seu próprio. The Minimum Y-Axis value.. Note this can be a negative number if you wish. O valor Mínimo de eixo-Y.. Note que esse pode ser um número negativo se você quiser. The Maximum Y-Axis value.. Must be greater than Minimum to work. O valor Máximo de eixo-Y.. Deve ser maior do que o Mínimo para funcionar. Scaling Mode Modo de Escala This button resets the Min and Max to match the Auto-Fit Esse botão redefine o Min e Máx para combinar com a Auto-Escala NewProfile Edit User Profile Editar Perfil de Usuário I agree to all the conditions above. Eu concordo com todas as condições acima. User Information Informação de Usuário User Name Nome do Usuário Password Protect Profile Proteger Perfil com Senha Password Senha ...twice... ...duas vezes... Locale Settings Configurações de Localidade Country País TimeZone Fuso Horário about:blank about:blank Very weak password protection and not recommended if security is required. Protecção muito fraca da senha e não recomendada se for necessária segurança. DST Zone Horário de Verão Personal Information (for reports) Informação Pessoal (para relatórios) First Name Primeiro Nome Last Name Sobrenome It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Não há problema em mentir ou pular isso, mas sua idade aproximada é necessária para melhorar a qualidade de certos cálculos. D.O.B. Data de Nasc. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Sexo biológico (nascença) é necessário algumas vezes para melhorar a precisão de alguns cálculos, sinta-se livre para deixar isso em branco e pular perguntas.</p></body></html> Gender Sexo Male Masculino Female Feminino Height Altura Metric Métrico English Inglês Contact Information Informações de Contato Address Endereço Email Email Phone Telefone CPAP Treatment Information Informação de Tratamento CPAP Date Diagnosed Data do Diagnóstico Untreated AHI IAH Não Tratado CPAP Mode Modo CPAP CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure Pressão RX Doctors / Clinic Information Informação do Médico / Clínica Doctors Name Nome do Médico Practice Name Nome da Clínica Patient ID RG do Paciente &Cancel &Cancel &Back &Voltar &Next &Próximo Select Country Selecione o País This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Esse software está sendo projetado para ajudá-lo a revisar os dados produzidos por aparelhos CPAP e equipamento relacionado. PLEASE READ CAREFULLY POR FAVOR LEIA COM ATENÇÃO Accuracy of any data displayed is not and can not be guaranteed. A precisão de qualquer dado mostrado não é não pode ser garantida. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Quaisquer relatórios gerados são para USO PESSOAL SOMENTE, e DE NENHUM MODO servem para propósitos de complacência ou diagnóstico médico. Use of this software is entirely at your own risk. O uso deste software é interamente por sua conta e risco. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team O OSCAR é marca registrada de &copy;2011-2018 Mark Watkins e partes &copy;2019-2022 The OSCAR Team Welcome to the Open Source CPAP Analysis Reporter Bem-vindo ao Open Source CPAP Analysis Reporter OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR é liberado livremente sob a licença <a href='qrc:/COPYING'>GNU Public License v3</a>, e não acompanha nenhuma garantia e NENHUMA afirmação de eficácia para qualquer propósito. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR destina-se apenas como um visualizador de dados e definitivamente não é um substituto para orientação médica competente do seu Médico. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Os autores não serão responsabilizados por <u>nada</u> relacionado ao uso ou mal uso desse software. Please provide a username for this profile Por favor forneça um nome de usuário para esse perfil Passwords don't match Senhas não correspondem Profile Changes Mudanças de Perfil Accept and save this information? Aceitar e salvar essa informação? &Finish &Finalizar &Close this window Fe&char essa janela Overview Range: Intervalo: Last Week Última Semana Last Two Weeks Últimas Duas Semanas Last Month Último Mês Last Two Months Últimos Dois Meses Last Three Months Últimos Três Meses Last 6 Months Últimos 6 Meses Last Year Último Ano Everything Tudo Custom Personalizado Snapshot Instantâneo Start: Início: End: Fim: Reset view to selected date range Redefinir visualização para a faixa de data selecionada Toggle Graph Visibility Alternar Visibilidade do Gráfico Drop down to see list of graphs to switch on/off. Desça para ver a lista de gráficos para des/ativar. Graphs Gráficos Respiratory Disturbance Index Índice Distúrbio Respiratório Apnea Hypopnea Index Índice Hipoapnéia Apnéia Usage Uso Usage (hours) Uso (horas) Session Times Tempos de Sessão Total Time in Apnea Tempo Total em Apnéia Total Time in Apnea (Minutes) Tempo Total em Apnéia (Minutos) Body Mass Index Índice Massa Corporea How you felt (0-10) Como se sentiu (0-10) 10 of 10 Charts 10 de 10 Gráficos Show all graphs Mostrar todos os gráficos Hide all graphs Ocultar todos os gráficos OximeterImport Oximeter Import Wizard Assistente de Importação do Oxímetro Skip this page next time. Pular essa página da próxima vez. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Por favor note: </span><span style=" font-style:italic;">Primeiro selecione seu tipo de oximetro correto do menu de seleção abaixo.</span></p></body></html> Where would you like to import from? De onde você gostaria de importar? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">PRIMEIRO Selecione seu Oximetro destes grupos:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. Usuários CMS50E/F, ao importar diretamente, por favor não selecionem upload no seu aparelho até o OSCAR pedir. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Se ativado, o OSCAR automaticamente reseta o relógio interno no CMS50 usando a hora atual do computador.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>Aqui você pode definir um nome de até 7 caractéres para esse oxímetro.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Essa opção apaga a sessão importada de seu oxímetro após importá-la.</p><p>Use com cautela, pois se algo der errado antes do OSCAR salvar a sessao, você não a conseguirá de volta.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Essa opção permite que você importe (via cabo) das gravações internas do seu oxímetro.</p><p>Após selecionar nessa opção, antigos oxímetros Contec exigirão que você utilize o menu do aparelho para iniciar o envio.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Se você não se importa de ficar conectado a um computador ligado a noite toda, essa opção fornece um gráfico pletismográfico útil, que fornece uma indicação de rítmo cardíaco, em adição às leituras oximétricas normais.</p></body></html> Record attached to computer overnight (provides plethysomogram) Registrar conectado ao computador durante a noite (fornece pletismograma) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Essa opção permite que você importe arquivos de dado criados pelo software que acompanha o seu Oxímetro de Pulso, como o SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Importar de um arquivo de dado salvo por outro programa, como SpO2Review Please connect your oximeter device Por favor conecte seu aparelho oxímetro If you can read this, you likely have your oximeter type set wrong in preferences. Se você pode ler isso, provavelmente você configurou o tipo de oxímetro errado nas preferências. Please connect your oximeter device, turn it on, and enter the menu Por favor conecte seu aparelho oxímetro, ligue o mesmo e entre no menu Press Start to commence recording Pressione Início para começar o registro Show Live Graphs Mostrar Gráficos em Tempo Real Duration Duração Pulse Rate Taxa de Pulso Multiple Sessions Detected Múltiplas Sessões Detectadas Start Time Tempo de Início Details Detalhes Import Completed. When did the recording start? Importação Completada. Quando a gravação começou? Oximeter Starting time Tempo de Início do Oxímetro I want to use the time reported by my oximeter's built in clock. Eu gostaria de usar o tempo relatado pelo relógio interno do meu oxímetro. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Eu comecei essa gravação de oxímetro ao mesmo (ou próximo) tempo que meu aparelho CPAP. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Nota: Sincronizar para o tempo de início do CPAP será mais preciso sempre.</p></body></html> Choose CPAP session to sync to: Escolha uma sessão CPAP com a qual sincronizar: You can manually adjust the time here if required: Você pode ajustar manualmente o tempo aqui se necessário: HH:mm:ssap HH:mm:ss &Cancel &Cancelar &Information Page Página de &Informação CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Definir data/hora do aparelho <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Marque para ativar a atualização do identificador do aparelho na próxima importação, o que é útil para aqueles que possuem múltiplos oxímeros largados por perto.</p></body></html> Set device identifier Definir identificador do aparelho Erase session after successful upload Apagar sessão após envio concluído Import directly from a recording on a device Importar diretamente de uma gravação num aparelho <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Lembrete para usuários de CPAP: </span><span style=" color:#fb0000;">Você lembrou de importar as sessões do CPAP primeiro?<br/></span>Se você esqueceu, você não terá um tempo válido para o qual sincronizar essa sessão de oximetria.<br/>Para garantir uma boa sincronia entre aparelhos, sempre tente iniciar ambos ao mesmo tempo.</p></body></html> Please choose which one you want to import into OSCAR Por favor escolha qual você deseja importar no OSCAR Day recording (normally would have) started A gravação do dia (normalmente teria) começou <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR precisa de uma hora inicial para saber onde salvar essa sessão de oxímetro.</p><p>Escolha uma das seguintes opções:</p></body></html> &Retry Tenta&r Novamente &Choose Session Es&colher Sessão &End Recording T&erminar Gravação &Sync and Save &Sincronizar e Salvar &Save and Finish &Salvar e Terminar &Start Ini&ciar Scanning for compatible oximeters Buscando por oxímetros compatíveis Could not detect any connected oximeter devices. Nenhum oxímetro conectado foi detectado. Connecting to %1 Oximeter Conectando ao Oxímetro %1 Renaming this oximeter from '%1' to '%2' Renomeando esse oxímetro de '%1' para '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. O nome do oxímetro é diferente.. Se você possui apenas um e está compartilhando-o entre perfis, defina o mesmo nome em todos os perfis. "%1", session %2 "%1", sessão %2 Nothing to import Nada para importar Your oximeter did not have any valid sessions. Seu oxímetro não tinha quaisquer sessões válidas. Close Fechar Waiting for %1 to start Aguardando %1 para começar Waiting for the device to start the upload process... Aguardando pelo dispositivo para iniciar o processo de envio... Select upload option on %1 Selecione a opção upload em %1 You need to tell your oximeter to begin sending data to the computer. Você precisa dizer ao oxímetro para começar a enviar dados para o computador. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Por favor conecte seu oxímetro, entre no menu e selecione upload para começar a transferência de dados... %1 device is uploading data... Dispositivo %1 está enviando dados... Please wait until oximeter upload process completes. Do not unplug your oximeter. Por favor aguarde até o processo de envio de dados do oxímetro terminar. Não desconecte seu oxímetro. Oximeter import completed.. Importação do oxímetro completada.. Select a valid oximetry data file Selecione um arquivo de dados válido de oxímetro Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Arquivos de Oximetria (*.so *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Nenhum módulo de oximetria pode interpretar o arquivo fornecido: Live Oximetry Mode Módo de Oximetria em Tempo Real Live Oximetry Stopped Oximetria em Tempo Real Parada Live Oximetry import has been stopped A importação de oximetria em tempo real foi parada Oximeter Session %1 Sessão de Oxímetro %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR lhe oferece a habilidade de acompanhar os dados de oximetria ao lado dos dados de sesão CPAP, o que fornece um vislumbre valioso na eficácia do tratamento CPAP. Isso também funcionará sozinho com seu Oxímetro de Pulso, permitindo que você salve, acompanhe e analise seus dados registrados. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR é compatível atualmente com os oxímetros série Contec CMS50D+, CMS50E, CMS50F e CMS50I.<br/>(Nota: Importação direta de modelos bluetooth <span style=" font-weight:600;">provavelmente</span> não é possível ainda) If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Se você está tentando sincronizar dados de oximetria e CPAP, por favor certifique de que você importou sua sessão de CPAP antes de prosseguir! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Para que o OSCAR seja capaz de localizar e ler diretamente do seu aparelho oxímetro, você precisa garantir que os drivers corretos (ex. UART USB para Serial) estão instalados no computador. Para mais informações sobre isso %1clique aqui%2. Oximeter not detected Oxímetro não detectado Couldn't access oximeter Impossível acessar oxímetro Starting up... Inicializando... If you can still read this after a few seconds, cancel and try again Se você ainda pode ler isso após alguns segundos, cancele e tente novamente Live Import Stopped Importação em Tempo Real Parada %1 session(s) on %2, starting at %3 %1 sessão(ões( em %2, começando em %3 No CPAP data available on %1 Nenhum dado de CPAP disponível em %1 Recording... Gravando... Finger not detected Dedo não detectado I want to use the time my computer recorded for this live oximetry session. Eu quero usar a hora que meu computador registrou para essa sessão de oximetria em tempo real. I need to set the time manually, because my oximeter doesn't have an internal clock. Eu preciso definir o tempo manualmente, porque meu oxímetro não possui relógio interno. Something went wrong getting session data Algo deu errado ao obter os dados da sessão Welcome to the Oximeter Import Wizard Bem-vindo ao Assistente de Importação de Oxímetro Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Oxímetros de pulso são dispositivos médicos usados para medir a saturação de oxigênio no sangue. Durante eventos extensos de apenai e padrões anormais de respiração, os níveis de saturação de oxigênio no sangue podem cair significativamente, e podem indicar problemas que precisam de atenção médica. You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Você pode desejar notar, outras companhias, como a Pulox, simplesmente rotulam o Contec CMS50 sob nomes novos, como o Pulox PO-200, PO-300, PO-400. Estes devem funcionar também. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Ele também pode ler dos arquivos .dat do ChoiceMMed MD300W1. Please remember: Por favor lembre-se: Important Notes: Notas importantes: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Aparelhos CMS50D+ não possuem relógio interno e não registram um tempo de inínio, se você não possui uma sessão de CPAP para sincronizar a gravação, você terá de especificar manualmente o tempo de início após completar o processo de importação. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Mesmo para aparelhos com um relógio interno, ainda é recomendado adquirir o hábito de iniciar as gravações de oxímetro ao mesmo tempo que as sessões de CPAP, porque os relógios internos dos CPAPs tendem a desviar com o tempo, e nem todos podem ser redefinidos facilmente. Oximetry Date Data d/MM/yy h:mm:ss AP d/MM/yy HH:mm:ss R&eset R&edefinir Pulse Pulso &Open .spo/R File Abrir arquivo .sp&o/R Serial &Import &Importação Serial &Start Live Ini&ciar em Tempo Real Serial Port Porta Serial &Rescan Ports &Rebuscar Portas PreferencesDialog Preferences Preferências &Import &Importar Combine Close Sessions Combinar Sessões Fechadas Minutes Minutos Multiple sessions closer together than this value will be kept on the same day. Múltiplas sessões mais próximas do que esse valor serão mantidas no mesmo dia. Ignore Short Sessions Ignorar Sessões Curtas <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessões mais curtas do que isso em duração não serão mostradas<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Tempo de Divisão do Dia Sessions starting before this time will go to the previous calendar day. Sessões iniciando antes dessa hora irão para o dia anterior no calendário. Session Storage Options Opções de Armazenamento de Sessões Create SD Card Backups during Import (Turn this off at your own peril!) Criar backups do cartão SD durante a importação (desative isso por sua conta e risco!) Compress SD Card Backups (slower first import, but makes backups smaller) Comprimir os Backups do Cartão SD (primeira importação mais lenta, mas os backups ficam mais leves) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Considere dias com uso abaixo disso como "não compatíveis". 4 horas são geralmente consideradas compatíveis. hours horas Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Ativar/desativar melhorias experimentais na marcação de eventos. Elas permitem detectar eventos limítrofes, e alguns que o aparelho deixa passar. Essa opção deve ser ativada antes da importação, do contrário uma limpeza é necessária. Flow Restriction Restrição de Fluxo Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Porcentagem da restrição no fluxo de ar do valor médio. Um valor de 20% funciona bem para detectar apnéias. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">As marcações personalizadas são um método experimental de detecção de eventos que o aparelho deixa passar. Eles <span style=" text-decoration: underline;">não</span> são incluídos no IAH.</p></body></html> Duration of airflow restriction Duração da restrição de fluxo de ar s s Event Duration Duração de Evento Allow duplicates near machine events. Permitir duplicados próximos de eventos do aparelho. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Ajuste a quantidade de dados considerada para cada ponto no gráfico IAH/Hora. Padrão em 60 minutos.. Altamente recomendado manter nesse valor. minutes minutos Reset the counter to zero at beginning of each (time) window. Redefinir o contador a zero no começo de cada janela (de tempo). Zero Reset Redefinir a Zero CPAP Clock Drift Deslocamento do Relógio do CPAP Do not import sessions older than: Não importar sessões mais antigas que: Sessions older than this date will not be imported Sessões mais antigas que essa data não serão importadas dd MMMM yyyy dd MMMM yyyy Show in Event Breakdown Piechart Mostrar no Gráfico de Pizza de Separação de Evento Resync Machine Detected Events (Experimental) Resincronizar Eventos Detectados pelo Aparelho (Experimental) User definable threshold considered large leak Limitar definível pelo usuário considerando vazamento grande Whether to show the leak redline in the leak graph Mostrar ou não a linha vermelha de vazamento no gráfico de vazamentos Search Buscar &Oximetry &Oximetria Percentage drop in oxygen saturation Queda percentual na saturação de oxigênio Pulse Pulso Sudden change in Pulse Rate of at least this amount Mudança brusca na Taxa de Pulso de pelo menos essa intensidade bpm bpm Minimum duration of drop in oxygen saturation Duração mínima da queda na saturação de oxigênio Minimum duration of pulse change event. Duração mínima do evento de mudança no pulso. Small chunks of oximetry data under this amount will be discarded. Pequenos pedaços dos dados de oximetria abaixo desse valor serão descartados. &General &Geral General Settings Configurações Gerais Daily view navigation buttons will skip over days without data records Os botões de navegação na visão diária.saltarão.os dias sem dados registrados Skip over Empty Days Saltar os Dias Vazios Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Permitir múltiplos núcleos de CPU quando disponíveis para melhorar desempenho. Afeta principalmente a importação. Enable Multithreading Ativar Multithreading Bypass the login screen and load the most recent User Profile Ignorar a tela de login e carregar o perfil do usuário mais recente <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Esses recursos foram retirados recentemente. Eles retornarão no futuro. </p></body></html> Changes to the following settings needs a restart, but not a recalc. Alterações nas configurações a seguir precisam de uma reinicialização, mas não de um recálculo. Preferred Calculation Methods Métodos de Cálculo Preferidos Middle Calculations Cálculos Intermediários Upper Percentile Percentuais Superiores For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Para consistência, usuários ResMed devem usar 95% aqui, já que esse é o único valor disponível nos dias de apenas resumo. Median is recommended for ResMed users. A mediana é recomendada para usuários ResMed. Median Mediana Weighted Average Média Ponderada Normal Average Média Normal True Maximum Máximo Verdadeiro 99% Percentile Percentuais Superiores Maximum Calcs Cálculos Máximos Session Splitting Settings Configurações de Separação de Sessão Don't Split Summary Days (Warning: read the tooltip!) Não Separar Dias Resumidos (Aviso: leia a dica de contexto!) Memory and Startup Options Opções de Memória e Inicialização Pre-Load all summary data at startup Pré-carregar todos os dados resumidos no início <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Essa configuração mantem os dados de formas de onda e eventos na memória após o uso para acelerar os dias de revisitação.</p><p>Essa não é uma opção realmente necessária, já que seu sistema operacional retém arquivos previamente utilizados.</p><p>A recomendação é deixá-la desativada, a menos que seu computador possua muita memória livre.</p></body></html> Keep Waveform/Event data in memory Manter dados de forma de onda/eventos na memória <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Reduzir quaisquer diálogos de confirmação sem importância durante a importação.</p></body></html> Import without asking for confirmation Importar sem pedir confirmação General CPAP and Related Settings Configurações Gerais e Relacionadas ao CPAP Enable Unknown Events Channels Ativar Canais de Evento Desconhecidos AHI Apnea Hypopnea Index Índice de Apnéia-Hipoapnéia IAH RDI Respiratory Disturbance Index Índice de Distúrbio Respiratório IDR AHI/Hour Graph Time Window Janela de Tempo do Gráfico IAH/Hora Preferred major event index Índice preferido de evento principal Compliance defined as Conformidade definida como Flag leaks over threshold Marcar vazamentos acima do limite Seconds Segundos <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Nota: Isso não é pretendido para correções de fuso-horário! Certifique-se de que o fuso-horário e o relógio do seu sistema estão configurados corretamente.</p></body></html> Hours Horas <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Máximo verdadeiro é o máximo do conjunto de dados.</p><p>O percentual 99 filtra os eventos mais isolados.</p></body></html> Combined Count divided by Total Hours Contagem Combinada divididade pelo Total de Horas Time Weighted average of Indice ***Needs Revision*** A Média Ponderada pelo tempo do Índice Standard average of indice Média padrão do índice Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Comprime Backups ResMed (EDF) para economizar.espaço em disco. Arquivos EDF salvos são armazenados no formato .gz, que é comum nas plataformas Mac & Linux.. OSCAR pode importar desse formato nativamente.. Usá-lo com o ResScan exigirá primeiro a descompressão dos arquivos .gz.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. As seguintes opções afetam o espaço em disco usado pelo OSCAR, e tem um efeito no tempo que a importação leva. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Isso faz com que os dados do OSCAR usem cerca de metade do espaço. Mas torna a importação e mudança de dias mais lentas.. Se você tem um PC novo com um disco rígido menor, essa é uma boa opção. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Comprimir Dados de Sessão (torna os dados do OSCAR menores, mas a mudança de dia mais lenta) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Isto mantém um backup do cartão SD para aparelhos ResMed, As máquinas da série ResMed S9 excluem dados de alta resolução com mais de 7 dias, e gráfico de dados com mais de 30 dias.. O OSCAR pode manter uma cópia desses dados se você precisar reinstalar. (Altamente recomendado, a menos que você não possua espaço ou não se importe com os dados gráficos) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Torna o início do OSCAR um pouco mais lento, pré-carregando todos os dados do resumo antecipadamente, o que acelera a navegação na visão geral e alguns outros cálculos posteriores.. </p><p>Se você possui uma grande quantidade de dados, pode valer a pena manter isso desativado, mas se você gosta de ver <span style=" font-style:italic;">tudo</span> na visão geral, todos os dados do resumo ainda precisam ser carregados de qualquer modo</p><p>Note que essa configuração não afeta dados de formas de onda e eventos, que são sempre carregados de acordo com a necessidade.</p></body></html> Custom CPAP User Event Flagging Marcação de Eventos Personalizada pelo Usuário de CPAP This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Essa opção experimental tenta usar o sistema de marcação de eventos do OSCAR para melhorar o posicionamento de eventos detectados pelo aparelho. l/min l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p> Índices Cumulativos</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturações Abaixo</p></body></html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Sincronizando dados da Oximetria e CPAP</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Dados do CMS50 importados do SpO2Review (do arquivo .spoR) ou do método de importação serial </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> necessita da data e horas corretas para sincronizar</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Modo de visão ao vivo (usando um cabo serial) é uma maneira de obter uma sincronização precisa nos oxímetros CMS50, mas não contraria o desvio do relógio CPAP.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Se você iniciar o modo de gravação do seu Oximetro em </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exatamente </span><span style=" font-family:'Sans'; font-size:10pt;">na mesma hora que você iniciar seu equipamento CPAP, você conseguirá sincronizar. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">O processo de importação em série toma a hora de início da primeira sessão de CPAP da noite anterior. (Lembre-se de importar seus dados CPAP primeiro!)</span></p></body></html> Show Remove Card reminder notification on OSCAR shutdown Exibir lembrete para Remover Cartão ao encerrar o OSCAR Check for new version every Buscar uma nova versão a cada days. dias. Last Checked For Updates: Última Busca Por Atualizações: TextLabel Rótulo I want to be notified of test versions. (Advanced users only please.) Eu gostaria de ser avisado sobre versões de teste. (Por favor somente usuários avançados.) &Appearance &Aparência Graph Settings Configurações de Gráfico <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Qual aba abrir ao carregar um perfil. (Nota: O padrão é Perfil será o OSCAR não estiver configurado para abrir um perfil ao iniciar)</p></body></html> Bar Tops Topo de Barra Line Chart Gráficos de Linha Overview Linecharts Visão-Geral de Gráficos de Linha <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Isso facilita a rolagem quando o zoom é mais fácil em TouchPads bidirecionais sensíveis.</p><p>50 ms é o valor recomendado.</p></body></html> Scroll Dampening Amortecimento de rolagem Overlay Flags Marcações Sobrepostas Line Thickness Espessura de Linha The pixel thickness of line plots Espessura em pixels dos traços de linha Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Cache de pixmap é uma técnica de aceleração gráfica. Pode causar problemas no desenho de fontes na área de mostragem de gráficos na sua plataforma. Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Tente alterar isso da configuração padrão (Desktop OpenGL) se você experimentar problemas de desenho nos gráficos do OSCAR. Fonts (Application wide settings) Fontes (afeta o aplicativo todo) The visual method of displaying waveform overlay flags. O método visual de mostrar marcações de sobreposição de formas de onda. Standard Bars Barras Padrão Graph Height Altura do Gráfico Default display height of graphs in pixels Altura de exibição, em pixels, padrão dos gráficos How long you want the tooltips to stay visible. Por quanto tempo você deseja que as dicas de contexto permaneçam visíveis. Events Eventos Reset &Defaults Redefinir Pa&drões <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Aviso: </span>Só porque você pode, não significa que seja uma boa prática.</p></body></html> Waveforms Formas de Onda Flag rapid changes in oximetry stats Marcar mudanças bruscas nos números de oximetria Other oximetry options Outras opções de oximetria Discard segments under Descartar segmentos abaixo de Flag Pulse Rate Above Marcar Taxa de Pulso Acima de Flag Pulse Rate Below Marcar Taxa de Pulso Abaixo de <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Essa configuração deve ser usada com cautela...</span> Desativá-la traz consequências envolvendo a precisão de dias resumo-apenas, pois certos cálculos só funcionam corretamente se sessões de resumo provenientes de registros de dias individuais forem mantidas juntas. </p><p><span style=" font-weight:600;">Usuários ResMed:</span> Apenas porque parece natural para você e eu que a sessão reiniciada ao meio-dia deve estar no dia anterior, não significa que os dados da ResMed concordam conosco. O formato de índice de resumo STF.edf tem sérios pontos fracos que fazem com que isso não seja uma boa idéia.</p><p>Esta opção existe para apaziguar aqueles que não se importam e querem ver isso &quot;corrigido&quot; não importando os custos, mas saiba que isso tem um custo. Se você mantiver seu cartão SD inserido todas as noites, e importar pelo menos uma vez por semana, você não verá problemas com isso com grande frequência..</p></body></html> This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Este cálculo requer dados de vazamentos totais a serem fornecidos pelo aparelho de CPAP. (Por exemplo, CAP1, mas não a ResMed, que já tem isso) Os cálculos de vazamentos não intencionais usados aqui são lineares, eles não modelam a curva de ventilação da máscara. Se você usar algumas máscaras diferentes, escolha valores médios. E deve ficar próximo o suficiente. Calculate Unintentional Leaks When Not Present Calcular Vazamentos Não Intencionais Quando Não Presente 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Nota: Um método de cálculo linear é usado. Alterar esses valores requer um recálculo. Show flags for machine detected events that haven't been identified yet. Mostrar marcações para eventos detectados por aparelhos que ainda não foram identificados. Tooltip Timeout Tempo Limite de Dica de Contexto Graph Tooltips Dicas de Contexto de Gráfico Top Markers Marcadores de Topo Changing SD Backup compression options doesn't automatically recompress backup data. Alterar as opções de compactação do Backup do SD não recompacta automaticamente os dados do backup. Auto-Launch CPAP Importer after opening profile Iniciar o importador de CPAP automaticamente depois de abrir o perfil Automatically load last used profile on start-up Carregar automaticamente o perfil usado por último ao abrir <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Prove um alerta quando importando dados de qualquer modelo de aparelho que ainda não foi testado pelos desenvolvedores do OSCAR.</p></body></html> Warn when importing data from an untested machine Alerta quando importando dados de uma manquina não testada <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>Prove um alerta quando importando dados que de alguma maneira são diferentes de algo visto anteriormente pelos desenvolvedores do OSCAR.</p></body></html> Warn when previously unseen data is encountered Alerta quando dados não vistos anteriormente são encontrados Your masks vent rate at 20 cmH2O pressure Sua máscara ventila a taxa de presão de 20 cmH2O Your masks vent rate at 4 cmH2O pressure Sua máscara ventila a taxa de presão de 4 cmH2O <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Devido a limitações no projeto dos resumos, os aparelhos da ResMed não suportam a alteração dessas opções.</p></body></html> Oximetry Settings Opções de oximetria Always save screenshots in the OSCAR Data folder Sempre salve as capturas de tela na pasta de Dados do OSCAR Check For Updates Verificar se há atualizações You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Você está usando uma versão de teste do OSCAR. As versões de teste verificam as atualizações automaticamente pelo menos uma vez a cada sete dias. Você pode definir o intervalo para menos de sete dias. Automatically check for updates Verificar atualizações automaticamente How often OSCAR should check for updates. Com que intervalo o OSCAR verifica se há atualizações. If you are interested in helping test new features and bugfixes early, click here. Se você estiver interessado em ajudar a testar novos recursos e correções de bugs com antecedência, clique aqui. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Se você gostaria de ajudar a testar as primeiras versões do OSCAR, consulte a página Wiki sobre como testar o OSCAR. Damos as boas-vindas a todos que desejam testar o OSCAR, ajudar a desenvolver o OSCAR e ajudar com traduções para idiomas novos ou existentes. https://www.sleepfiles.com/OSCAR On Opening Ao Abrir Profile Perfil Welcome Bem-vindo Daily Diariamente Statistics Estatísticas Switch Tabs Alternar Abas No change Nenhuma mudança After Import Após Importação Other Visual Settings Outras Opções Visuais Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. O anti-distorção aplica suavização aos desenhos do gráfico. Certas parcelas parecem mais atraentes com isso. Isso também afeta os relatórios impressos. Experimente e veja se você gosta. Use Anti-Aliasing Usar Anti-Distorção Makes certain plots look more "square waved". Faz com que certos gráficos pareçam mais "ondas quadradas". Square Wave Plots Gráficos de Onda Quadrada Use Pixmap Caching Usar Cache de Pixmap Animations && Fancy Stuff Animações e Coisas Chiques Whether to allow changing yAxis scales by double clicking on yAxis labels Permitir ou não a alteração das escalas do EixoY clicando duas vezes nos rótulos do EixoY Allow YAxis Scaling Permitir Escala do EixoY Whether to include machine serial number on machine settings changes report Se deseja incluir o número de série do aparelho no relatório de mudanças de configurações de equipamento Include Serial Number Inclui Número de Série Graphics Engine (Requires Restart) Motor Gráfico (Exige Reinício) Print reports in black and white, which can be more legible on non-color printers Imprimir relatórios em preto e branco, que podem ser mais legíveis em impressoras não coloridas Print reports in black and white (monochrome) Imprimir relatórios em Preto e Branco (monocromático) Font Fonte Size Tamanho Bold Negrito Italic Itálico Application Aplicativo Graph Text Texto do Gráfico Graph Titles Títulos do Gráfico Big Text Texto Grande Details Detalhes &Cancel &Cancelar &Ok &Ok Name Nome Color Cor Flag Type Tipo de Marcação Label Rótulo CPAP Events Eventos CPAP Oximeter Events Eventos Oxímetro Positional Events Eventos Posicionais Sleep Stage Events Eventos de Estágio do Sono Unknown Events Eventos Desconhecidos Double click to change the descriptive name this channel. Clique duas vezes para alterar o nome descritivo desse canal. Double click to change the default color for this channel plot/flag/data. Clique duas vezes para alterar a cor padrão para esse canal/desenho/marcação/dado. <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Note:</b> as capacidades avançadas de divisão de sessões do OSCAR não funcionam com aparelhos <b>ResMed</b> devido a uma limitação no modo como as configurações e dados de sumário são armazenadas, e portanto elas foram desativadas para esse perfil.</p><p>Em aparelhos ResMed os dias serão <b>devididos ao meio-dia</b> como no software comercial ResMed.</p> %1 %2 %1 %2 Overview Visão-Geral Double click to change the descriptive name the '%1' channel. Clique duass vezes para mudar o nome descritivo do canal '%1'. Whether this flag has a dedicated overview chart. Se esse marcador possui um gráfico de visão geral dedicado ou não. Here you can change the type of flag shown for this event Aqui você pode alterar o tipo de marcação mostrada para este evento This is the short-form label to indicate this channel on screen. Este é o rótulo de forma curta para indicar este canal na tela. This is a description of what this channel does. Esta é uma descrição do que esse canal faz. Lower Inferior Upper Superior CPAP Waveforms Formas de Onda de CPAP Oximeter Waveforms Formas de Onda de Oxímetro Positional Waveforms Formas de Onda Posicionais Sleep Stage Waveforms Formas de Onda de Estágio de Sono Whether a breakdown of this waveform displays in overview. Se uma separação dessa forma de onda é exibida na visão geral ou não. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Aqui você pode definir o limite <b>inferior</b> usado para determinados cálculos na forma de onda %1 Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Aqui você pode definir o limite <b>superior</b> usado para determinados cálculos na forma de onda %1 Data Processing Required Processamento de Dados Requerido A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Um procedimento de recuperação de dados / descompactação é necessário para aplicar essas alterações. Essa operação pode levar alguns minutos para ser concluída. Tem certeza de que deseja fazer essas alterações? Data Reindex Required Reordenação de Dados de Índice Requerida A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Um procedimento de reindexação de dados é necessário para aplicar essas alterações. Essa operação pode levar alguns minutos para ser concluída. Tem certeza de que deseja fazer essas alterações? Restart Required Reinício Requerido One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Uma ou mais das alterações feitas exigirão que esse aplicativo seja reiniciado para que essas alterações entrem em vigor. Você gostaria de fazer isso agora? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Se você precisar reimportar esse gráfico novamente (seja no OSCAR ou ResSca) esses dados não voltarão. If you need to conserve disk space, please remember to carry out manual backups. Se você precisa poupar espaço, por favor lembra de realizar backups manuais. Are you sure you want to disable these backups? Tem certeza de que deseja desativar esses backups? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Desativar backups não é boa ideia, pois o OSCAR precisa deles para recalcular o banco de dados em caso de erros. Are you really sure you want to do this? Tem certeza mesmo de que deseja fazer isso? Flag Marcação Minor Flag Pequena Marcação Span Período Always Minor Sempre Pequeno No CPAP machines detected Não foi detectada um aparelho CPAP Will you be using a ResMed brand machine? Você está usando um aparelho da marca ResMed? Never Nunca This may not be a good idea Isso pode não ser uma boa ideia ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Os aparelhos ResMed S9 apagam rotineiramente certos dados do seu cartão SD com mais de 7 e 30 dias (dependendo da resolução). ProfileSelector Filter: Filtro: Reset filter to see all profiles Version Versão &Open Profile Abr&ir Perfil &Edit Profile &Editar Perfil &New Profile &Novo Perfil Profile: None Perfil: Nenhum Please select or create a profile... Por favor selecione ou crie um perfil... Destroy Profile Destruir Perfil Profile Perfil Ventilator Brand Marca do Ventilador Ventilator Model Modelo do Ventilador Other Data Outros Dados Last Imported Importado por Último Name Nome You must create a profile Você deve criar um perfil Enter Password for %1 Digite a senha para %1 You entered an incorrect password Você digitou uma senha incorreta Forgot your password? Esqueceu sua senha? Ask on the forums how to reset it, it's actually pretty easy. Pergunte nos fóruns como redefiní-la, na verdade é muito fácil. Select a profile first Selecione um perfil primeiro The selected profile does not appear to contain any data and cannot be removed by OSCAR O perfil seleccionado não parece conter quaisquer dados e não pode ser removido pelo OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Se você está tentando deletar porque você esqueceu a senha, você precisa ou redefiní-la ou deletar a pasta do perfil manualmente. You are about to destroy profile '<b>%1</b>'. Você está prestes a destruir o perfil '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Pense com cuidado, já que isso irá irreverssívelmente deletar o perfil bem como todos os <b>dados de backup</b> guardados sob<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Digite a palavra <b>DELETE</b> abaixo (exatamente como exibida) parar confirmar. DELETE DELETE Sorry Desculpe You need to enter DELETE in capital letters. Você precisa digitar DELETE em letras maiúsculas. There was an error deleting the profile directory, you need to manually remove it. Houve um erro deletando a pasta do perfil, você precisa removê-lo manualmente. Profile '%1' was succesfully deleted Perfil '%1' foi deletado com sucesso Bytes Bytes KB KiB MB MiB GB GiB TB TiB PB PiB Summaries: Resumos: Events: Eventos: Backups: Backups: Hide disk usage information Esconder informação de uso de disco Show disk usage information Mostrar informação de uso de disco Name: %1, %2 Nome: %1, %2 Phone: %1 Telefone: %1 Email: <a href='mailto:%1'>%1</a> Email: <a href='mailto:%1'>%1</a> Address: Endereço: No profile information given Nenhuma informação de perfil fornecida Profile: %1 Perfil: %1 ProgressDialog Abort Abortar QObject No Data Nenhum Dado On Ligado Off Desligado ft ft lb lb oz oz Kg Kg cmH2O cmH2O Med. Med. Min: %1 Min: %1 Min: Min: Max: Max: Max: %1 Max: %1 %1 (%2 days): %1 (%2 dias): %1 (%2 day): %1 (%2 dia): % in %1 % em %1 Hours Horas Min %1 Mín %1 Hours: %1 Horas: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 baixo uso, %2 nenhum uso, de %3 dias (%4% obervância). Duração: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Sessões: %1 / %2 / %3 Duração: %4 / %5 / %6 Mais Longa: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Duração: %3 Início: %2 Mask On Máscara Colocada Mask Off Máscara Removida %1 Length: %3 Start: %2 %1 Duração: %3 Início: %2 TTIA: Tempo Total Em Apnéia? TTIA: TTIA: %1 TTIA: %1 bpm bpm ? ? Severity (0-1) Severidade (0-1) Error Erro Warning Aviso Please Note Por Favor Note Graphs Switched Off Gráficos Desativados Sessions Switched Off Sessões Desativadas &Yes &Sim &No &Não &Cancel &Cancelar &Destroy &Destruir &Save &Salvar BMI IMC Weight Peso Zombie Zumbi Pulse Rate Taxa de Pulso Plethy Pletis Pressure Pressão Daily Diário Profile Perfil Overview Visão-Geral Oximetry Oximetria Oximeter Oxímetro Event Flags Marcações de Evento Default Padrão CPAP Constant Positive Airway Pressure CPAP BiPAP Bi-Level Positive Airway Pressure - Presão de ar Positiva Bi-Level PAPBi Bi-Level Another name for BiPAP Bi-Level EPAP Expiratory Positive Airway Pressure - Pressão Ar Expiratóra Positiva PAEP Min EPAP Lower Expiratory Positive Airway Pressure PAEP Mín Max EPAP Higher Expiratory Positive Airway Pressure PAEP Máx IPAP Inspiratory Positive Airway Pressure - Pressão de ar Inspiratória Positiva PAIP APAP Lower Expiratory Positive Airway Pressure - Pressão de ar Expiratória Baixa PAEB ASV Assisted Servo Ventilator - Ventilador Servo Assistido VSA AVAPS Average Volume Assured Pressure Support ----Suporte de pressão média garantida por volume SPMGV ST/ASV ST/ASV Humidifier Umidifcador H Short form of Hypopnea H OA Short form of Obstructive Apnea - Apnéia Obstrutiva AO A Short form of Unspecified Apnea - Apnéia Não Especificada A CA Short form of Clear Airway Apnea -Apnéia de via aérea Desobstruída VRD FL Short form of Flow Limitation ---- Limitação de Fluxo LF LE Short form of Leak Event ---- Evento de Vazamento EV EP Short form of Expiratory Puff ---- Sopro Expiratório SE VS Short form of Vibratory Snore ---- Ronco Vibratório RV VS2 Short form of Secondary Vibratory Snore - Ronco Vibratório Secundário RV2 RERA Acronym for Respiratory Effort Related Arousal ---- Acrônimo de excitação relacionada ao esforço respiratório Excitação Esforço Respiratório PP Short form for Pressure Pulse ---- Pulso de Pressão PP P Short form for Pressure Event ---- Evento de Pressão P RE Short form of Respiratory Effort Related Arousal ---- Forma abreviada de Excitação Relacionada ao Esforço Respiratório ER NR Short form of Non Responding event ---- Evento sem Resposta SR NRI it's a flag on Intellipap machines NRI O2 SpO2 Desaturation - Desaturação SpO2 O2 PC Short form for Pulse Change ---- Mudança de Pulso MP UF1 Short form for User Flag ---- Marcação do Usuário MU1 UF2 Short form for User Flag ---- Marcação do Usuário MU2 UF3 Short form for User Flag ---- Marcação do Usuário MU3 PS Short form of Pressure Support ---- Suporte de Pressão SP AHI Short form of Apnea Hypopnea Index - ìndice Hipoapnéia IAH RDI Short form of Respiratory Distress Index ---- Forma abreviada do Índice de Insuficiência Respiratória IIR AI Short form of Apnea Index - Índice de Apnéia IA HI Short form of Hypopnea Index - Índice de Hipoapnéia IH UAI Short form of Uncatagorized Apnea Index ----- Índice de Apnéia Indeterminada IAI CAI Short form of Clear Airway Index - Índice de via aérea desobstruída IAC FLI Short form of Flow Limitation Index ---Índice de Limitação de Fluxo FLI REI Short form of RERA Index ---- Forma abreviada do excitação relacionada ao esforço respiratório IER EPI Short form of Expiratory Puff Index - Índice de Sopro Expiratório ISE Min IPAP Lower Inspiratory Positive Airway Pressure IPAP Mín App key: Chave app: Operating system: Sistema Operacional: Built with Qt %1 on %2 Construido com Qt %1 em %2 Graphics Engine: Mecanismo Gráfico: Graphics Engine type: Tipo Mecanismo Gráfico: Software Engine Motor de Software ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL OpenGL Desktop m m cm cm in pol Minutes Minutos Seconds Segundos h h m m s s ms ms Events/hr Eventos/hr Hz Hz Litres Litros ml ml Breaths/min Respirações/min Degrees Graus Information Informação Busy Ocupado Only Settings and Compliance Data Available Somente Daddos de Conformidade e Configuração Disponíveis Summary Data Only Apenas Dados Resumidos Max IPAP IPAP Máx SA Apnéia do Sono AS PB Respiração Periódica RP IE IE Insp. Time T. Inspiração Exp. Time T. Expiração Resp. Event Ends with abbreviation @ristraus Evento Resp. Flow Limitation Limitação de Fluxo Flow Limit Limite de Fluxo SensAwake DespSens Pat. Trig. Breath Resp. por Paciente Tgt. Min. Vent Vent. Mín. Alvo Target Vent. Ends with no abbreviation @ristraus Vent Alvo. Minute Vent. Ends with no abbreviation @ristraus Vent. Minuto Tidal Volume Volume Tidal Resp. Rate Ends with abbreviation @ristraus Taxa de Resp. Snore Ronco Leak Vazamento Leaks Vazamentos Total Leaks Vazamentos Totais Unintentional Leaks Vazamentos Não-Intencionais MaskPressure PressãoMáscara Flow Rate Taxa de Fluxo Sleep Stage Estágio do Sono Usage Uso Sessions Sessões Pr. Relief Alívio de Pressão No Data Available Nenhum Dado Disponível Bookmarks Favoritos Mode Modo Model Modelo Brand Marca Serial Serial Series Série Machine Aparelho Channel Canal Settings Configurações Motion Movimento Name Nome DOB Ends with abbreviation @ristraus Data Nasc. Phone Telefone Address Endereço Email Email Patient ID RG Paciente Date Data Bedtime Hora de Dormir Wake-up Acordar Mask Time Tempo Máscara Unknown Desconhecido None Nenhum Ready Pronto First Primeiro Last Último Start Início End Fim Yes Sim No Não Min Mín Max Máx Med Méd Average Média Median Mediana Avg Méd W-Avg Méd-Aco Getting Ready... Aprontando-se... Your %1 %2 (%3) generated data that OSCAR has never seen before. Seus dados gerados %1 %2 (%3)que o OSCAR nunca viu anteriormente. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Os dados importados podem não ser totalmente precisos, então os desenvolvedores gostariam de uma cópia .zip do cartão SD desta máquina e dos relatórios em .pdf do médico para se certificar de que o OSCAR está lidando com os dados corretamente. Non Data Capable Machine Aparelho Sem Capacidade de Dados Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Seu equipamento CPAP %1 (Modelo %2) infelizmente não é um modelo de dados compatível. Your %1 CPAP machine (Model %2) has not been tested yet. Seu equipamento CPAP %1 (Modelo %2) não foi testado ainda. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Parece suficientemente semelhante a outras máquinas para que funcione, mas os desenvolvedores gostariam de uma cópia .zip do cartão SD desta máquina e dos relatórios clínicos .pdf correspondentes para ter certeza de que funciona com o OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. Desculpe, Seu equipamento CPAP %1 Modelo %2 ainda não é suportado. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Os desenvolvedores precisam de uma cópia .zip do cartão SD desta máquina e dos relatórios em .pdf do médico para que funcione com o OSCAR. Machine Unsupported Aparelho Não Suportado I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Lamento relatar que OSCAR só pode registrar horas de uso e configurações básicas nesse aparelho. Scanning Files... Vasculhando Arquivos... Importing Sessions... Importando Sessões... Finishing up... Finalizando... Untested Data Dados não testados Machine Untested Aparelho Não Testado CPAP-Check CPAP-Verificar AutoCPAP AutoCPAP Auto-Trial Tentativas-Automáticas AutoBiLevel NìvelDuploAutomático S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Flex Flex Lock Travar Flexível Whether Flex settings are available to you. Se as configurações do Flex estão disponíveis para você. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Quanto tempo leva para fazer a transição do EPAP para o IPAP, quanto maior o número, mais lenta é a transição Rise Time Lock Bloqueio do tempo de subida Whether Rise Time settings are available to you. Se as configurações do Tempo de Subida estão disponíveis para você. Rise Lock Bloqueio de Subida Passover Atravessar Target Time Hora do Objetivo PRS1 Humidifier Target Time Hora do Objetivo do Umidificador PRS1 Hum. Tgt Time Ends with abbreviation @ristraus Hora Obj Umid. Mask Resistance Setting Configuração de Resistência da Máscara Mask Resist. Ends with no abbreviation @ristraus Resit. da Máscara Hose Diam. Ends with no abbreviation @ristraus Diam. da Mangueira 15mm 15mm Tubing Type Lock Bloqueio Tipo de Tubo Whether tubing type settings are available to you. Se as configurações do tipo de tubo estão disponíveis para você. Tube Lock Bloquieo Tubo Mask Resistance Lock Trava da Resistência da Máscara Whether mask resistance settings are available to you. Se as configurações de resistência da máscara estão disponíveis para você. Mask Res. Lock Bloqueio da Res. Máscara Whether or not machine shows AHI via built-in display. Se a máquina mostra ou não IAH através da tela embutida. Ramp Type Tipo Rampa Type of ramp curve to use. Tipo de curva de rampa a ser usada. Linear Linear SmartRamp RampaInteligente Ramp+ Rampa+ Backup Breath Mode Modo de Respiração Reserva The kind of backup breath rate in use: none (off), automatic, or fixed O tipo de taxa de respiração de reserva em uso: nenhuma (desativada), automática ou fixa Breath Rate Taxa de Respiração Fixed Fixa Fixed Backup Breath BPM RPM de respiração de reserva fixo Minimum breaths per minute (BPM) below which a timed breath will be initiated Respirações mínimas por minuto (RPM) abaixo das quais uma respiração programada será iniciada Breath BPM Respiração RPM Timed Inspiration Tempo de Inspiração The time that a timed breath will provide IPAP before transitioning to EPAP O tempo que uma respiração cronometrada fornecerá o PAIP antes da transição para o PAEP Timed Insp. Ends with no abbreviation @ristraus Insp. Cronometrada Auto-Trial Duration Duração da avaliação automática The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP O número de dias no período de avaliação do Auto-CPAP, após o qual a máquina reverterá para o CPAP Auto-Trial Dur. Ends with no abbreviation @ristraus Duração da avaliação automática EZ-Start Início-EZ Whether or not EZ-Start is enabled Se o Início-EZ está ou não ativado Variable Breathing Respiração variável UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend NÃO CONFIRMADO: Possivelmente respiração variável, que são períodos de alto desvio da tendência do pico de fluxo inspiratório Peak Flow Pico de Fluxo Peak flow during a 2-minute interval Pico de fluxo durante um intervalo de 2 minutos 22mm 22mm Backing Up Files... Salvando Arquivos... model %1 modelo %1 DreamStation 2 DreamStation 2 unknown model modelo desconhecido Flex Mode Flex Mode PRS1 pressure relief mode. Modo CAP1 de alívio de pressão. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time Tempo de Rampa Bi-Flex Bi-Flex Flex Level Flex Level PRS1 pressure relief setting. Configuração CAP1 alívio de pressão. Humidifier Status Estado do Umidificador PRS1 humidifier connected? Umidificador PRS1 conectado? Disconnected Disconectado Connected Conectado Humidification Mode Modo Umidificador PRS1 Humidification Mode Modo Umidificados PRS1 Humid. Mode Ends with abbreviation @ristraus Modo Umid. Fixed (Classic) Corrigido (Clássico) Adaptive (System One) Adaptivo (System One) Heated Tube Tubo Aquecido Tube Temperature Temperatura do Tubo PRS1 Heated Tube Temperature Temperatura do Tubo Aquecido PRS1 Tube Temp. Ends with no abbreviation @ristraus Temp. do Tubo PRS1 Humidifier Setting Configuração do Umidificador PRS1 Hose Diameter Diâmetro da Traquéia Diameter of primary CPAP hose Diâmetro da traquéia do CPAP 12mm 12mm Auto On Auto Ligar A few breaths automatically starts machine Algumas respirações automaticamente ligam o aparelho Auto Off Auto Desligar Machine automatically switches off Aparelho desliga automaticamente Mask Alert Alerta de Máscara Whether or not machine allows Mask checking. Se o aparelho permite verificação da máscara. Show AHI Mostrar IAH Breathing Not Detected Respiração Não Detectada A period during a session where the machine could not detect flow. Um período durante a sesão onde o aparelho não pôde detectar fluxo. BND Respiração Não Detectada RND Timed Breath Respiração Cronometrada Machine Initiated Breath Respiração Iniciada pelo Aparelho TB Respiração Cronometrada RC Windows User Usuário Windows Using Usando , found SleepyHead - , encontrado SleepyHead - You must run the OSCAR Migration Tool Você deve rodar a Ferramenta de Migração do OSCAR <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Seus dados de aparelhos antigos devem ser regenerados, desde que esse recurso de backup não tenha sido desativado nas preferências durante uma importação de dados anterior.</i> Launching Windows Explorer failed Execução do Windows Explorer falhou Could not find explorer.exe in path to launch Windows Explorer. Não foi possível encontrar o explorer.exe no caminho para iniciar o Windows Explorer. <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR mantém um backup dos dados do seu aparelho que ele usa para esse fim.</b> OSCAR does not yet have any automatic card backups stored for this device. OSCAR ainda não possui nenhum backup automático de cartão para esse aparelho. This means you will need to import this machine data again afterwards from your own backups or data card. Isso significa que você precisará importar os dados do aparelho novamente a partir de seus próprios backups ou cartão de dados. Important: Importante: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Se você está preocupado, clique Não para sair; e faça um backup manual do seu perfil, antes de iniciar o OSCAR novamente. Are you ready to upgrade, so you can run the new version of OSCAR? Você está pronto para atualizar, para poder executar a nova versão do OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Lamento, a operação de limpeza falhou, o que significa que essa versão do OSCAR não pode executar. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Você gostaria de mudar para backups automáticos, assim da próxima vez que uma nova versão do OSCAR precisar fazê-lo, ela pode recalcular por eles? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR iniciará o assistente de importação para reinstalar seus dados %1. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: OSCAR irá fechar, então (tentar) abrir o gerenciador de arquivos do seu PC para que você possa copar manualmente seu perfil: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Use o gerenciador de arquivos para copiar o diretório de perfil, então após isso, reinicie o OSCAR e complete o processo de atualização. Machine Database Changes Alterações no Banco de Dados do Aparelho OSCAR %1 needs to upgrade its database for %2 %3 %4 O OSCAR %1 precisa atualizar seu banco de dados para o %2 %3 %4 Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Após a atualização, você <font size = + 1> não pode mais usar esse perfil com a versão anterior. The machine data folder needs to be removed manually. A pasta de dados do aparelho precisa ser removida manualmente. This folder currently resides at the following location: Essa pasta reside atualmente na localização seguinte: Rebuilding from %1 Backup Recompilando %1 do backup or CANCEL to skip migration. ou CANCEL para pular a migração. You cannot use this folder: Você não pode usar esta pasta: Migrating Migrando files arquivos from de to para OSCAR crashed due to an incompatibility with your graphics hardware. O OSCAR parou devido a uma incompatibilidade com sua placa de vídeo. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. Para solucionar isso, o OSCAR voltou a um método de desenho mais lento, porém mais compatível. OSCAR will set up a folder for your data. OSCAR irá definir uma pasta para os seus dados. We suggest you use this folder: Sugerimos você utilizar esta pasta: Click Ok to accept this, or No if you want to use a different folder. Clique Ok para aceitar isto, ou Não se você quiser usar uma pasta diferente. Next time you run OSCAR, you will be asked again. Da próxima vez que você executar o OSCAR, você será perguntado novamente. Migrate SleepyHead or OSCAR Data? Migrar dados do SleepyHead ou do OSCAR? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Na próxima tela o OSCAR lhe pedirá para selecionar uma pasta com os dados do SleepyHead ou do OSCAR Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Clique [OK] para ir para a próxima tela ou [Não] se você não quiser usar nenhum dado do SleepyHead ou do OSCAR. Unable to create the OSCAR data folder at Não é possível criar a pasta de dados do OSCAR em Unable to write to OSCAR data directory Não é possível gravar no diretório de dados do OSCAR Error code Código de Erro OSCAR cannot continue and is exiting. o OSCAR não pode continuar e está encerrando. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Não foi possível gravar no log de depuração. Você ainda pode usar o painel de depuração (Ajuda/Solução de problemas/Mostrar painel de depuração), mas o log de depuração não será gravado no disco. Version "%1" is invalid, cannot continue! a versão "%1 é inválida, não pode continuar! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). A versão do OSCAR que você está executando (%1) é ANTERIOR a utilizada para criar estes dados (J%2). Choose or create a new folder for OSCAR data Escolha ou crie uma nova pasta para os dados do OSCAR Choose the SleepyHead or OSCAR data folder to migrate Escolha a pasta de dados do SleepyHead ou do OSCAR para migrar The folder you chose does not contain valid SleepyHead or OSCAR data. A pasta selecionada não contém dados válidos de SleepyHead ou OSCAR. If you have been using SleepyHead or an older version of OSCAR, Se você tem usado o SleepyHead ou uma versão mais antiga do OSCAR, OSCAR can copy your old data to this folder later. O OSCAR pode copiar seus dados antigos para esta pasta mais tarde. As you did not select a data folder, OSCAR will exit. Como você não selecionou uma pasta de dados, OSCAR fechará. The folder you chose is not empty, nor does it already contain valid OSCAR data. O diretório escolhido não está vazio, nem contém dados válidos do OSCAR. Data directory: Pasta de dados: It is likely that doing this will cause data corruption, are you sure you want to do this? É provável que isso cause corrupção de dados. Tem certeza de que deseja fazer isso? Question Pergunta Exiting Encerrando Are you sure you want to use this folder? Tem certeza de que deseja usar esta pasta? Don't forget to place your datacard back in your CPAP machine Não se esqueça de colocar o seu cartão de volta no seu aparelho de CPAP OSCAR Reminder Lembrete do OSCAR You can only work with one instance of an individual OSCAR profile at a time. Você pode trabalhar apenas com uma instância individual de um perfil do OSCAR por vez. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Se você está usando armazenamento na nuvem, certifique-se de que o OSCAR está fechado e sincronizado no outro computador primeiro. Loading profile "%1"... Carregando perfil "%1"... Chromebook file system detected, but no removable device found Sistema de arquivo Chromebook detectado, mas nenhum dispositivo removível encontrado You must share your SD card with Linux using the ChromeOS Files program Você deve compartilhar seu cartão SD com o Linux usando o programa ChromeOS Files Recompressing Session Files Recomprimindo Arquivos de Sessão Please select a location for your zip other than the data card itself! Por favor selecione uma localização para seu zip diferente que o próprio cartão! Unable to create zip! Não foi possível criar o zip! Are you sure you want to reset all your channel colors and settings to defaults? Tem certeza de que deseja redefinir todas as suas configurações de cores e canais para os padrões? Are you sure you want to reset all your waveform channel colors and settings to defaults? Tem certeza de que deseja redefinir todas as cores e configurações do seu canal de forma de onda para os padrões? There are no graphs visible to print Não há gráficos visíveis para imprimir Would you like to show bookmarked areas in this report? Gostaria de mostrar áreas favoritadas neste relatório? Printing %1 Report Imprimindo Relatório %1 %1 Report Relatório %1 : %1 hours, %2 minutes, %3 seconds : %1 horas, %2 minutos, %3 segundos RDI %1 IDR %1 AHI %1 IAHI %1 AI=%1 HI=%2 CAI=%3 IA=%1 IH=%2 IAC=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% IER=%1 IRV=%2 ILF=%3 RP/RCS=%4%% UAI=%1 IAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 ISE=%3 AI=%1 IA=%1 Reporting from %1 to %2 Relatando de %1 a %2 Entire Day's Flow Waveform Forma de Onda de Fluxo do Dia Todo Current Selection Seleção Atual Entire Day Dia Inteiro Page %1 of %2 Página %1 de %2 Jan Jan Feb Fev Mar Mar Apr Abr May ai Jun Jun Jul Jul Aug Ago Sep Set Oct Out Nov Nov Dec Dez Events Eventos Duration Duração (% %1 in events) (% %1 em eventos) Therapy Pressure Pressão da Terapia Inspiratory Pressure Pressão Inspiratória Lower Inspiratory Pressure Pressão Inferior Respiratória Higher Inspiratory Pressure Pressão Superior Respiratória Expiratory Pressure Pressão Expiratória Lower Expiratory Pressure Pressão Expiratória Inferior Higher Expiratory Pressure Pressão Expiratória Superior Pressure Support Suporte de Pressão PS Min PS Mín Pressure Support Minimum Mínima Pressão de Suporte PS Max PS Máx Pressure Support Maximum Máxima Pressão de Suporte Min Pressure Pressão Mín Minimum Therapy Pressure Mínima Pressão de Terapia Max Pressure Pressão Máx Maximum Therapy Pressure Máxima Pressão de Terapia Ramp Time Tempo de Rampa Ramp Delay Period Período de Atraso de Rampa Ramp Pressure Pressão de Rampa Starting Ramp Pressure Pressão de Rampa Inicial Ramp Event Evento de Rampa Ramp Rampa Vibratory Snore (VS2) Ronco Vibratório (VS2) A ResMed data item: Trigger Cycle Event Um ítem de dados ResMed:Disparo Evento Cíclico Mask On Time Tempo com Máscara Time started according to str.edf Tempo iniciado de acordo com str.edf Summary Only Apenas Resumo An apnea where the airway is open Uma apnéia em que a via aérea está aberta An apnea caused by airway obstruction Uma apnéia causada por uma bostrução de via aérea Hypopnea Hipoapnéia A partially obstructed airway Uma via aérea parcialmente obstruída Unclassified Apnea Apnéia Indeterminada UA AI Vibratory Snore Ronco Vibratório A vibratory snore Um ronco vibratório A vibratory snore as detcted by a System One machine Um ronco vibratório como detectado por um aparelho System One Pressure Pulse Pulso de Pressão A pulse of pressure 'pinged' to detect a closed airway. Um pulseo de pressão 'pingado' para detectar uma via aérea fechada. Large Leak Grande Vazamento A large mask leak affecting machine performance. Um grande vazamento de máscara afetando o desempenho do aparelho. LL Large Leak Grande Vazamento GV Non Responding Event Um Evento Não Respondendo A type of respiratory event that won't respond to a pressure increase. Um tipo de evento que não irá responder a um aumento na pressão. Expiratory Puff Sopro Expiratório Intellipap event where you breathe out your mouth. Evento Intellipap no qual você expira pela boca. SensAwake feature will reduce pressure when waking is detected. Recursos SensAwake reduzirá a pressão quando caminhar é detectado. User Flag #1 Marca de Usuário #1 User Flag #2 Marca de Usuário #2 User Flag #3 Marca de Usuário #3 Heart rate in beats per minute Taxa cardíaca em batimentos por minuto Blood-oxygen saturation percentage Porcentagem de saturação de oxigênio no sangue Plethysomogram Pletismograma An optical Photo-plethysomogram showing heart rhythm Um pletismograma foto-óptico mostrando o ritmo cardíaco Pulse Change Mudança no pulso A sudden (user definable) change in heart rate Uma mudança brusca (definível pelo usuário) na taxa cardíaca SpO2 Drop Queda de SpO2 A sudden (user definable) drop in blood oxygen saturation Uma quebra brusca (definível pelo usuário) na saturação do sangue SD A sudden (user definable) drop - Queda Brusca QB Breathing flow rate waveform Forma de onda da taxa de fluxo respiratório L/min L/min Mask Pressure Pressão de Máscara Amount of air displaced per breath Quantidade de ar deslocado por respiração Graph displaying snore volume Gráfico mostrando o volume de ronco Minute Ventilation Ventilação por Minuto Amount of air displaced per minute Quantidade de ar deslocado por minuto Respiratory Rate Taxa Respiratória Rate of breaths per minute Taxa de respirações por minuto Patient Triggered Breaths Respirações Iniciadas pelo Paciente Percentage of breaths triggered by patient Porcentagem de respirações iniciadas pelo paciente Pat. Trig. Breaths Resp. Inic. Paciente Leak Rate Taxa de Vazamento Rate of detected mask leakage Taxa do vazamento detectado na máscara I:E Ratio Taxa I:E Ratio between Inspiratory and Expiratory time Taxa entre tempo inspiratório e expiratório ratio taxa Pressure Min Mín Pressão Pressure Max Máx Pressão Pressure Set Pressão Configurada Pressure Setting Configuração de Pressão IPAP Set IPAP Configurado IPAP Setting Configurações de IPAP EPAP Set EPAP Configurado EPAP Setting Configurações de EPAP Cheyne Stokes Respiration To be confirmed, missing Cheyne translation Movimento de Respiração Cheyne An abnormal period of Cheyne Stokes Respiration Um período anormal de movimentos de respiração Cheyne Um período anormal de respiração Cheyne Stokes CSR RCS Periodic Breathing Respiração Periódica An abnormal period of Periodic Breathing Um período anormal de respiração Clear Airway Via Aérea Livre Obstructive Obstrutiva An apnea that couldn't be determined as Central or Obstructive. Uma apnéia que não pode ser determina como Central ou Obstrutiva. A restriction in breathing from normal, causing a flattening of the flow waveform. Uma restriçao na respiração normal, causando uma distorçao na forma de onda do fluxo. Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Excitação Relacionada ao Esforço Respiratório: Uma restrição na respiração que causa um despertar ou distúrbio do sono. Leak Flag Marcação Vazamento LF MV A user definable event detected by OSCAR's flow waveform processor. Um evento definível por usuário detectado pelo processador de forma de onda de fluxo do OSCAR. Perfusion Index Índice de Perfusão A relative assessment of the pulse strength at the monitoring site Uma avaliação relativa da força de pulso no lugar de monitoramente Perf. Index % Índice Perf. % Mask Pressure (High frequency) Pressão da Máscara (Alta frequencia) Expiratory Time Tempo Expiratório Time taken to breathe out Tempo usado para expirar Inspiratory Time Tempo Inspiratório Time taken to breathe in Tempo usado para inspirar Respiratory Event Evento Respiratório Graph showing severity of flow limitations Gráfico mostrando a severidade de limitações de fluxo Flow Limit. Limite de Fluxo. Target Minute Ventilation Alvo Ventilações Minuto Maximum Leak Vazamento Máximo The maximum rate of mask leakage A taxa máxima de vazamento da máscara Max Leaks Vazamentos Máx Apnea Hypopnea Index Índice de Apnéia Hipo-apnéia Graph showing running AHI for the past hour Gráfico mostrando IAH na hora precedente Total Leak Rate Taxa Total de Vazamento Detected mask leakage including natural Mask leakages Vazamento de máscara detectado incluindo vazamentos naturais de máscara Median Leak Rate Taxa Mediana de Vazamento Median rate of detected mask leakage Taxa mediana de vazamento detectado na máscara Median Leaks Vazamentos Medianos Respiratory Disturbance Index Índice de Distúrbio Respiratório Graph showing running RDI for the past hour Gráfico Mostrando o IDR na hora precedente Movement Movimento Movement detector Detetor de movimento CPAP Session contains summary data only Sessão CPAP contém apenas dados resumidos PAP Mode Modo PAP Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Não foi possível analisar Channels.xml, o OSCAR não pode continuar e está encerrando. Apnea Apnéia An apnea reportred by your CPAP machine. Uma apnéia reportada pelo seu equipamento CPAP. PAP Device Mode Modo Aparelho PAP APAP (Variable) APAP (Variável) ASV (Fixed EPAP) Assisted Servo Ventilator VSA (EPAP Fixo) ASV (Variable EPAP) VSA (EPAP Variável) Height Altura Physical Height Altura Física Notes Notas Bookmark Notes Notas de Favorito Body Mass Index Índice de Massa Corporal How you feel (0 = like crap, 10 = unstoppable) Como se sente (0 = como lixo, 10 = imparável) Bookmark Start Começo do Favorito Bookmark End Fim do Favorito Last Updated Última Atualização Journal Notes Notas de Diário Journal Diário 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=Acordado 2=REM 3=Sono Leve 4=Sono Profundo Brain Wave Onda Cerebral BrainWave OndaCerebral Awakenings Despertares Number of Awakenings Número de Despertares Morning Feel Sensação Matutina How you felt in the morning Como se sentiu na manhã Time Awake Tempo Acordado Time spent awake Tempo gasto acordado Time In REM Sleep Tempo No Sono REM Time spent in REM Sleep Tempo gasto no sono REM Time in REM Sleep Tempo no Sono REM Time In Light Sleep Tempo Em Sono Leve Time spent in light sleep Tempo gasto em sono leve Time in Light Sleep Tempo em Sono Leve Time In Deep Sleep Tempo Em Sono Profundo Time spent in deep sleep Tempo gasto em sono profundo Time in Deep Sleep Tempo em Sono Profundo Time to Sleep Tempo para Dormir Time taken to get to sleep Tempo exigido para conseguir adormecer Zeo ZQ Zeo sleep quality measurement Zeo ZQ Zeo sleep quality measurement Medição Zeo de qualidade do sono ZEO ZQ ZEO ZQ Debugging channel #1 Canal de depuração #1 Test #1 Teste #1 For internal use only Apenas para uso interno Debugging channel #2 Canal de depuração #2 Test #2 Teste #2 Zero Zero Upper Threshold Limite Superior Lower Threshold Limite Inferior Orientation Orientação Sleep position in degrees Posição de sono em graus Inclination Inclinação Upright angle in degrees Ângulo na vertical em graus Days: %1 Dias: %1 Low Usage Days: %1 Dias de Pouco Uso: %1 (%1% compliant, defined as > %2 hours) (%1% obervância, definida como > %2 horas) (Sess: %1) (Sess: %1) Bedtime: %1 Hora de cama: %1 Waketime: %1 Hora acordado: %1 (Summary Only) (Apenas Resumod) There is a lockfile already present for this profile '%1', claimed on '%2'. Existe um arquivo de bloqueio já presente para este perfil '%1', reivindicado em '%2'. Fixed Bi-Level Bi-Level Fixo Auto Bi-Level (Fixed PS) Auto Bi-Level (PS Fixa) Auto Bi-Level (Variable PS) Auto Bi-Level (PS Variável) 99.5% 99.5% varies varia n/a n/d Fixed %1 (%2) %1 (%2) Fixa Min %1 Max %2 (%3) Mín %1 Máx %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %1 sobre %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP Mín %1 IPAP Máx %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) PAPE %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) PAPE %1 PAPI %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) PAPE %1-%2 PAPI %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Dados de Oximetria mais recentes: <a onclick='alert("daily=%2");'>%1</a> (last night) (noite passada) (1 day ago) (1 dia atrás) (%2 days ago) (%2 dias atrás) No oximetry data has been imported yet. Nenum dado de oximetria foi importado ainda. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings Configurações SmartFlex ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Software Somnopose Zeo Zeo Personal Sleep Coach Personal Sleep Coach Database Outdated Please Rebuild CPAP Data Banco de Dados Desatualizado Por favor, Reconstrua os dados CPAP (%2 min, %3 sec) (%2 min, %3 seg) (%3 sec) (%3 seg) Pop out Graph Deslocar Gráfico The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. A janela de saída está cheia. Você deve capturar a janela existente, exclua-a e, em seguida, abra este gráfico novamente. Your machine doesn't record data to graph in Daily View Sua máquina não registra dados para o gráfico na Visualização Diária There is no data to graph Não há dados para desenhar d MMM yyyy [ %1 - %2 ] d MMM aaaa [ %1 - %2 ] Hide All Events Esconder Todos Eveitos Show All Events Mostrar Todos Eventos Unpin %1 Graph Fixar Gráfico %1 Popout %1 Graph Deslocar Gráfico %1 Pin %1 Graph Fixar Gráfico %1 Plots Disabled Desenhos Desativados Duration %1:%2:%3 Duração %1:%2:%3 AHI %1 IAH %1 Relief: %1 Alívio: %1 Hours: %1h, %2m, %3s Horas: %1h, %2m, %3s Machine Information Informação do Aparelho Journal Data Dados de Diário OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR encontrou um diretório antigo de Diário, mas parece que ele foi renomeado: OSCAR will not touch this folder, and will create a new one instead. OSCAR não tocará nessa pasta, e ao invés criará uma nova. Please be careful when playing in OSCAR's profile folders :-P Seja cauteloso ao brincar nas pastas de perfil do OSCAR :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. Por alguma razão, o OSCAR não pôde encontrar um objeto de registro de Diário em seu perfil, mas encontrou múltiplas pasta de dados de Diário. OSCAR picked only the first one of these, and will use it in future: OSCAR selecionou apenas o primeiro desses, e utilizará ele no futuro: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Se seus dados antigos estiverem faltando, copie o conteúdo de todas as outras pastas nomeadas Journal_XXXXXXX para esta manualmente. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Backing up files... Guardadno arquivos... Reading data files... Lendo arquivos de dados... SmartFlex Mode Modo SmartFlex Intellipap pressure relief mode. Modo Intellipap de alívio de pressão. Ramp Only Apenas Rampa Full Time Tempo Total SmartFlex Level Nível SmartFlex Intellipap pressure relief level. Nível de alívio de pressão Intellipap. Snoring event. Evento de Ronco. SN SN Locating STR.edf File(s)... Localizando arquivo(s) STR.edf... Cataloguing EDF Files... Catalogando arquivos EDF... Queueing Import Tasks... Ordenando Tarefas Importantes... Finishing Up... Terminando... CPAP Mode Modo CPAP VPAPauto VPAPauto ASVAuto ASVAuto iVAPS iVAPS PAC PAC Auto for Her Auto para Ela EPR EPR ResMed Exhale Pressure Relief Alívio de Pressão ResMed Exhale Patient??? Paciente??? EPR Level Nível EPR Exhale Pressure Relief Level Alívio de Pressão de Expiração Response Resposta Patient View Visão do Paciente SmartStart SmartStart Machine auto starts by breathing Aparelho liga automaticamente com a respiração Smart Start Smart Start Humid. Status ends with an abbreviation @ristraus Estado do Umidif. Humidifier Enabled Status Estado de Umidificador Ativo Humid. Level ends with an abbreviation @ristraus Nível do Umidif. Humidity Level Nível de Umidade Temperature Temperatura ClimateLine Temperature Temperatura ClimateLine Temp. Enable Temper. Ativa ClimateLine Temperature Enable Ativar Temperatura ClimateLine Temperature Enable Ativar Temperatura AB Filter Filtro AB Antibacterial Filter Filtro Antibacteriano Pt. Access Pto de Acesso Essentials Essenciais Plus Extra Climate Control Controle Climático Manual Manual Soft Macio Standard Padrão BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T SmartStop ParadaInteligente Machine auto stops by breathing Equipamento para automaticamente por respiração Smart Stop Parada Inteligente Simple Simples Advanced Avançado Your ResMed CPAP machine (Model %1) has not been tested yet. Seu equipamento CPAP ResMed (Modelo %1) ainda não foi testado. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parece suficientemente semelhante a outras máquinas para que funcione, mas os desenvolvedores gostariam de uma cópia .zip do cartão SD desta máquina para ter certeza de que funciona com o OSCAR. Parsing STR.edf records... Analisando registros STR.edf... Auto Automático Mask Máscara ResMed Mask Setting Configuração de Máscara ResMed Pillows Almofadas Full Face Facial Total Nasal Nasal Ramp Enable Ativar Rampa Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Snapshot %1 Captura %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... Carregando dados %1 para %2... Scanning Files Vasculhando arquivos Migrating Summary File Location Migrando Localização de Arquivo de Resumo Loading Summaries.xml.gz Carregando Summaries.xml.gz Loading Summary Data Carregando Dados de Resumos Please Wait... Por favor Aguarde... Updating Statistics cache Atualizando cache de Estatísticas Usage Statistics Estatísticas de Uso %1 Charts %1 Gráficos %1 of %2 Charts %1 de %2 Gráficos Loading summaries Carregando resumos Dreem Dreem Your Viatom device generated data that OSCAR has never seen before. Seu aparelho Viatom gerou dados que o OSCAR nunca viy anteriormente. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Os dados importados podem não estar inteiramente corretos, assim os desenvolvedores gostariam de uma cópia dos arquivos do seu Viatom a fim de assegurar que o OSCAR está manipulando os dados corretamente. Viatom Viatom Viatom Software Software Viatom New versions file improperly formed Arquivo de novas versões formatado incorretamente A more recent version of OSCAR is available Uma versão mais recente do OSCAR está disponível release versão test version versão de testes You are running the latest %1 of OSCAR Você está executando a última %1 do OSCAR You are running OSCAR %1 Você está executnado OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 está disponível <a href='%2'>aqui</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Informações sobre a versão mais recente de testes %1 está disponível em <a href='%2'>%2</a> Check for OSCAR Updates Buscar por atualizações do OSCAR Unable to check for updates. Please try again later. Incapaz de checar por atualizações. Por favor, tente novamente mais tarde. SensAwake level Expiratory Relief Alívio Expiratório Expiratory Relief Level Nível De Alívio Expiratório Humidity Umidade SleepStyle TypodeSono This page in other languages: Esta página está em outros idiomas: %1 Graphs Gráficos %1 %1 of %2 Graphs Gráficos %1 de %2 %1 Event Types Tipos de Eventos %1 %1 of %2 Event Types Tipos de Eventos %1 de %2 Report about:blank about:blank SessionBar %1h %2m %1h %2m No Sessions Present Nenhuma Sessão Presente SleepStyleLoader Import Error Erro de Importação This Machine Record cannot be imported in this profile. O registro deste aparelho não pode ser importado nesse perfil. The Day records overlap with already existing content. Os registros diários se sobrepõem com conteúdo pré-existente. Statistics Details Detalhes Most Recent Mais Recente Therapy Efficacy Eficácia da Terapia This report was prepared on %1 by OSCAR %2 Este relatório foi preparado em %1 pelo OSCAR %2 Last 30 Days Últimos 30 Dias Last Year Último Ano Average %1 Média %1 CPAP Statistics Estatísticas CPAP CPAP Usage Uso CPAP Average Hours per Night Horas Médias por Noite Compliance (%1 hrs/day) Complacência (%1 hrs/dia) Leak Statistics Estatísticas de Vazamento Pressure Statistics Estatísticas de Pressão Oximeter Statistics Estatísticas de Oxímetro Blood Oxygen Saturation Saturação de Oxigênio Sérico Pulse Rate Taxa de Pulso %1 Median Mediana %1 Min %1 Mín %1 Max %1 Máx %1 %1 Index Índice %1 % of time in %1 % de tempo em %1 % of time above %1 threshold % acima do limite %1 % of time below %1 threshold % do tempo abaixo do limite %1 Name: %1, %2 Nome: %1, %2 DOB: %1 Data Nasc.: %1 Phone: %1 Telefone: %1 Email: %1 Email: %1 Address: Endereço: Oscar has no data to report :( OSCAR não possui dados para relatar :( Days Used: %1 Dias de Uso: %1 Low Use Days: %1 Dias de Pouco Uso: %1 Compliance: %1% Observância: %1% Days AHI of 5 or greater: %1 Dias com IAH 5 ou mais: %1 Best AHI Melhor IAH Date: %1 AHI: %2 Data: %1 IAH: %2 Worst AHI Pior IAH Best Flow Limitation Melhor Limitação de Fluxo Date: %1 FL: %2 Data: %1 LF: %2 Worst Flow Limtation Pior Limitação de Fluxo No Flow Limitation on record Nenhuma Limitação de Fluxo na gravação Worst Large Leaks Pior Grande Vazamento Date: %1 Leak: %2% Data: %1 Vazamento: %2% No Large Leaks on record Nenhum Grande Vazamento na gravação Worst CSR Pior RCS Date: %1 CSR: %2% Data: %1 RCS: %2% No CSR on record Nenhuma RCS na gravação Worst PB Pior PR Date: %1 PB: %2% Data: %1 PR: %2% No PB on record Nenhum PR na gravação Want more information? Quer mais informações? OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR precisa carregar todos os dados de resumo para calcular melhor/pior para dias individuais. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Por favor ative a caixa de seleção Pré-Carregar Dados Resumidos nas preferências para garantir que esses dados estejam disponíveis. Best RX Setting Melhor Configuração RX Date: %1 - %2 Data: %1 - %2 AHI: %1 IAH: %1 Total Hours: %1 Total de Horas: %1 Worst RX Setting Pior Configuração RX Last Week Última Semana Changes to Machine Settings Mudanças nas Configurações do Aparelho No data found?!? Não foram encontrados dados?!? Last 6 Months Últimos 6 Meses Last Session Última Sessão No %1 data available. Nenhum dado %1 disponível. %1 day of %2 Data on %3 %1 dia de %2 dados em %3 %1 days of %2 Data, between %3 and %4 %1 dia de %2 dados em %3 e %4 OSCAR is free open-source CPAP report software OSCAR é um software livre de código aberto para análise de CPAP Days Dias Pressure Relief Alívio de Pressão Pressure Settings Configurações de Pressão Machine Information Informação do Aparelho First Use Primeiro Uso Last Use Último Uso Welcome Welcome to the Open Source CPAP Analysis Reporter Bem-vindo ao Open Source CPAP Analysis Reporter What would you like to do? O que você gostaria de fazer? CPAP Importer Importador CPAP Oximetry Wizard Assistente de Oximetria Daily View Visão Diária Overview Visão-Geral Statistics Estatísticas <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Atenção: </span><span style=" color:#ff0000;">cartões SD ResMed S9 precisam ser travados</span><span style=" font-weight:600; color:#ff0000;">>antes de inseri-los no seu computador&nbsp;&nbsp;&nbsp;</span><span style="color:#000000;"><br>Alguns sistemas operacionais escrevem arquivos de cache que corrompem o sistema de arquivos especial do diário deles</span></p></body></html> It would be a good idea to check File->Preferences first, Seria uma boa ideia verificar Arquivos->Preferências antes, as there are some options that affect import. Já que algumas opções afetam a importação. Note that some preferences are forced when a ResMed machine is detected Note que algumas preferências são obrigatórias se um aparelho ResMed é detectado First import can take a few minutes. A primeira importação pode levar alguns minutos. The last time you used your %1... A vez mais recente que você usou seu %1... last night noite passada %2 days ago %2 dias atrás was %1 (on %2) foi %1 (em %2) %1 hours, %2 minutes and %3 seconds %1 horas, %2 minutos e %3 segundos Your machine was on for %1. Seu aparelho estava ligado para %1. <font color = red>You only had the mask on for %1.</font> <font color = red>Você só manteve a máscara em uso por %1.</font> under abaixo over acima reasonably close to razoavelmente próximo de equal to igual a You had an AHI of %1, which is %2 your %3 day average of %4. Você teve um IAH de %1, que é %2 sua média de %3 dias de %4. Your CPAP machine used a constant %1 %2 of air Seu aparelho CPAP usa a constante %1 %2 de ar Your pressure was under %1 %2 for %3% of the time. Sua pressão ficou abaixo de %1 %2, %3% do tempo. Your machine used a constant %1-%2 %3 of air. Seu aparelho usa a constante %1-%2 %3 de ar. Your EPAP pressure fixed at %1 %2. Sua pressão EPAP fixou em %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Sua pressão IPAP ficou abaixo de %1 %2, %3% do tempo. Your EPAP pressure was under %1 %2 for %3% of the time. Sua pressão EPAP ficou abaixo de %1 %2, %3% do tempo. Your machine was under %1-%2 %3 for %4% of the time. Seu aparelho ficou abaixo de %1-%2 %3, %4% do tempo. 1 day ago 1 dia atrás Your average leaks were %1 %2, which is %3 your %4 day average of %5. Seus vazamentos médios foram %1 %2, que é %3 sua média de %4 dias de %5. No CPAP data has been imported yet. Nenhum dado CPAP foi importado ainda. gGraph %1 days %1 dias gGraphView 100% zoom level 100% de aproximação Restore X-axis zoom to 100% to view entire selected period. Restore a aproximação de EixoX para 100% para ver os dados completos do período selecionado. Restore X-axis zoom to 100% to view entire day's data. Restore a aproximação de EixoX para 100% para ver os dados completos do dia. Reset Graph Layout Redefinir Disposição de Gráfico Resets all graphs to a uniform height and default order. Redefine todos os gráficos para altura uniforme e ordenação padrão. Y-Axis EixoY Plots Desenhos CPAP Overlays Sobreposições CPAP Oximeter Overlays Sobreposições Oxímetro Dotted Lines Linhas Pontilhadas Double click title to pin / unpin Click and drag to reorder graphs Duplo Clique para marcar/desmarcar Clique e arraste para reordenar o gráfico Remove Clone Remover Clone Clone %1 Graph Clonar Gráfico %1 OSCAR-code-v1.3.1/Translations/Romanian.ro.ts000066400000000000000000014763711417327530600207300ustar00rootroot00000000000000 AboutDialog &About &Despre Release Notes Note despre actualizare Credits Multumiri GPL License Licenta generala publica Close Inchide Show data folder Arata dosarul cu datele About OSCAR %1 Despre OSCAR %1 Sorry, could not locate About file. Imi pare rau, nu am gasit fisierul Despre. Sorry, could not locate Credits file. Imi pare rau, nu am gasit fisierul Multumiri. Sorry, could not locate Release Notes. Imi pare rau, nu am gasit fisierul Note despre actualizare. OSCAR %1 OSCAR %1 As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Deoarece aceasta este o versiune de test, este recomandat să <b>salvați manual dosarul Data intr-un dosar backup</b> înainte de a continua, deoarece revenirea ulterioară la versiunea Oscar anterioară ar putea să deterioreze datele dvs. Important: Important: To see if the license text is available in your language, see %1. Pentru a vedea daca exista o licenta in limba dvs, vedeti %1. CMS50F37Loader Could not find the oximeter file: Nu am gasit fisierul cu oximetria: Could not open the oximeter file: Nu am putut deschide fisierul cu oximetria: CMS50Loader Could not get data transmission from oximeter. Nu am putut transfera datele din pulsoximetru. Please ensure you select 'upload' from the oximeter devices menu. Asigurati-va mai intai ca ati selectat 'upload' din meniul pulsoximetrului. Could not find the oximeter file: Nu am gasit fisierul cu oximetria: Could not open the oximeter file: Nu am putut deschide fisierul cu oximetria: CheckUpdates Checking for newer OSCAR versions Caut versiuni mai noi ale programului OSCAR Daily Go to the previous day Mergi la ziua precedenta Show or hide the calender Arata sau ascunde calendarul Go to the next day Mergi la ziua urmatoare Go to the most recent day with data records Mergi la cea mai recenta zi care are date inregistrate Events Evenimente View Size /Dimensiunea ferestrei de vizualizare Vezi dimensiunea Notes Note Journal Jurnal B B u u Color Culoare Small Mic Medium Mediu Big Mare Zombie Zombie I'm feeling ... Ma simt ... Weight Greutate If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Dacă înălțimea este mai mare decât zero la Preferințe, dacă introduceți înălțimea dvs aici se va afișa Indicele de Masă Corporală (IMC) Awesome Super B.M.I. I.M.C. Bookmarks Semne de carte Add Bookmark Adauga Semn de carte (Bookmark) Starts Porniri Remove Bookmark Elimina semn de carte Flags Atentionari Graphs Grafice Show/hide available graphs. Arata/ascunde graficele disponibile. Breakdown Detaliere events evenimente UF1 UF1 UF2 UF2 Time at Pressure Timp la Presiunea No %1 events are recorded this day Niciun eveniment %1 nu a fost inregistrat in aceasta zi %1 event %1 eveniment %1 events %1 evenimente Session Start Times Inceputul Sesiunii Session End Times Sfârsitul Sesiunii Session Information INFORMATII DESPRE SESIUNE Oximetry Sessions Sesiuni pulsoximetrie Duration Durata (Mode and Pressure settings missing; yesterday's shown.) (Lipsesc setarile Mod si Presiune; se afiseaza ziua de ieri) This bookmark is in a currently disabled area.. Acest semn de carte este într-o zonă momentan inactivă.. CPAP Sessions Sesiuni CPAP Details Detalii Sleep Stage Sessions Inregistrari ale Etapelor de Somn Position Sensor Sessions /Pozitionati Sesiunile Senzorului Inregistrari ale senzorului de pozitie Unknown Session Sesiune necunoscuta Machine Settings SETĂRI APARAT Model %1 - %2 Model %1 - %2 PAP Mode: %1 Mod PAP: %1 99.5% 99.5% This day just contains summary data, only limited information is available. Aceasta zi contine doar date sumare, datele disponibile sunt limitate. Total ramp time Timp total in rampă Time outside of ramp Timp după rampă Start Start End Sfârsit Unable to display Pie Chart on this system Nu pot afisa graficul PieChart pe acest sistem 10 of 10 Event Types 10 din 10 tipuri de evenimente This CPAP machine does NOT record detailed data Acest aparat CPAP nu inregistreaza date detaliate Sorry, this machine only provides compliance data. Regret, acest aparat CPAP furnizeaza doar date despre complianta (adica in ce masura respectati indicatiile medicului inregistrate in aparat). "Nothing's here!" "Nu e nimic aici!" No data is available for this day. Nu exista date pentru aceasta zi. 10 of 10 Graphs 10 din 10 Grafice Oximeter Information Informatii Pulsoximetru Click to %1 this session. Click pentru a %1 acesta sesiune. disable dezactiveaza enable activeaza %1 Session #%2 %1 Sesiune #%2 %1h %2m %3s %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Atenție:</b> Toate setările de mai jos se bazează pe presupunerea că nu s-a schimbat nimic față de zilele precedente. SpO2 Desaturations Desaturări SpO2 Pulse Change events Evenimente ale Pulsului SpO2 Baseline Used Saturatie SpO2 de bază Statistics STATISTICI Total time in apnea Timp in apnee Time over leak redline Timp cu scăpări Event Breakdown DETALIERE EVENIMENTE Sessions all off! Toate Sesiunile dezactivate! Sessions exist for this day but are switched off. Eexista Sesiuni in aceasta zi dar afisarea lor e dezactivata. Impossibly short session Sesiune mult prea scurta Zero hours?? Zero ore?? BRICK :( BRICK! :( - Acest parat nu inregistreaza date utile accesibile Complain to your Equipment Provider! Reclamati aceasta furnizorului dvs de CPAP! Pick a Colour Alegeti o culoare Bookmark at %1 Semne de carte la %1 ExportCSV Export as CSV Exporta ca fisier CSV Dates: Datele: Resolution: Concluzie: Details Detalii Sessions Sesiuni Daily Zilnic Filename: Nume fisier: Cancel Anuleaza Export Exportă Start: Start: End: Sfârșit: Quick Range: Interval rapid: Most Recent Day Cea mai recentă zi Last Week Saptamana trecuta Last Fortnight /Ultimele 14 zile Ultimele doua saptamani Last Month Ultima luna Last 6 Months Ultimele 6 luni Last Year Anul trecut Everything Totul Custom Personalizat Details_ Detalii_ Sessions_ Sesiuni_ Summary_ Sumar_ Select file to export to Selectati fisierul in care sa exportez CSV Files (*.csv) CSV Files (*.csv) DateTime Data Session Sesiune Event Eveniment Data/Duration Date/Durata Date Data Session Count Numar Sesiune Start Start End Sfârsit Total Time Timp Total AHI AHI Count Număr FPIconLoader Import Error Eroare la Importare This Machine Record cannot be imported in this profile. Datele din acest aparat nu pot fi importate in acest profil. The Day records overlap with already existing content. Datele din aceasta zi se suprapun cu cele deja existente. Help Hide this message Ascunde acest mesaj Search Topic: Cauta subiect: Help Files are not yet available for %1 and will display in %2. Fisierele de Help (ajutor) nu sunt inca disponibile pentru %1 si vor fi afisate in %2. Help files do not appear to be present. Fisierele Help (Ajutor) nu par sa fie prezente. HelpEngine did not set up correctly HelpEngine (subrutina de ajutor) nu este setata corect HelpEngine could not register documentation correctly. HelpEngine (subrutina de ajutor) nu a putut deschide corect documentatia. Contents Conținut Index Index Search Caută No documentation available Nu exista documentatie disponibila Please wait a bit.. Indexing still in progress Va rog asteptati putin.. Indexarea este in curs de finalizare No Nu %1 result(s) for "%2" %1 resultat(e) pentru "%2" clear curata MD300W1Loader Could not find the oximeter file: Nu am gasit fisierul cu oximetria: Could not open the oximeter file: Nu am putut deschide fisierul cu oximetria: MainWindow &Statistics &Statistici Report Mode Modul de Raportare Standard Standard Monthly Lunar Date Range Interval Date Statistics Statistici Daily Zilnic Overview Imagine de ansamblu Oximetry Pulsoximetrie Import Importa Help Ajutor &File &File &View &Vizualizare &Reset Graphs &Resetează Graficele &Help &Ajutor Troubleshooting Depanare &Data &Date &Advanced &Avansat Purge ALL Machine Data Only somnography data, or the profiles too? Șterge TOATE datele înregistrate Rebuild CPAP Data Reconstruieste Datele CPAP &Import CPAP Card Data &Importă datele CPAP din Card Show Daily view Arată Vizualizarea Zilnică Show Overview view Arată Vederea de ansamblu &Maximize Toggle &Maximizeaza fereastra Maximize window Mărește fereastra Reset Graph &Heights Restabilește înălțimea graficelor&H Reset sizes of graphs Resetează înălțimea graficelor Show Right Sidebar Arată Bara laterală Dreaptă Show Statistics view Arată vizualizare Statistici Import &Dreem Data Importă datele &Dreem Purge Current Selected Day Sterge ziua curenta selectata &CPAP &CPAP &Oximetry &Oximetrie &Sleep Stage Stadiu &Somn &Position &Pozitie &All except Notes Toate cu excepti&A Notes All including &Notes Toate inclusiv &Notes Show &Line Cursor Arată Cursorul &Linie Show Daily Left Sidebar Arată Bara Laterală Stângă Zilnică Show Daily Calendar Arată Calendarul Zilnic Create zip of CPAP data card Crează arhiva ZIP cu datele din card Create zip of OSCAR diagnostic logs Creaza arhiva ZIP cu log-urile de diagnostic ale OSCAR Create zip of all OSCAR data Crează arhiva ZIP cu toate datele OSCAR Report an Issue Raporteaza o problema System Information Informații sistem Show &Pie Chart Arată Graficul &Plăcintă Show Pie Chart on Daily page Arată Graficul Plăcintă pe pagina Vizualizare Zilnică Standard graph order, good for CPAP, APAP, Bi-Level Ordine grafice standard, bun pentru CPAP, APAP, Bi-Level Advanced Avansat Advanced graph order, good for ASV, AVAPS Ordine avansată a graficelor, bun pentru ASV, AVAPS Show Personal Data Arata date personale Check For &Updates Cautare &Updates &Preferences &Preferinte &Profiles &Profile &About OSCAR &Despre OSCAR Show Performance Information Arata informatii despre performanta CSV Export Wizard CSV Export semiautomat Export for Review Exporta pentru evaluare E&xit E&xit Exit Exit View &Daily Vizualizare &Zilnica View &Overview Vizualizare de &Ansamblu View &Welcome Vizualizare &Prima pagina Use &AntiAliasing Folositi &AntiAliasing Show Debug Pane Aratati panelul de depanare Take &Screenshot Capturati &Ecran O&ximetry Wizard Pulso&ximetrie semiautomata Print &Report Tipareste &Raportul &Edit Profile &Editeaza Profil Import &Viatom/Wellue Data Importa Date din aparatul &Viatom/Wellue Daily Calendar Calendar Zilnic Backup &Journal Backup &Jurnal Online Users &Guide &Ghid utilizator Online (EN) &Frequently Asked Questions &Intrebari frecvente &Automatic Oximetry Cleanup &Curatare automata pusoximetrie Change &User Schimba &Utilizator Purge &Current Selected Day Elimina Ziua &Curenta selectata Right &Sidebar Bara de unelte din &dreapta Daily Sidebar Bara de activitati Zilnica View S&tatistics Vizualizare S&tatistici Navigation Navigare Bookmarks Semne de carte Records Inregistrari Exp&ort Data Exp&orta Date Profiles Profile Purge Oximetry Data Elimina Datele de Pulsoximetrie View Statistics Vizualizare Statistici Import &ZEO Data Importa Date &ZEO Import RemStar &MSeries Data Importa Date din RemStar &MSeries Sleep Disorder Terms &Glossary &Glosar de termeni despre Apneea de somn Change &Language Schimba &Limba Change &Data Folder Schimba &Data Folder Import &Somnopose Data Importa Date din &Somnopose Current Days Zilele curente Welcome Bun venit &About &Despre Please wait, importing from backup folder(s)... Va rog asteptati, import date din backup... Import Problem Problema la importare Please insert your CPAP data card... Introduceti cardul cu datele dvs CPAP (vedeti sa fie blocat: Read-Only!)... Access to Import has been blocked while recalculations are in progress. Importul a fost dezactivat cat timp are loc reanaliza datelor. CPAP Data Located Date CPAP localizate Import Reminder Reamintire Importare Find your CPAP data card Gasiti-va cardul de date CPAP Importing Data Importez Datele The User's Guide will open in your default browser Ghidul de utilizare se va deschide in browser-ul dvs de internet The FAQ is not yet implemented Sectiune Intrebari frecvente nu este inca implementata If you can read this, the restart command didn't work. You will have to do it yourself manually. Daca puteti citi asta, inseamna ca nu a functionat repornirea. Va trebui sa reporniti manual. Are you sure you want to rebuild all CPAP data for the following machine: Sunteti sigur ca doriti sa reconstruiti toate datele CPAP pentru aparatul urmator: For some reason, OSCAR does not have any backups for the following machine: Dintr-un oarecare motiv OSCAR nu are un backup pentru aparatul: You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Sunteti pe cale sa <font size=+2>eliminati</font> baza de date a OSCAR pentru acest aparat CPAP:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: O eroare de permisiuni ale fisierelor a sabotat procesul de curatire; va trebui sa stergeti manual acest dosar: No help is available. Nu exista Help (Ajutor) disponibil. There was a problem opening %1 Data File: %2 A aparut o problema la deschiderea %1 fisier date: %2 %1 Data Import of %2 file(s) complete %1 Import Date din %2 fisier(e) complet %1 Import Partial Success %1 Import Partial Succes %1 Data Import complete %1 Import Date complet %1's Journal Jurnalul lui %1 Choose where to save journal Alegeti unde salvez jurnalul XML Files (*.xml) XML Files (*.xml) Export review is not yet implemented Exportul sumarului nu este inca implementat Would you like to zip this card? Doriți să arhivați acest card într-o arhivă ZIP? Choose where to save zip Unde să salvez arhiva ZIP ZIP files (*.zip) Fișier arhivă ZIP (*.zip) Creating zip... Crează arhiva ZIP... Calculating size... Calculez dimensiunea... Reporting issues is not yet implemented Raportarea problemelor online nu este inca implementata Help Browser Vizualizare Help (Ajutor) Please open a profile first. Va rugam deschideti mai intai un Profil pacient. Check for updates not implemented Cautarea actualizarilor nu e implementata Choose where to save screenshot Unde să salvez captura ecranului Image files (*.png) Fisier Imagine (*.png) Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Daca v-ati facut <i>propriile <b>backup-uri</b> la toate datele CPAP</i>, puteti finaliza aceasta operatiune, dar va trebui sa le restaurati la nevoie din backup manual. Are you really sure you want to do this? Sunteti sigur ca doriti ca asta doriti sa faceti? Because there are no internal backups to rebuild from, you will have to restore from your own. Deoarece nu exista backup-uri interne pentru a reface datele, va trebui sa faceti restaurarea manuala a lor. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Doriti sa importati din backup-ul dvs? (nu sunt Date pentru acest aparat CPAP pana nu faceti acest lucru) Note as a precaution, the backup folder will be left in place. Nota: Ca precautie, dosarul de backup va fi lasat la locul lui. OSCAR does not have any backups for this machine! OSCAR nu are backup-uri pentru acest aparat! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Cu excepția cazului în care ai făcut <i> <b> propriile tale copii de rezervă</b> pentru toate-TOATE datele tale pentru acast aparat </i>, <font size = + 2> vei pierde datele acestui aparat <b> permanent </b> >! </ font> Are you <b>absolutely sure</b> you want to proceed? Sunteti <b>absolut sigur</b> ca doriti ca continuati? The Glossary will open in your default browser Glosarul de termeni se va deschide in browser-ul dvs de internet Are you sure you want to delete oximetry data for %1 Sunteti sigur ca vreti sa stergeti datele pulsoximetriei pentru %1 <b>Please be aware you can not undo this operation!</b> <b>Atentie, nu veti mai putea reveni asupra acestei operatiuni !!</b> Select the day with valid oximetry data in daily view first. Selectati mai intai ziua cu Date valide de Pulsoximetrie in Fereasta de vizualizare a zilei. OSCAR Information Infoemații OSCAR Loading profile "%1" Incarc profilul "%1" %1 (Profile: %2) %1 (Pacient: %2) Imported %1 CPAP session(s) from %2 Am Importat %1 sesiuni CPAP din %2 Import Success Importul s-a finalizat cu succes Already up to date with CPAP data at %1 Datele actualizate deja cu aparatul CPAP la %1 Up to date La zi Couldn't find any valid Machine Data at %1 Nu am gasit date valide la %1 Choose a folder Alegeti un dosar No profile has been selected for Import. Nu a fost selectat niciun Profil pacient pentru Import. Import is already running in the background. Importarea ruleaza inca in fundal. A %1 file structure for a %2 was located at: O structura %1 a fisierului pentru %2 a fost localizata la: A %1 file structure was located at: Un fisier %1 a fost localizat la: Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Alegeți corect sursa, dosarul rădăcină al cardului (sau litera de ex E:\) și NU un alt dosar din card. Would you like to import from this location? Doriti sa importati din aceasta locatie? Specify Specificati Access to Preferences has been blocked until recalculation completes. Preferintele au fost dezactivate cat timp are loc reanaliza datelor. There was an error saving screenshot to file "%1" A aparut o problema la salvarea capturii in fisierul "%1" Screenshot saved to file "%1" Captura ecran salvata in fisierul "%1" Please note, that this could result in loss of data if OSCAR's backups have been disabled. Atentie, puteti pierde datele daca backup-ul OSCAR a fost dezactivat. There was a problem opening MSeries block File: A aparut o problema la deschiderea fisierului din aparatul MSeries: MSeries Import complete Importul finalizat din aparatul MSeries MinMaxWidget Auto-Fit Potrivire automata (Auto-Fit) Defaults Setari initiale Override Suprascrie The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Reglarea pe axa Y, 'Auto-Fit pentru potrivire automata in ecran, 'Setari initiale' pentru setarile initiale a le OSCAR, si 'Override' ca sa alegeti alte setari, proprii. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Valoarea maxima pe axa Y.. poate fi un numar negativ daca doriti. The Maximum Y-Axis value.. Must be greater than Minimum to work. Valoarea maxima pe axa Y.. trebuie sa fie mai mare decat Minimul. Scaling Mode Scaling Mode This button resets the Min and Max to match the Auto-Fit Acest buton reseteaza MIn si Max ca sa se potriveasca cu Auto-Fit NewProfile Edit User Profile Editeaza profil pacient I agree to all the conditions above. Accept si imi asum toate conditiile de mai sus. User Information Informatii utilizator User Name Numele utilizatorului Password Protect Profile Parola de protectie a Profilului pacientului Password Parola ...twice... ...de doua ori... Locale Settings Localizare (Locale) Country Tara TimeZone Zona orara about:blank about:blank Very weak password protection and not recommended if security is required. Parola e foarte slaba si nu e recomandata daca se doreste securizarea datelor. DST Zone DST Zone Personal Information (for reports) Informatii personale pentru Raportari First Name Prenume Last Name Nume It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Puteti sari peste astea, dar varsta este necesara pentru a imbunatati acuratetea unor calcule. D.O.B. Nascut pe. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Sexul biologic este necesar uneori pentru a imbunatati acuratetea unor calcule,puteti lasa liber daca doriti.</p></body></html> Gender Sex Male Barbat Female Femeie Height Inaltime Metric Metric English Engleză Contact Information Informatii Contact Address Adresa Email Email Phone Telefon CPAP Treatment Information Informatii tratament CPAP Date Diagnosed Data diagnostcului Untreated AHI AHI (apneea-hipopneea index) netratata CPAP Mode Mod CPAP CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure Presiuni recomandate de medic Doctors / Clinic Information Informatii Doctori / Clinica Doctors Name Numele doctorului Practice Name Numele clinicii Patient ID ID pacient &Cancel &Anuleaza &Back &Inapoi &Next &Urmatorul Select Country Alegeti tara Welcome to the Open Source CPAP Analysis Reporter Bun venit la Open Source CPAP Analysis Reporter This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Acest software este creat ca sa va asiste in vizualizarea datelor inregistrate de anumite aparate CPAP si echipamente inrudite. PLEASE READ CAREFULLY CITITI CU ATENTIE Accuracy of any data displayed is not and can not be guaranteed. Acuratetea datelor afisate nu poate fi garantata. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Orice rapoarte generate sunt pentru UZ PERSONAL si NICINTR-UN CAZ nu sunt potrivite pentru complianta sau pentru vreun diagnostic medical. Use of this software is entirely at your own risk. Utilizati acest software pe propriul dvs risc. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR este copyright &copy;2011-2018 Mark Watkins si module &copy;2019-2022 The OSCAR Team OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR este gratuit sub licenta <a href='qrc:/COPYING'>GNU Public License v3</a>, si nu are nicio garantie in ceea ce priveste scopul sau acuratetea informatiilor. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR este destinat doar a fi un vizualizator de date, și cu siguranță nu este un substitut pentru îndrumarea medicală competentă de la medicul dumneavoastră. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Autorii nu vor fi responsabili de <u>nimic</u>in legatura cu utilizarea sau neutilizarea acestui software. Asta e free GPU license. Please provide a username for this profile Va rugam furnizati numele de utilizator pentru acest profil Passwords don't match Parola introdusa nu e identica Profile Changes Schimbări profil Accept and save this information? Accepta si salveaza aceste informatii? &Finish &Finalizeaza &Close this window &Inchide aceasta fereastra Overview Range: Interval: Last Week Ultima saptamana Last Two Weeks Ultimele doua saptamani Last Month Ultima luna Last Two Months Ultimele doua luni Last Three Months Ultimele trei luni Last 6 Months Ultimele 6 luni Last Year Ultimul an Everything Tot Custom Particularizat Snapshot Captura Ecran Start: Start: End: Sfârsit: Reset view to selected date range Resetati vizualizarea la intervalul de timp selectat Toggle Graph Visibility Modifica vizibilitatea graficelor Drop down to see list of graphs to switch on/off. Alegeti graficele pe care doriti sa le dez/activati. Graphs Grafice Respiratory Disturbance Index Pe engleza RespDisturbanceIndex Indicele de Afectare Respiratorie (IAR) Apnea Hypopnea Index Apnoea Hypopnea Index Usage Utilizare Usage (hours) Utilizare (ore) Session Times Timp Sesiune Total Time in Apnea Timp Total in Apnee Total Time in Apnea (Minutes) Timp Total in Apnee (Minute) Body Mass Index Indice de Masa Corporala How you felt (0-10) Cum v-ati simtitt (0-10) 10 of 10 Charts Show all graphs Arata toate graficele Hide all graphs Ascunde toate graficele OximeterImport Oximeter Import Wizard Import semiautomat din Pulsoximetru Skip this page next time. Sari peste aceasta pagina data viitoare. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? De unde doriti sa importati? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. Utilizatorii de pulsoximetre CMS50E/F cand importati direct, nu confirmati pe ecranul pulsoximetrului pana cand OSCAR nu va spune sa faceti acest lucru. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Daca e bifat, OSACR va reseta automat ceasul pulsoximetrului Contec CMS50 folosind ora computerului.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>Aici puteți introduce un ume din 7 caractere pentru oximetru.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Această opțiune va șterge sesiunile importate din pulsoximetru la finalul descărcării. </p><p>Folosiți cu atenție, fiindcă în caz de eșec sau dacă OSCAR nu apucă să salveze sesiune, veți pierde datele.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Această opțiune permite importul (prin cablu USB) a datelor dintr-un oximetru cu memorie.</p><p>După selectarea opțiunii, pulsoximetrele vechi CONTEC vă vor cere să intrați în meniul aparatului pentru a începe descărcarea.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Daca nu va deranjeaza sa fiti agatat de un computer toata noaptea, acesta optiune creaza un grafic pletismografic util, care indica si ritmul cardiac, in afara de valorile oximetriei.</p></body></html> Record attached to computer overnight (provides plethysomogram) Inregistreaza atasat de computer toata noaptea (se obtine o pletismograma) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Importati datele dintr-un fisier creat pe computer de software-ul pulsoximetrului dvs cum ar fi SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Importa dintr-un fisier de date salvat de un alt program cum este SpO2Review Please connect your oximeter device Conectati pulsoximetrul If you can read this, you likely have your oximeter type set wrong in preferences. Daca cititi asta, cel mai probabil ati setat gresit tipul pulsoximetrului in preferinte. Please connect your oximeter device, turn it on, and enter the menu Conectați pulsoximetrul, porniți-l și intrați în meniul lui Press Start to commence recording Apasati START pentru a incepe inregistrarea Show Live Graphs Arata grafice in timp real Duration Durata Pulse Rate Pulsul Multiple Sessions Detected Au fost detectate mai multe sesiuni Start Time Timp de inceput Details Detalii Import Completed. When did the recording start? Importul s-a finalizat cu succes. Cand a inceput inregistrarea? Oximeter Starting time Ora inceperii oximetriei I want to use the time reported by my oximeter's built in clock. Vreau să folosesc ora raportată de ceasul intern al pulsoximetrului. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Am pornit aceasta sesiune de oximetrie la (sau aproape de) acelasi moment in care am pornit CPAP. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Atentie: sincronizarea sesiunii de oximetrie cu inceputul sesiunii CPAP va fi intotdeauna mai exacta.</p></body></html> Choose CPAP session to sync to: Alegeti sesiunea CPAP cu care sa fac sincronizarea oximetriei: You can manually adjust the time here if required: Puteti ajusta manual ora aici daca e nevoie: HH:mm:ssap HH:mm:ssap &Cancel &Anuleaza &Information Page Pagina cu &Informatii CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Setati data/timpul dispozitivului <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Setati pentru a activa identificatorul dispozitivului la urmatorul Import de date, util pentru cei care au mai multe pulsoximetre.</p></body></html> Set device identifier Setati identificatorul dispozitivului Erase session after successful upload Sterge sesiunea din aparat dupa ce a fost incarcata in OSCAR Import directly from a recording on a device Importa direct dintr-un dispozitiv <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Atentie utilizatorilor de CPAP: </span><span style=" color:#fb0000;">Ati importat mai intai sesiunile CPAP?<br/></span>Daca ati uitat, nu veti avea o linie temporala cu care sa va sincronizati inregistrarile pulsoximetrului, care nu are ceas propriu.<br/>Pentru a realiza sincronizarea intre dispozitive, incercati intotdeauna sa incepeti inregistrarile la aceeasi ora.</p></body></html> Please choose which one you want to import into OSCAR Alegeti fiserul pe care vreti sa-l importati in OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR are nevoie de o ora de inceput pentru a sti unde sa sincronizeze aceasta sesiune de oximetrie.</p><p>Alegeti din urmatoarele optiuni:</p></body></html> &Retry &Reincearca &Choose Session &Alege sesiune &End Recording &Finalul inregistrarii &Sync and Save &Sincronizeaza si Salveaza &Save and Finish &Salveaza si incheie &Start &Start Scanning for compatible oximeters Caut pulsoximetre compatibile Could not detect any connected oximeter devices. Nu am putut detecta niciun pulsoximetru conectat. Connecting to %1 Oximeter Conectare la pulsoximetrul %1 Renaming this oximeter from '%1' to '%2' Redenumesc acest pulsoximetru de la '%1' la '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Numele pulsoximetrului este diferit.. Daca aveti doar unul pe care il folositi in mai multe profile, setati acelasi nume in ambele profile OSCAR. "%1", session %2 "%1", sesiune %2 Nothing to import Nu e nimic de importat Your oximeter did not have any valid sessions. Pulsoximetrul dvs nu a salvat nicio sesiune valida de oximetrie. Close Inchide Waiting for %1 to start Astept sa porneasca %1 Waiting for the device to start the upload process... Astept ca dispozitivul sa porneasca procesul de incarcare a datelor... Select upload option on %1 Selectati optiunea de incarcare pe %1 You need to tell your oximeter to begin sending data to the computer. Din meniul pulsoximetrului, incepeti rrimiterea datelor catre computer. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Conectati pulsoximetrul, intrati in meniul lui si selectati "Upload" pentru a incepe transferul datelor... %1 device is uploading data... %1 incarca datele... Please wait until oximeter upload process completes. Do not unplug your oximeter. Asteptati pana cand se termina procesul de incarcare a datelor in OSCAR. Nu deconectati pulsoximetrul. Oximeter import completed.. Importul din pulsoximetru s-a finalizat cu succes. Select a valid oximetry data file Selectati un fisier de oximetrie compatibil Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Fisiere Oximetrie (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Niciun modul de oximetrie nu a putut descifra fisierul descarcat: Live Oximetry Mode Mod Oximetrie direct Live Oximetry Stopped Mod Oximetrie direct oprit Live Oximetry import has been stopped Importul direct al oximetriei fost oprit Oximeter Session %1 Sesiunea de oximetrie %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR va da posibilitatea sa urmariti datele oximetriei concomitent cu cele ale CPAP pentru a obtine informatii relevante despre eficienta tratamentului CPAP. Deasemenea, poate vizualiza si separat datele din Pulsoximetre, permitand inregistrarea si analiza ulterioara a datelor. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Daca incercati sa sincronizati datele din oximetrie si CPAP, asigurati-va ca ati importat INTAI datele din CPAP si abia apoi pe cele din Pulsoximetru! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Pentru ca OSCAR sa poata localiza si citi direct din pulsoximetrul dvs, asigurati-va ca aveti instalate driverele potrivite (ex. USB to Serial UART) pe computer. Pentru mai multe informatii %1 click aici %2. Oximeter not detected Nu ati selectat pulsoximetrul Couldn't access oximeter Nu am putut accesa pulsoximetrul Starting up... Pornesc... If you can still read this after a few seconds, cancel and try again Daca puteti inca citi asta dupa cateva secunde, anulati si incercati din nou Live Import Stopped Importul direct s-a oprit %1 session(s) on %2, starting at %3 %1 sesiune(i) pe %2, pornite la %3 No CPAP data available on %1 Nu exista date CPAP disponibile pe %1 Recording... Inregistrez... Finger not detected Degetul nu a fost detectat I want to use the time my computer recorded for this live oximetry session. Vreau sa folosesc ora computerului pentru aceasta sesiune de oximetrie. I need to set the time manually, because my oximeter doesn't have an internal clock. Pulsoximetrul meu nu are ceas intern, trebuie sa setez manual ora. Something went wrong getting session data Nu am reusit sa obtin datele acestei sesiuni Welcome to the Oximeter Import Wizard Bun venit la incarcarea semiautomata a oximetriei Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. PulsOximetrele sunt dispozitive medicale care masoara saturatia in oxigen a sangelui capilar. In timpul evenimentelor de apnee in somn si a respiratiei anormale, saturatia oxigenului din sange poate scadea semnificativ necesitand consultul unui medic. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR este momentan compatibil cu pulsoximetrele Contec CMS50D+, CMS50E, CMS50F si CMS50I seriale (USB).<br/>(Nota: Importul direct din modelele cu bluetooth <span style=" font-weight:600;">probabil nu este posibil inca</span>) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Poate vreti sa stiti, alte companii cum e Pulox, pur si simplu rebrand-uiesc pulsoximetrele Contec CMS50 sub alta denumire Pulox PO-200, PO-300, PO-400. Aceste aparate ar trebui sa fie compatibile cu OSCAR. It also can read from ChoiceMMed MD300W1 oximeter .dat files. OSCAR poate deasemenea descifra fisierele DAT ale pulsoximetrului ChoiceMMed MD300W1. Please remember: Amintiti-va: Important Notes: Iimportant: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Dispozitivul Contec CMS50D+ nu are un ceas intern si nu inregistreaza timpul. Daca nu aveti deja o sesiune CPAP descarcata cu care sa sincronizati inregistrarea din CMS50D+, va trebui sa introduceti manual ora de inceput dupa ce se finalizeaza importul datelor. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Chiar si la pulsoximetrele cu ceas intern, este recomandat sa incepeti sesiunea de oximetrie concomitent cu cea de CPAP, deoarece ceasul aparatelor CPAP are obiceiul sa ramana in urma cu timpul si nu toate pot fi resetate cu usurinta. Oximetry Date Data d/MM/yy h:mm:ss AP d/MM/yy h:mm:ss AP R&eset R&eset Pulse Puls &Open .spo/R File &Deschide .spo/R File Serial &Import Serial &Import &Start Live &Start monitorizare in timp real Serial Port Serial Port &Rescan Ports &Rescaneaza porturile PreferencesDialog Preferences Preferinte &Import &Importa Combine Close Sessions Combina sesiunile inchise Minutes Minute Multiple sessions closer together than this value will be kept on the same day. Mai multe sesiuni mai strânse decât această valoare vor fi păstrate în aceeași zi. Ignore Short Sessions Ignora sesiunile scurte <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sesiunie cu durata mai scurta de atat nu vor fi afisate<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Timpul de impartire a zilei Sessions starting before this time will go to the previous calendar day. Sesiunile care incep inainte de acest moment vor fi asociate zilei calendaristice precedente. Session Storage Options Setari stocare sesiune Compress SD Card Backups (slower first import, but makes backups smaller) Comprima Backup-urile SD (mai lent la primul import, dar face Backup-urile mai mici) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Considera zilele sub acest nivel ca necompliante. 4 ore de obicei e considerat complianta. hours ore Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Activați / dezactivați îmbunătățirile de înregistrare a evenimentelor experimentale. Permite detectarea evenimentelor echivoce, iar unele apaarate au ratat. Această opțiune trebuie activată înainte de importare, altfel este necesară o curățare. Flow Restriction Restrictie a fluxului de aer Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Procentul de restricție în fluxul de aer de la valoarea medie. O valoare de 20% ajuta la detectarea apneei. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Selectarea manuala este o metoda experimentala de a tetecta manual evenimente ratate de catre aparat. Ele <span style=" text-decoration: underline;">nu sunt incluse</span> in AHI.</p></body></html> Duration of airflow restriction Durata restrictiei fluxului de aer s s Event Duration Durata eveniment Allow duplicates near machine events. Permiteti duplicate langa evenimentele detectate de aparat. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Reglează cantitatea de date luată în considerare pentru fiecare punct din graficul AHI / Ora. Implicit la 60 de minute .. Vă recomandăm foarte mult să rămână la această valoare. minutes minute Reset the counter to zero at beginning of each (time) window. Reseteaza counterul la zero la inceputul fiecarei ferestre de timp. Zero Reset Reset la zero CPAP Clock Drift Intarzierea ceasului CPAP Do not import sessions older than: Nu importa sesiunile mai vechi de: Sessions older than this date will not be imported Sesiunile mai vechi decat aceasta data nu vor fi importate dd MMMM yyyy dd MMMM yyyy User definable threshold considered large leak Utilizare prag configurabil pentru scăpare mare pe lângă mască Whether to show the leak redline in the leak graph Daca arată sau nu linia rosie în graficul scăpărilor din mască Search Cautare &Oximetry &Oximetrie Show in Event Breakdown Piechart Afișați in diagrama grafică a evenimentelor Resync Machine Detected Events (Experimental) Resincronizeaza Evenimentele detectate de aparat (Experimental) Percentage drop in oxygen saturation Procent reducere in saturatia oxigenului Pulse Puls Sudden change in Pulse Rate of at least this amount Schimbari bruste n Puls cel putin in acest grad bpm bpm Minimum duration of drop in oxygen saturation Scaderea oxigenului cu Durata minima Minimum duration of pulse change event. Eveniment de schimbare a pulsului cu durata minima. Small chunks of oximetry data under this amount will be discarded. Mici fragmente de date oximetrice sub acest nivel se vor pierde. &General &General Changes to the following settings needs a restart, but not a recalc. Schimbările următoarelor setari necesită repornirea programului dar nu si recalcularea. Preferred Calculation Methods Metode de calcul preferate Middle Calculations Calcul Medii Upper Percentile Percentila superioara Session Splitting Settings Setari impartire sesiune <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Aceste setari trebuie utilizate cu grija...</span>Dezactivarea are consecințe care implică acuratețea rezumatului zilnic, deoarece anumite calcule funcționează în mod corespunzător doar daca sesiunile rezumate care au provenit din înregistrările individuale sunt păstrate împreună. </p><p><span style=" font-weight:600;">Utilizatorii ResMed:</span> Doar pentru că ni se pare natural să înceapă sesiunea la ora 12 în ziua precedentă, nu înseamnă că datele ResMed sunt de acord cu noi. Formatul indexului rezumat STF.edf prezintă slăbiciuni grave care fac ca aceasta să nu fie o idee bună. .</p><p>Această opțiune există pentru a-i mulțumi pe cei cărora nu le pasă și doresc să vadă treaba &quot;oablă&quot; indiferent de costuri, dar știu că vine cu un cost. Dacă țineți cardul SD în fiecare noapte și importați cel puțin o dată pe săptămână, nu veți avea probleme cu acest lucru foarte des.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Nu imparti sumarele zilelor (Atentie: cititi Tooltip-ul!) Memory and Startup Options Optiuni memorie si pornire Pre-Load all summary data at startup Incarca de la inceput toate rezumatele la lansare <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Această setare păstrează datele și evenimente din memorie după utilizare pentru a accelera vizualizarea zilelor precedente.</p><p>Aceasta nu este cu adevărat o opțiune necesară, deoarece sistemul de operare cache conține și fișierele utilizate anterior..</p><p>Recomandarea este lăsarea acesteia oprită, cu excepția cazului în care computerul dvs are o tona de memorie..</p></body></html> Keep Waveform/Event data in memory Mentine in memorie graficele <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Elimina confirmarile inutile in timpul Importului.</p></body></html> Import without asking for confirmation Importa fara sa ceri confirmare This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Acest calcul despre scăpările totale necesită date de la aparatul CPAP. (De exemplu PRS1, dar nu si ResMed, care tine deja evidenta) Calculele de scurgeri neintenționate utilizate aici sunt liniare, nu influienteaza curba de ventilare a măștii. Dacă utilizați câteva măști diferite, alegeți în schimb valorile medii. Ar trebui să fie destul de aproape de adevar. Calculate Unintentional Leaks When Not Present Calculeaza scurgerile neintentionate cand nu sunt prezente Note: A linear calculation method is used. Changing these values requires a recalculation. Notă: Este utilizată o metodă de calcul liniar. Modificarea acestor valori necesită o recalculare. General CPAP and Related Settings Setari Generale CPAP si inrudite Enable Unknown Events Channels Activeaza graficul de evenimente necunoscute (UA) AHI Apnea Hypopnea Index Apnee Hipopnee Index AHI (Apnea Hypopnea Index) RDI Respiratory Disturbance Index Indice de afectare respiratorie (RespirationDisturbanceIndex) IAR (RDI, Indice Afectare Respiratorie) AHI/Hour Graph Time Window Graficul AHI/ora in Timp Preferred major event index Index de evenimente majore preferat Indexul de evenimente majore preferat Compliance defined as Complianța minimă Flag leaks over threshold Atenționare la scăpări peste limita de Seconds Secunde <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Nota: Acest lucru nu este destinat corecțiilor de fus orar! Asigurați-vă că ceasul și fusul orar al sistemului de operare sunt setate corect.</p></body></html> Hours Ore For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Pentru consistență, utilizatorii ResMed ar trebui să folosească 95% aici, deoarece aceasta este singura valoare disponibilă în zilele rezumate. Median is recommended for ResMed users. Media e recomandata pentru utilizatorii de ResMed. Median Media Weighted Average Media ponderata Normal Average Medie normala True Maximum ADEVARATUL mAXIM 99% Percentile Percentila 99% Maximum Calcs Calcul Maxime General Settings Setari Generale Daily view navigation buttons will skip over days without data records Butoanele de navigare Vedere zilnica sar peste zile fara inregistrarile de date Skip over Empty Days Sari peste zilele fara inregistrari Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Permiteți utilizarea mai multor nuclee de CPU acolo unde acestea sunt disponibile pentru a îmbunătăți performanța. Afectează în principal importatorul. Enable Multithreading Activeaza Multithreading Bypass the login screen and load the most recent User Profile Sariti peste ecranul de conectare si incarcati cel mai recent utilizat Profil de pacient Create SD Card Backups during Import (Turn this off at your own peril!) Creaza un backup la SDcard in timpul Importului (daca dezactivati asta o patiti!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Adevartul maxim este maximul setului de date.</p><p>Percentila 99th filtreaza valorile discrepante.</p></body></html> Combined Count divided by Total Hours Numărătoare combinată împărțită la Total ore Time Weighted average of Indice Media ponderată a indicelui Standard average of indice Media standard a indiccilor Custom CPAP User Event Flagging Evenimente CPAP semnalate de utilizator Events Evenimente Reset &Defaults Resetare la &valorile initiale <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Atentie: </span>Doar pentru ca puteti, nu inseamna si ca e o idee buna sa faceti modificari aici.</p></body></html> Waveforms Variatii grafice Flag rapid changes in oximetry stats Atenționare variații rapide în oximetrie Other oximetry options Alte setari Oximetrie Discard segments under Elimina segmentele sub Flag Pulse Rate Above Atenționare Puls peste limita admisă Flag Pulse Rate Below Atenționare Puls sub limita admisă Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Comprimați copiile de rezervă ResMed (EDF) pentru a economisi spațiu pe disc. Fișierele EDF blocate sunt arhivate format .gz, care este comun și pe platformele Mac & Linux .. OSCAR poate importa nativ din acest director backup comprimat. Pentru a-l utiliza ulterior cu ResScan va fi necesar ca fișierele .gz să fie întâi decomprimate. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Următoarele opțiuni afectează spațiul pe disc pe care OSCAR îl utilizează și influiențează durata importului datelor. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Acest lucru face ca datele OSCAR să ocupe mai puțin spațiu. Dar face ca importul și schimbarea zilei să dureze mai mult .. Dacă aveți un computer rapid cu un SSD mic, aceasta este o opțiune bună. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Comprima Datele Sesiunii (face datele OSCAR mai mici, dar schimbarea zilei mai lenta) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Aceasta menține o copie de rezervă a datelor de pe cardul SD pentru aparatele ResMed, Aparatele ResMed S9 pot șterge date de înaltă rezoluție mai vechi de 7 zile, și date grafice mai vechi de 30 de zile .. OSCAR poate păstra o copie a acestor date dacă intenționați să reinstalați programul. (Foarte recomandat să faceți backup, cu excepția cazului când nu e spațiu pe disc sau nu vă pasă de datele respective) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Face ca OSCAR să pornească un pic mai lent, dar accelerează răsfoirea generală și câteva alte calcule ulterioare. </ p> <p> Dacă aveți o cantitate mare de date, s-ar putea să merite să lăsați această opțiune dezactivată, dar dacă doriți să vedeți <span style=" font-style:italic;">totul</span> in Vedere de ansamblu, toate datele de sinteză trebuie încărcate oricum. </p><p>Rețineți că această setare nu afectează graficele și de evenimentele, care sunt întotdeauna încărcate după cum este necesar.</p></body></html> 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Această opțiune experimentală încearcă să utilizeze sistemul de semnalizare a evenimentelor OSCAR pentru a îmbunătăți poziționarea evenimentului detectat de aparat. Show flags for machine detected events that haven't been identified yet. Afișați avertizari pentru evenimente detectate de aparat care nu au fost încă identificate. Show Remove Card reminder notification on OSCAR shutdown Afișați notificarea "Scoateti Cardul la oprirea OSCAR" si il deblocati pentru scriere inainte de a-l introduce in aparatul ResMed Check for new version every Verifica daca exista o noua versiune la fiecare days. zile. Last Checked For Updates: Ultia cautare de actualizari: TextLabel Eticheta Text I want to be notified of test versions. (Advanced users only please.) Vreau să fiu notificat cu privire la versiunile de testare. (Numai pentru utilizatorii avansați, vă rog.) &Appearance &Aspect Graph Settings Setari Grafic <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Ce filă se deschide la încărcarea unui profil pacient. (Notă: implicit va fi pagina de Profil dacă OSCAR este setat să nu deschidă un profil la pornire)</p></body></html> Bar Tops Barele de sus Line Chart Linia Graficului Overview Linecharts Vedere de ansamblu Grafice liniare Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Încercați să modificați această setare din setarea implicită (Desktop OpenGL) dacă întâmpinați probleme de redare cu graficele OSCAR. <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Aceasta face derularea mai ușoara pe TouchPad-urile bidirecționale sensibile atunci când imaginea este mărită</p><p>50ms este valoarea recomandata.</p></body></html> How long you want the tooltips to stay visible. Cat vreti sa ramana vizibile Tootips. Scroll Dampening Amortizare derulare Tooltip Timeout Timp inchidere Tooltip Default display height of graphs in pixels Înălțimea de afișare implicită a graficelor în pixeli Graph Tooltips Tooltips pe Grafic The visual method of displaying waveform overlay flags. Metoda vizuala de afisare acundelor e suprapune peste atentionari. Standard Bars Bare de unelte standard Top Markers Producatori de top Graph Height Inaltime Grafic Changing SD Backup compression options doesn't automatically recompress backup data. Modificarea opțiunilor de compresie SD Backup nu recomprimă automat datele de rezervă. Auto-Launch CPAP Importer after opening profile Lanseaza automat Importatorul de date CPAP dupa incarcarea Profilului pacientului Automatically load last used profile on start-up Incarca automat la pornire ultimul Pacient utilizat <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body> <p>Arată o alertă la importul de date de pe orice model de aparat care încă nu a fost testat de dezvoltatorii OSCAR. </p> </body> </html> Warn when importing data from an untested machine Avertizează atunci când importați datele dintr-un aparat netestat încă cu OSCAR <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body> <p> Arată o alertă atunci când importați date care sunt cumva diferite de orice au văzut anterior dezvoltatorii OSCAR. </p> </body> </html> Warn when previously unseen data is encountered Avertizează atunci când importați date nemaiîntâlnite încă de OSCAR Your masks vent rate at 20 cmH2O pressure Scăpările din masca dvs sunt la presiunea de 20 cmH2O Your masks vent rate at 4 cmH2O pressure Scăpările din masca dvs sunt la presiunea de 4 cmH2O <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Nota: </span>Din cauza limitarilor rezumatului, aparatele ResMed nu permit schimbarea acestor setari.</p></body></html> Oximetry Settings Setari Oximetrie Always save screenshots in the OSCAR Data folder Salvează întotdeauna capturile de ecran în dosarul OSCAR Data Check For Updates Cauta actualizari You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Folosiți o versiune de testare a OSCAR. Versiunile de testare verifică automat dacă există actualizări cel puțin o dată la șapte zile. Puteți seta intervalul la mai puțin de șapte zile. Automatically check for updates Cauta actualizari OSCAR automat How often OSCAR should check for updates. Frecvența cu care OSCAR trebuie să verifice dacă există actualizări. If you are interested in helping test new features and bugfixes early, click here. Dacă sunteți interesat să ajutați la testarea timpurie a noilor caracteristici și a corecțiilor de erori, faceți clic aici. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Dacă doriți să ajutați la testarea versiunilor timpurii ale OSCAR, vă rugăm să consultați pagina Wiki despre testarea OSCAR. Îi primim cu plăcere pe toți cei care doresc să testeze OSCAR, să contribuie la dezvoltarea OSCAR și să ajute la traducerile în limbi existente sau noi. https://www.sleepfiles.com/OSCAR On Opening La Deschidere Profile Profil Welcome Bun venit Daily Zilnic Statistics Statistici Switch Tabs Schimba ferestrele No change Nicio schimbare After Import Dupa Import Overlay Flags Atentionari suprapuse Line Thickness Grosimea Liniei The pixel thickness of line plots Grosimea pixelului din linii Other Visual Settings Alte setari Vizuale Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Anti-Aliasing aplică netezirea graficelor. Anumite parcele arata mai atragatoare cu asta. Acest lucru afectează de asemenea rapoartele tipărite. Încearcă și vezi dacă îți place. Use Anti-Aliasing Utilizare Anti-Aliasing Makes certain plots look more "square waved". Face anumite puncte grafice sa arate mai ca "undele patrate". Square Wave Plots Unde patrate Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Pixmap caching este o tehnică de accelerare grafică. Poate provoca probleme cu desenarea fontului în zona de afișare a graficelor de pe platforma dvs. Use Pixmap Caching Utilizare Pixmap Caching <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Aceste caracteristici au fost recent eliminate. Elevor fi reintroduse mai târziu. </p></body></html> Animations && Fancy Stuff Animatii && chestii Fancy Whether to allow changing yAxis scales by double clicking on yAxis labels Daca să permități modificarea scalei axei y prin dublu clic pe etichetele axei x Allow YAxis Scaling Permite scalarea pe axa Y Graphics Engine (Requires Restart) Graphics Engine (necesita Restart) l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Whether to include machine serial number on machine settings changes report Iincludeți sau nu numărul de serie al aparatului în raportul cu modificările setărilor aparatului Include Serial Number Include Serial Number Print reports in black and white, which can be more legible on non-color printers Imprimați rapoarte în alb-negru, care pot fi astfel mai ușor de citit Print reports in black and white (monochrome) Imprimarea rapoartelor în alb-negru (monocrom) Fonts (Application wide settings) Fonturi (valabile peste tot in aplicatie) Font Font Size Dimensiune Bold Bold Italic Italic Application Aplicatie Graph Text Textul Graficului Graph Titles Titlul Graficului Big Text Text Mare Details Detalii &Cancel &Anuleaza &Ok &Ok Name Nume Color Culoare Flag Type Tip Atenționare Label Eticheta CPAP Events Evenimente CPAP Oximeter Events Evenimente Oximetrie Positional Events Evenimente posturale Sleep Stage Events Evenimente stadiu de somn Unknown Events Evenimente necunoscute Double click to change the descriptive name this channel. DubluClick pentru a schimbadescrierea acestui parametru. Double click to change the default color for this channel plot/flag/data. DubluClick pentru a schimba culoarea implicita pentru acest parametru plot/flag/data. Overview Vedere de ansamblu <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Nota:</b> Capabilitatile avansate ale programului OSCAR de a analiza inregistrarile nu sunt posibile cu aparatele <b>ResMed</b> datorită limitării modului în care sunt stocate setările și datele sumare și prin urmare, au fost dezactivate pentru acest profil.</p><p>Pe aparatele ResMed zilele vor fi <b>despartite la pranz</b> ca in softul comercial al ResMed.</p> Double click to change the descriptive name the '%1' channel. DubluClick pentru a schimba numele descrierii parametrului %1. Whether this flag has a dedicated overview chart. Indiferent dacă acest steag are o diagramă dedicată generală. Here you can change the type of flag shown for this event Aici puteți schimba tipul de steag prezentat pentru acest eveniment This is the short-form label to indicate this channel on screen. Aceasta este o eticheta scurta pentru a identifica pe ecran acest parametru. This is a description of what this channel does. Aceasta este o descriere a ceea ce face acest parametru. Lower Mai jos Upper Mai sus CPAP Waveforms Grafice CPAP Oximeter Waveforms Pletismograma Positional Waveforms Unde posturale Sleep Stage Waveforms Grafice stadiu de somn Whether a breakdown of this waveform displays in overview. Dacă o defalcare a acestei forme de undă este afișată în prezentarea generală. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Aici puteți schimba pragul <b>minim</b> utilizat pentru anumite calcule ale undei %1 Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Aici puteți schimba pragul <b>maxim</b> utilizat pentru anumite calcule ale undei %1 Data Processing Required E nevoie de procesarea datelor A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Este necesară o procedură de re / decompresie a datelor pentru a aplica aceste modificări. Această operație poate dura câteva minute până la finalizare. Sigur doriți să faceți aceste modificări? Data Reindex Required E nevoie de reindexarea datelor A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Este necesară o procedură de reindexare a datelor pentru a aplica aceste modificări. Această operație poate dura câteva minute până la finalizare. Sigur doriți să faceți aceste modificări? Restart Required Este necesara repornirea One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Una sau mai multe dintre modificările pe care le-ați făcut vor necesita repornirea acestei aplicații pentru ca aceste modificări să intre în vigoare. Vreti să faceti asta acum? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Dacă vreți să reimportați din nou aceste date (fie în OSCAR sau ResScan), aceste date nu vor maiputea fi recuperate. If you need to conserve disk space, please remember to carry out manual backups. Dacă aveți nevoie pentru a economisi spațiu pe disc, vă rugăm să rețineți că efectuați backup-uri manuale. Are you sure you want to disable these backups? Sigur doriti sa dezactivati aceste Backup-uri? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Dezactivarea Backup-urior nu e o ide buna, deoarece OSCAR are nevoie de acestea pentru a reconstrui baza de date in caz ca apar erori. Are you really sure you want to do this? Sigur doriti sa faceti asta? %1 %2 %1 %2 Flag Atenționare Minor Flag Atenționare minoră Span Interval Always Minor Intotdeauna Minor No CPAP machines detected Nu am detectat niciun aparat CPAP Will you be using a ResMed brand machine? Veți folosi un aparat ResMed? Never Niciodata This may not be a good idea S-ar putea sa nu fi e o idee asa buna ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Aparatele ResMed S9 sterg de obicei anumite date mai vechi de 7 si 30 zile din cardul SD (in functie de rezolutie). ProfileSelector Filter: Filtru: Reset filter to see all profiles Resetați filtrele pentru a vedea toate profilele Version Versiunea &Open Profile &Deschide Profil pacient &Edit Profile &Editeaza Profil pacient &New Profile &Pacient nou Profile: None Profil: niciunul Please select or create a profile... Va rog selectati sau creati un profil pacient... Destroy Profile Elimina Pacient Profile Profil pacient Ventilator Brand Brandul aparatului Ventilator Model Modelul CPAP Other Data Alte Date Last Imported Ultimele Date Name Nume You must create a profile Trebuie sa creati un Profil pacient nou Enter Password for %1 Introduceti parola pentru %1 You entered an incorrect password Ati introdus o parola gresita Forgot your password? Ati uitat parola? Ask on the forums how to reset it, it's actually pretty easy. Intrebati p eforum cum sa faceti reset, e destul de simplu. Select a profile first Selectati intai un Profil pacient The selected profile does not appear to contain any data and cannot be removed by OSCAR Pacientul selectat nu contine date si nu poat efi eliminat de OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Daca incercati sa stergeti profilul pentru ca ati uitat parola, mai bine o resetati, sau daca nu, trebuie sa stergeti manual dosarul. You are about to destroy profile '<b>%1</b>'. Sunteti pe cale sa eliminati Profilul pacientului '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Ganditi-va bine, asta va sterge iremediabil Profilul pacientului impreuna cu toate<b>backup-urile</b> salvate in el<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Introducet cuvantul<b>DELETE</b> mai jos (exact asa cum se vede) pentru a confirma. DELETE DELETE Sorry Imi pare rau You need to enter DELETE in capital letters. Introducet cuvantul<b>DELETE</b> cu litere MARI. There was an error deleting the profile directory, you need to manually remove it. A aparut o eroare la stergerea Profilului, trebuie sa stergeti manual dosarul acestuia. Profile '%1' was succesfully deleted Profilul '%1' a fost sters Bytes Bytes KB KB MB MB GB GB TB TB PB RP Summaries: Rezumate: Events: Evenimente: Backups: Backup-uri: Hide disk usage information Ascunde utilizare disk Show disk usage information Arata utilizare disk Name: %1, %2 Nume: %1, %2 Phone: %1 Telefon: %1 Email: <a href='mailto:%1'>%1</a> Email: <a href='mailto:%1'>%1</a> Address: Adresa: No profile information given Nu au fost furnizate informatii pentru pacient Profile: %1 Pacient: %1 ProgressDialog Abort Anuleaza QObject No Data Lipsa Date Events Evenimente Duration Durata (% %1 in events) (% %1 in evenimente) Jan Ian Feb Feb Mar Mar Apr Apr May Mai Jun Iun Jul Iul Aug Aug Sep Sep Oct Oct Nov Noie Dec Dec ft ft lb lb oz oz Kg Kg cmH2O cmH2O Med. Med. Min: %1 Min: %1 Min: Min: Max: Max: Max: %1 Max: %1 %1 (%2 days): %1 (%2 zile): %1 (%2 day): %1 (%2 zi): % in %1 % in %1 Hours Ore Min %1 Min %1 Hours: %1 Ore: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 utilizare redusa, %2 neutilizat, din %3 zile (%4% compliant.) Lungime: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Sesiunile: %1 / %2 / %3 Lungime: %4 / %5 / %6 Cea mai lunga: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Lungime: %3 Start: %2 Mask On Masca conectata Mask Off Masca deconectata %1 Length: %3 Start: %2 %1 Lungime: %3 Start: %2 TTIA: TTIA: TTIA: %1 TTIA: %1 Minutes Minute Seconds Secunde h h m m s s ms ms Events/hr Evenimente/ora Hz Hz bpm bpm Litres Litri ml ml Breaths/min Respiratii/min ? ? Severity (0-1) Severitate (0-1) Degrees Grade Error Eroare Warning Avertisment Information Informatie Busy Ocupat Please Note Va rog aveti grija Graphs Switched Off Grafic dezactivat Sessions Switched Off Sesiunile au fost oprite &Yes &Da &No &Nu &Cancel &Anuleaza &Destroy &Elimina &Save &Salvare BMI indice de masa corporala IMC Weight Greutate Zombie Zombie Pulse Rate Puls Plethy Plethy Pressure Presiune Daily Zilnic Profile Pacient Overview Vedere de ansamblu Oximetry Pulsoximetrie Oximeter Pulsoximetru Event Flags Evenimente Default Implicite CPAP CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP Min EPAP Min EPAP Max EPAP Max EPAP IPAP IPAP Min IPAP Min IPAP Max IPAP Max IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Umidificator H Probabil Hipopnee, prescurtarile astea o sa trebuiasca corectate pe masura ce folosim OSCAR H OA Apnee obstructivă AO A Apnee A CA Căi aeriene Libere = Apnee Centrală CL=AC FL FL SA SA LE LE EP Presiune Expiratorie PE VS VS VS2 VS2 RERA Trezire Cauzata de Efortul Respirator: o restrictie in respiratie care determina fie o trezire, fie o tulburare a somnului. RERA PP PP P P RE RE NR NR NRI Non Responding Insomnia to Treatment Index NRI O2 O2 PC Presiune Suport (Bump) PC UF1 UF1 UF2 UF2 UF3 UF3 PS Presiune Suport, care se adauga presiunilor Min si Max setate in aparat. PS AHI Indice Apnne-Hipopnee AHI RDI Indice de afectare respiratorie, Resp Disturbance Index IAR AI indice apnee IA HI Indice hipopnee IH UAI Index de Apnnei Neclasificate UAI CAI Index Apnei Centrale CAI FLI FLI REI REI EPI EPI PB Respiratie periodică Resp period IE Indice Expirator? IE Insp. Time Timp Insp Exp. Time Timp Expir Resp. Event Ev. Resp Flow Limitation Limitare Flux Flow Limit Limita Flux SensAwake SensAwake Pat. Trig. Breath Resp decl.de pacient Tgt. Min. Vent Volum/Min Tinta Target Vent. Tinta Vent. Minute Vent. Volum/Min. Tidal Volume Vol.Respirator Resp. Rate Frecv. Resp Snore Sforăit Leak Scăpare Leaks Scăpări Large Leak Scăpare din Mască semnificativă LL Scăpare din mască Scăpări din Mască Total Leaks Scăpări totale Unintentional Leaks Scăpări Neintenționale MaskPressure PresiuneMască Flow Rate Flux Sleep Stage Stadiul somnului Usage Utilizare Sessions Sesiuni Pr. Relief Pr. Relief No Data Available Nu sunt Date disponibile Built with Qt %1 on %2 Creat cu Qt %1 pe %2 Operating system: Sistem de operare: Graphics Engine: Motor grafic: Graphics Engine type: Tip motor grafic: App key: App key: Software Engine Program ANGLE / OpenGLES Foloseste Graphic Engine "ANGLE / OpenGLES" din Windows pt redarea graficelor ANGLE / OpenGLES Desktop OpenGL Foloseste Graphic Engine " OpenGL" din Windows pt redarea graficelor Desktop OpenGL m m cm cm in Only Settings and Compliance Data Available Sunt disponibile doar setările și datele de conformitate Summary Data Only Doar date sumare Bookmarks Semne de carte Mode Mod Model Model Brand Brand Serial Serial Series Serie Machine Aparat Channel Parametru Settings Setari Inclination Inclinație Orientation Orientare Motion Deplasare Name Nume DOB disorder of breathing, number of apneas and hypopneas per hour Evenim Phone Telefon Address Adresa Email Email Patient ID ID pacient Date Data Bedtime Ora de culcare Wake-up Trezire Mask Time Timp cu Masca Unknown Necunoscut None Niciuna Ready Pregatit First Primul Last Ultimul Start Start End Sfârsit On Pornit Off Oprit Yes Da No Nu Min Min Max Max Med Med Average Medie Median Medie Avg Med W-Avg Medie Ponderată Your %1 %2 (%3) generated data that OSCAR has never seen before. Aparatul dvs %1 %2 (%3) a generat date pe care OSCAR nu le-a mai văzut până acum. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Este posibil ca datele importate să nu fie în întregime exacte, astfel încât dezvoltatorii ar dori o copie .zip a cardului SD al acestui aparat și rapoartele .pdf ale clinicianului pentru a se asigura că OSCAR gestionează corect datele. Non Data Capable Machine Acest aparat CPAP nu suporta inregistrarea datelor Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Aparatul dumneavoastră CPAP %1 (model %2) nu este, din păcate, un model cu card de date SD. Your %1 CPAP machine (Model %2) has not been tested yet. Aparatul dumneavoastră CPAP %1 (model %2) nu a fost încă testat. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Pare suficient de asemănător cu alte aparate pentru a putea funcționa, dar dezvoltatorii ar dori o copie .zip a cardului SD al acestui aparat și rapoartele .pdf ale medicilor pentru a se asigura că funcționează cu OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. Ne pare rău, aparatul dumneavoastră CPAP %1 (%2) nu este încă acceptat. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Dezvoltatorii au nevoie de o copie .zip a cardului SD al acestui aparat și de rapoartele .pdf corespunzătoare ale medicilor pentru a-l face să funcționeze cu OSCAR. Getting Ready... Pregatesc... Machine Unsupported Aparatul nu poate fi folosit cu OSCAR I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Imi pare rau, OSCAR poate urmari doar timpul de utilizare si cateva setari de baza pentru acest aparat CPAP. Scanning Files... Scanez fisierele... Importing Sessions... Import Sesiunile... Finishing up... Finalizare.... Untested Data Date netestate Machine Untested Aparat Netestat CPAP-Check CPAP-Check AutoCPAP AutoCPAP Auto-Trial Auto-Trial AutoBiLevel AutoBiLevel S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Flex Flex Lock Flex Lock Whether Flex settings are available to you. Sunt sau nu disponibile setări Flex. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Perioada de timp necesară pentru trecerea de la EPAP la IPAP, cu cât numărul este mai mare, cu atât tranziția este mai lentă Rise Time Lock Blocare Timp de creștere Whether Rise Time settings are available to you. Sunt sau nu disponibile setări Timp de Creștere. Rise Lock Blocare Creștere Humidification Mode Mod Umidificare PRS1 Humidification Mode PRS1 Mod umidificare Humid. Mode Mod Umid Fixed (Classic) Fix (Classic) Adaptive (System One) Adaptiv (aparate System One) Heated Tube Tub încălzit Passover Paștelke evreiesc? Nu, e un umidificator pasiv Umid. pasivă Tube Temperature Temperatură tub PRS1 Heated Tube Temperature PRS1 Temperatura tub încălzit Tube Temp. Temp. tub. Target Time Timp tinta PRS1 Humidifier Target Time Timp țintă pentru umidificatorul PRS1 Hum. Tgt Time Timp Tinta Umid Tubing Type Lock Blocare tip tub Whether tubing type settings are available to you. Sunt sau nu disponibile setări Tip tub. Tube Lock Blocare tip tub Mask Resistance Lock Rezistență mască: fixă Whether mask resistance settings are available to you. Sunt sau nu disponibile setări Rezistență mască. Mask Res. Lock Mask Res. Lock Whether or not machine shows AHI via built-in display. Arată sau nu aparatul AHI prin afișajul propriu. Ramp Type Tip Rampă Type of ramp curve to use. Tipul curbei Ramp. Linear Linear SmartRamp SmartRamp Ramp+ Ramp+ Backup Breath Mode Modul Respirație Asistată The kind of backup breath rate in use: none (off), automatic, or fixed Tipul de respirație asistatăȘ niciuna (off), automată, sau fixă Breath Rate Rata respirației Fixed Or Repaired? Fixat Fixed Backup Breath BPM Mod Respirație Asistată Fix (BPM fix) Minimum breaths per minute (BPM) below which a timed breath will be initiated Respirații minime pe minut (BPM) sub care va fi inițiată respirația asistată Breath BPM Respirații/min (BPM) Timed Inspiration Inspirație cronometrată The time that a timed breath will provide IPAP before transitioning to EPAP Perioada în care o respirație cronometrată va oferi IPAP înainte de a trece la EPAP Timed Insp. Insp. Cronom. Auto-Trial Duration Durata Auto-Trial The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Numărul de zile din perioada de încercare Auto-CPAP, după care aparatul va reveni la CPAP Auto-Trial Dur. Durata Auto-Trial. EZ-Start EZ-Start Whether or not EZ-Start is enabled Este sau nu activat EZ-Start Variable Breathing Respirație variabilă UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend NECONFIRMAT: respirație posibil variabilă, care sunt perioade de deviere mare de la tendința de vârf a fluxului inspirator Peak Flow Debit de vârf Peak flow during a 2-minute interval Debit de vf timp de 2min PRS1 Humidifier Setting PRS1 Setare umidificare Mask Resistance Setting Setare Rezist. Mască Mask Resist. Rezist.Mască. Hose Diam. Diametru tub. 15mm 15mm 22mm 22mm Backing Up Files... Fac copie de rezervă... model %1 model %1 DreamStation 2 DreamStation 2 unknown model model necunoscut Flex Mode Flex Mode PRS1 pressure relief mode. Mod eliberare presiune PRS1. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time Tmp de crestere Bi-Flex Bi-Flex Flex Level Flex Level PRS1 pressure relief setting. Setari presiune eliberare. Humidifier Status Stare Umidificator PRS1 humidifier connected? Umidificatorul PRS1 e conectat? Disconnected Deconectat Connected Conectat Hose Diameter Diametrul tubului Diameter of primary CPAP hose Diametrul principalului furtun CPAP 12mm 12mm Auto On Auto activat A few breaths automatically starts machine Aparatul va porni automat dupa ce detecteaza cateva respiratii Auto Off Auto dezactivat Machine automatically switches off Aparatul se opreste automat Mask Alert Alerta Masca Whether or not machine allows Mask checking. Daca aparatul dvs permite verificarea Mastii. Show AHI Arată AHI Breathing Not Detected Respiratia Nu a fost Detectata A period during a session where the machine could not detect flow. O perioada in care aparatul nu a aputut detecta flux de aer. BND Breath not detected - Respiratie nedetectata BND Timed Breath Respiratie Impusa Machine Initiated Breath Respiratie initiata de aparat cand pacientul nu a respirat o perioada cronometrata TB TB Windows User Utilizator Windows Using Utilizez , found SleepyHead - , am gasit SleepyHead - You must run the OSCAR Migration Tool Trebuie sa rulati OSCAR Migration Tool <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Datele din aparatul dvs CPAP vechi ar trebui sa fie restaurate, daca aceasta setarede Backup nu a fost dezactivata in preferinte cu ocaziua unui import de date.</i> Launching Windows Explorer failed Nu am putut lansa Windows Explorer Could not find explorer.exe in path to launch Windows Explorer. Nu am putut gasi explorer.exe in Path pentrua putea lansa Windows Explorer. OSCAR %1 needs to upgrade its database for %2 %3 %4 OSCAR %1 trebuie să actualizeze baza de date pentru %2 %3 %4 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR pastreaza un backup a datelor de pe SDcard.</b> OSCAR does not yet have any automatic card backups stored for this device. OSCAR nu are inca niciun backup automat pentru acest aparat. This means you will need to import this machine data again afterwards from your own backups or data card. Asta inseamna ca va trebui sa importati datele acestui aparat CPAP din nou ulterior din backup sau de pe cardul SD. Important: Important: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Daca va faceti griji, alegeti No pentru a iesi si faceti manual backupul Profilului pacientului inainte de a reporni OSCAR. Are you ready to upgrade, so you can run the new version of OSCAR? Sunteti gata sa actualizati la noua versiune programul OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Imi pare rau, operatiunea de curatare a esuat, cea ce inseamna ca acesta versiune de OSCAR nu poate porni. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Doriti sa activati Backup automat, astfel incat viitoarea versiune de OSCAR sa poata reconstrui din el baza de date? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR va porni Importul semiautomat ca sa puteti reinstala datele dvs %1. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: OSCAR se va opri si apoi va (incerca sa) porneasca Windows Explorer ca sa puteti face backup manual la Profilul pacientului: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Folositi managerul de fisiere pentru a face o copie a dosarului Pacientului, apoi restartati OSCAR si finalizati actualizarea versiunii. Machine Database Changes Schimbări in baza de date a aparatului Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. După ce faceți upgrade, <font size = + 1> nu mai puteți </font> utiliza acest profil de pacient cu versiunea anterioară. The machine data folder needs to be removed manually. Dosarul acestui aparat trebuie sters manual. This folder currently resides at the following location: Acest dosar se afla momentan la urmatoarea locatie: Rebuilding from %1 Backup Reconstruiesc din Backup %1 Therapy Pressure Presiune terapeutica Inspiratory Pressure Presiune Inspiratorie Lower Inspiratory Pressure Presiune Inspiratorie Minima Higher Inspiratory Pressure Presiune Inspiratorie Maxima Expiratory Pressure Presiune Expiratorie Lower Expiratory Pressure Presiune Expiratorie Minima Higher Expiratory Pressure Presiune Expiratorie Maxima Pressure Support Presiune Suport (PS) PS Min PS Min Pressure Support Minimum Presiune Suport (PS) Minim PS Max PS Max Pressure Support Maximum Presiune Suport (PS) Maxim Min Pressure Presiune Min Minimum Therapy Pressure Presiune Terapeutica Minima Max Pressure Presiune Max Maximum Therapy Pressure Presiune Terapueutica Maxima Ramp Time Timp in Rampă Ramp Delay Period Intarzierea Rampei Ramp Pressure Presiune Rampă Starting Ramp Pressure Presiune pornire Rampă Ramp Event Eveniment Rampă Ramp Rampă An abnormal period of Cheyne Stokes Respiration O perioada anormala de respiratie Cheyne Stokes An apnea that couldn't be determined as Central or Obstructive. Apnee care nu poate fi caracterizată ca fiind nici Centrală nici Obstructivă. A restriction in breathing from normal, causing a flattening of the flow waveform. O restrictie in respiratie fata de normal, care cauzeaza o aplatizare a valorilorfluxului de aer. Vibratory Snore (VS2) Sforait vibrator (VS2) A ResMed data item: Trigger Cycle Event O informație specifică ResMed: Trigger Cycle Event Mask On Time Timp cu Masca conectata Time started according to str.edf Timp incepere conform cu 'str.edf' Summary Only Doar Sumar An apnea where the airway is open Apnee Centrală, in care caile aeriene sunt permeabile An apnea caused by airway obstruction Apnee cauzata de obstructia cailor aeriene Hypopnea Hipopnea A partially obstructed airway Obstructie partiala a cailor aeriene Unclassified Apnea Apnee Neclasificata UA UA Vibratory Snore Sforait Vibrator A vibratory snore Un sforait vibrator A vibratory snore as detcted by a System One machine Un sforait vibrator detectat de un aparat System One Pressure Pulse Puls Presiune A pulse of pressure 'pinged' to detect a closed airway. Un puls de presiune fortat pentru a detecta cai aeriene blocate. A large mask leak affecting machine performance. O scăpare pe lângă mască semnificativă afectează performanta aparatului. Non Responding Event Eveniment fara Raspuns A type of respiratory event that won't respond to a pressure increase. Un tip de eveniment respirator care nu raspunde la un puls de presiune suplimnetar. Expiratory Puff Puls Expirator Intellipap event where you breathe out your mouth. Eveniment Intellipap cand ati expirat pe gura. SensAwake feature will reduce pressure when waking is detected. Facilitatea SensAwake va reduce presiunea cand detecteaza trezirea, pentru a facilita readormirea. User Flag #1 User Flag #1 User Flag #2 User Flag #2 User Flag #3 User Flag #3 Heart rate in beats per minute Pulsul in bpm Blood-oxygen saturation percentage Saturatia procentuala a oxigenului din sangele periferic Plethysomogram Plethysomograma An optical Photo-plethysomogram showing heart rhythm O fotopletismograma optica care arata ritmul cardiac Pulse Change Schimbare Puls A sudden (user definable) change in heart rate O schimbare brusca (definibila de utilizator) in frecventa cardiaca SpO2 Drop Desaturare O2 A sudden (user definable) drop in blood oxygen saturation O Desaturare Brusca (definibila de utilizator) in saturatia oxigenului (DB) SD DB Breathing flow rate waveform Graficul fluxului L/min L/min Mask Pressure Presiune Masca Amount of air displaced per breath Volum de aer mobilizat la fiecare respiratie Graph displaying snore volume Graficul arata volumul sforaitului Minute Ventilation Ventilatie pe minut Amount of air displaced per minute Volum de aer mobilizat pe minut Respiratory Rate Frecventa Respiratiei Rate of breaths per minute Respiratii pe minut Patient Triggered Breaths Respiratii declansate de pacient Percentage of breaths triggered by patient Procent de respiratii declansate de pacient Pat. Trig. Breaths Respiratii Decl. de Pacient Leak Rate Rata Scăpări Rate of detected mask leakage Rata scăpărilor pe lângă mască I:E Ratio Raport I:E Ratio between Inspiratory and Expiratory time Raport intre timpul Inspirator si Expirator ratio raport Pressure Min Presiune Min Pressure Max Presiune Max Pressure Set Presiune Setată Pressure Setting Setare Presiune IPAP Set IPAP Set IPAP Setting IPAP Setări EPAP Set EPAP Setat EPAP Setting EPAP Setări Cheyne Stokes Respiration Respiratie Cheyne Stokes (RCS) CSR Respiratie Cheyne Stokes RCS Periodic Breathing Respirație Periodică An abnormal period of Periodic Breathing O perioadă anormală de respirație periodică Clear Airway Căi aeriene Libere, Apnee Centrală Obstructive Apnee Obstructivă Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. RERA Trezire Cauzata de Efortul Respirator: o restrictie in respiratie care determina fie o trezire, fie o tulburare a somnului. Leak Flag Atetionare scăpări pe lângă mască (SM) LF Scăpări din Mască A user definable event detected by OSCAR's flow waveform processor. Un eveniment definibil de catre utilizator detectat de procesorul de flux al programului OSCAR. Perfusion Index Index Perfuzie A relative assessment of the pulse strength at the monitoring site O evaluare relativa a rezistentei pulsului la locul de monitorizare Perf. Index % Index Perf. % Mask Pressure (High frequency) Presiune Mască (Frecvență înaltă) Expiratory Time Timp Expirator Time taken to breathe out Timp pentru expir Inspiratory Time Timp Inspirator Time taken to breathe in Timp pentru inspir Respiratory Event Eveniment Respirator Graph showing severity of flow limitations Graficul arata severitatea limitatilor fluxului aerian Flow Limit. Limitare Flux. Target Minute Ventilation Tinta Ventilatiei pe minut Maximum Leak Scăpări Maxime The maximum rate of mask leakage Rata maxima de scăpare pe lângă mască Max Leaks Scăpări Max Apnea Hypopnea Index Apnea Hipopea Index (AHI) Graph showing running AHI for the past hour Graficul arată evolutia Indicele apnee-hipopnee (AHI) in ultima oră Total Leak Rate Rata totală de scăpăari Detected mask leakage including natural Mask leakages Pierderile pe lângă mască detectate, inclusiv scăpările naturale din mască Median Leak Rate Rata medie a scăpărilor Median rate of detected mask leakage Media scăpărilor detectate Median Leaks Scăpări Medii Respiratory Disturbance Index Indice de tulburare respiratorie Graph showing running RDI for the past hour Graficul arata evolutia indexului de tulburari respiratorii (RDI) in ultima ora Sleep position in degrees Pozitia somnului in Grade Upright angle in degrees Unghiul superior in grade Movement Mișcare Movement detector Detector de mișcare CPAP Session contains summary data only Sesiunea CPAP contine doar date sumare PAP Mode Mod PAP Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Nu s-a putut analiza Channels.xml, OSCAR nu poate continua și se va opri. Apnea Apnea An apnea reportred by your CPAP machine. O apnee raportata de aparatul dvs CPAP. PAP Device Mode Mod dispozitiv PAP APAP (Variable) APAP (Variable) ASV (Fixed EPAP) ASV (EPAP presiune expiratorie Fixa) ASV (Variable EPAP) ASV (EPAP presiune expiratorie variabila) Height Inaltime Physical Height Inaltime Notes Note Bookmark Notes Note Semn de carte Body Mass Index Indice de masa corporala How you feel (0 = like crap, 10 = unstoppable) Cum va simtiti (0 = rau, 10 = super) Bookmark Start /Semn de carte de inceput Inceputul Semnului de carte Bookmark End /Semn de carte de final Finalul semnului de carte Last Updated Ultima actualizate Journal Notes Note Jurnal Journal Jurnal 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=Treaz 2=REM 3=Somn superficial 4=Somn profund Brain Wave Unda cerebrala BrainWave UndaCerebrala Awakenings Treziri Number of Awakenings Numar de treziri Morning Feel Starea la trezire dimineata How you felt in the morning Cum v-ati simtit dimineata Time Awake Timp treaz Time spent awake Timp Treaz Time In REM Sleep Timp in faza REM Time spent in REM Sleep Timp petrecut in faza REM a somnului, odihnitoare Time in REM Sleep Timp in REM Time In Light Sleep Timp In Somn Superficial Time spent in light sleep Timp in somn superficial Time in Light Sleep Timp in Somn Superficial Time In Deep Sleep Timp In Somn Adanc Time spent in deep sleep Timp in somn adanc Time in Deep Sleep Timp in Somn Adanc Time to Sleep Tmp pana la adormire Time taken to get to sleep Timp pentru a adormi Zeo ZQ Zeo ZQ Zeo sleep quality measurement Zeo masurarea calitatii somnului ZEO ZQ ZEO ZQ Debugging channel #1 Depanare channel #1 Test #1 Test #1 For internal use only Doar pentru uz intern Debugging channel #2 Depanare channel #2 Test #2 Test #2 Zero Zero Upper Threshold Limita Superioara Lower Threshold Prag scazut As you did not select a data folder, OSCAR will exit. Deoarece nu ati ales un dosar, OSCAR se va opri. or CANCEL to skip migration. sau ANULATI pentru a opri migrarea. Choose the SleepyHead or OSCAR data folder to migrate Agegeti locatia fisierelor SleepyHead sau OSCAR pentru a migra datele The folder you chose does not contain valid SleepyHead or OSCAR data. Dosarul pe care l-ați ales nu conține date SleepyHead sau OSCAR valide. You cannot use this folder: Nu puteti utiliza acest dosar: Migrating Transfer files fisiere from de la to la OSCAR crashed due to an incompatibility with your graphics hardware. OSCAR s-a blocat din cauza unei incompatibilități cu placa dvs grafică. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. Pentru a ocoli problema OSCAR a trecut la un mod grafic mai lent dar mai compatibil. OSCAR will set up a folder for your data. OSCAR va stabili dosarul pentru date. If you have been using SleepyHead or an older version of OSCAR, Dacă ați utilizat SleepyHead sau o versiune mai veche de OSCAR, OSCAR can copy your old data to this folder later. OSCAR poate copia ulterior datele vechi în acest dosar. Migrate SleepyHead or OSCAR Data? Migrez datele SleepyHead sau OSCAR? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Pe ecranul următor, OSCAR vă va cere să selectați un dosar cu date SleepyHead sau OSCAR Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Faceți clic pe [OK] pentru a trece la ecranul următor sau pe [No] dacă nu doriți să utilizați datele SleepyHead sau OSCAR. We suggest you use this folder: Sugerez sa folositi acest dosar: Click Ok to accept this, or No if you want to use a different folder. Click OK pentru a accepta, sau NO daca doriti sa utilizati un alt dosar. Choose or create a new folder for OSCAR data Alegeti sau creati un nou dosar de date pentru OSCAR Next time you run OSCAR, you will be asked again. Data viitoare cand porniti OSCAR va intreba din nou. The folder you chose is not empty, nor does it already contain valid OSCAR data. Dosarul pe care l-ati ales nu este gol, dar nici nu contine date OSCAR valide. Data directory: Dosarul Data: Unable to create the OSCAR data folder at Nu se poate crea dosarul de date OSCAR in Unable to write to OSCAR data directory Nu se poate scrie în directorul de date OSCAR Error code Cod eroare OSCAR cannot continue and is exiting. OSCAR nu poate continua rularea și se va opri. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Nu se poate scrie în jurnalul de depanare. Puteți utiliza în continuare panoul de depanare (Help/Troubleshooting/Show Debug Pane), dar jurnalul de depanare nu va fi scris pe disc. Version "%1" is invalid, cannot continue! Versiunea "%1" este invalidă, nu pot continua! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Versiunea OSCAR pe care o executați (%1) este mai veche decât cea utilizată pentru a crea aceste date (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? Probabil daca faceti asta datele vor fi corupte, sigur doriti asta? Question Intrebare Exiting Oprire Are you sure you want to use this folder? Sunteti sigur ca doriti sa utilizati acest dosar? Don't forget to place your datacard back in your CPAP machine Amintiti-va sa puneti la loc in aparat cardul SD <b>dupa ce i-ati deblocat scrierea</b> OSCAR Reminder Reamintire OSCAR You can only work with one instance of an individual OSCAR profile at a time. Puteti lucra cu un singur Profil pacient la un moment dat. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Daca salvati datele in cloud, asigurati-va ca OSCAR este inchis si ca sincronizarea cu cloud-ul s-a finalizat inainte de a reporni programul. Loading profile "%1"... Incarc Profil pacient "%1"... Chromebook file system detected, but no removable device found Chromebook-ul dvs nu are un dispozitiv de memorie detasabil (card de memorie) You must share your SD card with Linux using the ChromeOS Files program Trebuie să partajați cardul SD cu Linux utilizând programul ChromeOS Files Recompressing Session Files Recomprim fișierele sesiunii Please select a location for your zip other than the data card itself! Vă rog selectați o altă locație pentru arhiva zip., în afară de cardul de date în sine! Unable to create zip! Nu pot crea zip! Are you sure you want to reset all your channel colors and settings to defaults? Sigur doriti resetarea culorilor si setarilor acestui parametru la cele implicite? Are you sure you want to reset all your waveform channel colors and settings to defaults? Sigur doriti resetarea culorilor si setarilor graficului acestui parametru la cele implicite? There are no graphs visible to print Nu exista grafice vizibile de printat Would you like to show bookmarked areas in this report? Doriti sa afisez zonele din Semne de carte in acest Raport? Printing %1 Report Tiparesc raportul %1 %1 Report %1 Raport : %1 hours, %2 minutes, %3 seconds : %1 ore, %2 minute, %3 secunde RDI %1 Respiration Disturbance Index IAR %1 AHI %1 AHI %1 AI=%1 HI=%2 CAI=%3 Iindexurile Apnee, Hipopnee si Apnee Centrală IA=%1 IH=%2 IAC=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 RP/RCS=%4%% UAI=%1 UAI=%1 NRI=%1 LKI=%2 EPI=%3 NonResponding to therapy Insomnia; ....; Expiratory Pressures Index ? NRI=%1 LKI=%2 EPI=%3 AI=%1 AI=%1 Reporting from %1 to %2 Raportez de la %1 la %2 Entire Day's Flow Waveform Graficul fluxului pe intreaga zi Current Selection Selectia curenta Entire Day Toata ziua Page %1 of %2 Pagina %1 din %2 Days: %1 Zile: %1 Low Usage Days: %1 Zile cu utilizare redusa: %1 (%1% compliant, defined as > %2 hours) (%1% compliant, definit ca > %2 ore) (Sess: %1) (Sesiune: %1) Bedtime: %1 Ora de culcare: %1 Waketime: %1 Timp trezire: %1 (Summary Only) (Doar sumar) There is a lockfile already present for this profile '%1', claimed on '%2'. Exista deja un fisier blocat pentru acest Profil pacient'%1', creat pe '%2'. Fixed Bi-Level Fixat Bi-Level Auto Bi-Level (Fixed PS) Auto Bi-Level (PS presiune fixa) Auto Bi-Level (Variable PS) Auto Bi-Level (PS presiune variabila) 99.5% 99.5% varies variante n/a n/a Fixed %1 (%2) Fixat %1 (%2) Min %1 Max %2 (%3) Min %1 Max %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %1 + %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1 IPAP %2-%3 (%4) {1-%2 ?} {3-%4 ?} {5)?} Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Cele mai recente date oximetrice: <a onclick='alert("daily=%2");'>%1</a> (last night) (noaptea trecuta) (1 day ago) (o zi in urma) (%2 days ago) (%2 zile in urma) No oximetry data has been imported yet. Nu au fost importate încă datele pulsoximetrice. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings Setari SmartFlex ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Somnopose Software Zeo Zeo Personal Sleep Coach Antrenor de somn personal Database Outdated Please Rebuild CPAP Data Baza de date expirata va rugam Reconstruiti datele CPAP (%2 min, %3 sec) (%2 min, %3 sec) (%3 sec) (%3 sec) Pop out Graph Grafic in relief The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Fereastra popout este plină. Ar trebui să capturați imaginea existentă fereastra popout, să o ștergeți, apoi să deschideți din nou acest grafic. Your machine doesn't record data to graph in Daily View Aparatul dvs. nu înregistrează date pentru a le reprezenta grafic în Vizualizare zilnică There is no data to graph Nu există date pentru a face un grafic d MMM yyyy [ %1 - %2 ] d MMM yyyy [ %1 - %2 ] Hide All Events Ascunde toate evenimentele Show All Events Arata toate Evenimentele Unpin %1 Graph Mobilizeaza raficul %1 Popout %1 Graph Arată graficul %1 Pin %1 Graph Fixează graficul %1 Plots Disabled Ploturi dezactivate Duration %1:%2:%3 Durata %1:%2:%3 AHI %1 AHI %1 Relief: %1 Eliberare: %1 Hours: %1h, %2m, %3s Ore: %1h, %2m, %3s Machine Information Informatii Aparat Journal Data Date Jurnal OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR a gasit un Jurnal mai vechi, dar se pare ca cineva l-a redenumit intre timp: OSCAR will not touch this folder, and will create a new one instead. OSCAR nu se atinge de acest dosar, ci va crea altul nou. Please be careful when playing in OSCAR's profile folders :-P Va rog aveti grija cand puneti ceva in dosarul cu Profile al programului OSCAR :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. Dintr-un motiv oarecare OSCAR nu a putut gasi un jurnal in profilul dvs, dar a gasit mai multe dosare cu jurnale. OSCAR picked only the first one of these, and will use it in future: OSCAR a inteles doar prima dintre acestea si va folosi in continuare: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Daca datele dvs vechi lipsesc, copiati intreg continutul celuilalt dosar Journal_XXXXXXX in acesta manual. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Backing up files... Salvez backup fisiere... Reading data files... Citesc fisierele de date... SmartFlex Mode Mod SmartFlex Intellipap pressure relief mode. Mod eliberare presiune Intellipap. Ramp Only Doar in Rampă Full Time Tot timpul SmartFlex Level Nivel SmartFlex Intellipap pressure relief level. Nivel eliberare presiune Intellipap. Snoring event. Sforait detectat. SN SN Locating STR.edf File(s)... Caut fisierul STR.edf... Cataloguing EDF Files... Ordonez fisierele EDF... Queueing Import Tasks... Salvez sarcinile de printare... Finishing Up... Finalizare.... CPAP Mode Mod CPAP VPAPauto VPAPauto ASVAuto ASVAuto iVAPS iVAPS PAC PAC Auto for Her Auto pentru femei EPR EPR ResMed Exhale Pressure Relief Presiune de eliberare la expir ResMed Patient??? PAcient??? EPR Level EPR Level Exhale Pressure Relief Level Nivelul eliberarii presiunii expiratorii Response Raspuns Patient View Vizualizare pacient SmartStart SmartStart Machine auto starts by breathing Aparatul porneste automat la detectarea respiratiei Smart Start Smart Start Humid. Status Stare Umidificator Humidifier Enabled Status Stare Umidificator Activat Humid. Level Nivel Umidificator Humidity Level Nivel Umiditate Temperature Temperatura ClimateLine Temperature Temperatura ClimateLine Temp. Enable Activare Temp ClimateLine Temperature Enable Activeaza Temperatura ClimateLine Temperature Enable Activare Temperatura AB Filter Filtru AB Antibacterial Filter Filtru Antibacterian Pt. Access Pt. Access Essentials Esentiale Plus Plus Climate Control Control temperatura Manual Manual Soft Soft Standard Standard BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T SmartStop SmartStop Machine auto stops by breathing Aparatul se oprește automat cand respirați Smart Stop Smart Stop Simple Simplu Advanced Avansat Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Pare suficient de asemănător cu alte aparate cpap pentru a putea funcționa, dar dezvoltatorii ar dori o copie .zip a cardului SD al acestui aparat pentru a se asigura că funcționează cu OSCAR. Parsing STR.edf records... Parcurg înregistrările STR.edf... Auto Auto Mask Masca ResMed Mask Setting Setari masca ResMed Pillows Perne Full Face Faciala Nasal Nazala Ramp Enable Activeaza Rampa Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Snapshot %1 Captura %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... Incarc %1 date pentru %2... Scanning Files Scanez fisierele Migrating Summary File Location Importez locatia fisierului Rezumat Loading Summaries.xml.gz Incarc Summaries.xml.gz Loading Summary Data Incarc Sumarul Datelor Please Wait... Va rog asteptati... Dreem Dreem Your Viatom device generated data that OSCAR has never seen before. Aparatul dvs Viatom a generat date pe care OSCAR nu le-a mai văzut încă. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Este posibil ca datele importate să nu fie în întregime exacte, astfel încât dezvoltatorii ar dori o copie a fișierelor dvs. Viatom pentru a se asigura că OSCAR tratează corect datele. Viatom Viatom Viatom Software Viatom Software %1 Charts %1 of %2 Charts Loading summaries Încarc rezumatele Updating Statistics cache Actualizez bufferul Statistici Usage Statistics Statistici utilizare New versions file improperly formed Noi versiuni de fișiere au formate necorespunzătoare A more recent version of OSCAR is available Exista o versiune mai recenta a programului OSCAR release versiune test version versiune de test You are running the latest %1 of OSCAR Rulezi cea mai recentă versiune %1 de OSCAR You are running OSCAR %1 Rulati OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 e disponibil <a href='%2'>aici</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Informații despre versiunea mai recentă de test %1 sunt disponibile la <a href='%2'>%2</a> Check for OSCAR Updates Caut actualizari ale programului OSCAR Unable to check for updates. Please try again later. Nu se poate verifica dacă există actualizări. Vă rugăm să încercați din nou mai târziu. SensAwake level Nivel SensAwake Expiratory Relief Usurare respiratie Expiratory Relief Level Nivel usurare respiratie Humidity Umiditate SleepStyle Stil de somn This page in other languages: Această pagină în alte limbi: %1 Graphs %1 Grafice %1 of %2 Graphs %1 din %2 Grafice %1 Event Types %1 Tipuri de evenimente %1 of %2 Event Types %1 din %2 Tipuri de evenimente Report about:blank about:blank SessionBar %1h %2m %1h %2m No Sessions Present Nu am gasit sesiuni SleepStyleLoader Import Error Eroare la Importare This Machine Record cannot be imported in this profile. Datele din acest aparat nu pot fi importate in acest profil. The Day records overlap with already existing content. Datele din aceasta zi se suprapun cu cele deja existente. Statistics CPAP Statistics Statistici CPAP CPAP Usage Utilizare CPAP Average Hours per Night Ore pe noapte (medie) Therapy Efficacy Eficiența terapiei Leak Statistics Statisticile pierderilor din mască Pressure Statistics Statistici presiune Oximeter Statistics Statistici oximetrie Blood Oxygen Saturation Saturatia oxigenului in sangele periferic Pulse Rate Pulsul %1 Median %1 Media Average %1 Media %1 Min %1 Min %1 Max %1 Max %1 %1 Index %1 Index % of time in %1 % din timp in %1 % of time above %1 threshold % din timp peste pragul %1 % of time below %1 threshold % din timp sub pragul de %1 Name: %1, %2 Nume: %1, %2 DOB: %1 Data nașterii: %1 Phone: %1 Telefon: %1 Email: %1 Email: %1 Address: Adresa: Days Used: %1 Zile de utilizare: %1 Low Use Days: %1 Zile cu utilizare mai redusa: %1 Compliance: %1% Complianta: %1% Days AHI of 5 or greater: %1 Zile cu AHI de 5 sau mai mare: %1 Best AHI Cel mai bun AHI Date: %1 AHI: %2 Data: %1 AHI: %2 Worst AHI Cel mai prost AHI Best Flow Limitation Cea mai buna limitare de flux aerian Date: %1 FL: %2 Data: %1 FL: %2 Worst Flow Limtation Cea mai proasta limitare de flux aerian No Flow Limitation on record Nu exista limitare de flux inregistrata Worst Large Leaks Cea mai mare pierdere din masca Date: %1 Leak: %2% Data: %1 pierderi din masca: %2% No Large Leaks on record Nu există scăpări mari inregistrate Worst CSR Episod maxim Cheyne Stokes (RCS) Date: %1 CSR: %2% Data: %1 RCS: %2% No CSR on record Nu sunt episoade de RCS inregistrate Worst PB Episod maxim RespiratiePeriodică (RP) Date: %1 PB: %2% Data: %1 RP: %2% No PB on record Nu există episoade de RP inregistrate Want more information? Doriti mai multe informatii? OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR are nevoie de toate datele sumare incarcate pentru a calcula cele mai bune/mai rele valori pentru fiecare zi in parte. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Vă rog activați Preîncarcă rezumatele la lansare in Preferinte ca să vă asigurati că aceste date vor fi disponibile. Best RX Setting Cea mai bună presiune recomandată de medic Date: %1 - %2 Data: %1 - %2 AHI: %1 AHI: %1 Total Hours: %1 Total ore: %1 Worst RX Setting Cele mai proaste setari recomandate de medic Most Recent Cel mai recent Compliance (%1 hrs/day) Complianță (%1 ore/zi) This report was prepared on %1 by OSCAR %2 Acest raport a fostt redactat pe %1 de OSCAR %2 OSCAR is free open-source CPAP report software OSCAR este un software gratuit open-source Changes to Machine Settings Schimbări Setări aparat No data found?!? Nu am găsit date?!? Oscar has no data to report :( OSCAR nu are date de raportat :( Last Week Ultima saptamana Last 30 Days Ultimele 30 zile Last 6 Months Ultimele 6 luni Last Year Ultimul an Last Session Ultima sesiune Details Detalii No %1 data available. Nu exista date %1 disponibile. %1 day of %2 Data on %3 %1 zi din %2 Date pe %3 %1 days of %2 Data, between %3 and %4 %1 zile din Datele %2, intre %3 si %4 Days Zile Pressure Relief Eliberare presiune Pressure Settings Setări presiune Machine Information Informatii aparat CPAP First Use Prima utilizare Last Use Ultima utilizare Welcome Welcome to the Open Source CPAP Analysis Reporter Bun venit la OSCAR - Open Source CPAP Analysis Reporter What would you like to do? Ce doriti sa faceti? CPAP Importer Importator Date din CPAP Oximetry Wizard Pulsoximetrie semiautomata Daily View Vizualizare zilnica Overview Privire de ansamblu Statistics Statistici <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">Cardurile SD utilizate ]n aparatele ResMed S9 trebuie protejate la scriere </span><span style=" font-weight:600; color:#ff0000;">înainte de a le introduce în computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Anumite sisteme de operare (Windows în special) scriu automat fișiere proprii pe cardurile mnou introduse, făcăndu-le astfel inutilizabile cu aparatul dvs CPAP. ResMed S10 nu are aceasta problemă. Ștergeți fișierele sau formatați cardul în aparat și se rezolvă, dar pierdeți datele.</span></p></body></html> It would be a good idea to check File->Preferences first, Ar fi o idee buna sa verificati intai File->Preferinte as there are some options that affect import. fiindca sunt cateva optiuni care afecteaza importul. Note that some preferences are forced when a ResMed machine is detected Anumite preferinte sunt fortate automat cand este detectat un aparat CPAP ResMed First import can take a few minutes. Primul import poate dura cateva minute. The last time you used your %1... Ultima data cand ati folosit %1... last night noaptea trecuta %2 days ago %2 zile in urma was %1 (on %2) a fost %1 (%2) %1 hours, %2 minutes and %3 seconds %1 ore, %2 minute si %3 secunde Your machine was on for %1. Aparatul dvs a functionat pentru %1. <font color = red>You only had the mask on for %1.</font> <font color = red>Ati avut masca pusa corect %1.</font> under sub over peste reasonably close to destul de aproape de equal to egal cu You had an AHI of %1, which is %2 your %3 day average of %4. Ati avut un AHI de %1, care este %2 media zilnica de %4 din ultimele %3 zile. Your CPAP machine used a constant %1 %2 of air Aparatul dvs CPAP a utilizat constant %1 %2 de aer Your pressure was under %1 %2 for %3% of the time. Presiunea dvs a fost sub %1 %2 pentru %3% din timp. Your machine used a constant %1-%2 %3 of air. Aparatul dvs a utilizat constant %1-%2 %3 de aer. Your EPAP pressure fixed at %1 %2. Presiunea dvs EPAP stabilita la %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Presiunea dvs IPAP a fost sub %1 %2 pentru %3% din timp. Your EPAP pressure was under %1 %2 for %3% of the time. Presiunea dvs EPAP a fost sub %1 %2 pentru %3% din timp. Your machine was under %1-%2 %3 for %4% of the time. Aparatul dvs a fost la presiunea %1-%2 %3 pentru %4% din timp. 1 day ago o zi in urma Your average leaks were %1 %2, which is %3 your %4 day average of %5. Pierderile din mască au fost %1 %2, care este %3 media zilnică de %5 din ultimele %4 zile. No CPAP data has been imported yet. Nu au fost incă importate date din CPAP. gGraph %1 days %1 zile gGraphView 100% zoom level 100% zoom level Restore X-axis zoom to 100% to view entire selected period. Restaurează zoom-ul pe axa X la 100% pentru a vedea datele întregii perioade. Restore X-axis zoom to 100% to view entire day's data. Restaurează zoom-ul pe axa X la 100% pentru a vedea datele întregii zile. Reset Graph Layout Reseteaza graficele Resets all graphs to a uniform height and default order. Reseteaza toate graficele la o inaltime uniforma si in ordinea lor initiala. Y-Axis Y-Axis Plots Plots CPAP Overlays Suprapunere CPAP Oximeter Overlays Suprapunere pulsoximetrie Dotted Lines Linii punctate Double click title to pin / unpin Click and drag to reorder graphs Dublu-click pe titlu pentru a fixa sau mobiliza, Click & trage pentru a reaseza graficele in fereastra Remove Clone Elimina clona Clone %1 Graph Cloneaza graficul %1 OSCAR-code-v1.3.1/Translations/Russkiy.ru.ts000066400000000000000000016046151417327530600206350ustar00rootroot00000000000000 AboutDialog &About &О программе Release Notes Примечания к этой версии Credits Авторы GPL License Лицензия GPL Close Закрыть Show data folder Показать папку с данными About OSCAR %1 О программе OSCAR %1 Sorry, could not locate About file. Не удалось найти файл "О программе". Sorry, could not locate Credits file. Не удалось найти файл "Авторы". Sorry, could not locate Release Notes. Не удалось найти файл "Примечания к этой версии". OSCAR %1 OSCAR %1 Important: Важная информация: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Это предварительная версия. Прежде чем продолжить, рекомендуется <b>создать резервную копию папки данных вручную</b>, поскольку попытка восстановить предыдущее состояние может привести к сбоям. To see if the license text is available in your language, see %1. Чтобы узнать, доступен ли текст лицензии на вашем языке, см. %1. CMS50F37Loader Could not find the oximeter file: Невозможно найти файл оксиметра: Could not open the oximeter file: Невозможно открыть файл оксиметра: CMS50Loader Could not get data transmission from oximeter. Невозможно получить данные из оксиметра. Please ensure you select 'upload' from the oximeter devices menu. Убедитесь, что выбрана 'Загрузка' в меню 'Устройства' оксиметра. Could not find the oximeter file: Невозможно найти файл оксиметра: Could not open the oximeter file: Невозможно открыть файл оксиметра: CheckUpdates Checking for newer OSCAR versions Поиск новых версий OSCAR Daily Go to the previous day Предыдущий день Show or hide the calender Показать или скрыть календарь Go to the next day Следующий день Go to the most recent day with data records Последний день с данными Events События View Size Размер Notes Примечания Journal Дневник B B u u Color Цвет Small Мелкий Medium Средний Big Большой Zombie Зомби I'm feeling ... Самочувствие... Weight Вес If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Если в настройках указан ваш рост, указание веса позволит определить индекс массы тела (BMI) Awesome Великолепно B.M.I. И.М.Т. Bookmarks Закладки Add Bookmark Добавить закладку Starts Начало Remove Bookmark Удалить закладку Flags Отметки Graphs Графики Show/hide available graphs. Показать доступные графики. Breakdown Разбор events события UF1 UF1 UF2 UF2 Time at Pressure Время терапии No %1 events are recorded this day В этот день не было зарегистрировано событий "%1" %1 event %1 cобытие %1 events %1 cобытий Session Start Times Начало сеанса Session End Times Конец сеанса Session Information Информация о сеансе Oximetry Sessions Сеансы оксиметрии Duration Длительность (Mode and Pressure settings missing; yesterday's shown.) (Нет настроек режима и давления; показаны вчерашние) This bookmark is in a currently disabled area.. Закладка в недоступной сейчас области. CPAP Sessions Сеансы CPAP Details Подробности Sleep Stage Sessions Сеансы стадий сна Position Sensor Sessions Сеансы датчика положения Unknown Session Неизвестный сеанс Machine Settings Настройки аппарата Model %1 - %2 Модель %1 - %2 PAP Mode: %1 Режим PAP: %1 99.5% 99,5% This day just contains summary data, only limited information is available. Этот день содержит только общие данные, информация ограничена. Total ramp time Время разгона Time outside of ramp Время вне разгона Start Начало End Конец Unable to display Pie Chart on this system В этой системе невозможно отобразить круговую диаграмму 10 of 10 Event Types 10 из 10 типов событий This CPAP machine does NOT record detailed data Этот аппарат не записывает подробные данные Sorry, this machine only provides compliance data. К сожалению, этот аппарат предоставляет только общие данные. "Nothing's here!" "Здесь ничего нет!" No data is available for this day. Данных за этот день нет. 10 of 10 Graphs 10 из 10 графиков Oximeter Information Информация об оксиметре Click to %1 this session. Нажмите, чтобы %1 этот сеанс. disable отключить enable включить %1 Session #%2 %1: сеанс #%2 %1h %2m %3s %1ч %2м %3с <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Обратите внимание:</b> Все настройки ниже основаны на предположении, что в предыдущие дни не произошло изменений. SpO2 Desaturations Десатурации SpO2 Pulse Change events Изменения пульса SpO2 Baseline Used Базовое значение SpO2 Statistics Статистика Total time in apnea Общее время апноэ Time over leak redline Время избыточных утечек Event Breakdown Разбор событий Sessions all off! Все сеансы отключены! Sessions exist for this day but are switched off. В этот день есть сеансы, но они отключены. Impossibly short session Недопустимо короткий сеанс Zero hours?? Ноль часов?? BRICK :( BRICK :( Complain to your Equipment Provider! Обратитесь к поставщику вашего аппарата! Pick a Colour Выберите цвет Bookmark at %1 Закладка на %1 ExportCSV Export as CSV Экспортировать CSV Dates: Даты: Resolution: Детализация: Details разбор Sessions сеансы Daily сводка Filename: Файл: Cancel Отмена Export Экспорт Start: начало: End: конец: Quick Range: Период: Most Recent Day Последний день Last Week Последняя неделя Last Fortnight Последние две недели Last Month Последний месяц Last 6 Months Последние 6 месяцев Last Year Последний год Everything Всё Custom Выбрать даты Details_ Details_ Sessions_ Sessions_ Summary_ Summary_ Select file to export to Выберите файл для экспорта CSV Files (*.csv) CSV-файлы (* .csv) DateTime DateTime Session Session Event Event Data/Duration Data/Duration Date Date Session Count Session Count Start Start End End Total Time Total Time AHI AHI Count Count FPIconLoader Import Error Ошибка импорта This Machine Record cannot be imported in this profile. Эти данные не могут быть импортированы в этот профиль. The Day records overlap with already existing content. Дневные записи пересекаются с существующими. Help Hide this message Скрыть это сообщение Search Topic: Поиск: Help Files are not yet available for %1 and will display in %2. Файлы помощи на языке %1 пока недоступны и будут показаны на %2. Help files do not appear to be present. Файлы помощи не найдены. HelpEngine did not set up correctly HelpEngine неправильно настроен HelpEngine could not register documentation correctly. HelpEngine не удалось правильно зарегистрировать документацию. Contents Содержание Index Индекс Search Поиск No documentation available Документация недоступна Please wait a bit.. Indexing still in progress Пожалуйста, подождите. Индексация не закончена No Нет %1 result(s) for "%2" результатов для "%2": %1 clear очистить MD300W1Loader Could not find the oximeter file: Невозможно найти файл оксиметра: Could not open the oximeter file: Невозможно открыть файл оксиметра: MainWindow &Statistics &Статистика Report Mode Режим отчета Standard Стандартный Monthly Месячный Date Range Период Statistics Статистика Daily День Overview Сводка Oximetry Оксиметрия Import Импорт Help Помощь &File &Файл &View &Вид &Reset Graphs &Сбросить графики &Help &Помощь Troubleshooting Решение проблем &Data &Данные &Advanced Д&ополнительно Purge ALL Machine Data Очистить ВСЕ данные аппарата Rebuild CPAP Data Перестроить данные CPAP &Import CPAP Card Data &Импортировать данные с карты CPAP Show Daily view День Show Overview view Сводка &Maximize Toggle &Развернуть Maximize window Развернуть окно Reset Graph &Heights Сбросить &высоты графиков Reset sizes of graphs Сбросить размеры графиков Show Right Sidebar Показать правую панель Show Statistics view Показать статистику Import &Dreem Data Загрузить данные &Dreem Purge Current Selected Day Очистить выбранный день &CPAP &CPAP &Oximetry &Оксиметрия &Sleep Stage &Стадии сна &Position &Положение &All except Notes &Все кроме примечаний All including &Notes Все включая &примечания Show &Line Cursor Показать &линию курсора Show Daily Left Sidebar Показать левую дневную панель Show Daily Calendar Показать дневной календарь Create zip of CPAP data card Создать архив карты памяти CPAP Create zip of OSCAR diagnostic logs Создать архив диагностических журналов OSCAR Create zip of all OSCAR data Создать архив всех данных OSCAR Report an Issue Сообщить о проблеме System Information Информация о системе Show &Pie Chart Показать &круговую диаграмму Show Pie Chart on Daily page Показать круговую диаграмму на дневной странице Standard graph order, good for CPAP, APAP, Bi-Level Стандартный порядок графиков, подходит для CPAP, APAP, Bi-Level Advanced Расширенный Advanced graph order, good for ASV, AVAPS Расширенный порядок графиков, подходит для ASV, AVAPS Show Personal Data Показывать личные данные Check For &Updates Проверить &обновления &Preferences &Настройки &Profiles &Профили &About OSCAR &О программе OSCAR Show Performance Information Показать данные о производительности CSV Export Wizard Мастер экспорта в CSV Export for Review Экспорт для просмотра E&xit В&ыход Exit Выход View &Daily &День View &Overview &Сводка View &Welcome &Начальный экран Use &AntiAliasing Использовать &сглаживание Show Debug Pane Показать панель отладки Take &Screenshot Сделать снимок &экрана O&ximetry Wizard Мастер О&ксиметрии Print &Report Печатать &отчет &Edit Profile &Редактировать профиль Import &Viatom/Wellue Data Импорт данных &Viatom/Wellue Daily Calendar Дневной календарь Backup &Journal Архивировать &дневник Online Users &Guide &Руководство пользователя в Интернете &Frequently Asked Questions &Часто Задаваемые Вопросы &Automatic Oximetry Cleanup &Автоматическая очистка данных оксиметрии Change &User &Сменить пользователя Purge &Current Selected Day &Очистить текущий день Right &Sidebar &Правая панель Daily Sidebar Дневная панель View S&tatistics &Статистика Navigation Навигация Bookmarks Закладки Records Записи Exp&ort Data &Экспорт данных Profiles Профили Purge Oximetry Data Очистить данные оксиметрии View Statistics Просмотр статистики Import &ZEO Data Импорт данных &ZEO Import RemStar &MSeries Data Импорт данных RemStar &Mseries Sleep Disorder Terms &Glossary &Словарь и терминология расстройств сна Change &Language Сменить &язык Change &Data Folder Сменить папку &данных Import &Somnopose Data Импорт данных &Somnopose Current Days Текущий день Welcome Начало &About &О программе Please wait, importing from backup folder(s)... Подождите, идет импорт из папок резервного копирования… Import Problem Проблема импорта данных Please insert your CPAP data card... Вставьте карту памяти CPAP... Access to Import has been blocked while recalculations are in progress. Доступ к импорту данных заблокирован на время пересчета. CPAP Data Located Обнаружены данные CPAP Import Reminder Напоминание об импорте данных Find your CPAP data card Найти карту памяти CPAP Importing Data Импорт данных Choose where to save screenshot Выберите место сохранения снимка экрана Image files (*.png) Изображения (*.png) The User's Guide will open in your default browser Руководство пользователя откроется в вашем браузере по умолчанию The FAQ is not yet implemented FAQ еще не реализован If you can read this, the restart command didn't work. You will have to do it yourself manually. Если вы видите это, перезапуск не сработал. Перезапустите приложение вручную. Are you sure you want to rebuild all CPAP data for the following machine: Подтвердите полную перестройку данных CPAP для данного аппарата: For some reason, OSCAR does not have any backups for the following machine: По какой-то причине OSCAR не содержит резервных копий для данного аппарата: OSCAR does not have any backups for this machine! OSCAR не содержит резервных копий для этого аппарата! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Если вы не сделали <i>свою <b>собственную</b> резервную копию ВСЕХ ваших данных для этого аппарата,</i>, <font size=+2> вы потеряете данные этого аппарата <b>навсегда</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Вы собираетесь <font size=+2>удалить</font> базу данных OSCAR для этого аппарата:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: Не удалось завершить очистку из за проблем с доступом к файлам; нужно удалить папку вручную: No help is available. Справка недоступна. %1's Journal Дневник пользователя %1 Choose where to save journal Выберите, куда сохранить дневник XML Files (*.xml) XML-файлы (* .xml) Export review is not yet implemented Экспорт для обзора еще не реализован Would you like to zip this card? Заархивировать эту карту? Choose where to save zip Выберите, куда сохранить архив ZIP files (*.zip) ZIP-файлы (* .zip) Creating zip... Создание архива... Calculating size... Расчет размера файла... Reporting issues is not yet implemented Сообщения о проблемах еще не реализованы OSCAR Information Информация OSCAR Help Browser Справка %1 (Profile: %2) %1 (профиль: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Выбирайте корневую папку или диск вашей карты памяти, а не папку внутри нее. Please open a profile first. Сначала откройте профиль. Check for updates not implemented Проверка обновлений не реализована Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. При условии, что вы сделали <i>свою <b>собственную</b> резервную копию всех данных CPAP</i>, вы все еще можете завершить эту операцию, но нужно будет восстановить резервные копии вручную. Are you really sure you want to do this? Уверены, что хотите это сделать? Because there are no internal backups to rebuild from, you will have to restore from your own. Поскольку нет внутренних резервных копий, используйте свои копии для восстановления. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Хотите импортировать собственные резервные копии? (У вас не будет данных, видимых для этого аппарата, пока вы этого не сделаете) Note as a precaution, the backup folder will be left in place. Предупреждение: папка с архивом будет сохранена. Are you <b>absolutely sure</b> you want to proceed? Вы <b>абсолютно уверены</b> что хотите продолжить? The Glossary will open in your default browser Глоссарий откроется в вашем браузере по умолчанию There was a problem opening %1 Data File: %2 Ошибка открытия файла данных %1: %2 %1 Data Import of %2 file(s) complete Импорт данных %1 из %2 файлов завершен %1 Import Partial Success Импорт %1 выполнен частично %1 Data Import complete Импорт данных %1 завершен Are you sure you want to delete oximetry data for %1 Удалить данные оксиметрии за %1? <b>Please be aware you can not undo this operation!</b> <b>Предупреждение: отменить эту операцию невозможно!</b> Select the day with valid oximetry data in daily view first. Выберите день с корректными данными оксиметрии в обзоре дня. Loading profile "%1" Загрузка профиля "%1" Imported %1 CPAP session(s) from %2 %1 сеанс(ов) CPAP импортировано из %2 Import Success Импорт успешно завершен Already up to date with CPAP data at %1 Данные CPAP уже загружены из %1 Up to date Обновление Couldn't find any valid Machine Data at %1 Не удалось найти корректные данные: %1 Choose a folder Выберите папку No profile has been selected for Import. Не выбран профиль для импорта. Import is already running in the background. Импорт уже работает в фоновом режиме. A %1 file structure for a %2 was located at: Обнаружена структура файлов %1 для %2: A %1 file structure was located at: Обнаружена структура файлов %1: Would you like to import from this location? Импортировать эти данные? Specify Выбрать Access to Preferences has been blocked until recalculation completes. Доступ к настройкам заблокирован до завершения пересчета. There was an error saving screenshot to file "%1" Ошибка сохранения снимка экрана в файл "%1" Screenshot saved to file "%1" Снимок сохранен в файл "%1" Please note, that this could result in loss of data if OSCAR's backups have been disabled. Обратите внимание, что это может привести к потере данных, если резервное копирование OSCAR было отключено. There was a problem opening MSeries block File: Ошибка открытия файла MSeries: MSeries Import complete Импорт данных MSeries завершен MinMaxWidget Auto-Fit Авто Defaults По умолчанию Override Переопределить The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Режим масштабирования оси Y: 'Авто' для автоматического масштабирования, По умолчанию' для параметров производителя, 'Переопределить' для самостоятельного выбора. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Минимальное значение оси Y. Примечание: может быть отрицательным. The Maximum Y-Axis value.. Must be greater than Minimum to work. Максимальное значение оси Y. Должно быть больше, чем минимальное. Scaling Mode Масштабирование This button resets the Min and Max to match the Auto-Fit Эта кнопка сбрасывает значения минимума и максимума, чтобы соответствовать автонастройке NewProfile Edit User Profile Редактирование профиля пользователя I agree to all the conditions above. Я согласен со всеми вышеуказанными условиями. User Information Данные пользователя User Name Имя пользователя Password Protect Profile Защитить профиль паролем Password Пароль ...twice... Еще раз Locale Settings Региональные настройки Country Страна TimeZone Часовой пояс about:blank about:blank Very weak password protection and not recommended if security is required. Очень слабая защита пароля; не рекомендуется, если нужна защита. DST Zone Летнее время Personal Information (for reports) Персональные данные (для отчетов) First Name Имя Last Name Фамилия It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Можно не указывать или указать неправильно, но возраст используется для улучшения точности некоторых расчетов. D.O.B. Дата рождения <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html> <head/><body><p> Ваш биологический пол(при рождении) требуется, чтобы повысить точность некоторых расчетов. Вы можете не заполнять эти данные.</p></body></html> Gender Пол Male мужской Female женский Height Рост Metric метрические English имперские Contact Information Контактные данные Address Адрес Email Эл. почта Phone Телефон CPAP Treatment Information Параметры CPAP терапии Date Diagnosed Дата диагноза Untreated AHI Начальный AHI CPAP Mode Режим CPAP CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure Давление RX Doctors / Clinic Information Данные врача и клиники Doctors Name Имя врача Practice Name Специализация врача Patient ID Идентификатор пациента &Cancel &Отмена &Back &Назад &Next &Дальше Select Country Выберите страну Welcome to the Open Source CPAP Analysis Reporter Добро пожаловать в програмное обеспечение с открытым исходным кодом для анализа CPAP данных This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Это программное обеспечение предназначено для помощи в анализе данных, сгенерированных аппаратами CPAP и другим оборудованием. PLEASE READ CAREFULLY Пожалуйста, прочитайте внимательно Accuracy of any data displayed is not and can not be guaranteed. Точность отображаемых данных не может быть гарантирована. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Все отчеты предназначены исключительно для личного пользования, и не предназначены для оценки и диагностики. Use of this software is entirely at your own risk. Вы несете полную ответственность за использование данного программного обеспечения. OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR свободно распостраняется в соответствии с </a href='qrc://COPYING'>GNU Public License v3</a> и поставляется без гарантии и без каких-либо претензий на соответствие любого рода. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR предназначен исключительно для просмотра данных, и ни в коей мере не заменяет проффесиональные медицинские указания вашего врача. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Авторы не будут нести никакой ответственности за <u>что угодно</u> связанное с использованием, либо неправильным использованием этого программного обеспечения. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team Авторские права на OSCAR: &copy;2011-2018 Mark Watkins и частично &copy;2019-2022 The OSCAR Team Please provide a username for this profile Укажите имя пользователя для этого профиля Passwords don't match Пароли не совпадают Profile Changes Профиль изменен Accept and save this information? Сохранить эту информацию? &Finish &Закончить &Close this window &Закрыть Overview Range: Период: Last Week Последняя неделя Last Two Weeks Последние две недели Last Month Последний месяц Last Two Months Последние два месяца Last Three Months Последние три месяца Last 6 Months Последние 6 месяцев Last Year Последний год Everything Всё Custom Выбрать даты Snapshot Снимок Start: начало: End: конец: Reset view to selected date range Показать выбранный период Toggle Graph Visibility Изменить видимость графика Drop down to see list of graphs to switch on/off. Раскройте, чтобы увидеть список графиков для включения. Graphs Графики Respiratory Disturbance Index Индекс нарушения дыхания (RDI) Apnea Hypopnea Index Индекс апноэ гипоапноэ (AHI) Usage Использование Usage (hours) Использование (часы) Session Times Время сеанса Total Time in Apnea Время в апноэ Total Time in Apnea (Minutes) Время в апноэ (минуты) Body Mass Index Индекс массы тела (BMI) How you felt (0-10) Самочувствие (0-10) 10 of 10 Charts 10 из 10 графиков Show all graphs Показать все графики Hide all graphs Скрыть все графики OximeterImport Oximeter Import Wizard Мастер импорта оксиметрии Skip this page next time. Не показывать эту страницу в следующий раз. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Обратите внимание: </span><span style=" font-style:italic;">Сначала выберите тип вашего оксиметра из списка ниже.</span></p></body></html> Where would you like to import from? Откуда импортировать? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">Выберите ваш оксиметр из перечисленных:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. Пользователи CMS50E/F: при импорте с устройста, не начинайте загрузку на устройстве, пока OSCAR не предложит это сделать. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Если включено, OSCAR автоматически установит внутренние часы CMS50, используя текущее время на компьютере.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>Здесь можно задать 7-буквенное имя для этого оксиметра.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Этот вариант удаляет импортированный сеанс с вашего оксиметра после завершения импорта.</p><p>Используйте осторожно - если что-то пойдет не так, вы не сможете вернуть данные.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Этот вариант позволяет импортировать данные (через кабель) из оксиметра.</p><p>В cтарых оксиметрах Contec нужно использовать меню устройства для начала загрузки.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p> Если вас устраивает подключение к работающему компьютеру в течение ночи, этот вариант предоставит плетизмограмму с индикацией сердечного ритма, вместе с обычными данными оксиметра.</p></body></html> Record attached to computer overnight (provides plethysomogram) Подключение к компьютеру в течение ночи (предоставляет плетизмограмму) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Этот вариант позволяет импортировать файлы данных, созданные программным обеспечением, поставляемым с пульсоксиметром, например SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Импортировать из стороннего файла данных, например SpO2Review Please connect your oximeter device Подключите оксиметр If you can read this, you likely have your oximeter type set wrong in preferences. Если вы видите это, вы, вероятно, неправильно выбрали тип оксиметра в настройках. Please connect your oximeter device, turn it on, and enter the menu Подключите оксиметр, включите его и зайдите в меню Press Start to commence recording Нажмите "Начать" (Start), чтобы начать запись Show Live Graphs Показывать графики в реальном времени Duration Длительность Pulse Rate Частота пульса Multiple Sessions Detected Обнаружены несколько сеансов Start Time Время начала Details Подробности Import Completed. When did the recording start? Импорт завершен. Когда началась запись? Oximeter Starting time Время запуска оксиметра I want to use the time reported by my oximeter's built in clock. Использовать время из встроенных часов оксиметра. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Запись оксиметра начата одновременно (или почти) с началом сеанса на аппарате CPAP. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Примечание: синхронизация с сеансом CPAP всегда будет более точной.</p></body></html> Choose CPAP session to sync to: Выберите сеанс CPAP для синхронизации: You can manually adjust the time here if required: Можно самостоятельно установить время при необходимости: HH:mm:ssap HH:mm:ssap &Cancel &Отмена &Information Page &Информационная страница CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Установить дату и время на устройстве <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Включить обновление идентификатора устройства во время следующего импорта. Это полезно, когда есть несколько оксиметров.</p></body></html> Set device identifier Установить идентификатор устройства Erase session after successful upload Удалить сеанс после успешной загрузки Import directly from a recording on a device Импортировать непосредственно из устройства <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Напоминание для пользователей CPAP: </span><span style=" color:#fb0000;">Сначала нужно импортировать сеансы CPAP.<br/></span> Если этого не сделать, у вас не будет точного времени для синхронизации оксиметрического сеанса.<br/>Для хорошей синхронизации между устройствами, всегда старайтесь начать сеанс CPAP и сеанс оксиметрии одновременно.</p></body></html> Please choose which one you want to import into OSCAR Выберите, какой из них импортировать в OSCAR Day recording (normally would have) started День начала записи <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR нуждается в начальном времени для сохранения этого сеанса оксиметрии.</p><p>Выберите один из вариантов:</p></body></html> &Retry &Повторить &Choose Session &Выбрать сессию &End Recording &Закончить запись &Sync and Save С&инхронизировать и сохранить &Save and Finish &Сохранить и закончить &Start &Начать Scanning for compatible oximeters Поиск совместимых оксиметров Could not detect any connected oximeter devices. Не удалось обнаружить подключенные оксиметрические устройства. Connecting to %1 Oximeter Подключение к оксиметру %1 Renaming this oximeter from '%1' to '%2' Переименование оксиметра из '%1' в ' %2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Имя оксиметра отличается. Если у вас только один оксиметр и вы используете его в разных профилях, установите одно и то же имя во всех профилях. "%1", session %2 "%1", сеанс %2 Nothing to import Нечего импортировать Your oximeter did not have any valid sessions. Оксиметр не содержит корректных сессий. Close Закрыть Waiting for %1 to start Ожидание запуска %1 Waiting for the device to start the upload process... Ожидание начала загрузки с устройства... Select upload option on %1 Выберите опцию загрузки на %1 You need to tell your oximeter to begin sending data to the computer. Включите передачу данных на компьютер в оксиметре. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Подключите свой оксиметр, зайдите в его меню и выберите Загрузить (upload), чтобы начать передачу данных... %1 device is uploading data... Устройство %1 загружает данные... Please wait until oximeter upload process completes. Do not unplug your oximeter. Дождитесь завершения процесса загрузки оксиметром. Не отключайте его. Oximeter import completed.. Импорт оксиметра завершен.. Select a valid oximetry data file Выберите корректный файл данных оксиметрии Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Файлы оксиметрии (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Нет подходящего модуля оксиметрии для распознавания файла: Live Oximetry Mode Оксиметрия в реальном времени Live Oximetry Stopped Оксиметрия в реальном времени остановлена Live Oximetry import has been stopped Импорт оксиметрии в реальном времени был остановлен Oximeter Session %1 Сеанс оксиметра %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR позволяет отслеживать данные оксиметрии вместе с данными сеансов CPAP, что может дать полезную информацию об эффективности CPAP терапии. Можно также работать только с самим пульсоксиметром: сохранять, отслеживать и просматривать записанные данные. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Если вы собираетесь синхронизировать данные оксиметрии и CPAP, убедитесь, что вы импортировали свои сеансы CPAP, прежде чем продолжить! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Чтобы OSCAR смог найти и прочесть данные с вашего оксиметра, на компьютере должны быть установлены нужные драйверы устройств (например, USB to Serial UART). Для получения дополнительной информации об этом %1нажмите здесь%2. Oximeter not detected Оксиметр не обнаружен Couldn't access oximeter Невозможно получить доступ к оксиметру Starting up... Запуск... If you can still read this after a few seconds, cancel and try again Если вы все еще видите это сообщение через несколько секунд, отмените и попробуйте снова Live Import Stopped Импорт в реальном времени остановлен %1 session(s) on %2, starting at %3 %1 сеанс(ов) за %2, начиная с %3 No CPAP data available on %1 Нет данных CPAP доступных на %1 Recording... Запись... Finger not detected Палец не обнаружен I want to use the time my computer recorded for this live oximetry session. Использовать время компьютера для этой оксиметрической сессии в реальном времени. I need to set the time manually, because my oximeter doesn't have an internal clock. Установить время вручную, так как оксиметр не имеет внутренних часов. Something went wrong getting session data Что-то пошло не так при получении данных сеанса Welcome to the Oximeter Import Wizard Добро пожаловать в Мастер импорта оксиметрии Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Пульсоксиметры - это медицинские устройства, используемые для измерения насыщения крови кислородом. Во время апноэ и ненормальных форм дыхания, уровень кислорода в крови может значительно снижаться, и может указывать на проблемы, требующие медицинского вмешательства. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) В настоящее время OSCAR совместим с оксиметрами Contec CMS50D +, CMS50E, CMS50F и CMS50i.<br/>(Примечание: прямой импорт данных из моделей с Bluetooth <span style="font-weight:600;">вероятно</span> еще невозможен) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Обратите внимание, что другие компании, такие как Pulox, выпускают Contec CMS50 под другими названиями, например Pulox PO-200, PO-300, PO-400. Они тоже будут работать. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Можно также загружать .dat файлы оксиметра ChoiceMMed MD300W1. Please remember: Пожалуйста, помните: Important Notes: Важное замечание: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Устройства Contec CMS50D+ не имеют внутренних часов и не записывают время запуска. Если у вас нет сеанса CPAP, чтобы связать запись, нужно будет ввести время начала вручную после завершения процесса импорта. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Даже для устройств с внутренними часами, рекомендуется запускать запись оксиметрии одновременно с сеансом CPAP, так как внутренние часы CPAP теряют точность со временем, и не все можно легко установить. Oximetry Date Дата d/MM/yy h:mm:ss AP d/MM/yy h:mm:ss AP R&eset &Сбросить Pulse Пульс &Open .spo/R File &Открыть .spo/R Serial &Import &Импорт из порта &Start Live &Начать запись Serial Port Последовательный порт &Rescan Ports &Пересканировать порты PreferencesDialog Preferences Настройки &Import &Импорт Combine Close Sessions Объединить близкие сеансы Minutes минуты Multiple sessions closer together than this value will be kept on the same day. Сеансы, расположенные друг к другу ближе, чем это значение, будут записываться на один день. Ignore Short Sessions Игнорировать короткие сеансы <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Сеансы короче этого времени не будут показаны<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Время завершения дня Sessions starting before this time will go to the previous calendar day. Сеансы, начинающиеся до этого времени, пойдут в предыдущий календарный день. Session Storage Options Параметры хранения сеансов Compress SD Card Backups (slower first import, but makes backups smaller) Сжимать резервные копии SD-карт (первый импорт медленнее, зато резервные копии меньше) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Рассматривать дни с временем использования менее данного значения, как "несоответствующие". Четырех часов обычно достаточно. hours час Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Включить/выключить экспериментальные метки событий. Это позволяет обнаруживать граничные события, которые некоторые аппараты пропускают. Опция должна быть включена перед импортом, в противном случае требуется очистка. Flow Restriction Ограничение потока Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Отклонение ограничения потока от медианного значения. Установка в 20% достаточно хорошо определяет апноэ. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Дополнительные метки являются экспериментальным способом определения событий, пропущенных аппаратом. Они <span style=" text-decoration: underline;">не</span> учитываются в AHI.</p></body></html> Duration of airflow restriction Длительность перекрытия воздушного потока s с Event Duration Длительность события Allow duplicates near machine events. Разрешить дублирование событий аппарата. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Устанавливает объем данных для каждой точки на графике AHI/час. По умолчанию 60 минут. Настоятельно рекомендуется оставить его таким. minutes минут Reset the counter to zero at beginning of each (time) window. Сбрасывать счетчик до нуля в начале каждого (временного) окна. Zero Reset Обнулять CPAP Clock Drift Сдвиг часов CPAP Do not import sessions older than: Не импортировать сеансы после: Sessions older than this date will not be imported Сеансы после этой даты не будут импортированы dd MMMM yyyy dd MMMM yyyy User definable threshold considered large leak Пороговое значение, считающееся большой утечкой Whether to show the leak redline in the leak graph Показывать границу избыточной утечки на графике Search Поиск &Oximetry &Оксиметрия Show in Event Breakdown Piechart Показывать в диаграмме событий Resync Machine Detected Events (Experimental) Пересинхронизировать события аппарата (экспериментальная опция) Percentage drop in oxygen saturation Процент падения сатурации Pulse Пульс Sudden change in Pulse Rate of at least this amount Внезапное изменение пульса по меньшей мере на это значение bpm уд/мин Minimum duration of drop in oxygen saturation Минимальная длительность падения сатурации Minimum duration of pulse change event. Минимальная длительность изменения пульса. Small chunks of oximetry data under this amount will be discarded. Фрагменты оксиметрических данных меньше этого значения будут игнорированы. &General &Общие Changes to the following settings needs a restart, but not a recalc. Изменение этих параметров требует перезапуска, но не требует пересчета. Preferred Calculation Methods Предпочтительные методы расчета Middle Calculations Расчет среднего Upper Percentile Верхний процентиль Session Splitting Settings Настройки разделения сеансов <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style="font-weight: 600;">Этот параметр следует использовать осторожно.</span> Его отключение может повлечь за собой потерю точности информации в сводные дни. Так происходит, поскольку некоторые расчеты работают должным образом только при условии, что данные за отдельные дни и сводные данные о сеансах хранятся в одном месте.</p><p><span style = "font-weight: 600;"> Пользователи ResMed:</span> Хотя логично, что дневной сеанс начинается в 12 дня вчера, но это не всегда так с данными ResMed. Формат сводного индекса STF.edf имеет серьезные недостатки, из-за которых использовать эту опцию не рекомендуется.</p><p>Эта опция существует, чтобы успокоить тех, кому все равно и кто хочет видеть это &quot;исправленным&quot; независимо от затрат, но знает, что за это придется заплатить. Если вы оставляете SD-карту каждый вечер в аппарате и импортируете ее раз в неделю, вы почти не столкнетесь с этой проблемой.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Не разделять сводные дни (предупреждение: прочитайте подсказку) Memory and Startup Options Параметры памяти и запуска Pre-Load all summary data at startup Загрузить все сводные данные при запуске <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p> Эта настройка сохраняет данные сигнала и данные событий в памяти после использования для ускорения повторного просмотра.</p><p>Это не критично, так как ваша операционная система тоже кэширует ранее использованные файлы.</p><p>Рекомендуется оставить ее выключенной, если ваш компьютер не имеет очень много памяти.</p></body></html> Keep Waveform/Event data in memory Хранить данные и события в памяти <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Убирает неважные диалоговые окна во время импорта.</p></body></html> Import without asking for confirmation Импортировать без запроса о подтверждении This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Для этого расчета нужны данные аппарата об общих утечках (например, PRS1, но не ResMed, который сам по себе показывает эти данные) Расчеты непреднамеренных утечек, используемые здесь, являются линейными, они не учитывают вентиляционную кривую маски. Если вы используете разные маски, выберите средние значения. Этого должно быть достаточно. Calculate Unintentional Leaks When Not Present Рассчитывать излишние утечки, когда необходимо Note: A linear calculation method is used. Changing these values requires a recalculation. Примечание: Используется линейный метод вычислений. Изменение этих значений требует пересчета. General CPAP and Related Settings Общие настройки CPAP и связанные с ними настройки Enable Unknown Events Channels Включить каналы неизвестных событий AHI Apnea Hypopnea Index Индекс апноэ/гипапноэ (AHI) RDI Respiratory Disturbance Index Индекс нарушений дыхания (RDI) AHI/Hour Graph Time Window Окно графика AHI/час Preferred major event index Предпочтительный индекс событий Compliance defined as Соответствие определяется как Flag leaks over threshold Отмечать утечки выше порога Seconds Секунды <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Примечание: не предназначено для исправления часового пояса! Убедитесь, что время и часовой пояс операционной системы установлены правильно.</p></body></html> Hours Часы For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Пользователям Resmed нужно использовать здесь 95%, так как это единственное значение, доступное для сводных дней. Median is recommended for ResMed users. Медиана рекомендуется для пользователей ResMed. Median Медианный Weighted Average Взвешенное среднее Normal Average Нормальное среднее True Maximum Истинный максимум 99% Percentile 99-й процентиль Maximum Calcs Расчеты максимумов General Settings Общие настройки Daily view navigation buttons will skip over days without data records Кнопки дневной навигации будут пропускать дни без данных Skip over Empty Days Пропускать пустые дни Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Разрешить использование нескольких ядер CPU для повышения производительности. В основном влияет на импорт. Enable Multithreading Разрешить многопоточность Bypass the login screen and load the most recent User Profile Пропустить экран входа и загрузить последний профиль пользователя Create SD Card Backups during Import (Turn this off at your own peril!) Создавать резервные копии SD-карты во время импорта (выключайте на свой страх и риск) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Истинный максимум - это максимум набора данных.</p><p>99-й процентиль отбрасывает случайные и нелогичные значения.</p></body></html> Combined Count divided by Total Hours Общее количество деленное на общее время Time Weighted average of Indice Временное взвешенное среднее индексов Standard average of indice Стандартное среднее индексов Custom CPAP User Event Flagging Дополнительные отметки CPAP событий Events События Reset &Defaults Загрузить значения по &умолчанию <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span Style=" font-weight:600;">Предупреждение: </span>То, что вы можете это сделать, не означает, что это хорошо.</p></<body></html> Waveforms Сигналы Flag rapid changes in oximetry stats Отмечать быстрые изменения в оксиметрии Other oximetry options Прочие настройки оксиметрии Discard segments under Игнорировать участки меньше Flag Pulse Rate Above Отмечать пульс выше Flag Pulse Rate Below Отмечать пульс ниже Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Сжимать резервные копии ResMed (EDF) для экономии места на диске. Резервные копии файлов EDF хранятся в формате .gz, который распространен на платформах Mac и Linux. OSCAR может выполнять импорт из этих сжатых резервных копий напрямую. Чтобы использовать их с ResScan, сначала необходимо распаковать файлы .gz. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Эти параметры влияют на использование дискового пространства и на время импорта. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Наполовину уменьшает объем данных OSCAR, но переключение между днями и импорт замедляются. Если у вас новый компьютер с твердотельным диском небольшого объема, это хороший вариант. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Сжимать данные сеансов (уменьшает объем данных OSCAR, но замедляет переключение между днями) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Сохраняет резервную копию SD-карты для машин ResMed, Машины серии ResMed S9 удаляют данные высокого разрешения старше 7 дней, и данные графиков старше 30 дней. OSCAR может сохранить копию этих данных, если вам когда-нибудь понадобится переустановка. (Настоятельно рекомендуется, если у вас хватает места на диске и вам нужны данные графиков) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Немного замедляет запуск OSCAR за счет предварительной загрузки всех сводных данных, что ускоряет обзорный просмотр а также некоторые другие вычисления в дальнейшем.</p><p>Если у вас большой объем данных, возможно, стоит оставить это выключенным, однако если вы предпочитаете просматривать <span style="font-style: italic;">все</span> данные, все равно придется загрузить все сводные данные.</p><p>Обратите внимание, что этот параметр не влияет на данные сигналов и событий, которые должны быть загружены в любом случае.</p></body></html> 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Эта экспериментальная настройка пытается использовать систему отметок Oscar, чтобы улучшить позиционирование событий, обнаруженных аппаратом. Show flags for machine detected events that haven't been identified yet. Показать отметки для событий, обнаруженных аппаратом, которые еще не были идентифицированы. Show Remove Card reminder notification on OSCAR shutdown Напоминать про карту памяти про закрытии OSCAR Check for new version every Проверять новую версию каждые days. дней. Last Checked For Updates: Последняя проверка обновлений: TextLabel TextLabel I want to be notified of test versions. (Advanced users only please.) Получать уведомления о тестовых версиях. (Только для опытных пользователей) &Appearance &Внешний вид Graph Settings Настройки графиков <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><head/><body><p> Какую вкладку открывать при загрузке профиля. (Примечание: Если OSCAR настроен не открывать профиль при запуске, по умолчанию будет открываться вкладка Профиль)</p></body></html> Bar Tops Гистограмма Line Chart Линейная диаграмма Overview Linecharts Сводные графики Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Попробуйте изменить значение по умолчанию (Desktop OpenGL), если у вас есть проблемы с рендерингом графиков в OSCAR. <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Облегчает прокрутку при увеличении на чувствительных двунаправленных тачпадах.</p><p>Рекомендованное значение: 50ms </p></body></html> How long you want the tooltips to stay visible. Как долго оставлять подсказки видимыми. Scroll Dampening Сглаживание прокрутки Tooltip Timeout Время отображения подсказок Default display height of graphs in pixels Высота отображения графиков по умолчанию в пикселях Graph Tooltips Подсказки на графиках The visual method of displaying waveform overlay flags. Способ отображения отметок на графике. Standard Bars Гистограмма Top Markers Верхние маркеры Graph Height Высота графика Changing SD Backup compression options doesn't automatically recompress backup data. Изменение параметров сжатия SD не подразумевает автоматического переархивирования данных резервного копирования. Auto-Launch CPAP Importer after opening profile Запускать импорт данных CPAP после открытия профиля Automatically load last used profile on start-up Загружать последний профиль при запуске <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p> Выдавать предупреждение при импорте данных из аппарата, модель которого еще не была протестирована разработчиками OSCAR.</p></body></html> Warn when importing data from an untested machine Предупреждать при импорте данных из непроверенного аппарата <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p> Выдавать предупреждение при импорте данных, которые каким-то образом отличаются от чего-либо ранее замеченного разработчиками Oscar.</p></body></html> Warn when previously unseen data is encountered Предупредить, если встречаются ранее неизвестные данные Your masks vent rate at 20 cmH2O pressure Интенсивность вентиляции вашей маски при давлении 20 cmH2O Your masks vent rate at 4 cmH2O pressure Интенсивность вентиляции вашей маски при давлении 4 cmH2O <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><head/><body><p><span style=" font-weight: 600;">Примечание: </span>из-за ограничений дизайна, аппараты ResMed не поддерживают изменение этих настроек.</p></body></html> Oximetry Settings Настройки оксиметрии Always save screenshots in the OSCAR Data folder Всегда сохранять снимки экрана в папку данных OSCAR Check For Updates Проверка обновлений You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Вы используете тестовую версию OSCAR. Тестовые версии проверяют наличие обновлений автоматически, не реже одного раза в семь дней. Вы можете установить интервал менее семи дней. Automatically check for updates Автоматически проверять наличие обновлений How often OSCAR should check for updates. Как часто OSCAR должен проверять наличие обновлений. If you are interested in helping test new features and bugfixes early, click here. Если вы хотите помочь с тестированием новых функций и исправлений на ранней стадии, нажмите здесь. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Если вы хотите помочь протестировать ранние версии OSCAR, ищите на Wiki страницу о тестировании OSCAR. Мы приветствуем всех, кто хотел бы тестировать OSCAR, помогать в разработке OSCAR и переводе на существующие или новые языки. https://www.sleepfiles.com/OSCAR On Opening При открытии Profile Профиль Welcome Начало Daily День Statistics Статистика Switch Tabs Переключать вкладки No change Не менять After Import После импорта Overlay Flags Отметки Line Thickness Толщина линии The pixel thickness of line plots Толщина линий в пикселях Other Visual Settings Другие визуальные настройки Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Включает сглаживание графиков. Некоторые графики при этом выглядят лучше. Это также влияет на печать графиков в отчетах. Попробуйте и посмотрите, понравится ли вам. Use Anti-Aliasing Использовать сглаживание Makes certain plots look more "square waved". Делает некоторые графики более "квадратными". Square Wave Plots Прямоугольные графики Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Кэширование Pixmap - это метод графического ускорения. Может вызвать проблемы со шрифтами на графиках. Use Pixmap Caching Использовать Pixmap Caching <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Эти возможности сейчас откючены. Они вернутся позже.</p></body></html> Animations && Fancy Stuff Анимация и украшения Whether to allow changing yAxis scales by double clicking on yAxis labels Разрешить изменять масштаб оси Y двойным щелчком на ярлыки оси Allow YAxis Scaling Разрешить масштабирование оси Y Whether to include machine serial number on machine settings changes report Включать серийный номер аппарата в отчет изменения настроек аппарата Include Serial Number Включать серийный номер Graphics Engine (Requires Restart) Графический движок (требуется перезапуск) l/min л/мин <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Суммарные индексы</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <html><head/><body><p>Отмечать десатурацию SpO<span style=" vertical-align:sub;">2</span> ниже</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Синхронизация данных оксиметрии и CPAP</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Данные CMS50, импортированные из SpO2Review (из файлов .spoR), или полученные напрямую </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">не содержат</span><span style=" font-family:'Sans'; font-size:10pt;"> корректных временных меток, необходимых для синхронизации.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Режим просмотра в реальном времени (с подключением кабеля) - это один из вариантов добиться точной синхронизации на оксиметрах CMS50, но он не учитывает дрейф часов CPAP.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Если вы запускаете режим записи оксиметра </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">одновременно </span><span style=" font-family:'Sans'; font-size:10pt;">с запуском аппарата CPAP, это тоже поможет синхронизации. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Импорт из последовательного порта получает время начала из последнего сеанса CPAP. Не забудьте импортировать данные CPAP заранее.</span></p></body></html> Print reports in black and white, which can be more legible on non-color printers Печатать монохромные отчеты, которые могу быть более различимы на нецветных принтерах Print reports in black and white (monochrome) Печатать монохромные отчеты Fonts (Application wide settings) Шрифты Font Шрифт Size Размер Bold Жирный Italic Наклонный Application Приложение Graph Text Текст графиков Graph Titles Заголовки графиков Big Text Большой текст Details Назначение &Cancel &Отмена &Ok &Ok Name Название Color Цвет Flag Type Тип события Label Ярлык CPAP Events События CPAP Oximeter Events События оксиметрии Positional Events Позиционные события Sleep Stage Events События стадий сна Unknown Events Неизвестные события Double click to change the descriptive name this channel. Дважды щелкните, чтобы изменить описание этого канала. Double click to change the default color for this channel plot/flag/data. Дважды щелкните, чтобы изменить цвет по умолчанию для данных этого канала. Overview Сводка <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Обратите внимание:</b> расширенные возможности разделения сеансов OSCAR невозможны с аппаратом <b>ResMed</b> из-за ограниченых возможностей хранения их данных и настроек, поэтому они отключены для этого профиля.</p><p>На аппаратах ResMed дни <b> разделяются в полдень</b>, как и в программном обеспечении Resmed.</p> Double click to change the descriptive name the '%1' channel. Дважды щелкните, чтобы изменить описание канала '%1'. Whether this flag has a dedicated overview chart. Имеет ли этот канал свою обзорную диаграмму. Here you can change the type of flag shown for this event Тип отметки, используемой для этого события This is the short-form label to indicate this channel on screen. Короткое название этого канала для отображения. This is a description of what this channel does. Описание того, что делает этот канал. Lower Нижний Upper Верхний CPAP Waveforms Графики CPAP Oximeter Waveforms Графики оксиметрии Positional Waveforms Графики позиции сна Sleep Stage Waveforms Графики стадий сна Whether a breakdown of this waveform displays in overview. Показывать ли разбор этого графика в обзоре. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Здесь вы можете установить <b>нижний</b> порог, используемый для расчетов формы графика %1 Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Здесь вы можете установить <b>верхний</b> порог, используемый для расчетов формы графика %1 Data Processing Required Необходима обработка данных A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Чтобы эти изменения вступили в силу, нужно переархивировать данные. Это займет несколько минут. Вы уверены, что хотите сделать эти изменения? Data Reindex Required Необходима переиндексация данных A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Чтобы эти изменения вступили в силу, нужно переиндексировать данные. Это займет несколько минут. Вы уверены, что хотите сделать эти изменения? Restart Required Необходим перезапуск One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Сделанные вами изменения требуют перезапуска приложения, чтобы эти изменения вступили в силу. Хотите сделать это сейчас? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Если вам когда-нибудь понадобится заново импортировать эти данные (в OSCAR или ResScan), их нельзя будет вернуть. If you need to conserve disk space, please remember to carry out manual backups. Если вам нужно свободное место на диске, не забывайте делать резервные копии вручную. Are you sure you want to disable these backups? Вы уверены, что хотите отключить эти резервные копии? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Отключение резервных копий - не очень хорошая идея, они нужны OSCAR для восстановления базы данных в случае ошибок. Are you really sure you want to do this? Вы действительно уверены, что хотите это сделать? %1 %2 %1 %2 Flag Событие Minor Flag Незначительное событие Span Период Always Minor Всегда незначительное событие No CPAP machines detected CPAP аппараты не найдены Will you be using a ResMed brand machine? Будете ли вы использовать аппарат ResMed? Never Никогда This may not be a good idea Скорей всего это плохая идея ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Аппараты ResMed S9 регулярно удаляют с SD-карты данные, записанные больше 7 и 30 дней назад (в зависимости от точности). ProfileSelector Filter: Фильтр: Reset filter to see all profiles Сбросить фильтр, чтобы увидеть все профили Version Версия &Open Profile &Открыть профиль &Edit Profile &Редактировать профиль &New Profile &Новый профиль Profile: None Профиль: нет Please select or create a profile... Выберите или создайте профиль... Destroy Profile Удалить профиль Profile Профиль Ventilator Brand Аппарат Ventilator Model Модель Other Data Прочее Last Imported Последний импорт Name Имя You must create a profile Нужно создать профиль Enter Password for %1 Введите пароль для %1 You entered an incorrect password Неверный пароль Forgot your password? Забыли пароль? Ask on the forums how to reset it, it's actually pretty easy. Спросите на форуме, как его сбросить, это на самом деле довольно легко. Select a profile first Выберите профиль The selected profile does not appear to contain any data and cannot be removed by OSCAR Выбранный профиль не содержит никаких данных и не может быть удален программой OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Если вы собираетесь удалить профиль, потому что забыли пароль, нужно либо сбросить пароль, либо удалить папку профиля вручную. You are about to destroy profile '<b>%1</b>'. Вы собираетесь удалить профиль '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Хорошо подумайте, так как это безвозвратно удалит профиль вместе со всеми <b>резервными копиями</b>, хранящимися в<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Введите слово <b>DELETE</b> ниже (так, как написано) для подтверждения. DELETE DELETE Sorry Извините You need to enter DELETE in capital letters. Вы должны ввести DELETE заглавными буквами. There was an error deleting the profile directory, you need to manually remove it. Ошибка удаления папки профиля, необходимо удалить её вручную. Profile '%1' was succesfully deleted Профиль '%1' успешно удален Bytes Байт KB Кбайт MB Мбайт GB Гбайт TB Тбайт PB Пбайт Summaries: Сводки: Events: События: Backups: Архивы: Hide disk usage information Скрыть информацию об использовании диска Show disk usage information Показать информацию об использовании диска Name: %1, %2 Имя: %1, %2 Phone: %1 Телефон: %1 Email: <a href='mailto:%1'>%1</a> Электронная почта: <a href='mailto:%1'>%1</a> Address: Адрес: No profile information given Нет информации о профиле Profile: %1 Профиль: %1 ProgressDialog Abort Прервать QObject No Data Нет данных Events События Duration Длительность (% %1 in events) (% %1 в событиях) Jan Янв Feb Фев Mar Мар Apr Апр May Май Jun Июн Jul Июл Aug Авг Sep Сен Oct Окт Nov Ноя Dec Дек ft фут lb фунт oz унц Kg кг cmH2O см H2O Med. Сред. Min: %1 Мин: %1 Min: Мин: Max: Макс: Max: %1 Макс: %1 %1 (%2 days): %1 (%2 дней): %1 (%2 day): %1 (%2 день): % in %1 % в %1 Hours Часы Min %1 Мин %1 Hours: %1 Часы: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 низкое потребление, %2 без потребления, из %3 дней (%4% соответствия.) Длительность: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Сеансы: %1 / %2 / %3 Длительность: %4 / %5 / %6 Максимальный: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Длительность: %3 Начало: %2 Mask On В маске Mask Off Без маски %1 Length: %3 Start: %2 %1 Длительность: %3 Начало: %2 TTIA: Нет устоявшегося сокращения в русской терминологии TTIA: TTIA: %1 TTIA: %1 Minutes минуты Seconds секунды h ч m м s с ms мс Events/hr События за час Hz Гц bpm уд/мин Litres Литры ml мл Breaths/min Вдохи/мин ? ? Severity (0-1) Серьезность (0-1) Degrees Градусы Error Ошибка Warning Предупреждение Information Информация Busy Занят Please Note Замечание Graphs Switched Off Графики отключены Sessions Switched Off Сеансы отключены &Yes &Да &No &Нет &Cancel &Отмена &Destroy &Удалить &Save &Сохранить BMI ИМТ Weight Вес Zombie Зомби Pulse Rate Пульс Plethy Плетизмография Pressure Давление Daily День Profile Профиль Overview Сводка Oximetry Оксиметрия Oximeter Оксиметр Event Flags События Default По умолчанию CPAP CPAP BiPAP BiPAP Bi-Level Двухуровневый EPAP EPAP Min EPAP Мин EPAP Max EPAP Макс EPAP IPAP IPAP Min IPAP Мин IPAP Max IPAP Макс IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Увлажнитель H гипапноэ H OA обструктивное апноэ OA A апноэ A CA центральное апноэ CA FL "ограничение потока" FL SA SA LE утечка (событие) LE EP утечка на выдохе EP VS храп (событие) VS VS2 храп (событие 2) VS2 RERA волнение (пробуждение?) связанное с дыханием REPA PP изменение (пульсация) давления PP P давление (событие) P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF1 UF2 UF2 UF3 UF3 PS PS AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI EPI PB PB IE IE Insp. Time Время вдоха Exp. Time Время выдоха Resp. Event Событие Flow Limitation Ограничение потока Flow Limit Предел потока SensAwake Пробуждение Pat. Trig. Breath вдох вызванный пациентом? Сам. вдох Tgt. Min. Vent Целевая минутная вентиляция Target Vent. Целевая вент. Minute Vent. Минутная вент. Tidal Volume Приливной объем Resp. Rate Частота дыхания Snore Храп Leak Утечка Leaks Утечки Large Leak Значительная утечка LL ЗнУт Total Leaks Всего утечек Unintentional Leaks Случайные утечки MaskPressure Давление маски Flow Rate Поток Sleep Stage Фаза сна Usage Использование Sessions Сеансы Pr. Relief Ослабление давления No Data Available Нет данных App key: Программа: Operating system: Операционная система: Built with Qt %1 on %2 Собрано с Qt %1 для %2 Graphics Engine: Графический движок: Graphics Engine type: Тип графического движка: Software Engine Программный ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Desktop OpenGL m м cm см in " Only Settings and Compliance Data Available Доступны только настройки и данные соответствия Summary Data Only Только итоговые данные Bookmarks Закладки Mode Режим Model Модель Brand Марка Serial Номер Series Серия Machine Аппарат Channel Канал Settings Настройки Inclination Наклон Orientation Направление Motion Движение Name Название DOB Дата рождения Phone Телефон Address Адрес Email Почта Patient ID Номер пациента Date Дата Bedtime Время сна Wake-up Пробуждение Mask Time Время в маске Unknown Неизвестно None Нет Ready Готово First Первый Last Последний Start Начало End Конец On Вкл Off Выкл Yes Да No Нет Min Мин Max Макс Med Мед Average Среднее Median Медиана Avg Сред W-Avg ВзвСред Your %1 %2 (%3) generated data that OSCAR has never seen before. Ваш %1 %2 (%3) предоставил данные, неизвестные OSCAR. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Загруженные данные могут быть неточными, поэтому мы бы хотели получить .zip архив данных вашего аппарата и соответствующие врачебные отчеты .pdf, для улучшения обработки данных. Non Data Capable Machine Аппарат не поддерживает сбор данных Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Ваш аппарат %1 (модель %2) не поддерживает передачу данных. Your %1 CPAP machine (Model %2) has not been tested yet. Ваш аппарат %1 (модель %2) еще не был протестирован. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Выглядит достаточно похоже на другие аппараты, чтобы работать с OSCAR, но мы бы хотели получить .zip архив данных вашего аппарата и соответствующие врачебные отчеты .pdf, чтобы это подтвердить. Sorry, your %1 CPAP machine (%2) is not supported yet. К сожалению, ваш аппарат %1 (%2) еще не поддерживается. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Нам необходим .zip архив данных вашего аппарата и соответствующие врачебные отчеты .pdf, чтобы OSCAR мог с ними работать. Getting Ready... Подготовка... Machine Unsupported Аппарат не поддерживается I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. К сожалению, OSCAR может отследить только время использования и самые основные настройки этого аппарата. Scanning Files... Сканирование файлов... Importing Sessions... Импорт сеансов... Finishing up... Завершение... Machine Untested Аппарат не проверен Flex Lock Блокировка Flex Whether Flex settings are available to you. Доступные настройки Flex. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Время, необходимое для переключения EPAP в IPAP: чем больше, тем медленее переключение Rise Time Lock Блокировка времени подъема Whether Rise Time settings are available to you. Доступные настройки времени подъема. Rise Lock Блокировка подъема Mask Resistance Setting Настройки сопротивления маски Mask Resist. Сопротивление маски Hose Diam. Диаметр трубки 15mm 15 мм 22mm 22 мм Backing Up Files... Резервное копирование... Untested Data Непроверенные данные model %1 модель %1 DreamStation 2 DreamStation 2 unknown model неизвестная модель CPAP-Check Проверка CPAP AutoCPAP AutoCPAP Auto-Trial Auto-Trial AutoBiLevel AutoBiLevel S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Mode Flex режим PRS1 pressure relief mode. Режим ослабления давления PRS1. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time Время подъема Bi-Flex Bi-Flex Flex Flex Flex Level Уровень Flex PRS1 pressure relief setting. Настройки ослабления давления PRS1. Passover Проток Target Time Целевое время PRS1 Humidifier Target Time Целевое время увлажнителя PS1 Hum. Tgt Time Увл. время целевое Tubing Type Lock Блокировка типа трубки Whether tubing type settings are available to you. Доступные настройки типа трубки. Tube Lock Блокировка трубки Mask Resistance Lock Блокировка сопротивления маски Whether mask resistance settings are available to you. Доступные настройки сопротивления маски. Mask Res. Lock Блокировка сопр. маски Whether or not machine shows AHI via built-in display. Отображение ИАГ на встроенном дисплее. Ramp Type Тип разгона Type of ramp curve to use. Тип кривой разгона. Linear Линейный SmartRamp SmartRamp Ramp+ Ramp+ Backup Breath Mode Режим поддержки дыхания The kind of backup breath rate in use: none (off), automatic, or fixed Тип поддержки дыхания: нет (выкл), автоматическая или заданная Breath Rate Частота дыхания Fixed Заданная Fixed Backup Breath BPM Заданная частота дыхания Minimum breaths per minute (BPM) below which a timed breath will be initiated Минимальная частота дыхания (BPM), ниже которой включится поддержка Breath BPM Дыхание, вдох/мин Timed Inspiration Временное дыхание The time that a timed breath will provide IPAP before transitioning to EPAP Время, в течение которого будет поддеживаться IPAP до переключения в EPAP Timed Insp. Временное дых. Auto-Trial Duration Длительность пробы Auto-CPAP The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Число дней пробного периода Auto-CPAP, после которого аппарат вернется к режиму CPAP Auto-Trial Dur. Длительность Auto-CPAP EZ-Start Быстрый запуск Whether or not EZ-Start is enabled Включен ли режим быстрого старта Variable Breathing Переменное дыхание UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend НЕ ПОДТВЕРЖДЕНО: вероятно, переменное дыхание - промежутки значительного отклонения от обычных показателей дыхания Peak Flow Максимальный поток Peak flow during a 2-minute interval Поток за 2-минутный промежуток Humidifier Status Состояние увлажнителя PRS1 humidifier connected? Увлажнитель PRS1 подключен? Disconnected Отключен Connected Подключен Humidification Mode Режим увлажнения PRS1 Humidification Mode Режим работы увлажнителя PRS1 Humid. Mode Режим увлажн Fixed (Classic) Заданное (Classic) Adaptive (System One) Регулируемое (System One) Heated Tube Подогрев трубки Tube Temperature Температура трубки PRS1 Heated Tube Temperature Температура подогреваемой трубки PRS1 Tube Temp. Т. трубки PRS1 Humidifier Setting Настройки увлажнителя PRS1 Hose Diameter Диаметр трубки Diameter of primary CPAP hose Диаметр основной трубки CPAP 12mm 12 мм Auto On Авто включение A few breaths automatically starts machine Аппарат включается после нескольких вдохов Auto Off Авто выключение Machine automatically switches off Аппарат автоматически выключается Mask Alert Сигнализация маски Whether or not machine allows Mask checking. Доступна ли проверка маски. Show AHI Показывать ИАГ Breathing Not Detected Нет дыхания (BND) A period during a session where the machine could not detect flow. Промежуток, в течение которого аппарат не может определить дыхание. BND BND Timed Breath Принудительное дыхание Machine Initiated Breath Дыхание стимулируется аппаратом TB ПД Windows User Пользователь Windows Using В файле , found SleepyHead - , обнаружены данные SleepyHead - You must run the OSCAR Migration Tool Воспользуйтесь OSCAR Migration Tool <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Данные аппарата нужно перестроить, если резервное копирование не было отключено в настройках во время предыдущей загрузки.</i> Launching Windows Explorer failed Не удалось запустить Windows Explorer Could not find explorer.exe in path to launch Windows Explorer. Не удалось найти explorer.exe для запуска Windows Explorer. OSCAR %1 needs to upgrade its database for %2 %3 %4 OSCAR %1 требуется обновление базы данных для %2 %3 %4 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>Для этого OSCAR хранит резервную копию карты памяти вашего аппарата.</b> OSCAR does not yet have any automatic card backups stored for this device. Автоматические резервные копии для этого аппарата еще не делались. This means you will need to import this machine data again afterwards from your own backups or data card. Это значит, что понадобится загрузить данные аппарата заново с резервной копии или карты памяти. Important: Важно: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Если это проблема, нажмите Нет для выхода, и создайте резервную копию профиля самостоятельно, прежде чем запустить OSCAR. Are you ready to upgrade, so you can run the new version of OSCAR? Продолжить обновление до новой версии OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. К сожалению, операция сброса не удалась, и данная версия OSCAR не будет работать. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Включить автоматическое резервное копирование данных, чтобы при следующем обновлении OSCAR мог восстановиться из них? OSCAR will now start the import wizard so you can reinstall your %1 data. Сейчас будет запущен мастер загрузки, чтобы заново загрузить данные %1. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Сейчас OSCAR будет закрыт, и откроется менеджер*файлов, чтобы сделать резервную копию вашего профиля вручную: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Сделайте копию папки с вашим профилем, затем перезапустите OSCAR и завершите обновление. Machine Database Changes Изменение базы данных аппарата Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. После обновления, <font size=+1>невозможно</font> будет использовать данный профиль с предущей версией. The machine data folder needs to be removed manually. Нужно удалить папку с данными аппарата вручную. This folder currently resides at the following location: Эта папка сейчас находится здесь: Rebuilding from %1 Backup Восстановление из резервной копии %1 Therapy Pressure Давление Inspiratory Pressure Давление вдоха Lower Inspiratory Pressure Минимальное давление вдоха Higher Inspiratory Pressure Максимальное давление вдоха Expiratory Pressure Давление выдоха Lower Expiratory Pressure Минимальное давление выдоха Higher Expiratory Pressure Максимальное давление выдоха Pressure Support Давление поддержки (ДП) PS Min Мин ДП Pressure Support Minimum Минимальное давление поддержки PS Max Макс ДП Pressure Support Maximum Максимальное давление поддержки Min Pressure Мин давление Minimum Therapy Pressure Минимальное давление Max Pressure Макс давление Maximum Therapy Pressure Максимальное давление Ramp Time Время разгона Ramp Delay Period Время задержки разгона Ramp Pressure Давление разгона Starting Ramp Pressure Начальное давление разгона Ramp Event Событие разгона Ramp Разгон An abnormal period of Cheyne Stokes Respiration Аномальный период дыхания Чейна-Стокса An apnea that couldn't be determined as Central or Obstructive. Апноэ, которое нельзя отнести к обструктивному или центральному. A restriction in breathing from normal, causing a flattening of the flow waveform. Нарушение дыхания, меняющее форму графика потока. Vibratory Snore (VS2) Храп (VS2) A ResMed data item: Trigger Cycle Event Данные ResMed: событие запуска цикла Mask On Time Время в маске Time started according to str.edf Время начала по данным str.edf Summary Only Только итоги An apnea where the airway is open Апноэ при открытых дыхательных путях An apnea caused by airway obstruction Апноэ, вызванное перекрытием дыхательных путей Hypopnea Гипопноэ A partially obstructed airway Частично перекрытые дыхательные пути Unclassified Apnea Непонятное апноэ UA UA Vibratory Snore Храп A vibratory snore Храп A vibratory snore as detcted by a System One machine Храп, определенный аппаратом System One Pressure Pulse Пульсация давления A pulse of pressure 'pinged' to detect a closed airway. Пульсация давления для определения перекрытых дыхательных путей. A large mask leak affecting machine performance. Серьезная утечка из маски, влияющая на работу аппарата. Non Responding Event Событие без реакции A type of respiratory event that won't respond to a pressure increase. Дыхательное событие, не реагирующее на увеличение давления. Expiratory Puff Утечка выдоха Intellipap event where you breathe out your mouth. Событие Intellipap при выдохе ртом. SensAwake feature will reduce pressure when waking is detected. SensAwake уменьшает давление, когда обнаруживает пробуждение. User Flag #1 Пользовательский флаг #1 User Flag #2 Пользовательский флаг #2 User Flag #3 Пользовательский флаг #3 Heart rate in beats per minute Пульс в ударах в минуту Blood-oxygen saturation percentage Оксигенация крови в процентах Plethysomogram Плетизмограмма An optical Photo-plethysomogram showing heart rhythm Оптическая плетизмограмма сердечного ритма Pulse Change Изменение пульса A sudden (user definable) change in heart rate Внезапное (задается пользователем) изменение сердечного ритма SpO2 Drop Падение SpO2 (SD) A sudden (user definable) drop in blood oxygen saturation Внезапное (задается пользователем) падение сатурации крови SD SD Breathing flow rate waveform Кривая изменения потока дыхания L/min л/мин Mask Pressure Давление маски Amount of air displaced per breath Расход воздуха на один вдох Graph displaying snore volume График силы храпа Minute Ventilation Минутная вентиляция Amount of air displaced per minute Расход воздуха в минуту Respiratory Rate Частота дыхания Rate of breaths per minute Количество вдохов в минуту Patient Triggered Breaths Самостоятельное дыхание Percentage of breaths triggered by patient Доля вдохов, сделанных самостоятельно Pat. Trig. Breaths Сам. вдох Leak Rate Объем утечки Rate of detected mask leakage Объем утечек из маски I:E Ratio Отношение I:E Ratio between Inspiratory and Expiratory time Соотношение между временем вдоха и выдоха ratio отношение Pressure Min Мин давление Pressure Max Макс давление Pressure Set Давление Pressure Setting Настройка давления IPAP Set Установка IPAP IPAP Setting Настройки IPAP EPAP Set Установка EPAP EPAP Setting Настройки EPAP Cheyne Stokes Respiration Дыхание Чейна-Стокса (CSR) CSR CSR Periodic Breathing Периодическое дыхание An abnormal period of Periodic Breathing Аномальный промежуток периодического дыхания Clear Airway Свободные дыхательные пути Obstructive Обструкция Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Пробуждение из-за дыхания: затруднение дыхания, вызвавшее пробуждение или нарушение сна. Leak Flag Флаг утечки (LF) LF LF A user definable event detected by OSCAR's flow waveform processor. Пользовательское событие, определяемое волновым процессором OSCAR. Perfusion Index Индекс перфузии A relative assessment of the pulse strength at the monitoring site Относительная оценка силы пульса в месте его измерения Perf. Index % Инд. перф. % Mask Pressure (High frequency) Давление маски (высокая частота) Expiratory Time Время выдоха Time taken to breathe out Время затраченное на выдохи Inspiratory Time Время вдоха Time taken to breathe in Время затраченное на вдохи Respiratory Event Дыхательное событие Graph showing severity of flow limitations График серьезности ограничений потока Flow Limit. Предел потока. Target Minute Ventilation Целевая минутная вентиляция Maximum Leak Максимальная утечка The maximum rate of mask leakage Максимальное значение утечек из маски Max Leaks Макс утечки Apnea Hypopnea Index Индекс апноэ-гипопноэ Graph showing running AHI for the past hour График изменений ИАГ за последний час Total Leak Rate Общий объем утечки Detected mask leakage including natural Mask leakages Вычисленные утечки воздуха, включая нормлаьные утечки из маски Median Leak Rate Медианный объем утечки Median rate of detected mask leakage Медианный объем вычисленной утечки из маски Median Leaks Медианные утечки Respiratory Disturbance Index Индекс нарушения дыхания (ИНД) Graph showing running RDI for the past hour График изменения ИНД за последний час Sleep position in degrees Позиция сна в градусах Upright angle in degrees Угол наклона в градусах Movement Движение Movement detector Детектор движения CPAP Session contains summary data only Сеанс CPAP содержит только общие данные PAP Mode Режим PAP Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Невозможно загрузить Channels.xml, приложение будет закрыто. Apnea Апноэ An apnea reportred by your CPAP machine. В исходном тексте опечатка Апноэ по данным вашего аппарата. PAP Device Mode Режим аппарата PAP APAP (Variable) APAP (переменный) ASV (Fixed EPAP) ASV (постоянный EPAP) ASV (Variable EPAP) ASV (переменный EPAP) Height Высота Physical Height Физическая высота Notes Заметки Bookmark Notes Закладка Body Mass Index Индекс массы тела How you feel (0 = like crap, 10 = unstoppable) Самочувствие (0 = отвратительно, 10 = превосходно) Bookmark Start Начало закладки Bookmark End Конец закладки Last Updated Последнее обновление Journal Notes Заметки дневника Journal Дневник 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=Пробуждение 2=REM 3=Быстрый сон 4=Глубокий сон Brain Wave Волна мозга BrainWave Волна мозга Awakenings Пробуждения Number of Awakenings Число пробуждений Morning Feel Утреннее самочувствие How you felt in the morning Самочувствие утром Time Awake Время бодрствования Time spent awake Время проведенное не во сне Time In REM Sleep Время REM сна Time spent in REM Sleep Время, проведенное в REM сне Time in REM Sleep Время REM сна Time In Light Sleep Время быстрого сна Time spent in light sleep Время, проведенное в быстром сне Time in Light Sleep Время быстрого сна Time In Deep Sleep Время глубокого сна Time spent in deep sleep Время проведенное в глубоком сне Time in Deep Sleep Время глубокого сна Time to Sleep Время засыпания Time taken to get to sleep Время, потраченное на засыпание Zeo ZQ Zeo ZQ Zeo sleep quality measurement Оценка качества сна Zeo ZEO ZQ ZEO ZQ Debugging channel #1 Канал отладки #1 Test #1 Тест #1 For internal use only Для служебного пользования Debugging channel #2 Канал отладки #2 Test #2 Тест #2 Zero Ноль Upper Threshold Верхняя граница Lower Threshold Нижняя граница As you did not select a data folder, OSCAR will exit. Папка с данными не выбрана, приложение будет закрыто. or CANCEL to skip migration. или Отмена, чтобы пропустить миграцию. Choose the SleepyHead or OSCAR data folder to migrate Выберите папку с данными SleepyHead или OSCAR для миграции The folder you chose does not contain valid SleepyHead or OSCAR data. Выбранная папка не содержит корректных данных SleepyHead или OSCAR. You cannot use this folder: Нельзя использовать папку: Migrating Миграция files файлов from из to в OSCAR crashed due to an incompatibility with your graphics hardware. Произошла ошибка OSCAR из-за несовместимости графического адаптера. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR переключен в более медленный совместимый режим отображения. OSCAR will set up a folder for your data. OSCAR настроит папку с данными. If you have been using SleepyHead or an older version of OSCAR, Если вы ранее использовали SleepyHead или более старую версию OSCAR, OSCAR can copy your old data to this folder later. можно будет потом скопировать данные в эту папку. Migrate SleepyHead or OSCAR Data? Мигрировать данные SleepyHead или OSCAR? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data На следующем экране выберите папку с данными SleepyHead или OSCAR Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Нажмите [ОК] для продолжения или [Нет], если вы не хотите использовать существующие данные SleepyHead или OSCAR. We suggest you use this folder: Предпочтительная папка: Click Ok to accept this, or No if you want to use a different folder. Нажмите [ОК] для продолжения, или [Нет] если вы хотите выбрать другую папку. Choose or create a new folder for OSCAR data Выберите или создайте новую папку для данных OSCAR Next time you run OSCAR, you will be asked again. При следующем запуске, вопрос повторится. The folder you chose is not empty, nor does it already contain valid OSCAR data. Выбранная папка не пустая, и не содержит корректных данных OSCAR. Data directory: Папка данных: Unable to create the OSCAR data folder at Невозможно создать папку данных OSCAR в Unable to write to OSCAR data directory Невозможно сохранить данные OSCAR Error code Код ошибки OSCAR cannot continue and is exiting. Невозможно продолжить, приложение будет закрыто. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Невозможно записать отладочный лог. Можно использовать окно отладки (Помощь/Разрешение проблем/Показать окно отладки), но эти данные не будут сохранены на диск. Version "%1" is invalid, cannot continue! Версия "%1" некорректна, невозможно продолжить! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Используемая версия OSCAR (%1) балее старая, чем использовавшаяся с этими данными (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? Скорее всего данные будут повреждены, точно хотите продолжить? Question Вопрос Exiting Завершение Are you sure you want to use this folder? Точно хотите использовать эту папку? Don't forget to place your datacard back in your CPAP machine Не забудьте вставить карту памяти обратно в CPAP аппарат OSCAR Reminder Напоминание OSCAR You can only work with one instance of an individual OSCAR profile at a time. Одновременно можно работать только с одним профилем OSCAR. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. При использовании облачного хранилища, убедитесь что OSCAR закрыт и синхронизация данных завершена, прежде чем продолжить. Loading profile "%1"... Загрузка профиля "%1"... Chromebook file system detected, but no removable device found Обнаружена файловая система Chromebook, но не найдено съемных устройств You must share your SD card with Linux using the ChromeOS Files program Нужно открыть доступ к SD карте из Linux с помощью программы ChromeOS Files Recompressing Session Files Распаковка файлов сеансов Please select a location for your zip other than the data card itself! Выберите расположение для zip файла, отличное от карты памяти! Unable to create zip! Невозможно создать zip! Are you sure you want to reset all your channel colors and settings to defaults? Точно сбросить все настройки и цвета каналов? Are you sure you want to reset all your waveform channel colors and settings to defaults? Точно сбросить все цвета и настройки графиков? There are no graphs visible to print Нет видимых графиков для печати Would you like to show bookmarked areas in this report? Показать отмеченные области в отчете? Printing %1 Report Печать отчета %1 %1 Report Отчет %1 : %1 hours, %2 minutes, %3 seconds : %1 часов, %2 минут, %3 секунд RDI %1 RDI %1 AHI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 UAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 AI=%1 AI=%1 Reporting from %1 to %2 Отчет с %1 по %2 Entire Day's Flow Waveform График потока за весь день Current Selection Текущая выборка Entire Day Весь день Page %1 of %2 Страница %1 из %2 Days: %1 Дни: %1 Low Usage Days: %1 Дни слабого использования: %1 (%1% compliant, defined as > %2 hours) (%1% соответствия, заданного как > %2 часов) (Sess: %1) (Сеанс: %1) Bedtime: %1 Время в кровати: %1 Waketime: %1 Время пробуждения: %1 (Summary Only) (Только итоги) There is a lockfile already present for this profile '%1', claimed on '%2'. Обнаружен файл блокировки профиля '%1', на '%2'. Fixed Bi-Level Постоянный Bi-Level Auto Bi-Level (Fixed PS) Авто Bi-Level (постоянный PS) Auto Bi-Level (Variable PS) Авто Bi-Level (переменный PS) 99.5% 99,5% varies переменный n/a н/д Fixed %1 (%2) Постоянный %1 (%2) Min %1 Max %2 (%3) Мин %1 Макс %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %1 за %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Мин EPAP %1 Макс IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Последние данные оксиметрии: <a onclick='alert("день=%2");'>%1</a> (last night) (вчера) (1 day ago) (1 день назад) (%2 days ago) (%2 дней назад) No oximetry data has been imported yet. Данные оксиметрии еще не импортированы. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings Настройки SmartFlex ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Somnopose Software Zeo Zeo Personal Sleep Coach Personal Sleep Coach Database Outdated Please Rebuild CPAP Data Данные устарели Пожалуйста перестройте данные CPAP (%2 min, %3 sec) (%2 мин, %3 сек) (%3 sec) (%3 сек) Pop out Graph График The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Окно переполнено. Выберите существующее окно, удалите его, и отобразите этот график снова. Your machine doesn't record data to graph in Daily View Ваш аппарат не сохраняет нужные для отображения данные There is no data to graph Нет данных для отображения d MMM yyyy [ %1 - %2 ] d MMM yyyy [ %1 - %2 ] Hide All Events Скрыть все события Show All Events Показать все события Unpin %1 Graph Открепить график %1 Popout %1 Graph Подвесить график %1 Pin %1 Graph Прикрепить график %1 Plots Disabled Отображение отключено Duration %1:%2:%3 Длительность %1:%2:%3 AHI %1 AHI %1 Relief: %1 Расслабление: %1 Hours: %1h, %2m, %3s Время: %1 ч, %2 м, %3 с Machine Information Информация об аппарате Journal Data Данные журнала OSCAR found an old Journal folder, but it looks like it's been renamed: Обнаружена переименованная папка журнала: OSCAR will not touch this folder, and will create a new one instead. Будет создан новый вместо этого. Please be careful when playing in OSCAR's profile folders :-P Будьте осторожны при манипуляциях с папками профиля OSCAR :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. Не удалось найти запись журнала в профиле, но нашлось несколько папок с данными журнала. OSCAR picked only the first one of these, and will use it in future: OSCAR загрузил только первый из них, и будет использовать его в будущем: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Если ваших данных не хватает, скопируйте вручную содержимое всех остальных папок Journal_XXXXXXX в эту папку. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Backing up files... Резервное копирование файлов... Reading data files... Чтение файлов данных... SmartFlex Mode Режим SmartFlex Intellipap pressure relief mode. Режим ослабления давления Intellipap. Ramp Only Только разгон Full Time Все время SmartFlex Level Уровень SmartFlex Intellipap pressure relief level. Уровень ослабления давления Intellipap. Snoring event. Событие храпа. SN SN Locating STR.edf File(s)... Поиск файлов STR.edf... Cataloguing EDF Files... Подсчет файлов EDF... Queueing Import Tasks... Планирование загрузки... Finishing Up... Завершение... CPAP Mode Режим CPAP VPAPauto VPAPauto ASVAuto ASVAuto iVAPS iVAPS PAC PAC Auto for Her Auto for Her EPR EPR ResMed Exhale Pressure Relief Ослабление давления выдоха ResMed Patient??? Пациент??? EPR Level EPR Level Exhale Pressure Relief Level Уровень ослабления давления для выдоха SmartStart SmartStart Machine auto starts by breathing Включает аппарат при появлении дыхания Smart Start Smart Start Humid. Status Вкл. увлажнитель Humidifier Enabled Status Включение увлажнителя Humid. Level Ур. влажности Humidity Level Уровень влажности Temperature Температура ClimateLine Temperature Температура ClimateLine Temp. Enable Состояние температуры ClimateLine Temperature Enable Состояние температуры ClimateLine Temperature Enable Температура включена AB Filter АБ фильтр Antibacterial Filter Антибактериальный фильтр Pt. Access Доступ пациента Essentials Essentials Plus Plus Climate Control Управление климатом Manual Вручную Response Реакция Soft Мягкий Standard Стандартный BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T SmartStop SmartStop Machine auto stops by breathing Автоматическое отключение по дыханию Smart Stop Smart Stop Patient View Экран пациента Simple Простой Advanced Расширенный Your ResMed CPAP machine (Model %1) has not been tested yet. Ваш аппарат ResMed (модель %1) еще не был протестирован. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Выглядит достаточно похоже на другие аппараты, чтобы работать с OSCAR, но мы бы хотели получить zip архив карты памяти аппарата, чтобы это подтвердить. Parsing STR.edf records... Разбор записей STR.edf... Auto Авто Mask Маска ResMed Mask Setting Настройки маски ResMed Pillows Канюли Full Face Полнолицевая Nasal Назальная Ramp Enable Состояние разгона Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Snapshot %1 Снимок %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... Загрузка данных %1 для %2... Scanning Files Сканирование файлов Migrating Summary File Location Обновление места хранения файла статистики Loading Summaries.xml.gz Загрузка Summaries.xml.gz Loading Summary Data Загрузка статистики Please Wait... Подождите... Updating Statistics cache Обновление кеша статистики Usage Statistics Статистика использования %1 Charts %1 графиков %1 of %2 Charts %1 из %2 графиков Loading summaries Загрузка статистики Dreem Dreem Your Viatom device generated data that OSCAR has never seen before. Ваше устройство Viatom представило данные, неизвестные OSCAR. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Загруженные данные могут быть неточными, разработчикам пригодится копия ваших данных Viatom для улучшения OSCAR. Viatom Viatom Viatom Software Viatom Software New versions file improperly formed Некорректное содержание файла обновлений A more recent version of OSCAR is available Доступна новая версия OSCAR release релизную версию test version тестовую версию You are running the latest %1 of OSCAR Вы используете последнюю %1 OSCAR You are running OSCAR %1 Вы используете OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 можно скачать <a href='%2'>здесь</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Информация о более свежей тестовой версии %1 доступна здесь: <a href='%2'>%2</a> Check for OSCAR Updates Проверка обновлений OSCAR Unable to check for updates. Please try again later. Невозможно проверить обновления. Попробуйте позже. SensAwake level Уровень SensAwake Expiratory Relief Облегчение выдоха Expiratory Relief Level Уровень облегчения выдоха Humidity Влажность SleepStyle SleepStyle This page in other languages: Эта страница на других языках: %1 Graphs %1 графиков %1 of %2 Graphs %1 из %2 графиков %1 Event Types %1 типов событий %1 of %2 Event Types %1 из %2 типов событий Report about:blank about:blank SessionBar %1h %2m %1 ч %2 м No Sessions Present Нет сеансов SleepStyleLoader Import Error Ошибка импорта This Machine Record cannot be imported in this profile. Эти данные не могут быть импортированы в этот профиль. The Day records overlap with already existing content. Дневные записи пересекаются с существующими. Statistics CPAP Statistics Статистика CPAP CPAP Usage Использование CPAP Average Hours per Night Часов за ночь в среднем Therapy Efficacy Эффективность терапии Leak Statistics Статистика утечек Pressure Statistics Статистика давления Oximeter Statistics Статистика оксиметра Blood Oxygen Saturation Оксигенация крови Pulse Rate Пульс %1 Median Медиана %1 Average %1 Среднее %1 Min %1 Мин %1 Max %1 Макс %1 %1 Index Индекс %1 % of time in %1 % времени из %1 % of time above %1 threshold % времени выше границы %1 % of time below %1 threshold % времени ниже границы %1 Name: %1, %2 Имя: %1, %2 DOB: %1 Дата рождения: %1 Phone: %1 Телефон: %1 Email: %1 Почта: %1 Address: Адрес: This report was prepared on %1 by OSCAR %2 Отчет создан %1 OSCAR %2 Days Used: %1 Дней использования: %1 Low Use Days: %1 Дней малого использования: %1 Compliance: %1% Соответствие: %1% Days AHI of 5 or greater: %1 Дней с ИАГ 5 и выше: %1 Best AHI Лучший ИАГ Date: %1 AHI: %2 Дата: %1, ИАГ: %2 Worst AHI Худший ИАГ Best Flow Limitation Лучшее ограничение потока Date: %1 FL: %2 Дата: %1, ограничение: %2 Worst Flow Limtation Худшее ограничение потока No Flow Limitation on record Нет ограничений потока Worst Large Leaks Худшие утечки Date: %1 Leak: %2% Дата: %1, утечка: %2 No Large Leaks on record Нет больших утечек Worst CSR Худшее дыхание Чейна-Стокса (ДЧС) Date: %1 CSR: %2% Дата: %1, ЧСД: %2 No CSR on record Нет эпизодов ЧСД Worst PB Худшее периодическое дыхание (ПД) Date: %1 PB: %2% Дата: %1, ПД: %2 No PB on record Нет эпизодов ПД Want more information? Хотите больше информации? OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR требует загрузки всех итоговых данных для вычисления лучших и худших данных для конкретных дней. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Включите "Предзагружать итоги" в настройках и убедитесь, что данные доступны. Best RX Setting Лучшая установка Date: %1 - %2 Дата: %1 - %2 AHI: %1 ИАГ: %1 Total Hours: %1 Всего часов: %1 Worst RX Setting Худшая установка Most Recent Последнее Compliance (%1 hrs/day) Соответствие (%1 ч в день) OSCAR is free open-source CPAP report software OSCAR является программой для отчетов CPAP Changes to Machine Settings Изменения настроек аппарата No data found?!? Данные не найдены?!? Oscar has no data to report :( У OSCAR нет данных Last Week Неделя Last 30 Days Последние 30 дней Last 6 Months Последние полгода Last Year Последний год Last Session Последний сеанс Details Подробности No %1 data available. Нет данных о %1. %1 day of %2 Data on %3 %1 дней данных %2 по %3 %1 days of %2 Data, between %3 and %4 %1 дней данных %2, между %3 и %4 Days Дни Pressure Relief Сброс давления Pressure Settings Установки давления Machine Information Информация об аппарате First Use Первое использование Last Use Последнее использование Welcome Welcome to the Open Source CPAP Analysis Reporter Добро пожаловать в свободную программу анализа данных для CPAP терапии What would you like to do? Что бы вы хотели сейчас сделать? CPAP Importer Импорт данных CPAP Oximetry Wizard Оксиметрия Daily View Обзор дня Overview Сводка Statistics Статистика <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Предупреждение: </span><span style=" color:#ff0000;">Нужно заблокировать SD-карту ResMed S9 </span><span style=" font-weight:600; color:#ff0000;">прежде чем вставлять в компьютер.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Некоторые операционные системы записывают индексные файлы на карту без предупреждения, что может сделать карту нечитаемой для вашего CPAP аппарата.</span></p></body></html> It would be a good idea to check File->Preferences first, Рекомедуется открыть Файл->Настройки, as there are some options that affect import. так как там есть ряд параметров, касающихся импорта данных. Note that some preferences are forced when a ResMed machine is detected Обратите внимание, что некоторые настройки активируются при обнаружении аппарата ResMed First import can take a few minutes. Первый импорт данных может занять несколько минут. The last time you used your %1... Последний раз, когда вы использовали %1... last night вчера %2 days ago %2 дней назад was %1 (on %2) был %1 (%2) %1 hours, %2 minutes and %3 seconds %1 ч, %2 мин и %3 сек Your machine was on for %1. Ваш аппарат был включен в течении %1. <font color = red>You only had the mask on for %1.</font> <font color=red>Маска была надета только в течение %1.</font> under ниже over выше reasonably close to достаточно близко к equal to равно You had an AHI of %1, which is %2 your %3 day average of %4. У вас был AHI %1, что %2 вашего %3-дневного среднего значения %4. Your CPAP machine used a constant %1 %2 of air Ваш CPAP аппарат использовал постоянные %1 %2 воздуха Your pressure was under %1 %2 for %3% of the time. Давление было ниже %1 %2 в %3% времени. Your machine used a constant %1-%2 %3 of air. Аппарат использовал постоянное %1-%2 %3 воздуха. Your EPAP pressure fixed at %1 %2. Давление EPAP установлено в %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Давление IPAP было ниже %1 %2 в %3% времени. Your EPAP pressure was under %1 %2 for %3% of the time. Давление EPAP было ниже %1 %2 в %3% времени. Your machine was under %1-%2 %3 for %4% of the time. Ваш аппарат был менее %1-%2 %3 %4% времени. 1 day ago 1 день назад Your average leaks were %1 %2, which is %3 your %4 day average of %5. Ваши средние утечки составили %1 %2, что составляет %3 вашего %4-дневного среднего значения %5. No CPAP data has been imported yet. Данные CPAP еще не импортированы. gGraph %1 days %1 дней gGraphView 100% zoom level 100% масштаб Restore X-axis zoom to 100% to view entire selected period. Восстановить 100% масштаб по оси X для просмотра всего выбранного периода. Restore X-axis zoom to 100% to view entire day's data. Восстановить 100% масштаб по оси X для просмотра всего дня. Reset Graph Layout Сбросить настройки графиков Resets all graphs to a uniform height and default order. Сбросить все графики к общей высоте и обычному порядку. Y-Axis Ось Y Plots Графики CPAP Overlays Данные CPAP Oximeter Overlays Данные оксиметра Dotted Lines Пунктирные линии Double click title to pin / unpin Click and drag to reorder graphs Двойной клик для закрепления Перетащите для смены порядка графиков Remove Clone Убрать клон Clone %1 Graph Клонировать график %1 OSCAR-code-v1.3.1/Translations/Suomi.fi.ts000066400000000000000000014734371417327530600202360ustar00rootroot00000000000000 AboutDialog &About Tietoj&a Release Notes Julkaisutiedot Credits Kiitokset GPL License GPL-lisenssi Close Sulje Show data folder Näytä tietojen kansio About OSCAR %1 Tietoja Oscarista %1 Sorry, could not locate About file. Olen pahoillani, Tietoja-tiedostoa ei löydy. Sorry, could not locate Credits file. Olen pahoillani, kiitokset -tiedostoa ei löydy. Sorry, could not locate Release Notes. Olen pahoillani, julkaisutietoja ei löytynyt. OSCAR %1 Oscar %1 Important: Tärkeää: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Tämä on ohjelman esiversio. On suositeltavaa, että <b>otat varmuuskopiot manuaalisesti datakansioista</b> ennen ohjelman käyttöä, sillä varmuuskopioiden palautus myöhemmin voi vioittaa tietoja. To see if the license text is available in your language, see %1. Nähdäksesi lisenssitekstin omalla kielelläsi, katso %1. CMS50F37Loader Could not find the oximeter file: Oksimetrin tietojen tiedostoa ei löytynyt: Could not open the oximeter file: Ei voitu avata oksimetrin tiedostoa: CMS50Loader Could not get data transmission from oximeter. Ei voitu saada tietoja oksimetristä. Please ensure you select 'upload' from the oximeter devices menu. Varmistu, että olet valinnut uploadin oksimetrilaitteen valikosta. Could not find the oximeter file: Ei voitu löytää oksimetrin tiedostoa: Could not open the oximeter file: Ei voitu avata oksimetrin tiedostoa: CheckUpdates Checking for newer OSCAR versions Tarkistaa Oscarin uusia versioita Daily Go to the previous day Siirry edelliseen päivään Show or hide the calender Näytä tai piilota kalenteri Go to the next day Siirry seuraavaan päivään Go to the most recent day with data records Mene tietojen uusimpiin päiviin Events Tapahtumat View Size Näytön koko Notes Huomautukset Journal Päivyri B B u u Color Väri Small Pieni Medium Keskikokoinen Big Suuri Zombie Zombie I'm feeling ... Tunnen ... Weight Paino If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Jos asetusvalintaikkunassa henkilön pituus on nollaa suurempi, kirjaamalla tähän henkilön paino, näytetään BMI-arvo Awesome Mahtava B.M.I. BMI Bookmarks Kirjanmerkit Add Bookmark Lisää kirjanmerkki Starts Kirjanmerkin kellonaika Aika Remove Bookmark Poista kirjanmerkki Flags Tapahtumat Graphs Kaaviot Show/hide available graphs. Näytä/piilota graafit. Breakdown Erittely events tapahtumat UF1 UF1 UF2 UF2 Time at Pressure Aika paineen alla No %1 events are recorded this day Yhtään %1 tapahtumaa ei ole tallennettu tänä päivänä %1 event %1 tapahtuma %1 events %1 tapahtumaa Session Start Times Käyttöjakson alkamisaika Session End Times Käyttöjakson lopetusaika Session Information Käyttöjakson tiedot Oximetry Sessions Oksimetrin käytöt Duration Kesto (Mode and Pressure settings missing; yesterday's shown.) (Moodi ja paineen asetuksia ei ole; näytetään eiliset arvot.) This bookmark is in a currently disabled area.. Tämä kirjanmerkki on tällä hetkellä kielletyllä alueella.. CPAP Sessions CPAP käyttöjaksot Sleep Stage Sessions Unen tilojen jaksot Position Sensor Sessions Asentotunnistimien jaksot Unknown Session Tuntemattomat käyttöjaksot Machine Settings Laitteen asetukset Model %1 - %2 Malli %1 - %2 PAP Mode: %1 PAP toimintatapa: %1 99.5% 99.5% This day just contains summary data, only limited information is available. Tarjolla on vain rajoitettu määrä tietoa. Tämä päivä sisältää vain yhteenvetotiedot. Total ramp time Viiveen kokonaisaika Time outside of ramp Viiveen ulkopuolinen aika Start Alku End Loppu Unable to display Pie Chart on this system Piirakkakaaviota ei voi näyttää tässä järjestelmässä 10 of 10 Event Types 10 10:stä tapahtumatyypit This CPAP machine does NOT record detailed data Tämä cpap-laite EI tallenna yksityiskohtaisia tietoja Sorry, this machine only provides compliance data. Valitettavasti tämä laite tuottaa vain sopivaa tietoa. "Nothing's here!" "Täällä ei ole mitään!" No data is available for this day. Tälle päivälle ei löydy tietoja. 10 of 10 Graphs 10 10:stä graafit Oximeter Information Oksimetrin tiedot Details Yksityiskohdat Click to %1 this session. Paina %1 tähän käyttöjaksoon. disable kiellä enable salli %1 Session #%2 %1 käyttöjakso #%2 %1h %2m %3s %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Huomaa:</b> Kaikki alla olevat asetukset perustuvat oletukseen, että mitään ei ole muutettu viime päivien jälkeen. SpO2 Desaturations Happisaturaatiolaskut Pulse Change events Pulssin muutostapahtumat SpO2 Baseline Used Happisaturaation vertailukohta Statistics Tilastot Total time in apnea Apnean kokonaisaika Time over leak redline Ohivuodon aika Event Breakdown Tapahtumaerittely Sessions all off! Käyttöjaksot poissa! Sessions exist for this day but are switched off. Tänä päivänä on käyttöjaksoja, mutta ne on kytketty pois. Impossibly short session Mahdottoman lyhyt käyttöjakso Zero hours?? Nollatunteja?? BRICK :( TIILI :( Complain to your Equipment Provider! Reklamoi laitteesi edustajalle! Pick a Colour Valitse väri Bookmark at %1 Kirjanmerkki paikassa %1 ExportCSV Export as CSV Vie CSV:nä Dates: Päivät: Resolution: Resoluutio: Details Yksityiskohdittain Sessions Istunnoittain Daily Päivittäin Filename: Tiedostonnimi: Cancel Keskeytä Export Vie Start: Alku: End: Loppu: Quick Range: Pikarajaus: Most Recent Day Viimeisimmät päivät Last Week Viimeinen viikko Last Fortnight Viimeiset kaksi viikkoa Last Month Viimeinen kuukausi Last 6 Months Viimeiset 6 kuukautta Last Year Viimeinen vuosi Everything Kaikki Custom Räätälöity Details_ Details_ Sessions_ Sessions_ Summary_ Summary_ Select file to export to Valitse tietojen vientitiedosto CSV Files (*.csv) CSV-tiedostot (*.csv) DateTime Aika ja päiväys Session Käyttöjakso Event Tapahtuma Data/Duration Tieto/Kesto Date Päiväys Session Count Käyttöjaksojen lukumäärä Start Alku End Loppu Total Time Kokonaisaika AHI AHI Count Lukumäärä FPIconLoader Import Error Tuontivirhe This Machine Record cannot be imported in this profile. Tämän koneen tietuetta ei voida tuoda tähän profiiliin. The Day records overlap with already existing content. Päivän tietueet ovat päällekkäisiä jo olemassa oleville tiedoille. Help Hide this message Piilota tämä viesti Search Topic: Hakusana: Help Files are not yet available for %1 and will display in %2. Ohje-tiedostoja %1:lle ei ole vielä saatavilla ja näytetään %2. Help files do not appear to be present. Ohje-tiedostot eivät näy olevan saatavilla. HelpEngine did not set up correctly Ohjejärjestelmää ei ole asetettu oikein HelpEngine could not register documentation correctly. Ohjejärjestelmä ei voinut rekisteröidä dokumentaatiota oikein. Contents Sisällöt Index Sisällys Search Etsi No documentation available Dokumentaatiota ei ole saatavilla Please wait a bit.. Indexing still in progress Odota vielä.. Indeksointi on vielä käynnissä No Ei %1 result(s) for "%2" %1 tulos(ta) "%2":lle clear tyhjennä MD300W1Loader Could not find the oximeter file: Oksimetritiedostoa ei löytynyt: Could not open the oximeter file: Oksimetritiedostoa ei voitu avata: MainWindow &Statistics Tila&stot Report Mode Raporttitila Standard Standardi Monthly Kuukausittain Date Range Päivien väli Statistics Tilastot Daily Päivittäin Overview Yleiskatsaus Oximetry Oksimetri Import Tuo Help Apua &File &Tiedostot &View &Näytä &Help &Apua Troubleshooting Ongelmien ratkaisu &Data Tie&dot &Advanced &Lisää Show Daily view Näytä päivittäiset tiedot Show Overview view Näytä yleistiedot &Maximize Toggle &Maksimoi Maximize window Suurenna ikkuna Reset Graph &Heights Pala&uta kaavioiden korkeudet Reset sizes of graphs Palauta alkuperäiset ikkunakoot Show Right Sidebar Näytä oikea sivupalkki Show Statistics view Näytä tilastotiedot Import &Dreem Data Tuo &Dream tiedot Purge Current Selected Day Tyhjennä nykyinen valittu päivä &CPAP &CPAP &Oximetry &Oksimetri &Sleep Stage &Univaihe &Position &Asema &All except Notes K&aikki paitsi muistiinpanot All including &Notes Kaikki mukaa&n lukien muistiinpanot Show &Line Cursor Näytä &linjakursori Show Daily Left Sidebar Näytä päivittäinen vasemmanpuolinen sivupalkki Show Daily Calendar Näytä päivittäinen kalenteri Create zip of CPAP data card Luo SD-kortin tiedoista pakattu zip-tiedosto Create zip of OSCAR diagnostic logs Luo zip-paketti Oscarin diagnostisista lokeista Create zip of all OSCAR data Luo kaikista Oscarin tiedoista pakattu zip-tiedosto Report an Issue Raportoi ongelma System Information Järjestelmäinformaatio Show &Pie Chart Näytä &Piirakkakaavio Show Pie Chart on Daily page Näytä piirakkakaavio päivittäisellä sivulla Standard graph order, good for CPAP, APAP, Bi-Level Normaali grafiikka. Hyvä CPAP, APAP ja Bi-PAP laitteille Advanced Lisää Advanced graph order, good for ASV, AVAPS Laajempi grafiikkavalinta. Hyvä ASV- ja AVAPS laitteille Show Personal Data Näytä henkilökohtaiset tiedot Check For &Updates Tarkista &päivitykset &Reset Graphs Palauta &Kaaviot Rebuild CPAP Data Rakenna CPAP tiedot uudelleen &Preferences &Asetukset &Profiles &Profiilit &About OSCAR Tietoj&a Oscarista Show Performance Information Näytä suoritusarvot CSV Export Wizard CSV:n vientivelho Export for Review Vie katselmoitavaksi E&xit &Poistu Exit Poistu View &Daily Näytä &Päivittäin View &Overview Näytä &Yleiskatsaus View &Welcome Näytä Ter&vetuloa Use &AntiAliasing Käytä &reunojen pehmennys Show Debug Pane Näytä debuggauspaneeli Take &Screenshot &Ota kuvaruudunkaappaus O&ximetry Wizard O&ksimetrivelho Print &Report Tulosta &raportti &Edit Profile &Muokkaa profiilia Import &Viatom/Wellue Data Tuo &Viatom/Wellue tietoja Daily Calendar Päivittäinen kalenteri Backup &Journal &Päivyrin varmuuskopiointi Online Users &Guide &Käyttäjäopas verkossa &Frequently Asked Questions &UKK &Automatic Oximetry Cleanup &Automaattinen oksimetrin puhdistus Change &User Vaihda &käyttäjä Purge &Current Selected Day Tyhjennä &valittu päivä Right &Sidebar Oikea &Sivuikkuna Daily Sidebar Päivittäinen sivupalkki View S&tatistics Näytä &Tilastot Navigation Navigointi Bookmarks Kirjanmerkit Records Tietueet Exp&ort Data &Vie tietoja Profiles Profiilit Purge Oximetry Data Poista oksimetrin tiedot Purge ALL Machine Data Poista KAIKKI koneen tiedot &Import CPAP Card Data Tuo CPAP kort&in tiedot View Statistics Näytä Tilastot Import &ZEO Data Tuo &ZEO-tietoja Import RemStar &MSeries Data Tuo RemStar &MSeries tietoja Sleep Disorder Terms &Glossary &Unihäiriöiden termistö Change &Language Vaihda kie&li Change &Data Folder Va&ihda tietojen kansio Import &Somnopose Data Tuo &Somnopose-tietoja Current Days Nykyiset päivät Welcome Tervetuloa &About Tietoj&a Please wait, importing from backup folder(s)... Odota. Tietoja tuodaan varmuuskopiokansioista... Import Problem Tuo ongelma Please insert your CPAP data card... Aseta CPAP-laitteen SD-kortti tietokoneeseen... Access to Import has been blocked while recalculations are in progress. Tietojen tuonti on estetty kun uudelleenlaskenta on käynnissä. CPAP Data Located CPAP-tietojen sijainti Import Reminder Tuonnin muistuttaja Importing Data Tuodaan tietoja The User's Guide will open in your default browser Käyttöopas avautuu oletusselaimessa The Glossary will open in your default browser Sanasto avautuu oletusselaimessa There was a problem opening %1 Data File: %2 %1 Data-tiedoston: %2 avaamisessa oli ongelma %1 Data Import of %2 file(s) complete %2 tiedosto(je)n %1 tietojen tuonti valmis %1 Import Partial Success %1 tuonti osittain valmis %1 Data Import complete %1 tietojen tuonti valmis %1's Journal %1n päivyri Choose where to save journal Valitse päivyrin tallennuskohde XML Files (*.xml) XML Tiedostot (*.xml) Help Browser Apua selain %1 (Profile: %2) %1 (Profiili: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Muista valita juurikansio tai levyasematunnus cpap-tiedoille, ja ei kansiota sen sisään. Find your CPAP data card Etsi CPAP-tietojen SD-kortti Please open a profile first. Ole hyvä ja avaa ensin profiili. Check for updates not implemented Tarkista päivitykset toimintoa ei ole käytössä Choose where to save screenshot Valitse kuvaruudunkaappausten talletuspaikka Image files (*.png) Kuvatiedostot (*.png) Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Jos olet <i><b>itse</b> tehnyt CPAP tietojen varmuuskopiot</i>, voit jatkaa toimenpidettä, mutta joudut palauttamaan varmuuskopiot käsin. Are you really sure you want to do this? Haluatko varmasti tehdä tämän? Because there are no internal backups to rebuild from, you will have to restore from your own. Sisäistä varmuuskopiota ei löydy, joudut palauttamaan käsin. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Haluatko tuoda sinun omat varmuuskopiosi nyt? (Sinula ei ole yhtään tietoja näkyvillä tälle laitteelle, kunnes teet sen) Note as a precaution, the backup folder will be left in place. Esivaroitus: varmuuskopiokansion sijainti muuttuu. OSCAR does not have any backups for this machine! Oscarilla ei ole minkäänlaista varmuuskopiointia tälle cpap-koneelle! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Ennen kuin olet tehnyt <i>sinun <b>omat</b> varmuuskopiot KAIKILLE tämän cpap-koneen tiedoille</i>, <font size=+2>menetät tämän cpap-koneen tiedot <b>lopullisesti</b>!</font> Are you <b>absolutely sure</b> you want to proceed? Oletko <b>absoluuttisesti varma</b>, että haluat tehdä tämän? A file permission error casued the purge process to fail; you will have to delete the following folder manually: Tiedoston lupavirhe aiheutti poistoprosessin epäonnistumisen; sinun on poistettava seuraava kansio käsin: No help is available. Apua ei ole saatavilla. Are you sure you want to delete oximetry data for %1 Oletko varma, että haluat poistaa oksimetrin tiedot kohteessa %1 <b>Please be aware you can not undo this operation!</b> <b>Huomaa, että sinä et voi perua tätä toimenpidettä!</b> Select the day with valid oximetry data in daily view first. Valitse päivä joka sisältää oksimetritietoa päivittäisessä näytössä. Would you like to zip this card? Haluatko zip-pakata tämän kortin? Choose where to save zip Valitse paikka, jonne zip talletetaan ZIP files (*.zip) ZIP tiedostot (*.zip) Creating zip... Luo zip-tiedostoa... Calculating size... Laskee kokoa... OSCAR Information Oscar informaatio Loading profile "%1" Ladataan profiili "%1" Imported %1 CPAP session(s) from %2 Tuotu %1 CPAP-käyttöjakso(a) kohteesta %2 Import Success tuonti onnistunut Already up to date with CPAP data at %1 Seuraavassa kohdassa on jo CPAP-tietoa %1 Up to date Ajantasalla Couldn't find any valid Machine Data at %1 Seuraavassa kohdassa ei löytynyt laitteen tietoa %1 Choose a folder Valitse kansio No profile has been selected for Import. Profiilia ei ole valittu latausta varten. Import is already running in the background. Tietojen tuonti käynnissä jo taustalla. A %1 file structure for a %2 was located at: %2 tiedoston struktuuri %1 löytyi kohdasta: A %1 file structure was located at: Tiedoston %1 struktuuri löytyi kohdasta: Would you like to import from this location? Haluatko tuoda jotain tästä paikasta? Specify Määrittele Please note, that this could result in loss of data if OSCAR's backups have been disabled. Huomaa, että tämä voi johtaa tietojen menetykseen, jos Oscar-varmuuskopiot on poistettu käytöstä. The FAQ is not yet implemented Usein kysyttyjä kysymyksiä ei ole vielä olemassa If you can read this, the restart command didn't work. You will have to do it yourself manually. Jos näet tämän tekstin, uudelleenkäynnistys ei ole toiminut. Sinun on käynnistettävä ohjelma uudelleen käsin. Are you sure you want to rebuild all CPAP data for the following machine: Oletko varma, että haluat rakentaa uudelleen kaikki CPAP-tiedot seuraavalle laitteelle: For some reason, OSCAR does not have any backups for the following machine: Erään syyn takia Oscarilla ei ole yhtään varmuuskopioita tässä koneessa: You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Sinä aiot <font size=+2>hävittää</font> Oscarin tietokannan seuraavalla koneella:</p> Export review is not yet implemented Viennin uudelleennäyttämistä ei ole toteutettu Reporting issues is not yet implemented Raporttiuutisia ei ole vielä toteutettu Access to Preferences has been blocked until recalculation completes. Pääsy asetuksiin on estetty niin kauan kun uudelleenlaskennat ovat valmiit. There was an error saving screenshot to file "%1" Kuvaruudunkaappauksen tallennuksessa tiedostoon "%1" tapahtui virhe Screenshot saved to file "%1" Kuvaruudunkaappaus tallennettu tiedostoon "%1" There was a problem opening MSeries block File: MSeries lukkotiedoston avauksessa oli ongelma: MSeries Import complete MSeries-tietojen tuonti valmis MinMaxWidget Auto-Fit Automaattiset arvot Defaults Oletusarvot Override Korvaa The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Y-akselin skaalaus. Automaattiset arvot automaattiseen skaalaukseen. Oletusarvot ovat tehdasasetukset ja korvaa valitaksesi omat arvot. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Pienin Y-akselin arvo. Huomaa, tämä voi olla negatiivinen numero halutessasi. The Maximum Y-Axis value.. Must be greater than Minimum to work. Suurin Y-akselin arvo. Sen pitää olla suurempi kuin pienin arvo toimiakseen. Scaling Mode Skaalausmoodi This button resets the Min and Max to match the Auto-Fit Palauta pienin ja suurin arvo automaattisille arvoille NewProfile Edit User Profile Muokkaa käyttäjän profiilia I agree to all the conditions above. Hyväksyn kaikki ylläolevat ehdot. User Information Käyttäjän tiedot User Name Käyttäjänimi Password Protect Profile Salasanasuojattu profiili Password Salasana ...twice... ...toista... Locale Settings Paikalliset asetukset Country Maa TimeZone Aikavyöhyke about:blank about:blank Very weak password protection and not recommended if security is required. Päivityksiä ei voi tarkistaa. Yritä uudelleen myöhemmin. DST Zone DST-vyöhyke Personal Information (for reports) Henkilökohtaiset tiedot (raportteihin) First Name Etunimi Last Name Sukunimi It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Tämä on täysin ok, jos haluat kiertää tai ohittaa tämän, mutta summittainen ikäsi on tarpeen tiettyjen laskelmien tarkkuuden parantamiseksi. D.O.B. Syntymäaika <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Biologinen (syntymä) sukupuolta tarvitaan joskus parantamaan joidenkin laskelmien tarkkuutta. Jätä halutessasi tämä tyhjäksi ja ohita mikä tahansa niistä.</p></body></html> Gender Sukupuoli Male Mies Female Nainen Height Pituus Metric Metrinen English Tuumainen (Englanti) Contact Information Yhteystiedot Address Osoite Email Sähköposti Phone Puhelin CPAP Treatment Information CPAP:in käsittelytiedot Date Diagnosed Diagnosointipäivä Untreated AHI Käsittelemätön AHI CPAP Mode CPAP-moodi CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure Määrätty paine Doctors / Clinic Information Lääkärit / Klinikan tiedot Doctors Name Lääkärin nimi Practice Name Harjoittelun nimi Patient ID Potilasnumero &Cancel &Keskeytä &Back &Edellinen &Next &Seuraava Select Country Valitse maa This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Tämä ohjelma on suunniteltu auttamaan sinua CPAP-laitteiden ja niihin liittyvien laitteiden tietojen näyttämiseen. PLEASE READ CAREFULLY LUE HUOLELLISESTI Accuracy of any data displayed is not and can not be guaranteed. Näytettyjen tietojen tarkkuudella ei ole takuita. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Kaikki luodut raportit ovat VAIN HENKILÖKOHTAISEEN KÄYTTÖÖN, eikä niitä voi käyttää MILLÄÄN TAVALLA lääketieteellisten diagnoosien tarkoituksiin. Use of this software is entirely at your own risk. Käytät tätä ohjelmaa vain omalla vastuullasi. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCARin tekijänoikeudet &copy;2011-2018 Mark Watkins ja osat &copy;2019-2022 OSCAR Tiimi Welcome to the Open Source CPAP Analysis Reporter Tervetuloa avoimen lähdekoodin CPAP analysoijaan ja raportoijaan OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. Oscar on julkaistu vapaasti <a href='qrc:/COPYING'>GNU Public License v3</a> -lisenssin alla, eikä sillä ole takuuta ja ilman mitään vaatimuksia sopivuudesta mihinkään tarkoitukseen. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. Oscar on tarkoitettu pelkästään tietojen näyttäjäksi, eikä se korvaa lääkäriin antamaa toimivaltaista lääketieteellistä ohjausta. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Ohjelman tekijät eivät ole vastuussa <u>mistään</u> tämän ohjelmiston käytöstä tai väärinkäytöstä. Please provide a username for this profile Anna tälle profiilille käyttäjänimi Passwords don't match Salasana ei täsmää Profile Changes Profiilin muutokset Accept and save this information? Hyväksy ja talleta nämä tiedot? &Finish &Valmis &Close this window &Sulje tämä ikkuna Overview Range: Väli: Last Week Viimeinen viikko Last Two Weeks Viimeiset kaksi viikkoa Last Month Viimeinen kuukausi Last Two Months Viimeiset kaksi kuukautta Last Three Months Viimeiset kolme kuukautta Last 6 Months Viimeiset 6 kuukautta Last Year Viimeinen vuosi Everything Kaikki Custom Räätälöity Snapshot Pikakuva Start: Alku: End: Loppu: Reset view to selected date range Tyhjennä valittujen päivien väli näyttö Toggle Graph Visibility Vaihda kaavioiden näkyvyys Drop down to see list of graphs to switch on/off. Pudota alas nähdäksesi luettelon graafeista näytä/piilota. Graphs Kaaviot Respiratory Disturbance Index Hengitys Häiriö Indeksi Apnea Hypopnea Index Apnea Hypopnea Indeksi Usage Käyttö Usage (hours) Käyttö (tunteja) Session Times Käyttöjaksojen ajat Total Time in Apnea Kokonaisaika apneassa Total Time in Apnea (Minutes) Kokonaisaika apneassa (minuutteja) Body Mass Index Paino- indeksi How you felt (0-10) Miten hyvin voit (0-10) 10 of 10 Charts 10 - 10 kaaviot Show all graphs Näytä kaikki kaaviot Hide all graphs Piilota kaikki kaaviot OximeterImport Oximeter Import Wizard Oksimetrin tuontivelho Skip this page next time. Ohita tämä sivu seuraavalla kerralla. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Huomaa: </span><span style=" font-style:italic;">Valitse ensin oikea oksimetrisi tyyppi allaolevasta avautuvasta valikosta.</span></p></body></html> Where would you like to import from? Mistä haluat tuoda tietoja? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">ENSIN valitse oksimetrisi näistä ryhmistä:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. CMS50E/F käyttäjille: kun tuotte tietoja suoraan, älkää valitko laitteellanne upload, ennen kuin Oscar kehoittaa siihen. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Tämä valittuna Oscar resetoi automaattisesti sinun CMS50:n sisäisen kellon käyttäen tietokoneesi nykyistä kellonaikaa.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>Tässä voit antaa tälle oksimetrille 7-merkkisen nimen.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Tämä valinta poistaa tuodut tiedot oksimetrista kun siirto on valmis. </p><p>Käytä varoen, sillä jos jokin menee pieleen ennenkuin Oscar tallentaa tiedot, et voi saada niitä enää takaisin.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Tämän vaihtoehdon avulla voit tuoda tietoja (kaapelin kautta) oksimetrien sisäisistä tallenteista.</p><p>Kun olet valinnut tämän vaihtoehdon, vanhat Contec-oksimetrit vaativat, että käytät laitteen valikkoa lataamiseen.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Jos sinulle sopii olla kytkettynä tietokoneeseen yli yön, tämä vaihtoehto näyttää plethysmogrammi-kaavion, osoittaen sydämen sykkeen tavallisten oksimetriarvojen lisäksi.</p></body></html> Record attached to computer overnight (provides plethysomogram) Tallenna kytkettynä tietokoneeseen yön yli (näyttää myös sykkeen) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Tämän vaihtoehdon avulla voit tuoda tiedostoja oksimetrien omista ohjelmista, esimerkiksi SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Tuo toisen ohjelman kuten SpO2Review tallentamia tietoja Please connect your oximeter device Liitä oksimetrilaite If you can read this, you likely have your oximeter type set wrong in preferences. Jos tämä teksti on lueattavissa, on mahdollista että oksimetrin tyyppi on asetettu väärin. Please connect your oximeter device, turn it on, and enter the menu Yhdistä oksimetrilaite, käynnistä se ja siirry valikkoon Press Start to commence recording Paina Käynnistä aloittaaksesi tallennuksen Show Live Graphs Näytä kaaviot livenä Duration Kesto Pulse Rate Pulssitaso Multiple Sessions Detected Useampi käyttöjakso havaittu Start Time Aloitusaika Details Yksityiskohdat Import Completed. When did the recording start? Tuonti valmis. Milloin tallennus alkoi? Oximeter Starting time Oksimetrin aloitusaika I want to use the time reported by my oximeter's built in clock. Haluan käyttää oksimetrin sisäänrakennetun kellon aikaa. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Aloitin tämän oksimetrin tallennuksen (tai lähellä) samaan aikaan kuin aloitin CPAP-laitteen istunnon. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Huomautus: Synkronointi CPAP-istunnon käynnistyksen ajankohtaan on aina tarkempi.</p></body></html> Choose CPAP session to sync to: Valitse synkronoitava CPAP-käyttöjakso: You can manually adjust the time here if required: Voit muuttaa tässä aikaa käsin, jos haluat: HH:mm:ssap HH:mm:ssap &Cancel &Keskeytä &Information Page &Infosivu CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Aseta laitteen pvm/aika <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Jos asetettu, laitteen tunniste päivittyy seuraavan tuonnin yhteydessä. Tämä helpottaa jos käytössä on useampi oksimetri.</p></body></html> Set device identifier Aseta laitteen tunnus Erase session after successful upload Poista käyttöjakso onnistuneen tuonnin jälkeen Import directly from a recording on a device Tuo suoraan laitteen tallennuksesta <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Muistutus CPAP-käyttäjille: </span><span style=" color:#fb0000;">Muistitko tuoda CPAP-session ensin?<br/></span>Jos unohdit, oksimetri-istunnolle ei löydy ajankohtaa mihin synkronoida.<br/>Hyvän synkronoinnin saavuttamiseksi kannattaa aina käynnistää CPAP- ja oksimetrilaitteet samaan aikaan.</p></body></html> Please choose which one you want to import into OSCAR Ole hyvä ja valitse minkä tiedon tahdot tuoda Oscariin Day recording (normally would have) started Päivän tallennus olisi alkanut (normaalisti) <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>Oscar tarvitsee aloitusajan tietääkseen, minne tämä oksimetritieto talletetaan.</p><p>Valitse yksi seuraavista:</p></body></html> &Retry &Yritä uudelleen &Choose Session &Valitse käyttöjakso &End Recording &Lopeta tallennus &Sync and Save &Synkronoi ja talleta &Save and Finish &Talleta ja lopeta &Start &Aloita Scanning for compatible oximeters Etsii mahdollisia oksimetrejä Could not detect any connected oximeter devices. Ei löytynyt yhtään liitettyä oksimetri-laitetta. Connecting to %1 Oximeter Yhdistää oksimetriin %1 Renaming this oximeter from '%1' to '%2' Oksimetrin edellinen nimi: %1. Uusi nimi: %2 Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Oksimetrin nimi on eri. Jos sinulla on vain yksi ja se on jaettu profiilien välillä, aseta sama nimi molempiin profiileihin. "%1", session %2 "%1", käyttöjakso %2 Nothing to import Mitään tuotavaa ei ole Your oximeter did not have any valid sessions. Oksimetrista ei löytynyt käyttöjaksoja. Close Sulje Waiting for %1 to start Odottaa %1 käynnistymistä Waiting for the device to start the upload process... Odottaa laitteen tietojen tuonnin aloitusta... Select upload option on %1 Valitse lähetä laitteesta %1 You need to tell your oximeter to begin sending data to the computer. Sinun on kerrottava oksimetrille että tietoja saa nyt lähettää tietokoneelle. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Kytke oksimetri tietokoneeseen ja valitse sen valikosta lähetä aloittaaksesi tiedonsiirron... %1 device is uploading data... %1 laite lähettää tietoja... Please wait until oximeter upload process completes. Do not unplug your oximeter. Odota niin kauan kunnes oksimetrin tietojen siirto on valmis. Älä irroita oksimetriä. Oximeter import completed.. Oksimetrin tietojen tuonti valmis. Select a valid oximetry data file Valitse oikeantyyppinen oksimetrin tietojen tiedosto Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Oksimetritiedostot (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Oksimetrimoduuli ei kyennyt käsittelemään annettua tiedostoa: Live Oximetry Mode Oksimetrin online toimintatapa Live Oximetry Stopped Oksimetrin online pysäytetty Live Oximetry import has been stopped Oksimetrin online tuonti on pysäytetty Oximeter Session %1 Oksimetrin käyttö %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. Oscar antaa mahdollisuuden seurata oksimetrin tietoja CPAP-istunnon tietojen rinnalla. Ne voivat antaa arvokasta tietoa CPAP-hoidon tehokkuudesta. Se toimii myös itsenäisenä pulssioksimetrin avulla, jolloin voit tallentaa, seurata ja tarkastella tallennettuja tietoja. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) Oscar on nyt yhteensopiva Contec CMS50D+, CMS50E, CMS50F ja CMS50I serial oksimetrien kanssa.<br/>(Huom: Suora tietojen tuonti bluetooth-malleista <span style=" font-weight:600;">ei ole todennäköisesti</span> mahdollista vielä) If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Jos olet yhdistämässä oksimetrin ja CPAP-laitteen dataa, varmistu, että tuot ensin tiedot CPAP-laitteesta ennen toimenpidettä! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Sinun tulee varmistaa oksimetrin oikea ajuri (USB tai sarjaportti), jotta voit paikallistaa ja lukea suoraan oksimetri-laitetta. Lisätietoja tästä %1 napsauta tästä %2. Oximeter not detected Oksimetriä ei tunnistettu Couldn't access oximeter Ei saa yhteyttä oksimetriin Starting up... Aloittaa... If you can still read this after a few seconds, cancel and try again Jos voit yhä lukea tämän muutaman sekunnin jälkeen, keskeytä ja yritä uudelleen Live Import Stopped Live-tuonti pysäytetty %1 session(s) on %2, starting at %3 %1 käyttöjakso(a) %2, alkaen %3 No CPAP data available on %1 CPAP-dataa ei ole saatavilla paikassa %1 Recording... Tallentaa... Finger not detected Sormea ei ole havaittu I want to use the time my computer recorded for this live oximetry session. Haluan käyttää tietokoneeni aikaa tämän oksimetrin käytön tallennukseen. I need to set the time manually, because my oximeter doesn't have an internal clock. Haluan asettaa ajan käsin, sillä oksimetrissani ei ole sisäistä kelloa. Something went wrong getting session data Jotain meni pieleen tietojen tuonnissa Welcome to the Oximeter Import Wizard Tervetuloa oksimetrin tuontivelhoon Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Pulssioksimetri on lääketieteellinen veren happisaturaation (happikyllästeisyys - SpO2) mittaamiseen tarkoitettu laite. Veren happisaturaatio voi apnean ja poikkeavan hengityksen aikana laskea huomattavasti mikä voi kertoa hoitotoimenpiteitä vaadittavista ongelmista. You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Huomaa että muiden valmistajien oksimetrit, esimerkiksi Pulox, on uudelleennimetty Contec CMS50. Toimivat mallit on esimerkiksi Pulox PO-200, PO-300, PO-400. It also can read from ChoiceMMed MD300W1 oximeter .dat files. ChoiceMMed MD300W1 laitteen oksimetritietojen .dat-tiedostot on myös luettavissa. Please remember: Huomaa myös: Important Notes: Tärkeää tietoa: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Contec CMS50D+ laitteista puuttuu sisäinen kello eivätkä tallenna aloitusaikaa. Jos ei löydy CPAP-käyttöjaksoa mihin voidaan synkronoida, joudut asettamaan aloitusajan käsin tuonnin jälkeen. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Myöskin sisäisellä kellolla omaavilla laitteilla, on suositeltavaa aina aloittaa oksimetrin käyttöjakso samaan aikaan CPAP-käyttöjakson kanssa, koska CPAP-laitteiden sisäinen kello saattaa ajelehtia ajan myötä eikä voida helposti säätää. Oximetry Date Päiväys d/MM/yy h:mm:ss AP p/KK/vv h:mm:ss AP R&eset &Palauta Pulse Pulssi &Open .spo/R File &Avaa .spo/R tiedosto Serial &Import Sarja&tuonti &Start Live &Käynnistä Live Serial Port Sarjaportti &Rescan Ports &Skannaa portit uudelleen PreferencesDialog Preferences Asetukset &Import &Tuonti Combine Close Sessions Yhdistä lähellä olevat istunnot Minutes Minuuttia Multiple sessions closer together than this value will be kept on the same day. Useampi kuin yksi käyttöjakso tätä arvoa lähempänä toisiaan yhdistetään samana päivänä. Ignore Short Sessions Jätä huomiotta lyhyet käyttöjaksot <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Kestoltaan lyhyempiä käyttöjaksoja kuin tämä ei näytetä<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Vuorokauden vaihtumisaika Sessions starting before this time will go to the previous calendar day. Tätä aikaa aiemmin alkaneet käyttöjaksot menevät edelliseen kalenteripäivään. Session Storage Options Käyttöjaksojen tallennuksen asetukset Compress SD Card Backups (slower first import, but makes backups smaller) Pakkaa SD-kortin varmuuskopiot (hitaampi ensimmäisellä tuonnilla, mutta tekee varmuuskopioista pienempiä) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Tätä vähäisempi tuntimäärä päivässä pidetään ei-hoitomyöntyvyytenä. Neljä tuntia päivässä pidetään yleisesti hoitomyöntyvyytenä. hours tuntia Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Salli/kiellä kokeelliset tapahtumalippujen parannukset. Se sallii rajatapahtumien havaitseminen, myös jotkut koneelta huomaamatta jääneet. Tämä vaihtoehto on otettava käyttöön ennen tuontia, muuten puhdistus on tarpeen. Flow Restriction Virtauksen rajoitus Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Rajoituksen prosenttiosuus ilman virtauksessa mediaaniarvosta. Arvo 20% toimii hyvin apnean tunnistamisessa. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mukautettu liputus on kokeellinen menetelmä laitteen havaitsemattomien tapahtumien havaitsemiseen. Niitä <span style=" text-decoration: underline;">ei</span> lasketa mukaan AHI-arvoon.</p></body></html> Duration of airflow restriction Ilmavirran rajoituksen kesto s s Event Duration Tapahtuman kesto Allow duplicates near machine events. Salli kaksoiskappaleet laitetapahtumien lähellä. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Säädä tiedon määrä jokaisen pisteen kohdalla kaaviossa AHI/tunti. Oletusarvo on 60 minuuttia. Suositellaan jättää muuttumattomana. minutes minuuttia Reset the counter to zero at beginning of each (time) window. Nollaa laskuri jokaisen (aika)ikkunan alussa. Zero Reset Nollaus CPAP Clock Drift CPAP kellon siirtymä Do not import sessions older than: Älä tuo vanhempia käyttöjakso kuin: Sessions older than this date will not be imported Tätä päivää vanhempia käyttöjaksoja ei tuoda dd MMMM yyyy Kuukausi 4 merkkiä pp KKKK vvvv User definable threshold considered large leak Käyttäjän määrittelemä kynnys suurelle vuodolle Whether to show the leak redline in the leak graph Näytetäänkö vuotokaaviossa punaista vuotoviivaa Search Etsi &Oximetry &Oksimetri Show in Event Breakdown Piechart Näytä ympyräkaaviossa tapahtumaerittelynä Resync Machine Detected Events (Experimental) Uudelleensynkronoi laitteen havaitsemat tapahtumat (kok.) Percentage drop in oxygen saturation Prosentuaalinen pudotus happisaturaatiossa Pulse Pulssi Sudden change in Pulse Rate of at least this amount Äkillinen muutos pulssitasossa ainakin tämän verran bpm bpm Minimum duration of drop in oxygen saturation Happisaturaation laskun ajan vähimmäiskesto Minimum duration of pulse change event. Sykkeen muutostapahtuman vähimmäiskesto. Small chunks of oximetry data under this amount will be discarded. Tätä pienemmät oksimetritietojen palaset hylätään. &General &Yleinen Changes to the following settings needs a restart, but not a recalc. Muutokset näihin asetuksiin vaativat uudelleenkäynnistämisen, mutta ei uudelleenlaskentaa. Preferred Calculation Methods Haluttu laskentametodi Middle Calculations Keskimääräislaskut Upper Percentile Ylin persenttiili Session Splitting Settings Istunnon jakamisen asetukset <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Tämän asetuksen kanssa pitää olla varovainen...</span> Tiettyjen yhteenvetopäivien tarkkuus voi heikentyä jos sen kääntää pois päältä, koska tietyt laskennat toimii yhdessä oikein vasta kun yksittäisten päivien tallenteista tulevat yhteenlasketut istunnot pidetään yhdessä. </p><p><span style=" font-weight:600;">ResMed-käyttäjät:</span> Vaikka meille kuulostaa luonnolliselta että 12-keskipäiväjakson uudellenkäynnistys tapahtuu edellisenä päivänä, se ei välttämättä tarkoita että ResMedin tiedot ovat samaa mieltä. STF.edf tiivistelmä indeksi-formaatissa on heikkouksia.</p><p>Tämä asetus on olemassa heille jotka eivät piittaa ja haluaa &quot;korjata&quot; tätä, kustannuksista riippumatta. Kunhan pidät SD-kortin laitteessa joka yö, ja tuot tiedot vähintään joka viikko, et tule näkemään ongelmia liian usein.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Älä jaa yhteenvetopäiviä (Varoitus: Lue vihje!) Memory and Startup Options Muisti- ja käynnistysasetukset Pre-Load all summary data at startup Lataa ennalta kaikki yhteenvetotiedot käynnistyksen aikana <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Tämä asetus pitää aaltomuodot ja tapahtumat muistissa käytön jälkeen nopeuttaakseen päivien uudelleennäyttämistä.</p><p>Tämä ei ole pakollinen asetus koska myös käyttöjärjestelmä pitää välimuistissa käytettyjä tiedostoja.</p><p>Suositeltavaa pitää pois päältä, jos ei tietokoneella ole runsaasti muistia.</p></body></html> Keep Waveform/Event data in memory Pidä aaltomuodot/tapahtumat muistissa <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Vähentää ylimääräisten vahvistusdialogien näyttämistä tuonnin aikana.</p></body></html> Import without asking for confirmation Tuo kysymättä vahvistusta General CPAP and Related Settings Yleiset CPAP- ja siihen kuuluvat asetukset Enable Unknown Events Channels Salli tuntemattomien tapahtumien näytön AHI Apnea Hypopnea Index AHI RDI Respiratory Disturbance Index RDI AHI/Hour Graph Time Window AHI/tunti aikaikkunakaavio Preferred major event index Ensisijainen päätapahtumaindeksi Compliance defined as Hoitomyöntyvyyden määritys Flag leaks over threshold Liputa ylimenevät vuodot Seconds Sekuntia Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Tiivistä ResMed (EDF) varmuuskoptio säästääksesi levytilaa. Varmuuskopioidut EDF-tiedostot on tallennettu .gz -muotoon, joka on yleinen Mac- ja Linux-järjestelmissä.. Oscar voi tuoda nämä tiivistetyt varmuuskopiot kansioon natiivisti.. Käyttääksesi sitä ResScan tarvitsee .gz tiedostojen purkamista ensin.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Seuraavat valinnat vaikuttavat Oscarin käyttämään levytilaan, ja niillä on vaikutusta tietojen tuonnin kuluttamaan aikaan. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Tällä Oscarin datatiedot vievät suunnilleen puolet käytetystä levytilasta. Mutta se tekee tietojen tuonnista ja päivän vaihdosta hitaamman.. Jos sinulla on uusi tietokone ja SSD-levy, tämä on hyvä valinta. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Tiivistä tietoja (tekee Oscarin tiedostoista pienempiä, mutta siirtyminen päivien välillä tulee hitaammaksi.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Tämä tekee SD-kortin tiedoista varmuuskopion ResMed-koneille, ResMed S9 -sarjan koneet poistavat tarkat tiedot yli 7 päivää vanhoista tiedoista, ja kaavioiden tietoja yli 30 päivän tiedoista.. Oscar voi säilyttää kopion tästä tiedosta, jos sinä asennat Oscarin uudelleen. (Vahvasti suositeltu, ellei vapaa levytila ole vähissä tai jos et halua kaavioiden tietoja) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Saa Oscarin käynnistymään hieman hitaammin. Lataamalla kaikki yhteenvetotiedot etukäteen, mikä nopeuttaa yleiskatsausta ja muutamia muita laskelmia myöhemmin. </p><p>Jos sinulla on suuri määrä tietoja, saattaa olla syytä pitää tämä pois päältä. Mutta jos haluat tarkastella yleensä <span style=" font-style:italic;">kaikkea</span> yleiskatsauksessa, kaikki yhteenvetotiedot pitää edelleen ladata. </p><p>Huomaa, että tämä asetus ei vaikuta aaltomuotoon ja tapahtumatietoihin, jotka on aina ladattu tarvittaessa.</p></body></html> <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Huom: Tätä ei saa käyttää aikavyöhykkeen korjaamiseen! Varmista että käyttöjärjestelmän kello ja aikavyöhyke on asetettu oikein.</p></body></html> Hours Tuntia This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Tämä kokeellinen vaihtoehto yrittää käyttää Oscarin tapahtumalippujärjestelmää koneen havaittujen tapahtumien paikannuksen parantamiseksi. For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Yhtenäisyyden vuoksi ResMed-käyttäjien on käytettävä 95% tässä, koska tämä on ainoa arvo saatavilla yhteenvetopäiville. <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Kumulatiiviset indeksit</p></body></html> Median is recommended for ResMed users. Mediaania suositellaan ResMedin käyttäjille. Median Mediaani Weighted Average Painotettu keskiarvo Normal Average Normaali keskiarvo True Maximum Oikea maksimi 99% Percentile 99% Prosenttia Maximum Calcs Maksimilaskennat General Settings Yleiset asetukset Show Remove Card reminder notification on OSCAR shutdown Näytä poista kortti muistutus kun sammutat Oscarin Daily view navigation buttons will skip over days without data records Päivittäisen näytön navigointinäppämet ohittavat päiviä ilman tietoja Skip over Empty Days Ohita tyhjät päivät Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Salli useamman CPU-ytimen käyttö kun saatavilla suorituskyvyn parantamiseen. Vaikuttaa eniten tuontitoimintoihin. Enable Multithreading Salli monisäikeisyys Bypass the login screen and load the most recent User Profile Ohita login-ruutu ja lataa viimeisin käytetty käyttäjäprofiili Create SD Card Backups during Import (Turn this off at your own peril!) Luo SD-kortista varmuuskopiot tietojen tuonnin aikana (aseta tämä pois omalla vastuullasi!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Oikea maksimi on tietojoukon maksimi.</p><p>99:s persenttiili suodattaa pois harvinaisimmat poikkeamat.</p></body></html> Combined Count divided by Total Hours Yhteenlaskettu määrä jaettuna kokonaistunneilla Time Weighted average of Indice Aikapainotettu keskiarvo Standard average of indice Keskiarvo Custom CPAP User Event Flagging Räätälöity CPAP tapahtumien liputus Events Tapahtumat Reset &Defaults P&alauta oletusarvot <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Varoitus: </span>Vaikka oletusarvot pystyy palauttamaan, ei tarkoita että se on hyvä tapa.</p></body></html> Waveforms Aaltomuodot Flag rapid changes in oximetry stats Liputa nopeat muutokset oksimetri-tiedoissa Other oximetry options Muut oksimetrin asetukset Discard segments under Hävitä lyhyemmät jaksot kuin Flag Pulse Rate Above Liputa sykettä yli Flag Pulse Rate Below Liputa sykettä alle This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Laskenta vaatii että kokonaisvuotojen määrä tulee CPAP-laitteesta (m.m. PRS1, mutta ei ResMed, koska se sisältää laskennat jo) Tahattomien vuotojen laskennat on tässä lineaarinen, koska maskin ilmanvaihdon käyrä ei ole mallinnettu. Käyttäessäsi eri maskeja, käytä keskiarvoja. Se on tarpeeksi lähellä. Calculate Unintentional Leaks When Not Present Laske tahattomia vuotoja kun ne eivät ole läsnä 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Huom.: Lineaarinen laskenta käytetty. Tietojen muutto vaatii uudelleenlaskenta. Show flags for machine detected events that haven't been identified yet. Näytä liputuksia vielä tunnistamattomien laitteen havaitsemien tapahtumien kohdalla. Check for new version every Tarkista uudet versiot joka days. päivä. Last Checked For Updates: Päivitykset tarkistettu viimeksi: TextLabel Tekstikenttä I want to be notified of test versions. (Advanced users only please.) Haluan, että minulle ilmoitetaan testiversioista. (vain edistyneet käyttäjät, kiitos.) &Appearance &Ulkomuoto Graph Settings Kaavion asetukset <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Mille välilehdelle profiili avataan. (Huom: Se avautuu oletusprofiilille, jos Oscaria ei ole asetettu avaamaan tiettyä profiilia käynnistyksessä)</p></body></html> Bar Tops Pylväiden kärjet Line Chart Viivakaavio Overview Linecharts Yleiskatsaus viivakaaviot <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Vieritys helpottuu zoomatessa herkkien kosketuslevyjen kanssa</p><p>50 ms on suositeltu arvo.</p></body></html> How long you want the tooltips to stay visible. Kuinka kauan haluat vihjeiden pysyvän näkyvinä. Scroll Dampening Vierityksen vaimennus Tooltip Timeout Vihjeiden aikaviive Default display height of graphs in pixels Kaavioiden näytön oletuskorkeus pikseleissä Graph Tooltips Kaavioiden vihjeet The visual method of displaying waveform overlay flags. Visuaalinen tapa näyttää aaltomuotojen liputukset peittokuvina. Standard Bars Normaalit pylväät Top Markers Merkit huipussa Graph Height Kaavion korkeus Changing SD Backup compression options doesn't automatically recompress backup data. SD-varmuuskopiointipakkausasetusten muuttaminen ei automaattisesti uudelleenpakkaa varmuuskopiotietoja. Auto-Launch CPAP Importer after opening profile Käynnistä CPAP-tietojen tuonei automaattisesti profiilin avaamisen jälkeen Automatically load last used profile on start-up Lataa viimeksi käytetty profiili automaattisesti käynnistyksessä <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Tee hälytys kun tuodaan tietoa laitteen mallista, jota Oscarin-kehittäjät eivät ole testanneet.</p></body></html> Warn when importing data from an untested machine Varoita kun tuodaan tietoa testaamattomasta laitteesta <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>Tee hälytys kun tuodaan tietoa laitteesta, joka on jollain tavalla erilainen kuin mikä tahansa Oscar-kehittäjien aiemmin testaama laite.</p></body></html> Warn when previously unseen data is encountered Varoita kun tuodaan aiemmin testaamattomasta laitteesta tietoa Your masks vent rate at 20 cmH2O pressure Maskin ilmavirtaus 20 cmH20: n paineessa Your masks vent rate at 4 cmH2O pressure Maskin virtausnopeus 4 4 cmH2O paineessa <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Huom: </span>Suunnittelurajoitusten takia ResMed-laitteet eivät tue näiden asetusten muuttamista.</p></body></html> Oximetry Settings Oksimetrin asetukset Always save screenshots in the OSCAR Data folder Talleta aina kuvaruudunkaappaukset Oscarin tietojen kansioon Check For Updates Tarkista päivitykset You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Käytät Oscarin testiversiota. Testiversiot tarkistavat päivitykset automaattisesti vähintään joka seitsemäs päivä. Voit asettaa tarkistusvälin lyhyemmäksi kuin seitsemän päivää. Automatically check for updates Tarkista päivitykset automaattisesti How often OSCAR should check for updates. Kuinka usein Oscarin tulisi tarkistaa päivitykset. If you are interested in helping test new features and bugfixes early, click here. Jos olet kiinnostunut auttamaan uusien ominaisuuksien testausta ja tekemään bugi-ilmoituksia varhain, napsauta tästä. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Jos haluat auttaa Oscarin testiversioiden testauksessa, ole hyvä ja lue Wiki-sivuilta Oscarin testauksesta. Me toivotamme tervetulleeksi jokaisen, joka pitäisi Oscarin testauksesta, auttaisi kehittämään Oscaria ja auttaisi ohjelman kääntämisessä olemassaolevalle tai uudelle kielelle. https://www.sleepfiles.com/OSCAR On Opening Avattaessa Profile Profiili Welcome Tervetuloa Daily Päivittäin Statistics Tilastot Switch Tabs Vaihda välilehtiä No change Ei muutoksia After Import Tietojen tuonnin jälkeen Overlay Flags Liput peittokuvana Line Thickness Viivojen paksuus The pixel thickness of line plots Viivojen paksuus pikseleissä Other Visual Settings Muut visuaaliset asetukset Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Reunojen pehmennys (anti aliasing) pehmentää kaavioita. Se antaa joillekin kaaviokuville miellyttävämmän ulkonäön. Se myös vaikuttaa tulostuksiin. Kokeile ja katso miellyttääkö. Use Anti-Aliasing Käytä reunojen pehmennystä Makes certain plots look more "square waved". Tietyt käyrät ovat tällä asetuksella enemmän suorakulman muotoisia. Square Wave Plots Käyrät kanttiaaltona Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Pixmap caching (värillisen bittikartan tallennus välimuistiin) on näytönohjaimen kiihdytystekniikka. Se voi aiheuttaa ongelmia fonttien piirtämisen kanssa kaaviokuvissa nyt käytössäsi olevalla tietokonealustalla. Use Pixmap Caching Käytä Pixmap Caching <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Nämä ominaisuudet on karsittu äsken. Ne tulevat käyttöön myöhemmin. </p></body></html> Animations && Fancy Stuff Animaatiot && mieltymykset Whether to allow changing yAxis scales by double clicking on yAxis labels Salli y-akselin muuttamista kaksoisnapsauttamalla y-akselin nimikettä Allow YAxis Scaling Salli y-akselin skaalaus Whether to include machine serial number on machine settings changes report Sisällytetäänkö laitteen sarjanumero muutosraportteihin Include Serial Number Sisällytä sarjanumero Graphics Engine (Requires Restart) Grafiikkajärjestelmä (vaatii uudelleenkäynnistyksen) l/min l/min <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <html><head/><body><p>Valitse SpO<span style=" vertical-align:sub;">2</span> Desaturaatiot alla</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Synkronoi oksimetri- ja cpap-tietoja</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50-tiedot SpO2-arviointi (.spoR-tiedostoista) tai sarjatuonti-metodi </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> on oikea aikaleima, jota tarvitaan synkronoinnissa.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Ajantasainen näyttö käyttää sarjakaapelia) on yksi tapa saavuttaa tarkka synkronointi CMS 50 -oksimetreissä, mutta se ei estä CPAP-kellon poikkeamaa.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Jos aloitat oksimetrisi tallennusmoodin </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">täsmällisesti </span><span style=" font-family:'Sans'; font-size:10pt;">samaan aikaan kun aloitat cpap-laitteen käytön, saavutat näin synkronoinnin. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Tietojen sarjatuontiprosessi alkaa ciime yön ensimmäisestä cpcp-tietojen tuonnista. (Muista tuoda cpap-tiedot ensin!)</span></p></body></html> Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Yritä muuttaa tätä oletusarvosta (Desktop OpenGL), jos koet ongelmia Oscarin kaavioissa. Print reports in black and white, which can be more legible on non-color printers Tulosta raportit mustavalkoisina, mikä voi olla luettavampaa mv-tulostimilla Print reports in black and white (monochrome) Tulosta raportit mustavalkoisina Fonts (Application wide settings) Fontit (koko ohjelmaa kattava asetus) Font Kirjasin Size Koko Bold Lihavoitu Italic Kursivoitu Application Sovellus Graph Text Kaavion teksti Graph Titles Kaavion otsikko Big Text Suuri teksti Details Yksityiskohdat &Cancel &Peru &Ok &Ok Name Nimi Color Väri Flag Type Lipputyyppi Label Nimike CPAP Events CPAP tapahtumat Oximeter Events Oksimetrin tapahtumat Positional Events Asentotapahtuma Sleep Stage Events Unen tilan tapahtumat Unknown Events Tuntemattomat tapahtumat Double click to change the descriptive name this channel. Kaksoisnapsauta muuttaaksesi tämän kanavan kuvaavaa nimeä. Double click to change the default color for this channel plot/flag/data. Kaksoisnapsauta muuttaaksesi tämän kanavan oletusväriä piste/lippu/tieto. <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Huomioi:</b> Oscarin edistykselliset käyttöjaksojen jako-ominaisuudet eivät ole mahdollisia <b>ResMed</b> laitteille niiden asetusten ja yhteenvetojen talletustavan takia. Siksi ne pitää estää tälle profiilille.</p><p>ResMedin laitteilla päivät <b>vaihtuvat puolen päivän aikoihin</b> kuten ResMedin kaupallisissa ohjelmistoissa.</p> %1 %2 %1 %2 Overview Yleiskatsaus Double click to change the descriptive name the '%1' channel. Kaksoisnapsauta muuttaaksesi %1-kanavan nimikettä. Whether this flag has a dedicated overview chart. Tällä lipulla on oma kaavio yleiskatsauksessa. Here you can change the type of flag shown for this event Tässä voit muuttaa lipun tyyppiä tälle näytettävälle tapahtumalle This is the short-form label to indicate this channel on screen. Tämä on lyhyt nimike tämän kanavan tunnistamiseen näyttöruudulla. This is a description of what this channel does. Tämä on kuvaus tämän kanavan toiminnalle. Lower Alempi Upper Ylempi CPAP Waveforms CPAP aaltomuodot Oximeter Waveforms Oksimetrin aaltomuodot Positional Waveforms Asentojen aaltomuodot Sleep Stage Waveforms Unen tilan aaltomuodot Whether a breakdown of this waveform displays in overview. Tämän aaltomuodon erittely näkyy yleiskatsauksessa. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Tässä voi asettaa %1 aaltomuodon tiettyjen laskentojen <b>alempi</b> kynnys Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Tässä voi asettaa %1 aaltomuodon tiettyjen laskentojen <b>ylempi</b> kynnys Data Processing Required Tarvitaan tietojen prosessointia A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Tiedon uudelleen tiivistys- ja purkuproseduuria tarvitaan näille muutoksille. Tämä toiminto vie aikaa useita minuutteja. Haluatko varmasti tehdä nämä muutokset? Data Reindex Required Tietojen uudelleen indeksointi tarpeen A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Tietojen uudelleenindeksointi vaaditaan näiden muutosten jälkeen. Tämä voi kestää muutaman minuutin. Oletko varma että haluat jatkaa? Restart Required Tarvitsee uudelleenkäynnistyksen If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Jos ikinä haluat ladata uudelleen tätä tietoa (joko Oscarin tai ResScanin) tämä tieto ei tule enää uudelleen takaisin. If you need to conserve disk space, please remember to carry out manual backups. Jos haluat säästää levytilaa, muista tehdä manuaalisia varmuuskopioita. Are you sure you want to disable these backups? Haluatko varmasti estää nämä varmuuskopioinnit? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Näiden varmuuskopioiden kytkeminen pois käytöstä ei ole hyvä idea, koska Oscar tarvitsee niitä tietokannan uudelleenrakentamiseen, jos jotain menee pieleen. Are you really sure you want to do this? Haluatko varmasti tehdä tämän? Flag Lippu Minor Flag Pieni lippu Span Kattaa Always Minor Aina pieni No CPAP machines detected CPAP-laitetta ei löydetty Will you be using a ResMed brand machine? Käytätkö ResMedin laitetta? Never Ei koskaan One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Yhden tai useamman asetuksen muutos tulee voimaan kun käynnistät tämän sovelluksen uudelleen.

Haluatko käynnistää uudelleen nyt? This may not be a good idea Tämä ei ehkä ole hyvä idea ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9 laitteet poistavat rutiinilla yli 7 päivää vanhoja ja yli 30 päivää vanhoja tietoja SD-kortilta riippuen resoluutiosta). ProfileSelector Filter: Suodin: Reset filter to see all profiles Palauta suodatin nähdäksesi kaikki profiilit Version Versio &Open Profile Avaa pr&ofiili &Edit Profile &Muokkaa profiilia &New Profile &Uusi profiili Profile: None Profiili: Ei mikään Please select or create a profile... Valitse tai luo profiili... Destroy Profile Poista profiili Profile Profiili Ventilator Brand Laitteen merkki Ventilator Model Laitteen malli Other Data Muut tiedot Last Imported Viimeksi tuotu Name Nimi You must create a profile On luotava profiili Enter Password for %1 Anna salasana kohteelle %1 You entered an incorrect password Annoit väärän salasanan Forgot your password? Unohditko salasanasi? Ask on the forums how to reset it, it's actually pretty easy. Kysy foorumilla, kuinka asettaa se uudelleen. Se on aika helppoa. Select a profile first Valitse ensin profiili The selected profile does not appear to contain any data and cannot be removed by OSCAR Valittu profiili ei näytä sisältävän tietoja, eikä OSCAR voi poistaa sitä If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Jos yrität poistaa, koska unohdit salasanan, sinun tulee asettaa uudelleen tai poistaa profiilikansio käsin. You are about to destroy profile '<b>%1</b>'. Olet poistamassa profiilia '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Mieti huolellisesti. Tämä poistaa profiilin auttamattomasti ja sen mukana kaikki <b>varmuuskopiot</b> talletettu paikkaan <br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Kirjoita sana <b>POISTA</b> alle tarkalleen samalla tavalla hyväksyessäsi poiston. DELETE POISTA Sorry Anteeksi You need to enter DELETE in capital letters. Sinun tulee kirjoittaa POISTA isoilla kirjaimilla. There was an error deleting the profile directory, you need to manually remove it. Profiilin poistossa tapahtui virhe. Sinun tulee poistaa profiili käsin. Profile '%1' was succesfully deleted Profiili '%1' poistettiin onnistuneesti Bytes tavua KB KB MB MB GB GB TB TB PB PB Summaries: Yhteenvedot: Events: Tapahtumat: Backups: Varmuuskopiot: Hide disk usage information Piilota levynkäyttötieto Show disk usage information Näytä levynkäyttötieto Name: %1, %2 Nimi: %1, %2 Phone: %1 Puhelin: %1 Email: <a href='mailto:%1'>%1</a> Sähköposti: <a href='mailto:%1'>%1</a> Address: Osoite: No profile information given Profiilitietoa ei ole annettu Profile: %1 Profiili: %1 ProgressDialog Abort Keskeytä QObject No Data Ei tietoja Events Tapahtumat Duration Kesto (% %1 in events) (% %1 tapahtumissa) Jan Tammi Feb Helmi Mar Maal Apr Huhti May Touko Jun Kesä Jul Heinä Aug Elo Sep Syys Oct Loka Nov Marras Dec Joulu ft ft lb lb oz oz Kg kg cmH2O cmH2O Med. Med. Min: %1 Min: %1 Min: Min: Max: Maks: Max: %1 Maks: %1 %1 (%2 days): %1 (%2 päivää): %1 (%2 day): %1 (%2 päivää): % in %1 % %1:ssa Hours Tuntia Min %1 Min %1 Hours: %1 Tunnit: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 vähäinen käyttö, %2 ei käyttöä, %3 päivistä (%4% hoitomyöntyvyys.) Pituus: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Istunnot: %1 / %2 / %3 Pituus: %4 / %5 / %6 Pisimmät: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Pituus: %3 Alku: %2 Mask On Maski päällä Mask Off Maski pois %1 Length: %3 Start: %2 %1 Pituus: %3 Alku: %2 TTIA: TTIA: TTIA: %1 TTIA: %1 Minutes Minuuttia Seconds Sekuntia h h m m s s ms ms Events/hr Tapahtumia tunnissa Hz Hz bpm bpm Litres Litraa ml ml Breaths/min Hengitystä/minuutissa ? ? Severity (0-1) Vakavuus (0-1) Degrees Astetta Error Virhe Warning Varoitus Information Tieto Busy Varattu Please Note Huomaa Graphs Switched Off Kaaviot kytketty pois Sessions Switched Off Käyttöjaksot poistettu &Yes K&yllä &No &Ei &Cancel &Peruuta &Destroy &Tuhoa &Save &Talleta BMI BMI Weight Paino Zombie Zombie Pulse Rate Pulssi Plethy Plethy Pressure Paine Daily Päivittäin Profile Profiili Overview Yleiskatsaus Oximetry Oksimetria Oximeter Oksimetri Event Flags Tapahtumat Default Oletusarvo CPAP CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP Min EPAP Min EPAP Max EPAP Max EPAP IPAP IPAP Min IPAP Min IPAP Max IPAP Max IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Kostutin H H OA OA A A CA CA FL FL SA SA LE LE EP EP VS VS VS2 VS2 RERA RERA PP PP P P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF1 UF2 UF2 UF3 UF3 PS PS AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI EPI PB PB IE IE Insp. Time Sisäänhengitysaika Exp. Time Uloshengitysaika Resp. Event Hengitystapahtuma Flow Limitation Virtauksen rajoite Flow Limit Virtauksen rajoite SensAwake SensAwake Pat. Trig. Breath Pot. lauk. hengitys Tgt. Min. Vent Min. ilmastointi Target Vent. Ilmastointi Minute Vent. Ilmamäärä minuutissa Tidal Volume Kertahengitystilavuus Resp. Rate Hengitystiheys Snore Kuorsaus Leak Vuoto Leaks Vuodot Large Leak Suuri vuoto LL LL Total Leaks Kaikki vuodot Unintentional Leaks Tahattomat vuodot MaskPressure Maskipaine Flow Rate Virtaustaso Sleep Stage Unen tila Usage Käyttö Sessions Käyttöjaksot Pr. Relief Pain. kev. No Data Available Tietoa ei ole saatavilla App key: Sovellusavain: Operating system: Käyttöjärjestelmä: Built with Qt %1 on %2 Rakennettu Qt %1 järjestelmässä %2 Graphics Engine: Graafinen järjestelmä: Graphics Engine type: Graafisen järjestelmän tyyppi: Software Engine Ohjelmistotuote ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Työpöydän OpenGL m m cm cm in tuumaa Only Settings and Compliance Data Available Vain asetukset ja noudattamistiedot saatavilla Summary Data Only Vain yhteenvetotiedot Bookmarks Kirjanmerkit Mode Moodi Model Malli Brand Merkki Serial Sarjanumero Series Malli Machine Laite Channel Kanava Settings Asetukset Inclination kaltevuus Orientation Suuntautuminen Motion Liike Name Nimi DOB Syntymäaika Phone Puhelin Address Osoite Email Sähköposti Patient ID Potilasnumero Date Päiväys Bedtime Nukkumaanmenoaika Wake-up Herääminen Mask Time Maskiaika Unknown Tuntematon None Ei mikään Ready Valmis First Ensimmäinen Last Viimeinen Start Alku End Loppu On On Off Ei Yes Kyllä No Ei Min Min Max Max Med Med Average Keskiarvo Median Mediaani Avg Keskim. W-Avg Pain. keskim. Your %1 %2 (%3) generated data that OSCAR has never seen before. Sinun %1 %2 (%3) luodut tiedot, joita Oscar ei ole nähnyt koskaan aiemmin. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Tuodut tiedot eivät välttämättä ole täysin tarkkoja, joten kehittäjät haluaisivat .zip-kopion tämän koneen SD-kortista ja vastaavat lääkärin .pdf-raportit varmistaakseen, että OSCAR käsittelee tietoja oikein. Non Data Capable Machine Laite ilman tietojenkäsittelyä Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Sinun %1 Cpap-laitteesi (Malli %2) ei valitettavasti ole tietoa tukevaa mallia. Your %1 CPAP machine (Model %2) has not been tested yet. Sinun %1 Cpap-laitettasi (Malli %2) ei ole testattu vielä. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Laitteesi antamat tiedot näyttävät riittävän samanlaiselta kuin muut koneet, jotta se voisi toimia. Mutta kehittäjät haluaisivat .zip-kopion tämän koneen SD-kortista ja vastaavat lääkärin .pdf-raportit varmistaakseen, että se toimii Oscarin kanssa. Sorry, your %1 CPAP machine (%2) is not supported yet. Pahoittelumme. Sinun %1 Cpap-laitettasi (%2) ei tueta vielä. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Kehittäjät tarvitsevat .zip-kopion tämän koneen SD-kortista ja vastaavat lääkärin .pdf-raportit, jotta se toimisi OSCAR:n kanssa. Getting Ready... Valmistautuu... Machine Unsupported Laittteelle ei löydy tukea I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Valitettavasti Oscar voi tutkia ja raportoida vain käyttötunteja ja tämän laitteen perustietoja. Scanning Files... Skannaa tiedostoja... Importing Sessions... Tuo käyttötietoja... Finishing up... Lopettelee... Untested Data Testaamattomat tiedot Machine Untested Laite testaamaton CPAP-Check CPAP-tarkistus AutoCPAP AutoCPAP Auto-Trial Automaattinen-kokeilu AutoBiLevel Automaattinen Bi-taso S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Flex Flex Lock Joustava lukko Whether Flex settings are available to you. Ovatko joustavat asetukset saatavilla. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Kuinka kauan kestää EPAP:ista siirtyä IPAP:iin. Mitä suurempi luku, sitä hitaaampi muutos. Rise Time Lock Nosta aikalukkoa Whether Rise Time settings are available to you. Ovatko aikalukkoasetukset muutettavissa. Rise Lock Kasvata lukkoa Passover Kostuttimen lämmityksen ohitus Target Time Tavoiteaika PRS1 Humidifier Target Time PRS1 kostuttimen tavoiteaika Hum. Tgt Time Kostuttimen tavoiteaika Mask Resistance Setting Maskin vastusasetukset Mask Resist. Maskin vastus. Hose Diam. Letkun paksuus 15mm 15 mm Tubing Type Lock Letkutyypin lukko Whether tubing type settings are available to you. Voiko letkutyypin lukkoasetuksia muuttaa. Tube Lock Latkulukko Mask Resistance Lock Maskin vastuksen lukko Whether mask resistance settings are available to you. Voiko maskin vastuksen asetuksia muuttaa. Mask Res. Lock Maskin vastuksen lukko Whether or not machine shows AHI via built-in display. Näyttääkö laite AHI:n sisäänrakennetulla näytöllä. Ramp Type Viiveen tyyppi Type of ramp curve to use. Käytettävän viivekäyrän tyyppi. Linear Suora SmartRamp Älykäs viive Ramp+ Viive+ Backup Breath Mode Backup-hengitys The kind of backup breath rate in use: none (off), automatic, or fixed Backup-hengityksen taso käytössä: ei mitään (pois), automaattinen, tai vakio Breath Rate Hengitysnopeus Fixed Vakio Fixed Backup Breath BPM Vakio backup hengityksen BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Minimihengitys per minuutti (BPM), jonka alapuolella aloitetaan ajoitettu hengitys Breath BPM Hengityksen BPM Timed Inspiration Ajoitettu siirtyminen The time that a timed breath will provide IPAP before transitioning to EPAP Aika, jonka ajoitettu hengitys antaa IPAP: n ennen siirtymistä EPAP: iin Timed Insp. Ajoitettu siirtyminen Auto-Trial Duration Automaattisen kokeilun kesto The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-CPAP-koeajanjakson päivien lukumäärä, jonka jälkeen kone palaa CPAP-tilaan Auto-Trial Dur. Auto-koeajan kesto EZ-Start EZ-käynnistys Whether or not EZ-Start is enabled EZ-käynnistys sallittu Variable Breathing Muuttuva hengitys UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend VAHVISTAMATON: Mahdollisesti muuttuva hengitys, jolloin ajanjaksot poikkeavat suuresti hengitysteiden huippuhengityksen trendistä Peak Flow Huippuvirtaus Peak flow during a 2-minute interval Huippuvirtaus 2 minuutin välein 22mm 22 mm Backing Up Files... Tiedostojen varmuuskopiointi... model %1 malli %1 DreamStation 2 DreamStation 2 unknown model tuntematon malli Flex Mode Flex Mode PRS1 pressure relief mode. PRS1 paineenalennustoiminto. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time Nousuaika Bi-Flex Bi-Flex Flex Level Flex taso PRS1 pressure relief setting. PRS1 paineenalennuksen asetus. Humidifier Status Kostuttimen tila PRS1 humidifier connected? PRS1 kostutin kytketty? Disconnected Irroitettu Connected Yhdistetty Humidification Mode Kostutustila PRS1 Humidification Mode PRS1 kostutustila Humid. Mode Kostutustila Fixed (Classic) Vakio (klassinen) Adaptive (System One) Adaptiivinen (System One) Heated Tube Lämmitetty letku Tube Temperature Letkun lämpötila PRS1 Heated Tube Temperature PRS1 lämmitetyn letkun lämpötila Tube Temp. Letkun lämpötila PRS1 Humidifier Setting PRS1 kostutuksen asetukset Hose Diameter Letkun halkaisija Diameter of primary CPAP hose CPAP ensiöletkun halkaisija 12mm 12mm Auto On Automaatti päälle A few breaths automatically starts machine Muutama hengitys käynnistää laitteen automaattisesti Auto Off Automaatti pois Machine automatically switches off Laite sammuu automaattisesti Mask Alert Maskihälytys Whether or not machine allows Mask checking. Salliiko laite maskin tarkastuksen. Show AHI Näytä AHI Breathing Not Detected Hengitystä ei löydy A period during a session where the machine could not detect flow. Aikajakso, jona aikana kone ei ole havainnut virtausta. BND BND Timed Breath Ajoitettu hengitys Machine Initiated Breath Laitteella aloitettu hengitys TB TB Windows User Windows-käyttäjä Using Käyttää , found SleepyHead - , löytyi SleepyHead - You must run the OSCAR Migration Tool Sinun tulee käyttää Oscar yhdistelytyökalua (migration) <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Vanhat tiedot täytyy generoida uudelleen edellyttäen että varmistus ei ole käännetty pois päältä asetuksista aikaisemmin tehdyssä tuonnissa.</i> Launching Windows Explorer failed Windows Explorerin käynnistys epäonnistui Could not find explorer.exe in path to launch Windows Explorer. Ei voitu löytää explorer.exe:n polkua Windows Explorerin käynnistämiseksi. <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>Oscar ylläpitää varmuuskopioita laitteistosi kortista, jota se käyttää tähän tarkoitukseen.</b> OSCAR does not yet have any automatic card backups stored for this device. Oscarilla ei ole vielä mitään automaattista kortin varmuuskopiointia tälle laitteelle. This means you will need to import this machine data again afterwards from your own backups or data card. Tämä tarkoittaa että joudut itse jälkeenpäin tuomaan tämän laitteen tietoja omasta varmistuksesta tai SD-kortista. Important: Tärkeää: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Jos olet huolissasi, napsauta EI poistuaksesi ja varmuuskopioi profiilisi käsin, ennen kuin käynnistät Oscarin uudelleen. Are you ready to upgrade, so you can run the new version of OSCAR? Oletko valmis päivittämään voidaksesi käyttää uutta Oscarin versiota? Sorry, the purge operation failed, which means this version of OSCAR can't start. Anteeksi, poisto-operaatio epäonnistui. Oscarin tätä versiota ei voi käynnistää. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Haluatko ottaa käyttöön automaattiset varmuuskopiot, joten seuraavan kerran uuden Oscar-version on tehtävä se uudelleen näistä? OSCAR will now start the import wizard so you can reinstall your %1 data. Oscar käynnistää nyt tuontivelhon, jotta voit asentaa uudelleen %1 tiedot. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Oscar sulkeutuu nyt, sitten (yritä) käynnistää tietokoneesi käyttöjärjestelmän tiedostoselain, jotta voit käsin varmuuskopioida profiilin: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Käytä käyttöjärjestelmäsi tiedostoselainta kopioidaksesi profiilikansion, sen jälkeen käynnistä Oscar uudelleen ja vie päivitysprosessi loppuun. Machine Database Changes Laitteen tietokannan muutokset OSCAR %1 needs to upgrade its database for %2 %3 %4 Oscar %1 pitää päivittää tietokanta kohteille %2 %3 %4 Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Kun päivität, <font size=+1>et voi</font> käyttää tätä profiilia ohjelman aiemmissa versioissa. The machine data folder needs to be removed manually. Laitteen tietokansio tulee poistaa käsin. This folder currently resides at the following location: Tämä kansio sijaitsee nyt seuraavassa paikassa: Rebuilding from %1 Backup Luo uudelleen tietoja varmuuskopioista %1 Therapy Pressure Terapiapaine Inspiratory Pressure Sisäänhengityspaine Lower Inspiratory Pressure Matalempi sisäänhengityspaine Higher Inspiratory Pressure Korkeampi sisäänhengityspaine Expiratory Pressure Uloshengityspaine Lower Expiratory Pressure Matalempi uloshengityspaine Higher Expiratory Pressure Korkeampi uloshengityspaine Pressure Support PSV = Pressure Support Ventilation, painetukiventilaatio Painetuki (PS) PS Min PS min Pressure Support Minimum Minimi painetuki PS Max PS maks Pressure Support Maximum Maksimi painetuki Min Pressure Min. paine Minimum Therapy Pressure Pienin hoitopaine Max Pressure Suurin paine Maximum Therapy Pressure Suurin hoitopaine Ramp Time Viiveen aika Ramp Delay Period Viiveen aika Ramp Pressure Viiveen paine Starting Ramp Pressure Viiveen aloituspaine Ramp Event Viivetapahtuma Ramp Viive Vibratory Snore (VS2) Värähtelevä kuorsaus (VS2) Mask On Time Maskin päälläoloaika Time started according to str.edf Aloitusaika str.edf:n mukaan Summary Only Vain yhteenveto An apnea where the airway is open Apnea, jossa ilmavirtaus on auki An apnea caused by airway obstruction Ilmavirtauksen tukoksen aiheuttama apnea Hypopnea Matala hengitys hypopnea A partially obstructed airway Osittain tukkeutunut ilmavirta Unclassified Apnea Luokittelematon apnea UA UA Vibratory Snore Värähtelevä kuorsaus A vibratory snore Värähtelevä kuorsaus A vibratory snore as detcted by a System One machine Systeme One laitteen havaitsema värähtelevä kuorsaus Pressure Pulse Painesykäys A pulse of pressure 'pinged' to detect a closed airway. Painesykäys käytetään havaitsemaan tukkeutunutta hengitystietä. A large mask leak affecting machine performance. Suuri maskivuoto, joka vaikuttaa laitteen suorituskykyyn. Non Responding Event Vastaamaton tapahtuma A type of respiratory event that won't respond to a pressure increase. Hengitystapahtuma, johon ei paineen nousu vaikuta. Expiratory Puff Puuskutus Intellipap event where you breathe out your mouth. Intellipap tapahtuma, jossa hengität ulospäin suullasi. SensAwake feature will reduce pressure when waking is detected. SensAwake ominaisuus vähentää painetta, kun herääminen havaitaan. User Flag #1 Käyttäjälippu #1 User Flag #2 Käyttäjälippu #2 User Flag #3 Käyttäjälippu #3 Heart rate in beats per minute Syke minuutissa Blood-oxygen saturation percentage Veren happisaturaatio prosenteissa Plethysomogram Plethysmogrammi An optical Photo-plethysomogram showing heart rhythm Sydämen sykkeen näyttävä optinen kuva-plethysmogrammi Pulse Change Pulssin muutos A sudden (user definable) change in heart rate Äkillinen (määriteltävissä) muutos sydämen lyöntitiheyteen SpO2 Drop SpO2 Pudotus A sudden (user definable) drop in blood oxygen saturation Äkillinen (määriteltävissä) pudotus veren happipitoisuuden saturaatiossa SD SD Breathing flow rate waveform Hengityksen virtauksen aaltomuoto L/min l/min Mask Pressure Maskipaine Amount of air displaced per breath Yksittäisen hengityksen ilmamäärä Graph displaying snore volume Kuorsauksen voimakkuuskaavio Minute Ventilation Ilmamäärä minuutissa Amount of air displaced per minute Litramäärä minuutissa (l/min) Respiratory Rate Hengitystiheys Rate of breaths per minute Hengitystä minuutissa Patient Triggered Breaths Potilaan käynnistämät hengitykset Percentage of breaths triggered by patient Potilaan laukaisemien hengityksien prosentuaalinen osuus Pat. Trig. Breaths Pot. lauk. heng. Leak Rate Vuototaso Rate of detected mask leakage Maskivuotojen määrä I:E Ratio I:E suhde Ratio between Inspiratory and Expiratory time Sisäänhengityksen ja uloshengityksen keston suhde ratio suhde Pressure Min Paine min Pressure Max Paine max Cheyne Stokes Respiration Cheyne Stokes hengitys (CSR) An abnormal period of Cheyne Stokes Respiration Poikkeava jaksoittainen Cheyne Stokes hengitys CSR CSR Periodic Breathing Jaksoittainen hengitys An abnormal period of Periodic Breathing Poikkeava jaksoittainen hengitys Clear Airway Sentraalinen Obstructive Obstruktiivinen Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Hengitysponnistuksen aiheuttama herääminen: Tukkeutunut hengitys aiheuttaa joko herääminen tai unen häiriö. Leak Flag Vuoto LF LF A user definable event detected by OSCAR's flow waveform processor. Oscar havaitsi käyttäjän määriteltävissä olevan tapahtuman virtauksen aaltomuodon prosessoinnissa. Perfusion Index Perfuusioindeksi A relative assessment of the pulse strength at the monitoring site Suhteellinen arvio pulssin voimakkuudesta tarkkailukohdassa Perf. Index % Perf.indeksi % Expiratory Time Uloshengitysaika s Time taken to breathe out Uloshengittämiseen kulunut aika Inspiratory Time Sisäänhengitysaika s Time taken to breathe in Sisäänhengittämiseen kulunut aika Respiratory Event Hengitystapahtuma Graph showing severity of flow limitations Kaavio näyttää virtauksen rajoitteiden kovuuden Flow Limit. Virtauksen rajoite. Target Minute Ventilation Ilmamäärä minuutissa An apnea that couldn't be determined as Central or Obstructive. Apnea, jota ei voida määritellä sentraaliseksi tai obstuktiiviseksi. Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Ei kyetty lukemaan Channels.xml tiedostoa. Oscar ei voi jatkaa ja sulkeutuu. Pressure Set Paineasetus Pressure Setting Paineen asetus IPAP Set IPAP asetus IPAP Setting IPAP:n asetus EPAP Set EPAP asetus EPAP Setting EPAP:n asetus Apnea Apnea-katkos An apnea reportred by your CPAP machine. CPAP-laitteesi raportoi apnea-katkoksen. A restriction in breathing from normal, causing a flattening of the flow waveform. Rajoitettu hengitys normaalista, mikä aiheuttaa virtausaaltomuodon litistymisen. Mask Pressure (High frequency) Maskin paine (korkea taajuus) A ResMed data item: Trigger Cycle Event ResMed-tietoelementti: käynnistysjakson tapahtuma Maximum Leak Maksimivuoto The maximum rate of mask leakage Maskin vuotojen enimmäismäärä Max Leaks Maksimivuodot Apnea Hypopnea Index Apnea Hypopnea Indeksi Graph showing running AHI for the past hour Kaavio näyttää AHI-tilanteen viimeiselle kuluneelle tunnille Total Leak Rate Vuotojen kokonaismäärän taso Detected mask leakage including natural Mask leakages Havaitut maskivuodot sisältäen luonnolliset maskivuodot Median Leak Rate Mediaani vuotomäärä Median rate of detected mask leakage Mediaani määrä havaitusta maskin vuodosta Median Leaks Mediaani vuoto Respiratory Disturbance Index Hengityshäiriöiden indeksi Graph showing running RDI for the past hour Jatkuva edellisen tunnin RDI:n kaavio Sleep position in degrees Nukkumisasento asteissa Upright angle in degrees Pystyasennon kulma asteissa Movement Liikkuminen Movement detector Liikkeentunnistin CPAP Session contains summary data only CPAP-käyttöjakso sisältää vain yhteenvetotiedot PAP Mode PAP-moodi PAP Device Mode PAP-laitteen moodi APAP (Variable) APAP (Muuttuva) ASV (Fixed EPAP) ASV (Kiinnitetty EPAP) ASV (Variable EPAP) ASV (Muuttuva EPAP) Height Pituus Physical Height Potilaan pituus Notes Huomautukset Bookmark Notes Kirjanmerkkihuomautus Body Mass Index Painoindeksi How you feel (0 = like crap, 10 = unstoppable) Tuntuma (0 = huono, 10 = pysäyttämätön) Bookmark Start Kirjanmerkki alku Bookmark End Kirjanmerkki loppu Last Updated Viimeksi päivitetty Journal Notes Päivyrin muistiinpano Journal Päivyri 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=Hereillä 2=REM 3=kevyt uni 4=syvä uni Brain Wave Aivo aalto BrainWave Aivoaalto Awakenings Heräämiset Number of Awakenings Heräämisten määrä Morning Feel Mieliala aamulla How you felt in the morning Mieliala aamulla Time Awake Aika hereillä Time spent awake Aika vietetty hereillä Time In REM Sleep Aika REM unessa Time spent in REM Sleep Aika vietetty REM unessa Time in REM Sleep Aika REM unessa Time In Light Sleep Aika kevyessä unessa Time spent in light sleep Aika vietetty kevyessä unessa Time in Light Sleep Aika kevyessä unessa Time In Deep Sleep Aika syvässä unessa Time spent in deep sleep Aika vietetty syvässä unessa Time in Deep Sleep Aika syvässä unessa Time to Sleep Aika nukahtamiseen Time taken to get to sleep Aika nukahtamiseen Zeo ZQ Zeo ZQ Zeo sleep quality measurement Zeo unen laadun mittaus ZEO ZQ ZEO ZQ Debugging channel #1 Debug kanava #1 Test #1 Testi #1 For internal use only Vain sisäiseen käyttöön Debugging channel #2 Debug kanava #2 Test #2 Testi #2 Zero Nolla Upper Threshold Yläraja Lower Threshold Alaraja or CANCEL to skip migration. tai KESKEYTÄ ohittaaksesi yhdistämisen. You cannot use this folder: Et voi käyttää tätä kansiota: Migrating Yhdistää files tiedostot from täältä to tänne OSCAR crashed due to an incompatibility with your graphics hardware. Oscar kaatui tietokoneesi graafisen järjestelmän takia. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. Tämän ratkaisemiseksi Oscar on palannut hitaampaan, mutta entistä yhteensopivampaan piirtämismenetelmään. OSCAR will set up a folder for your data. Oscar luo kansion tiedoillesi. We suggest you use this folder: Ehdotamme, että käytät tätä kansiota: Click Ok to accept this, or No if you want to use a different folder. Napsauta Ok hyväksyäksesi tämän, tai Ei jos haluat käyttää toista kansiota. Next time you run OSCAR, you will be asked again. Kun käytät Oscaria seuraavan kerran, sinulta kysytään tämä uudelleen. Migrate SleepyHead or OSCAR Data? Siirretäänkö SleepyHead- tai OSCAR-tiedot? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Seuraavassa näytössä OSCAR pyytää sinua valitsemaan kansion SleepyHead- tai OSCAR-tiedoilla Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Napsauta [OK] siirtyäksesi seuraavaan näyttöön tai [Ei], jos et halua käyttää mitään SleepyHead- tai OSCAR-tietoja. Unable to create the OSCAR data folder at Ei voitu luoda Oscarin datakansiota kohteessa Unable to write to OSCAR data directory Oscar ei voinut kirjoittaa Oscarin datakansioon Error code Virhekoodi OSCAR cannot continue and is exiting. Oscar ei voi jatkaa ja sulkeutuu. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Oscar ei voinut kirjoittaa debug-lokia. Voit silti käyttää debug-paneelia (Apua/Vianetsintä/Näytä Debug-paneeli), mutta debug-lokia ei voida kirjoittaa levylle. Version "%1" is invalid, cannot continue! Versio "%1" on epäkelpo, ei voi jatkaa! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Käyttämäsi Oscarin versio (%1) on VANHEMPI kuin millä nämä tiedot (%2) on luotu. Choose or create a new folder for OSCAR data Valitse tai luo uusi kansio Oscarin tiedoille Choose the SleepyHead or OSCAR data folder to migrate Valitse siirrettävä SleepyHead- tai OSCAR-datakansio The folder you chose does not contain valid SleepyHead or OSCAR data. Valitsemasi kansio ei sisällä kelvollisia SleepyHead- tai OSCAR-tietoja. If you have been using SleepyHead or an older version of OSCAR, Jos olet käyttänyt SleepyHeadia tai vanhempaa OSCAR-versiota, OSCAR can copy your old data to this folder later. OSCAR voi kopioida vanhat tiedot tähän kansioon myöhemmin. As you did not select a data folder, OSCAR will exit. Koska et valinnut tietojen kansiota, Oscar lopettaa toimintansa. The folder you chose is not empty, nor does it already contain valid OSCAR data. Valitsemasi kansio ei ole tyhjä eikä siellä ole Oscar-tietoa. Data directory: Tietojen kansio: It is likely that doing this will cause data corruption, are you sure you want to do this? Se voi mahdollisesti aiheuttaa tietojen hajoaminen. Oletko varma että haluat jatkaa? Question Kysymys Exiting Poistuu Are you sure you want to use this folder? Oletko varma, että haluat käyttää tätä kansiota? Don't forget to place your datacard back in your CPAP machine Älä unohda laittaa SD-kortti takaisin CPAP-laitteeseen OSCAR Reminder Oscar-muistutin You can only work with one instance of an individual OSCAR profile at a time. Voit käyttää kerrallaan vain yhtä Oscar-profiilia. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Jos käytät pilveä, varmista, että Oscar on suljettu ja synkronointi on valmis ensin toiselle koneelle. Loading profile "%1"... Lataa profiilia "%1"... Chromebook file system detected, but no removable device found Chromebook-tiedostojärjestelmä havaittu, mutta siirrettävää laitetta ei löytynyt You must share your SD card with Linux using the ChromeOS Files program Sinun on jaettava SD-korttisi Linuxin kanssa ChromeOS Files -ohjelmalla Recompressing Session Files Käyttöjaksotiedostojen uudelleenpakkaaminen Please select a location for your zip other than the data card itself! Valitse jokin toinen paikka zip-tiedostolle kuin Oscarin tietojen kansio! Unable to create zip! Ei voi luoda zip-tiedostoa! Are you sure you want to reset all your channel colors and settings to defaults? Haluatko varmasti palauttaa kaikki kanavien värit ja asetukset oletusasetuksiin? Are you sure you want to reset all your waveform channel colors and settings to defaults? Haluatko varmasti palauttaa kaikki aaltomuodon värit ja asetukset oletusasetuksiin? There are no graphs visible to print Yhtään näkyvää kaaviota ei ole tulostettavaksi Would you like to show bookmarked areas in this report? Haluatko näyttää kirjamerkityt alueet tässä raportissa? Printing %1 Report Tulostaa %1 raporttia %1 Report %1 Raportti : %1 hours, %2 minutes, %3 seconds : %1 tuntia, %2 minuuttia, %3 sekuntia RDI %1 RDI %1 AHI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 UAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 AI=%1 AI=%1 Reporting from %1 to %2 Raportti ajankohdasta %1 ajankohtaan %2 Entire Day's Flow Waveform Koko päivän virtauksen aaltomuoto Current Selection Nykyinen valinta Entire Day Koko päivä Page %1 of %2 Sivu %1 / %2 Days: %1 Päiviä: %1 Low Usage Days: %1 Vähäisen käytön päivät: %1 (%1% compliant, defined as > %2 hours) (%1% myöntyvyys, määritys > %2 tuntia) (Sess: %1) (Käyttöjakso: %1) Bedtime: %1 Nukkumaanmenoaika: %1 Waketime: %1 Heräämisaika: %1 (Summary Only) (Vain yhteenveto) There is a lockfile already present for this profile '%1', claimed on '%2'. Profiilille '%1' on asetettu jo aiemmin lukitus, kohde '%2'. Fixed Bi-Level Kiinnitetty Bi-taso Auto Bi-Level (Fixed PS) Automaattinen Bi-taso (kiinnitetty PS) Auto Bi-Level (Variable PS) Automaattinen Bi-taso (muuttuva PS) 99.5% 99.5% varies vaihtelee n/a - Fixed %1 (%2) Kiinnitetty %1 (%2) Min %1 Max %2 (%3) Min %1 Max %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %1 yli %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Viimeisimmät oksimetrin tiedot: <a onclick='alert("daily=%2");'>%1</a> (last night) (viime yö) (1 day ago) (1 päivä sitten) (%2 days ago) (%2 päivää sitten) No oximetry data has been imported yet. Vielä ei ole tuotu oksimetrin dataa. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings SmartFlex Settings ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Somnopose Software Zeo Zeo Personal Sleep Coach Henkilökohtainen univalmentaja Database Outdated Please Rebuild CPAP Data Tietokanta vanhentunut Ole hyvä ja uudista CPAP tiedot (%2 min, %3 sec) (%2 min, %3 sek) (%3 sec) (%3 sek) Pop out Graph Avaa kaavio The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Ponnahdusikkuna on täynnä. Sinun tulisi kaapata olemassa oleva ponnahdusikkuna, poista se ja avaa sitten tämä kaavio uudelleen. Your machine doesn't record data to graph in Daily View Cpap-laitteesi ei tallenna tietoa päivittäin näyttöön There is no data to graph Graafissa ei ole tietoja d MMM yyyy [ %1 - %2 ] p KKK vvvv [ %1 - %2 ] Hide All Events Piilota kaikki tapahtumat Show All Events Näytä kaikki tapahtumat Unpin %1 Graph Poista %1 kaavio Popout %1 Graph Avaa %1 kaavio Pin %1 Graph Kiinnitä %1 kaavio Plots Disabled Kuviot pois päältä Duration %1:%2:%3 Kesto %1:%2:%3 AHI %1 AHI %1 Relief: %1 Helpotus: %1 Hours: %1h, %2m, %3s Tunnit: %1h, %2m, %3s Machine Information Laitteen tiedot Journal Data Päivyritiedot OSCAR found an old Journal folder, but it looks like it's been renamed: Oscar löysi vanhan päivyrikansion, mutta se näyttää uudelleennimetyltä: OSCAR will not touch this folder, and will create a new one instead. Oscar ei koske tähän kansioon, ja sen sijaan luo uuden kansion. Please be careful when playing in OSCAR's profile folders :-P Ole huolellinen Oscarin profiilikansioiden kanssa :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. Jostakin syystä Oscar ei löytänyt päivyritietoja profiilistasi, mutta löysi useamman päivyrin kansiot. OSCAR picked only the first one of these, and will use it in future: Oscar poimi vain ensimmäisen näistä, ja käyttää sitä jatkossa: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Jos vanhoja tietoja ei löydy, kopioi kaikkien Journal_XXXXXXX kansioiden sisällön tähän kansioon käsin. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Backing up files... Varmuuskopioi tiedostoja... Reading data files... Lukee datatiedostoja... SmartFlex Mode SmartFlex-moodi Intellipap pressure relief mode. Intellipap paineenalennusmoodi. Ramp Only Vain viive Full Time Koko aika SmartFlex Level SmartFlex-taso Intellipap pressure relief level. Intellipap paineenalennuksen taso. Snoring event. Kuorsaustapahtuma. SN SN Locating STR.edf File(s)... Etsii STR.edf tiedosto(j)a... Cataloguing EDF Files... Järjestelee EDF-tiedostoja... Queueing Import Tasks... Jonottaa tiedon tuontitehtäviä... Finishing Up... Lopettelee... CPAP Mode CPAP-moodi VPAPauto VPAPauto ASVAuto ASVAuto iVAPS iVAPS PAC PAC Auto for Her Auto for Her EPR EPR ResMed Exhale Pressure Relief ResMed uloshengityksen paineenalennus Patient??? Potilas??? EPR Level EPR-taso Exhale Pressure Relief Level Uloshengityksen paineenalennuksen taso Response Vaste Patient View Käyttäjän näkymä SmartStart Älykäs käynnistys Machine auto starts by breathing Laite käynnistyy automaattisesti hengittäessä Smart Start Smart Start Humid. Status Kost. tila Humidifier Enabled Status Kostuttimen tila Humid. Level Kost. taso Humidity Level Kostutuksen taso Temperature Letkun lämpötila ClimateLine Temperature ClimateLine lämpötila Temp. Enable Lämmitys päällä ClimateLine Temperature Enable ClimateLine lämpötila päällä Temperature Enable Lämpötila päällä AB Filter AB suodatin Antibacterial Filter Antibakteerinen suodatin Pt. Access Pot. pääsy Essentials Olennaiset Plus Plus Climate Control Ilmastoint Manual Manuaalinen Soft Pehmeä Standard Standardi BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T SmartStop Älykäs pysäytys Machine auto stops by breathing Laite lopettaa automaattisesti hengityksen Smart Stop Älykäs pysäytys Simple Yksinkertainen Advanced Monipuolinen Your ResMed CPAP machine (Model %1) has not been tested yet. Sinun ResMed CPAP-laitemalliasi (Model %1) ei ole vielä testattu. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Se näyttää riittävän samanlaiselta kuin muut koneet, että se voisi toimia, mutta kehittäjät haluavat tämän koneen SD-kortin .zip -kopion varmistaakseen, että se toimii OSCAR:in kanssa. Parsing STR.edf records... Parsii STR.edf tietueita... Auto Auto Mask Maski ResMed Mask Setting ResMed maskin asetukset Pillows Sierain Full Face Kokokasvo Nasal Nenä Ramp Enable Viive päällä Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Snapshot %1 Kuvankaappaus %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... Lataa %1 tietoa kohteeseen %2... Scanning Files Selaa tiedostoja Migrating Summary File Location Yhteenvetotiedoston siirtäminen Loading Summaries.xml.gz Lataa Summaries.xml.gz Loading Summary Data Lataa yhteenvetotietoja Please Wait... Ole hyvä ja odota... Updating Statistics cache Päivittää tilastojen välimuistia Usage Statistics Käyttötilastot %1 Charts %1 Kaaviot %1 of %2 Charts %1 - %2 Kaaviot Loading summaries Lataa yhteenvetotietoja Dreem Dreem Your Viatom device generated data that OSCAR has never seen before. Sinun Viatom-laitteesi loi tietoja, joita Oscar ei ole aiemmin tavannut. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Tuodut tiedot eivät ole ajantasalla. Oscar-kehittäjät haluavat kopion Viatom-tiedostoista varmistaakseen, että Oscar käsittelee tietoa oikein. Viatom Viatom Viatom Software Viatom Software New versions file improperly formed Uuden version tiedosto väärin muodostettu A more recent version of OSCAR is available Oscarin uudempi versio on saatavilla release julkistus test version testiversio You are running the latest %1 of OSCAR Käytät Oscarin uusinta versiota %1 You are running OSCAR %1 Käytät Oscaria %1 OSCAR %1 is available <a href='%2'>here</a>. Oscar %1 on saatavilla <a href='%2'>täällä</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Tietoja uudemmasta testiversiosta %1 on saatavilla <a href='%2'>%2</a> Check for OSCAR Updates Tarkista Oscarin päivitykset Unable to check for updates. Please try again later. Päivityksiä ei voi tarkistaa. Yritä uudelleen myöhemmin. SensAwake level Automaattikäynnistyksen taso Expiratory Relief Uloshengityksen helpotus Expiratory Relief Level Uloshengityksen helpotuksen taso Humidity Kosteus SleepStyle Unityyli This page in other languages: Tämä sivu muilla kielillä: %1 Graphs %1 graafit %1 of %2 Graphs %1 graafi %2 graafeista %1 Event Types %1 tapahtumatyypit %1 of %2 Event Types %1 tapahtuma %2 tapahtumatyypeistä Report about:blank about:blank SessionBar %1h %2m %1h %2m No Sessions Present Ei käyttöjaksoja SleepStyleLoader Import Error Tuontivirhe This Machine Record cannot be imported in this profile. Tämän koneen tietuetta ei voida tuoda tähän profiiliin. The Day records overlap with already existing content. Päivän tietueet ovat päällekkäisiä jo olemassa oleville tiedoille. Statistics CPAP Statistics CPAP tilastot CPAP Usage CPAP käyttö Average Hours per Night Käyttöaika keskimäärin Therapy Efficacy Terapian tehokkuus Leak Statistics Vuototilastot Pressure Statistics Painetilastot Oximeter Statistics Oksimetritilastot Blood Oxygen Saturation Veren happipitoisuuden saturaatio Pulse Rate Pulssi %1 Median %1 Mediaani Average %1 Keskiarvo %1 Min %1 Minimi %1 Max %1 Maksimi %1 %1 Index %1 Indeksi % of time in %1 % ajasta %1:ssa % of time above %1 threshold % ajasta yli %1 kynnyksen % of time below %1 threshold % ajasta alle %1 kynnyksen Name: %1, %2 Nimi: %1, %2 DOB: %1 Synt: %1 Phone: %1 Puhelin: %1 Email: %1 Sähköposti: %1 Address: Osoite: Oscar has no data to report :( Oscarilla ei ole mitään tietoja raportoitavaksi :( Days Used: %1 Päivää käytössä: %1 Low Use Days: %1 Vähäinen käyttö, päivää: %1 Compliance: %1% Hoitomyöntyvyys: %1% Days AHI of 5 or greater: %1 Päivää, AHI 5 tai korkeampi: %1 Best AHI Paras AHI Date: %1 AHI: %2 Pvm: %1 AHI: %2 Worst AHI Huonoin AHI Best Flow Limitation Paras virtauksen rajoite (FL) Date: %1 FL: %2 Pvm: %1 FL: %2 Worst Flow Limtation Huonoin virtauksen rajoite No Flow Limitation on record Virtauksen rajoite ei löydy tallenteesta Worst Large Leaks Huonoimmat suuret vuodot Date: %1 Leak: %2% Pvm: %1 Vuoto: %2% No Large Leaks on record Suuria vuotoja ei löydy tallenteesta Worst CSR Huonoin CSR Date: %1 CSR: %2% Pvm: %1 CSR %2% No CSR on record CSR ei löydy tallenteesta Worst PB Huonoin PB Date: %1 PB: %2% Pvm: %1 PB: %2% No PB on record PB ei löydy tallenteesta Want more information? Haluatko enemmän tietoa? OSCAR needs all summary data loaded to calculate best/worst data for individual days. Oscar tarvitsee kaiken yhteenvetotiedon laskeakseen parhaimman/huonoimman tiedon yksittäiselle päivälle. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Valitse Oscarin asetuksista valintaruudun "Lataa ennalta kaikki yhteenvetotiedot" varmistamaan tietojen saatavuutta. Best RX Setting Paras paineasetus Date: %1 - %2 Pvm: %1 - %2 AHI: %1 AHI: %1 Total Hours: %1 Tunteja kaikkiaan: %1 Worst RX Setting Huonoin paineasetus Most Recent Viimeisin Compliance (%1 hrs/day) Sopivuus (%1 tuntia/päivä) Changes to Machine Settings Muutokset laitteen asetuksiin No data found?!? Yhtään tietoja ei löytynyt?!? Last Week Viime viikko Last 30 Days Viimeiset 30 päivää Last 6 Months Viimeiset 6 kuukautta Last Year Viimeinen vuosi Last Session Viimeinen käyttöjakso Details Yksityiskohdat No %1 data available. Yhtään %1 tietoa ei ole saatavilla. %1 day of %2 Data on %3 %1 päivä %2-tietoa %3 %1 days of %2 Data, between %3 and %4 %1 päivää %2-tietoja, aikavälillä %3 ja %4 OSCAR is free open-source CPAP report software Oscar on vapaa avoimen lähdekoodin CPAP raportointiohjelma Days Päiviä Pressure Relief Paineenalennus Pressure Settings Paineasetukset Machine Information Laitteen tiedot This report was prepared on %1 by OSCAR %2 Tämä raportti on luotu %1 Oscar %2 ohjelmalla First Use Ensimmäinen käyttökerta Last Use Viimeinen käyttö Welcome Welcome to the Open Source CPAP Analysis Reporter Tervetuloa avoimen lähdekoodin CPAP analysointi- ja raportointiohjelmaan What would you like to do? Mitä haluat tehdä? CPAP Importer CPAP-tuonti Oximetry Wizard Oksimetri-velho Daily View Päivittäin Overview Yleiskatsaus Statistics Tilastot <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Varoitus: </span><span style=" color:#ff0000;">ResMed S9 SD-kortit pitää lukita </span><span style=" font-weight:600; color:#ff0000;">ennen asettamista tietokoneeseen.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Jotkut käyttöjärjestelmät kirjoittavat indeksitiedostot kortille kysymättä. Se voi vahingoittaa kortin tietoja lukukelvottomiksi cpap-laitteellesi.</span></p></body></html> It would be a good idea to check File->Preferences first, On hyvä tarkistaa Tiedosto->Asetukset ensin, as there are some options that affect import. koska osa asetuksista vaikuttaa tiedon tuontiin. Note that some preferences are forced when a ResMed machine is detected Huomaa, että jotkut asetukset ovat pakotettuja kun ResMedin kone on tunnistettu First import can take a few minutes. Ensimmäinen tuonti voi kestää muutaman minuutin. The last time you used your %1... Käytit %1 laitetta edellisen kerran... last night viime yönä %2 days ago %2 päivää sitten was %1 (on %2) %1 (%2) %1 hours, %2 minutes and %3 seconds %1 tuntia, %2 minuuttia ja %3 sekuntia Your machine was on for %1. Laitteesi oli käytössä %1. <font color = red>You only had the mask on for %1.</font> <font color = red>Maskisi on ollut käytössäsi viimeksi vain %1.</font> under vähemmän over enemmän reasonably close to kohtuudella lähellä equal to yhtä kuin You had an AHI of %1, which is %2 your %3 day average of %4. AHI-arvosi oli %1, mikä on %2 kuin %3 päivän %4 keskiarvosi. Your CPAP machine used a constant %1 %2 of air CPAP-laitteesi käytti ilman vakiota %1 %2 Your pressure was under %1 %2 for %3% of the time. Hoitopaineesi oli %3% ajasta alle %1 %2. Your machine used a constant %1-%2 %3 of air. Laitteesi käytti ilman vakiota %1-%2 %3. Your EPAP pressure fixed at %1 %2. EPAP paineesi oli jatkuva %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. IPAP paineesi oli alle %1 %2 %3% ajasta. Your EPAP pressure was under %1 %2 for %3% of the time. EPAP paineesi oli alle %1 %2 %3% ajasta. Your machine was under %1-%2 %3 for %4% of the time. Laitteesi oli alle %1-%2 %3 %4% ajasta. 1 day ago 1 päivä sitten Your average leaks were %1 %2, which is %3 your %4 day average of %5. Keskimääräinen vuotosi oli %1 %2, mikä on %3 kuin sinun %4 päivän keskiarvosi %5 %2. No CPAP data has been imported yet. Yhtään CPAP-tietoja ei ole tuotu vielä. gGraph %1 days %1 päivää gGraphView 100% zoom level 100% zoom-taso Restore X-axis zoom to 100% to view entire selected period. Palauta X-akselin zoomaus 100% nähdäksesi koko valitun jakson tiedot. Restore X-axis zoom to 100% to view entire day's data. Palauta X-akselin zoom 100% nähdäksesi koko päivän tiedot. Reset Graph Layout Resetoi kaavion sijoitus Resets all graphs to a uniform height and default order. Resetoi kaikki kaaviot oletuskorkeuteen ja -järjestykseen. Y-Axis Y-akseli Plots Kuviot CPAP Overlays CPAP peittokuvat Oximeter Overlays Oksimetrin peittokuvat Dotted Lines Pisteviivat Double click title to pin / unpin Click and drag to reorder graphs Kaksoisnapsauta otsikkoa kiinnittääksesi / vapauttaaksesi Napsauta ja raahaa graafi haluamaasi kohtaan Remove Clone Poista klooni Clone %1 Graph Kloonaa %1-kaavio OSCAR-code-v1.3.1/Translations/Svenska.sv.ts000066400000000000000000014705061417327530600206000ustar00rootroot00000000000000 AboutDialog &About &Om Release Notes Nyheter i denna version Credits Delaktiga GPL License GPL License Close Stäng Show data folder Visa Data-katalog About OSCAR %1 Om OSCAR %1 Sorry, could not locate About file. Tyvärr, kunde inte hitta Om-filen. Sorry, could not locate Credits file. Tyvärr, kunde inte hitta Delaktiga-filen. Sorry, could not locate Release Notes. Tyvärr, kunde inte hitta versions-ändringar. OSCAR %1 OSCAR %1 Important: Viktigt: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Eftersom det här är en förhands-version rekommenderas att du <b> säkerhetskopierar din datamapp manuellt </b> innan du fortsätter, eftersom det kan misslyckas att backa tillbaka senare. To see if the license text is available in your language, see %1. För att se om lisenstexten är tillgänglig på ditt språk, klicka här %1. CMS50F37Loader Could not find the oximeter file: Kunde inte hitta oximeter-filen: Could not open the oximeter file: Kunde inte öppna oximeter-filen: CMS50Loader Could not get data transmission from oximeter. Kan inte få data-anslutningen att fungera från pulsoximetern. Please ensure you select 'upload' from the oximeter devices menu. Försäkra dig om att du valt "upload" från displayen på pulsoximetern. Could not find the oximeter file: Kunde inte hitta oximeter-filen: Could not open the oximeter file: Kunde inte öppna oximeter-filen: CheckUpdates Checking for newer OSCAR versions Letar efter en nyare version av OSCAR Daily Go to the previous day Gå till föregående dag Show or hide the calender Dölj eller visa kalender Go to the next day Gå till nästa dag Go to the most recent day with data records Gå till senaste dagen med data Events Händelser View Size Visa storlek Notes Noteringar Journal Journal Small Liten Medium Medium Big Stor Color Färg u u B B Zombie Zombie I'm feeling ... Jag känner mig ... Weight Vikt If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Om höjden är större än noll i längdrutan visas Body Mass Index (BMI) -värdet Awesome Toppen B.M.I. B.M.I. Bookmarks Bokmärken Add Bookmark Lägg till bokmärke Starts Startar Remove Bookmark Ta bort bokmärke Flags Flaggor Graphs Grafer Show/hide available graphs. Visa/dölj tillgängliga grafer. Breakdown Fördela events händelser Time at Pressure Tid vid tryck No %1 events are recorded this day Inga %1 händelser är registrerade denna dag %1 event %1 händelse %1 events %1 händelser Oximetry Sessions Oximeter-inspelning Click to %1 this session. Klicka för att %1 den här inspelningen. disable inaktivera enable aktivera %1 Session #%2 %1 Inspelning #%2 %1h %2m %3s %1t %2m %3s Model %1 - %2 Modell %1 - %2 PAP Mode: %1 PAP Läge: %1 This day just contains summary data, only limited information is available. Den här dagen innehåller bara sammanfattningsdata, endast begränsad information är tillgänglig. No data is available for this day. Ingen data är tillgänglig för den här dagen. Event Breakdown Händelser i detalj Sessions all off! Alla sessioner av! Sessions exist for this day but are switched off. Sessioner finns för denna dag men är avstängda. Impossibly short session Onaturligt kort session Zero hours?? 0 timmar?? BRICK :( Tegelsten :-( Complain to your Equipment Provider! Klaga till återförsäljaren! Statistics Statistisk Oximeter Information Oximeter information Session Start Times Periodens starttid Session End Times Periodens sluttid Duration Varaktighet UF1 UF1 UF2 UF2 Position Sensor Sessions Lägesgivaren Period Unknown Session Okänd session SpO2 Desaturations Minskning av syrgasmättnad Pulse Change events Pulsförändringar SpO2 Baseline Used Baslinje för syrgasmättnad Machine Settings Maskininställningar Details Detaljer Session Information Sessionsinformation CPAP Sessions CPAP-sessioner Sleep Stage Sessions Sömnstadiesessioner <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Observera:</b> Alla inställningar som visas nedan är baserade på att ingenting har ändrats sedan tidigare dagar. (Mode and Pressure settings missing; yesterday's shown.) (Tryckinställningar saknas; visar gårdagens.) 99.5% 99.5% Total time in apnea Total tid med apné Time over leak redline Tid över röda läckage-linjen Total ramp time Total ramptid Time outside of ramp Tid utanför ramp Start Börja End Stopp Unable to display Pie Chart on this system Kan inte visa tårtdiagram på denna dator 10 of 10 Event Types 10 av 10 typ av händelser This CPAP machine does NOT record detailed data Den här CPAP-maskinen sparar inga detaljerade data Sorry, this machine only provides compliance data. Tyvärr, den här maskinen sparar bara compliance-data. This bookmark is in a currently disabled area.. Detta bokmärke finns i ett för närvarande inaktiverat område.. 10 of 10 Graphs 10 av 10 grafer "Nothing's here!" Ingenting här Pick a Colour Välj en färg Bookmark at %1 Bokmärke på %1 ExportCSV Export as CSV Exportera som CSV Dates: Datum: Resolution: Upplösning: Details Detaljer Sessions Sessioner Daily Daglig Filename: Filnamn: Cancel Avbryt Export Exportera Start: Börja: End: Sluta: Quick Range: Snabbintervall: Most Recent Day Den senaste dagen Last Week Sista veckan Last Fortnight Senaste 2 veckorna Last Month Senaste månaden Last 6 Months Senaste 6 månaderna Last Year Senaste året Everything Allt Custom Anpassa Details_ Detaljer_ Sessions_ Sessioner_ Summary_ Sammanfattning_ Select file to export to Välj fil att exportera till CSV Files (*.csv) CSV-filer (*.csv) DateTime ??? Datum Session Session Event Händelse Data/Duration Data/Varaktighet Date Datum Session Count Antal sessioner Start Börja End Sluta Total Time Total tid AHI AHI Count Räkna FPIconLoader Import Error Import-fel This Machine Record cannot be imported in this profile. Maskin-data kan inte importeras till denna profil. The Day records overlap with already existing content. Den här dagens data överlappar redan sparat innehåll. Help Hide this message Dölj detta meddelande Search Topic: Sök ämne: Help Files are not yet available for %1 and will display in %2. Hjälp-filer är ännu ej tillgängliga för %1 och kommer att visas i %2. Help files do not appear to be present. Hjälp-filer verkar inte vara aktuella. HelpEngine did not set up correctly Hjälp-avsnittet installerades ej korrekt HelpEngine could not register documentation correctly. Hjälp-avsnittet kunde inte registrera informationen korrekt. Contents Innehåll Index Index Search Sök No documentation available Ingen dokumentation är tillgänglig Please wait a bit.. Indexing still in progress Var snäll och vänta.. Indexeringen pågår No Nej %1 result(s) for "%2" %1 resultat(en) för "%2" clear klar MD300W1Loader Could not find the oximeter file: Kunde inte hitta oximeter-filen: Could not open the oximeter file: Kunde inte öppna oximeter-filen: MainWindow &Statistics &Statistik Report Mode Rapportläge Standard Standard Monthly Månadsvis Date Range Datumintervall Statistics Statistik Daily Daglig vy Overview Översikt Oximetry Oximetri Import Import Help Hjälp &File &Fil &View &Visa &Help &Hjälp Troubleshooting Felsökning &Data &Data &Advanced &Avancerad &Maximize Toggle &Maximera fönster Reset Graph &Heights Återställ graf &höjd Import &Viatom/Wellue Data Importera &Viatom/Wellue Data Report an Issue Rapportera ett problem &Preferences &Inställningar &Profiles &Profiler E&xit G&å ur Navigation Navigering Profiles Profiler Bookmarks Bokmärken Records Rekord &Reset Graphs &Återställ graferna Purge Oximetry Data Radera Oximeter-data Rebuild CPAP Data Återuppbygg CPAP-data &Import CPAP Card Data &Importera data från CPAP:ens minneskort Exit Avsluta View &Daily Visa &Daglig vy Show Daily view Visa Daglig vy View &Overview Visa &Översikt Show Overview view Visa Översiktsvy View &Welcome Visa &Välkommen Use &AntiAliasing Använd &antialias Maximize window Maximera fönster Show Debug Pane Visa felsökningsfönster Import &Dreem Data Importera &Dreem data Purge Current Selected Day Rensa valda dagar &CPAP &CPAP &Oximetry &Oximeter &Sleep Stage &Sömnsteg &Position &Position &All except Notes &Alla utom noterade All including &Notes Alla inklusive &Noteringar Create zip of CPAP data card Skapa en zip-fil av CPAP:ens minneskort Create zip of OSCAR diagnostic logs Skapar en zip-fil av OSCAR:s felrapporteringslogg Create zip of all OSCAR data Skapa en zip-fil av alla OSCAR:s sparade data Standard graph order, good for CPAP, APAP, Bi-Level Standardvisning av grafer, passar för CPAP, APAP, Bi-Level Advanced Avancerat Advanced graph order, good for ASV, AVAPS Avancerad visning av grafer, passar AVS, AVAPS Show Personal Data Visa personliga data Check For &Updates Kontrollera om det finns &Uppdateringar Reset sizes of graphs Återställ storlek på grafer Take &Screenshot Ta en &skärmdump O&ximetry Wizard O&ximetriguiden Show Right Sidebar Visa högra menyn Show Statistics view Visa Statistikvyn Show &Line Cursor Visa &Linje Markör Show Daily Left Sidebar Visa Dagliga vänstermenyn Show Daily Calendar Visa Dagliga kalendern Show Performance Information Visa prestandainformation CSV Export Wizard CSV-exportguide Export for Review Exportera för granskning System Information System Information Show &Pie Chart Visa &Cirkeldiagram Show Pie Chart on Daily page Visa cirkeldiagram på Dagliga vyn Exp&ort Data Exp&ortera data Purge ALL Machine Data Rensa ALLA Maskindata &About OSCAR &Om OSCAR &Automatic Oximetry Cleanup &Automatisk oximeterrensning Purge &Current Selected Day Radera den &aktuella dagen View S&tatistics Visa S&tatistik View Statistics Visa Statistik Change &Language Ändra &språk Change &Data Folder Ändra &datamapp Import &Somnopose Data Importera &Somnopose-data Current Days Aktuella dagar Daily Calendar Visa daglig kalender Backup &Journal Backup &journal Print &Report Skriv ut &rapport &Edit Profile &Redigera profil Online Users &Guide Online användar&handbok &Frequently Asked Questions &Återkommande frågor Change &User Växla &användare Right &Sidebar Visa höger&menyn Daily Sidebar Visa dagliga sidopanelen Import &ZEO Data Importera &ZEO-data Import RemStar &MSeries Data Importera Remstar &MSeries-data Sleep Disorder Terms &Glossary Sömnstörningar uttryck och &ordlista Welcome Välkommen &About &Om Please wait, importing from backup folder(s)... Vänta, importerar från backup-mappen(s)... Import Problem Importproblem Choose a folder Välj en mapp Imported %1 CPAP session(s) from %2 Importerade %1 CPAP-session(er) från %2 Import Success Importering lyckades Already up to date with CPAP data at %1 Data är redan uppdaterade för %1 Up to date Uppdaterad Couldn't find any valid Machine Data at %1 Hittade inga giltiga maskindata på %1 Please insert your CPAP data card... Sätt in CPAP minnes-kortet... Access to Import has been blocked while recalculations are in progress. Tillgången till import har blockerats medan omräkning pågår. A %1 file structure for a %2 was located at: En %1 fil struktur för %2 hittades här: A %1 file structure was located at: En %1 fil struktur hittades här: CPAP Data Located CPAP-data hittades Import Reminder Importpåminnelse Importing Data Importerar data There was a problem opening %1 Data File: %2 Det var ett problem med att öppna %1 Datafil: %2 %1 Data Import of %2 file(s) complete %1 Dataimporten av %2 fil/filer är färdig %1 Import Partial Success %1 Importen är delvis lyckad %1 Data Import complete %1 Dataimporten är färdig OSCAR Information OSCAR Information Help Browser Hjälpavsnitt Loading profile "%1" Laddar profile "%1" Import is already running in the background. Importen körs redan i bakgrunden. Would you like to import from this location? Vill du importera härifrån? Specify Specificera The Glossary will open in your default browser Ordlistan öppnas i din standardwebbläsare Please note, that this could result in loss of data if OSCAR's backups have been disabled. Observera, det här kan resultera i förlorade data om OSCAR's interna backup är avstängd. The FAQ is not yet implemented Vanliga frågor (FAQ) är inte i funktion för tillfället No profile has been selected for Import. Ingen profil har blivit markerad för import. %1 (Profile: %2) %1 (Profil: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Kom ihåg att välja root-mappen eller enhetensbokstaven på minneskortet och inte en mapp på kortet. Find your CPAP data card Sök din CPAP:s minneskort Check for updates not implemented Kontrollera uppdateringar som ej är genomförda Choose where to save screenshot Välj vart du vill spara skärmdumpar Image files (*.png) Bildfiler (*.png) The User's Guide will open in your default browser Användarmanualen öpnnas i din standardwebbläsare If you can read this, the restart command didn't work. You will have to do it yourself manually. Om du kan läsa det här, så fungerar inte den automatiska omstarten. Du måste starta om manuellt. Are you sure you want to rebuild all CPAP data for the following machine: Är du säker att du vill återskapa alla CPAP-data för följande maskiner: For some reason, OSCAR does not have any backups for the following machine: Av någon anledning, så har inte OSCAR någon säkerhetskopia för följande maskiner: OSCAR does not have any backups for this machine! OSCAR har ingen backup för den här maskinen! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> Såvida du inte har gjort <i>din <b>egen</b> säkerhetskopia för ALLA dina data från den här maskinen</i>, <font size=+2>så förlorar du den här maskinens data <b>permanent</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> Du är på väg att <font size=+2>radera/förstöra</font> OSCAR's maskin-databas för följande maskiner:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: Åtkomst nekades under raderingsåtgärden, så följande katalog måste raderas manuellt: No help is available. Ingen hjälpfil är tillgänglig. Export review is not yet implemented Export-granskning är inte i funktion än Would you like to zip this card? Vill du göra en zip-fil av det här kortet? Choose where to save zip Välj vart du vill spara zip-filen ZIP files (*.zip) ZIP filer (*.zip) Creating zip... Skapa zip-fil... Calculating size... Beräknar storlek... Reporting issues is not yet implemented Att rapportera ett problem är inte i funktion än Please open a profile first. Öppna en profil först. Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Förutsett att du har gjort <i>en <b>egen</b> backup för alla dina CPAP-data</i>, så kan du fortfarande slutföra denna åtgärd. Men du måste återställa dina data manuellt från din backup. Are you really sure you want to do this? Är du verkligen säker på att du vill göra detta? Because there are no internal backups to rebuild from, you will have to restore from your own. Därför att det finns ingen automatisk backup att återställa från, så du måste återställa från din egna. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Vill du återställa från din egen backup nu? (du kommer inte att se några data förrän du gör så) Note as a precaution, the backup folder will be left in place. Notera att som en försiktighetsåtgärd, kommer säkerhetskopierings-mappen att lämnas kvar på samma plats. Are you <b>absolutely sure</b> you want to proceed? Är du <b>helt säker</b> att du vill fortsätta? %1's Journal %1's Journal Choose where to save journal Välj vart du vill spara din journal XML Files (*.xml) XML-filer (*.xml) There was an error saving screenshot to file "%1" Det uppstod ett fel när skärmdumpen skulle sparas till filen "%1" Screenshot saved to file "%1" Skärmdumpen sparades till filen "%1" Are you sure you want to delete oximetry data for %1 Är du säker på att du vill ta bort oximetridata för %1 <b>Please be aware you can not undo this operation!</b> <b>Tänk på att du INTE kan ångra den här åtgärden!</b> Select the day with valid oximetry data in daily view first. Markera dagen med giltiga oximetridata i daglig vy först. Access to Preferences has been blocked until recalculation completes. Tillgång till Preferences har blockerats tills omräkning avslutas. There was a problem opening MSeries block File: Det var ett problem att öppna MSeries block fil: MSeries Import complete MSeries import är klar MinMaxWidget Auto-Fit Autoanpassa Defaults Standard Override Skriva över The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Y-axelns skala "Auto-justerar" för rätt visning. "Standard" sätter skalan efter tillverkaren och "Skriv över" för att välja din egen. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Minimum Y-axelvärde .. Observera att detta kan vara ett negativt tal om du vill. The Maximum Y-Axis value.. Must be greater than Minimum to work. Maximum Y-axelvärde .. Måste vara större än Minimum för att fungera. Scaling Mode Skalans visning This button resets the Min and Max to match the Auto-Fit Den här knappen återställer Min och Max till att passa "Auto-justera" NewProfile Edit User Profile Redigera användarprofil about:blank om:tom I agree to all the conditions above. Jag godkänner alla villkor ovan. User Information Användar-information User Name Användar-namn Password Protect Profile Lösenordsskyddad profil Password Lösenord ...twice... igen... Locale Settings Lokala inställningar Country Land TimeZone Tidszon Very weak password protection and not recommended if security is required. Väldigt svagt lösenord som ej rekomenderas om säkerhet är viktigt. DST Zone Sommartid Personal Information (for reports) Personlig information (för rapporter) First Name Förnamn Last Name Efternamn It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Det är helt ok att ljuga eller hoppa över det här, men din ungefärliga ålder behövs för att öka noggrannheten i vissa beräkningar. D.O.B. Födelsedatum. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Biologiskt (född) kön behövs ibland för att öka noggrannheten i några beräkningar, men lämna gärna tomt om du kan vara utan dom </p></body></html> Gender Kön Male Man Female Kvinna Height Längd Metric Metrisk English Engelsk Contact Information Kontaktinformation Address Adress Email E-post Phone Telefon CPAP Treatment Information CPAP behandlingsinformation Date Diagnosed Datum för diagnos Untreated AHI Obehandlad AHI CPAP Mode CPAP-inställning CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure Föreskrivet tryck Doctors / Clinic Information Läkares journalföring Doctors Name Läkare Practice Name Sömnklinik Patient ID Patient ID &Cancel &Avbryt &Back &tillbaka &Next &nästa Select Country Välj land This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Denna programvara är utformad för att hjälpa dig att granska sömndata som sparats av din CPAP-maskin och av annan tillhörande utrustning. PLEASE READ CAREFULLY VAR VÄNLIG OCH LÄS DETTA NOGA Accuracy of any data displayed is not and can not be guaranteed. Noggrannhet av alla data som visas är inte och kan inte garanteras. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Alla rapporter som genereras är FÖR PERSONLIGT BRUK, och passar INTE PÅ NÅGOT SÄTT för Compliance eller för medicinska diagnostiska ändamål. Use of this software is entirely at your own risk. Användning av denna programvara sker helt på egen risk. Welcome to the Open Source CPAP Analysis Reporter Välkommen till Open Source CPAP Analys rapportering OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR har släppts fri under <a href='qrc:/COPYING'>GNU Public License v3</a>, och levereras utan garanti och utan att göra anspråk på att passa för några som helst ändamål. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR är endast avsedd som data-presenterare, och är definitivt inte en ersättning för kompetent medicinsk rådgivning från din läkare. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Upphovsmännen kan inte hållas ansvariga för <u>någonting</u> i samband med användning eller missbruk av denna programvara. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR är upphovsrättsskyddad &copy;2011-2018 Mark Watkins och delvis &copy;2019-2022 The OSCAR Team Please provide a username for this profile Ange ett användarnamn för den här profilen Passwords don't match Lösenorden matchar inte Profile Changes Profil-ändringar Accept and save this information? Acceptera och spara denna information? &Finish &Avsluta &Close this window &Stäng detta fönster Overview Range: Intervall: Last Week Sista veckan Last Two Weeks Senaste 2 veckorna Last Month Senaste månaden Last Two Months Senaste 2 månaderna Last Three Months Senaste 3 månaderna Last 6 Months Senaste 6 månaderna Last Year Senaste året Everything Allt Custom Anpassa Snapshot Skärmdump Start: Börja: End: Avsluta: Reset view to selected date range Återställ vy till valt datumintervall Toggle Graph Visibility Växla grafens synlighet Drop down to see list of graphs to switch on/off. Öppna meny för att se lista på grafer att visa/dölja. Graphs Grafer Respiratory Disturbance Index Andnings (Respiratory) Störnings (Disturbance) Index Apnea Hypopnea Index Apnea Hypopnea Index Usage Compliance Usage (hours) Användning (timmar) Session Times Antal perioder Total Time in Apnea Apné - total tid Total Time in Apnea (Minutes) Sammanlagd tid med andningsstillestånd (Minuter) Body Mass Index Body Mass Index How you felt (0-10) Hur du känner dig (0-10) 10 of 10 Charts 10 av 10 diagram Show all graphs Visa alla grafer Hide all graphs Dölj alla grafer OximeterImport Oximeter Import Wizard Oximeter importguide Skip this page next time. Hoppa över den här sidan nästa gång. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Vänligen notera: </span><span style=" font-style:italic;">Välj först rätt pulsoxymetertyp från listboxen nedan.</span></p></body></html> Where would you like to import from? Varifrån vill du importera? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FÖRST välj din pulsoxymeter från dessa grupper:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. CMS50E / F-användare, när du importerar direkt, snälla, välj inte uppladdning på din enhet förrän OSCAR uppmanar dig att göra det. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Om det är aktiverat, återställer OSCAR automatiskt din CMS50s interna klocka med datorns nuvarande tid. </p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>Här kan du skriva ett 7 tecken långt namn för din pulsoximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Det här valet raderar den sparade inspelningen från din pulsoximeter efter att importen är klar. </p><p>Använd med försiktighet, eftersom om något går snett innan OSCAR har hunnit spara inspelningen så kan du inte få den tillbaka.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Det här valet låter dig importera (via kabel) från din pulsoximeters sparade inspelningar.</p><p>Efter att du gjort det här valet så måste du på gamla Contec pulsoximetrar starta uppladdningen via Contecs meny.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Om du inte har något emot att vara ansluten till en dator som är påslagen över natten, kan detta alternativ ge en användbar plethysomogram-kurva, vilket ger en indikation på hjärtrytmen, ovanpå den normala oximetriavläsningen.</p></body></html> Record attached to computer overnight (provides plethysomogram) Registrering ansluten till dator över natten (erbjuder plethysomogram) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Det här alternativet tillåter dig att importera från datafiler som skapats av program som följde med pulsoximetern, tex: SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Importera från en datafil som sparats av ett annat program, tex: SpO2Review Please connect your oximeter device Anslut din oximeter-enhet Please connect your oximeter device, turn it on, and enter the menu Anslut din pulsoximeter, starta den och gå in i menyn Press Start to commence recording Tryck på Start för att påbörja inspelningen Show Live Graphs Visa Live grafer Duration Varaktighet Pulse Rate Puls Multiple Sessions Detected Flera inspelningar upptäckta Start Time Starttid Details Detaljer Import Completed. When did the recording start? Importen är klar. När påbörjades inspelningen? Oximeter Starting time Oximeter-inspelning startade I want to use the time reported by my oximeter's built in clock. Jag vill använda den tid som rapporteras av min oximeters inbyggda klocka. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Jag startade denna pulsoximeter-inspelning samtidigt (eller nära den tid) som jag startade min CPAP-maskin. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Observera: Att synkronisera med CPAP-inspelningens starttid kommer alltid att vara mera exakt.</p></body></html> Choose CPAP session to sync to: Välj CPAP-inspelning att synkronisera med: You can manually adjust the time here if required: Du kan manuellt justera tiden här om det behövs: HH:mm:ssap HH:mm:ssap &Cancel &Avsluta &Information Page &Informationssida CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Ställ in enhetens datum/tid <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Kryssa i för att automatiskt identifiera enheten vid nästa import, vilket är värdefullt om du har flera pulsoximetrar.</p></body></html> Set device identifier Välj enhetsidentifierare Erase session after successful upload Radera sessionen efter att uppladdningen har lyckats Import directly from a recording on a device Importerar direkt från en inspelning på en enhet <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Påminnelse för CPAP-användare: </span><span style=" color:#fb0000;">Kom du ihåg att importera dina sömn-data från CPAP:en först?<br/></span>Om du glömde så har du ingen giltig tid att synka denna oximeter-session mot.<br/>.</p></body></html> If you can read this, you likely have your oximeter type set wrong in preferences. Om du kan läsa denna text, har du sannolikt din oximeter-typ fel i inställningar. Please choose which one you want to import into OSCAR Vänligen välj vilken du vill importera till OSCAR Day recording (normally would have) started Dagsimport (normalt ska den ha) har startats <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR behöver en starttid för att veta var du vill spara den här oximetri-inspelningen.</p><p>Välj ett av följande alternativ:</p></body></html> &Retry &Försök igen &Choose Session &Välj inspelning &End Recording &Slut på inspelning &Sync and Save &Synka och spara &Save and Finish &Spara och avsluta &Start &Start Scanning for compatible oximeters Söker efter kompatibla oximetrar Could not detect any connected oximeter devices. Kunde inte detektera någon ansluten oximeter-enhet. Connecting to %1 Oximeter Ansluter till %1 oximeter Renaming this oximeter from '%1' to '%2' Ändra den här oximetern från '%1' till '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Pulsoximeterns namn är annorlunda. Om du bara har en och delar den mellan olika profiler, ange samma namn på båda profilerna. "%1", session %2 "%1", session %2 Nothing to import Ingenting att importera Your oximeter did not have any valid sessions. Din pulsoximeter har ingen giltig session. Close Stäng Waiting for %1 to start Väntar att %1 ska starta Waiting for the device to start the upload process... Väntar att enheten ska starta uppladdningsprocessen... Select upload option on %1 Välj metod för uppladdning på %1 You need to tell your oximeter to begin sending data to the computer. Du behöver tala om för pulsoximetern att den ska börja sända data till datorn. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Anslut din oximeter, gå in i menyn och välj "Upload" för att påbörja dataöverföring ... %1 device is uploading data... %1 enheten laddar upp data... Please wait until oximeter upload process completes. Do not unplug your oximeter. Vänta tills oximeter-uppladdningsprocessen är klar. Koppla inte bort din oximeter. Oximeter import completed.. Oximeter-import är färdig.. Select a valid oximetry data file Välj en giltig oximeter datafil Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Oximeter-filer (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Ingen Oximeter-modul kunde tolka den angivna filen: Live Oximetry Mode LIVE Oximeter-läge Live Oximetry Stopped LIVE Oximeter-läge stoppad Live Oximetry import has been stopped LIVE Oximeter-import har stoppats Oximeter Session %1 Oximeter inspelning %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR ger dig möjlighet att spara oximetridata tillsammans med CPAP sömndata , som kan ge värdefull insikt i effektiviteten av CPAP behandlingen. OSCAR kan också fungera fristående med pulsoximeter, så att du kan lagra, spåra och granska dina inspelade data. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR är för närvarande kompatibelt med Contec CMS50D+, CMS50E, CMS50F och CMS50I serial oximetrar.<br/>(Obs: Direkt import från bluetooth modeller är <span style=" font-weight:600;">förmodligen inte</span> möjligt ännu) If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Om du försöker synkronisera oximeter-data och CPAP data se till att du importerat CPAP-datan först innan du fortsätter! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. För att OSCAR ska kunna hitta och läsa direkt från din oximeter-enhet måste du se till att rätt drivrutiner (t.ex. USB till seriell UART) har installerats på datorn. För mer information om detta, %1klicka här%2. Oximeter not detected Ingen oximeter ansluten Couldn't access oximeter Kunde inte ansluta till oximetern Starting up... Startar... If you can still read this after a few seconds, cancel and try again Om du fortfarande efter några sekunder kan läsa detta, avsluta och försök igen Live Import Stopped "LIVE"-import stoppad %1 session(s) on %2, starting at %3 %1 inspelning(ar) på %2, startade %3 No CPAP data available on %1 Ingen CPAP-data tillgänglig på %1 Recording... Spelar in... Finger not detected Inget finger detekterat I want to use the time my computer recorded for this live oximetry session. Jag vill använda tiden datorn registrerat för denna "LIVE"-oximetri-inspelning. I need to set the time manually, because my oximeter doesn't have an internal clock. Jag vill sätta tiden manuellt, eftersom min oximeter inte har egen inbyggd klocka. Something went wrong getting session data Något gick fel vid mottagandet av inspelningsdata Welcome to the Oximeter Import Wizard Välkommen till importguiden för pulsoximetrar Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Pulsoximetrar är medicintekniska produkter som används för att mäta blodets syremättnad. Under längre andningsuppehåll och onormala andningsmönster, kan blodets syremättnad sjunka avsevärt, och kan tyda på tillstånd som behöver läkarvård. You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Du kanske vill veta att andra företag, såsom Pulox, helt enkelt märker om Contec CMS50's under nya namn, såsom Pulox PO-200, PO-300, PO-400. Dessa bör också fungera. It also can read from ChoiceMMed MD300W1 oximeter .dat files. OSCAR kan också läsa från ChoiceMMed's MD300W1 .dat filer. Please remember: Vänligen kom ihåg: Important Notes: Viktig notering: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Contec CMS50D+ enheter har inte en intern klocka, och kan inte spela in en starttid. Om du inte har en CPAP session att länka en inspelning till, måste du ange starttiden manuellt efter att importen är klar. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Även för enheter med en intern klocka, är det fortfarande rekommenderat att vänja sig vid att starta oximeter-inspelning samtidigt som CPAP sessioner, eftersom CPAP:ens interna klocka tenderar att glida över tiden, och inte alla kan återställas enkelt. Oximetry Date Datum d/MM/yy h:mm:ss AP d/MM/yy h:mm:ss AP R&eset R&eset Pulse Puls &Open .spo/R File &Öppna .spo/R Fil Serial &Import Seriell &Import &Start Live &Start Live Serial Port Serieport &Rescan Ports &Rescan Port PreferencesDialog Preferences Inställningar &Import &Import Combine Close Sessions Slå samman perioder med kort avbrott mellan Minutes Minuter Multiple sessions closer together than this value will be kept on the same day. Flera sessioner närmare varandra än detta värde kommer att hållas samma dag. Ignore Short Sessions Ignorera korta perioder <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Perioder med kortare varaktighet än detta kommer inte att visas<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Tidpunkt när dagar delas Sessions starting before this time will go to the previous calendar day. Perioder som startar före den här tiden kommer att höra till föregående dag. Session Storage Options Period lagrings-option Create SD Card Backups during Import (Turn this off at your own peril!) Skapa backup på SD-kort under Import (Stäng av det här på egen risk!) Compress SD Card Backups (slower first import, but makes backups smaller) Komprimera SD-kortets säkerhetskopiering (långsammare första importen, men gör säkerhetskopior mindre) &CPAP &CPAP Show in Event Breakdown Piechart Visa i cirkeldiagrammet Resync Machine Detected Events (Experimental) Synka detekterade händelser (Experimentell) Do not import sessions older than: Importera inte sessioner äldre än: Sessions older than this date will not be imported Sessioner äldre än detta datum kommer inte att importeras dd MMMM yyyy dd MMMM yyyy Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Betrakta dagar med mindre användning än detta som "uppfyller inte villkoren". 4 timmar brukar anses som "Compliant". hours timmar Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Aktivera/inaktivera experimentella händelseflaggningar. Det gör att man hittar gränsfallshändelser och en del av de maskinen missat. Detta alternativ måste aktiveras innan import, annars behövs en rensning. Flow Restriction Flödesbegränsning Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Procent av begränsning i luftflödet från medianvärdet. Ett värde på 20% fungerar bra för att upptäcka apnéer. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Användardefinerade flaggor är en experimentell metod för att detektera händelser som missats av maskinen. De är <span style=" text-decoration: underline;">inte</span> inräknade i AHI.</p></body></html> Duration of airflow restriction Varaktighet luftflödesbegränsning s s Event Duration Varaktighet Händelse Allow duplicates near machine events. Tillåt dubbletter nära maskinhändelser. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. Justerar mängden data som behövs för varje punkt i AHI / Timme grafen. Standardvärdet är 60 minuter. Rekommenderas starkt att använda detta värde. minutes minuter Reset the counter to zero at beginning of each (time) window. Återställ räknaren till noll vid början av varje (tid) fönster. Zero Reset Återställ till noll Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Pixmap caching är en grafikaccelerationsteknik. Kan orsaka problem med typsnittsvisning i grafvisningsområdet på din plattform. <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Dessa funktioner har nyligen beskurits. De kommer att komma tillbaka senare. </p></body></html> CPAP Clock Drift CPAP klockdrift User definable threshold considered large leak Användardefinerat tröskelvärde avseende stort läckage Whether to show the leak redline in the leak graph Huruvida den röda linjen ska visas i läckage-grafen Search Sök &Oximetry &Oximeter Percentage drop in oxygen saturation Procentuell minskning i syremättnaden Pulse Puls Sudden change in Pulse Rate of at least this amount Plötslig förändring i puls på minst denna nivå bpm bpm Minimum duration of drop in oxygen saturation Minsta tid för nedgång i syremättnad Minimum duration of pulse change event. Minsta tid för pulsändringshändelse. Small chunks of oximetry data under this amount will be discarded. Små bitar av oximetridata under detta värde kommer att raderas. &General &Allmänt General Settings Allmänna inställningar Daily view navigation buttons will skip over days without data records Dagliga vy/navigeringsknapparna hoppar över dagar utan dataposter Skip over Empty Days Hoppa över tomma dagar Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Tillåt användning av flera processorkärnor där sådana finns för att förbättra prestanda. Används främst av importmodulen. Enable Multithreading Aktivera Multithreading Bypass the login screen and load the most recent User Profile Hoppa över inloggningsskärmen och ladda den senaste användarprofilen Changes to the following settings needs a restart, but not a recalc. Ändras följande inställningar behövs en omstart, men inte en omräkning. Preferred Calculation Methods Prioriterade beräkningsmetoder Middle Calculations Medelberäkningar Upper Percentile Övre procentil For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. För konsekvensens skull bör ResMed-användare använda 95% här, eftersom det är det enda värdet som finns på bara-översikts dagar. Median is recommended for ResMed users. Median rekomenderas för ResMed-användare. Median Median Weighted Average Vägt genomsnitt Normal Average Normalt genomsnitt True Maximum Sant maximal 99% Percentile 99% percentil Maximum Calcs Max. beräkningar Session Splitting Settings Inställningar sessions-delningar <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <Html><head/><body><p><span style="font-weight: 600;"> Den här inställningen bör användas med försiktighet...</span> stänga av kommer innebära konsekvenser som innebär problem med noggrannheten på sammanställningsdata som kommer från enskilda dagar. </P><p><span style="font-weight:600;">ResMed-användare:</span> Den STF.edf sammanfattande indexformatet har allvarliga brister som gör att detta inte en bra idé.</p><p>Det här alternativet finns för att lugna dem som inte bryr sig och vill se detta &quot;Fixerat&quot; oavsett konsekvenser, men vet att det kommer såna. Om du håller ditt SD-kort i maskinen varje natt, och importerar åtminstone en gång i veckan, kommer du inte att få problem med detta särskilt ofta.</P></body></html> Don't Split Summary Days (Warning: read the tooltip!) Dela inte sammanställningsdata (Varning, läs inforuta!) Memory and Startup Options Minnes- och startinställningar Pre-Load all summary data at startup Förladda alla sammanställningsdata vid programstart <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Denna inställning håller andningsflöde- och händelsedata i minnet efter användning för att snabba upp återkommande dagar.</p><p>Detta är inte en nödvändig inställning, eftersom ditt operativsystem också cachar tidigare använda filer.</p><p>Det är rekommenderat att lämna avslaget, ifall din dator inte har enormt mycket minne.</p></body></html> Keep Waveform/Event data in memory Håll flödesdata och händelser i minnet <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>Skär ner på alla oviktiga bekräftelsedialogrutor vid import.</p></body></html> Import without asking for confirmation Importera utan att fråga om godkännande General CPAP and Related Settings Allmänna CPAP- och relaterade inställningar Enable Unknown Events Channels Aktivera "okända händelser" AHI Apnea Hypopnea Index Apnea Hypopnea Index RDI Respiratory Disturbance Index Respiratory Disturbance Index AHI/Hour Graph Time Window AHI/timme graf Preferred major event index Större händelser Compliance defined as Compliance defineras som Flag leaks over threshold Flaggar läckor över tröskeln Seconds Sekunder <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <Html><head/><body><p>Obs! Detta är inte avsett för tidszons-korrigeringar! Se till att ditt operativsystems klocka och tidszon är rätt inställd.</P></body></html> Hours Timmar <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Sant maximum är den högsta i datamängden.</p><p>99 procenten filtrerar bort de mest sällsynta extremvärdena.</p></body></html> Combined Count divided by Total Hours Kombinerad räkning dividerad med totala timmar Time Weighted average of Indice Medelvärde över tiden på indicier Standard average of indice Genomsnitt på indicier Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Komprimera ResMed (EDF) säkerhetskopior för att spara diskutrymme. Säkerhetskopierade EDF-filer lagras i .gz format, vilket är vanligt på Mac & Linux-plattformar.. OSCAR kan importera från denna komprimerade backup katalog.. Om du vill använda med ResScan krävs det att .gz filerna är okomprimerade först.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Följande alternativ påverkar hur mycket diskutrymme OSCAR använder, och alla har en effekt på hur lång tid importen tar. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Detta gör att OSCAR's data tar ungefär hälften så mycket utrymme. Men det gör att import och dag-förändringar tar längre tid. Om du har en dator med liten disk, är det här ett bra alternativ. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Komprimera period-data (gör OSCAR's data mindre, men att byta dag långsammare.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Detta innebär backup av SD-kortdata för ResMed maskiner, ResMed's S9 maskiner raderar högupplösta data äldre än 7 dagar, och grafdata äldre än 30 dagar.. OSCAR kan behålla en kopia av dessa uppgifter om du behöver installera om. (Starkt rekommenderat, om du inte har ont om diskutrymme eller inte bryr dig om grafdata) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Gör starten av OSCAR lite långsammare genom att förladda alla sammanställningsdata vid uppstart. Men visningen av sammanställngningsdata och vissa andra krävande visningar går snabbare sen i gengäld.</p><p>Om du har stora mängder sömndata, kan det vara värt att ha detta avstängt. Fast om du normalt vill se<span style=" font-style:italic;">ALLT</span> i översikten, så måste alla data laddas ändå. </p><p>Notera att denna inställning påverkar inte graferna för andningsflöde och händelsedata, som alltid laddas vid behov</p></body></html> Custom CPAP User Event Flagging CPAP-användares anpassade händelseflaggor This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Detta experimentella alternativ använder OSCAR's flaggningssystem för att förbättra programmets funktion. Show Remove Card reminder notification on OSCAR shutdown Visa/Dölj SD-kortpåminnelsen när OSCAR avslutas Check for new version every Leta efter en ny version med days. dagars intervall. Last Checked For Updates: Senaste kontroll efter uppdateringar: TextLabel Textetikett I want to be notified of test versions. (Advanced users only please.) Jag vill bli aviserad om test-versioner. (Endast erfarna användare.) &Appearance &Utseende Graph Settings Grafinställningar <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Vilken flik vill du ska öppna när du laddar en profil. (Obs! Det kommer att automatiskt visas Profil om OSCAR är inställt på att inte öppna en profil vid start)</p></body></html> Bar Tops Översta linjen Line Chart Linjediagram Overview Linecharts Översikt linjediagram <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Detta gör rullning när du har zoomat in lättare i känsliga dubbelriktade pekplattor</p><p>50ms rekommenderas som värde.</p></body></html> Scroll Dampening Skrolldämpning Graph Tooltips Graf inforuta Overlay Flags Täckande flagga Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. Prova att ändra det här från standardinställningen (Desktop OpenGL) om du upplever problem med OSCAR´s grafer. Fonts (Application wide settings) Typsnitt (breda inställningar) The visual method of displaying waveform overlay flags. Den visuella metoden att visa vågformsflaggor. Standard Bars Standard linje Graph Height Grafhöjd Default display height of graphs in pixels Standard höjd för grafer i pixlar Events Händelser Reset &Defaults Återställ &Standardvärden <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Varning: </span>Bara för att du kan, betyder det inte att det är en bra idé.</p></body></html> Waveforms Vågform Flag rapid changes in oximetry stats Flagga snabba förändringar i syresättning Other oximetry options Andra Oximeter inställningar Discard segments under Släng delar under Flag Pulse Rate Above Flagga puls över Flag Pulse Rate Below Flagga puls under This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Den här beräkningen kräver att totalt läckage-data skapas av CPAP-maskinen. (T.ex. PRS1, men inte ResMed, som redan har detta) Beräkningen för oönskat läckage som görs här är linjär och används inte för maskläckage-kurvan. Om du använder några olika masker, plocka medelvärden istället. Det bör fortfarande vara tillräckligt nära. Calculate Unintentional Leaks When Not Present Beräknar oönskade läckor om det inte framgår 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. Obs: En linjär beräkningsmetod används. Ändras dessa värden krävs en omräkning. Show flags for machine detected events that haven't been identified yet. Visa flagga för händelser som inte blivit identifierade än. How long you want the tooltips to stay visible. Hur länge du vill att inforutor ska vara synliga. Tooltip Timeout Inforuta visningstid Top Markers Toppmarkering Line Thickness Linjetjocklek Changing SD Backup compression options doesn't automatically recompress backup data. Ändring av komprimeringsalternativ för SD Backup omkomprimerar inte automatiskt befintliga säkerhetskopior. Auto-Launch CPAP Importer after opening profile Starta CPAP-importen automatiskt efter att profilen är vald Automatically load last used profile on start-up Ladda senast använda profil vid start av programmet <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Ger en varning när du importerar data från en maskinmodell som ännu inte har testats av OSCAR-utvecklarna. </p></body></html> Warn when importing data from an untested machine Varna när du importerar data från en otestad maskin <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p> Ger en varning när du importerar data som på något sätt skiljer sig från allt som tidigare har setts av OSCAR-utvecklarna.</p></body></html> Warn when previously unseen data is encountered Varna när ovanliga data upptäcks Your masks vent rate at 20 cmH2O pressure Din masks ventilation vid 20 cmH2O-tryck Your masks vent rate at 4 cmH2O pressure Din masks ventilation vid 4 cmH2O-tryck <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>.</p>På grund av begränsningar i ResMed's konstruktion så stöds inte ändringar av dessa inställningar </body></html> Oximetry Settings Oximeter inställningar Always save screenshots in the OSCAR Data folder Spara alltid skärmdumpar i OSCARs datamapp Check For Updates Kontrollera om det finns uppdateringar You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Du använder en testversion av OSCAR. Testversionen söker efter uppdateringar minst var 7:e dag. Du kan sätta ett tätare intervall om du vill. Automatically check for updates Sök automatiskt efter uppdateringar How often OSCAR should check for updates. Hur ofta OSCAR söker efter uppdateringar. If you are interested in helping test new features and bugfixes early, click here. Om du är intresserad av att testa nya funktioner, hitta buggar m,m, klicka här. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Om du vill hjälpa till att testa kommande versioner av OSCAR, vänligen titta på Wiki sidorna om att testa OSCAR. Vi välkomnar alla som vill testa OSCAR, hjälpa till med att utveckla OSCAR, samt att översätta till nya eller befintliga språk. https://www.sleepfiles.com/OSCAR On Opening Vid start Profile Profil Welcome Välkommen Daily Daglig Statistics Statistik Switch Tabs Välj Flik No change Ingen ändring After Import Efter import The pixel thickness of line plots Pixeltjocklek i linjediagram Other Visual Settings Andra visuella inställningar Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Anti-Aliasing utjämnar grafer och utskrifter.. Vissa grafer ser mer attraktiva ut med detta på. Detta påverkar även utskrivna rapporter. Prova och se om du gillar det. Use Anti-Aliasing Använd Anti-Aliasing Makes certain plots look more "square waved". Gör vissa grafer mer som "fyrkantsvågor". Square Wave Plots Fyrkantsvågs-visning Use Pixmap Caching Använd Pixmap Caching Animations && Fancy Stuff Animationer && andra roliga saker Whether to allow changing yAxis scales by double clicking on yAxis labels Om du vill tillåta att ändra y-axelns skala genom att dubbelklicka på y-axelns etikett Allow YAxis Scaling Tillåt skalning av y-axeln Whether to include machine serial number on machine settings changes report Huruvida man ska inkludera maskinens serienummer i rapporten om ändringar av maskininställningar Include Serial Number Inkludera Serienummer Graphics Engine (Requires Restart) Grafikmotor (Kräver omstart) l/min l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Sammanräknat Index</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <html><head/><body><p>Flagga SpO<span style=" vertical-align:sub;">2</span> Desaturation under</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncar Oximeter och CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data importerad från SpO2Review (från .spoR fil) eller från seriell import har </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">inte</span><span style=" font-family:'Sans'; font-size:10pt;"> rätt tidsstämpel som behövs för att synkronisera.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view-läge (med en seriell kabel) är ett sätt att uppnå en exakt synkronisering på CMS 50-oximetrar, men räknar inte för CPAP-klockdrift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Om du startar din Oximeters inspelningsläge kl </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exact </span><span style=" font-family:'Sans'; font-size:10pt;">samtidigt som du startar din CPAP-maskin, kan du nu också uppnå synkronisering.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Den seriella importprocessen tar starttiden från gårdagens första CPAP-session. (Kom ihåg att importera dina CPAP-data först!)</span></p></body></html> Print reports in black and white, which can be more legible on non-color printers Skriver rapporter i svartvitt, vilket kan bli mer läsbart på svartvita skrivare Print reports in black and white (monochrome) Skriver rapporter i svartvitt (monochrome) Font Teckensnitt Size Storlek Bold Markerad Italic Kursiv Application Applikation Graph Text Graftext Graph Titles Grafrubrik Big Text Stor text Details Detaljer &Cancel &Avbryt &Ok &Ok Flag Flagga Minor Flag Mindre flagga Span Spännvidd Always Minor Alltid mindre No CPAP machines detected Ingen CPAP maskin hittades Will you be using a ResMed brand machine? Kommer du att använda en ResMed maskin? Never Aldrig Name Namn Color Färg Flag Type Flagg-typ Label Etikett CPAP Events CPAP-händelser Oximeter Events Oximeter-händelser Positional Events Positionerings-händelser Sleep Stage Events Sömnstegs-händelser Unknown Events Okända händelser Double click to change the descriptive name this channel. Dubbelklicka för att ändra det beskrivande namnet på denna kanal. Double click to change the default color for this channel plot/flag/data. Dubbelklicka för att ändra standardfärgen för denna kanals diagram/flagga/data. <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Observera:</b>OSCARs avancerade data-uppdelningsfunktioner är inte möjliga med <b>ResMed</b>-maskiner på grund av en begränsning av hur deras inställningar och sammanfattningsdata lagras och de har därför blivit inaktiverade</p><p>På ResMed-maskiner kommer dagar <b>att delas vid middagstid dvs, kl.12.00</b>, precis som i ResMeds kommersiella programvara.</p> %1 %2 %1 %2 Overview Översikt Double click to change the descriptive name the '%1' channel. Dubbelklicka för att ändra det beskrivande namnet på '%1' kanalen. Whether this flag has a dedicated overview chart. Huruvida denna flagga har ett dedikerat översiktsdiagram. Here you can change the type of flag shown for this event Här kan du ändra typen av flagga som visas för denna händelse This is the short-form label to indicate this channel on screen. Detta är etiketten i kortform för att indikera den här kanalen på skärmen. This is a description of what this channel does. Det här är en beskrivning av vad den här kanalen gör. Lower Lägre Upper Övre CPAP Waveforms CPAP-vågform Oximeter Waveforms Oximeter-vågform Positional Waveforms Positionerings-vågform Sleep Stage Waveforms Sömnstegs-vågform Whether a breakdown of this waveform displays in overview. Om en uppdelning av denna vågform visas i översikten. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Här kan du ställa in <b>lägre</b> tröskel för vissa beräkningar på %1 vågformen Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Här kan du ställa in <b>övre</b> tröskel för vissa beräkningar på %1 vågformen Data Processing Required Databehandling krävs A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? En dataåterställning/dekomprimeringsprocedur krävs för att tillämpa dessa ändringar. Den här åtgärden kan ta några minuter att slutföra. Är du säker på att du vill göra dessa ändringar? Data Reindex Required Data indexering krävs A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? En data omindexerings-procedur krävs för att tillämpa dessa ändringar. Denna operation kan ta ett par minuter att slutföra. Är du säker på att du vill göra dessa förändringar? Restart Required Omstart krävs One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? En eller flera av de ändringar du har gjort kommer att kräva att det här programmet startas om, för att dessa ändringar skall träda i kraft. Vill du göra det nu? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Om du någonsin behöver importera dessa data igen (antingen i OSCAR eller ResScan) dessa data kommer inte att komma tillbaka. If you need to conserve disk space, please remember to carry out manual backups. Om du behöver för att spara diskutrymme, kom ihåg att utföra manuell säkerhetskopiering. Are you sure you want to disable these backups? Är du säker att du vill inaktivera dessa säkerhetskopior? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Att stänga av säkerhetskopior är inte en bra idé, eftersom OSCAR behöver dessa för att bygga om databasen om den hittar fel. This may not be a good idea Det här kanske inte är en bra idè ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9 maskiner tar rutinmässigt bort vissa data från ditt SD-kort äldre än 7 och 30 dagar (beroende på upplösning). Are you really sure you want to do this? Är du verkligen säker på att du vill göra detta? ProfileSelector Filter: Filter: Reset filter to see all profiles Återställ filtret för att se alla profiler Version Version &Open Profile &Öppna profile &Edit Profile &Redigera profil &New Profile &Ny Profil Profile: None Profil: Ingen Please select or create a profile... Välj eller skapa en profil... Destroy Profile Radera profil Profile Profil Ventilator Brand Maskin Ventilator Model Modell Other Data Övriga data Last Imported Senast importerad Name Namn You must create a profile Du måste skapa en profil Enter Password for %1 Skriv lösenord för %1 You entered an incorrect password Du skrev ett felaktigt lösenord Forgot your password? Glömt ditt lösenord? Ask on the forums how to reset it, it's actually pretty easy. Fråga i forum på nätet hur man återställer den, det är faktiskt ganska lätt. Select a profile first Välj en profil först The selected profile does not appear to contain any data and cannot be removed by OSCAR Den valda profilen verkar inte innehålla några data och kan inte tas bort av OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Om du försöker radera på grund av att du glömt ditt lösenord, så måste du antingen skapa en ny eller radera profilens katalog manuellt. You are about to destroy profile '<b>%1</b>'. Du håller på att radera profilen '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Tänk noga, eftersom det här oundvikligen kommer att radera profilen tillsammans med alla <b>säkerhetskopieringsdata</b>som lagras under <br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Skriv ordet<b>RADERA<b> nedan exakt som det visas för att bekräfta. DELETE RADERA Sorry Ledsen You need to enter DELETE in capital letters. Du måste skriva ordet RADERA med stora bokstäver. There was an error deleting the profile directory, you need to manually remove it. Det uppstod ett fel när du raderade katalogen, du måste ta bort den manuellt. Profile '%1' was succesfully deleted Profilen '%1' raderades fullständigt Bytes Bytes KB KB MB MB GB GB TB TB PB PB Summaries: Sammanfattning: Events: Händelser: Backups: Säkerhetskopior: Hide disk usage information Dölj disk-användnings information Show disk usage information Visa disk-användningsinformation Name: %1, %2 Namn: %1, %2 Phone: %1 Telefon: %1 Email: <a href='mailto:%1'>%1</a> Epost: <a href='mailto:%1'>%1</a> Address: Adress: No profile information given Ingen profil-information har angetts Profile: %1 Profil: %1 ProgressDialog Abort Ta bort QObject No Data Ingen data On Off Av ft ft lb lb oz oz Kg kg cmH2O cmH2O Med. Med. Min: %1 Min: %1 Min: Min: Max: Max: Max: %1 Max: %1 %1 (%2 days): %1 (%2 dagar): %1 (%2 day): %1 (%2 dag): % in %1 % i %1 Hours Timmar Min %1 Min %1 Hours: %1 Timmar: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %1 låg användning, %2 ingen användning, av %3 dagars (%4% compliance.) Längd: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Sessioner: %1 / %2 / %3 Längd: %4 / %5 / %6 Längsta: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Längd: %3 Start: %2 Mask On Mask på Mask Off Mask av %1 Length: %3 Start: %2 %1 Längd: %3 Start: %2 TTIA: TTIA: TTIA: %1 TTIA: %1 Minutes Minuter Seconds sekunder Events/hr Händelser/timme Hz Hz bpm bpm Error Fel Warning Varning BMI BMI Weight Vikt Zombie Zombie Pulse Rate Puls Plethy Volym-förändring Pressure Tryck Daily Daglig vy Overview Översikt Oximetry Oximetri Oximeter Oximeter Event Flags Händelseflagga CPAP CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP IPAP IPAP L/min l/minut in i Litres liter ml ml Breaths/min Andetag/minut ratio förhållande Degrees Grader Question Fråga Information Information Busy Upptagen Please Note Notera Graphs Switched Off Graf avstängd Sessions Switched Off Sessioner avstängda &Yes &Ja &No &Nej &Cancel &Avbryt &Destroy &Förstöra &Save &Spara Profile Profil Default Förvalt Min EPAP Min EPAP Max EPAP Max EPAP Min IPAP Min IPAP Max IPAP Max IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Befuktare H H OA OA A A CA CA FL FL SA SA LE LE EP EP VS VS VS2 VS2 RERA RERA PP PP P P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF1 UF2 UF2 UF3 UF3 PS PS AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI EPI PB PB IE IE Insp. Time Inandningstid Exp. Time Utandningstid Resp. Event Trigger Flow Limitation Flödesbegränsning Flow Limit Flödesgräns SensAwake SensAwake Pat. Trig. Breath Patientutl. andetag Tgt. Min. Vent Mål min. vent Target Vent. Målventilation. Minute Vent. Minutvent. Tidal Volume Tidalvolym Resp. Rate Andningsfrekvens Snore Snarkning Leak Läcka Leaks Läckage Large Leak Stor läcka LL LL Total Leaks Totalt läckage Unintentional Leaks Oavsiktlig Läcka MaskPressure Masktryck Flow Rate Andningsflöde Sleep Stage Sömnstadie Usage Compliance Sessions Sessioner Pr. Relief Trycklättnad No Data Available Ingen data tillgänglig App key: Lösenord: Operating system: Operativsystem: Built with Qt %1 on %2 Byggt med Qt%1 på %2 Graphics Engine: Grafikmotor: Graphics Engine type: Grafik Motor: Software Engine Mjukvarumotor ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Desktop OpenGL m m cm cm h h m m s s ms ms ? ? Severity (0-1) Svårighetsgrad (0-1) Only Settings and Compliance Data Available Endast inställningar och compliance data tillgängligt Summary Data Only Endast sammanfattningsdata Bookmarks Bokmärken Mode Läge Model Modell Brand Fabrikat Serial Serienummer Series Serie Machine Maskin Channel Kanal Settings Inställningar Inclination Dragning Orientation Inriktning Motion Rörelse Name Namn DOB Födelsedatum Phone Telefon Address Adress Email E-post Patient ID Patient ID Date Datum Bedtime Sängdags Wake-up Vakna Mask Time Mask på Unknown Okänd None Ingen trycklindring Ready Färdig First Först Last Sist Start Start End Sluta Yes Ja No Nej Min Min Max Max Med Medium Average Genomsnitt Median Median Avg Genomsnitt W-Avg Viktat genomsnitt Your %1 %2 (%3) generated data that OSCAR has never seen before. Din %1 %2 (%3) genererade data som OSCAR aldrig sett tidigare. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Den importerade datan kanske inte är helt korrekt, därför vill gärna utvecklarna få tillgång till en kopia av den här maskinens SD-kort och PDF-rapport från matchande kliniskt program för att säkerställa att OSCAR hanterar datan korrekt. Non Data Capable Machine EJ data-kapabel maskin Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Din %1 CPAP maskin (Modell %2) är tyvärr inte kapabel till att spara data. Your %1 CPAP machine (Model %2) has not been tested yet. Din %1 CPAP maskin (Modell %2) har inte blivit testad än. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Det ser tillräckligt lika ut som andra maskiner så det kan fungera, men utvecklarna vill ändå få tillgång till en kopia av den här maskinens SD-kort och PDF-rapport från matchande kliniskt program för att säkerställa att OSCAR hanterar datan korrekt. Sorry, your %1 CPAP machine (%2) is not supported yet. Ledsen, men din %1 CPAP maskin (%2) stöds inte än. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Utvecklarna behöver en kopia av den här maskinens SD-kort och PDF-rapport från matchande kliniskt program för att säkerställa att OSCAR hanterar datan korrekt. Getting Ready... Görs i ordning... Machine Unsupported Maskinen stöds inte I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Jag är ledsen att rapportera att OSCAR endast kan spåra timmar av användning och endast grundläggande inställningar för den här maskinen. Scanning Files... Skannar filer... Importing Sessions... Importerar inspelningar ... Finishing up... Avslutar... Untested Data Otestade data Machine Untested Denna maskin är otestad Flex Lock Lås Flex Whether Flex settings are available to you. Om FLEX inställningen är tillgänlig för dig. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Den tid det tar att gå från EPAP till IPAP, ju högre siffra ju långsammare övergång Rise Time Lock Lås Stigtid Whether Rise Time settings are available to you. Om Stigtids inställning är tillgängligt för dig. Rise Lock Lås höjning Mask Resistance Setting Motståndsinställning mask Mask Resist. Maskmotstånd. Hose Diam. Slangdiameter. 15mm 15mm 22mm 22mm Backing Up Files... Säkerhetskopierar filer... model %1 model %1 DreamStation 2 DreamStation 2 unknown model okänd modell Pressure Pulse Tryckpuls A pulse of pressure 'pinged' to detect a closed airway. En puls av lufttryck ivägskickad för att upptäcka en stängd luftväg. CPAP-Check CPAP-Check AutoCPAP AutoCPAP Auto-Trial Auto-Trial AutoBiLevel AutoBiLevel S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Mode Flex-läge PRS1 pressure relief mode. PRS1 trycklindringsläge. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time Stigtid Bi-Flex Bi-Flex Flex Flex Flex Level Flex-nivå PRS1 pressure relief setting. PRS1 trycklindrings-inställning. Passover Utan uppvärmning Target Time Målvärde PRS1 Humidifier Target Time PRS1 Befuktare målvärde Hum. Tgt Time Befuktare Målvärde Tubing Type Lock Lås Slangtyp Whether tubing type settings are available to you. Om slangtypsinställning är tillgänglig för dig. Tube Lock Lås Slang Mask Resistance Lock Lås Maskmotstånd Whether mask resistance settings are available to you. Om Maskmotstånds inställning är tillgänglig för dig. Mask Res. Lock Lås Maskmotstånd Whether or not machine shows AHI via built-in display. Om maskinen visar AHI på displayen eller inte. Ramp Type Rampinställning Type of ramp curve to use. Välj Rampinställning. Linear Linjär SmartRamp SmartRamp Ramp+ Ramp+ Backup Breath Mode Inställning andningsfrekvens The kind of backup breath rate in use: none (off), automatic, or fixed Den typ av andningsfrekvens som är inställd: Ingen (Av), Automatisk eller Fast Breath Rate Andningsfrekvens Fixed Fast Fixed Backup Breath BPM Fast Andningsfrekvens BPM (Andetag per mnut) Minimum breaths per minute (BPM) below which a timed breath will be initiated Under det inställda antalet andetag per minut (BPM) så initierar maskinen själv andetag Breath BPM Andetag BPM Timed Inspiration Tid för inandning The time that a timed breath will provide IPAP before transitioning to EPAP Den tid ett inställt värde för inandning IPAP förflyter innan den växlar till utandning EPAP Timed Insp. Tidsstyrd inandning. Auto-Trial Duration Auto-Trial period The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Antalet dagar som maskinen är i Auto-Trial innan den återgår till CPAP Auto-Trial Dur. Auto-Trial period. EZ-Start EZ-Start Whether or not EZ-Start is enabled Om EZ-Start är på eller inte Variable Breathing Periodisk Andning UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend EJ VERIFIERAT: Eventuellt Periodisk Andning, som är perioder med hög avvikelse från den normala flödeskurvan Peak Flow Högsta flöde Peak flow during a 2-minute interval Högsta flöde under en 2 minuters period Humidifier Status Befuktningsstatus PRS1 humidifier connected? PRS1 befuktare inkopplad? Disconnected Bortkopplad Connected Ansluten Humidification Mode Befuktningsläge PRS1 Humidification Mode PRS1 Befuktningsläge Humid. Mode Fuktighetsläge Fixed (Classic) Fast (Klassiskt) Adaptive (System One) Adaptive (System One) Heated Tube Uppvärmd slang Tube Temperature Slangtemperatur PRS1 Heated Tube Temperature PRS1 Slangtemperatur Tube Temp. Slangtemperatur. PRS1 Humidifier Setting PRS1 Befuktningsinställning Hose Diameter Slang Diameter Diameter of primary CPAP hose Diameter på primär CPAP slang 12mm 12mm Auto On Auto på A few breaths automatically starts machine Några få andetag startar maskinen automatiskt Auto Off Auto Av Machine automatically switches off Maskinen stängs av automatiskt Mask Alert Mask Varning Whether or not machine allows Mask checking. Huruvida maskinen har mask-kontroll eller inte. Show AHI Visa AHI Breathing Not Detected Andning ej detekterad A period during a session where the machine could not detect flow. En period under en session då maskinen inte kunde detektera flöde. BND BND Timed Breath Tidsinställd andning Machine Initiated Breath Maskin-initierade andetag TB TB Windows User Windows-användare Using Använder , found SleepyHead - , hittade OSCAR - You must run the OSCAR Migration Tool Du måste köra OSCAR Migrations-programmet Launching Windows Explorer failed Starta Utforskaren misslyckades Could not find explorer.exe in path to launch Windows Explorer. Det gick inte att hitta explorer.exe i datorn för att starta Utforskaren. <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Dina gamla maskindata bör regenereras förutsatt att denna backup funktion inte har inaktiverats i inställningarna under en tidigare dataimport.</i> <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR har en säkerhetskopia av dina enheters minneskort som den använder för detta ändamål.</b> OSCAR does not yet have any automatic card backups stored for this device. OSCAR har ännu inte några automatiska kortsäkerhetskopior som sparats för denna enhet. This means you will need to import this machine data again afterwards from your own backups or data card. Detta innebär att du kommer att behöva importera denna maskindata igen efteråt från dina egna säkerhetskopior eller datakort. Important: Viktigt: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Om du är orolig, klicka på Nej för att avsluta, och säkerhetskopiera din profil manuellt innan du startar OSCAR igen. Are you ready to upgrade, so you can run the new version of OSCAR? Är du redo att uppgradera, så du kan använda den nya versionen av OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Tyvärr, rensningen misslyckades, vilket innebär att den här versionen av OSCAR inte kan starta. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Vill du slå på automatisk säkerhetskopiering, så nästa gång en ny version av OSCAR behöver göra det, kan återskapa från dessa? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR startar nu import-guiden så du kan återinstallera dina %1 data. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: OSCAR kommer nu att avslutas, starta sen om din filhanterare så att du kan säkerhetskopiera din profil manuellt: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Använd filhanteraren för att göra en kopia av din profilmapp, sedan det är klart, starta om OSCAR och slutför uppgraderingen. Machine Database Changes Maskindatabas Förändringar OSCAR %1 needs to upgrade its database for %2 %3 %4 OSCAR %1 måste uppgradera sin databas för %2 %3 %4 Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. När du har uppgraderat <font size=+1>kan du inte</font> använda den här profilen med tidigare version mer. The machine data folder needs to be removed manually. Maskinens data-katalog måste raderas manuellt. This folder currently resides at the following location: Denna mapp är för närvarande på följande plats: Rebuilding from %1 Backup Återskapar från %1 säkerhetskopia Exiting Spännande or CANCEL to skip migration. eller AVBRYT för att ångra åtgärden. You cannot use this folder: Du kan inte använda den här mappen: Migrating Flyttar files filer from från to till OSCAR crashed due to an incompatibility with your graphics hardware. OSCAR krashade till följd av inkompabilitet med din grafikhårdvara. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. För att lösa detta har OSCAR återgått till en långsammare men mer kompatibel visning. OSCAR will set up a folder for your data. OSCAR skapar en ny mapp för dina data. We suggest you use this folder: Vi föreslår att du använder denna mapp: Click Ok to accept this, or No if you want to use a different folder. Klicka OK för att acceptera det här, eller NEJ om du vill använda en annan mapp. Next time you run OSCAR, you will be asked again. Nästa gång du startar OSCAR, blir du tillfrågad igen. Choose or create a new folder for OSCAR data Välj eller skapa en ny mapp för OSCAR:s data Choose the SleepyHead or OSCAR data folder to migrate Välj datamappen för SleepyHead eller OSCAR som ska flyttas The folder you chose does not contain valid SleepyHead or OSCAR data. Den mapp du valt innehåller ingen giltig data för SleepyHead eller OSCAR. If you have been using SleepyHead or an older version of OSCAR, Om du har använt SleepyHead eller en äldre version av OSCAR, OSCAR can copy your old data to this folder later. OSCAR kan kopiera dina gamla data till den här mappen senare. As you did not select a data folder, OSCAR will exit. Eftersom du inte valt en data-mapp så avslutas OSCAR. The folder you chose is not empty, nor does it already contain valid OSCAR data. Mappen du väljer är inte tom, och inte innehåller den giltiga OSCAR data heller. Data directory: Datakatalog: Are you sure you want to use this folder? Är du säker på att du vill använda den här mappen? Migrate SleepyHead or OSCAR Data? Flytta SleepyHead:s eller OSCAR:s data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data I nästa steg kommer OSCAR att be dig välja en mapp med data från SleepyHead eller OSCAR Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Klicka [Ja] för att fortsätta eller [Nej] om du inte vill använda SleepyHead eller OSCAR:s data. Unable to create the OSCAR data folder at Kan inte skapa en datamapp för OSCAR på Unable to write to OSCAR data directory Kan inte skriva till OSCAR:s datamapp Error code Felkod OSCAR cannot continue and is exiting. OSCAR kan inte fortsätta och avslutas. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Det gick inte att skriva till felsökningsloggen. Du kan fortfarande använda felsökningsfönstret (Hjälp/Felsökning/Visa felsökningsfönster) men felsökningsloggen kommer inte att skrivas till disken. Version "%1" is invalid, cannot continue! Version "%1" är ogiltig, kan inte fortsätta! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Den version av OSCAR som du kör (%1) är äldre än den som användes för att skapa dessa data (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? Det är troligt att om du gör detta så kommer det att leda till att data blir korrupt, är du säker på att du vill göra detta? Don't forget to place your datacard back in your CPAP machine Kom ihåg att sätta tillbaka ditt minneskort i CPAP:en OSCAR Reminder OSCAR Påminnelse You can only work with one instance of an individual OSCAR profile at a time. Du kan bara arbeta med en instans av en enskild OSCAR profil åt gången. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Om du använder molnlagring, se till OSCAR är stängd och synkronisering har slutförts först på den andra datorn innan du fortsätter. Loading profile "%1"... Laddar profile "%1"... Chromebook file system detected, but no removable device found Chromebook filsystem upptäcktes, men ingen flyttbar enhet hittades You must share your SD card with Linux using the ChromeOS Files program Du måste dela ditt Linux SD-kort med ChromeOS Fil-hanterare Recompressing Session Files Återkomprimera sessionsfiler Please select a location for your zip other than the data card itself! Välj en annan plats för din zip-kopia än själva datakortet! Unable to create zip! Kan inte skapa en zip-kopia! Are you sure you want to reset all your channel colors and settings to defaults? Är du säker på att du vill återställa alla dina kanalfärger och inställningar till standard? Are you sure you want to reset all your waveform channel colors and settings to defaults? Är du säker på att du vill återställa alla vågforms färger och inställningar till standard? There are no graphs visible to print Det finns inga grafer synliga för utskrift Would you like to show bookmarked areas in this report? Vill du visa bokmärkta områden i denna rapport? Printing %1 Report Skriver %1 rapport %1 Report %1 Rapport : %1 hours, %2 minutes, %3 seconds : %1 timme, %2 minuter, %3 sekunder RDI %1 RDI %1 AHI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 UAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 AI=%1 AI=%1 Reporting from %1 to %2 Rapporterar från %1 till %2 Entire Day's Flow Waveform Dagens alla flödesdata Current Selection Aktuell markering Entire Day Hela dagen Page %1 of %2 Sida %1 av %2 Events Händelser Duration Varaktighet (% %1 in events) (% %1 i händelser) Days: %1 Dagar: %1 Low Usage Days: %1 Dagar med låg compliance: %1 (%1% compliant, defined as > %2 hours) (%1% compliant, definerad som > %2 timmar) (Sess: %1) (Sess: %1) Bedtime: %1 Sängdags: %1 Waketime: %1 Uppvakningstid: %1 (Summary Only) (Enbart sammanställning) Jan Jan Feb Feb Mar Mar Apr Apr May Maj Jun Jun Jul Jul Aug Aug Sep Sep Oct Okt Nov Nov Dec Dec Therapy Pressure Terapitryck Inspiratory Pressure Inandningstryck Lower Inspiratory Pressure Lägre inandningstryck Higher Inspiratory Pressure Högre inandningstryck Expiratory Pressure Utandningstryck Lower Expiratory Pressure Lägre utandningstryck Higher Expiratory Pressure Högre utandningstryck Pressure Support Tryckstöd PS Min PS min Pressure Support Minimum Tryckstöd minimum PS Max PS max Pressure Support Maximum Tryckstöd maximum Min Pressure Min Tryck Minimum Therapy Pressure Minimum terapitryck Max Pressure Max tryck Maximum Therapy Pressure Maximum terapitryck Ramp Time Ramptid Ramp Delay Period Ramp fördröjningsperiod Ramp Pressure Ramptryck Starting Ramp Pressure Ramp starttryck Pressure Min Min. tryck Pressure Max Max. tryck Ramp Event Ramp händelser Ramp Ramp An abnormal period of Cheyne Stokes Respiration En onormal period av Cheyne-Stokes andning An apnea where the airway is open Ett andningsuppehåll där luftvägarna är öppna An apnea caused by airway obstruction Ett andningsuppehåll där luftvägarna är blockerade Hypopnea Hypopné A partially obstructed airway En delvis blockerad luftväg Unclassified Apnea Ospecifierat andningsuppehåll UA UA Cheyne Stokes Respiration Cheyne Stokes Andning CSR CSR Periodic Breathing Periodisk andning An abnormal period of Periodic Breathing En onormal period av periodisk andning Clear Airway Central Apne Obstructive Obstruktiv Apne Apnea Apne An apnea reportred by your CPAP machine. En apne rapporterad av din CPAP maskin. Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Andningsrelaterat uppvaknande: En begränsning av andningen som orsakar antingen ett uppvaknande eller en sömnstörning. Vibratory Snore Snarkning A vibratory snore En snarkning Vibratory Snore (VS2) Snarkning (VS2) A vibratory snore as detcted by a System One machine En snarkning som registreras av Philips System One maskin Leak Flag Läckage-flagga A large mask leak affecting machine performance. En stor mask läcka som påverkar maskinens prestanda. LF LF Non Responding Event En händelse som inte reageras på A type of respiratory event that won't respond to a pressure increase. En typ av andnings händelse som inte kommer att ge någon tryckökning. Expiratory Puff Utandningspuff Intellipap event where you breathe out your mouth. Intellipap händelse där du andas ut genom munnen. SensAwake feature will reduce pressure when waking is detected. SensAwake funktion minskar trycket när uppvaknande upptäcks. User Flag #1 Användarflagga #1 User Flag #2 Användarflagga #2 User Flag #3 Användarflagga #3 Heart rate in beats per minute Puls i slag per minut Blood-oxygen saturation percentage Blod-syremättnadsprocent Plethysomogram Plethysomogram An optical Photo-plethysomogram showing heart rhythm Ett optiskt foto-plethysomogram visande hjärtrytmen Perfusion Index Pulsstyrke-index A relative assessment of the pulse strength at the monitoring site En relativ bedömning av pulsstyrkan på mätstället Perf. Index % Pulsstyrke-index % Pulse Change Pulsförändring A sudden (user definable) change in heart rate En plötslig (användardefinierad) förändring av hjärtfrekvensen SpO2 Drop SpO2 nedgång A sudden (user definable) drop in blood oxygen saturation En plötslig (användardefinierad) nedgång i blodets syremättnad SD SD Breathing flow rate waveform Andningsflöde Mask Pressure Masktryck Amount of air displaced per breath Mängden luft visad per andetag Graph displaying snore volume Graf som visar omfattningen av snarkning Minute Ventilation Minutventilation Amount of air displaced per minute Mängden luft visad per minut Respiratory Rate Andningsfrekvens Rate of breaths per minute Andningsfrekvens per minut Patient Triggered Breaths Patientutlösta andetag Percentage of breaths triggered by patient Procentandel av andetag utlösta av patienten Pat. Trig. Breaths Patientutl. andetag Leak Rate Läckage Rate of detected mask leakage Storlek på upptäckta mask-läckage I:E Ratio I:E förhållande Ratio between Inspiratory and Expiratory time Förhållande mellan inandningstid och utandningstid Expiratory Time Utandningstid Time taken to breathe out Tid för att andas ut Inspiratory Time Inandningstid Time taken to breathe in Tid för att andas in Respiratory Event Andnings händelse Graph showing severity of flow limitations Graf som visar svårighetsgraden av flödesbegränsningar Flow Limit. Flödesbegr. Target Minute Ventilation Mål minutventilation An apnea that couldn't be determined as Central or Obstructive. En apnea som inte kunde kännas igen som Central eller Obstruktiv. Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Det gick inte att analysera Channels.xml, OSCAR kan inte fortsätta och avslutas. Pressure Set Tryckinställning Pressure Setting Tryckinställning IPAP Set IPAP Ställ in IPAP Setting IPAP Inställning EPAP Set EPAP Ställ in EPAP Setting EPAP Inställning A restriction in breathing from normal, causing a flattening of the flow waveform. En begränsning i andningen mot det normala Orsakar en plattare form på andningskurvan. A user definable event detected by OSCAR's flow waveform processor. En användardefinierad händelse som upptäcks av OSCAR's luftflödes processor. Mask Pressure (High frequency) Masktryck (hög frekvens) A ResMed data item: Trigger Cycle Event En ResMed-datapost: Trigger Cycle Event Maximum Leak Maximum läckage The maximum rate of mask leakage Största uppmätta mask-läckaget Max Leaks Max läcka Apnea Hypopnea Index Apnea Hypopnea Index Graph showing running AHI for the past hour Graf som visar rullande AHI den senaste timmen Total Leak Rate Totalt läckage Detected mask leakage including natural Mask leakages Upptäckta mask läckage inkluderande naturligt Mask läckage Median Leak Rate Medianläckage Median rate of detected mask leakage Median upptäckta mask läckage Median Leaks Medianläckage Respiratory Disturbance Index Andningsstörningsindex Graph showing running RDI for the past hour Graf som visar rullande RDI den senaste timmen Sleep position in degrees Sovposition i grader Upright angle in degrees Upprätt vinkel i grader Movement Rörelse Movement detector Rörelsesdetektor Mask On Time Tid för mask på Time started according to str.edf Tiden började enligt str.edf Summary Only Sammanställning enbart CPAP Session contains summary data only CPAP perioden innehåller enbart sammanfattningsdata PAP Mode Behandlingsläge PAP Device Mode Maskinens behandlingsläge APAP (Variable) APAP (Autojusterar) ASV (Fixed EPAP) ASV (Fast EPAP) ASV (Variable EPAP) ASV (Auto EPAP) Height Längd Physical Height Kroppslängd Notes Anteckningar Bookmark Notes Bokmärkeskommentarer Body Mass Index Kroppsmasseindex How you feel (0 = like crap, 10 = unstoppable) Hur mår du (0 = uselt, 10 = fantastiskt) Bookmark Start Bokmärke start Bookmark End Bokmärke slut Last Updated Senast uppdaterad Journal Notes Journalanteckningar Journal Journal 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=Vaken 2=REM 3=Lätt sömn 4=Djupsömn Brain Wave Hjärnvågor BrainWave Hjärnvågor Awakenings Uppvakningar Number of Awakenings Antal uppvakningar Morning Feel Morgonkänsla How you felt in the morning Hur du kände dig på morgonen Time Awake Vakentid Time spent awake Tid i vaket tillstånd Time In REM Sleep Tid i REM-sömn Time spent in REM Sleep Tid i REM-sömn Time in REM Sleep Tid i REM-sömn Time In Light Sleep Tid i lätt sömn Time spent in light sleep Tid i lätt sömn Time in Light Sleep Tid i lätt sömn Time In Deep Sleep Tid i djupsömn Time spent in deep sleep Tid i djupsömn Time in Deep Sleep Tid i djupsömn Time to Sleep Insomningstid Time taken to get to sleep Tid det tar att somna in Zeo ZQ Zeo ZQ Zeo sleep quality measurement Zeo sömnkvalitetsmätning ZEO ZQ ZEO ZQ Debugging channel #1 Felsökningskanal #1 Test #1 Test #1 For internal use only Endast för internt bruk Debugging channel #2 Felsökningskanal #2 Test #2 Test #2 Zero Noll Upper Threshold Övre tröskel Lower Threshold Nedre tröskel There is a lockfile already present for this profile '%1', claimed on '%2'. Det finns en låsningsfil redan för den här profilen '%1', hävdade på '%2'. Database Outdated Please Rebuild CPAP Data Databasen är för gammal Vänligen återskapa CPAP-data (%2 min, %3 sec) (%2 min, %3 sek) (%3 sec) (%3 sek) Pop out Graph Koppla loss graf The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Popup-fönstret är fullt. Du bör markera och radera det, sen öppna det igen. Your machine doesn't record data to graph in Daily View Din maskin sparar inte data till graferna i Daglig vy There is no data to graph Det finns inga data i graferna d MMM yyyy [ %1 - %2 ] d MMM åååå [ %1 - %2 ] Hide All Events Dölj alla händelser Show All Events Visa alla händelser Unpin %1 Graph Unpin %1 graf Popout %1 Graph Koppla loss %1 grafen Pin %1 Graph Pin %1 graf Plots Disabled Diagram ej aktiverat Duration %1:%2:%3 Varaktighet %1:%2:%3 AHI %1 AHI %1 Relief: %1 Lindring: %1 Hours: %1h, %2m, %3s Timmar: %1t, %2m, %3s Machine Information Maskininformation Fixed Bi-Level Fast Bi-Level Auto Bi-Level (Fixed PS) Auto Bi-Level (Fast PS) Auto Bi-Level (Variable PS) Auto Bi-Level (Variabel PS) 99.5% 99.5% varies varierar n/a Ej tillämplig Fixed %1 (%2) Fast %1 (%2) Min %1 Max %2 (%3) Min %1 Max %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %1 över %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1-%2 IPAP %3-%4 (%5) Journal Data Journaldata OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR hittade en gammal journalmapp, men den ser ut att ha bytt namn: OSCAR will not touch this folder, and will create a new one instead. OSCAR kommer inte att röra den här mappen utan kommer istället att skapa en ny. Please be careful when playing in OSCAR's profile folders :-P Var försiktig när du ändrar nåt i OSCARs profilmappar :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. Av någon anledning kunde inte OSCAR hitta en journalpost i din profil, men hittade flera mappar för journaldata. OSCAR picked only the first one of these, and will use it in future: OSCAR plockade endast den första av dessa och kommer att använda den i framtiden: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Om dina gamla data saknas, kopiera innehållet i alla andra Journal_XXXXXXX mappar till denna manuellt. Contec Contec CMS50 CMS50 CMS50F3.7 CMS50F3.7 CMS50F CMS50F Fisher & Paykel Fisher & Paykel ICON ICON Backing up files... Gör backup på filer.... Reading data files... Läser datafiler... SmartFlex Mode SmartFlex läge Intellipap pressure relief mode. Intellipap trycklindringsläge. Ramp Only Enbart ramp Full Time Heltid SmartFlex Level SmartFlex-nivå Intellipap pressure relief level. Intellipap trycklindringsnivå. Snoring event. Snarkning. SN SN DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings SmartFlex inställningar ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One Locating STR.edf File(s)... Söker STR.edf fil(er)... Cataloguing EDF Files... Katalogisering av EDF-filer... Queueing Import Tasks... Köar importen... Finishing Up... Avslutar... CPAP Mode CPAP-inställning VPAPauto VPAPauto ASVAuto ASVAuto iVAPS iVAPS PAC PAC Auto for Her Auto for Her EPR EPR ResMed Exhale Pressure Relief ResMed trycklindring på utandning Patient??? Patient??? EPR Level EPR-nivå Exhale Pressure Relief Level Nivå på trycklindring vid utandning Response Respons Patient View Patientvy SmartStart SmartStart Machine auto starts by breathing Maskinen startar automatiskt då man andas Smart Start SmartStart Humid. Status Befuktn. status Humidifier Enabled Status Befuktningsstatus Humid. Level Fuktigh. nivå Humidity Level Fuktighetsnivå Temperature Temperatur ClimateLine Temperature ClimateLine temperatur Temp. Enable Temp. på ClimateLine Temperature Enable ClimateLine temperatur på Temperature Enable Temperatur på AB Filter AB filter Antibacterial Filter Antibakteriellt filter Pt. Access Pt. access Essentials Grundinställning Plus Plus Climate Control Klimatkontroll Manual Manuell Soft Mjuk Standard Standard BiPAP-T BIPAP-T BiPAP-S BIPAP-S BiPAP-S/T BIPAP-S/T SmartStop SmartStop Machine auto stops by breathing Maskinen autostannar av andningen Smart Stop Smart Stop Simple Enkelt Advanced Avancerat Your ResMed CPAP machine (Model %1) has not been tested yet. Din ResMed CPAP maskin (Modell %1) är inte testad än. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Det ser ut som från andra maskiner, så det kan fungera, men utvecklarna vill gärna ha en .zip-kopia av den här maskinens SD-kort för att se till att det fungerar med OSCAR. Parsing STR.edf records... Analyserar STR.edf poster... Auto Auto Mask Mask ResMed Mask Setting ResMed maskinställning Pillows Näskudde Full Face Helmask Nasal Nasalmask Ramp Enable Ramptid ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Somnopose-mjukvara Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Zeo Zeo Personal Sleep Coach Personlig sömntränare Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> Senaste Oximeter data: <a onclick='alert("daily=%2");'>%1</a> (last night) (i natt) (1 day ago) (1 dag sen) (%2 days ago) (%2 dagar sen) No oximetry data has been imported yet. Ingen Oximeter-data har blivit importerad än. Snapshot %1 Skärmdump %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... Laddar %1 data för %2... Scanning Files Skannar filer Migrating Summary File Location Flyttar sammanställningsdatans sökväg Loading Summaries.xml.gz Laddar Summaries.xml.gz Loading Summary Data Laddar sammanställningsdata Please Wait... Vänta en stund... Updating Statistics cache Uppdaterar cache för statistik Usage Statistics Användningsstatistik %1 Charts %1 Diagram %1 of %2 Charts %1 av %2 Diagram Loading summaries Laddar sammanfattningar Dreem Dreem Your Viatom device generated data that OSCAR has never seen before. Din Viatom-enhet genererade data som OSCAR aldrig har sett förut. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Den importerade informationen kanske inte är helt korrekt, så utvecklarna vill gärna ha en kopia av dina Viatom-filer för att se till att OSCAR hanterar data korrekt. Viatom Viatom Viatom Software Viatom mjukvara New versions file improperly formed Nya versioner är felaktigt utformade A more recent version of OSCAR is available En nyare version av OSCAR finns tillgänglig release version test version test-version You are running the latest %1 of OSCAR Du använder den senaste %1 av OSCAR You are running OSCAR %1 Du använder OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 är tillgänglig <a href='%2'>här</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Information om nyare testversion %1 finns på <a href='%2'>%2</a> Check for OSCAR Updates Kontrollera för uppdateringar till OSCAR Unable to check for updates. Please try again later. Kan inte söka efter uppdateringar, försök igen senare. SensAwake level SenseAwake nivå Expiratory Relief Trycklättnad utandning Expiratory Relief Level Trycklättnad utandning nivå Humidity Befuktning SleepStyle SleepStyle This page in other languages: Den här sidan i andra språk: %1 Graphs %1 grafer %1 of %2 Graphs %1 av %2 grafer %1 Event Types %1 händelsetyp %1 of %2 Event Types %1 av %2 händelsetyper Report about:blank om:tom SessionBar %1h %2m %1h %2m No Sessions Present Ingen period finns för närvarande SleepStyleLoader Import Error Import-fel This Machine Record cannot be imported in this profile. Maskin-data kan inte importeras till denna profil. The Day records overlap with already existing content. Den här dagens data överlappar redan sparat innehåll. Statistics CPAP Statistics CPAP-statistik CPAP Usage CPAP-användning Average Hours per Night Genomsnitt timmar per natt Therapy Efficacy Terapieffekt Leak Statistics Läckagestatistik Pressure Statistics Tryckstatistik Oximeter Statistics Oximeterstatistik Blood Oxygen Saturation Blodets syremättnad Pulse Rate Puls %1 Median %1 Median Average %1 Genomsnitt %1 Min %1 Min %1 Max %1 Max %1 %1 Index %1 index % of time in %1 % av tiden i %1 % of time above %1 threshold % av tiden ovan %1-linjen % of time below %1 threshold % av tiden under %1-linjen Name: %1, %2 Namn: %1, %2 DOB: %1 Födelsedatum: %1 Phone: %1 Telefon: %1 Email: %1 E-post: %1 Address: Adress: Oscar has no data to report :( Oscar har ingen data att rapportera :( Days Used: %1 Dagar använd: %1 Low Use Days: %1 Dagar med låg compliance: %1 Compliance: %1% Compliance: %1% Days AHI of 5 or greater: %1 Dagar med AHI på 5 eller högre: %1 Best AHI Bästa AHI Date: %1 AHI: %2 Datum: %1 AHI: %2 Worst AHI Sämsta AHI Best Flow Limitation Bästa flödes-begränsning Date: %1 FL: %2 Datum: %1 FL: %2 Worst Flow Limtation Sämsta flödes-begränsning No Flow Limitation on record Ingen flödesbegränsning registrerad Worst Large Leaks Sämsta stort-läckage Date: %1 Leak: %2% Datum: %1 Läcka: %2% No Large Leaks on record Inget stort-läckage registrerat Worst CSR Sämsta CSR Date: %1 CSR: %2% Datum: %1 CSR: %2% No CSR on record Ingen CSR registrerad Worst PB Sämsta PB Date: %1 PB: %2% Datum: %1 PB: %2% No PB on record Ingen PB i lagrade data Want more information? Vill du ha mera information? OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR behöver alla sammanfattande data laddade för att beräkna bästa / värsta data för enskilda dagar. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Vänligen aktivera kryssrutan "Förladda alla sammanställningsdata vid programstart" i inställningarna för att se till att dessa data är tillgängliga. Best RX Setting Bästa Tryckinställning Date: %1 - %2 Datum: %1 - %2 AHI: %1 AHI: %1 Total Hours: %1 Totalt antal timmar: %1 Worst RX Setting Sämsta Tryckinställning Most Recent Nyaste Compliance (%1 hrs/day) Compliance (Minst %1 tim/dygn) Changes to Machine Settings Ändringar av maskininställningar No data found?!? Ingen data hittades?!? Last Week Sista veckan Last 30 Days Sista 30 dagarna Last 6 Months Senaste 6 månaderna Last Year Senaste året Last Session Sista perioden Details Detaljer No %1 data available. Ingen %1 data tillgänglig. %1 day of %2 Data on %3 %1 dag av %2 Data på %3 %1 days of %2 Data, between %3 and %4 %1 dagar av %2 Data, mellan %3 och %4 OSCAR is free open-source CPAP report software OSCAR är gratis CPAP-Programvara med öppen källkod This report was prepared on %1 by OSCAR %2 Denna rapport utarbetades %1 av OSCAR %2 Days Dagar Pressure Relief Trycklindring Pressure Settings Tryckinställning Machine Information Maskininformation First Use Först använd Last Use Sist använd Welcome Welcome to the Open Source CPAP Analysis Reporter Välkommen till sömnanalysprogrammet OSCAR<br/>Analysera data från minneskortet i CPAP:en<br/>Vad händer med andningen när du sover? What would you like to do? Vad vill du göra? CPAP Importer CPAP Import Oximetry Wizard Oximeter Guide Daily View Daglig vy Overview Översikt Statistics Statistik <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Varning: </span><span style=" color:#ff0000;">ResMed S9 SD-kort behöver skrivskyddas </span><span style=" font-weight:600; color:#ff0000;">före insättning i datorns kortläsare.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Vissa operativsystem skriver små indexfiler till minneskortet utan att fråga först, vilket gör kortet oläsligt för din CPAP.</span></p></body></html> It would be a good idea to check File->Preferences first, Det är en bra idé att kontrollera Arkiv-> Inställningar först, as there are some options that affect import. eftersom det finns några alternativ som påverkar importen. Note that some preferences are forced when a ResMed machine is detected Observera att vissa inställningar är givna när en ResMed-maskin detekteras First import can take a few minutes. Första importen kan ta några minuter. The last time you used your %1... Sista gången du använde din %1... last night i natt %2 days ago %2 dagar sen was %1 (on %2) var %1 (%2) %1 hours, %2 minutes and %3 seconds %1 timmar, %2 minuter och %3 sekunder Your machine was on for %1. Din maskin var på i %1. <font color = red>You only had the mask on for %1.</font> <font color = red>Du hade masken på i endast %1.</font> under under over över reasonably close to ganska nära equal to lika med You had an AHI of %1, which is %2 your %3 day average of %4. Du hade ett AHI på %1, vilket är %2 ditt %3-dygns medel-AHI på %4. Your CPAP machine used a constant %1 %2 of air Din CPAP maskin använde %1 %2 i tryck Your pressure was under %1 %2 for %3% of the time. Ditt tryck var på eller under %1 %2 i %3% av tiden. Your machine used a constant %1-%2 %3 of air. Din maskin använde %1-%2 %3 i tryck. Your EPAP pressure fixed at %1 %2. Ditt EPAP tryck är fast på %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Ditt IPAP-tryck var lägre än %1 %2 i %3% av tiden. Your EPAP pressure was under %1 %2 for %3% of the time. Ditt EPAP-tryck var lägre än %1 %2 i %3% av tiden. Your machine was under %1-%2 %3 for %4% of the time. Din maskin var under %1-%2 %3 i %4% av tiden. 1 day ago För 1 dag sedan Your average leaks were %1 %2, which is %3 your %4 day average of %5. Ditt medel-läckage var %1 %2, vilket är %3 ditt %4-dygns medel på %5. No CPAP data has been imported yet. Inga CPAP-data har blivit importerade ännu. gGraph %1 days %1 dagar gGraphView 100% zoom level 100% zoomnivå Restore X-axis zoom to 100% to view entire selected period. Återställ zoomning av X-axeln till 100% för att visa hela den valda perioden. Restore X-axis zoom to 100% to view entire day's data. Återställ zoomning av X-axeln till 100% för att visa hela dagens data. Reset Graph Layout Återställ grafens layout Resets all graphs to a uniform height and default order. Återställ alla grafer till enhetlig höjd och standardvisning. Y-Axis Y-axel Plots Diagram CPAP Overlays CPAP överlägg Oximeter Overlays Oximeter-överlägg Dotted Lines Prickade linjer Double click title to pin / unpin Click and drag to reorder graphs Dubbelklicka på rubriken för att fästa / lossa Klicka och dra för att omorganisera grafer Remove Clone Avlägsna klon Clone %1 Graph Klona %1 graf OSCAR-code-v1.3.1/Translations/Thai.th.ts000066400000000000000000013666031417327530600200400ustar00rootroot00000000000000 AboutDialog &About Release Notes Credits GPL License Close Show data folder About OSCAR %1 Sorry, could not locate About file. Sorry, could not locate Credits file. Sorry, could not locate Release Notes. OSCAR %1 Important: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. To see if the license text is available in your language, see %1. CMS50F37Loader Could not find the oximeter file: Could not open the oximeter file: CMS50Loader Could not get data transmission from oximeter. Please ensure you select 'upload' from the oximeter devices menu. Could not find the oximeter file: Could not open the oximeter file: CheckUpdates Checking for newer OSCAR versions Daily Go to the previous day Show or hide the calender Go to the next day Go to the most recent day with data records Events View Size Notes Journal B u Color Small Medium Big Zombie I'm feeling ... Weight If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Awesome B.M.I. Bookmarks Add Bookmark Starts Remove Bookmark Flags Graphs Show/hide available graphs. Breakdown events UF1 UF2 Time at Pressure No %1 events are recorded this day %1 event %1 events Session Start Times Session End Times Session Information Oximetry Sessions Duration (Mode and Pressure settings missing; yesterday's shown.) This bookmark is in a currently disabled area.. CPAP Sessions Details Sleep Stage Sessions Position Sensor Sessions Unknown Session Machine Settings Model %1 - %2 PAP Mode: %1 This day just contains summary data, only limited information is available. Total ramp time Time outside of ramp Start End Unable to display Pie Chart on this system 10 of 10 Event Types This CPAP machine does NOT record detailed data Sorry, this machine only provides compliance data. "Nothing's here!" No data is available for this day. 10 of 10 Graphs Oximeter Information Click to %1 this session. disable enable %1 Session #%2 %1h %2m %3s <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. SpO2 Desaturations Pulse Change events SpO2 Baseline Used Statistics 99.5% Total time in apnea Time over leak redline Event Breakdown Sessions all off! Sessions exist for this day but are switched off. Impossibly short session Zero hours?? BRICK :( Complain to your Equipment Provider! Pick a Colour Bookmark at %1 ExportCSV Export as CSV Dates: Resolution: Details Sessions Daily Filename: Cancel Export Start: End: Quick Range: Most Recent Day Last Week Last Fortnight Last Month Last 6 Months Last Year Everything Custom Details_ Sessions_ Summary_ Select file to export to CSV Files (*.csv) DateTime Session Event Data/Duration Date Session Count Start End Total Time AHI AHI Count FPIconLoader Import Error This Machine Record cannot be imported in this profile. The Day records overlap with already existing content. Help Hide this message Search Topic: Help Files are not yet available for %1 and will display in %2. Help files do not appear to be present. HelpEngine did not set up correctly HelpEngine could not register documentation correctly. Contents Index Search No documentation available Please wait a bit.. Indexing still in progress No %1 result(s) for "%2" clear MD300W1Loader Could not find the oximeter file: Could not open the oximeter file: MainWindow &Statistics Report Mode Standard Monthly Date Range Statistics Daily Overview Oximetry Import Help &File &View &Reset Graphs &Help Troubleshooting &Data &Advanced Purge ALL Machine Data Rebuild CPAP Data &Import CPAP Card Data Show Daily view Show Overview view &Maximize Toggle Maximize window Reset Graph &Heights Reset sizes of graphs Show Right Sidebar Show Statistics view Import &Dreem Data Show &Line Cursor Show Daily Left Sidebar Show Daily Calendar Create zip of CPAP data card Create zip of OSCAR diagnostic logs Create zip of all OSCAR data Report an Issue System Information Show &Pie Chart Show Pie Chart on Daily page Standard graph order, good for CPAP, APAP, Bi-Level Advanced Advanced graph order, good for ASV, AVAPS Show Personal Data Check For &Updates Purge Current Selected Day &CPAP &Oximetry &Sleep Stage &Position &All except Notes All including &Notes &Preferences &Profiles &About OSCAR Show Performance Information CSV Export Wizard Export for Review E&xit Exit View &Daily View &Overview View &Welcome Use &AntiAliasing Show Debug Pane Take &Screenshot O&ximetry Wizard Print &Report &Edit Profile Import &Viatom/Wellue Data Daily Calendar Backup &Journal Online Users &Guide &Frequently Asked Questions &Automatic Oximetry Cleanup Change &User Purge &Current Selected Day Right &Sidebar Daily Sidebar View S&tatistics Navigation Bookmarks Records Exp&ort Data Profiles Purge Oximetry Data View Statistics Import &ZEO Data Import RemStar &MSeries Data Sleep Disorder Terms &Glossary Change &Language Change &Data Folder Import &Somnopose Data Current Days Welcome &About Please wait, importing from backup folder(s)... Import Problem Please insert your CPAP data card... Access to Import has been blocked while recalculations are in progress. CPAP Data Located Import Reminder Find your CPAP data card Importing Data Choose where to save screenshot Image files (*.png) The User's Guide will open in your default browser The FAQ is not yet implemented If you can read this, the restart command didn't work. You will have to do it yourself manually. Are you sure you want to rebuild all CPAP data for the following machine: For some reason, OSCAR does not have any backups for the following machine: OSCAR does not have any backups for this machine! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: No help is available. %1's Journal Choose where to save journal XML Files (*.xml) Export review is not yet implemented Would you like to zip this card? Choose where to save zip ZIP files (*.zip) Creating zip... Calculating size... Reporting issues is not yet implemented OSCAR Information Help Browser %1 (Profile: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Please open a profile first. Check for updates not implemented Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. Are you really sure you want to do this? Because there are no internal backups to rebuild from, you will have to restore from your own. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Note as a precaution, the backup folder will be left in place. Are you <b>absolutely sure</b> you want to proceed? The Glossary will open in your default browser There was a problem opening %1 Data File: %2 %1 Data Import of %2 file(s) complete %1 Import Partial Success %1 Data Import complete Are you sure you want to delete oximetry data for %1 <b>Please be aware you can not undo this operation!</b> Select the day with valid oximetry data in daily view first. Loading profile "%1" Imported %1 CPAP session(s) from %2 Import Success Already up to date with CPAP data at %1 Up to date Couldn't find any valid Machine Data at %1 Choose a folder No profile has been selected for Import. Import is already running in the background. A %1 file structure for a %2 was located at: A %1 file structure was located at: Would you like to import from this location? Specify Access to Preferences has been blocked until recalculation completes. There was an error saving screenshot to file "%1" Screenshot saved to file "%1" Please note, that this could result in loss of data if OSCAR's backups have been disabled. There was a problem opening MSeries block File: MSeries Import complete MinMaxWidget Auto-Fit Defaults Override The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. The Minimum Y-Axis value.. Note this can be a negative number if you wish. The Maximum Y-Axis value.. Must be greater than Minimum to work. Scaling Mode This button resets the Min and Max to match the Auto-Fit NewProfile Edit User Profile I agree to all the conditions above. User Information User Name Password Protect Profile Password ...twice... Locale Settings Country TimeZone about:blank Very weak password protection and not recommended if security is required. DST Zone Personal Information (for reports) First Name Last Name It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. D.O.B. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> Gender Male Female Height Metric English Contact Information Address Email Phone CPAP Treatment Information Date Diagnosed Untreated AHI CPAP Mode CPAP APAP Bi-Level ASV RX Pressure Doctors / Clinic Information Doctors Name Practice Name Patient ID &Cancel &Back &Next Select Country Welcome to the Open Source CPAP Analysis Reporter This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. PLEASE READ CAREFULLY Accuracy of any data displayed is not and can not be guaranteed. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Use of this software is entirely at your own risk. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Please provide a username for this profile Passwords don't match Profile Changes Accept and save this information? &Finish &Close this window Overview Range: Last Week Last Two Weeks Last Month Last Two Months Last Three Months Last 6 Months Last Year Everything Custom Snapshot Start: End: Reset view to selected date range Toggle Graph Visibility Drop down to see list of graphs to switch on/off. Graphs Respiratory Disturbance Index Apnea Hypopnea Index Usage Usage (hours) Session Times Total Time in Apnea Total Time in Apnea (Minutes) Body Mass Index How you felt (0-10) 10 of 10 Charts Show all graphs Hide all graphs OximeterImport Oximeter Import Wizard Skip this page next time. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Where would you like to import from? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> Record attached to computer overnight (provides plethysomogram) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review Please connect your oximeter device If you can read this, you likely have your oximeter type set wrong in preferences. Please connect your oximeter device, turn it on, and enter the menu Press Start to commence recording Show Live Graphs Duration Pulse Rate Multiple Sessions Detected Start Time Details Import Completed. When did the recording start? Oximeter Starting time I want to use the time reported by my oximeter's built in clock. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> Choose CPAP session to sync to: You can manually adjust the time here if required: HH:mm:ssap &Cancel &Information Page CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 Set device date/time <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> Set device identifier Erase session after successful upload Import directly from a recording on a device <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> Please choose which one you want to import into OSCAR Day recording (normally would have) started <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> &Retry &Choose Session &End Recording &Sync and Save &Save and Finish &Start Scanning for compatible oximeters Could not detect any connected oximeter devices. Connecting to %1 Oximeter Renaming this oximeter from '%1' to '%2' Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. "%1", session %2 Nothing to import Your oximeter did not have any valid sessions. Close Waiting for %1 to start Waiting for the device to start the upload process... Select upload option on %1 You need to tell your oximeter to begin sending data to the computer. Please connect your oximeter, enter it's menu and select upload to commence data transfer... %1 device is uploading data... Please wait until oximeter upload process completes. Do not unplug your oximeter. Oximeter import completed.. Select a valid oximetry data file Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Live Oximetry Mode Live Oximetry Stopped Live Oximetry import has been stopped Oximeter Session %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. Oximeter not detected Couldn't access oximeter Starting up... If you can still read this after a few seconds, cancel and try again Live Import Stopped %1 session(s) on %2, starting at %3 No CPAP data available on %1 Recording... Finger not detected I want to use the time my computer recorded for this live oximetry session. I need to set the time manually, because my oximeter doesn't have an internal clock. Something went wrong getting session data Welcome to the Oximeter Import Wizard Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. It also can read from ChoiceMMed MD300W1 oximeter .dat files. Please remember: Important Notes: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Oximetry Date d/MM/yy h:mm:ss AP R&eset Pulse &Open .spo/R File Serial &Import &Start Live Serial Port &Rescan Ports PreferencesDialog Preferences &Import Combine Close Sessions Minutes Multiple sessions closer together than this value will be kept on the same day. Ignore Short Sessions <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Sessions starting before this time will go to the previous calendar day. Session Storage Options Compress SD Card Backups (slower first import, but makes backups smaller) &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. hours Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Flow Restriction Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> Duration of airflow restriction s Event Duration Allow duplicates near machine events. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. minutes Reset the counter to zero at beginning of each (time) window. Zero Reset CPAP Clock Drift Do not import sessions older than: Sessions older than this date will not be imported dd MMMM yyyy User definable threshold considered large leak Whether to show the leak redline in the leak graph Search &Oximetry Show in Event Breakdown Piechart Resync Machine Detected Events (Experimental) Percentage drop in oxygen saturation Pulse Sudden change in Pulse Rate of at least this amount bpm Minimum duration of drop in oxygen saturation Minimum duration of pulse change event. Small chunks of oximetry data under this amount will be discarded. &General Changes to the following settings needs a restart, but not a recalc. Preferred Calculation Methods Middle Calculations Upper Percentile Session Splitting Settings <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Memory and Startup Options Pre-Load all summary data at startup <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> Keep Waveform/Event data in memory <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> Import without asking for confirmation This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Calculate Unintentional Leaks When Not Present Note: A linear calculation method is used. Changing these values requires a recalculation. General CPAP and Related Settings Enable Unknown Events Channels AHI Apnea Hypopnea Index AHI RDI Respiratory Disturbance Index AHI/Hour Graph Time Window Preferred major event index Compliance defined as Flag leaks over threshold Seconds <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> Hours For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Median is recommended for ResMed users. Median Weighted Average Normal Average True Maximum 99% Percentile Maximum Calcs General Settings Daily view navigation buttons will skip over days without data records Skip over Empty Days Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Enable Multithreading Bypass the login screen and load the most recent User Profile Create SD Card Backups during Import (Turn this off at your own peril!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> Combined Count divided by Total Hours Time Weighted average of Indice Standard average of indice Custom CPAP User Event Flagging Events Reset &Defaults <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> Waveforms Flag rapid changes in oximetry stats Other oximetry options Discard segments under Flag Pulse Rate Above Flag Pulse Rate Below Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Compress Session Data (makes OSCAR data smaller, but day changing slower.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> 4 cmH2O 20 cmH2O This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Show flags for machine detected events that haven't been identified yet. Show Remove Card reminder notification on OSCAR shutdown Check for new version every days. Last Checked For Updates: TextLabel I want to be notified of test versions. (Advanced users only please.) &Appearance Graph Settings <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> Bar Tops Line Chart Overview Linecharts Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> How long you want the tooltips to stay visible. Scroll Dampening Tooltip Timeout Default display height of graphs in pixels Graph Tooltips The visual method of displaying waveform overlay flags. Standard Bars Top Markers Graph Height Changing SD Backup compression options doesn't automatically recompress backup data. Auto-Launch CPAP Importer after opening profile Automatically load last used profile on start-up <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered Your masks vent rate at 20 cmH2O pressure Your masks vent rate at 4 cmH2O pressure l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Always save screenshots in the OSCAR Data folder Check For Updates You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. Automatically check for updates How often OSCAR should check for updates. If you are interested in helping test new features and bugfixes early, click here. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR On Opening Profile Welcome Daily Statistics Switch Tabs No change After Import Overlay Flags Line Thickness The pixel thickness of line plots Other Visual Settings Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Use Anti-Aliasing Makes certain plots look more "square waved". Square Wave Plots Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Use Pixmap Caching <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> Animations && Fancy Stuff Whether to allow changing yAxis scales by double clicking on yAxis labels Allow YAxis Scaling Whether to include machine serial number on machine settings changes report Include Serial Number Graphics Engine (Requires Restart) Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Fonts (Application wide settings) Font Size Bold Italic Application Graph Text Graph Titles Big Text Details &Cancel &Ok Name Color Flag Type Label CPAP Events Oximeter Events Positional Events Sleep Stage Events Unknown Events Double click to change the descriptive name this channel. Double click to change the default color for this channel plot/flag/data. Overview <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> Double click to change the descriptive name the '%1' channel. Whether this flag has a dedicated overview chart. Here you can change the type of flag shown for this event This is the short-form label to indicate this channel on screen. This is a description of what this channel does. Lower Upper CPAP Waveforms Oximeter Waveforms Positional Waveforms Sleep Stage Waveforms Whether a breakdown of this waveform displays in overview. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Data Processing Required A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Data Reindex Required A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Restart Required One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. If you need to conserve disk space, please remember to carry out manual backups. Are you sure you want to disable these backups? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Are you really sure you want to do this? %1 %2 Flag Minor Flag Span Always Minor No CPAP machines detected Will you be using a ResMed brand machine? Never This may not be a good idea ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ProfileSelector Filter: Reset filter to see all profiles Version &Open Profile &Edit Profile &New Profile Profile: None Please select or create a profile... Destroy Profile Profile Ventilator Brand Ventilator Model Other Data Last Imported Name You must create a profile Enter Password for %1 You entered an incorrect password Forgot your password? Ask on the forums how to reset it, it's actually pretty easy. Select a profile first The selected profile does not appear to contain any data and cannot be removed by OSCAR If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. You are about to destroy profile '<b>%1</b>'. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. DELETE Sorry You need to enter DELETE in capital letters. There was an error deleting the profile directory, you need to manually remove it. Profile '%1' was succesfully deleted Bytes KB MB GB TB PB Summaries: Events: Backups: Hide disk usage information Show disk usage information Name: %1, %2 Phone: %1 Email: <a href='mailto:%1'>%1</a> Address: No profile information given Profile: %1 ProgressDialog Abort QObject No Data Events Duration (% %1 in events) Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ft lb oz Kg cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours Min %1 Hours: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 %1 Length: %3 Start: %2 Mask On Mask Off %1 Length: %3 Start: %2 TTIA: TTIA: %1 Minutes Seconds h m s ms Events/hr Hz bpm Litres ml Breaths/min ? Severity (0-1) Degrees Error Warning Information Busy Please Note Graphs Switched Off Sessions Switched Off &Yes &No &Cancel &Destroy &Save BMI Weight Zombie Pulse Rate Plethy Pressure Daily Profile Overview Oximetry Oximeter Event Flags Default CPAP BiPAP Bi-Level EPAP Min EPAP Max EPAP IPAP Min IPAP Max IPAP APAP ASV AVAPS ST/ASV Humidifier H OA A CA FL SA LE EP VS VS2 RERA PP P RE NR NRI O2 PC UF1 UF2 UF3 PS AHI AHI RDI AI HI UAI CAI FLI REI EPI PB IE Insp. Time Exp. Time Resp. Event Flow Limitation Flow Limit SensAwake Pat. Trig. Breath Tgt. Min. Vent Target Vent. Minute Vent. Tidal Volume Resp. Rate Snore Leak Leaks Large Leak LL Total Leaks Unintentional Leaks MaskPressure Flow Rate Sleep Stage Usage Sessions Pr. Relief No Data Available App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Software Engine ANGLE / OpenGLES Desktop OpenGL m cm in Only Settings and Compliance Data Available Summary Data Only Bookmarks Mode Model Brand Serial Series Machine Channel Settings Inclination Orientation Motion Name DOB Phone Address Email Patient ID Date Bedtime Wake-up Mask Time Unknown None Ready First Last Start End On Off Yes No Min Max Med Average Median Avg W-Avg Your %1 %2 (%3) generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Machine Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. Your %1 CPAP machine (Model %2) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sorry, your %1 CPAP machine (%2) is not supported yet. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Machine Unsupported I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. Scanning Files... Importing Sessions... Finishing up... Machine Untested Flex Lock Whether Flex settings are available to you. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Rise Time Lock Whether Rise Time settings are available to you. Rise Lock Mask Resistance Setting Mask Resist. Hose Diam. 15mm 22mm Backing Up Files... Untested Data model %1 DreamStation 2 unknown model CPAP-Check AutoCPAP Auto-Trial AutoBiLevel S S/T S/T - AVAPS PC - AVAPS Flex Mode PRS1 pressure relief mode. C-Flex C-Flex+ A-Flex P-Flex Rise Time Bi-Flex Flex Flex Level PRS1 pressure relief setting. Passover Target Time PRS1 Humidifier Target Time Hum. Tgt Time Tubing Type Lock Whether tubing type settings are available to you. Tube Lock Mask Resistance Lock Whether mask resistance settings are available to you. Mask Res. Lock Whether or not machine shows AHI via built-in display. Ramp Type Type of ramp curve to use. Linear SmartRamp Ramp+ Backup Breath Mode The kind of backup breath rate in use: none (off), automatic, or fixed Breath Rate Fixed Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Breath BPM Timed Inspiration The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Auto-Trial Duration The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Auto-Trial Dur. EZ-Start Whether or not EZ-Start is enabled Variable Breathing UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend Peak Flow Peak flow during a 2-minute interval Humidifier Status PRS1 humidifier connected? Disconnected Connected Humidification Mode PRS1 Humidification Mode Humid. Mode Fixed (Classic) Adaptive (System One) Heated Tube Tube Temperature PRS1 Heated Tube Temperature Tube Temp. PRS1 Humidifier Setting Hose Diameter Diameter of primary CPAP hose 12mm Auto On A few breaths automatically starts machine Auto Off Machine automatically switches off Mask Alert Whether or not machine allows Mask checking. Show AHI Breathing Not Detected A period during a session where the machine could not detect flow. BND Timed Breath Machine Initiated Breath TB Windows User Using , found SleepyHead - You must run the OSCAR Migration Tool <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> Launching Windows Explorer failed Could not find explorer.exe in path to launch Windows Explorer. OSCAR %1 needs to upgrade its database for %2 %3 %4 <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> OSCAR does not yet have any automatic card backups stored for this device. This means you will need to import this machine data again afterwards from your own backups or data card. Important: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Are you ready to upgrade, so you can run the new version of OSCAR? Sorry, the purge operation failed, which means this version of OSCAR can't start. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Machine Database Changes Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. The machine data folder needs to be removed manually. This folder currently resides at the following location: Rebuilding from %1 Backup Therapy Pressure Inspiratory Pressure Lower Inspiratory Pressure Higher Inspiratory Pressure Expiratory Pressure Lower Expiratory Pressure Higher Expiratory Pressure Pressure Support PS Min Pressure Support Minimum PS Max Pressure Support Maximum Min Pressure Minimum Therapy Pressure Max Pressure Maximum Therapy Pressure Ramp Time Ramp Delay Period Ramp Pressure Starting Ramp Pressure Ramp Event Ramp An abnormal period of Cheyne Stokes Respiration An apnea that couldn't be determined as Central or Obstructive. A restriction in breathing from normal, causing a flattening of the flow waveform. Vibratory Snore (VS2) A ResMed data item: Trigger Cycle Event Mask On Time Time started according to str.edf Summary Only An apnea where the airway is open An apnea caused by airway obstruction Hypopnea A partially obstructed airway Unclassified Apnea UA Vibratory Snore A vibratory snore A vibratory snore as detcted by a System One machine Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A large mask leak affecting machine performance. Non Responding Event A type of respiratory event that won't respond to a pressure increase. Expiratory Puff Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. User Flag #1 User Flag #2 User Flag #3 Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm Pulse Change A sudden (user definable) change in heart rate SpO2 Drop A sudden (user definable) drop in blood oxygen saturation SD Breathing flow rate waveform L/min Mask Pressure Amount of air displaced per breath Graph displaying snore volume Minute Ventilation Amount of air displaced per minute Respiratory Rate Rate of breaths per minute Patient Triggered Breaths Percentage of breaths triggered by patient Pat. Trig. Breaths Leak Rate Rate of detected mask leakage I:E Ratio Ratio between Inspiratory and Expiratory time ratio Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting Cheyne Stokes Respiration CSR Periodic Breathing An abnormal period of Periodic Breathing Clear Airway Obstructive Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Leak Flag LF A user definable event detected by OSCAR's flow waveform processor. Perfusion Index A relative assessment of the pulse strength at the monitoring site Perf. Index % Mask Pressure (High frequency) Expiratory Time Time taken to breathe out Inspiratory Time Time taken to breathe in Respiratory Event Graph showing severity of flow limitations Flow Limit. Target Minute Ventilation Maximum Leak The maximum rate of mask leakage Max Leaks Apnea Hypopnea Index Graph showing running AHI for the past hour Total Leak Rate Detected mask leakage including natural Mask leakages Median Leak Rate Median rate of detected mask leakage Median Leaks Respiratory Disturbance Index Graph showing running RDI for the past hour Sleep position in degrees Upright angle in degrees Movement Movement detector CPAP Session contains summary data only PAP Mode Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Apnea An apnea reportred by your CPAP machine. PAP Device Mode APAP (Variable) ASV (Fixed EPAP) ASV (Variable EPAP) Height Physical Height Notes Bookmark Notes Body Mass Index How you feel (0 = like crap, 10 = unstoppable) Bookmark Start Bookmark End Last Updated Journal Notes Journal 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep Brain Wave BrainWave Awakenings Number of Awakenings Morning Feel How you felt in the morning Time Awake Time spent awake Time In REM Sleep Time spent in REM Sleep Time in REM Sleep Time In Light Sleep Time spent in light sleep Time in Light Sleep Time In Deep Sleep Time spent in deep sleep Time in Deep Sleep Time to Sleep Time taken to get to sleep Zeo ZQ Zeo sleep quality measurement ZEO ZQ Debugging channel #1 Test #1 For internal use only Debugging channel #2 Test #2 Zero Upper Threshold Lower Threshold As you did not select a data folder, OSCAR will exit. or CANCEL to skip migration. Choose the SleepyHead or OSCAR data folder to migrate The folder you chose does not contain valid SleepyHead or OSCAR data. You cannot use this folder: Migrating files from to OSCAR crashed due to an incompatibility with your graphics hardware. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. OSCAR will set up a folder for your data. If you have been using SleepyHead or an older version of OSCAR, OSCAR can copy your old data to this folder later. Migrate SleepyHead or OSCAR Data? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. We suggest you use this folder: Click Ok to accept this, or No if you want to use a different folder. Choose or create a new folder for OSCAR data Next time you run OSCAR, you will be asked again. The folder you chose is not empty, nor does it already contain valid OSCAR data. Data directory: Unable to create the OSCAR data folder at Unable to write to OSCAR data directory Error code OSCAR cannot continue and is exiting. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Version "%1" is invalid, cannot continue! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). It is likely that doing this will cause data corruption, are you sure you want to do this? Question Exiting Are you sure you want to use this folder? Don't forget to place your datacard back in your CPAP machine OSCAR Reminder You can only work with one instance of an individual OSCAR profile at a time. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Loading profile "%1"... Chromebook file system detected, but no removable device found You must share your SD card with Linux using the ChromeOS Files program Recompressing Session Files Please select a location for your zip other than the data card itself! Unable to create zip! Are you sure you want to reset all your channel colors and settings to defaults? Are you sure you want to reset all your waveform channel colors and settings to defaults? There are no graphs visible to print Would you like to show bookmarked areas in this report? Printing %1 Report %1 Report : %1 hours, %2 minutes, %3 seconds RDI %1 AHI %1 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 NRI=%1 LKI=%2 EPI=%3 AI=%1 Reporting from %1 to %2 Entire Day's Flow Waveform Current Selection Entire Day Page %1 of %2 Days: %1 Low Usage Days: %1 (%1% compliant, defined as > %2 hours) (Sess: %1) Bedtime: %1 Waketime: %1 (Summary Only) There is a lockfile already present for this profile '%1', claimed on '%2'. Fixed Bi-Level Auto Bi-Level (Fixed PS) Auto Bi-Level (Variable PS) 99.5% varies n/a Fixed %1 (%2) Min %1 Max %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> (last night) (1 day ago) (%2 days ago) No oximetry data has been imported yet. Contec CMS50 Fisher & Paykel ICON DeVilbiss Intellipap SmartFlex Settings ChoiceMMed MD300 Respironics M-Series Philips Respironics System One ResMed S9 EPR: Somnopose Somnopose Software Zeo Personal Sleep Coach Database Outdated Please Rebuild CPAP Data (%2 min, %3 sec) (%3 sec) Pop out Graph The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Your machine doesn't record data to graph in Daily View There is no data to graph d MMM yyyy [ %1 - %2 ] Hide All Events Show All Events Unpin %1 Graph Popout %1 Graph Pin %1 Graph Plots Disabled Duration %1:%2:%3 AHI %1 Relief: %1 Hours: %1h, %2m, %3s Machine Information Journal Data OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR will not touch this folder, and will create a new one instead. Please be careful when playing in OSCAR's profile folders :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. OSCAR picked only the first one of these, and will use it in future: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. CMS50F3.7 CMS50F Backing up files... Reading data files... SmartFlex Mode Intellipap pressure relief mode. Ramp Only Full Time SmartFlex Level Intellipap pressure relief level. Snoring event. SN Locating STR.edf File(s)... Cataloguing EDF Files... Queueing Import Tasks... Finishing Up... CPAP Mode VPAPauto ASVAuto iVAPS PAC Auto for Her EPR ResMed Exhale Pressure Relief Patient??? EPR Level Exhale Pressure Relief Level Response Patient View SmartStart Machine auto starts by breathing Smart Start Humid. Status Humidifier Enabled Status Humid. Level Humidity Level Temperature ClimateLine Temperature Temp. Enable ClimateLine Temperature Enable Temperature Enable AB Filter Antibacterial Filter Pt. Access Essentials Plus Climate Control Manual Soft Standard BiPAP-T BiPAP-S BiPAP-S/T SmartStop Machine auto stops by breathing Smart Stop Simple Advanced Your ResMed CPAP machine (Model %1) has not been tested yet. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Parsing STR.edf records... Auto Mask ResMed Mask Setting Pillows Full Face Nasal Ramp Enable Weinmann SOMNOsoft2 Snapshot %1 CMS50D+ CMS50E/F Loading %1 data for %2... Scanning Files Migrating Summary File Location Loading Summaries.xml.gz Loading Summary Data Please Wait... Updating Statistics cache Usage Statistics %1 Charts %1 of %2 Charts Loading summaries Dreem Your Viatom device generated data that OSCAR has never seen before. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. Viatom Viatom Software New versions file improperly formed A more recent version of OSCAR is available release test version You are running the latest %1 of OSCAR You are running OSCAR %1 OSCAR %1 is available <a href='%2'>here</a>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates Unable to check for updates. Please try again later. SensAwake level Expiratory Relief Expiratory Relief Level Humidity SleepStyle This page in other languages: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Report about:blank SessionBar %1h %2m No Sessions Present SleepStyleLoader Import Error This Machine Record cannot be imported in this profile. The Day records overlap with already existing content. Statistics CPAP Statistics CPAP Usage Average Hours per Night Therapy Efficacy Leak Statistics Pressure Statistics Oximeter Statistics Blood Oxygen Saturation Pulse Rate %1 Median Average %1 Min %1 Max %1 %1 Index % of time in %1 % of time above %1 threshold % of time below %1 threshold Name: %1, %2 DOB: %1 Phone: %1 Email: %1 Address: This report was prepared on %1 by OSCAR %2 Days Used: %1 Low Use Days: %1 Compliance: %1% Days AHI of 5 or greater: %1 Best AHI Date: %1 AHI: %2 Worst AHI Best Flow Limitation Date: %1 FL: %2 Worst Flow Limtation No Flow Limitation on record Worst Large Leaks Date: %1 Leak: %2% No Large Leaks on record Worst CSR Date: %1 CSR: %2% No CSR on record Worst PB Date: %1 PB: %2% No PB on record Want more information? OSCAR needs all summary data loaded to calculate best/worst data for individual days. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Best RX Setting Date: %1 - %2 AHI: %1 Total Hours: %1 Worst RX Setting Most Recent Compliance (%1 hrs/day) OSCAR is free open-source CPAP report software Changes to Machine Settings No data found?!? Oscar has no data to report :( Last Week Last 30 Days Last 6 Months Last Year Last Session Details No %1 data available. %1 day of %2 Data on %3 %1 days of %2 Data, between %3 and %4 Days Pressure Relief Pressure Settings Machine Information First Use Last Use Welcome Welcome to the Open Source CPAP Analysis Reporter What would you like to do? CPAP Importer Oximetry Wizard Daily View Overview Statistics <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> It would be a good idea to check File->Preferences first, as there are some options that affect import. Note that some preferences are forced when a ResMed machine is detected First import can take a few minutes. The last time you used your %1... last night %2 days ago was %1 (on %2) %1 hours, %2 minutes and %3 seconds Your machine was on for %1. <font color = red>You only had the mask on for %1.</font> under over reasonably close to equal to You had an AHI of %1, which is %2 your %3 day average of %4. Your CPAP machine used a constant %1 %2 of air Your pressure was under %1 %2 for %3% of the time. Your machine used a constant %1-%2 %3 of air. Your EPAP pressure fixed at %1 %2. Your IPAP pressure was under %1 %2 for %3% of the time. Your EPAP pressure was under %1 %2 for %3% of the time. Your machine was under %1-%2 %3 for %4% of the time. 1 day ago Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. gGraph %1 days gGraphView 100% zoom level Restore X-axis zoom to 100% to view entire selected period. Restore X-axis zoom to 100% to view entire day's data. Reset Graph Layout Resets all graphs to a uniform height and default order. Y-Axis Plots CPAP Overlays Oximeter Overlays Dotted Lines Double click title to pin / unpin Click and drag to reorder graphs Remove Clone Clone %1 Graph OSCAR-code-v1.3.1/Translations/Turkish.tr.ts000066400000000000000000015071031417327530600206060ustar00rootroot00000000000000 AboutDialog &About &Hakkında Release Notes Sürüm Notları Credits Emeği geçenler GPL License GPL Lisansı Close Kapat Show data folder Veri klasörünü göster About OSCAR %1 OSCAR %1" Hakkında Sorry, could not locate About file. Üzgünüz, Hakkında dosyası bulunamadı. Sorry, could not locate Credits file. Üzgünüz, Emeği geçenler dosyası bulunamadı. Sorry, could not locate Release Notes. Üzgünüz, Sürüm Notları bulunamadı. OSCAR %1 OSCAR %1 Important: Önemli: As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things. Bu bir ön sürüm olduğundan, başka bir işlem yapmadan önce, daha sonra geri almaya teşebbüs ettiğinizde bazı şeyleri bozabileceğinden <b>veri klasörünüzü manüel olarak yedeklemeniz</b>, önerilir. To see if the license text is available in your language, see %1. Lisans metninin ana dilinizde mevcut olup olmadığını görmek için, %1'e bakınız. CMS50F37Loader Could not find the oximeter file: Oksimetre dosyası bulunamadı: Could not open the oximeter file: Oksimetre dosyası açılamadı: CMS50Loader Could not get data transmission from oximeter. Oksimetreden veri iletimi alınamadı. Please ensure you select 'upload' from the oximeter devices menu. Oksimetre cihazları menüsünden 'yükle' seçeneğini seçtiğinize emin olun. Could not find the oximeter file: Oksimetre dosyası bulunamadı: Could not open the oximeter file: Oksimetre dosyası açılamadı: CheckUpdates Checking for newer OSCAR versions OSCAR'ın yeni sürümü kontrol ediliyor Daily Go to the previous day Bir önceki güne git Show or hide the calender Takvimi göster veya gizle Go to the next day Bir sonraki güne git Go to the most recent day with data records Veri kayıtları mevcut olan en son güne git Events Olaylar View Size Boyutu Gör Notes Notlar Journal Günlük B B u u Color Renk Small Küçük Medium Orta Big Büyük Zombie Zombi I'm feeling ... Kendimi nasıl hissediyorum.... Weight Ağırlık If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Seçenekler Penceresinde yükseklik sıfırın üstünde ise, buraya ağırlığın girilmesi Vücut Kitle İndeksi (BMI) değerini gösterecektir Awesome Müthiş B.M.I. B.M.I. Bookmarks Yer İşaretleri Add Bookmark Yer İşareti Ekle Starts Başlangıçlar Remove Bookmark Yer İşaretini Kaldır Flags İşaretler Graphs Grafikler Show/hide available graphs. Mevcut grafikleri göster/gizle. Breakdown Döküm events olaylar UF1 UF1 UF2 UF2 Time at Pressure Basınçta Geçen Süre No %1 events are recorded this day Bu gün hiç %1 olayı kaydedilmemiş %1 event %1 olay %1 events %1 olay Session Start Times Seans Başlangıç Zamanları Session End Times Seans Bitiş Zamanları Session Information Seans Bilgisi Oximetry Sessions Oksimetre Seansları Duration Süre (Mode and Pressure settings missing; yesterday's shown.) (Mod ve Basınç ayarları bulunamadı; dünkü ayarlar gösteriliyor.) This bookmark is in a currently disabled area.. Bu yer işareti şu an devre dışı bırakılmış bir alandadır. CPAP Sessions CPAP Senasları Details Ayrıntılar Sleep Stage Sessions Uyku Evresi Seansları Position Sensor Sessions Pozisyon Algılayıcı Verileri Unknown Session Bilinmeyen Seans Machine Settings Cihaz Ayarları Model %1 - %2 Model %1 - %2 PAP Mode: %1 PAP Modu: %1 99.5% % 99.5 This day just contains summary data, only limited information is available. Bu gün sadece özet veri içermekte olup kısıtlı miktarda bilgi mevcuttur. Total ramp time Toplam rampa süresi Time outside of ramp Rampa dışındaki süre Start Başlangıç End Bitiş Unable to display Pie Chart on this system Bu sistemde Yuvarlak Diyagram gösterilemiyor 10 of 10 Event Types 10 Olay Tipinden 10 uncu This CPAP machine does NOT record detailed data Bu CPAP cihazı detaylı veri kaydı YAPMIYOR Sorry, this machine only provides compliance data. Üzgünüz, bu cihaz sadece uyum verisini sunmaktadır. "Nothing's here!" "Burada hiçbir şey yok!" No data is available for this day. Bu gün için veri mevcut değil. 10 of 10 Graphs 10 Grafikten 10'u Oximeter Information Oksimetre Verisi Click to %1 this session. Bu seansı %1 için tıklayın. disable devre dışı bırak enable etkinleştir %1 Session #%2 %1 Seans #%2 %1h %2m %3s %1st %2dk %3sn <b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days. <b>Lütfen Dikkat:</b> Aşağıda gösterilmekte olan tüm ayarlar önceki günlere kıyasla hiçbir şeyin değişmediği varsayımlarına dayanmaktadır. SpO2 Desaturations SpO2 Desatürasyonları Pulse Change events Nabız Değişimi olayları SpO2 Baseline Used Kullanılan SpO2 Bazal Değeri Statistics İstatistikler Total time in apnea Apnede geçen toplam süre Time over leak redline Sızma kırmızı çizgisi üstünde geçen süre Event Breakdown Olay Dökümü Sessions all off! Tüm seanslar kapalı! Sessions exist for this day but are switched off. Bu gün için mevcut seanslar var ancak kapatılmış durumdalar. Impossibly short session Gerçek olamayacak kadar kısa seans Zero hours?? Sıfır saat?? BRICK :( TUĞLA :( Complain to your Equipment Provider! Cihaz sağlayıcınıza şikayette bulunun! Pick a Colour Bir Renk Seçin Bookmark at %1 %1'deki yer işareti ExportCSV Export as CSV CSV olarak dışa aktar Dates: Tarihler: Resolution: Çözünülürlük: Details Detaylar Sessions Seanslar Daily Günlük Filename: Dosya Adı: Cancel İptal Export Dışa aktar Start: Başlangıç: End: Bitiş: Quick Range: Hızlı Aralık: Most Recent Day En Yeni Gün Last Week Geçen Hafta Last Fortnight Son iki hafta Last Month Geçen Ay Last 6 Months Son 6 Ay Last Year Geçen Yıl Everything Her şey Custom Özelleştirilmiş Details_ Detaylar_ Sessions_ Seanslar_ Summary_ Özet_ Select file to export to Dışa aktarımı yapılacak dosyayı seçin CSV Files (*.csv) CSV Dosyaları (*.csv) DateTime TarihZaman Session Seans Event Olay Data/Duration Veri/Süre Date Tarih Session Count Seans Sayısı Start Başlangıç End Bitiş Total Time Toplam Süre AHI AHI Count Sayı FPIconLoader Import Error İçe Aktarma Hatası This Machine Record cannot be imported in this profile. Bu Cihaz Kaydı bu profile aktarılamaz. The Day records overlap with already existing content. Günlük kayıtlar mevcut içerik ile çakışıyor. Help Hide this message Bu mesajı gizle Search Topic: Konu Ara: Help Files are not yet available for %1 and will display in %2. %1 için Yardım Dosyaları mevcut olmayıp %2'de gösterilecektir. Help files do not appear to be present. Yardım dosyaları mevcut değil gibi görünüyor. HelpEngine did not set up correctly YardımMotoru doğru bir şekilde kurulmadı HelpEngine could not register documentation correctly. YardımMotoru belgeleri doğru bir şekilde kaydedemedi. Contents İçerik Index Dizin Search Ara No documentation available Kullanılabilir belge yok Please wait a bit.. Indexing still in progress Lütfen biraz bekleyin. İndeksleme devam etmekte No Hiç %1 result(s) for "%2" "%2" için %1" sonuç clear temizle MD300W1Loader Could not find the oximeter file: Oksimetre dosyası bulunamadı: Could not open the oximeter file: Oksimetre dosyası açılamadı: MainWindow &Statistics &İstatistikler Report Mode Rapor Modu Standard Standart Monthly Aylık Date Range Veri Aralığı Statistics İstatistikler Daily Günlük Overview Genel bakış Oximetry Oksimetri Import İçe Aktar Help Yardım &File &Dosya &View &Görünüm &Reset Graphs Grafikleri &Sıfırla &Help &Yardım Troubleshooting Sorun giderme &Data &Veri &Advanced &İleri Purge ALL Machine Data TÜM Cihaz Verisini Sil Rebuild CPAP Data CPAP Verisini Yeniden İnşaa Et &Import CPAP Card Data CPAP Kart Verisini &İçe Aktar Show Daily view Günlük görünümü Göster Show Overview view Genel Bakış görünümünü göster &Maximize Toggle &Maksimizasyon Düğmesi Maximize window Pencereyi maksimize et Reset Graph &Heights Grafik &Yüksekliklerini Sıfırla Reset sizes of graphs Grafiklerin boyutlarını sıfırla Show Right Sidebar Sağ Kenar Çubuğunu Göster Show Statistics view İstatistik görünümünü Göster Import &Dreem Data &Dreem Verisini İçe Aktar Purge Current Selected Day Seçili Günü Temizle &CPAP &CPAP &Oximetry &Oksimetri &Sleep Stage &Uyku Evresi &Position &Pozisyon &All except Notes Notlar hariç &Tümü All including &Notes &Notlar dahil Tümü Show &Line Cursor &Çizgi Kürsörünü Göster Show Daily Left Sidebar Günlük Sol Kenar Çubuğunu Göster Show Daily Calendar Günlük Takvimi Göster Create zip of CPAP data card CPAP veri kartının zip dosyasını yarat Create zip of OSCAR diagnostic logs OSCAR'ın teşhis günlüklerinin zip dosyasını oluştur Create zip of all OSCAR data Tüm OSCAR verisinin zip dosyasını yarat Report an Issue Sorun Bildir System Information Sistem Bilgisi Show &Pie Chart &Dilim Grafiğini Göster Show Pie Chart on Daily page Günlük sayfada Dilim Grafiğini Göster Standard graph order, good for CPAP, APAP, Bi-Level Standart grafik dizilimi; CPAP, APAP,Bi-Level için iyi Advanced Gelişmiş Advanced graph order, good for ASV, AVAPS Gelişmiş grafik sıralaması, ASV, AVAPS için uygun Show Personal Data Kişisel Verileri Göster Check For &Updates &Güncellemeleri Kontrol Et &Preferences &Seçenekler &Profiles &Profiller &About OSCAR &OSCAR Hakkında Show Performance Information Performans Bilgisini Göster CSV Export Wizard CSV Dışa Aktarım Sihirbazı Export for Review Gözden Geçirme için Dışarı Veri Aktar E&xit &Çıkış Exit Çıkış View &Daily &Günlük Görünüm View &Overview &Genel Bakış Görünümü View &Welcome &Karşılama Sayfasını Göster Use &AntiAliasing &Kenar Yumuşatma Kullan Show Debug Pane Hata Ayıklama Bölmesini Göster Take &Screenshot &Ekran Görüntüsü Al O&ximetry Wizard O&ksimetri Sihirbazı Print &Report &Rapor Yaz &Edit Profile &Profili Düzenle Import &Viatom/Wellue Data &Viatom/Wellue Verisini İçe Aktar Daily Calendar Günlük Takvim Backup &Journal &Günlüğü Yedekle Online Users &Guide Çevirim içi Kullanıcı&Rehberi &Frequently Asked Questions &Sıkça Sorulan Sorular &Automatic Oximetry Cleanup &Otomatik Oksimetri Temizliği Change &User &Kullanıcıyı Değiştir Purge &Current Selected Day &Seçili Günü Sil Right &Sidebar Sağ &Kenar Çubuğu Daily Sidebar Günlük Kenar Çubuğu View S&tatistics &İstatistikleri Görüntüle Navigation Navigasyon Bookmarks Yer İşaretleri Records Kayıtlar Exp&ort Data Dışa Veri &Aktar Profiles Profiller Purge Oximetry Data Oksimetri Verisini Sil View Statistics İstatistikleri Görüntüle Import &ZEO Data &ZEO Verisini İçe Aktar Import RemStar &MSeries Data RemStar &MSeries Verisini İçe Aktar Sleep Disorder Terms &Glossary Uyku Hastalıkları Terimleri &Sözlüğü Change &Language &Dili Değiştir Change &Data Folder &Veri Klasörünü Değiştir Import &Somnopose Data &Somnopose Verisini İçe Aktar Current Days Mevcut Günler Welcome Karşılama &About &Hakkında Please wait, importing from backup folder(s)... Lütfen bekleyin, yedekleme klasör(ler)'inden içe aktarılıyor... Import Problem İçe Aktarma Sorunu Please insert your CPAP data card... Lütfen CPAP veri kartınızı yerleştirin... Access to Import has been blocked while recalculations are in progress. Yeniden hesaplama sürmekte iken Yükle'ye ulaşım bloke edilmiştir. CPAP Data Located CPAP Verisi Bulundu Import Reminder İçe Aktarma Hatırlatıcısı Find your CPAP data card CPAP veri kartınızı bulun Importing Data Veri İçe Aktarılıyor Choose where to save screenshot Ekran görüntüsünün nereye kaydedileceğini seç Image files (*.png) Resim dosyaları(*.png) The User's Guide will open in your default browser Kullanım Kılavuzu varsayılan tarayıcınızda açılacaktır The FAQ is not yet implemented SSS henüz hazırlanmamıştır If you can read this, the restart command didn't work. You will have to do it yourself manually. Eğer bunu okuyorsanız yeniden başlatma komutu çalışmamış demektir. Manüel olarak kendinizin yapması gerkecek. Are you sure you want to rebuild all CPAP data for the following machine: Bu cihaz için tüm CPAP verisini yeniden inşa etmek istediğinize emin misiniz: For some reason, OSCAR does not have any backups for the following machine: Bir sebepten ötürü OSCAR'ın şu cihazlar için alınmış herhangi bir yedeklemesi mevcut değildir: OSCAR does not have any backups for this machine! OSCAR'ın bu cihaz için herhangi bir yedeklemesi yok! Unless you have made <i>your <b>own</b> backups for ALL of your data for this machine</i>, <font size=+2>you will lose this machine's data <b>permanently</b>!</font> <i>Bu cihazdaki verileriniz için <b>kendi</b> yedeklemelerinizi</i> yapmadıysanız <font size=+2> bu cihazın verisini <b>kalıcı olarak</b!> kaybedeceksiniz!</font> You are about to <font size=+2>obliterate</font> OSCAR's machine database for the following machine:</p> OSCAR'ın bu cihaz için olan veri tabanını <font size=+2> yok etmek</font> üzeresiniz:</p> A file permission error casued the purge process to fail; you will have to delete the following folder manually: Bir dosya izni hatası temizleme işleminin başarısızlıkla sonlanmasına neden oldu; bu klasörü manüel olarak silmeniz gerekecek: No help is available. Yardım mevcut değil. %1's Journal %1'in Günlüğü Choose where to save journal Günlüğün nereye kaydedileceğini seç XML Files (*.xml) XML Dosyaları (*.xml) Export review is not yet implemented Dışa aktarım için gözden geçirme henüz uygulamaya alınmamıştır Would you like to zip this card? Bu kartı sıkıştırmak ister misiniz? Choose where to save zip Zip dosyasının nereye kaydedileceğini seç ZIP files (*.zip) ZIP dosyaları (*.zip) Creating zip... Zip yaratılıyor... Calculating size... Boyut hesaplanıyor... Reporting issues is not yet implemented Sorun bildirimi henüz uygulamaya geçmemiştir OSCAR Information OSCAR Bilgisi Help Browser Yardım Tarayıcısı %1 (Profile: %2) %1 (Profil: %2) Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Lütfen veri kartınızın içindeki bir klasörü değil, kök dizinini veya sürücü harfini seçin. Please open a profile first. Lütfen öncelikle bir profil açın. Check for updates not implemented Güncelleme kontrolü henüz eklenmedi Provided you have made <i>your <b>own</b> backups for ALL of your CPAP data</i>, you can still complete this operation, but you will have to restore from your backups manually. <i>TÜM CPAP veriniz için <b>kendi</b> yedeklemelerinizi</i> yaptıysanız bu işlemi hala tamamlayabilirsiniz, ancak manüel olarak kendi yedeklemelerinizden geri yükleme yapmanız gerekecektir. Are you really sure you want to do this? Bunu yapmak istediğinizden gerçekten emin misiniz? Because there are no internal backups to rebuild from, you will have to restore from your own. Dahili yedekleme mevcut olmadığından, kendi yedeklemenizden geri yüklemeniz gerekecektir. Would you like to import from your own backups now? (you will have no data visible for this machine until you do) Kendi yedeklemelerinizden şimdi içe aktarma yapmak ister misiniz? (aktarım yapana kadar bu cihaz için görülebilir veriniz olmayacaktır) Note as a precaution, the backup folder will be left in place. Önlem olarak, yedekleme klasörü yerinde bırakılacaktır. Are you <b>absolutely sure</b> you want to proceed? Devam etmek istediğinizden <b>kesinlikle emin</b> misiniz? The Glossary will open in your default browser Sözlük varsayılan tarayıcınızda açılacaktır There was a problem opening %1 Data File: %2 %1 Veri Dosyası açılırken bir sorun ile karşılaşıldı: %2 %1 Data Import of %2 file(s) complete %2 dosya(lar)ın %1 Veri İçe aktarımı tamamlandı %1 Import Partial Success %1 İçe Aktarımı Kısmen Başarılı Oldu %1 Data Import complete %1 Veri İçe Aktarımı tamamlandı Are you sure you want to delete oximetry data for %1 %1 için oksimetri verisini silmek istediğinize emin misiniz <b>Please be aware you can not undo this operation!</b> <b>Lütfen dikkat, bu işlem geri alınamaz!</b> Select the day with valid oximetry data in daily view first. Öncelikle günlük görünümden geçerli oksimetri verisi olan günü seçiniz. Loading profile "%1" "%1" profili yükleniyor Imported %1 CPAP session(s) from %2 %2'den %1 CPAP seansı içe aktarıldı Import Success İçe Aktarma Başarılı Already up to date with CPAP data at %1 %1'deki CPAP verisi zaten güncel Up to date Güncel Couldn't find any valid Machine Data at %1 %1'de herhangi bir geçerli Cihaz Verisi bulunamadı Choose a folder Bir klasör seç No profile has been selected for Import. İçe Aktarım için bir profil seçilmedi. Import is already running in the background. İçe aktarma zaten arka planda çalışıyor. A %1 file structure for a %2 was located at: %2 ile uyumlu bir %1 dosya yapısı şu konumda tespit edildi: A %1 file structure was located at: Şurada %1'e uyan bir dosya yapısı tespit edildi: Would you like to import from this location? Bu konumdan içe aktarma yapmak ister misiniz? Specify Belirt Access to Preferences has been blocked until recalculation completes. Yeniden hesaplama sonlanana kadar Seçenekler'e ulaşım bloke edilmiştir. There was an error saving screenshot to file "%1" Ekran görüntüsü "%1" dosyasına kaydedilirken bir hata oluştu Screenshot saved to file "%1" Ekran görüntüsü "%1" dosyasına kaydedildi Please note, that this could result in loss of data if OSCAR's backups have been disabled. OSCAR'ın yedeklemeleri devre dışı bırakılmış ise bunun veri kaybına neden olabileceğine lütfen dikkat edin. There was a problem opening MSeries block File: MSeries blok dosyası açılırken bir sorun meydana geldi: MSeries Import complete MSeries İçe Aktarması tamamlandı MinMaxWidget Auto-Fit Otomatik-Sığdır Defaults Varsayılanlar Override Geçersiz kıl The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own. Y-Aksı ölçekleme modu, Otomatik ölçekleme için "Otomatik Sığdır", üretici ayarları için 'Varsayılanlar', ve kendiniz seçmek için 'Geçersiz kıl'. The Minimum Y-Axis value.. Note this can be a negative number if you wish. Minimum Y Aksı değeri.. Bu sayı dilerseniz negatif bir değer alabilir. The Maximum Y-Axis value.. Must be greater than Minimum to work. Y Aksının Maksimum değeri.. Minimum'dan büyük olmalıdır. Scaling Mode Ölçekleme Modu This button resets the Min and Max to match the Auto-Fit Bu düğme Min ve Maks'ı Otomatik-Sığdır ile eşleşecek şekilde sıfırlar NewProfile Edit User Profile Kullanıcı Profilini Düzenle I agree to all the conditions above. Yukarıdaki tüm koşulları kabul ediyorum. User Information Kullanıcı Bilgisi User Name Kullanıcı Adı Password Protect Profile Profili Şifreyle Koru Password Şifre ...twice... ...iki kez... Locale Settings Yerel Ayarlar Country Ülke TimeZone SaatDilimi about:blank hakkında:boş Very weak password protection and not recommended if security is required. Şifre koruması çok zayıf olup eğer güvenliğe ihtiyaç duyuluyorsa kullanılması tavsiye edilmez. DST Zone Yaz Saati Bölgesi Personal Information (for reports) Kişisel Bilgiler (raporlar için) First Name Ad Last Name Soyad It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. Bu soruyu atlamanız veya yanlış bir cevap vermeniz sorun olmamakla birlikte, bazı hespların doğruluk oranını arttırmak için kabaca yaşınız gereklidir. D.O.B. D.T. <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> <html><head/><body><p>Biyolojik (doğumsal) cinsiyete bazı hesaplamaların doğruluk oranını arttırmak için belirli durumlarda ihtiyaç olabilir, bunlardan herhangi birini veya tümünü boş bırakmakta veya atlamakta serbestsiniz.</p></body></html> Gender Cinsiyet Male Erkek Female Kadın Height Boy Metric Metrik English İngiliz Contact Information İletişim Bilgileri Address Adres Email E-posta Phone Telefon CPAP Treatment Information CPAP Tedavi Bilgisi Date Diagnosed Tanının Konulduğu Tarih Untreated AHI Tedavisiz AHI CPAP Mode CPAP Modu CPAP CPAP APAP APAP Bi-Level Bi-Level ASV ASV RX Pressure Reçetelenmiş Basınç Doctors / Clinic Information Doktorlar/ Klinik Bilgileri Doctors Name Doktor İsmi Practice Name Muayenehane İsmi Patient ID Hasta Kimliği &Cancel &İptal &Back &Geri &Next İ&leri Select Country Ülke Seçimi Welcome to the Open Source CPAP Analysis Reporter Açık Kaynak Kodlu CPAP Analiz Raporlayıcısına Hoş Geldiniz This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment. Bu yazılım, CPAP cihazlarınız ve ilgili ekipmanlarınız tarafından üretilen verileri gözden geçirmenize yardımcı olmak için tasarlanmıştır. PLEASE READ CAREFULLY LÜTFEN DİKKATLE OKUYUN Accuracy of any data displayed is not and can not be guaranteed. Görüntülenen verilerin doğruluğu garantilenmiş değildir ve garantilenemez. Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes. Oluşturulan raporların hepsi KİŞİSEL KULLANIM İÇİNDİR, ve tedavi uyumu ispatı için veya tıbbi tanı amaçlı olarak kullanıma HİÇBİR ŞEKİLDE uygun değildir. Use of this software is entirely at your own risk. Bu yazılımın kullanımı tamamen sizin sorumluluğunuzdadır. OSCAR is copyright &copy;2011-2018 Mark Watkins and portions &copy;2019-2022 The OSCAR Team OSCAR'ın telif hakkı &copy;2011-2018 Mark Watkins'e ve bazı kısımları &copy;2019-2022 OSCAR Takımı'ına aittir OSCAR has been released freely under the <a href='qrc:/COPYING'>GNU Public License v3</a>, and comes with no warranty, and without ANY claims to fitness for any purpose. OSCAR <a href='qrc:/COPYING'>GNU Genel Lisansı v3</a> altında serbest bir şekilde yayınlanmış olup hiçbir garanti içermemekte ve herhangi bir amaca uygunluğu idda edilmemektedir. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. OSCAR sadece bir veri görüntüleyici olarak tasarlanmıştır ve kesinlikle doktorunuzun sağlayacağı tıbbi rehberliğinin yerini alamaz. The authors will not be held liable for <u>anything</u> related to the use or misuse of this software. Yazarlar bu yazılımın kullanımına veya kötüye kullanımına dair <u>hiçbir</u> şeyden sorumlu tutulamaz. Please provide a username for this profile Lütfen bu profil için bir kullanıcı adı girin Passwords don't match Şifreler uyuşmuyor Profile Changes Profil Değişiklikleri Accept and save this information? Bu bilgiyi kabul edip kaydetmek istiyor musunuz? &Finish &Bitir &Close this window Bu pencereyi &kapat Overview Range: Aralık: Last Week Geçen Hafta Last Two Weeks Son İki Hafta Last Month Geçen Ay Last Two Months Son İki Ay Last Three Months Son Üç Ay Last 6 Months Son 6 Ay Last Year Geçen Yıl Everything Herşey Custom Özelleştirilmiş Snapshot Anlık Görüntü Start: Başlangıç: End: Bitiş: Reset view to selected date range Görünümü seçili tarih aralığına sıfırla Toggle Graph Visibility Grafik Görülebilirliğini Değiştir Drop down to see list of graphs to switch on/off. Açılıp kapatılabilecek grafiklerin listesini gösteren açılır liste. Graphs Grafikler Respiratory Disturbance Index Solunum Bozukluğu Endeksi Apnea Hypopnea Index Apne Hipopne İndeksi Usage Kullanım Usage (hours) Kullanım (saat) Session Times Seans Süreleri Total Time in Apnea Apnede Geçirilen Toplam Süre Total Time in Apnea (Minutes) Apnede Geçirilen Toplam Süre (Dakika) Body Mass Index Vücut Kitle İndeksi How you felt (0-10) Nasıl hissettiniz (0-10) 10 of 10 Charts 10 Tablodan 10'u Show all graphs Tüm grafikleri göster Hide all graphs Tüm grafikleri gizle OximeterImport Oximeter Import Wizard Okismetre İçe Aktarım Sihirbazı Skip this page next time. Bir dahaki sefere bu sayfayı atla. <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Lütfrn dikkat: </span><span style=" font-style:italic;">Öncelikle aşağıda yer alan açılır menüyü kullanarak doğru oksimetre tipinizi seçin.</span></p></body></html> Where would you like to import from? Nereden içe aktarım yapmak istersiniz? <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">ÖNCELİKLE bu gruplardan Oksimetrenizi seçin:</span></p></body></html> CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. CMS50E/F kullanıcıları, doğrudan içe aktarım gerçekleştirirken, lütfen OSCAR size belirtene kadar cihazınızda yükleme seçeneğini seçmeyin. <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> <html><head/><body><p>Eğer etkinleştirilmiş ise, OSCAR otomatik olarak CMS50'nizin dahili saatini bilgisayarınızın güncel saatine ayarlayacaktır.</p></body></html> <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> <html><head/><body><p>Buraya oksimetreniz için 7 karakterden oluşan bir isim girebilirsiniz.</p></body></html> <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> <html><head/><body><p>Bu seçenek, içe aktarım sona erdikten sonra, içe aktarılan seansı oksimetrenizden silecektir. </p><p>Kullanırken dikkatli olun, çünkü OSCAR seansınızı kaydetmeden önce bir hata oluşursa bir daha geri alamazsınız.</p></body></html> <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> <html><head/><body><p>Bu seçenek oksimetrenizin içinde yer alan kayıtları (kablo aracılığıyla) içe aktarmanızı sağlar.</p><p>Bu seçeneği seçtikten sonra, eski Contec oksimetrelerinde yüklemeyi başlatmanızı için cihazın menüsünü kullanmanız gerekmektedir.</p></body></html> <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> <html><head/><body><p>Eğer bütün bir gece boyunca çalışmakta olan bir bilgisayara bağlı kalmak sizin için sorun değilse, bu seçenek normal oksimetri okumalarına ek olarak kalp ritmi ile ilgili faydalı bir pletismogram grafiği sunmaktadır.</p></body></html> Record attached to computer overnight (provides plethysomogram) Bilgisayara bağlı olarak gece boyunca kaydet (pletismografi sağlar) <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> <html><head/><body><p>Bu seçenek, Nabız Oksimetrenizle birlikte gelen SpO2Review gibi bir yazılım tarafından oluşturulan veri dosyalarından içe aktarmanıza olanak tanır.</p></body></html> Import from a datafile saved by another program, like SpO2Review SpO2Review gibi başka bir program tarafından kaydedilmiş bir veri dosyasını içe aktar Please connect your oximeter device Lütfen oksimetre cihazınızı bağlayın If you can read this, you likely have your oximeter type set wrong in preferences. Eğer bunu okuyorsanız, muhtemelen seçenekler bölümünde oksimetrenizin tipini yanlış ayarladınız. Please connect your oximeter device, turn it on, and enter the menu Lütfen oksimetre cihazınızı bağlayın, çalıştırın, ve menüye girin Press Start to commence recording Kaydetmeye başlamak için Başlat'a basın Show Live Graphs Canlı Grafikleri Göster Duration Süre Pulse Rate Nabız Hızı Multiple Sessions Detected Birden Fazla Seans Tespit Edildi Start Time Başlangıç Zamanı Details Detaylar Import Completed. When did the recording start? İçe Aktarma Tamamlandı. Kayıt ne zaman başladı? Oximeter Starting time Oksimetre Başlangıç zamanı I want to use the time reported by my oximeter's built in clock. Oksimetremin dahili saati tarafından bildirilen zamanı kullanmak istiyorum. I started this oximeter recording at (or near) the same time as a session on my CPAP machine. Bu oksimetre kaydını CPAP cihazımdaki bir seans ile aynı (veya ona yakın) zamanda başlattım. <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> <html><head/><body><p>Not: CPAP seansı başlangıç zamanına senkronizasyon her zaman daha doğru olacaktır.</p></body></html> Choose CPAP session to sync to: Senkronize edilecek CPAP seansını seçin: You can manually adjust the time here if required: İhtiyaç halinde saati buradan manüel olarak ayarlayabilirsiniz: HH:mm:ssap SS:dd:ssap &Cancel &İptal &Information Page &Bilgi Sayfası CMS50D+/E/F, Pulox PO-200/300 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 ChoiceMMed MD300W1 Set device date/time Cihaz tarih/saatini ayarlayın <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> <html><head/><body><p>Elinde birden fazla oksimetre cihazı bulunanlar için bir sonraki içe aktarım sırasında cihaz tanımlayıcısının güncellenmesini etkinleştirmek için işaretlemek faydalı olabilir.</p></body></html> Set device identifier Cihaz tanımlayıcısını belirleyin Erase session after successful upload Yükleme başarılı olduktan sonra seansı sil Import directly from a recording on a device Cihazdaki bir kayıttan doğrudan içe aktar <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">CPAP kullanıcıları için hatırlatma: </span><span style=" color:#fb0000;">Öncelikle CPAP seansınızı içe aktarmayı unutmadınız, değil mi?<br/></span>Eğer unuttuysanız, bu oksimetre seansını senkronize etmek için geçerli bir zamanınız olmayacaktır.<br/> Cihazlar arasında iyi bir senkronizasyon sağlamak için her zaman ikisini de aynı anda başlatmaya çalışın.</p></body></html> Please choose which one you want to import into OSCAR Lüften hangisini OSCAR'a içe aktarmak istediğinizi seçin Day recording (normally would have) started Kayıda başlanmış (olması gereken) gün <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> <html><head/><body><p>OSCAR'ın bu oksimetri seansını kaydedebilmesi için bir başlangıç saatine ihtiyacı vardır.</p><p>Şu seçeneklerden birisini seçin:</p></body></html> &Retry &Tekrar Dene &Choose Session &Seansı Seç &End Recording Kaydı &Bitir &Sync and Save &Senkronize et ve Kaydet &Save and Finish &Kaydet ve Bitir &Start &Başlat Scanning for compatible oximeters Uyumlu oksimetreler aranıyor Could not detect any connected oximeter devices. Herhangi bir bağlı oksimetre cihazı tespit edilemedi. Connecting to %1 Oximeter %1 Oksimetreye Bağlanılıyor Renaming this oximeter from '%1' to '%2' Bu oksimetrenin adı '%1' den '%2'ye değiştiriliyor Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles. Oksimetrenin farklı bir adı var. Eğer sadece bir taneye sahipseniz ve onu farklı profiller arasında paylaşarak kullanıyorsanız, her iki profilde de aynı ismi kullanın. "%1", session %2 "%1", seans %2 Nothing to import İçe aktarılacak hiçbir şey yok Your oximeter did not have any valid sessions. Oksimetrenizde herhangi bir geçerli seans bulunamadı. Close Kapat Waiting for %1 to start %1'in başlaması bekleniyor Waiting for the device to start the upload process... Cihazın yükleme işlemine başlaması bekleniyor... Select upload option on %1 %1'de yükleme seçeneğini seçin You need to tell your oximeter to begin sending data to the computer. Oksimetrenize bilgisayara veri göndermesini belirtmeniz gerekiyor. Please connect your oximeter, enter it's menu and select upload to commence data transfer... Lütfen oksimetrenizi bağlayın, menüsüne girin ve veri transferini başlatmak için yükleyi seçin... %1 device is uploading data... %1 cihazı veri yüklemesi gerçekleştiriyor... Please wait until oximeter upload process completes. Do not unplug your oximeter. Lütfen oksimetrenin yükleme işlemi bitene kadar bekleyiniz. Oksimetrenizi çıkarmayın. Oximeter import completed.. Oksimetre aktarımı tamamlandı.. Select a valid oximetry data file Geçerli bir oksimetri verisi seçin Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat) Oksimetri Dosyaları (*.spo *.spor *.spo2 *.SpO2 *.dat) No Oximetry module could parse the given file: Oksimetri modüllerinden hiçbiri belirtilen dosyayı çözümleyemedi: Live Oximetry Mode Canlı Oksimetri Modu Live Oximetry Stopped Canlı Oksimetri Durduruldu Live Oximetry import has been stopped Canlı Oksimetri aktarımı durduruldu Oximeter Session %1 Oksimetri Seansı %1 OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data. OSCAR, CPAP seansı verileriyle birlikte Oksimetri verilerini de izleme olanağı sunar ve bu da CPAP tedavisinin etkinliği hakkında değerli bilgiler verebilir. Ayrıca Nabız Oksimetrenizle bağımsız olarak çalışarak kayıtlı verilerinizi saklamanıza, izlemenize ve gözden geçirmenize imkan verir. If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Oksimetre ve CPAP verilerini senkronize etmeye çalışıyorsanız, devam etmeden önce lütfen CPAP oturumlarınızı içe aktardığınızdan emin olun! For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2. OSCAR'ın doğrudan Oksimetre cihazınızı bulup okuyabilmesi için, doğru cihaz sürücülerinin (örn. USB'den Seri UART'a) bilgisayarınızda kurulu olduğundan emin olmanız gerekir. Bununla ilgili daha fazla bilgi için%1 burayı tıklayın%2. Oximeter not detected Oksimetre bulunamadı Couldn't access oximeter Oksimetreye erişilemedi Starting up... Başlatılıyor... If you can still read this after a few seconds, cancel and try again Eğer birkaç saniye geçtiği halde bu yazıyı hala okuyabiliyorsanız, iptal edip yeniden deneyin Live Import Stopped Canlı İçe Aktarım Durduruldu %1 session(s) on %2, starting at %3 %2'de %3 ile başlayan %1 seans(lar) No CPAP data available on %1 %1'de CPAP verisi mevcut değil Recording... Kaydediliyor... Finger not detected Parmek tespit edilemedi I want to use the time my computer recorded for this live oximetry session. Bu canlı oksimetri seansı için bilgisayarımın kaydettiği zamanı kullanmak istiyorum. I need to set the time manually, because my oximeter doesn't have an internal clock. Oksimetremin dahili saati mevcut olmadığından zamanı manüel olarak ayarlamam gerekiyor. Something went wrong getting session data Seans verileri alınırken bir şeyler ters gitti Welcome to the Oximeter Import Wizard Oksimetre İçe Aktarım Sihirbazına Hoş Geldiniz Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention. Puls Oksimetreleri kandaki oksijen satürasyonunu ölçmek için kullanılan tıbbi cihazlardır. Uzamış apne olayları esnasında ve anormal solunum patternlerinde, kan oksijen satürasyonu düzeyleri anlamlı bir şekilde düşebilir ve tıbbi müdahale gerektirebilecek problemlere işaret edebilir. OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.<br/>(Note: Direct importing from bluetooth models is <span style=" font-weight:600;">probably not</span> possible yet) OSCAR şu an için Contec CMS50D+, CMS50E, CMS50F ve CMS50I seri oksimetreleri ile uyumludur<br/>(Not: Bluetooth'lu modellerden doğrudan aktarım henüz <span style=" font-weight:600;">olası olmayabilir</span>) You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work. Pulox gibi diğer bazı şirketlerin, Contec CMS50'leri Pulox PO-200, PO-300, PO-400 gibi farklı marka isimleri altında satışa sunduklarını bilmeniz faydalı olabilir. Bunların da çalışabilmeleri beklenir. It also can read from ChoiceMMed MD300W1 oximeter .dat files. ChoiceMMed MD300W1 oksimetresi .dat dosyalarından da okuyabilir. Please remember: Lütfen unutmayın: Important Notes: Önemli Notlar: Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed. Contec CMS50D+ cihazlarında dahili bir saat mevcut değildir ve başlangıç zamanını kaydetmezler. Bir kaydı ilişkilendirebileceğiniz bir CPAP oturumunuz yoksa, içe alma işlemi tamamlandıktan sonra başlangıç zamanını manuel olarak girmeniz gerekir. Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily. Dahili saati olan cihazlar için bile, CPAP seanslarıyla aynı zamanda oksimetre kayıtlarını başlatma alışkanlığı edinmeniz önerilir, çünkü CPAP dahili saatleri zamanla kaymaya eğilimlidir ve hepsi kolayca sıfırlanamaz. Oximetry Date Tarih d/MM/yy h:mm:ss AP g/AA/yy:st:dk:dn AP R&eset R&eset Pulse Nabız &Open .spo/R File .spo/R Dosyası &Aç Serial &Import Seri İçe &Aktarma &Start Live Canlı &Başlat Serial Port Seri Port &Rescan Ports &Rescan Portları PreferencesDialog Preferences Seçenekler &Import &İçe Aktar Combine Close Sessions Birbirine Yakın Seansları Birleştir Minutes Dakika Multiple sessions closer together than this value will be kept on the same day. Birbirlerine bu değerden daha yakın olan birden fazla seans aynı gün içerisinde tutulacaktır. Ignore Short Sessions Kısa Seansları Yoksay <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Bu süreden daha kısa olan seanslar gösterilmeyecektir<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> Day Split Time Gün Ayırım Zamanı Sessions starting before this time will go to the previous calendar day. Bu saatten önce başlayan seanslar bir önceki takvim gününe gidecektir. Session Storage Options Seans Depolama Seçenekleri Compress SD Card Backups (slower first import, but makes backups smaller) SD Kart Yedeklerini Sıkıştır (ilk içe alma daha yavaş olacaktır, ancak yedekleri daha küçük hale getirir) &CPAP &CPAP Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Bu miktarın altında kullanım süresi olan günleri "uyumsuz" olarak kabul et. Genellikle 4 saat uyumlu olarak kabul edilir. hours saat Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Deneysel olay işaretleme geliştirmelerini etkinleştirin / devre dışı bırakın. Sınırda yer alan olayların ve cihazın kaçırdığı bazı olayların algılanmasını sağlar. İçe aktarmadan önce bu seçeneğin etkinleştirilmesi gerekir, aksi takdirde temizleme gereklidir. Flow Restriction Akım Kısıtlaması Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Hava akışının medyan değere göre kısıtlanma yüzdesi. Apne tespiti için genellikle 20% değeri işe yarar. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Özel işaretleme, makine tarafından kaçırılan olayları tespit etmek için deneysel bir yöntemdir. AHI içerisinde <span style=" text-decoration: underline;">yer almazlar.</span></p></body></html> Duration of airflow restriction Hava akımı kısıtlanma süresi s s Event Duration Olay Süresi Allow duplicates near machine events. Cihaz olaylarına yakın kopyalara izin ver. Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. AHI/Saat grafiğinde her nokta için göz önünde bulundurulan veri miktarını ayarlar. Varsayılan değer 60 dakikadır .. Bu değerde bırakılması kesinlikle önerilir. minutes dakika Reset the counter to zero at beginning of each (time) window. Her (zaman) penceresi başlangıcında sayacı sıfırla. Zero Reset Sıfırlama CPAP Clock Drift CPAP Saat Kayması Do not import sessions older than: Bu tarihten daha eski seansları içe aktarma: Sessions older than this date will not be imported Bu tarihten daha eski olan seanslar içe aktarılmayacak dd MMMM yyyy gg AAAA yyyy User definable threshold considered large leak Büyük kaçak tanımı için kullanıcı tarafından belirlenebilen eşik Whether to show the leak redline in the leak graph Kaçak grafiğinde kaçak kırmızı çizgisinin gösterilip gösterilmeyeceği Search Ara &Oximetry &Oksimetri Show in Event Breakdown Piechart Olay Dökümü Dilim Grafiğinde Göster Resync Machine Detected Events (Experimental) Cihaz Tarafından Tespit Edilen Olayları Tekrar Senkronize Et (Deneysel) Percentage drop in oxygen saturation Oksijen satürasyonunda düşüş yüzdesi Pulse Nabız Sudden change in Pulse Rate of at least this amount Nabız Hızında izlenen en az bu miktardaki ani değişiklik bpm vuru/dakika Minimum duration of drop in oxygen saturation Oksijen satürasyonunda minimum düşüş süresi Minimum duration of pulse change event. Nabız değişikliği olayının minimum süresi. Small chunks of oximetry data under this amount will be discarded. Bu değerin altındaki küçük oksimetri veri parçaları atılacaktır. &General &Genel Changes to the following settings needs a restart, but not a recalc. Aşağıdaki ayarlarda yapılan değişiklikler programın yeniden başlatılmasını gerektirir, ancak yeniden hesaplanma gerekmez. Preferred Calculation Methods Tercih Edilen Hesaplama Yöntemleri Middle Calculations Orta Hesapları Upper Percentile Üst Yüzdelik Session Splitting Settings Seans Bölme Ayarları <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Bu ayar dikkatli bir şekilde kullanılmalıdır...</span>Kapatılması, sadece özet bilgi olan günlerin doğruluk oranları ile ilgili istenmeyen sonuçlar doğurabilir, çünkü bazı hesaplar sadece özet bilgi olan günlerin kayıtlarının birlikte tutulumaları halinde doğru bir şekilde çalışmaktadır. </p><p><span style=" font-weight:600;">ResMed kullanıcıları:</span> Size ve bana öğlen 12 de tekrar başlayan seansın bir önceki günde yer alması doğal gelse de, bu durum ResMed verisinin bizimle aynı görüşte olduğu anlamına gelmez. STF.edf özet dizini formatı ciddi zayıflıklara sahip olduğundan bunu yapmak iyi bir fikir değildir.</p><p>Bu seçenek bunu umursamayanları sakinleştirmek ve bedeli ne olursa olsun &quot;düzeltmek&quot; isteyenler içindir ancak bir bedeli olduğunu bilin.Eğer SD kartınızı her gece içerde tutuyor ve haftada en az bir kez içe aktarım gerçekleştiriyorsanız, bununla ilgili çok fazla sorunla karşılaşmayacaksınız.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Özet Günlerini Bölme (Dikkat: ipucu kutucuğunu okuyun!) Memory and Startup Options Hafıza ve Başlangıç Seçenekleri Pre-Load all summary data at startup Başlangıçta tüm özet veriyi ön-yükle <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> <html><head/><body><p>Bu seçenek dalga formu ve olay verisini hafızada tutarak aynı günlere tekrar geri gelişi hızlandırır.</p><p>İşletim sisteminiz daha önceden kullanılan dosyaları ön belleğe aldığından, çok da gerekli bir seçenek değildir.</p><p>Önerimiz, bilgisayarınızın tonla hafızası yoksa, kapalı tutmanızdır.</p></body></html> Keep Waveform/Event data in memory Dalgaformu/Olay verisini hafızada tut <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> <html><head/><body><p>İçe aktarım esnasında önemsiz onay pencerelerinin sayısını azaltır.</p></body></html> Import without asking for confirmation Onay istemeden içe aktar This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Bu ölçüm CPAP cihazı tarafından Toplam Kaçak verisinin sağlanmasını gerektirir. (Ör, PRS1; ama ResMed'de bunlar zaten mevcut) Burada kullanılan İstemsiz Kaçak hesaplamaları doğrusaldır, maske hava salınım eğrisini modellemezler. Birden fazla değişik maske kullanıyorsanız,bunun yerine ortalama değerler seçin. Yeterince yakın olacaktır. Calculate Unintentional Leaks When Not Present Mevcut Değilse İstemsiz Kaçakları Hesapla Note: A linear calculation method is used. Changing these values requires a recalculation. Not: Doğrusal bir hesaplama metodu kullanılmaktadır. Bu değerleri değiştirmek tekrar bir hesaplama yapılmasını gerektirir. General CPAP and Related Settings Genel CPAP ve İlişkili Ayarları Enable Unknown Events Channels Bilinmeyen Olaylar Kanallarını Etkinleştir AHI Apnea Hypopnea Index AHI RDI Respiratory Disturbance Index RDI AHI/Hour Graph Time Window AHI/Saat Grafik Zaman Penceresi Preferred major event index Tercih edilen önemli olay dizini Compliance defined as Uyumun nasıl tarif edildiği Flag leaks over threshold Eşik üstü kaçakları işaretle Seconds Saniye <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> <html><head/><body><p>Not: Bu saat dilimi düzeltmeleri için tasarlanmamıştır! İşletim sisteminizin saat ve saat diliminin doğru ayarlandığından emin olun.</p></body></html> Hours Saat For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. Tutarlılık amacıyla, ResMed kullanıcıları burada 95%'i kullanmalıdır, çünkü bu değer sadece özet verisi olan günlerdeki mevcut olan tek değerdir. Median is recommended for ResMed users. ResMed kullanıcıları için median önerilir. Median Median Weighted Average Ağırlıklı Ortalama Normal Average Normal Ortalama True Maximum Gerçek Maksimum 99% Percentile 99% Persantil Maximum Calcs Maksimum Hesapl General Settings Genel Ayarlar Daily view navigation buttons will skip over days without data records Günlük görünüm gezinme düğmeleri, veri kayıtları olmayan günleri atlayacak Skip over Empty Days Boş Günleri Atla Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Eğer mevcutsa, performansı iyileştirmek için birden fazla işlemci çekirdeği kullanımına izin ver. Özellikle içe aktarıcıya etki eder. Enable Multithreading Multithreading'i Etkinleştir Bypass the login screen and load the most recent User Profile Giriş ekranını atlayın ve en son Kullanıcı Profilini yükleyin Create SD Card Backups during Import (Turn this off at your own peril!) İçe aktarım esnasıında SD Kartının Yedeğini Al (Kapatırsanız günahı boynunuza!) <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> <html><head/><body><p>Gerçek maksimum veri setinin maksimumudur.</p><p>99. persantil nadir istisnaları eler.</p></body></html> Combined Count divided by Total Hours Kombine Sayım bölü Toplam Saat Time Weighted average of Indice Indeksin Zaman Ağırlıklı ortalaması Standard average of indice İndeksin standart ortalaması Custom CPAP User Event Flagging Kişiselleştirilmiş CPAP Olay İşaretleyicisi Events Olaylar Reset &Defaults Sıfırla&Varsayılanlar <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Uyarı: </span>Yapabiliyor olmanız, iyi bir uygulama örneği olduğu anlamına gelmez.</p></body></html> Waveforms Dalga formları Flag rapid changes in oximetry stats Oksimetri istatistiklerindeki hızlı değişiklikleri işaretle Other oximetry options Diğer oksimetri seçenekleri Discard segments under Bu değerin altındaki segmentleri at Flag Pulse Rate Above Bu Değerin Üstündeki Nabız Hızlarını İşaretle Flag Pulse Rate Below Bu Değerin Altındaki Nabız Hızlarını İşaretle Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. ResMed (EDF) yedeklemelerini sıkıştırarak disk boşluğu kazan. Yedeklenmiş EDF dosyaları .gz formatında saklanmakta olup, bu format Mac & Linux platformlarında sık kullanılır.. OSCAR bu sıkıştırılmış yedekleme dizininden içe aktarım yapabilir.. ResScan ile kullanılabilmeleri için öncelikle .gz dosyalarının açılmaları gerekir.. The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. Bu seçenek OSCAR'ın kullandığı disk boşluğu miktarını etkiler, ve içe aktarmaların ne kadar uzun süreceğine de etkisi mevcuttur. This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Bu seçim OSCAR'ın verisinin kapladığı alanı yarı yarıya düşürür. Ancak içe aktarmayı ve gün değiştirmeyi yavaşlatır.. Eğer küçük bir SSD (solid state disk) içeren yeni bir bilgisayarınız varsa, bu iyi bir seçenektir. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Seans Verisini Sıkıştır (OSCAR'ın verisini küçültür, ancak gün değiştirme yavaşlar.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Bu seçim ResMed cihazları için SD kartının bir yedeğini saklar, ResMed S9 serisi cihazlar 7 günden eski yüksek çözünürlülüklü veriyi, ve 30 günden eski grafik verisini silerler.. OSCAR, eğer tekrar kurulum gerçekleştirmeye ihtiyacınız olursa, bu verinin bir kopyasını saklayabilir. (Boş disk yerinizin kısıtlı olması veya grafik verilerini önemsememeniz haricinde şiddetle tavsiye edilir) <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> <html><head/><body><p>Özet verilerini önceden yükleyerek OSCAR'ın başlatılmasını biraz yavaşlatır, ancak daha sonra genel görünüm gezinmesini ve başka birkaç hesaplamayı hızlandırır.</p><p> Eğer sahip olduğunuz veri miktarı fazlaysa, bu seçeneği kapalı tutmak mantıklı olabilir, ancak genel görünümde <span style=" font-style:italic;">herşeyi</span> görmek istiyorsanız, her halukarda bu verinin yüklenmesi gerekecektir.</p><p>Bu seçeneğin, her zaman için sadece talep olduğunda yüklenen dalga formu ve olay verisini etkilemediğini unutmayın.</p></body></html> 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Bu deneysel seçenek OSCAR'ın olay işaretleme sistemini kullanarak cihaz tarafından tespit edilen olayların konumlarını iyileştirmeye çalışır. Show flags for machine detected events that haven't been identified yet. Cihaz tarafından tespit edilen henüz tanımlanamamış olayları işaretle. Show Remove Card reminder notification on OSCAR shutdown OSCAR kapatılırken Kartı Çıkar hatırlatıcısını göster Check for new version every Yeni sürüm varlığını bu aralıkla kontrol et days. gün. Last Checked For Updates: Güncellemelerin En Son Kontrol Edildiği Tarih: TextLabel YazıEtiketi I want to be notified of test versions. (Advanced users only please.) Test sürümlerinden haberdar olmak istiyorum. (Lütfen sadece ileri kullanıcılar.) &Appearance &Görünüm Graph Settings Grafik Ayarları <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> <html><head/><body><p>Bir profil yüklendiğinde hangi sekmenin açılacağı. (Not: Eğer OSCAR başlangıçta bir profil yüklemeyecek şekilde ayarlanmışsa, Profil varsayılan ayardır)</p></body></html> Bar Tops Bar Üstleri Line Chart Çizgi Grafik Overview Linecharts Çizgi Grafikleri Gözden Geçir Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. OSCAR'ın grafiklerinde görselleştirme sorunları yaşıyorsanız bunu varsayılan ayardan (Desktop OpenGL) değiştirmeyi deneyin. <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> <html><head/><body><p>Bu seçenek,duyarlı çiftyönlü dokunmatik yüzeylerde görüntü büyütülmüş iken kaydırmayı kolaylaştırır</p><p>50 ms tavsiye edilen değerdir.</p></body></html> How long you want the tooltips to stay visible. Yardım kutularının görünür halde kalmalarını istediğiniz süre. Scroll Dampening Kaydırma Sönümlemesi Tooltip Timeout Yardım Kutusu Zaman Aşımı Default display height of graphs in pixels Grafiklerin piksel cinsinden varsayılan gösterim yükseklikleri Graph Tooltips Grafik Yardım Kutuları The visual method of displaying waveform overlay flags. Dalgaformu işaretleyicilerinin görsel olarak gösterim yöntemi. Standard Bars Standart Çubuklar Top Markers Üst İşaretleyiciler Graph Height Grafik Yüksekliği Changing SD Backup compression options doesn't automatically recompress backup data. SD Yedekleme sıkıştırma seçeneklerinin değiştirilmesi, yedeklenmiş verinin otomatik olarak sıkıştırılmasına neden olmaz. Auto-Launch CPAP Importer after opening profile Profili açtıktan sonra CPAP İçe Aktarıcısını Otomatik Olarak Çalıştır Automatically load last used profile on start-up Başlangıçta en son kullanılan kullanıcı profilini otomatik olarak yükle <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>OSCAR'ın geliştiricileri tarafından henüz test edilmemiş bir cihazdan veri içe aktarımı gerçekleştirirken uyarı ver.</p></body></html> Warn when importing data from an untested machine Test edilmemiş bir cihazdan veri içe aktarımı yaparken uyar <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>OSCAR'ın geliştiricilerinin şimdiye kadar gördüklerinden bir şekilde farklı bir veriyi içe aktarırken uyarı ver.</p></body></html> Warn when previously unseen data is encountered Daha önceden görülmemiş bir veri ile karşılaşılınca uyar Your masks vent rate at 20 cmH2O pressure Maskenizin 20 cmH20 basınçta hava tahliye miktarı Your masks vent rate at 4 cmH2O pressure Maskenizin 4 cmH20 basınçta hava tahliye miktarı <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Not: </span>Özet tasarımındaki sınırlamalar nedeniyle, ResMed makineleri bu ayarların değiştirilmesine izin vermemektedir.</p></body></html> Oximetry Settings Oksimetri Ayarları Always save screenshots in the OSCAR Data folder Ekran görüntülerini her zaman OSCAR Veri klasörüne kaydet Check For Updates Güncellemeleri Kontrol Et You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. OSCAR'ın bir deneme sürümünü kullanmaktasınız. Deneme sürümleri otomatik olarak yedi günde bir güncelleme kontrolü yaparlar. Bu aralığı yedi günden daha kısa bir süreye ayarlayabilirsiniz. Automatically check for updates Güncellemeleri otomatik olarak kontrol et How often OSCAR should check for updates. OSCAR'ın hangi sıklıkta güncellemeleri kontrol edeceği. If you are interested in helping test new features and bugfixes early, click here. Eğer yeni özellikleri ve hata ayıklamalarını test etmek ilginizi çekiyorsa buraya klikleyin. If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR Eğer OSCAR'ın erken deneme sürümlerinin test edilmesine yardımcı olmak istiyorsanız, OSCAR'ı test etmekle ilgili Wiki sayfasına bakınız. OSCAR'ı test etmek isteyen, OSCAR'ın geliştirilmesine yardımcı olmak ve mevcut veya yeni dillere çeviriler konusunda katkıda bulunmak isteyen herkesi aramıza davet ediyoruz.. https://www.sleepfiles.com/OSCAR On Opening Açılışta Profile Profil Welcome Hoş Geldiniz Daily Günlük Statistics İstatistikler Switch Tabs Sekmeleri Değiştir No change Değişiklik yok After Import İçe Aktarım Sonrası Overlay Flags İşaretleri Çakıştır Line Thickness Çizgi Kalınlığı The pixel thickness of line plots Çizgi grafiklerinin piksel kalınlığı Other Visual Settings Diğer Görsel Ayarlar Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Anti-Aliasing grafik çizimlerine yumuşatma uygular.. Bu seçenek açıkken bazı grafikler daha çekici görünür. Yazılı raporlara da etkisi mevcuttur. Deneyin ve beğenip beğenmediğinizi görün. Use Anti-Aliasing Anti-Aliasing Kullan Makes certain plots look more "square waved". Bazı grafikleri daha "köşeli" hale sokar. Square Wave Plots Kare Dalga Grafikleri Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Pixmap önbellekleme bir grafik hızlandırma tekniğidir. Platformunuzdaki grafik görüntüleme alanında yazı karakteri çizimi ile ilgili sorunlara neden olabilir. Use Pixmap Caching Pixmap Önbelleklemesi Kullan <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> <html><head/><body><p>Bu özellikler yakın bir zamanda budanmıştır. Daha sonra tekrar eklenecektir. </p></body></html> Animations && Fancy Stuff Animasyonlar && Süslemeler Whether to allow changing yAxis scales by double clicking on yAxis labels yAksı skalasını yAksı etiketlerine çift klikleyerek değiştirmeye izin verip vermeme Allow YAxis Scaling YAksı Ölçeklemesine İzin Ver Whether to include machine serial number on machine settings changes report Cihaz ayar değişiklikleri raporuna cihazın seri numarasını ekleyip eklememe Include Serial Number Seri Numarasını Ekle Graphics Engine (Requires Restart) Grafik Motoru (Tekrardan Başlatmayı Gerektirir) l/min l/dk <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Toplu Endeksler</p></body></html> <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> <html><head/><body><p>SpO'yu İşaretle<span style=" vertical-align:sub;">2</span> Desaturasyon Altı</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.84158pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Oksimetri ve CPAP verisi senkronize ediliyor</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">SPO2Review'dan (.spoR dosyalarından) veya seri aktarma yöntemiyle içe aktarılan CMS50 verileri, senkronize edebilmek için gerekli olan doğru zaman mührüne sahip </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">değildir</span> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Canlı gösterim modu (seri kablo kullanarak) CMS50 oksimetreleri ile doğru bir şekilde senkronizasyon sağlamak için bir yöntemdir ancak CPAP saatindeki kaymaya karşı etkisi yoktur.</span></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Eğer Oksimetrenizi kayıt modunu CPAP makineniz ile</span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">tam olarak </span><span style=" font-family:'Sans'; font-size:10pt;">aynı anda başlatırsanız da senkronizasyon sağlayabilirsiniz. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Seri içe alma işlemi başlangıç zamanını önceki gecenin ilk CPAP seansından alır. (Önce CPAP verisini içe aktarmayı unutmayın!)</span></p></body></html> Print reports in black and white, which can be more legible on non-color printers Raporları siyah beyaz yazdır, renkli olmayan yazıcılarda daha okunabilir olabilir Print reports in black and white (monochrome) Raporları siyah beyaz yazdır (monokrom) Fonts (Application wide settings) Yazı Karakterleri (Tüm uygulamada geçerli) Font Yazı Karakteri Size Boyut Bold Kalın Italic İtalik Application Uygulama Graph Text Grafik Yazısı Graph Titles Grafik Başlıkları Big Text Büyük Yazı Details Detaylar &Cancel &İptal &Ok &Ok Name İsim Color Renk Flag Type İşaret Tipi Label Etiket CPAP Events CPAP Olayları Oximeter Events Oksimetre Olayları Positional Events Pozisyonel Olaylar Sleep Stage Events Uyku Evresi Olayları Unknown Events Bilinmeyen Olaylar Double click to change the descriptive name this channel. Bu kanalın açıklayıcı adını değiştirmek için çift tıklayın. Double click to change the default color for this channel plot/flag/data. Bu kanalın varsayılan çizim/olay/veri rengini değiştirmek için çift tıklayın. Overview Genel Bakış <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.</p><p>On ResMed machines, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Lütfen Dikkat::</b>OSCARI'n gelişmiş seans bölme özellikleri ayarlarının ve özet verilerinin depolanma biçimindeki bir sınırlama nedeniyle <b>ResMed</b> cihazlarında kullanılamamaktadır, ve dolayısıyla bu profil için devre dışı bırakılmışlardır. </p><p>ResMed makinelerinde, günler, ResMed'in ticari yazılımında olduğu gibi, <b>öğleden itibaren bölünecektir.</p> Double click to change the descriptive name the '%1' channel. '%1' kanalının tanımlayıcı ismini değiştirmek için çift tıklayın. Whether this flag has a dedicated overview chart. Bu işaretin özel bir genel bakış çizelgesinin olup olmadığı. Here you can change the type of flag shown for this event Burada bu olay türü için gösterilen işaret tipini değiştirebilirsiniz This is the short-form label to indicate this channel on screen. Kanalı ekranda işaretlemeye yarayan kısa-form etiketi. This is a description of what this channel does. Bu kanalın ne yaptığını tarif eder. Lower Alt Upper Üst CPAP Waveforms CPAP Dalga formları Oximeter Waveforms Oksimetri Dalga Formları Positional Waveforms Konumsal Dalga Formları Sleep Stage Waveforms Uyku Evresi Dalga Formları Whether a breakdown of this waveform displays in overview. Bu dalga formunun bir dökümünün genel bakışta görüntülenip görüntülenmeyeceği. Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform Burada %1 dalga formunda yapılan bazı hesaplamalarda kullanılan <b>alt</b> eşik değerini ayarlayabilirsiniz Here you can set the <b>upper</b> threshold used for certain calculations on the %1 waveform Burada %1 dalga formunda yapılan bazı hesaplamalarda kullanılan <b>üst</b> eşik değerini ayarlayabilirsiniz Data Processing Required Veri İşlenmesine İhtiyaç Var A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Bu değişiklikleri uygulayabilmek için bir sıkıştırma/yeniden açma işlemi gerekmektedir. Bu işlemin tamamlanması birkaç dakika alabilir. Bu değişiklikleri yapmak istediğinize emin misiniz? Data Reindex Required Veri Tekrar Endekslemesi Gerekiyor A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete. Are you sure you want to make these changes? Bu değişiklikleri uygulayabilmek için tekrar bir veri endeksleme işlemi gerekmektedir. Bu işlemin tamamlanması birkaç dakika alabilir. Bu değişiklikleri yapmak istediğinize emin misiniz? Restart Required Tekrar Başlatılması Gerekiyor One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect. Would you like do this now? Yaptığınız bir veya daha fazla değişiklik, devreye girebilmeleri için yazılımın tekrar başlatılmasına ihtiyaç duyuyor. Bunu şimdi yapmak ister misiniz? If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Eğer ilerde bu veriyi tekrar içe almanız gerekirse (OSCAR veya ResScan'da), veri geri gelmeyecek. If you need to conserve disk space, please remember to carry out manual backups. Eğer disk boşluğuna ihtiyacınız varsa manüel olarak yedekleme yapmayı unutmayın. Are you sure you want to disable these backups? Yedeklemeleri devre dışı birakmak istediğinize emin misiniz? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Yedeklemeyi devre dışı bırakmak iyi bir fikir değildir, çünkü OSCAR hata bulunması durumunda veri tabanını tekrar inşa edebilmek için bunlara ihtiyaç duyar. Are you really sure you want to do this? Bunu yapmak istediğinizden gerçekten emin misiniz? %1 %2 %1 %2 Flag İşaret Minor Flag Minör İşaret Span Süre Always Minor Her zaman Küçük No CPAP machines detected CPAP cihazı tespit edilmedi Will you be using a ResMed brand machine? ResMed marka bir cihaz kullanacak mısınız? Never Asla This may not be a good idea Bu iyi bir fikir olmayabilir ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9 makineleri SD kartınızdan 7 ve 30 günden eski bazı verileri rutin olarak siler (çözünlürlüğe bağlı). ProfileSelector Filter: Filtre: Reset filter to see all profiles Tüm profilleri görebilmek için filtreyi sıfırlayın Version Versiyon &Open Profile Profili &Aç &Edit Profile Profili &Düzenle &New Profile &Yeni Profil Profile: None Profil: Yok Please select or create a profile... Bir profil seçin veya yaratın... Destroy Profile Profili Yok Et Profile Profil Ventilator Brand Ventilatör Markası Ventilator Model Ventilatör Modeli Other Data Başka Veriler Last Imported Son İçe Aktarılan Name İsim You must create a profile Bir profil yaratmalısınız Enter Password for %1 %1 için Şifre Giriniz You entered an incorrect password Hatalı bir şifre girdiniz Forgot your password? Şifrenizi unuttunuz mu? Ask on the forums how to reset it, it's actually pretty easy. Forumlarda nasıl sıfırlayabileceğinizi sorun, aslında oldukça basit. Select a profile first Öncelikle bir profil seçin The selected profile does not appear to contain any data and cannot be removed by OSCAR Seçili profil herhangi bir veri içermemekte olup OSCAR tarafından kaldırılamaz If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually. Eğer şifreyi unuttuğunuz için silmeye çalışıyorsanız, ya sıfırlamanız, ya profil klasörünü manüel olarak silmeniz gerekiyor. You are about to destroy profile '<b>%1</b>'. Profil '<b>%1</b>'yi yok etmek üzeresiniz. Think carefully, as this will irretrievably delete the profile along with all <b>backup data</b> stored under<br/>%2. Bu profili ve <br/>%2 altında saklanan tüm <b>yedekleme verisini</b> geri alınamayacak şekilde sileceğinden dikkatli bir şekilde düşünün. Enter the word <b>DELETE</b> below (exactly as shown) to confirm. Onaylamak için aşağıya <b>SİL</b> kelimesini (aynen gösterildiği gibi) yazın. DELETE SİL Sorry Üzgünüz You need to enter DELETE in capital letters. SİL kelimesini büyük harflerle yazmanız gerekiyor. There was an error deleting the profile directory, you need to manually remove it. Profil klasörü silinirken bir hata oluştu, manüel olarak kaldırmanız gerekiyor. Profile '%1' was succesfully deleted Profil '%1' başarılı bir şekilde silindi Bytes Bayt KB KB MB MB GB GB TB TB PB PB Summaries: Özetler: Events: Olaylar: Backups: Yedeklemeler: Hide disk usage information Disk kullanım bilgisini gizle Show disk usage information Disk kullanım bilgisini göster Name: %1, %2 İsim: %1, %2 Phone: %1 Telefon: %1 Email: <a href='mailto:%1'>%1</a> E-posta: <a href='mailto:%1'>%1</a> Address: Adres: No profile information given Profil bilgisi verilmedi Profile: %1 Profil: %1 ProgressDialog Abort İptal QObject No Data Veri Yok Events Olaylar Duration Süre (% %1 in events) (Olayların % %1'i) Jan Ock Feb Şbt Mar Mar Apr Nis May May Jun Haz Jul Tem Aug Ağu Sep Eyl Oct Eki Nov Kas Dec Ara ft ft lb lb oz oz Kg Kg cmH2O cmH2O Med. Med. Min: %1 Min: %1 Min: Min: Max: Maks: Max: %1 Maks: %1 %1 (%2 days): %1 (%2 gün): %1 (%2 day): %1 (%2 gün): % in %1 %1'de % Hours Saat Min %1 Min %1 Hours: %1 Saat: %1 %1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7 %3 günde %1 az kullanım, %2 hiç kullanılmama (%4% uyum) Süre: %5 / %6 / %7 Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9 Seans: %1 / %2 / %3 Süre: %4 / %5 / %6 En uzun: %7 / %8 / %9 %1 Length: %3 Start: %2 %1 Süre: %3 Başlangıç: %2 Mask On Maske Takılı Mask Off Maske Çıkmış %1 Length: %3 Start: %2 %1 Süre: %3 Başlangıç: %2 TTIA: TTIA: TTIA: %1 TTIA: %1 Minutes Dakika Seconds Saniye h st m dk s sn ms ms Events/hr Olay/st Hz Hz bpm bpm Litres Litre ml ml Breaths/min Soluk/dk ? ? Severity (0-1) Ciddiyet (0-1) Degrees Derece Error Hata Warning Uyarı Information Bilgi Busy Meşgul Please Note Lütfen Dikkat Graphs Switched Off Grafikler Kapatıldı Sessions Switched Off Seans Kapatıldı &Yes &Evet &No &Hayır &Cancel &İptal &Destroy &Yok et &Save &Kaydet BMI BMI Weight Ağırlık Zombie Zombi Pulse Rate Nabız Hızı Plethy Pletismogram Pressure Basınç Daily Günlük Profile Profil Overview Genel bakış Oximetry Oksimetri Oximeter Oksimetre Event Flags Olay İşaretçileri Default Varsayılan CPAP CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP Min EPAP Min EPAP Max EPAP Maks EPAP IPAP IPAP Min IPAP Min IPAP Max IPAP Maks IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier Nemlendirici H H OA OA A A CA CA FL FL SA SA LE LE EP EP VS VS VS2 VS2 RERA RERA PP PP P P RE RE NR NR NRI NRI O2 O2 PC PC UF1 UF1 UF2 UF2 UF3 UF3 PS PS AHI AHI RDI RDI AI AI HI HI UAI UAI CAI CAI FLI FLI REI REI EPI EPI PB PB IE IE Insp. Time Insp Süresi Exp. Time Eksp Süresi Resp. Event Slnm Olayı Flow Limitation Akım Kısıtlaması Flow Limit Akımı Kısıtlaması SensAwake SensAwake Pat. Trig. Breath Hast. Taraf. Tetik. Nefes Tgt. Min. Vent Hdf. Min. Vent Target Vent. Hedef Vent. Minute Vent. Dakika Başı Vent. Tidal Volume Tidal Volüm Resp. Rate Slnm. Hızı Snore Horlama Leak Kaçak Leaks Kaçaklar Large Leak Büyük Kaçak LL BK Total Leaks Toplam Kaçaklar Unintentional Leaks İstemsiz Kaçaklar MaskPressure MaskeBasıncı Flow Rate Akış Hızı Sleep Stage Uyku Evresi Usage Kullanım Sessions Seanslar Pr. Relief Bsnç. Tahliyesi No Data Available Veri Mevcut Değil App key: Yazılım anahtarı: Operating system: İşletim sistemi: Built with Qt %1 on %2 %2'de QT %1 ile geliştirilmiştir Graphics Engine: Grafik Motoru: Graphics Engine type: Grafik Motoru tipi: Software Engine Yazılım Motoru ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Masaüstü OpenGL m m cm cm in inç Only Settings and Compliance Data Available Sadece Ayarlar ve Uyum Verisi Mevcut Summary Data Only Sadece Özet Veri Bookmarks Yer İşaretleri Mode Mod Model Model Brand Marka Serial Seri Series Seriler Machine Cihaz Channel Kanal Settings Ayarlar Inclination Eğim Orientation Yönlenim Motion Hareket Name İsim DOB Doğum Tarihi Phone Telefon Address Adres Email E-posta Patient ID Hasta Kimliği Date Tarih Bedtime Yatış Zamanı Wake-up Uyanma Mask Time Maske Takma Süresi Unknown Bilinmeyen None Hiçbiri Ready Hazır First İlk Last Son Start Başlangıç End Son On Açık Off Kapalı Yes Evet No Hayır Min Min Max Maks Med Med Average Ortalama Median Median Avg Ort W-Avg Ağırlıklı-Ortalama Your %1 %2 (%3) generated data that OSCAR has never seen before. %1 %2 (%3) cihazınız OSCAR'ın daha önce hiç karşılaşmadığı bir veri üretti. The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. İçe aktarılan veriler tamamıyla doğru olmayabilir, dolayısıyla geliştiriciler OSCAR'ın bu veriyi doğru bir şekilde işlediğinden emin olmak için bu cihazın SD kartının ve buna denk gelen klinisyen tarafından üretilen pdf raporunun birer kopyasına ihtiyaç duymaktalar. Non Data Capable Machine Veri Özelliğine Sahip Olmayan Cihaz Your %1 CPAP machine (Model %2) is unfortunately not a data capable model. %1 CPAP cihazınız (Model %2) maalesef veri üretebilen bir model değildir. Your %1 CPAP machine (Model %2) has not been tested yet. %1 CPAP cihazınız (Model %2) henüz test edilmdi. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Diğer cihazlar ile benzerlik gösterdiği için çalışma ihtimali olmakla birlikte, geliştiriciler cihazın OSCAR ile kullanılabilir olduğundan emin olmak için bu cihazın SD kartının .zip'li bir kopyasına ve eşlik eden klinisyence üretilmiş pdf raporuna ihtiyaç duymaktadırlar. Sorry, your %1 CPAP machine (%2) is not supported yet. Üzgünüz, %1 CPAP cihazınız (%2) henüz desteklenmemektedir. The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR. Geliştiriciler bu cihazın OSCAR ile çalışabilir hale getirilebilmesi için, SD kartının .zip'li bir kopyasına ve eşlik eden klinisyence üretilmiş .pdf raporlarına ihtiyaç duymaktadırlar. Getting Ready... Hazırlanıyor... Machine Unsupported Cihaz Desteklenmiyor I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine. OSCAR'ın bu cihaz için sadece kullanım süresi ve bazı çok basit ayarları takip edebileceğini üzülerek bildiririz. Scanning Files... Dosyalar Taranıyor... Importing Sessions... Seanslar İçe Aktarılıyor... Finishing up... Bitiriliyor... Machine Untested Test Edilmemiş Cihaz Flex Lock Flex Kilidi Whether Flex settings are available to you. Flex seçeneklerinin sizin için mevcut olup olmadığı. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition EPAP'tan IPAP'a geçmek için gereken süre, rakam yükseldikçe geçiş daha yavaştır Rise Time Lock Yükselme Süresi (Rise Time) Kilidi Whether Rise Time settings are available to you. Yükselme Süresi (Rise Time) seçeneklerinin sizin için mevcut olup olmadığı. Rise Lock Yükselme Kilidi (Rise Lock) Mask Resistance Setting Maske Direnci Ayarlı Mask Resist. Mask. Direnc. Hose Diam. Hortum Çapı. 15mm 15mm 22mm 22mm Backing Up Files... Dosyalar Yedekleniyor... Untested Data Test Edilmemiş Veri model %1 model %1 DreamStation 2 DreamStation 2 unknown model bilinmeyen model CPAP-Check CPAP-Kontrolü AutoCPAP AutoCPAP Auto-Trial Oto-Deneme AutoBiLevel AutoBiLevel S S S/T S/T S/T - AVAPS S/T - AVAPS PC - AVAPS PC - AVAPS Flex Mode Flex Modu PRS1 pressure relief mode. PRS1 basınç tahliyesi modu. C-Flex C-Flex C-Flex+ C-Flex+ A-Flex A-Flex P-Flex P-Flex Rise Time Yükselme Süresi (Rise Time) Bi-Flex Bi-Flex Flex Flex Flex Level Flex Düzeyi PRS1 pressure relief setting. PRS1 basınç tahliyesi ayarı. Passover Üzerinden geçerek Target Time Hedef Süre PRS1 Humidifier Target Time PRS1 Nemlendirici Hedef Süresi Hum. Tgt Time Nml. Hdf Süresi Tubing Type Lock Hortum Tipi Kilidi Whether tubing type settings are available to you. Hortum tipi seçeneklerinin sizin için mevcut olup olmadığı. Tube Lock Hortum Kilidi Mask Resistance Lock Maske Direnci Kilidi Whether mask resistance settings are available to you. Maske direnci seçeneklerinin sizin için mevcut olup olmadığı. Mask Res. Lock Maske Dir. Kilidi Whether or not machine shows AHI via built-in display. Cihazın AHI'yi dahili ekranı üzerinden gösterip göstermediği. Ramp Type Rampa Tipi Type of ramp curve to use. Kullanılacak rampa eğrisinin tipi. Linear Lineer SmartRamp SmartRamp Ramp+ Rampa+ Backup Breath Mode Nefes Destek Modu The kind of backup breath rate in use: none (off), automatic, or fixed Kullanımda olan destek nefes sayısı: yok (kapalı), otomatik, veya sabit Breath Rate Nefes Hızı Fixed Sabit Fixed Backup Breath BPM Sabit Nefes Desteği BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Dakika başına minimum nefes sayısının (BPM) altında olması durumunda zamanlanmış bir nefesin başlatılacağı değer Breath BPM Nefes BPM Timed Inspiration Zamanlanmış Nefes Alma (Inspiration) The time that a timed breath will provide IPAP before transitioning to EPAP Zamanlanmış bir nefesin EPAP'a geçmeden önce sağlayacağı IPAP süresi Timed Insp. Zamanl.Nfs Alm.(Timed Insp). Auto-Trial Duration Otomatik-Deneme Süresi The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP Deneme sürecinde cihazın Auto-CPAP modunda kalacağı ve sonrasında CPAP moduna döneceği gün sayısı Auto-Trial Dur. Oto-Dnm Sür. EZ-Start EZ-Start Whether or not EZ-Start is enabled EZ-Start'ın etkin olup olmadığı Variable Breathing Değişken Solunum (Variable Breathing) UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend TEYİT EDİLMEMİŞ: Tepe inspiratuar (nefes alma) akış trendinden yüksek sapma gösteren dönemler ile karakterize değişken soluma (Variable Breathing) olasılığı Peak Flow Tepe Akımı Peak flow during a 2-minute interval 2 dakikalık bir aralıktaki tepe akımı Humidifier Status Nemlendiricinin Durumu PRS1 humidifier connected? PSR1 nemlendiricisi bağlı? Disconnected Bağlı değil Connected Bağlı Humidification Mode Nemlendime Modu PRS1 Humidification Mode PRS1 Nemlendirme Modu Humid. Mode Neml. Modu Fixed (Classic) Sabitlenmiş (Klasik) Adaptive (System One) Uyarlanabilir (Adaptive)(System One) Heated Tube Isıtmalı Hortum Tube Temperature Hortum Sıcaklığı PRS1 Heated Tube Temperature PRS1 Isıtmalı Hortum Sıcaklığı Tube Temp. Hort.Sıcakl. PRS1 Humidifier Setting PRS1 Nemlendirici Ayarları Hose Diameter Hortum Çapı Diameter of primary CPAP hose Primer CPAP hortumunun çapı 12mm 12mm Auto On Otomatik Açılma A few breaths automatically starts machine Birkaç kez nefes alıp verme ile cihaz otomatik olarak çalışmaya başlar Auto Off Otomatik Kapanma Machine automatically switches off Cihaz otomatik olarak kapanır Mask Alert Maske Uyarısı Whether or not machine allows Mask checking. Cihazın Maske kontrolüne izin verip vermediği. Show AHI AHI'yi göster Breathing Not Detected Solunum Tespit Edilemedi A period during a session where the machine could not detect flow. Seans esnasında cihazın akımı tesbit edemediği bir dönem. BND BND Timed Breath Zamanlanmış Nefes Machine Initiated Breath Cihaz Tarafından Başlatılan Nefes TB TB Windows User Windows Kullanıcısı Using Kullanan , found SleepyHead - , SleepyHead bulundu - You must run the OSCAR Migration Tool OSCAR Geçiş Aracı'nı çalıştırmalısınız <i>Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i> Daha önceki bir veri aktarımı sırasında bu yedekleme özelliğinin tercihlerde devre dışı bırakılmış olması durumu haricinde, eski cihaz verileriniz yeniden oluşturulmalıdır. </i> Launching Windows Explorer failed Windows Gezgini Başlatılamadı Could not find explorer.exe in path to launch Windows Explorer. Windows Gezgini'ni başlatmak için explorer.exe dosya yolu bulunamadı. OSCAR %1 needs to upgrade its database for %2 %3 %4 OSCAR %1'in veri tabanını %2 %3 %4 için yükseltmesi gerekmekte <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR bu amaçla kullanmak için cihazınızın veri kartının bir yedeğini tutmaktadır.</b> OSCAR does not yet have any automatic card backups stored for this device. OSCAR'ın henüz bu cihaz için kaydedilmiş otomatik kart yedeklemesi yok. This means you will need to import this machine data again afterwards from your own backups or data card. Bu, daha sonra kendi yedeklemelerinizden veya veri kartınızdan bu cihaz verilerini tekrar içe aktarmanız gerekeceği anlamına gelir. Important: Önemli: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Endişeniz varsa, çıkmak için Hayır'ı tıklayın, ve OSCAR'ı yeniden başlatmadan önce profilinizi manuel olarak yedekleyin. Are you ready to upgrade, so you can run the new version of OSCAR? OSCAR'ın yeni versiyonunu çalıştırabilmek için yükseltmeye hazır mısınız? Sorry, the purge operation failed, which means this version of OSCAR can't start. Maalesef, temizleme işlemi başarısız oldu, yani OSCAR'ın bu sürümü başlatılamıyor. Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these? Otomatik yedeklemeleri,OSCAR'ın yeni bir sürümünün ihtiyaç halinde bunlar kullanılarak yeniden oluşturulabilmesi için, açmak ister misiniz? OSCAR will now start the import wizard so you can reinstall your %1 data. OSCAR şimdi %1 verinizi tekrar kurabilmeniz için içe aktarma sihirbazını başlatacak. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: OSCAR şimdi kapanacak, ve sonra profilinizi manüel olarak yedeklemeniz için bilgisyarınızın dosya yöneticisini (yapabilirse) açacak: Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process. Dosya yöneticinizi kullanarak profil klasörünüzün bir kopyasını alın, sonrasında OSCAR'ı tekrar başlatın ve yükseltme işlemini tamamlayın. Machine Database Changes Cihaz Veritabanı Değişiklikleri Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Yükseltmeyi gerçekleştirdikten sonra bu profili daha önceki versiyonla <font size=+1>kullanamazsınız</font>. The machine data folder needs to be removed manually. Cihazın veri klasörünün manüel olarak silinmesi gereklidir. This folder currently resides at the following location: Bu klasör şu anda bu konumda yer almakta: Rebuilding from %1 Backup %1 Yedekleme kullanılarak tekrar oluşturuluyor Therapy Pressure Tedavi Basıncı Inspiratory Pressure Nefes Alma Basıncı Lower Inspiratory Pressure Daha düşük Nefes Alma Basıncı Higher Inspiratory Pressure Daha Yüksek Nefes Alma Basıncı Expiratory Pressure Nefes Verme Basıncı Lower Expiratory Pressure Daha Düşük Nefes Verme Basıncı Higher Expiratory Pressure Daha Yüksek Nefes Verme Basıncı Pressure Support Basınç Desteği (Pressure Support) PS Min PS Min Pressure Support Minimum Basınç Desteği Minimum Değeri PS Max PS Maks Pressure Support Maximum Basınç Desteği Maksimum Değeri Min Pressure Min Basınç Minimum Therapy Pressure Minimum Tedavi Basıncı Max Pressure Maks. Basınç Maximum Therapy Pressure Maksimum Tedavi Basıncı Ramp Time Rampa Süresi Ramp Delay Period Rampa Geciktirme Periyodu Ramp Pressure Rampa Basıncı Starting Ramp Pressure Başlangıç Rampa Basıncı Ramp Event Rampa Olayı Ramp Rampa An abnormal period of Cheyne Stokes Respiration Anormal bir Cheyne Stokes Solunumu devresi An apnea that couldn't be determined as Central or Obstructive. Santral veya Obstrüktif olarak tanımlanamayan bir apne. A restriction in breathing from normal, causing a flattening of the flow waveform. Akış dalga formunun düzleşmesine neden olan, nefeste anormal kısıtlanma. Vibratory Snore (VS2) Titreşimli Horlama (Vibratory Snore-VS2) A ResMed data item: Trigger Cycle Event Bir ResMed veri öğesi: Tetikleyici Döngü Olayı Mask On Time Maske Takılı Süre Time started according to str.edf str.edf'ye göre başlangıç zamanı Summary Only Sadece Özet An apnea where the airway is open Hava yolunun açık olduğu bir apne An apnea caused by airway obstruction Hava yolu tıkanması sebebiyle oluşan bir apne Hypopnea Hipopne A partially obstructed airway Kısmi olarak tıkanmış bir hava yolu Unclassified Apnea Sınıflandırılamayan Apne UA UA Vibratory Snore Titreşimli Horlama A vibratory snore Titreşimli bir horlama A vibratory snore as detcted by a System One machine System One cihazı tarafından tespit edilen titreşimli horlama Pressure Pulse Basınç Darbesi A pulse of pressure 'pinged' to detect a closed airway. Kapalı bir hava yolunu tespit etmek için 'yollanan' basınç darbesi. A large mask leak affecting machine performance. Cihazın performansını etkileyecek seviyede bir maske kaçağı. Non Responding Event Cevap Vermeyen Olay A type of respiratory event that won't respond to a pressure increase. Basınç artışına cevap vermeyen tipte bir solunumsal olay. Expiratory Puff Nefes Verici Üfleme Intellipap event where you breathe out your mouth. Ağızdan nefes verdiğiniz Intellipap olayı. SensAwake feature will reduce pressure when waking is detected. SensAwake özelliği uyandığınızı fark ettiğinde basıncı düşürür. User Flag #1 Kullanıcı İşareti #1 User Flag #2 Kullanıcı İşareti #2 User Flag #3 Kullanıcı İşareti #3 Heart rate in beats per minute Vuru bölü dakika (bpm) biriminden kalp hızı Blood-oxygen saturation percentage Kan oksijen satürasyonu yüzdesi Plethysomogram Pletismogram An optical Photo-plethysomogram showing heart rhythm Kalp ritmini gösteren optik bir foto-pletismogram Pulse Change Nabız Değişikliği A sudden (user definable) change in heart rate Kalp hızında ani (kullanıcı tarafından tarif edilebilen) değişiklik SpO2 Drop SpO2 Düşmesi A sudden (user definable) drop in blood oxygen saturation Kan oksijen satürasyonunda ani (kullanıcı tarafından tarif edilebilen) düşme SD SD Breathing flow rate waveform Nefes alma akım hızı dalga formu L/min L/dk Mask Pressure Maske Basıncı Amount of air displaced per breath Her nefeste yer değiştiren hava miktarı Graph displaying snore volume Horlama şiddetini gösteren grafik Minute Ventilation Dakika Ventilasyonu Amount of air displaced per minute Dakika başı yer değiştiren hava miktarı Respiratory Rate Solunum Hızı Rate of breaths per minute Dakika başı solunum sayısı Patient Triggered Breaths Hasta Tarafından Başlatılan Solunum Percentage of breaths triggered by patient Hasta tarafından başlatılmış olan nefeslerin yüzdesi Pat. Trig. Breaths Hst. Bşl. Nefes Leak Rate Kaçak Hızı Rate of detected mask leakage Tespit edilen maske kaçağı miktarı I:E Ratio I:E Oranı Ratio between Inspiratory and Expiratory time Soluk alma ile soluk verme arasındaki oran ratio oran Pressure Min Min Basınç Pressure Max Maks Basınç Pressure Set Basınç Ayar Pressure Setting Basınç Ayarı IPAP Set IPAP Ayar IPAP Setting IPAP Ayarı EPAP Set EPAP Ayar EPAP Setting EPAP Ayarı Cheyne Stokes Respiration Cheyne Stokes Solunumu CSR CSR Periodic Breathing Peiyodik Solunum An abnormal period of Periodic Breathing Anormal bir Periyodik Solunum süreci Clear Airway Açık Havayolu (Clear Airway) Obstructive Tıkayıcı Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance. Solunum Eforuna Bağlı Uyanma: Nefes alıp vermede uyanma veya uyku bozukluğu ile sonuçlanan bir kısıtlama. Leak Flag Kaçak İşareti (Leak Flag) LF LF A user definable event detected by OSCAR's flow waveform processor. OSCAR'ın akım dalgaformu işlemcisi tarafından tespit edilen, kullanıcı tarafından tanımlanabilecek bir olay. Perfusion Index Perfüzyon İndeksi A relative assessment of the pulse strength at the monitoring site İzlenen bölgede nabız gücünün nisbi olarak değerlendirilmesi Perf. Index % Perf. Indeksi % Mask Pressure (High frequency) Maske Basıncı (Yüksek frekanslı) Expiratory Time Nefes Verme Süresi Time taken to breathe out Nefes verirken geçen süre Inspiratory Time Nefes Alma Süresi Time taken to breathe in Nefes alırken geçen süre Respiratory Event Solunum Olayı Graph showing severity of flow limitations Akım kısıtlamalarının ciddiyetini gösteren grafik Flow Limit. Akım Kısıtlaması. Target Minute Ventilation Hedeflenen Dakika Ventilasyonu Maximum Leak Maksimum Kaçak The maximum rate of mask leakage Maskeden meydana gelen maksimum kaçak hızı Max Leaks Maks Kaçak Apnea Hypopnea Index Apne Hipopne Indeksi Graph showing running AHI for the past hour Geçen saate ait AHI'yi gösteren grafik Total Leak Rate Toplam Kaçak Miktarı Detected mask leakage including natural Mask leakages Maskeden doğal olarak meydana gelen kaçak da dahil toplam kaçak miktarı Median Leak Rate Median Kaçak Oranı Median rate of detected mask leakage Maske kaçak miktarı median değeri Median Leaks Median Kaçak Respiratory Disturbance Index Solunum Bozukluğu İndeksi (Respiratory Disturbance Index) Graph showing running RDI for the past hour Geçen saate ait RDI'yi gösteren grafik Sleep position in degrees Derece cinsinden uyku pozisyonu Upright angle in degrees Derece cinsinden dikine açı Movement Hareket Movement detector Hareket dedektörü CPAP Session contains summary data only CPAP Seansı sadece özet verisi içeriyor PAP Mode PAP Modu Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Channels.xml çözümlenemedi, OSCAR devam edemeyecek ve kapanıyor. Apnea Apne An apnea reportred by your CPAP machine. CPAP cihazınız tarafından bildirilmiş olan bir apne. PAP Device Mode PAP Cihaz Modu APAP (Variable) APAP (Değişken) ASV (Fixed EPAP) ASV (Sabit EPAP) ASV (Variable EPAP) ASV (Değişken EPAP) Height Boy Physical Height Fiziki Boy Notes Notlar Bookmark Notes Yer İşareti Notları Body Mass Index Vücut Kitle İndeksi (Body Mass Index) How you feel (0 = like crap, 10 = unstoppable) Nasıl hissediyorsunuz (0=çok kötüyüm, 10=beni kimse durduramaz) Bookmark Start Yer İşareti Başlangıcı Bookmark End Yer İşareti Bitişi Last Updated En Son Güncelleme Journal Notes Günlük Notları Journal Günlük 1=Awake 2=REM 3=Light Sleep 4=Deep Sleep 1=Uyanık 2=REM 3=Yüzeyel Uyku 4=Derin Uyku Brain Wave Beyin Dalgası BrainWave BeyinDalgası Awakenings Uyanmalar Number of Awakenings Uyanma Sayısı Morning Feel Sabah Hissiyatı How you felt in the morning Sabahleyin kendinizi nasıl hissettiniz Time Awake Uyanık Zaman Time spent awake Uyanık geçen zaman Time In REM Sleep REM Uykusunda Zaman Time spent in REM Sleep REM Uykusunda geçen Zaman Time in REM Sleep REM Uykusunda geçen Zaman Time In Light Sleep Yüzeyel Uykuda Zaman Time spent in light sleep Yüzeyel Uykuda geçen Zaman Time in Light Sleep Yüzeyel Uykudaki Zaman Time In Deep Sleep Derin Uykuda geçen Zaman Time spent in deep sleep Derin Uykuda geçen Zaman Time in Deep Sleep Derin Uykuda geçen Zaman Time to Sleep Uykuya dalma Süresi Time taken to get to sleep Uykuya dalmak için geçen süre Zeo ZQ Zeo ZQ Zeo sleep quality measurement Zeo uyku kalitesi ölçümü ZEO ZQ ZEO ZQ Debugging channel #1 Hata ayıklama kanalı #1 Test #1 Test #1 For internal use only Sadece iç kullanım için Debugging channel #2 Hata ayıklama kanalı #2 Test #2 Test #2 Zero Sıfır Upper Threshold Üst Eşik Lower Threshold Alt Eşik As you did not select a data folder, OSCAR will exit. Herhangi bir veri klasörü seçmediğiniz için OSCAR kapanacak. or CANCEL to skip migration. veya İPTAL'i seçip taşımayı atlayın. Choose the SleepyHead or OSCAR data folder to migrate Taşınacak SleepyhHead veya OSCAR veri klasörünü seçin The folder you chose does not contain valid SleepyHead or OSCAR data. Seçtiğiniz klasörde geçerli SleepyHead veya OSCAR verisi bulunmuyor. You cannot use this folder: Bu klasörü kullanamazsınız: Migrating Taşınıyor files dosyalar from dan to ye OSCAR crashed due to an incompatibility with your graphics hardware. OSCAR grafik donanımınız ile olan bir uyuşmazlık sebebiyle çöktü. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. Bu durumu çözmek için, OSCAR daha yavaş ancak daha yüksek uyumluluğu olan bir çizim yöntemine döndü. OSCAR will set up a folder for your data. OSCAR veriniz için bir klasör oluşturacak. If you have been using SleepyHead or an older version of OSCAR, Eğer SleepyHead'i veya OSCAR'ın eski bir versiyonunu kullanıyorduysanız, OSCAR can copy your old data to this folder later. OSCAR eski verinizi bu klasöre daha sonra kopyalayabilir. Migrate SleepyHead or OSCAR Data? SleepyHead veya OSCAR Verisi Taşınsın Mı? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data Bir sonraki ekranda OSCAR SleepyHead veya OSCAR verisi içeren bir klasör seçmenizi isteyecek Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. Bir sonraki ekrana geçmek için [Tamam] düğmesine basın veya SleepyHead veya OSCAR verisi kullanmak istemiyorsanız [Hayır] düğmesini seçin. We suggest you use this folder: Şu klasörü kullanmanızı öneririz: Click Ok to accept this, or No if you want to use a different folder. Kabul etmek için Tamam'a basın, veya farklı bir klasör kullanmak istiyorsanız Hayır'ı seçin. Choose or create a new folder for OSCAR data OSCAR verisi için bir klasör seçin veya yeni bir klasör yaratın Next time you run OSCAR, you will be asked again. OSCAR'ı bir sonraki çalıştırmanızda bu soru tekrar sorulacak. The folder you chose is not empty, nor does it already contain valid OSCAR data. Seçtiğiniz klasör boş değil, veya içerisinde geçerli OSCAR verisi yok. Data directory: Veri klasörü: Unable to create the OSCAR data folder at Bu adreste OSCAR veri klasörü yaratılamadı Unable to write to OSCAR data directory OSCAR'ın veri klasörüne yazılamıyor Error code Hata kodu OSCAR cannot continue and is exiting. OSCAR devam edemiyor ve kapanacak. Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk. Hata ayıklama günlüğüne yazılamıyor. Hata ayıklama bölmesini hala kullanabilirsiniz (Yardım / Sorun Giderme / Hata Ayıklama Bölmesini Göster) , ancak hata ayıklama günlüğü diske yazılmaz. Version "%1" is invalid, cannot continue! Versiyon "%1" geçersiz, devam edilemiyor! The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2). Çalıştırmakta olduğunuz OSCAR sürümü (%1) bu veriyi yaratmak için kullanılan sürümden (%2) daha ESKİ. It is likely that doing this will cause data corruption, are you sure you want to do this? Bunu yapmanız muhtemelen veri bozulmasına yol açacaktır, yapmak istediğinize emin misiniz? Question Soru Exiting Çıkılıyor Are you sure you want to use this folder? Bu klasörü kullanmak istediğinize emin misiniz? Don't forget to place your datacard back in your CPAP machine Veri kartınızı CPAP makinenize geri koymayı unutmayın OSCAR Reminder OSCAR Hatırlatıcısı You can only work with one instance of an individual OSCAR profile at a time. Bir seferde yalnızca tek bir OSCAR profili örneğiyle çalışabilirsiniz. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Bulut depolama alanı kullanıyorsanız, devam etmeden önce OSCAR'ın kapalı olduğundan ve diğer bilgisayarda eşitlemenin tamamlandığından emin olun. Loading profile "%1"... Profil "%1" yükleniyor... Chromebook file system detected, but no removable device found Chromebook dosya sistemi tespit edildi, ancak çıkarılabilir bir cihaz bulunamadı You must share your SD card with Linux using the ChromeOS Files program SD kartınızı Linux ile ChromeOS Files yazılımını kullanarak paylaştırmanız gerekmekte Recompressing Session Files Seans Dosyaları Tekrar Sıkıştırılıyor Please select a location for your zip other than the data card itself! Lütfen zip dosyanız için veri kartının dışında bir konum seçin! Unable to create zip! Zip dosyası yaratılamadı! Are you sure you want to reset all your channel colors and settings to defaults? Tüm kanal renklerinizi ve seçeneklerinizi varsayılan değerlere sıfırlamak istediğinize emin misiniz? Are you sure you want to reset all your waveform channel colors and settings to defaults? Tüm dalga formu kanal renklerinizi ve seçeneklerinizi varsayılan değerlere sıfırlamak istediğinize emin misiniz? There are no graphs visible to print Yazdırlabilecek grafik yok Would you like to show bookmarked areas in this report? Bu rapordaki yer izi içeren alanların gösterilmesini ister misiniz? Printing %1 Report %1 Raporu Yazılıyor %1 Report %1 Raporu : %1 hours, %2 minutes, %3 seconds : %1 saat, %2 dakika, %3 saniye RDI %1 RDI %1 AHI %1 AHI %1 AI=%1 HI=%2 CAI=%3 AI=%1 HI=%2 CAI=%3 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% UAI=%1 UAI=%1 NRI=%1 LKI=%2 EPI=%3 NRI=%1 LKI=%2 EPI=%3 AI=%1 AI=%1 Reporting from %1 to %2 %1 den %2 ye raporlanıyor Entire Day's Flow Waveform Tüm Güne Ait Akım Dalga Formu Current Selection Güncel Seçim Entire Day Tüm Gün Page %1 of %2 %2 sayfadan %1.cisi Days: %1 Günler: %1 Low Usage Days: %1 Az Kullanılan Günler: %1 (%1% compliant, defined as > %2 hours) (%1% utumlu - %2 saatin üstünde olarak tanımlanmış) (Sess: %1) (Seans: %1) Bedtime: %1 Yatış Zamanı: %1 Waketime: %1 Uyanma Zamanı: %1 (Summary Only) (Sadece Özet) There is a lockfile already present for this profile '%1', claimed on '%2'. '%2' için talep edilen '%1' bu profili için zaten bir kilit dosyası var. Fixed Bi-Level Sabit Bi-Level Auto Bi-Level (Fixed PS) Oto Bi-Level (Sabit PS) Auto Bi-Level (Variable PS) Oto Bi-Level (Değişken PS) 99.5% 99.5% varies değişken n/a yok Fixed %1 (%2) Sabit %1 (%2) Min %1 Max %2 (%3) Min %1 Maks %2 (%3) EPAP %1 IPAP %2 (%3) EPAP %1 IPAP %2 (%3) PS %1 over %2-%3 (%4) PS %2-%3'ün üstüne %1 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EPAP %1 Maks IPAP %2 PS %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1 PS %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1 IPAP %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) EPAP %1-%2 IPAP %3-%4)(%5) Most recent Oximetry data: <a onclick='alert("daily=%2");'>%1</a> En yeni Oksimetri verisi: <a onclick='alert("daily=%2");'>%1</a> (last night) (dün gece) (1 day ago) (1 gün önce) (%2 days ago) (%2 gün önce) No oximetry data has been imported yet. Henüz içe aktarılmış oksimetri verisi mevcut değil. Contec Contec CMS50 CMS50 Fisher & Paykel Fisher & Paykel ICON ICON DeVilbiss DeVilbiss Intellipap Intellipap SmartFlex Settings SmartFlex Ayarları ChoiceMMed ChoiceMMed MD300 MD300 Respironics Respironics M-Series M-Series Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Somnopose Yazılımı Zeo Zeo Personal Sleep Coach Kişisel Uyku Koçu Database Outdated Please Rebuild CPAP Data Veritabanı Eskimiş Lütfen CPAP Verisini Yeniden Oluşturun (%2 min, %3 sec) (%2 dk, %3 sn) (%3 sec) (%3 sn) Pop out Graph Açılabilir Grafik The popout window is full. You should capture the existing popout window, delete it, then pop out this graph again. Açılır pencere dolu. Mevcut açılır pencereyi yakalamanız silmeniz, ve daha sonra bu grafiği tekrar açılır pencere haline getrimenzi gerekiyor. Your machine doesn't record data to graph in Daily View Cihazınız Günlük Görünüm'de yer alan grafiğe veri kaydetmiyor There is no data to graph Grafiği oluşturulabilecek bir veri yok d MMM yyyy [ %1 - %2 ] g AAA yyyy [ %1 - %2 ] Hide All Events Tüm Olayları Sakla Show All Events Tüm Olayları Göster Unpin %1 Graph %1 Grafiğinin Sabitlemesini Kaldır Popout %1 Graph %1 Grafiğiniz Ortaya Çıkar Pin %1 Graph %1 Grafiğini Sabitle Plots Disabled Çizimler Devre Dışı Bırakıldı Duration %1:%2:%3 Süre %1:%2:%3 AHI %1 AHI %1 Relief: %1 Rahatlama: %1 Hours: %1h, %2m, %3s Saat: %1h, %2m, %3s Machine Information Cihaz Bilgisi Journal Data Günlük Verisi OSCAR found an old Journal folder, but it looks like it's been renamed: OSCAR eski bir Günlük klasörü buldu, ancak tekrar adlandırılmış gibi duruyor: OSCAR will not touch this folder, and will create a new one instead. OSCAR bu klasöre dokunmayacak, ve yerine yenisini oluşturacak. Please be careful when playing in OSCAR's profile folders :-P Lütfen OSCAR'ın profil klasörleri ile oynarken dikkatli olun :-P For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders. Bir sebeple OSCAR profilinizde bir günlük nesnesi kaydı bulamadı, ancak birden fazla Günlük veri klasörü buldu. OSCAR picked only the first one of these, and will use it in future: OSCAR bunlardan sadece ilkini seçti, ve ilerde de bunu kullanacak: If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually. Eğer eski veriniz kaybolduysa, diğer tüm Journal_XXXXXXX klasörlerini bu klasöre manüel olarak kopyalayın. CMS50F3.7 CMS50F3.7 CMS50F CMS50F Backing up files... Dosyalar yedekleniyor... Reading data files... Veri dosyaları okunuyor... SmartFlex Mode SmartFlex Modu Intellipap pressure relief mode. Intellipap basınç tahliyesi modu. Ramp Only Sadece Rampa Full Time Tam Zamanlı SmartFlex Level SmartFlex Düzeyi Intellipap pressure relief level. Intellipap basınç tahliyesi seviyesi. Snoring event. Horlama olayı. SN SN Locating STR.edf File(s)... STR.edf Dosya(lar)sının Yeri Tespit Ediliyor... Cataloguing EDF Files... EDF Dosyaları Kataloglanıyor... Queueing Import Tasks... İçe Aktarma Görevleri Sıraya Alınıyor... Finishing Up... Bitiriliyor... CPAP Mode CPAP Modu VPAPauto VPAPauto ASVAuto ASVAuto iVAPS iVAPS PAC PAC Auto for Her Kadın için Oto (Auto for Her) EPR EPR ResMed Exhale Pressure Relief ResMed Nefes Verme Basınç Azaltıcısı (Exhale Pressure Relief) Patient??? Hasta??? EPR Level EPR Düzeyi Exhale Pressure Relief Level Nefes Verme Basınç Azaltıcısı (Exhale Pressure Relief) Response Yanıt Patient View Hasta Görünümü SmartStart SmartStart Machine auto starts by breathing Cihaz nefes almayla birlikte otomatik olarak başlar Smart Start Smart Start Humid. Status Neml. Durumu Humidifier Enabled Status Nemlendirici Etkin Durumu Humid. Level Neml. Düzeyi Humidity Level Nem Düzeyi Temperature Sıcaklık ClimateLine Temperature ClimateLine Sıcaklık Temp. Enable Sıcakl. Etkinleştir ClimateLine Temperature Enable ClimateLine Sıcaklık Etkinleştir Temperature Enable Sıcaklık Etkinleştir AB Filter AB Filtre Antibacterial Filter Antibakteriyel Filtre Pt. Access Hst. Erişimi Essentials Temeller Plus Plus Climate Control Klima Kontrolü Manual Manüel Soft Yumuşak Standard Standart BiPAP-T BiPAP-T BiPAP-S BİPAP-S BiPAP-S/T BİPAP-S/T SmartStop SmartStop (Akıllı Sonlanma) Machine auto stops by breathing Cihaz nefes almaya göre otomatik olarak durur Smart Stop Smart Stop (Akıllı Sonlanma) Simple Basit Advanced Gelişmiş Your ResMed CPAP machine (Model %1) has not been tested yet. ResMed CPAP cihazınız (Model %1) henüz test edilmemiştir. It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR. Diğer cihazlar ile benzerlik gösterdiği için çalışma ihtimali olmakla birlikte, geliştiriciler cihazın OSCAR ile kullanılabilir olduğundan emin olmak için bu cihazın SD kartının .zip'li bir kopyasına ihtiyaç duymaktadırlar. Parsing STR.edf records... STR.edf kayıtları çözümleniyor... Auto Oto Mask Maske ResMed Mask Setting ResMed Maske Ayarı Pillows Yastıkçıklar Full Face Tam Yüz Nasal Nazal Ramp Enable Rampayı Etkinleştir Weinmann Weinmann SOMNOsoft2 SOMNOsoft2 Snapshot %1 Anlık Görüntü %1 CMS50D+ CMS50D+ CMS50E/F CMS50E/F Loading %1 data for %2... %2 için %1 verisi yükleniyor... Scanning Files Dosyalar Taranıyor Migrating Summary File Location Özet Dosyasının Konumunu Taşınıyor Loading Summaries.xml.gz Summaries.xml.gz yükleniyor Loading Summary Data Özet Verisi Yükleniyor Please Wait... Lütfen Bekleyin... Updating Statistics cache İstatistik önbelleği güncelleniyor Usage Statistics Kullanım İstatistikleri %1 Charts %1 Tablolar %1 of %2 Charts %1 Tablodan %2'si Loading summaries Özetler yükleniyor Dreem Dreem Your Viatom device generated data that OSCAR has never seen before. Viatom cihazınız OSCAR'ın daha önce hiç görmediği bir veri üretti. The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly. İçe aktarılan veriler tamamıyla doğru olmayabilir, dolayısıyla geliştiriciler OSCAR'ın bu veriyi doğru bir şekilde işlediğinden emin olmak için Viatom dosyalarınızın bir kopyasına ihtiyaç duymaktalar. Viatom Viatom Viatom Software Viatom Yazılımı New versions file improperly formed Yeni sürümler dosyası uygun olmayan bir biçimde şekillendirilmiş A more recent version of OSCAR is available OSCAR'ın daha güncel bir sürümü mevcut release sürüm test version test sürmü You are running the latest %1 of OSCAR OSCAR'ın en son %1 'ini kullanmaktasınız You are running OSCAR %1 OSCAR %1'i kullanmaktasınız OSCAR %1 is available <a href='%2'>here</a>. OSCAR %1 <a href='%2'>burada</a> mevcut. Information about more recent test version %1 is available at <a href='%2'>%2</a> Daha yeni deneme sürümü olan %1 hakkında bilgi a href='%2'>%2</a> adresinde mevcuttur Check for OSCAR Updates OSCAR Güncellemelerini Kontrol Et Unable to check for updates. Please try again later. Güncellemeler kontrol edilemiyor. Lütfen daha sonra tekrar deneyiniz. SensAwake level SensAwake düzeyi Expiratory Relief Ekspiratuar Rahatlama Expiratory Relief Level Ekspiratuar Rahatlama Seviyesi Humidity Nem SleepStyle UykuTipi This page in other languages: Bu sayfanın başka dillere çevrimi: %1 Graphs %1 Grafikler %1 of %2 Graphs %2 Grafiklerin %1'i %1 Event Types %1 Olay Tipleri %1 of %2 Event Types %2 Olay Tiplerinin %1'i Report about:blank hakkında:boş SessionBar %1h %2m %1st %2dk No Sessions Present Seans Yok SleepStyleLoader Import Error İçe Aktarma Hatası This Machine Record cannot be imported in this profile. Bu Cihaz Kaydı bu profile aktarılamaz. The Day records overlap with already existing content. Günlük kayıtlar mevcut içerik ile çakışıyor. Statistics CPAP Statistics CPAP İstatistikleri CPAP Usage CPAP Kullanımı Average Hours per Night Gece başına Ortalama Saat Therapy Efficacy Tedavi Etkinliği Leak Statistics Kaçak İstatistikleri Pressure Statistics Basınç İstatistikleri Oximeter Statistics Oksimetre İstatistikleri Blood Oxygen Saturation Kan Oksijen Satürasyonu Pulse Rate Nabız Hızı %1 Median %1 Median Average %1 Ortalama %1 Min %1 Min %1 Max %1 Maks %1 %1 Index %1 Endeksi % of time in %1 %1'deki sürenin % si % of time above %1 threshold %1 eşiğinin üstünde geçen sürenin %'si % of time below %1 threshold %1 eşiğinin altında geçen sürenin %'si Name: %1, %2 İsim: %1, %2 DOB: %1 Doğum Tarihi: %1 Phone: %1 Telefon: %1 Email: %1 E-posta: %1 Address: Adres: This report was prepared on %1 by OSCAR %2 Bu rapor %1'de OSCAR %2 tarafından hazırlanmıştır Days Used: %1 Kullanılan Gün Sayısı: %1 Low Use Days: %1 Az Kullanılan Gün Sayısı: %1 Compliance: %1% Uyum: %1% Days AHI of 5 or greater: %1 AHI'nin 5 veya üzeri olduğu gün sayısı: %1 Best AHI En iyi AHI Date: %1 AHI: %2 Tarih: %1 AHI: %2 Worst AHI En kötü AHI Best Flow Limitation En iyi Hava Akımı Kısıtlaması Date: %1 FL: %2 Tarih: %1 FL: %2 Worst Flow Limtation En kötü Hava Akımı Kısıtlaması No Flow Limitation on record Kaydedilmiş hava akımı kısıtlaması mevcut değil Worst Large Leaks En Kötü Büyük Kaçaklar Date: %1 Leak: %2% Tarih: %1 Kaçak: %2% No Large Leaks on record Kaydedilmiş Büyük Kaçak mevcut değil Worst CSR En kötü CSR Date: %1 CSR: %2% Tarih: %1 CSR: %2% No CSR on record Kaydedilmiş CSR yok Worst PB En kötü PB Date: %1 PB: %2% Tarih: %1 PB: %2% No PB on record Kaydedilmiş PB yok Want more information? Daha fazla bilgi ister misiniz? OSCAR needs all summary data loaded to calculate best/worst data for individual days. OSCAR'ın günlük en iyi/en kötü verileri hesaplayabilmesi için tüm özet verinin yüklenmiş olması gerekir. Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available. Lütfen bu verinin mevcut olduğundan emin olmak için Özetleri Önceden-Yükle başlıklı işaretleme kutusunu aktifleyin. Best RX Setting En iyi Tedavi Ayarı Date: %1 - %2 Tarih: %1 - %2 AHI: %1 AHI: %1 Total Hours: %1 Toplam Saat: %1 Worst RX Setting En kötü Tedavi Ayarı Most Recent En Yeni Compliance (%1 hrs/day) Uyum (%1 saat/gün) OSCAR is free open-source CPAP report software OSCAR ücretsiz bir açık kaynak kodlu CPAP raporlama yazılımıdır Changes to Machine Settings Cihaz Ayarlarındaki Değişiklikler No data found?!? Hiç veri bulunmadı?!? Oscar has no data to report :( Oscar'ın raporlayabileceği veri yok :( Last Week Geçen Hafta Last 30 Days Son 30 Gün Last 6 Months Son 6 Ay Last Year Geçen Yıl Last Session Geçen Seans Details Detaylar No %1 data available. %1 verisi mevcut değil. %1 day of %2 Data on %3 %3'deki %1 günlük %2 Verisi %1 days of %2 Data, between %3 and %4 %3 ile %4 arasındaki %1 günlük %2 Verisi Days Günler Pressure Relief Basınç Tahliyesi Pressure Settings Basınç Ayarları Machine Information Cihaz Bilgisi First Use İlk Kullanım Last Use Son Kullanım Welcome Welcome to the Open Source CPAP Analysis Reporter Açık Kaynak Kodlu CPAP Analizi Raporlayıcısına Hoş Geldiniz What would you like to do? Ne yapmak istersiniz? CPAP Importer CPAP İçe Aktarıcısı Oximetry Wizard Oksimetri Sihirbazı Daily View Günlük Görünüm Overview Genel Bakış Statistics İstatistikler <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> <span style=" font-weight:600;">Uyarı: </span><span style=" color:#ff0000;">ResMed S9 SD Kartları </span><span style=" font-weight:600; color:#ff0000;">bilgisayarınıza yerleştirilmeden önce &nbsp;&nbsp;&nbsp;</span><span style=" color:#ff0000;">kilitlenmelidir</span><span style=" color:#000000;"><br>Bazı işletim sistemleri karta izin almadan endeks dostaları yazabilirler, ki bu durumda kart cpap cihazınız tarafından okunamaz hale gelebilir.</span></p></body></html> It would be a good idea to check File->Preferences first, Öncelikle Dosya->Seçenekler bölümünü kontrol etmeniz iyi bir fikir olabilir, as there are some options that affect import. içe aktarımı etkileyebilecek bazı seçenekler mevcut olduğundan. Note that some preferences are forced when a ResMed machine is detected Bir ResMed cihazı tespit edildiğinde bazı seçeneklerin zorunlu hale geldiğini unutmayın First import can take a few minutes. İlk içe aktarım birkaç dakika sürebilir. The last time you used your %1... %1 cihazınızı en son kullandığınız zaman... last night dün akşam %2 days ago %2 gün önce was %1 (on %2) %1 idi (%2'de) %1 hours, %2 minutes and %3 seconds %1 saat, %2 dakika ve %3 saniye Your machine was on for %1. Cihazınız %1 çalıştı. <font color = red>You only had the mask on for %1.</font> <font color = red>Maskenizi sadece %1 taktınız.</font> under altında over üstünde reasonably close to makul derecede yakın equal to eşit You had an AHI of %1, which is %2 your %3 day average of %4. AHI değeriniz %1 olup %3 günlük ortalama değeriniz olan %4'ün %2 idi. Your CPAP machine used a constant %1 %2 of air CPAP cihazınız sabit olarak %1 %2 hava kullandı Your pressure was under %1 %2 for %3% of the time. Basıncınız seansın %3%'ünde %1 %2'nin altındaydı. Your machine used a constant %1-%2 %3 of air. CPAP cihazınız %1-%2 %3 sabit hava kullandı. Your EPAP pressure fixed at %1 %2. EPAP basıncınız %1 %2'de sabitlenmişti. Your IPAP pressure was under %1 %2 for %3% of the time. IPAP basıncınız seansın %3%'ünde %1 %2'nin altındaydı. Your EPAP pressure was under %1 %2 for %3% of the time. EPAP basıncınız seansın %3%'ünde %1 %2'nin altındaydı. Your machine was under %1-%2 %3 for %4% of the time. Cihazınız seansın %4%'ünde %1-%2 %3'ün altındaydı. 1 day ago 1 gün önce Your average leaks were %1 %2, which is %3 your %4 day average of %5. Ortalama kaçak miktarınız %1 %2 olup, %4 günlük ortalamanız olan %5'in %3 idi. No CPAP data has been imported yet. Henüz içe aktarılmış bir CPAP verisi mevcut değil. gGraph %1 days %1 gün gGraphView 100% zoom level 100% yakınlaştırma seviyesi Restore X-axis zoom to 100% to view entire selected period. Seçilen sürenin tamamını görüntülemek için X ekseni yakınlaştırmasını% 100'e geri al. Restore X-axis zoom to 100% to view entire day's data. Günün tüm verisini görüntülemek için X ekseni yakınlaştırmasını% 100'e geri yükle. Reset Graph Layout Grafik Düzenini Sıfırla Resets all graphs to a uniform height and default order. Tüm grafikleri eşit dağılımlı bir yüksekliğe ve varsayılan düzene sıfırlar. Y-Axis Y-Ekseni Plots Çizimler CPAP Overlays CPAP Çakıştırmaları Oximeter Overlays Oksimetre Çakıştırmaları Dotted Lines Noktalı Çizgiler Double click title to pin / unpin Click and drag to reorder graphs Sabitlemek/sökmek için başlığa çift tıklayın Grafikleri yeniden düzenlemek için tıklayıp çekin Remove Clone Klonu Kaldır Clone %1 Graph %1 Grafiği Klonla OSCAR-code-v1.3.1/Translations/qt/000077500000000000000000000000001417327530600165765ustar00rootroot00000000000000OSCAR-code-v1.3.1/Translations/qt/oscar_qt_af.ts000066400000000000000000000044201417327530600214270ustar00rootroot00000000000000 QShortcut No Nee Yes Ja QPlatformTheme OK OK &No &Nee &Yes &Ja Help Hulp Open Oopmaak Save Stoor Abort Afbreek Apply Toepas Close Sluit Reset Herstel Retry Weer Restore Defaults Herstel verstek Cancel Kanselleer Ignore Ignoreer N&o to All N&ee vir almal Save All Spaar alles Discard Weggooi Yes to &All Ja vir &alles OSCAR-code-v1.3.1/Translations/qt/oscar_qt_ar.ts000066400000000000000000000045251417327530600214510ustar00rootroot00000000000000 QShortcut No لا Yes نعم QPlatformTheme OK حسنًا &No &لا &Yes &نعم Help مساعدة Open افتح Save احفظ Abort أجهض Apply طبّق Close أغلق Reset صفّر Retry حاول مجدّدًا Restore Defaults استعد الافتراضيّات Cancel ألغِ Ignore تجاهل N&o to All لا لل&كلّ Save All احفظ الكلّ Discard ارفض Yes to &All ن&عم للكلّ OSCAR-code-v1.3.1/Translations/qt/oscar_qt_bg.ts000066400000000000000000000047001417327530600214320ustar00rootroot00000000000000 QShortcut No Не Yes Да QPlatformTheme OK Добре &No &Не &Yes &Да Help Помощ Open Отваряне Save Запазване Abort Прекъсване Apply Прилагане Close Затваряне Reset Нулиране Retry Повторен опит Restore Defaults По подразбиране Cancel Отказ Ignore Пренебрегване N&o to All Н&е за всички Save All Запазване на всичко Discard Отхвърляне Yes to &All Да за &всички OSCAR-code-v1.3.1/Translations/qt/oscar_qt_da.ts000066400000000000000000000044221417327530600214270ustar00rootroot00000000000000 QShortcut No Ingen Yes Ja QPlatformTheme OK Okay &No &Ingen &Yes &Ja Help Hjælp Open Åben Save Gemme Abort Abort Apply Ansøge Close Tæt Reset Nulstil Retry Prøve igen Restore Defaults Gendanne standardindstillingerne Cancel Afbestille Ignore Ignorere N&o to All &Ingen for alle Save All Gem alle Discard Kassér Yes to &All Ja til &alle OSCAR-code-v1.3.1/Translations/qt/oscar_qt_de.ts000066400000000000000000000044301417327530600214320ustar00rootroot00000000000000 QShortcut No Nein Yes Ja QPlatformTheme OK OK &No &Nein &Yes &Ja Help Hilfe Open Öffnen Save Speichern Abort Abbrechen Apply Anwenden Close Schließen Reset Zurücksetzen Retry Wiederholen Restore Defaults Voreinstellungen Cancel Abbrechen Ignore Ignorieren N&o to All N&ein, keine Save All Alles speichern Discard Verwerfen Yes to &All Ja, &alle OSCAR-code-v1.3.1/Translations/qt/oscar_qt_el.ts000066400000000000000000000046731417327530600214530ustar00rootroot00000000000000 QShortcut No Οχι Yes Ναί QPlatformTheme OK Εντάξει &No &Οχι &Yes &Ναί Help Βοήθεια Open Ανοιξε Save Σώσει Abort Αποβάλλω Apply Ισχύουν Close Κλείσε Reset Επαναφορά Retry Προσπαθησε ξανα Restore Defaults Επαναφέρετε τις προεπιλογές Cancel Ματαίωση Ignore Αγνοώ N&o to All N&o σε όλους Save All Αποθήκευση όλων Discard Απορρίπτω Yes to &All Ν&αι σε όλα OSCAR-code-v1.3.1/Translations/qt/oscar_qt_es.ts000066400000000000000000000044411417327530600214530ustar00rootroot00000000000000 QShortcut No No Yes QPlatformTheme OK Aceptar &No &No &Yes &Sí Help Ayuda Open Abrir Save Guardar Abort Interrumpir Apply Aplicar Close Cerrar Reset Reinicializar Retry Reintentar Restore Defaults Restaurar los valores predeterminados Cancel Cancelar Ignore Ignorar N&o to All N&o a todo Save All Guardar todo Discard Descartar Yes to &All Sí a &todo OSCAR-code-v1.3.1/Translations/qt/oscar_qt_fi.ts000066400000000000000000000044151417327530600214430ustar00rootroot00000000000000 QShortcut No Ei Yes Kyllä QPlatformTheme OK OK &No &Ei &Yes &Kyllä Help Ohje Open Avaa Save Tallenna Abort Keskeytä Apply Käytä Close Sulje Reset Palauta Retry Yritä uudelleen Restore Defaults Palauta oletukset Cancel Peru Ignore Ohita N&o to All E&i kaikkiin Save All Tallenna kaikki Discard Hylkää Yes to &All Kyllä k&aikkiin OSCAR-code-v1.3.1/Translations/qt/oscar_qt_fr.ts000066400000000000000000000044601417327530600214540ustar00rootroot00000000000000 QShortcut No Non Yes Oui QPlatformTheme OK OK &No &Non &Yes &Oui Help Aide Open Ouvrir Save Enregistrer Abort Abandonner Apply Appliquer Close Fermer Reset Réinitialiser Retry Réessayer Restore Defaults Restaurer les valeurs par défaut Cancel Annuler Ignore Ignorer N&o to All Non à to&ut Save All Tout enregistrer Discard Ne pas tenir compte Yes to &All Oui à &tout OSCAR-code-v1.3.1/Translations/qt/oscar_qt_he.ts000066400000000000000000000045061417327530600214420ustar00rootroot00000000000000 QShortcut No לא Yes כן QPlatformTheme OK בסדר &No &לא &Yes &כן Help עזרה Open פתוח Save לְהוֹשִׁיעַ Abort בטל Apply שלח Close סגור Reset אפס Retry נסה שוב Restore Defaults לשחזר את ברירות מחדל Cancel ביטול Ignore להתעלם N&o to All לא לכולם Save All שמור הכל Discard להשליך Yes to &All כן לכולם OSCAR-code-v1.3.1/Translations/qt/oscar_qt_it.ts000066400000000000000000000044111417327530600214550ustar00rootroot00000000000000 QShortcut No No Yes QPlatformTheme OK OK &No &No &Yes &Sì Help Aiuto Open Apri Save Salva Abort Interrompi Apply Applica Close Chiudi Reset Ripristina Retry Riprova Restore Defaults Ripristina valori predefiniti Cancel Annulla Ignore Ignora N&o to All N&o a tutti Save All Salva tutti Discard Tralascia Yes to &All Sì &a tutti OSCAR-code-v1.3.1/Translations/qt/oscar_qt_ko.ts000066400000000000000000000044131417327530600214540ustar00rootroot00000000000000 QShortcut No 아니요 Yes QPlatformTheme OK OK &No 아니요 &Yes Help 도움말 Open 열기 Save 저장 Abort 중단 Apply 적용 Close 닫기 Reset 재설정 Retry 재시도 Restore Defaults 기본설정으로 되돌리기 Cancel 취소 Ignore 무시 N&o to All 모두 아니요 Save All 모두 저장 Discard 포기 Yes to &All 모두 예 OSCAR-code-v1.3.1/Translations/qt/oscar_qt_nl.ts000066400000000000000000000044151417327530600214560ustar00rootroot00000000000000 QShortcut No Nee Yes Ja QPlatformTheme OK OK &No &Nee &Yes &Ja Help Hulp Open Openen Save Opslaan Abort Afbreken Apply Toepassen Close Sluiten Reset Herstellen Retry Opnieuw Restore Defaults Standaardwaarden Cancel Annuleren Ignore Negeren N&o to All N&ee, geen enkele Save All Sla alles op Discard Verwijderen Yes to &All Ja, &allemaal OSCAR-code-v1.3.1/Translations/qt/oscar_qt_no.ts000066400000000000000000000044221417327530600214570ustar00rootroot00000000000000 QShortcut No Nei Yes Ja QPlatformTheme OK OK &No &Nei &Yes &Ja Help Hjelp Open Åpen Save Lagre Abort Avbryte Apply Søke om Close lukke Reset tilbakestille Retry Prøv på nytt Restore Defaults Gjenopprett standardverdier Cancel kansellere Ignore Overse N&o to All N&ei til alle Save All Lagre alt Discard Forkast Yes to &All Ja til &alt OSCAR-code-v1.3.1/Translations/qt/oscar_qt_ph.ts000066400000000000000000000044161417327530600214550ustar00rootroot00000000000000 QShortcut No Hindi Yes Oo QPlatformTheme OK OK &No &Hindi &Yes &Oo Help Tulong Open Buksan Save I-save Abort Abort Apply Mag-apply Close Isara Reset I-reset Retry Ulitin Restore Defaults Ibalik sa dating ayos Cancel Pagkansela Ignore ignorahin N&o to All H&indi sa lahat Save All Iligtas lahat Discard Itapon Yes to &All Oo sa &lahat OSCAR-code-v1.3.1/Translations/qt/oscar_qt_pl.ts000066400000000000000000000044311417327530600214560ustar00rootroot00000000000000 QShortcut No Nie Yes Tak QPlatformTheme OK OK &No &Nie &Yes &Tak Help Pomoc Open Otwórz Save Zachowaj Abort Przerwij Apply Zastosuj Close Zamknij Reset Zresetuj Retry Ponów Restore Defaults Przywróć domyślne Cancel Anuluj Ignore Zignoruj N&o to All Ni&e dla wszystkich Save All Zachowaj wszystko Discard Odrzuć Yes to &All Ta&k dla wszystkich OSCAR-code-v1.3.1/Translations/qt/oscar_qt_pt.ts000066400000000000000000000044161417327530600214710ustar00rootroot00000000000000 QShortcut No Não Yes sim QPlatformTheme OK OK &No &Não &Yes &sim Help Socorro Open Abrir Save salvar Abort Abortar Apply aplicar Close Fechar Reset Restabelecer Retry Repetir Restore Defaults Restaurar padrões Cancel Cancelar Ignore Ignorar N&o to All Nã&o para todos Save All Salvar tudo Discard Descartar Yes to &All Sim para &tudo OSCAR-code-v1.3.1/Translations/qt/oscar_qt_ro.ts000066400000000000000000000044311417327530600214630ustar00rootroot00000000000000 QShortcut No Nu Yes da QPlatformTheme OK O.K &No &Nu &Yes &Da Help Ajutor Open Deschis Save salva Abort Abandonați Apply aplica Close Închide Reset Resetați Retry Reîncercați Restore Defaults Restabiliti setarile de baza Cancel Anulare Ignore Ignora N&o to All N&u tuturor Save All Salvează tot Discard Renunțați Yes to &All D&a la toate OSCAR-code-v1.3.1/Translations/qt/oscar_qt_ru.ts000066400000000000000000000047251417327530600214770ustar00rootroot00000000000000 QShortcut No Нет Yes Да QPlatformTheme OK OK &No &Нет &Yes &Да Help Помощь Open Открыть Save Сохранить Abort Прервать Apply Применить Close Закрыть Reset Сброс Retry Повторная попытка Restore Defaults Восстановление настроек по умолчанию Cancel Отмена Ignore Игнорировать N&o to All Н&ет всем Save All Сохранить все Discard Отбросить Yes to &All Д&а всем OSCAR-code-v1.3.1/Translations/qt/oscar_qt_sv.ts000066400000000000000000000044411417327530600214740ustar00rootroot00000000000000 QShortcut No Nej Yes Ja QPlatformTheme OK OK &No &Nej &Yes &Ja Help Hjälp Open Öppen Save Spara Abort Avbryta Apply Tillämpa Close Stänga Reset Återställa Retry Försök igen Restore Defaults Återgå till grundinställningarna Cancel Avbryt Ignore Ignorera N&o to All &Inte till alla Save All Rädda alla Discard Kassera Yes to &All Ja till &allt OSCAR-code-v1.3.1/Translations/qt/oscar_qt_th.ts000066400000000000000000000050211417327530600214520ustar00rootroot00000000000000 QShortcut No ไม่ Yes ใช่ QPlatformTheme OK ตกลง &No &ไม่ &Yes &ใช่ Help ช่วยด้วย Open เปิด Save ไว้ Abort ยกเลิก Apply ใช้ Close ปิด Reset ตั้งค่าใหม่ Retry ลองใหม่ Restore Defaults คืนค่าเริ่มต้น Cancel ยกเลิก Ignore ไม่สนใจ N&o to All &ไม่ใช่ทั้งหมด Save All บันทึกทั้งหมด Discard ยกเลิก Yes to &All &ใช่ทั้งหมด OSCAR-code-v1.3.1/Translations/qt/oscar_qt_tr.ts000066400000000000000000000044411417327530600214710ustar00rootroot00000000000000 QShortcut No Hayır Yes Evet QPlatformTheme OK Tamam &No &Hayır &Yes &Evet Help Yardım Open Save Kaydet Abort Durdur Apply Uygula Close Kapat Reset Sıfırla Retry Tekrar Dene Restore Defaults Varsayılanları Geri Yükle Cancel İptal Ignore Görmezden Gel N&o to All &Tümüne Hayır Save All Hepsini Kaydet Discard At Yes to &All Tümüne E&vet OSCAR-code-v1.3.1/Translations/qt/oscar_qt_zh.ts000066400000000000000000000043761417327530600214740ustar00rootroot00000000000000 QShortcut No Yes QPlatformTheme OK 确定 &No 否(&N) &Yes 是(&Y) Help 帮助 Open 打开 Save 保存 Abort 中止 Apply 应用 Close 关闭 Reset 重置 Retry 重试 Restore Defaults 恢复默认 Cancel 取消 Ignore 忽略 N&o to All 全部否(&O) Save All 全部保存 Discard 放弃 Yes to &All 全部是(&A) OSCAR-code-v1.3.1/anotDump.pro000066400000000000000000000012071417327530600160020ustar00rootroot00000000000000#------------------------------------------------- # # Project created by pholynyk 2019Aug01T21:00:00 # #------------------------------------------------- message(Platform is $$QMAKESPEC ) CONFIG += c++11 CONFIG += rtti CONFIG -= debug_and_release QT += core widgets DEFINES+=DUMPSTR TARGET = anotDump TEMPLATE = app QMAKE_TARGET_PRODUCT = anotDump QMAKE_TARGET_COMPANY = The OSCAR Team QMAKE_TARGET_COPYRIGHT = © 2019 The OSCAR Team QMAKE_TARGET_DESCRIPTION = "OpenSource STR.edf Dumper" VERSION = 0.5.0 SOURCES += \ anotDump/main.cpp \ dumpSTR/edfparser.cpp \ HEADERS += \ dumpSTR/SleepLib/common.h \ dumpSTR/edfparser.h \ OSCAR-code-v1.3.1/anotDump/000077500000000000000000000000001417327530600152605ustar00rootroot00000000000000OSCAR-code-v1.3.1/anotDump/main.cpp000066400000000000000000000077341417327530600167230ustar00rootroot00000000000000/* Dump the annotations of an edf file */ #include // #include #include typedef float EventDataType; #include "../dumpSTR/edfparser.h" // using namespace std; void dumpHeader( const EDFHeaderQT hdr ) { qDebug() << "EDF Header:"; qDebug() << "Version " << hdr.version << " Patient >" << hdr.patientident << "<"; qDebug() << "Recording >" << hdr.recordingident << "<"; qDebug() << "Date: " << hdr.startdate_orig.toString(); qDebug() << "Header size (bytes): " << hdr.num_header_bytes; qDebug() << "EDF type: >" << hdr.reserved44 << "<"; qDebug() << "Duration(secs): " << hdr.duration_Seconds; qDebug() << "Number of Signals: " << hdr.num_signals << "\n"; } void ifprint( QString label, QString text) { if ( text.isEmpty() ) return; qDebug() << label << ": " << text; return; } void dumpSignals( const QVector sigs ) { int i = 1; for (auto sig: sigs) { qDebug() << "Signal #" << i++; qDebug() << "Label: " << sig.label; ifprint( "Transducer", sig.transducer_type ); ifprint( "Dimension", sig.physical_dimension ); qDebug() << "Physical min: " << sig.physical_minimum << " max: " << sig.physical_maximum; qDebug() << "Digital min: " << sig.digital_minimum << " max: " << sig.digital_maximum; qDebug() << "Gain: " << sig.gain << " Offset: " << sig.offset; ifprint( "Pre-filter", sig.prefiltering ); qDebug() << "Sample Count: " << sig.sampleCnt; qDebug() << "dataArray is at " << sig.dataArray << "\n"; } } int main(int argc, char *argv[]) { // QString homeDocs = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)+"/"; // QCoreApplication::setApplicationName(getAppName()); // QCoreApplication::setOrganizationName(getDeveloperName()); // QCoreApplication::setOrganizationDomain(getDeveloperDomain()); // int first = 0, last = 0; // int firstSig = 1, lastSig = 0; QApplication a(argc, argv); QStringList args = a.arguments(); if ( args.size() < 2 ) { qDebug() << args[0] << " needs a filename" ; exit(1); } QString filename = args[args.size()-1]; bool showall = false; // brief = false; for (int i = 1; i < args.size()-1; i++) { // if (args[i] == "-f") // first = args[++i].toInt(); // else if (args[i] == "-g") // firstSig = args[++i].toInt(); // else if (args[i] == "-l") // last = args[++i].toInt(); // else if (args[i] == "-m") // lastSig = args[++i].toInt(); if (args[i] == "-a") showall = true; // else if (args[i] == "-b") // brief = true; } EDFInfo edf; if ( ! edf.Open(filename) ) { qDebug() << "Failed to open" << filename; exit(-1); } if ( ! edf.Parse() ) { qDebug() << "Parsing failed on" << filename; exit(-1); } QDate d2 = edf.edfHdr.startdate_orig.date(); if (d2.year() < 2000) { d2.setDate(d2.year() + 100, d2.month(), d2.day()); edf.edfHdr.startdate_orig.setDate(d2); } QDateTime date = edf.edfHdr.startdate_orig; qDebug() << edf.filename << " starts at " << date.toString() << " with " << edf.GetNumSignals() << " signals" << " and " << edf.GetNumDataRecords() << " records"; // if (args.size() == 2) { // exit(0); // } if (showall) { dumpHeader( (edf.edfHdr) ); dumpSignals( (edf.edfsignals) ); } // if ( brief ) // exit(0); qDebug() << "File has " << edf.annotations.size() << "annotation vectors"; int vec = 1; for (auto annoVec = edf.annotations.begin(); annoVec != edf.annotations.end(); annoVec++ ) { qDebug() << "Vector " << vec++ << " has " << annoVec->size() << " annotations"; for (auto anno = annoVec->begin(); anno != annoVec->end(); anno++ ) { qDebug() << "Offset: " << anno->offset << " Duration: " << anno->duration << " Text: " << anno->text; } } exit(0); } OSCAR-code-v1.3.1/dumpSTR.pro000066400000000000000000000012021417327530600155440ustar00rootroot00000000000000#------------------------------------------------- # # Project created by pholynyk 2019Aug01T21:00:00 # #------------------------------------------------- message(Platform is $$QMAKESPEC ) CONFIG += c++11 CONFIG += rtti CONFIG -= debug_and_release QT += core widgets DEFINES+=DUMPSTR TARGET = dumpSTR TEMPLATE = app QMAKE_TARGET_PRODUCT = dumpSTR QMAKE_TARGET_COMPANY = The OSCAR Team QMAKE_TARGET_COPYRIGHT = © 2019 The OSCAR Team QMAKE_TARGET_DESCRIPTION = "OpenSource STR.edf Dumper" VERSION = 0.5.0 SOURCES += \ dumpSTR/main.cpp \ dumpSTR/edfparser.cpp HEADERS += \ dumpSTR/SleepLib/common.h \ dumpSTR/edfparser.h OSCAR-code-v1.3.1/dumpSTR/000077500000000000000000000000001417327530600150275ustar00rootroot00000000000000OSCAR-code-v1.3.1/dumpSTR/SleepLib/000077500000000000000000000000001417327530600165265ustar00rootroot00000000000000OSCAR-code-v1.3.1/dumpSTR/SleepLib/common.h000066400000000000000000000006361417327530600201740ustar00rootroot00000000000000/* Common code and junk * * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef COMMON_H #define COMMON_H #include #include #include typedef float EventDataType; #endif // COMMON_H OSCAR-code-v1.3.1/dumpSTR/edfparser.cpp000077700000000000000000000000001417327530600302102../oscar/SleepLib/loader_plugins/edfparser.cppustar00rootroot00000000000000OSCAR-code-v1.3.1/dumpSTR/edfparser.h000077700000000000000000000000001417327530600273222../oscar/SleepLib/loader_plugins/edfparser.hustar00rootroot00000000000000OSCAR-code-v1.3.1/dumpSTR/main.cpp000066400000000000000000000141721417327530600164640ustar00rootroot00000000000000/* Dump an STR.edf file */ #include // #include #include #include typedef float EventDataType; #include "edfparser.h" // using namespace std; void dumpHeader( const EDFHeaderQT hdr ) { qDebug() << "EDF Header:"; qDebug() << "Version " << hdr.version << " Patient >" << hdr.patientident << "<"; qDebug() << "Recording >" << hdr.recordingident << "<"; qDebug() << "Date: " << hdr.startdate_orig.toString(); qDebug() << "Header size (bytes): " << hdr.num_header_bytes; qDebug() << "EDF type: >" << hdr.reserved44 << "<"; qDebug() << "Duration(secs): " << hdr.duration_Seconds; qDebug() << "Number of Signals: " << hdr.num_signals << "\n"; } void ifprint( QString label, QString text) { if ( text.isEmpty() ) return; qDebug() << label << ": " << text; return; } void dumpSignals( const QVector sigs ) { int i = 1; for (auto sig: sigs) { qDebug() << "Signal #" << i++; qDebug() << "Label: " << sig.label; ifprint( "Transducer", sig.transducer_type ); ifprint( "Dimension", sig.physical_dimension ); qDebug() << "Physical min: " << sig.physical_minimum << " max: " << sig.physical_maximum; qDebug() << "Digital min: " << sig.digital_minimum << " max: " << sig.digital_maximum; qDebug() << "Gain: " << sig.gain << " Offset: " << sig.offset; ifprint( "Pre-filter", sig.prefiltering ); qDebug() << "Sample Count: " << sig.sampleCnt; qDebug() << "dataArray is at " << sig.dataArray << "\n"; } } void usage() { qDebug() << "dumpSTR [ options ] filename"; qDebug() << "Options"; qDebug() << "\t-a Show all"; qDebug() << "\t-f ### First day"; qDebug() << "\t-l ### Last day"; qDebug() << "\t-g ### First signal"; qDebug() << "\t-m ### Last signal"; qDebug() << "\t-s Signal list only"; qDebug() << "\t-H Don't print the header"; qDebug() << "\t-S Don't print signals list"; qDebug() << "\t-h or -? This help message"; } int main(int argc, char *argv[]) { // QString homeDocs = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)+"/"; // QCoreApplication::setApplicationName(getAppName()); // QCoreApplication::setOrganizationName(getDeveloperName()); // QCoreApplication::setOrganizationDomain(getDeveloperDomain()); int first = 0, last = 0; int firstSig = 1, lastSig = 0; QApplication a(argc, argv); QStringList args = a.arguments(); if ( args.size() < 2 ) { qDebug() << args[0] << " needs a filename" ; exit(1); } QString filename = args[args.size()-1]; bool showall = false, brief = false; bool skipHeader = false, skipSignals = false; for (int i = 1; i < args.size()-1; i++) { if (args[i] == "-f") first = args[++i].toInt(); else if (args[i] == "-g") firstSig = args[++i].toInt(); else if (args[i] == "-l") last = args[++i].toInt(); else if (args[i] == "-m") lastSig = args[++i].toInt(); else if (args[i] == "-a") showall = true; else if (args[i] == "-s") brief = true; else if (args[i] == "-H") skipHeader = true; else if (args[i] == "-S") skipSignals = true; else if ((args[i] == "-?") || (args[i] == "-h")) { usage(); exit(0); } } EDFInfo str; if ( ! str.Open(filename) ) { qDebug() << "Failed to open" << filename; exit(-1); } if ( ! str.Parse() ) { qDebug() << "Parsing failed on" << filename; exit(-1); } QDate d2 = str.edfHdr.startdate_orig.date(); if (d2.year() < 2000) { d2.setDate(d2.year() + 100, d2.month(), d2.day()); str.edfHdr.startdate_orig.setDate(d2); } QDate date = str.edfHdr.startdate_orig.date(); // each STR.edf record starts at 12 noon int numDays = str.GetNumDataRecords(); qDebug() << str.filename << " starts at " << date << " for " << numDays << " days, ending at " << date.addDays(numDays) << " with " << str.GetNumSignals() << " signals"; if (args.size() == 2) { exit(0); } if ( ! skipHeader) dumpHeader( (str.edfHdr) ); if ( ! skipSignals ) dumpSignals( (str.edfsignals) ); if ( brief ) exit(0); int size = str.GetNumDataRecords(); if (showall) { first = 0; last = size; firstSig = 1; lastSig = str.GetNumSignals(); } if (lastSig == 0 ) lastSig = str.GetNumSignals(); if ((last == 0) || (last > size)) last = size; date = date.addDays(first); // For each data record, representing 1 day each for (int rec = first; rec < last+1; ++rec, date = date.addDays(1)) { qDebug() << "Record no. " << rec << " Date: " << date.toString() ; for (int j = firstSig-1; j < lastSig; j++ ) { // qDebug() << "Signal #" << j; EDFSignal sig = str.edfsignals[j]; // if ( sig == nullptr ) { // qDebug() << "Bad sig pointer at signal " << j; // exit(2); // } if ( ! sig.label.contains("Annotations")) { qint16 * sample = sig.dataArray; // qDebug() << "Sample pointer is " << sample; if (sample == nullptr) { qDebug() << "Bad sample pointer at signal " << j; exit(3); } QString dataStr = ""; if (sig.sampleCnt == 1) { // qDebug() << "Single sample is " << sample[rec]; dataStr.setNum(sample[rec]); // qDebug() << "Datastr is " << dataStr; } else { for (int i = 0; i < sig.sampleCnt; i++ ) { QString num; num.setNum(sample[rec*sig.sampleCnt + i]); dataStr.append(" ").append(num); } } qDebug() << "#" << j+1 << sig.label << dataStr; } } } // qDebug() << "Deleting the edf object"; // delete &str; QThread::sleep(1); qDebug() << "Done"; exit(0); } OSCAR-code-v1.3.1/history/000077500000000000000000000000001417327530600151725ustar00rootroot00000000000000OSCAR-code-v1.3.1/history/README000066400000000000000000000007021417327530600160510ustar00rootroot00000000000000This folder contains the python/GTK script I (jedimark) wrote that eventually turned into SleepyHead I can't honestly tell you if this is the latest version, because the computer I originally wrote this on died. I put it here for project history, reference (and humour at how sad it is.) It requires matplotlib and pygtk to run.. Probably pytz too, I honestly can't remember if it needed any other libs. I've mostly forgotten python since then. OSCAR-code-v1.3.1/history/cms50f/000077500000000000000000000000001417327530600162675ustar00rootroot00000000000000OSCAR-code-v1.3.1/history/cms50f/cms50f_duration_check.py000077500000000000000000000027431417327530600230110ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: UTF-8 -*- # Contec CMS 50F firmware 3.7 sample duration test data = [ dict(a='08 80 80 80 a4 80 80 80', l=6), dict(a='08 80 80 80 d2 80 80 80', l=14), dict(a='08 80 80 80 c8 80 80 80', l=12), dict(a='08 84 80 80 ca 80 80 80', l=34), dict(a='08 80 80 80 a2 80 80 80', l=6), dict(a='08 80 80 80 a4 81 80 80', l=49), dict(a='08 84 80 80 98 80 80 80', l=26), dict(a='08 84 80 80 c2 80 80 80', l=33), dict(a='08 84 80 80 d2 84 80 80', l=206), dict(a='08 84 80 80 aa 81 80 80', l=71), dict(a='08 80 80 80 90 80 80 80', l=3), dict(a='08 84 80 80 84 80 80 80', l=22), dict(a='08 84 80 80 c0 93 80 80', l=843), dict(a='08 80 80 84 9c 82 80 80', l=0), dict(a='08 80 80 85 a8 80 80 80', l=0), dict(a='08 84 80 86 ec 80 80 80', l=0), dict(a='08 84 80 80 bc 84 80 80', l=0), dict(a='08 80 80 81 f0 81 80 80', l=0), dict(a='08 80 80 82 ac 80 80 80', l=0), dict(a='08 80 80 83 b8 80 80 80', l=0), dict(a='08 80 80 84 9c 82 80 80', l=0), dict(a='08 80 80 85 a8 80 80 80', l=0), dict(a='08 84 80 86 ec 80 80 80', l=0) #dict(a='08 80 80 80 93 c0 80 80', l=0) #dict(a='08 80 80 80 93 c0 80 80', l=0) ]; print data for entry in data: d=entry['a'].replace(' ', '').decode("hex") bytes = [ord(d[i]) for i in range(0, len(d) - 1)] #print bytes print entry['a'] duration = ( ( (bytes[1] & 4) << 5) | (bytes[4] ^ 0x80) | ((bytes[5] ^ 0x80) << 8)); bit = duration % 2; duration /= 2; print "d: %d [%02d:%02d] l: %d b: %d" % (duration, duration /60, duration%60, entry['l'] * 3, bit) OSCAR-code-v1.3.1/history/cms50f/dump_cms50f37.py000077500000000000000000000064271417327530600211510ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: UTF-8 -*- # Contec CMS 50F firmware 3.7 dumper # (C) 2014, François Revol #cf. http://elinux.org/Serial_port_programming import serial import sys import time def send_cmd(p, c): print "> %s" % c c = c.replace(' ', '') c = c.decode("hex") #print c.encode("hex") p.write(c) #p.flush() def recv_data(p, l=None): data = '' while l is None or len(data) < l: time.sleep(0.5) if p.inWaiting() < 1: print "No more data..." # XXX: do something? if l is None: break want = max(p.inWaiting(), 1) if l is not None: want = min(want, l - len(data)) # just for nicer hex dump, wrap at 8 bytes want = min(want, 8) #print len d = p.read(want) hx = d.encode("hex") print "< %s" % " ".join(hx[i:i+2] for i in range(0, len(hx), 2)) data += d print return data if len(sys.argv) < 2: print "Usage: %s serialdev" % sys.argv[0] exit(1) #, parity=serial.PARITY_ODD with serial.Serial(port=sys.argv[1], baudrate=115200) as p: send_cmd(p, '7d 81 a2 80 80 80 80 80 80 7d 81 a7 80 80 80 80 80 80 7d 81 a8 80 80 80 80 80 80 7d 81 a9 80 80 80 80 80 80 7d 81 aa 80 80 80 80 80 80 7d 81 b0 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 a2 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 a7 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 a8 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 a9 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 aa 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 b0 80 80 80 80 80 80') recv_data(p) print "------------" send_cmd(p, '7d 81 a7 80 80 80 80 80 80') recv_data(p, 2) send_cmd(p, '7d 81 a2 80 80 80 80 80 80') recv_data(p, 2) send_cmd(p, '7d 81 a0 80 80 80 80 80 80') recv_data(p, 4) send_cmd(p, '7d 81 b0 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 ac 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 b3 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 ad 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 a3 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 ab 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 a4 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 a5 80 80 80 80 80 80') d = recv_data(p) print "timestamp:" packet = d[0:8] if packet[0] == '\x07': print "%02d%02d-%02d-%02d" % ( ord(packet[4]) & ~0x80, ord(packet[5]) & ~0x80, ord(packet[6]) & ~0x80, ord(packet[7]) & ~0x80) packet = d[8:16] if packet[0] == '\x12': print "%02d:%02d:%02d.%02d" % ( ord(packet[4]) & ~0x80, ord(packet[5]) & ~0x80, ord(packet[6]) & ~0x80, ord(packet[7]) & ~0x80) send_cmd(p, '7d 81 af 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 a7 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 a2 80 80 80 80 80 80') recv_data(p) send_cmd(p, '7d 81 a6 80 80 80 80 80 80') d = recv_data(p) print "data:" for i in range(0, len(d) / 8): packet = d[i*8:i*8+8] print "packet %d" % (i) if packet[0] != '\x0f': print "invalid data packet:" print packet.encode("hex") continue if packet[1] != '\x80': print "unknown code for packet: %02x" % ord(packet[1]) print "%d%% %dbpm" % (ord(packet[2]) & ~0x80, ord(packet[3]) & ~0x80) print "%d%% %dbpm" % (ord(packet[4]) & ~0x80, ord(packet[5]) & ~0x80) print "%d%% %dbpm" % (ord(packet[6]) & ~0x80, ord(packet[7]) & ~0x80) OSCAR-code-v1.3.1/history/configure000077500000000000000000000010171417327530600171000ustar00rootroot00000000000000#!/bin/bash command_exists() { type "$1" &> /dev/null } # Get the base directory of the source distribution (where this script is) DIR="$(cd "$(dirname $0)" && pwd)" # Attempt distro-specific Qt5 QMake detection. if command_exists qmake-qt5; then QMAKE=qmake-qt5 elif command_exists qt5-qmake; then QMAKE=qt5-qmake elif command_exists qmake; then QMAKE=qmake fi # If no qmake found, fail. if [[ -z ${QMAKE} ]]; then echo "Missing QMake for Qt5 on system." exit 1 fi # Finally, configure the build. ${QMAKE} "${DIR}" OSCAR-code-v1.3.1/history/cpap.py000066400000000000000000001506241417327530600164770ustar00rootroot00000000000000#!/usr/bin/env python ''' This python script is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This python script is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this script; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' # Author: Mark Watkins # Date: 09/03/2011 # Purpose: CPAP Support # License: GPL #Attempt at faster CPAP Loader import sys import os from struct import * from datetime import datetime as DT from datetime import timedelta,date,time #,datetime,date,time import time from matplotlib.dates import drange from matplotlib.figure import Figure from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas from pylab import * #from pytz import timezone import pytz import gobject import gtk MYTIMEZONE="Australia/Queensland"; localtz=pytz.timezone(MYTIMEZONE) utc = pytz.utc utcoff=time.timezone / -(60*60) Device_Types={ "Unknown":0, "PAP":1, "Oximeter":2, "ZEO":3 } def LookupDeviceType(type): for k,v in Device_Types.iteritems(): if type.lower()==k.lower(): return v return 0 class Event: code=0 time=None data=[] def __init__(self,time,code,data): self.time=time self.code=code self.data=data class Waveform: time=None def __init__(self,time,waveform,size,duration,format,rate): self.time=time self.waveform=waveform self.size=size self.duration=duration self.format=format self.rate=rate class Machine: def __init__(self,brand,model,type): self.brand=brand self.model=model self.type=LookupDeviceType(type) def Open(self): print "in Machine.Open()"; class OxiMeter(Machine): CodeTypes=['Error','Pulse','SpO2'] def __init__(self,brand,model): Machine.__init__(self,brand,model,"Oximeter") import serial class CMS50X(OxiMeter): Baudrate=19200 Timeout=5 Home=os.path.expanduser('~') #LogDirectory=Home+os.sep+"CMS50" #os.system("mkdir "+LogDirectory) def __init__(self): OxiMeter.__init__(self,"Contec","CMS50X") self.Device=None self.devopen=False # Borrowed from PySerial if (os.name=="nt") or (sys.platform=="win32"): self.ports = [] for i in range(256): try: s = serial.Serial(i) self.ports.append( (i, s.portstr)) s.close() # explicit close 'cause of delayed GC in java except serial.SerialException: pass self.Device=self.ports[5] elif os.name=='posix': import glob self.ports=glob.glob('/dev/ttyUSB*') if (len(self.ports)>0): self.Device=self.ports[0] def Open(self): if not self.Device: print "No serial device detected" return False if self.devopen: print "Device is already open" return True try: self.ser=serial.Serial(self.Device,self.Baudrate,timeout=self.Timeout) except: print "Couldn't open",self.Device return False self.ser.flushInput() self.lastpulse=0 self.lastspo2=0 self.devopen=True return True def Close(self): if (self.devopen): ser.close() self.devopen=False def Read(self): while self.devopen: while self.devopen: h=self.ser.read(1) if len(h)>0: if (ord(h[0]) & 0x80): # Sync Bit break else: #print "Timeout!"; self.devopen=False return None c=self.ser.read(4) if len(c)==4: break else: #print "Sync error"; self.devopen=False return None hdr=ord(h) if (hdr & 0x10): alarm=True else: alarm=False if (hdr & 0x8):# or (hdr & 0x20): # (hdr & 0x10)==alarm signal=True else: signal=False wave1=ord(c[0]) wave2=ord(c[1]) pulse=ord(c[2]) spo2=ord(c[3]) return [signal,alarm,wave1,wave2,pulse,spo2] def Save(self,time,code,value): if (not self.start): return delta=time-self.lasttime s=int(((delta.seconds*1000)+delta.microseconds/1000)) self.events[self.start].append(s>>8) self.events[self.start].append(s&255) self.events[self.start].append(code) self.events[self.start].append(value) self.evcnt+=1 self.lasttime=time def Record(self,path): if self.devopen: return (None,None) self.starttime=DT.utcnow() self.lasttime=self.starttime lt=self.lasttime self.Open() lastpulse=0 lastspo2=0 lastalarm=True lastsignal=True self.evcnt=0 wave=[dict(),dict()] wavestart=None self.start=None self.events=dict() while self.devopen: D=self.Read() if not D: continue t=DT.utcnow(); d=t-lt if D[0]!=lastsignal or ((t-self.lasttime)>timedelta(seconds=64)): self.Save(t,0,D[0]) lastsignal=D[0] if D[1]!=lastalarm or ((t-self.lasttime)>timedelta(seconds=64)): self.Save(t,1,D[1]) lastalarm=D[1] if (not self.start or (d>timedelta(microseconds=30000))): #Lost serial sync for wavefom self.start=t print "Starting new event chunk",self.start self.events[self.start]=bytearray() lt=t if D[1]: continue if (not wavestart or (d>timedelta(microseconds=30000))): #Lost serial sync for wavefom wavestart=t wave[0][wavestart]=bytearray() wave[1][wavestart]=bytearray() print "Starting new wave chunk",wavestart wave[0][wavestart].append(D[2]) wave[1][wavestart].append(D[3]) if (D[4]!=lastpulse) or ((t-self.lasttime)>timedelta(seconds=64)): self.Save(t,2,D[4]) lastpulse=D[4] if (D[5]!=lastspo2) or ((t-self.lasttime)>timedelta(seconds=64)): self.Save(t,3,D[5]) lastspo2=D[5] self.Close() if (path[-1]!=os.sep): path+=os.sep ed=sorted(self.events.keys()) basename=path+ed[0].strftime("CMS50-%Y%m%d-%H%M%S") efname=basename+".001" magic=0x35534d43 #CMS5 f=open(efname,"wb"); j=0 for k,v in self.events.iteritems(): header=bytearray(16) timestamp=time.mktime(k.timetupple()) l=len(v) struct.pack_into('time[1]): continue if (endtime[1]): continue if (endself.sessiontimes[s][1]): val+=len(self.session[s][type]); else: for e in self.session[s][type]: if (e.time>=start) and (e.time<=end): val+=1 return val def FirstLastEventTime(self,field,start,end): sess=self.GetEvSessions(start,end) a1=self.sessiontimes[sess[0]][0] a2=self.sessiontimes[sess[-1]][1] #if (a1>=start): # st=a1 #else: st=a1 for e in self.session[sess[0]][field]: if (e.time>=start): st=e.time break; #if (a2<=end): #et=a2 #else: et=a2 for e in self.session[sess[-1]][field]: if (e.time>=end): et=e.time break; return (st,et) def GetTotalTime(self,start,end): t=timedelta(seconds=0) sess=self.GetEvSessions(start,end) for s in sess: #print self.sessiontimes[s] a1=self.sessiontimes[s][0] a2=self.sessiontimes[s][1] d=a2-a1 if a1end: d-=a2-end #print s,a2-a1 t+=d return t def GetEvents(self,type,start,end): if type not in self.CodeTypes: print "Unrecognized cpap code",field return None sess=self.GetEvSessions(start,end) E=[] for s in sess: for e in self.session[s][type]: if e.time>=start and e.time<=end: E.append(e) return E def GetEventsPlot(self,type,start,end,dc=None,di=0,padsession=False): if type not in self.CodeTypes: print "Unrecognized cpap code",field return None sess=self.GetEvSessions(start,end) T=[] D=[] laste=None firste=None for s in sess: for e in self.session[s][type]: if e.time>=start and e.time<=end: if not firste and padsession: firste=e D.append(0) T.append(e.time) T.append(e.time) if dc: D.append(dc) else: D.append(e.data[di]) laste=e if padsession: if laste: D.append(0) T.append(laste.time) return (T,D) def GetFlowPlots(self,start,end): sess=self.GetFlowSessions(start,end) T=[] D=[] for s in sess: X=[] Y=[] for w in self.flowrate[s]: d=timedelta(microseconds=w.rate*1000000.0) t=w.time for i in w.waveform: if t>=start and t<=end: Y.append(i) X.append(t) t+=d T.append(X) D.append(Y) return (T,D) def ScanMachines(self,path): print "Pure virtual function" exit(1) def OpenSD(self): self.machine=dict() self.session=dict() self.sessiontimes=dict() self.flowrate=dict() self.flowtimes=dict(); if os.name=="posix": posix_mountpoints=["/media","/mnt"] d=[] for i in posix_mountpoints: try: a=os.listdir(i) for j in range(0,len(a)): a[j]=i+os.sep+a[j] #print j d.extend(a) except: 1 elif (os.name=="nt") or (sys.platform=="win32"): #Meh.. i'll figure this out later. d=['D:','E:','F:','G:','H:','I:','J:'] #elif sys.platform=="darwin": #Darwin is posix aswell, but where? # d=[] r=0 if not len(d): print "I've have no idea where for an SDCard on",os.name,sys.platform return 0 print "Looking for CPAP data in",d for i in d: if self.ScanMachines(i): r+=1 return r def GetDays(self,numdays=7,date=None): DAYS=[] if (not date): dt=DT.now()#localtz.localize(DT.utcnow())-timedelta(hours=24); else: dt=date for i in range(0,numdays): d=dt.date(); (sleep,wake)=cpap.GetBedtime(dt) if sleep!=None: ln=wake-sleep b=cpap.GetTotalTime(sleep,wake) DAYS.append([d,sleep,wake,ln,b]) dt-=timedelta(hours=24) return DAYS class PRS1(CPAP): codes=dict() codes[0]=['UN1',[2,1]] codes[1]=['UN2',[2,1]] codes[2]=['PR',[2,1]] codes[3]=['BP',[2,1,1]] codes[4]=['PP',[2,1]] codes[5]=['RE',[2,1]] codes[6]=['OA',[2,1]] codes[7]=['CA',[2,1]] codes[0xa]=['H',[2,1]] codes[0xb]=['UNB',[2,2]] codes[0xc]=['FL',[2,1]] codes[0xd]=['VS',[2]] codes[0xe]=['UNE',[2,1,1,1]] codes[0xf]=['CSR',[2,2,1]] codes[0x10]=['UN10',[2,2,1]] codes[0x11]=['LR',[2,1,1]] codes[0x12]=['SUM',[1,1,2]] def __init__(self): CPAP.__init__(self,"Philips Respironics","System One") def ScanMachines(self,path): try: d=os.listdir(path); r=d.index("P-Series"); except: return False path+=os.sep+d[r]; try: d=os.listdir(path); except: print "Path",path,"unreadable" return False prs1unit=[] l=0 for f in d: if (f[0]!='P'): continue if (f[1].isdigit()): if f not in self.machine.keys(): self.machine[f]=[] self.machine[f].append(path+os.sep+f) l+=1 if not l: print "No",self.model,"machine data stored under",path return False return True def OpenMachine(self,serial): if serial not in self.machine.keys(): print "Couldn't open device!" return False self.session=dict() self.sessiontimes=dict() self.flowrate=dict() self.flowtimes=dict(); for path in self.machine[serial]: self.ReadMachineData(path,serial) def ReadMachineData(self,path,serial): try: d=os.listdir(path); r=d.index("p0"); except: print "Expected PRS1's p0 directory, and couldn't find it",path return False path+=os.sep+"p0" try: df=os.listdir(path); except: print "Couldn't read directory" return False r=0 for f in df: filename=f e2=f.rfind('.') if (e2<0): continue ext=int(f[e2+1:]) seq=int(f[0:e2]) if (ext==2) and (not seq in self.session.keys()): if self.Read002(path,filename): r+=1 elif (ext==5) and (not seq in self.flowrate.keys()): if self.Read005(path,filename): r+=1 if (r>0): print "Loaded",r,"files for",serial return True def Read002(self,path,filename): fn=path+os.sep+filename try: f=open(fn,'rb'); except: print "Couldn't Open File",fn return False header=f.read(16) if (len(header)<16): print "Not enough header data in",filename f.close() return False sm=0 for i in range(0,15): sm+=ord(header[i]) sm&=0xff h1=ord(header[0]); filesize,=unpack_from('0): #These events are also classed as vibratory snore E=Event(td,c,fields) self.session[sequence]['VS'].append(E); if (c==2) or (c==3): #CPAP Pressure fields[0]/=10.0 if c==3: fields[1]/=10.0 #Bipap E=Event(d,c,fields) #print E.time.astimezone(localtz),self.CodeTypes[gc],fields self.session[sequence][self.CodeTypes[gc]].append(E); self.sessiontimes[sequence]=[starttime,td] return True def Read005(self,path,filename): #print "Importing file",filename fn=path+os.sep+filename try: f=open(fn,'rb'); except: print "Couldn't Open File",fn return False done=0 blocks=0; starts=None while not done: header=f.read(24) if (len(header)<24): if (blocks==0): print "Not enough header data in",filename f.close() return False done=1 break; sm=0 for i in range(0,23): sm+=ord(header[i]) sm&=0xff h1=ord(header[0]); blocksize,=unpack_from('self.xlimits[1]): #check start and end are within xlimits print "Creating Highlights out of xlimit area is a sucky idea in matplotlib"; self.HL[index]=self.ax.axvspan(start,end,facecolor=color,alpha=0.5) #self.ax.draw_patches(self.HL[index]) self.ResetLimits() def SetXLim(self,start,end): self.xlimits=[start,end] self.ax.set_xlim(self.xlimits) def SetYLim(self,bottom,top): self.ylimits=[bottom,top] self.ax.set_ylim(self.ylimits) def ResetLimits(self): self.ax.set_xlim(self.xlimits) self.ax.set_ylim(self.ylimits) def SetDateTicks(self): e=self.xlimits[1]-self.xlimits[0] self.ax.xaxis.set_major_formatter(DateFormatter("%H:%M",tz=localtz)) if e>=timedelta(hours=10): self.ax.xaxis.set_major_locator(HourLocator(range(0,100,2),tz=localtz)) self.ax.xaxis.set_minor_locator(MinuteLocator(range( 0,100,10),tz=localtz)) elif e>=timedelta(hours=4): self.ax.xaxis.set_major_locator(HourLocator(range(0,100,1),tz=localtz)) self.ax.xaxis.set_minor_locator(MinuteLocator(range( 0,100,5),tz=localtz)) elif e>=timedelta(seconds=3600): self.ax.xaxis.set_major_locator(MinuteLocator(range(0,100,30),tz=localtz)) self.ax.xaxis.set_minor_locator(MinuteLocator(range( 0,100,1),tz=localtz)) elif e>=timedelta(seconds=1200): self.ax.xaxis.set_major_locator(MinuteLocator(range(0,100,5),tz=localtz)) self.ax.xaxis.set_minor_locator(SecondLocator(range( 0,100,15),tz=localtz)) elif e>=timedelta(seconds=300): self.ax.xaxis.set_major_locator(MinuteLocator(range(0,100,1),tz=localtz)) self.ax.xaxis.set_minor_locator(SecondLocator(range(0,100,5),tz=localtz)) else: self.ax.xaxis.set_major_locator(SecondLocator(range(0,100,30),tz=localtz)) self.ax.xaxis.set_minor_locator(SecondLocator(range(0,100,1),tz=localtz)) self.ax.xaxis.set_major_formatter(DateFormatter("%H:%M:%S",tz=localtz)) class LeaksGraph(Graph): def __init__(self,cpap,xlim=[0,0],ylim=[0,129],grid=True): Graph.__init__(self,"Leak Rate") #self.ax=ax self.machine=cpap self.Create() self.ylimits=ylim self.grid=grid self.xlimits=xlim self.T=[] self.D=[] def Update(self,start,end): if self.xlimits: if (start==self.xlimits[0]) and (end==self.xlimits[1]): return #(start,end)=self.machine.FirstLastEventTime('LR',start,end) (self.T,self.D)=self.machine.GetEventsPlot('LR',start=start,end=end,padsession=True) self.xlimits=[self.T[0],self.T[-1]] avg=sum(self.D)/len(self.D) for i in range(0,len(self.D)): self.D[i]-=avg; print "Average Leaks:",avg def Plot(self): self.ax.cla() self.SetTitle(self.name) if (self.grid): self.ax.grid(True); if len(self.T)>0: self.ax.plot_date(self.T,self.D,'black',aa=True,tz=localtz) self.ax.fill_between(self.T,self.D,0,color='gray') self.SetDateTicks() self.ax.yaxis.set_major_locator(MultipleLocator(20)) self.ax.yaxis.set_minor_locator(MultipleLocator(5)) self.ResetLimits() class PressureGraph(Graph): def __init__(self,cpap,xlim=[0,0],ylim=[1,20],grid=True): self.name="Pressure" #self.ax=ax self.machine=cpap self.Create() self.ylimits=ylim self.grid=grid self.xlimits=xlim self.T=[] self.D=[] def Update(self,start,end): if self.xlimits: if (start==self.xlimits[0]) and (end==self.xlimits[1]): return #(start,end)=self.machine.FirstLastEventTime('LR',start,end) self.xlimits=[start,end] (self.T,self.D)=self.machine.GetEventsPlot('PR',start=start,end=end) (self.T1,self.D1)=self.machine.GetEventsPlot('BP',start=start,end=end,di=0) (self.T2,self.D2)=self.machine.GetEventsPlot('BP',start=start,end=end,di=1) #for i in range(0,len(self.D)): # self.D[i]/=10.0; #avg=sum(self.D)/len(self.D) #print "Average Pressure:",avg def Plot(self): self.ax.cla() self.SetTitle(self.name) if (self.grid): self.ax.grid(True); if len(self.T)>0: self.ax.plot_date(self.T,self.D,'green',aa=True,tz=localtz) if (len(self.T1)>0): self.ax.plot_date(self.T1,self.D2,'orange',aa=True,tz=localtz) if (len(self.T2)>0): self.ax.plot_date(self.T2,self.D2,'purple',aa=True,tz=localtz) self.SetDateTicks() self.ax.yaxis.set_major_locator(MultipleLocator(5)) self.ax.yaxis.set_minor_locator(MultipleLocator(1)) self.ResetLimits() class SleepFlagsGraph(Graph): colours=['','y','r','k','b','c','m','g'] flags=['','RE','VS','FL','H','OA','CA','CSR'] barcolors=['w','#ffffd0','#ffdfdf','#efefef','#d0d0ff','#cfefff','#ebcdef','#dfffdf','w']; marker='.' def __init__(self,cpap,waveform,xlim=[0,0],ylim=[0,10],grid=True): self.name="Sleep Flags" self.waveform=waveform #self.ax=ax self.machine=cpap self.Create(height=175) self.ylimits=[0,len(self.flags)] self.grid=grid self.xlimits=xlim self.T=dict() self.D=dict() self.canvas.mpl_connect('pick_event', self.onpick) self.canvas.mpl_connect('button_press_event', self.on_press) self.canvas.mpl_connect('button_release_event', self.on_release) #self.canvas.mpl_connect('scroll_event', self.on_scroll) self.lastscroll=DT.now() self.scrollsteps=0 #self.canvas.mpl_connect('motion_notify_event', self.on_motion) def onpick(self,event): N = len(event.ind) if not N: return True return True thisline = event.artist xdata, ydata = thisline.get_data() ind = event.ind wavedelta=timedelta(seconds=300) #self.waveform.xlimits[1]-self.waveform.xlimits[0] d=timedelta(seconds=wavedelta.seconds/2) self.waveform.xlimits[0]=xdata[ind][0]-d if (self.waveform.xlimits[0](self.xlimits[1]-wavedelta)): self.waveform.xlimits[0]=self.xlimits[1]-wavedelta self.waveform.xlimits[1]=self.waveform.xlimits[0]+wavedelta; self.Highlight(self.waveform.xlimits[0],self.waveform.xlimits[1],'orange') self.Redraw() self.waveform.ResetLimits() self.waveform.SetDateTicks() self.waveform.Redraw() def do_scroll(self,steps,event): ct=DT.now() if (cttimedelta(seconds=3600)): wd=timedelta(seconds=300) self.waveform.xlimits[0]=d1-timedelta(seconds=wd.seconds/2); self.waveform.xlimits[1]=self.waveform.xlimits[0]+wd else: self.waveform.xlimits[0]=d1 self.waveform.xlimits[1]=d2 if (self.waveform.xlimits[0]self.xlimits[1]): self.waveform.xlimits[1]=self.xlimits[1] self.waveform.xlimits[0]=self.xlimits[1]-wd self.Highlight(self.waveform.xlimits[0],self.waveform.xlimits[1],'orange') self.Redraw() self.waveform.ResetLimits(); self.waveform.SetDateTicks() self.waveform.Redraw() def Update(self,start,end): if self.xlimits: if (start==self.xlimits[0]) and (end==self.xlimits[1]): return #(start,end)=self.machine.FirstLastEventTime('LR',start,end) self.xlimits=[start,end] #if self.waveform: #self.waveform.xlimits[0]=start #self.WaveDelta=timedelta(seconds=300) #self.waveform.xlimits[1]=start+self.WaveDelta j=0 for i in self.flags: if (i=="CSR"): self.T[i]=[] E=self.machine.GetEvents(i,start=start,end=end) for e in E: r=e.time-timedelta(seconds=e.data[1])-timedelta(seconds=e.data[0]/2) self.T[i].append(r) self.D[i]=[j]*len(self.T[i]) elif (i!=""): (self.T[i],self.D[i])=self.machine.GetEventsPlot(i,start=start,end=end,dc=j) j+=1 def Plot(self): self.ax.cla() self.SetTitle(self.name) if (self.grid): self.ax.grid(True); j=0; for i in self.flags: if (i!=""): if (len(self.T[i])>0): self.ax.plot_date(self.T[i],self.D[i],self.colours[j]+self.marker,picker=5,aa=False,tz=localtz,alpha=1) j+=1 self.SetDateTicks() self.ax.yaxis.set_major_locator(MultipleLocator(1)) self.ax.set_yticklabels(self.flags) yTicks=[0] yTicks.extend(self.ax.get_yticks()) h=(yTicks[1]-yTicks[0]) for i in range(1,len(yTicks)): yTicks[i]-=h/2 a1=date2num(self.xlimits[0]) a2=date2num(self.xlimits[1]) self.ax.barh(yTicks, [a2-a1]*len(yTicks), height=h, left=a1, color=self.barcolors,alpha=0.5) self.ResetLimits() class WaveformGraph(Graph): colours=['y','r','k','b','c','m','g'] flags=['RE','VS','FL','H','OA','CA'] def __init__(self,cpap,xlim=[0,0],ylim=[-69,69],grid=True): self.name="Flow Rate Waveform" #self.ax=ax self.machine=cpap self.Create() self.ylimits=ylim self.grid=grid self.xlimits=xlim self.T=[] self.D=[] self.FT=dict() self.FD=dict() self.canvas.mpl_connect('button_press_event', self.on_press) self.canvas.mpl_connect('button_release_event', self.on_release) self.canvas.mpl_connect('scroll_event', self.on_scroll) self.lastscroll=DT.now() self.scrollsteps=0 self.sg=None def set_sleepgraph(self,sg): self.sg=sg def on_press(self,event): if event.inaxes != self.ax: return contains, attrd = self.ax.patch.contains(event) if not contains: return #print 'event contains', self.ax.patch.xy x0, y0 = self.ax.patch.xy self.press = event.xdata, event.ydata def on_release(self,event): if event.inaxes != self.ax: return #if event.inaxes != self.ax: return #minx=min(event.xdata,self.press[0]); #maxx=max(event.xdata,self.press[0]); d1=num2date(self.press[0],tz=localtz) d2=num2date(event.xdata,tz=localtz) d=d2-d1 self.xlimits[0]-=d self.xlimits[1]-=d if self.xlimits[0]self.sg.xlimits[1]: self.xlimits[1]=self.sg.xlimits[1] self.xlimits[0]=self.xlimits[1]-d #update SleepGraph if (self.sg): self.sg.Highlight(self.xlimits[0],self.xlimits[1],'orange') self.sg.Redraw() self.ResetLimits() self.SetDateTicks() self.Redraw() def do_scroll(self,steps,event): ct=DT.now() if (ctlastpressure: cod="PUP" elif p0): self.ax.plot_date(self.FT[i],self.FD[i],self.colours[j]+'d',aa=True,alpha=.8,tz=localtz) self.ax.vlines(self.FT[i],50,-50,self.colours[j],lw=1,alpha=0.4) j+=1 j=0 for i in range(0,len(self.T)): self.ax.plot_date(self.T[i],self.D[i],'green',aa=True,tz=localtz,alpha=0.7) self.SetDateTicks() self.ax.yaxis.set_major_locator(MultipleLocator(20)) self.ax.yaxis.set_minor_locator(MultipleLocator(5)) if (len(self.FT['PUP'])): self.ax.plot_date(self.FT['PUP'],self.FD['PUP'],'k^',aa=True,alpha=.8,tz=localtz) if (len(self.FT['PDN'])): self.ax.plot_date(self.FT['PDN'],self.FD['PDN'],'kv',aa=True,alpha=.8,tz=localtz) if (len(self.FT['PP'])): self.ax.plot_date(self.FT['PP'],self.FD['PP'],'r.',aa=True,alpha=.8,tz=localtz) for E in self.FT['CSR']: e=E.time-timedelta(seconds=E.data[1]); s=e-timedelta(seconds=E.data[0]) self.ax.axvspan(s,e,facecolor='#d0ffd0'); #self.Highlight(s,e,color="#d0ffd0",index=j) self.ResetLimits() def AboutBox(a): txt='''SleepyHead v0.02 Details: Author: Mark Watkins (jedimark) Homepage: http://sleepyhead.sourceforge.net Please report any bugs on sourceforge. License: This software is released under the GNU Public Licence. Disclaimer: This is not medical software. Any output this program produces should not be used to make medical decisions. Special Thanks: Mike Hoolehan - Check out his awesome Onkor Project Troy Schultz - For great technical advice Mark Bruscke - For encouragement and advice and to the very awesome CPAPTalk Forum ''' msg=gtk.MessageDialog(flags=gtk.DIALOG_MODAL,type=gtk.MESSAGE_INFO,buttons=gtk.BUTTONS_CLOSE) msg.set_markup(txt) msg.run() msg.destroy() def CreateMenu(): file_menu = gtk.Menu() open_item = gtk.MenuItem("_Backup SD Card") save_item = gtk.MenuItem("_Print") quit_item = gtk.MenuItem("E_xit") file_menu.append(open_item) file_menu.append(save_item) file_menu.append(quit_item) quit_item.connect_object ("activate", lambda x: gtk.main_quit(), "file.quit") open_item.show() save_item.show() quit_item.show() help_menu = gtk.Menu() about_item = gtk.MenuItem("_About") about_item.connect_object("activate",AboutBox,"help.about") help_menu.append(about_item) about_item.show() file_item = gtk.MenuItem("_File") file_item.show() help_item = gtk.MenuItem("_Help") help_item.show() menu_bar = gtk.MenuBar() menu_bar.show() file_item.set_submenu(file_menu) menu_bar.append(file_item) help_item.set_submenu(help_menu) menu_bar.append(help_item) return menu_bar class DailyGraphs: def __init__(self,cpap): self.cpap=cpap self.layout=gtk.ScrolledWindow() self.layout.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) self.vbox = gtk.VBox() self.layout.add_with_viewport(self.vbox) self.graph=dict(); self.graph['Waveform']=WaveformGraph(cpap) self.graph['Leaks']=LeaksGraph(cpap) self.graph['Pressure']=PressureGraph(cpap) self.graph['SleepFlags']=SleepFlagsGraph(cpap,self.graph['Waveform']) self.graph['Waveform'].set_sleepgraph(self.graph['SleepFlags']) self.vbox.pack_start(self.graph['SleepFlags'].canvas,expand=False) self.vbox.pack_start(self.graph['Waveform'].canvas,expand=False) self.vbox.pack_start(self.graph['Leaks'].canvas,expand=False) self.vbox.pack_start(self.graph['Pressure'].canvas,expand=False) self.machines=gtk.combo_box_new_text() for mach in cpap.machine.keys(): self.machines.append_text(mach) self.datesel=gtk.Calendar() self.textbox=gtk.TextView(buffer=None) self.textbox.set_editable(False) self.datesel.connect('month_changed',self.cal_month_selected,cpap) self.datesel.connect('day_selected',self.cal_day_selected) self.machines.connect("changed",self.select_machine,cpap) self.databox = gtk.VBox(homogeneous=False) self.rescanbutton=gtk.Button("_Rescan Media") self.rescanbutton.connect('pressed',self.pushed_rescan) #self.zeobutton=gtk.Button("Load _ZEO Data") #self.oxibutton=gtk.Button("Load _Oximeter Data") self.databox.pack_start(self.machines,expand=False,padding=2) self.databox.pack_start(self.datesel,expand=False,padding=2) self.databox.pack_start(self.rescanbutton,expand=False,padding=0) #self.databox.pack_start(self.zeobutton,expand=False,padding=0) #self.databox.pack_start(self.oxibutton,expand=False,padding=0) self.databox.pack_start(self.textbox,expand=True,padding=2) self.machines.set_active(0) #self.cal_month_selected(self.datesel,cpap) #self.cal_day_selected(self.datesel) def select_machine(self,combo,cpap): msg=gtk.MessageDialog(type=gtk.MESSAGE_INFO,buttons=gtk.BUTTONS_NONE,message_format="Please wait, Loading CPAP Data") msg.show_all() gtk.gdk.window_process_all_updates() mach=combo.get_active_text(); cpap.OpenMachine(mach) msg.destroy() self.cal_month_selected(self.datesel,self.cpap) self.cal_day_selected(self.datesel) def pushed_rescan(self,event): self.cpap.OpenSD() mach=self.machines.get_active_text(); self.machines.get_model().clear() j=0 cmi=-1 for m in cpap.machine.keys(): i=self.machines.insert_text(j,m) if (m==mach): cmi=j j+=1 if (cmi>=0): self.machines.set_active(cmi) else: self.machines.set_active(0) #cpap.OpenMachine(mach) self.cal_month_selected(self.datesel,self.cpap) #self.cal_day_selected(self.datesel) def Draw(self): for k,v in self.graph.iteritems(): v.Redraw() def ShowGraphs(self,show): if show: vis=True else: vis=False for i in self.graph.keys(): self.graph[i].canvas.set_visible(vis) def Update(self,start,end): for k,v in self.graph.iteritems(): v.Update(start,end) sess=cpap.GetFlowSessions(start,end) if (len(sess)>0): wvis=True; else: wvis=False; self.graph['Waveform'].canvas.set_visible(wvis) text="Date: "+start.astimezone(localtz).strftime("%Y-%m-%d")+"\n\n" text+="Bedtime: "+start.astimezone(localtz).strftime("%H:%M:%S")+"\n" text+="Waketime: "+end.astimezone(localtz).strftime("%H:%M:%S")+"\n\n" tt=cpap.GetTotalTime(start,end) text+="Total Time: "+str(tt)+"\n\n" if not wvis: text+="No Waveform Data Available\n\n" oa=cpap.CountEvents('OA',start,end) h=cpap.CountEvents('H',start,end) ah=oa+h ca=cpap.CountEvents('CA',start,end) fl=cpap.CountEvents('FL',start,end) vs=cpap.CountEvents('VS',start,end) re=cpap.CountEvents('RE',start,end) PR=cpap.GetEvents('PR',start,end) if (len(PR)>0): avgp=0 laste=PR[0] lastp=int(PR[0].data[0]*10) lastt=PR[0].time TPR=[timedelta(seconds=0)]*256 don=False totalptime=timedelta(0) for e in PR[1:]: p=int(e.data[0]*10) TPR[lastp]+=(e.time-lastt) totalptime+=(e.time-lastt) lastt=e.time lastp=p #if (not don): # TPR[lastp]+=lastt- np=timedelta(seconds=totalptime.seconds*.9) npc=timedelta(seconds=0) npp=0 lastp=0 for i in range(0,256): lpc=npc npc+=TPR[i] if (npc>=np): s2=1-(float(lpc.seconds)/float(npc.seconds)) d=(i-lastp)/10.0 npp=(lastp/10.0)+(s2*d) break if TPR[i]>timedelta(seconds=0): lastp=i avgp=0 sm1=0 sm2=0 sm3=0 for i in range(0,256): if TPR[i]>timedelta(seconds=0): s=float(TPR[i].seconds)/float(totalptime.seconds) sm1+=s*float(i) sm2+=s sm3=(float(i)/10.0)*TPR[i].seconds #avgp=sm3/totalptime.seconds avgp=sm1/sm2/10.0 #Weighted Average else: avgp=0 npp=0 LK=cpap.GetEvents('LR',start,end); avgl=0 for e in LK: avgl+=e.data[0]-19 avgl/=len(LK) CSR=cpap.GetEvents('CSR',start,end); dur=0 for e in CSR: dur+=e.data[0]; csr=(100.0/tt.seconds)*dur text+="Average Pressure=%(#)0.2f\n"%{'#':avgp} text+="90%% Pressure=%(#)0.2f\n\n"%{'#':npp} text+="CSR %% of night=%(#)0.2f\n" % {"#":csr} s=tt.seconds/3600.0 text+="OA=%(#)0.2f\n"%{'#':oa/s} text+="H=%(#)0.2f\n"%{'#':h/s} text+="CA=%(#)0.2f\n"%{'#':ca/s} text+="FL=%(#)0.2f\n"%{'#':fl/s} text+="VS=%(#)0.2f\n"%{'#':vs/s} text+="RE=%(#)0.2f\n"%{'#':re/s} text+="AHI=%(#)0.2f\n\n"%{'#':ah/s} text+="Leak=%(#)0.2f\n"%{'#':avgl} buf=self.textbox.get_buffer() buf.set_text(text) #self.date.set_text()) # self.bedtime.set_text("Bedtime: "+start.astimezone(localtz).strftime("%H:%M:%S")) # self.waketime.set_text("Waketime: "+end.astimezone(localtz).strftime("%H:%M:%S")) def Plot(self): for k,v in self.graph.iteritems(): v.Plot() self.graph['Waveform'].ResetLimits() def cal_month_selected(self,cal,cpap): (y,m,d)=cal.get_date(); d=1 m+=2 if (m>11): y+=1 m%=12 ldom=DT(y,m,d,0,0,0)-timedelta(hours=1) #print "Getting",ldom.day,"days back from",ldom D=cpap.GetDays(ldom.day,date=ldom) cal.freeze() for i in range(0,ldom.day-1): cal.unmark_day(i) for i in D: cal.mark_day(i[0].day) cal.thaw() def cal_day_selected(self,cal): (y,m,d)=cal.get_date() dat=DT(y,m+1,d,0,0,0) (st,et)=cpap.GetBedtime(dat) if st: msg=gtk.MessageDialog(type=gtk.MESSAGE_INFO,buttons=gtk.BUTTONS_NONE,message_format="Updating Plots - Please wait") msg.show_all() gtk.gdk.window_process_all_updates() self.ShowGraphs(True) #print "Bedtime",st.astimezone(localtz),"Wakeup",et.astimezone(localtz) self.Update(st,et) self.Plot() self.Draw() msg.destroy() else: self.ShowGraphs(False) text="No data available for selected date" buf=self.textbox.get_buffer() buf.set_text(text) path="/home/mark/.sleepyhead/CMS50" #cms50=CMS50X() #(event,wave)=cms50.Record(path) #exit(1) cpap=PRS1() cpap.OpenSD() win=gtk.Window() win.connect("destroy", lambda x: gtk.main_quit()) win.set_default_size(1200,680) win.set_title("SleepyHead v0.02") mainbox=gtk.VBox() mainbox.pack_start(CreateMenu(),expand=False) notebook=gtk.Notebook() notebook.unset_flags(gtk.CAN_FOCUS) dailybox=gtk.HBox() spo2box=gtk.HBox() mainbox.pack_start(notebook,expand=True) DG=DailyGraphs(cpap) dailybox.pack_start(DG.databox,expand=False) dailybox.pack_start(DG.layout,expand=True) page1=notebook.insert_page(dailybox,gtk.Label("Daily")) #page2=notebook.insert_page(dailybox,gtk.Label("Overview")) #page3=notebook.insert_page(spo2box,gtk.Label("SpO2")) notebook.set_current_page(page1) win.add(mainbox) win.show_all() gtk.main() OSCAR-code-v1.3.1/history/migration.sh000077500000000000000000000013121417327530600175170ustar00rootroot00000000000000#! /bin/bash # if [ $# != 1 ] ; then echo $0 requires a SleepyHead data folder name ; exit ; fi SRC=~/Documents/$1 DEST=~/Documents/OSCAR_Data # echo This will create a new folder called $DEST with a copy of the data in $SRC echo suitably modified to work with OSCAR # #echo Copying $SRC to $DEST may take a while if you have many months of data cp -r $SRC $DEST cd $DEST # for f in *.xml ; do echo Fixing $f ; sed -i s/SleepyHead/OSCAR/ $f ; done # cd Profiles for f in * ; do if [[ -d ${f} ]]; then echo Entering folder $f ; cd "$f" ; for ff in *.xml ; do echo Fixing $ff ; sed -i s/SleepyHead/OSCAR/ $ff ; done cd .. fi done # echo All done! # OSCAR-code-v1.3.1/makedoxy.bat000066400000000000000000000001231417327530600157760ustar00rootroot00000000000000setlocal set path="c:\Program Files (x86)\Graphviz2.38\bin";%path% doxygen endlocalOSCAR-code-v1.3.1/oscar/000077500000000000000000000000001417327530600146005ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/Graphs/000077500000000000000000000000001417327530600160245ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/Graphs/MinutesAtPressure.cpp000066400000000000000000002022401417327530600221720ustar00rootroot00000000000000/* MinutesAtPressure Graph Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ /* MinutesAtPressure Graph The MinutesAtPressure (TimeAtPressure) Graph is the Pressure Graph transposed - with similar look and feel as other graphs. The Y-Axis (pressure) becomes the X-Axis (MinutesAtPressure). The X-Axis (Time) becomes the Y-Axis (duration in minutes). The MinutesAtPressure Graph uses the configurable Plot and Overlay Settings from the Pressure Graph, EPAP and IPAP(CPAP_Pressure) will both be conditionally displayed Events (H, CA, OA, UA) are displayed as tick marks similar to the Pressure Graph. Events (LL, CSR) are also displayed like the pressure Graph with lightgrap or lightgreen background coloring. This gives the MinutesAtPressure a similar look and feel as other graphs. The MetaData (top label Bar) now contains the total duration for each pressure pressure bucket (range of pressures) as well as the events that occured. The MinutesAtPressure tool-tips now contains just the names of the Event Ticks (similar to the pressure Graph) The tooltip information is minimal and only contains the name of the event and the number of occurrences for that pressure range. On Mouse Over 1. Each data point will be highlight with a small dot. 2. Tooltips will be displayed for the appropriate Pressure Range and the respective data point will be displayed with a square box - similar to the current current implementation. When there is no data available (the time selected is between sessions in a day) then "No Data" will be displayed. When no graphs are selected then "Plots Disabled" will be displayed - just like the Pressure Graph. The X-Axis start and end pressure now use the Machine limits. If plot data has a data outside the range is appopaitely updated. Refactoring was done to 1. Reduce duplicate/similar instances of code 2. Shorten long methods 3. Enhance readability, 5. Add Dynamic Meta data for Pressure times and Events. 6. insure data accuracy. 7. Conditional compilation for features Total Duration displayed by Minutes AT Pressure graphs is based on the actual waveform form files. In some cases this duration is different that the session time indicated in the daily session information - due to reasons below. Issues found while testing based on 1.2.0 base. * Event Flags do not display short SPANS. * Pressure Graph does not display short SPANS. * sessions times (as displayed in daily session information) are not always the same as the first and last time in the waveforms. for example for resmed the the session start time is about 40 seconds before the first sample in the waveform (eventlist). also the session end time is not always the same as the last sample in the waveform. typically 1 second different (either before or after waveform). * multiple eventlists in a session. time betwwen eventlists is small - under 1-2 minutes. This is not the same as multiple sessions per day. Naming convention pixel == at point on the display area. pixels == distance between to pixels Pressure == a value in cmH20 Bucket == A range of pressures to collect the ammount of time in that pressure range some messages from Apnea Board. 0000042: Graphs Daily "Time at Pressure" graph x-axis goes to -1 and plot trace showing behind other graphs Graphs Daily "Time at Pressure" graph x-axis goes to -1 and plot trace showing behind other graphs On the "Daily" tab, the Time at Pressure plot's x-axis minimum is a value of -1. i Also, the plot traces on this graph jump to unreasonably high values (e.g., 508) which are shown behind the other graphs above it. Picture available on GitLab All 2-high All Issue 0000049 from SH 1.1.0 GitLab Issue List 0000038: Graphs any "Time at Pressure graph does not show the right pressure Graphs any "Time at Pressure graph does not show the right pressure, i e.g. CPAP with constant pressure at 7.5 cmH2O , but the Time at Pressure graph only has a peak around 6.2 cmH2O " This is especially true when looking at data at constant pressure. It was found with DV64 data, but is presumably true for other machine types as well as the graphs do not differentiate on machine types - to be confirmed All 2-high All Issue 0000054 from SH 1.1.0 GitLab Issue List */ #include #include #include #include #include "MinutesAtPressure.h" #include "Graphs/gGraph.h" #include "Graphs/gGraphView.h" #include "SleepLib/profiles.h" #include "Graphs/gXAxis.h" #include "Graphs/gYAxis.h" #include "common_gui.h" #include "Graphs/gLineChart.h" // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // Compile time Features // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #define ENABLE_DISPLAY_SPAN_EVENTS_AS_BACKGROUND // Enables SPAN event to be displayed as a background (like pressure graph) #define ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS // Enables FLAG events to be displayed as tick (like pressure graph) #define ENABLE_BUCKET_PRESSURE_AVERAGE // Average method // Bucket pressure is in the middle of the pressure range. // New definition of bucket Pressure-0.1 - Pressure0.1 with INTERVALS_PER_CCMH2O=5 // Original bucket definition. Pressure - Pressure+0.2 wiyh INTERVALS_PER_CCMH2O=5 #define EXTRA_SPACE_ON_X_AXIS // adds a small space (Pressure/(INTERVALS_PER_CCMH2O*2) to each end. #define ENABLE_SMOOTH_CURVES // decreases performance. //#define ALIGN_X_AXIS_ON_INTEGER_BOUNDS //#define ENABLE_DISPLAY_FLAG_EVENTS_AS_GRAPH // enable graphing of flag events. Overlays plots on top on pressure plots. // ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS is enabled instead. // ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS and ENABLE_DISPLAY_FLAG_EVENTS_AS_GRAPH can both be enabled // Compile time Constants // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #define HIGHEST_POSSIBLE_PRESSURE 60 // should be the lowest possible pressure that a CPAP machine accepts - Plus spare. #define INTERVALS_PER_CCMH2O 5 // must be a positive integer > 0. Five (5) produces good graphs. Other values will work. // 10 also loogs good. larger number have smaller intervals and the starting pressure interval will be huge // relation to the rest of the pressure intervals - making the graph unusable. #define NUMBER_OF_CATMULLROMSPLINE_POINTS 5 // Higher the number decreases performance. 5 produces smooth curves. must be >= 1. 1 connects points with straight line. // ENABLE_SMOOTH_CURVES must also be enabled. // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< //#define ENABLE_MAP_DRAWING_DEBUG // ENABLE DEBUG / TESTING definitions #ifdef ENABLE_MAP_DRAWING_DEBUG // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // Define Enable common debug / test features //#define ENABLE_HOURS_TIME_DISPLAY //#define ENABLE_MOUSE_DEBUG_INFO //#define ENABLE_MAP_DRAWING_RECT_DEBUG //#define TEST_DURATION //#define MAP_LOG_EVENTS //#define ENABLE_UNEVEN_MACHINE_MIN_MAX_TEST // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // Define Display macros to enhance displays #define DEBUGQ qDebug() #define DEBUGT qDebug()< #define DEBUG qDebug()<5) return 1; if (value<0.5) return 3; return 2; } EventDataType pressureToBucket(EventDataType pressure, int bucketsPerPressure) { return pressure * bucketsPerPressure; } EventDataType convertBucketToPressure(EventDataType bucket, int bucketsPerPressure) { return bucket / bucketsPerPressure; } EventDataType pressureToXaxis( EventDataType pressure, EventDataType pixelsPerPressure , EventDataType minpressure , QRectF& drawingRect ) { return ((pressure-minpressure) * pixelsPerPressure) + drawingRect.left() ; } EventDataType convertXaxisToPressure( EventDataType mouseXaxis, EventDataType pixelsPerPressure , EventDataType minpressure , QRectF& drawingRect ) { return minpressure + ( EventDataType( mouseXaxis - drawingRect.left()) / pixelsPerPressure ); } EventDataType msecToMinutes(EventDataType value) { return value / 60000.0; // convert milli-seconds to minutes. } EventDataType getSetting(Session * sess,ChannelID code) { if (!sess->settings.contains(code)) { qWarning() << "MinutesAtPressure could not find channel" << code; return -1; } auto setting=sess->settings.value(code);/*[code]; */ enum schema::DataType datatype = schema::channel[code].datatype(); if (!( datatype == schema::DEFAULT || datatype == schema::DOUBLE )) return -1; return setting.toDouble(); } QString timeString(EventDataType milliSeconds) { #if 1 EventDataType h,m,s = milliSeconds; if (s<0) return QString(); s = 60*modf(s/60000,&m); m = 60*modf(m/60,&h ); // These string are existing translations static const char* TR_TIME_FMT_S =" (%3 sec)" ; static const char* TR_TIME_FMT_MS =" (%2 min, %3 sec)" ; static const char* TR_TIME_FMT_HMS ="%1 hours, %2 minutes and %3 seconds" ; if (m>0) { if (h<=0) { return QObject::tr(TR_TIME_FMT_MS).arg(m,0,'f',0).arg(s,0,'f',0); } else { return QString(" (%1)").arg(QObject::tr(TR_TIME_FMT_HMS).arg(h,0,'f',0).arg(m,0,'f',0).arg(s,2,'f',0)); } } else if (s<3 && s>0.01) { return QObject::tr(TR_TIME_FMT_S).arg(s,1,'f',1); } return QObject::tr(TR_TIME_FMT_S).arg(s,0,'f',0); #else EventDataType time= milliSeconds; QString unit; // these are already translated. if (time>60*1000) { if (time<=60*60*1000) { time /=(60*1000); unit=STR_UNIT_Minutes; } else { time /=(60*60*1000); unit=STR_UNIT_Hours; } } else { // have seconds. unit=STR_UNIT_Seconds; time /= (1000); } //DEBUG <(tableSize,-1); } void PressureInfo::finishCalcs() { peaktime = 0; peakevents = 0; firstPlotBucket = 0; lastPlotBucket = 0; int val; for (int i=0, end=times.size(); i0) numEvents[cod]+=val; peakevents = qMax(val, peakevents); } } } void PressureInfo::init() { chan = schema::channel[code]; peaktime = peakevents = 0; firstPlotBucket = 0; lastPlotBucket = 0; }; void PressureInfo::AddChannel(ChannelID c) { chans.append(c); events[c].resize(tableSize); } void PressureInfo::AddChannels(QList & chans) { for (int i=0; imachine()->loaderName() == "PRS1") ? 2 : INTERVALS_PER_CCMH2O; numberXaxisDivisions=qMin(2*bucketsPerPressure,10); } EventDataType PressureInfo:: rawToPressure ( EventStoreType raw,EventDataType gain) { return EventDataType(raw)*gain; } EventStoreType PressureInfo:: rawToBucketId ( EventStoreType raw,EventDataType gain) { EventDataType pressure = rawToPressure(raw,gain)+sampleIntervalStart; EventStoreType ret = floor(pressure * bucketsPerPressure ); //DEBUG <mutex.lock(); m_quit = true; map->mutex.unlock(); } // find pressure given the time of the event. void RecalcMAP::updateFlagData(int ¤tLoc, int & currentEL,int& currentData,qint64 eventTime, QVector &dataArray, PressureInfo & info ) { for (; currentELcount()); for (; currentLoc<(int)EL->count() ; currentLoc++) { if (m_quit) return ; qint64 sampleTime = EL->time(currentLoc); int raw = EL->raw(currentLoc); EventDataType gain= EL->gain(); EventStoreType data = info.rawToBucketId(raw,gain); if (data>=tableSize) { data=tableSize-1; } if (sampleTime<=eventTime) { currentData=data; if (sampleTime=0) { #if defined(MAP_LOG_EVENTS) DEBUG << NAME(chanId) < &dataArray, PressureInfo & info ) { EventStoreType useddata = ~0; for (; currentELcount() ); for (; currentLoc<(int)EL->count() ; currentLoc++) { if (m_quit) return ; qint64 sampleTime = EL->time(currentLoc); int raw = EL->raw(currentLoc); EventDataType gain= EL->gain(); EventStoreType data = info.rawToBucketId(raw,gain); if (data>=tableSize) { data=tableSize-1; } if (sampleTime0) { dataArray[currentData]++; #if defined(MAP_LOG_EVENTS) DEBUG << NAME(chanId) <=eventTime) return; currentData=data; } currentLoc=0; } return ; } void RecalcMAP::updateEventsChannel(Session*sess,ChannelID chanId, QVector &dataArray, PressureInfo & info ) { this->chanId=chanId; int qtyEvents=0; EventDataType duration = 0, gain; qint64 t , start; EventStoreType *dptr; EventStoreType *eptr; quint32 *tptr; int cnt= 0; schema::ChanType chanType = schema::channel[ chanId ].type(); auto channelEvents = sess->eventlist.value(chanId); // Loop through event lists for (int index =0; indexfirst(); tptr = EL->rawTime(); dptr = EL->rawData(); cnt = EL->count(); eptr = dptr + cnt; gain = EL->gain(); int currentLoc =0; int currentEL =0; int currentData =-1; for (; dptr < eptr; dptr++) { if (m_quit) return ; t = start + *tptr++; if (tmaxx) continue; if (tsmaxx) t=maxx; qtyEvents++; updateSpanData(currentLoc , currentEL , currentData , ts , t , dataArray , info ) ; } else { if (t>maxx) continue; if (schema::channel[ chanId ].type() == schema::FLAG) { updateFlagData(currentLoc , currentEL , currentData , t , dataArray , info ) ; } } } } return ; } void RecalcMAP::updateEvents(Session*sess,PressureInfo & info) { QHash >::iterator ei = sess->eventlist.find(info.code); if (ei == sess->eventlist.end()) return ; for (const auto & cod : info.chans) { updateEventsChannel(sess,cod, info.events[cod],info); if (m_quit) return ; } } void RecalcMAP::updateTimesValues(qint64 d1,qint64 d2, int key,PressureInfo & info) { qint64 duration = (d2 - d1); info.times[key] += duration; info.totalDuration+=duration; } //! \brief Updates Time At Pressure from session *sess void RecalcMAP::updateTimes(PressureInfo & info) { //DEBUGF <count(); if (ELsize < 1) continue; gain = EL->gain(); #if 1 // Workaround for the popout function. when the MAP popout graph is created the time selction mixx and miny are both zero. // this indicates that there is no data to be displayed. WHY ?? // This workaround uses the session min/max times when the selection min/max times are zero. if (map->numCloned>0) { bool cloneWorkAround =false; if (info.minTime==0) { cloneWorkAround = true; info.minTime = EL->first(); } if (cloneWorkAround) { if (info.maxTimelast()) info.maxTime=EL->last(); } } #endif // Skip if outside of range if ((EL->first() > info.maxTime) || (EL->last() < info.minTime)) { continue; } // adjust for multiple sessions. // EL->first and last are for the current session while minTime and MaxTime are for a set of seesion for the day. minx = qMax(info.minTime , EL->first()); maxx = qMin(info.maxTime , EL->last()); lasttime = 0; lastdata = 0; data = 0; first = true; // Scan through pressure samples for (int e = 0; e < ELsize; ++e) { if (m_quit) return ; time = EL->time(e); EventStoreType raw = EL->raw(e); test_data(e,ELsize,raw, time ,info.minTime ,info.maxTime,gain,EL); data = ipap_info->rawToBucketId(raw,gain); //DEBUG << OO(e=,e) << TIME(time) <first()) <last()) <=tableSize) { data=tableSize-1; } if ((time < minx) || first) { lasttime = time; lastdata = data; first = false; continue; } if (lastdata != data) { d1 = qMax(minx, lasttime); d2 = qMin(maxx, time); updateTimesValues(d1,d2,lastdata,info) ; lasttime = time; lastdata = data; } if (time > maxx) { break; } } if ((lasttime>0) &&((lasttime <= maxx) || (lastdata == data))) { d1 = qMax(minx, lasttime); d2 = qMin(maxx, EL->last()); updateTimesValues(d1,d2, lastdata,info) ; } } } void RecalcMAP:: setSelectionRange(gGraph* graph) { graph->graphView()->GetXBounds(minTime, maxTime); } void RecalcMAP::run() { QMutexLocker locker(&map->mutex); map->m_recalculating = true; Day * day = map->m_day; if (!day) return; // Get the channels for specified Channel types QList chans; #if defined(ENABLE_DISPLAY_FLAG_EVENTS_AS_GRAPH) || defined(ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS) chans = day->getSortedMachineChannels(schema::FLAG); chans.removeAll(CPAP_VSnore); chans.removeAll(CPAP_VSnore2); chans.removeAll(CPAP_FlowLimit); chans.removeAll(CPAP_RERA); #endif // Get the channels for specified Channel types QList chansSpan ; #ifdef ENABLE_DISPLAY_SPAN_EVENTS_AS_BACKGROUND chansSpan = day->getSortedMachineChannels(schema::SPAN); chansSpan.removeAll(CPAP_Ramp); #endif ChannelID ipapcode = CPAP_Pressure; // default for (auto & ch : { CPAP_IPAPSet, CPAP_IPAP, CPAP_PressureSet } ) { if (day->channelExists(ch)) { ipapcode = ch; break; } } ChannelID epapcode = NoChannel; // default for (auto & ch : { CPAP_EPAPSet, CPAP_EPAP } ) { if (day->channelExists(ch)) { epapcode = ch; break; } } PressureInfo IPAP(ipapcode, minTime, maxTime), EPAP(epapcode, minTime, maxTime); ipap_info=&IPAP; chans+=chansSpan; IPAP.AddChannels(chans); EventDataType minP = HIGHEST_POSSIBLE_PRESSURE; EventDataType maxP = 0; auto & sessions = day->sessions; #if defined(TEST_DURATION) if (sessions.size()==1) { auto & eventLists = sess->eventlist.value(ipapcode); if (eventLists.size()==1) { if (sess->first()!=minTime ) { DEBUG << "Session" << DATETIME(sess->first()) << "sessFirst" << TIME(sess->first()) << "minTime.." << TIME(minTime) << OO(diffMs,sess->first()-minTime) << NAME(info.code) ; } if (sess->last() !=maxTime) { DEBUG << "Session" << DATETIME(sess->first()) << "SessEnd.." << TIME(sess->last()) << "MaxTime,," << TIME(maxTime) << OO(diffMs,sess->last()-maxTime) << NAME(info.code) ; } } } #endif for ( int idx=0; idxtype() == MT_CPAP) { IPAP.updateBucketsPerPressure(sess); EPAP.updateBucketsPerPressure(sess); IPAP.eventLists = sess->eventlist.value(ipapcode); EPAP.eventLists = sess->eventlist.value(epapcode); updateTimes(IPAP); updateTimes(EPAP); EventDataType value = getSetting(sess, CPAP_PressureMin); if (value >=0.1 && minP >value) minP=value; value = getSetting(sess, CPAP_PressureMax); if (value >=0.1 && maxP maxP) minP=maxP; IPAP.setMachineTimes(minP,maxP); #ifdef ENABLE_UNEVEN_MACHINE_MIN_MAX_TEST int dayInMonth= day->date().day(); if ((dayInMonth&1)!=0) { machinePressureMin -= 0.05; machinePressureMax += 0.05; } #endif if (m_quit) { m_done = true; return; } IPAP.finishCalcs(); EPAP.finishCalcs(); map->timelock.lock(); map->epap = EPAP; map->ipap = IPAP; map->timelock.unlock(); map->recalcFinished(); m_done = true; } //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< MinutesAtPressure class <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< MinutesAtPressure::MinutesAtPressure() :Layer(NoChannel) { m_remap = nullptr; m_minimum_height = 0; } MinutesAtPressure::~MinutesAtPressure() { while (recalculating()) {}; } void MinutesAtPressure::SetDay(Day *day) { Layer::SetDay(day); //if (m_day) DEBUGTF << day->date().toString("dd MMM yyyy hh:mm:ss.zzz"); m_empty = false; m_recalculating = false; m_lastminx = 0; m_lastmaxx = 0; m_empty = !m_day || !(m_day->channelExists(CPAP_Pressure) || m_day->channelExists(CPAP_EPAP) || m_day->channelExists(CPAP_PressureSet) || m_day->channelExists(CPAP_EPAPSet)); } int MinutesAtPressure::minimumHeight() { return m_minimum_height; } bool MinutesAtPressure::isEmpty() { return m_empty; } void MinutesAtPressure::paint(QPainter &painter, gGraph &graph, const QRegion ®ion) { QRectF boundingRect = region.boundingRect(); m_minx = graph.min_x; m_maxx = graph.max_x; if (graph.printing() || ((m_lastminx != m_minx) || (m_lastmaxx != m_maxx))) { // note: this doesn't run on popout graphs that aren't linked with scrolling... // it's a pretty useless graph to popout, probably should just block it's popout instead. recalculate(&graph); } if (!initialized) return; // conditional display of TimeAtPressure Plots based on Plots displayed by the Pressure Graph. // if no Plots are displayed on the Pressure Graph then Displays "Plots Disabled" // if Pressure Graph is not displayed then "time at Pressure" displays both Plots // the max Y Axis value is updated for the displayed plot. setEnabled(graph); bool display_pressure = isEnabled(ipap.code); bool display_epap = isEnabled(epap.code); if (!( display_epap || display_pressure )) { // No Data QString msg = QObject::tr("Plots Disabled"); int x, y; GetTextExtent(msg, x, y, bigfont); graph.renderText(msg, boundingRect, Qt::AlignCenter, 0, Qt::gray, bigfont); return; } // check for empty data. if ( ipap.lastPlotBucket ==0 ) display_pressure = false; if ( epap.lastPlotBucket ==0 ) display_epap = false; if (!( display_epap || display_pressure )) { QString msg = QObject::tr("No Data"); int x, y; GetTextExtent(msg, x, y, bigfont); graph.renderText(msg, boundingRect, Qt::AlignCenter, 0, Qt::gray, bigfont); return; } // need to Check if windows has changed. m_lastminx = m_minx; m_lastmaxx = m_maxx; if (graph.printing()) { // Could just lock the mutex QMutex instead mutex.lock(); // do nothing between, it should hang until complete. mutex.unlock(); //while (recalculating()) { QThread::yieldCurrentThread(); } // yield or whatever } if (!painter.isActive()) return; // Recalculating in the background... So we just draw an old copy until then the new data is ready // (it will refresh itself when complete) // The only time we can't draw when at the end of the recalc when the map variables are being updated // So use a mutex to lock QMutexLocker TimeLock(&timelock); //////////////////////////////////////////////////////////////////// // calculate pressure Ranges //////////////////////////////////////////////////////////////////// int peaktime=0; EventDataType minpressure = ipap.machinePressureMin; EventDataType maxpressure = ipap.machinePressureMax; //DEBUG < maxpressure) { minpressure = HIGHEST_POSSIBLE_PRESSURE; maxpressure=0; } //DEBUG <quit(); m_remap = new RecalcMAP(this); m_remap->setAutoDelete(true); m_remap->setSelectionRange(graph); m_graph=graph; QThreadPool * tp = QThreadPool::globalInstance(); if (graph->printing()) { m_remap->run(); } else { while(!tp->tryStart(m_remap)); m_lastmaxx = m_maxx; m_lastminx = m_minx; } } void MinutesAtPressure::recalcFinished() { if (m_graph && !m_graph->printing()) { // Can't call this using standard timedRedraw function, we are in another thread, so have to use a throwaway timer QTimer::singleShot(0, m_graph->graphView(), SLOT(refreshTimeout())); // this causes MinutesAtPressure:: paint to be called. } m_remap = nullptr; m_recalculating = false; initialized=true; } bool MinutesAtPressure::mouseMoveEvent(QMouseEvent *, gGraph *graph) { if (graph) graph->timedRedraw(0); return false; } bool MinutesAtPressure::mousePressEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event); Q_UNUSED(graph); return false; } bool MinutesAtPressure::mouseReleaseEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event); Q_UNUSED(graph); return false; } bool MinutesAtPressure::isEnabled(ChannelID id) { return m_enabled[id]; } ; void MinutesAtPressure::setEnabled(gGraph &graph) { QList channels; channels+=ipap.code; channels+=epap.code; channels+=ipap.chans; gGraphView *graphView = graph.graphView(); gGraph* pressureGraph = graphView->findGraph(STR_GRAPH_Pressure); gLineChart * pressureGraphLC = NULL; if (!pressureGraph ) return; pressureGraphLC = dynamic_cast(pressureGraph->getLineChart()); if (!pressureGraph->visible()) return; if (!pressureGraphLC) return; m_enabled.clear(); for (QList::iterator it = channels.begin(); it != channels.end(); ++it) { ChannelID ch=*it; bool value; schema::Channel & chan =schema::channel[ch] ; value = chan.enabled(); if (chan.type() == schema::WAVEFORM) { value=pressureGraphLC->plotEnabled(ch); } else { value &= pressureGraphLC->m_flags_enabled[ch]; } //DEBUGF << FULLNAME(ch) << O(value); m_enabled[ch]=value; } }; EventDataType getStep(int &stepi, EventDataType& stepmult ) { static const QList stepArray {1.0, 2.0,5.0}; return stepmult * stepArray[stepi]; } void decStep(int &stepi, EventDataType& stepmult ) { stepmult = stepmult / 10; Q_UNUSED(stepi); } void MapPainter::calculatePeakY(int peaktime ){ GetTextExtent("W", singleCharWidth, textHeight); peakMinutes = msecToMinutes(peaktime+1); // peakMinutes must not be zero. static const QList stepArray {1.0, 2.0,5.0}; //static const QList stepArray {1.0, 2.5,5.0, 7.5}; int stepArraySize=stepArray.size(); int height = drawingRect.height(); height -= qMin ( int(drawingRect.height()/10), qMax(textHeight, drawTickLength)); int maxsteps=ceil(height / textHeight); #define MINSTEPS 1 #define MAXSTEPS 15 maxsteps=qMax (MINSTEPS, qMin(maxsteps,MAXSTEPS)); EventDataType minStep = peakMinutes / maxsteps; int stepi=0; // o - ArraySize-1 EventDataType stepmult=1; //10**n int numberSteps=1; EventDataType step = 1; yPixelsPerStep = 0; EventDataType totalMinutes = 0; EventDataType pixelsPerMinute = 0; bool up=false; // find smallest step // find smallest step that where step label do not overlap for (;;) { step = stepmult * stepArray[stepi]; if (step>minStep) { if (!up) { // very low levels. stepmult = stepmult / 10; continue; } numberSteps = ceil(peakMinutes/step); if (numberSteps==0) numberSteps=1; totalMinutes = step*numberSteps; if (totalMinutes>=peakMinutes) { // this works. break; } } up=true; stepi=(stepi+1)%stepArraySize; // next step module array size. if (stepi==0) stepmult*=10; } // determine Y-axis scale pixelsPerMinute = height / totalMinutes; totalMinutes = step*numberSteps; pixelsPerMinute = height / totalMinutes; yPixelsPerStep = height / numberSteps; // update parameters required for the Y-axis yPixelsPerMsec = pixelsPerMinute/60000; yMinutesPerStep=step; peakMinutes = totalMinutes; //DEBUG << O(drawingRect.height() ) << O(textHeight) << O(peaktime) << O(peakmult) << O(yPixelsPerMsec) << O(yMinutesPerStep) << O(peakMinutes) ; } int MapPainter::drawYaxis(int peaktime) { MapPainter::calculatePeakY(peaktime ); //////////////////////////////////////////////////////////////////// // Draw Y Axis labels //////////////////////////////////////////////////////////////////// QString label; int labelWidth,labelHeight; EventDataType bot = drawingRect.bottom(); int left= boundingRect.left(); int width= boundingRect.width(); int widest_YAxis = 2; EventDataType limit =peakMinutes +(yMinutesPerStep/2) ; for (EventDataType f=0.0; f& enabled , EventDataType minpressure , EventDataType maxpressure) { int top=boundingRect.top(); int bottom=boundingRect.bottom(); //////////////////////////////////////////////////////////////////// // Draw mouse over events //////////////////////////////////////////////////////////////////// mouseOverKey = -1; QPoint mouse=graph.graphView()->currentMousePos(); bool toolTipOff=false; if (mouse.x()==0 && mouse.y() ==0) { toolTipOff=true; mouse=last_mouse; } else { last_mouse=mouse; } graphSelected= (mouse.y()<=boundingRect.bottom() && mouse.y()>=boundingRect.top() ); bool eventOccured = false; if ((mouse.x()boundingRect.right() )) { graphSelected= false; // note until Session start times are synced with waveforms start time. there will be a difference in the total time displayed. // so don't display the total waveform time, because the user can see the difference between sessions times and the total duration // calculated. both the first and last times can be different for resmed machines. This can be confusing so don't display questionable data. topBarLabel = displayMetaData(ipap.chan.label(),minpressure, minpressure, maxpressure, timeString(ipap.totalDuration),"",""); //topBarLabel = displayMetaData(ipap.chan.label(),minpressure, minpressure, maxpressure, "" ,"",""); //So just display original Label instead of total Duration. // topBarLabel = QObject::tr("Peak %1").arg(msecToMinutes(qMax(ipap.peaktime, epap.peaktime)),1,'f',1); Q_UNUSED(maxpressure); } else { // Mouse is in the horizantile ploting area of all graphs. EventDataType pMousePressure = minpressure + ( (mouse.x() - drawingRect.left()) / pixelsPerPressure); mouseOverKey = floor((pMousePressure+sampleIntervalStart)*bucketsPerPressure); EventDataType mouseOverPressure = (EventDataType)mouseOverKey/bucketsPerPressure; int bucketX = ((mouseOverPressure-minpressure)*pixelsPerPressure) +drawingRect.left() ; // Draw veritical line for mouse cursor. jump to closest pressure bucket. painter.setPen(QPen(QColor(128,128,128,30), 1.5*AppSetting->lineThickness())); painter.drawLine(bucketX, top, bucketX, bottom); bool epapEnabled = enabled[epap.code] ; topBarLabel = displayMetaData( ipap.chan.label(), mouseOverPressure, mouseOverPressure-sampleIntervalStart, mouseOverPressure+sampleIntervalEnd, timeString(ipap.times[mouseOverKey]) , epapEnabled?epap.chan.label():"", epapEnabled?timeString(epap.times[mouseOverKey]):"" ); QString toolTipLabel = QString(); int nc = ipap.chans.size(); for (int i=0;i0 && opacity<=255) { color.setAlpha(opacity); //DEBUG << FULLNAME(channelId) << O(color.name()) << O(opacity); } linePen=QPen(color, AppSetting->lineThickness()); pointEnhancePen =QPen(QColor(Qt::black), 2.5*AppSetting->lineThickness()); pointSelectionPen=QPen(color, 1.5*AppSetting->lineThickness()); } void MapPainter::setChannelInfo(ChannelID id, QVector dataArray, EventDataType yPixelsPerUnit ,int startBucket ,int endBucket) { channel = &schema::channel[id]; chanType = channel->type(); //schema::channel[id].type() ; this->dataArray = dataArray; this->yPixelsPerUnit = yPixelsPerUnit; this->startBucket = startBucket; this->endBucket = endBucket; setPenColorAlpha(id , chanType!=schema::WAVEFORM ? 50 : 255); } // converts a y value to a graph point // Adjusting values from the min and max graph ranges. EventDataType MapPainter::verifyYaxis(EventDataType value) { EventDataType top=drawingRect.top(); EventDataType bottom=drawingRect.bottom(); if (value<=top) { return top+2; } else if (value>=bottom) { return bottom; } return value; } EventDataType MapPainter::dataToYaxis(int pp) { EventDataType val=verifyYaxis (drawingRect.bottom() - ((pp*yPixelsPerUnit))); return val; } // Initializes values used based void MapPainter::initCatmullRomSpline(EventDataType pixelsPerBucket,int numberOfPoints) { catmullRomSplineNumberOfPoints = numberOfPoints; catmullRomSplineIncrement = 1.0 / catmullRomSplineNumberOfPoints ; catmullRomSplineInterval = 0.0f; catmullRomSplineXstep = pixelsPerBucket / catmullRomSplineNumberOfPoints; } // Draws a line between two points // The line will be stright if anti-aliasing is turned off. // otherwise the line will be be curved to fit the data. EventDataType MapPainter::drawSegment( int bucket ,EventDataType lastxp,EventDataType lastyp) { #if defined(ENABLE_SMOOTH_CURVES) EventDataType xp=lastxp; EventDataType dM1 = dataToYaxis(dataArray[bucket-1]); EventDataType yp = dataToYaxis(dataArray[bucket +0]); EventDataType d1 = dataToYaxis(dataArray[bucket +1]); EventDataType d2 = dataToYaxis(dataArray[bucket +2]); catmullRomSplineInterval=catmullRomSplineIncrement; for (int loop=0;loop1) { yp= CatmullRomSpline( dM1, yp , d1, d2 , catmullRomSplineInterval) ; } yp=verifyYaxis(yp); xp+=catmullRomSplineXstep; painter.drawLine(lastxp, lastyp, xp, yp); lastxp = xp; lastyp = yp; } #else EventDataType yp = dataToYaxis(dataArray[bucket +1]); yp=verifyYaxis(yp); EventDataType xp= lastxp+pixelsPerBucket; painter.drawLine(lastxp, lastyp, xp, yp); #endif return yp; } void MapPainter::drawPoint(bool accent,int xp, int yp) { if (!graphSelected) return; if (yp>=drawingRect.bottom() && chanType!=schema::WAVEFORM) return; //DEBUG << FULLNAME(info.code) << OO(id,channel.id()); if (accent) { painter.setPen(pointEnhancePen); } else { painter.setPen(pointSelectionPen); } int radius=1; painter.drawEllipse(QPoint(xp,yp),radius,radius); painter.setPen(linePen); } // Draw a plot of points on Graphs // CPAP_Pressure or CPAP_EPAP or Events void MapPainter::drawPlot() { tickPen = QPen(Qt::black, 1); bool started=false; EventDataType yp=drawingRect.bottom(); EventDataType xp=drawingRect.left(); painter.setPen(linePen); for (int i=startGraphBucket; i<=endBucket; ++i,xp+=pixelsPerBucket) { //DEBUG << O(i) << OO(data,dataArray[i]); bool accent = (i==mouseOverKey); if (!started ) { if (dataArray[i]<=0) continue; if (i>=startBucket) { //draw vertical line to first point. started=true; // following used to test Y axis labels position. //int tmp=dataArray[i]; //if (tmp>61000 && tmp<63000) tmp=60000; //yp = dataToYaxis(tmp); yp = dataToYaxis(dataArray[i]); //DEBUG << OO(bucket,i) <=endBucket) { // draw vertical line to last point. EventDataType lastxp=xp; if (yp >=drawingRect.bottom() ) { //last point was at bottom. lastxp-=pixelsPerBucket; yp = dataToYaxis(dataArray[i]); } drawPoint( accent ,xp, yp); painter.drawLine(lastxp ,drawingRect.bottom(), xp, yp); return; } drawPoint( accent ,xp, yp); yp=drawSegment (i,xp,yp) ; } } // Draw a an Event tick at the top of ther graph #ifdef ENABLE_DISPLAY_SPAN_EVENTS_AS_BACKGROUND void MapPainter::drawSpanEvents() { if (dataArray.isEmpty()) return; EventDataType xp = drawingRect.left(); EventDataType pixelsPerBucket = this->pixelsPerBucket; #ifdef ENABLE_BUCKET_PRESSURE_AVERAGE EventDataType pixelsPerBucket2 = pixelsPerBucket/2; pixelsPerBucket = pixelsPerBucket2; #endif QRectF box= QRectF(xp,boundingRect.top(),pixelsPerBucket,boundingRect.height()); int tickTop = boundingRect.top(); int tickHeight =boundingRect.height(); QColor color=schema::channel[channel->id()].defaultColor(); color.setAlpha(128); for (int i=startGraphBucket; i=endGraphBucket) { #ifdef ENABLE_BUCKET_PRESSURE_AVERAGE if (i==endGraphBucket) { pixelsPerBucket = pixelsPerBucket2; } else { return; } #else return; #endif } int data=dataArray[i]; if (data>0) { box.setRect(xp,tickTop,pixelsPerBucket,tickHeight); painter.fillRect(box,color); } xp+=pixelsPerBucket; #ifdef ENABLE_BUCKET_PRESSURE_AVERAGE pixelsPerBucket=this->pixelsPerBucket; #endif } } #endif #ifdef ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS void MapPainter::drawEventTick() { EventDataType xp=drawingRect.left(); int tickLength=drawTickLength; int top = boundingRect.top(); int bottom = boundingRect.bottom(); painter.setPen(tickPen); for (int i=startGraphBucket; iid(),70); setPenColorAlpha(ChannelID(NoChannel),70); drawPlot(); #endif #ifdef ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS tickPen = QPen(Qt::black, 1); tickEnhancePen = QPen(QColor(0,0,255), 3.5*AppSetting->lineThickness()); tickEnhanceTransparentPen = QPen(QColor(0,0,255,60), 3.5*AppSetting->lineThickness()); drawEventTick(); #endif return; } if (chanType == schema::SPAN) { #ifdef ENABLE_DISPLAY_SPAN_EVENTS_AS_BACKGROUND drawSpanEvents(); #endif return; } } #endif //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST DATA <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #if defined(ENABLE_TEST_CPAP) || defined(ENABLE_TEST_SAWTOOTH) || defined(ENABLE_TEST_SINGLE) || defined(ENABLE_TEST_NODATA) EventDataType test_inc=0.0; EventDataType test_start; EventDataType test_mid; EventDataType test_end; EventDataType test_value; qint64 test_time; qint64 test_time_inc; int test_count; qint64 test_ELFirst; bool testdata(int e, int ELsize, EventStoreType& raw, qint64& time ,qint64 minTime ,qint64 maxTime , EventDataType gain, EventList* EL) { if (e==0) { test_ELFirst=EL->first(); test_start=(4.0f/gain); test_mid=(7.07f/gain); test_end=(15.0f/gain); test_value=test_start; test_inc=(test_end-test_start)/EventDataType(ELsize); test_time=time; //test_time_inc=(maxTime-minTime)/ELsize; test_time_inc=(EL->last()-EL->first())/ELsize; test_count=0; } if (test_ELFirst!=EL->first()) { test_ELFirst=EL->first(); } if (e>=ELsize) return false; #if defined(ENABLE_TEST_CPAP) raw= EventStoreType(test_mid); return true; #endif #if defined(ENABLE_TEST_SINGLE) int zz=ELsize-1; if (e==zz) { raw= (EventStoreType)test_mid; time=(minTime+maxTime)/2; return true; } return false; #endif #if defined(ENABLE_TEST_NODATA) return false; #endif // ENABLE_TEST_SAWTOOTH if (test_value>=test_end) { test_value=test_start; } ; raw=(EventStoreType)test_value;; time=(qint64)test_time; test_time+=test_time_inc; test_value+= test_inc; return true; Q_UNUSED(test_count); Q_UNUSED(e); Q_UNUSED(ELsize); Q_UNUSED(EL); Q_UNUSED(raw); Q_UNUSED(time); Q_UNUSED(minTime); Q_UNUSED(maxTime); } #endif OSCAR-code-v1.3.1/oscar/Graphs/MinutesAtPressure.h000066400000000000000000000201321417327530600216350ustar00rootroot00000000000000/* Minutes At Pressure Graph Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef MINUTESATPRESSURE_H #define MINUTESATPRESSURE_H #include #include "Graphs/layer.h" #include "SleepLib/day.h" #include "SleepLib/schema.h" #include "Graphs/gLineChart.h" class MinutesAtPressure; struct PressureInfo { public: PressureInfo(); PressureInfo(ChannelID code, qint64 minTime, qint64 maxTime) ; PressureInfo(PressureInfo ©) = default; void AddChannel(ChannelID c); void AddChannels(QList & chans); void finishCalcs(); void setMachineTimes(EventDataType min,EventDataType max); ChannelID code; schema::Channel chan; qint64 minTime, maxTime; QVector times; int peaktime, peakevents; QHash > events; QHash numEvents; QList chans; QVector eventLists; void updateBucketsPerPressure(Session* sess); int bucketsPerPressure = 1; int numberXaxisDivisions =10; EventDataType rawToPressure ( EventStoreType raw,EventDataType gain); EventStoreType rawToBucketId ( EventStoreType raw,EventDataType gain); EventDataType minpressure = 0.0; EventDataType maxpressure = 0.0; qint64 totalDuration = 0; EventDataType machinePressureMin = 0.0; EventDataType machinePressureMax = 0.0; int firstPlotBucket =0; int lastPlotBucket =0; private: void init(); }; class RecalcMAP:public QRunnable { friend class MinutesAtPressure; public: explicit RecalcMAP(MinutesAtPressure * map) :map(map), m_quit(false), m_done(false) {} virtual ~RecalcMAP(); virtual void run(); void quit(); protected: MinutesAtPressure * map; volatile bool m_quit; volatile bool m_done; private: void setSelectionRange(gGraph* graph); qint64 minTime, maxTime; ChannelID chanId; // required for debug. PressureInfo * ipap_info; void updateTimes(PressureInfo & info); void updateEvents(Session*sess,PressureInfo & info); void updateTimesValues(qint64 d1,qint64 d2, int key,PressureInfo & info); void updateEventsChannel(Session * sess,ChannelID id, QVector &background, PressureInfo & info ); void updateFlagData(int ¤tLoc, int & currentEL,int& currentData,qint64 eventTime, QVector &dataArray, PressureInfo & info ) ; void updateSpanData(int ¤tLoc, int & currentEL,int& currentData,qint64 startSpan, qint64 eventTime , QVector &dataArray, PressureInfo & info ) ; }; class MinutesAtPressure:public Layer { friend class RecalcMAP; public: MinutesAtPressure(); virtual ~MinutesAtPressure(); virtual void recalculate(gGraph * graph); virtual void SetDay(Day *d); virtual bool isEmpty(); virtual int minimumHeight(); //! Draw filled rectangles behind Event Flag's, and an outlines around them all, Calls the individual paint for each gFlagLine virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); bool mouseMoveEvent(QMouseEvent *event, gGraph *graph); bool mousePressEvent(QMouseEvent *event, gGraph *graph); bool mouseReleaseEvent(QMouseEvent *event, gGraph *graph); virtual void recalcFinished(); virtual Layer * Clone() { MinutesAtPressure * map = new MinutesAtPressure(); Layer::CloneInto(map); CloneInto(map); return map; } protected: int numCloned =0; void CloneInto(MinutesAtPressure * layer) { mutex.lock(); timelock.lock(); layer->m_empty = m_empty; layer->m_minimum_height = m_minimum_height; layer->m_lastminx = m_lastminx; layer->m_lastmaxx = m_lastmaxx; layer->ipap = ipap; layer->epap = epap; layer->numCloned=numCloned+1; timelock.unlock(); layer->m_enabled = m_enabled; mutex.unlock(); } bool isCLoned() {return numCloned!=0;}; RecalcMAP * m_remap; bool initialized=false; bool m_empty; QMutex mutex; QMutex timelock; int m_minimum_height; //QAtomicInteger m_recalcCount; private: PressureInfo epap, ipap; void setEnabled(gGraph &graph); QHash m_enabled; gGraph * m_graph; qint64 m_lastminx; qint64 m_lastmaxx; QPoint last_mouse=QPoint(0,0); //EventDataType m_last_height=0; // re-calculate only when needed. //int m_last_peaktime=0; // re-calculate only when needed. bool isEnabled(ChannelID id) ; QString topBarLabel; }; class MapPainter { public: // environment - set in constructor QPainter& painter; gGraph& graph; QRectF& drawingRect; QRectF& boundingRect; EventDataType lineThickness; MapPainter( QPainter& painter, gGraph& graph, QRectF& drawingRect , QRectF& boundingRect ) : painter(painter), graph(graph), drawingRect(drawingRect) , boundingRect(boundingRect) { lineThickness= AppSetting->lineThickness(); }; // mouse related int mouseOverKey; bool graphSelected; void setMouse(int mouseOverKey,bool graphSelected) { this->mouseOverKey=mouseOverKey; this->graphSelected=graphSelected; }; // for all graphs horizonatal int startGraphBucket; int endGraphBucket; EventDataType pixelsPerBucket; EventDataType minpressure; int bucketsPerPressure; void setHorizontal( EventDataType minpressure, EventDataType maxpressure,EventDataType pixelsPerBucket , int bucketsPerPressure, int catmullRomSplineNumberOfPoints) { this->startGraphBucket = minpressure*bucketsPerPressure; this->endGraphBucket = maxpressure*bucketsPerPressure; this->pixelsPerBucket = pixelsPerBucket; this->minpressure = minpressure; this->bucketsPerPressure = bucketsPerPressure; initCatmullRomSpline(pixelsPerBucket,catmullRomSplineNumberOfPoints); }; void drawEvent(); void drawSpanEvents(); void drawEventTick(); // Pen type for drawing - per graph QPen linePen; QPen pointSelectionPen; QPen pointEnhancePen; QPen tickPen; QPen tickEnhancePen; QPen tickEnhanceTransparentPen; //EventDataType bottom,top,height,left,right; schema::Channel* channel; schema::ChanType chanType; EventDataType yPixelsPerUnit; QVector dataArray; int startBucket; int endBucket; void setChannelInfo(ChannelID id, QVector dataArray, EventDataType yPixelsPerUnit ,int ,int) ; void initCatmullRomSpline(EventDataType pixelsPerBucket,int numberOfPoints); EventDataType catmullRomSplineIncrement, catmullRomSplineInterval; int catmullRomSplineNumberOfPoints; EventDataType catmullRomSplineXstep; // based on pixelsPerBucket. void setPenColorAlpha(ChannelID channelId ,int opacity) ; void setPenColorAlpha(ChannelID channelId ) ; void drawPlot(); EventDataType dataToYaxis(int value) ; EventDataType verifyYaxis(EventDataType value); void initCatmullRomSpline(int numberOfPoints); void drawPoint(bool fill,int xp, int yp); EventDataType drawSegment ( int i , EventDataType fromx,EventDataType fromy) ; EventDataType yPixelsPerMsec ; EventDataType yPixelsPerEvent; EventDataType pixelsPerPressure; EventDataType yPixelsPerStep ; EventDataType yMinutesPerStep; EventDataType peakMinutes; int singleCharWidth, textHeight; void calculatePeakY(int peaktime ); int drawYaxis(int peaktime); void drawEventYaxis(EventDataType peakEvents,int widest_YAxis); void drawXaxis(int numberXaxisDivisions , int startGraphBucket , int endGraphBucket); void drawMetaData(QPoint& last_mouse , QString& topBarLabel,PressureInfo& ipap , PressureInfo& epap ,QHash& enabled , EventDataType minpressure , EventDataType maxpressure); }; #endif // MINUTESATPRESSURE_H OSCAR-code-v1.3.1/oscar/Graphs/gFlagsLine.cpp000066400000000000000000000277741417327530600205640ustar00rootroot00000000000000/* gFlagsLine Implementation * * Copyright (c) 2011-2018 Mark Watkins * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include "SleepLib/profiles.h" #include "gFlagsLine.h" #include "gYAxis.h" gLabelArea::gLabelArea(Layer * layer) : gSpacer(20) { m_layertype = LT_Spacer; m_mainlayer = layer; } bool gLabelArea::mouseMoveEvent(QMouseEvent *event, gGraph *graph) { if (m_mainlayer) { return m_mainlayer->mouseMoveEvent(event, graph); } return false; } int gLabelArea::minimumWidth() { return gYAxis::Margin; } gFlagsGroup::gFlagsGroup() { m_barh = 0; m_empty = true; } gFlagsGroup::~gFlagsGroup() { } qint64 gFlagsGroup::Minx() { if (m_day) { return m_day->first(); } return 0; } qint64 gFlagsGroup::Maxx() { if (m_day) { return m_day->last(); } return 0; } void gFlagsGroup::SetDay(Day *d) { LayerGroup::SetDay(d); int cnt = 0; if (!d) { m_empty = true; return; } quint32 z = schema::FLAG | schema::SPAN; if (p_profile->general->showUnknownFlags()) z |= schema::UNKNOWN; availableChans = d->getSortedMachineChannels(z); m_rebuild_cpap = (availableChans.size() == 0); if (m_rebuild_cpap) { QHash chans; for (const auto & sess : m_day->sessions) { for (auto it=sess->eventlist.begin(), end=sess->eventlist.end(); it != end; ++it) { ChannelID code = it.key(); if (chans.contains(code)) continue; schema::Channel * chan = &schema::channel[code]; if (chan->type() == schema::FLAG) { availableChans.push_back(code); } else if (chan->type() == schema::MINOR_FLAG) { availableChans.push_back(code); } else if (chan->type() == schema::SPAN) { availableChans.push_back(code); } else if (chan->type() == schema::UNKNOWN) { availableChans.push_back(code); } } } availableChans = chans.keys(); } lvisible.clear(); for (const auto code : availableChans) { // const schema::Channel & chan = schema::channel[code]; gFlagsLine * fl = new gFlagsLine(code); fl->SetDay(d); lvisible.push_back(fl); } cnt = lvisible.size(); // for (int i = 0; i < layers.size(); i++) { // gFlagsLine *f = dynamic_cast(layers[i]); // if (!f) { continue; } // bool e = f->isEmpty(); // if (!e || f->isAlwaysVisible()) { // lvisible.push_back(f); // if (!e) { // cnt++; // } // } // } m_empty = (cnt == 0); if (m_empty) { if (d) { m_empty = !d->hasEvents(); } } m_barh = 0; } bool gFlagsGroup::isEmpty() { if (m_day) { if (m_day->hasEnabledSessions() && m_day->hasEvents()) return false; } return true; } void gFlagsGroup::paint(QPainter &painter, gGraph &g, const QRegion ®ion) { QRectF outline(region.boundingRect()); outline.translate(0.0f, 0.001f); int left = region.boundingRect().left()+1; int top = region.boundingRect().top()+1; int width = region.boundingRect().width(); int height = region.boundingRect().height(); if (!m_visible) { return; } if (!m_day) { return; } QVector visflags; for (const auto & flagsline : lvisible) { if (schema::channel[flagsline->code()].enabled()) visflags.push_back(flagsline); } int vis = visflags.size(); m_barh = float(height) / float(vis); float linetop = top; QColor barcol; for (int i=0, end=visflags.size(); i < end; i++) { // Alternating box color barcol = COLOR_ALT_BG2; if (i & 1) { if (g.printing() && AppSetting->monochromePrinting()) { barcol = QColor(0xe4, 0xe4, 0xe4, 0xff); } else { barcol = COLOR_ALT_BG1; } } painter.fillRect(left, floor(linetop), width-1, ceil(m_barh), QBrush(barcol)); // Paint the actual flags QRect rect(left, linetop, width, m_barh); visflags[i]->m_rect = rect; visflags[i]->paint(painter, g, QRegion(rect)); linetop += m_barh; } painter.setPen(COLOR_Outline); painter.drawRect(outline); if (m_rebuild_cpap) { QString txt = QObject::tr("Database Outdated\nPlease Rebuild CPAP Data"); painter.setFont(*bigfont); painter.setPen(QPen(QColor(0,0,0,32))); painter.drawText(region.boundingRect(), Qt::AlignCenter | Qt::AlignVCenter, txt); } } bool gFlagsGroup::mouseMoveEvent(QMouseEvent *event, gGraph *graph) { //if (p_profile->appearance->lineCursorMode()) { graph->timedRedraw(0); // } if (!AppSetting->graphTooltips()) { return false; } for (int i=0, end=lvisible.size(); i < end; i++) { gFlagsLine *fl = lvisible.at(i); if (fl->m_rect.contains(event->x(), event->y())) { if (fl->mouseMoveEvent(event, graph)) { return true; } } else { // Inside main graph area? if ((event->y() > fl->m_rect.y()) && (event->y()) < (fl->m_rect.y() + fl->m_rect.height())) { if (event->x() < fl->m_rect.x()) { // Display tooltip QString ttip = schema::channel[fl->code()].fullname() + "\n" + schema::channel[fl->code()].description(); graph->ToolTip(ttip, event->x()+15, event->y(), TT_AlignLeft); graph->timedRedraw(0); } } } } return false; } gFlagsLine::gFlagsLine(ChannelID code) : Layer(code) { } gFlagsLine::~gFlagsLine() { } void gFlagsLine::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { int left = region.boundingRect().left(); int top = region.boundingRect().top(); int width = region.boundingRect().width(); int height = region.boundingRect().height(); if (!m_visible) { return; } if (!m_day) { return; } double minx; double maxx; if (w.blockZoom()) { minx = w.rmin_x; maxx = w.rmax_x; } else { minx = w.min_x; maxx = w.max_x; } double xx = maxx - minx; if (xx <= 0) { return; } double xmult = width / xx; schema::Channel & chan = schema::channel[m_code]; GetTextExtent(chan.label(), m_lx, m_ly); // Draw text label w.renderText(chan.label(), left - m_lx - 10, top + (height / 2) + (m_ly / 2)); float x1, x2; float bartop = top + 2; float bottom = top + height - 2; qint64 X, X2, L; qint64 start; quint32 *tptr; EventStoreType *dptr, * eptr; int idx; QHash >::iterator cei; qint64 clockdrift = qint64(p_profile->cpap->clockDrift()) * 1000L; qint64 drift = 0; QVector vlines; QColor color=schema::channel[m_code].defaultColor(); if (w.printing() && AppSetting->monochromePrinting()) { color = Qt::black; } QBrush brush(color); int tooltipTimeout = AppSetting->tooltipTimeout(); bool hover = false; for (const auto & sess : m_day->sessions) { if (!sess->enabled()) { continue; } drift = (sess->type() == MT_CPAP) ? clockdrift : 0; cei = sess->eventlist.find(m_code); if (cei == sess->eventlist.end()) { continue; } for (const auto & el : cei.value()) { start = el->first() + drift; tptr = el->rawTime(); dptr = el->rawData(); int np = el->count(); eptr = dptr + np; for (idx = 0; dptr < eptr; dptr++, tptr++, idx++) { X = start + *tptr; L = *dptr * 1000; if (X >= minx) { break; } X2 = X - L; if (X2 >= minx) { break; } } np -= idx; if (chan.type() == schema::SPAN) { /////////////////////////////////////////////////////////////////////////// // Draw Event Flag Spans /////////////////////////////////////////////////////////////////////////// for (; dptr < eptr; dptr++) { X = start + * tptr++; L = *dptr * 1000L; X2 = X - L; if (X2 > maxx) { break; } x1 = double(X - minx) * xmult + left; x2 = double(X2 - minx) * xmult + left; int width = x1-x2; width = qMax(2,width); // Insure Rectangle will be visable. Flag events are 2 pixels wide. brush = QBrush(color); painter.fillRect(x2, bartop, width, bottom-bartop, brush); if (!w.selectingArea() && !hover && QRect(x2, bartop, width , bottom-bartop).contains(w.graphView()->currentMousePos())) { hover = true; painter.setPen(QPen(Qt::red,1)); painter.drawRect(x2, bartop, width, bottom-bartop); int x,y; double s = *dptr; double m; s=60*modf(s/60,&m); QString lab = QString("%1").arg(schema::channel[m_code].fullname()); if (m>0) { lab += QObject::tr(" (%2 min, %3 sec)").arg(m).arg(s); } else { lab += QObject::tr(" (%3 sec)").arg(s); } GetTextExtent(lab, x, y); w.ToolTip(lab, x2 - 10, bartop + (3 * w.printScaleY()), TT_AlignRight, tooltipTimeout); } } } else { //if (chan.type() == schema::FLAG) { /////////////////////////////////////////////////////////////////////////// // Draw Event Flag Bars /////////////////////////////////////////////////////////////////////////// for (int i = 0; i < np; i++, dptr++) { X = start + *tptr++; if (X > maxx) { break; } x1 = (X - minx) * xmult + left; if (!w.selectingArea() && !hover && QRect(x1-3, bartop-2, 6, bottom-bartop+4).contains(w.graphView()->currentMousePos())) { hover = true; painter.setPen(QPen(Qt::red,1)); painter.drawRect(x1-2, bartop-2, 4, bottom-bartop+4); int x,y; // QString lab = QString("%1 (%2)").arg(schema::channel[m_code].fullname()).arg(*dptr); QString lab = QString("%1").arg(schema::channel[m_code].fullname()); if (*dptr != 0) lab += QString(" (%2)").arg(*dptr); GetTextExtent(lab, x, y); w.ToolTip(lab, x1 - 10, bartop + (3 * w.printScaleY()), TT_AlignRight, tooltipTimeout); } vlines.append(QLine(x1, bartop, x1, bottom)); } } } } if (w.printing() && AppSetting->monochromePrinting()) { painter.setPen(QPen(Qt::black, 1.5)); } else { painter.setPen(color); } painter.drawLines(vlines); } bool gFlagsLine::mouseMoveEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) // qDebug() << code() << event->x() << event->y() << graph->rect(); return false; } OSCAR-code-v1.3.1/oscar/Graphs/gFlagsLine.h000066400000000000000000000112001417327530600202020ustar00rootroot00000000000000/* gFlagsLine Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code for more details. */ #ifndef GFLAGSLINE_H #define GFLAGSLINE_H #include "gGraphView.h" #include "gspacer.h" class gFlagsGroup; /*! \class gYSpacer \brief A dummy vertical spacer object */ class gLabelArea: public gSpacer { public: gLabelArea(Layer * layer); virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion) { Q_UNUSED(w); Q_UNUSED(painter); Q_UNUSED(region); } virtual int minimumWidth(); protected: Layer *m_mainlayer; virtual bool mouseMoveEvent(QMouseEvent *event, gGraph *graph); virtual Layer * Clone() { gLabelArea * layer = new gLabelArea(nullptr); //ouchie.. Layer::CloneInto(layer); CloneInto(layer); return layer; } void CloneInto(gLabelArea * ) { } }; /*! \class gFlagsLine \brief One single line of event flags in the Event Flags chart */ class gFlagsLine: public Layer { friend class gFlagsGroup; public: /*! \brief Constructs an individual gFlagsLine object \param code The Channel the data is sourced from \param col The colour to draw this flag \param label The label to show to the left of the Flags line. \param always_visible Whether to always show this line, even if empty \param Type of Flag, either FT_Bar, or FT_Span */ gFlagsLine(ChannelID code); virtual ~gFlagsLine(); //! \brief Drawing code to add the flags and span markers to the Vertex buffers. virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); void setTotalLines(int i) { total_lines = i; } void setLineNum(int i) { line_num = i; } virtual Layer * Clone() { gFlagsLine * layer = new gFlagsLine(NoChannel); //ouchie.. Layer::CloneInto(layer); CloneInto(layer); return layer; } void CloneInto(gFlagsLine * layer ) { layer->m_always_visible = m_always_visible; layer->total_lines = total_lines; layer->line_num = line_num; layer->m_lx = m_lx; layer->m_ly = m_ly; } protected: virtual bool mouseMoveEvent(QMouseEvent *event, gGraph *graph); bool m_always_visible; int total_lines, line_num; int m_lx, m_ly; }; /*! \class gFlagsGroup \brief Contains multiple gFlagsLine entries for the Events Flag graph */ class gFlagsGroup: public LayerGroup { friend class gFlagsLabelArea; public: gFlagsGroup(); virtual ~gFlagsGroup(); //! Draw filled rectangles behind Event Flag's, and an outlines around them all, Calls the individual paint for each gFlagLine virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); //! Returns the first time represented by all gFlagLine layers, in milliseconds since epoch virtual qint64 Minx(); //! Returns the last time represented by all gFlagLine layers, in milliseconds since epoch. virtual qint64 Maxx(); //! Checks if each flag has data, and adds valid gFlagLines to the visible layers list virtual void SetDay(Day *); //! Returns true if none of the gFlagLine objects contain any data for this day virtual bool isEmpty(); //! Returns the count of visible flag line entries int count() { return lvisible.size(); } //! Returns the height in pixels of each bar int barHeight() { return m_barh; } //! Returns a list of Visible gFlagsLine layers to draw QVector &visibleLayers() { return lvisible; } void alwaysVisible(ChannelID code) { m_alwaysvisible.push_back(code); } virtual Layer * Clone() { gFlagsGroup * layer = new gFlagsGroup(); //ouchie.. Layer::CloneInto(layer); CloneInto(layer); return layer; } void CloneInto(gFlagsGroup * layer) { layer->m_alwaysvisible = m_alwaysvisible; layer->availableChans = availableChans; for (int i=0; ilvisible.append(dynamic_cast(lvisible.at(i)->Clone())); } layer->m_barh = m_barh; layer->m_empty = m_empty; layer->m_rebuild_cpap = m_rebuild_cpap; } protected: virtual bool mouseMoveEvent(QMouseEvent *event, gGraph *graph); QList m_alwaysvisible; QList availableChans; QVector lvisible; float m_barh; bool m_empty; bool m_rebuild_cpap; }; #endif // GFLAGSLINE_H OSCAR-code-v1.3.1/oscar/Graphs/gFooBar.cpp000066400000000000000000000071531417327530600200550ustar00rootroot00000000000000/* gFooBar Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "Graphs/gFooBar.h" #include #include "Graphs/gGraph.h" #include "Graphs/gYAxis.h" gShadowArea::gShadowArea(QColor shadow_color, QColor line_color) : Layer(NoChannel), m_shadow_color(shadow_color), m_line_color(line_color) { } gShadowArea::~gShadowArea() { } void gShadowArea::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { float left = region.boundingRect().left()+1.0f; float top = region.boundingRect().top()+0.001f; float width = region.boundingRect().width(); float height = region.boundingRect().height(); if (!m_visible) { return; } double xx = w.max_x - w.min_x; if (xx == 0) { return; } float start_px = left - 1; float end_px = left + width; double rmx = w.rmax_x - w.rmin_x; double px = ((1.0 / rmx) * (w.min_x - w.rmin_x)) * width; double py = ((1.0 / rmx) * (w.max_x - w.rmin_x)) * width; painter.fillRect(start_px, top, px, height, QBrush(m_shadow_color)); painter.fillRect(start_px + py, top, end_px-start_px-py, height, QBrush(m_shadow_color)); painter.setPen(QPen(m_line_color,2)); painter.drawLine(QLineF(start_px + px, top, start_px + py, top)); painter.drawLine(QLineF(start_px + px, top + height + 1, start_px + py, top + height + 1)); } gFooBar::gFooBar(int offset, QColor handle_color, QColor line_color) : Layer(NoChannel), m_offset(offset), m_handle_color(handle_color), m_line_color(line_color) { } gFooBar::~gFooBar() { } void gFooBar::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { Q_UNUSED(painter); Q_UNUSED(region); if (!m_visible) { return; } double xx = w.max_x - w.min_x; if (xx == 0) { return; } //int start_px=left; //int end_px=left+width; //float h=top; /* glLineWidth(1); glBegin(GL_LINES); w.qglColor(m_line_color); glVertex2f(start_px, h); glVertex2f(start_px+width, h); glEnd(); double rmx=w.rmax_x-w.rmin_x; double px=((1/rmx)*(w.min_x-w.rmin_x))*width; double py=((1/rmx)*(w.max_x-w.rmin_x))*width; int extra=0; if (fabs(px-py)<2) extra=2; int hh=25; glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glBegin(GL_QUADS); w.qglColor(m_handle_color); glVertex2f(start_px+px-extra,top-hh); glVertex2f(start_px+py+extra,top-hh); //glColor4ub(255,255,255,128); glColor4ub(255,255,255,128); glVertex2f(start_px+py+extra,top-hh/2.0); glVertex2f(start_px+px-extra,top-hh/2.0); // glColor4ub(255,255,255,128); glColor4ub(255,255,255,128); glVertex2f(start_px+px-extra,top-hh/2.0); glVertex2f(start_px+py+extra,top-hh/2.0); w.qglColor(m_handle_color); // glColor4ub(192,192,192,128); glVertex2f(start_px+py+extra,h); glVertex2f(start_px+px-extra,h); glEnd(); glDisable(GL_BLEND); w.qglColor(m_handle_color); glBegin(GL_LINE_LOOP); glVertex2f(start_px+px-extra,top-hh); glVertex2f(start_px+py+extra,top-hh); glVertex2f(start_px+py+extra,h); glVertex2f(start_px+px-extra,h); glEnd(); glLineWidth(3); glBegin(GL_LINES); w.qglColor(m_handle_color); glVertex2f(start_px+px-extra,h); glVertex2f(start_px+py+extra,h); glEnd(); glLineWidth(1); */ } OSCAR-code-v1.3.1/oscar/Graphs/gFooBar.h000066400000000000000000000025421417327530600175170ustar00rootroot00000000000000/* gFooBar Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GFOOBAR_H #define GFOOBAR_H #include "Graphs/layer.h" /*! \class gShadowArea \brief Displays a Shadow for all graph areas not highlighted (used in Event Flags) */ class gShadowArea: public Layer { public: gShadowArea(QColor shadow_color = QColor(40, 40, 40, 40), QColor line_color = Qt::blue); virtual ~gShadowArea(); virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); protected: QColor m_shadow_color; QColor m_line_color; }; /*! \class gFooBar \brief Was a kind of scrollbar thingy that used to be used for representing the overall graph areas. Currently Unused and empty. */ class gFooBar: public Layer { public: static const int Margin = 15; public: gFooBar(int offset = 10, QColor handle_color = QColor("orange"), QColor line_color = QColor("dark grey")); virtual ~gFooBar(); virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); protected: int m_offset; QColor m_handle_color; QColor m_line_color; }; #endif // GFOOBAR_H OSCAR-code-v1.3.1/oscar/Graphs/gGraph.cpp000066400000000000000000001124561417327530600177510ustar00rootroot00000000000000/* gGraph Implemntation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "Graphs/gGraph.h" #include #include #include #include #include "mainwindow.h" #include "Graphs/gGraphView.h" #include "Graphs/layer.h" #include "SleepLib/profiles.h" extern MainWindow *mainwin; // Graph globals. QFont *defaultfont = nullptr; QFont *mediumfont = nullptr; QFont *bigfont = nullptr; QHash images; static bool globalsInitialized = false; // Graph constants. static const double zoom_hard_limit = 500.0; // Calculate Catmull-Rom Spline of given 4 samples, with t between 0-1; float CatmullRomSpline(float p0, float p1, float p2, float p3, float t) { float t2 = t*t; float t3 = t2 * t; return (float)0.5 * ((2 * p1) + (-p0 + p2) * t + (2*p0 - 5*p1 + 4*p2 - p3) * t2 + (-p0 + 3*p1- 3*p2 + p3) * t3); } // Must be called from a thread inside the application. bool InitGraphGlobals() { if (globalsInitialized) { return true; } if (!p_pref->contains("Fonts_Graph_Name")) { (*p_pref)["Fonts_Graph_Name"] = "Sans Serif"; (*p_pref)["Fonts_Graph_Size"] = 10; (*p_pref)["Fonts_Graph_Bold"] = false; (*p_pref)["Fonts_Graph_Italic"] = false; } if (!p_pref->contains("Fonts_Title_Name")) { (*p_pref)["Fonts_Title_Name"] = "Sans Serif"; (*p_pref)["Fonts_Title_Size"] = 12; (*p_pref)["Fonts_Title_Bold"] = true; (*p_pref)["Fonts_Title_Italic"] = false; } if (!p_pref->contains("Fonts_Big_Name")) { (*p_pref)["Fonts_Big_Name"] = "Serif"; (*p_pref)["Fonts_Big_Size"] = 35; (*p_pref)["Fonts_Big_Bold"] = false; (*p_pref)["Fonts_Big_Italic"] = false; } defaultfont = new QFont((*p_pref)["Fonts_Graph_Name"].toString(), (*p_pref)["Fonts_Graph_Size"].toInt(), (*p_pref)["Fonts_Graph_Bold"].toBool() ? QFont::Bold : QFont::Normal, (*p_pref)["Fonts_Graph_Italic"].toBool() ); mediumfont = new QFont((*p_pref)["Fonts_Title_Name"].toString(), (*p_pref)["Fonts_Title_Size"].toInt(), (*p_pref)["Fonts_Title_Bold"].toBool() ? QFont::Bold : QFont::Normal, (*p_pref)["Fonts_Title_Italic"].toBool() ); bigfont = new QFont((*p_pref)["Fonts_Big_Name"].toString(), (*p_pref)["Fonts_Big_Size"].toInt(), (*p_pref)["Fonts_Big_Bold"].toBool() ? QFont::Bold : QFont::Normal, (*p_pref)["Fonts_Big_Italic"].toBool() ); defaultfont->setStyleHint(QFont::AnyStyle, QFont::OpenGLCompatible); mediumfont->setStyleHint(QFont::AnyStyle, QFont::OpenGLCompatible); bigfont->setStyleHint(QFont::AnyStyle, QFont::OpenGLCompatible); //images["mask"] = new QImage(":/icons/mask.png"); images["oximeter"] = new QImage(":/icons/cubeoximeter.png"); images["smiley"] = new QImage(":/icons/smileyface.png"); //images["sad"] = new QImage(":/icons/sadface.png"); images["logo"] = new QImage(":/icons/logo-lm.png"); images["brick"] = new QImage(":/icons/brick.png"); images["nographs"] = new QImage(":/icons/nographs.png"); images["nodata"] = new QImage(":/icons/nodata.png"); globalsInitialized = true; return true; } void DestroyGraphGlobals() { if (!globalsInitialized) { return; } delete defaultfont; delete bigfont; delete mediumfont; for (auto & image : images) { delete image; } globalsInitialized = false; } gGraph::gGraph(QString name, gGraphView *graphview, QString title, QString units, int height, short group) : m_name(name), m_graphview(graphview), m_title(title), m_units(units), m_visible(true) { if (height == 0) { height = AppSetting->graphHeight(); Q_UNUSED(height) } if (graphview && graphview->contains(name)) { qDebug() << "Trying to duplicate " << name << " when a graph with the same name already exists"; name+="-1"; } m_min_height = 60; m_width = 0; m_layers.clear(); m_snapshot = false; f_miny = f_maxy = 0; rmin_x = rmin_y = 0; rmax_x = rmax_y = 0; max_x = max_y = 0; min_x = min_y = 0; rec_miny = rec_maxy = 0; rphysmax_y = rphysmin_y = 0; m_zoomY = 0; m_selectedDuration = 0; if (graphview) { graphview->addGraph(this, group); timer = new QTimer(graphview); connect(timer, SIGNAL(timeout()), SLOT(Timeout())); } else { timer = new QTimer(); connect(timer, SIGNAL(timeout()), SLOT(Timeout())); // know what I'm doing now.. ;) // qWarning() << "gGraph created without a gGraphView container.. Naughty programmer!! Bad!!!"; } m_margintop = 14; m_marginbottom = 5; m_marginleft = 0; m_marginright = 15; m_selecting_area = m_blockzoom = false; m_pinned = false; m_lastx23 = 0; invalidate_yAxisImage = true; invalidate_xAxisImage = true; m_block_select = false; m_enforceMinY = m_enforceMaxY = false; m_showTitle = true; m_printing = false; left = right = top = bottom = 0; } gGraph::~gGraph() { for (auto & layer : m_layers) { if (layer->unref()) { delete layer; } } m_layers.clear(); if (timer) { timer->stop(); disconnect(timer, 0, 0, 0); delete timer; } } void gGraph::Trigger(int ms) { if (timer->isActive()) { timer->stop(); } timer->setSingleShot(true); timer->start(ms); } void gGraph::Timeout() { deselect(); m_graphview->timedRedraw(0); } void gGraph::deselect() { for (auto & layer : m_layers) { layer->deselect(); } } bool gGraph::isSelected() { bool res = false; for (const auto & layer : m_layers) { res = layer->isSelected(); if (res) { break; } } return res; } bool gGraph::isEmpty() { bool empty = true; for (const auto & layer : m_layers) { if (!layer->isEmpty()) { empty = false; break; } } return empty; } float gGraph::printScaleX() { return m_graphview->printScaleX(); } float gGraph::printScaleY() { return m_graphview->printScaleY(); } //void gGraph::drawGLBuf() //{ // float linesize = 1; // if (m_printing) { linesize = 4; } //ceil(m_graphview->printScaleY()); // for (int i = 0; i < m_layers.size(); i++) { // m_layers[i]->drawGLBuf(linesize); // } //} void gGraph::setDay(Day *day) { // Don't update for snapshots.. if (m_snapshot) return; m_day = day; for (auto & layer : m_layers) { layer->SetDay(day); } rmin_y = rmax_y = 0; // This resets weight and bmi overview graphs to full date range when they are changed. // is it required ever? // ResetBounds(); } void gGraph::setZoomY(short zoom) { m_zoomY = zoom; timedRedraw(0); } void gGraph::renderText(QString text, int x, int y, float angle, QColor color, QFont *font, bool antialias) { m_graphview->AddTextQue(text, x, y, angle, color, font, antialias); } void gGraph::renderText(QString text, QRectF rect, quint32 flags, float angle, QColor color, QFont *font, bool antialias) { m_graphview->AddTextQue(text, rect, flags, angle, color, font, antialias); } void gGraph::paint(QPainter &painter, const QRegion ®ion) { m_rect = region.boundingRect(); int originX = m_rect.left(); int originY = m_rect.top(); int width = m_rect.width(); int height = m_rect.height(); int fw, font_height; GetTextExtent("Wg@", fw, font_height); if (m_margintop > 0) { m_margintop = font_height + (2*printScaleY()); } //m_marginbottom=5; left = marginLeft()*printScaleX(), right = marginRight()*printScaleX(), top = marginTop(), bottom = marginBottom() * printScaleY(); //int x; int y; if (m_showTitle) { int title_x, yh; painter.setFont(*mediumfont); QFontMetrics fm(*mediumfont); yh = fm.height(); //GetTextExtent("Wy@",x,yh,mediumfont); // This gets a better consistent height. should be cached. y = yh; //x = fm.width(title()); //GetTextExtent(title(),x,y,mediumfont); title_x = float(yh) ; QString & txt = title(); graphView()->AddTextQue(txt, marginLeft() + title_x + 8*printScaleX(), originY + height / 2 - y / 2, 90, Qt::black, mediumfont); left += graphView()->titleWidth*printScaleX(); } else { left = 0; } if (m_snapshot) { QLinearGradient linearGrad(QPointF(100, 100), QPointF(width / 2, 100)); linearGrad.setColorAt(0, QColor(255, 150, 150,40)); linearGrad.setColorAt(1, QColor(255,255,255,20)); painter.fillRect(m_rect, QBrush(linearGrad)); painter.setFont(*defaultfont); painter.setPen(QColor(0,0,0,255)); QString t = name().section(";", -1); QString txt = QObject::tr("Snapshot %1").arg(t); QRectF rec = QRect(m_rect.left(),m_rect.top()+6*printScaleY(), m_rect.width(), 0); rec = painter.boundingRect(rec, Qt::AlignCenter, txt); painter.drawText(rec, Qt::AlignCenter, txt); m_margintop += rec.height(); top = m_margintop; } #ifdef DEBUG_LAYOUT QColor col = Qt::red; painter.setPen(col); painter.drawLine(0, originY, 0, originY + height); painter.drawLine(left, originY, left, originY + height); #endif int tmp; // left = 0; for (const auto & layer : m_layers) { if (!layer->visible()) { continue; } tmp = layer->minimumHeight();// * m_graphview->printScaleY(); if (layer->position() == LayerTop) { top += tmp; } if (layer->position() == LayerBottom) { bottom += tmp * printScaleY(); } } for (const auto & layer : m_layers) { if (!layer->visible()) { continue; } tmp = layer->minimumWidth(); tmp *= m_graphview->printScaleX(); tmp *= m_graphview->devicePixelRatio(); if (layer->position() == LayerLeft) { QRect rect(originX + left, originY + top, tmp, height - top - bottom); layer->m_rect = rect; // layer->paint(painter, *this, QRegion(rect)); left += tmp; #ifdef DEBUG_LAYOUT QColor col = Qt::red; painter.setPen(col); painter.drawLine(originX + left - 1, originY, originX + left - 1, originY + height); #endif } if (layer->position() == LayerRight) { right += tmp; QRect rect(originX + width - right, originY + top, tmp, height - top - bottom); layer->m_rect = rect; //layer->paint(painter, *this, QRegion(rect)); #ifdef DEBUG_LAYOUT QColor col = Qt::red; painter.setPen(col); painter.drawLine(originX + width - right, originY, originX + width - right, originY + height); #endif } } bottom = marginBottom() * printScaleY(); top = marginTop(); for (const auto & layer : m_layers) { if (!layer->visible()) { continue; } tmp = layer->minimumHeight(); if (layer->position() == LayerTop) { QRect rect(originX + left, originY + top, width - left - right, tmp); layer->m_rect = rect; layer->paint(painter, *this, QRegion(rect)); top += tmp; } if (layer->position() == LayerBottom) { bottom += tmp * printScaleY(); QRect rect(originX + left, originY + height - bottom, width - left - right, tmp); layer->m_rect = rect; layer->paint(painter, *this, QRegion(rect)); } } if (isPinned()) { // Fill the background on pinned graphs painter.fillRect(originX + left, originY + top, width - right, height - bottom - top, QBrush(QColor(Qt::white))); } for (const auto & layer : m_layers) { if (!layer->visible()) { continue; } if (layer->position() == LayerCenter) { QRect rect(originX + left, originY + top, width - left - right, height - top - bottom); layer->m_rect = rect; layer->paint(painter, *this, QRegion(rect)); } } // Draw anything like the YAxis labels afterwards, in case the graph scale was updated during draw for (const auto & layer : m_layers) { if (!layer->visible()) { continue; } if ((layer->position() == LayerLeft) || (layer->position() == LayerRight)) { layer->paint(painter, *this, QRegion(layer->m_rect)); } } if (m_selection.width() > 0 && m_selecting_area) { QColor col(128, 128, 255, 128); painter.fillRect(originX + m_selection.x(), originY + top, m_selection.width(), height - bottom - top,QBrush(col)); // quads()->add(originX + m_selection.x(), originY + top, // originX + m_selection.x() + m_selection.width(), originY + top, col.rgba()); // quads()->add(originX + m_selection.x() + m_selection.width(), originY + height - bottom, // originX + m_selection.x(), originY + height - bottom, col.rgba()); } if (isPinned() && !printing()) { painter.drawPixmap(-5, originY-10, m_graphview->pin_icon); } } QPixmap gGraph::renderPixmap(int w, int h, bool printing) { QFont *_defaultfont = defaultfont; QFont *_mediumfont = mediumfont; QFont *_bigfont = bigfont; QFont fa = *defaultfont; QFont fb = *mediumfont; QFont fc = *bigfont; m_printing = printing; if (printing) { fa.setPixelSize(28); fb.setPixelSize(32); fc.setPixelSize(70); graphView()->setPrintScaleX(2.5f); graphView()->setPrintScaleY(2.2f); } else { graphView()->setPrintScaleX(1.0f); graphView()->setPrintScaleY(1.0f); } defaultfont = &fa; mediumfont = &fb; bigfont = &fc; QPixmap pm(w,h); bool pixcaching = AppSetting->usePixmapCaching(); graphView()->setUsePixmapCache(false); AppSetting->setUsePixmapCaching(false); QPainter painter(&pm); painter.fillRect(0,0,w,h,QBrush(QColor(Qt::white))); QRegion region(0,0,w,h); paint(painter, region); DrawTextQue(painter); painter.end(); graphView()->setUsePixmapCache(pixcaching); AppSetting->setUsePixmapCaching(pixcaching); graphView()->setPrintScaleX(1); graphView()->setPrintScaleY(1); defaultfont = _defaultfont; mediumfont = _mediumfont; bigfont = _bigfont; m_printing = false; return pm; } // Sets a new Min & Max X dates for clipping data (refresh done by caller) void gGraph::SetXBounds(qint64 minx, qint64 maxx) { invalidate_xAxisImage = true; min_x = minx; max_x = maxx; //repaint(); //m_graphview->redraw(); } int gGraph::flipY(int y) { return m_graphview->height() - y; } void gGraph::ResetBounds() { if (m_snapshot) return; invalidate_xAxisImage = true; min_x = MinX(); max_x = MaxX(); min_y = MinY(); max_y = MaxY(); } void gGraph::ToolTip(QString text, int x, int y, ToolTipAlignment align, int timeout) { if (timeout <= 0) { timeout = AppSetting->tooltipTimeout(); } m_graphview->m_tooltip->display(text, x, y, align, timeout); } // YAxis Autoscaling code void gGraph::roundY(EventDataType &miny, EventDataType &maxy) { if (zoomY() == 2) { miny = rec_miny; maxy = rec_maxy; if (maxy > miny) return; } else if (zoomY() ==1) { miny = physMinY(); maxy = physMaxY(); if (maxy > miny) return; } miny = MinY(); maxy = MaxY(); int m, t; bool ymin_good = false, ymax_good = false; // Have no minx/miny reference, have to create one if (maxy == miny) { m = ceil(maxy / 2.0); t = m * 2; if (maxy == t) { t += 2; } if (!ymax_good) { maxy = t; } m = floor(miny / 2.0); t = m * 2; if (miny == t) { t -= 2; } if (miny >= 0 && t < 0) { t = 0; } if (!ymin_good) { miny = t; } if (miny < 0) { EventDataType tmp = qMax(qAbs(miny), qAbs(maxy)); maxy = tmp; miny = -tmp; } return; } if (maxy >= 400) { m = ceil(maxy / 50.0); t = m * 50; if (!ymax_good) { maxy = t; } m = floor(miny / 50.0); if (!ymin_good) { miny = m * 50; } } else if (maxy >= 30) { m = ceil(maxy / 5.0); t = m * 5; if (!ymax_good) { maxy = t; } m = floor(miny / 5.0); if (!ymin_good) { miny = m * 5; } } else { if (maxy == miny && maxy == 0) { maxy = 0.5; } else { //maxy*=4.0; //miny*=4.0; if (!ymax_good) { maxy = ceil(maxy); } if (!ymin_good) { miny = floor(miny); } //maxy/=4.0; //miny/=4.0; } } // Make the range symmetrical if there are both positive and negative values. if (miny < 0 && maxy > 0) { EventDataType tmp = qMax(qAbs(miny), qAbs(maxy)); maxy = tmp; miny = -tmp; } //if (m_enforceMinY) { miny=f_miny; } //if (m_enforceMaxY) { maxy=f_maxy; } } void gGraph::AddLayer(Layer *l, LayerPosition position, short width, short height, short order, bool movable, short x, short y) { l->setLayout(position, width, height, order); l->setMovable(movable); l->setPos(x, y); l->addref(); m_layers.push_back(l); } void gGraph::dataChanged() { for (auto & layer : m_layers) { layer->dataChanged(); } } void gGraph::redraw() { m_graphview->redraw(); } void gGraph::timedRedraw(int ms) { m_graphview->timedRedraw(ms); } double gGraph::currentTime() const { return m_graphview->currentTime(); } double gGraph::screenToTime(int xpos) { double w = m_rect.width() - left - right; double xx = m_blockzoom ? rmax_x - rmin_x : max_x - min_x; double xmult = xx / w; double x = xpos - m_rect.left() - left; double res = xmult * x; res += m_blockzoom ? rmin_x : min_x; return res; } void gGraph::mouseMoveEvent(QMouseEvent *event) { // qDebug() << m_title << "Move" << event->pos() << m_graphview->pointClicked(); if (m_rect.width() == 0) return; int y = event->y(); int x = event->x(); //bool doredraw = false; timedRedraw(0); for (const auto & layer : m_layers) { if (layer->m_rect.contains(x, y)) if (layer->mouseMoveEvent(event, this)) { return; } } y -= m_rect.top(); x -= m_rect.left(); int x2 = m_graphview->pointClicked().x() - m_rect.left(); int w = m_rect.width() - left - right; double xx; double xmult; { xmult = (m_blockzoom ? double(rmax_x - rmin_x) : double(max_x - min_x)) / double(w); double a = x; if (a < left) a = left; if (a > left+w) a = left+w; a -= left; a *= xmult; a += m_blockzoom ? rmin_x : min_x; m_currentTime = a; m_graphview->setCurrentTime(a); } if (m_graphview->m_selected_graph == this) { // Left Mouse button dragging if (event->buttons() & Qt::LeftButton) { //qDebug() << m_title << "Moved" << x << y << left << right << top << bottom << m_width << h; int a1 = MIN(x, x2); int a2 = MAX(x, x2); if (a1 < left) { a1 = left; } if (a2 > left + w) { a2 = left + w; } m_selecting_area = true; m_selection = QRect(a1 - 1, 0, a2 - a1, m_rect.height()); double w2 = m_rect.width() - right - left; if (m_blockzoom) { xmult = (rmax_x - rmin_x) / w2; } else { xmult = (max_x - min_x) / w2; } qint64 a = double(a2 - a1) * xmult; m_selectedDuration = a; float d = double(a) / 86400000.0; int h = a / 3600000; int m = (a / 60000) % 60; int s = (a / 1000) % 60; int ms(a % 1000); if (d > 1) { m_selDurString = tr("%1 days").arg(floor(d)); } else { m_selDurString.sprintf("%02i:%02i:%02i:%03i", h, m, s, ms); } ToolTipAlignment align = x >= x2 ? TT_AlignLeft : TT_AlignRight; int offset = (x >= x2) ? 20 : - 20; ToolTip(m_selDurString, m_rect.left() + x + offset, m_rect.top() + y + 20, align); //doredraw = true; } else if (event->buttons() & Qt::RightButton) { // Right Mouse button dragging m_graphview->setPointClicked(event->pos()); x -= left; x2 -= left; if (!m_blockzoom) { xx = max_x - min_x; xmult = xx / double(w); qint64 j1 = xmult * x; qint64 j2 = xmult * x2; qint64 jj = j2 - j1; min_x += jj; max_x += jj; if (min_x < rmin_x) { min_x = rmin_x; max_x = rmin_x + xx; } if (max_x > rmax_x) { max_x = rmax_x; min_x = rmax_x - xx; } m_graphview->SetXBounds(min_x, max_x, m_group, false); // doredraw = true; } else { qint64 qq = rmax_x - rmin_x; xx = max_x - min_x; if (xx == qq) { xx = 1800000; } xmult = qq / double(w); qint64 j1 = (xmult * x); min_x = rmin_x + j1 - (xx / 2); max_x = min_x + xx; if (min_x < rmin_x) { min_x = rmin_x; max_x = rmin_x + xx; } if (max_x > rmax_x) { max_x = rmax_x; min_x = rmax_x - xx; } m_graphview->SetXBounds(min_x, max_x, m_group, false); //doredraw = true; } } } //if (!nolayer) { // no mouse button // if (doredraw) { // m_graphview->timedRedraw(0); // } //} //if (x>left+m_marginleft && xtop+m_margintop && ymetaSelect(); } void gGraph::mousePressEvent(QMouseEvent *event) { int y = event->pos().y(); int x = event->pos().x(); // qDebug() << m_title << "gGraph mousePressEvent, x=" << x << " y=" << y; for (const auto & layer : m_layers) { if (layer->m_rect.contains(x, y)) if (layer->mousePressEvent(event, this)) { return; } } /* int w=m_lastbounds.width()-(right+m_marginright); //int h=m_lastbounds.height()-(bottom+m_marginbottom); //int x2,y2; double xx=max_x-min_x; //double xmult=xx/w; if (x>left+m_marginleft && xtop+m_margintop && ypos().y(); int x = event->pos().x(); // qDebug() << m_title << "gGraph mouseReleaseEvent at x,y" << x << y; for (const auto & layer : m_layers) { if (layer->m_rect.contains(x, y)) if (layer->mouseReleaseEvent(event, this)) { return; } } x -= m_rect.left(); y -= m_rect.top(); int w = m_rect.width() - left - right; //(m_marginleft+left+right+m_marginright); int h = m_rect.height() - bottom; //+m_marginbottom); int x2 = m_graphview->pointClicked().x() - m_rect.left(); //int y2 = m_graphview->pointClicked().y() - m_rect.top(); m_selDurString = QString(); // qDebug() << m_title << "Released" << min_x << max_x << x << y << x2 << left << right << top << bottom << m_width << m_height; if (m_selecting_area) { m_selecting_area = false; m_selection.setWidth(0); // Shift-drag only measures a range and should not zoom in. if ((event->modifiers() & Qt::ShiftModifier) != 0) { return; } if (m_graphview->horizTravel() > mouse_movement_threshold) { x -= left; //+m_marginleft; //y -= top; //+m_margintop; x2 -= left; //+m_marginleft; //y2 -= top; //+m_margintop; if (x < 0) { x = 0; } if (x2 < 0) { x2 = 0; } if (x > w) { x = w; } if (x2 > w) { x2 = w; } double xx; double xmult; if (!m_blockzoom) { xx = max_x - min_x; xmult = xx / double(w); qint64 j1 = min_x + xmult * x; qint64 j2 = min_x + xmult * x2; qint64 a1 = MIN(j1, j2) qint64 a2 = MAX(j1, j2) //if (a1 rmax_x) { a2 = rmax_x; } if (a1 == a2) // Don't zoom into a block if range is zero return; if (a1 <= rmin_x && a2 <= rmin_x) { //qDebug() << "Foo??"; } else { // qDebug() << m_title << "!m_blockzoom (960), a1, a2" << a1 << a2; if (a2 - a1 < zoom_hard_limit) { a2 = a1 + zoom_hard_limit; } m_graphview->SetXBounds(a1, a2, m_group); } } else { xx = rmax_x - rmin_x; xmult = xx / double(w); qint64 j1 = rmin_x + xmult * x; qint64 j2 = rmin_x + xmult * x2; qint64 a1 = MIN(j1, j2) qint64 a2 = MAX(j1, j2) //if (a1 rmax_x) { a2 = rmax_x; } if (a1 <= rmin_x && a2 <= rmin_x) { qDebug() << "Foo2??"; } else { // qDebug() << m_title << "m_blockzoom (979), a1, a2" << a1 << a2; if (a2 - a1 < zoom_hard_limit) { a2 = a1 + zoom_hard_limit; } m_graphview->SetXBounds(a1, a2, m_group); } } return; } else { m_graphview->redraw(); } } if ((m_graphview->horizTravel() < mouse_movement_threshold) && (x > left && x < w + left && y > top && y < h)) { // normal click in main area if (!m_blockzoom) { double zoom; if (event->button() & Qt::RightButton) { zoom = 1.33; if (event->modifiers() & Qt::ControlModifier) { zoom *= 1.5; } ZoomX(zoom, x); // Zoom out return; } else if (event->button() & Qt::LeftButton) { zoom = 0.75; if (event->modifiers() & Qt::ControlModifier) { zoom /= 1.5; } ZoomX(zoom, x); // zoom in. return; } } else { x -= left; //y -= top; //w-=m_marginleft+left; double qq = rmax_x - rmin_x; double xmult; double xx = max_x - min_x; //if (xx==qq) xx=1800000; xmult = qq / double(w); if ((xx == qq) || (x == m_lastx23)) { double zoom = 1; if (event->button() & Qt::RightButton) { zoom = 1.33; if (event->modifiers() & Qt::ControlModifier) { zoom *= 1.5; } } else if (event->button() & Qt::LeftButton) { zoom = 0.75; if (event->modifiers() & Qt::ControlModifier) { zoom /= 1.5; } } xx *= zoom; if (xx < qq / zoom_hard_limit) { xx = qq / zoom_hard_limit; } if (xx > qq) { xx = qq; } } double j1 = xmult * x; min_x = rmin_x + j1 - (xx / 2.0); max_x = min_x + xx; if (min_x < rmin_x) { min_x = rmin_x; max_x = rmin_x + xx; } else if (max_x > rmax_x) { max_x = rmax_x; min_x = rmax_x - xx; } m_graphview->SetXBounds(min_x, max_x, m_group); m_lastx23 = x; } } //m_graphview->redraw(); } void gGraph::wheelEvent(QWheelEvent *event) { qDebug() << m_title << "Wheel" << event->x() << event->y() << event->delta(); //int y=event->pos().y(); if (event->orientation() == Qt::Horizontal) { return; } int x = event->pos().x() - m_graphview->titleWidth; //(left+m_marginleft); if (event->delta() > 0) { ZoomX(0.75, x); } else { ZoomX(1.5, x); } int y = event->pos().y(); x = event->pos().x(); for (const auto & layer : m_layers) { if (layer->m_rect.contains(x, y)) { layer->wheelEvent(event, this); } } } void gGraph::mouseDoubleClickEvent(QMouseEvent *event) { //mousePressEvent(event); //mouseReleaseEvent(event); int y = event->pos().y(); int x = event->pos().x(); for (const auto & layer : m_layers) { if (layer->m_rect.contains(x, y)) { layer->mouseDoubleClickEvent(event, this); } } //int w=m_lastbounds.width()-(m_marginleft+left+right+m_marginright); //int h=m_lastbounds.height()-(bottom+m_marginbottom); //int x2=m_graphview->pointClicked().x(),y2=m_graphview->pointClicked().y(); // if ((m_graphview->horizTravel()left+m_marginleft && xtop+m_margintop && ybutton() & Qt::RightButton) { // ZoomX(1.66,x); // Zoon out // return; // } else if (event->button() & Qt::LeftButton) { // ZoomX(0.75/2.0,x); // zoom in. // return; // } // } else { // Propagate the events to graph Layers // } //mousePressEvent(event); //mouseReleaseEvent(event); //qDebug() << m_title << "Double Clicked" << event->x() << event->y(); } void gGraph::keyPressEvent(QKeyEvent *event) { for (const auto & layer : m_layers) { layer->keyPressEvent(event, this); } //qDebug() << m_title << "Key Pressed.. implement me" << event->key(); } void gGraph::keyReleaseEvent(QKeyEvent *event) { if (!m_graphview) return; if (m_graphview->selectionInProgress() && m_graphview->metaSelect()) { if (!(event->modifiers() & Qt::AltModifier)) { } } } void gGraph::ZoomX(double mult, int origin_px) { // qDebug() << "gGraph::ZoomX width, left, right" << m_rect.width() << left << right; int width = m_rect.width() - left - right; //(m_marginleft+left+right+m_marginright); if (origin_px == 0) { origin_px = (width / 2); } else { origin_px -= left; } if (origin_px < 0) { origin_px = 0; } if (origin_px > width) { origin_px = width; } // Okay, I want it to zoom in centered on the mouse click area.. // Find X graph position of mouse click // find current zoom width // apply zoom // center on point found in step 1. qint64 min = min_x; qint64 max = max_x; double hardspan = rmax_x - rmin_x; double span = max - min; double ww = double(origin_px) / double(width); double origin = ww * span; //double center=0.5*span; //double dist=(origin-center); double q = span * mult; if (q > hardspan) { q = hardspan; } if (q < hardspan / zoom_hard_limit) { q = hardspan / zoom_hard_limit; } min = min + origin - (q * ww); max = min + q; if (min < rmin_x) { min = rmin_x; max = min + q; } if (max > rmax_x) { max = rmax_x; min = max - q; } //extern const int max_history; m_graphview->SetXBounds(min, max, m_group); //updateSelectionTime(max-min); } void gGraph::DrawTextQue(QPainter &painter) { AppSetting->usePixmapCaching() ? m_graphview->DrawTextQueCached(painter) : m_graphview->DrawTextQue(painter); } // margin recalcs.. void gGraph::resize(int width, int height) { invalidate_xAxisImage = true; invalidate_yAxisImage = true; Q_UNUSED(width); Q_UNUSED(height); //m_height=height; //m_width=width; } qint64 gGraph::MinX() { qint64 val = 0, tmp; for (const auto & layer : m_layers) { if (layer->isEmpty()) { continue; } tmp = layer->Minx(); if (!tmp) { continue; } if (!val || tmp < val) { val = tmp; } } if (val) { rmin_x = val; } return val; } qint64 gGraph::MaxX() { //bool first=true; qint64 val = 0, tmp; for (const auto & layer : m_layers) { if (layer->isEmpty()) { continue; } tmp = layer->Maxx(); //if (!tmp) continue; if (!val || (tmp > val)) { val = tmp; } } if (val) { rmax_x = val; } return val; } EventDataType gGraph::MinY() { bool first = true; EventDataType val = 0, tmp; if (m_enforceMinY) { return rmin_y = f_miny; } for (const auto & layer : m_layers) { if (layer->isEmpty() || (layer->layerType() == LT_Other)) { continue; } tmp = layer->Miny(); // if (tmp == 0 && tmp == (*l)->Maxy()) { // continue; // } if (first) { val = tmp; first = false; } else { if (tmp < val) { val = tmp; } } } return rmin_y = val; // return rmin_y = val * 0.9; } EventDataType gGraph::MaxY() { bool first = true; EventDataType val = 0, tmp; if (m_enforceMaxY) { return rmax_y = f_maxy; } for (const auto & layer : m_layers) { if (layer->isEmpty() || (layer->layerType() == LT_Other)) { continue; } tmp = layer->Maxy(); // if (tmp == 0 && layer->Miny() == 0) { // continue; // } if (first) { val = tmp; first = false; } else { if (tmp > val) { val = tmp; } } } return rmax_y = val; } EventDataType gGraph::physMinY() { bool first = true; EventDataType val = 0, tmp; //if (m_enforceMinY) return rmin_y=f_miny; for (const auto & layer : m_layers) { if (layer->isEmpty()) { continue; } tmp = layer->physMiny(); if (tmp == 0 && layer->physMaxy() == 0) { continue; } if (first) { val = tmp; first = false; } else { if (tmp < val) { val = tmp; } } } return rphysmin_y = val; } EventDataType gGraph::physMaxY() { bool first = true; EventDataType val = 0, tmp; // if (m_enforceMaxY) return rmax_y=f_maxy; for (const auto & layer : m_layers) { if (layer->isEmpty()) { continue; } tmp = layer->physMaxy(); if (tmp == 0 && layer->physMiny() == 0) { continue; } if (first) { val = tmp; first = false; } else { if (tmp > val) { val = tmp; } } } return rphysmax_y = val; } void gGraph::SetMinX(qint64 v) { rmin_x = min_x = v; } void gGraph::SetMaxX(qint64 v) { rmax_x = max_x = v; } void gGraph::SetMinY(EventDataType v) { rmin_y = min_y = v; } void gGraph::SetMaxY(EventDataType v) { rmax_y = max_y = v; } Layer *gGraph::getLineChart() { gLineChart *lc; for (auto & layer : m_layers) { lc = dynamic_cast(layer); if (lc) { return lc; } } return nullptr; } int gGraph::minHeight() { int minheight = m_min_height; // int top = 0; // int center = 0; // int bottom = 0; for (const auto & layer : m_layers) { int mh = layer->minimumHeight(); mh += m_margintop + m_marginbottom; if (mh > minheight) minheight = mh; } // layers need to set their own too.. return minheight; } int GetXHeight(QFont *font) { QFontMetrics fm(*font); return fm.xHeight(); } void gGraph::dumpInfo() { for (const auto & layer : m_layers) { if (!layer->visible()) { continue; } if (layer->position() == LayerCenter) { gLineChart *lc = dynamic_cast(layer); if (lc != nullptr) { QString text = lc->getMetaString(currentTime()); if (!text.isEmpty()) { mainwin->log(text); } } } } } OSCAR-code-v1.3.1/oscar/Graphs/gGraph.h000066400000000000000000000352761417327530600174220ustar00rootroot00000000000000/* gGraph Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef graphs_ggraph_h #define graphs_ggraph_h //#define DEBUG_LAYOUT #include #include #include #include #include #include #include "Graphs/glcommon.h" #include "Graphs/layer.h" class gGraphView; // Graph globals. extern QFont *defaultfont; extern QFont *mediumfont; extern QFont *bigfont; extern QHash images; bool InitGraphGlobals(); void DestroyGraphGlobals(); const int mouse_movement_threshold = 6; float CatmullRomSpline(float p0, float p1, float p2, float p3, float t = 0.5); inline void GetTextExtent(QString text, int &width, int &height, QFont *font) { QFontMetrics fm(*font); QRect r = fm.boundingRect(text); width = r.width(); height = r.height(); } /*! \class gGraph \brief Single Graph object, containing multiple layers and Layer layout code */ class gGraph : public QObject { Q_OBJECT public: friend class gGraphView; /*! \brief Creates a new graph object \param gGraphView * graphview if not null, links the graph to that object \param QString title containing the graph Title which is rendered vertically \param int height containing the opening height for this graph \param short group containing which graph-link group this graph belongs to */ gGraph(QString name, gGraphView *graphview = nullptr, QString title = "", QString units = "", int height = 0, short group = 0); virtual ~gGraph(); //! \brief Tells all Layers to deselect any highlighting void deselect(); //! \brief Returns true if any Layers have anything highlighted bool isSelected(); //! \brief Starts the singleshot Timer running, for ms milliseconds void Trigger(int ms); /*! \fn QPixmap renderPixmap(int width, int height, float fontscale=1.0); \brief Returns a QPixmap containing a snapshot of the graph rendered at size widthxheight \param int width Width of graph 'screenshot' \param int height Height of graph 'screenshot' \param float fontscale Scaling value to adjust DPI (when used for HighRes printing) Note if width or height is more than the OpenGL system allows, it could result in a crash Keeping them under 2048 is a reasonably safe value. */ QPixmap renderPixmap(int width, int height, bool printing = false); //! \brief Set Graph visibility status void setVisible(bool b) { m_visible = b; } //! \brief Return Graph visibility status bool visible() { return m_visible; } //! \brief Returns true if Graph is currently being snapshotted for printing bool printing() { return m_printing; } //! \brief Return height element. This is used by the scaler in gGraphView. inline const float & height() { return m_height; } //! \brief Set the height element. (relative to the total of all heights) void setHeight(float height) { m_height = height; invalidate_yAxisImage = true; } //! \brief Return minimum height this graph is allowed to (considering layer preferences too) int minHeight(); void setMinHeight(int height) { m_min_height = height; } int maxHeight() { return m_max_height; } void setMaxHeight(int height) { m_max_height = height; } //! \brief Returns true if the vertical graph title is shown bool showTitle() { return m_showTitle; } //! \brief Set whether or not to render the vertical graph title void setShowTitle(bool b) { m_showTitle = b; } //! \brief Returns printScaleX, used for DPI scaling in report printing float printScaleX(); //! \brief Returns printScaleY, used for DPI scaling in report printing float printScaleY(); //! \brief Returns true if none of the included layers have data attached bool isEmpty(); //! \brief Returns true if the user is currently dragging the mouse to select an area bool selectingArea(); double currentTime() const; void setCurrentTime(double value) { m_currentTime = value; } //! \brief Add Layer l to graph object, allowing you to specify position, // margin sizes, order, movability status and offsets void AddLayer(Layer *l, LayerPosition position = LayerCenter, short pixelsX = 0, short pixelsY = 0, short order = 0, bool movable = false, short x = 0, short y = 0); //! \brief Queues text for gGraphView object to draw it. void renderText(QString text, int x, int y, float angle = 0.0, QColor color = Qt::black, QFont *font = defaultfont, bool antialias = true); //! \brief Queues text for gGraphView object to draw it, using given rect. void renderText(QString text, QRectF rect, quint32 flags = Qt::AlignCenter, float angle = 0.0, QColor color = Qt::black, QFont *font = defaultfont, bool antialias = true); //! \brief Rounds Y scale values to make them look nice.. // Applies the Graph Preference min/max settings. void roundY(EventDataType &miny, EventDataType &maxy); // //! \brief Process all Layers GLBuffer (Vertex) objects, drawing the actual OpenGL stuff. // void drawGLBuf(); //! \brief Returns the Graph's (vertical) title inline QString & title() { return m_title; } //! \brief Returns the Graph's internal name inline QString & name() { return m_name; } //! \brief Sets the Graph's (vertical) title void setTitle(const QString title) { m_title = title; } //! \brief Returns the measurement Units the Y scale is referring to QString units() { return m_units; } //! \brief Sets the measurement Units the Y scale is referring to void setUnits(const QString units) { m_units = units; } //virtual void repaint(); // Repaint individual graph.. //! \brief Resets the graphs X & Y dimensions based on the Layer data virtual void ResetBounds(); //! \brief Sets the time range selected for this graph (in milliseconds since 1970 epoch) virtual void SetXBounds(qint64 minx, qint64 maxx); //! \brief Returns the physical Minimum time for all layers contained (in milliseconds since epoch) virtual qint64 MinX(); //! \brief Returns the physical Maximum time for all layers contained (in milliseconds since epoch) virtual qint64 MaxX(); //! \brief Returns the physical Minimum Y scale value for all layers contained virtual EventDataType MinY(); //! \brief Returns the physical Maximum Y scale value for all layers contained virtual EventDataType MaxY(); //! \brief Returns the physical Minimum Y scale value for all layers contained virtual EventDataType physMinY(); //! \brief Returns the physical Maximum Y scale value for all layers contained virtual EventDataType physMaxY(); //! \brief Sets the physical start of this graphs time range (in milliseconds since epoch) virtual void SetMinX(qint64 v); //! \brief Sets the physical end of this graphs time range (in milliseconds since epoch) virtual void SetMaxX(qint64 v); //! \brief Sets the physical Minimum Y scale value used while drawing this graph virtual void SetMinY(EventDataType v); //! \brief Sets the physical Maximum Y scale value used while drawing this graph virtual void SetMaxY(EventDataType v); //! \brief Forces Y Minimum to always select this value virtual void setForceMinY(EventDataType v) { f_miny = v; m_enforceMinY = true; } //! \brief Forces Y Maximum to always select this value virtual void setForceMaxY(EventDataType v) { f_maxy = v; m_enforceMaxY = true; } virtual EventDataType forceMinY() { return rec_miny; } virtual EventDataType forceMaxY() { return rec_maxy; } //! \brief Set recommended Y minimum.. It won't go under this unless the data does. // It won't go above this. virtual void setRecMinY(EventDataType v) { rec_miny = v; } //! \brief Set recommended Y minimum.. It won't go above this unless the data does. // It won't go under this. virtual void setRecMaxY(EventDataType v) { rec_maxy = v; } //! \brief Returns the recommended Y minimum.. It won't go under this unless the data does. // It won't go above this. virtual EventDataType RecMinY() { return rec_miny; } //! \brief Returns the recommended Y maximum.. It won't go under this unless the data does. // It won't go above this. virtual EventDataType RecMaxY() { return rec_maxy; } //! \brief Called when main graph area is resized void resize(int width, int height); // margin recalcs.. qint64 max_x, min_x, rmax_x, rmin_x; EventDataType max_y, min_y, rmax_y, rmin_y, f_miny, f_maxy, rec_miny, rec_maxy; EventDataType rphysmin_y, rphysmax_y; // not sure why there's two.. I can't remember void setEnforceMinY(bool b) { m_enforceMinY = b; } void setEnforceMaxY(bool b) { m_enforceMaxY = b; } //! \brief Returns whether this graph shows overall timescale, or a zoomed area bool blockZoom() { return m_blockzoom; } //! \brief Sets whether this graph shows an overall timescale, or a zoomed area. void setBlockZoom(bool b) { m_blockzoom = b; } //! \brief Flips the GL coordinates from the graphs perspective.. Used in Scissor calculations int flipY(int y); // flip GL coordinates //! \brief Returns the graph-linking group this Graph belongs in short group() { return m_group; } //! \brief Sets the graph-linking group this Graph belongs in void setGroup(short group) { m_group = group; } //! \brief Forces the main gGraphView object to draw all Text Components void DrawTextQue(QPainter &painter); //! \brief Sends supplied day object to all Graph layers so they can precalculate stuff void setDay(Day *day); //! \brief Returns the current day object Day *day() { return m_day; } //! \brief The Layer, layout and title drawing code virtual void paint(QPainter &painter, const QRegion ®ion); //! \brief Gives the supplied data to the main ToolTip object for display void ToolTip(QString text, int x, int y, ToolTipAlignment align = TT_AlignCenter, int timeout = 0); //! \brief Public version of updateGL(), to redraw all graphs.. Not for normal use void redraw(); //! \brief Asks the main gGraphView to redraw after ms milliseconds void timedRedraw(int ms); double screenToTime(int xpos); void dataChanged(); //! \brief Sets the margins for the four sides of this graph. void setMargins(short left, short right, short top, short bottom) { m_marginleft = left; m_marginright = right; m_margintop = top; m_marginbottom = bottom; } //! \brief Returns this graphs left margin inline short marginLeft() { return m_marginleft; } //! \brief Returns this graphs right margin inline short marginRight() { return m_marginright; } //! \brief Returns this graphs top margin inline short marginTop() { return m_margintop; } //! \brief Returns this graphs bottom margin inline short marginBottom() { return m_marginbottom; } const inline QRect &rect() const { return m_rect; } bool isPinned() { return m_pinned; } void setPinned(bool b) { m_pinned = b; } bool isSnapshot() { return m_snapshot; } void setSnapshot(bool b) { m_snapshot = b; } short left, right, top, bottom; // dirty magin hacks.. Layer *getLineChart(); QTimer *timer; // This gets set to true to force a redraw of the yAxis tickers when graphs are resized. bool invalidate_yAxisImage; bool invalidate_xAxisImage; //! \brief Returns a Vector reference containing all this graphs layers QVector &layers() { return m_layers; } gGraphView *graphView() { return m_graphview; } short m_marginleft, m_marginright, m_margintop, m_marginbottom; inline short zoomY() { return m_zoomY; } void setZoomY(short zoom); static const short maxZoomY = 2; inline qint64 selectedDuration() const { return m_selectedDuration; } inline QString selDurString() const { return m_selDurString; } inline bool blockSelect() const { return m_block_select; } void setBlockSelect(bool b) { m_block_select = b; } inline bool printing() const { return m_printing; } protected: //! \brief Mouse Wheel events virtual void wheelEvent(QWheelEvent *event); //! \brief Mouse Movement events virtual void mouseMoveEvent(QMouseEvent *event); //! \brief Mouse Button Pressed events virtual void mousePressEvent(QMouseEvent *event); //! \brief Mouse Button Released events virtual void mouseReleaseEvent(QMouseEvent *event); //! \brief Mouse Button Double Clicked events virtual void mouseDoubleClickEvent(QMouseEvent *event); //! \brief Key Pressed event virtual void keyPressEvent(QKeyEvent *event); //! \brief Key Pressed event virtual void keyReleaseEvent(QKeyEvent *event); void cancelSelection() { m_selecting_area = false; m_selection = QRect(0,0,0,0); } void dumpInfo(); //! \brief Change the current selected time boundaries by mult, from origin position origin_px void ZoomX(double mult, int origin_px); //! \brief The Main gGraphView object holding this graph // (this can be pinched temporarily by print code) QString m_name; gGraphView *m_graphview; QString m_title; QString m_units; //! \brief Vector containing all this graphs Layers QVector m_layers; float m_height, m_width; int m_min_height; int m_max_height; bool m_visible; bool m_blockzoom; QRect m_selection; bool m_selecting_area; QPoint m_current; short m_group; short m_lastx23; Day *m_day; bool m_enforceMinY, m_enforceMaxY; bool m_showTitle; bool m_printing; bool m_pinned; short m_zoomY; bool m_block_select; QRect m_rect; qint64 m_selectedDuration; double m_currentTime; qint64 m_clickTime; QString m_selDurString; bool m_snapshot; protected slots: //! \brief Deselects any highlights, and schedules a main gGraphView redraw void Timeout(); }; /*! \brief Gets the width and height parameters for supplied text \param QString text - The text string in question \param int & width \param int & height \param QFont * font - The selected font used in the size calculations */ void GetTextExtent(QString text, int &width, int &height, QFont *font = defaultfont); //! \brief Return the height of the letter x for the selected font. int GetXHeight(QFont *font = defaultfont); #endif // graphs_ggraph_h OSCAR-code-v1.3.1/oscar/Graphs/gGraphView.cpp000066400000000000000000003214141417327530600206000ustar00rootroot00000000000000/* gGraphView Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "Graphs/gGraphView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG_EFFICIENCY # include #endif #include #include "mainwindow.h" #include "Graphs/glcommon.h" #include "Graphs/gLineChart.h" #include "Graphs/gSummaryChart.h" #include "Graphs/gSessionTimesChart.h" #include "Graphs/gYAxis.h" #include "Graphs/gFlagsLine.h" #include "SleepLib/profiles.h" #include "overview.h" extern MainWindow *mainwin; #include MyLabel::MyLabel(QWidget * parent) : QWidget(parent) { m_font = QApplication::font(); time.start(); } MyLabel::~MyLabel() { } void MyLabel::setText(QString text) { m_text = text; update(); } void MyLabel::setFont(QFont & font) { m_font=font; } void MyLabel::doRedraw() { update(); } void MyLabel::setAlignment(Qt::Alignment alignment) { m_alignment = alignment; doRedraw(); } void MyLabel::paintEvent(QPaintEvent * /*event*/) { QPainter painter(this); painter.setFont(m_font); painter.drawText(rect(), m_alignment, m_text); } gToolTip::gToolTip(gGraphView *graphview) : m_graphview(graphview) { m_pos.setX(0); m_pos.setY(0); m_visible = false; m_alignment = TT_AlignCenter; m_spacer = 8; // pixels around text area timer = new QTimer(graphview); connect(timer, SIGNAL(timeout()), SLOT(timerDone())); } gToolTip::~gToolTip() { disconnect(timer, 0, 0, 0); delete timer; } //void gToolTip::calcSize(QString text,int &w, int &h) //{ /*GetTextExtent(text,w,h); w+=m_spacer*2; h+=m_spacer*2; */ //} void gToolTip::display(QString text, int x, int y, ToolTipAlignment align, int timeout) { if (timeout <= 0) { timeout = AppSetting->tooltipTimeout(); } m_alignment = align; m_text = text; m_visible = true; // TODO: split multiline here //calcSize(m_text,tw,th); m_pos.setX(x); m_pos.setY(y); //tw+=m_spacer*2; //th+=m_spacer*2; //th*=2; if (timer->isActive()) { timer->stop(); } timer->setSingleShot(true); timer->start(timeout); m_invalidate = true; } void gToolTip::cancel() { m_visible = false; timer->stop(); } void gToolTip::paint(QPainter &painter) //actually paints it. { if (!m_visible) { return; } int x = m_pos.x(); int y = m_pos.y(); QRect rect(x, y, 0, 0); painter.setFont(*defaultfont); rect = painter.boundingRect(rect, Qt::AlignCenter, m_text); int w = rect.width() + m_spacer * 2; int xx = rect.x() - m_spacer; if (xx < 0) { xx = 0; } rect.setLeft(xx); rect.setTop(rect.y() - 15); rect.setWidth(w); int z = rect.x() + rect.width(); if (z > m_graphview->width() - 10) { rect.setLeft(m_graphview->width() - 2 - rect.width()); rect.setRight(m_graphview->width() - 2); } int h = rect.height(); if (rect.y() < 0) { rect.setY(0); rect.setHeight(h); } if (m_alignment == TT_AlignRight) { rect.moveTopRight(m_pos); if ((x-w) < 0) { rect.moveLeft(0); } } else if (m_alignment == TT_AlignLeft) { rect.moveTopLeft(m_pos); } int bot = rect.bottom() - m_graphview->height(); if (bot > 0) { rect.setTop(rect.top()-bot); rect.setBottom(m_graphview->height()); } QBrush brush(QColor(255, 255, 128, 230)); brush.setStyle(Qt::SolidPattern); painter.setBrush(brush); painter.setPen(QColor(0, 0, 0, 255)); painter.drawRoundedRect(rect, 5, 5); painter.setBrush(Qt::black); painter.setFont(*defaultfont); painter.drawText(rect, Qt::AlignCenter, m_text); } void gToolTip::timerDone() { m_visible = false; m_graphview->redraw(); m_graphview->resetMouse(); } #ifdef ENABLE_THREADED_DRAWING gThread::gThread(gGraphView *g) { graphview = g; mutex.lock(); } gThread::~gThread() { if (isRunning()) { m_running = false; mutex.unlock(); wait(); terminate(); } } void gThread::run() { m_running = true; gGraph *g; while (m_running) { mutex.lock(); //mutex.unlock(); if (!m_running) { break; } do { g = graphview->popGraph(); if (g) { g->paint(QRegion(g->m_lastbounds)); //int i=0; } else { //mutex.lock(); graphview->masterlock->release(1); // This thread gives up for now.. } } while (g); } } #endif // ENABLE_THREADED_DRAWING void gGraphView::queGraph(gGraph *g, int left, int top, int width, int height) { g->m_rect = QRect(left, top, width, height); #ifdef ENABLED_THREADED_DRAWING dl_mutex.lock(); #endif m_drawlist.push_back(g); #ifdef ENABLED_THREADED_DRAWING dl_mutex.unlock(); #endif } void gGraphView::trashGraphs(bool destroy) { if (destroy) { for (auto & graph : m_graphs) { delete graph; } } // Don't actually want to delete them here.. we are just borrowing the graphs m_graphs.clear(); m_graphsbyname.clear(); } // Take the next graph to render from the drawing list gGraph *gGraphView::popGraph() { gGraph *g; #ifdef ENABLED_THREADED_DRAWING dl_mutex.lock(); #endif if (!m_drawlist.isEmpty()) { g = m_drawlist.at(0); m_drawlist.pop_front(); } else { g = nullptr; } #ifdef ENABLED_THREADED_DRAWING dl_mutex.unlock(); #endif return g; } gGraphView::gGraphView(QWidget *parent, gGraphView *shared, QWidget *caller) #ifdef BROKEN_OPENGL_BUILD : QWidget(parent), #elif QT_VERSION < QT_VERSION_CHECK(5,4,0) : QGLWidget(QGLFormat(QGL::DoubleBuffer | QGL::DirectRendering | QGL::HasOverlay | QGL::Rgba),parent,shared), #else :QOpenGLWidget(parent), #endif m_offsetY(0), m_offsetX(0), m_scaleY(0.0), m_scrollbar(nullptr) { this->caller = caller; // this->grabGesture(Qt::SwipeGesture); // this->grabGesture(Qt::PanGesture); // this->grabGesture(Qt::TapGesture); // this->grabGesture(Qt::TapAndHoldGesture); // this->grabGesture(Qt::CustomGesture); this->grabGesture(Qt::PinchGesture); this->setAttribute(Qt::WA_AcceptTouchEvents); // this->setAttribute(Qt::WA_TouchPadAcceptSingleTouchEvents); m_shared = shared; m_sizer_index = m_graph_index = 0; m_metaselect = m_button_down = m_graph_dragging = m_sizer_dragging = false; m_lastypos = m_lastxpos = 0; m_horiz_travel = 0; m_minx = m_maxx = 0; m_day = nullptr; m_selected_graph = nullptr; m_scrollbar = nullptr; m_point_released = m_point_clicked = QPoint(0,0); m_showAuthorMessage = true; horizScrollTime.start(); vertScrollTime.start(); this->setMouseTracking(true); m_emptytext = STR_Empty_NoData; m_emptyimage = QPixmap(":/icons/logo-md.png"); InitGraphGlobals(); // FIXME: sstangl: handle error return. #ifdef ENABLE_THREADED_DRAWING m_idealthreads = QThread::idealThreadCount(); if (m_idealthreads <= 0) { m_idealthreads = 1; } masterlock = new QSemaphore(m_idealthreads); #endif m_tooltip = new gToolTip(this); /*for (int i=0;istart(); }*/ setFocusPolicy(Qt::StrongFocus); m_showsplitter = true; timer = new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(refreshTimeout())); print_scaleY = print_scaleX = 1.0; redrawtimer = new QTimer(this); connect(redrawtimer, SIGNAL(timeout()), SLOT(redraw())); m_fadingOut = false; m_fadingIn = false; m_inAnimation = false; m_limbo = false; m_fadedir = false; m_blockUpdates = false; use_pixmap_cache = AppSetting->usePixmapCaching(); pin_graph = nullptr; popout_graph = nullptr; // pixmapcache.setCacheLimit(10240*2); m_dpr = devicePixelRatio(); m_dpr = 1; // meh??? #ifndef BROKEN_OPENGL_BUILD setAutoFillBackground(false); #if QT_VERSION < QT_VERSION_CHECK(5,4,0) // happens no matter what in 5.4+ setAutoBufferSwap(false); #endif #endif context_menu = new QMenu(this); pin_action = context_menu->addAction(QString(), this, SLOT(togglePin())); pin_icon = QPixmap(":/icons/pushpin.png"); popout_action = context_menu->addAction(QObject::tr("Pop out Graph"), this, SLOT(popoutGraph())); snap_action = context_menu->addAction(QString(), this, SLOT(onSnapshotGraphToggle())); context_menu->addSeparator(); zoom100_action = context_menu->addAction(tr("100% zoom level"), this, SLOT(resetZoom())); if (caller) zoom100_action->setToolTip(tr("Restore X-axis zoom to 100% to view entire selected period.")); else zoom100_action->setToolTip(tr("Restore X-axis zoom to 100% to view entire day's data.")); QAction * action = context_menu->addAction(tr("Reset Graph Layout"), this, SLOT(resetLayout())); action->setToolTip(tr("Resets all graphs to a uniform height and default order.")); context_menu->addSeparator(); limits_menu = context_menu->addMenu(tr("Y-Axis")); plots_menu = context_menu->addMenu(tr("Plots")); connect(plots_menu, SIGNAL(triggered(QAction*)), this, SLOT(onPlotsClicked(QAction*))); // overlay_menu = context_menu->addMenu("Overlays"); cpap_menu = context_menu->addMenu(tr("CPAP Overlays")); connect(cpap_menu, SIGNAL(triggered(QAction*)), this, SLOT(onOverlaysClicked(QAction*))); oximeter_menu = context_menu->addMenu(tr("Oximeter Overlays")); connect(oximeter_menu, SIGNAL(triggered(QAction*)), this, SLOT(onOverlaysClicked(QAction*))); lines_menu = context_menu->addMenu(tr("Dotted Lines")); connect(lines_menu, SIGNAL(triggered(QAction*)), this, SLOT(onLinesClicked(QAction*))); #if !defined(Q_OS_MAC) context_menu->setStyleSheet("QMenu {\ background-color: #f0f0f0; /* sets background of the menu */\ border: 1px solid black;\ }\ QMenu::item {\ /* sets background of menu item. set this to something non-transparent\ if you want menu color and menu item color to be different */\ background-color: #f0f0f0;\ }\ QMenu::item:selected { /* when user selects item using mouse or keyboard */\ background-color: #ABCDEF;\ }"); #else context_menu->setStyleSheet("QMenu::item:selected { /* when user selects item using mouse or keyboard */\ background-color: #ABCDEF;\ }"); #endif } void MyDockWindow::closeEvent(QCloseEvent *event) { gGraphView::dock->deleteLater(); gGraphView::dock=nullptr; QMainWindow::closeEvent(event); } MyDockWindow * gGraphView::dock = nullptr; void gGraphView::popoutGraph() { QScreen *screen = QGuiApplication::primaryScreen(); QRect screenGeometry = screen->availableGeometry(); int screenHeight = screenGeometry.height(); if (popout_graph) { // Create new dock if we don't have one already if (dock == nullptr) { dock = new MyDockWindow(mainwin->getDaily(), Qt::Window); dock->resize(width(),0); // QScrollArea } //////// Create dock widget and resize dock to hold new widget QDockWidget * newDockWidget = new QDockWidget(dock); newDockWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); newDockWidget->setMouseTracking(true); int titleBarHeight = 30; int newDockHeight = dock->height()+popout_graph->height()+titleBarHeight/*+2*/; // +2 for group box border qDebug() << "widget geometry" << newDockWidget->frameGeometry() << "title bar height" << titleBarHeight; if (newDockHeight > screenHeight) { QMessageBox::warning(nullptr, STR_MessageBox_Warning, QObject::tr("The popout window is full. You should capture the existing\npopout window, delete it, then pop out this graph again.")); return; } qDebug() << "dock height" << dock->height() << "popout graph height" << popout_graph->height(); dock->resize(dock->width(), newDockHeight); newDockWidget->setMinimumHeight(popout_graph->height()+titleBarHeight); newDockWidget->resize(width(), popout_graph->height()+titleBarHeight); qDebug() << "dock height resized to" << dock->height() << "widget resized to" << newDockWidget->height(); //////// End resize dock to hold new widget gGraphView * gv = new gGraphView(newDockWidget, this); newDockWidget->setWidget(gv); gv->setMouseTracking(true); gv->setDay(this->day()); dock->addDockWidget(Qt::BottomDockWidgetArea, newDockWidget, Qt::Vertical); /////// Fix some resize glitches /////// /********* Is this still needed? -- gts 8/1/2020 QDockWidget* dummy = new QDockWidget; dock->addDockWidget(Qt::BottomDockWidgetArea, dummy); dock->removeDockWidget(dummy); QPoint mousePos = dock->mapFromGlobal(QCursor::pos()); mousePos.setY(dock->rect().bottom()+2); QCursor::setPos(dock->mapToGlobal(mousePos)); QMouseEvent* grabSeparatorEvent = new QMouseEvent(QMouseEvent::MouseButtonPress,mousePos,Qt::LeftButton,Qt::LeftButton,Qt::NoModifier); qApp->postEvent(dock, grabSeparatorEvent); *************/ ///////////////////////////////////////// // dock->updateGeometry(); if (!dock->isVisible()) dock->show(); gGraph * graph = popout_graph; ///////////////////// // Construct name for this popout graph QString basename = graph->title()+" - "; if (graph->m_day) { // append the date of the graph's left edge to the snapshot name // so the user knows what day the snapshot starts // because the name is displayed to the user, use local time QDateTime date = QDateTime::fromMSecsSinceEpoch(graph->min_x, Qt::LocalTime); basename += date.date().toString(Qt::SystemLocaleLongDate); } QString newname = basename; // Find a new name.. How many snapshots for each graph counts as stupid? QString newtitle = graph->title(); newDockWidget->setWindowTitle(newname); // end name construction and setting title ///////////////////// qDebug() << "original graph height is" << graph->height(); gGraph * newgraph = new gGraph(newname, nullptr, newtitle, graph->units(), graph->height(), graph->group()); newgraph->setHeight(graph->height()); short group = 0; gv->m_graphs.insert(m_graphs.indexOf(graph)+1, newgraph); gv->m_graphsbyname[newname] = newgraph; newgraph->m_graphview = gv; for (auto & l : graph->m_layers) { Layer * layer = l->Clone(); if (layer) { newgraph->m_layers.append(layer); } } for (auto & g : m_graphs) { group = qMax(g->group(), group); } newgraph->setGroup(group+1); //newgraph->setMinHeight(pm.height()); newgraph->setDay(graph->m_day); if (graph->m_day) { graph->m_day->incUseCounter(); } newgraph->min_x = graph->min_x; newgraph->max_x = graph->max_x; newgraph->setBlockSelect(false); newgraph->setZoomY(graph->zoomY()); newgraph->setSnapshot(false); newgraph->setShowTitle(true); qDebug() << "newgraph height" << newgraph->height() << "gv height" << gv->height(); gv->timedRedraw(0); // Force dock to redraw (and return focus to OSCAR) dock->activateWindow(); dock->raise(); this->activateWindow(); this->raise(); } } void gGraphView::togglePin() { if (pin_graph) { pin_graph->setPinned(!pin_graph->isPinned()); timedRedraw(0); } } void gGraphView::closeEvent(QCloseEvent * event) { timer->stop(); redrawtimer->stop(); disconnect(redrawtimer, 0, 0, 0); disconnect(timer, 0, 0, 0); timer->deleteLater(); redrawtimer->deleteLater(); pixmapcache.clear(); if (m_scrollbar) { this->disconnect(m_scrollbar, SIGNAL(sliderMoved(int)), 0, 0); } #ifdef BROKEN_OPENGL_BUILD QWidget::closeEvent(event); #elif QT_VERSION < QT_VERSION_CHECK(5,4,0) QGLWidget::closeEvent(event); #else QOpenGLWidget::closeEvent(event); #endif } gGraphView::~gGraphView() { #ifndef BROKEN_OPENGL_BUILD doneCurrent(); #endif #ifdef ENABLE_THREADED_DRAWING for (int i=0; i < m_threads.size(); i++) { delete m_threads[i]; } delete masterlock; #endif // Note: This will cause a crash if two graphs accidentally have the same name for (auto & graph : m_graphs) { delete graph; } delete m_tooltip; m_graphs.clear(); } bool gGraphView::event(QEvent * event) { if (event->type() == QEvent::Gesture) { return gestureEvent(static_cast(event)); } return QWidget::event(event); } bool gGraphView::gestureEvent(QGestureEvent * event) { if (QGesture *pinch = event->gesture(Qt::PinchGesture)) pinchTriggered(static_cast(pinch)); return true; } bool gGraphView::pinchTriggered(QPinchGesture * gesture) { gGraph * graph = nullptr; int group =0; // if (!graph) { // just pick any graph then for (const auto & g : m_graphs) { if (!g) continue; if (!g->isEmpty()) { graph = g; group = graph->group(); break; } } // } else group=graph->group(); if (!graph) { return true; } Q_UNUSED(group) // qDebug() << gesture << gesture->scaleFactor(); if (gesture->state() == Qt::GestureStarted) { pinch_min = m_minx; pinch_max = m_maxx; } int origin_px = gesture->centerPoint().x() - titleWidth; // could use this instead, and have it more dynamic // graph->ZoomX(gesture->scaleFactor(), x); static const double zoom_hard_limit = 500.0; qint64 min = pinch_min; qint64 max = pinch_max; int width = graph->m_rect.width() - graph->left - graph->right; double hardspan = graph->rmax_x - graph->rmin_x; double span = max - min; double ww = double(origin_px) / double(width); double origin = ww * span; double q = span / gesture->totalScaleFactor(); if (q > hardspan) { q = hardspan; } if (q < hardspan / zoom_hard_limit) { q = hardspan / zoom_hard_limit; } min = min + origin - (q * ww); max = min + q; if (min < graph->rmin_x) { min = graph->rmin_x; max = min + q; } if (max > graph->rmax_x) { max = graph->rmax_x; min = max - q; } //extern const int max_history; SetXBounds(min, max, graph->m_group); return true; } void gGraphView::dumpInfo() { QDate date = mainwin->getDaily()->getDate(); QString text = "==================== CPAP Information Dump ===================="; mainwin->log(text); Day * day = p_profile->GetGoodDay(date, MT_CPAP); if (day) { QDateTime dt=QDateTime::fromMSecsSinceEpoch(day->first(), Qt::LocalTime); mainwin->log(QString("Available Channels for %1").arg(dt.toString("MMM dd yyyy"))); QHash > list; for (const auto & sess : day->sessions) { for (auto it=sess->eventlist.begin(), end=sess->eventlist.end(); it != end; ++it) { ChannelID code = it.key(); schema::Channel * chan = &schema::channel[code]; list[chan->type()].append(chan); } } QHash >::iterator lit; for (auto lit = list.begin(), end=list.end(); lit != end; ++lit) { switch (lit.key()) { case schema::DATA: text = "DATA: "; break; case schema::SETTING: text = "SETTING: "; break; case schema::FLAG: text = "FLAG: "; break; case schema::MINOR_FLAG: text = "MINOR_FLAG: "; break; case schema::SPAN: text = "SPAN: "; break; case schema::WAVEFORM: text = "WAVEFORM: "; break; case schema::UNKNOWN: text = "UNKNOWN: "; break; default: break; } QStringList str; for (const auto & chan : lit.value()) { str.append(chan->code()); } str.sort(); text.append(str.join(", ")); mainwin->log(text); } } // for (int i=0;idumpInfo(); // } } // Render graphs with QPainter or pixmap caching, depending on preferences void gGraphView::DrawTextQue(QPainter &painter) { // process the text drawing queue int h,w; strings_drawn_this_frame += m_textque.size() + m_textqueRect.size();; for (const TextQue & q : m_textque) { // can do antialiased text via texture cache fine on mac // Just draw the fonts.. painter.setPen(QColor(q.color)); painter.setFont(*q.font); if (q.angle == 0) { painter.drawText(q.x, q.y, q.text); } else { w = painter.fontMetrics().width(q.text); h = painter.fontMetrics().xHeight() + 2; painter.translate(q.x, q.y); painter.rotate(-q.angle); painter.drawText(floor(-w / 2.0)-6, floor(-h / 2.0), q.text); painter.rotate(+q.angle); painter.translate(-q.x, -q.y); } } m_textque.clear(); //////////////////////////////////////////////////////////////////////// // Text Rectangle Queues.. //////////////////////////////////////////////////////////////////////// for (const TextQueRect & q : m_textqueRect) { // Just draw the fonts.. painter.setPen(QColor(q.color)); painter.setFont(*q.font); if (q.angle == 0) { painter.drawText(q.rect, q.flags, q.text); } else { w = painter.fontMetrics().width(q.text); h = painter.fontMetrics().xHeight() + 2; painter.translate(q.rect.x(), q.rect.y()); painter.rotate(-q.angle); painter.drawText(floor(-w / 2.0), floor(-h / 2.0), q.text); painter.rotate(+q.angle); painter.translate(-q.rect.x(), -q.rect.y()); } } m_textqueRect.clear(); } const QString z__cacheStr = "%1:%2:%3"; void gGraphView::DrawTextQueCached(QPainter &painter) { // process the text drawing queue int h,w; QString hstr; QPixmap pm; float xxx, yyy; const int buf = 8; int fonta = defaultfont->pointSize(); int fontb = mediumfont->pointSize(); int fontc = bigfont->pointSize(); int size; for (const TextQue & q : m_textque) { // can do antialiased text via texture cache fine on mac // Generate the pixmap cache "key" size = (q.font == defaultfont) ? fonta : (q.font==mediumfont) ? fontb : (q.font == bigfont) ? fontc : q.font->pointSize(); hstr = z__cacheStr.arg(q.text).arg(q.color.name()).arg(size); if (!QPixmapCache::find(hstr, &pm)) { QFontMetrics fm(*q.font); w = fm.width(q.text); h = fm.height()+buf; pm = QPixmap(w, h); pm.fill(Qt::transparent); QPainter imgpainter(&pm); imgpainter.setPen(q.color); imgpainter.setFont(*q.font); imgpainter.setRenderHint(QPainter::TextAntialiasing, q.antialias); imgpainter.drawText(0, h-buf, q.text); imgpainter.end(); QPixmapCache::insert(hstr, pm); } h = pm.height(); w = pm.width(); if (q.angle != 0) { xxx = q.x - h - (h / 2); yyy = q.y + w / 2; xxx += 4; yyy += 4; painter.translate(xxx, yyy); painter.rotate(-q.angle); painter.drawPixmap(QRect(0, h / 2, w, h), pm); painter.rotate(+q.angle); painter.translate(-xxx, -yyy); } else { QRect r1(q.x - buf / 2 + 4, q.y - h + buf, w, h); painter.drawPixmap(r1, pm); } } //////////////////////////////////////////////////////////////////////// // Text Rectangle Queues.. //////////////////////////////////////////////////////////////////////// for (const TextQueRect & q : m_textqueRect) { // can do antialiased text via texture cache fine on mac // Generate the pixmap cache "key" size = (q.font == defaultfont) ? fonta : (q.font==mediumfont) ? fontb : (q.font == bigfont) ? fontc : q.font->pointSize(); hstr = z__cacheStr.arg(q.text).arg(q.color.name()).arg(size); if (!QPixmapCache::find(hstr, &pm)) { w = q.rect.width(); h = q.rect.height(); pm = QPixmap(w, h); pm.fill(Qt::transparent); QPainter imgpainter(&pm); imgpainter.setPen(q.color); imgpainter.setFont(*q.font); imgpainter.setRenderHint(QPainter::TextAntialiasing, true); imgpainter.drawText(QRect(0,0, w, h), q.flags, q.text); imgpainter.end(); QPixmapCache::insert(hstr, pm); } else { h = pm.height(); w = pm.width(); } if (q.angle != 0) { xxx = q.rect.x() - h - (h / 2); yyy = q.rect.y() + w / 2; xxx += 4; yyy += 4; painter.translate(xxx, yyy); painter.rotate(-q.angle); painter.drawPixmap(QRect(0, h / 2, w, h), pm); painter.rotate(+q.angle); painter.translate(-xxx, -yyy); } else { painter.drawPixmap(q.rect,pm, QRect(0,0,w,h)); } } strings_drawn_this_frame += m_textque.size() + m_textqueRect.size();; m_textque.clear(); m_textqueRect.clear(); } void gGraphView::AddTextQue(const QString &text, QRectF rect, quint32 flags, float angle, QColor color, QFont *font, bool antialias) { #ifdef ENABLED_THREADED_DRAWING text_mutex.lock(); #endif m_textqueRect.append(TextQueRect(rect,flags,text,angle,color,font,antialias)); #ifdef ENABLED_THREADED_DRAWING text_mutex.unlock(); #endif } void gGraphView::AddTextQue(const QString &text, short x, short y, float angle, QColor color, QFont *font, bool antialias) { #ifdef ENABLED_THREADED_DRAWING text_mutex.lock(); #endif m_textque.append(TextQue(x,y,angle,text,color,font,antialias)); #ifdef ENABLED_THREADED_DRAWING text_mutex.unlock(); #endif } void gGraphView::addGraph(gGraph *g, short group) { if (!g) { qDebug() << "Attempted to add an empty graph!"; return; } if (!m_graphs.contains(g)) { g->setGroup(group); m_graphs.push_back(g); if (!m_graphsbyname.contains(g->name())) { m_graphsbyname[g->name()] = g; } else { qDebug() << "Can't have two graphs with the same code string in the same GraphView!!"; } // updateScrollBar(); } } // Calculate total height of all graphs including spacers float gGraphView::totalHeight() { float th = 0; for (const auto & g : m_graphs) { if (g->isEmpty() || (!g->visible())) { continue; } th += g->height() + graphSpacer; } return ceil(th); } float gGraphView::findTop(gGraph *graph) { float th = -m_offsetY; for (const auto & g : m_graphs) { if (g == graph) { break; } if (g->isEmpty() || (!g->visible())) { continue; } th += g->height() * m_scaleY + graphSpacer; } return ceil(th); } float gGraphView::scaleHeight() { float th = 0; for (const auto & graph : m_graphs) { if (graph->isEmpty() || (!graph->visible())) { continue; } th += graph->height() * m_scaleY + graphSpacer; } return ceil(th); } void gGraphView::updateScale() { if (!isVisible()) { m_scaleY = 0.0; return; } float th = totalHeight(); // height of all graphs float h = height(); // height of main widget if (th < h) { th -= graphSpacer; // th -= visibleGraphs() * graphSpacer; // compensate for spacer height m_scaleY = h / th; // less graphs than fits on screen, so scale to fit } else { m_scaleY = 1.0; } updateScrollBar(); } void gGraphView::resizeEvent(QResizeEvent *e) { #if (QT_VERSION >= QT_VERSION_CHECK(5,4,0)) && !defined(BROKEN_OPENGL_BUILD) // This ques a needed redraw event.. QOpenGLWidget::resizeEvent(e); #endif updateScale(); if (m_scaleY > 0.0001) { for (auto & graph : m_graphs) { graph->resize(e->size().width(), graph->height() * m_scaleY); } } e->accept(); } void gGraphView::scrollbarValueChanged(int val) { //qDebug() << "Scrollbar Changed" << val; if (m_offsetY != val) { m_offsetY = val; #if QT_VERSION >= QT_VERSION_CHECK(5,4,0) update(); #else timedRedraw(); // do this on a timer? #endif } } void gGraphView::GetRXBounds(qint64 &st, qint64 &et) { for (const auto & graph : m_graphs) { if (graph->group() == 0) { st = graph->rmin_x; et = graph->rmax_x; break; } } } void gGraphView::resetZoom() { Overview *overvw = qobject_cast(caller); if (overvw) { overvw->on_zoomButton_clicked(); return; } ResetBounds(true); } void gGraphView::ResetBounds(bool refresh) //short group) { if (m_graphs.size() == 0) return; Q_UNUSED(refresh) qint64 m1 = 0, m2 = 0; gGraph *graph = nullptr; for (auto & g : m_graphs) { g->ResetBounds(); if (!g->min_x) { continue; } graph = g; if (!m1 || g->min_x < m1) { m1 = g->min_x; } if (!m2 || g->max_x > m2) { m2 = g->max_x; } } // if (p_profile->general->linkGroups()) { // for (int i = 0; i < m_graphs.size(); i++) { // m_graphs[i]->SetMinX(m1); // m_graphs[i]->SetMaxX(m2); // } // } if (!graph) { graph = m_graphs[0]; } m_minx = graph->min_x; m_maxx = graph->max_x; updateScale(); } void gGraphView::GetXBounds(qint64 &st, qint64 &et) { st = m_minx; et = m_maxx; } // Supplies time range to all graph objects in linked group, refreshing if requested void gGraphView::SetXBounds(qint64 minx, qint64 maxx, short group, bool refresh) { for (auto & graph : m_graphs) { if ((graph->group() == group)) { graph->SetXBounds(minx, maxx); } } m_minx = minx; // left and right edges of graph, in msec in epoch m_maxx = maxx; if (refresh) { timedRedraw(0); } } void gGraphView::updateScrollBar() { if (!m_scrollbar || (m_graphs.size() == 0)) { return; } float th = scaleHeight(); // height of all graphs float h = height(); // height of main widget float vis = 0; for (const auto & graph : m_graphs) { vis += (graph->isEmpty() || !graph->visible()) ? 0 : 1; } if (th < h) { // less graphs than fits on screen m_scrollbar->setMaximum(0); // turn scrollbar off. } else { // more graphs than fit on screen //m_scaleY=1.0; float avgheight = th / vis; m_scrollbar->setPageStep(avgheight); m_scrollbar->setSingleStep(avgheight / 8.0); m_scrollbar->setMaximum(th - height()); if (m_offsetY > th - height()) { m_offsetY = th - height(); } } } void gGraphView::setScrollBar(MyScrollBar *sb) { m_scrollbar = sb; m_scrollbar->setMinimum(0); updateScrollBar(); this->connect(m_scrollbar, SIGNAL(valueChanged(int)), SLOT(scrollbarValueChanged(int))); } bool gGraphView::renderGraphs(QPainter &painter) { float px = m_offsetX; float py = -m_offsetY; int numgraphs = 0; float h, w; //ax=px;//-m_offsetX; //bool threaded; // Tempory hack using this pref.. //#ifdef ENABLED_THREADED_DRAWING /*if (profile->session->multithreading()) { // && (m_idealthreads>1)) { threaded=true; for (int i=0;iisRunning()) m_threads[i]->start(); } } else threaded=false; */ //#endif //threaded=false; if (height() < 40) return false; if (m_scaleY < 0.0000001) { updateScale(); } lines_drawn_this_frame = 0; quads_drawn_this_frame = 0; // Calculate the height of pinned graphs float pinned_height = 0; // pixel height total int pinned_graphs = 0; // count for (auto & g : m_graphs) { int minh = g->minHeight(); if (g->height() < minh) { g->setHeight(minh); } if (g->isEmpty()) { continue; } if (!g->visible()) { continue; } if (!g->isPinned()) { continue; } h = g->height() * m_scaleY; pinned_height += h + graphSpacer; pinned_graphs++; } py += pinned_height; // start drawing at the end of pinned space // Draw non pinned graphs for (auto & g : m_graphs) { if (g->isEmpty()) { continue; } if (!g->visible()) { continue; } if (g->isPinned()) { continue; } numgraphs++; h = g->height() * m_scaleY; // set clipping? if (py > height()) { break; // we are done.. can't draw anymore } if ((py + h + graphSpacer) >= 0) { w = width(); int tw = 0; // (g->showTitle() ? titleWidth : 0); queGraph(g, px + tw, py, width() - tw, h); if ((m_graphs.size() > 1) && m_showsplitter) { // draw the splitter handle painter.setPen(QColor(220, 220, 220, 255)); painter.drawLine(0, py + h, w, py + h); painter.setPen(QColor(158,158,158,255)); painter.drawLine(0, py + h + 1, w, py + h + 1); painter.setPen(QColor(240, 240, 240, 255)); painter.drawLine(0, py + h + 2, w, py + h + 2); } } py = ceil(py + h + graphSpacer); } // Physically draw the unpinned graphs for (const auto & g : m_drawlist) { g->paint(painter, QRegion(g->m_rect)); } m_drawlist.clear(); if (m_graphs.size() > 1) { AppSetting->usePixmapCaching() ? DrawTextQueCached(painter) :DrawTextQue(painter); // Draw a gradient behind pinned graphs QLinearGradient linearGrad(QPointF(100, 100), QPointF(width() / 2, 100)); linearGrad.setColorAt(0, QColor(216, 216, 255)); linearGrad.setColorAt(1, Qt::white); painter.fillRect(0, 0, width(), pinned_height, QBrush(linearGrad)); } py = 0; // start drawing at top... // Draw Pinned graphs for (const auto & g : m_graphs) { if (g->isEmpty()) { continue; } if (!g->visible()) { continue; } if (!g->isPinned()) { continue; } h = g->height() * m_scaleY; numgraphs++; if (py > height()) { break; // we are done.. can't draw anymore } if ((py + h + graphSpacer) >= 0) { w = width(); int tw = 0; //(g->showTitle() ? titleWidth : 0); queGraph(g, px + tw, py, width() - tw, h); if ((m_graphs.size() > 1) && m_showsplitter) { // draw the splitter handle painter.setPen(QColor(220, 220, 220, 255)); painter.drawLine(0, py + h, w, py + h); painter.setPen(QColor(128, 128, 128, 255)); painter.drawLine(0, py + h + 1, w, py + h + 1); painter.setPen(QColor(190, 190, 190, 255)); painter.drawLine(0, py + h + 2, w, py + h + 2); } } py = ceil(py + h + graphSpacer); } //int thr=m_idealthreads; #ifdef ENABLED_THREADED_DRAWING if (threaded) { for (int i = 0; i < m_idealthreads; i++) { masterlock->acquire(1); m_threads[i]->mutex.unlock(); } // wait till all the threads are done // ask for all the CPU's back.. masterlock->acquire(m_idealthreads); masterlock->release(m_idealthreads); } else { #endif for (const auto & g : m_drawlist) { g->paint(painter, QRegion(g->m_rect)); } m_drawlist.clear(); #ifdef ENABLED_THREADED_DRAWING } #endif //int elapsed=time.elapsed(); //QColor col=Qt::black; // lines->setSize(linesize); AppSetting->usePixmapCaching() ? DrawTextQueCached(painter) :DrawTextQue(painter); //glDisable(GL_TEXTURE_2D); //glDisable(GL_DEPTH_TEST); return numgraphs > 0; } #ifdef BROKEN_OPENGL_BUILD void gGraphView::paintEvent(QPaintEvent *) #else void gGraphView::paintGL() #endif { if (!isVisible()) { // wtf is this even getting CALLED?? return; } #ifdef DEBUG_EFFICIENCY QElapsedTimer time; time.start(); #endif if (redrawtimer->isActive()) { redrawtimer->stop(); } bool render_cube = false; //p_profile->appearance->animations(); // do something to if (width() <= 0) { return; } if (height() <= 0) { return; } // Create QPainter object, note this is only valid from paintGL events! QPainter painter(this); painter.setRenderHint(QPainter::HighQualityAntialiasing, true); painter.setRenderHint(QPainter::TextAntialiasing, true); QRect bgrect(0, 0, width(), height()); painter.fillRect(bgrect,QBrush(QColor(255,255,255))); bool graphs_drawn = true; lines_drawn_this_frame = 0; quads_drawn_this_frame = 0; strings_drawn_this_frame = 0; strings_cached_this_frame = 0; // qDebug() << "About to call renderGraphs from paintGL()"; // sleep(3); graphs_drawn = renderGraphs(painter); // // Show message to user if no graphs available // if (!graphs_drawn) { qDebug() << "gGraphView: No graphs drawn"; QString txt; if (m_showAuthorMessage) { if (emptyText() == STR_Empty_Brick) { txt = QObject::tr("Your machine doesn't record data to graph in Daily View"); } else { // not proud of telling them their machine is a Brick.. ;) txt = QObject::tr("There is no data to graph"); } } qDebug() << "gGraphView:" + txt; /*** This doesn't seem to do anything? if (! this->m_emptyimage.isNull()) { int x = width()/2 - this->m_emptyimage.width()/2; int y = height()/2 - this->m_emptyimage.height()/2; QRectF target(QRect(x, y, this->m_emptyimage.width(), this->m_emptyimage.height())); QRectF source(QRect(0, 0, this->m_emptyimage.width(), this->m_emptyimage.height())); } ***/ QColor col = Qt::black; painter.setPen(col); QRectF rec(0,0,width(),0); // To put text in center of screen: // QRectF rec(0,0,width(),height(0)); painter.setFont(*mediumfont); // rec = painter.boundingRect(rec, Qt::AlignCenter, txt); rec = painter.boundingRect(rec, Qt::AlignHCenter | Qt::AlignBottom, txt); rec.moveBottom(height()-15); painter.drawText(rec, Qt::AlignCenter, txt); } if (AppSetting->lineCursorMode()) { emit updateCurrentTime(graphs_drawn ? m_currenttime : 0.0F); } else { emit updateRange(graphs_drawn ? m_minx : 0.0F, m_maxx); } AppSetting->usePixmapCaching() ? DrawTextQueCached(painter) :DrawTextQue(painter); m_tooltip->paint(painter); #ifdef DEBUG_EFFICIENCY const int rs = 20; static double ring[rs] = {0}; static int rp = 0; // Show FPS and draw time if (m_showsplitter && AppSetting->showPerformance()) { QString ss; qint64 ela = time.nsecsElapsed(); double ms = double(ela) / 1000000.0; ring[rp++] = 1000.0 / ms; rp %= rs; double v = 0; for (int i = 0; i < rs; i++) { v += ring[i]; } double fps = v / double(rs); ss = "Debug Mode " + QString::number(fps, 'f', 1) + "fps " + QString::number(lines_drawn_this_frame, 'f', 0) + " lines " // + QString::number(quads_drawn_this_frame, 'f', 0) + " quads " + QString::number(strings_drawn_this_frame, 'f', 0) + " strings " + QString::number(strings_cached_this_frame, 'f', 0) + " cached "; int w, h; // this uses tightBoundingRect, which is different on Mac than it is on Windows & Linux. GetTextExtent(ss, w, h); QColor col = Qt::white; if (m_graphs.size() > 0) { painter.fillRect(width() - m_graphs[0]->marginRight(), 0, m_graphs[0]->marginRight(), w, QBrush(col)); } #ifndef Q_OS_MAC // if (usePixmapCache()) xx+=4; else xx-=3; #endif AddTextQue(ss, width(), w / 2, 90, QColor(Qt::black), defaultfont); AppSetting->usePixmapCaching() ? DrawTextQueCached(painter) :DrawTextQue(painter); } // painter.setPen(Qt::lightGray); // painter.drawLine(0, 0, 0, height()); // painter.drawLine(0, 0, width(), 0); // painter.setPen(Qt::darkGray); //painter.drawLine(width(), 0, width(), height()); #endif painter.end(); // qDebug() << "Break 4"; // sleep(3); #ifndef BROKEN_OPENGL_BUILD #if QT_VERSION < QT_VERSION_CHECK(5,4,0) swapBuffers(); #endif #endif if (this->isVisible() && !graphs_drawn && render_cube) { // keep the cube spinning redrawtimer->setInterval(1000.0 / 50); // 50 FPS redrawtimer->setSingleShot(true); redrawtimer->start(); } // qDebug() << "Break 5"; // sleep(3); } QString gGraphView::getRangeInDaysString() { QDateTime st = QDateTime::fromMSecsSinceEpoch(m_minx); QDateTime et = QDateTime::fromMSecsSinceEpoch(m_maxx); QDate std = st.date(); QDate etd = et.date(); if (std.year() == etd.year()) return st.toString(" d MMM") + " - " + et.toString("d MMM yyyy"); else return st.toString(" d MMM yyyy") + " - " + et.toString("d MMM yyyy"); } QString gGraphView::getRangeString() { QDateTime st = QDateTime::fromMSecsSinceEpoch(m_minx); QDateTime et = QDateTime::fromMSecsSinceEpoch(m_maxx); QDate std = st.date(); QDate etd = et.date(); // Format if Begin and End are on different days if (std != etd) { // further adjust formatting if on different years if (std.year() == etd.year()) return st.toString(" d MMM [ HH:mm:ss") + " - " + et.toString("HH:mm:ss ] d MMM yyyy"); else return st.toString(" d MMM yyyy [ HH:mm:ss") + " - " + et.toString("HH:mm:ss ] d MMM yyyy"); } // Range is within one (local) day qint64 diff = m_maxx - m_minx; QString fmt; if (diff > 60000) { fmt = "HH:mm:ss"; } else { fmt = "HH:mm:ss:zzz"; } QString txt = st.toString(QObject::tr("d MMM yyyy [ %1 - %2 ]").arg(fmt).arg(et.toString(fmt))) ; return txt; /***** WTF is this code trying to do? Replaced by above 8/9/2019 // a note about time zone usage here // even though this string will be displayed to the user // the graph is drawn using UTC times, so no conversion // is needed to format the date and time for the user // i.e. if the graph says the cursor is at 5pm, then that // is what we should display. // passing in UTC below is necessary to prevent QT // from automatically converting the time to local time QString fmt; qint64 diff = m_maxx - m_minx; if (diff > 86400000) { // 86400000 is one day, in milliseconds int days = ceil(double(m_maxx-m_minx) / 86400000.0); qint64 minx = floor(double(m_minx)/86400000.0); minx *= 86400000L; qint64 maxx = minx + 86400000L * qint64(days)-1; QDateTime st = QDateTime::fromMSecsSinceEpoch(minx, Qt::UTC); QDateTime et = QDateTime::fromMSecsSinceEpoch(maxx, Qt::UTC); QString txt = st.toString("d MMM") + " - " + et.addDays(-1).toString("d MMM yyyy"); return txt; } else if (diff > 60000) { fmt = "HH:mm:ss"; } else { fmt = "HH:mm:ss:zzz"; } QString txt = st.toString(QObject::tr("d MMM [ %1 - %2 ]").arg(fmt).arg(et.toString(fmt))) ; return txt; */ } void gGraphView::leaveEvent(QEvent * event) { Q_UNUSED(event); if (m_metaselect) { m_metaselect = false; timedRedraw(0); } releaseKeyboard(); } // For manual scrolling void gGraphView::setOffsetY(int offsetY) { if (m_offsetY != offsetY) { m_offsetY = offsetY; redraw(); //issue full redraw.. } } // For manual X scrolling (not really needed) void gGraphView::setOffsetX(int offsetX) { if (m_offsetX != offsetX) { m_offsetX = offsetX; redraw(); //issue redraw } } void gGraphView::mouseMoveEvent(QMouseEvent *event) { grabKeyboard(); int x = event->x(); int y = event->y(); m_mouse = QPoint(x, y); if (m_sizer_dragging) { // Resize handle being dragged float my = y - m_sizer_point.y(); //qDebug() << "Sizer moved vertically" << m_sizer_index << my*m_scaleY; float h = m_graphs[m_sizer_index]->height(); h += my / m_scaleY; if (h > m_graphs[m_sizer_index]->minHeight()) { m_graphs[m_sizer_index]->setHeight(h); m_sizer_point.setX(x); m_sizer_point.setY(y); updateScrollBar(); timedRedraw(); } return; } if (m_graph_dragging) { // Title bar being dragged to reorder gGraph *p; int yy = m_sizer_point.y(); bool empty; if (y < yy) { for (int i = m_graph_index - 1; i >= 0; i--) { if (m_graphs[i]->isPinned() != m_graphs[m_graph_index]->isPinned()) { // fix cursor continue; } empty = m_graphs[i]->isEmpty() || (!m_graphs[i]->visible()); // swapping upwards. int yy2 = yy - graphSpacer - m_graphs[i]->height() * m_scaleY; yy2 += m_graphs[m_graph_index]->height() * m_scaleY; if (y < yy2) { //qDebug() << "Graph Reorder" << m_graph_index; p = m_graphs[m_graph_index]; m_graphs[m_graph_index] = m_graphs[i]; m_graphs[i] = p; if (!empty) { m_sizer_point.setY(yy - graphSpacer - m_graphs[m_graph_index]->height()*m_scaleY); redraw(); } m_graph_index--; } if (!empty) { break; } } } else if (y > yy + graphSpacer + m_graphs[m_graph_index]->height()*m_scaleY) { // swapping downwards //qDebug() << "Graph Reorder" << m_graph_index; for (int i = m_graph_index + 1; i < m_graphs.size(); i++) { if (m_graphs[i]->isPinned() != m_graphs[m_graph_index]->isPinned()) { //m_graph_dragging=false; // fix cursor continue; } empty = m_graphs[i]->isEmpty() || (!m_graphs[i]->visible()); p = m_graphs[m_graph_index]; m_graphs[m_graph_index] = m_graphs[i]; m_graphs[i] = p; if (!empty) { m_sizer_point.setY(yy + graphSpacer + m_graphs[m_graph_index]->height()*m_scaleY); timedRedraw(); } m_graph_index++; if (!empty) { break; } } } return; } float py = 0, pinned_height = 0, h; bool done = false; // Do pinned graphs first for (const auto & graph : m_graphs) { if (graph->isEmpty() || !graph->visible() || !graph->isPinned()) { continue; } h = graph->height() * m_scaleY; pinned_height += h + graphSpacer; if (py > height()) { break; // we are done.. can't draw anymore } if (!((y >= py + graph->top) && (y < py + h - graph->bottom))) { if (graph->isSelected()) { graph->deselect(); timedRedraw(150); } } // Update Mouse Cursor shape if ((y >= py + h - 1) && (y < (py + h + graphSpacer))) { this->setCursor(Qt::SplitVCursor); done = true; } else if ((y >= py + 1) && (y < py + h)) { if (x >= titleWidth + 10) { this->setCursor(Qt::ArrowCursor); } else { m_tooltip->display(tr("Double click title to pin / unpin\nClick and drag to reorder graphs"), x + 10, y, TT_AlignLeft); timedRedraw(0); this->setCursor(Qt::OpenHandCursor); } m_horiz_travel += qAbs(x - m_lastxpos) + qAbs(y - m_lastypos); m_lastxpos = x; m_lastypos = y; // QPoint p(x,y); // QMouseEvent e(event->type(),p,event->button(),event->buttons(),event->modifiers()); graph->mouseMoveEvent(event); done = true; } py += h + graphSpacer; } py = -m_offsetY; py += pinned_height; // Propagate mouseMove events to relevant graphs if (!done) for (const auto & graph : m_graphs) { if (graph->isEmpty() || !graph->visible() || graph->isPinned()) { continue; } h = graph->height() * m_scaleY; if (py > height()) { break; // we are done.. can't draw anymore } if (!((y >= py + graph->top) && (y < py + h - graph->bottom))) { if (graph->isSelected()) { graph->deselect(); timedRedraw(150); } } // Update Mouse Cursor shape if ((y >= py + h - 1) && (y < (py + h + graphSpacer))) { this->setCursor(Qt::SplitVCursor); } else if ((y >= py + 1) && (y < py + h)) { if (x >= titleWidth + 10) { this->setCursor(Qt::ArrowCursor); } else { m_tooltip->display(tr("Double click title to pin / unpin\nClick and drag to reorder graphs"), x + 10, y, TT_AlignLeft); timedRedraw(0); this->setCursor(Qt::OpenHandCursor); } m_horiz_travel += qAbs(x - m_lastxpos) + qAbs(y - m_lastypos); m_lastxpos = x; m_lastypos = y; if (graph) { graph->mouseMoveEvent(event); } } /* else if (!m_button_down && (y >= py) && (y < py+m_graphs[i]->top)) { // Mouse cursor is in top graph margin. } else if (!m_button_down && (y >= py+h-m_graphs[i]->bottom) && (y <= py+h)) { // Mouse cursor is in bottom grpah margin. } else if (m_button_down || ((y >= py+m_graphs[i]->top) && (y < py + h-m_graphs[i]->bottom))) { if (m_button_down || (x >= titleWidth+10)) { //(gYAxis::Margin-5) this->setCursor(Qt::ArrowCursor); m_horiz_travel+=qAbs(x-m_lastxpos)+qAbs(y-m_lastypos); m_lastxpos=x; m_lastypos=y; QPoint p(x-titleWidth,y-py); QMouseEvent e(event->type(),p,event->button(),event->buttons(),event->modifiers()); m_graphs[i]->mouseMoveEvent(&e); if (!m_button_down && (x<=titleWidth+(gYAxis::Margin-5))) { //qDebug() << "Hovering over" << m_graphs[i]->title(); if (m_graphsbytitle[STR_TR_EventFlags]==m_graphs[i]) { QVector & layers=m_graphs[i]->layers(); gFlagsGroup *fg; for (int i=0;i(layers[i]))!=nullptr) { float bh=fg->barHeight(); int count=fg->count(); float yp=py+m_graphs[i]->marginTop(); yp=y-yp; float th=(float(count)*bh); if (yp>=0 && ypvisibleLayers()[i]->code(); QString ttip=schema::channel[code].description(); m_tooltip->display(ttip,x,y-20,AppSetting->tooltipTimeout()); redraw(); //qDebug() << code << ttip; } } break; } } } else { if (!m_graphs[i]->units().isEmpty()) { m_tooltip->display(m_graphs[i]->units(),x,y-20,AppSetting->tooltipTimeout()); redraw(); } } } } else { this->setCursor(Qt::OpenHandCursor); } } */ // } py += h + graphSpacer; } } Layer * gGraphView::findLayer(gGraph * graph, LayerType type) { for (auto & layer : graph->m_layers) { if (layer->layerType() == type) { return layer; } } return nullptr; } class MyWidgetAction : public QWidgetAction { public: MyWidgetAction(ChannelID code, QObject * parent = nullptr) :QWidgetAction(parent), code(code) { chbox = nullptr; } protected: virtual QWidget * createWidget(QWidget * /*parent*/) { connect(chbox, SIGNAL(toggled(bool)), this, SLOT(setChecked(bool))); connect(chbox, SIGNAL(clicked()), this, SLOT(trigger())); return chbox; } QCheckBox * chbox; ChannelID code; }; MinMaxWidget::MinMaxWidget(gGraph * graph, QWidget *parent) :QWidget(parent), graph(graph) { step = 1; createLayout(); } void MinMaxWidget::onMinChanged(double d) { graph->rec_miny = d; graph->timedRedraw(0); } void MinMaxWidget::onMaxChanged(double d) { graph->rec_maxy = d; graph->timedRedraw(0); } void MinMaxWidget::onResetClicked() { int tmp = graph->zoomY(); graph->setZoomY(0); EventDataType miny = graph->MinY(), maxy = graph->MaxY(); graph->roundY(miny, maxy); setMin(graph->rec_miny = miny); setMax(graph->rec_maxy = maxy); float r = maxy-miny; if (r > 400) { step = 50; } else if (r > 100) { step = 10; } else if (r > 50) { step = 5; } else { step = 1; } graph->setZoomY(tmp); } void MinMaxWidget::onComboChanged(int idx) { minbox->setEnabled(idx == 2); maxbox->setEnabled(idx == 2); reset->setEnabled(idx == 2); graph->setZoomY(idx); if (idx == 2) { if (qAbs(graph->rec_maxy - graph->rec_miny) < 0.0001) { onResetClicked(); } } } void MinMaxWidget::createLayout() { QGridLayout * layout = new QGridLayout; layout->setMargin(4); layout->setSpacing(4); combobox = new QComboBox(this); combobox->addItem(tr("Auto-Fit"), 0); combobox->addItem(tr("Defaults"), 1); combobox->addItem(tr("Override"), 2); combobox->setToolTip(tr("The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own.")); connect(combobox, SIGNAL(activated(int)), this, SLOT(onComboChanged(int))); minbox = new QDoubleSpinBox(this); maxbox = new QDoubleSpinBox(this); minbox->setToolTip(tr("The Minimum Y-Axis value.. Note this can be a negative number if you wish.")); maxbox->setToolTip(tr("The Maximum Y-Axis value.. Must be greater than Minimum to work.")); int idx = graph->zoomY(); combobox->setCurrentIndex(idx); minbox->setEnabled(idx == 2); maxbox->setEnabled(idx == 2); minbox->setAlignment(Qt::AlignRight); maxbox->setAlignment(Qt::AlignRight); minbox->setMinimum(-9999.0); maxbox->setMinimum(-9999.0); minbox->setMaximum(9999.99); maxbox->setMaximum(9999.99); setMin(graph->rec_miny); setMax(graph->rec_maxy); float r = graph->rec_maxy - graph->rec_miny; if (r > 400) { step = 50; } else if (r > 100) { step = 10; } else if (r > 50) { step = 5; } else { step = 1; } minbox->setSingleStep(step); maxbox->setSingleStep(step); connect(minbox, SIGNAL(valueChanged(double)), this, SLOT(onMinChanged(double))); connect(maxbox, SIGNAL(valueChanged(double)), this, SLOT(onMaxChanged(double))); QLabel * label = new QLabel(tr("Scaling Mode")); QFont font = label->font(); font.setBold(true); label->setFont(font); label->setAlignment(Qt::AlignCenter); layout->addWidget(label,0,0); layout->addWidget(combobox,1,0); label = new QLabel(STR_TR_Min); label->setFont(font); label->setAlignment(Qt::AlignCenter); layout->addWidget(label,0,1); layout->addWidget(minbox,1,1); label = new QLabel(STR_TR_Max); label->setFont(font); label->setAlignment(Qt::AlignCenter); layout->addWidget(label,0,2); layout->addWidget(maxbox,1,2); reset = new QToolButton(this); reset->setIcon(QIcon(":/icons/refresh.png")); reset->setToolTip(tr("This button resets the Min and Max to match the Auto-Fit")); reset->setEnabled(idx == 2); layout->addWidget(reset,1,3); connect(reset, SIGNAL(clicked()), this, SLOT(onResetClicked())); this->setLayout(layout); } void gGraphView::populateMenu(gGraph * graph) { QAction * action; if (graph->isSnapshot()) { snap_action->setText(tr("Remove Clone")); snap_action->setData(graph->name()+"|remove"); // zoom100_action->setVisible(false); } else { snap_action->setText(tr("Clone %1 Graph").arg(graph->title())); snap_action->setData(graph->name()+"|snapshot"); // zoom100_action->setVisible(true); } // Menu title fonts QFont font = QApplication::font(); font.setBold(true); font.setPointSize(font.pointSize() + 3); gLineChart * lc = dynamic_cast(findLayer(graph,LT_LineChart)); SummaryChart * sc = dynamic_cast(findLayer(graph,LT_SummaryChart)); gSummaryChart * stg = dynamic_cast(findLayer(graph,LT_Overview)); limits_menu->clear(); if (lc || sc || stg) { QWidgetAction * widget = new QWidgetAction(this); MinMaxWidget * minmax = new MinMaxWidget(graph, this); widget->setDefaultWidget(minmax); limits_menu->addAction(widget); limits_menu->menuAction()->setVisible(true); } else { limits_menu->menuAction()->setVisible(false); } // First check for any linechart for this graph.. if (lc) { lines_menu->clear(); for (int i=0, end=lc->m_dotlines.size(); i < end; i++) { const DottedLine & dot = lc->m_dotlines[i]; if (!lc->m_enabled[dot.code]) continue; schema::Channel &chan = schema::channel[dot.code]; if (dot.available) { QWidgetAction * widget = new QWidgetAction(context_menu); QCheckBox *chbox = new QCheckBox(chan.calc[dot.type].label(), context_menu); chbox->setMouseTracking(true); chbox->setStyleSheet(QString("QCheckBox:hover { background: %1; }").arg(QApplication::palette().highlight().color().name())); widget->setDefaultWidget(chbox); widget->setCheckable(true); widget->setData(QString("%1|%2").arg(graph->name()).arg(i)); connect(chbox, SIGNAL(toggled(bool)), widget, SLOT(setChecked(bool))); connect(chbox, SIGNAL(clicked()), widget, SLOT(trigger())); bool b = lc->m_dot_enabled[dot.code][dot.type]; //chan.calc[dot.type].enabled; chbox->setChecked(b); lines_menu->addAction(widget); } } lines_menu->menuAction()->setVisible(lines_menu->actions().size() > 0); if (lines_menu->actions().size() > 0) { lines_menu->insertSeparator(lines_menu->actions()[0]); action = new QAction(QString("%1").arg(graph->title()), lines_menu); lines_menu->insertAction(lines_menu->actions()[0], action); action->setFont(font); action->setData(QString("")); action->setEnabled(false); } ////////////////////////////////////////////////////////////////////////////////////// // Populate Plots Menus ////////////////////////////////////////////////////////////////////////////////////// plots_menu->clear(); for (const auto code : lc->m_codes) { if (lc->m_day && !lc->m_day->channelHasData(code)) continue; QWidgetAction * widget = new QWidgetAction(context_menu); QCheckBox *chbox = new QCheckBox(schema::channel[code].label(), context_menu); chbox->setMouseTracking(true); chbox->setToolTip(schema::channel[code].description()); chbox->setStyleSheet(QString("QCheckBox:hover { background: %1; }").arg(QApplication::palette().highlight().color().name())); widget->setDefaultWidget(chbox); widget->setCheckable(true); widget->setData(QString("%1|%2").arg(graph->name()).arg(code)); connect(chbox, SIGNAL(toggled(bool)), widget, SLOT(setChecked(bool))); connect(chbox, SIGNAL(clicked()), widget, SLOT(trigger())); bool b = lc->m_enabled[code]; chbox->setChecked(b); plots_menu->addAction(widget); } plots_menu->menuAction()->setVisible((plots_menu->actions().size() > 1)); if (plots_menu->actions().size() > 0) { plots_menu->insertSeparator(plots_menu->actions()[0]); action = new QAction(QString("%1").arg(graph->title()), plots_menu); plots_menu->insertAction(plots_menu->actions()[0], action); action->setFont(font); action->setData(QString("")); action->setEnabled(false); } ////////////////////////////////////////////////////////////////////////////////////// // Populate Event Menus ////////////////////////////////////////////////////////////////////////////////////// oximeter_menu->clear(); cpap_menu->clear(); using namespace schema; quint32 showflags = schema::FLAG | schema::MINOR_FLAG | schema::SPAN; if (p_profile->general->showUnknownFlags()) showflags |= schema::UNKNOWN; QList chans = lc->m_day->getSortedMachineChannels(showflags); QHash Vis; for (const auto code : chans) { schema::Channel & chan = schema::channel[code]; QWidgetAction * widget = new QWidgetAction(context_menu); QCheckBox *chbox = new QCheckBox(schema::channel[code].fullname(), context_menu); chbox->setPalette(context_menu->palette()); chbox->setMouseTracking(true); chbox->setToolTip(schema::channel[code].description()); chbox->setStyleSheet(QString("QCheckBox:hover { background: %1; }").arg(QApplication::palette().highlight().color().name())); widget->setDefaultWidget(chbox); widget->setCheckable(true); widget->setData(QString("%1|%2").arg(graph->name()).arg(code)); connect(chbox, SIGNAL(toggled(bool)), widget, SLOT(setChecked(bool))); connect(chbox, SIGNAL(clicked()), widget, SLOT(trigger())); bool b = lc->m_flags_enabled[code]; chbox->setChecked(b); Vis[chan.machtype()] += b ? 1 : 0; action = nullptr; if (chan.machtype() == MT_OXIMETER) { oximeter_menu->insertAction(nullptr, widget); } else if ( chan.machtype() == MT_CPAP) { cpap_menu->insertAction(nullptr,widget); } } QString HideAllEvents = QObject::tr("Hide All Events"); QString ShowAllEvents = QObject::tr("Show All Events"); oximeter_menu->menuAction()->setVisible(oximeter_menu->actions().size() > 0); cpap_menu->menuAction()->setVisible(cpap_menu->actions().size() > 0); if (cpap_menu->actions().size() > 0) { cpap_menu->addSeparator(); if (Vis[MT_CPAP] > 0) { action = cpap_menu->addAction(HideAllEvents); action->setData(QString("%1|HideAll:CPAP").arg(graph->name())); } else { action = cpap_menu->addAction(ShowAllEvents); action->setData(QString("%1|ShowAll:CPAP").arg(graph->name())); } // Show CPAP Events menu Header... cpap_menu->insertSeparator(cpap_menu->actions()[0]); action = new QAction(QString("%1").arg(graph->title()), cpap_menu); cpap_menu->insertAction(cpap_menu->actions()[0], action); action->setFont(font); action->setData(QString("")); action->setEnabled(false); } if (oximeter_menu->actions().size() > 0) { oximeter_menu->addSeparator(); if (Vis[MT_OXIMETER] > 0) { action = oximeter_menu->addAction(HideAllEvents); action->setData(QString("%1|HideAll:OXI").arg(graph->name())); } else { action = oximeter_menu->addAction(ShowAllEvents); action->setData(QString("%1|ShowAll:OXI").arg(graph->name())); } oximeter_menu->insertSeparator(oximeter_menu->actions()[0]); action = new QAction(QString("%1").arg(graph->title()), oximeter_menu); oximeter_menu->insertAction(oximeter_menu->actions()[0], action); action->setFont(font); action->setData(QString("")); action->setEnabled(false); } } else { lines_menu->clear(); lines_menu->menuAction()->setVisible(false); plots_menu->clear(); plots_menu->menuAction()->setVisible(false); oximeter_menu->clear(); oximeter_menu->menuAction()->setVisible(false); cpap_menu->clear(); cpap_menu->menuAction()->setVisible(false); } } void gGraphView::onSnapshotGraphToggle() { QString name = snap_action->data().toString().section("|",0,0); QString cmd = snap_action->data().toString().section("|",-1).toLower(); auto it = m_graphsbyname.find(name); if (it == m_graphsbyname.end()) return; gGraph * graph = it.value(); if (cmd == "snapshot") { QString basename = name+";"; if (graph->m_day) { // append the date of the graph's left edge to the snapshot name // so the user knows what day the snapshot starts // because the name is displayed to the user, use local time QDateTime date = QDateTime::fromMSecsSinceEpoch(graph->min_x, Qt::LocalTime); basename += date.date().toString(Qt::SystemLocaleLongDate); } QString newname; // Find a new name.. How many snapshots for each graph counts as stupid? for (int i=1;i < 100;i++) { newname = basename+" ("+QString::number(i)+")"; it = m_graphsbyname.find(newname); if (it == m_graphsbyname.end()) { break; } } QString newtitle; bool fnd = false; // someday, some clown will keep adding new graphs to break this.. for (int i=1; i < 100; i++) { newtitle = graph->title()+"-"+QString::number(i); fnd = false; for (const auto & graph : m_graphs) { if (graph->title() == newtitle) { fnd = true; break; } } if (!fnd) break; } if (fnd) { // holy crap.. what patience. but not what I meant by as many as you like ;) return; } gGraph * newgraph = new gGraph(newname, nullptr, newtitle, graph->units(), graph->height(), graph->group()); // newgraph->setBlockSelect(true); newgraph->setHeight(graph->height()); short group = 0; m_graphs.insert(m_graphs.indexOf(graph)+1, newgraph); m_graphsbyname[newname] = newgraph; newgraph->m_graphview = this; for (const auto & l : graph->m_layers) { Layer * layer = l->Clone(); if (layer) { newgraph->m_layers.append(layer); } } for (const auto & g : m_graphs) { group = qMax(g->group(), group); } newgraph->setGroup(group+1); //newgraph->setMinHeight(pm.height()); newgraph->setDay(graph->m_day); if (graph->m_day) { graph->m_day->incUseCounter(); } newgraph->min_x = graph->min_x; newgraph->max_x = graph->max_x; if (graph->blockZoom()) { newgraph->setBlockZoom(graph->blockZoom()); newgraph->setBlockSelect(true); } if (graph->blockSelect()) { newgraph->setBlockSelect(true); } newgraph->setZoomY(graph->zoomY()); newgraph->setSnapshot(true); emit GraphsChanged(); // addGraph(newgraph); updateScale(); timedRedraw(0); } else if (cmd == "remove") { if (graph->m_day) { graph->m_day->decUseCounter(); if (graph->m_day->useCounter() == 0) { } } m_graphsbyname.remove(graph->name()); m_graphs.removeAll(it.value()); delete graph; updateScale(); timedRedraw(0); emit GraphsChanged(); } qDebug() << cmd << name; } bool gGraphView::hasSnapshots() { bool snap = false; for (const auto & graph : m_graphs) { if (graph->isSnapshot()) { snap = true; break; } } return snap; } void gGraphView::onPlotsClicked(QAction *action) { QString name = action->data().toString().section("|",0,0); ChannelID code = action->data().toString().section("|",-1).toInt(); auto it = m_graphsbyname.find(name); if (it == m_graphsbyname.end()) return; gGraph * graph = it.value(); gLineChart * lc = dynamic_cast(findLayer(graph, LT_LineChart)); if (!lc) return; lc->m_enabled[code] = !lc->m_enabled[code]; graph->min_y = graph->MinY(); graph->max_y = graph->MaxY(); graph->timedRedraw(0); // lc->Miny(); // lc->Maxy(); } void gGraphView::onOverlaysClicked(QAction *action) { QString name = action->data().toString().section("|",0,0); QString data = action->data().toString().section("|",-1); auto it = m_graphsbyname.find(name); if (it == m_graphsbyname.end()) return; gGraph * graph = it.value(); gLineChart * lc = dynamic_cast(findLayer(graph, LT_LineChart)); if (!lc) return; bool ok; ChannelID code = data.toInt(&ok); if (ok) { // Just toggling a flag on/off bool b = ! lc->m_flags_enabled[code]; lc->m_flags_enabled[code] = b; QWidgetAction * widget = qobject_cast(action); if (widget) { widget->setChecked(b); } timedRedraw(0); return; } QString hideall = data.section(":",0,0); if ((hideall == "HideAll") || (hideall == "ShowAll")) { bool value = (hideall == "HideAll") ? false : true; QString group = data.section(":",-1).toUpper(); MachineType mtype; if (group == "CPAP") mtype = MT_CPAP; else if (group == "OXI") mtype = MT_OXIMETER; else mtype = MT_UNKNOWN; // First toggle the actual flag bits for (auto it=lc->m_flags_enabled.begin(), end=lc->m_flags_enabled.end(); it != end; ++it) { if (schema::channel[it.key()].machtype() == mtype) { lc->m_flags_enabled[it.key()] = value; } } // Now toggle the menu actions.. bleh if (mtype == MT_CPAP) { for (auto & action : cpap_menu->actions()) { if (action->isCheckable()) { action->setChecked(value); } } } else if (mtype == MT_OXIMETER) { for (auto & action : oximeter_menu->actions()) { if (action->isCheckable()) { action->setChecked(value); } } } } } void gGraphView::onLinesClicked(QAction *action) { QString name = action->data().toString().section("|",0,0); QString data = action->data().toString().section("|",-1); auto it = m_graphsbyname.find(name); if (it == m_graphsbyname.end()) return; gGraph * graph = it.value(); gLineChart * lc = dynamic_cast(findLayer(graph, LT_LineChart)); if (!lc) return; bool ok; int i = data.toInt(&ok); if (ok) { DottedLine & dot = lc->m_dotlines[i]; schema::Channel &chan = schema::channel[dot.code]; chan.calc[dot.type].enabled = !chan.calc[dot.type].enabled; lc->m_dot_enabled[dot.code][dot.type] = !lc->m_dot_enabled[dot.code][dot.type]; } timedRedraw(0); } void gGraphView::mousePressEvent(QMouseEvent *event) { int x = event->x(); int y = event->y(); float h, pinned_height = 0, py = 0; bool done = false; // first handle pinned graphs. // Calculate total height of all pinned graphs for (int i=0, end=m_graphs.size(); iisEmpty() || !g->visible() || !g->isPinned()) { continue; } h = g->height() * m_scaleY; pinned_height += h + graphSpacer; if (py > height()) { break; } if ((py + h + graphSpacer) >= 0) { if ((y >= py + h - 1) && (y <= py + h + graphSpacer)) { this->setCursor(Qt::SplitVCursor); m_sizer_dragging = true; m_sizer_index = i; m_sizer_point.setX(x); m_sizer_point.setY(y); done = true; } else if ((y >= py) && (y < py + h)) { //qDebug() << "Clicked" << i; if ((event->button() == Qt::LeftButton) && (x < titleWidth + 20)) { // clicked on title to drag graph.. // Note: reorder has to be limited to pinned graphs. m_graph_dragging = true; m_tooltip->cancel(); timedRedraw(50); m_graph_index = i; m_sizer_point.setX(x); m_sizer_point.setY(py); // point at top of graph.. this->setCursor(Qt::ClosedHandCursor); //done=true; } else if ((event->button() == Qt::RightButton) && (x < (titleWidth + gYAxis::Margin))) { this->setCursor(Qt::ArrowCursor); pin_action->setText(QObject::tr("Unpin %1 Graph").arg(g->title())); pin_graph = g; popout_action->setText(QObject::tr("Popout %1 Graph").arg(g->title())); popout_graph = g; populateMenu(g); context_menu->popup(event->globalPos()); //done=true; } else if (!g->blockSelect()) { if (m_metaselect) { if (m_selected_graph) { m_selected_graph->m_selecting_area = false; } } // send event to graph.. m_point_clicked = QPoint(event->x(), event->y()); //QMouseEvent e(event->type(),m_point_clicked,event->button(),event->buttons(),event->modifiers()); m_button_down = true; m_metaselect = event->modifiers() & Qt::AltModifier; m_horiz_travel = 0; m_graph_index = i; m_selected_graph = g; g->mousePressEvent(event); } done = true; } } py += h + graphSpacer; } // then handle the remainder... py = -m_offsetY; py += pinned_height; if (!done) { for (int i=0, end=m_graphs.size(); iisEmpty() || !g->visible() || g->isPinned()) { continue; } h = g->height() * m_scaleY; if (py > height()) { break; } if ((py + h + graphSpacer) >= 0) { if ((y >= py + h - 1) && (y <= py + h + graphSpacer)) { this->setCursor(Qt::SplitVCursor); m_sizer_dragging = true; m_sizer_index = i; m_sizer_point.setX(x); m_sizer_point.setY(y); //qDebug() << "Sizer clicked" << i; //done=true; } else if ((y >= py) && (y < py + h)) { //qDebug() << "Clicked" << i; if ((event->button() == Qt::LeftButton) && (x < (titleWidth + 20))) { // clicked on title to drag graph.. m_graph_dragging = true; m_tooltip->cancel(); redraw(); m_graph_index = i; m_sizer_point.setX(x); m_sizer_point.setY(py); // point at top of graph.. this->setCursor(Qt::ClosedHandCursor); //done=true; } else if ((event->button() == Qt::RightButton) && (x < (titleWidth + gYAxis::Margin))) { this->setCursor(Qt::ArrowCursor); popout_action->setText(QObject::tr("Popout %1 Graph").arg(g->title())); popout_graph = g; pin_action->setText(QObject::tr("Pin %1 Graph").arg(g->title())); pin_graph = g; populateMenu(g); context_menu->popup(event->globalPos()); //done=true; } else if (!g->blockSelect()) { if (m_metaselect) { if (m_selected_graph) { m_selected_graph->m_selecting_area = false; } } // send event to graph.. m_point_clicked = QPoint(event->x(), event->y()); //QMouseEvent e(event->type(),m_point_clicked,event->button(),event->buttons(),event->modifiers()); m_button_down = true; m_metaselect = event->modifiers() & Qt::AltModifier; m_horiz_travel = 0; m_graph_index = i; m_selected_graph = g; g->mousePressEvent(event); } } } py += h + graphSpacer; done=true; } } if (!done) { // if (event->button() == Qt::RightButton) { // this->setCursor(Qt::ArrowCursor); // context_menu->popup(event->globalPos()); // done=true; // } } } void gGraphView::mouseReleaseEvent(QMouseEvent *event) { int x = event->x(); int y = event->y(); float h, py = 0, pinned_height = 0; bool done = false; // Copy to a local variable to make sure this gets cleared bool button_down = m_button_down; m_button_down = false; // Handle pinned graphs first for (const auto & g : m_graphs) { if (!g || g->isEmpty() || !g->visible() || !g->isPinned()) { continue; } h = g->height() * m_scaleY; pinned_height += h + graphSpacer; if (py > height()) { break; // we are done.. can't draw anymore } if ((y >= py + h - 1) && (y < (py + h + graphSpacer))) { this->setCursor(Qt::SplitVCursor); done = true; } else if ((y >= py + 1) && (y <= py + h)) { // if (!m_sizer_dragging && !m_graph_dragging) { // g->mouseReleaseEvent(event); // } if (x >= titleWidth + 10) { this->setCursor(Qt::ArrowCursor); } else { this->setCursor(Qt::OpenHandCursor); } done = true; } py += h + graphSpacer; } // Now do the unpinned ones py = -m_offsetY; py += pinned_height; if (done) for (const auto & g : m_graphs) { if (!g || g->isEmpty() || !g->visible() || g->isPinned()) { continue; } h = g->height() * m_scaleY; if (py > height()) { break; // we are done.. can't draw anymore } if ((y >= py + h - 1) && (y < (py + h + graphSpacer))) { this->setCursor(Qt::SplitVCursor); } else if ((y >= py + 1) && (y <= py + h)) { // if (!m_sizer_dragging && !m_graph_dragging) { // g->mouseReleaseEvent(event); // } if (x >= titleWidth + 10) { this->setCursor(Qt::ArrowCursor); } else { this->setCursor(Qt::OpenHandCursor); } } py += h + graphSpacer; } if (m_sizer_dragging) { m_sizer_dragging = false; return; } if (m_graph_dragging) { m_graph_dragging = false; // not sure why the cursor code doesn't catch this.. if (x >= titleWidth + 10) { this->setCursor(Qt::ArrowCursor); } else { this->setCursor(Qt::OpenHandCursor); } return; } // The graph that got the button press gets the release event if (button_down) { // m_button_down = false; m_metaselect = event->modifiers() & Qt::AltModifier; saveHistory(); if (m_metaselect) { m_point_released = event->pos(); } else { if ((m_graph_index >= 0) && (m_graphs[m_graph_index])) { m_graphs[m_graph_index]->mouseReleaseEvent(event); } } } timedRedraw(0); } void gGraphView::keyReleaseEvent(QKeyEvent *event) { if (m_metaselect && !(event->modifiers() & Qt::AltModifier)) { QMouseEvent mevent(QEvent::MouseButtonRelease, m_point_released, Qt::LeftButton, Qt::LeftButton, event->modifiers()); if (m_graph_index>=0) { m_graphs[m_graph_index]->mouseReleaseEvent(&mevent); } m_metaselect = false; timedRedraw(50); } if (event->key() == Qt::Key_Escape) { if (history.size() > 0) { SelectionHistoryItem h = history.takeFirst(); SetXBounds(h.minx, h.maxx); // could Forward push this to another list? } else { ResetBounds(); } return; } #ifdef BROKEN_OPENGL_BUILD QWidget::keyReleaseEvent(event); #elif QT_VERSION < QT_VERSION_CHECK(5,4,0) QGLWidget::keyReleaseEvent(event); #else QOpenGLWidget::keyReleaseEvent(event); #endif } void gGraphView::mouseDoubleClickEvent(QMouseEvent *event) { mousePressEvent(event); // signal missing.. a qt change might "fix" this if we are not careful. int x = event->x(); int y = event->y(); float h, py = 0, pinned_height = 0; bool done = false; // Handle pinned graphs first for (const auto & g : m_graphs) { if (!g || g->isEmpty() || !g->visible() || !g->isPinned()) { continue; } h = g->height() * m_scaleY; pinned_height += h + graphSpacer; if (py > height()) { break; // we are done.. can't draw anymore } if ((py + h + graphSpacer) >= 0) { if ((y >= py) && (y <= py + h)) { if (x < titleWidth) { // What to do when double clicked on the graph title ?? g->mouseDoubleClickEvent(event); // pin the graph?? g->setPinned(false); redraw(); } else { // send event to graph.. g->mouseDoubleClickEvent(event); } done = true; } else if ((y >= py + h) && (y <= py + h + graphSpacer + 1)) { // What to do when double clicked on the resize handle? done = true; } } py += h; py += graphSpacer; // do we want the extra spacer down the bottom? } py = -m_offsetY; py += pinned_height; if (!done) // then handle unpinned graphs for (const auto & g : m_graphs) { if (!g || g->isEmpty() || !g->visible() || g->isPinned()) { continue; } h = g->height() * m_scaleY; if (py > height()) { break; } if ((py + h + graphSpacer) >= 0) { if ((y >= py) && (y <= py + h)) { if (x < titleWidth) { // What to do when double clicked on the graph title ?? g->mouseDoubleClickEvent(event); g->setPinned(true); redraw(); } else { // send event to graph.. g->mouseDoubleClickEvent(event); } } else if ((y >= py + h) && (y <= py + h + graphSpacer + 1)) { // What to do when double clicked on the resize handle? } } py += h; py += graphSpacer; // do we want the extra spacer down the bottom? } } void gGraphView::wheelEvent(QWheelEvent *event) { // Hmm.. I could optionalize this to change mousewheel behaviour without affecting the scrollbar now.. if (m_button_down) return; if (event->modifiers() == Qt::NoModifier) { int scrollDampening = AppSetting->scrollDampening(); if (event->orientation() == Qt::Vertical) { // Vertical Scrolling if (horizScrollTime.elapsed() < scrollDampening) { return; } if (m_scrollbar) m_scrollbar->SendWheelEvent(event); // Just forwarding the event to scrollbar for now.. m_tooltip->cancel(); vertScrollTime.start(); return; } // (This is a total pain in the butt on MacBook touchpads..) if (vertScrollTime.elapsed() < scrollDampening) { return; } horizScrollTime.start(); } gGraph *graph = nullptr; int group = 0; //int x = event->x(); int y = event->y(); float h, py = 0, pinned_height = 0; // Find graph hovered over for (const auto & g : m_graphs) { if (!g || g->isEmpty() || !g->visible() || !g->isPinned()) { continue; } h = g->height() * m_scaleY; pinned_height += h + graphSpacer; if (py > height()) { break; // we are done.. can't draw anymore } if ((py + h + graphSpacer) >= 0) { if ((y >= py) && (y <= py + h)) { graph = g; break; } else if ((y >= py + h) && (y <= py + h + graphSpacer + 1)) { // What to do when double clicked on the resize handle? graph = g; break; } } py += h; py += graphSpacer; // do we want the extra spacer down the bottom? } if (!graph) { py = -m_offsetY; py += pinned_height; for (const auto & g : m_graphs) { if (!g || g->isEmpty() || !g->visible() || g->isPinned()) { continue; } h = g->height() * m_scaleY; if (py > height()) { break; } if ((py + h + graphSpacer) >= 0) { if ((y >= py) && (y <= py + h)) { graph = g; break; } else if ((y >= py + h) && (y <= py + h + graphSpacer + 1)) { // What to do when double clicked on the resize handle? graph = g; break; } } py += h; py += graphSpacer; // do we want the extra spacer down the bottom? } } if (event->modifiers() == Qt::NoModifier) { if (!graph) { // just pick any graph then for (const auto & g : m_graphs) { if (!g) continue; if (!g->isEmpty()) { graph = g; group = graph->group(); break; } } } else group=graph->group(); if (!graph) { return; } double xx = (graph->max_x - graph->min_x); double zoom = 240.0; int delta = event->delta(); if (delta > 0) { graph->min_x -= (xx / zoom) * (float)abs(delta); } else { graph->min_x += (xx / zoom) * (float)abs(delta); } graph->max_x = graph->min_x + xx; if (graph->min_x < graph->rmin_x) { graph->min_x = graph->rmin_x; graph->max_x = graph->rmin_x + xx; } if (graph->max_x > graph->rmax_x) { graph->max_x = graph->rmax_x; graph->min_x = graph->max_x - xx; } saveHistory(); SetXBounds(graph->min_x, graph->max_x, group); } else if ((event->modifiers() & Qt::ControlModifier)) { if (graph) graph->wheelEvent(event); // int x = event->x(); // int y = event->y(); // float py = -m_offsetY; // float h; // for (int i = 0; i < m_graphs.size(); i++) { // gGraph *g = m_graphs[i]; // if (!g || g->isEmpty() || !g->visible()) { continue; } // h = g->height() * m_scaleY; // if (py > height()) { // break; // } // if ((py + h + graphSpacer) >= 0) { // if ((y >= py) && (y <= py + h)) { // if (x < titleWidth) { // // What to do when ctrl+wheel is used on the graph title ?? // } else { // // send event to graph.. // g->wheelEvent(event); // } // } else if ((y >= py + h) && (y <= py + h + graphSpacer + 1)) { // // What to do when the wheel is used on the resize handle? // } // } // py += h; // py += graphSpacer; // do we want the extra spacer down the bottom? // } } } void gGraphView::getSelectionTimes(qint64 & start, qint64 & end) { if (m_graph_index >= 0) { gGraph *g = m_graphs[m_graph_index]; if (!g) { start = 0; end = 0; return; } int x1 = g->m_selection.x() + titleWidth; int x2 = x1 + g->m_selection.width(); start = g->screenToTime(x1); end = g->screenToTime(x2); } } void gGraphView::keyPressEvent(QKeyEvent *event) { m_metaselect = event->modifiers() & Qt::AltModifier; if (m_metaselect && ((event->key() == Qt::Key_B) || (event->key() == 8747))) { if (mainwin->getDaily()->graphView() == this) { if (m_graph_index >= 0) { m_metaselect=false; qint64 start,end; getSelectionTimes(start,end); QDateTime d1 = QDateTime::fromMSecsSinceEpoch(start, Qt::LocalTime); mainwin->getDaily()->addBookmark(start, end, QString("Bookmark at %1").arg(d1.time().toString("HH:mm:ss"))); m_graphs[m_graph_index]->cancelSelection(); m_graph_index = -1; timedRedraw(0); } event->accept(); return; } } if ((m_metaselect) && (event->key() >= Qt::Key_0) && (event->key() <= Qt::Key_9)) { //int bk = (int)event->key()-Qt::Key_0; m_metaselect = false; timedRedraw(0); } if (event->key() == Qt::Key_F3) { AppSetting->setLineCursorMode(!AppSetting->lineCursorMode()); timedRedraw(0); } if ((event->key() == Qt::Key_F1)) { dumpInfo(); } if (event->key() == Qt::Key_Tab) { event->ignore(); return; } if (event->key() == Qt::Key_PageUp) { if (m_scrollbar) { m_offsetY -= AppSetting->graphHeight() * 3 * m_scaleY; m_scrollbar->setValue(m_offsetY); m_offsetY = m_scrollbar->value(); redraw(); } return; } else if (event->key() == Qt::Key_PageDown) { if (m_scrollbar) { m_offsetY += AppSetting->graphHeight() * 3 * m_scaleY; if (m_offsetY < 0) { m_offsetY = 0; } m_scrollbar->setValue(m_offsetY); m_offsetY = m_scrollbar->value(); redraw(); } return; // redraw(); } gGraph *g = nullptr; int group = 0; // Pick the first valid graph in the primary group for (const auto & gr : m_graphs) { if (gr->group() == group) { if (!gr->isEmpty() && gr->visible()) { g = gr; break; } } } if (!g) { for (const auto & gr : m_graphs) { if (!gr->isEmpty()) { g = gr; group = g->group(); break; } } } if (!g) { return; } g->keyPressEvent(event); if (event->key() == Qt::Key_Left) { double xx = g->max_x - g->min_x; double zoom = 8.0; if (event->modifiers() & Qt::ControlModifier) { zoom /= 4; } g->min_x -= xx / zoom;; g->max_x = g->min_x + xx; if (g->min_x < g->rmin_x) { g->min_x = g->rmin_x; g->max_x = g->rmin_x + xx; } saveHistory(); SetXBounds(g->min_x, g->max_x, group); } else if (event->key() == Qt::Key_Right) { double xx = g->max_x - g->min_x; double zoom = 8.0; if (event->modifiers() & Qt::ControlModifier) { zoom /= 4; } g->min_x += xx / zoom; g->max_x = g->min_x + xx; if (g->max_x > g->rmax_x) { g->max_x = g->rmax_x; g->min_x = g->rmax_x - xx; } saveHistory(); SetXBounds(g->min_x, g->max_x, group); } else if (event->key() == Qt::Key_Up) { float zoom = 0.75F; if (event->modifiers() & Qt::ControlModifier) { zoom /= 1.5; } g->ZoomX(zoom, 0); // zoom in. } else if (event->key() == Qt::Key_Down) { float zoom = 1.33F; if (event->modifiers() & Qt::ControlModifier) { zoom *= 1.5; } g->ZoomX(zoom, 0); // Zoom out } //qDebug() << "Keypress??"; } // Sends day object to be distributed to all Graphs Layers objects void gGraphView::setDay(Day *day) { m_day = day; for (const auto & g : m_graphs) { if (g) g->setDay(day); } ResetBounds(false); } bool gGraphView::isEmpty() { bool res = true; for (const auto & graph : m_graphs) { if (!graph->isSnapshot() && !graph->isEmpty()) { res = false; break; } } return res; } void gGraphView::refreshTimeout() { if (this->isVisible()) { redraw(); } } void gGraphView::timedRedraw(int ms) { if (timer->isActive()) { if (ms == 0) { timer->stop(); } else { int m = timer->remainingTime(); if (m > ms) { timer->stop(); } else return; } } timer->setSingleShot(true); timer->start(ms); } void gGraphView::deselect() { for (auto & graph : m_graphs) { if (graph) graph->deselect(); } } void gGraphView::resetLayout() { int default_height = AppSetting->graphHeight(); for (auto & graph : m_graphs) { if (graph) graph->setHeight(default_height); } updateScale(); timedRedraw(0); } // Reset order of current graphs to new order, remove pinning void gGraphView::resetGraphOrder(bool pinFirst, const QList graphOrder) { // qDebug() << "gGraphView::resetGraphOrder new order" << graphOrder; QList new_graphs; QList old_graphs = m_graphs; // Create new_graphs in order specified by graphOrder for (int i = 0; i < graphOrder.size(); ++i) { QString nextGraph = graphOrder.at(i); auto it = m_graphsbyname.find(nextGraph); if (it == m_graphsbyname.end()) { qDebug() << "resetGraphOrder could not find" << nextGraph; continue; // should not happen } gGraph * graph = it.value(); new_graphs.append(graph); int idx = old_graphs.indexOf(graph); old_graphs.removeAt(idx); // qDebug() << "resetGraphOrder added to new graphs" << nextGraph; } // If we didn't find everything, append anything extra we have for (int i = 0; i < old_graphs.size(); i++) { qDebug() << "resetGraphOrder added leftover" << old_graphs.at(i)->name(); new_graphs.append(old_graphs.at(i)); } m_graphs = new_graphs; for (auto & graph : m_graphs) { if (!graph) continue; if (graph->isSnapshot()) continue; graph->setPinned(false); } if (pinFirst) m_graphs[0]->setPinned(true); } // Reset order of current graphs to match defaults, remove pinning void gGraphView::resetGraphOrder(bool pinFirst) { m_graphs = m_default_graphs; for (auto & graph : m_graphs) { if (!graph) continue; if (graph->isSnapshot()) continue; graph->setPinned(false); } if (pinFirst) m_graphs[0]->setPinned(true); } void gGraphView::SaveDefaultSettings() { m_default_graphs = m_graphs; } const quint32 gvmagic = 0x41756728; const quint16 gvversion = 4; void gGraphView::SaveSettings(QString title) { qDebug() << "Saving" << title << "settings"; QString filename = p_profile->Get("{DataFolder}/") + title.toLower() + ".shg"; QFile f(filename); f.open(QFile::WriteOnly); QDataStream out(&f); out.setVersion(QDataStream::Qt_4_6); out.setByteOrder(QDataStream::LittleEndian); out << (quint32)gvmagic; out << (quint16)gvversion; out << (qint16)size(); for (auto & graph : m_graphs) { if (!graph) continue; if (graph->isSnapshot()) continue; // qDebug() << "Saving graph" << title << graph->name(); out << graph->name(); out << graph->height(); out << graph->visible(); out << graph->RecMinY(); out << graph->RecMaxY(); out << graph->zoomY(); out << (bool)graph->isPinned(); gLineChart * lc = dynamic_cast(findLayer(graph, LT_LineChart)); if (lc) { out << (quint32)LT_LineChart; out << lc->m_flags_enabled; out << lc->m_enabled; out << lc->m_dot_enabled; } else { out << (quint32)LT_Other; } } f.close(); } // Merge and overwrite two hashes. (We can't use QHash::unite because it doesn't overwrite.) // This is useful for loading settings, where we want to leave the defaults alone for features // that don't yet have settings specified. template inline void hashMerge(T & a, const T & b) { for (auto key : b.keys()) { a[key] = b[key]; } } bool gGraphView::LoadSettings(QString title) { QString filename = p_profile->Get("{DataFolder}/") + title.toLower() + ".shg"; QFile f(filename); if (!f.exists()) { return false; } f.open(QFile::ReadOnly); QDataStream in(&f); in.setVersion(QDataStream::Qt_4_6); in.setByteOrder(QDataStream::LittleEndian); quint32 t1; quint16 version; in >> t1; if (t1 != gvmagic) { qDebug() << "gGraphView" << title << "settings magic doesn't match" << t1 << gvmagic; return false; } in >> version; if (version < gvversion) { qDebug() << "gGraphView" << title << "settings will be upgraded."; } qint16 siz; in >> siz; QString name; float hght; bool vis; EventDataType recminy, recmaxy; bool pinned; short zoomy = 0; QList neworder; QHash::iterator gi; for (int i = 0; i < siz; i++) { in >> name; in >> hght; in >> vis; in >> recminy; in >> recmaxy; //qDebug() << "Loading graph" << title << name; if (gvversion >= 1) { in >> zoomy; } if (gvversion >= 2) { in >> pinned; } QHash flags_enabled; QHash plots_enabled; QHash > dot_enabled; // Warning: Do not break the follow section up!!! quint32 layertype; if (gvversion >= 4) { in >> layertype; if (layertype == LT_LineChart) { in >> flags_enabled; in >> plots_enabled; in >> dot_enabled; } } gGraph *g = nullptr; if (version <= 2) { continue; // // Names were stored as translated strings, so look up title instead. // g = nullptr; // for (int z=0; ztitle() == name) { // g = m_graphs[z]; // break; // } // } } else { gi = m_graphsbyname.find(name); if (gi == m_graphsbyname.end()) { qDebug() << "Graph" << name << "has been renamed or removed"; } else { g = gi.value(); } } if (g) { neworder.push_back(g); g->setHeight(hght); g->setVisible(vis); g->setRecMinY(recminy); g->setRecMaxY(recmaxy); g->setZoomY(zoomy); g->setPinned(pinned); if (gvversion >= 4) { if (layertype == LT_LineChart) { gLineChart * lc = dynamic_cast(findLayer(g, LT_LineChart)); if (lc) { hashMerge(lc->m_flags_enabled, flags_enabled); hashMerge(lc->m_enabled, plots_enabled); hashMerge(lc->m_dot_enabled, dot_enabled); } } } } } if (neworder.size() == m_graphs.size()) { m_graphs = neworder; } f.close(); updateScale(); return true; } gGraph *gGraphView::findGraph(QString name) { auto it = m_graphsbyname.find(name); if (it == m_graphsbyname.end()) { return nullptr; } return it.value(); } gGraph *gGraphView::findGraphTitle(QString title) { for (auto & graph : m_graphs) { if (graph->title() == title) return graph; } return nullptr; } int gGraphView::visibleGraphs() { int cnt = 0; for (auto & graph : m_graphs) { if (graph->visible() && !graph->isEmpty()) { cnt++; } } return cnt; } void gGraphView::dataChanged() { for (auto & graph : m_graphs) { graph->dataChanged(); } } void gGraphView::redraw() { #ifdef BROKEN_OPENGL_BUILD repaint(); #else update(); #endif } OSCAR-code-v1.3.1/oscar/Graphs/gGraphView.h000066400000000000000000000542261417327530600202510ustar00rootroot00000000000000/* gGraphView Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2015 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GGRAPHVIEW_H #define GGRAPHVIEW_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef BROKEN_OPENGL_BUILD #if QT_VERSION < QT_VERSION_CHECK(5,4,0) #include #else #include #endif #endif #include #include #include class MinMaxWidget:public QWidget { Q_OBJECT public: explicit MinMaxWidget(gGraph * graph, QWidget *parent); ~MinMaxWidget() {} void createLayout(); void setMin(double d) { minbox->setValue(d); } void setMax(double d) { maxbox->setValue(d); } double min() { return minbox->value(); } double max() { return maxbox->value(); } void setComboIndex(int i) { combobox->setCurrentIndex(i); } int comboIndex() { return combobox->currentIndex(); } // void setChecked(bool b) { checkbox->setChecked(b); } // bool checked() { return checkbox->isChecked(); } public slots: void onMinChanged(double d); void onMaxChanged(double d); void onComboChanged(int idx); void onResetClicked(); protected: gGraph * graph; QComboBox *combobox; // QCheckBox *checkbox; QDoubleSpinBox *minbox; QDoubleSpinBox *maxbox; double step; QToolButton * reset; }; enum FlagType { FT_Bar, FT_Dot, FT_Span }; //void setEmptyImage(QString text, QPixmap pixmap); class MyLabel:public QWidget { Q_OBJECT public: MyLabel(QWidget * parent); virtual ~MyLabel(); void setText(QString text); void setAlignment(Qt::Alignment alignment); void setFont(QFont & font); QFont & font() { return m_font; } virtual void paintEvent(QPaintEvent *); QFont m_font; QString m_text; Qt::Alignment m_alignment; QTime time; protected slots: void doRedraw(); }; class gGraphView; const int textque_max = 512; /*! \struct TextQue \brief Holds a single item of text for the drawing queue */ struct TextQue { TextQue() { } TextQue(short x, short y, float angle, QString text, QColor color, QFont * font, bool antialias): x(x), y(y), angle(angle), text(text), color(color), font(font), antialias(antialias) { } TextQue(const TextQue & copy) { x=copy.x; y=copy.y; text=copy.text; angle=copy.angle; color=copy.color; font=copy.font; antialias=copy.antialias; } //! \variable contains the x axis screen position to draw the text short x; //! \variable contains the y axis screen position to draw the text short y; //! \variable the angle in degrees for drawing rotated text float angle; //! \variable the actual text to draw QString text; //! \variable the color the text will be drawn in QColor color; //! \variable a pointer to the QFont to use to draw this text QFont *font; //! \variable whether to use antialiasing to draw this text bool antialias; }; struct TextQueRect { TextQueRect() { } TextQueRect(QRectF r, quint32 flags, QString text, float angle, QColor color, QFont * font, bool antialias): rect(r), flags(flags), text(text), angle(angle), color(color), font(font), antialias(antialias) { } TextQueRect(const TextQueRect & copy) { rect = copy.rect; flags = copy.flags; text = copy.text; angle = copy.angle; color = copy.color; font = copy.font; antialias = copy.antialias; } //! \variable contains the QRect containing the text object QRectF rect; //! \variable Qt alignment flags.. quint32 flags; //! \variable the actual text to draw QString text; //! \variable the angle in degrees for drawing rotated text float angle; //! \variable the color the text will be drawn in QColor color; //! \variable a pointer to the QFont to use to draw this text QFont *font; //! \variable whether to use antialiasing to draw this text bool antialias; }; /*! \class MyScrollBar \brief An custom scrollbar to interface with gGraphWindow */ class MyScrollBar : public QScrollBar { public: MyScrollBar(QWidget *parent = nullptr) : QScrollBar(parent) { } void SendWheelEvent(QWheelEvent *e) { wheelEvent(e); } }; /*! \class gThread \brief Part of the Threaded drawing code This is currently broken, as Qt didn't behave anyway, and it offered no performance improvement drawing-wise */ class gThread : public QThread { public: gThread(gGraphView *g); ~gThread(); //! \brief Start thread process void run(); //! \brief Kill thread process void die() { m_running = false; } QMutex mutex; protected: gGraphView *graphview; volatile bool m_running; }; /*! \class gToolTip \brief Popup Tooltip to display information over the OpenGL graphs */ class gToolTip : public QObject { Q_OBJECT public: //! \brief Initializes the ToolTip object, and connects the timeout to the gGraphView object gToolTip(gGraphView *graphview); virtual ~gToolTip(); /*! \fn virtual void display(QString text, int x, int y, int timeout=2000); \brief Set the tooltips display message, position, and timeout value */ virtual void display(QString text, int x, int y, ToolTipAlignment align = TT_AlignCenter, int timeout = 0); //! \brief Draw the tooltip virtual void paint(QPainter &paint); //actually paints it. //! \brief Close the tooltip early. void cancel(); //! \brief Returns true if the tooltip is currently visible bool visible() { return m_visible; } protected: gGraphView *m_graphview; QTimer *timer; QPoint m_pos; int tw, th; QString m_text; bool m_visible; int m_spacer; QImage m_image; bool m_invalidate; ToolTipAlignment m_alignment; protected slots: //! \brief Timeout to hide tooltip, and redraw without it. void timerDone(); }; struct SelectionHistoryItem { SelectionHistoryItem() { minx=maxx=0; } SelectionHistoryItem(quint64 m1, quint64 m2) { minx=m1; maxx=m2; } SelectionHistoryItem(const SelectionHistoryItem & copy) { minx = copy.minx; maxx = copy.maxx; } quint64 minx; quint64 maxx; SelectionHistoryItem& operator=(const SelectionHistoryItem& other) = default; }; class MyDockWindow:public QMainWindow { public: MyDockWindow(QWidget * parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) {} void closeEvent(QCloseEvent *event); }; /*! \class gGraphView \brief Main OpenGL Graph Area, derived from QGLWidget This widget contains a list of graphs, and provides the means to display them, scroll via an attached scrollbar, change the order around, and resize individual graphs. It replaced QScrollArea and multiple QGLWidgets graphs, and a very buggy QSplitter. It led to quite a performance increase over the old Qt method. */ class gGraphView #ifdef BROKEN_OPENGL_BUILD :public QWidget #elif QT_VERSION < QT_VERSION_CHECK(5,4,0) :public QGLWidget #else :public QOpenGLWidget #endif { friend class gGraph; Q_OBJECT public: /*! \fn explicit gGraphView(QWidget *parent = 0,gGraphView * shared=0); \brief Constructs a new gGraphView object (main graph area) \param QWidget * parent \param gGraphView * shared The shared parameter allows for OpenGL context sharing. But this must not be shared with Printers snapshot gGraphView object, or it will lead to display/font corruption */ explicit gGraphView(QWidget *parent = 0, gGraphView *shared = 0, QWidget *caller = 0); virtual ~gGraphView(); void closeEvent(QCloseEvent * event) override; //! \brief Add gGraph g to this gGraphView, in the requested graph-linkage group void addGraph(gGraph *g, short group = 0); //! \brief The width of the vertical text Title area for all graphs static const int titleWidth = 30; //! \brief The splitter is drawn inside this gap static const int graphSpacer = 4; bool contains(QString name) { for (int i=0; iname() == name) return true; } return false; } gGraph *operator [](QString name) { for (int i=0; iname() == name) return m_graphs[i]; } return nullptr; } //! \brief Finds the top pixel position of the supplied graph float findTop(gGraph *graph); //! \brief Returns the scaleY value, which is used when laying out less graphs than fit on the screen. float scaleY() { return m_scaleY; } //! \brief Sets the scaleY value, which is used when laying out less graphs than fit on the screen. void setScaleY(float sy) { m_scaleY = sy; } //! \brief Returns the current selected time range void GetXBounds(qint64 &st, qint64 &et); //! \brief Returns the maximum time range bounds void GetRXBounds(qint64 &st, qint64 &et); //! \brief Resets the time range to default for this day. Refreshing the display if refresh==true. void ResetBounds(bool refresh = true); //! \brief Supplies time range to all graph objects in linked group, refreshing if requested void SetXBounds(qint64 minx, qint64 maxx, short group = 0, bool refresh = true); //! \brief Saves the current graph order, heights, min & Max Y values to disk void SaveSettings(QString title); //! \brief Loads the current graph order, heights, min & max Y values from disk bool LoadSettings(QString title); //! \brief Saves the current (initial) graph order, heights, min & Max Y values for future recovery void SaveDefaultSettings(); //! \brief Reset the current graph order, heights, min & max Y values to match default values void resetGraphOrder(bool pinFirst); //! \brief Reset the current graph order, heights, min & max Y values to match default values void resetGraphOrder(bool pinFirst, const QList graphOrder); //! \brief Returns the graph object matching the supplied name, nullptr if it does not exist. gGraph *findGraph(QString name); //! \brief Returns the graph object matching the graph title, nullptr if it does not exist. gGraph *findGraphTitle(QString title); //! \brief Returns true if control key is down during select operation inline bool metaSelect() const { return m_metaselect; } //! \brief Returns true if currently selecting data with mouse inline bool selectionInProgress() const { return m_button_down; } inline float printScaleX() const { return print_scaleX; } inline float printScaleY() const { return print_scaleY; } inline void setPrintScaleX(float x) { print_scaleX = x; } inline void setPrintScaleY(float y) { print_scaleY = y; } void saveHistory() { history.push_front(SelectionHistoryItem(m_minx, m_maxx)); if (history.size() > max_history) { history.pop_back(); } } //! \brief Returns true if all Graph objects contain NO day data. ie, graph area is completely empty. bool isEmpty(); Day * day() { return m_day; } //! \brief Tell all graphs to deslect any highlighted areas void deselect(); QPoint pointClicked() const { return m_point_clicked; } void setPointClicked(QPoint p) { m_point_clicked = p; } //! \brief Set a redraw timer for ms milliseconds, clearing any previous redraw timer. void timedRedraw(int ms=0); gGraph *m_selected_graph; gToolTip *m_tooltip; QTimer *timer; //! \brief Add the Text information to the Text Drawing Queue (called by gGraphs renderText method) void AddTextQue(const QString &text, QRectF rect, quint32 flags, float angle = 0.0, QColor color = Qt::black, QFont *font = defaultfont, bool antialias = true); //! \brief Add the Text information to the Text Drawing Queue (called by gGraphs renderText method) void AddTextQue(const QString &text, short x, short y, float angle = 0.0, QColor color = Qt::black, QFont *font = defaultfont, bool antialias = true); // //! \brief Draw all Text in the text drawing queue // void DrawTextQue(); //! \brief Draw all text components using QPainter object painter void DrawTextQue(QPainter &painter); //! \brief Draw all text components using QPainter object painter using Pixmapcache void DrawTextQueCached(QPainter &painter); //! \brief Returns number of graphs contained (whether they are visible or not) int size() const { return m_graphs.size(); } //! \brief Return individual graph by index value gGraph *operator[](int i) { return m_graphs[i]; } //! \brief Returns the custom scrollbar object linked to this gGraphArea MyScrollBar *scrollBar() const { return m_scrollbar; } //! \brief Sets the custom scrollbar object linked to this gGraphArea void setScrollBar(MyScrollBar *sb); //! \brief Calculates the correct scrollbar parameters for all visible, non empty graphs. void updateScrollBar(); //! \brief Called on resize, fits graphs when too few to show, by scaling to fit screen size. Calls updateScrollBar() void updateScale(); // update scale & Scrollbar //! \brief Returns a count of all visible, non-empty Graphs. int visibleGraphs(); //! \brief Returns the horizontal travel of the mouse, for use in Mouse Handling code. int horizTravel() const { return m_horiz_travel; } //! \brief Sets the message displayed when there are no graphs to draw void setEmptyText(QString s) { m_emptytext = s; } //! \brief Returns the message displayed when there are no graphs to draw QString emptyText() { return m_emptytext; } //! \brief Sets the message displayed when there are no graphs to draw void setEmptyImage(QPixmap pm) { this->m_emptyimage = pm; } inline const float &devicePixelRatio() { return m_dpr; } void setDevicePixelRatio(float dpr) { m_dpr = dpr; } #ifdef ENABLE_THREADED_DRAWING QMutex text_mutex; QMutex gl_mutex; QSemaphore *masterlock; bool useThreads() { return m_idealthreads > 1; } QVector m_threads; int m_idealthreads; QMutex dl_mutex; #endif //! \brief Sends day object to be distributed to all Graphs Layers objects void setDay(Day *day); //! \brief pops a graph off the list for multithreaded drawing code gGraph *popGraph(); // exposed for multithreaded drawing //! \brief Hides the splitter, used in report printing code void hideSplitter() { m_showsplitter = false; } //! \brief Re-enabled the in-between graph splitters. void showSplitter() { m_showsplitter = true; } //! \brief Trash all graph objects listed (without destroying Graph contents) void trashGraphs(bool destroy); //! \brief Enable or disable the Text Pixmap Caching system preference overide void setUsePixmapCache(bool b) { use_pixmap_cache = b; } //! \brief Graph drawing routines, returns true if there weren't any graphs to draw bool renderGraphs(QPainter &painter); //! \brief Used internally by graph mousehandler to set modifier state void setMetaSelect(bool b) { m_metaselect = b; } //! \brief The current time the mouse pointer is hovering over inline double currentTime() { return m_currenttime; } //! \brief Returns a context formatted text string with the currently selected time range in days QString getRangeInDaysString(); //! \brief Returns a context formatted text string with the currently selected time range QString getRangeString(); Layer * findLayer(gGraph * graph, LayerType type); void populateMenu(gGraph *); QMenu * limits_menu; QMenu * lines_menu; QMenu * plots_menu; QMenu * oximeter_menu; QMenu * cpap_menu; inline void setCurrentTime(double time) { m_currenttime = time; } inline QPoint currentMousePos() const { return m_mouse; } void dumpInfo(); void resetMouse() { m_mouse = QPoint(0,0); } void getSelectionTimes(qint64 & start, qint64 & end); //! \brief Whether to show a little authorship message down the bottom of empty graphs. void setShowAuthorMessage(bool b) { m_showAuthorMessage = b; } // for profiling purposes, a count of lines drawn in a single frame int lines_drawn_this_frame; int quads_drawn_this_frame; int strings_drawn_this_frame; int strings_cached_this_frame; QVector history; static MyDockWindow * dock; protected: bool event(QEvent * event) Q_DECL_OVERRIDE; bool gestureEvent(QGestureEvent * event); bool pinchTriggered(QPinchGesture * gesture); void leaveEvent (QEvent * event) override; //! \brief The heart of the drawing code #ifdef BROKEN_OPENGL_BUILD void paintEvent(QPaintEvent *) override; #else void paintGL() override; #endif //! \brief Calculates the sum of all graph heights float totalHeight(); //! \brief Calculates the sum of all graph heights, taking scaling into consideration float scaleHeight(); //! \brief Update the OpenGL area when the screen is resized void resizeEvent(QResizeEvent *) override; //! \brief Set the Vertical offset (used in scrolling) void setOffsetY(int offsetY); //! \brief Set the Horizontal offset (not used yet) void setOffsetX(int offsetX); //! \brief Mouse Moved somewhere in main gGraphArea, propagates to the individual graphs void mouseMoveEvent(QMouseEvent *event) override; //! \brief Mouse Button Press Event somewhere in main gGraphArea, propagates to the individual graphs void mousePressEvent(QMouseEvent *event) override; //! \brief Mouse Button Release Event somewhere in main gGraphArea, propagates to the individual graphs void mouseReleaseEvent(QMouseEvent *event) override; //! \brief Mouse Button Double Click Event somewhere in main gGraphArea, propagates to the individual graphs void mouseDoubleClickEvent(QMouseEvent *event) override; //! \brief Mouse Wheel Event somewhere in main gGraphArea, propagates to the individual graphs void wheelEvent(QWheelEvent *event) override; //! \brief Keyboard event while main gGraphArea has focus. void keyPressEvent(QKeyEvent *event) override; //! \brief Keyboard event while main gGraphArea has focus. void keyReleaseEvent(QKeyEvent *event) override; //! \brief Add Graph to drawing queue, mainly for the benefit of multithreaded drawing code void queGraph(gGraph *, int originX, int originY, int width, int height); Day *m_day; //! \brief the list of graphs to draw this frame QList m_drawlist; //! \brief Linked graph object containing shared GL Context (in this app, daily view's gGraphView) gGraphView *m_shared; //! \brief List of all graphs contained in this area QList m_graphs; //! \brief List of all graphs contained, indexed by title QHash m_graphsbyname; //! \brief List of initial settings of all graphs contained in this area QList m_default_graphs; //! \variable Vertical scroll offset (adjusted when scrollbar gets moved) int m_offsetY; //! \variable Horizontal scroll offset (unused, but can be made to work if necessary) int m_offsetX; //! \variable Scale used to enlarge graphs when less graphs than can fit on screen. float m_scaleY; float m_dpr; bool m_sizer_dragging; int m_sizer_index; bool m_button_down; QPoint m_point_clicked; QPoint m_point_released; bool m_metaselect; QPoint m_sizer_point; int m_horiz_travel; MyScrollBar *m_scrollbar; QTimer *redrawtimer; bool m_graph_dragging; int m_graph_index; qint64 pinch_min, pinch_max; //! \brief List of all queue text to draw.. not sure why I didn't use a vector here.. Might of been a leak issue QVector m_textque; //! \brief ANother text que with rect alignment capabilities... QVector m_textqueRect; int m_lastxpos, m_lastypos; QString m_emptytext; QPixmap m_emptyimage; bool m_showsplitter; // msec in epoch of first day and last day of range qint64 m_minx, m_maxx; QVector fwd_history; float print_scaleX, print_scaleY; QPixmap previous_day_snapshot; QPixmap current_day_snapshot; bool m_fadingOut; bool m_fadingIn; bool m_limbo; bool m_fadedir; bool m_inAnimation; bool m_blockUpdates; QPoint m_mouse; double m_currenttime; QTime m_animationStarted; bool use_pixmap_cache; QPixmapCache pixmapcache; QTime horizScrollTime, vertScrollTime; QMenu * context_menu; QAction * pin_action; QAction * popout_action; QPixmap pin_icon; gGraph *pin_graph; gGraph *popout_graph; QAction * snap_action; QAction * zoom100_action; QWidget * caller; bool m_showAuthorMessage; signals: void updateCurrentTime(double); void updateRange(double,double); void GraphsChanged(); public slots: //! \brief Callback from the ScrollBar, to change scroll position void scrollbarValueChanged(int val); //! \brief Simply refreshes the GL view, called when timeout expires. void refreshTimeout(); //! \brief Call UpdateGL unless animation is in progress void redraw(); //! \brief Resets all contained graphs to have a uniform height. void resetLayout(); void resetZoom(); void dataChanged(); bool hasSnapshots(); void popoutGraph(); void togglePin(); protected slots: void onLinesClicked(QAction *); void onPlotsClicked(QAction *); void onOverlaysClicked(QAction *); void onSnapshotGraphToggle(); }; #endif // GGRAPHVIEW_H OSCAR-code-v1.3.1/oscar/Graphs/gLineChart.cpp000066400000000000000000001114701417327530600205540ustar00rootroot00000000000000/* gLineChart Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "Graphs/gLineChart.h" #include #include #include #include "Graphs/glcommon.h" #include "Graphs/gGraph.h" #include "Graphs/gGraphView.h" #include "SleepLib/profiles.h" #include "Graphs/gLineOverlay.h" #define EXTRA_ASSERTS 1 QDataStream & operator<<(QDataStream & stream, const DottedLine & dot) { stream << dot.code; stream << dot.type; stream << dot.value; stream << dot.visible; stream << dot.available; return stream; } QDataStream & operator>>(QDataStream & stream, DottedLine & dot) { quint32 tmp; stream >> dot.code; stream >> tmp; stream >> dot.value; stream >> dot.visible; stream >> dot.available; dot.type = (ChannelCalcType)tmp; return stream; } QColor darken(QColor color, float p) { int r = qMin(int(color.red() * p), 255); int g = qMin(int(color.green() * p), 255); int b = qMin(int(color.blue() * p), 255); return QColor(r,g,b, color.alpha()); } gLineChart::gLineChart(ChannelID code, bool square_plot, bool disable_accel) : Layer(code), m_square_plot(square_plot), m_disable_accel(disable_accel) { addPlot(code, square_plot); m_report_empty = false; lines.reserve(50000); lasttime = 0; m_layertype = LT_LineChart; } gLineChart::~gLineChart() { for (auto fit = flags.begin(), end=flags.end(); fit != end; ++fit) { // destroy any overlay bar from previous day delete fit.value(); } flags.clear(); } bool gLineChart::isEmpty() { if (!m_day) { return true; } for (const auto code : m_codes) { for (int i=0, end=m_day->size(); i < end; i++) { Session *sess = m_day->sessions[i]; if (sess->channelExists(code)) { return false; } } } return true; } void gLineChart::SetDay(Day *d) { // Layer::SetDay(d); m_day = d; m_minx = 0, m_maxx = 0; m_miny = 0, m_maxy = 0; m_physminy = 0, m_physmaxy = 0; if (!d) { return; } qint64 t64; EventDataType tmp; bool first = true; for (auto & code : m_codes) { for (int i=0, end=d->size(); i < end; i++) { Session *sess = d->sessions[i]; if (!sess->enabled()) continue; // Don't use operator[] here or else it will insert a default-constructed entry // into sess->settings if it's not present. CPAPMode mode = (CPAPMode)sess->settings.value(CPAP_Mode).toInt(); if (mode >= MODE_BILEVEL_FIXED) { m_enabled[CPAP_Pressure] = true; // probably a confusion of Pressure and IPAP somewhere m_enabled[CPAP_IPAP] = true; m_enabled[CPAP_EPAP] = true; } if (code == CPAP_MaskPressure) { if (sess->channelExists(CPAP_MaskPressureHi)) { code = CPAP_MaskPressureHi; // verify setting m_codes[] m_enabled[code] = schema::channel[CPAP_MaskPressureHi].enabled(); goto skipcheck; // why not :P } } if (!sess->channelExists(code)) { continue; } skipcheck: if (first) { m_miny = sess->Min(code); m_maxy = sess->Max(code); m_physminy = sess->physMin(code); m_physmaxy = sess->physMax(code); m_minx = sess->first(code); m_maxx = sess->last(code); first = false; } else { tmp = sess->physMin(code); if (m_physminy > tmp) { m_physminy = tmp; } tmp = sess->physMax(code); if (m_physmaxy < tmp) { m_physmaxy = tmp; } tmp = sess->Min(code); if (m_miny > tmp) { m_miny = tmp; } tmp = sess->Max(code); if (m_maxy < tmp) { m_maxy = tmp; } t64 = sess->first(code); if (m_minx > t64) { m_minx = t64; } t64 = sess->last(code); if (m_maxx < t64) { m_maxx = t64; } } } } subtract_offset = 0; for (auto fit = flags.begin(), end=flags.end(); fit != end; ++fit) { // destroy any overlay bar from previous day delete fit.value(); } flags.clear(); quint32 z = schema::FLAG | schema::MINOR_FLAG | schema::SPAN; if (p_profile->general->showUnknownFlags()) z |= schema::UNKNOWN; QList available = m_day->getSortedMachineChannels(z); for (const auto & code : available) { if (!m_flags_enabled.contains(code)) { bool b = false; if (((m_codes[0] == CPAP_FlowRate) || ((m_codes[0] == CPAP_MaskPressureHi))) && (schema::channel[code].machtype() == MT_CPAP)) b = true; if ((m_codes[0] == CPAP_Leak) && (code == CPAP_LargeLeak)) b = true; m_flags_enabled[code] = b; } if (!m_day->channelExists(code)) continue; schema::Channel * chan = &schema::channel[code]; gLineOverlayBar * lob = nullptr; if (chan->type() == schema::FLAG) { lob = new gLineOverlayBar(code, chan->defaultColor(), chan->label(), FT_Bar); } else if ((chan->type() == schema::MINOR_FLAG) || (chan->type() == schema::UNKNOWN)) { lob = new gLineOverlayBar(code, chan->defaultColor(), chan->label(), FT_Dot); } else if (chan->type() == schema::SPAN) { lob = new gLineOverlayBar(code, chan->defaultColor(), chan->label(), FT_Span); } if (lob != nullptr) { lob->setOverlayDisplayType(((m_codes[0] == CPAP_FlowRate))? (OverlayDisplayType)AppSetting->overlayType() : ODT_TopAndBottom); lob->SetDay(m_day); flags[code] = lob; } } m_dotlines.clear(); for (const auto & code : m_codes) { const schema::Channel & chan = schema::channel[code]; addDotLine(DottedLine(code, Calc_Max,chan.calc[Calc_Max].enabled)); if ((code != CPAP_FlowRate) && (code != CPAP_MaskPressure) && (code != CPAP_MaskPressureHi)) { addDotLine(DottedLine(code, Calc_Perc,chan.calc[Calc_Perc].enabled)); addDotLine(DottedLine(code, Calc_Middle, chan.calc[Calc_Middle].enabled)); } if ((code != CPAP_Snore) && (code != CPAP_FlowLimit) && (code != CPAP_RDI) && (code != CPAP_AHI)) { addDotLine(DottedLine(code, Calc_Min, chan.calc[Calc_Min].enabled)); } } if (m_codes[0] == CPAP_Leak) { addDotLine(DottedLine(CPAP_Leak, Calc_UpperThresh, schema::channel[CPAP_Leak].calc[Calc_UpperThresh].enabled)); } else if (m_codes[0] == CPAP_FlowRate) { addDotLine(DottedLine(CPAP_FlowRate, Calc_Zero, schema::channel[CPAP_FlowRate].calc[Calc_Zero].enabled)); } else if (m_codes[0] == OXI_Pulse) { addDotLine(DottedLine(OXI_Pulse, Calc_UpperThresh, schema::channel[OXI_Pulse].calc[Calc_UpperThresh].enabled)); addDotLine(DottedLine(OXI_Pulse, Calc_LowerThresh, schema::channel[OXI_Pulse].calc[Calc_LowerThresh].enabled)); } else if (m_codes[0] == OXI_SPO2) { addDotLine(DottedLine(OXI_SPO2, Calc_LowerThresh, schema::channel[OXI_SPO2].calc[Calc_LowerThresh].enabled)); } if (m_day) { for (auto & dot : m_dotlines) { dot.calc(m_day); ChannelID code = dot.code; ChannelCalcType type = dot.type; bool b = false; // default value const auto & cit = m_dot_enabled.find(code); if (cit == m_dot_enabled.end()) { m_dot_enabled[code].insert(type, b); } else { const auto & it = cit.value().find(type); if (it == cit.value().end()) { cit.value().insert(type, b); } } } } } EventDataType gLineChart::Miny() { if (m_codes.size() == 0) return 0; if (!m_day) return 0; bool first = false; EventDataType min = 0, tmp; for (const auto code : m_codes) { if (!m_enabled[code] || !m_day->channelExists(code)) continue; tmp = m_day->Min(code); if (!first) { min = tmp; first = true; } else { min = qMin(tmp, min); } } if (!first) min = 0; m_miny = min; return min; } EventDataType gLineChart::Maxy() { if (m_codes.size() == 0) return 0; if (!m_day) return 0; bool first = false; EventDataType max = 0, tmp; for (const auto code : m_codes) { if (!m_enabled[code] || !m_day->channelExists(code)) continue; tmp = m_day->Max(code); if (!first) { max = tmp; first = true; } else { max = qMax(tmp, max); } } if (!first) max = 0; m_maxy = max; return max; // return Layer::Maxy() - subtract_offset; } bool gLineChart::mouseMoveEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) graph->timedRedraw(0); return false; } QString gLineChart::getMetaString(qint64 time) { if (!m_day) return lasttext; lasttext = QString(); EventDataType val; EventDataType ipap = 0, epap = 0; bool addPS = false; for (const auto code : m_codes) { if (m_day->channelHasData(code)) { val = m_day->lookupValue(code, time, m_square_plot); lasttext += " "+QString("%1: %2").arg(schema::channel[code].label()).arg(val,0,'f',2); //.arg(schema::channel[code].units()); if (code == CPAP_IPAP || code == CPAP_IPAPSet) { ipap = val; addPS = true; } if (code == CPAP_EPAP || code == CPAP_EPAPSet) { epap = val; } } } if (addPS) { val = ipap - epap; lasttext += " "+QString("%1: %2").arg(schema::channel[CPAP_PS].label()).arg(val,0,'f',2);//.arg(schema::channel[CPAP_PS].units()); } lasttime = time; return lasttext; } // Time Domain Line Chart void gLineChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { QRectF rect = region.boundingRect(); rect.translate(0.0f, 0.001f); // TODO: Just use QRect directly. int left = rect.left(); int top = rect.top(); int width = rect.width(); int height = rect.height(); if (!m_visible) { return; } if (!m_day) { return; } //if (!m_day->channelExists(m_code)) return; if (width < 0) { return; } top++; double minx, maxx; if (w.blockZoom()) { minx = w.rmin_x, maxx = w.rmax_x; } else { maxx = w.max_x, minx = w.min_x; } // hmmm.. subtract_offset.. EventDataType miny = m_physminy; EventDataType maxy = m_physmaxy; w.roundY(miny, maxy); //#define DEBUG_AUTOSCALER #ifdef DEBUG_AUTOSCALER QString a = QString().sprintf("%.2f - %.2f",miny, maxy); w.renderText(a,width/2,top-5); #endif // the middle of minx and maxy does not have to be the center... double logX = painter.device()->logicalDpiX(); double physX = painter.device()->physicalDpiX(); double ratioX = physX / logX * w.printScaleX(); double logY = painter.device()->logicalDpiY(); double physY = painter.device()->physicalDpiY(); double ratioY = physY / logY * w.printScaleY(); double xx = maxx - minx; double xmult = double(width) / xx; EventDataType yy = maxy - miny; EventDataType ymult = EventDataType(height - 3) / yy; // time to pixel conversion multiplier // Return on screwy min/max conditions if (xx < 0) { return; } if (yy <= 0) { if (miny == 0) { return; } } painter.setRenderHint(QPainter::Antialiasing, AppSetting->antiAliasing()); //bool mouseover = false; if (rect.contains(w.graphView()->currentMousePos())) { //mouseover = true; painter.fillRect(rect, QBrush(QColor(255,255,245,128))); } bool linecursormode = AppSetting->lineCursorMode(); //////////////////////////////////////////////////////////////////////// // Display Line Cursor //////////////////////////////////////////////////////////////////////// if (linecursormode) { double time = w.currentTime(); if ((time > minx) && (time < maxx)) { double xpos = (time - double(minx)) * xmult; painter.setPen(QPen(QBrush(QColor(0,255,0,255)),1)); painter.drawLine(left+xpos, top-w.marginTop()-3, left+xpos, top+height+w.bottom-1); } if ((time != lasttime) || lasttext.isEmpty()) { getMetaString(time); } if (m_codes[0] != CPAP_FlowRate) { QString text = lasttext; int wid, h; GetTextExtent(text, wid, h); w.renderText(text, left , top-6); //(h+(4 * w.printScaleY()))); //+ width/2 - wid/2 } } double lastpx, lastpy; double px, py; int idx; bool done; double x0, xL; double sr = 0.0; int sam; int minz, maxz; // Draw bounding box painter.setPen(QColor(Qt::black)); painter.drawRect(rect); width--; height -= 2; int num_points = 0; int visible_points = 0; int total_points = 0; int total_visible = 0; bool square_plot, accel; qint64 clockdrift = qint64(p_profile->cpap->clockDrift()) * 1000L; qint64 drift = 0; QHash >::iterator ci; //m_line_color=schema::channel[m_code].defaultColor(); int legendx = left + width; int codepoints; painter.setClipRect(left, top, width, height+1); painter.setClipping(true); painter.setFont(*defaultfont); bool showDottedLines = true; // Unset Dotted lines visible status, so we only draw necessary labels later for (auto & dot : m_dotlines) { dot.visible = false; } ChannelID code; float lineThickness = AppSetting->lineThickness()+0.001F; for (int ic = 0; ic < m_codes.count(); ic++) { const auto & code = m_codes[ic]; square_plot = m_square[ic]; // set the mode per-channel const schema::Channel &chan = schema::channel[code]; //////////////////////////////////////////////////////////////////////// // Draw the Channel Threshold dotted lines, and flow waveform centreline //////////////////////////////////////////////////////////////////////// if (showDottedLines) { for (auto & dot : m_dotlines) { if ((dot.code != code) || (!m_dot_enabled[dot.code][dot.type]) || (!dot.available) || (!m_enabled[dot.code])) { continue; } //schema::Channel & chan = schema::channel[code]; dot.visible = true; QColor color = chan.calc[dot.type].color; color.setAlpha(200); painter.setPen(QPen(QBrush(color), lineThickness, Qt::DotLine)); EventDataType y=top + height + 1 - ((dot.value - miny) * ymult); painter.drawLine(left + 1, y, left + 1 + width, y); } } if (!m_enabled[code]) continue; lines.clear(); codepoints = 0; // For each session... for (const auto & sess : m_day->sessions) { if (!sess) { qWarning() << "gLineChart::Plot() nullptr Session Record.. This should not happen"; continue; } drift = (sess->type() == MT_CPAP) ? clockdrift : 0; if (!sess->enabled()) { continue; } schema::Channel ch = schema::channel[code]; bool fndbetter = false; for (const auto & c : ch.m_links) { ci = sess->eventlist.find(c->id()); if (ci != sess->eventlist.end()) { fndbetter = true; break; } } if (!fndbetter) { ci = sess->eventlist.find(code); if (ci == sess->eventlist.end()) { continue; } } num_points = 0; for (const auto & ni : ci.value()) { num_points += ni->count(); } total_points += num_points; codepoints += num_points; // Max number of samples taken from samples per pixel for better min/max values const int num_averages = 20; for (auto & ni : ci.value()) { EventList & el = (*ni); accel = (el.type() == EVL_Waveform); // Turn on acceleration if this is a waveform. if (accel) { sr = el.rate(); // Time distance between samples if (sr <= 0) { qWarning() << "qLineChart::Plot() assert(sr>0)"; continue; } } if (m_disable_accel) { accel = false; } //square_plot = m_square_plot; // now we set this per-channel above if (accel || num_points > 20000) { // Don't square plot if too many points or waveform square_plot = false; } int siz = el.count(); if (siz <= 1) { continue; } // Don't bother drawing 1 point or less. x0 = el.time(0) + drift; xL = el.time(siz - 1) + drift; if (maxx < x0) { continue; } if (xL < minx) { continue; } if (x0 > xL) { if (siz == 2) { // this happens on CPAP quint32 t = el.getTime()[0]; el.getTime()[0] = el.getTime()[1]; el.getTime()[1] = t; EventStoreType d = el.getData()[0]; el.getData()[0] = el.getData()[1]; el.getData()[1] = d; } else { qDebug() << "Reversed order sample fed to gLineChart - ignored."; continue; //assert(x1 0) { // sam = 1; // } if (ZW < num_averages) { sam = 1; accel = false; } else { sam = ZW / num_averages; if (sam < 1) { sam = 1; accel = false; } } // Prepare the min max y values if we still are accelerating this plot if (accel) { for (int i = 0; i < width; i++) { m_drawlist[i].setX(height); m_drawlist[i].setY(0); } minz = width; maxz = 0; } total_visible += visible_points; } else { sam = 1; } // these calculations over estimate // The Z? values are much more accurate idx = 0; if (el.type() == EVL_Waveform) { // We can skip data previous to minx if this is a waveform if (minx > x0) { double j = minx - x0; // == starting min of first sample in this segment idx = (j / sr); //idx/=(sam*num_averages); //idx*=(sam*num_averages); // Loose the precision idx += sam - (idx % sam); } // else just start from the beginning } int xst = left + 1; int yst = top + height + 1; double time; EventDataType data; EventDataType gain = el.gain(); done = false; if (el.type() == EVL_Waveform) { // Waveform Plot if (idx > sam) { idx -= sam; } time = el.time(idx) + drift; double rate = double(sr) * double(sam); EventStoreType *ptr = el.rawData() + idx; if ((unsigned) siz > el.count()) siz = el.count(); if (accel) { ////////////////////////////////////////////////////////////////// // Accelerated Waveform Plot ////////////////////////////////////////////////////////////////// for (int i = idx; i <= siz; i += sam, ptr += sam) { time += rate; // This is much faster than QVector access. data = *ptr * gain; // Scale the time scale X to pixel scale X px = ((time - minx) * xmult); // Same for Y scale, with gain factored in nmult py = ((data - miny) * ymult); // In accel mode, each pixel has a min/max Y value. // m_drawlist's index is the pixel index for the X pixel axis. //int z = round(px); // Hmmm... round may screw this up. int z = (px>=0.5)?(int(px)+1):int(px); if (z < minz) { minz = z; // minz=First pixel } if (z > maxz) { maxz = z; // maxz=Last pixel } if (minz < 0) { qDebug() << "gLineChart::Plot() minz<0 should never happen!! minz =" << minz; minz = 0; } if (maxz > max_drawlist_size) { qDebug() << "gLineChart::Plot() maxz>max_drawlist_size!!!! maxz = " << maxz << " max_drawlist_size =" << max_drawlist_size; maxz = max_drawlist_size; } // Update the Y pixel bounds. if (py < m_drawlist[z].x()) { m_drawlist[z].setX(py); } if (py > m_drawlist[z].y()) { m_drawlist[z].setY(py); } if (time > maxx) { done = true; break; } } // Plot compressed accelerated vertex list if (maxz > width) { maxz = width; } float ax1, ay1; QPointF *drl = m_drawlist + minz; // Don't need to cap VertexBuffer here, as it's limited to max_drawlist_size anyway // Cap within VertexBuffer capacity, one vertex per line point // int np = (maxz - minz) * 2; for (int i = minz; i < maxz; i++, drl++) { ax1 = drl->x(); ay1 = drl->y(); lines.append(QLine(xst + i, yst - ax1, xst + i, yst - ay1)); } } else { // Zoomed in Waveform ////////////////////////////////////////////////////////////////// // Normal Waveform Plot ////////////////////////////////////////////////////////////////// // Prime first point data = (*ptr + el.offset()) * gain; lastpx = xst + ((time - minx) * xmult); lastpy = yst - ((data - miny) * ymult); siz--; for (int i = idx; i < siz; i += sam) { ptr += sam; time += rate; data = (*ptr + el.offset()) * gain; px = xst + ((time - minx) * xmult); // Scale the time scale X to pixel scale X py = yst - ((data - miny) * ymult); // Same for Y scale, with precomputed gain //py=yst-((data - ymin) * nmult); // Same for Y scale, with precomputed gain lines.append(QLine(lastpx, lastpy, px, py)); lastpx = px; lastpy = py; if (time >= maxx) { done = true; break; } } } if (w.printing() && AppSetting->monochromePrinting()) { painter.setPen(QPen(Qt::black, lineThickness + 0.5)); } else { painter.setPen(QPen(chan.defaultColor(), lineThickness)); } painter.drawLines(lines); w.graphView()->lines_drawn_this_frame += lines.count(); lines.clear(); } else { ////////////////////////////////////////////////////////////////// // Standard events/zoomed in Plot ////////////////////////////////////////////////////////////////// double start = el.first() + drift; quint32 *tptr = el.rawTime(); int idx = 0; if (siz > 15) { // Prime a bit... for (; idx < siz; ++idx) { time = start + *tptr++; if (time >= minx) { break; } } if (idx > 0) { idx--; } } // Step one backwards if possible (to draw through the left margin) EventStoreType *dptr = el.rawData() + idx; tptr = el.rawTime() + idx; time = start + *tptr++; data = (*dptr++ + el.offset()) * gain; idx++; lastpx = xst + ((time - minx) * xmult); // Scale the time scale X to pixel scale X lastpy = yst - ((data - miny) * ymult); // Same for Y scale without precomputed gain siz -= idx; // int gs = siz << 1; // if (square_plot) { // gs <<= 1; // } // Unrolling square plot outside of loop to gain a minor speed improvement. EventStoreType *eptr = dptr + siz; if (square_plot) { for (; dptr < eptr; dptr++) { time = start + *tptr++; data = gain * (*dptr + el.offset()); px = xst + ((time - minx) * xmult); // Scale the time scale X to pixel scale X py = yst - ((data - miny) * ymult); // Same for Y scale without precomputed gain // Horizontal lines are easy to cap if (py == lastpy) { // Cap px to left margin if (lastpx < xst) { lastpx = xst; } // Cap px to right margin if (px > xst + width) { px = xst + width; } // lines.append(QLine(lastpx, lastpy, px, lastpy)); // lines.append(QLine(px, lastpy, px, py)); } // else { // Letting the scissor do the dirty work for non horizontal lines // This really should be changed, as it might be cause that weird // display glitch on Linux.. lines.append(QLine(lastpx, lastpy, px, lastpy)); lines.append(QLine(px, lastpy, px, py)); // } lastpx = px; lastpy = py; if (time > maxx) { done = true; // Let this iteration finish.. (This point will be in far clipping) break; } } } else { for (; dptr < eptr; dptr++) { //for (int i=0;i xst + width) { px = xst + width; } // lines.append(QLine(lastpx, lastpy, px, py)); } //else { // Letting the scissor do the dirty work for non horizontal lines // This really should be changed, as it might be cause that weird // display glitch on Linux.. lines.append(QLine(lastpx, lastpy, px, py)); //} lastpx = px; lastpy = py; if (time > maxx) { // Past right edge, abort further drawing.. done = true; break; } } } if (w.printing() && AppSetting->monochromePrinting()) { painter.setPen(QPen(Qt::black, lineThickness + 0.5)); } else { painter.setPen(QPen(chan.defaultColor(), lineThickness)); } painter.drawLines(lines); w.graphView()->lines_drawn_this_frame+=lines.count(); lines.clear(); } if (done) { break; } } } // painter.setPen(QPen(m_colors[gi],lineThickness)); // painter.drawLines(lines); // w.graphView()->lines_drawn_this_frame+=lines.count(); // lines.clear(); //////////////////////////////////////////////////////////////////// // Draw Legends on the top line //////////////////////////////////////////////////////////////////// if ((codepoints > 0)) { QString text = schema::channel[code].label(); QRectF rec(0, rect.top()-3, 0,0); rec = painter.boundingRect(rec, Qt::AlignBottom | Qt::AlignLeft, text); rec.moveRight(legendx); legendx -= rec.width(); painter.setClipping(false); painter.setPen(Qt::black); painter.drawText(rec, Qt::AlignBottom | Qt::AlignRight, text); float ps = 2.0 * ratioY; ps = qMax(ps, 1.0f); painter.setPen(QPen(chan.defaultColor(), ps)); int linewidth = (10 * ratioX); int yp = rec.top()+(rec.height()/2); painter.drawLine(QLineF(rec.left()-linewidth, yp+0.001f , rec.left()-(2 * ratioX), yp)); painter.setClipping(true); legendx -= linewidth + (2*ratioX); } } painter.setClipping(false); //////////////////////////////////////////////////////////////////// // Draw Channel Threshold legend markers //////////////////////////////////////////////////////////////////// for (const auto & dot : m_dotlines) { if (!dot.visible) continue; code = dot.code; schema::Channel &chan = schema::channel[code]; int linewidth = (10 * ratioX); QRectF rec(0, rect.top()-3, 0,0); QString text = chan.calc[dot.type].label(); rec = painter.boundingRect(rec, Qt::AlignBottom | Qt::AlignLeft, text); rec.moveRight(legendx); legendx -= rec.width(); painter.setPen(Qt::black); painter.drawText(rec, Qt::AlignBottom | Qt::AlignRight, text); QColor color = chan.calc[dot.type].color; color.setAlpha(200); float ps = 2.0 * ratioY; ps = qMax(ps, 1.0f); painter.setPen(QPen(QBrush(color), ps,Qt::DotLine)); int yp = rec.top()+(rec.height()/2); painter.drawLine(QLineF(rec.left()-linewidth, yp+0.001f, rec.left()-(2 * ratioX), yp)); legendx -= linewidth + (2*ratioX); } painter.setClipping(true); if (!total_points) { // No Data? QString msg = QObject::tr("Plots Disabled"); int x, y; GetTextExtent(msg, x, y, bigfont); w.renderText(msg, rect, Qt::AlignCenter, 0, Qt::gray, bigfont); } painter.setClipping(false); // Calculate combined session times within selected area... double first, last; double time = 0; // Calculate the CPAP session time. for (auto & sess : m_day->sessions) { if (!sess->enabled() || (sess->type() != MT_CPAP)) continue; first = sess->first(); last = sess->last(); if (last < w.min_x) { continue; } if (first > w.max_x) { continue; } if (first < w.min_x) { first = w.min_x; } if (last > w.max_x) { last = w.max_x; } time += last - first; } time /= 1000; QList ahilist; for (int i = 0; i < ahiChannels.size(); i++) ahilist.push_back(ahiChannels.at(i)); // ahilist.push_back(CPAP_Hypopnea); // ahilist.push_back(CPAP_AllApnea); // ahilist.push_back(CPAP_Obstructive); // ahilist.push_back(CPAP_Apnea); // ahilist.push_back(CPAP_ClearAirway); QList extras; extras.push_back(CPAP_NRI); extras.push_back(CPAP_UserFlag1); extras.push_back(CPAP_UserFlag2); double sum = 0; int cnt = 0; // Draw the linechart overlays if (m_day && (AppSetting->lineCursorMode() || (m_codes[0]==CPAP_FlowRate))) { bool blockhover = false; for (auto fit=flags.begin(), end=flags.end(); fit != end; ++fit) { code = fit.key(); if ((!m_flags_enabled[code]) || (!m_day->channelExists(code))) continue; gLineOverlayBar * lob = fit.value(); lob->setBlockHover(blockhover); lob->paint(painter, w, region); if (lob->hover()) blockhover = true; // did it render a hover over? if (ahilist.contains(code)) { sum += lob->sum(); cnt += lob->count(); } } } if (m_codes[0] == CPAP_FlowRate) { float hours = time / 3600.0; int h = time / 3600; int m = int(time / 60) % 60; int s = int(time) % 60; double f = double(cnt) / hours; // / (sum / 3600.0); QString txt = QObject::tr("Duration %1:%2:%3").arg(h,2,10,QChar('0')).arg(m,2,10,QChar('0')).arg(s,2,10,QChar('0')) + " "+ QObject::tr("AHI %1").arg(f,0,'f',2);// +" " + // QObject::tr("Events %1").arg(cnt) + " " + // QObject::tr("Hours %1").arg(hours,0,'f',2); if (linecursormode) txt+=lasttext; w.renderText(txt,left,top-5); } // painter.setRenderHint(QPainter::Antialiasing, false); } OSCAR-code-v1.3.1/oscar/Graphs/gLineChart.h000066400000000000000000000141771417327530600202270ustar00rootroot00000000000000/* gLineChart Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GLINECHART_H #define GLINECHART_H #include #include #include "Graphs/layer.h" #include "SleepLib/event.h" #include "SleepLib/day.h" #include "Graphs/gLineOverlay.h" enum DottedLineCalc { DLC_Zero, DLC_Min, DLC_Mid, DLC_Perc, DLC_Max, DLC_UpperThresh, DLC_LowerThresh }; QColor darken(QColor color, float p = 0.5); struct DottedLine { public: DottedLine() { code = NoChannel; type = Calc_Zero; value = 0; visible = false; available = false; } DottedLine(const DottedLine & copy) { code = copy.code; type = copy.type; value = copy.value; available = copy.available; visible = copy.visible; } DottedLine(ChannelID code, ChannelCalcType type, bool available = false): code(code), type(type), available(available) {} EventDataType calc(Day * day) { if (day == nullptr) { qWarning() << "DottedLine::calc called with null day object"; return 0; } available = day->channelExists(code); value = day->calc(code, type); return value; } DottedLine& operator=(const DottedLine& other) = default; ChannelID code; ChannelCalcType type; EventDataType value; bool visible; bool available; }; QDataStream & operator<<(QDataStream &, const DottedLine &); QDataStream & operator>>(QDataStream &, DottedLine &); /*! \class gLineChart \brief Draws a 2D linechart from all Session data in a day. EVL_Waveforms typed EventLists are accelerated. */ class gLineChart: public Layer { friend class gGraphView; public: /*! \brief Creates a new 2D gLineChart Layer \param code The Channel that gets drawn by this layer \param square_plot Whether or not to use square plots (only effective for EVL_Event typed EventList data) \param disable_accel Whether or not to disable acceleration for EVL_Waveform typed EventList data */ gLineChart(ChannelID code, bool square_plot = false, bool disable_accel = false); virtual ~gLineChart(); //! \brief The drawing code that fills the vertex buffers virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); //! \brief Set Use Square plots for non EVL_Waveform data void SetSquarePlot(bool b) { m_square_plot = b; } //! \brief Returns true if using Square plots for non EVL_Waveform data bool GetSquarePlot() { return m_square_plot; } //! \brief Set this if you want this layer to draw it's empty data message //! They don't show anymore due to the changes in gGraphView. Should modify isEmpty if this still gets to live void ReportEmpty(bool b) { m_report_empty = b; } //! \brief Returns whether or not to show the empty text message bool GetReportEmpty() { return m_report_empty; } //! \brief Sets the ability to Disable waveform plot acceleration (which hides unseen data) void setDisableAccel(bool b) { m_disable_accel = b; } //! \brief Returns true if waveform plot acceleration is disabled bool disableAccel() { return m_disable_accel; } //! \brief Sets the Day object containing the Sessions this linechart draws from virtual void SetDay(Day *d); //! \brief Returns Minimum Y-axis value for this layer virtual EventDataType Miny(); //! \brief Returns Maximum Y-axis value for this layer virtual EventDataType Maxy(); //! \brief Returns true if all subplots contain no data virtual bool isEmpty(); //! \brief Add Subplot 'code'. Note the first one is added in the constructor. void addPlot(ChannelID code, bool square=false) { m_codes.push_back(code); m_enabled[code] = true; m_square.push_back(square); } //! \brief Returns true of the subplot 'code' is enabled. bool plotEnabled(ChannelID code) { if ((m_enabled.contains(code)) && m_enabled[code]) { return true; } else { return false; } } //! \brief Enable or Disable the subplot identified by code. void setPlotEnabled(ChannelID code, bool b) { m_enabled[code] = b; } QString getMetaString(qint64 time); void addDotLine(DottedLine dot) { m_dotlines.append(dot); } QVector m_dotlines; QHash m_flags_enabled; QHash > m_dot_enabled; virtual Layer * Clone() { gLineChart * lc = new gLineChart(m_code); Layer::CloneInto(lc); CloneInto(lc); return lc; } void CloneInto(gLineChart * layer) { layer->m_dotlines = m_dotlines; layer->m_flags_enabled = m_flags_enabled; layer->m_dot_enabled = m_dot_enabled; layer->m_report_empty = m_report_empty; layer->m_square_plot = m_square_plot; layer->m_disable_accel = m_disable_accel; layer->subtract_offset = subtract_offset; layer->m_codes = m_codes; layer->m_threshold = m_threshold; layer->m_square = m_square; layer->m_enabled = m_enabled; layer->lines = lines; layer->lasttext = lasttext; layer->lasttime = lasttime; } protected: //! \brief Mouse moved over this layers area (shows the hover-over tooltips here) virtual bool mouseMoveEvent(QMouseEvent *event, gGraph *graph); bool m_report_empty; bool m_square_plot; bool m_disable_accel; //! \brief Used by accelerated waveform plots. Must be >= Screen Resolution (or at least graph width) static const int max_drawlist_size = 10000; //! \brief The list of screen points used for accelerated waveform plots.. QPointF m_drawlist[max_drawlist_size]; int subtract_offset; QVector m_codes; QStringList m_threshold; QVector m_square; QHash m_enabled; // plot enabled QHash flags; QVector lines; QString lasttext; qint64 lasttime; }; #endif // GLINECHART_H OSCAR-code-v1.3.1/oscar/Graphs/gLineOverlay.cpp000066400000000000000000000310321417327530600211270ustar00rootroot00000000000000/* gLineOverlayBar Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include "SleepLib/profiles.h" #include "gLineOverlay.h" gLineOverlayBar::gLineOverlayBar(ChannelID code, QColor color, QString label, FlagType flt) : Layer(code), m_flag_color(color), m_label(label), m_flt(flt), m_odt(ODT_TopAndBottom) { m_hover = false; m_blockhover = false; } gLineOverlayBar::~gLineOverlayBar() { } QColor brighten(QColor, float); void gLineOverlayBar::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { m_hover = false; if (!schema::channel[m_code].enabled()) return; int left = region.boundingRect().left(); int topp = region.boundingRect().top(); // FIXME: Misspelling intentional. double width = region.boundingRect().width(); int height = region.boundingRect().height(); if (!m_visible) { return; } if (!m_day) { return; } int start_py = topp; double xx = w.max_x - w.min_x; //double yy = w.max_y - w.min_y; if (xx <= 0) { return; } double jj = width / xx; double x1, x2; int x, y; float bottom = start_py + height - 25 * w.printScaleY(), top = start_py + 25 * w.printScaleY(); qint64 X; qint64 Y; QPoint mouse=w.graphView()->currentMousePos(); m_count = 0; m_sum = 0; m_flag_color = schema::channel[m_code].defaultColor(); if (m_flt == FT_Span) { m_flag_color.setAlpha(128); } painter.setPen(m_flag_color); EventStoreType raw; quint32 *tptr; EventStoreType *dptr, *eptr; qint64 stime; OverlayDisplayType odt = m_odt; QHash >::iterator cei; int count; qint64 clockdrift = qint64(p_profile->cpap->clockDrift()) * 1000L; qint64 drift = 0; //bool hover = false; int tooltipTimeout = AppSetting->tooltipTimeout(); // For each session, process it's eventlist for (const auto sess : m_day->sessions) { if (!sess->enabled()) { continue; } cei = sess->eventlist.find(m_code); if (cei == sess->eventlist.end()) { continue; } if (cei.value().size() == 0) { continue; } drift = (sess->type() == MT_CPAP) ? clockdrift : 0; // Could loop through here, but nowhere uses more than one yet.. for (const auto & el : cei.value()) { count = el->count(); stime = el->first() + drift; dptr = el->rawData(); eptr = dptr + count; tptr = el->rawTime(); //////////////////////////////////////////////////////////////////////////// // Skip data previous to minx bounds //////////////////////////////////////////////////////////////////////////// for (; dptr < eptr; ++dptr) { if ((stime + *tptr) >= w.min_x) { break; } ++tptr; } if (m_flt == FT_Span) { //////////////////////////////////////////////////////////////////////////// // FT_Span //////////////////////////////////////////////////////////////////////////// QBrush brush(m_flag_color); for (; dptr < eptr; dptr++) { X = stime + *tptr++; raw = *dptr; Y = X - (qint64(raw) * 1000.0L); // duration if (Y > w.max_x) { break; } m_sum += raw; ++m_count; x1 = jj * double(X - w.min_x); x2 = jj * double(Y - w.min_x); x2 = qMax(0.0, x2)+left; x1 = qMin(width, x1)+left; // x2 represents the begining of a span in pixels // x1 represent the end of the span in pixels // BUG HERE //x2 += (int(x1)==int(x2)) ? 1 : 0; // Fixed BY int duration = x1-x2; if (duration<2) duration=2; // display minial span with 2 pixels. x2 =x1-duration; painter.fillRect(QRect(x2, start_py, duration, height), brush); } }/* else if (m_flt == FT_Dot) { //////////////////////////////////////////////////////////////////////////// // FT_Dot //////////////////////////////////////////////////////////////////////////// for (; dptr < eptr; dptr++) { hover = false; X = stime + *tptr++; //el.time(i); raw = *dptr; //el.data(i); if (X > w.max_x) { break; } x1 = jj * double(X - w.min_x) + left; m_count++; m_sum += raw; // if ((odt == ODT_Bars) || (xx < 3600000)) { // show the fat dots in the middle painter.setPen(QPen(m_flag_color,4)); painter.drawPoint(x1, double(height) / double(yy)*double(-20 - w.min_y) + topp); painter.setPen(QPen(m_flag_color,1)); // } else { // // thin lines down the bottom // painter.drawLine(x1, start_py + 1, x1, start_py + 1 + 12); // } } } */else if ((m_flt == FT_Bar) || (m_flt == FT_Dot)) { //////////////////////////////////////////////////////////////////////////// // FT_Bar //////////////////////////////////////////////////////////////////////////// QColor col = m_flag_color; QString lab = QString("%1").arg(m_label); GetTextExtent(lab, x, y); //int lx,ly; for (; dptr < eptr; dptr++) { // hover = false; X = stime + *tptr++; raw = *dptr; if (X > w.max_x) { break; } x1 = jj * double(X - w.min_x) + left; m_count++; m_sum += raw; int z = start_py + height; double d1 = jj * double(raw) * 1000.0; if ((m_flt == FT_Bar) && (odt == ODT_Bars)) { QRect rect(x1-d1, top, d1+4, height); painter.setPen(QPen(col,4)); painter.drawPoint(x1, top); if (!w.selectingArea() && !m_blockhover && rect.contains(mouse) && !m_hover) { m_hover = true; QColor col2(230,230,230,128); QRect rect((x1-d1), start_py+2, d1, height-2); if (rect.x() < left) { rect.setX(left); } painter.fillRect(rect, QBrush(col2)); painter.setPen(col); painter.drawRect(rect); // Queue tooltip QString strEventTooltip = QString("%1").arg(schema::channel[m_code].fullname()); if (raw != 0) // Hide duration when it is zero strEventTooltip += QString(" (%1)").arg(raw); w.ToolTip(strEventTooltip, x1 - 10, start_py + 24 + (3 * w.printScaleY()), TT_AlignRight, AppSetting->tooltipTimeout()); painter.setPen(QPen(col,3)); } else { painter.setPen(QPen(col,1)); } painter.drawLine(x1, top, x1, bottom); if (xx < (3600000)) { w.renderText(lab, x1 - (x / 2), top - y + (5 * w.printScaleY()),0); } } else { ////////////////////////////////////////////////////////////////////////////////////// // Top and bottom markers ////////////////////////////////////////////////////////////////////////////////////// //bool b = false; if (!w.selectingArea() && !m_blockhover && QRect(x1-2, topp, 6, height).contains(mouse) && !m_hover) { // only want to draw the highlight/label once per frame m_hover = true; // Draw text label QString lab = QString("%1 (%2)").arg(schema::channel[m_code].fullname()).arg(raw); GetTextExtent(lab, x, y, defaultfont); w.ToolTip(lab, x1 - 10, start_py + 24 + (3 * w.printScaleY()), TT_AlignRight, tooltipTimeout); QColor col = m_flag_color; col.setAlpha(60); painter.setPen(QPen(col, 4)); painter.drawLine(x1, start_py+14, x1, z - 12); painter.setPen(QPen(m_flag_color,4)); painter.drawLine(x1, z, x1, z - 14); painter.drawLine(x1, start_py+2, x1, start_py + 16); } else { QColor col = m_flag_color; col.setAlpha(10); painter.setPen(QPen(col,1)); painter.drawLine(x1, start_py+14, x1, z); painter.setPen(QPen(m_flag_color,1)); painter.drawLine(x1, start_py+2, x1, start_py + 14); } } } } } } } bool gLineOverlayBar::mouseMoveEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) return false; } gLineOverlaySummary::gLineOverlaySummary(QString text, int x, int y) : Layer(CPAP_Obstructive), m_text(text), m_x(x), m_y(y) // The Layer code is a dummy here. { } gLineOverlaySummary::~gLineOverlaySummary() { } void gLineOverlaySummary::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { Q_UNUSED(painter) int left = region.boundingRect().left(); int top = region.boundingRect().top(); int width = region.boundingRect().width(); int height = region.boundingRect().height(); if (!m_visible) { return; } if (!m_day) { return; } Q_UNUSED(width); Q_UNUSED(height); float cnt = 0; double sum = 0; bool isSpan = false; for (const auto & lobar : m_overlays) { cnt += lobar->count(); sum += lobar->sum(); if (lobar->flagtype() == FT_Span) { isSpan = true; } } double val, first, last; double time = 0; // Calculate the session time. for (const auto & sess : m_day->sessions) { if (!sess->enabled()) { continue; } first = sess->first(); last = sess->last(); if (last < w.min_x) { continue; } if (first > w.max_x) { continue; } if (first < w.min_x) { first = w.min_x; } if (last > w.max_x) { last = w.max_x; } time += last - first; } val = 0; time /= 1000; int h = time / 3600; int m = int(time / 60) % 60; int s = int(time) % 60; time /= 3600; //if (time<1) time=1; if (time > 0) { val = cnt / time; } QString a; if (0) { //(w.graphView()->selectionInProgress())) { // || w.graphView()->metaSelect()) && (!w.selDurString().isEmpty())) { a = QObject::tr("Duration")+": "+w.selDurString(); } else { a = QObject::tr("Events") + ": " + QString::number(cnt) + ", " + QObject::tr("Duration") + " " + QString().sprintf("%02i:%02i:%02i", h, m, s) + ", " + m_text + ": " + QString::number(val, 'f', 2); } if (isSpan) { float sph; if (!time) { sph = 0; } else { sph = (100.0 / float(time)) * (sum / 3600.0); if (sph > 100) { sph = 100; } } // eg: %num of time in a span, like Periodic Breathing a += " " + QObject::tr("(% %1 in events)").arg(sph, 0, 'f', 2); } w.renderText(a, left + m_x, top + m_y+2); } OSCAR-code-v1.3.1/oscar/Graphs/gLineOverlay.h000066400000000000000000000055421417327530600206030ustar00rootroot00000000000000/* gLineOverlayBar Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GLINEOVERLAY_H #define GLINEOVERLAY_H #include "SleepLib/common.h" #include "gGraphView.h" /*! \class gLineOverlayBar \brief Shows a flag line, a dot, or a span over the top of a 2D line chart. */ class gLineOverlayBar: public Layer { public: //! \brief Constructs a gLineOverlayBar object of type code, setting the flag/span color, the label to show when zoomed //! in, and the display method requested, of either FT_Bar, FT_Span, or FT_Dot gLineOverlayBar(ChannelID code, QColor col, QString _label = "", FlagType _flt = FT_Bar); virtual ~gLineOverlayBar(); //! \brief The drawing code that fills the OpenGL vertex GLBuffers virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); virtual EventDataType Miny() { return 0; } virtual EventDataType Maxy() { return 0; } //! \brief Returns true if no Channel data available for this code virtual bool isEmpty() { return true; } //! \brief returns a count of all flags drawn during render. (for gLineOverlaySummary) int count() { return m_count; } double sum() { return m_sum; } FlagType flagtype() { return m_flt; } bool hover() { return m_hover; } void setBlockHover(bool b) { m_blockhover = b; } inline void setOverlayDisplayType(OverlayDisplayType odt) { m_odt = odt; } inline OverlayDisplayType overlayDisplayType() { return m_odt; } protected: //! \brief Mouse moved over this layers area (shows the hover-over tooltips here) virtual bool mouseMoveEvent(QMouseEvent *event, gGraph *graph); QColor m_flag_color; QString m_label; FlagType m_flt; OverlayDisplayType m_odt; int m_count; double m_sum; bool m_hover; bool m_blockhover; }; /*! \class gLineOverlaySummary \brief A container class to hold a group of gLineOverlayBar's, and shows the "Section AHI" over the Flow Rate waveform. */ class gLineOverlaySummary: public Layer { public: gLineOverlaySummary(QString text, int x, int y); virtual ~gLineOverlaySummary(); virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); virtual EventDataType Miny() { return 0; } virtual EventDataType Maxy() { return 0; } //! \brief Returns true if no Channel data available for this code virtual bool isEmpty() { return true; } //! \brief Adds a gLineOverlayBar to this list gLineOverlayBar *add(gLineOverlayBar *bar) { m_overlays.push_back(bar); return bar; } protected: QVector m_overlays; QString m_text; int m_x, m_y; }; #endif // GLINEOVERLAY_H OSCAR-code-v1.3.1/oscar/Graphs/gPressureChart.cpp000066400000000000000000000162371417327530600215020ustar00rootroot00000000000000/* gPressureChart Implementation * * Copyright (c) 2020-2022 The Oscar Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "gPressureChart.h" gPressureChart::gPressureChart() : gSummaryChart("Pressure", MT_CPAP) { addCalc(CPAP_Pressure, ST_SETMAX); addCalc(CPAP_Pressure, ST_MID); addCalc(CPAP_Pressure, ST_90P); addCalc(CPAP_PressureMin, ST_SETMIN); addCalc(CPAP_PressureMax, ST_SETMAX); addCalc(CPAP_EPAP, ST_SETMAX); addCalc(CPAP_IPAP, ST_SETMAX); addCalc(CPAP_EPAPLo, ST_SETMAX); addCalc(CPAP_IPAPHi, ST_SETMAX); addCalc(CPAP_EPAP, ST_MID); addCalc(CPAP_EPAP, ST_90P); addCalc(CPAP_IPAP, ST_MID); addCalc(CPAP_IPAP, ST_90P); // PRS1 reports pressure adjustments instead of observed pressures on some machines addCalc(CPAP_PressureSet, ST_MID); addCalc(CPAP_PressureSet, ST_90P); addCalc(CPAP_EPAPSet, ST_MID); addCalc(CPAP_EPAPSet, ST_90P); addCalc(CPAP_IPAPSet, ST_MID); addCalc(CPAP_IPAPSet, ST_90P); } int gPressureChart::addCalc(ChannelID code, SummaryType type) { QColor color = schema::channel[code].defaultColor(); if (type == ST_90P) { color = brighten(color, 1.33f); } int index = gSummaryChart::addCalc(code, type, color); // Save the code and type used to add this calculation so that getCalc() // can retrieve it by code and type instead of by hard-coded index. m_calcs[code][type] = index; return index; } SummaryCalcItem* gPressureChart::getCalc(ChannelID code, SummaryType type) { return &calcitems[m_calcs[code][type]]; } void gPressureChart::afterDraw(QPainter &, gGraph &graph, QRectF rect) { QStringList presstr; if (getCalc(CPAP_Pressure)->cnt > 0) { presstr.append(channelRange(CPAP_Pressure, STR_TR_CPAP)); } if (getCalc(CPAP_PressureMin, ST_SETMIN)->cnt > 0) { // TODO: If using machines from different manufacturers in an overview, // the below may not accurately find the APAP pressure channel for all // days; but it only affects the summary label at the top. ChannelID pressure = CPAP_Pressure; if (getCalc(CPAP_PressureSet, ST_MID)->cnt > 0) { pressure = CPAP_PressureSet; } presstr.append(QString("%1 %2/%3/%4/%5"). arg(STR_TR_APAP). arg(getCalc(CPAP_PressureMin, ST_SETMIN)->min,0,'f',1). arg(getCalc(pressure, ST_MID)->mid(), 0, 'f', 1). arg(getCalc(pressure, ST_90P)->mid(),0,'f',1). arg(getCalc(CPAP_PressureMax, ST_SETMAX)->max, 0, 'f', 1)); } if (getCalc(CPAP_EPAP)->cnt > 0) { // See CPAP_PressureSet note above. ChannelID epap = CPAP_EPAP; if (getCalc(CPAP_EPAPSet, ST_MID)->cnt > 0) { epap = CPAP_EPAPSet; } presstr.append(channelRange(epap, STR_TR_EPAP)); } if (getCalc(CPAP_IPAP)->cnt > 0) { // See CPAP_PressureSet note above. ChannelID ipap = CPAP_IPAP; if (getCalc(CPAP_IPAPSet, ST_MID)->cnt > 0) { ipap = CPAP_IPAPSet; } presstr.append(channelRange(ipap, STR_TR_IPAP)); } if (getCalc(CPAP_EPAPLo)->cnt > 0) { presstr.append(channelRange(CPAP_EPAPLo, STR_TR_EPAPLo)); } if (getCalc(CPAP_IPAPHi)->cnt > 0) { presstr.append(channelRange(CPAP_IPAPHi, STR_TR_IPAPHi)); } QString txt = presstr.join(" "); graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0); } QString gPressureChart::channelRange(ChannelID code, const QString & label) { SummaryCalcItem* calc = getCalc(code); return QString("%1 %2/%3/%4"). arg(label). arg(calc->min, 0, 'f', 1). arg(calc->mid(), 0, 'f', 1). arg(calc->max, 0, 'f', 1); } void gPressureChart::addSlice(ChannelID code, SummaryType type) { float value = 0; QString label; switch (type) { case ST_SETMIN: value = m_day->settings_min(code); label = schema::channel[code].label(); break; case ST_SETMAX: value = m_day->settings_max(code); label = schema::channel[code].label(); break; case ST_MID: value = m_day->calcMiddle(code); label = m_day->calcMiddleLabel(code); break; case ST_90P: value = m_day->calcPercentile(code); label = m_day->calcPercentileLabel(code); break; default: qWarning() << "Unsupported summary type in gPressureChart"; break; } SummaryCalcItem* calc = getCalc(code, type); float height = value - m_height; m_slices->append(SummaryChartSlice(calc, value, height, label, calc->color)); m_height += height; } void gPressureChart::populate(Day * day, int idx) { CPAPMode mode = (CPAPMode)(int)qRound(day->settings_wavg(CPAP_Mode)); m_day = day; m_slices = &cache[idx]; m_height = 0; if (mode == MODE_CPAP) { addSlice(CPAP_Pressure); } else if (mode == MODE_APAP) { addSlice(CPAP_PressureMin, ST_SETMIN); if (!day->summaryOnly()) { // Handle PRS1 pressure adjustments reported separately from average (EPAP) pressure ChannelID pressure = CPAP_Pressure; if (m_day->channelHasData(CPAP_PressureSet)) { pressure = CPAP_PressureSet; } addSlice(pressure, ST_MID); addSlice(pressure, ST_90P); } addSlice(CPAP_PressureMax, ST_SETMAX); } else if (mode == MODE_BILEVEL_FIXED) { addSlice(CPAP_EPAP); addSlice(CPAP_IPAP); } else if (mode == MODE_BILEVEL_AUTO_FIXED_PS) { addSlice(CPAP_EPAPLo); if (!day->summaryOnly()) { addSlice(CPAP_EPAP, ST_MID); addSlice(CPAP_EPAP, ST_90P); addSlice(CPAP_IPAP, ST_MID); addSlice(CPAP_IPAP, ST_90P); } addSlice(CPAP_IPAPHi); } else if ((mode == MODE_BILEVEL_AUTO_VARIABLE_PS) || (mode == MODE_ASV_VARIABLE_EPAP)) { addSlice(CPAP_EPAPLo); if (!day->summaryOnly()) { // Handle PRS1 pressure adjustments when reported instead of observed pressures ChannelID epap = CPAP_EPAP; if (m_day->channelHasData(CPAP_EPAPSet)) { epap = CPAP_EPAPSet; } ChannelID ipap = CPAP_IPAP; if (m_day->channelHasData(CPAP_IPAPSet)) { ipap = CPAP_IPAPSet; } addSlice(epap, ST_MID); addSlice(epap, ST_90P); addSlice(ipap, ST_MID); addSlice(ipap, ST_90P); } addSlice(CPAP_IPAPHi); } else if (mode == MODE_ASV) { addSlice(CPAP_EPAP); if (!day->summaryOnly()) { addSlice(CPAP_IPAP, ST_MID); addSlice(CPAP_IPAP, ST_90P); } addSlice(CPAP_IPAPHi); } else if (mode == MODE_AVAPS) { addSlice(CPAP_EPAP); if (!day->summaryOnly()) { addSlice(CPAP_IPAP, ST_MID); addSlice(CPAP_IPAP, ST_90P); } addSlice(CPAP_IPAPHi); } } OSCAR-code-v1.3.1/oscar/Graphs/gPressureChart.h000066400000000000000000000034321417327530600211400ustar00rootroot00000000000000/* gPressureChart Header * * Copyright (c) 2020-2022 The Oscar Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GPRESSURECHART_H #define GPRESSURECHART_H #include "gSessionTimesChart.h" class gPressureChart : public gSummaryChart { public: gPressureChart(); virtual ~gPressureChart() {} virtual Layer * Clone() { gPressureChart * sc = new gPressureChart(); gSummaryChart::CloneInto(sc); return sc; } // virtual void preCalc(); virtual void customCalc(Day *day, QVector &slices) { int size = slices.size(); float hour = day->hours(m_machtype); for (int i=0; i < size; ++i) { SummaryChartSlice & slice = slices[i]; SummaryCalcItem * calc = slices[i].calc; calc->update(slice.value, hour); } } virtual void afterDraw(QPainter &, gGraph &, QRectF); virtual void populate(Day * day, int idx); virtual QString tooltipData(Day * day, int idx) { return day->getCPAPModeStr() + "\n" + day->getPressureSettings() + gSummaryChart::tooltipData(day, idx); } virtual int addCalc(ChannelID code, SummaryType type); protected: SummaryCalcItem* getCalc(ChannelID code, SummaryType type = ST_SETMAX); QString channelRange(ChannelID code, const QString & label); void addSlice(ChannelID code, SummaryType type = ST_SETMAX); QHash> m_calcs; // State passed between populate() and addSlice(): Day* m_day; QVector* m_slices; float m_height; }; #endif // GPRESSURECHART_H OSCAR-code-v1.3.1/oscar/Graphs/gSegmentChart.cpp000066400000000000000000000225261417327530600212720ustar00rootroot00000000000000/* gSegmentChart Implementation * * Copyright (c) 2011-2018 Mark Watkins * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include "gSegmentChart.h" gSegmentChart::gSegmentChart(GraphSegmentType type, QColor gradient_color, QColor outline_color) : Layer(NoChannel), m_graph_type(type), m_gradient_color(gradient_color), m_outline_color(outline_color) { m_empty = true; } gSegmentChart::~gSegmentChart() { } void gSegmentChart::AddSlice(ChannelID code, QColor color, QString name) { m_codes.push_back(code); m_values.push_back(0); m_colors.push_back(color); m_names.push_back(name); m_total = 0; } void gSegmentChart::SetDay(Day *d) { Layer::SetDay(d); m_total = 0; if (!m_day) { return; } for (int c = 0; c < m_codes.size(); c++) { auto & mval = m_values[c]; auto & mcode = m_codes[c]; mval = 0; for (const auto & sess : m_day->sessions) { if (sess->enabled() && sess->m_cnt.contains(mcode)) { EventDataType cnt = sess->count(mcode); mval += cnt; m_total += cnt; } } } m_empty = true; for (const auto & mc : m_codes) { if (m_day->count(mc) > 0) { m_empty = false; break; } } } bool gSegmentChart::isEmpty() { return m_empty; } void gSegmentChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { QRect rect = region.boundingRect(); int height = qMin(rect.height(), rect.width()); int width = qMin(rect.height(), rect.width()); int left = rect.left(); int top = rect.top(); if (rect.width() > rect.height()) { left = rect.left() + (rect.width() - rect.height()); } //left --; if (!m_visible) { return; } if (!m_day) { return; } int start_px = left; int start_py = top; width--; float diameter = MIN(width, height); diameter -= 8; float radius = diameter / 2.0; float xmult = float(width) / float(m_total); float ymult = float(height) / float(m_total); float xp = left; int xoffset = width / 2; int yoffset = height / 2; if (m_total == 0) { QColor col = Qt::green; QString a = ":-)"; int x, y; GetTextExtent(a, x, y, bigfont); w.renderText(a, start_px + xoffset - x / 2, (start_py + yoffset + y / 2), 0, col, bigfont); return; } EventDataType data; unsigned size = m_values.size(); float line_step = float(width) / float(size - 1); bool line_first = true; int line_last; float sum = -90.0; painter.setFont(*defaultfont); for (unsigned m = 0; m < size; m++) { data = m_values[m]; if (data == 0) { continue; } ///////////////////////////////////////////////////////////////////////////////////// // Pie Chart ///////////////////////////////////////////////////////////////////////////////////// if (m_graph_type == GST_Pie) { QColor col = schema::channel[m_codes[m]].defaultColor(); if (w.printing() && AppSetting->monochromePrinting()) { col = Qt::white; } // length of this segment in degrees float len = 360.0 / m_total * data; // Setup the shiny radial gradient painter.setRenderHint(QPainter::Antialiasing); QRect pierect(start_px+1, start_py+1, width-2, height-2); painter.setPen(QPen(col, 0)); QRadialGradient gradient(pierect.center(), pierect.width()/2, pierect.center()); gradient.setColorAt(0, Qt::white); gradient.setColorAt(1, col); // draw filled pie painter.setBrush(gradient); painter.setBackgroundMode(Qt::OpaqueMode); if (m_total == data) { painter.drawEllipse(pierect); } else { painter.drawPie(pierect, -sum * 16.0, -len * 16.0); } // draw outline painter.setBackgroundMode(Qt::TransparentMode); painter.setBrush(QBrush(col,Qt::NoBrush)); painter.setPen(QPen(QColor(Qt::black),1.5)); if (m_total == data) { painter.drawEllipse(pierect); } else { painter.drawPie(pierect, -sum * 16.0, -len * 16.0); } // Draw text labels if they fit if (len > 20) { float angle = (sum+90.0) + len / 2.0; double tpx = (pierect.x() + pierect.width()/2) + (sin((180 - angle) * (M_PI / 180.0)) * (radius / 1.65)); double tpy = (pierect.y() + pierect.height()/2) + (cos((180 - angle) * (M_PI / 180.0)) * (radius / 1.65)); QString txt = schema::channel[m_codes[m]].label(); //QString::number(floor(100.0/m_total*data),'f',0)+"%"; int x, y; GetTextExtent(txt, x, y); // antialiasing looks like crap here.. painter.setPen(QColor(Qt::black)); painter.drawText(tpx - (x / 2.0), tpy+3, txt); } sum += len; } else if (m_graph_type == GST_CandleStick) { ///////////////////////////////////////////////////////////////////////////////////// // CandleStick Chart ///////////////////////////////////////////////////////////////////////////////////// QColor &col = m_colors[m % m_colors.size()]; float bw = xmult * float(data); QLinearGradient linearGrad(QPointF(0, 0), QPointF(bw, 0)); linearGrad.setColorAt(0, col); linearGrad.setColorAt(1, Qt::white); painter.fillRect(xp, start_py, bw, height, QBrush(linearGrad)); painter.setPen(m_outline_color); painter.drawLine(xp, start_py, xp + bw, start_py); painter.drawLine(xp + bw, start_py + height, xp, start_py + height); if (!m_names[m].isEmpty()) { int px, py; GetTextExtent(m_names[m], px, py); if (px + 5 < bw) { w.renderText(m_names[m], (xp + bw / 2) - (px / 2), top + ((height / 2) - (py / 2)), 0, Qt::black); } } xp += bw; ///////////////////////////////////////////////////////////////////////////////////// // Line Chart ///////////////////////////////////////////////////////////////////////////////////// } else if (m_graph_type == GST_Line) { QColor col = Qt::black; //m_colors[m % m_colors.size()]; painter.setPen(col); float h = (top + height) - (float(data) * ymult); if (line_first) { line_first = false; } else { painter.drawLine(xp, line_last, xp + line_step, h); xp += line_step; } line_last = h; } } } gTAPGraph::gTAPGraph(ChannelID code, GraphSegmentType gt, QColor gradient_color, QColor outline_color) : gSegmentChart(gt, gradient_color, outline_color), m_code(code) { m_colors.push_back(Qt::red); m_colors.push_back(Qt::green); } gTAPGraph::~gTAPGraph() { } void gTAPGraph::SetDay(Day *d) { Layer::SetDay(d); m_total = 0; if (!m_day) { return; } QMap tap; EventStoreType data = 0, lastval = 0; qint64 time = 0, lasttime = 0; //bool first; bool rfirst = true; //bool changed; EventDataType gain = 1, offset = 0; QHash >::iterator ei; for (const auto & sess : m_day->sessions) { if (!sess->enabled()) { continue; } ei = sess->eventlist.find(m_code); if (ei == sess->eventlist.end()) { continue; } for (const auto & el : ei.value()) { lasttime = el->time(0); lastval = el->raw(0); if (rfirst) { gain = el->gain(); offset = el->offset(); rfirst = false; } for (quint32 i=1, end=el->count(); i < end; i++) { data = el->raw(i); time = el->time(i); if (lastval != data) { qint64 v = (time - lasttime); if (tap.find(lastval) != tap.end()) { tap[lastval] += v; } else { tap[lastval] = v; } //changed=true; lasttime = time; lastval = data; } } if (time != lasttime) { qint64 v = (time - lasttime); if (tap.find(lastval) != tap.end()) { tap[data] += v; } else { tap[data] = v; } } } } m_values.clear(); m_names.clear(); m_total = 0; EventDataType val; for (auto i=tap.begin(), end=tap.end(); i != end; i++) { val = float(i.key()) * gain + offset; m_values.push_back(i.value() / 1000L); m_total += i.value() / 1000L; m_names.push_back(QString::number(val, 'f', 2)); } m_empty = m_values.size() == 0; } OSCAR-code-v1.3.1/oscar/Graphs/gSegmentChart.h000066400000000000000000000045601417327530600207350ustar00rootroot00000000000000/* gSegmentChart Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GSEGMENTCHART_H #define GSEGMENTCHART_H #include "gGraphView.h" enum GraphSegmentType { GST_Pie, GST_CandleStick, GST_Line }; /*! \class gSegmentChart \brief Draws a PieChart, CandleStick or 2D Line plots containing multiple Channel 'slices' */ class gSegmentChart : public Layer { public: gSegmentChart(GraphSegmentType gt = GST_Pie, QColor gradient_color = Qt::white, QColor outline_color = Qt::black); virtual ~gSegmentChart(); //! \brief The drawing code that fills the Vertex buffers virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); //! \brief Pre-fills a buffer with the data needed to draw virtual void SetDay(Day *d); //! \brief Returns true if no data available for drawing virtual bool isEmpty(); //! \brief Adds a channel slice, and sets the color and label void AddSlice(ChannelID code, QColor col, QString name = ""); //! \brief Sets the fade-out color to make the graphs look more attractive void setGradientColor(QColor &color) { m_gradient_color = color; } //! \brief Sets the outline color for the edges drawn around the Pie slices void setOutlineColor(QColor &color) { m_outline_color = color; } const GraphSegmentType &graphType() { return m_graph_type; } void setGraphType(GraphSegmentType type) { m_graph_type = type; } protected: QVector m_codes; QVector m_names; QVector m_values; QVector m_colors; EventDataType m_total; GraphSegmentType m_graph_type; QColor m_gradient_color; QColor m_outline_color; bool m_empty; }; /*! \class gTAPGraph \brief Time at Pressure chart, derived from gSegmentChart \notes Currently unused */ class gTAPGraph: public gSegmentChart { public: gTAPGraph(ChannelID code, GraphSegmentType gt = GST_CandleStick, QColor gradient_color = Qt::lightGray, QColor outline_color = Qt::black); virtual ~gTAPGraph(); virtual void SetDay(Day *d); protected: ChannelID m_code; }; #endif // GSEGMENTCHART_H OSCAR-code-v1.3.1/oscar/Graphs/gSessionTimesChart.cpp000066400000000000000000001127501417327530600223140ustar00rootroot00000000000000/* gSessionTimesChart Implementation * * Copyright (c) 2019-2022 The Oscar Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include "mainwindow.h" #include "SleepLib/profiles.h" #include "SleepLib/machine_common.h" #include "gSessionTimesChart.h" #include "gYAxis.h" extern MainWindow * mainwin; short SummaryCalcItem::midcalc; gSummaryChart::gSummaryChart(QString label, MachineType machtype) :Layer(NoChannel), m_label(label), m_machtype(machtype) { m_layertype = LT_Overview; QDateTime d1 = QDateTime::currentDateTime(); QDateTime d2 = d1; d1.setTimeSpec(Qt::UTC); // CHECK: Does this deal with DST? tz_offset = d2.secsTo(d1); tz_hours = tz_offset / 3600.0; expected_slices = 5; idx_end = 0; idx_start = 0; } gSummaryChart::gSummaryChart(ChannelID code, MachineType machtype) :Layer(code), m_machtype(machtype) { m_layertype = LT_Overview; QDateTime d1 = QDateTime::currentDateTime(); QDateTime d2 = d1; d1.setTimeSpec(Qt::UTC); // CHECK: Does this deal with DST? tz_offset = d2.secsTo(d1); tz_hours = tz_offset / 3600.0; expected_slices = 5; addCalc(code, ST_MIN, brighten(schema::channel[code].defaultColor() ,0.60f)); addCalc(code, ST_MID, brighten(schema::channel[code].defaultColor() ,1.20f)); addCalc(code, ST_90P, brighten(schema::channel[code].defaultColor() ,1.70f)); addCalc(code, ST_MAX, brighten(schema::channel[code].defaultColor() ,2.30f)); idx_end = 0; idx_start = 0; } gSummaryChart::~gSummaryChart() { } void gSummaryChart::SetDay(Day *unused_day) { cache.clear(); Q_UNUSED(unused_day) Layer::SetDay(nullptr); if (m_machtype != MT_CPAP) { // Channels' machine types are not terribly reliable: oximetry channels can be reported by a CPAP, // and position channels can be reported by an oximeter. So look for any days with data. firstday = p_profile->FirstDay(); lastday = p_profile->LastDay(); } else { // But CPAP channels (like pressure settings) can only be reported by a CPAP. firstday = p_profile->FirstDay(m_machtype); lastday = p_profile->LastDay(m_machtype); } dayindex.clear(); daylist.clear(); if (!firstday.isValid() || !lastday.isValid()) return; // daylist.reserve(firstday.daysTo(lastday)+1); QDate date = firstday; int idx = 0; do { auto di = p_profile->daylist.find(date); Day * day = nullptr; if (di != p_profile->daylist.end()) { day = di.value(); } daylist.append(day); dayindex[date] = idx; idx++; date = date.addDays(1); } while (date <= lastday); m_minx = QDateTime(firstday, QTime(0,0,0), Qt::LocalTime).toMSecsSinceEpoch(); m_maxx = QDateTime(lastday, QTime(23,59,59), Qt::LocalTime).toMSecsSinceEpoch(); m_miny = 0; m_maxy = 20; m_empty = false; } //QMap gSummaryChart::dayindex; //QList gSummaryChart::daylist; int gSummaryChart::addCalc(ChannelID code, SummaryType type, QColor color) { calcitems.append(SummaryCalcItem(code, type, color)); return calcitems.size() - 1; // return the index of the newly appended calc } int gSummaryChart::addCalc(ChannelID code, SummaryType type) { return addCalc(code, type, schema::channel[code].defaultColor()); } bool gSummaryChart::keyPressEvent(QKeyEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) return false; } bool gSummaryChart::mouseMoveEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) return false; } bool gSummaryChart::mousePressEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) return false; } bool gSummaryChart::mouseReleaseEvent(QMouseEvent *event, gGraph *graph) { if (!(event->modifiers() & Qt::ShiftModifier)) return false; float x = event->x() - m_rect.left(); float y = event->y() - m_rect.top(); qDebug() << x << y; EventDataType miny; EventDataType maxy; graph->roundY(miny, maxy); QDate date = QDateTime::fromMSecsSinceEpoch(m_minx, Qt::LocalTime).date(); int days = ceil(double(m_maxx - m_minx) / 86400000.0); float barw = float(m_rect.width()) / float(days); float idx = x/barw; date = date.addDays(idx); auto it = dayindex.find(date); if (it != dayindex.end()) { Day * day = daylist.at(it.value()); if (day) { mainwin->getDaily()->LoadDate(date); mainwin->JumpDaily(); } } return true; } void gSummaryChart::preCalc() { midcalc = p_profile->general->prefCalcMiddle(); for (auto & calc : calcitems) { calc.reset(idx_end - idx_start, midcalc); } } void gSummaryChart::customCalc(Day *day, QVector & slices) { int size = slices.size(); if (size != calcitems.size()) { return; } float hour = day->hours(m_machtype); for (int i=0; i < size; ++i) { const SummaryChartSlice & slice = slices.at(i); SummaryCalcItem & calc = calcitems[i]; calc.update(slice.value, hour); } } void gSummaryChart::afterDraw(QPainter &painter, gGraph &graph, QRectF rect) { if (totaldays == nousedays) return; if (calcitems.size() == 0) return; QStringList strlist; QString txt; int midcalc = p_profile->general->prefCalcMiddle(); QString midstr; if (midcalc == 0) { midstr = QObject::tr("Med."); } else if (midcalc == 1) { midstr = QObject::tr("W-Avg"); } else { midstr = QObject::tr("Avg"); } float perc = p_profile->general->prefCalcPercentile(); QString percstr = QString("%1%").arg(perc, 0, 'f',0); schema::Channel & chan = schema::channel[calcitems.at(0).code]; for (auto & calc : calcitems) { if (calcitems.size() == 1) { float val = calc.min; if (val < 99998) strlist.append(QObject::tr("Min: %1").arg(val,0,'f',2)); } float mid = 0; switch (midcalc) { case 0: if (calc.median_data.size() > 0) { mid = median(calc.median_data.begin(), calc.median_data.end()); } break; case 1: mid = calc.wavg_sum / calc.divisor; break; case 2: mid = calc.avg_sum / calc.cnt; break; } float val = 0; switch (calc.type) { case ST_CPH: val = mid; txt = midstr+": "; break; case ST_SPH: val = mid; txt = midstr+": "; break; case ST_MIN: val = calc.min; if (val >= 99998) continue; txt = QObject::tr("Min: "); break; case ST_MAX: val = calc.max; if (val <= -99998) continue; txt = QObject::tr("Max: "); break; case ST_SETMIN: val = calc.min; if (val >= 99998) continue; txt = QObject::tr("Min: "); break; case ST_SETMAX: val = calc.max; if (val <= -99998) continue; txt = QObject::tr("Max: "); break; case ST_MID: val = mid; txt = QString("%1: ").arg(midstr); break; case ST_90P: val = mid; txt = QString("%1: ").arg(percstr); break; default: val = mid; txt = QString("???: "); break; } strlist.append(QString("%1%2").arg(txt).arg(val,0,'f',2)); if (calcitems.size() == 1) { val = calc.max; if (val > -99998) strlist.append(QObject::tr("Max: %1").arg(val,0,'f',2)); } } QString str; if (totaldays > 1) { str = QObject::tr("%1 (%2 days): ").arg(chan.fullname()).arg(totaldays); } else { str = QObject::tr("%1 (%2 day): ").arg(chan.fullname()).arg(totaldays); } str += " "+strlist.join(", "); QRectF rec(rect.left(), rect.top(), 0,0); painter.setFont(*defaultfont); rec = painter.boundingRect(rec, Qt::AlignTop, str); rec.moveBottom(rect.top()-3*graph.printScaleY()); painter.drawText(rec, Qt::AlignTop, str); // graph.renderText(str, rect.left(), rect.top()-5*graph.printScaleY(), 0); } QString gSummaryChart::tooltipData(Day *, int idx) { QString txt; const auto & slices = cache[idx]; int i = slices.size(); while (i > 0) { i--; txt += QString("\n%1: %2").arg(slices[i].name).arg(float(slices[i].value), 0, 'f', 2); } return txt; } void gSummaryChart::populate(Day * day, int idx) { bool good = false; for (const auto & item : calcitems) { if (day->hasData(item.code, item.type)) { good = true; break; } } if (!good) return; auto & slices = cache[idx]; float hours = day->hours(m_machtype); float base = 0; for (auto & item : calcitems) { ChannelID code = item.code; schema::Channel & chan = schema::channel[code]; float value = 0; QString name; QColor color; switch (item.type) { case ST_CPH: value = day->count(code) / hours; name = chan.label(); color = item.color; slices.append(SummaryChartSlice(&item, value, value, name, color)); break; case ST_SPH: value = (100.0 / hours) * (day->sum(code) / 3600.0); name = QObject::tr("% in %1").arg(chan.label()); color = item.color; slices.append(SummaryChartSlice(&item, value, value, name, color)); break; case ST_HOURS: value = hours; name = QObject::tr("Hours"); color = COLOR_LightBlue; slices.append(SummaryChartSlice(&item, value, hours, name, color)); break; case ST_MIN: value = day->Min(code); name = QObject::tr("Min %1").arg(chan.label()); color = item.color; slices.append(SummaryChartSlice(&item, value, value - base, name, color)); base = value; break; case ST_MID: value = day->calcMiddle(code); name = day->calcMiddleLabel(code); color = item.color; slices.append(SummaryChartSlice(&item, value, value - base, name, color)); base = value; break; case ST_90P: value = day->calcPercentile(code); name = day->calcPercentileLabel(code); color = item.color; slices.append(SummaryChartSlice(&item, value, value - base, name, color)); base = value; break; case ST_MAX: value = day->calcMax(code); name = day->calcMaxLabel(code); color = item.color; slices.append(SummaryChartSlice(&item, value, value - base, name, color)); base = value; break; default: break; } } } void gSummaryChart::paint(QPainter &painter, gGraph &graph, const QRegion ®ion) { QRectF rect = region.boundingRect(); rect.translate(0.0f, 0.001f); painter.setPen(QColor(Qt::black)); painter.drawRect(rect); rect.moveBottom(rect.bottom()+1); m_minx = graph.min_x; m_maxx = graph.max_x; QDateTime date2 = QDateTime::fromMSecsSinceEpoch(m_minx, Qt::LocalTime); QDateTime enddate2 = QDateTime::fromMSecsSinceEpoch(m_maxx, Qt::LocalTime); QDate date = date2.date(); QDate enddate = enddate2.date(); int days = ceil(double(m_maxx - m_minx) / 86400000.0); //float lasty1 = rect.bottom(); auto it = dayindex.find(date); idx_start = 0; if (it == dayindex.end()) { it = dayindex.begin(); } else { idx_start = it.value(); } int idx = idx_start; // Determine how many days after the first day of the chart that this data is to begin int numDaysOffset = 0; if (firstday > date) { // date = beginning date of chart; firstday = beginning date of data numDaysOffset = date.daysTo(firstday); } // Determine how many days before the last day of the chart that this data is to end int numDaysAfter = 0; if (enddate > lastday) { // enddate = last date of chart; lastday = last date of data numDaysAfter = lastday.daysTo(enddate); } if (numDaysAfter > days) // Nothing to do if this data is off the left edge of the chart return; auto ite = dayindex.find(enddate); idx_end = daylist.size()-1; if (ite != dayindex.end()) { idx_end = ite.value(); } QPoint mouse = graph.graphView()->currentMousePos(); nousedays = 0; totaldays = 0; QRectF hl_rect; QDate hl_date; Day * hl_day = nullptr; int hl_idx = -1; bool hl = false; if ((daylist.size() == 0) || (it == dayindex.end())) return; //Day * lastday = nullptr; // int dc = 0; // for (int i=idx; i<=idx_end; ++i) { // Day * day = daylist.at(i); // if (day || lastday) { // dc++; // } // lastday = day; // } // days = dc; // lastday = nullptr; float barw = float(rect.width()) / float(days); QString hl2_text = ""; QVector outlines; int size = idx_end - idx; outlines.reserve(size * expected_slices); // Virtual call to setup any custom graph stuff preCalc(); float lastx1 = rect.left(); lastx1 += numDaysOffset * barw; float right_edge = (rect.left()+rect.width()+1); ///////////////////////////////////////////////////////////////////// /// Calculate Graph Peaks ///////////////////////////////////////////////////////////////////// peak_value = 0; for (int i=idx; i <= idx_end; ++i, lastx1 += barw) { Day * day = daylist.at(i); if ((lastx1 + barw) > right_edge) break; if (!day) { continue; } day->OpenSummary(); auto cit = cache.find(i); if (cit == cache.end()) { populate(day, i); cit = cache.find(i); } if (cit != cache.end()) { float base = 0, val; for (const auto & slice : cit.value()) { val = slice.height; base += val; } peak_value = qMax(peak_value, base); } } m_miny = 0; m_maxy = ceil(peak_value); ///////////////////////////////////////////////////////////////////// /// Y-Axis scaling ///////////////////////////////////////////////////////////////////// EventDataType miny; EventDataType maxy; graph.roundY(miny, maxy); float ymult = float(rect.height()) / (maxy-miny); lastx1 = rect.left(); lastx1 += numDaysOffset * barw; ///////////////////////////////////////////////////////////////////// /// Main drawing loop ///////////////////////////////////////////////////////////////////// do { Day * day = daylist.at(idx); if ((lastx1 + barw) > right_edge) break; totaldays++; if (!day) {// || !day->hasMachine(m_machtype)) { // lasty1 = rect.bottom(); lastx1 += barw; it++; nousedays++; //lastday = day; continue; } //lastday = day; float x1 = lastx1 + barw; day->OpenSummary(); QRectF hl2_rect; bool hlday = false; QRectF rec2(lastx1, rect.top(), barw, rect.height()); if (rec2.contains(mouse)) { hl_rect = rec2; hl_day = day; hl_date = it.key(); hl_idx = idx; hl = true; hlday = true; } auto cit = cache.find(idx); if (cit == cache.end()) { populate(day, idx); cit = cache.find(idx); } float lastval = 0, val, y1,y2; if (cit != cache.end()) { ///////////////////////////////////////////////////////////////////////////////////// /// Draw pressure settings ///////////////////////////////////////////////////////////////////////////////////// QVector & list = cit.value(); customCalc(day, list); QLinearGradient gradient(lastx1, 0, lastx1 + barw, 0); //rect.bottom(), barw, rect.bottom()); for (const auto & slice : list) { val = slice.height; y1 = ((lastval-miny) * ymult); y2 = (val * ymult); QColor color = slice.color; QRectF rec = QRectF(lastx1, rect.bottom() - y1, barw, -y2).intersected(rect); if (hlday) { if (rec.contains(mouse.x(), mouse.y())) { color = Qt::yellow; hl2_rect = rec; } } if (barw <= 3) { painter.fillRect(rec, QBrush(color)); } else if (barw > 8) { gradient.setColorAt(0,color); gradient.setColorAt(1,brighten(color, 2.0)); painter.fillRect(rec, QBrush(gradient)); // painter.fillRect(rec, slice.brush); outlines.append(rec); } else { painter.fillRect(rec, brighten(color, 1.25)); outlines.append(rec); } lastval += val; } } lastx1 = x1; it++; } while (++idx <= idx_end); painter.setPen(QPen(Qt::black,1)); painter.drawRects(outlines); if (hl) { QColor col2(255,0,0,64); painter.fillRect(hl_rect, QBrush(col2)); QString txt = hl_date.toString(Qt::SystemLocaleShortDate)+" "; if (hl_day) { // grab extra tooltip data txt += tooltipData(hl_day, hl_idx); if (!hl2_text.isEmpty()) { QColor col = Qt::yellow; col.setAlpha(255); // painter.fillRect(hl2_rect, QBrush(col)); txt += hl2_text; } } graph.ToolTip(txt, mouse.x()-15, mouse.y()+5, TT_AlignRight); } try { afterDraw(painter, graph, rect); } catch(...) { qDebug() << "Bad median call in" << m_label; } // This could be turning off graphs prematurely.. if (cache.size() == 0) { m_empty = true; graph.graphView()->updateScale(); } } QString gUsageChart::tooltipData(Day * day, int) { return QObject::tr("\nHours: %1").arg(day->hours(m_machtype), 0, 'f', 2); } void gUsageChart::populate(Day *day, int idx) { QVector & slices = cache[idx]; float hours = day->hours(m_machtype); QColor cpapcolor = day->summaryOnly() ? QColor(128,128,128) : calcitems[0].color; bool haveoxi = day->hasMachine(MT_OXIMETER); QColor goodcolor = haveoxi ? QColor(128,255,196) : cpapcolor; QColor color = (hours < compliance_threshold) ? QColor(255,64,64) : goodcolor; slices.append(SummaryChartSlice(&calcitems[0], hours, hours, QObject::tr("Hours"), color)); } void gUsageChart::preCalc() { midcalc = p_profile->general->prefCalcMiddle(); compliance_threshold = p_profile->cpap->complianceHours(); incompdays = 0; SummaryCalcItem & calc = calcitems[0]; calc.reset(idx_end - idx_start, midcalc); } void gUsageChart::customCalc(Day *, QVector &list) { if (list.size() == 0) { incompdays++; return; } SummaryChartSlice & slice = list[0]; SummaryCalcItem & calc = calcitems[0]; if (slice.value < compliance_threshold) incompdays++; calc.update(slice.value, 1); } void gUsageChart::afterDraw(QPainter &, gGraph &graph, QRectF rect) { if (totaldays == nousedays) return; if (totaldays > 1) { float comp = 100.0 - ((float(incompdays + nousedays) / float(totaldays)) * 100.0); int midcalc = p_profile->general->prefCalcMiddle(); float mid = 0; SummaryCalcItem & calc = calcitems[0]; switch (midcalc) { case 0: // median mid = median(calc.median_data.begin(), calc.median_data.end()); break; case 1: // w-avg mid = calc.wavg_sum / calc.divisor; break; case 2: mid = calc.avg_sum / calc.cnt; break; } QString txt = QObject::tr("%1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7"). arg(incompdays).arg(nousedays).arg(totaldays).arg(comp,0,'f',1).arg(calc.min, 0, 'f', 2).arg(mid, 0, 'f', 2).arg(calc.max, 0, 'f', 2);; graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0); } } void gSessionTimesChart::preCalc() { midcalc = p_profile->general->prefCalcMiddle(); num_slices = 0; num_days = 0; total_length = 0; SummaryCalcItem & calc = calcitems[0]; calc.reset((idx_end - idx_start) * 6, midcalc); SummaryCalcItem & calc1 = calcitems[1]; calc1.reset(idx_end - idx_start, midcalc); SummaryCalcItem & calc2 = calcitems[2]; calc2.reset(idx_end - idx_start, midcalc); } void gSessionTimesChart::customCalc(Day *, QVector & slices) { int size = slices.size(); num_slices += size; SummaryCalcItem & calc1 = calcitems[1]; calc1.update(slices.size(), 1); EventDataType max = 0; for (auto & slice : slices) { slice.calc->update(slice.height, slice.height); max = qMax(slice.height, max); } SummaryCalcItem & calc2 = calcitems[2]; calc2.update(max, max); num_days++; } void gSessionTimesChart::afterDraw(QPainter & /*painter */, gGraph &graph, QRectF rect) { if (totaldays == nousedays) return; SummaryCalcItem & calc = calcitems[0]; // session length SummaryCalcItem & calc1 = calcitems[1]; // number of sessions SummaryCalcItem & calc2 = calcitems[2]; // number of sessions int midcalc = p_profile->general->prefCalcMiddle(); float mid = 0, mid1 = 0, midlongest = 0; switch (midcalc) { case 0: if (calc.median_data.size() > 0) { mid = median(calc.median_data.begin(), calc.median_data.end()); mid1 = median(calc1.median_data.begin(), calc1.median_data.end()); midlongest = median(calc2.median_data.begin(), calc2.median_data.end()); } break; case 1: mid = calc.wavg_sum / calc.divisor; mid1 = calc1.wavg_sum / calc1.divisor; midlongest = calc2.wavg_sum / calc2.divisor; break; case 2: mid = calc.avg_sum / calc.cnt; mid1 = calc1.avg_sum / calc1.cnt; midlongest = calc2.avg_sum / calc2.cnt; break; } // float avgsess = float(num_slices) / float(num_days); QString txt = QObject::tr("Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9") .arg(calc1.min, 0, 'f', 2).arg(mid1, 0, 'f', 2).arg(calc1.max, 0, 'f', 2) .arg(calc.min, 0, 'f', 2).arg(mid, 0, 'f', 2).arg(calc.max, 0, 'f', 2) .arg(calc2.min, 0, 'f', 2).arg(midlongest, 0, 'f', 2).arg(calc2.max, 0, 'f', 2); graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0); } void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion ®ion) { QRectF rect = region.boundingRect(); painter.setPen(QColor(Qt::black)); painter.drawRect(rect); m_minx = graph.min_x; m_maxx = graph.max_x; QDateTime date2 = QDateTime::fromMSecsSinceEpoch(m_minx, Qt::LocalTime); QDateTime enddate2 = QDateTime::fromMSecsSinceEpoch(m_maxx, Qt::LocalTime); QDate date = date2.date(); QDate enddate = enddate2.date(); int days = ceil(double(m_maxx - m_minx) / 86400000.0); float barw = float(rect.width()) / float(days); QDateTime splittime; auto it = dayindex.find(date); int idx=0; if (it == dayindex.end()) { it = dayindex.begin(); } else { idx = it.value(); } // Determine how many days after the first day of the chart that this data is to begin int numDaysOffset = 0; if (firstday > date) { // date = beginning date of chart; firstday = beginning date of data numDaysOffset = date.daysTo(firstday); } // Determine how many days before the last day of the chart that this data is to end int numDaysAfter = 0; if (enddate > lastday) { // enddate = last date of chart; lastday = last date of data numDaysAfter = lastday.daysTo(enddate); } if (numDaysAfter > days) // Nothing to do if this data is off the left edge of the chart return; // float lasty1 = rect.bottom(); float lastx1 = rect.left(); lastx1 += numDaysOffset * barw; auto ite = dayindex.find(enddate); int idx_end = daylist.size()-1; if (ite != dayindex.end()) { idx_end = ite.value(); } QPoint mouse = graph.graphView()->currentMousePos(); if (daylist.size() == 0) return; QVector outlines; int size = idx_end - idx; outlines.reserve(size * 5); auto it2 = it; ///////////////////////////////////////////////////////////////////// /// Calculate Graph Peaks ///////////////////////////////////////////////////////////////////// peak_value = 0; min_value = 999; auto it_end = dayindex.end(); float right_edge = (rect.left()+rect.width()+1); for (int i=idx; (i <= idx_end) && (it2 != it_end); ++i, ++it2, lastx1 += barw) { Day * day = daylist.at(i); if ((lastx1 + barw) > right_edge) break; if (!day) { continue; } auto cit = cache.find(i); if (cit == cache.end()) { day->OpenSummary(); date = it2.key(); splittime = QDateTime(date, split); QVector & slices = cache[i]; bool haveoxi = day->hasMachine(MT_OXIMETER); QColor goodcolor = haveoxi ? QColor(128,255,196) : QColor(64,128,255); QString datestr = date.toString(Qt::SystemLocaleShortDate); for (const auto & sess : day->sessions) { if (!sess->enabled() || (sess->type() != m_machtype)) continue; // Look at mask on/off slices... if (sess->m_slices.size() > 0) { // segments for (const auto & slice : sess->m_slices) { QDateTime st = QDateTime::fromMSecsSinceEpoch(slice.start, Qt::LocalTime); float s1 = float(splittime.secsTo(st)) / 3600.0; float s2 = double(slice.end - slice.start) / 3600000.0; QColor col = (slice.status == MaskOn) ? goodcolor : Qt::black; QString txt = QObject::tr("%1\nLength: %3\nStart: %2\n").arg(datestr).arg(st.time().toString("hh:mm:ss")).arg(s2,0,'f',2); txt += (slice.status == MaskOn) ? QObject::tr("Mask On") : QObject::tr("Mask Off"); slices.append(SummaryChartSlice(&calcitems[0], s1, s2, txt, col)); } } else { // otherwise just show session duration qint64 sf = sess->first(); QDateTime st = QDateTime::fromMSecsSinceEpoch(sf, Qt::LocalTime); float s1 = float(splittime.secsTo(st)) / 3600.0; float s2 = sess->hours(); QString txt = QObject::tr("%1\nLength: %3\nStart: %2").arg(datestr).arg(st.time().toString("hh:mm:ss")).arg(s2,0,'f',2); slices.append(SummaryChartSlice(&calcitems[0], s1, s2, txt, goodcolor)); } } cit = cache.find(i); } if (cit != cache.end()) { float peak = 0, base = 999; for (const auto & slice : cit.value()) { float s1 = slice.value; float s2 = slice.height; peak = qMax(peak, s1+s2); base = qMin(base, s1); } peak_value = qMax(peak_value, peak); min_value = qMin(min_value, base); } } m_miny = (min_value < 999) ? floor(min_value) : 0; m_maxy = ceil(peak_value); ///////////////////////////////////////////////////////////////////// /// Y-Axis scaling ///////////////////////////////////////////////////////////////////// EventDataType miny; EventDataType maxy; graph.roundY(miny, maxy); float ymult = float(rect.height()) / (maxy-miny); preCalc(); totaldays = 0; nousedays = 0; lastx1 = rect.left(); lastx1 += numDaysOffset * barw; ///////////////////////////////////////////////////////////////////// /// Main Loop scaling ///////////////////////////////////////////////////////////////////// do { Day * day = daylist.at(idx); if ((lastx1 + barw) > right_edge) break; totaldays++; if (!day) { // || !day->hasMachine(m_machtype)) { // lasty1 = rect.bottom(); lastx1 += barw; nousedays++; // it++; continue; } auto cit = cache.find(idx); float x1 = lastx1 + barw; //bool hl = false; QRectF rec2(lastx1, rect.top(), barw, rect.height()); if (rec2.contains(mouse)) { QColor col2(255,0,0,64); painter.fillRect(rec2, QBrush(col2)); //hl = true; } if (cit != cache.end()) { QVector & slices = cit.value(); customCalc(day, slices); QLinearGradient gradient(lastx1, rect.bottom(), lastx1+barw, rect.bottom()); for (const auto & slice : slices) { float s1 = slice.value - miny; float s2 = slice.height; float y1 = (s1 * ymult); float y2 = (s2 * ymult); QColor col = slice.color; QRectF rec(lastx1, rect.bottom() - y1 - y2, barw, y2); rec = rec.intersected(rect); if (rec.contains(mouse)) { col = Qt::yellow; graph.ToolTip(slice.name, mouse.x() - 15,mouse.y() + 15, TT_AlignRight); } if (barw > 8) { gradient.setColorAt(0,col); gradient.setColorAt(1,brighten(col, 2.0)); painter.fillRect(rec, QBrush(gradient)); // painter.fillRect(rec, brush); outlines.append(rec); } else if (barw > 3) { painter.fillRect(rec, QBrush(brighten(col,1.25))); outlines.append(rec); } else { painter.fillRect(rec, QBrush(col)); } } } lastx1 = x1; } while (++idx <= idx_end); painter.setPen(QPen(Qt::black,1)); painter.drawRects(outlines); afterDraw(painter, graph, rect); } //////////////////////////////////////////////////////////////////////////// /// Total Time in Apnea Chart Stuff //////////////////////////////////////////////////////////////////////////// void gTTIAChart::preCalc() { gSummaryChart::preCalc(); } void gTTIAChart::customCalc(Day *, QVector & slices) { if (slices.size() == 0) return; const SummaryChartSlice & slice = slices.at(0); calcitems[0].update(slice.value, slice.value); } void gTTIAChart::afterDraw(QPainter &, gGraph &graph, QRectF rect) { QStringList txtlist; for (auto & calc : calcitems) { //ChannelID code = calc.code; //schema::Channel & chan = schema::channel[code]; float mid = 0; switch (midcalc) { case 0: if (calc.median_data.size() > 0) { mid = median(calc.median_data.begin(), calc.median_data.end()); } break; case 1: if (calc.divisor > 0) { mid = calc.wavg_sum / calc.divisor; } break; case 2: if (calc.cnt > 0) { mid = calc.avg_sum / calc.cnt; } break; } txtlist.append(QString("%1 %2 / %3 / %4").arg(QObject::tr("TTIA:")).arg(calc.min, 0, 'f', 2).arg(mid, 0, 'f', 2).arg(calc.max, 0, 'f', 2)); } QString txt = txtlist.join(", "); graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0); } void gTTIAChart::populate(Day *day, int idx) { QVector & slices = cache[idx]; // float ttia = day->sum(CPAP_AllApnea) + day->sum(CPAP_Obstructive) + day->sum(CPAP_ClearAirway) + day->sum(CPAP_Apnea) + day->sum(CPAP_Hypopnea); float ttia = day->sum(AllAhiChannels); int h = ttia / 3600; int m = int(ttia) / 60 % 60; int s = int(ttia) % 60; slices.append(SummaryChartSlice(&calcitems[0], ttia / 60.0, ttia / 60.0, QObject::tr("\nTTIA: %1").arg(QString().sprintf("%02i:%02i:%02i",h,m,s)), QColor(255,147,150))); } QString gTTIAChart::tooltipData(Day *, int idx) { QVector & slices = cache[idx]; if (slices.size() == 0) return QString(); const SummaryChartSlice & slice = slices.at(0); return slice.name; } //////////////////////////////////////////////////////////////////////////// /// AHI Chart Stuff //////////////////////////////////////////////////////////////////////////// void gAHIChart::preCalc() { gSummaryChart::preCalc(); ahi_wavg = 0; ahi_avg = 0; total_days = 0; total_hours = 0; min_ahi = 99999; max_ahi = -99999; ahi_data.clear(); ahi_data.reserve(idx_end-idx_start); } void gAHIChart::customCalc(Day *day, QVector &list) { int size = list.size(); if (size == 0) return; EventDataType hours = day->hours(m_machtype); EventDataType ahi_cnt = 0; for (auto & slice : list) { SummaryCalcItem * calc = slice.calc; EventDataType value = slice.value; float valh = value/ hours; switch (midcalc) { case 0: calc->median_data.append(valh); break; case 1: calc->wavg_sum += value; calc->divisor += hours; break; case 2: calc->avg_sum += value; calc->cnt++; break; } calc->min = qMin(valh, calc->min); calc->max = qMax(valh, calc->max); ahi_cnt += value; } min_ahi = qMin(ahi_cnt / hours, min_ahi); max_ahi = qMax(ahi_cnt / hours, max_ahi); ahi_data.append(ahi_cnt / hours); ahi_wavg += ahi_cnt; ahi_avg += ahi_cnt; total_hours += hours; total_days++; } void gAHIChart::afterDraw(QPainter & /*painter */, gGraph &graph, QRectF rect) { if (totaldays == nousedays) return; //int size = idx_end - idx_start; bool skip = true; float med = 0; switch (midcalc) { case 0: if (ahi_data.size() > 0) { med = median(ahi_data.begin(), ahi_data.end()); skip = false; } break; case 1: // wavg if (total_hours > 0) { med = ahi_wavg / total_hours; skip = false; } break; case 2: // avg if (total_days > 0) { med = ahi_avg / total_days; skip = false; } break; } QStringList txtlist; if (!skip) txtlist.append(QString("%1 %2 / %3 / %4").arg(STR_TR_AHI).arg(min_ahi, 0, 'f', 2).arg(med, 0, 'f', 2).arg(max_ahi, 0, 'f', 2)); int i = calcitems.size(); while (i > 0) { i--; ChannelID code = calcitems[i].code; schema::Channel & chan = schema::channel[code]; float mid = 0; skip = true; switch (midcalc) { case 0: if (calcitems[i].median_data.size() > 0) { mid = median(calcitems[i].median_data.begin(), calcitems[i].median_data.end()); skip = false; } break; case 1: if (calcitems[i].divisor > 0) { mid = calcitems[i].wavg_sum / calcitems[i].divisor; skip = false; } break; case 2: if (calcitems[i].cnt > 0) { mid = calcitems[i].avg_sum / calcitems[i].cnt; skip = false; } break; } if (!skip) txtlist.append(QString("%1 %2 / %3 / %4").arg(chan.label()).arg(calcitems[i].min, 0, 'f', 2).arg(mid, 0, 'f', 2).arg(calcitems[i].max, 0, 'f', 2)); } QString txt = txtlist.join(", "); graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0); } void gAHIChart::populate(Day *day, int idx) { QVector & slices = cache[idx]; float hours = day->hours(m_machtype); for (auto & calc : calcitems) { ChannelID code = calc.code; if (!day->hasData(code, ST_CNT)) continue; schema::Channel *chan = schema::channel.channels.find(code).value(); float c = day->count(code); slices.append(SummaryChartSlice(&calc, c, c / hours, chan->label(), calc.color)); } } QString gAHIChart::tooltipData(Day *day, int idx) { QVector & slices = cache[idx]; float total = 0; float hour = day->hours(m_machtype); QString txt; int i = slices.size(); while (i > 0) { i--; total += slices[i].value; txt += QString("\n%1: %2").arg(slices[i].name).arg(float(slices[i].value) / hour, 0, 'f', 2); } return QString("\n%1: %2").arg(STR_TR_AHI).arg(float(total) / hour,0,'f',2)+txt; } OSCAR-code-v1.3.1/oscar/Graphs/gSessionTimesChart.h000066400000000000000000000257051417327530600217640ustar00rootroot00000000000000/* gSessionTimesChart Header * * Copyright (c) 2019-2022 The Oscar Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GSESSIONTIMESCHART_H #define GSESSIONTIMESCHART_H #include "SleepLib/day.h" #include "SleepLib/profiles.h" #include "gGraphView.h" struct TimeSpan { public: TimeSpan():begin(0), end(0) {} TimeSpan(float b, float e) : begin(b), end(e) {} TimeSpan(const TimeSpan & copy) { begin = copy.begin; end = copy.end; } ~TimeSpan() {} float begin; float end; }; struct SummaryCalcItem { SummaryCalcItem() { code = 0; type = ST_CNT; color = Qt::black; wavg_sum = 0; avg_sum = 0; cnt = 0; divisor = 0; min = 0; max = 0; } SummaryCalcItem(const SummaryCalcItem & copy) { code = copy.code; type = copy.type; color = copy.color; wavg_sum = 0; avg_sum = 0; cnt = 0; divisor = 0; min = 0; max = 0; midcalc = p_profile->general->prefCalcMiddle(); } SummaryCalcItem(ChannelID code, SummaryType type, QColor color) :code(code), type(type), color(color) { } float mid() { float val = 0; switch (midcalc) { case 0: if (median_data.size() > 0) val = median(median_data.begin(), median_data.end()); break; case 1: if (divisor > 0) val = wavg_sum / divisor; break; case 2: if (cnt > 0) val = avg_sum / cnt; } return val; } inline void update(float value, float weight) { if (midcalc == 0) { median_data.append(value); } avg_sum += value; cnt++; wavg_sum += value * weight; divisor += weight; min = qMin(min, value); max = qMax(max, value); } void reset(int reserve, short mc) { midcalc = mc; wavg_sum = 0; avg_sum = 0; divisor = 0; cnt = 0; min = 99999; max = -99999; median_data.clear(); if (midcalc == 0) { median_data.reserve(reserve); } } ChannelID code; SummaryType type; QColor color; double wavg_sum; double divisor; double avg_sum; int cnt; EventDataType min; EventDataType max; static short midcalc; QList median_data; }; struct SummaryChartSlice { SummaryChartSlice() { calc = nullptr; height = 0; value = 0; name = ST_CNT; color = Qt::black; } SummaryChartSlice(const SummaryChartSlice & copy) { calc = copy.calc; value = copy.value; height = copy.height; name = copy.name; color = copy.color; // brush = copy.brush; } SummaryChartSlice(SummaryCalcItem * calc, EventDataType value, EventDataType height, QString name, QColor color) :calc(calc), value(value), height(height), name(name), color(color) { // QLinearGradient gradient(0, 0, 1, 0); // gradient.setCoordinateMode(QGradient::ObjectBoundingMode); // gradient.setColorAt(0,color); // gradient.setColorAt(1,brighten(color)); // brush = QBrush(gradient); } SummaryCalcItem * calc; EventDataType value; EventDataType height; QString name; QColor color; // QBrush brush; }; class gSummaryChart : public Layer { public: gSummaryChart(QString label, MachineType machtype); gSummaryChart(ChannelID code, MachineType machtype); virtual ~gSummaryChart(); //! \brief Renders the graph to the QPainter object virtual void paint(QPainter &, gGraph &, const QRegion &); //! \brief Called whenever data model changes underneath. Day object is not needed here, it's just here for Layer compatability. virtual void SetDay(Day *day = nullptr); //! \brief Returns true if no data was found for this day during SetDay virtual bool isEmpty() { return m_empty; } virtual void populate(Day *, int idx); //! \brief Override to setup custom stuff before main loop virtual void preCalc(); //! \brief Override to call stuff in main loop virtual void customCalc(Day *, QVector &); //! \brief Override to call stuff after draw is complete virtual void afterDraw(QPainter &, gGraph &, QRectF); //! \brief Return any extra data to show beneath the date in the hover over tooltip virtual QString tooltipData(Day *, int); virtual void dataChanged() { cache.clear(); } virtual int addCalc(ChannelID code, SummaryType type, QColor color); virtual int addCalc(ChannelID code, SummaryType type); virtual Layer * Clone() { gSummaryChart * sc = new gSummaryChart(m_label, m_machtype); Layer::CloneInto(sc); CloneInto(sc); // copy this here, because only base summary charts need it sc->calcitems = calcitems; return sc; } void CloneInto(gSummaryChart * layer) { layer->m_empty = m_empty; layer->firstday = firstday; layer->lastday = lastday; layer->expected_slices = expected_slices; layer->nousedays = nousedays; layer->totaldays = totaldays; layer->peak_value = peak_value; layer->idx_start = idx_start; layer->idx_end = idx_end; layer->cache.clear(); layer->dayindex = dayindex; layer->daylist = daylist; } protected: //! \brief Key was pressed that effects this layer virtual bool keyPressEvent(QKeyEvent *event, gGraph *graph); //! \brief Mouse moved over this layers area (shows the hover-over tooltips here) virtual bool mouseMoveEvent(QMouseEvent *event, gGraph *graph); //! \brief Mouse Button was pressed over this area virtual bool mousePressEvent(QMouseEvent *event, gGraph *graph); //! \brief Mouse Button was released over this area. (jumps to daily view here) virtual bool mouseReleaseEvent(QMouseEvent *event, gGraph *graph); QString m_label; MachineType m_machtype; bool m_empty; int hl_day; int tz_offset; float tz_hours; QDate firstday; QDate lastday; QMap dayindex; QList daylist; QHash > cache; QVector calcitems; int expected_slices; int nousedays; int totaldays; EventDataType peak_value; EventDataType min_value; int idx_start; int idx_end; short midcalc; }; /*! \class gSessionTimesChart \brief Displays a summary of session times */ class gSessionTimesChart : public gSummaryChart { public: gSessionTimesChart() :gSummaryChart("SessionTimes", MT_CPAP) { addCalc(NoChannel, ST_SESSIONS, QColor(64,128,255)); addCalc(NoChannel, ST_SESSIONS, QColor(64,128,255)); addCalc(NoChannel, ST_SESSIONS, QColor(64,128,255)); } virtual ~gSessionTimesChart() {} virtual void SetDay(Day * day = nullptr) { gSummaryChart::SetDay(day); split = p_profile->session->daySplitTime(); m_miny = 0; m_maxy = 28; } virtual void preCalc(); virtual void customCalc(Day *, QVector & slices); virtual void afterDraw(QPainter &, gGraph &, QRectF); //! \brief Renders the graph to the QPainter object virtual void paint(QPainter &painter, gGraph &graph, const QRegion ®ion); virtual Layer * Clone() { gSessionTimesChart * sc = new gSessionTimesChart(); gSummaryChart::CloneInto(sc); CloneInto(sc); return sc; } void CloneInto(gSessionTimesChart * layer) { layer->split = split; } QTime split; int num_slices; int num_days; int total_slices; double total_length; QList session_data; }; class gUsageChart : public gSummaryChart { public: gUsageChart() :gSummaryChart("Usage", MT_CPAP) { addCalc(NoChannel, ST_HOURS, QColor(64,128,255)); } virtual ~gUsageChart() {} virtual void preCalc(); virtual void customCalc(Day *, QVector &); virtual void afterDraw(QPainter &, gGraph &, QRectF); virtual void populate(Day *day, int idx); virtual QString tooltipData(Day * day, int); virtual Layer * Clone() { gUsageChart * sc = new gUsageChart(); gSummaryChart::CloneInto(sc); CloneInto(sc); return sc; } void CloneInto(gUsageChart * layer) { layer->incompdays = incompdays; layer->compliance_threshold = compliance_threshold; } private: int incompdays; EventDataType compliance_threshold; }; class gTTIAChart : public gSummaryChart { public: gTTIAChart() :gSummaryChart("TTIA", MT_CPAP) { addCalc(NoChannel, ST_CNT, QColor(255,147,150)); } virtual ~gTTIAChart() {} virtual void preCalc(); virtual void customCalc(Day *, QVector &); virtual void afterDraw(QPainter &, gGraph &, QRectF); virtual void populate(Day *day, int idx); virtual QString tooltipData(Day * day, int); virtual Layer * Clone() { gTTIAChart * sc = new gTTIAChart(); gSummaryChart::CloneInto(sc); CloneInto(sc); return sc; } void CloneInto(gTTIAChart * /* layer*/) { } private: }; class gAHIChart : public gSummaryChart { public: gAHIChart() :gSummaryChart("AHIChart", MT_CPAP) { for (int i = 0; i < ahiChannels.size(); i++) addCalc(ahiChannels.at(i), ST_CPH); // addCalc(CPAP_ClearAirway, ST_CPH); // addCalc(CPAP_AllApnea, ST_CPH); // addCalc(CPAP_Obstructive, ST_CPH); // addCalc(CPAP_Apnea, ST_CPH); // addCalc(CPAP_Hypopnea, ST_CPH); if (p_profile->general->calculateRDI()) addCalc(CPAP_RERA, ST_CPH); } virtual ~gAHIChart() {} virtual void preCalc(); virtual void customCalc(Day *, QVector &); virtual void afterDraw(QPainter &, gGraph &, QRectF); virtual void populate(Day *, int idx); virtual QString tooltipData(Day * day, int); virtual Layer * Clone() { gAHIChart * sc = new gAHIChart(); gSummaryChart::CloneInto(sc); CloneInto(sc); return sc; } void CloneInto(gAHIChart * /* layer */) { // layer->ahicalc = ahicalc; // layer->ahi_wavg = ahi_wavg; // layer->ahi_avg = ahi_avg; // layer->total_hours = total_hours; // layer->max_ahi = max_ahi; // layer->min_ahi = min_ahi; // layer->total_days = total_days; // layer->ahi_data = ahi_data; } // SummaryCalcItem ahicalc; double ahi_wavg; double ahi_avg; double total_hours; float max_ahi; float min_ahi; int total_days; QList ahi_data; }; #endif // GSESSIONTIMESCHART_H OSCAR-code-v1.3.1/oscar/Graphs/gStatsLine.cpp000066400000000000000000000032131417327530600206040ustar00rootroot00000000000000/* gStatsLine Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "SleepLib/day.h" #include "gYAxis.h" #include "gStatsLine.h" gStatsLine::gStatsLine(ChannelID code, QString label, QColor textcolor) : Layer(code), m_label(label), m_textcolor(textcolor) { } void gStatsLine::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { Q_UNUSED(painter) int left = region.boundingRect().left(); int top = region.boundingRect().top(); int width = region.boundingRect().width(); int height = region.boundingRect().height(); if (!m_visible) { return; } //if (m_empty) return; Q_UNUSED(height); int z = (width + gYAxis::Margin) / 5; int p = left - gYAxis::Margin; top += 4; w.renderText(m_label, p, top); //w.renderText(m_text,p,top,0,m_textcolor); p += z; w.renderText(st_min, p, top); p += z; w.renderText(st_avg, p, top); p += z; w.renderText(st_p90, p, top); p += z; w.renderText(st_max, p, top); } void gStatsLine::SetDay(Day *d) { Layer::SetDay(d); if (!m_day) { return; } m_min = d->Min(m_code); m_max = d->Max(m_code); m_avg = d->wavg(m_code); m_p90 = d->p90(m_code); st_min = "Min=" + QString::number(m_min, 'f', 2); st_max = "Max=" + QString::number(m_max, 'f', 2); st_avg = "Avg=" + QString::number(m_avg, 'f', 2); st_p90 = "90%=" + QString::number(m_p90, 'f', 2); } OSCAR-code-v1.3.1/oscar/Graphs/gStatsLine.h000066400000000000000000000016501417327530600202540ustar00rootroot00000000000000/* gStatsLine * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GSTATSLINE_H #define GSTATSLINE_H #include "SleepLib/machine.h" #include "gGraphView.h" /*! \class gStatsLine \brief Show a rendered stats area in place of a graph. This is currently unused */ class gStatsLine : public Layer { public: gStatsLine(ChannelID code, QString label = "", QColor textcolor = Qt::black); virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); void SetDay(Day *d); protected: QString m_label; QColor m_textcolor; EventDataType m_min, m_max, m_avg, m_p90; QString st_min, st_max, st_avg, st_p90; float m_tx, m_ty; }; #endif // GSTATSLINE_H OSCAR-code-v1.3.1/oscar/Graphs/gSummaryChart.cpp000066400000000000000000001147461417327530600213330ustar00rootroot00000000000000/* gSummaryChart Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include "gYAxis.h" #include "gSummaryChart.h" SummaryChart::SummaryChart(QString label, GraphType type) : Layer(NoChannel), m_label(label), m_graphtype(type) { m_empty = true; hl_day = -1; m_machinetype = MT_CPAP; QDateTime d1 = QDateTime::currentDateTime(); QDateTime d2 = d1; d1.setTimeSpec(Qt::UTC); tz_offset = d2.secsTo(d1); tz_hours = tz_offset / 3600.0; m_layertype = LT_SummaryChart; } SummaryChart::~SummaryChart() { } void SummaryChart::SetDay(Day * nullday) { Q_UNUSED(nullday) Day *day = nullptr; Layer::SetDay(day); m_values.clear(); m_times.clear(); m_days.clear(); m_hours.clear(); m_goodcodes.clear(); m_miny = 999999999.0F; m_maxy = -999999999.0F; m_physmaxy = 0; m_physminy = 0; m_minx = 0; m_maxx = 0; int dn; EventDataType tmp, tmp2, total; ChannelID code; CPAPMode cpapmode = (CPAPMode)(int)p_profile->calcSettingsMax(CPAP_Mode, MT_CPAP, p_profile->FirstDay(MT_CPAP), p_profile->LastDay(MT_CPAP)); ////////////////////////////////////////////////////////// // Setup for dealing with different CPAP Pressure types ////////////////////////////////////////////////////////// if (m_label == STR_TR_Pressure) { m_codes.clear(); m_colors.clear(); m_type.clear(); m_typeval.clear(); float perc = p_profile->general->prefCalcPercentile() / 100.0; int mididx = p_profile->general->prefCalcMiddle(); SummaryType mid; if (mididx == 0) { mid = ST_PERC; } else if (mididx == 1) { mid = ST_WAVG; } else mid = ST_AVG; if (cpapmode >= MODE_ASV) { addSlice(CPAP_EPAP, QColor("green"), ST_SETMIN); addSlice(CPAP_IPAPLo, QColor("light blue"), ST_SETMIN); addSlice(CPAP_IPAP, QColor("cyan"), mid, 0.5); addSlice(CPAP_IPAP, QColor("dark cyan"), ST_PERC, perc); //addSlice(CPAP_IPAP,QColor("light blue"),ST_PERC,0.95); addSlice(CPAP_IPAPHi, QColor("blue"), ST_SETMAX); } else if (cpapmode >= MODE_BILEVEL_AUTO_FIXED_PS) { addSlice(CPAP_EPAP, QColor("green"), ST_SETMIN); addSlice(CPAP_IPAP, QColor("light cyan"), mid, 0.5); addSlice(CPAP_IPAP, QColor("light blue"), ST_PERC, perc); addSlice(CPAP_PSMin, QColor("blue"), ST_SETMIN, perc); addSlice(CPAP_PSMax, QColor("red"), ST_SETMAX, perc); } else if (cpapmode >= MODE_BILEVEL_FIXED) { addSlice(CPAP_EPAP, QColor("green"), ST_SETMIN); addSlice(CPAP_EPAP, QColor("light green"), ST_PERC, perc); addSlice(CPAP_IPAP, QColor("light cyan"), mid, 0.5); addSlice(CPAP_IPAP, QColor("light blue"), ST_PERC, perc); addSlice(CPAP_IPAPHi, QColor("blue"), ST_SETMAX); } else if (cpapmode >= MODE_APAP) { addSlice(CPAP_PressureMin, QColor("orange"), ST_SETMIN); addSlice(CPAP_Pressure, QColor("dark green"), mid, 0.5f); addSlice(CPAP_Pressure, QColor("grey"), ST_PERC, perc); addSlice(CPAP_PressureMax, QColor("red"), ST_SETMAX); } else { addSlice(CPAP_Pressure, QColor("dark green"), ST_SETWAVG); } } // Initialize goodcodes (which identified which legends are drawn) to all off m_goodcodes.resize(m_codes.size()); for (int i = 0; i < m_codes.size(); i++) { m_goodcodes[i] = false; } m_fday = 0; qint64 tt, zt; m_empty = true; if (m_graphtype == GT_SESSIONS) { // No point drawing anything if no real data on record if (p_profile->countDays(MT_CPAP, p_profile->FirstDay(MT_CPAP), p_profile->LastDay(MT_CPAP)) == 0) { return; } } int suboffset; SummaryType type; bool first = true; // For each day in the main profile daylist for (auto d=p_profile->daylist.begin(), dend=p_profile->daylist.end(); d!=dend; ++d) { Day * day = d.value(); // get the timestamp of this day. tt = QDateTime(d.key(), QTime(0, 0, 0), Qt::UTC).toTime_t(); // calculate day number dn = tt / 86400; // to ms since epoch. tt *= 1000L; // update min and max for this timestamp if (!m_minx || tt < m_minx) { m_minx = tt; } if (!m_maxx || tt > m_maxx) { m_maxx = tt; } total = 0; bool fnd = false; ////////////////////////////////////////////////////////// // Setup for Sessions Time display chart ////////////////////////////////////////////////////////// if (m_graphtype == GT_SESSIONS) { // Turn all legends on for (int i = 0; i < m_codes.size(); i++) { m_goodcodes[i] = true; } // for each day object on record for this date // skip any empty or irrelevant day records if (!day || (day->machine(m_machinetype) == nullptr)) { continue; } //int ft = qint64(day->first()) / 1000L; //ft += tz_offset; // convert to local time //int dz2 = ft / 86400; //dz2 *= 86400; // ft = first sessions time, rounded back to midnight.. // For each session in this day record for (int s=0, size=day->size(); s < size; s++) { Session *sess = (*day)[s]; if (!sess->enabled()) { continue; } // Get session duration tmp = sess->hours(); m_values[dn][s] = tmp; total += tmp; // Get session start timestamp zt = qint64(sess->first()) / 1000L; zt += tz_offset; // Calculate the starting hour tmp2 = zt - dn * 86400; tmp2 /= 3600.0; m_times[dn][s] = tmp2; // Update min & max Y values if (first) { m_miny = tmp2; m_maxy = tmp2 + tmp; first = false; } else { if (tmp2 < m_miny) { m_miny = tmp2; } if (tmp2 + tmp > m_maxy) { m_maxy = tmp2 + tmp; } } } // for each session // if total hours for all sessions more than 0, register the day as valid if (total > 0) { m_days[dn] = day; m_hours[dn] = total; m_empty = false; } } else { ////////////////////////////////////////////////////////////////////////////// // Data Channel summary charts ////////////////////////////////////////////////////////////////////////////// // For each Channel for (int j = 0; j < m_codes.size(); j++) { code = m_codes[j]; suboffset = 0; type = m_type[j]; EventDataType typeval = m_typeval[j]; day = d.value(); CPAPMode mode = (CPAPMode)(int)day->settings_max(CPAP_Mode); // ignore irrelevent day objects if (day->machine(m_machinetype) == nullptr) { continue; } bool hascode = //day->channelHasData(code) || (type == ST_HOURS) || (type == ST_SESSIONS) || day->settingExists(code) || day->hasData(code, type); if (code == CPAP_Pressure) { if ((cpapmode > MODE_CPAP) && (mode == MODE_CPAP)) { hascode = false; if ((type == ST_WAVG) || (type == ST_AVG) || ((type == ST_PERC) && (typeval == 0.5))) { type = ST_SETWAVG; hascode = true; } } else { type = m_type[j]; } } //if (code==CPAP_Hypopnea) { // Make sure at least one of the CPAP data gets through with 0 // hascode=true; //} if (hascode) { m_days[dn] = day; switch (type) { case ST_AVG: tmp = day->avg(code); break; case ST_SUM: tmp = day->sum(code); break; case ST_WAVG: tmp = day->wavg(code); break; case ST_90P: tmp = day->p90(code); break; case ST_PERC: tmp = day->percentile(code, typeval); break; case ST_MIN: tmp = day->Min(code); break; case ST_MAX: tmp = day->Max(code); break; case ST_CNT: tmp = day->count(code); break; case ST_CPH: tmp = day->count(code) / day->hours(m_machinetype); break; case ST_SPH: tmp = day->sph(code); break; case ST_HOURS: tmp = day->hours(m_machinetype); break; case ST_SESSIONS: tmp = day->size(); break; case ST_SETMIN: tmp = day->settings_min(code); break; case ST_SETMAX: tmp = day->settings_max(code); break; case ST_SETAVG: tmp = day->settings_avg(code); break; case ST_SETWAVG: tmp = day->settings_wavg(code); break; case ST_SETSUM: tmp = day->settings_sum(code); break; default: tmp = 0; break; } if (suboffset > 0) { tmp -= suboffset; if (tmp < 0) { tmp = 0; } } total += tmp; m_values[dn][j + 1] = tmp; if (tmp < m_miny) { m_miny = tmp; } if (tmp > m_maxy) { m_maxy = tmp; } m_goodcodes[j] = true; fnd = true; } } if (fnd) { if (!m_fday) { m_fday = dn; } m_values[dn][0] = total; m_hours[dn] = day->hours(m_machinetype); if (m_graphtype == GT_BAR) { if (total < m_miny) { m_miny = total; } if (total > m_maxy) { m_maxy = total; } } } } } m_empty = true; for (const auto & goodcode : m_goodcodes) { if (goodcode) { m_empty = false; break; } } if (m_graphtype == GT_BAR) { m_miny = 0; } // m_minx=qint64(QDateTime(p_profile->FirstDay(),QTime(0,0,0),Qt::UTC).toTime_t())*1000L; m_maxx = qint64(QDateTime(p_profile->LastDay(), QTime(23, 59, 0), Qt::UTC).toTime_t()) * 1000L; m_physmaxy = m_maxy; m_physminy = m_miny; } void SummaryChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { int left = region.boundingRect().left(); int top = region.boundingRect().top(); int width = region.boundingRect().width(); int height = region.boundingRect().height(); if (!m_visible) { return; } GraphType graphtype = m_graphtype; if (graphtype == GT_LINE || graphtype == GT_POINTS) { bool pts = AppSetting->overviewLinechartMode() == OLC_Lines; graphtype = pts ? GT_POINTS : GT_LINE; } rtop = top; painter.setPen(QColor(Qt::black)); painter.drawLine(left, top, left, top+height); painter.drawLine(left, top+height, left+width, top+height); painter.drawLine(left+width, top+height, left+width, top); painter.drawLine( left+width, top, left, top); qint64 minx = w.min_x, maxx = w.max_x; int days = ceil(double(maxx-minx) / 86400000.0); bool buttuglydaysteps = false ; //!p_profile->appearance->animations(); double lcursor = w.graphView()->currentTime(); if (days >= 1) { double b = w.max_x - w.min_x; double a = lcursor - w.min_x; double c = a / b; if (buttuglydaysteps) { // this kills the beautiful smooth scrolling and makes days stop on day boundaries :( minx = floor(double(minx)/86400000.0); minx *= 86400000L; maxx = minx + 86400000L * qint64(days)-1; } b = maxx - minx; double d = c * b; lcursor = d + minx; } qint64 xx = maxx - minx; EventDataType miny = m_physminy; EventDataType maxy = m_physmaxy; w.roundY(miny, maxy); EventDataType yy = maxy - miny; EventDataType ymult = float(height - 2) / yy; barw = (float(width) / float(days)); // graph = &w; float px;// = left; l_left = w.marginLeft() + gYAxis::Margin; l_top = w.marginTop(); l_width = width; l_height = height; float py; EventDataType total; int daynum = 0; EventDataType h, tmp, tmp2; l_offset = (minx) % 86400000L; offset = float(l_offset) / 86400000.0; offset *= barw; px = left - offset; l_minx = minx; l_maxx = maxx + 86400000L; int total_days = 0; double total_val = 0; double total_hours = 0; bool lastdaygood = false; QVector totalcounts; QVector totalvalues; QVector lastX; QVector lastY; int numcodes = m_codes.size(); totalcounts.resize(numcodes); totalvalues.resize(numcodes); lastX.resize(numcodes); lastY.resize(numcodes); int zd = minx / 86400000L; zd--; auto d = m_values.find(zd); QVector goodcodes; goodcodes.resize(m_goodcodes.size()); lastdaygood = true; // Display Line Cursor if (AppSetting->lineCursorMode()) { qint64 time = lcursor; double xmult = double(width) / xx; if ((time > minx) && (time < maxx)) { double xpos = (time - minx) * xmult; painter.setPen(QPen(QBrush(QColor(0,255,0,255)),1)); painter.drawLine(left+xpos, top-w.marginTop()-3, left+xpos, top+height+w.bottom-1); } // QDateTime dt=QDateTime::fromMSecsSinceEpoch(time,Qt::UTC); // QString text = dt.date().toString(Qt::SystemLocaleLongDate); // int wid, h; // GetTextExtent(text, wid, h); // w.renderText(text, left + width/2 - wid/2, top-h+5); } for (int i = 0; i < numcodes; i++) { totalcounts[i] = 0; // Set min and max to the opposite largest starting value if ((m_type[i] == ST_MIN) || (m_type[i] == ST_SETMIN)) { totalvalues[i] = maxy; } else if ((m_type[i] == ST_MAX) || (m_type[i] == ST_SETMAX)) { totalvalues[i] = miny; } else { totalvalues[i] = 0; } // Turn off legend display.. It will only display if it's turned back on during draw. goodcodes[i] = false; if (!m_goodcodes[i]) { continue; } lastX[i] = px; if (d != m_values.end() && d.value().contains(i + 1)) { tmp = d.value()[i + 1]; h = tmp * ymult; } else { lastdaygood = false; h = 0; } lastY[i] = top + height - 1 - h; } float compliance_hours = 0; if (p_profile->cpap->showComplianceInfo()) { compliance_hours = p_profile->cpap->complianceHours(); } int incompliant = 0; Day *day; EventDataType hours; //quint32 * tptr; //EventStoreType * dptr; short px2, py2; const qint64 ms_per_day = 86400000L; painter.setClipRect(left, top, width, height); painter.setClipping(true); QColor summaryColor = QColor("dark gray"); float lineThickness = AppSetting->lineThickness(); for (qint64 Q = minx; Q <= maxx + ms_per_day; Q += ms_per_day) { zd = Q / ms_per_day; d = m_values.find(zd); if (Q < minx) { goto jumpnext; } if (d != m_values.end()) { day = m_days[zd]; bool summary_only = day && day->summaryOnly(); if (!m_hours.contains(zd)) { goto jumpnext; } hours = m_hours[zd]; int x1 = px; //x1-=(barw/2.0); int x2 = px + barw; //if (x1 < left) { x1 = left; } if (x2 > left + width) { x2 = left + width; } // if (x2add(x1 - 1, top, x1 - 1, top + height, x2, top + height, x2, top, col.rgba()); } else { painter.fillRect((x1+barw/2)-5, top, barw, height, QBrush(col)); // quads->add((x1 + barw / 2) - 5, top, (x1 + barw / 2) - 5, top + height, (x2 - barw / 2) + 5, // top + height, (x2 - barw / 2) + 5, top, col.rgba()); } } if (graphtype == GT_SESSIONS) { int j; auto times = m_times.find(zd); QColor col = m_colors[0]; //if (hourssetColor(Qt::black); int np = d.value().size(); if (np > 0) { for (auto & goodcode : goodcodes) { goodcode = true; } } for (j = 0; j < np; j++) { tmp2 = times.value()[j] - miny; py = top + height - (tmp2 * ymult); tmp = d.value()[j]; // length //tmp-=miny; h = tmp * ymult; QLinearGradient gradient(x1, py-h, x1+barw, py-h); gradient.setColorAt(0,col1); gradient.setColorAt(1,col2); painter.fillRect(x1, py-h, barw, h, QBrush(gradient)); // quads->add(x1, py, x1, py - h, x2, py - h, x2, py, col1, col2); if ((h > 0) && (barw > 2)) { painter.setPen(QColor(Qt::black)); painter.drawLine(x1, py, x1, py - h); painter.drawLine(x1, py - h, x2, py - h); painter.drawLine(x1, py, x2, py); painter.drawLine(x2, py, x2, py - h); } totalvalues[0] += hours * tmp; } totalcounts[0] += hours; totalvalues[1] += j; totalcounts[1]++; total_val += hours; total_hours += hours; total_days++; } else { if (!d.value().contains(0)) { goto jumpnext; } total = d.value()[0]; //if (total>0) { if (day) { EventDataType hours = m_hours[zd]; total_val += total * hours; total_hours += hours; total_days++; } py = top + height; //} bool good; SummaryType type; for (auto g=d.value().begin(), dend=d.value().end(); g != dend; g++) { short j = g.key(); if (!j) { continue; } j--; good = m_goodcodes[j]; if (!good) { continue; } type = m_type[j]; // code was actually used (to signal the display of the legend summary) goodcodes[j] = good; tmp = g.value(); QColor col = m_colors[j]; if (type == ST_HOURS) { if (tmp < compliance_hours) { col = QColor("#f04040"); incompliant++; } else if (summary_only) { col = summaryColor; } } if (zd == hl_day) { col = COLOR_Gold; } //if (!tmp) continue; if ((type == ST_MAX) || (type == ST_SETMAX)) { if (totalvalues[j] < tmp) { totalvalues[j] = tmp; } } else if ((type == ST_MIN) || (type == ST_SETMIN)) { if (totalvalues[j] > tmp) { totalvalues[j] = tmp; } } else { totalvalues[j] += tmp * hours; } //if (tmp) { totalcounts[j] += hours; //} tmp -= miny; h = tmp * ymult; // height in pixels if (graphtype == GT_BAR) { QColor col1 = col; QColor col2 = brighten(col,2.5); QLinearGradient gradient(x1, py-h, x1+barw, py-h); gradient.setColorAt(0,col1); gradient.setColorAt(1,col2); painter.fillRect(x1, py-h, barw, h, QBrush(gradient)); // quads->add(x1, py, x1, py - h, col1); // quads->add(x2, py - h, x2, py, col2); if (h > 0 && barw > 2) { painter.setPen(QColor(Qt::black)); painter.drawLine(x1, py, x1, py - h); painter.drawLine(x1, py - h, x2, py - h); painter.drawLine(x1, py, x2, py); painter.drawLine(x2, py, x2, py - h); } // if (bar py -= h; } else if (graphtype == GT_LINE) { // if (m_graphtype==GT_BAR QColor col1 = col; QColor col2 = m_colors[j]; px2 = px + barw; py2 = (top + height - 1) - h; // If more than 1 day between records, skip the vertical crud. if ((px2 - lastX[j]) > barw + 1) { lastdaygood = false; } if (lastdaygood) { if (lastY[j] != py2) { // vertical line painter.setPen(QPen(col2, lineThickness)); painter.drawLine(lastX[j], lastY[j], px, py2); } painter.setPen(QPen(col1, lineThickness)); painter.drawLine(px, py2, px2, py2); } else { painter.setPen(QPen(col1, lineThickness)); painter.drawLine(x1, py2, x2, py2); } lastX[j] = px2; lastY[j] = py2; } else if (graphtype == GT_POINTS) { QColor col1 = col; QColor col2 = m_colors[j]; px2 = px + barw; py2 = (top + height - 2) - h; // If more than 1 day between records, skip the vertical crud. if ((px2 - lastX[j]) > barw + 1) { lastdaygood = false; } if (zd == hl_day) { painter.setPen(QPen(brighten(col2),10)); painter.drawPoint(px2 - barw / 2, py2); } if (lastdaygood) { painter.setPen(QPen(col2, lineThickness)); painter.drawLine(lastX[j] - barw / 2, lastY[j], px2 - barw / 2, py2); } else { painter.setPen(QPen(col1, lineThickness)); painter.drawLine(px + barw / 2 - 1, py2, px + barw / 2 + 1, py2); } lastX[j] = px2; lastY[j] = py2; } } // for(QHashmaxx+extra) break; } else { if (Q < maxx) { incompliant++; } lastdaygood = false; } jumpnext: if (px >= left + width + barw) { break; } px += barw; daynum++; //lastQ=Q; } painter.setClipping(false); // Draw Ledgend px = left + width - 3; py = top - 5; int legendx = px; QString a, b; int x, y; QFontMetrics fm(*defaultfont); int bw = fm.width('X'); int bh = fm.height() / 1.8; bool ishours = false; int good = 0; for (int j = 0; j < m_codes.size(); j++) { if (!goodcodes[j]) { continue; } good++; SummaryType type = m_type[j]; ChannelID code = m_codes[j]; EventDataType tval = m_typeval[j]; switch (type) { case ST_WAVG: b = "Avg"; break; case ST_AVG: b = "Avg"; break; case ST_90P: b = "90%"; break; case ST_PERC: if (tval >= 0.99) { b = STR_TR_Max; } else if (tval == 0.5) { b = STR_TR_Med; } else { b = QString("%1%").arg(tval * 100.0, 0, 'f', 0); } break; //b=QString("%1%").arg(tval*100.0,0,'f',0); break; case ST_MIN: b = STR_TR_Min; break; case ST_MAX: b = STR_TR_Max; break; case ST_SETMIN: b = STR_TR_Min; break; case ST_SETMAX: b = STR_TR_Max; break; case ST_CPH: b = ""; break; case ST_SPH: b = "%"; break; case ST_HOURS: b = STR_UNIT_Hours; break; case ST_SESSIONS: b = STR_TR_Sessions; break; default: b = ""; break; } a = schema::channel[code].label(); if (a == w.title() && !b.isEmpty()) { a = b; } else { a += " " + b; } QString val; float f = 0; if (totalcounts[j] > 0) { if ((type == ST_MIN) || (type == ST_MAX) || (type == ST_SETMIN) || (type == ST_SETMAX)) { f = totalvalues[j]; } else { f = totalvalues[j] / totalcounts[j]; } } if (type == ST_HOURS) { int h = f; int m = int(f * 60) % 60; val.sprintf("%02i:%02i", h, m); ishours = true; } else { val = QString::number(f, 'f', 2); } a += ": " + val; //GetTextExtent(a,x,y); //float wt=20*w.printScaleX(); //px-=wt+x; //w.renderText(a,px+wt,py+1); //quads->add(px+wt-y/4-y,py-y,px+wt-y/4,py-y,px+wt-y/4,py+1,px+wt-y/4-y,py+1,m_colors[j].rgba()); //QString text=schema::channel[code].label(); int wid, hi; GetTextExtent(a, wid, hi); legendx -= wid; w.renderText(a, legendx, top - 4); // legendx-=bw/2; painter.fillRect(legendx - bw-4, top-w.marginTop()-1, bh, w.marginTop(), QBrush(m_colors[j])); legendx -= bw * 2; //lines->add(px,py,px+20,py,m_colors[j]); //lines->add(px,py+1,px+20,py+1,m_colors[j]); } if ((m_graphtype == GT_BAR) && (good > 0)) { if (m_type.size() > 1) { float val = total_val / float(total_hours); a = m_label + ": " + QString::number(val, 'f', 2) + " "; GetTextExtent(a, x, y); legendx -= x; w.renderText(a, legendx, py + 1); } } a = ""; /*if (m_graphtype==GT_BAR) { if (m_type.size()>1) { float val=total_val/float(total_days); a+=m_label+": "+QString::number(val,'f',2)+" "; // } }*/ a += QString(QObject::tr("Days: %1")).arg(total_days, 0); if (p_profile->cpap->showComplianceInfo()) { if (ishours && incompliant > 0) { a += " "+QString(QObject::tr("Low Usage Days: %1")).arg(incompliant, 0)+ " "+QString(QObject::tr("(%1% compliant, defined as > %2 hours)")). arg((1.0 / daynum) * (total_days - incompliant) * 100.0, 0, 'f', 2).arg(compliance_hours, 0, 'f', 1); } } //GetTextExtent(a,x,y); //legendx-=30+x; //w.renderText(a,px+24,py+5); w.renderText(a, left, py + 1); } QString formatTime(EventDataType v, bool show_seconds = false, bool duration = false, bool show_12hr = false) { int h = int(v); if (!duration) { h %= 24; } else { show_12hr = false; } int m = int(v * 60) % 60; int s = int(v * 3600) % 60; char pm[3] = {"am"}; if (show_12hr) { h >= 12 ? pm[0] = 'p' : pm[0] = 'a'; h %= 12; if (h == 0) { h = 12; } } else { pm[0] = 0; } if (show_seconds) { return QString().sprintf("%i:%02i:%02i%s", h, m, s, pm); } else { return QString().sprintf("%i:%02i%s", h, m, pm); } } bool SummaryChart::mouseMoveEvent(QMouseEvent *event, gGraph *graph) { graph->timedRedraw(0); int xposLeft = event->x(); int yPosTop = event->y(); if (!m_rect.contains(xposLeft, yPosTop)) { // if ((x<0 || y<0 || x>l_width || y>l_height)) { hl_day = -1; //graph->timedRedraw(2000); return false; } xposLeft -= m_rect.left(); yPosTop -= m_rect.top(); Q_UNUSED(yPosTop) double xx = l_maxx - l_minx; double xmult = xx / double(l_width + barw); qint64 mx = ceil(xmult * double(xposLeft - offset)); mx += l_minx; mx = mx + l_offset; //-86400000L; int zd = mx / 86400000L; Day *day; //if (hl_day!=zd) // This line is an optimization { hl_day = zd; graph->Trigger(2000); auto d = m_values.find(hl_day); QMap &valhash = d.value(); xposLeft += m_rect.left(); //gYAxis::Margin+gGraphView::titleWidth; //graph->m_marginleft+ int y = event->y() - m_rect.top() + rtop - 15; //QDateTime dt1=QDateTime::fromTime_t(hl_day*86400).toLocalTime(); QDateTime dt2 = QDateTime::fromTime_t(hl_day * 86400).toUTC(); // QDateTime dt2 = QDateTime::fromTime_t(hl_day * 86400).toLocalTime(); //QTime t1=dt1.time(); //QTime t2=dt2.time(); QDate dt = dt2.date(); day = m_days[zd]; if ((d != m_values.end()) && (day != nullptr)) { bool summary_only = day->summaryOnly(); QString strTooltip = dt.toString(Qt::SystemLocaleShortDate); // Day * day=m_days[hl_day]; //EventDataType val; QString val; if (m_graphtype == GT_SESSIONS) { if (m_type[0] == ST_HOURS) { int t = m_hours[zd] * 3600.0; int h = t / 3600; int m = (t / 60) % 60; //int s=t % 60; val.sprintf("%02i:%02i", h, m); } else { val = QString::number(d.value()[0], 'f', 2); } strTooltip += "\r\n" + m_label + ": " + val; if (m_type[1] == ST_SESSIONS) { strTooltip += " "+QString(QObject::tr("(Sess: %1)")).arg(day->size(), 0); } EventDataType v = m_times[zd][0]; int lastt = m_times[zd].size() - 1; if (lastt < 0) { lastt = 0; } strTooltip += "\r\n"+QString(QObject::tr("Bedtime: %1")).arg(formatTime(v, false, false, true)); v = m_times[zd][lastt] + m_values[zd][lastt]; strTooltip += "\r\n"+QString(QObject::tr("Waketime: %1")).arg(formatTime(v, false, false, true)); } else if (m_graphtype == GT_BAR) { if (m_type[0] == ST_HOURS) { int t = d.value()[0] * 3600.0; int h = t / 3600; int m = (t / 60) % 60; //int s=t % 60; val.sprintf("%02i:%02i", h, m); } else { val = QString::number(d.value()[0], 'f', 2); } strTooltip += "\r\n" + m_label + ": " + val; //z+="\r\nMode="+QString::number(day->settings_min("FlexSet"),'f',0); } else { QString strDataType; for (int i = 0; i < m_type.size(); i++) { if (!m_goodcodes[i]) { continue; } if (!valhash.contains(i + 1)) { continue; } EventDataType tval = m_typeval[i]; switch (m_type[i]) { case ST_WAVG: strDataType = STR_TR_WAvg; break; case ST_AVG: strDataType = STR_TR_Avg; break; case ST_90P: strDataType = QString("90%"); break; case ST_PERC: if (tval >= 0.99) { strDataType = STR_TR_Max; } else if (tval == 0.5) { strDataType = STR_TR_Med; } else { strDataType = QString("%1%").arg(tval * 100.0, 0, 'f', 0); } break; case ST_MIN: strDataType = STR_TR_Min; break; case ST_MAX: strDataType = STR_TR_Max; break; case ST_CPH: strDataType = ""; break; case ST_SPH: strDataType = "%"; break; case ST_HOURS: strDataType = STR_UNIT_Hours; break; case ST_SESSIONS: strDataType = STR_TR_Sessions; break; case ST_SETMIN: strDataType = STR_TR_Min; break; case ST_SETMAX: strDataType = STR_TR_Max; break; default: strDataType = ""; break; } if (m_type[i] == ST_SESSIONS) { val = QString::number(d.value()[i + 1], 'f', 0); strTooltip += "\r\n" + strDataType + ": " + val; } else { //if (day && (day->channelExists(m_codes[i]) || day->settingExists(m_codes[i]))) { schema::Channel &chan = schema::channel[m_codes[i]]; EventDataType v; if (valhash.contains(i + 1)) { v = valhash[i + 1]; } else { v = 0; } if (m_codes[i] == Journal_Weight) { val = weightString(v, p_profile->general->unitSystem()); } else { val = QString::number(v, 'f', 2); } strTooltip += "\r\n" + chan.label() + " " + strDataType + ": " + val; //} } } } if (summary_only) { strTooltip += "\r\n"+QObject::tr("(Summary Only)"); } graph->ToolTip(strTooltip, xposLeft, y - 15); return false; } else { QString z = dt.toString(Qt::SystemLocaleShortDate) + "\r\n"+QObject::tr("No Data"); graph->ToolTip(z, xposLeft, y - 15); return false; } } return false; } bool SummaryChart::mousePressEvent(QMouseEvent *event, gGraph *graph) { if (event->modifiers() & Qt::ShiftModifier) { //qDebug() << "Jump to daily view?"; return true; } Q_UNUSED(graph) return false; } bool SummaryChart::keyPressEvent(QKeyEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) //qDebug() << "Summarychart Keypress"; return false; } #include "mainwindow.h" extern MainWindow *mainwin; bool SummaryChart::mouseReleaseEvent(QMouseEvent *event, gGraph *graph) { if (event->modifiers() & Qt::ShiftModifier) { if (hl_day < 0) { mouseMoveEvent(event, graph); } if (hl_day > 0) { QDateTime d = QDateTime::fromTime_t(hl_day * 86400).toUTC(); // QDateTime d = QDateTime::fromTime_t(hl_day * 86400).toLocalTime(); mainwin->getDaily()->LoadDate(d.date()); mainwin->JumpDaily(); //qDebug() << "Jump to daily view?" << d; return true; } } Q_UNUSED(event) hl_day = -1; graph->timedRedraw(2000); return false; } OSCAR-code-v1.3.1/oscar/Graphs/gSummaryChart.h000066400000000000000000000113771417327530600207740ustar00rootroot00000000000000/* gSummaryChart Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GBARCHART_H #define GBARCHART_H #include #include "gGraphView.h" #include "gXAxis.h" /*! \enum GraphType \value GT_BAR Display as a BarGraph \value GT_LINE Display as a line plot \value GT_SESSIONS Display type for session times chart */ enum GraphType { GT_BAR, GT_LINE, GT_POINTS, GT_SESSIONS }; /*! \class SummaryChart \brief The main overall chart type layer used in Overview page */ class SummaryChart: public Layer { public: //! \brief Constructs a SummaryChart with QString label, of GraphType type SummaryChart(QString label, GraphType type = GT_BAR); virtual ~SummaryChart(); //! \brief Renders the graph to the QPainter object virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); //! \brief Precalculation code prior to drawing. Day object is not needed here, it's just here for Layer compatability. virtual void SetDay(Day *day = nullptr); //! \brief Returns true if no data was found for this day during SetDay virtual bool isEmpty() { return m_empty; } //! \brief Adds a layer to the summaryChart (When in Bar mode, it becomes culminative, eg, the AHI chart) void addSlice(ChannelID code, QColor color, SummaryType type, EventDataType tval = 0.00f) { m_codes.push_back(code); m_colors.push_back(color); m_type.push_back(type); //m_zeros.push_back(ignore_zeros); m_typeval.push_back(tval); } //! \brief Deselect highlighting (the gold bar) virtual void deselect() { hl_day = -1; } //! \brief Returns true if currently selected.. virtual bool isSelected() { return hl_day >= 0; } //! \brief Sets the MachineType this SummaryChart is interested in void setMachineType(MachineType type) { m_machinetype = type; } //! \brief Returns the MachineType this SummaryChart is interested in MachineType machineType() { return m_machinetype; } virtual Layer * Clone() { SummaryChart * sc = new SummaryChart(m_label); Layer::CloneInto(sc); CloneInto(sc); return sc; } void CloneInto(SummaryChart * layer) { layer->m_orientation = m_orientation; layer->m_colors = m_colors; layer->m_codes = m_codes; layer->m_goodcodes = m_goodcodes; layer->m_type = m_type; layer->m_typeval = m_typeval; layer->m_values = m_values; layer->m_times = m_times; layer->m_hours = m_hours; layer->m_days = m_days; layer->m_empty = m_empty; layer->m_fday = m_fday; layer->m_label = m_label; layer->barw = barw; layer->l_offset = l_offset; layer->offset = offset; layer->l_left = l_left; layer->l_top = l_top; layer->l_width= l_width; layer->l_height = l_height; layer->rtop = rtop; layer->l_minx = l_minx; layer->l_maxx = l_maxx; layer->hl_day = hl_day; layer->m_graphtype = m_graphtype; layer->m_machinetype = m_machinetype; layer->tz_offset = tz_offset; layer->tz_hours = tz_hours; } protected: Qt::Orientation m_orientation; QVector m_colors; QVector m_codes; QVector m_goodcodes; //QVector m_zeros; QVector m_type; QVector m_typeval; QHash > m_values; QHash > m_times; QHash m_hours; QHash m_days; bool m_empty; int m_fday; QString m_label; float barw; // bar width from last draw qint64 l_offset; // last offset float offset; // in pixels; int l_left, l_top, l_width, l_height; int rtop; qint64 l_minx, l_maxx; int hl_day; //gGraph *graph; GraphType m_graphtype; MachineType m_machinetype; int tz_offset; float tz_hours; //! \brief Key was pressed that effects this layer virtual bool keyPressEvent(QKeyEvent *event, gGraph *graph); //! \brief Mouse moved over this layers area (shows the hover-over tooltips here) virtual bool mouseMoveEvent(QMouseEvent *event, gGraph *graph); //! \brief Mouse Button was pressed over this area virtual bool mousePressEvent(QMouseEvent *event, gGraph *graph); //! \brief Mouse Button was released over this area. (jumps to daily view here) virtual bool mouseReleaseEvent(QMouseEvent *event, gGraph *graph); }; #endif // GBARCHART_H OSCAR-code-v1.3.1/oscar/Graphs/gXAxis.cpp000066400000000000000000000334441417327530600177430ustar00rootroot00000000000000/* gXAxis Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include "Graphs/gXAxis.h" #include "SleepLib/profiles.h" #include "SleepLib/common.h" #include "Graphs/glcommon.h" #include "Graphs/gGraph.h" #include "Graphs/gGraphView.h" // These divisors are used to round xaxis timestamps to reasonable increments const quint64 divisors[] = { 15552000000ULL, 7776000000ULL, 5184000000ULL, 2419200000ULL, 1814400000ULL, 1209600000L, 604800000L, 259200000L, 172800000L, 86400000, 2880000, 14400000, 7200000, 3600000, 2700000, 1800000, 1200000, 900000, 600000, 300000, 120000, 60000, 45000, 30000, 20000, 15000, 10000, 5000, 2000, 1000, 500, 100, 50, 10, 1 }; const int divcnt = sizeof(divisors) / sizeof(quint64); gXAxis::gXAxis(QColor col, bool fadeout) : Layer(NoChannel) { m_line_color = col; m_text_color = col; m_major_color = Qt::darkGray; m_minor_color = Qt::lightGray; m_show_major_lines = false; m_show_minor_lines = false; m_show_minor_ticks = true; m_show_major_ticks = true; m_utcfix = false; m_fadeout = fadeout; tz_offset = timezoneOffset(); tz_hours = tz_offset / 3600000.0; m_roundDays = false; } gXAxis::~gXAxis() { } int gXAxis::minimumHeight() { QFontMetrics fm(*defaultfont); int h = fm.height(); #if defined(Q_OS_MAC) return 9+h; #else return 11+h; #endif } const QString months[] = { QObject::tr("Jan"), QObject::tr("Feb"), QObject::tr("Mar"), QObject::tr("Apr"), QObject::tr("May"), QObject::tr("Jun"), QObject::tr("Jul"), QObject::tr("Aug"), QObject::tr("Sep"), QObject::tr("Oct"), QObject::tr("Nov"), QObject::tr("Dec") }; //static QString dow[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; void gXAxis::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { float left = region.boundingRect().left(); float top = region.boundingRect().top()-0.999f; float width = region.boundingRect().width(); float height = region.boundingRect().height(); QVector ticks; QPainter painter2; // Only need this for pixmap caching // pixmap caching screws font size when printing QFontMetrics fm(*defaultfont); bool usepixmap = AppSetting->usePixmapCaching(); // Whether or not to use pixmap caching if (!usepixmap || (usepixmap && w.invalidate_xAxisImage)) { // Redraw graph xaxis labels and ticks either to pixmap or directly to screen if (usepixmap) { // Initialize a new cache image m_image = QImage(width + 22, height + 4, QImage::Format_ARGB32_Premultiplied); m_image.fill(Qt::transparent); painter2.begin(&m_image); painter2.setPen(Qt::black); painter2.setFont(*defaultfont); } double px, py; int start_px = left; //int start_py=top; //int width=scrx-(w.GetLeftMargin()+w.GetRightMargin()); // float height=scry-(w.GetTopMargin()+w.GetBottomMargin()); if (width < 40) { return; } qint64 minx; qint64 maxx; if (w.blockZoom()) { // Lock zoom to entire data range minx = w.rmin_x; maxx = w.rmax_x; } else { // Allow zoom minx = w.min_x; maxx = w.max_x; } int days = ceil(double(maxx-minx) / 86400000.0); if (m_roundDays) { minx = floor(double(minx)/86400000.0); minx *= 86400000L; maxx = minx + 86400000L * qint64(days); } // duration of graph display window in milliseconds. qint64 xx = maxx - minx; // shouldn't really be negative, but this is safer than an assert if (xx <= 0) { return; } //Most of this could be precalculated when min/max is set.. QString fd, tmpstr; int divmax, dividx; int fitmode; // Have a quick look at the scale and prep the autoscaler a little faster if (xx >= 86400000L) { // Day fd = "Mjj 00"; dividx = 0; divmax = 10; fitmode = 0; } else if (xx > 1800000) { // Minutes fd = " j0:00"; dividx = 10; divmax = 21; fitmode = 1; } else if (xx > 5000) { // Seconds fd = " j0:00:00"; dividx = 16; divmax = 29; fitmode = 2; } else { // Microseconds fd = "j0:00:00:000"; dividx = 28; divmax = divcnt; fitmode = 3; } //if (divmax>divcnt) divmax=divcnt; int x, y; // grab the text extent of the dummy text fields above to know how much space is needed QRect r2 = fm.boundingRect(fd); x = r2.width(); y = r2.height(); // Not sure when this was a problem... if (x<=0) { qWarning() << "gXAxis::Paint called with x<=0"; return; } // Max number of ticks that will fit, with a bit of room for a buffer int max_ticks = width / (x + 15); int fit_ticks = 0; int div = -1; qint64 closest = 0, tmp, tmpft; // Scan through divisor list with the index range given above, to find which // gives the closest number of ticks to the maximum that will physically fit for (int i = dividx; i < divmax; i++) { tmpft = xx / divisors[i]; tmp = max_ticks - tmpft; if (tmp < 0) { continue; } if (tmpft > closest) { // Find the closest scale to the number closest = tmpft; // that will fit div = i; fit_ticks = tmpft; } } if (fit_ticks == 0) { qDebug() << "gXAxis::Plot() Couldn't fit ticks.. Too short?" << minx << maxx << xx; return; } if ((div < 0) || (div > divcnt)) { qDebug() << "gXAxis::Plot() div out of bounds"; return; } qint64 step = divisors[div]; //Align left minimum to divisor by losing precision qint64 aligned_start = minx / step; aligned_start *= step; while (aligned_start < minx) { aligned_start += step; } painter.setPen(QColor(Qt::black)); //int utcoff=m_utcfix ? tz_hours : 0; //utcoff=0; int num_minor_ticks; if (step >= 86400000) { qint64 i = step / 86400000L; // number of days if (i > 14) { i /= 2; } if (i < 0) { i = 1; } num_minor_ticks = i; } else { num_minor_ticks = 10; } float xmult = double(width) / double(xx); float step_pixels = double(step / float(num_minor_ticks)) * xmult; py = left + float(aligned_start - minx) * xmult; //py+=usepixmap ? 20 : left; int mintop = top + 3.0 * (float(y) / 10.0); int majtop = top + 6.0 * (float(y) / 10.0); int texttop = majtop + y; // 18*w.printScaleY(); #if defined (Q_OS_MAC) texttop += 2; #endif // Fill in the minor tick marks up to the first major alignment tick for (int i = 0; i < num_minor_ticks; i++) { py -= step_pixels; if (py < start_px) { continue; } if (usepixmap) { ticks.append(QLineF(py - left + 20, 0, py - left + 20, mintop - top)); } else { ticks.append(QLineF(py, top+2, py, mintop+2)); } } int ms, m, h, s, d; qint64 j; for (qint64 i = aligned_start; i < maxx; i += step) { px = (i - minx) * xmult; px += left; if (usepixmap) { ticks.append(QLineF(px - left + 20, 0, px - left + 20, majtop - top)); } else { ticks.append(QLineF(px, top+2, px, majtop+2)); } j = i; if (!m_utcfix) { j += tz_offset; } ms = j % 1000; s = (j / 1000L) % 60L; m = (j / 60000L) % 60L; h = (j / 3600000L) % 24L; //int d=(j/86400000) % 7; if (fitmode == 0) { d = (j / 1000); QDateTime dt = QDateTime::fromTime_t(d).toLocalTime(); QDate date = dt.date(); // SLOW SLOW SLOW!!! On Mac especially, this function is pathetically slow. //dt.toString("MMM dd"); // Doing it this way instead because it's MUUUUUUCH faster tmpstr = QString(dayFirst?"%1 %2":"%2 %1").arg(date.day()).arg(months[date.month() - 1]); //} else if (fitmode==0) { // tmpstr=QString("%1 %2:%3").arg(dow[d]).arg(h,2,10,QChar('0')).arg(m,2,10,QChar('0')); } else if (fitmode == 1) { // minute tmpstr = QString("%1:%2").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0')); } else if (fitmode == 2) { // second tmpstr = QString("%1:%2:%3").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0')).arg(s, 2, 10, QChar('0')); } else if (fitmode == 3) { // milli tmpstr = QString("%1:%2:%3:%4").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0')).arg(s, 2, 10, QChar('0')).arg(ms, 3, 10, QChar('0')); } int tx = px - x / 2.0; if (m_utcfix) { tx += step_pixels / 2.0; } if ((tx + x) < (left + width)) { if (!usepixmap) { w.renderText(tmpstr, tx, texttop, 0, Qt::black, defaultfont); } else { painter2.drawText(tx - left + 20, texttop - top, tmpstr); } } py = px; for (int j = 1; j < num_minor_ticks; j++) { py += step_pixels; if (py >= left + width) { break; } if (usepixmap) { ticks.append(QLineF(py - left + 20, 0, py - left + 20, mintop - top)); } else { ticks.append(QLineF(py, top+2, py, mintop+2)); } } } if (usepixmap) { painter2.drawLines(ticks); painter2.end(); } else { painter.drawLines(ticks); } w.graphView()->lines_drawn_this_frame += ticks.size(); w.invalidate_xAxisImage = false; } if (usepixmap && !m_image.isNull()) { painter.drawImage(QPoint(left - 20, top + height - m_image.height() + 5), m_image); } } gXAxisDay::gXAxisDay(QColor col) :Layer(NoChannel) { m_line_color = col; m_text_color = col; m_major_color = Qt::darkGray; m_minor_color = Qt::lightGray; m_show_major_lines = false; m_show_minor_lines = false; m_show_minor_ticks = true; m_show_major_ticks = true; } gXAxisDay::~gXAxisDay() { } int gXAxisDay::minimumHeight() { QFontMetrics fm(*defaultfont); int h = fm.height(); #if defined(Q_OS_MAC) return 9+h; #else return 11+h; #endif } void gXAxisDay::paint(QPainter &painter, gGraph &graph, const QRegion ®ion) { float left = region.boundingRect().left(); float top = region.boundingRect().top(); float width = region.boundingRect().width(); //float height = region.boundingRect().height(); QString months[] = { QObject::tr("Jan"), QObject::tr("Feb"), QObject::tr("Mar"), QObject::tr("Apr"), QObject::tr("May"), QObject::tr("Jun"), QObject::tr("Jul"), QObject::tr("Aug"), QObject::tr("Sep"), QObject::tr("Oct"), QObject::tr("Nov"), QObject::tr("Dec") }; qint64 minx; qint64 maxx; minx = graph.min_x; maxx = graph.max_x; QDateTime date2 = QDateTime::fromMSecsSinceEpoch(minx, Qt::LocalTime); // QDateTime enddate2 = QDateTime::fromMSecsSinceEpoch(maxx, Qt::UTC); //qInfo() << "Drawing date axis from " << date2 << " to " << enddate2; QDate date = date2.date(); // QDate enddate = enddate2.date(); int days = ceil(double(maxx - minx) / 86400000.0); float barw = width / float(days); qint64 xx = maxx - minx; // shouldn't really be negative, but this is safer than an assert if (xx <= 0) { return; } float lastx = left; float y1 = top; QString fd = "Mjj 00"; int x,y; GetTextExtent(fd, x, y); float xpos = (barw / 2.0) - (float(x) / 2.0); float lastxpos = 0; QVector lines; for (int i=0; i < days; i++) { if ((lastx + barw) > (left + width + 1)) break; QString tmpstr = QString(dayFirst?"%2 %1":"%1 %2").arg(months[date.month() - 1]).arg(date.day(), 2, 10, QChar('0')); float x1 = lastx + xpos; //lines.append(QLine(lastx, top, lastx, top+6)); if (x1 > (lastxpos + x + 8*graph.printScaleX())) { graph.renderText(tmpstr, x1, y1 + y + 8); lastxpos = x1; lines.append(QLineF(lastx+barw/2, top, lastx+barw/2, top+6)); } lastx = lastx + barw; date = date.addDays(1); } painter.setPen(QPen(Qt::black,1)); painter.drawLines(lines); } gXAxisPressure::gXAxisPressure(QColor col) :Layer(NoChannel) { m_line_color = col; m_text_color = col; m_major_color = Qt::darkGray; m_minor_color = Qt::lightGray; m_show_major_lines = false; m_show_minor_lines = false; m_show_minor_ticks = true; m_show_major_ticks = true; } gXAxisPressure::~gXAxisPressure() { } int gXAxisPressure::minimumHeight() { QFontMetrics fm(*defaultfont); int h = fm.height(); #if defined(Q_OS_MAC) return 9+h; #else return 11+h; #endif } void gXAxisPressure::paint(QPainter & /*painter*/, gGraph &/*graph*/, const QRegion &/*region*/) { } OSCAR-code-v1.3.1/oscar/Graphs/gXAxis.h000066400000000000000000000130051417327530600173770ustar00rootroot00000000000000/* gXAxis Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GXAXIS_H #define GXAXIS_H #include #include #include "Graphs/layer.h" /*! \class gXAxis \brief Draws the XTicker timescales underneath graphs */ class gXAxis: public Layer { public: static const int Margin = 30; // How much room does this take up. (Bottom margin) public: gXAxis(QColor col = Qt::black, bool fadeout = true); virtual ~gXAxis(); virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); void SetShowMinorLines(bool b) { m_show_minor_lines = b; } void SetShowMajorLines(bool b) { m_show_major_lines = b; } bool ShowMinorLines() { return m_show_minor_lines; } bool ShowMajorLines() { return m_show_major_lines; } void SetShowMinorTicks(bool b) { m_show_minor_ticks = b; } void SetShowMajorTicks(bool b) { m_show_major_ticks = b; } bool ShowMinorTicks() { return m_show_minor_ticks; } bool ShowMajorTicks() { return m_show_major_ticks; } void setUtcFix(bool b) { m_utcfix = b; } void setRoundDays(bool b) { m_roundDays = b; } //! \brief Returns the minimum height needed to fit virtual int minimumHeight(); virtual Layer * Clone() { gXAxis * xaxis = new gXAxis(); Layer::CloneInto(xaxis); CloneInto(xaxis); return xaxis; } void CloneInto(gXAxis * layer) { layer->m_show_major_ticks = m_show_major_ticks; layer->m_show_minor_ticks = m_show_minor_ticks; layer->m_show_major_lines = m_show_major_lines; layer->m_show_minor_lines = m_show_minor_lines; layer->m_major_color = m_major_color; layer->m_minor_color = m_minor_color; layer->m_line_color = m_line_color; layer->m_text_color = m_text_color; layer->m_utcfix = m_utcfix; layer->m_fadeout = m_fadeout; layer->tz_offset = tz_offset; layer->tz_hours = tz_hours; layer->m_image = m_image; layer->m_roundDays = m_roundDays; } protected: bool m_show_major_lines; bool m_show_minor_lines; bool m_show_minor_ticks; bool m_show_major_ticks; bool m_utcfix; QColor m_line_color; QColor m_text_color; QColor m_major_color; QColor m_minor_color; bool m_fadeout; qint64 tz_offset; float tz_hours; QImage m_image; bool m_roundDays; }; class gXAxisDay: public Layer { public: static const int Margin = 30; // How much room does this take up. (Bottom margin) public: gXAxisDay(QColor col = Qt::black); virtual ~gXAxisDay(); virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); void SetShowMinorLines(bool b) { m_show_minor_lines = b; } void SetShowMajorLines(bool b) { m_show_major_lines = b; } bool ShowMinorLines() { return m_show_minor_lines; } bool ShowMajorLines() { return m_show_major_lines; } void SetShowMinorTicks(bool b) { m_show_minor_ticks = b; } void SetShowMajorTicks(bool b) { m_show_major_ticks = b; } bool ShowMinorTicks() { return m_show_minor_ticks; } bool ShowMajorTicks() { return m_show_major_ticks; } //! \brief Returns the minimum height needed to fit virtual int minimumHeight(); virtual Layer * Clone() { gXAxisDay * xaxis = new gXAxisDay(); Layer::CloneInto(xaxis); CloneInto(xaxis); return xaxis; } void CloneInto(gXAxisDay * layer) { layer->m_show_major_ticks = m_show_major_ticks; layer->m_show_minor_ticks = m_show_minor_ticks; layer->m_show_major_lines = m_show_major_lines; layer->m_show_minor_lines = m_show_minor_lines; layer->m_major_color = m_major_color; layer->m_minor_color = m_minor_color; layer->m_line_color = m_line_color; layer->m_text_color = m_text_color; layer->m_image = m_image; } protected: bool m_show_major_lines; bool m_show_minor_lines; bool m_show_minor_ticks; bool m_show_major_ticks; QColor m_line_color; QColor m_text_color; QColor m_major_color; QColor m_minor_color; QImage m_image; }; class gXAxisPressure: public Layer { public: static const int Margin = 30; // How much room does this take up. (Bottom margin) public: gXAxisPressure(QColor col = Qt::black); virtual ~gXAxisPressure(); virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); virtual int minimumHeight(); virtual Layer * Clone() { gXAxisPressure * xaxis = new gXAxisPressure(); Layer::CloneInto(xaxis); CloneInto(xaxis); return xaxis; } void CloneInto(gXAxisPressure * layer) { layer->m_show_major_ticks = m_show_major_ticks; layer->m_show_minor_ticks = m_show_minor_ticks; layer->m_show_major_lines = m_show_major_lines; layer->m_show_minor_lines = m_show_minor_lines; layer->m_major_color = m_major_color; layer->m_minor_color = m_minor_color; layer->m_line_color = m_line_color; layer->m_text_color = m_text_color; //layer->m_image = m_image; } protected: bool m_show_major_lines; bool m_show_minor_lines; bool m_show_minor_ticks; bool m_show_major_ticks; QColor m_line_color; QColor m_text_color; QColor m_major_color; QColor m_minor_color; }; #endif // GXAXIS_H OSCAR-code-v1.3.1/oscar/Graphs/gYAxis.cpp000066400000000000000000000201771417327530600177430ustar00rootroot00000000000000/* gYAxis Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "Graphs/gYAxis.h" #include #include #include "Graphs/glcommon.h" #include "Graphs/gGraph.h" #include "Graphs/gGraphView.h" #include "SleepLib/profiles.h" #include gXGrid::gXGrid(QColor col) : Layer(NoChannel) { Q_UNUSED(col) m_major_color = QColor(180, 180, 180, 64); //m_major_color=QColor(180,180,180,92); m_minor_color = QColor(230, 230, 230, 64); m_show_major_lines = true; m_show_minor_lines = true; } gXGrid::~gXGrid() { } void gXGrid::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { float left = region.boundingRect().left(); float top = region.boundingRect().top()+0.001f; float width = region.boundingRect().width(); float height = region.boundingRect().height(); //int x, y; EventDataType miny = w.physMinY(); EventDataType maxy = w.physMaxY(); w.roundY(miny, maxy); //EventDataType dy=maxy-miny; if (height < 0) { return; } // static QString fd = "0"; // GetTextExtent(fd, x, y); QFontMetrics fm(*defaultfont); int y=fm.height(); double max_yticks = round(height / (y + 14.0*w.printScaleY())); // plus spacing between lines //double yt=1/max_yticks; double mxy = maxy; //MAX(fabs(maxy), fabs(miny)); double mny = miny; // if (miny < 0) { // mny = -mxy; // } double rxy = mxy - mny; int myt; bool fnd = false; for (myt = max_yticks; myt >= 1.001; myt--) { float v = rxy / float(myt); if (float(v) == int(v)) { fnd = true; break; } } if (fnd) { max_yticks = myt; } else { max_yticks = 2; } double yt = 1 / max_yticks; double ymult = height / rxy; double min_ytick = rxy * yt; float ty, h; if (min_ytick <= 0) { qDebug() << "min_ytick error in gXGrid::paint() in" << w.title(); return; } if (min_ytick >= 1000000) { min_ytick = 100; } QVector majorlines; QVector minorlines; for (double i = miny; i <= maxy + min_ytick + 0.001; i += min_ytick) { ty = (i - miny) * ymult; h = top + height - ty; if (m_show_major_lines && (i > miny)) { majorlines.append(QLineF(left, h, left + width, h)); } double z = (min_ytick / 4) * ymult; double g = h; for (int i = 0; i < 3; i++) { g += z; if (g > top + height) { break; } //if (vertcnt>=maxverts) { // qWarning() << "vertarray bounds exceeded in gYAxis for " << w.title() << "graph" << "MinY =" <= 1000000) { min_ytick = 100; } QVector ticks; QRect r2; float shorttick = 4.0 * w.printScaleX(); for (double i = miny; i <= maxy + min_ytick + 0.001; i += min_ytick) { ty = (i - miny) * ymult; if (dy < 5) { fd = Format(i * m_yaxis_scale, 2); } else { fd = Format(i * m_yaxis_scale, 1); } r2 = fm.boundingRect(fd); x = r2.width(); y = r2.height(); //GetTextExtent(fd, x, y); // performance bottleneck.. if (x > labelW) { labelW = x; } h = top + height - ty; if (h < top-0.002) { continue; } w.renderText(fd, left + width - shorttick*2 - x, (h + (y / 2.0)), 0, m_text_color, defaultfont); ticks.append(QLineF(left + width - shorttick, h, left + width, h)); double z = (min_ytick / 4) * ymult; double g = h; for (int i = 0; i < 3; i++) { g += z; if (g > top + height + 0.002) { break; } ticks.append(QLineF(left + width - shorttick/2, g, left + width, g)); } } painter.setPen(Qt::black); painter.drawLines(ticks); w.graphView()->lines_drawn_this_frame += ticks.size(); } } const QString gYAxis::Format(EventDataType v, int dp) { return QString::number(v, 'f', dp); } bool gYAxis::mouseMoveEvent(QMouseEvent *event, gGraph *graph) { if (!AppSetting->graphTooltips()) { return false; } graph->timedRedraw(0); int x = event->x(); int y = event->y(); if (!graph->units().isEmpty()) { graph->ToolTip(graph->units(), x+10, y+10, TT_AlignLeft); // graph->redraw(); } return false; } bool gYAxis::mouseDoubleClickEvent(QMouseEvent *event, gGraph *graph) { if (graph) { // int x=event->x(); // int y=event->y(); short z = (graph->zoomY() + 1) % gGraph::maxZoomY; graph->setZoomY(z); qDebug() << "Mouse double clicked for" << graph->name() << z; } Q_UNUSED(event); return false; } const QString gYAxisTime::Format(EventDataType v, int dp) { int h = int(v) % 24; int m = int(v * 60) % 60; int s = int(v * 3600) % 60; char pm[3] = {"pm"}; if (show_12hr) { h >= 12 ? pm[0] = 'a' : pm[0] = 'p'; h %= 12; if (h == 0) { h = 12; } } else { pm[0] = 0; } if (dp > 2) { return QString().sprintf("%02i:%02i:%02i%s", h, m, s, pm); } return QString().sprintf("%i:%02i%s", h, m, pm); } const QString gYAxisWeight::Format(EventDataType v, int dp) { Q_UNUSED(dp) return weightString(v, m_unitsystem); } OSCAR-code-v1.3.1/oscar/Graphs/gYAxis.h000066400000000000000000000135751417327530600174140ustar00rootroot00000000000000/* gYAxis Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GYAXIS_H #define GYAXIS_H #include #include "Graphs/layer.h" /*! \class gXGrid \brief Draws the horizintal major/minor grids over graphs */ class gXGrid: public Layer { public: //! \brief Constructs an gXGrid object with default settings, and col for line colour. gXGrid(QColor col = QColor("black")); virtual ~gXGrid(); //! \brief Draw the horizontal lines by adding the to the Vertex GLbuffers virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); //! \brief set the visibility status of Major lines void setShowMinorLines(bool b) { m_show_minor_lines = b; } //! \brief set the visibility status of Minor lines void setShowMajorLines(bool b) { m_show_major_lines = b; } //! \brief Returns the visibility status of minor lines bool showMinorLines() { return m_show_minor_lines; } //! \brief Returns the visibility status of Major lines bool showMajorLines() { return m_show_major_lines; } virtual Layer * Clone() { gXGrid * grid = new gXGrid(); Layer::CloneInto(grid); CloneInto(grid); return grid; } void CloneInto(gXGrid * layer) { layer->m_show_major_lines = m_show_major_lines; layer->m_show_minor_lines = m_show_minor_lines; layer->m_major_color = m_major_color; layer->m_minor_color = m_minor_color; } protected: bool m_show_major_lines; bool m_show_minor_lines; QColor m_major_color; QColor m_minor_color; }; /*! \class gYAxis \brief Draws the YAxis tick markers, and numeric labels */ class gYAxis: public Layer { public: //! \brief Left Margin space in pixels static const int Margin = 60; public: //! \brief Construct a gYAxis object, with QColor col for tickers & text gYAxis(QColor col = Qt::black); virtual ~gYAxis(); //! \brief Draw the horizontal tickers display virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); //! \brief Sets the visibility status of minor ticks void SetShowMinorTicks(bool b) { m_show_minor_ticks = b; } //! \brief Sets the visibility status of Major ticks void SetShowMajorTicks(bool b) { m_show_major_ticks = b; } //! \brief Returns the visibility status of Minor ticks bool ShowMinorTicks() { return m_show_minor_ticks; } //! \brief Returns the visibility status of Major ticks bool ShowMajorTicks() { return m_show_major_ticks; } //! \brief Formats the ticker value.. Override to implement other types virtual const QString Format(EventDataType v, int dp); virtual int minimumWidth(); //! \brief Set the scale of the Y axis values.. Values can be multiplied by this to convert formats void SetScale(float f) { m_yaxis_scale = f; } //! \brief Returns the scale of the Y axis values.. // Values can be multiplied by this to convert formats float Scale() { return m_yaxis_scale; } protected: bool m_show_minor_ticks; bool m_show_major_ticks; float m_yaxis_scale; QColor m_line_color; QColor m_text_color; virtual bool mouseMoveEvent(QMouseEvent *event, gGraph *graph); virtual bool mouseDoubleClickEvent(QMouseEvent *event, gGraph *graph); QImage m_image; virtual Layer * Clone() { gYAxis * yaxis = new gYAxis(); Layer::CloneInto(yaxis); CloneInto(yaxis); return yaxis; } void CloneInto(gYAxis * layer) { layer->m_show_major_ticks = m_show_major_ticks; layer->m_show_minor_ticks = m_show_minor_ticks; layer->m_line_color = m_line_color; layer->m_text_color = m_text_color; layer->m_image = m_image; } }; /*! \class gYAxisTime \brief Draws the YAxis tick markers, and labels in time format */ class gYAxisTime: public gYAxis { public: //! \brief Construct a gYAxisTime object, with QColor col for tickers & times gYAxisTime(bool hr12 = true, QColor col = Qt::black) : gYAxis(col), show_12hr(hr12) {} virtual ~gYAxisTime() {} protected: //! \brief Overrides gYAxis Format to display Time format virtual const QString Format(EventDataType v, int dp); //! \brief Whether to format as 12 or 24 hour times bool show_12hr; virtual Layer * Clone() { gYAxisTime * yaxis = new gYAxisTime(); Layer::CloneInto(yaxis); CloneInto(yaxis); return yaxis; } void CloneInto(gYAxisTime * layer) { gYAxis::CloneInto(layer); layer->show_12hr = show_12hr; } }; /*! \class gYAxisWeight \brief Draws the YAxis tick markers, and labels in weight format */ class gYAxisWeight: public gYAxis { public: //! \brief Construct a gYAxisWeight object, with QColor col for tickers & weight values gYAxisWeight(UnitSystem us = US_Metric, QColor col = Qt::black) : gYAxis(col), m_unitsystem(us) {} virtual ~gYAxisWeight() {} //! \brief Returns the current UnitSystem displayed (eg, US_Metric (the rest of the world), US_English (American) ) UnitSystem unitSystem() { return m_unitsystem; } //! \brief Set the unit system displayed by this YTicker void setUnitSystem(UnitSystem us) { m_unitsystem = us; } protected: //! \brief Overrides gYAxis Format to display Time format virtual const QString Format(EventDataType v, int dp); UnitSystem m_unitsystem; virtual Layer * Clone() { gYAxisWeight * yaxis = new gYAxisWeight(); Layer::CloneInto(yaxis); CloneInto(yaxis); return yaxis; } void CloneInto(gYAxisWeight * layer) { gYAxis::CloneInto(layer); layer->m_unitsystem = m_unitsystem; } }; #endif // GYAXIS_H OSCAR-code-v1.3.1/oscar/Graphs/gdailysummary.cpp000066400000000000000000000372601417327530600214270ustar00rootroot00000000000000/* gDailySummary Graph Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include "gdailysummary.h" #include "Graphs/gGraph.h" #include "Graphs/gGraphView.h" #include "SleepLib/profiles.h" gDailySummary::gDailySummary() : Layer(NoChannel) { } void gDailySummary::SetDay(Day *day) { QList piechans; for (int i = 0; i < ahiChannels.size(); i++) piechans.append(ahiChannels.at(i)); // piechans.append(CPAP_ClearAirway); // piechans.append(CPAP_AllApnea); // piechans.append(CPAP_Obstructive); // piechans.append(CPAP_Apnea); // piechans.append(CPAP_Hypopnea); piechans.append(CPAP_RERA); piechans.append(CPAP_FlowLimit); pie_data.clear(); pie_chan.clear(); pie_labels.clear(); pie_total = 0; m_day = day; Machine * cpap = nullptr; if (day) cpap = day->machine(MT_CPAP); if (cpap) { m_minx = m_day->first(); m_maxx = m_day->last();; quint32 zchans = schema::SPAN | schema::FLAG; bool show_minors = true; if (p_profile->general->showUnknownFlags()) zchans |= schema::UNKNOWN; if (show_minors) zchans |= schema::MINOR_FLAG; QList available = day->getSortedMachineChannels(zchans); flag_values.clear(); flag_background.clear(); flag_foreground.clear(); flag_labels.clear(); flag_codes.clear(); EventDataType val; EventDataType hours = day->hours(); int x,y; flag_value_width = flag_label_width = flag_height = 0; for (int i=0; i < available.size(); ++i) { ChannelID code = available.at(i); schema::Channel & chan = schema::channel[code]; QString str; if (chan.type() == schema::SPAN) { val = (100.0 / hours)*(day->sum(code)/3600.0); str = QString("%1%").arg(val,0,'f',2); } else { val = day->count(code) / hours; str = QString("%1").arg(val,0,'f',2); } flag_values.push_back(str); flag_codes.push_back(code); flag_background.push_back(chan.defaultColor()); flag_foreground.push_back((brightness(chan.defaultColor()) < 0.3) ? Qt::white : Qt::black); // pick a contrasting color QString label = chan.fullname(); if (piechans.contains(code)) { pie_data.push_back(val); pie_labels.push_back(chan.label()); pie_chan.append(code); pie_total += val; } flag_labels.push_back(label); GetTextExtent(label, x, y, defaultfont); // Update maximum text boundaries if (y > flag_height) flag_height = y; if (x > flag_label_width) flag_label_width = x; GetTextExtent(str, x, y, defaultfont); if (x > flag_value_width) flag_value_width = x; if (y > flag_height) flag_height = y; } info.clear(); info_background.clear(); settings.clear(); ahi = day->calcAHI(); CPAPMode mode = (CPAPMode)(int)round(day->settings_wavg(CPAP_Mode)); info.append(QString("%1: %2").arg(STR_TR_AHI).arg(day->calcAHI(),0,'f',2)); info_background.append(QColor("orange")); settings.append(cpap->brand()); settings.append(cpap->model()+ " " + cpap->modelnumber()); settings.append(schema::channel[CPAP_Mode].option(mode)); if (mode == MODE_CPAP) { EventDataType p = round(day->settings_max(CPAP_Pressure)); settings.append(QString("Fixed %1 %2").arg(p,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); } else if (mode == MODE_APAP) { EventDataType min = round(day->settings_min(CPAP_PressureMin)); EventDataType max = round(day->settings_max(CPAP_PressureMax)); settings.append(QString("Min Pressure %1 %2").arg(min,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); settings.append(QString("Max Pressure %1 %2").arg(max,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); } else if (mode == MODE_BILEVEL_AUTO_FIXED_PS) { EventDataType min = round(day->settings_min(CPAP_EPAPLo)); EventDataType max = round(day->settings_max(CPAP_IPAPHi)); EventDataType ps = round(day->settings_max(CPAP_PS)); settings.append(QString("Min EPAP %1 %2").arg(min,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); settings.append(QString("Max IPAP %1 %2").arg(max,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); settings.append(QString("PS %1 %2").arg(ps,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); } else if (mode == MODE_BILEVEL_FIXED) { EventDataType min = round(day->settings_min(CPAP_EPAP)); EventDataType max = round(day->settings_max(CPAP_IPAP)); settings.append(QString("EPAP %1 %2").arg(min,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); settings.append(QString("IPAP %1 %2").arg(max,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); } else if (mode == MODE_BILEVEL_AUTO_VARIABLE_PS) { EventDataType min = round(day->settings_min(CPAP_EPAPLo)); EventDataType max = round(day->settings_max(CPAP_IPAPHi)); EventDataType ps = round(day->settings_max(CPAP_PSMin)); EventDataType pshi = round(day->settings_max(CPAP_PSMax)); settings.append(QString("Min EPAP %1 %2").arg(min,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); settings.append(QString("Max IPAP %1 %2").arg(max,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); settings.append(QString("PS %1-%2 %3").arg(ps,0,'f',2).arg(pshi,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); } else if (mode == MODE_ASV) { EventDataType min = round(day->settings_min(CPAP_EPAP)); EventDataType ps = round(day->settings_max(CPAP_PSMin)); EventDataType pshi = round(day->settings_max(CPAP_PSMax)); settings.append(QString("EPAP %1 %2").arg(min,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); settings.append(QString("PS %1-%2 %3").arg(ps,0,'f',2).arg(pshi,0,'f',2).arg(schema::channel[CPAP_Pressure].units())); } settings.append(QObject::tr("Relief: %1").arg(day->getPressureRelief())); int secs = hours * 3600.0; int h = secs / 3600; int m = secs / 60 % 60; int s = secs % 60; info.append(QObject::tr("Hours: %1h, %2m, %3s").arg(h).arg(m).arg(s)); info_background.append(QColor("light blue")); info_width = info_height = 0; for (int i=0; i < info.size(); ++i) { GetTextExtent(info.at(i), x, y, mediumfont); if (y > info_height) info_height = y; if (x > info_width) info_width = x; } m_minimum_height = flag_values.size() * flag_height; m_empty = !(day->channelExists(CPAP_Pressure) || day->channelExists(CPAP_IPAP)); } else { m_minx = m_maxx = 0; m_miny = m_maxy = 0; m_empty = true; m_day = nullptr; } } bool gDailySummary::isEmpty() { return m_empty; } void gDailySummary::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { QRect rect = region.boundingRect(); int top = rect.top()-10; int left = rect.left(); //int width = rect.width(); int height = rect.height()+10; // Draw bounding box painter.setPen(QColor(Qt::black)); // painter.drawRect(QRect(left,top,width,height),5,5); QRectF rect1, rect2; int size; // QFontMetrics fm(*mediumfont); // top += fm.height(); // painter.setFont(*mediumfont); // size = info_values.size(); // // for (int i=0; i < size; ++i) { // rect1 = QRect(0,0,200,100), rect2 = QRect(0,0,200,100); // rect1 = painter.boundingRect(rect1, info_labels.at(i)); // w.renderText(info_labels.at(i), column, row, 0, Qt::black, mediumfont); // rect2 = painter.boundingRect(rect2, info_values.at(i)); // w.renderText(info_values.at(i), column, row + rect1.height(), 0, Qt::black, mediumfont); // column += qMax(rect1.width(), rect2.width()) + 15; // } // row += rect1.height()+rect2.height()-5; // column = left + 10; float row = top + 10; float column = left+10; painter.setFont(*mediumfont); size = info.size(); float xpos = left + 10;; float ypos = top + 10; double maxwidth = 0 ; for (int i=0; i< size; ++i) { rect1 = QRectF(xpos, ypos, 0, 0); QString txt = info.at(i); rect1 = painter.boundingRect(rect1, Qt::AlignTop | Qt::AlignLeft, txt); rect1.setHeight(rect1.height() * 1.25); maxwidth = qMax(rect1.width(), maxwidth); ypos += rect1.height() + 5; } painter.setFont(*defaultfont); float tpos = ypos+5; for (int i=0; i< settings.size(); ++i) { rect1 = QRectF(xpos, tpos, 0, 0); QString txt = settings.at(i); rect1 = painter.boundingRect(rect1, Qt::AlignTop | Qt::AlignLeft, txt); rect1.setHeight(rect1.height() * 1.25); maxwidth = qMax(rect1.width(), maxwidth); tpos += rect1.height(); } maxwidth *= 1.1; QRectF rect3 = QRectF(xpos, tpos, 0, 0); QString machinfo = QObject::tr("Machine Information"); rect3 = painter.boundingRect(rect1, Qt::AlignTop | Qt::AlignLeft, machinfo); maxwidth = qMax(rect1.width(), maxwidth); painter.drawRect(QRect(xpos, ypos + rect3.height()+4, maxwidth, tpos-ypos)); ypos = top + 10; painter.setFont(*mediumfont); for (int i=0; i< info.size(); ++i) { rect1 = QRectF(xpos, ypos, 0, 0); QString txt = info.at(i); rect1 = painter.boundingRect(rect1, Qt::AlignTop | Qt::AlignLeft, txt); rect1.setWidth(maxwidth); rect1.setHeight(rect1.height() * 1.25); painter.fillRect(rect1, QColor(info_background.at(i))); painter.setPen(Qt::black); painter.drawText(rect1, Qt::AlignCenter, txt); painter.drawRoundedRect(rect1, 5, 5); ypos += rect1.height() + 5; } rect3.moveTop(ypos+1); rect3.setWidth(maxwidth); QFont ffont = *defaultfont; ffont.setBold(true); painter.setFont(ffont); painter.drawText(rect3, Qt::AlignCenter, machinfo); painter.setFont(*defaultfont); ypos += 6 + rect3.height(); for (int i=0; i< settings.size(); ++i) { rect1 = QRectF(xpos, ypos, 0, 0); QString txt = settings.at(i); rect1 = painter.boundingRect(rect1, Qt::AlignTop | Qt::AlignLeft, txt); rect1.setWidth(maxwidth); rect1.setHeight(rect1.height() * 1.25); // painter.fillRect(rect1, QColor("orange")); painter.setPen(Qt::black); painter.drawText(rect1, Qt::AlignCenter, txt); // painter.drawRoundedRect(rect1, 5, 5); ypos += rect1.height(); } column += rect1.width() + 15; size = flag_values.size(); int vis = 0; for (int i=0; i < size; ++i) { schema::Channel & chan = schema::channel[flag_codes.at(i)]; if (chan.enabled()) vis++; } flag_value_width = 0; flag_label_width = 0; flag_height = 0; float hpl = float(height-20) / float(vis); QFont font(defaultfont->family()); font.setPixelSize(hpl*0.75); font.setBold(true); font.setItalic(true); painter.setFont(font); for (int i=0; i < size; ++i) { rect1 = QRectF(0,0,0,0), rect2 = QRectF(0,0,0,0); rect1 = painter.boundingRect(rect1, Qt::AlignLeft | Qt::AlignTop, flag_labels.at(i)); rect2 = painter.boundingRect(rect2, Qt::AlignLeft | Qt::AlignTop, flag_values.at(i)); if (rect1.width() > flag_label_width) flag_label_width = rect1.width(); if (rect2.width() > flag_value_width) flag_value_width = rect2.width(); if (rect1.height() > flag_height) flag_height = rect1.height(); if (rect2.height() > flag_height) flag_height = rect2.height(); } flag_height = hpl; QRect flag_outline(column -5, row -5, (flag_value_width + flag_label_width + 20 + 4) + 10, (hpl * vis) + 10); painter.setPen(QPen(Qt::gray, 1)); painter.drawRoundedRect(flag_outline, 5, 5); font.setBold(false); font.setItalic(false); painter.setFont(font); for (int i=0; i < size; ++i) { schema::Channel & chan = schema::channel[flag_codes.at(i)]; if (!chan.enabled()) continue; painter.setPen(flag_foreground.at(i)); QRectF box(column, floor(row) , (flag_value_width + flag_label_width + 20 + 4), ceil(flag_height)); painter.fillRect(box, QBrush(flag_background.at(i))); if (box.contains(w.graphView()->currentMousePos())) { w.ToolTip(chan.description(), w.graphView()->currentMousePos().x()+5, w.graphView()->currentMousePos().y(), TT_AlignLeft); font.setBold(true); font.setItalic(true); painter.setFont(font); QRect rect1 = QRect(column+2, row , flag_label_width, ceil(hpl)); painter.drawText(rect1, Qt::AlignVCenter, flag_labels.at(i)); QRect rect2 = QRect(column+2 + flag_label_width + 20, row, flag_value_width, ceil(hpl)); painter.drawText(rect2, Qt::AlignVCenter, flag_values.at(i)); font.setBold(false); font.setItalic(false); painter.setFont(font); } else { QRect rect1 = QRect(column+2, row , flag_label_width, ceil(hpl)); painter.drawText(rect1, Qt::AlignVCenter, flag_labels.at(i)); QRect rect2 = QRect(column+2 + flag_label_width + 20, row, flag_value_width, ceil(hpl)); painter.drawText(rect2, Qt::AlignVCenter, flag_values.at(i)); } row += (flag_height); } column += 22 + flag_label_width + flag_value_width + 20; row = top + 10; //////////////////////////////////////////////////////////////////////////////// // Pie Chart //////////////////////////////////////////////////////////////////////////////// painter.setRenderHint(QPainter::Antialiasing); QRect pierect(column, row, height-30, height-30); float sum = -90.0; int slices = pie_data.size(); EventDataType data; for (int i=0; i < slices; ++i) { data = pie_data[i]; if (data == 0) { continue; } // Setup the shiny radial gradient float len = 360.0 / float(pie_total) * float(data); QColor col = schema::channel[pie_chan[i]].defaultColor(); painter.setPen(QPen(col, 0)); QRadialGradient gradient(pierect.center(), float(pierect.width()) / 2.0, pierect.center()); gradient.setColorAt(0, Qt::white); gradient.setColorAt(1, col); // draw filled pie painter.setBrush(gradient); painter.setBackgroundMode(Qt::OpaqueMode); painter.drawPie(pierect, -sum * 16.0, -len * 16.0); // draw outline painter.setBackgroundMode(Qt::TransparentMode); painter.setBrush(QBrush(col,Qt::NoBrush)); painter.setPen(QPen(QColor(Qt::black),1.5)); painter.drawPie(pierect, -sum * 16.0, -len * 16.0); sum += len; } } bool gDailySummary::mousePressEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) return true; } bool gDailySummary::mouseReleaseEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) return true; } bool gDailySummary::mouseMoveEvent(QMouseEvent *event, gGraph *graph) { graph->timedRedraw(0); Q_UNUSED(event) Q_UNUSED(graph) return true; } OSCAR-code-v1.3.1/oscar/Graphs/gdailysummary.h000066400000000000000000000032151417327530600210650ustar00rootroot00000000000000/* gDailySummary Graph Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GDAILYSUMMARY_H #define GDAILYSUMMARY_H #include "Graphs/layer.h" #include "SleepLib/day.h" class gDailySummary:public Layer { public: gDailySummary(); virtual ~gDailySummary() {} virtual void SetDay(Day *d); virtual bool isEmpty(); //! Draw filled rectangles behind Event Flag's, and an outlines around them all, Calls the individual paint for each gFlagLine virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion); virtual int minimumHeight() { return m_minimum_height; } bool mouseMoveEvent(QMouseEvent *event, gGraph *graph); bool mousePressEvent(QMouseEvent *event, gGraph *graph); bool mouseReleaseEvent(QMouseEvent *event, gGraph *graph); protected: QList flag_values; QList flag_labels; QList flag_codes; QList flag_foreground; QList flag_background; QList pie_chan; QList pie_data; QList pie_labels; EventDataType pie_total; QList settings; QList info; QList info_background; QList info_foreground; float flag_height; float flag_label_width; float flag_value_width; double ahi; int info_height; int info_width; int m_minimum_height; bool m_empty; }; #endif // GDAILYSUMMARY_H OSCAR-code-v1.3.1/oscar/Graphs/glcommon.cpp000066400000000000000000000020171417327530600203430ustar00rootroot00000000000000/* glcommon GL code & font stuff * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include "glcommon.h" float brightness(QColor color) { return color.redF()*0.299 + color.greenF()*0.587 + color.blueF()*0.114; } QColor brighten(QColor color, float mult) { int cr, cg, cb; cr = color.red(); cg = color.green(); cb = color.blue(); if (cr < 64) { cr = 64; } if (cg < 64) { cg = 64; } if (cb < 64) { cb = 64; } cr *= mult; cg *= mult; cb *= mult; if (cr > 255) { cr = 255; } if (cg > 255) { cg = 255; } if (cb > 255) { cb = 255; } return QColor(cr, cg, cb, 255); } #if defined(_MSC_VER) && (_MSC_VER < 1800) double round(double number) { return number < 0.0 ? ceil(number - 0.5) : floor(number + 0.5); } #endif OSCAR-code-v1.3.1/oscar/Graphs/glcommon.h000066400000000000000000000043261417327530600200150ustar00rootroot00000000000000/* glcommon GL code & font stuff Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GLCOMMON_H #define GLCOMMON_H #include //! \brief Returns the grayscale brightness (between 0 and 1) of a color float brightness(QColor color); #define MIN(a,b) (((a)<(b)) ? (a) : (b)); #define MAX(a,b) (((a)<(b)) ? (b) : (a)); const QColor COLOR_Black = Qt::black; const QColor COLOR_LightGreen = QColor("light green"); const QColor COLOR_DarkGreen = Qt::darkGreen; const QColor COLOR_Purple = QColor("purple"); const QColor COLOR_Aqua = QColor("#40c0ff"); const QColor COLOR_Magenta = Qt::magenta; const QColor COLOR_Blue = Qt::blue; const QColor COLOR_LightBlue = QColor("light blue"); const QColor COLOR_Gray = Qt::gray; const QColor COLOR_LightGray = Qt::lightGray; const QColor COLOR_DarkGray = Qt::darkGray; const QColor COLOR_Cyan = Qt::cyan; const QColor COLOR_DarkCyan = Qt::darkCyan; const QColor COLOR_DarkBlue = Qt::darkBlue; const QColor COLOR_DarkMagenta = Qt::darkMagenta; const QColor COLOR_Gold = QColor("gold"); const QColor COLOR_White = Qt::white; const QColor COLOR_Red = Qt::red; const QColor COLOR_Pink = QColor("pink"); const QColor COLOR_DarkRed = Qt::darkRed; const QColor COLOR_Yellow = Qt::yellow; const QColor COLOR_DarkYellow = Qt::darkYellow; const QColor COLOR_Orange = QColor("orange"); const QColor COLOR_Green = Qt::green; const QColor COLOR_Brown = QColor("brown"); const QColor COLOR_Text = Qt::black; const QColor COLOR_Outline = Qt::black; const QColor COLOR_ALT_BG1 = QColor(0xc8, 0xff, 0xc8, 0x7f); // Alternating Background Color 1 (Event Flags) const QColor COLOR_ALT_BG2 = COLOR_White; // Alternating Background Color 2 (Event Flags) QColor brighten(QColor color, float mult = 2.0); const int max_history = 50; #ifndef M_PI const double M_PI = 3.141592653589793; #endif // Visual C++ earlier than 2013 doesn't have round in it's maths header.. #if defined(_MSC_VER) && (_MSC_VER < 1800) double round(double number); #endif #endif // GLCOMMON_H OSCAR-code-v1.3.1/oscar/Graphs/graphdata.h000066400000000000000000000073511417327530600201360ustar00rootroot00000000000000/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * * gGraphData Header * * Copyright (c) 2011-2014 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the Linux * distribution for more details. */ #ifndef GRAPHDATA_H #define GRAPHDATA_H #include "graphlayer.h" #include "SleepLib/day.h" #include using namespace std; /*enum gDataType { gDT_Point, gDT_Point3D, gDT_Stacked, gDT_Segmented }; class gLayer; class gGraphData { public: gGraphData(int mp,gDataType t=gDT_Point); virtual ~gGraphData(); virtual void Reload(Day *day=nullptr) { day=day; } virtual void Update(Day *day=nullptr); //inline wxRealPoint & operator [](int i) { return vpoint[seg][i]; } //inline vector & Vec(int i) { return yaxis[i]; } //virtual inline const int & NP(int i) { return vnp[i]; } //virtual inline const int & MP(int i) { return vsize[i]; } inline const gDataType & Type() { return type; } virtual double CalcAverage()=0; virtual double CalcMinY()=0; virtual double CalcMaxY()=0; virtual inline double MaxX() { return max_x; } virtual inline double MinX() { return min_x; } virtual inline double MaxY() { return max_y; } virtual inline double MinY() { return min_y; } virtual inline void SetMaxX(double v) { max_x=v; if (max_x>real_max_x) max_x=real_max_x; } virtual inline void SetMinX(double v) { min_x=v; if (min_xreal_max_y) max_y=real_max_y; } virtual inline void SetMinY(double v) { min_y=v; if (min_y np; vector maxsize; bool IsReady() { return m_ready; } void SetReady(bool b) { m_ready=b; } bool isEmpty(); void AddLayer(gLayer *g); protected: virtual void AddSegment(int max_points) { max_points=max_points; } double real_min_x, real_max_x, real_min_y, real_max_y; double min_x, max_x, min_y, max_y; double force_min_y,force_max_y; int vc; gDataType type; int max_points; bool m_ready; list notify_layers; }; class QPointD { public: QPointD() {}; QPointD(double _x,double _y):X(_x),Y(_y) {}; //QPointD(const QPointD & ref):X(ref.X),Y(ref.Y) {}; double x() { return X; }; double y() { return Y; }; void setX(double v) { X=v; }; void setY(double v) { Y=v; }; protected: double X,Y; }; class gPointData:public gGraphData { public: gPointData(int mp); virtual ~gPointData(); virtual void Reload(Day *day=nullptr){ day=day; }; virtual void AddSegment(int max_points); virtual double CalcAverage(); virtual double CalcMinY(); virtual double CalcMaxY(); vector point; }; */ #endif // GRAPHDATA_H OSCAR-code-v1.3.1/oscar/Graphs/graphdata_custom.h000066400000000000000000000061461417327530600215310ustar00rootroot00000000000000/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * * Custom graph data Headers * * Copyright (c) 2011-2014 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the Linux * distribution for more details. */ /*#ifndef GRAPHDATA_CUSTOM_H #define GRAPHDATA_CUSTOM_H #include #include "SleepLib/profiles.h" #include "SleepLib/day.h" #include "SleepLib/machine_common.h" #include "graphdata.h" class FlagData:public gPointData { public: FlagData(MachineCode _code,int _field=-1,int _offset=-1); virtual ~FlagData(); virtual void Reload(Day *day=nullptr); protected: MachineCode code; int field; int offset; }; class TAPData:public gPointData { public: TAPData(MachineCode _code); virtual ~TAPData(); virtual void Reload(Day *day=nullptr); static const int max_slots=4096; double pTime[max_slots]; MachineCode code; }; class WaveData:public gPointData { public: WaveData(MachineCode _code,int _size=1000000); virtual ~WaveData(); virtual void Reload(Day *day=nullptr); protected: MachineCode code; }; class EventData:public gPointData { public: EventData(MachineCode _code,int _field=0,int _size=250000,bool _skipzero=false); virtual ~EventData(); virtual void Reload(Day *day=nullptr); protected: MachineCode code; int field; bool skipzero; }; class AHIData:public gPointData { public: AHIData(); virtual ~AHIData(); virtual void Reload(Day *day=nullptr); }; class HistoryData:public gPointData { public: HistoryData(Profile * _profile,int mpts=2048); virtual ~HistoryData(); void SetProfile(Profile *_profile) { profile=_profile; Reload(); } Profile * GetProfile() { return profile; } //double GetAverage(); virtual double Calc(Day *day); virtual void Reload(Day *day=nullptr); virtual void ResetDateRange(); virtual void SetDateRange(QDate start,QDate end); // virtual void Reload(Machine *machine=nullptr); protected: Profile * profile; }; class SessionTimes:public HistoryData { public: SessionTimes(Profile * _profile); virtual ~SessionTimes(); //void SetProfile(Profile *_profile) { profile=_profile; Reload(); } //Profile * GetProfile() { return profile; } //virtual double GetAverage(); // length?? virtual void Reload(Day *day=nullptr); //virtual void ResetDateRange(); //virtual void SetDateRange(QDate start,QDate end); protected: // Profile * profile; }; class HistoryCodeData:public HistoryData { public: HistoryCodeData(Profile *_profile,MachineCode _code); virtual ~HistoryCodeData(); virtual double Calc(Day *day); protected: MachineCode code; }; enum T_UHD { UHD_Bedtime, UHD_Waketime, UHD_Hours }; class UsageHistoryData:public HistoryData { public: UsageHistoryData(Profile *_profile,T_UHD _uhd); virtual ~UsageHistoryData(); virtual double Calc(Day *day); protected: T_UHD uhd; }; #endif // GRAPHDATA_CUSTOM_H */ OSCAR-code-v1.3.1/oscar/Graphs/gspacer.cpp000066400000000000000000000006361417327530600201610ustar00rootroot00000000000000/* graph spacer Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "gspacer.h" gSpacer::gSpacer(int space) : Layer(NoChannel) { m_space = space; } OSCAR-code-v1.3.1/oscar/Graphs/gspacer.h000066400000000000000000000014071417327530600176230ustar00rootroot00000000000000/* graph spacer Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef GSPACER_H #define GSPACER_H #include "gGraphView.h" /*! \class gSpacer \brief A dummy graph spacer layer object */ class gSpacer: public Layer { public: gSpacer(int space = 20); // orientation? virtual void paint(QPainter &painter, gGraph &g, const QRegion ®ion) { Q_UNUSED(painter); Q_UNUSED(g); Q_UNUSED(region); } int space() { return m_space; } protected: int m_space; }; #endif // GSPACER_H OSCAR-code-v1.3.1/oscar/Graphs/layer.cpp000066400000000000000000000146231417327530600176520ustar00rootroot00000000000000/* Graph Layer Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "Graphs/layer.h" Layer::~Layer() { // for (int i = 0; i < mgl_buffers.size(); i++) { // delete mgl_buffers[i]; // } // for (int i = 0; i < mv_buffers.size(); i++) { // delete mv_buffers[i]; // } } //void Layer::drawGLBuf(float linesize) //{ // int type; // float size; // if (!m_visible) { return; } // GLBuffer *buf; // gVertexBuffer *vb; // for (int i = 0; i < mv_buffers.size(); i++) { // vb = mv_buffers[i]; // size = vb->size(); // type = vb->type(); // if ((linesize > size) && ((type == GL_LINES) || (type == GL_LINE_LOOP))) { // vb->setSize(linesize); // } // vb->draw(); // vb->setSize(size); // } // for (int i = 0; i < mgl_buffers.size(); i++) { // buf = mgl_buffers[i]; // size = buf->size(); // type = buf->type(); // if ((linesize > size) && ((type == GL_LINES) || (type == GL_LINE_LOOP))) { // buf->setSize(linesize); // } // buf->draw(); // //if ((linesize>size) && ((type==GL_LINES) || (type==GL_LINE_LOOP))) { // buf->setSize(size); // //} // } //} void Layer::CloneInto(Layer * layer) { layer->m_refcount = m_refcount; layer->m_day = m_day; layer->m_visible = m_visible; layer->m_movable = m_movable; layer->m_minx = m_minx; layer->m_maxx = m_maxx; layer->m_miny = m_miny; layer->m_maxy = m_maxy; layer->m_physmaxy = m_physmaxy; layer->m_physminy = m_physminy; layer->m_code = m_code; layer->m_width = m_width; layer->m_height = m_height; layer->m_X = m_X; layer->m_Y = m_Y; layer->m_order = m_order; layer->m_position = m_position; layer->m_rect = m_rect; layer->m_mouseover = m_mouseover; layer->m_recalculating = m_recalculating; layer->m_layertype = m_layertype; } void Layer::SetDay(Day *d) { m_day = d; if (d) { m_minx = d->first(m_code); m_maxx = d->last(m_code); m_miny = d->Min(m_code); m_maxy = d->Max(m_code); } else { m_day = nullptr; } } bool Layer::isEmpty() { //if (m_day && (m_day->count(m_code)>0)) if (m_day && (m_day->channelExists(m_code))) { return false; } return true; } void Layer::setLayout(LayerPosition position, short width, short height, short order) { m_position = position; m_width = width; m_height = height; m_order = order; } LayerGroup::~LayerGroup() { for (int i = 0; i < layers.size(); i++) { delete layers[i]; } } bool LayerGroup::isEmpty() { if (!m_day) { return true; } bool empty = true; for (int i = 0; i < layers.size(); i++) { if (layers[i]->isEmpty()) { empty = false; break; } } return empty; } //void LayerGroup::drawGLBuf(float linesize) //{ // Layer::drawGLBuf(linesize); // for (int i = 0; i < layers.size(); i++) { // layers[i]->drawGLBuf(linesize); // } //} void LayerGroup::SetDay(Day *d) { m_day = d; for (int i = 0; i < layers.size(); i++) { layers[i]->SetDay(d); } } void LayerGroup::AddLayer(Layer *l) { layers.push_back(l); l->addref(); } qint64 LayerGroup::Minx() { bool first = true; qint64 m = 0, t; for (int i = 0; i < layers.size(); i++) { t = layers[i]->Minx(); if (!t) { continue; } if (first) { m = t; first = false; } else if (m > t) { m = t; } } return m; } qint64 LayerGroup::Maxx() { bool first = true; qint64 m = 0, t; for (int i = 0; i < layers.size(); i++) { t = layers[i]->Maxx(); if (!t) { continue; } if (first) { m = t; first = false; } else if (m < t) { m = t; } } return m; } EventDataType LayerGroup::Miny() { bool first = true; EventDataType m = 0, t; for (int i = 0; i < layers.size(); i++) { t = layers[i]->Miny(); if (t == layers[i]->Maxy()) { continue; } if (first) { m = t; first = false; } else { if (m > t) { m = t; } } } return m; } EventDataType LayerGroup::Maxy() { bool first = true; EventDataType m = 0, t; for (int i = 0; i < layers.size(); i++) { t = layers[i]->Maxy(); if (t == layers[i]->Miny()) { continue; } if (first) { m = t; first = false; } else if (m < t) { m = t; } } return m; } //! \brief Mouse wheel moved somewhere over this layer bool LayerGroup::wheelEvent(QWheelEvent *event, gGraph *graph) { for (int i = 0; i < layers.size(); i++) if (layers[i]->wheelEvent(event, graph)) { return true; } return false; } //! \brief Mouse moved somewhere over this layer bool LayerGroup::mouseMoveEvent(QMouseEvent *event, gGraph *graph) { for (int i = 0; i < layers.size(); i++) if (layers[i]->mouseMoveEvent(event, graph)) { return true; } return false; } //! \brief Mouse left or right button pressed somewhere on this layer bool LayerGroup::mousePressEvent(QMouseEvent *event, gGraph *graph) { for (int i = 0; i < layers.size(); i++) if (layers[i]->mousePressEvent(event, graph)) { return true; } return false; } //! \brief Mouse button released that was originally pressed somewhere on this layer bool LayerGroup::mouseReleaseEvent(QMouseEvent *event, gGraph *graph) { for (int i = 0; i < layers.size(); i++) if (layers[i]->mouseReleaseEvent(event, graph)) { return true; } return false; } //! \brief Mouse button double clicked somewhere on this layer bool LayerGroup::mouseDoubleClickEvent(QMouseEvent *event, gGraph *graph) { for (int i = 0; i < layers.size(); i++) if (layers[i]->mouseDoubleClickEvent(event, graph)) { return true; } return false; } //! \brief A key was pressed on the keyboard while the graph area was focused. bool LayerGroup::keyPressEvent(QKeyEvent *event, gGraph *graph) { for (int i = 0; i < layers.size(); i++) if (layers[i]->keyPressEvent(event, graph)) { return true; } return false; } OSCAR-code-v1.3.1/oscar/Graphs/layer.h000066400000000000000000000240361417327530600173160ustar00rootroot00000000000000/* Graph Layer Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef graphs_layer_h #define graphs_layer_h #include #include #include #include #include #include "SleepLib/common.h" #include "SleepLib/day.h" #include "SleepLib/machine_common.h" class gGraph; class LayerGroup; enum LayerPosition { LayerLeft, LayerRight, LayerTop, LayerBottom, LayerCenter, LayerOverlay }; enum ToolTipAlignment { TT_AlignCenter, TT_AlignLeft, TT_AlignRight }; enum LayerType { LT_Other = 0, LT_LineChart, LT_SummaryChart, LT_EventFlags, LT_Spacer, LT_Overview }; /*! \class Layer \brief The base component for all individual Graph layers */ class Layer { friend class gGraph; friend class LayerGroup; public: Layer(ChannelID code) : m_refcount(0), m_day(nullptr), m_visible(true), m_movable(false), m_minx(0), m_maxx(0), m_miny(0), m_maxy(0), m_physminy(0), m_physmaxy(0), m_code(code), m_width(0), m_height(0), m_X(0), m_Y(0), m_order(0), m_position(LayerCenter), m_recalculating(false), m_layertype(LT_Other) { } virtual void recalculate(gGraph * graph) { Q_UNUSED(graph)} virtual ~Layer(); virtual Layer * Clone() { return nullptr; } void CloneInto(Layer *); //! \brief This gets called on day selection, allowing this layer to precalculate any drawing data virtual void SetDay(Day *d); //! \brief Set the ChannelID used in this layer virtual void SetCode(ChannelID c) { m_code = c; } //! \brief Return the ChannelID used in this layer const ChannelID & code() { return m_code; } const LayerType & layerType() { return m_layertype; } //! \brief returns true if this layer contains no data. virtual bool isEmpty(); //! \brief Override and returns true if there are any highlighted components virtual bool isSelected() { return false; } //! \brief Deselect any highlighted components virtual void deselect() { } //! \brief Override to set the minimum allowed height for this layer virtual int minimumHeight() { return 0; } //! \brief Override to set the minimum allowed width for this layer virtual int minimumWidth() { return 0; } //! \brief Return this layers physical minimum date boundary virtual qint64 Minx() { return m_day ? m_day->first() : m_minx; } //! \brief Return this layers physical maximum date boundary virtual qint64 Maxx() { return m_day ? m_day->last() : m_maxx; } //! \brief Return this layers physical minimum Yaxis value virtual EventDataType Miny() { return m_miny; } //! \brief Return this layers physical maximum Yaxis value virtual EventDataType Maxy() { return m_maxy; } //! \brief Return this layers physical minimum Yaxis value virtual EventDataType physMiny() { return m_physminy; } //! \brief Return this layers physical maximum Yaxis value virtual EventDataType physMaxy() { return m_physmaxy; } //! \brief Set this layers physical minimum date boundary virtual void setMinX(qint64 val) { m_minx = val; } //! \brief Set this layers physical maximum date boundary virtual void setMaxX(qint64 val) { m_maxx = val; } //! \brief Set this layers physical minimum Yaxis value virtual void setMinY(EventDataType val) { m_miny = val; } //! \brief Set this layers physical maximum Yaxis value virtual void setMaxY(EventDataType val) { m_maxy = val; } //! \brief Set this layers Visibility status void setVisible(bool b) { m_visible = b; } //! \brief Return this layers Visibility status inline bool visible() const { return m_visible; } //! \brief Set this layers Moveability status (not really used yet) void setMovable(bool b) { m_movable = b; } //! \brief Return this layers Moveability status (not really used yet) inline bool movable() const { return m_movable; } inline bool recalculating() const { return m_recalculating; } virtual void dataChanged() {} /*! \brief Override this for the drawing code, using GLBuffer components for drawing \param gGraph & gv Graph Object that holds this layer \param int left \param int top \param int width \param int height */ virtual void paint(QPainter &painter, gGraph &gv, const QRegion ®ion) = 0; //! \brief Set the layout position and order for this layer. void setLayout(LayerPosition position, short width, short height, short order); void setPos(short x, short y) { m_X = x; m_Y = y; } inline int Width() const { return m_width; } inline int Height() const { return m_height; } //! \brief Return this Layers Layout Position. LayerPosition position() { return m_position; } //void X() { return m_X; } //void Y() { return m_Y; } // //! \brief Draw all this layers custom GLBuffers (ie. the actual OpenGL Vertices) // virtual void drawGLBuf(float linesize); //! \brief not sure why I needed the reference counting stuff. short m_refcount; void addref() { m_refcount++; } bool unref() { m_refcount--; return (m_refcount <= 0); } protected: // //! \brief Add a GLBuffer (vertex) object customized to this layer // void addGLBuf(GLBuffer *buf) { mgl_buffers.push_back(buf); } // void addVertexBuffer(gVertexBuffer *buf) { mv_buffers.push_back(buf); } //QRect bounds; // bounds, relative to top of individual graph. Day *m_day; bool m_visible; bool m_movable; qint64 m_minx, m_maxx; EventDataType m_miny, m_maxy; EventDataType m_physminy, m_physmaxy; ChannelID m_code; short m_width; // reserved x pixels needed for this layer. 0==Depends on position.. short m_height; // reserved y pixels needed for this layer. both 0 == expand to all free area. short m_X; // offset for repositionable layers.. short m_Y; short m_order; // order for positioning.. LayerPosition m_position; QRect m_rect; bool m_mouseover; volatile bool m_recalculating; LayerType m_layertype; public: // //! \brief A vector containing all this layers custom drawing buffers // QVector mgl_buffers; // QVector mv_buffers; //! \brief Mouse wheel moved somewhere over this layer virtual bool wheelEvent(QWheelEvent *event, gGraph *graph) { Q_UNUSED(event); Q_UNUSED(graph); return false; } //! \brief Mouse moved somewhere over this layer virtual bool mouseMoveEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event); Q_UNUSED(graph); return false; } //! \brief Mouse left or right button pressed somewhere on this layer virtual bool mousePressEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event); Q_UNUSED(graph); return false; } //! \brief Mouse button released that was originally pressed somewhere on this layer virtual bool mouseReleaseEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event); Q_UNUSED(graph); return false; } //! \brief Mouse button double clicked somewhere on this layer virtual bool mouseDoubleClickEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event); Q_UNUSED(graph); return false; } //! \brief A key was pressed on the keyboard while the graph area was focused. virtual bool keyPressEvent(QKeyEvent *event, gGraph *graph) { Q_UNUSED(event); Q_UNUSED(graph); return false; } }; /*! \class LayerGroup \brief Contains a list of graph Layer objects */ class LayerGroup : public Layer { public: LayerGroup() : Layer(NoChannel) { } virtual ~LayerGroup(); //! \brief Add Layer to this Layer Group virtual void AddLayer(Layer *l); //! \brief Returns the minimum time value for all Layers contained in this group (milliseconds since epoch) virtual qint64 Minx(); //! \brief Returns the maximum time value for all Layers contained in this group (milliseconds since epoch) virtual qint64 Maxx(); //! \brief Returns the minimum Y-axis value for all Layers contained in this group virtual EventDataType Miny(); //! \brief Returns the maximum Y-axis value for all Layers contained in this group virtual EventDataType Maxy(); //! \brief Check all layers contained and return true if none contain data virtual bool isEmpty(); //! \brief Calls SetDay for all Layers contained in this object virtual void SetDay(Day *d); // //! \brief Calls drawGLBuf for all Layers contained in this object // virtual void drawGLBuf(float linesize); //! \brief Return the list of Layers this object holds QVector &getLayers() { return layers; } protected: //! \brief Contains all Layer objects in this group QVector layers; //! \brief Mouse wheel moved somewhere over this LayerGroup virtual bool wheelEvent(QWheelEvent *event, gGraph *graph); //! \brief Mouse moved somewhere over this LayerGroup virtual bool mouseMoveEvent(QMouseEvent *event, gGraph *graph); //! \brief Mouse left or right button pressed somewhere on this LayerGroup virtual bool mousePressEvent(QMouseEvent *event, gGraph *graph); //! \brief Mouse button released that was originally pressed somewhere on this LayerGroup virtual bool mouseReleaseEvent(QMouseEvent *event, gGraph *graph); //! \brief Mouse button double clicked somewhere on this layerGroup virtual bool mouseDoubleClickEvent(QMouseEvent *event, gGraph *graph); //! \brief A key was pressed on the keyboard while the graph area was focused. virtual bool keyPressEvent(QKeyEvent *event, gGraph *graph); }; #endif // graphs_layer_h OSCAR-code-v1.3.1/oscar/Resources.qrc000066400000000000000000000044271417327530600172700ustar00rootroot00000000000000 docs/0.0.gif docs/GPLv3-en_US docs/script.js docs/tooltips.css docs/countries.txt docs/tz.txt docs/schema.xml docs/channels.xml fonts/FreeSans.ttf icons/sdcard.png icons/preferences.png icons/overview.png icons/moon.png icons/go-home.png icons/forward.png icons/edit-find.png icons/back.png icons/refresh.png icons/oximeter.png icons/last.png icons/save.png icons/arrow-end.png icons/arrow-left.png icons/arrow-right.png icons/smileyface.png icons/sadface.png icons/mask.png icons/cubeoximeter.png icons/trophy.png icons/bookmark.png icons/help.png icons/session-off.png icons/session-on.png icons/logo-lg.png icons/logo-lm.png icons/logo-md.png icons/logo-sm.png icons/sdcard-lock.png icons/statistics.png icons/prs1.png icons/prs1vent.png icons/cms50f.png icons/rms9.png icons/intellipap.png icons/pushpin.png icons/eye.png icons/prs1_60s.png icons/dreamstation.png icons/prds2.png icons/airsense10.png icons/aircurve.png icons/prs1_960.png icons/daily.png icons/dv64.png icons/overview-page.png icons/fp_icon.png icons/up-down.png icons/warning.png OSCAR-code-v1.3.1/oscar/STYLE000066400000000000000000000020251417327530600154220ustar00rootroot00000000000000# Style definitions for Artistic Style. # Usage: astyle --options=STYLE --style=otbs # "One True Brace Style" --max-code-length=99 # Maximum length of a single line. --lineend=linux # Use \n, not \r\n. --attach-namespaces # Attach brackets to a namespace statement. --attach-inlines # Attach brackets to class and struct inline function definitions. --indent-modifiers # Indent C++ class and struct access modifiers one half-indent. --indent-col1-comments # Indent C++ comments beginning in column one, with the code. --break-blocks # Pad empty lines around if, for, and while blocks. --pad-oper # Insert space padding around operators. --pad-header # Insert space padding after if, while, and for paren headers. --unpad-paren # Remove extra space padding around parentheses on inside and outside. --align-pointer=name # char* foo ==> char *foo. Also applies to references. --remove-brackets # Remove brackets from one-line conditional statements. --keep-one-line-blocks # don't break apart multiple statements on one line. OSCAR-code-v1.3.1/oscar/SleepLib/000077500000000000000000000000001417327530600162775ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/SleepLib/appsettings.cpp000066400000000000000000000057461417327530600213600ustar00rootroot00000000000000/* SleepLib AppSettings Initialization * * This isolates the initialization and its dependencies from the header file, * which is widely included. * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the sourcecode. */ #include "appsettings.h" #include "version.h" AppWideSetting::AppWideSetting(Preferences *pref) : PrefSettings(pref) { // m_multithreading = initPref(STR_IS_Multithreading, idealThreads() > 1).toBool(); m_multithreading = false; // too dangerous to allow m_showPerformance = initPref(STR_US_ShowPerformance, false).toBool(); m_showDebug = initPref(STR_US_ShowDebug, false).toBool(); initPref(STR_AS_CalendarVisible, false); m_scrollDampening = initPref(STR_US_ScrollDampening, (int)50).toInt(); m_tooltipTimeout = initPref(STR_US_TooltipTimeout, (int)2500).toInt(); m_graphHeight=initPref(STR_AS_GraphHeight, 180).toInt(); initPref(STR_AS_DailyPanelWidth, 250.0); initPref(STR_AS_RightPanelWidth, 230.0); m_antiAliasing=initPref(STR_AS_AntiAliasing, true).toBool(); // initPref(STR_AS_GraphSnapshots, true); initPref(STR_AS_IncludeSerial, false); initPref(STR_AS_MonochromePrinting, false); initPref(STR_AS_ShowPieChart, false); m_animations = initPref(STR_AS_Animations, true).toBool(); m_squareWavePlots = initPref(STR_AS_SquareWave, false).toBool(); initPref(STR_AS_AllowYAxisScaling, true); m_graphTooltips = initPref(STR_AS_GraphTooltips, true).toBool(); m_usePixmapCaching = initPref(STR_AS_UsePixmapCaching, false).toBool(); m_odt = (OverlayDisplayType)initPref(STR_AS_OverlayType, (int)ODT_Bars).toInt(); m_olm = (OverviewLinechartModes)initPref(STR_AS_OverviewLinechartMode, (int)OLC_Bartop).toInt(); m_lineThickness=initPref(STR_AS_LineThickness, 1.0).toFloat(); m_lineCursorMode = initPref(STR_AS_LineCursorMode, true).toBool(); initPref(STR_AS_RightSidebarVisible, false); initPref(STR_CS_UserEventPieChart, false); initPref(STR_US_ShowSerialNumbers, false); initPref(STR_US_ShowPersonalData, true); initPref(STR_US_OpenTabAtStart, 1); initPref(STR_US_OpenTabAfterImport, 0); initPref(STR_US_AutoLaunchImport, false); m_cacheSessions = initPref(STR_IS_CacheSessions, false).toBool(); initPref(STR_US_RemoveCardReminder, true); initPref(STR_US_DontAskWhenSavingScreenshots, false); m_profileName = initPref(STR_GEN_Profile, "").toString(); initPref(STR_GEN_AutoOpenLastUsed, true); #ifndef NO_CHECKUPDATES initPref(STR_GEN_UpdatesAutoCheck, true); initPref(STR_GEN_UpdateCheckFrequency, 14); initPref(STR_PREF_AllowEarlyUpdates, false); initPref(STR_GEN_UpdatesLastChecked, QDateTime()); #endif initPref(STR_PREF_VersionString, getVersion().toString()); m_language = initPref(STR_GEN_Language, "en_US").toString(); initPref(STR_GEN_ShowAboutDialog, 0); // default to about screen, set to -1 afterwards } OSCAR-code-v1.3.1/oscar/SleepLib/appsettings.h000066400000000000000000000315671417327530600210250ustar00rootroot00000000000000/* SleepLib AppSettings Header * * This file for all settings related stuff to clean up Preferences & Profiles. * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the sourcecode. */ #ifndef APPSETTINGS_H #define APPSETTINGS_H #include #include "preferences.h" #include "common.h" class Preferences; enum OverviewLinechartModes { OLC_Bartop, OLC_Lines }; // ApplicationWideSettings Strings const QString STR_CS_UserEventPieChart = "UserEventPieChart"; const QString STR_IS_Multithreading = "EnableMultithreading"; const QString STR_AS_GraphHeight = "GraphHeight"; const QString STR_AS_DailyPanelWidth = "DailyPanelWidth"; const QString STR_AS_RightPanelWidth = "RightPanelWidth"; const QString STR_AS_AntiAliasing = "UseAntiAliasing"; const QString STR_AS_GraphSnapshots = "EnableGraphSnapshots"; // Obsolete, replaced by ShowPieChart const QString STR_AS_ShowPieChart = "EnablePieChart"; const QString STR_AS_Animations = "AnimationsAndTransitions"; const QString STR_AS_SquareWave = "SquareWavePlots"; const QString STR_AS_OverlayType = "OverlayType"; const QString STR_AS_OverviewLinechartMode = "OverviewLinechartMode"; const QString STR_AS_UsePixmapCaching = "UsePixmapCaching"; const QString STR_AS_AllowYAxisScaling = "AllowYAxisScaling"; const QString STR_AS_IncludeSerial = "IncludeSerial"; const QString STR_AS_MonochromePrinting = "PrintBW"; const QString STR_AS_GraphTooltips = "GraphTooltips"; const QString STR_AS_LineThickness = "LineThickness"; const QString STR_AS_LineCursorMode = "LineCursorMode"; const QString STR_AS_CalendarVisible = "CalendarVisible"; const QString STR_AS_RightSidebarVisible = "RightSidebarVisible"; const QString STR_US_TooltipTimeout = "TooltipTimeout"; const QString STR_US_ScrollDampening = "ScrollDampening"; const QString STR_US_ShowDebug = "ShowDebug"; const QString STR_US_ShowPerformance = "ShowPerformance"; const QString STR_US_ShowSerialNumbers = "ShowSerialNumbers"; const QString STR_US_OpenTabAtStart = "OpenTabAtStart"; const QString STR_US_OpenTabAfterImport = "OpenTabAfterImport"; const QString STR_US_AutoLaunchImport = "AutoLaunchImport"; const QString STR_US_RemoveCardReminder = "RemoveCardReminder"; const QString STR_US_DontAskWhenSavingScreenshots = "DontAskWhenSavingScreenshots"; const QString STR_US_ShowPersonalData = "ShowPersonalData"; const QString STR_IS_CacheSessions = "MemoryHog"; const QString STR_GEN_AutoOpenLastUsed = "AutoOpenLastUsed"; const QString STR_GEN_Language = "Language"; const QString STR_PREF_VersionString = "VersionString"; const QString STR_GEN_ShowAboutDialog = "ShowAboutDialog"; #ifndef NO_CHECKUPDATES const QString STR_GEN_UpdatesLastChecked = "UpdatesLastChecked"; const QString STR_GEN_UpdatesAutoCheck = "Updates_AutoCheck"; const QString STR_GEN_UpdateCheckFrequency = "Updates_CheckFrequency"; const QString STR_PREF_AllowEarlyUpdates = "AllowEarlyUpdates"; const QString STR_GEN_SkippedReleaseVersion = "SkippedReleaseVersion"; const QString STR_GEN_SkippedTestVersion = "SkippedTestVersion"; #endif class AppWideSetting: public PrefSettings { public: AppWideSetting(Preferences *pref); bool m_usePixmapCaching, m_antiAliasing, m_squareWavePlots,m_graphTooltips, m_lineCursorMode, m_animations; bool m_showPerformance, m_showDebug; int m_tooltipTimeout, m_graphHeight, m_scrollDampening; bool m_multithreading, m_cacheSessions; float m_lineThickness; OverlayDisplayType m_odt; OverviewLinechartModes m_olm; QString m_profileName, m_language; QString versionString() const { return getPref(STR_PREF_VersionString).toString(); } #ifndef NO_CHECKUPDATES bool updatesAutoCheck() const { return getPref(STR_GEN_UpdatesAutoCheck).toBool(); } bool allowEarlyUpdates() const { return getPref(STR_PREF_AllowEarlyUpdates).toBool(); } QDateTime updatesLastChecked() const { return getPref(STR_GEN_UpdatesLastChecked).toDateTime(); } int updateCheckFrequency() const { return getPref(STR_GEN_UpdateCheckFrequency).toInt(); } #endif int showAboutDialog() const { return getPref(STR_GEN_ShowAboutDialog).toInt(); } void setShowAboutDialog(int tab) {setPref(STR_GEN_ShowAboutDialog, tab); } inline const QString & profileName() const { return m_profileName; } bool autoLaunchImport() const { return getPref(STR_US_AutoLaunchImport).toBool(); } bool cacheSessions() const { return m_cacheSessions; } inline bool multithreading() const { return m_multithreading; } bool showDebug() const { return m_showDebug; } bool showPerformance() const { return m_showPerformance; } //! \brief Whether to show the calendar bool calendarVisible() const { return getPref(STR_AS_CalendarVisible).toBool(); } inline int scrollDampening() const { return m_scrollDampening; } inline int tooltipTimeout() const { return m_tooltipTimeout; } //! \brief Returns the normal (unscaled) height of a graph inline int graphHeight() const { return m_graphHeight; } //! \brief Returns the normal (unscaled) height of a graph int dailyPanelWidth() const { return getPref(STR_AS_DailyPanelWidth).toInt(); } //! \brief Returns the normal (unscaled) height of a graph int rightPanelWidth() const { return getPref(STR_AS_RightPanelWidth).toInt(); } //! \brief Returns true if AntiAliasing (the graphical smoothing method) is enabled inline bool antiAliasing() const { return m_antiAliasing; } //! \brief Returns true if renderPixmap function is in use, which takes snapshots of graphs bool showPieChart() const { return getPref(STR_AS_ShowPieChart).toBool(); } //! \brief Returns true if Graphical animations & Transitions will be drawn bool animations() const { return m_animations; } //! \brief Returns true if PixmapCaching acceleration will be used inline bool usePixmapCaching() const { return m_usePixmapCaching; } //! \brief Returns true if Square Wave plots are preferred (where possible) inline bool squareWavePlots() const { return m_squareWavePlots; } //! \brief Whether to allow double clicking on Y-Axis labels to change vertical scaling mode bool allowYAxisScaling() const { return getPref(STR_AS_AllowYAxisScaling).toBool(); } //! \brief Whether to include serial number in machine settings changes report bool includeSerial() const { return getPref(STR_AS_IncludeSerial).toBool(); } //! \brief Whether to print reports in black and white, which can be more legible on non-color printers bool monochromePrinting() const { return getPref(STR_AS_MonochromePrinting).toBool(); } //! \brief Whether to show graph tooltips inline bool graphTooltips() const { return m_graphTooltips; } //! \brief Pen width of line plots inline float lineThickness() const { return m_lineThickness; } //! \brief Whether to show line cursor inline bool lineCursorMode() const { return m_lineCursorMode; } //! \brief Whether to show the right sidebar bool rightSidebarVisible() const { return getPref(STR_AS_RightSidebarVisible).toBool(); } //! \brief Returns the type of overlay flags (which are displayed over the Flow Waveform) inline OverlayDisplayType overlayType() const { return m_odt; } //! \brief Returns the display type of Overview pages linechart inline OverviewLinechartModes overviewLinechartMode() const { return m_olm; } bool userEventPieChart() const { return getPref(STR_CS_UserEventPieChart).toBool(); } bool showSerialNumbers() const { return getPref(STR_US_ShowSerialNumbers).toBool(); } int openTabAtStart() const { return getPref(STR_US_OpenTabAtStart).toInt(); } int openTabAfterImport() const { return getPref(STR_US_OpenTabAfterImport).toInt(); } bool removeCardReminder() const { return getPref(STR_US_RemoveCardReminder).toBool(); } bool dontAskWhenSavingScreenshots() const { return getPref(STR_US_DontAskWhenSavingScreenshots).toBool(); } bool autoOpenLastUsed() const { return getPref(STR_GEN_AutoOpenLastUsed).toBool(); } inline const QString & language() const { return m_language; } bool showPersonalData() const { return getPref(STR_US_ShowPersonalData).toBool(); } void setProfileName(QString name) { setPref(STR_GEN_Profile, m_profileName=name); } void setAutoLaunchImport(bool b) { setPref(STR_US_AutoLaunchImport, b); } void setCacheSessions(bool c) { setPref(STR_IS_CacheSessions, m_cacheSessions=c); } // force multithreading to false until proven OK void setMultithreading(bool b) { Q_UNUSED(b) setPref(STR_IS_Multithreading, m_multithreading = false); } void setShowDebug(bool b) { setPref(STR_US_ShowDebug, m_showDebug=b); } void setShowPerformance(bool b) { setPref(STR_US_ShowPerformance, m_showPerformance=b); } //! \brief Sets whether to display the (Daily View) Calendar void setCalendarVisible(bool b) { setPref(STR_AS_CalendarVisible, b); } void setScrollDampening(int i) { setPref(STR_US_ScrollDampening, m_scrollDampening=i); } void setTooltipTimeout(int i) { setPref(STR_US_TooltipTimeout, m_tooltipTimeout=i); } //! \brief Set the normal (unscaled) height of a graph. void setGraphHeight(int height) { setPref(STR_AS_GraphHeight, m_graphHeight=height); } //! \brief Set the normal (unscaled) height of a graph. void setDailyPanelWidth(int width) { setPref(STR_AS_DailyPanelWidth, width); } //! \brief Set the normal (unscaled) height of a graph. void setRightPanelWidth(int width) { setPref(STR_AS_RightPanelWidth, width); } //! \brief Set to true to turn on AntiAliasing (the graphical smoothing method) void setAntiAliasing(bool aa) { setPref(STR_AS_AntiAliasing, m_antiAliasing=aa); } //! \brief Set to true if renderPixmap functions are in use, which takes snapshots of graphs. void setShowPieChart(bool gs) { setPref(STR_AS_ShowPieChart, gs); } //! \brief Set to true if Graphical animations & Transitions will be drawn void setAnimations(bool anim) { setPref(STR_AS_Animations, m_animations=anim); } //! \brief Set to true to use Pixmap Caching of Text and other graphics caching speedup techniques void setUsePixmapCaching(bool b) { setPref(STR_AS_UsePixmapCaching, m_usePixmapCaching=b); } //! \brief Set whether or not to useSquare Wave plots (where possible) void setSquareWavePlots(bool sw) { setPref(STR_AS_SquareWave, m_squareWavePlots=sw); } //! \brief Sets the type of overlay flags (which are displayed over the Flow Waveform) void setOverlayType(OverlayDisplayType odt) { setPref(STR_AS_OverlayType, (int)(m_odt=odt)); } //! \brief Sets whether to allow double clicking on Y-Axis labels to change vertical scaling mode void setAllowYAxisScaling(bool b) { setPref(STR_AS_AllowYAxisScaling, b); } //! \brief Sets whether to include machine serial number on machine settings report void setIncludeSerial(bool b) { setPref(STR_AS_IncludeSerial, b); } //! \brief Sets whether to print reports in black and white, which can be more legible on non-color printers void setMonochromePrinting(bool b) { setPref(STR_AS_MonochromePrinting, b); } //! \brief Sets whether to allow double clicking on Y-Axis labels to change vertical scaling mode void setGraphTooltips(bool b) { setPref(STR_AS_GraphTooltips, m_graphTooltips=b); } //! \brief Sets the type of overlay flags (which are displayed over the Flow Waveform) void setOverviewLinechartMode(OverviewLinechartModes olm) { setPref(STR_AS_OverviewLinechartMode, (int)(m_olm=olm)); } //! \brief Set the pen width of line plots. void setLineThickness(float size) { setPref(STR_AS_LineThickness, m_lineThickness=size); } //! \brief Sets whether to display Line Cursor void setLineCursorMode(bool b) { setPref(STR_AS_LineCursorMode, m_lineCursorMode=b); } //! \brief Sets whether to display the right sidebar void setRightSidebarVisible(bool b) { setPref(STR_AS_RightSidebarVisible, b); } void setUserEventPieChart(bool b) { setPref(STR_CS_UserEventPieChart, b); } void setShowSerialNumbers(bool enabled) { setPref(STR_US_ShowSerialNumbers, enabled); } void setOpenTabAtStart(int idx) { setPref(STR_US_OpenTabAtStart, idx); } void setOpenTabAfterImport(int idx) { setPref(STR_US_OpenTabAfterImport, idx); } void setRemoveCardReminder(bool b) { setPref(STR_US_RemoveCardReminder, b); } void setDontAskWhenSavingScreenshots(bool b) { setPref(STR_US_DontAskWhenSavingScreenshots, b); } void setShowPersonalData(bool b) { setPref(STR_US_ShowPersonalData, b); } void setVersionString(QString version) { setPref(STR_PREF_VersionString, version); } #ifndef NO_CHECKUPDATES void setUpdatesAutoCheck(bool b) { setPref(STR_GEN_UpdatesAutoCheck, b); } void setAllowEarlyUpdates(bool b) { setPref(STR_PREF_AllowEarlyUpdates, b); } void setUpdatesLastChecked(QDateTime datetime) { setPref(STR_GEN_UpdatesLastChecked, datetime); } void setUpdateCheckFrequency(int freq) { setPref(STR_GEN_UpdateCheckFrequency,freq); } #endif void setAutoOpenLastUsed(bool b) { setPref(STR_GEN_AutoOpenLastUsed , b); } void setLanguage(QString language) { setPref(STR_GEN_Language, m_language=language); } }; extern AppWideSetting *AppSetting; #endif // APPSETTINGS_H OSCAR-code-v1.3.1/oscar/SleepLib/calcs.cpp000066400000000000000000001350211417327530600200720ustar00rootroot00000000000000/* Custom CPAP/Oximetry Calculations Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include "calcs.h" #include "profiles.h" bool SearchEvent(Session * session, ChannelID code, qint64 time, int dur, bool update=true) { qint64 t, start; auto it = session->eventlist.find(code); quint32 *tptr; EventStoreType *dptr; int cnt; //qint64 rate; // bool fixdurations = (session->machine()->loaderName() != STR_MACH_ResMed); if (!p_profile->cpap->resyncFromUserFlagging()) { update=false; } auto evend = session->eventlist.end(); if (it != evend) { for (const auto & el : it.value()) { //rate=el->rate(); cnt = el->count(); // why would this be necessary??? if (el->type() == EVL_Waveform) { qDebug() << "Called SearchEvent on a waveform object!"; return false; } else { start = el->first(); tptr = el->rawTime(); dptr = el->rawData(); for (int j = 0; j < cnt; j++) { t = start + *tptr; // Move the position and set the duration qint64 end1 = time + 5000L; qint64 start1 = end1 - quint64(dur+10)*1000L; qint64 end2 = t + 5000L; qint64 start2 = end2 - quint64(*dptr+10)*1000L; bool overlap = (start1 <= end2) && (start2 <= end1); if (overlap) { if (update) { qint32 delta = time-start; if (delta >= 0) { *tptr = delta; *dptr = (EventStoreType)dur; } } return true; } tptr++; dptr++; } } } } return false; } bool SearchApnea(Session *session, qint64 time, double dur) { for (int i = 0; i < ahiChannels.size(); i++) if (SearchEvent(session, ahiChannels.at(i), time, dur)) return true; // if (SearchEvent(session, CPAP_AllApnea, time, dur)) // return true; // // if (SearchEvent(session, CPAP_Obstructive, time, dur)) // return true; // // if (SearchEvent(session, CPAP_Apnea, time, dur)) // return true; // // if (SearchEvent(session, CPAP_ClearAirway, time, dur)) // return true; // // if (SearchEvent(session, CPAP_Hypopnea, time, dur)) // return true; if (SearchEvent(session, CPAP_UserFlag1, time, dur, false)) return true; if (SearchEvent(session, CPAP_UserFlag2, time, dur, false)) return true; return false; } // Sort BreathPeak by peak index bool operator<(const BreathPeak &p1, const BreathPeak &p2) { return p1.start < p2.start; } //! \brief Filters input to output with a percentile filter with supplied width. //! \param samples Number of samples //! \param width number of surrounding samples to consider //! \param percentile fractional percentage, between 0 and 1 void percentileFilter(EventDataType *input, EventDataType *output, int samples, int width, EventDataType percentile) { if (samples <= 0) { return; } if (percentile > 1) { percentile = 1; } QVector buf(width); int s, e; int z1 = width / 2; int z2 = z1 + (width % 2); int nm1 = samples - 1; //int j; // Scan through all of input for (int k = 0; k < samples; k++) { s = k - z1; e = k + z2; // Cap bounds if (s < 0) { s = 0; } if (e > nm1) { e = nm1; } // int j = 0; for (int i = s; i < e; i++) { buf[j++] = input[i]; } j--; EventDataType val = j * percentile; EventDataType fl = floor(val); // If even percentile, or already max value.. if ((val == fl) || (j >= width - 1)) { nth_element(buf.begin(), buf.begin() + j, buf.begin() + width - 1); val = buf[j]; } else { // Percentile lies between two points, interpolate. double v1, v2; nth_element(buf.begin(), buf.begin() + j, buf.begin() + width - 1); v1 = buf[j]; nth_element(buf.begin(), buf.begin() + j + 1, buf.begin() + width - 1); v2 = buf[j + 1]; val = v1 + (v2 - v1) * (val - fl); } output[k] = val; } } void xpassFilter(EventDataType *input, EventDataType *output, int samples, EventDataType weight) { // prime the first value output[0] = input[0]; for (int i = 1; i < samples; i++) { output[i] = weight * input[i] + (1.0 - weight) * output[i - 1]; } //output[samples-1]=input[samples-1]; } FlowParser::FlowParser() { m_session = nullptr; m_flow = nullptr; m_filtered = nullptr; m_gain = 1; m_samples = 0; m_startsUpper = true; // Allocate filter chain buffers.. m_filtered = (EventDataType *) malloc(max_filter_buf_size); } FlowParser::~FlowParser() { free(m_filtered); // for (int i=0;igain(); m_rate = flow->rate(); m_samples = flow->count(); EventStoreType *inraw = flow->rawData(); // Make sure we won't overflow internal buffers if (m_samples > max_filter_buf_size) { qDebug() << "Error: Sample size exceeds max_filter_buf_size in FlowParser::openFlow().. Capping!!!"; m_samples = max_filter_buf_size; } // Begin with the second internal buffer EventDataType *buf = m_filtered; // Apply gain to waveform EventStoreType *eptr = inraw + m_samples; // Convert from store type to floats.. for (; inraw < eptr; ++inraw) { *buf++ = EventDataType(*inraw) * m_gain; } // Apply the rest of the filters chain buf = applyFilters(m_filtered, m_samples); Q_UNUSED(buf) // Scan for and create an index of each breath calcPeaks(m_filtered, m_samples); } // Calculates breath upper & lower peaks for a chunk of EventList data void FlowParser::calcPeaks(EventDataType *input, int samples) { if (samples <= 0) { return; } EventDataType min = 0, max = 0, c, lastc = 0; EventDataType zeroline = 0; double rate = m_flow->rate(); double flowstart = m_flow->first(); double time; //, lasttime; //double peakmax = flowstart, //double peakmin = flowstart; // lasttime = time = flowstart; breaths.clear(); // Estimate storage space needed using typical average breaths per minute. m_minutes = double(m_flow->last() - m_flow->first()) / 60000.0; const double avgbpm = 20; // average breaths per minute of a standard human int guestimate = m_minutes * avgbpm; // reserve some memory breaths.reserve(guestimate); // Prime min & max, and see which side of the zero line we are starting from. c = input[0]; min = max = c; lastc = c; m_startsUpper = (c >= zeroline); qint32 start = 0, middle = 0; int sps = 1000 / m_rate; int len = 0; //int lastk = 0; //qint64 sttime = time; // For each samples, find the peak upper and lower breath components for (int k = 0; k < samples; k++) { c = input[k]; if (c >= zeroline) { // Did we just cross the zero line going up? if (lastc < zeroline) { // This helps filter out dirty breaths.. len = k - start; if ((max > 3) && ((max - min) > 8) && (len > sps) && (middle > start)) { // peak detection may not be needed.. breaths.push_back(BreathPeak(min, max, start, middle, k)); // Set max for start of the upper breath cycle max = c; //peakmax = time; // Starting point of next breath cycle start = k; //sttime = time; } } else if (c > max) { // Update upper breath peak max = c; //peakmax = time; } } if (c < zeroline) { // Did we just cross the zero line going down? if (lastc >= zeroline) { // Set min for start of the lower breath cycle min = c; //peakmin = time; middle = k; } else if (c < min) { // Update lower breath peak min = c; //peakmin = time; } } //lasttime = time; time += rate; lastc = c; //lastk = k; } } //! \brief Calculate Respiratory Rate, TidalVolume, Minute Ventilation, Ti & Te.. // These are grouped together because, a) it's faster, and b) some of these calculations rely on others. void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool calcMv) { if (!m_session) { return; } // Don't even bother if only a few breaths in this chunk const int lowthresh = 4; int nm = breaths.size(); if (nm < lowthresh) { return; } const qint64 minute = 60000; double start = m_flow->first(); double time = start; int bs, be, bm; double st, et=0, mt; ///////////////////////////////////////////////////////////////////////////////// // Respiratory Rate setup ///////////////////////////////////////////////////////////////////////////////// EventDataType minrr = 0, maxrr = 0; EventList *RR = nullptr; quint32 *rr_tptr = nullptr; EventStoreType *rr_dptr = nullptr; if (calcResp) { RR = m_session->AddEventList(CPAP_RespRate, EVL_Event); minrr = RR->Min(), maxrr = RR->Max(); RR->setGain(0.2F); RR->setFirst(time + minute); RR->getData().resize(nm); RR->getTime().resize(nm); rr_tptr = RR->rawTime(); rr_dptr = RR->rawData(); } int rr_count = 0; double len, st2, et2, adj, stmin, b, rr = 0; double len2; ///////////////////////////////////////////////////////////////////////////////// // Inspiratory / Expiratory Time setup ///////////////////////////////////////////////////////////////////////////////// double lastte2 = 0, lastti2 = 0, lastte = 0, lastti = 0, te, ti, ti1, te1, c; EventList *Te = nullptr, * Ti = nullptr; if (calcTi) { Ti = m_session->AddEventList(CPAP_Ti, EVL_Event); Ti->setGain(0.02F); } if (calcTe) { Te = m_session->AddEventList(CPAP_Te, EVL_Event); Te->setGain(0.02F); } ///////////////////////////////////////////////////////////////////////////////// // Tidal Volume setup ///////////////////////////////////////////////////////////////////////////////// EventList *TV = nullptr; EventDataType mintv = 0, maxtv = 0, tv = 0; double val1, val2; quint32 *tv_tptr = nullptr; EventStoreType *tv_dptr = nullptr; int tv_count = 0; double tvlast = 0, tvlast2 = 0, tvlast3 = 0; if (calcTv) { TV = m_session->AddEventList(CPAP_TidalVolume, EVL_Event); mintv = TV->Min(), maxtv = TV->Max(); TV->setGain(20); TV->setFirst(start); TV->getData().resize(nm); TV->getTime().resize(nm); tv_tptr = TV->rawTime(); tv_dptr = TV->rawData(); } ///////////////////////////////////////////////////////////////////////////////// // Minute Ventilation setup ///////////////////////////////////////////////////////////////////////////////// EventList *MV = nullptr; EventDataType mv; if (calcMv) { MV = m_session->AddEventList(CPAP_MinuteVent, EVL_Event); MV->setGain(0.125); // gain set to 1/8th } EventDataType sps = (1000.0 / m_rate); // Samples Per Second qint32 timeval = 0; // Time relative to start BreathPeak * bpstr = breaths.data(); BreathPeak * bpend = bpstr + nm; // For each breath... for (BreathPeak * bp = bpstr; bp != bpend; ++bp) { bs = bp->start; bm = bp->middle; be = bp->end; // Calculate start, middle and end time of this breath st = start + bs * m_rate; mt = start + bm * m_rate; timeval = be * m_rate; et = start + timeval; ///////////////////////////////////////////////////////////////////// // Calculate Inspiratory Time (Ti) for this breath ///////////////////////////////////////////////////////////////////// if (calcTi) { // Ti is simply the time between the start of a breath and it's midpoint // Note the 50.0 is the gain value, to give it better resolution // (and mt and st are in milliseconds) ti = ((mt - st) / 1000.0) * 50.0; // Average for a little smoothing ti1 = (lastti2 + lastti + ti * 2) / 4.0; Ti->AddEvent(mt, ti1); // Add the event // Track the last two values to use for averaging lastti2 = lastti; lastti = ti; } ///////////////////////////////////////////////////////////////////// // Calculate Expiratory Time (Te) for this breath ///////////////////////////////////////////////////////////////////// if (calcTe) { // Te is simply the time between the second half of the breath te = ((et - mt) / 1000.0) * 50.0; // Average last three values.. te1 = (lastte2 + lastte + te * 2 ) / 4.0; Te->AddEvent(mt, te1); lastte2 = lastte; lastte = te; } ///////////////////////////////////////////////////////////////////// // Calculate TidalVolume ///////////////////////////////////////////////////////////////////// if (calcTv) { val1 = 0, val2 = 0; // Scan the upper breath for (int j = bs; j < bm; j++) { // convert flow to ml/s to L/min and divide by samples per second c = double(qAbs(m_filtered[j])) * 1000.0 / 60.0 / sps; val2 += c; //val2+=c*c; // for RMS } tv = val2; bool usebothhalves = false; if (usebothhalves) { for (int j = bm; j < be; j++) { // convert flow to ml/s to L/min and divide by samples per second c = double(qAbs(m_filtered[j])) * 1000.0 / 60.0 / sps; val1 += c; //val1 += c*c; // for RMS } tv = (qAbs(val2) + qAbs(val1)) / 2; } // Add the other half here and average might make it more accurate // But last time I tried it was pretty messy // Perhaps needs a bit of history averaging.. // calculate root mean square //double n=bm-bs; //double q=(1/n)*val2; //double x=sqrt(q)*2; //val2=x; // Average TV over last three data points if (tv_count == 0) { if (tv > 800) // Very much a Q&D patch to handle the first TV value not being calculated well tv = 300; // If unreasonable, just set to a "reasonable" number. tvlast = tvlast2 = tvlast3 = tv; } // if (tv_count < 4) { // qDebug() << "tv" << tv << tvlast << tvlast2 << tvlast3 << "avg" << (tvlast + tvlast2 + tvlast3 + tv*2)/5; // } tv = (tvlast + tvlast2 + tvlast3 + tv*2)/5; tvlast3 = tvlast2; tvlast2 = tvlast; tvlast = tv; if (tv < mintv) { mintv = tv; } if (tv > maxtv) { maxtv = tv; } *tv_tptr++ = timeval; *tv_dptr++ = tv / 20.0; tv_count++; } ///////////////////////////////////////////////////////////////////// // Respiratory Rate Calculations ///////////////////////////////////////////////////////////////////// if (calcResp) { stmin = et - minute; if (stmin < start) { stmin = start; } //len = et - stmin; rr = 0; len2 = 0; // Step back through last minute and count breaths BreathPeak *bpstr1 = bpstr-1; for (BreathPeak *p = bp; p != bpstr1; --p) { st2 = start + double(p->start) * m_rate; et2 = start + double(p->end) * m_rate; if (et2 < stmin) { break; } len = et2 - st2; if (st2 < stmin) { // Partial breath st2 = stmin; adj = et2 - st2; b = (1.0 / len) * adj; len2 += adj; } else { b = 1; len2 += len; } rr += b; } if (len2 < minute) { rr *= minute / len2; } // Calculate min & max if (rr < minrr) { minrr = rr; } if (rr > maxrr) { maxrr = rr; } // Add manually.. (much quicker) *rr_tptr++ = timeval; // Use the same gains as ResMed.. *rr_dptr++ = rr * 5.0; rr_count++; } if (calcMv && calcResp && calcTv) { // Minute Ventilation is tidal volume times respiratory rate mv = (tv / 1000.0) * rr; // The 8.0 is the gain of the MV EventList to boost resolution MV->AddEvent(et, mv * 8.0); } } ///////////////////////////////////////////////////////////////////// // Respiratory Rate post filtering ///////////////////////////////////////////////////////////////////// if (calcResp) { RR->setMin(minrr); RR->setMax(maxrr); RR->setFirst(start); RR->setLast(et); RR->setCount(rr_count); } ///////////////////////////////////////////////////////////////////// // Tidal Volume post filtering ///////////////////////////////////////////////////////////////////// if (calcTv) { TV->setMin(mintv); TV->setMax(maxtv); TV->setFirst(start); TV->setLast(et); TV->setCount(tv_count); } } void FlowParser::flagUserEvents(ChannelID code, EventDataType restriction, EventDataType duration) { int numbreaths = breaths.size(); //EventDataType mx, mn; QVector br; QVector bstart; QVector bend; // Allocate some memory beforehand so it doesn't have to slow it down mid calculations bstart.reserve(numbreaths * 2); bend.reserve(numbreaths * 2); br.reserve(numbreaths * 2); double start = m_flow->first(); double st, et, dur; qint64 len; bool allowDuplicates = p_profile->cpap->userEventDuplicates(); // Get the Breath list, which is calculated by the previously run breath marker algorithm. BreathPeak *bpstr = breaths.data(); BreathPeak *bpend = bpstr + numbreaths; // Create a list containing the abs of min and max waveform flow for each breath for (BreathPeak *p = bpstr; p != bpend; ++p) { br.push_back(qAbs(p->max)); br.push_back(qAbs(p->min)); } // The following ignores the outliers to get a cleaner cutoff value // 60th percentile was chosen because it's a little more than median, and, well, reasons I can't remember specifically.. :-} // Look for the 60th percentile of the abs'ed min/max values const EventDataType perc = 0.6F; int idx = float(br.size()) * perc; nth_element(br.begin(), br.begin() + idx, br.end() - 1); // Take this value as the peak EventDataType peak = br[idx]; ; // Scale the restriction percentage to the peak to find the cutoff value EventDataType cutoffval = peak * (restriction / 100.0F); int bs, bm, be, bs1, bm1, be1; // For each Breath, search for flow restrictions for (BreathPeak *p = bpstr; p != bpend; ++p) { // Todo: Define these markers in the comments better bs = p->start; // breath start bm = p->middle; // breath middle be = p->end; // breath end // mx = p->max; // mn = p->min; //val = mx - mn; // the total height from top to bottom of this breath // Scan the breath in the flow data and stop at the first location more than the cutoff value // (Only really needs to scan to the middle.. I'm not sure why I made it go all the way to the end.) for (bs1 = bs; bs1 < be; bs1++) { if (qAbs(m_filtered[bs1]) > cutoffval) { break; } } // if bs1 has reached the end, this means the entire marked breath is within the restriction // Scan backwards from the middle to the start, stopping at the first value past the cutoff value for (bm1 = bm; bm1 > bs; bm1--) { if (qAbs(m_filtered[bm1]) > cutoffval) { break; } } // Now check if a value was found in the first half of the breath above the cutoff value if (bm1 >= bs1) { // Good breath... And add it as a beginning/end marker for the next stage bstart.push_back(bs1); bend.push_back(bm1); } // else we crossed over and the first half of the breath is under the threshold, therefore has a flow restricted // Now do the other half of the breath.... // Scan from middle to end of breath, stopping at first cutoff value for (bm1 = bm; bm1 < be; bm1++) { if (qAbs(m_filtered[bm1]) > cutoffval) { break; } } // Scan backwards from the end to the middle of the breath, stopping at the first cutoff value for (be1 = be; be1 > bm; be1--) { if (qAbs(m_filtered[be1]) > cutoffval) { break; } } // Check for crossover again if (be1 >= bm1) { // Good strong healthy breath.. add the beginning and end to the breath markers. bstart.push_back(bm1); bend.push_back(be1); } // else crossed over again.. breathe damn you! // okay, this looks like cruft.. // st = start + bs1 * m_rate; // et = start + be1 * m_rate; } int bsize = bstart.size(); // Number of breath components over cutoff threshold EventList *uf = nullptr; // For each good breath marker, look at the gaps in between for (int i = 0; i < bsize - 1; i++) { bs = bend[i]; // start at the end of the healthy breath be = bstart[i + 1]; // look ahead to the beginning of the next one // Calculate the start and end timestamps st = start + bs * m_rate; et = start + be * m_rate; // Calculate the length of the flow restriction len = et - st; dur = len / 1000.0; // (scale to seconds, not milliseconds) if (dur >= duration) { // is the event greater than the duration threshold? // Unless duplicates have been specifically allowed, scan for any apnea's already detected by the machine if (allowDuplicates || !SearchApnea(m_session, et, dur)) { if (!uf) { // Create event list if not already done uf = m_session->AddEventList(code, EVL_Event); } // Add the user flag at the end uf->AddEvent(et, dur); } } } } void FlowParser::flagEvents() { if (!p_profile->cpap->userEventFlagging()) { return; } int numbreaths = breaths.size(); if (numbreaths < 5) { return; } flagUserEvents(CPAP_UserFlag1, p_profile->cpap->userEventRestriction(), p_profile->cpap->userEventDuration()); flagUserEvents(CPAP_UserFlag2, p_profile->cpap->userEventRestriction2(), p_profile->cpap->userEventDuration2()); } void calcRespRate(Session *session, FlowParser *flowparser) { if (session->type() != MT_CPAP) { return; } // if (session->machine()->loaderName() != STR_MACH_PRS1) return; if (!session->eventlist.contains(CPAP_FlowRate)) { //qDebug() << "calcRespRate called without FlowRate waveform available"; return; //need flow waveform } bool trashfp; if (!flowparser) { flowparser = new FlowParser(); trashfp = true; //qDebug() << "calcRespRate called without valid FlowParser object.. using a slow throw-away!"; //return; } else { trashfp = false; } bool calcResp = !session->eventlist.contains(CPAP_RespRate); bool calcTv = !session->eventlist.contains(CPAP_TidalVolume); bool calcTi = !session->eventlist.contains(CPAP_Ti); bool calcTe = !session->eventlist.contains(CPAP_Te); bool calcMv = !session->eventlist.contains(CPAP_MinuteVent); int z = (calcResp ? 1 : 0) + (calcTv ? 1 : 0) + (calcMv ? 1 : 0); // Force calculation for testing calculation vs CPAP data // z = 1; // If any of these three missing, remove all, and switch all on if (z > 0 && z < 3) { if (!calcResp && !calcTv && !calcMv) { calcTv = calcMv = calcResp = true; } auto & list = session->eventlist[CPAP_RespRate]; for (auto & l : list) { delete l; } session->eventlist[CPAP_RespRate].clear(); auto & list2 = session->eventlist[CPAP_TidalVolume]; for (auto & l2 : list2) { delete l2; } session->eventlist[CPAP_TidalVolume].clear(); auto & list3 = session->eventlist[CPAP_MinuteVent]; for (auto & l3 : list3) { delete l3; } session->eventlist[CPAP_MinuteVent].clear(); } flowparser->clearFilters(); // No filters works rather well with the new peak detection algorithm.. // Although the output could use filtering. //flowparser->addFilter(FilterPercentile,7,0.5); //flowparser->addFilter(FilterPercentile,5,0.5); //flowparser->addFilter(FilterXPass,0.5); auto & EVL = session->eventlist[CPAP_FlowRate]; for (auto & flow : EVL) { if (flow->count() > 20) { flowparser->openFlow(session, flow); flowparser->calc(calcResp, calcTv, calcTi , calcTe, calcMv); flowparser->flagEvents(); } } if (trashfp) { delete flowparser; } } EventDataType calcAHI(Session *session, qint64 start, qint64 end) { bool rdi = p_profile->general->calculateRDI(); double hours, ahi, cnt; if (start < 0) { // much faster.. hours = session->hours(); cnt = session->count(AllAhiChannels); // + session->count(CPAP_AllApnea) // + session->count(CPAP_Hypopnea) // + session->count(CPAP_ClearAirway) // + session->count(CPAP_Apnea); if (rdi) { cnt += session->count(CPAP_RERA); } ahi = cnt / hours; } else { hours = double(end - start) / 3600000L; if (hours == 0) { return 0; } cnt = session->rangeCount(AllAhiChannels, start, end); // cnt = session->rangeCount(CPAP_Obstructive, start, end) // + session->rangeCount(CPAP_AllApnea, start, end) // + session->rangeCount(CPAP_Hypopnea, start, end) // + session->rangeCount(CPAP_ClearAirway, start, end) // + session->rangeCount(CPAP_Apnea, start, end); if (rdi) { cnt += session->rangeCount(CPAP_RERA, start, end); } ahi = cnt / hours; } return ahi; } int calcAHIGraph(Session *session) { bool calcrdi = session->machine()->loaderName() == "PRS1"; const qint64 window_step = 30000; // 30 second windows double window_size = p_profile->cpap->AHIWindow(); qint64 window_size_ms = window_size * 60000L; bool zeroreset = p_profile->cpap->AHIReset(); if (session->type() != MT_CPAP) { return 0; } bool hasahi = session->eventlist.contains(CPAP_AHI); bool hasrdi = session->eventlist.contains(CPAP_RDI); if (hasahi && hasrdi) { return 0; // abort if already there } if (!(!hasahi && !hasrdi)) { session->destroyEvent(CPAP_AHI); session->destroyEvent(CPAP_RDI); } bool gotsome = false; for (int i = 0; i < ahiChannels.size(); i++) gotsome = gotsome || session->channelExists(ahiChannels.at(i)); // if (!session->channelExists(CPAP_Obstructive) && // !session->channelExists(CPAP_AllApnea) && // !session->channelExists(CPAP_Hypopnea) && // !session->channelExists(CPAP_Apnea) && // !session->channelExists(CPAP_ClearAirway) && // !session->channelExists(CPAP_RERA) if (!gotsome) return 0; qint64 first = session->first(), last = session->last(), f; EventList *AHI = new EventList(EVL_Event); AHI->setGain(0.02F); session->eventlist[CPAP_AHI].push_back(AHI); EventList *RDI = nullptr; if (calcrdi) { RDI = new EventList(EVL_Event); RDI->setGain(0.02F); session->eventlist[CPAP_RDI].push_back(RDI); } EventDataType ahi, rdi; qint64 ti = first; //, lastti = first; double avgahi = 0; double avgrdi = 0; int cnt = 0; double events; double hours = (window_size / 60.0F); if (zeroreset) { // I personally don't see the point of resetting each hour. do { // For each window, in 30 second increments for (qint64 t = ti; t < ti + window_size_ms; t += window_step) { if (t > last) { break; } events = session->rangeCount(AllAhiChannels, ti, t); // events = session->rangeCount(CPAP_Obstructive, ti, t) // + session->rangeCount(CPAP_Hypopnea, ti, t) // + session->rangeCount(CPAP_Hypopnea, ti, t) // + session->rangeCount(CPAP_ClearAirway, ti, t) // + session->rangeCount(CPAP_Apnea, ti, t); ahi = events / hours; AHI->AddEvent(t, ahi * 50); avgahi += ahi; if (calcrdi) { events += session->rangeCount(CPAP_RERA, ti, t); rdi = events / hours; RDI->AddEvent(t, rdi * 50); avgrdi += rdi; } cnt++; } //lastti = ti; ti += window_size_ms; } while (ti < last); } else { for (ti = first; ti < last; ti += window_step) { f = ti - window_size_ms; //hours=window_size; //double(ti-f)/3600000L; // events = session->rangeCount(CPAP_Obstructive, f, ti) // + session->rangeCount(CPAP_AllApnea, f, ti) // + session->rangeCount(CPAP_Hypopnea, f, ti) // + session->rangeCount(CPAP_ClearAirway, f, ti) // + session->rangeCount(CPAP_Apnea, f, ti); events = session->rangeCount(AllAhiChannels, f, ti); ahi = events / hours; avgahi += ahi; AHI->AddEvent(ti, ahi * 50); if (calcrdi) { events += session->rangeCount(CPAP_RERA, f, ti); rdi = events / hours; RDI->AddEvent(ti, rdi * 50); avgrdi += rdi; } cnt++; //lastti = ti; ti += window_step; } } AHI->AddEvent(last, 0); if (calcrdi) { RDI->AddEvent(last, 0); } if (!cnt) { avgahi = 0; avgrdi = 0; } else { avgahi /= double(cnt); avgrdi /= double(cnt); } cnt++; session->setAvg(CPAP_AHI, avgahi); if (calcrdi) { session->setAvg(CPAP_RDI, avgrdi); } return cnt; } class LeakCalculator { public: virtual ~LeakCalculator() {} virtual EventDataType calcLeakAt(EventDataType pressure) = 0; }; class LinearInterpolateLeak : public LeakCalculator { public: LinearInterpolateLeak(EventDataType minPressure, EventDataType leakAtMinPressure, EventDataType maxPressure, EventDataType leakAtMaxPressure) : m_minPressure(minPressure), m_leakAtMinPressure(leakAtMinPressure), m_maxPressure(maxPressure), m_leakAtMaxPressure(leakAtMaxPressure) { m_leakSlope = (m_leakAtMaxPressure - m_leakAtMinPressure) / (m_maxPressure - m_minPressure); } virtual ~LinearInterpolateLeak() {} virtual EventDataType calcLeakAt(EventDataType pressure) { float dx = pressure - m_minPressure; float leak = dx * m_leakSlope + m_leakAtMinPressure; return leak; } protected: EventDataType m_minPressure, m_leakAtMinPressure; EventDataType m_maxPressure, m_leakAtMaxPressure; float m_leakSlope; }; class ProfileLeakCalculator : public LinearInterpolateLeak { public: ProfileLeakCalculator(Profile* profile) : LinearInterpolateLeak(4.0, profile->cpap->custom4cmH2OLeaks(), 20.0, profile->cpap->custom20cmH2OLeaks()) {} virtual ~ProfileLeakCalculator() {} }; // Provides an accessor for the value at a point in time for discontinuous (channel) data. // This is designed to be efficient for the common case of repeated searches over increasing timestamps. class TimeSeries { public: TimeSeries(QVector events) : m_events(events) { m_curEventList = nullptr; m_curEvent = 0; m_lastTime = 0; } EventDataType valueAt(qint64 time, bool* outValid) { EventDataType result = 0; bool found = findEventListContaining(time); if (found) { m_lastTime = time; result = findValueAtOrBefore(time); } *outValid = found; return result; } protected: bool findEventListContaining(qint64 time) { // Fast return if we're already there. if (m_curEventList) { if (m_curEventList->first() <= time && time <= m_curEventList->last()) { if (time < m_lastTime) { // Reset the offset for correctness in case someone searches backwards. m_curEvent = 0; } return true; } } // Search the event lists to see if any match. bool found = false; for (auto & eventlist : m_events) { if (eventlist->first() <= time && time <= eventlist->last()) { found = true; m_curEventList = eventlist; m_curEvent = 0; break; } } return found; } EventDataType findValueAtOrBefore(qint64 time) { // m_curEvent is always <= time, due to eventlist first/last search and m_curEvent reset. for (quint32 i = m_curEvent + 1; i < m_curEventList->count(); i++) { qint64 nextTime = m_curEventList->time(i); if (nextTime > time) { // stick with the current value break; } // else nextTime <= time, advance m_curEvent = i; } EventDataType result = m_curEventList->data(m_curEvent); return result; } QVector m_events; EventList* m_curEventList; int m_curEvent; qint64 m_lastTime; }; int calcLeaks(Session *session) { if (!p_profile->cpap->calculateUnintentionalLeaks()) { return 0; } if (session->type() != MT_CPAP) { return 0; } if (session->eventlist.contains(CPAP_Leak)) { return 0; } // abort if already there if (!session->eventlist.contains(CPAP_LeakTotal)) { return 0; } // can't calculate without this.. // Choose the formula for calculating mask leakage as a function of pressure. LeakCalculator* calc = new ProfileLeakCalculator(p_profile); // Prefer IPAPSet/PressureSet for machines (PRS1) that use these, since they use Pressure to report averages. ChannelID pressure_channel = CPAP_Pressure; // default for (auto & ch : { CPAP_IPAPSet, CPAP_IPAP, CPAP_PressureSet }) { if (session->eventlist.contains(ch)) { pressure_channel = ch; break; } } TimeSeries pressureEvents(session->eventlist[pressure_channel]); int totalEvents = 0; auto & totalLeaks = session->eventlist[CPAP_LeakTotal]; for (auto & eventList : totalLeaks) { EventList *leak = session->AddEventList(CPAP_Leak, EVL_Event, 1); // Scan through this Total Leak list's data for (quint32 i = 0; i < eventList->count(); i++) { bool valid; qint64 time = eventList->time(i); EventDataType pressure = pressureEvents.valueAt(time, &valid); if (valid) { // lookup and subtract the calculated leak baseline for this pressure EventDataType totalLeak = eventList->data(i); EventDataType maskLeak = calc->calcLeakAt(pressure); EventDataType val = totalLeak - maskLeak; if (val < 0) { val = 0; } leak->AddEvent(time, val); } } totalEvents += leak->count(); } delete calc; return totalEvents; } void flagLargeLeaks(Session *session) { // Already contains? if (session->eventlist.contains(CPAP_LargeLeak)) return; if (!session->eventlist.contains(CPAP_Leak)) return; EventDataType threshold = p_profile->cpap->leakRedline(); if (threshold <= 0) { return; } QVector & EVL = session->eventlist[CPAP_Leak]; int evlsize = EVL.size(); if (evlsize == 0) return; EventList * LL = nullptr; qint64 time = 0; EventDataType value, lastvalue=-1; qint64 leaktime=0; int count; for (auto & el : EVL) { count = el->count(); if (!count) continue; leaktime = 0; //EventDataType leakvalue = 0; lastvalue = -1; for (int i=0; i < count; ++i) { time = el->time(i); value = el->data(i); if (value >= threshold) { if (lastvalue < threshold) { leaktime = time; //leakvalue = value; } } else if (lastvalue > threshold) { if (!LL) { LL=session->AddEventList(CPAP_LargeLeak, EVL_Event); } int duration = (time - leaktime) / 1000L; LL->AddEvent(time, duration); } lastvalue = value; } } if (lastvalue > threshold) { if (!LL) { LL=session->AddEventList(CPAP_LargeLeak, EVL_Event); } int duration = (time - leaktime) / 1000L; LL->AddEvent(time, duration); } } int calcPulseChange(Session *session) { if (session->eventlist.contains(OXI_PulseChange)) { return 0; } auto it = session->eventlist.find(OXI_Pulse); if (it == session->eventlist.end()) { return 0; } EventDataType val, val2, change, tmp; qint64 time, time2; qint64 window = p_profile->oxi->pulseChangeDuration(); window *= 1000; change = p_profile->oxi->pulseChangeBPM(); EventList *pc = new EventList(EVL_Event, 1, 0, 0, 0, 0, true); pc->setFirst(session->first(OXI_Pulse)); qint64 lastt; EventDataType lv = 0; int li = 0; int max; int elcount; for (auto & el : it.value()) { elcount = el->count(); for (int i = 0; i < elcount; ++i) { val = el->data(i); time = el->time(i); lastt = 0; lv = change; max = 0; for (int j = i + 1; j < elcount; ++j) { // scan ahead in the window time2 = el->time(j); if (time2 > time + window) { break; } val2 = el->data(j); tmp = qAbs(val2 - val); if (tmp > lv) { lastt = time2; if (tmp > max) { max = tmp; } //lv=tmp; li = j; } } if (lastt > 0) { qint64 len = (lastt - time) / 1000.0; pc->AddEvent(lastt, len, tmp); i = li; } } } if (pc->count() == 0) { delete pc; return 0; } session->eventlist[OXI_PulseChange].push_back(pc); session->setMin(OXI_PulseChange, pc->Min()); session->setMax(OXI_PulseChange, pc->Max()); session->setCount(OXI_PulseChange, pc->count()); session->setFirst(OXI_PulseChange, pc->first()); session->setLast(OXI_PulseChange, pc->last()); return pc->count(); } int calcSPO2Drop(Session *session) { if (session->eventlist.contains(OXI_SPO2Drop)) { return 0; } auto it = session->eventlist.find(OXI_SPO2); if (it == session->eventlist.end()) { return 0; } EventDataType val, val2, change, tmp; qint64 time, time2; qint64 window = p_profile->oxi->spO2DropDuration(); window *= 1000; change = p_profile->oxi->spO2DropPercentage(); EventList *pc = new EventList(EVL_Event, 1, 0, 0, 0, 0, true); qint64 lastt; //EventDataType lv = 0; int li = 0; // Fix me.. Time scale varies. //const unsigned ringsize=30; //EventDataType ring[ringsize]={0}; //qint64 rtime[ringsize]={0}; //int rp=0; int min; int cnt = 0; tmp = 0; qint64 start = 0; // Calculate median baseline QList med; int elcount; for (auto & el : it.value()) { elcount = el->count(); for (int i = 0; i < elcount; i++) { val = el->data(i); time = el->time(i); if (val > 0) { med.push_back(val); } if (!start) { start = time; } if (time > start + 3600000) { break; } // just look at the first hour tmp += val; cnt++; } } EventDataType baseline = 0; if (med.size() > 0) { std::sort(med.begin(), med.end()); int midx = float(med.size()) * 0.90; if (midx > med.size() - 1) { midx = med.size() - 1; } if (midx < 0) { midx = 0; } baseline = med[midx]; } session->settings[OXI_SPO2Drop] = baseline; //EventDataType baseline=round(tmp/EventDataType(cnt)); EventDataType current; qDebug() << "Calculated baseline" << baseline; for (auto & el : it.value()) { elcount = el->count(); for (int i = 0; i < elcount; ++i) { current = el->data(i); if (!current) { continue; } time = el->time(i); /*ring[rp]=val; rtime[rp]=time; rp++; rp=rp % ringsize; if (i time-300000) { // only look at recent entries.. tmp+=ring[j]; cnt++; } } if (!cnt) { unsigned j=abs((rp-1) % ringsize); tmp=(ring[j]+val)/2; } else tmp/=EventDataType(cnt); */ val = baseline; lastt = 0; //lv = val; min = val; for (int j = i; j < elcount; ++j) { // scan ahead in the window time2 = el->time(j); //if (time2 > time+window) break; val2 = el->data(j); if (val2 > baseline - change) { break; } lastt = time2; li = j + 1; } if (lastt > 0) { qint64 len = (lastt - time); if (len >= window) { pc->AddEvent(lastt, len / 1000, val - min); i = li; } } } } if (pc->count() == 0) { delete pc; return 0; } session->eventlist[OXI_SPO2Drop].push_back(pc); session->setMin(OXI_SPO2Drop, pc->Min()); session->setMax(OXI_SPO2Drop, pc->Max()); session->setCount(OXI_SPO2Drop, pc->count()); session->setFirst(OXI_SPO2Drop, pc->first()); session->setLast(OXI_SPO2Drop, pc->last()); return pc->count(); } OSCAR-code-v1.3.1/oscar/SleepLib/calcs.h000066400000000000000000000121221417327530600175330ustar00rootroot00000000000000/* Custom CPAP/Oximetry Calculations Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef CALCS_H #define CALCS_H #include "day.h" //! param samples Number of samples //! width number of surrounding samples to consider //! percentile fractional percentage, between 0 and 1 void percentileFilter(EventDataType *input, EventDataType *output, int samples, int width, EventDataType percentile); void xpassFilter(EventDataType *input, EventDataType *output, int samples, EventDataType weight); enum FilterType { FilterNone = 0, FilterPercentile, FilterXPass }; struct Filter { Filter(FilterType t, EventDataType p1, EventDataType p2, EventDataType p3) { type = t; param1 = p1; param2 = p2; param3 = p3; } Filter() { type = FilterNone; param1 = 0; param2 = 0; param3 = 0; } Filter(const Filter ©) { type = copy.type; param1 = copy.param1; param2 = copy.param2; param3 = copy.param3; } FilterType type; EventDataType param1; EventDataType param2; EventDataType param3; }; struct BreathPeak { BreathPeak() { min = 0; max = 0; start = 0; middle = 0; end = 0; } // peakmin=0; peakmax=0; } BreathPeak(EventDataType _min, EventDataType _max, qint32 _start, qint32 _middle, qint32 _end) {//, qint64 _peakmin, qint64 _peakmax) { min = _min; max = _max; start = _start; middle = _middle; end = _end; //peakmax=_peakmax; //peakmin=_peakmin; } BreathPeak(const BreathPeak ©) { min = copy.min; max = copy.max; start = copy.start; middle = copy.middle; end = copy.end; //peakmin=copy.peakmin; //peakmax=copy.peakmax; } int samplelength() { return end - start; } int upperLength() { return middle - start; } int lowerLength() { return end - middle; } EventDataType min; // peak value EventDataType max; // peak value qint32 start; // beginning zero cross qint32 middle; // ending zero cross qint32 end; // ending zero cross //qint64 peakmin; // min peak index //qint64 peakmax; // max peak index }; bool operator<(const BreathPeak &p1, const BreathPeak &p2); const int num_filter_buffers = 2; const int max_filter_buf_size = 2097152 * sizeof(EventDataType); //! \brief Class to process Flow Rate waveform data class FlowParser { public: FlowParser(); ~FlowParser(); //! \brief Clears the (input) filter chain void clearFilters(); //! \brief Applies the filter chain to input, with supplied number of samples EventDataType *applyFilters(EventDataType *input, int samples); //! \brief Add the filter void addFilter(FilterType ft, EventDataType p1 = 0, EventDataType p2 = 0, EventDataType p3 = 0) { m_filters.push_back(Filter(ft, p1, p2, p3)); } //! \brief Opens the flow rate EventList, applies the input filter chain, and calculates peaks void openFlow(Session *session, EventList *flow); //! \brief Calculates the upper and lower breath peaks void calcPeaks(EventDataType *input, int samples); // Minute vent needs Resp & TV calcs made here.. void calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool calcMv); void flagEvents(); void flagUserEvents(ChannelID code, EventDataType restriction, EventDataType duration); /*void calcTidalVolume(); void calcRespRate(); void calcMinuteVent(); */ QList m_filters; protected: QVector breaths; int m_samples; EventList *m_flow; Session *m_session; EventDataType m_gain; EventDataType m_rate; EventDataType m_minutes; //! \brief The filtered waveform EventDataType *m_filtered; //! \brief BreathPeak's start on positive cycle? bool m_startsUpper; private: EventDataType *m_buffers[num_filter_buffers]; }; bool SearchApnea(Session *session, qint64 time, double dur); //! \brief Calculate Respiratory Rate, Tidal Volume & Minute Ventilation for PRS1 data void calcRespRate(Session *session, FlowParser *flowparser = nullptr); //! \brief Calculates the sliding window AHI graph int calcAHIGraph(Session *session); //! \brief Calculates AHI for a session between start & end (a support function for the sliding window graph) EventDataType calcAHI(Session *session, qint64 start = -1, qint64 end = -1); //! \brief Scans for leaks over Redline and flags as large leaks, unless machine provided them already void flagLargeLeaks(Session *session); //! \brief Leaks calculations for PRS1 int calcLeaks(Session *session); //! \brief Calculate Pulse change flagging, according to preferences int calcPulseChange(Session *session); //! \brief Calculate SPO2 Drop flagging, according to preferences int calcSPO2Drop(Session *session); #endif // CALCS_H OSCAR-code-v1.3.1/oscar/SleepLib/common.cpp000066400000000000000000001024141417327530600202750ustar00rootroot00000000000000/* SleepLib Common Functions * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #ifndef BROKEN_OPENGL_BUILD #include #endif #include #include #include #include #include #include #include #include #include #include "SleepLib/common.h" #ifdef _MSC_VER #include #else #include #include #endif #include "version.h" #include "profiles.h" #include "mainwindow.h" extern MainWindow * mainwin; QString MedDateFormat = "ddd MMM d yyyy"; // QT default value, which we override if we can bool dayFirst = false; // System locale and regional settings support only a "short" date (m/d/yyy) and a "long" // date (day of week, month, day, year -- all spelled out fully). We get the formatting // for the long format, shorten day and month name, and remove excess commas. void SetDateFormat () { QLocale sysLocale = QLocale::system(); QString dfmt = sysLocale.dateFormat(); qDebug() << "system locale date format" << dfmt; QString s = dfmt.replace("dddd", "ddd"); if (!s.isEmpty()) s = s.replace("MMMM", "MMM"); if (!s.isEmpty()) s = s.replace(",", ""); if (!s.isEmpty()) { QDate testDate (2018, 12, 31); QString testresult = testDate.toString(s); if (!testresult.isEmpty()) // make sure we can translate a date MedDateFormat = s; // If we can, save the format for future use } // Record whether month or day is first in the formatting QString s2 = MedDateFormat; s2 = s2.replace("ddd",""); int monthidx = s2.indexOf("MMM"); if (s2.indexOf("d") < monthidx) dayFirst = true; qDebug() << "shortened date format" << MedDateFormat << "dayFirst" << dayFirst; } const QString getDeveloperName() { return STR_DeveloperName; } const QString getDeveloperDomain() { return STR_DeveloperDomain; } const QString getAppName() { QString name = STR_AppName; name += getPrereleaseSuffix(); return name; } const QString getModifiedAppData() { QString appdata = STR_AppData; appdata += getPrereleaseSuffix(); return appdata; } bool gfxEgnineIsSupported(GFXEngine e) { #if defined(Q_OS_WIN32) Q_UNUSED(e) return true; #else switch(e) { case GFX_OpenGL: case GFX_Software: return true; case GFX_ANGLE: default: return false; } #endif } GFXEngine currentGFXEngine() { QSettings settings; return (GFXEngine)qMin(settings.value(GFXEngineSetting, GFX_OpenGL).toUInt(), (unsigned int)MaxGFXEngine); } void setCurrentGFXEngine(GFXEngine e) { QSettings settings; settings.setValue(GFXEngineSetting, qMin((unsigned int)e, (unsigned int)MaxGFXEngine)); } QString getOpenGLVersionString() { static QString glversion; if (glversion.isEmpty()) { #ifdef BROKEN_OPENGL_BUILD glversion="LegacyGFX"; qDebug() << "This LegacyGFX build has been created without the need for OpenGL"; #else QGLWidget w; w.makeCurrent(); QOpenGLFunctions f; f.initializeOpenGLFunctions(); glversion = QString(QLatin1String(reinterpret_cast(f.glGetString(GL_VERSION)))); // qDebug() << "Graphics Engine:" << glversion; #endif } return glversion; } float getOpenGLVersion() { #ifdef BROKEN_OPENGL_BUILD return 0; #else QString glversion = getOpenGLVersionString(); glversion = glversion.section(" ",0,0); bool ok; float v = glversion.toFloat(&ok); if (!ok) { QString tmp = glversion.section(".",0,1); v = tmp.toFloat(&ok); if (!ok) { // just look at major, we are only interested in whether we have OpenGL 2.0 anyway tmp = glversion.section(".",0,0); v = tmp.toFloat(&ok); } } return v; #endif } // Obtains graphic engine as a string // This works on Windows. Don't know about other platforms. QString getGraphicsEngine() { QString gfxEngine = QString(); #ifdef BROKEN_OPENGL_BUILD gfxEngine = CSTR_GFX_BrokenGL; #else if (QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL)) gfxEngine = CSTR_GFX_BrokenGL; else if (QCoreApplication::testAttribute(Qt::AA_UseOpenGLES)) gfxEngine = CSTR_GFX_ANGLE; else gfxEngine = CSTR_GFX_OpenGL; #endif return gfxEngine; } QStringList buildInfo; QStringList makeBuildInfo (QString forcedEngine){ // application name and version has already been added buildInfo << (QObject::tr("Built with Qt %1 on %2").arg(QT_VERSION_STR).arg(getBuildDateTime())); buildInfo << QString(""); buildInfo << (QObject::tr("Operating system:") + " " + QSysInfo::prettyProductName()); buildInfo << (QObject::tr("Graphics Engine:") + " " + getOpenGLVersionString()); buildInfo << (QObject::tr("Graphics Engine type:") + " " + getGraphicsEngine()); if (forcedEngine != "") buildInfo << forcedEngine; buildInfo << QString(""); if (getAppName() != STR_AppName) // Report any non-standard app key buildInfo << (QObject::tr("App key:") + " " + getAppName()); // Data directory will always be added, later. return buildInfo; } QStringList addBuildInfo (QString value) { buildInfo << (value); return buildInfo; } QStringList getBuildInfo() { return buildInfo; } QString appResourcePath() { static QString path; if ( path.size() != 0 ) return path; #ifdef Q_OS_MAC path = QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../Resources"); #else // QStringList paths = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); // Check the Appiication Path first, so we can execute out of the build directory QStringList paths; // This one will be used if the Html and Translations folders // are in the same folder as the OSCAR executable paths.append( QCoreApplication::applicationDirPath() ); #ifdef Q_OS_LINUX QString appName = QCoreApplication::applicationName(); if (appName != QString("OSCAR")) appName = QString("OSCAR-test"); paths.append( QString( "/usr/share/" ) + appName ); paths.append( QString( "/usr/local/share/" ) + appName ); #endif for (auto p = begin(paths); p != end(paths); ++p ) { QString fname = *p+QString("/Translations/oscar_qt_fr.qm"); qDebug() << "Trying" << fname; QFileInfo f = QFileInfo(fname); if ( f.exists() ) { path = *p; break; } } if ( path.size() == 0 ) path = QCoreApplication::applicationDirPath(); #endif return path; } Qt::DayOfWeek firstDayOfWeekFromLocale() { return QLocale::system().firstDayOfWeek(); } int idealThreads() { return QThread::idealThreadCount(); } qint64 timezoneOffset() { static bool ok = false; static qint64 _TZ_offset = 0; if (ok) { return _TZ_offset; } QDateTime d1 = QDateTime::currentDateTime(); QDateTime d2 = d1; d1.setTimeSpec(Qt::UTC); _TZ_offset = d2.secsTo(d1); _TZ_offset *= 1000L; return _TZ_offset; } QString weightString(float kg, UnitSystem us) { if (us == US_Undefined) { us = p_profile->general->unitSystem(); } if (us == US_Metric) { return QString("%1kg").arg(kg, 0, 'f', 2); } else if (us == US_English) { int oz = (kg * 1000.0) / (float)ounce_convert; int lb = oz / 16.0; oz = oz % 16; return QString("%1lb %2oz").arg(lb, 0, 10).arg(oz); } return ("Bad UnitSystem"); } // Format perssure relief QString formatRelief (QString relief) { int icm = 0; QString newRelief = relief; if (relief == nullptr ) return relief; icm = relief.indexOf("cmH2O"); if (icm >= 1) { QChar t = relief.mid(icm-1,1)[0]; if (t.isDigit()) newRelief = relief.insert(icm, " "); } // qDebug() << "Relief input" << relief << "returning" << newRelief; return newRelief; } bool operator <(const ValueCount &a, const ValueCount &b) { return a.value < b.value; } static QStringList installedFontFamilies; // Validate all fonts void validateAllFonts () { validateFont("Application", 10, false, false); validateFont("Graph", 10, false, false); validateFont("Title", 12, true, false); validateFont("Big", 35, false, false); } // Validate font from preference settings, and substitute system font if font in preferences cannot be found on this system void validateFont (QString which, int size, bool bold, bool italic) { // Get list of installed font families, including system font // Do this just once so we don't have to call font functions repeatedly // (This list includes private fonts) QFontDatabase fontdatabase; if (installedFontFamilies.isEmpty()) { installedFontFamilies = fontdatabase.families(); } qDebug() << "validateFont found" << installedFontFamilies.count() << "installed font families"; QString prefPrefix = "Fonts_" + which + "_"; // start off assuming we don't have a font specified, and system font is desired font QString desiredFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont).family(); bool forceFont = true; // If a font is specified, make sure it is a valid font on this platform if (p_pref->contains(prefPrefix + "Name")) { // We already have a font, so it becomes desired font (if valid) QString testFont = (*p_pref)[prefPrefix + "Name"].toString(); int prefSize = (*p_pref)[prefPrefix+"Size"].toInt(); // Is this a good font? if (testFont.length() > 0 ) { qDebug() << which << "Preferences font is" << testFont; if ( installedFontFamilies.indexOf(testFont) >= 0) { desiredFont = testFont; forceFont = false; } else { qDebug() << testFont << prefSize << "not found, substituting" << desiredFont << size; for (int i = 0; i< installedFontFamilies.size(); i++) qDebug() << installedFontFamilies.at(i); } } } #ifdef Q_OS_MAC // Don't allow private font to be set for anything other than Application font (Mac restricts use to UI) if (which != "Application" && fontdatabase.isPrivateFamily(desiredFont)) { desiredFont = "Helvetica"; // We assume "Helvetica" is universally available on Mac forceFont = true; } #endif // Font not valid or not specified. Set a default font in its place if (forceFont) { (*p_pref)[prefPrefix + "Name"] = desiredFont; (*p_pref)[prefPrefix + "Size"] = size; (*p_pref)[prefPrefix + "Bold"] = bold; (*p_pref)[prefPrefix + "Italic"] = italic; } qDebug() << which << "font set to" << desiredFont << "at size" << (*p_pref)[prefPrefix + "Size"]; } void setApplicationFont () { qDebug() << "Application font starts out as" << QApplication::font(); QFont font = QFont(((*p_pref)["Fonts_Application_Name"]).toString()); font.setPointSize(((*p_pref)["Fonts_Application_Size"]).toInt()); font.setWeight(((*p_pref)["Fonts_Application_Bold"]).toBool() ? QFont::Bold : QFont::Normal); font.setItalic(((*p_pref)["Fonts_Application_Italic"]).toBool()); QApplication::setFont(font); mainwin->menuBar()->setFont(font); qDebug() << "Application font set to" << font; qDebug() << "Application font reads back as" << QApplication::font(); qDebug() << "system font is" << QFontDatabase::systemFont(QFontDatabase::GeneralFont).family(); } bool removeDir(const QString &path) { bool result = true; QDir dir(path); if (dir.exists(path)) { Q_FOREACH(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) { if (info.isDir()) { // Recurse to remove this child directory result = removeDir(info.absoluteFilePath()); } else { // File result = QFile::remove(info.absoluteFilePath()); } if (!result) { return result; } } result = dir.rmdir(path); } return result; } void copyPath(QString src, QString dst, bool overwrite) { QDir dir(src); if (!dir.exists()) return; // Recursively handle directories foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { QString dst_path = dst + QDir::separator() + d; dir.mkpath(dst_path); copyPath(src + QDir::separator() + d, dst_path, overwrite); } // Files foreach (QString f, dir.entryList(QDir::Files)) { QString srcFile = src + QDir::separator() + f; QString destFile = dst + QDir::separator() + f; if (overwrite && QFile::exists(destFile)) { QFile::remove(destFile); } if (!QFile::exists(destFile)) { if (!QFile::copy(srcFile, destFile)) { qWarning() << "copyPath: could not copy" << srcFile << "to" << destFile; } // TODO: Since copyPath is only used by loaders, it should // build the list of files first, and then update the progress bar // while copying. // TODO: copyPath should also either hide the abort button // or respond to it. QCoreApplication::processEvents(); } } } QString GFXEngineNames[MaxGFXEngine+1]; // Set by initializeStrings() QString STR_UNIT_M; QString STR_UNIT_CM; QString STR_UNIT_INCH; QString STR_UNIT_FOOT; QString STR_UNIT_POUND; QString STR_UNIT_OUNCE; QString STR_UNIT_KG; QString STR_UNIT_CMH2O; QString STR_UNIT_Hours; QString STR_UNIT_Minutes; QString STR_UNIT_Seconds; QString STR_UNIT_h; QString STR_UNIT_m; QString STR_UNIT_s; QString STR_UNIT_ms; QString STR_UNIT_BPM; // Beats per Minute QString STR_UNIT_LPM; // Litres per Minute QString STR_UNIT_ml; // MilliLitres QString STR_UNIT_Litres; QString STR_UNIT_Hz; QString STR_UNIT_EventsPerHour; QString STR_UNIT_BreathsPerMinute; QString STR_UNIT_Percentage; QString STR_UNIT_Unknown; QString STR_UNIT_Ratio; QString STR_UNIT_Severity; QString STR_UNIT_Degrees; QString STR_MessageBox_Question; QString STR_MessageBox_Error; QString STR_MessageBox_Warning; QString STR_MessageBox_Information; QString STR_MessageBox_Busy; QString STR_MessageBox_PleaseNote; QString STR_MessageBox_Yes; QString STR_MessageBox_No; QString STR_MessageBox_Cancel; QString STR_MessageBox_Destroy; QString STR_MessageBox_Save; QString STR_Empty_NoData; QString STR_Empty_Brick; QString STR_Empty_NoGraphs; QString STR_Empty_SummaryOnly; QString STR_Empty_NoSessions; QString STR_TR_BMI; // Short form of Body Mass Index QString STR_TR_Weight; QString STR_TR_Zombie; QString STR_TR_PulseRate; // Pulse / Heart rate QString STR_TR_SpO2; QString STR_TR_Plethy; // Plethysomogram QString STR_TR_Pressure; QString STR_TR_Daily; QString STR_TR_Profile; QString STR_TR_Overview; QString STR_TR_Oximetry; QString STR_TR_Oximeter; QString STR_TR_EventFlags; QString STR_TR_Inclination; QString STR_TR_Orientation; QString STR_TR_Motion; // Machine type names. QString STR_TR_CPAP; // Constant Positive Airway Pressure QString STR_TR_BIPAP; // Bi-Level Positive Airway Pressure QString STR_TR_BiLevel; // Another name for BiPAP QString STR_TR_EPAP; // Expiratory Positive Airway Pressure QString STR_TR_EPAPLo; // Expiratory Positive Airway Pressure, Low QString STR_TR_EPAPHi; // Expiratory Positive Airway Pressure, High QString STR_TR_IPAP; // Inspiratory Positive Airway Pressure QString STR_TR_IPAPLo; // Inspiratory Positive Airway Pressure, Low QString STR_TR_IPAPHi; // Inspiratory Positive Airway Pressure, High QString STR_TR_APAP; // Automatic Positive Airway Pressure QString STR_TR_ASV; // Assisted Servo Ventilator QString STR_TR_AVAPS; // Average Volume Assured Pressure Support QString STR_TR_STASV; QString STR_TR_Humidifier; QString STR_TR_H; // Short form of Hypopnea QString STR_TR_OA; // Short form of Obstructive Apnea QString STR_TR_A; // Short form of Apnea QString STR_TR_UA; // Short form of Unspecified Apnea QString STR_TR_CA; // Short form of Clear Airway Apnea QString STR_TR_FL; // Short form of Flow Limitation QString STR_TR_SA; // Short form of SensAwake QString STR_TR_LE; // Short form of Leak Event QString STR_TR_EP; // Short form of Expiratory Puff QString STR_TR_VS; // Short form of Vibratory Snore QString STR_TR_VS2; // Short form of Secondary Vibratory Snore (Some Philips Respironics Machines have two sources) QString STR_TR_RERA; // Acronym for Respiratory Effort Related Arousal QString STR_TR_PP; // Short form for Pressure Pulse QString STR_TR_P; // Short form for Pressure Event QString STR_TR_RE; // Short form of Respiratory Effort Related Arousal QString STR_TR_NR; // Short form of Non Responding event? (forgot sorry) QString STR_TR_NRI; // Sorry I Forgot.. it's a flag on Intellipap machines QString STR_TR_O2; // SpO2 Desaturation QString STR_TR_PC; // Short form for Pulse Change QString STR_TR_UF1; // Short form for User Flag 1 QString STR_TR_UF2; // Short form for User Flag 2 QString STR_TR_UF3; // Short form for User Flag 3 QString STR_TR_PS; // Short form of Pressure Support QString STR_TR_AHI; // Short form of Apnea Hypopnea Index QString STR_TR_RDI; // Short form of Respiratory Distress Index QString STR_TR_AI; // Short form of Apnea Index QString STR_TR_HI; // Short form of Hypopnea Index QString STR_TR_UAI; // Short form of Uncatagorized Apnea Index QString STR_TR_CAI; // Short form of Clear Airway Index QString STR_TR_FLI; // Short form of Flow Limitation Index //QString STR_TR_SAI; // Short form of SensAwake Index QString STR_TR_REI; // Short form of RERA Index QString STR_TR_EPI; // Short form of Expiratory Puff Index QString STR_TR_CSR; // Short form of Cheyne Stokes Respiration QString STR_TR_PB; // Short form of Periodic Breathing // Graph Titles QString STR_TR_IE; // Inspiratory Expiratory Ratio QString STR_TR_InspTime; // Inspiratory Time QString STR_TR_ExpTime; // Expiratory Time QString STR_TR_RespEvent; // Respiratory Event QString STR_TR_FlowLimitation; QString STR_TR_FlowLimit; //QString STR_TR_FlowLimitation; QString STR_TR_SensAwake; QString STR_TR_PatTrigBreath; // Patient Triggered Breath QString STR_TR_TgtMinVent; // Target Minute Ventilation QString STR_TR_TargetVent; // Target Ventilation QString STR_TR_MinuteVent; // Minute Ventilation QString STR_TR_TidalVolume; QString STR_TR_RespRate; // Respiratory Rate QString STR_TR_Snore; QString STR_TR_Leak; QString STR_TR_Leaks; QString STR_TR_LargeLeak; QString STR_TR_LL; QString STR_TR_TotalLeaks; QString STR_TR_UnintentionalLeaks; QString STR_TR_MaskPressure; QString STR_TR_FlowRate; QString STR_TR_SleepStage; QString STR_TR_Usage; QString STR_TR_Sessions; QString STR_TR_PrRelief; // Pressure Relief QString STR_TR_Bookmarks; QString STR_TR_OSCAR; //QString STR_TR_AppVersion; QString STR_TR_Default; QString STR_TR_Mode; QString STR_TR_Model; QString STR_TR_Brand; QString STR_TR_Serial; QString STR_TR_Series; QString STR_TR_Machine; QString STR_TR_Channel; QString STR_TR_Settings; QString STR_TR_Name; QString STR_TR_DOB; // Date of Birth QString STR_TR_Phone; QString STR_TR_Address; QString STR_TR_Email; QString STR_TR_PatientID; QString STR_TR_Date; QString STR_TR_BedTime; QString STR_TR_WakeUp; QString STR_TR_MaskTime; QString STR_TR_Unknown; QString STR_TR_None; QString STR_TR_Ready; QString STR_TR_First; QString STR_TR_Last; QString STR_TR_Start; QString STR_TR_End; QString STR_TR_On; QString STR_TR_Off; QString STR_TR_Auto; QString STR_TR_Yes; QString STR_TR_No; QString STR_TR_Min; // Minimum QString STR_TR_Max; // Maximum QString STR_TR_Med; // Median QString STR_TR_Average; QString STR_TR_Median; QString STR_TR_Avg; // Short form of Average QString STR_TR_WAvg; // Short form of Weighted Average void initializeStrings() { GFXEngineNames[GFX_Software] = QObject::tr("Software Engine"); GFXEngineNames[GFX_ANGLE] = QObject::tr("ANGLE / OpenGLES"); GFXEngineNames[GFX_OpenGL] = QObject::tr("Desktop OpenGL"); STR_UNIT_M = QObject::tr(" m"); STR_UNIT_CM = QObject::tr(" cm"); STR_UNIT_INCH = QObject::tr("in"); STR_UNIT_FOOT = QObject::tr("ft"); STR_UNIT_POUND = QObject::tr("lb"); STR_UNIT_OUNCE = QObject::tr("oz"); STR_UNIT_KG = QObject::tr("Kg"); STR_UNIT_CMH2O = QObject::tr("cmH2O"); STR_UNIT_Hours = QObject::tr("Hours"); STR_UNIT_Minutes = QObject::tr("Minutes"); STR_UNIT_Seconds = QObject::tr("Seconds"); STR_UNIT_h = QObject::tr("h"); // hours shortform STR_UNIT_m = QObject::tr("m"); // minutes shortform STR_UNIT_s = QObject::tr("s"); // seconds shortform STR_UNIT_ms = QObject::tr("ms"); // milliseconds STR_UNIT_EventsPerHour = QObject::tr("Events/hr"); // Events per hour STR_UNIT_Percentage = QString("%"); STR_UNIT_Hz = QObject::tr("Hz"); // Hertz STR_UNIT_BPM = QObject::tr("bpm"); // Beats per Minute STR_UNIT_LPM = QObject::tr("L/min"); // Litres per Minute STR_UNIT_Litres = QObject::tr("Litres"); STR_UNIT_ml = QObject::tr("ml"); // millilitres STR_UNIT_BreathsPerMinute = QObject::tr("Breaths/min"); // Breaths per minute STR_UNIT_Unknown = QObject::tr("?"); STR_UNIT_Ratio = QObject::tr("ratio"); STR_UNIT_Severity = QObject::tr("Severity (0-1)"); STR_UNIT_Degrees = QObject::tr("Degrees"); STR_MessageBox_Question = QObject::tr("Question"); STR_MessageBox_Error = QObject::tr("Error"); STR_MessageBox_Warning = QObject::tr("Warning"); STR_MessageBox_Information = QObject::tr("Information"); STR_MessageBox_Busy = QObject::tr("Busy"); STR_MessageBox_PleaseNote = QObject::tr("Please Note"); STR_Empty_NoData = QObject::tr("No Data Available"); STR_Empty_Brick = QObject::tr("Only Settings and Compliance Data Available"); STR_Empty_NoGraphs = QObject::tr("Graphs Switched Off"); STR_Empty_SummaryOnly = QObject::tr("Summary Data Only"); STR_Empty_NoSessions = QObject::tr("Sessions Switched Off"); // Dialog box options STR_MessageBox_Yes = QObject::tr("&Yes"); STR_MessageBox_No = QObject::tr("&No"); STR_MessageBox_Cancel = QObject::tr("&Cancel"); STR_MessageBox_Destroy = QObject::tr("&Destroy");; STR_MessageBox_Save = QObject::tr("&Save"); STR_TR_BMI = QObject::tr("BMI"); // Short form of Body Mass Index STR_TR_Weight = QObject::tr("Weight"); STR_TR_Zombie = QObject::tr("Zombie"); STR_TR_PulseRate = QObject::tr("Pulse Rate"); // Pulse / Heart rate STR_TR_SpO2 = QString("SpO2"); STR_TR_Plethy = QObject::tr("Plethy"); // Plethysomogram STR_TR_Pressure = QObject::tr("Pressure"); STR_TR_Daily = QObject::tr("Daily"); STR_TR_Profile = QObject::tr("Profile"); STR_TR_Overview = QObject::tr("Overview"); STR_TR_Oximetry = QObject::tr("Oximetry"); STR_TR_Oximeter = QObject::tr("Oximeter"); STR_TR_EventFlags = QObject::tr("Event Flags"); STR_TR_Default = QObject::tr("Default"); // Machine type names. STR_TR_CPAP = QObject::tr("CPAP"); // Constant Positive Airway Pressure STR_TR_BIPAP = QObject::tr("BiPAP"); // Bi-Level Positive Airway Pressure STR_TR_BiLevel = QObject::tr("Bi-Level"); // Another name for BiPAP STR_TR_EPAP = QObject::tr("EPAP"); // Expiratory Positive Airway Pressure STR_TR_EPAPLo = QObject::tr("Min EPAP"); // Lower Expiratory Positive Airway Pressure STR_TR_EPAPHi = QObject::tr("Max EPAP"); // Higher Expiratory Positive Airway Pressure STR_TR_IPAP = QObject::tr("IPAP"); // Inspiratory Positive Airway Pressure STR_TR_IPAPLo = QObject::tr("Min IPAP"); // Lower Inspiratory Positive Airway Pressure STR_TR_IPAPHi = QObject::tr("Max IPAP"); // Higher Inspiratory Positive Airway Pressure STR_TR_APAP = QObject::tr("APAP"); // Automatic Positive Airway Pressure STR_TR_ASV = QObject::tr("ASV"); // Assisted Servo Ventilator STR_TR_AVAPS = QObject::tr("AVAPS"); // Average Volume Assured Pressure Support STR_TR_STASV = QObject::tr("ST/ASV"); STR_TR_Humidifier = QObject::tr("Humidifier"); STR_TR_H = QObject::tr("H"); // Short form of Hypopnea STR_TR_OA = QObject::tr("OA"); // Short form of Obstructive Apnea STR_TR_A = QObject::tr("A"); // Short form of All Apnea STR_TR_UA = QObject::tr("UA"); // Short form of Unspecified Apnea STR_TR_CA = QObject::tr("CA"); // Short form of Clear Airway Apnea STR_TR_FL = QObject::tr("FL"); // Short form of Flow Limitation STR_TR_SA = QObject::tr("SA"); // Short form of Flow Limitation STR_TR_LE = QObject::tr("LE"); // Short form of Leak Event STR_TR_EP = QObject::tr("EP"); // Short form of Expiratory Puff STR_TR_VS = QObject::tr("VS"); // Short form of Vibratory Snore STR_TR_VS2 = QObject::tr("VS2"); // Short form of Secondary Vibratory Snore (Some Philips Respironics Machines have two sources) STR_TR_RERA = QObject::tr("RERA"); // Acronym for Respiratory Effort Related Arousal STR_TR_PP = QObject::tr("PP"); // Short form for Pressure Pulse STR_TR_P = QObject::tr("P"); // Short form for Pressure Event STR_TR_RE = QObject::tr("RE"); // Short form of Respiratory Effort Related Arousal STR_TR_NR = QObject::tr("NR"); // Short form of Non Responding event? (forgot sorry) STR_TR_NRI = QObject::tr("NRI"); // Sorry I Forgot.. it's a flag on Intellipap machines STR_TR_O2 = QObject::tr("O2"); // SpO2 Desaturation STR_TR_PC = QObject::tr("PC"); // Short form for Pulse Change STR_TR_UF1 = QObject::tr("UF1"); // Short form for User Flag 1 STR_TR_UF2 = QObject::tr("UF2"); // Short form for User Flag 2 STR_TR_UF3 = QObject::tr("UF3"); // Short form for User Flag 3 STR_TR_PS = QObject::tr("PS"); // Short form of Pressure Support STR_TR_AHI = QObject::tr("AHI"); // Short form of Apnea Hypopnea Index STR_TR_RDI = QObject::tr("RDI"); // Short form of Respiratory Distress Index STR_TR_AI = QObject::tr("AI"); // Short form of Apnea Index STR_TR_HI = QObject::tr("HI"); // Short form of Hypopnea Index STR_TR_UAI = QObject::tr("UAI"); // Short form of Uncatagorized Apnea Index STR_TR_CAI = QObject::tr("CAI"); // Short form of Clear Airway Index STR_TR_FLI = QObject::tr("FLI"); // Short form of Flow Limitation Index // STR_TR_SAI = QObject::tr("SAI"); // Short form of SleepAwake Index STR_TR_REI = QObject::tr("REI"); // Short form of RERA Index STR_TR_EPI = QObject::tr("EPI"); // Short form of Expiratory Puff Index STR_TR_CSR = QObject::tr("CSR"); // Short form of Cheyne Stokes Respiration STR_TR_PB = QObject::tr("PB"); // Short form of Periodic Breathing // Graph Titles STR_TR_IE = QObject::tr("IE"); // Inspiratory Expiratory Ratio STR_TR_InspTime = QObject::tr("Insp. Time"); // Inspiratory Time STR_TR_ExpTime = QObject::tr("Exp. Time"); // Expiratory Time STR_TR_RespEvent = QObject::tr("Resp. Event"); // Respiratory Event STR_TR_FlowLimitation = QObject::tr("Flow Limitation"); STR_TR_FlowLimit = QObject::tr("Flow Limit"); STR_TR_SensAwake = QObject::tr("SensAwake"); STR_TR_PatTrigBreath = QObject::tr("Pat. Trig. Breath"); // Patient Triggered Breath STR_TR_TgtMinVent = QObject::tr("Tgt. Min. Vent"); // Target Minute Ventilation STR_TR_TargetVent = QObject::tr("Target Vent."); // Target Ventilation STR_TR_MinuteVent = QObject::tr("Minute Vent."); // Minute Ventilation STR_TR_TidalVolume = QObject::tr("Tidal Volume"); STR_TR_RespRate = QObject::tr("Resp. Rate"); // Respiratory Rate STR_TR_Snore = QObject::tr("Snore"); STR_TR_Leak = QObject::tr("Leak"); STR_TR_Leaks = QObject::tr("Leaks"); STR_TR_LargeLeak = QObject::tr("Large Leak"); STR_TR_LL = QObject::tr("LL"); // Large Leak STR_TR_TotalLeaks = QObject::tr("Total Leaks"); STR_TR_UnintentionalLeaks = QObject::tr("Unintentional Leaks"); STR_TR_MaskPressure = QObject::tr("MaskPressure"); STR_TR_FlowRate = QObject::tr("Flow Rate"); STR_TR_SleepStage = QObject::tr("Sleep Stage"); STR_TR_Usage = QObject::tr("Usage"); STR_TR_Sessions = QObject::tr("Sessions"); STR_TR_PrRelief = QObject::tr("Pr. Relief"); // Pressure Relief STR_TR_Bookmarks = QObject::tr("Bookmarks"); STR_TR_OSCAR = QString("OSCAR"); //STR_TR_AppVersion = QObject::tr("v%1").arg(getVersion()); STR_TR_Mode = QObject::tr("Mode"); STR_TR_Model = QObject::tr("Model"); STR_TR_Brand = QObject::tr("Brand"); STR_TR_Serial = QObject::tr("Serial"); STR_TR_Series = QObject::tr("Series"); STR_TR_Machine = QObject::tr("Machine"); STR_TR_Channel = QObject::tr("Channel"); STR_TR_Settings = QObject::tr("Settings"); STR_TR_Inclination = QObject::tr("Inclination"); STR_TR_Orientation = QObject::tr("Orientation"); STR_TR_Motion = QObject::tr("Motion"); STR_TR_Name = QObject::tr("Name"); STR_TR_DOB = QObject::tr("DOB"); // Date of Birth STR_TR_Phone = QObject::tr("Phone"); STR_TR_Address = QObject::tr("Address"); STR_TR_Email = QObject::tr("Email"); STR_TR_PatientID = QObject::tr("Patient ID"); STR_TR_Date = QObject::tr("Date"); STR_TR_BedTime = QObject::tr("Bedtime"); STR_TR_WakeUp = QObject::tr("Wake-up"); STR_TR_MaskTime = QObject::tr("Mask Time"); STR_TR_Unknown = QObject::tr("Unknown"); STR_TR_None = QObject::tr("None"); STR_TR_Ready = QObject::tr("Ready"); STR_TR_First = QObject::tr("First"); STR_TR_Last = QObject::tr("Last"); STR_TR_Start = QObject::tr("Start"); STR_TR_End = QObject::tr("End"); STR_TR_On = QObject::tr("On"); STR_TR_Off = QObject::tr("Off"); STR_TR_Auto = QObject::tr("Auto"); STR_TR_Yes = QObject::tr("Yes"); STR_TR_No = QObject::tr("No"); STR_TR_Min = QObject::tr("Min"); // Minimum STR_TR_Max = QObject::tr("Max"); // Maximum STR_TR_Med = QObject::tr("Med"); // Median STR_TR_Average = QObject::tr("Average"); STR_TR_Median = QObject::tr("Median"); STR_TR_Avg = QObject::tr("Avg"); // Average STR_TR_WAvg = QObject::tr("W-Avg"); // Weighted Average } quint32 CRC32(const char * data, quint32 length) { quint32 crc32 = 0xffffffff; for (quint32 idx=0; idx 0; j--) { if (i & 1) { i = (i >> 1) ^ 0xedb88320; } else { i >>= 1; } } crc32 = ((crc32) >> 8) ^ i; } return ~crc32; } quint32 crc32buf(const QByteArray& data) { return CRC32(data.constData(), data.size()); } // Gzip function QByteArray gCompress(const QByteArray& data) { QByteArray compressedData = qCompress(data); // Strip the first six bytes (a 4-byte length put on by qCompress and a 2-byte zlib header) // and the last four bytes (a zlib integrity check). compressedData.remove(0, 6); compressedData.chop(4); QByteArray header; QDataStream ds1(&header, QIODevice::WriteOnly); // Prepend a generic 10-byte gzip header (see RFC 1952), ds1 << quint16(0x1f8b) << quint16(0x0800) << quint16(0x0000) << quint16(0x0000) << quint16(0x000b); // Append a four-byte CRC-32 of the uncompressed data // Append 4 bytes uncompressed input size modulo 2^32 QByteArray footer; QDataStream ds2(&footer, QIODevice::WriteOnly); ds2.setByteOrder(QDataStream::LittleEndian); ds2 << crc32buf(data) << quint32(data.size()); return header + compressedData + footer; } // Pinched from http://stackoverflow.com/questions/2690328/qt-quncompress-gzip-data QByteArray gUncompress(const QByteArray & data) { if (data.size() <= 4) { qWarning("gUncompress: Input data is truncated"); return QByteArray(); } QByteArray result; int ret; z_stream strm; static const int CHUNK_SIZE = 1048576; char *out = new char [CHUNK_SIZE]; /* allocate inflate state */ strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = data.size(); strm.next_in = (Bytef*)(data.data()); ret = inflateInit2(&strm, 15 + 32); // gzip decoding if (ret != Z_OK) { delete [] out; return QByteArray(); } // run inflate() do { strm.avail_out = CHUNK_SIZE; strm.next_out = (Bytef*)(out); ret = inflate(&strm, Z_NO_FLUSH); if (ret == Z_STREAM_ERROR) { qWarning() << "ret == Z_STREAM_ERROR in gzUncompress in common.cpp"; delete [] out; return QByteArray(); } switch (ret) { case Z_NEED_DICT: ret = Z_DATA_ERROR; // fall through case Z_DATA_ERROR: case Z_MEM_ERROR: Q_UNUSED(ret) (void)inflateEnd(&strm); delete [] out; return QByteArray(); } result.append(out, CHUNK_SIZE - strm.avail_out); } while (strm.avail_out == 0); // clean up and return inflateEnd(&strm); delete [] out; return result; } OSCAR-code-v1.3.1/oscar/SleepLib/common.h000066400000000000000000000321341417327530600177430ustar00rootroot00000000000000/* Common code and junk * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef COMMON_H #define COMMON_H #include #include #include #include // #define DEBUG_EFFICIENCY 1 // Developers can define this for qmake if they want it #include #include "Graphs/glcommon.h" enum GFXEngine { GFX_OpenGL=0, GFX_ANGLE, GFX_Software, MaxGFXEngine=GFX_Software}; const QString GFXEngineSetting = "GFXEngine"; extern QString GFXEngineNames[MaxGFXEngine+1]; // Set by initializeStrings() const QString CSTR_GFX_ANGLE = "ANGLE"; const QString CSTR_GFX_OpenGL = "OpenGL"; const QString CSTR_GFX_BrokenGL = "LegacyGFX"; extern QString MedDateFormat; extern bool dayFirst; //! \brief Gets the first day of week from the system locale, to show in the calendars. Qt::DayOfWeek firstDayOfWeekFromLocale(); QString getGFXEngine(); bool gfxEgnineIsSupported(GFXEngine e); GFXEngine currentGFXEngine(); void setCurrentGFXEngine(GFXEngine e); QString appResourcePath(); QString getGraphicsEngine(); QString getOpenGLVersionString(); float getOpenGLVersion(); QStringList makeBuildInfo(QString forcedEngine); QStringList getBuildInfo(); QStringList addBuildInfo (QString value); void SetDateFormat (); QByteArray gCompress(const QByteArray& data); QByteArray gUncompress(const QByteArray &data); const quint16 filetype_summary = 0; const quint16 filetype_data = 1; const quint16 filetype_sessenabled = 5; enum UnitSystem { US_Undefined, US_Metric, US_English }; typedef float EventDataType; struct ValueCount { ValueCount() { value = 0; count = 0; p = 0; } ValueCount( EventDataType val, qint64 cnt, double pp) :value(val), count(cnt), p(pp) {} ValueCount(const ValueCount ©) = default; EventDataType value; qint64 count; double p; }; extern int idealThreads(); void copyPath(QString src, QString dst, bool overwrite=false); // Primarily sort by value bool operator <(const ValueCount &a, const ValueCount &b); const float ounce_convert = 28.3495231F; // grams const float pound_convert = ounce_convert * 16; QString weightString(float kg, UnitSystem us = US_Undefined); //! \brief Format pressure relief by placing a space before cmH2O if not already there QString formatRelief (QString relief); //! \brief Mercilessly trash a directory bool removeDir(const QString &path); ///Represents the exception for taking the median of an empty list class median_of_empty_list_exception:public std::exception{ virtual const char* what() const throw() { return "Attempt to take the median of an empty list of numbers. " "The median of an empty list is undefined."; } }; ///Return the median of a sequence of numbers defined by the random ///access iterators begin and end. The sequence must not be empty ///(median is undefined for an empty set). /// ///The numbers must be convertible to double. template float median(RandAccessIter begin, RandAccessIter end) // throw (median_of_empty_list_exception) { if (begin == end) { throw median_of_empty_list_exception(); } int size = end - begin; int middleIdx = size/2; RandAccessIter target = begin + middleIdx; std::nth_element(begin, target, end); if (size % 2 != 0) { //Odd number of elements return *target; } else { //Even number of elements double a = *target; RandAccessIter targetNeighbor= target-1; std::nth_element(begin, targetNeighbor, end); return (a+*targetNeighbor)/2.0; } } const QString getAppName(); const QString getDeveloperName(); const QString getDeveloperDomain(); const QString getModifiedAppData(); void validateAllFonts (); void validateFont (QString which, int size, bool bold, bool italic); void setApplicationFont (); void initializeStrings(); enum OverlayDisplayType { ODT_Bars, ODT_TopAndBottom }; /////////////////////////////////////////////////////////////////////////////////////////////// // Preference Name Strings /////////////////////////////////////////////////////////////////////////////////////////////// const QString STR_GEN_Profile = "Profile"; const QString STR_GEN_SkipLogin = "SkipLoginScreen"; const QString STR_GEN_DataFolder = "DataFolder"; const QString STR_PREF_ReimportBackup = "ReimportBackup"; const QString STR_PREF_LastCPAPPath = "LastCPAPPath"; const QString STR_PREF_LastOximetryPath = "LastOximetryPath"; const QString STR_MACH_ResMed = "ResMed"; const QString STR_MACH_PRS1 = "PRS1"; const QString STR_MACH_Journal = "Journal"; const QString STR_MACH_Intellipap = "Intellipap"; const QString STR_MACH_Weinmann= "Weinmann"; const QString STR_MACH_FPIcon = "FPIcon"; const QString STR_MACH_SleepStyle = "SleepStyle"; const QString STR_MACH_MSeries = "MSeries"; const QString STR_MACH_CMS50 = "CMS50"; const QString STR_MACH_ZEO = "Zeo"; const QString STR_PREF_Language = "Language"; const QString STR_AppName = "OSCAR"; const QString STR_DeveloperName = "OSCAR_Team"; const QString STR_DeveloperDomain = "oscar-team.org"; const QString STR_AppData = "OSCAR_Data"; /////////////////////////////////////////////////////////////////////////////////////////////// // Commonly used translatable text strings /////////////////////////////////////////////////////////////////////////////////////////////// extern QString STR_UNIT_M; extern QString STR_UNIT_CM; extern QString STR_UNIT_INCH; extern QString STR_UNIT_FOOT; extern QString STR_UNIT_POUND; extern QString STR_UNIT_OUNCE; extern QString STR_UNIT_KG; extern QString STR_UNIT_CMH2O; extern QString STR_UNIT_Hours; extern QString STR_UNIT_Minutes; extern QString STR_UNIT_Seconds; extern QString STR_UNIT_h; // (h)ours, (m)inutes, (s)econds extern QString STR_UNIT_m; extern QString STR_UNIT_s; extern QString STR_UNIT_ms; extern QString STR_UNIT_BPM; // Beats per Minute extern QString STR_UNIT_LPM; // Litres per Minute extern QString STR_UNIT_ml; // millilitres extern QString STR_UNIT_Litres; extern QString STR_UNIT_Hz; extern QString STR_UNIT_EventsPerHour; extern QString STR_UNIT_Percentage; extern QString STR_UNIT_BreathsPerMinute; extern QString STR_UNIT_Unknown; extern QString STR_UNIT_Ratio; extern QString STR_UNIT_Severity; extern QString STR_UNIT_Degrees; extern QString STR_MessageBox_Question; extern QString STR_MessageBox_Information; extern QString STR_MessageBox_Error; extern QString STR_MessageBox_Warning; extern QString STR_MessageBox_Busy; extern QString STR_MessageBox_PleaseNote; extern QString STR_MessageBox_Yes; extern QString STR_MessageBox_No; extern QString STR_MessageBox_Cancel; extern QString STR_MessageBox_Destroy; extern QString STR_MessageBox_Save; extern QString STR_Empty_NoData; extern QString STR_Empty_NoSessions; extern QString STR_Empty_Brick; extern QString STR_Empty_NoGraphs; extern QString STR_Empty_SummaryOnly; extern QString STR_TR_Default; extern QString STR_TR_BMI; // Short form of Body Mass Index extern QString STR_TR_Weight; extern QString STR_TR_Zombie; extern QString STR_TR_PulseRate; // Pulse / Heart rate extern QString STR_TR_SpO2; extern QString STR_TR_Plethy; // Plethysomogram extern QString STR_TR_Pressure; extern QString STR_TR_Daily; extern QString STR_TR_Profile; extern QString STR_TR_Overview; extern QString STR_TR_Oximetry; extern QString STR_TR_Oximeter; extern QString STR_TR_EventFlags; extern QString STR_TR_Inclination; extern QString STR_TR_Orientation; extern QString STR_TR_Motion; // Machine type names. extern QString STR_TR_CPAP; // Constant Positive Airway Pressure extern QString STR_TR_BIPAP; // Bi-Level Positive Airway Pressure extern QString STR_TR_BiLevel; // Another name for BiPAP extern QString STR_TR_EPAP; // Expiratory Positive Airway Pressure extern QString STR_TR_EPAPLo; // Expiratory Positive Airway Pressure, Low extern QString STR_TR_EPAPHi; // Expiratory Positive Airway Pressure, High extern QString STR_TR_IPAP; // Inspiratory Positive Airway Pressure extern QString STR_TR_IPAPLo; // Inspiratory Positive Airway Pressure, Low extern QString STR_TR_IPAPHi; // Inspiratory Positive Airway Pressure, High extern QString STR_TR_APAP; // Automatic Positive Airway Pressure extern QString STR_TR_ASV; // Assisted Servo Ventilator extern QString STR_TR_AVAPS; // Average Volume Assured Pressure Support extern QString STR_TR_STASV; extern QString STR_TR_Humidifier; extern QString STR_TR_H; // Short form of Hypopnea extern QString STR_TR_OA; // Short form of Obstructive Apnea extern QString STR_TR_A; // Short form of Apnea extern QString STR_TR_UA; // Short form of Unspecified Apnea extern QString STR_TR_CA; // Short form of Clear Airway Apnea extern QString STR_TR_FL; // Short form of Flow Limitation extern QString STR_TR_SA; // Short form of SensAwake extern QString STR_TR_LE; // Short form of Leak Event extern QString STR_TR_EP; // Short form of Expiratory Puff extern QString STR_TR_VS; // Short form of Vibratory Snore extern QString STR_TR_VS2; // Short form of Secondary Vibratory Snore (Some Philips Respironics Machines have two sources) extern QString STR_TR_RERA; // Acronym for Respiratory Effort Related Arousal extern QString STR_TR_PP; // Short form for Pressure Pulse extern QString STR_TR_P; // Short form for Pressure Event extern QString STR_TR_RE; // Short form of Respiratory Effort Related Arousal extern QString STR_TR_NR; // Short form of Non Responding event? (forgot sorry) extern QString STR_TR_NRI; // Sorry I Forgot.. it's a flag on Intellipap machines extern QString STR_TR_O2; // SpO2 Desaturation extern QString STR_TR_PC; // Short form for Pulse Change extern QString STR_TR_UF1; // Short form for User Flag 1 extern QString STR_TR_UF2; // Short form for User Flag 2 extern QString STR_TR_UF3; // Short form for User Flag 3 extern QString STR_TR_PS; // Short form of Pressure Support extern QString STR_TR_AHI; // Short form of Apnea Hypopnea Index extern QString STR_TR_RDI; // Short form of Respiratory Distress Index extern QString STR_TR_AI; // Short form of Apnea Index extern QString STR_TR_HI; // Short form of Hypopnea Index extern QString STR_TR_UAI; // Short form of Uncatagorized Apnea Index extern QString STR_TR_CAI; // Short form of Clear Airway Index extern QString STR_TR_FLI; // Short form of Flow Limitation Index extern QString STR_TR_REI; // Short form of RERA Index extern QString STR_TR_EPI; // Short form of Expiratory Puff Index extern QString STR_TR_CSR; // Short form of Cheyne Stokes Respiration extern QString STR_TR_PB; // Short form of Periodic Breathing // Graph Titles extern QString STR_TR_IE; // Inspiratory Expiratory Ratio extern QString STR_TR_InspTime; // Inspiratory Time extern QString STR_TR_ExpTime; // Expiratory Time extern QString STR_TR_RespEvent; // Respiratory Event extern QString STR_TR_FlowLimitation; extern QString STR_TR_FlowLimit; extern QString STR_TR_PatTrigBreath; // Patient Triggered Breath extern QString STR_TR_TgtMinVent; // Target Minute Ventilation extern QString STR_TR_TargetVent; // Target Ventilation extern QString STR_TR_MinuteVent; // Minute Ventilation extern QString STR_TR_TidalVolume; extern QString STR_TR_RespRate; // Respiratory Rate extern QString STR_TR_Snore; extern QString STR_TR_Leak; extern QString STR_TR_LargeLeak; extern QString STR_TR_LL; extern QString STR_TR_Leaks; extern QString STR_TR_TotalLeaks; extern QString STR_TR_UnintentionalLeaks; extern QString STR_TR_MaskPressure; extern QString STR_TR_FlowRate; extern QString STR_TR_SleepStage; extern QString STR_TR_Usage; extern QString STR_TR_Sessions; extern QString STR_TR_PrRelief; // Pressure Relief extern QString STR_TR_SensAwake; extern QString STR_TR_Bookmarks; extern QString STR_TR_OSCAR; extern QString STR_TR_AppVersion; extern QString STR_TR_Mode; extern QString STR_TR_Model; extern QString STR_TR_Brand; extern QString STR_TR_Series; extern QString STR_TR_Serial; extern QString STR_TR_Machine; extern QString STR_TR_Channel; extern QString STR_TR_Settings; extern QString STR_TR_Name; extern QString STR_TR_DOB; // Date of Birth extern QString STR_TR_Phone; extern QString STR_TR_Address; extern QString STR_TR_Email; extern QString STR_TR_PatientID; extern QString STR_TR_Date; extern QString STR_TR_BedTime; extern QString STR_TR_WakeUp; extern QString STR_TR_MaskTime; extern QString STR_TR_Unknown; extern QString STR_TR_None; extern QString STR_TR_Ready; extern QString STR_TR_First; extern QString STR_TR_Last; extern QString STR_TR_Start; extern QString STR_TR_End; extern QString STR_TR_On; extern QString STR_TR_Off; extern QString STR_TR_Auto; extern QString STR_TR_Yes; extern QString STR_TR_No; extern QString STR_TR_Min; // Minimum extern QString STR_TR_Max; // Maximum extern QString STR_TR_Med; // Median extern QString STR_TR_Average; extern QString STR_TR_Median; extern QString STR_TR_Avg; // Short form of Average extern QString STR_TR_WAvg; // Short form of Weighted Average #endif // COMMON_H OSCAR-code-v1.3.1/oscar/SleepLib/day.cpp000066400000000000000000001310201417327530600175550ustar00rootroot00000000000000/* SleepLib Day Class Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include "day.h" #include "profiles.h" Day::Day() { d_firstsession = true; d_summaries_open = false; d_events_open = false; d_invalidate = true; } Day::~Day() { for (auto & sess : sessions) { delete sess; } } void Day::updateCPAPCache() { d_count.clear(); d_sum.clear(); OpenSummary(); QList channels = getSortedMachineChannels(MT_CPAP, schema::FLAG | schema::MINOR_FLAG | schema::SPAN); for (const auto code : channels) { d_count[code] = count(code); d_sum[code] = count(code); d_machhours[MT_CPAP] = hours(MT_CPAP); } } Session * Day::firstSession(MachineType type) { for (auto & sess : sessions) { if (!sess->enabled()) continue; if (sess->type() == type) { return sess; } } return nullptr; } bool Day::addMachine(Machine *mach) { invalidate(); if (!machines.contains(mach->type())) { machines[mach->type()] = mach; return true; } return false; } Machine *Day::machine(MachineType type) { auto it = machines.find(type); if (it != machines.end()) return it.value(); return nullptr; } QList Day::getSessions(MachineType type, bool ignore_enabled) { QList newlist; for (auto & sess : sessions) { if (!ignore_enabled && !sess->enabled()) continue; if (sess->type() == type) newlist.append(sess); } return newlist; } Session *Day::find(SessionID sessid) { for (auto & sess : sessions) { if (sess->session() == sessid) { return sess; } } return nullptr; } Session *Day::find(SessionID sessid, MachineType mt) { for (auto & sess : sessions) { if ((sess->session() == sessid) && (sess->s_machtype == mt)) { return sess; } } return nullptr; } void Day::addSession(Session *s) { if (s == nullptr) { qDebug() << "addSession called with null session pointer"; return; } invalidate(); auto mi = machines.find(s->type()); if (mi != machines.end()) { if (mi.value() != s->machine()) { qDebug() << "OSCAR can't add session" << s->session() << "["+QDateTime::fromTime_t(s->session()).toString("MMM dd, yyyy hh:mm:ss")+"]" << "from machine" << mi.value()->serial() << "to machine" << s->machine()->serial() << "to this day record, as it already contains a different machine of the same MachineType" << s->type(); return; } } else { machines[s->type()] = s->machine(); } if (s->first() == 0) { qWarning() << "Day::addSession discarding session" << s->session() << "from machine" << s->machine()->serial() << "with first=0"; return; } for (auto & sess : sessions) { if (sess->session() == s->session() && sess->type() == s->type()) { // This usually indicates a problem in purging or cleanup somewhere, // unless there's a problem with a parser. qCritical() << "Day object" << this->date().toString() << "adding duplicate session" << s->session() << "["+QDateTime::fromTime_t(s->session()).toString("MMM dd, yyyy hh:mm:ss")+"]"; // Don't skip this one, since it might have replaced the original elsewhere already. //return; } } sessions.push_back(s); } EventDataType Day::calcMiddle(ChannelID code) { int c = p_profile->general->prefCalcMiddle(); if (c == 0) { return percentile(code, 0.5); // Median } else if (c == 1 ) { return wavg(code); // Weighted Average } else { return avg(code); // Average } } EventDataType Day::calcMax(ChannelID code) { return p_profile->general->prefCalcMax() ? percentile(code, 0.995f) : Max(code); } EventDataType Day::calcPercentile(ChannelID code) { double p = p_profile->general->prefCalcPercentile() / 100.0; return percentile(code, p); } QString Day::calcMiddleLabel(ChannelID code) { int c = p_profile->general->prefCalcMiddle(); if (c == 0) { return QString("%1 %2").arg(STR_TR_Median).arg(schema::channel[code].label()); } else if (c == 1) { return QString("%1 %2").arg(STR_TR_WAvg).arg(schema::channel[code].label()); } else { return QString("%1 %2").arg(STR_TR_Avg).arg(schema::channel[code].label()); } } QString Day::calcMaxLabel(ChannelID code) { return QString("%1 %2").arg(p_profile->general->prefCalcMax() ? QObject::tr("99.5%") : STR_TR_Max).arg(schema::channel[code].label()); } QString Day::calcPercentileLabel(ChannelID code) { return QString("%1% %2").arg(p_profile->general->prefCalcPercentile(),0, 'f',0).arg(schema::channel[code].label()); } EventDataType Day::countInsideSpan(ChannelID span, ChannelID code) { int count = 0; for (auto & sess : sessions) { if (sess->enabled()) { count += sess->countInsideSpan(span, code); } } return count; } EventDataType Day::lookupValue(ChannelID code, qint64 time, bool square) { for (auto & sess : sessions) { if (sess->enabled()) { // drift is handled by session first/last/SearchValue if ((time > sess->first()) && (time < sess->last())) { if (sess->channelExists(code)) { return sess->SearchValue(code,time,square); } } } } return 0; } EventDataType Day::timeAboveThreshold(ChannelID code, EventDataType threshold) { EventDataType val = 0; for (auto & sess : sessions) { if (sess->enabled() && sess->m_availableChannels.contains(code)) { val += sess->timeAboveThreshold(code,threshold); } } return val; } EventDataType Day::timeBelowThreshold(ChannelID code, EventDataType threshold) { EventDataType val = 0; for (auto & sess : sessions) { if (sess->enabled()) { val += sess->timeBelowThreshold(code,threshold); } } return val; } EventDataType Day::settings_sum(ChannelID code) { EventDataType val = 0; for (auto & sess : sessions) { if (sess->enabled()) { auto set = sess->settings.find(code); if (set != sess->settings.end()) { val += set.value().toDouble(); } } } return val; } EventDataType Day::settings_max(ChannelID code) { EventDataType min = -std::numeric_limits::max(); EventDataType max = min; EventDataType value; for (auto & sess : sessions) { if (sess->enabled() && sess->s_machtype != MT_JOURNAL) { value = sess->settings.value(code, min).toFloat(); if (value > max) { max = value; } } } return max; } EventDataType Day::settings_min(ChannelID code) { EventDataType max = std::numeric_limits::max(); EventDataType min = max; EventDataType value; for (auto & sess : sessions) { if (sess->enabled() && sess->s_machtype != MT_JOURNAL) { value = sess->settings.value(code, max).toFloat(); if (value < min) { min = value; } } } return min; } EventDataType Day::settings_avg(ChannelID code) { EventDataType val = 0; int cnt = 0; for (auto & sess : sessions) { if (sess->enabled()) { auto set = sess->settings.find(code); if (set != sess->settings.end()) { val += set.value().toDouble(); cnt++; } } } val = (cnt > 0) ? (val / EventDataType(cnt)) : val; return val; } EventDataType Day::settings_wavg(ChannelID code) { double s0 = 0, s1 = 0, s2 = 0, tmp; for (auto & sess : sessions) { if (sess->enabled()) { auto set = sess->settings.find(code); if (set != sess->settings.end()) { if (code == CPAP_Mode && sess->type() != MT_CPAP) { // There used to be a bug in gLineChart::SetDay that inserted a CPAP_Mode // setting in any session that didn't already have one. That shouldn't // happen any more, but leave this diagnostic message here in case it does. qWarning() << sess->session() << "non-CPAP session with CPAP mode setting"; continue; } s0 = sess->hours(); tmp = set.value().toDouble(); s1 += tmp * s0; s2 += s0; } } } if (s2 == 0) { return 0; } tmp = (s1 / s2); return tmp; } EventDataType Day::percentile(ChannelID code, EventDataType percentile) { // Cache this calculation? // QHash >::iterator pi; // pi=perc_cache.find(code); // if (pi!=perc_cache.end()) { // QHash & hsh=pi.value(); // QHash::iterator hi=hsh.find( // if (hi!=pi.value().end()) { // return hi.value(); // } // } QHash wmap; // weight map QHash::iterator wmapit; qint64 SN = 0; EventDataType lastgain = 0, gain = 0; // First Calculate count of all events bool timeweight; for (auto & sess : sessions) { if (!sess->enabled()) { continue; } auto ei = sess->m_valuesummary.find(code); if (ei == sess->m_valuesummary.end()) { continue; } auto tei = sess->m_timesummary.find(code); timeweight = (tei != sess->m_timesummary.end()); gain = sess->m_gain[code]; // Here's assuming gains don't change accross a days sessions // Can't assume this in any multi day calculations.. if (lastgain > 0) { if (gain != lastgain) { qDebug() << "Gains differ across sessions: " << gain << lastgain; } } lastgain = gain; qint64 weight; //qint64 tval; if (timeweight) { wmap.reserve(wmap.size() + tei.value().size()); for (auto it = tei.value().begin(), teival_end=tei.value().end(); it != teival_end; ++it) { weight = it.value(); SN += weight; wmap[it.key()] += weight; } } else { wmap.reserve(wmap.size() + ei.value().size()); for (auto it = ei.value().begin(), eival_end=ei.value().end(); it != eival_end; ++it) { weight = it.value(); SN += weight; wmap[it.key()] += weight; } } } QVector valcnt; valcnt.resize(wmap.size()); // Build sorted list of value/counts auto wmap_end = wmap.end(); int ii=0; for (auto it = wmap.begin(); it != wmap_end; ++it) { valcnt[ii++]=ValueCount(EventDataType(it.key()) * gain, it.value(), 0); } // sort by weight, then value //qSort(valcnt); std::sort(valcnt.begin(), valcnt.end()); //double SN=100.0/double(N); // 100% / overall sum double p = 100.0 * percentile; double nth = double(SN) * percentile; // index of the position in the unweighted set would be double nthi = floor(nth); qint64 sum1 = 0, sum2 = 0; qint64 w1, w2 = 0; double v1 = 0, v2; int N = valcnt.size(); int k = 0; for (k = 0; k < N; k++) { v1 = valcnt.at(k).value; w1 = valcnt.at(k).count; sum1 += w1; if (sum1 > nthi) { return v1; } if (sum1 == nthi) { break; // boundary condition } } if (k >= N) { return v1; } if (valcnt.size() == 1) { return valcnt[0].value; } v2 = valcnt[k + 1].value; w2 = valcnt[k + 1].count; sum2 = sum1 + w2; // value lies between v1 and v2 double px = 100.0 / double(SN); // Percentile represented by one full value // calculate percentile ranks double p1 = px * (double(sum1) - (double(w1) / 2.0)); double p2 = px * (double(sum2) - (double(w2) / 2.0)); // calculate linear interpolation double v = v1 + ((p - p1) / (p2 - p1)) * (v2 - v1); return v; // p1.....p.............p2 // 37 55 70 } EventDataType Day::p90(ChannelID code) { return percentile(code, 0.90F); } EventDataType Day::rangeCount(ChannelID code, qint64 st, qint64 et) { int cnt = 0; for (auto & sess : sessions) { if (sess->enabled()) { cnt += sess->rangeCount(code, st, et); } } return cnt; } EventDataType Day::rangeSum(ChannelID code, qint64 st, qint64 et) { double val = 0; for (auto & sess : sessions) { if (sess->enabled()) { val += sess->rangeSum(code, st, et); } } return val; } EventDataType Day::rangeAvg(ChannelID code, qint64 st, qint64 et) { double val = 0; int cnt = 0; for (auto & sess : sessions) { if (sess->enabled()) { val += sess->rangeSum(code, st, et); cnt += sess->rangeCount(code, st,et); } } if (cnt == 0) { return 0; } val /= double(cnt); return val; } EventDataType Day::rangeWavg(ChannelID code, qint64 st, qint64 et) { double sum = 0; double cnt = 0; qint64 lasttime, time; double data, duration; for (auto & sess : sessions) { auto EVEC = sess->eventlist.find(code); if (EVEC == sess->eventlist.end()) continue; for (auto & el : EVEC.value()) { if (el->count() < 1) continue; lasttime = el->time(0); if (lasttime < st) lasttime = st; for (unsigned i=1; icount(); i++) { data = el->data(i); time = el->time(i); if (time < st) { lasttime = st; continue; } if (time > et) { time = et; } duration = double(time - lasttime) / 1000.0; sum += data * duration; cnt += duration; if (time >= et) break; lasttime = time; } } } if (cnt < 0.000001) return 0; return sum / cnt; } // Boring non weighted percentile EventDataType Day::rangePercentile(ChannelID code, float p, qint64 st, qint64 et) { int count = rangeCount(code, st,et); QVector list; list.resize(count); int idx = 0; qint64 time; for (auto & sess : sessions) { auto EVEC = sess->eventlist.find(code); if (EVEC == sess->eventlist.end()) continue; for (auto & el : EVEC.value()) { for (unsigned i=0; icount(); i++) { time = el->time(i); if ((time < st) || (time > et)) continue; list[idx++] = el->data(i); } } } // TODO: use nth_element instead.. //qSort(list); std::sort(list.begin(), list.end()); float b = float(idx) * p; int a = floor(b); int c = ceil(b); if ((a == c) || (c >= idx)) { return list[a]; } EventDataType v1 = list[a]; EventDataType v2 = list[c]; EventDataType diff = v2 - v1; // the whole == C-A double ba = b - float(a); // A....B...........C == B-A double val = v1 + diff * ba; return val; } EventDataType Day::avg(ChannelID code) { double val = 0; // Cache this? int cnt = 0; for (auto & sess : sessions) { if (sess->enabled()) { val += sess->sum(code); cnt += sess->count(code); } } if (cnt == 0) { return 0; } val /= double(cnt); return val; } EventDataType Day::sum(ChannelID code) { // Cache this? EventDataType val = 0; if (code == AllAhiChannels) { for (int i = 0; i < ahiChannels.size(); i++) val += sum(ahiChannels.at(i)); return val; } for (auto & sess : sessions) { if (sess->enabled() && sess->m_sum.contains(code)) { val += sess->sum(code); } } return val; } EventDataType Day::wavg(ChannelID code) { double s0 = 0, s1 = 0, s2 = 0; qint64 d; for (auto & sess : sessions) { if (sess->enabled() && sess->m_wavg.contains(code)) { d = sess->length(); s0 = double(d) / 3600000.0; if (s0 > 0) { s1 += sess->wavg(code) * s0; s2 += s0; } } } if (s2 == 0) { return 0; } return (s1 / s2); } // Total session time in milliseconds qint64 Day::total_time() { qint64 d_totaltime = 0; QMultiMap range; //range.reserve(size()*2); // Remember sessions may overlap.. qint64 first, last; for (auto & sess : sessions) { int slicesize = sess->m_slices.size(); if (sess->enabled() && (sess->type() != MT_JOURNAL)) { first = sess->first(); last = sess->last(); if (slicesize == 0) { // This algorithm relies on non zero length, and correctly ordered sessions if (last > first) { range.insert(first, 0); range.insert(last, 1); d_totaltime += sess->length(); if (sess->length() == 0) { qWarning() << sess->s_session << "0 length session"; } } } else { for (auto & slice : sess->m_slices) { if (slice.status == MaskOn) { range.insert(slice.start, 0); range.insert(slice.end, 1); d_totaltime += slice.end - slice.start; if (slice.end - slice.start == 0) { qWarning() << sess->s_session << "0 length slice"; } } } } } } bool b; int nest = 0; qint64 ti = 0; qint64 total = 0; // This is my implementation of a typical "brace counting" algorithm mentioned here: // http://stackoverflow.com/questions/7468948/problem-calculating-overlapping-date-ranges auto rend = range.end(); for (auto rit = range.begin(); rit != rend; ++rit) { b = rit.value(); if (!b) { if (!ti) { ti = rit.key(); } nest++; } else { if (--nest <= 0) { total += rit.key() - ti; ti = 0; } } } if (total != d_totaltime) { // They can overlap.. tough. // qDebug() << "Sessions Times overlaps!" << total << d_totaltime; } return total; //d_totaltime; } // Total session time in milliseconds, only considering machinetype qint64 Day::total_time(MachineType type) { qint64 d_totaltime = 0; QMultiMap range; //range.reserve(size()*2); // Remember sessions may overlap.. qint64 first, last; for (auto & sess : sessions) { int slicesize = sess->m_slices.size(); if (sess->enabled() && (sess->type() == type)) { first = sess->first(); last = sess->last(); // This algorithm relies on non zero length, and correctly ordered sessions if (slicesize == 0) { if (last > first) { range.insert(first, 0); range.insert(last, 1); d_totaltime += sess->length(); if (sess->length() == 0) { qWarning() << sess->s_session << "0 length session"; } } } else { for (const auto & slice : sess->m_slices) { if (slice.status == MaskOn) { range.insert(slice.start, 0); range.insert(slice.end, 1); d_totaltime += slice.end - slice.start; if (slice.end - slice.start == 0) { qWarning() << sess->s_session << "0 length slice"; } } } } } } bool b; int nest = 0; qint64 ti = 0; qint64 total = 0; // This is my implementation of a typical "brace counting" algorithm mentioned here: // http://stackoverflow.com/questions/7468948/problem-calculating-overlapping-date-ranges auto rend = range.end(); for (auto rit = range.begin(); rit != rend; ++rit) { b = rit.value(); if (!b) { if (!ti) { ti = rit.key(); } nest++; } else { if (--nest <= 0) { total += rit.key() - ti; ti = 0; } } } if (total != d_totaltime) { // They can overlap.. tough. // qDebug() << "Sessions Times overlaps!" << total << d_totaltime; } return total; //d_totaltime; } ChannelID Day::getPressureChannelID() { // TODO: This is an awful hack that depends on the enum ordering of the generic CPAP_Mode channel. // See the comment in getCPAPModeStr(). // Determined the preferred pressure channel (CPAP_IPAP or CPAP_Pressure) CPAPMode cpapmode = (CPAPMode)(int)settings_max(CPAP_Mode); // TODO: PRS1 ventilators in CPAP mode report IPAP rather than pressure...but their pressure setting channel is CPAP_Pressure, // so this currently gets fixed in the welcome screen manually. // And why would ASV or AVAPS have Pressure channels? QList preferredIDs = { CPAP_Pressure, CPAP_PressureSet, CPAP_IPAP, CPAP_IPAPSet }; if (cpapmode == MODE_ASV || cpapmode == MODE_ASV_VARIABLE_EPAP || cpapmode == MODE_AVAPS || cpapmode == MODE_BILEVEL_FIXED || cpapmode == MODE_BILEVEL_AUTO_FIXED_PS || cpapmode == MODE_BILEVEL_AUTO_VARIABLE_PS) { preferredIDs = { CPAP_IPAP, CPAP_IPAPSet, CPAP_Pressure, CPAP_PressureSet }; } for (auto & preferredID : preferredIDs) { // If preferred channel has data, return it if (channelHasData(preferredID)) { // if ( cpapmode == MODE_AVAPS ) // qDebug() << QString("Found pressure channel 0x%1").arg(preferredID, 4, 16, QChar('0')); return preferredID; } } qDebug() << "No pressure channel for " << getCPAPModeStr(); return NoChannel; } bool Day::hasEnabledSessions() { for (auto & sess : sessions) { if (sess->enabled()) { return true; } } return false; } bool Day::hasEnabledSessions(MachineType type) { for (auto & sess : sessions) { if ((sess->type() == type) && sess->enabled()) { return true; } } return false; } /*EventDataType Day::percentile(ChannelID code,double percent) { double val=0; int cnt=0; for (auto & sess : sessions) { if (sess->eventlist.find(code)!=sess->eventlist.end()) { val+=sess->percentile(code,percent); cnt++; } } if (cnt==0) return 0; return EventDataType(val/cnt); }*/ qint64 Day::first(ChannelID code) { qint64 date = 0; qint64 tmp; for (auto & sess : sessions) { if (sess->enabled()) { tmp = sess->first(code); if (!tmp) { continue; } if (!date) { date = tmp; } else { if (tmp < date) { date = tmp; } } } } return date; } qint64 Day::last(ChannelID code) { qint64 date = 0; qint64 tmp; for (auto & sess : sessions) { if (sess->enabled()) { tmp = sess->last(code); if (!tmp) { continue; } if (!date) { date = tmp; } else { if (tmp > date) { date = tmp; } } } } return date; } EventDataType Day::Min(ChannelID code) { EventDataType min = 0; EventDataType tmp; bool first = true; for (auto & sess : sessions) { if (sess->enabled() && sess->m_min.contains(code)) { tmp = sess->Min(code); if (first) { min = tmp; first = false; } else { if (tmp < min) { min = tmp; } } } } return min; } EventDataType Day::physMin(ChannelID code) { EventDataType min = 0; EventDataType tmp; bool first = true; for (auto & sess : sessions) { if (sess->enabled() && sess->m_min.contains(code)) { tmp = sess->physMin(code); if (first) { min = tmp; first = false; } else { if (tmp < min) { min = tmp; } } } } return min; } bool Day::hasData(ChannelID code, SummaryType type) { bool has = false; for (auto & sess : sessions) { if (sess->type() == MT_JOURNAL) continue; if (sess->enabled()) { switch (type) { // case ST_90P: // has=sess->m_90p.contains(code); // break; case ST_PERC: has = sess->m_valuesummary.contains(code); break; case ST_MIN: has = sess->m_min.contains(code); break; case ST_MAX: has = sess->m_max.contains(code); break; case ST_CNT: has = sess->m_cnt.contains(code); break; case ST_AVG: has = sess->m_avg.contains(code); break; case ST_WAVG: has = sess->m_wavg.contains(code); break; case ST_CPH: has = sess->m_cph.contains(code); break; case ST_SPH: has = sess->m_sph.contains(code); break; case ST_FIRST: has = sess->m_firstchan.contains(code); break; case ST_LAST: has = sess->m_lastchan.contains(code); break; case ST_SUM: has = sess->m_sum.contains(code); break; default: break; } if (has) { break; } } } return has; } EventDataType Day::Max(ChannelID code) { EventDataType max = 0; EventDataType tmp; bool first = true; for (auto & sess : sessions) { if (sess->enabled() && sess->m_max.contains(code)) { tmp = sess->Max(code); if (first) { max = tmp; first = false; } else { if (tmp > max) { max = tmp; } } } } return max; } EventDataType Day::physMax(ChannelID code) { EventDataType max = 0; EventDataType tmp; bool first = true; for (auto & sess : sessions) { if (sess->enabled() && sess->m_max.contains(code)) { tmp = sess->physMax(code); if (first) { max = tmp; first = false; } else { if (tmp > max) { max = tmp; } } } } return max; } EventDataType Day::cph(ChannelID code) { double sum = 0; for (auto & sess : sessions) { if (sess->enabled() && sess->m_cnt.contains(code)) { sum += sess->count(code); } } sum /= hours(); return sum; } EventDataType Day::sph(ChannelID code) { EventDataType sum = 0; EventDataType h = 0; for (auto & sess : sessions) { if (sess->enabled() && sess->m_sum.contains(code)) { sum += sess->sum(code) / 3600.0; } } h = hours(); sum = (100.0 / h) * sum; return sum; } EventDataType Day::count(ChannelID code) { EventDataType total = 0; if (code == AllAhiChannels) { for (int i = 0; i < ahiChannels.size(); i++) total += count(ahiChannels.at(i)); return total; } for (auto & sess : sessions) { if (sess->enabled() && sess->m_cnt.contains(code)) { total += sess->count(code); } } return total; } bool Day::noSettings(Machine * mach) { for (auto & sess : sessions) { if ((mach == nullptr) && sess->noSettings()) { // If this day generally has just summary data. return true; } else if ((mach == sess->machine()) && sess->noSettings()) { // Focus only on machine mach return true; } } return false; } bool Day::summaryOnly(Machine * mach) { for (auto & sess : sessions) { if ((mach == nullptr) && sess->summaryOnly()) { // If this day generally has just summary data. return true; } else if ((mach == sess->machine()) && sess->summaryOnly()) { // Focus only on machine mach return true; } } return false; } bool Day::settingExists(ChannelID id) { for (auto & sess : sessions) { if (sess->enabled()) { auto set = sess->settings.find(id); if (set != sess->settings.end()) { return true; } } } return false; } bool Day::eventsLoaded() { for (auto & sess : sessions) { if (sess->eventsLoaded()) { return true; } } return false; } bool Day::channelExists(ChannelID id) { for (auto & sess : sessions) { if (sess->enabled() && sess->eventlist.contains(id)) { return true; } } return false; } bool Day::hasEvents() { for (auto & sess : sessions) { if (sess->eventlist.size() > 0) return true; } return false; } bool Day::channelHasData(ChannelID id) { for (auto & sess : sessions) { if (sess->enabled()) { if (sess->m_cnt.contains(id)) { return true; } if (sess->eventlist.contains(id)) { return true; } if (sess->m_valuesummary.contains(id)) { return true; } } } return false; } void Day::OpenEvents() { for (auto & sess : sessions) { if (sess->type() != MT_JOURNAL) sess->OpenEvents(); } d_events_open = true; } void Day::OpenSummary() { if (d_summaries_open) return; for (auto & sess : sessions) { sess->LoadSummary(); } d_summaries_open = true; } void Day::CloseEvents() { for (auto & sess : sessions) { sess->TrashEvents(); } d_events_open = false; } QList Day::getSortedMachineChannels(MachineType type, quint32 chantype) { QList available; auto mi_end = machines.end(); for (auto mi = machines.begin(); mi != mi_end; mi++) { if (mi.key() != type) continue; available.append(mi.value()->availableChannels(chantype)); } QMultiMap order; for (const auto code : available) { order.insert(schema::channel[code].order(), code); } QList channels; for (auto it = order.begin(); it != order.end(); ++it) { ChannelID code = it.value(); channels.append(code); } return channels; } QList Day::getSortedMachineChannels(quint32 chantype) { QList available; auto mi_end = machines.end(); for (auto mi = machines.begin(); mi != mi_end; mi++) { if (mi.key() == MT_JOURNAL) continue; available.append(mi.value()->availableChannels(chantype)); } QMultiMap order; for (auto code : available) { order.insert(schema::channel[code].order(), code); } QList channels; for (auto it = order.begin(); it != order.end(); ++it) { ChannelID code = it.value(); channels.append(code); } return channels; } qint64 Day::first(MachineType type) { qint64 date = 0; qint64 tmp; for (auto & sess : sessions) { if ((sess->type() == type) && sess->enabled()) { tmp = sess->first(); if (!tmp) { continue; } if (!date) { date = tmp; } else { if (tmp < date) { date = tmp; } } } } return date; } qint64 Day::first() { qint64 date = 0; qint64 tmp; for (auto & sess : sessions) { if (sess->type() == MT_JOURNAL) continue; if (sess->enabled()) { tmp = sess->first(); if (!tmp) { continue; } if (!date) { date = tmp; } else { if (tmp < date) { date = tmp; } } } } return date; } //! \brief Returns the last session time of this day qint64 Day::last() { qint64 date = 0; qint64 tmp; for (auto & sess : sessions) { if (sess->type() == MT_JOURNAL) continue; if (sess->enabled()) { tmp = sess->last(); if (!tmp) { continue; } if (!date) { date = tmp; } else { if (tmp > date) { date = tmp; } } } } return date; } qint64 Day::last(MachineType type) { qint64 date = 0; qint64 tmp; for (auto & sess : sessions) { if ((sess->type() == type) && sess->enabled()) { tmp = sess->last(); if (!tmp) { continue; } if (!date) { date = tmp; } else { if (tmp > date) { date = tmp; } } } } return date; } bool Day::removeSession(Session *sess) { sess->machine()->sessionlist.remove(sess->session()); MachineType mt = sess->type(); bool b = sessions.removeAll(sess) > 0; if (!searchMachine(mt)) { machines.remove(mt); } return b; } bool Day::searchMachine(MachineType mt) { for (auto & sess : sessions) { if (sess->type() == mt) return true; } return false; } bool Day::hasMachine(Machine * mach) { for (auto & sess : sessions) { if (sess->machine() == mach) return true; } return false; } void Day::removeMachine(Machine * mach) { // Yell about and fix any dangling references rather than possibly crashing later. // // This has no functional use and can be removed when the data structures are cleaned up // with better encapsulation and fewer unnecessary references between each other. QList list = sessions; // make a copy so the iterator doesn't get broken by removals for (auto & sess : list) { if (sess->machine() == mach) { // This indicates a problem with the Machine class not tracking all of its sessions, for // example if there's a duplicate session ID. qCritical() << "Day object" << this->date().toString() << "session" << sess->session() << "refers to machine" << mach->serial(); removeSession(sess); } } for (auto & m : machines.keys()) { if (machines[m] == mach) { // This indicates a problem internal to the Day class, since removeSession should remove // machines from this list if there are no longer any sessions pointing to it. qCritical() << "Day object" << this->date().toString() << "refers to machine" << mach->serial(); machines.remove(m); } } } int Day::getCPAPMode() { // NOTE: This needs to return the generic mode, unlike getCPAPModeStr(). // This function is used only to determine whether to use advanced graphs, // which refer to the generic mode. /* Machine * mach = machine(MT_CPAP); if (!mach) return 0; CPAPLoader * loader = qobject_cast(mach->loader()); ChannelID modechan = loader->CPAPModeChannel(); */ ChannelID modechan = CPAP_Mode; // schema::Channel & chan = schema::channel[modechan]; // TODO: This is an awful hack that depends on the enum ordering of the machine-specific CPAP mode. // See the comment in getCPAPModeStr(). int mode = (CPAPMode)(int)qRound(settings_wavg(modechan)); return mode; } QString Day::getCPAPModeStr() { Machine * mach = machine(MT_CPAP); if (!mach) return STR_MessageBox_Error; CPAPLoader * loader = qobject_cast(mach->loader()); ChannelID modechan = loader->CPAPModeChannel(); schema::Channel & chan = schema::channel[modechan]; // TODO: This is an awful hack that depends on the enum ordering of the machine-specific CPAP mode. // Instead, we should calculate how long each mode was in operation and // determine the one that was running the longest, along with the settings // while that mode was in operation. int mode = (CPAPMode)(int)qRound(settings_wavg(modechan)); return chan.option(mode); // if (mode == MODE_CPAP) { // return QObject::tr("Fixed"); // } else if (mode == MODE_APAP) { // return QObject::tr("Auto"); // } else if (mode == MODE_BILEVEL_FIXED ) { // return QObject::tr("Fixed Bi-Level"); // } else if (mode == MODE_BILEVEL_AUTO_FIXED_PS) { // return QObject::tr("Auto Bi-Level (Fixed PS)"); // } else if (mode == MODE_BILEVEL_AUTO_VARIABLE_PS) { // return QObject::tr("Auto Bi-Level (Variable PS)"); // } else if (mode == MODE_ASV) { // return QObject::tr("ASV Fixed EPAP"); // } else if (mode == MODE_ASV_VARIABLE_EPAP) { // return QObject::tr("ASV Variable EPAP"); // } // return STR_TR_Unknown; } QString Day::getPressureRelief() { Machine * mach = machine(MT_CPAP); if (!mach) return STR_MessageBox_Error; CPAPLoader * loader = qobject_cast(mach->loader()); if (!loader) return STR_MessageBox_Error; QString pr_str; ChannelID pr_level_chan = loader->PresReliefLevel(); ChannelID pr_mode_chan = loader->PresReliefMode(); // Separate calculation for SleepStyle machines if (mach->info.loadername == "SleepStyle") { pr_str = loader->PresReliefLabel(); int pr_level = -1; if (pr_level_chan != NoChannel && settingExists(pr_level_chan)) { pr_level = qRound(settings_wavg(pr_level_chan)); } if (pr_level == -1) return STR_TR_None; if ((pr_mode_chan != NoChannel) && settingExists(pr_mode_chan)) { schema::Channel & chan = schema::channel[pr_level_chan]; QString level = chan.option(pr_level); if (level.isEmpty()) { level = QString().number(pr_level) + " " + chan.units(); if (settings_min(pr_level_chan) != settings_max(pr_level_chan)) level = QObject::tr("varies"); } pr_str += QString(" %1").arg(level); } return pr_str; } if ((pr_mode_chan != NoChannel) && settingExists(pr_mode_chan)) { // TODO: This is an awful hack that depends on the enum ordering of the pressure relief mode. // See the comment in getCPAPModeStr(). int pr_mode = qRound(settings_wavg(pr_mode_chan)); pr_str = QString("%1%2").arg(loader->PresReliefLabel()).arg(schema::channel[pr_mode_chan].option(pr_mode)); int pr_level = -1; if (pr_level_chan != NoChannel && settingExists(pr_level_chan)) { pr_level = qRound(settings_wavg(pr_level_chan)); } if (pr_level >= 0) { // TODO: Ideally the formatting of LOOKUP datatypes should be done in only one place. schema::Channel & chan = schema::channel[pr_level_chan]; QString level = chan.option(pr_level); if (level.isEmpty()) { level = QString().number(pr_level) + " " + chan.units();; } pr_str += QString(" %1").arg(level); } } else pr_str = STR_TR_None; return pr_str; } QString Day::validPressure(float pressure) { if (fabsf(pressure) == std::numeric_limits::max()) return QObject::tr("n/a"); return QString("%1").arg(pressure, 0, 'f', 1); } QString Day::getPressureSettings() { if (machine(MT_CPAP) == nullptr) { qCritical("getPressureSettings called with no CPAP machine record"); return QString(); } // TODO: This is an awful hack that depends on the enum ordering of the generic CPAP_Mode channel. // See the comment in getCPAPModeStr(). CPAPMode mode = (CPAPMode)(int)settings_max(CPAP_Mode); QString units = schema::channel[CPAP_Pressure].units(); if (mode == MODE_CPAP) { return QObject::tr("Fixed %1 (%2)").arg(validPressure(settings_min(CPAP_Pressure))). arg(units); } else if (mode == MODE_APAP) { return QObject::tr("Min %1 Max %2 (%3)").arg(validPressure(settings_min(CPAP_PressureMin))). arg(validPressure(settings_max(CPAP_PressureMax))). arg(units); } else if (mode == MODE_BILEVEL_FIXED ) { return QObject::tr("EPAP %1 IPAP %2 (%3)").arg(validPressure(settings_min(CPAP_EPAP))). arg(validPressure(settings_max(CPAP_IPAP))). arg(units); } else if (mode == MODE_BILEVEL_AUTO_FIXED_PS) { return QObject::tr("PS %1 over %2-%3 (%4)").arg(validPressure(settings_max(CPAP_PS))). arg(validPressure(settings_min(CPAP_EPAPLo))). arg(validPressure(settings_max(CPAP_IPAPHi))). arg(units); } else if (mode == MODE_BILEVEL_AUTO_VARIABLE_PS) { return QObject::tr("Min EPAP %1 Max IPAP %2 PS %3-%4 (%5)").arg(validPressure(settings_min(CPAP_EPAPLo))). arg(validPressure(settings_max(CPAP_IPAPHi))). arg(validPressure(settings_min(CPAP_PSMin))). arg(validPressure(settings_max(CPAP_PSMax))).arg(units); } else if (mode == MODE_ASV) { return QObject::tr("EPAP %1 PS %2-%3 (%4)").arg(validPressure(settings_min(CPAP_EPAP))). arg(validPressure(settings_min(CPAP_PSMin))). arg(validPressure(settings_max(CPAP_PSMax))).arg(units); } else if (mode == MODE_ASV_VARIABLE_EPAP) { return QObject::tr("Min EPAP %1 Max IPAP %2 PS %3-%4 (%5)"). arg(validPressure(settings_min(CPAP_EPAPLo))). arg(validPressure(settings_max(CPAP_IPAPHi))). arg(validPressure(settings_max(CPAP_PSMin))). arg(validPressure(settings_min(CPAP_PSMax))). arg(units); } else if (mode == MODE_AVAPS) { // qDebug() << "AVAPS: EPAP" << settings_min(CPAP_EPAP) << "IPAP min" << settings_max(CPAP_IPAPLo) << // "IPAP max" << settings_max(CPAP_IPAPHi); QString retStr; if (settings_min(CPAP_EPAPLo) == settings_max(CPAP_EPAPHi)) retStr = QObject::tr("EPAP %1 IPAP %2-%3 (%4)"). arg(validPressure(settings_min(CPAP_EPAP))). arg(validPressure(settings_max(CPAP_IPAPLo))). arg(validPressure(settings_max(CPAP_IPAPHi))). arg(units); else retStr = QObject::tr("EPAP %1-%2 IPAP %3-%4 (%5)"). arg(validPressure(settings_min(CPAP_EPAPLo))). arg(validPressure(settings_min(CPAP_EPAPHi))). arg(validPressure(settings_max(CPAP_IPAPLo))). arg(validPressure(settings_max(CPAP_IPAPHi))). arg(units); // qDebug() << "AVAPS mode:" << retStr; return retStr; } return STR_TR_Unknown; } EventDataType Day::calc(ChannelID code, ChannelCalcType type) { EventDataType value; switch(type) { case Calc_Min: value = Min(code); break; case Calc_Middle: value = calcMiddle(code); break; case Calc_Perc: value = calcPercentile(code); break; case Calc_Max: value = calcMax(code); break; case Calc_UpperThresh: value = schema::channel[code].upperThreshold(); break; case Calc_LowerThresh: value = schema::channel[code].lowerThreshold(); break; case Calc_Zero: default: value = 0; break; }; return value; } OSCAR-code-v1.3.1/oscar/SleepLib/day.h000066400000000000000000000276261417327530600172420ustar00rootroot00000000000000/* SleepLib Day Class Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef DAY_H #define DAY_H #include "SleepLib/common.h" #include "SleepLib/machine_common.h" #include "SleepLib/machine.h" #include "SleepLib/event.h" #include "SleepLib/session.h" /*! \class OneTypePerDay \brief An Exception class to catch multiple machine records per day */ class OneTypePerDay { }; class Machine; class Session; /*! \class Day \brief Contains a list of all Sessions for single date, for a single machine */ class Day { public: Day(); ~Day(); //! \brief Add a new machine to this day record bool addMachine(Machine *m); //! \brief Returns a machine record if present of specified machine type Machine *machine(MachineType type); //! \brief Returns a list of sessions for the specified machine type QList getSessions(MachineType type, bool ignore_enabled = false); //! \brief Add Session to this Day object (called during Load) void addSession(Session *s); EventDataType rangeCount(ChannelID code, qint64 st, qint64 et); EventDataType rangeSum(ChannelID code, qint64 st, qint64 et); EventDataType rangeAvg(ChannelID code, qint64 st, qint64 et); EventDataType rangeWavg(ChannelID code, qint64 st, qint64 et); EventDataType rangePercentile(ChannelID code, float p, qint64 st, qint64 et); //! \brief Returns the count of all this days sessions' events for this day EventDataType count(ChannelID code); //! \brief Returns the Minimum of all this sessions' events for this day EventDataType Min(ChannelID code); //! \brief Returns the Maximum of all sessions' events for this day EventDataType Max(ChannelID code); //! \brief Returns the Minimum of all this sessions' events for this day EventDataType physMin(ChannelID code); //! \brief Returns the Maximum of all sessions' events for this day EventDataType physMax(ChannelID code); //! \brief Returns the Count-per-hour of all sessions' events for this day EventDataType cph(ChannelID code); //! \brief Returns the Sum-per-hour of all this sessions' events for this day EventDataType sph(ChannelID code); //! \brief Returns (and caches) the 90th Percentile of all this sessions' events for this day EventDataType p90(ChannelID code); //! \brief Returns the Average of all this sessions' events for this day EventDataType avg(ChannelID code); //! \brief Returns the Sum of all this sessions' events for this day EventDataType sum(ChannelID code); //! \brief Returns the Time-Weighted Average of all this sessions' events for this day EventDataType wavg(ChannelID code); //! \brief Returns a requested Percentile of all this sessions' events for this day EventDataType percentile(ChannelID code, EventDataType percentile); //! \brief Returns if the cache contains SummaryType information about the requested code bool hasData(ChannelID code, SummaryType type); //! \brief Returns true if Day has specific machine type inline bool hasMachine(MachineType mt) const { return machines.contains(mt); } //! \brief Returns true if Day has specific machine record bool hasMachine(Machine * mach); //! \brief Returns true if any sessions have records matching specific machine type bool searchMachine(MachineType mt); //! \brief Removes any lingering references to a specific machine record and emits a warning if there were any void removeMachine(Machine * mach); //! \brief Returns the Average of all Sessions setting 'code' for this day EventDataType settings_avg(ChannelID code); //! \brief Returns the Time-Weighted Average of all Sessions setting 'code' for this day EventDataType settings_wavg(ChannelID code); //! \brief Returns the Sum of all Sessions setting 'code' for this day EventDataType settings_sum(ChannelID code); //! \brief Returns the Minimum of all Sessions setting 'code' for this day EventDataType settings_min(ChannelID code); //! \brief Returns the Maximum of all Sessions setting 'code' for this day EventDataType settings_max(ChannelID code); //! \brief Returns the amount of time (in decimal minutes) the Channel spent above the threshold EventDataType timeAboveThreshold(ChannelID code, EventDataType threshold); //! \brief Returns the amount of time (in decimal minutes) the Channel spent below the threshold EventDataType timeBelowThreshold(ChannelID code, EventDataType threshold); //! \brief Returns the value for Channel code at a given time EventDataType lookupValue(ChannelID code, qint64 time, bool square); //! \brief Returns the count of code events inside span flag event durations EventDataType countInsideSpan(ChannelID span, ChannelID code); //! \brief Returns the first session time of this day qint64 first(); //! \brief Returns the last session time of this day qint64 last(); //! \brief Returns the first session time of this machine type for this day qint64 first(MachineType type); //! \brief Returns the last session time of this machine type for this day qint64 last(MachineType type); // //! \brief Sets the first session time of this day // void setFirst(qint64 val) { d_first=val; } // //! \brief Sets the last session time of this day // void setLast(qint64 val) { d_last=val; } //! \brief Returns the last session time of this day for the supplied Channel code qint64 first(ChannelID code); //! \brief Returns the last session time of this day for the supplied Channel code qint64 last(ChannelID code); //! \brief Returns the total time in milliseconds for this day qint64 total_time(); //! \brief Returns the total time in milliseconds for this day for given machine type qint64 total_time(MachineType type); //! \brief Returns true if this day has enabled sessions for supplied machine type bool hasEnabledSessions(MachineType); //! \brief Returns true if this day has enabled sessions bool hasEnabledSessions(); //! \brief Return the total time in decimal hours for this day EventDataType hours() { if (!d_invalidate) return d_hours; d_invalidate = false; return d_hours = double(total_time()) / 3600000.0; } EventDataType hours(MachineType type) { auto it = d_machhours.find(type); if (it == d_machhours.end()) { return d_machhours[type] = double(total_time(type)) / 3600000.0; } return it.value(); } //! \brief Return the session indexed by i Session *operator [](int i) { return sessions[i]; } //! \brief Return the first session as a QVector::iterator QList::iterator begin() { return sessions.begin(); } //! \brief Return the end session record as a QVector::iterator QList::iterator end() { return sessions.end(); } //! \brief Check if day contains SummaryOnly records bool summaryOnly(Machine * mach = nullptr); //! \brief Check if day has missing Summary/Settings records bool noSettings(Machine * mach = nullptr); //! \brief Finds and returns the index of a session, otherwise -1 if it's not there int find(Session *sess) { return sessions.indexOf(sess); } Session *find(SessionID sessid); Session *find(SessionID sessid, MachineType mt); //! \brief Returns the number of Sessions in this day record int size() { return sessions.size(); } //! \brief Loads all Events files for this Days Sessions void OpenEvents(); void OpenSummary(); //! \brief Closes all Events files for this Days Sessions void CloseEvents(); //! \brief Get the ChannelID to be used for reporting pressure ChannelID getPressureChannelID(); //! \brief Returns true if this Day contains loaded Event Data for this channel. bool channelExists(ChannelID id); //! \brief Returns true if session events are loaded bool eventsLoaded(); //! \brief Returns true if this Day contains loaded Event Data or a cached count for this channel bool channelHasData(ChannelID id); //! \brief Returns true if this day contains the supplied settings Channel id bool settingExists(ChannelID id); //! \brief Removes a session from this day bool removeSession(Session *sess); //! \brief Returns a list of channels of supplied types, according to channel orders QList getSortedMachineChannels(quint32 chantype); //! \brief Returns a list of machine specific channels of supplied types, according to channel orders QList getSortedMachineChannels(MachineType type, quint32 chantype); // Some ugly CPAP specific stuff int getCPAPMode(); QString getCPAPModeStr(); QString getPressureRelief(); QString getPressureSettings(); QString validPressure(float pressure); // Some more very much CPAP only related stuff //! \brief Calculate AHI (Apnea Hypopnea Index) EventDataType calcAHI() { EventDataType c = count(AllAhiChannels); EventDataType minutes = hours(MT_CPAP) * 60.0; return (c * 60.0) / minutes; } //! \brief Calculate RDI (Respiratory Disturbance Index) EventDataType calcRDI() { EventDataType c = count(AllAhiChannels) + count(CPAP_RERA); EventDataType minutes = hours(MT_CPAP) * 60.0; return (c * 60.0) / minutes; } //! \brief Percent of night for specified channel EventDataType calcPON(ChannelID code) { EventDataType c = sum(code); EventDataType minutes = hours(MT_CPAP) * 60.0; return (100.0 / minutes) * (c / 60.0); } //! \brief Calculate index (count per hour) for specified channel EventDataType calcIdx(ChannelID code) { EventDataType c = count(code); EventDataType minutes = hours(MT_CPAP) * 60.0; return (c * 60.0) / minutes; } //! \brief SleepyyHead Events Index, AHI combined with OSCAR detected events.. :) EventDataType calcSHEI() { EventDataType c = count(AllAhiChannels) + count(CPAP_UserFlag1) + count(CPAP_UserFlag2); EventDataType minutes = hours(MT_CPAP) * 60.0; return (c * 60.0) / minutes; } //! \brief Total duration of all Apnea/Hypopnea events in seconds, EventDataType calcTTIA() { EventDataType c = sum(AllAhiChannels); return c; } bool hasEvents(); // According to preferences.. EventDataType calcMiddle(ChannelID code); EventDataType calcMax(ChannelID code); EventDataType calcPercentile(ChannelID code); static QString calcMiddleLabel(ChannelID code); static QString calcMaxLabel(ChannelID code); static QString calcPercentileLabel(ChannelID code); EventDataType calc(ChannelID code, ChannelCalcType type); Session * firstSession(MachineType type); //! \brief A QList containing all Sessions objects for this day QList sessions; QHash machines; void incUseCounter() { d_useCounter++; } void decUseCounter() { d_useCounter--; if (d_useCounter<0) d_useCounter = 0; } int useCounter() { return d_useCounter; } void invalidate() { d_invalidate = true; d_machhours.clear(); } void updateCPAPCache(); inline QDate date() const { return d_date; } void setDate(QDate date) { d_date = date; } protected: QHash > perc_cache; //qint64 d_first,d_last; private: bool d_firstsession; int d_useCounter; bool d_summaries_open; bool d_events_open; float d_hours; QHash d_machhours; QHash d_count; QHash d_sum; bool d_invalidate; QDate d_date; }; #endif // DAY_H OSCAR-code-v1.3.1/oscar/SleepLib/deviceconnection.cpp000066400000000000000000000731131417327530600223270ustar00rootroot00000000000000/* Device Connection Manager * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "deviceconnection.h" #include "xmlreplay.h" #include "version.h" #include #include static QString hex(int i) { return QString("0x") + QString::number(i, 16).toUpper(); } // MARK: - // MARK: Device connection manager /* * DeviceRecorder/DeviceReplay subclasses of XmlRecorder/XmlReplay * * Used by DeviceConnectionManager to record its activity, such as * port scanning and connection opening/closing. */ class DeviceRecorder : public XmlRecorder { public: static const QString TAG; DeviceRecorder(class QFile * file) : XmlRecorder(file, DeviceRecorder::TAG) { m_xml->writeAttribute("oscar", getVersion().toString()); } DeviceRecorder(QString & string) : XmlRecorder(string, DeviceRecorder::TAG) { m_xml->writeAttribute("oscar", getVersion().toString()); } }; const QString DeviceRecorder::TAG = "devicereplay"; class DeviceReplay : public XmlReplay { public: DeviceReplay(class QFile * file) : XmlReplay(file, DeviceRecorder::TAG) {} DeviceReplay(QXmlStreamReader & xml) : XmlReplay(xml, DeviceRecorder::TAG) {} }; void DeviceConnectionManager::record(QFile* stream) { if (m_record) { delete m_record; } if (stream) { m_record = new DeviceRecorder(stream); } else { // nullptr turns off recording m_record = nullptr; } } void DeviceConnectionManager::record(QString & string) { if (m_record) { delete m_record; } m_record = new DeviceRecorder(string); } void DeviceConnectionManager::replay(const QString & string) { QXmlStreamReader xml(string); reset(); if (m_replay) { delete m_replay; } m_replay = new DeviceReplay(xml); } void DeviceConnectionManager::replay(QFile* file) { reset(); if (m_replay) { delete m_replay; } if (file) { m_replay = new DeviceReplay(file); } else { // nullptr turns off replay m_replay = nullptr; } } // Return singleton instance of DeviceConnectionManager, creating it if necessary. DeviceConnectionManager & DeviceConnectionManager::getInstance() { static DeviceConnectionManager instance; return instance; } // Protected constructor DeviceConnectionManager::DeviceConnectionManager() : m_record(nullptr), m_replay(nullptr) { } DeviceConnection* DeviceConnectionManager::openConnection(const QString & type, const QString & name) { if (!factories().contains(type)) { qWarning() << "Unknown device connection type:" << type; return nullptr; } if (m_connections.contains(name)) { qWarning() << "connection to" << name << "already open"; return nullptr; } // Recording/replay (if any) is handled by the connection. DeviceConnection* conn = factories()[type](name, m_record, m_replay); if (conn) { if (conn->open()) { m_connections[name] = conn; } else { qWarning().noquote() << "unable to open" << type << "connection to" << name; delete conn; conn = nullptr; } } else { qWarning() << "unable to create" << type << "connection to" << name; } return conn; } // Called by connections to deregister themselves. void DeviceConnectionManager::connectionClosed(DeviceConnection* conn) { Q_ASSERT(conn); const QString & type = conn->type(); const QString & name = conn->name(); if (m_connections.contains(name)) { if (m_connections[name] == conn) { m_connections.remove(name); } else { qWarning() << "connection to" << name << "not created by openConnection!"; } } else { qWarning() << type << "connection to" << name << "missing"; } } // Temporary convenience function for code that still supports only serial ports. SerialPortConnection* DeviceConnectionManager::openSerialPortConnection(const QString & portName) { return dynamic_cast(getInstance().openConnection(SerialPortConnection::TYPE, portName)); } QHash & DeviceConnectionManager::factories() { static QHash s_factories; return s_factories; } bool DeviceConnectionManager::registerClass(const QString & type, DeviceConnection::FactoryMethod factory) { if (factories().contains(type)) { qWarning() << "Connection class already registered for type" << type; return false; } factories()[type] = factory; return true; } // Since there are relatively few connection types, don't bother with a CRTP // parent class. Instead, this macro defines the factory method, and the // subclass will need to declare createInstance() and TYPE manually. #define REGISTER_DEVICECONNECTION(type, T) \ const QString T::TYPE = type; \ const bool T::registered = DeviceConnectionManager::registerClass(T::TYPE, T::createInstance); \ DeviceConnection* T::createInstance(const QString & name, XmlRecorder* record, XmlReplay* replay) { return static_cast(new T(name, record, replay)); } // MARK: - // MARK: Device manager events // See XmlReplayEvent discussion of complex data types above. class GetAvailableSerialPortsEvent : public XmlReplayBase { public: QList m_ports; protected: virtual void write(QXmlStreamWriter & xml) const { xml << m_ports; } virtual void read(QXmlStreamReader & xml) { xml >> m_ports; } }; REGISTER_XMLREPLAYEVENT("getAvailableSerialPorts", GetAvailableSerialPortsEvent); QList DeviceConnectionManager::getAvailableSerialPorts() { XmlReplayLock lock(this, m_replay); GetAvailableSerialPortsEvent event; if (!m_replay) { // Query the actual hardware present. for (auto & info : QSerialPortInfo::availablePorts()) { event.m_ports.append(SerialPortInfo(info)); } } else { auto replayEvent = m_replay->getNextEvent(); if (replayEvent) { event.m_ports = replayEvent->m_ports; } else { // If there are no replay events available, reuse the most recent state. event.m_ports = m_serialPorts; } } m_serialPorts = event.m_ports; event.record(m_record); return event.m_ports; } // MARK: - // MARK: Serial port info /* * This class is both a drop-in replacement for QSerialPortInfo and * supports XML serialization for the GetAvailableSerialPortsEvent above. */ SerialPortInfo::SerialPortInfo(const QSerialPortInfo & other) { if (other.isNull() == false) { m_info["portName"] = other.portName(); m_info["systemLocation"] = other.systemLocation(); m_info["description"] = other.description(); m_info["manufacturer"] = other.manufacturer(); m_info["serialNumber"] = other.serialNumber(); if (other.hasVendorIdentifier()) { m_info["vendorIdentifier"] = other.vendorIdentifier(); } if (other.hasProductIdentifier()) { m_info["productIdentifier"] = other.productIdentifier(); } } } SerialPortInfo::SerialPortInfo(const SerialPortInfo & other) : m_info(other.m_info) { } SerialPortInfo::SerialPortInfo(const QString & data) { QXmlStreamReader xml(data); xml.readNextStartElement(); xml >> *this; } SerialPortInfo::SerialPortInfo() { } // TODO: This method is a temporary wrapper that mimics the QSerialPortInfo interface until we begin refactoring. QList SerialPortInfo::availablePorts() { return DeviceConnectionManager::getInstance().getAvailableSerialPorts(); } QXmlStreamWriter & operator<<(QXmlStreamWriter & xml, const SerialPortInfo & info) { xml.writeStartElement("serial"); if (info.isNull() == false) { xml.writeAttribute("portName", info.portName()); xml.writeAttribute("systemLocation", info.systemLocation()); xml.writeAttribute("description", info.description()); xml.writeAttribute("manufacturer", info.manufacturer()); xml.writeAttribute("serialNumber", info.serialNumber()); if (info.hasVendorIdentifier()) { xml.writeAttribute("vendorIdentifier", hex(info.vendorIdentifier())); } if (info.hasProductIdentifier()) { xml.writeAttribute("productIdentifier", hex(info.productIdentifier())); } } xml.writeEndElement(); return xml; } QXmlStreamReader & operator>>(QXmlStreamReader & xml, SerialPortInfo & info) { if (xml.atEnd() == false && xml.isStartElement() && xml.name() == "serial") { for (auto & attribute : xml.attributes()) { QString name = attribute.name().toString(); QString value = attribute.value().toString(); if (name == "vendorIdentifier" || name == "productIdentifier") { bool ok; quint16 id = value.toUInt(&ok, 0); if (ok) { info.m_info[name] = id; } else { qWarning() << "invalid" << name << "value" << value; } } else { info.m_info[name] = value; } } } else { qWarning() << "no tag"; } xml.skipCurrentElement(); return xml; } SerialPortInfo::operator QString() const { QString out; QXmlStreamWriter xml(&out); xml << *this; return out; } bool SerialPortInfo::operator==(const SerialPortInfo & other) const { return m_info == other.m_info; } // MARK: - // MARK: Device connection base classes and events /* * Event recorded in the Device Connection Manager XML stream that indicates * a connection was opened (or attempted). On success, a ConnectionEvent * (see below) will begin the connection's substream. */ class OpenConnectionEvent : public XmlReplayBase { public: OpenConnectionEvent() {} OpenConnectionEvent(const QString & type, const QString & name) { set("type", type); set("name", name); } virtual const QString id() const { return m_values["name"]; } }; REGISTER_XMLREPLAYEVENT("openConnection", OpenConnectionEvent); /* * Event created when a connection is successfully opened, used as the * enclosing tag for the connection substream. */ class ConnectionEvent : public XmlReplayBase { public: ConnectionEvent() { Q_ASSERT(false); } // Implement if we ever support string-based substreams ConnectionEvent(const OpenConnectionEvent & trigger) { copy(trigger); } virtual const QString id() const { QString time = m_time.toString("yyyyMMdd.HHmmss.zzz"); return m_values["name"] + "-" + time; } }; REGISTER_XMLREPLAYEVENT("connection", ConnectionEvent); /* * ConnectionRecorder/ConnectionReplay subclasses of XmlRecorder/XmlReplay * * Used by DeviceConnection subclasses to record their activity, such as * configuration and data sent and received. */ class ConnectionRecorder : public XmlRecorder { public: ConnectionRecorder(XmlRecorder* parent, const ConnectionEvent& event) : XmlRecorder(parent, event.id(), event.tag()) { Q_ASSERT(m_xml); event.writeTag(*m_xml); m_xml->writeAttribute("oscar", getVersion().toString()); } }; class ConnectionReplay : public XmlReplay { public: ConnectionReplay(XmlReplay* parent, const ConnectionEvent& event) : XmlReplay(parent, event.id(), event.tag()) {} }; // Device connection base class DeviceConnection::DeviceConnection(const QString & name, XmlRecorder* record, XmlReplay* replay) : m_name(name), m_record(record), m_replay(replay), m_opened(false) { } DeviceConnection::~DeviceConnection() { } /* * Generic get/set events */ class SetValueEvent : public XmlReplayBase { public: SetValueEvent() {} SetValueEvent(const QString & name, int value) { set(name, value); } virtual const QString id() const { return m_keys.first(); } }; REGISTER_XMLREPLAYEVENT("set", SetValueEvent); class GetValueEvent : public XmlReplayBase { public: GetValueEvent() {} GetValueEvent(const QString & id) { set(id, 0); } virtual const QString id() const { return m_keys.first(); } void setValue(qint64 value) { if (m_keys.isEmpty()) { qWarning() << "setValue: get event missing key"; return; } set(m_keys.first(), value); } QString value() const { if (m_keys.isEmpty()) { qWarning() << "getValue: get event missing key"; return 0; } return get(m_keys.first()); } }; REGISTER_XMLREPLAYEVENT("get", GetValueEvent); /* * Event recorded in the Device Connection Manager XML stream when a * open connection is closed. This is the complement to a successful * OpenConnectionEvent (see above), and does not appear when the connection * failed to open. */ class CloseConnectionEvent : public XmlReplayBase { public: CloseConnectionEvent() {} CloseConnectionEvent(const QString & type, const QString & name) { set("type", type); set("name", name); } virtual const QString id() const { return m_values["name"]; } }; REGISTER_XMLREPLAYEVENT("closeConnection", CloseConnectionEvent); class ClearConnectionEvent : public XmlReplayBase { }; REGISTER_XMLREPLAYEVENT("clear", ClearConnectionEvent); class FlushConnectionEvent : public XmlReplayBase { }; REGISTER_XMLREPLAYEVENT("flush", FlushConnectionEvent); /* * Event representing data received from a device * * The data is stored as hexadecimal data in the XML tag's contents. */ class ReceiveDataEvent : public XmlReplayBase { virtual bool usesData() const { return true; } }; REGISTER_XMLREPLAYEVENT("rx", ReceiveDataEvent); /* * Event representing data sent to a device * * The data is stored as hexadecimal data in the XML tag's contents. * * These events are random-access events (see discussion above), which cause * subsequent event retrieval to begin searching after the transmission * event. * * Since the data sent is used as the ID for these events, we can treat * these like distinct "commands" that that elicit a deterministic response, * which can be replayed independently of other events if desired. * * Of course, for any device that has more complex internal state (where * responses to multiple transmissions of a particular "command" depend * on some intervening event), this reordering will not be accurate. * * But the intent is that some small changes to client code should still * work with existing recordings before requiring creation of new ones. */ class TransmitDataEvent : public XmlReplayBase { virtual bool usesData() const { return true; } public: virtual const QString id() const { return m_data; } virtual bool randomAccess() const { return true; } }; REGISTER_XMLREPLAYEVENT("tx", TransmitDataEvent); /* * Event representing a "readyRead" signal emitted by a physical device. * * These events are marked as "signal" events (see discussion of m_signal * above) so that connections will automatically send them to clients when * the preceding event (API call) is processed. */ class ReadyReadEvent : public XmlReplayBase { public: // Use the connection's slot that receives readyRead signals. ReadyReadEvent() { m_signal = "onReadyRead"; } }; REGISTER_XMLREPLAYEVENT("readyRead", ReadyReadEvent); // MARK: - // MARK: Serial port connection /* * Serial port connection class * * This class wraps calls to an underlying QSerialPort with the logic * necessary to record and replay arbitrary serial port activity. * (or, at least, the serial port functionality currently used by OSCAR). * * Clients obtain a connection instance via DeviceConnectionManager::openConnection() * or openSerialPortConnection (for convenience, if they require a serial port). */ REGISTER_DEVICECONNECTION("serial", SerialPortConnection); SerialPortConnection::SerialPortConnection(const QString & name, XmlRecorder* record, XmlReplay* replay) : DeviceConnection(name, record, replay) { connect(&m_port, SIGNAL(readyRead()), this, SLOT(onReadyRead())); } SerialPortConnection::~SerialPortConnection() { // This will only be false if the connection failed to open immediately after construction. if (m_opened) { close(); DeviceConnectionManager::getInstance().connectionClosed(this); } disconnect(&m_port, SIGNAL(readyRead()), this, SLOT(onReadyRead())); } /* * Try to open the physical port (or replay a previous attempt), returning * false if the port was not opened. * * DeviceConnectionManager::openConnection calls this immediately after * creating a connection instance, and will only return open connections * to clients. */ bool SerialPortConnection::open() { if (m_opened) { qWarning() << "serial connection to" << m_name << "already opened"; return false; } XmlReplayLock lock(this, m_replay); OpenConnectionEvent* replayEvent = nullptr; OpenConnectionEvent event("serial", m_name); if (!m_replay) { // TODO: move this into SerialPortConnection::openDevice() and move // the rest of the logic up to DeviceConnection::open(). m_port.setPortName(m_name); checkResult(m_port.open(QSerialPort::ReadWrite), event); } else { replayEvent = m_replay->getNextEvent(event.id()); if (replayEvent) { event.copyIf(replayEvent); } else { event.set("error", QSerialPort::DeviceNotFoundError); } } event.record(m_record); m_opened = event.ok(); if (m_opened) { // open a connection substream for connection events if (m_record) { ConnectionEvent connEvent(event); m_record = new ConnectionRecorder(m_record, connEvent); } if (m_replay) { Q_ASSERT(replayEvent); ConnectionEvent connEvent(*replayEvent); // we need to use the replay's timestamp to find the referenced substream m_replay = new ConnectionReplay(m_replay, connEvent); } } return event.ok(); } bool SerialPortConnection::setBaudRate(qint32 baudRate, QSerialPort::Directions directions) { XmlReplayLock lock(this, m_replay); SetValueEvent event("baudRate", baudRate); event.set("directions", directions); if (!m_replay) { checkResult(m_port.setBaudRate(baudRate, directions), event); } else { auto replayEvent = m_replay->getNextEvent(event.id()); event.copyIf(replayEvent); } event.record(m_record); return event.ok(); } bool SerialPortConnection::setDataBits(QSerialPort::DataBits dataBits) { XmlReplayLock lock(this, m_replay); SetValueEvent event("setDataBits", dataBits); if (!m_replay) { checkResult(m_port.setDataBits(dataBits), event); } else { auto replayEvent = m_replay->getNextEvent(event.id()); event.copyIf(replayEvent); } event.record(m_record); return event.ok(); } bool SerialPortConnection::setParity(QSerialPort::Parity parity) { XmlReplayLock lock(this, m_replay); SetValueEvent event("setParity", parity); if (!m_replay) { checkResult(m_port.setParity(parity), event); } else { auto replayEvent = m_replay->getNextEvent(event.id()); event.copyIf(replayEvent); } event.record(m_record); return event.ok(); } bool SerialPortConnection::setStopBits(QSerialPort::StopBits stopBits) { XmlReplayLock lock(this, m_replay); SetValueEvent event("setStopBits", stopBits); if (!m_replay) { checkResult(m_port.setStopBits(stopBits), event); } else { auto replayEvent = m_replay->getNextEvent(event.id()); event.copyIf(replayEvent); } event.record(m_record); return event.ok(); } bool SerialPortConnection::setFlowControl(QSerialPort::FlowControl flowControl) { XmlReplayLock lock(this, m_replay); SetValueEvent event("setFlowControl", flowControl); if (!m_replay) { checkResult(m_port.setFlowControl(flowControl), event); } else { auto replayEvent = m_replay->getNextEvent(event.id()); event.copyIf(replayEvent); } event.record(m_record); return event.ok(); } bool SerialPortConnection::clear(QSerialPort::Directions directions) { XmlReplayLock lock(this, m_replay); ClearConnectionEvent event; event.set("directions", directions); if (!m_replay) { checkResult(m_port.clear(directions), event); } else { auto replayEvent = m_replay->getNextEvent(); event.copyIf(replayEvent); } event.record(m_record); return event.ok(); } qint64 SerialPortConnection::bytesAvailable() const { XmlReplayLock lock(this, m_replay); GetValueEvent event("bytesAvailable"); qint64 result; if (!m_replay) { result = m_port.bytesAvailable(); event.setValue(result); checkResult(result, event); } else { auto replayEvent = m_replay->getNextEvent(event.id()); event.copyIf(replayEvent); bool ok; result = event.value().toLong(&ok); if (!ok) { qWarning() << event.tag() << event.id() << "has bad value"; } } event.record(m_record); return result; } qint64 SerialPortConnection::read(char *data, qint64 maxSize) { XmlReplayLock lock(this, m_replay); qint64 len; ReceiveDataEvent event; if (!m_replay) { len = m_port.read(data, maxSize); if (len > 0) { event.setData(data, len); } event.set("len", len); if (len != maxSize) { event.set("req", maxSize); } checkResult(len, event); } else { auto replayEvent = m_replay->getNextEvent(); event.copyIf(replayEvent); if (!replayEvent) { qWarning() << "reading data past replay"; event.set("len", -1); event.set("error", QSerialPort::ReadError); } bool ok; len = event.get("len").toLong(&ok); if (ok) { if (event.ok()) { if (len != maxSize) { qWarning() << "replay of" << len << "bytes but" << maxSize << "requested"; } if (len > maxSize) { len = maxSize; } QByteArray replayData = event.getData(); memcpy(data, replayData, len); } } else { qWarning() << event << "has bad len"; len = -1; } } event.record(m_record); return len; } qint64 SerialPortConnection::write(const char *data, qint64 maxSize) { XmlReplayLock lock(this, m_replay); qint64 len; TransmitDataEvent event; event.setData(data, maxSize); if (!m_replay) { len = m_port.write(data, maxSize); event.set("len", len); if (len != maxSize) { event.set("req", maxSize); } checkResult(len, event); } else { auto replayEvent = m_replay->getNextEvent(event.id()); event.copyIf(replayEvent); if (!replayEvent) { qWarning() << "writing data past replay"; event.set("len", -1); event.set("error", QSerialPort::WriteError); } bool ok; len = event.get("len").toLong(&ok); // No need to copy any data, since the event already contains it. if (!ok) { qWarning() << event << "has bad len"; len = -1; } } event.record(m_record); return len; } bool SerialPortConnection::flush() { XmlReplayLock lock(this, m_replay); FlushConnectionEvent event; if (!m_replay) { checkResult(m_port.flush(), event); } else { auto replayEvent = m_replay->getNextEvent(); event.copyIf(replayEvent); } event.record(m_record); return event.ok(); } void SerialPortConnection::close() { if (m_opened) { // close event substream first if (m_record) { m_record = m_record->closeSubstream(); } if (m_replay) { m_replay = m_replay->closeSubstream(); } } XmlReplayLock lock(this, m_replay); CloseConnectionEvent event("serial", m_name); // TODO: We'll also need to include a loader ID and stream version number // in the "connection" tag, so that if we ever have to change a loader's download code, // the older replays will still work as expected. // NOTE: This may only be required for downloads rather than all connections. if (!m_replay) { // TODO: move this into SerialPortConnection::closeDevice() and move // the remaining logic up to DeviceConnection::close(). m_port.close(); checkError(event); } else { auto replayEvent = m_replay->getNextEvent(event.id()); if (replayEvent) { event.copyIf(replayEvent); } else { event.set("error", QSerialPort::ResourceError); } } event.record(m_record); } void SerialPortConnection::onReadyRead() { { // Wait until the replay signaler (if any) has released its lock. XmlReplayLock lock(this, m_replay); // This needs to be recorded before the signal below, since the slot may trigger more events. ReadyReadEvent event; event.record(m_record); // Unlocking will queue any subsequent signals. } // Because clients typically leave this as Qt::AutoConnection, the below emit may // execute immediately in this thread, so we have to release the lock before sending // the signal. // Unlike client-called events, We don't need to handle replay differently here, // because the replay will signal this slot just like the serial port. emit readyRead(); } // Check the boolean returned by a serial port call and the port's error status, and update the event accordingly. void SerialPortConnection::checkResult(bool ok, XmlReplayEvent & event) const { QSerialPort::SerialPortError error = m_port.error(); if (ok && error == QSerialPort::NoError) return; event.set("error", error); if (ok) event.set("ok", ok); // we don't expect to see this, but we should know if it happens } // Check the length returned by a serial port call and the port's error status, and update the event accordingly. void SerialPortConnection::checkResult(qint64 len, XmlReplayEvent & event) const { QSerialPort::SerialPortError error = m_port.error(); if (len < 0 || error != QSerialPort::NoError) { event.set("error", error); } } // Check the port's error status, and update the event accordingly. void SerialPortConnection::checkError(XmlReplayEvent & event) const { QSerialPort::SerialPortError error = m_port.error(); if (error != QSerialPort::NoError) { event.set("error", error); } } // MARK: - // MARK: SerialPort legacy class /* * SerialPort drop-in replacement for QSerialPort * * This class mimics the interface of QSerialPort for client code, while * using DeviceConnectionManager to open the SerialPortConnection, allowing * for transparent recording and replay. */ SerialPort::SerialPort() : m_conn(nullptr) { } SerialPort::~SerialPort() { if (m_conn) { close(); } } void SerialPort::setPortName(const QString &name) { m_portName = name; } bool SerialPort::open(QIODevice::OpenMode mode) { Q_ASSERT(!m_conn); Q_ASSERT(mode == QSerialPort::ReadWrite); m_conn = DeviceConnectionManager::openSerialPortConnection(m_portName); if (m_conn) { // Listen for readyRead events from the connection so that we can relay them to the client. connect(m_conn, SIGNAL(readyRead()), this, SLOT(onReadyRead())); } return m_conn != nullptr; } bool SerialPort::setBaudRate(qint32 baudRate, QSerialPort::Directions directions) { Q_ASSERT(m_conn); return m_conn->setBaudRate(baudRate, directions); } bool SerialPort::setDataBits(QSerialPort::DataBits dataBits) { Q_ASSERT(m_conn); return m_conn->setDataBits(dataBits); } bool SerialPort::setParity(QSerialPort::Parity parity) { Q_ASSERT(m_conn); return m_conn->setParity(parity); } bool SerialPort::setStopBits(QSerialPort::StopBits stopBits) { Q_ASSERT(m_conn); return m_conn->setStopBits(stopBits); } bool SerialPort::setFlowControl(QSerialPort::FlowControl flowControl) { Q_ASSERT(m_conn); return m_conn->setFlowControl(flowControl); } bool SerialPort::clear(QSerialPort::Directions directions) { Q_ASSERT(m_conn); return m_conn->clear(directions); } qint64 SerialPort::bytesAvailable() const { Q_ASSERT(m_conn); return m_conn->bytesAvailable(); } qint64 SerialPort::read(char *data, qint64 maxSize) { Q_ASSERT(m_conn); return m_conn->read(data, maxSize); } qint64 SerialPort::write(const char *data, qint64 maxSize) { Q_ASSERT(m_conn); return m_conn->write(data, maxSize); } bool SerialPort::flush() { Q_ASSERT(m_conn); return m_conn->flush(); } void SerialPort::close() { Q_ASSERT(m_conn); disconnect(m_conn, SIGNAL(readyRead()), this, SLOT(onReadyRead())); delete m_conn; // this will close the connection m_conn = nullptr; } void SerialPort::onReadyRead() { // Relay readyRead events from the connection on to the client. emit readyRead(); } OSCAR-code-v1.3.1/oscar/SleepLib/deviceconnection.h000066400000000000000000000271041417327530600217730ustar00rootroot00000000000000/* Device Connection Manager * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef DEVICECONNECTION_H #define DEVICECONNECTION_H // TODO: This file will eventually abstract serial port or bluetooth (or other) // connections to devices. For now it just supports serial ports. #include #include #include /* * Device connection base class * * Clients obtain a connection instance via DeviceConnectionManager::openConnection(). * See SerialPortConnection for the only current concrete implementation. * * See DeviceConnectionManager for the primary interface to device * connections. */ class DeviceConnection : public QObject { Q_OBJECT protected: // Constructor is protected so that only subclasses and DeviceConnectionManager can call it. DeviceConnection(const QString & name, class XmlRecorder* record, class XmlReplay* replay); const QString & m_name; // port/device identifier used to open the connection XmlRecorder* m_record; // nullptr or pointer to recorder instance XmlReplay* m_replay; // nullptr or pointer to replay instance bool m_opened; // true if open() succeeded virtual bool open() = 0; friend class DeviceConnectionManager; public: // See DeviceConnectionManager::openConnection() to create connections. virtual ~DeviceConnection(); virtual const QString & type() const = 0; const QString & name() const { return m_name; } typedef DeviceConnection* (*FactoryMethod)(const QString & name, XmlRecorder* record, XmlReplay* replay); }; /* * Device connection manager * * Principal class used to abstract direct connections to devices, * eventually encompassing serial port, Bluetooth, and BLE. This class not * only provides an abstraction for the specific connection type (where * possible), but it also provides the capability to record and replay * connections transparently to clients. * * Clients obtain the singleton instance via DeviceConnectionManager::getInstance(). * * TODO: Eventually they will be able to connect to signals when a device * becomes available or is removed. For now they need to call instance-> * getAvailableSerialPorts() to poll. * * When a device becomes available, clients call instance->openSerialPortConnection(). * TODO: This will eventually probably be openConnection() once Bluetooth is * supported. * * To enable recording and replay of connections, call instance->record() * and/or instance->replay(), which will cause all subsequent connections to * be recorded or replayed, respectively. Passing nullptr to record() or * replay() will turn off recording/replaying for subsequent connections. * This allows an application to record or replay connection data * transparently to client code that assumes it is talking directly to a * real device. */ class DeviceConnectionManager : public QObject { Q_OBJECT private: // See getInstance() for creating/using the device connection manager. DeviceConnectionManager(); XmlRecorder* m_record; // nullptr or pointer to recorder instance XmlReplay* m_replay; // nullptr or pointer to replay instance QList m_serialPorts; // currently available serial ports void reset() { // clear state m_serialPorts.clear(); } QHash m_connections; // currently open connections public: //! \brief Obtain pointer to global DeviceConnectionManager instance, creating it if necessary. static DeviceConnectionManager & getInstance(); //! \brief Open a connection to a device, returning an instance of the appropriate type, or nullptr if the connection couldn't be opened. class DeviceConnection* openConnection(const QString & type, const QString & name); //! \brief Open a serial port connection (convenience function, hopefully temporary), returning nullptr if the connection couldn't be opened. static class SerialPortConnection* openSerialPortConnection(const QString & portName); // temporary //! \brief Return the list of currently available serial ports. QList getAvailableSerialPorts(); // TODO: method to start a polling thread that maintains the list of ports // TODO: emit signal when new port is detected (or removed) //! \brief Record all subsequent device activity to the given file, and subsequent connections to separate files alongside it. Passing nullptr turns off recording. void record(class QFile* stream); // Record all subsequent device activity to the given string. Primarily for testing; connection recordings are not supported. void record(QString & string); //! \brief Replay the activity previously recorded in the given file, allowing for some simple variation in the order of API calls. Passing nullptr turns off replay. void replay(class QFile* stream); // Replay the activity represented by the given string. Primarily for testing; connection replay is not supported. void replay(const QString & string); // DeviceConnection subclasses registration, not intended for client use. protected: static QHash & factories(); public: static bool registerClass(const QString & type, DeviceConnection::FactoryMethod factory); static class DeviceConnection* createInstance(const QString & type); // Currently public only so that connections can deregister themselves. // Eventually this could move to protected if that gets handled by the // DeviceConnection destructor and DeviceConnection is declared a friend. void connectionClosed(DeviceConnection* conn); }; /* * Serial port connection class * * This class provides functionality equivalent to QSerialPort, but * specifically represents an opened connection rather than the port itself. * (See the SerialPort class for the QSerialPort equivalent.) This class * also provides support for recording and replay of an opened serial port * connection. * * TODO: This class may eventually be internal to DeviceConnection, if its * interface shares enough in common with Bluetooth and/or BLE. */ class SerialPortConnection : public DeviceConnection { Q_OBJECT private: QSerialPort m_port; // physical port used by connection void checkResult(bool ok, class XmlReplayEvent & event) const; void checkResult(qint64 len, XmlReplayEvent & event) const; void checkError(XmlReplayEvent & event) const; void close(); private slots: void onReadyRead(); signals: // The readyRead() signal is emitted with the same semantics as QSerialPort::readyRead(). void readyRead(); protected: SerialPortConnection(const QString & name, XmlRecorder* record, XmlReplay* replay); virtual bool open(); public: // See DeviceConnectionManager::openConnection() or openSerialPortConnection() to create connections. virtual ~SerialPortConnection(); // See QSerialPort for semantics of the below functions. bool setBaudRate(qint32 baudRate, QSerialPort::Directions directions = QSerialPort::AllDirections); bool setDataBits(QSerialPort::DataBits dataBits); bool setParity(QSerialPort::Parity parity); bool setStopBits(QSerialPort::StopBits stopBits); bool setFlowControl(QSerialPort::FlowControl flowControl); bool clear(QSerialPort::Directions directions = QSerialPort::AllDirections); qint64 bytesAvailable() const; qint64 read(char *data, qint64 maxSize); qint64 write(const char *data, qint64 maxSize); bool flush(); // Subclass registration with DeviceConnectionManager, not intended for client use. public: static DeviceConnection* createInstance(const QString & name, XmlRecorder* record, XmlReplay* replay); static const QString TYPE; static const bool registered; virtual const QString & type() const { return TYPE; } }; /* * SerialPort temporary class for legacy compatibility * * This class is a temporary drop-in replacement for QSerialPort for code * that currently assumes serial port connectivity. Using this class instead * of QSerialPort allows for recording and replay of connection data. * * See QSerialPort documentation for interface details. See * DeviceConnectionManager::record() and replay() for enabling recording * and replay. * * See SerialPortConnection for implementation details. */ class SerialPort : public QObject { Q_OBJECT private: SerialPortConnection* m_conn; QString m_portName; private slots: void onReadyRead(); signals: void readyRead(); public: SerialPort(); virtual ~SerialPort(); void setPortName(const QString &name); bool open(QIODevice::OpenMode mode); bool setBaudRate(qint32 baudRate, QSerialPort::Directions directions = QSerialPort::AllDirections); bool setDataBits(QSerialPort::DataBits dataBits); bool setParity(QSerialPort::Parity parity); bool setStopBits(QSerialPort::StopBits stopBits); bool setFlowControl(QSerialPort::FlowControl flowControl); bool clear(QSerialPort::Directions directions = QSerialPort::AllDirections); qint64 bytesAvailable() const; qint64 read(char *data, qint64 maxSize); qint64 write(const char *data, qint64 maxSize); bool flush(); void close(); }; /* * SerialPortInfo temporary class for legacy compatibility * * This class is a temporary drop-in replacement for QSerialPortInfo for * code that currently assumes serial port connectivity. Using this class * instead of QSerialPortInfo allows for recording and replay of port * scanning. * * See QSerialPortInfo documentation for interface details. See * DeviceConnectionManager::record() and replay() for enabling recording * and replay. * * TODO: This class's functionality may either become internal to * DeviceConnection or may be moved to a generic port info class that * supports Bluetooth and BLE as well as serial. Such a class might then be * used instead of port "name" between DeviceConnectionManager and clients. */ class QXmlStreamWriter; class QXmlStreamReader; class SerialPortInfo { public: static QList availablePorts(); SerialPortInfo(const SerialPortInfo & other); SerialPortInfo(const QString & data); SerialPortInfo(); inline QString portName() const { return m_info["portName"].toString(); } inline QString systemLocation() const { return m_info["systemLocation"].toString(); } inline QString description() const { return m_info["description"].toString(); } inline QString manufacturer() const { return m_info["manufacturer"].toString(); } inline QString serialNumber() const { return m_info["serialNumber"].toString(); } inline quint16 vendorIdentifier() const { return m_info["vendorIdentifier"].toInt(); } inline quint16 productIdentifier() const { return m_info["productIdentifier"].toInt(); } inline bool hasVendorIdentifier() const { return m_info.contains("vendorIdentifier"); } inline bool hasProductIdentifier() const { return m_info.contains("productIdentifier"); } inline bool isNull() const { return m_info.isEmpty(); } operator QString() const; friend class QXmlStreamWriter & operator<<(QXmlStreamWriter & xml, const SerialPortInfo & info); friend class QXmlStreamReader & operator>>(QXmlStreamReader & xml, SerialPortInfo & info); bool operator==(const SerialPortInfo & other) const; protected: SerialPortInfo(const class QSerialPortInfo & other); QHash m_info; friend class DeviceConnectionManager; }; #endif // DEVICECONNECTION_H OSCAR-code-v1.3.1/oscar/SleepLib/event.cpp000066400000000000000000000176621417327530600201400ustar00rootroot00000000000000/* SleepLib Event Class Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include "event.h" EventList::EventList(EventListType et, EventDataType gain, EventDataType offset, EventDataType min, EventDataType max, double rate, bool second_field) : m_type(et), m_gain(gain), m_offset(offset), m_min(min), m_max(max), m_rate(rate), m_second_field(second_field) { m_first = m_last = 0; m_count = 0; if (min == max) { // Update Min & Max unless forceably set here.. m_update_minmax = true; m_min2 = m_min = 999999999.0F; m_max2 = m_max = -999999999.0F; } else { m_update_minmax = false; } m_data.reserve(2048); // Reserve a few to increase performace?? } void EventList::clear() { m_min2 = m_min = 999999999.0F; m_max2 = m_max = -999999999.0F; m_update_minmax = true; m_first = m_last = 0; m_count = 0; m_data.clear(); m_data2.clear(); m_time.clear(); } qint64 EventList::time(quint32 i) const { if (m_type == EVL_Event) { return m_first + qint64(m_time[i]); } return m_first + qint64((EventDataType(i) * m_rate)); } EventDataType EventList::data(quint32 i) { return EventDataType(m_data[i]) * m_gain; } EventDataType EventList::data2(quint32 i) { return EventDataType(m_data2[i]); } static QString ts(qint64 msecs) { // TODO: make this UTC so that tests don't vary by where they're run return QDateTime::fromMSecsSinceEpoch(msecs).toString(Qt::ISODate); //FIXME? LocalTime? } void EventList::AddEvent(qint64 time, EventStoreType data) { // Apply gain & offset EventDataType val = EventDataType(data) * m_gain; // ignoring m_offset if (m_update_minmax) { if (m_count == 0) { m_max = m_min = val; } else { m_min = (val < m_min) ? val : m_min; m_max = (val > m_max) ? val : m_max; } } if (!m_first) { m_first = time; m_last = time; } if (m_first > time) { // Crud.. Update all the previous records // This really shouldn't happen. qDebug() << "Unordered time detected in AddEvent()" << m_count << ts(m_first) << ts(time) << data; qint32 delta = (m_first - time); for (quint32 i = 0; i < m_count; ++i) { m_time[i] += delta; } m_first = time; } if (m_last < time) { m_last = time; } quint32 delta = (time - m_first); m_data.push_back(data); m_time.push_back(delta); m_count++; } void EventList::AddEvent(qint64 time, EventStoreType data, EventStoreType data2) { AddEvent(time, data); if (!m_second_field) return; m_min2 = (data2 < m_min2) ? data2 : m_min2; m_max2 = (data2 > m_max2) ? data2 : m_max2; m_data2.push_back(data2); } // Adds a consecutive waveform chunk void EventList::AddWaveform(qint64 start, qint16 *data, int recs, qint64 duration) { if (m_type != EVL_Waveform) { qWarning() << "Attempted to add waveform data to non-waveform object"; return; } if (!m_rate) { qWarning() << "Attempted to add waveform without setting sample rate"; return; } qint64 last = start + duration; if (!m_first) { m_first = start; m_last = last; } if (m_last > start) { //qWarning() << "Attempted to add waveform with previous timestamp"; // return; // technically start should equal m_last+1 sample.. check this too. } if (m_last < last) { m_last = last; } // TODO: Check waveform chunk really is contiguous //double rate=duration/recs; //realloc buffers. int r = m_count; m_count += recs; m_data.resize(m_count); // EventStoreType *edata = m_data.data(); //EventStoreType raw; const qint16 *sp = data; const qint16 *ep = data + recs; EventStoreType *dp = (EventStoreType *)m_data.data()+r; // EventStoreType *dp = &edata[r]; if (m_update_minmax) { EventDataType min = m_min, max = m_max, val, gain = m_gain; memcpy(dp, sp, recs*2); for (sp = data; sp < ep; ++sp) { // *dp++ = raw = *sp; val = EventDataType(*sp) * gain + m_offset; if (min > val) { min = val; } if (max < val) { max = val; } } m_min = min; m_max = max; } else { //register EventDataType val,gain=m_gain; for (int i=0; i < recs; ++i) { m_data[i] = *sp++; } // for (sp = data; sp < ep; ++sp) { // *dp++ = *sp; // //val=EventDataType(raw)*gain; // } } } void EventList::AddWaveform(qint64 start, unsigned char *data, int recs, qint64 duration) { if (m_type != EVL_Waveform) { qWarning() << "Attempted to add waveform data to non-waveform object"; return; } if (!m_rate) { qWarning() << "Attempted to add waveform without setting sample rate"; return; } // duration=recs*rate; qint64 last = start + duration; if (!m_first) { m_first = start; m_last = last; } if (m_last > start) { //qWarning() << "Attempted to add waveform with previous timestamp"; // return; // technically start should equal m_last+1 sample.. check this too. } if (m_last < last) { m_last = last; } // TODO: Check waveform chunk really is contiguos //realloc buffers. int r = m_count; m_count += recs; m_data.resize(m_count); EventStoreType *edata = m_data.data(); EventStoreType raw; EventDataType val; unsigned char *sp; unsigned char *ep = data + recs; EventStoreType *dp = &edata[r]; if (m_update_minmax) { // ignoring m_offset for (sp = data; sp < ep; ++sp) { raw = *sp; val = EventDataType(raw) * m_gain; if (m_min > val) { m_min = val; } if (m_max < val) { m_max = val; } *dp++ = raw; } } else { for (sp = data; sp < ep; ++sp) { raw = *sp; //val = EventDataType(raw) * m_gain; *dp++ = raw; } } } void EventList::AddWaveform(qint64 start, char *data, int recs, qint64 duration) { if (m_type != EVL_Waveform) { qWarning() << "Attempted to add waveform data to non-waveform object"; return; } if (!m_rate) { qWarning() << "Attempted to add waveform without setting sample rate"; return; } // duration=recs*rate; qint64 last = start + duration; if (!m_first) { m_first = start; m_last = last; } else { if (m_last > start) { //qWarning() << "Attempted to add waveform with previous timestamp"; //return; // technically start should equal m_last+1 sample.. check this too. } if (m_last < last) { m_last = last; } } // TODO: Check waveform chunk really is contiguos //realloc buffers. int r = m_count; m_count += recs; m_data.resize(m_count); EventStoreType *edata = m_data.data(); EventStoreType raw; EventDataType val; // FIXME: sstangl: accesses random memory char *sp; char *ep = data + recs; EventStoreType *dp = &edata[r]; if (m_update_minmax) { for (sp = data; sp < ep; ++sp) { raw = *sp; val = EventDataType(raw) * m_gain + m_offset; if (m_min > val) { m_min = val; } if (m_max < val) { m_max = val; } *dp++ = raw; } } else { for (sp = data; sp < ep; ++sp) { raw = *sp; // val = EventDataType(raw) * m_gain + m_offset; *dp++ = raw; } } } OSCAR-code-v1.3.1/oscar/SleepLib/event.h000066400000000000000000000156361417327530600176040ustar00rootroot00000000000000/* SleepLib Event Class Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef EVENT_H #define EVENT_H #include #include "machine_common.h" #ifdef UNITTEST_MODE #define private public #define protected public #endif //! \brief EventLists can either be Waveform or Event types enum EventListType { EVL_Waveform, EVL_Event }; /*! \class EventList \author Mark Watkins \brief EventLists contains waveforms at a specified rate, or a list of event and time data. */ class EventList { friend class Session; public: EventList(EventListType et, EventDataType gain = 1.0, EventDataType offset = 0.0, EventDataType min = 0.0, EventDataType max = 0.0, double rate = 0.0, bool second_field = false); //! \brief Wipe the event list so it can be reused void clear(); /*! \brief Add an event starting at time, containing data to this event list Note, data2 is only used if second_field is specified in the constructor */ void AddEvent(qint64 time, EventStoreType data); void AddEvent(qint64 time, EventStoreType data, EventStoreType data2); void AddWaveform(qint64 start, qint16 *data, int recs, qint64 duration); void AddWaveform(qint64 start, unsigned char *data, int recs, qint64 duration); void AddWaveform(qint64 start, char *data, int recs, qint64 duration); //! \brief Returns a count of records contained in this EventList inline quint32 count() const { return m_count; } //! \brief Manually sets a count of records contained in this EventList void setCount(quint32 count) { m_count = count; } //! \brief Returns a raw ("ungained") data value from index position i inline EventStoreType raw(int i) const { return m_data[i]; } //! \brief Returns a raw ("ungained") data2 value from index position i inline EventStoreType raw2(int i) const { return m_data2[i]; } //! \brief Returns a data value multiplied by gain from index position i EventDataType data(quint32 i); //! \brief Returns a data2 value multiplied by gain from index position i EventDataType data2(quint32 i); //! \brief Returns either the timestamp for the i'th event, or calculates the waveform time position i qint64 time(quint32 i) const; //! \brief Returns true if this EventList uses the second data field bool hasSecondField() { return m_second_field; } //! \brief Returns the first events/waveforms starting time in milliseconds since epoch inline qint64 first() const { return m_first; } //! \brief Returns the last events/waveforms ending time in milliseconds since epoch inline qint64 last() const { return m_last; } //! \brief Returns the timespan covered by this EventList, in milliseconds since epoch inline qint64 duration() { return m_last - m_first; } //! \brief Sets the first events/waveforms starting time in milliseconds since epoch void setFirst(qint64 val) { m_first = val; } //! \brief Sets the last events/waveforms ending time in milliseconds since epoch void setLast(qint64 val) { m_last = val; } //! \brief Set this EventList to either EVL_Waveform or EVL_Event type void setType(EventListType type) { m_type = type; } //! \brief Change the gain multiplier value void setGain(EventDataType v) { m_gain = v; } //! \brief Change the gain offset value void setOffset(EventDataType v) { m_offset = v; } //! \brief Set the Minimum value for data void setMin(EventDataType v) { m_min = v; } //! \brief Set the Maximum value for data void setMax(EventDataType v) { m_max = v; } //! \brief Set the Minimum value for data2 void setMin2(EventDataType v) { m_min2 = v; } //! \brief Set the Maximum value for data2 void setMax2(EventDataType v) { m_max2 = v; } //! \brief Set the sample rate void setRate(EventDataType v) { m_rate = v; } //void setCode(ChannelID id) { m_code=id; } //! \brief Return the Minimum data value inline EventDataType Min() { return m_min; } //! \brief Return the Maximum data value inline EventDataType Max() { return m_max; } //! \brief Return the Minimum data2 value inline EventDataType min2() { return m_min2; } //! \brief Return the Maximum data value inline EventDataType max2() { return m_max2; } //! \brief Return the gain value inline EventDataType gain() const { return m_gain; } //! \brief Return the gain offset inline EventDataType offset() { return m_offset; } //! \brief Return the sample rate inline EventDataType rate() { return m_rate; } //! \brief Return the EventList type, either EVL_Waveform or EVL_Event inline EventListType type() { return m_type; } //inline const ChannelID & code() { return m_code; } //! \brief Returns whether or not min/max values are updated while adding events inline const bool &update_minmax() { return m_update_minmax; } //! \brief Returns the dimension (units type) of the contained data object QString dimension() { return m_dimension; } //! \brief Sets the dimension (units type) of the contained data object void setDimension(QString dimension) { m_dimension = dimension; } //! \brief Returns the data storage vector QVector &getData() { return m_data; } //! \brief Returns the data2 storage vector QVector &getData2() { return m_data2; } //! \brief Returns the time storage vector (only used in EVL_Event types) QVector &getTime() { return m_time; } // Don't mess with these without considering the consequences void rawDataResize(quint32 i) { m_data.resize(i); m_count = i; } void rawData2Resize(quint32 i) { m_data2.resize(i); m_count = i; } void rawTimeResize(quint32 i) { m_time.resize(i); m_count = i; } EventStoreType *rawData() { return m_data.data(); } EventStoreType *rawData2() { return m_data2.data(); } quint32 *rawTime() { return m_time.data(); } protected: //! \brief The time storage vector, in 32bits delta format, added as offsets to m_first QVector m_time; //! \brief The "ungained" raw data storage vector QVector m_data; //! \brief The "ungained" raw data2 storage vector QVector m_data2; //ChannelID m_code; //! \brief Either EVL_Waveform or EVL_Event EventListType m_type; //! \brief Count of events quint32 m_count; EventDataType m_gain; EventDataType m_offset; EventDataType m_min, m_min2; EventDataType m_max, m_max2; EventDataType m_rate; // Waveform sample rate QString m_dimension; qint64 m_first, m_last; bool m_update_minmax; bool m_second_field; }; #endif // EVENT_H OSCAR-code-v1.3.1/oscar/SleepLib/importcontext.cpp000066400000000000000000000150301417327530600217210ustar00rootroot00000000000000/* SleepLib Import Context Implementation * * Copyright (c) 2021-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include "SleepLib/importcontext.h" ImportContext::ImportContext() : m_machine(nullptr) { } ImportContext::~ImportContext() { FlushUnexpectedMessages(); } void ImportContext::LogUnexpectedMessage(const QString & message) { m_logMutex.lock(); m_unexpectedMessages += message; m_logMutex.unlock(); } void ImportContext::FlushUnexpectedMessages() { if (m_unexpectedMessages.count() > 0 && m_machine) { // Compare this to the list of messages previously seen for this machine // and only alert if there are new ones. QSet newMessages = m_unexpectedMessages - m_machine->previouslySeenUnexpectedData(); if (newMessages.count() > 0) { emit importEncounteredUnexpectedData(m_machine->getInfo(), newMessages); m_machine->previouslySeenUnexpectedData() += newMessages; } } m_unexpectedMessages.clear(); } QString ImportContext::GetBackupPath() { Q_ASSERT(m_machine); return m_machine->getBackupPath(); } bool ImportContext::SessionExists(SessionID sid) { Q_ASSERT(m_machine); return m_machine->SessionExists(sid); } Session* ImportContext::CreateSession(SessionID sid) { Q_ASSERT(m_machine); return new Session(m_machine, sid); } bool ImportContext::AddSession(Session* session) { // Make sure the session will be saved. session->SetChanged(true); // Update indexes, process waveform and perform flagging. session->UpdateSummaries(); // Write the session file to disk. bool ok = session->Store(session->machine()->getDataPath()); if (!ok) { qWarning() << "Failed to store session" << session->session(); } // Unload the memory-intensive data now that it's written to disk. session->TrashEvents(); // TODO: Remove MachineLoader::addSession once all loaders use this. // Add the session to the database m_sessionMutex.lock(); m_sessions[session->session()] = session; m_sessionMutex.unlock(); return ok; } bool ImportContext::Commit() { bool ok = true; // TODO: Remove MachineLoader::finishAddingSessions once all loaders use this. // Using a map specifically so they are inserted in order. for (auto session : m_sessions) { bool added = session->machine()->AddSession(session); if (!added) { qWarning() << "Session" << session->session() << "was not addded"; ok = false; } } m_sessions.clear(); // TODO: Move what we can from finishCPAPImport into here, // e.g. Profile::StoreMachines and Machine::SaveSummaryCache. return ok; } ProfileImportContext::ProfileImportContext(Profile* profile) : m_profile(profile) { Q_ASSERT(m_profile); } bool ProfileImportContext::ShouldIgnoreOldSessions() { return m_profile->session->ignoreOlderSessions(); } QDateTime ProfileImportContext::IgnoreSessionsOlderThan() { return m_profile->session->ignoreOlderSessionsDate(); } Machine* ProfileImportContext::CreateMachineFromInfo(const MachineInfo & info) { if (m_machine) { // TODO: Ultimately a context will probably take MachineInfo as a constructor, // once all loaders fully populate MachineInfo prior to import. qWarning() << "ProfileImportContext::CreateMachineFromInfo called more than once for this context!"; } m_machineInfo = info; m_machine = m_profile->CreateMachine(m_machineInfo); return m_machine; } // MARK: - ImportUI::ImportUI(Profile* profile) : m_profile(profile) { Q_ASSERT(m_profile); } void ImportUI::onUnexpectedData(const MachineInfo & info, QSet & /*newMessages*/) { QMessageBox::information(QApplication::activeWindow(), QObject::tr("Untested Data"), QObject::tr("Your %1 %2 (%3) generated data that OSCAR has never seen before.").arg(info.brand).arg(info.model).arg(info.modelnumber) +"\n\n"+ QObject::tr("The imported data may not be entirely accurate, so the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly.") ,QMessageBox::Ok); } void ImportUI::onDeviceReportsUsageOnly(const MachineInfo & info) { if (m_profile->cpap->brickWarning()) { QApplication::processEvents(); QMessageBox::information(QApplication::activeWindow(), QObject::tr("Non Data Capable Machine"), QString(QObject::tr("Your %1 CPAP machine (Model %2) is unfortunately not a data capable model.").arg(info.brand).arg(info.modelnumber) +"\n\n"+ QObject::tr("I'm sorry to report that OSCAR can only track hours of use and very basic settings for this machine.")) ,QMessageBox::Ok); m_profile->cpap->setBrickWarning(false); } } void ImportUI::onDeviceIsUntested(const MachineInfo & info) { Machine* m = m_profile->CreateMachine(info); if (m_profile->session->warnOnUntestedMachine() && m->warnOnUntested()) { m->suppressWarnOnUntested(); // don't warn the user more than once QMessageBox::information(QApplication::activeWindow(), QObject::tr("Machine Untested"), QObject::tr("Your %1 CPAP machine (Model %2) has not been tested yet.").arg(info.brand).arg(info.modelnumber) +"\n\n"+ QObject::tr("It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching clinician .pdf reports to make sure it works with OSCAR.") ,QMessageBox::Ok); } } void ImportUI::onDeviceIsUnsupported(const MachineInfo & info) { QMessageBox::information(QApplication::activeWindow(), QObject::tr("Machine Unsupported"), QObject::tr("Sorry, your %1 CPAP machine (%2) is not supported yet.").arg(info.brand).arg(info.modelnumber) +"\n\n"+ QObject::tr("The developers need a .zip copy of this machine's SD card and matching clinician .pdf reports to make it work with OSCAR.") ,QMessageBox::Ok); } OSCAR-code-v1.3.1/oscar/SleepLib/importcontext.h000066400000000000000000000077131417327530600213770ustar00rootroot00000000000000/* SleepLib Import Context Header * * Copyright (c) 2021-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef IMPORTCONTEXT_H #define IMPORTCONTEXT_H #include "SleepLib/machine_loader.h" class ImportContext : public QObject { Q_OBJECT public: ImportContext(); virtual ~ImportContext(); // Loaders will call this directly. It manages the machine's stored set of previously seen messages // and will emit an importEncounteredUnexpectedData signal in its dtor if any are new. void LogUnexpectedMessage(const QString & message); signals: void importEncounteredUnexpectedData(const MachineInfo & info, QSet & newMessages); public: // Emit the importEncounteredUnexpectedData signal if there are any new messages and clear the list. // TODO: This will no longer need to be public once a context doesn't get reused between machines. void FlushUnexpectedMessages(); virtual bool ShouldIgnoreOldSessions() { return false; } virtual QDateTime IgnoreSessionsOlderThan() { return QDateTime(); } // TODO: Isolate the Machine object from the loader rather than returning it. virtual Machine* CreateMachineFromInfo(const MachineInfo & info) = 0; // TODO: Eventually backup (and rebuild) should be handled invisibly to loaders. virtual QString GetBackupPath(); virtual bool SessionExists(SessionID sid); // Create an in-memory Session object for the importer to fill out. virtual Session* CreateSession(SessionID sid); // Write the session to disk and release its memory, adding it to the queue to be committed. virtual bool AddSession(Session* session); // Update the database to include all the newly added sessions. virtual bool Commit(); protected: QMutex m_logMutex; QSet m_unexpectedMessages; MachineInfo m_machineInfo; Machine* m_machine; QMutex m_sessionMutex; QMap m_sessions; }; // Loaders and parsers #define IMPORT_CTX and SESSIONID based on their internal data structures. #define UNEXPECTED_VALUE(SRC, VALS) { \ QString message = QString("%1:%2: %3 = %4 != %5").arg(__func__).arg(__LINE__).arg(#SRC).arg(SRC).arg(VALS); \ qWarning() << SESSIONID << message; \ IMPORT_CTX->LogUnexpectedMessage(message); \ } #define CHECK_VALUE(SRC, VAL) if ((SRC) != (VAL)) UNEXPECTED_VALUE(SRC, VAL) #define CHECK_VALUES(SRC, VAL1, VAL2) if ((SRC) != (VAL1) && (SRC) != (VAL2)) UNEXPECTED_VALUE(SRC, #VAL1 " or " #VAL2) // For more than 2 values, just write the test manually and use UNEXPECTED_VALUE if it fails. #define HEX(SRC) { qWarning() << SESSIONID << QString("%1:%2: %3 = %4").arg(__func__).arg(__LINE__).arg(#SRC).arg((SRC & 0xFF), 2, 16, QChar('0')); } // ProfileImportContext isolates the loader from Profile and its storage. class Profile; class ProfileImportContext : public ImportContext { Q_OBJECT public: ProfileImportContext(Profile* profile); virtual ~ProfileImportContext() {}; virtual bool ShouldIgnoreOldSessions(); virtual QDateTime IgnoreSessionsOlderThan(); virtual Machine* CreateMachineFromInfo(const MachineInfo & info); protected: Profile* m_profile; }; // TODO: Add a TestImportContext that writes the data to YAML for regression tests. // TODO: Once all loaders support context and UI, move this into the main application // and refactor its import UI logic into this class. class ImportUI : public QObject { Q_OBJECT public: ImportUI(Profile* profile); virtual ~ImportUI() {} public slots: void onUnexpectedData(const MachineInfo & machine, QSet & newMessages); void onDeviceReportsUsageOnly(const MachineInfo & info); void onDeviceIsUntested(const MachineInfo & info); void onDeviceIsUnsupported(const MachineInfo & info); protected: Profile* m_profile; }; #endif // IMPORTCONTEXT_H OSCAR-code-v1.3.1/oscar/SleepLib/journal.cpp000066400000000000000000000254741417327530600204710ustar00rootroot00000000000000/* SleepLib Journal Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "journal.h" #include "machine_common.h" #include #include #include #include #include #include #include const int journal_data_version = 1; JournalEntry::JournalEntry(QDate date) { Machine * jmach = p_profile->GetMachine(MT_JOURNAL); if (jmach == nullptr) { // Create Journal machine record if it doesn't already exist MachineInfo info(MT_JOURNAL,0, "Journal", QObject::tr("Journal Data"), QString(), QString(), QString(), QString("OSCAR"), QDateTime::currentDateTime(), journal_data_version); // Using machine ID 1 rather than a random number, so in future, if profile.xml gets screwed up they'll get their data back.. // TODO: Perhaps search for unlinked journal folders here to save some anger and frustration? :P MachineID machid = 1; QString path = p_profile->Get("{" + STR_GEN_DataFolder + "}"); QDir dir(path); QStringList filters; filters << "Journal_*"; QStringList dirs = dir.entryList(filters,QDir::Dirs); int journals = dirs.size(); if (journals > 0) { QString tmp = dirs[0].section("_", -1); bool ok; machid = tmp.toUInt(&ok, 16); if (!ok) { QMessageBox::warning(nullptr, STR_MessageBox_Warning, QObject::tr("OSCAR found an old Journal folder, but it looks like it's been renamed:")+"\n\n"+ QString("%1").arg(dirs[0])+ QObject::tr("OSCAR will not touch this folder, and will create a new one instead.")+"\n\n"+ QObject::tr("Please be careful when playing in OSCAR's profile folders :-P"), QMessageBox::Ok); // User renamed the folder.. report this machid = 1; } if (journals > 1) { QMessageBox::warning(nullptr, STR_MessageBox_Warning, QObject::tr("For some reason, OSCAR couldn't find a journal object record in your profile, but did find multiple Journal data folders.\n\n")+ QObject::tr("OSCAR picked only the first one of these, and will use it in future:\n\n")+ QString("%1").arg(dirs[0])+ QObject::tr("If your old data is missing, copy the contents of all the other Journal_XXXXXXX folders to this one manually."), QMessageBox::Ok); // more then one.. report this. } } jmach = p_profile->CreateMachine(info, machid); } m_date = date; session = nullptr; day = p_profile->GetDay(date, MT_JOURNAL); if (day != nullptr) { session = day->firstSession(MT_JOURNAL); } else { // Doesn't exist.. create a new one.. session = new Session(jmach,0); qint64 st,et; QDateTime dt(date,QTime(22,0)); // 10pm localtime st=qint64(dt.toTime_t())*1000L; et=st+3600000L; session->set_first(st); session->set_last(et); // Let it live in memory...but not on disk unless data is changed... jmach->AddSession(session, true); // and where does day get set??? does day actually need to be set?? day = p_profile->GetDay(date, MT_JOURNAL); } } JournalEntry::~JournalEntry() { if (session && session->IsChanged()) { Save(); } } bool JournalEntry::Save() { if (session && session->IsChanged()) { qDebug() << "Saving journal session for" << m_date; // just need to write bookmarks, the rest are already stored in the session QVariantList start; QVariantList end; QStringList notes; int size = bookmarks.size(); for (int i=0; isettings[Bookmark_Start] = start; session->settings[Bookmark_End] = end; session->settings[Bookmark_Notes] = notes; session->settings[LastUpdated] = QDateTime::currentDateTime().toTime_t(); session->StoreSummary(); return true; } return false; } QString JournalEntry::notes() { QHash::iterator it; if (session && ((it=session->settings.find(Journal_Notes)) != session->settings.end())) { return it.value().toString(); } return QString(); } void JournalEntry::setNotes(QString notes) { if (!session) return; session->settings[Journal_Notes] = notes; session->SetChanged(true); } EventDataType JournalEntry::weight() { QHash::iterator it; if (session && ((it = session->settings.find(Journal_Weight)) != session->settings.end())) { return it.value().toFloat(); } return 0; } void JournalEntry::setWeight(EventDataType weight) { if (!session) return; session->settings[Journal_Weight] = weight; session->SetChanged(true); } int JournalEntry::zombie() { QHash::iterator it; if (session && ((it = session->settings.find(Journal_ZombieMeter)) != session->settings.end())) { return it.value().toFloat(); } return 0; } void JournalEntry::setZombie(int zombie) { if (!session) return; session->settings[Journal_ZombieMeter] = zombie; session->SetChanged(true); } QList & JournalEntry::getBookmarks() { bookmarks.clear(); if (!session || !session->settings.contains(Bookmark_Start)) { return bookmarks; } QVariantList start=session->settings[Bookmark_Start].toList(); QVariantList end=session->settings[Bookmark_End].toList(); QStringList notes=session->settings[Bookmark_Notes].toStringList(); int size = start.size(); for (int i=0; i < size; ++i) { bookmarks.append(Bookmark(start.at(i).toLongLong(), end.at(i).toLongLong(), notes.at(i))); } return bookmarks; } void JournalEntry::addBookmark(qint64 start, qint64 end, QString note) { bookmarks.append(Bookmark(start,end,note)); session->SetChanged(true); } void JournalEntry::delBookmark(qint64 start, qint64 end) { bool removed; do { removed = false; int size = bookmarks.size(); for (int i=0; iSetChanged(true); // make sure it gets saved later.. removed=true; break; } } } while (removed); // clean up any stupid duplicates just in case.. :P // if I wanted to be nice above, I could add the note string to the search as well.. // (some users might be suprised to see the lot go with the same start and end index) } void BackupJournal(QString filename) { QString outBuf; QXmlStreamWriter stream(&outBuf); stream.setAutoFormatting(true); stream.setAutoFormattingIndent(2); stream.writeStartDocument(); // stream.writeProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""); stream.writeStartElement("OSCAR"); stream.writeStartElement("Journal"); stream.writeAttribute("username", p_profile->user->userName()); QDate first = p_profile->FirstDay(MT_JOURNAL); QDate last = p_profile->LastDay(MT_JOURNAL); QDate date = first.addDays(-1); do { date = date.addDays(1); Day * journal = p_profile->GetDay(date, MT_JOURNAL); if (!journal) continue; Session * sess = journal->firstSession(MT_JOURNAL); if (!sess) continue; if ( !journal->settingExists(Journal_Notes) && !journal->settingExists(Journal_Weight) && !journal->settingExists(Journal_ZombieMeter) && !journal->settingExists(LastUpdated) && !journal->settingExists(Bookmark_Start)) { continue; } stream.writeStartElement("day"); stream.writeAttribute("date", date.toString()); if (journal->settingExists(Journal_Weight)) { QString weight = sess->settings[Journal_Weight].toString(); stream.writeAttribute("weight", weight); } if (journal->settingExists(Journal_ZombieMeter)) { QString zombie = sess->settings[Journal_ZombieMeter].toString(); stream.writeAttribute("zombie", zombie); } if (journal->settingExists(LastUpdated)) { QDateTime dt = sess->settings[LastUpdated].toDateTime(); #if QT_VERSION < QT_VERSION_CHECK(5,8,0) qint64 dtx = dt.toMSecsSinceEpoch()/1000L; #else qint64 dtx = dt.toSecsSinceEpoch(); #endif QString dts = QString::number(dtx); stream.writeAttribute("lastupdated", dts); } if (journal->settingExists(Journal_Notes)) { stream.writeStartElement("note"); stream.writeTextElement("text", sess->settings[Journal_Notes].toString()); stream.writeEndElement(); // notes } if (journal->settingExists(Bookmark_Start)) { QVariantList start=sess->settings[Bookmark_Start].toList(); QVariantList end=sess->settings[Bookmark_End].toList(); QStringList notes=sess->settings[Bookmark_Notes].toStringList(); stream.writeStartElement("bookmarks"); int size = start.size(); for (int i=0; i< size; i++) { stream.writeStartElement("bookmark"); stream.writeAttribute("notes",notes.at(i)); stream.writeAttribute("start",start.at(i).toString()); stream.writeAttribute("end",end.at(i).toString()); stream.writeEndElement(); // bookmark } stream.writeEndElement(); // bookmarks } stream.writeEndElement(); // day } while (date <= last); stream.writeEndElement(); // Journal stream.writeEndElement(); // OSCAR stream.writeEndDocument(); QFile file(filename); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "Couldn't open journal file" << filename << "error code" << file.error() << file.errorString(); return; } QTextStream ts(&file); ts.setCodec("UTF-8"); ts.setGenerateByteOrderMark(true); ts << outBuf; file.close(); } DayController::DayController() { journal = nullptr; cpap = nullptr; oximeter = nullptr; } DayController::~DayController() { delete journal; } void DayController::setDate(QDate date) { if (journal) { delete journal; } journal = new JournalEntry(date); } OSCAR-code-v1.3.1/oscar/SleepLib/journal.h000066400000000000000000000030471417327530600201260ustar00rootroot00000000000000/* SleepLib Journal Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef JOURNAL_H #define JOURNAL_H #include "SleepLib/profiles.h" void BackupJournal(QString filename); class Bookmark { public: Bookmark() { start = end = 0; } Bookmark(const Bookmark & copy) { start = copy.start; end = copy.end; notes = copy.notes; } Bookmark(qint64 start, qint64 end, QString notes): start(start), end(end), notes(notes) {} qint64 start; qint64 end; QString notes; }; class JournalEntry { public: JournalEntry(QDate date); ~JournalEntry(); bool Save(); QString notes(); void setNotes(QString notes); EventDataType weight(); void setWeight(EventDataType weight); int zombie(); void setZombie(int zombie); QList & getBookmarks(); void addBookmark(qint64 start, qint64 end, QString note); void delBookmark(qint64 start, qint64 end); protected: QDate m_date; QList bookmarks; Day * day; Session * session; bool newsession; }; void BackupJournal(QString filename); class DayController { DayController(); ~DayController(); void setDate(QDate date); QDate m_date; JournalEntry * journal; Day * cpap; Day * oximeter; }; #endif // JOURNAL_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/000077500000000000000000000000001417327530600213065ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/cms50_loader.cpp000066400000000000000000000473401417327530600242770ustar00rootroot00000000000000/* SleepLib CMS50X Loader Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ //******************************************************************************************** // Please only INCREMENT the cms50_data_version in cms50_loader.h when making changes // that change loader behaviour or modify channels in a manner that fixes old data imports. // Note that changing the data version will require a reimport of existing data for which OSCAR // does not keep a backup - so it should be avoided if possible. // i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; #include "cms50_loader.h" #include "SleepLib/machine.h" #include "SleepLib/session.h" CMS50Loader::CMS50Loader() { m_type = MT_OXIMETER; m_abort = false; m_streaming = false; m_importing = false; imp_callbacks = 0; m_vendorID = 0x10c4; m_productID = 0xea60; cms50dplus = false; oxirec = nullptr; startTimer.setParent(this); resetTimer.setParent(this); } CMS50Loader::~CMS50Loader() { } bool CMS50Loader::Detect(const QString &path) { if (p_profile->oxi->oximeterType() == 1) { return true; } Q_UNUSED(path); return false; } int CMS50Loader::Open(const QString & path) { // Only one active Oximeter module at a time, set in preferences m_itemCnt = 0; m_itemTotal = 0; m_abort = false; m_importing = false; started_import = false; started_reading = false; finished_import = false; setStatus(NEUTRAL); imp_callbacks = 0; cb_reset = 0; m_time.start(); if (oxirec) { trashRecords(); } // Cheating using path for two serial oximetry modes if (path.compare("import") == 0) { for (int i=0; i<5; ++i) { resetDevice(); serial.flush(); // QThread::msleep(50); QApplication::processEvents(); } serial.clear(); setStatus(IMPORTING); startTimer.stop(); startImportTimeout(); return 1; } else if (path.compare("live") == 0) { for (int i=0; i<5; ++i) { resetDevice(); serial.flush(); QApplication::processEvents(); } serial.clear(); m_startTime = QDateTime::currentDateTime(); oxirec = new QVector; oxisessions[m_startTime] = oxirec; setStatus(LIVE); return 1; } QString ext = path.section(".", -1); // find the last '.' if ((ext.compare("spo2", Qt::CaseInsensitive)==0) || (ext.compare("spo", Qt::CaseInsensitive)==0) || (ext.compare("spor", Qt::CaseInsensitive)==0)) { // try to read and process SpoR file.. return readSpoRFile(path) ? 1 : 0; } return 0; } void CMS50Loader::processBytes(QByteArray bytes) { // Sync to start of message type we are interested in quint8 c; quint8 msgcode = 0x80; int idx=0; int bytesread = bytes.size(); while ((idx < bytesread) && (((c=(quint8)bytes.at(idx)) & msgcode)!=msgcode)) { if (buffer.length()>0) { // If buffer is the start of a valid but short frame, add to it.. buffer.append(c); }// otherwise dump these bytes, as they are out of sequence. ++idx; } // Copy the rest to the buffer. buffer.append(bytes.mid(idx)); int available = buffer.length(); switch (status()) { case IMPORTING: idx = doImportMode(); break; case LIVE: idx = doLiveMode(); break; default: ; // qDebug() << "Device mode not supported by" << loaderName(); } if (idx >= available) { buffer.clear(); } else if (idx > 0) { // Trim any processed bytes from the buffer. buffer = buffer.mid(idx); } if (buffer.length() > 0) { // If what's left doesn't start with a marker bit, dump it if (((unsigned char)buffer.at(0) & 0x80) != 0x80) { buffer.clear(); } } } int CMS50Loader::doImportMode() { int available = buffer.size(); int hour,minute; int idx = 0; while (idx < available) { unsigned char c=(unsigned char)buffer.at(idx); if (!started_import) { // There are three [0xf2 0xXX 0xXX] trio's at start of recording // Followed by [0xfX 0xXX 0xXX] trios containing spo2 and pulse till the end of recording // Scan for first header trio starting byte. if (c != 0xf2) { idx++; continue; } // sometimes a f2 starting trio can be corrupted by live data // peek ahead and see where the f2 headers are int f2cnt = 0; int f2idx[3] = {-1}; for (int i=0; i < 30; ++i) { if ((idx + i) >= available) { qDebug() << "Not enough bytes to read CMS50 headers"; break; } c = (unsigned char)buffer.at(idx+i); if (c == 0xf2) { f2idx[f2cnt++] = idx+i; if (f2cnt >= 3) break; // got all 3 headers // Skip the following two bytes i += 2; } } if (f2cnt<3) { qDebug() << "Did not get all header Trio's"; } f2cnt--; // CHECK: Check there might be length data after the last header trio.. received_bytes=0; bool badheader = false; // Look for the best of three headers trios int bestf2 = 0; if ((f2cnt >= 1) && ((f2idx[1] - f2idx[0]) == 3)) { bestf2 = f2idx[0]; } else if ((f2cnt >= 2) && ((f2idx[2] - f2idx[1]) == 3)) { bestf2 = f2idx[1]; } else { bestf2 = f2idx[f2cnt]; // ouch.. check if f0 starts afterwards if (((unsigned char)buffer.at(bestf2+3) & 0xf0) != 0xf0) { // crap.. bad time badheader = true; } } if (!badheader) { hour = (unsigned char)buffer.at(bestf2 + 1) & 0x7f; minute = (unsigned char)buffer.at(bestf2 + 2) & 0x7f; } else { hour = 0; minute = 0; } // Either a CMS50D+, has a bad header, or it's really midnight, set a flag anyway for later to help choose the right sync time cms50dplus = (hour == 0) && (minute == 0); MachineInfo info = newInfo(); info.model = cms50dplus ? QObject::tr("CMS50D+") : QObject::tr("CMS50E/F"); info.serial = QString(); Machine * mach = p_profile->CreateMachine(info); Q_UNUSED(mach); qDebug() << QString("Receiving Oximeter transmission %1:%2").arg(hour).arg(minute); // set importing to true or whatever.. finished_import = false; started_import = true; started_reading = false; m_importing = true; m_itemCnt=0; m_itemTotal=5000; killTimers(); qDebug() << "Getting ready for import"; oxirec = new QVector; oxirec->reserve(30000); QDate oda=QDate::currentDate(); QTime oti=QTime(hour,minute); // Only CMS50E/F's have a realtime clock. CMS50D+ will set this to midnight // If the oximeter record time is more than the current time, then assume it was from the day before // Or should I use split time preference instead??? Foggy Confusements.. if (oti > QTime::currentTime()) { oda = oda.addDays(-1); } m_startTime = QDateTime(oda,oti); oxisessions[m_startTime] = oxirec; qDebug() << "Session start (according to CMS50)" << m_startTime << hex << buffer.at(idx + 1) << buffer.at(idx + 2) << ":" << dec << hour << minute ; cb_reset = 1; // CMS50D+ needs an end timer because it just stops dead after uploading resetTimer.singleShot(2000,this,SLOT(resetImportTimeout())); QStringList data; int len = f2idx[f2cnt]+3; for (int i=idx; i < len; ++i) { data.append(QString::number((unsigned char)buffer.at(i),16)); } qDebug() << "CMS50 Record Header bytes:" << data.join(","); idx = len; // peek ahead to see if there really is data length bytes.. data.clear(); for (int i=0; i < 12; ++i) { if ((idx+i) > available) break; data.push_back(QString::number((unsigned char)buffer.at(idx+i), 16)); } qDebug() << "bytes directly following header trio's:" << data.join(","); } else { // have started import if ((c & 0xf0) == 0xf0) { // Data trio started_reading=true; // Sometimes errornous crap is sent after data rec header // Recording import if ((idx + 2) >= available) { return idx; } quint8 pulse=(unsigned char)((buffer.at(idx + 1) & 0x7f) | ((c & 1) << 7)); quint8 spo2=(unsigned char)buffer.at(idx + 2) & 0xff; oxirec->append(OxiRecord(pulse,spo2)); received_bytes+=3; // TODO: Store the data to the session emit updateProgress(0, 0); idx += 3; } else if (!started_reading) { // have not got a valid trio yet, skip... idx += 1; } else { // scan ahead for another 0xf0 in case it's corrupted.. bool resync = false; for (int i=idx; i < available; ++i) { c=(unsigned char)buffer.at(i); if ((c & 0xf0) == 0xf0) { idx = i; resync = true; break; } } if (!resync) { // Data transfer has completed finished_import = true; killTimers(); m_importing = false; m_status = NEUTRAL; emit importComplete(this); resetTimer.singleShot(2000, this, SLOT(shutdownPorts())); return available; } } } } if (!started_import) { imp_callbacks = 0; } else { imp_callbacks++; } return idx; } int CMS50Loader::doLiveMode() { if (oxirec == nullptr) { qWarning() << "CMS50Loader::doLiveMode() called when null oxirec object"; return 0; } int available = buffer.size(); int idx = 0; QByteArray plethy; while (idx < available-5) { if (((unsigned char)buffer.at(idx) & 0x80) != 0x80) { idx++; continue; } int pwave=(unsigned char)buffer.at(idx + 1); int pbeat=(unsigned char)buffer.at(idx + 2); int pulse=((unsigned char)buffer.at(idx + 3) & 0x7f) | ((pbeat & 0x40) << 1); int spo2=(unsigned char)buffer.at(idx + 4) & 0x7f; oxirec->append(OxiRecord(pulse, spo2)); plethy.append(pwave); idx += 5; } emit updatePlethy(plethy); return idx; } void CMS50Loader::resetDevice() // Switch CMS50D+ device to live streaming mode { //qDebug() << "Sending reset code to CMS50 device"; //m_port->flush(); static unsigned char b1[3]={0xf6,0xf6,0xf6}; if (serial.write((char *)b1,3) == -1) { qDebug() << "Couldn't write data reset bytes to CMS50"; } QApplication::processEvents(); } void CMS50Loader::requestData() // Switch CMS50D+ device to record transmission mode { static unsigned char b1[2]={0xf5,0xf5}; //qDebug() << "Sending request code to CMS50 device"; if (serial.write((char *)b1,2) == -1) { qDebug() << "Couldn't write data request bytes to CMS50"; } QApplication::processEvents(); } void CMS50Loader::killTimers() { startTimer.stop(); resetTimer.stop(); } void CMS50Loader::startImportTimeout() { if (!m_streaming) return; if (started_import) { return; } if (finished_import != false) { qWarning() << "CMS50Loader::startImportTimeout() called when finished_import != false"; return; } //qDebug() << "Starting oximeter import timeout"; // Wait until events really are jammed on the CMS50D+ before re-requesting data. const int delay = 500; if (m_abort) { m_streaming = false; closeDevice(); return; } if (imp_callbacks == 0) { // Frozen, but still hasn't started? m_itemCnt = m_time.elapsed(); if (m_itemCnt > START_TIMEOUT) { // Give up after START_TIMEOUT closeDevice(); abort(); QMessageBox::warning(nullptr, STR_MessageBox_Error, "

"+tr("Could not get data transmission from oximeter.")+"

"+tr("Please ensure you select 'upload' from the oximeter devices menu.")+"

"); return; } else { // Note: Newer CMS50 devices transmit from user input, but there is no way of differentiating between models requestData(); } emit updateProgress(m_itemCnt, START_TIMEOUT); // Schedule another callback to make sure it's started startTimer.singleShot(delay, this, SLOT(startImportTimeout())); } } void CMS50Loader::resetImportTimeout() { if (finished_import) { return; } if (imp_callbacks != cb_reset) { // Still receiving data.. reset timer qDebug() << "Still receiving data in resetImportTimeout()" << imp_callbacks << cb_reset; if (resetTimer.isActive()) resetTimer.stop(); if (!finished_import) resetTimer.singleShot(2000, this, SLOT(resetImportTimeout())); } else { qDebug() << "Oximeter device stopped transmitting.. Transfer complete"; // We were importing, but now are done if (!finished_import && (started_import && started_reading)) { qDebug() << "Switching CMS50 back to live mode and finalizing import"; // Turn back on live streaming so the end of capture can be dealt with resetTimer.stop(); resetDevice(); // Send Reset to CMS50D+ serial.flush(); QThread::msleep(200); resetDevice(); // Send Reset to CMS50D+ serial.flush(); serial.clear(); //started_import = false; // finished_import = true; //m_streaming=false; //closeDevice(); //emit transferComplete(); //doImportComplete(); return; } qDebug() << "Should CMS50 resetImportTimeout reach here?"; // else what??? } cb_reset = imp_callbacks; } void CMS50Loader::shutdownPorts() { closeDevice(); } bool CMS50Loader::readSpoRFile(QString path) { QFile file(path); if (!file.exists()) { qWarning() << "Can't find the oximeter file: " << path; QMessageBox::warning(nullptr, STR_MessageBox_Error, "

"+tr("Could not find the oximeter file:")+"

"+path+"

"); return false; } if (!file.open(QFile::ReadOnly)) { qWarning() << "Can't open the oximeter file: " << path; QMessageBox::warning(nullptr, STR_MessageBox_Error, "

"+tr("Could not open the oximeter file:")+"

"+path+"

"); return false; } bool spo2header = false; QString ext = path.section('.', -1); qDebug() << "Oximeter file extention is " << ext; if (ext.compare("spo2",Qt::CaseInsensitive) == 0) { spo2header = true; qDebug() << "Oximeter file looks like an SpO2 type" ; } QByteArray data; qint64 filesize = file.size(); data = file.readAll(); QDataStream in(data); in.setByteOrder(QDataStream::LittleEndian); quint16 pos; in >> pos; in.skipRawData(pos - 2); //long size = data.size(); int bytes_per_record = 2; if (!spo2header) { // next is 0x0002 // followed by 16bit duration in seconds // Read date and time (it's a 16bit charset) char dchr[20]; int j = 0; for (int i = 0; i < 18 * 2; i += 2) { dchr[j++] = data.at(8 + i); } dchr[j] = 0; if (dchr[0]) { QString dstr(dchr); // Ensure date is correct first to ensure DST is handled correctly QDate date = QDate::fromString(dstr.left(8),"MM/dd/yy"); QTime time = QTime::fromString(dstr.right(8),"HH:mm:ss"); if (date.year() < 2000) { date = date.addYears(100); } m_startTime = QDateTime(date, time); } else { m_startTime = QDateTime(QDate::currentDate(), QTime(0,0,0)); cms50dplus = true; } } else { // it is an spo2header quint32 samples = 0; // number of samples quint32 year, month, day; quint32 hour, minute, second; if (data.at(pos) != 1) { qWarning() << "oximeter file" << path << "might be odd format"; } // Unknown cruft header... in.skipRawData(200); in >> year >> month >> day; in >> hour >> minute >> second; if ( year == 0 ) { // typically from a CMS50D+ m_startTime = QDateTime(QDate::currentDate(), QTime(hour, minute, second)); cms50dplus = true; } else m_startTime = QDateTime(QDate(year, month, day), QTime(hour, minute, second)); // ignoring it for now pos += 0x1c + 200; in >> samples; int remainder = filesize - pos; bytes_per_record = remainder / samples; qDebug() << samples << "samples of" << bytes_per_record << "bytes each"; // CMS50I .spo2 data have 4 bytes: a 16bit, followed by spo2 then pulse } oxirec = new QVector; oxisessions[m_startTime] = oxirec; unsigned char o2, pr; quint16 un; // Read all Pulse and SPO2 data do { if (bytes_per_record > 2) { in >> un; } in >> o2; in >> pr; if ((o2 == 0x7f) && (pr == 0xff)) { o2 = pr = 0; un = 0; } if (spo2header) { oxirec->append(OxiRecord(pr, o2)); } else { oxirec->append(OxiRecord(o2, pr)); } } while (!in.atEnd()); // for (int i = pos; i < size - 2;) { // o2 = (unsigned char)(data.at(i + 1)); // pr = (unsigned char)(data.at(i + 0)); // oxirec->append(OxiRecord(pr, o2)); // i += 2; // } // processing gets done later return true; } void CMS50Loader::process() { // Just clean up any extra crap before oximeterimport parses the oxirecords.. return; // if (!oxirec) // return; // int size=oxirec->size(); // if (size<10) // return; } static bool cms50_initialized = false; void CMS50Loader::Register() { if (cms50_initialized) { return; } qDebug() << "Registering CMS50Loader"; RegisterLoader(new CMS50Loader()); cms50_initialized = true; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/cms50_loader.h000066400000000000000000000041041417327530600237330ustar00rootroot00000000000000/* SleepLib CMS50X Loader Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef CMS50LOADER_H #define CMS50LOADER_H #include "SleepLib/serialoximeter.h" const QString cms50_class_name = "CMS50"; const int cms50_data_version = 4; /*! \class CMS50Loader \brief Importer for CMS50 Oximeter */ class CMS50Loader : public SerialOximeter { Q_OBJECT public: CMS50Loader(); virtual ~CMS50Loader(); virtual bool Detect(const QString &path); virtual int Open(const QString & path); static void Register(); virtual int Version() { return cms50_data_version; } virtual const QString &loaderName() { return cms50_class_name; } virtual MachineInfo newInfo() { return MachineInfo(MT_OXIMETER, 0, cms50_class_name, QObject::tr("Contec"), QObject::tr("CMS50"), QString(), QString(), QObject::tr("CMS50"), QDateTime::currentDateTime(), cms50_data_version); } // Machine *CreateMachine(); virtual void process(); virtual bool isStartTimeValid() { return !cms50dplus; } protected slots: // virtual void dataAvailable(); virtual void resetImportTimeout(); virtual void startImportTimeout(); virtual void shutdownPorts(); protected: bool readSpoRFile(QString path); virtual void processBytes(QByteArray bytes); int doImportMode(); int doLiveMode(); virtual void killTimers(); // Switch CMS50D+ device to live streaming mode virtual void resetDevice(); // Switch CMS50D+ device to record transmission mode void requestData(); private: EventList *PULSE; EventList *SPO2; QTime m_time; QByteArray buffer; bool started_import; bool finished_import; bool started_reading; bool cms50dplus; int cb_reset,imp_callbacks; int received_bytes; int m_itemCnt; int m_itemTotal; }; #endif // CMS50LOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/cms50f37_loader.cpp000066400000000000000000000703151417327530600246150ustar00rootroot00000000000000/* SleepLib CMS50X Loader Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ //******************************************************************************************** // Please only INCREMENT the cms50f37_data_version in cms50f37_loader.h when making changes // that change loader behaviour or modify channels in a manner that fixes old data imports. // Note that changing the data version will require a reimport of existing data for which OSCAR // does not keep a backup - so it should be avoided if possible. // i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** // #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; #include "cms50f37_loader.h" #include "SleepLib/machine.h" #include "SleepLib/session.h" CMS50F37Loader::CMS50F37Loader() { m_type = MT_OXIMETER; m_abort = false; m_streaming = false; m_importing = false; started_reading = false; cms50dplus = false; imp_callbacks = 0; m_vendorID = 0x10c4; m_productID = 0xea60; oxirec = nullptr; startTimer.setParent(this); resetTimer.setParent(this); duration_divisor = 2; model = QString(); vendor = QString(); } CMS50F37Loader::~CMS50F37Loader() { } bool CMS50F37Loader::openDevice() { if (port.isEmpty()) { bool b = scanDevice("",m_vendorID, m_productID); if (!b) { b = scanDevice("rfcomm", 0, 0); // Linux } if (!b) { qWarning() << "cms50f37 - No oximeter found"; return false; } } serial.setPortName(port); if (!serial.open(QSerialPort::ReadWrite)) { qDebug() << "cms50f37 - Failed to open oximeter"; return false; } // forward this stuff // Set up serial port attributes serial.setBaudRate(QSerialPort::Baud115200); serial.setParity(QSerialPort::NoParity); serial.setStopBits(QSerialPort::OneStop); serial.setDataBits(QSerialPort::Data8); serial.setFlowControl(QSerialPort::NoFlowControl); m_streaming = true; m_abort = false; m_importing = false; // connect relevant signals connect(&serial,SIGNAL(readyRead()), this, SLOT(dataAvailable())); started_import = false; started_reading = false; finished_import = false; resetDevice(); return true; } bool CMS50F37Loader::Detect(const QString &path) { if (p_profile->oxi->oximeterType() == 0) { return true; } Q_UNUSED(path); return false; } int CMS50F37Loader::Open(const QString & path) { // Only one active Oximeter module at a time, set in preferences m_itemCnt = 0; m_itemTotal = 0; m_abort = false; m_importing = false; started_import = false; started_reading = false; finished_import = false; setStatus(NEUTRAL); imp_callbacks = 0; cb_reset = 0; m_time.start(); if (oxirec) { trashRecords(); } // Cheating using path for two serial oximetry modes if (path.compare("import") == 0) { serial.clear(); sequence = 0; buffer.clear(); // nextCommand(); setStatus(IMPORTING); return 1; } else if (path.compare("live") == 0) { return 0; } QString ext = path.section(".", -1); // find the LAST '.' if ((ext.compare("spo2", Qt::CaseInsensitive)==0) || (ext.compare("spo", Qt::CaseInsensitive)==0) || (ext.compare("spor", Qt::CaseInsensitive)==0)) { // try to read and process SpoR file.. return readSpoRFile(path) ? 1 : 0; } return 0; } unsigned char cms50_sequence[] = { 0xa7, 0xa2, 0xa0, 0xb0, 0xac, 0xb3, 0xad, 0xa3, 0xab, 0xa4, 0xa5, 0xaf, 0xa7, 0xa2, 0xa6 }; const int TIMEOUT = 2000; //const quint8 COMMAND_GET_VERSION = 0xA0; // not sure of this one const quint8 COMMAND_CMS50_HELLO2 = 0xA2; // stop live data stream const quint8 COMMAND_GET_SESSION_COUNT = 0xA3; const quint8 COMMAND_GET_SESSION_DURATION = 0xA4; const quint8 COMMAND_GET_SESSION_TIME = 0xA5; const quint8 COMMAND_GET_SESSION_DATA = 0xA6; const quint8 COMMAND_CMS50_HELLO1 = 0xA7; // stop stored data stream const quint8 COMMAND_GET_OXIMETER_MODEL = 0xA8; const quint8 COMMAND_GET_OXIMETER_VENDOR = 0xA9; const quint8 COMMAND_GET_OXIMETER_DEVICEID = 0xAA; const quint8 COMMAND_GET_USER_INFO = 0xAB; const quint8 COMMAND_GET_USER_COUNT = 0xAD; // for future check and use const quint8 COMMAND_SESSION_ERASE = 0xAE; const quint8 COMMAND_GET_OXIMETER_INFO = 0xB0; int cms50_seqlength = sizeof(cms50_sequence); QString CMS50F37Loader::getUser() { if (!userName.isEmpty()) return userName; sendCommand(COMMAND_GET_USER_INFO); QTime time; time.start(); do { QApplication::processEvents(); } while (userName.isEmpty() && (time.elapsed() < TIMEOUT)); qDebug() << "cms50f37 - User " << userIdx << " is " << userName; return userName; } QString CMS50F37Loader::getVendor() { if (!vendor.isEmpty()) return vendor; sendCommand(COMMAND_GET_OXIMETER_VENDOR); QTime time; time.start(); do { QApplication::processEvents(); } while (vendor.isEmpty() && (time.elapsed() < TIMEOUT)); qDebug() << "cms50f37 - Vendor is " << vendor; return vendor; } QString CMS50F37Loader::getModel() { if (!model.isEmpty()) return model; modelsegments = 0; sendCommand(COMMAND_GET_OXIMETER_MODEL); QTime time; time.start(); do { QApplication::processEvents(); } while ((modelsegments < 2) && (time.elapsed() < TIMEOUT)); // Give a little more time for the second one.. QThread::msleep(100); QApplication::processEvents(); if (model.startsWith("CMS50I") || model.startsWith("CMS50H")) { duration_divisor = 4; } else { duration_divisor = 2; } qDebug() << "cms50f37 - Model is " << model; return model; } QString CMS50F37Loader::getDeviceString() { QString VendDev = QString("%1 %2").arg(getVendor()).arg(getModel()); qDebug() << "cms50f37 - USB Device String is " << VendDev; return VendDev; } QString CMS50F37Loader::getDeviceID() { if (!devid.isEmpty()) return devid; sendCommand(COMMAND_GET_OXIMETER_DEVICEID); QTime time; time.start(); do { QApplication::processEvents(); } while (devid.isEmpty() && (time.elapsed() < TIMEOUT)); qDebug() << "cms50f37 - Device Id is " << devid; return devid; } int CMS50F37Loader::getUserCount() // for future use, check, then add select user if > 1 { userCount = -1; sendCommand(COMMAND_GET_USER_COUNT); QTime time; time.start(); do { QApplication::processEvents(); } while ((userCount < 0) && (time.elapsed() < TIMEOUT)); qDebug() << "cms50f37 - User count is " << userCount; return userCount; } int CMS50F37Loader::getSessionCount() { session_count = -1; sendCommand(COMMAND_GET_SESSION_COUNT); QTime time; time.start(); do { QApplication::processEvents(); } while ((session_count < 0) && (time.elapsed() < TIMEOUT)); qDebug() << "cms50f37 - Session count is " << session_count; return session_count; } int CMS50F37Loader::getOximeterInfo() { device_info = -1; sendCommand(COMMAND_GET_OXIMETER_INFO); QTime time; time.start(); do { QApplication::processEvents(); } while ((device_info < 0) && (time.elapsed() < TIMEOUT)); qDebug() << "cms50f37 - Device Info is " << device_info; return device_info; } int CMS50F37Loader::getDuration(int session) { getOximeterInfo(); duration = -1; sendCommand(COMMAND_GET_SESSION_DURATION, session); QTime time; time.start(); do { QApplication::processEvents(); } while ((duration < 0) && (time.elapsed() < TIMEOUT)); qDebug() << "cms50f37 - Session duration is " << duration << "Divided by " << duration_divisor; return duration / duration_divisor; } QDateTime CMS50F37Loader::getDateTime(int session) { QDateTime datetime; imp_date = QDate(); imp_time = QTime(); sendCommand(COMMAND_GET_SESSION_TIME, session); QTime time; time.start(); do { QApplication::processEvents(); } while ((imp_date.isNull() || imp_time.isNull()) && (time.elapsed() < TIMEOUT)); if (imp_date.isNull() && imp_time.isNull()) datetime = QDateTime(QDate::currentDate(), QTime::currentTime()); else if ( imp_date.isNull() ) datetime = QDateTime(QDate::currentDate(), imp_time); else if ( imp_time.isNull() ) datetime = QDateTime(imp_date, QTime::currentTime()); else datetime = QDateTime(imp_date, imp_time); qDebug() << "cms50f37 - Oximeter DateTime is " << datetime.toString("yyyy-MMM-dd HH:mm:ssap"); return datetime; } void CMS50F37Loader::processBytes(QByteArray bytes) { static quint8 resimport = 0; int data; QString tmpstr; int lengths[32] = { 0, 0, 9, 9, 9, 9, 4, 8, 8, 6, 4, 4, 2, 0, 3, 8, 3, 9, 8, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; buffer.append(bytes); int size = buffer.size(); int idx = 0; int len; int year, month, day; quint8 pulse; quint8 spo2; quint16 pi; // perfusion index do { quint8 res = buffer.at(idx); len = lengths[res & 0x1f]; if ((idx+len) > size) break; if (started_reading && (res != resimport)) { len = 0; } if (len == 0) { // lost sync if (started_reading) { while (idx < size) { res = buffer.at(idx++); if (res == resimport) break; } // add a dummy to make up for it. qDebug() << "cms50f37 - pB: lost sync, padding..."; oxirec->append(OxiRecord(0,0,0)); } continue; } // always copy the high bits into the following bytes for (int i = 2, msb = buffer.at(idx+1); i < len; i++, msb>>= 1) { buffer[idx+i] = (buffer[idx+i] & 0x7f) | (msb & 0x01 ? 0x80 : 0); } if (!started_reading) switch(res) { case 0x02: // Model name string (there are two in sequnce.. second might be the next chunk!) data = buffer.at(idx+2); if (data == 0) { model = QString(buffer.mid(idx+3, 6)); modelsegments++; } else { QString extra = QString(buffer.mid(idx+3, 6)); model += extra.trimmed(); modelsegments++; qDebug() << "cms50f37 - pB: Model:" << model; } break; case 0x03: // Vendor string vendor = QString(buffer.mid(idx+2, 7)); qDebug() << "cms50f37 - pB: Vendor:" << vendor; break; case 0x04: // Device Identifiers devid = QString(buffer.mid(idx+2, 7)); qDebug() << "cms50f37 - pB: Device ID:" << devid; break; // COMMAND_GET_USER_INFO case 0x05: // 5,80,80,f5,f3,e5,f2,80,80 userIdx = buffer.at(idx+2); // for future use userName = QString(buffer.mid(idx+3).trimmed()); qDebug() << "cms50f37 - pB: 0x05:" << userName; break; // Command GET_VERSION case 0x6: // 6,80,80,87 protocolVersion = buffer.at(idx+3) | (buffer.at(idx+2)<<8); break; // COMMAND_GET_SESSION_TIME --- the date part case 0x07: // 7,80,80,80,94,8e,88,92 year = QString().sprintf("%02i%02i",buffer.at(idx+4), buffer.at(idx+5)).toInt(); month = QString().sprintf("%02i", buffer.at(idx+6)).toInt(); day = QString().sprintf("%02i", buffer.at(idx+7)).toInt(); if ( year == 0 ) { imp_date = QDate::currentDate(); cms50dplus = true; } else imp_date = QDate(year,month,day); qDebug() << "cms50f37 - cms50D+ detected: " << (cms50dplus ? "yes" : "no"); qDebug() << "cms50f37 - pB: ymd " << year << month << day << " impDate: " << imp_date; break; // COMMAND_GET_SESSION_DURATION case 0x08: // 8,80,80,80,a4,81,80,80 // 00, 00, 24, 01, 00, 00 duration = buffer.at(idx+4); duration |= (buffer.at(idx+5) << 8); duration |= (buffer.at(idx+6) << 16); duration |= (buffer.at(idx+7) << 24); break; // COMMAND_GET_SESSION_COUNT case 0x0a: // a,80,80,81 userIdx = buffer.at(idx+2); session_count = buffer.at(idx+3); break; case 0x0b: timectr++; break; // COMMAND_CMS50_HELLO1 && COMMAND_CMS50_HELLO2 case 0xc: // a7 & a2 // responds with: c,80 //data = buffer.at(idx+1); break; // Query Perfusion Index available (0 is yes, 1 is no) case 0x0e: // e,80,81 //data = buffer.at(idx+2); break; // Get User Count case 0x10: // 10,80,81 userCount = buffer.at(idx+2); break; // COMMAND_GET_OXIMETER_INFO case 0x11: // 11,80,81,81,80,80,80,80,80 device_info = buffer.at(idx+3); break; // COMMAND_GET_SESSION_TIME case 0x12: // 12,80,80,80,82,a6,92,80 tmpstr = QString().sprintf("%02i:%02i:%02i",buffer.at(idx+4), buffer.at(idx+5), buffer.at(idx+6)); imp_time = QTime::fromString(tmpstr, "HH:mm:ss"); qDebug() << "cms50f37 - pB: tmpStr:" << tmpstr << " impTime: " << imp_time; break; case 0x13: // 13,80,a0,a0,a0,a0,a0,a0,a0 break; case 0x14: //14,80,80,80,80,80,80,80,80 break; case 0x09: // cms50i data sequence case 0x0f: // f,80,de,c2,de,c2,de,c2 cms50F data... if (!started_import) { qDebug() << "cms50f37 - pB: Starting import"; started_import = true; started_reading = true; finished_import = false; m_importing = true; m_itemCnt=0; m_itemTotal=duration; have_perfindex = (res == 0x9); oxirec = new QVector; oxirec->reserve(30000); oxisessions[m_startTime] = oxirec; cb_reset = 1; resetTimer.singleShot(2000,this,SLOT(resetImportTimeout())); resimport = res; } break; default: qDebug() << "cms50f37 - pB: unknown cms50F result?" << hex << (int)res; break; } if (res == 0x09) { // 9,80,e1,c4,ce,82 // cms50i data pi = buffer.at(idx+4) | (buffer.at(idx+5) << 8); pulse = buffer.at(idx+3); spo2 = buffer.at(idx+2); // qDebug() << "cms50f37 - pB: Pulse=" << pulse << "SPO2=" << spo2 << "PI=" << pi; oxirec->append(((spo2 == 0) || (pulse == 0)) ? OxiRecord(0,0,0) : OxiRecord(pulse, spo2, pi)); } else if (res == 0x0f) { // f,80,de,c2,de,c2,de,c2 cms50F data... pulse = buffer.at(idx+3); spo2 = buffer.at(idx+2); // qDebug() << "cms50f37 - pB: Pulse=" << pulse << "SPO2=" << spo2; oxirec->append((pulse == 0xff) ? OxiRecord(0,0) : OxiRecord(pulse, spo2)); pulse = buffer.at(idx+5); spo2 = buffer.at(idx+4); // qDebug() << "cms50f37 - pB: Pulse=" << pulse << "SPO2=" << spo2; oxirec->append((pulse == 0xff) ? OxiRecord(0,0) : OxiRecord(pulse, spo2)); pulse = buffer.at(idx+7); spo2 = buffer.at(idx+6); // qDebug() << "cms50f37 - pB: Pulse=" << pulse << "SPO2=" << spo2; oxirec->append((pulse == 0xff) ? OxiRecord(0,0) : OxiRecord(pulse, spo2)); } QStringList str; for (int i=0; i < len; ++i) { str.append(QString::number((unsigned char)buffer.at(idx + i),16)); } if (!started_import) { // startTimer.singleShot(2000, this, SLOT(requestData())); importCount = 0; qDebug() << "cms50f37 - pB: Read:" << len << size << str.join(","); } else { importCount++; // qDebug() << "cms50f37 - pB: Import:" << len << size << str.join(","); } idx += len; } while (idx < size); if (!started_import) { imp_callbacks = 0; } else { emit updateProgress(oxirec->size(), duration); imp_callbacks++; } buffer = buffer.mid(idx); } //int CMS50F37Loader::doLiveMode() //{ // if (oxirec == nullptr) { // warn //} // // int available = buffer.size(); // int idx = 0; // // QByteArray plethy; // while (idx < available-5) { // if (((unsigned char)buffer.at(idx) & 0x80) != 0x80) { // idx++; // continue; // } // int pwave=(unsigned char)buffer.at(idx + 1); // int pbeat=(unsigned char)buffer.at(idx + 2); // int pulse=((unsigned char)buffer.at(idx + 3) & 0x7f) | ((pbeat & 0x40) << 1); // int spo2=(unsigned char)buffer.at(idx + 4) & 0x7f; // // oxirec->append(OxiRecord(pulse, spo2)); // plethy.append(pwave); // // idx += 5; // } // emit updatePlethy(plethy); // // return idx; //} void CMS50F37Loader::sendCommand(quint8 c) { quint8 cmd[] = { 0x7d, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }; cmd[2] |= (c & 0x7f); QString out; for (int i=0;i < 9;i++) out += QString().sprintf("%02X ",cmd[i]); qDebug() << "cms50f37 - Write:" << out; if (serial.write((char *)cmd, 9) == -1) { qDebug() << "cms50f37 - Couldn't write data bytes to CMS50F"; } } void CMS50F37Loader::sendCommand(quint8 c, quint8 c2) { quint8 cmd[] = { 0x7d, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }; cmd[2] |= (c & 0x7f); cmd[4] |= (c2 & 0x7f); QString out; for (int i=0; i < 9; ++i) out += QString().sprintf("%02X ",cmd[i]); qDebug() << "cms50f37 - Write:" << out; if (serial.write((char *)cmd, 9) == -1) { qDebug() << "cms50f37 - Couldn't write data bytes to CMS50F"; } } void CMS50F37Loader::eraseSession(int user, int session) { quint8 cmd[] = { 0x7d, 0x81, COMMAND_SESSION_ERASE, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }; cmd[3] = (user & 0x7f) | 0x80; cmd[4] = (session & 0x7f) | 0x80; QString out; for (int i=0; i < 9; ++i) out += QString().sprintf("%02X ",cmd[i]); qDebug() << "cms50f37 - Erase Session: Write:" << out; if (serial.write((char *)cmd, 9) == -1) { qDebug() << "cms50f37 - Couldn't write Erase session bytes to CMS50F"; } int z = timectr; QTime time; time.start(); do { QApplication::processEvents(); } while ((timectr == z) && (time.elapsed() < TIMEOUT)); } void CMS50F37Loader::setDeviceID(const QString & newid) { QString str = newid; str.truncate(7); if (str.length() < 7) { str = QString(" ").repeated(7-str.length()) + str; } quint8 cmd[] = { 0x04, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }; quint8 msb = 0; QByteArray ba = str.toLocal8Bit(); for (int i=6; i >= 0; i--) { msb <<= 1; msb |= (ba.at(i) >> 7) & 1; cmd[i+2] = ba.at(i) | 0x80; } cmd[1] = msb | 0x80; QString out; for (int i=0; i < 9; ++i) out += QString().sprintf("%02X ",cmd[i]); qDebug() << "cms50f37 - setDeviceID: Write:" << out; if (serial.write((char *)cmd, 9) == -1) { qDebug() << "cms50f37 - Couldn't write DeviceID data bytes to CMS50F"; } // Supposed to return 0x04 command, so reset devid.. devid = QString(); QTime time; time.start(); do { QApplication::processEvents(); } while (devid.isEmpty() && (time.elapsed() < TIMEOUT)); } void CMS50F37Loader::syncClock() { QDate date = QDate::currentDate(); int year = date.year(); quint8 yh = (year / 100) | 0x80; quint8 yl = (year % 100) | 0x80; quint8 mon = date.month() | 0x80; quint8 day = date.day() | 0x80; quint8 wd = (date.dayOfWeek() % 7) | 0x80; quint8 datecmd[] = { 0x7d, 0x81, 0xb2, yh, yl, mon, day, wd, 0x80 }; timectr = 0; if (serial.write((char *)datecmd, 9) == -1) { qDebug() << "cms50f37 - Couldn't write date bytes to CMS50F"; } QTime time; time.start(); do { QApplication::processEvents(); } while ((timectr == 0) && (time.elapsed() < TIMEOUT)); QThread::msleep(100); QApplication::processEvents(); QTime ctime = QTime::currentTime(); quint8 h = ctime.hour() | 0x80; quint8 m = ctime.minute() | 0x80; quint8 s = ctime.second() | 0x80; quint8 timecmd[] = { 0x7d, 0x81, 0xb1, h, m, s, 0x80, 0x80, 0x80 }; timectr = 0; if (serial.write((char *)timecmd, 9) == -1) { qDebug() << "cms50f37 - Couldn't write time bytes to CMS50F"; } time.start(); do { QApplication::processEvents(); } while ((timectr == 0) && (time.elapsed() < TIMEOUT)); } void CMS50F37Loader::nextCommand() { qDebug() << "nextCommand sequence: " << sequence; if (++sequence < cms50_seqlength) { // Send the next command packet in sequence sendCommand(cms50_sequence[sequence]); } else { qDebug() << "cms50f37 - Run out of startup tasks to do and import failed!"; } } void CMS50F37Loader::getSessionData(int session) { resetDevice(); selected_session = session; requestData(); } void CMS50F37Loader::resetDevice() { qDebug() << "cms50f37 - Resetting oximeter"; sendCommand(COMMAND_CMS50_HELLO1); QThread::msleep(100); QApplication::processEvents(); sendCommand(COMMAND_CMS50_HELLO2); QThread::msleep(100); QApplication::processEvents(); } void CMS50F37Loader::requestData() { qDebug() << "cms50f37 - Requesting session data"; sendCommand(COMMAND_GET_SESSION_DATA, selected_session); } void CMS50F37Loader::killTimers() { if (resetTimer.isActive()) resetTimer.stop(); if (startTimer.isActive()) startTimer.stop(); } void CMS50F37Loader::startImportTimeout() { } void CMS50F37Loader::resetImportTimeout() { if (finished_import) { return; } if (imp_callbacks != cb_reset) { // Still receiving data.. reset timer qDebug() << "cms50f37 - Still receiving data in resetImportTimeout()" << imp_callbacks << cb_reset; if (resetTimer.isActive()) resetTimer.stop(); if (!finished_import) resetTimer.singleShot(2000, this, SLOT(resetImportTimeout())); } else { qDebug() << "cms50f37 - Oximeter device stopped transmitting.. Transfer complete"; qDebug() << "cms50f37 - Import packet count: " << importCount; // We were importing, but now are done if (!finished_import && (started_import && started_reading)) { qDebug() << "cms50f37 - Switching CMS50F37 back to live mode and finalizing import"; // Turn back on live streaming so the end of capture can be dealt with killTimers(); finished_import = true; m_streaming = false; m_importing = false; started_reading = false; started_import = false; emit importComplete(this); m_status = NEUTRAL; shutdownPorts(); return; } qDebug() << "cms50f37 - Should CMS50F37 resetImportTimeout reach here?"; // else what??? } cb_reset = imp_callbacks; } void CMS50F37Loader::shutdownPorts() { closeDevice(); } bool CMS50F37Loader::readSpoRFile(const QString & path) { QFile file(path); if (!file.exists()) { qWarning() << "cms50f37 - Can't find the oximeter file: " << path; QMessageBox::warning(nullptr, STR_MessageBox_Error, "

"+tr("Could not find the oximeter file:")+"

"+path+"

"); return false; } if (!file.open(QFile::ReadOnly)) { qWarning() << "cms50f37 - Can't open the oximeter file: " << path; QMessageBox::warning(nullptr, STR_MessageBox_Error, "

"+tr("Could not open the oximeter file:")+"

"+path+"

"); return false; } bool spo2header = false; QString ext = path.section('.', -1); qDebug() << "cms50f37 - Oximeter file extention is " << ext; if (ext.compare("spo2",Qt::CaseInsensitive) == 0) { spo2header = true; qDebug() << "cms50f37 - Oximeter file looks like an SpO2 type" ; } QByteArray data; qint64 filesize = file.size(); data = file.readAll(); QDataStream in(data); in.setByteOrder(QDataStream::LittleEndian); quint16 pos; in >> pos; in.skipRawData(pos - 2); //long size = data.size(); int bytes_per_record = 2; if (!spo2header) { // next is 0x0002 // followed by 16bit duration in seconds // Read date and time (it's a 16bit charset) char dchr[20]; int j = 0; for (int i = 0; i < 18 * 2; i += 2) { dchr[j++] = data.at(8 + i); } dchr[j] = 0; if (dchr[0]) { QString dstr(dchr); // Ensure date is correct first to ensure DST is handled correctly QDate date = QDate::fromString(dstr.left(8),"MM/dd/yy"); QTime time = QTime::fromString(dstr.right(8),"HH:mm:ss"); if (date.year() < 2000) { date = date.addYears(100); } m_startTime = QDateTime(date, time); } else { // this should probaly find the most recent SH data day m_startTime = QDateTime(QDate::currentDate(), QTime(0,0,0)); // make it today at midnight cms50dplus = true; } } else { // it is an spo2header quint32 samples = 0; // number of samples quint32 year, month, day; quint32 hour, minute, second; if (data.at(pos) != 1) { qWarning() << "cms50f37 - .spo2 file" << path << "might be a different"; } // Unknown cruft header... in.skipRawData(200); in >> year >> month >> day; in >> hour >> minute >> second; if ( year+month+day == 0 ) { m_startTime = QDateTime(QDate::currentDate(), QTime(0,0,0)); cms50dplus = true; } else m_startTime = QDateTime(QDate(year, month, day), QTime(hour, minute, second)); // ignoring it for now pos += 0x1c + 200; in >> samples; int remainder = filesize - pos; bytes_per_record = remainder / samples; qDebug() << "cms50f37 - " << samples << "samples of" << bytes_per_record << "bytes each"; // CMS50I .spo2 data have 4 digits, a 16bit, followed by spo2 then pulse } oxirec = new QVector; oxisessions[m_startTime] = oxirec; unsigned char o2, pr; quint16 un; // Read all Pulse and SPO2 data do { if (bytes_per_record > 2) { in >> un; } in >> o2; in >> pr; if ((o2 == 0x7f) && (pr == 0xff)) { o2 = pr = 0; un = 0; } if (spo2header) { oxirec->append(OxiRecord(pr, o2)); } else { oxirec->append(OxiRecord(o2, pr)); } } while (!in.atEnd()); // for (int i = pos; i < size - 2;) { // o2 = (unsigned char)(data.at(i + 1)); // pr = (unsigned char)(data.at(i + 0)); // oxirec->append(OxiRecord(pr, o2)); // i += 2; // } // processing gets done later return true; } void CMS50F37Loader::process() { // Just clean up any extra crap before oximeterimport parses the oxirecords.. return; // if (!oxirec) // return; // int size=oxirec->size(); // if (size<10) // return; } static bool cms50f37_initialized = false; void CMS50F37Loader::Register() { if (cms50f37_initialized) { return; } qDebug() << "cms50f37 - Registering CMS50F37Loader"; RegisterLoader(new CMS50F37Loader()); cms50f37_initialized = true; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/cms50f37_loader.h000066400000000000000000000064751417327530600242700ustar00rootroot00000000000000/* SleepLib CMS50X Loader Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef CMS50F37LOADER_H #define CMS50F37LOADER_H #include "SleepLib/serialoximeter.h" const QString cms50f37_class_name = "CMS50F37"; const int cms50f37_data_version = 0; /*! \class CMS5037Loader \brief Bulk Importer for newer CMS50 oximeters */ class CMS50F37Loader : public SerialOximeter { Q_OBJECT public: CMS50F37Loader(); virtual ~CMS50F37Loader(); virtual bool Detect(const QString &path); virtual int Open(const QString & path); virtual bool openDevice(); static void Register(); virtual int Version() { return cms50f37_data_version; } virtual const QString &loaderName() { return cms50f37_class_name; } virtual MachineInfo newInfo() { return MachineInfo(MT_OXIMETER, 0, cms50f37_class_name, QObject::tr("Contec"), QObject::tr("CMS50F3.7"), QString(), QString(), QObject::tr("CMS50F"), QDateTime::currentDateTime(), cms50f37_data_version); } // Machine *CreateMachine(); virtual void process(); virtual bool isStartTimeValid() { return !cms50dplus; } virtual QString getUser(); virtual QString getModel(); virtual QString getVendor(); virtual QString getDeviceString(); virtual QDateTime getDateTime(int session); virtual int getDuration(int session); virtual int getSessionCount(); virtual int getUserCount(); virtual int getOximeterInfo(); virtual void eraseSession(int user, int session); virtual void syncClock(); virtual QString getDeviceID(); virtual void setDeviceID(const QString &); virtual void setDuration(int d) { duration=d; } virtual bool commandDriven() { return true; } virtual void getSessionData(int session); // Switch device to record transmission mode void requestData(); protected slots: // virtual void dataAvailable(); virtual void resetImportTimeout(); virtual void startImportTimeout(); virtual void shutdownPorts(); void nextCommand(); protected: bool readSpoRFile(const QString & path); virtual void processBytes(QByteArray bytes); // int doLiveMode(); virtual void killTimers(); void sendCommand(quint8 c); void sendCommand(quint8 c, quint8 c2); // Switch device to live streaming mode virtual void resetDevice(); private: int sequence; EventList *PULSE; EventList *SPO2; QTime m_time; QByteArray buffer; bool started_import; bool finished_import; bool started_reading; bool cms50dplus; int cb_reset,imp_callbacks; int received_bytes; int importCount; int m_itemCnt; int m_itemTotal; QDate imp_date; QTime imp_time; QString userName; unsigned char current_command; volatile int session_count; volatile int duration; volatile int userCount; volatile int userIdx; volatile int protocolVersion; int device_info; QString model; QString vendor; QString devid; int duration_divisor; int selected_session; int timectr; int modelsegments; }; #endif // CMS50F37LOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/dreem_loader.cpp000066400000000000000000000222441417327530600244400ustar00rootroot00000000000000/* SleepLib Dreem Loader Implementation * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ //******************************************************************************************** // Please only INCREMENT the dreem_data_version in dreem_loader.h when making changes // that change loader behaviour or modify channels in a manner that fixes old data imports. // Note that changing the data version will require a reimport of existing data for which OSCAR // does not keep a backup - so it should be avoided if possible. // i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** #include #include #include #include #include "dreem_loader.h" #include "SleepLib/machine.h" #include "csv.h" static QSet s_unexpectedMessages; DreemLoader::DreemLoader() { m_type = MT_SLEEPSTAGE; csv = nullptr; } DreemLoader::~DreemLoader() { closeCSV(); } bool DreemLoader::Detect(const QString & path) { // This is only used for CPAP machines, when detecting CPAP cards. qDebug() << "DreemLoader::Detect(" << path << ")"; return false; } int DreemLoader::OpenFile(const QString & filename) { if (!openCSV(filename)) { closeCSV(); return -1; } int count = 0; Session* sess; // TODO: add progress bar support, perhaps move shared logic into shared parent class with Zeo loader while ((sess = readNextSession()) != nullptr) { sess->SetChanged(true); mach->AddSession(sess); count++; } if (count > 0) { mach->Save(); mach->SaveSummaryCache(); p_profile->StoreMachines(); } closeCSV(); return count; } bool DreemLoader::openCSV(const QString & filename) { file.setFileName(filename); if (filename.toLower().endsWith(".csv")) { if (!file.open(QFile::ReadOnly)) { qDebug() << "Couldn't open Dreem file" << filename; return false; } } else { return false; } QStringList header; csv = new CSVReader(file, ";", "#"); bool ok = csv->readRow(header); if (!ok) { qWarning() << "no header row"; return false; } csv->setFieldNames(header); MachineInfo info = newInfo(); mach = p_profile->CreateMachine(info); return true; } void DreemLoader::closeCSV() { if (csv != nullptr) { delete csv; csv = nullptr; } if (file.isOpen()) { file.close(); } } const QStringList s_sleepStageLabels = { "NA", "WAKE", "REM", "Light", "Deep" }; Session* DreemLoader::readNextSession() { if (csv == nullptr) { qWarning() << "no CSV open!"; return nullptr; } static QHash s_sleepStages; for (int i = 0; i < s_sleepStageLabels.size(); i++) { const QString & label = s_sleepStageLabels[i]; s_sleepStages[label] = i; // match ZEO sleep stages for now // TODO: generalize sleep stage integers between Dreem and Zeo } Session* sess = nullptr; QDateTime start_time, stop_time; int sleep_onset; //, sleep_duration; int light_sleep_duration, deep_sleep_duration, rem_duration, awakened_duration; int awakenings; //, position_changes, average_hr, average_rr; float sleep_efficiency; QStringList hypnogram; QHash row; while (csv->readRow(row)) { SessionID sid = 0; invalid_fields = false; start_time = readDateTime(row["Start Time"]); if (start_time.isValid()) { sid = start_time.toTime_t(); if (mach->SessionExists(sid)) { continue; } } // else invalid_fields will be true // "Type" always seems to be "night" stop_time = readDateTime(row["Stop Time"]); sleep_onset = readDuration(row["Sleep Onset Duration"]); //sleep_duration = readDuration(row["Sleep Duration"]); light_sleep_duration = readDuration(row["Light Sleep Duration"]); deep_sleep_duration = readDuration(row["Deep Sleep Duration"]); rem_duration = readDuration(row["REM Duration"]); awakened_duration = readDuration(row["Wake After Sleep Onset Duration"]); awakenings = readInt(row["Number of awakenings"]); //position_changes = readInt(row["Position Changes"]); //average_hr = readInt(row["Mean Heart Rate"]); // TODO: sometimes "None" //average_rr = readInt(row["Mean Respiration CPM"]); // "Number of Stimulations" is 0 for US models sleep_efficiency = readInt(row["Sleep efficiency"]) / 100.0; if (invalid_fields) { qWarning() << "invalid Dreem row, skipping" << start_time; continue; } QString h = row["Hypnogram"]; // with "[" at the beginning and "]" at the end hypnogram = h.mid(1, h.length()-2).split(","); if (hypnogram.size() == 0) { continue; } sess = new Session(mach, sid); break; }; if (sess) { const quint64 step = 30 * 1000; m_session = sess; // TODO: rename Zeo channels to be generic sess->settings[ZEO_Awakenings] = awakenings; sess->settings[ZEO_TimeToZ] = sleep_onset / 60; // TODO: convert durations to seconds and update Zeo loader accordingly, also below sess->settings[ZEO_ZQ] = int(sleep_efficiency * 100.0); // TODO: ZQ may be better expressed as a percent? sess->settings[ZEO_TimeInWake] = awakened_duration / 60; sess->settings[ZEO_TimeInREM] = rem_duration / 60; sess->settings[ZEO_TimeInLight] = light_sleep_duration / 60; sess->settings[ZEO_TimeInDeep] = deep_sleep_duration / 60; //sess->settings[OXI_Pulse] = average_hr; //sess->settings[CPAP_RespRate] = average_rr; // Dreem also provides: // total sleep duration // # position changes qint64 st = qint64(start_time.toTime_t()) * 1000L; qint64 last = qint64(stop_time.toTime_t()) * 1000L; sess->really_set_first(st); // It appears that the first sample occurs at start time and // the second sample occurs at the next 30-second boundary. // // TODO: About half the time there are still too many samples? qint64 tt = st; qint64 second_sample_tt = ((tt + step - 1L) / step) * step; for (int i = 0; i < hypnogram.size(); i++) { auto & label = hypnogram.at(i); if (s_sleepStages.contains(label)) { int stage = s_sleepStages[label]; // It appears that the last sample occurs at the stop time. if (tt > last) { if (i != hypnogram.size() - 1) { qWarning() << sess->session() << "more hypnogram samples than time" << tt << last; } tt = last; } if (stage == 0) { EndEventList(ZEO_SleepStage, tt); } else { AddEvent(ZEO_SleepStage, tt, -stage); // use negative values so that the chart is oriented the right way } } else { qWarning() << sess->session() << start_time << "@" << i << "unknown sleep stage" << label; } if (i == 0) { tt = second_sample_tt; } else { tt += step; } } EndEventList(ZEO_SleepStage, last); sess->really_set_last(last); } return sess; } void DreemLoader::AddEvent(ChannelID channel, qint64 t, EventDataType value) { EventList* C = m_importChannels[channel]; if (C == nullptr) { C = m_session->AddEventList(channel, EVL_Event, 1, 0, -5, 0); Q_ASSERT(C); // Once upon a time AddEventList could return nullptr, but not any more. m_importChannels[channel] = C; } // Add the event C->AddEvent(t, value); m_importLastValue[channel] = value; } void DreemLoader::EndEventList(ChannelID channel, qint64 t) { EventList* C = m_importChannels[channel]; if (C != nullptr) { C->AddEvent(t, m_importLastValue[channel]); // Mark this channel's event list as ended. m_importChannels[channel] = nullptr; } } QDateTime DreemLoader::readDateTime(const QString & text) { QDateTime dt = QDateTime::fromString(text, Qt::ISODate); if (!dt.isValid()) invalid_fields = true; return dt; } int DreemLoader::readDuration(const QString & text) { QTime t = QTime::fromString(text, "H:mm:ss"); if (!t.isValid()) invalid_fields = true; return t.msecsSinceStartOfDay() / 1000L; } int DreemLoader::readInt(const QString & text) { bool ok; int value = text.toInt(&ok); if (!ok) invalid_fields = true; return value; } static bool dreem_initialized = false; void DreemLoader::Register() { if (dreem_initialized) { return; } qDebug("Registering DreemLoader"); RegisterLoader(new DreemLoader()); dreem_initialized = true; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/dreem_loader.h000066400000000000000000000034551417327530600241100ustar00rootroot00000000000000/* SleepLib Dreem Loader Header * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef DREEMLOADER_H #define DREEMLOADER_H #include "SleepLib/machine_loader.h" const QString dreem_class_name = "Dreem"; const int dreem_data_version = 2; /*! \class DreemLoader */ class DreemLoader : public MachineLoader { public: DreemLoader(); virtual ~DreemLoader(); virtual bool Detect(const QString & path); virtual int Open(const QString & path) { Q_UNUSED(path); return 0; } // Only for CPAP virtual int OpenFile(const QString & path); virtual QStringList getNameFilter() { return QStringList("Dreem CSV File (*.csv)"); } static void Register(); virtual int Version() { return dreem_data_version; } virtual const QString &loaderName() { return dreem_class_name; } virtual MachineInfo newInfo() { return MachineInfo(MT_SLEEPSTAGE, 0, dreem_class_name, QObject::tr("Dreem"), QString(), QString(), QString(), QObject::tr("Dreem"), QDateTime::currentDateTime(), dreem_data_version); } bool openCSV(const QString & filename); void closeCSV(); Session* readNextSession(); protected: QDateTime readDateTime(const QString & text); int readDuration(const QString & text); int readInt(const QString & text); private: QFile file; class CSVReader* csv; Machine *mach; bool invalid_fields; void AddEvent(ChannelID channel, qint64 t, EventDataType value); void EndEventList(ChannelID channel, qint64 t); Session* m_session; QHash m_importChannels; QHash m_importLastValue; }; #endif // DREEMLOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/edfparser.cpp000066400000000000000000000320251417327530600237670ustar00rootroot00000000000000/* EDF Parser Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #ifdef _MSC_VER #include #else #include #endif #include "edfparser.h" //EDFSignal::~EDFSignal() //{ // delete [] dataArray; //} EDFInfo::EDFInfo() { filename = QString(); edfsignals.clear(); filesize = 0; datasize = 0; signalPtr = nullptr; hdrPtr = nullptr; // fileData = nullptr; } int EDFInfo::TZ_offset = QTimeZone::systemTimeZone().offsetFromUtc(QDateTime::currentDateTime()); QTimeZone EDFInfo::localNoDST = QTimeZone(TZ_offset); EDFInfo::~EDFInfo() { // if ( fileData ) { if (fileData.size() > 0) { #ifdef EDF_DEBUG qDebug() << "EDFInfo destructor clearing fileData"; #endif fileData.clear(); } // } for (auto & s : edfsignals) { if (s.dataArray) delete [] s.dataArray; } // for (auto & a : annotations) // delete a; } // Set timezone to UTC void EDFInfo::setTimeZoneUTC () { TZ_offset = 0; EDFInfo::localNoDST = QTimeZone(TZ_offset); } bool EDFInfo::Open(const QString & name) { if (hdrPtr != nullptr) { qWarning() << "EDFInfo::Open() called with file already open " << name; return false; } QFile fi(name); if (!fi.open(QFile::ReadOnly)) { qDebug() << "EDFInfo::Open() Couldn't open file" << name << "error" << fi.error() << fi.errorString(); return false; } // fileData = new QByteArray(); #ifndef DUMPSTR if (name.endsWith(STR_ext_gz)) { fileData = gUncompress(fi.readAll()); // Open and decompress file } else { fileData = fi.readAll(); // Open and read uncompressed file } #else fileData = fi.readAll(); // Open and read uncompressed file #endif fi.close(); if (fileData.size() <= EDFHeaderSize) { fileData.clear();; qDebug() << "EDFInfo::Open() File too short " << name; return false; } filename = name; // return fileData; return true; } QDateTime EDFInfo::getStartDT( QString dateTimeStr ) { // edfHdr.startdate_orig = QDateTime::fromString(QString::fromLatin1(hdrPtr->datetime, 16), "dd.MM.yyHH.mm.ss"); // QString dateTimeStr; // , dateStr, timeStr; QDate qDate; QTime qTime; // dateTimeStr = QString::fromLatin1(hdrPtr->datetime, 16); // dateStr = dateTimeStr.left(8); // timeStr = dateTimeStr.right(8); qDate = QDate::fromString(dateTimeStr.left(8), "dd.MM.yy"); qTime = QTime::fromString(dateTimeStr.right(8), "HH.mm.ss"); return QDateTime(qDate, qTime, localNoDST); } bool EDFInfo::parseHeader( EDFHeaderRaw *hdrPtr ) { bool ok; edfHdr.version = QString::fromLatin1(hdrPtr->version, 8).toLong(&ok); if (!ok) { #ifdef EDF_DEBUG qWarning() << "EDFInfo::Parse() Bad Version " << filename; // sleep(1); #endif fileData.clear(); return false; } edfHdr.patientident=QString::fromLatin1(hdrPtr->patientident,80).trimmed(); edfHdr.recordingident = QString::fromLatin1(hdrPtr->recordingident, 80).trimmed(); // Serial number is in here.. edfHdr.startdate_orig = getStartDT(QString::fromLatin1(hdrPtr->datetime, 16)); // This conversion will fail in 2084 after when the spec calls for the year to be 'yy' instead of digits // The solution is left for the afflicted - it won't be me! QDate d2 = edfHdr.startdate_orig.date(); if (d2.year() < 2000) { d2.setDate(d2.year() + 100, d2.month(), d2.day()); edfHdr.startdate_orig.setDate(d2); } edfHdr.num_header_bytes = QString::fromLatin1(hdrPtr->num_header_bytes, 8).toLong(&ok); if (!ok) { #ifdef EDF_DEBUG qWarning() << "EDFInfo::Parse() Bad header byte count " << filename; // sleep(1); #endif fileData.clear(); return false; } edfHdr.reserved44=QString::fromLatin1(hdrPtr->reserved, 44).trimmed(); edfHdr.num_data_records = QString::fromLatin1(hdrPtr->num_data_records, 8).toLong(&ok); if ( (! ok) || (edfHdr.num_data_records < 1) ) { #ifdef EDF_DEBUG qWarning() << "EDFInfo::Parse() Bad data record count " << filename; // sleep(1); #endif fileData.clear(); return false; } edfHdr.duration_Seconds = QString::fromLatin1(hdrPtr->dur_data_records, 8).toDouble(&ok); if (!ok) { #ifdef EDF_DEBUG qWarning() << "EDFInfo::Parse() Bad duration " << filename; // sleep(1); #endif fileData.clear(); return false; } edfHdr.num_signals = QString::fromLatin1(hdrPtr->num_signals, 4).toLong(&ok); if ( (! ok) || (edfHdr.num_signals < 1) || (edfHdr.num_signals > 256) ) { #ifdef EDF_DEBUG qWarning() << "EDFInfo::Parse() Bad number of signals " << filename; // sleep(1); #endif fileData.clear(); return false; } return true; } bool EDFInfo::Parse() { bool ok; if (fileData.size() == 0) { qWarning() << "EDFInfo::Parse() called without valid EDF data " << filename; // sleep(1); return false; } hdrPtr = (EDFHeaderRaw *)fileData.constData(); signalPtr = (char *)fileData.constData() + EDFHeaderSize; filesize = fileData.size(); datasize = filesize - EDFHeaderSize; pos = 0; eof = false; if ( ! parseHeader( hdrPtr ) ) return false; // Initialize fixed-size signal list. edfsignals.resize(edfHdr.num_signals); // Now copy all the Signal descriptives into edfsignals for (auto & sig : edfsignals) { sig.dataArray = nullptr; sig.label = ReadBytes(16); signal_labels.push_back(sig.label); signalList[sig.label].push_back(&sig); if (eof) { qWarning() << "EDFInfo::Parse() Early end of file " << filename; // sleep(1); fileData.clear(); return false; } } for (auto & sig : edfsignals) { sig.transducer_type = ReadBytes(80); } for (auto & sig : edfsignals) { sig.physical_dimension = ReadBytes(8); } for (auto & sig : edfsignals) { sig.physical_minimum = ReadBytes(8).toDouble(&ok); } for (auto & sig : edfsignals) { sig.physical_maximum = ReadBytes(8).toDouble(&ok); } for (auto & sig : edfsignals) { sig.digital_minimum = ReadBytes(8).toDouble(&ok); } for (auto & sig : edfsignals) { sig.digital_maximum = ReadBytes(8).toDouble(&ok); sig.gain = (sig.physical_maximum - sig.physical_minimum) / (sig.digital_maximum - sig.digital_minimum); sig.offset = 0; } for (auto & sig : edfsignals) { sig.prefiltering = ReadBytes(80); } for (auto & sig : edfsignals) { sig.sampleCnt = ReadBytes(8).toLong(&ok); } for (auto & sig : edfsignals) { sig.reserved = ReadBytes(32); } // could do it earlier, but it won't crash from > EOF Reads if (eof) { qWarning() << "EDFInfo::Parse() Early end of file " << filename; // sleep(1); fileData.clear(); return false; } // Now check the file isn't truncated before allocating space for the values long allocsize = 0; for (auto & sig : edfsignals) { if (edfHdr.num_data_records > 0) { allocsize += sig.sampleCnt * edfHdr.num_data_records * 2; } } if (allocsize > (datasize - pos)) { // Space required more than the remainder left to read, // so abort and let the user clean up the corrupted file themselves qWarning() << "EDFInfo::Parse(): " << filename << " is too short!"; // sleep(1); fileData.clear(); return false; } // allocate the arrays for the signal values for (auto & sig : edfsignals) { long samples = sig.sampleCnt * edfHdr.num_data_records; if (edfHdr.num_data_records <= 0) { sig.dataArray = nullptr; continue; } sig.dataArray = new qint16 [samples]; // sig.pos = 0; } for (int recNo = 0; recNo < edfHdr.num_data_records; recNo++) { for (auto & sig : edfsignals) { if ( sig.label.contains("Annotations") ) { annotations.push_back(ReadAnnotations( (char *)&signalPtr[pos], sig.sampleCnt*2)); pos += sig.sampleCnt * 2; } else { // it's got genuine 16-bit values for (int j=0;j EDFInfo::ReadAnnotations(const char * data, int charLen) { QVector annoVec = QVector(); // Process event annotation record long pos = 0; double offset; double duration; while (pos < charLen) { QString text; bool sign, ok; char c = data[pos]; if ((c != '+') && (c != '-')) // Annotaion must start with a +/- sign break; sign = (data[pos++] == '+'); text = ""; c = data[pos]; do { // collect the offset text += c; pos++; c = data[pos]; } while ((c != AnnoSep) && (c != AnnoDurMark)); // a duration is optional offset = text.toDouble(&ok); if (!ok) { #ifdef EDF_DEBUG qDebug() << "Faulty offset in annotation record "; // sleep(1); #endif break; } if (!sign) offset = -offset; duration = -1.0; // This indicates no duration supplied // First entry if (data[pos] == AnnoDurMark) { // get duration.(preceded by decimal 21 byte) pos++; text = ""; do { // collect the duration text += data[pos]; pos++; } while ((data[pos] != AnnoSep) && (pos < charLen)); // separator code duration = text.toDouble(&ok); if (!ok) { #ifdef EDF_DEBUG qDebug() << "Faulty duration in annotation record "; // sleep(1); #endif break; } } while ((data[pos] == AnnoSep) && (pos < charLen)) { int textLen = 0; pos++; const char * textStart = &data[pos]; if (data[pos] == AnnoEnd) break; if (data[pos] == AnnoSep) { pos++; break; } do { // collect the annotation text pos++; // officially UTF-8 is allowed here, so don't mangle it textLen++; } while ((data[pos] != AnnoSep) && (pos < charLen)); // separator code text = QString::fromUtf8(textStart, textLen); annoVec.push_back( Annotation( offset, duration, text) ); if (pos >= charLen) { #ifdef EDF_DEBUG qDebug() << "Short EDF Annotations record"; // sleep(1); #endif break; } } while ((pos < charLen) && (data[pos] == AnnoEnd)) pos++; if (pos >= charLen) break; } return annoVec; } // Read a 16 bits integer qint16 EDFInfo::Read16() { if ((pos + 2) > datasize) { eof = true; return 0; } #ifdef Q_LITTLE_ENDIAN // Intel, etc... qint16 res = *(qint16 *)&signalPtr[pos]; #else // ARM, PPC, etc.. qint16 res = quint8(signalPtr[pos]) | (qint8(signalPtr[pos+1]) << 8); #endif pos += 2; return res; } QString EDFInfo::ReadBytes(unsigned n) { if ((pos + long(n)) > datasize) { eof = true; return QString(); } QByteArray buf(&signalPtr[pos], n); pos+=n; return buf.trimmed(); } EDFSignal *EDFInfo::lookupLabel(const QString & name, int index) { auto it = signalList.find(name); if (it == signalList.end()) return nullptr; if (index >= it.value().size()) return nullptr; return it.value()[index]; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/edfparser.h000066400000000000000000000153661417327530600234450ustar00rootroot00000000000000/* EDF Parser Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef EDFPARSER_H #define EDFPARSER_H #include #include #include #include #include #include #include "SleepLib/common.h" const QString STR_ext_EDF = "edf"; const QString STR_ext_gz = ".gz"; const char AnnoSep = 20; const char AnnoDurMark = 21; const char AnnoEnd = 0; /*! \struct EDFHeader \brief Represents the EDF+ header structure, used as a place holder while processing the text data. \note More information on the EDF+ file format can be obtained from http://edfplus.info */ struct EDFHeaderRaw { char version[8]; char patientident[80]; char recordingident[80]; char datetime[16]; char num_header_bytes[8]; char reserved[44]; char num_data_records[8]; char dur_data_records[8]; char num_signals[4]; } #ifndef _MSC_VER __attribute__((packed)) #endif ; const int EDFHeaderSize = sizeof(EDFHeaderRaw); /*! \struct EDFHeaderQT \brief Contains the QT version of the EDF header information */ struct EDFHeaderQT { public: long version; QString patientident; QString recordingident; QDateTime startdate_orig; long num_header_bytes; QString reserved44; long num_data_records; double duration_Seconds; int num_signals; }; /*! \struct EDFSignal \brief Contains information about a single EDF+ Signal \note More information on the EDF+ file format can be obtained from http://edfplus.info */ struct EDFSignal { public: // virtual ~EDFSignal(); QString label; //! \brief Name of this Signal QString transducer_type; //! \brief Tranducer Type (source of the data, usually blank) QString physical_dimension; //! \brief The units of measurements represented by this signal EventDataType physical_minimum; //! \brief The minimum limits of the ungained data EventDataType physical_maximum; //! \brief The maximum limits of the ungained data EventDataType digital_minimum; //! \brief The minimum limits of the data with gain and offset applied EventDataType digital_maximum; //! \brief The maximum limits of the data with gain and offset applied EventDataType gain; //! \brief Raw integer data is multiplied by this value EventDataType offset; //! \brief This value is added to the raw data QString prefiltering; //! \brief Any prefiltering methods used (usually blank) long sampleCnt; //! \brief Number of samples per record QString reserved; //! \brief Reserved (usually blank) qint16 * dataArray; //! \brief Pointer to the signals sample data // int pos; //! \brief a non-EDF extra used internally to count the signal data }; /*! \class Annotation \author Phil Olynyk \brief Hold the annotation text from an EDF file */ class Annotation { public: Annotation() { duration = -1.0; }; Annotation( double off, double dur, QString tx ) { offset = off; duration = dur; text = tx; }; virtual ~Annotation() {}; double offset; double duration; QString text; }; /*! \class EDFInfo \author Phil Olynyk \author Mark Watkins \brief Parse an EDF+ data file into a list of EDFSignal's \note More information on the EDF+ file format can be obtained from http://edfplus.info */ class EDFInfo { public: //! \brief Constructs an EDFParser object, opening the filename if one supplied EDFInfo(); virtual ~EDFInfo(); virtual bool Open(const QString & name); //! \brief Open the EDF+ file, and read it's header virtual bool Parse(); //! \brief Parse the EDF+ file into the EDFheaderQT. Must call Open(..) first. virtual bool parseHeader( EDFHeaderRaw * hdrPtr ); //! \brief parse just the edf header for duration, etc virtual EDFSignal * lookupLabel(const QString & name, int index=0); //! \brief Return a ptr to the i'th signal with that name virtual EDFHeaderQT * GetHeader( const QString & name); //! \brief returna pointer to the header block virtual long GetNumSignals() { return edfHdr.num_signals; } //! \brief Returns the number of signals contained in this EDF file virtual long GetNumDataRecords() { return edfHdr.num_data_records; } //! \brief Returns the number of data records contained per signal. virtual double GetDuration() { return edfHdr.duration_Seconds; } //! \brief Returns the duration represented by this EDF file virtual QString GetPatient() { return edfHdr.patientident; } //! \brief Returns the patientid field from the EDF header static QDateTime getStartDT(const QString str); //! \brief Returns the start time using noLocalDST static void setTimeZoneUTC(); //! \brief Sets noLocalDST to UTC (for EDF files using UTC time) // The data members follow static int TZ_offset; static QTimeZone localNoDST; QString filename; //! \brief For debug and error messages EDFHeaderQT edfHdr; //! \brief The header in a QT friendly form QVector edfsignals; //! \brief Holds the EDFSignals contained in this edf file QVector< QVector > annotations; //! \brief Holds the Annotaions for this EDF file QStringList signal_labels; //! \brief An by-name indexed into the EDFSignal data QHash > signalList; //! \brief ResMed sometimes re-uses the SAME signal name // the following could be private private: QVector ReadAnnotations( const char * data, int charLen ); //! \brief Create an Annotaion vector from the signal values QString ReadBytes(unsigned n); //! \brief Read n bytes of 8 bit data from the EDF+ data stream qint16 Read16(); //! \brief Read 16 bit word of data from the EDF+ data stream //! \brief This is the array holding the EDF file data QByteArray fileData; //! \brief The EDF+ files header structure, used as a place holder while processing the text data. EDFHeaderRaw *hdrPtr; //! \brief This is the array of signal descriptors and values char *signalPtr; long filesize; long datasize; long pos; bool eof; }; #endif // EDFPARSER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/icon_loader.cpp000066400000000000000000000704051417327530600242760ustar00rootroot00000000000000/* SleepLib Fisher & Paykel Icon Loader Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include "icon_loader.h" const QString FPHCARE = "FPHCARE"; FPIcon::FPIcon(Profile *profile, MachineID id) : CPAP(profile, id) { } FPIcon::~FPIcon() { } FPIconLoader::FPIconLoader() { m_buffer = nullptr; m_type = MT_CPAP; } FPIconLoader::~FPIconLoader() { } /* * getIconDir - returns the path to the ICON directory */ QString getIconDir2 (QString givenpath) { QString path = givenpath; path = path.replace("\\", "/"); if (path.endsWith("/")) { path.chop(1); } if (path.endsWith("/" + FPHCARE)) { path = path.section("/",0,-2); } QDir dir(path); if (!dir.exists()) { return ""; } // If this is a backup directory, higher level directories have been // omitted. if (path.endsWith("/Backup/", Qt::CaseInsensitive)) return path; // F&P Icon have a folder called FPHCARE in the root directory if (!dir.exists(FPHCARE)) { return ""; } // CHECKME: I can't access F&P ICON data right now if (!dir.exists("FPHCARE/ICON")) { return ""; } return dir.filePath("FPHCARE/ICON"); } /* * getIconMachines returns a list of all Iocn machine folders in the ICON directory */ QStringList getIconMachines (QString iconPath) { QStringList iconMachines; QDir iconDir (iconPath); iconDir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks); iconDir.setSorting(QDir::Name); QFileInfoList flist = iconDir.entryInfoList(); // List of Icon subdirectories // Walk though directory list and save those that appear to be for SleepStyle machins. for (int i = 0; i < flist.size(); i++) { QFileInfo fi = flist.at(i); QString filename = fi.fileName(); // directory is serial number and must have a SUM*.FPH file within it to be an Icon or SleepStyle folder QDir machineDir (iconPath + "/" + filename); machineDir.setFilter(QDir::NoDotAndDotDot | QDir::Files | QDir::Hidden | QDir::NoSymLinks); machineDir.setSorting(QDir::Name); QStringList filters; filters << "SUM*.fph"; machineDir.setNameFilters(filters); QFileInfoList flist = machineDir.entryInfoList(); if (flist.size() <= 0) { continue; } // Find out what machine model this is QFile sumFile (flist.at(0).absoluteFilePath()); QString line; sumFile.open(QIODevice::ReadOnly); QTextStream instr(&sumFile); for (int j = 0; j < 5; j++) { line = ""; QString c = ""; while ((c = instr.read(1)) != "\r") { line += c; } } sumFile.close(); if (line.toUpper() == "ICON") iconMachines.push_back(filename); } return iconMachines; } bool FPIconLoader::Detect(const QString & givenpath) { QString iconPath = getIconDir2(givenpath); if (iconPath.isEmpty()) return false; QStringList machines = getIconMachines(iconPath); if (machines.length() <= 0) // Did not find any SleepStyle machine directories return false; return true; /**** QString path = givenpath; path = path.replace("\\", "/"); if (path.endsWith("/")) { path.chop(1); } if (path.endsWith("/" + FPHCARE)) { path = path.section("/",0,-2); } QDir dir(path); if (!dir.exists()) { return false; } // F&P Icon have a folder called FPHCARE in the root directory if (!dir.exists(FPHCARE)) { return false; } // CHECKME: I can't access F&P ICON data right now if (!dir.exists("FPHCARE/ICON")) { return false; } // ICON serial numbers (directory names) are all digits (SleepStyle are mixed alpha and numeric) QString serialDir(dir.path() + "/FPHCARE/ICON"); QDir iconDir(serialDir); iconDir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks); iconDir.setSorting(QDir::Name); QFileInfoList flist = iconDir.entryInfoList(); bool ok; for (int i = 0; i < flist.size(); i++) { QFileInfo fi = flist.at(i); QString filename = fi.fileName(); filename.toInt(&ok); if (!ok) { return false; } } return true; ****/ } int FPIconLoader::Open(const QString & path) { QString iconPath = getIconDir2(path); if (iconPath.isEmpty()) return false; QStringList serialNumbers = getIconMachines(iconPath); if (serialNumbers.length() <= 0) // Did not find any SleepStyle machine directories return false; Machine *m; QString npath; int c = 0; for (int i = 0; i < serialNumbers.size(); i++) { MachineInfo info = newInfo(); info.serial = serialNumbers[i]; m = p_profile->CreateMachine(info); npath = iconPath + "/" + info.serial; try { if (m) { c+=OpenMachine(m, npath); } } catch (OneTypePerDay& e) { Q_UNUSED(e) p_profile->DelMachine(m); MachList.erase(MachList.find(info.serial)); QMessageBox::warning(nullptr, tr("Import Error"), tr("This Machine Record cannot be imported in this profile.")+"\n\n"+tr("The Day records overlap with already existing content."), QMessageBox::Ok); delete m; } } return c; } struct FPWaveChunk { FPWaveChunk() { st = 0; duration = 0; flow = nullptr; pressure = nullptr; leak = nullptr; file = 0; } FPWaveChunk(qint64 start, qint64 dur, int f) { st = start; duration = dur; file = f, flow = nullptr; leak = nullptr; pressure = nullptr; } FPWaveChunk(const FPWaveChunk ©) { st = copy.st; duration = copy.duration; flow = copy.flow; leak = copy.leak; pressure = copy.pressure; file = copy.file; } qint64 st; qint64 duration; int file; EventList *flow; EventList *leak; EventList *pressure; }; bool operator<(const FPWaveChunk &a, const FPWaveChunk &b) { return (a.st < b.st); } int FPIconLoader::OpenMachine(Machine *mach, const QString & path) { qDebug() << "Opening FPIcon " << path; QDir dir(path); if (!dir.exists() || (!dir.isReadable())) { return -1; } dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); QFileInfoList flist = dir.entryInfoList(); QString filename, fpath; emit setProgressValue(0); QStringList summary, log, flw, det; Sessions.clear(); for (int i = 0; i < flist.size(); i++) { QFileInfo fi = flist.at(i); filename = fi.fileName(); fpath = path + "/" + filename; if (filename.left(3).toUpper() == "SUM") { summary.push_back(fpath); OpenSummary(mach, fpath); } else if (filename.left(3).toUpper() == "DET") { det.push_back(fpath); } else if (filename.left(3).toUpper() == "FLW") { flw.push_back(fpath); } else if (filename.left(3).toUpper() == "LOG") { log.push_back(fpath); } } for (int i = 0; i < det.size(); i++) { OpenDetail(mach, det[i]); } for (int i = 0; i < flw.size(); i++) { OpenFLW(mach, flw[i]); } SessionID sid;//,st; float hours, mins; qDebug() << "Last 20 Sessions"; int cnt = 0; QDateTime dt; QString a; if (Sessions.size() > 0) { QMap::iterator it = Sessions.end(); it--; dt = QDateTime::fromTime_t(qint64(it.value()->first()) / 1000L); QDate date = dt.date().addDays(-7); it++; do { it--; Session *sess = it.value(); sid = sess->session(); hours = sess->hours(); mins = hours * 60; dt = QDateTime::fromTime_t(sid); if (sess->channelDataExists(CPAP_FlowRate)) { a = "(flow)"; } else { a = ""; } qDebug() << cnt << ":" << dt << "session" << sid << "," << mins << "minutes" << a; if (dt.date() < date) { break; } ++cnt; } while (it != Sessions.begin()); } // qDebug() << "Unmatched Sessions"; // QList chunks; // for (QMap::iterator dit=FLWDate.begin();dit!=FLWDate.end();dit++) { // int k=dit.key(); // //QDate date=dit.value(); //// QList values = SessDate.values(date); // for (int j=0;jchannelDataExists(CPAP_FlowRate)) c=true; // } // qDebug() << k << "-" <channelDataExists(CPAP_FlowRate)) c=true; // } // qDebug() << chunk.file << ":" << i << zz << dur << "minutes" << (b ? "*" : "") << (c ? QDateTime::fromTime_t(zz).toString() : ""); // } int c = Sessions.size(); finishAddingSessions(); mach->Save(); return c; } // !\brief Convert F&P 32bit date format to 32bit UNIX Timestamp quint32 convertDate(quint32 timestamp) { quint16 day, month,hour=0, minute=0, second=0; quint16 year; day = timestamp & 0x1f; month = (timestamp >> 5) & 0x0f; year = 2000 + ((timestamp >> 9) & 0x3f); timestamp >>= 15; second = timestamp & 0x3f; minute = (timestamp >> 6) & 0x3f; hour = (timestamp >> 12); QDateTime dt = QDateTime(QDate(year, month, day), QTime(hour, minute, second),Qt::UTC); // Q NO!!! _ASSERT(dt.isValid()); // if ((year == 2013) && (month == 9) && (day == 18)) { // // this is for testing.. set a breakpoint on here and // int i=5; // } // From Rudd's data set compared to times reported from his F&P software's report (just the time bits left over) // 90514 = 00:06:18 WET 23:06:18 UTC 09:06:18 AEST // 94360 = 01:02:24 WET // 91596 = 00:23:12 WET // 19790 = 23:23:50 WET return dt.addSecs(-54).toTime_t(); } quint32 convertFLWDate(quint32 timestamp) // Bit format: hhhhhmmmmmmssssssYYYYYYMMMMDDDDD { quint16 day, month, hour, minute, second; quint16 year; day = timestamp & 0x1f; month = (timestamp >> 5) & 0x0f; year = 2000 + ((timestamp >> 9) & 0x3f); timestamp >>= 15; second = timestamp & 0x3f; minute = (timestamp >> 6) & 0x3f; hour = (timestamp >> 12); QDateTime dt = QDateTime(QDate(year, month, day), QTime(hour, minute, second), Qt::UTC); if(!dt.isValid()){ // make this date too early, then change test later // dt = QDateTime(QDate(2015,1,1), QTime(0,0,1)); dt = QDateTime(QDate(2010,1,1), QTime(0,0,0)); } // Q NO!!! _ASSERT(dt.isValid()); // if ((year == 2013) && (month == 9) && (day == 18)) { // int i=5; // } // 87922 23:23:50 WET return dt.addSecs(-54).toTime_t(); } //QDateTime FPIconLoader::readFPDateTime(quint8 *data) //{ // quint32 ts = (data[3] << 24) | (data[2] << 16) | ((data[1] << 8) | data[0]); // ^ 0xc00000; // // 0x20a41b18 // quint8 day = ts & 0x1f; // 0X18 24 // ts >>= 5; // 10520D8 // quint8 month = ts & 0x0f; // 0X08 8 // ts >>= 4; // 10520D // quint8 year = ts & 0x3f; // 0X0D 13 // ts >>= 6; // 4148 // quint8 second = ts & 0x3f; // 0X08 8 // ts >>= 6; // 20A // quint8 minute = ts & 0x3f; // 0A 10 // ts >>= 6; // 10 // quint8 hour = ts & 0x1f; // 10 16 // QDate date = QDate(2000 + year, month, day); // QTime time = QTime(hour, minute, second); // QDateTime datetime = QDateTime(date, time, Qt::UTC); // return datetime; //} /* *in >> a1; in >> a2; t1=a2 << 8 | a1; if (t1==0xfafe) break; day=t1 & 0x1f; month=(t1 >> 5) & 0x0f; year=2000+((t1 >> 9) & 0x3f); in >> a1; in >> a2; ts=((a2 << 8) | a1) << 1; ts|=(t1 >> 15) & 1; second=(ts & 0x3f); minute=(ts >> 6) & 0x3f; hour=(ts >> 12) & 0x1f; */ // FLW Header Structure // 0x0000-0x01fe // newline (0x0d) seperated list of machine information strings. // magic? 0201 // version 1.5.0 // serial number 12 digits // Machine Series "ICON" // Machine Model "Auto" // Remainder of header is 0 filled... // 0x01ff 8 bit additive sum checksum byte of previous header bytes // 0x0200-0x0203 32bit timestamp in bool FPIconLoader::OpenFLW(Machine *mach, const QString & filename) { Q_UNUSED(mach); quint32 ts; double ti; EventList *flow = nullptr, * pressure = nullptr; qDebug() << filename; QFile file(filename); if (!file.open(QFile::ReadOnly)) { qDebug() << "Couldn't open" << filename; return false; } QByteArray header = file.read(0x200); if (header.size() != 0x200) { qDebug() << "Short file" << filename; return false; } unsigned char hsum = 0x0; for (int i = 0; i < 0x1ff; i++) { hsum ^= header[i]; } if (hsum != header[0x1ff]) { qDebug() << "Header checksum mismatch" << filename; } QTextStream htxt(&header); QString h1, version, fname, serial, model, type; htxt >> h1; htxt >> version; htxt >> fname; htxt >> serial; htxt >> model; htxt >> type; if (mach->model().isEmpty()) { mach->setModel(model+" "+type); } QByteArray buf = file.read(4); unsigned char * data = (unsigned char *)buf.data(); quint32 t2 = data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24; // this line is probably superflous crud. if (t2 == 0xffffffff) return false; QByteArray block = file.readAll(); file.close(); data = (unsigned char *)block.data(); // Abort if crapy if (!(data[103]==0xff && data[104]==0xff)) return false; ts = convertFLWDate(t2); if (ts < QDateTime(QDate(2010,1,1), QTime(0,1,0)).toTime_t()) { return false; } ti = qint64(ts) * 1000L; QMap::iterator sit = Sessions.find(ts); Session *sess; bool newsess = false; if (sit != Sessions.end()) { sess = sit.value(); // qDebug() << filenum << ":" << date << sess->session() << ":" << sess->hours() * 60.0; } else { // Create a session qint64 k = -1; Session *s1 = nullptr; sess = nullptr; sit = Sessions.end(); if (Sessions.begin() != sit) { do { sit--; s1 = sit.value(); qint64 z = qAbs(qint64(sit.key()) - qint64(ts)); if (z < 3600) { if ((k < 0) || (k > z)) { k = z; sess = s1; } } } while (sit != Sessions.begin()); } if (sess) { sess->set_first(ti); sess->setFirst(CPAP_FlowRate, ti); sess->setFirst(CPAP_MaskPressure, ti); } else { sess = new Session(mach, ts); sess->set_first(ti); sess->setFirst(CPAP_FlowRate, ti); sess->setFirst(CPAP_MaskPressure, ti); newsess = true; // qDebug() << filenum << ":" << date << "couldn't find matching session for" << ts; } } const int samples_per_block = 50; const double rate = 1000.0 / double(samples_per_block); // F&P Overwrites this file, not appends to it. flow = new EventList(EVL_Waveform, 1.0F, 0, 0, 0, rate); pressure = new EventList(EVL_Event, 0.01F, 0, 0, 0, rate * double(samples_per_block)); flow->setFirst(ti); pressure->setFirst(ti); quint16 endMarker; qint8 offset; // offset from center for this block quint16 pres; // mask pressure qint16 tmp; qint16 samples[samples_per_block]; EventDataType val; unsigned char *p = data; int datasize = block.size(); unsigned char *end = data+datasize; do { endMarker = *((quint16 *)p); if (endMarker == 0xffff) { p += 2; continue; } if (endMarker == 0x7fff) { break; } offset = ((qint8*)p)[102]; for (int i=0; i< samples_per_block; ++i) { tmp = ((char *)p)[1] << 8 | p[0]; p += 2; // Assuming Litres per hour, converting to litres per minute and applying offset? // As in should be 60.0? val = (EventDataType(tmp) / 100.0) - offset; samples[i] = val; } flow->AddWaveform(ti, samples, samples_per_block, rate); pres = *((quint16 *)p); pressure->AddEvent(ti, pres); ti += samples_per_block * rate; p+=3; // (offset too) } while (p < end); if (endMarker != 0x7fff) { qDebug() << fname << "waveform does not end with the corrent marker" << hex << endMarker; } if (sess) { sess->setLast(CPAP_FlowRate, ti); sess->setLast(CPAP_MaskPressure, ti); sess->eventlist[CPAP_FlowRate].push_back(flow); sess->eventlist[CPAP_MaskPressure].push_back(pressure); } if (newsess) { addSession(sess); } if (p_profile->session->backupCardData()) { QString backup = mach->getBackupPath()+"FPHCARE/ICON/"+serial.right(serial.size()-4)+"/"; QDir dir; QString newname = QString("FLW%1.FPH").arg(ts); dir.mkpath(backup); dir.cd(backup); if (!dir.exists(newname)) { file.copy(backup+newname); } } return true; } //////////////////////////////////////////////////////////////////////////////////////////// // Open Summary file //////////////////////////////////////////////////////////////////////////////////////////// bool FPIconLoader::OpenSummary(Machine *mach, const QString & filename) { qDebug() << filename; QByteArray header; QFile file(filename); if (!file.open(QFile::ReadOnly)) { qDebug() << "Couldn't open" << filename; return false; } header = file.read(0x200); if (header.size() != 0x200) { qDebug() << "Short file" << filename; return false; } unsigned char hsum = 0xff; for (int i = 0; i < 0x1ff; i++) { hsum += header[i]; } if (hsum != header[0x1ff]) { qDebug() << "Header checksum mismatch" << filename; } QTextStream htxt(&header); QString h1, version, fname, serial, model, type; htxt >> h1; htxt >> version; htxt >> fname; htxt >> serial; htxt >> model; htxt >> type; mach->setModel(model + " " + type); QByteArray data; data = file.readAll(); QDataStream in(data); in.setVersion(QDataStream::Qt_4_8); in.setByteOrder(QDataStream::LittleEndian); quint32 ts; //QByteArray line; unsigned char a1, a2, a3, a4, a5, p1, p2, p3, p4, p5, j1, j2, j3 , j4, j5, j6, j7, x1, x2; quint16 d1, d2, d3; int usage; //,runtime; QDate date; do { in >> ts; if (ts == 0xffffffff) break; if ((ts & 0xfafe) == 0xfafe) break; ts = convertDate(ts); // the following two quite often match in value in >> a1; // 0x04 Run Time in >> a2; // 0x05 Usage Time //runtime = a1 * 360; // durations are in tenth of an hour intervals usage = a2 * 360; in >> a3; // 0x06 // Ramps??? in >> a4; // 0x07 // a pressure value? in >> a5; // 0x08 // ?? varies.. always less than 90% leak.. in >> d1; // 0x09 in >> d2; // 0x0b in >> d3; // 0x0d // 90% Leak value.. in >> p1; // 0x0f in >> p2; // 0x10 in >> j1; // 0x11 in >> j2; // 0x12 // Apnea Events in >> j3; // 0x13 // Hypopnea events in >> j4; // 0x14 // Flow Limitation events in >> j5; // 0x15 in >> j6; // 0x16 in >> j7; // 0x17 in >> p3; // 0x18 in >> p4; // 0x19 in >> p5; // 0x1a in >> x1; // 0x1b in >> x2; // 0x1c // humidifier setting if (!mach->SessionExists(ts)) { Session *sess = new Session(mach, ts); sess->really_set_first(qint64(ts) * 1000L); sess->really_set_last(qint64(ts + usage) * 1000L); sess->SetChanged(true); sess->setCount(CPAP_Obstructive, j2); sess->setCph(CPAP_Obstructive, j2 / (float(usage)/3600.00)); sess->setCount(CPAP_Hypopnea, j3); sess->setCph(CPAP_Hypopnea, j3 / (float(usage)/3600.00)); sess->setCount(CPAP_FlowLimit, j4); sess->setCph(CPAP_FlowLimit, j4 / (float(usage)/3600.00)); SessDate.insert(date, sess); // sess->setCount(CPAP_Obstructive,j1); // sess->setCount(CPAP_Hypopnea,j2); // sess->setCount(CPAP_ClearAirway,j3); // sess->setCount(CPAP_Apnea,j4); //sess->setCount(CPAP_,j5); if (p1 != p2) { sess->settings[CPAP_Mode] = (int)MODE_APAP; sess->settings[CPAP_PressureMin] = p3 / 10.0; sess->settings[CPAP_PressureMax] = p4 / 10.0; } else { sess->settings[CPAP_Mode] = (int)MODE_CPAP; sess->settings[CPAP_Pressure] = p1 / 10.0; } sess->settings[CPAP_HumidSetting] = x2; //sess->settings[CPAP_PresReliefType]=PR_SENSAWAKE; Sessions[ts] = sess; addSession(sess); } } while (!in.atEnd()); if (p_profile->session->backupCardData()) { QString backup = mach->getBackupPath()+"FPHCARE/ICON/"+serial.right(serial.size()-4)+"/"; QDir dir; QString newname = QString("SUM%1.FPH").arg(QDate::currentDate().year(),4,10,QChar('0')); dir.mkpath(backup); dir.cd(backup); if (!dir.exists(newname)) { file.copy(backup+newname); } } return true; } bool FPIconLoader::OpenDetail(Machine *mach, const QString & filename) { Q_UNUSED(mach); qDebug() << filename; QByteArray header; QFile file(filename); if (!file.open(QFile::ReadOnly)) { qDebug() << "Couldn't open" << filename; return false; } header = file.read(0x200); if (header.size() != 0x200) { qDebug() << "Short file" << filename; return false; } // Calculate and test checksum unsigned char hsum = 0; for (int i = 0; i < 0x1ff; i++) { hsum += header[i]; } if (hsum != header[0x1ff]) { qDebug() << "Header checksum mismatch" << filename; } QTextStream htxt(&header); QString h1, version, fname, serial, model, type; htxt >> h1; htxt >> version; htxt >> fname; htxt >> serial; htxt >> model; htxt >> type; QByteArray index = file.read(0x800); if (index.size()!=0x800) { // faulty file.. return false; } QDataStream in(index); quint32 ts; in.setVersion(QDataStream::Qt_4_6); in.setByteOrder(QDataStream::LittleEndian); QVector times; QVector start; QVector records; quint16 strt; quint8 recs; int totalrecs = 0; do { in >> ts; if (ts == 0xffffffff) break; if ((ts & 0xfafe) == 0xfafe) break; ts = convertDate(ts); in >> strt; in >> recs; totalrecs += recs; if (Sessions.contains(ts)) { times.push_back(ts); start.push_back(strt); records.push_back(recs); } } while (!in.atEnd()); QByteArray databytes = file.readAll(); in.setVersion(QDataStream::Qt_4_6); in.setByteOrder(QDataStream::BigEndian); // 5 byte repeating patterns quint8 *data = (quint8 *)databytes.data(); qint64 ti; quint8 pressure, leak, a1, a2, a3, a4; // quint8 sa1, sa2; // The two sense awake bits per 2 minutes SessionID sessid; Session *sess; int idx; for (int r = 0; r < start.size(); r++) { sessid = times[r]; sess = Sessions[sessid]; ti = qint64(sessid) * 1000L; sess->really_set_first(ti); EventList *LK = sess->AddEventList(CPAP_LeakTotal, EVL_Event, 1); EventList *PR = sess->AddEventList(CPAP_Pressure, EVL_Event, 0.1F); EventList *OA = sess->AddEventList(CPAP_Obstructive, EVL_Event); EventList *H = sess->AddEventList(CPAP_Hypopnea, EVL_Event); EventList *FL = sess->AddEventList(CPAP_FlowLimit, EVL_Event); EventList *SA = sess->AddEventList(CPAP_SensAwake, EVL_Event); unsigned stidx = start[r]; int rec = records[r]; idx = stidx * 15; quint8 bitmask; for (int i = 0; i < rec; ++i) { for (int j = 0; j < 3; ++j) { pressure = data[idx]; PR->AddEvent(ti+120000, pressure); leak = data[idx + 1]; LK->AddEvent(ti+120000, leak); a1 = data[idx + 2]; // [0..5] Obstructive flag, [6..7] Unknown a2 = data[idx + 3]; // [0..5] Hypopnea, [6..7] Unknown a3 = data[idx + 4]; // [0..5] Flow Limitation, [6..7] SensAwake bitflags, 1 per minute // Sure there isn't 6 SenseAwake bits? // a4 = (a1 >> 6) << 4 | ((a2 >> 6) << 2) | (a3 >> 6); // this does the same thing as behaviour a4 = (a3 >> 7) << 3 | ((a3 >> 6) & 1); bitmask = 1; for (int k = 0; k < 6; k++) { // There are 6 flag sets per 2 minutes if (a1 & bitmask) { OA->AddEvent(ti, 1); } if (a2 & bitmask) { H->AddEvent(ti, 1); } if (a3 & bitmask) { FL->AddEvent(ti, 1); } if (a4 & bitmask) { SA->AddEvent(ti, 1); } bitmask <<= 1; ti += 20000L; // Increment 20 seconds } idx += 5; } } // sess->really_set_last(ti-360000L); // sess->SetChanged(true); // addSession(sess,profile); } if (p_profile->session->backupCardData()) { unsigned char *data = (unsigned char *)index.data(); ts = data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24; ts = convertDate(ts); QString backup = mach->getBackupPath()+"FPHCARE/ICON/"+serial.right(serial.size()-4)+"/"; QDir dir; QString newname = QString("DET%1.FPH").arg(ts); dir.mkpath(backup); dir.cd(backup); if (!dir.exists(newname)) { file.copy(backup+newname); } } return 1; } bool fpicon_initialized = false; void FPIconLoader::Register() { if (fpicon_initialized) { return; } qDebug() << "Registering F&P Icon Loader"; RegisterLoader(new FPIconLoader()); //InitModelMap(); fpicon_initialized = true; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/icon_loader.h000066400000000000000000000073621417327530600237450ustar00rootroot00000000000000/* SleepLib Fisher & Paykel Icon Loader Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef ICON_LOADER_H #define ICON_LOADER_H #include #include "SleepLib/machine.h" #include "SleepLib/machine_loader.h" #include "SleepLib/profiles.h" //******************************************************************************************** /// IMPORTANT!!! //******************************************************************************************** // Please INCREMENT the following value when making changes to this loaders implementation. // const int fpicon_data_version = 3; // //******************************************************************************************** /*! \class FPIcon \brief F&P Icon customized machine object */ class FPIcon: public CPAP { public: FPIcon(Profile *, MachineID id = 0); virtual ~FPIcon(); }; const int fpicon_load_buffer_size = 1024 * 1024; const QString fpicon_class_name = STR_MACH_FPIcon; /*! \class FPIconLoader \brief Loader for Fisher & Paykel Icon data This is only relatively recent addition and still needs more work */ class FPIconLoader : public CPAPLoader { Q_OBJECT public: FPIconLoader(); virtual ~FPIconLoader(); //! \brief Detect if the given path contains a valid Folder structure virtual bool Detect(const QString & path); //! \brief Scans path for F&P Icon data signature, and Loads any new data virtual int Open(const QString & path); int OpenMachine(Machine *mach, const QString &path); bool OpenSummary(Machine *mach, const QString & path); bool OpenDetail(Machine *mach, const QString & path); bool OpenFLW(Machine *mach, const QString & filename); //! \brief Returns SleepLib database version of this F&P Icon loader virtual int Version() { return fpicon_data_version; } //! \brief Returns the machine class name of this CPAP machine, "FPIcon" virtual const QString & loaderName() { return fpicon_class_name; } // ! \brief Creates a machine object, indexed by serial number //Machine *CreateMachine(QString serial); virtual MachineInfo newInfo() { return MachineInfo(MT_CPAP, 0, fpicon_class_name, QObject::tr("Fisher & Paykel"), QString(), QString(), QString(), QObject::tr("ICON"), QDateTime::currentDateTime(), fpicon_data_version); } //! \brief Registers this MachineLoader with the master list, so F&P Icon data can load static void Register(); //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Now for some CPAPLoader overrides //////////////////////////////////////////////////////////////////////////////////////////////////////////// virtual QString presRelType() { return QObject::tr(""); } // might not need this one virtual ChannelID presRelSet() { return NoChannel; } virtual ChannelID presRelLevel() { return NoChannel; } //////////////////////////////////////////////////////////////////////////////////////////////////////////// protected: QDateTime readFPDateTime(quint8 *data); QString last; QHash MachList; QMap Sessions; QMultiMap SessDate; //QMap > FLWMapFlow; //QMap > FLWMapLeak; //QMap > FLWMapPres; //QMap > FLWDuration; //QMap > FLWTS; //QMap FLWDate; unsigned char *m_buffer; }; #endif // ICON_LOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/intellipap_loader.cpp000066400000000000000000003115001417327530600255010ustar00rootroot00000000000000/* SleepLib (DeVilbiss) Intellipap Loader Implementation * * Notes: Intellipap DV54 requires the SmartLink attachment to access this data. * * Copyright (c) 2011-2018 Mark Watkins * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include "intellipap_loader.h" //#define DEBUG6 ChannelID INTP_SmartFlexMode, INTP_SmartFlexLevel; Intellipap::Intellipap(Profile *profile, MachineID id) : CPAP(profile, id) { } Intellipap::~Intellipap() { } IntellipapLoader::IntellipapLoader() { const QString INTELLIPAP_ICON = ":/icons/intellipap.png"; const QString DV6_ICON = ":/icons/dv64.png"; QString s = newInfo().series; m_pixmap_paths[s] = INTELLIPAP_ICON; m_pixmaps[s] = QPixmap(INTELLIPAP_ICON); m_pixmap_paths["DV6"] = DV6_ICON; m_pixmaps["DV6"] = QPixmap(DV6_ICON); m_buffer = nullptr; m_type = MT_CPAP; } IntellipapLoader::~IntellipapLoader() { } const QString SET_BIN = "SET.BIN"; const QString SET1 = "SET1"; const QString DV6 = "DV6"; const QString SL = "SL"; const QString DV6_DIR = "/" + DV6; const QString SL_DIR = "/" + SL; bool IntellipapLoader::Detect(const QString & givenpath) { QString path = givenpath; if (path.endsWith(SL_DIR)) { path.chop(3); } if (path.endsWith(DV6_DIR)) { path.chop(4); } QDir dir(path); if (!dir.exists()) { return false; } // Intellipap DV54 has a folder called SL in the root directory, DV64 has DV6 if (dir.cd(SL)) { // Test for presence of settings file return dir.exists(SET1) ? true : false; } if (dir.cd(DV6)) { // DV64 return dir.exists(SET_BIN) ? true : false; } return false; } enum INTPAP_Type { INTPAP_Unknown, INTPAP_DV5, INTPAP_DV6 }; int IntellipapLoader::OpenDV5(const QString & path) { QString newpath = path + SL_DIR; QString filename; qDebug() << "DV5 Loader started"; ////////////////////////// // Parse the Settings File ////////////////////////// filename = newpath + "/" + SET1; QFile f(filename); if (!f.exists()) { return -1; } f.open(QFile::ReadOnly); QTextStream tstream(&f); const QString INT_PROP_Serial = "Serial"; const QString INT_PROP_Model = "Model"; const QString INT_PROP_Mode = "Mode"; const QString INT_PROP_MaxPressure = "Max Pressure"; const QString INT_PROP_MinPressure = "Min Pressure"; const QString INT_PROP_IPAP = "IPAP"; const QString INT_PROP_EPAP = "EPAP"; const QString INT_PROP_PS = "PS"; const QString INT_PROP_RampPressure = "Ramp Pressure"; const QString INT_PROP_RampTime = "Ramp Time"; const QString INT_PROP_HourMeter = "Usage Hours"; const QString INT_PROP_ComplianceMeter = "Compliance Hours"; const QString INT_PROP_ErrorCode = "Error"; const QString INT_PROP_LastErrorCode = "Long Error"; const QString INT_PROP_LowUseThreshold = "Low Usage"; const QString INT_PROP_SmartFlex = "SmartFlex"; const QString INT_PROP_SmartFlexMode = "SmartFlexMode"; QHash lookup; lookup["Sn"] = INT_PROP_Serial; lookup["Mn"] = INT_PROP_Model; lookup["Mo"] = INT_PROP_Mode; // 0 cpap, 1 auto //lookup["Pn"]="??"; lookup["Pu"] = INT_PROP_MaxPressure; lookup["Pl"] = INT_PROP_MinPressure; lookup["Pi"] = INT_PROP_IPAP; lookup["Pe"] = INT_PROP_EPAP; // == WF on Auto models lookup["Ps"] = INT_PROP_PS; // == WF on Auto models, Pressure support //lookup["Ds"]="??"; //lookup["Pc"]="??"; lookup["Pd"] = INT_PROP_RampPressure; lookup["Dt"] = INT_PROP_RampTime; //lookup["Ld"]="??"; //lookup["Lh"]="??"; //lookup["FC"]="??"; //lookup["FE"]="??"; //lookup["FL"]="??"; lookup["A%"]="ApneaThreshold"; lookup["Ad"]="ApneaDuration"; lookup["H%"]="HypopneaThreshold"; lookup["Hd"]="HypopneaDuration"; //lookup["Pi"]="??"; //lookup["Pe"]="??"; lookup["Ri"]="SmartFlexIRnd"; // Inhale Rounding (0-5) lookup["Re"]="SmartFlexERnd"; // Inhale Rounding (0-5) //lookup["Bu"]="??"; // WF //lookup["Ie"]="??"; // 20 //lookup["Se"]="??"; // 05 //Inspiratory trigger? //lookup["Si"]="??"; // 05 // Expiratory Trigger? //lookup["Mi"]="??"; // 0 lookup["Uh"]="HoursMeter"; // 0000.0 lookup["Up"]="ComplianceMeter"; // 0000.00 //lookup["Er"]="ErrorCode";, // E00 //lookup["El"]="LongErrorCode"; // E00 00/00/0000 //lookup["Hp"]="??";, // 1 //lookup["Hs"]="??";, // 02 //lookup["Lu"]="LowUseThreshold"; // defaults to 0 (4 hours) lookup["Sf"] = INT_PROP_SmartFlex; lookup["Sm"] = INT_PROP_SmartFlexMode; lookup["Ks=s"]="Ks_s"; lookup["Ks=i"]="ks_i"; QHash set1; QHash::iterator hi; Machine *mach = nullptr; MachineInfo info = newInfo(); bool ok; //EventDataType min_pressure = 0, max_pressure = 0, set_ipap = 0, set_ps = 0, EventDataType ramp_pressure = 0, set_epap = 0, ramp_time = 0; int papmode = 0, smartflex = 0, smartflexmode = 0; while (1) { QString line = tstream.readLine(); if ((line.length() <= 2) || (line.isNull())) { break; } QString key = line.section("\t", 0, 0).trimmed(); hi = lookup.find(key); if (hi != lookup.end()) { key = hi.value(); } QString value = line.section("\t", 1).trimmed(); if (key == INT_PROP_Mode) { papmode = value.toInt(&ok); } else if (key == INT_PROP_Serial) { info.serial = value; } else if (key == INT_PROP_Model) { info.model = value; } else if (key == INT_PROP_MinPressure) { //min_pressure = value.toFloat() / 10.0; } else if (key == INT_PROP_MaxPressure) { //max_pressure = value.toFloat() / 10.0; } else if (key == INT_PROP_IPAP) { //set_ipap = value.toFloat() / 10.0; } else if (key == INT_PROP_EPAP) { set_epap = value.toFloat() / 10.0; } else if (key == INT_PROP_PS) { //set_ps = value.toFloat() / 10.0; } else if (key == INT_PROP_RampPressure) { ramp_pressure = value.toFloat() / 10.0; } else if (key == INT_PROP_RampTime) { ramp_time = value.toFloat() / 10.0; } else if (key == INT_PROP_SmartFlex) { smartflex = value.toInt(); } else if (key == INT_PROP_SmartFlexMode) { smartflexmode = value.toInt(); } else { set1[key] = value; } qDebug() << key << "=" << value; } CPAPMode mode = MODE_UNKNOWN; switch (papmode) { case 0: mode = MODE_CPAP; break; case 1: mode = (set_epap > 0) ? MODE_BILEVEL_FIXED : MODE_APAP; break; default: qDebug() << "New machine mode"; } if (!info.serial.isEmpty()) { mach = p_profile->CreateMachine(info); } if (!mach) { qDebug() << "Couldn't get Intellipap machine record"; return -1; } QString backupPath = mach->getBackupPath(); QString copypath = path; if (QDir::cleanPath(path).compare(QDir::cleanPath(backupPath)) != 0) { copyPath(path, backupPath); } // Refresh properties data.. for (QHash::iterator i = set1.begin(); i != set1.end(); i++) { mach->info.properties[i.key()] = i.value(); } f.close(); /////////////////////////////////////////////// // Parse the Session Index (U File) /////////////////////////////////////////////// unsigned char buf[27]; filename = newpath + "/U"; f.setFileName(filename); if (!f.exists()) { return -1; } QVector SessionStart; QVector SessionEnd; QHash Sessions; quint32 ts1, ts2;//, length; //unsigned char cs; f.open(QFile::ReadOnly); int cnt = 0; QDateTime epoch(QDate(2002, 1, 1), QTime(0, 0, 0), Qt::UTC); // Intellipap Epoch int ep = epoch.toTime_t(); do { cnt = f.read((char *)buf, 9); // big endian ts1 = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; ts2 = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; // buf[8] == ??? What is this byte? A Bit Field? A checksum? ts1 += ep; ts2 += ep; SessionStart.append(ts1); SessionEnd.append(ts2); } while (cnt > 0); qDebug() << "U file logs" << SessionStart.size() << "sessions."; f.close(); /////////////////////////////////////////////// // Parse the Session Data (L File) /////////////////////////////////////////////// filename = newpath + "/L"; f.setFileName(filename); if (!f.exists()) { return -1; } f.open(QFile::ReadOnly); long size = f.size(); int recs = size / 26; m_buffer = new unsigned char [size]; if (size != f.read((char *)m_buffer, size)) { qDebug() << "Couldn't read 'L' data" << filename; return -1; } Session *sess; SessionID sid; QHash rampstart; QHash rampend; for (int i = 0; i < SessionStart.size(); i++) { sid = SessionStart[i]; if (mach->SessionExists(sid)) { // knock out the already imported sessions.. SessionStart[i] = 0; SessionEnd[i] = 0; } else if (!Sessions.contains(sid)) { sess = Sessions[sid] = new Session(mach, sid); sess->really_set_first(qint64(sid) * 1000L); // sess->really_set_last(qint64(SessionEnd[i]) * 1000L); rampstart[sid] = 0; rampend[sid] = 0; sess->SetChanged(true); if (mode >= MODE_BILEVEL_FIXED) { sess->AddEventList(CPAP_IPAP, EVL_Event); sess->AddEventList(CPAP_EPAP, EVL_Event); sess->AddEventList(CPAP_PS, EVL_Event); } else { sess->AddEventList(CPAP_Pressure, EVL_Event); } sess->AddEventList(INTELLIPAP_Unknown1, EVL_Event); sess->AddEventList(INTELLIPAP_Unknown2, EVL_Event); sess->AddEventList(CPAP_LeakTotal, EVL_Event); sess->AddEventList(CPAP_MaxLeak, EVL_Event); sess->AddEventList(CPAP_TidalVolume, EVL_Event); sess->AddEventList(CPAP_MinuteVent, EVL_Event); sess->AddEventList(CPAP_RespRate, EVL_Event); sess->AddEventList(CPAP_Snore, EVL_Event); sess->AddEventList(CPAP_Obstructive, EVL_Event); sess->AddEventList(INTP_SnoreFlag, EVL_Event); sess->AddEventList(CPAP_Hypopnea, EVL_Event); sess->AddEventList(CPAP_NRI, EVL_Event); sess->AddEventList(CPAP_LeakFlag, EVL_Event); sess->AddEventList(CPAP_ExP, EVL_Event); } else { // If there is a double up, null out the earlier session // otherwise there will be a crash on shutdown. for (int z = 0; z < SessionStart.size(); z++) { if (SessionStart[z] == (quint32)sid) { SessionStart[z] = 0; SessionEnd[z] = 0; break; } } QDateTime d = QDateTime::fromSecsSinceEpoch(sid); qDebug() << sid << "has double ups" << d; /*Session *sess=Sessions[sid]; Sessions.erase(Sessions.find(sid)); delete sess; SessionStart[i]=0; SessionEnd[i]=0; */ } } long pos = 0; int rampval = 0; sid = 0; //SessionID lastsid = 0; //int last_minp=0, last_maxp=0, last_ps=0, last_pres = 0; for (int i = 0; i < recs; i++) { // convert timestamp to real epoch ts1 = ((m_buffer[pos] << 24) | (m_buffer[pos + 1] << 16) | (m_buffer[pos + 2] << 8) | m_buffer[pos + 3]) + ep; for (int j = 0; j < SessionStart.size(); j++) { sid = SessionStart[j]; if (!sid) { continue; } if ((ts1 >= (quint32)sid) && (ts1 <= SessionEnd[j])) { Session *sess = Sessions[sid]; qint64 time = quint64(ts1) * 1000L; sess->really_set_last(time); sess->settings[CPAP_Mode] = mode; int minp = m_buffer[pos + 0x13]; int maxp = m_buffer[pos + 0x14]; int ps = m_buffer[pos + 0x15]; int pres = m_buffer[pos + 0xd]; if (mode >= MODE_BILEVEL_FIXED) { rampval = maxp; } else { rampval = minp; } qint64 rs = rampstart[sid]; if (pres < rampval) { if (!rs) { // ramp started // int rv = pres-rampval; // double ramp = rampstart[sid] = time; } rampend[sid] = time; } else { if (rs > 0) { if (!sess->eventlist.contains(CPAP_Ramp)) { sess->AddEventList(CPAP_Ramp, EVL_Event); } int duration = (time - rs) / 1000L; sess->eventlist[CPAP_Ramp][0]->AddEvent(time, duration); rampstart.remove(sid); rampend.remove(sid); } } // Do this after ramp, because ramp calcs might need to insert interpolated pressure samples if (mode >= MODE_BILEVEL_FIXED) { sess->settings[CPAP_EPAP] = float(minp) / 10.0; sess->settings[CPAP_IPAP] = float(maxp) / 10.0; sess->settings[CPAP_PS] = float(ps) / 10.0; sess->eventlist[CPAP_IPAP][0]->AddEvent(time, float(pres) / 10.0); sess->eventlist[CPAP_EPAP][0]->AddEvent(time, float(pres-ps) / 10.0); // rampval = maxp; } else { sess->eventlist[CPAP_Pressure][0]->AddEvent(time, float(pres) / 10.0); // current pressure // rampval = minp; if (mode == MODE_APAP) { sess->settings[CPAP_PressureMin] = float(minp) / 10.0; sess->settings[CPAP_PressureMax] = float(maxp) / 10.0; } else if (mode == MODE_CPAP) { sess->settings[CPAP_Pressure] = float(maxp) / 10.0; } } sess->eventlist[CPAP_LeakTotal][0]->AddEvent(time, m_buffer[pos + 0x7]); // "Average Leak" sess->eventlist[CPAP_MaxLeak][0]->AddEvent(time, m_buffer[pos + 0x6]); // "Max Leak" int rr = m_buffer[pos + 0xa]; sess->eventlist[CPAP_RespRate][0]->AddEvent(time, rr); // Respiratory Rate // sess->eventlist[INTELLIPAP_Unknown1][0]->AddEvent(time, m_buffer[pos + 0xf]); // sess->eventlist[INTELLIPAP_Unknown1][0]->AddEvent(time, m_buffer[pos + 0xc]); sess->eventlist[CPAP_Snore][0]->AddEvent(time, m_buffer[pos + 0x4]); //4/5?? if (m_buffer[pos+0x4] > 0) { sess->eventlist[INTP_SnoreFlag][0]->AddEvent(time, m_buffer[pos + 0x5]); } // 0x0f == Leak Event // 0x04 == Snore? if (m_buffer[pos + 0xf] > 0) { // Leak Event sess->eventlist[CPAP_LeakFlag][0]->AddEvent(time, m_buffer[pos + 0xf]); } if (m_buffer[pos + 0x5] > 4) { // This matches Exhale Puff.. not sure why 4 //MW: Are the lower 2 bits something else? sess->eventlist[CPAP_ExP][0]->AddEvent(time, m_buffer[pos + 0x5]); } if (m_buffer[pos + 0x10] > 0) { sess->eventlist[CPAP_Obstructive][0]->AddEvent(time, m_buffer[pos + 0x10]); } if (m_buffer[pos + 0x11] > 0) { sess->eventlist[CPAP_Hypopnea][0]->AddEvent(time, m_buffer[pos + 0x11]); } if (m_buffer[pos + 0x12] > 0) { // NRI // is this == to RERA?? CA?? sess->eventlist[CPAP_NRI][0]->AddEvent(time, m_buffer[pos + 0x12]); } quint16 tv = (m_buffer[pos + 0x8] << 8) | m_buffer[pos + 0x9]; // correct sess->eventlist[CPAP_TidalVolume][0]->AddEvent(time, tv); EventDataType mv = tv * rr; // MinuteVent=TidalVolume * Respiratory Rate sess->eventlist[CPAP_MinuteVent][0]->AddEvent(time, mv / 1000.0); break; } else { } //lastsid = sid; } pos += 26; } // Close any open ramps and store the event. QHash::iterator rit; QHash::iterator rit_end = rampstart.end(); for (rit = rampstart.begin(); rit != rit_end; ++rit) { qint64 rs = rit.value(); SessionID sid = rit.key(); if (rs > 0) { qint64 re = rampend[rit.key()]; Session *sess = Sessions[sid]; if (!sess->eventlist.contains(CPAP_Ramp)) { sess->AddEventList(CPAP_Ramp, EVL_Event); } int duration = (re - rs) / 1000L; sess->eventlist[CPAP_Ramp][0]->AddEvent(re, duration); rit.value() = 0; } } for (int i = 0; i < SessionStart.size(); i++) { SessionID sid = SessionStart[i]; if (sid) { sess = Sessions[sid]; if (!sess) continue; // quint64 first = qint64(sid) * 1000L; //quint64 last = qint64(SessionEnd[i]) * 1000L; if (sess->last() > 0) { // sess->really_set_last(last); sess->settings[INTP_SmartFlexLevel] = smartflex; if (smartflexmode == 0) { sess->settings[INTP_SmartFlexMode] = PM_FullTime; } else { sess->settings[INTP_SmartFlexMode] = PM_RampOnly; } sess->settings[CPAP_RampPressure] = ramp_pressure; sess->settings[CPAP_RampTime] = ramp_time; sess->UpdateSummaries(); addSession(sess); } else { delete sess; } } } finishAddingSessions(); mach->Save(); delete [] m_buffer; f.close(); int c = Sessions.size(); return c; } //////////////////////////////////////////////////////////////////////////// // Devilbiss DV64 Notes // 1) High resolution data (flow and pressure) is kept on SD for only 100 hours // 1a) Flow graph for days without high resolution data is absent // 1b) Pressure graph is high resolution when high res data is available and // only 1 per minute when using low resolution data. // 2) Max and Average leak rates are as reported by DV64 machine but we're // not sure how those measures relate to other machine's data. Leak rate // seems to include the intentional mask leak. // 2a) Not sure how SmartLink calculates the pct of time of poor mask fit. // May be same as what we call large leak time for other machines? //////////////////////////////////////////////////////////////////////////// struct DV6TestedModel { QString model; QString name; }; static const DV6TestedModel testedModels[] = { { "DV64D", "Blue StandardPlus" }, { "DV64E", "Blue AutoPlus" }, { "DV63E", "Blue (IntelliPAP 2) AutoPlus" }, { "", "unknown product" } // List stopper -- must be last entry }; struct DV6_S_Data // Daily summary { /*** Session * sess; unsigned char u1; //00 (position) ***/ unsigned int start_time; //01 Start time for date unsigned int stop_time; //05 End time unsigned int written; //09 timestamp when this record was written EventDataType hours; //13 // EventDataType unknown14; //14 EventDataType pressureAvg; //15 EventDataType pressureMax; //16 EventDataType pressure50; //17 50th percentile EventDataType pressure90; //18 90th percentile EventDataType pressure95; //19 95th percentile EventDataType pressureStdDev;//20 std deviation // EventDataType unknown_21; //21 EventDataType leakAvg; //22 EventDataType leakMax; //23 EventDataType leak50; //24 50th percentile EventDataType leak90; //25 90th percentile EventDataType leak95; //26 95th percentile EventDataType leakStdDev; //27 std deviation EventDataType tidalVolume; //28 & 0x29 EventDataType avgBreathRate; //30 EventDataType unknown_31; //31 EventDataType snores; //32 snores / hypopnea per minute EventDataType timeInExPuf; //33 Time in Expiratory Puff EventDataType timeInFL; //34 Time in Flow Limitation EventDataType timeInPB; //35 Time in Periodic Breathing EventDataType maskFit; //36 mask fit (or rather, not fit) percentage EventDataType indexOA; //37 Obstructive EventDataType indexCA; //38 Central index EventDataType indexHyp; //39 Hypopnea Index EventDataType unknown_40; //40 Reserved? EventDataType unknown_41; //40 Reserved? //42-48 unknown EventDataType pressureSetMin; //49 EventDataType pressureSetMax; //50 }; #ifdef _MSC_VER #define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) ) #else #define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__)) #endif // DV6_S_REC is the day structure in the S.BIN file PACK (struct DV6_S_REC{ unsigned char begin[4]; //0 Beginning of day unsigned char end[4]; //4 End of day unsigned char written[4]; //8 When this record was written?? unsigned char hours; //12 Hours in session * 10 unsigned char unknown_13; //13 unsigned char pressureAvg; //14 All pressure settings are * 10 unsigned char pressureMax; //15 unsigned char pressure50; //16 50th percentile unsigned char pressure90; //17 90th percentile unsigned char pressure95; //18 95th percentile unsigned char pressureStdDev; //19 std deviation unsigned char unknown_20; //20 unsigned char leakAvg; //21 unsigned char leakMax; //22 unsigned char leak50; //23 50th percentile unsigned char leak90; //24 90th percentile unsigned char leak95; //25 95th percentile unsigned char leakStdDev; //26 std deviation unsigned char tv1; //27 tidal volume = tv2 * 256 + tv1 unsigned char tv2; //28 unsigned char avgBreathRate; //29 unsigned char unknown_30; //30 unsigned char snores; //31 snores / hypopnea per minute unsigned char timeInExPuf; //32 % Time in Expiratory Puff * 2 unsigned char timeInFL; //33 % Time in Flow Limitation * 2 unsigned char timeInPB; //34 % Time in Periodic Breathing * 2 unsigned char maskFit; //35 mask fit (or rather, not fit) percentage * 2 unsigned char indexOA; //36 Obstructive index * 4 unsigned char indexCA; //37 Central index * 4 unsigned char indexHyp; //38 Hypopnea Index * 4 unsigned char unknown_39; //39 Reserved? unsigned char unknown_40; //40 Reserved? unsigned char unknown_41; //41 unsigned char unknown_42; //42 unsigned char unknown_43; //43 unsigned char unknown_44; //44 % time snoring *4 unsigned char unknown_45; //45 unsigned char unknown_46; //46 unsigned char unknown_47; //47 (related to smartflex and flow rounding?) unsigned char pressureSetMin; //48 unsigned char pressureSetMax; //49 unsigned char unknown_50; //50 unsigned char unknown_51; //51 unsigned char unknown_52; //52 unsigned char unknown_53; //53 unsigned char checksum; //54 }); // DV6 SET.BIN - structure of the entire settings file PACK (struct SET_BIN_REC { char unknown_00; // assuming file version char serial[11]; // null terminated unsigned char language; unsigned char capabilities; // CPAP or APAP unsigned char unknown_11; unsigned char cpap_pressure; unsigned char unknown_12; unsigned char max_pressure; unsigned char unknown_13; unsigned char min_pressure; unsigned char alg_apnea_threshhold; // always locked at 00 unsigned char alg_apnea_duration; unsigned char alg_hypop_threshold; unsigned char alg_hypop_duration; unsigned char ramp_pressure; unsigned char unknown_01; unsigned char ramp_duration; unsigned char unknown_02[3]; unsigned char smartflex_setting; unsigned char smartflex_when; unsigned char inspFlowRounding; unsigned char expFlowRounding; unsigned char complianceHours; unsigned char unknown_03; unsigned char tubing_diameter; unsigned char autostart_setting; unsigned char unknown_04; unsigned char show_hide; unsigned char unknown_05; unsigned char lock_flags; unsigned char unknown_06; unsigned char humidifier_setting; // 0-5 unsigned char unknown_7; unsigned char possible_alg_apnea; unsigned char unknown_8[7]; unsigned char bacteria_filter; unsigned char unused[73]; unsigned char checksum; }); // http://digitalvampire.org/blog/index.php/2006/07/31/why-you-shouldnt-use-__attribute__packed/ // Unless explicitly noted, all other DV6_x_REC are definitions for the repeating data structure that follows the header PACK (struct DV6_HEADER { unsigned char unknown; // 0 always zero unsigned char filetype; // 1 e.g. "R" for a R.BIN file unsigned char serial[11]; // 2 serial number unsigned char numRecords[4]; // 13 Number of records in file (always fixed, 180,000 for R.BIN) unsigned char recordLength; // 17 Length of data record (always 117) unsigned char recordStart[4]; // 18 First record in wrap-around buffer unsigned char unknown_22[21]; // 22 Unknown values unsigned char unknown_43[8]; // 43 Seems always to be zero unsigned char lasttime[4]; // 51 OSCAR only: Last timestamp, in history files only unsigned char checksum; // 55 Checksum }); // DV6 E.BIN - event data struct DV6_E_REC { // event log record unsigned char begin[4]; unsigned char end[4]; unsigned char unknown_01; unsigned char unknown_02; unsigned char unknown_03; unsigned char unknown_04; unsigned char event_type; unsigned char event_severity; unsigned char value; unsigned char reduction; unsigned char duration; unsigned char unknown[7]; unsigned char checksum; }; // DV6 U.BIN - session start and stop times struct DV6_U_REC { unsigned char begin[4]; unsigned char end[4]; unsigned char checksum; // possible checksum? Not really sure }; // DV6 R.BIN - High resolution data (breath) and moderate resolution (pressure, flags) struct DV6_R_REC { unsigned char timestamp[4]; qint16 breath[50]; // 50 breath flow records at 25 Hz unsigned char pressure1; // pressure in first second of frame unsigned char pressure2; // pressure in second second of frame unsigned char unknown106; unsigned char unknown107; unsigned char flags1[4]; // flags for first second of frame unsigned char flags2[4]; // flags for second second of frame unsigned char checksum; }; // DV6 L.BIN - Low resolution data PACK (struct DV6_L_REC { unsigned char timestamp[4]; // 0 timestamp unsigned char maxLeak; // 4 lpm unsigned char avgLeak; // 5 lpm unsigned char tidalVolume6; // 6 unsigned char tidalVolume7; // 7 unsigned char breathRate; // 8 breaths per minute unsigned char unknown9; // 9 unsigned char avgPressure; // 10 pressure * 10 unsigned char unknown11; // 11 always zero? unsigned char unknown12; // 12 unsigned char pressureLimitLow; // 13 pressure * 10 unsigned char pressureLimitHigh;// 14 pressure * 10 unsigned char timeSnoring; // 15 unsigned char snoringSeverity; // 16 unsigned char timeEP; // 17 unsigned char epSeverity; // 18 unsigned char timeX1; // 19 ?? unsigned char x1Severity; // 20 ?? unsigned char timeX2; // 21 ?? unsigned char x2Severity; // 22 ?? unsigned char timeX3; // 23 ?? unsigned char x3Severity; // 24 ?? unsigned char apSeverity; // 25 unsigned char TimeApnea; // 26 unsigned char noaSeverity; // 27 unsigned char timeNOA; // 28 unsigned char ukSeverity; // 29 ?? unsigned char timeUk; // 30 ?? unsigned char unknown31; // 31 unsigned char unknown32; // 32 unsigned char unknown33; // 33 unsigned char unknownFlag34; // 34 unsigned char unknownTime35; // 35 unsigned char unknownFlag36; // 36 unsigned char unknown37; // 37 unsigned char unknown38; // 38 unsigned char unknown39; // 39 unsigned char unknown40; // 40 unsigned char unknown41; // 41 unsigned char unknown42; // 42 unsigned char unknown43; // 43 unsigned char checksum; // 44 }); // Our structure for managing sessions struct DV6_SessionInfo { Session * sess; DV6_S_Data *dailyData; SET_BIN_REC * dv6Settings; unsigned int begin; unsigned int end; unsigned int written; // bool haveHighResData; unsigned int firstHighRes; unsigned int lastHighRes; CPAPMode mode = MODE_UNKNOWN; }; QString card_path; QString backup_path; QString history_path; QString rebuild_path; MachineInfo info; Machine * mach = nullptr; bool rebuild_from_backups = false; bool create_backups = false; QStringList inputFilePaths; QMap DailySummaries; QMap SessionData; SET_BIN_REC * settings; unsigned int ep = 0; // Convert a 4-character number in DV6 data file to a standard int unsigned int convertNum (unsigned char num[]) { return ((num[3] << 24) + (num[2] << 16) + (num[1] << 8) + num[0]); } // Convert a timestamp in DV6 data file to a standard Unix epoch timestamp as used in OSCAR unsigned int convertTime (unsigned char time[]) { if (ep == 0) { QDateTime epoch(QDate(2002, 1, 1), QTime(0, 0, 0), Qt::UTC); // Intellipap Epoch ep = epoch.toTime_t(); } return ((time[3] << 24) + (time[2] << 16) + (time[1] << 8) + time[0]) + ep; // Time as Unix epoch time } class RollingBackup { public: RollingBackup () {} ~RollingBackup () { } bool open (const QString filetype, DV6_HEADER * newhdr, QByteArray * startTime); // Open the file bool close(); // close the file bool save(const QByteArray &dataBA); // save the next record in the file private: DV6_HEADER hdr; // file header QString filetype; QFile histfile; const qint64 maxHistFileSize = 10000000; // Maximum size of file before we create a new file, in MB (40 MB) // (While 40e6 would be easier to understand, 40e6 is a double, not an int) unsigned int lastTimeInFile; // Timestamp of last data record in history file int numWritten; // Number of records written }; QStringList getHistoryFileNames (const QString filetype, bool reversed = false) { QStringList filters; QDir hpath(history_path); filters.append(filetype); // Assume one-letter file name like "S.BIN" filters[0].insert(1, "_*"); // Add a wild card like "S_*.BIN" hpath.setNameFilters(filters); hpath.setFilter(QDir::Files); hpath.setSorting(QDir::Name); if (reversed) hpath.setSorting(QDir::Name | QDir::Reversed); return hpath.entryList(); // Get list of files } QString getNewFileName (QString filetype, QByteArray * startTime, int offset=0) { unsigned char startTimeChar[5]; for (int i = 0; i < 4; i++) startTimeChar[i] = startTime->at(offset+i); unsigned int ts = convertTime(startTimeChar); QString newfile = filetype.left(1) + "_" + QDateTime::fromSecsSinceEpoch(ts).toString("yyyyMMdd") + ".BIN"; qDebug() << "DV6 getNewFileName returns" << newfile; return newfile; } bool RollingBackup::open (const QString filetype, DV6_HEADER * inputhdr, QByteArray * startTimeOfBackup) { if (!create_backups) return true; QDir hpath(history_path); QString historypath = hpath.absolutePath() + "/"; int histfilesize = 0; this->filetype = filetype; bool needNewFile = false; memcpy (&hdr, inputhdr, sizeof(DV6_HEADER)); numWritten = 0; QStringList fileNames = getHistoryFileNames(filetype, true); // Handle first time a history file is being created if (fileNames.isEmpty()) { for (int i = 0; i < 4; i++) { hdr.recordStart[i] = 0; hdr.lasttime[i] = 0; } lastTimeInFile = 0; histfile.setFileName(historypath + getNewFileName (filetype, startTimeOfBackup)); needNewFile= true; } // We have an existing history record if (! fileNames.isEmpty()) { histfile.setFileName(historypath + fileNames.first()); // File names are in reverse order, so latest is first // Open and read history file header and save the header if (!histfile.open(QIODevice::ReadWrite)) { qWarning() << "DV6 rb(open) could not open" << fileNames.first() << "for readwrite, error code" << histfile.error() << histfile.errorString(); return false; } histfilesize = histfile.size(); QByteArray dataBA = histfile.read(sizeof(DV6_HEADER)); memcpy (&hdr, dataBA.data(), sizeof(DV6_HEADER)); lastTimeInFile = convertTime(hdr.lasttime); // See if this file is large enough that we want to create a new file // If it is large, we'll start a new file. if (histfile.size() > maxHistFileSize) { QString nextFile = historypath + getNewFileName (filetype, &dataBA, 51); QString hh = histfile.fileName(); if (hh != nextFile) { lastTimeInFile = convertTime(hdr.lasttime); histfile.close(); // Update header saying we are starting at record 0 for (int i = 0; i < 4; i++) hdr.recordStart[i] = 0; histfile.setFileName(nextFile); needNewFile = true; } } } if (needNewFile) { if (!histfile.open(QIODevice::ReadWrite)) { qWarning() << "DV6 rb(open) could not create new file" << histfile.fileName() << "for readwrite, error code" << histfile.error() << histfile.errorString(); return false; } if (histfile.write((char *)&hdr.unknown, sizeof(DV6_HEADER)) != sizeof(DV6_HEADER)) { qWarning() << "DV6 rb(open) could not write header to new file" << histfile.fileName() << "for readwrite, error code" << histfile.error() << histfile.errorString(); histfile.close(); return false; } } else { // qDebug() << "DV6 rb(open) history file size" << histfilesize; histfile.seek(histfilesize); } return true; } bool RollingBackup::close() { if (!create_backups) return true; qint32 size = histfile.size(); if (!histfile.seek(0)) { qWarning() << "DV6 rb(close) unable to seek to file beginning" << histfile.error() << histfile.errorString(); histfile.close(); return false; } quint32 sizehdr = sizeof(DV6_HEADER); quint32 reclen = hdr.recordLength; quint32 wrap_point = (size - sizehdr) / reclen; hdr.recordStart[0] = wrap_point & 0xff; hdr.recordStart[1] = (wrap_point >> 8) & 0xff; hdr.recordStart[2] = (wrap_point >> 16) & 0xff; hdr.recordStart[3] = (wrap_point >> 24) & 0xff; if (histfile.write((char *)&hdr, sizeof(DV6_HEADER)) != sizeof(DV6_HEADER)) { qWarning() << "DV6 rb(close) could not write header to file" << histfile.fileName() << "error code" << histfile.error() << histfile.errorString(); histfile.close(); return false; } histfile.close(); qDebug() << "DV6 rb(close) wrote" << numWritten << "records."; return true; } bool RollingBackup::save(const QByteArray &dataBA) { if (!create_backups) return true; unsigned char * data = (unsigned char *)dataBA.data(); unsigned int thisTimeStamp = convertTime(data); if (thisTimeStamp > lastTimeInFile) { // Is this data new to us? // If so, save it to the history file. if (histfile.write(dataBA) == -1) { qWarning() << "DV6 rb(save) could not save record" << histfile.fileName() << "for readwrite, error code" << histfile.error() << histfile.errorString(); histfile.close(); return false; } memcpy(&hdr.lasttime, data, 4); // if (!histfile.seek(histfile.pos() + dataBA.length())) // qWarning() << "DV6 rb(save) failed respositioning" << histfile.fileName() << "for readwrite, error code" << histfile.error() << histfile.errorString(); numWritten++; } /*** else { qDebug() << "DV6 rb(save) skipping record" << numWritten << QDateTime::fromSecsSinceEpoch(thisTimeStamp).toString("MM/dd/yyyy hh:mm:ss") << "last in file" << QDateTime::fromSecsSinceEpoch(lastTimeInFile).toString("MM/dd/yyyy hh:mm:ss"); } ***/ return true; } class RollingFile { public: RollingFile () { } ~RollingFile () { if (data) delete [] data; data = nullptr; if (hdr) delete hdr; hdr = nullptr; } bool open (QString fn, bool getNext = false); // Open the file bool close(); // close the file unsigned char * get(); // read the next record in the file int numread () {return number_read;}; // Return number of records read int recnum () {return record_number;}; // Return last-read record number RollingBackup rb; private: QString filename; QFile file; int record_length; int wrap_record; bool wrapping = false; int number_read = 0; // Number of records read int record_number = 0; // Number of record. First record in the file is #1. First record read is wrap_record; DV6_HEADER * hdr; // file header unsigned char * data = nullptr; // record pointer }; bool RollingFile::open(QString filetype, bool getNext) { filename = filetype; if (rebuild_from_backups) { // Building from backup if (!getNext) { // Initialize on first call inputFilePaths.clear(); QStringList histFileNames = getHistoryFileNames(filetype); qDebug() << "DV6 rf(open) History file names" << histFileNames; for (int i=0; i < histFileNames.size(); i++) { file.setFileName(history_path + "/" + histFileNames.at(i)); inputFilePaths.append(file.fileName()); } } if (inputFilePaths.empty()) return false; file.setFileName(inputFilePaths.at(0)); inputFilePaths.removeAt(0); } else { file.setFileName(card_path + "/" +filetype); inputFilePaths.clear(); } if (!file.open(QIODevice::ReadOnly)) { qWarning() << "DV6 rf(open) could not open" << filename << "for reading, error code" << file.error() << file.errorString(); return false; } // Save header for use in making backups of data hdr = new DV6_HEADER; QByteArray dataBA = file.read(sizeof(DV6_HEADER)); memcpy (hdr, dataBA.data(), sizeof(DV6_HEADER)); // Extract control information from header record_length = hdr->recordLength; wrap_record = convertNum(hdr->recordStart); record_number = wrap_record; number_read = 0; wrapping = false; // Create buffer to hold each record as it is read data = new unsigned char[record_length]; // Seek to oldest data record in file, which is always at the wrap point // wrap_record is the C offset where the next data record is to be written. // Since C offsets begin with zero, it is also the number of records in the file. int seekpos = sizeof(DV6_HEADER) + wrap_record * record_length; if (!file.seek(seekpos)) { qWarning() << "DV6 rf(open) unable to make initial seek to record" << wrap_record << "in" + filename << file.error() << file.errorString(); file.close(); return false; } qDebug() << "DV6 rf(open)" << filetype << "positioning to oldest record at pos" << seekpos << "after seek" << file.pos(); if (file.atEnd()) { file.seek(sizeof(DV6_HEADER)); } dataBA = file.read(4); // Read timestamp of newest data record file.seek(seekpos); // Reset read position before what we just read so we start reading data records here if (!rb.open(filetype, hdr, &dataBA)) { qWarning() << "DV6 rf(open) failed"; file.close(); return false; } qDebug() << "DV6 rf(open)" << filename << "at wrap record" << wrap_record << "now at pos" << file.pos(); return true; } bool RollingFile::close() { /*** Works for backing up but prevents chart appearing for the last day // Flush any additional input that has not been backed up if (create_backups) { do { DV6_U_REC * rec = (DV6_U_REC *) get(); if (rec == nullptr) break; } while (true); } ***/ file.close(); rb.close(); if (data) delete [] data; data = nullptr; if (hdr) delete hdr; hdr = nullptr; return true; } unsigned char * RollingFile::get() { // int readpos; record_number++; // If we have found the wrap record again, we are done if (wrapping && record_number == wrap_record) { // Unless we are rebuilding from backup and may have more files if (rebuild_from_backups && !inputFilePaths.empty()) { qDebug() << "DV6 rf(get) closing" << file.fileName(); file.close(); open(inputFilePaths.at(0), true); } else { return nullptr; } } // Have we reached end of file and need to wrap around to beginning? if (file.atEnd()) { if (wrapping) { qDebug() << "DV6 rf(get) wrap - second time through"; return nullptr; } qDebug() << "DV6 rf(get) wrapping to beginning of data in" << filename << "record number is" << record_number-1 << "records read" << number_read; record_number = 1; wrapping = true; if (!file.seek(sizeof(DV6_HEADER))) { file.close(); qWarning() << "DV6 rf(get) unable to seek to first data record in file"; return nullptr; } qDebug() << "DV6 rf(get) #" << record_number << "now at pos" << file.pos(); } QByteArray dataBA; // readpos = file.pos(); dataBA=file.read(record_length); // read next record if (dataBA.size() != record_length) { qWarning() << "DV6 rf(get) #" << record_number << "wrong length"; file.close(); return nullptr; } if (!rb.save(dataBA)) { qWarning() << "DV6 rf(get) failed"; } number_read++; // qDebug() << "DV6 rf(get)" << filename << "at start pos" << readpos << "end pos" << file.pos() << "record number" << record_number << "of length" << record_length << "number read so far" << number_read; memcpy (data, (unsigned char *) dataBA.data(), record_length); return data; } // Returns empty QByteArray() on failure. QByteArray fileChecksum(const QString &fileName, QCryptographicHash::Algorithm hashAlgorithm) { QFile f(fileName); if (f.open(QFile::ReadOnly)) { QCryptographicHash hash(hashAlgorithm); bool res = hash.addData(&f); f.close(); if (res) { return hash.result(); } } return QByteArray(); } // Return date used within OSCAR, assuming day ends at split time in preferences (usually noon) QDate getNominalDate (QDateTime dt) { QDate d = dt.date(); QTime tm = dt.time(); QTime daySplitTime = p_profile->session->getPref(STR_IS_DaySplitTime).toTime(); if (tm < daySplitTime) d = d.addDays(-1); return d; } QDate getNominalDate (unsigned int dt) { QDateTime xdt = QDateTime::fromSecsSinceEpoch(dt); return getNominalDate(xdt); } /////////////////////////////////////////////// // U.BIN - Open and parse session list and create session data structures // with session start and stop times. /////////////////////////////////////////////// bool load6Sessions () { RollingFile rf; unsigned int ts1,ts2; SessionData.clear(); qDebug() << "Parsing U.BIN"; if (!rf.open("U.BIN")) { qWarning() << "Unable to open U.BIN"; return false; } do { DV6_U_REC * rec = (DV6_U_REC *) rf.get(); if (rec == nullptr) break; DV6_SessionInfo sinfo; // big endian ts1 = convertTime(rec->begin); // session start time (this is also the session id) ts2 = convertTime(rec->end); // session end time //#ifdef DEBUG6 qDebug() << "U.BIN Session" << QDateTime::fromSecsSinceEpoch(ts1).toString("MM/dd/yyyy hh:mm:ss") << ts1 << "to" << QDateTime::fromSecsSinceEpoch(ts2).toString("MM/dd/yyyy hh:mm:ss") << ts2; //#endif sinfo.sess = nullptr; sinfo.dailyData = nullptr; sinfo.begin = ts1; sinfo.end = ts2; sinfo.written = 0; // sinfo.haveHighResData = false; sinfo.firstHighRes = 0; sinfo.lastHighRes = 0; SessionData[ts1] = sinfo; } while (true); rf.close(); qDebug() << "DV6 U.BIN processed" << rf.numread() << "records"; return true; } ///////////////////////////////////////////////////////////////////////////////// // Parse SET.BIN settings file ///////////////////////////////////////////////////////////////////////////////// bool load6Settings (const QString & path) { QByteArray dataBA; QFile f(path+"/"+SET_BIN); if (rebuild_from_backups) f.setFileName(rebuild_path+"/"+SET_BIN); if (f.open(QIODevice::ReadOnly)) { // Read and parse entire SET.BIN file dataBA = f.readAll(); f.close(); settings = (SET_BIN_REC *)dataBA.data(); } else { // if f.open settings file // Settings file open failed, return qWarning() << "Unable to open SET.BIN file"; return false; } return true; } //////////////////////////////////////////////////////////////////////////////////////// // S.BIN - Open and load day summary list //////////////////////////////////////////////////////////////////////////////////////// bool load6DailySummaries () { RollingFile rf; DailySummaries.clear(); if (!rf.open("S.BIN")) { qWarning() << "Unable to open S.BIN"; return false; } qDebug() << "Reading S.BIN summaries"; do { DV6_S_REC * rec = (DV6_S_REC *) rf.get(); if (rec == nullptr) break; DV6_S_Data dailyData; dailyData.start_time = convertTime(rec->begin); dailyData.stop_time = convertTime(rec->end); dailyData.written = convertTime(rec->written); #ifdef DEBUG6 qDebug() << "DV6 S.BIN start" << dailyData.start_time << "stop" << dailyData.stop_time << "written" << dailyData.written; #endif dailyData.hours = float(rec->hours) / 10.0F; dailyData.pressureSetMin = float(rec->pressureSetMin) / 10.0F; dailyData.pressureSetMax = float(rec->pressureSetMax) / 10.0F; // The following stuff is not necessary to decode, but can be used to verify we are on the right track dailyData.pressureAvg = float(rec->pressureAvg) / 10.0F; dailyData.pressureMax = float(rec->pressureMax) / 10.0F; dailyData.pressure50 = float(rec->pressure50) / 10.0F; dailyData.pressure90 = float(rec->pressure90) / 10.0F; dailyData.pressure95 = float(rec->pressure95) / 10.0F; dailyData.pressureStdDev = float(rec->pressureStdDev) / 10.0F; dailyData.leakAvg = float(rec->leakAvg) / 10.0F; dailyData.leakMax = float(rec->leakMax) / 10.0F; dailyData.leak50= float(rec->leak50) / 10.0F; dailyData.leak90 = float(rec->leak90) / 10.0F; dailyData.leak95 = float(rec->leak95) / 10.0F; dailyData.leakStdDev = float(rec->leakStdDev) / 10.0F; dailyData.tidalVolume = float(rec->tv1 | rec->tv2 << 8); dailyData.avgBreathRate = float(rec->avgBreathRate); dailyData.snores = float(rec->snores); dailyData.timeInExPuf = float(rec->timeInExPuf) / 2.0F; dailyData.timeInFL = float(rec->timeInFL) / 2.0F; dailyData.timeInPB = float(rec->timeInPB) / 2.0F; dailyData.maskFit = float(rec->maskFit) / 2.0F; dailyData.indexOA = float(rec->indexOA) / 4.0F; dailyData.indexCA = float(rec->indexCA) / 4.0F; dailyData.indexHyp = float(rec->indexHyp) / 4.0F; DailySummaries[dailyData.start_time] = dailyData; /**** Previous loader did this: if (!mach->sessionlist.contains(ts1)) { // Check if already imported qDebug() << "Detected new Session" << ts1; R.sess = new Session(mach, ts1); R.sess->SetChanged(true); R.sess->really_set_first(qint64(ts1) * 1000L); R.sess->really_set_last(qint64(ts2) * 1000L); if (data[49] != data[50]) { R.sess->settings[CPAP_PressureMin] = R.pressureSetMin; R.sess->settings[CPAP_PressureMax] = R.pressureSetMax; R.sess->settings[CPAP_Mode] = MODE_APAP; } else { R.sess->settings[CPAP_Mode] = MODE_CPAP; R.sess->settings[CPAP_Pressure] = R.pressureSetMin; } R.hasMaskPressure = false; ***/ } while (true); rf.close(); qDebug() << "DV6 S.BIN processed" << rf.numread() << "records"; return true; } //////////////////////////////////////////////////////////////////////////////////////// // Parse VER.BIN for model number, serial, etc. //////////////////////////////////////////////////////////////////////////////////////// bool load6VersionInfo(const QString & path) { QByteArray dataBA; QByteArray str; QFile f(path+"/VER.BIN"); if (rebuild_from_backups) f.setFileName(rebuild_path+"/VER.BIN"); info.series = "DV6"; info.brand = "DeVilbiss"; if (f.open(QIODevice::ReadOnly)) { dataBA = f.readAll(); f.close(); int cnt = 0; for (int i=0; i< dataBA.size(); ++i) { // deliberately going one further to catch end condition if ((dataBA.at(i) == 0) || (i >= dataBA.size()-1)) { // if null terminated or last byte switch(cnt) { case 1: // serial info.serial = str; break; case 2: // modelnumber // info.model = str; info.modelnumber = str; info.modelnumber = info.modelnumber.trimmed(); for (int i = 0; i < (int)sizeof(testedModels); i++) { if ( testedModels[i].model == info.modelnumber || testedModels[i].model.isEmpty()) { info.model = testedModels[i].name; break; } } break; case 7: // ??? V025RN20170 break; case 9: // ??? V014BL20150630 break; case 11: // ??? 01 09 break; case 12: // ??? 0C 0C break; case 14: // ??? BA 0C break; default: break; } // Clear and start a new data record str.clear(); cnt++; } else { // Add the character to the current string str.append(dataBA[i]); } } return true; } else { // if (f.open(...) // VER.BIN open failed qWarning() << "Unable to open VER.BIN"; return false; } } //////////////////////////////////////////////////////////////////////////////////////// // Create DV6_SessionInfo structures for each session and store in SessionData qmap //////////////////////////////////////////////////////////////////////////////////////// int create6Sessions() { SessionID sid = 0; Session * sess; for (auto sinfo=SessionData.begin(), end=SessionData.end(); sinfo != end; ++sinfo) { sid = sinfo->begin; if (mach->SessionExists(sid)) { // skip already imported sessions.. qDebug() << "Session already exists" << QDateTime::fromSecsSinceEpoch(sid).toString("MM/dd/yyyy hh:mm:ss"); } else if (sinfo->sess == nullptr) { // process new sessions sess = new Session(mach, sid); #ifdef DEBUG6 qDebug() << "Creating session" << QDateTime::fromSecsSinceEpoch(sinfo->begin).toString("MM/dd/yyyy hh:mm:ss") << "to" << QDateTime::fromSecsSinceEpoch(sinfo->end).toString("MM/dd/yyyy hh:mm:ss"); #endif sinfo->sess = sess; sinfo->dailyData = nullptr; sinfo->written = 0; // sinfo->haveHighResData = false; sinfo->firstHighRes = 0; sinfo->lastHighRes = 0; sess->really_set_first(quint64(sinfo->begin) * 1000L); sess->really_set_last(quint64(sinfo->end) * 1000L); // rampstart[sid] = 0; // rampend[sid] = 0; sess->SetChanged(true); sess->AddEventList(INTELLIPAP_Unknown1, EVL_Event); sess->AddEventList(INTELLIPAP_Unknown2, EVL_Event); sess->AddEventList(CPAP_LeakTotal, EVL_Event); sess->AddEventList(CPAP_MaxLeak, EVL_Event); sess->AddEventList(CPAP_TidalVolume, EVL_Event); sess->AddEventList(CPAP_MinuteVent, EVL_Event); sess->AddEventList(CPAP_RespRate, EVL_Event); sess->AddEventList(CPAP_Snore, EVL_Event); sess->AddEventList(INTP_SnoreFlag, EVL_Event); sess->AddEventList(CPAP_Obstructive, EVL_Event); sess->AddEventList(CPAP_Hypopnea, EVL_Event); sess->AddEventList(CPAP_NRI, EVL_Event); // sess->AddEventList(CPAP_LeakFlag, EVL_Event); sess->AddEventList(CPAP_ExP, EVL_Event); sess->AddEventList(CPAP_FlowLimit, EVL_Event); } else { // If there is a duplicate session, null out the earlier session // otherwise there will be a crash on shutdown. //?? for (int z = 0; z < SessionStart.size(); z++) { //?? if (SessionStart[z] == sid) { //?? SessionStart[z] = 0; //?? SessionEnd[z] = 0; //?? break; //?? } qDebug() << sid << "has double ups" << QDateTime::fromSecsSinceEpoch(sid).toString("MM/dd/yyyy hh:mm:ss"); /*Session *sess=Sessions[sid]; Sessions.erase(Sessions.find(sid)); delete sess; SessionStart[i]=0; SessionEnd[i]=0; */ } } qDebug() << "Created" << SessionData.size() << "sessions"; return SessionData.size(); } //////////////////////////////////////////////////////////////////////////////////////// // Parse R.BIN for high resolution flow data //////////////////////////////////////////////////////////////////////////////////////// bool load6HighResData () { RollingFile rf; Session *sess = nullptr; unsigned int rec_ts1, previousRecBegin = 0; bool inSession = false; // true if we are adding data to this session if (!rf.open("R.BIN")) { qWarning() << "DV6 Unable to open R.BIN"; return false; } qDebug() << "R.BIN starting at record" << rf.recnum(); sess = NULL; EventList * flow = NULL; EventList * pressure = NULL; EventList * FLG = NULL; EventList * snore = NULL; // EventList * leak = NULL; /*** EventList * OA = NULL; EventList * HY = NULL; EventList * NOA = NULL; EventList * EXP = NULL; EventList * FL = NULL; EventList * PB = NULL; EventList * VS = NULL; EventList * LL = NULL; EventList * RE = NULL; bool inOA = false, inH = false, inCA = false, inExP = false, inVS = false, inFL = false, inPB = false, inRE = false, inLL = false; qint64 OAstart = 0, OAend = 0; qint64 Hstart = 0, Hend = 0; qint64 CAstart = 0, CAend = 0; qint64 ExPstart = 0, ExPend = 0; qint64 VSstart = 0, VSend = 0; qint64 FLstart = 0, FLend = 0; qint64 PBstart = 0, PBend = 0; qint64 REstart =0, REend = 0; qint64 LLstart =0, LLend = 0; // lastts1 = 0; ***/ // sinfo is for walking through sessions when matching up with flow data records QMap::iterator sinfo; sinfo = SessionData.begin(); do { DV6_R_REC * R = (DV6_R_REC *) rf.get(); if (R == nullptr) break; sess = sinfo->sess; // Get the timestamp from the record rec_ts1 = convertTime(R->timestamp); if (rec_ts1 < previousRecBegin) { qWarning() << "R.BIN - Corruption/Out of sequence data found, skipping record" << rf.recnum() << ", prev" << QDateTime::fromSecsSinceEpoch(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss") << previousRecBegin << "this" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << rec_ts1; continue; } // Look for a gap in DV6_R records. They should be at two second intervals. // If there is a gap, we are probably in a new session if (inSession && ((rec_ts1 - previousRecBegin) > 2)) { if (sess) { sess->set_last(flow->last()); if (sess->first() == 0) qWarning() << "R.BIN first = 0 - 1320"; EventDataType min = flow->Min(); EventDataType max = flow->Max(); sess->setMin(CPAP_FlowRate, min); sess->setMax(CPAP_FlowRate, max); sess->setPhysMax(CPAP_FlowRate, min); // not sure :/ sess->setPhysMin(CPAP_FlowRate, max); // sess->really_set_last(flow->last()); } sess = nullptr; flow = nullptr; pressure = nullptr; FLG = nullptr; snore = nullptr; inSession = false; } // Skip over sessions until we find one that this record is in while (rec_ts1 > sinfo->end) { #ifdef DEBUG6 qDebug() << "R.BIN - skipping session" << QDateTime::fromSecsSinceEpoch(sinfo->begin).toString("MM/dd/yyyy hh:mm:ss") << "looking for" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "record" << rf.recnum(); #endif if (inSession && sess) { // update min and max // then add to machine if (sess->first() == 0) qWarning() << "R.BIN first = 0 - 1284"; EventDataType min = flow->Min(); EventDataType max = flow->Max(); sess->setMin(CPAP_FlowRate, min); sess->setMax(CPAP_FlowRate, max); sess->setPhysMax(CPAP_FlowRate, min); // not sure :/ sess->setPhysMin(CPAP_FlowRate, max); sess = nullptr; flow = nullptr; pressure = nullptr; FLG = nullptr; snore = nullptr; inSession = false; } sinfo++; if (sinfo == SessionData.end()) break; } previousRecBegin = rec_ts1; // If we have data beyond last session, we are in trouble (for unknown reasons) if (sinfo == SessionData.end()) { qWarning() << "DV6 R.BIN import ran out of sessions to match flow data, record" << rf.recnum(); break; } // Check if record belongs in this session or a future session if (!inSession && rec_ts1 <= sinfo->end) { sess = sinfo->sess; // this is the Session we want if (!inSession && sess) { inSession = true; flow = sess->AddEventList(CPAP_FlowRate, EVL_Waveform, 0.01f, 0.0f, 0.0f, 0.0f, double(2000) / double(50)); pressure = sess->AddEventList(CPAP_Pressure, EVL_Waveform, 0.1f, 0.0f, 0.0f, 0.0f, double(2000) / double(2)); FLG = sess->AddEventList(CPAP_FLG, EVL_Waveform, 1.0f, 0.0f, 0.0f, 0.0f, double(2000) / double(2)); snore = sess->AddEventList(CPAP_Snore, EVL_Waveform, 1.0f, 0.0f, 0.0f, 0.0f, double(2000) / double(2)); // sinfo->hasMaskPressure = true; // leak = R->sess->AddEventList(CPAP_Leak, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, double(2000) / double(1)); /*** OA = R->sess->AddEventList(CPAP_Obstructive, EVL_Event); NOA = R->sess->AddEventList(CPAP_NRI, EVL_Event); RE = R->sess->AddEventList(CPAP_RERA, EVL_Event); VS = R->sess->AddEventList(CPAP_VSnore, EVL_Event); HY = R->sess->AddEventList(CPAP_Hypopnea, EVL_Event); EXP = R->sess->AddEventList(CPAP_ExP, EVL_Event); FL = R->sess->AddEventList(CPAP_FlowLimit, EVL_Event); PB = R->sess->AddEventList(CPAP_PB, EVL_Event); LL = R->sess->AddEventList(CPAP_LargeLeak, EVL_Event); ***/ } } if (inSession) { // Record breath and pressure waveforms qint64 ti = qint64(rec_ts1) * 1000; flow->AddWaveform(ti,R->breath,50,2000); pressure->AddWaveform(ti, &R->pressure1, 2, 2000); if (sinfo->firstHighRes == 0 || sinfo->firstHighRes > rec_ts1) sinfo->firstHighRes = rec_ts1; if (sinfo->lastHighRes == 0 || sinfo->lastHighRes < rec_ts1+2) sinfo->lastHighRes = rec_ts1+2; // sinfo->haveHighResData = true; if (sess->first() == 0) qWarning() << "first = 0 - 1442"; ////////////////////////////////////////////////////////////////// // Show Flow Limitation Events as a graph ////////////////////////////////////////////////////////////////// qint16 severity = (R->flags1[0] >> 4) & 0x03; FLG->AddWaveform(ti, &severity, 1, 1000); severity = (R->flags2[0] >> 4) & 0x03; FLG->AddWaveform(ti+1000, &severity, 1, 1000); ////////////////////////////////////////////////////////////////// // Show Snore Events as a graph ////////////////////////////////////////////////////////////////// severity = R->flags1[0] & 0x03; snore->AddWaveform(ti, &severity, 1, 1000); severity = R->flags2[0] & 0x03; snore->AddWaveform(ti+1000, &severity, 1, 1000); /**** // Fields data[107] && data[108] are bitfields default is 0x90, occasionally 0x98 d[0] = data[107]; d[1] = data[108]; //leak->AddWaveform(ti+40000, d, 2, 2000); // Needs to track state to pull events out cleanly.. ////////////////////////////////////////////////////////////////// // High Leak ////////////////////////////////////////////////////////////////// if (data[110] & 3) { // LL state 1st second if (!inLL) { LLstart = ti; inLL = true; } LLend = ti+1000L; } else { if (inLL) { inLL = false; LL->AddEvent(LLstart,(LLend-LLstart) / 1000L); LLstart = 0; } } if (data[114] & 3) { if (!inLL) { LLstart = ti+1000L; inLL = true; } LLend = ti+2000L; } else { if (inLL) { inLL = false; LL->AddEvent(LLstart,(LLend-LLstart) / 1000L); LLstart = 0; } } ////////////////////////////////////////////////////////////////// // Obstructive Apnea ////////////////////////////////////////////////////////////////// if (data[110] & 12) { // OA state 1st second if (!inOA) { OAstart = ti; inOA = true; } OAend = ti+1000L; } else { if (inOA) { inOA = false; OA->AddEvent(OAstart,(OAend-OAstart) / 1000L); OAstart = 0; } } if (data[114] & 12) { if (!inOA) { OAstart = ti+1000L; inOA = true; } OAend = ti+2000L; } else { if (inOA) { inOA = false; OA->AddEvent(OAstart,(OAend-OAstart) / 1000L); OAstart = 0; } } ////////////////////////////////////////////////////////////////// // Hypopnea ////////////////////////////////////////////////////////////////// if (data[110] & 192) { if (!inH) { Hstart = ti; inH = true; } Hend = ti + 1000L; } else { if (inH) { inH = false; HY->AddEvent(Hstart,(Hend-Hstart) / 1000L); Hstart = 0; } } if (data[114] & 192) { if (!inH) { Hstart = ti+1000L; inH = true; } Hend = ti + 2000L; } else { if (inH) { inH = false; HY->AddEvent(Hstart,(Hend-Hstart) / 1000L); Hstart = 0; } } ////////////////////////////////////////////////////////////////// // Non Responding Apnea Event (Are these CA's???) ////////////////////////////////////////////////////////////////// if (data[110] & 48) { // OA state 1st second if (!inCA) { CAstart = ti; inCA = true; } CAend = ti+1000L; } else { if (inCA) { inCA = false; NOA->AddEvent(CAstart,(CAend-CAstart) / 1000L); CAstart = 0; } } if (data[114] & 48) { if (!inCA) { CAstart = ti+1000L; inCA = true; } CAend = ti+2000L; } else { if (inCA) { inCA = false; NOA->AddEvent(CAstart,(CAend-CAstart) / 1000L); CAstart = 0; } } ////////////////////////////////////////////////////////////////// // VSnore Event ////////////////////////////////////////////////////////////////// if (data[109] & 3) { // OA state 1st second if (!inVS) { VSstart = ti; inVS = true; } VSend = ti+1000L; } else { if (inVS) { inVS = false; VS->AddEvent(VSstart,(VSend-VSstart) / 1000L); VSstart = 0; } } if (data[113] & 3) { if (!inVS) { VSstart = ti+1000L; inVS = true; } VSend = ti+2000L; } else { if (inVS) { inVS = false; VS->AddEvent(VSstart,(VSend-VSstart) / 1000L); VSstart = 0; } } ////////////////////////////////////////////////////////////////// // Expiratory puff Event ////////////////////////////////////////////////////////////////// if (data[109] & 12) { // OA state 1st second if (!inExP) { ExPstart = ti; inExP = true; } ExPend = ti+1000L; } else { if (inExP) { inExP = false; EXP->AddEvent(ExPstart,(ExPend-ExPstart) / 1000L); ExPstart = 0; } } if (data[113] & 12) { if (!inExP) { ExPstart = ti+1000L; inExP = true; } ExPend = ti+2000L; } else { if (inExP) { inExP = false; EXP->AddEvent(ExPstart,(ExPend-ExPstart) / 1000L); ExPstart = 0; } } ////////////////////////////////////////////////////////////////// // Flow Limitation Event ////////////////////////////////////////////////////////////////// if (data[109] & 48) { // OA state 1st second if (!inFL) { FLstart = ti; inFL = true; } FLend = ti+1000L; } else { if (inFL) { inFL = false; FL->AddEvent(FLstart,(FLend-FLstart) / 1000L); FLstart = 0; } } if (data[113] & 48) { if (!inFL) { FLstart = ti+1000L; inFL = true; } FLend = ti+2000L; } else { if (inFL) { inFL = false; FL->AddEvent(FLstart,(FLend-FLstart) / 1000L); FLstart = 0; } } ////////////////////////////////////////////////////////////////// // Periodic Breathing Event ////////////////////////////////////////////////////////////////// if (data[109] & 192) { // OA state 1st second if (!inPB) { PBstart = ti; inPB = true; } PBend = ti+1000L; } else { if (inPB) { inPB = false; PB->AddEvent(PBstart,(PBend-PBstart) / 1000L); PBstart = 0; } } if (data[113] & 192) { if (!inPB) { PBstart = ti+1000L; inPB = true; } PBend = ti+2000L; } else { if (inPB) { inPB = false; PB->AddEvent(PBstart,(PBend-PBstart) / 1000L); PBstart = 0; } } ////////////////////////////////////////////////////////////////// // Respiratory Effort Related Arousal Event ////////////////////////////////////////////////////////////////// if (data[111] & 48) { // OA state 1st second if (!inRE) { REstart = ti; inRE = true; } REend = ti+1000L; } else { if (inRE) { inRE = false; RE->AddEvent(REstart,(REend-REstart) / 1000L); REstart = 0; } } if (data[115] & 48) { if (!inRE) { REstart = ti+1000L; inRE = true; } REend = ti+2000L; } else { if (inRE) { inRE = false; RE->AddEvent(REstart,(REend-REstart) / 1000L); REstart = 0; } } ***/ } } while (true); if (inSession && sess) { /*** // Close event states if they are still open, and write event. if (inH) HY->AddEvent(Hstart,(Hend-Hstart) / 1000L); if (inOA) OA->AddEvent(OAstart,(OAend-OAstart) / 1000L); if (inCA) NOA->AddEvent(CAstart,(CAend-CAstart) / 1000L); if (inLL) LL->AddEvent(LLstart,(LLend-LLstart) / 1000L); if (inVS) HY->AddEvent(VSstart,(VSend-VSstart) / 1000L); if (inExP) EXP->AddEvent(ExPstart,(ExPend-ExPstart) / 1000L); if (inFL) FL->AddEvent(FLstart,(FLend-FLstart) / 1000L); if (inPB) PB->AddEvent(PBstart,(PBend-PBstart) / 1000L); if (inPB) RE->AddEvent(REstart,(REend-REstart) / 1000L); ***/ // update min and max // then add to machine if (sess->first() == 0) qWarning() << "R.BIN first = 0 - 1665"; EventDataType min = flow->Min(); EventDataType max = flow->Max(); sess->setMin(CPAP_FlowRate, min); sess->setMax(CPAP_FlowRate, max); sess->setPhysMax(CPAP_FlowRate, min); // TODO: not sure :/ sess->setPhysMin(CPAP_FlowRate, max); sess->really_set_last(flow->last()); sess = nullptr; inSession = false; } rf.close(); qDebug() << "DV6 R.BIN processed" << rf.numread() << "records"; return true; } //////////////////////////////////////////////////////////////////////////////////////// // Parse L.BIN for per minute data //////////////////////////////////////////////////////////////////////////////////////// bool load6PerMinute () { RollingFile rf; Session *sess = nullptr; unsigned int rec_ts1, previousRecBegin = 0; bool inSession = false; // true if we are adding data to this session if (!rf.open("L.BIN")) { qWarning() << "DV6 Unable to open L.BIN"; return false; } qDebug() << "L.BIN Minute Data starting at record" << rf.recnum(); EventList * leak = NULL; EventList * maxleak = NULL; EventList * RR = NULL; EventList * Pressure = NULL; EventList * TV = NULL; EventList * MV = NULL; QMap::iterator sinfo; sinfo = SessionData.begin(); // Walk through all the records do { DV6_L_REC * rec = (DV6_L_REC *) rf.get(); if (rec == nullptr) break; sess = sinfo->sess; // Get the timestamp from the record rec_ts1 = convertTime(rec->timestamp); if (rec_ts1 < previousRecBegin) { #ifdef DEBUG6 qWarning() << "L.BIN - Corruption/Out of sequence data found, skipping record" << rf.recnum() << ", prev" << QDateTime::fromSecsSinceEpoch(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss") << previousRecBegin << "this" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << rec_ts1; #endif continue; } /**** // Look for a gap in DV6_L records. They should be at one minute intervals. // If there is a gap, we are probably in a new session if (inSession && ((rec_ts1 - previousRecBegin) > 60)) { qDebug() << "L.BIN record gap, current" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "previous" << QDateTime::fromSecsSinceEpoch(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss"); sess->set_last(maxleak->last()); sess = nullptr; leak = maxleak = MV = TV = RR = Pressure = nullptr; inSession = false; } ****/ // Skip over sessions until we find one that this record is in while (rec_ts1 > sinfo->end) { #ifdef DEBUG6 qDebug() << "L.BIN - skipping session" << QDateTime::fromSecsSinceEpoch(sinfo->begin).toString("MM/dd/yyyy hh:mm:ss") << "looking for" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss"); #endif if (inSession && sess) { // Close the open session and update the min and max sess->set_last(maxleak->last()); sess = nullptr; leak = maxleak = MV = TV = RR = Pressure = nullptr; inSession = false; } sinfo++; if (sinfo == SessionData.end()) break; } previousRecBegin = rec_ts1; // If we have data beyond last session, we are in trouble (for unknown reasons) if (sinfo == SessionData.end()) { qWarning() << "DV6 L.BIN import ran out of sessions to match flow data"; break; } if (rec_ts1 < previousRecBegin) { qWarning() << "L.BIN - Corruption/Out of sequence data found, stopping import, prev" << QDateTime::fromSecsSinceEpoch(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss") << "this" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss"); break; } // Check if record belongs in this session or a future session if (!inSession && rec_ts1 <= sinfo->end) { sess = sinfo->sess; // this is the Session we want if (!inSession && sess) { leak = sess->AddEventList(CPAP_Leak, EVL_Event); // , 1.0, 0.0, 0.0, 0.0, double(60000) / double(1)); maxleak = sess->AddEventList(CPAP_LeakTotal, EVL_Event);// , 1.0, 0.0, 0.0, 0.0, double(60000) / double(1)); RR = sess->AddEventList(CPAP_RespRate, EVL_Event); MV = sess->AddEventList(CPAP_MinuteVent, EVL_Event); TV = sess->AddEventList(CPAP_TidalVolume, EVL_Event); if (sess->last()/1000 > sinfo->end) sinfo->end = sess->last()/1000; // if (!sinfo->haveHighResData) { // Don't use this pressure if we already have higher resolution data if (sinfo->mode == MODE_UNKNOWN) { if (rec->pressureLimitLow != rec->pressureLimitHigh) { sess->settings[CPAP_PressureMin] = rec->pressureLimitLow / 10.0f; sess->settings[CPAP_PressureMax] = rec->pressureLimitHigh / 10.0f; // if available sess->settings[CPAP_PS) = .... sess->settings[CPAP_Mode] = MODE_APAP; sinfo->mode = MODE_APAP; } else { sess->settings[CPAP_Mode] = MODE_CPAP; sess->settings[CPAP_Pressure] = rec->pressureLimitHigh / 10.0f; sinfo->mode = MODE_CPAP; } inSession = true; } } } if (inSession) { // Record breath and pressure waveforms qint64 ti = qint64(rec_ts1) * 1000; maxleak->AddEvent(ti, rec->maxLeak); //??? leak->AddEvent(ti, rec->avgLeak); //??? RR->AddEvent(ti, rec->breathRate); if ( sinfo->firstHighRes == 0 // No high res data || rec_ts1 < sinfo->firstHighRes // Before high res data begins || ((rec_ts1 > (sinfo->lastHighRes+2)) && (sinfo->lastHighRes > 0))) // or after high res data ends { if (!Pressure) Pressure = sess->AddEventList(CPAP_Pressure, EVL_Event, 0.1f); // if (sinfo->firstHighRes == 0) { Pressure->AddEvent(ti, rec->avgPressure); // average pressure for next minute Pressure->AddEvent(ti + 59998, rec->avgPressure); // end of pressure block // } else { // for (int i = 0; i < 60; i++) { // Pressure->AddEvent(ti+i, rec->avgPressure); // average pressure for next minute // } // } } /*** if (Pressure) qDebug() << "Lowres pressure" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "rec_ts1" << rec_ts1 << "firstHighRes" << sinfo->firstHighRes << "last" << sinfo->lastHighRes << "Pressure" << rec->avgPressure / 10.0f; ***/ unsigned tv = rec->tidalVolume6 + (rec->tidalVolume7 << 8); MV->AddEvent(ti, rec->breathRate * tv / 1000.0 ); TV->AddEvent(ti, tv); if (!sess->channelExists(CPAP_FlowRate)) { // No flow rate, so lets grab this data... } } } while (true); if (sess && inSession) { sess->set_last(maxleak->last()); } rf.close(); qDebug() << "DV6 L.BIN processed" << rf.numread() << "records"; return true; } //////////////////////////////////////////////////////////////////////////////////////// // Parse E.BIN for event data //////////////////////////////////////////////////////////////////////////////////////// bool load6EventData () { RollingFile rf; Session *sess = nullptr; unsigned int rec_ts1, rec_ts2, previousRecBegin; bool inSession = false; // true if we are adding data to this session EventList * OA = nullptr; EventList * CA = nullptr; EventList * H = nullptr; EventList * RE = nullptr; EventList * PB = nullptr; EventList * LL = nullptr; EventList * EP = nullptr; EventList * SN = nullptr; EventList * FL = nullptr; // EventList * FLG = nullptr; if (!rf.open("E.BIN")) { qWarning() << "DV6 Unable to open E.BIN"; return false; } qDebug() << "Processing E.BIN starting at record" << rf.recnum(); QMap::iterator sinfo; sinfo = SessionData.begin(); // Walk through all the records do { DV6_E_REC * rec = (DV6_E_REC *) rf.get(); if (rec == nullptr) break; sess = sinfo->sess; // Get the timestamp from the record rec_ts1 = convertTime(rec->begin); rec_ts2 = convertTime(rec->end); // Skip over sessions until we find one that this record is in while (rec_ts1 > sinfo->end) { #ifdef DEBUG6 qDebug() << "E.BIN - skipping session" << QDateTime::fromSecsSinceEpoch(sinfo->begin).toString("MM/dd/yyyy hh:mm:ss") << "looking for" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss"); #endif if (inSession) { // Close the open session and update the min and max if (OA->last() > 0) sess->set_last(OA->last()); if (CA->last() > 0) sess->set_last(CA->last()); if (H->last() > 0) sess->set_last(H->last()); if (RE->last() > 0) sess->set_last(RE->last()); if (PB->last() > 0) sess->set_last(PB->last()); if (LL->last() > 0) sess->set_last(LL->last()); if (EP->last() > 0) sess->set_last(EP->last()); if (FL->last() > 0) sess->set_last(FL->last()); if (SN->last() > 0) sess->set_last(SN->last()); /*** if (FLG->last() > 0) sess->set_last(FLG->last()); ***/ sess = nullptr; H = CA = RE = OA = PB = LL = EP = SN = FL = nullptr; inSession = false; } sinfo++; if (sinfo == SessionData.end()) break; } previousRecBegin = rec_ts1; // If we have data beyond last session, we are in trouble (for unknown reasons) if (sinfo == SessionData.end()) { qWarning() << "DV6 E.BIN import ran out of sessions," << "event data begins" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss"); break; } if (rec_ts1 < previousRecBegin) { qWarning() << "DV6 E.BIN - Out of sequence data found, skipping, prev" << QDateTime::fromSecsSinceEpoch(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss") << "this event" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss"); continue; // break; } // Check if record belongs in this session or a future session if (!inSession && rec_ts1 <= sinfo->end) { sess = sinfo->sess; // this is the Session we want if (!inSession && sess) { OA = sess->AddEventList(CPAP_Obstructive, EVL_Event); H = sess->AddEventList(CPAP_Hypopnea, EVL_Event); RE = sess->AddEventList(CPAP_RERA, EVL_Event); CA = sess->AddEventList(CPAP_ClearAirway, EVL_Event); PB = sess->AddEventList(CPAP_PB, EVL_Event); LL = sess->AddEventList(CPAP_LargeLeak, EVL_Event); EP = sess->AddEventList(CPAP_ExP, EVL_Event); SN = sess->AddEventList(INTP_SnoreFlag, EVL_Event); FL = sess->AddEventList(CPAP_FlowLimit, EVL_Event); // FLG = sess->AddEventList(CPAP_FLG, EVL_Waveform, 1.0f, 0.0f, 0.0f, 0.0f, double(2000) / double(2)); // SN = sess->AddEventList(CPAP_Snore, EVL_Waveform, 1.0f, 0.0f, 0.0f, 0.0f, double(2000) / double(2)); inSession = true; } } if (inSession) { qint64 duration = rec_ts2 - rec_ts1; // We make an ad hoc adjustment to the start time so that the event lines up better with the flow graph // TODO: We don't know what is really going on here. Is it sloppiness on the part of the DV6 in recording time stamps? // qint64 ti = qint64(rec_ts1 - (duration/2)) * 1000L; qint64 ti = qint64(rec_ts1 - duration) * 1000L; if (duration < 0) { qDebug() << "E.BIN at" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "reports duration of" << duration << "ending" << QDateTime::fromSecsSinceEpoch(rec_ts2).toString("MM/dd/yyyy hh:mm:ss"); } int code = rec->event_type; /*** ////////////////////////////////////////////////////////////////// // Show Snore Events as a graph ////////////////////////////////////////////////////////////////// if (code == 9) { qint16 severity = rec->event_severity; SN->AddWaveform(ti, &severity, 1, duration*1000); } ////////////////////////////////////////////////////////////////// // Show Flow Limit Events as a graph ////////////////////////////////////////////////////////////////// if (code == 10) { qint16 severity = rec->event_severity; FLG->AddWaveform(ti, &severity, 1, duration*1000); } ***/ if (rec->event_severity >= 3) switch (code) { case 1: CA->AddEvent(ti, duration); break; case 2: OA->AddEvent(ti, duration); #ifdef DEBUGDV6 qDebug() << "E.BIN - OA" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "ti" << QDateTime::fromSecsSinceEpoch(ti/1000).toString("hh:mm:ss") << "duration" << duration << "r" << rf.recnum(); #endif break; case 4: H->AddEvent(ti, duration); #ifdef DEBUGDV6 qDebug() << "E.BIN - H" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "ti" << QDateTime::fromSecsSinceEpoch(ti/1000).toString("hh:mm:ss") << "duration" << duration << "r" << rf.recnum(); #endif break; case 5: RE->AddEvent(ti, duration); #ifdef DEBUGDV6 qDebug() << "E.BIN - RERA" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "ti" << QDateTime::fromSecsSinceEpoch(ti/1000).toString("hh:mm:ss") << "duration" << duration << "r" << rf.recnum(); #endif break; case 8: // snore SN->AddEvent(ti, duration); #ifdef DEBUGDV6 qDebug() << "E.BIN - Snore" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "ti" << QDateTime::fromSecsSinceEpoch(ti/1000).toString("hh:mm:ss") << "duration" << duration << "r" << rf.recnum(); #endif break; case 9: // expiratory puff EP->AddEvent(ti, duration); #ifdef DEBUGDV6 qDebug() << "E.BIN - exhale puff" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "ti" << QDateTime::fromSecsSinceEpoch(ti/1000).toString("hh:mm:ss") << "duration" << duration << "r" << rf.recnum(); #endif break; case 10: // flow limitation FL->AddEvent(ti, duration); #ifdef DEBUGDV6 qDebug() << "E.BIN - flow limit" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "ti" << QDateTime::fromSecsSinceEpoch(ti/1000).toString("hh:mm:ss") << "duration" << duration << "r" << rf.recnum(); #endif break; case 11: // periodic breathing PB->AddEvent(ti, duration); #ifdef DEBUGDV6 qDebug() << "E.BIN - periodic breathing" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "ti" << QDateTime::fromSecsSinceEpoch(ti/1000).toString("hh:mm:ss") << "duration" << duration << "r" << rf.recnum(); #endif break; case 12: // large leaks LL->AddEvent(ti, duration); #ifdef DEBUGDV6 qDebug() << "E.BIN - large leak" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "ti" << QDateTime::fromSecsSinceEpoch(ti/1000).toString("hh:mm:ss") << "duration" << duration << "r" << rf.recnum(); #endif break; case 13: // pressure change break; case 14: // start of session #ifdef DEBUGDV6 qDebug() << "E.BIN - session start" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum(); #endif break; default: break; } } } while (true); rf.close(); qDebug() << "DV6 E.BIN processed" << rf.numread() << "records"; return true; } //////////////////////////////////////////////////////////////////////////////////////// // Finalize data and add to database //////////////////////////////////////////////////////////////////////////////////////// int addSessions() { for (auto si=SessionData.begin(), end=SessionData.end(); si != end; ++si) { Session * sess = si.value().sess; if (sess) { if ( ! mach->AddSession(sess) ) { qWarning() << "Session" << sess->session() << "was not addded"; } #ifdef DEBUG6 else qDebug() << "Added session" << sess->session() << QDateTime::fromSecsSinceEpoch(sess->session()).toString("MM/dd/yyyy hh:mm:ss");; #endif // Update indexes, process waveform and perform flagging sess->UpdateSummaries(); // Save is not threadsafe sess->Store(mach->getDataPath()); // Unload them from memory sess->TrashEvents(); } // else // qWarning() << "addSessions: session pointer is null"; } return SessionData.size(); } //////////////////////////////////////////////////////////////////////////////////////// // Create backup of input files // Create dated backup of settings file if changed //////////////////////////////////////////////////////////////////////////////////////// bool backup6 (const QString & path) { if (rebuild_from_backups || !create_backups) return true; QDir ipath(path); QDir cpath(card_path); QDir bpath(backup_path); QDir hpath(history_path); // Copy input data to backup location copyPath(ipath.absolutePath(), bpath.absolutePath(), true); // Create archive of settings file if needed (SET.BIN) bool backup_settings = true; QStringList filters; QFile settingsFile; QString inputFile = cpath.absolutePath() + "/SET.BIN"; settingsFile.setFileName(inputFile); filters << "SET_*.BIN"; hpath.setNameFilters(filters); hpath.setFilter(QDir::Files); hpath.setSorting(QDir::Name | QDir::Reversed); QStringList fileNames = hpath.entryList(); // Get list of files if (! fileNames.isEmpty()) { QString lastFile = fileNames.first(); qDebug() << "last settings file is" << lastFile << "new file is" << settingsFile.fileName(); QByteArray newMD5 = fileChecksum(settingsFile.fileName(), QCryptographicHash::Md5); QByteArray oldMD5 = fileChecksum(hpath.absolutePath()+"/"+lastFile, QCryptographicHash::Md5); if (newMD5 == oldMD5) backup_settings = false; } if (backup_settings && !DailySummaries.isEmpty()) { DV6_S_Data ds = DailySummaries.last(); QString newFile = hpath.absolutePath() + "/SET_" + getNominalDate(ds.start_time).toString("yyyyMMdd") + ".BIN"; if (!settingsFile.copy(inputFile, newFile)) { qWarning() << "DV6 backup could not copy" << inputFile << "to" << newFile << ", error code" << settingsFile.error() << settingsFile.errorString(); } } // We're done! return true; } //////////////////////////////////////////////////////////////////////////////////////// // Initialize DV6 environment //////////////////////////////////////////////////////////////////////////////////////// bool init6Environment (const QString & path) { // Create Machine database record if it doesn't exist already mach = p_profile->CreateMachine(info); if (mach == nullptr) { qWarning() << "Could not create DV6 Machine data structure"; return false; } backup_path = mach->getBackupPath(); history_path = backup_path + "/HISTORY"; rebuild_path = backup_path + "/DV6"; // Compare QDirs rather than QStrings because separators may be different, especially on Windows. QDir ipath(path); QDir bpath(backup_path); QDir hpath(history_path); if (ipath == bpath) { // Don't create backups if importing from backup folder rebuild_from_backups = true; create_backups = false; } else { rebuild_from_backups = false; create_backups = p_profile->session->backupCardData(); if ( ! bpath.exists()) { if ( ! bpath.mkpath(backup_path) ) { qWarning() << "Could not create DV6 backup directory" << backup_path; return false; } } if ( ! hpath.exists()) { if ( ! hpath.mkpath(history_path) ) { qWarning() << "Could not create DV6 backup HISTORY directory" << history_path; return false; } } } return true; } //////////////////////////////////////////////////////////////////////////////////////// // Open a DV6 SD card, parse everything, add to OSCAR database //////////////////////////////////////////////////////////////////////////////////////// int IntellipapLoader::OpenDV6(const QString & path) { qDebug() << "DV6 loader started"; card_path = path + DV6_DIR; emit updateMessage(QObject::tr("Getting Ready...")); emit setProgressValue(0); QCoreApplication::processEvents(); // 1. Prime the machine database's info field with this machine info = newInfo(); // 2. VER.BIN - Parse model number, serial, etc. into info structure if (!load6VersionInfo(card_path)) return -1; // 3. Initialize rest of the DV6 loader environment if (!init6Environment (path)) return -1; // 4. SET.BIN - Parse settings file (which is only the latest settings) if (!load6Settings(card_path)) return -1; // 5. S.BIN - Open and parse day summary list and create a list of days if (!load6DailySummaries()) return -1; emit updateMessage(QObject::tr("Backing up files...")); QCoreApplication::processEvents(); // 6. Back up data files (must do after parsing VER.BIN, S.BIN, and creating Machine) if (!backup6(path)) return -1; emit updateMessage(QObject::tr("Reading data files...")); QCoreApplication::processEvents(); // 7. U.BIN - Open and parse session list and create a list of session times // (S.BIN must already be loaded) if (!load6Sessions()) return -1; // Create OSCAR session list from session times and summary data if (create6Sessions() <= 0) return -1; // R.BIN - Open and parse flow data if (!load6HighResData()) return -1; // L.BIN - Open and parse per minute data if (!load6PerMinute()) return -1; // E.BIN - Open and parse event data if (!load6EventData()) return -1; emit updateMessage(QObject::tr("Finishing up...")); QCoreApplication::processEvents(); // Finalize input return addSessions(); } int IntellipapLoader::Open(const QString & dirpath) { // Check for SL directory // Check for DV5MFirm.bin? QString path(dirpath); path = path.replace("\\", "/"); if (path.endsWith(SL_DIR)) { path.chop(3); } else if (path.endsWith(DV6_DIR)) { path.chop(4); } QDir dir; int r = -1; // Sometimes there can be an SL folder because SmartLink dumps an old DV5 firmware in it, so check it first if (dir.exists(path + SL_DIR)) r = OpenDV5(path); if ((r<0) && dir.exists(path + DV6_DIR)) r = OpenDV6(path); return r; } void IntellipapLoader::initChannels() { using namespace schema; Channel * chan = nullptr; channel.add(GRP_CPAP, chan = new Channel(INTP_SmartFlexMode = 0x1165, SETTING, MT_CPAP, SESSION, "INTPSmartFlexMode", QObject::tr("SmartFlex Mode"), QObject::tr("Intellipap pressure relief mode."), QObject::tr("SmartFlex Mode"), "", DEFAULT, Qt::green)); chan->addOption(0, STR_TR_Off); chan->addOption(1, QObject::tr("Ramp Only")); chan->addOption(2, QObject::tr("Full Time")); channel.add(GRP_CPAP, chan = new Channel(INTP_SmartFlexLevel = 0x1169, SETTING, MT_CPAP, SESSION, "INTPSmartFlexLevel", QObject::tr("SmartFlex Level"), QObject::tr("Intellipap pressure relief level."), QObject::tr("SmartFlex Level"), "", DEFAULT, Qt::green)); channel.add(GRP_CPAP, new Channel(INTP_SnoreFlag = 0xe301, FLAG, MT_CPAP, SESSION, "INTP_SnoreFlag", QObject::tr("Snore"), QObject::tr("Snoring event."), QObject::tr("SN"), STR_UNIT_EventsPerHour, DEFAULT, QColor("#e20004"))); } bool intellipap_initialized = false; void IntellipapLoader::Register() { if (!intellipap_initialized) { qDebug() << "Registering IntellipapLoader"; RegisterLoader(new IntellipapLoader()); //InitModelMap(); intellipap_initialized = true; } return; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/intellipap_loader.h000066400000000000000000000070721417327530600251540ustar00rootroot00000000000000/* Intellipap Loader Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef INTELLIPAP_LOADER_H #define INTELLIPAP_LOADER_H #include "SleepLib/machine.h" // Base class: MachineLoader #include "SleepLib/machine_loader.h" #include "SleepLib/profiles.h" //******************************************************************************************** /// IMPORTANT!!! //******************************************************************************************** // Please INCREMENT the following value when making changes to this loaders implementation. // const int intellipap_data_version = 3; // //******************************************************************************************** /*! \class Intellipap \brief Intellipap customized machine object */ class Intellipap: public CPAP { public: Intellipap(Profile *, MachineID id = 0); virtual ~Intellipap(); }; const int intellipap_load_buffer_size = 1024 * 1024; extern ChannelID INTP_SmartFlexMode; extern ChannelID INTP_SmartFlexLevel; const QString intellipap_class_name = STR_MACH_Intellipap; /*! \class IntellipapLoader \brief Loader for DeVilbiss Intellipap Auto data This is only relatively recent addition and still needs more work */ class IntellipapLoader : public CPAPLoader { public: IntellipapLoader(); virtual ~IntellipapLoader(); //! \brief Detect if the given path contains a valid Folder structure virtual bool Detect(const QString & path); //! \brief Scans path for Intellipap data signature, and Loads any new data virtual int Open(const QString & path); //! \brief Scans path for Intellipap DV5 data signature, and Loads any new data virtual int OpenDV5(const QString & path); //! \brief Scans path for Intellipap DV6 data signature, and Loads any new data virtual int OpenDV6(const QString & path); //! \brief Returns SleepLib database version of this IntelliPap loader virtual int Version() { return intellipap_data_version; } //! \brief Returns the machine class name of this IntelliPap, "Intellipap" virtual const QString &loaderName() { return intellipap_class_name; } //! \brief Creates a machine object, indexed by serial number // Machine *CreateMachine(QString serial); //! \brief Registers this MachineLoader with the master list, so Intellipap data can load static void Register(); virtual MachineInfo newInfo() { return MachineInfo(MT_CPAP, 0, intellipap_class_name, QObject::tr("DeVilbiss"), QString(), QString(), QString(), QObject::tr("Intellipap"), QDateTime::currentDateTime(), intellipap_data_version); } virtual void initChannels(); //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Now for some CPAPLoader overrides //////////////////////////////////////////////////////////////////////////////////////////////////////////// virtual QString presRelLabel() { return QObject::tr("SmartFlex Settings"); } // might not need this one virtual ChannelID presReliefMode() { return INTP_SmartFlexMode; } virtual ChannelID presRelLevel() { return INTP_SmartFlexLevel; } //////////////////////////////////////////////////////////////////////////////////////////////////////////// protected: QString last; unsigned char *m_buffer; }; #endif // INTELLIPAP_LOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/md300w1_loader.cpp000066400000000000000000000145001417327530600244330ustar00rootroot00000000000000/* SleepLib ChoiceMMed MD300W1 Oximeter Loader Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ //******************************************************************************************** /// IMPORTANT!!! //******************************************************************************************** // Please INCREMENT the md300w1_data_version in md300w1_loader.h when making changes to this // loader that change loader behaviour or modify channels. //******************************************************************************************** // #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; #include "md300w1_loader.h" #include "SleepLib/machine.h" #include "SleepLib/session.h" MD300W1Loader::MD300W1Loader() { m_type = MT_OXIMETER; m_abort = false; m_streaming = false; m_importing = false; imp_callbacks = 0; // have no idea.. assuming it's another CP2102 USB UART, which won't help detection :/ m_vendorID = 0; m_productID = 0; startTimer.setParent(this); resetTimer.setParent(this); } MD300W1Loader::~MD300W1Loader() { } bool MD300W1Loader::Detect(const QString &path) { Q_UNUSED(path); return false; } int MD300W1Loader::Open(const QString & path) { // Only one active Oximeter module at a time, set in preferences qDebug() << "MD300W1 Loader opening " << path; m_itemCnt = 0; m_itemTotal = 0; m_abort = false; m_importing = false; started_import = false; started_reading = false; finished_import = false; imp_callbacks = 0; cb_reset = 0; m_time.start(); // Cheating using path for two serial oximetry modes if (path.compare("import") == 0) { setStatus(IMPORTING); startTimer.stop(); startImportTimeout(); return 1; } else if (path.compare("live") == 0) { m_startTime = oxitime = QDateTime::currentDateTime(); setStatus(LIVE); return 1; } QString ext = path.section(".", -1); // find the last '.' if (ext.compare("dat", Qt::CaseInsensitive)==0) { // try to read and process SpoR file.. return readDATFile(path) ? 1 : 0; } return 0; } void MD300W1Loader::processBytes(QByteArray bytes) { Q_UNUSED(bytes); return; } int MD300W1Loader::doImportMode() { return 0; } int MD300W1Loader::doLiveMode() { return 0; } // Switch MD300W1 device to live streaming mode void MD300W1Loader::resetDevice() { } // Switch MD300W1 device to record transmission mode void MD300W1Loader::requestData() { } void MD300W1Loader::killTimers() { startTimer.stop(); resetTimer.stop(); } void MD300W1Loader::startImportTimeout() { return; } void MD300W1Loader::resetImportTimeout() { return; } // MedView .dat file (ChoiceMMed MD300B, MD300KI, MD300I, MD300W1, MD300C318, MD2000A) // Format: // Bytes 0 (1 2) // id n // n*11 0 1 2 3 4 5 6 7 8 9 10 // 0 0 id yr mm dd hh mm ss o2 pulse // report title etc. bool MD300W1Loader::readDATFile(const QString & path) { QFile file(path); qDebug() << "MD300W Loader attempting to read " << path; if (!file.exists()) { qDebug() << "File does not exist: " << path; QMessageBox::warning(nullptr, STR_MessageBox_Error, "

"+tr("Could not find the oximeter file:")+"

"+path+"

"); return false; } if (!file.open(QFile::ReadOnly)) { qDebug() << "Can't open file R/O: " << path; QMessageBox::warning(nullptr, STR_MessageBox_Error, "

"+tr("Could not open the oximeter file:")+"

"+path+"

"); return false; } QByteArray data; data = file.readAll(); long size = data.size(); // Number of records int n = ((unsigned char)data.at(2) << 8) | (unsigned char)data.at(1); // CHECKME: if (size < (n*11)+3) { qDebug() << "Short MD300W1 .dat file" << path; return false; } unsigned char o2, pr; qint32 lasttime=0, ts=0; int gap; for (int pos = 0; pos < n; ++pos) { int i = 3 + (pos * 11); QString datestr = QString().sprintf("%02d/%02d/%02d %02d:%02d:%02d", (unsigned char)data.at(i+4),(unsigned char)data.at(i+5),(unsigned char)data.at(i+3), (unsigned char)data.at(i+6),(unsigned char)data.at(i+7),(unsigned char)data.at(i+8)); // Ensure date is correct first to ensure DST is handled correctly QDate date = QDate::fromString(datestr.left(8),"MM/dd/yy"); QTime time = QTime::fromString(datestr.right(8),"HH:mm:ss"); if (date.year() < 2000) { date = date.addYears(100); } QDateTime datetime = QDateTime(date, time); ts = datetime.toTime_t(); gap = ts - lasttime; if (gap > 1) { // always true for first record, b/c time started on 1 Jan 1970 if (gap < 360) { // Less than 5 minutes? Merge session gap--; // fill with zeroes for (int j = 0; j < gap; j++) { oxirec->append(OxiRecord(0,0)); } } else { // Create a new session, always for first record qDebug() << "Create session for " << datetime.toString("yyyy-MMM-dd HH:mm:ss"); oxirec = new QVector; oxisessions[datetime] = oxirec; m_startTime = datetime; // works for single session files... } } pr=(unsigned char)(data.at(i+10)); o2=(unsigned char)(data.at(i+9)); oxirec->append(OxiRecord(pr, o2)); lasttime = ts; } // processing gets done later return true; } void MD300W1Loader::process() { } static bool MD300W1_initialized = false; void MD300W1Loader::Register() { if (MD300W1_initialized) { return; } qDebug() << "Registering MD300W1Loader"; RegisterLoader(new MD300W1Loader()); MD300W1_initialized = true; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/md300w1_loader.h000066400000000000000000000040731417327530600241040ustar00rootroot00000000000000/* SleepLib ChoiceMMed MD300W1 Oximeter Loader Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef MD300W1LOADER_H #define MD300W1LOADER_H #include "SleepLib/serialoximeter.h" const QString md300w1_class_name = "MD300W1"; const int md300w1_data_version = 1; /*! \class MD300W1Loader \brief Importer for ChoiceMMed MD300W1 data format.. */ class MD300W1Loader : public SerialOximeter { Q_OBJECT public: MD300W1Loader(); virtual ~MD300W1Loader(); virtual bool Detect(const QString &path); virtual int Open(const QString & path); static void Register(); virtual int Version() { return md300w1_data_version; } virtual const QString &loaderName() { return md300w1_class_name; } // Machine *CreateMachine(); virtual MachineInfo newInfo() { return MachineInfo(MT_OXIMETER, 0, md300w1_class_name, QObject::tr("ChoiceMMed"), QString(), QString(), QString(), QObject::tr("MD300"), QDateTime::currentDateTime(), md300w1_data_version); } virtual void process(); virtual bool isStartTimeValid() { return true; } protected slots: virtual void resetImportTimeout(); virtual void startImportTimeout(); protected: bool readDATFile(const QString & path); virtual void processBytes(QByteArray bytes); int doImportMode(); int doLiveMode(); virtual void killTimers(); // Switch MD300W1 device to live streaming mode virtual void resetDevice(); // Switch MD300W1 device to record transmission mode void requestData(); private: EventList *PULSE; EventList *SPO2; QTime m_time; QByteArray buffer; bool started_import; bool finished_import; bool started_reading; QDateTime oxitime; int cb_reset,imp_callbacks; int received_bytes; int m_itemCnt; int m_itemTotal; }; #endif // MD300W1LOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/mseries_loader.cpp000066400000000000000000000342551417327530600250200ustar00rootroot00000000000000/* SleepLib RemStar M-Series Loader Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include "mseries_loader.h" MSeries::MSeries(Profile *profile, MachineID id) : CPAP(profile, id) { } MSeries::~MSeries() { } MSeriesLoader::MSeriesLoader() { m_type = MT_CPAP; epoch = QDateTime(QDate(2000, 1, 1), QTime(0, 0, 0), Qt::UTC).toTime_t(); epoch -= QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0), Qt::UTC).toTime_t(); } MSeriesLoader::~MSeriesLoader() { } //struct MSeriesHeader { // quint8 b1; //0x52 // quint32 a32; //0x00000049 // quint16 u16[8]; // quint8 b2; //0x02 // char setname[16]; // char firstname[25]; // char lastname[25]; // char serial[50]; // quint16 b3; //0x00 // quint16 b4; //0x66 // quint16 b5; //0xff //} __attribute__((packed)); /* blockLayoutOffsets { cardInformationBlock = 0, brandID = 0, cardType = 2, cardVersion = 3 startUIDB = 4 endUIDB = 6, startCPB = 8, endCPB = 10, startCDCB = 12, endCDCB = 14, startCDB = 0x10, endCDB = 0x12, checksum = 20, userIDBlock = 0x15 personalID = 1, patientFName = 0x11, patientLName = 0x2a, serialNumber = 0x43, modelNumber = 0x4d, textData = 0x57 checksum = 0x77, cardPrescriptionBlock = 0x8d, cardDataControlBlock = 0xa3, validFlagOne = 3, headPtrOne = 4, tailPtrOne = 6, cdbChecksumOne = 8, validFlagTwo = 9 headPtrTwo = 10, tailPtrTwo = 12, cdbChecksumTwo = 14, cardDataBlock = 0xb2, basicCompliance = 1, fosq = 2, Invalid = 0xff, sleepProfile = 8, sleepProfile2 = 10, sleepProfile3 = 14, sleepTrend = 9, sleepTrend2 = 11, sleepTrend3 = 15, smartAutoCPAPProfile = 3, smartAutoCPAPTrend = 4, ventCompliance2 = 13, ventilatorCompliance = 7, ventilatorProfile = 6, ventProfile2 = 12 startChar = 0xfe, stopChar = 0x7f */ int MSeriesLoader::Open(const QString & path) { // Until a smartcard reader is written, this is not an auto-scanner.. it just opens a block file.. QFile file(path); if (!file.exists()) { return 0; } if (file.size() != 32768) { // Check filesize matches smartcard? return 0; } if (!file.open(QFile::ReadOnly)) { qDebug() << "Couldn't open M-Series file:" << path; return 0; } QByteArray block = file.readAll(); // Thanks to Phil Gillam for the pointers on this one.. const unsigned char *cardinfo = (unsigned char *)block.data(); quint16 magic = cardinfo[0] << 8 | cardinfo[1]; if (magic != 0x5249) { // "RI" Respironics Magic number return 0; } //quint8 cardtype=cardinfo[2]; //quint8 cardver=cardinfo[3]; quint16 user_offset = (cardinfo[4] << 8) | cardinfo[5]; //quint16 rx_offset=(cardinfo[8] << 8) | cardinfo[9]; quint16 control_offset = (cardinfo[12] << 8) | cardinfo[13]; //quint16 data_offset=(cardinfo[16] << 8) | cardinfo[17]; const char *userinfo = block.data() + user_offset; QString setname = QString(userinfo + 0x1); QString firstname = QString(userinfo + 0x11); QString lastname = QString(userinfo + 0x2a); QString serial = QString(userinfo + 0x43); serial.truncate(10); QString model = QString(userinfo + 0x4d); QString textdata = QString(userinfo + 0x57); quint8 userinfochk = *(userinfo + 0x77); quint8 tmp = 0; for (int i = 0; i < 0x77; i++) { tmp += userinfo[i]; } if (tmp != userinfochk) { qDebug() << "MSeries UserInfo block checksum failure" << path; } //const unsigned char * rxblock=(unsigned char *)block.data()+rx_offset; unsigned char *controlblock = (unsigned char *)block.data() + control_offset; quint16 count = controlblock[0] << 8 | controlblock[1]; // number of control blocks if (controlblock[1] != controlblock[2]) { qDebug() << "Control block count does not match." << path; } QList head, tail; controlblock += 3; quint16 datastarts=0, dataends=0, h16, t16;//, tmp16, if (controlblock[0]) { datastarts = controlblock[1] | (controlblock[2] << 8); dataends = controlblock[3] | (controlblock[4] << 8); } controlblock += 6; if (controlblock[0]) { if ((controlblock[1] | (controlblock[2] << 8)) != datastarts) { qDebug() << "Non matching card size start identifier" << path; } if ((controlblock[3] | (controlblock[4] << 8)) != dataends) { qDebug() << "Non matching card size end identifier" << path; } } controlblock += 6; count -= 2; //tmp16 = controlblock[0] | controlblock[1] << 8; controlblock += 2; for (int i = 0; i < count / 2; i++) { if (controlblock[0]) { h16 = controlblock[1] | (controlblock[2] << 8); t16 = controlblock[3] | (controlblock[4] << 8); head.push_back(h16); tail.push_back(t16); } controlblock += 6; if (controlblock[0]) { if ((controlblock[1] | (controlblock[2] << 8)) != h16) { qDebug() << "Non matching control block head value" << path; } if ((controlblock[3] | (controlblock[4] << 8)) != t16) { qDebug() << "Non matching control block tail value" << path; } } controlblock += 6; } unsigned char *cb = controlblock; quint16 u1, u2, u3, u4, d1; quint32 ts, st; //, lt; QDateTime dt; QDate date; QTime time; for (int chk = 0; chk < 7; chk++) { ts = cb[0] << 24 | cb[1] << 16 | cb[2] << 8 | cb[3]; //ts-=epoch; dt = QDateTime::fromTime_t(ts); date = dt.date(); time = dt.time(); qDebug() << "New Sparse Chunk" << chk << dt << hex << ts; cb += 4; quint8 sum = 0; for (int i = 0; i < 0x268; i++) { sum += cb[i]; } if (cb[0x268] == sum) { qDebug() << "Checksum bad for block" << chk << path; } cb += 0x26a; } unsigned char *endcard = (unsigned char *)block.data() + dataends; bool done = false; //qint64 ti; int cnt = 0; do { ts = cb[0] << 24 | cb[1] << 16 | cb[2] << 8 | cb[3]; //lt = st = ts; //ti = qint64(ts) * 1000L; dt = QDateTime::fromTime_t(ts); date = dt.date(); time = dt.time(); qDebug() << "Details New Data Chunk" << cnt << dt << hex << ts; cb += 4; do { if (cb[0] == 0xfe) { // not sure what this means cb++; } u1 = cb[0] << 8 | cb[1]; // expecting 0xCXXX if (u1 == 0xffff) { // adjust timestamp code cb += 2; u1 = cb[0]; cb++; if (cb[0] == 0xfe) { u1 = cb[0] << 8 | cb[1]; // fe 0a, followed by timestamp cb += 2; break; // start on the next timestamp } } else { if ((cb[0] & 0xc0) == 0xc0) { cb += 2; u1 &= 0x0fff; // time delta?? //lt = ts; ts = st + (u1 * 60); //ti = qint64(ts) * 1000L; d1 = cb[0] << 8 | cb[1]; u2 = cb[2] << 8 | cb[3]; u3 = cb[4] << 8 | cb[5]; u4 = cb[6] << 8 | cb[7]; if ((d1 != 0xf302) || (u2 != 0xf097) || (u3 != 0xf2ff) || (u4 != 0xf281)) { qDebug() << "Lost details sync reading M-Series file" << path; return false; } cb += 8; } else { cb++; } dt = QDateTime::fromTime_t(ts); qDebug() << "Details Data Chunk" << cnt++ << dt; do { d1 = cb[0] << 8 | cb[1]; cb += 2; if (d1 == 0x7f0a) { // end of entire block done = true; break; } if ((d1 & 0xb000) == 0xb000) { qDebug() << "Duration" << (d1 & 0x7ff); break; // end of section } // process binary data.. // 64 c0 } while (cb < endcard); } } while (cb < endcard && !done); } while (cb < endcard && !done); // done = false; //bool first=true; //quint8 exch; cnt = 0; do { u1 = cb[0] << 8 | cb[1]; if (u1 != 0xfe0b) { // done = true; break; } cb += 2; st = ts = cb[0] << 24 | cb[1] << 16 | cb[2] << 8 | cb[3]; dt = QDateTime::fromTime_t(ts); date = dt.date(); time = dt.time(); //qDebug() << "Summary Data Chunk" << cnt << dt << hex << ts; cb += 4; while (cb < endcard) { if (((cb[0] & 0xc0) != 0xc0) || ((cb[0] & 0xf0) == 0xf0)) { // what is this for?? //exch = cb[0]; cb++; } u1 = (cb[0] << 8 | cb[1]) & 0x7ff; // time delta u2 = (cb[2] << 8 | cb[3]) & 0x7ff; // 0xBX XX?? ts = st + u1 * 60; dt = QDateTime::fromTime_t(ts); //qDebug() << "Summary Sub Chunk" << dt << u1 << u2 << hex << ts; cb += 4; if (cb[0] == 0xff) { break; } } cb++; // ff; // 05905: "22 48 00 00 04 01 01 5C 9E 30 00 F0 00 01 73 00 00 00 F2 Sat Jul 9 2011 10:44:25" // 05905: "20 58 00 00 00 00 00 32 69 88 00 70 00 01 73 00 00 00 AF Sun Jul 10 2011 05:09:21" // 05906: "22 00 00 00 0B 00 01 4E 79 F8 02 70 00 01 73 00 00 00 56 Sun Jul 10 2011 10:27:05" // 05907: "21 4C 00 00 11 00 01 5C 95 F8 01 F0 00 01 73 00 00 00 54 Mon Jul 11 2011 10:59:42" // 05908: "20 A8 00 00 02 00 01 4E 7D 88 00 F0 00 01 73 00 00 00 90 Tue Jul 12 2011 03:44:38" // 05909: "21 94 00 00 34 01 01 6A 96 D8 01 70 00 01 73 00 00 00 FC Tue Jul 12 2011 10:30:49" // 05910: "21 84 00 00 19 01 01 6A A2 30 00 F0 00 01 73 00 00 00 3E Wed Jul 13 2011 10:30:14" // 05911: "22 38 00 00 3F 01 01 86 B2 A0 00 F1 00 01 73 00 00 00 F4 Thu Jul 14 2011 10:01:50" // 05912: "21 68 00 00 36 01 01 5C 91 F8 02 70 00 01 73 00 00 00 BF Fri Jul 15 2011 10:46:33" // 05913: "22 6C 0E 00 A1 01 01 78 AB 10 00 F0 00 01 73 00 00 00 9A Sat Jul 16 2011 10:44:56" // 0x04 Vibratory Snore cnt++; QString a; for (int i = 0; i < 0x13; i++) { a += QString().sprintf("%02X ", cb[i]); } a += " " + date.toString() + " " + time.toString(); qDebug() << a; cb += 0x13; } while (cb < endcard && !done); //graph data //starts with timestamp.. or time delta if high bit is set. // validFlagOne = 3, // headPtrOne = 4, // tailPtrOne = 6, // cdbChecksumOne = 8, // validFlagTwo = 9 // headPtrTwo = 10, // tailPtrTwo = 12, // cdbChecksumTwo = 14, // const char * datablock=block.data()+data_offset; // quint8 basicCompliance=datablock[1]; // quint8 fosq=datablock[2]; // quint8 smartAutoCPAPProfile=datablock[3]; // quint8 smartAutoCPAPTrend=datablock[4]; // quint8 ventProfile=datablock[6]; // quint8 ventCompliance1=datablock[7]; // quint8 sleepProfile1=datablock[8]; // quint8 sleepTrend1=datablock[9]; // quint8 sleepProfile2=datablock[10]; // quint8 sleepTrend2=datablock[11]; // quint8 ventProfile2=datablock[12]; // quint8 ventCompliance2=datablock[13]; // quint8 sleepProfile3=datablock[14]; // quint8 sleepTrend3=datablock[15]; // 0xa6: 01 00 b2 7f ff 31 // 0xac: 01 00 b2 7f ff 31 // 0xb2: ??? block... ? // 0xb2: 00 00 // 0xb4: 01 36 a3 36 a2 b2 // the last bytes of all these are 8 bit additive checksums. // 0xba: 01 36 a3 36 a2 b2 // 0xc0: 01 00 26 00 07 2e // 0xc6: 01 00 26 00 07 2e // 0xcc: 01 52 5a 58 e6 eb // 0xd2: 01 52 5a 58 e6 eb // repeat 8 times // 0xd8: 4e 1a 4a fe // 268 bytes // 1 byte checksum // starting at 0xD8, with timestamp? // 8 blocks of 0x26e in size // idx 0x159 = // basicCompliance = 1, // fosq = 2, // sleepProfile = 8, // sleepProfile2 = 10, // sleepProfile3 = 14, // sleepTrend = 9, // sleepTrend2 = 11, // sleepTrend3 = 15, // smartAutoCPAPProfile = 3, // smartAutoCPAPTrend = 4, // ventCompliance2 = 13, // ventilatorCompliance = 7, // ventilatorProfile = 6, // ventProfile2 = 12 // Invalid = 0xff, // startChar = 0xfe, // stopChar = 0x7f //Machine *mach=CreateMachine(serial,profile); // 0xcount till next block (between f3 02... blocks) // 0xc0 00 // varies // 0xf3 02 f0 97 f2 ff f2 81 return 1; } bool mseries_initialized = false; void MSeriesLoader::Register() { if (mseries_initialized) { return; } qDebug() << "Registering RemStar M-Series Loader"; RegisterLoader(new MSeriesLoader()); //InitModelMap(); mseries_initialized = true; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/mseries_loader.h000066400000000000000000000047361417327530600244660ustar00rootroot00000000000000/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * * SleepLib RemStar M-Series Loader Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef MSERIES_LOADER_H #define MSERIES_LOADER_H #include "SleepLib/machine.h" #include "SleepLib/machine_loader.h" #include "SleepLib/profiles.h" //******************************************************************************************** /// IMPORTANT!!! //******************************************************************************************** // Please INCREMENT the following value when making changes to this loaders implementation. // const int mseries_data_version = 2; // //******************************************************************************************** /*! \class MSeries \brief RemStar M-Series customized machine object */ class MSeries: public CPAP { public: MSeries(Profile *, MachineID id = 0); virtual ~MSeries(); }; const int mseries_load_buffer_size = 1024 * 1024; const QString mseries_class_name = STR_MACH_MSeries; class MSeriesLoader : public MachineLoader { public: MSeriesLoader(); virtual ~MSeriesLoader(); //! \brief Detect if the given path contains a valid Folder structure virtual bool Detect(const QString & path) { Q_UNUSED(path); return false; } //! \brief Opens M-Series block device virtual int Open(const QString & file); //! \brief Returns the database version of this loader virtual int Version() { return mseries_data_version; } //! \brief Return the loaderName, in this case "MSeries" virtual const QString & loaderName() { return mseries_class_name; } //! \brief Create a new PRS1 machine record, indexed by Serial number. // Machine *CreateMachine(QString serial); virtual MachineInfo newInfo() { return MachineInfo(MT_CPAP, 0, mseries_class_name, QObject::tr("Respironics"), QString(), QString(), QString(), QObject::tr("M-Series"), QDateTime::currentDateTime(), mseries_data_version); } //! \brief Register this Module to the list of Loaders, so it knows to search for PRS1 data. static void Register(); protected: QHash MachList; quint32 epoch; }; #endif // MSERIES_LOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/prs1_loader.cpp000066400000000000000000003613251417327530600242370ustar00rootroot00000000000000/* SleepLib PRS1 Loader Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include #include #include "SleepLib/schema.h" #include "SleepLib/importcontext.h" #include "prs1_loader.h" #include "prs1_parser.h" #include "SleepLib/session.h" #include "SleepLib/calcs.h" #include "rawdata.h" // Disable this to cut excess debug messages #define DEBUG_SUMMARY //const int PRS1_MAGIC_NUMBER = 2; //const int PRS1_SUMMARY_FILE=1; //const int PRS1_EVENT_FILE=2; //const int PRS1_WAVEFORM_FILE=5; //******************************************************************************************** /// IMPORTANT!!! //******************************************************************************************** // Please INCREMENT the prs1_data_version in prs1_loader.h when making changes to this loader // that change loader behaviour or modify channels. //******************************************************************************************** QString ts(qint64 msecs) { // TODO: make this UTC so that tests don't vary by where they're run return QDateTime::fromMSecsSinceEpoch(msecs).toString(Qt::ISODate); } ChannelID PRS1_Mode = 0; ChannelID PRS1_TimedBreath = 0, PRS1_HumidMode = 0, PRS1_TubeTemp = 0; ChannelID PRS1_FlexLock = 0, PRS1_TubeLock = 0, PRS1_RampType = 0; ChannelID PRS1_BackupBreathMode = 0, PRS1_BackupBreathRate = 0, PRS1_BackupBreathTi = 0; ChannelID PRS1_AutoTrial = 0, PRS1_EZStart = 0, PRS1_RiseTime = 0, PRS1_RiseTimeLock = 0; ChannelID PRS1_PeakFlow = 0; ChannelID PRS1_VariableBreathing = 0; // TODO: UNCONFIRMED, but seems to match sample data QString PRS1Loader::PresReliefLabel() { return QObject::tr(""); } ChannelID PRS1Loader::PresReliefMode() { return PRS1_FlexMode; } ChannelID PRS1Loader::PresReliefLevel() { return PRS1_FlexLevel; } ChannelID PRS1Loader::CPAPModeChannel() { return PRS1_Mode; } ChannelID PRS1Loader::HumidifierConnected() { return PRS1_HumidStatus; } ChannelID PRS1Loader::HumidifierLevel() { return PRS1_HumidLevel; } struct PRS1TestedModel { QString model; int family; int familyVersion; const char* name; }; static const PRS1TestedModel s_PRS1TestedModels[] = { // This first set says "(Philips Respironics)" intead of "(System One)" on official reports. { "251P", 0, 2, "REMstar Plus (System One)" }, // (brick) { "450P", 0, 2, "REMstar Pro (System One)" }, { "450P", 0, 3, "REMstar Pro (System One)" }, { "451P", 0, 2, "REMstar Pro (System One)" }, { "451P", 0, 3, "REMstar Pro (System One)" }, { "452P", 0, 3, "REMstar Pro (System One)" }, { "550P", 0, 2, "REMstar Auto (System One)" }, { "550P", 0, 3, "REMstar Auto (System One)" }, { "551P", 0, 2, "REMstar Auto (System One)" }, { "650P", 0, 2, "BiPAP Pro (System One)" }, { "750P", 0, 2, "BiPAP Auto (System One)" }, { "261CA", 0, 4, "REMstar Plus (System One 60 Series)" }, // (brick) { "261P", 0, 4, "REMstar Plus (System One 60 Series)" }, // (brick) { "460P", 0, 4, "REMstar Pro (System One 60 Series)" }, { "460PBT", 0, 4, "REMstar Pro (System One 60 Series)" }, // evidently built-in bluetooth { "461P", 0, 4, "REMstar Pro (System One 60 Series)" }, { "462P", 0, 4, "REMstar Pro (System One 60 Series)" }, { "461CA", 0, 4, "REMstar Pro (System One 60 Series)" }, { "560P", 0, 4, "REMstar Auto (System One 60 Series)" }, { "560PBT", 0, 4, "REMstar Auto (System One 60 Series)" }, { "561P", 0, 4, "REMstar Auto (System One 60 Series)" }, { "562P", 0, 4, "REMstar Auto (System One 60 Series)" }, { "660P", 0, 4, "BiPAP Pro (System One 60 Series)" }, { "760P", 0, 4, "BiPAP Auto (System One 60 Series)" }, { "501V", 0, 5, "Dorma 500 Auto (System One 60 Series)" }, // (brick) { "200X110", 0, 6, "DreamStation CPAP" }, // (brick) { "400G110", 0, 6, "DreamStation Go" }, { "400X110", 0, 6, "DreamStation CPAP Pro" }, { "400X120", 0, 6, "DreamStation CPAP Pro" }, { "400X130", 0, 6, "DreamStation CPAP Pro" }, { "400X150", 0, 6, "DreamStation CPAP Pro" }, { "401X150", 0, 6, "DreamStation CPAP Pro with Auto-Trial" }, { "500X110", 0, 6, "DreamStation Auto CPAP" }, { "500X120", 0, 6, "DreamStation Auto CPAP" }, { "500X130", 0, 6, "DreamStation Auto CPAP" }, { "500X140", 0, 6, "DreamStation Auto CPAP with A-Flex" }, { "500X150", 0, 6, "DreamStation Auto CPAP" }, { "500X180", 0, 6, "DreamStation Auto CPAP" }, { "501X120", 0, 6, "DreamStation Auto CPAP with P-Flex" }, { "500G110", 0, 6, "DreamStation Go Auto" }, { "500G120", 0, 6, "DreamStation Go Auto" }, { "502G150", 0, 6, "DreamStation Go Auto" }, { "600X110", 0, 6, "DreamStation BiPAP Pro" }, { "600X150", 0, 6, "DreamStation BiPAP Pro" }, { "700X110", 0, 6, "DreamStation Auto BiPAP" }, { "700X120", 0, 6, "DreamStation Auto BiPAP" }, { "700X130", 0, 6, "DreamStation Auto BiPAP" }, { "700X150", 0, 6, "DreamStation Auto BiPAP" }, { "520X110C", 0, 6, "DreamStation 2 Auto CPAP Advanced" }, // based on bottom label, boot screen says "Advanced Auto CPAP" { "950P", 5, 0, "BiPAP AutoSV Advanced System One" }, { "951P", 5, 0, "BiPAP AutoSV Advanced System One" }, { "960P", 5, 1, "BiPAP autoSV Advanced (System One 60 Series)" }, { "961P", 5, 1, "BiPAP autoSV Advanced (System One 60 Series)" }, { "960T", 5, 2, "BiPAP autoSV Advanced 30 (System One 60 Series)" }, // omits "(System One 60 Series)" on official reports { "900X110", 5, 3, "DreamStation BiPAP autoSV" }, { "900X120", 5, 3, "DreamStation BiPAP autoSV" }, { "900X150", 5, 3, "DreamStation BiPAP autoSV" }, { "1061401", 3, 0, "BiPAP S/T (C Series)" }, { "1061T", 3, 3, "BiPAP S/T 30 (System One 60 Series)" }, { "1160P", 3, 3, "BiPAP AVAPS 30 (System One 60 Series)" }, { "1030X110", 3, 6, "DreamStation BiPAP S/T 30" }, { "1030X150", 3, 6, "DreamStation BiPAP S/T 30 with AAM" }, { "1130X110", 3, 6, "DreamStation BiPAP AVAPS 30" }, { "1131X150", 3, 6, "DreamStation BiPAP AVAPS 30 AE" }, { "1130X200", 3, 6, "DreamStation BiPAP AVAPS 30" }, { "", 0, 0, "" }, }; PRS1ModelInfo s_PRS1ModelInfo; PRS1ModelInfo::PRS1ModelInfo() { for (int i = 0; !s_PRS1TestedModels[i].model.isEmpty(); i++) { const PRS1TestedModel & model = s_PRS1TestedModels[i]; m_testedModels[model.family][model.familyVersion].append(model.model); m_modelNames[model.model] = model.name; } m_bricks = { "251P", "261CA", "261P", "200X110", "501V" }; } bool PRS1ModelInfo::IsSupported(int family, int familyVersion) const { if (m_testedModels.value(family).contains(familyVersion)) { return true; } return false; } bool PRS1ModelInfo::IsTested(const QString & model, int family, int familyVersion) const { if (m_testedModels.value(family).value(familyVersion).contains(model)) { return true; } // Some 500X150 C0/C1 folders have contained this bogus model number in their PROP.TXT file, // with the same serial number seen in the main PROP.TXT file that shows the real model number. if (model == "100X100") { #ifndef UNITTEST_MODE qDebug() << "Ignoring 100X100 for untested alert"; #endif return true; } return false; }; static bool getVersionFromProps(const QHash & props, int & family, int & familyVersion) { bool ok; family = props["Family"].toInt(&ok, 10); if (ok) { familyVersion = props["FamilyVersion"].toInt(&ok, 10); } return ok; } bool PRS1ModelInfo::IsSupported(const QHash & props) const { int family, familyVersion; bool ok = getVersionFromProps(props, family, familyVersion); if (ok) { ok = IsSupported(family, familyVersion); } return ok; } bool PRS1ModelInfo::IsTested(const QHash & props) const { int family, familyVersion; bool ok = getVersionFromProps(props, family, familyVersion); if (ok) { ok = IsTested(props["ModelNumber"], family, familyVersion); } return ok; }; bool PRS1ModelInfo::IsBrick(const QString & model) const { bool is_brick = false; if (m_modelNames.contains(model)) { is_brick = m_bricks.contains(model); } else if (model.length() > 0) { // If we haven't seen it before, assume any 2xx is a brick. is_brick = (model.at(0) == QChar('2')); } return is_brick; }; const char* PRS1ModelInfo::Name(const QString & model) const { const char* name; if (m_modelNames.contains(model)) { name = m_modelNames[model]; } else { name = "Unknown Model"; } return name; }; //******************************************************************************************** #include "SleepLib/thirdparty/botan_all.h" // Decoder for DreamStation 2 files, which encrypt the actual data after a header with the key. // The public read/seek/pos/etc. functions are all in terms of the decoded stream. class PRDS2File : public RawDataFile { public: PRDS2File(class QFile & file); virtual ~PRDS2File() {}; bool isValid() const; private: bool parseDS2Header(); int read16(); QByteArray readBytes(); bool initializeKey(); bool decryptData(); QByteArray m_iv; QByteArray e, j, k; QByteArray m_payload_key; QByteArray m_payload_tag; QBuffer m_payload; bool m_valid; protected: virtual qint64 readData(char *data, qint64 maxSize); virtual bool seek(qint64 pos); virtual qint64 pos() const; virtual qint64 size() const; QByteArray m_guid; static const int m_header_size = 0xCA; }; PRDS2File::PRDS2File(class QFile & file) : RawDataFile(file) { bool valid = parseDS2Header(); if (valid) { valid = initializeKey(); if (valid) { valid = decryptData(); } } m_valid = valid; if (m_valid) { seek(0); // initialize internal position } } bool PRDS2File::isValid() const { return m_valid; } bool PRDS2File::seek(qint64 pos) { if (!m_valid) { qWarning() << "seeking in unsupported DS2 file"; return false; } QIODevice::seek(pos); return m_payload.seek(pos); } qint64 PRDS2File::pos() const { if (!m_valid) { qWarning() << "querying pos in unsupported DS2 file"; return 0; } return m_payload.pos(); } qint64 PRDS2File::size() const { return m_payload.size(); } qint64 PRDS2File::readData(char *data, qint64 maxSize) { if (!m_valid) { qWarning() << "reading from unsupported DS2 file"; return -1; } return m_payload.read(data, maxSize); } bool PRDS2File::decryptData() { bool valid = false; try { QByteArray ciphertext = m_device.read(m_device.size() - m_device.pos()); const std::vector key(m_payload_key.begin(), m_payload_key.end()); const std::vector iv(m_iv.begin(), m_iv.end()); const std::vector tag(m_payload_tag.begin(), m_payload_tag.end()); Botan::secure_vector message(ciphertext.begin(), ciphertext.end()); message += tag; std::unique_ptr dec = Botan::Cipher_Mode::create("AES-256/GCM", Botan::DECRYPTION); dec->set_key(key); dec->start(iv); try { dec->finish(message); //qDebug() << QString::fromStdString(Botan::hex_encode(message.data(), message.size())); m_payload.setData((char*) message.data(), message.size()); m_payload.open(QIODevice::ReadOnly); valid = true; } catch (const Botan::Invalid_Authentication_Tag& e) { qWarning() << "DS2 payload doesn't match tag in" << name(); } } catch (exception& e) { // Make sure no Botan exceptions leak out and terminate the application. qWarning() << "*** DS2 unexpected exception decrypting" << name() << ":" << e.what(); } return valid; } bool PRDS2File::initializeKey() { bool valid = false; // TODO: Figure out how the non-default payload key is derived. static const unsigned char knownIV[] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1 }; static const unsigned char knownE[] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1 }; static const unsigned char knownJ[] = { 0x9a, 0x93, 0x15, 0xc8, 0xd4, 0x24, 0xef, 0x7f, 0xa6, 0xa7, 0x9f, 0xce, 0x82, 0xdd, 0x5d, 0xfe, 0xde, 0x8d, 0x4f, 0x9f, 0x15, 0x32, 0x4d, 0x2e, 0x6d, 0x1d, 0x6e, 0xc4, 0xcb, 0x5f, 0xce, 0x64 }; static const unsigned char knownK[] = { 0xc1, 0x70, 0x9e, 0xe9, 0xf0, 0xdf, 0x0a, 0xd4, 0x79, 0xd5, 0xaa, 0x07, 0x97, 0xd4, 0x5c, 0x33 }; if (m_iv == QByteArray((const char*) knownIV, sizeof(knownIV)) && e == QByteArray((const char*) knownE, sizeof(knownE))) { if (j == QByteArray((const char*) knownJ, sizeof(knownJ)) && k == QByteArray((const char*) knownK, sizeof(knownK))) { m_payload_key = e + e; // This doesn't seem to apply to non-default keys. valid = true; } else { qWarning() << "*** DS2 unexpected j,k for default key in" << name(); } } else { qWarning() << "DS2 unknown key for" << name(); } return valid; } bool PRDS2File::parseDS2Header() { int a = read16(); int b = read16(); int c = read16(); if (a != 0x0D || b != 1 || c != 1) { qWarning() << "DS2 unexpected first bytes =" << a << b << c; return false; } m_guid = readBytes(); if (m_guid.size() != 36) { qWarning() << "DS2 guid unexpected length" << m_guid.size(); } else { //qDebug() << "DS2 guid {" << m_guid << "}"; } m_iv = readBytes(); // 96-bit IV e = readBytes(); // 128 bits, somehow seeds key if (m_iv.size() != 12 || e.size() != 16) { qWarning() << "DS2 IV,e sizes =" << m_iv.size() << e.size(); } else { //qDebug() << "DS2 IV,e =" << m_iv.toHex() << e.toHex(); } int f = read16(); int g = read16(); if (f != 0 || g != 1) { qWarning() << "DS2 unexpected middle bytes =" << f << g; } QByteArray h = readBytes(); // same per d,e pair, varies per machine QByteArray i = readBytes(); // same per d,e pair, varies per machine if (h.size() != 32 || i.size() != 16) { qWarning() << "DS2 h,i sizes =" << h.size() << i.size(); } else { //qDebug() << "DS2 h,i =" << h.toHex() << i.toHex(); } j = readBytes(); // same per d,e pair, does NOT vary per machine; possibly key or IV k = readBytes(); // same per d,e pair, does NOT vary per machine; possibly key or IV if (j.size() != 32 || k.size() != 16) { qWarning() << "DS2 j,k sizes =" << j.size() << k.size(); } else { //qDebug() << "DS2 j,k =" << j.toHex() << k.toHex(); } m_payload_tag = readBytes(); if (m_payload_tag.size() != 16) { qWarning() << "DS2 payload tag size =" << m_payload_tag.size(); } else { //qDebug() << "DS2 payload tag =" << m_payload_tag.toHex(); } if (m_device.pos() != m_header_size) { qWarning() << "DS2 header size !=" << m_header_size; } return true; } int PRDS2File::read16() { unsigned char data[2]; int result; result = m_device.read((char*) data, sizeof(data)); // access the underlying data for the header if (result == sizeof(data)) { result = data[0] | (data[1] << 8); } else { result = 0; } return result; } QByteArray PRDS2File::readBytes() { int length = read16(); QByteArray result = m_device.read(length); // access the underlying data for the header if (result.size() < length) { result.clear(); } return result; } //******************************************************************************************** QMap s_PRS1Series = { { "System One 60 Series", ":/icons/prs1_60s.png" }, // needs to come before following substring { "System One", ":/icons/prs1.png" }, { "C Series", ":/icons/prs1vent.png" }, { "DreamStation 2", ":/icons/prds2.png" }, // needs to come before following substring { "DreamStation", ":/icons/dreamstation.png" }, }; PRS1Loader::PRS1Loader() { #ifndef UNITTEST_MODE // no QPixmap without a QGuiApplication for (auto & series : s_PRS1Series.keys()) { QString path = s_PRS1Series[series]; m_pixmap_paths[series] = path; m_pixmaps[series] = QPixmap(path); } #endif m_type = MT_CPAP; } PRS1Loader::~PRS1Loader() { } bool isdigit(QChar c) { if ((c >= '0') && (c <= '9')) { return true; } return false; } // Tests path to see if it has (what looks like) a valid PRS1 folder structure // This is used both to detect newly inserted media and to decide which loader // to use after the user selects a folder. // // TODO: Ideally there should be a way to handle the two scenarios slightly // differently. In the latter case, it should clean up the selection and // return the canonical path if it detects one, allowing us to remove the // notification about selecting the root of the card. That kind of cleanup // wouldn't be appropriate when scanning devices. bool PRS1Loader::Detect(const QString & selectedPath) { QString path = selectedPath; if (GetPSeriesPath(path).isEmpty()) { // Try up one level in case the user selected the P-Series folder within the SD card. path = QFileInfo(path).canonicalPath(); } QStringList machines = FindMachinesOnCard(path); return !machines.isEmpty(); } QString PRS1Loader::GetPSeriesPath(const QString & path) { QString outpath = ""; QDir root(path); QStringList dirs = root.entryList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Hidden | QDir::NoSymLinks); for (auto & dir : dirs) { // We've seen P-Series, P-SERIES, and p-series, so we need to search for the directory // in a way that won't break on a case-sensitive filesystem. if (dir.toUpper() == "P-SERIES") { outpath = path + QDir::separator() + dir; break; } } return outpath; } QStringList PRS1Loader::FindMachinesOnCard(const QString & cardPath) { QStringList machinePaths; QString pseriesPath = this->GetPSeriesPath(cardPath); QDir pseries(pseriesPath); // If it contains a P-Series folder, it's a PRS1 SD card if (!pseriesPath.isEmpty() && pseries.exists()) { pseries.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks); pseries.setSorting(QDir::Name); QFileInfoList plist = pseries.entryInfoList(); // Look for machine directories (containing a PROP.TXT or properties.txt) QFileInfoList propertyfiles; for (auto & pfi : plist) { if (pfi.isDir()) { QString machinePath = pfi.canonicalFilePath(); QDir machineDir(machinePath); QFileInfoList mlist = machineDir.entryInfoList(); for (auto & mfi : mlist) { if (QDir::match("PROP*.TXT", mfi.fileName())) { // Found a properties file, this is a machine folder propertyfiles.append(mfi); } if (QDir::match("PROP.BIN", mfi.fileName())) { // Found a DreamStation 2 properties file, this is a machine folder propertyfiles.append(mfi); } } } } // Sort machines from oldest to newest. std::sort(propertyfiles.begin(), propertyfiles.end(), [](const QFileInfo & a, const QFileInfo & b) { return a.lastModified() < b.lastModified(); }); for (auto & propertyfile : propertyfiles) { machinePaths.append(propertyfile.canonicalPath()); } } return machinePaths; } void parseModel(MachineInfo & info, const QString & modelnum) { info.modelnumber = modelnum; const char* name = s_PRS1ModelInfo.Name(modelnum); const char* series = nullptr; for (auto & s : s_PRS1Series.keys()) { if (QString(name).contains(s)) { series = s; break; } } if (series == nullptr) { if (modelnum != "100X100") { // Bogus model number seen in empty C0/Clear0 directories. qWarning() << "unknown series for" << name << modelnum; } series = "unknown"; } info.model = QObject::tr(name); info.series = series; } bool PRS1Loader::PeekProperties(const QString & filename, QHash & props) { const static QMap s_longFieldNames = { // CF? { "SN", "SerialNumber" }, { "MN", "ModelNumber" }, { "PT", "ProductType" }, { "DF", "DataFormat" }, { "DFV", "DataFormatVersion" }, { "F", "Family" }, { "FV", "FamilyVersion" }, { "SV", "SoftwareVersion" }, { "FD", "FirstDate" }, { "LD", "LastDate" }, // SID? // SK? // TS? // DC? { "BK", "BasicKey" }, { "DK", "DetailsKey" }, { "EK", "ErrorKey" }, { "FN", "PatientFolderNum" }, // most recent Pn directory { "PFN", "PatientFileNum" }, // number of files in the most recent Pn directory { "EFN", "EquipFileNum" }, // number of .004 files in the E directory { "DFN", "DFileNum" }, // number of .003 files in the D directory { "VC", "ValidCheck" }, }; QFile f(filename); if (!f.open(QFile::ReadOnly)) { return false; } RawDataFile* src; if (QFileInfo(f).suffix().toUpper() == "BIN") { // If it's a DS2 file, insert the DS2 wrapper to decode the chunk stream. PRDS2File* ds2 = new PRDS2File(f); if (!ds2->isValid()) { qWarning() << filename << "unable to decrypt"; delete ds2; return false; } src = ds2; } else { // Otherwise just use the file as input. src = new RawDataFile(f); } { QTextStream in(src); // Scope this here so that it's torn down before we delete src below. do { QString line = in.readLine(); QStringList pair = line.split("="); if (pair.size() != 2) { qWarning() << src->name() << "malformed line:" << line; QHashIterator i(props); while (i.hasNext()) { i.next(); qDebug() << i.key() << ":" << i.value(); } break; } if (s_longFieldNames.contains(pair[0])) { pair[0] = s_longFieldNames[pair[0]]; } if (pair[0] == "Family") { if (pair[1] == "xPAP") { pair[1] = "0"; } else if (pair[1] == "Ventilator") { pair[1] = "3"; } } props[pair[0]] = pair[1]; } while (!in.atEnd()); } delete src; return props.size() > 0; } bool PRS1Loader::PeekProperties(MachineInfo & info, const QString & filename) { QHash props; if (!PeekProperties(filename, props)) { return false; } QString modelnum; for (auto & key : props.keys()) { bool skip = false; if (key == "ModelNumber") { modelnum = props[key]; skip = true; } if (key == "SerialNumber") { info.serial = props[key]; skip = true; } if (key == "ProductType") { bool ok; props[key].toInt(&ok, 16); if (!ok) qWarning() << "ProductType" << props[key]; skip = true; } if (skip) continue; info.properties[key] = props[key]; }; if (!modelnum.isEmpty()) { parseModel(info, modelnum); } else { qWarning() << "missing model number" << filename; } return true; } MachineInfo PRS1Loader::PeekInfo(const QString & path) { QStringList machines = FindMachinesOnCard(path); if (machines.isEmpty()) { return MachineInfo(); } // Present information about the newest machine on the card. QString newpath = machines.last(); MachineInfo info = newInfo(); if (!PeekProperties(info, newpath+"/properties.txt")) { if (!PeekProperties(info, newpath+"/PROP.TXT")) { // Detect (unsupported) DreamStation 2 QString filepath(newpath + "/PROP.BIN"); if (!PeekProperties(info, filepath)) { qWarning() << "No properties file found in" << newpath; } } } return info; } int PRS1Loader::Open(const QString & selectedPath) { QString path = selectedPath; if (GetPSeriesPath(path).isEmpty()) { // Try up one level in case the user selected the P-Series folder within the SD card. path = QFileInfo(path).canonicalPath(); } QStringList machines = FindMachinesOnCard(path); // Return an error if no machines were found. if (machines.isEmpty()) { qDebug() << "No PRS1 machines found at" << path; return -1; } // Import each machine, from oldest to newest. // TODO: Loaders should return the set of machines during detection, so that Open() will // open a unique device, instead of surprising the user. int c = 0; bool failures = false; for (auto & machinePath : machines) { if (m_ctx == nullptr) { qWarning() << "PRS1Loader::Open() called without a valid m_ctx object present"; return 0; } int imported = OpenMachine(machinePath); if (imported >= 0) { // don't let errors < 0 suppress subsequent successes c += imported; } else { failures = true; } m_ctx->FlushUnexpectedMessages(); } if (c == 0 && failures) { // report an error when there were failures and no successess c = -1; } return c; } int PRS1Loader::OpenMachine(const QString & path) { Q_ASSERT(m_ctx); qDebug() << "Opening PRS1 " << path; QDir dir(path); if (!dir.exists() || (!dir.isReadable())) { return 0; } m_abort = false; emit updateMessage(QObject::tr("Getting Ready...")); QCoreApplication::processEvents(); emit setProgressValue(0); QStringList paths; QString propertyfile; int sessionid_base; sessionid_base = FindSessionDirsAndProperties(path, paths, propertyfile); bool supported = CreateMachineFromProperties(propertyfile); if (!supported) { // Device is unsupported. return -1; } emit updateMessage(QObject::tr("Backing Up Files...")); QCoreApplication::processEvents(); QString backupPath = context()->GetBackupPath() + path.section("/", -2); if (QDir::cleanPath(path).compare(QDir::cleanPath(backupPath)) != 0) { copyPath(path, backupPath); } emit updateMessage(QObject::tr("Scanning Files...")); QCoreApplication::processEvents(); // Walk through the files and create an import task for each logical session. ScanFiles(paths, sessionid_base); int tasks = countTasks(); emit updateMessage(QObject::tr("Importing Sessions...")); QCoreApplication::processEvents(); runTasks(AppSetting->multithreading()); return tasks; } int PRS1Loader::FindSessionDirsAndProperties(const QString & path, QStringList & paths, QString & propertyfile) { QDir dir(path); dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); QFileInfoList flist = dir.entryInfoList(); QString filename; int sessionid_base = 10; for (int i = 0; i < flist.size(); i++) { QFileInfo fi = flist.at(i); filename = fi.fileName(); if (fi.isDir()) { if ((filename[0].toLower() == 'p') && (isdigit(filename[1]))) { // p0, p1, p2.. etc.. folders contain the session data paths.push_back(fi.canonicalFilePath()); } else if (filename.toLower() == "e") { // Error files.. // Reminder: I have been given some info about these. should check it over. } } else if (filename.compare("properties.txt",Qt::CaseInsensitive) == 0) { propertyfile = fi.canonicalFilePath(); } else if (filename.compare("PROP.TXT",Qt::CaseInsensitive) == 0) { sessionid_base = 16; propertyfile = fi.canonicalFilePath(); } else if (filename.compare("PROP.BIN", Qt::CaseInsensitive) == 0) { sessionid_base = 16; propertyfile = fi.canonicalFilePath(); } } return sessionid_base; } bool PRS1Loader::CreateMachineFromProperties(QString propertyfile) { MachineInfo info = newInfo(); QHash props; if (!PeekProperties(propertyfile, props) || !s_PRS1ModelInfo.IsSupported(props)) { if (props.contains("ModelNumber")) { int family, familyVersion; getVersionFromProps(props, family, familyVersion); QString model_number = props["ModelNumber"]; qWarning().noquote() << "Model" << model_number << QString("(F%1V%2)").arg(family).arg(familyVersion) << "unsupported."; info.modelnumber = QObject::tr("model %1").arg(model_number); } else if (propertyfile.endsWith("PROP.BIN")) { // TODO: If we end up releasing without support for non-default keys, // add a loaderSpecificAlert(QString & message, bool deferred=false) signal // and use that instead of telling the user that the DS2 is entirely unsupported. qWarning() << "DreamStation 2 not using default keys:" << propertyfile; info.modelnumber = QObject::tr("DreamStation 2"); } else { qWarning() << "Unable to identify model or series!"; info.modelnumber = QObject::tr("unknown model"); } emit deviceIsUnsupported(info); return false; } // Have a peek first to get the model number. PeekProperties(info, propertyfile); if (s_PRS1ModelInfo.IsBrick(info.modelnumber)) { emit deviceReportsUsageOnly(info); } // Which is needed to get the right machine record.. m_ctx->CreateMachineFromInfo(info); if (!s_PRS1ModelInfo.IsTested(props)) { qDebug() << info.modelnumber << "untested"; emit deviceIsUntested(info); } return true; } static QString relativePath(const QString & inpath) { QStringList pathlist = QDir::toNativeSeparators(inpath).split(QDir::separator(), QString::SkipEmptyParts); QString relative = pathlist.mid(pathlist.size()-3).join(QDir::separator()); return relative; } static bool chunksIdentical(const PRS1DataChunk* a, const PRS1DataChunk* b) { return (a->hash() == b->hash()); } static QString chunkComparison(const PRS1DataChunk* a, const PRS1DataChunk* b) { return QString("Session %1 in %2 @ %3 %4 %5 @ %6, skipping") .arg(a->sessionid) .arg(relativePath(a->m_path)).arg(a->m_filepos) .arg(chunksIdentical(a, b) ? "is identical to" : "differs from") .arg(relativePath(b->m_path)).arg(b->m_filepos); } void PRS1Loader::ScanFiles(const QStringList & paths, int sessionid_base) { Q_ASSERT(m_ctx); SessionID sid; long ext; QDir dir; dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); int size = paths.size(); sesstasks.clear(); new_sessions.clear(); // this hash is used by OpenFile PRS1Import * task = nullptr; // Note, I have observed p0/p1/etc folders containing duplicates session files (in Robin Sanders data.) QDateTime datetime; qint64 ignoreBefore = m_ctx->IgnoreSessionsOlderThan().toMSecsSinceEpoch()/1000; bool ignoreOldSessions = m_ctx->ShouldIgnoreOldSessions(); QSet skipped; // for each p0/p1/p2/etc... folder for (int p=0; p < size; ++p) { dir.setPath(paths.at(p)); if (!dir.exists() || !dir.isReadable()) { qWarning() << dir.canonicalPath() << "can't read directory"; continue; } QFileInfoList flist = dir.entryInfoList(); // Scan for individual session files for (int i = 0; i < flist.size(); i++) { #ifndef UNITTEST_MODE QCoreApplication::processEvents(); #endif if (isAborted()) { qDebug() << "received abort signal"; break; } QFileInfo fi = flist.at(i); QString path = fi.canonicalFilePath(); bool ok; if (fi.fileName() == ".DS_Store") { continue; } QString ext_s = fi.fileName().section(".", -1); if (ext_s.toUpper().startsWith("B")) { // .B01, .B02, etc. ext_s = ext_s.mid(1); } ext = ext_s.toInt(&ok); if (!ok) { // not a numerical extension qInfo() << path << "unexpected filename"; continue; } QString session_s = fi.fileName().section(".", 0, -2); sid = session_s.toInt(&ok, sessionid_base); if (!ok) { // not a numerical session ID qInfo() << path << "unexpected filename"; continue; } // TODO: BUG: This isn't right, since files can have multiple session // chunks, which might not correspond to the filename. But before we can // fix this we need to come up with a reasonably fast way to filter previously // imported files without re-reading all of them. if (context()->SessionExists(sid)) { // Skip already imported session // TODO: Consider reinstating this debug statement if/when we scan only new/changed files. //qDebug() << path << "session already exists, skipping" << sid; continue; } if ((ext == 5) || (ext == 6)) { if (skipped.contains(sid)) { // We don't know the timestamp until the file is parsed, which we only do for // waveform data at import (after scanning) since it's so large. If we relied // solely on the chunks' timestamps at that point, we'd get half of an otherwise // skipped session (the half after midnight). // // So we skip the entire file here based on the session's other data. continue; } // Waveform files aren't grouped... so we just want to add the filename for later QHash::iterator it = sesstasks.find(sid); if (it != sesstasks.end()) { task = it.value(); } else { // Should probably check if session already imported has this data missing.. // Create the group if we see it first.. task = new PRS1Import(this, sid, sessionid_base); sesstasks[sid] = task; queTask(task); } if (ext == 5) { // Occasionally waveforms in a session can be split into multiple files. // // This seems to happen when the machine begins writing the waveform file // before realizing that it will hit its 500-file-per-directory limit // for the remaining session files, at which point it appears to write // the rest of the waveform data along with the summary and event files // in the next directory. // // All samples exhibiting this behavior are DreamStations. task->m_wavefiles.append(fi.canonicalFilePath()); } else if (ext == 6) { // Oximetry data can also be split into multiple files, see waveform // comment above. task->m_oxifiles.append(fi.canonicalFilePath()); } continue; } // Parse the data chunks and read the files.. QList Chunks = ParseFile(fi.canonicalFilePath()); for (int i=0; i < Chunks.size(); ++i) { if (isAborted()) { qDebug() << "received abort signal 2"; break; } PRS1DataChunk * chunk = Chunks.at(i); SessionID chunk_sid = chunk->sessionid; if (i == 0 && chunk_sid != sid) { // log session ID mismatches // This appears to be benign, probably when a card is out of the machine one night and // then inserted in the morning. It writes out all of the still-in-memory summaries and // events up through the last night (and no waveform data). // // This differs from the first time a card is inserted, because in that case the filename // *is* equal to the first session contained within it, and then filenames for the // remaining sessions contained in that file are skipped. // // Because the card was present and previous sessions were written with their filenames, // the first available filename isn't the first session contained in the file. //qDebug() << fi.canonicalFilePath() << "first session is" << chunk_sid << "instead of" << sid; } if (context()->SessionExists(chunk_sid)) { qDebug() << path << "session already imported, skipping" << sid << chunk_sid; delete chunk; continue; } if (ignoreOldSessions && chunk->timestamp < ignoreBefore) { qDebug().noquote() << relativePath(path) << "skipping session" << chunk_sid << ":" << QDateTime::fromMSecsSinceEpoch(chunk->timestamp*1000).toString() << "older than" << QDateTime::fromMSecsSinceEpoch(ignoreBefore*1000).toString(); skipped += chunk_sid; delete chunk; continue; } task = nullptr; QHash::iterator it = sesstasks.find(chunk_sid); if (it != sesstasks.end()) { task = it.value(); } else { task = new PRS1Import(this, chunk_sid, sessionid_base); sesstasks[chunk_sid] = task; // save a loop an que this now queTask(task); } switch (ext) { case 0: if (task->compliance) { if (chunksIdentical(chunk, task->summary)) { // Never seen identical compliance chunks, so keep logging this for now. qDebug() << chunkComparison(chunk, task->summary); } else { qWarning() << chunkComparison(chunk, task->summary); } delete chunk; continue; // (skipping to avoid duplicates) } task->compliance = chunk; break; case 1: if (task->summary) { if (chunksIdentical(chunk, task->summary)) { // This seems to be benign. It happens most often when a single file contains // a bunch of chunks and subsequent files each contain a single chunk that was // already covered by the first file. It also sometimes happens with entirely // duplicate files between e.g. a P1 and P0 directory. // // It's common enough that we don't emit a message about it by default. //qDebug() << chunkComparison(chunk, task->summary); } else { // Warn about any non-identical duplicate session IDs. // // This seems to happen with F5V1 slice 8, which is the only slice in a session, // and which doesn't update the session ID, so the following slice 7 session // (which can be hours later) has the same session ID. Neither affects import. qWarning() << chunkComparison(chunk, task->summary); } delete chunk; continue; } task->summary = chunk; break; case 2: if (task->m_event_chunks.count() > 0) { PRS1DataChunk* previous; if (chunk->family == 3 && chunk->familyVersion <= 3) { // F3V0 and F3V3 events are formatted as waveforms, with one chunk per mask-on slice, // and thus multiple chunks per session. previous = task->m_event_chunks[chunk->timestamp]; if (previous != nullptr) { // Skip any chunks with identical timestamps. qWarning() << chunkComparison(chunk, previous); delete chunk; continue; } // fall through to add the new chunk } else { // Nothing else should have multiple event chunks per session. previous = task->m_event_chunks.first(); if (chunksIdentical(chunk, previous)) { // See comment above regarding identical summary chunks. //qDebug() << chunkComparison(chunk, previous); } else { qWarning() << chunkComparison(chunk, previous); } delete chunk; continue; } } task->m_event_chunks[chunk->timestamp] = chunk; break; default: qWarning() << path << "unexpected file"; break; } } } if (isAborted()) { qDebug() << "received abort signal 3"; break; } } } // The set of PRS1 "on-demand" channels that only get created on import if the session // contains events of that type. Any channels not on this list always get created if // they're reported/supported by the parser. static const QVector PRS1OnDemandChannels = { PRS1TimedBreathEvent::TYPE, PRS1PressurePulseEvent::TYPE, // Pressure initialized on-demand for F0 due to the possibility of bilevel vs. single pressure. PRS1PressureSetEvent::TYPE, PRS1IPAPSetEvent::TYPE, PRS1EPAPSetEvent::TYPE, // Pressure average initialized on-demand for F0 due to the different semantics of bilevel vs. single pressure. PRS1PressureAverageEvent::TYPE, PRS1FlexPressureAverageEvent::TYPE, }; // The set of "non-slice" channels are independent of mask-on slices, i.e. they // are continuously reported and charted regardless of whether the mask is on. static const QSet PRS1NonSliceChannels = { PRS1PressureSetEvent::TYPE, PRS1IPAPSetEvent::TYPE, PRS1EPAPSetEvent::TYPE, PRS1SnoresAtPressureEvent::TYPE, }; // The channel ID (referenced by pointer because their values aren't initialized // prior to runtime) to which a given PRS1 event should be added. Events with // no channel IDs are silently dropped, and events with more than one channel ID // must be handled specially. static const QHash> PRS1ImportChannelMap = { { PRS1ClearAirwayEvent::TYPE, { &CPAP_ClearAirway } }, { PRS1ObstructiveApneaEvent::TYPE, { &CPAP_Obstructive } }, { PRS1HypopneaEvent::TYPE, { &CPAP_Hypopnea } }, { PRS1FlowLimitationEvent::TYPE, { &CPAP_FlowLimit } }, { PRS1SnoreEvent::TYPE, { &CPAP_Snore, &CPAP_VSnore2 } }, // VSnore2 is calculated from snore count, used to flag nonzero intervals on overview { PRS1VibratorySnoreEvent::TYPE, { &CPAP_VSnore } }, { PRS1RERAEvent::TYPE, { &CPAP_RERA } }, { PRS1PeriodicBreathingEvent::TYPE, { &CPAP_PB } }, { PRS1LargeLeakEvent::TYPE, { &CPAP_LargeLeak } }, { PRS1TotalLeakEvent::TYPE, { &CPAP_LeakTotal } }, { PRS1LeakEvent::TYPE, { &CPAP_Leak } }, { PRS1RespiratoryRateEvent::TYPE, { &CPAP_RespRate } }, { PRS1TidalVolumeEvent::TYPE, { &CPAP_TidalVolume } }, { PRS1MinuteVentilationEvent::TYPE, { &CPAP_MinuteVent } }, { PRS1PatientTriggeredBreathsEvent::TYPE, { &CPAP_PTB } }, { PRS1TimedBreathEvent::TYPE, { &PRS1_TimedBreath } }, { PRS1FlowRateEvent::TYPE, { &PRS1_PeakFlow } }, // Only reported by F3V0 and F3V3 // TODO: should this stat be calculated from flow waveforms on other models? { PRS1PressureSetEvent::TYPE, { &CPAP_PressureSet } }, { PRS1IPAPSetEvent::TYPE, { &CPAP_IPAPSet, &CPAP_PS } }, // PS is calculated from IPAPset and EPAPset when both are supported (F0) TODO: Should this be a separate channel since it's not a 2-minute average? { PRS1EPAPSetEvent::TYPE, { &CPAP_EPAPSet } }, // EPAPset is supported on F5 without any corresponding IPAPset, so it shouldn't always create a PS channel { PRS1PressureAverageEvent::TYPE, { &CPAP_Pressure } }, // This is the time-weighted average pressure in bilevel modes. { PRS1FlexPressureAverageEvent::TYPE, { &CPAP_EPAP } }, // This is effectively EPAP due to Flex reduced pressure in single-pressure modes. { PRS1IPAPAverageEvent::TYPE, { &CPAP_IPAP } }, { PRS1EPAPAverageEvent::TYPE, { &CPAP_EPAP, &CPAP_PS } }, // PS is calculated from IPAP and EPAP averages (F3 and F5) { PRS1IPAPLowEvent::TYPE, { &CPAP_IPAPLo } }, { PRS1IPAPHighEvent::TYPE, { &CPAP_IPAPHi } }, { PRS1Test1Event::TYPE, { &CPAP_Test1 } }, // ??? F3V6 { PRS1Test2Event::TYPE, { &CPAP_Test2 } }, // ??? F3V6 { PRS1PressurePulseEvent::TYPE, { &CPAP_PressurePulse } }, { PRS1ApneaAlarmEvent::TYPE, { /* Not imported */ } }, { PRS1SnoresAtPressureEvent::TYPE, { /* Not imported */ } }, { PRS1AutoPressureSetEvent::TYPE, { /* Not imported */ } }, { PRS1VariableBreathingEvent::TYPE, { &PRS1_VariableBreathing } }, // UNCONFIRMED { PRS1HypopneaCount::TYPE, { &CPAP_Hypopnea } }, // F3V3 only, generates individual events on import { PRS1ObstructiveApneaCount::TYPE, { &CPAP_Obstructive } }, // F3V3 only, generates individual events on import { PRS1ClearAirwayCount::TYPE, { &CPAP_ClearAirway } }, // F3V3 only, generates individual events on import }; //******************************************************************************************** PRS1Import::~PRS1Import() { delete compliance; delete summary; for (auto & e : m_event_chunks.values()) { delete e; } for (int i=0;i < waveforms.size(); ++i) { delete waveforms.at(i); } for (auto & c : oximetry) { delete c; } } void PRS1Import::CreateEventChannels(const PRS1DataChunk* chunk) { const QVector & supported = GetSupportedEvents(chunk); // Generate the list of channels created by non-slice events for this machine. // We can't just use the full list of non-slice events, since on some machines // PS is generated by slice events (EPAP/IPAP average). // TODO: convert supported to QSet and clean this up. QSet supportedNonSliceEvents = QSet::fromList(QList::fromVector(supported)); supportedNonSliceEvents.intersect(PRS1NonSliceChannels); QSet supportedNonSliceChannels; for (auto & e : supportedNonSliceEvents) { for (auto & pChannelID : PRS1ImportChannelMap[e]) { supportedNonSliceChannels += *pChannelID; } } // Clear channels to prepare for a new slice, except for channels created by // non-slice events. for (auto & c : m_importChannels.keys()) { if (supportedNonSliceChannels.contains(c) == false) { m_importChannels.remove(c); } } // Create all supported channels (except for on-demand ones that only get created if an event appears) for (auto & e : supported) { if (!PRS1OnDemandChannels.contains(e)) { for (auto & pChannelID : PRS1ImportChannelMap[e]) { GetImportChannel(*pChannelID); } } } } EventList* PRS1Import::GetImportChannel(ChannelID channel) { if (!channel) { qCritical() << this->sessionid << "channel in import table has not been added to schema!"; } EventList* C = m_importChannels[channel]; if (C == nullptr) { C = session->AddEventList(channel, EVL_Event); Q_ASSERT(C); // Once upon a time AddEventList could return nullptr, but not any more. m_importChannels[channel] = C; } return C; } void PRS1Import::AddEvent(ChannelID channel, qint64 t, float value, float gain) { EventList* C = GetImportChannel(channel); Q_ASSERT(C); if (C->count() == 0) { // Initialize the gain (here, since required channels are created with default gain). C->setGain(gain); } else { // Any change in gain is a programming error. if (gain != C->gain()) { qWarning() << "gain mismatch for channel" << channel << "at" << ts(t); } } // Add the event C->AddEvent(t, value, gain); } bool PRS1Import::UpdateCurrentSlice(PRS1DataChunk* chunk, qint64 t) { bool updated = false; if (!m_currentSliceInitialized) { m_currentSliceInitialized = true; m_currentSlice = m_slices.constBegin(); m_lastIntervalEvents.clear(); // there was no previous slice, so there are no pending end-of-slice events m_lastIntervalEnd = 0; updated = true; } // Update the slice iterator to point to the mask-on slice encompassing time t. while ((*m_currentSlice).status != MaskOn || t > (*m_currentSlice).end) { m_currentSlice++; updated = true; if (m_currentSlice == m_slices.constEnd()) { qWarning() << sessionid << "Events after last mask-on slice?"; m_currentSlice--; break; } } if (updated) { // Write out any pending end-of-slice events. FinishSlice(); } if (updated && (*m_currentSlice).status == MaskOn) { // Set the interval start times based on the new slice's start time. m_statIntervalStart = 0; StartNewInterval((*m_currentSlice).start); // Create a new eventlist for this new slice, to allow for a gap in the data between slices. CreateEventChannels(chunk); } return updated; } void PRS1Import::FinishSlice() { qint64 t = m_lastIntervalEnd; // end of the slice (at least of its interval data) // If the most recently recorded interval stats aren't at the end of the slice, // import additional events marking the end of the data. if (t != m_prevIntervalStart) { // Make sure to use the same pressure used to import the original events, // otherwise calculated channels (such as PS or LEAK) will be wrong. EventDataType orig_pressure = m_currentPressure; m_currentPressure = m_intervalPressure; // Import duplicates of each event with the end-of-slice timestamp. for (auto & e : m_lastIntervalEvents) { ImportEvent(t, e); } // Restore the current pressure. m_currentPressure = orig_pressure; } m_lastIntervalEvents.clear(); } void PRS1Import::StartNewInterval(qint64 t) { if (t == m_prevIntervalStart) { qWarning() << sessionid << "Multiple zero-length intervals at end of slice?"; } m_prevIntervalStart = m_statIntervalStart; m_statIntervalStart = t; } bool PRS1Import::IsIntervalEvent(PRS1ParsedEvent* e) { bool intervalEvent = false; // Statistical timestamps are reported at the end of a (generally) 2-minute // interval, rather than the start time that OSCAR expects for its imported // events. (When a session or slice ends, there will be a shorter interval, // the previous statistics to the end of the session/slice.) switch (e->m_type) { case PRS1PressureAverageEvent::TYPE: case PRS1FlexPressureAverageEvent::TYPE: case PRS1IPAPAverageEvent::TYPE: case PRS1IPAPLowEvent::TYPE: case PRS1IPAPHighEvent::TYPE: case PRS1EPAPAverageEvent::TYPE: case PRS1TotalLeakEvent::TYPE: case PRS1LeakEvent::TYPE: case PRS1RespiratoryRateEvent::TYPE: case PRS1PatientTriggeredBreathsEvent::TYPE: case PRS1MinuteVentilationEvent::TYPE: case PRS1TidalVolumeEvent::TYPE: case PRS1FlowRateEvent::TYPE: case PRS1Test1Event::TYPE: case PRS1Test2Event::TYPE: case PRS1SnoreEvent::TYPE: case PRS1HypopneaCount::TYPE: case PRS1ClearAirwayCount::TYPE: case PRS1ObstructiveApneaCount::TYPE: intervalEvent = true; break; default: break; } return intervalEvent; } bool PRS1Import::ImportEventChunk(PRS1DataChunk* event) { m_currentPressure=0; const QVector & supported = GetSupportedEvents(event); // Calculate PS from IPAP/EPAP set events only when both are supported. This includes F0, but excludes // F5, which only reports EPAP set events, but both IPAP/EPAP average, from which PS will be calculated. m_calcPSfromSet = supported.contains(PRS1IPAPSetEvent::TYPE) && supported.contains(PRS1EPAPSetEvent::TYPE); qint64 t = qint64(event->timestamp) * 1000L; if (session->first() == 0) { qWarning() << sessionid << "Start time not set by summary?"; } else if (t < session->first()) { qWarning() << sessionid << "Events start before summary?"; } bool ok; ok = event->ParseEvents(); // Set up the (possibly initial) slice based on the chunk's starting timestamp. UpdateCurrentSlice(event, t); for (int i=0; i < event->m_parsedData.count(); i++) { PRS1ParsedEvent* e = event->m_parsedData.at(i); t = qint64(event->timestamp + e->m_start) * 1000L; // Skip unknown events with no timestamp if (e->m_type == PRS1UnknownDataEvent::TYPE) { continue; } // Skip zero-length PB or LL or VB events if ((e->m_type == PRS1PeriodicBreathingEvent::TYPE || e->m_type == PRS1LargeLeakEvent::TYPE || e->m_type == PRS1VariableBreathingEvent::TYPE) && (e->m_duration == 0)) { // LL occasionally appear about a minute before a new mask-on slice // begins, when the previous mask-on slice ended with a large leak. // This probably indicates the end of LL and beginning // of breath detection, but we don't get any real data until mask-on. // // It has also happened once in a similar scenario for PB and VB, even when // the two mask-on slices are in different sessions! continue; } if (e->m_type == PRS1IntervalBoundaryEvent::TYPE) { StartNewInterval(t); continue; // these internal pseudo-events don't get imported } bool intervalEvent = IsIntervalEvent(e); qint64 interval_end_t = 0; if (intervalEvent) { // Deal with statistics that are reported at the end of an interval, but which need to be imported // at the start of the interval. if (event->family == 3 && event->familyVersion <= 3) { // In F3V0 and F3V3, each slice has its own chunk, so the initial call to UpdateCurrentSlice() // for this chunk is all that's needed. // // We can't just call it again here for simplicity, since the timestamps of F3V3 stat events // can go past the end of the slice. } else { // For all other machines, the event's time stamp will be within bounds of its slice, so // we can use it to find the current slice. UpdateCurrentSlice(event, t); } // Clamp this interval's end time to the end of the slice. interval_end_t = min(t, (*m_currentSlice).end); // Set this event's timestamp as the start of the interval, since that what OSCAR assumes. t = m_statIntervalStart; // TODO: ideally we would also set the duration of the event, but OSCAR doesn't have any notion of that yet. } else { // Advance the slice if needed for the regular event's timestamp. if (!PRS1NonSliceChannels.contains(e->m_type)) { UpdateCurrentSlice(event, t); } } // Sanity check: warn if a (non-slice) event is earlier than the current mask-on slice if (t < (*m_currentSlice).start && (*m_currentSlice).status == MaskOn) { if (!PRS1NonSliceChannels.contains(e->m_type)) { // LL and VB at the beginning of a mask-on session sometimes start 1 second early, // so suppress that warning. if ((*m_currentSlice).start - t > 1000 || (e->m_type != PRS1LargeLeakEvent::TYPE && e->m_type != PRS1VariableBreathingEvent::TYPE)) { qWarning() << sessionid << "Event" << e->m_type << "before mask-on slice:" << ts(t); } } } // Import the event. switch (e->m_type) { // F3V3 doesn't have individual HY/CA/OA events, only counts every 2 minutes, where // nonzero counts show up as overview flags. Currently OSCAR doesn't have a way to // chart those numeric statistics, so we generate events based on the count. // // TODO: This (and VS2) would probably be better handled as numeric charts only, // along with enhancing overview flags to be drawn when channels have nonzero values, // instead of the fictitious "events" that are currently generated. case PRS1HypopneaCount::TYPE: case PRS1ClearAirwayCount::TYPE: case PRS1ObstructiveApneaCount::TYPE: // Make sure PRS1ClearAirwayEvent/etc. isn't supported before generating events from counts. CHECK_VALUE(supported.contains(PRS1HypopneaEvent::TYPE), false); CHECK_VALUE(supported.contains(PRS1ClearAirwayEvent::TYPE), false); CHECK_VALUE(supported.contains(PRS1ObstructiveApneaEvent::TYPE), false); // Divide each count into events evenly spaced over the interval. // NOTE: This is slightly fictional, but there's no waveform data for F3V3, so it won't // incorrectly associate specific events with specific flow or pressure events. if (e->m_value > 0) { qint64 blockduration = interval_end_t - m_statIntervalStart; qint64 div = blockduration / e->m_value; qint64 tt = t; PRS1ParsedDurationEvent ee(e->m_type, t, 0); for (int i=0; i < e->m_value; ++i) { ImportEvent(tt, &ee); tt += div; } } // TODO: Consider whether to have a numeric channel for HY/CA/OA that gets charted like VS does, // in which case we can fall through. break; default: ImportEvent(t, e); // Cache the most recently imported interval events so that we can import duplicate end-of-slice events if needed. // We can't write them here because we don't yet know if they're the last in the slice. if (intervalEvent) { // Reset the list of pending events when we encounter a stat event in a new interval. // // This logic has grown sufficiently complex that it may eventually be worth encapsulating // each batch of parsed interval events into a composite interval event when parsing, // rather than requiring timestamp-based gymnastics to infer that structure on import. if (m_lastIntervalEnd != interval_end_t) { m_lastIntervalEvents.clear(); m_lastIntervalEnd = interval_end_t; m_intervalPressure = m_currentPressure; } // The events need to be in order so that any dynamically calculated channels (such as PS) are properly computed. m_lastIntervalEvents.append(e); } break; } } // Write out any pending end-of-slice events. FinishSlice(); if (!ok) { return false; } // TODO: This needs to be special-cased for F3V0 and F3V3 due to their weird interval-based event format // until there's a way for its parser to correctly set the timestamps for truncated // intervals in sessions that don't end on a 2-minute boundary. if (!(event->family == 3 && event->familyVersion <= 3)) { // If the last event has a non-zero duration, t will not reflect the full duration of the chunk, so update it. t = qint64(event->timestamp + event->duration) * 1000L; if (session->last() == 0) { qWarning() << sessionid << "End time not set by summary?"; } else if (t > session->last()) { // This has only been seen in two instances: // 1. Once with corrupted data, in which the summary and event files each contained // multiple conflicting sessions (all brief) with the same ID. // 2. On one 500G110, multiple PRS1PressureSetEvents appear after the end of the session, // across roughtly two dozen sessions. These seem to be discarded on official reports, // see ImportEvent() below. qWarning() << sessionid << "Events continue after summary?"; } // Events can end before the session if the mask was off before the equipment turned off. } return true; } void PRS1Import::ImportEvent(qint64 t, PRS1ParsedEvent* e) { qint64 duration; // TODO: Filter out duplicate/overlapping PB and RE events. // // These actually get reported by the machines, but they cause "unordered time" warnings // and they throw off the session statistics. Even official reports show the wrong stats, // for example counting each of 3 duplicate PBs towards the total time in PB. // // It's not clear whether filtering can reasonably be done here or whether it needs // to be done in ImportEventChunk. const QVector & channels = PRS1ImportChannelMap[e->m_type]; ChannelID channel = NoChannel, PS, VS2; if (channels.count() > 0) { channel = *channels.at(0); } switch (e->m_type) { case PRS1PressureSetEvent::TYPE: // currentPressure is used to calculate unintentional leak, not just PS // TODO: These have sometimes been observed with t > session->last() on a 500G110. // Official reports seem to discard such events, OSCAR currently doesn't. // Test this more thoroughly before changing behavior here. // fall through case PRS1IPAPSetEvent::TYPE: case PRS1IPAPAverageEvent::TYPE: AddEvent(channel, t, e->m_value, e->m_gain); m_currentPressure = e->m_value; break; case PRS1EPAPSetEvent::TYPE: AddEvent(channel, t, e->m_value, e->m_gain); if (m_calcPSfromSet) { PS = *(PRS1ImportChannelMap[PRS1IPAPSetEvent::TYPE].at(1)); AddEvent(PS, t, m_currentPressure - e->m_value, e->m_gain); // Pressure Support } break; case PRS1EPAPAverageEvent::TYPE: PS = *channels.at(1); AddEvent(channel, t, e->m_value, e->m_gain); AddEvent(PS, t, m_currentPressure - e->m_value, e->m_gain); // Pressure Support break; case PRS1TimedBreathEvent::TYPE: // The duration appears to correspond to the length of the timed breath in seconds when multiplied by 0.1 (100ms)! // TODO: consider changing parsers to use milliseconds for time, since it turns out there's at least one way // they can express durations less than 1 second. // TODO: consider allowing OSCAR to record millisecond durations so that the display will say "2.1" instead of "21" or "2". duration = e->m_duration * 100L; // for now do this here rather than in parser, since parser events don't use milliseconds AddEvent(*channels.at(0), t - duration, e->m_duration * 0.1F, 0.1F); // TODO: a gain of 0.1 should render this unnecessary, but gain doesn't seem to work currently break; case PRS1ObstructiveApneaEvent::TYPE: case PRS1ClearAirwayEvent::TYPE: case PRS1HypopneaEvent::TYPE: case PRS1FlowLimitationEvent::TYPE: AddEvent(channel, t, e->m_duration, e->m_gain); break; case PRS1PeriodicBreathingEvent::TYPE: case PRS1LargeLeakEvent::TYPE: case PRS1VariableBreathingEvent::TYPE: // TODO: The graphs silently treat the timestamp of a span as an end time rather than start (see gFlagsLine::paint). // Decide whether to preserve that behavior or change it universally and update either this code or comment. duration = e->m_duration * 1000L; AddEvent(channel, t + duration, e->m_duration, e->m_gain); break; case PRS1SnoreEvent::TYPE: // snore count that shows up in flags but not waveform // TODO: The numeric snore graph is the right way to present this information, // but it needs to be shifted left 2 minutes, since it's not a starting value // but a past statistic. AddEvent(channel, t, e->m_value, e->m_gain); // Snore count, continuous data if (e->m_value > 0) { // TODO: currently these get drawn on our waveforms, but they probably shouldn't, // since they don't have a precise timestamp. They should continue to be drawn // on the flags overview. See the comment in ImportEventChunk regarding flags // for numeric channels. // // We need to pass the count along so that the VS2 index will tabulate correctly. VS2 = *channels.at(1); AddEvent(VS2, t, e->m_value, 1); } break; case PRS1VibratorySnoreEvent::TYPE: // real VS marker on waveform // TODO: These don't need to be drawn separately on the flag overview, since // they're presumably included in the overall snore count statistic. They should // continue to be drawn on the waveform, due to their precise timestamp. AddEvent(channel, t, e->m_value, e->m_gain); break; default: if (channels.count() == 1) { // For most events, simply pass the value through to the mapped channel. AddEvent(channel, t, e->m_value, e->m_gain); } else if (channels.count() > 1) { // Anything mapped to more than one channel must have a case statement above. qWarning() << "Missing import handler for PRS1 event type" << (int) e->m_type; break; } else { // Not imported, no channels mapped to this event // These will show up in chunk YAML and any user alerts will be driven by the parser. } break; } } CPAPMode PRS1Import::importMode(int prs1mode) { CPAPMode mode = MODE_UNKNOWN; switch (prs1mode) { case PRS1_MODE_CPAPCHECK: mode = MODE_CPAP; break; case PRS1_MODE_CPAP: mode = MODE_CPAP; break; case PRS1_MODE_AUTOCPAP: mode = MODE_APAP; break; case PRS1_MODE_AUTOTRIAL: mode = MODE_APAP; break; case PRS1_MODE_BILEVEL: mode = MODE_BILEVEL_FIXED; break; case PRS1_MODE_AUTOBILEVEL: mode = MODE_BILEVEL_AUTO_VARIABLE_PS; break; case PRS1_MODE_ASV: mode = MODE_ASV_VARIABLE_EPAP; break; case PRS1_MODE_S: mode = MODE_BILEVEL_FIXED; break; case PRS1_MODE_ST: mode = MODE_BILEVEL_FIXED; break; case PRS1_MODE_PC: mode = MODE_BILEVEL_FIXED; break; case PRS1_MODE_ST_AVAPS: mode = MODE_AVAPS; break; case PRS1_MODE_PC_AVAPS: mode = MODE_AVAPS; break; default: UNEXPECTED_VALUE(prs1mode, "known PRS1 mode"); break; } return mode; } bool PRS1Import::ImportCompliance() { bool ok; ok = compliance->ParseCompliance(); qint64 start = qint64(compliance->timestamp) * 1000L; for (int i=0; i < compliance->m_parsedData.count(); i++) { PRS1ParsedEvent* e = compliance->m_parsedData.at(i); if (e->m_type == PRS1ParsedSliceEvent::TYPE) { AddSlice(start, e); continue; } else if (e->m_type != PRS1ParsedSettingEvent::TYPE) { qWarning() << "Compliance had non-setting event:" << (int) e->m_type; continue; } PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; switch (s->m_setting) { case PRS1_SETTING_CPAP_MODE: session->settings[PRS1_Mode] = (PRS1Mode) e->m_value; session->settings[CPAP_Mode] = importMode(e->m_value); break; case PRS1_SETTING_PRESSURE: session->settings[CPAP_Pressure] = e->value(); break; case PRS1_SETTING_PRESSURE_MIN: session->settings[CPAP_PressureMin] = e->value(); break; case PRS1_SETTING_PRESSURE_MAX: session->settings[CPAP_PressureMax] = e->value(); break; case PRS1_SETTING_FLEX_MODE: session->settings[PRS1_FlexMode] = e->m_value; break; case PRS1_SETTING_FLEX_LEVEL: session->settings[PRS1_FlexLevel] = e->m_value; break; case PRS1_SETTING_FLEX_LOCK: session->settings[PRS1_FlexLock] = (bool) e->m_value; break; case PRS1_SETTING_RAMP_TIME: session->settings[CPAP_RampTime] = e->m_value; break; case PRS1_SETTING_RAMP_PRESSURE: session->settings[CPAP_RampPressure] = e->value(); break; case PRS1_SETTING_RAMP_TYPE: session->settings[PRS1_RampType] = e->m_value; break; case PRS1_SETTING_HUMID_STATUS: session->settings[PRS1_HumidStatus] = (bool) e->m_value; break; case PRS1_SETTING_HUMID_MODE: session->settings[PRS1_HumidMode] = e->m_value; break; case PRS1_SETTING_HEATED_TUBE_TEMP: session->settings[PRS1_TubeTemp] = e->m_value; break; case PRS1_SETTING_HUMID_LEVEL: session->settings[PRS1_HumidLevel] = e->m_value; break; case PRS1_SETTING_MASK_RESIST_LOCK: session->settings[PRS1_MaskResistLock] = (bool) e->m_value; break; case PRS1_SETTING_MASK_RESIST_SETTING: session->settings[PRS1_MaskResistSet] = e->m_value; break; case PRS1_SETTING_HOSE_DIAMETER: session->settings[PRS1_HoseDiam] = e->m_value; break; case PRS1_SETTING_TUBING_LOCK: session->settings[PRS1_TubeLock] = (bool) e->m_value; break; case PRS1_SETTING_AUTO_ON: session->settings[PRS1_AutoOn] = (bool) e->m_value; break; case PRS1_SETTING_AUTO_OFF: session->settings[PRS1_AutoOff] = (bool) e->m_value; break; case PRS1_SETTING_MASK_ALERT: session->settings[PRS1_MaskAlert] = (bool) e->m_value; break; case PRS1_SETTING_SHOW_AHI: session->settings[PRS1_ShowAHI] = (bool) e->m_value; break; default: qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; break; } } if (!ok) { return false; } if (compliance->duration == 0) { // This does occasionally happen and merely indicates a brief session with no useful data. // This requires the use of really_set_last below, which otherwise rejects 0 length. qDebug() << compliance->sessionid << "compliance duration == 0"; } session->setSummaryOnly(true); session->set_first(start); session->really_set_last(qint64(compliance->timestamp + compliance->duration) * 1000L); return true; } void PRS1Import::AddSlice(qint64 start, PRS1ParsedEvent* e) { // Cache all slices and incrementally calculate their durations. PRS1ParsedSliceEvent* s = (PRS1ParsedSliceEvent*) e; qint64 tt = start + qint64(s->m_start) * 1000L; if (!m_slices.isEmpty()) { SessionSlice & prevSlice = m_slices.last(); prevSlice.end = tt; } m_slices.append(SessionSlice(tt, tt, (SliceStatus) s->m_value)); } bool PRS1Import::ImportSummary() { if (!summary) { qWarning() << "ImportSummary() called with no summary?"; return false; } qint64 start = qint64(summary->timestamp) * 1000L; session->set_first(start); // TODO: The below max pressures aren't right for the 30 cmH2O models. session->setPhysMax(CPAP_LeakTotal, 120); session->setPhysMin(CPAP_LeakTotal, 0); session->setPhysMax(CPAP_Pressure, 25); session->setPhysMin(CPAP_Pressure, 4); session->setPhysMax(CPAP_IPAP, 25); session->setPhysMin(CPAP_IPAP, 4); session->setPhysMax(CPAP_EPAP, 25); session->setPhysMin(CPAP_EPAP, 4); session->setPhysMax(CPAP_PS, 25); session->setPhysMin(CPAP_PS, 0); bool ok; ok = summary->ParseSummary(); PRS1Mode nativemode = PRS1_MODE_UNKNOWN; CPAPMode cpapmode = MODE_UNKNOWN; bool humidifierConnected = false; for (int i=0; i < summary->m_parsedData.count(); i++) { PRS1ParsedEvent* e = summary->m_parsedData.at(i); if (e->m_type == PRS1ParsedSliceEvent::TYPE) { AddSlice(start, e); continue; } else if (e->m_type != PRS1ParsedSettingEvent::TYPE) { qWarning() << "Summary had non-setting event:" << (int) e->m_type; continue; } PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; switch (s->m_setting) { case PRS1_SETTING_CPAP_MODE: nativemode = (PRS1Mode) e->m_value; cpapmode = importMode(e->m_value); break; case PRS1_SETTING_PRESSURE: session->settings[CPAP_Pressure] = e->value(); break; case PRS1_SETTING_PRESSURE_MIN: session->settings[CPAP_PressureMin] = e->value(); break; case PRS1_SETTING_PRESSURE_MAX: session->settings[CPAP_PressureMax] = e->value(); break; case PRS1_SETTING_EPAP: session->settings[CPAP_EPAP] = e->value(); break; case PRS1_SETTING_IPAP: session->settings[CPAP_IPAP] = e->value(); break; case PRS1_SETTING_PS: session->settings[CPAP_PS] = e->value(); break; case PRS1_SETTING_EPAP_MIN: session->settings[CPAP_EPAPLo] = e->value(); break; case PRS1_SETTING_EPAP_MAX: session->settings[CPAP_EPAPHi] = e->value(); break; case PRS1_SETTING_IPAP_MIN: session->settings[CPAP_IPAPLo] = e->value(); break; case PRS1_SETTING_IPAP_MAX: session->settings[CPAP_IPAPHi] = e->value(); break; case PRS1_SETTING_PS_MIN: session->settings[CPAP_PSMin] = e->value(); break; case PRS1_SETTING_PS_MAX: session->settings[CPAP_PSMax] = e->value(); break; case PRS1_SETTING_FLEX_MODE: session->settings[PRS1_FlexMode] = e->m_value; break; case PRS1_SETTING_FLEX_LEVEL: session->settings[PRS1_FlexLevel] = e->m_value; break; case PRS1_SETTING_FLEX_LOCK: session->settings[PRS1_FlexLock] = (bool) e->m_value; break; case PRS1_SETTING_RAMP_TIME: session->settings[CPAP_RampTime] = e->m_value; break; case PRS1_SETTING_RAMP_PRESSURE: session->settings[CPAP_RampPressure] = e->value(); break; case PRS1_SETTING_RAMP_TYPE: session->settings[PRS1_RampType] = e->m_value; break; case PRS1_SETTING_HUMID_STATUS: humidifierConnected = (bool) e->m_value; session->settings[PRS1_HumidStatus] = humidifierConnected; break; case PRS1_SETTING_HUMID_MODE: session->settings[PRS1_HumidMode] = e->m_value; break; case PRS1_SETTING_HEATED_TUBE_TEMP: session->settings[PRS1_TubeTemp] = e->m_value; break; case PRS1_SETTING_HUMID_LEVEL: session->settings[PRS1_HumidLevel] = e->m_value; break; case PRS1_SETTING_HUMID_TARGET_TIME: // Only import this setting if there's a humidifier connected. // (This setting appears in the data even when it's disconnected.) // TODO: Consider moving this logic into the parser for target time. if (humidifierConnected) { if (e->m_value > 1) { // use scaled numeric value session->settings[PRS1_HumidTargetTime] = e->value(); } else { // use unscaled 0 or 1 for Off or Auto respectively session->settings[PRS1_HumidTargetTime] = e->m_value; } } break; case PRS1_SETTING_MASK_RESIST_LOCK: session->settings[PRS1_MaskResistLock] = (bool) e->m_value; break; case PRS1_SETTING_MASK_RESIST_SETTING: session->settings[PRS1_MaskResistSet] = e->m_value; break; case PRS1_SETTING_HOSE_DIAMETER: session->settings[PRS1_HoseDiam] = e->m_value; break; case PRS1_SETTING_TUBING_LOCK: session->settings[PRS1_TubeLock] = (bool) e->m_value; break; case PRS1_SETTING_AUTO_ON: session->settings[PRS1_AutoOn] = (bool) e->m_value; break; case PRS1_SETTING_AUTO_OFF: session->settings[PRS1_AutoOff] = (bool) e->m_value; break; case PRS1_SETTING_MASK_ALERT: session->settings[PRS1_MaskAlert] = (bool) e->m_value; break; case PRS1_SETTING_SHOW_AHI: session->settings[PRS1_ShowAHI] = (bool) e->m_value; break; case PRS1_SETTING_BACKUP_BREATH_MODE: session->settings[PRS1_BackupBreathMode] = e->m_value; break; case PRS1_SETTING_BACKUP_BREATH_RATE: session->settings[PRS1_BackupBreathRate] = e->m_value; break; case PRS1_SETTING_BACKUP_TIMED_INSPIRATION: session->settings[PRS1_BackupBreathTi] = e->value(); break; case PRS1_SETTING_TIDAL_VOLUME: session->settings[CPAP_TidalVolume] = e->m_value; break; case PRS1_SETTING_AUTO_TRIAL: // new to F0V6 session->settings[PRS1_AutoTrial] = e->m_value; nativemode = PRS1_MODE_AUTOTRIAL; // Note: F0V6 reports show the underlying CPAP mode rather than Auto-Trial. cpapmode = importMode(nativemode); break; case PRS1_SETTING_EZ_START: session->settings[PRS1_EZStart] = (bool) e->m_value; break; case PRS1_SETTING_RISE_TIME: session->settings[PRS1_RiseTime] = e->m_value; break; case PRS1_SETTING_RISE_TIME_LOCK: session->settings[PRS1_RiseTimeLock] = (bool) e->m_value; break; case PRS1_SETTING_APNEA_ALARM: case PRS1_SETTING_DISCONNECT_ALARM: case PRS1_SETTING_LOW_MV_ALARM: case PRS1_SETTING_LOW_TV_ALARM: // TODO: define and add new channels for alarms once we have more samples and can reliably parse them. break; default: qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; break; } } if (!ok) { return false; } if (summary->m_parsedData.count() > 0) { if (nativemode == PRS1_MODE_UNKNOWN) UNEXPECTED_VALUE(nativemode, "known mode"); if (cpapmode == MODE_UNKNOWN) UNEXPECTED_VALUE(cpapmode, "known mode"); session->settings[PRS1_Mode] = nativemode; session->settings[CPAP_Mode] = cpapmode; } if (summary->duration == 0) { // This does occasionally happen and merely indicates a brief session with no useful data. // This requires the use of really_set_last below, which otherwise rejects 0 length. //qDebug() << summary->sessionid << "session duration == 0"; } session->really_set_last(qint64(summary->timestamp + summary->duration) * 1000L); return true; } bool PRS1Import::ImportEvents() { bool ok = true; for (auto & event : m_event_chunks.values()) { bool chunk_ok = this->ImportEventChunk(event); if (!chunk_ok && m_event_chunks.count() > 1) { // Specify which chunk had problems if there's more than one. ParseSession will warn about the overall result. qWarning() << event->sessionid << QString("Error parsing events in %1 @ %2, continuing") .arg(relativePath(event->m_path)) .arg(event->m_filepos); } ok &= chunk_ok; } if (ok) { // Sanity check: warn if channels' eventlists don't line up with the final mask-on slices. // First make a list of the mask-on slices that will be imported (nonzero duration) QVector maskOn; for (auto & slice : m_slices) { if (slice.status == MaskOn) { if (slice.end > slice.start) { maskOn.append(slice); } else { qWarning() << this->sessionid << "Dropping empty mask-on slice:" << ts(slice.start); } } } // Then go through each required channel and make sure each eventlist is within // the bounds of the corresponding slice, warn if not. if (maskOn.count() > 0 && m_event_chunks.count() > 0) { QVector maskOnWithEvents = maskOn; if (m_event_chunks.first()->family == 3 && m_event_chunks.first()->familyVersion <= 3) { // F3V0 and F3V3 sometimes omit (empty) event chunks if the mask-on slice is shorter than 2 minutes. // Specifically, 1061401 and 1061T always do, but 1160P usually doesn't. Sometimes 1160P will omit // just the first event chunk if the first mask-on slice is shorter than 2 minutes. int empty = maskOn.count() - m_event_chunks.count(); if (empty > 0) { // If there are fewer event chunks than mask-on slices, filter the list to have just the // mask-on slices that we expect to have events. int skipped = 0; maskOnWithEvents.clear(); for (auto & slice : maskOn) { if (skipped < empty && slice.end - slice.start < 120 * 1000L) { skipped++; continue; } maskOnWithEvents.append(slice); } } } if (maskOnWithEvents.count() < m_event_chunks.count()) { qWarning() << sessionid << "has more event chunks than mask-on slices!"; } const QVector & supported = GetSupportedEvents(m_event_chunks.first()); for (auto & e : supported) { if (!PRS1OnDemandChannels.contains(e) && !PRS1NonSliceChannels.contains(e)) { for (auto & pChannelID : PRS1ImportChannelMap[e]) { auto & eventlists = session->eventlist[*pChannelID]; if (eventlists.count() != maskOnWithEvents.count()) { qWarning() << sessionid << "has" << maskOnWithEvents.count() << "mask-on slices, channel" << *pChannelID << "has" << eventlists.count() << "eventlists"; continue; } for (int i = 0; i < eventlists.count(); i++) { if (eventlists[i]->count() == 0) continue; // no first/last timestamp auto & list = eventlists[i]; auto & slice = maskOnWithEvents[i]; if (list->first() < slice.start || list->first() > slice.end || list->last() < slice.start || list->last() > slice.end) { qWarning() << sessionid << "channel" << *pChannelID << "has events outside of mask-on slice" << i; } } } } } } // The above is just sanity-checking the results of our import process, that discontinuous // data is fully contained within mask-on slices. session->m_cnt.clear(); session->m_cph.clear(); session->m_valuesummary[CPAP_Pressure].clear(); session->m_valuesummary.erase(session->m_valuesummary.find(CPAP_Pressure)); } return ok; } QList PRS1Import::CoalesceWaveformChunks(QList & allchunks) { QList coalesced; PRS1DataChunk *chunk = nullptr, *lastchunk = nullptr; int num; for (int i=0; i < allchunks.size(); ++i) { chunk = allchunks.at(i); // Log mismatched waveform session IDs QFileInfo fi(chunk->m_path); bool numeric; QString session_s = fi.fileName().section(".", 0, -2); qint32 sid = session_s.toInt(&numeric, m_sessionid_base); if (!numeric || sid != chunk->sessionid) { qWarning() << chunk->m_path << "@" << chunk->m_filepos << "session ID mismatch:" << chunk->sessionid; } if (lastchunk != nullptr) { // A handful of 960P waveform files have been observed to have multiple sessions. // // This breaks the current approach of deferring waveform parsing until the (multithreaded) // import, since each session is in a separate import task and could be in a separate // thread, or already imported by the time it is discovered that this file contains // more than one session. // // For now, we just dump the chunks that don't belong to the session currently // being imported in this thread, since this happens so rarely. // // TODO: Rework the import process to handle waveform data after compliance/summary/ // events (since we're no longer inferring session information from it) and add it to the // newly imported sessions. if (lastchunk->sessionid != chunk->sessionid) { qWarning() << chunk->m_path << "@" << chunk->m_filepos << "session ID" << lastchunk->sessionid << "->" << chunk->sessionid << ", skipping" << allchunks.size() - i << "remaining chunks"; // Free any remaining chunks for (int j=i; j < allchunks.size(); ++j) { chunk = allchunks.at(j); delete chunk; } break; } // Check whether the data format is the same between the two chunks bool same_format = (lastchunk->waveformInfo.size() == chunk->waveformInfo.size()); if (same_format) { num = chunk->waveformInfo.size(); for (int n=0; n < num; n++) { const PRS1Waveform &a = lastchunk->waveformInfo.at(n); const PRS1Waveform &b = chunk->waveformInfo.at(n); if (a.interleave != b.interleave) { // We've never seen this before qWarning() << chunk->m_path << "format change?" << a.interleave << b.interleave; same_format = false; break; } } } else { // We've never seen this before qWarning() << chunk->m_path << "channels change?" << lastchunk->waveformInfo.size() << chunk->waveformInfo.size(); } qint64 diff = (chunk->timestamp - lastchunk->timestamp) - lastchunk->duration; if (same_format && diff == 0) { // Same format and in sync, so append waveform data to previous chunk lastchunk->m_data.append(chunk->m_data); lastchunk->duration += chunk->duration; delete chunk; continue; } // else start a new chunk to resync } // Report any formats we haven't seen before num = chunk->waveformInfo.size(); if (num > 2) { qDebug() << chunk->m_path << num << "channels"; } for (int n=0; n < num; n++) { int interleave = chunk->waveformInfo.at(n).interleave; switch (chunk->ext) { case 5: // flow data, 5 samples per second if (interleave != 5) { qDebug() << chunk->m_path << "interleave?" << interleave; } break; case 6: // oximetry, 1 sample per second if (interleave != 1) { qDebug() << chunk->m_path << "interleave?" << interleave; } break; default: qWarning() << chunk->m_path << "unknown waveform?" << chunk->ext; break; } } coalesced.append(chunk); lastchunk = chunk; } // In theory there could be broken sessions that have waveform data but no summary or events. // Those waveforms won't be skipped by the scanner, so we have to check for them here. // // This won't be perfect, since any coalesced chunks starting after midnight of the threshhold // date will also be imported, but those should be relatively few, and tolerable imprecision. QList coalescedAndFiltered; qint64 ignoreBefore = loader->context()->IgnoreSessionsOlderThan().toMSecsSinceEpoch()/1000; bool ignoreOldSessions = loader->context()->ShouldIgnoreOldSessions(); for (auto & chunk : coalesced) { if (ignoreOldSessions && chunk->timestamp < ignoreBefore) { qWarning().noquote() << relativePath(chunk->m_path) << "skipping session" << chunk->sessionid << ":" << QDateTime::fromMSecsSinceEpoch(chunk->timestamp*1000).toString() << "older than" << QDateTime::fromMSecsSinceEpoch(ignoreBefore*1000).toString(); delete chunk; continue; } coalescedAndFiltered.append(chunk); } return coalescedAndFiltered; } void PRS1Import::ImportOximetry() { int size = oximetry.size(); for (int i=0; i < size; ++i) { PRS1DataChunk * oxi = oximetry.at(i); int num = oxi->waveformInfo.size(); CHECK_VALUE(num, 2); int size = oxi->m_data.size(); if (size == 0) { qDebug() << oxi->sessionid << oxi->timestamp << "empty?"; continue; } quint64 ti = quint64(oxi->timestamp) * 1000L; qint64 dur = qint64(oxi->duration) * 1000L; if (num > 1) { CHECK_VALUE(oxi->waveformInfo.at(0).interleave, 1); CHECK_VALUE(oxi->waveformInfo.at(1).interleave, 1); // Process interleaved samples QVector data; data.resize(num); int pos = 0; do { for (int n=0; n < num; n++) { int interleave = oxi->waveformInfo.at(n).interleave; data[n].append(oxi->m_data.mid(pos, interleave)); pos += interleave; } } while (pos < size); CHECK_VALUE(data[0].size(), data[1].size()); ImportOximetryChannel(OXI_Pulse, data[0], ti, dur); ImportOximetryChannel(OXI_SPO2, data[1], ti, dur); } } } void PRS1Import::ImportOximetryChannel(ChannelID channel, QByteArray & data, quint64 ti, qint64 dur) { if (data.size() == 0) return; unsigned char* raw = (unsigned char*) data.data(); qint64 step = dur / data.size(); CHECK_VALUE(dur % data.size(), 0); bool pending_samples = false; quint64 start_ti; int start_i; // Split eventlist on invalid values (254-255) for (int i=0; i < data.size(); i++) { unsigned char value = raw[i]; bool valid = (value < 254); if (valid) { if (pending_samples == false) { pending_samples = true; start_i = i; start_ti = ti; } if (channel == OXI_Pulse) { // Values up through 253 are confirmed to be reported as valid on official reports. } else { if (value > 100) UNEXPECTED_VALUE(value, "<= 100%"); } } else { if (pending_samples) { // Create the pending event list EventList* el = session->AddEventList(channel, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, step); el->AddWaveform(start_ti, &raw[start_i], i - start_i, ti - start_ti); pending_samples = false; } } ti += step; } if (pending_samples) { // Create the pending event list EventList* el = session->AddEventList(channel, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, step); el->AddWaveform(start_ti, &raw[start_i], data.size() - start_i, ti - start_ti); pending_samples = false; } } void PRS1Import::ImportWaveforms() { int size = waveforms.size(); quint64 s1, s2; int discontinuities = 0; qint64 lastti=0; for (int i=0; i < size; ++i) { PRS1DataChunk * waveform = waveforms.at(i); int num = waveform->waveformInfo.size(); int size = waveform->m_data.size(); if (size == 0) { qDebug() << waveform->sessionid << waveform->timestamp << "empty?"; continue; } quint64 ti = quint64(waveform->timestamp) * 1000L; quint64 dur = qint64(waveform->duration) * 1000L; qint64 diff = ti - lastti; if ((lastti != 0) && (diff == 1000 || diff == -1000)) { // TODO: Handle discontinuities properly. // Option 1: preserve the discontinuity and make it apparent: // - In the case of a 1-sec overlap, truncate the previous waveform by 1s (+1 sample). // - Then start a new eventlist for the new section. // > The down side of this approach is gaps in the data. // Option 2: slide the waveform data a fraction of a second to avoid the discontinuity // - In the case of a single discontinuity, simply adjust the timestamps of each section by 0.5s so they meet. // - In the case of multiple discontinuities, fitting them is more complicated // > The down side of this approach is that events won't line up exactly the same as official reports. // // Evidently the machines' internal clock drifts slightly, and in some sessions that // means two adjacent (5-minute) waveform chunks have have a +/- 1 second difference in // their notion of the correct time, since the machines only record time at 1-second // resolution. Presumably the real drift is fractional, but there's no way to tell from // the data. // // Encore apparently drops the second chunk entirely if it overlaps with the first // (even by 1 second), and inserts a 1-second gap in the data if it's 1 second later than // the first ended. // // At worst in the former case it seems preferable to drop the overlap and then one // additional second to mark the discontinuity. But depending how often these drifts // occur, it may be possible to adjust all the data so that it's continuous. "Overlapping" // data is not identical, so it seems like these discontinuities are simply an artifact // of timestamping at 1-second intervals right around the 1-second boundary. //qDebug() << waveform->sessionid << "waveform discontinuity:" << (diff / 1000L) << "s @" << ts(waveform->timestamp * 1000L); discontinuities++; } if (num > 1) { float pressure_gain = 0.1F; // standard pressure gain if ((waveform->family == 5 && (waveform->familyVersion == 2 || waveform->familyVersion == 3)) || (waveform->family == 3 && waveform->familyVersion == 6)){ // F5V2, F5V3, and F3V6 use a gain of 0.125 rather than 0.1 to allow for a maximum value of 30 cmH2O pressure_gain = 0.125F; // TODO: this should be parameterized somewhere better, once we have a clear idea of which machines use this } // Process interleaved samples QVector data; data.resize(num); int pos = 0; do { for (int n=0; n < num; n++) { int interleave = waveform->waveformInfo.at(n).interleave; data[n].append(waveform->m_data.mid(pos, interleave)); pos += interleave; } } while (pos < size); s1 = data[0].size(); s2 = data[1].size(); if (s1 > 0) { EventList * flow = session->AddEventList(CPAP_FlowRate, EVL_Waveform, 1.0f, 0.0f, 0.0f, 0.0f, double(dur) / double(s1)); flow->AddWaveform(ti, (char *)data[0].data(), data[0].size(), dur); } if (s2 > 0) { // NOTE: The 900X (F5V3) clamps the values at 127 (15.875 cmH2O) for some reason. // // Previous autoSV machines (950P-961P, F5V0-F5V2) didn't, nor do 1030X (F3V6). // 1130X (also F3V6) is unknown, but likely follows the 1030X. Older ventilators // (F3V3) are also unknown. EventList * pres = session->AddEventList(CPAP_MaskPressureHi, EVL_Waveform, pressure_gain, 0.0f, 0.0f, 0.0f, double(dur) / double(s2)); pres->AddWaveform(ti, (unsigned char *)data[1].data(), data[1].size(), dur); } } else { // Non interleaved, so can process it much faster EventList * flow = session->AddEventList(CPAP_FlowRate, EVL_Waveform, 1.0f, 0.0f, 0.0f, 0.0f, double(dur) / double(waveform->m_data.size())); flow->AddWaveform(ti, (char *)waveform->m_data.data(), waveform->m_data.size(), dur); } lastti = dur+ti; } if (discontinuities > 1) { qWarning() << session->session() << "multiple discontinuities!" << discontinuities; } } void PRS1Import::run() { if (ParseSession()) { loader->context()->AddSession(session); } } bool PRS1Import::ParseSession(void) { bool ok = false; bool save = false; session = loader->context()->CreateSession(sessionid); do { if (compliance != nullptr) { ok = ImportCompliance(); if (!ok) { // We don't see any parse errors with our test data, so warn if there's ever an error encountered. qWarning() << sessionid << "Error parsing compliance, skipping session"; break; } } if (summary != nullptr) { if (compliance != nullptr) { qWarning() << sessionid << "Has both compliance and summary?!"; // Never seen this, but try the summary anyway. } ok = ImportSummary(); if (!ok) { // We don't see any parse errors with our test data, so warn if there's ever an error encountered. qWarning() << sessionid << "Error parsing summary, skipping session"; break; } } if (compliance == nullptr && summary == nullptr) { // With one exception, the only time we've seen missing .000 or .001 data has been with a corrupted card, // or occasionally with partial cards where the .002 is the first file in the Pn directory // and we're missing the preceding directory. Since the lack of compliance or summary means we // don't know the therapy settings or if the mask was ever off, we just skip this very rare case. qWarning() << sessionid << "No compliance or summary, skipping session"; break; } // Import the slices into the session for (auto & slice : m_slices) { // Filter out 0-length slices, since they cause problems for Day::total_time(). if (slice.end > slice.start) { // Filter out everything except mask on/off, since gSessionTimesChart::paint assumes those are the only options. if (slice.status == MaskOn) { session->m_slices.append(slice); } else if (slice.status == MaskOff) { // Mark this slice as BND AddEvent(PRS1_BND, slice.end, (slice.end - slice.start) / 1000L, 1.0); session->m_slices.append(slice); } } } // If are no mask-on slices, then there's not any meaningful event or waveform data for the session. // If there's no no event or waveform data, mark this session as a summary. if (session->m_slices.count() == 0 || (m_event_chunks.count() == 0 && m_wavefiles.isEmpty() && m_oxifiles.isEmpty())) { session->setSummaryOnly(true); save = true; break; // and skip the occasional fragmentary event or waveform data } // TODO: There should be a way to distinguish between no-data-to-import vs. parsing errors // (once we figure out what's benign and what isn't). if (m_event_chunks.count() > 0) { ok = ImportEvents(); if (!ok) { qWarning() << sessionid << "Error parsing events, proceeding anyway?"; } } if (!m_wavefiles.isEmpty()) { // Parse .005 Waveform files waveforms = ReadWaveformData(m_wavefiles, "Waveform"); // Extract and import raw data into channels. ImportWaveforms(); } if (!m_oxifiles.isEmpty()) { // Parse .006 Waveform files oximetry = ReadWaveformData(m_oxifiles, "Oximetry"); // Extract and import raw data into channels. ImportOximetry(); } save = true; } while (false); return save; } QList PRS1Import::ReadWaveformData(QList & files, const char* label) { QMap waveform_chunks; QList result; if (files.count() > 1) { qDebug() << session->session() << label << "data split across multiple files"; } for (auto & f : files) { // Parse a single .005 or .006 waveform file QList file_chunks = loader->ParseFile(f); for (auto & chunk : file_chunks) { PRS1DataChunk* previous = waveform_chunks[chunk->timestamp]; if (previous != nullptr) { // Skip any chunks with identical timestamps. Never yet seen, so warn. qWarning() << chunkComparison(chunk, previous); delete chunk; continue; } waveform_chunks[chunk->timestamp] = chunk; } } // Get the list of pointers sorted by timestamp. result = waveform_chunks.values(); // Coalesce contiguous waveform chunks into larger chunks. result = CoalesceWaveformChunks(result); return result; } QList PRS1Loader::ParseFile(const QString & path) { QList CHUNKS; if (path.isEmpty()) { // ParseSession passes empty filepaths for waveforms if none exist. //qWarning() << path << "ParseFile given empty path"; return CHUNKS; } QFile f(path); if (!f.exists()) { qWarning() << path << "missing"; return CHUNKS; } if (!f.open(QIODevice::ReadOnly)) { qWarning() << path << "can't open"; return CHUNKS; } RawDataFile* src; if (QFileInfo(f).suffix().toUpper().startsWith("B")) { // .B01, .B02, etc. // If it's a DS2 file, insert the DS2 wrapper to decode the chunk stream. PRDS2File* ds2 = new PRDS2File(f); if (!ds2->isValid()) { qWarning() << path << "unable to decrypt"; delete ds2; return CHUNKS; } src = ds2; } else { // Otherwise just use the file as input. src = new RawDataFile(f); } PRS1DataChunk *chunk = nullptr, *lastchunk = nullptr; int cnt = 0; do { chunk = PRS1DataChunk::ParseNext(*src, this); if (chunk == nullptr) { break; } chunk->SetIndex(cnt); // for logging/debugging purposes if (lastchunk != nullptr) { if ((lastchunk->fileVersion != chunk->fileVersion) || (lastchunk->ext != chunk->ext) || (lastchunk->family != chunk->family) || (lastchunk->familyVersion != chunk->familyVersion) || (lastchunk->htype != chunk->htype)) { QString message = "*** unexpected change in header data"; qWarning() << path << message; m_ctx->LogUnexpectedMessage(message); // There used to be error-recovery code here, written before we checked CRCs. // If we ever encounter data with a valid CRC that triggers the above warnings, // we can then revisit how to handle it. } } CHUNKS.append(chunk); lastchunk = chunk; cnt++; } while (!src->atEnd()); delete src; return CHUNKS; } bool initialized = false; using namespace schema; Channel PRS1Channels; void PRS1Loader::initChannels() { Channel * chan = nullptr; channel.add(GRP_CPAP, new Channel(CPAP_PressurePulse = 0x1009, MINOR_FLAG, MT_CPAP, SESSION, "PressurePulse", QObject::tr("Pressure Pulse"), QObject::tr("A pulse of pressure 'pinged' to detect a closed airway."), QObject::tr("PP"), STR_UNIT_EventsPerHour, DEFAULT, QColor("dark red"))); channel.add(GRP_CPAP, chan = new Channel(PRS1_Mode = 0xe120, SETTING, MT_CPAP, SESSION, "PRS1Mode", QObject::tr("Mode"), QObject::tr("PAP Mode"), QObject::tr("Mode"), "", LOOKUP, Qt::green)); chan->addOption(PRS1_MODE_CPAPCHECK, QObject::tr("CPAP-Check")); chan->addOption(PRS1_MODE_CPAP, QObject::tr("CPAP")); chan->addOption(PRS1_MODE_AUTOCPAP, QObject::tr("AutoCPAP")); chan->addOption(PRS1_MODE_AUTOTRIAL, QObject::tr("Auto-Trial")); chan->addOption(PRS1_MODE_BILEVEL, QObject::tr("Bi-Level")); chan->addOption(PRS1_MODE_AUTOBILEVEL, QObject::tr("AutoBiLevel")); chan->addOption(PRS1_MODE_ASV, QObject::tr("ASV")); chan->addOption(PRS1_MODE_S, QObject::tr("S")); chan->addOption(PRS1_MODE_ST, QObject::tr("S/T")); chan->addOption(PRS1_MODE_PC, QObject::tr("PC")); chan->addOption(PRS1_MODE_ST_AVAPS, QObject::tr("S/T - AVAPS")); chan->addOption(PRS1_MODE_PC_AVAPS, QObject::tr("PC - AVAPS")); channel.add(GRP_CPAP, chan = new Channel(PRS1_FlexMode = 0xe105, SETTING, MT_CPAP, SESSION, "PRS1FlexMode", QObject::tr("Flex Mode"), QObject::tr("PRS1 pressure relief mode."), QObject::tr("Flex Mode"), "", LOOKUP, Qt::green)); chan->addOption(FLEX_None, STR_TR_None); chan->addOption(FLEX_CFlex, QObject::tr("C-Flex")); chan->addOption(FLEX_CFlexPlus, QObject::tr("C-Flex+")); chan->addOption(FLEX_AFlex, QObject::tr("A-Flex")); chan->addOption(FLEX_PFlex, QObject::tr("P-Flex")); chan->addOption(FLEX_RiseTime, QObject::tr("Rise Time")); chan->addOption(FLEX_BiFlex, QObject::tr("Bi-Flex")); //chan->addOption(FLEX_AVAPS, QObject::tr("AVAPS")); // Converted into AVAPS PRS1_Mode with FLEX_RiseTime chan->addOption(FLEX_Flex, QObject::tr("Flex")); channel.add(GRP_CPAP, chan = new Channel(PRS1_FlexLevel = 0xe106, SETTING, MT_CPAP, SESSION, "PRS1FlexSet", QObject::tr("Flex Level"), QObject::tr("PRS1 pressure relief setting."), QObject::tr("Flex Level"), "", LOOKUP, Qt::blue)); chan->addOption(0, STR_TR_Off); channel.add(GRP_CPAP, chan = new Channel(PRS1_FlexLock = 0xe111, SETTING, MT_CPAP, SESSION, "PRS1FlexLock", QObject::tr("Flex Lock"), QObject::tr("Whether Flex settings are available to you."), QObject::tr("Flex Lock"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(PRS1_RiseTime = 0xe119, SETTING, MT_CPAP, SESSION, "PRS1RiseTime", QObject::tr("Rise Time"), QObject::tr("Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition"), QObject::tr("Rise Time"), "", LOOKUP, Qt::blue)); channel.add(GRP_CPAP, chan = new Channel(PRS1_RiseTimeLock = 0xe11a, SETTING, MT_CPAP, SESSION, "PRS1RiseTimeLock", QObject::tr("Rise Time Lock"), QObject::tr("Whether Rise Time settings are available to you."), QObject::tr("Rise Lock"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(PRS1_HumidStatus = 0xe101, SETTING, MT_CPAP, SESSION, "PRS1HumidStat", QObject::tr("Humidifier Status"), QObject::tr("PRS1 humidifier connected?"), QObject::tr("Humidifier"), "", LOOKUP, Qt::green)); chan->addOption(0, QObject::tr("Disconnected")); chan->addOption(1, QObject::tr("Connected")); channel.add(GRP_CPAP, chan = new Channel(PRS1_HumidMode = 0xe110, SETTING, MT_CPAP, SESSION, "PRS1HumidMode", QObject::tr("Humidification Mode"), QObject::tr("PRS1 Humidification Mode"), QObject::tr("Humid. Mode"), "", LOOKUP, Qt::green)); chan->addOption(HUMID_Fixed, QObject::tr("Fixed (Classic)")); chan->addOption(HUMID_Adaptive, QObject::tr("Adaptive (System One)")); chan->addOption(HUMID_HeatedTube, QObject::tr("Heated Tube")); chan->addOption(HUMID_Passover, QObject::tr("Passover")); chan->addOption(HUMID_Error, QObject::tr("Error")); channel.add(GRP_CPAP, chan = new Channel(PRS1_TubeTemp = 0xe10f, SETTING, MT_CPAP, SESSION, "PRS1TubeTemp", QObject::tr("Tube Temperature"), QObject::tr("PRS1 Heated Tube Temperature"), QObject::tr("Tube Temp."), "", LOOKUP, Qt::red)); chan->addOption(0, STR_TR_Off); channel.add(GRP_CPAP, chan = new Channel(PRS1_HumidLevel = 0xe102, SETTING, MT_CPAP, SESSION, "PRS1HumidLevel", QObject::tr("Humidifier"), // label varies in reports, "Humidifier Setting" in 50-series, "Humidity Level" in 60-series, "Humidifier" in DreamStation QObject::tr("PRS1 Humidifier Setting"), QObject::tr("Humid. Level"), "", LOOKUP, Qt::blue)); chan->addOption(0, STR_TR_Off); channel.add(GRP_CPAP, chan = new Channel(PRS1_HumidTargetTime = 0xe11b, SETTING, MT_CPAP, SESSION, "PRS1HumidTargetTime", QObject::tr("Target Time"), QObject::tr("PRS1 Humidifier Target Time"), QObject::tr("Hum. Tgt Time"), STR_UNIT_Hours, DEFAULT, Qt::green)); chan->addOption(0, STR_TR_Off); chan->addOption(1, QObject::tr("Auto")); channel.add(GRP_CPAP, chan = new Channel(PRS1_MaskResistSet = 0xe104, SETTING, MT_CPAP, SESSION, "MaskResistSet", QObject::tr("Mask Resistance Setting"), QObject::tr("Mask Resistance Setting"), QObject::tr("Mask Resist."), "", LOOKUP, Qt::green)); chan->addOption(0, STR_TR_Off); channel.add(GRP_CPAP, chan = new Channel(PRS1_HoseDiam = 0xe107, SETTING, MT_CPAP, SESSION, "PRS1HoseDiam", QObject::tr("Hose Diameter"), QObject::tr("Diameter of primary CPAP hose"), QObject::tr("Hose Diam."), "", LOOKUP, Qt::green)); chan->addOption(22, QObject::tr("22mm")); chan->addOption(15, QObject::tr("15mm")); chan->addOption(12, QObject::tr("12mm")); channel.add(GRP_CPAP, chan = new Channel(PRS1_TubeLock = 0xe112, SETTING, MT_CPAP, SESSION, "PRS1TubeLock", QObject::tr("Tubing Type Lock"), QObject::tr("Whether tubing type settings are available to you."), QObject::tr("Tube Lock"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(PRS1_MaskResistLock = 0xe108, SETTING, MT_CPAP, SESSION, "MaskResistLock", QObject::tr("Mask Resistance Lock"), QObject::tr("Whether mask resistance settings are available to you."), QObject::tr("Mask Res. Lock"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(PRS1_AutoOn = 0xe109, SETTING, MT_CPAP, SESSION, "PRS1AutoOn", QObject::tr("Auto On"), QObject::tr("A few breaths automatically starts machine"), QObject::tr("Auto On"), "", LOOKUP, Qt::green)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(PRS1_AutoOff = 0xe10a, SETTING, MT_CPAP, SESSION, "PRS1AutoOff", QObject::tr("Auto Off"), QObject::tr("Machine automatically switches off"), QObject::tr("Auto Off"), "", LOOKUP, Qt::green)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(PRS1_MaskAlert = 0xe10b, SETTING, MT_CPAP, SESSION, "PRS1MaskAlert", QObject::tr("Mask Alert"), QObject::tr("Whether or not machine allows Mask checking."), QObject::tr("Mask Alert"), "", LOOKUP, Qt::green)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(PRS1_ShowAHI = 0xe10c, SETTING, MT_CPAP, SESSION, "PRS1ShowAHI", QObject::tr("Show AHI"), QObject::tr("Whether or not machine shows AHI via built-in display."), QObject::tr("Show AHI"), "", LOOKUP, Qt::green)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(PRS1_RampType = 0xe113, SETTING, MT_CPAP, SESSION, "PRS1RampType", QObject::tr("Ramp Type"), QObject::tr("Type of ramp curve to use."), QObject::tr("Ramp Type"), "", LOOKUP, Qt::black)); chan->addOption(0, QObject::tr("Linear")); chan->addOption(1, QObject::tr("SmartRamp")); chan->addOption(2, QObject::tr("Ramp+")); channel.add(GRP_CPAP, chan = new Channel(PRS1_BackupBreathMode = 0xe114, SETTING, MT_CPAP, SESSION, "PRS1BackupBreathMode", QObject::tr("Backup Breath Mode"), QObject::tr("The kind of backup breath rate in use: none (off), automatic, or fixed"), QObject::tr("Breath Rate"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, QObject::tr("Auto")); chan->addOption(2, QObject::tr("Fixed")); channel.add(GRP_CPAP, chan = new Channel(PRS1_BackupBreathRate = 0xe115, SETTING, MT_CPAP, SESSION, "PRS1BackupBreathRate", QObject::tr("Fixed Backup Breath BPM"), QObject::tr("Minimum breaths per minute (BPM) below which a timed breath will be initiated"), QObject::tr("Breath BPM"), STR_UNIT_BreathsPerMinute, LOOKUP, Qt::black)); channel.add(GRP_CPAP, chan = new Channel(PRS1_BackupBreathTi = 0xe116, SETTING, MT_CPAP, SESSION, "PRS1BackupBreathTi", QObject::tr("Timed Inspiration"), QObject::tr("The time that a timed breath will provide IPAP before transitioning to EPAP"), QObject::tr("Timed Insp."), STR_UNIT_Seconds, DEFAULT, Qt::blue)); channel.add(GRP_CPAP, chan = new Channel(PRS1_AutoTrial = 0xe117, SETTING, MT_CPAP, SESSION, "PRS1AutoTrial", QObject::tr("Auto-Trial Duration"), QObject::tr("The number of days in the Auto-CPAP trial period, after which the machine will revert to CPAP"), QObject::tr("Auto-Trial Dur."), "", LOOKUP, Qt::black)); channel.add(GRP_CPAP, chan = new Channel(PRS1_EZStart = 0xe118, SETTING, MT_CPAP, SESSION, "PRS1EZStart", QObject::tr("EZ-Start"), QObject::tr("Whether or not EZ-Start is enabled"), QObject::tr("EZ-Start"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(PRS1_VariableBreathing = 0x1156, SPAN, MT_CPAP, SESSION, "PRS1_VariableBreathing", QObject::tr("Variable Breathing"), QObject::tr("UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend"), "VB", STR_UNIT_Seconds, DEFAULT, QColor("#ffe8f0"))); chan->setEnabled(false); // disable by default channel.add(GRP_CPAP, new Channel(PRS1_BND = 0x1159, SPAN, MT_CPAP, SESSION, "PRS1_BND", QObject::tr("Breathing Not Detected"), QObject::tr("A period during a session where the machine could not detect flow."), QObject::tr("BND"), STR_UNIT_Unknown, DEFAULT, QColor("light purple"))); channel.add(GRP_CPAP, new Channel(PRS1_TimedBreath = 0x1180, MINOR_FLAG, MT_CPAP, SESSION, "PRS1TimedBreath", QObject::tr("Timed Breath"), QObject::tr("Machine Initiated Breath"), QObject::tr("TB"), STR_UNIT_Seconds, DEFAULT, QColor("black"))); channel.add(GRP_CPAP, chan = new Channel(PRS1_PeakFlow = 0x115a, WAVEFORM, MT_CPAP, SESSION, "PRS1PeakFlow", QObject::tr("Peak Flow"), QObject::tr("Peak flow during a 2-minute interval"), QObject::tr("Peak Flow"), STR_UNIT_LPM, DEFAULT, QColor("red"))); chan->setShowInOverview(true); } void PRS1Loader::Register() { if (initialized) { return; } qDebug() << "Registering PRS1Loader"; RegisterLoader(new PRS1Loader()); initialized = true; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/prs1_loader.h000066400000000000000000000232721417327530600237000ustar00rootroot00000000000000/* SleepLib PRS1 Loader Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef PRS1LOADER_H #define PRS1LOADER_H #include "SleepLib/machine_loader.h" #ifdef UNITTEST_MODE #define private public #define protected public #endif //******************************************************************************************** /// IMPORTANT!!! //******************************************************************************************** // Please INCREMENT the following value when making changes to this loaders implementation // BEFORE making a release const int prs1_data_version = 20; // //******************************************************************************************** const QString prs1_class_name = STR_MACH_PRS1; QString ts(qint64 msecs); /*! \struct PRS1Waveform \brief Used in PRS1 Waveform Parsing */ struct PRS1Waveform { PRS1Waveform(quint16 i, quint8 f) { interleave = i; sample_format = f; } quint16 interleave; quint8 sample_format; }; class PRS1DataChunk; class PRS1ParsedEvent; class PRS1Loader; /*! \class PRS1Import * \brief Contains the functions to parse a single session... multithreaded */ class PRS1Import:public ImportTask { public: PRS1Import(PRS1Loader * l, SessionID s, int base): loader(l), sessionid(s), m_sessionid_base(base) { summary = nullptr; compliance = nullptr; session = nullptr; m_currentSliceInitialized = false; } virtual ~PRS1Import(); //! \brief PRS1Import thread starts execution here. virtual void run(); PRS1DataChunk * compliance; PRS1DataChunk * summary; QMap m_event_chunks; QList waveforms; QList oximetry; QList m_wavefiles; QList m_oxifiles; //! \brief Imports .000 files for bricks. bool ImportCompliance(); //! \brief Imports the .001 summary file. bool ImportSummary(); //! \brief Imports the .002 event file(s). bool ImportEvents(); //! \brief Reads the .005 or .006 waveform file(s). QList ReadWaveformData(QList & files, const char* label); //! \brief Coalesce contiguous .005 or .006 waveform chunks from the file into larger chunks for import. QList CoalesceWaveformChunks(QList & allchunks); //! \brief Takes the parsed list of Flow/MaskPressure waveform chunks and adds them to the database void ImportWaveforms(); //! \brief Takes the parsed list of oximeter waveform chunks and adds them to the database. void ImportOximetry(); //! \brief Adds a single channel of continuous oximetry data to the database, splitting on any missing samples. void ImportOximetryChannel(ChannelID channel, QByteArray & data, quint64 ti, qint64 dur); protected: Session * session; PRS1Loader * loader; SessionID sessionid; QHash m_importChannels; // map channel ID to the session's current EventList* int summary_duration; int m_sessionid_base; // base for inferring session ID from filename //! \brief Translate the PRS1-specific machine mode to the importable vendor-neutral enum. CPAPMode importMode(int mode); //! \brief Parse all the chunks in a single machine session bool ParseSession(void); //! \brief Cache a single slice from a summary or compliance chunk. void AddSlice(qint64 chunk_start, PRS1ParsedEvent* e); QVector m_slices; //! \brief Import a single event from a data chunk. void ImportEvent(qint64 t, PRS1ParsedEvent* event); // State that needs to persist between individual events: EventDataType m_currentPressure; bool m_calcPSfromSet; //! \brief Advance the current mask-on slice if needed and update import data structures accordingly. bool UpdateCurrentSlice(PRS1DataChunk* chunk, qint64 t); bool m_currentSliceInitialized; QVector::const_iterator m_currentSlice; qint64 m_statIntervalStart, m_prevIntervalStart; QList m_lastIntervalEvents; qint64 m_lastIntervalEnd; EventDataType m_intervalPressure; //! \brief Write out any pending end-of-slice events. void FinishSlice(); //! \brief Record the beginning timestamp of a new stat interval, and do related housekeeping. void StartNewInterval(qint64 t); //! \brief Identify statistical events that are reported at the end of an interval. bool IsIntervalEvent(PRS1ParsedEvent* e); //! \brief Import a single data chunk from a .002 file containing event data. bool ImportEventChunk(PRS1DataChunk* event); //! \brief Create all supported channels (except for on-demand ones that only get created if an event appears). void CreateEventChannels(const PRS1DataChunk* event); //! \brief Get the EventList* for the import channel, creating it if necessary. EventList* GetImportChannel(ChannelID channel); //! \brief Import a single event to a channel, creating the channel if necessary. void AddEvent(ChannelID channel, qint64 t, float value, float gain); }; /*! \class PRS1Loader \brief Philips Respironics System One Loader Module */ class PRS1Loader : public CPAPLoader { Q_OBJECT public: PRS1Loader(); virtual ~PRS1Loader(); //! \brief Peek into PROP.TXT or properties.txt at given path, and return it as a normalized key/value hash bool PeekProperties(const QString & filename, QHash & props); //! \brief Peek into PROP.TXT or properties.txt at given path, and use it to fill MachineInfo structure bool PeekProperties(MachineInfo & info, const QString & path); //! \brief Detect if the given path contains a valid Folder structure virtual bool Detect(const QString & path); //! \brief Wrapper for PeekProperties that creates the MachineInfo structure. virtual MachineInfo PeekInfo(const QString & path); //! \brief Scans directory path for valid PRS1 signature virtual int Open(const QString & path); //! \brief Returns the database version of this loader virtual int Version() { return prs1_data_version; } //! \brief Return the loaderName, in this case "PRS1" virtual const QString &loaderName() { return prs1_class_name; } //! \brief Parse a PRS1 summary/event/waveform file and break into invidivual session or waveform chunks QList ParseFile(const QString & path); //! \brief Register this Module to the list of Loaders, so it knows to search for PRS1 data. static void Register(); //! \brief Generate a generic MachineInfo structure, with basic PRS1 info to be expanded upon. virtual MachineInfo newInfo() { return MachineInfo(MT_CPAP, 0, prs1_class_name, QObject::tr("Philips Respironics"), QString(), QString(), QString(), QObject::tr("System One"), QDateTime::currentDateTime(), prs1_data_version); } virtual QString PresReliefLabel(); //! \brief Returns the PRS1 specific code for Pressure Relief Mode virtual ChannelID PresReliefMode(); //! \brief Returns the PRS1 specific code for Pressure Relief Setting virtual ChannelID PresReliefLevel(); //! \brief Returns the PRS1 specific code for PAP mode virtual ChannelID CPAPModeChannel(); //! \brief Returns the PRS1 specific code for Humidifier Connected virtual ChannelID HumidifierConnected(); //! \brief Returns the PRS1 specific code for Humidifier Level virtual ChannelID HumidifierLevel(); //! \brief Called at application init, to set up any custom PRS1 Channels void initChannels(); QHash sesstasks; protected: //! \brief Returns the path of the P-Series folder (whatever case) if present on the card QString GetPSeriesPath(const QString & path); //! \brief Returns the path for each machine detected on an SD card, from oldest to newest QStringList FindMachinesOnCard(const QString & cardPath); //! \brief Opens the SD folder structure for this machine, scans for data files and imports any new sessions int OpenMachine(const QString & path); //! \brief Finds the P0,P1,... session paths and property pathname and returns the base (10 or 16) of the session filenames int FindSessionDirsAndProperties(const QString & path, QStringList & paths, QString & propertyfile); //! \brief Reads the model number from the property file, evaluates its capabilities, and returns true if the machine is supported bool CreateMachineFromProperties(QString propertyfile); //! \brief Scans the given directories for session data and create an import task for each logical session. void ScanFiles(const QStringList & paths, int sessionid_base); //! \brief PRS1 Data files can store multiple sessions, so store them in this list for later processing. QHash new_sessions; }; //******************************************************************************************** class PRS1ModelInfo { protected: QHash> m_testedModels; QHash m_modelNames; QSet m_bricks; public: PRS1ModelInfo(); bool IsSupported(const QHash & properties) const; bool IsSupported(int family, int familyVersion) const; bool IsTested(const QHash & properties) const; bool IsTested(const QString & modelNumber, int family, int familyVersion) const; bool IsBrick(const QString & model) const; const char* Name(const QString & model) const; }; #endif // PRS1LOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/prs1_parser.cpp000066400000000000000000001512241417327530600242600ustar00rootroot00000000000000/* SleepLib PRS1 Loader Parser Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Portions copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "prs1_parser.h" #include "prs1_loader.h" #include "rawdata.h" const PRS1ParsedEventType PRS1TidalVolumeEvent::TYPE; const PRS1ParsedEventType PRS1SnoresAtPressureEvent::TYPE; const PRS1ParsedEventType PRS1TimedBreathEvent::TYPE; const PRS1ParsedEventType PRS1ObstructiveApneaEvent::TYPE; const PRS1ParsedEventType PRS1ClearAirwayEvent::TYPE; const PRS1ParsedEventType PRS1FlowLimitationEvent::TYPE; const PRS1ParsedEventType PRS1PeriodicBreathingEvent::TYPE; const PRS1ParsedEventType PRS1LargeLeakEvent::TYPE; const PRS1ParsedEventType PRS1VariableBreathingEvent::TYPE; const PRS1ParsedEventType PRS1HypopneaEvent::TYPE; const PRS1ParsedEventType PRS1TotalLeakEvent::TYPE; const PRS1ParsedEventType PRS1LeakEvent::TYPE; const PRS1ParsedEventType PRS1AutoPressureSetEvent::TYPE; const PRS1ParsedEventType PRS1PressureSetEvent::TYPE; const PRS1ParsedEventType PRS1IPAPSetEvent::TYPE; const PRS1ParsedEventType PRS1EPAPSetEvent::TYPE; const PRS1ParsedEventType PRS1PressureAverageEvent::TYPE; const PRS1ParsedEventType PRS1FlexPressureAverageEvent::TYPE; const PRS1ParsedEventType PRS1IPAPAverageEvent::TYPE; const PRS1ParsedEventType PRS1IPAPHighEvent::TYPE; const PRS1ParsedEventType PRS1IPAPLowEvent::TYPE; const PRS1ParsedEventType PRS1EPAPAverageEvent::TYPE; const PRS1ParsedEventType PRS1RespiratoryRateEvent::TYPE; const PRS1ParsedEventType PRS1PatientTriggeredBreathsEvent::TYPE; const PRS1ParsedEventType PRS1MinuteVentilationEvent::TYPE; const PRS1ParsedEventType PRS1SnoreEvent::TYPE; const PRS1ParsedEventType PRS1VibratorySnoreEvent::TYPE; const PRS1ParsedEventType PRS1PressurePulseEvent::TYPE; const PRS1ParsedEventType PRS1RERAEvent::TYPE; const PRS1ParsedEventType PRS1FlowRateEvent::TYPE; const PRS1ParsedEventType PRS1Test1Event::TYPE; const PRS1ParsedEventType PRS1Test2Event::TYPE; const PRS1ParsedEventType PRS1HypopneaCount::TYPE; const PRS1ParsedEventType PRS1ClearAirwayCount::TYPE; const PRS1ParsedEventType PRS1ObstructiveApneaCount::TYPE; //const PRS1ParsedEventType PRS1DisconnectAlarmEvent::TYPE; const PRS1ParsedEventType PRS1ApneaAlarmEvent::TYPE; //const PRS1ParsedEventType PRS1LowMinuteVentilationAlarmEvent::TYPE; //******************************************************************************************** // MARK: Render parsed events as text static QString hex(int i) { return QString("0x") + QString::number(i, 16).toUpper(); } #define ENUMSTRING(ENUM) case ENUM: s = QStringLiteral(#ENUM); break QString PRS1ParsedEvent::typeName() const { PRS1ParsedEventType t = m_type; QString s; switch (t) { ENUMSTRING(EV_PRS1_RAW); ENUMSTRING(EV_PRS1_UNKNOWN); ENUMSTRING(EV_PRS1_TB); ENUMSTRING(EV_PRS1_OA); ENUMSTRING(EV_PRS1_CA); ENUMSTRING(EV_PRS1_FL); ENUMSTRING(EV_PRS1_PB); ENUMSTRING(EV_PRS1_LL); ENUMSTRING(EV_PRS1_VB); ENUMSTRING(EV_PRS1_HY); ENUMSTRING(EV_PRS1_OA_COUNT); ENUMSTRING(EV_PRS1_CA_COUNT); ENUMSTRING(EV_PRS1_HY_COUNT); ENUMSTRING(EV_PRS1_TOTLEAK); ENUMSTRING(EV_PRS1_LEAK); ENUMSTRING(EV_PRS1_AUTO_PRESSURE_SET); ENUMSTRING(EV_PRS1_PRESSURE_SET); ENUMSTRING(EV_PRS1_IPAP_SET); ENUMSTRING(EV_PRS1_EPAP_SET); ENUMSTRING(EV_PRS1_PRESSURE_AVG); ENUMSTRING(EV_PRS1_FLEX_PRESSURE_AVG); ENUMSTRING(EV_PRS1_IPAP_AVG); ENUMSTRING(EV_PRS1_IPAPLOW); ENUMSTRING(EV_PRS1_IPAPHIGH); ENUMSTRING(EV_PRS1_EPAP_AVG); ENUMSTRING(EV_PRS1_RR); ENUMSTRING(EV_PRS1_PTB); ENUMSTRING(EV_PRS1_MV); ENUMSTRING(EV_PRS1_TV); ENUMSTRING(EV_PRS1_SNORE); ENUMSTRING(EV_PRS1_VS); ENUMSTRING(EV_PRS1_PP); ENUMSTRING(EV_PRS1_RERA); ENUMSTRING(EV_PRS1_FLOWRATE); ENUMSTRING(EV_PRS1_TEST1); ENUMSTRING(EV_PRS1_TEST2); ENUMSTRING(EV_PRS1_SETTING); ENUMSTRING(EV_PRS1_SLICE); ENUMSTRING(EV_PRS1_DISCONNECT_ALARM); ENUMSTRING(EV_PRS1_APNEA_ALARM); ENUMSTRING(EV_PRS1_LOW_MV_ALARM); ENUMSTRING(EV_PRS1_SNORES_AT_PRESSURE); ENUMSTRING(EV_PRS1_INTERVAL_BOUNDARY); default: s = hex(t); qDebug() << "Unknown PRS1ParsedEventType type:" << qPrintable(s); return s; } return s.mid(8).toLower(); // lop off initial EV_PRS1_ } QString PRS1ParsedSettingEvent::settingName() const { PRS1ParsedSettingType t = m_setting; QString s; switch (t) { ENUMSTRING(PRS1_SETTING_CPAP_MODE); ENUMSTRING(PRS1_SETTING_AUTO_TRIAL); ENUMSTRING(PRS1_SETTING_PRESSURE); ENUMSTRING(PRS1_SETTING_PRESSURE_MIN); ENUMSTRING(PRS1_SETTING_PRESSURE_MAX); ENUMSTRING(PRS1_SETTING_EPAP); ENUMSTRING(PRS1_SETTING_EPAP_MIN); ENUMSTRING(PRS1_SETTING_EPAP_MAX); ENUMSTRING(PRS1_SETTING_IPAP); ENUMSTRING(PRS1_SETTING_IPAP_MIN); ENUMSTRING(PRS1_SETTING_IPAP_MAX); ENUMSTRING(PRS1_SETTING_PS); ENUMSTRING(PRS1_SETTING_PS_MIN); ENUMSTRING(PRS1_SETTING_PS_MAX); ENUMSTRING(PRS1_SETTING_BACKUP_BREATH_MODE); ENUMSTRING(PRS1_SETTING_BACKUP_BREATH_RATE); ENUMSTRING(PRS1_SETTING_BACKUP_TIMED_INSPIRATION); ENUMSTRING(PRS1_SETTING_TIDAL_VOLUME); ENUMSTRING(PRS1_SETTING_EZ_START); ENUMSTRING(PRS1_SETTING_FLEX_LOCK); ENUMSTRING(PRS1_SETTING_FLEX_MODE); ENUMSTRING(PRS1_SETTING_FLEX_LEVEL); ENUMSTRING(PRS1_SETTING_RISE_TIME); ENUMSTRING(PRS1_SETTING_RISE_TIME_LOCK); ENUMSTRING(PRS1_SETTING_RAMP_TYPE); ENUMSTRING(PRS1_SETTING_RAMP_TIME); ENUMSTRING(PRS1_SETTING_RAMP_PRESSURE); ENUMSTRING(PRS1_SETTING_HUMID_STATUS); ENUMSTRING(PRS1_SETTING_HUMID_MODE); ENUMSTRING(PRS1_SETTING_HEATED_TUBE_TEMP); ENUMSTRING(PRS1_SETTING_HUMID_LEVEL); ENUMSTRING(PRS1_SETTING_HUMID_TARGET_TIME); ENUMSTRING(PRS1_SETTING_MASK_RESIST_LOCK); ENUMSTRING(PRS1_SETTING_MASK_RESIST_SETTING); ENUMSTRING(PRS1_SETTING_HOSE_DIAMETER); ENUMSTRING(PRS1_SETTING_TUBING_LOCK); ENUMSTRING(PRS1_SETTING_AUTO_ON); ENUMSTRING(PRS1_SETTING_AUTO_OFF); ENUMSTRING(PRS1_SETTING_APNEA_ALARM); ENUMSTRING(PRS1_SETTING_DISCONNECT_ALARM); ENUMSTRING(PRS1_SETTING_LOW_MV_ALARM); ENUMSTRING(PRS1_SETTING_LOW_TV_ALARM); ENUMSTRING(PRS1_SETTING_MASK_ALERT); ENUMSTRING(PRS1_SETTING_SHOW_AHI); default: s = hex(t); qDebug() << "Unknown PRS1ParsedSettingType type:" << qPrintable(s); return s; } return s.mid(13).toLower(); // lop off initial PRS1_SETTING_ } QString PRS1ParsedSettingEvent::modeName() const { int m = value(); QString s; switch ((PRS1Mode) m) { ENUMSTRING(PRS1_MODE_UNKNOWN); // TODO: Remove this when all the parsers are complete. ENUMSTRING(PRS1_MODE_CPAP); ENUMSTRING(PRS1_MODE_CPAPCHECK); ENUMSTRING(PRS1_MODE_AUTOTRIAL); ENUMSTRING(PRS1_MODE_AUTOCPAP); ENUMSTRING(PRS1_MODE_BILEVEL); ENUMSTRING(PRS1_MODE_AUTOBILEVEL); ENUMSTRING(PRS1_MODE_ASV); ENUMSTRING(PRS1_MODE_S); ENUMSTRING(PRS1_MODE_ST); ENUMSTRING(PRS1_MODE_PC); ENUMSTRING(PRS1_MODE_ST_AVAPS); ENUMSTRING(PRS1_MODE_PC_AVAPS); default: s = hex(m); qDebug() << "Unknown PRS1Mode:" << qPrintable(s); return s; } return s.mid(10).toLower(); // lop off initial PRS1_MODE_ } QString PRS1ParsedEvent::timeStr(int t) { int h = t / 3600; int m = (t - (h * 3600)) / 60; int s = t % 60; #if 1 // Optimized after profiling regression tests. return QString::asprintf("%02d:%02d:%02d", h, m, s); #else // Unoptimized original, slows down regression tests. return QString("%1:%2:%3").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0')).arg(s, 2, 10, QChar('0')); #endif } static QString byteList(QByteArray data, int limit=-1) { int count = data.size(); if (limit == -1 || limit > count) limit = count; QStringList l; for (int i = 0; i < limit; i++) { l.push_back(QString( "%1" ).arg((int) data[i] & 0xFF, 2, 16, QChar('0') ).toUpper()); } if (limit < count) l.push_back("..."); QString s = l.join(" "); return s; } QMap PRS1IntervalBoundaryEvent::contents(void) { QMap out; out["start"] = timeStr(m_start); return out; } QMap PRS1ParsedDurationEvent::contents(void) { QMap out; out["start"] = timeStr(m_start); out["duration"] = timeStr(m_duration); return out; } QMap PRS1ParsedValueEvent::contents(void) { QMap out; out["start"] = timeStr(m_start); out["value"] = QString::number(value()); return out; } QMap PRS1UnknownDataEvent::contents(void) { QMap out; out["pos"] = QString::number(m_pos); out["data"] = byteList(m_data); return out; } QMap PRS1ParsedSettingEvent::contents(void) { QMap out; QString v; if (m_setting == PRS1_SETTING_CPAP_MODE) { v = modeName(); } else { v = QString::number(value()); } out[settingName()] = v; return out; } QMap PRS1ParsedSliceEvent::contents(void) { QMap out; out["start"] = timeStr(m_start); QString s; switch ((SliceStatus) m_value) { case MaskOn: s = "MaskOn"; break; case MaskOff: s = "MaskOff"; break; case EquipmentOff: s = "EquipmentOff"; break; case UnknownStatus: s = "Unknown"; break; } out["status"] = s; return out; } QMap PRS1ParsedAlarmEvent::contents(void) { QMap out; out["start"] = timeStr(m_start); return out; } QMap PRS1SnoresAtPressureEvent::contents(void) { QString label; switch (m_kind) { case 0: label = "pressure"; break; case 1: label = "epap"; break; case 2: label = "ipap"; break; default: label = "unknown_pressure"; break; } QMap out; out["start"] = timeStr(m_start); out[label] = QString::number(value()); out["count"] = QString::number(m_count); return out; } //******************************************************************************************** // MARK: - // MARK: Parse chunk contents bool PRS1DataChunk::ParseCompliance(void) { switch (this->family) { case 0: switch (this->familyVersion) { case 2: case 3: return this->ParseComplianceF0V23(); case 4: return this->ParseComplianceF0V4(); case 5: return this->ParseComplianceF0V5(); case 6: return this->ParseComplianceF0V6(); } default: ; } qWarning() << "unexpected compliance family" << this->family << "familyVersion" << this->familyVersion; return false; } bool PRS1DataChunk::ParseSummary() { switch (this->family) { case 0: if (this->familyVersion == 6) { return this->ParseSummaryF0V6(); } else if (this->familyVersion == 4) { return this->ParseSummaryF0V4(); } else { return this->ParseSummaryF0V23(); } case 3: switch (this->familyVersion) { case 0: return this->ParseSummaryF3V03(); case 3: return this->ParseSummaryF3V03(); case 6: return this->ParseSummaryF3V6(); } break; case 5: if (this->familyVersion == 1) { return this->ParseSummaryF5V012(); } else if (this->familyVersion == 0) { return this->ParseSummaryF5V012(); } else if (this->familyVersion == 2) { return this->ParseSummaryF5V012(); } else if (this->familyVersion == 3) { return this->ParseSummaryF5V3(); } default: ; } qWarning() << "unexpected family" << this->family << "familyVersion" << this->familyVersion; return false; } // TODO: The nested switch statement below just begs for per-version subclasses. bool PRS1DataChunk::ParseEvents() { bool ok = false; switch (this->family) { case 0: switch (this->familyVersion) { case 2: ok = this->ParseEventsF0V23(); break; case 3: ok = this->ParseEventsF0V23(); break; case 4: ok = this->ParseEventsF0V4(); break; case 6: ok = this->ParseEventsF0V6(); break; } break; case 3: switch (this->familyVersion) { case 0: ok = this->ParseEventsF3V03(); break; case 3: ok = this->ParseEventsF3V03(); break; case 6: ok = this->ParseEventsF3V6(); break; } break; case 5: switch (this->familyVersion) { case 0: ok = this->ParseEventsF5V0(); break; case 1: ok = this->ParseEventsF5V1(); break; case 2: ok = this->ParseEventsF5V2(); break; case 3: ok = this->ParseEventsF5V3(); break; } break; default: qDebug() << "Unknown PRS1 family" << this->family << "familyVersion" << this->familyVersion; } return ok; } // TODO: This really should be in some kind of class hierarchy, once we figure out // the right one. const QVector & GetSupportedEvents(const PRS1DataChunk* chunk) { static const QVector none; switch (chunk->family) { case 0: switch (chunk->familyVersion) { case 2: return ParsedEventsF0V23; break; case 3: return ParsedEventsF0V23; break; case 4: return ParsedEventsF0V4; break; case 6: return ParsedEventsF0V6; break; } break; case 3: switch (chunk->familyVersion) { case 0: return ParsedEventsF3V0; break; case 3: return ParsedEventsF3V3; break; case 6: return ParsedEventsF3V6; break; } break; case 5: switch (chunk->familyVersion) { case 0: return ParsedEventsF5V0; break; case 1: return ParsedEventsF5V1; break; case 2: return ParsedEventsF5V2; break; case 3: return ParsedEventsF5V3; break; } break; } qWarning() << "Missing supported event list for family" << chunk->family << "version" << chunk->familyVersion; return none; } QString PRS1DataChunk::DumpEvent(int t, int code, const unsigned char* data, int size) { int s = t; int h = s / 3600; s -= h * 3600; int m = s / 60; s -= m * 60; QString dump = QString("%1:%2:%3 ") .arg(h, 2, 10, QChar('0')) .arg(m, 2, 10, QChar('0')) .arg(s, 2, 10, QChar('0')); dump = dump + " " + hex(code) + ":"; for (int i = 0; i < size; i++) { dump = dump + QString(" %1").arg(data[i]); } return dump; } void PRS1DataChunk::AddEvent(PRS1ParsedEvent* const event) { m_parsedData.push_back(event); } //******************************************************************************************** // MARK: - // MARK: Parse settings shared by multiple families // Humid F0V2 confirmed // 0x00 = Off (presumably no humidifier present) // 0x80 = Off // 0x81 = 1 // 0x82 = 2 // 0x83 = 3 // 0x84 = 4 // 0x85 = 5 // Humid F3V0 confirmed // 0x03 = 3 (but no humidification shown on hours of usage chart) // 0x04 = 4 (but no humidification shown on hours of usage chart) // 0x80 = Off // 0x81 = 1 // 0x82 = 2 // 0x83 = 3 // 0x84 = 4 // 0x85 = 5 // Humid F5V0 confirmed // 0x00 = Off (presumably no humidifier present) // 0x80 = Off // 0x81 = 1, bypass = no // 0x82 = 2, bypass = no // 0x83 = 3, bypass = no // 0x84 = 4, bypass = no // 0x85 = 5, bypass = no // 0xA0 = Off, bypass = yes void PRS1DataChunk::ParseHumidifierSetting50Series(int humid, bool add_setting) { if (humid & (0x40 | 0x10 | 0x08)) UNEXPECTED_VALUE(humid, "known bits"); if (humid & 0x20) { if (this->family == 5) { CHECK_VALUE(humid, 0xA0); // only example of bypass set, unsure whether it can appear otherwise } else { CHECK_VALUE(humid & 0x20, 0); // only ever seen on 950P, where "Bypass System One humidification" is "Yes" } } bool humidifier_present = ((humid & 0x80) != 0); // humidifier connected int humidlevel = humid & 7; // humidification level HumidMode humidmode = HUMID_Fixed; // 50-Series didn't have adaptive or heated tube humidification if (add_setting) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, humidifier_present)); if (humidifier_present) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_MODE, humidmode)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, humidlevel)); } } // Check for truly unexpected values: if (humidlevel > 5) UNEXPECTED_VALUE(humidlevel, "<= 5"); //if (!humidifier_present) CHECK_VALUES(humidlevel, 0, 1); // Some machines appear to encode the humidlevel setting even when the humidifier is not present. } // F0V4 confirmed: // B3 0A = HT=5, H=3, HT // A3 0A = HT=5, H=2, HT // 33 0A = HT=4, H=3, HT // 23 4A = HT=4, H=2, HT // B3 09 = HT=3, H=3, HT // A4 09 = HT=3, H=2, HT // A3 49 = HT=3, H=2, HT // 22 09 = HT=2, H=2, HT // 33 09 = HT=2, H=3, HT // 21 09 = HT=2, H=2, HT // 13 09 = HT=2, H=1, HT // B5 08 = HT=1, H=3, HT // 03 08 = HT=off, HT; data=tube t=0,h=0 // 05 24 = H=5, S1 // 95 06 = H=5, S1 // 95 05 = H=5, S1 // 94 05 = H=4, S1 // 04 24 = H=4, S1 // A3 05 = H=3, S1 // 92 05 = H=2, S1 // A2 05 = H=2, S1 // 01 24 = H=1, S1 // 90 05 = H=off, S1 // 30 05 = H=off, S1 // 95 41 = H=5, Classic // A4 61 = H=4, Classic // A3 61 = H=3, Classic // A2 61 = H=2, Classic // A1 61 = H=1, Classic // 90 41 = H=Off, Classic; data=classic h=0 // 94 11 = H=3, S1, no data [note that bits encode H=4, so no data falls back to H=3] // 93 11 = H=3, S1, no data // 04 30 = H=3, S1, no data // F0V5 confirmed: // 00 60 = H=Off, Classic // 02 60 = H=2, Classic // 05 60 = H=5, Classic // 00 70 = H=Off, no data in chart // F5V1 confirmed: // A0 4A = HT=5, H=2, HT // B1 09 = HT=3, H=3, HT // 91 09 = HT=3, H=1, HT // 32 09 = HT=2, H=3, HT // B2 08 = HT=1, H=3, HT // 00 48 = HT=off, data=tube t=0,h=0 // 95 05 = H=5, S1 // 94 05 = H=4, S1 // 93 05 = H=3, S1 // 92 05 = H=2, S1 // 91 05 = H=1, S1 // 90 05 = H=Off, S1 // 95 41 = H=5, Classic // 94 41 = H=4, Classic // 93 41 = H=3, Classic // 92 41 = H=2, Classic // 01 60 = H=1, Classic // 00 60 = H=Off, Classic // 00 70 = H=3, S1, no data [no data ignores Classic mode, H bits, falls back to S1 H=3] // F5V2 confirmed: // 00 48 = HT=off, data=tube t=0,h=0 // 93 09 = HT=3, H=1, HT // 00 10 = H=3, S1, no data // XX XX = 60-Series Humidifier bytes // 7 = humidity level without tube [on tube disconnect / system one with 22mm hose / classic] : 0 = humidifier off // 8 = [never seen] // 3 = humidity level with tube // 4 = maybe part of humidity level? [never seen] // 8 3 = tube temperature (high bit of humid 1 is low bit of temp) // 4 = "System One" mode (valid even when humidifier is off) // 8 = heated tube present // 10 = no data in chart, maybe no humidifier attached? Seems to fall back on System One = 3 despite other (humidity level and S1) bits. // 20 = unknown, something tube related since whenever it's set tubepresent is false // 40 = "Classic" mode (valid even when humidifier is off, ignored when heated tube is present) // 80 = [never seen] void PRS1DataChunk::ParseHumidifierSetting60Series(unsigned char humid1, unsigned char humid2, bool add_setting) { int humidlevel = humid1 & 7; // Ignored when heated tube is present: humidifier setting on tube disconnect is always reported as 3 if (humidlevel > 5) UNEXPECTED_VALUE(humidlevel, "<= 5"); CHECK_VALUE(humid1 & 8, 0); // never seen int tubehumidlevel = (humid1 >> 4) & 7; // This mask is a best guess based on other masks. if (tubehumidlevel > 5) UNEXPECTED_VALUE(tubehumidlevel, "<= 5"); CHECK_VALUE(tubehumidlevel & 4, 0); // never seen, but would clarify whether above mask is correct int tubetemp = (humid1 >> 7) | ((humid2 & 3) << 1); if (tubetemp > 5) UNEXPECTED_VALUE(tubetemp, "<= 5"); CHECK_VALUE(humid2 & 0x80, 0); // never seen bool humidclassic = (humid2 & 0x40) != 0; // Set on classic mode reports; evidently ignored (sometimes set!) when tube is present //bool no_tube? = (humid2 & 0x20) != 0; // Something tube related: whenever it is set, tube is never present (inverse is not true) bool no_data = (humid2 & 0x10) != 0; // As described in chart, settings still show up int tubepresent = (humid2 & 0x08) != 0; bool humidsystemone = (humid2 & 0x04) != 0; // Set on "System One" humidification mode reports when tubepresent is false if (humidsystemone && tubepresent) { // On a 560P, we've observed a spurious tubepresent bit being set during two sessions. // Those sessions (and the ones that followed) used a 22mm hose. CHECK_VALUE(add_setting, false); // We've only seen this appear during a session, not in the initial settings. tubepresent = false; } // When no_data, reports always say "System One" with humidity level 3, regardless of humidlevel and humidsystemone if (humidsystemone + tubepresent + no_data == 0) CHECK_VALUE(humidclassic, true); // Always set when everything else is off if (no_data && humidsystemone && add_setting == false) { // This has been seen once on a 560P in a session that also generated a file in the error directory. qWarning() << this->sessionid << "Humidification error during session?"; } else { if (humidsystemone + tubepresent + no_data > 1) UNEXPECTED_VALUE(humid2, "one bit set"); // Only one of these ever seems to be set at a time } if (tubepresent && tubetemp == 0) CHECK_VALUE(tubehumidlevel, 0); // When the heated tube is off, tube humidity seems to be 0 if (tubepresent) humidclassic = false; // Classic mode bit is evidently ignored when tube is present if (no_data) humidclassic = false; // Classic mode bit is evidently ignored when tube is present //qWarning() << this->sessionid << (humidclassic ? "C" : ".") << (humid2 & 0x20 ? "?" : ".") << (tubepresent ? "T" : ".") << (no_data ? "X" : ".") << (humidsystemone ? "1" : "."); /* if (tubepresent) { if (tubetemp) { qWarning() << this->sessionid << "tube temp" << tubetemp << "tube humidity" << tubehumidlevel << (humidclassic ? "classic" : "systemone") << "humidity" << humidlevel; } else { qWarning() << this->sessionid << "heated tube off" << (humidclassic ? "classic" : "systemone") << "humidity" << humidlevel; } } else { qWarning() << this->sessionid << (humidclassic ? "classic" : "systemone") << "humidity" << humidlevel; } */ HumidMode humidmode = HUMID_Fixed; if (tubepresent) { humidmode = HUMID_HeatedTube; } else { if (humidsystemone + humidclassic > 1) UNEXPECTED_VALUE(humid2, "fixed or adaptive"); if (humidsystemone) humidmode = HUMID_Adaptive; } if (add_setting) { bool humidifier_present = (no_data == 0); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, humidifier_present)); if (humidifier_present) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_MODE, humidmode)); if (humidmode == HUMID_HeatedTube) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HEATED_TUBE_TEMP, tubetemp)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, tubehumidlevel)); } else { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, humidlevel)); } } } // Check for previously unseen data that we expect to be normal: if (this->family == 0) { // F0V4 if (tubetemp && (tubehumidlevel < 1 || tubehumidlevel > 3)) UNEXPECTED_VALUE(tubehumidlevel, "1-3"); } else if (this->familyVersion == 1) { // F5V1 if (tubepresent) { // all tube temperatures seen if (tubetemp) { if (tubehumidlevel == 0 || tubehumidlevel > 3) UNEXPECTED_VALUE(tubehumidlevel, "1-3"); } } } else if (this->familyVersion == 2) { // F5V2 if (tubepresent) { CHECK_VALUES(tubetemp, 0, 3); if (tubetemp) { CHECK_VALUE(tubehumidlevel, 1); } } CHECK_VALUE(humidsystemone, false); CHECK_VALUE(humidclassic, false); } } // F0V6 confirmed // 90 B0 = HT=3!,H=3!,data=none [no humidifier appears to ignore HT and H bits and show HT=3,H=3 in details] // 8C 6C = HT=3, H=3, data=none // 80 00 = nothing listed in details, data=none, only seen on 400G and 502G // 54 B4 = HT=5, H=5, data=tube // 50 90 = HT=4, H=4, data=tube // 4C 6C = HT=3, H=3, data=tube // 48 68 = HT=3, H=2, data=tube // 40 60 = HT=3, H=Off, data=tube t=3,h=0 // 50 50 = HT=2, H=4, data=tube // 4C 4C = HT=2, H=3, data=tube // 50 30 = HT=1, H=4, data=tube // 4C 0C = HT=off, H=3, data=tube t=0,h=3 // 34 74 = HT=3, H=5, data=adaptive (5) // 50 B0 = HT=5, H=4, adaptive // 30 B0 = HT=3, H=4, data=adaptive (4) // 30 50 = HT=3, H=4, data=adaptive (4) // 30 10 = HT=3!,H=4, data=adaptive (4) [adaptive mode appears to ignore HT bits and show HT=3 in details] // 30 70 = HT=3, H=4, data=adaptive (4) // 2C 6C = HT=3, H=3, data=adaptive (3) // 28 08 = H=2, data=adaptive (2), no details (400G) // 28 48 = HT=3!,H=2, data=adaptive (2) [adaptive mode appears to ignore HT bits and show HT=3 in details] // 28 68 = HT=3, H=2, data=adaptive (2) // 24 64 = HT=3, H=1, data=adaptive (1) // 20 60 = HT=3, H=off, data=adaptive (0) // 14 74 = HT=3, H=5, data=fixed (5) // 10 70 = HT=3, H=4, data=fixed (4) // 0C 6C = HT=3, H=3, data=fixed (3) // 08 48 = HT=3, H=2, data=fixed (2) // 08 68 = HT=3, H=2, data=fixed (2) // 04 64 = HT=3, H=1, data=fixed (1) // 00 00 = HT=3, H=off, data=fixed (0) // F5V3 confirmed: // 90 70 = HT=3, H=3, adaptive, data=no data // 54 14 = HT=Off, H=5, adaptive, data=tube t=0,h=5 // 54 34 = HT=1, H=5, adaptive, data=tube t=1,h=5 // 50 70 = HT=3, H=4, adaptive, data=tube t=3,h=4 // 4C 6C = HT=3, H=3, adaptive, data=tube t=3,h=3 // 4C 4C = HT=2, H=3, adaptive, data=tube t=2,h=3 // 4C 2C = HT=1, H=3, adaptive, data=tube t=1,h=3 // 4C 0C = HT=off, H=3, adaptive, data=tube t=0,h=3 // 48 08 = HT=off, H=2, adaptive, data=tube t=0,h=2 // 44 04 = HT=off, H=1, adaptive, data=tube t=0,h=1 // 40 00 = HT=off,H=off, adaptive, data=tube t=0,h=0 // 34 74 = HT=3, H=5, adaptive, data=s1 (5) // 30 70 = HT=3, H=4, adaptive, data=s1 (4) // 2C 6C = HT=3, H=3, adaptive, data=s1 (3) // 28 68 = HT=3, H=2, adaptive, data=s1 (2) // 24 64 = HT=3, H=1, adaptive, data=s1 (1) // F3V6 confirmed: // 84 24 = HT=3, H=3, disconnect=adaptive, data=no data // 50 90 = HT=4, H=4, disconnect=adaptive, data=tube t=4,h=4 // 44 84 = HT=4, H=1, disconnect=adaptive, data=tube t=4,h=1 // 40 80 = HT=4, H=Off,disconnect=adaptive, data=tube t=4,h=0 // 4C 6C = HT=3, H=3, disconnect=adaptive, data=tube t=3,h=3 // 48 68 = HT=3, H=2, disconnect=adaptive, data=tube t=3,h=2 // 44 44 = HT=2, H=1, disconnect=adaptive, data=tube t=2,h=1 // 48 28 = HT=1, H=2, disconnect=adaptive, data=tube t=1,h=2 // 54 14 = HT=Off,H=5, disconnect=adaptive data=tube t=0,h=5 // 34 14 = HT=3, H=5, disconnect=adaptive, data=s1 (5) // 30 70 = HT=3, H=4, disconnect=adaptive, data=s1 (4) // 2C 6C = HT=3, H=3, disconnect=adaptive, data=s1 (3) // 28 08 = HT=3, H=2, disconnect=adaptive, data=s1 (2) // 20 20 = HT=3, H=Off, disconnect=adaptive, data=s1 (0) // 14 14 = HT=3, H=3, disconnect=fixed, data=classic (5) // 10 10 = HT=3, H=4, disconnect=fixed, data=classic (4) [fixed mode appears to ignore HT bits and show HT=3 in details] // 0C 0C = HT=3, H=3, disconnect=fixed, data=classic (3) // 08 08 = HT=3, H=2, disconnect=fixed, data=classic (2) // 04 64 = HT=3, H=1, disconnect=fixed, data=classic (1) // The data is consistent among all fileVersion 3 models: F0V6, F5V3, F3V6. // // NOTE: F5V3 and F3V6 charts report the "Adaptive" setting as "System One" and the "Fixed" // setting as "Classic", despite labeling the settings "Adaptive" and "Fixed" just like F0V6. // F0V6 is consistent and labels both settings and chart as "Adaptive" and "Fixed". // // 400G and 502G appear to omit the humidifier settings in their details, though they // do support humidifiers, and will show the humidification in the charts. void PRS1DataChunk::ParseHumidifierSettingV3(unsigned char byte1, unsigned char byte2, bool add_setting) { bool humidifier_present = true; bool humidfixed = false; // formerly called "Classic" bool humidadaptive = false; // formerly called "System One" bool tubepresent = false; bool passover = false; bool error = false; // Byte 1: 0x90 (no humidifier data), 0x50 (15ht, tube 4/5, humid 4), 0x54 (15ht, tube 5, humid 5) 0x4c (15ht, tube temp 3, humidifier 3) // 0x0c (15, tube 3, humid 3, fixed) // 0b1001 0000 no humidifier data // 0b0101 0000 tube 4 and 5, humidifier 4 // 0b0101 0100 15ht, tube 5, humidifier 5 // 0b0100 1100 15ht, tube 3, humidifier 3 // 0b1011 0000 15, tube 3, humidifier 3, "Error" on humidification chart with asterisk at 4 // 0b0111 0000 15, tube 3, humidifier 3, "Passover" on humidification chart with notch at 4 // 842 = humidifier status // 1 84 = humidifier setting // ?? CHECK_VALUE(byte1 & 3, 0); int humid = byte1 >> 5; switch (humid) { case 0: humidfixed = true; break; // fixed, ignores tubetemp bits and reports tubetemp=3 case 1: humidadaptive = true; break; // adaptive, ignores tubetemp bits and reports tubetemp=3 case 2: tubepresent = true; break; // heated tube case 3: passover = true; break; // passover mode (only visible in chart) case 4: humidifier_present = false; break; // no humidifier, reports tubetemp=3 and humidlevel=3 case 5: error = true; break; // "Error" in humidification chart, reports tubetemp=3 and humidlevel=3 in settings default: UNEXPECTED_VALUE(humid, "known value"); break; } int humidlevel = (byte1 >> 2) & 7; // Byte 2: 0xB4 (15ht, tube 5, humid 5), 0xB0 (15ht, tube 5, humid 4), 0x90 (tube 4, humid 4), 0x6C (15ht, tube temp 3, humidifier 3) // 0x80? // 0b1011 0100 15ht, tube 5, humidifier 5 // 0b1011 0000 15ht, tube 5, humidifier 4 // 0b1001 0000 tube 4, humidifier 4 // 0b0110 1100 15ht, tube 3, humidifier 3 // 842 = tube temperature // 1 84 = humidity level when using heated tube, thus far always identical to humidlevel // ?? CHECK_VALUE(byte2 & 3, 0); int tubehumidlevel = (byte2 >> 2) & 7; CHECK_VALUE(humidlevel, tubehumidlevel); // thus far always the same int tubetemp = (byte2 >> 5) & 7; if (humidifier_present) { if (humidlevel > 5 || humidlevel < 0) UNEXPECTED_VALUE(humidlevel, "0-5"); // 0=off is valid when a humidifier is attached if (humid == 2) { // heated tube if (tubetemp > 5 || tubetemp < 0) UNEXPECTED_VALUE(tubetemp, "0-5"); // TODO: maybe this is only if heated tube? 0=off is valid even in heated tube mode } } // TODO: move this up into the switch statement above, given how many modes there now are. HumidMode humidmode = HUMID_Fixed; if (tubepresent) { humidmode = HUMID_HeatedTube; } else if (humidadaptive) { humidmode = HUMID_Adaptive; } else if (passover) { humidmode = HUMID_Passover; } else if (error) { humidmode = HUMID_Error; } if (add_setting) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, humidifier_present)); if (humidifier_present) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_MODE, humidmode)); if (humidmode == HUMID_HeatedTube) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HEATED_TUBE_TEMP, tubetemp)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, tubehumidlevel)); } else { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, humidlevel)); } } } // Check for previously unseen data that we expect to be normal: if (family == 0) { // All variations seen. } else if (family == 5) { if (tubepresent) { // All tube temperature and humidity levels seen. } else if (humidadaptive) { // All humidity levels seen. } else if (humidfixed) { if (humidlevel < 3) UNEXPECTED_VALUE(humidlevel, "3-5"); } } else if (family == 3) { if (tubepresent) { // All tube temperature and humidity levels seen. } else if (humidadaptive) { // All humidity levels seen. } else if (humidfixed) { // All humidity levels seen. } } } void PRS1DataChunk::ParseTubingTypeV3(unsigned char type) { int diam; switch (type) { case 0: diam = 22; break; case 1: diam = 15; break; case 2: diam = 15; break; // 15HT, though the reports only say "15" for DreamStation models case 3: diam = 12; break; // seen on DreamStation Go models default: UNEXPECTED_VALUE(type, "known tubing type"); return; } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HOSE_DIAMETER, diam)); } //******************************************************************************************** // MARK: - // MARK: Parse and verify chunk from stream typedef quint16 crc16_t; typedef quint32 crc32_t; static crc16_t CRC16(unsigned char * data, size_t data_len, crc16_t crc=0); static crc32_t CRC32(const unsigned char *data, size_t data_len, crc32_t crc=0xffffffffU); static crc32_t CRC32wchar(const unsigned char *data, size_t data_len, crc32_t crc=0xffffffffU); PRS1DataChunk::PRS1DataChunk(RawDataDevice & f, PRS1Loader* in_loader) : loader(in_loader) { m_path = f.name(); } PRS1DataChunk::~PRS1DataChunk() { for (int i=0; i < m_parsedData.count(); i++) { PRS1ParsedEvent* e = m_parsedData.at(i); delete e; } } PRS1DataChunk* PRS1DataChunk::ParseNext(RawDataDevice & f, PRS1Loader* loader) { PRS1DataChunk* out_chunk = nullptr; PRS1DataChunk* chunk = new PRS1DataChunk(f, loader); do { // Parse the header and calculate its checksum. bool ok = chunk->ReadHeader(f); if (!ok) { break; } // Make sure the calculated checksum matches the stored checksum. if (chunk->calcChecksum != chunk->storedChecksum) { qWarning() << chunk->m_path << "header checksum calc" << chunk->calcChecksum << "!= stored" << chunk->storedChecksum; break; } // Read the block's data and calculate the block CRC. ok = chunk->ReadData(f); if (!ok) { break; } // Make sure the calculated CRC over the entire chunk (header and data) matches the stored CRC. if (chunk->calcCrc != chunk->storedCrc) { // Corrupt data block, warn about it. qWarning() << chunk->m_path << "@" << chunk->m_filepos << "block CRC calc" << hex << chunk->calcCrc << "!= stored" << hex << chunk->storedCrc; // TODO: When this happens, it's usually because the chunk was truncated and another chunk header // exists within the blockSize bytes. In theory it should be possible to rewing and resync by // looking for another chunk header with the same fileVersion, htype, family, familyVersion, and // ext (blockSize and other fields could vary). // // But this is quite rare, so for now we bail on the rest of the file. break; } // Only return the chunk if it has passed all tests above. out_chunk = chunk; } while (false); if (out_chunk == nullptr) delete chunk; return out_chunk; } bool PRS1DataChunk::ReadHeader(RawDataDevice & f) { bool ok = false; do { // Read common header fields. this->m_filepos = f.pos(); this->m_header = f.read(15); if (this->m_header.size() != 15) { if (this->m_header.size() == 0) { qWarning() << this->m_path << "empty, skipping"; } else { qWarning() << this->m_path << "file too short?"; } break; } unsigned char * header = (unsigned char *)this->m_header.data(); this->fileVersion = header[0]; // Correlates to DataFileVersion in PROP[erties].TXT, only 2 or 3 has ever been observed this->blockSize = (header[2] << 8) | header[1]; this->htype = header[3]; // 00 = normal, 01=waveform this->family = header[4]; this->familyVersion = header[5]; this->ext = header[6]; this->sessionid = (header[10] << 24) | (header[9] << 16) | (header[8] << 8) | header[7]; this->timestamp = (header[14] << 24) | (header[13] << 16) | (header[12] << 8) | header[11]; // Do a few early sanity checks before any variable-length header data. if (this->blockSize == 0) { qWarning() << this->m_path << "@" << hex << this->m_filepos << "blocksize 0, skipping remainder of file"; break; } if (this->fileVersion < 2 || this->fileVersion > 3) { if (this->m_filepos > 0) { qWarning() << this->m_path << "@" << hex << this->m_filepos << "corrupt PRS1 header, skipping remainder of file"; } else { qWarning() << this->m_path << "unsupported PRS1 header version" << this->fileVersion; } break; } if (this->htype != PRS1_HTYPE_NORMAL && this->htype != PRS1_HTYPE_INTERVAL) { qWarning() << this->m_path << "unexpected htype:" << this->htype; break; } // Read format-specific variable-length header data. bool hdr_ok = false; if (this->htype != PRS1_HTYPE_INTERVAL) { // Not just waveforms: the 1160P uses this for its .002 events file. // Not a waveform/interval chunk switch (this->fileVersion) { case 2: hdr_ok = ReadNormalHeaderV2(f); break; case 3: hdr_ok = ReadNormalHeaderV3(f); break; default: //hdr_ok remains false, warning is above break; } } else { // Waveform/interval chunk hdr_ok = ReadWaveformHeader(f); } if (!hdr_ok) { break; } // The 8bit checksum comes at the end. QByteArray checksum = f.read(1); if (checksum.size() < 1) { qWarning() << this->m_path << "read error header checksum"; break; } this->storedChecksum = checksum.data()[0]; // Calculate 8bit additive header checksum. header = (unsigned char *)this->m_header.data(); // important because its memory location could move int header_size = this->m_header.size(); quint8 achk=0; for (int i=0; i < header_size; i++) { achk += header[i]; } this->calcChecksum = achk; // Append the stored checksum to the raw data *after* calculating the checksum on the preceding data. this->m_header.append(checksum); ok = true; } while (false); return ok; } bool PRS1DataChunk::ReadNormalHeaderV2(RawDataDevice & /*f*/) { this->m_headerblock = QByteArray(); return true; // always OK } bool PRS1DataChunk::ReadNormalHeaderV3(RawDataDevice & f) { bool ok = false; unsigned char * header; QByteArray headerB2; // This is a new machine, byte 15 is header data block length // followed by variable, data byte pairs do { QByteArray extra = f.read(1); if (extra.size() < 1) { qWarning() << this->m_path << "read error extended header"; break; } this->m_header.append(extra); header = (unsigned char *)this->m_header.data(); int hdb_len = header[15]; int hdb_size = hdb_len * 2; headerB2 = f.read(hdb_size); if (headerB2.size() != hdb_size) { qWarning() << this->m_path << "read error in extended header"; break; } this->m_headerblock = headerB2; this->m_header.append(headerB2); header = (unsigned char *)this->m_header.data(); const unsigned char * hd = (unsigned char *)headerB2.constData(); int pos = 0; int recs = header[15]; for (int i=0; ihblock[hd[pos]] = hd[pos+1]; pos += 2; } ok = true; } while (false); return ok; } bool PRS1DataChunk::ReadWaveformHeader(RawDataDevice & f) { bool ok = false; unsigned char * header; do { // Read the fixed-length waveform header. QByteArray extra = f.read(4); if (extra.size() != 4) { qWarning() << this->m_path << "read error in waveform header"; break; } this->m_header.append(extra); header = (unsigned char *)this->m_header.data(); // Parse the fixed-length portion. this->interval_count = header[0x0f] | header[0x10] << 8; this->interval_seconds = header[0x11]; // not always 1 after all this->duration = this->interval_count * this->interval_seconds; // ??? the last entry doesn't always seem to be a full interval? quint8 wvfm_signals = header[0x12]; // Read the variable-length data + trailing byte. int ws_size = (this->fileVersion == 3) ? 4 : 3; int sbsize = wvfm_signals * ws_size + 1; extra = f.read(sbsize); if (extra.size() != sbsize) { qWarning() << this->m_path << "read error in waveform header 2"; break; } this->m_header.append(extra); header = (unsigned char *)this->m_header.data(); // Parse the variable-length waveform information. // TODO: move these checks into the parser, after the header checksum has been verified // For now just skip them for the one known sample with a bad checksum. if (this->sessionid == 268962649) return true; int pos = 0x13; for (int i = 0; i < wvfm_signals; ++i) { quint8 kind = header[pos]; CHECK_VALUE(kind, i); // always seems to range from 0...wvfm_signals-1, alert if not quint16 interleave = header[pos + 1] | header[pos + 2] << 8; // samples per interval if (this->fileVersion == 2) { this->waveformInfo.push_back(PRS1Waveform(interleave, kind)); pos += 3; } else if (this->fileVersion == 3) { int always_8 = header[pos + 3]; // sample size in bits? CHECK_VALUE(always_8, 8); this->waveformInfo.push_back(PRS1Waveform(interleave, kind)); pos += 4; } } // And the trailing byte, whatever it is. int always_0 = header[pos]; CHECK_VALUE(always_0, 0); ok = true; } while (false); return ok; } bool PRS1DataChunk::ReadData(RawDataDevice & f) { bool ok = false; do { // Read data block int data_size = this->blockSize - this->m_header.size(); if (data_size < 0) { qWarning() << this->m_path << "chunk size smaller than header"; break; } this->m_data = f.read(data_size); if (this->m_data.size() < data_size) { qWarning() << this->m_path << "less data in file than specified in header"; break; } // Extract the stored CRC from the data buffer and calculate the current CRC. if (this->fileVersion==3) { // The last 4 bytes contain a CRC32 checksum of the data. if (!ExtractStoredCrc(4)) { break; } this->calcCrc = CRC32wchar((unsigned char *)this->m_data.data(), this->m_data.size()); } else { // The last 2 bytes contain a CRC16 checksum of the data. if (!ExtractStoredCrc(2)) { break; } this->calcCrc = CRC16((unsigned char *)this->m_data.data(), this->m_data.size()); } ok = true; } while (false); return ok; } bool PRS1DataChunk::ExtractStoredCrc(int size) { // Make sure there's enough data for the CRC. int offset = this->m_data.size() - size; if (offset < 0) { qWarning() << this->m_path << "chunk truncated"; return false; } // Read the last 16- or 32-bit little-endian integer. quint32 storedCrc = 0; unsigned char* data = (unsigned char*)this->m_data.data(); for (int i=0; i < size; i++) { storedCrc |= data[offset+i] << (8*i); } this->storedCrc = storedCrc; // Drop the CRC from the data. this->m_data.chop(size); return true; } // CRC-16/KERMIT, polynomial: 0x11021, bit reverse algorithm // Table generated by crcmod (crc-kermit) typedef quint16 crc16_t; static crc16_t CRC16(unsigned char * data, size_t data_len, crc16_t crc) { static const crc16_t table[256] = { 0x0000U, 0x1189U, 0x2312U, 0x329bU, 0x4624U, 0x57adU, 0x6536U, 0x74bfU, 0x8c48U, 0x9dc1U, 0xaf5aU, 0xbed3U, 0xca6cU, 0xdbe5U, 0xe97eU, 0xf8f7U, 0x1081U, 0x0108U, 0x3393U, 0x221aU, 0x56a5U, 0x472cU, 0x75b7U, 0x643eU, 0x9cc9U, 0x8d40U, 0xbfdbU, 0xae52U, 0xdaedU, 0xcb64U, 0xf9ffU, 0xe876U, 0x2102U, 0x308bU, 0x0210U, 0x1399U, 0x6726U, 0x76afU, 0x4434U, 0x55bdU, 0xad4aU, 0xbcc3U, 0x8e58U, 0x9fd1U, 0xeb6eU, 0xfae7U, 0xc87cU, 0xd9f5U, 0x3183U, 0x200aU, 0x1291U, 0x0318U, 0x77a7U, 0x662eU, 0x54b5U, 0x453cU, 0xbdcbU, 0xac42U, 0x9ed9U, 0x8f50U, 0xfbefU, 0xea66U, 0xd8fdU, 0xc974U, 0x4204U, 0x538dU, 0x6116U, 0x709fU, 0x0420U, 0x15a9U, 0x2732U, 0x36bbU, 0xce4cU, 0xdfc5U, 0xed5eU, 0xfcd7U, 0x8868U, 0x99e1U, 0xab7aU, 0xbaf3U, 0x5285U, 0x430cU, 0x7197U, 0x601eU, 0x14a1U, 0x0528U, 0x37b3U, 0x263aU, 0xdecdU, 0xcf44U, 0xfddfU, 0xec56U, 0x98e9U, 0x8960U, 0xbbfbU, 0xaa72U, 0x6306U, 0x728fU, 0x4014U, 0x519dU, 0x2522U, 0x34abU, 0x0630U, 0x17b9U, 0xef4eU, 0xfec7U, 0xcc5cU, 0xddd5U, 0xa96aU, 0xb8e3U, 0x8a78U, 0x9bf1U, 0x7387U, 0x620eU, 0x5095U, 0x411cU, 0x35a3U, 0x242aU, 0x16b1U, 0x0738U, 0xffcfU, 0xee46U, 0xdcddU, 0xcd54U, 0xb9ebU, 0xa862U, 0x9af9U, 0x8b70U, 0x8408U, 0x9581U, 0xa71aU, 0xb693U, 0xc22cU, 0xd3a5U, 0xe13eU, 0xf0b7U, 0x0840U, 0x19c9U, 0x2b52U, 0x3adbU, 0x4e64U, 0x5fedU, 0x6d76U, 0x7cffU, 0x9489U, 0x8500U, 0xb79bU, 0xa612U, 0xd2adU, 0xc324U, 0xf1bfU, 0xe036U, 0x18c1U, 0x0948U, 0x3bd3U, 0x2a5aU, 0x5ee5U, 0x4f6cU, 0x7df7U, 0x6c7eU, 0xa50aU, 0xb483U, 0x8618U, 0x9791U, 0xe32eU, 0xf2a7U, 0xc03cU, 0xd1b5U, 0x2942U, 0x38cbU, 0x0a50U, 0x1bd9U, 0x6f66U, 0x7eefU, 0x4c74U, 0x5dfdU, 0xb58bU, 0xa402U, 0x9699U, 0x8710U, 0xf3afU, 0xe226U, 0xd0bdU, 0xc134U, 0x39c3U, 0x284aU, 0x1ad1U, 0x0b58U, 0x7fe7U, 0x6e6eU, 0x5cf5U, 0x4d7cU, 0xc60cU, 0xd785U, 0xe51eU, 0xf497U, 0x8028U, 0x91a1U, 0xa33aU, 0xb2b3U, 0x4a44U, 0x5bcdU, 0x6956U, 0x78dfU, 0x0c60U, 0x1de9U, 0x2f72U, 0x3efbU, 0xd68dU, 0xc704U, 0xf59fU, 0xe416U, 0x90a9U, 0x8120U, 0xb3bbU, 0xa232U, 0x5ac5U, 0x4b4cU, 0x79d7U, 0x685eU, 0x1ce1U, 0x0d68U, 0x3ff3U, 0x2e7aU, 0xe70eU, 0xf687U, 0xc41cU, 0xd595U, 0xa12aU, 0xb0a3U, 0x8238U, 0x93b1U, 0x6b46U, 0x7acfU, 0x4854U, 0x59ddU, 0x2d62U, 0x3cebU, 0x0e70U, 0x1ff9U, 0xf78fU, 0xe606U, 0xd49dU, 0xc514U, 0xb1abU, 0xa022U, 0x92b9U, 0x8330U, 0x7bc7U, 0x6a4eU, 0x58d5U, 0x495cU, 0x3de3U, 0x2c6aU, 0x1ef1U, 0x0f78U, }; for (size_t i=0; i < data_len; i++) { crc = table[(*data ^ (unsigned char)crc) & 0xFF] ^ (crc >> 8); data++; } return crc; } // CRC-32/MPEG-2, polynomial: 0x104C11DB7 // Table generated by crcmod (crc-32-mpeg) static crc32_t CRC32(const unsigned char *data, size_t data_len, crc32_t crc) { static const crc32_t table[256] = { 0x00000000U, 0x04c11db7U, 0x09823b6eU, 0x0d4326d9U, 0x130476dcU, 0x17c56b6bU, 0x1a864db2U, 0x1e475005U, 0x2608edb8U, 0x22c9f00fU, 0x2f8ad6d6U, 0x2b4bcb61U, 0x350c9b64U, 0x31cd86d3U, 0x3c8ea00aU, 0x384fbdbdU, 0x4c11db70U, 0x48d0c6c7U, 0x4593e01eU, 0x4152fda9U, 0x5f15adacU, 0x5bd4b01bU, 0x569796c2U, 0x52568b75U, 0x6a1936c8U, 0x6ed82b7fU, 0x639b0da6U, 0x675a1011U, 0x791d4014U, 0x7ddc5da3U, 0x709f7b7aU, 0x745e66cdU, 0x9823b6e0U, 0x9ce2ab57U, 0x91a18d8eU, 0x95609039U, 0x8b27c03cU, 0x8fe6dd8bU, 0x82a5fb52U, 0x8664e6e5U, 0xbe2b5b58U, 0xbaea46efU, 0xb7a96036U, 0xb3687d81U, 0xad2f2d84U, 0xa9ee3033U, 0xa4ad16eaU, 0xa06c0b5dU, 0xd4326d90U, 0xd0f37027U, 0xddb056feU, 0xd9714b49U, 0xc7361b4cU, 0xc3f706fbU, 0xceb42022U, 0xca753d95U, 0xf23a8028U, 0xf6fb9d9fU, 0xfbb8bb46U, 0xff79a6f1U, 0xe13ef6f4U, 0xe5ffeb43U, 0xe8bccd9aU, 0xec7dd02dU, 0x34867077U, 0x30476dc0U, 0x3d044b19U, 0x39c556aeU, 0x278206abU, 0x23431b1cU, 0x2e003dc5U, 0x2ac12072U, 0x128e9dcfU, 0x164f8078U, 0x1b0ca6a1U, 0x1fcdbb16U, 0x018aeb13U, 0x054bf6a4U, 0x0808d07dU, 0x0cc9cdcaU, 0x7897ab07U, 0x7c56b6b0U, 0x71159069U, 0x75d48ddeU, 0x6b93dddbU, 0x6f52c06cU, 0x6211e6b5U, 0x66d0fb02U, 0x5e9f46bfU, 0x5a5e5b08U, 0x571d7dd1U, 0x53dc6066U, 0x4d9b3063U, 0x495a2dd4U, 0x44190b0dU, 0x40d816baU, 0xaca5c697U, 0xa864db20U, 0xa527fdf9U, 0xa1e6e04eU, 0xbfa1b04bU, 0xbb60adfcU, 0xb6238b25U, 0xb2e29692U, 0x8aad2b2fU, 0x8e6c3698U, 0x832f1041U, 0x87ee0df6U, 0x99a95df3U, 0x9d684044U, 0x902b669dU, 0x94ea7b2aU, 0xe0b41de7U, 0xe4750050U, 0xe9362689U, 0xedf73b3eU, 0xf3b06b3bU, 0xf771768cU, 0xfa325055U, 0xfef34de2U, 0xc6bcf05fU, 0xc27dede8U, 0xcf3ecb31U, 0xcbffd686U, 0xd5b88683U, 0xd1799b34U, 0xdc3abdedU, 0xd8fba05aU, 0x690ce0eeU, 0x6dcdfd59U, 0x608edb80U, 0x644fc637U, 0x7a089632U, 0x7ec98b85U, 0x738aad5cU, 0x774bb0ebU, 0x4f040d56U, 0x4bc510e1U, 0x46863638U, 0x42472b8fU, 0x5c007b8aU, 0x58c1663dU, 0x558240e4U, 0x51435d53U, 0x251d3b9eU, 0x21dc2629U, 0x2c9f00f0U, 0x285e1d47U, 0x36194d42U, 0x32d850f5U, 0x3f9b762cU, 0x3b5a6b9bU, 0x0315d626U, 0x07d4cb91U, 0x0a97ed48U, 0x0e56f0ffU, 0x1011a0faU, 0x14d0bd4dU, 0x19939b94U, 0x1d528623U, 0xf12f560eU, 0xf5ee4bb9U, 0xf8ad6d60U, 0xfc6c70d7U, 0xe22b20d2U, 0xe6ea3d65U, 0xeba91bbcU, 0xef68060bU, 0xd727bbb6U, 0xd3e6a601U, 0xdea580d8U, 0xda649d6fU, 0xc423cd6aU, 0xc0e2d0ddU, 0xcda1f604U, 0xc960ebb3U, 0xbd3e8d7eU, 0xb9ff90c9U, 0xb4bcb610U, 0xb07daba7U, 0xae3afba2U, 0xaafbe615U, 0xa7b8c0ccU, 0xa379dd7bU, 0x9b3660c6U, 0x9ff77d71U, 0x92b45ba8U, 0x9675461fU, 0x8832161aU, 0x8cf30badU, 0x81b02d74U, 0x857130c3U, 0x5d8a9099U, 0x594b8d2eU, 0x5408abf7U, 0x50c9b640U, 0x4e8ee645U, 0x4a4ffbf2U, 0x470cdd2bU, 0x43cdc09cU, 0x7b827d21U, 0x7f436096U, 0x7200464fU, 0x76c15bf8U, 0x68860bfdU, 0x6c47164aU, 0x61043093U, 0x65c52d24U, 0x119b4be9U, 0x155a565eU, 0x18197087U, 0x1cd86d30U, 0x029f3d35U, 0x065e2082U, 0x0b1d065bU, 0x0fdc1becU, 0x3793a651U, 0x3352bbe6U, 0x3e119d3fU, 0x3ad08088U, 0x2497d08dU, 0x2056cd3aU, 0x2d15ebe3U, 0x29d4f654U, 0xc5a92679U, 0xc1683bceU, 0xcc2b1d17U, 0xc8ea00a0U, 0xd6ad50a5U, 0xd26c4d12U, 0xdf2f6bcbU, 0xdbee767cU, 0xe3a1cbc1U, 0xe760d676U, 0xea23f0afU, 0xeee2ed18U, 0xf0a5bd1dU, 0xf464a0aaU, 0xf9278673U, 0xfde69bc4U, 0x89b8fd09U, 0x8d79e0beU, 0x803ac667U, 0x84fbdbd0U, 0x9abc8bd5U, 0x9e7d9662U, 0x933eb0bbU, 0x97ffad0cU, 0xafb010b1U, 0xab710d06U, 0xa6322bdfU, 0xa2f33668U, 0xbcb4666dU, 0xb8757bdaU, 0xb5365d03U, 0xb1f740b4U, }; for (size_t i=0; i < data_len; i++) { crc = table[(*data ^ (unsigned char)(crc >> 24)) & 0xFF] ^ (crc << 8); data++; } return crc; } // Strangely, the PRS1 CRC32 appears to consider every byte a 32-bit wchar_t. // Nothing like trying a bunch of encodings and CRC32 variants on PROP.TXT files // until you find a winner. static crc32_t CRC32wchar(const unsigned char *data, size_t data_len, crc32_t crc) { for (size_t i=0; i < data_len; i++) { static unsigned char wch[4] = { 0, 0, 0, 0 }; wch[3] = *data++; crc = CRC32(wch, 4, crc); } return crc; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/prs1_parser.h000066400000000000000000000556641417327530600237400ustar00rootroot00000000000000/* SleepLib PRS1 Loader Parser Header * * Copyright (c) 2019-2022 The OSCAR Team * Portions copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef PRS1PARSER_H #define PRS1PARSER_H #include #include #include "SleepLib/session.h" // Include and configure the CHECK_VALUE and UNEXPECTED_VALUE macros. #include "SleepLib/importcontext.h" #define IMPORT_CTX loader->context() #define SESSIONID this->sessionid //******************************************************************************************** // MARK: - // MARK: Internal PRS1 parsed data types //******************************************************************************************** // For new events, add an enum here and then a class below with an PRS1_*_EVENT macro enum PRS1ParsedEventType { EV_PRS1_RAW = -1, // these only get logged EV_PRS1_UNKNOWN = 0, // these have their value graphed EV_PRS1_TB, EV_PRS1_OA, EV_PRS1_CA, EV_PRS1_FL, EV_PRS1_PB, EV_PRS1_LL, EV_PRS1_VB, // UNCONFIRMED EV_PRS1_HY, EV_PRS1_OA_COUNT, // F3V3 only EV_PRS1_CA_COUNT, // F3V3 only EV_PRS1_HY_COUNT, // F3V3 only EV_PRS1_TOTLEAK, EV_PRS1_LEAK, // unintentional leak EV_PRS1_AUTO_PRESSURE_SET, EV_PRS1_PRESSURE_SET, EV_PRS1_IPAP_SET, EV_PRS1_EPAP_SET, EV_PRS1_PRESSURE_AVG, EV_PRS1_FLEX_PRESSURE_AVG, EV_PRS1_IPAP_AVG, EV_PRS1_IPAPLOW, EV_PRS1_IPAPHIGH, EV_PRS1_EPAP_AVG, EV_PRS1_RR, EV_PRS1_PTB, EV_PRS1_MV, EV_PRS1_TV, EV_PRS1_SNORE, EV_PRS1_VS, EV_PRS1_PP, EV_PRS1_RERA, EV_PRS1_FLOWRATE, EV_PRS1_TEST1, EV_PRS1_TEST2, EV_PRS1_SETTING, EV_PRS1_SLICE, EV_PRS1_DISCONNECT_ALARM, EV_PRS1_APNEA_ALARM, EV_PRS1_LOW_MV_ALARM, EV_PRS1_SNORES_AT_PRESSURE, EV_PRS1_INTERVAL_BOUNDARY, // An artificial internal-only event used to separate stat intervals }; enum PRS1ParsedEventUnit { PRS1_UNIT_NONE, PRS1_UNIT_CMH2O, PRS1_UNIT_ML, PRS1_UNIT_S, }; enum PRS1ParsedSettingType { PRS1_SETTING_CPAP_MODE, PRS1_SETTING_AUTO_TRIAL, PRS1_SETTING_PRESSURE, PRS1_SETTING_PRESSURE_MIN, PRS1_SETTING_PRESSURE_MAX, PRS1_SETTING_EPAP, PRS1_SETTING_EPAP_MIN, PRS1_SETTING_EPAP_MAX, PRS1_SETTING_IPAP, PRS1_SETTING_IPAP_MIN, PRS1_SETTING_IPAP_MAX, PRS1_SETTING_PS, PRS1_SETTING_PS_MIN, PRS1_SETTING_PS_MAX, PRS1_SETTING_BACKUP_BREATH_MODE, PRS1_SETTING_BACKUP_BREATH_RATE, PRS1_SETTING_BACKUP_TIMED_INSPIRATION, PRS1_SETTING_TIDAL_VOLUME, PRS1_SETTING_EZ_START, PRS1_SETTING_FLEX_LOCK, PRS1_SETTING_FLEX_MODE, PRS1_SETTING_FLEX_LEVEL, PRS1_SETTING_RISE_TIME, PRS1_SETTING_RISE_TIME_LOCK, PRS1_SETTING_RAMP_TYPE, PRS1_SETTING_RAMP_TIME, PRS1_SETTING_RAMP_PRESSURE, PRS1_SETTING_HUMID_STATUS, PRS1_SETTING_HUMID_MODE, PRS1_SETTING_HEATED_TUBE_TEMP, PRS1_SETTING_HUMID_LEVEL, PRS1_SETTING_MASK_RESIST_LOCK, PRS1_SETTING_MASK_RESIST_SETTING, PRS1_SETTING_HOSE_DIAMETER, PRS1_SETTING_TUBING_LOCK, PRS1_SETTING_AUTO_ON, PRS1_SETTING_AUTO_OFF, PRS1_SETTING_APNEA_ALARM, PRS1_SETTING_DISCONNECT_ALARM, // Is this any different from mask alert? PRS1_SETTING_LOW_MV_ALARM, PRS1_SETTING_LOW_TV_ALARM, PRS1_SETTING_MASK_ALERT, PRS1_SETTING_SHOW_AHI, PRS1_SETTING_HUMID_TARGET_TIME, }; class PRS1ParsedEvent { public: PRS1ParsedEventType m_type; int m_start; // seconds relative to chunk timestamp at which this event began int m_duration; int m_value; float m_offset; float m_gain; PRS1ParsedEventUnit m_unit; inline float value(void) const { return (m_value * m_gain) + m_offset; } static const PRS1ParsedEventType TYPE = EV_PRS1_UNKNOWN; static constexpr float GAIN = 1.0; static const PRS1ParsedEventUnit UNIT = PRS1_UNIT_NONE; QString typeName(void) const; virtual QMap contents(void) = 0; protected: PRS1ParsedEvent(PRS1ParsedEventType type, int start) : m_type(type), m_start(start), m_duration(0), m_value(0), m_offset(0.0), m_gain(GAIN), m_unit(UNIT) { } static QString timeStr(int t); public: virtual ~PRS1ParsedEvent() { } }; class PRS1IntervalBoundaryEvent : public PRS1ParsedEvent { public: virtual QMap contents(void); static const PRS1ParsedEventType TYPE = EV_PRS1_INTERVAL_BOUNDARY; PRS1IntervalBoundaryEvent(int start) : PRS1ParsedEvent(TYPE, start) {} }; class PRS1ParsedDurationEvent : public PRS1ParsedEvent { public: virtual QMap contents(void); static const PRS1ParsedEventUnit UNIT = PRS1_UNIT_S; PRS1ParsedDurationEvent(PRS1ParsedEventType type, int start, int duration) : PRS1ParsedEvent(type, start) { m_duration = duration; } }; class PRS1ParsedValueEvent : public PRS1ParsedEvent { public: virtual QMap contents(void); protected: PRS1ParsedValueEvent(PRS1ParsedEventType type, int start, int value) : PRS1ParsedEvent(type, start) { m_value = value; } }; /* class PRS1UnknownValueEvent : public PRS1ParsedValueEvent { public: virtual QMap contents(void) { QMap out; out["start"] = timeStr(m_start); out["code"] = hex(m_code); out["value"] = QString::number(value()); return out; } int m_code; PRS1UnknownValueEvent(int code, int start, int value, float gain=1.0) : PRS1ParsedValueEvent(TYPE, start, value), m_code(code) { m_gain = gain; } }; */ class PRS1UnknownDataEvent : public PRS1ParsedEvent { public: virtual QMap contents(void); static const PRS1ParsedEventType TYPE = EV_PRS1_RAW; int m_pos; unsigned char m_code; QByteArray m_data; PRS1UnknownDataEvent(const QByteArray & data, int pos, int len=18) : PRS1ParsedEvent(TYPE, 0) { m_pos = pos; m_data = data.mid(pos, len); Q_ASSERT(m_data.size() >= 1); m_code = m_data.at(0); } }; class PRS1PressureEvent : public PRS1ParsedValueEvent { public: static constexpr float GAIN = 0.1; static const PRS1ParsedEventUnit UNIT = PRS1_UNIT_CMH2O; PRS1PressureEvent(PRS1ParsedEventType type, int start, int value, float gain=GAIN) : PRS1ParsedValueEvent(type, start, value) { m_gain = gain; m_unit = UNIT; } }; class PRS1TidalVolumeEvent : public PRS1ParsedValueEvent { public: static const PRS1ParsedEventType TYPE = EV_PRS1_TV; static constexpr float GAIN = 10.0; static const PRS1ParsedEventUnit UNIT = PRS1_UNIT_ML; PRS1TidalVolumeEvent(int start, int value) : PRS1ParsedValueEvent(TYPE, start, value) { m_gain = GAIN; m_unit = UNIT; } }; class PRS1ParsedSettingEvent : public PRS1ParsedValueEvent { public: virtual QMap contents(void); static const PRS1ParsedEventType TYPE = EV_PRS1_SETTING; PRS1ParsedSettingType m_setting; PRS1ParsedSettingEvent(PRS1ParsedSettingType setting, int value) : PRS1ParsedValueEvent(TYPE, 0, value), m_setting(setting) {} protected: QString settingName(void) const; QString modeName(void) const; }; class PRS1ScaledSettingEvent : public PRS1ParsedSettingEvent { public: PRS1ScaledSettingEvent(PRS1ParsedSettingType setting, int value, float gain) : PRS1ParsedSettingEvent(setting, value) { m_gain = gain; } }; class PRS1PressureSettingEvent : public PRS1ScaledSettingEvent { public: static constexpr float GAIN = PRS1PressureEvent::GAIN; static const PRS1ParsedEventUnit UNIT = PRS1PressureEvent::UNIT; PRS1PressureSettingEvent(PRS1ParsedSettingType setting, int value, float gain=GAIN) : PRS1ScaledSettingEvent(setting, value, gain) { m_unit = UNIT; } }; class PRS1ParsedSliceEvent : public PRS1ParsedValueEvent { public: virtual QMap contents(void); static const PRS1ParsedEventType TYPE = EV_PRS1_SLICE; PRS1ParsedSliceEvent(int start, SliceStatus status) : PRS1ParsedValueEvent(TYPE, start, (int) status) {} }; class PRS1ParsedAlarmEvent : public PRS1ParsedEvent { public: virtual QMap contents(void); protected: PRS1ParsedAlarmEvent(PRS1ParsedEventType type, int start, int /*unused*/) : PRS1ParsedEvent(type, start) {} }; class PRS1SnoresAtPressureEvent : public PRS1PressureEvent { public: static const PRS1ParsedEventType TYPE = EV_PRS1_SNORES_AT_PRESSURE; PRS1SnoresAtPressureEvent(int start, int kind, int pressure, int count, float gain=GAIN) : PRS1PressureEvent(TYPE, start, pressure, gain) { m_kind = kind; m_count = count; } virtual QMap contents(void); protected: int m_kind; // m_value is pressure int m_count; }; #define _PRS1_EVENT(T, E, P, ARG) \ class T : public P \ { \ public: \ static const PRS1ParsedEventType TYPE = E; \ T(int start, int ARG) : P(TYPE, start, ARG) {} \ }; #define PRS1_DURATION_EVENT(T, E) _PRS1_EVENT(T, E, PRS1ParsedDurationEvent, duration) #define PRS1_VALUE_EVENT(T, E) _PRS1_EVENT(T, E, PRS1ParsedValueEvent, value) #define PRS1_ALARM_EVENT(T, E) _PRS1_EVENT(T, E, PRS1ParsedAlarmEvent, value) #define PRS1_PRESSURE_EVENT(T, E) \ class T : public PRS1PressureEvent \ { \ public: \ static const PRS1ParsedEventType TYPE = E; \ T(int start, int value, float gain=PRS1PressureEvent::GAIN) : PRS1PressureEvent(TYPE, start, value, gain) {} \ }; PRS1_DURATION_EVENT(PRS1TimedBreathEvent, EV_PRS1_TB); PRS1_DURATION_EVENT(PRS1ObstructiveApneaEvent, EV_PRS1_OA); PRS1_DURATION_EVENT(PRS1ClearAirwayEvent, EV_PRS1_CA); PRS1_DURATION_EVENT(PRS1FlowLimitationEvent, EV_PRS1_FL); PRS1_DURATION_EVENT(PRS1PeriodicBreathingEvent, EV_PRS1_PB); PRS1_DURATION_EVENT(PRS1LargeLeakEvent, EV_PRS1_LL); PRS1_DURATION_EVENT(PRS1VariableBreathingEvent, EV_PRS1_VB); PRS1_DURATION_EVENT(PRS1HypopneaEvent, EV_PRS1_HY); PRS1_VALUE_EVENT(PRS1TotalLeakEvent, EV_PRS1_TOTLEAK); PRS1_VALUE_EVENT(PRS1LeakEvent, EV_PRS1_LEAK); PRS1_PRESSURE_EVENT(PRS1AutoPressureSetEvent, EV_PRS1_AUTO_PRESSURE_SET); PRS1_PRESSURE_EVENT(PRS1PressureSetEvent, EV_PRS1_PRESSURE_SET); PRS1_PRESSURE_EVENT(PRS1IPAPSetEvent, EV_PRS1_IPAP_SET); PRS1_PRESSURE_EVENT(PRS1EPAPSetEvent, EV_PRS1_EPAP_SET); PRS1_PRESSURE_EVENT(PRS1PressureAverageEvent, EV_PRS1_PRESSURE_AVG); PRS1_PRESSURE_EVENT(PRS1FlexPressureAverageEvent, EV_PRS1_FLEX_PRESSURE_AVG); PRS1_PRESSURE_EVENT(PRS1IPAPAverageEvent, EV_PRS1_IPAP_AVG); PRS1_PRESSURE_EVENT(PRS1IPAPHighEvent, EV_PRS1_IPAPHIGH); PRS1_PRESSURE_EVENT(PRS1IPAPLowEvent, EV_PRS1_IPAPLOW); PRS1_PRESSURE_EVENT(PRS1EPAPAverageEvent, EV_PRS1_EPAP_AVG); PRS1_VALUE_EVENT(PRS1RespiratoryRateEvent, EV_PRS1_RR); PRS1_VALUE_EVENT(PRS1PatientTriggeredBreathsEvent, EV_PRS1_PTB); PRS1_VALUE_EVENT(PRS1MinuteVentilationEvent, EV_PRS1_MV); PRS1_VALUE_EVENT(PRS1SnoreEvent, EV_PRS1_SNORE); PRS1_VALUE_EVENT(PRS1VibratorySnoreEvent, EV_PRS1_VS); PRS1_VALUE_EVENT(PRS1PressurePulseEvent, EV_PRS1_PP); PRS1_VALUE_EVENT(PRS1RERAEvent, EV_PRS1_RERA); // TODO: should this really be a duration event? PRS1_VALUE_EVENT(PRS1FlowRateEvent, EV_PRS1_FLOWRATE); // TODO: is this a single event or an index/hour? PRS1_VALUE_EVENT(PRS1Test1Event, EV_PRS1_TEST1); PRS1_VALUE_EVENT(PRS1Test2Event, EV_PRS1_TEST2); PRS1_VALUE_EVENT(PRS1HypopneaCount, EV_PRS1_HY_COUNT); // F3V3 only PRS1_VALUE_EVENT(PRS1ClearAirwayCount, EV_PRS1_CA_COUNT); // F3V3 only PRS1_VALUE_EVENT(PRS1ObstructiveApneaCount, EV_PRS1_OA_COUNT); // F3V3 only PRS1_ALARM_EVENT(PRS1DisconnectAlarmEvent, EV_PRS1_DISCONNECT_ALARM); PRS1_ALARM_EVENT(PRS1ApneaAlarmEvent, EV_PRS1_APNEA_ALARM); PRS1_ALARM_EVENT(PRS1LowMinuteVentilationAlarmEvent, EV_PRS1_LOW_MV_ALARM); enum PRS1Mode { PRS1_MODE_UNKNOWN = -1, PRS1_MODE_CPAPCHECK = 0, // "CPAP-Check" PRS1_MODE_CPAP, // "CPAP" PRS1_MODE_AUTOCPAP, // "AutoCPAP" PRS1_MODE_AUTOTRIAL, // "Auto-Trial" PRS1_MODE_BILEVEL, // "Bi-Level" PRS1_MODE_AUTOBILEVEL, // "AutoBiLevel" PRS1_MODE_ASV, // "ASV" PRS1_MODE_S, // "S" PRS1_MODE_ST, // "S/T" PRS1_MODE_PC, // "PC" PRS1_MODE_ST_AVAPS, // "S/T - AVAPS" PRS1_MODE_PC_AVAPS, // "PC - AVAPS" }; // Returns the set of all channels ever reported/supported by the parser for the given chunk. const QVector & GetSupportedEvents(const class PRS1DataChunk* chunk); //******************************************************************************************** // MARK: - //******************************************************************************************** struct PRS1Waveform; /*! \class PRS1DataChunk * \brief Representing a chunk of event/summary/waveform data after the header is parsed. */ class PRS1DataChunk { public: /* PRS1DataChunk() { fileVersion = 0; blockSize = 0; htype = 0; family = 0; familyVersion = 0; ext = 255; sessionid = 0; timestamp = 0; duration = 0; m_filepos = -1; m_index = -1; } */ PRS1DataChunk(class RawDataDevice & f, class PRS1Loader* loader); ~PRS1DataChunk(); inline int size() const { return m_data.size(); } QByteArray m_header; QByteArray m_data; QByteArray m_headerblock; QList m_parsedData; QString m_path; qint64 m_filepos; // file offset int m_index; // nth chunk in file inline void SetIndex(int index) { m_index = index; } // Common fields quint8 fileVersion; quint16 blockSize; quint8 htype; quint8 family; quint8 familyVersion; quint8 ext; SessionID sessionid; quint32 timestamp; // Waveform-specific fields quint16 interval_count; quint8 interval_seconds; int duration; QList waveformInfo; // V3 normal/non-waveform fields QMap hblock; QMap mainblock; QMap hbdata; // Trailing common fields quint8 storedChecksum; // header checksum stored in file, last byte of m_header quint8 calcChecksum; // header checksum as calculated when parsing quint32 storedCrc; // header + data CRC stored in file, last 2-4 bytes of chunk quint32 calcCrc; // header + data CRC as calculated when parsing //! \brief Calculate a simplistic hash to check whether two chunks are identical. inline quint64 hash(void) const { return ((((quint64) this->calcCrc) << 32) | this->timestamp); } //! \brief Parse and return the next chunk from a PRS1 file static PRS1DataChunk* ParseNext(class RawDataDevice & f, class PRS1Loader* loader); //! \brief Read and parse the next chunk header from a PRS1 file bool ReadHeader(class RawDataDevice & f); //! \brief Read the chunk's data from a PRS1 file and calculate its CRC, must be called after ReadHeader bool ReadData(class RawDataDevice & f); //! \brief Figures out which Compliance Parser to call, based on machine family/version and calls it. bool ParseCompliance(void); //! \brief Parse a single data chunk from a .000 file containing compliance data for a P25x brick bool ParseComplianceF0V23(void); //! \brief Parse a single data chunk from a .000 file containing compliance data for a P256x brick bool ParseComplianceF0V4(void); //! \brief Parse a single data chunk from a .000 file containing compliance data for a x00V brick bool ParseComplianceF0V5(void); //! \brief Parse a single data chunk from a .000 file containing compliance data for a DreamStation 200X brick bool ParseComplianceF0V6(void); //! \brief Figures out which Summary Parser to call, based on machine family/version and calls it. bool ParseSummary(); //! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 2 or 3 machine bool ParseSummaryF0V23(void); //! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 4 machine bool ParseSummaryF0V4(void); //! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 6 machine bool ParseSummaryF0V6(void); //! \brief Parse a single data chunk from a .001 file containing summary data for a family 3 ventilator (family version 0 or 3) machine bool ParseSummaryF3V03(void); //! \brief Parse a single data chunk from a .001 file containing summary data for a family 3 ventilator (family version 6) machine bool ParseSummaryF3V6(void); //! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 0-2 machine bool ParseSummaryF5V012(void); //! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 3 machine bool ParseSummaryF5V3(void); //! \brief Parse a flex setting byte from a .000 or .001 containing compliance/summary data for CPAP/APAP family versions 2, 3, 4, or 5 void ParseFlexSettingF0V2345(quint8 flex, int prs1mode); //! \brief Parse a flex setting byte from a .000 or .001 containing compliance/summary data for ASV family versions 0, 1, or 2 void ParseFlexSettingF5V012(quint8 flex, int prs1mode); //! \brief Parse an humidifier setting byte from a .000 or .001 containing compliance/summary data for original System One (50-Series) machines: F0V23 and F5V0 void ParseHumidifierSetting50Series(int humid, bool add_setting=false); //! \brief Parse an humidifier setting byte from a .000 or .001 containing compliance/summary data for F0V4 and F5V012 (60-Series) machines void ParseHumidifierSetting60Series(unsigned char humid1, unsigned char humid2, bool add_setting=false); //! \brief Parse an humidifier setting byte from a .000 or .001 containing compliance/summary data for F3V3 machines (differs from other 60-Series machines) void ParseHumidifierSettingF3V3(unsigned char humid1, unsigned char humid2, bool add_setting=false); //! \brief Parse humidifier setting bytes from a .000 or .001 containing compliance/summary data for fileversion 3 machines void ParseHumidifierSettingV3(unsigned char byte1, unsigned char byte2, bool add_setting=false); //! \brief Parse tubing type from a .001 containing summary data for fileversion 3 machines void ParseTubingTypeV3(unsigned char type); //! \brief Figures out which Event Parser to call, based on machine family/version and calls it. bool ParseEvents(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 0 CPAP/APAP machine bool ParseEventsF0V23(void); //! \brief Parse a single data chunk from a .002 file containing event data for a 60 Series family 0 CPAP/APAP 60machine bool ParseEventsF0V4(void); //! \brief Parse a single data chunk from a .002 file containing event data for a DreamStation family 0 CPAP/APAP machine bool ParseEventsF0V6(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 3 ventilator family version 0 or 3 machine bool ParseEventsF3V03(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 3 ventilator family version 6 machine bool ParseEventsF3V6(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 0 machine bool ParseEventsF5V0(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 1 machine bool ParseEventsF5V1(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 2 machine bool ParseEventsF5V2(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 3 machine bool ParseEventsF5V3(void); protected: class PRS1Loader* loader; //! \brief Add a parsed event to the chunk void AddEvent(class PRS1ParsedEvent* event); //! \brief Read and parse the non-waveform header data from a V2 PRS1 file bool ReadNormalHeaderV2(class RawDataDevice & f); //! \brief Read and parse the non-waveform header data from a V3 PRS1 file bool ReadNormalHeaderV3(class RawDataDevice & f); //! \brief Read and parse the waveform-specific header data from a PRS1 file bool ReadWaveformHeader(class RawDataDevice & f); //! \brief Extract the stored CRC from the end of the data of a PRS1 chunk bool ExtractStoredCrc(int size); //! \brief Parse a settings slice from a .000 and .001 file bool ParseSettingsF0V23(const unsigned char* data, int size); //! \brief Parse a settings slice from a .000 and .001 file bool ParseSettingsF0V45(const unsigned char* data, int size); //! \brief Parse a settings slice from a .000 and .001 file bool ParseSettingsF0V6(const unsigned char* data, int size); //! \brief Parse a settings slice from a .000 and .001 file bool ParseSettingsF5V012(const unsigned char* data, int size); //! \brief Parse a settings slice from a .000 and .001 file bool ParseSettingsF5V3(const unsigned char* data, int size); //! \brief Parse a settings slice from a .000 and .001 file bool ParseSettingsF3V03(const unsigned char* data, int size); //! \brief Parse a settings slice from a .000 and .001 file bool ParseSettingsF3V6(const unsigned char* data, int size); protected: QString DumpEvent(int t, int code, const unsigned char* data, int size); }; #define DUMP_EVENT() qWarning() << this->sessionid << DumpEvent(t, code, data + pos, size - (pos - startpos)) + " @ " + QString("0x") + QString::number(startpos-1, 16).toUpper() enum FlexMode { FLEX_None, FLEX_CFlex, FLEX_CFlexPlus, FLEX_AFlex, FLEX_RiseTime, FLEX_BiFlex, FLEX_PFlex, FLEX_Flex, FLEX_Unknown = -1 }; enum BackupBreathMode { PRS1Backup_Off, PRS1Backup_Auto, PRS1Backup_Fixed }; enum HumidMode { HUMID_Fixed, HUMID_Adaptive, HUMID_HeatedTube, HUMID_Passover, HUMID_Error }; const int PRS1_HTYPE_NORMAL=0; const int PRS1_HTYPE_INTERVAL=1; extern const QVector ParsedEventsF0V23; extern const QVector ParsedEventsF0V4; extern const QVector ParsedEventsF0V6; extern const QVector ParsedEventsF3V0; extern const QVector ParsedEventsF3V3; extern const QVector ParsedEventsF3V6; extern const QVector ParsedEventsF5V0; extern const QVector ParsedEventsF5V1; extern const QVector ParsedEventsF5V2; extern const QVector ParsedEventsF5V3; #endif // PRS1PARSER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/prs1_parser_asv.cpp000066400000000000000000002223641417327530600251350ustar00rootroot00000000000000/* PRS1 Parsing for BiPAP autoSV (ASV) (Family 5) * * Copyright (c) 2019-2022 The OSCAR Team * Portions copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "prs1_parser.h" #include "prs1_loader.h" static QString hex(int i) { return QString("0x") + QString::number(i, 16).toUpper(); } //******************************************************************************************** // MARK: - // MARK: 50 and 60 Series // borrowed largely from ParseSummaryF0V4 bool PRS1DataChunk::ParseSummaryF5V012(void) { if (this->family != 5 || (this->familyVersion > 2)) { qWarning() << "ParseSummaryF5V012 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); QVector minimum_sizes; switch (this->familyVersion) { case 0: minimum_sizes = { 0x12, 4, 3, 0x1f, 0, 4, 0, 2, 2 }; break; case 1: minimum_sizes = { 0x13, 7, 5, 0x20, 0, 4, 0, 2, 2, 4 }; break; case 2: minimum_sizes = { 0x13, 7, 5, 0x22, 0, 4, 0, 2, 2, 4 }; break; } // NOTE: These are fixed sizes, but are called minimum to more closely match the F0V6 parser. bool ok = true; int pos = 0; int code, size; int tt = 0; while (ok && pos < chunk_size) { code = data[pos++]; // There is no hblock prior to F0V6. size = 0; if (code < minimum_sizes.length()) { // make sure the handlers below don't go past the end of the buffer size = minimum_sizes[code]; } else { // We can't defer warning until later, because F5V0 doesn't have slice 9. UNEXPECTED_VALUE(code, "known slice code"); ok = false; // unlike F0V6, we don't know the size of unknown slices, so we can't recover break; } if (pos + size > chunk_size) { qWarning() << this->sessionid << "slice" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } switch (code) { case 0: // Equipment On CHECK_VALUE(pos, 1); // Always first /* CHECK_VALUE(data[pos] & 0xF0, 0); // TODO: what are these? if ((data[pos] & 0x0F) != 1) { // This is the most frequent value. //CHECK_VALUES(data[pos] & 0x0F, 3, 5); // TODO: what are these? 0 seems to be related to errors. } */ // F5V012 doesn't have a separate settings record like F5V3 does, the settings just follow the EquipmentOn data. ok = this->ParseSettingsF5V012(data, size); /* CHECK_VALUE(data[pos+0x11], 0); CHECK_VALUE(data[pos+0x12], 0); CHECK_VALUE(data[pos+0x13], 0); CHECK_VALUE(data[pos+0x14], 0); CHECK_VALUE(data[pos+0x15], 0); CHECK_VALUE(data[pos+0x16], 0); CHECK_VALUE(data[pos+0x17], 0); */ break; case 2: // Mask On tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn)); /* //CHECK_VALUES(data[pos+2], 120, 110); // probably initial pressure //CHECK_VALUE(data[pos+3], 0); // initial IPAP on bilevel? //CHECK_VALUES(data[pos+4], 0, 130); // minimum pressure in auto-cpap this->ParseHumidifierSetting60Series(data[pos+5], data[pos+6]); */ break; case 3: // Mask Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff)); // F5V012 doesn't have a separate stats record like F5V3 does, the stats just follow the MaskOff data. /* //CHECK_VALUES(data[pos+2], 130); // probably ending pressure //CHECK_VALUE(data[pos+3], 0); // ending IPAP for bilevel? average? //CHECK_VALUES(data[pos+4], 0, 130); // 130 pressure in auto-cpap: min pressure? 90% IPAP in bilevel? //CHECK_VALUES(data[pos+5], 0, 130); // 130 pressure in auto-cpap, 90% EPAP in bilevel? //CHECK_VALUE(data[pos+6], 0); // 145 maybe max pressure in Auto-CPAP? //CHECK_VALUE(data[pos+7], 0); // Average 90% Pressure (Auto-CPAP) //CHECK_VALUE(data[pos+8], 0); // Average CPAP (Auto-CPAP) //CHECK_VALUES(data[pos+9], 0, 4); // or 1; PB count? LL count? minutes of something? CHECK_VALUE(data[pos+0xa], 0); //CHECK_VALUE(data[pos+0xb], 0); // OA count, probably 16-bit CHECK_VALUE(data[pos+0xc], 0); //CHECK_VALUE(data[pos+0xd], 0); CHECK_VALUE(data[pos+0xe], 0); //CHECK_VALUE(data[pos+0xf], 0); // CA count, probably 16-bit CHECK_VALUE(data[pos+0x10], 0); //CHECK_VALUE(data[pos+0x11], 40); // 16-bit something: 0x88, 0x26, etc. ??? //CHECK_VALUE(data[pos+0x12], 0); //CHECK_VALUE(data[pos+0x13], 0); // 16-bit minutes in LL //CHECK_VALUE(data[pos+0x14], 0); //CHECK_VALUE(data[pos+0x15], 0); // minutes in PB, probably 16-bit CHECK_VALUE(data[pos+0x16], 0); //CHECK_VALUE(data[pos+0x17], 0); // 16-bit VS count //CHECK_VALUE(data[pos+0x18], 0); //CHECK_VALUE(data[pos+0x19], 0); // H count, probably 16-bit CHECK_VALUE(data[pos+0x1a], 0); //CHECK_VALUE(data[pos+0x1b], 0); // 0 when no PB or LL? CHECK_VALUE(data[pos+0x1c], 0); //CHECK_VALUE(data[pos+0x1d], 9); // RE count, probably 16-bit CHECK_VALUE(data[pos+0x1e], 0); //CHECK_VALUE(data[pos+0x1f], 0); // FL count, probably 16-bit CHECK_VALUE(data[pos+0x20], 0); //CHECK_VALUE(data[pos+0x21], 0x32); // 0x55, 0x19 // ??? //CHECK_VALUE(data[pos+0x22], 0x23); // 0x3f, 0x14 // Average total leak //CHECK_VALUE(data[pos+0x23], 0x40); // 0x7d, 0x3d // ??? */ break; case 1: // Equipment Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff)); if (this->familyVersion == 0) { //CHECK_VALUE(data[pos+2], 1); // Usually 1, also seen 0, 6, and 7. ParseHumidifierSetting50Series(data[pos+3]); } /* Possibly F5V12? CHECK_VALUE(data[pos+2] & ~(0x40|8|4|2|1), 0); // ???, seen various bit combinations //CHECK_VALUE(data[pos+3], 0x19); // 0x17, 0x16 //CHECK_VALUES(data[pos+4], 0, 1); // or 2 //CHECK_VALUE(data[pos+5], 0x35); // 0x36, 0x36 if (data[pos+6] != 1) { // This is the usual value. CHECK_VALUE(data[pos+6] & ~(8|4|2|1), 0); // On F0V23 0 seems to be related to errors, 3 seen after 90 sec large leak before turning off? } // pos+4 == 2, pos+6 == 10 on the session that had a time-elapsed event, maybe it shut itself off // when approaching 24h of continuous use? */ break; case 5: // Clock adjustment? See ParseSummaryF0V4. CHECK_VALUE(pos, 1); // Always first CHECK_VALUE(chunk_size, 5); // and the only record in the session. if (false) { long value = data[pos] | data[pos+1]<<8 | data[pos+2]<<16 | data[pos+3]<<24; qDebug() << this->sessionid << "clock changing from" << ts(value * 1000L) << "to" << ts(this->timestamp * 1000L) << "delta:" << (this->timestamp - value); } break; case 6: // Cleared? // Appears in the very first session when that session number is > 1. // Presumably previous sessions were cleared out. // TODO: add an internal event for this. CHECK_VALUE(pos, 1); // Always first CHECK_VALUE(chunk_size, 1); // and the only record in the session. if (this->sessionid == 1) UNEXPECTED_VALUE(this->sessionid, ">1"); break; case 7: // Time Elapsed? tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) break; case 8: // Time Elapsed? How is this different from 7? tt += data[pos] | (data[pos+1] << 8); // This also adds to the total duration (otherwise it won't match report) break; case 9: // Humidifier setting change, F5V1 and F5V2 only tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) this->ParseHumidifierSetting60Series(data[pos+2], data[pos+3]); break; default: UNEXPECTED_VALUE(code, "known slice code"); ok = false; // unlike F0V6, we don't know the size of unknown slices, so we can't recover break; } pos += size; } if (ok && pos != chunk_size) { qWarning() << this->sessionid << (this->size() - pos) << "trailing bytes"; } this->duration = tt; return ok; } bool PRS1DataChunk::ParseSettingsF5V012(const unsigned char* data, int /*size*/) { PRS1Mode cpapmode = PRS1_MODE_UNKNOWN; float GAIN = PRS1PressureSettingEvent::GAIN; if (this->familyVersion == 2) GAIN = 0.125f; // TODO: parameterize this somewhere better int imax_pressure = data[0x2]; int imin_epap = data[0x3]; int imax_epap = data[0x4]; int imin_ps = data[0x5]; int imax_ps = data[0x6]; // Only one mode available, so apparently there's no byte in the settings that encodes it? cpapmode = PRS1_MODE_ASV; this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, imin_epap)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MAX, imax_epap)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, imin_epap + imin_ps)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, imax_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, imin_ps)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, imax_ps)); //CHECK_VALUE(data[0x07], 1, 2); // 1 = backup breath rate "Auto"; 2 = fixed BPM, see below //CHECK_VALUE(data[0x08], 0); // backup "Breath Rate" in mode 2 //CHECK_VALUE(data[0x09], 0); // backup "Timed Inspiration" (gain 0.1) in mode 2 int pos = 0x7; int backup_mode = data[pos]; int breath_rate; int timed_inspiration; switch (backup_mode) { case 0: this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_MODE, PRS1Backup_Off)); break; case 1: this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_MODE, PRS1Backup_Auto)); CHECK_VALUE(data[pos+1], 0); CHECK_VALUE(data[pos+2], 0); break; case 2: breath_rate = data[pos+1]; timed_inspiration = data[pos+2]; if (breath_rate < 4 || breath_rate > 29) UNEXPECTED_VALUE(breath_rate, "4-29"); if (timed_inspiration < 5 || timed_inspiration > 30) UNEXPECTED_VALUE(timed_inspiration, "5-30"); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_MODE, PRS1Backup_Fixed)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_RATE, breath_rate)); this->AddEvent(new PRS1ScaledSettingEvent(PRS1_SETTING_BACKUP_TIMED_INSPIRATION, timed_inspiration, 0.1)); break; default: UNEXPECTED_VALUE(backup_mode, "0-2"); break; } int ramp_time = data[0x0a]; int ramp_pressure = data[0x0b]; if (ramp_time > 0) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure, GAIN)); } quint8 flex = data[0x0c]; this->ParseFlexSettingF5V012(flex, cpapmode); if (this->familyVersion == 0) { // TODO: either split this into two functions or use size to differentiate like FV3 parsers do this->ParseHumidifierSetting50Series(data[0x0d], true); pos = 0xe; } else { // 60-Series machines have a 2-byte humidfier setting. this->ParseHumidifierSetting60Series(data[0x0d], data[0x0e], true); pos = 0xf; } // TODO: may differ between F5V0 and F5V12 // 0x01, 0x41 = auto-on, view AHI, tubing type = 15 // 0x41, 0x41 = auto-on, view AHI, tubing type = 15, resist lock // 0x42, 0x01 = (no auto-on), view AHI, tubing type = 22, resist lock, tubing lock // 0x00, 0x41 = auto-on, view AHI, tubing type = 22, no tubing lock // 0x0B, 0x41 = mask resist 1, tube lock, tubing type = 15, auto-on, view AHI // 0x09, 0x01 = mask resist 1, tubing 15, view AHI // 0x19, 0x41 = mask resist 3, tubing 15, auto-on, view AHI // 0x29, 0x41 = mask resist 5, tubing 15, auto-on, view AHI // 1 = view AHI // 4 = auto-on // 1 = tubing type: 0=22, 1=15 // 2 = tubing lock // 38 = mask resist level // 4 = resist lock int resist_level = (data[pos] >> 3) & 7; // 0x09 resist=1, 0x11 resist=2, 0x19=resist 3, 0x29=resist 5 this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_LOCK, (data[pos] & 0x40) != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_SETTING, resist_level)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HOSE_DIAMETER, (data[pos] & 0x01) ? 15 : 22)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_TUBING_LOCK, (data[pos] & 0x02) != 0)); CHECK_VALUE(data[pos] & (0x80|0x04), 0); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_AUTO_ON, (data[pos+1] & 0x40) != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SHOW_AHI, (data[pos+1] & 1) != 0)); CHECK_VALUE(data[pos+1] & ~(0x40|1), 0); int apnea_alarm = data[pos+2]; int low_mv_alarm = data[pos+3]; int disconnect_alarm = data[pos+4]; if (apnea_alarm) { CHECK_VALUES(apnea_alarm, 1, 3); // 1 = apnea alarm 10, 3 = apnea alarm 30 } if (low_mv_alarm) { if (low_mv_alarm < 20 || low_mv_alarm > 99) { UNEXPECTED_VALUE(low_mv_alarm, "20-99"); // we've seen 20, 80 and 99, all of which correspond to the number on the report } } if (disconnect_alarm) { CHECK_VALUES(disconnect_alarm, 1, 2); // 1 = disconnect alarm 15, 2 = disconnect alarm 60 } return true; } // Flex F5V0 confirmed // 0x81 = Bi-Flex 1 (ASV mode) // 0x82 = Bi-Flex 2 (ASV mode) // 0x83 = Bi-Flex 3 (ASV mode) // Flex F5V1 confirmed // 0x81 = Bi-Flex 1 (ASV mode) // 0x82 = Bi-Flex 2 (ASV mode) // 0x83 = Bi-Flex 3 (ASV mode) // 0xC9 = Rise Time 1, Rise Time Lock (ASV mode) // 0x8A = Rise Time 2 (ASV mode) (Shows "ASV - None" in mode summary, but then rise time in details) // 0x8B = Rise Time 3 (ASV mode) (breath rate auto) // 0x08 = Rise Time 2 (ASV mode) (falls back to level=2? bits encode level=0) // Flex F5V2 confirmed // 0x02 = Bi-Flex 2 (ASV mode) (breath rate auto, but min/max PS=0) // this could be different from F5V01, or PS=0 could disable flex? // 8 = ? (once was 0 when rise time was on and backup breathing was off, rise time level was also 0 in that case) // (was also 0 on F5V2) // 4 = Rise Time Lock // 8 = Rise Time (vs. Bi-Flex) // 3 = level void PRS1DataChunk::ParseFlexSettingF5V012(quint8 flex, int cpapmode) { FlexMode flexmode = FLEX_Unknown; bool valid = (flex & 0x80) != 0; bool lock = (flex & 0x40) != 0; bool risetime = (flex & 0x08) != 0; int flexlevel = flex & 0x03; if (flex & (0x20 | 0x10 | 0x04)) UNEXPECTED_VALUE(flex, "known bits"); CHECK_VALUE(cpapmode, PRS1_MODE_ASV); if (this->familyVersion == 0) { CHECK_VALUE(valid, true); CHECK_VALUE(lock, false); CHECK_VALUE(risetime, false); } else if (this->familyVersion == 1) { if (valid == false) { CHECK_VALUE(flex, 0x08); flexlevel = 2; // These get reported as Rise Time 2 valid = true; } } else { CHECK_VALUE(flex, 0x02); // only seen one example, unsure if it matches F5V01; seems to encode Bi-Flex 2 valid = true; // add the flex mode and setting to the parsed settings } if (flexlevel == 0 || flexlevel >3) UNEXPECTED_VALUE(flexlevel, "1-3"); CHECK_VALUE(valid, true); if (risetime) { flexmode = FLEX_RiseTime; } else { flexmode = FLEX_BiFlex; } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode)); if (flexmode == FLEX_BiFlex) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, flexlevel)); CHECK_VALUE(lock, 0); // Flag any sample data that will let us confirm flex lock //this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LOCK, lock != 0)); } else { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RISE_TIME, flexlevel)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RISE_TIME_LOCK, lock != 0)); } } const QVector ParsedEventsF5V0 = { PRS1EPAPSetEvent::TYPE, // No PP, unlike F5V1 PRS1TimedBreathEvent::TYPE, PRS1ObstructiveApneaEvent::TYPE, PRS1ClearAirwayEvent::TYPE, PRS1HypopneaEvent::TYPE, PRS1FlowLimitationEvent::TYPE, PRS1VibratorySnoreEvent::TYPE, PRS1PeriodicBreathingEvent::TYPE, PRS1LargeLeakEvent::TYPE, PRS1IPAPAverageEvent::TYPE, PRS1IPAPLowEvent::TYPE, PRS1IPAPHighEvent::TYPE, PRS1TotalLeakEvent::TYPE, PRS1RespiratoryRateEvent::TYPE, PRS1PatientTriggeredBreathsEvent::TYPE, PRS1MinuteVentilationEvent::TYPE, PRS1TidalVolumeEvent::TYPE, PRS1SnoreEvent::TYPE, PRS1EPAPAverageEvent::TYPE, // No LEAK, unlike F5V1 }; // 950P is F5V0 bool PRS1DataChunk::ParseEventsF5V0(void) { if (this->family != 5 || this->familyVersion != 0) { qWarning() << "ParseEventsF5V0 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const QMap event_sizes = { {1,2}, {3,4}, {8,4}, {0xa,2}, {0xb,5}, {0xc,5}, {0xd,0xc} }; if (chunk_size < 1) { // This does occasionally happen in F0V6. qDebug() << this->sessionid << "Empty event data"; return false; } bool ok = true; int pos = 0, startpos; int code, size; int t = 0; int elapsed, duration; do { code = data[pos++]; size = 3; // default size = 2 bytes time delta + 1 byte data if (event_sizes.contains(code)) { size = event_sizes[code]; } if (pos + size > chunk_size) { qWarning() << this->sessionid << "event" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } startpos = pos; t += data[pos] | (data[pos+1] << 8); pos += 2; switch (code) { case 0x00: // Humidifier setting change (logged in summary in 60 series) this->ParseHumidifierSetting50Series(data[pos]); break; //case 0x01: // never seen on F5V0 case 0x02: // Pressure adjustment this->AddEvent(new PRS1EPAPSetEvent(t, data[pos++])); break; //case 0x03: // never seen on F5V0; probably pressure pulse, see F5V1 case 0x04: // Timed Breath // TB events have a duration in 0.1s, based on the review of pressure waveforms. // TODO: Ideally the starting time here would be adjusted here, but PRS1ParsedEvents // currently assume integer seconds rather than ms, so that's done at import. duration = data[pos]; this->AddEvent(new PRS1TimedBreathEvent(t, duration)); break; case 0x05: // Obstructive Apnea // OA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ObstructiveApneaEvent(t - elapsed, 0)); break; case 0x06: // Clear Airway Apnea // CA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ClearAirwayEvent(t - elapsed, 0)); break; case 0x07: // Hypopnea // NOTE: No additional (unknown) first byte as in F5V3 0x07, but see below. // This seems closer to F5V3 0x0d or 0x0e. elapsed = data[pos]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; case 0x08: // Hypopnea, note this is 0x7 in F5V3 // TODO: How is this hypopnea different from event 0x7? // TODO: What is the first byte? //data[pos+0]; // unknown first byte? elapsed = data[pos+1]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; case 0x09: // Flow Limitation, note this is 0x8 in F5V3 // TODO: We should revisit whether this is elapsed or duration once (if) // we start calculating flow limitations ourselves. Flow limitations aren't // as obvious as OA/CA when looking at a waveform. elapsed = data[pos]; this->AddEvent(new PRS1FlowLimitationEvent(t - elapsed, 0)); break; case 0x0a: // Vibratory Snore, note this is 0x9 in F5V3 // VS events are instantaneous flags with no duration, drawn on the official waveform. // The current thinking is that these are the snores that cause a change in auto-titrating // pressure. The snoring statistic above seems to be a total count. It's unclear whether // the trigger for pressure change is severity or count or something else. // no data bytes this->AddEvent(new PRS1VibratorySnoreEvent(t, 0)); break; case 0x0b: // Periodic Breathing, note this is 0xa in F5V3 // PB events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); // confirmed to double in F5V0 elapsed = data[pos+2]; this->AddEvent(new PRS1PeriodicBreathingEvent(t - elapsed - duration, duration)); break; case 0x0c: // Large Leak, note this is 0xb in F5V3 // LL events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); // confirmed to double in F5V0 elapsed = data[pos+2]; this->AddEvent(new PRS1LargeLeakEvent(t - elapsed - duration, duration)); break; case 0x0d: // Statistics // These appear every 2 minutes, so presumably summarize the preceding period. this->AddEvent(new PRS1IPAPAverageEvent(t, data[pos+0])); // 00=IPAP this->AddEvent(new PRS1IPAPLowEvent(t, data[pos+1])); // 01=IAP Low this->AddEvent(new PRS1IPAPHighEvent(t, data[pos+2])); // 02=IAP High this->AddEvent(new PRS1TotalLeakEvent(t, data[pos+3])); // 03=Total leak (average?) this->AddEvent(new PRS1RespiratoryRateEvent(t, data[pos+4])); // 04=Breaths Per Minute (average?) this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, data[pos+5])); // 05=Patient Triggered Breaths (average?) this->AddEvent(new PRS1MinuteVentilationEvent(t, data[pos+6])); // 06=Minute Ventilation (average?) this->AddEvent(new PRS1TidalVolumeEvent(t, data[pos+7])); // 07=Tidal Volume (average?) this->AddEvent(new PRS1SnoreEvent(t, data[pos+8])); // 08=Snore count // TODO: not a VS on official waveform, but appears in flags and contributes to overall VS index this->AddEvent(new PRS1EPAPAverageEvent(t, data[pos+9])); // 09=EPAP average this->AddEvent(new PRS1IntervalBoundaryEvent(t)); break; default: DUMP_EVENT(); UNEXPECTED_VALUE(code, "known event code"); this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos)); ok = false; // unlike F0V6, we don't know the size of unknown slices, so we can't recover break; } pos = startpos + size; } while (ok && pos < chunk_size); if (ok && pos != chunk_size) { qWarning() << this->sessionid << (this->size() - pos) << "trailing event bytes"; } this->duration = t; return ok; } const QVector ParsedEventsF5V1 = { PRS1EPAPSetEvent::TYPE, PRS1PressurePulseEvent::TYPE, PRS1TimedBreathEvent::TYPE, PRS1ObstructiveApneaEvent::TYPE, PRS1ClearAirwayEvent::TYPE, PRS1HypopneaEvent::TYPE, PRS1FlowLimitationEvent::TYPE, PRS1VibratorySnoreEvent::TYPE, PRS1PeriodicBreathingEvent::TYPE, PRS1LargeLeakEvent::TYPE, PRS1IPAPAverageEvent::TYPE, PRS1IPAPLowEvent::TYPE, PRS1IPAPHighEvent::TYPE, PRS1TotalLeakEvent::TYPE, PRS1RespiratoryRateEvent::TYPE, PRS1PatientTriggeredBreathsEvent::TYPE, PRS1MinuteVentilationEvent::TYPE, PRS1TidalVolumeEvent::TYPE, PRS1SnoreEvent::TYPE, PRS1EPAPAverageEvent::TYPE, PRS1LeakEvent::TYPE, }; // 960P and 961P are F5V1 bool PRS1DataChunk::ParseEventsF5V1(void) { if (this->family != 5 || this->familyVersion != 1) { qWarning() << "ParseEventsF5V1 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const QMap event_sizes = { {1,2}, {8,4}, {9,3}, {0xa,2}, {0xb,5}, {0xc,5}, {0xd,0xd} }; if (chunk_size < 1) { // This does occasionally happen in F0V6. qDebug() << this->sessionid << "Empty event data"; return false; } bool ok = true; int pos = 0, startpos; int code, size; int t = 0; int elapsed, duration; do { code = data[pos++]; size = 3; // default size = 2 bytes time delta + 1 byte data if (event_sizes.contains(code)) { size = event_sizes[code]; } if (pos + size > chunk_size) { qWarning() << this->sessionid << "event" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } startpos = pos; if (code != 0) { // Does this code really not have a timestamp? Never seen on F5V1, checked in F5V0. t += data[pos] | (data[pos+1] << 8); pos += 2; } switch (code) { //case 0x00: // never seen on F5V1 //case 0x01: // never seen on F5V1 case 0x02: // Pressure adjustment this->AddEvent(new PRS1EPAPSetEvent(t, data[pos++])); break; case 0x03: // Pressure Pulse duration = data[pos]; // TODO: is this a duration? this->AddEvent(new PRS1PressurePulseEvent(t, duration)); break; case 0x04: // Timed Breath // TB events have a duration in 0.1s, based on the review of pressure waveforms. // TODO: Ideally the starting time here would be adjusted here, but PRS1ParsedEvents // currently assume integer seconds rather than ms, so that's done at import. duration = data[pos]; this->AddEvent(new PRS1TimedBreathEvent(t, duration)); break; case 0x05: // Obstructive Apnea // OA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ObstructiveApneaEvent(t - elapsed, 0)); break; case 0x06: // Clear Airway Apnea // CA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ClearAirwayEvent(t - elapsed, 0)); break; case 0x07: // Hypopnea // TODO: How is this hypopnea different from event 0x8? // NOTE: No additional (unknown) first byte as in F5V3 0x7, but see below. // This seems closer to F5V3 0x0d or 0x0e. elapsed = data[pos]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; case 0x08: // Hypopnea, note this is 0x7 in F5V3 // TODO: How is this hypopnea different from event 0x7? // TODO: What is the first byte? //data[pos+0]; // unknown first byte? elapsed = data[pos+1]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; case 0x09: // Flow Limitation, note this is 0x8 in F5V3 // TODO: We should revisit whether this is elapsed or duration once (if) // we start calculating flow limitations ourselves. Flow limitations aren't // as obvious as OA/CA when looking at a waveform. elapsed = data[pos]; this->AddEvent(new PRS1FlowLimitationEvent(t - elapsed, 0)); break; case 0x0a: // Vibratory Snore, note this is 0x9 in F5V3 // VS events are instantaneous flags with no duration, drawn on the official waveform. // The current thinking is that these are the snores that cause a change in auto-titrating // pressure. The snoring statistic above seems to be a total count. It's unclear whether // the trigger for pressure change is severity or count or something else. // no data bytes this->AddEvent(new PRS1VibratorySnoreEvent(t, 0)); break; case 0x0b: // Periodic Breathing, note this is 0xa in F5V3 // PB events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); // confirmed to double in F5V0 elapsed = data[pos+2]; this->AddEvent(new PRS1PeriodicBreathingEvent(t - elapsed - duration, duration)); break; case 0x0c: // Large Leak, note this is 0xb in F5V3 // LL events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); // confirmed to double in F5V0 elapsed = data[pos+2]; this->AddEvent(new PRS1LargeLeakEvent(t - elapsed - duration, duration)); break; case 0x0d: // Statistics // These appear every 2 minutes, so presumably summarize the preceding period. this->AddEvent(new PRS1IPAPAverageEvent(t, data[pos+0])); // 00=IPAP this->AddEvent(new PRS1IPAPLowEvent(t, data[pos+1])); // 01=IAP Low this->AddEvent(new PRS1IPAPHighEvent(t, data[pos+2])); // 02=IAP High this->AddEvent(new PRS1TotalLeakEvent(t, data[pos+3])); // 03=Total leak (average?) this->AddEvent(new PRS1RespiratoryRateEvent(t, data[pos+4])); // 04=Breaths Per Minute (average?) this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, data[pos+5])); // 05=Patient Triggered Breaths (average?) this->AddEvent(new PRS1MinuteVentilationEvent(t, data[pos+6])); // 06=Minute Ventilation (average?) this->AddEvent(new PRS1TidalVolumeEvent(t, data[pos+7])); // 07=Tidal Volume (average?) this->AddEvent(new PRS1SnoreEvent(t, data[pos+8])); // 08=Snore count // TODO: not a VS on official waveform, but appears in flags and contributes to overall VS index this->AddEvent(new PRS1EPAPAverageEvent(t, data[pos+9])); // 09=EPAP average this->AddEvent(new PRS1LeakEvent(t, data[pos+0xa])); // 0A=Leak (average?) new to F5V1 (originally found in F5V3) this->AddEvent(new PRS1IntervalBoundaryEvent(t)); break; default: DUMP_EVENT(); UNEXPECTED_VALUE(code, "known event code"); this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos)); ok = false; // unlike F0V6, we don't know the size of unknown slices, so we can't recover break; } pos = startpos + size; } while (ok && pos < chunk_size); if (ok && pos != chunk_size) { qWarning() << this->sessionid << (this->size() - pos) << "trailing event bytes"; } this->duration = t; return ok; } const QVector ParsedEventsF5V2 = { PRS1EPAPSetEvent::TYPE, PRS1TimedBreathEvent::TYPE, PRS1ObstructiveApneaEvent::TYPE, //PRS1ClearAirwayEvent::TYPE, // not yet seen PRS1HypopneaEvent::TYPE, PRS1FlowLimitationEvent::TYPE, //PRS1VibratorySnoreEvent::TYPE, // not yet seen PRS1PeriodicBreathingEvent::TYPE, //PRS1LargeLeakEvent::TYPE, // not yet seen PRS1IPAPAverageEvent::TYPE, PRS1IPAPLowEvent::TYPE, PRS1IPAPHighEvent::TYPE, PRS1TotalLeakEvent::TYPE, PRS1RespiratoryRateEvent::TYPE, PRS1PatientTriggeredBreathsEvent::TYPE, PRS1MinuteVentilationEvent::TYPE, PRS1TidalVolumeEvent::TYPE, PRS1SnoreEvent::TYPE, PRS1EPAPAverageEvent::TYPE, PRS1LeakEvent::TYPE, }; // 960T is F5V2 bool PRS1DataChunk::ParseEventsF5V2(void) { if (this->family != 5 || this->familyVersion != 2) { qWarning() << "ParseEventsF5V2 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const QMap event_sizes = { {0,4}, {1,2}, {3,4}, {8,3}, {9,4}, {0xa,3}, {0xb,5}, {0xc,5}, {0xd,5}, {0xe,0xd}, {0xf,5}, {0x10,5}, {0x11,2}, {0x12,6} }; if (chunk_size < 1) { // This does occasionally happen in F0V6. qDebug() << this->sessionid << "Empty event data"; return false; } // F5V3 uses a gain of 0.125 rather than 0.1 to allow for a maximum value of 30 cmH2O static const float GAIN = 0.125; // TODO: this should be parameterized somewhere more logical bool ok = true; int pos = 0, startpos; int code, size; int t = 0; int elapsed/*, duration, value*/; do { code = data[pos++]; size = 3; // default size = 2 bytes time delta + 1 byte data if (event_sizes.contains(code)) { size = event_sizes[code]; } if (pos + size > chunk_size) { qWarning() << this->sessionid << "event" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } startpos = pos; if (code != 0 && code != 0x12) { // These two codes have no timestamp TODO: verify this applies to F5V012 t += data[pos] /*| (data[pos+1] << 8)*/; // TODO: Is this really only 1 byte? if (data[pos+1] != 0) qWarning() << this->sessionid << "nonzero time? byte" << hex(startpos); CHECK_VALUE(data[pos+1], 0); pos += 2; } switch (code) { /* case 0x00: // Unknown (ASV Pressure value) DUMP_EVENT(); // offset? data0 = data[pos++]; if (!data[pos - 1]) { // WTH??? data1 = data[pos++]; } if (!data[pos - 1]) { //data2 = data[pos++]; pos++; } break; case 0x01: // Unknown DUMP_EVENT(); this->AddEvent(new PRS1UnknownValueEvent(code, t, 0, 0.1F)); break; */ case 0x02: // Pressure adjustment this->AddEvent(new PRS1EPAPSetEvent(t, data[pos++], GAIN)); break; /* case 0x03: // BIPAP Pressure DUMP_EVENT(); qDebug() << "0x03 Observed in ASV data!!????"; data0 = data[pos++]; data1 = data[pos++]; // data0/=10.0; // data1/=10.0; // session->AddEvent(new Event(t,CPAP_EAP, 0, data, 1)); // session->AddEvent(new Event(t,CPAP_IAP, 0, &data1, 1)); break; */ case 0x04: // Timed Breath // TB events have a duration in 0.1s, based on the review of pressure waveforms. // TODO: Ideally the starting time here would be adjusted here, but PRS1ParsedEvents // currently assume integer seconds rather than ms, so that's done at import. duration = data[pos]; this->AddEvent(new PRS1TimedBreathEvent(t, duration)); break; case 0x05: // Obstructive Apnea // OA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ObstructiveApneaEvent(t - elapsed, 0)); break; /* case 0x06: DUMP_EVENT(); //code=CPAP_ClearAirway; data0 = data[pos++]; this->AddEvent(new PRS1ClearAirwayEvent(t - data0, data0)); break; */ case 0x07: // Hypopnea // NOTE: No additional (unknown) first byte as in F5V3 0x07, but see below. // This seems closer to F5V3 0x0d or 0x0e. // What's different about this an 0x08? This was seen in a PB at least once? elapsed = data[pos]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; case 0x08: // Hypopnea, note this is 0x7 in F5V1 // TODO: How is this hypopnea different from event 0x9 and 0x7? // NOTE: No additional (unknown) first byte as in F5V3 0x7, but see below. // This seems closer to F5V3 0x0d or 0x0e. elapsed = data[pos]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; /* case 0x09: // ASV Codes DUMP_EVENT(); / * if (this->familyVersion<2) { //code=CPAP_FlowLimit; data0 = data[pos++]; this->AddEvent(new PRS1FlowLimitationEvent(t - data0, data0)); } else { * / data0 = data[pos++]; data1 = data[pos++]; break; */ case 0x0a: // Flow Limitation, note this is 0x9 in F5V1 and 0x8 in F5V3 // TODO: We should revisit whether this is elapsed or duration once (if) // we start calculating flow limitations ourselves. Flow limitations aren't // as obvious as OA/CA when looking at a waveform. elapsed = data[pos]; this->AddEvent(new PRS1FlowLimitationEvent(t - elapsed, 0)); break; /* case 0x0b: // Cheyne Stokes DUMP_EVENT(); data0 = ((unsigned char *)data)[pos + 1] << 8 | ((unsigned char *)data)[pos]; //data0*=2; pos += 2; data1 = ((unsigned char *)data)[pos]; //|data[pos+1] << 8 pos += 1; //tt-=delta; this->AddEvent(new PRS1PeriodicBreathingEvent(t - data1, data0)); break; */ case 0x0c: // Periodic Breathing, note this is 0xb in F5V1 and 0xa in F5V3 // PB events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); // confirmed to double in F5V0 elapsed = data[pos+2]; this->AddEvent(new PRS1PeriodicBreathingEvent(t - elapsed - duration, duration)); break; /* case 0x0d: DUMP_EVENT(); data0 = (data[pos + 1] << 8 | data[pos]); data0 *= 2; pos += 2; data1 = data[pos++]; //tt = t - qint64(data1) * 1000L; break; */ case 0x0e: // Statistics, note this was 0x0d in F5V0 and F5V1 // These appear every 2 minutes, so presumably summarize the preceding period. this->AddEvent(new PRS1IPAPAverageEvent(t, data[pos+0], GAIN)); // 00=IPAP this->AddEvent(new PRS1IPAPLowEvent(t, data[pos+1], GAIN)); // 01=IAP Low this->AddEvent(new PRS1IPAPHighEvent(t, data[pos+2], GAIN)); // 02=IAP High this->AddEvent(new PRS1TotalLeakEvent(t, data[pos+3])); // 03=Total leak (average?) this->AddEvent(new PRS1RespiratoryRateEvent(t, data[pos+4])); // 04=Breaths Per Minute (average?) this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, data[pos+5])); // 05=Patient Triggered Breaths (average?) this->AddEvent(new PRS1MinuteVentilationEvent(t, data[pos+6])); // 06=Minute Ventilation (average?) this->AddEvent(new PRS1TidalVolumeEvent(t, data[pos+7])); // 07=Tidal Volume (average?) this->AddEvent(new PRS1SnoreEvent(t, data[pos+8])); // 08=Snore count // TODO: not a VS on official waveform, but appears in flags and contributes to overall VS index this->AddEvent(new PRS1EPAPAverageEvent(t, data[pos+9], GAIN)); // 09=EPAP average this->AddEvent(new PRS1LeakEvent(t, data[pos+0xa])); // 0A=Leak (average?) new to F5V1 (originally found in F5V3) this->AddEvent(new PRS1IntervalBoundaryEvent(t)); break; /* case 0x0f: DUMP_EVENT(); qDebug() << "0x0f Observed in ASV data!!????"; data0 = data[pos + 1] << 8 | data[pos]; pos += 2; data1 = data[pos]; //|data[pos+1] << 8 pos += 1; //tt -= qint64(data1) * 1000L; //session->AddEvent(new Event(tt,cpapcode, 0, data, 2)); break; case 0x10: // Unknown DUMP_EVENT(); data0 = data[pos + 1] << 8 | data[pos]; pos += 2; data1 = data[pos++]; this->AddEvent(new PRS1LargeLeakEvent(t - data1, data0)); // qDebug() << "0x10 Observed in ASV data!!????"; // data0 = data[pos++]; // << 8) | data[pos]; // data1 = data[pos++]; // data2 = data[pos++]; //session->AddEvent(new Event(t,cpapcode, 0, data, 3)); break; case 0x11: // Not Leak Rate DUMP_EVENT(); qDebug() << "0x11 Observed in ASV data!!????"; //if (!Code[24]) { // Code[24]=new EventList(cpapcode,EVL_Event); //} //Code[24]->AddEvent(t,data[pos++]); break; case 0x12: // Summary DUMP_EVENT(); qDebug() << "0x12 Observed in ASV data!!????"; data0 = data[pos++]; data1 = data[pos++]; //data2 = data[pos + 1] << 8 | data[pos]; pos += 2; //session->AddEvent(new Event(t,cpapcode, 0, data,3)); break; */ default: DUMP_EVENT(); UNEXPECTED_VALUE(code, "known event code"); this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos)); ok = false; // unlike F0V6, we don't know the size of unknown slices, so we can't recover break; } pos = startpos + size; } while (ok && pos < chunk_size); if (ok && pos != chunk_size) { qWarning() << this->sessionid << (this->size() - pos) << "trailing event bytes"; } this->duration = t; return ok; } //******************************************************************************************** // MARK: - // MARK: DreamStation // Originally based on ParseSummaryF0V6, with changes observed in ASV sample data // based on size, slices 0-5 look similar, and it looks like F0V6 slides 8-B are equivalent to 6-9 // // TODO: surely there will be a way to merge these loops and abstract the machine-specific // encodings into another function or class, but that's probably worth pursuing only after // the details have been figured out. bool PRS1DataChunk::ParseSummaryF5V3(void) { if (this->family != 5 || this->familyVersion != 3) { qWarning() << "ParseSummaryF5V3 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const int minimum_sizes[] = { 1, 0x35, 9, 4, 2, 4, 0x1e, 2, 4, 9 }; static const int ncodes = sizeof(minimum_sizes) / sizeof(int); // NOTE: The sizes contained in hblock can vary, even within a single machine, as can the length of hblock itself! // TODO: hardcoding this is ugly, think of a better approach if (chunk_size < minimum_sizes[0] + minimum_sizes[1] + minimum_sizes[2]) { qWarning() << this->sessionid << "summary data too short:" << chunk_size; return false; } // We've once seen a short summary with no mask-on/off: just equipment-on, settings, 9, equipment-off // (And we've seen something similar in F3V6.) if (chunk_size < 75) UNEXPECTED_VALUE(chunk_size, ">= 75"); bool ok = true; int pos = 0; int code, size; int tt = 0; while (ok && pos < chunk_size) { code = data[pos++]; if (!this->hblock.contains(code)) { qWarning() << this->sessionid << "missing hblock entry for" << code; ok = false; break; } size = this->hblock[code]; if (code < ncodes) { // make sure the handlers below don't go past the end of the buffer if (size < minimum_sizes[code]) { UNEXPECTED_VALUE(size, minimum_sizes[code]); qWarning() << this->sessionid << "slice" << code << "too small" << size << "<" << minimum_sizes[code]; if (code != 1) { // Settings are variable-length, so shorter settings slices aren't fatal. ok = false; break; } } } // else if it's past ncodes, we'll log its information below (rather than handle it) if (pos + size > chunk_size) { qWarning() << this->sessionid << "slice" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } int alarm; switch (code) { case 0: // Equipment On CHECK_VALUE(pos, 1); // Always first? //CHECK_VALUES(data[pos], 1, 7); // or 3, or 0? 3 when machine turned on via auto-on, 1 when turned on via button CHECK_VALUE(size, 1); break; case 1: // Settings ok = this->ParseSettingsF5V3(data + pos, size); break; case 9: // new to F5V3 vs. F0V6, comes right after settings, before mask on? CHECK_VALUE(data[pos], 0); CHECK_VALUE(data[pos+1], 1); CHECK_VALUES(data[pos+2], 0, 4); // Apnea Alarm, 0 = off, 4 = 40 CHECK_VALUE(data[pos+3], 1); CHECK_VALUE(data[pos+4], 1); if (data[pos+5] > 3) { UNEXPECTED_VALUE(data[pos+5], "0-3"); // Low Minute Ventilation Alarm, 0 = off, 1-3 = 1-3 } CHECK_VALUE(data[pos+6], 2); CHECK_VALUE(data[pos+7], 1); alarm = 0; switch (data[pos+8]) { case 1: alarm = 15; break; // 15 sec case 2: alarm = 60; break; // 60 sec case 0: break; default: UNEXPECTED_VALUE(data[pos+8], "0-2"); break; } if (alarm) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_DISCONNECT_ALARM, alarm)); } CHECK_VALUE(size, 9); break; case 3: // Mask On tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn)); this->ParseHumidifierSettingV3(data[pos+2], data[pos+3]); break; case 4: // Mask Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff)); break; case 5: // ASV pressure stats per mask-on slice //CHECK_VALUE(data[pos], 0x28); // 90% EPAP //CHECK_VALUE(data[pos+1], 0x23); // average EPAP //CHECK_VALUE(data[pos+2], 0x24); // 90% PS //CHECK_VALUE(data[pos+3], 0x17); // average PS break; case 6: // Patient statistics per mask-on slice // These get averaged on a time-weighted basis in the final report. // Where is H count? //CHECK_VALUE(data[pos], 0x00); // probably 16-bit value CHECK_VALUE(data[pos+1], 0x00); //CHECK_VALUE(data[pos+2], 0x00); // 16-bit OA count //CHECK_VALUE(data[pos+3], 0x00); //CHECK_VALUE(data[pos+4], 0x00); // probably 16-bit value CHECK_VALUE(data[pos+5], 0x00); //CHECK_VALUE(data[pos+6], 0x00); // 16-bit CA count //CHECK_VALUE(data[pos+7], 0x00); //CHECK_VALUE(data[pos+8], 0x00); // 16-bit minutes in LL //CHECK_VALUE(data[pos+9], 0x00); //CHECK_VALUE(data[pos+0xa], 0x0f); // 16-bit minutes in PB //CHECK_VALUE(data[pos+0xb], 0x00); //CHECK_VALUE(data[pos+0xc], 0x14); // 16-bit VS count //CHECK_VALUE(data[pos+0xd], 0x00); //CHECK_VALUE(data[pos+0xe], 0x05); // 16-bit H count for type 0xd //CHECK_VALUE(data[pos+0xf], 0x00); //CHECK_VALUE(data[pos+0x10], 0x00); // 16-bit H count for type 7 //CHECK_VALUE(data[pos+0x11], 0x00); //CHECK_VALUE(data[pos+0x12], 0x02); // 16-bit FL count //CHECK_VALUE(data[pos+0x13], 0x00); //CHECK_VALUE(data[pos+0x14], 0x28); // 0x69 (105) //CHECK_VALUE(data[pos+0x15], 0x17); // average total leak //CHECK_VALUE(data[pos+0x16], 0x5b); // 0x7d (125) //CHECK_VALUE(data[pos+0x17], 0x09); // 16-bit H count for type 0xe //CHECK_VALUE(data[pos+0x18], 0x00); //CHECK_VALUE(data[pos+0x19], 0x10); // average breath rate //CHECK_VALUE(data[pos+0x1a], 0x2d); // average TV / 10 //CHECK_VALUE(data[pos+0x1b], 0x63); // average % PTB //CHECK_VALUE(data[pos+0x1c], 0x07); // average minute vent //CHECK_VALUE(data[pos+0x1d], 0x06); // average leak break; case 2: // Equipment Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff)); //CHECK_VALUE(data[pos+2], 0x01); // 0x08 //CHECK_VALUE(data[pos+3], 0x17); // 0x16, 0x18 //CHECK_VALUE(data[pos+4], 0x00); //CHECK_VALUE(data[pos+5], 0x29); // 0x2a, 0x28, 0x26, 0x36 //CHECK_VALUE(data[pos+6], 0x01); // 0x00 CHECK_VALUE(data[pos+7], 0x00); CHECK_VALUE(data[pos+8], 0x00); break; case 8: // Humidier setting change tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) this->ParseHumidifierSettingV3(data[pos+2], data[pos+3]); break; default: UNEXPECTED_VALUE(code, "known slice code"); break; } pos += size; } this->duration = tt; return ok; } // Based initially on ParseSettingsF0V6. Many of the codes look the same, like always starting with 0, 0x35 looking like // a humidifier setting, etc., but the contents are sometimes a bit different, such as mode values and pressure settings. // // new settings to find: breath rate, tubing lock, alarms, bool PRS1DataChunk::ParseSettingsF5V3(const unsigned char* data, int size) { static const QMap expected_lengths = { {0x0a,5}, /*{0x0c,3}, {0x0d,2}, {0x0e,2}, {0x0f,4}, {0x10,3},*/ {0x14,3}, {0x2e,2}, {0x35,2} }; bool ok = true; PRS1Mode cpapmode = PRS1_MODE_UNKNOWN; // F5V3 uses a gain of 0.125 rather than 0.1 to allow for a maximum value of 30 cmH2O static const float GAIN = 0.125; // TODO: parameterize this somewhere better int max_pressure = 0; int min_ps = 0; int max_ps = 0; int min_epap = 0; int max_epap = 0; int rise_time; int breath_rate; int timed_inspiration; // Parse the nested data structure which contains settings int pos = 0; do { int code = data[pos++]; int len = data[pos++]; int expected_len = 1; if (expected_lengths.contains(code)) { expected_len = expected_lengths[code]; } //CHECK_VALUE(len, expected_len); if (len < expected_len) { qWarning() << this->sessionid << "setting" << code << "too small" << len << "<" << expected_len; ok = false; break; } if (pos + len > size) { qWarning() << this->sessionid << "setting" << code << "@" << pos << "longer than remaining slice"; ok = false; break; } switch (code) { case 0: // Device Mode CHECK_VALUE(pos, 2); // always first? CHECK_VALUE(len, 1); switch (data[pos]) { case 0: cpapmode = PRS1_MODE_ASV; break; default: UNEXPECTED_VALUE(data[pos], "known device mode"); break; } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode)); break; case 1: // ??? CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 1); // 1 when when Opti-Start is on? 0 when off? /* if (data[pos] != 0 && data[pos] != 3) { CHECK_VALUES(data[pos], 1, 2); // 1 when EZ-Start is enabled? 2 when Auto-Trial? 3 when Auto-Trial is off or Opti-Start isn't off? } */ break; case 0x0a: // ASV with variable EPAP pressure setting CHECK_VALUE(len, 5); CHECK_VALUE(cpapmode, PRS1_MODE_ASV); max_pressure = data[pos]; min_epap = data[pos+1]; max_epap = data[pos+2]; min_ps = data[pos+3]; max_ps = data[pos+4]; this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, min_epap, GAIN)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MAX, max_epap, GAIN)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, min_epap + min_ps, GAIN)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, qMin(max_pressure, max_epap + max_ps), GAIN)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, min_ps, GAIN)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, max_ps, GAIN)); break; case 0x14: // ASV backup rate CHECK_VALUE(len, 3); CHECK_VALUE(cpapmode, PRS1_MODE_ASV); switch (data[pos]) { //case 0: // Breath Rate Off in F3V6 setting 0x1e case 1: // Breath Rate Auto this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_MODE, PRS1Backup_Auto)); CHECK_VALUE(data[pos+1], 0); // 0 for auto CHECK_VALUE(data[pos+2], 0); // 0 for auto break; case 2: // Breath Rate (fixed BPM) breath_rate = data[pos+1]; timed_inspiration = data[pos+2]; if (breath_rate < 4 || breath_rate > 16) UNEXPECTED_VALUE(breath_rate, "4-16"); if (timed_inspiration < 12 || timed_inspiration > 30) UNEXPECTED_VALUE(timed_inspiration, "12-30"); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_MODE, PRS1Backup_Fixed)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_RATE, breath_rate)); // BPM this->AddEvent(new PRS1ScaledSettingEvent(PRS1_SETTING_BACKUP_TIMED_INSPIRATION, timed_inspiration, 0.1)); break; default: CHECK_VALUES(data[pos], 1, 2); // 1 = auto, 2 = fixed BPM (0 = off in F3V6 setting 0x1e) break; } break; /* case 0x2a: // EZ-Start CHECK_VALUE(data[pos], 0x80); // EZ-Start enabled break; */ case 0x2b: // Ramp Type CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); // 0 == "Linear", 0x80 = "SmartRamp" this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TYPE, data[pos] != 0)); break; case 0x2c: // Ramp Time CHECK_VALUE(len, 1); if (data[pos] != 0) { // 0 == ramp off, and ramp pressure setting doesn't appear if (data[pos] < 5 || data[pos] > 45) UNEXPECTED_VALUE(data[pos], "5-45"); } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, data[pos])); break; case 0x2d: // Ramp Pressure (with ASV pressure encoding) CHECK_VALUE(len, 1); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, data[pos], GAIN)); break; case 0x2e: // Flex mode and level (ASV variant) CHECK_VALUE(len, 2); switch (data[pos]) { case 0: // Bi-Flex // [0x00, N] for Bi-Flex level N this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, FLEX_BiFlex)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, data[pos+1])); break; case 0x20: // Rise Time // [0x20, 0x03] for no flex, rise time setting = 3, no rise lock rise_time = data[pos+1]; if (rise_time < 1 || rise_time > 6) UNEXPECTED_VALUE(rise_time, "1-6"); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RISE_TIME, rise_time)); break; default: CHECK_VALUES(data[pos], 0, 0x20); break; } break; case 0x2f: // Flex lock? (was on F0V6, 0x80 for locked) CHECK_VALUE(len, 1); CHECK_VALUE(data[pos], 0); //this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LOCK, data[pos] != 0)); break; //case 0x30: ASV puts the flex level in the 0x2e setting for some reason case 0x35: // Humidifier setting CHECK_VALUE(len, 2); this->ParseHumidifierSettingV3(data[pos], data[pos+1], true); break; case 0x36: // Mask Resistance Lock CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); // 0x80 = locked this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_LOCK, data[pos] != 0)); break; case 0x38: // Mask Resistance CHECK_VALUE(len, 1); if (data[pos] != 0) { // 0 == mask resistance off if (data[pos] < 1 || data[pos] > 5) UNEXPECTED_VALUE(data[pos], "1-5"); } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_SETTING, data[pos])); break; case 0x39: CHECK_VALUE(len, 1); CHECK_VALUE(data[pos], 0); // 0x80 maybe auto-trial in F0V6? break; case 0x3b: // Tubing Type CHECK_VALUE(len, 1); if (data[pos] > 2) UNEXPECTED_VALUE(data[pos], "0-2"); // 15HT = 2, 15 = 1, 22 = 0, though report only says "15" for 15HT this->ParseTubingTypeV3(data[pos]); break; case 0x3c: // View Optional Screens CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SHOW_AHI, data[pos] != 0)); break; case 0x3d: // Auto On (ASV variant) CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_AUTO_ON, data[pos] != 0)); break; default: UNEXPECTED_VALUE(code, "known setting"); qDebug() << "Unknown setting:" << hex << code << "in" << this->sessionid << "at" << pos; this->AddEvent(new PRS1UnknownDataEvent(QByteArray((const char*) data, size), pos, len)); break; } pos += len; } while (ok && pos + 2 <= size); return ok; } const QVector ParsedEventsF5V3 = { PRS1EPAPSetEvent::TYPE, PRS1TimedBreathEvent::TYPE, PRS1IPAPAverageEvent::TYPE, PRS1IPAPLowEvent::TYPE, PRS1IPAPHighEvent::TYPE, PRS1TotalLeakEvent::TYPE, PRS1RespiratoryRateEvent::TYPE, PRS1PatientTriggeredBreathsEvent::TYPE, PRS1MinuteVentilationEvent::TYPE, PRS1TidalVolumeEvent::TYPE, PRS1SnoreEvent::TYPE, PRS1EPAPAverageEvent::TYPE, PRS1LeakEvent::TYPE, PRS1PressurePulseEvent::TYPE, PRS1ObstructiveApneaEvent::TYPE, PRS1ClearAirwayEvent::TYPE, PRS1HypopneaEvent::TYPE, PRS1FlowLimitationEvent::TYPE, PRS1VibratorySnoreEvent::TYPE, PRS1PeriodicBreathingEvent::TYPE, PRS1LargeLeakEvent::TYPE, }; // Outer loop based on ParseSummaryF5V3 along with hint as to event codes from old ParseEventsF5V3, // except this actually does something with the data. bool PRS1DataChunk::ParseEventsF5V3(void) { if (this->family != 5 || this->familyVersion != 3) { qWarning() << "ParseEventsF5V3 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const int minimum_sizes[] = { 2, 3, 3, 0xd, 3, 3, 3, 4, 3, 2, 5, 5, 3, 3, 3, 3 }; static const int ncodes = sizeof(minimum_sizes) / sizeof(int); if (chunk_size < 1) { // This does occasionally happen. qDebug() << this->sessionid << "Empty event data"; return false; } // F5V3 uses a gain of 0.125 rather than 0.1 to allow for a maximum value of 30 cmH2O static const float GAIN = 0.125; // TODO: this should be parameterized somewhere more logical bool ok = true; int pos = 0, startpos; int code, size; int t = 0; int elapsed, duration; do { code = data[pos++]; if (!this->hblock.contains(code)) { qWarning() << this->sessionid << "missing hblock entry for event" << code; ok = false; break; } size = this->hblock[code]; if (code < ncodes) { // make sure the handlers below don't go past the end of the buffer if (size < minimum_sizes[code]) { qWarning() << this->sessionid << "event" << code << "too small" << size << "<" << minimum_sizes[code]; ok = false; break; } } // else if it's past ncodes, we'll log its information below (rather than handle it) if (pos + size > chunk_size) { qWarning() << this->sessionid << "event" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } startpos = pos; t += data[pos] | (data[pos+1] << 8); pos += 2; switch (code) { case 1: // Pressure adjustment this->AddEvent(new PRS1EPAPSetEvent(t, data[pos++], GAIN)); this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos-1, size+1)); // TODO: what is this? break; case 2: // Timed Breath // TB events have a duration in 0.1s, based on the review of pressure waveforms. // TODO: Ideally the starting time here would be adjusted here, but PRS1ParsedEvents // currently assume integer seconds rather than ms, so that's done at import. duration = data[pos]; this->AddEvent(new PRS1TimedBreathEvent(t, duration)); break; case 3: // Statistics // These appear every 2 minutes, so presumably summarize the preceding period. this->AddEvent(new PRS1IPAPAverageEvent(t, data[pos+0], GAIN)); // 00=IPAP this->AddEvent(new PRS1IPAPLowEvent(t, data[pos+1], GAIN)); // 01=IAP Low this->AddEvent(new PRS1IPAPHighEvent(t, data[pos+2], GAIN)); // 02=IAP High this->AddEvent(new PRS1TotalLeakEvent(t, data[pos+3])); // 03=Total leak (average?) this->AddEvent(new PRS1RespiratoryRateEvent(t, data[pos+4])); // 04=Breaths Per Minute (average?) this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, data[pos+5])); // 05=Patient Triggered Breaths (average?) this->AddEvent(new PRS1MinuteVentilationEvent(t, data[pos+6])); // 06=Minute Ventilation (average?) this->AddEvent(new PRS1TidalVolumeEvent(t, data[pos+7])); // 07=Tidal Volume (average?) this->AddEvent(new PRS1SnoreEvent(t, data[pos+8])); // 08=Snore count // TODO: not a VS on official waveform, but appears in flags and contributes to overall VS index this->AddEvent(new PRS1EPAPAverageEvent(t, data[pos+9], GAIN)); // 09=EPAP average this->AddEvent(new PRS1LeakEvent(t, data[pos+0xa])); // 0A=Leak (average?) this->AddEvent(new PRS1IntervalBoundaryEvent(t)); break; case 0x04: // Pressure Pulse duration = data[pos]; // TODO: is this a duration? this->AddEvent(new PRS1PressurePulseEvent(t, duration)); break; case 0x05: // Obstructive Apnea // OA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ObstructiveApneaEvent(t - elapsed, 0)); break; case 0x06: // Clear Airway Apnea // CA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ClearAirwayEvent(t - elapsed, 0)); break; case 0x07: // Hypopnea // TODO: How is this hypopnea different from events 0xd and 0xe? // TODO: What is the first byte? //data[pos+0]; // unknown first byte? elapsed = data[pos+1]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; case 0x08: // Flow Limitation // TODO: We should revisit whether this is elapsed or duration once (if) // we start calculating flow limitations ourselves. Flow limitations aren't // as obvious as OA/CA when looking at a waveform. elapsed = data[pos]; this->AddEvent(new PRS1FlowLimitationEvent(t - elapsed, 0)); break; case 0x09: // Vibratory Snore // VS events are instantaneous flags with no duration, drawn on the official waveform. // The current thinking is that these are the snores that cause a change in auto-titrating // pressure. The snoring statistic above seems to be a total count. It's unclear whether // the trigger for pressure change is severity or count or something else. // no data bytes this->AddEvent(new PRS1VibratorySnoreEvent(t, 0)); break; case 0x0a: // Periodic Breathing // PB events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; this->AddEvent(new PRS1PeriodicBreathingEvent(t - elapsed - duration, duration)); break; case 0x0b: // Large Leak // LL events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; this->AddEvent(new PRS1LargeLeakEvent(t - elapsed - duration, duration)); break; case 0x0d: // Hypopnea // TODO: Why does this hypopnea have a different event code? // fall through case 0x0e: // Hypopnea // TODO: We should revisit whether this is elapsed or duration once (if) // we start calculating hypopneas ourselves. Their official definition // is 40% reduction in flow lasting at least 10s. duration = data[pos]; this->AddEvent(new PRS1HypopneaEvent(t - duration, 0)); break; case 0x0f: // TODO: some other pressure adjustment? // Appears near the beginning and end of a session when Opti-Start is on, at least once in middle //CHECK_VALUES(data[pos], 0x20, 0x28); this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos-1, size+1)); break; default: DUMP_EVENT(); UNEXPECTED_VALUE(code, "known event code"); this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos-1, size+1)); break; } pos = startpos + size; } while (ok && pos < chunk_size); this->duration = t; return ok; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/prs1_parser_vent.cpp000066400000000000000000001626561417327530600253270ustar00rootroot00000000000000/* PRS1 Parsing for S/T and AVAPS ventilators (Family 3) * * Copyright (c) 2019-2022 The OSCAR Team * Portions copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "prs1_parser.h" #include "prs1_loader.h" static QString hex(int i) { return QString("0x") + QString::number(i, 16).toUpper(); } //******************************************************************************************** // MARK: - // MARK: 50 and 60 Series // borrowed largely from ParseSummaryF5V012 bool PRS1DataChunk::ParseSummaryF3V03(void) { if (this->family != 3 || (this->familyVersion > 3)) { qWarning() << "ParseSummaryF3V03 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); QVector minimum_sizes; if (this->familyVersion == 0) { minimum_sizes = { 0x19, 3, 3, 9 }; } else { minimum_sizes = { 0x1b, 3, 5, 9 }; } // NOTE: These are fixed sizes, but are called minimum to more closely match the F0V6 parser. bool ok = true; int pos = 0; int code, size; int tt = 0; while (ok && pos < chunk_size) { code = data[pos++]; // There is no hblock prior to F3V6. size = 0; if (code < minimum_sizes.length()) { // make sure the handlers below don't go past the end of the buffer size = minimum_sizes[code]; } // else if it's past ncodes, we'll log its information below (rather than handle it) if (pos + size > chunk_size) { qWarning() << this->sessionid << "slice" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } // NOTE: F3V3 doesn't use 16-bit time deltas in its summary events, it uses absolute timestamps! // It's possible that these are 24-bit, but haven't yet seen a timestamp that large. const unsigned char * ondata = data; switch (code) { case 0: // Equipment On CHECK_VALUE(pos, 1); // Always first if (this->familyVersion == 0) { // F3V0 inserts an extra byte in front CHECK_VALUE(data[pos], 1); ondata = ondata + 1; } CHECK_VALUE(ondata[pos], 0); /* CHECK_VALUE(data[pos] & 0xF0, 0); // TODO: what are these? if ((data[pos] & 0x0F) != 1) { // This is the most frequent value. //CHECK_VALUES(data[pos] & 0x0F, 3, 5); // TODO: what are these? 0 seems to be related to errors. } */ // F3V3 doesn't have a separate settings record like F3V6 does, the settings just follow the EquipmentOn data. ok = this->ParseSettingsF3V03(ondata, size); break; case 2: // Mask On tt = data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn)); CHECK_VALUE(data[pos+2], 0); // may be high byte of timestamp if (size > 3) { // F3V3 records the humidifier setting at each mask-on, F3V0 only records the initial setting. this->ParseHumidifierSettingF3V3(data[pos+3], data[pos+4]); } break; case 3: // Mask Off tt = data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff)); // F3V3 doesn't have a separate stats record like F3V6 does, the stats just follow the MaskOff data. CHECK_VALUE(data[pos+0x2], 0); // may be high byte of timestamp //CHECK_VALUES(data[pos+0x3], 0, 1); // OA count, probably 16-bit CHECK_VALUE(data[pos+0x4], 0); //CHECK_VALUE(data[pos+0x5], 0); // CA count, probably 16-bit CHECK_VALUE(data[pos+0x6], 0); //CHECK_VALUE(data[pos+0x7], 0); // H count, probably 16-bit CHECK_VALUE(data[pos+0x8], 0); break; case 1: // Equipment Off tt = data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff)); CHECK_VALUE(data[pos+2], 0); // may be high byte of timestamp break; /* case 5: // Clock adjustment? See ParseSummaryF0V4. CHECK_VALUE(pos, 1); // Always first CHECK_VALUE(chunk_size, 5); // and the only record in the session. if (false) { long value = data[pos] | data[pos+1]<<8 | data[pos+2]<<16 | data[pos+3]<<24; qDebug() << this->sessionid << "clock changing from" << ts(value * 1000L) << "to" << ts(this->timestamp * 1000L) << "delta:" << (this->timestamp - value); } break; case 6: // Cleared? // Appears in the very first session when that session number is > 1. // Presumably previous sessions were cleared out. // TODO: add an internal event for this. CHECK_VALUE(pos, 1); // Always first CHECK_VALUE(chunk_size, 1); // and the only record in the session. if (this->sessionid == 1) UNEXPECTED_VALUE(this->sessionid, ">1"); break; case 7: // ??? tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) break; case 8: // ??? tt += data[pos] | (data[pos+1] << 8); // Since 7 and 8 seem to occur near each other, let's assume 8 also has a timestamp CHECK_VALUE(pos, 1); CHECK_VALUE(chunk_size, 3); CHECK_VALUE(data[pos], 0); // and alert us if the timestamp is nonzero CHECK_VALUE(data[pos+1], 0); break; case 9: // Humidifier setting change tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) this->ParseHumidifierSetting60Series(data[pos+2], data[pos+3]); break; */ default: UNEXPECTED_VALUE(code, "known slice code"); ok = false; // unlike F0V6, we don't know the size of unknown slices, so we can't recover break; } pos += size; } if (ok && pos != chunk_size) { qWarning() << this->sessionid << (this->size() - pos) << "trailing bytes"; } this->duration = tt; return ok; } // Support for 1061, 1061T, 1160P // logic largely borrowed from ParseSettingsF3V6, values based on sample data bool PRS1DataChunk::ParseSettingsF3V03(const unsigned char* data, int /*size*/) { PRS1Mode cpapmode = PRS1_MODE_UNKNOWN; FlexMode flexmode = FLEX_Unknown; // data[0] is the event code // data[1] is checked in the calling function switch (data[2]) { case 0: cpapmode = PRS1_MODE_CPAP; break; // "CPAP" mode case 1: cpapmode = PRS1_MODE_S; break; // "S" mode case 2: cpapmode = PRS1_MODE_ST; break; // "S/T" mode; pressure seems variable? case 4: cpapmode = PRS1_MODE_PC; break; // "PC" mode? Usually "PC - AVAPS", see setting 1 below default: UNEXPECTED_VALUE(data[2], "known device mode"); break; } switch (data[3]) { case 0: // 0 = None switch (cpapmode) { case PRS1_MODE_CPAP: flexmode = FLEX_None; break; case PRS1_MODE_S: flexmode = FLEX_RiseTime; break; // reports say "None" but then list a rise time setting case PRS1_MODE_ST: flexmode = FLEX_RiseTime; break; // reports say "None" but then list a rise time setting default: UNEXPECTED_VALUE(cpapmode, "CPAP, S, or S/T"); break; } break; case 1: // 1 = Bi-Flex, only seen with "S - Bi-Flex" flexmode = FLEX_BiFlex; CHECK_VALUE(cpapmode, PRS1_MODE_S); break; case 2: // 2 = AVAPS: usually "PC - AVAPS", sometimes "S/T - AVAPS" switch (cpapmode) { case PRS1_MODE_ST: cpapmode = PRS1_MODE_ST_AVAPS; break; case PRS1_MODE_PC: cpapmode = PRS1_MODE_PC_AVAPS; break; default: UNEXPECTED_VALUE(cpapmode, "S/T or PC"); break; } flexmode = FLEX_RiseTime; // reports say "AVAPS" but then list a rise time setting break; default: UNEXPECTED_VALUE(data[3], "known flex mode"); break; } if (this->familyVersion == 0) { // Confirm F3V0 setting encoding switch (cpapmode) { case PRS1_MODE_CPAP: break; // CPAP has been confirmed case PRS1_MODE_S: break; // S bi-flex and rise time have been confirmed case PRS1_MODE_ST: CHECK_VALUE(flexmode, FLEX_RiseTime); // only rise time has been confirmed break; default: UNEXPECTED_VALUE(cpapmode, "tested modes"); } } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode)); int epap = data[4] + (data[5] << 8); // 0x82 = EPAP 13 cmH2O; 0x78 = EPAP 12 cmH2O; 0x50 = EPAP 8 cmH2O int min_ipap = data[6] + (data[7] << 8); // 0xA0 = IPAP 16 cmH2O; 0xBE = 19 cmH2O min IPAP (in AVAPS); 0x78 = IPAP 12 cmH2O int max_ipap = data[8] + (data[9] << 8); // 0xAA = ???; 0x12C = 30 cmH2O max IPAP (in AVAPS); 0x78 = ??? switch (cpapmode) { case PRS1_MODE_CPAP: this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE, epap)); CHECK_VALUE(min_ipap, 0); CHECK_VALUE(max_ipap, 0); break; case PRS1_MODE_S: case PRS1_MODE_ST: this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP, epap)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP, min_ipap)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS, min_ipap - epap)); //CHECK_VALUES(max_ipap, 170, 300); break; case PRS1_MODE_ST_AVAPS: case PRS1_MODE_PC_AVAPS: this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP, epap)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, min_ipap)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, max_ipap)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, min_ipap - epap)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, max_ipap - epap)); break; default: UNEXPECTED_VALUE(cpapmode, "expected mode"); break; } if (cpapmode == PRS1_MODE_CPAP) { CHECK_VALUE(flexmode, FLEX_None); CHECK_VALUE(data[0xa], 0); CHECK_VALUE(data[0xb], 0); CHECK_VALUE(data[0xc], 0); CHECK_VALUE(data[0xd], 0); } if (flexmode == FLEX_RiseTime) { int rise_time = data[0xa]; // 1 = Rise Time Setting 1, 2 = Rise Time Setting 2, 3 = Rise Time Setting 3 if (rise_time < 1 || rise_time > 6) UNEXPECTED_VALUE(rise_time, "1-6"); // TODO: what is 0? CHECK_VALUES(data[0xb], 0, 1); // 1 = Rise Time Lock (in "None" and AVAPS flex mode) CHECK_VALUE(data[0xc], 0); CHECK_VALUES(data[0xd], 0, 1); // TODO: What is this? It's usually 0. this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RISE_TIME, rise_time)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RISE_TIME_LOCK, data[0xb] == 1)); } else if (flexmode == FLEX_BiFlex) { CHECK_VALUES(data[0xa], 2, 3); // TODO: May also be Bi-Flex level? But how is this different from [0xc] below? CHECK_VALUES(data[0xb], 0, 1); // TODO: What is this? It doesn't always match [0xd]. CHECK_VALUES(data[0xc], 2, 3); CHECK_VALUE(data[0x0a], data[0xc]); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, data[0xc])); // 3 = Bi-Flex 3, 2 = Bi-Flex 2 (in bi-flex mode) this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LOCK, data[0xd] == 1)); } if (flexmode == FLEX_None) CHECK_VALUE(data[0xe], 0); if (cpapmode == PRS1_MODE_ST_AVAPS || cpapmode == PRS1_MODE_PC_AVAPS) { if (data[0xe] < 24 || data[0xe] > 65) UNEXPECTED_VALUE(data[0xe], "24-65"); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_TIDAL_VOLUME, data[0xe] * 10.0)); } else if (flexmode == FLEX_BiFlex || flexmode == FLEX_RiseTime) { CHECK_VALUE(data[0xe], 0x14); // 0x14 = ??? } int breath_rate = data[0xf]; int timed_inspiration = data[0x10]; bool backup = false; switch (cpapmode) { case PRS1_MODE_CPAP: CHECK_VALUE(breath_rate, 0); CHECK_VALUE(timed_inspiration, 0); break; case PRS1_MODE_S: if (this->familyVersion == 0) { CHECK_VALUE(breath_rate, 10); CHECK_VALUE(timed_inspiration, 10); } else { CHECK_VALUE(breath_rate, 0); CHECK_VALUE(timed_inspiration, 0); } break; case PRS1_MODE_PC_AVAPS: CHECK_VALUE(breath_rate, 0); // only ever seen 0 on reports so far CHECK_VALUE(timed_inspiration, 30); backup = true; break; case PRS1_MODE_ST_AVAPS: if (breath_rate) { // can be 0 on reports CHECK_VALUES(breath_rate, 9, 10); } if (timed_inspiration < 10 || timed_inspiration > 30) UNEXPECTED_VALUE(timed_inspiration, "10-30"); backup = true; break; case PRS1_MODE_ST: if (breath_rate < 8 || breath_rate > 18) UNEXPECTED_VALUE(breath_rate, "8-18"); // can this be 0? if (timed_inspiration < 10 || timed_inspiration > 20) UNEXPECTED_VALUE(timed_inspiration, "10-20"); // 16 = 1.6s backup = true; break; default: UNEXPECTED_VALUE(cpapmode, "CPAP, S, S/T, or PC"); break; } if (backup) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_MODE, PRS1Backup_Fixed)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_RATE, breath_rate)); this->AddEvent(new PRS1ScaledSettingEvent(PRS1_SETTING_BACKUP_TIMED_INSPIRATION, timed_inspiration, 0.1)); } CHECK_VALUE(data[0x11], 0); //CHECK_VALUE(data[0x12], 0x1E, 0x0F); // 0x1E = ramp time 30 minutes, 0x0F = ramp time 15 minutes //CHECK_VALUE(data[0x13], 0x3C, 0x5A, 0x28); // 0x3C = ramp pressure 6 cmH2O, 0x28 = ramp pressure 4 cmH2O, 0x5A = ramp pressure 9 cmH2O CHECK_VALUE(data[0x14], 0); // the ramp pressure is probably a 16-bit value like the ones above are int ramp_time = data[0x12]; int ramp_pressure = data[0x13]; if (ramp_time > 0) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure)); } int pos; if (this->familyVersion == 0) { ParseHumidifierSetting50Series(data[0x15], true); pos = 0x16; } else { this->ParseHumidifierSettingF3V3(data[0x15], data[0x16], true); // Menu options? CHECK_VALUES(data[0x17], 0x10, 0x90); // 0x10 = resist 1; 0x90 = resist 1, resist lock this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_LOCK, (data[0x17] & 0x80) != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_SETTING, 1)); // only value seen so far, CHECK_VALUES above will flag any others this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_TUBING_LOCK, (data[0x18] & 0x80) != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HOSE_DIAMETER, (data[0x18] & 0x7f))); CHECK_VALUES(data[0x18] & 0x7f, 22, 15); // 0x16 = tubing 22; 0x0F = tubing 15, 0x96 = tubing 22 with lock pos = 0x19; } // Alarms? if (this->familyVersion == 0) { if (data[pos] != 0) { CHECK_VALUES(data[pos], 10, 30); // Apnea alarm on F3V0 } CHECK_VALUES(data[pos+1], 0, 15); // Disconnect alarm on F3V0 CHECK_VALUES(data[pos+2], 0, 17); // Low MV alarm on F3V0 } else { CHECK_VALUE(data[pos], 0); CHECK_VALUE(data[pos+1], 0); CHECK_VALUE(data[pos+2], 0); } return true; } // XX XX = F3V3 Humidifier bytes // 43 15 = heated tube temp 5, humidity 2 // 43 14 = heated tube temp 4, humidity 2 // 63 13 = heated tube temp 3, humidity 3 // 63 11 = heated tube temp 1, humidity 3 // 45 08 = system one 5 // 44 08 = system one 4 // 43 08 = system one 3 // 42 08 = system one 2 // 41 08 = system one 1 // 40 08 = system one 0 (off) // 40 60 = system one 3, no data // 40 20 = system one 3, no data // 40 90 = heated tube, tube off, data=tube t=0,h=0 // 45 80 = classic 5 // 44 80 = classic 4 // 43 80 = classic 3 // 42 80 = classic 2 // 40 80 = classic 0 (off) // // 7 = humidity level without tube // 8 = ? (never seen) // 1 = ? (never seen) // 6 = heated tube humidity level (when tube present, 0x40 all other times? including when tube is off?) // 8 = ? (never seen) // 7 = tube temp // 8 = "System One" mode // 1 = tube present // 6 = no data, seems to show system one 3 in settings // 8 = (classic mode; also seen when heated tube present but off, possibly ignored in that case) // // Note that, while containing similar fields as ParseHumidifierSetting60Series, the bit arrangement is different for F3V3! void PRS1DataChunk::ParseHumidifierSettingF3V3(unsigned char humid1, unsigned char humid2, bool add_setting) { if (false) qWarning() << this->sessionid << "humid" << hex(humid1) << hex(humid2) << add_setting; int humidlevel = humid1 & 7; // Ignored when heated tube is present: humidifier setting on tube disconnect is always reported as 3 if (humidlevel > 5) UNEXPECTED_VALUE(humidlevel, "<= 5"); CHECK_VALUE(humid1 & 0x40, 0x40); // seems always set, even without heated tube CHECK_VALUE(humid1 & 0x98, 0); // never seen int tubehumidlevel = (humid1 >> 5) & 7; // This mask is a best guess based on other masks. if (tubehumidlevel > 5) UNEXPECTED_VALUE(tubehumidlevel, "<= 5"); CHECK_VALUE(tubehumidlevel & 4, 0); // never seen, but would clarify whether above mask is correct int tubetemp = humid2 & 7; if (tubetemp > 5) UNEXPECTED_VALUE(tubetemp, "<= 5"); if (humid2 & 0x60) { CHECK_VALUES(humid2 & 0x60, 0x20, 0x60); // no humidifier data on chart } bool humidclassic = (humid2 & 0x80) != 0; // Set on classic mode reports; evidently ignored (sometimes set!) when tube is present //bool no_tube? = (humid2 & 0x20) != 0; // Something tube related: whenever it is set, tube is never present (inverse is not true) bool no_data = (humid2 & 0x60) != 0; // As described in chart, settings still show up int tubepresent = (humid2 & 0x10) != 0; bool humidsystemone = (humid2 & 0x08) != 0; // Set on "System One" humidification mode reports when tubepresent is false if (humidsystemone + tubepresent + no_data == 0) CHECK_VALUE(humidclassic, true); // Always set when everything else is off in F0V4 if (humidsystemone + tubepresent /*+ no_data*/ > 1) UNEXPECTED_VALUE(humid2, "one bit set"); // Only one of these ever seems to be set at a time //if (tubepresent && tubetemp == 0) CHECK_VALUE(tubehumidlevel, 0); // When the heated tube is off, tube humidity seems to be 0 in F0V4, but not F3V3 if (tubepresent) humidclassic = false; // Classic mode bit is evidently ignored when tube is present //qWarning() << this->sessionid << (humidclassic ? "C" : ".") << (humid2 & 0x20 ? "?" : ".") << (tubepresent ? "T" : ".") << (no_data ? "X" : ".") << (humidsystemone ? "1" : "."); /* if (tubepresent) { if (tubetemp) { qWarning() << this->sessionid << "tube temp" << tubetemp << "tube humidity" << tubehumidlevel << (humidclassic ? "classic" : "systemone") << "humidity" << humidlevel; } else { qWarning() << this->sessionid << "heated tube off" << (humidclassic ? "classic" : "systemone") << "humidity" << humidlevel; } } else { qWarning() << this->sessionid << (humidclassic ? "classic" : "systemone") << "humidity" << humidlevel; } */ HumidMode humidmode = HUMID_Fixed; if (tubepresent) { humidmode = HUMID_HeatedTube; } else { if (humidsystemone + humidclassic > 1) UNEXPECTED_VALUE(humid2, "fixed or adaptive"); if (humidsystemone) humidmode = HUMID_Adaptive; } if (add_setting) { bool humidifier_present = (no_data == 0); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, humidifier_present)); if (humidifier_present) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_MODE, humidmode)); if (humidmode == HUMID_HeatedTube) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HEATED_TUBE_TEMP, tubetemp)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, tubehumidlevel)); } else { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, humidlevel)); } } } // Check for previously unseen data that we expect to be normal: if (humidclassic && humidlevel == 1) UNEXPECTED_VALUE(humidlevel, "!= 1"); if (tubepresent) { if (tubetemp) CHECK_VALUES(tubehumidlevel, 2, 3); if (tubetemp == 2) UNEXPECTED_VALUE(tubetemp, "!= 2"); } } const QVector ParsedEventsF3V0 = { PRS1IPAPAverageEvent::TYPE, PRS1EPAPAverageEvent::TYPE, PRS1TotalLeakEvent::TYPE, PRS1TidalVolumeEvent::TYPE, PRS1FlowRateEvent::TYPE, PRS1PatientTriggeredBreathsEvent::TYPE, PRS1RespiratoryRateEvent::TYPE, PRS1MinuteVentilationEvent::TYPE, // No LEAK, unlike F3V3 PRS1HypopneaCount::TYPE, PRS1ClearAirwayCount::TYPE, // TODO PRS1ObstructiveApneaCount::TYPE, // TODO // No PP, FL, VS, RERA, PB, LL // No TB }; const QVector ParsedEventsF3V3 = { PRS1IPAPAverageEvent::TYPE, PRS1EPAPAverageEvent::TYPE, PRS1TotalLeakEvent::TYPE, PRS1TidalVolumeEvent::TYPE, PRS1FlowRateEvent::TYPE, PRS1PatientTriggeredBreathsEvent::TYPE, PRS1RespiratoryRateEvent::TYPE, PRS1MinuteVentilationEvent::TYPE, PRS1LeakEvent::TYPE, PRS1HypopneaCount::TYPE, PRS1ClearAirwayCount::TYPE, PRS1ObstructiveApneaCount::TYPE, // No PP, FL, VS, RERA, PB, LL // No TB }; // 1061, 1061T, 1160P series bool PRS1DataChunk::ParseEventsF3V03(void) { // NOTE: Older ventilators (BiPAP S/T and AVAPS) machines don't use timestamped events like everything else. // Instead, they use a fixed interval format like waveforms do (see PRS1_HTYPE_INTERVAL). if (this->family != 3 || (this->familyVersion != 0 && this->familyVersion != 3)) { qWarning() << "ParseEventsF3V03 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } if (this->fileVersion == 3) { // NOTE: The original comment in the header for ParseF3EventsV3 said there was a 1060P with fileVersion 3. // We've never seen that, so warn if it ever shows up. qWarning() << "F3V3 event file with fileVersion 3?"; } int t = 0; static const int record_size = 0x10; int size = this->m_data.size()/record_size; CHECK_VALUE(this->m_data.size() % record_size, 0); unsigned char * h = (unsigned char *)this->m_data.data(); static const qint64 block_duration = 120; // Make sure the assumptions here agree with the header CHECK_VALUE(this->htype, PRS1_HTYPE_INTERVAL); CHECK_VALUE(this->interval_count, size); CHECK_VALUE(this->interval_seconds, block_duration); for (auto & channel : this->waveformInfo) { CHECK_VALUE(channel.interleave, 1); } for (int x=0; x < size; x++) { // Use the timestamp of the end of this interval, to be consistent with other parsers, // but see note below regarding the duration of the final interval. t += block_duration; // TODO: The duration of the final interval isn't clearly defined in this format: // there appears to be no way (apart from looking at the summary or waveform data) // to determine the end time, which may truncate the last interval. // // TODO: What if there are multiple "final" intervals in a session due to multiple // mask-on slices? this->AddEvent(new PRS1IPAPAverageEvent(t, h[0] | (h[1] << 8))); this->AddEvent(new PRS1EPAPAverageEvent(t, h[2] | (h[3] << 8))); this->AddEvent(new PRS1TotalLeakEvent(t, h[4])); this->AddEvent(new PRS1TidalVolumeEvent(t, h[5])); this->AddEvent(new PRS1FlowRateEvent(t, h[6])); this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, h[7])); this->AddEvent(new PRS1RespiratoryRateEvent(t, h[8])); if (this->familyVersion == 0) { if (h[9] < 4 || h[9] > 65) UNEXPECTED_VALUE(h[9], "4-65"); } else { if (h[9] < 4 || h[9] > 84) UNEXPECTED_VALUE(h[9], "5-84"); // not sure what this is.. encore doesn't graph it. } if (this->familyVersion == 0) { // 1 shows as Apnea (AP) alarm // 2 shows as a Patient Disconnect (PD) alarm // 4 shows as a Low Minute Vent (LMV) alarm // 8 shows as a Low Pressure (LP) alarm // 10 shows as PD + LP in the same interval if (h[10] & ~(0x01 | 0x02 | 0x04 | 0x08)) UNEXPECTED_VALUE(h[10], "known bits"); } else { // This is probably the same as F3V0, but we don't yet have the sample data to confirm. CHECK_VALUES(h[10], 0, 8); // 8 shows as a Low Pressure (LP) alarm } this->AddEvent(new PRS1MinuteVentilationEvent(t, h[11])); if (this->familyVersion == 0) { CHECK_VALUE(h[12], 0); this->AddEvent(new PRS1HypopneaCount(t, h[13])); // count of hypopnea events this->AddEvent(new PRS1ClearAirwayCount(t, h[14])); // count of clear airway events this->AddEvent(new PRS1ObstructiveApneaCount(t, h[15])); // count of obstructive events } else { this->AddEvent(new PRS1HypopneaCount(t, h[12])); // count of hypopnea events this->AddEvent(new PRS1ClearAirwayCount(t, h[13])); // count of clear airway events this->AddEvent(new PRS1ObstructiveApneaCount(t, h[14])); // count of obstructive events this->AddEvent(new PRS1LeakEvent(t, h[15])); } this->AddEvent(new PRS1IntervalBoundaryEvent(t)); h += record_size; } this->duration = t; return true; } //******************************************************************************************** // MARK: - // MARK: DreamStation // Originally based on ParseSummaryF5V3, with changes observed in ventilator sample data // // TODO: surely there will be a way to merge ParseSummary (FV3) loops and abstract the machine-specific // encodings into another function or class, but that's probably worth pursuing only after // the details have been figured out. bool PRS1DataChunk::ParseSummaryF3V6(void) { if (this->family != 3 || this->familyVersion != 6) { qWarning() << "ParseSummaryF3V6 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const int minimum_sizes[] = { 1, 0x25, 9, 7, 4, 2, 1, 2, 2, 1, 0x18, 2, 4 }; // F5V3 = { 1, 0x38, 4, 2, 4, 0x1e, 2, 4, 9 }; static const int ncodes = sizeof(minimum_sizes) / sizeof(int); // NOTE: The sizes contained in hblock can vary, even within a single machine, as can the length of hblock itself! // TODO: hardcoding this is ugly, think of a better approach if (chunk_size < minimum_sizes[0] + minimum_sizes[1] + minimum_sizes[2]) { qWarning() << this->sessionid << "summary data too short:" << chunk_size; return false; } // We've once seen a short summary with no mask-on/off: just equipment-on, settings, 2, equipment-off // (And we've seen something similar in F5V3.) if (chunk_size < 58) UNEXPECTED_VALUE(chunk_size, ">= 58"); bool ok = true; int pos = 0; int code, size; int tt = 0; while (ok && pos < chunk_size) { code = data[pos++]; if (!this->hblock.contains(code)) { qWarning() << this->sessionid << "missing hblock entry for" << code; ok = false; break; } size = this->hblock[code]; if (code < ncodes) { // make sure the handlers below don't go past the end of the buffer if (size < minimum_sizes[code]) { UNEXPECTED_VALUE(size, minimum_sizes[code]); qWarning() << this->sessionid << "slice" << code << "too small" << size << "<" << minimum_sizes[code]; if (code != 1) { // Settings are variable-length, so shorter settings slices aren't fatal. ok = false; break; } } } // else if it's past ncodes, we'll log its information below (rather than handle it) if (pos + size > chunk_size) { qWarning() << this->sessionid << "slice" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } switch (code) { case 0: // Equipment On CHECK_VALUE(pos, 1); // Always first? //CHECK_VALUE(data[pos], 0x10); // usually 0x10 for 1030X, sometimes 0x40 or 0x80 are set in addition or instead CHECK_VALUE(size, 1); break; case 1: // Settings ok = this->ParseSettingsF3V6(data + pos, size); break; case 2: // seems equivalent to F5V3 #9, comes right after settings, usually 9 bytes, identical values // TODO: This may be structurally similar to settings: a list of (code, length, value). CHECK_VALUE(data[pos], 0); CHECK_VALUE(data[pos+1], 1); //CHECK_VALUE(data[pos+2], 0); // Apnea Alarm (0=off, 1=10, 2=20) if (data[pos+2] != 0) { CHECK_VALUES(data[pos+2], 1, 2); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_APNEA_ALARM, data[pos+2] * 10)); } CHECK_VALUE(data[pos+3], 1); CHECK_VALUE(data[pos+4], 1); CHECK_VALUES(data[pos+5], 0, 1); // 1 = Low Minute Ventilation Alarm set to 1 CHECK_VALUE(data[pos+6], 2); CHECK_VALUE(data[pos+7], 1); CHECK_VALUE(data[pos+8], 0); // 1 = patient disconnect alarm of 15 sec on F5V3, not sure where time is encoded if (size > 9) { CHECK_VALUE(data[pos+9], 3); CHECK_VALUE(data[pos+10], 1); CHECK_VALUE(data[pos+11], 0); CHECK_VALUE(size, 12); } break; case 4: // Mask On tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn)); this->ParseHumidifierSettingV3(data[pos+2], data[pos+3]); break; case 5: // Mask Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff)); break; case 6: // Ventilator CPAP stats, presumably per mask-on slice //CHECK_VALUE(data[pos], 0x3C); // Average CPAP break; case 7: // Ventilator EPAP stats, presumably per mask-on slice //CHECK_VALUE(data[pos], 0x69); // Average EPAP //CHECK_VALUE(data[pos+1], 0x80); // Average 90% EPAP break; case 8: // Ventilator IPAP stats, presumably per mask-on slice //CHECK_VALUE(data[pos], 0x86); // Average IPAP //CHECK_VALUE(data[pos+1], 0xA8); // Average 90% IPAP break; case 0xa: // Patient statistics, presumably per mask-on slice //CHECK_VALUE(data[pos], 0x00); // 16-bit OA count CHECK_VALUE(data[pos+1], 0x00); //CHECK_VALUE(data[pos+2], 0x00); // 16-bit CA count CHECK_VALUE(data[pos+3], 0x00); //CHECK_VALUE(data[pos+4], 0x00); // 16-bit minutes in LL CHECK_VALUE(data[pos+5], 0x00); //CHECK_VALUE(data[pos+6], 0x0A); // 16-bit VS count //CHECK_VALUE(data[pos+7], 0x00); // We've actually seen someone with more than 255 VS in a night! //CHECK_VALUE(data[pos+8], 0x01); // 16-bit H count (partial) CHECK_VALUE(data[pos+9], 0x00); //CHECK_VALUE(data[pos+0xa], 0x00); // 16-bit H count (partial) CHECK_VALUE(data[pos+0xb], 0x00); //CHECK_VALUE(data[pos+0xc], 0x00); // 16-bit RE count CHECK_VALUE(data[pos+0xd], 0x00); //CHECK_VALUE(data[pos+0xe], 0x3e); // average total leak //CHECK_VALUE(data[pos+0xf], 0x03); // 16-bit H count (partial) CHECK_VALUE(data[pos+0x10], 0x00); //CHECK_VALUE(data[pos+0x11], 0x11); // average breath rate //CHECK_VALUE(data[pos+0x12], 0x41); // average TV / 10 //CHECK_VALUE(data[pos+0x13], 0x60); // average % PTB //CHECK_VALUE(data[pos+0x14], 0x0b); // average minute vent //CHECK_VALUE(data[pos+0x15], 0x1d); // average leak? (similar position to F5V3, similar delta to total leak) //CHECK_VALUE(data[pos+0x16], 0x00); // 16-bit minutes in PB CHECK_VALUE(data[pos+0x17], 0x00); break; case 3: // Equipment Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff)); //CHECK_VALUES(data[pos+2], 1, 4); // bitmask, have seen 1, 4, 6, 0x41 //CHECK_VALUE(data[pos+3], 0x17); // 0x16, etc. //CHECK_VALUES(data[pos+4], 0, 1); // or 2 //CHECK_VALUE(data[pos+5], 0x15); // 0x16, etc. //CHECK_VALUES(data[pos+6], 0, 1); // or 2 break; case 0xc: // Humidier setting change tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) this->ParseHumidifierSettingV3(data[pos+2], data[pos+3]); break; default: UNEXPECTED_VALUE(code, "known slice code"); break; } pos += size; } this->duration = tt; return ok; } // Based initially on ParseSettingsF5V3. Many of the codes look the same, like always starting with 0, 0x35 looking like // a humidifier setting, etc., but the contents are sometimes a bit different, such as mode values and pressure settings. // // new settings to find: ... bool PRS1DataChunk::ParseSettingsF3V6(const unsigned char* data, int size) { static const QMap expected_lengths = { {0x1e,3}, {0x35,2} }; bool ok = true; PRS1Mode cpapmode = PRS1_MODE_UNKNOWN; FlexMode flexmode = FLEX_Unknown; // F5V3 and F3V6 use a gain of 0.125 rather than 0.1 to allow for a maximum value of 30 cmH2O static const float GAIN = 0.125; // TODO: parameterize this somewhere better int fixed_pressure = 0; int fixed_epap = 0; int fixed_ipap = 0; int min_ipap = 0; int max_ipap = 0; int breath_rate; int timed_inspiration; // Parse the nested data structure which contains settings int pos = 0; do { int code = data[pos++]; int len = data[pos++]; int expected_len = 1; if (expected_lengths.contains(code)) { expected_len = expected_lengths[code]; } //CHECK_VALUE(len, expected_len); if (len < expected_len) { qWarning() << this->sessionid << "setting" << code << "too small" << len << "<" << expected_len; ok = false; break; } if (pos + len > size) { qWarning() << this->sessionid << "setting" << code << "@" << pos << "longer than remaining slice"; ok = false; break; } switch (code) { case 0: // Device Mode CHECK_VALUE(pos, 2); // always first? CHECK_VALUE(len, 1); switch (data[pos]) { case 0: cpapmode = PRS1_MODE_CPAP; break; // "CPAP" mode case 1: cpapmode = PRS1_MODE_S; break; // "S" mode case 2: cpapmode = PRS1_MODE_ST; break; // "S/T" mode; pressure seems variable? case 4: cpapmode = PRS1_MODE_PC; break; // "PC" mode? Usually "PC - AVAPS", see setting 1 below default: UNEXPECTED_VALUE(data[pos], "known device mode"); break; } break; case 1: // Flex Mode CHECK_VALUE(len, 1); switch (data[pos]) { case 0: // 0 = None switch (cpapmode) { case PRS1_MODE_CPAP: flexmode = FLEX_None; break; case PRS1_MODE_S: flexmode = FLEX_RiseTime; break; // reports say "None" but then list a rise time setting case PRS1_MODE_ST: flexmode = FLEX_RiseTime; break; // reports say "None" but then list a rise time setting default: UNEXPECTED_VALUE(cpapmode, "CPAP, S, or S/T"); break; } break; case 1: // 1 = Bi-Flex, only seen with "S - Bi-Flex" flexmode = FLEX_BiFlex; CHECK_VALUE(cpapmode, PRS1_MODE_S); break; case 2: // 2 = AVAPS: usually "PC - AVAPS", sometimes "S/T - AVAPS" switch (cpapmode) { case PRS1_MODE_ST: cpapmode = PRS1_MODE_ST_AVAPS; break; case PRS1_MODE_PC: cpapmode = PRS1_MODE_PC_AVAPS; break; default: UNEXPECTED_VALUE(cpapmode, "S/T or PC"); break; } flexmode = FLEX_RiseTime; // reports say "AVAPS" but then list a rise time setting break; default: UNEXPECTED_VALUE(data[pos], "known flex mode"); break; } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode)); break; case 2: // ??? Maybe AAM? CHECK_VALUE(len, 1); CHECK_VALUE(data[pos], 0); break; case 3: // CPAP Pressure CHECK_VALUE(len, 1); CHECK_VALUE(cpapmode, PRS1_MODE_CPAP); fixed_pressure = data[pos]; this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE, fixed_pressure, GAIN)); break; case 4: // EPAP Pressure CHECK_VALUE(len, 1); if (cpapmode == PRS1_MODE_CPAP) UNEXPECTED_VALUE(cpapmode, "!cpap"); // pressures seem variable on practice, maybe due to ramp or leaks? fixed_epap = data[pos]; this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP, fixed_epap, GAIN)); break; case 7: // IPAP Pressure CHECK_VALUE(len, 1); CHECK_VALUES(cpapmode, PRS1_MODE_S, PRS1_MODE_ST); // pressures seem variable on practice, maybe due to ramp or leaks? fixed_ipap = data[pos]; this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP, fixed_ipap, GAIN)); // TODO: We need to revisit whether PS should be shown as a setting. this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS, fixed_ipap - fixed_epap, GAIN)); if (fixed_epap == 0) UNEXPECTED_VALUE(fixed_epap, ">0"); break; case 8: // Min IPAP CHECK_VALUE(len, 1); CHECK_VALUE(fixed_ipap, 0); CHECK_VALUES(cpapmode, PRS1_MODE_ST_AVAPS, PRS1_MODE_PC_AVAPS); min_ipap = data[pos]; this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, min_ipap, GAIN)); // TODO: We need to revisit whether PS should be shown as a setting. this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, min_ipap - fixed_epap, GAIN)); if (fixed_epap == 0) UNEXPECTED_VALUE(fixed_epap, ">0"); break; case 9: // Max IPAP CHECK_VALUE(len, 1); CHECK_VALUE(fixed_ipap, 0); CHECK_VALUES(cpapmode, PRS1_MODE_ST_AVAPS, PRS1_MODE_PC_AVAPS); if (min_ipap == 0) UNEXPECTED_VALUE(min_ipap, ">0"); max_ipap = data[pos]; this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, max_ipap, GAIN)); // TODO: We need to revisit whether PS should be shown as a setting. this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, max_ipap - fixed_epap, GAIN)); if (fixed_epap == 0) UNEXPECTED_VALUE(fixed_epap, ">0"); break; case 0x19: // Tidal Volume (AVAPS) CHECK_VALUE(len, 1); CHECK_VALUES(cpapmode, PRS1_MODE_ST_AVAPS, PRS1_MODE_PC_AVAPS); //CHECK_VALUE(data[pos], 47); // gain 10.0 this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_TIDAL_VOLUME, data[pos] * 10.0)); break; case 0x1e: // (Backup) Breath Rate (S/T and PC) CHECK_VALUE(len, 3); if (cpapmode == PRS1_MODE_CPAP || cpapmode == PRS1_MODE_S) UNEXPECTED_VALUE(cpapmode, "S/T or PC"); switch (data[pos]) { case 0: // Breath Rate Off // TODO: Is this mode essentially bilevel? The pressure graphs are confusing. this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_MODE, PRS1Backup_Off)); CHECK_VALUE(data[pos+1], 0); CHECK_VALUE(data[pos+2], 0); break; //case 1: // Breath Rate Auto in F5V3 setting 0x14 case 2: // Breath Rate (fixed BPM) breath_rate = data[pos+1]; timed_inspiration = data[pos+2]; if (breath_rate < 9 || breath_rate > 15) UNEXPECTED_VALUE(breath_rate, "9-15"); if (timed_inspiration < 8 || timed_inspiration > 20) UNEXPECTED_VALUE(timed_inspiration, "8-20"); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_MODE, PRS1Backup_Fixed)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_RATE, breath_rate)); this->AddEvent(new PRS1ScaledSettingEvent(PRS1_SETTING_BACKUP_TIMED_INSPIRATION, timed_inspiration, 0.1)); break; default: CHECK_VALUES(data[pos], 0, 2); // 0 = Breath Rate off (S), 2 = fixed BPM (1 = auto on F5V3 setting 0x14, haven't seen it on F3V6 yet) break; } break; //0x2b: Ramp type sounds like it's linear for F3V6 unless AAM is enabled, so no setting may be needed. case 0x2c: // Ramp Time CHECK_VALUE(len, 1); if (data[pos] != 0) { // 0 == ramp off, and ramp pressure setting doesn't appear if (data[pos] < 5 || data[pos] > 45) UNEXPECTED_VALUE(data[pos], "5-45"); } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, data[pos])); break; case 0x2d: // Ramp Pressure (with ASV/ventilator pressure encoding), only present when ramp is on CHECK_VALUE(len, 1); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, data[pos], GAIN)); break; case 0x2e: // Bi-Flex level or Rise Time // On F5V3 the first byte could specify Bi-Flex or Rise Time, and second byte contained the value. // On F3V6 there's only one byte, which seems to correspond to Rise Time on the reports with flex // mode None or AVAPS and to Bi-Flex Setting (level) in Bi-Flex mode. CHECK_VALUE(len, 1); if (flexmode == FLEX_BiFlex) { // Bi-Flex level this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, data[pos])); } else if (flexmode == FLEX_RiseTime) { // Rise time if (data[pos] < 1 || data[pos] > 6) UNEXPECTED_VALUE(data[pos], "1-6"); // 1-6 have been seen this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RISE_TIME, data[pos])); } else { UNEXPECTED_VALUE(flexmode, "BiFlex or RiseTime"); } // Timed inspiration specified in the backup breath rate. break; case 0x2f: // Flex / Rise Time lock CHECK_VALUE(len, 1); if (flexmode == FLEX_BiFlex) { CHECK_VALUE(cpapmode, PRS1_MODE_S); CHECK_VALUES(data[pos], 0, 0x80); // Bi-Flex Lock this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LOCK, data[pos] != 0)); } else if (flexmode == FLEX_RiseTime) { CHECK_VALUES(data[pos], 0, 0x80); // Rise Time Lock this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RISE_TIME_LOCK, data[pos] != 0)); } else { UNEXPECTED_VALUE(flexmode, "BiFlex or RiseTime"); } break; case 0x35: // Humidifier setting CHECK_VALUE(len, 2); this->ParseHumidifierSettingV3(data[pos], data[pos+1], true); break; case 0x36: // Mask Resistance Lock CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_LOCK, data[pos] != 0)); break; case 0x38: // Mask Resistance CHECK_VALUE(len, 1); if (data[pos] != 0) { // 0 == mask resistance off if (data[pos] < 1 || data[pos] > 5) UNEXPECTED_VALUE(data[pos], "1-5"); } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_SETTING, data[pos])); break; case 0x39: // Tubing Type Lock CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_TUBING_LOCK, data[pos] != 0)); break; case 0x3b: // Tubing Type CHECK_VALUE(len, 1); if (data[pos] != 0) { CHECK_VALUES(data[pos], 2, 1); // 15HT = 2, 15 = 1, 22 = 0, though report only says "15" for 15HT } this->ParseTubingTypeV3(data[pos]); break; case 0x3c: // View Optional Screens CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SHOW_AHI, data[pos] != 0)); break; default: UNEXPECTED_VALUE(code, "known setting"); qDebug() << "Unknown setting:" << hex << code << "in" << this->sessionid << "at" << pos; this->AddEvent(new PRS1UnknownDataEvent(QByteArray((const char*) data, size), pos, len)); break; } pos += len; } while (ok && pos + 2 <= size); return ok; } const QVector ParsedEventsF3V6 = { PRS1TimedBreathEvent::TYPE, PRS1IPAPAverageEvent::TYPE, PRS1EPAPAverageEvent::TYPE, PRS1TotalLeakEvent::TYPE, PRS1RespiratoryRateEvent::TYPE, PRS1PatientTriggeredBreathsEvent::TYPE, PRS1MinuteVentilationEvent::TYPE, PRS1TidalVolumeEvent::TYPE, PRS1Test2Event::TYPE, PRS1Test1Event::TYPE, PRS1SnoreEvent::TYPE, // No individual VS, only snore count PRS1LeakEvent::TYPE, PRS1PressurePulseEvent::TYPE, PRS1ObstructiveApneaEvent::TYPE, PRS1ClearAirwayEvent::TYPE, PRS1HypopneaEvent::TYPE, PRS1PeriodicBreathingEvent::TYPE, PRS1RERAEvent::TYPE, PRS1LargeLeakEvent::TYPE, PRS1ApneaAlarmEvent::TYPE, // No FL? }; // 1030X, 11030X series // based on ParseEventsF5V3, updated for F3V6 bool PRS1DataChunk::ParseEventsF3V6(void) { if (this->family != 3 || this->familyVersion != 6) { qWarning() << "ParseEventsF3V6 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const int minimum_sizes[] = { 2, 3, 0xe, 3, 3, 3, 4, 5, 3, 5, 3, 3, 2, 2, 2, 2 }; static const int ncodes = sizeof(minimum_sizes) / sizeof(int); if (chunk_size < 1) { // This does occasionally happen. qDebug() << this->sessionid << "Empty event data"; return false; } // F3V6 uses a gain of 0.125 rather than 0.1 to allow for a maximum value of 30 cmH2O static const float GAIN = 0.125; // TODO: this should be parameterized somewhere more logical bool ok = true; int pos = 0, startpos; int code, size; int t = 0; int elapsed, duration; do { code = data[pos++]; if (!this->hblock.contains(code)) { qWarning() << this->sessionid << "missing hblock entry for event" << code; ok = false; break; } size = this->hblock[code]; if (code < ncodes) { // make sure the handlers below don't go past the end of the buffer if (size < minimum_sizes[code]) { qWarning() << this->sessionid << "event" << code << "too small" << size << "<" << minimum_sizes[code]; ok = false; break; } } // else if it's past ncodes, we'll log its information below (rather than handle it) if (pos + size > chunk_size) { qWarning() << this->sessionid << "event" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } startpos = pos; t += data[pos] | (data[pos+1] << 8); pos += 2; switch (code) { // case 0x00? case 1: // Timed Breath // TB events have a duration in 0.1s, based on the review of pressure waveforms. // TODO: Ideally the starting time here would be adjusted here, but PRS1ParsedEvents // currently assume integer seconds rather than ms, so that's done at import. duration = data[pos]; // TODO: make sure F3 import logic matches F5 in adjusting TB start time this->AddEvent(new PRS1TimedBreathEvent(t, duration)); break; case 2: // Statistics // These appear every 2 minutes, so presumably summarize the preceding period. //data[pos+0]; // TODO: 0 = ??? this->AddEvent(new PRS1IPAPAverageEvent(t, data[pos+2], GAIN)); // 02=IPAP this->AddEvent(new PRS1EPAPAverageEvent(t, data[pos+1], GAIN)); // 01=EPAP, needs to be added second to calculate PS this->AddEvent(new PRS1TotalLeakEvent(t, data[pos+3])); // 03=Total leak (average?) this->AddEvent(new PRS1RespiratoryRateEvent(t, data[pos+4])); // 04=Breaths Per Minute (average?) this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, data[pos+5])); // 05=Patient Triggered Breaths (average?) this->AddEvent(new PRS1MinuteVentilationEvent(t, data[pos+6])); // 06=Minute Ventilation (average?) this->AddEvent(new PRS1TidalVolumeEvent(t, data[pos+7])); // 07=Tidal Volume (average?) this->AddEvent(new PRS1Test2Event(t, data[pos+8])); // 08=Flow??? this->AddEvent(new PRS1Test1Event(t, data[pos+9])); // 09=TMV??? this->AddEvent(new PRS1SnoreEvent(t, data[pos+0xa])); // 0A=Snore count // TODO: not a VS on official waveform, but appears in flags and contributes to overall VS index this->AddEvent(new PRS1LeakEvent(t, data[pos+0xb])); // 0B=Leak (average?) this->AddEvent(new PRS1IntervalBoundaryEvent(t)); break; case 0x03: // Pressure Pulse duration = data[pos]; // TODO: is this a duration? this->AddEvent(new PRS1PressurePulseEvent(t, duration)); break; case 0x04: // Obstructive Apnea // OA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ObstructiveApneaEvent(t - elapsed, 0)); break; case 0x05: // Clear Airway Apnea // CA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ClearAirwayEvent(t - elapsed, 0)); break; case 0x06: // Hypopnea // TODO: How is this hypopnea different from events 0xd and 0xe? // TODO: What is the first byte? //data[pos+0]; // unknown first byte? elapsed = data[pos+1]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; case 0x07: // Periodic Breathing // PB events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; this->AddEvent(new PRS1PeriodicBreathingEvent(t - elapsed - duration, duration)); break; case 0x08: // RERA elapsed = data[pos]; // based on sample waveform, the RERA is over after this this->AddEvent(new PRS1RERAEvent(t - elapsed, 0)); break; case 0x09: // Large Leak // LL events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; this->AddEvent(new PRS1LargeLeakEvent(t - elapsed - duration, duration)); break; case 0x0a: // Hypopnea // TODO: Why does this hypopnea have a different event code? // fall through case 0x0b: // Hypopnea // TODO: We should revisit whether this is elapsed or duration once (if) // we start calculating hypopneas ourselves. Their official definition // is 40% reduction in flow lasting at least 10s. duration = data[pos]; this->AddEvent(new PRS1HypopneaEvent(t - duration, 0)); break; case 0x0c: // Apnea Alarm // no additional data this->AddEvent(new PRS1ApneaAlarmEvent(t, 0)); break; case 0x0d: // Low MV Alarm // no additional data this->AddEvent(new PRS1LowMinuteVentilationAlarmEvent(t, 0)); break; // case 0x0e? // case 0x0f? default: DUMP_EVENT(); UNEXPECTED_VALUE(code, "known event code"); this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos-1, size+1)); break; } pos = startpos + size; } while (ok && pos < chunk_size); this->duration = t; return ok; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/prs1_parser_xpap.cpp000066400000000000000000003475031417327530600253170ustar00rootroot00000000000000/* PRS1 Parsing for CPAP and BIPAP (Family 0) * * Copyright (c) 2019-2022 The OSCAR Team * Portions copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "prs1_parser.h" #include "prs1_loader.h" //******************************************************************************************** // MARK: 50 Series bool PRS1DataChunk::ParseComplianceF0V23(void) { if (this->family != 0 || (this->familyVersion != 2 && this->familyVersion != 3)) { qWarning() << "ParseComplianceF0V23 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } // All sample machines with FamilyVersion 3 in the properties.txt file have familyVersion 2 in their .001/.002/.005 files! // We should flag an actual familyVersion 3 file if we ever encounter one! CHECK_VALUE(this->familyVersion, 2); const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const int minimum_sizes[] = { 0xd, 5, 2, 2 }; static const int ncodes = sizeof(minimum_sizes) / sizeof(int); // NOTE: These are fixed sizes, but are called minimum to more closely match the F0V6 parser. bool ok = true; int pos = 0; int code, size, delta; int tt = 0; while (ok && pos < chunk_size) { code = data[pos++]; // There is no hblock prior to F0V6. size = 0; if (code < ncodes) { // make sure the handlers below don't go past the end of the buffer size = minimum_sizes[code]; } // else if it's past ncodes, we'll log its information below (rather than handle it) if (pos + size > chunk_size) { qWarning() << this->sessionid << "slice" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } switch (code) { case 0: // Equipment On CHECK_VALUE(pos, 1); // Always first CHECK_VALUES(data[pos], 1, 0); // usually 1, occasionally 0, no visible difference in report // F0V23 doesn't have a separate settings record like F0V6 does, the settings just follow the EquipmentOn data. ok = ParseSettingsF0V23(data, 0x0e); // Compliance doesn't have pressure set events following settings like summary does. break; case 2: // Mask On delta = data[pos] | (data[pos+1] << 8); if (tt == 0) { CHECK_VALUE(delta, 0); // we've never seen the initial MaskOn have any delta } else { if (delta % 60) UNEXPECTED_VALUE(delta, "even minutes"); // mask-off events seem to be whole minutes? } tt += delta; this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn)); // no per-slice humidifer settings as in F0V6 break; case 3: // Mask Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff)); // Compliance doesn't record any stats after mask-off like summary does. break; case 1: // Equipment Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff)); // also seems to be a trailing 01 00 81 after the slices? CHECK_VALUES(data[pos+2], 1, 0); // usually 1, occasionally 0, no visible difference in report //CHECK_VALUE(data[pos+3], 0); // sometimes 1, 2, or 5, no visible difference in report, maybe ramp? ParseHumidifierSetting50Series(data[pos+4]); break; default: UNEXPECTED_VALUE(code, "known slice code"); ok = false; // unlike F0V6, we don't know the size of unknown slices, so we can't recover break; } pos += size; } if (ok && pos != chunk_size) { qWarning() << this->sessionid << (this->size() - pos) << "trailing bytes"; } this->duration = tt; return ok; } bool PRS1DataChunk::ParseSummaryF0V23() { if (this->family != 0 || (this->familyVersion != 2 && this->familyVersion != 3)) { qWarning() << "ParseSummaryF0V23 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } // All sample machines with FamilyVersion 3 in the properties.txt file have familyVersion 2 in their .001/.002/.005 files! // We should flag an actual familyVersion 3 file if we ever encounter one! CHECK_VALUE(this->familyVersion, 2); const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const int minimum_sizes[] = { 0xf, 5, 2, 0x21, 0, 4 }; static const int ncodes = sizeof(minimum_sizes) / sizeof(int); // NOTE: These are fixed sizes, but are called minimum to more closely match the F0V6 parser. bool ok = true; int pos = 0; int code, size, delta; int tt = 0; while (ok && pos < chunk_size) { code = data[pos++]; // There is no hblock prior to F0V6. size = 0; if (code < ncodes) { // make sure the handlers below don't go past the end of the buffer size = minimum_sizes[code]; } // else if it's past ncodes, we'll log its information below (rather than handle it) if (pos + size > chunk_size) { qWarning() << this->sessionid << "slice" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } switch (code) { case 0: // Equipment On CHECK_VALUE(pos, 1); // Always first CHECK_VALUES(data[pos] & 0xF0, 0x60, 0x70); // TODO: what are these? switch (data[pos] & 0x0F) { case 0: // TODO: What is this? It seems to be related to errors. case 1: // This is the most frequent value. case 3: // TODO: What is this? case 4: // This seems to be related to an automatic transition from CPAP to AutoCPAP. break; default: UNEXPECTED_VALUE(data[pos] & 0x0F, "[0,1,3,4]"); } // F0V23 doesn't have a separate settings record like F0V6 does, the settings just follow the EquipmentOn data. ok = ParseSettingsF0V23(data, 0x0e); // TODO: register these as pressure set events //CHECK_VALUES(data[0x0e], ramp_pressure, min_pressure); // initial CPAP/EPAP, can be minimum pressure or ramp, or whatever auto decides to use //if (cpapmode == PRS1_MODE_BILEVEL) { // initial IPAP for bilevel modes // CHECK_VALUE(data[0x0f], max_pressure); //} else if (cpapmode == PRS1_MODE_AUTOBILEVEL) { // CHECK_VALUE(data[0x0f], min_pressure + 20); //} break; case 2: // Mask On delta = data[pos] | (data[pos+1] << 8); if (tt == 0) { if (delta) { CHECK_VALUES(delta, 1, 59); // we've seen the 550P start its first mask-on at these time deltas } } else { if (delta % 60) { if (this->familyVersion == 2 && ((delta + 1) % 60) == 0) { // For some reason F0V2 frequently is frequently 1 second less than whole minute intervals. } else { UNEXPECTED_VALUE(delta, "even minutes"); // mask-off events seem to be whole minutes? } } } tt += delta; this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn)); // no per-slice humidifer settings as in F0V6 break; case 3: // Mask Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff)); // F0V23 doesn't have a separate stats record like F0V6 does, the stats just follow the MaskOff data. // These are 0x22 bytes in a summary vs. 3 bytes in compliance data // TODO: What are these values? break; case 1: // Equipment Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff)); switch (data[pos+2]) { case 0: // TODO: What is this? It seems to be related to errors. case 1: // This is the usual value. case 3: // TODO: What is this? This has been seen after 90 sec large leak before turning off. case 4: // TODO: What is this? We've seen it once. case 5: // This seems to be related to an automatic transition from CPAP to AutoCPAP. break; default: UNEXPECTED_VALUE(data[pos+2], "[0,1,3,4,5]"); } //CHECK_VALUES(data[pos+3], 0, 1); // TODO: may be related to ramp? 1-5 seems to have a ramp start or two ParseHumidifierSetting50Series(data[pos+4]); break; case 5: // Clock adjustment? See ParseSummaryF0V4. CHECK_VALUE(pos, 1); // Always first CHECK_VALUE(chunk_size, 5); // and the only record in the session. if (false) { long value = data[pos] | data[pos+1]<<8 | data[pos+2]<<16 | data[pos+3]<<24; qDebug() << this->sessionid << "clock changing from" << ts(value * 1000L) << "to" << ts(this->timestamp * 1000L) << "delta:" << (this->timestamp - value); } break; case 6: // Cleared? // Appears in the very first session when that session number is > 1. // Presumably previous sessions were cleared out. // TODO: add an internal event for this. CHECK_VALUE(pos, 1); // Always first CHECK_VALUE(chunk_size, 1); // and the only record in the session. if (this->sessionid == 1) UNEXPECTED_VALUE(this->sessionid, ">1"); break; default: UNEXPECTED_VALUE(code, "known slice code"); ok = false; // unlike F0V6, we don't know the size of unknown slices, so we can't recover break; } pos += size; } if (ok && pos != chunk_size) { qWarning() << this->sessionid << (this->size() - pos) << "trailing bytes"; } this->duration = tt; return ok; } bool PRS1DataChunk::ParseSettingsF0V23(const unsigned char* data, int /*size*/) { PRS1Mode cpapmode = PRS1_MODE_UNKNOWN; switch (data[0x02]) { // PRS1 mode // 0 = CPAP, 2 = APAP case 0x00: cpapmode = PRS1_MODE_CPAP; break; case 0x01: cpapmode = PRS1_MODE_BILEVEL; break; case 0x02: cpapmode = PRS1_MODE_AUTOCPAP; break; case 0x03: cpapmode = PRS1_MODE_AUTOBILEVEL; break; default: UNEXPECTED_VALUE(data[0x02], "known device mode"); break; } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode)); int min_pressure = data[0x03]; int max_pressure = data[0x04]; int ps = data[0x05]; // max pressure support (for variable), seems to be zero otherwise if (cpapmode == PRS1_MODE_CPAP) { this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE, min_pressure)); //CHECK_VALUE(max_pressure, 0); // occasionally nonzero, usually seems to be when the next session is AutoCPAP with this max CHECK_VALUE(ps, 0); } else if (cpapmode == PRS1_MODE_AUTOCPAP) { this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MIN, min_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MAX, max_pressure)); CHECK_VALUE(ps, 0); } else if (cpapmode == PRS1_MODE_BILEVEL) { this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP, min_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP, max_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS, max_pressure - min_pressure)); CHECK_VALUE(ps, 0); // this seems to be unused on fixed bilevel } else if (cpapmode == PRS1_MODE_AUTOBILEVEL) { int min_ps = 20; // 2.0 cmH2O this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, min_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MAX, max_pressure - min_ps)); // TODO: not yet confirmed this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, min_pressure + min_ps)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, max_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, min_ps)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, ps)); } int ramp_time = data[0x06]; int ramp_pressure = data[0x07]; if (ramp_time > 0) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure)); } quint8 flex = data[0x08]; this->ParseFlexSettingF0V2345(flex, cpapmode); int humid = data[0x09]; this->ParseHumidifierSetting50Series(humid, true); // Tubing lock has no setting byte // Menu Options bool mask_resist_on = ((data[0x0a] & 0x40) != 0); // System One Resistance Status bit int mask_resist_setting = data[0x0a] & 7; // System One Resistance setting value CHECK_VALUE(mask_resist_on, mask_resist_setting > 0); // Confirm that we can ignore the status bit. this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_LOCK, (data[0x0a] & 0x80) != 0)); // System One Resistance Lock Setting, only seen on bricks this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HOSE_DIAMETER, (data[0x0a] & 0x08) ? 15 : 22)); // TODO: unconfirmed this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_SETTING, mask_resist_setting)); CHECK_VALUE(data[0x0a] & (0x20 | 0x10), 0); CHECK_VALUE(data[0x0b], 1); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_AUTO_ON, (data[0x0c] & 0x40) != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_AUTO_OFF, (data[0x0c] & 0x10) != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_ALERT, (data[0x0c] & 0x04) != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SHOW_AHI, (data[0x0c] & 0x02) != 0)); CHECK_VALUE(data[0x0c] & (0xA0 | 0x09), 0); CHECK_VALUE(data[0x0d], 0); return true; } // Flex F0V2 confirmed // 0x00 = None // 0x81 = C-Flex 1, lock off (AutoCPAP mode) // 0x82 = Bi-Flex 2 (Bi-Level mode) // 0x89 = A-Flex 1 (AutoCPAP mode) // 0x8A = A-Flex 2, lock off (AutoCPAP mode) // 0x8B = C-Flex+ 3, lock off (CPAP mode) // 0x93 = Rise Time 3 (AutoBiLevel mode) // Flex F0V4 confirmed // 0x00 = None // 0x81 = Bi-Flex 1 (AutoBiLevel mode) // 0x81 = C-Flex 1 (AutoCPAP mode) // 0x82 = C-Flex 2 (CPAP mode) // 0x82 = C-Flex 2 (CPAP-Check mode) // 0x82 = C-Flex 2 (Auto-Trial mode) // 0x83 = Bi-Flex 3 (Bi-Level mode) // 0x89 = A-Flex 1 (AutoCPAP mode) // 0x8A = C-Flex+ 2 (CPAP mode) // 0x8A = C-Flex+ 2, lock off (CPAP-Check mode) // 0x8A = A-Flex 2, lock off (Auto-Trial mode) // 0xCB = C-Flex+ 3 (CPAP-Check mode), C-Flex+ Lock on // // 0x8A = A-Flex 1 (AutoCPAP mode) // 0x8B = C-Flex+ 3 (CPAP mode) // 0x8B = A-Flex 3 (AutoCPAP mode) // Flex F0V5 confirmed // 0xE1 = Flex (AutoCPAP mode) // 0xA1 = Flex (AutoCPAP mode) // 0xA2 = Flex (AutoCPAP mode) // 8 = enabled // 4 = lock // 2 = Flex (only seen on Dorma series) // 1 = rise time // 8 = C-Flex+ / A-Flex (depending on mode) // 3 = level void PRS1DataChunk::ParseFlexSettingF0V2345(quint8 flex, int cpapmode) { FlexMode flexmode = FLEX_None; bool enabled = (flex & 0x80) != 0; bool lock = (flex & 0x40) != 0; bool plain_flex = (flex & 0x20) != 0; // "Flex", seen on Dorma series bool risetime = (flex & 0x10) != 0; bool plusmode = (flex & 0x08) != 0; int flexlevel = flex & 0x03; if (flex & 0x04) UNEXPECTED_VALUE(flex, "known bits"); if (this->familyVersion == 2) { //CHECK_VALUE(lock, false); // We've seen this set on F0V2, but it doesn't appear on the reports. } if (enabled) { if (flexlevel < 1) UNEXPECTED_VALUE(flexlevel, "!= 0"); if (risetime) { flexmode = FLEX_RiseTime; CHECK_VALUES(cpapmode, PRS1_MODE_BILEVEL, PRS1_MODE_AUTOBILEVEL); CHECK_VALUE(plusmode, 0); } else if (plusmode) { switch (cpapmode) { case PRS1_MODE_CPAP: case PRS1_MODE_CPAPCHECK: flexmode = FLEX_CFlexPlus; break; case PRS1_MODE_AUTOCPAP: case PRS1_MODE_AUTOTRIAL: flexmode = FLEX_AFlex; break; default: HEX(flex); UNEXPECTED_VALUE(cpapmode, "expected C-Flex+/A-Flex mode"); break; } } else if (plain_flex) { CHECK_VALUE(this->familyVersion, 5); // so far only seen with F0V5 switch (cpapmode) { case PRS1_MODE_AUTOCPAP: flexmode = FLEX_Flex; // unknown whether this is equivalent to C-Flex, C-Flex+, or A-Flex break; default: UNEXPECTED_VALUE(cpapmode, "expected mode"); flexmode = FLEX_Flex; // probably the same for CPAP mode as well, but we haven't tested that yet break; } } else { switch (cpapmode) { case PRS1_MODE_CPAP: case PRS1_MODE_CPAPCHECK: case PRS1_MODE_AUTOCPAP: case PRS1_MODE_AUTOTRIAL: flexmode = FLEX_CFlex; break; case PRS1_MODE_BILEVEL: case PRS1_MODE_AUTOBILEVEL: flexmode = FLEX_BiFlex; break; default: HEX(flex); UNEXPECTED_VALUE(cpapmode, "expected mode"); break; } } } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode)); if (flexmode != FLEX_None) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, flexlevel)); } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LOCK, lock)); } const QVector ParsedEventsF0V23 = { PRS1PressureSetEvent::TYPE, PRS1IPAPSetEvent::TYPE, PRS1EPAPSetEvent::TYPE, PRS1PressurePulseEvent::TYPE, PRS1RERAEvent::TYPE, PRS1ObstructiveApneaEvent::TYPE, PRS1ClearAirwayEvent::TYPE, PRS1HypopneaEvent::TYPE, PRS1FlowLimitationEvent::TYPE, PRS1VibratorySnoreEvent::TYPE, PRS1VariableBreathingEvent::TYPE, PRS1PeriodicBreathingEvent::TYPE, PRS1LargeLeakEvent::TYPE, PRS1TotalLeakEvent::TYPE, PRS1SnoreEvent::TYPE, PRS1SnoresAtPressureEvent::TYPE, }; // 750P is F0V2; 550P is F0V2/F0V3 (properties.txt sometimes says F0V3, data files always say F0V2); 450P is F0V3 bool PRS1DataChunk::ParseEventsF0V23() { if (this->family != 0 || this->familyVersion < 2 || this->familyVersion > 3) { qWarning() << "ParseEventsF0V23 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } // All sample machines with FamilyVersion 3 in the properties.txt file have familyVersion 2 in their .001/.002/.005 files! // We should flag an actual familyVersion 3 file if we ever encounter one! CHECK_VALUE(this->familyVersion, 2); const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const QMap event_sizes = { {1,2}, {3,4}, {0xb,4}, {0xd,2}, {0xe,5}, {0xf,5}, {0x10,5}, {0x11,4}, {0x12,4} }; if (chunk_size < 1) { // This does occasionally happen in F0V6. qDebug() << this->sessionid << "Empty event data"; return false; } bool ok = true; int pos = 0, startpos; int code, size; int t = 0; int elapsed, duration, value; do { code = data[pos++]; size = 3; // default size = 2 bytes time delta + 1 byte data if (event_sizes.contains(code)) { size = event_sizes[code]; } if (pos + size > chunk_size) { qWarning() << this->sessionid << "event" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } startpos = pos; if (code != 0x12 && code != 0x01) { // This one event has no timestamp in F0V6 elapsed = data[pos] | (data[pos+1] << 8); if (elapsed > 0x7FFF) UNEXPECTED_VALUE(elapsed, "<32768s"); // check whether this is generally unsigned, since 0x01 isn't t += elapsed; pos += 2; } switch (code) { case 0x00: // Humidifier setting change (logged in summary in 60 series) ParseHumidifierSetting50Series(data[pos]); if (this->familyVersion == 3) DUMP_EVENT(); break; case 0x01: // Time elapsed? // Only seen twice, on a 550P and 650P. // It looks almost like a time-elapsed event 4 found in F0V4 summaries, but // 0xFFCC looks like it represents a time adjustment of -52 seconds, // since the subsequent 0x11 statistics event has a time offset of 172 seconds, // and counting this as -52 seconds results in a total session time that // matches the summary and waveform data. Very weird. // // Similarly 0xFFDC looks like it represents a time adjustment of -36 seconds. CHECK_VALUES(data[pos], 0xDC, 0xCC); CHECK_VALUE(data[pos+1], 0xFF); elapsed = data[pos] | (data[pos+1] << 8); if (elapsed & 0x8000) { elapsed = (~0xFFFF | elapsed); // sign extend 16-bit number to native int } t += elapsed; break; case 0x02: // Pressure adjustment // See notes in ParseEventsF0V6. this->AddEvent(new PRS1PressureSetEvent(t, data[pos])); break; case 0x03: // Pressure adjustment (bi-level) // See notes in ParseEventsF0V6. this->AddEvent(new PRS1IPAPSetEvent(t, data[pos+1])); this->AddEvent(new PRS1EPAPSetEvent(t, data[pos])); // EPAP needs to be added second to calculate PS break; case 0x04: // Pressure Pulse duration = data[pos]; // TODO: is this a duration? this->AddEvent(new PRS1PressurePulseEvent(t, duration)); break; case 0x05: // RERA elapsed = data[pos++]; this->AddEvent(new PRS1RERAEvent(t - elapsed, 0)); break; case 0x06: // Obstructive Apnea // OA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ObstructiveApneaEvent(t - elapsed, 0)); break; case 0x07: // Clear Airway Apnea // CA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ClearAirwayEvent(t - elapsed, 0)); break; //case 0x08: // never seen //case 0x09: // never seen case 0x0a: // Hypopnea // TODO: How is this hypopnea different from events 0xb, [0x14 and 0x15 on F0V6]? elapsed = data[pos++]; this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; case 0x0b: // Hypopnea // TODO: How is this hypopnea different from events 0xa, [0x14 and 0x15 on F0V6]? // TODO: What is the first byte? //data[pos+0]; // unknown first byte? elapsed = data[pos+1]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; case 0x0c: // Flow Limitation // TODO: We should revisit whether this is elapsed or duration once (if) // we start calculating flow limitations ourselves. Flow limitations aren't // as obvious as OA/CA when looking at a waveform. elapsed = data[pos]; this->AddEvent(new PRS1FlowLimitationEvent(t - elapsed, 0)); break; case 0x0d: // Vibratory Snore // VS events are instantaneous flags with no duration, drawn on the official waveform. // The current thinking is that these are the snores that cause a change in auto-titrating // pressure. The snoring statistics below seem to be a total count. It's unclear whether // the trigger for pressure change is severity or count or something else. // no data bytes this->AddEvent(new PRS1VibratorySnoreEvent(t, 0)); break; case 0x0e: // Variable Breathing? // TODO: does duration double like F0V4? duration = (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; // this is always 60 seconds unless it's at the end, so it seems like elapsed CHECK_VALUES(elapsed, 60, 0); this->AddEvent(new PRS1VariableBreathingEvent(t - elapsed - duration, duration)); break; case 0x0f: // Periodic Breathing // PB events are reported some time after they conclude, and they do have a reported duration. // NOTE: F0V2 does NOT double this like F0V6 does if (this->familyVersion == 3) // double-check whether there's doubling on F0V3 DUMP_EVENT(); duration = (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; this->AddEvent(new PRS1PeriodicBreathingEvent(t - elapsed - duration, duration)); break; case 0x10: // Large Leak // LL events are reported some time after they conclude, and they do have a reported duration. // NOTE: F0V2 does NOT double this like F0V4 and F0V6 does if (this->familyVersion == 3) // double-check whether there's doubling on F0V3 DUMP_EVENT(); duration = (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; this->AddEvent(new PRS1LargeLeakEvent(t - elapsed - duration, duration)); break; case 0x11: // Statistics this->AddEvent(new PRS1TotalLeakEvent(t, data[pos])); this->AddEvent(new PRS1SnoreEvent(t, data[pos+1])); this->AddEvent(new PRS1IntervalBoundaryEvent(t)); break; case 0x12: // Snore count per pressure // Some sessions (with lots of ramps) have multiple of these, each with a // different pressure. The total snore count across all of them matches the // total found in the stats event. if (data[pos] != 0) { CHECK_VALUES(data[pos], 1, 2); // 0 = CPAP pressure, 1 = bi-level EPAP, 2 = bi-level IPAP } //CHECK_VALUE(data[pos+1], 0x78); // pressure //CHECK_VALUE(data[pos+2], 1); // 16-bit snore count //CHECK_VALUE(data[pos+3], 0); value = (data[pos+2] | (data[pos+3] << 8)); this->AddEvent(new PRS1SnoresAtPressureEvent(t, data[pos], data[pos+1], value)); break; default: DUMP_EVENT(); UNEXPECTED_VALUE(code, "known event code"); this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos)); ok = false; // unlike F0V6, we don't know the size of unknown events, so we can't recover break; } pos = startpos + size; } while (ok && pos < chunk_size); if (ok && pos != chunk_size) { qWarning() << this->sessionid << (this->size() - pos) << "trailing event bytes"; } this->duration = t; return ok; } //******************************************************************************************** // MARK: - // MARK: 60 Series bool PRS1DataChunk::ParseComplianceF0V4(void) { if (this->family != 0 || (this->familyVersion != 4)) { qWarning() << "ParseComplianceF0V4 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const int minimum_sizes[] = { 0x18, 7, 4, 2, 0, 0, 0, 4, 0 }; static const int ncodes = sizeof(minimum_sizes) / sizeof(int); // NOTE: These are fixed sizes, but are called minimum to more closely match the F0V6 parser. bool ok = true; int pos = 0; int code, size; int tt = 0; while (ok && pos < chunk_size) { code = data[pos++]; // There is no hblock prior to F0V6. size = 0; if (code < ncodes) { // make sure the handlers below don't go past the end of the buffer size = minimum_sizes[code]; } // else if it's past ncodes, we'll log its information below (rather than handle it) if (pos + size > chunk_size) { qWarning() << this->sessionid << "slice" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } switch (code) { case 0: // Equipment On CHECK_VALUE(pos, 1); // Always first CHECK_VALUES(data[pos], 1, 3); // F0V4 doesn't have a separate settings record like F0V6 does, the settings just follow the EquipmentOn data. ok = ParseSettingsF0V45(data, 0x11); CHECK_VALUE(data[pos+0x11], 0); CHECK_VALUE(data[pos+0x12], 0); CHECK_VALUE(data[pos+0x13], 0); CHECK_VALUE(data[pos+0x14], 0); CHECK_VALUE(data[pos+0x15], 0); CHECK_VALUE(data[pos+0x16], 0); CHECK_VALUE(data[pos+0x17], 0); break; case 2: // Mask On tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn)); this->ParseHumidifierSetting60Series(data[pos+2], data[pos+3]); break; case 3: // Mask Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff)); // Compliance doesn't have any MaskOff stats like summary does break; case 1: // Equipment Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff)); //CHECK_VALUES(data[pos+2], 1, 3); // or 0 //CHECK_VALUE(data[pos+2] & ~(0x40|8|4|2|1), 0); // ???, seen various bit combinations //CHECK_VALUE(data[pos+3], 0x19); // 0x17, 0x16 //CHECK_VALUES(data[pos+4], 0, 1); // or 2 //CHECK_VALUES(data[pos+4], 0, 1); // or 2 //CHECK_VALUE(data[pos+5], 0x35); // 0x36, 0x36 if (data[pos+6] != 1) { CHECK_VALUE(data[pos+6] & ~(4|2|1), 0); // On F0V23 0 seems to be related to errors, 3 seen after 90 sec large leak before turning off? } // pos+4 == 2, pos+6 == 10 on the session that had a time-elapsed event, maybe it shut itself off // when approaching 24h of continuous use? break; /* case 4: // Time Elapsed // For example: mask-on 5:18:49 in a session of 23:41:20 total leaves mask-off time of 18:22:31. // That's represented by a mask-off event 19129 seconds after the mask-on, then a time-elapsed // event after 65535 seconds, then an equipment off event after another 616 seconds. tt += data[pos] | (data[pos+1] << 8); // TODO: see if this event exists in earlier versions break; case 5: // Clock adjustment? CHECK_VALUE(pos, 1); // Always first CHECK_VALUE(chunk_size, 5); // and the only record in the session. // This looks like it's minor adjustments to the clock, but 560PBT-3917 sessions 1-2 are weird: // session 1 starts at 2015-12-23T00:01:20 and contains this event with timestamp 2015-12-23T00:05:14. // session 2 starts at 2015-12-23T00:01:29, which suggests the event didn't change the clock. // // It looks like this happens when there are discontinuities in timestamps, for example 560P-4727: // session 58 ends at 2015-05-26T09:53:17. // session 59 starts at 2015-05-26T09:53:15 with an event 5 timestamp of 2015-05-26T09:53:18. // // So the session/chunk timestamp has gone backwards. Whenever this happens, it seems to be in // a session with an event-5 event having a timestamp that hasn't gone backwards. So maybe // this timestamp is the old clock before adjustment? This would explain the 560PBT-3917 sessions above. // // This doesn't seem particularly associated with discontinuities in the waveform data: there are // often clock adjustments without corresponding discontinuities in the waveform, and vice versa. // It's possible internal clock inaccuracy causes both independently. // // TODO: why do some machines have lots of these and others none? Maybe cellular modems make daily tweaks? if (false) { long value = data[pos] | data[pos+1]<<8 | data[pos+2]<<16 | data[pos+3]<<24; qDebug() << this->sessionid << "clock changing from" << ts(value * 1000L) << "to" << ts(this->timestamp * 1000L) << "delta:" << (this->timestamp - value); } break; */ case 6: // Cleared? // Appears in the very first session when that session number is > 1. // Presumably previous sessions were cleared out. // TODO: add an internal event for this. CHECK_VALUE(pos, 1); // Always first CHECK_VALUE(chunk_size, 1); // and the only record in the session. if (this->sessionid == 1) UNEXPECTED_VALUE(this->sessionid, ">1"); break; case 7: // Humidifier setting change (logged in events in 50 series) tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) this->ParseHumidifierSetting60Series(data[pos+2], data[pos+3]); break; /* case 8: // CPAP-Check related, follows Mask On in CPAP-Check mode tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) //CHECK_VALUES(data[pos+2], 0, 79); // probably 16-bit value, sometimes matches OA + H + FL + VS + RE? CHECK_VALUE(data[pos+3], 0); //CHECK_VALUES(data[pos+4], 0, 10); // probably 16-bit value CHECK_VALUE(data[pos+5], 0); //CHECK_VALUES(data[pos+6], 0, 79); // probably 16-bit value, usually the same as +2, but not always? CHECK_VALUE(data[pos+7], 0); //CHECK_VALUES(data[pos+8], 0, 10); // probably 16-bit value CHECK_VALUE(data[pos+9], 0); //CHECK_VALUES(data[pos+0xa], 0, 4); // or 0? 44 when changed pressure mid-session? break; */ default: UNEXPECTED_VALUE(code, "known slice code"); ok = false; // unlike F0V6, we don't know the size of unknown slices, so we can't recover break; } pos += size; } if (ok && pos != chunk_size) { qWarning() << this->sessionid << (this->size() - pos) << "trailing bytes"; } this->duration = tt; return ok; } bool PRS1DataChunk::ParseSummaryF0V4(void) { if (this->family != 0 || (this->familyVersion != 4)) { qWarning() << "ParseSummaryF0V4 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const int minimum_sizes[] = { 0x18, 7, 7, 0x24, 2, 4, 0, 4, 0xb }; static const int ncodes = sizeof(minimum_sizes) / sizeof(int); // NOTE: These are fixed sizes, but are called minimum to more closely match the F0V6 parser. bool ok = true; int pos = 0; int code, size; int tt = 0; while (ok && pos < chunk_size) { code = data[pos++]; // There is no hblock prior to F0V6. size = 0; if (code < ncodes) { // make sure the handlers below don't go past the end of the buffer size = minimum_sizes[code]; } // else if it's past ncodes, we'll log its information below (rather than handle it) if (pos + size > chunk_size) { qWarning() << this->sessionid << "slice" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } switch (code) { case 0: // Equipment On CHECK_VALUE(pos, 1); // Always first CHECK_VALUES(data[pos] & 0xF0, 0x80, 0xC0); // TODO: what are these? if ((data[pos] & 0x0F) != 1) { // This is the most frequent value. //CHECK_VALUES(data[pos] & 0x0F, 3, 5); // TODO: what are these? 0 seems to be related to errors. } // F0V4 doesn't have a separate settings record like F0V6 does, the settings just follow the EquipmentOn data. ok = ParseSettingsF0V45(data, 0x11); CHECK_VALUE(data[pos+0x11], 0); CHECK_VALUE(data[pos+0x12], 0); CHECK_VALUE(data[pos+0x13], 0); CHECK_VALUE(data[pos+0x14], 0); CHECK_VALUE(data[pos+0x15], 0); CHECK_VALUE(data[pos+0x16], 0); CHECK_VALUE(data[pos+0x17], 0); break; case 2: // Mask On tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn)); //CHECK_VALUES(data[pos+2], 120, 110); // probably initial pressure //CHECK_VALUE(data[pos+3], 0); // initial IPAP on bilevel? //CHECK_VALUES(data[pos+4], 0, 130); // minimum pressure in auto-cpap this->ParseHumidifierSetting60Series(data[pos+5], data[pos+6]); break; case 3: // Mask Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff)); // F0V4 doesn't have a separate stats record like F0V6 does, the stats just follow the MaskOff data. //CHECK_VALUES(data[pos+2], 130); // probably ending pressure //CHECK_VALUE(data[pos+3], 0); // ending IPAP for bilevel? average? //CHECK_VALUES(data[pos+4], 0, 130); // 130 pressure in auto-cpap: min pressure? 90% IPAP in bilevel? //CHECK_VALUES(data[pos+5], 0, 130); // 130 pressure in auto-cpap, 90% EPAP in bilevel? //CHECK_VALUE(data[pos+6], 0); // 145 maybe max pressure in Auto-CPAP? //CHECK_VALUE(data[pos+7], 0); // Average 90% Pressure (Auto-CPAP) //CHECK_VALUE(data[pos+8], 0); // Average CPAP (Auto-CPAP) //CHECK_VALUES(data[pos+9], 0, 4); // or 1; PB count? LL count? minutes of something? CHECK_VALUE(data[pos+0xa], 0); //CHECK_VALUE(data[pos+0xb], 0); // OA count, probably 16-bit CHECK_VALUE(data[pos+0xc], 0); //CHECK_VALUE(data[pos+0xd], 0); CHECK_VALUE(data[pos+0xe], 0); //CHECK_VALUE(data[pos+0xf], 0); // CA count, probably 16-bit CHECK_VALUE(data[pos+0x10], 0); //CHECK_VALUE(data[pos+0x11], 40); // 16-bit something: 0x88, 0x26, etc. ??? //CHECK_VALUE(data[pos+0x12], 0); //CHECK_VALUE(data[pos+0x13], 0); // 16-bit minutes in LL //CHECK_VALUE(data[pos+0x14], 0); //CHECK_VALUE(data[pos+0x15], 0); // minutes in PB, probably 16-bit CHECK_VALUE(data[pos+0x16], 0); //CHECK_VALUE(data[pos+0x17], 0); // 16-bit VS count //CHECK_VALUE(data[pos+0x18], 0); //CHECK_VALUE(data[pos+0x19], 0); // H count, probably 16-bit CHECK_VALUE(data[pos+0x1a], 0); //CHECK_VALUE(data[pos+0x1b], 0); // 0 when no PB or LL? CHECK_VALUE(data[pos+0x1c], 0); //CHECK_VALUE(data[pos+0x1d], 9); // RE count, probably 16-bit CHECK_VALUE(data[pos+0x1e], 0); //CHECK_VALUE(data[pos+0x1f], 0); // FL count, probably 16-bit CHECK_VALUE(data[pos+0x20], 0); //CHECK_VALUE(data[pos+0x21], 0x32); // 0x55, 0x19 // ??? //CHECK_VALUE(data[pos+0x22], 0x23); // 0x3f, 0x14 // Average total leak //CHECK_VALUE(data[pos+0x23], 0x40); // 0x7d, 0x3d // ??? break; case 1: // Equipment Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff)); CHECK_VALUE(data[pos+2] & ~(0x40|8|4|2|1), 0); // ???, seen various bit combinations //CHECK_VALUE(data[pos+3], 0x19); // 0x17, 0x16 //CHECK_VALUES(data[pos+4], 0, 1); // or 2 //CHECK_VALUE(data[pos+5], 0x35); // 0x36, 0x36 if (data[pos+6] != 1) { // This is the usual value. CHECK_VALUE(data[pos+6] & ~(8|4|2|1), 0); // On F0V23 0 seems to be related to errors, 3 seen after 90 sec large leak before turning off? } // pos+4 == 2, pos+6 == 10 on the session that had a time-elapsed event, maybe it shut itself off // when approaching 24h of continuous use? break; case 4: // Time Elapsed // For example: mask-on 5:18:49 in a session of 23:41:20 total leaves mask-off time of 18:22:31. // That's represented by a mask-off event 19129 seconds after the mask-on, then a time-elapsed // event after 65535 seconds, then an equipment off event after another 616 seconds. tt += data[pos] | (data[pos+1] << 8); // TODO: see if this event exists in earlier versions break; case 5: // Clock adjustment? CHECK_VALUE(pos, 1); // Always first CHECK_VALUE(chunk_size, 5); // and the only record in the session. // This looks like it's minor adjustments to the clock, but 560PBT-3917 sessions 1-2 are weird: // session 1 starts at 2015-12-23T00:01:20 and contains this event with timestamp 2015-12-23T00:05:14. // session 2 starts at 2015-12-23T00:01:29, which suggests the event didn't change the clock. // // It looks like this happens when there are discontinuities in timestamps, for example 560P-4727: // session 58 ends at 2015-05-26T09:53:17. // session 59 starts at 2015-05-26T09:53:15 with an event 5 timestamp of 2015-05-26T09:53:18. // // So the session/chunk timestamp has gone backwards. Whenever this happens, it seems to be in // a session with an event-5 event having a timestamp that hasn't gone backwards. So maybe // this timestamp is the old clock before adjustment? This would explain the 560PBT-3917 sessions above. // // This doesn't seem particularly associated with discontinuities in the waveform data: there are // often clock adjustments without corresponding discontinuities in the waveform, and vice versa. // It's possible internal clock inaccuracy causes both independently. // // TODO: why do some machines have lots of these and others none? Maybe cellular modems make daily tweaks? if (false) { long value = data[pos] | data[pos+1]<<8 | data[pos+2]<<16 | data[pos+3]<<24; qDebug() << this->sessionid << "clock changing from" << ts(value * 1000L) << "to" << ts(this->timestamp * 1000L) << "delta:" << (this->timestamp - value); } break; case 6: // Cleared? // Appears in the very first session when that session number is > 1. // Presumably previous sessions were cleared out. // TODO: add an internal event for this. CHECK_VALUE(pos, 1); // Always first CHECK_VALUE(chunk_size, 1); // and the only record in the session. if (this->sessionid == 1) UNEXPECTED_VALUE(this->sessionid, ">1"); break; case 7: // Humidifier setting change (logged in events in 50 series) tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) this->ParseHumidifierSetting60Series(data[pos+2], data[pos+3]); break; case 8: // CPAP-Check related, follows Mask On in CPAP-Check mode tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) //CHECK_VALUES(data[pos+2], 0, 79); // probably 16-bit value, sometimes matches OA + H + FL + VS + RE? CHECK_VALUE(data[pos+3], 0); //CHECK_VALUES(data[pos+4], 0, 10); // probably 16-bit value CHECK_VALUE(data[pos+5], 0); //CHECK_VALUES(data[pos+6], 0, 79); // probably 16-bit value, usually the same as +2, but not always? CHECK_VALUE(data[pos+7], 0); //CHECK_VALUES(data[pos+8], 0, 10); // probably 16-bit value CHECK_VALUE(data[pos+9], 0); //CHECK_VALUES(data[pos+0xa], 0, 4); // or 0? 44 when changed pressure mid-session? break; default: UNEXPECTED_VALUE(code, "known slice code"); ok = false; // unlike F0V6, we don't know the size of unknown slices, so we can't recover break; } pos += size; } if (ok && pos != chunk_size) { qWarning() << this->sessionid << (this->size() - pos) << "trailing bytes"; } this->duration = tt; return ok; } bool PRS1DataChunk::ParseSettingsF0V45(const unsigned char* data, int size) { if (size < 0xd) { qWarning() << "invalid size passed to ParseSettingsF0V45"; return false; } PRS1Mode cpapmode = PRS1_MODE_UNKNOWN; switch (data[0x02]) { // PRS1 mode case 0x00: cpapmode = PRS1_MODE_CPAP; break; case 0x20: cpapmode = PRS1_MODE_BILEVEL; break; case 0x40: cpapmode = PRS1_MODE_AUTOCPAP; break; case 0x60: cpapmode = PRS1_MODE_AUTOBILEVEL; break; case 0x80: cpapmode = PRS1_MODE_AUTOTRIAL; // Auto-Trial TODO: where is duration? break; case 0xA0: cpapmode = PRS1_MODE_CPAPCHECK; break; default: UNEXPECTED_VALUE(data[0x02], "known device mode"); break; } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode)); int min_pressure = data[0x03]; int max_pressure = data[0x04]; int min_ps = data[0x05]; // pressure support int max_ps = data[0x06]; // pressure support if (cpapmode == PRS1_MODE_CPAP) { this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE, min_pressure)); CHECK_VALUE(max_pressure, 0); CHECK_VALUE(min_ps, 0); CHECK_VALUE(max_ps, 0); } else if (cpapmode == PRS1_MODE_AUTOCPAP || cpapmode == PRS1_MODE_AUTOTRIAL) { this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MIN, min_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MAX, max_pressure)); CHECK_VALUE(min_ps, 0); CHECK_VALUE(max_ps, 0); } else if (cpapmode == PRS1_MODE_CPAPCHECK) { // Sometimes the CPAP pressure is stored in max_ps instead of min_ps, not sure why. if (min_ps == 0) { if (max_ps == 0) UNEXPECTED_VALUE(max_ps, "nonzero"); min_ps = max_ps; } else { CHECK_VALUE(max_ps, 0); } this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE, min_ps)); // TODO: Once OSCAR can handle more modes, we can include these settings; right now including // these settings makes it think this is AutoCPAP. //this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MIN, min_pressure)); //this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MAX, max_pressure)); } else if (cpapmode == PRS1_MODE_BILEVEL) { this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP, min_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP, max_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS, max_pressure - min_pressure)); CHECK_VALUE(min_ps, 0); // this seems to be unused on fixed bilevel CHECK_VALUE(max_ps, 0); // this seems to be unused on fixed bilevel } else if (cpapmode == PRS1_MODE_AUTOBILEVEL) { this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, min_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MAX, max_pressure - min_ps)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, min_pressure + min_ps)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, max_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, min_ps)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, max_ps)); } CHECK_VALUES(data[0x07], 0, 0x20); // 0x20 seems to be Opti-Start int ramp_time = data[0x08]; int ramp_pressure = data[0x09]; if (ramp_time > 0) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure)); } quint8 flex = data[0x0a]; if (this->familyVersion == 5) { if (flex != 0xE1) CHECK_VALUES(flex, 0xA1, 0xA2); } this->ParseFlexSettingF0V2345(flex, cpapmode); if (this->familyVersion == 5) { CHECK_VALUES(data[0x0c], 0x60, 0x70); } this->ParseHumidifierSetting60Series(data[0x0b], data[0x0c], true); if (size <= 0xd) { return true; } int resist_level = (data[0x0d] >> 3) & 7; // 0x18 resist=3, 0x11 resist=2, 0x28 resist=5 this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_LOCK, (data[0x0d] & 0x40) != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_SETTING, resist_level)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HOSE_DIAMETER, (data[0x0d] & 0x01) ? 15 : 22)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_TUBING_LOCK, (data[0x0d] & 0x02) != 0)); CHECK_VALUE(data[0x0d] & (0x80|0x04), 0); CHECK_VALUE(data[0x0e], 1); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_AUTO_ON, (data[0x0f] & 0x40) != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_AUTO_OFF, (data[0x0f] & 0x10) != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_ALERT, (data[0x0f] & 0x04) != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SHOW_AHI, (data[0x0f] & 0x02) != 0)); CHECK_VALUE(data[0x0f] & (0xA0 | 0x08), 0); //CHECK_VALUE(data[0x0f] & 0x01, 0); // TODO: What is bit 1? It's sometimes set. // TODO: Where is altitude compensation set? We've seen it on 261CA. CHECK_VALUE(data[0x10], 0); int autotrial_duration = data[0x11]; if (cpapmode == PRS1_MODE_AUTOTRIAL) { CHECK_VALUES(autotrial_duration, 7, 30); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_AUTO_TRIAL, autotrial_duration)); } else { CHECK_VALUE(autotrial_duration, 0); } return true; } const QVector ParsedEventsF0V4 = { PRS1PressureSetEvent::TYPE, PRS1IPAPSetEvent::TYPE, PRS1EPAPSetEvent::TYPE, PRS1AutoPressureSetEvent::TYPE, PRS1PressurePulseEvent::TYPE, PRS1RERAEvent::TYPE, PRS1ObstructiveApneaEvent::TYPE, PRS1ClearAirwayEvent::TYPE, PRS1HypopneaEvent::TYPE, PRS1FlowLimitationEvent::TYPE, PRS1VibratorySnoreEvent::TYPE, PRS1VariableBreathingEvent::TYPE, PRS1PeriodicBreathingEvent::TYPE, PRS1LargeLeakEvent::TYPE, PRS1TotalLeakEvent::TYPE, PRS1SnoreEvent::TYPE, PRS1PressureAverageEvent::TYPE, PRS1FlexPressureAverageEvent::TYPE, PRS1SnoresAtPressureEvent::TYPE, }; // 460P, 560P[BT], 660P, 760P are F0V4 bool PRS1DataChunk::ParseEventsF0V4() { if (this->family != 0 || this->familyVersion != 4) { qWarning() << "ParseEventsF0V4 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const QMap event_sizes = { {0,4}, {2,4}, {3,3}, {0xb,4}, {0xd,2}, {0xe,5}, {0xf,5}, {0x10,5}, {0x11,5}, {0x12,4} }; if (chunk_size < 1) { // This does occasionally happen in F0V6. qDebug() << this->sessionid << "Empty event data"; return false; } bool ok = true; int pos = 0, startpos; int code, size; int t = 0; int elapsed, duration, value; bool is_bilevel = false; do { code = data[pos++]; size = 3; // default size = 2 bytes time delta + 1 byte data if (event_sizes.contains(code)) { size = event_sizes[code]; } if (pos + size > chunk_size) { qWarning() << this->sessionid << "event" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } startpos = pos; if (code != 0x12) { // This one event has no timestamp in F0V6 t += data[pos] | (data[pos+1] << 8); pos += 2; } switch (code) { //case 0x00: // never seen // NOTE: the original code thought 0x00 had 2 data bytes, unlike the 1 in F0V23. // We don't have any sample data with this event, so it's left out here. case 0x01: // Pressure adjustment: note this was 0x02 in F0V23 and is 0x01 in F0V6 // See notes in ParseEventsF0V6. this->AddEvent(new PRS1PressureSetEvent(t, data[pos])); break; case 0x02: // Pressure adjustment (bi-level): note that this was 0x03 in F0V23 and is 0x02 in F0V6 // See notes above on interpolation. this->AddEvent(new PRS1IPAPSetEvent(t, data[pos+1])); this->AddEvent(new PRS1EPAPSetEvent(t, data[pos])); // EPAP needs to be added second to calculate PS is_bilevel = true; break; case 0x03: // Adjust Opti-Start pressure // On F0V4 this occasionally shows up in the middle of a session. // In that cases, the new pressure corresponds to the next night's Opti-Start // pressure. It does not appear to have any effect on the current night's pressure, // though presumaby it could if there's a long gap between sessions. // See F0V6 event 3 for comparison. // TODO: Does this occur in bi-level mode? this->AddEvent(new PRS1AutoPressureSetEvent(t, data[pos])); break; case 0x04: // Pressure Pulse duration = data[pos]; // TODO: is this a duration? this->AddEvent(new PRS1PressurePulseEvent(t, duration)); break; case 0x05: // RERA elapsed = data[pos]; this->AddEvent(new PRS1RERAEvent(t - elapsed, 0)); break; case 0x06: // Obstructive Apnea // OA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ObstructiveApneaEvent(t - elapsed, 0)); break; case 0x07: // Clear Airway Apnea // CA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ClearAirwayEvent(t - elapsed, 0)); break; //case 0x08: // never seen //case 0x09: // never seen case 0x0a: // Hypopnea // TODO: How is this hypopnea different from events 0xb, [0x14 and 0x15 on F0V6]? elapsed = data[pos++]; this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; case 0x0b: // Hypopnea // TODO: How is this hypopnea different from events 0xa, 0x14 and 0x15? // TODO: What is the first byte? //data[pos+0]; // unknown first byte? elapsed = data[pos+1]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; case 0x0c: // Flow Limitation // TODO: We should revisit whether this is elapsed or duration once (if) // we start calculating flow limitations ourselves. Flow limitations aren't // as obvious as OA/CA when looking at a waveform. elapsed = data[pos]; this->AddEvent(new PRS1FlowLimitationEvent(t - elapsed, 0)); break; case 0x0d: // Vibratory Snore // VS events are instantaneous flags with no duration, drawn on the official waveform. // The current thinking is that these are the snores that cause a change in auto-titrating // pressure. The snoring statistics below seem to be a total count. It's unclear whether // the trigger for pressure change is severity or count or something else. // no data bytes this->AddEvent(new PRS1VibratorySnoreEvent(t, 0)); break; case 0x0e: // Variable Breathing? // TODO: does duration double like it does for PB/LL? duration = 2 * (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; // this is always 60 seconds unless it's at the end, so it seems like elapsed CHECK_VALUES(elapsed, 60, 0); this->AddEvent(new PRS1VariableBreathingEvent(t - elapsed - duration, duration)); break; case 0x0f: // Periodic Breathing // PB events are reported some time after they conclude, and they do have a reported duration. // NOTE: This (and F0V6) doubles the duration, unlike F0V23. duration = 2 * (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; this->AddEvent(new PRS1PeriodicBreathingEvent(t - elapsed - duration, duration)); break; case 0x10: // Large Leak // LL events are reported some time after they conclude, and they do have a reported duration. // NOTE: This (and F0V6) doubles the duration, unlike F0V23. duration = 2 * (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; this->AddEvent(new PRS1LargeLeakEvent(t - elapsed - duration, duration)); break; case 0x11: // Statistics this->AddEvent(new PRS1TotalLeakEvent(t, data[pos])); this->AddEvent(new PRS1SnoreEvent(t, data[pos+1])); value = data[pos+2]; if (is_bilevel) { // For bi-level modes, this appears to be the time-weighted average of EPAP and IPAP actually provided. this->AddEvent(new PRS1PressureAverageEvent(t, value)); } else { // For single-pressure modes, this appears to be the average effective "EPAP" provided by Flex. // // Sample data shows this value around 10.3 cmH2O for a prescribed pressure of 12.0 (C-Flex+ 3). // That's too low for an average pressure over time, but could easily be an average commanded EPAP. // When flex mode is off, this is exactly the current CPAP set point. this->AddEvent(new PRS1FlexPressureAverageEvent(t, value)); } this->AddEvent(new PRS1IntervalBoundaryEvent(t)); break; case 0x12: // Snore count per pressure // Some sessions (with lots of ramps) have multiple of these, each with a // different pressure. The total snore count across all of them matches the // total found in the stats event. if (data[pos] != 0) { CHECK_VALUES(data[pos], 1, 2); // 0 = CPAP pressure, 1 = bi-level EPAP, 2 = bi-level IPAP } //CHECK_VALUE(data[pos+1], 0x78); // pressure //CHECK_VALUE(data[pos+2], 1); // 16-bit snore count //CHECK_VALUE(data[pos+3], 0); value = (data[pos+2] | (data[pos+3] << 8)); this->AddEvent(new PRS1SnoresAtPressureEvent(t, data[pos], data[pos+1], value)); break; default: DUMP_EVENT(); UNEXPECTED_VALUE(code, "known event code"); this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos)); ok = false; // unlike F0V6, we don't know the size of unknown events, so we can't recover break; } pos = startpos + size; } while (ok && pos < chunk_size); if (ok && pos != chunk_size) { qWarning() << this->sessionid << (this->size() - pos) << "trailing event bytes"; } this->duration = t; return ok; } // Based on ParseComplianceF0V4, but this has shorter settings and stats following equipment off. bool PRS1DataChunk::ParseComplianceF0V5(void) { if (this->family != 0 || (this->familyVersion != 5)) { qWarning() << "ParseComplianceF0V5 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const int minimum_sizes[] = { 0xf, 7, 4, 0xf, 0, 4, 0, 4 }; static const int ncodes = sizeof(minimum_sizes) / sizeof(int); // NOTE: These are fixed sizes, but are called minimum to more closely match the F0V6 parser. bool ok = true; int pos = 0; int code, size; int tt = 0; while (ok && pos < chunk_size) { code = data[pos++]; // There is no hblock prior to F0V6. size = 0; if (code < ncodes) { // make sure the handlers below don't go past the end of the buffer size = minimum_sizes[code]; } // else if it's past ncodes, we'll log its information below (rather than handle it) if (pos + size > chunk_size) { qWarning() << this->sessionid << "slice" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } switch (code) { case 0: // Equipment On CHECK_VALUE(pos, 1); // Always first //CHECK_VALUES(data[pos], 0x73, 0x31); // 0x71 // F0V5 doesn't have a separate settings record like F0V6 does, the settings just follow the EquipmentOn data. ok = ParseSettingsF0V45(data, 0x0d); CHECK_VALUE(data[pos+0xd], 0); CHECK_VALUE(data[pos+0xe], 0); CHECK_VALUES(data[pos+0xf], 0, 2); break; case 2: // Mask On tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn)); CHECK_VALUES(data[pos+3], 0x60, 0x70); this->ParseHumidifierSetting60Series(data[pos+2], data[pos+3]); break; case 3: // Mask Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff)); // F0V5 compliance has MaskOff stats unlike all other compliance. // This is presumably because the 501V is an Auto-CPAP, so it needs to record titration data. //CHECK_VALUES(data[pos+2], 40, 50); // min pressure //CHECK_VALUES(data[pos+3], 40, 150); // max pressure //CHECK_VALUES(data[pos+4], 40, 150); // Average Device Pressure <= 90% of Time (report is time-weighted per slice, for all sessions) //CHECK_VALUES(data[pos+5], 40, 108); // Auto CPAP Mean Pressure (report is time-weighted per slice, for all sessions) // Peak Average Pressure is the maximum "mean pressure" reported in any session. //CHECK_VALUES(data[pos+6], 0, 5); // Apnea or Hypopnea count (probably 16-bit), contributes to AHI CHECK_VALUE(data[pos+7], 0); //CHECK_VALUES(data[pos+8], 0, 6); // Apnea or Hypopnea count (probably 16-bit), contributes to AHI CHECK_VALUE(data[pos+9], 0); //CHECK_VALUES(data[pos+10], 0, 2); // Average Large Leak minutes (probably 16-bit, report show sum of all slices) CHECK_VALUE(data[pos+11], 0); //CHECK_VALUES(data[pos+12], 179, 50); // Average 90% Leak (report is time-weighted per slice) //CHECK_VALUES(data[pos+13], 178, 32); // Average Total Leak (report is time-weighted per slice) //CHECK_VALUES(data[pos+14], 180, 36); // Max leak (report shows max for all slices) break; case 1: // Equipment Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff)); CHECK_VALUE(data[pos+2] & ~(0x40|0x02|0x01), 0); //CHECK_VALUES(data[pos+3], 0x16, 0x13); // 22, 19 if (data[pos+4] > 3) UNEXPECTED_VALUE(data[pos+4], "0-3"); //CHECK_VALUES(data[pos+5], 0x2F, 0x26); // 47, 38 if (data[pos+6] > 7) UNEXPECTED_VALUE(data[pos+6], "0-7"); break; //case 4: // Time Elapsed? See ParseComplianceF0V4 if we encounter this. case 5: // Clock adjustment? CHECK_VALUE(pos, 1); // Always first CHECK_VALUE(chunk_size, 5); // and the only record in the session. // This looks like it's minor adjustments to the clock, see ParseComplianceF0V4 for details. break; //case 6: // Cleared? See ParseComplianceF0V4 if we encounter this. case 7: // Humidifier setting change (logged in events in 50 series) tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) this->ParseHumidifierSetting60Series(data[pos+2], data[pos+3]); break; default: UNEXPECTED_VALUE(code, "known slice code"); ok = false; // unlike F0V6, we don't know the size of unknown slices, so we can't recover break; } pos += size; } if (ok && pos != chunk_size) { qWarning() << this->sessionid << (this->size() - pos) << "trailing bytes"; } this->duration = tt; return ok; } //******************************************************************************************** // MARK: - // MARK: DreamStation // The below is based on fixing the fileVersion == 3 parsing in ParseSummary() based // on our understanding of slices from F0V23. The switch values come from sample files. bool PRS1DataChunk::ParseComplianceF0V6(void) { if (this->family != 0 || this->familyVersion != 6) { qWarning() << "ParseComplianceF0V6 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } // TODO: hardcoding this is ugly, think of a better approach if (this->m_data.size() < 82) { qWarning() << this->sessionid << "compliance data too short:" << this->m_data.size(); return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const int expected_sizes[] = { 1, 0x34, 9, 4, 2, 2, 4, 8 }; static const int ncodes = sizeof(expected_sizes) / sizeof(int); for (int i = 0; i < ncodes; i++) { if (this->hblock.contains(i)) { CHECK_VALUE(this->hblock[i], expected_sizes[i]); } else { UNEXPECTED_VALUE(this->hblock.contains(i), true); } } bool ok = true; int pos = 0; int code, size; int tt = 0; while (ok && pos < chunk_size) { code = data[pos++]; if (!this->hblock.contains(code)) { qWarning() << this->sessionid << "missing hblock entry for" << code; ok = false; break; } size = this->hblock[code]; if (size < expected_sizes[code]) { UNEXPECTED_VALUE(size, expected_sizes[code]); qWarning() << this->sessionid << "slice" << code << "too small" << size << "<" << expected_sizes[code]; if (code != 1) { // Settings are variable-length, so shorter settings slices aren't fatal. ok = false; break; } } if (pos + size > chunk_size) { qWarning() << this->sessionid << "slice" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } switch (code) { case 0: // always first? Maybe equipmenton? Maybe 0 was always equipmenton, even in F0V23? CHECK_VALUE(pos, 1); //CHECK_VALUES(data[pos], 1, 3); // sometimes 7? break; case 1: // Settings // This is where ParseSummaryF0V6 started (after "3 bytes that don't follow the pattern") // Both compliance and summary files seem to have the same length for this slice, so maybe the // settings are the same? ok = this->ParseSettingsF0V6(data + pos, size); break; case 3: // Mask On tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn)); this->ParseHumidifierSettingV3(data[pos+2], data[pos+3]); break; case 4: // Mask Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff)); break; case 7: // Always follows mask off? //CHECK_VALUES(data[pos], 0x01, 0x00); // sometimes 32, 4 CHECK_VALUE(data[pos+1], 0x00); //CHECK_VALUES(data[pos+2], 0x00, 0x01); // sometimes 11, 3, 15 CHECK_VALUE(data[pos+3], 0x00); //CHECK_VALUE(data[pos+4], 0x05, 0x0A); // 00 CHECK_VALUE(data[pos+5], 0x00); //CHECK_VALUE(data[pos+6], 0x64, 0x69); // 6E, 6D, 6E, 6E, 80 //CHECK_VALUE(data[pos+7], 0x3d, 0x5c); // 6A, 6A, 6B, 6C, 80 break; case 2: // Equipment Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff)); //CHECK_VALUE(data[pos+2], 0x08); // 0x01 //CHECK_VALUE(data[pos+3], 0x14); // 0x12 //CHECK_VALUE(data[pos+4], 0x01); // 0x00 //CHECK_VALUE(data[pos+5], 0x22); // 0x28 //CHECK_VALUE(data[pos+6], 0x02); // sometimes 1, 0 CHECK_VALUE(data[pos+7], 0x00); // 0x00 CHECK_VALUE(data[pos+8], 0x00); // 0x00 break; case 6: // Humidier setting change tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) this->ParseHumidifierSettingV3(data[pos+2], data[pos+3]); break; default: UNEXPECTED_VALUE(code, "known slice code"); break; } pos += size; } this->duration = tt; return ok; } bool PRS1DataChunk::ParseSummaryF0V6(void) { if (this->family != 0 || this->familyVersion != 6) { qWarning() << "ParseSummaryF0V6 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const int minimum_sizes[] = { 1, 0x29, 9, 4, 2, 4, 1, 4, 0x1b, 2, 4, 0x0b, 1, 2, 6 }; static const int ncodes = sizeof(minimum_sizes) / sizeof(int); // NOTE: The sizes contained in hblock can vary, even within a single machine, as can the length of hblock itself! // TODO: hardcoding this is ugly, think of a better approach if (chunk_size < minimum_sizes[0] + minimum_sizes[1] + minimum_sizes[2]) { qWarning() << this->sessionid << "summary data too short:" << chunk_size; return false; } if (chunk_size < 59) UNEXPECTED_VALUE(chunk_size, ">= 59"); bool ok = true; int pos = 0; int code, size; int tt = 0; while (ok && pos < chunk_size) { code = data[pos++]; if (!this->hblock.contains(code)) { qWarning() << this->sessionid << "missing hblock entry for" << code; ok = false; break; } size = this->hblock[code]; if (code < ncodes) { // make sure the handlers below don't go past the end of the buffer if (size < minimum_sizes[code]) { UNEXPECTED_VALUE(size, minimum_sizes[code]); qWarning() << this->sessionid << "slice" << code << "too small" << size << "<" << minimum_sizes[code]; if (code != 1) { // Settings are variable-length, so shorter settings slices aren't fatal. ok = false; break; } } } // else if it's past ncodes, we'll log its information below (rather than handle it) if (pos + size > chunk_size) { qWarning() << this->sessionid << "slice" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } switch (code) { case 0: // Equipment On CHECK_VALUE(pos, 1); // Always first? //CHECK_VALUES(data[pos], 1, 7); // or 3? if (size == 4) { // 400G has 3 more bytes? //CHECK_VALUE(data[pos+1], 0); // or 2, 14, 4, etc. //CHECK_VALUES(data[pos+2], 8, 65); // or 1 //CHECK_VALUES(data[pos+3], 0, 20); // or 21, 22, etc. } break; case 1: // Settings ok = this->ParseSettingsF0V6(data + pos, size); break; case 3: // Mask On tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn)); this->ParseHumidifierSettingV3(data[pos+2], data[pos+3]); break; case 4: // Mask Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff)); break; case 8: // vs. 7 in compliance, always follows mask off (except when there's a 5, see below), also longer // Maybe statistics of some kind, given the pressure stats that seem to appear before it on AutoCPAP machines? //CHECK_VALUES(data[pos], 0x02, 0x01); // probably 16-bit value CHECK_VALUE(data[pos+1], 0x00); //CHECK_VALUES(data[pos+2], 0x0d, 0x0a); // probably 16-bit value, maybe OA count? CHECK_VALUE(data[pos+3], 0x00); //CHECK_VALUES(data[pos+4], 0x09, 0x0b); // probably 16-bit value CHECK_VALUE(data[pos+5], 0x00); //CHECK_VALUES(data[pos+6], 0x1e, 0x35); // probably 16-bit value CHECK_VALUE(data[pos+7], 0x00); //CHECK_VALUES(data[pos+8], 0x8c, 0x4c); // 16-bit value, not sure what //CHECK_VALUE(data[pos+9], 0x00); //CHECK_VALUES(data[pos+0xa], 0xbb, 0x00); // 16-bit minutes in large leak //CHECK_VALUE(data[pos+0xb], 0x00); //CHECK_VALUES(data[pos+0xc], 0x15, 0x02); // 16-bit minutes in PB //CHECK_VALUE(data[pos+0xd], 0x00); //CHECK_VALUES(data[pos+0xe], 0x01, 0x00); // 16-bit VS count //CHECK_VALUE(data[pos+0xf], 0x00); //CHECK_VALUES(data[pos+0x10], 0x21, 5); // probably 16-bit value, maybe H count? CHECK_VALUE(data[pos+0x11], 0x00); //CHECK_VALUES(data[pos+0x12], 0x13, 0); // 16-bit value, not sure what //CHECK_VALUE(data[pos+0x13], 0x00); //CHECK_VALUES(data[pos+0x14], 0x05, 0); // probably 16-bit value, maybe RE count? CHECK_VALUE(data[pos+0x15], 0x00); //CHECK_VALUE(data[pos+0x16], 0x00, 4); // probably a 16-bit value, PB or FL count? CHECK_VALUE(data[pos+0x17], 0x00); //CHECK_VALUES(data[pos+0x18], 0x69, 0x23); //CHECK_VALUES(data[pos+0x19], 0x44, 0x18); //CHECK_VALUES(data[pos+0x1a], 0x80, 0x49); if (size >= 0x1f) { // 500X is only 0x1b long! //CHECK_VALUES(data[pos+0x1b], 0x00, 6); CHECK_VALUE(data[pos+0x1c], 0x00); //CHECK_VALUES(data[pos+0x1d], 0x0c, 0x0d); //CHECK_VALUES(data[pos+0x1e], 0x31, 0x3b); // TODO: 400G and 500G has 8 more bytes? // TODO: 400G sometimes has another 4 on top of that? } break; case 2: // Equipment Off tt += data[pos] | (data[pos+1] << 8); this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff)); //CHECK_VALUE(data[pos+2], 0x08); // 0x01 //CHECK_VALUE(data[pos+3], 0x14); // 0x12 //CHECK_VALUE(data[pos+4], 0x01); // 0x00 //CHECK_VALUE(data[pos+5], 0x22); // 0x28 //CHECK_VALUE(data[pos+6], 0x02); // sometimes 1, 0 CHECK_VALUE(data[pos+7], 0x00); // 0x00 CHECK_VALUE(data[pos+8], 0x00); // 0x00 if (size == 0x0c) { // 400G has 3 more bytes, seem to match Equipment On bytes //CHECK_VALUE(data[pos+1], 0); //CHECK_VALUES(data[pos+2], 8, 65); //CHECK_VALUE(data[pos+3], 0); } break; case 0x09: // Time Elapsed (event 4 in F0V4) tt += data[pos] | (data[pos+1] << 8); break; case 0x0a: // Humidifier setting change tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) this->ParseHumidifierSettingV3(data[pos+2], data[pos+3]); break; case 0x0d: // ??? // seen on one 500G multiple times //CHECK_VALUE(data[pos], 0); // 16-bit value //CHECK_VALUE(data[pos+1], 0); break; case 0x0e: // only seen once on 400G, many times on 500G //CHECK_VALUES(data[pos], 0, 6); // 16-bit value //CHECK_VALUE(data[pos+1], 0); //CHECK_VALUES(data[pos+2], 7, 9); //CHECK_VALUES(data[pos+3], 7, 15); //CHECK_VALUES(data[pos+4], 7, 12); //CHECK_VALUES(data[pos+5], 0, 3); break; case 0x05: // AutoCPAP-related? First appeared on 500X, follows 4, before 8, look like pressure values //CHECK_VALUE(data[pos], 0x4b); // maybe min pressure? (matches ramp pressure, see ramp on pressure graph) //CHECK_VALUE(data[pos+1], 0x5a); // maybe max pressure? (close to max on pressure graph, time at pressure graph) //CHECK_VALUE(data[pos+2], 0x5a); // seems to match Average 90% Pressure //CHECK_VALUE(data[pos+3], 0x58); // seems to match Average CPAP break; case 0x07: // AutoBiLevel-related? First appeared on 700X, follows 4, before 8, looks like pressure values //CHECK_VALUE(data[pos], 0x50); // maybe min IPAP or max titrated EPAP? (matches time at pressure graph, auto bi-level summary) //CHECK_VALUE(data[pos+1], 0x64); // maybe max IPAP or max titrated IPAP? (matches time at pressure graph, auto bi-level summary) //CHECK_VALUE(data[pos+2], 0x4b); // seems to match 90% EPAP //CHECK_VALUE(data[pos+3], 0x64); // seems to match 90% IPAP break; case 0x0b: // CPAP-Check related, follows Mask On in CPAP-Check mode tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) //CHECK_VALUE(data[pos+2], 0); // probably 16-bit value CHECK_VALUE(data[pos+3], 0); //CHECK_VALUE(data[pos+4], 0); // probably 16-bit value CHECK_VALUE(data[pos+5], 0); //CHECK_VALUE(data[pos+6], 0); // probably 16-bit value CHECK_VALUE(data[pos+7], 0); //CHECK_VALUE(data[pos+8], 0); // probably 16-bit value CHECK_VALUE(data[pos+9], 0); //CHECK_VALUES(data[pos+0xa], 20, 60); // or 0? 44 when changed pressure mid-session? break; case 0x06: // Maybe starting pressure? follows 4, before 8, looks like a pressure value, seen with CPAP-Check and EZ-Start // Maybe ending pressure: matches ending CPAP-Check pressure if it changes mid-session. // TODO: The daily details will show when it changed, so maybe there's an event that indicates a pressure change. //CHECK_VALUES(data[pos], 90, 60); // maybe CPAP-Check pressure, also matches EZ-Start Pressure break; case 0x0c: // EZ-Start pressure for Auto-CPAP, seen on 500X110 following 4, before 8 // Appears to reflect the current session's EZ-Start pressure, though reported afterwards //CHECK_VALUE(data[pos], 70, 80); break; default: UNEXPECTED_VALUE(code, "known slice code"); break; } pos += size; } this->duration = tt; return ok; } // The below is based on a combination of the old mainblock parsing for fileVersion == 3 // in ParseSummary() and the switch statements of ParseSummaryF0V6. // // Both compliance and summary files (at least for 200X and 400X machines) seem to have // the same length for this slice, so maybe the settings are the same? At least 0x0a // looks like a pressure in compliance files. bool PRS1DataChunk::ParseSettingsF0V6(const unsigned char* data, int size) { static const QMap expected_lengths = { {0x0c,3}, {0x0d,2}, {0x0e,2}, {0x0f,4}, {0x10,3}, {0x35,2} }; bool ok = true; PRS1Mode cpapmode = PRS1_MODE_UNKNOWN; FlexMode flexmode = FLEX_Unknown; int pressure = 0; int imin_ps = 0; int imax_ps = 0; int min_pressure = 0; int max_pressure = 0; bool ramp_type_set = false; // Parse the nested data structure which contains settings int pos = 0; do { int code = data[pos++]; int len = data[pos++]; int expected_len = 1; if (expected_lengths.contains(code)) { expected_len = expected_lengths[code]; } //CHECK_VALUE(len, expected_len); if (len < expected_len) { qWarning() << this->sessionid << "setting" << code << "too small" << len << "<" << expected_len; ok = false; break; } if (pos + len > size) { qWarning() << this->sessionid << "setting" << code << "@" << pos << "longer than remaining slice"; ok = false; break; } switch (code) { case 0: // Device Mode CHECK_VALUE(pos, 2); // always first? CHECK_VALUE(len, 1); switch (data[pos]) { case 0: cpapmode = PRS1_MODE_CPAP; break; case 1: cpapmode = PRS1_MODE_BILEVEL; break; case 2: cpapmode = PRS1_MODE_AUTOCPAP; break; case 3: cpapmode = PRS1_MODE_AUTOBILEVEL; break; case 4: cpapmode = PRS1_MODE_CPAPCHECK; break; default: UNEXPECTED_VALUE(data[pos], "known device mode"); break; } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode)); break; case 1: // ??? CHECK_VALUES(len, 1, 2); if (data[pos] != 0 && data[pos] != 3) { CHECK_VALUES(data[pos], 1, 2); // 1 when EZ-Start is enabled? 2 when Auto-Trial? 3 when Auto-Trial is off or Opti-Start isn't off? } if (len == 2) { // 400G, 500G has extra byte switch (data[pos+1]) { case 0x00: // 0x00 seen with EZ-Start disabled, no auto-trial, with CPAP-Check on 400X110 case 0x10: // 0x10 seen with EZ-Start enabled, Opti-Start off on 500X110 case 0x20: // 0x20 seen with Opti-Start enabled case 0x30: // 0x30 seen with both Opti-Start and EZ-Start enabled on 500X110 case 0x40: // 0x40 seen with Auto-Trial case 0x80: // 0x80 seen with EZ-Start and CPAP-Check+ on 500X150 break; default: UNEXPECTED_VALUE(data[pos+1], "[0,0x10,0x20,0x30,0x40,0x80]") } } break; case 0x0a: // CPAP pressure setting CHECK_VALUE(len, 1); CHECK_VALUE(cpapmode, PRS1_MODE_CPAP); pressure = data[pos]; this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE, pressure)); break; case 0x0c: // CPAP-Check pressure setting CHECK_VALUE(len, 3); CHECK_VALUE(cpapmode, PRS1_MODE_CPAPCHECK); min_pressure = data[pos]; // Min Setting on pressure graph max_pressure = data[pos+1]; // Max Setting on pressure graph pressure = data[pos+2]; // CPAP on pressure graph and CPAP-Check Pressure on settings detail // This seems to be the initial pressure. If the pressure changes mid-session, the pressure // graph will show either the changed pressure or the majority pressure, not sure which. // The time of change is most likely in the events file. See slice 6 for ending pressure. //CHECK_VALUE(pressure, 0x5a); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE, pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MIN, min_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MAX, max_pressure)); break; case 0x0d: // AutoCPAP pressure setting CHECK_VALUE(len, 2); CHECK_VALUE(cpapmode, PRS1_MODE_AUTOCPAP); min_pressure = data[pos]; max_pressure = data[pos+1]; this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MIN, min_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MAX, max_pressure)); break; case 0x0e: // Bi-Level pressure setting CHECK_VALUE(len, 2); CHECK_VALUE(cpapmode, PRS1_MODE_BILEVEL); min_pressure = data[pos]; max_pressure = data[pos+1]; imin_ps = max_pressure - min_pressure; this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP, min_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP, max_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS, imin_ps)); break; case 0x0f: // Auto Bi-Level pressure setting CHECK_VALUE(len, 4); CHECK_VALUE(cpapmode, PRS1_MODE_AUTOBILEVEL); min_pressure = data[pos]; max_pressure = data[pos+1]; imin_ps = data[pos+2]; imax_ps = data[pos+3]; this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, min_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, max_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, imin_ps)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, imax_ps)); break; case 0x10: // Auto-Trial mode // This is not encoded as a separate mode as in F0V4, but instead as an auto-trial // duration on top of the CPAP or CPAP-Check mode. Reports show Auto-CPAP results, // but curiously report the use of C-Flex+, even though Auto-CPAP uses A-Flex. CHECK_VALUE(len, 3); CHECK_VALUES(cpapmode, PRS1_MODE_CPAP, PRS1_MODE_CPAPCHECK); if (data[pos] < 5 || data[pos] > 30) { // We've seen 5, 9, 14, 25, and 30 UNEXPECTED_VALUE(data[pos], "5-30"); // Auto-Trial Duration } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_AUTO_TRIAL, data[pos])); // If we want C-Flex+ to be reported as A-Flex, we can set cpapmode = PRS1_MODE_AUTOTRIAL here. // (Note that the setting event has already been added above, which is why ImportSummary needs // to adjust it when it sees this setting.) min_pressure = data[pos+1]; max_pressure = data[pos+2]; this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MIN, min_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MAX, max_pressure)); break; case 0x2a: // EZ-Start CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0x00, 0x80); // both seem to mean enabled // 0x80 is CPAP Mode - EZ-Start in pressure detail chart, 0x00 is just CPAP mode with no EZ-Start pressure // TODO: How to represent which one is active in practice? Should this always be "true" since // either value means that the setting is enabled? this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_EZ_START, data[pos] != 0)); break; case 0x42: // EZ-Start enabled for Auto-CPAP? // Seen on 500X110 before 0x2b when EZ-Start is enabled on Auto-CPAP CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0x00, 0x80); // both seem to mean enabled, 0x00 appears when Opti-Start is used instead // TODO: How to represent which one is active in practice? Should this always be "true" since // either value means that the setting is enabled? this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_EZ_START, data[pos] != 0)); break; case 0x2b: // Ramp Type CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); // 0 == "Linear", 0x80 = "SmartRamp" this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TYPE, data[pos] != 0)); ramp_type_set = true; break; case 0x2c: // Ramp Time CHECK_VALUE(len, 1); if (data[pos] != 0) { // 0 == ramp off, and ramp pressure setting doesn't appear if (data[pos] < 5 || data[pos] > 45) UNEXPECTED_VALUE(data[pos], "5-45"); } if (!ramp_type_set) { // If there's a ramp time that's neither linear nor SmartRamp, then it's Ramp+. this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TYPE, 2)); ramp_type_set = true; } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, data[pos])); break; case 0x2d: // Ramp Pressure CHECK_VALUE(len, 1); // 0 = Off for Ramp+ (since time is always set) // Turning it on during therapy creates a new session. this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, data[pos])); break; case 0x2e: // Flex mode CHECK_VALUE(len, 1); switch (data[pos]) { case 0: flexmode = FLEX_None; break; case 0x80: switch (cpapmode) { case PRS1_MODE_CPAP: case PRS1_MODE_CPAPCHECK: case PRS1_MODE_AUTOCPAP: //case PRS1_MODE_AUTOTRIAL: flexmode = FLEX_CFlex; break; case PRS1_MODE_BILEVEL: case PRS1_MODE_AUTOBILEVEL: flexmode = FLEX_BiFlex; break; default: HEX(flexmode); UNEXPECTED_VALUE(cpapmode, "untested mode"); break; } break; case 0x90: // C-Flex+ or A-Flex, depending on machine mode switch (cpapmode) { case PRS1_MODE_CPAP: case PRS1_MODE_CPAPCHECK: flexmode = FLEX_CFlexPlus; break; case PRS1_MODE_AUTOCPAP: flexmode = FLEX_AFlex; break; default: UNEXPECTED_VALUE(cpapmode, "cpap or apap"); break; } break; case 0xA0: // Rise Time flexmode = FLEX_RiseTime; switch (cpapmode) { case PRS1_MODE_BILEVEL: case PRS1_MODE_AUTOBILEVEL: break; default: HEX(flexmode); UNEXPECTED_VALUE(cpapmode, "autobilevel"); break; } break; case 0xB0: // P-Flex flexmode = FLEX_PFlex; switch (cpapmode) { case PRS1_MODE_AUTOCPAP: break; default: HEX(flexmode); UNEXPECTED_VALUE(cpapmode, "apap"); break; } break; default: UNEXPECTED_VALUE(data[pos], "known flex mode"); break; } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, flexmode)); break; case 0x2f: // Flex lock CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); // DS2 doesn't have a specific flex lock. See patient controls access below. this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LOCK, data[pos] != 0)); break; case 0x30: // Flex level CHECK_VALUE(len, 1); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, data[pos])); if (flexmode == FLEX_PFlex) { CHECK_VALUE(data[pos], 4); // No number appears on reports. } if (flexmode == FLEX_RiseTime) { if (data[pos] < 1 || data[pos] > 3) UNEXPECTED_VALUE(data[pos], "1-3"); } break; case 0x35: // Humidifier setting CHECK_VALUE(len, 2); this->ParseHumidifierSettingV3(data[pos], data[pos+1], true); break; case 0x36: // Mask Resistance Lock CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); // DS2 doesn't have a mask resistance lock, as the resistance setting is only in the provider menu. this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_LOCK, data[pos] != 0)); break; case 0x38: // Mask Resistance CHECK_VALUE(len, 1); if (data[pos] != 0) { // 0 == mask resistance off if (data[pos] < 1 || data[pos] > 5) UNEXPECTED_VALUE(data[pos], "1-5"); } this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_SETTING, data[pos])); break; case 0x39: // Tubing Type Lock CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); // DS2 doesn't have a tubing type lock, it is always available (unless a heated tube is auto-detected). this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_TUBING_LOCK, data[pos] != 0)); break; case 0x3b: // Tubing Type CHECK_VALUE(len, 1); if (data[pos] != 0) { CHECK_VALUES(data[pos], 2, 1); // 15HT = 2, 15 = 1, 22 = 0 } this->ParseTubingTypeV3(data[pos]); break; case 0x40: // new to 400G, also seen on 500X110, alternate tubing type? appears after 0x39 and before 0x3c CHECK_VALUE(len, 1); if (data[pos] > 3) UNEXPECTED_VALUE(data[pos], "0-3"); // 0 = 22mm, 1 = 15mm, 2 = 15HT, 3 = 12mm this->ParseTubingTypeV3(data[pos]); break; case 0x3c: // View Optional Screens CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SHOW_AHI, data[pos] != 0)); break; case 0x3e: // Auto On CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_AUTO_ON, data[pos] != 0)); break; case 0x3f: // Auto Off CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_AUTO_OFF, data[pos] != 0)); break; case 0x43: // new to 502G, sessions 3-8, Auto-Trial is off, Opti-Start is missing CHECK_VALUE(len, 1); CHECK_VALUE(data[pos], 0x3C); break; case 0x44: // new to 502G, sessions 3-8, Auto-Trial is off, Opti-Start is missing CHECK_VALUE(len, 1); CHECK_VALUE(data[pos], 0xFF); break; case 0x45: // Target Time, specific to DreamStation Go CHECK_VALUE(len, 1); // Included in the data, but not shown on reports when humidifier is in Fixed mode. // According to the FAQ, this setting is only available in Adaptive mode. if (data[pos] < 40 || data[pos] > 100) { // 4.0 through 10.0 hours in 0.5-hour increments CHECK_VALUES(data[pos], 0, 1); // Off and Auto } this->AddEvent(new PRS1ScaledSettingEvent(PRS1_SETTING_HUMID_TARGET_TIME, data[pos], 0.1)); break; case 0x46: // Tubing Type (alternate, seen instead of 0x3b on 700X110 v1.2 firmware and on DS2) CHECK_VALUE(len, 1); if (data[pos] > 3) UNEXPECTED_VALUE(data[pos], "0-3"); // 0 = 22mm, 1 = 15mm, 2 = 15HT, 3 = 12mm // TODO: Confirm that 4 is 12HT and update ParseTubingTypeV3. this->ParseTubingTypeV3(data[pos]); break; case 0x4a: // Patient controls access, specific to DreamStation 2. CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); // Turning off patient controls access essentially locks only flex and ramp time. // Humidification, heated tube temperature, and ramp level are still adjustable during therapy. // (DS2 doesn't have a separate flex lock setting.) if (data[pos] == 0) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LOCK, true)); } break; case 0x4b: // Patient data access, specific to DreamStation 2. CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); // Turning off patient data access hides both AHI and on-device reports. this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SHOW_AHI, data[pos] != 0)); break; default: UNEXPECTED_VALUE(code, "known setting"); qDebug() << "Unknown setting:" << hex << code << "in" << this->sessionid << "at" << pos; this->AddEvent(new PRS1UnknownDataEvent(QByteArray((const char*) data, size), pos, len)); break; } pos += len; } while (ok && pos + 2 <= size); return ok; } const QVector ParsedEventsF0V6 = { PRS1PressureSetEvent::TYPE, PRS1IPAPSetEvent::TYPE, PRS1EPAPSetEvent::TYPE, PRS1AutoPressureSetEvent::TYPE, PRS1PressurePulseEvent::TYPE, PRS1RERAEvent::TYPE, PRS1ObstructiveApneaEvent::TYPE, PRS1ClearAirwayEvent::TYPE, PRS1HypopneaEvent::TYPE, PRS1FlowLimitationEvent::TYPE, PRS1VibratorySnoreEvent::TYPE, PRS1VariableBreathingEvent::TYPE, PRS1PeriodicBreathingEvent::TYPE, PRS1LargeLeakEvent::TYPE, PRS1TotalLeakEvent::TYPE, PRS1SnoreEvent::TYPE, PRS1PressureAverageEvent::TYPE, PRS1FlexPressureAverageEvent::TYPE, PRS1SnoresAtPressureEvent::TYPE, }; // DreamStation family 0 CPAP/APAP machines (400X-700X, 400G-502G) // Originally derived from F5V3 parsing + (incomplete) F0V234 parsing + sample data bool PRS1DataChunk::ParseEventsF0V6() { if (this->family != 0 || this->familyVersion != 6) { qWarning() << "ParseEventsF0V6 called with family" << this->family << "familyVersion" << this->familyVersion; return false; } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); static const int minimum_sizes[] = { 2, 3, 4, 3, 3, 3, 3, 3, 3, 2, 3, 4, 3, 2, 5, 5, 5, 5, 4, 3, 3, 3 }; static const int ncodes = sizeof(minimum_sizes) / sizeof(int); if (chunk_size < 1) { // This does occasionally happen. qDebug() << this->sessionid << "Empty event data"; return false; } bool ok = true; int pos = 0, startpos; int code, size; int t = 0; int elapsed, duration, value; bool is_bilevel = false; do { code = data[pos++]; if (!this->hblock.contains(code)) { qWarning() << this->sessionid << "missing hblock entry for event" << code; ok = false; break; } size = this->hblock[code]; if (code < ncodes) { // make sure the handlers below don't go past the end of the buffer if (size < minimum_sizes[code]) { qWarning() << this->sessionid << "event" << code << "too small" << size << "<" << minimum_sizes[code]; ok = false; break; } } // else if it's past ncodes, we'll log its information below (rather than handle it) if (pos + size > chunk_size) { qWarning() << this->sessionid << "event" << code << "@" << pos << "longer than remaining chunk"; ok = false; break; } startpos = pos; if (code != 0x12) { // This one event has no timestamp t += data[pos] | (data[pos+1] << 8); pos += 2; } switch (code) { //case 0x00: // never seen case 0x01: // Pressure adjustment // Matches pressure setting, both initial and when ramp button pressed. // Based on waveform reports, it looks like the pressure graph is drawn by // interpolating between these pressure adjustments, by 0.5 cmH2O spaced evenly between // adjustments. E.g. 6 at 28:11 and 7.3 at 29:05 results in the following dots: // 6 at 28:11, 6.5 around 28:30, 7.0 around 28:50, 7(.3) at 29:05. That holds until // subsequent "adjustment" of 7.3 at 30:09 followed by 8.0 at 30:19. this->AddEvent(new PRS1PressureSetEvent(t, data[pos])); break; case 0x02: // Pressure adjustment (bi-level) // See notes above on interpolation. this->AddEvent(new PRS1IPAPSetEvent(t, data[pos+1])); this->AddEvent(new PRS1EPAPSetEvent(t, data[pos])); // EPAP needs to be added second to calculate PS is_bilevel = true; break; case 0x03: // Auto-CPAP starting pressure // Most of the time this occurs, it's at the start and end of a session with // the same pressure at both. Occasionally an additional event shows up in the // middle of a session, and then the pressure at the end matches that. // In these cases, the new pressure corresponds to the next night's starting // pressure for auto-CPAP. It does not appear to have any effect on the current // night's pressure, unless there's a substantial gap between sessions, in // which case the next session may use the new starting pressure. //CHECK_VALUE(data[pos], 40); // TODO: What does this mean in bi-level mode? // See F0V4 event 3 for comparison. TODO: See if there's an Opti-Start label on F0V6 reports. this->AddEvent(new PRS1AutoPressureSetEvent(t, data[pos])); break; case 0x04: // Pressure Pulse duration = data[pos]; // TODO: is this a duration? this->AddEvent(new PRS1PressurePulseEvent(t, duration)); break; case 0x05: // RERA elapsed = data[pos]; // based on sample waveform, the RERA is over after this this->AddEvent(new PRS1RERAEvent(t - elapsed, 0)); break; case 0x06: // Obstructive Apnea // OA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ObstructiveApneaEvent(t - elapsed, 0)); break; case 0x07: // Clear Airway Apnea // CA events are instantaneous flags with no duration: reviewing waveforms // shows that the time elapsed between the flag and reporting often includes // non-apnea breathing. elapsed = data[pos]; this->AddEvent(new PRS1ClearAirwayEvent(t - elapsed, 0)); break; //case 0x08: // never seen //case 0x09: // never seen //case 0x0a: // Hypopnea, see 0x15 case 0x0b: // Hypopnea // TODO: How is this hypopnea different from events 0xa, 0x14 and 0x15? // TODO: What is the first byte? //data[pos+0]; // unknown first byte? elapsed = data[pos+1]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; case 0x0c: // Flow Limitation // TODO: We should revisit whether this is elapsed or duration once (if) // we start calculating flow limitations ourselves. Flow limitations aren't // as obvious as OA/CA when looking at a waveform. elapsed = data[pos]; this->AddEvent(new PRS1FlowLimitationEvent(t - elapsed, 0)); break; case 0x0d: // Vibratory Snore // VS events are instantaneous flags with no duration, drawn on the official waveform. // The current thinking is that these are the snores that cause a change in auto-titrating // pressure. The snoring statistics below seem to be a total count. It's unclear whether // the trigger for pressure change is severity or count or something else. // no data bytes this->AddEvent(new PRS1VibratorySnoreEvent(t, 0)); break; case 0x0e: // Variable Breathing? duration = 2 * (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; // this is always 60 seconds unless it's at the end, so it seems like elapsed CHECK_VALUES(elapsed, 60, 0); this->AddEvent(new PRS1VariableBreathingEvent(t - elapsed - duration, duration)); break; case 0x0f: // Periodic Breathing // PB events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; this->AddEvent(new PRS1PeriodicBreathingEvent(t - elapsed - duration, duration)); break; case 0x10: // Large Leak // LL events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); elapsed = data[pos+2]; this->AddEvent(new PRS1LargeLeakEvent(t - elapsed - duration, duration)); break; case 0x11: // Statistics this->AddEvent(new PRS1TotalLeakEvent(t, data[pos])); this->AddEvent(new PRS1SnoreEvent(t, data[pos+1])); value = data[pos+2]; if (is_bilevel) { // For bi-level modes, this appears to be the time-weighted average of EPAP and IPAP actually provided. this->AddEvent(new PRS1PressureAverageEvent(t, value)); } else { // For single-pressure modes, this appears to be the average effective "EPAP" provided by Flex. // // Sample data shows this value around 10.3 cmH2O for a prescribed pressure of 12.0 (C-Flex+ 3). // That's too low for an average pressure over time, but could easily be an average commanded EPAP. // When flex mode is off, this is exactly the current CPAP set point. this->AddEvent(new PRS1FlexPressureAverageEvent(t, value)); } this->AddEvent(new PRS1IntervalBoundaryEvent(t)); break; case 0x12: // Snore count per pressure // Some sessions (with lots of ramps) have multiple of these, each with a // different pressure. The total snore count across all of them matches the // total found in the stats event. if (data[pos] != 0) { CHECK_VALUES(data[pos], 1, 2); // 0 = CPAP pressure, 1 = bi-level EPAP, 2 = bi-level IPAP } //CHECK_VALUE(data[pos+1], 0x78); // pressure //CHECK_VALUE(data[pos+2], 1); // 16-bit snore count //CHECK_VALUE(data[pos+3], 0); value = (data[pos+2] | (data[pos+3] << 8)); this->AddEvent(new PRS1SnoresAtPressureEvent(t, data[pos], data[pos+1], value)); break; //case 0x13: // never seen case 0x0a: // Hypopnea // TODO: Why does this hypopnea have a different event code? // fall through case 0x14: // Hypopnea, new to F0V6 // TODO: Why does this hypopnea have a different event code? // fall through case 0x15: // Hypopnea, new to F0V6 // TODO: We should revisit whether this is elapsed or duration once (if) // we start calculating hypopneas ourselves. Their official definition // is 40% reduction in flow lasting at least 10s. duration = data[pos]; this->AddEvent(new PRS1HypopneaEvent(t - duration, 0)); break; default: DUMP_EVENT(); UNEXPECTED_VALUE(code, "known event code"); this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos-1, size+1)); break; } pos = startpos + size; } while (ok && pos < chunk_size); this->duration = t; return ok; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/resmed_EDFinfo.cpp000066400000000000000000000053641417327530600246330ustar00rootroot00000000000000/* SleepLib ResMed Loader Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include #include #include "SleepLib/session.h" #include "SleepLib/calcs.h" // #include "SleepLib/loader_plugins/resmed_loader.h" #include "SleepLib/loader_plugins/resmed_EDFinfo.h" ResMedEDFInfo::ResMedEDFInfo() :EDFInfo() { } ResMedEDFInfo::~ResMedEDFInfo() { } bool ResMedEDFInfo::Parse( ) // overrides and calls the super's Parse { if ( ! EDFInfo::Parse( ) ) { // qWarning() << "EDFInfo::Parse failed!"; // sleep(1); return false; } // Now massage some stuff into OSCAR's layout int snp = edfHdr.recordingident.indexOf("SRN="); serialnumber.clear(); for (int i = snp + 4; i < edfHdr.recordingident.length(); i++) { if (edfHdr.recordingident[i] == ' ') { break; } serialnumber += edfHdr.recordingident[i]; } if (!edfHdr.startdate_orig.isValid()) { qDebug() << "Invalid date time retreieved parsing EDF File " << filename; // sleep(1); return false; } startdate = qint64(edfHdr.startdate_orig.toTime_t()) * 1000LL; //startdate-=timezoneOffset(); if (startdate == 0) { qDebug() << "Invalid startdate = 0 in EDF File " << filename; // sleep(1); return false; } dur_data_record = (edfHdr.duration_Seconds * 1000.0L); enddate = startdate + dur_data_record * qint64(edfHdr.num_data_records); return true; } extern QHash resmed_codes; // Looks up foreign language Signal names that match this channelID EDFSignal *ResMedEDFInfo::lookupSignal(ChannelID ch) { // Get list of all known foreign language names for this channel auto channames = resmed_codes.find(ch); if (channames == resmed_codes.end()) { // no alternatives strings found for this channel return nullptr; } // This is bad, because ResMed thinks it was a cool idea to use two channels with the same name. // Scan through EDF's list of signals to see if any match for (auto & name : channames.value()) { EDFSignal *sig = lookupLabel(name); if (sig) return sig; } // Failed return nullptr; } void dumpEDFduration( EDFduration dur ) { qDebug() << "Fullpath" << dur.path << "Filename" << dur.filename << "Start" << dur.start << "End" << dur.end; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/resmed_EDFinfo.h000066400000000000000000000126041417327530600242730ustar00rootroot00000000000000/* SleepLib RESMED EDFinfo Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef RESMED_EDFINFO_H #define RESMED_EDFINFO_H #include #include "SleepLib/machine.h" // Base class: MachineLoader #include "SleepLib/machine_loader.h" #include "SleepLib/profiles.h" #include "SleepLib/loader_plugins/edfparser.h" enum EDFType { EDF_UNKNOWN, EDF_BRP, EDF_PLD, EDF_SAD, EDF_EVE, EDF_CSL, EDF_AEV }; EDFType lookupEDFType(const QString & filename); const QString resmed_class_name = STR_MACH_ResMed; class STRFile; // forward class ResMedEDFInfo : public EDFInfo { public: ResMedEDFInfo(); ~ResMedEDFInfo(); virtual bool Parse() override; // overrides and calls the super's Parse virtual qint64 GetDurationMillis() { return dur_data_record; } // overrides the super EDFSignal *lookupSignal(ChannelID ch); //! \brief The following are computed from the edfHdr data QString serialnumber; qint64 dur_data_record; qint64 startdate; qint64 enddate; }; class EDFduration { public: EDFduration() { start = end = 0; type = EDF_UNKNOWN; } EDFduration(quint32 start, quint32 end, QString path) : start(start), end(end), path(path) {} quint32 start; quint32 end; QString path; QString filename; EDFType type; }; void dumpEDFduration( EDFduration dur ); class STRRecord { public: STRRecord() { maskon.clear(); maskoff.clear(); maskdur = 0; maskevents = -1; mode = -1; rms9_mode = -1; set_pressure = -1; epap = -1; max_pressure = -1; min_pressure = -1; max_epap = -1; min_epap = -1; max_ps = -1; min_ps = -1; ps = -1; ipap = -1; max_ipap = -1; min_ipap = -1; epr = -1; epr_level = -1; sessionid = 0; ahi = -1; oai = -1; ai = -1; hi = -1; uai = -1; cai = -1; csr = -1; leak50 = -1; leak95 = -1; leakmax = -1; rr50 = -1; rr95 = -1; rrmax = -1; mv50 = -1; mv95 = -1; mvmax = -1; ie50 = -1; ie95 = -1; iemax = -1; tv50 = -1; tv95 = -1; tvmax = -1; mp50 = -1; mp95 = -1; mpmax = -1; tgtepap50 = -1; tgtepap95 = -1; tgtepapmax = -1; tgtipap50 = -1; tgtipap95 = -1; tgtipapmax = -1; s_RampTime = -1; s_RampEnable = -1; s_EPR_ClinEnable = -1; s_EPREnable = -1; s_PtAccess = -1; s_PtView = -1; s_ABFilter = -1; s_Mask = -1; s_Tube = -1; s_ClimateControl = -1; s_HumEnable = -1; s_HumLevel = -1; s_TempEnable = -1; s_Temp = -1; s_SmartStart = -1; s_SmartStop = -1; s_Comfort = -1; ramp_pressure = -1; date=QDate(); } STRRecord(const STRRecord & /*copy*/) = default; // All the data members QVector maskon; QVector maskoff; EventDataType maskdur; EventDataType maskevents; EventDataType mode; EventDataType rms9_mode; EventDataType set_pressure; EventDataType max_pressure; EventDataType min_pressure; EventDataType epap; EventDataType max_ps; EventDataType min_ps; EventDataType ps; EventDataType max_epap; EventDataType min_epap; EventDataType ipap; EventDataType max_ipap; EventDataType min_ipap; EventDataType epr; EventDataType epr_level; quint32 sessionid; EventDataType ahi; EventDataType oai; EventDataType ai; EventDataType hi; EventDataType uai; EventDataType cai; EventDataType csr; EventDataType leak50; EventDataType leak95; EventDataType leakmax; EventDataType rr50; EventDataType rr95; EventDataType rrmax; EventDataType mv50; EventDataType mv95; EventDataType mvmax; EventDataType tv50; EventDataType tv95; EventDataType tvmax; EventDataType mp50; EventDataType mp95; EventDataType mpmax; EventDataType ie50; EventDataType ie95; EventDataType iemax; EventDataType tgtepap50; EventDataType tgtepap95; EventDataType tgtepapmax; EventDataType tgtipap50; EventDataType tgtipap95; EventDataType tgtipapmax; EventDataType ramp_pressure; QDate date; EventDataType s_RampTime; int s_RampEnable; int s_EPR_ClinEnable; int s_EPREnable; int s_PtAccess; int s_PtView; int s_ABFilter; int s_Mask; int s_Tube; int s_ClimateControl; int s_HumEnable; EventDataType s_HumLevel; int s_TempEnable; EventDataType s_Temp; int s_SmartStart; int s_SmartStop; int s_Comfort; }; class STRFile { public: STRFile() : filename(QString()), days(0), edf(nullptr) {} STRFile(QString name, long int recCnt, ResMedEDFInfo *str) : filename(name), days(recCnt), edf(str) {} STRFile(const STRFile & /*copy*/) = default; virtual ~STRFile() {} QString filename; long int days; ResMedEDFInfo * edf; }; #endif // RESMED_EDFINFO_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/resmed_loader.cpp000066400000000000000000004606371417327530600246370ustar00rootroot00000000000000/* SleepLib ResMed Loader Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "SleepLib/session.h" #include "SleepLib/calcs.h" #include "SleepLib/loader_plugins/resmed_loader.h" #include "SleepLib/loader_plugins/resmed_EDFinfo.h" #ifdef DEBUG_EFFICIENCY #include // only available in 4.8 and later #endif ChannelID RMS9_EPR, RMS9_EPRLevel, RMS9_Mode, RMS9_SmartStart, RMS9_HumidStatus, RMS9_HumidLevel, RMS9_PtAccess, RMS9_Mask, RMS9_ABFilter, RMS9_ClimateControl, RMS9_TubeType, RMAS11_SmartStop, RMS9_Temp, RMS9_TempEnable, RMS9_RampEnable, RMAS1x_Comfort, RMAS11_PtView; const QString STR_ResMed_AirSense10 = "AirSense 10"; const QString STR_ResMed_AirSense11 = "AirSense 11"; const QString STR_ResMed_AirCurve10 = "AirCurve 10"; const QString STR_ResMed_AirCurve11 = "AirCurve 11"; const QString STR_ResMed_S9 = "S9"; const QString STR_UnknownModel = "Resmed ???"; // TODO: See the PRSLoader::LogUnexpectedMessage TODO about generalizing this for other loaders. void ResmedLoader::LogUnexpectedMessage(const QString & message) { m_importMutex.lock(); m_unexpectedMessages += message; m_importMutex.unlock(); } static const QVector AS11TestedModels {39420, 39423, 0}; ResmedLoader::ResmedLoader() { #ifndef UNITTEST_MODE const QString RMS9_ICON = ":/icons/rms9.png"; const QString RM10_ICON = ":/icons/airsense10.png"; const QString RM10C_ICON = ":/icons/aircurve.png"; m_pixmaps[STR_ResMed_S9] = QPixmap(RMS9_ICON); m_pixmap_paths[STR_ResMed_S9] = RMS9_ICON; m_pixmaps[STR_ResMed_AirSense10] = QPixmap(RM10_ICON); m_pixmap_paths[STR_ResMed_AirSense10] = RM10_ICON; m_pixmaps[STR_ResMed_AirCurve10] = QPixmap(RM10C_ICON); m_pixmap_paths[STR_ResMed_AirCurve10] = RM10C_ICON; #endif m_type = MT_CPAP; #ifdef DEBUG_EFFICIENCY timeInTimeDelta = timeInLoadBRP = timeInLoadPLD = timeInLoadEVE = 0; timeInLoadCSL = timeInLoadSAD = timeInEDFInfo = timeInEDFOpen = timeInAddWaveform = 0; #endif saveCallback = SaveSession; } ResmedLoader::~ResmedLoader() { } bool resmed_initialized = false; void ResmedLoader::Register() { if (resmed_initialized) return; qDebug() << "Registering ResmedLoader"; RegisterLoader(new ResmedLoader()); resmed_initialized = true; } void setupResMedTranslationMap(); // forward void ResmedLoader::initChannels() { using namespace schema; // Channel(ChannelID id, ChanType type, MachineType machtype, ScopeType scope, QString code, QString fullname, // QString description, QString label, QString unit, DataType datatype = DEFAULT, QColor = Qt::black, int link = 0); Channel * chan = new Channel(RMS9_Mode = 0xe203, SETTING, MT_CPAP, SESSION, "RMS9_Mode", QObject::tr("Mode"), QObject::tr("CPAP Mode"), QObject::tr("Mode"), "", LOOKUP, Qt::green); channel.add(GRP_CPAP, chan); chan->addOption(0, QObject::tr("CPAP")); chan->addOption(1, QObject::tr("APAP")); chan->addOption(2, QObject::tr("BiPAP-T")); chan->addOption(3, QObject::tr("BiPAP-S")); chan->addOption(4, QObject::tr("BiPAP-S/T")); chan->addOption(5, QObject::tr("BiPAP-T")); chan->addOption(6, QObject::tr("VPAPauto")); chan->addOption(7, QObject::tr("ASV")); chan->addOption(8, QObject::tr("ASVAuto")); chan->addOption(9, QObject::tr("iVAPS")); chan->addOption(10, QObject::tr("PAC")); chan->addOption(11, QObject::tr("Auto for Her")); chan->addOption(16, QObject::tr("Unknown")); // out of bounds of edf signal channel.add(GRP_CPAP, chan = new Channel(RMS9_EPR = 0xe201, SETTING, MT_CPAP, SESSION, "EPR", QObject::tr("EPR"), QObject::tr("ResMed Exhale Pressure Relief"), QObject::tr("EPR"), "", LOOKUP, Qt::green)); chan->addOption(0, STR_TR_Off); chan->addOption(1, QObject::tr("Ramp Only")); chan->addOption(2, QObject::tr("Full Time")); chan->addOption(3, QObject::tr("Patient???")); channel.add(GRP_CPAP, chan = new Channel(RMS9_EPRLevel = 0xe202, SETTING, MT_CPAP, SESSION, "EPRLevel", QObject::tr("EPR Level"), QObject::tr("Exhale Pressure Relief Level"), QObject::tr("EPR Level"), STR_UNIT_CMH2O, LOOKUP, Qt::blue)); // RMS9_SmartStart, RMS9_HumidStatus, RMS9_HumidLevel, // RMS9_PtAccess, RMS9_Mask, RMS9_ABFilter, RMS9_ClimateControl, RMS9_TubeType, // RMS9_Temp, RMS9_TempEnable; channel.add(GRP_CPAP, chan = new Channel(RMS9_SmartStart = 0xe204, SETTING, MT_CPAP, SESSION, "RMS9_SmartStart", QObject::tr("SmartStart"), QObject::tr("Machine auto starts by breathing"), QObject::tr("Smart Start"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(RMS9_HumidStatus = 0xe205, SETTING, MT_CPAP, SESSION, "RMS9_HumidStat", QObject::tr("Humid. Status"), QObject::tr("Humidifier Enabled Status"), QObject::tr("Humidifier Status"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(RMS9_HumidLevel = 0xe206, SETTING, MT_CPAP, SESSION, "RMS9_HumidLevel", QObject::tr("Humid. Level"), QObject::tr("Humidity Level"), QObject::tr("Humidity Level"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); // chan->addOption(1, "1"); // chan->addOption(2, "2"); // chan->addOption(3, "3"); // chan->addOption(4, "4"); // chan->addOption(5, "5"); // chan->addOption(6, "6"); // chan->addOption(7, "7"); // chan->addOption(8, "8"); channel.add(GRP_CPAP, chan = new Channel(RMS9_Temp = 0xe207, SETTING, MT_CPAP, SESSION, "RMS9_Temp", QObject::tr("Temperature"), QObject::tr("ClimateLine Temperature"), QObject::tr("Temperature"), "ºC", INTEGER, Qt::black)); channel.add(GRP_CPAP, chan = new Channel(RMS9_TempEnable = 0xe208, SETTING, MT_CPAP, SESSION, "RMS9_TempEnable", QObject::tr("Temp. Enable"), QObject::tr("ClimateLine Temperature Enable"), QObject::tr("Temperature Enable"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); chan->addOption(2, STR_TR_Auto); channel.add(GRP_CPAP, chan = new Channel(RMS9_ABFilter= 0xe209, SETTING, MT_CPAP, SESSION, "RMS9_ABFilter", QObject::tr("AB Filter"), QObject::tr("Antibacterial Filter"), QObject::tr("Antibacterial Filter"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_No); chan->addOption(1, STR_TR_Yes); channel.add(GRP_CPAP, chan = new Channel(RMS9_PtAccess= 0xe20A, SETTING, MT_CPAP, SESSION, "RMS9_PTAccess", QObject::tr("Pt. Access"), QObject::tr("Essentials"), QObject::tr("Essentials"), "", LOOKUP, Qt::black)); chan->addOption(0, QObject::tr("Plus")); chan->addOption(1, QObject::tr("On")); channel.add(GRP_CPAP, chan = new Channel(RMS9_ClimateControl= 0xe20B, SETTING, MT_CPAP, SESSION, "RMS9_ClimateControl", QObject::tr("Climate Control"), QObject::tr("Climate Control"), QObject::tr("Climate Control"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Auto); chan->addOption(1, QObject::tr("Manual")); channel.add(GRP_CPAP, chan = new Channel(RMS9_Mask= 0xe20C, SETTING, MT_CPAP, SESSION, "RMS9_Mask", QObject::tr("Mask"), QObject::tr("ResMed Mask Setting"), QObject::tr("Mask"), "", LOOKUP, Qt::black)); chan->addOption(0, QObject::tr("Pillows")); chan->addOption(1, QObject::tr("Full Face")); chan->addOption(2, QObject::tr("Nasal")); chan->addOption(3, QObject::tr("Unknown")); channel.add(GRP_CPAP, chan = new Channel(RMS9_RampEnable = 0xe20D, SETTING, MT_CPAP, SESSION, "RMS9_RampEnable", QObject::tr("Ramp"), QObject::tr("Ramp Enable"), QObject::tr("Ramp"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); chan->addOption(2, STR_TR_Auto); channel.add(GRP_CPAP, chan = new Channel(RMAS1x_Comfort = 0xe20E, SETTING, MT_CPAP, SESSION, "RMAS1x_Comfort", QObject::tr("Response"), QObject::tr("Response"), QObject::tr("Response"), "", LOOKUP, Qt::black)); chan->addOption(0, QObject::tr("Standard")); // This must be verified chan->addOption(1, QObject::tr("Soft")); channel.add(GRP_CPAP, chan = new Channel(RMAS11_SmartStop = 0xe20F, SETTING, MT_CPAP, SESSION, "RMAS11_SmartStop", QObject::tr("SmartStop"), QObject::tr("Machine auto stops by breathing"), QObject::tr("Smart Stop"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(RMAS11_PtView= 0xe210, SETTING, MT_CPAP, SESSION, "RMAS11_PTView", QObject::tr("Patient View"), QObject::tr("Patient View"), QObject::tr("Patient View"), "", LOOKUP, Qt::black)); chan->addOption(0, QObject::tr("Advanced")); chan->addOption(1, QObject::tr("Simple")); // Setup ResMeds signal name translation map setupResMedTranslationMap(); } ChannelID ResmedLoader::CPAPModeChannel() { return RMS9_Mode; } ChannelID ResmedLoader::PresReliefMode() { return RMS9_EPR; } ChannelID ResmedLoader::PresReliefLevel() { return RMS9_EPRLevel; } QHash resmed_codes; const QString STR_ext_TGT = "tgt"; const QString STR_ext_JSON = "json"; const QString STR_ext_CRC = "crc"; const QString RMS9_STR_datalog = "DATALOG"; const QString RMS9_STR_idfile = "Identification."; const QString RMS9_STR_strfile = "STR."; bool ResmedLoader::Detect(const QString & givenpath) { QDir dir(givenpath); if (!dir.exists()) { return false; } // ResMed drives contain a folder named "DATALOG". if (!dir.exists(RMS9_STR_datalog)) { return false; } // They also contain a file named "STR.edf". if (!dir.exists("STR.edf")) { return false; } return true; } QHash parseIdentLine( const QString line, MachineInfo * info); // forward void scanProductObject( const QJsonObject product, MachineInfo *info, QHash *idmap ); // forward MachineInfo ResmedLoader::PeekInfo(const QString & path) { if (!Detect(path)) return MachineInfo(); QFile f(path+"/"+RMS9_STR_idfile+"tgt"); QFile j(path+"/"+RMS9_STR_idfile+"json"); // Check for AS11 file first, just in case if (j.exists() ) { // somebody is reusing an SD card w/o re-formatting if ( !j.open(QIODevice::ReadOnly)) { return MachineInfo(); } if ( f.exists() ) { qDebug() << "Old Ident.tgt file is ignored"; } QByteArray identData = j.readAll(); j.close(); QJsonDocument identDoc(QJsonDocument::fromJson(identData)); QJsonObject identObj = identDoc.object(); if ( identObj.contains("FlowGenerator") && identObj["FlowGenerator"].isObject()) { QJsonObject flow = identObj["FlowGenerator"].toObject(); if ( flow.contains("IdentificationProfiles") && flow["IdentificationProfiles"].isObject()) { QJsonObject profiles = flow["IdentificationProfiles"].toObject(); if ( profiles.contains("Product") && profiles["Product"].isObject()) { QJsonObject product = profiles["Product"].toObject(); MachineInfo info = newInfo(); scanProductObject( product, &info, nullptr); return info; } else qDebug() << "No Product in Profiles"; } else qDebug() << "No IdentificationProfiles in FlowGenerator"; } else qDebug() << "No FlowGenerator in Identification.json"; return MachineInfo(); } // Abort if this file is dodgy.. if (f.exists() ) { if ( !f.open(QIODevice::ReadOnly)) { return MachineInfo(); } MachineInfo info = newInfo(); // Parse # entries into idmap. while (!f.atEnd()) { QString line = f.readLine().trimmed(); QHash hash = parseIdentLine( line, & info ); } return info; } // neither filename exists, return empty info return MachineInfo(); } long event_cnt = 0; bool parseIdentFile( QString path, MachineInfo * info, QHash & idmap ); // forward void backupSTRfiles( const QString strpath, const QString importPath, const QString backupPath, MachineInfo & info, QMap & STRmap ); // forward ResMedEDFInfo * fetchSTRandVerify( QString filename, QString serialNumber ); // forward int ResmedLoader::OpenWithCallback(const QString & dirpath, ResDaySaveCallback s) // alternate for unit testing { ResDaySaveCallback origCallback = saveCallback; saveCallback = s; int value = Open(dirpath); saveCallback = origCallback; return value; } int ResmedLoader::Open(const QString & dirpath) { qDebug() << "Starting ResmedLoader::Open( with " << dirpath << ")"; QString datalogPath; QHash idmap; // Temporary machine ID properties hash QString importPath(dirpath); importPath = importPath.replace("\\", "/"); // Strip off end "/" if any if (importPath.endsWith("/")) { importPath = importPath.section("/", 0, -2); } // Strip off DATALOG from importPath, and set newimportPath to the importPath containing DATALOG if (importPath.endsWith(RMS9_STR_datalog)) { datalogPath = importPath + "/"; importPath = importPath.section("/", 0, -2); } else { datalogPath = importPath + "/" + RMS9_STR_datalog + "/"; } // Add separator back importPath += "/"; // Check DATALOG folder exists and is readable if (!QDir().exists(datalogPath)) { qDebug() << "Missing DATALOG in" << dirpath; return -1; } m_abort = false; MachineInfo info = newInfo(); if ( ! parseIdentFile(importPath, & info, idmap) ) { qDebug() << "Failed to parse Identification file"; return -1; } qDebug() << "Info:" << info.series << info.model << info.modelnumber << info.serial; #ifdef IDENT_DEBUG qDebug() << "IdMap size:" << idmap.size(); foreach ( QString st , idmap.keys() ) { qDebug() << "Key" << st << "Value" << idmap[st]; } #endif // Abort if no serial number if (info.serial.isEmpty()) { qDebug() << "ResMed Data card is missing serial number in Indentification.tgt"; return -1; } bool compress_backups = p_profile->session->compressBackupData(); // Early check for STR.edf file, so we can early exit before creating faulty machine record. // str.edf is the first (primary) file to check, str.edf.gz is the secondary QString pripath = importPath + "STR.edf"; // STR.edf file QString secpath = pripath + STR_ext_gz; // STR.edf.gz file QString strpath; // If compression is enabled, swap primary and secondary paths if (compress_backups) { strpath = pripath; pripath = secpath; secpath = strpath; } // Check if primary path exists QFile f(pripath); if (f.exists()) { strpath = pripath; // If no primary file, check for secondary } else { f.setFileName(secpath); strpath = secpath; if (!f.exists()) { qDebug() << "Missing STR.edf file"; return -1; } } /////////////////////////////////////////////////////////////////////////////////// // Create machine object (unless it's already registered) /////////////////////////////////////////////////////////////////////////////////// QDate firstImportDay = QDate().fromString("2010-01-01", "yyyy-MM-dd"); // Before Series 8 machines (I think) Machine *mach = p_profile->lookupMachine(info.serial, info.loadername); if ( mach ) { // we have seen this machine qDebug() << "We have seen this machime"; mach->setInfo( info ); // update info QDate lastDate = mach->LastDay(); // use the last day for this machine firstImportDay = lastDate; // re-import the last day, to pick up partial days QDate purgeDate = mach->purgeDate(); if (purgeDate.isValid()) { firstImportDay = min(firstImportDay, purgeDate); } // firstImportDay = lastDate.addDays(-1); // start the day before, to pick up partial days // firstImportDay = lastDate.addDays(1); // start the day after until we figure out the purge } else { // Starting from new beginnings - new or purged qDebug() << "New machine or just purged"; p_profile->forceResmedPrefs(); int modelNum = info.modelnumber.toInt(); if ( modelNum >= 39000 ) { if ( ! AS11TestedModels.contains(modelNum) ) { QMessageBox::information(QApplication::activeWindow(), QObject::tr("Machine Untested"), QObject::tr("Your ResMed CPAP machine (Model %1) has not been tested yet.").arg(info.modelnumber) +"\n\n"+ QObject::tr("It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card to make sure it works with OSCAR.") ,QMessageBox::Ok); } } mach = p_profile->CreateMachine( info ); } QDateTime ignoreBefore = p_profile->session->ignoreOlderSessionsDate(); bool ignoreOldSessions = p_profile->session->ignoreOlderSessions(); if (ignoreOldSessions && (ignoreBefore.date() > firstImportDay)) firstImportDay = ignoreBefore.date(); qDebug() << "First day to import: " << firstImportDay.toString(); bool rebuild_from_backups = false; bool create_backups = p_profile->session->backupCardData(); QString backup_path = mach->getBackupPath(); // Compare QDirs rather than QStrings because separators may be different, especially on Windows. // We want to check whether import and backup paths are the same, regardless of variations in the string representations. QDir ipath(importPath); QDir bpath(backup_path); if (ipath == bpath) { // Don't create backups if importing from backup folder rebuild_from_backups = true; create_backups = false; } /////////////////////////////////////////////////////////////////////////////////// // Copy the idmap into machine objects properties, (overwriting any old values) /////////////////////////////////////////////////////////////////////////////////// for (auto i=idmap.begin(), idend=idmap.end(); i != idend; i++) { mach->info.properties[i.key()] = i.value(); } /////////////////////////////////////////////////////////////////////////////////// // Create the backup folder structure for storing a copy of everything in.. // (Unless we are importing from this backup folder) /////////////////////////////////////////////////////////////////////////////////// QDir dir; if (create_backups) { if ( ! dir.exists(backup_path)) { if ( ! dir.mkpath(backup_path) ) { qWarning() << "Could not create ResMed backup directory" << backup_path; } } // Create the STR_Backup folder if it doesn't exist QString strBackupPath = backup_path + "STR_Backup"; if ( ! dir.exists(strBackupPath) ) if (!dir.mkpath(strBackupPath)) qWarning() << "Could not create ResMed STR backup directory" << strBackupPath; QString newpath = backup_path + "DATALOG"; if ( ! dir.exists(newpath) ) if (!dir.mkpath(newpath)) qWarning() << "Could not create ResMed DATALOG backup directory" << newpath; // Copy Identification files to backup folder QString idfile_ext; if (QFile(importPath+RMS9_STR_idfile+STR_ext_TGT).exists()) { idfile_ext = STR_ext_TGT; } else if (QFile(importPath+RMS9_STR_idfile+STR_ext_JSON).exists()) { idfile_ext = STR_ext_JSON; } else { idfile_ext = ""; // should never happen... } QFile backupFile(backup_path + RMS9_STR_idfile + idfile_ext); if (backupFile.exists()) backupFile.remove(); if ( ! QFile::copy(importPath + RMS9_STR_idfile + idfile_ext, backup_path + RMS9_STR_idfile + idfile_ext)) qWarning() << "Could not copy" << importPath + RMS9_STR_idfile + idfile_ext << "to backup" << backupFile.fileName(); backupFile.setFileName(backup_path + RMS9_STR_idfile + STR_ext_CRC); if (backupFile.exists()) backupFile.remove(); if ( ! QFile::copy(importPath + RMS9_STR_idfile + STR_ext_CRC, backup_path + RMS9_STR_idfile + STR_ext_CRC)) qWarning() << "Could not copy" << importPath + RMS9_STR_idfile + STR_ext_CRC << "to backup" << backup_path; } /////////////////////////////////////////////////////////////////////////////////// // Open and Process STR.edf files (including those listed in STR_Backup) /////////////////////////////////////////////////////////////////////////////////// resdayList.clear(); emit updateMessage(QObject::tr("Locating STR.edf File(s)...")); QCoreApplication::processEvents(); // List all STR.edf backups and tag on latest for processing QMap STRmap; if ( ( ! rebuild_from_backups) /* && create_backups */ ) { // first we copy any STR_yyyymmdd.edf files and the Backup/STR.edf into STR_Backup and the STRmap backupSTRfiles( strpath, importPath, backup_path, info, STRmap ); //Then we copy the new imported STR.edf into Backup/STR.edf and add it to the STRmap QString importFile(importPath+"STR.edf"); QString backupFile(backup_path + "STR.edf"); ResMedEDFInfo * stredf = fetchSTRandVerify( importFile, info.serial ); if ( stredf != nullptr ) { bool addToSTRmap = true; QDate date = stredf->edfHdr.startdate_orig.date(); long int days = stredf->GetNumDataRecords(); qDebug() << importFile.section("/",-3,-1) << "starts at" << date << "for" << days << "ends" << date.addDays(days-1); if (STRmap.contains(date)) { // Keep the longer of the two STR files - or newer if equal! qDebug().noquote() << importFile.section("/",-3,-1) << "overlaps" << STRmap[date].filename.section("/",-3,-1) << "for" << days << "days, ends" << date.addDays(days-1); if (days >= STRmap[date].days) { qDebug() << "Removing" << STRmap[date].filename.section("/",-3,-1) << "with" << STRmap[date].days << "days from STRmap"; STRmap.remove(date); } else { qDebug() << "Skipping" << importFile.section("/",-3,-1); qWarning() << "New import str.edf file is shorter than exisiting files - should never happen"; delete stredf; addToSTRmap = false; } } if ( addToSTRmap ) { if ( compress_backups ) { backupFile += ".gz"; if ( QFile::exists( backupFile ) ) QFile::remove( backupFile ); compressFile(importFile, backupFile); } else { if ( QFile::exists( backupFile ) ) QFile::remove( backupFile ); if ( ! QFile::copy(importFile, backupFile) ) qWarning() << "Failed to copy" << importFile << "to" << backupFile; } STRmap[date] = STRFile(backupFile, days, stredf); qDebug() << "Adding" << importFile << "to STRmap as" << backupFile; // Meh.. these can be calculated if ever needed for ResScan SDcard export QFile sourcePath(importPath + "STR.crc"); if (sourcePath.exists()) { QFile backupFile(backup_path + "STR.crc"); if (backupFile.exists()) if (!backupFile.remove()) qWarning() << "Failed to remove" << backupFile.fileName(); if (!QFile::copy(importPath + "STR.crc", backup_path + "STR.crc")) qWarning() << "Failed to copy STR.crc from" << importPath << "to" << backup_path; } } } } else { // get the STR file that is in the BACKUP folder that we are rebuilding from qDebug() << "Rebuilding from BACKUP folder"; ResMedEDFInfo * stredf = fetchSTRandVerify( strpath, info.serial ); if ( stredf != nullptr ) { QDate date = stredf->edfHdr.startdate_orig.date(); long int days = stredf->GetNumDataRecords(); qDebug() << strpath.section("/",-2,-1) << "starts at" << date << "for" << days << "ends" << date.addDays(days-1); STRmap[date] = STRFile(strpath, days, stredf); } else { qDebug() << "Failed to open" << strpath; } } // end if not importing the backup files #ifdef STR_DEBUG qDebug() << "STRmap size is " << STRmap.size(); #endif // Now we open the REAL destination STR_Backup, and open the rest for later parsing dir.setPath(backup_path + "STR_Backup"); dir.setFilter(QDir::Files | QDir::Hidden | QDir::Readable); QFileInfoList flist = dir.entryInfoList(); QDate date; long int days; #ifdef STR_DEBUG qDebug() << "STR_Backup folder size is " << flist.size(); #endif qDebug() << "Add files in STR_Backup to STRmap (unless they are already there)"; // Add any STR_Backup versions to the file list for (auto & fi : flist) { QString filename = fi.fileName(); if ( ! filename.startsWith("STR", Qt::CaseInsensitive)) continue; if ( ! (filename.endsWith("edf.gz", Qt::CaseInsensitive) || filename.endsWith("edf", Qt::CaseInsensitive))) continue; QString datestr = filename.section("STR-",-1).section(".edf",0,0); // +"01"; ResMedEDFInfo * stredf = fetchSTRandVerify( fi.canonicalFilePath(), info.serial ); if ( stredf == nullptr ) continue; // Don't trust the filename date, pick the one inside the STR... date = stredf->edfHdr.startdate_orig.date(); days = stredf->GetNumDataRecords(); if (STRmap.contains(date)) { // Keep the longer of the two STR files qDebug().noquote() << fi.canonicalFilePath().section("/",-3,-1) << "overlaps" << STRmap[date].filename.section("/",-3,-1) << "for" << days << "ends" << date.addDays(days-1); if (days <= STRmap[date].days) { qDebug() << "Skipping" << fi.canonicalFilePath().section("/",-3,-1); delete stredf; continue; } else { qDebug() << "Removing" << STRmap[date].filename.section("/",-3,-1) << "from STRmap"; STRmap.remove(date); } } qDebug() << "Adding" << fi.canonicalFilePath().section("/", -3,-1) << "starts at" << date << "for" << days << "to STRmap"; STRmap[date] = STRFile(fi.canonicalFilePath(), days, stredf); } // end for walking the STR_Backup directory #ifdef STR_DEBUG qDebug() << "Finished STRmap size is now " << STRmap.size(); #endif /////////////////////////////////////////////////////////////////////////////////// // Build a Date map of all records in STR.edf files, populating ResDayList /////////////////////////////////////////////////////////////////////////////////// if ( ! ProcessSTRfiles(mach, STRmap, firstImportDay) ) { qCritical() << "ProcessSTR failed, abandoning this import"; return -1; } // We are done with the Parsed STR EDF objects, so delete them for (auto it=STRmap.begin(), end=STRmap.end(); it != end; ++it) { QString fullname = it.value().filename; #ifdef STR_DEBUG qDebug() << "Deleting edf object of" << fullname; #endif QString datepart = fullname.section("STR-",-1).section(".edf",0,0); if (datepart.size() == 6 ) { // old style name, change to full date QFile str(fullname); QString newdate = it.key().toString("yyyyMMdd"); QString newName = fullname.replace(datepart, newdate); qDebug() << "Renaming" << it.value().filename << "to" << newName; if ( ! str.rename(newName) ) qWarning() << "Rename Failed"; } delete it.value().edf; } #ifdef STR_DEBUG qDebug() << "Finished STRmap cleanup"; #endif /////////////////////////////////////////////////////////////////////////////////// // Scan DATALOG files, sort, and import any new sessions /////////////////////////////////////////////////////////////////////////////////// // First remove a legacy file if present... QFile impfile(mach->getDataPath()+"/imported_files.csv"); if (impfile.exists()) impfile.remove(); emit updateMessage(QObject::tr("Cataloguing EDF Files...")); QApplication::processEvents(); if (isAborted()) return 0; qDebug() << "Starting scan of DATALOG"; // sleep(1); dir.setPath(datalogPath); ScanFiles(mach, datalogPath, firstImportDay); if (isAborted()) return 0; qDebug() << "Finished DATALOG scan"; // sleep(1); // Now at this point we have resdayList populated with processable summary and EDF files data // that can be processed in threads.. emit updateMessage(QObject::tr("Queueing Import Tasks...")); QApplication::processEvents(); for (auto rdi=resdayList.begin(), rend=resdayList.end(); rdi != rend; rdi++) { if (isAborted()) return 0; QDate date = rdi.key(); ResMedDay & resday = rdi.value(); resday.date = date; checkSummaryDay( resday, date, mach ); } sessionCount = 0; emit updateMessage(QObject::tr("Importing Sessions...")); // Walk down the resDay list qDebug() << "About to call runTasks()"; runTasks(); qDebug() << "Finshed runTasks() with" << sessionCount << "new sessions"; int num_new_sessions = sessionCount; //////////////////////////////////////////////////////////////////////////////////// // Now look for any new summary data that can be extracted from STR.edf records //////////////////////////////////////////////////////////////////////////////////// emit updateMessage(QObject::tr("Finishing Up...")); QApplication::processEvents(); qDebug() << "About to call finishAddingSessions()"; finishAddingSessions(); qDebug() << "Finshed finishedAddingSessions() with" << sessionCount << "new sessions"; #ifdef DEBUG_EFFICIENCY { qint64 totalbytes = 0; qint64 totalns = 0; qDebug() << "Performance / Efficiency Information"; for (auto it = channel_efficiency.begin(), end=channel_efficiency.end(); it != end; it++) { ChannelID code = it.key(); qint64 value = it.value(); qint64 ns = channel_time[code]; totalbytes += value; totalns += ns; double secs = double(ns) / 1000000000.0L; QString s = value < 0 ? "saved" : "cost"; qDebug() << "Time-Delta conversion for " + schema::channel[code].label() + " " + s + " " + QString::number(qAbs(value)) + " bytes and took " + QString::number(secs, 'f', 4) + "s"; } qDebug() << "Total toTimeDelta function usage:" << totalbytes << "in" << double(totalns) / 1000000000.0 << "seconds"; qDebug() << "Total CPU time in EDF Open" << timeInEDFOpen; qDebug() << "Total CPU time in EDF Parser" << timeInEDFInfo; qDebug() << "Total CPU time in LoadBRP" << timeInLoadBRP; qDebug() << "Total CPU time in LoadPLD" << timeInLoadPLD; qDebug() << "Total CPU time in LoadSAD" << timeInLoadSAD; qDebug() << "Total CPU time in LoadEVE" << timeInLoadEVE; qDebug() << "Total CPU time in LoadCSL" << timeInLoadCSL; qDebug() << "Total CPU time in (BRP) AddWaveform" << timeInAddWaveform; qDebug() << "Total CPU time in TimeDelta function" << timeInTimeDelta; channel_efficiency.clear(); channel_time.clear(); } #endif // sessfiles.clear(); // strsess.clear(); // strdate.clear(); qDebug() << "Total Events " << event_cnt; qDebug() << "Total new Sessions " << num_new_sessions; mach->clearPurgeDate(); return num_new_sessions; } // end Open() ResMedEDFInfo * fetchSTRandVerify( QString filename, QString serialNumber) { ResMedEDFInfo * stredf = new ResMedEDFInfo(); if ( ! stredf->Open(filename ) ) { qWarning() << "Failed to open" << filename; delete stredf; return nullptr; } if ( ! stredf->Parse()) { qDebug() << "Faulty STR file" << filename; delete stredf; return nullptr; } if (stredf->serialnumber != serialNumber) { qDebug() << "Identification.tgt Serial number doesn't match" << filename; delete stredf; return nullptr; } return stredf; } void StoreSettings(Session * sess, STRRecord & R); // forward void ResmedLoader::checkSummaryDay( ResMedDay & resday, QDate date, Machine * mach ) { Day * day = p_profile->FindDay(date, MT_CPAP); bool reimporting = false; #ifdef STR_DEBUG qDebug() << "Starting checkSummary for" << date.toString(); #endif if (day && day->hasMachine(mach)) { // Sessions found for this machine, check if only summary info #ifdef STR_DEBUG qDebug() << "Sessions already found for this date"; #endif if (day->summaryOnly(mach) && (resday.files.size()> 0)) { // Note: if this isn't an EDF file, there's really no point doing this here, // but the worst case scenario is this session is deleted and reimported.. this just slows things down a bit in that case // This day was first imported as a summary from STR.edf, so we now totally want to redo this day #ifdef STR_DEBUG qDebug() << "Summary sessions only - delete them"; #endif QList sessions = day->getSessions(MT_CPAP); for (auto & sess : sessions) { day->removeSession(sess); delete sess; } } else if (day->noSettings(mach) && resday.str.date.isValid()) { // STR is present now, it wasn't before... we don't need to trash the files, but we do want the official settings. // Do it right here #ifdef STR_DEBUG qDebug() << "Date was missing settings, now we have them"; #endif for (auto & sess : day->sessions) { if (sess->machine() != mach) continue; #ifdef STR_DEBUG qDebug() << "Adding STR.edf information to session" << sess->session(); #endif StoreSettings(sess, resday.str); sess->setNoSettings(false); sess->SetChanged(true); sess->StoreSummary(); } } else { #ifdef STR_DEBUG qDebug() << "Have summary and details for this date!"; #endif int numPairs = 0; for (int i = 0; i sessions = day->getSessions(MT_CPAP, true); // If we have more sessions that we found in the str file, // or if the sessions are for a different machine, // leave well enough alone and don't re-import the day if (sessions.length() >= numPairs || sessions[0]->machine() != mach) { #ifdef STR_DEBUG qDebug() << "No new sessions -- skipping. Sessions now in day:"; qDebug() << " i sessionID s_first from - to"; for (int i=0; i < sessions.length(); i++) { qDebug().noquote() << i << sessions[i]->session() << sessions[i]->first() << QDateTime::fromMSecsSinceEpoch(sessions[i]->first()).toString(" hh:mm:ss") << "-" << QDateTime::fromMSecsSinceEpoch(sessions[i]->last()).toString("hh:mm:ss"); } #endif return; } qDebug() << "Maskevent count/2 (modified)" << numPairs << "is greater than the existing MT_CPAP session count" << sessions.length(); qDebug().noquote() << "Purging and re-importing" << day->date().toString(); for (auto & sess : sessions) { day->removeSession(sess); delete sess; } } } ResDayTask * rdt = new ResDayTask(this, mach, &resday, saveCallback); rdt->reimporting = reimporting; #ifdef STR_DEBUG qDebug() << "in checkSummary, Queue task for" << resday.date.toString(); #endif queTask(rdt); } /////////////////////////////////////////////////////////////////////////////////////////// // Sorted EDF files that need processing into date records according to ResMed noon split /////////////////////////////////////////////////////////////////////////////////////////// int ResmedLoader::ScanFiles(Machine * mach, const QString & datalog_path, QDate firstImport) { #ifdef DEBUG_EFFICIENCY QTime time; #endif bool create_backups = p_profile->session->backupCardData(); QString backup_path = mach->getBackupPath(); if (datalog_path == (backup_path + RMS9_STR_datalog + "/")) { // Don't create backups if importing from backup folder create_backups = false; } /////////////////////////////////////////////////////////////////////////////////////// // Generate list of files for later processing /////////////////////////////////////////////////////////////////////////////////////// qDebug() << "Generating list of EDF files"; qDebug() << "First Import date is " << firstImport; #ifdef DEBUG_EFFICIENCY time.start(); #endif QDir dir(datalog_path); // First list any EDF files in DATALOG folder - Series 9 machines QStringList filter; filter << "*.edf"; dir.setNameFilters(filter); QFileInfoList EDFfiles = dir.entryInfoList(); // Scan through all folders looking for EDF files, skip any already imported and peek inside to get durations dir.setNameFilters(QStringList()); dir.setFilter(QDir::Dirs | QDir::Hidden | QDir::NoDotAndDotDot); QString filename; bool ok; QFileInfoList dirlist = dir.entryInfoList(); int dirlistSize = dirlist.size(); // QDateTime ignoreBefore = p_profile->session->ignoreOlderSessionsDate(); // bool ignoreOldSessions = p_profile->session->ignoreOlderSessions(); // Scan for any sub folders and create files lists for (int i = 0; i < dirlistSize ; i++) { const QFileInfo & fi = dirlist.at(i); filename = fi.fileName(); int len = filename.length(); if (len == 4) { // This is a year folder in BackupDATALOG filename.toInt(&ok); if ( ! ok ) { qDebug() << "Skipping directory - bad 4-letter name" << filename; continue; } } else if (len == 8) { // test directory date QDate dirDate = QDate().fromString(filename, "yyyyMMdd"); if (dirDate < firstImport) { #ifdef SESSION_DEBUG qDebug() << "Skipping directory - ignore before " << filename; #endif continue; } } else { qDebug() << "Skipping directory - bad name size " << filename; continue; } // Get file lists under this directory dir.setPath(fi.canonicalFilePath()); dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); // Append all files to one big QFileInfoList EDFfiles.append(dir.entryInfoList()); } #ifdef DEBUG_EFFICIENCY qDebug() << "Generating EDF files list took" << time.elapsed() << "ms"; #endif qDebug() << "EDFfiles list size is " << EDFfiles.size(); //////////////////////////////////////////////////////////////////////////////////////// // Scan through EDF files, Extracting EDF Durations, and skipping already imported files // Check for duplicates along the way from compressed/uncompressed files //////////////////////////////////////////////////////////////////////////////////////// #ifdef DEBUG_EFFICIENCY time.start(); #endif QString datestr; QDateTime datetime; QDate date; int totalfiles = EDFfiles.size(); qDebug() << "Scanning " << totalfiles << " EDF files"; // Calculate number of files for progress bar for this stage int pbarFreq = totalfiles / 50; if (pbarFreq < 1) // stop a divide by zero pbarFreq = 1; emit setProgressValue(0); emit setProgressMax(totalfiles); QCoreApplication::processEvents(); qDebug() << "Starting EDF duration scan pass"; for (int i=0; i < totalfiles; ++i) { if (isAborted()) return 0; const QFileInfo & fi = EDFfiles.at(i); // Update progress bar if ((i % pbarFreq) == 0) { emit setProgressValue(i); QCoreApplication::processEvents(); } // Forget about it if it can't be read. if (!fi.isReadable()) { qWarning() << fi.fileName() << "is unreadable and has been ignored"; continue; } // Skip empty files if (fi.size() == 0) { qWarning() << fi.fileName() << "is empty and has been ignored"; continue; } filename = fi.fileName(); datestr = filename.section("_", 0, 1); datetime = QDateTime().fromString(datestr,"yyyyMMdd_HHmmss"); date = datetime.date(); // ResMed splits days at noon and now so do we, so all times before noon // go to the previous day if (datetime.time().hour() < 12) { date = date.addDays(-1); } if (date < firstImport) { #ifdef SESSION_DEBUG qDebug() << "Skipping file - ignore before " << filename; #endif continue; } // Chop off the .gz component if it exists, it's not needed at this stage if (filename.endsWith(STR_ext_gz)) { filename.chop(3); } QString fullpath = fi.filePath(); QString newpath = create_backups ? Backup(fullpath, backup_path) : fullpath; // Accept only .edf and .edf.gz files if (filename.right(4).toLower() != ("."+STR_ext_EDF)) continue; // QString ext = key.section("_", -1).section(".",0,0).toUpper(); // EDFType type = lookupEDFType(ext); // Find or create ResMedDay object for this date auto rd = resdayList.find(date); if (rd == resdayList.end()) { rd = resdayList.insert(date, ResMedDay(date)); rd.value().date = date; // We have data files without STR.edf record... the user MAY be planning on importing from another backup // later which could cause problems if we don't deal with it. // Best solution I can think of is import and tag the day No Settings and skip the day from overview. } ResMedDay & resday = rd.value(); if ( ! resday.files.contains(filename)) { resday.files[filename] = newpath; } } #ifdef DEBUG_EFFICIENCY qDebug() << "Scanning EDF files took" << time.elapsed() << "ms"; #endif qDebug() << "resdayList size is " << resdayList.size(); return resdayList.size(); } // end of scanFiles QString ResmedLoader::Backup(const QString & fullname, const QString & backup_path) { QDir dir; QString filename, yearstr, newname, oldname; bool compress = p_profile->session->compressBackupData(); bool ok; bool gz = (fullname.right(3).toLower() == STR_ext_gz); // Input file is a .gz? filename = fullname.section("/", -1); if (gz) { filename.chop(3); } yearstr = filename.left(4); yearstr.toInt(&ok, 10); if ( ! ok) { qDebug() << "Invalid EDF filename given to ResMedLoader::Backup()" << fullname; return ""; } QString newpath = backup_path + "DATALOG" + "/" + yearstr; if ( ! dir.exists(newpath) ) dir.mkpath(newpath); newname = newpath+"/"+filename; QString tmpname = newname; QString newnamegz = newname + STR_ext_gz; QString newnamenogz = newname; newname = compress ? newnamegz : newnamenogz; // First make sure the correct backup exists in the right place // Allow for second import of newer version of EVE and CSL edf files // But don't try to copy onto itself (as when rebuilding CPAP data from backup) // Compare QDirs rather than QStrings to handle variations in separators, etc. QFile nf(newname); QFile of(fullname); QFileInfo nfi(nf); QFileInfo ofi(of); QDir nfdir = nfi.dir(); QDir ofdir = ofi.dir(); if (nfdir != ofdir) { if (QFile::exists(newname)) // remove existing backup QFile::remove(newname); if (compress) { // If input file is already compressed.. copy it to the right location, otherwise compress it if (gz) { if (!QFile::copy(fullname, newname)) qWarning() << "unable to copy" << fullname << "to" << newname; } else compressFile(fullname, newname); } else { // If inputs a gz, uncompress it, otherwise copy is raw if (gz) uncompressFile(fullname, newname); else { if (!QFile::copy(fullname, newname)) qWarning() << "unable to copy" << fullname << "to" << newname; } } } // Now the correct backup is in place, we can trash any unneeded backup if (compress) { // Remove any uncompressed duplicate if (QFile::exists(newnamenogz)) QFile::remove(newnamenogz); } else { // Delete the compressed copy if (QFile::exists(newnamegz)) QFile::remove(newnamegz); } // Used to store it under Backup\Datalog // Remove any traces from old backup directory structure if (nfdir != ofdir) { oldname = backup_path + RMS9_STR_datalog + "/" + filename; if (QFile::exists(oldname)) QFile::remove(oldname); if (QFile::exists(oldname + STR_ext_gz)) QFile::remove(oldname + STR_ext_gz); } return newname; } // This function parses a list of STR files and creates a date ordered map of individual records bool ResmedLoader::ProcessSTRfiles(Machine *mach, QMap & STRmap, QDate firstImport) { bool AS_eleven = (mach->info.modelnumber.toInt() >= 39000); // QDateTime ignoreBefore = p_profile->session->ignoreOlderSessionsDate(); // bool ignoreOldSessions = p_profile->session->ignoreOlderSessions(); qDebug() << "Starting ProcessSTRfiles"; int totalRecs = 0; // Count the STR days for (auto it=STRmap.begin(), end=STRmap.end(); it != end; ++it) { STRFile & file = it.value(); ResMedEDFInfo & str = *file.edf; int days = str.GetNumDataRecords(); totalRecs += days; #ifdef STR_DEBUG qDebug() << "STR file is" << file.filename.section("/", -3, -1); qDebug() << "First day" << QDateTime::fromMSecsSinceEpoch(str.startdate, EDFInfo::localNoDST).date().toString() << "for" << days << "days"; #endif } emit updateMessage(QObject::tr("Parsing STR.edf records...")); emit setProgressMax(totalRecs); QCoreApplication::processEvents(); int currentRec = 0; // Walk through all the STR files in the STRmap for (auto it=STRmap.begin(), end=STRmap.end(); it != end; ++it) { STRFile & file = it.value(); ResMedEDFInfo & str = *file.edf; QDate date = str.edfHdr.startdate_orig.date(); // each STR.edf record starts at 12 noon int size = str.GetNumDataRecords(); QDate lastDay = date.addDays(size-1); #ifdef STR_DEBUG QString & strfile = file.filename; qDebug() << "Processing" << strfile.section("/", -3, -1) << date.toString() << "for" << size << "days"; qDebug() << "Last day is" << lastDay; #endif if ( lastDay < firstImport ) { #ifdef STR_DEBUG qDebug() << "LastDay before firstImport, skipping" << strfile.section("/", -3, -1); #endif continue; } // ResMed and their consistent naming and spacing... :/ EDFSignal *maskon = str.lookupLabel("Mask On"); // Series 9 machines if (!maskon) { maskon = str.lookupLabel("MaskOn"); // Series 1x machines } EDFSignal *maskoff = str.lookupLabel("Mask Off"); if (!maskoff) { maskoff = str.lookupLabel("MaskOff"); } EDFSignal *maskeventcount = str.lookupLabel("Mask Events"); if ( ! maskeventcount) { maskeventcount = str.lookupLabel("MaskEvents"); } if ( !maskon || !maskoff || !maskeventcount ) { qCritical() << "Corrupt or untranslated STR.edf file"; return false; } EDFSignal *sig = nullptr; // For each data record, representing 1 day each for (int rec = 0; rec < size; ++rec, date = date.addDays(1)) { emit setProgressValue(++currentRec); QCoreApplication::processEvents(); if (date < firstImport) { #ifdef SESSION_DEBUG qDebug() << "Skipping" << date.toString() << "Before" << firstImport.toString(); #endif continue; } // This is not what we want to check, we must look at this day in the database files... // Found the place in checkSummaryDay to compare session count with maskevents divided by 2 #ifdef SESSION_DEBUG qDebug() << "ResdayList size is" << resdayList.size(); #endif // auto rit = resdayList.find(date); // if (rit != resdayList.end()) { // // Already seen this record.. should check if the data is the same, but meh. // // At least check the maskeventcount to see if it changed... // if ( maskeventcount->dataArray[0] != rit.value().str.maskevents ) { // qDebug() << "Mask events don't match, purge" << rit.value().date.toString(); // // purge... // } // // #ifdef SESSION_DEBUG // qDebug() << "Skipping" << date.toString() << "Already saw this one"; // // #endif // continue; // } // else { // // qWarning() << date.toString() << "is missing from resdayList - FIX THIS"; // // continue; // // } int recstart = rec * maskon->sampleCnt; bool validday = false; for (int s = 0; s < maskon->sampleCnt; ++s) { qint32 on = maskon->dataArray[recstart + s]; qint32 off = maskoff->dataArray[recstart + s]; if (((on >= 0) && (off >= 0)) && (on != off)) {// ignore very short on-off times validday=true; } } if ( ! validday) { // There are no mask on/off events, so this STR day is useless. qDebug() << "Skipping" << date.toString() << "No mask events"; continue; } #ifdef STR_DEBUG qDebug() << "Adding" << date.toString() << "to resdayLisyt b/c we have STR record"; #endif auto rit = resdayList.insert(date, ResMedDay(date)); #ifdef STR_DEBUG qDebug() << "Setting up STRRecord for" << date.toString(); #endif STRRecord &R = rit.value().str; uint noonstamp = QDateTime(date,QTime(12,0,0), EDFInfo::localNoDST).toTime_t(); R.date = date; // skipday = false; // For every mask on, there will be a session within 1 minute either way // We can use that for data matching // Scan the mask on/off events by minute R.maskon.resize(maskon->sampleCnt); R.maskoff.resize(maskoff->sampleCnt); int lastOn = -1; int lastOff = -1; for (int s = 0; s < maskon->sampleCnt; ++s) { qint32 on = maskon->dataArray[recstart + s]; // these on/off times are minutes since noon qint32 off = maskoff->dataArray[recstart + s]; // we want the actual time in seconds if ( (on > 24*60) || (off > 24*60) ) { qWarning().noquote() << "Mask times are out of range. Possible SDcard corruption" << "date" << date << "on" << on << "off" < 0 ) { // convert them to seconds since midnight lastOn = s; R.maskon[s] = (noonstamp + (on * 60)); } else R.maskon[s] = 0; if ( off > 0 ) { lastOff = s; R.maskoff[s] = (noonstamp + (off * 60)); } else R.maskoff[s] = 0; } // two conditions that need dealing with, mask running at noon start, and finishing at noon start.. // (Sessions are forcibly split by resmed.. why the heck don't they store it that way???) if ((R.maskon[0]==0) && (R.maskoff[0]>0)) { R.maskon[0] = noonstamp; } if ( (lastOn >= 0) && (lastOff >= 0) ) { if ((R.maskon[lastOn] > 0) && (R.maskoff[lastOff] == 0)) { R.maskoff[lastOff] = QDateTime(date,QTime(12,0,0), EDFInfo::localNoDST).addDays(1).toTime_t() - 1; } } R.maskevents = maskeventcount->dataArray[rec]; CPAPMode mode = MODE_UNKNOWN; if ((sig = str.lookupSignal(CPAP_Mode))) { int mod = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; R.rms9_mode = mod; if ( AS_eleven ) { // translate AS11 mode values back to S9 / AS10 values switch ( mod ) { case 0: R.rms9_mode = 16; // Unknown break; case 1: R.rms9_mode = 1; // still APAP break; case 2: R.rms9_mode = 11; //make it look like A4Her break; case 3: R.rms9_mode = 0; // make it be CPAP break; default: R.rms9_mode = 16; // unknown for now break; } } int RMS9_mode = R.rms9_mode; switch ( RMS9_mode ) { case 11: mode = MODE_APAP; // For her is a special apap break; case 9: mode = MODE_AVAPS; break; case 8: // mod 8 == vpap adapt variable epap mode = MODE_ASV_VARIABLE_EPAP; break; case 7: // mod 7 == vpap adapt mode = MODE_ASV; break; case 6: // mod 6 == vpap auto (Min EPAP, Max IPAP, PS) mode = MODE_BILEVEL_AUTO_FIXED_PS; break; case 5: // 4,5 are S/T types... case 4: case 3: // mod 3 == vpap s fixed pressure (EPAP, IPAP, No PS) mode = MODE_BILEVEL_FIXED; break; case 2: mode = MODE_BILEVEL_FIXED; break; case 1: mode = MODE_APAP; // mod 1 == apap break; case 0: mode = MODE_CPAP; // mod 0 == cpap break; default: mode = MODE_UNKNOWN; } R.mode = mode; // Settings.CPAP.Starting Pressure if ((R.rms9_mode == 0) && (sig = str.lookupLabel("S.C.StartPress"))) { R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } // Settings.Adaptive Starting Pressure? if ( (R.rms9_mode == 1) && ((sig = str.lookupLabel("S.AS.StartPress")) || (sig = str.lookupLabel("S.A.StartPress"))) ) { R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } // mode 11 = APAP for her? if ( (R.rms9_mode == 11) && (sig = str.lookupLabel("S.AFH.StartPress"))) { R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((R.mode == MODE_BILEVEL_FIXED) && (sig = str.lookupLabel("S.BL.StartPress"))) { // Bilevel Starting Pressure R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if (((R.mode == MODE_ASV) || (R.mode == MODE_ASV_VARIABLE_EPAP) || (R.mode == MODE_BILEVEL_AUTO_FIXED_PS)) && (sig = str.lookupLabel("S.VA.StartPress"))) { // Bilevel Starting Pressure R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } } // Collect the staistics if ((sig = str.lookupLabel("Mask Dur")) || (sig = str.lookupLabel("Duration"))) { R.maskdur = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("Leak Med")) || (sig = str.lookupLabel("Leak.50"))) { float gain = sig->gain * 60.0; R.leak50 = EventDataType(sig->dataArray[rec]) * gain; } if ((sig = str.lookupLabel("Leak Max"))|| (sig = str.lookupLabel("Leak.Max"))) { float gain = sig->gain * 60.0; R.leakmax = EventDataType(sig->dataArray[rec]) * gain; } if ((sig = str.lookupLabel("Leak 95")) || (sig = str.lookupLabel("Leak.95"))) { float gain = sig->gain * 60.0; R.leak95 = EventDataType(sig->dataArray[rec]) * gain; } if ((sig = str.lookupLabel("RespRate.50")) || (sig = str.lookupLabel("RR Med"))) { R.rr50 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("RespRate.Max")) || (sig = str.lookupLabel("RR Max"))) { R.rrmax = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("RespRate.95")) || (sig = str.lookupLabel("RR 95"))) { R.rr95 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("MinVent.50")) || (sig = str.lookupLabel("Min Vent Med"))) { R.mv50 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("MinVent.Max")) || (sig = str.lookupLabel("Min Vent Max"))) { R.mvmax = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("MinVent.95")) || (sig = str.lookupLabel("Min Vent 95"))) { R.mv95 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("TidVol.50")) || (sig = str.lookupLabel("Tid Vol Med"))) { R.tv50 = EventDataType(sig->dataArray[rec]) * (sig->gain*1000.0); } if ((sig = str.lookupLabel("TidVol.Max")) || (sig = str.lookupLabel("Tid Vol Max"))) { R.tvmax = EventDataType(sig->dataArray[rec]) * (sig->gain*1000.0); } if ((sig = str.lookupLabel("TidVol.95")) || (sig = str.lookupLabel("Tid Vol 95"))) { R.tv95 = EventDataType(sig->dataArray[rec]) * (sig->gain*1000.0); } if ((sig = str.lookupLabel("MaskPress.50")) || (sig = str.lookupLabel("Mask Pres Med"))) { R.mp50 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("MaskPress.Max")) || (sig = str.lookupLabel("Mask Pres Max"))) { R.mpmax = EventDataType(sig->dataArray[rec]) * sig->gain ; } if ((sig = str.lookupLabel("MaskPress.95")) || (sig = str.lookupLabel("Mask Pres 95"))) { R.mp95 = EventDataType(sig->dataArray[rec]) * sig->gain ; } if ((sig = str.lookupLabel("TgtEPAP.50")) || (sig = str.lookupLabel("Exp Pres Med"))) { R.tgtepap50 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("TgtEPAP.Max")) || (sig = str.lookupLabel("Exp Pres Max"))) { R.tgtepapmax = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("TgtEPAP.95")) || (sig = str.lookupLabel("Exp Pres 95"))) { R.tgtepap95 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("TgtIPAP.50")) || (sig = str.lookupLabel("Insp Pres Med"))) { R.tgtipap50 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("TgtIPAP.Max")) || (sig = str.lookupLabel("Insp Pres Max"))) { R.tgtipapmax = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("TgtIPAP.95")) || (sig = str.lookupLabel("Insp Pres 95"))) { R.tgtipap95 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("I:E Med"))) { R.ie50 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("I:E Max"))) { R.iemax = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("I:E 95"))) { R.ie95 = EventDataType(sig->dataArray[rec]) * sig->gain; } // Collect the pressure settings bool haveipap = false; Q_UNUSED( haveipap ); if ((sig = str.lookupSignal(CPAP_IPAP))) { R.ipap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; haveipap = true; } if ((sig = str.lookupSignal(CPAP_EPAP))) { R.epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if (R.mode == MODE_AVAPS) { if ((sig = str.lookupLabel("S.i.StartPress"))) { R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.i.EPAP"))) { R.min_epap = R.max_epap = R.epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.i.MinPS"))) { R.min_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.i.MinEPAP"))) { R.min_epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.i.MaxEPAP"))) { R.max_epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.i.MaxPS"))) { R.max_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ( (R.epap >= 0) && (R.max_epap == R.epap) ) { R.max_ipap = R.epap + R.max_ps; R.min_ipap = R.epap + R.min_ps; } else { R.max_ipap = R.max_epap + R.max_ps; R.min_ipap = R.min_epap + R.min_ps; } // qDebug() << "AVAPS mode; Ramp" << R.ramp_pressure << "Min EPAP" << R.min_epap << // "Max EPAP" << R.max_epap << "Min PS" << R.min_ps << "Max PS" << R.max_ps << // "Min IPAP" << R.min_ipap << "Max_IPAP" << R.max_ipap; } if (R.mode == MODE_ASV) { if ((sig = str.lookupLabel("S.AV.StartPress"))) { R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.AV.EPAP"))) { R.min_epap = R.max_epap = R.epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.AV.MinPS"))) { R.min_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.AV.MaxPS"))) { R.max_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; R.max_ipap = R.epap + R.max_ps; R.min_ipap = R.epap + R.min_ps; } } if (R.mode == MODE_ASV_VARIABLE_EPAP) { if ((sig = str.lookupLabel("S.AA.StartPress"))) { EventDataType sp = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; R.ramp_pressure = sp; } if ((sig = str.lookupLabel("S.AA.MinEPAP"))) { R.min_epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.AA.MaxEPAP"))) { R.max_epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.AA.MinPS"))) { R.min_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.AA.MaxPS"))) { R.max_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; R.max_ipap = R.max_epap + R.max_ps; R.min_ipap = R.min_epap + R.min_ps; } } if ( (R.rms9_mode == 11) && (sig = str.lookupLabel("S.AFH.MaxPress")) ) { R.max_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } else if ((sig = str.lookupSignal(CPAP_PressureMax))) { R.max_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ( (R.rms9_mode == 11) && (sig = str.lookupLabel("S.AFH.MinPress")) ) { R.min_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } else if ((sig = str.lookupSignal(CPAP_PressureMin))) { R.min_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupSignal(RMS9_SetPressure))) { R.set_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupSignal(CPAP_EPAPHi))) { R.max_epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupSignal(CPAP_EPAPLo))) { R.min_epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupSignal(CPAP_IPAPHi))) { R.max_ipap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; haveipap = true; } if ((sig = str.lookupSignal(CPAP_IPAPLo))) { R.min_ipap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; haveipap = true; } if ((sig = str.lookupSignal(CPAP_PS))) { R.ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } // Okay, problem here: THere are TWO PSMin & MAX dataArrays on the 36037 with the same string // One is for ASV mode, and one is for ASVAuto int psvar = (mode == MODE_ASV_VARIABLE_EPAP) ? 1 : 0; if ((sig = str.lookupLabel("Max PS", psvar))) { R.max_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("Min PS", psvar))) { R.min_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } // ///// if (!haveipap) { // ///// } if (mode == MODE_ASV_VARIABLE_EPAP) { R.min_ipap = R.min_epap + R.min_ps; R.max_ipap = R.max_epap + R.max_ps; } else if (mode == MODE_ASV) { R.min_ipap = R.epap + R.min_ps; R.max_ipap = R.epap + R.max_ps; } // Collect the other settings if ((sig = str.lookupLabel("S.AS.Comfort"))) { R.s_Comfort = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) R.s_Comfort--; } EventDataType epr = -1, epr_level = -1; bool a1x = false; // AS-10 or AS-11 if ((mode == MODE_CPAP) || (mode == MODE_APAP) ) { if ((sig = str.lookupSignal(RMS9_EPR))) { epr= EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) epr--; } if ((sig = str.lookupSignal(RMS9_EPRLevel))) { epr_level= EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.EPR.EPRType"))) { a1x = true; epr = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; epr += 1; if ( AS_eleven ) epr--; } int epr_on=0, clin_epr_on=0; if ((sig = str.lookupLabel("S.EPR.EPREnable"))) { // first check machines opinion a1x = true; epr_on = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) epr_on--; } if (epr_on && (sig = str.lookupLabel("S.EPR.ClinEnable"))) { a1x = true; clin_epr_on = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) clin_epr_on--; } if (a1x && !(epr_on && clin_epr_on)) { epr = 0; epr_level = 0; } } if ((epr >= 0) && (epr_level >= 0)) { R.epr_level = epr_level; R.epr = epr; } else { if (epr >= 0) { static bool warn=false; if (!warn) { // just nag once qDebug() << "If you can read this, please tell the developers you found a ResMed with EPR but no EPR_LEVEL so he can remove this warning"; // sleep(1); warn = true; } R.epr = (epr > 0) ? 1 : 0; R.epr_level = epr; } else if (epr_level >= 0) { R.epr_level = epr_level; R.epr = (epr_level > 0) ? 1 : 0; } } if ((sig = str.lookupLabel("AHI"))) { R.ahi = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("AI"))) { R.ai = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("HI"))) { R.hi = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("UAI"))) { R.uai = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("CAI"))) { R.cai = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("OAI"))) { R.oai = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("CSR"))) { R.csr = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.RampTime"))) { R.s_RampTime = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.RampEnable"))) { R.s_RampEnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) R.s_RampEnable--; if ( R.s_RampEnable == 2 ) R.s_RampTime = -1; } if ((sig = str.lookupLabel("S.EPR.ClinEnable"))) { R.s_EPR_ClinEnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) R.s_EPR_ClinEnable--; } if ((sig = str.lookupLabel("S.EPR.EPREnable"))) { R.s_EPREnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) R.s_EPREnable--; } if ((sig = str.lookupLabel("S.ABFilter"))) { R.s_ABFilter = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) R.s_ABFilter--; } if ((sig = str.lookupLabel("S.ClimateControl"))) { R.s_ClimateControl = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) R.s_ClimateControl--; } if ((sig = str.lookupLabel("S.Mask"))) { R.s_Mask = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) { if ( R.s_Mask < 2 || R.s_Mask > 4 ) R.s_Mask = 4; // unknown mask type else R.s_Mask -= 2; // why be consistent? } } if ((sig = str.lookupLabel("S.PtAccess"))) { if ( AS_eleven ) { R.s_PtView = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; R.s_PtView--; } else R.s_PtAccess = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.SmartStart"))) { R.s_SmartStart = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) R.s_SmartStart--; // qDebug() << "SmartStart is set to" << R.s_SmartStart; } if ((sig = str.lookupLabel("S.SmartStop"))) { R.s_SmartStop = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) R.s_SmartStop--; qDebug() << "SmartStop is set to" << R.s_SmartStop; } if ((sig = str.lookupLabel("S.HumEnable"))) { R.s_HumEnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) R.s_HumEnable--; } if ((sig = str.lookupLabel("S.HumLevel"))) { R.s_HumLevel = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.TempEnable"))) { R.s_TempEnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; if ( AS_eleven ) R.s_TempEnable--; } if ((sig = str.lookupLabel("S.Temp"))) { R.s_Temp = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.Tube"))) { R.s_Tube = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ( R.min_pressure == 0 ) { qDebug() << "Min Pressure is zero on" << date.toString(); } #ifdef STR_DEBUG qDebug() << "Finished" << date.toString(); #endif } #ifdef STR_DEBUG qDebug() << "Finished" << strfile; #endif } qDebug() << "Finished ProcessSTR"; return true; } /////////////////////////////////////////////////////////////////////////////////// // Parse Identification.tgt file (containing serial number and machine information) /////////////////////////////////////////////////////////////////////////////////// // QHash parseIdentLine( const QString line, MachineInfo * info); // forward // void scanProductObject( QJsonObject product, MachineInfo *info, QHash *idmap); // forward bool parseIdentFile( QString path, MachineInfo * info, QHash & idmap ) { QString filename = path + RMS9_STR_idfile + STR_ext_TGT; QFile f(filename); QFile j(path + RMS9_STR_idfile + STR_ext_JSON); if (j.exists() ) { // chose the AS11 file if both exist if ( !j.open(QIODevice::ReadOnly)) { return false; } QByteArray identData = j.readAll(); j.close(); QJsonDocument identDoc(QJsonDocument::fromJson(identData)); QJsonObject identObj(identDoc.object()); if ( identObj.contains("FlowGenerator") && identObj["FlowGenerator"].isObject()) { QJsonObject flow = identObj["FlowGenerator"].toObject(); if ( flow.contains("IdentificationProfiles") && flow["IdentificationProfiles"].isObject()) { QJsonObject profiles = flow["IdentificationProfiles"].toObject(); if ( profiles.contains("Product") && profiles["Product"].isObject()) { QJsonObject product = profiles["Product"].toObject(); // passed in MachineInfo info = newInfo(); scanProductObject( product, info, &idmap); return true; } } } return false; } // Abort if this file is dodgy.. if (f.exists() ) { if ( !f.open(QIODevice::ReadOnly)) { return false; } qDebug() << "Parsing Identification File " << filename; // emit updateMessage(QObject::tr("Parsing Identification File")); // QApplication::processEvents(); // Parse # entries into idmap. while (!f.atEnd()) { QString line = f.readLine().trimmed(); QHash hash = parseIdentLine( line, info ); idmap.unite(hash); } f.close(); return true; } return false; } void scanProductObject( QJsonObject product, MachineInfo *info, QHash *idmap) { QHash hash1, hash2, hash3; if (product.contains("SerialNumber")) { info->serial = product["SerialNumber"].toString(); hash1["SerialNumber"] = product["SerialNumber"].toString(); if (idmap) idmap->unite(hash1); } if (product.contains("ProductCode")) { info->modelnumber = product["ProductCode"].toString(); hash2["ProductCode"] = info->modelnumber; if (idmap) idmap->unite(hash2); } if (product.contains("ProductName")) { info->model = product["ProductName"].toString(); hash3["ProductName"] = info->model; if (idmap) idmap->unite(hash3); int idx = info->model.indexOf("11"); info->series = info->model.left(idx+2); } } void backupSTRfiles( const QString strpath, const QString importPath, const QString backupPath, MachineInfo & info, QMap & STRmap ) { Q_UNUSED(strpath); qDebug() << "Starting backupSTRfiles during new IMPORT"; QDir dir; // Qstring strBackupPath(backupPath+"STR_Backup"); QStringList strfiles; // add Backup/STR.edf - make sure it ends up in the STRmap strfiles.push_back(backupPath+"STR.edf"); // Just in case we are importing from a Backup folder in a different Profile, process OSCAR backup structures QString strBackupPath(importPath + "STR_Backup"); dir.setPath(strBackupPath); dir.setFilter(QDir::Files | QDir::Hidden | QDir::Readable); QFileInfoList flist = dir.entryInfoList(); // Add any STR_Backup versions to the file list for (auto & fi : flist) { // this is empty if imprting from an SD card QString filename = fi.fileName(); if ( ! filename.startsWith("STR", Qt::CaseInsensitive)) continue; if ( ! (filename.endsWith("edf.gz", Qt::CaseInsensitive) || filename.endsWith("edf", Qt::CaseInsensitive))) continue; strfiles.push_back(fi.canonicalFilePath()); } #ifdef STR_DEBUG qDebug() << "STR file list size is" << strfiles.size(); #endif // Now copy any of these files to the Backup folder adding the file date to the file name // and put it into the STRmap structure for (auto & filename : strfiles) { QDate date; long int days; ResMedEDFInfo * stredf = fetchSTRandVerify( filename, info.serial ); if ( stredf == nullptr ) continue; date = stredf->edfHdr.startdate_orig.date(); days = stredf->GetNumDataRecords(); if (STRmap.contains(date)) { qDebug() << "STRmap already contains" << date.toString("yyyy-MM-dd") << "for" << STRmap[date].days << "ending" << date.addDays(STRmap[date].days-1); qDebug() << filename.section("/",-2,-1) << "has" << days << "ending" << date.addDays(days-1); if ( days <= STRmap[date].days ) { qDebug() << "Skipping" << filename.section("/",-2,-1) << "Keeping" << STRmap[date].filename.section("/",-2,-1); delete stredf; continue; } else { qDebug() << "Dropping" << STRmap[date].filename.section("/", -2, -1) << "Keeping" << filename.section("/",-2,-1); delete STRmap[date].edf; STRmap.remove(date); // new one gets added after we know its new name } } // now create the new backup name QString newname = "STR-"+date.toString("yyyyMMdd")+"."+STR_ext_EDF; QString backupfile = backupPath+"/STR_Backup/"+newname; QString gzfile = backupfile + STR_ext_gz; QString nongzfile = backupfile; bool compress_backups = p_profile->session->compressBackupData(); backupfile = compress_backups ? gzfile : nongzfile; STRmap[date] = STRFile(backupfile, days, stredf); qDebug() << "Adding" << filename.section("/",-3,-1) << "with" << days << "days as" << backupfile.section("/", -3, -1) << "to STRmap"; if ( QFile::exists(backupfile)) { QFile::remove(backupfile); } #ifdef STR_DEBUG qDebug() << "Copying" << filename.section("/",-3,-1) << "to" << backupfile.section("/",-3,-1); #endif if (filename.endsWith(STR_ext_gz,Qt::CaseInsensitive)) { // we have a compressed file if (compress_backups) { // fine, copy it to backup folder if (!QFile::copy(filename, backupfile)) qWarning() << "Failed to copy" << filename << "to" << backupfile; } else { // oops, uncompress it to the backup folder uncompressFile(filename, backupfile); } } else { // file is not compressed if (compress_backups) { // so compress it into the backup folder compressFile(filename, backupfile); } else { // and that's OK, just copy it over if (!QFile::copy(filename, backupfile)) qWarning() << "Failed to copy" << filename << "to" << backupfile; } } // Remove any duplicate compressed/uncompressed backup file if (compress_backups) QFile::exists(nongzfile) && QFile::remove(nongzfile); else QFile::exists(gzfile) && QFile::remove(gzfile); } // end for walking the STR files list #ifdef STR_DEBUG qDebug() << "STRmap has" << STRmap.size() << "entries"; #endif qDebug() << "Finished backupSTRfiles during new IMPORT"; } QHash parseIdentLine( const QString line, MachineInfo * info) { QHash hash; if (!line.isEmpty()) { QString key = line.section(" ", 0, 0).section("#", 1); QString value = line.section(" ", 1); if (key == "SRN") { // Serial Number info->serial = value; } else if (key == "PNA") { // Product Name value.replace("_"," "); if (value.contains(STR_ResMed_AirSense10)) { // value.replace(STR_ResMed_AirSense10, ""); info->series = STR_ResMed_AirSense10; } else if (value.contains(STR_ResMed_AirCurve10)) { // value.replace(STR_ResMed_AirCurve10, ""); info->series = STR_ResMed_AirCurve10; } else { // it will be a Series 9, and might not contain (STR_ResMed_S9)) value.replace("("," "); // might sometimes have a double space... value.replace(")",""); if ( ! value.startsWith(STR_ResMed_S9)) { value.replace(STR_ResMed_S9, ""); value.insert(0, " "); // There's proablely a better way than this value.insert(0, STR_ResMed_S9); // two step way to put "S9 " at the start } info->series = STR_ResMed_S9; // value.replace(STR_ResMed_S9, ""); } // if (value.contains("Adapt", Qt::CaseInsensitive)) { // if (!value.contains("VPAP")) { // value.replace("Adapt", QObject::tr("VPAP Adapt")); // } // } info->model = value.trimmed(); } else if (key == "PCD") { // Product Code info->modelnumber = value; } hash[key] = value; } return hash; } EDFType lookupEDFType(const QString & filename) { QString text = filename.section("_", -1).section(".",0,0).toUpper(); if (text == "EVE") { return EDF_EVE; } else if (text =="BRP") { return EDF_BRP; } else if (text == "PLD") { return EDF_PLD; } else if (text == "SAD") { return EDF_SAD; } else if (text == "CSL") { return EDF_CSL; } else if (text == "AEV") { return EDF_AEV; } else return EDF_UNKNOWN; } /////////////////////////////////////////////////////////////////////////////// // Looks inside an EDF or EDF.gz and grabs the start and duration /////////////////////////////////////////////////////////////////////////////// EDFduration getEDFDuration(const QString & filename) { // qDebug() << "getEDFDuration called for" << filename; QString ext = filename.section("_", -1).section(".",0,0).toUpper(); if ((ext == "EVE") || (ext == "CSL")) { // don't even try with Annotation-only edf files EDFduration dur(0, 0, filename); dur.type = lookupEDFType(filename); qDebug() << "File ext is" << ext; dumpEDFduration(dur); return dur; } bool ok1, ok2; int num_records; double rec_duration; QDateTime startDate; // We will just look at the header part of the edf file here if (!filename.endsWith(".gz", Qt::CaseInsensitive)) { QFile file(filename); if (!file.open(QFile::ReadOnly)) return EDFduration(0, 0, filename); if (!file.seek(0xa8)) { file.close(); return EDFduration(0, 0, filename); } QByteArray bytes = file.read(16).trimmed(); // We'll fix the xx85 problem below // startDate = QDateTime::fromString(QString::fromLatin1(bytes, 16), "dd.MM.yyHH.mm.ss"); // getStartDT ought to be named getStartNoDST ... TODO someday startDate = EDFInfo::getStartDT(QString::fromLatin1(bytes,16)); if (!file.seek(0xec)) { file.close(); return EDFduration(0, 0, filename); } bytes = file.read(8).trimmed(); num_records = bytes.toInt(&ok1); bytes = file.read(8).trimmed(); rec_duration = bytes.toDouble(&ok2); file.close(); } else { gzFile f = gzopen(filename.toLatin1(), "rb"); if (!f) return EDFduration(0, 0, filename); // Decompressed header and data block if (!gzseek(f, 0xa8, SEEK_SET)) { gzclose(f); return EDFduration(0, 0, filename); } char datebytes[17] = {0}; gzread(f, (char *)&datebytes, 16); QString str = QString(QString::fromLatin1(datebytes,16)).trimmed(); // startDate = QDateTime::fromString(str, "dd.MM.yyHH.mm.ss"); startDate = EDFInfo::getStartDT(str); if (!gzseek(f, 0xec-0xa8-16, SEEK_CUR)) { // 0xec gzclose(f); return EDFduration(0, 0, filename); } char cbytes[9] = {0}; gzread(f, (char *)&cbytes, 8); str = QString(cbytes).trimmed(); num_records = str.toInt(&ok1); gzread(f, (char *)&cbytes, 8); str = QString(cbytes).trimmed(); rec_duration = str.toDouble(&ok2); gzclose(f); } QDate d2 = startDate.date(); if (d2.year() < 2000) { d2.setDate(d2.year() + 100, d2.month(), d2.day()); startDate.setDate(d2); } if ( (! startDate.isValid()) || ( startDate > QDateTime::currentDateTime()) ) { qDebug() << "Invalid date time retreieved parsing EDF duration for" << filename; qDebug() << "Time zone(Utc) is" << startDate.timeZone().abbreviation(QDateTime::currentDateTimeUtc()); qDebug() << "Time zone is" << startDate.timeZone().abbreviation(QDateTime::currentDateTime()); return EDFduration(0, 0, filename); } if (!(ok1 && ok2)) return EDFduration(0, 0, filename); quint32 start = startDate.toTime_t(); quint32 end = start + rec_duration * num_records; QString filedate = filename.section("/",-1).section("_",0,1); // QDateTime dt2 = QDateTime::fromString(filedate, "yyyyMMdd_hhmmss"); d2 = QDate::fromString( filedate.left(8), "yyyyMMdd"); QTime t2 = QTime::fromString( filedate.right(6), "hhmmss"); QDateTime dt2 = QDateTime( d2, t2, EDFInfo::localNoDST ); quint32 st2 = dt2.toTime_t(); start = qMin(st2, start); // They should be the same, usually if (end < start) end = qMax(st2, start); EDFduration dur(start, end, filename); dur.type = lookupEDFType(filename); return dur; } void GuessPAPMode(Session *sess) { if (sess->channelDataExists(CPAP_Pressure)) { // Determine CPAP or APAP? EventDataType min = sess->Min(CPAP_Pressure); EventDataType max = sess->Max(CPAP_Pressure); if ((max-min)<0.1) { sess->settings[CPAP_Mode] = MODE_CPAP; sess->settings[CPAP_Pressure] = qRound(max * 10.0)/10.0; // early call.. It's CPAP mode } else { // Ramp is ugly - but this is a bad way to test for it if (sess->length() > 1800000L) { // half an hour } sess->settings[CPAP_Mode] = MODE_APAP; sess->settings[CPAP_PressureMin] = qRound(min * 10.0)/10.0; sess->settings[CPAP_PressureMax] = qRound(max * 10.0)/10.0; } } else if (sess->eventlist.contains(CPAP_IPAP)) { sess->settings[CPAP_Mode] = MODE_BILEVEL_AUTO_VARIABLE_PS; // Determine BiPAP or ASV } } void StoreSummaryStatistics(Session * sess, STRRecord & R) { if (R.mode >= 0) { if (R.mode == MODE_CPAP) { } else if (R.mode == MODE_APAP) { } } if (R.leak50 >= 0) { // sess->setp95(CPAP_Leak, R.leak95); // sess->setp50(CPAP_Leak, R.leak50); sess->setMax(CPAP_Leak, R.leakmax); } if (R.rr50 >= 0) { // sess->setp95(CPAP_RespRate, R.rr95); // sess->setp50(CPAP_RespRate, R.rr50); sess->setMax(CPAP_RespRate, R.rrmax); } if (R.mv50 >= 0) { // sess->setp95(CPAP_MinuteVent, R.mv95); // sess->setp50(CPAP_MinuteVent, R.mv50); sess->setMax(CPAP_MinuteVent, R.mvmax); } if (R.tv50 >= 0) { // sess->setp95(CPAP_TidalVolume, R.tv95); // sess->setp50(CPAP_TidalVolume, R.tv50); sess->setMax(CPAP_TidalVolume, R.tvmax); } if (R.mp50 >= 0) { // sess->setp95(CPAP_MaskPressure, R.mp95); // sess->seTTtp50(CPAP_MaskPressure, R.mp50); sess->setMax(CPAP_MaskPressure, R.mpmax); } if (R.oai > 0) { sess->setCph(CPAP_Obstructive, R.oai); sess->setCount(CPAP_Obstructive, R.oai * sess->hours()); } if (R.hi > 0) { sess->setCph(CPAP_Hypopnea, R.hi); sess->setCount(CPAP_Hypopnea, R.hi * sess->hours()); } if (R.cai > 0) { sess->setCph(CPAP_ClearAirway, R.cai); sess->setCount(CPAP_ClearAirway, R.cai * sess->hours()); } if (R.uai > 0) { sess->setCph(CPAP_Apnea, R.uai); sess->setCount(CPAP_Apnea, R.uai * sess->hours()); } if (R.csr > 0) { sess->setCph(CPAP_CSR, R.csr); sess->setCount(CPAP_CSR, R.csr * sess->hours()); } } void StoreSettings(Session * sess, STRRecord & R) { if (R.mode >= 0) { sess->settings[CPAP_Mode] = R.mode; sess->settings[RMS9_Mode] = R.rms9_mode; if ( R.min_pressure == 0 ) qDebug() << "Min Pressure is zero, R.mode is" << R.mode; if (R.mode == MODE_CPAP) { if (R.set_pressure >= 0) sess->settings[CPAP_Pressure] = R.set_pressure; } else if (R.mode == MODE_APAP) { if (R.min_pressure >= 0) sess->settings[CPAP_PressureMin] = R.min_pressure; if (R.max_pressure >= 0) sess->settings[CPAP_PressureMax] = R.max_pressure; } else if (R.mode == MODE_BILEVEL_FIXED) { if (R.epap >= 0) sess->settings[CPAP_EPAP] = R.epap; if (R.ipap >= 0) sess->settings[CPAP_IPAP] = R.ipap; if (R.ps >= 0) sess->settings[CPAP_PS] = R.ps; } else if (R.mode == MODE_BILEVEL_AUTO_FIXED_PS) { if (R.min_epap >= 0) sess->settings[CPAP_EPAPLo] = R.min_epap; if (R.max_ipap >= 0) sess->settings[CPAP_IPAPHi] = R.max_ipap; if (R.ps >= 0) sess->settings[CPAP_PS] = R.ps; } else if (R.mode == MODE_ASV) { if (R.epap >= 0) sess->settings[CPAP_EPAP] = R.epap; if (R.min_ps >= 0) sess->settings[CPAP_PSMin] = R.min_ps; if (R.max_ps >= 0) sess->settings[CPAP_PSMax] = R.max_ps; if (R.max_ipap >= 0) sess->settings[CPAP_IPAPHi] = R.max_ipap; } else if (R.mode == MODE_ASV_VARIABLE_EPAP) { if (R.max_epap >= 0) sess->settings[CPAP_EPAPHi] = R.max_epap; if (R.min_epap >= 0) sess->settings[CPAP_EPAPLo] = R.min_epap; if (R.max_ipap >= 0) sess->settings[CPAP_IPAPHi] = R.max_ipap; if (R.min_ipap >= 0) sess->settings[CPAP_IPAPLo] = R.min_ipap; if (R.min_ps >= 0) sess->settings[CPAP_PSMin] = R.min_ps; if (R.max_ps >= 0) sess->settings[CPAP_PSMax] = R.max_ps; } else { qDebug() << "Setting session pressures for R.mode" << R.mode; if (R.set_pressure > 0) sess->settings[CPAP_Pressure] = R.set_pressure; if (R.min_pressure > 0) sess->settings[CPAP_PressureMin] = R.min_pressure; if (R.max_pressure > 0) sess->settings[CPAP_PressureMax] = R.max_pressure; if (R.max_epap > 0) sess->settings[CPAP_EPAPHi] = R.max_epap; if (R.min_epap > 0) sess->settings[CPAP_EPAPLo] = R.min_epap; if (R.max_ipap > 0) sess->settings[CPAP_IPAPHi] = R.max_ipap; if (R.min_ipap > 0) sess->settings[CPAP_IPAPLo] = R.min_ipap; if (R.min_ps > 0) sess->settings[CPAP_PSMin] = R.min_ps; if (R.max_ps > 0) sess->settings[CPAP_PSMax] = R.max_ps; if (R.ps > 0) sess->settings[CPAP_PS] = R.ps; if (R.epap > 0) sess->settings[CPAP_EPAP] = R.epap; if (R.ipap > 0) sess->settings[CPAP_IPAP] = R.ipap; } } if (R.epr >= 0) { sess->settings[RMS9_EPR] = (int)R.epr; if (R.epr > 0) { if (R.epr_level >= 0) { sess->settings[RMS9_EPRLevel] = (int)R.epr_level; } } } if (R.s_RampEnable >= 0) { sess->settings[RMS9_RampEnable] = R.s_RampEnable; if (R.s_RampEnable >= 1) { if (R.s_RampTime >= 0) { sess->settings[CPAP_RampTime] = R.s_RampTime; } if (R.ramp_pressure >= 0) { sess->settings[CPAP_RampPressure] = R.ramp_pressure; } } } if (R.s_SmartStart >= 0) { sess->settings[RMS9_SmartStart] = R.s_SmartStart; } if (R.s_SmartStop >= 0) { sess->settings[RMAS11_SmartStop] = R.s_SmartStop; } if (R.s_ABFilter >= 0) { sess->settings[RMS9_ABFilter] = R.s_ABFilter; } if (R.s_ClimateControl >= 0) { sess->settings[RMS9_ClimateControl] = R.s_ClimateControl; } if (R.s_Mask >= 0) { sess->settings[RMS9_Mask] = R.s_Mask; } if (R.s_PtAccess >= 0) { sess->settings[RMS9_PtAccess] = R.s_PtAccess; } if (R.s_PtView >= 0) { sess->settings[RMAS11_PtView] = R.s_PtView; } if (R.s_HumEnable >= 0) { sess->settings[RMS9_HumidStatus] = (short)R.s_HumEnable; if ((R.s_HumEnable >= 1) && (R.s_HumLevel >= 0)) { sess->settings[RMS9_HumidLevel] = (short)R.s_HumLevel; } } if (R.s_TempEnable >= 0) { sess->settings[RMS9_TempEnable] = (short)R.s_TempEnable; if ((R.s_TempEnable >= 1) && (R.s_Temp >= 0)){ sess->settings[RMS9_Temp] = (short)R.s_Temp; } } if (R.s_Comfort >= 0) { sess->settings[RMAS1x_Comfort] = R.s_Comfort; } } struct OverlappingEDF { quint32 start; quint32 end; QMultiMap filemap; // key is start time, value is filename Session * sess; }; void ResDayTask::run() { #ifdef SESSION_DEBUG qDebug() << "Processing STR and edf files for" << resday->date; #endif if (resday->files.size() == 0) { // No EDF files??? if (( ! resday->str.date.isValid()) || (resday->str.date > QDate::currentDate()) ) { // This condition should be impossible, but just in case something gets fudged up elsewhere later qDebug() << "No edf files in resday" << resday->date << "and the str date is inValid"; return; } // Summary only day, create sessions for each mask-on/off pair and tag them summary only STRRecord & R = resday->str; #ifdef SESSION_DEBUG qDebug() << "Creating summary-only sessions for" << resday->date; #endif for (int i=0;istr.maskon.size();++i) { quint32 maskon = resday->str.maskon[i]; quint32 maskoff = resday->str.maskoff[i]; /** QTime noon(12,00,00); QDateTime daybegin(resday->date,noon); // Beginning of ResMed day quint32 dayend = daybegin.addDays(1).addMSecs(-1).toTime_t(); // End of ResMed day if ( (maskon > dayend) || (maskoff > dayend) ) { qWarning() << "mask time in future" << resday->date << daybegin << dayend << "maskon" << maskon << "maskoff" << maskoff; continue; } **/ if (((maskon>0) && (maskoff>0)) && (maskon != maskoff)) { //ignore very short sessions Session * sess = new Session(mach, maskon); sess->set_first(quint64(maskon) * 1000L); sess->set_last(quint64(maskoff) * 1000L); StoreSettings(sess, R); // Process the STR.edf settings StoreSummaryStatistics(sess, R); // We want the summary information too sess->setSummaryOnly(true); sess->SetChanged(true); // loader->sessionMutex.lock(); // This chunk moved into SaveSession below // sess->Store(mach->getDataPath()); // mach->AddSession(sess); // loader->sessionCount++; // loader->sessionMutex.unlock(); //// delete sess; save(loader, sess); // This is aliased to SaveSession - unless testing } } // qDebug() << "Finished summary processing for" << resday->date; return; } // sooo... at this point we have // resday record populated with correct STR.edf settings for this date // files list containing unsorted EDF files that match this day // guaranteed no sessions for this day for this machine. // Need to check overlapping files in session candidates QList overlaps; int maskOnSize = resday->str.maskon.size(); if (resday->str.date.isValid()) { //First populate Overlaps with Mask ON/OFF events for (int i=0; i < maskOnSize; ++i) { // if ( (resday->str.maskon[i] > QDateTime::currentDateTime().toTime_t()) || // (resday->str.maskoff[i] > QDateTime::currentDateTime().toTime_t()) ) { // qWarning() << "mask time in future" << resday->date << "now" << QDateTime::currentDateTime().toTime_t() << "maskon" << resday->str.maskon[i] << "maskoff" << resday->str.maskoff[i]; // continue; // } /* QTime noon(12,00,00); QDateTime daybegin(resday->date,noon); // Beginning of ResMed day quint32 dayend = daybegin.addDays(1).addMSecs(-1).toTime_t(); // End of ResMed day if ( (resday->str.maskon[i] > dayend) || (resday->str.maskoff[i] > dayend) ) { qWarning() << "mask time in future" << resday->date << "daybegin:" << daybegin << "dayend:" << dayend << "maskon" << resday->str.maskon[i] << "maskoff" << resday->str.maskoff[i]; continue; } */ if (((resday->str.maskon[i]>0) || (resday->str.maskoff[i]>0)) && (resday->str.maskon[i] != resday->str.maskoff[i]) ) { OverlappingEDF ov; ov.start = resday->str.maskon[i]; ov.end = resday->str.maskoff[i]; ov.sess = nullptr; overlaps.append(ov); } } } #ifdef STR_DEBUG if (overlaps.size() > 0) qDebug().noquote() << "Created" << overlaps.size() << "sessionGroups from STR record for" << resday->str.date.toString(); #endif QMap EVElist, CSLlist; for (auto f_itr=resday->files.begin(), fend=resday->files.end(); f_itr!=fend; ++f_itr) { const QString & filename = f_itr.key(); const QString & fullpath = f_itr.value(); // QString ext = filename.section("_", -1).section(".",0,0).toUpper(); EDFType type = lookupEDFType(filename); QString datestr = filename.section("_", 0, 1); // QDateTime filetime = QDateTime().fromString(datestr,"yyyyMMdd_HHmmss"); QDate d2 = QDate::fromString( datestr.left(8), "yyyyMMdd"); QTime t2 = QTime::fromString( datestr.right(6), "hhmmss"); QDateTime filetime = QDateTime( d2, t2, EDFInfo::localNoDST ); quint32 filetime_t = filetime.toTime_t(); if (type == EDF_EVE) { // skip the EVE and CSL files, b/c they often cover all sessions EVElist[filetime_t] = filename; continue; } else if (type == EDF_CSL) { CSLlist[filetime_t] = filename; continue; } bool added = false; for (auto & ovr : overlaps) { if ((filetime_t >= (ovr.start)) && (filetime_t < ovr.end)) { ovr.filemap.insert(filetime_t, filename); added = true; break; } } if ( ! added) { // Didn't get a hit, look at the EDF files duration and check for an overlap EDFduration dur = getEDFDuration(fullpath); /** QTime noon(12,00,00); QDateTime daybegin(resday->date,noon); // Beginning of ResMed day quint32 dayend = daybegin.addDays(1).addMSecs(-1).toTime_t(); // End of ResMed day if ((dur.start > (dayend)) || (dur.end > (dayend)) ) { qWarning() << "Future Date in" << fullpath << "dayend" << dayend << "dur.start" << dur.start << "dur.end" << dur.end; continue; // skip this file } **/ for (int i=overlaps.size()-1; i>=0; --i) { OverlappingEDF & ovr = overlaps[i]; if ((ovr.start < dur.end) && (dur.start < ovr.end)) { ovr.filemap.insert(filetime_t, filename); added = true; #ifdef SESSION_DEBUG qDebug() << "Adding" << filename << "to overlap" << i; qDebug() << "Overlap starts:" << ovr.start << "ends:" << ovr.end; qDebug() << "File time starts:" << dur.start << "ends:" << dur.end; #endif // Expand ovr's scope -- I think this is necessary!! (PO) // YES! when the STR file is missing, there are no mask on/off entries // and the edf files are not always created at the same time ovr.start = min(ovr.start, dur.start); ovr.end = max(ovr.end, dur.end); // if ( (dur.start < ovr.start) || (dur.end > ovr.end) ) // qDebug() << "Should have expanded overlap" << i << "for" << filename; break; } } // end for walk existing overlap entries if ( ! added ) { if (dur.start != dur.end) { // Didn't fit it in anywhere, create a new Overlap entry/session OverlappingEDF ov; ov.start = dur.start; ov.end = dur.end; ov.filemap.insert(filetime_t, filename); #ifdef SESSION_DEBUG qDebug() << "Creating overlap for" << filename << "missing STR record"; qDebug() << "Starts:" << dur.start << "Ends:" << dur.end; #endif overlaps.append(ov); } else { #ifdef SESSION_DEBUG qDebug() << "Skipping zero duration file" << filename; #endif } } // end create a new overlap entry } // end check for file overlap } // end for walk resday files list // Create an ordered map and see how far apart the sessions really are. QMap mapov; for (auto & ovr : overlaps) { mapov[ovr.start] = ovr; } // We are not going to merge close sessions - gaps can be useful markers for users // // Examine the gaps in between to see if we should merge sessions // for (auto oit=mapov.begin(), oend=mapov.end(); oit != oend; ++oit) { // // Get next in line // auto next_oit = oit+1; // if (next_oit != mapov.end()) { // OverlappingEDF & A = oit.value(); // OverlappingEDF & B = next_oit.value(); // int gap = B.start - A.end; // if (gap < 60) { // TODO see if we should use the prefs value here... ??? // // qDebug() << "Only a" << gap << "s sgap between ResMed sessions on" << resday->date.toString(); // } // } // } if (overlaps.size()==0) { qDebug() << "No sessionGroups for" << resday->date << "FINSIHED"; return; } // Now overlaps is populated with zero or more individual session groups of EDF files (zero because of sucky summary only days) for (auto & ovr : overlaps) { if (ovr.filemap.size() == 0) continue; Session * sess = new Session(mach, ovr.start); // Do not set the session times according to Mask on/off times // The LoadXXX edf routines will update them with recording start and durations // sess->set_first(quint64(ovr.start)*1000L); // sess->set_last(quint64(ovr.end)*1000L); ovr.sess = sess; for (auto mit=ovr.filemap.begin(), mend=ovr.filemap.end(); mit != mend; ++mit) { const QString & filename = mit.value(); const QString & fullpath = resday->files[filename]; EDFType type = lookupEDFType(filename); #ifdef SESSION_DEBUG sess->session_files.append(filename); #endif switch (type) { case EDF_BRP: loader->LoadBRP(sess, fullpath); break; case EDF_PLD: loader->LoadPLD(sess, fullpath); break; case EDF_SAD: loader->LoadSAD(sess, fullpath); break; case EDF_EVE: case EDF_CSL: case EDF_AEV: // this is in the 36039 - must figure out what to do with it break; default: qWarning() << "Unrecognized file type for" << filename; } } // end for each edf file in the sessionGroup // Turns out there is only one or sometimes two EVE's per day, and they store data for the whole day // So we have to extract Annotations data and apply it for all sessions for (auto eit=EVElist.begin(), eveend=EVElist.end(); eit != eveend; ++eit) { const QString & fullpath = resday->files[eit.value()]; loader->LoadEVE(ovr.sess, fullpath); } for (auto eit=CSLlist.begin(), cslend=CSLlist.end(); eit != cslend; ++eit) { const QString & fullpath = resday->files[eit.value()]; loader->LoadCSL(ovr.sess, fullpath); } if (EVElist.size() == 0) { sess->AddEventList(CPAP_Obstructive, EVL_Event); sess->AddEventList(CPAP_ClearAirway, EVL_Event); sess->AddEventList(CPAP_Apnea, EVL_Event); sess->AddEventList(CPAP_Hypopnea, EVL_Event); } sess->setSummaryOnly(false); sess->SetChanged(true); if (sess->length() == 0) { // we want empty sessions even though they are crap qDebug() << "Session" << sess->session() << "["+QDateTime::fromTime_t(sess->session()).toString("MMM dd, yyyy hh:mm:ss")+"]" << "has zero duration" << QString("Start: %1").arg(sess->realFirst(),0,16) << QString("End: %1").arg(sess->realLast(),0,16); } if (sess->length() < 0) { // we want empty sessions even though they are crap qDebug() << "Session" << sess->session() << "["+QDateTime::fromTime_t(sess->session()).toString("MMM dd, yyyy hh:mm:ss")+"]" << "has negative duration"; qDebug() << QString("Start: %1").arg(sess->realFirst(),0,16) << QString("End: %1").arg(sess->realLast(),0,16); } if (resday->str.date.isValid()) { STRRecord & R = resday->str; // Claim this session R.sessionid = sess->session(); // Save maskon time in session setting so we can use it later to avoid doubleups. //sess->settings[RMS9_MaskOnTime] = R.maskon; #ifdef SESSION_DEBUG sess->session_files.append("STR.edf"); #endif StoreSettings(sess, R); } else { // No corresponding STR.edf record, but we have EDF files #ifdef STR_DEBUG qDebug() << "EDF files without STR record" << resday->date.toString(); #endif bool foundprev = false; loader->sessionMutex.lock(); auto it=p_profile->daylist.find(resday->date); // should exist already to be here auto begin = p_profile->daylist.begin(); while (it!=begin) { --it; Day * day = it.value(); bool hasmachine = day && day->hasMachine(mach); if ( ! hasmachine) continue; QList sessions = day->getSessions(MT_CPAP); if (sessions.size() > 0) { Session *chksess = sessions[0]; sess->settings = chksess->settings; foundprev = true; break; } } loader->sessionMutex.unlock(); sess->setNoSettings(true); if (!foundprev) { // We have no Summary or Settings data... we need to do something to indicate this, and detect the mode if (sess->channelDataExists(CPAP_Pressure)) { qDebug() << "Guessing the PAP mode..."; GuessPAPMode(sess); } } } // end else no STR record for these edf files sess->UpdateSummaries(); #ifdef SESSION_DEBUG qDebug() << "Adding session" << sess->session() << "["+QDateTime::fromTime_t(sess->session()).toString("MMM dd, yyyy hh:mm:ss")+"]"; #endif // Save is not threadsafe? (meh... it seems to be) // loader->saveMutex.lock(); // loader->saveMutex.unlock(); // if ( (QDateTime::fromTime_t(sess->session()) > QDateTime::currentDateTime()) || if ( (sess->realFirst() == 0) || (sess->realLast() == 0) ) qWarning().noquote() << "Skipping future or absent date session:" << sess->session() << "["+QDateTime::fromTime_t(sess->session()).toString("MMM dd, yyyy hh:mm:ss")+"]" << "\noriginal date is" << resday->date.toString() << "session realFirst" << sess->realFirst() << "realLast" << sess->realLast(); else save(loader, sess); // Free the memory used by this session sess->TrashEvents(); // delete sess; } // end for-loop walking the overlaps (file groups per session } void ResmedLoader::SaveSession(ResmedLoader* loader, Session* sess) { Machine* mach = sess->machine(); loader->sessionMutex.lock(); // AddSession definitely ain't threadsafe. if ( ! sess->Store(mach->getDataPath()) ) { qWarning() << "Failed to store session" << sess->session(); } if ( ! mach->AddSession(sess) ) { qWarning() << "Session" << sess->session() << "was not addded"; } loader->sessionCount++; loader->sessionMutex.unlock(); } bool matchSignal(ChannelID ch, const QString & name); // forward bool ResmedLoader::LoadCSL(Session *sess, const QString & path) { #ifdef DEBUG_EFFICIENCY QTime time; time.start(); #endif QString filename = path.section(-2, -1); ResMedEDFInfo edf; if ( ! edf.Open(path) ) { qDebug() << "LoadCSL failed to open" << filename; return false; } #ifdef DEBUG_EFFICIENCY int edfopentime = time.elapsed(); time.start(); #endif if (!edf.Parse()) { qDebug() << "LoadCSL failed to parse" << filename; return false; } #ifdef DEBUG_EFFICIENCY int edfparsetime = time.elapsed(); time.start(); #endif EventList *CSR = nullptr; // Allow for empty sessions.. qint64 csr_starts = 0; // Process event annotation records // qDebug() << "File has " << edf.annotations.size() << "annotation vectors"; // int vec = 1; for (auto annoVec = edf.annotations.begin(); annoVec != edf.annotations.end(); annoVec++ ) { // qDebug() << "Vector " << vec++ << " has " << annoVec->size() << " annotations"; for (auto anno = annoVec->begin(); anno != annoVec->end(); anno++ ) { // qDebug() << "Offset: " << anno->offset << " Duration: " << anno->duration << " Text: " << anno->text; qint64 tt = edf.startdate + qint64(anno->offset*1000L); if ( ! anno->text.isEmpty()) { if (anno->text == "CSR Start") { csr_starts = tt; } else if (anno->text == "CSR End") { if ( ! CSR) { CSR = sess->AddEventList(CPAP_CSR, EVL_Event); } if (csr_starts > 0) { if (sess->checkInside(csr_starts)) { CSR->AddEvent(tt, double(tt - csr_starts) / 1000.0); } csr_starts = 0; } else { qWarning() << "Split csr event flag in " << edf.filename; } } else if (anno->text != "Recording starts") { qWarning() << "Unobserved ResMed CSL annotation field: " << anno->text; } } } } if (csr_starts > 0) { qDebug() << "Unfinished csr event in " << edf.filename; } #ifdef DEBUG_EFFICIENCY timeMutex.lock(); timeInLoadCSL += time.elapsed(); timeInEDFOpen += edfopentime; timeInEDFInfo += edfparsetime; timeMutex.unlock(); #endif return true; } bool ResmedLoader::LoadEVE(Session *sess, const QString & path) { #ifdef DEBUG_EFFICIENCY QTime time; time.start(); #endif QString filename = path.section(-2, -1); ResMedEDFInfo edf; if ( ! edf.Open(path) ) { qDebug() << "LoadEVE failed to open" << filename; return false; } #ifdef DEBUG_EFFICIENCY int edfopentime = time.elapsed(); time.start(); #endif if (!edf.Parse()) { qDebug() << "LoadEVE failed to parse" << filename; return false; } #ifdef DEBUG_EFFICIENCY int edfparsetime = time.elapsed(); time.start(); #endif // Notes: Event records have useless duration record. // Do not update session start / end times because they are needed to determine if events belong in this session or not... EventList *OA = nullptr, *HY = nullptr, *CA = nullptr, *UA = nullptr, *RE = nullptr; // Allow for empty sessions.. // Create some EventLists OA = sess->AddEventList(CPAP_Obstructive, EVL_Event); HY = sess->AddEventList(CPAP_Hypopnea, EVL_Event); UA = sess->AddEventList(CPAP_Apnea, EVL_Event); // Process event annotation records // qDebug() << "File has " << edf.annotations.size() << "annotation vectors"; // int vec = 1; for (auto annoVec = edf.annotations.begin(); annoVec != edf.annotations.end(); annoVec++ ) { // qDebug() << "Vector " << vec++ << " has " << annoVec->size() << " annotations"; for (auto anno = annoVec->begin(); anno != annoVec->end(); anno++ ) { qint64 tt = edf.startdate + qint64(anno->offset*1000L); // qDebug() << "Offset: " << anno->offset << " Duration: " << anno->duration << " Text: " << anno->text; // qDebug() << "Time: " << (tt/1000L). << " Duration: " << anno->duration << " Text: " << anno->text; if ( ! anno->text.isEmpty()) { if (matchSignal(CPAP_Obstructive, anno->text)) { if (sess->checkInside(tt)) OA->AddEvent(tt, anno->duration); } else if (matchSignal(CPAP_Hypopnea, anno->text)) { if (sess->checkInside(tt)) HY->AddEvent(tt, anno->duration); // Hyponeas may not have any duration! } else if (matchSignal(CPAP_Apnea, anno->text)) { if (sess->checkInside(tt)) UA->AddEvent(tt, anno->duration); } else if (matchSignal(CPAP_RERA, anno->text)) { // Not all machines have it, so only create it when necessary.. if ( ! RE) RE = sess->AddEventList(CPAP_RERA, EVL_Event); if (sess->checkInside(tt)) RE->AddEvent(tt, anno->duration); } else if (matchSignal(CPAP_ClearAirway, anno->text)) { // Not all machines have it, so only create it when necessary.. if ( ! CA) CA = sess->AddEventList(CPAP_ClearAirway, EVL_Event); if (sess->checkInside(tt)) CA->AddEvent(tt, anno->duration); } else if (anno->text == "SpO2 Desaturation") { // Used in 28509 continue; // ignored for now } else { if (anno->text != "Recording starts") { qDebug() << "Unobserved ResMed annotation field: " << anno->text; } } } } } #ifdef DEBUG_EFFICIENCY timeMutex.lock(); timeInLoadEVE += time.elapsed(); timeInEDFOpen += edfopentime; timeInEDFInfo += edfparsetime; timeMutex.unlock(); #endif return true; } bool ResmedLoader::LoadBRP(Session *sess, const QString & path) { #ifdef DEBUG_EFFICIENCY QTime time; time.start(); #endif QString filename = path.section(-2, -1); ResMedEDFInfo edf; if ( ! edf.Open(path) ) { qDebug() << "LoadBRP failed to open" << filename.section("/", -2, -1); return false; } #ifdef DEBUG_EFFICIENCY int edfopentime = time.elapsed(); time.start(); #endif if (!edf.Parse()) { #ifdef EDF_DEBUG qDebug() << "LoadBRP failed to parse" << filename.section("/", -2, -1); #endif return false; } #ifdef DEBUG_EFFICIENCY int edfparsetime = time.elapsed(); time.start(); int AddWavetime = 0; QTime time2; #endif sess->updateFirst(edf.startdate); qint64 duration = edf.GetNumDataRecords() * edf.GetDurationMillis(); sess->updateLast(edf.startdate + duration); for (auto & es : edf.edfsignals) { long recs = es.sampleCnt * edf.GetNumDataRecords(); if (recs < 0) continue; ChannelID code; if (matchSignal(CPAP_FlowRate, es.label)) { code = CPAP_FlowRate; es.gain *= 60.0; es.physical_minimum *= 60.0; es.physical_maximum *= 60.0; es.physical_dimension = "L/M"; } else if (matchSignal(CPAP_MaskPressureHi, es.label)) { code = CPAP_MaskPressureHi; } else if (matchSignal(CPAP_RespEvent, es.label)) { code = CPAP_RespEvent; // } else if (es.label == "TrigCycEvt.40ms") { // we need a real code for this signal // code = CPAP_TriggerEvent; // Well, it got folded into RespEvent // continue; } else if (es.label != "Crc16") { qDebug() << "Unobserved ResMed BRP Signal " << es.label; continue; } else continue; if (code) { double rate = double(duration) / double(recs); EventList *a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate); a->setDimension(es.physical_dimension); #ifdef DEBUG_EFFICIENCY time2.start(); #endif a->AddWaveform(edf.startdate, es.dataArray, recs, duration); #ifdef DEBUG_EFFICIENCY AddWavetime+= time2.elapsed(); #endif EventDataType min = a->Min(); EventDataType max = a->Max(); // Cap to physical dimensions, because there can be ram glitches/whatever that throw really big outliers. if (min < es.physical_minimum) min = es.physical_minimum; if (max > es.physical_maximum) max = es.physical_maximum; sess->updateMin(code, min); sess->updateMax(code, max); sess->setPhysMin(code, es.physical_minimum); sess->setPhysMax(code, es.physical_maximum); } } #ifdef DEBUG_EFFICIENCY timeMutex.lock(); timeInLoadBRP += time.elapsed(); timeInEDFOpen += edfopentime; timeInEDFInfo += edfparsetime; timeInAddWaveform += AddWavetime; timeMutex.unlock(); #endif return true; } // Load SAD Oximetry Signals bool ResmedLoader::LoadSAD(Session *sess, const QString & path) { #ifdef DEBUG_EFFICIENCY QTime time; time.start(); #endif QString filename = path.section(-2, -1); ResMedEDFInfo edf; if ( ! edf.Open(path) ) { qDebug() << "LoadSAD failed to open" << filename.section("/", -2, -1); return false; } #ifdef DEBUG_EFFICIENCY int edfopentime = time.elapsed(); time.start(); #endif if (!edf.Parse()) { #ifdef EDF_DEBUG qDebug() << "LoadSAD failed to parse" << filename.section("/", -2, -1); #endif return false; } #ifdef DEBUG_EFFICIENCY int edfparsetime = time.elapsed(); time.start(); #endif sess->updateFirst(edf.startdate); qint64 duration = edf.GetNumDataRecords() * edf.GetDurationMillis(); sess->updateLast(edf.startdate + duration); for (auto & es : edf.edfsignals) { //qDebug() << "SAD:" << es.label << es.digital_maximum << es.digital_minimum << es.physical_maximum << es.physical_minimum; long recs = es.sampleCnt * edf.GetNumDataRecords(); ChannelID code; bool hasdata = false; for (int i = 0; i < recs; ++i) { if (es.dataArray[i] != -1) { hasdata = true; break; } } if (!hasdata) continue; if (matchSignal(OXI_Pulse, es.label)) { code = OXI_Pulse; ToTimeDelta(sess, edf, es, code, recs, duration); sess->setPhysMax(code, 180); sess->setPhysMin(code, 18); } else if (matchSignal(OXI_SPO2, es.label)) { code = OXI_SPO2; es.physical_minimum = 60; ToTimeDelta(sess, edf, es, code, recs, duration); sess->setPhysMax(code, 100); sess->setPhysMin(code, 60); } else if (es.label != "Crc16") { qDebug() << "Unobserved ResMed SAD Signal " << es.label; } } #ifdef DEBUG_EFFICIENCY timeMutex.lock(); timeInLoadSAD += time.elapsed(); timeInEDFOpen += edfopentime; timeInEDFInfo += edfparsetime; timeMutex.unlock(); #endif return true; } bool ResmedLoader::LoadPLD(Session *sess, const QString & path) { #ifdef DEBUG_EFFICIENCY QTime time; time.start(); #endif QString filename = path.section(-2, -1); // qDebug() << "LoadPLD opening" << filename.section("/", -2, -1); ResMedEDFInfo edf; if ( ! edf.Open(path) ) { qDebug() << "LoadPLD failed to open" << filename.section("/", -2, -1); return false; } #ifdef DEBUG_EFFICIENCY int edfopentime = time.elapsed(); time.start(); #endif if (!edf.Parse()) { #ifdef EDF_DEBUG qDebug() << "LoadPLD failed to parse" << filename.section("/", -2, -1); #endif return false; } #ifdef DEBUG_EFFICIENCY int edfparsetime = time.elapsed(); time.start(); #endif // Is it safe to assume the order does not change here? enum PLDType { MaskPres = 0, TherapyPres, ExpPress, Leak, RR, Vt, Mv, SnoreIndex, FFLIndex, U1, U2 }; qint64 duration = edf.GetNumDataRecords() * edf.GetDurationMillis(); sess->updateFirst(edf.startdate); sess->updateLast(edf.startdate + duration); QString t; int emptycnt = 0; EventList *a = nullptr; // double rate; long samples; ChannelID code; bool square = AppSetting->squareWavePlots(); // The following is a hack to skip the multiple uses of Ti and Te by Resmed for signal labels // It should be replaced when code in resmed_info class changes the labels to be unique bool found_Ti_code = false; bool found_Te_code = false; QDateTime sessionStartDT = QDateTime:: fromMSecsSinceEpoch(sess->first()); // bool forceDebug = (sessionStartDT > QDateTime::fromString("2021-02-26 12:00:00", "yyyy-MM-dd HH:mm:ss")) && // (sessionStartDT < QDateTime::fromString("2021-02-27 12:00:00", "yyyy-MM-dd HH:mm:ss")); bool forceDebug = false; for (auto & es : edf.edfsignals) { a = nullptr; samples = es.sampleCnt * edf.GetNumDataRecords(); if (samples <= 0) continue; // rate = double(duration) / double(samples); //qDebug() << "EVE:" << es.digital_maximum << es.digital_minimum << es.physical_maximum << es.physical_minimum << es.gain; if (forceDebug) { qDebug() << "Session" << sessionStartDT.toString() << filename.section("/", -2, -1) << "signal" << es.label; qDebug() << "\tSecond/rec:" << edf.GetDurationMillis()/1000 << "Samples/rec:" << es.sampleCnt; } if (matchSignal(CPAP_Snore, es.label)) { code = CPAP_Snore; ToTimeDelta(sess, edf, es, code, samples, duration, 0, 0, square); } else if (matchSignal(CPAP_Pressure, es.label)) { code = CPAP_Pressure; // es.physical_maximum = 25; // es.physical_minimum = 4; ToTimeDelta(sess, edf, es, code, samples, duration, 0, 0, square); } else if (matchSignal(CPAP_IPAP, es.label)) { code = CPAP_IPAP; // es.physical_maximum = 25; // es.physical_minimum = 4; ToTimeDelta(sess, edf, es, code, samples, duration, 0, 0, square); } else if (matchSignal(CPAP_EPAP, es.label)) { // Expiratory Pressure code = CPAP_EPAP; // es.physical_maximum = 25; // es.physical_minimum = 4; ToTimeDelta(sess, edf, es, code, samples, duration, 0, 0, square); } else if (matchSignal(CPAP_MinuteVent,es.label)) { code = CPAP_MinuteVent; ToTimeDelta(sess, edf, es, code, samples, duration, 0, 0, square); } else if (matchSignal(CPAP_RespRate, es.label)) { code = CPAP_RespRate; // a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate); // a->AddWaveform(edf.startdate, es.dataArray, samples, duration); ToTimeDelta(sess, edf, es, code, samples, duration, 0, 0, square); } else if (matchSignal(CPAP_TidalVolume, es.label)) { code = CPAP_TidalVolume; es.physical_dimension = "mL"; es.gain *= 1000.0; es.physical_maximum *= 1000.0; es.physical_minimum *= 1000.0; // es.digital_maximum*=1000.0; // es.digital_minimum*=1000.0; ToTimeDelta(sess, edf, es, code, samples, duration, 0, 0, square); } else if (matchSignal(CPAP_Leak, es.label)) { code = CPAP_Leak; es.gain *= 60.0; es.physical_maximum *= 60.0; es.physical_minimum *= 60.0; // es.digital_maximum*=60.0; // es.digital_minimum*=60.0; es.physical_dimension = "L/M"; ToTimeDelta(sess, edf, es, code, samples, duration, 0, 0, true); sess->setPhysMax(code, 120.0); sess->setPhysMin(code, 0); } else if (matchSignal(CPAP_FLG, es.label)) { code = CPAP_FLG; ToTimeDelta(sess, edf, es, code, samples, duration, 0, 0, square); } else if (matchSignal(CPAP_MaskPressure, es.label)) { code = CPAP_MaskPressure; // es.physical_maximum = 25; // es.physical_minimum = 4; ToTimeDelta(sess, edf, es, code, samples, duration, 0, 0, square); } else if (matchSignal(CPAP_IE, es.label)) { //I:E ratio code = CPAP_IE; // es.gain /= 100.0; // es.physical_maximum /= 100.0; // es.physical_minimum /= 100.0; // qDebug() << "IE Gain, Max, Min" << es.gain << es.physical_maximum << es.physical_minimum; // qDebug() << "IE count, data..." << es.sampleCnt << es.dataArray[0] << es.dataArray[1] << es.dataArray[2] << es.dataArray[3] << es.dataArray[4]; // a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate); // a->AddWaveform(edf.startdate, es.dataArray, samples, duration); ToTimeDelta(sess,edf,es, code,samples,duration,0,0, square); } else if (matchSignal(CPAP_Ti, es.label)) { code = CPAP_Ti; // There are TWO of these with the same label on 36037, 36039, 36377 and others // Also 37051 has R5Ti.2s and Ti.2s. We use R5Ti.2s and ignore the Ti.2s if ( found_Ti_code ) continue; found_Ti_code = true; // a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate); // a->AddWaveform(edf.startdate, es.dataArray, samples, duration); ToTimeDelta(sess,edf,es, code,samples,duration,0,0, square); } else if (matchSignal(CPAP_Te, es.label)) { code = CPAP_Te; // There are TWO of these with the same label on my VPAP Adapt 36037 if ( found_Te_code ) continue; found_Te_code = true; // a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate); // a->AddWaveform(edf.startdate, es.dataArray, samples, duration); ToTimeDelta(sess,edf,es, code,samples,duration,0,0, square); } else if (matchSignal(CPAP_TgMV, es.label)) { code = CPAP_TgMV; // a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate); // a->AddWaveform(edf.startdate, es.dataArray, samples, duration); ToTimeDelta(sess,edf,es, code,samples,duration,0,0, square); } else if (es.label == "Va") { // Signal used in 36039... What to do with it??? a = nullptr; // We'll skip it for now } else if (es.label == "AlvMinVent.2s") { // Signal used in 28509... What to do with it??? a = nullptr; // We'll skip it for now } else if (es.label == "CLRatio.2s") { // Signal used in 28509... What to do with it??? a = nullptr; // We'll skip it for now } else if (es.label == "TRRatio.2s") { // Signal used in 28509... What to do with it??? a = nullptr; // We'll skip it for now } else if (es.label == "") { // What the hell resmed?? // these empty lables should be changed in resmed_EDFInfo to something unique if (emptycnt == 0) { code = RMS9_E01; // ToTimeDelta(sess, edf, es, code, samples, duration, 0, 0, square); } else if (emptycnt == 1) { code = RMS9_E02; // ToTimeDelta(sess, edf, es, code, samples, duration, 0, 0, square); } else { qDebug() << "Unobserved Empty Signal " << es.label; } emptycnt++; } else if (es.label != "Crc16") { qDebug() << "Unobserved ResMed PLD Signal " << es.label; a = nullptr; } if (a) { sess->updateMin(code, a->Min()); sess->updateMax(code, a->Max()); sess->setPhysMin(code, es.physical_minimum); sess->setPhysMax(code, es.physical_maximum); a->setDimension(es.physical_dimension); } } #ifdef DEBUG_EFFICIENCY timeMutex.lock(); timeInLoadPLD += time.elapsed(); timeInEDFOpen += edfopentime; timeInEDFInfo += edfparsetime; timeMutex.unlock(); #endif return true; } // Convert EDFSignal data to OSCAR's Time-Delta Event format EventList * buildEventList( EventStoreType est, EventDataType t_min, EventDataType t_max, EDFSignal &es, EventDataType *min, EventDataType *max, double tt, EventList *el, Session * sess, ChannelID code ); // forward void ResmedLoader::ToTimeDelta(Session *sess, ResMedEDFInfo &edf, EDFSignal &es, ChannelID code, long samples, qint64 duration, EventDataType t_min, EventDataType t_max, bool square) { using namespace schema; ChannelList channel; // QDateTime sessionStartDT = QDateTime:: fromMSecsSinceEpoch(sess->first()); // bool forceDebug = (sessionStartDT > QDateTime::fromString("2021-02-26 12:00:00", "yyyy-MM-dd HH:mm:ss")) && // (sessionStartDT < QDateTime::fromString("2021-02-27 12:00:00", "yyyy-MM-dd HH:mm:ss")); bool forceDebug = false; if (t_min == t_max) { t_min = es.physical_minimum; t_max = es.physical_maximum; } #ifdef DEBUG_EFFICIENCY QElapsedTimer time; time.start(); #endif double rate = (duration / samples); // milliseconds per record double tt = edf.startdate; EventStoreType c=0, last; int startpos = 0; // There's no reason to skip the first 40 seconds of slow data // Reduce that to 10 seconds, to allow presssures to stabilise if ((code == CPAP_Pressure) || (code == CPAP_IPAP) || (code == CPAP_EPAP)) { startpos = 5; // Shave the first 10 seconds of pressure data tt += rate * startpos; } // Likewise for the values that the machine computes for us, but 20 seconds if ((code == CPAP_MinuteVent) || (code == CPAP_RespRate) || (code == CPAP_TidalVolume)) { startpos = 10; // Shave the first 20 seconds of computed data tt += rate * startpos; } qint16 *sptr = es.dataArray; qint16 *eptr = sptr + samples; sptr += startpos; EventDataType min = t_max, max = t_min, tmp; EventList *el = nullptr; if (forceDebug) qDebug() << "Code:" << QString::number(code, 16) << "Samples:" << samples; if (samples > startpos + 1) { // Prime last with a good starting value do { last = *sptr++; tmp = EventDataType(last) * es.gain; if ((tmp >= t_min) && (tmp <= t_max)) { min = tmp; max = tmp; el = sess->AddEventList(code, EVL_Event, es.gain, es.offset, 0, 0); if (forceDebug) // qDebug() << "New EventList:" << channel.channels[code]->code() << QDateTime::fromMSecsSinceEpoch(tt).toString(); qDebug() << "New EventList:" << QString::number(code, 16) << QDateTime::fromMSecsSinceEpoch(tt).toString(); el->AddEvent(tt, last); if (forceDebug && ((code == CPAP_Pressure) || (code == CPAP_IPAP) || (code == CPAP_EPAP)) ) qDebug() << "First Event:" << tmp << QDateTime::fromMSecsSinceEpoch(tt).toString() << "Pos:" << (sptr-1) - es.dataArray; tt += rate; break; } tt += rate; } while (sptr < eptr); if ( ! el) { qWarning() << "No eventList for" << QDateTime::fromMSecsSinceEpoch(sess->first()).toString() << "code" // << channel.channels[code]->code(); << QString::number(code, 16); #ifdef DEBUG_EFFICIENCY timeMutex.lock(); timeInTimeDelta += time.elapsed(); timeMutex.unlock(); #endif return; } if (forceDebug && ((code == CPAP_Pressure) || (code == CPAP_IPAP) || (code == CPAP_EPAP)) ) qDebug() << "Before loop to buildEventList" << el->count() << "Last:" << last*es.gain << "Next:" << (*sptr)*es.gain << "Pos:" << sptr - es.dataArray << QDateTime::fromMSecsSinceEpoch(tt).toString(); for (; sptr < eptr; sptr++) { c = *sptr; if (last != c) { if (square) { if (forceDebug && ((code == CPAP_Pressure) || (code == CPAP_IPAP) || (code == CPAP_EPAP)) ) qDebug() << "Before square call to buildEventList" << el->count(); el = buildEventList( last, t_min, t_max, es, &min, &max, tt, el, sess, code ); if (forceDebug && ((code == CPAP_Pressure) || (code == CPAP_IPAP) || (code == CPAP_EPAP)) ) qDebug() << "After square call to buildEventList" << el->count(); } if (forceDebug && ((code == CPAP_Pressure) || (code == CPAP_IPAP) || (code == CPAP_EPAP)) ) qDebug() << "Before call to buildEventList" << el->count() << "Cur:" << c*es.gain << "Last:" << last*es.gain << "Pos:" << sptr - es.dataArray << QDateTime::fromMSecsSinceEpoch(tt).toString(); el = buildEventList( c, t_min, t_max, es, &min, &max, tt, el, sess, code ); if (forceDebug && ((code == CPAP_Pressure) || (code == CPAP_IPAP) || (code == CPAP_EPAP)) ) qDebug() << "After call to buildEventList" << el->count(); } tt += rate; last = c; } tmp = EventDataType(c) * es.gain; if ((tmp >= t_min) && (tmp <= t_max)) { el->AddEvent(tt, c); if (forceDebug && ((code == CPAP_Pressure) || (code == CPAP_IPAP) || (code == CPAP_EPAP)) ) qDebug() << "Last Event:" << tmp << QDateTime::fromMSecsSinceEpoch(tt).toString() << "Pos:" << (sptr-1) - es.dataArray; } else qDebug() << "Failed to add last event - Code:" << QString::number(code, 16) << "Value:" << tmp << QDateTime::fromMSecsSinceEpoch(tt).toString() << "Pos:" << (sptr-1) - es.dataArray; sess->updateMin(code, min); sess->updateMax(code, max); sess->setPhysMin(code, es.physical_minimum); sess->setPhysMax(code, es.physical_maximum); sess->updateLast(tt); if (forceDebug) // qDebug() << "EventList:" << channel.channels[code]->code() << QDateTime::fromMSecsSinceEpoch(tt).toString() << "Size" << el->count(); qDebug() << "EventList:" << QString::number(code, 16) << QDateTime::fromMSecsSinceEpoch(tt).toString() << "Size" << el->count(); } else { qWarning() << "not enough records for EventList" << QDateTime::fromMSecsSinceEpoch(sess->first()).toString() << "code" // << channel.channels[code]->code(); << QString::number(code, 16); } #ifdef DEBUG_EFFICIENCY timeMutex.lock(); if (el != nullptr) { qint64 t = time.nsecsElapsed(); int cnt = el->count(); int bytes = cnt * (sizeof(EventStoreType) + sizeof(quint32)); int wvbytes = samples * (sizeof(EventStoreType)); auto it = channel_efficiency.find(code); if (it == channel_efficiency.end()) { channel_efficiency[code] = wvbytes - bytes; channel_time[code] = t; } else { it.value() += wvbytes - bytes; channel_time[code] += t; } } timeInTimeDelta += time.elapsed(); timeMutex.unlock(); #endif } // end ResMedLoader::ToTimeDelta EventList * buildEventList( EventStoreType est, EventDataType t_min, EventDataType t_max, EDFSignal &es, EventDataType *min, EventDataType *max, double tt, EventList *el, Session * sess, ChannelID code ) { using namespace schema; ChannelList channel; // QDateTime sessionStartDT = QDateTime:: fromMSecsSinceEpoch(sess->first()); // bool forceDebug = (sessionStartDT > QDateTime::fromString("2021-02-26 12:00:00", "yyyy-MM-dd HH:mm:ss")) && // (sessionStartDT < QDateTime::fromString("2021-02-27 12:00:00", "yyyy-MM-dd HH:mm:ss")); bool forceDebug = false; EventDataType tmp = EventDataType(est) * es.gain; if ((tmp >= t_min) && (tmp <= t_max)) { if (tmp < *min) *min = tmp; if (tmp > *max) *max = tmp; el->AddEvent(tt, est); } else { // if ( tmp > 0 ) qDebug() << "Code:" << QString::number(code, 16) <<"Value:" << tmp << "Out of range:\n\t t_min:" << t_min << "t_max:" << t_max << "EL count:" << el->count(); // Out of bounds value, start a new eventlist // But first drop a closing value that repeats the last one el->AddEvent(tt, el->raw(el->count() - 1)); if (el->count() > 1) { // that should be in session, not the eventlist.. handy for debugging though el->setDimension(es.physical_dimension); el = sess->AddEventList(code, EVL_Event, es.gain, es.offset, 0, 0); if (forceDebug) // qDebug() << "New EventList:" << channel.channels[code]->code() << QDateTime::fromMSecsSinceEpoch(tt).toString(); qDebug() << "New EventList:" << QString::number(code, 16) << QDateTime::fromMSecsSinceEpoch(tt).toString(); } else { el->clear(); // reuse the object if (forceDebug) // qDebug() << "Clear EventList:" << channel.channels[code]->code() << QDateTime::fromMSecsSinceEpoch(tt).toString(); qDebug() << "Clear EventList:" << QString::number(code, 16) << QDateTime::fromMSecsSinceEpoch(tt).toString(); } } return el; } // Check if given string matches any alternative signal names for this channel bool matchSignal(ChannelID ch, const QString & name) { auto channames = resmed_codes.find(ch); if (channames == resmed_codes.end()) { return false; } for (auto & string : channames.value()) { // Using starts with, because ResMed is very lazy about consistency if (name.startsWith(string, Qt::CaseInsensitive)) { return true; } } return false; } void setupResMedTranslationMap() { //////////////////////////////////////////////////////////////////////////// // Translation lookup table for non-english machines // Also combine S9, AS10, and AS11 variants //////////////////////////////////////////////////////////////////////////// // Only put the first part, enough to be identifiable, because ResMed likes // to crop short the signal names // Read this from a table? resmed_codes.clear(); // BRP file resmed_codes[CPAP_FlowRate] = QStringList{ "Flow", "Flow.40ms" }; resmed_codes[CPAP_MaskPressureHi] = QStringList{ "Mask Pres", "Press.40ms" }; // resmed_codes[CPAP_TriggerEvent] = QStringList{ "TrigCycEvt.40ms" }; // AC10 VAuto and -S resmed_codes[CPAP_RespEvent] = QStringList {"Resp Event", "TrigCycEvt.40ms" }; // S9 VPAPS and STA-IVAPS call it RespEvent // PLD File resmed_codes[CPAP_MaskPressure] = QStringList { "Mask Pres", "MaskPress.2s" }; // resmed_codes[CPAP_RespEvent] = QStringList {"Resp Event" }; resmed_codes[CPAP_Pressure] = QStringList { "Therapy Pres", "Press.2s" }; // Un problemo... IPAP also uses Press.2s.. check the mode :/ resmed_codes[CPAP_IPAP] = QStringList { "Insp Pres", "IPAP", "S.BL.IPAP" }; resmed_codes[CPAP_EPAP] = QStringList { "Exp Pres", "EprPress.2s", "EPAP", "S.BL.EPAP", "EPRPress.2s" }; resmed_codes[CPAP_EPAPHi] = QStringList { "Max EPAP" }; resmed_codes[CPAP_EPAPLo] = QStringList { "Min EPAP", "S.VA.MinEPAP" }; resmed_codes[CPAP_IPAPHi] = QStringList { "Max IPAP", "S.VA.MaxIPAP" }; resmed_codes[CPAP_IPAPLo] = QStringList { "Min IPAP" }; resmed_codes[CPAP_PS] = QStringList { "PS", "S.VA.PS" }; resmed_codes[CPAP_PSMin] = QStringList { "Min PS" }; resmed_codes[CPAP_PSMax] = QStringList { "Max PS" }; resmed_codes[CPAP_Leak] = QStringList { "Leak", "Leck", "Fuites", "Fuite", "Fuga", "\xE6\xBC\x8F\xE6\xB0\x94", "Lekk", "Läck","Läck", "Leak.2s", "Sızıntı" }; resmed_codes[CPAP_RespRate] = QStringList { "RR", "AF", "FR", "RespRate.2s" }; resmed_codes[CPAP_MinuteVent] = QStringList { "MV", "VM", "MinVent.2s" }; resmed_codes[CPAP_TidalVolume] = QStringList { "Vt", "VC", "TidVol.2s" }; resmed_codes[CPAP_IE] = QStringList { "I:E", "IERatio.2s" }; resmed_codes[CPAP_Snore] = QStringList { "Snore", "Snore.2s" }; resmed_codes[CPAP_FLG] = QStringList { "FFL Index", "FlowLim.2s" }; resmed_codes[CPAP_Ti] = QStringList { "Ti", "B5ITime.2s" }; resmed_codes[CPAP_Te] = QStringList { "Te", "B5ETime.2s" }; resmed_codes[CPAP_TgMV] = QStringList { "TgMV", "TgtVent.2s" }; resmed_codes[OXI_Pulse] = QStringList { "Pulse", "Puls", "Pouls", "Pols", "Pulse.1s", "Nabiz" }; resmed_codes[OXI_SPO2] = QStringList { "SpO2", "SpO2.1s" }; resmed_codes[CPAP_Obstructive] = QStringList { "Obstructive apnea" }; resmed_codes[CPAP_Hypopnea] = QStringList { "Hypopnea" }; resmed_codes[CPAP_Apnea] = QStringList { "Apnea" }; resmed_codes[CPAP_RERA] = QStringList { "Arousal" }; resmed_codes[CPAP_ClearAirway] = QStringList { "Central apnea" }; // STR signals resmed_codes[CPAP_Mode] = QStringList { "Mode", "Modus", "Funktion", "\xE6\xA8\xA1\xE5\xBC\x8F", "Mod" }; resmed_codes[RMS9_SetPressure] = QStringList { "Set Pressure", "Eingest. Druck", "Ingestelde druk", "\xE8\xAE\xBE\xE5\xAE\x9A\xE5\x8E\x8B\xE5\x8A\x9B", "Pres. prescrite", "Inställt tryck", "Inställt tryck", "S.C.Press", "Basıncı Ayarl" }; resmed_codes[RMS9_EPR] = QStringList { "EPR", "\xE5\x91\xBC\xE6\xB0\x94\xE9\x87\x8A\xE5\x8E\x8B\x28\x45\x50" }; resmed_codes[RMS9_EPRLevel] = QStringList { "EPR Level", "EPR-Stufe", "EPR-niveau", "\x45\x50\x52\x20\xE6\xB0\xB4\xE5\xB9\xB3", "Niveau EPR", "EPR-nivå", "EPR-nivÃ¥", "S.EPR.Level", "EPR Düzeyi" }; resmed_codes[CPAP_PressureMax] = QStringList { "Max Pressure", "Max. Druck", "Max druk", "\xE6\x9C\x80\xE5\xA4\xA7\xE5\x8E\x8B\xE5\x8A\x9B", "Pression max.", "Max tryck", "S.AS.MaxPress", "S.A.MaxPress", "Azami Basınç" }; resmed_codes[CPAP_PressureMin] = QStringList { "Min Pressure", "Min. Druck", "Min druk", "\xE6\x9C\x80\xE5\xB0\x8F\xE5\x8E\x8B\xE5\x8A\x9B", "Pression min.", "Min tryck", "S.AS.MinPress", "S.A.MinPress", "Min Basınç" }; //resmed_codes[RMS9_EPR].push_back("S.EPR.EPRType"); } // don't really need this anymore, but perhaps it's useful info for reference // Resmed_Model_Map = { // { "S9 Escape", { 36001, 36011, 36021, 36141, 36201, 36221, 36261, 36301, 36361 } }, // { "S9 Escape Auto", { 36002, 36012, 36022, 36302, 36362 } }, // { "S9 Elite", { 36003, 36013, 36023, 36103, 36113, 36123, 36143, 36203, 36223, 36243, 36263, 36303, 36343, 36363 } }, // { "S9 Autoset", { 36005, 36015, 36025, 36105, 36115, 36125, 36145, 36205, 36225, 36245, 36265, 36305, 36325, 36345, 36365 } }, // { "S9 AutoSet CS", { 36100, 36110, 36120, 36140, 36200, 36220, 36360 } }, // { "S9 AutoSet 25", { 36106, 36116, 36126, 36146, 36206, 36226, 36366 } }, // { "S9 AutoSet for Her", { 36065 } }, // { "S9 VPAP S", { 36004, 36014, 36024, 36114, 36124, 36144, 36204, 36224, 36284, 36304 } }, // { "S9 VPAP Auto", { 36006, 36016, 36026 } }, // { "S9 VPAP Adapt", { 36037, 36007, 36017, 36027, 36367 } }, // { "S9 VPAP ST", { 36008, 36018, 36028, 36108, 36148, 36208, 36228, 36368 } }, // { "S9 VPAP ST 22", { 36118, 36128 } }, // { "S9 VPAP ST-A", { 36039, 36159, 36169, 36379 } }, // //S8 Series // { "S8 Escape", { 33007 } }, // { "S8 Elite II", { 33039 } }, // { "S8 Escape II", { 33051 } }, // { "S8 Escape II AutoSet", { 33064 } }, // { "S8 AutoSet II", { 33129 } }, // }; // // Return the model name matching the supplied model number. // const QString & lookupModel(quint16 model) // { // // for (auto it=Resmed_Model_Map.begin(),end = Resmed_Model_Map.end(); it != end; ++it) { // QList & list = it.value(); // for (auto val : list) { // if (val == model) { // return it.key(); // } // } // } // return STR_UnknownModel; // } //////////////////////////////////////////////////////////////////////////////////////////////// // Model number information // 36003, 36013, 36023, 36103, 36113, 36123, 36143, 36203, // 36223, 36243, 36263, 36303, 36343, 36363 S9 Elite Series // 36005, 36015, 36025, 36105, 36115, 36125, 36145, 36205, // 36225, 36245, 36265, 36305, 36325, 36345, 36365 S9 AutoSet Series // 36065 S9 AutoSet for Her // 36001, 36011, 36021, 36141, 36201, 36221, 36261, 36301, // 36361 S9 Escape // 36002, 36012, 36022, 36302, 36362 S9 Escape Auto // 36004, 36014, 36024, 36114, 36124, 36144, 36204, 36224, // 36284, 36304 S9 VPAP S (+ H5i, + Climate Control) // 36006, 36016, 36026 S9 VPAP AUTO (+ H5i, + Climate Control) // 36007, 36017, 36027, 36367 // S9 VPAP ADAPT (+ H5i, + Climate // Control) // 36008, 36018, 36028, 36108, 36148, 36208, 36228, 36368 S9 VPAP ST (+ H5i, + Climate Control) // 36100, 36110, 36120, 36140, 36200, 36220, 36360 S9 AUTOSET CS // 36106, 36116, 36126, 36146, 36206, 36226, 36366 S9 AUTOSET 25 // 36118, 36128 S9 VPAP ST 22 // 36039, 36159, 36169, 36379 S9 VPAP ST-A // 24921, 24923, 24925, 24926, 24927 ResMed Power Station II (RPSII) // 33030 S8 Compact // 33001, 33007, 33013, 33036, 33060 S8 Escape // 33032 S8 Lightweight // 33033 S8 AutoScore // 33048, 33051, 33052, 33053, 33054, 33061 S8 Escape II // 33055 S8 Lightweight II // 33021 S8 Elite // 33039, 33045, 33062, 33072, 33073, 33074, 33075 S8 Elite II // 33044 S8 AutoScore II // 33105, 33112, 33126 S8 AutoSet (including Spirit & Vantage) // 33128, 33137 S8 Respond // 33129, 33141, 33150 S8 AutoSet II // 33136, 33143, 33144, 33145, 33146, 33147, 33148 S8 AutoSet Spirit II // 33138 S8 AutoSet C // 26101, 26121 VPAP Auto 25 // 26119, 26120 VPAP S // 26110, 26122 VPAP ST // 26104, 26105, 26125, 26126 S8 Auto 25 // 26102, 26103, 26106, 26107, 26108, 26109, 26123, 26127 VPAP IV // 26112, 26113, 26114, 26115, 26116, 26117, 26118, 26124 VPAP IV ST OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/resmed_loader.h000066400000000000000000000153061417327530600242710ustar00rootroot00000000000000/* SleepLib RESMED Loader Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef RESMED_LOADER_H #define RESMED_LOADER_H #include #include "SleepLib/machine.h" // Base class: MachineLoader #include "SleepLib/machine_loader.h" #include "SleepLib/profiles.h" #include "SleepLib/loader_plugins/edfparser.h" #include "SleepLib/loader_plugins/resmed_EDFinfo.h" //******************************************************************************************** /// IMPORTANT!!! //******************************************************************************************** // Please INCREMENT the following value when making changes to this loaders implementation. // const int resmed_data_version = 15; // //******************************************************************************************** class ResmedLoader; class ResMedDay { public: ResMedDay( QDate d) : date(d) {} QDate date; STRRecord str; QHash files; // key is filename, value is fullpath }; typedef void (*ResDaySaveCallback)(ResmedLoader* loader, Session* session); class ResDayTask:public ImportTask { public: ResDayTask(ResmedLoader * l, Machine * m, ResMedDay * d, ResDaySaveCallback s): reimporting(false), loader(l), mach(m), resday(d), save(s) {} virtual ~ResDayTask() {} virtual void run(); bool reimporting; protected: ResmedLoader * loader; Machine * mach; ResMedDay * resday; ResDaySaveCallback save; }; /*! \class ResmedLoader \brief Importer for ResMed S9 Data */ class ResmedLoader : public CPAPLoader { Q_OBJECT friend class ResmedImport; friend class ResmedImportStage2; public: ResmedLoader(); virtual ~ResmedLoader(); //! \brief Register the ResmedLoader with the list of other machine loaders static void Register(); //! \brief Detect if the given path contains a valid Folder structure virtual bool Detect(const QString & path); //! \brief Look up machine model information of ResMed file structure stored at path virtual MachineInfo PeekInfo(const QString & path); virtual void checkSummaryDay( ResMedDay & resday, QDate date, Machine * mach ); //! \brief Scans for ResMed SD folder structure signature, and loads any new data if found virtual int Open(const QString &); //! \brief Returns the version number of this ResMed loader virtual int Version() { return resmed_data_version; } //! \brief Returns the Machine class name of this loader. ("ResMed") virtual const QString &loaderName() { return resmed_class_name; } virtual MachineInfo newInfo() { return MachineInfo(MT_CPAP, 0, resmed_class_name, QObject::tr("ResMed"), QString(), QString(), QString(), QObject::tr("S9"), QDateTime::currentDateTime(), resmed_data_version); } virtual void initChannels(); //! \brief Converts EDFSignal data to time delta packed EventList, and adds to Session void ToTimeDelta(Session *sess, ResMedEDFInfo &edf, EDFSignal &es, ChannelID code, long recs, qint64 duration, EventDataType min = 0, EventDataType max = 0, bool square = false); //! \brief Parse the EVE Event annotation data, and save to Session * sess //! This contains all Hypopnea, Obstructive Apnea, Central and Apnea codes bool LoadEVE(Session *sess, const QString & path); //! \brief Parse the CSL Event annotation data, and save to Session * sess //! This contains Cheyne Stokes Respiration flagging on the AirSense 10 bool LoadCSL(Session *sess, const QString & path); //! \brief Parse the BRP High Resolution data, and save to Session * sess //! This contains Flow Rate, Mask Pressure, and Resp. Event data bool LoadBRP(Session *sess, const QString & path); //! \brief Parse the SAD Pulse oximetry attachment data, and save to Session * sess //! This contains Pulse Rate and SpO2 Oxygen saturation data bool LoadSAD(Session *sess, const QString & path); //! \brief Parse the PRD low resolution data, and save to Session * sess //! This contains the Pressure, Leak, Respiratory Rate, Minute Ventilation, Tidal Volume, etc.. bool LoadPLD(Session *sess, const QString & path); //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Now for some CPAPLoader overrides //////////////////////////////////////////////////////////////////////////////////////////////////////////// virtual QString PresReliefLabel() { return QObject::tr("EPR: "); } virtual ChannelID PresReliefMode() ; virtual ChannelID PresReliefLevel() ; virtual ChannelID CPAPModeChannel() ; //////////////////////////////////////////////////////////////////////////////////////////////////////////// volatile int sessionCount; static void SaveSession(ResmedLoader* loader, Session* session); ResDaySaveCallback saveCallback; int OpenWithCallback(const QString & dirpath, ResDaySaveCallback s); void LogUnexpectedMessage(const QString & message); protected: //! \brief The STR.edf file is a unique edf file with many signals bool ProcessSTRfiles(Machine *, QMap &, QDate); //! \brief Scan for new files to import, group into sessions and add to task que int ScanFiles(Machine * mach, const QString & datalog_path, QDate firstImport); //! \brief Write a backup copy to the backup path QString Backup(const QString & file, const QString & backup_path); // The data members // QMap sessfiles; // QMap strsess; // QMap > strdate; QMap resdayList; #ifdef DEBUG_EFFICIENCY QHash channel_efficiency; QHash channel_time; volatile qint64 timeInLoadBRP; volatile qint64 timeInLoadPLD; volatile qint64 timeInLoadEVE; volatile qint64 timeInLoadCSL; volatile qint64 timeInLoadSAD; volatile qint64 timeInEDFOpen; volatile qint64 timeInEDFInfo; volatile qint64 timeInAddWaveform; volatile qint64 timeInTimeDelta; QMutex timeMutex; #endif // TODO: This really belongs in a generic location that all loaders can use. // But that will require retooling the overall call structure so that there's // a top-level import job that's managing a specific import. Right now it's // essentially managed by the importCPAP method rather than an object instance // with state. QMutex m_importMutex; QSet m_unexpectedMessages; }; #endif // RESMED_LOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/sleepstyle_EDFinfo.cpp000066400000000000000000000065571417327530600255520ustar00rootroot00000000000000/* SleepLib SleepStyle Loader Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include #include #include "SleepLib/session.h" #include "SleepLib/calcs.h" #include "SleepLib/loader_plugins/sleepstyle_EDFinfo.h" SleepStyleEDFInfo::SleepStyleEDFInfo() : EDFInfo() { setTimeZoneUTC(); // Ask EDF Parser to assume data is in UTC, not in local time } SleepStyleEDFInfo::~SleepStyleEDFInfo() { } bool SleepStyleEDFInfo::Parse( ) // overrides and calls the super's Parse { if ( ! EDFInfo::Parse( ) ) { qWarning() << "sleepStyle EDFInfo::Parse failed!"; // sleep(1); return false; } // Now massage some stuff into OSCAR's layout // Extract the serial number from header string QStringList parts = edfHdr.recordingident.split(' '); serialnumber = parts[6]; if (!edfHdr.startdate_orig.isValid()) { qDebug() << "sleepStyle EDFInfo::Parse Invalid date time retreieved parsing EDF File" << filename; // sleep(1); return false; } startdate = qint64(edfHdr.startdate_orig.toTime_t()) * 1000L; //startdate-=timezoneOffset(); if (startdate == 0) { qDebug() << "sleepStyle EDFInfo::Parse Invalid startdate = 0 in EDF File" << filename; // sleep(1); return false; } dur_data_record = (edfHdr.duration_Seconds * 1000.0L); enddate = startdate + dur_data_record * qint64(edfHdr.num_data_records); return true; } extern QHash resmed_codes; // Looks up foreign language Signal names that match this channelID EDFSignal *SleepStyleEDFInfo::lookupSignal(ChannelID ch) { // Get list of all known foreign language names for this channel auto channames = resmed_codes.find(ch); if (channames == resmed_codes.end()) { // no alternatives strings found for this channel return nullptr; } // This is bad, because ResMed thinks it was a cool idea to use two channels with the same name. // Scan through EDF's list of signals to see if any match for (auto & name : channames.value()) { EDFSignal *sig = lookupLabel(name); if (sig) return sig; } // Failed return nullptr; } QDateTime SleepStyleEDFInfo::getStartDT( QString dateTimeStr ) { // edfHdr.startdate_orig = QDateTime::fromString(QString::fromLatin1(hdrPtr->datetime, 16), "dd.MM.yyHH.mm.ss"); // QString dateTimeStr; // , dateStr, timeStr; QDate qDate; QTime qTime; // dateTimeStr = QString::fromLatin1(hdrPtr->datetime, 16); // dateStr = dateTimeStr.left(8); // timeStr = dateTimeStr.right(8); qDate = QDate::fromString(dateTimeStr.left(8), "dd.MM.yy"); if (qDate.year() < 2000) { qDate = qDate.addYears(100); } qTime = QTime::fromString(dateTimeStr.right(8), "HH.mm.ss"); return QDateTime(qDate, qTime, Qt::UTC); } void dumpEDFduration( ssEDFduration dur ) { qDebug() << "Fullpath" << dur.path << "Filename" << dur.filename << "Start" << dur.start << "End" << dur.end; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/sleepstyle_EDFinfo.h000066400000000000000000000033321417327530600252030ustar00rootroot00000000000000/* SleepLib SleepStyle EDFinfo Header * * Copyright (c) 2021-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef SLEEPSTYLE_EDFINFO_H #define SLEEPSTYLE_EDFINFO_H #include #include "SleepLib/machine.h" // Base class: MachineLoader #include "SleepLib/machine_loader.h" #include "SleepLib/profiles.h" #include "SleepLib/loader_plugins/edfparser.h" //enum EDFType { EDF_UNKNOWN, EDF_BRP, EDF_PLD, EDF_SAD, EDF_EVE, EDF_CSL, EDF_AEV }; enum EDFType { EDF_UNKNOWN, EDF_RT }; // EDFType lookupEDFType(const QString & filename); const QString SLEEPSTYLE_class_name = STR_MACH_ResMed; //class STRFile; // forward class SleepStyleEDFInfo : public EDFInfo { public: SleepStyleEDFInfo(); ~SleepStyleEDFInfo(); virtual bool Parse() override; // overrides and calls the super's Parse virtual qint64 GetDurationMillis() { return dur_data_record; } // overrides the super EDFSignal *lookupSignal(ChannelID ch); QDateTime getStartDT( QString dateTimeStr ); //! \brief The following are computed from the edfHdr data QString serialnumber; qint64 dur_data_record; qint64 startdate; qint64 enddate; }; class ssEDFduration { public: ssEDFduration() { start = end = 0; type = EDF_UNKNOWN; } ssEDFduration(quint32 start, quint32 end, QString path) : start(start), end(end), path(path) {} quint32 start; quint32 end; QString path; QString filename; EDFType type; }; void dumpEDFduration( ssEDFduration dur ); #endif // SLEEPSTYLE_EDFINFO_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/sleepstyle_loader.cpp000066400000000000000000001051221417327530600255320ustar00rootroot00000000000000/* SleepLib Fisher & Paykel SleepStyle Loader Implementation * * Copyright (c) 2020-2022 The Oscar Team * * Derived from icon_loader.cpp * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include "sleepstyle_loader.h" #include "sleepstyle_EDFinfo.h" const QString FPHCARE = "FPHCARE"; ChannelID SS_SensAwakeLevel; ChannelID SS_EPR; ChannelID SS_EPRLevel; ChannelID SS_Ramp; ChannelID SS_Humidity; SleepStyle::SleepStyle(Profile *profile, MachineID id) : CPAP(profile, id) { } SleepStyle::~SleepStyle() { } SleepStyleLoader::SleepStyleLoader() { m_buffer = nullptr; m_type = MT_CPAP; } SleepStyleLoader::~SleepStyleLoader() { } /* * getIconDir - returns the path to the ICON directory */ QString getIconDir (QString givenpath) { QString path = givenpath; path = path.replace("\\", "/"); if (path.endsWith("/")) { path.chop(1); } if (path.endsWith("/" + FPHCARE)) { path = path.section("/",0,-2); } QDir dir(path); if (!dir.exists()) { return ""; } // If this is a backup directory, higher level directories have been // omitted. if (path.endsWith("/Backup/", Qt::CaseInsensitive)) return path; // F&P Icon have a folder called FPHCARE in the root directory if (!dir.exists(FPHCARE)) { return ""; } // CHECKME: I can't access F&P ICON data right now if (!dir.exists("FPHCARE/ICON")) { return ""; } return dir.filePath("FPHCARE/ICON"); } /* * getSleepStyleMachines returns a list of all SleepStyle machine folders in the ICON directory */ QStringList getSleepStyleMachines (QString iconPath) { QStringList ssMachines; QDir iconDir (iconPath); iconDir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks); iconDir.setSorting(QDir::Name); QFileInfoList flist = iconDir.entryInfoList(); // List of Icon subdirectories // Walk though directory list and save those that appear to be for SleepStyle machins. for (int i = 0; i < flist.size(); i++) { QFileInfo fi = flist.at(i); QString filename = fi.fileName(); // directory is serial number and must have a SUM*.FPH file within it to be an Icon or SleepStyle folder QDir machineDir (iconPath + "/" + filename); machineDir.setFilter(QDir::NoDotAndDotDot | QDir::Files | QDir::Hidden | QDir::NoSymLinks); machineDir.setSorting(QDir::Name); QStringList filters; filters << "SUM*.fph"; machineDir.setNameFilters(filters); QFileInfoList flist = machineDir.entryInfoList(); if (flist.size() <= 0) { continue; } // Find out what machine model this is QFile sumFile (flist.at(0).absoluteFilePath()); QString line; sumFile.open(QIODevice::ReadOnly); QTextStream instr(&sumFile); for (int j = 0; j < 5; j++) { line = ""; QString c = ""; while ((c = instr.read(1)) != "\r") { line += c; } } sumFile.close(); if (line.toUpper() == "SLEEPSTYLE") ssMachines.push_back(filename); } return ssMachines; } bool SleepStyleLoader::Detect(const QString & givenpath) { QString iconPath = getIconDir(givenpath); if (iconPath.isEmpty()) return false; QStringList machines = getSleepStyleMachines(iconPath); if (machines.length() <= 0) // Did not find any SleepStyle machine directories return false; return true; } bool SleepStyleLoader::backupData (Machine * mach, const QString & path) { QDir ipath(path); QDir bpath(mach->getBackupPath()); // Compare QDirs rather than QStrings because separators may be different, especially on Windows. if (ipath == bpath) { // Don't create backups if importing from backup folder rebuild_from_backups = true; create_backups = false; } else { rebuild_from_backups = false; create_backups = p_profile->session->backupCardData(); } if (rebuild_from_backups || !create_backups) return true; // Copy input data to backup location copyPath(ipath.absolutePath(), bpath.absolutePath(), true); return true; } int SleepStyleLoader::Open(const QString & path) { QString iconPath = getIconDir(path); if (iconPath.isEmpty()) return false; QStringList serialNumbers = getSleepStyleMachines(iconPath); if (serialNumbers.length() <= 0) // Did not find any SleepStyle machine directories return false; Machine *m; int c = 0; for (int i = 0; i < serialNumbers.size(); i++) { MachineInfo info = newInfo(); info.serial = serialNumbers[i]; m = p_profile->CreateMachine(info); setSerialPath(iconPath + "/" + info.serial); try { if (m) { c+=OpenMachine(m, path, serialPath); } } catch (OneTypePerDay& e) { Q_UNUSED(e) p_profile->DelMachine(m); MachList.erase(MachList.find(info.serial)); QMessageBox::warning(nullptr, tr("Import Error"), tr("This Machine Record cannot be imported in this profile.")+"\n\n"+tr("The Day records overlap with already existing content."), QMessageBox::Ok); delete m; } } return c; } int SleepStyleLoader::OpenMachine(Machine *mach, const QString & path, const QString & ssPath) { emit updateMessage(QObject::tr("Getting Ready...")); emit setProgressValue(0); QCoreApplication::processEvents(); QDir dir(ssPath); if (!dir.exists() || (!dir.isReadable())) { return -1; } backupData(mach, path); calc_leaks = p_profile->cpap->calculateUnintentionalLeaks(); lpm4 = p_profile->cpap->custom4cmH2OLeaks(); lpm20 = p_profile->cpap->custom20cmH2OLeaks(); qDebug() << "Opening F&P SleepStyle" << ssPath; dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); QFileInfoList flist = dir.entryInfoList(); QString filename, fpath; emit updateMessage(QObject::tr("Reading data files...")); QCoreApplication::processEvents(); QStringList summary, det, his; Sessions.clear(); for (int i = 0; i < flist.size(); i++) { QFileInfo fi = flist.at(i); filename = fi.fileName(); fpath = ssPath + "/" + filename; if (filename.left(3).toUpper() == "SUM") { summary.push_back(fpath); OpenSummary(mach, fpath); } else if (filename.left(3).toUpper() == "DET") { det.push_back(fpath); } else if (filename.left(3).toUpper() == "HIS") { his.push_back(fpath); } } for (int i = 0; i < det.size(); i++) { OpenDetail(mach, det[i]); } // Process REALTIME files dir.cd("REALTIME"); QFileInfoList rtlist = dir.entryInfoList(); for (int i = 0; i < rtlist.size(); i++) { QFileInfo fi = rtlist.at(i); filename = fi.fileName(); fpath = ssPath + "/REALTIME/" + filename; if (filename.left(3).toUpper() == "HRD" && filename.right(3).toUpper() == "EDF" ) { OpenRealTime (mach, filename, fpath); } } // LOG files were not processed by icon_loader // So we don't need to do anything SessionID sid;//,st; float hours, mins; // For diagnostics, print summary of last 20 session or one week qDebug() << "SS Loader - last 20 Sessions:"; int cnt = 0; QDateTime dt; QString a = ""; if (Sessions.size() > 0) { QMap::iterator it = Sessions.end(); it--; dt = QDateTime::fromTime_t(qint64(it.value()->first()) / 1000L); QDate date = dt.date().addDays(-7); it++; do { it--; Session *sess = it.value(); sid = sess->session(); hours = sess->hours(); mins = hours * 60; dt = QDateTime::fromTime_t(sid); qDebug() << cnt << ":" << dt << "session" << sid << "," << mins << "minutes" << a; if (dt.date() < date) { break; } ++cnt; } while (it != Sessions.begin()); } // qDebug() << "Unmatched Sessions"; // QList chunks; // for (QMap::iterator dit=FLWDate.begin();dit!=FLWDate.end();dit++) { // int k=dit.key(); // //QDate date=dit.value(); //// QList values = SessDate.values(date); // for (int j=0;jchannelDataExists(CPAP_FlowRate)) c=true; // } // qDebug() << k << "-" <channelDataExists(CPAP_FlowRate)) c=true; // } // qDebug() << chunk.file << ":" << i << zz << dur << "minutes" << (b ? "*" : "") << (c ? QDateTime::fromTime_t(zz).toString() : ""); // } int c = Sessions.size(); qDebug() << "SS Loader found" << c << "sessions"; emit updateMessage(QObject::tr("Finishing up...")); QCoreApplication::processEvents(); finishAddingSessions(); mach->Save(); return c; } // !\brief Convert F&P 32bit date format to 32bit UNIX Timestamp quint32 ssconvertDate(quint32 timestamp) { quint16 day, month,hour=0, minute=0, second=0; quint16 year; day = timestamp & 0x1f; month = (timestamp >> 5) & 0x0f; year = 2000 + ((timestamp >> 9) & 0x3f); quint32 ts2 = timestamp >> 15; second = ts2 & 0x3f; minute = (ts2 >> 6) & 0x3f; hour = (ts2 >> 12); QDateTime dt = QDateTime(QDate(year, month, day), QTime(hour, minute, second), Qt::UTC); #ifdef DEBUGSS // qDebug().noquote() << "SS timestamp" << timestamp << year << month << day << dt << hour << minute << second; #endif // Q NO!!! _ASSERT(dt.isValid()); // if ((year == 2013) && (month == 9) && (day == 18)) { // // this is for testing.. set a breakpoint on here and // int i=5; // } // From Rudd's data set compared to times reported from his F&P software's report (just the time bits left over) // 90514 = 00:06:18 WET 23:06:18 UTC 09:06:18 AEST // 94360 = 01:02:24 WET // 91596 = 00:23:12 WET // 19790 = 23:23:50 WET return dt.addSecs(-54).toTime_t(); // Huh? Why do this? } // SessionID is in seconds, not msec SessionID SleepStyleLoader::findSession (SessionID sid) { for(auto sessKey : Sessions.keys()) { Session * sess = Sessions.value(sessKey); if (sid >= (sess->realFirst() / 1000L) && sid <= (sess->realLast() / 1000L)) return sessKey; } return 0; } bool SleepStyleLoader::OpenRealTime(Machine *mach, const QString & fname, const QString & filepath) { // Q_UNUSED(filepath) Q_UNUSED(mach) Q_UNUSED(fname) SleepStyleEDFInfo edf; // Open the EDF file and read contents into edf object if (!edf.Open(filepath)) { qWarning() << "SS Realtime failed to open" << filepath; return false; } if (!edf.Parse()) { qWarning() << "SS Realtime Parse failed to open" << filepath; return false; } #ifdef DEBUGSS qDebug().noquote() << "SS ORT timestamp" << edf.startdate / 1000L << QDateTime::fromSecsSinceEpoch(edf.startdate / 1000L).toString("MM/dd/yyyy hh:mm:ss"); #endif SessionID sessKey = findSession(edf.startdate / 1000L); if (sessKey == 0) { qWarning() << "SS ORT session not found"; return true; } Session * sess = Sessions.value(sessKey); if (sess == nullptr) { qWarning() << "SS ORT session not found - nullptr"; return true; } // sess->updateFirst(edf.startdate); sess->really_set_first(edf.startdate); qint64 duration = edf.GetNumDataRecords() * edf.GetDurationMillis(); qDebug() << "SS EDF millis" << edf.GetDurationMillis() << "num recs" << edf.GetNumDataRecords(); sess->updateLast(edf.startdate + duration); // Find the leak signal and data long leakrecs = 0; EDFSignal leakSignal; EDFSignal maskSignal; long maskRecs; for (auto & esleak : edf.edfsignals) { leakrecs = esleak.sampleCnt * edf.GetNumDataRecords(); if (leakrecs < 0) continue; if (esleak.label == "Leak") { leakSignal = esleak; break; } } // Walk through all signals, ignoring leaks for (auto & es : edf.edfsignals) { long recs = es.sampleCnt * edf.GetNumDataRecords(); #ifdef DEBUGSS qDebug() << "SS EDF" << es.label << "count" << es.sampleCnt << "gain" << es.gain << "offset" << es.offset << "dim" << es.physical_dimension << "phys min" << es.physical_minimum << "max" << es.physical_maximum << "dig min" << es.digital_minimum << "max" << es.digital_maximum; #endif if (recs < 0) continue; ChannelID code = 0; if (es.label == "Flow") { // Flow data appears to include total leaks, which are also reported in the edf file. // We subtract the leak from the flow data to get flow data that is centered around zero. // This is needed for other derived graphs (tidal volume, insp and exp times, etc.) to be reasonable code = CPAP_FlowRate; bool done = false; if (leakrecs > 0) { for (int ileak = 0; ileak < leakrecs && !done; ileak++) { for (int iflow = 0; iflow < 25 && !done; iflow++) { if (ileak*25 + iflow >= recs) { done = true; break; } es.dataArray[ileak*25 + iflow] -= leakSignal.dataArray[ileak] - 500; } } } } else if (es.label == "Pressure") { // First compute CPAP_Leak data maskRecs = es.sampleCnt * edf.GetNumDataRecords(); maskSignal = es; float lpm = lpm20 - lpm4; float ppm = lpm / 16.0; if (maskRecs != leakrecs) { qWarning() << "SS ORT maskRecs" << maskRecs << "!= leakrecs" << leakrecs; } else { qint16 * leakarray = new qint16 [maskRecs]; for (int i = 0; i < maskRecs; i++) { // Extract IPAP from mask pressure, which is a combination of IPAP and EPAP values // get maximum mask pressure over several adjacent data points to make best guess at IPAP float mp = es.dataArray[i]; int jrange = 3; // Number on each side of center int jstart = std::max(0, i-jrange); int jend = (i+jrange)>maskRecs ? maskRecs : i+jrange; for (int j = jstart; j < jend; j++) mp = fmaxf(mp, es.dataArray[j]); float press = mp * es.gain - 4.0; // Convert pressure to cmH2O and get difference from low end of adjustment curve // Calculate expected (intentional) leak in l/m float expLeak = press * ppm + lpm4; qint16 unintLeak = leakSignal.dataArray[i] - (qint16)(expLeak / es.gain); if (unintLeak < 0) unintLeak = 0; leakarray[i] = unintLeak; } ChannelID leakcode = CPAP_Leak; double rate = double(duration) / double(recs); EventList *a = sess->AddEventList(leakcode, EVL_Waveform, es.gain, es.offset, 0, 0, rate); a->setDimension(es.physical_dimension); a->AddWaveform(edf.startdate, leakarray, recs, duration); EventDataType min = a->Min(); EventDataType max = a->Max(); /*** // Cap to physical dimensions, because there can be ram glitches/whatever that throw really big outliers. if (min < es.physical_minimum) min = es.physical_minimum; if (max > es.physical_maximum) max = es.physical_maximum; ***/ sess->updateMin(leakcode, min); sess->updateMax(leakcode, max); sess->setPhysMin(leakcode, es.physical_minimum); sess->setPhysMax(leakcode, es.physical_maximum); delete [] leakarray; } // Now do normal processing for Mask Pressure code = CPAP_MaskPressure; } else if (es.label == "Leak") { code = CPAP_LeakTotal; } else continue; if (code) { double rate = double(duration) / double(recs); EventList *a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate); a->setDimension(es.physical_dimension); a->AddWaveform(edf.startdate, es.dataArray, recs, duration); #ifdef DEBUGSS qDebug() << "SS EDF recs" << recs << "duration" << duration << "rate" << rate; #endif EventDataType min = a->Min(); EventDataType max = a->Max(); // Cap to physical dimensions, because there can be ram glitches/whatever that throw really big outliers. if (min < es.physical_minimum) min = es.physical_minimum; if (max > es.physical_maximum) max = es.physical_maximum; sess->updateMin(code, min); sess->updateMax(code, max); sess->setPhysMin(code, es.physical_minimum); sess->setPhysMax(code, es.physical_maximum); } } return true; } //////////////////////////////////////////////////////////////////////////////////////////// // Open Summary file, create list of sessions and session summary data //////////////////////////////////////////////////////////////////////////////////////////// bool SleepStyleLoader::OpenSummary(Machine *mach, const QString & filename) { qDebug() << "SS SUM File" << filename; QByteArray header; QFile file(filename); QString typex; if (!file.open(QFile::ReadOnly)) { qWarning() << "SS SUM Couldn't open" << filename; return false; } // Read header of summary file header = file.read(0x200); if (header.size() != 0x200) { qWarning() << "SS SUM Short file" << filename; file.close(); return false; } // Header is terminated by ';' at 0x1ff unsigned char hterm = 0x3b; if (hterm != header[0x1ff]) { qWarning() << "SS SUM Header missing ';' terminator" << filename; } QTextStream htxt(&header); QString h1, version, fname, serial, model, type, unknownident; htxt >> h1; htxt >> version; htxt >> fname; htxt >> serial; htxt >> model; //TODO: Should become Series in machine info??? htxt >> type; // SPSAAN etc with 4th character being A (Auto) or C (CPAP) htxt >> unknownident; // Constant, but has different value when version number is different. #ifdef DEBUGSS qDebug() << "SS SUM header" << h1 << version << fname << serial << model << type << unknownident; #endif if (type.length() > 4) typex = (type.at(3) == 'C' ? "CPAP" : "Auto"); mach->setModel(model + " " + typex); mach->info.modelnumber = type; // Read remainder of summary file QByteArray data; data = file.readAll(); file.close(); QDataStream in(data); in.setVersion(QDataStream::Qt_4_8); in.setByteOrder(QDataStream::LittleEndian); quint32 ts; //QByteArray line; unsigned char ramp, j1, x1, x2, mode; unsigned char runTime, useTime, minPressSet, maxPressSet, minPressSeen, pct95PressSeen, maxPressSeen; unsigned char sensAwakeLevel, humidityLevel, EPRLevel; unsigned char CPAPpressSet, flags; quint16 c1, c2, c3, c4; // quint16 d1, d2, d3; unsigned char d1, d2, d3, d4, d5, d6; int usage; QDate date; int nblock = 0; // Go through blocks of data until end marker is found do { nblock++; in >> ts; if (ts == 0xffffffff) { #ifdef DEBUGSS qDebug() << "SS SUM 0xffffffff terminator found at block" << nblock; #endif break; } if ((ts & 0xffff) == 0xfafe) { #ifdef DEBUGSS qDebug() << "SS SUM 0xfafa terminator found at block" << nblock; #endif break; } ts = ssconvertDate(ts); #ifdef DEBUGSS qDebug() << "\nSS SUM Session" << nblock << "ts" << ts << QDateTime::fromSecsSinceEpoch(ts).toString("MM/dd/yyyy hh:mm:ss"); #endif // the following two quite often match in value in >> runTime; // 0x04 in >> useTime; // 0x05 usage = useTime * 360; // Convert to seconds (durations are in .1 hour intervals) in >> minPressSeen; // 0x06 in >> pct95PressSeen; // 0x07 in >> maxPressSeen; // 0x08 in >> d1; // 0x09 in >> d2; // 0x0a in >> d3; // 0x0b in >> d4; // 0x0c in >> d5; // 0x0d in >> d6; // 0x0e in >> c1; // 0x0f in >> c2; // 0x11 in >> c3; // 0x13 in >> c4; // 0x15 in >> j1; // 0x17 in >> mode; // 0x18 in >> ramp; // 0x19 in >> x1; // 0x1a in >> x2; // 0x1b in >> CPAPpressSet; // 0x1c in >> minPressSet; in >> maxPressSet; in >> sensAwakeLevel; in >> humidityLevel; in >> EPRLevel; in >> flags; // soak up unknown stuff to apparent end of data for the day unsigned char s [5]; for (unsigned int i=0; i < sizeof(s); i++) in >> s[i]; #ifdef DEBUGSS qDebug() << "\nRuntime" << runTime << "useTime" << useTime << (runTime!=useTime?"****runTime != useTime":"") << "\nPressure Min"<SessionExists(ts)) { Session *sess = new Session(mach, ts); sess->really_set_first(qint64(ts) * 1000L); sess->really_set_last(qint64(ts + usage) * 1000L); sess->SetChanged(true); SessDate.insert(date, sess); if ((maxPressSeen == CPAPpressSet) && (pct95PressSeen == CPAPpressSet)) { sess->settings[CPAP_Mode] = (int)MODE_CPAP; sess->settings[CPAP_Pressure] = CPAPpressSet / 10.0; } else { sess->settings[CPAP_Mode] = (int)MODE_APAP; sess->settings[CPAP_PressureMin] = minPressSet / 10.0; sess->settings[CPAP_PressureMax] = maxPressSet / 10.0; } if (EPRLevel == 0) sess->settings[SS_EPR] = 0; // Show EPR off else { sess->settings[SS_EPRLevel] = EPRLevel; sess->settings[SS_EPR] = 1; } sess->settings[SS_Humidity] = humidityLevel; sess->settings[SS_Ramp] = ramp; if (flags & 0x04) sess->settings[SS_SensAwakeLevel] = sensAwakeLevel / 10.0; else sess->settings[SS_SensAwakeLevel] = 0; sess->settings[CPAP_PresReliefMode] = PR_EPR; Sessions[ts] = sess; addSession(sess); } } while (!in.atEnd()); return true; } //////////////////////////////////////////////////////////////////////////////////////////// // Open Detail record contains list of sessions and pressure, leak, and event flags //////////////////////////////////////////////////////////////////////////////////////////// bool SleepStyleLoader::OpenDetail(Machine *mach, const QString & filename) { Q_UNUSED(mach); #ifdef DEBUGSS qDebug() << "SS DET Opening Detail" << filename; #endif QByteArray header; QFile file(filename); if (!file.open(QFile::ReadOnly)) { qWarning() << "SS DET Couldn't open" << filename; return false; } header = file.read(0x200); if (header.size() != 0x200) { qWarning() << "SS DET short file" << filename; file.close(); return false; } // Header is terminated by ';' at 0x1ff unsigned char hterm = 0x3b; if (hterm != header[0x1ff]) { file.close(); qWarning() << "SS DET Header missing ';' terminator" << filename; return false; } QTextStream htxt(&header); QString h1, version, fname, serial, model, type, unknownident; htxt >> h1; htxt >> version; htxt >> fname; htxt >> serial; htxt >> model; //TODO: Should become Series in machine info??? htxt >> type; // SPSAAN etc with 4th character being A (Auto) or C (CPAP) htxt >> unknownident; // Constant, but has different value when version number is different. #ifdef DEBUGSS qDebug() << "SS DET file header" << h1 << version << fname << serial << model << type << unknownident; #endif // Read session indices QByteArray index = file.read(0x800); if (index.size()!=0x800) { // faulty file.. qWarning() << "SS DET file short index block"; file.close(); return false; } QDataStream in(index); quint32 ts; in.setVersion(QDataStream::Qt_4_6); in.setByteOrder(QDataStream::LittleEndian); QVector times; QVector start; QVector records; quint16 strt; quint8 recs; quint16 unknownIndex; int totalrecs = 0; do { // Read timestamp for session and check for end of data signal in >> ts; if (ts == 0xffffffff) break; if ((ts & 0xffff) == 0xfafe) break; ts = ssconvertDate(ts); in >> strt; in >> recs; in >> unknownIndex; totalrecs += recs; // Number of data records for this session #ifdef DEBUGSS qDebug().noquote() << "SS DET block timestamp" << ts << QDateTime::fromSecsSinceEpoch(ts).toString("MM/dd/yyyy hh:mm:ss") << "start" << strt << "records" << recs << "unknown" << unknownIndex; #endif if (Sessions.contains(ts)) { times.push_back(ts); start.push_back(strt); records.push_back(recs); } else qDebug() << "SS DET session not found" << ts << QDateTime::fromSecsSinceEpoch(ts).toString("MM/dd/yyyy hh:mm:ss") << "start" << strt << "records" << recs << "unknown" << unknownIndex;; } while (!in.atEnd()); QByteArray databytes = file.readAll(); file.close(); in.setVersion(QDataStream::Qt_4_6); in.setByteOrder(QDataStream::BigEndian); // 7 (was 5) byte repeating patterns quint8 *data = (quint8 *)databytes.data(); qint64 ti; quint8 pressure, leak, a1, a2, a3, a4, a5, a6; Q_UNUSED(leak) // quint8 sa1, sa2; // The two sense awake bits per 2 minutes SessionID sessid; Session *sess; int idx; for (int r = 0; r < start.size(); r++) { sessid = times[r]; sess = Sessions[sessid]; ti = qint64(sessid) * 1000L; sess->really_set_first(ti); long PRSessCount = 0; //fastleak EventList *LK = sess->AddEventList(CPAP_LeakTotal, EVL_Event, 1); EventList *PR = sess->AddEventList(CPAP_Pressure, EVL_Event, 0.1F); EventList *A = sess->AddEventList(CPAP_AllApnea, EVL_Event); EventList *H = sess->AddEventList(CPAP_Hypopnea, EVL_Event); EventList *FL = sess->AddEventList(CPAP_FlowLimit, EVL_Event); EventList *SA = sess->AddEventList(CPAP_SensAwake, EVL_Event); // EventList *CA = sess->AddEventList(CPAP_ClearAirway, EVL_Event); // EventList *UA = sess->AddEventList(CPAP_Apnea, EVL_Event); // For testing to determine which bit is for which event type: // EventList *UF1 = sess->AddEventList(CPAP_UserFlag1, EVL_Event); // EventList *UF2 = sess->AddEventList(CPAP_UserFlag2, EVL_Event); unsigned stidx = start[r]; int rec = records[r]; idx = stidx * 21; // Each record has three blocks of 7 bytes for 21 bytes total quint8 bitmask; for (int i = 0; i < rec; ++i) { for (int j = 0; j < 3; ++j) { pressure = data[idx]; PR->AddEvent(ti+120000, pressure); PRSessCount++; #ifdef DEBUGSS leak = data[idx + 1]; #endif /* fastleak LK->AddEvent(ti+120000, leak); */ // Comments below from MW. Appear not to be accurate a1 = data[idx + 2]; // [0..5] Obstructive flag, [6..7] Unknown a2 = data[idx + 3]; // [0..5] Hypopnea, [6..7] Unknown a3 = data[idx + 4]; // [0..5] Flow Limitation, [6..7] Unknown a4 = data[idx + 5]; // [0..5] UF1, [6..7] Unknown a5 = data[idx + 6]; // [0..5] UF2, [6..7] Unknown // SensAwake bits are in the first two bits of the last three data fields // TODO: Confirm that the bits are in the right order a6 = (a3 >> 6) << 4 | ((a4 >> 6) << 2) | (a5 >> 6); bitmask = 1; for (int k = 0; k < 6; k++) { // There are 6 flag sets per 2 minutes // TODO: Modify if all four channels are to be reported separately if (a1 & bitmask) { A->AddEvent(ti+60000, 0); } // Grouped by F&P as A if (a2 & bitmask) { A->AddEvent(ti+60000, 0); } // Grouped by F&P as A if (a3 & bitmask) { H->AddEvent(ti+60000, 0); } // Grouped by F&P as H if (a4 & bitmask) { H->AddEvent(ti+60000, 0); } // Grouped by F&P as H if (a5 & bitmask) { FL->AddEvent(ti+60000, 0); } if (a6 & bitmask) { SA->AddEvent(ti+60000, 0); } bitmask = bitmask << 1; ti += 20000L; // Increment 20 seconds } #ifdef DEBUGSS // Debug print non-zero flags // See if extra bits from the first two fields are used at any time (see debug later) quint8 a7 = ((a1 >> 6) << 2) | (a2 >> 6); if (a1 != 0 || a2 != 0 || a3 != 0 || a4 != 0 || a5 != 0 || a6 != 0 || a7 != 0) { qDebug() << "SS DET events" << QDateTime::fromSecsSinceEpoch(ti/1000).toString("MM/dd/yyyy hh:mm:ss") << "pressure" << pressure << "leak" << leak << "flags" << a1 << a2 << a3 << a4 << a5 << a6 << "unknown" << a7; } #endif idx += 7; //was 5; } } #ifdef DEBUGSS qDebug() << "SS DET pressure events" << PR->count() << "prSessVount" << PRSessCount << "beginning" << QDateTime::fromSecsSinceEpoch(ti/1000).toString("MM/dd/yyyy hh:mm:ss"); #endif // Update indexes, process waveform and perform flagging sess->setSummaryOnly(false); sess->UpdateSummaries(); // sess->really_set_last(ti-360000L); // sess->SetChanged(true); // addSession(sess,profile); } return 1; } ChannelID SleepStyleLoader::PresReliefMode() { return SS_EPR; } ChannelID SleepStyleLoader::PresReliefLevel() { return SS_EPRLevel; } void SleepStyleLoader::initChannels() { using namespace schema; Channel * chan = nullptr; channel.add(GRP_CPAP, chan = new Channel(SS_SensAwakeLevel = 0xf305, SETTING, MT_CPAP, SESSION, "SensAwakeLevel-ss", QObject::tr("SensAwake level"), QObject::tr("SensAwake level"), QObject::tr("SensAwake"), STR_UNIT_CMH2O, DEFAULT, Qt::black)); chan->addOption(0, STR_TR_Off); channel.add(GRP_CPAP, chan = new Channel(SS_EPR = 0xf306, SETTING, MT_CPAP, SESSION, "EPR-ss", QObject::tr("EPR"), QObject::tr("Expiratory Relief"), QObject::tr("EPR"), "", DEFAULT, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(SS_EPRLevel = 0xf307, SETTING, MT_CPAP, SESSION, "EPRLevel-ss", QObject::tr("EPR Level"), QObject::tr("Expiratory Relief Level"), QObject::tr("EPR Level"), STR_UNIT_CMH2O, INTEGER, Qt::black)); chan->addOption(0, STR_TR_Off); channel.add(GRP_CPAP, chan = new Channel(SS_Ramp = 0xf308, SETTING, MT_CPAP, SESSION, "Ramp-ss", QObject::tr("Ramp"), QObject::tr("Ramp"), QObject::tr("Ramp"), "", DEFAULT, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(SS_Humidity = 0xf309, SETTING, MT_CPAP, SESSION, "Humidity-ss", QObject::tr("Humidity"), QObject::tr("Humidity"), QObject::tr("Humidity"), "", INTEGER, Qt::black)); chan->addOption(0, STR_TR_Off); } bool sleepstyle_initialized = false; void SleepStyleLoader::Register() { if (sleepstyle_initialized) { return; } qDebug() << "Registering F&P Sleepstyle Loader"; RegisterLoader(new SleepStyleLoader()); //InitModelMap(); sleepstyle_initialized = true; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/sleepstyle_loader.h000066400000000000000000000105531417327530600252020ustar00rootroot00000000000000/* SleepLib Fisher & Paykel SleepStyle Loader Implementation * * Copyright (c) 2020-2022 The Oscar Team (info@oscar-team.org) * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef SLEEPSTYLE_LOADER_H #define SLEEPSTYLE_LOADER_H #include #include "SleepLib/machine.h" #include "SleepLib/machine_loader.h" #include "SleepLib/profiles.h" //******************************************************************************************** /// IMPORTANT!!! //******************************************************************************************** // Please INCREMENT the following value when making changes to this loaders implementation. // const int sleepstyle_data_version = 1; // //******************************************************************************************** /*! \class SleepStyle \brief F&P SleepStyle customized machine object */ class SleepStyle: public CPAP { public: SleepStyle(Profile *, MachineID id = 0); virtual ~SleepStyle(); }; const int sleepstyle_load_buffer_size = 1024 * 1024; extern ChannelID SS_SensAwakeLevel, SS_EPR, SS_EPRLevel, SS_Ramp, SS_Humidity; const QString sleepstyle_class_name = STR_MACH_SleepStyle; /*! \class SleepStyleLoader \brief Loader for Fisher & Paykel SleepStyle data This is only relatively recent addition and still needs more work */ class SleepStyleLoader : public CPAPLoader { Q_OBJECT public: SleepStyleLoader(); virtual ~SleepStyleLoader(); //! \brief Detect if the given path contains a valid Folder structure virtual bool Detect(const QString & path); //! \brief Scans path for F&P SleepStyle data signature, and Loads any new data virtual int Open(const QString & path); int OpenMachine(Machine *mach, const QString & path, const QString & ssPath); bool OpenSummary(Machine *mach, const QString & path); bool OpenDetail(Machine *mach, const QString & path); // bool OpenFLW(Machine *mach, const QString & filename); bool OpenRealTime(Machine *mach, const QString & fname, const QString & filename); //! \brief Returns SleepLib database version of this F&P SleepStyle loader virtual int Version() { return sleepstyle_data_version; } //! \brief Returns the machine class name of this CPAP machine, "SleepStyle" virtual const QString & loaderName() { return sleepstyle_class_name; } // ! \brief Creates a machine object, indexed by serial number //Machine *CreateMachine(QString serial); QString getSerialPath () {return serialPath;} void setSerialPath (QString sp) {serialPath = sp;} bool backupData (Machine * mach, const QString & path); SessionID findSession (SessionID sid); void initChannels(); virtual MachineInfo newInfo() { return MachineInfo(MT_CPAP, 0, sleepstyle_class_name, QObject::tr("Fisher & Paykel"), QString(), QString(), QString(), QObject::tr("SleepStyle"), QDateTime::currentDateTime(), sleepstyle_data_version); } //! \brief Registers this MachineLoader with the master list, so F&P Icon data can load static void Register(); //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Now for some CPAPLoader overrides //////////////////////////////////////////////////////////////////////////////////////////////////////////// // virtual QString presRelType() { return QObject::tr("EPR"); } virtual QString PresReliefLabel() { return QObject::tr("EPR: "); } virtual ChannelID PresReliefMode(); virtual ChannelID PresReliefLevel(); //////////////////////////////////////////////////////////////////////////////////////////////////////////// protected: // QDateTime readFPDateTime(quint8 *data); QString last; QHash MachList; QMap Sessions; QMultiMap SessDate; QString serialPath; // fully qualified path to the input data, ...SDCard.../FPHCARE/ICON/serial // QString serial; // Serial number bool rebuild_from_backups = false; bool create_backups = true; bool calc_leaks = true; float lpm4, lpm20; // Leak per minute at 4 and 20 cmH20 unsigned char *m_buffer; }; #endif // SLEEPSTYLE_LOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/somnopose_loader.cpp000066400000000000000000000203501417327530600253620ustar00rootroot00000000000000/* SleepLib Somnopose Loader Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ //******************************************************************************************** // Please only INCREMENT the somnopose_data_version in somnopose_loader.h when making changes // that change loader behaviour or modify channels in a manner that fixes old data imports. // Note that changing the data version will require a reimport of existing data for which OSCAR // does not keep a backup - so it should be avoided if possible. // i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** #include #include #include "somnopose_loader.h" #include "SleepLib/machine.h" SomnoposeLoader::SomnoposeLoader() { m_type = MT_POSITION; } SomnoposeLoader::~SomnoposeLoader() { } int SomnoposeLoader::OpenFile(const QString & filename) { QFile file(filename); if (filename.toLower().endsWith(".csv")) { if (!file.open(QFile::ReadOnly)) { qDebug() << "Couldn't open Somnopose data file" << filename; return -1; } } else { return -1; } qDebug() << "Opening file" << filename; QTextStream ts(&file); // Read header line and determine order of fields QString hdr = ts.readLine(); QStringList headers = hdr.split(","); QString model = ""; QString serial = ""; int col_inclination = -1, col_orientation = -1, col_timestamp = -1, col_movement = -1; int hdr_size = headers.size(); for (int i = 0; i < hdr_size; i++) { // Optional header model= if (headers.at(i).startsWith("model=", Qt::CaseInsensitive)) { model=headers.at(i).split("=")[1]; } // Optional header serial= if (headers.at(i).startsWith("serial=", Qt::CaseInsensitive)) { serial=headers.at(i).split("=")[1]; } if (headers.at(i).compare("timestamp", Qt::CaseInsensitive) == 0) { col_timestamp = i; } if (headers.at(i).compare("inclination", Qt::CaseInsensitive) == 0) { col_inclination = i; } if (headers.at(i).compare("orientation", Qt::CaseInsensitive) == 0) { col_orientation = i; } if (headers.at(i).compare("movement", Qt::CaseInsensitive) == 0) { col_movement = i; } } // Check we have all fields available if (col_timestamp < 0) { qDebug() << "Header missing timestamp"; return -1; } if ((col_inclination < 0) && (col_orientation < 0) && (col_movement < 0)) { qDebug() << "Header missing all of inclination, orientation, movement (at least one must be present)"; return -1; } QDateTime epoch(QDate(2001, 1, 1)); qint64 ep = qint64(epoch.toTime_t()+epoch.offsetFromUtc()) * 1000, time=0; qDebug() << "Epoch starts at" << epoch.toString(); double timestamp, orientation=0, inclination=0, movement=0; QString data; QStringList fields; bool ok, orientation_ok, inclination_ok, movement_ok; bool first = true, skip_session = false; int session_count = 0; MachineInfo info = newInfo(); info.model = model; info.serial = serial; Machine *mach = p_profile->CreateMachine(info); Session *sess = nullptr; SessionID sid; EventList *ev_orientation = nullptr, *ev_inclination = nullptr, *ev_movement = nullptr; while (!(data = ts.readLine()).isEmpty()) { fields = data.split(","); if (fields.size() >= col_timestamp && fields[col_timestamp] == "-") { // Flag end of session... if (sess) { if (ev_orientation) { sess->setMin(POS_Orientation, ev_orientation->Min()); sess->setMax(POS_Orientation, ev_orientation->Max()); } if (ev_inclination) { sess->setMin(POS_Inclination, ev_inclination->Min()); sess->setMax(POS_Inclination, ev_inclination->Max()); } if (ev_movement) { sess->setMin(POS_Movement, ev_movement->Min()); sess->setMax(POS_Movement, ev_movement->Max()); } sess->really_set_last(time); sess->SetChanged(true); mach->AddSession(sess); session_count++; } // Prepare for potential next session... sess = nullptr; ev_orientation = ev_inclination = ev_movement = nullptr; first = true; skip_session = false; continue; } if (skip_session) { continue; } if (fields.size() < hdr_size) { // missing fields.. skip this record continue; } timestamp = fields[col_timestamp].toDouble(&ok); if (!ok) { continue; } orientation_ok = inclination_ok = movement_ok = false; if (col_orientation >= 0) { orientation = fields[col_orientation].toDouble(&orientation_ok); } if (col_inclination >= 0) { inclination = fields[col_inclination].toDouble(&inclination_ok); } if (col_movement >= 0) { movement = fields[col_movement].toDouble(&movement_ok); } if (!orientation_ok && !inclination_ok && !movement_ok) { continue; } // convert to milliseconds since epoch time = (timestamp * 1000.0) + ep; if (first) { first = false; sid = time / 1000; qDebug() << "First timestamp is" << QDateTime::fromMSecsSinceEpoch(time).toString(); if (mach->SessionExists(sid)) { qDebug() << "File " << filename << " session " << sid << " already loaded... skipping"; // Continue processing file to allow for case where new sessions are added to a file skip_session = true; continue; } sess = new Session(mach, sid); sess->really_set_first(time); if (col_orientation >= 0) { ev_orientation = sess->AddEventList(POS_Orientation, EVL_Event, 1, 0, 0, 0); } if (col_inclination >= 0) { ev_inclination = sess->AddEventList(POS_Inclination, EVL_Event, 1, 0, 0, 0); } if (col_movement >= 0) { ev_movement = sess->AddEventList(POS_Movement, EVL_Event, 1, 0, 0, 0); } } sess->set_last(time); if (ev_orientation && orientation_ok) { ev_orientation->AddEvent(time, orientation); } if (ev_inclination && inclination_ok) { ev_inclination->AddEvent(time, inclination); } if (ev_movement && movement_ok) { ev_movement->AddEvent(time, movement); } } if (sess) { if (ev_orientation) { sess->setMin(POS_Orientation, ev_orientation->Min()); sess->setMax(POS_Orientation, ev_orientation->Max()); } if (ev_inclination) { sess->setMin(POS_Inclination, ev_inclination->Min()); sess->setMax(POS_Inclination, ev_inclination->Max()); } if (ev_movement) { sess->setMin(POS_Movement, ev_movement->Min()); sess->setMax(POS_Movement, ev_movement->Max()); } sess->really_set_last(time); sess->SetChanged(true); mach->AddSession(sess); session_count++; } if (session_count) { mach->Save(); // Adding these to hopefully make data persistent... mach->SaveSummaryCache(); p_profile->StoreMachines(); } return session_count; } static bool somnopose_initialized = false; void SomnoposeLoader::Register() { if (somnopose_initialized) { return; } qDebug("Registering SomnoposeLoader"); RegisterLoader(new SomnoposeLoader()); //InitModelMap(); somnopose_initialized = true; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/somnopose_loader.h000066400000000000000000000030361417327530600250310ustar00rootroot00000000000000/* SleepLib Somnopose Loader Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef SOMNOPOSELOADER_H #define SOMNOPOSELOADER_H #include "SleepLib/machine_loader.h" const QString somnopose_class_name = "Somnopose"; const int somnopose_data_version = 1; /*! \class SomnoposeLoader \brief Unfinished stub for loading Somnopose Positional CSV data */ class SomnoposeLoader : public MachineLoader { public: SomnoposeLoader(); virtual ~SomnoposeLoader(); virtual bool Detect(const QString & path) { Q_UNUSED(path); return false; } // bypass autoscanner virtual int Open(const QString & path) { Q_UNUSED(path); return 0; } // Only for CPAP virtual int OpenFile(const QString & filename); virtual QStringList getNameFilter() { return QStringList("Somnopose CSV File (*.csv)"); } static void Register(); virtual int Version() { return somnopose_data_version; } virtual const QString &loaderName() { return somnopose_class_name; } virtual MachineInfo newInfo() { return MachineInfo(MT_POSITION, 0, somnopose_class_name, QObject::tr("Somnopose"), QString(), QString(), QString(), QObject::tr("Somnopose Software"), QDateTime::currentDateTime(), somnopose_data_version); } //Machine *CreateMachine(); protected: private: }; #endif // SOMNOPOSELOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/viatom_loader.cpp000066400000000000000000000414371417327530600246500ustar00rootroot00000000000000/* SleepLib Viatom Loader Implementation * * Copyright (c) 2020-2022 The OSCAR Team * (Initial importer written by dave madden ) * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ //******************************************************************************************** // Please only INCREMENT the viatom_data_version in viatom_loader.h when making changes // that change loader behaviour or modify channels in a manner that fixes old data imports. // Note that changing the data version will require a reimport of existing data for which OSCAR // does not keep a backup - so it should be avoided if possible. // i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** #include #include #include #include #include "viatom_loader.h" #include "SleepLib/machine.h" // TODO: Merge this with PRS1 macros and generalize for all loaders. #define SESSIONID m_session->session() #define UNEXPECTED_VALUE(SRC, VALS) { \ QString message = QString("%1:%2: %3 = %4 != %5").arg(__func__).arg(__LINE__).arg(#SRC).arg(SRC).arg(VALS); \ qWarning() << SESSIONID << message; \ s_unexpectedMessages += message; \ } #define CHECK_VALUE(SRC, VAL) if ((SRC) != (VAL)) UNEXPECTED_VALUE(SRC, VAL) #define CHECK_VALUES(SRC, VAL1, VAL2) if ((SRC) != (VAL1) && (SRC) != (VAL2)) UNEXPECTED_VALUE(SRC, #VAL1 " or " #VAL2) // for more than 2 values, just write the test manually and use UNEXPECTED_VALUE if it fails static QSet s_unexpectedMessages; bool ViatomLoader::Detect(const QString & path) { // This is only used for CPAP machines, when detecting CPAP cards. qDebug() << "ViatomLoader::Detect(" << path << ")"; return false; } int ViatomLoader::Open(const QStringList & paths) { qDebug() << "ViatomLoader::Open(" << paths.join("; ") << ")"; m_mach = nullptr; int imported = 0; int found = 0; s_unexpectedMessages.clear(); int size = paths.size(); for (int i=0; i < size; i++) { if (isAborted()) { break; } // This filename has already been filtered by QFileDialog. int ok = OpenFile(paths[i]); if (ok > 0) { imported++; } else if (ok < 0) { // Stop on error... break; } found++; emit setProgressValue(i+1); QCoreApplication::processEvents(); } if (!found) { return -1; } Machine* mach = m_mach; if (imported && mach == nullptr) qWarning() << "No machine record created?"; if (mach) { qDebug() << "Imported" << imported << "sessions"; mach->Save(); mach->SaveSummaryCache(); p_profile->StoreMachines(); } if (mach && s_unexpectedMessages.count() > 0 && p_profile->session->warnOnUnexpectedData()) { // Compare this to the list of messages previously seen for this machine // and only alert if there are new ones. QSet newMessages = s_unexpectedMessages - mach->previouslySeenUnexpectedData(); if (newMessages.count() > 0) { // TODO: Rework the importer call structure so that this can become an // emit statement to the appropriate import job. QMessageBox::information(QApplication::activeWindow(), QObject::tr("Untested Data"), QObject::tr("Your Viatom device generated data that OSCAR has never seen before.") +"\n\n"+ QObject::tr("The imported data may not be entirely accurate, so the developers would like a copy of your Viatom files to make sure OSCAR is handling the data correctly.") ,QMessageBox::Ok); mach->previouslySeenUnexpectedData() += newMessages; } } return found; } int ViatomLoader::OpenFile(const QString & filename) { Machine* mach = nullptr; bool existing = false; Session* sess = ParseFile(filename, &existing); if (sess) { SaveSessionToDatabase(sess); mach = sess->machine(); m_mach = mach; return 1; } return existing ? 0 : -1; // -1 = error } Session* ViatomLoader::ParseFile(const QString & filename, bool *existing) { if (existing) { *existing = false; } QFile file(filename); if (!file.open(QFile::ReadOnly)) { qDebug() << "Couldn't open Viatom data file" << filename; return nullptr; } ViatomFile v(file); if (v.ParseHeader() == false) { return nullptr; } MachineInfo info = newInfo(); // Check whether the enclosing folder looks like a Viatom serial number, and if so, use it. QString foldername = QFileInfo(filename).dir().dirName(); if (foldername.length() >= 9) { bool numeric; foldername.rightRef(4).toInt(&numeric); if (numeric) { info.serial = foldername; } } Machine *mach = p_profile->CreateMachine(info); if (mach->SessionExists(v.sessionid())) { // Skip already imported session //qDebug() << filename << "session already exists, skipping" << v.sessionid(); if (existing) { // Inform the caller (if they are interested) that this session was already imported *existing = true; } return nullptr; } qint64 time_ms = v.timestamp(); m_session = new Session(mach, v.sessionid()); m_session->set_first(time_ms); QList records = v.ReadData(); m_step = v.duration() / records.size() * 1000L; // Import data for (auto & rec : records) { if (rec.oximetry_invalid) { EndEventList(OXI_Pulse, time_ms); EndEventList(OXI_SPO2, time_ms); } else { // Viatom advertises a range of 30 - 250 bpm. if (rec.hr < 30 || rec.hr > 250) { UNEXPECTED_VALUE(rec.hr, "30-250"); } AddEvent(OXI_Pulse, time_ms, rec.hr); if (rec.spo2 == 0xFF) { // When the readings fall below 61%, Viatom devices record 0xFF for SpO2. // The official software discards these readings. // TODO: Consider whether to import these as 60% since they reflect hypoxia. EndEventList(OXI_SPO2, time_ms); //qDebug() << "<61% at" << QDateTime::fromMSecsSinceEpoch(time_ms); } else { // Viatom advertises (and graphs) a range of 70% - 99%, but apparently records down to 61%. // The official software graphs 61%-70% as 70%. // TODO: Consider whether we should import 61%-70% as 70% to match the official reports. if (rec.spo2 < 61 || rec.spo2 > 99) { UNEXPECTED_VALUE(rec.spo2, "61-99%"); } AddEvent(OXI_SPO2, time_ms, rec.spo2); } } AddEvent(POS_Movement, time_ms, rec.motion); time_ms += m_step; } EndEventList(OXI_Pulse, time_ms); EndEventList(OXI_SPO2, time_ms); EndEventList(POS_Movement, time_ms); m_session->set_last(time_ms); return m_session; } void ViatomLoader::SaveSessionToDatabase(Session* sess) { Machine* mach = sess->machine(); sess->SetChanged(true); mach->AddSession(sess); } void ViatomLoader::AddEvent(ChannelID channel, qint64 t, EventDataType value) { EventList* C = m_importChannels[channel]; if (C == nullptr) { C = m_session->AddEventList(channel, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, m_step); Q_ASSERT(C); // Once upon a time AddEventList could return nullptr, but not any more. m_importChannels[channel] = C; } // Add the event C->AddEvent(t, value); m_importLastValue[channel] = value; } void ViatomLoader::EndEventList(ChannelID channel, qint64 /*t*/) { EventList* C = m_importChannels[channel]; if (C != nullptr) { // The below would be needed for square charts if the first sample represents // the 4 seconds following the starting timestamp: //C->AddEvent(t, m_importLastValue[channel]); // Mark this channel's event list as ended. m_importChannels[channel] = nullptr; } } QStringList ViatomLoader::getNameFilter() { // Sometimes the files have a SleepU_ or O2Ring_ prefix. // Sometimes they have punctuation in the timestamp. // Note that ":" is not allowed on macOS, so Mac users will need to rename their files in order to select and import them. return QStringList({"*20[0-5][0-9][01][0-9][0-3][0-9][012][0-9][0-5][0-9][0-5][0-9]", "*20[0-5][0-9]-[01][0-9]-[0-3][0-9] [012][0-9]:[0-5][0-9]:[0-5][0-9]" }); } static bool viatom_initialized = false; void ViatomLoader::Register() { if (!viatom_initialized) { qDebug("Registering ViatomLoader"); RegisterLoader(new ViatomLoader()); //InitModelMap(); viatom_initialized = true; } } // =============================================================================================== #undef SESSIONID #define SESSIONID this->m_sessionid ViatomFile::ViatomFile(QFile & file) : m_file(file) { } bool ViatomFile::ParseHeader() { static const int HEADER_SIZE = 40; QByteArray data = m_file.read(HEADER_SIZE); if (data.size() < HEADER_SIZE) { qDebug() << m_file.fileName() << "too short for a Viatom data file"; return false; } const unsigned char* header = (const unsigned char*) data.constData(); int sig = header[0] | (header[1] << 8); int year = header[2] | (header[3] << 8); int month = header[4]; int day = header[5]; int hour = header[6]; int min = header[7]; int sec = header[8]; switch (sig) { case 0x0003: case 0x0005: // CheckMe O2 Max break; default: qDebug() << m_file.fileName() << "invalid signature for Viatom data file" << sig; return false; break; } m_sig = sig; CHECK_VALUES(m_sig, 3, 5); if ((year < 2015 || year > 2059) || (month < 1 || month > 12) || (day < 1 || day > 31) || (hour > 23) || (min > 59) || (sec > 59)) { qDebug() << m_file.fileName() << "invalid timestamp in Viatom data file"; return false; } // It's unclear what the starting timestamp represents: is it the time at which // the device starts measuring data, and the first sample is 4s after that? Or // is the starting timestamp the time at which the first 4s average is reported // (and the first 4 seconds being average precede the starting timestamp)? // // If the former, then the chart draws the first sample too early (right at the // starting timestamp). Technically these should probably be square charts, but // the code currently forces them to be non-square. QDateTime data_timestamp = QDateTime(QDate(year, month, day), QTime(hour, min, sec)); QString date_string = QFileInfo(m_file).fileName().section("_", -1); // Strip any SleepU_ etc. prefix. QString format_string = "yyyyMMddHHmmss"; if (date_string.contains(":")) { format_string = "yyyy-MM-dd HH:mm:ss"; } QDateTime filename_timestamp = QDateTime::fromString(date_string, format_string); if (filename_timestamp.isValid()) { if (filename_timestamp != data_timestamp) { // TODO: Once there's a better/easier way to adjust session times within OSCAR, we can remove the below. qDebug() << m_file.fileName() << "Using filename timestamp" << filename_timestamp.toString("yyyy-MM-dd HH:mm:ss") << "instead of header timestamp" << data_timestamp.toString("yyyy-MM-dd HH:mm:ss"); data_timestamp = filename_timestamp; } } else { qWarning() << m_file.fileName() << "invalid timestamp in Viatom filename"; } m_timestamp = data_timestamp.toMSecsSinceEpoch(); m_sessionid = m_timestamp / 1000L; int filesize = header[9] | (header[10] << 8) | (header[11] << 16); // possibly 32-bit CHECK_VALUE(header[12], 0); m_duration = header[13] | (header[14] << 8); // possibly 32-bit CHECK_VALUE(header[15], 0); CHECK_VALUE(header[16], 0); //int spo2_avg = header[17]; //int spo2_min = header[18]; //int spo2_3pct = header[19]; // number of events //int spo2_4pct = header[20]; // number of events //CHECK_VALUE(header[21], 0); // ??? sometimes nonzero; maybe pulse spike, not a threshold of SpO2 or pulse, not always smaller than spo2_4pct //int time_under_90pct = header[22] | (header[23] << 8); // in seconds //int events_under_90pct = header[24]; // number of distinct events //float o2_score = header[25] * 0.1; //CHECK_VALUES(header[26], 0, 4); // number of steps taken (when nonzero, only reported by some models) CHECK_VALUE(header[27], 0); CHECK_VALUE(header[28], 0); CHECK_VALUE(header[29], 0); //CHECK_VALUE(header[30], 0); // average pulse rate (when nonzero) CHECK_VALUE(header[31], 0); CHECK_VALUE(header[32], 0); CHECK_VALUE(header[33], 0); CHECK_VALUE(header[34], 0); CHECK_VALUE(header[35], 0); CHECK_VALUE(header[36], 0); CHECK_VALUE(header[37], 0); CHECK_VALUE(header[38], 0); CHECK_VALUE(header[39], 0); // Calculate timing resolution (in ms) of the data qint64 datasize = m_file.size() - HEADER_SIZE; m_record_count = datasize / RECORD_SIZE; m_resolution = m_duration / m_record_count * 1000L; if (m_resolution == 2000 && m_sig == 3) { // Interestingly the file size in the header corresponds the number of // distinct samples. These files actually double-report each sample! // So this resolution isn't really the real one. The importer should // calculate resolution from duration / record count after reading the // records, which will be deduplicated. CHECK_VALUE(filesize, ((m_file.size() - HEADER_SIZE) / 2) + HEADER_SIZE); } else { CHECK_VALUE(filesize, m_file.size()); } CHECK_VALUES(m_resolution, 2000, 4000); if (true) { // TODO: We need CheckMe sample data where this doesn't hold true. CHECK_VALUE(datasize % RECORD_SIZE, 0); CHECK_VALUE(m_duration % m_record_count, 0); } //qDebug().noquote() << m_file.fileName() << ts(m_timestamp) << dur(m_duration * 1000L) << ":" << m_record_count << "records @" << m_resolution << "ms"; return true; } QList ViatomFile::ReadData() { QByteArray data = m_file.readAll(); QDataStream in(data); in.setByteOrder(QDataStream::LittleEndian); QList records; // Read all Pulse, SPO2 and Motion data do { ViatomFile::Record rec; in >> rec.spo2 >> rec.hr >> rec.oximetry_invalid >> rec.motion >> rec.vibration; CHECK_VALUES(rec.oximetry_invalid, 0, 0xFF); if (rec.vibration) { CHECK_VALUES(rec.vibration, 0x40, 0x80); // 0x40 or 0x80 when vibration is triggered } // Invalid readings indicate any interruption in the measurements, whether // transitory (e.g. due to movement) or when the device is removed at the end of a session. if (rec.oximetry_invalid == 0xFF) { CHECK_VALUE(rec.spo2, 0xFF); CHECK_VALUE(rec.hr, 0xFF); } records.append(rec); } while (records.size() < m_record_count); // It turns out 2s files are actually just double-reported samples on older models! if (m_resolution == 2000) { QList dedup; bool all_are_duplicated = true; CHECK_VALUE(records.size() % 2, 0); for (int i = 0; i < records.size(); i += 2) { auto & a = records.at(i); auto & b = records.at(i+1); if (a.spo2 != b.spo2 || a.hr != b.hr || a.oximetry_invalid != b.oximetry_invalid || a.motion != b.motion || a.vibration != b.vibration) { all_are_duplicated = false; break; } dedup.append(a); } if (m_sig == 5) { // Confirm that CheckMe O2 Max is a true 2s sample rate. CHECK_VALUE(all_are_duplicated, false); } else { // Confirm that older models are actually a 4s sample rate. CHECK_VALUE(m_sig, 3); CHECK_VALUE(all_are_duplicated, true); } if (all_are_duplicated) { // Return the deduplicated list. records = dedup; } } if (m_sig == 5) { CHECK_VALUES(duration() / records.size(), 2, 4); // We've seen 2s and 4s resolution. } else { CHECK_VALUE(duration() / records.size(), 4); // We've only seen 4s true resolution so far. } return records; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/viatom_loader.h000066400000000000000000000050461417327530600243110ustar00rootroot00000000000000/* SleepLib Viatom Loader Header * * Copyright (c) 2020-2022 The OSCAR Team * (Initial importer written by dave madden ) * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef VIATOMLOADER_H #define VIATOMLOADER_H #include "SleepLib/machine_loader.h" const QString viatom_class_name = "Viatom"; const int viatom_data_version = 2; /*! \class ViatomLoader \brief Unfinished stub for loading Viatom Sleep Ring / Wrist Pulse Oximeter data */ class ViatomLoader : public MachineLoader { public: ViatomLoader() { m_type = MT_OXIMETER; } virtual ~ViatomLoader() { } virtual bool Detect(const QString & path); virtual int Open(const QString & path) { Q_UNUSED(path); return 0; } // Only for CPAP virtual int Open(const QStringList & paths); Session* ParseFile(const QString & filename, bool *existing=0); static void Register(); virtual int Version() { return viatom_data_version; } virtual const QString &loaderName() { return viatom_class_name; } virtual MachineInfo newInfo() { return MachineInfo(MT_OXIMETER, 0, viatom_class_name, QObject::tr("Viatom"), QString(), QString(), QString(), QObject::tr("Viatom Software"), QDateTime::currentDateTime(), viatom_data_version); } virtual QStringList getNameFilter(); //Machine *CreateMachine(); protected: int OpenFile(const QString & filename); void SaveSessionToDatabase(Session* session); void AddEvent(ChannelID channel, qint64 t, EventDataType value); void EndEventList(ChannelID channel, qint64 t); Machine* m_mach; Session* m_session; qint64 m_step; QHash m_importChannels; QHash m_importLastValue; private: }; class ViatomFile { public: struct Record { unsigned char spo2; unsigned char hr; unsigned char oximetry_invalid; unsigned char motion; unsigned char vibration; }; ViatomFile(QFile & file); ~ViatomFile() = default; bool ParseHeader(); QList ReadData(); SessionID sessionid() const { return m_sessionid; } quint64 timestamp() const { return m_timestamp; } int duration() const { return m_duration; } protected: static const int RECORD_SIZE = 5; QFile & m_file; int m_sig; quint64 m_timestamp; int m_duration; int m_record_count; int m_resolution; SessionID m_sessionid; }; #endif // VIATOMLOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/weinmann_loader.cpp000066400000000000000000000416341417327530600251640ustar00rootroot00000000000000/* SleepLib Weinmann SOMNOsoft/Balance Loader Implementation * * Copyright (c) 2011-2018 Mark Watkins * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include "weinmann_loader.h" Weinmann::Weinmann(Profile *profile, MachineID id) : CPAP(profile, id) { } Weinmann::~Weinmann() { } WeinmannLoader::WeinmannLoader() { m_buffer = nullptr; m_type = MT_CPAP; } WeinmannLoader::~WeinmannLoader() { } bool WeinmannLoader::Detect(const QString & givenpath) { QDir dir(givenpath); if (!dir.exists()) { return false; } // Check for the settings file inside the .. folder if (!dir.exists("WM_DATA.TDF")) { return false; } return true; } int WeinmannLoader::ParseIndex(QFile & wmdata) { QByteArray xml; do { xml += wmdata.readLine(250); } while (!wmdata.atEnd()); QDomDocument index_xml("weinmann"); index_xml.setContent(xml); QDomElement docElem = index_xml.documentElement(); QDomNode n = docElem.firstChild(); index.clear(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { bool ok; int val = e.attribute("val").toInt(&ok); if (ok) { index[e.attribute("name")] = val; qDebug() << e.attribute("name") << "=" << hex << val; } } n = n.nextSibling(); } return index.size(); } const QString DayComplianceCount = "DayComplianceCount"; const QString CompOffset = "DayComplianceOffset"; const QString FlowOffset = "TID_Flow_Offset"; const QString StatusOffset = "TID_Status_Offset"; const QString PresOffset = "TID_P_Offset"; const QString AMVOffset = "TID_AMV_Offset"; const QString EventsOffset = "TID_Events_Offset"; void HighPass(char * data, int samples, float cutoff, float dt) { float *Y = new float [samples]; for (int i=0; i < samples; ++i) Y[i] = 0.0f; Y[0] = ((unsigned char *)data)[0] ; float RC = 1.0 / (cutoff * 2 * 3.1415926); float alpha = RC / (RC + dt); for (int i=1; i < samples; ++i) { float x = ((unsigned char *)data)[i] ; float x1 = ((unsigned char *)data)[i-1] ; Y[i] = alpha * (Y[i-1] + x - x1); } for (int i=0; i< samples; ++i) { data[i] = Y[i]; } delete [] Y; } int WeinmannLoader::Open(const QString & dirpath) { QString path(dirpath); path = path.replace("\\", "/"); QFile wmdata(path + "/WM_DATA.TDF"); if (!wmdata.open(QFile::ReadOnly)) { return -1; } int res = ParseIndex(wmdata); if (res < 0) return -1; MachineInfo info = newInfo(); info.serial = "141819"; Machine * mach = p_profile->CreateMachine(info); int WeekComplianceOffset = index["WeekComplianceOffset"]; int WCD_Pin_Offset = index["WCD_Pin_Offset"]; // int WCD_Pex_Offset = index["WCD_Pex_Offset"]; // int WCD_Snore_Offset = index["WCD_Snore_Offset"]; // int WCD_Lf_Offset = index["WCD_Lf_Offset"]; // int WCD_Events_Offset = index["WCD_Events_Offset"]; // int WCD_IO_Offset = index["WCD_IO_Offset"]; int comp_start = index[CompOffset]; int wccount = index["WeekComplianceCount"]; int size = WCD_Pin_Offset - WeekComplianceOffset; quint8 * weekco = new quint8 [size]; memset(weekco, 0, size); wmdata.seek(WeekComplianceOffset); wmdata.read((char *)weekco, size); unsigned char *p = weekco; for (int c=0; c < wccount; ++c) { int year = QString().sprintf("%02i%02i", p[0], p[1]).toInt(); int month = p[2]; int day = p[3]; int hour = p[5]; int minute = p[6]; int second = p[7]; QDateTime date = QDateTime(QDate(year,month,day), QTime(hour,minute,second)); quint32 ts = date.toTime_t(); if (!mach->SessionExists(ts)) { qDebug() << date; } // stores used length of data at 0x46, in 16bit integers, for IPAP, EPAP, snore, leak, // stores total length of data block at 0x66, in 16 bit integers for IPAP, EPAP, snore, leak p+=0x84; } delete [] weekco; ////////////////////////////////////////////////////////////////////// // Read Day Compliance Information.... ////////////////////////////////////////////////////////////////////// int comp_end = index[FlowOffset]; int comp_size = comp_end - comp_start; // TODO: This entire loader needs significant work. For now just // hard-code values here to make sure we don't crash in the loop below. if (comp_size < 5 * 0xd6) { qWarning() << "Weinmann loader comp_size too short:" << comp_size; return -1; } quint8 * comp = new quint8 [comp_size]; memset((char *)comp, 0, comp_size); wmdata.seek(comp_start); wmdata.read((char *)comp, comp_size); p = comp; QDateTime dt_epoch(QDate(2000,1,1), QTime(0,0,0)); //int epoch = dt_epoch.toTime_t(); //epoch = 0; float flow_sample_duration = 1000.0 / 5; float pressure_sample_duration = 1000.0 / 2; //float amv_sample_duration = 200 * 10; //int c = index[DayComplianceCount]; for (int i=0; i < 5; i++) { int year = QString().sprintf("%02i%02i", p[0], p[1]).toInt(); int month = p[2]; int day = p[3]; int hour = p[5]; int minute = p[6]; int second = p[7]; QDateTime date = QDateTime(QDate(year,month,day), QTime(hour,minute,second)); quint32 ts = date.toTime_t(); if (mach->SessionExists(ts)) continue; Session * sess = new Session(mach, ts); sess->SetChanged(true); // Flow Waveform quint32 fs = p[8] | p[9] << 8 | p[10] << 16 | p[11] << 24; quint32 fl = p[0x44] | p[0x45] << 8 | p[0x46] << 16 | p[0x47] << 24; // Status quint32 ss = p[12] | p[13] << 8 | p[14] << 16 | p[15] << 24; quint32 sl = p[0x48] | p[0x49] << 8 | p[0x4a] << 16 | p[0x4b] << 24; // Pressure quint32 ps = p[16] | p[17] << 8 | p[18] << 16 | p[19] << 24; quint32 pl = p[0x4c] | p[0x4d] << 8 | p[0x4e] << 16 | p[0x4f] << 24; // AMV quint32 ms = p[20] | p[21] << 8 | p[22] << 16 | p[23] << 24; quint32 ml = p[0x50] | p[0x51] << 8 | p[0x52] << 16 | p[0x53] << 24; // Events quint32 es = p[24] | p[25] << 8 | p[26] << 16 | p[27] << 24; quint32 er = p[0x54] | p[0x55] << 8 | p[0x56] << 16 | p[0x57] << 24; // number of records compinfo.append(CompInfo(sess, date, fs, fl, ss, sl, ps, pl, ms, ml, es, er)); int dur = fl / 5; sess->really_set_first(qint64(ts) * 1000L); sess->really_set_last(qint64(ts+dur) * 1000L); sessions[ts] = sess; // qDebug() << date << ts << dur << QString().sprintf("%02i:%02i:%02i", dur / 3600, dur/60 % 60, dur % 60); p += 0xd6; } delete [] comp; ////////////////////////////////////////////////////////////////////// // Read Flow Waveform.... ////////////////////////////////////////////////////////////////////// int flowstart = index[FlowOffset]; int flowend = index[StatusOffset]; wmdata.seek(flowstart); int flowsize = flowend - flowstart; char * data = new char [flowsize]; memset((char *)data, 0, flowsize); wmdata.read((char *)data, flowsize); float dt = 1.0 / (1000.0 / flow_sample_duration); // samples per second // Centre Waveform using High Pass Filter HighPass(data, flowsize, 0.1f, dt); ////////////////////////////////////////////////////////////////////// // Read Status.... ////////////////////////////////////////////////////////////////////// int st_start = index[StatusOffset]; int st_end = index[PresOffset]; int st_size = st_end - st_start; char * st = new char [st_size]; memset(st, 0, st_size); wmdata.seek(st_start); wmdata.read(st, st_size); ////////////////////////////////////////////////////////////////////// // Read Mask Pressure.... ////////////////////////////////////////////////////////////////////// int pr_start = index[PresOffset]; int pr_end = index[AMVOffset]; int pr_size = pr_end - pr_start; char * pres = new char [pr_size]; memset(pres, 0, pr_size); wmdata.seek(pr_start); wmdata.read(pres, pr_size); ////////////////////////////////////////////////////////////////////// // Read AMV.... ////////////////////////////////////////////////////////////////////// int mv_start = index[AMVOffset]; int mv_end = index[EventsOffset]; int mv_size = mv_end - mv_start; char * mv = new char [mv_size]; memset(mv, 0, mv_size); wmdata.seek(mv_start); wmdata.read(mv, mv_size); ////////////////////////////////////////////////////////////////////// // Read Events.... ////////////////////////////////////////////////////////////////////// int ev_start = index[EventsOffset]; int ev_end = wmdata.size(); int ev_size = ev_end - ev_start; quint8 * ev = new quint8 [ev_size]; memset((char *) ev, 0, ev_size); wmdata.seek(ev_start); wmdata.read((char *) ev, ev_size); ////////////////////////////////////////////////////////////////////// // Process sessions ////////////////////////////////////////////////////////////////////// for (int i=0; i< compinfo.size(); ++i) { const CompInfo & ci = compinfo.at(i); Session * sess = ci.session; qint64 ti = sess->first(); EventList * flow = sess->AddEventList(CPAP_FlowRate, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, flow_sample_duration); flow->AddWaveform(ti, &data[ci.flow_start], ci.flow_size, (ci.flow_size/(1000.0/flow_sample_duration)) * 1000.0); EventList * PR = sess->AddEventList(CPAP_MaskPressure, EVL_Waveform, 0.1f, 0.0, 0.0, 0.0, pressure_sample_duration); PR->AddWaveform(ti, (unsigned char *)&pres[ci.pres_start], ci.pres_size, (ci.pres_size/(1000.0/pressure_sample_duration)) * 1000.0); // Weinmann's MV graph is pretty dodgy... commenting this out and using my flow calced ones instead (the code below is mapped to snore for comparison purposes) //EventList * MV = sess->AddEventList(CPAP_Snore, EVL_Waveform, 1.0f, 0.0, 0.0, 0.0, amv_sample_duration); //MV->AddWaveform(ti, (unsigned char *)&mv[ci.amv_start], ci.amv_size, (ci.amv_size/(1000/amv_sample_duration)) * 1000L); // EventList * L = sess->AddEventList(CPAP_Leak, EVL_Event); // EventList * S = sess->AddEventList(CPAP_Snore, EVL_Event); EventList * OA = sess->AddEventList(CPAP_Obstructive, EVL_Event); EventList * A = sess->AddEventList(CPAP_Apnea, EVL_Event); EventList * H = sess->AddEventList(CPAP_Hypopnea, EVL_Event); EventList * FL = sess->AddEventList(CPAP_FlowLimit, EVL_Event); // EventList * VS = sess->AddEventList(CPAP_VSnore, EVL_Event); quint64 tt = ti; quint64 step = sess->length() / ci.event_recs; unsigned char *p = &ev[ci.event_start]; for (quint32 j=0; j < ci.event_recs; ++j) { QDate evdate = ci.time.date(); QTime evtime(p[1], p[2], p[3]); if (evtime < ci.time.time()) { evdate = evdate.addDays(1); } quint64 ts = QDateTime(evdate, evtime).toMSecsSinceEpoch(); // I think p[0] is amount of flow restriction.. unsigned char evcode = p[0]; EventStoreType data = p[4] | p[5] << 8; if (evcode == '@') { OA->AddEvent(ts,data/10.0); } else if (evcode =='A') { A->AddEvent(ts,data/10.0); } else if (evcode == 'F') { FL->AddEvent(ts,data/10.0); } else if (evcode == '*') { H->AddEvent(ts,data/10.0); } /* switch (evcode) { case 0x03: break; case 0x04: break; case 0x08: break; case 0x09: break; case 0x0a: break; case 0x0b: break; case 0x0c: break; case 0x10: break; case 0x11: break; case 0x12: break; case 0x13: S->AddEvent(ts, data); break; case 0x22: // VS->AddEvent(ts, data/10.0); break; case 0x28: VS->AddEvent(ts, data/10.0); break; case 'F': FL->AddEvent(ts, data/10.0); break; case '@': OA->AddEvent(ts, data/10.0); break; case '\'': //A->AddEvent(ts, data/10.0); break; case 'a': A->AddEvent(ts, data/10.0); break; case 'A': // A->AddEvent(ts, data/10.0); break; case '*': H->AddEvent(ts, data/10.0); break; case 'd': break; case 0x91: break; case 0x96: break; case 0x84: break; default: qDebug() << (int)evcode << endl; }*/ // S->AddEvent(ts, p[5]); // p[5] == 0 corresponds to peak events // p[5] == 1 corresponds to hypopnea/bstructive events //if (p[5] == 2) OA->AddEvent(ts, p[4]); // This is ugggggly... tt += step; p += 6; } sess->UpdateSummaries(); mach->AddSession(sess); } delete [] data; delete [] st; delete [] pres; delete [] mv; delete [] ev; mach->Save(); return 1; /* // Center the waveform HighPass(data, flowsize, 0.6, dt); EventList * flow = sess->AddEventList(CPAP_FlowRate, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, sample_duration); flow->AddWaveform(tt, (char *)data, flowsize, (flowsize/(1000/sample_duration)) * 1000L); qint64 ti = tt; for (int i=0; i < pr_size; ++i) { EventStoreType c = ((unsigned char *)pres)[i]; PR->AddEvent(ti, c); ti += sample_duration * 2.5; //46296296296296; } // Their calcs is uglier than mine! EventList * MV = sess->AddEventList(CPAP_Snore, EVL_Event, 1.0); ti = tt; for (int i=0; i < mv_size; ++i) { EventStoreType c = ((unsigned char *)mv)[i]; MV->AddEvent(ti, c); ti += sample_duration * 9; } // Their calcs is uglier than mine! EventList * ST = sess->AddEventList(CPAP_Leak, EVL_Event, 1.0); int st_start = index[StatusOffset]; int st_end = index[PresOffset]; int st_size = st_end - st_start; char st[st_size]; memset(st, 0, st_size); wmdata.seek(st_start); wmdata.read(st, st_size); ti = tt; for (int i=0; i < st_size; ++i) { EventStoreType c = ((unsigned char *)st)[i]; // if (c & 0x80) { ST->AddEvent(ti, c & 0x10); // } ti += sample_duration*4; // *9 } // EventList * LEAK = sess->AddEventList(CPAP_Leak, EVL_Event); // EventList * SNORE = sess->AddEventList(CPAP_Snore, EVL_Event); // int ev_start = index[EventsOffset]; // int ev_end = wmdata.size(); // int ev_size = ev_end - ev_start; // int recs = ev_size / 0x12; // unsigned char ev[ev_size]; // memset((char *) ev, 0, ev_size); // wmdata.seek(ev_start); // wmdata.read((char *) ev, ev_size); sess->really_set_last(flow->last()); // int pos = 0; // ti = tt; // // 6 byte repeating structure.. No Leaks :( // do { // //EventStoreType c = ((unsigned char*)ev)[pos+0]; // TV? // //c = ((unsigned char*)ev)[pos+6]; // MV? // EventStoreType c = ((EventStoreType*)ev)[pos+0]; // LEAK->AddEvent(ti, c); // SNORE->AddEvent(ti, ((unsigned char*)ev)[pos+2]); // pos += 0x6; // ti += 30000; // if (ti > sess->last()) // break; // } while (pos < (ev_size - 0x12)); m->AddSession(sess); sess->UpdateSummaries(); return 1;*/ } void WeinmannLoader::initChannels() { //using namespace schema; //Channel * chan = nullptr; // channel.add(GRP_CPAP, chan = new Channel(INTP_SmartFlex = 0x1165, SETTING, SESSION, // "INTPSmartFlex", QObject::tr("SmartFlex"), // QObject::tr("Weinmann pressure relief setting."), // QObject::tr("SmartFlex"), // "", DEFAULT, Qt::green)); // chan->addOption(1, STR_TR_None); } bool weinmann_initialized = false; void WeinmannLoader::Register() { if (weinmann_initialized) { return; } qDebug() << "Registering WeinmannLoader"; RegisterLoader(new WeinmannLoader()); //InitModelMap(); weinmann_initialized = true; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/weinmann_loader.h000066400000000000000000000111571417327530600246260ustar00rootroot00000000000000/* SleepLib Weinmann SOMNOsoft/Balance Loader Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef WEINMANN_LOADER_H #define WEINMANN_LOADER_H #include "SleepLib/machine.h" // Base class: MachineLoader #include "SleepLib/machine_loader.h" #include "SleepLib/profiles.h" //******************************************************************************************** /// IMPORTANT!!! //******************************************************************************************** // Please INCREMENT the following value when making changes to this loaders implementation. // const int weinmann_data_version = 3; // //******************************************************************************************** /*! \class Weinmann \brief Weinmann customized machine object */ class Weinmann: public CPAP { public: Weinmann(Profile *, MachineID id = 0); virtual ~Weinmann(); }; struct CompInfo { CompInfo() { session = nullptr; flow_start = 0; flow_size = 0; stat_start = 0; stat_size = 0; pres_start = 0; pres_size = 0; amv_start = 0; amv_size =0; event_start = 0; event_recs = 0; } CompInfo(const CompInfo & copy) { session = copy.session; time = copy.time; flow_start = copy.flow_start; flow_size= copy.flow_size; stat_start = copy.flow_start; stat_size= copy.flow_size; pres_start = copy.pres_start; pres_size = copy.pres_size; amv_start = copy.amv_start; amv_size = copy.amv_size; event_start = copy.event_start; event_recs = copy.event_recs; } CompInfo(Session * sess, QDateTime dt, quint32 fs, quint32 fl, quint32 ss, quint32 sl,quint32 ps, quint32 pl, quint32 ms, quint32 ml, quint32 es, quint32 er): session(sess), time(dt), flow_start(fs), flow_size(fl), stat_start(ss), stat_size(sl), pres_start(ps), pres_size(pl), amv_start(ms), amv_size(ml), event_start(es), event_recs(er) {} Session * session; QDateTime time; quint32 flow_start; quint32 flow_size; quint32 stat_start; quint32 stat_size; quint32 pres_start; quint32 pres_size; quint32 amv_start; quint32 amv_size; quint32 event_start; quint32 event_recs; }; const QString weinmann_class_name = STR_MACH_Weinmann; /*! \class WeinmannLoader \brief Loader for Weinmann CPAP data This is only relatively recent addition and still needs more work */ class WeinmannLoader : public CPAPLoader { public: WeinmannLoader(); virtual ~WeinmannLoader(); //! \brief Detect if the given path contains a valid Folder structure virtual bool Detect(const QString & path); //! \brief Scans path for Weinmann data signature, and Loads any new data virtual int Open(const QString & path); //! \brief Returns SleepLib database version of this Weinmann loader virtual int Version() { return weinmann_data_version; } //! \brief Returns the machine loader name of this class virtual const QString &loaderName() { return weinmann_class_name; } int ParseIndex(QFile & wmdata); //! \brief Creates a machine object, indexed by serial number // Machine *CreateMachine(QString serial); //! \brief Registers this MachineLoader with the master list, so Weinmann data can load static void Register(); virtual MachineInfo newInfo() { return MachineInfo(MT_CPAP, 0, weinmann_class_name, QObject::tr("Weinmann"), QObject::tr("SOMNOsoft2"), QString(), QString(), QObject::tr(""), QDateTime::currentDateTime(), weinmann_data_version); } virtual void initChannels(); //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Now for some CPAPLoader overrides //////////////////////////////////////////////////////////////////////////////////////////////////////////// virtual QString presRelType() { return QObject::tr("Unknown"); } // might not need this one virtual ChannelID presRelSet() { return NoChannel; } virtual ChannelID presRelLevel() { return NoChannel; } //////////////////////////////////////////////////////////////////////////////////////////////////////////// protected: QHash index; QList compinfo; QMap sessions; QString last; unsigned char *m_buffer; }; #endif // WEINMANN_LOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/zeo_loader.cpp000066400000000000000000000227111417327530600241400ustar00rootroot00000000000000/* SleepLib ZEO Loader Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ //******************************************************************************************** // Please only INCREMENT the zeo_data_version in zeo_loader.h when making changes // that change loader behaviour or modify channels in a manner that fixes old data imports. // Note that changing the data version will require a reimport of existing data for which OSCAR // does not keep a backup - so it should be avoided if possible. // i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** #include #include #include "zeo_loader.h" #include "SleepLib/machine.h" #include "csv.h" ZEOLoader::ZEOLoader() { m_type = MT_SLEEPSTAGE; csv = nullptr; } ZEOLoader::~ZEOLoader() { closeCSV(); } /*15233: "Sleep Date" 15234: "ZQ" 15236: "Total Z" 15237: "Time to Z" 15237: "Time in Wake" 15238: "Time in REM" 15238: "Time in Light" 15241: "Time in Deep" 15242: "Awakenings" 15245: "Start of Night" 15246: "End of Night" 15246: "Rise Time" 15247: "Alarm Reason" 15247: "Snooze Time" 15254: "Wake Tone" 15259: "Wake Window" 15259: "Alarm Type" 15260: "First Alarm Ring" 15261: "Last Alarm Ring" 15261: "First Snooze Time" 15265: "Last Snooze Time" 15266: "Set Alarm Time" 15266: "Morning Feel" 15267: "Sleep Graph" 15267: "Detailed Sleep Graph" 15268: "Firmware Version" */ int ZEOLoader::OpenFile(const QString & filename) { if (!openCSV(filename)) { closeCSV(); return -1; } int count = 0; Session* sess; // TODO: add progress bar support, perhaps move shared logic into shared parent class with Dreem loader while ((sess = readNextSession()) != nullptr) { sess->SetChanged(true); mach->AddSession(sess); count++; } if (count > 0) { mach->Save(); mach->SaveSummaryCache(); p_profile->StoreMachines(); } closeCSV(); return count; } bool ZEOLoader::openCSV(const QString & filename) { file.setFileName(filename); if (filename.toLower().endsWith(".csv")) { if (!file.open(QFile::ReadOnly)) { qDebug() << "Couldn't open zeo file" << filename; return false; } } else {// if (filename.toLower().endsWith(".dat")) { // TODO: add direct support for .dat files return false; // not supported. } QStringList header; csv = new CSVReader(file); bool ok = csv->readRow(header); if (!ok) { qWarning() << "no header row"; return false; } csv->setFieldNames(header); MachineInfo info = newInfo(); mach = p_profile->CreateMachine(info); return true; } void ZEOLoader::closeCSV() { if (csv != nullptr) { delete csv; csv = nullptr; } if (file.isOpen()) { file.close(); } } // int idxTotalZ = header.indexOf("Total Z"); // int idxAlarmReason = header.indexOf("Alarm Reason"); // int idxSnoozeTime = header.indexOf("Snooze Time"); // int idxWakeTone = header.indexOf("Wake Tone"); // int idxWakeWindow = header.indexOf("Wake Window"); // int idxAlarmType = header.indexOf("Alarm Type"); static const EventDataType GAIN = 0.25; // allow for fractional sleep stages (such as Deep (2)) Session* ZEOLoader::readNextSession() { if (csv == nullptr) { qWarning() << "no CSV open!"; return nullptr; } Session* sess = nullptr; QDateTime start_of_night; //, end_of_night, rise_time; qint64 st, tt; int stage; int ZQ, TimeToZ, TimeInWake, TimeInREM, TimeInLight, TimeInDeep, Awakenings; int MorningFeel; //QString FirmwareVersion, MyZeoVersion; //QDateTime FirstAlarmRing, LastAlarmRing, FirstSnoozeTime, LastSnoozeTime, SetAlarmTime; QStringList /*SG,*/ DSG; QHash row; while (csv->readRow(row)) { SessionID sid = 0; invalid_fields = false; start_of_night = readDateTime(row["Start of Night"]); if (start_of_night.isValid()) { sid = start_of_night.toTime_t(); if (mach->SessionExists(sid)) { continue; } } // else invalid_fields will be true ZQ = readInt(row["ZQ"]); TimeToZ = readInt(row["Time to Z"]); TimeInWake = readInt(row["Time in Wake"]); TimeInREM = readInt(row["Time in REM"]); TimeInLight = readInt(row["Time in Light"]); TimeInDeep = readInt(row["Time in Deep"]); Awakenings = readInt(row["Awakenings"]); //end_of_night = readDateTime(row["End of Night"]); //rise_time = readDateTime(row["Rise Time"]); //FirstAlarmRing = readDateTime(row["First Alarm Ring"], false); //LastAlarmRing = readDateTime(row["Last Alarm Ring"], false); //FirstSnoozeTime = readDateTime(row["First Snooze Time"], false); //LastSnoozeTime = readDateTime(row["Last Snooze Time"], false); //SetAlarmTime = readDateTime(row["Set Alarm Time"], false); MorningFeel = readInt(row["Morning Feel"], false); //FirmwareVersion = row["Firmware Version"]; //MyZeoVersion = row["My ZEO Version"]; if (invalid_fields) { continue; } //SG = row["Sleep Graph"].trimmed().split(" "); DSG = row["Detailed Sleep Graph"].trimmed().split(" "); if (DSG.size() == 0) { continue; } sess = new Session(mach, sid); break; }; if (sess) { const int WindowSize = 30 * 1000; m_session = sess; sess->settings[ZEO_Awakenings] = Awakenings; sess->settings[ZEO_MorningFeel] = MorningFeel; sess->settings[ZEO_TimeToZ] = TimeToZ; sess->settings[ZEO_ZQ] = ZQ; sess->settings[ZEO_TimeInWake] = TimeInWake; sess->settings[ZEO_TimeInREM] = TimeInREM; sess->settings[ZEO_TimeInLight] = TimeInLight; sess->settings[ZEO_TimeInDeep] = TimeInDeep; st = qint64(start_of_night.toTime_t()) * 1000L; sess->really_set_first(st); tt = st; for (int i = 0; i < DSG.size(); i++) { bool ok; stage = DSG[i].toInt(&ok); if (ok) { // 0 = no data, 1 = Awake, 2 = REM, 3 = Light Sleep, 4 = Deep Sleep, 6 = Deep Sleep (2), drawn slightly less deep int value = -stage / GAIN; // use negative values so that the chart is oriented the right way switch (stage) { case 0: EndEventList(ZEO_SleepStage, tt); break; case 6: // According to ZeoViewer, 6 is a "Deep (2)" and is drawn somewhere between Light and Deep. value = -3.75 / GAIN; // fall through case 1: case 2: case 3: case 4: AddEvent(ZEO_SleepStage, tt, value); break; default: qWarning() << sess->session() << start_of_night << "@" << i << "unknown sleep stage" << stage; break; } } else { qWarning() << sess->session() << start_of_night << "@" << i << "unknown sleep stage" << DSG[i]; } tt += WindowSize; } EndEventList(ZEO_SleepStage, tt); sess->really_set_last(tt); //int size = DSG.size(); //qDebug() << linecomp[0] << start_of_night << end_of_night << rise_time << size << "30 second chunks"; } return sess; } void ZEOLoader::AddEvent(ChannelID channel, qint64 t, EventDataType value) { EventList* C = m_importChannels[channel]; if (C == nullptr) { C = m_session->AddEventList(channel, EVL_Event, GAIN, 0, -5, 0); Q_ASSERT(C); // Once upon a time AddEventList could return nullptr, but not any more. m_importChannels[channel] = C; } // Add the event C->AddEvent(t, value); m_importLastValue[channel] = value; } void ZEOLoader::EndEventList(ChannelID channel, qint64 t) { EventList* C = m_importChannels[channel]; if (C != nullptr) { C->AddEvent(t, m_importLastValue[channel]); // Mark this channel's event list as ended. m_importChannels[channel] = nullptr; } } QDateTime ZEOLoader::readDateTime(const QString & text, bool required) { QDateTime dt = QDateTime::fromString(text, "MM/dd/yyyy HH:mm"); if (required || !text.isEmpty()) { if (!dt.isValid()) { dt = QDateTime::fromString(text, "yyyy-MM-dd HH:mm:ss"); if (!dt.isValid()) { invalid_fields = true; } } } return dt; } int ZEOLoader::readInt(const QString & text, bool required) { bool ok; int value = text.toInt(&ok); if (!ok) { if (required) { invalid_fields = true; } else { value = 0; } } return value; } static bool zeo_initialized = false; void ZEOLoader::Register() { if (zeo_initialized) { return; } qDebug("Registering ZEOLoader"); RegisterLoader(new ZEOLoader()); //InitModelMap(); zeo_initialized = true; } OSCAR-code-v1.3.1/oscar/SleepLib/loader_plugins/zeo_loader.h000066400000000000000000000037561417327530600236150ustar00rootroot00000000000000/* SleepLib ZEO Loader Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef ZEOLOADER_H #define ZEOLOADER_H #include "SleepLib/machine_loader.h" const QString zeo_class_name = "ZEO"; const int zeo_data_version = 2; /*! \class ZEOLoader \brief Unfinished stub for loading ZEO Personal Sleep Coach data */ class ZEOLoader : public MachineLoader { public: ZEOLoader(); virtual ~ZEOLoader(); virtual bool Detect(const QString &path) { Q_UNUSED(path); return false; } // bypass autoscanner virtual int Open(const QString & path) { Q_UNUSED(path); return 0; } // Only for CPAP virtual int OpenFile(const QString & filename); virtual QStringList getNameFilter() { return QStringList("Zeo CSV File (*.csv)"); } static void Register(); virtual int Version() { return zeo_data_version; } virtual const QString &loaderName() { return zeo_class_name; } //Machine *CreateMachine(); virtual MachineInfo newInfo() { return MachineInfo(MT_SLEEPSTAGE, 0, zeo_class_name, QObject::tr("Zeo"), QString(), QString(), QString(), QObject::tr("Personal Sleep Coach"), QDateTime::currentDateTime(), zeo_data_version); } bool openCSV(const QString & filename); void closeCSV(); Session* readNextSession(); protected: QDateTime readDateTime(const QString & text, bool required=true); int readInt(const QString & text, bool required=true); private: QFile file; class CSVReader* csv; Machine *mach; bool invalid_fields; void AddEvent(ChannelID channel, qint64 t, EventDataType value); void EndEventList(ChannelID channel, qint64 t); Session* m_session; QHash m_importChannels; QHash m_importLastValue; }; #endif // ZEOLOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/machine.cpp000066400000000000000000001115201417327530600204070ustar00rootroot00000000000000/* SleepLib Machine Class Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mainwindow.h" #include "progressdialog.h" #include #include "machine.h" #include "profiles.h" #include #include "SleepLib/schema.h" #include "SleepLib/day.h" #include "mainwindow.h" extern MainWindow * mainwin; //void SaveThread::run() //{ // bool running = true; // while (running) { // Session //sess = machine->popSaveList(); // if (sess) { // if (machine->m_donetasks % 20 == 0) { // int i = (float(machine->m_donetasks) / float(machine->m_totaltasks) * 100.0); // emit UpdateProgress(i); // } // sess->UpdateSummaries(); // //machine->saveMutex.lock(); // sess->Store(path); // //machine->saveMutex.unlock(); // // sess->TrashEvents(); // } else { // if (!machine->m_save_threads_running) { // break; // done // } else { // yieldCurrentThread(); // go do something else for a while // } // } // } // // machine->savelistSem->release(1); //} void SaveTask::run() { sess->UpdateSummaries(); mach->saveMutex.lock(); sess->Store(mach->getDataPath()); mach->saveMutex.unlock(); sess->TrashEvents(); } void LoadTask::run() { sess->LoadSummary(); } ////////////////////////////////////////////////////////////////////////////////////////// // Machine Base-Class implmementation ////////////////////////////////////////////////////////////////////////////////////////// Machine::Machine(Profile *_profile, MachineID id) : profile(_profile) { day.clear(); highest_sessionid = 0; m_suppressUntestedWarning = false; // TODO: Have the machine write m_suppressUntestedWarning and m_previousUnexpected // to XML (along with the current OSCAR version number) so that they persist across // application launches (but reset with each new OSCAR version). if (!id) { srand(time(nullptr)); MachineID temp; bool found; // Keep trying until we get a unique machineID for this profile do { temp = rand(); found = false; for (int i=0;im_machlist.size(); ++i) { if (profile->m_machlist.at(i)->id() == temp) found = true; } } while (found); m_id = temp; } else { m_id = id; } m_loader = nullptr; // qDebug() << "Create Machine: " << hex << m_id; //%lx",m_id); m_type = MT_UNKNOWN; firstsession = true; } Machine::~Machine() { saveSessionInfo(); //qDebug() << "Destroy Machine" << info.loadername << hex << m_id; } Session *Machine::SessionExists(SessionID session) { if (sessionlist.find(session) != sessionlist.end()) { return sessionlist[session]; } else { return nullptr; } } const quint16 sessinfo_version = 2; bool Machine::saveSessionInfo() { if (info.type == MT_JOURNAL) return false; if (sessionlist.size() == 0) return false; qDebug() << "Saving" << info.brand << "session info" << info.loadername; QString filename = getDataPath() + "Sessions.info"; QFile file(filename); if (!file.open(QFile::WriteOnly)) { // qDebug() << "Couldn't open" << filename << "for writing"; qWarning() << "Couldn't open" << filename << "for writing, error code" << file.error() << file.errorString(); return false; } QDataStream out(&file); out.setByteOrder(QDataStream::LittleEndian); out.setVersion(QDataStream::Qt_5_0); out << magic; out << filetype_sessenabled; out << sessinfo_version; QHash::iterator s; out << (int)sessionlist.size(); for (s = sessionlist.begin(); s != sessionlist.end(); ++s) { Session * sess = s.value(); if (sess->s_first != 0) { out << (quint32) sess->session(); out << (bool)(sess->enabled()); } else { qWarning() << "Machine::SaveSessionInfo discarding session" << sess->s_session << "["+QDateTime::fromTime_t(sess->s_session).toString("MMM dd, yyyy hh:mm:ss")+"]" << "from machine" << serial() << "with first=0"; } //out << sess->m_availableChannels; } qDebug() << "Done Saving" << info.brand << "session info"; return true; } bool Machine::loadSessionInfo() { // SessionInfo basically just contains a list of all sessions and their enabled status, // so the enabling/reenabling doesn't require a summary rewrite every time. if (info.type == MT_JOURNAL) return true; QHash::iterator s; QFile file(getDataPath() + "Sessions.info"); if ( ! file.open(QFile::ReadOnly)) { // No session.info file present, so let's create one... // But first check for legacy SESSION_ENABLED field in session settings for (s = sessionlist.begin(); s!= sessionlist.end(); ++s) { Session * sess = s.value(); QHash::iterator it = sess->settings.find(SESSION_ENABLED); bool b = true; if (it != sess->settings.end()) { b = it.value().toBool(); } sess->setEnabled(b); // Extract from session settings and save.. } // Now write the file saveSessionInfo(); return true; } QDataStream in(&file); in.setByteOrder(QDataStream::LittleEndian); in.setVersion(QDataStream::Qt_5_0); quint32 mag32; in >> mag32; quint16 ft16, version; in >> ft16; in >> version; // Legacy crud if (version == 1) { // was available channels QHash crap; in >> crap; } // Read in size record, followed by size * [SessionID, bool] pairs containing the enable status. int size; in >> size; quint32 sid; bool b; for (int i=0; i< size; ++i) { in >> sid; in >> b; s = sessionlist.find(sid); if (s != sessionlist.end()) { Session * sess = s.value(); sess->setEnabled(b); } } return true; } // Find date this session belongs in QDate Machine::pickDate(qint64 first) { QTime split_time = profile->session->daySplitTime(); int combine_sessions = profile->session->combineCloseSessions(); QDateTime d2 = QDateTime::fromTime_t(first / 1000); QDate date = d2.date(); QTime time = d2.time(); int closest_session = 0; if (time < split_time) { date = date.addDays(-1); } else if (combine_sessions > 0) { QMap::iterator dit = day.find(date.addDays(-1)); // Check Day Before if (dit != day.end()) { QDateTime lt = QDateTime::fromTime_t(dit.value()->last() / 1000L); closest_session = lt.secsTo(d2) / 60; if (closest_session < combine_sessions) { date = date.addDays(-1); } } } return date; } // allowOldSessions defaults to false and is only set to true when loading // summary data on profile load. This true setting prevents old sessions from // becoming lost if user preference indicates to not import sessions prior to a // given date. bool Machine::AddSession(Session *s, bool allowOldSessions) { if (s == nullptr) { qCritical() << "AddSession() called with a null object"; return false; } if (profile == nullptr) { qCritical() << "AddSession() called without a valid profile"; return false; } if (sessionlist.contains(s->session())) { qCritical() << "Machine::AddSession called with duplicate session" << s->session() << "["+QDateTime::fromTime_t(s->session()).toString("MMM dd, yyyy hh:mm:ss")+"]" << "for machine" << serial(); return false; } if (s->first() == 0) { qWarning() << "Machine::AddSession called with session" << s->session() << "["+QDateTime::fromTime_t(s->session()).toString("MMM dd, yyyy hh:mm:ss")+"]" << "with first=0"; return false; } // allowOldSessions is true when loading summaries (already imported sessions) // We don't want to throw away data already in the database in circumstances // where user wants to ignore old sessions on import. if (profile->session->ignoreOlderSessions() && !allowOldSessions) { qint64 ignorebefore = profile->session->ignoreOlderSessionsDate().toMSecsSinceEpoch(); if (s->last() < ignorebefore) { qDebug() << s->session() << "Ignoring old session"; skipped_sessions++; return false; } } updateChannels(s); if (s->session() > highest_sessionid) { highest_sessionid = s->session(); } QTime split_time; int combine_sessions; bool locksessions = profile->session->lockSummarySessions(); if (locksessions) { split_time = s->summaryOnly() ? QTime(12,0,0) : profile->session->daySplitTime(); combine_sessions = s->summaryOnly() ? 0 : profile->session->combineCloseSessions(); } else { split_time = profile->session->daySplitTime(); combine_sessions = profile->session->combineCloseSessions(); } int ignore_sessions = profile->session->ignoreShortSessions(); qint64 session_length = s->last() - s->first(); session_length /= 60000L; sessionlist[s->session()] = s; // To make sure it get's saved later even if it's not wanted. //int drift=profile->cpap->clockDrift(); QDateTime d2 = QDateTime::fromTime_t(s->first() / 1000); QDate date = d2.date(); QTime time = d2.time(); QMap::iterator dit, nextday; bool combine_next_day = false; int closest_session = 0; // Multithreaded import screws this up. :( if (time < split_time) { date = date.addDays(-1); } else if (combine_sessions > 0) { dit = day.find(date.addDays(-1)); // Check Day Before if (dit != day.end()) { QDateTime lt = QDateTime::fromTime_t(dit.value()->last() / 1000); closest_session = lt.secsTo(d2) / 60; if (closest_session < combine_sessions) { date = date.addDays(-1); } else { if ((split_time < time) && (split_time.secsTo(time) < 2)) { if (s->machine()->loaderName() == STR_MACH_ResMed) { date = date.addDays(-1); } } } } else { nextday = day.find(date.addDays(1)); // Check Day Afterwards if (nextday != day.end()) { QDateTime lt = QDateTime::fromTime_t(nextday.value()->first() / 1000); closest_session = d2.secsTo(lt) / 60; if (closest_session < combine_sessions) { // add todays here. pull all tomorrows records to this date. combine_next_day = true; } } } } if (session_length < ignore_sessions) { // keep the session to save importing it again, but don't add it to the day record this time qDebug() << s->session() << "Ignoring short session <" << ignore_sessions << "["+QDateTime::fromMSecsSinceEpoch(s->first()).toString("MMM dd, yyyy hh:mm:ss")+"]"; return true; } if ( ! firstsession) { if (firstday > date) { firstday = date; } if (lastday < date) { lastday = date; } } else { firstday = lastday = date; firstsession = false; } Day *dd = nullptr; dit = day.find(date); if (dit == day.end()) { dit = day.insert(date, profile->addDay(date)); } dd = dit.value(); dd->addSession(s); if (combine_next_day) { for (QList::iterator i = nextday.value()->begin(); i != nextday.value()->end(); i++) { // i may need to do something here if (locksessions && (*i)->summaryOnly()) continue; // can't move summary only sessions.. unlinkSession(*i); // Add it back sessionlist[(*i)->session()] = *i; dd->addSession(*i); } // QMap >::iterator nd = profile->daylist.find(date.addDays(1)); // if (nd != profile->daylist.end()) { // profile->unlinkDay(nd.key(), nd.value()); // } // QList::iterator iend = nd.value().end(); // for (QList::iterator i = nd.value()->begin(); i != iend; ++i) { // if (*i == nextday.value()) { // nd.value().erase(i); // } // } // day.erase(nextday); } return true; } bool Machine::unlinkDay(Day * d) { return day.remove(day.key(d)) > 0; } QString Machine::getPixmapPath() { if (!loader()) return ""; return loader()->getPixmapPath(info.series); } QPixmap & Machine::getPixmap() { static QPixmap pm; if (!loader()) return pm; return loader()->getPixmap(info.series); } bool Machine::unlinkSession(Session * sess) { MachineType mt = sess->type(); // Remove the object from the machine object's session list bool b=sessionlist.remove(sess->session()); QList dates; QList days; QMap::iterator it; Day * d; // Doing this in case of accidental double linkages for (it = day.begin(); it != day.end(); ++it) { d = it.value(); if (it.value()->sessions.contains(sess)) { days.push_back(d); dates.push_back(it.key()); } } for (int i=0; i < days.size(); ++i) { d = days.at(i); if (d->sessions.removeAll(sess)) { b=true; if (!d->searchMachine(mt)) { d->machines.remove(mt); day.remove(dates[i]); } if (d->size() == 0) { profile->unlinkDay(d); } } } return b; } // This functions purpose is murder and mayhem... It deletes all of a machines data. bool Machine::Purge(int secret) { // Boring api key to stop this function getting called by accident :) if (secret != 3478216) { return false; } QString path = getDataPath(); QDir dir(path); if (!dir.exists()) { // It doesn't exist anyway. return true; } if (!dir.isReadable()) { return false; } qDebug() << "Purging" << info.loadername << info.serial << dir.absoluteFilePath(path); // Remove any imported file list QFile impfile(getDataPath()+"/imported_files.csv"); impfile.remove(); QFile rxcache(profile->Get("{" + STR_GEN_DataFolder + "}/RXChanges.cache" )); rxcache.remove(); QFile sumfile(getDataPath()+"/Summaries.xml.gz"); sumfile.remove(); QFile sessinfofile(getDataPath()+"/Sessions.info"); sessinfofile.remove(); // Create a copy of the list so the hash can be manipulated QList sessions = sessionlist.values(); QList days = day.values(); // Clean up any loaded sessions from memory first.. //bool success = true; for (int i=0; i < sessions.size(); ++i) { Session * sess = sessions[i]; if (!sess->Destroy()) { qDebug() << "Could not destroy "+ info.loadername +" ("+info.serial+") session" << sess->session(); // success = false; } else { // sessionlist.remove(sess->session()); } delete sess; } // Make sure there aren't any dangling references to this machine for (auto & d : days) { d->removeMachine(this); } // Remove EVERYTHING under Events folder.. QString eventspath = getEventsPath(); QDir evdir(eventspath); evdir.removeRecursively(); QString summariespath = getSummariesPath(); QDir sumdir(summariespath); sumdir.removeRecursively(); // Clean up any straggling files (like from short sessions not being loaded...) dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); const QFileInfoList & list = dir.entryInfoList(); int could_not_kill = 0; for (const auto & fi : list) { QString fullpath = fi.canonicalFilePath(); QString ext_s = fullpath.section('.', -1); bool ok; ext_s.toInt(&ok, 10); if (ok) { qDebug() << "Deleting " << fullpath; if (!dir.remove(fullpath)) { qDebug() << "Could not purge file" << fullpath; //success=false; could_not_kill++; } } else { qDebug() << "Didn't bother deleting cruft file" << fullpath; // cruft file.. } } if (could_not_kill > 0) { qWarning() << "Could not purge path" << could_not_kill << "files in " << path; return false; } return true; } void Machine::setLoaderName(QString value) { info.loadername = value; m_loader = GetLoader(value); } void Machine::setInfo(MachineInfo inf) { MachineInfo merged = inf; if (info.purgeDate.isValid()) merged.purgeDate = info.purgeDate; info = merged; m_loader = GetLoader(inf.loadername); } const QString Machine::getDataPath() { // TODO: Rework the underlying database so that file storage doesn't rely on consistent presence or absence of the serial number. m_dataPath = p_pref->Get("{home}/Profiles/")+profile->user->userName()+"/"+info.loadername + "_" + (info.serial.isEmpty() ? hexid() : info.serial) + "/"; return m_dataPath; } const QString Machine::getSummariesPath() { return getDataPath() + "Summaries/"; } const QString Machine::getEventsPath() { return getDataPath() + "Events/"; } const QString Machine::getBackupPath() { qDebug() << "Backup Path is " + getDataPath() + "Backup/"; return getDataPath() + "Backup/"; } // dirSize lazily pinched from https://stackoverflow.com/questions/47854288/can-not-get-directory-size-in-qt-c, thank's "Mike" qint64 dirSize(QString dirPath) { qint64 size = 0; QDir dir(dirPath); QDir::Filters fileFilters = QDir::Files | QDir::System | QDir::Hidden; for(QString filePath : dir.entryList(fileFilters)) { QFileInfo fi(dir, filePath); size += fi.size(); } QDir::Filters dirFilters = QDir::Dirs | QDir::NoDotAndDotDot | QDir::System | QDir::Hidden; for(QString childDirPath : dir.entryList(dirFilters)) size += dirSize(dirPath + QDir::separator() + childDirPath); return size; } qint64 Machine::diskSpaceSummaries() { return dirSize(getSummariesPath()); } qint64 Machine::diskSpaceEvents() { return dirSize(getEventsPath()); } qint64 Machine::diskSpaceBackups() { return dirSize(getBackupPath()); } bool Machine::Load(ProgressDialog *progress) { QString path = getDataPath(); QDir dir(path); qDebug() << "Loading" << info.loadername.toLocal8Bit().data() << "record:" << path.toLocal8Bit().data(); if (!dir.exists() || !dir.isReadable()) { return false; } QPixmap image = getPixmap(); if (!image.isNull()) { image = image.scaled(64,64); progress->setPixmap(image); } progress->setMessage(QObject::tr("Loading %1 data for %2...").arg(info.brand).arg(profile->user->userName())); if (loader()) { mainwin->connect(loader(), SIGNAL(updateMessage(QString)), progress, SLOT(setMessage(QString))); mainwin->connect(loader(), SIGNAL(setProgressMax(int)), progress, SLOT(setProgressMax(int))); mainwin->connect(loader(), SIGNAL(setProgressValue(int)), progress, SLOT(setProgressValue(int))); } if ( ! LoadSummary(progress)) { qDebug() << "Recreating the Summary index XML file"; // No XML index file, so assume upgrading, or it simply just got screwed up or deleted... progress->setMessage(QObject::tr("Scanning Files")); progress->setProgressValue(0); QApplication::processEvents(); QTime time; time.start(); dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); /////////////////////////////////////////////////////////////////////// // First move any old files to correct locations /////////////////////////////////////////////////////////////////////// QString summarypath = getSummariesPath(); QString eventpath = getEventsPath(); if (!dir.exists(summarypath)) dir.mkpath(summarypath); QStringList filters; filters << "*.000"; dir.setNameFilters(filters); QStringList filelist = dir.entryList(); int size = filelist.size(); // Legacy crap.. Summary and Event stuff used to be in one big pile in the machine folder root for (auto & filename : filelist) { QFile::rename(path+filename, summarypath+filename); } // Copy old Event files to folder filters.clear(); filters << "*.001"; dir.setNameFilters(filters); filelist = dir.entryList(); size = filelist.size(); progress->setMessage(QObject::tr("Migrating Summary File Location")); progress->setProgressMax(size); QApplication::processEvents(); if (size > 0) { if (!dir.exists(eventpath)) dir.mkpath(eventpath); for (int i=0; i< size; i++) { if ((i % 20) == 0) { // This is slow.. :-/ progress->setProgressValue(i); QApplication::processEvents(); } QString filename = filelist.at(i); QFile::rename(path+filename, eventpath+filename); } } /////////////////////////////////////////////////////////////////////// // Now read summary files from correct location and load them /////////////////////////////////////////////////////////////////////// dir.setPath(summarypath); filters.clear(); filters << "*.000"; dir.setNameFilters(filters); filelist = dir.entryList(); size = filelist.size(); progress->setMessage("Reading summary files"); qDebug() << "Reading summary files (.000)"; progress->setProgressValue(0); QApplication::processEvents(); QString sesstr; SessionID sessid; bool ok; for (int i=0; i < size; i++) { if ((i % 20) == 0) { // This is slow.. :-/ progress->setProgressValue(i); QApplication::processEvents(); } QString filename = filelist.at(i); sesstr = filename.section(".", 0, -2); sessid = sesstr.toLong(&ok, 16); if (!ok) { continue; } Session *sess = new Session(this, sessid); // Forced to load it, because know nothing about this session.. if (sess->LoadSummary()) { AddSession(sess, true); } else { qWarning() << "Error loading summary file" << filename; delete sess; } } SaveSummaryCache(); qDebug() << "Loaded" << info.model.toLocal8Bit().data() << "data in" << time.elapsed() << "ms"; progress->setProgressValue(size); } progress->setMessage("Loading Session Info"); qDebug() << "Loading Session Info"; QApplication::processEvents(); loadSessionInfo(); if (loader()) { mainwin->disconnect(loader(), SIGNAL(updateMessage(QString)), progress, SLOT(setMessage(QString))); mainwin->disconnect(loader(), SIGNAL(setProgressMax(int)), progress, SLOT(setProgressMax(int))); mainwin->disconnect(loader(), SIGNAL(setProgressValue(int)), progress, SLOT(setProgressValue(int))); } return true; } bool Machine::SaveSession(Session *sess) { QString path = getDataPath(); if (sess->IsChanged() && sess->first() != 0) { sess->Store(path); } return true; } //Session *Machine::popSaveList() //{ // Session *sess = nullptr; // listMutex.lock(); // // if (!m_savelist.isEmpty()) { // sess = m_savelist.at(0); // m_savelist.pop_front(); // m_donetasks++; // } // // listMutex.unlock(); // return sess; //} void Machine::queTask(ImportTask * task) { if (AppSetting->multithreading()) { m_tasklist.push_back(task); return; } // Not multithreading, run it right now... task->run(); return; } void Machine::runTasks() { if (m_tasklist.isEmpty()) { qDebug() << "No tasks in m_tasklist"; return; } qDebug() << "m_tasklist size is" << m_tasklist.size(); QThreadPool * threadpool = QThreadPool::globalInstance(); /*********************************************************** // int m_totaltasks=m_tasklist.size(); // int m_currenttask=0; // if (loader()) // emit loader()->setProgressMax(m_totaltasks); ***********************************************************/ while ( ! m_tasklist.isEmpty()) { if (threadpool->tryStart(m_tasklist.at(0))) { m_tasklist.pop_front(); /************************************************************ // if (loader()) { // emit loader()->setProgressValue(++m_currenttask); // QApplication::processEvents(); // } ***************************************************************/ } } QThreadPool::globalInstance()->waitForDone(-1); } bool Machine::hasModifiedSessions() { QHash::iterator s; for (s = sessionlist.begin(); s != sessionlist.end(); s++) { if (s.value()->IsChanged()) { return true; } } return false; } const QString summaryFileName = "Summaries.xml"; const int summaryxml_version=1; bool Machine::LoadSummary(ProgressDialog * progress) { QTime time; time.start(); QString filename = getDataPath() + summaryFileName + ".gz"; QDomDocument doc; QFile file(filename); qDebug() << "Loading" << filename.toLocal8Bit().data(); progress->setMessage(QObject::tr("Loading Summaries.xml.gz")); QApplication::processEvents(); if (!file.open(QIODevice::ReadOnly)) { // qWarning() << "Could not open" << filename; qWarning() << "Could not open" << filename << "for reading, error code" << file.error() << file.errorString(); return false; } QByteArray data = file.readAll(); QByteArray uncompressed = gUncompress(data); QString errorMsg; int errorLine; if (!doc.setContent(uncompressed, false, &errorMsg, &errorLine)) { qWarning() << "Invalid XML Content in" << filename; qWarning() << "Error line" << errorLine << ":" << errorMsg; return false; } file.close(); QDomElement root = doc.documentElement(); if (root.tagName().compare("sessions", Qt::CaseInsensitive) != 0) { qDebug() << "Summaries cache messed up, recreating..."; return false; } bool ok; int version = root.attribute("version", "").toInt(&ok); if (!ok || (version != summaryxml_version)) { qDebug() << "Summaries cache outdated, recreating..."; return false; } QDomNode node; bool s_ok; QDomNodeList sessionlist = root.childNodes(); int size = sessionlist.size(); QMap sess_order; progress->setProgressMax(size); for (int s=0; s < size; ++s) { if ((s % 20) == 0) { progress->setProgressValue(s); QApplication::processEvents(); } node = sessionlist.at(s); QDomElement e = node.toElement(); SessionID sessid = e.attribute("id", "0").toLong(&s_ok); qint64 first = e.attribute("first", "0").toLongLong(); qint64 last = e.attribute("last", "0").toLongLong(); bool enabled = e.attribute("enabled", "1").toInt() == 1; bool events = e.attribute("events", "1").toInt() == 1; if (s_ok) { Session * sess = new Session(this, sessid); sess->really_set_first(first); sess->really_set_last(last); sess->setEnabled(enabled); sess->setSummaryOnly(!events); if (e.hasChildNodes()) { QList available_channels; QList available_settings; QDomElement chans = e.firstChildElement("channels"); if (chans.isElement()) { QDomNode node = chans.firstChild(); QString txt = node.nodeValue(); QStringList channels = txt.split(","); for (int i=0; im_availableChannels = available_channels; QDomElement sete = e.firstChildElement("settings"); if (sete.isElement()) { QString sets = sete.firstChild().nodeValue(); QStringList settings = sets.split(","); for (int i=0; im_availableSettings = available_settings; } sess_order[first] = sess; } } QMap::iterator it_end = sess_order.end(); QMap::iterator it; bool loadSummaries = profile->session->preloadSummaries(); qDebug() << "PreloadSummaries is" << (loadSummaries ? "true" : "false"); qDebug() << "Queue task loader is" << (loader() ? "" : "not ") << "available"; // sleep(1); // progress->setMessage(QObject::tr("Queueing Open Tasks")); // QApplication::processEvents(); // int cnt = 0; // progress->setMaximum(sess_order.size()); for (it = sess_order.begin(); it != it_end; ++it /*, ++cnt*/ ) { /**************************************************************** // if ((cnt % 100) == 0) { // progress->setValue(cnt); // //QApplication::processEvents(); // } *****************************************************************/ Session * sess = it.value(); if ( ! AddSession(sess, true)) { delete sess; } else { if (loadSummaries) { if (loader()) { loader()->queTask(new LoadTask(sess,this)); } else { // no progress bar queTask(new LoadTask(sess,this)); } } } } progress->setMessage(QObject::tr("Loading Summary Data")); qDebug() << "Loading Summary Data"; QApplication::processEvents(); if (loader()) { loader()->runTasks(); } else { runTasks(); } progress->setProgressValue(sess_order.size()); QApplication::processEvents(); qDebug() << "Loaded" << info.model.toLocal8Bit().data() << "data in" << time.elapsed() << "ms"; return true; } bool Machine::SaveSummaryCache() { qDebug() << "Saving" << info.brand << info.model << "Summaries"; QString filename = getDataPath() + summaryFileName; QDomDocument doc("OSCAR_SessionIndex"); QDomElement root = doc.createElement("sessions"); root.setAttribute("version", summaryxml_version); root.setAttribute("profile", profile->user->userName()); root.setAttribute("count", sessionlist.size()); root.setAttribute("loader", info.loadername); root.setAttribute("serial", info.serial); doc.appendChild(root); if (!QDir().exists(getSummariesPath())) QDir().mkpath(getSummariesPath()); QHash::iterator s; QHash::iterator sess_end = sessionlist.end(); for (s = sessionlist.begin(); s != sess_end; ++s) { QDomElement el = doc.createElement("session"); Session * sess = s.value(); el.setAttribute("id", (quint32)sess->session()); el.setAttribute("first", sess->realFirst()); el.setAttribute("last", sess->realLast()); el.setAttribute("enabled", sess->enabled() ? "1" : "0"); el.setAttribute("events", sess->summaryOnly() ? "0" : "1"); QHash >::iterator ev; QHash >::iterator ev_end = sess->eventlist.end(); QStringList chanlist; for (ev = sess->eventlist.begin(); ev != ev_end; ++ev) { chanlist.append(QString::number(ev.key(), 16)); } if (chanlist.size() == 0) { for (int i=0; im_availableChannels.size(); i++) { ChannelID code = sess->m_availableChannels.at(i); chanlist.append(QString::number(code, 16)); } } QDomElement chans = doc.createElement("channels"); chans.appendChild(doc.createTextNode(chanlist.join(","))); el.appendChild(chans); chanlist.clear(); QHash::iterator si; QHash::iterator set_end = sess->settings.end(); for (si = sess->settings.begin(); si != set_end; ++si) { chanlist.append(QString::number(si.key(), 16)); } QDomElement settings = doc.createElement("settings"); settings.appendChild(doc.createTextNode(chanlist.join(","))); el.appendChild(settings); root.appendChild(el); if (sess->IsChanged()) sess->StoreSummary(); } QString xmltext; QTextStream ts(&xmltext); doc.save(ts, 1); QByteArray data = gCompress(xmltext.toUtf8()); QFile file(filename + ".gz"); if (!file.open(QFile::WriteOnly)) { qWarning() << "Couldn't open summary cache" << filename << "for writing, error code" << file.error() << file.errorString(); } file.write(data); return true; } bool Machine::Save() { //int size; int cnt = 0; QString path = getDataPath(); QDir dir(path); if (!dir.exists()) { dir.mkdir(path); } QHash::iterator s; // m_savelist.clear(); // store any event summaries.. for (s = sessionlist.begin(); s != sessionlist.end(); s++) { cnt++; if ((*s)->IsChanged()) { queTask(new SaveTask(*s, this)); } } runTasks(); return true; } void Machine::updateChannels(Session * sess) { int size = sess->m_availableChannels.size(); for (int i=0; i < size; ++i) { ChannelID code = sess->m_availableChannels.at(i); m_availableChannels[code] = true; } size = sess->m_availableSettings.size(); for (int i=0; i < size; ++i) { ChannelID code = sess->m_availableSettings.at(i); m_availableSettings[code] = true; } } QList Machine::availableChannels(quint32 chantype) { QList list; QHash::iterator end = m_availableChannels.end(); QHash::iterator it; for (it = m_availableChannels.begin(); it != end; ++it) { ChannelID code = it.key(); const schema::Channel & chan = schema::channel[code]; if (chan.type() & chantype) { list.push_back(code); } } return list; } ////////////////////////////////////////////////////////////////////////////////////////// // CPAP implmementation ////////////////////////////////////////////////////////////////////////////////////////// CPAP::CPAP(Profile * profile, MachineID id): Machine(profile, id) { m_type = MT_CPAP; } CPAP::~CPAP() { } ////////////////////////////////////////////////////////////////////////////////////////// // Oximeter Class implmementation ////////////////////////////////////////////////////////////////////////////////////////// Oximeter::Oximeter(Profile * profile, MachineID id): Machine(profile, id) { m_type = MT_OXIMETER; } Oximeter::~Oximeter() { } ////////////////////////////////////////////////////////////////////////////////////////// // SleepStage Class implmementation ////////////////////////////////////////////////////////////////////////////////////////// SleepStage::SleepStage(Profile * profile, MachineID id): Machine(profile, id) { m_type = MT_SLEEPSTAGE; } SleepStage::~SleepStage() { } ////////////////////////////////////////////////////////////////////////////////////////// // PositionSensor Class implmementation ////////////////////////////////////////////////////////////////////////////////////////// PositionSensor::PositionSensor(Profile * profile, MachineID id): Machine(profile, id) { m_type = MT_POSITION; } PositionSensor::~PositionSensor() { } OSCAR-code-v1.3.1/oscar/SleepLib/machine.h000066400000000000000000000227631417327530600200660ustar00rootroot00000000000000/* SleepLib Machine Class Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef MACHINE_H #define MACHINE_H #include #include #include #include #include #include #include #include #include #include #include #include "SleepLib/preferences.h" #include "SleepLib/progressdialog.h" #include "SleepLib/machine_common.h" #include "SleepLib/event.h" #include "SleepLib/session.h" #include "SleepLib/schema.h" #include "SleepLib/day.h" class Day; class Session; class Profile; class Machine; /*! \class SaveThread \brief This class is used in the multithreaded save code.. It accelerates the indexing of summary data. */ //class SaveThread: public QThread //{ // Q_OBJECT // public: // SaveThread(Machine *m, QString p) { machine = m; path = p; } // // //! \brief Static millisecond sleep function.. Can be used from anywhere // static void msleep(unsigned long msecs) { QThread::msleep(msecs); } // // //! \brief Start Save processing thread running // virtual void run(); // protected: // Machine *machine; // QString path; // signals: // //! \brief Signal sent to update the Progress Bar // void UpdateProgress(int i); //}; class ImportTask:public QRunnable { public: explicit ImportTask() {} virtual ~ImportTask() {} virtual void run() {} }; class SaveTask:public ImportTask { public: SaveTask(Session * s, Machine * m): sess(s), mach(m) {} virtual ~SaveTask() {} virtual void run(); protected: Session * sess; Machine * mach; }; class LoadTask:public ImportTask { public: LoadTask(Session * s, Machine * m): sess(s), mach(m) {} virtual ~LoadTask() {} virtual void run(); protected: Session * sess; Machine * mach; }; class MachineLoader; // forward /*! \class Machine \brief This Machine class is the Heart of SleepyLib, representing a single Machine and holding it's data */ class Machine { friend class SaveThread; // friend class MachineLaoder; public: /*! \fn Machine(MachineID id=0); \brief Constructs a Machine object with MachineID id If supplied MachineID is zero, it will generate a new unused random one. */ Machine(Profile * _profile, MachineID id = 0); virtual ~Machine(); //! \brief Load all Machine summary data bool Load(ProgressDialog *progress); bool LoadSummary(ProgressDialog *progress); //! \brief Save all Sessions where changed bit is set. bool Save(); bool SaveSummaryCache(); //! \brief Save individual session bool SaveSession(Session *sess); //! \brief Deletes the crud out of all machine data in the SleepLib database bool Purge(int secret); //! \brief Unlink a session from any Machine related indexes bool unlinkSession(Session * sess); bool unlinkDay(Day * day); inline bool hasChannel(ChannelID code) { return m_availableChannels.contains(code); } inline bool hasSetting(ChannelID code) { return m_availableSettings.contains(code); } //! \brief Returns a pointer to a valid Session object if SessionID exists Session *SessionExists(SessionID session); //! \brief Adds the session to this machine object, and the Master Profile list. (used during load) bool AddSession(Session *s, bool allowOldSessions=false); //! \brief Find the date this session belongs in, according to profile settings QDate pickDate(qint64 start); const QString getDataPath(); const QString getEventsPath(); const QString getSummariesPath(); const QString getBackupPath(); qint64 diskSpaceSummaries(); qint64 diskSpaceEvents(); qint64 diskSpaceBackups(); //! \brief Returns the machineID as a lower case hexadecimal string QString hexid() { return QString().sprintf("%08lx", m_id); } //! \brief Unused, increments the most recent sessionID SessionID CreateSessionID() { return highest_sessionid + 1; } //! \brief Returns this objects MachineID const MachineID &id() { return m_id; } void setId(MachineID id) { m_id = id; } //! \brief Returns the date of the first loaded Session const QDate &FirstDay() { return firstday; } //! \brief Returns the date of the most recent loaded Session const QDate &LastDay() { return lastday; } bool hasModifiedSessions(); bool warnOnUntested() { return m_suppressUntestedWarning == false; } void suppressWarnOnUntested() { m_suppressUntestedWarning = true; } QSet & previouslySeenUnexpectedData() { return m_previousUnexpected; } inline MachineType type() const { return info.type; } inline QString brand() const { return info.brand; } inline QString loaderName() const { return info.loadername; } inline QString model() const { return info.model; } inline QString modelnumber() const { return info.modelnumber; } inline QString serial() const { return info.serial; } inline QString series() const { return info.series; } inline quint32 cap() const { return info.cap; } inline int version() const { return info.version; } inline QDateTime lastImported() const { return info.lastimported; } inline QDate purgeDate() const { return info.purgeDate; } inline void setModel(QString value) { info.model = value; } inline void setBrand(QString value) { info.brand = value; } inline void setSerial(QString value) { info.serial = value; } inline void setType(MachineType type) { info.type = type; } inline void setCap(quint32 value) { info.cap = value; } inline void setPurgeDate(QDate value) {info.purgeDate = value; } inline void clearPurgeDate() {info.purgeDate = QDate(); } bool saveSessionInfo(); bool loadSessionInfo(); void setLoaderName(QString value); QList availableChannels(quint32 chantype); MachineLoader * loader() { return m_loader; } void setInfo(MachineInfo inf); const MachineInfo getInfo() { return info; } void updateChannels(Session * sess); QString getPixmapPath(); QPixmap & getPixmap(); // //! \brief The multi-threading methods follow // //! \brief Add a new task to the multithreaded save code //void queSaveList(Session * sess); //! \brief Grab the next task in the multithreaded save code //Session *popSaveList(); // //! \brief Start the save threads which handle indexing, file storage and waveform processing //void StartSaveThreads(); // //! \brief Finish the save threads and safely close them //void FinishSaveThreads(); // void lockSaveMutex() { listMutex.lock(); } // void unlockSaveMutex() { listMutex.unlock(); } // void skipSaveTask() { lockSaveMutex(); m_donetasks++; unlockSaveMutex(); } // // void clearSkipped() { skipped_sessions = 0; } // int skippedSessions() { return skipped_sessions; } // // inline int totalTasks() { return m_totaltasks; } // inline void setTotalTasks(int value) { m_totaltasks = value; } // inline int doneTasks() { return m_donetasks; } // much more simpler multithreading...(no such thing - pholynyk) void queTask(ImportTask * task); void runTasks(); // Public Data Members follow MachineInfo info; bool m_suppressUntestedWarning; QSet m_previousUnexpected; //! \brief Contains a secondary index of day data, containing just this machines sessions QMap day; //! \brief Contains all sessions for this machine, indexed by SessionID QHash sessionlist; //! \brief The list of sessions that need saving (for multithreaded save code) // QList m_savelist; // Following are the items related to multi-threading // QVectorthread; // volatile int savelistCnt; // int savelistSize; QMutex listMutex; QMutex saveMutex; // QSemaphore *savelistSem; protected: // only Data Memebers here QDate firstday, lastday; SessionID highest_sessionid; MachineID m_id; MachineType m_type; QString m_path; MachineLoader * m_loader; bool changed; bool firstsession; QHash m_availableChannels; QHash m_availableSettings; QString m_summaryPath; QString m_eventsPath; QString m_dataPath; Profile * profile; // The following are the multi-threading data members int m_totaltasks; int m_donetasks; int skipped_sessions; volatile bool m_save_threads_running; QList m_tasklist; }; /*! \class CPAP \brief A CPAP classed machine object.. */ class CPAP: public Machine { public: CPAP(Profile *, MachineID id = 0); virtual ~CPAP(); }; /*! \class Oximeter \brief An Oximeter classed machine object.. */ class Oximeter: public Machine { public: Oximeter(Profile *, MachineID id = 0); virtual ~Oximeter(); protected: }; /*! \class SleepStage \brief A SleepStage classed machine object.. */ class SleepStage: public Machine { public: SleepStage(Profile *, MachineID id = 0); virtual ~SleepStage(); protected: }; /*! \class PositionSensor \brief A PositionSensor classed machine object.. */ class PositionSensor: public Machine { public: PositionSensor(Profile *, MachineID id = 0); virtual ~PositionSensor(); protected: }; #endif // MACHINE_H OSCAR-code-v1.3.1/oscar/SleepLib/machine_common.cpp000066400000000000000000000052201417327530600217560ustar00rootroot00000000000000/* SleepLib Common Machine Stuff * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "machine_common.h" ChannelID AllAhiChannels = 0xffff; QVector ahiChannels; ChannelID NoChannel, SESSION_ENABLED, CPAP_SummaryOnly; ChannelID CPAP_IPAP, CPAP_IPAPLo, CPAP_IPAPHi, CPAP_EPAP, CPAP_EPAPLo, CPAP_EPAPHi, CPAP_Pressure, CPAP_PS, CPAP_Mode, CPAP_AHI, CPAP_PressureMin, CPAP_PressureMax, CPAP_Ramp, CPAP_RampTime, CPAP_RampPressure, CPAP_Obstructive, CPAP_Hypopnea, CPAP_AllApnea, CPAP_ClearAirway, CPAP_Apnea, CPAP_PB, CPAP_CSR, CPAP_LeakFlag, CPAP_ExP, CPAP_NRI, CPAP_VSnore, CPAP_VSnore2, CPAP_RERA, CPAP_PressurePulse, CPAP_FlowLimit, CPAP_SensAwake, CPAP_FlowRate, CPAP_MaskPressure, CPAP_MaskPressureHi, CPAP_RespEvent, CPAP_Snore, CPAP_MinuteVent, CPAP_RespRate, CPAP_TidalVolume, CPAP_PTB, CPAP_Leak, CPAP_LeakMedian, CPAP_LeakTotal, CPAP_MaxLeak, CPAP_FLG, CPAP_IE, CPAP_Te, CPAP_Ti, CPAP_TgMV, CPAP_UserFlag1, CPAP_UserFlag2, CPAP_UserFlag3, /*CPAP_BrokenSummary, CPAP_BrokenWaveform,*/ CPAP_RDI, CPAP_PresReliefMode, CPAP_PresReliefLevel, CPAP_PSMin, CPAP_PSMax, CPAP_Test1, CPAP_Test2, CPAP_HumidSetting, CPAP_PressureSet, CPAP_IPAPSet, CPAP_EPAPSet; ChannelID RMS9_E01, RMS9_E02, RMS9_SetPressure, RMS9_MaskOnTime; ChannelID INTELLIPAP_Unknown1, INTELLIPAP_Unknown2, INTP_SnoreFlag; ChannelID CPAP_LargeLeak, PRS1_BND, PRS1_FlexMode, PRS1_FlexLevel, PRS1_HumidStatus, PRS1_HumidLevel, PRS1_HumidTargetTime, PRS1_MaskResistLock, PRS1_MaskResistSet, PRS1_HoseDiam, PRS1_AutoOn, PRS1_AutoOff, PRS1_MaskAlert, PRS1_ShowAHI; //ChannelID SS_SenseAwakeLevel, SS_EPR, SS_EPRLevel, SS_Ramp; ChannelID OXI_Pulse, OXI_SPO2, OXI_Perf, OXI_PulseChange, OXI_SPO2Drop, OXI_Plethy; ChannelID Journal_Notes, Journal_Weight, Journal_BMI, Journal_ZombieMeter, LastUpdated, Bookmark_Start, Bookmark_End, Bookmark_Notes; ChannelID ZEO_SleepStage, ZEO_ZQ, ZEO_TotalZ, ZEO_TimeToZ, ZEO_TimeInWake, ZEO_TimeInREM, ZEO_TimeInLight, ZEO_TimeInDeep, ZEO_Awakenings, ZEO_AlarmReason, ZEO_SnoozeTime, ZEO_WakeTone, ZEO_WakeWindow, ZEO_AlarmType, ZEO_MorningFeel, ZEO_FirmwareVersion, ZEO_FirstAlarmRing, ZEO_LastAlarmRing, ZEO_FirstSnoozeTime, ZEO_LastSnoozeTime, ZEO_SetAlarmTime, ZEO_RiseTime; ChannelID POS_Orientation, POS_Inclination, POS_Movement; OSCAR-code-v1.3.1/oscar/SleepLib/machine_common.h000066400000000000000000000210711417327530600214250ustar00rootroot00000000000000/* SleepLib Common Machine Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef MACHINE_COMMON_H #define MACHINE_COMMON_H #include #include #include #include #include #include #include #include using namespace std; // Do not change these without considering the consequences.. For one the Loader needs changing & version increase typedef quint32 ChannelID; typedef long MachineID; typedef long SessionID; typedef float EventDataType; typedef qint16 EventStoreType; //! \brief Exception class for out of Bounds error.. Unused. class BoundsError {}; //! \brief Exception class for to trap old database versions. class OldDBVersion {}; const quint32 magic = 0xC73216AB; // Magic number for OSCAR Data Files.. Don't touch! //const int max_number_event_fields=10; // This should probably move somewhere else //! \fn timezoneOffset(); //! \brief Calculate the timezone Offset in milliseconds between system timezone and UTC qint64 timezoneOffset(); /*! \enum SummaryType \brief Calculation/Display method to select from dealing with summary information */ enum SummaryType { ST_CNT, ST_SUM, ST_AVG, ST_WAVG, ST_PERC, ST_90P, ST_MIN, ST_MAX, ST_MID, ST_CPH, ST_SPH, ST_FIRST, ST_LAST, ST_HOURS, ST_SESSIONS, ST_SETMIN, ST_SETAVG, ST_SETMAX, ST_SETWAVG, ST_SETSUM, ST_SESSIONID, ST_DATE }; /*! \enum MachineType \brief Generalized type of a machine. MT_CPAP is any type of xPAP machine, MT_OXIMETER any type of Oximeter \brief MT_SLEEPSTAGE stage of sleep detector (ZEO importer), MT_JOURNAL for optional notes, MT_POSITION for sleep position detector (Somnopose) */ // TODO: This really needs to be a bitmask, since there are increasing numbers of machines that provide // multiple kinds of data, such as oximetry + motion/position, or sleep stage + oximetry, etc. // // Machine/loader classes will use the bitmask to identify which data they are capable of importing. // It may be that we ultimately prefer to have each machine identify a primary type instead or in addition. // // The channel schema's use of these is probably fine. // // Days/Sessions/etc. that currently search for data based on the machines they contain will instead // need to search for channels with data of that MT type. And anywhere else the code makes decisions // based on MT. // // Unfortunately, this also includes previously imported data, as Session encodes the machine's type in // each file on disk. We might be partially saved by the fact that MT_CPAP and MT_OXIMETER were originally // 1 and 2, which would only break MT_SLEEPSTAGE and higher. enum MachineType { MT_UNKNOWN = 0, MT_CPAP, MT_OXIMETER, MT_SLEEPSTAGE, MT_JOURNAL, MT_POSITION, MT_UNCATEGORIZED = 99}; //void InitMapsWithoutAwesomeInitializerLists(); /***** NEVER USED --- 8/2019 // PAP Device Capabilities const quint32 CAP_Fixed = 0x0000001; // Constant PAP const quint32 CAP_Variable = 0x0000002; // Variable Base (EPAP) pressure const quint32 CAP_BiLevel = 0x0000004; // Fixed Pressure Support const quint32 CAP_Variable_PS = 0x0000008; // Pressure support can range const quint32 CAP_PressureRelief = 0x0000010; // Device has a pressure relief mode (EPR; Flex; SmartFlex) const quint32 CAP_Humidification = 0x0000020; // Device has a humidifier attached // PAP Mode Capabilities const quint32 PAP_CPAP = 0x0001; // Fixed Pressure PAP const quint32 PAP_APAP = 0x0002; // Auto Ranging PAP const quint32 PAP_BiLevelFixed = 0x0004; // Fixed BiLevel const quint32 PAP_BiLevelAutoFixed = 0x0008; // Auto BiLevel with Fixed EPAP const quint32 PAP_BiLevelAutoVariable = 0x0010; // Auto BiLevel with full ranging capabilities const quint32 PAP_ASV_Fixed = 0x0020; // ASV with fixed EPAP const quint32 PAP_ASV_Variable = 0x0040; // ASV with full ranging capabilities const quint32 PAP_SplitNight = 0x8000; // Split night capabilities *****/ /*! \enum CPAPMode \brief CPAP Machines mode of operation */ enum CPAPMode { //:short MODE_UNKNOWN = 0, MODE_CPAP, MODE_APAP, MODE_BILEVEL_FIXED, MODE_BILEVEL_AUTO_FIXED_PS, MODE_BILEVEL_AUTO_VARIABLE_PS, MODE_ASV, MODE_ASV_VARIABLE_EPAP, MODE_AVAPS }; /*! \enum PRTypes \brief Pressure Relief Types, used by CPAP machines */ enum PRTypes { //:short PR_UNKNOWN = 0, PR_NONE, PR_CFLEX, PR_CFLEXPLUS, PR_AFLEX, PR_BIFLEX, PR_EPR, PR_SMARTFLEX, PR_EASYBREATHE, PR_SENSAWAKE }; enum PRTimeModes { //:short PM_UNKNOWN = 0, PM_RampOnly, PM_FullTime }; struct MachineInfo { MachineInfo() { type = MT_UNKNOWN; version = 0; cap=0; } MachineInfo(const MachineInfo & /*copy*/) = default; MachineInfo(MachineType type, quint32 cap, QString loadername, QString brand, QString model, QString modelnumber, QString serial, QString series, QDateTime lastimported, int version, QDate purgeDate = QDate()) : type(type), cap(cap), loadername(loadername), brand(brand), model(model), modelnumber(modelnumber), serial(serial), series(series), lastimported(lastimported), version(version), purgeDate(purgeDate) {} MachineType type; quint32 cap; QString loadername; QString brand; QString model; QString modelnumber; QString serial; QString series; QDateTime lastimported; int version; QDate purgeDate; //! \brief List of text machine properties, like brand, model, etc... QHash properties; }; //extern map DefaultMCShortNames; //extern map DefaultMCLongNames; //extern map PressureReliefNames; //extern map CPAPModeNames; /*! \enum MCDataType \brief Data Types stored by Profile/Preferences objects, etc.. */ enum MCDataType { MC_bool = 0, MC_int, MC_long, MC_float, MC_double, MC_string, MC_datetime }; extern ChannelID AllAhiChannels; extern QVector ahiChannels; extern ChannelID NoChannel, SESSION_ENABLED, CPAP_SummaryOnly; extern ChannelID CPAP_IPAP, CPAP_IPAPLo, CPAP_IPAPHi, CPAP_EPAP, CPAP_EPAPLo, CPAP_EPAPHi, CPAP_Pressure, CPAP_PS, CPAP_PSMin, CPAP_PSMax, CPAP_Mode, CPAP_AHI, CPAP_PressureMin, CPAP_PressureMax, CPAP_Ramp, CPAP_RampTime, CPAP_RampPressure, CPAP_Obstructive, CPAP_Hypopnea, CPAP_AllApnea, CPAP_ClearAirway, CPAP_Apnea, CPAP_PB, CPAP_CSR, CPAP_LeakFlag, CPAP_ExP, CPAP_NRI, CPAP_VSnore, CPAP_VSnore2, CPAP_RERA, CPAP_PressurePulse, CPAP_FlowLimit, CPAP_SensAwake, CPAP_FlowRate, CPAP_MaskPressure, CPAP_MaskPressureHi, CPAP_RespEvent, CPAP_Snore, CPAP_MinuteVent, CPAP_RespRate, CPAP_TidalVolume, CPAP_PTB, CPAP_Leak, CPAP_LeakMedian, CPAP_LeakTotal, CPAP_MaxLeak, CPAP_FLG, CPAP_IE, CPAP_Te, CPAP_Ti, CPAP_TgMV, CPAP_UserFlag1, CPAP_UserFlag2, CPAP_UserFlag3, /*CPAP_BrokenSummary, CPAP_BrokenWaveform,*/ CPAP_RDI, CPAP_PresReliefMode, CPAP_PresReliefLevel, CPAP_Test1, CPAP_Test2, CPAP_PressureSet, CPAP_IPAPSet, CPAP_EPAPSet; extern ChannelID RMS9_E01, RMS9_E02, RMS9_SetPressure, RMS9_MaskOnTime; extern ChannelID CPAP_LargeLeak, PRS1_BND, PRS1_FlexMode, PRS1_FlexLevel, PRS1_HumidStatus, PRS1_HumidLevel, PRS1_HumidTargetTime, PRS1_MaskResistLock, CPAP_HumidSetting, PRS1_MaskResistSet, PRS1_HoseDiam, PRS1_AutoOn, PRS1_AutoOff, PRS1_MaskAlert, PRS1_ShowAHI; extern ChannelID INTELLIPAP_Unknown1, INTELLIPAP_Unknown2, INTP_SnoreFlag; //extern ChannelID SS_SenseAwakeLevel, SS_EPR, SS_EPRLevel, SS_Ramp; extern ChannelID OXI_Pulse, OXI_SPO2, OXI_Perf, OXI_PulseChange, OXI_SPO2Drop, OXI_Plethy; extern ChannelID Journal_Notes, Journal_Weight, Journal_BMI, Journal_ZombieMeter, Bookmark_Start, Bookmark_End, Bookmark_Notes, LastUpdated; extern ChannelID ZEO_SleepStage, ZEO_ZQ, ZEO_TotalZ, ZEO_TimeToZ, ZEO_TimeInWake, ZEO_TimeInREM, ZEO_TimeInLight, ZEO_TimeInDeep, ZEO_Awakenings, ZEO_AlarmReason, ZEO_SnoozeTime, ZEO_WakeTone, ZEO_WakeWindow, ZEO_AlarmType, ZEO_MorningFeel, ZEO_FirmwareVersion, ZEO_FirstAlarmRing, ZEO_LastAlarmRing, ZEO_FirstSnoozeTime, ZEO_LastSnoozeTime, ZEO_SetAlarmTime, ZEO_RiseTime; extern ChannelID POS_Orientation, POS_Inclination, POS_Movement; const QString GRP_CPAP = "CPAP"; const QString GRP_POS = "POS"; const QString GRP_OXI = "OXI"; const QString GRP_JOURNAL = "JOURNAL"; const QString GRP_SLEEP = "SLEEP"; #endif // MACHINE_COMMON_H OSCAR-code-v1.3.1/oscar/SleepLib/machine_loader.cpp000066400000000000000000000176061417327530600217470ustar00rootroot00000000000000/* SleepLib Machine Loader Class Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include "machine_loader.h" // GLOBALS: bool genpixmapinit = false; QList m_loaders; QPixmap * MachineLoader::genericCPAPPixmap; MachineLoader::MachineLoader() :QObject(nullptr) { #ifndef UNITTEST_MODE // no QPixmap without a QGuiApplication if (!genpixmapinit) { genericCPAPPixmap = new QPixmap(genericPixmapPath); genpixmapinit = true; } #endif m_abort = false; m_type = MT_UNKNOWN; m_status = NEUTRAL; m_ctx = nullptr; } MachineLoader::~MachineLoader() { } void MachineLoader::addSession(Session * sess) { sessionMutex.lock(); new_sessions[sess->session()] = sess; sessionMutex.unlock(); } void MachineLoader::finishAddingSessions() { // Using a map specifically so they are inserted in order. for (auto it=new_sessions.begin(), end=new_sessions.end(); it != end; ++it) { Session * sess = it.value(); Machine * mach = sess->machine(); mach->AddSession(sess); } new_sessions.clear(); } QPixmap & MachineLoader::getPixmap(QString series) { QHash::iterator it = m_pixmaps.find(series); if (it != m_pixmaps.end()) { return it.value(); } return *genericCPAPPixmap; } QString MachineLoader::getPixmapPath(QString series) { QHash::iterator it = m_pixmap_paths.find(series); if (it != m_pixmap_paths.end()) { return it.value(); } return genericPixmapPath; } void MachineLoader::queTask(ImportTask * task) { m_MLtasklist.push_back(task); } void MachineLoader::runTasks(bool threaded) { m_totalMLtasks = m_MLtasklist.size(); qDebug() << "MachineLoader::runTasks MLtasklist size is" << m_totalMLtasks; if (m_totalMLtasks == 0) return; emit setProgressMax(m_totalMLtasks); m_currentMLtask=0; threaded=AppSetting->multithreading(); if ( ! threaded) { while (!m_MLtasklist.isEmpty() && !m_abort) { ImportTask * task = m_MLtasklist.takeFirst(); task->run(); // update progress bar m_currentMLtask++; emit setProgressValue(m_currentMLtask); QApplication::processEvents(); delete task; } } else { ImportTask * task = m_MLtasklist[0]; QThreadPool * threadpool = QThreadPool::globalInstance(); while (!m_abort) { if (threadpool->tryStart(task)) { m_MLtasklist.pop_front(); if (!m_MLtasklist.isEmpty()) { // next task to be run task = m_MLtasklist[0]; // update progress bar emit setProgressValue(++m_currentMLtask); QApplication::processEvents(); } else { // job list finished break; } } //QThread::sleep(100); } QThreadPool::globalInstance()->waitForDone(-1); } if (m_abort) { // delete remaining tasks and clear task list for (auto & task : m_MLtasklist) { delete task; } m_MLtasklist.clear(); } } QList GetLoaders(MachineType mt) { QList list; for (int i=0; i < m_loaders.size(); ++i) { if (mt == MT_UNKNOWN) { list.push_back(m_loaders.at(i)); } else { MachineType mtype = m_loaders.at(i)->type(); if (mtype == mt) { list.push_back(m_loaders.at(i)); } } } return list; } MachineLoader * lookupLoader(Machine * m) { for (int i=0; i < m_loaders.size(); ++i) { MachineLoader * loader = m_loaders.at(i); if (loader->loaderName() == m->loaderName()) return loader; } return nullptr; } MachineLoader * lookupLoader(QString loaderName) { for (int i=0; i < m_loaders.size(); ++i) { MachineLoader * loader = m_loaders.at(i); if (loader->loaderName() == loaderName) return loader; } return nullptr; } void RegisterLoader(MachineLoader *loader) { loader->initChannels(); m_loaders.push_back(loader); } void DestroyLoaders() { for (auto & loader : m_loaders) { delete(loader); } m_loaders.clear(); } /* QList CPAPLoader::eventFlags(Day * day) { Machine * mach = day->machine(MT_CPAP); QList list; if (mach->loader() != this) { qDebug() << "Trying to ask" << loaderName() << "for" << mach->loaderName() << "data"; return list; } for (int i = 0; i < ahiChannels.size(); i++) list.push_back(ahiChannels.at(i)); // list.push_back(CPAP_ClearAirway); // list.push_back(CPAP_AllApnea); // list.push_back(CPAP_Obstructive); // list.push_back(CPAP_Hypopnea); // list.push_back(CPAP_Apnea); return list; } */ bool uncompressFile(QString infile, QString outfile) { if (!infile.endsWith(".gz",Qt::CaseInsensitive)) { qDebug() << "uncompressFile()" << outfile << "missing .gz extension???"; return false; } if (QFile::exists(outfile)) { qDebug() << "uncompressFile()" << outfile << "already exists"; return false; } // Get file length from inside gzip file QFile fi(infile); if (!fi.open(QFile::ReadOnly) || !fi.seek(fi.size() - 4)) { return false; } unsigned char ch[4]; fi.read((char *)ch, 4); quint32 datasize = ch[0] | (ch [1] << 8) | (ch[2] << 16) | (ch[3] << 24); // Open gzip file for reading gzFile f = gzopen(infile.toLatin1(), "rb"); if (!f) { return false; } // Decompressed header and data block char * buffer = new char [datasize]; gzread(f, buffer, datasize); gzclose(f); QFile out(outfile); if (out.open(QFile::WriteOnly)) { out.write(buffer, datasize); out.close(); } delete [] buffer; return true; } bool compressFile(QString infile, QString outfile) { if (outfile.isEmpty()) { outfile = infile + ".gz"; } else if (!outfile.endsWith(".gz")) { outfile += ".gz"; } if (QFile::exists(outfile)) { qDebug() << "compressFile()" << outfile << "already exists"; return false; } QFile f(infile); if (!f.exists(infile)) { qDebug() << "compressFile()" << infile << "does not exist"; return false; } qint64 size = f.size(); if (!f.open(QFile::ReadOnly)) { qDebug() << "compressFile() Couldn't open" << infile; return false; } char *buf = new char [size]; if (!f.read(buf, size)) { delete [] buf; qDebug() << "compressFile() Couldn't read all of" << infile; return false; } f.close(); gzFile gz = gzopen(outfile.toLatin1(), "wb"); //gzbuffer(gz,65536*2); if (!gz) { qDebug() << "compressFile() Couldn't open" << outfile << "for writing"; delete [] buf; return false; } gzwrite(gz, buf, size); gzclose(gz); delete [] buf; return true; } int MachineLoader::Open(const QStringList & paths) { int i, skipped = 0; int size = paths.size(); for (i=0; i < size; i++) { if (isAborted()) { break; } QString filename = paths[i]; int res = OpenFile(filename); if (res < 0) { break; } if (res == 0) { // Should we report on skipped count? skipped++; } emit setProgressValue(i+1); QCoreApplication::processEvents(); } return i; } OSCAR-code-v1.3.1/oscar/SleepLib/machine_loader.h000066400000000000000000000122401417327530600214010ustar00rootroot00000000000000/* SleepLib MachineLoader Base Class Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef MACHINE_LOADER_H #define MACHINE_LOADER_H #include #include #include #include "profiles.h" #include "machine.h" #ifdef _MSC_VER #include "QtZlib/zlib.h" #else #include "zlib.h" #endif #ifdef UNITTEST_MODE #define private public #define protected public #endif class MachineLoader; // forward class ImportContext; enum DeviceStatus { NEUTRAL, IMPORTING, LIVE, DETECTING }; const QString genericPixmapPath = ":/icons/mask.png"; /*! \class MachineLoader \brief Base class to derive a new Machine importer from */ class MachineLoader: public QObject { Q_OBJECT //friend class ImportThread; //friend class Machine; public: MachineLoader(); virtual ~MachineLoader(); void SetContext(ImportContext* ctx) { m_ctx = ctx; } inline ImportContext* context() { return m_ctx; } //! \brief Detect if the given path contains a valid folder structure virtual bool Detect(const QString & path) = 0; //! \brief Look up and return machine model information stored at path virtual MachineInfo PeekInfo(const QString & path) { Q_UNUSED(path); return MachineInfo(); } //! \brief Override this to scan path and detect new machine data virtual int Open(const QString & path) = 0; //! \brief Load all of the given files and update dialog with progress (for non-CPAP devices) virtual int Open(const QStringList & paths); //! \brief Load a specific (non-CPAP) file virtual int OpenFile(const QString & path) { Q_UNUSED(path); return 0; } //! \brief Override to returns the Version number of this MachineLoader virtual int Version() = 0; //! \brief Name filter for files for this loader virtual QStringList getNameFilter() { return QStringList(""); } // !\\brief Used internally by loaders, override to return base MachineInfo record virtual MachineInfo newInfo() { return MachineInfo(); } //! \brief Override to returns the class name of this MachineLoader virtual const QString & loaderName() = 0; virtual void process() {} virtual void initChannels() {} void addSession(Session * sess); inline MachineType type() { return m_type; } inline DeviceStatus status() { return m_status; } inline void setStatus(DeviceStatus status) { m_status = status; } QPixmap & getPixmap(QString series); QString getPixmapPath(QString series); void queTask(ImportTask * task); //! \brief Process Task list using all available threads. void runTasks(bool threaded = false); inline int countTasks() { return m_MLtasklist.size(); } inline bool isAborted() { return m_abort; } inline void abort() { m_abort = true; } QMutex sessionMutex; QMutex saveMutex; public slots: void abortImport() { abort(); } signals: void updateProgress(int cnt, int total); void setProgressMax(int max); void setProgressValue(int val); void updateMessage(QString); void deviceReportsUsageOnly(MachineInfo & info); void deviceIsUntested(MachineInfo & info); void deviceIsUnsupported(MachineInfo & info); protected: ImportContext* m_ctx; void finishAddingSessions(); static QPixmap * genericCPAPPixmap; int m_currentMLtask; int m_totalMLtasks; bool m_abort; DeviceStatus m_status; MachineType m_type; QString m_class; QMap new_sessions; QHash m_pixmaps; QHash m_pixmap_paths; private: QList m_MLtasklist; }; class CPAPLoader:public MachineLoader { Q_OBJECT public: CPAPLoader() : MachineLoader() {} virtual ~CPAPLoader() {} //virtual QList eventFlags(Day * day); virtual QString PresReliefLabel() { return QString(""); } virtual ChannelID PresReliefMode() { return NoChannel; } virtual ChannelID PresReliefLevel() { return NoChannel; } //virtual ChannelID HumidifierConnected() { return NoChannel; } //virtual ChannelID HumidifierLevel() { return CPAP_HumidSetting; } virtual ChannelID CPAPModeChannel() { return CPAP_Mode; } virtual void initChannels() {} }; class ImportPath { public: ImportPath() { path = QString(); loader = nullptr; } // ImportPath(const ImportPath & copy) { // loader = copy.loader; // path = copy.path; // } ImportPath(QString path, MachineLoader * loader) : path(path), loader(loader) {} QString path; MachineLoader * loader; }; // Put in machine loader class as static?? void RegisterLoader(MachineLoader *loader); QList GetLoaders(MachineType mt = MT_UNKNOWN); MachineLoader * lookupLoader(Machine * m); MachineLoader * lookupLoader(QString loaderName); void DestroyLoaders(); // Why here? Where are these called? bool compressFile(QString inpath, QString outpath = ""); bool uncompressFile(QString infile, QString outfile); #endif //MACHINE_LOADER_H OSCAR-code-v1.3.1/oscar/SleepLib/preferences.cpp000066400000000000000000000252121417327530600213060ustar00rootroot00000000000000/* SleepLib Preferences Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include "windows.h" #include "lmcons.h" #endif #include "common.h" #include "preferences.h" const QString &getUserName() { static QString userName; userName = getenv("USER"); if (userName.isEmpty()) { userName = QObject::tr("Windows User"); #if defined (Q_OS_WIN) #if defined(UNICODE) if (QSysInfo::WindowsVersion >= QSysInfo::WV_NT) { TCHAR winUserName[UNLEN + 1]; // UNLEN is defined in LMCONS.H DWORD winUserNameSize = sizeof(winUserName); GetUserNameW(winUserName, &winUserNameSize); userName = QString::fromStdWString(winUserName); } else #endif { char winUserName[UNLEN + 1]; // UNLEN is defined in LMCONS.H DWORD winUserNameSize = sizeof(winUserName); GetUserNameA(winUserName, &winUserNameSize); userName = QString::fromLocal8Bit(winUserName); } #endif } return userName; } QString GetAppData() { QSettings settings; QString HomeAppData = settings.value("Settings/AppData").toString(); if (HomeAppData.isEmpty()) { HomeAppData = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)+"/"+getModifiedAppData(); } return HomeAppData; } Preferences::Preferences() { p_name = "Preferences"; p_path = GetAppData(); } Preferences::Preferences(QString name, QString filename) { if (name.endsWith(STR_ext_XML)) { p_name = name.section(".", 0, 0); } else { p_name = name; } if (filename.isEmpty()) { p_filename = GetAppData() + "/" + p_name + STR_ext_XML; } else { if (!filename.contains("/")) { p_filename = GetAppData() + "/"; } else { p_filename = ""; } p_filename += filename; if (!p_filename.endsWith(STR_ext_XML)) { p_filename += STR_ext_XML; } } } Preferences::~Preferences() { //Save(); // Don't..Save calls a virtual function. } /*int Preferences::GetCode(QString s) { int prefcode=0; for (QHash::iterator i=p_codes.begin(); i!=p_codes.end(); i++) { if (i.value()==s) return i.key(); prefcode++; } p_codes[prefcode]=s; return prefcode; }*/ const QString Preferences::Get(QString name) { QString temp; QChar obr = QChar('{'); QChar cbr = QChar('}'); QString t, a, ref; // How I miss Regular Expressions here.. if (p_preferences.find(name) != p_preferences.end()) { temp = ""; t = p_preferences[name].toString(); if (p_preferences[name].type() != QVariant::String) { return t; } } else { t = name; // parse the string.. } while (t.contains(obr)) { temp += t.section(obr, 0, 0); a = t.section(obr, 1); if (a.startsWith("{")) { temp += obr; t = a.section(obr, 1); continue; } ref = a.section(cbr, 0, 0); if (ref.toLower() == "home") { temp += GetAppData(); } else if (ref.toLower() == "user") { temp += getUserName(); } else if (ref.toLower() == "sep") { // redundant in QT temp += "/"; } else { temp += Get(ref); } t = a.section(cbr, 1); } temp += t; temp.replace("}}", "}"); // Make things look a bit better when escaping braces. return temp; } bool Preferences::Open(QString filename) { if (!filename.isEmpty()) { p_filename = filename; } QDomDocument doc(p_name); QFile file(p_filename); qDebug() << "Opening " << p_filename.toLocal8Bit().data(); if (!file.open(QIODevice::ReadOnly)) { // qWarning() << "Could not open" << p_filename.toLocal8Bit().data() << " Error: " << file.error(); qWarning() << "Could not open preferences file for reading, error code" << file.error() << file.errorString(); return false; } QString errorMsg; int errorLine; int errorColumn; if (!doc.setContent(&file,false, &errorMsg, &errorLine, &errorColumn)) { qWarning() << "Invalid XML Content in" << p_filename.toLocal8Bit().data(); qWarning() << "Error:" << errorMsg << "in line" << errorLine << ":" << errorColumn; return false; } file.close(); QDomElement root = doc.documentElement(); if (root.tagName() != STR_AppName) { if (root.tagName() == "SleepyHead" ) { QString msg = QObject::tr("Using ") + p_filename + QObject::tr(", found SleepyHead -\n") + QObject::tr( "You must run the OSCAR Migration Tool"); QMessageBox::warning(nullptr, STR_MessageBox_Error, msg, QMessageBox::Ok); exit(1); } return false; } root = root.firstChildElement(); if (root.tagName() != p_name) { return false; } bool ok; p_preferences.clear(); QDomNode n = root.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { QString name = e.tagName(); QString type = e.attribute("type").toLower(); QString value = e.text(); if (type == "double") { double d; d = value.toDouble(&ok); if (ok) { p_preferences[name] = d; } else { qDebug() << "XML Error:" << name << "=" << value << "??"; } } else if (type == "qlonglong") { qint64 d; d = value.toLongLong(&ok); if (ok) { p_preferences[name] = d; } else { qDebug() << "XML Error:" << name << "=" << value << "??"; } } else if (type == "int") { int d; d = value.toInt(&ok); if (ok) { p_preferences[name] = d; } else { qDebug() << "XML Error:" << name << "=" << value << "??"; } } else if (type == "bool") { QString v = value.toLower(); if ((v == "true") || (v == "on") || (v == "yes")) { p_preferences[name] = true; } else if ((v == "false") || (v == "off") || (v == "no")) { p_preferences[name] = false; } else { int d; d = value.toInt(&ok); if (ok) { p_preferences[name] = d != 0; } else { qDebug() << "XML Error:" << name << "=" << value << "??"; } } } else if (type == "qdatetime") { QDateTime d; d = QDateTime::fromString(value, "yyyy-MM-dd HH:mm:ss"); if (d.isValid()) { p_preferences[name] = d; } else { qWarning() << "XML Error: Invalid DateTime record" << name << value; } } else if (type == "qtime") { QTime d; d = QTime::fromString(value, "hh:mm:ss"); if (d.isValid()) { p_preferences[name] = d; } else { qWarning() << "XML Error: Invalid Time record" << name << value; } } else { p_preferences[name] = value; } } n = n.nextSibling(); } root = root.nextSiblingElement(); ////////////////////////////////////////////////////////////////////////////////////// // This is a dirty hack to clean up a legacy issue // The old Profile system used to have machines in Profile.xml // We need to clean up this mistake up here, because C++ polymorphism won't otherwise // let us open properly in constructor ////////////////////////////////////////////////////////////////////////////////////// if ((p_name == "Profile") && (root.tagName().toLower() == "machines")) { // Save this sucker QDomDocument doc("Machines"); doc.appendChild(root); QFile file(p_path+"/machines.xml"); // Don't do anything if machines.xml already exists.. the user ran the old version! if (!file.exists()) { if (!file.open(QFile::WriteOnly)) { qWarning() << "Could not open" << filename << "for writing, error code" << file.error() << file.errorString(); } else { file.write(doc.toByteArray()); file.close(); } } } return true; } bool Preferences::Save(QString filename) { if (!filename.isEmpty()) { p_filename = filename; } QDomDocument doc(p_name); QDomProcessingInstruction pi = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""); doc.appendChild(pi); QDomElement droot = doc.createElement(STR_AppName); doc.appendChild(droot); QDomElement root = doc.createElement(p_name); droot.appendChild(root); for (QHash::iterator i = p_preferences.begin(); i != p_preferences.end(); i++) { QVariant::Type type = i.value().type(); if (type == QVariant::Invalid) { continue; } QDomElement cn = doc.createElement(i.key()); cn.setAttribute("type", i.value().typeName()); if (type == QVariant::DateTime) { cn.appendChild(doc.createTextNode(i.value().toDateTime().toString("yyyy-MM-dd HH:mm:ss"))); } else if (type == QVariant::Time) { cn.appendChild(doc.createTextNode(i.value().toTime().toString("hh:mm:ss"))); } else { cn.appendChild(doc.createTextNode(i.value().toString())); } root.appendChild(cn); } QFile file(p_filename); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "Could not open" << p_filename << "for writing, error code" << file.error() << file.errorString(); return false; } QTextStream ts(&file); ts.setCodec("UTF-8"); ts.setGenerateByteOrderMark(true); ts << doc.toString(); file.close(); return true; } AppWideSetting *AppSetting = nullptr; OSCAR-code-v1.3.1/oscar/SleepLib/preferences.h000066400000000000000000000123771417327530600207630ustar00rootroot00000000000000/* SleepLib Preferences Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef PREFERENCES_H #define PREFERENCES_H #include #include #include #include #include #include const QString STR_ext_XML = ".xml"; extern QString GetAppData(); //returns app data path plus trailing path separator. inline QString PrefMacro(QString s) { return "{" + s + "}"; } //! \brief Returns a QString containing the Username, according to the Operating System const QString &getUserName(); /*! \class Preferences \author Mark Watkins \brief Holds a group of preference variables */ class Preferences { public: //! \brief Constructs a Preferences object 'name', and remembers sets the filename Preferences(QString name, QString filename = ""); Preferences(); virtual ~Preferences(); //! \brief Returns a QString containing preference 'name', processing any {} macros const QString Get(QString name); //! \brief Returns the QVariant value of the selected preference.. Note, preference must exist, and will not expand {} macros QVariant &operator[](QString name) { return p_preferences[name]; } //! \brief Sets the Preference 'name' to QVariant 'value' void Set(QString name, QVariant value) { p_preferences[name] = value; } //! \brief Returns true if preference 'name' exists bool contains(QString name) { return (p_preferences.contains(name)); } //! \brief Create a preference and set the default if it doesn't exists QVariant & init(QString name, QVariant value) { auto it = p_preferences.find(name); if (it == p_preferences.end()) { return p_preferences[name] = value; } return it.value(); } //! \brief Returns true if preference 'name' exists, and contains a boolean true value bool ExistsAndTrue(QString name) { QHash::iterator i = p_preferences.find(name); if (i == p_preferences.end()) { return false; } return i.value().toBool(); } //! \brief Removes preference 'name' from this Preferences group void Erase(QString name) { QHash::iterator i = p_preferences.find(name); if (i != p_preferences.end()) { p_preferences.erase(i); } } //! \brief Rename a preference void Rename (QString oldname, QString newname) { if (contains(oldname)) { QVariant val = Get(oldname); Set(newname, val); Erase(oldname); } } //! \brief Opens, processes the XML for this Preferences group, loading all preferences stored therein. //! \note If filename is empty, it will use the one specified in the constructor //! \returns true if succesful bool Open(QString filename = ""); //! \brief Saves all preferences to XML file. //! \note If filename is empty, it will use the one specified in the constructor //! \returns true if succesful bool Save(QString filename = ""); //! \note Sets a comment string whici will be stored in the XML void SetComment(const QString &str) { p_comment = str; } //! \brief Finds a given preference. //! \returns a QHash::iterator pointing to the preference named 'key', or an empty end() iterator inline QHash::iterator find(QString key) { return p_preferences.find(key); } //! \brief Returns an empty iterator pointing to the end of the preferences list inline QHash::iterator end() { return p_preferences.end(); } //! \brief Returns an iterator pointing to the first item in the preferences list inline QHash::iterator begin() { return p_preferences.begin(); } //int GetCode(QString name); // For registering/looking up new preference code. //! \brief Stores all the variants indexed by a QString name for this Preferences object QHash p_preferences; void setPath(const QString &path) { p_path = path; } void setFilename(const QString &filename) { p_filename = filename; } const QString name() { return p_name; } protected: //QHash p_codes; QString p_comment; QString p_name; QString p_filename; QString p_path; }; //! \brief Main Preferences Object used throughout the application // extern Preferences PREF; // Parent class for subclasses that manipulate the profile. class PrefSettings { public: PrefSettings(Preferences *pref) : m_pref(pref) { } inline void setPref(QString name, QVariant value) { (*m_pref)[name] = value; } inline QVariant & initPref(QString name, QVariant value) { return m_pref->init(name, value); } inline QVariant & getPref(QString name) const { return (*m_pref)[name]; } void setPrefObject(Preferences *pref) { m_pref = pref; } public: Preferences *m_pref; }; #include "appsettings.h" #endif // PREFERENCES_H OSCAR-code-v1.3.1/oscar/SleepLib/profiles.cpp000066400000000000000000001523701417327530600206360ustar00rootroot00000000000000/* SleepLib Profiles Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "preferences.h" #include "profiles.h" #include "machine.h" #include "machine_common.h" #include "machine_loader.h" #include "mainwindow.h" #include "translation.h" #include "version.h" extern MainWindow *mainwin; Preferences *p_pref; Preferences *p_layout; Profile *p_profile; Profile::Profile(QString path, bool open) : is_first_day(true), m_opened(false) { p_name = STR_GEN_Profile; if (path.isEmpty()) { p_path = GetAppData(); } else { p_path = path; } (*this)[STR_GEN_DataFolder] = p_path; path = path.replace("\\", "/"); if (!p_path.endsWith("/")) { p_path += "/"; } p_filename = p_path + p_name + STR_ext_XML; m_machlist.clear(); if (open) { Open(p_filename); } Set(STR_GEN_DataFolder, QString("{home}/Profiles/{UserName}")); // Reset import warnings when running a new version of OSCAR init(STR_PREF_VersionString, getVersion().toString()); Version prefVersion = Version((*this)[STR_PREF_VersionString].toString()); if (prefVersion != getVersion()) { qDebug() << " Resetting import warnings: version" << prefVersion << "to" << getVersion(); Set(STR_PREF_VersionString, getVersion().toString()); this->Erase(STR_IS_WarnOnUntestedMachine); this->Erase(STR_IS_WarnOnUnexpectedData); } doctor = new DoctorInfo(this); user = new UserInfo(this); cpap = new CPAPSettings(this); oxi = new OxiSettings(this); appearance = new AppearanceSettings(this); session = new SessionSettings(this); general = new UserSettings(this); if (open) { OpenMachines(); m_opened=true; } } Profile::~Profile() { if (m_opened) { removeLock(); } // delete machine objects... for (auto & mach : m_machlist) { delete mach; } for (auto & day : daylist) { delete day; } delete user; delete doctor; delete cpap; delete oxi; delete appearance; delete session; delete general; } bool Profile::Save(QString filename) { if (m_opened) { return Preferences::Save(filename) && StoreMachines(); } else return false; } bool Profile::removeLock() { QString filename=p_path+"/lockfile"; QFile file(filename); return file.remove(); } QString Profile::checkLock() { QString filename=p_path+"/lockfile"; QFile file(filename); if (!file.exists()) return QString(); file.open(QFile::ReadOnly); QString lockhost = file.readLine(1024).trimmed(); return lockhost; } // Properties for machines.xml: const QString STR_PROP_Brand = "brand"; const QString STR_PROP_Model = "model"; const QString STR_PROP_Series = "series"; const QString STR_PROP_ModelNumber = "modelnumber"; const QString STR_PROP_SubModel = "submodel"; const QString STR_PROP_Serial = "serial"; const QString STR_PROP_DataVersion = "dataversion"; const QString STR_PROP_LastImported = "lastimported"; const QString STR_PROP_PurgeDate = "purgedate"; void Profile::addLock() { QFile lockfile(p_path+"lockfile"); lockfile.open(QFile::WriteOnly); QByteArray ba; ba.append(QHostInfo::localHostName()); lockfile.write(ba); lockfile.close(); } bool Profile::OpenMachines() { if (m_machlist.size() > 0) { qCritical() << "Skipping redundant call to Profile::OpenMachines"; return true; } QString filename = p_path+"machines.xml"; QFile file(filename); if (!file.open(QFile::ReadOnly)) { qWarning() << "Could not open" << filename.toLocal8Bit().data() << "for reading, error code" << file.error() << file.errorString(); return false; } // qDebug() << "OpenMachines opened" << filename.toLocal8Bit().data(); QDomDocument doc("machines.xml"); if (!doc.setContent(&file)) { qWarning() << "Invalid XML Content in" << filename.toLocal8Bit().data(); return false; } file.close(); QDomElement root = doc.firstChild().toElement(); if (root.tagName().toLower() != "machines") { qWarning() << "No Machines Tag in machines.xml"; return false; } QDomElement elem = root.firstChildElement(); while (!elem.isNull()) { QString pKey = elem.tagName(); if (pKey.toLower() != "machine") { qWarning() << "Profile::OpenMachines() pKey!=\"machine\""; elem = elem.nextSiblingElement(); continue; } int m_id; bool ok; m_id = elem.attribute("id", "").toInt(&ok); int mt; mt = elem.attribute("type", "").toInt(&ok); MachineType m_type = (MachineType)mt; QString m_class = elem.attribute("class", ""); MachineInfo info; info.type = m_type; info.loadername = m_class; QHash prop; QDomElement e = elem.firstChildElement(); for (; !e.isNull(); e = e.nextSiblingElement()) { QString pKey = e.tagName(); QString key = pKey.toLower(); if (key == STR_PROP_Brand) { info.brand = e.text(); } else if (key == STR_PROP_Model) { info.model = e.text(); } else if (key == STR_PROP_ModelNumber) { info.modelnumber = e.text(); } else if (key == STR_PROP_Serial) { info.serial = e.text(); } else if (key == STR_PROP_Series) { info.series = e.text(); } else if (key == STR_PROP_DataVersion) { info.version = e.text().toInt(); } else if (key == STR_PROP_LastImported) { info.lastimported = QDateTime::fromString(e.text(), Qt::ISODate); } else if (key == STR_PROP_PurgeDate) { info.purgeDate = QDate::fromString(e.text(), Qt::ISODate); } else if (key == "properties") { QDomElement pe = e.firstChildElement(); for (; !pe.isNull(); pe = pe.nextSiblingElement()) { prop[pe.tagName()] = pe.text(); } } else { // skip any old rubbish if ((key == "backuppath") || (key == "path") || (key == "submodel")) continue; prop[pKey] = e.text(); } } Machine *m = nullptr; // Create Machine needs a profile passed to it.. m = CreateMachine(info, m_id); if (m) m->info.properties = prop; elem = elem.nextSiblingElement(); } return true; } bool Profile::StoreMachines() { QDomDocument doc("Machines"); QDomElement mach = doc.createElement("machines"); for (int i=0; iid()); me.setAttribute("type", (int)m->type()); me.setAttribute("class", m->loaderName()); QDomElement pe = doc.createElement("properties"); me.appendChild(pe); for (QHash::iterator j = m->info.properties.begin(); j != m->info.properties.end(); j++) { QDomElement pp = doc.createElement(j.key()); pp.appendChild(doc.createTextNode(j.value())); pe.appendChild(pp); } QDomElement mp = doc.createElement(STR_PROP_Brand); mp.appendChild(doc.createTextNode(m->brand())); me.appendChild(mp); mp = doc.createElement(STR_PROP_Model); mp.appendChild(doc.createTextNode(m->model())); me.appendChild(mp); mp = doc.createElement(STR_PROP_ModelNumber); mp.appendChild(doc.createTextNode(m->modelnumber())); me.appendChild(mp); mp = doc.createElement(STR_PROP_Serial); mp.appendChild(doc.createTextNode(m->serial())); me.appendChild(mp); mp = doc.createElement(STR_PROP_Series); mp.appendChild(doc.createTextNode(m->series())); me.appendChild(mp); mp = doc.createElement(STR_PROP_DataVersion); mp.appendChild(doc.createTextNode(QString::number(m->version()))); me.appendChild(mp); mp = doc.createElement(STR_PROP_LastImported); mp.appendChild(doc.createTextNode(m->lastImported().toString(Qt::ISODate))); me.appendChild(mp); mp = doc.createElement(STR_PROP_PurgeDate); mp.appendChild(doc.createTextNode(m->purgeDate().toString(Qt::ISODate))); me.appendChild(mp); mach.appendChild(me); } doc.appendChild(mach); QString filename = p_path+"machines.xml"; QFile file(filename); if (!file.open(QFile::WriteOnly)) { qWarning() << "Could not open" << filename << "for writing, error code" << file.error() << file.errorString(); return false; } file.write(doc.toByteArray()); return true; } qint64 Profile::diskSpaceSummaries() { qint64 size = 0; for (auto & mach : m_machlist) { size += mach->diskSpaceSummaries(); } return size; } qint64 Profile::diskSpaceEvents() { qint64 size = 0; for (auto & mach : m_machlist) { size += mach->diskSpaceEvents(); } return size; } qint64 Profile::diskSpaceBackups() { qint64 size = 0; for (auto & mach : m_machlist) { size += mach->diskSpaceBackups(); } return size; } qint64 Profile::diskSpace() { return (diskSpaceSummaries()+diskSpaceEvents()+diskSpaceBackups()); } void Profile::forceResmedPrefs() { session->setBackupCardData(true); session->setDaySplitTime(QTime(12,0,0)); session->setIgnoreShortSessions(0); session->setCombineCloseSessions(0); session->setLockSummarySessions(true); general->setPrefCalcPercentile(95.0); // 95% general->setPrefCalcMiddle(0); // Median (50%) general->setPrefCalcMax(1); // 99.9th percentile max } #if defined(Q_OS_WIN) class Environment { public: Environment(); QStringList path(); QString searchInDirectory(const QStringList & execs, QString directory); QString searchInPath(const QString &executable, const QStringList & additionalDirs = QStringList()); QProcessEnvironment env; }; Environment::Environment() { env = QProcessEnvironment::systemEnvironment(); } QStringList Environment::path() { return env.value(QLatin1String("PATH"), "").split(';'); } QString Environment::searchInDirectory(const QStringList & execs, QString directory) { const QChar slash = QLatin1Char('/'); if (directory.isEmpty()) return QString(); if (!directory.endsWith(slash)) directory += slash; for (auto & exec : execs) { QFileInfo fi(directory + exec); if (fi.exists() && fi.isFile() && fi.isExecutable()) return fi.absoluteFilePath(); } return QString(); } QString Environment::searchInPath(const QString &executable, const QStringList & additionalDirs) { if (executable.isEmpty()) return QString(); QString exec = QDir::cleanPath(executable); QFileInfo fi(exec); QStringList execs(exec); if (fi.suffix().isEmpty()) { QStringList extensions = env.value(QLatin1String("PATHEXT")).split(QLatin1Char(';')); foreach (const QString &ext, extensions) { QString tmp = executable + ext.toLower(); if (fi.isAbsolute()) { if (QFile::exists(tmp)) return tmp; } else { execs << tmp; } } } if (fi.isAbsolute()) return exec; QSet alreadyChecked; foreach (const QString &dir, additionalDirs) { if (alreadyChecked.contains(dir)) continue; alreadyChecked.insert(dir); QString tmp = searchInDirectory(execs, dir); if (!tmp.isEmpty()) return tmp; } if (executable.indexOf(QLatin1Char('/')) != -1) return QString(); for (auto & p : path()) { if (alreadyChecked.contains(p)) continue; alreadyChecked.insert(p); QString tmp = searchInDirectory(execs, QDir::fromNativeSeparators(p)); if (!tmp.isEmpty()) return tmp; } return QString(); } #endif // Borrowed from QtCreator (http://stackoverflow.com/questions/3490336/how-to-reveal-in-finder-or-show-in-explorer-with-qt) void showInGraphicalShell(const QString & pathIn) { // Mac, Windows support folder or file. #if defined(Q_OS_WIN) QWidget * parent = NULL; Environment env; const QString explorer = env.searchInPath(QLatin1String("explorer.exe")); if (explorer.isEmpty()) { QMessageBox::warning(parent, QObject::tr("Launching Windows Explorer failed"), QObject::tr("Could not find explorer.exe in path to launch Windows Explorer.")); return; } QString param; //if (!QFileInfo(pathIn).isDir()) param = QLatin1String("/select,"); param += QDir::toNativeSeparators(pathIn); QProcess::startDetached(explorer, QStringList(param)); #elif defined(Q_OS_MAC) // Q_UNUSED(parent) QStringList scriptArgs; scriptArgs << QLatin1String("-e") << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"") .arg(pathIn); QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs); scriptArgs.clear(); scriptArgs << QLatin1String("-e") << QLatin1String("tell application \"Finder\" to activate"); QProcess::execute("/usr/bin/osascript", scriptArgs); #else Q_UNUSED(pathIn); // we cannot select a file here, because no file browser really supports it... /* const QFileInfo fileInfo(pathIn); const QString folder = fileInfo.absoluteFilePath(); const QString app = Utils::UnixUtils::fileBrowser(Core::ICore::instance()->settings()); QProcess browserProc; const QString browserArgs = Utils::UnixUtils::substituteFileBrowserParameters(app, folder); if (debug) qDebug() << browserArgs; bool success = browserProc.startDetached(browserArgs); const QString error = QString::fromLocal8Bit(browserProc.readAllStandardError()); success = success && error.isEmpty(); if (!success) { QMessageBox::warning(NULL,STR_MessageBox_Error, "Could not find the file browser for your system, you will have to find your profile directory yourself."+"\n\n"+error, QMessageBox::Ok); // showGraphicalShellError(parent, app, error); }*/ #endif } int dirCount(QString path) { QDir dir(path); QStringList list = dir.entryList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); return list.size(); } void Profile::DataFormatError(Machine *m) { QString msg; msg = ""+QObject::tr("OSCAR %1 needs to upgrade its database for %2 %3 %4"). arg(getVersion().displayString()). arg(m->brand()).arg(m->model()).arg(m->serial()) + "

"; bool backups = false; if (p_profile->session->backupCardData()) { QString bpath = m->getBackupPath(); int cnt = dirCount(bpath); if (cnt > 0) backups = true; } if (backups) { msg = msg + QObject::tr("OSCAR maintains a backup of your devices data card that it uses for this purpose.")+ "

"; msg = msg + QObject::tr("Your old machine data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.") + "

"; backups = true; } else { msg = msg + ""+STR_MessageBox_Warning+": "+QObject::tr("OSCAR does not yet have any automatic card backups stored for this device.") + "

"; msg = msg + QObject::tr("This means you will need to import this machine data again afterwards from your own backups or data card.") + "

"; } msg += ""+QObject::tr("Important:")+" "+QObject::tr("Once you upgrade, you cannot use this profile with the previous version anymore.")+"

"+ QObject::tr("If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again.")+ "

"; msg = msg + ""+QObject::tr("Are you ready to upgrade, so you can run the new version of OSCAR?")+""; QMessageBox * question = new QMessageBox(QMessageBox::Warning, QObject::tr("Machine Database Changes"), msg, QMessageBox::Yes | QMessageBox::No); question->setDefaultButton(QMessageBox::Yes); QFont font("Sans Serif", 11, QFont::Normal); question->setFont(font); if (question->exec() == QMessageBox::Yes) { if (!m->Purge(3478216)) { // Purge failed.. probably a permissions error.. let the user deal with it. QMessageBox::critical(nullptr, STR_MessageBox_Error, QObject::tr("Sorry, the purge operation failed, which means this version of OSCAR can't start.")+"\n\n"+ QObject::tr("The machine data folder needs to be removed manually.")+"\n\n"+ QObject::tr("This folder currently resides at the following location:")+"\n\n"+ QDir::toNativeSeparators(Get(p_preferences[STR_GEN_DataFolder].toString())), QMessageBox::Ok); QApplication::exit(-1); } // Note: I deliberately haven't added a Profile help for this if (backups) { MachineLoader * loader = lookupLoader(m); int c = mainwin->importCPAP(ImportPath(m->getBackupPath(), loader), QObject::tr("Rebuilding from %1 Backup").arg(m->brand())); if (c >= 0) { // Make sure the updated version gets saved, even if there were no sessions to import. mainwin->finishCPAPImport(); } } else { if (!p_profile->session->backupCardData()) { // Automatic backups not available for Intellipap users yet, so don't taunt them.. if (m->loaderName() != STR_MACH_Intellipap) { if (QMessageBox::question(nullptr, STR_MessageBox_Question, QObject::tr("Would you like to switch on automatic backups, so next time a new version of OSCAR needs to do so, it can rebuild from these?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)) { p_profile->session->setBackupCardData(true); } } } QMessageBox::information(nullptr, STR_MessageBox_Information, QObject::tr("OSCAR will now start the import wizard so you can reinstall your %1 data.").arg(m->brand()) ,QMessageBox::Ok, QMessageBox::Ok); mainwin->startImportDialog(); } p_profile->Save(); delete question; } else { delete question; QMessageBox::information(nullptr, STR_MessageBox_Information, QObject::tr("OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up:")+"\n\n"+ QDir::toNativeSeparators(Get(p_preferences[STR_GEN_DataFolder].toString()))+"\n\n"+ QObject::tr("Use your file manager to make a copy of your profile directory, then afterwards, restart OSCAR and complete the upgrade process.") , QMessageBox::Ok, QMessageBox::Ok); showInGraphicalShell(Get(p_preferences[STR_GEN_DataFolder].toString())); QApplication::exit(-1); } return; } void Profile::UnloadMachineData() { for (auto & mach : m_machlist) { mach->saveSessionInfo(); mach->sessionlist.clear(); mach->day.clear(); } for (auto & day : daylist) { delete day; } daylist.clear(); removeLock(); } void Profile::LoadMachineData(ProgressDialog *progress) { addLock(); for (auto & mach : m_machlist) { MachineLoader *loader = lookupLoader(mach); if (loader) { if (mach->version() < loader->Version()) { qDebug() << "LoadMachineData" << mach->loaderName() << "data format error, machine version" << mach->version() << "loader version" << loader->Version(); progress->hide(); DataFormatError(mach); progress->show(); } else { try { mach->Load(progress); } catch (OldDBVersion& e) { qDebug() << "LoadMachineData" << mach->loaderName() << "load failure, machine version" << mach->version() << "loader version" << loader->Version(); Q_UNUSED(e) progress->hide(); DataFormatError(mach); progress->show(); } } } else { mach->Load(progress); } } progress->setMessage("Loading Channel Information"); loadChannels(); } void Profile::removeMachine(Machine * mach) { if (m_machlist.removeAll(mach)) { QHash >::iterator mlit = MachineList.find(mach->loaderName()); if (mlit != MachineList.end()) { QHash::iterator mit = mlit.value().find(mach->serial()); if (mit != mlit.value().end()) { mlit.value().erase(mit); } } } } Machine * Profile::lookupMachine(QString serial, QString loadername) { auto mlit = MachineList.find(loadername); if (mlit != MachineList.end()) { auto mit = mlit.value().find(serial); if (mit != mlit.value().end()) { return mit.value(); } } return nullptr; } Machine * Profile::CreateMachine(MachineInfo info, MachineID id) { Machine *m = nullptr; auto mlit = MachineList.find(info.loadername); if (mlit != MachineList.end()) { auto mit = mlit.value().find(info.serial); if (mit != mlit.value().end()) { mit.value()->setInfo(info); // update info return mit.value(); } } // Before we create, find any lost folder to get the old ID if ((id == 0) && ((info.type == MT_OXIMETER) || (info.type == MT_JOURNAL) || (info.type == MT_POSITION)|| (info.type == MT_SLEEPSTAGE))) { QString dataPath = Get("{" + STR_GEN_DataFolder + "}/"); QDir dir(dataPath); QStringList namefilter(QString(info.loadername+"_*")); QStringList files = dir.entryList(namefilter, QDir::Dirs); if (files.size() > 0) { QString idstr = files[0].section("_",-1); bool ok; id = idstr.toInt(&ok, 16); } } switch (info.type) { case MT_CPAP: m = new CPAP(this, id); break; case MT_SLEEPSTAGE: m = new SleepStage(this, id); break; case MT_OXIMETER: m = new Oximeter(this, id); break; case MT_POSITION: m = new PositionSensor(this, id); break; case MT_JOURNAL: m = new Machine(this, id); m->setType(MT_JOURNAL); break; default: m = new Machine(this, id); break; } m->setInfo(info); // qDebug() << "Reading" << info.loadername << "Machine Record" << (info.serial.isEmpty() ? m->hexid() : info.serial); MachineList[info.loadername][info.serial] = m; AddMachine(m); return m; } void Profile::AddMachine(Machine *m) { if (!m) { qWarning() << "Empty Machine in Profile::AddMachine()"; return; } m_machlist.append(m); } void Profile::DelMachine(Machine *m) { if (!m) { qWarning() << "Empty Machine in Profile::AddMachine()"; return; } removeMachine(m); } Day *Profile::addDay(QDate date) { auto dit = daylist.find(date); if (dit == daylist.end()) { dit = daylist.insert(date, new Day()); } Day * day = dit.value(); day->setDate(date); if (is_first_day) { m_first = m_last = date; is_first_day = false; } if (m_first > date) { m_first = date; } if (m_last < date) { m_last = date; } return day; } // Get Day record if data available for date and machine type, // and has enabled session data, else return nullptr Day *Profile::GetGoodDay(QDate date, MachineType type) { Day *day = GetDay(date, type); if (!day) return nullptr; // For a machine match, find at least one enabled Session. for (auto & sess : day->sessions) { if (((type == MT_UNKNOWN) || (sess->type() == type)) && sess->enabled()) { day->OpenSummary(); return day; } } // No enabled Sessions were found. return nullptr; } Day *Profile::FindGoodDay(QDate date, MachineType type) { Day *day = FindDay(date, type); if (!day) return nullptr; // For a machine match, find at least one enabled Session. for (auto & sess : day->sessions) { if (((type == MT_UNKNOWN) || (sess->type() == type)) && sess->enabled()) { return day; } } // No enabled Sessions were found. return nullptr; } Day *Profile::GetDay(QDate date, MachineType type) { auto di = daylist.find(date); if (di == daylist.end()) return nullptr; Day * day = di.value(); if (type == MT_UNKNOWN) { day->OpenSummary(); return day; // just want the day record } if (day->machines.contains(type)) { day->OpenSummary(); return day; } return nullptr; } Day *Profile::FindDay(QDate date, MachineType type) { auto di = daylist.find(date); if (di == daylist.end()) return nullptr; Day * day = di.value(); if (type == MT_UNKNOWN) { return day; // just want the day record } if (day->machines.contains(type)) { return day; } return nullptr; } MachineLoader *GetLoader(QString name) { QList loaders = GetLoaders(); for (auto & loader : loaders) { if (loader->loaderName() == name) { return loader; } } return nullptr; } // Returns a QVector containing all machine objects regisered of type t QList Profile::GetMachines(MachineType t) { QList vec; for (auto & mach : m_machlist) { if (!mach) { qWarning() << "Profile::GetMachines() m == nullptr"; continue; } MachineType mt = mach->type(); if ((t == MT_UNKNOWN) || (mt == t)) { vec.push_back(mach); } } return vec; } Machine *Profile::GetMachine(MachineType t) { QListvec = GetMachines(t); if (vec.size() == 0) { return nullptr; } // Find most recently imported machine int idx = 0; for (int i=1; i < vec.size(); i++) { if (vec[i]->lastImported() > vec[idx]->lastImported()) idx = i; } return vec[idx]; } //bool Profile::trashMachine(Machine * mach) //{ // QMap >::iterator it_end = daylist.end(); // QMap >::iterator it; // // QList datelist; // QList days; // // for (it = daylist.begin(); it != it_end; ++it) { // for (int i = 0; i< it.value().size(); ++i) { // Day * day = it.value().at(i); // if (day->machine() == mach) { // days.push_back(day); // datelist.push_back(it.key()); // } // } // } // // for (int i=0; i < datelist.size(); ++i) { // Day * day = days.at(i); // it = daylist.find(datelist.at(i)); // if (it != daylist.end()) { // it.value().removeAll(day); // if (it.value().size() == 0) { // daylist.erase(it); // } // } // mach->unlinkDay(days.at(i)); // } // //} bool Profile::unlinkDay(Day * day) { // Find the key... for (auto it = daylist.begin(), it_end = daylist.end(); it != it_end; ++it) { if (it.value() == day) { daylist.erase(it); return true; } } return false; } //Profile *profile=nullptr; QString SHA1(QString pass) { return pass; } namespace Profiles { QMap profiles; void Done() { p_pref->Save(); profiles.clear(); delete p_pref; delete AppSetting; DestroyLoaders(); } Profile *Get(QString name) { auto it = profiles.find(name); if (it != profiles.end()) { return it.value(); } return nullptr; } Profile *Create(QString name, const QString* in_path) { QString path; if (in_path == nullptr) { path = p_pref->Get("{home}/Profiles/") + name; } else { path = *in_path; } QDir dir(path); if (!dir.exists(path)) { dir.mkpath(path); } //path+="/"+name; p_profile = new Profile(path); profiles[name] = p_profile; p_profile->user->setUserName(name); //p_profile->Set("Realname",realname); //if (!password.isEmpty()) p_profile.user->setPassword(password); p_profile->Set(STR_GEN_DataFolder, QString("{home}/Profiles/{") + QString(STR_UI_UserName) + QString("}")); Machine *m = new Machine(p_profile, 0); m->setType(MT_JOURNAL); MachineInfo info(MT_JOURNAL, 0, STR_MACH_Journal, "OSCAR", STR_MACH_Journal, QString(), m->hexid(), QString(), QDateTime::currentDateTime(), 0); m->setInfo(info); p_profile->AddMachine(m); p_profile->Save(); return p_profile; } Profile *Get() { // username lookup //getUserName() return profiles[getUserName()];; } void saveProfileList() { QString filename = p_pref->Get("{home}/profiles.xml"); QDomDocument doc("profiles"); QDomElement root = doc.createElement("profiles"); doc.appendChild(root); root.appendChild(doc.createComment("This file is created during Profile Scan for cloud access convenience, it's not used by Desktop version of OSCAR.")); for (auto it = profiles.begin(); it != profiles.end(); ++it) { QDomElement elem = doc.createElement("profile"); elem.setAttribute("name", it.key()); // Not technically nessesary.. elem.setAttribute("path", QString("{home}/Profiles/%1/Profile.xml").arg(it.key())); root.appendChild(elem); } QFile file(filename); if (!file.open(QFile::WriteOnly)) { qWarning() << "Could not open" << filename << "for writing, error code" << file.error() << file.errorString(); return; } file.write(doc.toByteArray()); file.close(); } int CleanupProfile(Profile *prof) { // Migrate old per Profile settings that should have been put in program main preferences. QStringList migrateList; migrateList << STR_IS_Multithreading << STR_US_ShowPerformance << STR_US_ShowDebug << STR_US_ScrollDampening << STR_AS_CalendarVisible << STR_IS_CacheSessions << STR_AS_LineCursorMode << STR_AS_RightSidebarVisible << STR_AS_DailyPanelWidth << STR_US_ShowPerformance << STR_AS_GraphHeight << STR_AS_GraphSnapshots << STR_AS_AntiAliasing << STR_AS_LineThickness << STR_AS_UsePixmapCaching << STR_AS_SquareWave << STR_AS_RightPanelWidth << STR_US_TooltipTimeout << STR_AS_Animations << STR_AS_AllowYAxisScaling << STR_AS_GraphTooltips << STR_CS_UserEventPieChart << STR_AS_OverlayType << STR_AS_OverviewLinechartMode; int cnt = 0; for (auto & prf :migrateList) { if (prof->contains(prf)) { qDebug() << "Migrating profile preference" << prf; (*p_pref)[prf] = (*prof)[prf]; prof->Erase(prf); cnt++; } } if (cnt > 0) { qDebug() << "Migrated" << cnt << "preferences for profile" << (*prof)[STR_UI_UserName]; prof->Save(); } return cnt; } /** * @brief Scan Profile directory loading user profiles */ void Scan() { QString path = p_pref->Get("{home}/Profiles"); QDir dir(path); profiles.clear(); if (!dir.exists(path)) { return; } if (!dir.isReadable()) { qWarning() << "Can't open " << path; return; } dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); //dir.setSorting(QDir::Name); QFileInfoList list = dir.entryInfoList(); int cleanup = 0; // Iterate through subdirectories and load profiles.. for (auto & fi : list) { QString npath = fi.canonicalFilePath(); QDir profilePath(npath); if (profilePath.isEmpty()) // skip any empty folders continue; Profile *prof = new Profile(npath); //prof->Open(); profiles[fi.fileName()] = prof; // Migrate any old settings cleanup += CleanupProfile(prof); } if (cleanup > 0) { qDebug() << "Saving preferences after migration"; p_pref->Save(); } // Update profiles.xml for mobile version saveProfileList(); } } // namespace Profiles // Returns a list of all days records matching machine type between start and end date QList Profile::getDays(MachineType mt, QDate start, QDate end) { QList list; if (!start.isValid()) { return list; } if (!end.isValid()) { return list; } QDate date = start; if (date.isNull()) { return list; } QMap::iterator it; do { it = daylist.find(date); if (it != daylist.end()) { Day *day = it.value(); if (mt != MT_UNKNOWN) { if (day->hasEnabledSessions(mt)) { list.push_back(day); } } else { if (day->hasEnabledSessions()) { list.push_back(day); } } } date = date.addDays(1); } while (date <= end); return list; } // Counts number of days in range with data for specified machine type int Profile::countDays(MachineType mt, QDate start, QDate end) { if (!start.isValid()) { return 0; } if (!end.isValid()) { return 0; } QDate date = start; if (date.isNull()) { return 0; } int days = 0; do { Day *day = FindGoodDay(date, mt); if (day) { days++; } date = date.addDays(1); } while (date <= end); return days; } int Profile::countCompliantDays(MachineType mt, QDate start, QDate end) { EventDataType compliance = cpap->complianceHours(); if (!start.isValid()) { return 0; } if (!end.isValid()) { return 0; } QDate date = start; if (date.isNull()) { return 0; } int days = 0; do { Day *day = FindGoodDay(date, mt); if (day) { if (day->hours(mt) > compliance) { days++; } } date = date.addDays(1); } while (date <= end); return days; } // Count number of events of type code in period EventDataType Profile::calcCount(ChannelID code, MachineType mt, QDate start, QDate end) { if (!start.isValid()) { start = LastGoodDay(mt); } if (!end.isValid()) { end = LastGoodDay(mt); } QDate date = start; if (date.isNull()) { return 0; } double val = 0; do { Day *day = GetGoodDay(date, mt); if (day) { val += day->count(code); } date = date.addDays(1); } while (date <= end); return val; } double Profile::calcSum(ChannelID code, MachineType mt, QDate start, QDate end) { if (!start.isValid()) { start = LastGoodDay(mt); } if (!end.isValid()) { end = LastGoodDay(mt); } QDate date = start; double val = 0; do { Day *day = GetGoodDay(date, mt); if (day) { val += day->sum(code); } date = date.addDays(1); } while (date <= end); return val; } EventDataType Profile::calcHours(MachineType mt, QDate start, QDate end) { if (!start.isValid()) { start = LastGoodDay(mt); } if (!end.isValid()) { end = LastGoodDay(mt); } QDate date = start; if (date.isNull()) { return 0; } double val = 0; do { Day *day = GetGoodDay(date, mt); if (day) { val += day->hours(mt); } date = date.addDays(1); } while (date <= end); return val; } EventDataType Profile::calcAboveThreshold(ChannelID code, EventDataType threshold, MachineType mt, QDate start, QDate end) { if (!start.isValid()) { start = LastGoodDay(mt); } if (!end.isValid()) { end = LastGoodDay(mt); } QDate date = start; if (date.isNull()) { return 0; } EventDataType val = 0; do { Day *day = GetGoodDay(date, mt); if (day) { val += day->timeAboveThreshold(code, threshold); } date = date.addDays(1); } while (date <= end); return val; } EventDataType Profile::calcBelowThreshold(ChannelID code, EventDataType threshold, MachineType mt, QDate start, QDate end) { if (!start.isValid()) { start = LastGoodDay(mt); } if (!end.isValid()) { end = LastGoodDay(mt); } QDate date = start; if (date.isNull()) { return 0; } EventDataType val = 0; do { Day *day = GetGoodDay(date, mt); if (day) { val += day->timeBelowThreshold(code, threshold); } date = date.addDays(1); } while (date <= end); return val; } Day * Profile::findSessionDay(Session * session) { for (auto it=p_profile->daylist.begin(),it_end = p_profile->daylist.end(); it != it_end; ++it) { Day *day = it.value(); for (auto & sess : day->sessions) { if (sess == session) { return day; } } } return nullptr; } EventDataType Profile::calcAvg(ChannelID code, MachineType mt, QDate start, QDate end) { if (!start.isValid()) { start = LastGoodDay(mt); } if (!end.isValid()) { end = LastGoodDay(mt); } QDate date = start; if (date.isNull()) { return 0; } double val = 0; int cnt = 0; do { Day *day = GetGoodDay(date, mt); if (day) { if (!day->summaryOnly() || day->hasData(code, ST_AVG)) { val += day->sum(code); cnt += day->count(code); } } date = date.addDays(1); } while (date <= end); if (!cnt) { return 0; } return val / float(cnt); } EventDataType Profile::calcWavg(ChannelID code, MachineType mt, QDate start, QDate end) { if (!start.isValid()) { start = LastGoodDay(mt); } if (!end.isValid()) { end = LastGoodDay(mt); } QDate date = start; if (date.isNull()) { return 0; } double val = 0, tmp, tmph, hours = 0; do { Day *day = GetGoodDay(date, mt); if (day) { if (!day->summaryOnly() || day->hasData(code, ST_WAVG)) { tmph = day->hours(); tmp = day->wavg(code); val += tmp * tmph; hours += tmph; } } date = date.addDays(1); } while (date <= end); if (!hours) { return 0; } val = val / hours; return val; } EventDataType Profile::calcMin(ChannelID code, MachineType mt, QDate start, QDate end) { if (!start.isValid()) { start = LastGoodDay(mt); } if (!end.isValid()) { end = LastGoodDay(mt); } QDate date = start; if (date.isNull()) { return 0; } bool first = true; double min = 0, tmp; do { Day *day = GetGoodDay(date, mt); if (day) { if (!day->summaryOnly() || day->hasData(code, ST_MIN)) { tmp = day->Min(code); if (first || (min > tmp)) { min = tmp; first = false; } } } date = date.addDays(1); } while (date <= end); if (first) { min = 0; } return min; } EventDataType Profile::calcMax(ChannelID code, MachineType mt, QDate start, QDate end) { if (!start.isValid()) { start = LastGoodDay(mt); } if (!end.isValid()) { end = LastGoodDay(mt); } QDate date = start; bool first = true; double max = 0, tmp; do { Day *day = GetGoodDay(date, mt); if (day) { if (!day->summaryOnly() || day->hasData(code, ST_MAX)) { tmp = day->Max(code); if (first || (max < tmp)) { max = tmp; first = false; } } } date = date.addDays(1); } while (date <= end); if (first) { max = 0; } return max; } EventDataType Profile::calcSettingsMin(ChannelID code, MachineType mt, QDate start, QDate end) { if (!start.isValid()) { start = LastGoodDay(mt); } if (!end.isValid()) { end = LastGoodDay(mt); } QDate date = start; if (date.isNull()) { return 0; } bool first = true; double min = 0, tmp; do { Day *day = GetGoodDay(date, mt); if (day) { tmp = day->settings_min(code); if (first || (min > tmp)) { min = tmp; first = false; } } date = date.addDays(1); } while (date <= end); if (first) { min = 0; } return min; } EventDataType Profile::calcSettingsMax(ChannelID code, MachineType mt, QDate start, QDate end) { if (!start.isValid()) { start = LastGoodDay(mt); } if (!end.isValid()) { end = LastGoodDay(mt); } QDate date = start; if (date.isNull()) { return 0; } bool first = true; double max = 0, tmp; do { Day *day = GetGoodDay(date, mt); if (day) { tmp = day->settings_max(code); if (first || (max < tmp)) { max = tmp; first = false; } } date = date.addDays(1); } while (date <= end); if (first) { max = 0; } return max; } struct CountSummary { CountSummary(EventStoreType v) : val(v), count(0), time(0) {} EventStoreType val; EventStoreType count; quint32 time; }; EventDataType Profile::calcPercentile(ChannelID code, EventDataType percent, MachineType mt, QDate start, QDate end) { if (!start.isValid()) { start = LastGoodDay(mt); } if (!end.isValid()) { end = LastGoodDay(mt); } QDate date = start; if (date.isNull()) { return 0; } QMap wmap; QMap::iterator wmi; QHash >::iterator vsi; QHash >::iterator tsi; EventDataType gain; //bool setgain=false; EventDataType value; int weight; qint64 SN = 0; bool timeweight; bool summaryOnly = true; do { Day *day = GetGoodDay(date, mt); if (day) { if (day->summaryOnly()) { date = date.addDays(1); continue; } summaryOnly = false; // why was this nested like this??? //for (int i = 0; i < day->size(); i++) { for (auto & sess : day->sessions) { if (!sess->enabled()) { continue; } gain = sess->m_gain[code]; if (!gain) { gain = 1; } vsi = sess->m_valuesummary.find(code); if (vsi == sess->m_valuesummary.end()) { continue; } tsi = sess->m_timesummary.find(code); timeweight = (tsi != sess->m_timesummary.end()); QHash &vsum = vsi.value(); QHash &tsum = tsi.value(); if (timeweight) { for (auto k=tsum.begin(), tsumend=tsum.end(); k != tsumend; k++) { weight = k.value(); value = EventDataType(k.key()) * gain; SN += weight; wmi = wmap.find(value); if (wmi == wmap.end()) { wmap[value] = weight; } else { wmi.value() += weight; } } } else { for (auto k=vsum.begin(), vsumend=vsum.end(); k!=vsumend; k++) { weight = k.value(); value = EventDataType(k.key()) * gain; SN += weight; wmi = wmap.find(value); if (wmi == wmap.end()) { wmap[value] = weight; } else { wmi.value() += weight; } } } } // } } date = date.addDays(1); } while (date <= end); if (summaryOnly) { // abort percentile calculation, there is not enough data return 0; } QVector valcnt; // Build sorted list of value/counts for (wmi = wmap.begin(); wmi != wmap.end(); wmi++) { ValueCount vc; vc.value = wmi.key(); vc.count = wmi.value(); vc.p = 0; valcnt.push_back(vc); } // sort by weight, then value std::sort(valcnt.begin(), valcnt.end()); //double SN=100.0/double(N); // 100% / overall sum double p = 100.0 * percent; double nth = double(SN) * percent; // index of the position in the unweighted set would be double nthi = floor(nth); qint64 sum1 = 0, sum2 = 0; qint64 w1, w2 = 0; double v1 = 0, v2 = 0; int N = valcnt.size(); int k = 0; for (k = 0; k < N; k++) { v1 = valcnt[k].value; w1 = valcnt[k].count; sum1 += w1; if (sum1 > nthi) { return v1; } if (sum1 == nthi) { break; // boundary condition } } if (k >= N) { return v1; } v2 = valcnt[k + 1].value; w2 = valcnt[k + 1].count; sum2 = sum1 + w2; // value lies between v1 and v2 double px = 100.0 / double(SN); // Percentile represented by one full value // calculate percentile ranks double p1 = px * (double(sum1) - (double(w1) / 2.0)); double p2 = px * (double(sum2) - (double(w2) / 2.0)); // calculate linear interpolation double v = v1 + ((p - p1) / (p2 - p1)) * (v2 - v1); // p1.....p.............p2 // 37 55 70 return v; } // Lookup first day record of the specified machine type, or return the first day overall if MT_UNKNOWN QDate Profile::FirstDay(MachineType mt) { if ((mt == MT_UNKNOWN) || (!m_last.isValid()) || (!m_first.isValid())) { return m_first; } QDate d = m_first; do { if (FindDay(d, mt) != nullptr) { return d; } d = d.addDays(1); } while (d <= m_last); return m_last; } // Lookup last day record of the specified machine type, or return the last day overall if MT_UNKNOWN QDate Profile::LastDay(MachineType mt) { if ((mt == MT_UNKNOWN) || (!m_last.isValid()) || (!m_first.isValid())) { return m_last; } QDate d = m_last; do { if (FindDay(d, mt) != nullptr) { return d; } d = d.addDays(-1); } while (d >= m_first); return m_first; } QDate Profile::FirstGoodDay(MachineType mt) { if (mt == MT_UNKNOWN) { return FirstDay(); } QDate d = FirstDay(mt); QDate l = LastDay(mt); // No data will return invalid date records if (!d.isValid() || !l.isValid()) { return QDate(); } do { if (FindGoodDay(d, mt) != nullptr) { return d; } d = d.addDays(1); } while (d <= l); return l; //m_last; } QDate Profile::LastGoodDay(MachineType mt) { if (mt == MT_UNKNOWN) { return FirstDay(); } QDate d = LastDay(mt); QDate f = FirstDay(mt); if (!(d.isValid() && f.isValid())) { return QDate(); } do { if (FindGoodDay(d, mt) != nullptr) { return d; } d = d.addDays(-1); } while (d >= f); return f; } bool Profile::channelAvailable(ChannelID code) { for (auto & mach : m_machlist) { if (mach->hasChannel(code)) return true; } return false; } bool Profile::hasChannel(ChannelID code) { QDate d = LastDay(); QDate f = FirstDay(); if (!(d.isValid() && f.isValid())) { return false; } QMap::iterator dit; bool found = false; do { dit = daylist.find(d); if (dit != daylist.end()) { Day *day = dit.value(); if (day->channelHasData(code)) { found = true; break; } } d = d.addDays(-1); } while (d >= f); return found; } const quint16 chandata_version = 1; void Profile::saveChannels() { // First save the XML version for Mobile versions schema::channel.Save(Get("{DataFolder}/") + "channels.xml"); QString filename = Get("{DataFolder}/") + "channels.dat"; QFile f(filename); qDebug() << "Saving Channel States"; f.open(QFile::WriteOnly); QDataStream out(&f); out.setVersion(QDataStream::Qt_4_6); out.setByteOrder(QDataStream::LittleEndian); out << (quint32)magic; out << (quint16)chandata_version; QSettings settings; (*p_profile)[STR_PREF_Language] = settings.value(LangSetting, "").toString(); quint16 size = schema::channel.channels.size(); out << size; for (auto it = schema::channel.channels.begin(),it_end = schema::channel.channels.end(); it != it_end; ++it) { schema::Channel * chan = it.value(); out << it.key(); out << chan->code(); out << chan->enabled(); out << chan->defaultColor(); out << chan->fullname(); out << chan->label(); out << chan->description(); out << chan->lowerThreshold(); out << chan->lowerThresholdColor(); out << chan->upperThreshold(); out << chan->upperThresholdColor(); out << chan->showInOverview(); } f.close(); } void Profile::loadChannels() { bool changing_language = false; QString filename = Get("{DataFolder}/") + "channels.dat"; QFile f(filename); if (!f.open(QFile::ReadOnly)) { return; } qDebug() << "Loading channel.dat States"; QDataStream in(&f); in.setVersion(QDataStream::Qt_4_6); in.setByteOrder(QDataStream::LittleEndian); quint32 mag; in >> mag; if (magic != mag) { qDebug() << "LoadChannels: Faulty data"; return; } quint16 version; in >> version; QSettings settings; QString language = Get(STR_PREF_Language); if (settings.value(LangSetting, "").toString() != language) { qDebug() << "Language change detected, resetting default channel names"; changing_language = true; } quint16 size; in >> size; QString name; ChannelID code; bool enabled; QColor color; EventDataType lowerThreshold; QColor lowerThresholdColor; EventDataType upperThreshold; QColor upperThresholdColor; QString fullname; QString label; QString description; bool showOverview = false; for (int i=0; i < size; i++) { in >> code; schema::Channel * chan = &schema::channel[code]; in >> name; if (chan->code() != name) { qDebug() << "Looking up channel" << name << "by name, as it's ChannedID must have changed"; chan = &schema::channel[name]; } in >> enabled; in >> color; in >> fullname; in >> label; in >> description; in >> lowerThreshold; in >> lowerThresholdColor; in >> upperThreshold; in >> upperThresholdColor; if (version >= 1) { in >> showOverview; } if (chan->isNull()) { qDebug() << "loadChannels has no idea about channel" << name; if (in.atEnd()) return; continue; } chan->setEnabled(enabled); chan->setDefaultColor(color); // Don't import channel descriptions if event renaming is turned off. (helps pick up new translations) if (changing_language) { // Nothing } else { chan->setFullname(fullname); chan->setLabel(label); chan->setDescription(description); } chan->setLowerThreshold(lowerThreshold); chan->setLowerThresholdColor(lowerThresholdColor); chan->setUpperThreshold(upperThreshold); chan->setUpperThresholdColor(upperThresholdColor); chan->setShowInOverview(showOverview); if (in.atEnd()) return; } f.close(); } OSCAR-code-v1.3.1/oscar/SleepLib/profiles.h000066400000000000000000001116441417327530600203020ustar00rootroot00000000000000/* SleepLib Profiles Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef PROFILES_H #define PROFILES_H #include #include #include #include "progressdialog.h" #include "machine.h" #include "machine_loader.h" #include "preferences.h" #include "common.h" class Machine; enum Gender { GenderNotSpecified, Male, Female }; enum MaskType { Mask_Unknown, Mask_NasalPillows, Mask_Hybrid, Mask_StandardNasal, Mask_FullFace }; class DoctorInfo; class UserInfo; class UserSettings; class OxiSettings; class CPAPSettings; class AppearanceSettings; class SessionSettings; /*! \class Profile \author Mark Watkins \date 28/04/11 \brief The User profile system, containing all information for a user, and an index into all Machine data */ class Profile : public Preferences { public: //! \brief Constructor.. Does not open profile in UI, but loads it from disk by default Profile(QString path, bool open=true); virtual ~Profile(); //! \brief Parse machines.xml bool OpenMachines(); bool StoreMachines(); qint64 diskSpaceSummaries(); qint64 diskSpaceEvents(); qint64 diskSpaceBackups(); qint64 diskSpace(); //! \brief Force some preferences for ResMed machines virtual void forceResmedPrefs(); //! \brief Returns hostname that locked profile, or empty string if unlocked QString checkLock(); //! \brief Removes a lockfile bool removeLock(); void addLock(); //! \brief Save Profile object (This is an extension to Preference::Save(..)) virtual bool Save(QString filename = ""); //! \brief Add machine to this profiles machlist void AddMachine(Machine *m); //! \brief Remove machine from this profiles machlist void DelMachine(Machine *m); //! \brief Loads all machine (summary) data belonging to this profile void LoadMachineData(ProgressDialog *progress); //! \brief Unloads all machine (summary) data for this profile to free up memory; void UnloadMachineData(); //! \brief Barf because data format has changed. This does a purge of CPAP data for machine *m void DataFormatError(Machine *m); QString path() { return p_path; } //! \brief Removes a given day from the date, destroying the daylist date record if empty bool unlinkDay(Day * day); // bool trashMachine(Machine * mach); //! \brief Add Day record to Profile Day list Day *addDay(QDate date); //! \brief Get Day record if data available for date and machine type, else return nullptr Day *GetDay(QDate date, MachineType type = MT_UNKNOWN); //! \brief Same as GetDay but does not open the summaries Day *FindDay(QDate date, MachineType type = MT_UNKNOWN); //! \brief Get Day record if data available for date and machine type, // and has enabled session data, else return nullptr Day *GetGoodDay(QDate date, MachineType type); //! \breif Same as GetGoodDay but does not open the summaries Day *FindGoodDay(QDate date, MachineType type); //! \brief Returns a list of all machines of type t QList GetMachines(MachineType t = MT_UNKNOWN); //! \brief Returns the machine of type t used on date, nullptr if none.. Machine *GetMachine(MachineType t, QDate date); //! \brief return the first machine of type t Machine *GetMachine(MachineType t); //! \brief Returns true if this profile stores this variable identified by key bool contains(QString key) { return p_preferences.contains(key); } //! \brief Get all days records of machine type between start and end dates QList getDays(MachineType mt, QDate start, QDate end); //! \brief Returns a count of all days (with data) of machine type, between start and end dates int countDays(MachineType mt = MT_UNKNOWN, QDate start = QDate(), QDate end = QDate()); //! \brief Returns a count of all compliant days of machine type between start and end dates int countCompliantDays(MachineType mt, QDate start, QDate end); //! \brief Returns a count of all event entries for code, matching machine type between start an end dates EventDataType calcCount(ChannelID code, MachineType mt = MT_CPAP, QDate start = QDate(), QDate end = QDate()); //! \brief Returns a sum of all event data for Channel code, matching machine type between start an end dates double calcSum(ChannelID code, MachineType mt = MT_CPAP, QDate start = QDate(), QDate end = QDate()); //! \brief Returns a sum of all session durations for machine type, between start and end dates EventDataType calcHours(MachineType mt = MT_CPAP, QDate start = QDate(), QDate end = QDate()); //! \brief Calculates Channel Average (Sums and counts all events, returning the sum divided by the count.) EventDataType calcAvg(ChannelID code, MachineType mt = MT_CPAP, QDate start = QDate(), QDate end = QDate()); //! \brief Calculates Channel Weighted Average between start and end dates EventDataType calcWavg(ChannelID code, MachineType mt = MT_CPAP, QDate start = QDate(), QDate end = QDate()); //! \brief Calculates the minimum value for channel code, between start and end dates EventDataType calcMin(ChannelID code, MachineType mt = MT_CPAP, QDate start = QDate(), QDate end = QDate()); //! \brief Calculates the maximum value for channel code, between start and end dates EventDataType calcMax(ChannelID code, MachineType mt = MT_CPAP, QDate start = QDate(), QDate end = QDate()); //! \brief Calculates a percentile value percent for channel code, between start and end dates EventDataType calcPercentile(ChannelID code, EventDataType percent, MachineType mt = MT_CPAP, QDate start = QDate(), QDate end = QDate()); //! \brief Tests if Channel code is available in all day sets bool hasChannel(ChannelID code); //! \brief Looks up if any machines report channel is available bool channelAvailable(ChannelID code); //! \brief Calculates the minimum session settings value for channel code, between start and end dates EventDataType calcSettingsMin(ChannelID code, MachineType mt = MT_CPAP, QDate start = QDate(), QDate end = QDate()); //! \brief Calculates the maximum session settings value for channel code, between start and end dates EventDataType calcSettingsMax(ChannelID code, MachineType mt = MT_CPAP, QDate start = QDate(), QDate end = QDate()); //! \brief Calculates the time channel code spends above threshold value for machine type, between start and end dates EventDataType calcAboveThreshold(ChannelID code, EventDataType threshold, MachineType mt = MT_CPAP, QDate start = QDate(), QDate end = QDate()); //! \brief Calculates the time channel code spends below threshold value for machine type, between start and end dates EventDataType calcBelowThreshold(ChannelID code, EventDataType threshold, MachineType mt = MT_CPAP, QDate start = QDate(), QDate end = QDate()); Day * findSessionDay(Session * session); //! \brief Looks for the first date containing a day record matching machinetype QDate FirstDay(MachineType mt = MT_UNKNOWN); //! \brief Looks for the last date containing a day record matching machinetype QDate LastDay(MachineType mt = MT_UNKNOWN); //! \brief Looks for the first date containing a day record with enabled sessions matching machinetype QDate FirstGoodDay(MachineType mt = MT_UNKNOWN); //! \brief Looks for the last date containing a day record with enabled sessions matching machinetype QDate LastGoodDay(MachineType mt = MT_UNKNOWN); //! \brief Returns this profiles data folder QString dataFolder() { return (*this).Get("{DataFolder}"); } //! \brief Return if this profile has been opened or not bool isOpen() { return m_opened; } //! \brief QMap of day records (iterates in order). QMap daylist; void removeMachine(Machine *); Machine * lookupMachine(QString serial, QString loadername); Machine * CreateMachine(MachineInfo info, MachineID id = 0); void loadChannels(); void saveChannels(); bool is_first_day; UserInfo *user; CPAPSettings *cpap; OxiSettings *oxi; DoctorInfo *doctor; AppearanceSettings *appearance; UserSettings *general; SessionSettings *session; QList m_machlist; protected: QDate m_first; QDate m_last; bool m_opened; QHash > MachineList; }; class MachineLoader; extern MachineLoader *GetLoader(QString name); extern Preferences *p_pref; extern Profile *p_profile; // these are bad and must change // #define PREF (*p_pref) //! \brief Returns a count of all files & directories in a supplied folder int dirCount(QString path); namespace Profiles { extern QMap profiles; void Scan(); // Initialize and load Profile void Done(); // Save all Profile objects and clear list int CleanupProfile(Profile *prof); Profile *Create(QString name, const QString* in_path=nullptr); Profile *Get(QString name); Profile *Get(); } // DoctorInfo Strings const QString STR_DI_Name = "DoctorName"; const QString STR_DI_Phone = "DoctorPhone"; const QString STR_DI_Email = "DoctorEmail"; const QString STR_DI_Practice = "DoctorPractice"; const QString STR_DI_Address = "DoctorAddress"; const QString STR_DI_PatientID = "DoctorPatientID"; // UserInfo Strings const QString STR_UI_DOB = "DOB"; const QString STR_UI_FirstName = "FirstName"; const QString STR_UI_LastName = "LastName"; const QString STR_UI_UserName = "UserName"; const QString STR_UI_Password = "Password"; const QString STR_UI_Address = "Address"; const QString STR_UI_Phone = "Phone"; const QString STR_UI_EmailAddress = "EmailAddress"; const QString STR_UI_Country = "Country"; const QString STR_UI_Height = "Height"; const QString STR_UI_Gender = "Gender"; const QString STR_UI_TimeZone = "TimeZone"; const QString STR_UI_DST = "DST"; // OxiSettings Strings const QString STR_OS_EnableOximetry = "EnableOximetry"; const QString STR_OS_DefaultDevice = "DefaultOxiDevice"; const QString STR_OS_SyncOximeterClock = "SyncOximeterClock"; const QString STR_OS_OximeterType = "OximeterType"; const QString STR_OS_OxiDiscardThreshold = "OxiDiscardThreshold"; const QString STR_OS_SPO2DropDuration = "SPO2DropDuration"; const QString STR_OS_SPO2DropPercentage = "SPO2DropPercentage"; const QString STR_OS_PulseChangeDuration = "PulseChangeDuration"; const QString STR_OS_PulseChangeBPM = "PulseChangeBPM"; const QString STR_OS_SkipOxiIntroScreen = "SkipOxiIntroScreen"; // CPAPSettings Strings const QString STR_CS_ComplianceHours = "ComplianceHours"; const QString STR_CS_ShowCompliance = "ShowCompliance"; const QString STR_CS_ShowLeaksMode = "ShowLeaksMode"; const QString STR_CS_MaskStartDate = "MaskStartDate"; const QString STR_CS_MaskDescription = "MaskDescription"; const QString STR_CS_MaskType = "MaskType"; const QString STR_CS_PrescribedMode = "CPAPPrescribedMode"; const QString STR_CS_PrescribedMinPressure = "CPAPPrescribedMinPressure"; const QString STR_CS_PrescribedMaxPressure = "CPAPPrescribedMaxPressure"; const QString STR_CS_UntreatedAHI = "UntreatedAHI"; const QString STR_CS_Notes = "CPAPNotes"; const QString STR_CS_DateDiagnosed = "DateDiagnosed"; const QString STR_CS_UserEventFlagging = "UserEventFlagging"; const QString STR_CS_AutoImport = "AutoImport"; const QString STR_CS_BrickWarning = "BrickWarning"; const QString STR_CS_UserFlowRestriction = "UserFlowRestriction"; const QString STR_CS_UserEventDuration = "UserEventDuration"; const QString STR_CS_UserFlowRestriction2 = "UserFlowRestriction2"; const QString STR_CS_UserEventDuration2 = "UserEventDuration2"; const QString STR_CS_UserEventDuplicates = "UserEventDuplicates"; const QString STR_CS_ResyncFromUserFlagging = "ResyncFromUserFlagging"; const QString STR_CS_AHIWindow = "AHIWindow"; const QString STR_CS_AHIReset = "AHIReset"; const QString STR_CS_ClockDrift = "ClockDrift"; const QString STR_CS_LeakRedline = "LeakRedline"; const QString STR_CS_ShowLeakRedline = "ShowLeakRedline"; const QString STR_CS_CalculateUnintentionalLeaks = "CalculateUnintentionalLeaks"; const QString STR_CS_4cmH2OLeaks = "Custom4cmH2OLeaks"; const QString STR_CS_20cmH2OLeaks = "Custom20cmH2OLeaks"; // ImportSettings Strings const QString STR_IS_DaySplitTime = "DaySplitTime"; const QString STR_IS_PreloadSummaries = "PreloadSummaries"; const QString STR_IS_CombineCloseSessions = "CombineCloserSessions"; const QString STR_IS_IgnoreShorterSessions = "IgnoreShorterSessions"; const QString STR_IS_BackupCardData = "BackupCardData"; const QString STR_IS_CompressBackupData = "CompressBackupData"; const QString STR_IS_CompressSessionData = "CompressSessionData"; const QString STR_IS_IgnoreOlderSessions = "IgnoreOlderSessions"; const QString STR_IS_IgnoreOlderSessionsDate = "IgnoreOlderSessionsDate"; const QString STR_IS_LockSummarySessions = "LockSummarySessions"; const QString STR_IS_WarnOnUntestedMachine = "WarnOnUntestedMachine"; const QString STR_IS_WarnOnUnexpectedData = "WarnOnUnexpectedData"; // UserSettings Strings const QString STR_US_UnitSystem = "UnitSystem"; const QString STR_US_EventWindowSize = "EventWindowSize"; const QString STR_US_SkipEmptyDays = "SkipEmptyDays"; const QString STR_US_RebuildCache = "RebuildCache"; const QString STR_US_LinkGroups = "LinkGroups"; const QString STR_US_CalculateRDI = "CalculateRDI"; const QString STR_US_PrefCalcMiddle = "PrefCalcMiddle"; const QString STR_US_PrefCalcPercentile = "PrefCalcPercentile"; const QString STR_US_PrefCalcMax = "PrefCalcMax"; const QString STR_US_ShowUnknownFlags = "ShowUnknownFlags"; const QString STR_US_StatReportMode = "StatReportMode"; const QString STR_US_StatReportRangeStart = "StatReportRangeStart"; const QString STR_US_StatReportRangeEnd = "StatReportRangeEnd"; const QString STR_US_LastOverviewRange = "LastOverviewRange"; const QString STR_US_CustomOverviewRangeStart = "CustomOverviewRangeStart"; const QString STR_US_CustomOverviewRangeEnd = "CustomOverviewRangeEnd"; // Values for StatReportMode const int STAT_MODE_STANDARD = 0; const int STAT_MODE_MONTHLY = 1; const int STAT_MODE_RANGE = 2; class DoctorInfo : public PrefSettings { public: DoctorInfo(Profile *profile) : PrefSettings(profile) { initPref(STR_DI_Name, QString()); initPref(STR_DI_Phone, QString()); initPref(STR_DI_Email, QString()); initPref(STR_DI_Practice, QString()); initPref(STR_DI_Address, QString()); initPref(STR_DI_PatientID, QString()); } const QString name() const { return getPref(STR_DI_Name).toString(); } const QString phone() const { return getPref(STR_DI_Phone).toString(); } const QString email() const { return getPref(STR_DI_Email).toString(); } const QString practiceName() const { return getPref(STR_DI_Practice).toString(); } const QString address() const { return getPref(STR_DI_Address).toString(); } const QString patientID() const { return getPref(STR_DI_PatientID).toString(); } void setName(QString name) { setPref(STR_DI_Name, name); } void setPhone(QString phone) { setPref(STR_DI_Phone, phone); } void setEmail(QString phone) { setPref(STR_DI_Email, phone); } void setPracticeName(QString practice) { setPref(STR_DI_Practice, practice); } void setAddress(QString address) { setPref(STR_DI_Address, address); } void setPatientID(QString pid) { setPref(STR_DI_PatientID, pid); } }; /*! \class UserInfo \brief Profile Options relating to the User Information */ class UserInfo : public PrefSettings { public: UserInfo(Profile *profile) : PrefSettings(profile) { initPref(STR_UI_DOB, QDate(1970, 1, 1)); initPref(STR_UI_FirstName, QString()); initPref(STR_UI_LastName, QString()); initPref(STR_UI_UserName, QString()); initPref(STR_UI_Password, QString()); initPref(STR_UI_Address, QString()); initPref(STR_UI_Phone, QString()); initPref(STR_UI_EmailAddress, QString()); initPref(STR_UI_Country, QString()); initPref(STR_UI_Height, 0.0); initPref(STR_UI_Gender, (int)GenderNotSpecified); initPref(STR_UI_TimeZone, QString()); initPref(STR_UI_DST, false); } QDate DOB() const { return getPref(STR_UI_DOB).toDate(); } const QString firstName() const { return getPref(STR_UI_FirstName).toString(); } const QString lastName() const { return getPref(STR_UI_LastName).toString(); } const QString userName() const { return getPref(STR_UI_UserName).toString(); } const QString address() const { return getPref(STR_UI_Address).toString(); } const QString phone() const { return getPref(STR_UI_Phone).toString(); } const QString email() const { return getPref(STR_UI_EmailAddress).toString(); } double height() const { return getPref(STR_UI_Height).toDouble(); } const QString country() const { return getPref(STR_UI_Country).toString(); } Gender gender() const { return (Gender)getPref(STR_UI_Gender).toInt(); } const QString timeZone() const { return getPref(STR_UI_TimeZone).toString(); } bool daylightSaving() const { return getPref(STR_UI_DST).toBool(); } void setDOB(QDate date) { setPref(STR_UI_DOB, date); } void setFirstName(QString name) { setPref(STR_UI_FirstName, name); } void setLastName(QString name) { setPref(STR_UI_LastName, name); } void setUserName(QString username) { setPref(STR_UI_UserName, username); } void setAddress(QString address) { setPref(STR_UI_Address, address); } void setPhone(QString phone) { setPref(STR_UI_Phone, phone); } void setEmail(QString email) { setPref(STR_UI_EmailAddress, email); } void setHeight(double height) { setPref(STR_UI_Height, height); } void setCountry(QString country) { setPref(STR_UI_Country, country); } void setGender(Gender g) { setPref(STR_UI_Gender, (int)g); } void setTimeZone(QString tz) { setPref(STR_UI_TimeZone, tz); } void setDaylightSaving(bool ds) { setPref(STR_UI_DST, ds); } bool hasPassword() { return !getPref(STR_UI_Password).toString().isEmpty(); } bool checkPassword(QString password) { QByteArray ba = password.toUtf8(); QString hashedPass = QString(QCryptographicHash::hash(ba, QCryptographicHash::Sha1).toHex()); return getPref(STR_UI_Password).toString() == hashedPass; } void setPassword(QString password) { QByteArray ba = password.toUtf8(); QString hashedPass = QString(QCryptographicHash::hash(ba, QCryptographicHash::Sha1).toHex()); setPref(STR_UI_Password, hashedPass); } }; /*! \class OxiSettings \brief Profile Options relating to the Oximetry settings */ class OxiSettings : public PrefSettings { public: //! \brief Create OxiSettings object given Profile *p, and initialize the defaults OxiSettings(Profile *profile) : PrefSettings(profile) { initPref(STR_OS_EnableOximetry, false); initPref(STR_OS_DefaultDevice, QString()); initPref(STR_OS_SyncOximeterClock, true); initPref(STR_OS_OximeterType, 0); initPref(STR_OS_OxiDiscardThreshold, 0.0); initPref(STR_OS_SPO2DropDuration, 8.0); initPref(STR_OS_SPO2DropPercentage, 3.0); initPref(STR_OS_PulseChangeDuration, 8.0); initPref(STR_OS_PulseChangeBPM, 5.0); initPref(STR_OS_SkipOxiIntroScreen, false); } bool oximetryEnabled() const { return getPref(STR_OS_EnableOximetry).toBool(); } QString defaultDevice() const { return getPref(STR_OS_DefaultDevice).toString(); } bool syncOximeterClock() const { return getPref(STR_OS_SyncOximeterClock).toBool(); } int oximeterType() const { return getPref(STR_OS_OximeterType).toInt(); } double oxiDiscardThreshold() const { return getPref(STR_OS_OxiDiscardThreshold).toDouble(); } double spO2DropDuration() const { return getPref(STR_OS_SPO2DropDuration).toDouble(); } double spO2DropPercentage() const { return getPref(STR_OS_SPO2DropPercentage).toDouble(); } double pulseChangeDuration() const { return getPref(STR_OS_PulseChangeDuration).toDouble(); } double pulseChangeBPM() const { return getPref(STR_OS_PulseChangeBPM).toDouble(); } bool skipOxiIntroScreen() const { return getPref(STR_OS_SkipOxiIntroScreen).toBool(); } void setOximetryEnabled(bool enabled) { setPref(STR_OS_EnableOximetry, enabled); } void setDefaultDevice(QString name) { setPref(STR_OS_DefaultDevice, name); } void setSyncOximeterClock(bool synced) { setPref(STR_OS_SyncOximeterClock, synced); } void setOximeterType(int oxitype) { setPref(STR_OS_OximeterType, oxitype); } void setOxiDiscardThreshold(double thresh) { setPref(STR_OS_OxiDiscardThreshold, thresh); } void setSpO2DropDuration(double duration) { setPref(STR_OS_SPO2DropDuration, duration); } void setPulseChangeBPM(double bpm) { setPref(STR_OS_PulseChangeBPM, bpm); } void setSkipOxiIntroScreen(bool skip) { setPref(STR_OS_SkipOxiIntroScreen, skip); } void setSpO2DropPercentage(double percentage) { setPref(STR_OS_SPO2DropPercentage, percentage); } void setPulseChangeDuration(double duration) { setPref(STR_OS_PulseChangeDuration, duration); } }; /*! \class CPAPSettings \brief Profile Options relating to the CPAP settings */ class CPAPSettings : public PrefSettings { public: CPAPSettings(Profile *profile) : PrefSettings(profile) { m_complianceHours = initPref(STR_CS_ComplianceHours, 4.0f).toFloat(); initPref(STR_CS_ShowCompliance, true); initPref(STR_CS_ShowLeaksMode, 0); // TODO: jedimark: Check if this date is initiliazed yet initPref(STR_CS_MaskStartDate, QDate()); initPref(STR_CS_MaskDescription, QString()); initPref(STR_CS_MaskType, Mask_Unknown); initPref(STR_CS_PrescribedMode, MODE_UNKNOWN); initPref(STR_CS_PrescribedMinPressure, 0.0f); initPref(STR_CS_PrescribedMaxPressure, 0.0f); initPref(STR_CS_UntreatedAHI, 0.0f); initPref(STR_CS_Notes, QString()); initPref(STR_CS_DateDiagnosed, QDate()); m_userEventRestriction1 = initPref(STR_CS_UserFlowRestriction, 20.0f).toFloat(); m_userEventDuration1 = initPref(STR_CS_UserEventDuration, 8.0f).toFloat(); m_userEventRestriction2 = initPref(STR_CS_UserFlowRestriction2, 50.0f).toFloat(); m_userEventDuration2 = initPref(STR_CS_UserEventDuration2, 8.0f).toFloat(); m_userEventDuplicates = initPref(STR_CS_UserEventDuplicates, false).toBool(); m_userEventFlagging = initPref(STR_CS_UserEventFlagging, false).toBool(); m_ahiWindow = initPref(STR_CS_AHIWindow, 60.0).toFloat(); m_ahiReset = initPref(STR_CS_AHIReset, false).toBool(); m_leakRedLine = initPref(STR_CS_LeakRedline, 24.0f).toFloat(); m_showLeakRedline = initPref(STR_CS_ShowLeakRedline, true).toBool(); m_resyncFromUserFlagging = initPref(STR_CS_ResyncFromUserFlagging, false).toBool(); initPref(STR_CS_AutoImport, false); initPref(STR_CS_BrickWarning, true); // From old zMaskProfile::calcLeak comments: // Average mask leak minimum at pressure 4 = 20.167 // Average mask slope = 1.76 m_calcUnintentionalLeaks = initPref(STR_CS_CalculateUnintentionalLeaks, true).toBool(); m_4cmH2OLeaks = initPref(STR_CS_4cmH2OLeaks, 20.167).toDouble(); m_20cmH2OLeaks = initPref(STR_CS_20cmH2OLeaks, 48.333).toDouble(); m_clock_drift = initPref(STR_CS_ClockDrift, (int)0).toInt(); } //Getters double complianceHours() const { return m_complianceHours; } bool showComplianceInfo() const { return getPref(STR_CS_ShowCompliance).toBool(); } int leakMode() const { return getPref(STR_CS_ShowLeaksMode).toInt(); } QDate maskStartDate() const { return getPref(STR_CS_MaskStartDate).toDate(); } QString maskDescription() const { return getPref(STR_CS_MaskDescription).toString(); } MaskType maskType() const { return (MaskType)getPref(STR_CS_MaskType).toInt(); } CPAPMode mode() const { return CPAPMode(getPref(STR_CS_PrescribedMode).toInt()); } EventDataType minPressure() const { return getPref(STR_CS_PrescribedMinPressure).toFloat(); } EventDataType maxPressure() const { return getPref(STR_CS_PrescribedMaxPressure).toFloat(); } EventDataType untreatedAHI() const { return getPref(STR_CS_UntreatedAHI).toFloat(); } const QString notes() const { return getPref(STR_CS_Notes).toString(); } QDate dateDiagnosed() const { return getPref(STR_CS_DateDiagnosed).toDate(); } inline EventDataType userEventRestriction() const { return m_userEventRestriction1; } inline EventDataType userEventDuration() const { return m_userEventDuration1; } inline EventDataType userEventRestriction2() const { return m_userEventRestriction2; } inline EventDataType userEventDuration2() const { return m_userEventDuration2; } inline bool userEventDuplicates() const { return m_userEventDuplicates; } inline EventDataType AHIWindow() const { return m_ahiWindow; } inline bool AHIReset() const { return m_ahiReset; } inline bool userEventFlagging() const { return m_userEventFlagging; } inline int clockDrift() const { return m_clock_drift; } inline EventDataType leakRedline() const { return m_leakRedLine; } inline bool showLeakRedline() const { return m_showLeakRedline; } inline bool resyncFromUserFlagging() const { return m_resyncFromUserFlagging; } bool autoImport() const { return getPref(STR_CS_AutoImport).toBool(); } bool brickWarning() const { return getPref(STR_CS_BrickWarning).toBool(); } inline bool calculateUnintentionalLeaks() const { return m_calcUnintentionalLeaks; } inline double custom4cmH2OLeaks() const { return m_4cmH2OLeaks; } inline double custom20cmH2OLeaks() const { return m_20cmH2OLeaks; } //Setters void setMode(CPAPMode mode) { setPref(STR_CS_PrescribedMode, (int)mode); } void setMinPressure(EventDataType pressure) { setPref(STR_CS_PrescribedMinPressure, pressure); } void setMaxPressure(EventDataType pressure) { setPref(STR_CS_PrescribedMaxPressure, pressure); } void setUntreatedAHI(EventDataType ahi) { setPref(STR_CS_UntreatedAHI, ahi); } void setNotes(QString notes) { setPref(STR_CS_Notes, notes); } void setDateDiagnosed(QDate date) { setPref(STR_CS_DateDiagnosed, date); } void setComplianceHours(EventDataType hours) { setPref(STR_CS_ComplianceHours, m_complianceHours=hours); } void setShowComplianceInfo(bool b) { setPref(STR_CS_ShowCompliance, b); } void setLeakMode(int leakmode) { setPref(STR_CS_ShowLeaksMode, (int)leakmode); } void setMaskStartDate(QDate date) { setPref(STR_CS_MaskStartDate, date); } void setMaskType(MaskType masktype) { setPref(STR_CS_MaskType, (int)masktype); } void setUserEventRestriction(EventDataType flow) { setPref(STR_CS_UserFlowRestriction, m_userEventRestriction1=flow); } void setUserEventDuration(EventDataType duration) { setPref(STR_CS_UserEventDuration, m_userEventDuration1=duration); } void setUserEventRestriction2(EventDataType flow) { setPref(STR_CS_UserFlowRestriction2, m_userEventRestriction2=flow); } void setUserEventDuration2(EventDataType duration) { setPref(STR_CS_UserEventDuration2, m_userEventDuration2=duration); } void setAHIWindow(EventDataType window) { setPref(STR_CS_AHIWindow, m_ahiWindow=window); } void setAHIReset(bool b) { setPref(STR_CS_AHIReset, m_ahiReset=b); } void setUserEventFlagging(bool flagging) { setPref(STR_CS_UserEventFlagging, m_userEventFlagging=flagging); } void setUserEventDuplicates(bool dup) { setPref(STR_CS_UserEventDuplicates, m_userEventDuplicates=dup); } void setMaskDescription(QString description) { setPref(STR_CS_MaskDescription, description); } void setClockDrift(int seconds) { setPref(STR_CS_ClockDrift, m_clock_drift = seconds); } void setLeakRedline(EventDataType value) { setPref(STR_CS_LeakRedline, m_leakRedLine=value); } void setShowLeakRedline(bool b) { setPref(STR_CS_ShowLeakRedline, m_showLeakRedline=b); } void setResyncFromUserFlagging(bool b) { setPref(STR_CS_ResyncFromUserFlagging, m_resyncFromUserFlagging=b); } void setAutoImport(bool b) { setPref(STR_CS_AutoImport, b); } void setBrickWarning(bool b) { setPref(STR_CS_BrickWarning, b); } void setCalculateUnintentionalLeaks(bool b) { setPref(STR_CS_CalculateUnintentionalLeaks, m_calcUnintentionalLeaks=b); } void setCustom4cmH2OLeaks(double val) { setPref(STR_CS_4cmH2OLeaks, m_4cmH2OLeaks=val); } void setCustom20cmH2OLeaks(double val) { setPref(STR_CS_20cmH2OLeaks, m_20cmH2OLeaks=val); } public: int m_clock_drift; double m_4cmH2OLeaks, m_20cmH2OLeaks; bool m_userEventFlagging, m_userEventDuplicates, m_calcUnintentionalLeaks, m_resyncFromUserFlagging, m_ahiReset; bool m_showLeakRedline; EventDataType m_leakRedLine, m_complianceHours, m_ahiWindow; EventDataType m_userEventRestriction1, m_userEventRestriction2, m_userEventDuration1, m_userEventDuration2; }; /*! \class ImportSettings \brief Profile Options relating to the Import process */ class SessionSettings : public PrefSettings { public: SessionSettings(Profile *profile) : PrefSettings(profile) { m_daySplitTime = initPref(STR_IS_DaySplitTime, QTime(12, 0, 0)).toTime(); m_preloadSummaries = initPref(STR_IS_PreloadSummaries, false).toBool(); m_combineCloseSessions = initPref(STR_IS_CombineCloseSessions, 240.0).toDouble(); m_ignoreShortSessions = initPref(STR_IS_IgnoreShorterSessions, 5.0).toDouble(); m_backupCardData = initPref(STR_IS_BackupCardData, true).toBool(); m_compressBackupData = initPref(STR_IS_CompressBackupData, false).toBool(); m_compressSessionData = initPref(STR_IS_CompressSessionData, false).toBool(); m_ignoreOlderSessions = initPref(STR_IS_IgnoreOlderSessions, false).toBool(); m_ignoreOlderSessionsDate=initPref(STR_IS_IgnoreOlderSessionsDate, QDateTime(QDate::currentDate().addYears(-1), daySplitTime()) ).toDateTime(); m_lockSummarySessions = initPref(STR_IS_LockSummarySessions, true).toBool(); m_warnOnUntestedMachine = initPref(STR_IS_WarnOnUntestedMachine, true).toBool(); m_warnOnUnexpectedData = initPref(STR_IS_WarnOnUnexpectedData, true).toBool(); } inline QTime daySplitTime() const { return m_daySplitTime; } inline bool preloadSummaries() const { return m_preloadSummaries; } inline double combineCloseSessions() const { return m_combineCloseSessions; } inline double ignoreShortSessions() const { return m_ignoreShortSessions; } inline bool compressSessionData() const { return m_compressSessionData; } inline bool compressBackupData() const { return m_compressBackupData; } inline bool backupCardData() const { return m_backupCardData; } inline bool ignoreOlderSessions() const { return m_ignoreOlderSessions; } inline QDateTime ignoreOlderSessionsDate() const { return m_ignoreOlderSessionsDate; } inline bool lockSummarySessions() const { return m_lockSummarySessions; } inline bool warnOnUntestedMachine() const { return m_warnOnUntestedMachine; } inline bool warnOnUnexpectedData() const { return m_warnOnUnexpectedData; } void setDaySplitTime(QTime time) { setPref(STR_IS_DaySplitTime, m_daySplitTime=time); } void setPreloadSummaries(bool b) { setPref(STR_IS_PreloadSummaries, m_preloadSummaries=b); } void setCombineCloseSessions(double val) { setPref(STR_IS_CombineCloseSessions, m_combineCloseSessions=val); } void setIgnoreShortSessions(double val) { setPref(STR_IS_IgnoreShorterSessions, m_ignoreShortSessions=val); } void setBackupCardData(bool b) { setPref(STR_IS_BackupCardData, m_backupCardData=b); } void setCompressBackupData(bool b) { setPref(STR_IS_CompressBackupData, m_compressBackupData=b); } void setCompressSessionData(bool b) { setPref(STR_IS_CompressSessionData, m_compressSessionData=b); } void setIgnoreOlderSessions(bool b) { setPref(STR_IS_IgnoreOlderSessions, m_ignoreOlderSessions=b); } void setIgnoreOlderSessionsDate(QDate date) { setPref(STR_IS_IgnoreOlderSessionsDate, m_ignoreOlderSessionsDate=QDateTime(date, daySplitTime())); } void setLockSummarySessions(bool b) { setPref(STR_IS_LockSummarySessions, m_lockSummarySessions=b); } void setWarnOnUntestedMachine(bool b) { setPref(STR_IS_WarnOnUntestedMachine, m_warnOnUntestedMachine=b); } void setWarnOnUnexpectedData(bool b) { setPref(STR_IS_WarnOnUnexpectedData, m_warnOnUnexpectedData=b); } QTime m_daySplitTime; QDateTime m_ignoreOlderSessionsDate; bool m_preloadSummaries, m_backupCardData, m_compressBackupData, m_compressSessionData, m_ignoreOlderSessions, m_lockSummarySessions; bool m_warnOnUntestedMachine, m_warnOnUnexpectedData; double m_combineCloseSessions, m_ignoreShortSessions; }; /*! \class AppearanceSettings \brief Profile Options relating to Visual Appearance */ class AppearanceSettings : public PrefSettings { public: //! \brief Create AppearanceSettings object given Profile *p, and initialize the defaults AppearanceSettings(Profile *profile) : PrefSettings(profile) { } }; /*! \class UserSettings \brief Profile Options relating to General User Settings */ class UserSettings : public PrefSettings { public: UserSettings(Profile *profile) : PrefSettings(profile) { initPref(STR_US_UnitSystem, US_Metric); initPref(STR_US_EventWindowSize, 4.0); m_skipEmptyDays = initPref(STR_US_SkipEmptyDays, true).toBool(); initPref(STR_US_RebuildCache, false); // FIXME: jedimark: can't remember... m_calculateRDI = initPref(STR_US_CalculateRDI, false).toBool(); m_prefCalcMiddle = initPref(STR_US_PrefCalcMiddle, (int)0).toInt(); m_prefCalcPercentile = initPref(STR_US_PrefCalcPercentile, (double)95.0).toDouble(); m_prefCalcMax = initPref(STR_US_PrefCalcMax, (int)0).toInt(); initPref(STR_US_StatReportMode, 0); initPref(STR_US_StatReportRangeStart, QDate(1,1,2000)); initPref(STR_US_StatReportRangeEnd, QDate(1,1,2000)); m_showUnownFlags = initPref(STR_US_ShowUnknownFlags, false).toBool(); initPref(STR_US_LastOverviewRange, 4); } UnitSystem unitSystem() const { return (UnitSystem)getPref(STR_US_UnitSystem).toInt(); } double eventWindowSize() const { return getPref(STR_US_EventWindowSize).toDouble(); } inline bool skipEmptyDays() const { return m_skipEmptyDays; } bool rebuildCache() const { return getPref(STR_US_RebuildCache).toBool(); } inline bool calculateRDI() const { return m_calculateRDI; } inline int prefCalcMiddle() const { return m_prefCalcMiddle; } inline double prefCalcPercentile() const { return m_prefCalcPercentile; } inline int prefCalcMax() const { return m_prefCalcMax; } int statReportMode() const { return getPref(STR_US_StatReportMode).toInt(); } QDate statReportRangeStart() const { return getPref(STR_US_StatReportRangeStart).toDate(); } QDate statReportRangeEnd() const { return getPref(STR_US_StatReportRangeEnd).toDate(); } inline bool showUnknownFlags() const { return m_showUnownFlags; } int lastOverviewRange() const { return getPref(STR_US_LastOverviewRange).toInt(); } QDate customOverviewRangeStart () const { return getPref(STR_US_CustomOverviewRangeStart).toDate(); } QDate customOverviewRangeEnd () const { return getPref(STR_US_CustomOverviewRangeEnd).toDate(); } void setUnitSystem(UnitSystem us) { setPref(STR_US_UnitSystem, (int)us); } void setEventWindowSize(double size) { setPref(STR_US_EventWindowSize, size); } void setSkipEmptyDays(bool b) { setPref(STR_US_SkipEmptyDays, m_skipEmptyDays=b); } void setRebuildCache(bool rebuild) { setPref(STR_US_RebuildCache, rebuild); } void setCalculateRDI(bool rdi) { setPref(STR_US_CalculateRDI, m_calculateRDI=rdi); } void setPrefCalcMiddle(int i) { setPref(STR_US_PrefCalcMiddle, m_prefCalcMiddle=i); } void setPrefCalcPercentile(double p) { setPref(STR_US_PrefCalcPercentile, m_prefCalcPercentile=p); } void setPrefCalcMax(int i) { setPref(STR_US_PrefCalcMax, m_prefCalcMax=i); } void setStatReportMode(int i) { setPref(STR_US_StatReportMode, i); } void setStatReportRangeStart(QDate i) { setPref (STR_US_StatReportRangeStart, i); } void setStatReportRangeEnd(QDate i) { setPref (STR_US_StatReportRangeEnd, i); } void setShowUnknownFlags(bool b) { setPref(STR_US_ShowUnknownFlags, m_showUnownFlags=b); } void setLastOverviewRange(int i) { setPref(STR_US_LastOverviewRange, i); } void setCustomOverviewRangeStart(QDate i) { setPref(STR_US_CustomOverviewRangeStart, i); } void setCustomOverviewRangeEnd(QDate i) { setPref(STR_US_CustomOverviewRangeEnd, i); } bool m_calculateRDI, m_showUnownFlags, m_skipEmptyDays; int m_prefCalcMiddle, m_prefCalcMax; double m_prefCalcPercentile; }; #endif // PROFILES_H OSCAR-code-v1.3.1/oscar/SleepLib/progressdialog.cpp000066400000000000000000000031211417327530600220240ustar00rootroot00000000000000/* SleepLib Progress Dialog Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "progressdialog.h" ProgressDialog::ProgressDialog(QWidget * parent): QDialog(parent, Qt::Tool | Qt::FramelessWindowHint) { statusMsg = new QLabel(QObject::tr("Please Wait...")); hlayout = new QHBoxLayout; imglabel = new QLabel(this); vlayout = new QVBoxLayout; progress = new QProgressBar(this); this->setLayout(vlayout); vlayout->addLayout(hlayout); hlayout->addWidget(imglabel); hlayout->addWidget(statusMsg,1,Qt::AlignCenter); vlayout->addWidget(progress,1); progress->setMaximum(100); abortButton = nullptr; setWindowModality(Qt::ApplicationModal); } ProgressDialog::~ProgressDialog() { if (abortButton) { disconnect(abortButton, SIGNAL(released()), this, SLOT(onAbortClicked())); } } void ProgressDialog::setProgressMax(int max) { progress->setMaximum(max); } void ProgressDialog::setProgressValue(int val) { progress->setValue(val); } void ProgressDialog::setMessage(QString msg) { statusMsg->setText(msg); } void ProgressDialog::addAbortButton() { abortButton = new QPushButton(tr("Abort"),this); connect(abortButton, SIGNAL(released()), this, SLOT(onAbortClicked())); hlayout->addWidget(abortButton); } void ProgressDialog::onAbortClicked() { emit abortClicked(); } OSCAR-code-v1.3.1/oscar/SleepLib/progressdialog.h000066400000000000000000000021401417327530600214710ustar00rootroot00000000000000/* SleepLib Progress Dialog Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef PROGRESSDIALOG_H #define PROGRESSDIALOG_H #include #include #include #include #include #include class ProgressDialog:public QDialog { Q_OBJECT public: explicit ProgressDialog(QWidget * parent); virtual ~ProgressDialog(); void addAbortButton(); void setPixmap(QPixmap &pixmap) { imglabel->setPixmap(pixmap); } QProgressBar * progress; public slots: void setMessage(QString msg); void onAbortClicked(); void setProgressMax(int max); void setProgressValue(int val); signals: void abortClicked(); protected: QLabel * statusMsg; QHBoxLayout *hlayout; QLabel * imglabel; QVBoxLayout * vlayout; QPushButton * abortButton; }; #endif // PROGRESSDIALOG_H OSCAR-code-v1.3.1/oscar/SleepLib/schema.cpp000066400000000000000000001315771417327530600202610ustar00rootroot00000000000000/* Channel / Schema Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include "common.h" #include "schema.h" #include "common_gui.h" #include "SleepLib/profiles.h" QColor adjustcolor(QColor color, float ar=1.0, float ag=1.0, float ab=1.0) { int r = color.red(); int g = color.green(); int b = color.blue(); r += rand() & 64; g += rand() & 64; b += rand() & 64; r = qMin(int(r * ar), 255); g = qMin(int(g * ag), 255); b = qMin(int(b * ab), 255); return QColor(r,g,b, color.alpha()); } QColor darken(QColor color, float p); namespace schema { void resetChannels(); ChannelList channel; Channel EmptyChannel; Channel *SessionEnabledChannel; QHash ChanTypes; QHash DataTypes; QHash Scopes; bool schema_initialized = false; // Order in which indices appear on Daily page void setOrders() { int order = 1; schema::channel[CPAP_PB].setOrder(order++); schema::channel[CPAP_CSR].setOrder(order++); schema::channel[CPAP_Ramp].setOrder(order++); schema::channel[CPAP_LargeLeak].setOrder(order++); schema::channel[CPAP_ClearAirway].setOrder(order++); schema::channel[CPAP_NRI].setOrder(order++); schema::channel[CPAP_AllApnea].setOrder(order++); schema::channel[CPAP_Obstructive].setOrder(order++); schema::channel[CPAP_Apnea].setOrder(order++); schema::channel[CPAP_Hypopnea].setOrder(order++); schema::channel[CPAP_FlowLimit].setOrder(order++); schema::channel[CPAP_RERA].setOrder(order++); schema::channel[CPAP_ExP].setOrder(order++); schema::channel[CPAP_VSnore].setOrder(order++); schema::channel[CPAP_VSnore2].setOrder(order++); // Any channels not set above appear here, as default value for order is 255. // Finally, include user flags order = 256; schema::channel[CPAP_UserFlag1].setOrder(order++); schema::channel[CPAP_UserFlag2].setOrder(order++); } void init() { if (schema_initialized) { return; } schema_initialized = true; EmptyChannel = Channel(0, DATA, MT_UNKNOWN, DAY, "Empty", "Empty", "Empty Channel", "", ""); SessionEnabledChannel = new Channel(1, DATA, MT_UNKNOWN, DAY, "Enabled", "Enabled", "Session Enabled", "", ""); channel.channels[1] = SessionEnabledChannel; channel.names["Enabled"] = SessionEnabledChannel; SESSION_ENABLED = 1; ChanTypes["data"] = DATA; //Types["waveform"]=WAVEFORM; ChanTypes["setting"] = SETTING; Scopes["session"] = SESSION; Scopes["day"] = DAY; Scopes["machine"] = MACHINE; Scopes["global"] = GLOBAL; DataTypes[""] = DEFAULT; DataTypes["bool"] = BOOL; DataTypes["double"] = DOUBLE; DataTypes["integer"] = INTEGER; DataTypes["string"] = STRING; DataTypes["richtext"] = RICHTEXT; DataTypes["date"] = DATE; DataTypes["datetime"] = DATETIME; DataTypes["time"] = TIME; // Note: Old channel names stored in channels.xml are not translatable.. they need to be moved to be defined AFTER here instead if (!schema::channel.Load(":/docs/channels.xml")) { QMessageBox::critical(0, STR_MessageBox_Error, QObject::tr("Couldn't parse Channels.xml, OSCAR cannot continue and is exiting."), QMessageBox::Ok); QApplication::exit(-1); } Channel *ch; // Lookup Code strings are used internally and not meant to be translated // Lookup Code strings are also used as a key to a graph of this channel // Group ChannelID Code Type Scope Lookup Code Translable Name Description Shortened Name Units String FieldType Default Color // Pressure Related Settings schema::channel.add(GRP_CPAP, new Channel(CPAP_Pressure = 0x110C, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_Pressure, STR_TR_Pressure, QObject::tr("Therapy Pressure"), STR_TR_Pressure, STR_UNIT_CMH2O, DEFAULT, QColor("red"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_IPAP = 0x110D, WAVEFORM, MT_CPAP, SESSION, "IPAP", STR_TR_IPAP, QObject::tr("Inspiratory Pressure"), STR_TR_IPAP, STR_UNIT_CMH2O, DEFAULT, QColor("red"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_IPAPLo = 0x1110, WAVEFORM, MT_CPAP, SESSION, "IPAPLo", STR_TR_IPAPLo, QObject::tr("Lower Inspiratory Pressure"), STR_TR_IPAPLo, STR_UNIT_CMH2O, DEFAULT, QColor("orange"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_IPAPHi = 0x1111, WAVEFORM, MT_CPAP, SESSION, "IPAPHi", STR_TR_IPAPHi, QObject::tr("Higher Inspiratory Pressure"), STR_TR_IPAPHi, STR_UNIT_CMH2O, DEFAULT, QColor("orange"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_EPAP = 0x110E, WAVEFORM, MT_CPAP, SESSION, "EPAP", STR_TR_EPAP, QObject::tr("Expiratory Pressure"), STR_TR_EPAP, STR_UNIT_CMH2O, DEFAULT, QColor("green"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_EPAPLo = 0x111C, WAVEFORM, MT_CPAP, SESSION, "EPAPLo", STR_TR_EPAPLo, QObject::tr("Lower Expiratory Pressure"), STR_TR_EPAPLo, STR_UNIT_CMH2O, DEFAULT, QColor("light blue"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_EPAPHi = 0x111D, WAVEFORM, MT_CPAP, SESSION, "EPAPHi", STR_TR_EPAPHi, QObject::tr("Higher Expiratory Pressure"), STR_TR_EPAPHi, STR_UNIT_CMH2O, DEFAULT, QColor("aqua"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_PS = 0x110F, WAVEFORM, MT_CPAP, SESSION, "PS", STR_TR_PS, QObject::tr("Pressure Support"), STR_TR_PS, STR_UNIT_CMH2O, DEFAULT, QColor("grey"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_PSMin = 0x111A, SETTING, MT_CPAP, SESSION, "PSMin", QObject::tr("PS Min") , QObject::tr("Pressure Support Minimum"), QObject::tr("PS Min"), STR_UNIT_CMH2O, DEFAULT, QColor("dark cyan"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_PSMax = 0x111B, SETTING, MT_CPAP, SESSION, "PSMax", QObject::tr("PS Max"), QObject::tr("Pressure Support Maximum"), QObject::tr("PS Max"), STR_UNIT_CMH2O, DEFAULT, QColor("dark magenta"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_PressureMin = 0x1020, SETTING, MT_CPAP, SESSION, "PressureMin", QObject::tr("Min Pressure"), QObject::tr("Minimum Therapy Pressure"), QObject::tr("Pressure Min"), STR_UNIT_CMH2O, DEFAULT, QColor("orange"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_PressureMax = 0x1021, SETTING, MT_CPAP, SESSION, "PressureMax", QObject::tr("Max Pressure"), QObject::tr("Maximum Therapy Pressure"), QObject::tr("Pressure Max"), STR_UNIT_CMH2O, DEFAULT, QColor("light blue"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_RampTime = 0x1022, SETTING, MT_CPAP, SESSION, "RampTime", QObject::tr("Ramp Time") , QObject::tr("Ramp Delay Period"), QObject::tr("Ramp Time"), STR_UNIT_Minutes, DEFAULT, QColor("black"))); schema::channel.add(GRP_CPAP, ch = new Channel(CPAP_RampPressure = 0x1023, SETTING, MT_CPAP, SESSION, "RampPressure", QObject::tr("Ramp Pressure"), QObject::tr("Starting Ramp Pressure"), QObject::tr("Ramp Pressure"),STR_UNIT_CMH2O, LOOKUP, QColor("black"))); ch->addOption(0, STR_TR_Off); schema::channel.add(GRP_CPAP, new Channel(CPAP_Ramp = 0x1027, SPAN, MT_CPAP, SESSION, "Ramp", QObject::tr("Ramp Event") , QObject::tr("Ramp Event"), QObject::tr("Ramp"), STR_UNIT_EventsPerHour,DEFAULT, QColor("light blue"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_PressureSet = 0x11A4, WAVEFORM, MT_CPAP, SESSION, "PressureSet", QObject::tr("Pressure Set"), QObject::tr("Pressure Setting"), QObject::tr("Pressure Set"), STR_UNIT_CMH2O, DEFAULT, QColor("dark red"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_IPAPSet = 0x11A5, WAVEFORM, MT_CPAP, SESSION, "IPAPSet", QObject::tr("IPAP Set"), QObject::tr("IPAP Setting"), QObject::tr("IPAP Set"), STR_UNIT_CMH2O, DEFAULT, QColor("dark red"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_EPAPSet = 0x11A6, WAVEFORM, MT_CPAP, SESSION, "EPAPSet", QObject::tr("EPAP Set"), QObject::tr("EPAP Setting"), QObject::tr("EPAP Set"), STR_UNIT_CMH2O, DEFAULT, QColor("dark green"))); // Flags schema::channel.add(GRP_CPAP, new Channel(CPAP_CSR = 0x1000, SPAN, MT_CPAP, SESSION, "CSR", QObject::tr("Cheyne Stokes Respiration"), QObject::tr("An abnormal period of Cheyne Stokes Respiration"), QObject::tr("CSR"), STR_UNIT_Percentage,DEFAULT, COLOR_CSR)); schema::channel.add(GRP_CPAP, new Channel(CPAP_PB = 0x1028, SPAN, MT_CPAP, SESSION, "PB", QObject::tr("Periodic Breathing"),QObject::tr("An abnormal period of Periodic Breathing"), QObject::tr("PB"),STR_UNIT_Percentage, DEFAULT, COLOR_CSR)); schema::channel.add(GRP_CPAP, new Channel(CPAP_ClearAirway = 0x1001, FLAG, MT_CPAP, SESSION, "ClearAirway", QObject::tr("Clear Airway"), QObject::tr("An apnea where the airway is open"), QObject::tr("CA"), STR_UNIT_EventsPerHour, DEFAULT, QColor("purple"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_Obstructive = 0x1002, FLAG, MT_CPAP, SESSION, "Obstructive", QObject::tr("Obstructive"), QObject::tr("An apnea caused by airway obstruction"), QObject::tr("OA"), STR_UNIT_EventsPerHour, DEFAULT, QColor("#40c0ff"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_Hypopnea = 0x1003, FLAG, MT_CPAP, SESSION, "Hypopnea", QObject::tr("Hypopnea"), QObject::tr("A partially obstructed airway"), QObject::tr("H"), STR_UNIT_EventsPerHour, DEFAULT, QColor("blue"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_Apnea = 0x1004, FLAG, MT_CPAP, SESSION, "Apnea", QObject::tr("Unclassified Apnea"), QObject::tr("An apnea that couldn't be determined as Central or Obstructive."),QObject::tr("UA"), STR_UNIT_EventsPerHour, DEFAULT, QColor("dark green"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_AllApnea = 0x1010, FLAG, MT_CPAP, SESSION, "AllApnea", QObject::tr("Apnea"), QObject::tr("An apnea reportred by your CPAP machine."),QObject::tr("A"), STR_UNIT_EventsPerHour, DEFAULT, QColor("#40c0ff"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_FlowLimit = 0x1005, FLAG, MT_CPAP, SESSION, "FlowLimit", QObject::tr("Flow Limitation"), QObject::tr("A restriction in breathing from normal, causing a flattening of the flow waveform."), QObject::tr("FL"), STR_UNIT_EventsPerHour, DEFAULT, QColor("#404040"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_RERA = 0x1006, FLAG, MT_CPAP, SESSION, "RERA", QObject::tr("RERA"),QObject::tr("Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance."), QObject::tr("RE"), STR_UNIT_EventsPerHour, DEFAULT, COLOR_Gold)); schema::channel.add(GRP_CPAP, new Channel(CPAP_VSnore = 0x1007, FLAG, MT_CPAP, SESSION, "VSnore", QObject::tr("Vibratory Snore"), QObject::tr("A vibratory snore"), QObject::tr("VS"), STR_UNIT_EventsPerHour, DEFAULT, QColor("red"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_VSnore2 = 0x1008, FLAG, MT_CPAP, SESSION, "VSnore2", QObject::tr("Vibratory Snore (VS2) "),QObject::tr("A vibratory snore as detcted by a System One machine"),QObject::tr("VS2"), STR_UNIT_EventsPerHour, DEFAULT, QColor("red"))); // This Large Leak record is just a flag marker, used by Intellipap for one schema::channel.add(GRP_CPAP, new Channel(CPAP_LeakFlag = 0x100a, FLAG, MT_CPAP, SESSION, "LeakFlag", QObject::tr("Leak Flag"), QObject::tr("A large mask leak affecting machine performance."), QObject::tr("LF"), STR_UNIT_EventsPerHour, DEFAULT, QColor("light gray"))); // The following is a Large Leak record that references a waveform span schema::channel.add(GRP_CPAP, new Channel(CPAP_LargeLeak = 0x1158, SPAN, MT_CPAP, SESSION, "LeakSpan", QObject::tr("Large Leak"),QObject::tr("A large mask leak affecting machine performance."), QObject::tr("LL"), STR_UNIT_EventsPerHour, DEFAULT, QColor("light gray"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_NRI = 0x100b, FLAG, MT_CPAP, SESSION, "NRI", QObject::tr("Non Responding Event"), QObject::tr("A type of respiratory event that won't respond to a pressure increase."), QObject::tr("NR"), STR_UNIT_EventsPerHour, DEFAULT, QColor("orange"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_ExP = 0x100c, FLAG, MT_CPAP, SESSION, "ExP", QObject::tr("Expiratory Puff"), QObject::tr("Intellipap event where you breathe out your mouth."), QObject::tr("EP"), STR_UNIT_EventsPerHour, DEFAULT, QColor("dark magenta"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_SensAwake = 0x100d, FLAG, MT_CPAP, SESSION, "SensAwake", QObject::tr("SensAwake"),QObject::tr("SensAwake feature will reduce pressure when waking is detected."),QObject::tr("SA"), STR_UNIT_EventsPerHour, DEFAULT, COLOR_Gold)); schema::channel.add(GRP_CPAP, new Channel(CPAP_UserFlag1 = 0x101e, FLAG, MT_CPAP, SESSION, "UserFlag1", QObject::tr("User Flag #1"), QObject::tr("A user definable event detected by OSCAR's flow waveform processor."), QObject::tr("UF1"), STR_UNIT_EventsPerHour, DEFAULT, QColor(0xc0,0xc0,0xe0))); schema::channel.add(GRP_CPAP, new Channel(CPAP_UserFlag2 = 0x101f, FLAG, MT_CPAP, SESSION, "UserFlag2", QObject::tr("User Flag #2"),QObject::tr("A user definable event detected by OSCAR's flow waveform processor."), QObject::tr("UF2"), STR_UNIT_EventsPerHour, DEFAULT, QColor(0xa0,0xa0,0xc0))); schema::channel.add(GRP_CPAP, new Channel(CPAP_UserFlag3 = 0x1024, FLAG, MT_CPAP, SESSION, "UserFlag3", QObject::tr("User Flag #3"),QObject::tr("A user definable event detected by OSCAR's flow waveform processor."), QObject::tr("UF3"), STR_UNIT_EventsPerHour, DEFAULT, QColor("dark grey"))); // Oximetry schema::channel.add(GRP_OXI, new Channel(OXI_Pulse = 0x1800, WAVEFORM, MT_OXIMETER, SESSION, STR_GRAPH_Oxi_Pulse, QObject::tr("Pulse Rate"), QObject::tr("Heart rate in beats per minute"), QObject::tr("Pulse Rate"), STR_UNIT_BPM, DEFAULT, QColor("red"))); schema::channel[OXI_Pulse].setLowerThreshold(40); schema::channel[OXI_Pulse].setUpperThreshold(130); schema::channel.add(GRP_OXI, new Channel(OXI_SPO2 = 0x1801, WAVEFORM, MT_OXIMETER, SESSION, STR_GRAPH_Oxi_SPO2, QString("SpO2 %"), QObject::tr("Blood-oxygen saturation percentage"), QString("SpO2"), STR_UNIT_Percentage, DEFAULT, QColor("blue"))); schema::channel[OXI_SPO2].setLowerThreshold(88); schema::channel.add(GRP_OXI, new Channel(OXI_Plethy = 0x1802, WAVEFORM, MT_OXIMETER, SESSION, STR_GRAPH_Oxi_Plethy, QObject::tr("Plethysomogram"), QObject::tr("An optical Photo-plethysomogram showing heart rhythm"), QObject::tr("Plethy"), STR_UNIT_Hz, DEFAULT, QColor("#404040"))); schema::channel.add(GRP_OXI, new Channel(OXI_Perf = 0x1805, WAVEFORM, MT_OXIMETER, SESSION, STR_GRAPH_Oxi_Perf, QObject::tr("Perfusion Index"), QObject::tr("A relative assessment of the pulse strength at the monitoring site"), QObject::tr("Perf. Index %"), STR_UNIT_Percentage, DEFAULT, QColor("magenta"))); schema::channel.add(GRP_OXI, new Channel(OXI_PulseChange = 0x1803, FLAG, MT_OXIMETER, SESSION, STR_GRAPH_Oxi_PulseChange, QObject::tr("Pulse Change"), QObject::tr("A sudden (user definable) change in heart rate"), QObject::tr("PC"), STR_UNIT_EventsPerHour, DEFAULT, QColor("light grey"))); schema::channel.add(GRP_OXI, new Channel(OXI_SPO2Drop = 0x1804, FLAG, MT_OXIMETER, SESSION, STR_GRAPH_Oxi_SPO2Drop, QObject::tr("SpO2 Drop"), QObject::tr("A sudden (user definable) drop in blood oxygen saturation"), QObject::tr("SD"), STR_UNIT_EventsPerHour, DEFAULT, QColor("light blue"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_FlowRate = 0x1100, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_FlowRate, QObject::tr("Flow Rate"), QObject::tr("Breathing flow rate waveform"), QObject::tr("Flow Rate"), STR_UNIT_LPM, DEFAULT, QColor("black"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_MaskPressure = 0x1101, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_MaskPressure, QObject::tr("Mask Pressure"), QObject::tr("Mask Pressure"), QObject::tr("Mask Pressure"), STR_UNIT_CMH2O, DEFAULT, QColor("blue"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_MaskPressureHi = 0x1102, WAVEFORM, MT_CPAP, SESSION, "MaskPressureHi", QObject::tr("Mask Pressure"), QObject::tr("Mask Pressure (High frequency)"), QObject::tr("Mask Pressure"), STR_UNIT_CMH2O, DEFAULT, QColor("blue"), 0x1101)); // linked to CPAP_MaskPressure schema::channel.add(GRP_CPAP, new Channel(CPAP_TidalVolume = 0x1103, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_TidalVolume, QObject::tr("Tidal Volume"), QObject::tr("Amount of air displaced per breath"), QObject::tr("Tidal Volume"), STR_UNIT_ml, DEFAULT, QColor("magenta"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_Snore = 0x1104, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_Snore, QObject::tr("Snore"), QObject::tr("Graph displaying snore volume"), QObject::tr("Snore"), STR_UNIT_Unknown, DEFAULT, QColor("grey"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_MinuteVent = 0x1105, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_MinuteVent, QObject::tr("Minute Ventilation"), QObject::tr("Amount of air displaced per minute"), QObject::tr("Minute Vent."), STR_UNIT_LPM, DEFAULT, QColor("dark cyan"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_RespRate = 0x1106, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_RespRate, QObject::tr("Respiratory Rate"), QObject::tr("Rate of breaths per minute"), QObject::tr("Resp. Rate"), STR_UNIT_BreathsPerMinute, DEFAULT, QColor("dark magenta"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_PTB = 0x1107, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_PTB, QObject::tr("Patient Triggered Breaths"), QObject::tr("Percentage of breaths triggered by patient"), QObject::tr("Pat. Trig. Breaths"), STR_UNIT_Percentage, DEFAULT, QColor("dark grey"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_Leak = 0x1108, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_LeakRate, QObject::tr("Leak Rate"), QObject::tr("Rate of detected mask leakage"), QObject::tr("Leak Rate"), STR_UNIT_LPM, DEFAULT, QColor("dark green"))); schema::channel[CPAP_Leak].setLowerThreshold(24.0); schema::channel.add(GRP_CPAP, new Channel(CPAP_IE = 0x1109, WAVEFORM, MT_CPAP, SESSION, "IE", QObject::tr("I:E Ratio"), QObject::tr("Ratio between Inspiratory and Expiratory time"), QObject::tr("I:E Ratio"), STR_UNIT_Ratio, DEFAULT, QColor("dark red"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_Te = 0x110A, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_Te, QObject::tr("Expiratory Time"), QObject::tr("Time taken to breathe out"), QObject::tr("Exp. Time"), STR_UNIT_Seconds, DEFAULT, QColor("dark green"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_Ti = 0x110B, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_Ti, QObject::tr("Inspiratory Time"), QObject::tr("Time taken to breathe in"), QObject::tr("Insp. Time"), STR_UNIT_Seconds, DEFAULT, QColor("dark blue"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_RespEvent = 0x1112, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_RespEvent, QObject::tr("Respiratory Event"), QObject::tr("A ResMed data item: Trigger Cycle Event"), QObject::tr("Resp. Event"), STR_UNIT_CMH2O, DEFAULT, QColor("black"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_FLG = 0x1113, WAVEFORM, MT_CPAP, SESSION, STR_GRAPH_FlowLimitation, QObject::tr("Flow Limitation"), QObject::tr("Graph showing severity of flow limitations"), QObject::tr("Flow Limit."), STR_UNIT_Severity, DEFAULT, QColor("#585858"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_TgMV = 0x1114, WAVEFORM, MT_CPAP, SESSION, "TgMV", QObject::tr("Target Minute Ventilation"), QObject::tr("Target Minute Ventilation"), QObject::tr("Target Vent."), STR_UNIT_LPM, DEFAULT, QColor("dark red"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_MaxLeak = 0x1115, WAVEFORM, MT_CPAP, SESSION, "MaxLeak", QObject::tr("Maximum Leak"), QObject::tr("The maximum rate of mask leakage"), QObject::tr("Max Leaks"), STR_UNIT_LPM, DEFAULT, QColor("dark red"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_AHI = 0x1116, WAVEFORM, MT_CPAP, SESSION, "AHI", QObject::tr("Apnea Hypopnea Index"), QObject::tr("Graph showing running AHI for the past hour"), QObject::tr("AHI"), STR_UNIT_EventsPerHour, DEFAULT, QColor("dark red"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_LeakTotal = 0x1117, WAVEFORM, MT_CPAP, SESSION, "LeakTotal", QObject::tr("Total Leak Rate"), QObject::tr("Detected mask leakage including natural Mask leakages"), QObject::tr("Total Leaks"), STR_UNIT_LPM, DEFAULT, QColor("dark green"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_LeakMedian = 0x1118, WAVEFORM, MT_CPAP, SESSION, "LeakMedian", QObject::tr("Median Leak Rate"), QObject::tr("Median rate of detected mask leakage"), QObject::tr("Median Leaks"), STR_UNIT_LPM, DEFAULT, QColor("dark green"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_RDI = 0x1119, WAVEFORM, MT_CPAP, SESSION, "RDI", QObject::tr("Respiratory Disturbance Index"), QObject::tr("Graph showing running RDI for the past hour"), QObject::tr("RDI"), STR_UNIT_EventsPerHour, DEFAULT, QColor("dark red"))); // Positional sensors schema::channel.add(GRP_POS, new Channel(POS_Orientation = 0x2990, WAVEFORM, MT_POSITION, SESSION, STR_GRAPH_Orientation, QObject::tr("Orientation"), QObject::tr("Sleep position in degrees"), QObject::tr("Orientation"), STR_UNIT_Degrees, DEFAULT, QColor("dark blue"))); schema::channel.add(GRP_POS, new Channel(POS_Inclination = 0x2991, WAVEFORM, MT_POSITION, SESSION, STR_GRAPH_Inclination, QObject::tr("Inclination"), QObject::tr("Upright angle in degrees"), QObject::tr("Inclination"), STR_UNIT_Degrees, DEFAULT, QColor("dark magenta"))); schema::channel.add(GRP_POS, new Channel(POS_Movement = 0x2992, WAVEFORM, MT_POSITION, SESSION, STR_GRAPH_Motion, QObject::tr("Movement"), QObject::tr("Movement detector"), QObject::tr("Movement"), STR_UNIT_Unknown, DEFAULT, QColor("dark green"))); schema::channel.add(GRP_CPAP, new Channel(RMS9_MaskOnTime = 0x1025, DATA, MT_CPAP, SESSION, "MaskOnTime", QObject::tr("Mask On Time"), QObject::tr("Time started according to str.edf"), QObject::tr("Mask On Time"), STR_UNIT_Unknown, DEFAULT, Qt::black)); schema::channel.add(GRP_CPAP, new Channel(CPAP_SummaryOnly = 0x1026, DATA, MT_CPAP, SESSION, "SummaryOnly", QObject::tr("Summary Only"), QObject::tr("CPAP Session contains summary data only"), QObject::tr("Summary Only"), STR_UNIT_Unknown, DEFAULT, Qt::black)); schema::channel.add(GRP_CPAP, ch = new Channel(CPAP_Mode = 0x1200, SETTING, MT_CPAP, SESSION, "PAPMode", QObject::tr("PAP Mode"), QObject::tr("PAP Device Mode"), QObject::tr("PAP Mode"), QString(), LOOKUP, Qt::black)); ch->addOption(0, STR_TR_Unknown); ch->addOption(1, STR_TR_CPAP); ch->addOption(2, QObject::tr("APAP (Variable)")); ch->addOption(3, QObject::tr("Fixed Bi-Level")); ch->addOption(4, QObject::tr("Auto Bi-Level (Fixed PS)")); ch->addOption(5, QObject::tr("Auto Bi-Level (Variable PS)")); ch->addOption(6, QObject::tr("ASV (Fixed EPAP)")); ch->addOption(7, QObject::tr("ASV (Variable EPAP)")); ch->addOption(8, QObject::tr("AVAPS")); ///////////////////////////////////////////////////////////////// // Old Journal system crap ///////////////////////////////////////////////////////////////// schema::channel.add(GRP_JOURNAL, ch = new Channel(Journal_Weight = 0x0803, DATA, MT_JOURNAL, DAY, "Weight", QObject::tr("Weight"), QObject::tr("Weight"), QObject::tr("Weight"), STR_UNIT_KG, DOUBLE, Qt::black)); schema::channel.add(GRP_JOURNAL, ch = new Channel(0x0804, DATA, MT_JOURNAL, DAY, "Height", QObject::tr("Height"), QObject::tr("Physical Height"), QObject::tr("Height"), STR_UNIT_CM, DOUBLE, Qt::black)); schema::channel.add(GRP_JOURNAL, ch = new Channel(Bookmark_Notes=0x0805, DATA, MT_JOURNAL, DAY, "BookmarkNotes", QObject::tr("Notes"), QObject::tr("Bookmark Notes"), QObject::tr("Notes"), QString(), STRING, Qt::black)); // This may as well be calculated schema::channel.add(GRP_JOURNAL, ch = new Channel(Journal_BMI = 0x0806, DATA, MT_JOURNAL, DAY, "BMI", QObject::tr("BMI"), QObject::tr("Body Mass Index"), QObject::tr("BMI"), QString(), DOUBLE, Qt::black)); schema::channel.add(GRP_JOURNAL, ch = new Channel(Journal_ZombieMeter = 0x0807, DATA, MT_JOURNAL, DAY, "ZombieMeter", QObject::tr("Zombie"), QObject::tr("How you feel (0 = like crap, 10 = unstoppable)"), QObject::tr("Zombie"), QString(), DOUBLE, Qt::black)); schema::channel.add(GRP_JOURNAL, ch = new Channel(Bookmark_Start=0x0808, DATA, MT_JOURNAL, DAY, "BookmarkStart", QObject::tr("Start"), QObject::tr("Bookmark Start"), QObject::tr("Start"), QString(), INTEGER, Qt::black)); schema::channel.add(GRP_JOURNAL, ch = new Channel(Bookmark_End=0x0809, DATA, MT_JOURNAL, DAY, "BookmarkEnd", QObject::tr("End"), QObject::tr("Bookmark End"), QObject::tr("End"), QString(), DOUBLE, Qt::black)); schema::channel.add(GRP_JOURNAL, ch = new Channel(LastUpdated=0x080a, DATA, MT_JOURNAL, DAY, "LastUpdated", QObject::tr("Last Updated"), QObject::tr("Last Updated"), QObject::tr("Last Updated"), QString(), DATETIME, Qt::black)); schema::channel.add(GRP_JOURNAL, ch = new Channel(Journal_Notes = 0xd000, DATA, MT_JOURNAL, DAY, "Journal", QObject::tr("Journal Notes"), QObject::tr("Journal Notes"), QObject::tr("Journal"), QString(), RICHTEXT, Qt::black)); ////////////////////////////////////////////////////////////////////// // Sleep Stage Channels ////////////////////////////////////////////////////////////////////// schema::channel.add(GRP_SLEEP, ch = new Channel(ZEO_SleepStage = 0x2000, WAVEFORM, MT_SLEEPSTAGE, SESSION, "SleepStage", QObject::tr("Sleep Stage"), QObject::tr("1=Awake 2=REM 3=Light Sleep 4=Deep Sleep"), QObject::tr("Sleep Stage"), QString(), INTEGER, Qt::darkGray)); schema::channel.add(GRP_SLEEP, ch = new Channel(0x2001, WAVEFORM, MT_SLEEPSTAGE, SESSION, "ZeoBW", QObject::tr("Brain Wave"), QObject::tr("Brain Wave"), QObject::tr("BrainWave"), QString(), INTEGER, Qt::black)); schema::channel.add(GRP_SLEEP, ch = new Channel(ZEO_Awakenings = 0x2002, DATA, MT_SLEEPSTAGE, SESSION, "Awakenings", QObject::tr("Awakenings"), QObject::tr("Number of Awakenings"), QObject::tr("Awakenings"), QString(), INTEGER, Qt::black)); schema::channel.add(GRP_SLEEP, ch = new Channel(ZEO_MorningFeel= 0x2003, DATA, MT_SLEEPSTAGE, SESSION, "MorningFeel", QObject::tr("Morning Feel"), QObject::tr("How you felt in the morning"), QObject::tr("Morning Feel"), QString(), INTEGER, Qt::black)); schema::channel.add(GRP_SLEEP, ch = new Channel(ZEO_TimeInWake = 0x2004, DATA, MT_SLEEPSTAGE, SESSION, "TimeInWake", QObject::tr("Time Awake"), QObject::tr("Time spent awake"), QObject::tr("Time Awake"), STR_UNIT_Minutes, INTEGER, Qt::black)); schema::channel.add(GRP_SLEEP, ch = new Channel(ZEO_TimeInREM = 0x2005, DATA, MT_SLEEPSTAGE, SESSION, "TimeInREM", QObject::tr("Time In REM Sleep"), QObject::tr("Time spent in REM Sleep"), QObject::tr("Time in REM Sleep"), STR_UNIT_Minutes, INTEGER, Qt::black)); schema::channel.add(GRP_SLEEP, ch = new Channel(ZEO_TimeInLight= 0x2006, DATA, MT_SLEEPSTAGE, SESSION, "TimeInLight",QObject::tr("Time In Light Sleep"), QObject::tr("Time spent in light sleep"), QObject::tr("Time in Light Sleep"), STR_UNIT_Minutes, INTEGER, Qt::black)); schema::channel.add(GRP_SLEEP, ch = new Channel(ZEO_TimeInDeep = 0x2007, DATA, MT_SLEEPSTAGE, SESSION, "TimeInDeep", QObject::tr("Time In Deep Sleep"), QObject::tr("Time spent in deep sleep"), QObject::tr("Time in Deep Sleep"), STR_UNIT_Minutes, INTEGER, Qt::black)); schema::channel.add(GRP_SLEEP, ch = new Channel(ZEO_TimeToZ = 0x2008, DATA, MT_SLEEPSTAGE, SESSION, "TimeToZ", QObject::tr("Time to Sleep"), QObject::tr("Time taken to get to sleep"), QObject::tr("Time to Sleep"), STR_UNIT_Minutes, INTEGER, Qt::black)); schema::channel.add(GRP_SLEEP, ch = new Channel(ZEO_ZQ = 0x2009, DATA, MT_SLEEPSTAGE, SESSION, "ZeoZQ", QObject::tr("Zeo ZQ"), QObject::tr("Zeo sleep quality measurement"), QObject::tr("ZEO ZQ"), QString(), INTEGER, Qt::black)); NoChannel = 0; /* CPAP_BrokenSummary = schema::channel["BrokenSummary"].id(); CPAP_BrokenWaveform = schema::channel["BrokenWaveform"].id(); */ // // schema::channel.add(GRP_CPAP, ch=new Channel(CPAP_Test1 = 0x111e, DATA, MT_CPAP, SESSION, STR_GRAPH_TestChan1, QObject::tr("Debugging channel #1"), QObject::tr("For internal use only"), QObject::tr("Test #1"), QString(), INTEGER, QColor("pink"))); schema::channel.add(GRP_CPAP, ch=new Channel(CPAP_Test2 = 0x111f, DATA, MT_CPAP, SESSION, STR_GRAPH_TestChan2, QObject::tr("Debugging channel #2"), QObject::tr("For internal use only"), QObject::tr("Test #2"), QString(), INTEGER, Qt::blue)); RMS9_E01 = schema::channel["RMS9_E01"].id(); RMS9_E02 = schema::channel["RMS9_E02"].id(); RMS9_SetPressure = schema::channel["SetPressure"].id(); // TODO: this isn't needed anymore CPAP_HumidSetting = schema::channel["HumidSet"].id(); INTELLIPAP_Unknown1 = schema::channel["IntUnk1"].id(); INTELLIPAP_Unknown2 = schema::channel["IntUnk2"].id(); schema::channel[CPAP_Leak].setShowInOverview(true); schema::channel[CPAP_RespRate].setShowInOverview(true); schema::channel[CPAP_MinuteVent].setShowInOverview(true); schema::channel[CPAP_TidalVolume].setShowInOverview(true); schema::channel[CPAP_CSR].setShowInOverview(true); schema::channel[CPAP_PB].setShowInOverview(true); schema::channel[CPAP_LargeLeak].setShowInOverview(true); schema::channel[CPAP_FLG].setShowInOverview(true); // Identify the channels that contribute to AHI calculation // When adding more AHI-contributing channels, // 1) update this list // 2) Update setOrders() above // 3) Search source for CPAP_Obstructive to look for possible other places to add new channel // 4) Search for AllAhiChannels to find all uses of the AHI-contributing channel list ahiChannels.append(CPAP_ClearAirway); ahiChannels.append(CPAP_AllApnea); ahiChannels.append(CPAP_Obstructive); ahiChannels.append(CPAP_Hypopnea); ahiChannels.append(CPAP_Apnea); } void done() { schema::channel.names.clear(); for (auto & c : schema::channel.channels.values()) { delete c; } schema::channel.channels.clear(); schema::channel.groups.clear(); schema_initialized = false; } void resetChannels() { done(); init(); QList list = GetLoaders(); for (int i=0; i< list.size(); ++i) { MachineLoader * loader = list.at(i); loader->initChannels(); } setOrders(); } Channel::Channel(ChannelID id, ChanType type, MachineType machtype, ScopeType scope, QString code, QString fullname, QString description, QString label, QString unit, DataType datatype, QColor color, int link): m_id(id), m_type(type), m_machtype(machtype), m_scope(scope), m_code(code), m_fullname(fullname), m_description(description), m_label(label), m_unit(unit), m_datatype(datatype), m_defaultcolor(color), m_link(link), m_upperThreshold(0), m_lowerThreshold(0), m_upperThresholdColor(Qt::red), m_lowerThresholdColor(Qt::green), m_enabled(true), m_order(255) { if (type == WAVEFORM) { calc[Calc_Min] = ChannelCalc(id, Calc_Min, adjustcolor(color, 0.25f, 1.0f, 1.3f), false); calc[Calc_Middle] = ChannelCalc(id, Calc_Middle, adjustcolor(color, 1.3f, 1.0f, 1.0f), false); calc[Calc_Perc] = ChannelCalc(id, Calc_Perc, adjustcolor(color, 1.1f, 1.2f, 1.0f), false); calc[Calc_Max] = ChannelCalc(id, Calc_Max, adjustcolor(color, 0.5f, 1.2f, 1.0f), false); calc[Calc_Zero] = ChannelCalc(id, Calc_Zero, Qt::red, false); calc[Calc_LowerThresh] = ChannelCalc(id, Calc_LowerThresh, Qt::blue, false); calc[Calc_UpperThresh] = ChannelCalc(id, Calc_UpperThresh, Qt::red, false); } m_showInOverview = false; default_fullname = fullname; default_label = label; default_description = description; } bool Channel::isNull() { return (this == &EmptyChannel); } ChannelList::ChannelList() : m_doctype("channels") { } ChannelList::~ChannelList() { for (auto & chan : channels) { delete chan; } } bool ChannelList::Load(QString filename) { QDomDocument doc(m_doctype); QFile file(filename); qDebug() << "Opening " << filename; if (!file.open(QIODevice::ReadOnly)) { // qWarning() << "Could not open" << filename; qWarning() << "Could not open" << filename << "for reading, error code" << file.error() << file.errorString(); return false; } QString errorMsg; int errorLine; if (!doc.setContent(&file, false, &errorMsg, &errorLine)) { qWarning() << "Invalid XML Content in" << filename; qWarning() << "Error line" << errorLine << ":" << errorMsg; return false; } file.close(); QDomElement root = doc.documentElement(); if (root.tagName().toLower() != "channels") { return false; } QString language = root.attribute("language", "en"); QString version = root.attribute("version", ""); if (version.isEmpty()) { qWarning() << "No Version Field in" << m_doctype << "Schema, assuming 1.0" << filename; version = "1.0"; } qDebug() << "Processing xml file:" << m_doctype << language << version; QDomNodeList grp = root.elementsByTagName("group"); QDomNode node, n, ch; QDomElement e; bool ok; int id, linkid; QString chantype, scopestr, typestr, name, group, idtxt, details, label, unit, datatypestr, defcolor, link; ChanType type; DataType datatype; Channel *chan; QColor color; //bool multi; ScopeType scope; int line; for (int i = 0; i < grp.size(); i++) { node = grp.at(i); group = node.toElement().attribute("name"); //qDebug() << "Group Name" << group; // Why do I have to skip the first node here? (shows up empty) n = node.firstChildElement(); while (!n.isNull()) { line = n.lineNumber(); e = n.toElement(); if (e.nodeName().toLower() != "channel") { qWarning() << "Ignoring unrecognized schema type " << e.nodeName() << "in" << filename << "line" << line; continue; } ch = n.firstChild(); n = n.nextSibling(); idtxt = e.attribute("id"); id = idtxt.toInt(&ok, 16); if (!ok) { qWarning() << "Dodgy ID number " << e.nodeName() << "in" << filename << "line" << line; continue; } chantype = e.attribute("class", "data").toLower(); if (!ChanTypes.contains(chantype)) { qWarning() << "Dodgy class " << chantype << "in" << filename << "line" << line; continue; } type = ChanTypes[chantype]; scopestr = e.attribute("scope", "session"); if (scopestr.at(0) == QChar('!')) { scopestr = scopestr.mid(1); //multi=true; } //multi=false; if (!Scopes.contains(scopestr)) { qWarning() << "Dodgy Scope " << scopestr << "in" << filename << "line" << line; continue; } scope = Scopes[scopestr]; name = e.attribute("name", ""); details = e.attribute("details", ""); label = e.attribute("label", ""); if (name.isEmpty() || details.isEmpty() || label.isEmpty()) { qWarning() << "Missing name,details or label attribute in" << filename << "line" << line; continue; } unit = e.attribute("unit"); defcolor = e.attribute("color", "black"); color = QColor(defcolor); if (!color.isValid()) { qWarning() << "Invalid Color " << defcolor << "in" << filename << "line" << line; color = Qt::black; } datatypestr = e.attribute("type", "").toLower(); link = e.attribute("link", ""); if (!link.isEmpty()) { linkid = link.toInt(&ok, 16); if (!ok) { qWarning() << "Dodgy Link ID number " << e.nodeName() << "in" << filename << " line" << line; } } else { linkid = 0; } if (DataTypes.contains(datatypestr)) { datatype = DataTypes[typestr]; } else { qWarning() << "Ignoring unrecognized schema datatype in" << filename << "line" << line; continue; } if (channels.contains(id)) { qWarning() << "Schema already contains id" << id << "in" << filename << "line" << line; continue; } if (names.contains(name)) { qWarning() << "Schema already contains name" << name << "in" << filename << "line" << line; continue; } chan = new Channel(id, type, MT_UNKNOWN, scope, name, name, details, label, unit, datatype, color, linkid); channels[id] = chan; names[name] = chan; //qDebug() << "Channel" << id << name << label; groups[group][name] = chan; if (linkid > 0) { if (channels.contains(linkid)) { Channel *it = channels[linkid]; it->m_links.push_back(chan); //int i=0; } else { qWarning() << "Linked channel" << name << ":" << linkid << "should be defined first in" << filename << "line" << line; } } // process children while (!ch.isNull()) { e = ch.toElement(); QString sub = ch.nodeName().toLower(); QString id2str, name2str; int id2; if (sub == "option") { id2str = e.attribute("id"); id2 = id2str.toInt(&ok, 10); name2str = e.attribute("value"); //qDebug() << sub << id2 << name2str; chan->addOption(id2, name2str); } else if (sub == "color") { } ch = ch.nextSibling(); } } } return true; } void ChannelList::add(QString group, Channel *chan) { if (chan == nullptr) { qCritical() << "ChannelList::add called with null chan object"; return; } if (channels.contains(chan->id())) { qCritical() << "Channels already contains id" << chan->id() << chan->code(); return; } if (names.contains(chan->code())) { qCritical() << "Channels already contains name" << chan->id() << chan->code(); return; } channels[chan->id()] = chan; names[chan->code()] = chan; groups[group][chan->code()] = chan; if (channels.contains(chan->linkid())) { Channel *it = channels[chan->linkid()]; it->m_links.push_back(chan); //int i=0; } else { if (chan->linkid()>0) { qWarning() << "Linked channel must be defined first for" << chan->code(); } } } bool ChannelList::Save(QString filename) { if (filename.isEmpty()) return false; qDebug() << "In ChannelList::Save() saving " << filename;; QDomDocument doc("channels"); QDomElement droot = doc.createElement(STR_AppName); doc.appendChild(droot); QDomElement root = doc.createElement("channels"); droot.appendChild(root); for (auto git=groups.begin(), end=groups.end(); git != end; ++git) { auto & chanlist = git.value(); QDomElement grp = doc.createElement("group"); grp.setAttribute("name", git.key()); root.appendChild(grp); for (auto it = chanlist.begin(), cend=chanlist.end(); it!=cend; ++it) { Channel * chan = it.value(); QDomElement cn = doc.createElement("channel"); cn.setAttribute("id", chan->id()); cn.setAttribute("code", it.key()); cn.setAttribute("label", chan->label()); cn.setAttribute("name", chan->fullname()); cn.setAttribute("description", chan->description()); cn.setAttribute("color", chan->defaultColor().name()); cn.setAttribute("upper", chan->upperThreshold()); cn.setAttribute("lower", chan->lowerThreshold()); cn.setAttribute("order", chan->order()); cn.setAttribute("type", chan->type()); cn.setAttribute("datatype", chan->datatype()); cn.setAttribute("overview", chan->showInOverview()); for (auto op=chan->m_options.begin(), opend=chan->m_options.end(); op!=opend; ++op) { QDomElement c2 = doc.createElement("option"); c2.setAttribute("key", op.key()); c2.setAttribute("value", op.value()); cn.appendChild(c2); } //cn.appendChild(doc.createTextNode(i.value().toDateTime().toString("yyyy-MM-dd HH:mm:ss"))); grp.appendChild(cn); } } QFile file(filename); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "Could not open" << filename << "for writing, error code" << file.error() << file.errorString(); return false; } QTextStream ts(&file); ts << doc.toString(); file.close(); return true; } } // namespace QString ChannelCalc::label() { QString lab = schema::channel[code].label(); QString m_label; switch(type) { case Calc_Min: m_label = QString("%1 %2").arg(STR_TR_Min).arg(lab); break; case Calc_Middle: m_label = Day::calcMiddleLabel(code); break; case Calc_Perc: m_label = Day::calcPercentileLabel(code); break; case Calc_Max: m_label = Day::calcMaxLabel(code); break; case Calc_Zero: m_label = QObject::tr("Zero"); break; case Calc_UpperThresh: m_label = QString("%1 %2").arg(lab).arg(QObject::tr("Upper Threshold")); break; case Calc_LowerThresh: m_label = QString("%1 %2").arg(lab).arg(QObject::tr("Lower Threshold")); break; } return m_label; } //typedef schema::Channel * ChannelID; OSCAR-code-v1.3.1/oscar/SleepLib/schema.h000066400000000000000000000221641417327530600177150ustar00rootroot00000000000000/* Schema Header (Parse Channel XML data) * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef SCHEMA_H #define SCHEMA_H #include #include #include #include #include "machine_common.h" enum ChannelCalcType { Calc_Zero, Calc_Min, Calc_Middle, Calc_Perc, Calc_Max, Calc_UpperThresh, Calc_LowerThresh }; struct ChannelCalc { public: ChannelCalc() { code = 0; enabled = false; color = Qt::black; type = Calc_Zero; } ChannelCalc(const ChannelCalc & /*copy*/) = default; ChannelCalc(ChannelID code, ChannelCalcType type, QColor color, bool enabled): code(code), type(type), color(color), enabled(enabled) {} QString label(); ChannelID code; ChannelCalcType type; QColor color; bool enabled; }; namespace schema { void resetChannels(); void setOrders(); enum Function { NONE = 0, AVG, WAVG, MIN, MAX, SUM, CNT, P90, CPH, SPH, HOURS, SET }; /// /// \brief The ChanType enum defines the type of data channel. Bit flags so multiple settings are possible. /// DATA: A single number such as Height, ZombieMeter. /// SETTING: Machine setting, such as EPR, temperature, Ramp enabled. /// FLAG: Event flags reported by CPAP machine. Each flag has its own channel. /// MINOR_FLAG: More event flags such as PressurePulse and TimedBreath. /// SPAN: A flag that has a timespan associated with it (CSR, LeakSpan, Ramp, ...). /// WAVEFORM: A waveform such as flow rate. /// UNKNOWN: Some PRS1 flags, but not sure what they are for. Considered to be minor flags. /// enum ChanType { DATA = 1, SETTING = 2, FLAG = 4, MINOR_FLAG = 8, SPAN = 16, WAVEFORM = 32, UNKNOWN = 64, ALL = 0xFFFF }; enum DataType { DEFAULT = 0, INTEGER, BOOL, DOUBLE, STRING, RICHTEXT, DATE, TIME, DATETIME, LOOKUP }; enum ScopeType { GLOBAL = 0, MACHINE, DAY, SESSION }; class Channel; extern Channel EmptyChannel; /*! \class Channel \brief Contains information about a SleepLib data Channel (aka signals) */ class Channel { public: Channel() { m_id = 0; m_upperThreshold = 0; m_lowerThreshold = 0; m_enabled = true; m_order = 255; m_machtype = MT_UNKNOWN; m_showInOverview = false; } Channel(ChannelID id, ChanType type, MachineType machtype, ScopeType scope, QString code, QString fullname, QString description, QString label, QString unit, DataType datatype = DEFAULT, QColor = Qt::black, int link = 0); void addColor(Function f, QColor color) { m_colors[f] = color; } void addOption(int i, QString option) { m_options[i] = option; } //! \brief Unique identifier of channel. Value set when channel is created. See schema.cpp and loader modules. inline ChannelID id() const { return m_id; } //! \brief Type of channel, such as WAVEFORM, FLAG, etc. See ChanType enum. inline ChanType type() const { return m_type; } //! \brief Data format such as integer vs RTF, called Field Type in channel initializers in schema.cpp inline DataType datatype() const { return m_datatype; } //! \brief Type of machine (CPAP, Oximeter, Journal, etc.) as defined in machine_common.h. Set in channel initializers in schema.cpp inline MachineType machtype() const { return m_machtype; } //! \brief Unique string identifier for this channel. Must not be translated. Later used as a unique key to identify graph derived from this channel. const QString &code() { return m_code; } //! \brief Full name of channel. Translatable. Used generally for channel names such as rows on Statistics page. const QString &fullname() { return m_fullname; } //! \brief Short description of what this channel does. Translatable. Used in tooltips for graphs to explain what the graph shows. const QString &description() { return m_description; } //! \brief Short-form label to indicate this channel on screen. Translatable. Used for vertical labels in graphs. //! Can be changed in Preferences dialog. const QString &label() { return m_label; } //! \brief Units, such as cmH2O, events per hour, etc. See STR_UNIT_* for possible values. const QString &units() { return m_unit; } //! \brief Seems to be some kind of sort order for event flags. Not sure this is used. inline short order() const { return m_order; } //! \brief Whether or not chart of this channel is to be shown on Overview page //! Initial settings of this flag for individual channels set in schema.cpp. //! May be changed by user in Preferences Dialog. bool showInOverview() { return m_showInOverview; } //! \brief Upper threshold for channel, apparently used in Statistics.cpp for calculation purposes. Not sure if it is used elsewhere. inline EventDataType upperThreshold() const { return m_upperThreshold; } //! \brief Lower threshold for channel, apparently used in Statistics.cpp for calculation purposes. Not sure if it is used elsewhere. inline EventDataType lowerThreshold() const { return m_lowerThreshold; } //! \brief Does not appear to be used? inline QColor upperThresholdColor() const { return m_upperThresholdColor; } //! \brief Does not appear to be used? inline QColor lowerThresholdColor() const { return m_lowerThresholdColor; } //! \brief Links channels. Links to better versions of this data type. inline ChannelID linkid() const { return m_link; } void setFullname(QString fullname) { m_fullname = fullname; } void setLabel(QString label) { m_label = label; } void setType(ChanType type) { m_type = type; } void setUnit(QString unit) { m_unit = unit; } void setDescription(QString desc) { m_description = desc; } void setUpperThreshold(EventDataType value) { m_upperThreshold = value; } void setUpperThresholdColor(QColor color) { m_upperThresholdColor = color; } void setLowerThreshold(EventDataType value) { m_lowerThreshold = value; } void setLowerThresholdColor(QColor color) { m_lowerThresholdColor = color; } void setOrder(short order) { m_order = order; } void setShowInOverview(bool b) { m_showInOverview = b; } //! \brief Retrieves options that may have been set for the channel. Used for CPAP Mode, EPR level. QString option(int i) { if (m_options.contains(i)) { return m_options[i]; } return QString(); } //! \brief Default color for plotting this channel inline QColor defaultColor() const { return m_defaultcolor; } inline void setDefaultColor(QColor color) { m_defaultcolor = color; } QHash m_options; QHash m_colors; QList m_links; // better versions of this data type bool isNull(); inline bool enabled() const { return m_enabled; } void setEnabled(bool value) { m_enabled = value; } //! \brief Types of calculations that can be plotted on this channel and color to be used for plotting QHash calc; protected: int m_id; ChanType m_type; MachineType m_machtype; ScopeType m_scope; QString m_code; // Untranslatable QString m_fullname; // Translatable Name QString m_description; QString m_label; QString m_unit; QString default_fullname; QString default_label; QString default_description; DataType m_datatype; QColor m_defaultcolor; int m_link; EventDataType m_upperThreshold; EventDataType m_lowerThreshold; QColor m_upperThresholdColor; QColor m_lowerThresholdColor; bool m_enabled; short m_order; bool m_showInOverview; }; /*! \class ChannelList \brief A list containing a group of Channel objects, and XML storage and retrieval capability */ class ChannelList { public: ChannelList(); virtual ~ChannelList(); //! \brief Loads Channel list from XML file specified by filename bool Load(QString filename); //! \brief Stores Channel list to XML file specified by filename bool Save(QString filename = QString()); void add(QString group, Channel *chan); //! \brief Looks up Channel in this List with the index idx, returns EmptyChannel if not found Channel & operator[](ChannelID idx) { if (channels.contains(idx)) { return *channels[idx]; } else { return EmptyChannel; } } //! \brief Looks up Channel from this list by name, returns Empty Channel if not found. Channel &operator[](QString name) { if (names.contains(name)) { return *names[name]; } else { return EmptyChannel; } } //! \brief Channel List indexed by integer ID QHash channels; //! \brief Channel List index by name QHash names; //! \brief Channel List indexed by group QHash > groups; QString m_doctype; }; extern ChannelList channel; void init(); void done(); } // namespace schema #endif // SCHEMA_H OSCAR-code-v1.3.1/oscar/SleepLib/serialoximeter.cpp000066400000000000000000000110121417327530600220320ustar00rootroot00000000000000/* SleepLib Machine Loader Class Implementation * * Copyright (c) 2011-2018 Mark Watkins * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "serialoximeter.h" // Possibly need to replan this to include oximetry QList GetOxiLoaders() { QList oxiloaders; QList loaders = GetLoaders(MT_OXIMETER); Q_FOREACH(MachineLoader * loader, loaders) { SerialOximeter * oxi = qobject_cast(loader); oxiloaders.push_back(oxi); } return oxiloaders; } bool SerialOximeter::scanDevice(QString keyword, quint16 vendor_id, quint16 product_id) { static bool dumponce = true; QStringList ports; qDebug() << "seroxi - Scanning for USB Serial devices"; QList list=SerialPortInfo::availablePorts(); // How does the mac detect this as a SPO2 device? for (int i=0;iportName(); QString desc = info->description(); if ((!keyword.isEmpty() && (desc.contains(keyword, Qt::CaseInsensitive) || name.contains(keyword, Qt::CaseInsensitive))) || ((info->hasVendorIdentifier() && (info->vendorIdentifier() == vendor_id)) && (info->hasProductIdentifier() && (info->productIdentifier() == product_id)))) { ports.push_back(name); QString dbg=QString("Found Serial Port: Name: %1 Desc: %2 Manufacturer: %3 Location: %4").arg(name).arg(desc).arg(info->manufacturer()).arg(info->systemLocation()); if (info->hasProductIdentifier()) //60000 dbg += QString(" PID: %1").arg(info->productIdentifier()); if (info->hasVendorIdentifier()) // 4292 dbg += QString(" VID: %1").arg(info->vendorIdentifier()); qDebug() << "seroxi - " << dbg.toLocal8Bit().data(); break; } else if (dumponce) { QString dbg=QString("Other Serial Port: Name: %1 Desc: %2 Manufacturer: %3 Location: %4").arg(name).arg(desc).arg(info->manufacturer()).arg(info->systemLocation()); if (info->hasProductIdentifier()) //60000 dbg += QString(" PID: %1").arg(info->productIdentifier()); if (info->hasVendorIdentifier()) // 4292 dbg += QString(" VID: %1").arg(info->vendorIdentifier()); qDebug() << "seroxi - " << dbg.toLocal8Bit().data(); } } dumponce = false; if (ports.isEmpty()) { return false; } if (ports.size()>1) { qDebug() << "seroxi - More than one serial device matching these parameters was found, choosing the first by default"; } port=ports.at(0); return true; } void SerialOximeter::closeDevice() { killTimers(); disconnect(&serial,SIGNAL(readyRead()), this, SLOT(dataAvailable())); serial.close(); m_streaming = false; qDebug() << "seroxi - Port" << port << "closed"; } bool SerialOximeter::openDevice() { if (port.isEmpty()) { if (!scanDevice("",m_vendorID, m_productID)) return false; } serial.setPortName(port); if (!serial.open(QSerialPort::ReadWrite)) return false; // forward this stuff // Set up serial port attributes serial.setBaudRate(QSerialPort::Baud19200); serial.setParity(QSerialPort::OddParity); serial.setStopBits(QSerialPort::OneStop); serial.setDataBits(QSerialPort::Data8); serial.setFlowControl(QSerialPort::NoFlowControl); m_streaming = true; m_abort = false; m_importing = false; // connect relevant signals connect(&serial,SIGNAL(readyRead()), this, SLOT(dataAvailable())); return true; } void SerialOximeter::dataAvailable() { QByteArray bytes; int available = serial.bytesAvailable(); bytes.resize(available); int bytesread = serial.read(bytes.data(), available); if (bytesread == 0) return; if (m_abort) { closeDevice(); return; } processBytes(bytes); } void SerialOximeter::stopRecording() { closeDevice(); m_status = NEUTRAL; emit importComplete(this); } void SerialOximeter::trashRecords() { QMap *>::iterator it; for (it = oxisessions.begin(); it != oxisessions.end(); ++it) { delete it.value(); } oxisessions.clear(); oxirec = nullptr; } OSCAR-code-v1.3.1/oscar/SleepLib/serialoximeter.h000066400000000000000000000076611417327530600215160ustar00rootroot00000000000000/* SleepLib MachineLoader Base Class Header * * Copyright (C) 2011-2018 Mark Watkins * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef SERIALOXIMETER_H #define SERIALOXIMETER_H #include #include "SleepLib/deviceconnection.h" #include "SleepLib/machine_loader.h" const int START_TIMEOUT = 30000; struct OxiRecord { quint8 pulse; quint8 spo2; quint16 perf; OxiRecord():pulse(0), spo2(0),perf(0) {} OxiRecord(quint8 p, quint8 s): pulse(p), spo2(s) {} OxiRecord(quint8 p, quint8 s, quint16 pi): pulse(p), spo2(s), perf(pi) {} // with perfusion index OxiRecord(const OxiRecord & copy) { pulse = copy.pulse; spo2 = copy.spo2; perf = copy.perf; } }; class SerialOximeter : public MachineLoader { Q_OBJECT public: SerialOximeter() : MachineLoader() { m_importing = m_streaming = false; m_productID = m_vendorID = 0; have_perfindex = false; } virtual ~SerialOximeter() {} virtual bool Detect(const QString &path)=0; virtual int Open(const QString & path)=0; static void Register() {} virtual int Version()=0; virtual const QString &loaderName()=0; virtual QDateTime getDateTime(int session) { Q_UNUSED(session); return QDateTime(); } virtual int getDuration(int session) { Q_UNUSED(session); return 0; } virtual int getSessionCount() { return 0; } virtual QString getUser() { return QString(); } virtual QString getModel() { return QString(); } virtual QString getVendor() { return QString(); } virtual QString getDeviceString() { return QString(); } virtual void getSessionData(int session) { Q_UNUSED(session); } virtual void syncClock() {} virtual QString getDeviceID() { return QString(); } virtual void setDeviceID(const QString &) {} virtual void eraseSession(int /*user*/, int /*session*/) {} virtual bool commandDriven() { return false; } virtual MachineInfo newInfo() { return MachineInfo(MT_OXIMETER, 0, "", QString(), QString(), QString(), QString(), "Generic", QDateTime::currentDateTime(), 0); } // Serial Stuff virtual bool scanDevice(QString keyword="",quint16 vendor_id=0, quint16 product_id=0); virtual bool openDevice(); virtual void closeDevice(); inline bool isStreaming() { return m_streaming; } inline bool isImporting() { return m_importing; } bool havePerfIndex() { return have_perfindex; } virtual void process() {} //virtual Machine *CreateMachine()=0; // available sessions QMap *> oxisessions; // current session QVector * oxirec; QDateTime startTime() { return m_startTime; } void setStartTime(QDateTime datetime) { m_startTime = datetime; } virtual bool isStartTimeValid() { return true; } virtual void setDuration(int) { } virtual qint64 importResolution() { return 1000; } virtual qint64 liveResolution() { return 20; } void trashRecords(); virtual void resetDevice() {} signals: void noDeviceFound(); void deviceDetected(); void updatePlethy(QByteArray plethy); void importComplete(SerialOximeter *); protected slots: virtual void dataAvailable(); virtual void resetImportTimeout() {} virtual void startImportTimeout() {} virtual void stopRecording(); virtual void shutdownPorts() {} // virtual void abortTask(); protected: virtual void processBytes(QByteArray buffer) { Q_UNUSED(buffer) } virtual void killTimers() {} virtual void requestData() {} QString port; SerialPort serial; QTimer startTimer; QTimer resetTimer; QDateTime m_startTime; quint16 m_productID; quint16 m_vendorID; bool m_streaming; bool m_importing; bool have_perfindex; }; #endif // SERIALOXIMETER_H OSCAR-code-v1.3.1/oscar/SleepLib/session.cpp000066400000000000000000001713641417327530600205020ustar00rootroot00000000000000/* SleepLib Session Implementation * This stuff contains the base calculation smarts * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "session.h" #include "version.h" #include #include #include #include #include #include #include #include "SleepLib/calcs.h" #include "SleepLib/profiles.h" using namespace std; // This is the uber important database version for OSCAR's internal storage // Increment this after stuffing with Session's save & load code. const quint16 summary_version = 18; const quint16 events_version = 10; Session::Session(Machine *m, SessionID session) { s_lonesession = false; if (!session) { session = m->CreateSessionID(); } s_machine = m; s_machtype = m->type(); s_session = session; s_changed = false; s_events_loaded = false; s_summary_loaded = false; _first_session = true; s_enabled = true; s_first = s_last = 0; s_evchecksum_checked = false; s_noSettings = s_summaryOnly = false; destroyed = false; } Session::~Session() { TrashEvents(); destroyed = true; } void Session::TrashEvents() // Trash this sessions Events and release memory. { QVector::iterator j; QVector::iterator j_end; QHash >::iterator i; QHash >::iterator i_end=eventlist.end(); if (s_changed) { // Save first.. } for (i = eventlist.begin(); i != i_end; ++i) { j_end=i.value().end(); for (j = i.value().begin(); j != j_end; ++j) { EventList * ev = *j; ev->clear(); ev->m_data.squeeze(); ev->m_data2.squeeze(); ev->m_time.squeeze(); delete ev; } } s_events_loaded = false; eventlist.clear(); eventlist.squeeze(); } void Session::setEnabled(bool b) { s_enabled = b; // not so simple.. we have to invalidate the hours cache in the day record.. Day * day = p_profile->findSessionDay(this); if (day) { day->invalidate(); } } QString Session::eventFile() const { return s_machine->getEventsPath()+QString().sprintf("%08lx.001", s_session); } //const int max_pack_size=128; bool Session::OpenEvents() { if (s_events_loaded) { return true; } s_events_loaded = eventlist.size() > 0; if (s_events_loaded) { return true; } QString filename = eventFile(); #ifdef DEBUG_EVENTS qDebug() << "Loading" << s_machine->loaderName().toLocal8Bit().data() << "Events:" << filename.toLocal8Bit().data(); #endif bool b = LoadEvents(filename); if ( ! b) { qWarning() << "Error Loading Events" << filename; return false; } return s_events_loaded = true; } bool Session::Destroy() { QDir dir; QString base; base.sprintf("%08lx", s_session); QString summaryfile = s_machine->getSummariesPath() + base + ".000"; QString eventfile = s_machine->getEventsPath() + base + ".001"; if ( ! dir.remove(summaryfile)) { qWarning() << "Could not delete" << summaryfile; } if ( ! dir.remove(eventfile)) { qWarning() << "Could not delete" << eventfile; } return s_machine->unlinkSession(this); } bool Session::Store(QString path) // Storing Session Data in our format // {DataDir}/{MachineID}/{SessionID}.{ext} { QDir dir(path); if ( ! dir.exists(path)) { dir.mkpath(path); } //qDebug() << "Storing Session: " << base; bool a; a = StoreSummary(); // if actually has events //qDebug() << " Summary done"; if (eventlist.size() > 0) { StoreEvents(); } else { // who cares.. //qDebug() << "Trying to save empty events file"; } //qDebug() << " Events done"; s_changed = false; s_events_loaded = true; //} else { // qDebug() << "Session::Store() No event data saved" << s_session; //} return a; } //QDataStream & operator<<(QDataStream & out, const Session & session) //{ // session.StoreSummaryData(out); // return out; //} // //void Session::StoreSummaryData(QDataStream & out) const //{ // out << summary_version; // out << (quint32)s_session; // out << s_first; // Session Start Time // out << s_last; // Duration of sesion in seconds. // // out << settings; // out << m_cnt; // out << m_sum; // out << m_avg; // out << m_wavg; // // out << m_min; // out << m_max; // // out << m_physmin; // out << m_physmax; // // out << m_cph; // out << m_sph; // // out << m_firstchan; // out << m_lastchan; // // out << m_valuesummary; // out << m_timesummary; // // out << m_gain; // // out << m_availableChannels; // // out << m_timeAboveTheshold; // out << m_upperThreshold; // out << m_timeBelowTheshold; // out << m_lowerThreshold; // // out << s_summaryOnly; //} // //QDataStream & operator>>(QDataStream & in, Session & session) //{ // session.LoadSummaryData(in); // return in; //} // // //void Session::LoadSummaryData(QDataStream & in) //{ // quint16 version; // in >> version; // // quint32 t32; // in >> t32; // Sessionid; // s_session = t32; // // in >> s_first; // Start time // in >> s_last; // Duration // // in >> settings; // in >> m_cnt; // in >> m_sum; // in >> m_avg; // in >> m_wavg; // // in >> m_min; // in >> m_max; // // in >> m_physmin; // in >> m_physmax; // // in >> m_cph; // in >> m_sph; // in >> m_firstchan; // in >> m_lastchan; // // in >> m_valuesummary; // in >> m_timesummary; // // in >> m_gain; // // in >> m_availableChannels; // in >> m_timeAboveTheshold; // in >> m_upperThreshold; // in >> m_timeBelowTheshold; // in >> m_lowerThreshold; // // in >> s_summaryOnly; // // s_enabled = 1; //} QDataStream & operator>>(QDataStream & in, SessionSlice & slice) { in >> slice.start; quint32 length; in >> length; slice.end = slice.start + length; quint16 i; in >> i; slice.status = (SliceStatus)i; return in; } QDataStream & operator<<(QDataStream & out, const SessionSlice & slice) { out << slice.start; quint32 length = slice.end - slice.start; out << length; out << (quint16)slice.status; return out; } bool Session::StoreSummary() { if (s_first == 0) { qWarning() << "Session::StoreSummary discarding session" << s_session << "["+QDateTime::fromTime_t(s_session).toString("MMM dd, yyyy hh:mm:ss")+"]" << "from machine" << machine()->serial() << "with first=0"; return false; } QString filename = s_machine->getSummariesPath() + QString().sprintf("%08lx.000", s_session); QFile file(filename); if (!file.open(QIODevice::WriteOnly)) { QDir dir; dir.mkpath(s_machine->getSummariesPath()); if (!file.open(QIODevice::WriteOnly)) { // qWarning() << "Summary open for writing failed" << "error code" << file.error() << file.errorString(); qWarning() << "Could not open summary" << filename << "for writing, error code" << file.error() << file.errorString(); return false; } } QDataStream out(&file); out.setVersion(QDataStream::Qt_4_6); out.setByteOrder(QDataStream::LittleEndian); out << (quint32)magic; out << (quint16)summary_version; out << (quint16)filetype_summary; out << (quint32)s_machine->id(); out << (quint32)s_session; out << s_first; // Session Start Time out << s_last; // Duration of sesion in seconds. //out << (quint16)settings.size(); out << settings; out << m_cnt; out << m_sum; out << m_avg; out << m_wavg; out << m_min; out << m_max; out << m_physmin; out << m_physmax; out << m_cph; out << m_sph; out << m_firstchan; out << m_lastchan; // <- 8 out << m_valuesummary; out << m_timesummary; // 8 -> // <- 9 out << m_gain; // 9 -> // <- 15 out << m_availableChannels; out << m_timeAboveTheshold; out << m_upperThreshold; out << m_timeBelowTheshold; out << m_lowerThreshold; out << s_summaryOnly; // 13 -> out << s_noSettings; // 18 out << m_slices; file.close(); return true; } bool Session::LoadSummary() { // static int sumcnt = 0; if (s_summary_loaded) return true; QString filename = s_machine->getSummariesPath() + QString().sprintf("%08lx.000", s_session); if (filename.isEmpty()) { qDebug() << "Empty summary filename"; return false; } QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Could not open summary file" << filename << "for reading, error code" << file.error() << file.errorString(); return false; } // qDebug() << "Loading" << s_machine->loaderName() << "Summary" << filename << sumcnt++; QDataStream in(&file); in.setVersion(QDataStream::Qt_4_6); in.setByteOrder(QDataStream::LittleEndian); quint32 t32; quint16 t16; //QHash mctype; //QVector mcorder; in >> t32; if (t32 != magic) { qDebug() << "Wrong magic number in " << filename; return false; } quint16 version; in >> version; // DB Version if (version < 6) { //throw OldDBVersion(); qWarning() << "Old dbversion " << version << "summary file.. Sorry, you need to purge and reimport"; return false; } in >> t16; // File Type if (t16 != filetype_summary) { qDebug() << "Wrong file type"; //wrong file type return false; } qint32 ts32; in >> ts32; // MachineID (dont need this result) bool upgrade = false; if (ts32 != s_machine->id()) { upgrade = true; qWarning() << "Machine ID does not match in" << filename << " I will try to load anyway in case you know what your doing."; } in >> t32; // Sessionid; s_session = t32; in >> s_first; // Start time in >> s_last; // Duration // (16bit==Limited to 18 hours) QHash cruft; if (version < 7) { // This code is deprecated.. just here incase anyone tries anything crazy... QHash v1; in >> v1; settings.clear(); ChannelID code; for (QHash::iterator i = v1.begin(); i != v1.end(); i++) { code = schema::channel[i.key()].id(); settings[code] = i.value(); } QHash zcnt; in >> zcnt; for (QHash::iterator i = zcnt.begin(); i != zcnt.end(); i++) { code = schema::channel[i.key()].id(); m_cnt[code] = i.value(); } QHash zsum; in >> zsum; for (QHash::iterator i = zsum.begin(); i != zsum.end(); i++) { code = schema::channel[i.key()].id(); m_sum[code] = i.value(); } QHash ztmp; in >> ztmp; // avg for (QHash::iterator i = ztmp.begin(); i != ztmp.end(); i++) { code = schema::channel[i.key()].id(); m_avg[code] = i.value(); } ztmp.clear(); in >> ztmp; // wavg for (QHash::iterator i = ztmp.begin(); i != ztmp.end(); i++) { code = schema::channel[i.key()].id(); m_wavg[code] = i.value(); } ztmp.clear(); in >> ztmp; // 90p ztmp.clear(); in >> ztmp; // min for (QHash::iterator i = ztmp.begin(); i != ztmp.end(); i++) { code = schema::channel[i.key()].id(); m_min[code] = i.value(); } ztmp.clear(); in >> ztmp; // max for (QHash::iterator i = ztmp.begin(); i != ztmp.end(); i++) { code = schema::channel[i.key()].id(); m_max[code] = i.value(); } ztmp.clear(); in >> ztmp; // cph for (QHash::iterator i = ztmp.begin(); i != ztmp.end(); i++) { code = schema::channel[i.key()].id(); m_cph[code] = i.value(); } ztmp.clear(); in >> ztmp; // sph for (QHash::iterator i = ztmp.begin(); i != ztmp.end(); i++) { code = schema::channel[i.key()].id(); m_sph[code] = i.value(); } QHash ztim; in >> ztim; //firstchan for (QHash::iterator i = ztim.begin(); i != ztim.end(); i++) { code = schema::channel[i.key()].id(); m_firstchan[code] = i.value(); } ztim.clear(); in >> ztim; // lastchan for (QHash::iterator i = ztim.begin(); i != ztim.end(); i++) { code = schema::channel[i.key()].id(); m_lastchan[code] = i.value(); } //SetChanged(true); } else { // version > 7 in >> settings; if (version < 13) { QHash cnt2; in >> cnt2; QHash::iterator it; for (it = cnt2.begin(); it != cnt2.end(); ++it) { m_cnt[it.key()] = it.value(); } } else { in >> m_cnt; } in >> m_sum; in >> m_avg; in >> m_wavg; if (version < 11) { cruft.clear(); in >> cruft; // 90% if (version >= 10) { cruft.clear(); in >> cruft;// med cruft.clear(); in >> cruft; //p95 } } in >> m_min; in >> m_max; // Added 24/10/2013 by MW to support physical graph min/max values if (version >= 12) { in >> m_physmin; in >> m_physmax; } in >> m_cph; in >> m_sph; in >> m_firstchan; in >> m_lastchan; if (version >= 8) { in >> m_valuesummary; in >> m_timesummary; if (version >= 9) { in >> m_gain; } } // screwed up with version 14 if (version >= 15) { in >> m_availableChannels; in >> m_timeAboveTheshold; in >> m_upperThreshold; in >> m_timeBelowTheshold; in >> m_lowerThreshold; } // else this is ugly.. forced machine database upgrade will solve it though. if (version == 13) { QHash::iterator it = settings.find(CPAP_SummaryOnly); if (it != settings.end()) { s_summaryOnly = (*it).toBool(); } else s_summaryOnly = false; } else if (version > 13) { in >> s_summaryOnly; } if (version >= 18) { in >> s_noSettings; // qDebug() << "Session::LoadSummary" << s_session << "[" // << QDateTime::fromTime_t(s_session).toString("MM/dd/yyyy hh:mm:ss") // << "] s_noSettings" << s_noSettings << "size" << settings.size(); } else { s_noSettings = (settings.size() == 0); } if (version == 16) { QList slices; in >> slices; m_slices.clear(); for (int i=0;i= 17) { in >> m_slices; } } // not really a good idea to do this... should flag and do a reindex if (upgrade || (version < summary_version)) { qDebug() << "Upgrading Summary file to version" << summary_version; if (!s_summaryOnly) { OpenEvents(); UpdateSummaries(); TrashEvents(); } else { // summary only upgrades go here. } StoreSummary(); } s_summary_loaded = true; return true; } const quint16 compress_method = 1; bool Session::StoreEvents() { QString path = s_machine->getEventsPath(); QDir dir; dir.mkpath(path); QString filename = path+QString().sprintf("%08lx.001", s_session); QFile file(filename); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "Could not open events file" << filename << "for writing, error code" << file.error() << file.errorString(); return false; } QByteArray headerbytes; QDataStream header(&headerbytes, QIODevice::WriteOnly); header.setVersion(QDataStream::Qt_4_6); header.setByteOrder(QDataStream::LittleEndian); header << (quint32)magic; // New Magic Number header << (quint16)events_version; // File Version header << (quint16)filetype_data; // File type 1 == Event header << (quint32)s_machine->id();// Machine Type header << (quint32)s_session; // This session's ID header << s_first; header << s_last; quint16 compress = 0; if (p_profile->session->compressSessionData()) { compress = compress_method; } header << (quint16)compress; header << (quint16)s_machine->type();// Machine Type QByteArray databytes; QDataStream out(&databytes, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_6); out.setByteOrder(QDataStream::LittleEndian); out << (qint16)eventlist.size(); // Number of event categories QHash >::iterator i; QHash >::iterator i_end=eventlist.end(); qint16 ev_size; for (i = eventlist.begin(); i != i_end; i++) { ev_size=i.value().size(); out << i.key(); // ChannelID out << (qint16)ev_size; for (int j = 0; j < ev_size; j++) { EventList &e = *i.value()[j]; out << e.first(); out << e.last(); out << (qint32)e.count(); out << (qint8)e.type(); out << e.rate(); out << e.gain(); out << e.offset(); out << e.Min(); out << e.Max(); out << e.dimension(); out << e.hasSecondField(); if (e.hasSecondField()) { out << e.min2(); out << e.max2(); } } } for (i = eventlist.begin(); i != i_end; i++) { ev_size=i.value().size(); for (int j = 0; j < ev_size; j++) { EventList &e = *i.value()[j]; // ****** This is assuming little endian ****** // Store the raw event list data in EventStoreType (16bit short) EventStoreType *ptr = e.m_data.data(); out.writeRawData((char *)ptr, e.count() << 1); //*** Don't delete these comments *** // for (quint32 c=0;c 0) { data = qCompress(databytes); } else { data = databytes; } file.write(headerbytes); file.write(data); file.close(); return true; } bool Session::LoadEvents(QString filename) { quint32 magicnum, machid, sessid; quint16 version, type, crc16, machtype, compmethod; quint8 t8; qint32 datasize; if (filename.isEmpty()) { qDebug() << "Session::LoadEvents() Filename is empty"; return false; } QFile file(filename); if ( ! file.open(QIODevice::ReadOnly)) { // qDebug() << "No Event/Waveform data available for" << s_session; qWarning() << "No Event/Waveform data available for" << s_session << "filename" << filename << "error code" << file.error() << file.errorString(); return false; } QByteArray headerbytes = file.read(42); QDataStream header(headerbytes); header.setVersion(QDataStream::Qt_4_6); header.setByteOrder(QDataStream::LittleEndian); header >> magicnum; // Magic Number (quint32) header >> version; // Version (quint16) header >> type; // File type (quint16) header >> machid; // Machine ID (quint32) header >> sessid; //(quint32) header >> s_first; //(qint64) header >> s_last; //(qint64) #ifdef DEBUG_EVENTS qDebug() << "Session ID" << sessid << "Start Time" << QDateTime::fromMSecsSinceEpoch(s_first); #endif if (type != filetype_data) { qDebug() << "Wrong File Type in " << filename; return false; } if (magicnum != magic) { qWarning() << "Wrong Magic number in " << filename; return false; } if (version < 6) { // prior to version 6 is too old to deal with qDebug() << "Old File Version, can't open file"; return false; } if (version < 10) { file.seek(32); } else { header >> compmethod; // Compression Method (quint16) header >> machtype; // Machine Type (quint16) header >> datasize; // Size of Uncompressed Data (quint32) header >> crc16; // CRC16 of Uncompressed Data (quint16) } QByteArray databytes, temp = file.readAll(); file.close(); if (version >= 10) { if (compmethod > 0) { databytes = qUncompress(temp); if (!s_evchecksum_checked) { if (databytes.size() != datasize) { qDebug() << "File" << filename << "has returned wrong datasize"; return false; } quint16 crc = qChecksum(databytes.data(), databytes.size()); if (crc != crc16) { qDebug() << "CRC Doesn't match in" << filename; return false; } s_evchecksum_checked = true; } } else { databytes = temp; } } else { databytes = temp; } QDataStream in(databytes); in.setVersion(QDataStream::Qt_4_6); in.setByteOrder(QDataStream::LittleEndian); qint16 mcsize; in >> mcsize; // number of Machine Code lists #ifdef DEBUG_EVENTS qDebug() << "Number of Channels" << mcsize; #endif ChannelID code; qint64 ts1, ts2; qint32 evcount; EventListType elt; EventDataType rate, gain, offset, mn, mx; qint16 size2; QVector mcorder; QVector sizevec; QString dim; for (int i = 0; i < mcsize; i++) { if (version < 8) { QString txt; in >> txt; code = schema::channel[txt].id(); } else { in >> code; } mcorder.push_back(code); in >> size2; sizevec.push_back(size2); #ifdef DEBUG_EVENTS qDebug() << "For Channel (hex)" << QString::number(code, 16) << "there are" << size2 << "EventLists"; #endif for (int j = 0; j < size2; j++) { in >> ts1; in >> ts2; #ifdef DEBUG_EVENTS qDebug() << "Start:" << QDateTime::fromMSecsSinceEpoch(ts1).toString() << "Finish:" << QDateTime::fromMSecsSinceEpoch(ts2).toString(); #endif in >> evcount; in >> t8; elt = (EventListType)t8; in >> rate; in >> gain; in >> offset; in >> mn; in >> mx; in >> dim; bool second_field = false; if (version >= 7) { // version 7 added this field in >> second_field; } EventList *elist = AddEventList(code, elt, gain, offset, mn, mx, rate, second_field); elist->setDimension(dim); //eventlist[code].push_back(elist); elist->m_count = evcount; elist->m_first = ts1; elist->m_last = ts2; if (second_field) { EventDataType min, max; in >> min; in >> max; elist->setMin2(min); elist->setMax2(max); } } } //EventStoreType t; // qint16 //quint32 x; for (int i = 0; i < mcsize; i++) { code = mcorder[i]; size2 = sizevec[i]; for (int j = 0; j < size2; j++) { EventList &evec = *eventlist[code][j]; evec.m_data.resize(evec.m_count); EventStoreType *ptr = evec.m_data.data(); // ****** This is assuming little endian ****** in.readRawData((char *)ptr, evec.m_count << 1); //*** Don't delete these comments *** //*** They explain what the above ReadRawData is doing! // for (quint32 c=0;c> t; // *ptr++=t; // } if (evec.hasSecondField()) { evec.m_data2.resize(evec.m_count); ptr = evec.m_data2.data(); in.readRawData((char *)ptr, evec.m_count << 1); //*** Don't delete these comments *** //*** They explain what the above ReadRawData is doing! // for (quint32 c=0;c> t; // *ptr++=t; // } } if (evec.type() != EVL_Waveform) { evec.m_time.resize(evec.m_count); quint32 *tptr = evec.m_time.data(); in.readRawData((char *)tptr, evec.m_count << 2); //*** Don't delete these comments *** // for (quint32 c=0;c> x; // *tptr++=x; // } } } } if (version < events_version) { qDebug() << "Upgrading Events file" << filename << "to version" << events_version; UpdateSummaries(); StoreEvents(); } return true; } void Session::destroyEvent(ChannelID code) { QHash >::iterator it = eventlist.find(code); if (it != eventlist.end()) { for (int i = 0; i < it.value().size(); i++) { delete it.value()[i]; } eventlist.erase(it); } m_gain.erase(m_gain.find(code)); m_firstchan.erase(m_firstchan.find(code)); m_lastchan.erase(m_lastchan.find(code)); m_sph.erase(m_sph.find(code)); m_cph.erase(m_cph.find(code)); m_min.erase(m_min.find(code)); m_max.erase(m_max.find(code)); m_avg.erase(m_avg.find(code)); m_wavg.erase(m_wavg.find(code)); m_sum.erase(m_sum.find(code)); m_cnt.erase(m_cnt.find(code)); m_valuesummary.erase(m_valuesummary.find(code)); m_timesummary.erase(m_timesummary.find(code)); // does not trash settings.. } // TODO: The below assumes values are held for their duration. This does not properly handle // CPAP_PressureSet/EPAPSet/IPAPSet or other interpolated channels. The proper "value" held // for any given duration is the average of the starding and ending values, for the duration // between them. void Session::updateCountSummary(ChannelID code) { QHash >::iterator ev = eventlist.find(code); if (ev == eventlist.end()) { qDebug() << "No events for channel (hex)" << QString::number(code, 16); return; } QHash >::iterator vs = m_valuesummary.find(code); if (vs != m_valuesummary.end()) { // already calculated? return; } QHash valsum; QHash timesum; QHash::iterator it; QHash::iterator valsum_end; EventDataType raw, lastraw = 0; qint64 start, time, lasttime = 0; qint32 len, cnt; quint32 *tptr; EventStoreType *dptr, * eptr; int ev_size=ev.value().size(); for (int i = 0; i < ev_size; i++) { EventList &e = *(ev.value()[i]); start = e.first(); cnt = e.count(); dptr = e.rawData(); eptr = dptr + cnt; EventDataType rate = 0; m_gain[code] = e.gain(); if (e.type() == EVL_Event) { lastraw = *dptr++; tptr = e.rawTime(); lasttime = start + *tptr++; // Event version for (; dptr < eptr; dptr++) { time = start + *tptr++; raw = *dptr; valsum[raw]++; // elapsed time in seconds since last event occurred len = (time - lasttime) / 1000L; timesum[lastraw] += len; lastraw = raw; lasttime = time; } } else { // Waveform version, first just count for (; dptr < eptr; dptr++) { raw = *dptr; valsum[raw]++; } // Then process the list of values, time is simply (rate * count) rate = e.rate(); EventDataType t; QHash::iterator it = valsum.begin(); QHash::iterator valsum_end = valsum.end(); for (; it != valsum_end; ++it) { t = EventDataType(it.value()) * rate; timesum[it.key()] += t; } } } if ( valsum.size() == 0) { // no value summary for this channel using namespace schema; Channel *ch_p = channel.channels[code]; if ( ! ch_p->isNull() ) { // the channel was found in the channel list if ( ((ch_p->type() & (FLAG|SPAN|MINOR_FLAG)) == 0) ) { // the channel is not a flag or span type qDebug() << "No valuesummary for channel " << ch_p->label(); // so tell about missing summary } } else { // This channel wasn't added to the channel list, so we can't check its type qDebug() << "No valuesummary for channel (hex)" << QString::number(code, 16); } return; } m_valuesummary[code] = valsum; m_timesummary[code] = timesum; } void Session::UpdateSummaries() { ChannelID id; // Generate that AHI per hour graph in daily view. calcAHIGraph(this); // Calculates RespRate and related waveforms (Tv, MV, Te, Ti) if missing calcRespRate(this); // Generate unintentional leaks if not present calcLeaks(this); // Flag the Large Leaks if unintentional leaks is available, and no LargeLeaks weren't flagged by the machine already. flagLargeLeaks(this); calcSPO2Drop(this); calcPulseChange(this); QHash >::iterator c = eventlist.begin(); QHash >::iterator ev_end = eventlist.end(); m_availableChannels.clear(); for (; c != ev_end; c++) { id = c.key(); m_availableChannels.push_back(id); schema::ChanType ctype = schema::channel[id].type(); if (ctype != schema::SETTING) { //sum(id); // avg calculates this and cnt. if (c.value().size() > 0) { EventList *el = c.value()[0]; EventDataType gain = el->gain(); m_gain[id] = gain; } if (!((id == CPAP_FlowRate) || (id == CPAP_MaskPressureHi) || (id == CPAP_RespEvent) || (id == CPAP_MaskPressure))) { updateCountSummary(id); } Min(id); Max(id); count(id); last(id); first(id); if (((id == CPAP_FlowRate) || (id == CPAP_MaskPressureHi) || (id == CPAP_RespEvent) || (id == CPAP_MaskPressure))) { continue; } cph(id); sph(id); avg(id); wavg(id); } } timeAboveThreshold(CPAP_Leak, p_profile->cpap->leakRedline()); s_machine->updateChannels(this); } EventDataType Session::SearchValue(ChannelID code, qint64 time, bool square) { qint64 drift = qint64(p_profile->cpap->clockDrift()) * 1000L; // Address clock drift for CPAP so correct value is displayed if (s_machine->type() == MT_CPAP) { time -= drift; } qint64 t1, t2, start; QHash >::iterator it; it = eventlist.find(code); quint32 *tptr; int cnt; EventDataType a,b,c,d,e; if (it != eventlist.end()) { int el_size=it.value().size(); for (int i = 0; i < el_size; i++) { EventList *el = it.value()[i]; if ((time >= el->first()) && (time < el->last())) { cnt = el->count(); if (el->type() == EVL_Waveform) { qint64 tt = time - el->first(); double i = tt / el->rate(); if (i> cnt) { qWarning() << "Session" << session() << "time bounds are broken.. There is a fault in the" << machine()->loaderName().toLocal8Bit().data() << "loader"; return 0; } int i1 = int(floor(i)); int i2 = int(ceil(i)); a = el->data(i1); // Don't interpolate if next data point is past end or on exact data point if (i2 >= cnt || i1 == i2) { return a; } qint64 t1 = i1 * el->rate(); qint64 t2 = i2 * el->rate(); c = EventDataType(t2 - t1); // Don't interpolate if t2-t1==0 (should be caught by i1==i2 above) if (c == 0) return a; d = EventDataType(t2 - tt); e = d/c; b = el->data(i2); return b + ((a-b) * e); } else { start = el->first(); tptr = el->rawTime(); // TODO: square plots need fixing if (square) { for (int j = 0; j < cnt-1; ++j) { tptr++; t2 = start + *tptr; if (t2 > time) { return el->data(j); } } } else { for (int j = 0; j < cnt-1; ++j) { tptr++; t2 = start + *tptr; if (t2 > time) { tptr--; t1 = start + *tptr; c = EventDataType(t2 - t1); d = EventDataType(t2 - time); e = d/c; a = el->data(j); b = el->data(j+1); if (a == b) { return a; } else { return b + ((a-b) * e); } } } } } } } } return 0; } QString Session::dimension(ChannelID id) { // Cheat for now return schema::channel[id].units(); } EventDataType Session::Min(ChannelID id) { QHash::iterator i = m_min.find(id); if (i != m_min.end()) { return i.value(); } QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { m_min[id] = 0; return 0; } QVector &evec = j.value(); bool first = true; EventDataType min = 0, t1; int evec_size = evec.size(); for (int i = 0; i < evec_size; ++i) { if (evec[i]->count() != 0) { t1 = evec[i]->Min(); if ((t1 == 0) && (t1 == evec[i]->Max())) { continue; } if (first) { min = t1; first = false; } else { if (min > t1) { min = t1; } } } } m_min[id] = min; return min; } EventDataType Session::Max(ChannelID id) { QHash::iterator i = m_max.find(id); if (i != m_max.end()) { return i.value(); } QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { m_max[id] = 0; return 0; } QVector &evec = j.value(); bool first = true; EventDataType max = 0, t1; int evec_size=evec.size(); for (int i = 0; i < evec_size; ++i) { if (evec.at(i)->count() != 0) { t1 = evec.at(i)->Max(); if (t1 == 0 && t1 == evec.at(i)->Min()) { continue; } if (first) { max = t1; first = false; } else { if (max < t1) { max = t1; } } } } m_max[id] = max; return max; } //// EventDataType Session::physMin(ChannelID id) { QHash::iterator i = m_physmin.find(id); if (i != m_physmin.end()) { return i.value(); } QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { m_physmin[id] = 0; return 0; } EventDataType min = floor(Min(id)); m_physmin[id] = min; return min; } EventDataType Session::physMax(ChannelID id) { QHash::iterator i = m_physmax.find(id); if (i != m_physmax.end()) { return i.value(); } QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { m_physmax[id] = 0; return 0; } EventDataType max = ceil(Max(id) + 0.5); m_physmax[id] = max; return max; } qint64 Session::first(ChannelID id) { qint64 drift = qint64(p_profile->cpap->clockDrift()) * 1000L; qint64 tmp; QHash::iterator i = m_firstchan.find(id); if (i != m_firstchan.end()) { tmp = i.value(); if (s_machine->type() == MT_CPAP) { tmp += drift; } return tmp; } QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { return 0; } QVector &evec = j.value(); bool first = true; qint64 min = 0, t1; int evec_size=evec.size(); for (int i = 0; i < evec_size; ++i) { t1 = evec[i]->first(); if (first) { min = t1; first = false; } else { if (min > t1) { min = t1; } } } m_firstchan[id] = min; if (s_machine->type() == MT_CPAP) { min += drift; } return min; } qint64 Session::last(ChannelID id) { qint64 drift = qint64(p_profile->cpap->clockDrift()) * 1000L; qint64 tmp; QHash::iterator i = m_lastchan.find(id); if (i != m_lastchan.end()) { tmp = i.value(); if (s_machine->type() == MT_CPAP) { tmp += drift; } return tmp; } QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { return 0; } QVector &evec = j.value(); bool first = true; qint64 max = 0, t1; int evec_size=evec.size(); for (int i = 0; i < evec_size; ++i) { t1 = evec[i]->last(); if (first) { max = t1; first = false; } else { if (max < t1) { max = t1; } } } m_lastchan[id] = max; if (s_machine->type() == MT_CPAP) { max += drift; } return max; } bool Session::channelDataExists(ChannelID id) { if (s_events_loaded) { QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { // eventlist not loaded. return false; } return true; } else { qDebug() << "Calling channelDataExists without open eventdata!"; } return false; } bool Session::channelExists(ChannelID id) { if ( ! enabled()) { return false; } if (s_events_loaded) { QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { // eventlist not loaded. return false; } } else { QHash::iterator q = m_cnt.find(id); if (q == m_cnt.end()) { return false; } if (q.value() == 0) { return false; } } return true; } EventDataType Session::countInsideSpan(ChannelID span, ChannelID code) { // TODO: Cache me! QHash >::iterator j = eventlist.find(span); if (j == eventlist.end()) { return 0; } QVector &evec = j.value(); qint64 t1,t2; int evec_size=evec.size(); QList start; QList end; // Simplify the span flags to start and end times list for (int el = 0; el < evec_size; ++el) { EventList &ev = *evec[el]; for (quint32 i=0; i < ev.count(); ++i) { end.push_back(t2=ev.time(i)); start.push_back(t2 - (qint64(ev.data(i)) * 1000L)); } } j = eventlist.find(code); if (j == eventlist.end()) { return 0; } QVector &evec2 = j.value(); evec_size=evec2.size(); int count = 0; int spans = start.size(); for (int el = 0; el < evec_size; ++el) { EventList &ev = *evec2[el]; for (quint32 i=0; i < ev.count(); ++i) { t1 = ev.time(i); for (int z=0; z < spans; ++z) { if ((t1 >= start.at(z)) && (t1 <= end.at(z))) { count++; break; } } } } return count; } EventDataType Session::rangeCount(ChannelID id, qint64 first, qint64 last) { int total = 0, cnt; if (id == AllAhiChannels) { for (int i = 0; i < ahiChannels.size(); i++) total += rangeCount(ahiChannels.at(i), first, last); return (EventDataType)total; } QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { return 0; } QVector &evec = j.value(); qint64 t, start; int evec_size=evec.size(); for (int i = 0; i < evec_size; ++i) { EventList &ev = *evec[i]; if ((ev.last() < first) || (ev.first() > last)) { continue; } if (ev.type() == EVL_Waveform) { qint64 et = last; if (et > ev.last()) { et = ev.last(); } qint64 st = first; if (st < ev.first()) { st = ev.first(); } t = (et - st) / ev.rate(); total += t; } else { cnt = ev.count(); start = ev.first(); quint32 *tptr = ev.rawTime(); quint32 *eptr = tptr + cnt; for (; tptr < eptr; tptr++) { t = start + *tptr; if (t >= first) { if (t <= last) { total++; } else { break; } } } } } return (EventDataType)total; } double Session::rangeSum(ChannelID id, qint64 first, qint64 last) { QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { return 0; } QVector &evec = j.value(); double sum = 0, gain; qint64 t, start; EventStoreType *dptr, * eptr; quint32 *tptr; int cnt, idx = 0; qint64 rate; int evec_size=evec.size(); for (int i = 0; i < evec_size; i++) { EventList &ev = *evec[i]; if ((ev.last() < first) || (ev.first() > last)) { continue; } start = ev.first(); dptr = ev.rawData(); cnt = ev.count(); eptr = dptr + cnt; gain = ev.gain(); rate = ev.rate(); if (ev.type() == EVL_Waveform) { if (first > ev.first()) { // Skip the samples before first idx = (first - ev.first()) / rate; } dptr += idx; //???? foggy. t = start; for (; dptr < eptr; dptr++) { //int j=idx;j= first) { if (t <= last) { sum += EventDataType(*dptr) * gain; } else { break; } } } } } return sum; } EventDataType Session::rangeMin(ChannelID id, qint64 first, qint64 last) { QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { return 0; } QVector &evec = j.value(); EventDataType gain, v, min = std::numeric_limits::max(); qint64 t, start, rate; EventStoreType *dptr, * eptr; quint32 *tptr; int cnt, idx; int evec_size=evec.size(); for (int i = 0; i < evec_size; ++i) { EventList &ev = *evec[i]; if ((ev.last() < first) || (ev.first() > last)) { continue; } dptr = ev.rawData(); start = ev.first(); cnt = ev.count(); eptr = dptr + cnt; gain = ev.gain(); if (ev.type() == EVL_Waveform) { rate = ev.rate(); t = start; idx = 0; if (first > ev.first()) { // Skip the samples before first idx = (first - ev.first()) / rate; } dptr += idx; for (; dptr < eptr; dptr++) { //int j=idx;j= first) { if (t <= last) { v = EventDataType(*dptr) * gain; if (v < min) { min = v; } } else { break; } } } } } return min; } EventDataType Session::rangeMax(ChannelID id, qint64 first, qint64 last) { QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { return 0; } QVector &evec = j.value(); EventDataType gain, v, max = std::numeric_limits::min(); qint64 t, start, rate; EventStoreType *dptr, * eptr; quint32 *tptr; int cnt, idx; int evec_size=evec.size(); for (int i = 0; i < evec_size; i++) { EventList &ev = *evec[i]; if ((ev.last() < first) || (ev.first() > last)) { continue; } start = ev.first(); dptr = ev.rawData(); cnt = ev.count(); eptr = dptr + cnt; gain = ev.gain(); if (ev.type() == EVL_Waveform) { rate = ev.rate(); t = start; idx = 0; if (first > ev.first()) { // Skip the samples before first idx = (first - ev.first()) / rate; } dptr += idx; for (; dptr < eptr; dptr++) { //int j=idx;j max) { max = v; } } else { break; } t += rate; } } else { tptr = ev.rawTime(); for (; dptr < eptr; dptr++) { t = start + *tptr++; if (t >= first) { if (t <= last) { v = EventDataType(*dptr) * gain; if (v > max) { max = v; } } else { break; } } } } } return max; } EventDataType Session::count(ChannelID id) { int sum = 0; if (id == AllAhiChannels) { for (int i = 0; i < ahiChannels.size(); i++) sum += count(ahiChannels.at(i)); return sum; } QHash::iterator i = m_cnt.find(id); if (i != m_cnt.end()) { return i.value(); } QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { // m_cnt[id] = 0; return 0; } QVector &evec = j.value(); int evec_size=evec.size(); if (evec_size == 0) return 0; for (int i = 0; i < evec_size; ++i) { sum += evec.at(i)->count(); } m_cnt[id] = sum; return sum; } double Session::sum(ChannelID id) { QHash::iterator i = m_sum.find(id); if (i != m_sum.end()) { return i.value(); } QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { m_sum[id] = 0; return 0; } QVector &evec = j.value(); double gain, sum = 0; EventStoreType *dptr, * eptr; int cnt; int evec_size=evec.size(); for (int i = 0; i < evec_size; ++i) { EventList &ev = *(evec[i]); gain = ev.gain(); cnt = ev.count(); dptr = ev.rawData(); eptr = dptr + cnt; for (; dptr < eptr; dptr++) { sum += double(*dptr) * gain; } } m_sum[id] = sum; return sum; } EventDataType Session::avg(ChannelID id) { QHash::iterator i = m_avg.find(id); if (i != m_avg.end()) { return i.value(); } QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { m_avg[id] = 0; return 0; } QVector &evec = j.value(); double val = 0, gain; int cnt = 0; EventStoreType *dptr, * eptr; int evec_size=evec.size(); for (int i = 0; i < evec_size; ++i) { EventList &ev = *(evec[i]); dptr = ev.rawData(); gain = ev.gain(); cnt = ev.count(); eptr = dptr + cnt; for (; dptr < eptr; dptr++) { val += double(*dptr) * gain; } } if (cnt > 0) { // Shouldn't really happen.. Should aways contain data val /= double(cnt); } m_avg[id] = val; return val; } EventDataType Session::cph(ChannelID id) // count per hour { QHash::iterator i = m_cph.find(id); if (i != m_cph.end()) { return i.value(); } EventDataType val = count(id); val /= hours(); m_cph[id] = val; return val; } EventDataType Session::sph(ChannelID id) // sum per hour, assuming id is a time field in seconds { QHash::iterator i = m_sph.find(id); if (i != m_sph.end()) { return i.value(); } EventDataType val = sum(id) / 3600.0; val = 100.0 / hours() * val; m_sph[id] = val; return val; } EventDataType Session::timeAboveThreshold(ChannelID id, EventDataType threshold) { QHash::iterator th = m_upperThreshold.find(id); if (th != m_upperThreshold.end()) { if (fabs(th.value()-threshold) < 0.00000001) { // close enough th = m_timeAboveTheshold.find(id); if (th != m_timeAboveTheshold.end()) { return th.value(); } } } bool loaded = s_events_loaded; OpenEvents(); QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { if (!loaded) { TrashEvents(); } return 0.0f; } QVector &evec = j.value(); int evec_size=evec.size(); qint64 ti, started=0, total=0; EventDataType data; int elsize; for (int i = 0; i < evec_size; ++i) { EventList &ev = *(evec[i]); elsize = ev.count(); for (int j=0; j < elsize; ++j) { ti=ev.time(j); data=ev.data(j); if (started == 0) { if (data >= threshold) { started=ti; } } else { if (data < threshold) { total += ti-started; started = 0; } } } } if (started) { total += ti-started; } EventDataType time = double(total) / 60000.0; m_timeAboveTheshold[id] = time; m_upperThreshold[id] = threshold; if (!loaded) this->TrashEvents(); // otherwise leave it open return time; } EventDataType Session::timeBelowThreshold(ChannelID id, EventDataType threshold) { QHash::iterator th = m_lowerThreshold.find(id); if (th != m_lowerThreshold.end()) { if (fabs(th.value()-threshold) < 0.00000001) { // close enough th = m_timeBelowTheshold.find(id); if (th != m_timeBelowTheshold.end()) { return th.value(); } } } bool loaded = s_events_loaded; QHash >::iterator j = eventlist.find(id); if (j == eventlist.end()) { return 0.0f; } QVector &evec = j.value(); int evec_size=evec.size(); qint64 ti, started=0, total=0; EventDataType data; int elsize; for (int i = 0; i < evec_size; ++i) { EventList &ev = *(evec[i]); elsize = ev.count(); for (int j=0; j < elsize; ++j) { ti=ev.time(j); data=ev.data(j); if (started == 0) { if (data <= threshold) { started=ti; } } else { if (data > threshold) { total += ti-started; started = 0; } } } } if (started) { total += ti-started; } EventDataType time = double(total) / 60000.0; m_timeBelowTheshold[id] = time; m_lowerThreshold[id] = threshold; if (!loaded) this->TrashEvents(); // otherwise leave it open return time; } bool sortfunction(EventStoreType i, EventStoreType j) { return (i < j); } EventDataType Session::percentile(ChannelID id, EventDataType percent) { QHash >::iterator jj = eventlist.find(id); if (jj == eventlist.end()) { return 0; } QVector &evec = jj.value(); if (percent > 1.0) { qWarning() << "Session::percentile() called with > 1.0"; return 0; } int evec_size = evec.size(); if (evec_size == 0) { return 0; } QVector array; EventDataType gain = evec[0]->gain(); EventStoreType *dptr, * sptr, *eptr; int tt = 0, cnt = 0; for (int i = 0; i < evec_size; ++i) { EventList &ev = *evec[i]; cnt = ev.count(); tt += cnt; } array.resize(tt); for (int i = 0; i < evec_size; ++i) { EventList &ev = *evec[i]; sptr = ev.rawData(); dptr = array.data(); eptr = sptr + cnt; for (; sptr < eptr; sptr++) { *dptr++ = * sptr; } } int n = array.size() * percent; if (n > array.size() - 1) { n--; } nth_element(array.begin(), array.begin() + n, array.end()); // slack, no averaging.. fixme if this function is ever used.. return array[n] * gain; } EventDataType Session::wavg(ChannelID id) { QHash vtime; QHash::iterator i = m_wavg.find(id); if (i != m_wavg.end()) { return i.value(); } updateCountSummary(id); QHash >::iterator j2 = m_timesummary.find(id); if (j2 == m_timesummary.end()) { return 0; } QHash ×um = j2.value(); if (!m_gain.contains(id)) { return 0; } double s0 = 0, s1 = 0, s2; EventDataType val, gain = m_gain[id]; QHash::iterator vi = timesum.begin(); QHash::iterator ts_end = timesum.end(); for (; vi != ts_end; vi++) { val = vi.key() * gain; s2 = vi.value(); s0 += s2; s1 += val * s2; } if (s0 > 0) { val = s1 / s0; } else { val = 0; } m_wavg[id] = val; return val; } EventDataType Session::calcMiddle(ChannelID code) { int c = p_profile->general->prefCalcMiddle(); if (c == 0) { return percentile(code, 0.5); // Median } else if (c == 1 ) { return wavg(code); // Weighted Average } else { return avg(code); // Average } } EventDataType Session::calcMax(ChannelID code) { return p_profile->general->prefCalcMax() ? percentile(code, 0.995f) : Max(code); } EventDataType Session::calcPercentile(ChannelID code) { double p = p_profile->general->prefCalcPercentile() / 100.0; return percentile(code, p); } EventList *Session::AddEventList(ChannelID code, EventListType et, EventDataType gain, EventDataType offset, EventDataType min, EventDataType max, EventDataType rate, bool second_field) { schema::Channel *channel = &schema::channel[code]; if (!channel) { qWarning() << "Channel" << code << "does not exist!"; //return nullptr; } EventList *el = new EventList(et, gain, offset, min, max, rate, second_field); eventlist[code].push_back(el); //s_machine->registerChannel(chan); return el; } void Session::offsetSession(qint64 offset) { //qDebug() << "Session starts" << QDateTime::fromTime_t(s_first/1000).toString("yyyy-MM-dd HH:mm:ss"); s_first += offset; s_last += offset; QHash::iterator it; QHash::iterator end; it = m_firstchan.begin(); end = m_firstchan.end(); for (; it != end; it++) { if (it.value() > 0) { it.value() += offset; } } it = m_lastchan.begin(); end = m_lastchan.end(); for (; it != end; it++) { if (it.value() > 0) { it.value() += offset; } } QHash >::iterator i; QHash >::iterator el_end=eventlist.end(); int el_s; for (i = eventlist.begin(); i != el_end; i++) { el_s=i.value().size(); for (int j = 0; j < el_s; j++) { EventList *e = i.value()[j]; e->setFirst(e->first() + offset); e->setLast(e->last() + offset); } } qDebug() << "Session now starts" << QDateTime::fromTime_t(s_first / 1000).toString("yyyy-MM-dd HH:mm:ss"); } qint64 Session::first() { qint64 start = s_first; if (s_machine->type() == MT_CPAP) { start += qint64(p_profile->cpap->clockDrift()) * 1000L; } return start; } qint64 Session::last() { qint64 last = s_last; if (s_machine->type() == MT_CPAP) { last += qint64(p_profile->cpap->clockDrift()) * 1000L; } return last; } OSCAR-code-v1.3.1/oscar/SleepLib/session.h000066400000000000000000000406251417327530600201420ustar00rootroot00000000000000/* SleepLib Session Header * * This stuff contains the session calculation smarts * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef SESSION_H #define SESSION_H // this is added as DEFINES += SESSION_DEBUG in the Qmake line // to see how the Resmed loader assigns files to sessions // #define SESSION_DEBUG #include #include #include #include "SleepLib/machine.h" #include "SleepLib/schema.h" #include "SleepLib/event.h" //class EventList; class Machine; enum SliceStatus { UnknownStatus=0, EquipmentOff, MaskOn, MaskOff // is there an EquipmentOn? }; class SessionSlice { public: SessionSlice() { start = end = 0; status = UnknownStatus; } SessionSlice(const SessionSlice & copy) { start = copy.start; end = copy.end; status = copy.status; } SessionSlice& operator=(const SessionSlice& other) = default; SessionSlice(qint64 start, qint64 end, SliceStatus status):start(start), end(end), status(status) {} qint64 start; qint64 end; SliceStatus status; }; /*! \class Session \brief Contains a single Sessions worth of machine event/waveform information. This class also contains all the primary database logic for SleepLib */ class Session { friend class Day; friend class Machine; public: /*! \fn Session(Machine *,SessionID); \brief Create a session object belonging to Machine, with supplied SessionID If sessionID is 0, the next in sequence will be picked */ Session(Machine *, SessionID); virtual ~Session(); //! \brief Checks whether the supplied time is within the bounds of this session (ms since epoch) inline bool checkInside(qint64 time) { return ((time >= s_first) && (time <= s_last)); } //! \brief Stores the session in the directory supplied by path bool Store(QString path); //! \brief Writes the Sessions Summary Indexes to filename, in SleepLibs custom data format. bool StoreSummary(); // //! \brief Save the Sessions Summary Indexes to the stream // void StoreSummaryData(QDataStream & out) const; //! \brief Writes the Sessions EventLists to filename, in SleepLibs custom data format. bool StoreEvents(); //bool Load(QString path); // //! \brief Loads the Sessions Summary Indexes from stream // void LoadSummaryData(QDataStream & in); //! \brief Loads the Sessions Summary Indexes from filename, from SleepLibs custom data format. bool LoadSummary(); //! \brief Loads the Sessions EventLists from filename, from SleepLibs custom data format. bool LoadEvents(QString filename); //! \brief Loads the events for this session when requested (only the summaries are loaded at startup) bool OpenEvents(); //! \brief Put the events away until needed again, freeing memory void TrashEvents(); //! \brief Returns true if session contains an empty duration inline bool isEmpty() { return (s_first == s_last); } //! \brief Search for Event code happening at supplied time (ms since epoch) EventDataType SearchValue(ChannelID code, qint64 time, bool square); //! \brief Return the sessionID inline const SessionID &session() { return s_session; } //! \brief Returns whether or not session is being used. inline bool enabled() const { return s_enabled; } //! \brief Sets whether or not session is being used. void setEnabled(bool b); //! \brief Return the earliest time in session (in milliseconds since epoch) inline qint64 realFirst() const { return s_first; } //! \brief Return the latest time in session (in milliseconds since epoch) inline qint64 realLast() const { return s_last; } //! \brief Return the start of this sessions time range, adjusted for clock drift (in milliseconds since epoch) qint64 first(); //! \brief Return the end of this sessions time range, adjusted for clock drift (in milliseconds since epoch) qint64 last(); //! \brief Return the millisecond length of this session qint64 length() { return s_last - s_first; // qint64 t; // int size = m_slices.size(); // if (size == 0) { // t = (s_last - s_first); // } else { // t = 0; // for (int i=0; i > eventlist; //! \brief Sessions Settings List, contianing single settings for this session. QHash settings; // Session caches QHash m_cnt; QHash m_sum; QHash m_avg; QHash m_wavg; QHash m_min; // The actual minimum QHash m_max; // This could go in channels, but different machines interpret it differently // Under the new SleepyLib data Device model this can be done, but unfortunately not here.. QHash m_physmin; // The physical minimum for graph display purposes QHash m_physmax; // The physical maximum QHash m_cph; // Counts per hour (eg AHI) QHash m_sph; // % indice (eg % night in CSR) QHash m_firstchan; QHash m_lastchan; QHash > m_valuesummary; QHash > m_timesummary; QHash m_gain; QHash m_lowerThreshold; QHash m_timeBelowTheshold; QHash m_upperThreshold; QHash m_timeAboveTheshold; QList m_availableChannels; QList m_availableSettings; QVector m_slices; //! \brief Generates sum and time data for each distinct value in 'code' events.. void updateCountSummary(ChannelID code); //! \brief Destroy any trace of event 'code', freeing any memory if loaded. void destroyEvent(ChannelID code); // UpdateSummaries may recalculate all these, but it may be faster setting upfront void setCount(ChannelID id, EventDataType val) { m_cnt[id] = val; } void setSum(ChannelID id, EventDataType val) { m_sum[id] = val; } void setMin(ChannelID id, EventDataType val) { m_min[id] = val; } void setMax(ChannelID id, EventDataType val) { m_max[id] = val; } void setPhysMin(ChannelID id, EventDataType val) { m_physmin[id] = val; } void setPhysMax(ChannelID id, EventDataType val) { m_physmax[id] = val; } void updateMin(ChannelID id, EventDataType val) { QHash::iterator i = m_min.find(id); if (i == m_min.end()) { m_min[id] = val; } else if (i.value() > val) { i.value() = val; } } void updateMax(ChannelID id, EventDataType val) { QHash::iterator i = m_max.find(id); if (i == m_max.end()) { m_max[id] = val; } else if (i.value() < val) { i.value() = val; } } void setAvg(ChannelID id, EventDataType val) { m_avg[id] = val; } void setWavg(ChannelID id, EventDataType val) { m_wavg[id] = val; } // void setMedian(ChannelID id,EventDataType val) { m_med[id]=val; } // void set90p(ChannelID id,EventDataType val) { m_90p[id]=val; } // void set95p(ChannelID id,EventDataType val) { m_95p[id]=val; } void setCph(ChannelID id, EventDataType val) { m_cph[id] = val; } void setSph(ChannelID id, EventDataType val) { m_sph[id] = val; } void setFirst(ChannelID id, qint64 val) { m_firstchan[id] = val; } void setLast(ChannelID id, qint64 val) { m_lastchan[id] = val; } EventDataType count(ChannelID id); //! \brief Returns the Count of all events of type id between time range EventDataType rangeCount(ChannelID id, qint64 first, qint64 last); //! \brief Returns the Sum of all events of type id between time range double rangeSum(ChannelID id, qint64 first, qint64 last); //! \brief Returns the minimum of events of type id between time range EventDataType rangeMin(ChannelID id, qint64 first, qint64 last); //! \brief Returns the maximum of events of type id between time range EventDataType rangeMax(ChannelID id, qint64 first, qint64 last); //! \brief Returns the count of code events inside span flag event durations EventDataType countInsideSpan(ChannelID span, ChannelID code); //! \brief Returns (and caches) the Sum of all events of type id double sum(ChannelID id); //! \brief Returns (and caches) the Average of all events of type id EventDataType avg(ChannelID id); //! \brief Returns (and caches) the Time Weighted Average of all events of type id EventDataType wavg(ChannelID i); //! \brief Returns (and caches) the Minimum of all events of type id EventDataType Min(ChannelID id); //! \brief Returns (and caches) the Maximum of all events of type id EventDataType Max(ChannelID id); //! \brief Returns (and caches) the Minimum of all events of type id EventDataType physMin(ChannelID id); //! \brief Returns (and caches) the Maximum of all events of type id EventDataType physMax(ChannelID id); //! \brief Returns (and caches) the 90th Percentile of all events of type id EventDataType p90(ChannelID id); //! \brief Returns (and caches) the 95th Percentile of all events of type id EventDataType p95(ChannelID id); //! \brief Returns (and caches) the Median (50th Perc) of all events of type id EventDataType median(ChannelID id); //! \brief Returns (and caches) the Count-Per-Hour of all events of type id EventDataType cph(ChannelID id); //! \brief Returns (and caches) the Sum-Per-Hour of all events of type id EventDataType sph(ChannelID id); //! \brief Returns (without caching) the requested Percentile of all events of type id EventDataType percentile(ChannelID id, EventDataType percentile); //! \brief Returns the amount of time (in decimal minutes) the Channel spent above the threshold EventDataType timeAboveThreshold(ChannelID id, EventDataType threshold); //! \brief Returns the amount of time (in decimal minutes) the Channel spent below the threshold EventDataType timeBelowThreshold(ChannelID id, EventDataType threshold); //! \brief According to preferences.. EventDataType calcMiddle(ChannelID code); EventDataType calcMax(ChannelID code); EventDataType calcPercentile(ChannelID code); //! \brief Returns true if the channel has events loaded, or a record of a count for when they are not bool channelExists(ChannelID name); //! \brief Returns true if the channel has event data available (must be loaded first) bool channelDataExists(ChannelID id); bool IsLoneSession() { return s_lonesession; } void SetLoneSession(bool b) { s_lonesession = b; } bool eventsLoaded() { return s_events_loaded; } //! \brief Update this sessions first time if it's less than the current record inline void updateFirst(qint64 v) { if (!s_first) { s_first = v; } else if (s_first > v) { s_first = v; } } //! \brief Update this sessions latest time if it's more than the current record inline void updateLast(qint64 v) { if (!s_last) { s_last = v; } else if (s_last < v) { s_last = v; } } //! \brief Returns (and caches) the first time for Channel code qint64 first(ChannelID code); //! \brief Returns (and caches) the last time for Channel code qint64 last(ChannelID code); //! \brief Regenerates the Session Index Caches, and calls the fun calculation functions void UpdateSummaries(); //! \brief Creates and returns a new EventList for the supplied Channel code EventList *AddEventList(ChannelID code, EventListType et, EventDataType gain = 1.0, EventDataType offset = 0.0, EventDataType min = 0.0, EventDataType max = 0.0, EventDataType rate = 0.0, bool second_field = false); //! \brief Returns this sessions MachineID Machine *machine() { return s_machine; } //! \brief Returns true if session only contains summary data inline bool summaryOnly() { return s_summaryOnly; } //! \brief Returns true if there are no settings for this session inline bool noSettings() { return s_noSettings; } //! \brief Mark this session as containing summary data only or not (true, false) inline void setSummaryOnly(bool b) { s_summaryOnly = b; } //! \brief Mark this ssession as having settings data or not (true, false) inline void setNoSettings(bool b) { s_noSettings = b; } //! \brief Mark whether this session has loaded data (true, false) void setOpened(bool b = true) { s_events_loaded = b; s_summary_loaded = b; } //! \brief Completely purges Session from memory and disk. bool Destroy(); void wipeSummary() { s_first = s_last = 0; s_enabled = true; m_cph.clear(); m_sum.clear(); m_cnt.clear(); } QString eventFile() const; //! \brief Returns MachineType for this session MachineType type() { return s_machtype; } #ifdef SESSION_DEBUG QStringList session_files; #endif protected: SessionID s_session; Machine *s_machine; //! \brief Time session begins (in ms since epoch) qint64 s_first; //! \brief Time session ends (in ms since epoch) qint64 s_last; bool s_changed; bool s_lonesession; bool s_evchecksum_checked; bool _first_session; bool s_summaryOnly; //! \brief True if there are no settings for this session bool s_noSettings; bool s_summary_loaded; bool s_events_loaded; bool s_enabled; // for debugging bool destroyed; MachineType s_machtype; }; QDataStream & operator<<(QDataStream & out, const Session & session); QDataStream & operator>>(QDataStream & in, Session & session); #endif // SESSION_H OSCAR-code-v1.3.1/oscar/SleepLib/thirdparty/000077500000000000000000000000001417327530600204715ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/SleepLib/thirdparty/botan_all.cpp000066400000000000000000006425531417327530600231470ustar00rootroot00000000000000/* * Botan 2.18.2 Amalgamation * (C) 1999-2020 The Botan Authors * * Botan is released under the Simplified BSD License (see license.txt) */ #include "botan_all.h" #include #include #include #include #include #include #include namespace Botan { /** * If top bit of arg is set, return ~0. Otherwise return 0. */ template inline T expand_top_bit(T a) { return static_cast(0) - (a >> (sizeof(T)*8-1)); } /** * If arg is zero, return ~0. Otherwise return 0 */ template inline T ct_is_zero(T x) { return expand_top_bit(~x & (x - 1)); } /** * Power of 2 test. T should be an unsigned integer type * @param arg an integer value * @return true iff arg is 2^n for some n > 0 */ template inline constexpr bool is_power_of_2(T arg) { return (arg != 0) && (arg != 1) && ((arg & static_cast(arg-1)) == 0); } /** * Return the index of the highest set bit * T is an unsigned integer type * @param n an integer value * @return index of the highest set bit in n */ template inline size_t high_bit(T n) { size_t hb = 0; for(size_t s = 8*sizeof(T) / 2; s > 0; s /= 2) { const size_t z = s * ((~ct_is_zero(n >> s)) & 1); hb += z; n >>= z; } hb += n; return hb; } /** * Return the number of significant bytes in n * @param n an integer value * @return number of significant bytes in n */ template inline size_t significant_bytes(T n) { size_t b = 0; for(size_t s = 8*sizeof(n) / 2; s >= 8; s /= 2) { const size_t z = s * (~ct_is_zero(n >> s) & 1); b += z/8; n >>= z; } b += (n != 0); return b; } /** * Count the trailing zero bits in n * @param n an integer value * @return maximum x st 2^x divides n */ template inline size_t ctz(T n) { /* * If n == 0 then this function will compute 8*sizeof(T)-1, so * initialize lb to 1 if n == 0 to produce the expected result. */ size_t lb = ct_is_zero(n) & 1; for(size_t s = 8*sizeof(T) / 2; s > 0; s /= 2) { const T mask = (static_cast(1) << s) - 1; const size_t z = s * (ct_is_zero(n & mask) & 1); lb += z; n >>= z; } return lb; } template uint8_t ceil_log2(T x) { static_assert(sizeof(T) < 32, "Abnormally large scalar"); if(x >> (sizeof(T)*8-1)) return sizeof(T)*8; uint8_t result = 0; T compare = 1; while(compare < x) { compare <<= 1; result++; } return result; } // Potentially variable time ctz used for OCB inline size_t var_ctz32(uint32_t n) { #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || defined(BOTAN_BUILD_COMPILER_IS_CLANG) if(n == 0) return 32; return __builtin_ctz(n); #else return ctz(n); #endif } template inline T bit_permute_step(T x, T mask, size_t shift) { /* See https://reflectionsonsecurity.wordpress.com/2014/05/11/efficient-bit-permutation-using-delta-swaps/ and http://programming.sirrida.de/bit_perm.html */ const T swap = ((x >> shift) ^ x) & mask; return (x ^ swap) ^ (swap << shift); } template inline void swap_bits(T& x, T& y, T mask, size_t shift) { const T swap = ((x >> shift) ^ y) & mask; x ^= swap << shift; y ^= swap; } } namespace Botan { /** * Perform encoding using the base provided * @param base object giving access to the encodings specifications * @param output an array of at least base.encode_max_output bytes * @param input is some binary data * @param input_length length of input in bytes * @param input_consumed is an output parameter which says how many * bytes of input were actually consumed. If less than * input_length, then the range input[consumed:length] * should be passed in later along with more input. * @param final_inputs true iff this is the last input, in which case padding chars will be applied if needed * @return number of bytes written to output */ template size_t base_encode(Base&& base, char output[], const uint8_t input[], size_t input_length, size_t& input_consumed, bool final_inputs) { input_consumed = 0; const size_t encoding_bytes_in = base.encoding_bytes_in(); const size_t encoding_bytes_out = base.encoding_bytes_out(); size_t input_remaining = input_length; size_t output_produced = 0; while(input_remaining >= encoding_bytes_in) { base.encode(output + output_produced, input + input_consumed); input_consumed += encoding_bytes_in; output_produced += encoding_bytes_out; input_remaining -= encoding_bytes_in; } if(final_inputs && input_remaining) { std::vector remainder(encoding_bytes_in, 0); for(size_t i = 0; i != input_remaining; ++i) { remainder[i] = input[input_consumed + i]; } base.encode(output + output_produced, remainder.data()); const size_t bits_consumed = base.bits_consumed(); const size_t remaining_bits_before_padding = base.remaining_bits_before_padding(); size_t empty_bits = 8 * (encoding_bytes_in - input_remaining); size_t index = output_produced + encoding_bytes_out - 1; while(empty_bits >= remaining_bits_before_padding) { output[index--] = '='; empty_bits -= bits_consumed; } input_consumed += input_remaining; output_produced += encoding_bytes_out; } return output_produced; } template std::string base_encode_to_string(Base&& base, const uint8_t input[], size_t input_length) { const size_t output_length = base.encode_max_output(input_length); std::string output(output_length, 0); size_t consumed = 0; size_t produced = 0; if(output_length > 0) { produced = base_encode(base, &output.front(), input, input_length, consumed, true); } BOTAN_ASSERT_EQUAL(consumed, input_length, "Consumed the entire input"); BOTAN_ASSERT_EQUAL(produced, output.size(), "Produced expected size"); return output; } /** * Perform decoding using the base provided * @param base object giving access to the encodings specifications * @param output an array of at least Base::decode_max_output bytes * @param input some base input * @param input_length length of input in bytes * @param input_consumed is an output parameter which says how many * bytes of input were actually consumed. If less than * input_length, then the range input[consumed:length] * should be passed in later along with more input. * @param final_inputs true iff this is the last input, in which case padding is allowed * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return number of bytes written to output */ template size_t base_decode(Base&& base, uint8_t output[], const char input[], size_t input_length, size_t& input_consumed, bool final_inputs, bool ignore_ws = true) { const size_t decoding_bytes_in = base.decoding_bytes_in(); const size_t decoding_bytes_out = base.decoding_bytes_out(); uint8_t* out_ptr = output; std::vector decode_buf(decoding_bytes_in, 0); size_t decode_buf_pos = 0; size_t final_truncate = 0; clear_mem(output, base.decode_max_output(input_length)); for(size_t i = 0; i != input_length; ++i) { const uint8_t bin = base.lookup_binary_value(input[i]); if(base.check_bad_char(bin, input[i], ignore_ws)) // May throw Invalid_Argument { decode_buf[decode_buf_pos] = bin; ++decode_buf_pos; } /* * If we're at the end of the input, pad with 0s and truncate */ if(final_inputs && (i == input_length - 1)) { if(decode_buf_pos) { for(size_t j = decode_buf_pos; j < decoding_bytes_in; ++j) { decode_buf[j] = 0; } final_truncate = decoding_bytes_in - decode_buf_pos; decode_buf_pos = decoding_bytes_in; } } if(decode_buf_pos == decoding_bytes_in) { base.decode(out_ptr, decode_buf.data()); out_ptr += decoding_bytes_out; decode_buf_pos = 0; input_consumed = i+1; } } while(input_consumed < input_length && base.lookup_binary_value(input[input_consumed]) == 0x80) { ++input_consumed; } size_t written = (out_ptr - output) - base.bytes_to_remove(final_truncate); return written; } template size_t base_decode_full(Base&& base, uint8_t output[], const char input[], size_t input_length, bool ignore_ws) { size_t consumed = 0; const size_t written = base_decode(base, output, input, input_length, consumed, true, ignore_ws); if(consumed != input_length) { throw Invalid_Argument(base.name() + " decoding failed, input did not have full bytes"); } return written; } template Vector base_decode_to_vec(Base&& base, const char input[], size_t input_length, bool ignore_ws) { const size_t output_length = base.decode_max_output(input_length); Vector bin(output_length); const size_t written = base_decode_full(base, bin.data(), input, input_length, ignore_ws); bin.resize(written); return bin; } } #if defined(BOTAN_HAS_VALGRIND) #include #endif namespace Botan { namespace CT { /** * Use valgrind to mark the contents of memory as being undefined. * Valgrind will accept operations which manipulate undefined values, * but will warn if an undefined value is used to decided a conditional * jump or a load/store address. So if we poison all of our inputs we * can confirm that the operations in question are truly const time * when compiled by whatever compiler is in use. * * Even better, the VALGRIND_MAKE_MEM_* macros work even when the * program is not run under valgrind (though with a few cycles of * overhead, which is unfortunate in final binaries as these * annotations tend to be used in fairly important loops). * * This approach was first used in ctgrind (https://github.com/agl/ctgrind) * but calling the valgrind mecheck API directly works just as well and * doesn't require a custom patched valgrind. */ template inline void poison(const T* p, size_t n) { #if defined(BOTAN_HAS_VALGRIND) VALGRIND_MAKE_MEM_UNDEFINED(p, n * sizeof(T)); #else BOTAN_UNUSED(p); BOTAN_UNUSED(n); #endif } template inline void unpoison(const T* p, size_t n) { #if defined(BOTAN_HAS_VALGRIND) VALGRIND_MAKE_MEM_DEFINED(p, n * sizeof(T)); #else BOTAN_UNUSED(p); BOTAN_UNUSED(n); #endif } template inline void unpoison(T& p) { #if defined(BOTAN_HAS_VALGRIND) VALGRIND_MAKE_MEM_DEFINED(&p, sizeof(T)); #else BOTAN_UNUSED(p); #endif } /** * A Mask type used for constant-time operations. A Mask always has value * either 0 (all bits cleared) or ~0 (all bits set). All operations in a Mask * are intended to compile to code which does not contain conditional jumps. * This must be verified with tooling (eg binary disassembly or using valgrind) * since you never know what a compiler might do. */ template class Mask { public: static_assert(std::is_unsigned::value, "CT::Mask only defined for unsigned integer types"); Mask(const Mask& other) = default; Mask& operator=(const Mask& other) = default; /** * Derive a Mask from a Mask of a larger type */ template Mask(Mask o) : m_mask(static_cast(o.value())) { static_assert(sizeof(U) > sizeof(T), "sizes ok"); } /** * Return a Mask with all bits set */ static Mask set() { return Mask(static_cast(~0)); } /** * Return a Mask with all bits cleared */ static Mask cleared() { return Mask(0); } /** * Return a Mask which is set if v is != 0 */ static Mask expand(T v) { return ~Mask::is_zero(v); } /** * Return a Mask which is set if m is set */ template static Mask expand(Mask m) { static_assert(sizeof(U) < sizeof(T), "sizes ok"); return ~Mask::is_zero(m.value()); } /** * Return a Mask which is set if v is == 0 or cleared otherwise */ static Mask is_zero(T x) { return Mask(ct_is_zero(x)); } /** * Return a Mask which is set if x == y */ static Mask is_equal(T x, T y) { return Mask::is_zero(static_cast(x ^ y)); } /** * Return a Mask which is set if x < y */ static Mask is_lt(T x, T y) { return Mask(expand_top_bit(x^((x^y) | ((x-y)^x)))); } /** * Return a Mask which is set if x > y */ static Mask is_gt(T x, T y) { return Mask::is_lt(y, x); } /** * Return a Mask which is set if x <= y */ static Mask is_lte(T x, T y) { return ~Mask::is_gt(x, y); } /** * Return a Mask which is set if x >= y */ static Mask is_gte(T x, T y) { return ~Mask::is_lt(x, y); } static Mask is_within_range(T v, T l, T u) { //return Mask::is_gte(v, l) & Mask::is_lte(v, u); const T v_lt_l = v^((v^l) | ((v-l)^v)); const T v_gt_u = u^((u^v) | ((u-v)^u)); const T either = v_lt_l | v_gt_u; return ~Mask(expand_top_bit(either)); } static Mask is_any_of(T v, std::initializer_list accepted) { T accept = 0; for(auto a: accepted) { const T diff = a ^ v; const T eq_zero = ~diff & (diff - 1); accept |= eq_zero; } return Mask(expand_top_bit(accept)); } /** * AND-combine two masks */ Mask& operator&=(Mask o) { m_mask &= o.value(); return (*this); } /** * XOR-combine two masks */ Mask& operator^=(Mask o) { m_mask ^= o.value(); return (*this); } /** * OR-combine two masks */ Mask& operator|=(Mask o) { m_mask |= o.value(); return (*this); } /** * AND-combine two masks */ friend Mask operator&(Mask x, Mask y) { return Mask(x.value() & y.value()); } /** * XOR-combine two masks */ friend Mask operator^(Mask x, Mask y) { return Mask(x.value() ^ y.value()); } /** * OR-combine two masks */ friend Mask operator|(Mask x, Mask y) { return Mask(x.value() | y.value()); } /** * Negate this mask */ Mask operator~() const { return Mask(~value()); } /** * Return x if the mask is set, or otherwise zero */ T if_set_return(T x) const { return m_mask & x; } /** * Return x if the mask is cleared, or otherwise zero */ T if_not_set_return(T x) const { return ~m_mask & x; } /** * If this mask is set, return x, otherwise return y */ T select(T x, T y) const { // (x & value()) | (y & ~value()) return static_cast(y ^ (value() & (x ^ y))); } T select_and_unpoison(T x, T y) const { T r = this->select(x, y); CT::unpoison(r); return r; } /** * If this mask is set, return x, otherwise return y */ Mask select_mask(Mask x, Mask y) const { return Mask(select(x.value(), y.value())); } /** * Conditionally set output to x or y, depending on if mask is set or * cleared (resp) */ void select_n(T output[], const T x[], const T y[], size_t len) const { for(size_t i = 0; i != len; ++i) output[i] = this->select(x[i], y[i]); } /** * If this mask is set, zero out buf, otherwise do nothing */ void if_set_zero_out(T buf[], size_t elems) { for(size_t i = 0; i != elems; ++i) { buf[i] = this->if_not_set_return(buf[i]); } } /** * Return the value of the mask, unpoisoned */ T unpoisoned_value() const { T r = value(); CT::unpoison(r); return r; } /** * Return true iff this mask is set */ bool is_set() const { return unpoisoned_value() != 0; } /** * Return the underlying value of the mask */ T value() const { return m_mask; } private: Mask(T m) : m_mask(m) {} T m_mask; }; template inline Mask conditional_copy_mem(T cnd, T* to, const T* from0, const T* from1, size_t elems) { const auto mask = CT::Mask::expand(cnd); mask.select_n(to, from0, from1, elems); return mask; } template inline void conditional_swap(bool cnd, T& x, T& y) { const auto swap = CT::Mask::expand(cnd); T t0 = swap.select(y, x); T t1 = swap.select(x, y); x = t0; y = t1; } template inline void conditional_swap_ptr(bool cnd, T& x, T& y) { uintptr_t xp = reinterpret_cast(x); uintptr_t yp = reinterpret_cast(y); conditional_swap(cnd, xp, yp); x = reinterpret_cast(xp); y = reinterpret_cast(yp); } /** * If bad_mask is unset, return in[delim_idx:input_length] copied to * new buffer. If bad_mask is set, return an all zero vector of * unspecified length. */ secure_vector copy_output(CT::Mask bad_input, const uint8_t input[], size_t input_length, size_t delim_idx); secure_vector strip_leading_zeros(const uint8_t in[], size_t length); inline secure_vector strip_leading_zeros(const secure_vector& in) { return strip_leading_zeros(in.data(), in.size()); } } } namespace Botan { class donna128 final { public: donna128(uint64_t ll = 0, uint64_t hh = 0) { l = ll; h = hh; } donna128(const donna128&) = default; donna128& operator=(const donna128&) = default; friend donna128 operator>>(const donna128& x, size_t shift) { donna128 z = x; if(shift > 0) { const uint64_t carry = z.h << (64 - shift); z.h = (z.h >> shift); z.l = (z.l >> shift) | carry; } return z; } friend donna128 operator<<(const donna128& x, size_t shift) { donna128 z = x; if(shift > 0) { const uint64_t carry = z.l >> (64 - shift); z.l = (z.l << shift); z.h = (z.h << shift) | carry; } return z; } friend uint64_t operator&(const donna128& x, uint64_t mask) { return x.l & mask; } uint64_t operator&=(uint64_t mask) { h = 0; l &= mask; return l; } donna128& operator+=(const donna128& x) { l += x.l; h += x.h; const uint64_t carry = (l < x.l); h += carry; return *this; } donna128& operator+=(uint64_t x) { l += x; const uint64_t carry = (l < x); h += carry; return *this; } uint64_t lo() const { return l; } uint64_t hi() const { return h; } private: uint64_t h = 0, l = 0; }; inline donna128 operator*(const donna128& x, uint64_t y) { BOTAN_ARG_CHECK(x.hi() == 0, "High 64 bits of donna128 set to zero during multiply"); uint64_t lo = 0, hi = 0; mul64x64_128(x.lo(), y, &lo, &hi); return donna128(lo, hi); } inline donna128 operator*(uint64_t y, const donna128& x) { return x * y; } inline donna128 operator+(const donna128& x, const donna128& y) { donna128 z = x; z += y; return z; } inline donna128 operator+(const donna128& x, uint64_t y) { donna128 z = x; z += y; return z; } inline donna128 operator|(const donna128& x, const donna128& y) { return donna128(x.lo() | y.lo(), x.hi() | y.hi()); } inline uint64_t carry_shift(const donna128& a, size_t shift) { return (a >> shift).lo(); } inline uint64_t combine_lower(const donna128& a, size_t s1, const donna128& b, size_t s2) { donna128 z = (a >> s1) | (b << s2); return z.lo(); } #if defined(BOTAN_TARGET_HAS_NATIVE_UINT128) inline uint64_t carry_shift(const uint128_t a, size_t shift) { return static_cast(a >> shift); } inline uint64_t combine_lower(const uint128_t a, size_t s1, const uint128_t b, size_t s2) { return static_cast((a >> s1) | (b << s2)); } #endif } namespace Botan { /** * No_Filesystem_Access Exception */ class BOTAN_PUBLIC_API(2,0) No_Filesystem_Access final : public Exception { public: No_Filesystem_Access() : Exception("No filesystem access enabled.") {} }; BOTAN_TEST_API bool has_filesystem_impl(); BOTAN_TEST_API std::vector get_files_recursive(const std::string& dir); } namespace Botan { namespace OS { /* * This header is internal (not installed) and these functions are not * intended to be called by applications. However they are given public * visibility (using BOTAN_TEST_API macro) for the tests. This also probably * allows them to be overridden by the application on ELF systems, but * this hasn't been tested. */ /** * @return process ID assigned by the operating system. * On Unix and Windows systems, this always returns a result * On IncludeOS it returns 0 since there is no process ID to speak of * in a unikernel. */ uint32_t BOTAN_TEST_API get_process_id(); /** * Test if we are currently running with elevated permissions * eg setuid, setgid, or with POSIX caps set. */ bool running_in_privileged_state(); /** * @return CPU processor clock, if available * * On Windows, calls QueryPerformanceCounter. * * Under GCC or Clang on supported platforms the hardware cycle counter is queried. * Currently supported processors are x86, PPC, Alpha, SPARC, IA-64, S/390x, and HP-PA. * If no CPU cycle counter is available on this system, returns zero. */ uint64_t BOTAN_TEST_API get_cpu_cycle_counter(); size_t BOTAN_TEST_API get_cpu_total(); size_t BOTAN_TEST_API get_cpu_available(); /** * Return the ELF auxiliary vector cooresponding to the given ID. * This only makes sense on Unix-like systems and is currently * only supported on Linux, Android, and FreeBSD. * * Returns zero if not supported on the current system or if * the id provided is not known. */ unsigned long get_auxval(unsigned long id); /* * @return best resolution timestamp available * * The epoch and update rate of this clock is arbitrary and depending * on the hardware it may not tick at a constant rate. * * Uses hardware cycle counter, if available. * On POSIX platforms clock_gettime is used with a monotonic timer * As a final fallback std::chrono::high_resolution_clock is used. */ uint64_t BOTAN_TEST_API get_high_resolution_clock(); /** * @return system clock (reflecting wall clock) with best resolution * available, normalized to nanoseconds resolution. */ uint64_t BOTAN_TEST_API get_system_timestamp_ns(); /** * @return maximum amount of memory (in bytes) Botan could/should * hyptothetically allocate for the memory poool. Reads environment * variable "BOTAN_MLOCK_POOL_SIZE", set to "0" to disable pool. */ size_t get_memory_locking_limit(); /** * Return the size of a memory page, if that can be derived on the * current system. Otherwise returns some default value (eg 4096) */ size_t system_page_size(); /** * Read the value of an environment variable, setting it to value_out if it * exists. Returns false and sets value_out to empty string if no such variable * is set. If the process seems to be running in a privileged state (such as * setuid) then always returns false and does not examine the environment. */ bool read_env_variable(std::string& value_out, const std::string& var_name); /** * Read the value of an environment variable and convert it to an * integer. If not set or conversion fails, returns the default value. * * If the process seems to be running in a privileged state (such as setuid) * then always returns nullptr, similiar to glibc's secure_getenv. */ size_t read_env_variable_sz(const std::string& var_name, size_t def_value = 0); /** * Request count pages of RAM which are locked into memory using mlock, * VirtualLock, or some similar OS specific API. Free it with free_locked_pages. * * Returns an empty list on failure. This function is allowed to return fewer * than count pages. * * The contents of the allocated pages are undefined. * * Each page is preceded by and followed by a page which is marked * as noaccess, such that accessing it will cause a crash. This turns * out of bound reads/writes into crash events. * * @param count requested number of locked pages */ std::vector allocate_locked_pages(size_t count); /** * Free memory allocated by allocate_locked_pages * @param pages a list of pages returned by allocate_locked_pages */ void free_locked_pages(const std::vector& pages); /** * Set the MMU to prohibit access to this page */ void page_prohibit_access(void* page); /** * Set the MMU to allow R/W access to this page */ void page_allow_access(void* page); /** * Run a probe instruction to test for support for a CPU instruction. * Runs in system-specific env that catches illegal instructions; this * function always fails if the OS doesn't provide this. * Returns value of probe_fn, if it could run. * If error occurs, returns negative number. * This allows probe_fn to indicate errors of its own, if it wants. * For example the instruction might not only be only available on some * CPUs, but also buggy on some subset of these - the probe function * can test to make sure the instruction works properly before * indicating that the instruction is available. * * @warning on Unix systems uses signal handling in a way that is not * thread safe. It should only be called in a single-threaded context * (ie, at static init time). * * If probe_fn throws an exception the result is undefined. * * Return codes: * -1 illegal instruction detected */ int BOTAN_TEST_API run_cpu_instruction_probe(std::function probe_fn); /** * Represents a terminal state */ class BOTAN_UNSTABLE_API Echo_Suppression { public: /** * Reenable echo on this terminal. Can be safely called * multiple times. May throw if an error occurs. */ virtual void reenable_echo() = 0; /** * Implicitly calls reenable_echo, but swallows/ignored all * errors which would leave the terminal in an invalid state. */ virtual ~Echo_Suppression() = default; }; /** * Suppress echo on the terminal * Returns null if this operation is not supported on the current system. */ std::unique_ptr BOTAN_UNSTABLE_API suppress_echo_on_terminal(); } } namespace Botan { template inline void prefetch_readonly(const T* addr, size_t length) { #if defined(__GNUG__) const size_t Ts_per_cache_line = CPUID::cache_line_size() / sizeof(T); for(size_t i = 0; i <= length; i += Ts_per_cache_line) __builtin_prefetch(addr + i, 0); #endif } template inline void prefetch_readwrite(const T* addr, size_t length) { #if defined(__GNUG__) const size_t Ts_per_cache_line = CPUID::cache_line_size() / sizeof(T); for(size_t i = 0; i <= length; i += Ts_per_cache_line) __builtin_prefetch(addr + i, 1); #endif } } namespace Botan { /** * Round up * @param n a non-negative integer * @param align_to the alignment boundary * @return n rounded up to a multiple of align_to */ inline size_t round_up(size_t n, size_t align_to) { BOTAN_ARG_CHECK(align_to != 0, "align_to must not be 0"); if(n % align_to) n += align_to - (n % align_to); return n; } /** * Round down * @param n an integer * @param align_to the alignment boundary * @return n rounded down to a multiple of align_to */ template inline constexpr T round_down(T n, T align_to) { return (align_to == 0) ? n : (n - (n % align_to)); } /** * Clamp */ inline size_t clamp(size_t n, size_t lower_bound, size_t upper_bound) { if(n < lower_bound) return lower_bound; if(n > upper_bound) return upper_bound; return n; } } namespace Botan { class BOTAN_PUBLIC_API(2,0) Integer_Overflow_Detected final : public Exception { public: Integer_Overflow_Detected(const std::string& file, int line) : Exception("Integer overflow detected at " + file + ":" + std::to_string(line)) {} ErrorType error_type() const noexcept override { return ErrorType::InternalError; } }; inline size_t checked_add(size_t x, size_t y, const char* file, int line) { // TODO: use __builtin_x_overflow on GCC and Clang size_t z = x + y; if(z < x) { throw Integer_Overflow_Detected(file, line); } return z; } #define BOTAN_CHECKED_ADD(x,y) checked_add(x,y,__FILE__,__LINE__) } namespace Botan { inline std::vector to_byte_vector(const std::string& s) { return std::vector(s.cbegin(), s.cend()); } inline std::string to_string(const secure_vector &bytes) { return std::string(bytes.cbegin(), bytes.cend()); } /** * Return the keys of a map as a std::set */ template std::set map_keys_as_set(const std::map& kv) { std::set s; for(auto&& i : kv) { s.insert(i.first); } return s; } /* * Searching through a std::map * @param mapping the map to search * @param key is what to look for * @param null_result is the value to return if key is not in mapping * @return mapping[key] or null_result */ template inline V search_map(const std::map& mapping, const K& key, const V& null_result = V()) { auto i = mapping.find(key); if(i == mapping.end()) return null_result; return i->second; } template inline R search_map(const std::map& mapping, const K& key, const R& null_result, const R& found_result) { auto i = mapping.find(key); if(i == mapping.end()) return null_result; return found_result; } /* * Insert a key/value pair into a multimap */ template void multimap_insert(std::multimap& multimap, const K& key, const V& value) { multimap.insert(std::make_pair(key, value)); } /** * Existence check for values */ template bool value_exists(const std::vector& vec, const T& val) { for(size_t i = 0; i != vec.size(); ++i) if(vec[i] == val) return true; return false; } template void map_remove_if(Pred pred, T& assoc) { auto i = assoc.begin(); while(i != assoc.end()) { if(pred(i->first)) assoc.erase(i++); else i++; } } } namespace Botan { class BOTAN_TEST_API Timer final { public: Timer(const std::string& name, const std::string& provider, const std::string& doing, uint64_t event_mult, size_t buf_size, double clock_cycle_ratio, uint64_t clock_speed) : m_name(name + ((provider.empty() || provider == "base") ? "" : " [" + provider + "]")) , m_doing(doing) , m_buf_size(buf_size) , m_event_mult(event_mult) , m_clock_cycle_ratio(clock_cycle_ratio) , m_clock_speed(clock_speed) {} Timer(const std::string& name) : Timer(name, "", "", 1, 0, 0.0, 0) {} Timer(const std::string& name, size_t buf_size) : Timer(name, "", "", buf_size, buf_size, 0.0, 0) {} Timer(const Timer& other) = default; Timer& operator=(const Timer& other) = default; void start(); void stop(); bool under(std::chrono::milliseconds msec) { return (milliseconds() < msec.count()); } class Timer_Scope final { public: explicit Timer_Scope(Timer& timer) : m_timer(timer) { m_timer.start(); } ~Timer_Scope() { try { m_timer.stop(); } catch(...) {} } private: Timer& m_timer; }; template auto run(F f) -> decltype(f()) { Timer_Scope timer(*this); return f(); } template void run_until_elapsed(std::chrono::milliseconds msec, F f) { while(this->under(msec)) { run(f); } } uint64_t value() const { return m_time_used; } double seconds() const { return milliseconds() / 1000.0; } double milliseconds() const { return value() / 1000000.0; } double ms_per_event() const { return milliseconds() / events(); } uint64_t cycles_consumed() const { if(m_clock_speed != 0) { return static_cast((m_clock_speed * value()) / 1000.0); } return m_cpu_cycles_used; } uint64_t events() const { return m_event_count * m_event_mult; } const std::string& get_name() const { return m_name; } const std::string& doing() const { return m_doing; } size_t buf_size() const { return m_buf_size; } double bytes_per_second() const { return seconds() > 0.0 ? events() / seconds() : 0.0; } double events_per_second() const { return seconds() > 0.0 ? events() / seconds() : 0.0; } double seconds_per_event() const { return events() > 0 ? seconds() / events() : 0.0; } void set_custom_msg(const std::string& s) { m_custom_msg = s; } bool operator<(const Timer& other) const; std::string to_string() const; private: std::string result_string_bps() const; std::string result_string_ops() const; // const data std::string m_name, m_doing; size_t m_buf_size; uint64_t m_event_mult; double m_clock_cycle_ratio; uint64_t m_clock_speed; // set at runtime std::string m_custom_msg; uint64_t m_time_used = 0, m_timer_start = 0; uint64_t m_event_count = 0; uint64_t m_max_time = 0, m_min_time = 0; uint64_t m_cpu_cycles_start = 0, m_cpu_cycles_used = 0; }; } /* * (C) 2013,2015 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include #if defined(BOTAN_HAS_BLOCK_CIPHER) #endif #if defined(BOTAN_HAS_AEAD_CCM) #endif #if defined(BOTAN_HAS_AEAD_CHACHA20_POLY1305) #endif #if defined(BOTAN_HAS_AEAD_EAX) #endif #if defined(BOTAN_HAS_AEAD_GCM) #endif #if defined(BOTAN_HAS_AEAD_OCB) #endif #if defined(BOTAN_HAS_AEAD_SIV) #endif namespace Botan { void AEAD_Mode::set_associated_data_n(size_t i, const uint8_t ad[], size_t ad_len) { if(i == 0) this->set_associated_data(ad, ad_len); else throw Invalid_Argument("AEAD '" + name() + "' does not support multiple associated data"); } std::unique_ptr AEAD_Mode::create_or_throw(const std::string& algo, Cipher_Dir dir, const std::string& provider) { if(auto aead = AEAD_Mode::create(algo, dir, provider)) return aead; throw Lookup_Error("AEAD", algo, provider); } std::unique_ptr AEAD_Mode::create(const std::string& algo, Cipher_Dir dir, const std::string& provider) { BOTAN_UNUSED(provider); #if defined(BOTAN_HAS_AEAD_CHACHA20_POLY1305) if(algo == "ChaCha20Poly1305") { if(dir == ENCRYPTION) return std::unique_ptr(new ChaCha20Poly1305_Encryption); else return std::unique_ptr(new ChaCha20Poly1305_Decryption); } #endif if(algo.find('/') != std::string::npos) { const std::vector algo_parts = split_on(algo, '/'); const std::string cipher_name = algo_parts[0]; const std::vector mode_info = parse_algorithm_name(algo_parts[1]); if(mode_info.empty()) return std::unique_ptr(); std::ostringstream alg_args; alg_args << '(' << cipher_name; for(size_t i = 1; i < mode_info.size(); ++i) alg_args << ',' << mode_info[i]; for(size_t i = 2; i < algo_parts.size(); ++i) alg_args << ',' << algo_parts[i]; alg_args << ')'; const std::string mode_name = mode_info[0] + alg_args.str(); return AEAD_Mode::create(mode_name, dir); } #if defined(BOTAN_HAS_BLOCK_CIPHER) SCAN_Name req(algo); if(req.arg_count() == 0) { return std::unique_ptr(); } std::unique_ptr bc(BlockCipher::create(req.arg(0), provider)); if(!bc) { return std::unique_ptr(); } #if defined(BOTAN_HAS_AEAD_CCM) if(req.algo_name() == "CCM") { size_t tag_len = req.arg_as_integer(1, 16); size_t L_len = req.arg_as_integer(2, 3); if(dir == ENCRYPTION) return std::unique_ptr(new CCM_Encryption(bc.release(), tag_len, L_len)); else return std::unique_ptr(new CCM_Decryption(bc.release(), tag_len, L_len)); } #endif #if defined(BOTAN_HAS_AEAD_GCM) if(req.algo_name() == "GCM") { size_t tag_len = req.arg_as_integer(1, 16); if(dir == ENCRYPTION) return std::unique_ptr(new GCM_Encryption(bc.release(), tag_len)); else return std::unique_ptr(new GCM_Decryption(bc.release(), tag_len)); } #endif #if defined(BOTAN_HAS_AEAD_OCB) if(req.algo_name() == "OCB") { size_t tag_len = req.arg_as_integer(1, 16); if(dir == ENCRYPTION) return std::unique_ptr(new OCB_Encryption(bc.release(), tag_len)); else return std::unique_ptr(new OCB_Decryption(bc.release(), tag_len)); } #endif #if defined(BOTAN_HAS_AEAD_EAX) if(req.algo_name() == "EAX") { size_t tag_len = req.arg_as_integer(1, bc->block_size()); if(dir == ENCRYPTION) return std::unique_ptr(new EAX_Encryption(bc.release(), tag_len)); else return std::unique_ptr(new EAX_Decryption(bc.release(), tag_len)); } #endif #if defined(BOTAN_HAS_AEAD_SIV) if(req.algo_name() == "SIV") { if(dir == ENCRYPTION) return std::unique_ptr(new SIV_Encryption(bc.release())); else return std::unique_ptr(new SIV_Decryption(bc.release())); } #endif #endif return std::unique_ptr(); } } /* * (C) 1999-2010,2015,2017,2018,2020 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { #if defined(BOTAN_HAS_AES_POWER8) || defined(BOTAN_HAS_AES_ARMV8) || defined(BOTAN_HAS_AES_NI) #define BOTAN_HAS_HW_AES_SUPPORT #endif /* * One of three AES implementation strategies are used to get a constant time * implementation which is immune to common cache/timing based side channels: * * - If AES hardware support is available (AES-NI, POWER8, Aarch64) use that * * - If 128-bit SIMD with byte shuffles are available (SSSE3, NEON, or Altivec), * use the vperm technique published by Mike Hamburg at CHES 2009. * * - If no hardware or SIMD support, fall back to a constant time bitsliced * implementation. This uses 32-bit words resulting in 2 blocks being processed * in parallel. Moving to 4 blocks (with 64-bit words) would approximately * double performance on 64-bit CPUs. Likewise moving to 128 bit SIMD would * again approximately double performance vs 64-bit. However the assumption is * that most 64-bit CPUs either have hardware AES or SIMD shuffle support and * that the majority of users falling back to this code will be 32-bit cores. * If this assumption proves to be unsound, the bitsliced code can easily be * extended to operate on either 32 or 64 bit words depending on the native * wordsize of the target processor. * * Useful references * * - "Accelerating AES with Vector Permute Instructions" Mike Hamburg * https://www.shiftleft.org/papers/vector_aes/vector_aes.pdf * * - "Faster and Timing-Attack Resistant AES-GCM" Käsper and Schwabe * https://eprint.iacr.org/2009/129.pdf * * - "A new combinational logic minimization technique with applications to cryptology." * Boyar and Peralta https://eprint.iacr.org/2009/191.pdf * * - "A depth-16 circuit for the AES S-box" Boyar and Peralta * https://eprint.iacr.org/2011/332.pdf * * - "A Very Compact S-box for AES" Canright * https://www.iacr.org/archive/ches2005/032.pdf * https://core.ac.uk/download/pdf/36694529.pdf (extended) */ namespace { /* This is an AES sbox circuit which can execute in bitsliced mode up to 32x in parallel. The circuit is from the "Circuit Minimization Team" group http://www.cs.yale.edu/homes/peralta/CircuitStuff/CMT.html http://www.cs.yale.edu/homes/peralta/CircuitStuff/SLP_AES_113.txt This circuit has size 113 and depth 27. In software it is much faster than circuits which are considered faster for hardware purposes (where circuit depth is the critical constraint), because unlike in hardware, on common CPUs we can only execute - at best - 3 or 4 logic operations per cycle. So a smaller circuit is superior. On an x86-64 machine this circuit is about 15% faster than the circuit of size 128 and depth 16 given in "A depth-16 circuit for the AES S-box". Another circuit for AES Sbox of size 102 and depth 24 is describted in "New Circuit Minimization Techniques for Smaller and Faster AES SBoxes" [https://eprint.iacr.org/2019/802] however it relies on "non-standard" gates like MUX, NOR, NAND, etc and so in practice in bitsliced software, its size is actually a bit larger than this circuit, as few CPUs have such instructions and otherwise they must be emulated using a sequence of available bit operations. */ void AES_SBOX(uint32_t V[8]) { const uint32_t U0 = V[0]; const uint32_t U1 = V[1]; const uint32_t U2 = V[2]; const uint32_t U3 = V[3]; const uint32_t U4 = V[4]; const uint32_t U5 = V[5]; const uint32_t U6 = V[6]; const uint32_t U7 = V[7]; const uint32_t y14 = U3 ^ U5; const uint32_t y13 = U0 ^ U6; const uint32_t y9 = U0 ^ U3; const uint32_t y8 = U0 ^ U5; const uint32_t t0 = U1 ^ U2; const uint32_t y1 = t0 ^ U7; const uint32_t y4 = y1 ^ U3; const uint32_t y12 = y13 ^ y14; const uint32_t y2 = y1 ^ U0; const uint32_t y5 = y1 ^ U6; const uint32_t y3 = y5 ^ y8; const uint32_t t1 = U4 ^ y12; const uint32_t y15 = t1 ^ U5; const uint32_t y20 = t1 ^ U1; const uint32_t y6 = y15 ^ U7; const uint32_t y10 = y15 ^ t0; const uint32_t y11 = y20 ^ y9; const uint32_t y7 = U7 ^ y11; const uint32_t y17 = y10 ^ y11; const uint32_t y19 = y10 ^ y8; const uint32_t y16 = t0 ^ y11; const uint32_t y21 = y13 ^ y16; const uint32_t y18 = U0 ^ y16; const uint32_t t2 = y12 & y15; const uint32_t t3 = y3 & y6; const uint32_t t4 = t3 ^ t2; const uint32_t t5 = y4 & U7; const uint32_t t6 = t5 ^ t2; const uint32_t t7 = y13 & y16; const uint32_t t8 = y5 & y1; const uint32_t t9 = t8 ^ t7; const uint32_t t10 = y2 & y7; const uint32_t t11 = t10 ^ t7; const uint32_t t12 = y9 & y11; const uint32_t t13 = y14 & y17; const uint32_t t14 = t13 ^ t12; const uint32_t t15 = y8 & y10; const uint32_t t16 = t15 ^ t12; const uint32_t t17 = t4 ^ y20; const uint32_t t18 = t6 ^ t16; const uint32_t t19 = t9 ^ t14; const uint32_t t20 = t11 ^ t16; const uint32_t t21 = t17 ^ t14; const uint32_t t22 = t18 ^ y19; const uint32_t t23 = t19 ^ y21; const uint32_t t24 = t20 ^ y18; const uint32_t t25 = t21 ^ t22; const uint32_t t26 = t21 & t23; const uint32_t t27 = t24 ^ t26; const uint32_t t28 = t25 & t27; const uint32_t t29 = t28 ^ t22; const uint32_t t30 = t23 ^ t24; const uint32_t t31 = t22 ^ t26; const uint32_t t32 = t31 & t30; const uint32_t t33 = t32 ^ t24; const uint32_t t34 = t23 ^ t33; const uint32_t t35 = t27 ^ t33; const uint32_t t36 = t24 & t35; const uint32_t t37 = t36 ^ t34; const uint32_t t38 = t27 ^ t36; const uint32_t t39 = t29 & t38; const uint32_t t40 = t25 ^ t39; const uint32_t t41 = t40 ^ t37; const uint32_t t42 = t29 ^ t33; const uint32_t t43 = t29 ^ t40; const uint32_t t44 = t33 ^ t37; const uint32_t t45 = t42 ^ t41; const uint32_t z0 = t44 & y15; const uint32_t z1 = t37 & y6; const uint32_t z2 = t33 & U7; const uint32_t z3 = t43 & y16; const uint32_t z4 = t40 & y1; const uint32_t z5 = t29 & y7; const uint32_t z6 = t42 & y11; const uint32_t z7 = t45 & y17; const uint32_t z8 = t41 & y10; const uint32_t z9 = t44 & y12; const uint32_t z10 = t37 & y3; const uint32_t z11 = t33 & y4; const uint32_t z12 = t43 & y13; const uint32_t z13 = t40 & y5; const uint32_t z14 = t29 & y2; const uint32_t z15 = t42 & y9; const uint32_t z16 = t45 & y14; const uint32_t z17 = t41 & y8; const uint32_t tc1 = z15 ^ z16; const uint32_t tc2 = z10 ^ tc1; const uint32_t tc3 = z9 ^ tc2; const uint32_t tc4 = z0 ^ z2; const uint32_t tc5 = z1 ^ z0; const uint32_t tc6 = z3 ^ z4; const uint32_t tc7 = z12 ^ tc4; const uint32_t tc8 = z7 ^ tc6; const uint32_t tc9 = z8 ^ tc7; const uint32_t tc10 = tc8 ^ tc9; const uint32_t tc11 = tc6 ^ tc5; const uint32_t tc12 = z3 ^ z5; const uint32_t tc13 = z13 ^ tc1; const uint32_t tc14 = tc4 ^ tc12; const uint32_t S3 = tc3 ^ tc11; const uint32_t tc16 = z6 ^ tc8; const uint32_t tc17 = z14 ^ tc10; const uint32_t tc18 = ~tc13 ^ tc14; const uint32_t S7 = z12 ^ tc18; const uint32_t tc20 = z15 ^ tc16; const uint32_t tc21 = tc2 ^ z11; const uint32_t S0 = tc3 ^ tc16; const uint32_t S6 = tc10 ^ tc18; const uint32_t S4 = tc14 ^ S3; const uint32_t S1 = ~(S3 ^ tc16); const uint32_t tc26 = tc17 ^ tc20; const uint32_t S2 = ~(tc26 ^ z17); const uint32_t S5 = tc21 ^ tc17; V[0] = S0; V[1] = S1; V[2] = S2; V[3] = S3; V[4] = S4; V[5] = S5; V[6] = S6; V[7] = S7; } /* A circuit for inverse AES Sbox of size 121 and depth 21 from http://www.cs.yale.edu/homes/peralta/CircuitStuff/CMT.html http://www.cs.yale.edu/homes/peralta/CircuitStuff/Sinv.txt */ void AES_INV_SBOX(uint32_t V[8]) { const uint32_t U0 = V[0]; const uint32_t U1 = V[1]; const uint32_t U2 = V[2]; const uint32_t U3 = V[3]; const uint32_t U4 = V[4]; const uint32_t U5 = V[5]; const uint32_t U6 = V[6]; const uint32_t U7 = V[7]; const uint32_t Y0 = U0 ^ U3; const uint32_t Y2 = ~(U1 ^ U3); const uint32_t Y4 = U0 ^ Y2; const uint32_t RTL0 = U6 ^ U7; const uint32_t Y1 = Y2 ^ RTL0; const uint32_t Y7 = ~(U2 ^ Y1); const uint32_t RTL1 = U3 ^ U4; const uint32_t Y6 = ~(U7 ^ RTL1); const uint32_t Y3 = Y1 ^ RTL1; const uint32_t RTL2 = ~(U0 ^ U2); const uint32_t Y5 = U5 ^ RTL2; const uint32_t sa1 = Y0 ^ Y2; const uint32_t sa0 = Y1 ^ Y3; const uint32_t sb1 = Y4 ^ Y6; const uint32_t sb0 = Y5 ^ Y7; const uint32_t ah = Y0 ^ Y1; const uint32_t al = Y2 ^ Y3; const uint32_t aa = sa0 ^ sa1; const uint32_t bh = Y4 ^ Y5; const uint32_t bl = Y6 ^ Y7; const uint32_t bb = sb0 ^ sb1; const uint32_t ab20 = sa0 ^ sb0; const uint32_t ab22 = al ^ bl; const uint32_t ab23 = Y3 ^ Y7; const uint32_t ab21 = sa1 ^ sb1; const uint32_t abcd1 = ah & bh; const uint32_t rr1 = Y0 & Y4; const uint32_t ph11 = ab20 ^ abcd1; const uint32_t t01 = Y1 & Y5; const uint32_t ph01 = t01 ^ abcd1; const uint32_t abcd2 = al & bl; const uint32_t r1 = Y2 & Y6; const uint32_t pl11 = ab22 ^ abcd2; const uint32_t r2 = Y3 & Y7; const uint32_t pl01 = r2 ^ abcd2; const uint32_t r3 = sa0 & sb0; const uint32_t vr1 = aa & bb; const uint32_t pr1 = vr1 ^ r3; const uint32_t wr1 = sa1 & sb1; const uint32_t qr1 = wr1 ^ r3; const uint32_t ab0 = ph11 ^ rr1; const uint32_t ab1 = ph01 ^ ab21; const uint32_t ab2 = pl11 ^ r1; const uint32_t ab3 = pl01 ^ qr1; const uint32_t cp1 = ab0 ^ pr1; const uint32_t cp2 = ab1 ^ qr1; const uint32_t cp3 = ab2 ^ pr1; const uint32_t cp4 = ab3 ^ ab23; const uint32_t tinv1 = cp3 ^ cp4; const uint32_t tinv2 = cp3 & cp1; const uint32_t tinv3 = cp2 ^ tinv2; const uint32_t tinv4 = cp1 ^ cp2; const uint32_t tinv5 = cp4 ^ tinv2; const uint32_t tinv6 = tinv5 & tinv4; const uint32_t tinv7 = tinv3 & tinv1; const uint32_t d2 = cp4 ^ tinv7; const uint32_t d0 = cp2 ^ tinv6; const uint32_t tinv8 = cp1 & cp4; const uint32_t tinv9 = tinv4 & tinv8; const uint32_t tinv10 = tinv4 ^ tinv2; const uint32_t d1 = tinv9 ^ tinv10; const uint32_t tinv11 = cp2 & cp3; const uint32_t tinv12 = tinv1 & tinv11; const uint32_t tinv13 = tinv1 ^ tinv2; const uint32_t d3 = tinv12 ^ tinv13; const uint32_t sd1 = d1 ^ d3; const uint32_t sd0 = d0 ^ d2; const uint32_t dl = d0 ^ d1; const uint32_t dh = d2 ^ d3; const uint32_t dd = sd0 ^ sd1; const uint32_t abcd3 = dh & bh; const uint32_t rr2 = d3 & Y4; const uint32_t t02 = d2 & Y5; const uint32_t abcd4 = dl & bl; const uint32_t r4 = d1 & Y6; const uint32_t r5 = d0 & Y7; const uint32_t r6 = sd0 & sb0; const uint32_t vr2 = dd & bb; const uint32_t wr2 = sd1 & sb1; const uint32_t abcd5 = dh & ah; const uint32_t r7 = d3 & Y0; const uint32_t r8 = d2 & Y1; const uint32_t abcd6 = dl & al; const uint32_t r9 = d1 & Y2; const uint32_t r10 = d0 & Y3; const uint32_t r11 = sd0 & sa0; const uint32_t vr3 = dd & aa; const uint32_t wr3 = sd1 & sa1; const uint32_t ph12 = rr2 ^ abcd3; const uint32_t ph02 = t02 ^ abcd3; const uint32_t pl12 = r4 ^ abcd4; const uint32_t pl02 = r5 ^ abcd4; const uint32_t pr2 = vr2 ^ r6; const uint32_t qr2 = wr2 ^ r6; const uint32_t p0 = ph12 ^ pr2; const uint32_t p1 = ph02 ^ qr2; const uint32_t p2 = pl12 ^ pr2; const uint32_t p3 = pl02 ^ qr2; const uint32_t ph13 = r7 ^ abcd5; const uint32_t ph03 = r8 ^ abcd5; const uint32_t pl13 = r9 ^ abcd6; const uint32_t pl03 = r10 ^ abcd6; const uint32_t pr3 = vr3 ^ r11; const uint32_t qr3 = wr3 ^ r11; const uint32_t p4 = ph13 ^ pr3; const uint32_t S7 = ph03 ^ qr3; const uint32_t p6 = pl13 ^ pr3; const uint32_t p7 = pl03 ^ qr3; const uint32_t S3 = p1 ^ p6; const uint32_t S6 = p2 ^ p6; const uint32_t S0 = p3 ^ p6; const uint32_t X11 = p0 ^ p2; const uint32_t S5 = S0 ^ X11; const uint32_t X13 = p4 ^ p7; const uint32_t X14 = X11 ^ X13; const uint32_t S1 = S3 ^ X14; const uint32_t X16 = p1 ^ S7; const uint32_t S2 = X14 ^ X16; const uint32_t X18 = p0 ^ p4; const uint32_t X19 = S5 ^ X16; const uint32_t S4 = X18 ^ X19; V[0] = S0; V[1] = S1; V[2] = S2; V[3] = S3; V[4] = S4; V[5] = S5; V[6] = S6; V[7] = S7; } inline void bit_transpose(uint32_t B[8]) { swap_bits(B[1], B[0], 0x55555555, 1); swap_bits(B[3], B[2], 0x55555555, 1); swap_bits(B[5], B[4], 0x55555555, 1); swap_bits(B[7], B[6], 0x55555555, 1); swap_bits(B[2], B[0], 0x33333333, 2); swap_bits(B[3], B[1], 0x33333333, 2); swap_bits(B[6], B[4], 0x33333333, 2); swap_bits(B[7], B[5], 0x33333333, 2); swap_bits(B[4], B[0], 0x0F0F0F0F, 4); swap_bits(B[5], B[1], 0x0F0F0F0F, 4); swap_bits(B[6], B[2], 0x0F0F0F0F, 4); swap_bits(B[7], B[3], 0x0F0F0F0F, 4); } inline void ks_expand(uint32_t B[8], const uint32_t K[], size_t r) { /* This is bit_transpose of K[r..r+4] || K[r..r+4], we can save some computation due to knowing the first and second halves are the same data. */ for(size_t i = 0; i != 4; ++i) B[i] = K[r + i]; swap_bits(B[1], B[0], 0x55555555, 1); swap_bits(B[3], B[2], 0x55555555, 1); swap_bits(B[2], B[0], 0x33333333, 2); swap_bits(B[3], B[1], 0x33333333, 2); B[4] = B[0]; B[5] = B[1]; B[6] = B[2]; B[7] = B[3]; swap_bits(B[4], B[0], 0x0F0F0F0F, 4); swap_bits(B[5], B[1], 0x0F0F0F0F, 4); swap_bits(B[6], B[2], 0x0F0F0F0F, 4); swap_bits(B[7], B[3], 0x0F0F0F0F, 4); } inline void shift_rows(uint32_t B[8]) { // 3 0 1 2 7 4 5 6 10 11 8 9 14 15 12 13 17 18 19 16 21 22 23 20 24 25 26 27 28 29 30 31 #if defined(BOTAN_TARGET_CPU_HAS_NATIVE_64BIT) for(size_t i = 0; i != 8; i += 2) { uint64_t x = (static_cast(B[i]) << 32) | B[i+1]; x = bit_permute_step(x, 0x0022331100223311, 2); x = bit_permute_step(x, 0x0055005500550055, 1); B[i] = static_cast(x >> 32); B[i+1] = static_cast(x); } #else for(size_t i = 0; i != 8; ++i) { uint32_t x = B[i]; x = bit_permute_step(x, 0x00223311, 2); x = bit_permute_step(x, 0x00550055, 1); B[i] = x; } #endif } inline void inv_shift_rows(uint32_t B[8]) { // Inverse of shift_rows, just inverting the steps #if defined(BOTAN_TARGET_CPU_HAS_NATIVE_64BIT) for(size_t i = 0; i != 8; i += 2) { uint64_t x = (static_cast(B[i]) << 32) | B[i+1]; x = bit_permute_step(x, 0x0055005500550055, 1); x = bit_permute_step(x, 0x0022331100223311, 2); B[i] = static_cast(x >> 32); B[i+1] = static_cast(x); } #else for(size_t i = 0; i != 8; ++i) { uint32_t x = B[i]; x = bit_permute_step(x, 0x00550055, 1); x = bit_permute_step(x, 0x00223311, 2); B[i] = x; } #endif } inline void mix_columns(uint32_t B[8]) { // carry high bits in B[0] to positions in 0x1b == 0b11011 const uint32_t X2[8] = { B[1], B[2], B[3], B[4] ^ B[0], B[5] ^ B[0], B[6], B[7] ^ B[0], B[0], }; for(size_t i = 0; i != 8; i++) { const uint32_t X3 = B[i] ^ X2[i]; B[i] = X2[i] ^ rotr<8>(B[i]) ^ rotr<16>(B[i]) ^ rotr<24>(X3); } } void inv_mix_columns(uint32_t B[8]) { /* OpenSSL's bsaes implementation credits Jussi Kivilinna with the lovely matrix decomposition | 0e 0b 0d 09 | | 02 03 01 01 | | 05 00 04 00 | | 09 0e 0b 0d | = | 01 02 03 01 | x | 00 05 00 04 | | 0d 09 0e 0b | | 01 01 02 03 | | 04 00 05 00 | | 0b 0d 09 0e | | 03 01 01 02 | | 00 04 00 05 | Notice the first component is simply the MixColumns matrix. So we can multiply first by (05,00,04,00) then perform MixColumns to get the equivalent of InvMixColumn. */ const uint32_t X4[8] = { B[2], B[3], B[4] ^ B[0], B[5] ^ B[0] ^ B[1], B[6] ^ B[1], B[7] ^ B[0], B[0] ^ B[1], B[1], }; for(size_t i = 0; i != 8; i++) { const uint32_t X5 = X4[i] ^ B[i]; B[i] = X5 ^ rotr<16>(X4[i]); } mix_columns(B); } /* * AES Encryption */ void aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks, const secure_vector& EK) { BOTAN_ASSERT(EK.size() == 44 || EK.size() == 52 || EK.size() == 60, "Key was set"); const size_t rounds = (EK.size() - 4) / 4; uint32_t KS[13*8] = { 0 }; // actual maximum is (rounds - 1) * 8 for(size_t i = 0; i < rounds - 1; i += 1) { ks_expand(&KS[8*i], EK.data(), 4*i + 4); } const size_t BLOCK_SIZE = 16; const size_t BITSLICED_BLOCKS = 8*sizeof(uint32_t) / BLOCK_SIZE; while(blocks > 0) { const size_t this_loop = std::min(blocks, BITSLICED_BLOCKS); uint32_t B[8] = { 0 }; load_be(B, in, this_loop*4); for(size_t i = 0; i != 8; ++i) B[i] ^= EK[i % 4]; bit_transpose(B); for(size_t r = 0; r != rounds - 1; ++r) { AES_SBOX(B); shift_rows(B); mix_columns(B); for(size_t i = 0; i != 8; ++i) B[i] ^= KS[8*r + i]; } // Final round: AES_SBOX(B); shift_rows(B); bit_transpose(B); for(size_t i = 0; i != 8; ++i) B[i] ^= EK[4*rounds + i % 4]; copy_out_be(out, this_loop*4*sizeof(uint32_t), B); in += this_loop * BLOCK_SIZE; out += this_loop * BLOCK_SIZE; blocks -= this_loop; } } /* * AES Decryption */ void aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks, const secure_vector& DK) { BOTAN_ASSERT(DK.size() == 44 || DK.size() == 52 || DK.size() == 60, "Key was set"); const size_t rounds = (DK.size() - 4) / 4; uint32_t KS[13*8] = { 0 }; // actual maximum is (rounds - 1) * 8 for(size_t i = 0; i < rounds - 1; i += 1) { ks_expand(&KS[8*i], DK.data(), 4*i + 4); } const size_t BLOCK_SIZE = 16; const size_t BITSLICED_BLOCKS = 8*sizeof(uint32_t) / BLOCK_SIZE; while(blocks > 0) { const size_t this_loop = std::min(blocks, BITSLICED_BLOCKS); uint32_t B[8] = { 0 }; load_be(B, in, this_loop*4); for(size_t i = 0; i != 8; ++i) B[i] ^= DK[i % 4]; bit_transpose(B); for(size_t r = 0; r != rounds - 1; ++r) { AES_INV_SBOX(B); inv_shift_rows(B); inv_mix_columns(B); for(size_t i = 0; i != 8; ++i) B[i] ^= KS[8*r + i]; } // Final round: AES_INV_SBOX(B); inv_shift_rows(B); bit_transpose(B); for(size_t i = 0; i != 8; ++i) B[i] ^= DK[4*rounds + i % 4]; copy_out_be(out, this_loop*4*sizeof(uint32_t), B); in += this_loop * BLOCK_SIZE; out += this_loop * BLOCK_SIZE; blocks -= this_loop; } } inline uint32_t xtime32(uint32_t s) { const uint32_t lo_bit = 0x01010101; const uint32_t mask = 0x7F7F7F7F; const uint32_t poly = 0x1B; return ((s & mask) << 1) ^ (((s >> 7) & lo_bit) * poly); } inline uint32_t InvMixColumn(uint32_t s1) { const uint32_t s2 = xtime32(s1); const uint32_t s4 = xtime32(s2); const uint32_t s8 = xtime32(s4); const uint32_t s9 = s8 ^ s1; const uint32_t s11 = s9 ^ s2; const uint32_t s13 = s9 ^ s4; const uint32_t s14 = s8 ^ s4 ^ s2; return s14 ^ rotr<8>(s9) ^ rotr<16>(s13) ^ rotr<24>(s11); } void InvMixColumn_x4(uint32_t x[4]) { x[0] = InvMixColumn(x[0]); x[1] = InvMixColumn(x[1]); x[2] = InvMixColumn(x[2]); x[3] = InvMixColumn(x[3]); } uint32_t SE_word(uint32_t x) { uint32_t I[8] = { 0 }; for(size_t i = 0; i != 8; ++i) I[i] = (x >> (7-i)) & 0x01010101; AES_SBOX(I); x = 0; for(size_t i = 0; i != 8; ++i) x |= ((I[i] & 0x01010101) << (7-i)); return x; } void aes_key_schedule(const uint8_t key[], size_t length, secure_vector& EK, secure_vector& DK, bool bswap_keys = false) { static const uint32_t RC[10] = { 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000 }; const size_t X = length / 4; // Can't happen, but make static analyzers happy BOTAN_ASSERT_NOMSG(X == 4 || X == 6 || X == 8); const size_t rounds = (length / 4) + 6; // Help the optimizer BOTAN_ASSERT_NOMSG(rounds == 10 || rounds == 12 || rounds == 14); CT::poison(key, length); EK.resize(length + 28); DK.resize(length + 28); for(size_t i = 0; i != X; ++i) EK[i] = load_be(key, i); for(size_t i = X; i < 4*(rounds+1); i += X) { EK[i] = EK[i-X] ^ RC[(i-X)/X] ^ rotl<8>(SE_word(EK[i-1])); for(size_t j = 1; j != X && (i+j) < EK.size(); ++j) { EK[i+j] = EK[i+j-X]; if(X == 8 && j == 4) EK[i+j] ^= SE_word(EK[i+j-1]); else EK[i+j] ^= EK[i+j-1]; } } for(size_t i = 0; i != 4*(rounds+1); i += 4) { DK[i ] = EK[4*rounds - i ]; DK[i+1] = EK[4*rounds - i+1]; DK[i+2] = EK[4*rounds - i+2]; DK[i+3] = EK[4*rounds - i+3]; } for(size_t i = 4; i != 4*rounds; i += 4) { InvMixColumn_x4(&DK[i]); } if(bswap_keys) { // HW AES on little endian needs the subkeys to be byte reversed for(size_t i = 0; i != EK.size(); ++i) EK[i] = reverse_bytes(EK[i]); for(size_t i = 0; i != DK.size(); ++i) DK[i] = reverse_bytes(DK[i]); } CT::unpoison(EK.data(), EK.size()); CT::unpoison(DK.data(), DK.size()); CT::unpoison(key, length); } size_t aes_parallelism() { #if defined(BOTAN_HAS_HW_AES_SUPPORT) if(CPUID::has_hw_aes()) { return 4; // pipelined } #endif #if defined(BOTAN_HAS_AES_VPERM) if(CPUID::has_vperm()) { return 2; // pipelined } #endif // bitsliced: return 2; } const char* aes_provider() { #if defined(BOTAN_HAS_HW_AES_SUPPORT) if(CPUID::has_hw_aes()) { return "cpu"; } #endif #if defined(BOTAN_HAS_AES_VPERM) if(CPUID::has_vperm()) { return "vperm"; } #endif return "base"; } } std::string AES_128::provider() const { return aes_provider(); } std::string AES_192::provider() const { return aes_provider(); } std::string AES_256::provider() const { return aes_provider(); } size_t AES_128::parallelism() const { return aes_parallelism(); } size_t AES_192::parallelism() const { return aes_parallelism(); } size_t AES_256::parallelism() const { return aes_parallelism(); } void AES_128::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { verify_key_set(m_EK.empty() == false); #if defined(BOTAN_HAS_HW_AES_SUPPORT) if(CPUID::has_hw_aes()) { return hw_aes_encrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_AES_VPERM) if(CPUID::has_vperm()) { return vperm_encrypt_n(in, out, blocks); } #endif aes_encrypt_n(in, out, blocks, m_EK); } void AES_128::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { verify_key_set(m_DK.empty() == false); #if defined(BOTAN_HAS_HW_AES_SUPPORT) if(CPUID::has_hw_aes()) { return hw_aes_decrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_AES_VPERM) if(CPUID::has_vperm()) { return vperm_decrypt_n(in, out, blocks); } #endif aes_decrypt_n(in, out, blocks, m_DK); } void AES_128::key_schedule(const uint8_t key[], size_t length) { #if defined(BOTAN_HAS_AES_NI) if(CPUID::has_aes_ni()) { return aesni_key_schedule(key, length); } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) if(CPUID::has_hw_aes()) { return aes_key_schedule(key, length, m_EK, m_DK, CPUID::is_little_endian()); } #endif #if defined(BOTAN_HAS_AES_VPERM) if(CPUID::has_vperm()) { return vperm_key_schedule(key, length); } #endif aes_key_schedule(key, length, m_EK, m_DK); } void AES_128::clear() { zap(m_EK); zap(m_DK); } void AES_192::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { verify_key_set(m_EK.empty() == false); #if defined(BOTAN_HAS_HW_AES_SUPPORT) if(CPUID::has_hw_aes()) { return hw_aes_encrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_AES_VPERM) if(CPUID::has_vperm()) { return vperm_encrypt_n(in, out, blocks); } #endif aes_encrypt_n(in, out, blocks, m_EK); } void AES_192::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { verify_key_set(m_DK.empty() == false); #if defined(BOTAN_HAS_HW_AES_SUPPORT) if(CPUID::has_hw_aes()) { return hw_aes_decrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_AES_VPERM) if(CPUID::has_vperm()) { return vperm_decrypt_n(in, out, blocks); } #endif aes_decrypt_n(in, out, blocks, m_DK); } void AES_192::key_schedule(const uint8_t key[], size_t length) { #if defined(BOTAN_HAS_AES_NI) if(CPUID::has_aes_ni()) { return aesni_key_schedule(key, length); } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) if(CPUID::has_hw_aes()) { return aes_key_schedule(key, length, m_EK, m_DK, CPUID::is_little_endian()); } #endif #if defined(BOTAN_HAS_AES_VPERM) if(CPUID::has_vperm()) { return vperm_key_schedule(key, length); } #endif aes_key_schedule(key, length, m_EK, m_DK); } void AES_192::clear() { zap(m_EK); zap(m_DK); } void AES_256::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { verify_key_set(m_EK.empty() == false); #if defined(BOTAN_HAS_HW_AES_SUPPORT) if(CPUID::has_hw_aes()) { return hw_aes_encrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_AES_VPERM) if(CPUID::has_vperm()) { return vperm_encrypt_n(in, out, blocks); } #endif aes_encrypt_n(in, out, blocks, m_EK); } void AES_256::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { verify_key_set(m_DK.empty() == false); #if defined(BOTAN_HAS_HW_AES_SUPPORT) if(CPUID::has_hw_aes()) { return hw_aes_decrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_AES_VPERM) if(CPUID::has_vperm()) { return vperm_decrypt_n(in, out, blocks); } #endif aes_decrypt_n(in, out, blocks, m_DK); } void AES_256::key_schedule(const uint8_t key[], size_t length) { #if defined(BOTAN_HAS_AES_NI) if(CPUID::has_aes_ni()) { return aesni_key_schedule(key, length); } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) if(CPUID::has_hw_aes()) { return aes_key_schedule(key, length, m_EK, m_DK, CPUID::is_little_endian()); } #endif #if defined(BOTAN_HAS_AES_VPERM) if(CPUID::has_vperm()) { return vperm_key_schedule(key, length); } #endif aes_key_schedule(key, length, m_EK, m_DK); } void AES_256::clear() { zap(m_EK); zap(m_DK); } } /* * (C) 2019 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { void Buffered_Computation::update_be(uint16_t val) { uint8_t inb[sizeof(val)]; store_be(val, inb); add_data(inb, sizeof(inb)); } void Buffered_Computation::update_be(uint32_t val) { uint8_t inb[sizeof(val)]; store_be(val, inb); add_data(inb, sizeof(inb)); } void Buffered_Computation::update_be(uint64_t val) { uint8_t inb[sizeof(val)]; store_be(val, inb); add_data(inb, sizeof(inb)); } void Buffered_Computation::update_le(uint16_t val) { uint8_t inb[sizeof(val)]; store_le(val, inb); add_data(inb, sizeof(inb)); } void Buffered_Computation::update_le(uint32_t val) { uint8_t inb[sizeof(val)]; store_le(val, inb); add_data(inb, sizeof(inb)); } void Buffered_Computation::update_le(uint64_t val) { uint8_t inb[sizeof(val)]; store_le(val, inb); add_data(inb, sizeof(inb)); } } /* * SCAN Name Abstraction * (C) 2008-2009,2015 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { namespace { std::string make_arg(const std::vector>& name, size_t start) { std::string output = name[start].second; size_t level = name[start].first; size_t paren_depth = 0; for(size_t i = start + 1; i != name.size(); ++i) { if(name[i].first <= name[start].first) break; if(name[i].first > level) { output += "(" + name[i].second; ++paren_depth; } else if(name[i].first < level) { for (size_t j = name[i].first; j < level; j++) { output += ")"; --paren_depth; } output += "," + name[i].second; } else { if(output[output.size() - 1] != '(') output += ","; output += name[i].second; } level = name[i].first; } for(size_t i = 0; i != paren_depth; ++i) output += ")"; return output; } } SCAN_Name::SCAN_Name(const char* algo_spec) : SCAN_Name(std::string(algo_spec)) { } SCAN_Name::SCAN_Name(std::string algo_spec) : m_orig_algo_spec(algo_spec), m_alg_name(), m_args(), m_mode_info() { if(algo_spec.size() == 0) throw Invalid_Argument("Expected algorithm name, got empty string"); std::vector> name; size_t level = 0; std::pair accum = std::make_pair(level, ""); const std::string decoding_error = "Bad SCAN name '" + algo_spec + "': "; for(size_t i = 0; i != algo_spec.size(); ++i) { char c = algo_spec[i]; if(c == '/' || c == ',' || c == '(' || c == ')') { if(c == '(') ++level; else if(c == ')') { if(level == 0) throw Decoding_Error(decoding_error + "Mismatched parens"); --level; } if(c == '/' && level > 0) accum.second.push_back(c); else { if(accum.second != "") name.push_back(accum); accum = std::make_pair(level, ""); } } else accum.second.push_back(c); } if(accum.second != "") name.push_back(accum); if(level != 0) throw Decoding_Error(decoding_error + "Missing close paren"); if(name.size() == 0) throw Decoding_Error(decoding_error + "Empty name"); m_alg_name = name[0].second; bool in_modes = false; for(size_t i = 1; i != name.size(); ++i) { if(name[i].first == 0) { m_mode_info.push_back(make_arg(name, i)); in_modes = true; } else if(name[i].first == 1 && !in_modes) m_args.push_back(make_arg(name, i)); } } std::string SCAN_Name::arg(size_t i) const { if(i >= arg_count()) throw Invalid_Argument("SCAN_Name::arg " + std::to_string(i) + " out of range for '" + to_string() + "'"); return m_args[i]; } std::string SCAN_Name::arg(size_t i, const std::string& def_value) const { if(i >= arg_count()) return def_value; return m_args[i]; } size_t SCAN_Name::arg_as_integer(size_t i, size_t def_value) const { if(i >= arg_count()) return def_value; return to_u32bit(m_args[i]); } } /* * (C) 2018 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { void SymmetricAlgorithm::throw_key_not_set_error() const { throw Key_Not_Set(name()); } void SymmetricAlgorithm::set_key(const uint8_t key[], size_t length) { if(!valid_keylength(length)) throw Invalid_Key_Length(name(), length); key_schedule(key, length); } } /* * OctetString * (C) 1999-2007 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include namespace Botan { /* * Create an OctetString from RNG output */ OctetString::OctetString(RandomNumberGenerator& rng, size_t len) { rng.random_vec(m_data, len); } /* * Create an OctetString from a hex string */ OctetString::OctetString(const std::string& hex_string) { if(!hex_string.empty()) { m_data.resize(1 + hex_string.length() / 2); m_data.resize(hex_decode(m_data.data(), hex_string)); } } /* * Create an OctetString from a byte string */ OctetString::OctetString(const uint8_t in[], size_t n) { m_data.assign(in, in + n); } /* * Set the parity of each key byte to odd */ void OctetString::set_odd_parity() { const uint8_t ODD_PARITY[256] = { 0x01, 0x01, 0x02, 0x02, 0x04, 0x04, 0x07, 0x07, 0x08, 0x08, 0x0B, 0x0B, 0x0D, 0x0D, 0x0E, 0x0E, 0x10, 0x10, 0x13, 0x13, 0x15, 0x15, 0x16, 0x16, 0x19, 0x19, 0x1A, 0x1A, 0x1C, 0x1C, 0x1F, 0x1F, 0x20, 0x20, 0x23, 0x23, 0x25, 0x25, 0x26, 0x26, 0x29, 0x29, 0x2A, 0x2A, 0x2C, 0x2C, 0x2F, 0x2F, 0x31, 0x31, 0x32, 0x32, 0x34, 0x34, 0x37, 0x37, 0x38, 0x38, 0x3B, 0x3B, 0x3D, 0x3D, 0x3E, 0x3E, 0x40, 0x40, 0x43, 0x43, 0x45, 0x45, 0x46, 0x46, 0x49, 0x49, 0x4A, 0x4A, 0x4C, 0x4C, 0x4F, 0x4F, 0x51, 0x51, 0x52, 0x52, 0x54, 0x54, 0x57, 0x57, 0x58, 0x58, 0x5B, 0x5B, 0x5D, 0x5D, 0x5E, 0x5E, 0x61, 0x61, 0x62, 0x62, 0x64, 0x64, 0x67, 0x67, 0x68, 0x68, 0x6B, 0x6B, 0x6D, 0x6D, 0x6E, 0x6E, 0x70, 0x70, 0x73, 0x73, 0x75, 0x75, 0x76, 0x76, 0x79, 0x79, 0x7A, 0x7A, 0x7C, 0x7C, 0x7F, 0x7F, 0x80, 0x80, 0x83, 0x83, 0x85, 0x85, 0x86, 0x86, 0x89, 0x89, 0x8A, 0x8A, 0x8C, 0x8C, 0x8F, 0x8F, 0x91, 0x91, 0x92, 0x92, 0x94, 0x94, 0x97, 0x97, 0x98, 0x98, 0x9B, 0x9B, 0x9D, 0x9D, 0x9E, 0x9E, 0xA1, 0xA1, 0xA2, 0xA2, 0xA4, 0xA4, 0xA7, 0xA7, 0xA8, 0xA8, 0xAB, 0xAB, 0xAD, 0xAD, 0xAE, 0xAE, 0xB0, 0xB0, 0xB3, 0xB3, 0xB5, 0xB5, 0xB6, 0xB6, 0xB9, 0xB9, 0xBA, 0xBA, 0xBC, 0xBC, 0xBF, 0xBF, 0xC1, 0xC1, 0xC2, 0xC2, 0xC4, 0xC4, 0xC7, 0xC7, 0xC8, 0xC8, 0xCB, 0xCB, 0xCD, 0xCD, 0xCE, 0xCE, 0xD0, 0xD0, 0xD3, 0xD3, 0xD5, 0xD5, 0xD6, 0xD6, 0xD9, 0xD9, 0xDA, 0xDA, 0xDC, 0xDC, 0xDF, 0xDF, 0xE0, 0xE0, 0xE3, 0xE3, 0xE5, 0xE5, 0xE6, 0xE6, 0xE9, 0xE9, 0xEA, 0xEA, 0xEC, 0xEC, 0xEF, 0xEF, 0xF1, 0xF1, 0xF2, 0xF2, 0xF4, 0xF4, 0xF7, 0xF7, 0xF8, 0xF8, 0xFB, 0xFB, 0xFD, 0xFD, 0xFE, 0xFE }; for(size_t j = 0; j != m_data.size(); ++j) m_data[j] = ODD_PARITY[m_data[j]]; } /* * Hex encode an OctetString */ std::string OctetString::to_string() const { return hex_encode(m_data.data(), m_data.size()); } /* * XOR Operation for OctetStrings */ OctetString& OctetString::operator^=(const OctetString& k) { if(&k == this) { zeroise(m_data); return (*this); } xor_buf(m_data.data(), k.begin(), std::min(length(), k.length())); return (*this); } /* * Equality Operation for OctetStrings */ bool operator==(const OctetString& s1, const OctetString& s2) { return (s1.bits_of() == s2.bits_of()); } /* * Unequality Operation for OctetStrings */ bool operator!=(const OctetString& s1, const OctetString& s2) { return !(s1 == s2); } /* * Append Operation for OctetStrings */ OctetString operator+(const OctetString& k1, const OctetString& k2) { secure_vector out; out += k1.bits_of(); out += k2.bits_of(); return OctetString(out); } /* * XOR Operation for OctetStrings */ OctetString operator^(const OctetString& k1, const OctetString& k2) { secure_vector out(std::max(k1.length(), k2.length())); copy_mem(out.data(), k1.begin(), k1.length()); xor_buf(out.data(), k2.begin(), k2.length()); return OctetString(out); } } /* * Block Ciphers * (C) 2015 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #if defined(BOTAN_HAS_AES) #endif #if defined(BOTAN_HAS_ARIA) #endif #if defined(BOTAN_HAS_BLOWFISH) #endif #if defined(BOTAN_HAS_CAMELLIA) #endif #if defined(BOTAN_HAS_CAST_128) #endif #if defined(BOTAN_HAS_CAST_256) #endif #if defined(BOTAN_HAS_CASCADE) #endif #if defined(BOTAN_HAS_DES) #endif #if defined(BOTAN_HAS_GOST_28147_89) #endif #if defined(BOTAN_HAS_IDEA) #endif #if defined(BOTAN_HAS_KASUMI) #endif #if defined(BOTAN_HAS_LION) #endif #if defined(BOTAN_HAS_MISTY1) #endif #if defined(BOTAN_HAS_NOEKEON) #endif #if defined(BOTAN_HAS_SEED) #endif #if defined(BOTAN_HAS_SERPENT) #endif #if defined(BOTAN_HAS_SHACAL2) #endif #if defined(BOTAN_HAS_SM4) #endif #if defined(BOTAN_HAS_TWOFISH) #endif #if defined(BOTAN_HAS_THREEFISH_512) #endif #if defined(BOTAN_HAS_XTEA) #endif #if defined(BOTAN_HAS_OPENSSL) #endif #if defined(BOTAN_HAS_COMMONCRYPTO) #endif namespace Botan { std::unique_ptr BlockCipher::create(const std::string& algo, const std::string& provider) { #if defined(BOTAN_HAS_COMMONCRYPTO) if(provider.empty() || provider == "commoncrypto") { if(auto bc = make_commoncrypto_block_cipher(algo)) return bc; if(!provider.empty()) return nullptr; } #endif #if defined(BOTAN_HAS_OPENSSL) if(provider.empty() || provider == "openssl") { if(auto bc = make_openssl_block_cipher(algo)) return bc; if(!provider.empty()) return nullptr; } #endif // TODO: CryptoAPI // TODO: /dev/crypto // Only base providers from here on out if(provider.empty() == false && provider != "base") return nullptr; #if defined(BOTAN_HAS_AES) if(algo == "AES-128") { return std::unique_ptr(new AES_128); } if(algo == "AES-192") { return std::unique_ptr(new AES_192); } if(algo == "AES-256") { return std::unique_ptr(new AES_256); } #endif #if defined(BOTAN_HAS_ARIA) if(algo == "ARIA-128") { return std::unique_ptr(new ARIA_128); } if(algo == "ARIA-192") { return std::unique_ptr(new ARIA_192); } if(algo == "ARIA-256") { return std::unique_ptr(new ARIA_256); } #endif #if defined(BOTAN_HAS_SERPENT) if(algo == "Serpent") { return std::unique_ptr(new Serpent); } #endif #if defined(BOTAN_HAS_SHACAL2) if(algo == "SHACAL2") { return std::unique_ptr(new SHACAL2); } #endif #if defined(BOTAN_HAS_TWOFISH) if(algo == "Twofish") { return std::unique_ptr(new Twofish); } #endif #if defined(BOTAN_HAS_THREEFISH_512) if(algo == "Threefish-512") { return std::unique_ptr(new Threefish_512); } #endif #if defined(BOTAN_HAS_BLOWFISH) if(algo == "Blowfish") { return std::unique_ptr(new Blowfish); } #endif #if defined(BOTAN_HAS_CAMELLIA) if(algo == "Camellia-128") { return std::unique_ptr(new Camellia_128); } if(algo == "Camellia-192") { return std::unique_ptr(new Camellia_192); } if(algo == "Camellia-256") { return std::unique_ptr(new Camellia_256); } #endif #if defined(BOTAN_HAS_DES) if(algo == "DES") { return std::unique_ptr(new DES); } if(algo == "DESX") { return std::unique_ptr(new DESX); } if(algo == "TripleDES" || algo == "3DES" || algo == "DES-EDE") { return std::unique_ptr(new TripleDES); } #endif #if defined(BOTAN_HAS_NOEKEON) if(algo == "Noekeon") { return std::unique_ptr(new Noekeon); } #endif #if defined(BOTAN_HAS_CAST_128) if(algo == "CAST-128" || algo == "CAST5") { return std::unique_ptr(new CAST_128); } #endif #if defined(BOTAN_HAS_CAST_256) if(algo == "CAST-256") { return std::unique_ptr(new CAST_256); } #endif #if defined(BOTAN_HAS_IDEA) if(algo == "IDEA") { return std::unique_ptr(new IDEA); } #endif #if defined(BOTAN_HAS_KASUMI) if(algo == "KASUMI") { return std::unique_ptr(new KASUMI); } #endif #if defined(BOTAN_HAS_MISTY1) if(algo == "MISTY1") { return std::unique_ptr(new MISTY1); } #endif #if defined(BOTAN_HAS_SEED) if(algo == "SEED") { return std::unique_ptr(new SEED); } #endif #if defined(BOTAN_HAS_SM4) if(algo == "SM4") { return std::unique_ptr(new SM4); } #endif #if defined(BOTAN_HAS_XTEA) if(algo == "XTEA") { return std::unique_ptr(new XTEA); } #endif const SCAN_Name req(algo); #if defined(BOTAN_HAS_GOST_28147_89) if(req.algo_name() == "GOST-28147-89") { return std::unique_ptr(new GOST_28147_89(req.arg(0, "R3411_94_TestParam"))); } #endif #if defined(BOTAN_HAS_CASCADE) if(req.algo_name() == "Cascade" && req.arg_count() == 2) { std::unique_ptr c1(BlockCipher::create(req.arg(0))); std::unique_ptr c2(BlockCipher::create(req.arg(1))); if(c1 && c2) return std::unique_ptr(new Cascade_Cipher(c1.release(), c2.release())); } #endif #if defined(BOTAN_HAS_LION) if(req.algo_name() == "Lion" && req.arg_count_between(2, 3)) { std::unique_ptr hash(HashFunction::create(req.arg(0))); std::unique_ptr stream(StreamCipher::create(req.arg(1))); if(hash && stream) { const size_t block_size = req.arg_as_integer(2, 1024); return std::unique_ptr(new Lion(hash.release(), stream.release(), block_size)); } } #endif BOTAN_UNUSED(req); BOTAN_UNUSED(provider); return nullptr; } //static std::unique_ptr BlockCipher::create_or_throw(const std::string& algo, const std::string& provider) { if(auto bc = BlockCipher::create(algo, provider)) { return bc; } throw Lookup_Error("Block cipher", algo, provider); } std::vector BlockCipher::providers(const std::string& algo) { return probe_providers_of(algo, { "base", "openssl", "commoncrypto" }); } } /* * Runtime CPU detection * (C) 2009,2010,2013,2017 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include namespace Botan { bool CPUID::has_simd_32() { #if defined(BOTAN_TARGET_SUPPORTS_SSE2) return CPUID::has_sse2(); #elif defined(BOTAN_TARGET_SUPPORTS_ALTIVEC) return CPUID::has_altivec(); #elif defined(BOTAN_TARGET_SUPPORTS_NEON) return CPUID::has_neon(); #else return true; #endif } //static std::string CPUID::to_string() { std::vector flags; #define CPUID_PRINT(flag) do { if(has_##flag()) { flags.push_back(#flag); } } while(0) #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) CPUID_PRINT(sse2); CPUID_PRINT(ssse3); CPUID_PRINT(sse41); CPUID_PRINT(sse42); CPUID_PRINT(avx2); CPUID_PRINT(avx512f); CPUID_PRINT(avx512dq); CPUID_PRINT(avx512bw); CPUID_PRINT(avx512_icelake); CPUID_PRINT(rdtsc); CPUID_PRINT(bmi1); CPUID_PRINT(bmi2); CPUID_PRINT(adx); CPUID_PRINT(aes_ni); CPUID_PRINT(clmul); CPUID_PRINT(rdrand); CPUID_PRINT(rdseed); CPUID_PRINT(intel_sha); CPUID_PRINT(avx512_aes); CPUID_PRINT(avx512_clmul); #endif #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) CPUID_PRINT(altivec); CPUID_PRINT(power_crypto); CPUID_PRINT(darn_rng); #endif #if defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) CPUID_PRINT(neon); CPUID_PRINT(arm_sve); CPUID_PRINT(arm_sha1); CPUID_PRINT(arm_sha2); CPUID_PRINT(arm_aes); CPUID_PRINT(arm_pmull); CPUID_PRINT(arm_sha2_512); CPUID_PRINT(arm_sha3); CPUID_PRINT(arm_sm3); CPUID_PRINT(arm_sm4); #endif #undef CPUID_PRINT return string_join(flags, ' '); } //static void CPUID::print(std::ostream& o) { o << "CPUID flags: " << CPUID::to_string() << "\n"; } //static void CPUID::initialize() { state() = CPUID_Data(); } CPUID::CPUID_Data::CPUID_Data() { m_cache_line_size = 0; m_processor_features = 0; #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) || \ defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) || \ defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) m_processor_features = detect_cpu_features(&m_cache_line_size); #endif m_processor_features |= CPUID::CPUID_INITIALIZED_BIT; if(m_cache_line_size == 0) m_cache_line_size = BOTAN_TARGET_CPU_DEFAULT_CACHE_LINE_SIZE; m_endian_status = runtime_check_endian(); } //static CPUID::Endian_Status CPUID::CPUID_Data::runtime_check_endian() { // Check runtime endian const uint32_t endian32 = 0x01234567; const uint8_t* e8 = reinterpret_cast(&endian32); CPUID::Endian_Status endian = CPUID::Endian_Status::Unknown; if(e8[0] == 0x01 && e8[1] == 0x23 && e8[2] == 0x45 && e8[3] == 0x67) { endian = CPUID::Endian_Status::Big; } else if(e8[0] == 0x67 && e8[1] == 0x45 && e8[2] == 0x23 && e8[3] == 0x01) { endian = CPUID::Endian_Status::Little; } else { throw Internal_Error("Unexpected endian at runtime, neither big nor little"); } // If we were compiled with a known endian, verify it matches at runtime #if defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) BOTAN_ASSERT(endian == CPUID::Endian_Status::Little, "Build and runtime endian match"); #elif defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) BOTAN_ASSERT(endian == CPUID::Endian_Status::Big, "Build and runtime endian match"); #endif return endian; } std::vector CPUID::bit_from_string(const std::string& tok) { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) if(tok == "sse2" || tok == "simd") return {Botan::CPUID::CPUID_SSE2_BIT}; if(tok == "ssse3") return {Botan::CPUID::CPUID_SSSE3_BIT}; if(tok == "sse41") return {Botan::CPUID::CPUID_SSE41_BIT}; if(tok == "sse42") return {Botan::CPUID::CPUID_SSE42_BIT}; // aes_ni is the string printed on the console when running "botan cpuid" if(tok == "aesni" || tok == "aes_ni") return {Botan::CPUID::CPUID_AESNI_BIT}; if(tok == "clmul") return {Botan::CPUID::CPUID_CLMUL_BIT}; if(tok == "avx2") return {Botan::CPUID::CPUID_AVX2_BIT}; if(tok == "avx512f") return {Botan::CPUID::CPUID_AVX512F_BIT}; if(tok == "avx512_icelake") return {Botan::CPUID::CPUID_AVX512_ICL_BIT}; // there were two if statements testing "sha" and "intel_sha" separately; combined if(tok == "sha" || tok=="intel_sha") return {Botan::CPUID::CPUID_SHA_BIT}; if(tok == "rdtsc") return {Botan::CPUID::CPUID_RDTSC_BIT}; if(tok == "bmi1") return {Botan::CPUID::CPUID_BMI1_BIT}; if(tok == "bmi2") return {Botan::CPUID::CPUID_BMI2_BIT}; if(tok == "adx") return {Botan::CPUID::CPUID_ADX_BIT}; if(tok == "rdrand") return {Botan::CPUID::CPUID_RDRAND_BIT}; if(tok == "rdseed") return {Botan::CPUID::CPUID_RDSEED_BIT}; if(tok == "avx512_aes") return {Botan::CPUID::CPUID_AVX512_AES_BIT}; if(tok == "avx512_clmul") return {Botan::CPUID::CPUID_AVX512_CLMUL_BIT}; #elif defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) if(tok == "altivec" || tok == "simd") return {Botan::CPUID::CPUID_ALTIVEC_BIT}; if(tok == "power_crypto") return {Botan::CPUID::CPUID_POWER_CRYPTO_BIT}; if(tok == "darn_rng") return {Botan::CPUID::CPUID_DARN_BIT}; #elif defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) if(tok == "neon" || tok == "simd") return {Botan::CPUID::CPUID_ARM_NEON_BIT}; if(tok == "arm_sve") return {Botan::CPUID::CPUID_ARM_SVE_BIT}; if(tok == "armv8sha1" || tok == "arm_sha1") return {Botan::CPUID::CPUID_ARM_SHA1_BIT}; if(tok == "armv8sha2" || tok == "arm_sha2") return {Botan::CPUID::CPUID_ARM_SHA2_BIT}; if(tok == "armv8aes" || tok == "arm_aes") return {Botan::CPUID::CPUID_ARM_AES_BIT}; if(tok == "armv8pmull" || tok == "arm_pmull") return {Botan::CPUID::CPUID_ARM_PMULL_BIT}; if(tok == "armv8sha3" || tok == "arm_sha3") return {Botan::CPUID::CPUID_ARM_SHA3_BIT}; if(tok == "armv8sha2_512" || tok == "arm_sha2_512") return {Botan::CPUID::CPUID_ARM_SHA2_512_BIT}; if(tok == "armv8sm3" || tok == "arm_sm3") return {Botan::CPUID::CPUID_ARM_SM3_BIT}; if(tok == "armv8sm4" || tok == "arm_sm4") return {Botan::CPUID::CPUID_ARM_SM4_BIT}; #else BOTAN_UNUSED(tok); #endif return {}; } } /* * Runtime CPU detection for ARM * (C) 2009,2010,2013,2017 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #if defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) #if defined(BOTAN_TARGET_OS_IS_IOS) #include #include #else #if defined(BOTAN_TARGET_OS_HAS_GETAUXVAL) || defined(BOTAN_TARGET_OS_HAS_ELF_AUX_INFO) #include #endif #endif #endif namespace Botan { #if defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) #if defined(BOTAN_TARGET_OS_IS_IOS) namespace { uint64_t flags_by_ios_machine_type(const std::string& machine) { /* * This relies on a map of known machine names to features. This * will quickly grow out of date as new products are introduced, but * is apparently the best we can do for iOS. */ struct version_info { std::string name; size_t min_version_neon; size_t min_version_armv8; }; static const version_info min_versions[] = { { "iPhone", 2, 6 }, { "iPad", 1, 4 }, { "iPod", 4, 7 }, { "AppleTV", 2, 5 }, }; if(machine.size() < 3) return 0; auto comma = machine.find(','); // Simulator, or something we don't know about if(comma == std::string::npos) return 0; std::string product = machine.substr(0, comma); size_t version = 0; size_t place = 1; while(product.size() > 1 && ::isdigit(product.back())) { const size_t digit = product.back() - '0'; version += digit * place; place *= 10; product.pop_back(); } if(version == 0) return 0; for(const version_info& info : min_versions) { if(info.name != product) continue; if(version >= info.min_version_armv8) { return CPUID::CPUID_ARM_AES_BIT | CPUID::CPUID_ARM_PMULL_BIT | CPUID::CPUID_ARM_SHA1_BIT | CPUID::CPUID_ARM_SHA2_BIT | CPUID::CPUID_ARM_NEON_BIT; } if(version >= info.min_version_neon) return CPUID::CPUID_ARM_NEON_BIT; } // Some other product we don't know about return 0; } } #endif uint64_t CPUID::CPUID_Data::detect_cpu_features(size_t* cache_line_size) { BOTAN_UNUSED(cache_line_size); uint64_t detected_features = 0; #if defined(BOTAN_TARGET_OS_HAS_GETAUXVAL) || defined(BOTAN_TARGET_OS_HAS_ELF_AUX_INFO) /* * On systems with getauxval these bits should normally be defined * in bits/auxv.h but some buggy? glibc installs seem to miss them. * These following values are all fixed, for the Linux ELF format, * so we just hardcode them in ARM_hwcap_bit enum. */ enum ARM_hwcap_bit { #if defined(BOTAN_TARGET_ARCH_IS_ARM32) NEON_bit = (1 << 12), AES_bit = (1 << 0), PMULL_bit = (1 << 1), SHA1_bit = (1 << 2), SHA2_bit = (1 << 3), ARCH_hwcap_neon = 16, // AT_HWCAP ARCH_hwcap_crypto = 26, // AT_HWCAP2 #elif defined(BOTAN_TARGET_ARCH_IS_ARM64) NEON_bit = (1 << 1), AES_bit = (1 << 3), PMULL_bit = (1 << 4), SHA1_bit = (1 << 5), SHA2_bit = (1 << 6), SHA3_bit = (1 << 17), SM3_bit = (1 << 18), SM4_bit = (1 << 19), SHA2_512_bit = (1 << 21), SVE_bit = (1 << 22), ARCH_hwcap_neon = 16, // AT_HWCAP ARCH_hwcap_crypto = 16, // AT_HWCAP #endif }; #if defined(AT_DCACHEBSIZE) // Exists only on Linux const unsigned long dcache_line = OS::get_auxval(AT_DCACHEBSIZE); // plausibility check if(dcache_line == 32 || dcache_line == 64 || dcache_line == 128) *cache_line_size = static_cast(dcache_line); #endif const unsigned long hwcap_neon = OS::get_auxval(ARM_hwcap_bit::ARCH_hwcap_neon); if(hwcap_neon & ARM_hwcap_bit::NEON_bit) detected_features |= CPUID::CPUID_ARM_NEON_BIT; /* On aarch64 this ends up calling getauxval twice with AT_HWCAP It doesn't seem worth optimizing this out, since getauxval is just reading a field in the ELF header. */ const unsigned long hwcap_crypto = OS::get_auxval(ARM_hwcap_bit::ARCH_hwcap_crypto); if(hwcap_crypto & ARM_hwcap_bit::AES_bit) detected_features |= CPUID::CPUID_ARM_AES_BIT; if(hwcap_crypto & ARM_hwcap_bit::PMULL_bit) detected_features |= CPUID::CPUID_ARM_PMULL_BIT; if(hwcap_crypto & ARM_hwcap_bit::SHA1_bit) detected_features |= CPUID::CPUID_ARM_SHA1_BIT; if(hwcap_crypto & ARM_hwcap_bit::SHA2_bit) detected_features |= CPUID::CPUID_ARM_SHA2_BIT; #if defined(BOTAN_TARGET_ARCH_IS_ARM64) if(hwcap_crypto & ARM_hwcap_bit::SHA3_bit) detected_features |= CPUID::CPUID_ARM_SHA3_BIT; if(hwcap_crypto & ARM_hwcap_bit::SM3_bit) detected_features |= CPUID::CPUID_ARM_SM3_BIT; if(hwcap_crypto & ARM_hwcap_bit::SM4_bit) detected_features |= CPUID::CPUID_ARM_SM4_BIT; if(hwcap_crypto & ARM_hwcap_bit::SHA2_512_bit) detected_features |= CPUID::CPUID_ARM_SHA2_512_BIT; if(hwcap_crypto & ARM_hwcap_bit::SVE_bit) detected_features |= CPUID::CPUID_ARM_SVE_BIT; #endif #elif defined(BOTAN_TARGET_OS_IS_IOS) char machine[64] = { 0 }; size_t size = sizeof(machine) - 1; ::sysctlbyname("hw.machine", machine, &size, nullptr, 0); detected_features = flags_by_ios_machine_type(machine); // No way to detect cache line size on iOS? #elif defined(BOTAN_USE_GCC_INLINE_ASM) && defined(BOTAN_TARGET_ARCH_IS_ARM64) /* No getauxval API available, fall back on probe functions. We only bother with Aarch64 here to simplify the code and because going to extreme contortions to support detect NEON on devices that probably don't support it doesn't seem worthwhile. NEON registers v0-v7 are caller saved in Aarch64 */ auto neon_probe = []() noexcept -> int { asm("and v0.16b, v0.16b, v0.16b"); return 1; }; auto aes_probe = []() noexcept -> int { asm(".word 0x4e284800"); return 1; }; auto pmull_probe = []() noexcept -> int { asm(".word 0x0ee0e000"); return 1; }; auto sha1_probe = []() noexcept -> int { asm(".word 0x5e280800"); return 1; }; auto sha2_probe = []() noexcept -> int { asm(".word 0x5e282800"); return 1; }; // Only bother running the crypto detection if we found NEON if(OS::run_cpu_instruction_probe(neon_probe) == 1) { detected_features |= CPUID::CPUID_ARM_NEON_BIT; if(OS::run_cpu_instruction_probe(aes_probe) == 1) detected_features |= CPUID::CPUID_ARM_AES_BIT; if(OS::run_cpu_instruction_probe(pmull_probe) == 1) detected_features |= CPUID::CPUID_ARM_PMULL_BIT; if(OS::run_cpu_instruction_probe(sha1_probe) == 1) detected_features |= CPUID::CPUID_ARM_SHA1_BIT; if(OS::run_cpu_instruction_probe(sha2_probe) == 1) detected_features |= CPUID::CPUID_ARM_SHA2_BIT; } #endif return detected_features; } #endif } /* * Runtime CPU detection for POWER/PowerPC * (C) 2009,2010,2013,2017 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) /* * On macOS and OpenBSD ppc, use sysctl to detect AltiVec */ #if defined(BOTAN_TARGET_OS_IS_MACOS) #include #elif defined(BOTAN_TARGET_OS_IS_OPENBSD) #include #include #include #endif #endif namespace Botan { #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) /* * PowerPC specific block: check for AltiVec using either * sysctl or by reading processor version number register. */ uint64_t CPUID::CPUID_Data::detect_cpu_features(size_t* cache_line_size) { BOTAN_UNUSED(cache_line_size); #if defined(BOTAN_TARGET_OS_IS_MACOS) || defined(BOTAN_TARGET_OS_IS_OPENBSD) // On macOS and OpenBSD, use sysctl int sels[2] = { #if defined(BOTAN_TARGET_OS_IS_OPENBSD) CTL_MACHDEP, CPU_ALTIVEC #else CTL_HW, HW_VECTORUNIT #endif }; int vector_type = 0; size_t length = sizeof(vector_type); int error = ::sysctl(sels, 2, &vector_type, &length, NULL, 0); if(error == 0 && vector_type > 0) return CPUID::CPUID_ALTIVEC_BIT; #elif (defined(BOTAN_TARGET_OS_HAS_GETAUXVAL) || defined(BOTAN_TARGET_HAS_ELF_AUX_INFO)) && defined(BOTAN_TARGET_ARCH_IS_PPC64) enum PPC_hwcap_bit { ALTIVEC_bit = (1 << 28), CRYPTO_bit = (1 << 25), DARN_bit = (1 << 21), ARCH_hwcap_altivec = 16, // AT_HWCAP ARCH_hwcap_crypto = 26, // AT_HWCAP2 }; uint64_t detected_features = 0; const unsigned long hwcap_altivec = OS::get_auxval(PPC_hwcap_bit::ARCH_hwcap_altivec); if(hwcap_altivec & PPC_hwcap_bit::ALTIVEC_bit) detected_features |= CPUID::CPUID_ALTIVEC_BIT; const unsigned long hwcap_crypto = OS::get_auxval(PPC_hwcap_bit::ARCH_hwcap_crypto); if(hwcap_crypto & PPC_hwcap_bit::CRYPTO_bit) detected_features |= CPUID::CPUID_POWER_CRYPTO_BIT; if(hwcap_crypto & PPC_hwcap_bit::DARN_bit) detected_features |= CPUID::CPUID_DARN_BIT; return detected_features; #else /* On PowerPC, MSR 287 is PVR, the Processor Version Number Normally it is only accessible to ring 0, but Linux and NetBSD (others, too, maybe?) will trap and emulate it for us. */ int pvr = OS::run_cpu_instruction_probe([]() noexcept -> int { uint32_t pvr = 0; asm volatile("mfspr %0, 287" : "=r" (pvr)); // Top 16 bits suffice to identify the model return static_cast(pvr >> 16); }); if(pvr > 0) { const uint16_t ALTIVEC_PVR[] = { 0x003E, // IBM POWER6 0x003F, // IBM POWER7 0x004A, // IBM POWER7p 0x004B, // IBM POWER8E 0x004C, // IBM POWER8 NVL 0x004D, // IBM POWER8 0x004E, // IBM POWER9 0x000C, // G4-7400 0x0039, // G5 970 0x003C, // G5 970FX 0x0044, // G5 970MP 0x0070, // Cell PPU 0, // end }; for(size_t i = 0; ALTIVEC_PVR[i]; ++i) { if(pvr == ALTIVEC_PVR[i]) return CPUID::CPUID_ALTIVEC_BIT; } return 0; } // TODO try direct instruction probing #endif return 0; } #endif } /* * Runtime CPU detection for x86 * (C) 2009,2010,2013,2017 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) #if defined(BOTAN_BUILD_COMPILER_IS_MSVC) #include #elif defined(BOTAN_BUILD_COMPILER_IS_INTEL) #include #elif defined(BOTAN_BUILD_COMPILER_IS_GCC) || defined(BOTAN_BUILD_COMPILER_IS_CLANG) #include #endif #endif namespace Botan { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) uint64_t CPUID::CPUID_Data::detect_cpu_features(size_t* cache_line_size) { #if defined(BOTAN_BUILD_COMPILER_IS_MSVC) #define X86_CPUID(type, out) do { __cpuid((int*)out, type); } while(0) #define X86_CPUID_SUBLEVEL(type, level, out) do { __cpuidex((int*)out, type, level); } while(0) #elif defined(BOTAN_BUILD_COMPILER_IS_INTEL) #define X86_CPUID(type, out) do { __cpuid(out, type); } while(0) #define X86_CPUID_SUBLEVEL(type, level, out) do { __cpuidex((int*)out, type, level); } while(0) #elif defined(BOTAN_TARGET_ARCH_IS_X86_64) && defined(BOTAN_USE_GCC_INLINE_ASM) #define X86_CPUID(type, out) \ asm("cpuid\n\t" : "=a" (out[0]), "=b" (out[1]), "=c" (out[2]), "=d" (out[3]) \ : "0" (type)) #define X86_CPUID_SUBLEVEL(type, level, out) \ asm("cpuid\n\t" : "=a" (out[0]), "=b" (out[1]), "=c" (out[2]), "=d" (out[3]) \ : "0" (type), "2" (level)) #elif defined(BOTAN_BUILD_COMPILER_IS_GCC) || defined(BOTAN_BUILD_COMPILER_IS_CLANG) #define X86_CPUID(type, out) do { __get_cpuid(type, out, out+1, out+2, out+3); } while(0) #define X86_CPUID_SUBLEVEL(type, level, out) \ do { __cpuid_count(type, level, out[0], out[1], out[2], out[3]); } while(0) #else #warning "No way of calling x86 cpuid instruction for this compiler" #define X86_CPUID(type, out) do { clear_mem(out, 4); } while(0) #define X86_CPUID_SUBLEVEL(type, level, out) do { clear_mem(out, 4); } while(0) #endif uint64_t features_detected = 0; uint32_t cpuid[4] = { 0 }; // CPUID 0: vendor identification, max sublevel X86_CPUID(0, cpuid); const uint32_t max_supported_sublevel = cpuid[0]; const uint32_t INTEL_CPUID[3] = { 0x756E6547, 0x6C65746E, 0x49656E69 }; const uint32_t AMD_CPUID[3] = { 0x68747541, 0x444D4163, 0x69746E65 }; const bool is_intel = same_mem(cpuid + 1, INTEL_CPUID, 3); const bool is_amd = same_mem(cpuid + 1, AMD_CPUID, 3); if(max_supported_sublevel >= 1) { // CPUID 1: feature bits X86_CPUID(1, cpuid); const uint64_t flags0 = (static_cast(cpuid[2]) << 32) | cpuid[3]; enum x86_CPUID_1_bits : uint64_t { RDTSC = (1ULL << 4), SSE2 = (1ULL << 26), CLMUL = (1ULL << 33), SSSE3 = (1ULL << 41), SSE41 = (1ULL << 51), SSE42 = (1ULL << 52), AESNI = (1ULL << 57), RDRAND = (1ULL << 62) }; if(flags0 & x86_CPUID_1_bits::RDTSC) features_detected |= CPUID::CPUID_RDTSC_BIT; if(flags0 & x86_CPUID_1_bits::SSE2) features_detected |= CPUID::CPUID_SSE2_BIT; if(flags0 & x86_CPUID_1_bits::CLMUL) features_detected |= CPUID::CPUID_CLMUL_BIT; if(flags0 & x86_CPUID_1_bits::SSSE3) features_detected |= CPUID::CPUID_SSSE3_BIT; if(flags0 & x86_CPUID_1_bits::SSE41) features_detected |= CPUID::CPUID_SSE41_BIT; if(flags0 & x86_CPUID_1_bits::SSE42) features_detected |= CPUID::CPUID_SSE42_BIT; if(flags0 & x86_CPUID_1_bits::AESNI) features_detected |= CPUID::CPUID_AESNI_BIT; if(flags0 & x86_CPUID_1_bits::RDRAND) features_detected |= CPUID::CPUID_RDRAND_BIT; } if(is_intel) { // Intel cache line size is in cpuid(1) output *cache_line_size = 8 * get_byte(2, cpuid[1]); } else if(is_amd) { // AMD puts it in vendor zone X86_CPUID(0x80000005, cpuid); *cache_line_size = get_byte(3, cpuid[2]); } if(max_supported_sublevel >= 7) { clear_mem(cpuid, 4); X86_CPUID_SUBLEVEL(7, 0, cpuid); enum x86_CPUID_7_bits : uint64_t { BMI1 = (1ULL << 3), AVX2 = (1ULL << 5), BMI2 = (1ULL << 8), AVX512_F = (1ULL << 16), AVX512_DQ = (1ULL << 17), RDSEED = (1ULL << 18), ADX = (1ULL << 19), AVX512_IFMA = (1ULL << 21), SHA = (1ULL << 29), AVX512_BW = (1ULL << 30), AVX512_VL = (1ULL << 31), AVX512_VBMI = (1ULL << 33), AVX512_VBMI2 = (1ULL << 38), AVX512_VAES = (1ULL << 41), AVX512_VCLMUL = (1ULL << 42), AVX512_VBITALG = (1ULL << 44), }; const uint64_t flags7 = (static_cast(cpuid[2]) << 32) | cpuid[1]; if(flags7 & x86_CPUID_7_bits::AVX2) features_detected |= CPUID::CPUID_AVX2_BIT; if(flags7 & x86_CPUID_7_bits::BMI1) { features_detected |= CPUID::CPUID_BMI1_BIT; /* We only set the BMI2 bit if BMI1 is also supported, so BMI2 code can safely use both extensions. No known processor implements BMI2 but not BMI1. */ if(flags7 & x86_CPUID_7_bits::BMI2) features_detected |= CPUID::CPUID_BMI2_BIT; } if(flags7 & x86_CPUID_7_bits::AVX512_F) { features_detected |= CPUID::CPUID_AVX512F_BIT; if(flags7 & x86_CPUID_7_bits::AVX512_DQ) features_detected |= CPUID::CPUID_AVX512DQ_BIT; if(flags7 & x86_CPUID_7_bits::AVX512_BW) features_detected |= CPUID::CPUID_AVX512BW_BIT; const uint64_t ICELAKE_FLAGS = x86_CPUID_7_bits::AVX512_F | x86_CPUID_7_bits::AVX512_DQ | x86_CPUID_7_bits::AVX512_IFMA | x86_CPUID_7_bits::AVX512_BW | x86_CPUID_7_bits::AVX512_VL | x86_CPUID_7_bits::AVX512_VBMI | x86_CPUID_7_bits::AVX512_VBMI2 | x86_CPUID_7_bits::AVX512_VBITALG; if((flags7 & ICELAKE_FLAGS) == ICELAKE_FLAGS) features_detected |= CPUID::CPUID_AVX512_ICL_BIT; if(flags7 & x86_CPUID_7_bits::AVX512_VAES) features_detected |= CPUID::CPUID_AVX512_AES_BIT; if(flags7 & x86_CPUID_7_bits::AVX512_VCLMUL) features_detected |= CPUID::CPUID_AVX512_CLMUL_BIT; } if(flags7 & x86_CPUID_7_bits::RDSEED) features_detected |= CPUID::CPUID_RDSEED_BIT; if(flags7 & x86_CPUID_7_bits::ADX) features_detected |= CPUID::CPUID_ADX_BIT; if(flags7 & x86_CPUID_7_bits::SHA) features_detected |= CPUID::CPUID_SHA_BIT; } #undef X86_CPUID #undef X86_CPUID_SUBLEVEL /* * If we don't have access to CPUID, we can still safely assume that * any x86-64 processor has SSE2 and RDTSC */ #if defined(BOTAN_TARGET_ARCH_IS_X86_64) if(features_detected == 0) { features_detected |= CPUID::CPUID_SSE2_BIT; features_detected |= CPUID::CPUID_RDTSC_BIT; } #endif return features_detected; } #endif } /* * Counter mode * (C) 1999-2011,2014 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { CTR_BE::CTR_BE(BlockCipher* ciph) : m_cipher(ciph), m_block_size(m_cipher->block_size()), m_ctr_size(m_block_size), m_ctr_blocks(m_cipher->parallel_bytes() / m_block_size), m_counter(m_cipher->parallel_bytes()), m_pad(m_counter.size()), m_pad_pos(0) { } CTR_BE::CTR_BE(BlockCipher* cipher, size_t ctr_size) : m_cipher(cipher), m_block_size(m_cipher->block_size()), m_ctr_size(ctr_size), m_ctr_blocks(m_cipher->parallel_bytes() / m_block_size), m_counter(m_cipher->parallel_bytes()), m_pad(m_counter.size()), m_pad_pos(0) { BOTAN_ARG_CHECK(m_ctr_size >= 4 && m_ctr_size <= m_block_size, "Invalid CTR-BE counter size"); } void CTR_BE::clear() { m_cipher->clear(); zeroise(m_pad); zeroise(m_counter); zap(m_iv); m_pad_pos = 0; } size_t CTR_BE::default_iv_length() const { return m_block_size; } bool CTR_BE::valid_iv_length(size_t iv_len) const { return (iv_len <= m_block_size); } Key_Length_Specification CTR_BE::key_spec() const { return m_cipher->key_spec(); } CTR_BE* CTR_BE::clone() const { return new CTR_BE(m_cipher->clone(), m_ctr_size); } void CTR_BE::key_schedule(const uint8_t key[], size_t key_len) { m_cipher->set_key(key, key_len); // Set a default all-zeros IV set_iv(nullptr, 0); } std::string CTR_BE::name() const { if(m_ctr_size == m_block_size) return ("CTR-BE(" + m_cipher->name() + ")"); else return ("CTR-BE(" + m_cipher->name() + "," + std::to_string(m_ctr_size) + ")"); } void CTR_BE::cipher(const uint8_t in[], uint8_t out[], size_t length) { verify_key_set(m_iv.empty() == false); const uint8_t* pad_bits = &m_pad[0]; const size_t pad_size = m_pad.size(); if(m_pad_pos > 0) { const size_t avail = pad_size - m_pad_pos; const size_t take = std::min(length, avail); xor_buf(out, in, pad_bits + m_pad_pos, take); length -= take; in += take; out += take; m_pad_pos += take; if(take == avail) { add_counter(m_ctr_blocks); m_cipher->encrypt_n(m_counter.data(), m_pad.data(), m_ctr_blocks); m_pad_pos = 0; } } while(length >= pad_size) { xor_buf(out, in, pad_bits, pad_size); length -= pad_size; in += pad_size; out += pad_size; add_counter(m_ctr_blocks); m_cipher->encrypt_n(m_counter.data(), m_pad.data(), m_ctr_blocks); } xor_buf(out, in, pad_bits, length); m_pad_pos += length; } void CTR_BE::set_iv(const uint8_t iv[], size_t iv_len) { if(!valid_iv_length(iv_len)) throw Invalid_IV_Length(name(), iv_len); m_iv.resize(m_block_size); zeroise(m_iv); buffer_insert(m_iv, 0, iv, iv_len); seek(0); } void CTR_BE::add_counter(const uint64_t counter) { const size_t ctr_size = m_ctr_size; const size_t ctr_blocks = m_ctr_blocks; const size_t BS = m_block_size; if(ctr_size == 4) { const size_t off = (BS - 4); const uint32_t low32 = static_cast(counter + load_be(&m_counter[off], 0)); for(size_t i = 0; i != ctr_blocks; ++i) { store_be(uint32_t(low32 + i), &m_counter[i*BS+off]); } } else if(ctr_size == 8) { const size_t off = (BS - 8); const uint64_t low64 = counter + load_be(&m_counter[off], 0); for(size_t i = 0; i != ctr_blocks; ++i) { store_be(uint64_t(low64 + i), &m_counter[i*BS+off]); } } else if(ctr_size == 16) { const size_t off = (BS - 16); uint64_t b0 = load_be(&m_counter[off], 0); uint64_t b1 = load_be(&m_counter[off], 1); b1 += counter; b0 += (b1 < counter) ? 1 : 0; // carry for(size_t i = 0; i != ctr_blocks; ++i) { store_be(b0, &m_counter[i*BS+off]); store_be(b1, &m_counter[i*BS+off+8]); b1 += 1; b0 += (b1 == 0); // carry } } else { for(size_t i = 0; i != ctr_blocks; ++i) { uint64_t local_counter = counter; uint16_t carry = static_cast(local_counter); for(size_t j = 0; (carry || local_counter) && j != ctr_size; ++j) { const size_t off = i*BS + (BS-1-j); const uint16_t cnt = static_cast(m_counter[off]) + carry; m_counter[off] = static_cast(cnt); local_counter = (local_counter >> 8); carry = (cnt >> 8) + static_cast(local_counter); } } } } void CTR_BE::seek(uint64_t offset) { verify_key_set(m_iv.empty() == false); const uint64_t base_counter = m_ctr_blocks * (offset / m_counter.size()); zeroise(m_counter); buffer_insert(m_counter, 0, m_iv); const size_t BS = m_block_size; // Set m_counter blocks to IV, IV + 1, ... IV + n if(m_ctr_size == 4 && BS >= 8) { const uint32_t low32 = load_be(&m_counter[BS-4], 0); if(m_ctr_blocks >= 4 && is_power_of_2(m_ctr_blocks)) { size_t written = 1; while(written < m_ctr_blocks) { copy_mem(&m_counter[written*BS], &m_counter[0], BS*written); written *= 2; } } else { for(size_t i = 1; i != m_ctr_blocks; ++i) { copy_mem(&m_counter[i*BS], &m_counter[0], BS - 4); } } for(size_t i = 1; i != m_ctr_blocks; ++i) { const uint32_t c = static_cast(low32 + i); store_be(c, &m_counter[(BS-4)+i*BS]); } } else { // do everything sequentially: for(size_t i = 1; i != m_ctr_blocks; ++i) { buffer_insert(m_counter, i*BS, &m_counter[(i-1)*BS], BS); for(size_t j = 0; j != m_ctr_size; ++j) if(++m_counter[i*BS + (BS - 1 - j)]) break; } } if(base_counter > 0) add_counter(base_counter); m_cipher->encrypt_n(m_counter.data(), m_pad.data(), m_ctr_blocks); m_pad_pos = offset % m_counter.size(); } } /* * Entropy Source Polling * (C) 2008-2010,2015 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #if defined(BOTAN_HAS_SYSTEM_RNG) #endif #if defined(BOTAN_HAS_PROCESSOR_RNG) #endif #if defined(BOTAN_HAS_ENTROPY_SRC_RDRAND) #endif #if defined(BOTAN_HAS_ENTROPY_SRC_RDSEED) #endif #if defined(BOTAN_HAS_ENTROPY_SRC_DARN) #endif #if defined(BOTAN_HAS_ENTROPY_SRC_DEV_RANDOM) #endif #if defined(BOTAN_HAS_ENTROPY_SRC_WIN32) #endif #if defined(BOTAN_HAS_ENTROPY_SRC_PROC_WALKER) #endif #if defined(BOTAN_HAS_ENTROPY_SRC_GETENTROPY) #endif namespace Botan { namespace { #if defined(BOTAN_HAS_SYSTEM_RNG) class System_RNG_EntropySource final : public Entropy_Source { public: size_t poll(RandomNumberGenerator& rng) override { const size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS; rng.reseed_from_rng(system_rng(), poll_bits); return poll_bits; } std::string name() const override { return "system_rng"; } }; #endif #if defined(BOTAN_HAS_PROCESSOR_RNG) class Processor_RNG_EntropySource final : public Entropy_Source { public: size_t poll(RandomNumberGenerator& rng) override { /* * Intel's documentation for RDRAND at * https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide * claims that software can guarantee a reseed event by polling enough data: * "There is an upper bound of 511 samples per seed in the implementation * where samples are 128 bits in size and can provide two 64-bit random * numbers each." * * By requesting 65536 bits we are asking for 512 samples and thus are assured * that at some point in producing the output, at least one reseed of the * internal state will occur. * * The reseeding conditions of the POWER and ARM processor RNGs are not known * but probably work in a somewhat similar manner. The exact amount requested * may be tweaked if and when such conditions become publically known. */ const size_t poll_bits = 65536; rng.reseed_from_rng(m_hwrng, poll_bits); // Avoid trusting a black box, don't count this as contributing entropy: return 0; } std::string name() const override { return m_hwrng.name(); } private: Processor_RNG m_hwrng; }; #endif } std::unique_ptr Entropy_Source::create(const std::string& name) { #if defined(BOTAN_HAS_SYSTEM_RNG) if(name == "system_rng" || name == "win32_cryptoapi") { return std::unique_ptr(new System_RNG_EntropySource); } #endif #if defined(BOTAN_HAS_PROCESSOR_RNG) if(name == "hwrng" || name == "rdrand" || name == "p9_darn") { if(Processor_RNG::available()) { return std::unique_ptr(new Processor_RNG_EntropySource); } } #endif #if defined(BOTAN_HAS_ENTROPY_SRC_RDSEED) if(name == "rdseed") { return std::unique_ptr(new Intel_Rdseed); } #endif #if defined(BOTAN_HAS_ENTROPY_SRC_GETENTROPY) if(name == "getentropy") { return std::unique_ptr(new Getentropy); } #endif #if defined(BOTAN_HAS_ENTROPY_SRC_DEV_RANDOM) if(name == "dev_random") { return std::unique_ptr(new Device_EntropySource(BOTAN_SYSTEM_RNG_POLL_DEVICES)); } #endif #if defined(BOTAN_HAS_ENTROPY_SRC_PROC_WALKER) if(name == "proc_walk" && OS::running_in_privileged_state() == false) { const std::string root_dir = BOTAN_ENTROPY_PROC_FS_PATH; if(!root_dir.empty()) return std::unique_ptr(new ProcWalking_EntropySource(root_dir)); } #endif #if defined(BOTAN_HAS_ENTROPY_SRC_WIN32) if(name == "system_stats") { return std::unique_ptr(new Win32_EntropySource); } #endif BOTAN_UNUSED(name); return std::unique_ptr(); } void Entropy_Sources::add_source(std::unique_ptr src) { if(src.get()) { m_srcs.push_back(std::move(src)); } } std::vector Entropy_Sources::enabled_sources() const { std::vector sources; for(size_t i = 0; i != m_srcs.size(); ++i) { sources.push_back(m_srcs[i]->name()); } return sources; } size_t Entropy_Sources::poll(RandomNumberGenerator& rng, size_t poll_bits, std::chrono::milliseconds timeout) { typedef std::chrono::system_clock clock; auto deadline = clock::now() + timeout; size_t bits_collected = 0; for(size_t i = 0; i != m_srcs.size(); ++i) { bits_collected += m_srcs[i]->poll(rng); if (bits_collected >= poll_bits || clock::now() > deadline) break; } return bits_collected; } size_t Entropy_Sources::poll_just(RandomNumberGenerator& rng, const std::string& the_src) { for(size_t i = 0; i != m_srcs.size(); ++i) { if(m_srcs[i]->name() == the_src) { return m_srcs[i]->poll(rng); } } return 0; } Entropy_Sources::Entropy_Sources(const std::vector& sources) { for(auto&& src_name : sources) { add_source(Entropy_Source::create(src_name)); } } Entropy_Sources& Entropy_Sources::global_sources() { static Entropy_Sources global_entropy_sources(BOTAN_ENTROPY_DEFAULT_SOURCES); return global_entropy_sources; } } /* * GCM Mode Encryption * (C) 2013,2015 Jack Lloyd * (C) 2016 Daniel Neus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { /* * GCM_Mode Constructor */ GCM_Mode::GCM_Mode(BlockCipher* cipher, size_t tag_size) : m_tag_size(tag_size), m_cipher_name(cipher->name()), m_ctr(new CTR_BE(cipher, 4)), m_ghash(new GHASH) { if(cipher->block_size() != GCM_BS) throw Invalid_Argument("Invalid block cipher for GCM"); /* We allow any of the values 128, 120, 112, 104, or 96 bits as a tag size */ /* 64 bit tag is still supported but deprecated and will be removed in the future */ if(m_tag_size != 8 && (m_tag_size < 12 || m_tag_size > 16)) throw Invalid_Argument(name() + ": Bad tag size " + std::to_string(m_tag_size)); } GCM_Mode::~GCM_Mode() { /* for unique_ptr */ } void GCM_Mode::clear() { m_ctr->clear(); m_ghash->clear(); reset(); } void GCM_Mode::reset() { m_ghash->reset(); } std::string GCM_Mode::name() const { return (m_cipher_name + "/GCM(" + std::to_string(tag_size()) + ")"); } std::string GCM_Mode::provider() const { return m_ghash->provider(); } size_t GCM_Mode::update_granularity() const { return GCM_BS * std::max(2, BOTAN_BLOCK_CIPHER_PAR_MULT); } bool GCM_Mode::valid_nonce_length(size_t len) const { // GCM does not support empty nonces return (len > 0); } Key_Length_Specification GCM_Mode::key_spec() const { return m_ctr->key_spec(); } void GCM_Mode::key_schedule(const uint8_t key[], size_t keylen) { m_ctr->set_key(key, keylen); const std::vector zeros(GCM_BS); m_ctr->set_iv(zeros.data(), zeros.size()); secure_vector H(GCM_BS); m_ctr->encipher(H); m_ghash->set_key(H); } void GCM_Mode::set_associated_data(const uint8_t ad[], size_t ad_len) { m_ghash->set_associated_data(ad, ad_len); } void GCM_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) { if(!valid_nonce_length(nonce_len)) throw Invalid_IV_Length(name(), nonce_len); if(m_y0.size() != GCM_BS) m_y0.resize(GCM_BS); clear_mem(m_y0.data(), m_y0.size()); if(nonce_len == 12) { copy_mem(m_y0.data(), nonce, nonce_len); m_y0[15] = 1; } else { m_ghash->nonce_hash(m_y0, nonce, nonce_len); } m_ctr->set_iv(m_y0.data(), m_y0.size()); clear_mem(m_y0.data(), m_y0.size()); m_ctr->encipher(m_y0); m_ghash->start(m_y0.data(), m_y0.size()); clear_mem(m_y0.data(), m_y0.size()); } size_t GCM_Encryption::process(uint8_t buf[], size_t sz) { BOTAN_ARG_CHECK(sz % update_granularity() == 0, "Invalid buffer size"); m_ctr->cipher(buf, buf, sz); m_ghash->update(buf, sz); return sz; } void GCM_Encryption::finish(secure_vector& buffer, size_t offset) { BOTAN_ARG_CHECK(offset <= buffer.size(), "Invalid offset"); const size_t sz = buffer.size() - offset; uint8_t* buf = buffer.data() + offset; m_ctr->cipher(buf, buf, sz); m_ghash->update(buf, sz); uint8_t mac[16] = { 0 }; m_ghash->final(mac, tag_size()); buffer += std::make_pair(mac, tag_size()); } size_t GCM_Decryption::process(uint8_t buf[], size_t sz) { BOTAN_ARG_CHECK(sz % update_granularity() == 0, "Invalid buffer size"); m_ghash->update(buf, sz); m_ctr->cipher(buf, buf, sz); return sz; } void GCM_Decryption::finish(secure_vector& buffer, size_t offset) { BOTAN_ARG_CHECK(offset <= buffer.size(), "Invalid offset"); const size_t sz = buffer.size() - offset; uint8_t* buf = buffer.data() + offset; if(sz < tag_size()) throw Decoding_Error("Insufficient input for GCM decryption, tag missing"); const size_t remaining = sz - tag_size(); // handle any final input before the tag if(remaining) { m_ghash->update(buf, remaining); m_ctr->cipher(buf, buf, remaining); } uint8_t mac[16] = { 0 }; m_ghash->final(mac, tag_size()); const uint8_t* included_tag = &buffer[remaining+offset]; if(!constant_time_compare(mac, included_tag, tag_size())) throw Invalid_Authentication_Tag("GCM tag check failed"); buffer.resize(offset + remaining); } } /* * GCM GHASH * (C) 2013,2015,2017 Jack Lloyd * (C) 2016 Daniel Neus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { std::string GHASH::provider() const { #if defined(BOTAN_HAS_GHASH_CLMUL_CPU) if(CPUID::has_carryless_multiply()) return "clmul"; #endif #if defined(BOTAN_HAS_GHASH_CLMUL_VPERM) if(CPUID::has_vperm()) return "vperm"; #endif return "base"; } void GHASH::ghash_multiply(secure_vector& x, const uint8_t input[], size_t blocks) { #if defined(BOTAN_HAS_GHASH_CLMUL_CPU) if(CPUID::has_carryless_multiply()) { return ghash_multiply_cpu(x.data(), m_H_pow.data(), input, blocks); } #endif #if defined(BOTAN_HAS_GHASH_CLMUL_VPERM) if(CPUID::has_vperm()) { return ghash_multiply_vperm(x.data(), m_HM.data(), input, blocks); } #endif CT::poison(x.data(), x.size()); const uint64_t ALL_BITS = 0xFFFFFFFFFFFFFFFF; uint64_t X[2] = { load_be(x.data(), 0), load_be(x.data(), 1) }; for(size_t b = 0; b != blocks; ++b) { X[0] ^= load_be(input, 2*b); X[1] ^= load_be(input, 2*b+1); uint64_t Z[2] = { 0, 0 }; for(size_t i = 0; i != 64; ++i) { const uint64_t X0MASK = (ALL_BITS + (X[0] >> 63)) ^ ALL_BITS; const uint64_t X1MASK = (ALL_BITS + (X[1] >> 63)) ^ ALL_BITS; X[0] <<= 1; X[1] <<= 1; Z[0] ^= m_HM[4*i ] & X0MASK; Z[1] ^= m_HM[4*i+1] & X0MASK; Z[0] ^= m_HM[4*i+2] & X1MASK; Z[1] ^= m_HM[4*i+3] & X1MASK; } X[0] = Z[0]; X[1] = Z[1]; } store_be(x.data(), X[0], X[1]); CT::unpoison(x.data(), x.size()); } void GHASH::ghash_update(secure_vector& ghash, const uint8_t input[], size_t length) { verify_key_set(!m_HM.empty()); /* This assumes if less than block size input then we're just on the final block and should pad with zeros */ const size_t full_blocks = length / GCM_BS; const size_t final_bytes = length - (full_blocks * GCM_BS); if(full_blocks > 0) { ghash_multiply(ghash, input, full_blocks); } if(final_bytes) { uint8_t last_block[GCM_BS] = { 0 }; copy_mem(last_block, input + full_blocks * GCM_BS, final_bytes); ghash_multiply(ghash, last_block, 1); secure_scrub_memory(last_block, final_bytes); } } void GHASH::key_schedule(const uint8_t key[], size_t length) { m_H.assign(key, key+length); m_H_ad.resize(GCM_BS); m_ad_len = 0; m_text_len = 0; uint64_t H0 = load_be(m_H.data(), 0); uint64_t H1 = load_be(m_H.data(), 1); const uint64_t R = 0xE100000000000000; m_HM.resize(256); // precompute the multiples of H for(size_t i = 0; i != 2; ++i) { for(size_t j = 0; j != 64; ++j) { /* we interleave H^1, H^65, H^2, H^66, H3, H67, H4, H68 to make indexing nicer in the multiplication code */ m_HM[4*j+2*i] = H0; m_HM[4*j+2*i+1] = H1; // GCM's bit ops are reversed so we carry out of the bottom const uint64_t carry = R * (H1 & 1); H1 = (H1 >> 1) | (H0 << 63); H0 = (H0 >> 1) ^ carry; } } #if defined(BOTAN_HAS_GHASH_CLMUL_CPU) if(CPUID::has_carryless_multiply()) { m_H_pow.resize(8); ghash_precompute_cpu(m_H.data(), m_H_pow.data()); } #endif } void GHASH::start(const uint8_t nonce[], size_t len) { BOTAN_ARG_CHECK(len == 16, "GHASH requires a 128-bit nonce"); m_nonce.assign(nonce, nonce + len); m_ghash = m_H_ad; } void GHASH::set_associated_data(const uint8_t input[], size_t length) { if(m_ghash.empty() == false) throw Invalid_State("Too late to set AD in GHASH"); zeroise(m_H_ad); ghash_update(m_H_ad, input, length); m_ad_len = length; } void GHASH::update_associated_data(const uint8_t ad[], size_t length) { verify_key_set(m_ghash.size() == GCM_BS); m_ad_len += length; ghash_update(m_ghash, ad, length); } void GHASH::update(const uint8_t input[], size_t length) { verify_key_set(m_ghash.size() == GCM_BS); m_text_len += length; ghash_update(m_ghash, input, length); } void GHASH::add_final_block(secure_vector& hash, size_t ad_len, size_t text_len) { /* * stack buffer is fine here since the text len is public * and the length of the AD is probably not sensitive either. */ uint8_t final_block[GCM_BS]; store_be(final_block, 8*ad_len, 8*text_len); ghash_update(hash, final_block, GCM_BS); } void GHASH::final(uint8_t mac[], size_t mac_len) { BOTAN_ARG_CHECK(mac_len > 0 && mac_len <= 16, "GHASH output length"); add_final_block(m_ghash, m_ad_len, m_text_len); for(size_t i = 0; i != mac_len; ++i) mac[i] = m_ghash[i] ^ m_nonce[i]; m_ghash.clear(); m_text_len = 0; } void GHASH::nonce_hash(secure_vector& y0, const uint8_t nonce[], size_t nonce_len) { BOTAN_ASSERT(m_ghash.size() == 0, "nonce_hash called during wrong time"); ghash_update(y0, nonce, nonce_len); add_final_block(y0, 0, nonce_len); } void GHASH::clear() { zap(m_H); zap(m_HM); reset(); } void GHASH::reset() { zeroise(m_H_ad); m_ghash.clear(); m_nonce.clear(); m_text_len = m_ad_len = 0; } } /* * Hex Encoding and Decoding * (C) 2010,2020 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { namespace { char hex_encode_nibble(uint8_t n, bool uppercase) { BOTAN_DEBUG_ASSERT(n <= 15); const auto in_09 = CT::Mask::is_lt(n, 10); const char c_09 = n + '0'; const char c_af = n + (uppercase ? 'A' : 'a') - 10; return in_09.select(c_09, c_af); } } void hex_encode(char output[], const uint8_t input[], size_t input_length, bool uppercase) { for(size_t i = 0; i != input_length; ++i) { const uint8_t n0 = (input[i] >> 4) & 0xF; const uint8_t n1 = (input[i] ) & 0xF; output[2*i ] = hex_encode_nibble(n0, uppercase); output[2*i+1] = hex_encode_nibble(n1, uppercase); } } std::string hex_encode(const uint8_t input[], size_t input_length, bool uppercase) { std::string output(2 * input_length, 0); if(input_length) hex_encode(&output.front(), input, input_length, uppercase); return output; } namespace { uint8_t hex_char_to_bin(char input) { const uint8_t c = static_cast(input); const auto is_alpha_upper = CT::Mask::is_within_range(c, uint8_t('A'), uint8_t('F')); const auto is_alpha_lower = CT::Mask::is_within_range(c, uint8_t('a'), uint8_t('f')); const auto is_decimal = CT::Mask::is_within_range(c, uint8_t('0'), uint8_t('9')); const auto is_whitespace = CT::Mask::is_any_of(c, { uint8_t(' '), uint8_t('\t'), uint8_t('\n'), uint8_t('\r') }); const uint8_t c_upper = c - uint8_t('A') + 10; const uint8_t c_lower = c - uint8_t('a') + 10; const uint8_t c_decim = c - uint8_t('0'); uint8_t ret = 0xFF; // default value ret = is_alpha_upper.select(c_upper, ret); ret = is_alpha_lower.select(c_lower, ret); ret = is_decimal.select(c_decim, ret); ret = is_whitespace.select(0x80, ret); return ret; } } size_t hex_decode(uint8_t output[], const char input[], size_t input_length, size_t& input_consumed, bool ignore_ws) { uint8_t* out_ptr = output; bool top_nibble = true; clear_mem(output, input_length / 2); for(size_t i = 0; i != input_length; ++i) { const uint8_t bin = hex_char_to_bin(input[i]); if(bin >= 0x10) { if(bin == 0x80 && ignore_ws) continue; std::string bad_char(1, input[i]); if(bad_char == "\t") bad_char = "\\t"; else if(bad_char == "\n") bad_char = "\\n"; throw Invalid_Argument( std::string("hex_decode: invalid hex character '") + bad_char + "'"); } if(top_nibble) *out_ptr |= bin << 4; else *out_ptr |= bin; top_nibble = !top_nibble; if(top_nibble) ++out_ptr; } input_consumed = input_length; size_t written = (out_ptr - output); /* * We only got half of a uint8_t at the end; zap the half-written * output and mark it as unread */ if(!top_nibble) { *out_ptr = 0; input_consumed -= 1; } return written; } size_t hex_decode(uint8_t output[], const char input[], size_t input_length, bool ignore_ws) { size_t consumed = 0; size_t written = hex_decode(output, input, input_length, consumed, ignore_ws); if(consumed != input_length) throw Invalid_Argument("hex_decode: input did not have full bytes"); return written; } size_t hex_decode(uint8_t output[], const std::string& input, bool ignore_ws) { return hex_decode(output, input.data(), input.length(), ignore_ws); } secure_vector hex_decode_locked(const char input[], size_t input_length, bool ignore_ws) { secure_vector bin(1 + input_length / 2); size_t written = hex_decode(bin.data(), input, input_length, ignore_ws); bin.resize(written); return bin; } secure_vector hex_decode_locked(const std::string& input, bool ignore_ws) { return hex_decode_locked(input.data(), input.size(), ignore_ws); } std::vector hex_decode(const char input[], size_t input_length, bool ignore_ws) { std::vector bin(1 + input_length / 2); size_t written = hex_decode(bin.data(), input, input_length, ignore_ws); bin.resize(written); return bin; } std::vector hex_decode(const std::string& input, bool ignore_ws) { return hex_decode(input.data(), input.size(), ignore_ws); } } /* * Cipher Modes * (C) 2015 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #if defined(BOTAN_HAS_BLOCK_CIPHER) #endif #if defined(BOTAN_HAS_AEAD_MODES) #endif #if defined(BOTAN_HAS_MODE_CBC) #endif #if defined(BOTAN_HAS_MODE_CFB) #endif #if defined(BOTAN_HAS_MODE_XTS) #endif #if defined(BOTAN_HAS_OPENSSL) #endif #if defined(BOTAN_HAS_COMMONCRYPTO) #endif namespace Botan { std::unique_ptr Cipher_Mode::create_or_throw(const std::string& algo, Cipher_Dir direction, const std::string& provider) { if(auto mode = Cipher_Mode::create(algo, direction, provider)) return mode; throw Lookup_Error("Cipher mode", algo, provider); } std::unique_ptr Cipher_Mode::create(const std::string& algo, Cipher_Dir direction, const std::string& provider) { #if defined(BOTAN_HAS_COMMONCRYPTO) if(provider.empty() || provider == "commoncrypto") { std::unique_ptr commoncrypto_cipher(make_commoncrypto_cipher_mode(algo, direction)); if(commoncrypto_cipher) return commoncrypto_cipher; if(!provider.empty()) return std::unique_ptr(); } #endif #if defined(BOTAN_HAS_OPENSSL) if(provider.empty() || provider == "openssl") { std::unique_ptr openssl_cipher(make_openssl_cipher_mode(algo, direction)); if(openssl_cipher) return openssl_cipher; if(!provider.empty()) return std::unique_ptr(); } #endif #if defined(BOTAN_HAS_STREAM_CIPHER) if(auto sc = StreamCipher::create(algo)) { return std::unique_ptr(new Stream_Cipher_Mode(sc.release())); } #endif #if defined(BOTAN_HAS_AEAD_MODES) if(auto aead = AEAD_Mode::create(algo, direction)) { return std::unique_ptr(aead.release()); } #endif if(algo.find('/') != std::string::npos) { const std::vector algo_parts = split_on(algo, '/'); const std::string cipher_name = algo_parts[0]; const std::vector mode_info = parse_algorithm_name(algo_parts[1]); if(mode_info.empty()) return std::unique_ptr(); std::ostringstream alg_args; alg_args << '(' << cipher_name; for(size_t i = 1; i < mode_info.size(); ++i) alg_args << ',' << mode_info[i]; for(size_t i = 2; i < algo_parts.size(); ++i) alg_args << ',' << algo_parts[i]; alg_args << ')'; const std::string mode_name = mode_info[0] + alg_args.str(); return Cipher_Mode::create(mode_name, direction, provider); } #if defined(BOTAN_HAS_BLOCK_CIPHER) SCAN_Name spec(algo); if(spec.arg_count() == 0) { return std::unique_ptr(); } std::unique_ptr bc(BlockCipher::create(spec.arg(0), provider)); if(!bc) { return std::unique_ptr(); } #if defined(BOTAN_HAS_MODE_CBC) if(spec.algo_name() == "CBC") { const std::string padding = spec.arg(1, "PKCS7"); if(padding == "CTS") { if(direction == ENCRYPTION) return std::unique_ptr(new CTS_Encryption(bc.release())); else return std::unique_ptr(new CTS_Decryption(bc.release())); } else { std::unique_ptr pad(get_bc_pad(padding)); if(pad) { if(direction == ENCRYPTION) return std::unique_ptr(new CBC_Encryption(bc.release(), pad.release())); else return std::unique_ptr(new CBC_Decryption(bc.release(), pad.release())); } } } #endif #if defined(BOTAN_HAS_MODE_XTS) if(spec.algo_name() == "XTS") { if(direction == ENCRYPTION) return std::unique_ptr(new XTS_Encryption(bc.release())); else return std::unique_ptr(new XTS_Decryption(bc.release())); } #endif #if defined(BOTAN_HAS_MODE_CFB) if(spec.algo_name() == "CFB") { const size_t feedback_bits = spec.arg_as_integer(1, 8*bc->block_size()); if(direction == ENCRYPTION) return std::unique_ptr(new CFB_Encryption(bc.release(), feedback_bits)); else return std::unique_ptr(new CFB_Decryption(bc.release(), feedback_bits)); } #endif #endif return std::unique_ptr(); } //static std::vector Cipher_Mode::providers(const std::string& algo_spec) { const std::vector& possible = { "base", "openssl", "commoncrypto" }; std::vector providers; for(auto&& prov : possible) { std::unique_ptr mode = Cipher_Mode::create(algo_spec, ENCRYPTION, prov); if(mode) { providers.push_back(prov); // available } } return providers; } } /* * (C) 2016 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #if defined(BOTAN_HAS_AUTO_SEEDING_RNG) #endif namespace Botan { void RandomNumberGenerator::randomize_with_ts_input(uint8_t output[], size_t output_len) { if(this->accepts_input()) { /* Form additional input which is provided to the PRNG implementation to paramaterize the KDF output. */ uint8_t additional_input[16] = { 0 }; store_le(OS::get_system_timestamp_ns(), additional_input); store_le(OS::get_high_resolution_clock(), additional_input + 8); this->randomize_with_input(output, output_len, additional_input, sizeof(additional_input)); } else { this->randomize(output, output_len); } } void RandomNumberGenerator::randomize_with_input(uint8_t output[], size_t output_len, const uint8_t input[], size_t input_len) { this->add_entropy(input, input_len); this->randomize(output, output_len); } size_t RandomNumberGenerator::reseed(Entropy_Sources& srcs, size_t poll_bits, std::chrono::milliseconds poll_timeout) { if(this->accepts_input()) { return srcs.poll(*this, poll_bits, poll_timeout); } else { return 0; } } void RandomNumberGenerator::reseed_from_rng(RandomNumberGenerator& rng, size_t poll_bits) { if(this->accepts_input()) { secure_vector buf(poll_bits / 8); rng.randomize(buf.data(), buf.size()); this->add_entropy(buf.data(), buf.size()); } } RandomNumberGenerator* RandomNumberGenerator::make_rng() { #if defined(BOTAN_HAS_AUTO_SEEDING_RNG) return new AutoSeeded_RNG; #else throw Not_Implemented("make_rng failed, no AutoSeeded_RNG in this build"); #endif } #if defined(BOTAN_TARGET_OS_HAS_THREADS) #if defined(BOTAN_HAS_AUTO_SEEDING_RNG) Serialized_RNG::Serialized_RNG() : m_rng(new AutoSeeded_RNG) {} #else Serialized_RNG::Serialized_RNG() { throw Not_Implemented("Serialized_RNG default constructor failed: AutoSeeded_RNG disabled in build"); } #endif #endif } /* * Stream Ciphers * (C) 2015,2016 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #if defined(BOTAN_HAS_CHACHA) #endif #if defined(BOTAN_HAS_SALSA20) #endif #if defined(BOTAN_HAS_SHAKE_CIPHER) #endif #if defined(BOTAN_HAS_CTR_BE) #endif #if defined(BOTAN_HAS_OFB) #endif #if defined(BOTAN_HAS_RC4) #endif #if defined(BOTAN_HAS_OPENSSL) #endif namespace Botan { std::unique_ptr StreamCipher::create(const std::string& algo_spec, const std::string& provider) { const SCAN_Name req(algo_spec); #if defined(BOTAN_HAS_CTR_BE) if((req.algo_name() == "CTR-BE" || req.algo_name() == "CTR") && req.arg_count_between(1,2)) { if(provider.empty() || provider == "base") { auto cipher = BlockCipher::create(req.arg(0)); if(cipher) { size_t ctr_size = req.arg_as_integer(1, cipher->block_size()); return std::unique_ptr(new CTR_BE(cipher.release(), ctr_size)); } } } #endif #if defined(BOTAN_HAS_CHACHA) if(req.algo_name() == "ChaCha") { if(provider.empty() || provider == "base") return std::unique_ptr(new ChaCha(req.arg_as_integer(0, 20))); } if(req.algo_name() == "ChaCha20") { if(provider.empty() || provider == "base") return std::unique_ptr(new ChaCha(20)); } #endif #if defined(BOTAN_HAS_SALSA20) if(req.algo_name() == "Salsa20") { if(provider.empty() || provider == "base") return std::unique_ptr(new Salsa20); } #endif #if defined(BOTAN_HAS_SHAKE_CIPHER) if(req.algo_name() == "SHAKE-128" || req.algo_name() == "SHAKE-128-XOF") { if(provider.empty() || provider == "base") return std::unique_ptr(new SHAKE_128_Cipher); } #endif #if defined(BOTAN_HAS_OFB) if(req.algo_name() == "OFB" && req.arg_count() == 1) { if(provider.empty() || provider == "base") { if(auto c = BlockCipher::create(req.arg(0))) return std::unique_ptr(new OFB(c.release())); } } #endif #if defined(BOTAN_HAS_RC4) if(req.algo_name() == "RC4" || req.algo_name() == "ARC4" || req.algo_name() == "MARK-4") { const size_t skip = (req.algo_name() == "MARK-4") ? 256 : req.arg_as_integer(0, 0); #if defined(BOTAN_HAS_OPENSSL) if(provider.empty() || provider == "openssl") { return std::unique_ptr(make_openssl_rc4(skip)); } #endif if(provider.empty() || provider == "base") { return std::unique_ptr(new RC4(skip)); } } #endif BOTAN_UNUSED(req); BOTAN_UNUSED(provider); return nullptr; } //static std::unique_ptr StreamCipher::create_or_throw(const std::string& algo, const std::string& provider) { if(auto sc = StreamCipher::create(algo, provider)) { return sc; } throw Lookup_Error("Stream cipher", algo, provider); } std::vector StreamCipher::providers(const std::string& algo_spec) { return probe_providers_of(algo_spec, {"base", "openssl"}); } } /* * Runtime assertion checking * (C) 2010,2012,2018 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { void throw_invalid_argument(const char* message, const char* func, const char* file) { std::ostringstream format; format << message << " in " << func << ":" << file; throw Invalid_Argument(format.str()); } void throw_invalid_state(const char* expr, const char* func, const char* file) { std::ostringstream format; format << "Invalid state: " << expr << " was false in " << func << ":" << file; throw Invalid_State(format.str()); } void assertion_failure(const char* expr_str, const char* assertion_made, const char* func, const char* file, int line) { std::ostringstream format; format << "False assertion "; if(assertion_made && assertion_made[0] != 0) format << "'" << assertion_made << "' (expression " << expr_str << ") "; else format << expr_str << " "; if(func) format << "in " << func << " "; format << "@" << file << ":" << line; throw Internal_Error(format.str()); } } /* * Calendar Functions * (C) 1999-2010,2017 Jack Lloyd * (C) 2015 Simon Warta (Kullo GmbH) * * Botan is released under the Simplified BSD License (see license.txt) */ #include #include #include namespace Botan { namespace { std::tm do_gmtime(std::time_t time_val) { std::tm tm; #if defined(BOTAN_TARGET_OS_HAS_WIN32) ::gmtime_s(&tm, &time_val); // Windows #elif defined(BOTAN_TARGET_OS_HAS_POSIX1) ::gmtime_r(&time_val, &tm); // Unix/SUSv2 #else std::tm* tm_p = std::gmtime(&time_val); if (tm_p == nullptr) throw Encoding_Error("time_t_to_tm could not convert"); tm = *tm_p; #endif return tm; } /* Portable replacement for timegm, _mkgmtime, etc Algorithm due to Howard Hinnant See https://howardhinnant.github.io/date_algorithms.html#days_from_civil for details and explaination. The code is slightly simplified by our assumption that the date is at least 1970, which is sufficient for our purposes. */ size_t days_since_epoch(uint32_t year, uint32_t month, uint32_t day) { if(month <= 2) year -= 1; const uint32_t era = year / 400; const uint32_t yoe = year - era * 400; // [0, 399] const uint32_t doy = (153*(month + (month > 2 ? -3 : 9)) + 2)/5 + day-1; // [0, 365] const uint32_t doe = yoe * 365 + yoe/4 - yoe/100 + doy; // [0, 146096] return era * 146097 + doe - 719468; } } std::chrono::system_clock::time_point calendar_point::to_std_timepoint() const { if(get_year() < 1970) throw Invalid_Argument("calendar_point::to_std_timepoint() does not support years before 1970"); // 32 bit time_t ends at January 19, 2038 // https://msdn.microsoft.com/en-us/library/2093ets1.aspx // Throw after 2037 if 32 bit time_t is used BOTAN_IF_CONSTEXPR(sizeof(std::time_t) == 4) { if(get_year() > 2037) { throw Invalid_Argument("calendar_point::to_std_timepoint() does not support years after 2037 on this system"); } } // This upper bound is completely arbitrary if(get_year() >= 2400) { throw Invalid_Argument("calendar_point::to_std_timepoint() does not support years after 2400"); } const uint64_t seconds_64 = (days_since_epoch(get_year(), get_month(), get_day()) * 86400) + (get_hour() * 60 * 60) + (get_minutes() * 60) + get_seconds(); const time_t seconds_time_t = static_cast(seconds_64); if(seconds_64 - seconds_time_t != 0) { throw Invalid_Argument("calendar_point::to_std_timepoint time_t overflow"); } return std::chrono::system_clock::from_time_t(seconds_time_t); } std::string calendar_point::to_string() const { // desired format: --
T:: std::stringstream output; output << std::setfill('0') << std::setw(4) << get_year() << "-" << std::setw(2) << get_month() << "-" << std::setw(2) << get_day() << "T" << std::setw(2) << get_hour() << ":" << std::setw(2) << get_minutes() << ":" << std::setw(2) << get_seconds(); return output.str(); } calendar_point calendar_value( const std::chrono::system_clock::time_point& time_point) { std::tm tm = do_gmtime(std::chrono::system_clock::to_time_t(time_point)); return calendar_point(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); } } /* * Character Set Handling * (C) 1999-2007 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include namespace Botan { namespace { void append_utf8_for(std::string& s, uint32_t c) { if(c >= 0xD800 && c < 0xE000) throw Decoding_Error("Invalid Unicode character"); if(c <= 0x7F) { const uint8_t b0 = static_cast(c); s.push_back(static_cast(b0)); } else if(c <= 0x7FF) { const uint8_t b0 = 0xC0 | static_cast(c >> 6); const uint8_t b1 = 0x80 | static_cast(c & 0x3F); s.push_back(static_cast(b0)); s.push_back(static_cast(b1)); } else if(c <= 0xFFFF) { const uint8_t b0 = 0xE0 | static_cast(c >> 12); const uint8_t b1 = 0x80 | static_cast((c >> 6) & 0x3F); const uint8_t b2 = 0x80 | static_cast(c & 0x3F); s.push_back(static_cast(b0)); s.push_back(static_cast(b1)); s.push_back(static_cast(b2)); } else if(c <= 0x10FFFF) { const uint8_t b0 = 0xF0 | static_cast(c >> 18); const uint8_t b1 = 0x80 | static_cast((c >> 12) & 0x3F); const uint8_t b2 = 0x80 | static_cast((c >> 6) & 0x3F); const uint8_t b3 = 0x80 | static_cast(c & 0x3F); s.push_back(static_cast(b0)); s.push_back(static_cast(b1)); s.push_back(static_cast(b2)); s.push_back(static_cast(b3)); } else throw Decoding_Error("Invalid Unicode character"); } } std::string ucs2_to_utf8(const uint8_t ucs2[], size_t len) { if(len % 2 != 0) throw Decoding_Error("Invalid length for UCS-2 string"); const size_t chars = len / 2; std::string s; for(size_t i = 0; i != chars; ++i) { const uint16_t c = load_be(ucs2, i); append_utf8_for(s, c); } return s; } std::string ucs4_to_utf8(const uint8_t ucs4[], size_t len) { if(len % 4 != 0) throw Decoding_Error("Invalid length for UCS-4 string"); const size_t chars = len / 4; std::string s; for(size_t i = 0; i != chars; ++i) { const uint32_t c = load_be(ucs4, i); append_utf8_for(s, c); } return s; } /* * Convert from UTF-8 to ISO 8859-1 */ std::string utf8_to_latin1(const std::string& utf8) { std::string iso8859; size_t position = 0; while(position != utf8.size()) { const uint8_t c1 = static_cast(utf8[position++]); if(c1 <= 0x7F) { iso8859 += static_cast(c1); } else if(c1 >= 0xC0 && c1 <= 0xC7) { if(position == utf8.size()) throw Decoding_Error("UTF-8: sequence truncated"); const uint8_t c2 = static_cast(utf8[position++]); const uint8_t iso_char = ((c1 & 0x07) << 6) | (c2 & 0x3F); if(iso_char <= 0x7F) throw Decoding_Error("UTF-8: sequence longer than needed"); iso8859 += static_cast(iso_char); } else throw Decoding_Error("UTF-8: Unicode chars not in Latin1 used"); } return iso8859; } namespace Charset { namespace { /* * Convert from UCS-2 to ISO 8859-1 */ std::string ucs2_to_latin1(const std::string& ucs2) { if(ucs2.size() % 2 == 1) throw Decoding_Error("UCS-2 string has an odd number of bytes"); std::string latin1; for(size_t i = 0; i != ucs2.size(); i += 2) { const uint8_t c1 = ucs2[i]; const uint8_t c2 = ucs2[i+1]; if(c1 != 0) throw Decoding_Error("UCS-2 has non-Latin1 characters"); latin1 += static_cast(c2); } return latin1; } /* * Convert from ISO 8859-1 to UTF-8 */ std::string latin1_to_utf8(const std::string& iso8859) { std::string utf8; for(size_t i = 0; i != iso8859.size(); ++i) { const uint8_t c = static_cast(iso8859[i]); if(c <= 0x7F) utf8 += static_cast(c); else { utf8 += static_cast((0xC0 | (c >> 6))); utf8 += static_cast((0x80 | (c & 0x3F))); } } return utf8; } } /* * Perform character set transcoding */ std::string transcode(const std::string& str, Character_Set to, Character_Set from) { if(to == LOCAL_CHARSET) to = LATIN1_CHARSET; if(from == LOCAL_CHARSET) from = LATIN1_CHARSET; if(to == from) return str; if(from == LATIN1_CHARSET && to == UTF8_CHARSET) return latin1_to_utf8(str); if(from == UTF8_CHARSET && to == LATIN1_CHARSET) return utf8_to_latin1(str); if(from == UCS2_CHARSET && to == LATIN1_CHARSET) return ucs2_to_latin1(str); throw Invalid_Argument("Unknown transcoding operation from " + std::to_string(from) + " to " + std::to_string(to)); } /* * Check if a character represents a digit */ bool is_digit(char c) { if(c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7' || c == '8' || c == '9') return true; return false; } /* * Check if a character represents whitespace */ bool is_space(char c) { if(c == ' ' || c == '\t' || c == '\n' || c == '\r') return true; return false; } /* * Convert a character to a digit */ uint8_t char2digit(char c) { switch(c) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; } throw Invalid_Argument("char2digit: Input is not a digit character"); } /* * Convert a digit to a character */ char digit2char(uint8_t b) { switch(b) { case 0: return '0'; case 1: return '1'; case 2: return '2'; case 3: return '3'; case 4: return '4'; case 5: return '5'; case 6: return '6'; case 7: return '7'; case 8: return '8'; case 9: return '9'; } throw Invalid_Argument("digit2char: Input is not a digit"); } /* * Case-insensitive character comparison */ bool caseless_cmp(char a, char b) { return (std::tolower(static_cast(a)) == std::tolower(static_cast(b))); } } } /* * (C) 2018 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { namespace CT { secure_vector copy_output(CT::Mask bad_input, const uint8_t input[], size_t input_length, size_t offset) { if(input_length == 0) return secure_vector(); /* * Ensure at runtime that offset <= input_length. This is an invalid input, * but we can't throw without using the poisoned value. Instead, if it happens, * set offset to be equal to the input length (so output_bytes becomes 0 and * the returned vector is empty) */ const auto valid_offset = CT::Mask::is_lte(offset, input_length); offset = valid_offset.select(offset, input_length); const size_t output_bytes = input_length - offset; secure_vector output(input_length); /* Move the desired output bytes to the front using a slow (O^n) but constant time loop that does not leak the value of the offset */ for(size_t i = 0; i != input_length; ++i) { /* start index from i rather than 0 since we know j must be >= i + offset to have any effect, and starting from i does not reveal information */ for(size_t j = i; j != input_length; ++j) { const uint8_t b = input[j]; const auto is_eq = CT::Mask::is_equal(j, offset + i); output[i] |= is_eq.if_set_return(b); } } bad_input.if_set_zero_out(output.data(), output.size()); CT::unpoison(output.data(), output.size()); CT::unpoison(output_bytes); /* This is potentially not const time, depending on how std::vector is implemented. But since we are always reducing length, it should just amount to setting the member var holding the length. */ output.resize(output_bytes); return output; } secure_vector strip_leading_zeros(const uint8_t in[], size_t length) { size_t leading_zeros = 0; auto only_zeros = Mask::set(); for(size_t i = 0; i != length; ++i) { only_zeros &= CT::Mask::is_zero(in[i]); leading_zeros += only_zeros.if_set_return(1); } return copy_output(CT::Mask::cleared(), in, length, leading_zeros); } } } /* * DataSource * (C) 1999-2007 Jack Lloyd * 2005 Matthew Gregan * * Botan is released under the Simplified BSD License (see license.txt) */ #include #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) #include #endif namespace Botan { /* * Read a single byte from the DataSource */ size_t DataSource::read_byte(uint8_t& out) { return read(&out, 1); } /* * Peek a single byte from the DataSource */ size_t DataSource::peek_byte(uint8_t& out) const { return peek(&out, 1, 0); } /* * Discard the next N bytes of the data */ size_t DataSource::discard_next(size_t n) { uint8_t buf[64] = { 0 }; size_t discarded = 0; while(n) { const size_t got = this->read(buf, std::min(n, sizeof(buf))); discarded += got; n -= got; if(got == 0) break; } return discarded; } /* * Read from a memory buffer */ size_t DataSource_Memory::read(uint8_t out[], size_t length) { const size_t got = std::min(m_source.size() - m_offset, length); copy_mem(out, m_source.data() + m_offset, got); m_offset += got; return got; } bool DataSource_Memory::check_available(size_t n) { return (n <= (m_source.size() - m_offset)); } /* * Peek into a memory buffer */ size_t DataSource_Memory::peek(uint8_t out[], size_t length, size_t peek_offset) const { const size_t bytes_left = m_source.size() - m_offset; if(peek_offset >= bytes_left) return 0; const size_t got = std::min(bytes_left - peek_offset, length); copy_mem(out, &m_source[m_offset + peek_offset], got); return got; } /* * Check if the memory buffer is empty */ bool DataSource_Memory::end_of_data() const { return (m_offset == m_source.size()); } /* * DataSource_Memory Constructor */ DataSource_Memory::DataSource_Memory(const std::string& in) : m_source(cast_char_ptr_to_uint8(in.data()), cast_char_ptr_to_uint8(in.data()) + in.length()), m_offset(0) { } /* * Read from a stream */ size_t DataSource_Stream::read(uint8_t out[], size_t length) { m_source.read(cast_uint8_ptr_to_char(out), length); if(m_source.bad()) throw Stream_IO_Error("DataSource_Stream::read: Source failure"); const size_t got = static_cast(m_source.gcount()); m_total_read += got; return got; } bool DataSource_Stream::check_available(size_t n) { const std::streampos orig_pos = m_source.tellg(); m_source.seekg(0, std::ios::end); const size_t avail = static_cast(m_source.tellg() - orig_pos); m_source.seekg(orig_pos); return (avail >= n); } /* * Peek into a stream */ size_t DataSource_Stream::peek(uint8_t out[], size_t length, size_t offset) const { if(end_of_data()) throw Invalid_State("DataSource_Stream: Cannot peek when out of data"); size_t got = 0; if(offset) { secure_vector buf(offset); m_source.read(cast_uint8_ptr_to_char(buf.data()), buf.size()); if(m_source.bad()) throw Stream_IO_Error("DataSource_Stream::peek: Source failure"); got = static_cast(m_source.gcount()); } if(got == offset) { m_source.read(cast_uint8_ptr_to_char(out), length); if(m_source.bad()) throw Stream_IO_Error("DataSource_Stream::peek: Source failure"); got = static_cast(m_source.gcount()); } if(m_source.eof()) m_source.clear(); m_source.seekg(m_total_read, std::ios::beg); return got; } /* * Check if the stream is empty or in error */ bool DataSource_Stream::end_of_data() const { return (!m_source.good()); } /* * Return a human-readable ID for this stream */ std::string DataSource_Stream::id() const { return m_identifier; } #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) /* * DataSource_Stream Constructor */ DataSource_Stream::DataSource_Stream(const std::string& path, bool use_binary) : m_identifier(path), m_source_memory(new std::ifstream(path, use_binary ? std::ios::binary : std::ios::in)), m_source(*m_source_memory), m_total_read(0) { if(!m_source.good()) { throw Stream_IO_Error("DataSource: Failure opening file " + path); } } #endif /* * DataSource_Stream Constructor */ DataSource_Stream::DataSource_Stream(std::istream& in, const std::string& name) : m_identifier(name), m_source(in), m_total_read(0) { } DataSource_Stream::~DataSource_Stream() { // for ~unique_ptr } } /* * (C) 2017 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { std::string to_string(ErrorType type) { switch(type) { case ErrorType::Unknown: return "Unknown"; case ErrorType::SystemError: return "SystemError"; case ErrorType::NotImplemented: return "NotImplemented"; case ErrorType::OutOfMemory: return "OutOfMemory"; case ErrorType::InternalError: return "InternalError"; case ErrorType::IoError: return "IoError"; case ErrorType::InvalidObjectState : return "InvalidObjectState"; case ErrorType::KeyNotSet: return "KeyNotSet"; case ErrorType::InvalidArgument: return "InvalidArgument"; case ErrorType::InvalidKeyLength: return "InvalidKeyLength"; case ErrorType::InvalidNonceLength: return "InvalidNonceLength"; case ErrorType::LookupError: return "LookupError"; case ErrorType::EncodingFailure: return "EncodingFailure"; case ErrorType::DecodingFailure: return "DecodingFailure"; case ErrorType::TLSError: return "TLSError"; case ErrorType::HttpError: return "HttpError"; case ErrorType::InvalidTag: return "InvalidTag"; case ErrorType::RoughtimeError: return "RoughtimeError"; case ErrorType::OpenSSLError : return "OpenSSLError"; case ErrorType::CommonCryptoError: return "CommonCryptoError"; case ErrorType::Pkcs11Error: return "Pkcs11Error"; case ErrorType::TPMError: return "TPMError"; case ErrorType::DatabaseError: return "DatabaseError"; case ErrorType::ZlibError : return "ZlibError"; case ErrorType::Bzip2Error: return "Bzip2Error" ; case ErrorType::LzmaError: return "LzmaError"; } // No default case in above switch so compiler warns return "Unrecognized Botan error"; } Exception::Exception(const std::string& msg) : m_msg(msg) {} Exception::Exception(const std::string& msg, const std::exception& e) : m_msg(msg + " failed with " + std::string(e.what())) {} Exception::Exception(const char* prefix, const std::string& msg) : m_msg(std::string(prefix) + " " + msg) {} Invalid_Argument::Invalid_Argument(const std::string& msg) : Exception(msg) {} Invalid_Argument::Invalid_Argument(const std::string& msg, const std::string& where) : Exception(msg + " in " + where) {} Invalid_Argument::Invalid_Argument(const std::string& msg, const std::exception& e) : Exception(msg, e) {} Lookup_Error::Lookup_Error(const std::string& type, const std::string& algo, const std::string& provider) : Exception("Unavailable " + type + " " + algo + (provider.empty() ? std::string("") : (" for provider " + provider))) {} Internal_Error::Internal_Error(const std::string& err) : Exception("Internal error: " + err) {} Invalid_Key_Length::Invalid_Key_Length(const std::string& name, size_t length) : Invalid_Argument(name + " cannot accept a key of length " + std::to_string(length)) {} Invalid_IV_Length::Invalid_IV_Length(const std::string& mode, size_t bad_len) : Invalid_Argument("IV length " + std::to_string(bad_len) + " is invalid for " + mode) {} Key_Not_Set::Key_Not_Set(const std::string& algo) : Invalid_State("Key not set in " + algo) {} Policy_Violation::Policy_Violation(const std::string& err) : Invalid_State("Policy violation: " + err) {} PRNG_Unseeded::PRNG_Unseeded(const std::string& algo) : Invalid_State("PRNG not seeded: " + algo) {} Algorithm_Not_Found::Algorithm_Not_Found(const std::string& name) : Lookup_Error("Could not find any algorithm named \"" + name + "\"") {} No_Provider_Found::No_Provider_Found(const std::string& name) : Exception("Could not find any provider for algorithm named \"" + name + "\"") {} Provider_Not_Found::Provider_Not_Found(const std::string& algo, const std::string& provider) : Lookup_Error("Could not find provider '" + provider + "' for " + algo) {} Invalid_Algorithm_Name::Invalid_Algorithm_Name(const std::string& name): Invalid_Argument("Invalid algorithm name: " + name) {} Encoding_Error::Encoding_Error(const std::string& name) : Invalid_Argument("Encoding error: " + name) {} Decoding_Error::Decoding_Error(const std::string& name) : Invalid_Argument(name) {} Decoding_Error::Decoding_Error(const std::string& msg, const std::exception& e) : Invalid_Argument(msg, e) {} Decoding_Error::Decoding_Error(const std::string& name, const char* exception_message) : Invalid_Argument(name + " failed with exception " + exception_message) {} Invalid_Authentication_Tag::Invalid_Authentication_Tag(const std::string& msg) : Exception("Invalid authentication tag: " + msg) {} Invalid_OID::Invalid_OID(const std::string& oid) : Decoding_Error("Invalid ASN.1 OID: " + oid) {} Stream_IO_Error::Stream_IO_Error(const std::string& err) : Exception("I/O error: " + err) {} System_Error::System_Error(const std::string& msg, int err_code) : Exception(msg + " error code " + std::to_string(err_code)), m_error_code(err_code) {} Self_Test_Failure::Self_Test_Failure(const std::string& err) : Internal_Error("Self test failed: " + err) {} Not_Implemented::Not_Implemented(const std::string& err) : Exception("Not implemented", err) {} } /* * (C) 2015,2017,2019 Jack Lloyd * (C) 2015 Simon Warta (Kullo GmbH) * * Botan is released under the Simplified BSD License (see license.txt) */ #include #include #if defined(BOTAN_TARGET_OS_HAS_POSIX1) #include #include #include #include #elif defined(BOTAN_TARGET_OS_HAS_WIN32) #define NOMINMAX 1 #define _WINSOCKAPI_ // stop windows.h including winsock.h #include #endif namespace Botan { namespace { #if defined(BOTAN_TARGET_OS_HAS_POSIX1) std::vector impl_readdir(const std::string& dir_path) { std::vector out; std::deque dir_list; dir_list.push_back(dir_path); while(!dir_list.empty()) { const std::string cur_path = dir_list[0]; dir_list.pop_front(); std::unique_ptr> dir(::opendir(cur_path.c_str()), ::closedir); if(dir) { while(struct dirent* dirent = ::readdir(dir.get())) { const std::string filename = dirent->d_name; if(filename == "." || filename == "..") continue; const std::string full_path = cur_path + "/" + filename; struct stat stat_buf; if(::stat(full_path.c_str(), &stat_buf) == -1) continue; if(S_ISDIR(stat_buf.st_mode)) dir_list.push_back(full_path); else if(S_ISREG(stat_buf.st_mode)) out.push_back(full_path); } } } return out; } #elif defined(BOTAN_TARGET_OS_HAS_WIN32) std::vector impl_win32(const std::string& dir_path) { std::vector out; std::deque dir_list; dir_list.push_back(dir_path); while(!dir_list.empty()) { const std::string cur_path = dir_list[0]; dir_list.pop_front(); WIN32_FIND_DATAA find_data; HANDLE dir = ::FindFirstFileA((cur_path + "/*").c_str(), &find_data); if(dir != INVALID_HANDLE_VALUE) { do { const std::string filename = find_data.cFileName; if(filename == "." || filename == "..") continue; const std::string full_path = cur_path + "/" + filename; if(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { dir_list.push_back(full_path); } else { out.push_back(full_path); } } while(::FindNextFileA(dir, &find_data)); } ::FindClose(dir); } return out; } #endif } bool has_filesystem_impl() { #if defined(BOTAN_TARGET_OS_HAS_POSIX1) return true; #elif defined(BOTAN_TARGET_OS_HAS_WIN32) return true; #else return false; #endif } std::vector get_files_recursive(const std::string& dir) { std::vector files; #if defined(BOTAN_TARGET_OS_HAS_POSIX1) files = impl_readdir(dir); #elif defined(BOTAN_TARGET_OS_HAS_WIN32) files = impl_win32(dir); #else BOTAN_UNUSED(dir); throw No_Filesystem_Access(); #endif std::sort(files.begin(), files.end()); return files; } } /* * (C) 2017 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include #include #if defined(BOTAN_HAS_LOCKING_ALLOCATOR) #endif namespace Botan { BOTAN_MALLOC_FN void* allocate_memory(size_t elems, size_t elem_size) { if(elems == 0 || elem_size == 0) return nullptr; #if defined(BOTAN_HAS_LOCKING_ALLOCATOR) if(void* p = mlock_allocator::instance().allocate(elems, elem_size)) return p; #endif void* ptr = std::calloc(elems, elem_size); if(!ptr) throw std::bad_alloc(); return ptr; } void deallocate_memory(void* p, size_t elems, size_t elem_size) { if(p == nullptr) return; secure_scrub_memory(p, elems * elem_size); #if defined(BOTAN_HAS_LOCKING_ALLOCATOR) if(mlock_allocator::instance().deallocate(p, elems, elem_size)) return; #endif std::free(p); } void initialize_allocator() { #if defined(BOTAN_HAS_LOCKING_ALLOCATOR) mlock_allocator::instance(); #endif } uint8_t ct_compare_u8(const uint8_t x[], const uint8_t y[], size_t len) { volatile uint8_t difference = 0; for(size_t i = 0; i != len; ++i) difference |= (x[i] ^ y[i]); return CT::Mask::is_zero(difference).value(); } } /* * OS and machine specific utility functions * (C) 2015,2016,2017,2018 Jack Lloyd * (C) 2016 Daniel Neus * * Botan is released under the Simplified BSD License (see license.txt) */ #include #if defined(BOTAN_TARGET_OS_HAS_THREADS) #include #endif #if defined(BOTAN_TARGET_OS_HAS_EXPLICIT_BZERO) #include #endif #if defined(BOTAN_TARGET_OS_HAS_POSIX1) #include #include #include #include #include #include #include #include #undef B0 #endif #if defined(BOTAN_TARGET_OS_IS_EMSCRIPTEN) #include #endif #if defined(BOTAN_TARGET_OS_HAS_GETAUXVAL) || defined(BOTAN_TARGET_OS_IS_ANDROID) || \ defined(BOTAN_TARGET_OS_HAS_ELF_AUX_INFO) #include #endif #if defined(BOTAN_TARGET_OS_HAS_WIN32) #define NOMINMAX 1 #define _WINSOCKAPI_ // stop windows.h including winsock.h #include #endif #if defined(BOTAN_TARGET_OS_IS_ANDROID) #include extern "C" char **environ; #endif #if defined(BOTAN_TARGET_OS_IS_IOS) || defined(BOTAN_TARGET_OS_IS_MACOS) #include #endif namespace Botan { // Not defined in OS namespace for historical reasons void secure_scrub_memory(void* ptr, size_t n) { #if defined(BOTAN_TARGET_OS_HAS_RTLSECUREZEROMEMORY) ::RtlSecureZeroMemory(ptr, n); #elif defined(BOTAN_TARGET_OS_HAS_EXPLICIT_BZERO) ::explicit_bzero(ptr, n); #elif defined(BOTAN_TARGET_OS_HAS_EXPLICIT_MEMSET) (void)::explicit_memset(ptr, 0, n); #elif defined(BOTAN_USE_VOLATILE_MEMSET_FOR_ZERO) && (BOTAN_USE_VOLATILE_MEMSET_FOR_ZERO == 1) /* Call memset through a static volatile pointer, which the compiler should not elide. This construct should be safe in conforming compilers, but who knows. I did confirm that on x86-64 GCC 6.1 and Clang 3.8 both create code that saves the memset address in the data segment and unconditionally loads and jumps to that address. */ static void* (*const volatile memset_ptr)(void*, int, size_t) = std::memset; (memset_ptr)(ptr, 0, n); #else volatile uint8_t* p = reinterpret_cast(ptr); for(size_t i = 0; i != n; ++i) p[i] = 0; #endif } uint32_t OS::get_process_id() { #if defined(BOTAN_TARGET_OS_HAS_POSIX1) return ::getpid(); #elif defined(BOTAN_TARGET_OS_HAS_WIN32) return ::GetCurrentProcessId(); #elif defined(BOTAN_TARGET_OS_IS_INCLUDEOS) || defined(BOTAN_TARGET_OS_IS_LLVM) || defined(BOTAN_TARGET_OS_IS_NONE) return 0; // truly no meaningful value #else #error "Missing get_process_id" #endif } unsigned long OS::get_auxval(unsigned long id) { #if defined(BOTAN_TARGET_OS_HAS_GETAUXVAL) return ::getauxval(id); #elif defined(BOTAN_TARGET_OS_IS_ANDROID) && defined(BOTAN_TARGET_ARCH_IS_ARM32) if(id == 0) return 0; char **p = environ; while(*p++ != nullptr) ; Elf32_auxv_t *e = reinterpret_cast(p); while(e != nullptr) { if(e->a_type == id) return e->a_un.a_val; e++; } return 0; #elif defined(BOTAN_TARGET_OS_HAS_ELF_AUX_INFO) unsigned long auxinfo = 0; ::elf_aux_info(id, &auxinfo, sizeof(auxinfo)); return auxinfo; #else BOTAN_UNUSED(id); return 0; #endif } bool OS::running_in_privileged_state() { #if defined(AT_SECURE) return OS::get_auxval(AT_SECURE) != 0; #elif defined(BOTAN_TARGET_OS_HAS_POSIX1) return (::getuid() != ::geteuid()) || (::getgid() != ::getegid()); #else return false; #endif } uint64_t OS::get_cpu_cycle_counter() { uint64_t rtc = 0; #if defined(BOTAN_TARGET_OS_HAS_WIN32) LARGE_INTEGER tv; ::QueryPerformanceCounter(&tv); rtc = tv.QuadPart; #elif defined(BOTAN_USE_GCC_INLINE_ASM) #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) if(CPUID::has_rdtsc()) { uint32_t rtc_low = 0, rtc_high = 0; asm volatile("rdtsc" : "=d" (rtc_high), "=a" (rtc_low)); rtc = (static_cast(rtc_high) << 32) | rtc_low; } #elif defined(BOTAN_TARGET_ARCH_IS_PPC64) for(;;) { uint32_t rtc_low = 0, rtc_high = 0, rtc_high2 = 0; asm volatile("mftbu %0" : "=r" (rtc_high)); asm volatile("mftb %0" : "=r" (rtc_low)); asm volatile("mftbu %0" : "=r" (rtc_high2)); if(rtc_high == rtc_high2) { rtc = (static_cast(rtc_high) << 32) | rtc_low; break; } } #elif defined(BOTAN_TARGET_ARCH_IS_ALPHA) asm volatile("rpcc %0" : "=r" (rtc)); // OpenBSD does not trap access to the %tick register #elif defined(BOTAN_TARGET_ARCH_IS_SPARC64) && !defined(BOTAN_TARGET_OS_IS_OPENBSD) asm volatile("rd %%tick, %0" : "=r" (rtc)); #elif defined(BOTAN_TARGET_ARCH_IS_IA64) asm volatile("mov %0=ar.itc" : "=r" (rtc)); #elif defined(BOTAN_TARGET_ARCH_IS_S390X) asm volatile("stck 0(%0)" : : "a" (&rtc) : "memory", "cc"); #elif defined(BOTAN_TARGET_ARCH_IS_HPPA) asm volatile("mfctl 16,%0" : "=r" (rtc)); // 64-bit only? #else //#warning "OS::get_cpu_cycle_counter not implemented" #endif #endif return rtc; } size_t OS::get_cpu_total() { #if defined(BOTAN_TARGET_OS_HAS_POSIX1) && defined(_SC_NPROCESSORS_CONF) const long res = ::sysconf(_SC_NPROCESSORS_CONF); if(res > 0) return static_cast(res); #endif #if defined(BOTAN_TARGET_OS_HAS_THREADS) return static_cast(std::thread::hardware_concurrency()); #else return 1; #endif } size_t OS::get_cpu_available() { #if defined(BOTAN_TARGET_OS_HAS_POSIX1) && defined(_SC_NPROCESSORS_ONLN) const long res = ::sysconf(_SC_NPROCESSORS_ONLN); if(res > 0) return static_cast(res); #endif return OS::get_cpu_total(); } uint64_t OS::get_high_resolution_clock() { if(uint64_t cpu_clock = OS::get_cpu_cycle_counter()) return cpu_clock; #if defined(BOTAN_TARGET_OS_IS_EMSCRIPTEN) return emscripten_get_now(); #endif /* If we got here either we either don't have an asm instruction above, or (for x86) RDTSC is not available at runtime. Try some clock_gettimes and return the first one that works, or otherwise fall back to std::chrono. */ #if defined(BOTAN_TARGET_OS_HAS_CLOCK_GETTIME) // The ordering here is somewhat arbitrary... const clockid_t clock_types[] = { #if defined(CLOCK_MONOTONIC_HR) CLOCK_MONOTONIC_HR, #endif #if defined(CLOCK_MONOTONIC_RAW) CLOCK_MONOTONIC_RAW, #endif #if defined(CLOCK_MONOTONIC) CLOCK_MONOTONIC, #endif #if defined(CLOCK_PROCESS_CPUTIME_ID) CLOCK_PROCESS_CPUTIME_ID, #endif #if defined(CLOCK_THREAD_CPUTIME_ID) CLOCK_THREAD_CPUTIME_ID, #endif }; for(clockid_t clock : clock_types) { struct timespec ts; if(::clock_gettime(clock, &ts) == 0) { return (static_cast(ts.tv_sec) * 1000000000) + static_cast(ts.tv_nsec); } } #endif // Plain C++11 fallback auto now = std::chrono::high_resolution_clock::now().time_since_epoch(); return std::chrono::duration_cast(now).count(); } uint64_t OS::get_system_timestamp_ns() { #if defined(BOTAN_TARGET_OS_HAS_CLOCK_GETTIME) struct timespec ts; if(::clock_gettime(CLOCK_REALTIME, &ts) == 0) { return (static_cast(ts.tv_sec) * 1000000000) + static_cast(ts.tv_nsec); } #endif auto now = std::chrono::system_clock::now().time_since_epoch(); return std::chrono::duration_cast(now).count(); } size_t OS::system_page_size() { const size_t default_page_size = 4096; #if defined(BOTAN_TARGET_OS_HAS_POSIX1) long p = ::sysconf(_SC_PAGESIZE); if(p > 1) return static_cast(p); else return default_page_size; #elif defined(BOTAN_TARGET_OS_HAS_VIRTUAL_LOCK) BOTAN_UNUSED(default_page_size); SYSTEM_INFO sys_info; ::GetSystemInfo(&sys_info); return sys_info.dwPageSize; #else return default_page_size; #endif } size_t OS::get_memory_locking_limit() { #if defined(BOTAN_TARGET_OS_HAS_POSIX1) && defined(BOTAN_TARGET_OS_HAS_POSIX_MLOCK) && defined(RLIMIT_MEMLOCK) /* * If RLIMIT_MEMLOCK is not defined, likely the OS does not support * unprivileged mlock calls. * * Linux defaults to only 64 KiB of mlockable memory per process * (too small) but BSDs offer a small fraction of total RAM (more * than we need). Bound the total mlock size to 512 KiB which is * enough to run the entire test suite without spilling to non-mlock * memory (and thus presumably also enough for many useful * programs), but small enough that we should not cause problems * even if many processes are mlocking on the same machine. */ const size_t user_req = read_env_variable_sz("BOTAN_MLOCK_POOL_SIZE", BOTAN_MLOCK_ALLOCATOR_MAX_LOCKED_KB); const size_t mlock_requested = std::min(user_req, BOTAN_MLOCK_ALLOCATOR_MAX_LOCKED_KB); if(mlock_requested > 0) { struct ::rlimit limits; ::getrlimit(RLIMIT_MEMLOCK, &limits); if(limits.rlim_cur < limits.rlim_max) { limits.rlim_cur = limits.rlim_max; ::setrlimit(RLIMIT_MEMLOCK, &limits); ::getrlimit(RLIMIT_MEMLOCK, &limits); } return std::min(limits.rlim_cur, mlock_requested * 1024); } #elif defined(BOTAN_TARGET_OS_HAS_VIRTUAL_LOCK) SIZE_T working_min = 0, working_max = 0; if(!::GetProcessWorkingSetSize(::GetCurrentProcess(), &working_min, &working_max)) { return 0; } // According to Microsoft MSDN: // The maximum number of pages that a process can lock is equal to the number of pages in its minimum working set minus a small overhead // In the book "Windows Internals Part 2": the maximum lockable pages are minimum working set size - 8 pages // But the information in the book seems to be inaccurate/outdated // I've tested this on Windows 8.1 x64, Windows 10 x64 and Windows 7 x86 // On all three OS the value is 11 instead of 8 const size_t overhead = OS::system_page_size() * 11; if(working_min > overhead) { const size_t lockable_bytes = working_min - overhead; return std::min(lockable_bytes, BOTAN_MLOCK_ALLOCATOR_MAX_LOCKED_KB * 1024); } #endif // Not supported on this platform return 0; } bool OS::read_env_variable(std::string& value_out, const std::string& name) { value_out = ""; if(running_in_privileged_state()) return false; #if defined(BOTAN_TARGET_OS_HAS_WIN32) && defined(BOTAN_BUILD_COMPILER_IS_MSVC) char val[128] = { 0 }; size_t req_size = 0; if(getenv_s(&req_size, val, sizeof(val), name.c_str()) == 0) { value_out = std::string(val, req_size); return true; } #else if(const char* val = std::getenv(name.c_str())) { value_out = val; return true; } #endif return false; } size_t OS::read_env_variable_sz(const std::string& name, size_t def) { std::string value; if(read_env_variable(value, name)) { try { const size_t val = std::stoul(value, nullptr); return val; } catch(std::exception&) { /* ignore it */ } } return def; } #if defined(BOTAN_TARGET_OS_HAS_POSIX1) && defined(BOTAN_TARGET_OS_HAS_POSIX_MLOCK) namespace { int get_locked_fd() { #if defined(BOTAN_TARGET_OS_IS_IOS) || defined(BOTAN_TARGET_OS_IS_MACOS) // On Darwin, tagging anonymous pages allows vmmap to track these. // Allowed from 240 to 255 for userland applications static constexpr int default_locked_fd = 255; int locked_fd = default_locked_fd; if(size_t locked_fdl = OS::read_env_variable_sz("BOTAN_LOCKED_FD", default_locked_fd)) { if(locked_fdl < 240 || locked_fdl > 255) { locked_fdl = default_locked_fd; } locked_fd = static_cast(locked_fdl); } return VM_MAKE_TAG(locked_fd); #else return -1; #endif } } #endif std::vector OS::allocate_locked_pages(size_t count) { std::vector result; #if (defined(BOTAN_TARGET_OS_HAS_POSIX1) && defined(BOTAN_TARGET_OS_HAS_POSIX_MLOCK)) || defined(BOTAN_TARGET_OS_HAS_VIRTUAL_LOCK) result.reserve(count); const size_t page_size = OS::system_page_size(); #if defined(BOTAN_TARGET_OS_HAS_POSIX1) && defined(BOTAN_TARGET_OS_HAS_POSIX_MLOCK) static const int locked_fd = get_locked_fd(); #endif for(size_t i = 0; i != count; ++i) { void* ptr = nullptr; #if defined(BOTAN_TARGET_OS_HAS_POSIX1) && defined(BOTAN_TARGET_OS_HAS_POSIX_MLOCK) #if !defined(MAP_ANONYMOUS) #define MAP_ANONYMOUS MAP_ANON #endif #if !defined(MAP_NOCORE) #if defined(MAP_CONCEAL) #define MAP_NOCORE MAP_CONCEAL #else #define MAP_NOCORE 0 #endif #endif #if !defined(PROT_MAX) #define PROT_MAX(p) 0 #endif const int pflags = PROT_READ | PROT_WRITE; ptr = ::mmap(nullptr, 3*page_size, pflags | PROT_MAX(pflags), MAP_ANONYMOUS | MAP_PRIVATE | MAP_NOCORE, /*fd=*/locked_fd, /*offset=*/0); if(ptr == MAP_FAILED) { continue; } // lock the data page if(::mlock(static_cast(ptr) + page_size, page_size) != 0) { ::munmap(ptr, 3*page_size); continue; } #if defined(MADV_DONTDUMP) // we ignore errors here, as DONTDUMP is just a bonus ::madvise(static_cast(ptr) + page_size, page_size, MADV_DONTDUMP); #endif #elif defined(BOTAN_TARGET_OS_HAS_VIRTUAL_LOCK) ptr = ::VirtualAlloc(nullptr, 3*page_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if(ptr == nullptr) continue; if(::VirtualLock(static_cast(ptr) + page_size, page_size) == 0) { ::VirtualFree(ptr, 0, MEM_RELEASE); continue; } #endif std::memset(ptr, 0, 3*page_size); // zero data page and both guard pages // Make guard page preceeding the data page page_prohibit_access(static_cast(ptr)); // Make guard page following the data page page_prohibit_access(static_cast(ptr) + 2*page_size); result.push_back(static_cast(ptr) + page_size); } #else BOTAN_UNUSED(count); #endif return result; } void OS::page_allow_access(void* page) { #if defined(BOTAN_TARGET_OS_HAS_POSIX1) const size_t page_size = OS::system_page_size(); ::mprotect(page, page_size, PROT_READ | PROT_WRITE); #elif defined(BOTAN_TARGET_OS_HAS_VIRTUAL_LOCK) const size_t page_size = OS::system_page_size(); DWORD old_perms = 0; ::VirtualProtect(page, page_size, PAGE_READWRITE, &old_perms); BOTAN_UNUSED(old_perms); #else BOTAN_UNUSED(page); #endif } void OS::page_prohibit_access(void* page) { #if defined(BOTAN_TARGET_OS_HAS_POSIX1) const size_t page_size = OS::system_page_size(); ::mprotect(page, page_size, PROT_NONE); #elif defined(BOTAN_TARGET_OS_HAS_VIRTUAL_LOCK) const size_t page_size = OS::system_page_size(); DWORD old_perms = 0; ::VirtualProtect(page, page_size, PAGE_NOACCESS, &old_perms); BOTAN_UNUSED(old_perms); #else BOTAN_UNUSED(page); #endif } void OS::free_locked_pages(const std::vector& pages) { const size_t page_size = OS::system_page_size(); for(size_t i = 0; i != pages.size(); ++i) { void* ptr = pages[i]; secure_scrub_memory(ptr, page_size); // ptr points to the data page, guard pages are before and after page_allow_access(static_cast(ptr) - page_size); page_allow_access(static_cast(ptr) + page_size); #if defined(BOTAN_TARGET_OS_HAS_POSIX1) && defined(BOTAN_TARGET_OS_HAS_POSIX_MLOCK) ::munlock(ptr, page_size); ::munmap(static_cast(ptr) - page_size, 3*page_size); #elif defined(BOTAN_TARGET_OS_HAS_VIRTUAL_LOCK) ::VirtualUnlock(ptr, page_size); ::VirtualFree(static_cast(ptr) - page_size, 0, MEM_RELEASE); #endif } } #if defined(BOTAN_TARGET_OS_HAS_POSIX1) && !defined(BOTAN_TARGET_OS_IS_EMSCRIPTEN) namespace { static ::sigjmp_buf g_sigill_jmp_buf; void botan_sigill_handler(int) { siglongjmp(g_sigill_jmp_buf, /*non-zero return value*/1); } } #endif int OS::run_cpu_instruction_probe(std::function probe_fn) { volatile int probe_result = -3; #if defined(BOTAN_TARGET_OS_HAS_POSIX1) && !defined(BOTAN_TARGET_OS_IS_EMSCRIPTEN) struct sigaction old_sigaction; struct sigaction sigaction; sigaction.sa_handler = botan_sigill_handler; sigemptyset(&sigaction.sa_mask); sigaction.sa_flags = 0; int rc = ::sigaction(SIGILL, &sigaction, &old_sigaction); if(rc != 0) throw System_Error("run_cpu_instruction_probe sigaction failed", errno); rc = sigsetjmp(g_sigill_jmp_buf, /*save sigs*/1); if(rc == 0) { // first call to sigsetjmp probe_result = probe_fn(); } else if(rc == 1) { // non-local return from siglongjmp in signal handler: return error probe_result = -1; } // Restore old SIGILL handler, if any rc = ::sigaction(SIGILL, &old_sigaction, nullptr); if(rc != 0) throw System_Error("run_cpu_instruction_probe sigaction restore failed", errno); #else BOTAN_UNUSED(probe_fn); #endif return probe_result; } std::unique_ptr OS::suppress_echo_on_terminal() { #if defined(BOTAN_TARGET_OS_HAS_POSIX1) class POSIX_Echo_Suppression : public Echo_Suppression { public: POSIX_Echo_Suppression() { m_stdin_fd = fileno(stdin); if(::tcgetattr(m_stdin_fd, &m_old_termios) != 0) throw System_Error("Getting terminal status failed", errno); struct termios noecho_flags = m_old_termios; noecho_flags.c_lflag &= ~ECHO; noecho_flags.c_lflag |= ECHONL; if(::tcsetattr(m_stdin_fd, TCSANOW, &noecho_flags) != 0) throw System_Error("Clearing terminal echo bit failed", errno); } void reenable_echo() override { if(m_stdin_fd > 0) { if(::tcsetattr(m_stdin_fd, TCSANOW, &m_old_termios) != 0) throw System_Error("Restoring terminal echo bit failed", errno); m_stdin_fd = -1; } } ~POSIX_Echo_Suppression() { try { reenable_echo(); } catch(...) { } } private: int m_stdin_fd; struct termios m_old_termios; }; return std::unique_ptr(new POSIX_Echo_Suppression); #elif defined(BOTAN_TARGET_OS_HAS_WIN32) class Win32_Echo_Suppression : public Echo_Suppression { public: Win32_Echo_Suppression() { m_input_handle = ::GetStdHandle(STD_INPUT_HANDLE); if(::GetConsoleMode(m_input_handle, &m_console_state) == 0) throw System_Error("Getting console mode failed", ::GetLastError()); DWORD new_mode = ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; if(::SetConsoleMode(m_input_handle, new_mode) == 0) throw System_Error("Setting console mode failed", ::GetLastError()); } void reenable_echo() override { if(m_input_handle != INVALID_HANDLE_VALUE) { if(::SetConsoleMode(m_input_handle, m_console_state) == 0) throw System_Error("Setting console mode failed", ::GetLastError()); m_input_handle = INVALID_HANDLE_VALUE; } } ~Win32_Echo_Suppression() { try { reenable_echo(); } catch(...) { } } private: HANDLE m_input_handle; DWORD m_console_state; }; return std::unique_ptr(new Win32_Echo_Suppression); #else // Not supported on this platform, return null return std::unique_ptr(); #endif } } /* * Various string utils and parsing functions * (C) 1999-2007,2013,2014,2015,2018 Jack Lloyd * (C) 2015 Simon Warta (Kullo GmbH) * (C) 2017 René Korthaus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ #include #include #if defined(BOTAN_HAS_ASN1) #endif namespace Botan { uint16_t to_uint16(const std::string& str) { const uint32_t x = to_u32bit(str); if(x >> 16) throw Invalid_Argument("Integer value exceeds 16 bit range"); return static_cast(x); } uint32_t to_u32bit(const std::string& str) { // std::stoul is not strict enough. Ensure that str is digit only [0-9]* for(const char chr : str) { if(chr < '0' || chr > '9') { std::string chrAsString(1, chr); throw Invalid_Argument("String contains non-digit char: " + chrAsString); } } const unsigned long int x = std::stoul(str); if(sizeof(unsigned long int) > 4) { // x might be uint64 if (x > std::numeric_limits::max()) { throw Invalid_Argument("Integer value of " + str + " exceeds 32 bit range"); } } return static_cast(x); } /* * Convert a string into a time duration */ uint32_t timespec_to_u32bit(const std::string& timespec) { if(timespec.empty()) return 0; const char suffix = timespec[timespec.size()-1]; std::string value = timespec.substr(0, timespec.size()-1); uint32_t scale = 1; if(Charset::is_digit(suffix)) value += suffix; else if(suffix == 's') scale = 1; else if(suffix == 'm') scale = 60; else if(suffix == 'h') scale = 60 * 60; else if(suffix == 'd') scale = 24 * 60 * 60; else if(suffix == 'y') scale = 365 * 24 * 60 * 60; else throw Decoding_Error("timespec_to_u32bit: Bad input " + timespec); return scale * to_u32bit(value); } /* * Parse a SCAN-style algorithm name */ std::vector parse_algorithm_name(const std::string& namex) { if(namex.find('(') == std::string::npos && namex.find(')') == std::string::npos) return std::vector(1, namex); std::string name = namex, substring; std::vector elems; size_t level = 0; elems.push_back(name.substr(0, name.find('('))); name = name.substr(name.find('(')); for(auto i = name.begin(); i != name.end(); ++i) { char c = *i; if(c == '(') ++level; if(c == ')') { if(level == 1 && i == name.end() - 1) { if(elems.size() == 1) elems.push_back(substring.substr(1)); else elems.push_back(substring); return elems; } if(level == 0 || (level == 1 && i != name.end() - 1)) throw Invalid_Algorithm_Name(namex); --level; } if(c == ',' && level == 1) { if(elems.size() == 1) elems.push_back(substring.substr(1)); else elems.push_back(substring); substring.clear(); } else substring += c; } if(!substring.empty()) throw Invalid_Algorithm_Name(namex); return elems; } std::vector split_on(const std::string& str, char delim) { return split_on_pred(str, [delim](char c) { return c == delim; }); } std::vector split_on_pred(const std::string& str, std::function pred) { std::vector elems; if(str.empty()) return elems; std::string substr; for(auto i = str.begin(); i != str.end(); ++i) { if(pred(*i)) { if(!substr.empty()) elems.push_back(substr); substr.clear(); } else substr += *i; } if(substr.empty()) throw Invalid_Argument("Unable to split string: " + str); elems.push_back(substr); return elems; } /* * Join a string */ std::string string_join(const std::vector& strs, char delim) { std::string out = ""; for(size_t i = 0; i != strs.size(); ++i) { if(i != 0) out += delim; out += strs[i]; } return out; } /* * Parse an ASN.1 OID string */ std::vector parse_asn1_oid(const std::string& oid) { #if defined(BOTAN_HAS_ASN1) return OID(oid).get_components(); #else BOTAN_UNUSED(oid); throw Not_Implemented("ASN1 support not available"); #endif } /* * X.500 String Comparison */ bool x500_name_cmp(const std::string& name1, const std::string& name2) { auto p1 = name1.begin(); auto p2 = name2.begin(); while((p1 != name1.end()) && Charset::is_space(*p1)) ++p1; while((p2 != name2.end()) && Charset::is_space(*p2)) ++p2; while(p1 != name1.end() && p2 != name2.end()) { if(Charset::is_space(*p1)) { if(!Charset::is_space(*p2)) return false; while((p1 != name1.end()) && Charset::is_space(*p1)) ++p1; while((p2 != name2.end()) && Charset::is_space(*p2)) ++p2; if(p1 == name1.end() && p2 == name2.end()) return true; if(p1 == name1.end() || p2 == name2.end()) return false; } if(!Charset::caseless_cmp(*p1, *p2)) return false; ++p1; ++p2; } while((p1 != name1.end()) && Charset::is_space(*p1)) ++p1; while((p2 != name2.end()) && Charset::is_space(*p2)) ++p2; if((p1 != name1.end()) || (p2 != name2.end())) return false; return true; } /* * Convert a decimal-dotted string to binary IP */ uint32_t string_to_ipv4(const std::string& str) { std::vector parts = split_on(str, '.'); if(parts.size() != 4) throw Decoding_Error("Invalid IP string " + str); uint32_t ip = 0; for(auto part = parts.begin(); part != parts.end(); ++part) { uint32_t octet = to_u32bit(*part); if(octet > 255) throw Decoding_Error("Invalid IP string " + str); ip = (ip << 8) | (octet & 0xFF); } return ip; } /* * Convert an IP address to decimal-dotted string */ std::string ipv4_to_string(uint32_t ip) { std::string str; for(size_t i = 0; i != sizeof(ip); ++i) { if(i) str += "."; str += std::to_string(get_byte(i, ip)); } return str; } std::string erase_chars(const std::string& str, const std::set& chars) { std::string out; for(auto c: str) if(chars.count(c) == 0) out += c; return out; } std::string replace_chars(const std::string& str, const std::set& chars, char to_char) { std::string out = str; for(size_t i = 0; i != out.size(); ++i) if(chars.count(out[i])) out[i] = to_char; return out; } std::string replace_char(const std::string& str, char from_char, char to_char) { std::string out = str; for(size_t i = 0; i != out.size(); ++i) if(out[i] == from_char) out[i] = to_char; return out; } std::string tolower_string(const std::string& in) { std::string s = in; for(size_t i = 0; i != s.size(); ++i) { const int cu = static_cast(s[i]); if(std::isalpha(cu)) s[i] = static_cast(std::tolower(cu)); } return s; } bool host_wildcard_match(const std::string& issued_, const std::string& host_) { const std::string issued = tolower_string(issued_); const std::string host = tolower_string(host_); if(host.empty() || issued.empty()) return false; /* If there are embedded nulls in your issued name Well I feel bad for you son */ if(std::count(issued.begin(), issued.end(), char(0)) > 0) return false; // If more than one wildcard, then issued name is invalid const size_t stars = std::count(issued.begin(), issued.end(), '*'); if(stars > 1) return false; // '*' is not a valid character in DNS names so should not appear on the host side if(std::count(host.begin(), host.end(), '*') != 0) return false; // Similarly a DNS name can't end in . if(host[host.size() - 1] == '.') return false; // And a host can't have an empty name component, so reject that if(host.find("..") != std::string::npos) return false; // Exact match: accept if(issued == host) { return true; } /* Otherwise it might be a wildcard If the issued size is strictly longer than the hostname size it couldn't possibly be a match, even if the issued value is a wildcard. The only exception is when the wildcard ends up empty (eg www.example.com matches www*.example.com) */ if(issued.size() > host.size() + 1) { return false; } // If no * at all then not a wildcard, and so not a match if(stars != 1) { return false; } /* Now walk through the issued string, making sure every character matches. When we come to the (singular) '*', jump forward in the hostname by the corresponding amount. We know exactly how much space the wildcard takes because it must be exactly `len(host) - len(issued) + 1 chars`. We also verify that the '*' comes in the leftmost component, and doesn't skip over any '.' in the hostname. */ size_t dots_seen = 0; size_t host_idx = 0; for(size_t i = 0; i != issued.size(); ++i) { dots_seen += (issued[i] == '.'); if(issued[i] == '*') { // Fail: wildcard can only come in leftmost component if(dots_seen > 0) { return false; } /* Since there is only one * we know the tail of the issued and hostname must be an exact match. In this case advance host_idx to match. */ const size_t advance = (host.size() - issued.size() + 1); if(host_idx + advance > host.size()) // shouldn't happen return false; // Can't be any intervening .s that we would have skipped if(std::count(host.begin() + host_idx, host.begin() + host_idx + advance, '.') != 0) return false; host_idx += advance; } else { if(issued[i] != host[host_idx]) { return false; } host_idx += 1; } } // Wildcard issued name must have at least 3 components if(dots_seen < 2) { return false; } return true; } } /* * Simple config/test file reader * (C) 2013,2014,2015 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { std::string clean_ws(const std::string& s) { const char* ws = " \t\n"; auto start = s.find_first_not_of(ws); auto end = s.find_last_not_of(ws); if(start == std::string::npos) return ""; if(end == std::string::npos) return s.substr(start, end); else return s.substr(start, start + end + 1); } std::map read_cfg(std::istream& is) { std::map kv; size_t line = 0; while(is.good()) { std::string s; std::getline(is, s); ++line; if(s.empty() || s[0] == '#') continue; s = clean_ws(s.substr(0, s.find('#'))); if(s.empty()) continue; auto eq = s.find("="); if(eq == std::string::npos || eq == 0 || eq == s.size() - 1) throw Decoding_Error("Bad read_cfg input '" + s + "' on line " + std::to_string(line)); const std::string key = clean_ws(s.substr(0, eq)); const std::string val = clean_ws(s.substr(eq + 1, std::string::npos)); kv[key] = val; } return kv; } } /* * (C) 2018 Ribose Inc * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { std::map read_kv(const std::string& kv) { std::map m; if(kv == "") return m; std::vector parts; try { parts = split_on(kv, ','); } catch(std::exception&) { throw Invalid_Argument("Bad KV spec"); } bool escaped = false; bool reading_key = true; std::string cur_key; std::string cur_val; for(char c : kv) { if(c == '\\' && !escaped) { escaped = true; } else if(c == ',' && !escaped) { if(cur_key.empty()) throw Invalid_Argument("Bad KV spec empty key"); if(m.find(cur_key) != m.end()) throw Invalid_Argument("Bad KV spec duplicated key"); m[cur_key] = cur_val; cur_key = ""; cur_val = ""; reading_key = true; } else if(c == '=' && !escaped) { if(reading_key == false) throw Invalid_Argument("Bad KV spec unexpected equals sign"); reading_key = false; } else { if(reading_key) cur_key += c; else cur_val += c; if(escaped) escaped = false; } } if(!cur_key.empty()) { if(reading_key == false) { if(m.find(cur_key) != m.end()) throw Invalid_Argument("Bad KV spec duplicated key"); m[cur_key] = cur_val; } else throw Invalid_Argument("Bad KV spec incomplete string"); } return m; } } /* * (C) 2018 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { void Timer::start() { stop(); m_timer_start = OS::get_system_timestamp_ns(); m_cpu_cycles_start = OS::get_cpu_cycle_counter(); } void Timer::stop() { if(m_timer_start) { if(m_cpu_cycles_start != 0) { const uint64_t cycles_taken = OS::get_cpu_cycle_counter() - m_cpu_cycles_start; if(cycles_taken > 0) { m_cpu_cycles_used += static_cast(cycles_taken * m_clock_cycle_ratio); } } const uint64_t now = OS::get_system_timestamp_ns(); if(now > m_timer_start) { const uint64_t dur = now - m_timer_start; m_time_used += dur; if(m_event_count == 0) { m_min_time = m_max_time = dur; } else { m_max_time = std::max(m_max_time, dur); m_min_time = std::min(m_min_time, dur); } } m_timer_start = 0; ++m_event_count; } } bool Timer::operator<(const Timer& other) const { if(this->doing() != other.doing()) return (this->doing() < other.doing()); return (this->get_name() < other.get_name()); } std::string Timer::to_string() const { if(m_custom_msg.size() > 0) { return m_custom_msg; } else if(this->buf_size() == 0) { return result_string_ops(); } else { return result_string_bps(); } } std::string Timer::result_string_bps() const { const size_t MiB = 1024 * 1024; const double MiB_total = static_cast(events()) / MiB; const double MiB_per_sec = MiB_total / seconds(); std::ostringstream oss; oss << get_name(); if(!doing().empty()) { oss << " " << doing(); } if(buf_size() > 0) { oss << " buffer size " << buf_size() << " bytes:"; } if(events() == 0) oss << " " << "N/A"; else oss << " " << std::fixed << std::setprecision(3) << MiB_per_sec << " MiB/sec"; if(cycles_consumed() != 0) { const double cycles_per_byte = static_cast(cycles_consumed()) / events(); oss << " " << std::fixed << std::setprecision(2) << cycles_per_byte << " cycles/byte"; } oss << " (" << MiB_total << " MiB in " << milliseconds() << " ms)\n"; return oss.str(); } std::string Timer::result_string_ops() const { std::ostringstream oss; oss << get_name() << " "; if(events() == 0) { oss << "no events\n"; } else { oss << static_cast(events_per_second()) << ' ' << doing() << "/sec; " << std::setprecision(2) << std::fixed << ms_per_event() << " ms/op"; if(cycles_consumed() != 0) { const double cycles_per_op = static_cast(cycles_consumed()) / events(); const int precision = (cycles_per_op < 10000) ? 2 : 0; oss << " " << std::fixed << std::setprecision(precision) << cycles_per_op << " cycles/op"; } oss << " (" << events() << " " << (events() == 1 ? "op" : "ops") << " in " << milliseconds() << " ms)\n"; } return oss.str(); } } /* * Version Information * (C) 1999-2013,2015 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { /* These are intentionally compiled rather than inlined, so an application running against a shared library can test the true version they are running against. */ #define QUOTE(name) #name #define STR(macro) QUOTE(macro) const char* short_version_cstr() { return STR(BOTAN_VERSION_MAJOR) "." STR(BOTAN_VERSION_MINOR) "." STR(BOTAN_VERSION_PATCH) #if defined(BOTAN_VERSION_SUFFIX) STR(BOTAN_VERSION_SUFFIX) #endif ; } const char* version_cstr() { /* It is intentional that this string is a compile-time constant; it makes it much easier to find in binaries. */ return "Botan " STR(BOTAN_VERSION_MAJOR) "." STR(BOTAN_VERSION_MINOR) "." STR(BOTAN_VERSION_PATCH) #if defined(BOTAN_VERSION_SUFFIX) STR(BOTAN_VERSION_SUFFIX) #endif " (" #if defined(BOTAN_UNSAFE_FUZZER_MODE) "UNSAFE FUZZER MODE BUILD " #endif BOTAN_VERSION_RELEASE_TYPE #if (BOTAN_VERSION_DATESTAMP != 0) ", dated " STR(BOTAN_VERSION_DATESTAMP) #endif ", revision " BOTAN_VERSION_VC_REVISION ", distribution " BOTAN_DISTRIBUTION_INFO ")"; } #undef STR #undef QUOTE /* * Return the version as a string */ std::string version_string() { return std::string(version_cstr()); } std::string short_version_string() { return std::string(short_version_cstr()); } uint32_t version_datestamp() { return BOTAN_VERSION_DATESTAMP; } /* * Return parts of the version as integers */ uint32_t version_major() { return BOTAN_VERSION_MAJOR; } uint32_t version_minor() { return BOTAN_VERSION_MINOR; } uint32_t version_patch() { return BOTAN_VERSION_PATCH; } std::string runtime_version_check(uint32_t major, uint32_t minor, uint32_t patch) { if(major != version_major() || minor != version_minor() || patch != version_patch()) { std::ostringstream oss; oss << "Warning: linked version (" << short_version_string() << ")" << " does not match version built against " << "(" << major << '.' << minor << '.' << patch << ")\n"; return oss.str(); } return ""; } } OSCAR-code-v1.3.1/oscar/SleepLib/thirdparty/botan_all.h000066400000000000000000000010721417327530600225750ustar00rootroot00000000000000/* Botan platform-specific wrapper * * Copyright (c) 2021 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef BOTAN_ALL_H #define BOTAN_ALL_H // This wrapper makes it easy to regenerate Botan's platform-specific headers. #include #ifdef Q_OS_WIN #include "botan_windows.h" #endif #ifdef Q_OS_LINUX #include "botan_linux.h" #endif #ifdef Q_OS_MACOS #include "botan_macos.h" #endif #endif // BOTAN_ALL_H OSCAR-code-v1.3.1/oscar/SleepLib/thirdparty/botan_linux.h000066400000000000000000005261451417327530600232010ustar00rootroot00000000000000/* * Botan 2.18.2 Amalgamation * (C) 1999-2020 The Botan Authors * * Botan is released under the Simplified BSD License (see license.txt) */ #ifndef BOTAN_AMALGAMATION_H_ #define BOTAN_AMALGAMATION_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Build configuration for Botan 2.18.2 * * Automatically generated from * 'configure.py --amalgamation --os=linux --cc=gcc --cpu=generic --disable-shared --minimized-build --enable-modules=aes,gcm' * * Target * - Compiler: g++ -fstack-protector -pthread -std=c++11 -D_REENTRANT -O3 * - Arch: generic * - OS: linux */ #define BOTAN_VERSION_MAJOR 2 #define BOTAN_VERSION_MINOR 18 #define BOTAN_VERSION_PATCH 2 #define BOTAN_VERSION_DATESTAMP 20211025 #define BOTAN_VERSION_RELEASE_TYPE "release" #define BOTAN_VERSION_VC_REVISION "git:a44f1489239e80937ca67564ff103421e5584069" #define BOTAN_DISTRIBUTION_INFO "unspecified" /* How many bits per limb in a BigInt */ #define BOTAN_MP_WORD_BITS 32 #define BOTAN_INSTALL_PREFIX R"(/usr/local)" #define BOTAN_INSTALL_HEADER_DIR R"(include/botan-2)" #define BOTAN_INSTALL_LIB_DIR R"(/usr/local/lib)" #define BOTAN_LIB_LINK "" #define BOTAN_LINK_FLAGS "-fstack-protector -pthread" #define BOTAN_SYSTEM_CERT_BUNDLE "/etc/ssl/cert.pem" #ifndef BOTAN_DLL #define BOTAN_DLL #endif /* Target identification and feature test macros */ #define BOTAN_TARGET_OS_IS_LINUX #define BOTAN_TARGET_OS_HAS_ATOMICS #define BOTAN_TARGET_OS_HAS_CLOCK_GETTIME #define BOTAN_TARGET_OS_HAS_DEV_RANDOM #define BOTAN_TARGET_OS_HAS_FILESYSTEM #define BOTAN_TARGET_OS_HAS_GETAUXVAL #define BOTAN_TARGET_OS_HAS_POSIX1 #define BOTAN_TARGET_OS_HAS_POSIX_MLOCK #define BOTAN_TARGET_OS_HAS_PROC_FS #define BOTAN_TARGET_OS_HAS_SOCKETS #define BOTAN_TARGET_OS_HAS_THREAD_LOCAL #define BOTAN_TARGET_OS_HAS_THREADS #define BOTAN_BUILD_COMPILER_IS_GCC #define BOTAN_TARGET_ARCH_IS_GENERIC /* * Module availability definitions */ #define BOTAN_HAS_AEAD_GCM 20131128 #define BOTAN_HAS_AEAD_MODES 20131128 #define BOTAN_HAS_AES 20131128 #define BOTAN_HAS_BLOCK_CIPHER 20131128 #define BOTAN_HAS_CIPHER_MODES 20180124 #define BOTAN_HAS_CPUID 20170917 #define BOTAN_HAS_CTR_BE 20131128 #define BOTAN_HAS_ENTROPY_SOURCE 20151120 #define BOTAN_HAS_GHASH 20201002 #define BOTAN_HAS_HEX_CODEC 20131128 #define BOTAN_HAS_MODES 20150626 #define BOTAN_HAS_STREAM_CIPHER 20131128 #define BOTAN_HAS_UTIL_FUNCTIONS 20180903 /* * Local/misc configuration options (if any) follow */ /* * Things you can edit (but probably shouldn't) */ #if !defined(BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES) #if defined(BOTAN_NO_DEPRECATED) #define BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES private #else #define BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES public #endif #endif /* How much to allocate for a buffer of no particular size */ #define BOTAN_DEFAULT_BUFFER_SIZE 1024 /* * Total maximum amount of RAM (in KiB) we will lock into memory, even * if the OS would let us lock more */ #define BOTAN_MLOCK_ALLOCATOR_MAX_LOCKED_KB 512 /* * If BOTAN_MEM_POOL_USE_MMU_PROTECTIONS is defined, the Memory_Pool * class used for mlock'ed memory will use OS calls to set page * permissions so as to prohibit access to pages on the free list, then * enable read/write access when the page is set to be used. This will * turn (some) use after free bugs into a crash. * * The additional syscalls have a substantial performance impact, which * is why this option is not enabled by default. */ #if defined(BOTAN_HAS_VALGRIND) || defined(BOTAN_ENABLE_DEBUG_ASSERTS) #define BOTAN_MEM_POOL_USE_MMU_PROTECTIONS #endif /* * If enabled uses memset via volatile function pointer to zero memory, * otherwise does a byte at a time write via a volatile pointer. */ #define BOTAN_USE_VOLATILE_MEMSET_FOR_ZERO 1 /* * Normally blinding is performed by choosing a random starting point (plus * its inverse, of a form appropriate to the algorithm being blinded), and * then choosing new blinding operands by successive squaring of both * values. This is much faster than computing a new starting point but * introduces some possible corelation * * To avoid possible leakage problems in long-running processes, the blinder * periodically reinitializes the sequence. This value specifies how often * a new sequence should be started. */ #define BOTAN_BLINDING_REINIT_INTERVAL 64 /* * Userspace RNGs like HMAC_DRBG will reseed after a specified number * of outputs are generated. Set to zero to disable automatic reseeding. */ #define BOTAN_RNG_DEFAULT_RESEED_INTERVAL 1024 #define BOTAN_RNG_RESEED_POLL_BITS 256 #define BOTAN_RNG_AUTO_RESEED_TIMEOUT std::chrono::milliseconds(10) #define BOTAN_RNG_RESEED_DEFAULT_TIMEOUT std::chrono::milliseconds(50) /* * Specifies (in order) the list of entropy sources that will be used * to seed an in-memory RNG. */ #define BOTAN_ENTROPY_DEFAULT_SOURCES \ { "rdseed", "hwrng", "p9_darn", "getentropy", "dev_random", \ "system_rng", "proc_walk", "system_stats" } /* Multiplier on a block cipher's native parallelism */ #define BOTAN_BLOCK_CIPHER_PAR_MULT 4 /* * These control the RNG used by the system RNG interface */ #define BOTAN_SYSTEM_RNG_DEVICE "/dev/urandom" #define BOTAN_SYSTEM_RNG_POLL_DEVICES { "/dev/urandom", "/dev/random" } /* * This directory will be monitored by ProcWalking_EntropySource and * the contents provided as entropy inputs to the RNG. May also be * usefully set to something like "/sys", depending on the system being * deployed to. Set to an empty string to disable. */ #define BOTAN_ENTROPY_PROC_FS_PATH "/proc" /* * These paramaters control how many bytes to read from the system * PRNG, and how long to block if applicable. The timeout only applies * to reading /dev/urandom and company. */ #define BOTAN_SYSTEM_RNG_POLL_REQUEST 64 #define BOTAN_SYSTEM_RNG_POLL_TIMEOUT_MS 20 /* * When a PBKDF is self-tuning parameters, it will attempt to take about this * amount of time to self-benchmark. */ #define BOTAN_PBKDF_TUNING_TIME std::chrono::milliseconds(10) /* * If no way of dynamically determining the cache line size for the * system exists, this value is used as the default. Used by the side * channel countermeasures rather than for alignment purposes, so it is * better to be on the smaller side if the exact value cannot be * determined. Typically 32 or 64 bytes on modern CPUs. */ #if !defined(BOTAN_TARGET_CPU_DEFAULT_CACHE_LINE_SIZE) #define BOTAN_TARGET_CPU_DEFAULT_CACHE_LINE_SIZE 32 #endif /** * Controls how AutoSeeded_RNG is instantiated */ #if !defined(BOTAN_AUTO_RNG_HMAC) #if defined(BOTAN_HAS_SHA2_64) #define BOTAN_AUTO_RNG_HMAC "HMAC(SHA-384)" #elif defined(BOTAN_HAS_SHA2_32) #define BOTAN_AUTO_RNG_HMAC "HMAC(SHA-256)" #elif defined(BOTAN_HAS_SHA3) #define BOTAN_AUTO_RNG_HMAC "HMAC(SHA-3(256))" #elif defined(BOTAN_HAS_SHA1) #define BOTAN_AUTO_RNG_HMAC "HMAC(SHA-1)" #endif /* Otherwise, no hash found: leave BOTAN_AUTO_RNG_HMAC undefined */ #endif /* Check for a common build problem */ #if defined(BOTAN_TARGET_ARCH_IS_X86_64) && ((defined(_MSC_VER) && !defined(_WIN64)) || \ (defined(__clang__) && !defined(__x86_64__)) || \ (defined(__GNUG__) && !defined(__x86_64__))) #error "Trying to compile Botan configured as x86_64 with non-x86_64 compiler." #endif #if defined(BOTAN_TARGET_ARCH_IS_X86_32) && ((defined(_MSC_VER) && defined(_WIN64)) || \ (defined(__clang__) && !defined(__i386__)) || \ (defined(__GNUG__) && !defined(__i386__))) #error "Trying to compile Botan configured as x86_32 with non-x86_32 compiler." #endif /* Should we use GCC-style inline assembler? */ #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || \ defined(BOTAN_BUILD_COMPILER_IS_CLANG) || \ defined(BOTAN_BUILD_COMPILER_IS_XLC) || \ defined(BOTAN_BUILD_COMPILER_IS_SUN_STUDIO) #define BOTAN_USE_GCC_INLINE_ASM #endif /** * Used to annotate API exports which are public and supported. * These APIs will not be broken/removed unless strictly required for * functionality or security, and only in new major versions. * @param maj The major version this public API was released in * @param min The minor version this public API was released in */ #define BOTAN_PUBLIC_API(maj,min) BOTAN_DLL /** * Used to annotate API exports which are public, but are now deprecated * and which will be removed in a future major release. */ #define BOTAN_DEPRECATED_API(msg) BOTAN_DLL BOTAN_DEPRECATED(msg) /** * Used to annotate API exports which are public and can be used by * applications if needed, but which are intentionally not documented, * and which may change incompatibly in a future major version. */ #define BOTAN_UNSTABLE_API BOTAN_DLL /** * Used to annotate API exports which are exported but only for the * purposes of testing. They should not be used by applications and * may be removed or changed without notice. */ #define BOTAN_TEST_API BOTAN_DLL /* * Define BOTAN_GCC_VERSION */ #if defined(__GNUC__) && !defined(__clang__) #define BOTAN_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__ * 10 + __GNUC_PATCHLEVEL__) #else #define BOTAN_GCC_VERSION 0 #endif /* * Define BOTAN_CLANG_VERSION */ #if defined(__clang__) #define BOTAN_CLANG_VERSION (__clang_major__ * 10 + __clang_minor__) #else #define BOTAN_CLANG_VERSION 0 #endif /* * Define BOTAN_FUNC_ISA */ #if (defined(__GNUC__) && !defined(__clang__)) || (BOTAN_CLANG_VERSION > 38) #define BOTAN_FUNC_ISA(isa) __attribute__ ((target(isa))) #else #define BOTAN_FUNC_ISA(isa) #endif /* * Define BOTAN_WARN_UNUSED_RESULT */ #if defined(__GNUC__) || defined(__clang__) #define BOTAN_WARN_UNUSED_RESULT __attribute__ ((warn_unused_result)) #else #define BOTAN_WARN_UNUSED_RESULT #endif /* * Define BOTAN_MALLOC_FN */ #if defined(__ibmxl__) /* XLC pretends to be both Clang and GCC, but is neither */ #define BOTAN_MALLOC_FN __attribute__ ((malloc)) #elif defined(__GNUC__) #define BOTAN_MALLOC_FN __attribute__ ((malloc, alloc_size(1,2))) #elif defined(_MSC_VER) #define BOTAN_MALLOC_FN __declspec(restrict) #else #define BOTAN_MALLOC_FN #endif /* * Define BOTAN_DEPRECATED */ #if !defined(BOTAN_NO_DEPRECATED_WARNINGS) && !defined(BOTAN_IS_BEING_BUILT) && !defined(BOTAN_AMALGAMATION_H_) #if defined(__clang__) #define BOTAN_DEPRECATED(msg) __attribute__ ((deprecated(msg))) #define BOTAN_DEPRECATED_HEADER(hdr) _Pragma("message \"this header is deprecated\"") #define BOTAN_FUTURE_INTERNAL_HEADER(hdr) _Pragma("message \"this header will be made internal in the future\"") #elif defined(_MSC_VER) #define BOTAN_DEPRECATED(msg) __declspec(deprecated(msg)) #define BOTAN_DEPRECATED_HEADER(hdr) __pragma(message("this header is deprecated")) #define BOTAN_FUTURE_INTERNAL_HEADER(hdr) __pragma(message("this header will be made internal in the future")) #elif defined(__GNUC__) /* msg supported since GCC 4.5, earliest we support is 4.8 */ #define BOTAN_DEPRECATED(msg) __attribute__ ((deprecated(msg))) #define BOTAN_DEPRECATED_HEADER(hdr) _Pragma("GCC warning \"this header is deprecated\"") #define BOTAN_FUTURE_INTERNAL_HEADER(hdr) _Pragma("GCC warning \"this header will be made internal in the future\"") #endif #endif #if !defined(BOTAN_DEPRECATED) #define BOTAN_DEPRECATED(msg) #endif #if !defined(BOTAN_DEPRECATED_HEADER) #define BOTAN_DEPRECATED_HEADER(hdr) #endif #if !defined(BOTAN_FUTURE_INTERNAL_HEADER) #define BOTAN_FUTURE_INTERNAL_HEADER(hdr) #endif /* * Define BOTAN_NORETURN */ #if !defined(BOTAN_NORETURN) #if defined (__clang__) || defined (__GNUC__) #define BOTAN_NORETURN __attribute__ ((__noreturn__)) #elif defined (_MSC_VER) #define BOTAN_NORETURN __declspec(noreturn) #else #define BOTAN_NORETURN #endif #endif /* * Define BOTAN_THREAD_LOCAL */ #if !defined(BOTAN_THREAD_LOCAL) #if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_TARGET_OS_HAS_THREAD_LOCAL) #define BOTAN_THREAD_LOCAL thread_local #else #define BOTAN_THREAD_LOCAL /**/ #endif #endif /* * Define BOTAN_IF_CONSTEXPR */ #if !defined(BOTAN_IF_CONSTEXPR) #if __cplusplus >= 201703 #define BOTAN_IF_CONSTEXPR if constexpr #else #define BOTAN_IF_CONSTEXPR if #endif #endif /* * Define BOTAN_PARALLEL_FOR */ #if !defined(BOTAN_PARALLEL_FOR) #if defined(BOTAN_TARGET_HAS_OPENMP) #define BOTAN_PARALLEL_FOR _Pragma("omp parallel for") for #else #define BOTAN_PARALLEL_FOR for #endif #endif /* * Define BOTAN_FORCE_INLINE */ #if !defined(BOTAN_FORCE_INLINE) #if defined (__clang__) || defined (__GNUC__) #define BOTAN_FORCE_INLINE __attribute__ ((__always_inline__)) inline #elif defined (_MSC_VER) #define BOTAN_FORCE_INLINE __forceinline #else #define BOTAN_FORCE_INLINE inline #endif #endif /* * Define BOTAN_PARALLEL_SIMD_FOR */ #if !defined(BOTAN_PARALLEL_SIMD_FOR) #if defined(BOTAN_TARGET_HAS_OPENMP) #define BOTAN_PARALLEL_SIMD_FOR _Pragma("omp simd") for #elif defined(BOTAN_BUILD_COMPILER_IS_GCC) && (BOTAN_GCC_VERSION >= 490) #define BOTAN_PARALLEL_SIMD_FOR _Pragma("GCC ivdep") for #else #define BOTAN_PARALLEL_SIMD_FOR for #endif #endif namespace Botan { /** * Called when an assertion fails * Throws an Exception object */ BOTAN_NORETURN void BOTAN_PUBLIC_API(2,0) assertion_failure(const char* expr_str, const char* assertion_made, const char* func, const char* file, int line); /** * Called when an invalid argument is used * Throws Invalid_Argument */ BOTAN_NORETURN void BOTAN_UNSTABLE_API throw_invalid_argument(const char* message, const char* func, const char* file); #define BOTAN_ARG_CHECK(expr, msg) \ do { if(!(expr)) Botan::throw_invalid_argument(msg, __func__, __FILE__); } while(0) /** * Called when an invalid state is encountered * Throws Invalid_State */ BOTAN_NORETURN void BOTAN_UNSTABLE_API throw_invalid_state(const char* message, const char* func, const char* file); #define BOTAN_STATE_CHECK(expr) \ do { if(!(expr)) Botan::throw_invalid_state(#expr, __func__, __FILE__); } while(0) /** * Make an assertion */ #define BOTAN_ASSERT(expr, assertion_made) \ do { \ if(!(expr)) \ Botan::assertion_failure(#expr, \ assertion_made, \ __func__, \ __FILE__, \ __LINE__); \ } while(0) /** * Make an assertion */ #define BOTAN_ASSERT_NOMSG(expr) \ do { \ if(!(expr)) \ Botan::assertion_failure(#expr, \ "", \ __func__, \ __FILE__, \ __LINE__); \ } while(0) /** * Assert that value1 == value2 */ #define BOTAN_ASSERT_EQUAL(expr1, expr2, assertion_made) \ do { \ if((expr1) != (expr2)) \ Botan::assertion_failure(#expr1 " == " #expr2, \ assertion_made, \ __func__, \ __FILE__, \ __LINE__); \ } while(0) /** * Assert that expr1 (if true) implies expr2 is also true */ #define BOTAN_ASSERT_IMPLICATION(expr1, expr2, msg) \ do { \ if((expr1) && !(expr2)) \ Botan::assertion_failure(#expr1 " implies " #expr2, \ msg, \ __func__, \ __FILE__, \ __LINE__); \ } while(0) /** * Assert that a pointer is not null */ #define BOTAN_ASSERT_NONNULL(ptr) \ do { \ if((ptr) == nullptr) \ Botan::assertion_failure(#ptr " is not null", \ "", \ __func__, \ __FILE__, \ __LINE__); \ } while(0) #if defined(BOTAN_ENABLE_DEBUG_ASSERTS) #define BOTAN_DEBUG_ASSERT(expr) BOTAN_ASSERT_NOMSG(expr) #else #define BOTAN_DEBUG_ASSERT(expr) do {} while(0) #endif /** * Mark variable as unused. Takes between 1 and 9 arguments and marks all as unused, * e.g. BOTAN_UNUSED(a); or BOTAN_UNUSED(x, y, z); */ #define _BOTAN_UNUSED_IMPL1(a) static_cast(a) #define _BOTAN_UNUSED_IMPL2(a, b) static_cast(a); _BOTAN_UNUSED_IMPL1(b) #define _BOTAN_UNUSED_IMPL3(a, b, c) static_cast(a); _BOTAN_UNUSED_IMPL2(b, c) #define _BOTAN_UNUSED_IMPL4(a, b, c, d) static_cast(a); _BOTAN_UNUSED_IMPL3(b, c, d) #define _BOTAN_UNUSED_IMPL5(a, b, c, d, e) static_cast(a); _BOTAN_UNUSED_IMPL4(b, c, d, e) #define _BOTAN_UNUSED_IMPL6(a, b, c, d, e, f) static_cast(a); _BOTAN_UNUSED_IMPL5(b, c, d, e, f) #define _BOTAN_UNUSED_IMPL7(a, b, c, d, e, f, g) static_cast(a); _BOTAN_UNUSED_IMPL6(b, c, d, e, f, g) #define _BOTAN_UNUSED_IMPL8(a, b, c, d, e, f, g, h) static_cast(a); _BOTAN_UNUSED_IMPL7(b, c, d, e, f, g, h) #define _BOTAN_UNUSED_IMPL9(a, b, c, d, e, f, g, h, i) static_cast(a); _BOTAN_UNUSED_IMPL8(b, c, d, e, f, g, h, i) #define _BOTAN_UNUSED_GET_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, IMPL_NAME, ...) IMPL_NAME #define BOTAN_UNUSED(...) _BOTAN_UNUSED_GET_IMPL(__VA_ARGS__, \ _BOTAN_UNUSED_IMPL9, \ _BOTAN_UNUSED_IMPL8, \ _BOTAN_UNUSED_IMPL7, \ _BOTAN_UNUSED_IMPL6, \ _BOTAN_UNUSED_IMPL5, \ _BOTAN_UNUSED_IMPL4, \ _BOTAN_UNUSED_IMPL3, \ _BOTAN_UNUSED_IMPL2, \ _BOTAN_UNUSED_IMPL1, \ unused dummy rest value \ ) /* we got an one of _BOTAN_UNUSED_IMPL*, now call it */ (__VA_ARGS__) } namespace Botan { /** * @mainpage Botan Crypto Library API Reference * *
*
Abstract Base Classes
* BlockCipher, HashFunction, KDF, MessageAuthenticationCode, RandomNumberGenerator, * StreamCipher, SymmetricAlgorithm, AEAD_Mode, Cipher_Mode *
Public Key Interface Classes
* PK_Key_Agreement, PK_Signer, PK_Verifier, PK_Encryptor, PK_Decryptor *
Authenticated Encryption Modes
* @ref CCM_Mode "CCM", @ref ChaCha20Poly1305_Mode "ChaCha20Poly1305", @ref EAX_Mode "EAX", * @ref GCM_Mode "GCM", @ref OCB_Mode "OCB", @ref SIV_Mode "SIV" *
Block Ciphers
* @ref aria.h "ARIA", @ref aes.h "AES", @ref Blowfish, @ref camellia.h "Camellia", @ref Cascade_Cipher "Cascade", * @ref CAST_128 "CAST-128", @ref CAST_128 "CAST-256", DES, @ref DESX "DES-X", @ref TripleDES "3DES", * @ref GOST_28147_89 "GOST 28147-89", IDEA, KASUMI, Lion, MISTY1, Noekeon, SEED, Serpent, SHACAL2, SM4, * @ref Threefish_512 "Threefish", Twofish, XTEA *
Stream Ciphers
* ChaCha, @ref CTR_BE "CTR", OFB, RC4, Salsa20 *
Hash Functions
* BLAKE2b, @ref GOST_34_11 "GOST 34.11", @ref Keccak_1600 "Keccak", MD4, MD5, @ref RIPEMD_160 "RIPEMD-160", * @ref SHA_160 "SHA-1", @ref SHA_224 "SHA-224", @ref SHA_256 "SHA-256", @ref SHA_384 "SHA-384", * @ref SHA_512 "SHA-512", @ref Skein_512 "Skein-512", SM3, Streebog, Tiger, Whirlpool *
Non-Cryptographic Checksums
* Adler32, CRC24, CRC32 *
Message Authentication Codes
* @ref CBC_MAC "CBC-MAC", CMAC, HMAC, Poly1305, SipHash, ANSI_X919_MAC *
Random Number Generators
* AutoSeeded_RNG, HMAC_DRBG, Processor_RNG, System_RNG *
Key Derivation
* HKDF, @ref KDF1 "KDF1 (IEEE 1363)", @ref KDF1_18033 "KDF1 (ISO 18033-2)", @ref KDF2 "KDF2 (IEEE 1363)", * @ref sp800_108.h "SP800-108", @ref SP800_56C "SP800-56C", @ref PKCS5_PBKDF1 "PBKDF1 (PKCS#5), * @ref PKCS5_PBKDF2 "PBKDF2 (PKCS#5)" *
Password Hashing
* @ref argon2.h "Argon2", @ref scrypt.h "scrypt", @ref bcrypt.h "bcrypt", @ref passhash9.h "passhash9" *
Public Key Cryptosystems
* @ref dlies.h "DLIES", @ref ecies.h "ECIES", @ref elgamal.h "ElGamal" * @ref rsa.h "RSA", @ref newhope.h "NewHope", @ref mceliece.h "McEliece" and @ref mceies.h "MCEIES", * @ref sm2.h "SM2" *
Public Key Signature Schemes
* @ref dsa.h "DSA", @ref ecdsa.h "ECDSA", @ref ecgdsa.h "ECGDSA", @ref eckcdsa.h "ECKCDSA", * @ref gost_3410.h "GOST 34.10-2001", @ref sm2.h "SM2", @ref xmss.h "XMSS" *
Key Agreement
* @ref dh.h "DH", @ref ecdh.h "ECDH" *
Compression
* @ref bzip2.h "bzip2", @ref lzma.h "lzma", @ref zlib.h "zlib" *
TLS
* TLS::Client, TLS::Server, TLS::Policy, TLS::Protocol_Version, TLS::Callbacks, TLS::Ciphersuite, * TLS::Session, TLS::Session_Manager, Credentials_Manager *
X.509
* X509_Certificate, X509_CRL, X509_CA, Certificate_Extension, PKCS10_Request, X509_Cert_Options, * Certificate_Store, Certificate_Store_In_SQL, Certificate_Store_In_SQLite *
*/ using std::uint8_t; using std::uint16_t; using std::uint32_t; using std::uint64_t; using std::int32_t; using std::int64_t; using std::size_t; /* * These typedefs are no longer used within the library headers * or code. They are kept only for compatability with software * written against older versions. */ using byte = std::uint8_t; using u16bit = std::uint16_t; using u32bit = std::uint32_t; using u64bit = std::uint64_t; using s32bit = std::int32_t; #if (BOTAN_MP_WORD_BITS == 32) typedef uint32_t word; #elif (BOTAN_MP_WORD_BITS == 64) typedef uint64_t word; #else #error BOTAN_MP_WORD_BITS must be 32 or 64 #endif /* * Should this assert fail on your system please contact the developers * for assistance in porting. */ static_assert(sizeof(std::size_t) == 8 || sizeof(std::size_t) == 4, "This platform has an unexpected size for size_t"); } namespace Botan { /** * Allocate a memory buffer by some method. This should only be used for * primitive types (uint8_t, uint32_t, etc). * * @param elems the number of elements * @param elem_size the size of each element * @return pointer to allocated and zeroed memory, or throw std::bad_alloc on failure */ BOTAN_PUBLIC_API(2,3) BOTAN_MALLOC_FN void* allocate_memory(size_t elems, size_t elem_size); /** * Free a pointer returned by allocate_memory * @param p the pointer returned by allocate_memory * @param elems the number of elements, as passed to allocate_memory * @param elem_size the size of each element, as passed to allocate_memory */ BOTAN_PUBLIC_API(2,3) void deallocate_memory(void* p, size_t elems, size_t elem_size); /** * Ensure the allocator is initialized */ void BOTAN_UNSTABLE_API initialize_allocator(); class Allocator_Initializer { public: Allocator_Initializer() { initialize_allocator(); } }; /** * Scrub memory contents in a way that a compiler should not elide, * using some system specific technique. Note that this function might * not zero the memory (for example, in some hypothetical * implementation it might combine the memory contents with the output * of a system PRNG), but if you can detect any difference in behavior * at runtime then the clearing is side-effecting and you can just * use `clear_mem`. * * Use this function to scrub memory just before deallocating it, or on * a stack buffer before returning from the function. * * @param ptr a pointer to memory to scrub * @param n the number of bytes pointed to by ptr */ BOTAN_PUBLIC_API(2,0) void secure_scrub_memory(void* ptr, size_t n); /** * Memory comparison, input insensitive * @param x a pointer to an array * @param y a pointer to another array * @param len the number of Ts in x and y * @return 0xFF iff x[i] == y[i] forall i in [0...n) or 0x00 otherwise */ BOTAN_PUBLIC_API(2,9) uint8_t ct_compare_u8(const uint8_t x[], const uint8_t y[], size_t len); /** * Memory comparison, input insensitive * @param x a pointer to an array * @param y a pointer to another array * @param len the number of Ts in x and y * @return true iff x[i] == y[i] forall i in [0...n) */ inline bool constant_time_compare(const uint8_t x[], const uint8_t y[], size_t len) { return ct_compare_u8(x, y, len) == 0xFF; } /** * Zero out some bytes. Warning: use secure_scrub_memory instead if the * memory is about to be freed or otherwise the compiler thinks it can * elide the writes. * * @param ptr a pointer to memory to zero * @param bytes the number of bytes to zero in ptr */ inline void clear_bytes(void* ptr, size_t bytes) { if(bytes > 0) { std::memset(ptr, 0, bytes); } } /** * Zero memory before use. This simply calls memset and should not be * used in cases where the compiler cannot see the call as a * side-effecting operation (for example, if calling clear_mem before * deallocating memory, the compiler would be allowed to omit the call * to memset entirely under the as-if rule.) * * @param ptr a pointer to an array of Ts to zero * @param n the number of Ts pointed to by ptr */ template inline void clear_mem(T* ptr, size_t n) { clear_bytes(ptr, sizeof(T)*n); } // is_trivially_copyable is missing in g++ < 5.0 #if (BOTAN_GCC_VERSION > 0 && BOTAN_GCC_VERSION < 500) #define BOTAN_IS_TRIVIALLY_COPYABLE(T) true #else #define BOTAN_IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable::value #endif /** * Copy memory * @param out the destination array * @param in the source array * @param n the number of elements of in/out */ template inline void copy_mem(T* out, const T* in, size_t n) { static_assert(std::is_trivial::type>::value, ""); BOTAN_ASSERT_IMPLICATION(n > 0, in != nullptr && out != nullptr, "If n > 0 then args are not null"); if(in != nullptr && out != nullptr && n > 0) { std::memmove(out, in, sizeof(T)*n); } } template inline void typecast_copy(uint8_t out[], T in[], size_t N) { static_assert(BOTAN_IS_TRIVIALLY_COPYABLE(T), ""); std::memcpy(out, in, sizeof(T)*N); } template inline void typecast_copy(T out[], const uint8_t in[], size_t N) { static_assert(std::is_trivial::value, ""); std::memcpy(out, in, sizeof(T)*N); } template inline void typecast_copy(uint8_t out[], T in) { typecast_copy(out, &in, 1); } template inline void typecast_copy(T& out, const uint8_t in[]) { static_assert(std::is_trivial::type>::value, ""); typecast_copy(&out, in, 1); } template inline To typecast_copy(const From *src) noexcept { static_assert(BOTAN_IS_TRIVIALLY_COPYABLE(From) && std::is_trivial::value, ""); To dst; std::memcpy(&dst, src, sizeof(To)); return dst; } /** * Set memory to a fixed value * @param ptr a pointer to an array of bytes * @param n the number of Ts pointed to by ptr * @param val the value to set each byte to */ inline void set_mem(uint8_t* ptr, size_t n, uint8_t val) { if(n > 0) { std::memset(ptr, val, n); } } inline const uint8_t* cast_char_ptr_to_uint8(const char* s) { return reinterpret_cast(s); } inline const char* cast_uint8_ptr_to_char(const uint8_t* b) { return reinterpret_cast(b); } inline uint8_t* cast_char_ptr_to_uint8(char* s) { return reinterpret_cast(s); } inline char* cast_uint8_ptr_to_char(uint8_t* b) { return reinterpret_cast(b); } /** * Memory comparison, input insensitive * @param p1 a pointer to an array * @param p2 a pointer to another array * @param n the number of Ts in p1 and p2 * @return true iff p1[i] == p2[i] forall i in [0...n) */ template inline bool same_mem(const T* p1, const T* p2, size_t n) { volatile T difference = 0; for(size_t i = 0; i != n; ++i) difference |= (p1[i] ^ p2[i]); return difference == 0; } template size_t buffer_insert(std::vector& buf, size_t buf_offset, const T input[], size_t input_length) { BOTAN_ASSERT_NOMSG(buf_offset <= buf.size()); const size_t to_copy = std::min(input_length, buf.size() - buf_offset); if(to_copy > 0) { copy_mem(&buf[buf_offset], input, to_copy); } return to_copy; } template size_t buffer_insert(std::vector& buf, size_t buf_offset, const std::vector& input) { BOTAN_ASSERT_NOMSG(buf_offset <= buf.size()); const size_t to_copy = std::min(input.size(), buf.size() - buf_offset); if(to_copy > 0) { copy_mem(&buf[buf_offset], input.data(), to_copy); } return to_copy; } /** * XOR arrays. Postcondition out[i] = in[i] ^ out[i] forall i = 0...length * @param out the input/output buffer * @param in the read-only input buffer * @param length the length of the buffers */ inline void xor_buf(uint8_t out[], const uint8_t in[], size_t length) { const size_t blocks = length - (length % 32); for(size_t i = 0; i != blocks; i += 32) { uint64_t x[4]; uint64_t y[4]; typecast_copy(x, out + i, 4); typecast_copy(y, in + i, 4); x[0] ^= y[0]; x[1] ^= y[1]; x[2] ^= y[2]; x[3] ^= y[3]; typecast_copy(out + i, x, 4); } for(size_t i = blocks; i != length; ++i) { out[i] ^= in[i]; } } /** * XOR arrays. Postcondition out[i] = in[i] ^ in2[i] forall i = 0...length * @param out the output buffer * @param in the first input buffer * @param in2 the second output buffer * @param length the length of the three buffers */ inline void xor_buf(uint8_t out[], const uint8_t in[], const uint8_t in2[], size_t length) { const size_t blocks = length - (length % 32); for(size_t i = 0; i != blocks; i += 32) { uint64_t x[4]; uint64_t y[4]; typecast_copy(x, in + i, 4); typecast_copy(y, in2 + i, 4); x[0] ^= y[0]; x[1] ^= y[1]; x[2] ^= y[2]; x[3] ^= y[3]; typecast_copy(out + i, x, 4); } for(size_t i = blocks; i != length; ++i) { out[i] = in[i] ^ in2[i]; } } template void xor_buf(std::vector& out, const std::vector& in, size_t n) { xor_buf(out.data(), in.data(), n); } template void xor_buf(std::vector& out, const uint8_t* in, size_t n) { xor_buf(out.data(), in, n); } template void xor_buf(std::vector& out, const uint8_t* in, const std::vector& in2, size_t n) { xor_buf(out.data(), in, in2.data(), n); } template std::vector& operator^=(std::vector& out, const std::vector& in) { if(out.size() < in.size()) out.resize(in.size()); xor_buf(out.data(), in.data(), in.size()); return out; } } namespace Botan { template class secure_allocator { public: /* * Assert exists to prevent someone from doing something that will * probably crash anyway (like secure_vector where ~non_POD_t * deletes a member pointer which was zeroed before it ran). * MSVC in debug mode uses non-integral proxy types in container types * like std::vector, thus we disable the check there. */ #if !defined(_ITERATOR_DEBUG_LEVEL) || _ITERATOR_DEBUG_LEVEL == 0 static_assert(std::is_integral::value, "secure_allocator supports only integer types"); #endif typedef T value_type; typedef std::size_t size_type; secure_allocator() noexcept = default; secure_allocator(const secure_allocator&) noexcept = default; secure_allocator& operator=(const secure_allocator&) noexcept = default; ~secure_allocator() noexcept = default; template secure_allocator(const secure_allocator&) noexcept {} T* allocate(std::size_t n) { return static_cast(allocate_memory(n, sizeof(T))); } void deallocate(T* p, std::size_t n) { deallocate_memory(p, n, sizeof(T)); } }; template inline bool operator==(const secure_allocator&, const secure_allocator&) { return true; } template inline bool operator!=(const secure_allocator&, const secure_allocator&) { return false; } template using secure_vector = std::vector>; template using secure_deque = std::deque>; // For better compatibility with 1.10 API template using SecureVector = secure_vector; template std::vector unlock(const secure_vector& in) { return std::vector(in.begin(), in.end()); } template std::vector& operator+=(std::vector& out, const std::vector& in) { out.reserve(out.size() + in.size()); out.insert(out.end(), in.begin(), in.end()); return out; } template std::vector& operator+=(std::vector& out, T in) { out.push_back(in); return out; } template std::vector& operator+=(std::vector& out, const std::pair& in) { out.reserve(out.size() + in.second); out.insert(out.end(), in.first, in.first + in.second); return out; } template std::vector& operator+=(std::vector& out, const std::pair& in) { out.reserve(out.size() + in.second); out.insert(out.end(), in.first, in.first + in.second); return out; } /** * Zeroise the values; length remains unchanged * @param vec the vector to zeroise */ template void zeroise(std::vector& vec) { clear_mem(vec.data(), vec.size()); } /** * Zeroise the values then free the memory * @param vec the vector to zeroise and free */ template void zap(std::vector& vec) { zeroise(vec); vec.clear(); vec.shrink_to_fit(); } } namespace Botan { /** * Octet String */ class BOTAN_PUBLIC_API(2,0) OctetString final { public: /** * @return size of this octet string in bytes */ size_t length() const { return m_data.size(); } size_t size() const { return m_data.size(); } /** * @return this object as a secure_vector */ secure_vector bits_of() const { return m_data; } /** * @return start of this string */ const uint8_t* begin() const { return m_data.data(); } /** * @return end of this string */ const uint8_t* end() const { return begin() + m_data.size(); } /** * @return this encoded as hex */ std::string to_string() const; std::string BOTAN_DEPRECATED("Use OctetString::to_string") as_string() const { return this->to_string(); } /** * XOR the contents of another octet string into this one * @param other octet string * @return reference to this */ OctetString& operator^=(const OctetString& other); /** * Force to have odd parity */ void set_odd_parity(); /** * Create a new OctetString * @param str is a hex encoded string */ explicit OctetString(const std::string& str = ""); /** * Create a new random OctetString * @param rng is a random number generator * @param len is the desired length in bytes */ OctetString(class RandomNumberGenerator& rng, size_t len); /** * Create a new OctetString * @param in is an array * @param len is the length of in in bytes */ OctetString(const uint8_t in[], size_t len); /** * Create a new OctetString * @param in a bytestring */ OctetString(const secure_vector& in) : m_data(in) {} /** * Create a new OctetString * @param in a bytestring */ OctetString(const std::vector& in) : m_data(in.begin(), in.end()) {} private: secure_vector m_data; }; /** * Compare two strings * @param x an octet string * @param y an octet string * @return if x is equal to y */ BOTAN_PUBLIC_API(2,0) bool operator==(const OctetString& x, const OctetString& y); /** * Compare two strings * @param x an octet string * @param y an octet string * @return if x is not equal to y */ BOTAN_PUBLIC_API(2,0) bool operator!=(const OctetString& x, const OctetString& y); /** * Concatenate two strings * @param x an octet string * @param y an octet string * @return x concatenated with y */ BOTAN_PUBLIC_API(2,0) OctetString operator+(const OctetString& x, const OctetString& y); /** * XOR two strings * @param x an octet string * @param y an octet string * @return x XORed with y */ BOTAN_PUBLIC_API(2,0) OctetString operator^(const OctetString& x, const OctetString& y); /** * Alternate name for octet string showing intent to use as a key */ using SymmetricKey = OctetString; /** * Alternate name for octet string showing intent to use as an IV */ using InitializationVector = OctetString; } namespace Botan { /** * Represents the length requirements on an algorithm key */ class BOTAN_PUBLIC_API(2,0) Key_Length_Specification final { public: /** * Constructor for fixed length keys * @param keylen the supported key length */ explicit Key_Length_Specification(size_t keylen) : m_min_keylen(keylen), m_max_keylen(keylen), m_keylen_mod(1) { } /** * Constructor for variable length keys * @param min_k the smallest supported key length * @param max_k the largest supported key length * @param k_mod the number of bytes the key must be a multiple of */ Key_Length_Specification(size_t min_k, size_t max_k, size_t k_mod = 1) : m_min_keylen(min_k), m_max_keylen(max_k ? max_k : min_k), m_keylen_mod(k_mod) { } /** * @param length is a key length in bytes * @return true iff this length is a valid length for this algo */ bool valid_keylength(size_t length) const { return ((length >= m_min_keylen) && (length <= m_max_keylen) && (length % m_keylen_mod == 0)); } /** * @return minimum key length in bytes */ size_t minimum_keylength() const { return m_min_keylen; } /** * @return maximum key length in bytes */ size_t maximum_keylength() const { return m_max_keylen; } /** * @return key length multiple in bytes */ size_t keylength_multiple() const { return m_keylen_mod; } /* * Multiplies all length requirements with the given factor * @param n the multiplication factor * @return a key length specification multiplied by the factor */ Key_Length_Specification multiple(size_t n) const { return Key_Length_Specification(n * m_min_keylen, n * m_max_keylen, n * m_keylen_mod); } private: size_t m_min_keylen, m_max_keylen, m_keylen_mod; }; /** * This class represents a symmetric algorithm object. */ class BOTAN_PUBLIC_API(2,0) SymmetricAlgorithm { public: virtual ~SymmetricAlgorithm() = default; /** * Reset the state. */ virtual void clear() = 0; /** * @return object describing limits on key size */ virtual Key_Length_Specification key_spec() const = 0; /** * @return maximum allowed key length */ size_t maximum_keylength() const { return key_spec().maximum_keylength(); } /** * @return minimum allowed key length */ size_t minimum_keylength() const { return key_spec().minimum_keylength(); } /** * Check whether a given key length is valid for this algorithm. * @param length the key length to be checked. * @return true if the key length is valid. */ bool valid_keylength(size_t length) const { return key_spec().valid_keylength(length); } /** * Set the symmetric key of this object. * @param key the SymmetricKey to be set. */ void set_key(const SymmetricKey& key) { set_key(key.begin(), key.length()); } template void set_key(const std::vector& key) { set_key(key.data(), key.size()); } /** * Set the symmetric key of this object. * @param key the to be set as a byte array. * @param length in bytes of key param */ void set_key(const uint8_t key[], size_t length); /** * @return the algorithm name */ virtual std::string name() const = 0; protected: void verify_key_set(bool cond) const { if(cond == false) throw_key_not_set_error(); } private: void throw_key_not_set_error() const; /** * Run the key schedule * @param key the key * @param length of key */ virtual void key_schedule(const uint8_t key[], size_t length) = 0; }; } namespace Botan { /** * Different types of errors that might occur */ enum class ErrorType { /** Some unknown error */ Unknown = 1, /** An error while calling a system interface */ SystemError, /** An operation seems valid, but not supported by the current version */ NotImplemented, /** Memory allocation failure */ OutOfMemory, /** An internal error occurred */ InternalError, /** An I/O error occurred */ IoError, /** Invalid object state */ InvalidObjectState = 100, /** A key was not set on an object when this is required */ KeyNotSet, /** The application provided an argument which is invalid */ InvalidArgument, /** A key with invalid length was provided */ InvalidKeyLength, /** A nonce with invalid length was provided */ InvalidNonceLength, /** An object type was requested but cannot be found */ LookupError, /** Encoding a message or datum failed */ EncodingFailure, /** Decoding a message or datum failed */ DecodingFailure, /** A TLS error (error_code will be the alert type) */ TLSError, /** An error during an HTTP operation */ HttpError, /** A message with an invalid authentication tag was detected */ InvalidTag, /** An error during Roughtime validation */ RoughtimeError, /** An error when calling OpenSSL */ OpenSSLError = 200, /** An error when interacting with CommonCrypto API */ CommonCryptoError, /** An error when interacting with a PKCS11 device */ Pkcs11Error, /** An error when interacting with a TPM device */ TPMError, /** An error when interacting with a database */ DatabaseError, /** An error when interacting with zlib */ ZlibError = 300, /** An error when interacting with bzip2 */ Bzip2Error, /** An error when interacting with lzma */ LzmaError, }; //! \brief Convert an ErrorType to string std::string BOTAN_PUBLIC_API(2,11) to_string(ErrorType type); /** * Base class for all exceptions thrown by the library */ class BOTAN_PUBLIC_API(2,0) Exception : public std::exception { public: /** * Return a descriptive string which is hopefully comprehensible to * a developer. It will likely not be useful for an end user. * * The string has no particular format, and the content of exception * messages may change from release to release. Thus the main use of this * function is for logging or debugging. */ const char* what() const noexcept override { return m_msg.c_str(); } /** * Return the "type" of error which occurred. */ virtual ErrorType error_type() const noexcept { return Botan::ErrorType::Unknown; } /** * Return an error code associated with this exception, or otherwise 0. * * The domain of this error varies depending on the source, for example on * POSIX systems it might be errno, while on a Windows system it might be * the result of GetLastError or WSAGetLastError. For error_type() is * OpenSSLError, it will (if nonzero) be an OpenSSL error code from * ERR_get_error. */ virtual int error_code() const noexcept { return 0; } /** * Avoid throwing base Exception, use a subclass */ explicit Exception(const std::string& msg); /** * Avoid throwing base Exception, use a subclass */ Exception(const char* prefix, const std::string& msg); /** * Avoid throwing base Exception, use a subclass */ Exception(const std::string& msg, const std::exception& e); private: std::string m_msg; }; /** * An invalid argument was provided to an API call. */ class BOTAN_PUBLIC_API(2,0) Invalid_Argument : public Exception { public: explicit Invalid_Argument(const std::string& msg); explicit Invalid_Argument(const std::string& msg, const std::string& where); Invalid_Argument(const std::string& msg, const std::exception& e); ErrorType error_type() const noexcept override { return ErrorType::InvalidArgument; } }; /** * An invalid key length was used */ class BOTAN_PUBLIC_API(2,0) Invalid_Key_Length final : public Invalid_Argument { public: Invalid_Key_Length(const std::string& name, size_t length); ErrorType error_type() const noexcept override { return ErrorType::InvalidKeyLength; } }; /** * An invalid nonce length was used */ class BOTAN_PUBLIC_API(2,0) Invalid_IV_Length final : public Invalid_Argument { public: Invalid_IV_Length(const std::string& mode, size_t bad_len); ErrorType error_type() const noexcept override { return ErrorType::InvalidNonceLength; } }; /** * Invalid_Algorithm_Name Exception */ class BOTAN_PUBLIC_API(2,0) Invalid_Algorithm_Name final : public Invalid_Argument { public: explicit Invalid_Algorithm_Name(const std::string& name); }; /** * Encoding_Error Exception * * This exception derives from Invalid_Argument for historical reasons, and it * does not make any real sense for it to do so. In a future major release this * exception type will derive directly from Exception instead. */ class BOTAN_PUBLIC_API(2,0) Encoding_Error final : public Invalid_Argument { public: explicit Encoding_Error(const std::string& name); ErrorType error_type() const noexcept override { return ErrorType::EncodingFailure; } }; /** * A decoding error occurred. * * This exception derives from Invalid_Argument for historical reasons, and it * does not make any real sense for it to do so. In a future major release this * exception type will derive directly from Exception instead. */ class BOTAN_PUBLIC_API(2,0) Decoding_Error : public Invalid_Argument { public: explicit Decoding_Error(const std::string& name); Decoding_Error(const std::string& name, const char* exception_message); Decoding_Error(const std::string& msg, const std::exception& e); ErrorType error_type() const noexcept override { return ErrorType::DecodingFailure; } }; /** * Invalid state was encountered. A request was made on an object while the * object was in a state where the operation cannot be performed. */ class BOTAN_PUBLIC_API(2,0) Invalid_State : public Exception { public: explicit Invalid_State(const std::string& err) : Exception(err) {} ErrorType error_type() const noexcept override { return ErrorType::InvalidObjectState; } }; /** * A PRNG was called on to produce output while still unseeded */ class BOTAN_PUBLIC_API(2,0) PRNG_Unseeded final : public Invalid_State { public: explicit PRNG_Unseeded(const std::string& algo); }; /** * The key was not set on an object. This occurs with symmetric objects where * an operation which requires the key is called prior to set_key being called. */ class BOTAN_PUBLIC_API(2,4) Key_Not_Set : public Invalid_State { public: explicit Key_Not_Set(const std::string& algo); ErrorType error_type() const noexcept override { return ErrorType::KeyNotSet; } }; /** * A request was made for some kind of object which could not be located */ class BOTAN_PUBLIC_API(2,0) Lookup_Error : public Exception { public: explicit Lookup_Error(const std::string& err) : Exception(err) {} Lookup_Error(const std::string& type, const std::string& algo, const std::string& provider); ErrorType error_type() const noexcept override { return ErrorType::LookupError; } }; /** * Algorithm_Not_Found Exception * * @warning This exception type will be removed in the future. Instead * just catch Lookup_Error. */ class BOTAN_PUBLIC_API(2,0) Algorithm_Not_Found final : public Lookup_Error { public: explicit Algorithm_Not_Found(const std::string& name); }; /** * Provider_Not_Found is thrown when a specific provider was requested * but that provider is not available. * * @warning This exception type will be removed in the future. Instead * just catch Lookup_Error. */ class BOTAN_PUBLIC_API(2,0) Provider_Not_Found final : public Lookup_Error { public: Provider_Not_Found(const std::string& algo, const std::string& provider); }; /** * An AEAD or MAC check detected a message modification * * In versions before 2.10, Invalid_Authentication_Tag was named * Integrity_Failure, it was renamed to make its usage more clear. */ class BOTAN_PUBLIC_API(2,0) Invalid_Authentication_Tag final : public Exception { public: explicit Invalid_Authentication_Tag(const std::string& msg); ErrorType error_type() const noexcept override { return ErrorType::InvalidTag; } }; /** * For compatability with older versions */ typedef Invalid_Authentication_Tag Integrity_Failure; /** * An error occurred while operating on an IO stream */ class BOTAN_PUBLIC_API(2,0) Stream_IO_Error final : public Exception { public: explicit Stream_IO_Error(const std::string& err); ErrorType error_type() const noexcept override { return ErrorType::IoError; } }; /** * System_Error * * This exception is thrown in the event of an error related to interacting * with the operating system. * * This exception type also (optionally) captures an integer error code eg * POSIX errno or Windows GetLastError. */ class BOTAN_PUBLIC_API(2,9) System_Error : public Exception { public: System_Error(const std::string& msg) : Exception(msg), m_error_code(0) {} System_Error(const std::string& msg, int err_code); ErrorType error_type() const noexcept override { return ErrorType::SystemError; } int error_code() const noexcept override { return m_error_code; } private: int m_error_code; }; /** * An internal error occurred. If observed, please file a bug. */ class BOTAN_PUBLIC_API(2,0) Internal_Error : public Exception { public: explicit Internal_Error(const std::string& err); ErrorType error_type() const noexcept override { return ErrorType::InternalError; } }; /** * Not Implemented Exception * * This is thrown in the situation where a requested operation is * logically valid but is not implemented by this version of the library. */ class BOTAN_PUBLIC_API(2,0) Not_Implemented final : public Exception { public: explicit Not_Implemented(const std::string& err); ErrorType error_type() const noexcept override { return ErrorType::NotImplemented; } }; /* The following exception types are still in use for compatability reasons, but are deprecated and will be removed in a future major release. Instead catch the base class. */ /** * An invalid OID string was used. * * This exception will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) Invalid_OID final : public Decoding_Error { public: explicit Invalid_OID(const std::string& oid); }; /* The following exception types are deprecated, no longer used, and will be removed in a future major release */ /** * Self Test Failure Exception * * This exception is no longer used. It will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) Self_Test_Failure final : public Internal_Error { public: BOTAN_DEPRECATED("no longer used") explicit Self_Test_Failure(const std::string& err); }; /** * No_Provider_Found Exception * * This exception is no longer used. It will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) No_Provider_Found final : public Exception { public: BOTAN_DEPRECATED("no longer used") explicit No_Provider_Found(const std::string& name); }; /** * Policy_Violation Exception * * This exception is no longer used. It will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) Policy_Violation final : public Invalid_State { public: BOTAN_DEPRECATED("no longer used") explicit Policy_Violation(const std::string& err); }; /** * Unsupported_Argument Exception * * An argument that is invalid because it is not supported by Botan. * It might or might not be valid in another context like a standard. * * This exception is no longer used, instead Not_Implemented is thrown. * It will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) Unsupported_Argument final : public Invalid_Argument { public: BOTAN_DEPRECATED("no longer used") explicit Unsupported_Argument(const std::string& msg) : Invalid_Argument(msg) {} }; template inline void do_throw_error(const char* file, int line, const char* func, Args... args) { throw E(file, line, func, args...); } } namespace Botan { /** * The two possible directions for cipher filters, determining whether they * actually perform encryption or decryption. */ enum Cipher_Dir : int { ENCRYPTION, DECRYPTION }; /** * Interface for cipher modes */ class BOTAN_PUBLIC_API(2,0) Cipher_Mode : public SymmetricAlgorithm { public: /** * @return list of available providers for this algorithm, empty if not available * @param algo_spec algorithm name */ static std::vector providers(const std::string& algo_spec); /** * Create an AEAD mode * @param algo the algorithm to create * @param direction specify if this should be an encryption or decryption AEAD * @param provider optional specification for provider to use * @return an AEAD mode or a null pointer if not available */ static std::unique_ptr create(const std::string& algo, Cipher_Dir direction, const std::string& provider = ""); /** * Create an AEAD mode, or throw * @param algo the algorithm to create * @param direction specify if this should be an encryption or decryption AEAD * @param provider optional specification for provider to use * @return an AEAD mode, or throw an exception */ static std::unique_ptr create_or_throw(const std::string& algo, Cipher_Dir direction, const std::string& provider = ""); /* * Prepare for processing a message under the specified nonce */ virtual void start_msg(const uint8_t nonce[], size_t nonce_len) = 0; /** * Begin processing a message. * @param nonce the per message nonce */ template void start(const std::vector& nonce) { start_msg(nonce.data(), nonce.size()); } /** * Begin processing a message. * @param nonce the per message nonce * @param nonce_len length of nonce */ void start(const uint8_t nonce[], size_t nonce_len) { start_msg(nonce, nonce_len); } /** * Begin processing a message. */ void start() { return start_msg(nullptr, 0); } /** * Process message blocks * * Input must be a multiple of update_granularity * * Processes msg in place and returns bytes written. Normally * this will be either msg_len (indicating the entire message was * processed) or for certain AEAD modes zero (indicating that the * mode requires the entire message be processed in one pass). * * @param msg the message to be processed * @param msg_len length of the message in bytes */ virtual size_t process(uint8_t msg[], size_t msg_len) = 0; /** * Process some data. Input must be in size update_granularity() uint8_t blocks. * @param buffer in/out parameter which will possibly be resized * @param offset an offset into blocks to begin processing */ void update(secure_vector& buffer, size_t offset = 0) { BOTAN_ASSERT(buffer.size() >= offset, "Offset ok"); uint8_t* buf = buffer.data() + offset; const size_t buf_size = buffer.size() - offset; const size_t written = process(buf, buf_size); buffer.resize(offset + written); } /** * Complete processing of a message. * * @param final_block in/out parameter which must be at least * minimum_final_size() bytes, and will be set to any final output * @param offset an offset into final_block to begin processing */ virtual void finish(secure_vector& final_block, size_t offset = 0) = 0; /** * Returns the size of the output if this transform is used to process a * message with input_length bytes. In most cases the answer is precise. * If it is not possible to precise (namely for CBC decryption) instead a * lower bound is returned. */ virtual size_t output_length(size_t input_length) const = 0; /** * @return size of required blocks to update */ virtual size_t update_granularity() const = 0; /** * @return required minimium size to finalize() - may be any * length larger than this. */ virtual size_t minimum_final_size() const = 0; /** * @return the default size for a nonce */ virtual size_t default_nonce_length() const = 0; /** * @return true iff nonce_len is a valid length for the nonce */ virtual bool valid_nonce_length(size_t nonce_len) const = 0; /** * Resets just the message specific state and allows encrypting again under the existing key */ virtual void reset() = 0; /** * @return true iff this mode provides authentication as well as * confidentiality. */ virtual bool authenticated() const { return false; } /** * @return the size of the authentication tag used (in bytes) */ virtual size_t tag_size() const { return 0; } /** * @return provider information about this implementation. Default is "base", * might also return "sse2", "avx2", "openssl", or some other arbitrary string. */ virtual std::string provider() const { return "base"; } }; /** * Get a cipher mode by name (eg "AES-128/CBC" or "Serpent/XTS") * @param algo_spec cipher name * @param direction ENCRYPTION or DECRYPTION * @param provider provider implementation to choose */ inline Cipher_Mode* get_cipher_mode(const std::string& algo_spec, Cipher_Dir direction, const std::string& provider = "") { return Cipher_Mode::create(algo_spec, direction, provider).release(); } } namespace Botan { /** * Interface for AEAD (Authenticated Encryption with Associated Data) * modes. These modes provide both encryption and message * authentication, and can authenticate additional per-message data * which is not included in the ciphertext (for instance a sequence * number). */ class BOTAN_PUBLIC_API(2,0) AEAD_Mode : public Cipher_Mode { public: /** * Create an AEAD mode * @param algo the algorithm to create * @param direction specify if this should be an encryption or decryption AEAD * @param provider optional specification for provider to use * @return an AEAD mode or a null pointer if not available */ static std::unique_ptr create(const std::string& algo, Cipher_Dir direction, const std::string& provider = ""); /** * Create an AEAD mode, or throw * @param algo the algorithm to create * @param direction specify if this should be an encryption or decryption AEAD * @param provider optional specification for provider to use * @return an AEAD mode, or throw an exception */ static std::unique_ptr create_or_throw(const std::string& algo, Cipher_Dir direction, const std::string& provider = ""); bool authenticated() const override { return true; } /** * Set associated data that is not included in the ciphertext but * that should be authenticated. Must be called after set_key and * before start. * * Unless reset by another call, the associated data is kept * between messages. Thus, if the AD does not change, calling * once (after set_key) is the optimum. * * @param ad the associated data * @param ad_len length of add in bytes */ virtual void set_associated_data(const uint8_t ad[], size_t ad_len) = 0; /** * Set associated data that is not included in the ciphertext but * that should be authenticated. Must be called after set_key and * before start. * * Unless reset by another call, the associated data is kept * between messages. Thus, if the AD does not change, calling * once (after set_key) is the optimum. * * Some AEADs (namely SIV) support multiple AD inputs. For * all other modes only nominal AD input 0 is supported; all * other values of i will cause an exception. * * @param ad the associated data * @param ad_len length of add in bytes */ virtual void set_associated_data_n(size_t i, const uint8_t ad[], size_t ad_len); /** * Returns the maximum supported number of associated data inputs which * can be provided to set_associated_data_n * * If returns 0, then no associated data is supported. */ virtual size_t maximum_associated_data_inputs() const { return 1; } /** * Most AEADs require the key to be set prior to setting the AD * A few allow the AD to be set even before the cipher is keyed. * Such ciphers would return false from this function. */ virtual bool associated_data_requires_key() const { return true; } /** * Set associated data that is not included in the ciphertext but * that should be authenticated. Must be called after set_key and * before start. * * See @ref set_associated_data(). * * @param ad the associated data */ template void set_associated_data_vec(const std::vector& ad) { set_associated_data(ad.data(), ad.size()); } /** * Set associated data that is not included in the ciphertext but * that should be authenticated. Must be called after set_key and * before start. * * See @ref set_associated_data(). * * @param ad the associated data */ template void set_ad(const std::vector& ad) { set_associated_data(ad.data(), ad.size()); } /** * @return default AEAD nonce size (a commonly supported value among AEAD * modes, and large enough that random collisions are unlikely) */ size_t default_nonce_length() const override { return 12; } virtual ~AEAD_Mode() = default; }; /** * Get an AEAD mode by name (eg "AES-128/GCM" or "Serpent/EAX") * @param name AEAD name * @param direction ENCRYPTION or DECRYPTION */ inline AEAD_Mode* get_aead(const std::string& name, Cipher_Dir direction) { return AEAD_Mode::create(name, direction, "").release(); } } namespace Botan { /** * This class represents a block cipher object. */ class BOTAN_PUBLIC_API(2,0) BlockCipher : public SymmetricAlgorithm { public: /** * Create an instance based on a name * If provider is empty then best available is chosen. * @param algo_spec algorithm name * @param provider provider implementation to choose * @return a null pointer if the algo/provider combination cannot be found */ static std::unique_ptr create(const std::string& algo_spec, const std::string& provider = ""); /** * Create an instance based on a name, or throw if the * algo/provider combination cannot be found. If provider is * empty then best available is chosen. */ static std::unique_ptr create_or_throw(const std::string& algo_spec, const std::string& provider = ""); /** * @return list of available providers for this algorithm, empty if not available * @param algo_spec algorithm name */ static std::vector providers(const std::string& algo_spec); /** * @return block size of this algorithm */ virtual size_t block_size() const = 0; /** * @return native parallelism of this cipher in blocks */ virtual size_t parallelism() const { return 1; } /** * @return prefererred parallelism of this cipher in bytes */ size_t parallel_bytes() const { return parallelism() * block_size() * BOTAN_BLOCK_CIPHER_PAR_MULT; } /** * @return provider information about this implementation. Default is "base", * might also return "sse2", "avx2", "openssl", or some other arbitrary string. */ virtual std::string provider() const { return "base"; } /** * Encrypt a block. * @param in The plaintext block to be encrypted as a byte array. * Must be of length block_size(). * @param out The byte array designated to hold the encrypted block. * Must be of length block_size(). */ void encrypt(const uint8_t in[], uint8_t out[]) const { encrypt_n(in, out, 1); } /** * Decrypt a block. * @param in The ciphertext block to be decypted as a byte array. * Must be of length block_size(). * @param out The byte array designated to hold the decrypted block. * Must be of length block_size(). */ void decrypt(const uint8_t in[], uint8_t out[]) const { decrypt_n(in, out, 1); } /** * Encrypt a block. * @param block the plaintext block to be encrypted * Must be of length block_size(). Will hold the result when the function * has finished. */ void encrypt(uint8_t block[]) const { encrypt_n(block, block, 1); } /** * Decrypt a block. * @param block the ciphertext block to be decrypted * Must be of length block_size(). Will hold the result when the function * has finished. */ void decrypt(uint8_t block[]) const { decrypt_n(block, block, 1); } /** * Encrypt one or more blocks * @param block the input/output buffer (multiple of block_size()) */ template void encrypt(std::vector& block) const { return encrypt_n(block.data(), block.data(), block.size() / block_size()); } /** * Decrypt one or more blocks * @param block the input/output buffer (multiple of block_size()) */ template void decrypt(std::vector& block) const { return decrypt_n(block.data(), block.data(), block.size() / block_size()); } /** * Encrypt one or more blocks * @param in the input buffer (multiple of block_size()) * @param out the output buffer (same size as in) */ template void encrypt(const std::vector& in, std::vector& out) const { return encrypt_n(in.data(), out.data(), in.size() / block_size()); } /** * Decrypt one or more blocks * @param in the input buffer (multiple of block_size()) * @param out the output buffer (same size as in) */ template void decrypt(const std::vector& in, std::vector& out) const { return decrypt_n(in.data(), out.data(), in.size() / block_size()); } /** * Encrypt one or more blocks * @param in the input buffer (multiple of block_size()) * @param out the output buffer (same size as in) * @param blocks the number of blocks to process */ virtual void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const = 0; /** * Decrypt one or more blocks * @param in the input buffer (multiple of block_size()) * @param out the output buffer (same size as in) * @param blocks the number of blocks to process */ virtual void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const = 0; virtual void encrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const { const size_t BS = block_size(); xor_buf(data, mask, blocks * BS); encrypt_n(data, data, blocks); xor_buf(data, mask, blocks * BS); } virtual void decrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const { const size_t BS = block_size(); xor_buf(data, mask, blocks * BS); decrypt_n(data, data, blocks); xor_buf(data, mask, blocks * BS); } /** * @return new object representing the same algorithm as *this */ virtual BlockCipher* clone() const = 0; virtual ~BlockCipher() = default; }; /** * Tweakable block ciphers allow setting a tweak which is a non-keyed * value which affects the encryption/decryption operation. */ class BOTAN_PUBLIC_API(2,8) Tweakable_Block_Cipher : public BlockCipher { public: /** * Set the tweak value. This must be called after setting a key. The value * persists until either set_tweak, set_key, or clear is called. * Different algorithms support different tweak length(s). If called with * an unsupported length, Invalid_Argument will be thrown. */ virtual void set_tweak(const uint8_t tweak[], size_t len) = 0; }; /** * Represents a block cipher with a single fixed block size */ template class Block_Cipher_Fixed_Params : public BaseClass { public: enum { BLOCK_SIZE = BS }; size_t block_size() const final override { return BS; } // override to take advantage of compile time constant block size void encrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const final override { xor_buf(data, mask, blocks * BS); this->encrypt_n(data, data, blocks); xor_buf(data, mask, blocks * BS); } void decrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const final override { xor_buf(data, mask, blocks * BS); this->decrypt_n(data, data, blocks); xor_buf(data, mask, blocks * BS); } Key_Length_Specification key_spec() const final override { return Key_Length_Specification(KMIN, KMAX, KMOD); } }; } BOTAN_FUTURE_INTERNAL_HEADER(aes.h) namespace Botan { /** * AES-128 */ class BOTAN_PUBLIC_API(2,0) AES_128 final : public Block_Cipher_Fixed_Params<16, 16> { public: void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void clear() override; std::string provider() const override; std::string name() const override { return "AES-128"; } BlockCipher* clone() const override { return new AES_128; } size_t parallelism() const override; private: void key_schedule(const uint8_t key[], size_t length) override; #if defined(BOTAN_HAS_AES_VPERM) void vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_NI) void aesni_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_POWER8) || defined(BOTAN_HAS_AES_ARMV8) || defined(BOTAN_HAS_AES_NI) void hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; #endif secure_vector m_EK, m_DK; }; /** * AES-192 */ class BOTAN_PUBLIC_API(2,0) AES_192 final : public Block_Cipher_Fixed_Params<16, 24> { public: void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void clear() override; std::string provider() const override; std::string name() const override { return "AES-192"; } BlockCipher* clone() const override { return new AES_192; } size_t parallelism() const override; private: #if defined(BOTAN_HAS_AES_VPERM) void vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_NI) void aesni_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_POWER8) || defined(BOTAN_HAS_AES_ARMV8) || defined(BOTAN_HAS_AES_NI) void hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; #endif void key_schedule(const uint8_t key[], size_t length) override; secure_vector m_EK, m_DK; }; /** * AES-256 */ class BOTAN_PUBLIC_API(2,0) AES_256 final : public Block_Cipher_Fixed_Params<16, 32> { public: void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void clear() override; std::string provider() const override; std::string name() const override { return "AES-256"; } BlockCipher* clone() const override { return new AES_256; } size_t parallelism() const override; private: #if defined(BOTAN_HAS_AES_VPERM) void vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_NI) void aesni_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_POWER8) || defined(BOTAN_HAS_AES_ARMV8) || defined(BOTAN_HAS_AES_NI) void hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; #endif void key_schedule(const uint8_t key[], size_t length) override; secure_vector m_EK, m_DK; }; } #if defined(BOTAN_BUILD_COMPILER_IS_MSVC) #include #endif BOTAN_FUTURE_INTERNAL_HEADER(bswap.h) namespace Botan { /** * Swap a 16 bit integer */ inline uint16_t reverse_bytes(uint16_t val) { #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || defined(BOTAN_BUILD_COMPILER_IS_CLANG) || defined(BOTAN_BUILD_COMPILER_IS_XLC) return __builtin_bswap16(val); #else return static_cast((val << 8) | (val >> 8)); #endif } /** * Swap a 32 bit integer */ inline uint32_t reverse_bytes(uint32_t val) { #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || defined(BOTAN_BUILD_COMPILER_IS_CLANG) || defined(BOTAN_BUILD_COMPILER_IS_XLC) return __builtin_bswap32(val); #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC) return _byteswap_ulong(val); #elif defined(BOTAN_USE_GCC_INLINE_ASM) && defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) // GCC-style inline assembly for x86 or x86-64 asm("bswapl %0" : "=r" (val) : "0" (val)); return val; #else // Generic implementation uint16_t hi = static_cast(val >> 16); uint16_t lo = static_cast(val); hi = reverse_bytes(hi); lo = reverse_bytes(lo); return (static_cast(lo) << 16) | hi; #endif } /** * Swap a 64 bit integer */ inline uint64_t reverse_bytes(uint64_t val) { #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || defined(BOTAN_BUILD_COMPILER_IS_CLANG) || defined(BOTAN_BUILD_COMPILER_IS_XLC) return __builtin_bswap64(val); #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC) return _byteswap_uint64(val); #elif defined(BOTAN_USE_GCC_INLINE_ASM) && defined(BOTAN_TARGET_ARCH_IS_X86_64) // GCC-style inline assembly for x86-64 asm("bswapq %0" : "=r" (val) : "0" (val)); return val; #else /* Generic implementation. Defined in terms of 32-bit bswap so any * optimizations in that version can help. */ uint32_t hi = static_cast(val >> 32); uint32_t lo = static_cast(val); hi = reverse_bytes(hi); lo = reverse_bytes(lo); return (static_cast(lo) << 32) | hi; #endif } /** * Swap 4 Ts in an array */ template inline void bswap_4(T x[4]) { x[0] = reverse_bytes(x[0]); x[1] = reverse_bytes(x[1]); x[2] = reverse_bytes(x[2]); x[3] = reverse_bytes(x[3]); } } namespace Botan { /** * This class represents any kind of computation which uses an internal * state, such as hash functions or MACs */ class BOTAN_PUBLIC_API(2,0) Buffered_Computation { public: /** * @return length of the output of this function in bytes */ virtual size_t output_length() const = 0; /** * Add new input to process. * @param in the input to process as a byte array * @param length of param in in bytes */ void update(const uint8_t in[], size_t length) { add_data(in, length); } /** * Add new input to process. * @param in the input to process as a secure_vector */ void update(const secure_vector& in) { add_data(in.data(), in.size()); } /** * Add new input to process. * @param in the input to process as a std::vector */ void update(const std::vector& in) { add_data(in.data(), in.size()); } void update_be(uint16_t val); void update_be(uint32_t val); void update_be(uint64_t val); void update_le(uint16_t val); void update_le(uint32_t val); void update_le(uint64_t val); /** * Add new input to process. * @param str the input to process as a std::string. Will be interpreted * as a byte array based on the strings encoding. */ void update(const std::string& str) { add_data(cast_char_ptr_to_uint8(str.data()), str.size()); } /** * Process a single byte. * @param in the byte to process */ void update(uint8_t in) { add_data(&in, 1); } /** * Complete the computation and retrieve the * final result. * @param out The byte array to be filled with the result. * Must be of length output_length() */ void final(uint8_t out[]) { final_result(out); } /** * Complete the computation and retrieve the * final result. * @return secure_vector holding the result */ secure_vector final() { secure_vector output(output_length()); final_result(output.data()); return output; } std::vector final_stdvec() { std::vector output(output_length()); final_result(output.data()); return output; } template void final(std::vector& out) { out.resize(output_length()); final_result(out.data()); } /** * Update and finalize computation. Does the same as calling update() * and final() consecutively. * @param in the input to process as a byte array * @param length the length of the byte array * @result the result of the call to final() */ secure_vector process(const uint8_t in[], size_t length) { add_data(in, length); return final(); } /** * Update and finalize computation. Does the same as calling update() * and final() consecutively. * @param in the input to process * @result the result of the call to final() */ secure_vector process(const secure_vector& in) { add_data(in.data(), in.size()); return final(); } /** * Update and finalize computation. Does the same as calling update() * and final() consecutively. * @param in the input to process * @result the result of the call to final() */ secure_vector process(const std::vector& in) { add_data(in.data(), in.size()); return final(); } /** * Update and finalize computation. Does the same as calling update() * and final() consecutively. * @param in the input to process as a string * @result the result of the call to final() */ secure_vector process(const std::string& in) { update(in); return final(); } virtual ~Buffered_Computation() = default; private: /** * Add more data to the computation * @param input is an input buffer * @param length is the length of input in bytes */ virtual void add_data(const uint8_t input[], size_t length) = 0; /** * Write the final output to out * @param out is an output buffer of output_length() */ virtual void final_result(uint8_t out[]) = 0; }; } namespace Botan { /** * Struct representing a particular date and time */ class BOTAN_PUBLIC_API(2,0) calendar_point { public: /** The year */ uint32_t get_year() const { return year; } /** The month, 1 through 12 for Jan to Dec */ uint32_t get_month() const { return month; } /** The day of the month, 1 through 31 (or 28 or 30 based on month */ uint32_t get_day() const { return day; } /** Hour in 24-hour form, 0 to 23 */ uint32_t get_hour() const { return hour; } /** Minutes in the hour, 0 to 60 */ uint32_t get_minutes() const { return minutes; } /** Seconds in the minute, 0 to 60, but might be slightly larger to deal with leap seconds on some systems */ uint32_t get_seconds() const { return seconds; } /** * Initialize a calendar_point * @param y the year * @param mon the month * @param d the day * @param h the hour * @param min the minute * @param sec the second */ calendar_point(uint32_t y, uint32_t mon, uint32_t d, uint32_t h, uint32_t min, uint32_t sec) : year(y), month(mon), day(d), hour(h), minutes(min), seconds(sec) {} /** * Returns an STL timepoint object */ std::chrono::system_clock::time_point to_std_timepoint() const; /** * Returns a human readable string of the struct's components. * Formatting might change over time. Currently it is RFC339 'iso-date-time'. */ std::string to_string() const; BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES: /* The member variables are public for historical reasons. Use the get_xxx() functions defined above. These members will be made private in a future major release. */ uint32_t year; uint32_t month; uint32_t day; uint32_t hour; uint32_t minutes; uint32_t seconds; }; /** * Convert a time_point to a calendar_point * @param time_point a time point from the system clock * @return calendar_point object representing this time point */ BOTAN_PUBLIC_API(2,0) calendar_point calendar_value( const std::chrono::system_clock::time_point& time_point); } BOTAN_FUTURE_INTERNAL_HEADER(charset.h) namespace Botan { /** * Convert a sequence of UCS-2 (big endian) characters to a UTF-8 string * This is used for ASN.1 BMPString type * @param ucs2 the sequence of UCS-2 characters * @param len length of ucs2 in bytes, must be a multiple of 2 */ std::string BOTAN_UNSTABLE_API ucs2_to_utf8(const uint8_t ucs2[], size_t len); /** * Convert a sequence of UCS-4 (big endian) characters to a UTF-8 string * This is used for ASN.1 UniversalString type * @param ucs4 the sequence of UCS-4 characters * @param len length of ucs4 in bytes, must be a multiple of 4 */ std::string BOTAN_UNSTABLE_API ucs4_to_utf8(const uint8_t ucs4[], size_t len); /** * Convert a UTF-8 string to Latin-1 * If a character outside the Latin-1 range is encountered, an exception is thrown. */ std::string BOTAN_UNSTABLE_API utf8_to_latin1(const std::string& utf8); /** * The different charsets (nominally) supported by Botan. */ enum Character_Set { LOCAL_CHARSET, UCS2_CHARSET, UTF8_CHARSET, LATIN1_CHARSET }; namespace Charset { /* * Character set conversion - avoid this. * For specific conversions, use the functions above like * ucs2_to_utf8 and utf8_to_latin1 * * If you need something more complex than that, use a real library * such as iconv, Boost.Locale, or ICU */ std::string BOTAN_PUBLIC_API(2,0) BOTAN_DEPRECATED("Avoid. See comment in header.") transcode(const std::string& str, Character_Set to, Character_Set from); /* * Simple character classifier functions */ bool BOTAN_PUBLIC_API(2,0) is_digit(char c); bool BOTAN_PUBLIC_API(2,0) is_space(char c); bool BOTAN_PUBLIC_API(2,0) caseless_cmp(char x, char y); uint8_t BOTAN_PUBLIC_API(2,0) char2digit(char c); char BOTAN_PUBLIC_API(2,0) digit2char(uint8_t b); } } BOTAN_FUTURE_INTERNAL_HEADER(cpuid.h) namespace Botan { /** * A class handling runtime CPU feature detection. It is limited to * just the features necessary to implement CPU specific code in Botan, * rather than being a general purpose utility. * * This class supports: * * - x86 features using CPUID. x86 is also the only processor with * accurate cache line detection currently. * * - PowerPC AltiVec detection on Linux, NetBSD, OpenBSD, and macOS * * - ARM NEON and crypto extensions detection. On Linux and Android * systems which support getauxval, that is used to access CPU * feature information. Otherwise a relatively portable but * thread-unsafe mechanism involving executing probe functions which * catching SIGILL signal is used. */ class BOTAN_PUBLIC_API(2,1) CPUID final { public: /** * Probe the CPU and see what extensions are supported */ static void initialize(); static bool has_simd_32(); /** * Deprecated equivalent to * o << "CPUID flags: " << CPUID::to_string() << "\n"; */ BOTAN_DEPRECATED("Use CPUID::to_string") static void print(std::ostream& o); /** * Return a possibly empty string containing list of known CPU * extensions. Each name will be seperated by a space, and the ordering * will be arbitrary. This list only contains values that are useful to * Botan (for example FMA instructions are not checked). * * Example outputs "sse2 ssse3 rdtsc", "neon arm_aes", "altivec" */ static std::string to_string(); /** * Return a best guess of the cache line size */ static size_t cache_line_size() { return state().cache_line_size(); } static bool is_little_endian() { #if defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) return true; #elif defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) return false; #else return state().endian_status() == Endian_Status::Little; #endif } static bool is_big_endian() { #if defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) return true; #elif defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) return false; #else return state().endian_status() == Endian_Status::Big; #endif } enum CPUID_bits : uint64_t { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) // These values have no relation to cpuid bitfields // SIMD instruction sets CPUID_SSE2_BIT = (1ULL << 0), CPUID_SSSE3_BIT = (1ULL << 1), CPUID_SSE41_BIT = (1ULL << 2), CPUID_SSE42_BIT = (1ULL << 3), CPUID_AVX2_BIT = (1ULL << 4), CPUID_AVX512F_BIT = (1ULL << 5), CPUID_AVX512DQ_BIT = (1ULL << 6), CPUID_AVX512BW_BIT = (1ULL << 7), // Ice Lake profile: AVX-512 F, DQ, BW, IFMA, VBMI, VBMI2, BITALG CPUID_AVX512_ICL_BIT = (1ULL << 11), // Crypto-specific ISAs CPUID_AESNI_BIT = (1ULL << 16), CPUID_CLMUL_BIT = (1ULL << 17), CPUID_RDRAND_BIT = (1ULL << 18), CPUID_RDSEED_BIT = (1ULL << 19), CPUID_SHA_BIT = (1ULL << 20), CPUID_AVX512_AES_BIT = (1ULL << 21), CPUID_AVX512_CLMUL_BIT = (1ULL << 22), // Misc useful instructions CPUID_RDTSC_BIT = (1ULL << 48), CPUID_ADX_BIT = (1ULL << 49), CPUID_BMI1_BIT = (1ULL << 50), CPUID_BMI2_BIT = (1ULL << 51), #endif #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) CPUID_ALTIVEC_BIT = (1ULL << 0), CPUID_POWER_CRYPTO_BIT = (1ULL << 1), CPUID_DARN_BIT = (1ULL << 2), #endif #if defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) CPUID_ARM_NEON_BIT = (1ULL << 0), CPUID_ARM_SVE_BIT = (1ULL << 1), CPUID_ARM_AES_BIT = (1ULL << 16), CPUID_ARM_PMULL_BIT = (1ULL << 17), CPUID_ARM_SHA1_BIT = (1ULL << 18), CPUID_ARM_SHA2_BIT = (1ULL << 19), CPUID_ARM_SHA3_BIT = (1ULL << 20), CPUID_ARM_SHA2_512_BIT = (1ULL << 21), CPUID_ARM_SM3_BIT = (1ULL << 22), CPUID_ARM_SM4_BIT = (1ULL << 23), #endif CPUID_INITIALIZED_BIT = (1ULL << 63) }; #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) /** * Check if the processor supports AltiVec/VMX */ static bool has_altivec() { return has_cpuid_bit(CPUID_ALTIVEC_BIT); } /** * Check if the processor supports POWER8 crypto extensions */ static bool has_power_crypto() { return has_cpuid_bit(CPUID_POWER_CRYPTO_BIT); } /** * Check if the processor supports POWER9 DARN RNG */ static bool has_darn_rng() { return has_cpuid_bit(CPUID_DARN_BIT); } #endif #if defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) /** * Check if the processor supports NEON SIMD */ static bool has_neon() { return has_cpuid_bit(CPUID_ARM_NEON_BIT); } /** * Check if the processor supports ARMv8 SVE */ static bool has_arm_sve() { return has_cpuid_bit(CPUID_ARM_SVE_BIT); } /** * Check if the processor supports ARMv8 SHA1 */ static bool has_arm_sha1() { return has_cpuid_bit(CPUID_ARM_SHA1_BIT); } /** * Check if the processor supports ARMv8 SHA2 */ static bool has_arm_sha2() { return has_cpuid_bit(CPUID_ARM_SHA2_BIT); } /** * Check if the processor supports ARMv8 AES */ static bool has_arm_aes() { return has_cpuid_bit(CPUID_ARM_AES_BIT); } /** * Check if the processor supports ARMv8 PMULL */ static bool has_arm_pmull() { return has_cpuid_bit(CPUID_ARM_PMULL_BIT); } /** * Check if the processor supports ARMv8 SHA-512 */ static bool has_arm_sha2_512() { return has_cpuid_bit(CPUID_ARM_SHA2_512_BIT); } /** * Check if the processor supports ARMv8 SHA-3 */ static bool has_arm_sha3() { return has_cpuid_bit(CPUID_ARM_SHA3_BIT); } /** * Check if the processor supports ARMv8 SM3 */ static bool has_arm_sm3() { return has_cpuid_bit(CPUID_ARM_SM3_BIT); } /** * Check if the processor supports ARMv8 SM4 */ static bool has_arm_sm4() { return has_cpuid_bit(CPUID_ARM_SM4_BIT); } #endif #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) /** * Check if the processor supports RDTSC */ static bool has_rdtsc() { return has_cpuid_bit(CPUID_RDTSC_BIT); } /** * Check if the processor supports SSE2 */ static bool has_sse2() { return has_cpuid_bit(CPUID_SSE2_BIT); } /** * Check if the processor supports SSSE3 */ static bool has_ssse3() { return has_cpuid_bit(CPUID_SSSE3_BIT); } /** * Check if the processor supports SSE4.1 */ static bool has_sse41() { return has_cpuid_bit(CPUID_SSE41_BIT); } /** * Check if the processor supports SSE4.2 */ static bool has_sse42() { return has_cpuid_bit(CPUID_SSE42_BIT); } /** * Check if the processor supports AVX2 */ static bool has_avx2() { return has_cpuid_bit(CPUID_AVX2_BIT); } /** * Check if the processor supports AVX-512F */ static bool has_avx512f() { return has_cpuid_bit(CPUID_AVX512F_BIT); } /** * Check if the processor supports AVX-512DQ */ static bool has_avx512dq() { return has_cpuid_bit(CPUID_AVX512DQ_BIT); } /** * Check if the processor supports AVX-512BW */ static bool has_avx512bw() { return has_cpuid_bit(CPUID_AVX512BW_BIT); } /** * Check if the processor supports AVX-512 Ice Lake profile */ static bool has_avx512_icelake() { return has_cpuid_bit(CPUID_AVX512_ICL_BIT); } /** * Check if the processor supports AVX-512 AES (VAES) */ static bool has_avx512_aes() { return has_cpuid_bit(CPUID_AVX512_AES_BIT); } /** * Check if the processor supports AVX-512 VPCLMULQDQ */ static bool has_avx512_clmul() { return has_cpuid_bit(CPUID_AVX512_CLMUL_BIT); } /** * Check if the processor supports BMI1 */ static bool has_bmi1() { return has_cpuid_bit(CPUID_BMI1_BIT); } /** * Check if the processor supports BMI2 */ static bool has_bmi2() { return has_cpuid_bit(CPUID_BMI2_BIT); } /** * Check if the processor supports AES-NI */ static bool has_aes_ni() { return has_cpuid_bit(CPUID_AESNI_BIT); } /** * Check if the processor supports CLMUL */ static bool has_clmul() { return has_cpuid_bit(CPUID_CLMUL_BIT); } /** * Check if the processor supports Intel SHA extension */ static bool has_intel_sha() { return has_cpuid_bit(CPUID_SHA_BIT); } /** * Check if the processor supports ADX extension */ static bool has_adx() { return has_cpuid_bit(CPUID_ADX_BIT); } /** * Check if the processor supports RDRAND */ static bool has_rdrand() { return has_cpuid_bit(CPUID_RDRAND_BIT); } /** * Check if the processor supports RDSEED */ static bool has_rdseed() { return has_cpuid_bit(CPUID_RDSEED_BIT); } #endif /** * Check if the processor supports byte-level vector permutes * (SSSE3, NEON, Altivec) */ static bool has_vperm() { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) return has_ssse3(); #elif defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) return has_neon(); #elif defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) return has_altivec(); #else return false; #endif } /** * Check if the processor supports hardware AES instructions */ static bool has_hw_aes() { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) return has_aes_ni(); #elif defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) return has_arm_aes(); #elif defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) return has_power_crypto(); #else return false; #endif } /** * Check if the processor supports carryless multiply * (CLMUL, PMULL) */ static bool has_carryless_multiply() { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) return has_clmul(); #elif defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) return has_arm_pmull(); #elif defined(BOTAN_TARGET_ARCH_IS_PPC64) return has_power_crypto(); #else return false; #endif } /* * Clear a CPUID bit * Call CPUID::initialize to reset * * This is only exposed for testing, don't use unless you know * what you are doing. */ static void clear_cpuid_bit(CPUID_bits bit) { state().clear_cpuid_bit(static_cast(bit)); } /* * Don't call this function, use CPUID::has_xxx above * It is only exposed for the tests. */ static bool has_cpuid_bit(CPUID_bits elem) { const uint64_t elem64 = static_cast(elem); return state().has_bit(elem64); } static std::vector bit_from_string(const std::string& tok); private: enum class Endian_Status : uint32_t { Unknown = 0x00000000, Big = 0x01234567, Little = 0x67452301, }; struct CPUID_Data { public: CPUID_Data(); CPUID_Data(const CPUID_Data& other) = default; CPUID_Data& operator=(const CPUID_Data& other) = default; void clear_cpuid_bit(uint64_t bit) { m_processor_features &= ~bit; } bool has_bit(uint64_t bit) const { return (m_processor_features & bit) == bit; } uint64_t processor_features() const { return m_processor_features; } Endian_Status endian_status() const { return m_endian_status; } size_t cache_line_size() const { return m_cache_line_size; } private: static Endian_Status runtime_check_endian(); #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) || \ defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) || \ defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) static uint64_t detect_cpu_features(size_t* cache_line_size); #endif uint64_t m_processor_features; size_t m_cache_line_size; Endian_Status m_endian_status; }; static CPUID_Data& state() { static CPUID::CPUID_Data g_cpuid; return g_cpuid; } }; } namespace Botan { /** * Base class for all stream ciphers */ class BOTAN_PUBLIC_API(2,0) StreamCipher : public SymmetricAlgorithm { public: virtual ~StreamCipher() = default; /** * Create an instance based on a name * If provider is empty then best available is chosen. * @param algo_spec algorithm name * @param provider provider implementation to use * @return a null pointer if the algo/provider combination cannot be found */ static std::unique_ptr create(const std::string& algo_spec, const std::string& provider = ""); /** * Create an instance based on a name * If provider is empty then best available is chosen. * @param algo_spec algorithm name * @param provider provider implementation to use * Throws a Lookup_Error if the algo/provider combination cannot be found */ static std::unique_ptr create_or_throw(const std::string& algo_spec, const std::string& provider = ""); /** * @return list of available providers for this algorithm, empty if not available */ static std::vector providers(const std::string& algo_spec); /** * Encrypt or decrypt a message * @param in the plaintext * @param out the byte array to hold the output, i.e. the ciphertext * @param len the length of both in and out in bytes */ virtual void cipher(const uint8_t in[], uint8_t out[], size_t len) = 0; /** * Write keystream bytes to a buffer * @param out the byte array to hold the keystream * @param len the length of out in bytes */ virtual void write_keystream(uint8_t out[], size_t len) { clear_mem(out, len); cipher1(out, len); } /** * Encrypt or decrypt a message * The message is encrypted/decrypted in place. * @param buf the plaintext / ciphertext * @param len the length of buf in bytes */ void cipher1(uint8_t buf[], size_t len) { cipher(buf, buf, len); } /** * Encrypt a message * The message is encrypted/decrypted in place. * @param inout the plaintext / ciphertext */ template void encipher(std::vector& inout) { cipher(inout.data(), inout.data(), inout.size()); } /** * Encrypt a message * The message is encrypted in place. * @param inout the plaintext / ciphertext */ template void encrypt(std::vector& inout) { cipher(inout.data(), inout.data(), inout.size()); } /** * Decrypt a message in place * The message is decrypted in place. * @param inout the plaintext / ciphertext */ template void decrypt(std::vector& inout) { cipher(inout.data(), inout.data(), inout.size()); } /** * Resync the cipher using the IV * @param iv the initialization vector * @param iv_len the length of the IV in bytes */ virtual void set_iv(const uint8_t iv[], size_t iv_len) = 0; /** * Return the default (preferred) nonce length * If this function returns 0, then this cipher does not support nonces */ virtual size_t default_iv_length() const { return 0; } /** * @param iv_len the length of the IV in bytes * @return if the length is valid for this algorithm */ virtual bool valid_iv_length(size_t iv_len) const { return (iv_len == 0); } /** * @return a new object representing the same algorithm as *this */ virtual StreamCipher* clone() const = 0; /** * Set the offset and the state used later to generate the keystream * @param offset the offset where we begin to generate the keystream */ virtual void seek(uint64_t offset) = 0; /** * @return provider information about this implementation. Default is "base", * might also return "sse2", "avx2", "openssl", or some other arbitrary string. */ virtual std::string provider() const { return "base"; } }; } BOTAN_FUTURE_INTERNAL_HEADER(ctr.h) namespace Botan { /** * CTR-BE (Counter mode, big-endian) */ class BOTAN_PUBLIC_API(2,0) CTR_BE final : public StreamCipher { public: void cipher(const uint8_t in[], uint8_t out[], size_t length) override; void set_iv(const uint8_t iv[], size_t iv_len) override; size_t default_iv_length() const override; bool valid_iv_length(size_t iv_len) const override; Key_Length_Specification key_spec() const override; std::string name() const override; CTR_BE* clone() const override; void clear() override; /** * @param cipher the block cipher to use */ explicit CTR_BE(BlockCipher* cipher); CTR_BE(BlockCipher* cipher, size_t ctr_size); void seek(uint64_t offset) override; private: void key_schedule(const uint8_t key[], size_t key_len) override; void add_counter(const uint64_t counter); std::unique_ptr m_cipher; const size_t m_block_size; const size_t m_ctr_size; const size_t m_ctr_blocks; secure_vector m_counter, m_pad; std::vector m_iv; size_t m_pad_pos; }; } namespace Botan { /** * This class represents an abstract data source object. */ class BOTAN_PUBLIC_API(2,0) DataSource { public: /** * Read from the source. Moves the internal offset so that every * call to read will return a new portion of the source. * * @param out the byte array to write the result to * @param length the length of the byte array out * @return length in bytes that was actually read and put * into out */ virtual size_t read(uint8_t out[], size_t length) BOTAN_WARN_UNUSED_RESULT = 0; virtual bool check_available(size_t n) = 0; /** * Read from the source but do not modify the internal * offset. Consecutive calls to peek() will return portions of * the source starting at the same position. * * @param out the byte array to write the output to * @param length the length of the byte array out * @param peek_offset the offset into the stream to read at * @return length in bytes that was actually read and put * into out */ virtual size_t peek(uint8_t out[], size_t length, size_t peek_offset) const BOTAN_WARN_UNUSED_RESULT = 0; /** * Test whether the source still has data that can be read. * @return true if there is no more data to read, false otherwise */ virtual bool end_of_data() const = 0; /** * return the id of this data source * @return std::string representing the id of this data source */ virtual std::string id() const { return ""; } /** * Read one byte. * @param out the byte to read to * @return length in bytes that was actually read and put * into out */ size_t read_byte(uint8_t& out); /** * Peek at one byte. * @param out an output byte * @return length in bytes that was actually read and put * into out */ size_t peek_byte(uint8_t& out) const; /** * Discard the next N bytes of the data * @param N the number of bytes to discard * @return number of bytes actually discarded */ size_t discard_next(size_t N); /** * @return number of bytes read so far. */ virtual size_t get_bytes_read() const = 0; DataSource() = default; virtual ~DataSource() = default; DataSource& operator=(const DataSource&) = delete; DataSource(const DataSource&) = delete; }; /** * This class represents a Memory-Based DataSource */ class BOTAN_PUBLIC_API(2,0) DataSource_Memory final : public DataSource { public: size_t read(uint8_t[], size_t) override; size_t peek(uint8_t[], size_t, size_t) const override; bool check_available(size_t n) override; bool end_of_data() const override; /** * Construct a memory source that reads from a string * @param in the string to read from */ explicit DataSource_Memory(const std::string& in); /** * Construct a memory source that reads from a byte array * @param in the byte array to read from * @param length the length of the byte array */ DataSource_Memory(const uint8_t in[], size_t length) : m_source(in, in + length), m_offset(0) {} /** * Construct a memory source that reads from a secure_vector * @param in the MemoryRegion to read from */ explicit DataSource_Memory(const secure_vector& in) : m_source(in), m_offset(0) {} /** * Construct a memory source that reads from a std::vector * @param in the MemoryRegion to read from */ explicit DataSource_Memory(const std::vector& in) : m_source(in.begin(), in.end()), m_offset(0) {} size_t get_bytes_read() const override { return m_offset; } private: secure_vector m_source; size_t m_offset; }; /** * This class represents a Stream-Based DataSource. */ class BOTAN_PUBLIC_API(2,0) DataSource_Stream final : public DataSource { public: size_t read(uint8_t[], size_t) override; size_t peek(uint8_t[], size_t, size_t) const override; bool check_available(size_t n) override; bool end_of_data() const override; std::string id() const override; DataSource_Stream(std::istream&, const std::string& id = ""); #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) /** * Construct a Stream-Based DataSource from filesystem path * @param file the path to the file * @param use_binary whether to treat the file as binary or not */ DataSource_Stream(const std::string& file, bool use_binary = false); #endif DataSource_Stream(const DataSource_Stream&) = delete; DataSource_Stream& operator=(const DataSource_Stream&) = delete; ~DataSource_Stream(); size_t get_bytes_read() const override { return m_total_read; } private: const std::string m_identifier; std::unique_ptr m_source_memory; std::istream& m_source; size_t m_total_read; }; } namespace Botan { class BOTAN_PUBLIC_API(2,0) SQL_Database { public: class BOTAN_PUBLIC_API(2,0) SQL_DB_Error final : public Exception { public: explicit SQL_DB_Error(const std::string& what) : Exception("SQL database", what), m_rc(0) {} SQL_DB_Error(const std::string& what, int rc) : Exception("SQL database", what), m_rc(rc) {} ErrorType error_type() const noexcept override { return Botan::ErrorType::DatabaseError; } int error_code() const noexcept override { return m_rc; } private: int m_rc; }; class BOTAN_PUBLIC_API(2,0) Statement { public: /* Bind statement parameters */ virtual void bind(int column, const std::string& str) = 0; virtual void bind(int column, size_t i) = 0; virtual void bind(int column, std::chrono::system_clock::time_point time) = 0; virtual void bind(int column, const std::vector& blob) = 0; virtual void bind(int column, const uint8_t* data, size_t len) = 0; /* Get output */ virtual std::pair get_blob(int column) = 0; virtual std::string get_str(int column) = 0; virtual size_t get_size_t(int column) = 0; /* Run to completion */ virtual size_t spin() = 0; /* Maybe update */ virtual bool step() = 0; virtual ~Statement() = default; }; /* * Create a new statement for execution. * Use ?1, ?2, ?3, etc for parameters to set later with bind */ virtual std::shared_ptr new_statement(const std::string& base_sql) const = 0; virtual size_t row_count(const std::string& table_name) = 0; virtual void create_table(const std::string& table_schema) = 0; virtual ~SQL_Database() = default; }; } #if defined(BOTAN_TARGET_OS_HAS_THREADS) namespace Botan { template using lock_guard_type = std::lock_guard; typedef std::mutex mutex_type; typedef std::recursive_mutex recursive_mutex_type; } #else // No threads namespace Botan { template class lock_guard final { public: explicit lock_guard(Mutex& m) : m_mutex(m) { m_mutex.lock(); } ~lock_guard() { m_mutex.unlock(); } lock_guard(const lock_guard& other) = delete; lock_guard& operator=(const lock_guard& other) = delete; private: Mutex& m_mutex; }; class noop_mutex final { public: void lock() {} void unlock() {} }; typedef noop_mutex mutex_type; typedef noop_mutex recursive_mutex_type; template using lock_guard_type = lock_guard; } #endif namespace Botan { class Entropy_Sources; /** * An interface to a cryptographic random number generator */ class BOTAN_PUBLIC_API(2,0) RandomNumberGenerator { public: virtual ~RandomNumberGenerator() = default; RandomNumberGenerator() = default; /* * Never copy a RNG, create a new one */ RandomNumberGenerator(const RandomNumberGenerator& rng) = delete; RandomNumberGenerator& operator=(const RandomNumberGenerator& rng) = delete; /** * Randomize a byte array. * @param output the byte array to hold the random output. * @param length the length of the byte array output in bytes. */ virtual void randomize(uint8_t output[], size_t length) = 0; /** * Returns false if it is known that this RNG object is not able to accept * externally provided inputs (via add_entropy, randomize_with_input, etc). * In this case, any such provided inputs are ignored. * * If this function returns true, then inputs may or may not be accepted. */ virtual bool accepts_input() const = 0; /** * Incorporate some additional data into the RNG state. For * example adding nonces or timestamps from a peer's protocol * message can help hedge against VM state rollback attacks. * A few RNG types do not accept any externally provided input, * in which case this function is a no-op. * * @param input a byte array containg the entropy to be added * @param length the length of the byte array in */ virtual void add_entropy(const uint8_t input[], size_t length) = 0; /** * Incorporate some additional data into the RNG state. */ template void add_entropy_T(const T& t) { static_assert(std::is_standard_layout::value && std::is_trivial::value, "add_entropy_T data must be POD"); this->add_entropy(reinterpret_cast(&t), sizeof(T)); } /** * Incorporate entropy into the RNG state then produce output. * Some RNG types implement this using a single operation, default * calls add_entropy + randomize in sequence. * * Use this to further bind the outputs to your current * process/protocol state. For instance if generating a new key * for use in a session, include a session ID or other such * value. See NIST SP 800-90 A, B, C series for more ideas. * * @param output buffer to hold the random output * @param output_len size of the output buffer in bytes * @param input entropy buffer to incorporate * @param input_len size of the input buffer in bytes */ virtual void randomize_with_input(uint8_t output[], size_t output_len, const uint8_t input[], size_t input_len); /** * This calls `randomize_with_input` using some timestamps as extra input. * * For a stateful RNG using non-random but potentially unique data the * extra input can help protect against problems with fork, VM state * rollback, or other cases where somehow an RNG state is duplicated. If * both of the duplicated RNG states later incorporate a timestamp (and the * timestamps don't themselves repeat), their outputs will diverge. */ virtual void randomize_with_ts_input(uint8_t output[], size_t output_len); /** * @return the name of this RNG type */ virtual std::string name() const = 0; /** * Clear all internally held values of this RNG * @post is_seeded() == false */ virtual void clear() = 0; /** * Check whether this RNG is seeded. * @return true if this RNG was already seeded, false otherwise. */ virtual bool is_seeded() const = 0; /** * Poll provided sources for up to poll_bits bits of entropy * or until the timeout expires. Returns estimate of the number * of bits collected. */ virtual size_t reseed(Entropy_Sources& srcs, size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS, std::chrono::milliseconds poll_timeout = BOTAN_RNG_RESEED_DEFAULT_TIMEOUT); /** * Reseed by reading specified bits from the RNG */ virtual void reseed_from_rng(RandomNumberGenerator& rng, size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS); // Some utility functions built on the interface above: /** * Return a random vector * @param bytes number of bytes in the result * @return randomized vector of length bytes */ secure_vector random_vec(size_t bytes) { secure_vector output; random_vec(output, bytes); return output; } template void random_vec(std::vector& v, size_t bytes) { v.resize(bytes); this->randomize(v.data(), v.size()); } /** * Return a random byte * @return random byte */ uint8_t next_byte() { uint8_t b; this->randomize(&b, 1); return b; } /** * @return a random byte that is greater than zero */ uint8_t next_nonzero_byte() { uint8_t b = this->next_byte(); while(b == 0) b = this->next_byte(); return b; } /** * Create a seeded and active RNG object for general application use * Added in 1.8.0 * Use AutoSeeded_RNG instead */ BOTAN_DEPRECATED("Use AutoSeeded_RNG") static RandomNumberGenerator* make_rng(); }; /** * Convenience typedef */ typedef RandomNumberGenerator RNG; /** * Hardware_RNG exists to tag hardware RNG types (PKCS11_RNG, TPM_RNG, Processor_RNG) */ class BOTAN_PUBLIC_API(2,0) Hardware_RNG : public RandomNumberGenerator { public: virtual void clear() final override { /* no way to clear state of hardware RNG */ } }; /** * Null/stub RNG - fails if you try to use it for anything * This is not generally useful except for in certain tests */ class BOTAN_PUBLIC_API(2,0) Null_RNG final : public RandomNumberGenerator { public: bool is_seeded() const override { return false; } bool accepts_input() const override { return false; } void clear() override {} void randomize(uint8_t[], size_t) override { throw PRNG_Unseeded("Null_RNG called"); } void add_entropy(const uint8_t[], size_t) override {} std::string name() const override { return "Null_RNG"; } }; #if defined(BOTAN_TARGET_OS_HAS_THREADS) /** * Wraps access to a RNG in a mutex * Note that most of the time it's much better to use a RNG per thread * otherwise the RNG will act as an unnecessary contention point * * Since 2.16.0 all Stateful_RNG instances have an internal lock, so * this class is no longer needed. It will be removed in a future major * release. */ class BOTAN_PUBLIC_API(2,0) Serialized_RNG final : public RandomNumberGenerator { public: void randomize(uint8_t out[], size_t len) override { lock_guard_type lock(m_mutex); m_rng->randomize(out, len); } bool accepts_input() const override { lock_guard_type lock(m_mutex); return m_rng->accepts_input(); } bool is_seeded() const override { lock_guard_type lock(m_mutex); return m_rng->is_seeded(); } void clear() override { lock_guard_type lock(m_mutex); m_rng->clear(); } std::string name() const override { lock_guard_type lock(m_mutex); return m_rng->name(); } size_t reseed(Entropy_Sources& src, size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS, std::chrono::milliseconds poll_timeout = BOTAN_RNG_RESEED_DEFAULT_TIMEOUT) override { lock_guard_type lock(m_mutex); return m_rng->reseed(src, poll_bits, poll_timeout); } void add_entropy(const uint8_t in[], size_t len) override { lock_guard_type lock(m_mutex); m_rng->add_entropy(in, len); } BOTAN_DEPRECATED("Use Serialized_RNG(new AutoSeeded_RNG) instead") Serialized_RNG(); /* * Since 2.16.0 this is no longer needed for any RNG type. This * class will be removed in a future major release. */ explicit Serialized_RNG(RandomNumberGenerator* rng) : m_rng(rng) {} private: mutable mutex_type m_mutex; std::unique_ptr m_rng; }; #endif } namespace Botan { class RandomNumberGenerator; /** * Abstract interface to a source of entropy */ class BOTAN_PUBLIC_API(2,0) Entropy_Source { public: /** * Return a new entropy source of a particular type, or null * Each entropy source may require substantial resources (eg, a file handle * or socket instance), so try to share them among multiple RNGs, or just * use the preconfigured global list accessed by Entropy_Sources::global_sources() */ static std::unique_ptr create(const std::string& type); /** * @return name identifying this entropy source */ virtual std::string name() const = 0; /** * Perform an entropy gathering poll * @param rng will be provided with entropy via calls to add_entropy * @return conservative estimate of actual entropy added to rng during poll */ virtual size_t poll(RandomNumberGenerator& rng) = 0; Entropy_Source() = default; Entropy_Source(const Entropy_Source& other) = delete; Entropy_Source(Entropy_Source&& other) = delete; Entropy_Source& operator=(const Entropy_Source& other) = delete; virtual ~Entropy_Source() = default; }; class BOTAN_PUBLIC_API(2,0) Entropy_Sources final { public: static Entropy_Sources& global_sources(); void add_source(std::unique_ptr src); std::vector enabled_sources() const; size_t poll(RandomNumberGenerator& rng, size_t bits, std::chrono::milliseconds timeout); /** * Poll just a single named source. Ordinally only used for testing */ size_t poll_just(RandomNumberGenerator& rng, const std::string& src); Entropy_Sources() = default; explicit Entropy_Sources(const std::vector& sources); Entropy_Sources(const Entropy_Sources& other) = delete; Entropy_Sources(Entropy_Sources&& other) = delete; Entropy_Sources& operator=(const Entropy_Sources& other) = delete; private: std::vector> m_srcs; }; } BOTAN_FUTURE_INTERNAL_HEADER(gcm.h) namespace Botan { class BlockCipher; class StreamCipher; class GHASH; /** * GCM Mode */ class BOTAN_PUBLIC_API(2,0) GCM_Mode : public AEAD_Mode { public: void set_associated_data(const uint8_t ad[], size_t ad_len) override; std::string name() const override; size_t update_granularity() const override; Key_Length_Specification key_spec() const override; bool valid_nonce_length(size_t len) const override; size_t tag_size() const override { return m_tag_size; } void clear() override; void reset() override; std::string provider() const override; protected: GCM_Mode(BlockCipher* cipher, size_t tag_size); ~GCM_Mode(); static const size_t GCM_BS = 16; const size_t m_tag_size; const std::string m_cipher_name; std::unique_ptr m_ctr; std::unique_ptr m_ghash; private: void start_msg(const uint8_t nonce[], size_t nonce_len) override; void key_schedule(const uint8_t key[], size_t length) override; secure_vector m_y0; }; /** * GCM Encryption */ class BOTAN_PUBLIC_API(2,0) GCM_Encryption final : public GCM_Mode { public: /** * @param cipher the 128 bit block cipher to use * @param tag_size is how big the auth tag will be */ GCM_Encryption(BlockCipher* cipher, size_t tag_size = 16) : GCM_Mode(cipher, tag_size) {} size_t output_length(size_t input_length) const override { return input_length + tag_size(); } size_t minimum_final_size() const override { return 0; } size_t process(uint8_t buf[], size_t size) override; void finish(secure_vector& final_block, size_t offset = 0) override; }; /** * GCM Decryption */ class BOTAN_PUBLIC_API(2,0) GCM_Decryption final : public GCM_Mode { public: /** * @param cipher the 128 bit block cipher to use * @param tag_size is how big the auth tag will be */ GCM_Decryption(BlockCipher* cipher, size_t tag_size = 16) : GCM_Mode(cipher, tag_size) {} size_t output_length(size_t input_length) const override { BOTAN_ASSERT(input_length >= tag_size(), "Sufficient input"); return input_length - tag_size(); } size_t minimum_final_size() const override { return tag_size(); } size_t process(uint8_t buf[], size_t size) override; void finish(secure_vector& final_block, size_t offset = 0) override; }; } BOTAN_FUTURE_INTERNAL_HEADER(ghash.h) namespace Botan { /** * GCM's GHASH * This is not intended for general use, but is exposed to allow * shared code between GCM and GMAC */ class BOTAN_PUBLIC_API(2,0) GHASH final : public SymmetricAlgorithm { public: void set_associated_data(const uint8_t ad[], size_t ad_len); secure_vector BOTAN_DEPRECATED("Use other impl") nonce_hash(const uint8_t nonce[], size_t nonce_len) { secure_vector y0(GCM_BS); nonce_hash(y0, nonce, nonce_len); return y0; } void nonce_hash(secure_vector& y0, const uint8_t nonce[], size_t len); void start(const uint8_t nonce[], size_t len); /* * Assumes input len is multiple of 16 */ void update(const uint8_t in[], size_t len); /* * Incremental update of associated data */ void update_associated_data(const uint8_t ad[], size_t len); secure_vector BOTAN_DEPRECATED("Use version taking output params") final() { secure_vector mac(GCM_BS); final(mac.data(), mac.size()); return mac; } void final(uint8_t out[], size_t out_len); Key_Length_Specification key_spec() const override { return Key_Length_Specification(16); } void clear() override; void reset(); std::string name() const override { return "GHASH"; } std::string provider() const; void ghash_update(secure_vector& x, const uint8_t input[], size_t input_len); void add_final_block(secure_vector& x, size_t ad_len, size_t pt_len); private: #if defined(BOTAN_HAS_GHASH_CLMUL_CPU) static void ghash_precompute_cpu(const uint8_t H[16], uint64_t H_pow[4*2]); static void ghash_multiply_cpu(uint8_t x[16], const uint64_t H_pow[4*2], const uint8_t input[], size_t blocks); #endif #if defined(BOTAN_HAS_GHASH_CLMUL_VPERM) static void ghash_multiply_vperm(uint8_t x[16], const uint64_t HM[256], const uint8_t input[], size_t blocks); #endif void key_schedule(const uint8_t key[], size_t key_len) override; void ghash_multiply(secure_vector& x, const uint8_t input[], size_t blocks); static const size_t GCM_BS = 16; secure_vector m_H; secure_vector m_H_ad; secure_vector m_ghash; secure_vector m_nonce; secure_vector m_HM; secure_vector m_H_pow; size_t m_ad_len = 0; size_t m_text_len = 0; }; } namespace Botan { /** * Perform hex encoding * @param output an array of at least input_length*2 bytes * @param input is some binary data * @param input_length length of input in bytes * @param uppercase should output be upper or lower case? */ void BOTAN_PUBLIC_API(2,0) hex_encode(char output[], const uint8_t input[], size_t input_length, bool uppercase = true); /** * Perform hex encoding * @param input some input * @param input_length length of input in bytes * @param uppercase should output be upper or lower case? * @return hexadecimal representation of input */ std::string BOTAN_PUBLIC_API(2,0) hex_encode(const uint8_t input[], size_t input_length, bool uppercase = true); /** * Perform hex encoding * @param input some input * @param uppercase should output be upper or lower case? * @return hexadecimal representation of input */ template std::string hex_encode(const std::vector& input, bool uppercase = true) { return hex_encode(input.data(), input.size(), uppercase); } /** * Perform hex decoding * @param output an array of at least input_length/2 bytes * @param input some hex input * @param input_length length of input in bytes * @param input_consumed is an output parameter which says how many * bytes of input were actually consumed. If less than * input_length, then the range input[consumed:length] * should be passed in later along with more input. * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return number of bytes written to output */ size_t BOTAN_PUBLIC_API(2,0) hex_decode(uint8_t output[], const char input[], size_t input_length, size_t& input_consumed, bool ignore_ws = true); /** * Perform hex decoding * @param output an array of at least input_length/2 bytes * @param input some hex input * @param input_length length of input in bytes * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return number of bytes written to output */ size_t BOTAN_PUBLIC_API(2,0) hex_decode(uint8_t output[], const char input[], size_t input_length, bool ignore_ws = true); /** * Perform hex decoding * @param output an array of at least input_length/2 bytes * @param input some hex input * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return number of bytes written to output */ size_t BOTAN_PUBLIC_API(2,0) hex_decode(uint8_t output[], const std::string& input, bool ignore_ws = true); /** * Perform hex decoding * @param input some hex input * @param input_length the length of input in bytes * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return decoded hex output */ std::vector BOTAN_PUBLIC_API(2,0) hex_decode(const char input[], size_t input_length, bool ignore_ws = true); /** * Perform hex decoding * @param input some hex input * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return decoded hex output */ std::vector BOTAN_PUBLIC_API(2,0) hex_decode(const std::string& input, bool ignore_ws = true); /** * Perform hex decoding * @param input some hex input * @param input_length the length of input in bytes * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return decoded hex output */ secure_vector BOTAN_PUBLIC_API(2,0) hex_decode_locked(const char input[], size_t input_length, bool ignore_ws = true); /** * Perform hex decoding * @param input some hex input * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return decoded hex output */ secure_vector BOTAN_PUBLIC_API(2,0) hex_decode_locked(const std::string& input, bool ignore_ws = true); } BOTAN_FUTURE_INTERNAL_HEADER(loadstor.h) #if defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) #define BOTAN_ENDIAN_N2L(x) reverse_bytes(x) #define BOTAN_ENDIAN_L2N(x) reverse_bytes(x) #define BOTAN_ENDIAN_N2B(x) (x) #define BOTAN_ENDIAN_B2N(x) (x) #elif defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) #define BOTAN_ENDIAN_N2L(x) (x) #define BOTAN_ENDIAN_L2N(x) (x) #define BOTAN_ENDIAN_N2B(x) reverse_bytes(x) #define BOTAN_ENDIAN_B2N(x) reverse_bytes(x) #endif namespace Botan { /** * Byte extraction * @param byte_num which byte to extract, 0 == highest byte * @param input the value to extract from * @return byte byte_num of input */ template inline constexpr uint8_t get_byte(size_t byte_num, T input) { return static_cast( input >> (((~byte_num)&(sizeof(T)-1)) << 3) ); } /** * Make a uint16_t from two bytes * @param i0 the first byte * @param i1 the second byte * @return i0 || i1 */ inline constexpr uint16_t make_uint16(uint8_t i0, uint8_t i1) { return static_cast((static_cast(i0) << 8) | i1); } /** * Make a uint32_t from four bytes * @param i0 the first byte * @param i1 the second byte * @param i2 the third byte * @param i3 the fourth byte * @return i0 || i1 || i2 || i3 */ inline constexpr uint32_t make_uint32(uint8_t i0, uint8_t i1, uint8_t i2, uint8_t i3) { return ((static_cast(i0) << 24) | (static_cast(i1) << 16) | (static_cast(i2) << 8) | (static_cast(i3))); } /** * Make a uint64_t from eight bytes * @param i0 the first byte * @param i1 the second byte * @param i2 the third byte * @param i3 the fourth byte * @param i4 the fifth byte * @param i5 the sixth byte * @param i6 the seventh byte * @param i7 the eighth byte * @return i0 || i1 || i2 || i3 || i4 || i5 || i6 || i7 */ inline constexpr uint64_t make_uint64(uint8_t i0, uint8_t i1, uint8_t i2, uint8_t i3, uint8_t i4, uint8_t i5, uint8_t i6, uint8_t i7) { return ((static_cast(i0) << 56) | (static_cast(i1) << 48) | (static_cast(i2) << 40) | (static_cast(i3) << 32) | (static_cast(i4) << 24) | (static_cast(i5) << 16) | (static_cast(i6) << 8) | (static_cast(i7))); } /** * Load a big-endian word * @param in a pointer to some bytes * @param off an offset into the array * @return off'th T of in, as a big-endian value */ template inline T load_be(const uint8_t in[], size_t off) { in += off * sizeof(T); T out = 0; for(size_t i = 0; i != sizeof(T); ++i) out = static_cast((out << 8) | in[i]); return out; } /** * Load a little-endian word * @param in a pointer to some bytes * @param off an offset into the array * @return off'th T of in, as a litte-endian value */ template inline T load_le(const uint8_t in[], size_t off) { in += off * sizeof(T); T out = 0; for(size_t i = 0; i != sizeof(T); ++i) out = (out << 8) | in[sizeof(T)-1-i]; return out; } /** * Load a big-endian uint16_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint16_t of in, as a big-endian value */ template<> inline uint16_t load_be(const uint8_t in[], size_t off) { in += off * sizeof(uint16_t); #if defined(BOTAN_ENDIAN_N2B) uint16_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2B(x); #else return make_uint16(in[0], in[1]); #endif } /** * Load a little-endian uint16_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint16_t of in, as a little-endian value */ template<> inline uint16_t load_le(const uint8_t in[], size_t off) { in += off * sizeof(uint16_t); #if defined(BOTAN_ENDIAN_N2L) uint16_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2L(x); #else return make_uint16(in[1], in[0]); #endif } /** * Load a big-endian uint32_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint32_t of in, as a big-endian value */ template<> inline uint32_t load_be(const uint8_t in[], size_t off) { in += off * sizeof(uint32_t); #if defined(BOTAN_ENDIAN_N2B) uint32_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2B(x); #else return make_uint32(in[0], in[1], in[2], in[3]); #endif } /** * Load a little-endian uint32_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint32_t of in, as a little-endian value */ template<> inline uint32_t load_le(const uint8_t in[], size_t off) { in += off * sizeof(uint32_t); #if defined(BOTAN_ENDIAN_N2L) uint32_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2L(x); #else return make_uint32(in[3], in[2], in[1], in[0]); #endif } /** * Load a big-endian uint64_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint64_t of in, as a big-endian value */ template<> inline uint64_t load_be(const uint8_t in[], size_t off) { in += off * sizeof(uint64_t); #if defined(BOTAN_ENDIAN_N2B) uint64_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2B(x); #else return make_uint64(in[0], in[1], in[2], in[3], in[4], in[5], in[6], in[7]); #endif } /** * Load a little-endian uint64_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint64_t of in, as a little-endian value */ template<> inline uint64_t load_le(const uint8_t in[], size_t off) { in += off * sizeof(uint64_t); #if defined(BOTAN_ENDIAN_N2L) uint64_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2L(x); #else return make_uint64(in[7], in[6], in[5], in[4], in[3], in[2], in[1], in[0]); #endif } /** * Load two little-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written */ template inline void load_le(const uint8_t in[], T& x0, T& x1) { x0 = load_le(in, 0); x1 = load_le(in, 1); } /** * Load four little-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written * @param x2 where the third word will be written * @param x3 where the fourth word will be written */ template inline void load_le(const uint8_t in[], T& x0, T& x1, T& x2, T& x3) { x0 = load_le(in, 0); x1 = load_le(in, 1); x2 = load_le(in, 2); x3 = load_le(in, 3); } /** * Load eight little-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written * @param x2 where the third word will be written * @param x3 where the fourth word will be written * @param x4 where the fifth word will be written * @param x5 where the sixth word will be written * @param x6 where the seventh word will be written * @param x7 where the eighth word will be written */ template inline void load_le(const uint8_t in[], T& x0, T& x1, T& x2, T& x3, T& x4, T& x5, T& x6, T& x7) { x0 = load_le(in, 0); x1 = load_le(in, 1); x2 = load_le(in, 2); x3 = load_le(in, 3); x4 = load_le(in, 4); x5 = load_le(in, 5); x6 = load_le(in, 6); x7 = load_le(in, 7); } /** * Load a variable number of little-endian words * @param out the output array of words * @param in the input array of bytes * @param count how many words are in in */ template inline void load_le(T out[], const uint8_t in[], size_t count) { if(count > 0) { #if defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) typecast_copy(out, in, count); #elif defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) typecast_copy(out, in, count); const size_t blocks = count - (count % 4); const size_t left = count - blocks; for(size_t i = 0; i != blocks; i += 4) bswap_4(out + i); for(size_t i = 0; i != left; ++i) out[blocks+i] = reverse_bytes(out[blocks+i]); #else for(size_t i = 0; i != count; ++i) out[i] = load_le(in, i); #endif } } /** * Load two big-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written */ template inline void load_be(const uint8_t in[], T& x0, T& x1) { x0 = load_be(in, 0); x1 = load_be(in, 1); } /** * Load four big-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written * @param x2 where the third word will be written * @param x3 where the fourth word will be written */ template inline void load_be(const uint8_t in[], T& x0, T& x1, T& x2, T& x3) { x0 = load_be(in, 0); x1 = load_be(in, 1); x2 = load_be(in, 2); x3 = load_be(in, 3); } /** * Load eight big-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written * @param x2 where the third word will be written * @param x3 where the fourth word will be written * @param x4 where the fifth word will be written * @param x5 where the sixth word will be written * @param x6 where the seventh word will be written * @param x7 where the eighth word will be written */ template inline void load_be(const uint8_t in[], T& x0, T& x1, T& x2, T& x3, T& x4, T& x5, T& x6, T& x7) { x0 = load_be(in, 0); x1 = load_be(in, 1); x2 = load_be(in, 2); x3 = load_be(in, 3); x4 = load_be(in, 4); x5 = load_be(in, 5); x6 = load_be(in, 6); x7 = load_be(in, 7); } /** * Load a variable number of big-endian words * @param out the output array of words * @param in the input array of bytes * @param count how many words are in in */ template inline void load_be(T out[], const uint8_t in[], size_t count) { if(count > 0) { #if defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) typecast_copy(out, in, count); #elif defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) typecast_copy(out, in, count); const size_t blocks = count - (count % 4); const size_t left = count - blocks; for(size_t i = 0; i != blocks; i += 4) bswap_4(out + i); for(size_t i = 0; i != left; ++i) out[blocks+i] = reverse_bytes(out[blocks+i]); #else for(size_t i = 0; i != count; ++i) out[i] = load_be(in, i); #endif } } /** * Store a big-endian uint16_t * @param in the input uint16_t * @param out the byte array to write to */ inline void store_be(uint16_t in, uint8_t out[2]) { #if defined(BOTAN_ENDIAN_N2B) uint16_t o = BOTAN_ENDIAN_N2B(in); typecast_copy(out, o); #else out[0] = get_byte(0, in); out[1] = get_byte(1, in); #endif } /** * Store a little-endian uint16_t * @param in the input uint16_t * @param out the byte array to write to */ inline void store_le(uint16_t in, uint8_t out[2]) { #if defined(BOTAN_ENDIAN_N2L) uint16_t o = BOTAN_ENDIAN_N2L(in); typecast_copy(out, o); #else out[0] = get_byte(1, in); out[1] = get_byte(0, in); #endif } /** * Store a big-endian uint32_t * @param in the input uint32_t * @param out the byte array to write to */ inline void store_be(uint32_t in, uint8_t out[4]) { #if defined(BOTAN_ENDIAN_B2N) uint32_t o = BOTAN_ENDIAN_B2N(in); typecast_copy(out, o); #else out[0] = get_byte(0, in); out[1] = get_byte(1, in); out[2] = get_byte(2, in); out[3] = get_byte(3, in); #endif } /** * Store a little-endian uint32_t * @param in the input uint32_t * @param out the byte array to write to */ inline void store_le(uint32_t in, uint8_t out[4]) { #if defined(BOTAN_ENDIAN_L2N) uint32_t o = BOTAN_ENDIAN_L2N(in); typecast_copy(out, o); #else out[0] = get_byte(3, in); out[1] = get_byte(2, in); out[2] = get_byte(1, in); out[3] = get_byte(0, in); #endif } /** * Store a big-endian uint64_t * @param in the input uint64_t * @param out the byte array to write to */ inline void store_be(uint64_t in, uint8_t out[8]) { #if defined(BOTAN_ENDIAN_B2N) uint64_t o = BOTAN_ENDIAN_B2N(in); typecast_copy(out, o); #else out[0] = get_byte(0, in); out[1] = get_byte(1, in); out[2] = get_byte(2, in); out[3] = get_byte(3, in); out[4] = get_byte(4, in); out[5] = get_byte(5, in); out[6] = get_byte(6, in); out[7] = get_byte(7, in); #endif } /** * Store a little-endian uint64_t * @param in the input uint64_t * @param out the byte array to write to */ inline void store_le(uint64_t in, uint8_t out[8]) { #if defined(BOTAN_ENDIAN_L2N) uint64_t o = BOTAN_ENDIAN_L2N(in); typecast_copy(out, o); #else out[0] = get_byte(7, in); out[1] = get_byte(6, in); out[2] = get_byte(5, in); out[3] = get_byte(4, in); out[4] = get_byte(3, in); out[5] = get_byte(2, in); out[6] = get_byte(1, in); out[7] = get_byte(0, in); #endif } /** * Store two little-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word */ template inline void store_le(uint8_t out[], T x0, T x1) { store_le(x0, out + (0 * sizeof(T))); store_le(x1, out + (1 * sizeof(T))); } /** * Store two big-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word */ template inline void store_be(uint8_t out[], T x0, T x1) { store_be(x0, out + (0 * sizeof(T))); store_be(x1, out + (1 * sizeof(T))); } /** * Store four little-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word * @param x2 the third word * @param x3 the fourth word */ template inline void store_le(uint8_t out[], T x0, T x1, T x2, T x3) { store_le(x0, out + (0 * sizeof(T))); store_le(x1, out + (1 * sizeof(T))); store_le(x2, out + (2 * sizeof(T))); store_le(x3, out + (3 * sizeof(T))); } /** * Store four big-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word * @param x2 the third word * @param x3 the fourth word */ template inline void store_be(uint8_t out[], T x0, T x1, T x2, T x3) { store_be(x0, out + (0 * sizeof(T))); store_be(x1, out + (1 * sizeof(T))); store_be(x2, out + (2 * sizeof(T))); store_be(x3, out + (3 * sizeof(T))); } /** * Store eight little-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word * @param x2 the third word * @param x3 the fourth word * @param x4 the fifth word * @param x5 the sixth word * @param x6 the seventh word * @param x7 the eighth word */ template inline void store_le(uint8_t out[], T x0, T x1, T x2, T x3, T x4, T x5, T x6, T x7) { store_le(x0, out + (0 * sizeof(T))); store_le(x1, out + (1 * sizeof(T))); store_le(x2, out + (2 * sizeof(T))); store_le(x3, out + (3 * sizeof(T))); store_le(x4, out + (4 * sizeof(T))); store_le(x5, out + (5 * sizeof(T))); store_le(x6, out + (6 * sizeof(T))); store_le(x7, out + (7 * sizeof(T))); } /** * Store eight big-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word * @param x2 the third word * @param x3 the fourth word * @param x4 the fifth word * @param x5 the sixth word * @param x6 the seventh word * @param x7 the eighth word */ template inline void store_be(uint8_t out[], T x0, T x1, T x2, T x3, T x4, T x5, T x6, T x7) { store_be(x0, out + (0 * sizeof(T))); store_be(x1, out + (1 * sizeof(T))); store_be(x2, out + (2 * sizeof(T))); store_be(x3, out + (3 * sizeof(T))); store_be(x4, out + (4 * sizeof(T))); store_be(x5, out + (5 * sizeof(T))); store_be(x6, out + (6 * sizeof(T))); store_be(x7, out + (7 * sizeof(T))); } template void copy_out_be(uint8_t out[], size_t out_bytes, const T in[]) { while(out_bytes >= sizeof(T)) { store_be(in[0], out); out += sizeof(T); out_bytes -= sizeof(T); in += 1; } for(size_t i = 0; i != out_bytes; ++i) out[i] = get_byte(i%8, in[0]); } template void copy_out_vec_be(uint8_t out[], size_t out_bytes, const std::vector& in) { copy_out_be(out, out_bytes, in.data()); } template void copy_out_le(uint8_t out[], size_t out_bytes, const T in[]) { while(out_bytes >= sizeof(T)) { store_le(in[0], out); out += sizeof(T); out_bytes -= sizeof(T); in += 1; } for(size_t i = 0; i != out_bytes; ++i) out[i] = get_byte(sizeof(T) - 1 - (i % 8), in[0]); } template void copy_out_vec_le(uint8_t out[], size_t out_bytes, const std::vector& in) { copy_out_le(out, out_bytes, in.data()); } } BOTAN_FUTURE_INTERNAL_HEADER(mul128.h) namespace Botan { #if defined(__SIZEOF_INT128__) && defined(BOTAN_TARGET_CPU_HAS_NATIVE_64BIT) #define BOTAN_TARGET_HAS_NATIVE_UINT128 // Prefer TI mode over __int128 as GCC rejects the latter in pendantic mode #if defined(__GNUG__) typedef unsigned int uint128_t __attribute__((mode(TI))); #else typedef unsigned __int128 uint128_t; #endif #endif } #if defined(BOTAN_TARGET_HAS_NATIVE_UINT128) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) \ do { \ const uint128_t r = static_cast(a) * b; \ *hi = (r >> 64) & 0xFFFFFFFFFFFFFFFF; \ *lo = (r ) & 0xFFFFFFFFFFFFFFFF; \ } while(0) #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC) && defined(BOTAN_TARGET_CPU_HAS_NATIVE_64BIT) #include #pragma intrinsic(_umul128) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) \ do { *lo = _umul128(a, b, hi); } while(0) #elif defined(BOTAN_USE_GCC_INLINE_ASM) #if defined(BOTAN_TARGET_ARCH_IS_X86_64) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) do { \ asm("mulq %3" : "=d" (*hi), "=a" (*lo) : "a" (a), "rm" (b) : "cc"); \ } while(0) #elif defined(BOTAN_TARGET_ARCH_IS_ALPHA) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) do { \ asm("umulh %1,%2,%0" : "=r" (*hi) : "r" (a), "r" (b)); \ *lo = a * b; \ } while(0) #elif defined(BOTAN_TARGET_ARCH_IS_IA64) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) do { \ asm("xmpy.hu %0=%1,%2" : "=f" (*hi) : "f" (a), "f" (b)); \ *lo = a * b; \ } while(0) #elif defined(BOTAN_TARGET_ARCH_IS_PPC64) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) do { \ asm("mulhdu %0,%1,%2" : "=r" (*hi) : "r" (a), "r" (b) : "cc"); \ *lo = a * b; \ } while(0) #endif #endif namespace Botan { /** * Perform a 64x64->128 bit multiplication */ inline void mul64x64_128(uint64_t a, uint64_t b, uint64_t* lo, uint64_t* hi) { #if defined(BOTAN_FAST_64X64_MUL) BOTAN_FAST_64X64_MUL(a, b, lo, hi); #else /* * Do a 64x64->128 multiply using four 32x32->64 multiplies plus * some adds and shifts. Last resort for CPUs like UltraSPARC (with * 64-bit registers/ALU, but no 64x64->128 multiply) or 32-bit CPUs. */ const size_t HWORD_BITS = 32; const uint32_t HWORD_MASK = 0xFFFFFFFF; const uint32_t a_hi = (a >> HWORD_BITS); const uint32_t a_lo = (a & HWORD_MASK); const uint32_t b_hi = (b >> HWORD_BITS); const uint32_t b_lo = (b & HWORD_MASK); uint64_t x0 = static_cast(a_hi) * b_hi; uint64_t x1 = static_cast(a_lo) * b_hi; uint64_t x2 = static_cast(a_hi) * b_lo; uint64_t x3 = static_cast(a_lo) * b_lo; // this cannot overflow as (2^32-1)^2 + 2^32-1 < 2^64-1 x2 += x3 >> HWORD_BITS; // this one can overflow x2 += x1; // propagate the carry if any x0 += static_cast(static_cast(x2 < x1)) << HWORD_BITS; *hi = x0 + (x2 >> HWORD_BITS); *lo = ((x2 & HWORD_MASK) << HWORD_BITS) + (x3 & HWORD_MASK); #endif } } BOTAN_FUTURE_INTERNAL_HEADER(parsing.h) namespace Botan { /** * Parse a SCAN-style algorithm name * @param scan_name the name * @return the name components */ BOTAN_PUBLIC_API(2,0) std::vector parse_algorithm_name(const std::string& scan_name); /** * Split a string * @param str the input string * @param delim the delimitor * @return string split by delim */ BOTAN_PUBLIC_API(2,0) std::vector split_on( const std::string& str, char delim); /** * Split a string on a character predicate * @param str the input string * @param pred the predicate * * This function will likely be removed in a future release */ BOTAN_PUBLIC_API(2,0) std::vector split_on_pred(const std::string& str, std::function pred); /** * Erase characters from a string */ BOTAN_PUBLIC_API(2,0) BOTAN_DEPRECATED("Unused") std::string erase_chars(const std::string& str, const std::set& chars); /** * Replace a character in a string * @param str the input string * @param from_char the character to replace * @param to_char the character to replace it with * @return str with all instances of from_char replaced by to_char */ BOTAN_PUBLIC_API(2,0) BOTAN_DEPRECATED("Unused") std::string replace_char(const std::string& str, char from_char, char to_char); /** * Replace a character in a string * @param str the input string * @param from_chars the characters to replace * @param to_char the character to replace it with * @return str with all instances of from_chars replaced by to_char */ BOTAN_PUBLIC_API(2,0) BOTAN_DEPRECATED("Unused") std::string replace_chars(const std::string& str, const std::set& from_chars, char to_char); /** * Join a string * @param strs strings to join * @param delim the delimitor * @return string joined by delim */ BOTAN_PUBLIC_API(2,0) std::string string_join(const std::vector& strs, char delim); /** * Parse an ASN.1 OID * @param oid the OID in string form * @return OID components */ BOTAN_PUBLIC_API(2,0) std::vector BOTAN_DEPRECATED("Use OID::from_string(oid).get_components()") parse_asn1_oid(const std::string& oid); /** * Compare two names using the X.509 comparison algorithm * @param name1 the first name * @param name2 the second name * @return true if name1 is the same as name2 by the X.509 comparison rules */ BOTAN_PUBLIC_API(2,0) bool x500_name_cmp(const std::string& name1, const std::string& name2); /** * Convert a string to a number * @param str the string to convert * @return number value of the string */ BOTAN_PUBLIC_API(2,0) uint32_t to_u32bit(const std::string& str); /** * Convert a string to a number * @param str the string to convert * @return number value of the string */ BOTAN_PUBLIC_API(2,3) uint16_t to_uint16(const std::string& str); /** * Convert a time specification to a number * @param timespec the time specification * @return number of seconds represented by timespec */ BOTAN_PUBLIC_API(2,0) uint32_t BOTAN_DEPRECATED("Not used anymore") timespec_to_u32bit(const std::string& timespec); /** * Convert a string representation of an IPv4 address to a number * @param ip_str the string representation * @return integer IPv4 address */ BOTAN_PUBLIC_API(2,0) uint32_t string_to_ipv4(const std::string& ip_str); /** * Convert an IPv4 address to a string * @param ip_addr the IPv4 address to convert * @return string representation of the IPv4 address */ BOTAN_PUBLIC_API(2,0) std::string ipv4_to_string(uint32_t ip_addr); std::map BOTAN_PUBLIC_API(2,0) read_cfg(std::istream& is); /** * Accepts key value pairs deliminated by commas: * * "" (returns empty map) * "K=V" (returns map {'K': 'V'}) * "K1=V1,K2=V2" * "K1=V1,K2=V2,K3=V3" * "K1=V1,K2=V2,K3=a_value\,with\,commas_and_\=equals" * * Values may be empty, keys must be non-empty and unique. Duplicate * keys cause an exception. * * Within both key and value, comma and equals can be escaped with * backslash. Backslash can also be escaped. */ std::map BOTAN_PUBLIC_API(2,8) read_kv(const std::string& kv); std::string BOTAN_PUBLIC_API(2,0) clean_ws(const std::string& s); std::string tolower_string(const std::string& s); /** * Check if the given hostname is a match for the specified wildcard */ bool BOTAN_PUBLIC_API(2,0) host_wildcard_match(const std::string& wildcard, const std::string& host); } BOTAN_FUTURE_INTERNAL_HEADER(rotate.h) namespace Botan { /** * Bit rotation left by a compile-time constant amount * @param input the input word * @return input rotated left by ROT bits */ template inline constexpr T rotl(T input) { static_assert(ROT > 0 && ROT < 8*sizeof(T), "Invalid rotation constant"); return static_cast((input << ROT) | (input >> (8*sizeof(T) - ROT))); } /** * Bit rotation right by a compile-time constant amount * @param input the input word * @return input rotated right by ROT bits */ template inline constexpr T rotr(T input) { static_assert(ROT > 0 && ROT < 8*sizeof(T), "Invalid rotation constant"); return static_cast((input >> ROT) | (input << (8*sizeof(T) - ROT))); } /** * Bit rotation left, variable rotation amount * @param input the input word * @param rot the number of bits to rotate, must be between 0 and sizeof(T)*8-1 * @return input rotated left by rot bits */ template inline T rotl_var(T input, size_t rot) { return rot ? static_cast((input << rot) | (input >> (sizeof(T)*8 - rot))) : input; } /** * Bit rotation right, variable rotation amount * @param input the input word * @param rot the number of bits to rotate, must be between 0 and sizeof(T)*8-1 * @return input rotated right by rot bits */ template inline T rotr_var(T input, size_t rot) { return rot ? static_cast((input >> rot) | (input << (sizeof(T)*8 - rot))) : input; } #if defined(BOTAN_USE_GCC_INLINE_ASM) #if defined(BOTAN_TARGET_ARCH_IS_X86_64) || defined(BOTAN_TARGET_ARCH_IS_X86_32) template<> inline uint32_t rotl_var(uint32_t input, size_t rot) { asm("roll %1,%0" : "+r" (input) : "c" (static_cast(rot)) : "cc"); return input; } template<> inline uint32_t rotr_var(uint32_t input, size_t rot) { asm("rorl %1,%0" : "+r" (input) : "c" (static_cast(rot)) : "cc"); return input; } #endif #endif template BOTAN_DEPRECATED("Use rotl or rotl_var") inline T rotate_left(T input, size_t rot) { // rotl_var does not reduce return rotl_var(input, rot % (8 * sizeof(T))); } template BOTAN_DEPRECATED("Use rotr or rotr_var") inline T rotate_right(T input, size_t rot) { // rotr_var does not reduce return rotr_var(input, rot % (8 * sizeof(T))); } } BOTAN_FUTURE_INTERNAL_HEADER(scan_name.h) namespace Botan { /** A class encapsulating a SCAN name (similar to JCE conventions) http://www.users.zetnet.co.uk/hopwood/crypto/scan/ */ class BOTAN_PUBLIC_API(2,0) SCAN_Name final { public: /** * Create a SCAN_Name * @param algo_spec A SCAN-format name */ explicit SCAN_Name(const char* algo_spec); /** * Create a SCAN_Name * @param algo_spec A SCAN-format name */ explicit SCAN_Name(std::string algo_spec); /** * @return original input string */ const std::string& to_string() const { return m_orig_algo_spec; } BOTAN_DEPRECATED("Use SCAN_Name::to_string") const std::string& as_string() const { return this->to_string(); } /** * @return algorithm name */ const std::string& algo_name() const { return m_alg_name; } /** * @return number of arguments */ size_t arg_count() const { return m_args.size(); } /** * @param lower is the lower bound * @param upper is the upper bound * @return if the number of arguments is between lower and upper */ bool arg_count_between(size_t lower, size_t upper) const { return ((arg_count() >= lower) && (arg_count() <= upper)); } /** * @param i which argument * @return ith argument */ std::string arg(size_t i) const; /** * @param i which argument * @param def_value the default value * @return ith argument or the default value */ std::string arg(size_t i, const std::string& def_value) const; /** * @param i which argument * @param def_value the default value * @return ith argument as an integer, or the default value */ size_t arg_as_integer(size_t i, size_t def_value) const; /** * @return cipher mode (if any) */ std::string cipher_mode() const { return (m_mode_info.size() >= 1) ? m_mode_info[0] : ""; } /** * @return cipher mode padding (if any) */ std::string cipher_mode_pad() const { return (m_mode_info.size() >= 2) ? m_mode_info[1] : ""; } private: std::string m_orig_algo_spec; std::string m_alg_name; std::vector m_args; std::vector m_mode_info; }; // This is unrelated but it is convenient to stash it here template std::vector probe_providers_of(const std::string& algo_spec, const std::vector& possible) { std::vector providers; for(auto&& prov : possible) { std::unique_ptr o(T::create(algo_spec, prov)); if(o) { providers.push_back(prov); // available } } return providers; } } #if __cplusplus < 201402L #endif BOTAN_FUTURE_INTERNAL_HEADER(stl_compatability.h) namespace Botan { /* * std::make_unique functionality similar as we have in C++14. * C++11 version based on proposal for C++14 implemenatation by Stephan T. Lavavej * source: https://isocpp.org/files/papers/N3656.txt */ #if __cplusplus >= 201402L template constexpr auto make_unique(Args&&... args) { return std::make_unique(std::forward(args)...); } template constexpr auto make_unique(std::size_t size) { return std::make_unique(size); } #else namespace stlCompatibilityDetails { template struct _Unique_if { typedef std::unique_ptr _Single_object; }; template struct _Unique_if { typedef std::unique_ptr _Unknown_bound; }; template struct _Unique_if { typedef void _Known_bound; }; } // namespace stlCompatibilityDetails template typename stlCompatibilityDetails::_Unique_if::_Single_object make_unique(Args&&... args) { return std::unique_ptr(new T(std::forward(args)...)); } template typename stlCompatibilityDetails::_Unique_if::_Unknown_bound make_unique(size_t n) { typedef typename std::remove_extent::type U; return std::unique_ptr(new U[n]()); } template typename stlCompatibilityDetails::_Unique_if::_Known_bound make_unique(Args&&...) = delete; #endif } // namespace Botan #if defined(BOTAN_HAS_STREAM_CIPHER) #endif BOTAN_FUTURE_INTERNAL_HEADER(stream_mode.h) namespace Botan { #if defined(BOTAN_HAS_STREAM_CIPHER) class BOTAN_PUBLIC_API(2,0) Stream_Cipher_Mode final : public Cipher_Mode { public: /** * @param cipher underyling stream cipher */ explicit Stream_Cipher_Mode(StreamCipher* cipher) : m_cipher(cipher) {} size_t process(uint8_t buf[], size_t sz) override { m_cipher->cipher1(buf, sz); return sz; } void finish(secure_vector& buf, size_t offset) override { return update(buf, offset); } size_t output_length(size_t input_length) const override { return input_length; } size_t update_granularity() const override { return 1; } size_t minimum_final_size() const override { return 0; } size_t default_nonce_length() const override { return 0; } bool valid_nonce_length(size_t nonce_len) const override { return m_cipher->valid_iv_length(nonce_len); } Key_Length_Specification key_spec() const override { return m_cipher->key_spec(); } std::string name() const override { return m_cipher->name(); } void clear() override { m_cipher->clear(); reset(); } void reset() override { /* no msg state */ } private: void start_msg(const uint8_t nonce[], size_t nonce_len) override { if(nonce_len > 0) { m_cipher->set_iv(nonce, nonce_len); } } void key_schedule(const uint8_t key[], size_t length) override { m_cipher->set_key(key, length); } std::unique_ptr m_cipher; }; #endif } namespace Botan { /* * Get information describing the version */ /** * Get a human-readable string identifying the version of Botan. * No particular format should be assumed. * @return version string */ BOTAN_PUBLIC_API(2,0) std::string version_string(); /** * Same as version_string() except returning a pointer to a statically * allocated string. * @return version string */ BOTAN_PUBLIC_API(2,0) const char* version_cstr(); /** * Return a version string of the form "MAJOR.MINOR.PATCH" where * each of the values is an integer. */ BOTAN_PUBLIC_API(2,4) std::string short_version_string(); /** * Same as version_short_string except returning a pointer to the string. */ BOTAN_PUBLIC_API(2,4) const char* short_version_cstr(); /** * Return the date this version of botan was released, in an integer of * the form YYYYMMDD. For instance a version released on May 21, 2013 * would return the integer 20130521. If the currently running version * is not an official release, this function will return 0 instead. * * @return release date, or zero if unreleased */ BOTAN_PUBLIC_API(2,0) uint32_t version_datestamp(); /** * Get the major version number. * @return major version number */ BOTAN_PUBLIC_API(2,0) uint32_t version_major(); /** * Get the minor version number. * @return minor version number */ BOTAN_PUBLIC_API(2,0) uint32_t version_minor(); /** * Get the patch number. * @return patch number */ BOTAN_PUBLIC_API(2,0) uint32_t version_patch(); /** * Usable for checking that the DLL version loaded at runtime exactly * matches the compile-time version. Call using BOTAN_VERSION_* macro * values. Returns the empty string if an exact match, otherwise an * appropriate message. Added with 1.11.26. */ BOTAN_PUBLIC_API(2,0) std::string runtime_version_check(uint32_t major, uint32_t minor, uint32_t patch); /* * Macros for compile-time version checks */ #define BOTAN_VERSION_CODE_FOR(a,b,c) ((a << 16) | (b << 8) | (c)) /** * Compare using BOTAN_VERSION_CODE_FOR, as in * # if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,8,0) * # error "Botan version too old" * # endif */ #define BOTAN_VERSION_CODE BOTAN_VERSION_CODE_FOR(BOTAN_VERSION_MAJOR, \ BOTAN_VERSION_MINOR, \ BOTAN_VERSION_PATCH) } #endif // BOTAN_AMALGAMATION_H_ OSCAR-code-v1.3.1/oscar/SleepLib/thirdparty/botan_macos.h000066400000000000000000005263301417327530600231400ustar00rootroot00000000000000/* * Botan 2.18.2 Amalgamation * (C) 1999-2020 The Botan Authors * * Botan is released under the Simplified BSD License (see license.txt) */ #ifndef BOTAN_AMALGAMATION_H_ #define BOTAN_AMALGAMATION_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Build configuration for Botan 2.18.2 * * Automatically generated from * 'configure.py --amalgamation --os=macos --cpu=generic --disable-shared --minimized-build --enable-modules=aes,gcm' * * Target * - Compiler: clang++ -fstack-protector -pthread -stdlib=libc++ -std=c++11 -D_REENTRANT -O3 * - Arch: generic * - OS: macos */ #define BOTAN_VERSION_MAJOR 2 #define BOTAN_VERSION_MINOR 18 #define BOTAN_VERSION_PATCH 2 #define BOTAN_VERSION_DATESTAMP 20211025 #define BOTAN_VERSION_RELEASE_TYPE "release" #define BOTAN_VERSION_VC_REVISION "git:a44f1489239e80937ca67564ff103421e5584069" #define BOTAN_DISTRIBUTION_INFO "unspecified" /* How many bits per limb in a BigInt */ #define BOTAN_MP_WORD_BITS 32 #define BOTAN_INSTALL_PREFIX R"(/usr/local)" #define BOTAN_INSTALL_HEADER_DIR R"(include/botan-2)" #define BOTAN_INSTALL_LIB_DIR R"(/usr/local/lib)" #define BOTAN_LIB_LINK "" #define BOTAN_LINK_FLAGS "-fstack-protector -pthread -stdlib=libc++" #define BOTAN_SYSTEM_CERT_BUNDLE "/etc/ssl/cert.pem" #ifndef BOTAN_DLL #define BOTAN_DLL #endif /* Target identification and feature test macros */ #define BOTAN_TARGET_OS_IS_MACOS #define BOTAN_TARGET_OS_HAS_APPLE_KEYCHAIN #define BOTAN_TARGET_OS_HAS_ARC4RANDOM #define BOTAN_TARGET_OS_HAS_ATOMICS #define BOTAN_TARGET_OS_HAS_CLOCK_GETTIME #define BOTAN_TARGET_OS_HAS_COMMONCRYPTO #define BOTAN_TARGET_OS_HAS_DEV_RANDOM #define BOTAN_TARGET_OS_HAS_FILESYSTEM #define BOTAN_TARGET_OS_HAS_GETENTROPY #define BOTAN_TARGET_OS_HAS_POSIX1 #define BOTAN_TARGET_OS_HAS_POSIX_MLOCK #define BOTAN_TARGET_OS_HAS_SOCKETS #define BOTAN_TARGET_OS_HAS_THREAD_LOCAL #define BOTAN_TARGET_OS_HAS_THREADS #define BOTAN_BUILD_COMPILER_IS_CLANG #define BOTAN_TARGET_ARCH_IS_GENERIC /* * Module availability definitions */ #define BOTAN_HAS_AEAD_GCM 20131128 #define BOTAN_HAS_AEAD_MODES 20131128 #define BOTAN_HAS_AES 20131128 #define BOTAN_HAS_BLOCK_CIPHER 20131128 #define BOTAN_HAS_CIPHER_MODES 20180124 #define BOTAN_HAS_CPUID 20170917 #define BOTAN_HAS_CTR_BE 20131128 #define BOTAN_HAS_ENTROPY_SOURCE 20151120 #define BOTAN_HAS_GHASH 20201002 #define BOTAN_HAS_HEX_CODEC 20131128 #define BOTAN_HAS_MODES 20150626 #define BOTAN_HAS_STREAM_CIPHER 20131128 #define BOTAN_HAS_UTIL_FUNCTIONS 20180903 /* * Local/misc configuration options (if any) follow */ /* * Things you can edit (but probably shouldn't) */ #if !defined(BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES) #if defined(BOTAN_NO_DEPRECATED) #define BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES private #else #define BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES public #endif #endif /* How much to allocate for a buffer of no particular size */ #define BOTAN_DEFAULT_BUFFER_SIZE 1024 /* * Total maximum amount of RAM (in KiB) we will lock into memory, even * if the OS would let us lock more */ #define BOTAN_MLOCK_ALLOCATOR_MAX_LOCKED_KB 512 /* * If BOTAN_MEM_POOL_USE_MMU_PROTECTIONS is defined, the Memory_Pool * class used for mlock'ed memory will use OS calls to set page * permissions so as to prohibit access to pages on the free list, then * enable read/write access when the page is set to be used. This will * turn (some) use after free bugs into a crash. * * The additional syscalls have a substantial performance impact, which * is why this option is not enabled by default. */ #if defined(BOTAN_HAS_VALGRIND) || defined(BOTAN_ENABLE_DEBUG_ASSERTS) #define BOTAN_MEM_POOL_USE_MMU_PROTECTIONS #endif /* * If enabled uses memset via volatile function pointer to zero memory, * otherwise does a byte at a time write via a volatile pointer. */ #define BOTAN_USE_VOLATILE_MEMSET_FOR_ZERO 1 /* * Normally blinding is performed by choosing a random starting point (plus * its inverse, of a form appropriate to the algorithm being blinded), and * then choosing new blinding operands by successive squaring of both * values. This is much faster than computing a new starting point but * introduces some possible corelation * * To avoid possible leakage problems in long-running processes, the blinder * periodically reinitializes the sequence. This value specifies how often * a new sequence should be started. */ #define BOTAN_BLINDING_REINIT_INTERVAL 64 /* * Userspace RNGs like HMAC_DRBG will reseed after a specified number * of outputs are generated. Set to zero to disable automatic reseeding. */ #define BOTAN_RNG_DEFAULT_RESEED_INTERVAL 1024 #define BOTAN_RNG_RESEED_POLL_BITS 256 #define BOTAN_RNG_AUTO_RESEED_TIMEOUT std::chrono::milliseconds(10) #define BOTAN_RNG_RESEED_DEFAULT_TIMEOUT std::chrono::milliseconds(50) /* * Specifies (in order) the list of entropy sources that will be used * to seed an in-memory RNG. */ #define BOTAN_ENTROPY_DEFAULT_SOURCES \ { "rdseed", "hwrng", "p9_darn", "getentropy", "dev_random", \ "system_rng", "proc_walk", "system_stats" } /* Multiplier on a block cipher's native parallelism */ #define BOTAN_BLOCK_CIPHER_PAR_MULT 4 /* * These control the RNG used by the system RNG interface */ #define BOTAN_SYSTEM_RNG_DEVICE "/dev/urandom" #define BOTAN_SYSTEM_RNG_POLL_DEVICES { "/dev/urandom", "/dev/random" } /* * This directory will be monitored by ProcWalking_EntropySource and * the contents provided as entropy inputs to the RNG. May also be * usefully set to something like "/sys", depending on the system being * deployed to. Set to an empty string to disable. */ #define BOTAN_ENTROPY_PROC_FS_PATH "/proc" /* * These paramaters control how many bytes to read from the system * PRNG, and how long to block if applicable. The timeout only applies * to reading /dev/urandom and company. */ #define BOTAN_SYSTEM_RNG_POLL_REQUEST 64 #define BOTAN_SYSTEM_RNG_POLL_TIMEOUT_MS 20 /* * When a PBKDF is self-tuning parameters, it will attempt to take about this * amount of time to self-benchmark. */ #define BOTAN_PBKDF_TUNING_TIME std::chrono::milliseconds(10) /* * If no way of dynamically determining the cache line size for the * system exists, this value is used as the default. Used by the side * channel countermeasures rather than for alignment purposes, so it is * better to be on the smaller side if the exact value cannot be * determined. Typically 32 or 64 bytes on modern CPUs. */ #if !defined(BOTAN_TARGET_CPU_DEFAULT_CACHE_LINE_SIZE) #define BOTAN_TARGET_CPU_DEFAULT_CACHE_LINE_SIZE 32 #endif /** * Controls how AutoSeeded_RNG is instantiated */ #if !defined(BOTAN_AUTO_RNG_HMAC) #if defined(BOTAN_HAS_SHA2_64) #define BOTAN_AUTO_RNG_HMAC "HMAC(SHA-384)" #elif defined(BOTAN_HAS_SHA2_32) #define BOTAN_AUTO_RNG_HMAC "HMAC(SHA-256)" #elif defined(BOTAN_HAS_SHA3) #define BOTAN_AUTO_RNG_HMAC "HMAC(SHA-3(256))" #elif defined(BOTAN_HAS_SHA1) #define BOTAN_AUTO_RNG_HMAC "HMAC(SHA-1)" #endif /* Otherwise, no hash found: leave BOTAN_AUTO_RNG_HMAC undefined */ #endif /* Check for a common build problem */ #if defined(BOTAN_TARGET_ARCH_IS_X86_64) && ((defined(_MSC_VER) && !defined(_WIN64)) || \ (defined(__clang__) && !defined(__x86_64__)) || \ (defined(__GNUG__) && !defined(__x86_64__))) #error "Trying to compile Botan configured as x86_64 with non-x86_64 compiler." #endif #if defined(BOTAN_TARGET_ARCH_IS_X86_32) && ((defined(_MSC_VER) && defined(_WIN64)) || \ (defined(__clang__) && !defined(__i386__)) || \ (defined(__GNUG__) && !defined(__i386__))) #error "Trying to compile Botan configured as x86_32 with non-x86_32 compiler." #endif /* Should we use GCC-style inline assembler? */ #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || \ defined(BOTAN_BUILD_COMPILER_IS_CLANG) || \ defined(BOTAN_BUILD_COMPILER_IS_XLC) || \ defined(BOTAN_BUILD_COMPILER_IS_SUN_STUDIO) #define BOTAN_USE_GCC_INLINE_ASM #endif /** * Used to annotate API exports which are public and supported. * These APIs will not be broken/removed unless strictly required for * functionality or security, and only in new major versions. * @param maj The major version this public API was released in * @param min The minor version this public API was released in */ #define BOTAN_PUBLIC_API(maj,min) BOTAN_DLL /** * Used to annotate API exports which are public, but are now deprecated * and which will be removed in a future major release. */ #define BOTAN_DEPRECATED_API(msg) BOTAN_DLL BOTAN_DEPRECATED(msg) /** * Used to annotate API exports which are public and can be used by * applications if needed, but which are intentionally not documented, * and which may change incompatibly in a future major version. */ #define BOTAN_UNSTABLE_API BOTAN_DLL /** * Used to annotate API exports which are exported but only for the * purposes of testing. They should not be used by applications and * may be removed or changed without notice. */ #define BOTAN_TEST_API BOTAN_DLL /* * Define BOTAN_GCC_VERSION */ #if defined(__GNUC__) && !defined(__clang__) #define BOTAN_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__ * 10 + __GNUC_PATCHLEVEL__) #else #define BOTAN_GCC_VERSION 0 #endif /* * Define BOTAN_CLANG_VERSION */ #if defined(__clang__) #define BOTAN_CLANG_VERSION (__clang_major__ * 10 + __clang_minor__) #else #define BOTAN_CLANG_VERSION 0 #endif /* * Define BOTAN_FUNC_ISA */ #if (defined(__GNUC__) && !defined(__clang__)) || (BOTAN_CLANG_VERSION > 38) #define BOTAN_FUNC_ISA(isa) __attribute__ ((target(isa))) #else #define BOTAN_FUNC_ISA(isa) #endif /* * Define BOTAN_WARN_UNUSED_RESULT */ #if defined(__GNUC__) || defined(__clang__) #define BOTAN_WARN_UNUSED_RESULT __attribute__ ((warn_unused_result)) #else #define BOTAN_WARN_UNUSED_RESULT #endif /* * Define BOTAN_MALLOC_FN */ #if defined(__ibmxl__) /* XLC pretends to be both Clang and GCC, but is neither */ #define BOTAN_MALLOC_FN __attribute__ ((malloc)) #elif defined(__GNUC__) #define BOTAN_MALLOC_FN __attribute__ ((malloc, alloc_size(1,2))) #elif defined(_MSC_VER) #define BOTAN_MALLOC_FN __declspec(restrict) #else #define BOTAN_MALLOC_FN #endif /* * Define BOTAN_DEPRECATED */ #if !defined(BOTAN_NO_DEPRECATED_WARNINGS) && !defined(BOTAN_IS_BEING_BUILT) && !defined(BOTAN_AMALGAMATION_H_) #if defined(__clang__) #define BOTAN_DEPRECATED(msg) __attribute__ ((deprecated(msg))) #define BOTAN_DEPRECATED_HEADER(hdr) _Pragma("message \"this header is deprecated\"") #define BOTAN_FUTURE_INTERNAL_HEADER(hdr) _Pragma("message \"this header will be made internal in the future\"") #elif defined(_MSC_VER) #define BOTAN_DEPRECATED(msg) __declspec(deprecated(msg)) #define BOTAN_DEPRECATED_HEADER(hdr) __pragma(message("this header is deprecated")) #define BOTAN_FUTURE_INTERNAL_HEADER(hdr) __pragma(message("this header will be made internal in the future")) #elif defined(__GNUC__) /* msg supported since GCC 4.5, earliest we support is 4.8 */ #define BOTAN_DEPRECATED(msg) __attribute__ ((deprecated(msg))) #define BOTAN_DEPRECATED_HEADER(hdr) _Pragma("GCC warning \"this header is deprecated\"") #define BOTAN_FUTURE_INTERNAL_HEADER(hdr) _Pragma("GCC warning \"this header will be made internal in the future\"") #endif #endif #if !defined(BOTAN_DEPRECATED) #define BOTAN_DEPRECATED(msg) #endif #if !defined(BOTAN_DEPRECATED_HEADER) #define BOTAN_DEPRECATED_HEADER(hdr) #endif #if !defined(BOTAN_FUTURE_INTERNAL_HEADER) #define BOTAN_FUTURE_INTERNAL_HEADER(hdr) #endif /* * Define BOTAN_NORETURN */ #if !defined(BOTAN_NORETURN) #if defined (__clang__) || defined (__GNUC__) #define BOTAN_NORETURN __attribute__ ((__noreturn__)) #elif defined (_MSC_VER) #define BOTAN_NORETURN __declspec(noreturn) #else #define BOTAN_NORETURN #endif #endif /* * Define BOTAN_THREAD_LOCAL */ #if !defined(BOTAN_THREAD_LOCAL) #if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_TARGET_OS_HAS_THREAD_LOCAL) #define BOTAN_THREAD_LOCAL thread_local #else #define BOTAN_THREAD_LOCAL /**/ #endif #endif /* * Define BOTAN_IF_CONSTEXPR */ #if !defined(BOTAN_IF_CONSTEXPR) #if __cplusplus >= 201703 #define BOTAN_IF_CONSTEXPR if constexpr #else #define BOTAN_IF_CONSTEXPR if #endif #endif /* * Define BOTAN_PARALLEL_FOR */ #if !defined(BOTAN_PARALLEL_FOR) #if defined(BOTAN_TARGET_HAS_OPENMP) #define BOTAN_PARALLEL_FOR _Pragma("omp parallel for") for #else #define BOTAN_PARALLEL_FOR for #endif #endif /* * Define BOTAN_FORCE_INLINE */ #if !defined(BOTAN_FORCE_INLINE) #if defined (__clang__) || defined (__GNUC__) #define BOTAN_FORCE_INLINE __attribute__ ((__always_inline__)) inline #elif defined (_MSC_VER) #define BOTAN_FORCE_INLINE __forceinline #else #define BOTAN_FORCE_INLINE inline #endif #endif /* * Define BOTAN_PARALLEL_SIMD_FOR */ #if !defined(BOTAN_PARALLEL_SIMD_FOR) #if defined(BOTAN_TARGET_HAS_OPENMP) #define BOTAN_PARALLEL_SIMD_FOR _Pragma("omp simd") for #elif defined(BOTAN_BUILD_COMPILER_IS_GCC) && (BOTAN_GCC_VERSION >= 490) #define BOTAN_PARALLEL_SIMD_FOR _Pragma("GCC ivdep") for #else #define BOTAN_PARALLEL_SIMD_FOR for #endif #endif namespace Botan { /** * Called when an assertion fails * Throws an Exception object */ BOTAN_NORETURN void BOTAN_PUBLIC_API(2,0) assertion_failure(const char* expr_str, const char* assertion_made, const char* func, const char* file, int line); /** * Called when an invalid argument is used * Throws Invalid_Argument */ BOTAN_NORETURN void BOTAN_UNSTABLE_API throw_invalid_argument(const char* message, const char* func, const char* file); #define BOTAN_ARG_CHECK(expr, msg) \ do { if(!(expr)) Botan::throw_invalid_argument(msg, __func__, __FILE__); } while(0) /** * Called when an invalid state is encountered * Throws Invalid_State */ BOTAN_NORETURN void BOTAN_UNSTABLE_API throw_invalid_state(const char* message, const char* func, const char* file); #define BOTAN_STATE_CHECK(expr) \ do { if(!(expr)) Botan::throw_invalid_state(#expr, __func__, __FILE__); } while(0) /** * Make an assertion */ #define BOTAN_ASSERT(expr, assertion_made) \ do { \ if(!(expr)) \ Botan::assertion_failure(#expr, \ assertion_made, \ __func__, \ __FILE__, \ __LINE__); \ } while(0) /** * Make an assertion */ #define BOTAN_ASSERT_NOMSG(expr) \ do { \ if(!(expr)) \ Botan::assertion_failure(#expr, \ "", \ __func__, \ __FILE__, \ __LINE__); \ } while(0) /** * Assert that value1 == value2 */ #define BOTAN_ASSERT_EQUAL(expr1, expr2, assertion_made) \ do { \ if((expr1) != (expr2)) \ Botan::assertion_failure(#expr1 " == " #expr2, \ assertion_made, \ __func__, \ __FILE__, \ __LINE__); \ } while(0) /** * Assert that expr1 (if true) implies expr2 is also true */ #define BOTAN_ASSERT_IMPLICATION(expr1, expr2, msg) \ do { \ if((expr1) && !(expr2)) \ Botan::assertion_failure(#expr1 " implies " #expr2, \ msg, \ __func__, \ __FILE__, \ __LINE__); \ } while(0) /** * Assert that a pointer is not null */ #define BOTAN_ASSERT_NONNULL(ptr) \ do { \ if((ptr) == nullptr) \ Botan::assertion_failure(#ptr " is not null", \ "", \ __func__, \ __FILE__, \ __LINE__); \ } while(0) #if defined(BOTAN_ENABLE_DEBUG_ASSERTS) #define BOTAN_DEBUG_ASSERT(expr) BOTAN_ASSERT_NOMSG(expr) #else #define BOTAN_DEBUG_ASSERT(expr) do {} while(0) #endif /** * Mark variable as unused. Takes between 1 and 9 arguments and marks all as unused, * e.g. BOTAN_UNUSED(a); or BOTAN_UNUSED(x, y, z); */ #define _BOTAN_UNUSED_IMPL1(a) static_cast(a) #define _BOTAN_UNUSED_IMPL2(a, b) static_cast(a); _BOTAN_UNUSED_IMPL1(b) #define _BOTAN_UNUSED_IMPL3(a, b, c) static_cast(a); _BOTAN_UNUSED_IMPL2(b, c) #define _BOTAN_UNUSED_IMPL4(a, b, c, d) static_cast(a); _BOTAN_UNUSED_IMPL3(b, c, d) #define _BOTAN_UNUSED_IMPL5(a, b, c, d, e) static_cast(a); _BOTAN_UNUSED_IMPL4(b, c, d, e) #define _BOTAN_UNUSED_IMPL6(a, b, c, d, e, f) static_cast(a); _BOTAN_UNUSED_IMPL5(b, c, d, e, f) #define _BOTAN_UNUSED_IMPL7(a, b, c, d, e, f, g) static_cast(a); _BOTAN_UNUSED_IMPL6(b, c, d, e, f, g) #define _BOTAN_UNUSED_IMPL8(a, b, c, d, e, f, g, h) static_cast(a); _BOTAN_UNUSED_IMPL7(b, c, d, e, f, g, h) #define _BOTAN_UNUSED_IMPL9(a, b, c, d, e, f, g, h, i) static_cast(a); _BOTAN_UNUSED_IMPL8(b, c, d, e, f, g, h, i) #define _BOTAN_UNUSED_GET_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, IMPL_NAME, ...) IMPL_NAME #define BOTAN_UNUSED(...) _BOTAN_UNUSED_GET_IMPL(__VA_ARGS__, \ _BOTAN_UNUSED_IMPL9, \ _BOTAN_UNUSED_IMPL8, \ _BOTAN_UNUSED_IMPL7, \ _BOTAN_UNUSED_IMPL6, \ _BOTAN_UNUSED_IMPL5, \ _BOTAN_UNUSED_IMPL4, \ _BOTAN_UNUSED_IMPL3, \ _BOTAN_UNUSED_IMPL2, \ _BOTAN_UNUSED_IMPL1, \ unused dummy rest value \ ) /* we got an one of _BOTAN_UNUSED_IMPL*, now call it */ (__VA_ARGS__) } namespace Botan { /** * @mainpage Botan Crypto Library API Reference * *
*
Abstract Base Classes
* BlockCipher, HashFunction, KDF, MessageAuthenticationCode, RandomNumberGenerator, * StreamCipher, SymmetricAlgorithm, AEAD_Mode, Cipher_Mode *
Public Key Interface Classes
* PK_Key_Agreement, PK_Signer, PK_Verifier, PK_Encryptor, PK_Decryptor *
Authenticated Encryption Modes
* @ref CCM_Mode "CCM", @ref ChaCha20Poly1305_Mode "ChaCha20Poly1305", @ref EAX_Mode "EAX", * @ref GCM_Mode "GCM", @ref OCB_Mode "OCB", @ref SIV_Mode "SIV" *
Block Ciphers
* @ref aria.h "ARIA", @ref aes.h "AES", @ref Blowfish, @ref camellia.h "Camellia", @ref Cascade_Cipher "Cascade", * @ref CAST_128 "CAST-128", @ref CAST_128 "CAST-256", DES, @ref DESX "DES-X", @ref TripleDES "3DES", * @ref GOST_28147_89 "GOST 28147-89", IDEA, KASUMI, Lion, MISTY1, Noekeon, SEED, Serpent, SHACAL2, SM4, * @ref Threefish_512 "Threefish", Twofish, XTEA *
Stream Ciphers
* ChaCha, @ref CTR_BE "CTR", OFB, RC4, Salsa20 *
Hash Functions
* BLAKE2b, @ref GOST_34_11 "GOST 34.11", @ref Keccak_1600 "Keccak", MD4, MD5, @ref RIPEMD_160 "RIPEMD-160", * @ref SHA_160 "SHA-1", @ref SHA_224 "SHA-224", @ref SHA_256 "SHA-256", @ref SHA_384 "SHA-384", * @ref SHA_512 "SHA-512", @ref Skein_512 "Skein-512", SM3, Streebog, Tiger, Whirlpool *
Non-Cryptographic Checksums
* Adler32, CRC24, CRC32 *
Message Authentication Codes
* @ref CBC_MAC "CBC-MAC", CMAC, HMAC, Poly1305, SipHash, ANSI_X919_MAC *
Random Number Generators
* AutoSeeded_RNG, HMAC_DRBG, Processor_RNG, System_RNG *
Key Derivation
* HKDF, @ref KDF1 "KDF1 (IEEE 1363)", @ref KDF1_18033 "KDF1 (ISO 18033-2)", @ref KDF2 "KDF2 (IEEE 1363)", * @ref sp800_108.h "SP800-108", @ref SP800_56C "SP800-56C", @ref PKCS5_PBKDF1 "PBKDF1 (PKCS#5), * @ref PKCS5_PBKDF2 "PBKDF2 (PKCS#5)" *
Password Hashing
* @ref argon2.h "Argon2", @ref scrypt.h "scrypt", @ref bcrypt.h "bcrypt", @ref passhash9.h "passhash9" *
Public Key Cryptosystems
* @ref dlies.h "DLIES", @ref ecies.h "ECIES", @ref elgamal.h "ElGamal" * @ref rsa.h "RSA", @ref newhope.h "NewHope", @ref mceliece.h "McEliece" and @ref mceies.h "MCEIES", * @ref sm2.h "SM2" *
Public Key Signature Schemes
* @ref dsa.h "DSA", @ref ecdsa.h "ECDSA", @ref ecgdsa.h "ECGDSA", @ref eckcdsa.h "ECKCDSA", * @ref gost_3410.h "GOST 34.10-2001", @ref sm2.h "SM2", @ref xmss.h "XMSS" *
Key Agreement
* @ref dh.h "DH", @ref ecdh.h "ECDH" *
Compression
* @ref bzip2.h "bzip2", @ref lzma.h "lzma", @ref zlib.h "zlib" *
TLS
* TLS::Client, TLS::Server, TLS::Policy, TLS::Protocol_Version, TLS::Callbacks, TLS::Ciphersuite, * TLS::Session, TLS::Session_Manager, Credentials_Manager *
X.509
* X509_Certificate, X509_CRL, X509_CA, Certificate_Extension, PKCS10_Request, X509_Cert_Options, * Certificate_Store, Certificate_Store_In_SQL, Certificate_Store_In_SQLite *
*/ using std::uint8_t; using std::uint16_t; using std::uint32_t; using std::uint64_t; using std::int32_t; using std::int64_t; using std::size_t; /* * These typedefs are no longer used within the library headers * or code. They are kept only for compatability with software * written against older versions. */ using byte = std::uint8_t; using u16bit = std::uint16_t; using u32bit = std::uint32_t; using u64bit = std::uint64_t; using s32bit = std::int32_t; #if (BOTAN_MP_WORD_BITS == 32) typedef uint32_t word; #elif (BOTAN_MP_WORD_BITS == 64) typedef uint64_t word; #else #error BOTAN_MP_WORD_BITS must be 32 or 64 #endif /* * Should this assert fail on your system please contact the developers * for assistance in porting. */ static_assert(sizeof(std::size_t) == 8 || sizeof(std::size_t) == 4, "This platform has an unexpected size for size_t"); } namespace Botan { /** * Allocate a memory buffer by some method. This should only be used for * primitive types (uint8_t, uint32_t, etc). * * @param elems the number of elements * @param elem_size the size of each element * @return pointer to allocated and zeroed memory, or throw std::bad_alloc on failure */ BOTAN_PUBLIC_API(2,3) BOTAN_MALLOC_FN void* allocate_memory(size_t elems, size_t elem_size); /** * Free a pointer returned by allocate_memory * @param p the pointer returned by allocate_memory * @param elems the number of elements, as passed to allocate_memory * @param elem_size the size of each element, as passed to allocate_memory */ BOTAN_PUBLIC_API(2,3) void deallocate_memory(void* p, size_t elems, size_t elem_size); /** * Ensure the allocator is initialized */ void BOTAN_UNSTABLE_API initialize_allocator(); class Allocator_Initializer { public: Allocator_Initializer() { initialize_allocator(); } }; /** * Scrub memory contents in a way that a compiler should not elide, * using some system specific technique. Note that this function might * not zero the memory (for example, in some hypothetical * implementation it might combine the memory contents with the output * of a system PRNG), but if you can detect any difference in behavior * at runtime then the clearing is side-effecting and you can just * use `clear_mem`. * * Use this function to scrub memory just before deallocating it, or on * a stack buffer before returning from the function. * * @param ptr a pointer to memory to scrub * @param n the number of bytes pointed to by ptr */ BOTAN_PUBLIC_API(2,0) void secure_scrub_memory(void* ptr, size_t n); /** * Memory comparison, input insensitive * @param x a pointer to an array * @param y a pointer to another array * @param len the number of Ts in x and y * @return 0xFF iff x[i] == y[i] forall i in [0...n) or 0x00 otherwise */ BOTAN_PUBLIC_API(2,9) uint8_t ct_compare_u8(const uint8_t x[], const uint8_t y[], size_t len); /** * Memory comparison, input insensitive * @param x a pointer to an array * @param y a pointer to another array * @param len the number of Ts in x and y * @return true iff x[i] == y[i] forall i in [0...n) */ inline bool constant_time_compare(const uint8_t x[], const uint8_t y[], size_t len) { return ct_compare_u8(x, y, len) == 0xFF; } /** * Zero out some bytes. Warning: use secure_scrub_memory instead if the * memory is about to be freed or otherwise the compiler thinks it can * elide the writes. * * @param ptr a pointer to memory to zero * @param bytes the number of bytes to zero in ptr */ inline void clear_bytes(void* ptr, size_t bytes) { if(bytes > 0) { std::memset(ptr, 0, bytes); } } /** * Zero memory before use. This simply calls memset and should not be * used in cases where the compiler cannot see the call as a * side-effecting operation (for example, if calling clear_mem before * deallocating memory, the compiler would be allowed to omit the call * to memset entirely under the as-if rule.) * * @param ptr a pointer to an array of Ts to zero * @param n the number of Ts pointed to by ptr */ template inline void clear_mem(T* ptr, size_t n) { clear_bytes(ptr, sizeof(T)*n); } // is_trivially_copyable is missing in g++ < 5.0 #if (BOTAN_GCC_VERSION > 0 && BOTAN_GCC_VERSION < 500) #define BOTAN_IS_TRIVIALLY_COPYABLE(T) true #else #define BOTAN_IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable::value #endif /** * Copy memory * @param out the destination array * @param in the source array * @param n the number of elements of in/out */ template inline void copy_mem(T* out, const T* in, size_t n) { static_assert(std::is_trivial::type>::value, ""); BOTAN_ASSERT_IMPLICATION(n > 0, in != nullptr && out != nullptr, "If n > 0 then args are not null"); if(in != nullptr && out != nullptr && n > 0) { std::memmove(out, in, sizeof(T)*n); } } template inline void typecast_copy(uint8_t out[], T in[], size_t N) { static_assert(BOTAN_IS_TRIVIALLY_COPYABLE(T), ""); std::memcpy(out, in, sizeof(T)*N); } template inline void typecast_copy(T out[], const uint8_t in[], size_t N) { static_assert(std::is_trivial::value, ""); std::memcpy(out, in, sizeof(T)*N); } template inline void typecast_copy(uint8_t out[], T in) { typecast_copy(out, &in, 1); } template inline void typecast_copy(T& out, const uint8_t in[]) { static_assert(std::is_trivial::type>::value, ""); typecast_copy(&out, in, 1); } template inline To typecast_copy(const From *src) noexcept { static_assert(BOTAN_IS_TRIVIALLY_COPYABLE(From) && std::is_trivial::value, ""); To dst; std::memcpy(&dst, src, sizeof(To)); return dst; } /** * Set memory to a fixed value * @param ptr a pointer to an array of bytes * @param n the number of Ts pointed to by ptr * @param val the value to set each byte to */ inline void set_mem(uint8_t* ptr, size_t n, uint8_t val) { if(n > 0) { std::memset(ptr, val, n); } } inline const uint8_t* cast_char_ptr_to_uint8(const char* s) { return reinterpret_cast(s); } inline const char* cast_uint8_ptr_to_char(const uint8_t* b) { return reinterpret_cast(b); } inline uint8_t* cast_char_ptr_to_uint8(char* s) { return reinterpret_cast(s); } inline char* cast_uint8_ptr_to_char(uint8_t* b) { return reinterpret_cast(b); } /** * Memory comparison, input insensitive * @param p1 a pointer to an array * @param p2 a pointer to another array * @param n the number of Ts in p1 and p2 * @return true iff p1[i] == p2[i] forall i in [0...n) */ template inline bool same_mem(const T* p1, const T* p2, size_t n) { volatile T difference = 0; for(size_t i = 0; i != n; ++i) difference |= (p1[i] ^ p2[i]); return difference == 0; } template size_t buffer_insert(std::vector& buf, size_t buf_offset, const T input[], size_t input_length) { BOTAN_ASSERT_NOMSG(buf_offset <= buf.size()); const size_t to_copy = std::min(input_length, buf.size() - buf_offset); if(to_copy > 0) { copy_mem(&buf[buf_offset], input, to_copy); } return to_copy; } template size_t buffer_insert(std::vector& buf, size_t buf_offset, const std::vector& input) { BOTAN_ASSERT_NOMSG(buf_offset <= buf.size()); const size_t to_copy = std::min(input.size(), buf.size() - buf_offset); if(to_copy > 0) { copy_mem(&buf[buf_offset], input.data(), to_copy); } return to_copy; } /** * XOR arrays. Postcondition out[i] = in[i] ^ out[i] forall i = 0...length * @param out the input/output buffer * @param in the read-only input buffer * @param length the length of the buffers */ inline void xor_buf(uint8_t out[], const uint8_t in[], size_t length) { const size_t blocks = length - (length % 32); for(size_t i = 0; i != blocks; i += 32) { uint64_t x[4]; uint64_t y[4]; typecast_copy(x, out + i, 4); typecast_copy(y, in + i, 4); x[0] ^= y[0]; x[1] ^= y[1]; x[2] ^= y[2]; x[3] ^= y[3]; typecast_copy(out + i, x, 4); } for(size_t i = blocks; i != length; ++i) { out[i] ^= in[i]; } } /** * XOR arrays. Postcondition out[i] = in[i] ^ in2[i] forall i = 0...length * @param out the output buffer * @param in the first input buffer * @param in2 the second output buffer * @param length the length of the three buffers */ inline void xor_buf(uint8_t out[], const uint8_t in[], const uint8_t in2[], size_t length) { const size_t blocks = length - (length % 32); for(size_t i = 0; i != blocks; i += 32) { uint64_t x[4]; uint64_t y[4]; typecast_copy(x, in + i, 4); typecast_copy(y, in2 + i, 4); x[0] ^= y[0]; x[1] ^= y[1]; x[2] ^= y[2]; x[3] ^= y[3]; typecast_copy(out + i, x, 4); } for(size_t i = blocks; i != length; ++i) { out[i] = in[i] ^ in2[i]; } } template void xor_buf(std::vector& out, const std::vector& in, size_t n) { xor_buf(out.data(), in.data(), n); } template void xor_buf(std::vector& out, const uint8_t* in, size_t n) { xor_buf(out.data(), in, n); } template void xor_buf(std::vector& out, const uint8_t* in, const std::vector& in2, size_t n) { xor_buf(out.data(), in, in2.data(), n); } template std::vector& operator^=(std::vector& out, const std::vector& in) { if(out.size() < in.size()) out.resize(in.size()); xor_buf(out.data(), in.data(), in.size()); return out; } } namespace Botan { template class secure_allocator { public: /* * Assert exists to prevent someone from doing something that will * probably crash anyway (like secure_vector where ~non_POD_t * deletes a member pointer which was zeroed before it ran). * MSVC in debug mode uses non-integral proxy types in container types * like std::vector, thus we disable the check there. */ #if !defined(_ITERATOR_DEBUG_LEVEL) || _ITERATOR_DEBUG_LEVEL == 0 static_assert(std::is_integral::value, "secure_allocator supports only integer types"); #endif typedef T value_type; typedef std::size_t size_type; secure_allocator() noexcept = default; secure_allocator(const secure_allocator&) noexcept = default; secure_allocator& operator=(const secure_allocator&) noexcept = default; ~secure_allocator() noexcept = default; template secure_allocator(const secure_allocator&) noexcept {} T* allocate(std::size_t n) { return static_cast(allocate_memory(n, sizeof(T))); } void deallocate(T* p, std::size_t n) { deallocate_memory(p, n, sizeof(T)); } }; template inline bool operator==(const secure_allocator&, const secure_allocator&) { return true; } template inline bool operator!=(const secure_allocator&, const secure_allocator&) { return false; } template using secure_vector = std::vector>; template using secure_deque = std::deque>; // For better compatibility with 1.10 API template using SecureVector = secure_vector; template std::vector unlock(const secure_vector& in) { return std::vector(in.begin(), in.end()); } template std::vector& operator+=(std::vector& out, const std::vector& in) { out.reserve(out.size() + in.size()); out.insert(out.end(), in.begin(), in.end()); return out; } template std::vector& operator+=(std::vector& out, T in) { out.push_back(in); return out; } template std::vector& operator+=(std::vector& out, const std::pair& in) { out.reserve(out.size() + in.second); out.insert(out.end(), in.first, in.first + in.second); return out; } template std::vector& operator+=(std::vector& out, const std::pair& in) { out.reserve(out.size() + in.second); out.insert(out.end(), in.first, in.first + in.second); return out; } /** * Zeroise the values; length remains unchanged * @param vec the vector to zeroise */ template void zeroise(std::vector& vec) { clear_mem(vec.data(), vec.size()); } /** * Zeroise the values then free the memory * @param vec the vector to zeroise and free */ template void zap(std::vector& vec) { zeroise(vec); vec.clear(); vec.shrink_to_fit(); } } namespace Botan { /** * Octet String */ class BOTAN_PUBLIC_API(2,0) OctetString final { public: /** * @return size of this octet string in bytes */ size_t length() const { return m_data.size(); } size_t size() const { return m_data.size(); } /** * @return this object as a secure_vector */ secure_vector bits_of() const { return m_data; } /** * @return start of this string */ const uint8_t* begin() const { return m_data.data(); } /** * @return end of this string */ const uint8_t* end() const { return begin() + m_data.size(); } /** * @return this encoded as hex */ std::string to_string() const; std::string BOTAN_DEPRECATED("Use OctetString::to_string") as_string() const { return this->to_string(); } /** * XOR the contents of another octet string into this one * @param other octet string * @return reference to this */ OctetString& operator^=(const OctetString& other); /** * Force to have odd parity */ void set_odd_parity(); /** * Create a new OctetString * @param str is a hex encoded string */ explicit OctetString(const std::string& str = ""); /** * Create a new random OctetString * @param rng is a random number generator * @param len is the desired length in bytes */ OctetString(class RandomNumberGenerator& rng, size_t len); /** * Create a new OctetString * @param in is an array * @param len is the length of in in bytes */ OctetString(const uint8_t in[], size_t len); /** * Create a new OctetString * @param in a bytestring */ OctetString(const secure_vector& in) : m_data(in) {} /** * Create a new OctetString * @param in a bytestring */ OctetString(const std::vector& in) : m_data(in.begin(), in.end()) {} private: secure_vector m_data; }; /** * Compare two strings * @param x an octet string * @param y an octet string * @return if x is equal to y */ BOTAN_PUBLIC_API(2,0) bool operator==(const OctetString& x, const OctetString& y); /** * Compare two strings * @param x an octet string * @param y an octet string * @return if x is not equal to y */ BOTAN_PUBLIC_API(2,0) bool operator!=(const OctetString& x, const OctetString& y); /** * Concatenate two strings * @param x an octet string * @param y an octet string * @return x concatenated with y */ BOTAN_PUBLIC_API(2,0) OctetString operator+(const OctetString& x, const OctetString& y); /** * XOR two strings * @param x an octet string * @param y an octet string * @return x XORed with y */ BOTAN_PUBLIC_API(2,0) OctetString operator^(const OctetString& x, const OctetString& y); /** * Alternate name for octet string showing intent to use as a key */ using SymmetricKey = OctetString; /** * Alternate name for octet string showing intent to use as an IV */ using InitializationVector = OctetString; } namespace Botan { /** * Represents the length requirements on an algorithm key */ class BOTAN_PUBLIC_API(2,0) Key_Length_Specification final { public: /** * Constructor for fixed length keys * @param keylen the supported key length */ explicit Key_Length_Specification(size_t keylen) : m_min_keylen(keylen), m_max_keylen(keylen), m_keylen_mod(1) { } /** * Constructor for variable length keys * @param min_k the smallest supported key length * @param max_k the largest supported key length * @param k_mod the number of bytes the key must be a multiple of */ Key_Length_Specification(size_t min_k, size_t max_k, size_t k_mod = 1) : m_min_keylen(min_k), m_max_keylen(max_k ? max_k : min_k), m_keylen_mod(k_mod) { } /** * @param length is a key length in bytes * @return true iff this length is a valid length for this algo */ bool valid_keylength(size_t length) const { return ((length >= m_min_keylen) && (length <= m_max_keylen) && (length % m_keylen_mod == 0)); } /** * @return minimum key length in bytes */ size_t minimum_keylength() const { return m_min_keylen; } /** * @return maximum key length in bytes */ size_t maximum_keylength() const { return m_max_keylen; } /** * @return key length multiple in bytes */ size_t keylength_multiple() const { return m_keylen_mod; } /* * Multiplies all length requirements with the given factor * @param n the multiplication factor * @return a key length specification multiplied by the factor */ Key_Length_Specification multiple(size_t n) const { return Key_Length_Specification(n * m_min_keylen, n * m_max_keylen, n * m_keylen_mod); } private: size_t m_min_keylen, m_max_keylen, m_keylen_mod; }; /** * This class represents a symmetric algorithm object. */ class BOTAN_PUBLIC_API(2,0) SymmetricAlgorithm { public: virtual ~SymmetricAlgorithm() = default; /** * Reset the state. */ virtual void clear() = 0; /** * @return object describing limits on key size */ virtual Key_Length_Specification key_spec() const = 0; /** * @return maximum allowed key length */ size_t maximum_keylength() const { return key_spec().maximum_keylength(); } /** * @return minimum allowed key length */ size_t minimum_keylength() const { return key_spec().minimum_keylength(); } /** * Check whether a given key length is valid for this algorithm. * @param length the key length to be checked. * @return true if the key length is valid. */ bool valid_keylength(size_t length) const { return key_spec().valid_keylength(length); } /** * Set the symmetric key of this object. * @param key the SymmetricKey to be set. */ void set_key(const SymmetricKey& key) { set_key(key.begin(), key.length()); } template void set_key(const std::vector& key) { set_key(key.data(), key.size()); } /** * Set the symmetric key of this object. * @param key the to be set as a byte array. * @param length in bytes of key param */ void set_key(const uint8_t key[], size_t length); /** * @return the algorithm name */ virtual std::string name() const = 0; protected: void verify_key_set(bool cond) const { if(cond == false) throw_key_not_set_error(); } private: void throw_key_not_set_error() const; /** * Run the key schedule * @param key the key * @param length of key */ virtual void key_schedule(const uint8_t key[], size_t length) = 0; }; } namespace Botan { /** * Different types of errors that might occur */ enum class ErrorType { /** Some unknown error */ Unknown = 1, /** An error while calling a system interface */ SystemError, /** An operation seems valid, but not supported by the current version */ NotImplemented, /** Memory allocation failure */ OutOfMemory, /** An internal error occurred */ InternalError, /** An I/O error occurred */ IoError, /** Invalid object state */ InvalidObjectState = 100, /** A key was not set on an object when this is required */ KeyNotSet, /** The application provided an argument which is invalid */ InvalidArgument, /** A key with invalid length was provided */ InvalidKeyLength, /** A nonce with invalid length was provided */ InvalidNonceLength, /** An object type was requested but cannot be found */ LookupError, /** Encoding a message or datum failed */ EncodingFailure, /** Decoding a message or datum failed */ DecodingFailure, /** A TLS error (error_code will be the alert type) */ TLSError, /** An error during an HTTP operation */ HttpError, /** A message with an invalid authentication tag was detected */ InvalidTag, /** An error during Roughtime validation */ RoughtimeError, /** An error when calling OpenSSL */ OpenSSLError = 200, /** An error when interacting with CommonCrypto API */ CommonCryptoError, /** An error when interacting with a PKCS11 device */ Pkcs11Error, /** An error when interacting with a TPM device */ TPMError, /** An error when interacting with a database */ DatabaseError, /** An error when interacting with zlib */ ZlibError = 300, /** An error when interacting with bzip2 */ Bzip2Error, /** An error when interacting with lzma */ LzmaError, }; //! \brief Convert an ErrorType to string std::string BOTAN_PUBLIC_API(2,11) to_string(ErrorType type); /** * Base class for all exceptions thrown by the library */ class BOTAN_PUBLIC_API(2,0) Exception : public std::exception { public: /** * Return a descriptive string which is hopefully comprehensible to * a developer. It will likely not be useful for an end user. * * The string has no particular format, and the content of exception * messages may change from release to release. Thus the main use of this * function is for logging or debugging. */ const char* what() const noexcept override { return m_msg.c_str(); } /** * Return the "type" of error which occurred. */ virtual ErrorType error_type() const noexcept { return Botan::ErrorType::Unknown; } /** * Return an error code associated with this exception, or otherwise 0. * * The domain of this error varies depending on the source, for example on * POSIX systems it might be errno, while on a Windows system it might be * the result of GetLastError or WSAGetLastError. For error_type() is * OpenSSLError, it will (if nonzero) be an OpenSSL error code from * ERR_get_error. */ virtual int error_code() const noexcept { return 0; } /** * Avoid throwing base Exception, use a subclass */ explicit Exception(const std::string& msg); /** * Avoid throwing base Exception, use a subclass */ Exception(const char* prefix, const std::string& msg); /** * Avoid throwing base Exception, use a subclass */ Exception(const std::string& msg, const std::exception& e); private: std::string m_msg; }; /** * An invalid argument was provided to an API call. */ class BOTAN_PUBLIC_API(2,0) Invalid_Argument : public Exception { public: explicit Invalid_Argument(const std::string& msg); explicit Invalid_Argument(const std::string& msg, const std::string& where); Invalid_Argument(const std::string& msg, const std::exception& e); ErrorType error_type() const noexcept override { return ErrorType::InvalidArgument; } }; /** * An invalid key length was used */ class BOTAN_PUBLIC_API(2,0) Invalid_Key_Length final : public Invalid_Argument { public: Invalid_Key_Length(const std::string& name, size_t length); ErrorType error_type() const noexcept override { return ErrorType::InvalidKeyLength; } }; /** * An invalid nonce length was used */ class BOTAN_PUBLIC_API(2,0) Invalid_IV_Length final : public Invalid_Argument { public: Invalid_IV_Length(const std::string& mode, size_t bad_len); ErrorType error_type() const noexcept override { return ErrorType::InvalidNonceLength; } }; /** * Invalid_Algorithm_Name Exception */ class BOTAN_PUBLIC_API(2,0) Invalid_Algorithm_Name final : public Invalid_Argument { public: explicit Invalid_Algorithm_Name(const std::string& name); }; /** * Encoding_Error Exception * * This exception derives from Invalid_Argument for historical reasons, and it * does not make any real sense for it to do so. In a future major release this * exception type will derive directly from Exception instead. */ class BOTAN_PUBLIC_API(2,0) Encoding_Error final : public Invalid_Argument { public: explicit Encoding_Error(const std::string& name); ErrorType error_type() const noexcept override { return ErrorType::EncodingFailure; } }; /** * A decoding error occurred. * * This exception derives from Invalid_Argument for historical reasons, and it * does not make any real sense for it to do so. In a future major release this * exception type will derive directly from Exception instead. */ class BOTAN_PUBLIC_API(2,0) Decoding_Error : public Invalid_Argument { public: explicit Decoding_Error(const std::string& name); Decoding_Error(const std::string& name, const char* exception_message); Decoding_Error(const std::string& msg, const std::exception& e); ErrorType error_type() const noexcept override { return ErrorType::DecodingFailure; } }; /** * Invalid state was encountered. A request was made on an object while the * object was in a state where the operation cannot be performed. */ class BOTAN_PUBLIC_API(2,0) Invalid_State : public Exception { public: explicit Invalid_State(const std::string& err) : Exception(err) {} ErrorType error_type() const noexcept override { return ErrorType::InvalidObjectState; } }; /** * A PRNG was called on to produce output while still unseeded */ class BOTAN_PUBLIC_API(2,0) PRNG_Unseeded final : public Invalid_State { public: explicit PRNG_Unseeded(const std::string& algo); }; /** * The key was not set on an object. This occurs with symmetric objects where * an operation which requires the key is called prior to set_key being called. */ class BOTAN_PUBLIC_API(2,4) Key_Not_Set : public Invalid_State { public: explicit Key_Not_Set(const std::string& algo); ErrorType error_type() const noexcept override { return ErrorType::KeyNotSet; } }; /** * A request was made for some kind of object which could not be located */ class BOTAN_PUBLIC_API(2,0) Lookup_Error : public Exception { public: explicit Lookup_Error(const std::string& err) : Exception(err) {} Lookup_Error(const std::string& type, const std::string& algo, const std::string& provider); ErrorType error_type() const noexcept override { return ErrorType::LookupError; } }; /** * Algorithm_Not_Found Exception * * @warning This exception type will be removed in the future. Instead * just catch Lookup_Error. */ class BOTAN_PUBLIC_API(2,0) Algorithm_Not_Found final : public Lookup_Error { public: explicit Algorithm_Not_Found(const std::string& name); }; /** * Provider_Not_Found is thrown when a specific provider was requested * but that provider is not available. * * @warning This exception type will be removed in the future. Instead * just catch Lookup_Error. */ class BOTAN_PUBLIC_API(2,0) Provider_Not_Found final : public Lookup_Error { public: Provider_Not_Found(const std::string& algo, const std::string& provider); }; /** * An AEAD or MAC check detected a message modification * * In versions before 2.10, Invalid_Authentication_Tag was named * Integrity_Failure, it was renamed to make its usage more clear. */ class BOTAN_PUBLIC_API(2,0) Invalid_Authentication_Tag final : public Exception { public: explicit Invalid_Authentication_Tag(const std::string& msg); ErrorType error_type() const noexcept override { return ErrorType::InvalidTag; } }; /** * For compatability with older versions */ typedef Invalid_Authentication_Tag Integrity_Failure; /** * An error occurred while operating on an IO stream */ class BOTAN_PUBLIC_API(2,0) Stream_IO_Error final : public Exception { public: explicit Stream_IO_Error(const std::string& err); ErrorType error_type() const noexcept override { return ErrorType::IoError; } }; /** * System_Error * * This exception is thrown in the event of an error related to interacting * with the operating system. * * This exception type also (optionally) captures an integer error code eg * POSIX errno or Windows GetLastError. */ class BOTAN_PUBLIC_API(2,9) System_Error : public Exception { public: System_Error(const std::string& msg) : Exception(msg), m_error_code(0) {} System_Error(const std::string& msg, int err_code); ErrorType error_type() const noexcept override { return ErrorType::SystemError; } int error_code() const noexcept override { return m_error_code; } private: int m_error_code; }; /** * An internal error occurred. If observed, please file a bug. */ class BOTAN_PUBLIC_API(2,0) Internal_Error : public Exception { public: explicit Internal_Error(const std::string& err); ErrorType error_type() const noexcept override { return ErrorType::InternalError; } }; /** * Not Implemented Exception * * This is thrown in the situation where a requested operation is * logically valid but is not implemented by this version of the library. */ class BOTAN_PUBLIC_API(2,0) Not_Implemented final : public Exception { public: explicit Not_Implemented(const std::string& err); ErrorType error_type() const noexcept override { return ErrorType::NotImplemented; } }; /* The following exception types are still in use for compatability reasons, but are deprecated and will be removed in a future major release. Instead catch the base class. */ /** * An invalid OID string was used. * * This exception will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) Invalid_OID final : public Decoding_Error { public: explicit Invalid_OID(const std::string& oid); }; /* The following exception types are deprecated, no longer used, and will be removed in a future major release */ /** * Self Test Failure Exception * * This exception is no longer used. It will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) Self_Test_Failure final : public Internal_Error { public: BOTAN_DEPRECATED("no longer used") explicit Self_Test_Failure(const std::string& err); }; /** * No_Provider_Found Exception * * This exception is no longer used. It will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) No_Provider_Found final : public Exception { public: BOTAN_DEPRECATED("no longer used") explicit No_Provider_Found(const std::string& name); }; /** * Policy_Violation Exception * * This exception is no longer used. It will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) Policy_Violation final : public Invalid_State { public: BOTAN_DEPRECATED("no longer used") explicit Policy_Violation(const std::string& err); }; /** * Unsupported_Argument Exception * * An argument that is invalid because it is not supported by Botan. * It might or might not be valid in another context like a standard. * * This exception is no longer used, instead Not_Implemented is thrown. * It will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) Unsupported_Argument final : public Invalid_Argument { public: BOTAN_DEPRECATED("no longer used") explicit Unsupported_Argument(const std::string& msg) : Invalid_Argument(msg) {} }; template inline void do_throw_error(const char* file, int line, const char* func, Args... args) { throw E(file, line, func, args...); } } namespace Botan { /** * The two possible directions for cipher filters, determining whether they * actually perform encryption or decryption. */ enum Cipher_Dir : int { ENCRYPTION, DECRYPTION }; /** * Interface for cipher modes */ class BOTAN_PUBLIC_API(2,0) Cipher_Mode : public SymmetricAlgorithm { public: /** * @return list of available providers for this algorithm, empty if not available * @param algo_spec algorithm name */ static std::vector providers(const std::string& algo_spec); /** * Create an AEAD mode * @param algo the algorithm to create * @param direction specify if this should be an encryption or decryption AEAD * @param provider optional specification for provider to use * @return an AEAD mode or a null pointer if not available */ static std::unique_ptr create(const std::string& algo, Cipher_Dir direction, const std::string& provider = ""); /** * Create an AEAD mode, or throw * @param algo the algorithm to create * @param direction specify if this should be an encryption or decryption AEAD * @param provider optional specification for provider to use * @return an AEAD mode, or throw an exception */ static std::unique_ptr create_or_throw(const std::string& algo, Cipher_Dir direction, const std::string& provider = ""); /* * Prepare for processing a message under the specified nonce */ virtual void start_msg(const uint8_t nonce[], size_t nonce_len) = 0; /** * Begin processing a message. * @param nonce the per message nonce */ template void start(const std::vector& nonce) { start_msg(nonce.data(), nonce.size()); } /** * Begin processing a message. * @param nonce the per message nonce * @param nonce_len length of nonce */ void start(const uint8_t nonce[], size_t nonce_len) { start_msg(nonce, nonce_len); } /** * Begin processing a message. */ void start() { return start_msg(nullptr, 0); } /** * Process message blocks * * Input must be a multiple of update_granularity * * Processes msg in place and returns bytes written. Normally * this will be either msg_len (indicating the entire message was * processed) or for certain AEAD modes zero (indicating that the * mode requires the entire message be processed in one pass). * * @param msg the message to be processed * @param msg_len length of the message in bytes */ virtual size_t process(uint8_t msg[], size_t msg_len) = 0; /** * Process some data. Input must be in size update_granularity() uint8_t blocks. * @param buffer in/out parameter which will possibly be resized * @param offset an offset into blocks to begin processing */ void update(secure_vector& buffer, size_t offset = 0) { BOTAN_ASSERT(buffer.size() >= offset, "Offset ok"); uint8_t* buf = buffer.data() + offset; const size_t buf_size = buffer.size() - offset; const size_t written = process(buf, buf_size); buffer.resize(offset + written); } /** * Complete processing of a message. * * @param final_block in/out parameter which must be at least * minimum_final_size() bytes, and will be set to any final output * @param offset an offset into final_block to begin processing */ virtual void finish(secure_vector& final_block, size_t offset = 0) = 0; /** * Returns the size of the output if this transform is used to process a * message with input_length bytes. In most cases the answer is precise. * If it is not possible to precise (namely for CBC decryption) instead a * lower bound is returned. */ virtual size_t output_length(size_t input_length) const = 0; /** * @return size of required blocks to update */ virtual size_t update_granularity() const = 0; /** * @return required minimium size to finalize() - may be any * length larger than this. */ virtual size_t minimum_final_size() const = 0; /** * @return the default size for a nonce */ virtual size_t default_nonce_length() const = 0; /** * @return true iff nonce_len is a valid length for the nonce */ virtual bool valid_nonce_length(size_t nonce_len) const = 0; /** * Resets just the message specific state and allows encrypting again under the existing key */ virtual void reset() = 0; /** * @return true iff this mode provides authentication as well as * confidentiality. */ virtual bool authenticated() const { return false; } /** * @return the size of the authentication tag used (in bytes) */ virtual size_t tag_size() const { return 0; } /** * @return provider information about this implementation. Default is "base", * might also return "sse2", "avx2", "openssl", or some other arbitrary string. */ virtual std::string provider() const { return "base"; } }; /** * Get a cipher mode by name (eg "AES-128/CBC" or "Serpent/XTS") * @param algo_spec cipher name * @param direction ENCRYPTION or DECRYPTION * @param provider provider implementation to choose */ inline Cipher_Mode* get_cipher_mode(const std::string& algo_spec, Cipher_Dir direction, const std::string& provider = "") { return Cipher_Mode::create(algo_spec, direction, provider).release(); } } namespace Botan { /** * Interface for AEAD (Authenticated Encryption with Associated Data) * modes. These modes provide both encryption and message * authentication, and can authenticate additional per-message data * which is not included in the ciphertext (for instance a sequence * number). */ class BOTAN_PUBLIC_API(2,0) AEAD_Mode : public Cipher_Mode { public: /** * Create an AEAD mode * @param algo the algorithm to create * @param direction specify if this should be an encryption or decryption AEAD * @param provider optional specification for provider to use * @return an AEAD mode or a null pointer if not available */ static std::unique_ptr create(const std::string& algo, Cipher_Dir direction, const std::string& provider = ""); /** * Create an AEAD mode, or throw * @param algo the algorithm to create * @param direction specify if this should be an encryption or decryption AEAD * @param provider optional specification for provider to use * @return an AEAD mode, or throw an exception */ static std::unique_ptr create_or_throw(const std::string& algo, Cipher_Dir direction, const std::string& provider = ""); bool authenticated() const override { return true; } /** * Set associated data that is not included in the ciphertext but * that should be authenticated. Must be called after set_key and * before start. * * Unless reset by another call, the associated data is kept * between messages. Thus, if the AD does not change, calling * once (after set_key) is the optimum. * * @param ad the associated data * @param ad_len length of add in bytes */ virtual void set_associated_data(const uint8_t ad[], size_t ad_len) = 0; /** * Set associated data that is not included in the ciphertext but * that should be authenticated. Must be called after set_key and * before start. * * Unless reset by another call, the associated data is kept * between messages. Thus, if the AD does not change, calling * once (after set_key) is the optimum. * * Some AEADs (namely SIV) support multiple AD inputs. For * all other modes only nominal AD input 0 is supported; all * other values of i will cause an exception. * * @param ad the associated data * @param ad_len length of add in bytes */ virtual void set_associated_data_n(size_t i, const uint8_t ad[], size_t ad_len); /** * Returns the maximum supported number of associated data inputs which * can be provided to set_associated_data_n * * If returns 0, then no associated data is supported. */ virtual size_t maximum_associated_data_inputs() const { return 1; } /** * Most AEADs require the key to be set prior to setting the AD * A few allow the AD to be set even before the cipher is keyed. * Such ciphers would return false from this function. */ virtual bool associated_data_requires_key() const { return true; } /** * Set associated data that is not included in the ciphertext but * that should be authenticated. Must be called after set_key and * before start. * * See @ref set_associated_data(). * * @param ad the associated data */ template void set_associated_data_vec(const std::vector& ad) { set_associated_data(ad.data(), ad.size()); } /** * Set associated data that is not included in the ciphertext but * that should be authenticated. Must be called after set_key and * before start. * * See @ref set_associated_data(). * * @param ad the associated data */ template void set_ad(const std::vector& ad) { set_associated_data(ad.data(), ad.size()); } /** * @return default AEAD nonce size (a commonly supported value among AEAD * modes, and large enough that random collisions are unlikely) */ size_t default_nonce_length() const override { return 12; } virtual ~AEAD_Mode() = default; }; /** * Get an AEAD mode by name (eg "AES-128/GCM" or "Serpent/EAX") * @param name AEAD name * @param direction ENCRYPTION or DECRYPTION */ inline AEAD_Mode* get_aead(const std::string& name, Cipher_Dir direction) { return AEAD_Mode::create(name, direction, "").release(); } } namespace Botan { /** * This class represents a block cipher object. */ class BOTAN_PUBLIC_API(2,0) BlockCipher : public SymmetricAlgorithm { public: /** * Create an instance based on a name * If provider is empty then best available is chosen. * @param algo_spec algorithm name * @param provider provider implementation to choose * @return a null pointer if the algo/provider combination cannot be found */ static std::unique_ptr create(const std::string& algo_spec, const std::string& provider = ""); /** * Create an instance based on a name, or throw if the * algo/provider combination cannot be found. If provider is * empty then best available is chosen. */ static std::unique_ptr create_or_throw(const std::string& algo_spec, const std::string& provider = ""); /** * @return list of available providers for this algorithm, empty if not available * @param algo_spec algorithm name */ static std::vector providers(const std::string& algo_spec); /** * @return block size of this algorithm */ virtual size_t block_size() const = 0; /** * @return native parallelism of this cipher in blocks */ virtual size_t parallelism() const { return 1; } /** * @return prefererred parallelism of this cipher in bytes */ size_t parallel_bytes() const { return parallelism() * block_size() * BOTAN_BLOCK_CIPHER_PAR_MULT; } /** * @return provider information about this implementation. Default is "base", * might also return "sse2", "avx2", "openssl", or some other arbitrary string. */ virtual std::string provider() const { return "base"; } /** * Encrypt a block. * @param in The plaintext block to be encrypted as a byte array. * Must be of length block_size(). * @param out The byte array designated to hold the encrypted block. * Must be of length block_size(). */ void encrypt(const uint8_t in[], uint8_t out[]) const { encrypt_n(in, out, 1); } /** * Decrypt a block. * @param in The ciphertext block to be decypted as a byte array. * Must be of length block_size(). * @param out The byte array designated to hold the decrypted block. * Must be of length block_size(). */ void decrypt(const uint8_t in[], uint8_t out[]) const { decrypt_n(in, out, 1); } /** * Encrypt a block. * @param block the plaintext block to be encrypted * Must be of length block_size(). Will hold the result when the function * has finished. */ void encrypt(uint8_t block[]) const { encrypt_n(block, block, 1); } /** * Decrypt a block. * @param block the ciphertext block to be decrypted * Must be of length block_size(). Will hold the result when the function * has finished. */ void decrypt(uint8_t block[]) const { decrypt_n(block, block, 1); } /** * Encrypt one or more blocks * @param block the input/output buffer (multiple of block_size()) */ template void encrypt(std::vector& block) const { return encrypt_n(block.data(), block.data(), block.size() / block_size()); } /** * Decrypt one or more blocks * @param block the input/output buffer (multiple of block_size()) */ template void decrypt(std::vector& block) const { return decrypt_n(block.data(), block.data(), block.size() / block_size()); } /** * Encrypt one or more blocks * @param in the input buffer (multiple of block_size()) * @param out the output buffer (same size as in) */ template void encrypt(const std::vector& in, std::vector& out) const { return encrypt_n(in.data(), out.data(), in.size() / block_size()); } /** * Decrypt one or more blocks * @param in the input buffer (multiple of block_size()) * @param out the output buffer (same size as in) */ template void decrypt(const std::vector& in, std::vector& out) const { return decrypt_n(in.data(), out.data(), in.size() / block_size()); } /** * Encrypt one or more blocks * @param in the input buffer (multiple of block_size()) * @param out the output buffer (same size as in) * @param blocks the number of blocks to process */ virtual void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const = 0; /** * Decrypt one or more blocks * @param in the input buffer (multiple of block_size()) * @param out the output buffer (same size as in) * @param blocks the number of blocks to process */ virtual void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const = 0; virtual void encrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const { const size_t BS = block_size(); xor_buf(data, mask, blocks * BS); encrypt_n(data, data, blocks); xor_buf(data, mask, blocks * BS); } virtual void decrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const { const size_t BS = block_size(); xor_buf(data, mask, blocks * BS); decrypt_n(data, data, blocks); xor_buf(data, mask, blocks * BS); } /** * @return new object representing the same algorithm as *this */ virtual BlockCipher* clone() const = 0; virtual ~BlockCipher() = default; }; /** * Tweakable block ciphers allow setting a tweak which is a non-keyed * value which affects the encryption/decryption operation. */ class BOTAN_PUBLIC_API(2,8) Tweakable_Block_Cipher : public BlockCipher { public: /** * Set the tweak value. This must be called after setting a key. The value * persists until either set_tweak, set_key, or clear is called. * Different algorithms support different tweak length(s). If called with * an unsupported length, Invalid_Argument will be thrown. */ virtual void set_tweak(const uint8_t tweak[], size_t len) = 0; }; /** * Represents a block cipher with a single fixed block size */ template class Block_Cipher_Fixed_Params : public BaseClass { public: enum { BLOCK_SIZE = BS }; size_t block_size() const final override { return BS; } // override to take advantage of compile time constant block size void encrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const final override { xor_buf(data, mask, blocks * BS); this->encrypt_n(data, data, blocks); xor_buf(data, mask, blocks * BS); } void decrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const final override { xor_buf(data, mask, blocks * BS); this->decrypt_n(data, data, blocks); xor_buf(data, mask, blocks * BS); } Key_Length_Specification key_spec() const final override { return Key_Length_Specification(KMIN, KMAX, KMOD); } }; } BOTAN_FUTURE_INTERNAL_HEADER(aes.h) namespace Botan { /** * AES-128 */ class BOTAN_PUBLIC_API(2,0) AES_128 final : public Block_Cipher_Fixed_Params<16, 16> { public: void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void clear() override; std::string provider() const override; std::string name() const override { return "AES-128"; } BlockCipher* clone() const override { return new AES_128; } size_t parallelism() const override; private: void key_schedule(const uint8_t key[], size_t length) override; #if defined(BOTAN_HAS_AES_VPERM) void vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_NI) void aesni_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_POWER8) || defined(BOTAN_HAS_AES_ARMV8) || defined(BOTAN_HAS_AES_NI) void hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; #endif secure_vector m_EK, m_DK; }; /** * AES-192 */ class BOTAN_PUBLIC_API(2,0) AES_192 final : public Block_Cipher_Fixed_Params<16, 24> { public: void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void clear() override; std::string provider() const override; std::string name() const override { return "AES-192"; } BlockCipher* clone() const override { return new AES_192; } size_t parallelism() const override; private: #if defined(BOTAN_HAS_AES_VPERM) void vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_NI) void aesni_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_POWER8) || defined(BOTAN_HAS_AES_ARMV8) || defined(BOTAN_HAS_AES_NI) void hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; #endif void key_schedule(const uint8_t key[], size_t length) override; secure_vector m_EK, m_DK; }; /** * AES-256 */ class BOTAN_PUBLIC_API(2,0) AES_256 final : public Block_Cipher_Fixed_Params<16, 32> { public: void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void clear() override; std::string provider() const override; std::string name() const override { return "AES-256"; } BlockCipher* clone() const override { return new AES_256; } size_t parallelism() const override; private: #if defined(BOTAN_HAS_AES_VPERM) void vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_NI) void aesni_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_POWER8) || defined(BOTAN_HAS_AES_ARMV8) || defined(BOTAN_HAS_AES_NI) void hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; #endif void key_schedule(const uint8_t key[], size_t length) override; secure_vector m_EK, m_DK; }; } #if defined(BOTAN_BUILD_COMPILER_IS_MSVC) #include #endif BOTAN_FUTURE_INTERNAL_HEADER(bswap.h) namespace Botan { /** * Swap a 16 bit integer */ inline uint16_t reverse_bytes(uint16_t val) { #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || defined(BOTAN_BUILD_COMPILER_IS_CLANG) || defined(BOTAN_BUILD_COMPILER_IS_XLC) return __builtin_bswap16(val); #else return static_cast((val << 8) | (val >> 8)); #endif } /** * Swap a 32 bit integer */ inline uint32_t reverse_bytes(uint32_t val) { #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || defined(BOTAN_BUILD_COMPILER_IS_CLANG) || defined(BOTAN_BUILD_COMPILER_IS_XLC) return __builtin_bswap32(val); #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC) return _byteswap_ulong(val); #elif defined(BOTAN_USE_GCC_INLINE_ASM) && defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) // GCC-style inline assembly for x86 or x86-64 asm("bswapl %0" : "=r" (val) : "0" (val)); return val; #else // Generic implementation uint16_t hi = static_cast(val >> 16); uint16_t lo = static_cast(val); hi = reverse_bytes(hi); lo = reverse_bytes(lo); return (static_cast(lo) << 16) | hi; #endif } /** * Swap a 64 bit integer */ inline uint64_t reverse_bytes(uint64_t val) { #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || defined(BOTAN_BUILD_COMPILER_IS_CLANG) || defined(BOTAN_BUILD_COMPILER_IS_XLC) return __builtin_bswap64(val); #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC) return _byteswap_uint64(val); #elif defined(BOTAN_USE_GCC_INLINE_ASM) && defined(BOTAN_TARGET_ARCH_IS_X86_64) // GCC-style inline assembly for x86-64 asm("bswapq %0" : "=r" (val) : "0" (val)); return val; #else /* Generic implementation. Defined in terms of 32-bit bswap so any * optimizations in that version can help. */ uint32_t hi = static_cast(val >> 32); uint32_t lo = static_cast(val); hi = reverse_bytes(hi); lo = reverse_bytes(lo); return (static_cast(lo) << 32) | hi; #endif } /** * Swap 4 Ts in an array */ template inline void bswap_4(T x[4]) { x[0] = reverse_bytes(x[0]); x[1] = reverse_bytes(x[1]); x[2] = reverse_bytes(x[2]); x[3] = reverse_bytes(x[3]); } } namespace Botan { /** * This class represents any kind of computation which uses an internal * state, such as hash functions or MACs */ class BOTAN_PUBLIC_API(2,0) Buffered_Computation { public: /** * @return length of the output of this function in bytes */ virtual size_t output_length() const = 0; /** * Add new input to process. * @param in the input to process as a byte array * @param length of param in in bytes */ void update(const uint8_t in[], size_t length) { add_data(in, length); } /** * Add new input to process. * @param in the input to process as a secure_vector */ void update(const secure_vector& in) { add_data(in.data(), in.size()); } /** * Add new input to process. * @param in the input to process as a std::vector */ void update(const std::vector& in) { add_data(in.data(), in.size()); } void update_be(uint16_t val); void update_be(uint32_t val); void update_be(uint64_t val); void update_le(uint16_t val); void update_le(uint32_t val); void update_le(uint64_t val); /** * Add new input to process. * @param str the input to process as a std::string. Will be interpreted * as a byte array based on the strings encoding. */ void update(const std::string& str) { add_data(cast_char_ptr_to_uint8(str.data()), str.size()); } /** * Process a single byte. * @param in the byte to process */ void update(uint8_t in) { add_data(&in, 1); } /** * Complete the computation and retrieve the * final result. * @param out The byte array to be filled with the result. * Must be of length output_length() */ void final(uint8_t out[]) { final_result(out); } /** * Complete the computation and retrieve the * final result. * @return secure_vector holding the result */ secure_vector final() { secure_vector output(output_length()); final_result(output.data()); return output; } std::vector final_stdvec() { std::vector output(output_length()); final_result(output.data()); return output; } template void final(std::vector& out) { out.resize(output_length()); final_result(out.data()); } /** * Update and finalize computation. Does the same as calling update() * and final() consecutively. * @param in the input to process as a byte array * @param length the length of the byte array * @result the result of the call to final() */ secure_vector process(const uint8_t in[], size_t length) { add_data(in, length); return final(); } /** * Update and finalize computation. Does the same as calling update() * and final() consecutively. * @param in the input to process * @result the result of the call to final() */ secure_vector process(const secure_vector& in) { add_data(in.data(), in.size()); return final(); } /** * Update and finalize computation. Does the same as calling update() * and final() consecutively. * @param in the input to process * @result the result of the call to final() */ secure_vector process(const std::vector& in) { add_data(in.data(), in.size()); return final(); } /** * Update and finalize computation. Does the same as calling update() * and final() consecutively. * @param in the input to process as a string * @result the result of the call to final() */ secure_vector process(const std::string& in) { update(in); return final(); } virtual ~Buffered_Computation() = default; private: /** * Add more data to the computation * @param input is an input buffer * @param length is the length of input in bytes */ virtual void add_data(const uint8_t input[], size_t length) = 0; /** * Write the final output to out * @param out is an output buffer of output_length() */ virtual void final_result(uint8_t out[]) = 0; }; } namespace Botan { /** * Struct representing a particular date and time */ class BOTAN_PUBLIC_API(2,0) calendar_point { public: /** The year */ uint32_t get_year() const { return year; } /** The month, 1 through 12 for Jan to Dec */ uint32_t get_month() const { return month; } /** The day of the month, 1 through 31 (or 28 or 30 based on month */ uint32_t get_day() const { return day; } /** Hour in 24-hour form, 0 to 23 */ uint32_t get_hour() const { return hour; } /** Minutes in the hour, 0 to 60 */ uint32_t get_minutes() const { return minutes; } /** Seconds in the minute, 0 to 60, but might be slightly larger to deal with leap seconds on some systems */ uint32_t get_seconds() const { return seconds; } /** * Initialize a calendar_point * @param y the year * @param mon the month * @param d the day * @param h the hour * @param min the minute * @param sec the second */ calendar_point(uint32_t y, uint32_t mon, uint32_t d, uint32_t h, uint32_t min, uint32_t sec) : year(y), month(mon), day(d), hour(h), minutes(min), seconds(sec) {} /** * Returns an STL timepoint object */ std::chrono::system_clock::time_point to_std_timepoint() const; /** * Returns a human readable string of the struct's components. * Formatting might change over time. Currently it is RFC339 'iso-date-time'. */ std::string to_string() const; BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES: /* The member variables are public for historical reasons. Use the get_xxx() functions defined above. These members will be made private in a future major release. */ uint32_t year; uint32_t month; uint32_t day; uint32_t hour; uint32_t minutes; uint32_t seconds; }; /** * Convert a time_point to a calendar_point * @param time_point a time point from the system clock * @return calendar_point object representing this time point */ BOTAN_PUBLIC_API(2,0) calendar_point calendar_value( const std::chrono::system_clock::time_point& time_point); } BOTAN_FUTURE_INTERNAL_HEADER(charset.h) namespace Botan { /** * Convert a sequence of UCS-2 (big endian) characters to a UTF-8 string * This is used for ASN.1 BMPString type * @param ucs2 the sequence of UCS-2 characters * @param len length of ucs2 in bytes, must be a multiple of 2 */ std::string BOTAN_UNSTABLE_API ucs2_to_utf8(const uint8_t ucs2[], size_t len); /** * Convert a sequence of UCS-4 (big endian) characters to a UTF-8 string * This is used for ASN.1 UniversalString type * @param ucs4 the sequence of UCS-4 characters * @param len length of ucs4 in bytes, must be a multiple of 4 */ std::string BOTAN_UNSTABLE_API ucs4_to_utf8(const uint8_t ucs4[], size_t len); /** * Convert a UTF-8 string to Latin-1 * If a character outside the Latin-1 range is encountered, an exception is thrown. */ std::string BOTAN_UNSTABLE_API utf8_to_latin1(const std::string& utf8); /** * The different charsets (nominally) supported by Botan. */ enum Character_Set { LOCAL_CHARSET, UCS2_CHARSET, UTF8_CHARSET, LATIN1_CHARSET }; namespace Charset { /* * Character set conversion - avoid this. * For specific conversions, use the functions above like * ucs2_to_utf8 and utf8_to_latin1 * * If you need something more complex than that, use a real library * such as iconv, Boost.Locale, or ICU */ std::string BOTAN_PUBLIC_API(2,0) BOTAN_DEPRECATED("Avoid. See comment in header.") transcode(const std::string& str, Character_Set to, Character_Set from); /* * Simple character classifier functions */ bool BOTAN_PUBLIC_API(2,0) is_digit(char c); bool BOTAN_PUBLIC_API(2,0) is_space(char c); bool BOTAN_PUBLIC_API(2,0) caseless_cmp(char x, char y); uint8_t BOTAN_PUBLIC_API(2,0) char2digit(char c); char BOTAN_PUBLIC_API(2,0) digit2char(uint8_t b); } } BOTAN_FUTURE_INTERNAL_HEADER(cpuid.h) namespace Botan { /** * A class handling runtime CPU feature detection. It is limited to * just the features necessary to implement CPU specific code in Botan, * rather than being a general purpose utility. * * This class supports: * * - x86 features using CPUID. x86 is also the only processor with * accurate cache line detection currently. * * - PowerPC AltiVec detection on Linux, NetBSD, OpenBSD, and macOS * * - ARM NEON and crypto extensions detection. On Linux and Android * systems which support getauxval, that is used to access CPU * feature information. Otherwise a relatively portable but * thread-unsafe mechanism involving executing probe functions which * catching SIGILL signal is used. */ class BOTAN_PUBLIC_API(2,1) CPUID final { public: /** * Probe the CPU and see what extensions are supported */ static void initialize(); static bool has_simd_32(); /** * Deprecated equivalent to * o << "CPUID flags: " << CPUID::to_string() << "\n"; */ BOTAN_DEPRECATED("Use CPUID::to_string") static void print(std::ostream& o); /** * Return a possibly empty string containing list of known CPU * extensions. Each name will be seperated by a space, and the ordering * will be arbitrary. This list only contains values that are useful to * Botan (for example FMA instructions are not checked). * * Example outputs "sse2 ssse3 rdtsc", "neon arm_aes", "altivec" */ static std::string to_string(); /** * Return a best guess of the cache line size */ static size_t cache_line_size() { return state().cache_line_size(); } static bool is_little_endian() { #if defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) return true; #elif defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) return false; #else return state().endian_status() == Endian_Status::Little; #endif } static bool is_big_endian() { #if defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) return true; #elif defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) return false; #else return state().endian_status() == Endian_Status::Big; #endif } enum CPUID_bits : uint64_t { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) // These values have no relation to cpuid bitfields // SIMD instruction sets CPUID_SSE2_BIT = (1ULL << 0), CPUID_SSSE3_BIT = (1ULL << 1), CPUID_SSE41_BIT = (1ULL << 2), CPUID_SSE42_BIT = (1ULL << 3), CPUID_AVX2_BIT = (1ULL << 4), CPUID_AVX512F_BIT = (1ULL << 5), CPUID_AVX512DQ_BIT = (1ULL << 6), CPUID_AVX512BW_BIT = (1ULL << 7), // Ice Lake profile: AVX-512 F, DQ, BW, IFMA, VBMI, VBMI2, BITALG CPUID_AVX512_ICL_BIT = (1ULL << 11), // Crypto-specific ISAs CPUID_AESNI_BIT = (1ULL << 16), CPUID_CLMUL_BIT = (1ULL << 17), CPUID_RDRAND_BIT = (1ULL << 18), CPUID_RDSEED_BIT = (1ULL << 19), CPUID_SHA_BIT = (1ULL << 20), CPUID_AVX512_AES_BIT = (1ULL << 21), CPUID_AVX512_CLMUL_BIT = (1ULL << 22), // Misc useful instructions CPUID_RDTSC_BIT = (1ULL << 48), CPUID_ADX_BIT = (1ULL << 49), CPUID_BMI1_BIT = (1ULL << 50), CPUID_BMI2_BIT = (1ULL << 51), #endif #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) CPUID_ALTIVEC_BIT = (1ULL << 0), CPUID_POWER_CRYPTO_BIT = (1ULL << 1), CPUID_DARN_BIT = (1ULL << 2), #endif #if defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) CPUID_ARM_NEON_BIT = (1ULL << 0), CPUID_ARM_SVE_BIT = (1ULL << 1), CPUID_ARM_AES_BIT = (1ULL << 16), CPUID_ARM_PMULL_BIT = (1ULL << 17), CPUID_ARM_SHA1_BIT = (1ULL << 18), CPUID_ARM_SHA2_BIT = (1ULL << 19), CPUID_ARM_SHA3_BIT = (1ULL << 20), CPUID_ARM_SHA2_512_BIT = (1ULL << 21), CPUID_ARM_SM3_BIT = (1ULL << 22), CPUID_ARM_SM4_BIT = (1ULL << 23), #endif CPUID_INITIALIZED_BIT = (1ULL << 63) }; #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) /** * Check if the processor supports AltiVec/VMX */ static bool has_altivec() { return has_cpuid_bit(CPUID_ALTIVEC_BIT); } /** * Check if the processor supports POWER8 crypto extensions */ static bool has_power_crypto() { return has_cpuid_bit(CPUID_POWER_CRYPTO_BIT); } /** * Check if the processor supports POWER9 DARN RNG */ static bool has_darn_rng() { return has_cpuid_bit(CPUID_DARN_BIT); } #endif #if defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) /** * Check if the processor supports NEON SIMD */ static bool has_neon() { return has_cpuid_bit(CPUID_ARM_NEON_BIT); } /** * Check if the processor supports ARMv8 SVE */ static bool has_arm_sve() { return has_cpuid_bit(CPUID_ARM_SVE_BIT); } /** * Check if the processor supports ARMv8 SHA1 */ static bool has_arm_sha1() { return has_cpuid_bit(CPUID_ARM_SHA1_BIT); } /** * Check if the processor supports ARMv8 SHA2 */ static bool has_arm_sha2() { return has_cpuid_bit(CPUID_ARM_SHA2_BIT); } /** * Check if the processor supports ARMv8 AES */ static bool has_arm_aes() { return has_cpuid_bit(CPUID_ARM_AES_BIT); } /** * Check if the processor supports ARMv8 PMULL */ static bool has_arm_pmull() { return has_cpuid_bit(CPUID_ARM_PMULL_BIT); } /** * Check if the processor supports ARMv8 SHA-512 */ static bool has_arm_sha2_512() { return has_cpuid_bit(CPUID_ARM_SHA2_512_BIT); } /** * Check if the processor supports ARMv8 SHA-3 */ static bool has_arm_sha3() { return has_cpuid_bit(CPUID_ARM_SHA3_BIT); } /** * Check if the processor supports ARMv8 SM3 */ static bool has_arm_sm3() { return has_cpuid_bit(CPUID_ARM_SM3_BIT); } /** * Check if the processor supports ARMv8 SM4 */ static bool has_arm_sm4() { return has_cpuid_bit(CPUID_ARM_SM4_BIT); } #endif #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) /** * Check if the processor supports RDTSC */ static bool has_rdtsc() { return has_cpuid_bit(CPUID_RDTSC_BIT); } /** * Check if the processor supports SSE2 */ static bool has_sse2() { return has_cpuid_bit(CPUID_SSE2_BIT); } /** * Check if the processor supports SSSE3 */ static bool has_ssse3() { return has_cpuid_bit(CPUID_SSSE3_BIT); } /** * Check if the processor supports SSE4.1 */ static bool has_sse41() { return has_cpuid_bit(CPUID_SSE41_BIT); } /** * Check if the processor supports SSE4.2 */ static bool has_sse42() { return has_cpuid_bit(CPUID_SSE42_BIT); } /** * Check if the processor supports AVX2 */ static bool has_avx2() { return has_cpuid_bit(CPUID_AVX2_BIT); } /** * Check if the processor supports AVX-512F */ static bool has_avx512f() { return has_cpuid_bit(CPUID_AVX512F_BIT); } /** * Check if the processor supports AVX-512DQ */ static bool has_avx512dq() { return has_cpuid_bit(CPUID_AVX512DQ_BIT); } /** * Check if the processor supports AVX-512BW */ static bool has_avx512bw() { return has_cpuid_bit(CPUID_AVX512BW_BIT); } /** * Check if the processor supports AVX-512 Ice Lake profile */ static bool has_avx512_icelake() { return has_cpuid_bit(CPUID_AVX512_ICL_BIT); } /** * Check if the processor supports AVX-512 AES (VAES) */ static bool has_avx512_aes() { return has_cpuid_bit(CPUID_AVX512_AES_BIT); } /** * Check if the processor supports AVX-512 VPCLMULQDQ */ static bool has_avx512_clmul() { return has_cpuid_bit(CPUID_AVX512_CLMUL_BIT); } /** * Check if the processor supports BMI1 */ static bool has_bmi1() { return has_cpuid_bit(CPUID_BMI1_BIT); } /** * Check if the processor supports BMI2 */ static bool has_bmi2() { return has_cpuid_bit(CPUID_BMI2_BIT); } /** * Check if the processor supports AES-NI */ static bool has_aes_ni() { return has_cpuid_bit(CPUID_AESNI_BIT); } /** * Check if the processor supports CLMUL */ static bool has_clmul() { return has_cpuid_bit(CPUID_CLMUL_BIT); } /** * Check if the processor supports Intel SHA extension */ static bool has_intel_sha() { return has_cpuid_bit(CPUID_SHA_BIT); } /** * Check if the processor supports ADX extension */ static bool has_adx() { return has_cpuid_bit(CPUID_ADX_BIT); } /** * Check if the processor supports RDRAND */ static bool has_rdrand() { return has_cpuid_bit(CPUID_RDRAND_BIT); } /** * Check if the processor supports RDSEED */ static bool has_rdseed() { return has_cpuid_bit(CPUID_RDSEED_BIT); } #endif /** * Check if the processor supports byte-level vector permutes * (SSSE3, NEON, Altivec) */ static bool has_vperm() { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) return has_ssse3(); #elif defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) return has_neon(); #elif defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) return has_altivec(); #else return false; #endif } /** * Check if the processor supports hardware AES instructions */ static bool has_hw_aes() { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) return has_aes_ni(); #elif defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) return has_arm_aes(); #elif defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) return has_power_crypto(); #else return false; #endif } /** * Check if the processor supports carryless multiply * (CLMUL, PMULL) */ static bool has_carryless_multiply() { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) return has_clmul(); #elif defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) return has_arm_pmull(); #elif defined(BOTAN_TARGET_ARCH_IS_PPC64) return has_power_crypto(); #else return false; #endif } /* * Clear a CPUID bit * Call CPUID::initialize to reset * * This is only exposed for testing, don't use unless you know * what you are doing. */ static void clear_cpuid_bit(CPUID_bits bit) { state().clear_cpuid_bit(static_cast(bit)); } /* * Don't call this function, use CPUID::has_xxx above * It is only exposed for the tests. */ static bool has_cpuid_bit(CPUID_bits elem) { const uint64_t elem64 = static_cast(elem); return state().has_bit(elem64); } static std::vector bit_from_string(const std::string& tok); private: enum class Endian_Status : uint32_t { Unknown = 0x00000000, Big = 0x01234567, Little = 0x67452301, }; struct CPUID_Data { public: CPUID_Data(); CPUID_Data(const CPUID_Data& other) = default; CPUID_Data& operator=(const CPUID_Data& other) = default; void clear_cpuid_bit(uint64_t bit) { m_processor_features &= ~bit; } bool has_bit(uint64_t bit) const { return (m_processor_features & bit) == bit; } uint64_t processor_features() const { return m_processor_features; } Endian_Status endian_status() const { return m_endian_status; } size_t cache_line_size() const { return m_cache_line_size; } private: static Endian_Status runtime_check_endian(); #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) || \ defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) || \ defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) static uint64_t detect_cpu_features(size_t* cache_line_size); #endif uint64_t m_processor_features; size_t m_cache_line_size; Endian_Status m_endian_status; }; static CPUID_Data& state() { static CPUID::CPUID_Data g_cpuid; return g_cpuid; } }; } namespace Botan { /** * Base class for all stream ciphers */ class BOTAN_PUBLIC_API(2,0) StreamCipher : public SymmetricAlgorithm { public: virtual ~StreamCipher() = default; /** * Create an instance based on a name * If provider is empty then best available is chosen. * @param algo_spec algorithm name * @param provider provider implementation to use * @return a null pointer if the algo/provider combination cannot be found */ static std::unique_ptr create(const std::string& algo_spec, const std::string& provider = ""); /** * Create an instance based on a name * If provider is empty then best available is chosen. * @param algo_spec algorithm name * @param provider provider implementation to use * Throws a Lookup_Error if the algo/provider combination cannot be found */ static std::unique_ptr create_or_throw(const std::string& algo_spec, const std::string& provider = ""); /** * @return list of available providers for this algorithm, empty if not available */ static std::vector providers(const std::string& algo_spec); /** * Encrypt or decrypt a message * @param in the plaintext * @param out the byte array to hold the output, i.e. the ciphertext * @param len the length of both in and out in bytes */ virtual void cipher(const uint8_t in[], uint8_t out[], size_t len) = 0; /** * Write keystream bytes to a buffer * @param out the byte array to hold the keystream * @param len the length of out in bytes */ virtual void write_keystream(uint8_t out[], size_t len) { clear_mem(out, len); cipher1(out, len); } /** * Encrypt or decrypt a message * The message is encrypted/decrypted in place. * @param buf the plaintext / ciphertext * @param len the length of buf in bytes */ void cipher1(uint8_t buf[], size_t len) { cipher(buf, buf, len); } /** * Encrypt a message * The message is encrypted/decrypted in place. * @param inout the plaintext / ciphertext */ template void encipher(std::vector& inout) { cipher(inout.data(), inout.data(), inout.size()); } /** * Encrypt a message * The message is encrypted in place. * @param inout the plaintext / ciphertext */ template void encrypt(std::vector& inout) { cipher(inout.data(), inout.data(), inout.size()); } /** * Decrypt a message in place * The message is decrypted in place. * @param inout the plaintext / ciphertext */ template void decrypt(std::vector& inout) { cipher(inout.data(), inout.data(), inout.size()); } /** * Resync the cipher using the IV * @param iv the initialization vector * @param iv_len the length of the IV in bytes */ virtual void set_iv(const uint8_t iv[], size_t iv_len) = 0; /** * Return the default (preferred) nonce length * If this function returns 0, then this cipher does not support nonces */ virtual size_t default_iv_length() const { return 0; } /** * @param iv_len the length of the IV in bytes * @return if the length is valid for this algorithm */ virtual bool valid_iv_length(size_t iv_len) const { return (iv_len == 0); } /** * @return a new object representing the same algorithm as *this */ virtual StreamCipher* clone() const = 0; /** * Set the offset and the state used later to generate the keystream * @param offset the offset where we begin to generate the keystream */ virtual void seek(uint64_t offset) = 0; /** * @return provider information about this implementation. Default is "base", * might also return "sse2", "avx2", "openssl", or some other arbitrary string. */ virtual std::string provider() const { return "base"; } }; } BOTAN_FUTURE_INTERNAL_HEADER(ctr.h) namespace Botan { /** * CTR-BE (Counter mode, big-endian) */ class BOTAN_PUBLIC_API(2,0) CTR_BE final : public StreamCipher { public: void cipher(const uint8_t in[], uint8_t out[], size_t length) override; void set_iv(const uint8_t iv[], size_t iv_len) override; size_t default_iv_length() const override; bool valid_iv_length(size_t iv_len) const override; Key_Length_Specification key_spec() const override; std::string name() const override; CTR_BE* clone() const override; void clear() override; /** * @param cipher the block cipher to use */ explicit CTR_BE(BlockCipher* cipher); CTR_BE(BlockCipher* cipher, size_t ctr_size); void seek(uint64_t offset) override; private: void key_schedule(const uint8_t key[], size_t key_len) override; void add_counter(const uint64_t counter); std::unique_ptr m_cipher; const size_t m_block_size; const size_t m_ctr_size; const size_t m_ctr_blocks; secure_vector m_counter, m_pad; std::vector m_iv; size_t m_pad_pos; }; } namespace Botan { /** * This class represents an abstract data source object. */ class BOTAN_PUBLIC_API(2,0) DataSource { public: /** * Read from the source. Moves the internal offset so that every * call to read will return a new portion of the source. * * @param out the byte array to write the result to * @param length the length of the byte array out * @return length in bytes that was actually read and put * into out */ virtual size_t read(uint8_t out[], size_t length) BOTAN_WARN_UNUSED_RESULT = 0; virtual bool check_available(size_t n) = 0; /** * Read from the source but do not modify the internal * offset. Consecutive calls to peek() will return portions of * the source starting at the same position. * * @param out the byte array to write the output to * @param length the length of the byte array out * @param peek_offset the offset into the stream to read at * @return length in bytes that was actually read and put * into out */ virtual size_t peek(uint8_t out[], size_t length, size_t peek_offset) const BOTAN_WARN_UNUSED_RESULT = 0; /** * Test whether the source still has data that can be read. * @return true if there is no more data to read, false otherwise */ virtual bool end_of_data() const = 0; /** * return the id of this data source * @return std::string representing the id of this data source */ virtual std::string id() const { return ""; } /** * Read one byte. * @param out the byte to read to * @return length in bytes that was actually read and put * into out */ size_t read_byte(uint8_t& out); /** * Peek at one byte. * @param out an output byte * @return length in bytes that was actually read and put * into out */ size_t peek_byte(uint8_t& out) const; /** * Discard the next N bytes of the data * @param N the number of bytes to discard * @return number of bytes actually discarded */ size_t discard_next(size_t N); /** * @return number of bytes read so far. */ virtual size_t get_bytes_read() const = 0; DataSource() = default; virtual ~DataSource() = default; DataSource& operator=(const DataSource&) = delete; DataSource(const DataSource&) = delete; }; /** * This class represents a Memory-Based DataSource */ class BOTAN_PUBLIC_API(2,0) DataSource_Memory final : public DataSource { public: size_t read(uint8_t[], size_t) override; size_t peek(uint8_t[], size_t, size_t) const override; bool check_available(size_t n) override; bool end_of_data() const override; /** * Construct a memory source that reads from a string * @param in the string to read from */ explicit DataSource_Memory(const std::string& in); /** * Construct a memory source that reads from a byte array * @param in the byte array to read from * @param length the length of the byte array */ DataSource_Memory(const uint8_t in[], size_t length) : m_source(in, in + length), m_offset(0) {} /** * Construct a memory source that reads from a secure_vector * @param in the MemoryRegion to read from */ explicit DataSource_Memory(const secure_vector& in) : m_source(in), m_offset(0) {} /** * Construct a memory source that reads from a std::vector * @param in the MemoryRegion to read from */ explicit DataSource_Memory(const std::vector& in) : m_source(in.begin(), in.end()), m_offset(0) {} size_t get_bytes_read() const override { return m_offset; } private: secure_vector m_source; size_t m_offset; }; /** * This class represents a Stream-Based DataSource. */ class BOTAN_PUBLIC_API(2,0) DataSource_Stream final : public DataSource { public: size_t read(uint8_t[], size_t) override; size_t peek(uint8_t[], size_t, size_t) const override; bool check_available(size_t n) override; bool end_of_data() const override; std::string id() const override; DataSource_Stream(std::istream&, const std::string& id = ""); #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) /** * Construct a Stream-Based DataSource from filesystem path * @param file the path to the file * @param use_binary whether to treat the file as binary or not */ DataSource_Stream(const std::string& file, bool use_binary = false); #endif DataSource_Stream(const DataSource_Stream&) = delete; DataSource_Stream& operator=(const DataSource_Stream&) = delete; ~DataSource_Stream(); size_t get_bytes_read() const override { return m_total_read; } private: const std::string m_identifier; std::unique_ptr m_source_memory; std::istream& m_source; size_t m_total_read; }; } namespace Botan { class BOTAN_PUBLIC_API(2,0) SQL_Database { public: class BOTAN_PUBLIC_API(2,0) SQL_DB_Error final : public Exception { public: explicit SQL_DB_Error(const std::string& what) : Exception("SQL database", what), m_rc(0) {} SQL_DB_Error(const std::string& what, int rc) : Exception("SQL database", what), m_rc(rc) {} ErrorType error_type() const noexcept override { return Botan::ErrorType::DatabaseError; } int error_code() const noexcept override { return m_rc; } private: int m_rc; }; class BOTAN_PUBLIC_API(2,0) Statement { public: /* Bind statement parameters */ virtual void bind(int column, const std::string& str) = 0; virtual void bind(int column, size_t i) = 0; virtual void bind(int column, std::chrono::system_clock::time_point time) = 0; virtual void bind(int column, const std::vector& blob) = 0; virtual void bind(int column, const uint8_t* data, size_t len) = 0; /* Get output */ virtual std::pair get_blob(int column) = 0; virtual std::string get_str(int column) = 0; virtual size_t get_size_t(int column) = 0; /* Run to completion */ virtual size_t spin() = 0; /* Maybe update */ virtual bool step() = 0; virtual ~Statement() = default; }; /* * Create a new statement for execution. * Use ?1, ?2, ?3, etc for parameters to set later with bind */ virtual std::shared_ptr new_statement(const std::string& base_sql) const = 0; virtual size_t row_count(const std::string& table_name) = 0; virtual void create_table(const std::string& table_schema) = 0; virtual ~SQL_Database() = default; }; } #if defined(BOTAN_TARGET_OS_HAS_THREADS) namespace Botan { template using lock_guard_type = std::lock_guard; typedef std::mutex mutex_type; typedef std::recursive_mutex recursive_mutex_type; } #else // No threads namespace Botan { template class lock_guard final { public: explicit lock_guard(Mutex& m) : m_mutex(m) { m_mutex.lock(); } ~lock_guard() { m_mutex.unlock(); } lock_guard(const lock_guard& other) = delete; lock_guard& operator=(const lock_guard& other) = delete; private: Mutex& m_mutex; }; class noop_mutex final { public: void lock() {} void unlock() {} }; typedef noop_mutex mutex_type; typedef noop_mutex recursive_mutex_type; template using lock_guard_type = lock_guard; } #endif namespace Botan { class Entropy_Sources; /** * An interface to a cryptographic random number generator */ class BOTAN_PUBLIC_API(2,0) RandomNumberGenerator { public: virtual ~RandomNumberGenerator() = default; RandomNumberGenerator() = default; /* * Never copy a RNG, create a new one */ RandomNumberGenerator(const RandomNumberGenerator& rng) = delete; RandomNumberGenerator& operator=(const RandomNumberGenerator& rng) = delete; /** * Randomize a byte array. * @param output the byte array to hold the random output. * @param length the length of the byte array output in bytes. */ virtual void randomize(uint8_t output[], size_t length) = 0; /** * Returns false if it is known that this RNG object is not able to accept * externally provided inputs (via add_entropy, randomize_with_input, etc). * In this case, any such provided inputs are ignored. * * If this function returns true, then inputs may or may not be accepted. */ virtual bool accepts_input() const = 0; /** * Incorporate some additional data into the RNG state. For * example adding nonces or timestamps from a peer's protocol * message can help hedge against VM state rollback attacks. * A few RNG types do not accept any externally provided input, * in which case this function is a no-op. * * @param input a byte array containg the entropy to be added * @param length the length of the byte array in */ virtual void add_entropy(const uint8_t input[], size_t length) = 0; /** * Incorporate some additional data into the RNG state. */ template void add_entropy_T(const T& t) { static_assert(std::is_standard_layout::value && std::is_trivial::value, "add_entropy_T data must be POD"); this->add_entropy(reinterpret_cast(&t), sizeof(T)); } /** * Incorporate entropy into the RNG state then produce output. * Some RNG types implement this using a single operation, default * calls add_entropy + randomize in sequence. * * Use this to further bind the outputs to your current * process/protocol state. For instance if generating a new key * for use in a session, include a session ID or other such * value. See NIST SP 800-90 A, B, C series for more ideas. * * @param output buffer to hold the random output * @param output_len size of the output buffer in bytes * @param input entropy buffer to incorporate * @param input_len size of the input buffer in bytes */ virtual void randomize_with_input(uint8_t output[], size_t output_len, const uint8_t input[], size_t input_len); /** * This calls `randomize_with_input` using some timestamps as extra input. * * For a stateful RNG using non-random but potentially unique data the * extra input can help protect against problems with fork, VM state * rollback, or other cases where somehow an RNG state is duplicated. If * both of the duplicated RNG states later incorporate a timestamp (and the * timestamps don't themselves repeat), their outputs will diverge. */ virtual void randomize_with_ts_input(uint8_t output[], size_t output_len); /** * @return the name of this RNG type */ virtual std::string name() const = 0; /** * Clear all internally held values of this RNG * @post is_seeded() == false */ virtual void clear() = 0; /** * Check whether this RNG is seeded. * @return true if this RNG was already seeded, false otherwise. */ virtual bool is_seeded() const = 0; /** * Poll provided sources for up to poll_bits bits of entropy * or until the timeout expires. Returns estimate of the number * of bits collected. */ virtual size_t reseed(Entropy_Sources& srcs, size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS, std::chrono::milliseconds poll_timeout = BOTAN_RNG_RESEED_DEFAULT_TIMEOUT); /** * Reseed by reading specified bits from the RNG */ virtual void reseed_from_rng(RandomNumberGenerator& rng, size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS); // Some utility functions built on the interface above: /** * Return a random vector * @param bytes number of bytes in the result * @return randomized vector of length bytes */ secure_vector random_vec(size_t bytes) { secure_vector output; random_vec(output, bytes); return output; } template void random_vec(std::vector& v, size_t bytes) { v.resize(bytes); this->randomize(v.data(), v.size()); } /** * Return a random byte * @return random byte */ uint8_t next_byte() { uint8_t b; this->randomize(&b, 1); return b; } /** * @return a random byte that is greater than zero */ uint8_t next_nonzero_byte() { uint8_t b = this->next_byte(); while(b == 0) b = this->next_byte(); return b; } /** * Create a seeded and active RNG object for general application use * Added in 1.8.0 * Use AutoSeeded_RNG instead */ BOTAN_DEPRECATED("Use AutoSeeded_RNG") static RandomNumberGenerator* make_rng(); }; /** * Convenience typedef */ typedef RandomNumberGenerator RNG; /** * Hardware_RNG exists to tag hardware RNG types (PKCS11_RNG, TPM_RNG, Processor_RNG) */ class BOTAN_PUBLIC_API(2,0) Hardware_RNG : public RandomNumberGenerator { public: virtual void clear() final override { /* no way to clear state of hardware RNG */ } }; /** * Null/stub RNG - fails if you try to use it for anything * This is not generally useful except for in certain tests */ class BOTAN_PUBLIC_API(2,0) Null_RNG final : public RandomNumberGenerator { public: bool is_seeded() const override { return false; } bool accepts_input() const override { return false; } void clear() override {} void randomize(uint8_t[], size_t) override { throw PRNG_Unseeded("Null_RNG called"); } void add_entropy(const uint8_t[], size_t) override {} std::string name() const override { return "Null_RNG"; } }; #if defined(BOTAN_TARGET_OS_HAS_THREADS) /** * Wraps access to a RNG in a mutex * Note that most of the time it's much better to use a RNG per thread * otherwise the RNG will act as an unnecessary contention point * * Since 2.16.0 all Stateful_RNG instances have an internal lock, so * this class is no longer needed. It will be removed in a future major * release. */ class BOTAN_PUBLIC_API(2,0) Serialized_RNG final : public RandomNumberGenerator { public: void randomize(uint8_t out[], size_t len) override { lock_guard_type lock(m_mutex); m_rng->randomize(out, len); } bool accepts_input() const override { lock_guard_type lock(m_mutex); return m_rng->accepts_input(); } bool is_seeded() const override { lock_guard_type lock(m_mutex); return m_rng->is_seeded(); } void clear() override { lock_guard_type lock(m_mutex); m_rng->clear(); } std::string name() const override { lock_guard_type lock(m_mutex); return m_rng->name(); } size_t reseed(Entropy_Sources& src, size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS, std::chrono::milliseconds poll_timeout = BOTAN_RNG_RESEED_DEFAULT_TIMEOUT) override { lock_guard_type lock(m_mutex); return m_rng->reseed(src, poll_bits, poll_timeout); } void add_entropy(const uint8_t in[], size_t len) override { lock_guard_type lock(m_mutex); m_rng->add_entropy(in, len); } BOTAN_DEPRECATED("Use Serialized_RNG(new AutoSeeded_RNG) instead") Serialized_RNG(); /* * Since 2.16.0 this is no longer needed for any RNG type. This * class will be removed in a future major release. */ explicit Serialized_RNG(RandomNumberGenerator* rng) : m_rng(rng) {} private: mutable mutex_type m_mutex; std::unique_ptr m_rng; }; #endif } namespace Botan { class RandomNumberGenerator; /** * Abstract interface to a source of entropy */ class BOTAN_PUBLIC_API(2,0) Entropy_Source { public: /** * Return a new entropy source of a particular type, or null * Each entropy source may require substantial resources (eg, a file handle * or socket instance), so try to share them among multiple RNGs, or just * use the preconfigured global list accessed by Entropy_Sources::global_sources() */ static std::unique_ptr create(const std::string& type); /** * @return name identifying this entropy source */ virtual std::string name() const = 0; /** * Perform an entropy gathering poll * @param rng will be provided with entropy via calls to add_entropy * @return conservative estimate of actual entropy added to rng during poll */ virtual size_t poll(RandomNumberGenerator& rng) = 0; Entropy_Source() = default; Entropy_Source(const Entropy_Source& other) = delete; Entropy_Source(Entropy_Source&& other) = delete; Entropy_Source& operator=(const Entropy_Source& other) = delete; virtual ~Entropy_Source() = default; }; class BOTAN_PUBLIC_API(2,0) Entropy_Sources final { public: static Entropy_Sources& global_sources(); void add_source(std::unique_ptr src); std::vector enabled_sources() const; size_t poll(RandomNumberGenerator& rng, size_t bits, std::chrono::milliseconds timeout); /** * Poll just a single named source. Ordinally only used for testing */ size_t poll_just(RandomNumberGenerator& rng, const std::string& src); Entropy_Sources() = default; explicit Entropy_Sources(const std::vector& sources); Entropy_Sources(const Entropy_Sources& other) = delete; Entropy_Sources(Entropy_Sources&& other) = delete; Entropy_Sources& operator=(const Entropy_Sources& other) = delete; private: std::vector> m_srcs; }; } BOTAN_FUTURE_INTERNAL_HEADER(gcm.h) namespace Botan { class BlockCipher; class StreamCipher; class GHASH; /** * GCM Mode */ class BOTAN_PUBLIC_API(2,0) GCM_Mode : public AEAD_Mode { public: void set_associated_data(const uint8_t ad[], size_t ad_len) override; std::string name() const override; size_t update_granularity() const override; Key_Length_Specification key_spec() const override; bool valid_nonce_length(size_t len) const override; size_t tag_size() const override { return m_tag_size; } void clear() override; void reset() override; std::string provider() const override; protected: GCM_Mode(BlockCipher* cipher, size_t tag_size); ~GCM_Mode(); static const size_t GCM_BS = 16; const size_t m_tag_size; const std::string m_cipher_name; std::unique_ptr m_ctr; std::unique_ptr m_ghash; private: void start_msg(const uint8_t nonce[], size_t nonce_len) override; void key_schedule(const uint8_t key[], size_t length) override; secure_vector m_y0; }; /** * GCM Encryption */ class BOTAN_PUBLIC_API(2,0) GCM_Encryption final : public GCM_Mode { public: /** * @param cipher the 128 bit block cipher to use * @param tag_size is how big the auth tag will be */ GCM_Encryption(BlockCipher* cipher, size_t tag_size = 16) : GCM_Mode(cipher, tag_size) {} size_t output_length(size_t input_length) const override { return input_length + tag_size(); } size_t minimum_final_size() const override { return 0; } size_t process(uint8_t buf[], size_t size) override; void finish(secure_vector& final_block, size_t offset = 0) override; }; /** * GCM Decryption */ class BOTAN_PUBLIC_API(2,0) GCM_Decryption final : public GCM_Mode { public: /** * @param cipher the 128 bit block cipher to use * @param tag_size is how big the auth tag will be */ GCM_Decryption(BlockCipher* cipher, size_t tag_size = 16) : GCM_Mode(cipher, tag_size) {} size_t output_length(size_t input_length) const override { BOTAN_ASSERT(input_length >= tag_size(), "Sufficient input"); return input_length - tag_size(); } size_t minimum_final_size() const override { return tag_size(); } size_t process(uint8_t buf[], size_t size) override; void finish(secure_vector& final_block, size_t offset = 0) override; }; } BOTAN_FUTURE_INTERNAL_HEADER(ghash.h) namespace Botan { /** * GCM's GHASH * This is not intended for general use, but is exposed to allow * shared code between GCM and GMAC */ class BOTAN_PUBLIC_API(2,0) GHASH final : public SymmetricAlgorithm { public: void set_associated_data(const uint8_t ad[], size_t ad_len); secure_vector BOTAN_DEPRECATED("Use other impl") nonce_hash(const uint8_t nonce[], size_t nonce_len) { secure_vector y0(GCM_BS); nonce_hash(y0, nonce, nonce_len); return y0; } void nonce_hash(secure_vector& y0, const uint8_t nonce[], size_t len); void start(const uint8_t nonce[], size_t len); /* * Assumes input len is multiple of 16 */ void update(const uint8_t in[], size_t len); /* * Incremental update of associated data */ void update_associated_data(const uint8_t ad[], size_t len); secure_vector BOTAN_DEPRECATED("Use version taking output params") final() { secure_vector mac(GCM_BS); final(mac.data(), mac.size()); return mac; } void final(uint8_t out[], size_t out_len); Key_Length_Specification key_spec() const override { return Key_Length_Specification(16); } void clear() override; void reset(); std::string name() const override { return "GHASH"; } std::string provider() const; void ghash_update(secure_vector& x, const uint8_t input[], size_t input_len); void add_final_block(secure_vector& x, size_t ad_len, size_t pt_len); private: #if defined(BOTAN_HAS_GHASH_CLMUL_CPU) static void ghash_precompute_cpu(const uint8_t H[16], uint64_t H_pow[4*2]); static void ghash_multiply_cpu(uint8_t x[16], const uint64_t H_pow[4*2], const uint8_t input[], size_t blocks); #endif #if defined(BOTAN_HAS_GHASH_CLMUL_VPERM) static void ghash_multiply_vperm(uint8_t x[16], const uint64_t HM[256], const uint8_t input[], size_t blocks); #endif void key_schedule(const uint8_t key[], size_t key_len) override; void ghash_multiply(secure_vector& x, const uint8_t input[], size_t blocks); static const size_t GCM_BS = 16; secure_vector m_H; secure_vector m_H_ad; secure_vector m_ghash; secure_vector m_nonce; secure_vector m_HM; secure_vector m_H_pow; size_t m_ad_len = 0; size_t m_text_len = 0; }; } namespace Botan { /** * Perform hex encoding * @param output an array of at least input_length*2 bytes * @param input is some binary data * @param input_length length of input in bytes * @param uppercase should output be upper or lower case? */ void BOTAN_PUBLIC_API(2,0) hex_encode(char output[], const uint8_t input[], size_t input_length, bool uppercase = true); /** * Perform hex encoding * @param input some input * @param input_length length of input in bytes * @param uppercase should output be upper or lower case? * @return hexadecimal representation of input */ std::string BOTAN_PUBLIC_API(2,0) hex_encode(const uint8_t input[], size_t input_length, bool uppercase = true); /** * Perform hex encoding * @param input some input * @param uppercase should output be upper or lower case? * @return hexadecimal representation of input */ template std::string hex_encode(const std::vector& input, bool uppercase = true) { return hex_encode(input.data(), input.size(), uppercase); } /** * Perform hex decoding * @param output an array of at least input_length/2 bytes * @param input some hex input * @param input_length length of input in bytes * @param input_consumed is an output parameter which says how many * bytes of input were actually consumed. If less than * input_length, then the range input[consumed:length] * should be passed in later along with more input. * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return number of bytes written to output */ size_t BOTAN_PUBLIC_API(2,0) hex_decode(uint8_t output[], const char input[], size_t input_length, size_t& input_consumed, bool ignore_ws = true); /** * Perform hex decoding * @param output an array of at least input_length/2 bytes * @param input some hex input * @param input_length length of input in bytes * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return number of bytes written to output */ size_t BOTAN_PUBLIC_API(2,0) hex_decode(uint8_t output[], const char input[], size_t input_length, bool ignore_ws = true); /** * Perform hex decoding * @param output an array of at least input_length/2 bytes * @param input some hex input * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return number of bytes written to output */ size_t BOTAN_PUBLIC_API(2,0) hex_decode(uint8_t output[], const std::string& input, bool ignore_ws = true); /** * Perform hex decoding * @param input some hex input * @param input_length the length of input in bytes * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return decoded hex output */ std::vector BOTAN_PUBLIC_API(2,0) hex_decode(const char input[], size_t input_length, bool ignore_ws = true); /** * Perform hex decoding * @param input some hex input * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return decoded hex output */ std::vector BOTAN_PUBLIC_API(2,0) hex_decode(const std::string& input, bool ignore_ws = true); /** * Perform hex decoding * @param input some hex input * @param input_length the length of input in bytes * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return decoded hex output */ secure_vector BOTAN_PUBLIC_API(2,0) hex_decode_locked(const char input[], size_t input_length, bool ignore_ws = true); /** * Perform hex decoding * @param input some hex input * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return decoded hex output */ secure_vector BOTAN_PUBLIC_API(2,0) hex_decode_locked(const std::string& input, bool ignore_ws = true); } BOTAN_FUTURE_INTERNAL_HEADER(loadstor.h) #if defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) #define BOTAN_ENDIAN_N2L(x) reverse_bytes(x) #define BOTAN_ENDIAN_L2N(x) reverse_bytes(x) #define BOTAN_ENDIAN_N2B(x) (x) #define BOTAN_ENDIAN_B2N(x) (x) #elif defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) #define BOTAN_ENDIAN_N2L(x) (x) #define BOTAN_ENDIAN_L2N(x) (x) #define BOTAN_ENDIAN_N2B(x) reverse_bytes(x) #define BOTAN_ENDIAN_B2N(x) reverse_bytes(x) #endif namespace Botan { /** * Byte extraction * @param byte_num which byte to extract, 0 == highest byte * @param input the value to extract from * @return byte byte_num of input */ template inline constexpr uint8_t get_byte(size_t byte_num, T input) { return static_cast( input >> (((~byte_num)&(sizeof(T)-1)) << 3) ); } /** * Make a uint16_t from two bytes * @param i0 the first byte * @param i1 the second byte * @return i0 || i1 */ inline constexpr uint16_t make_uint16(uint8_t i0, uint8_t i1) { return static_cast((static_cast(i0) << 8) | i1); } /** * Make a uint32_t from four bytes * @param i0 the first byte * @param i1 the second byte * @param i2 the third byte * @param i3 the fourth byte * @return i0 || i1 || i2 || i3 */ inline constexpr uint32_t make_uint32(uint8_t i0, uint8_t i1, uint8_t i2, uint8_t i3) { return ((static_cast(i0) << 24) | (static_cast(i1) << 16) | (static_cast(i2) << 8) | (static_cast(i3))); } /** * Make a uint64_t from eight bytes * @param i0 the first byte * @param i1 the second byte * @param i2 the third byte * @param i3 the fourth byte * @param i4 the fifth byte * @param i5 the sixth byte * @param i6 the seventh byte * @param i7 the eighth byte * @return i0 || i1 || i2 || i3 || i4 || i5 || i6 || i7 */ inline constexpr uint64_t make_uint64(uint8_t i0, uint8_t i1, uint8_t i2, uint8_t i3, uint8_t i4, uint8_t i5, uint8_t i6, uint8_t i7) { return ((static_cast(i0) << 56) | (static_cast(i1) << 48) | (static_cast(i2) << 40) | (static_cast(i3) << 32) | (static_cast(i4) << 24) | (static_cast(i5) << 16) | (static_cast(i6) << 8) | (static_cast(i7))); } /** * Load a big-endian word * @param in a pointer to some bytes * @param off an offset into the array * @return off'th T of in, as a big-endian value */ template inline T load_be(const uint8_t in[], size_t off) { in += off * sizeof(T); T out = 0; for(size_t i = 0; i != sizeof(T); ++i) out = static_cast((out << 8) | in[i]); return out; } /** * Load a little-endian word * @param in a pointer to some bytes * @param off an offset into the array * @return off'th T of in, as a litte-endian value */ template inline T load_le(const uint8_t in[], size_t off) { in += off * sizeof(T); T out = 0; for(size_t i = 0; i != sizeof(T); ++i) out = (out << 8) | in[sizeof(T)-1-i]; return out; } /** * Load a big-endian uint16_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint16_t of in, as a big-endian value */ template<> inline uint16_t load_be(const uint8_t in[], size_t off) { in += off * sizeof(uint16_t); #if defined(BOTAN_ENDIAN_N2B) uint16_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2B(x); #else return make_uint16(in[0], in[1]); #endif } /** * Load a little-endian uint16_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint16_t of in, as a little-endian value */ template<> inline uint16_t load_le(const uint8_t in[], size_t off) { in += off * sizeof(uint16_t); #if defined(BOTAN_ENDIAN_N2L) uint16_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2L(x); #else return make_uint16(in[1], in[0]); #endif } /** * Load a big-endian uint32_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint32_t of in, as a big-endian value */ template<> inline uint32_t load_be(const uint8_t in[], size_t off) { in += off * sizeof(uint32_t); #if defined(BOTAN_ENDIAN_N2B) uint32_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2B(x); #else return make_uint32(in[0], in[1], in[2], in[3]); #endif } /** * Load a little-endian uint32_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint32_t of in, as a little-endian value */ template<> inline uint32_t load_le(const uint8_t in[], size_t off) { in += off * sizeof(uint32_t); #if defined(BOTAN_ENDIAN_N2L) uint32_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2L(x); #else return make_uint32(in[3], in[2], in[1], in[0]); #endif } /** * Load a big-endian uint64_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint64_t of in, as a big-endian value */ template<> inline uint64_t load_be(const uint8_t in[], size_t off) { in += off * sizeof(uint64_t); #if defined(BOTAN_ENDIAN_N2B) uint64_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2B(x); #else return make_uint64(in[0], in[1], in[2], in[3], in[4], in[5], in[6], in[7]); #endif } /** * Load a little-endian uint64_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint64_t of in, as a little-endian value */ template<> inline uint64_t load_le(const uint8_t in[], size_t off) { in += off * sizeof(uint64_t); #if defined(BOTAN_ENDIAN_N2L) uint64_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2L(x); #else return make_uint64(in[7], in[6], in[5], in[4], in[3], in[2], in[1], in[0]); #endif } /** * Load two little-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written */ template inline void load_le(const uint8_t in[], T& x0, T& x1) { x0 = load_le(in, 0); x1 = load_le(in, 1); } /** * Load four little-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written * @param x2 where the third word will be written * @param x3 where the fourth word will be written */ template inline void load_le(const uint8_t in[], T& x0, T& x1, T& x2, T& x3) { x0 = load_le(in, 0); x1 = load_le(in, 1); x2 = load_le(in, 2); x3 = load_le(in, 3); } /** * Load eight little-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written * @param x2 where the third word will be written * @param x3 where the fourth word will be written * @param x4 where the fifth word will be written * @param x5 where the sixth word will be written * @param x6 where the seventh word will be written * @param x7 where the eighth word will be written */ template inline void load_le(const uint8_t in[], T& x0, T& x1, T& x2, T& x3, T& x4, T& x5, T& x6, T& x7) { x0 = load_le(in, 0); x1 = load_le(in, 1); x2 = load_le(in, 2); x3 = load_le(in, 3); x4 = load_le(in, 4); x5 = load_le(in, 5); x6 = load_le(in, 6); x7 = load_le(in, 7); } /** * Load a variable number of little-endian words * @param out the output array of words * @param in the input array of bytes * @param count how many words are in in */ template inline void load_le(T out[], const uint8_t in[], size_t count) { if(count > 0) { #if defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) typecast_copy(out, in, count); #elif defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) typecast_copy(out, in, count); const size_t blocks = count - (count % 4); const size_t left = count - blocks; for(size_t i = 0; i != blocks; i += 4) bswap_4(out + i); for(size_t i = 0; i != left; ++i) out[blocks+i] = reverse_bytes(out[blocks+i]); #else for(size_t i = 0; i != count; ++i) out[i] = load_le(in, i); #endif } } /** * Load two big-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written */ template inline void load_be(const uint8_t in[], T& x0, T& x1) { x0 = load_be(in, 0); x1 = load_be(in, 1); } /** * Load four big-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written * @param x2 where the third word will be written * @param x3 where the fourth word will be written */ template inline void load_be(const uint8_t in[], T& x0, T& x1, T& x2, T& x3) { x0 = load_be(in, 0); x1 = load_be(in, 1); x2 = load_be(in, 2); x3 = load_be(in, 3); } /** * Load eight big-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written * @param x2 where the third word will be written * @param x3 where the fourth word will be written * @param x4 where the fifth word will be written * @param x5 where the sixth word will be written * @param x6 where the seventh word will be written * @param x7 where the eighth word will be written */ template inline void load_be(const uint8_t in[], T& x0, T& x1, T& x2, T& x3, T& x4, T& x5, T& x6, T& x7) { x0 = load_be(in, 0); x1 = load_be(in, 1); x2 = load_be(in, 2); x3 = load_be(in, 3); x4 = load_be(in, 4); x5 = load_be(in, 5); x6 = load_be(in, 6); x7 = load_be(in, 7); } /** * Load a variable number of big-endian words * @param out the output array of words * @param in the input array of bytes * @param count how many words are in in */ template inline void load_be(T out[], const uint8_t in[], size_t count) { if(count > 0) { #if defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) typecast_copy(out, in, count); #elif defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) typecast_copy(out, in, count); const size_t blocks = count - (count % 4); const size_t left = count - blocks; for(size_t i = 0; i != blocks; i += 4) bswap_4(out + i); for(size_t i = 0; i != left; ++i) out[blocks+i] = reverse_bytes(out[blocks+i]); #else for(size_t i = 0; i != count; ++i) out[i] = load_be(in, i); #endif } } /** * Store a big-endian uint16_t * @param in the input uint16_t * @param out the byte array to write to */ inline void store_be(uint16_t in, uint8_t out[2]) { #if defined(BOTAN_ENDIAN_N2B) uint16_t o = BOTAN_ENDIAN_N2B(in); typecast_copy(out, o); #else out[0] = get_byte(0, in); out[1] = get_byte(1, in); #endif } /** * Store a little-endian uint16_t * @param in the input uint16_t * @param out the byte array to write to */ inline void store_le(uint16_t in, uint8_t out[2]) { #if defined(BOTAN_ENDIAN_N2L) uint16_t o = BOTAN_ENDIAN_N2L(in); typecast_copy(out, o); #else out[0] = get_byte(1, in); out[1] = get_byte(0, in); #endif } /** * Store a big-endian uint32_t * @param in the input uint32_t * @param out the byte array to write to */ inline void store_be(uint32_t in, uint8_t out[4]) { #if defined(BOTAN_ENDIAN_B2N) uint32_t o = BOTAN_ENDIAN_B2N(in); typecast_copy(out, o); #else out[0] = get_byte(0, in); out[1] = get_byte(1, in); out[2] = get_byte(2, in); out[3] = get_byte(3, in); #endif } /** * Store a little-endian uint32_t * @param in the input uint32_t * @param out the byte array to write to */ inline void store_le(uint32_t in, uint8_t out[4]) { #if defined(BOTAN_ENDIAN_L2N) uint32_t o = BOTAN_ENDIAN_L2N(in); typecast_copy(out, o); #else out[0] = get_byte(3, in); out[1] = get_byte(2, in); out[2] = get_byte(1, in); out[3] = get_byte(0, in); #endif } /** * Store a big-endian uint64_t * @param in the input uint64_t * @param out the byte array to write to */ inline void store_be(uint64_t in, uint8_t out[8]) { #if defined(BOTAN_ENDIAN_B2N) uint64_t o = BOTAN_ENDIAN_B2N(in); typecast_copy(out, o); #else out[0] = get_byte(0, in); out[1] = get_byte(1, in); out[2] = get_byte(2, in); out[3] = get_byte(3, in); out[4] = get_byte(4, in); out[5] = get_byte(5, in); out[6] = get_byte(6, in); out[7] = get_byte(7, in); #endif } /** * Store a little-endian uint64_t * @param in the input uint64_t * @param out the byte array to write to */ inline void store_le(uint64_t in, uint8_t out[8]) { #if defined(BOTAN_ENDIAN_L2N) uint64_t o = BOTAN_ENDIAN_L2N(in); typecast_copy(out, o); #else out[0] = get_byte(7, in); out[1] = get_byte(6, in); out[2] = get_byte(5, in); out[3] = get_byte(4, in); out[4] = get_byte(3, in); out[5] = get_byte(2, in); out[6] = get_byte(1, in); out[7] = get_byte(0, in); #endif } /** * Store two little-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word */ template inline void store_le(uint8_t out[], T x0, T x1) { store_le(x0, out + (0 * sizeof(T))); store_le(x1, out + (1 * sizeof(T))); } /** * Store two big-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word */ template inline void store_be(uint8_t out[], T x0, T x1) { store_be(x0, out + (0 * sizeof(T))); store_be(x1, out + (1 * sizeof(T))); } /** * Store four little-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word * @param x2 the third word * @param x3 the fourth word */ template inline void store_le(uint8_t out[], T x0, T x1, T x2, T x3) { store_le(x0, out + (0 * sizeof(T))); store_le(x1, out + (1 * sizeof(T))); store_le(x2, out + (2 * sizeof(T))); store_le(x3, out + (3 * sizeof(T))); } /** * Store four big-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word * @param x2 the third word * @param x3 the fourth word */ template inline void store_be(uint8_t out[], T x0, T x1, T x2, T x3) { store_be(x0, out + (0 * sizeof(T))); store_be(x1, out + (1 * sizeof(T))); store_be(x2, out + (2 * sizeof(T))); store_be(x3, out + (3 * sizeof(T))); } /** * Store eight little-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word * @param x2 the third word * @param x3 the fourth word * @param x4 the fifth word * @param x5 the sixth word * @param x6 the seventh word * @param x7 the eighth word */ template inline void store_le(uint8_t out[], T x0, T x1, T x2, T x3, T x4, T x5, T x6, T x7) { store_le(x0, out + (0 * sizeof(T))); store_le(x1, out + (1 * sizeof(T))); store_le(x2, out + (2 * sizeof(T))); store_le(x3, out + (3 * sizeof(T))); store_le(x4, out + (4 * sizeof(T))); store_le(x5, out + (5 * sizeof(T))); store_le(x6, out + (6 * sizeof(T))); store_le(x7, out + (7 * sizeof(T))); } /** * Store eight big-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word * @param x2 the third word * @param x3 the fourth word * @param x4 the fifth word * @param x5 the sixth word * @param x6 the seventh word * @param x7 the eighth word */ template inline void store_be(uint8_t out[], T x0, T x1, T x2, T x3, T x4, T x5, T x6, T x7) { store_be(x0, out + (0 * sizeof(T))); store_be(x1, out + (1 * sizeof(T))); store_be(x2, out + (2 * sizeof(T))); store_be(x3, out + (3 * sizeof(T))); store_be(x4, out + (4 * sizeof(T))); store_be(x5, out + (5 * sizeof(T))); store_be(x6, out + (6 * sizeof(T))); store_be(x7, out + (7 * sizeof(T))); } template void copy_out_be(uint8_t out[], size_t out_bytes, const T in[]) { while(out_bytes >= sizeof(T)) { store_be(in[0], out); out += sizeof(T); out_bytes -= sizeof(T); in += 1; } for(size_t i = 0; i != out_bytes; ++i) out[i] = get_byte(i%8, in[0]); } template void copy_out_vec_be(uint8_t out[], size_t out_bytes, const std::vector& in) { copy_out_be(out, out_bytes, in.data()); } template void copy_out_le(uint8_t out[], size_t out_bytes, const T in[]) { while(out_bytes >= sizeof(T)) { store_le(in[0], out); out += sizeof(T); out_bytes -= sizeof(T); in += 1; } for(size_t i = 0; i != out_bytes; ++i) out[i] = get_byte(sizeof(T) - 1 - (i % 8), in[0]); } template void copy_out_vec_le(uint8_t out[], size_t out_bytes, const std::vector& in) { copy_out_le(out, out_bytes, in.data()); } } BOTAN_FUTURE_INTERNAL_HEADER(mul128.h) namespace Botan { #if defined(__SIZEOF_INT128__) && defined(BOTAN_TARGET_CPU_HAS_NATIVE_64BIT) #define BOTAN_TARGET_HAS_NATIVE_UINT128 // Prefer TI mode over __int128 as GCC rejects the latter in pendantic mode #if defined(__GNUG__) typedef unsigned int uint128_t __attribute__((mode(TI))); #else typedef unsigned __int128 uint128_t; #endif #endif } #if defined(BOTAN_TARGET_HAS_NATIVE_UINT128) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) \ do { \ const uint128_t r = static_cast(a) * b; \ *hi = (r >> 64) & 0xFFFFFFFFFFFFFFFF; \ *lo = (r ) & 0xFFFFFFFFFFFFFFFF; \ } while(0) #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC) && defined(BOTAN_TARGET_CPU_HAS_NATIVE_64BIT) #include #pragma intrinsic(_umul128) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) \ do { *lo = _umul128(a, b, hi); } while(0) #elif defined(BOTAN_USE_GCC_INLINE_ASM) #if defined(BOTAN_TARGET_ARCH_IS_X86_64) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) do { \ asm("mulq %3" : "=d" (*hi), "=a" (*lo) : "a" (a), "rm" (b) : "cc"); \ } while(0) #elif defined(BOTAN_TARGET_ARCH_IS_ALPHA) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) do { \ asm("umulh %1,%2,%0" : "=r" (*hi) : "r" (a), "r" (b)); \ *lo = a * b; \ } while(0) #elif defined(BOTAN_TARGET_ARCH_IS_IA64) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) do { \ asm("xmpy.hu %0=%1,%2" : "=f" (*hi) : "f" (a), "f" (b)); \ *lo = a * b; \ } while(0) #elif defined(BOTAN_TARGET_ARCH_IS_PPC64) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) do { \ asm("mulhdu %0,%1,%2" : "=r" (*hi) : "r" (a), "r" (b) : "cc"); \ *lo = a * b; \ } while(0) #endif #endif namespace Botan { /** * Perform a 64x64->128 bit multiplication */ inline void mul64x64_128(uint64_t a, uint64_t b, uint64_t* lo, uint64_t* hi) { #if defined(BOTAN_FAST_64X64_MUL) BOTAN_FAST_64X64_MUL(a, b, lo, hi); #else /* * Do a 64x64->128 multiply using four 32x32->64 multiplies plus * some adds and shifts. Last resort for CPUs like UltraSPARC (with * 64-bit registers/ALU, but no 64x64->128 multiply) or 32-bit CPUs. */ const size_t HWORD_BITS = 32; const uint32_t HWORD_MASK = 0xFFFFFFFF; const uint32_t a_hi = (a >> HWORD_BITS); const uint32_t a_lo = (a & HWORD_MASK); const uint32_t b_hi = (b >> HWORD_BITS); const uint32_t b_lo = (b & HWORD_MASK); uint64_t x0 = static_cast(a_hi) * b_hi; uint64_t x1 = static_cast(a_lo) * b_hi; uint64_t x2 = static_cast(a_hi) * b_lo; uint64_t x3 = static_cast(a_lo) * b_lo; // this cannot overflow as (2^32-1)^2 + 2^32-1 < 2^64-1 x2 += x3 >> HWORD_BITS; // this one can overflow x2 += x1; // propagate the carry if any x0 += static_cast(static_cast(x2 < x1)) << HWORD_BITS; *hi = x0 + (x2 >> HWORD_BITS); *lo = ((x2 & HWORD_MASK) << HWORD_BITS) + (x3 & HWORD_MASK); #endif } } BOTAN_FUTURE_INTERNAL_HEADER(parsing.h) namespace Botan { /** * Parse a SCAN-style algorithm name * @param scan_name the name * @return the name components */ BOTAN_PUBLIC_API(2,0) std::vector parse_algorithm_name(const std::string& scan_name); /** * Split a string * @param str the input string * @param delim the delimitor * @return string split by delim */ BOTAN_PUBLIC_API(2,0) std::vector split_on( const std::string& str, char delim); /** * Split a string on a character predicate * @param str the input string * @param pred the predicate * * This function will likely be removed in a future release */ BOTAN_PUBLIC_API(2,0) std::vector split_on_pred(const std::string& str, std::function pred); /** * Erase characters from a string */ BOTAN_PUBLIC_API(2,0) BOTAN_DEPRECATED("Unused") std::string erase_chars(const std::string& str, const std::set& chars); /** * Replace a character in a string * @param str the input string * @param from_char the character to replace * @param to_char the character to replace it with * @return str with all instances of from_char replaced by to_char */ BOTAN_PUBLIC_API(2,0) BOTAN_DEPRECATED("Unused") std::string replace_char(const std::string& str, char from_char, char to_char); /** * Replace a character in a string * @param str the input string * @param from_chars the characters to replace * @param to_char the character to replace it with * @return str with all instances of from_chars replaced by to_char */ BOTAN_PUBLIC_API(2,0) BOTAN_DEPRECATED("Unused") std::string replace_chars(const std::string& str, const std::set& from_chars, char to_char); /** * Join a string * @param strs strings to join * @param delim the delimitor * @return string joined by delim */ BOTAN_PUBLIC_API(2,0) std::string string_join(const std::vector& strs, char delim); /** * Parse an ASN.1 OID * @param oid the OID in string form * @return OID components */ BOTAN_PUBLIC_API(2,0) std::vector BOTAN_DEPRECATED("Use OID::from_string(oid).get_components()") parse_asn1_oid(const std::string& oid); /** * Compare two names using the X.509 comparison algorithm * @param name1 the first name * @param name2 the second name * @return true if name1 is the same as name2 by the X.509 comparison rules */ BOTAN_PUBLIC_API(2,0) bool x500_name_cmp(const std::string& name1, const std::string& name2); /** * Convert a string to a number * @param str the string to convert * @return number value of the string */ BOTAN_PUBLIC_API(2,0) uint32_t to_u32bit(const std::string& str); /** * Convert a string to a number * @param str the string to convert * @return number value of the string */ BOTAN_PUBLIC_API(2,3) uint16_t to_uint16(const std::string& str); /** * Convert a time specification to a number * @param timespec the time specification * @return number of seconds represented by timespec */ BOTAN_PUBLIC_API(2,0) uint32_t BOTAN_DEPRECATED("Not used anymore") timespec_to_u32bit(const std::string& timespec); /** * Convert a string representation of an IPv4 address to a number * @param ip_str the string representation * @return integer IPv4 address */ BOTAN_PUBLIC_API(2,0) uint32_t string_to_ipv4(const std::string& ip_str); /** * Convert an IPv4 address to a string * @param ip_addr the IPv4 address to convert * @return string representation of the IPv4 address */ BOTAN_PUBLIC_API(2,0) std::string ipv4_to_string(uint32_t ip_addr); std::map BOTAN_PUBLIC_API(2,0) read_cfg(std::istream& is); /** * Accepts key value pairs deliminated by commas: * * "" (returns empty map) * "K=V" (returns map {'K': 'V'}) * "K1=V1,K2=V2" * "K1=V1,K2=V2,K3=V3" * "K1=V1,K2=V2,K3=a_value\,with\,commas_and_\=equals" * * Values may be empty, keys must be non-empty and unique. Duplicate * keys cause an exception. * * Within both key and value, comma and equals can be escaped with * backslash. Backslash can also be escaped. */ std::map BOTAN_PUBLIC_API(2,8) read_kv(const std::string& kv); std::string BOTAN_PUBLIC_API(2,0) clean_ws(const std::string& s); std::string tolower_string(const std::string& s); /** * Check if the given hostname is a match for the specified wildcard */ bool BOTAN_PUBLIC_API(2,0) host_wildcard_match(const std::string& wildcard, const std::string& host); } BOTAN_FUTURE_INTERNAL_HEADER(rotate.h) namespace Botan { /** * Bit rotation left by a compile-time constant amount * @param input the input word * @return input rotated left by ROT bits */ template inline constexpr T rotl(T input) { static_assert(ROT > 0 && ROT < 8*sizeof(T), "Invalid rotation constant"); return static_cast((input << ROT) | (input >> (8*sizeof(T) - ROT))); } /** * Bit rotation right by a compile-time constant amount * @param input the input word * @return input rotated right by ROT bits */ template inline constexpr T rotr(T input) { static_assert(ROT > 0 && ROT < 8*sizeof(T), "Invalid rotation constant"); return static_cast((input >> ROT) | (input << (8*sizeof(T) - ROT))); } /** * Bit rotation left, variable rotation amount * @param input the input word * @param rot the number of bits to rotate, must be between 0 and sizeof(T)*8-1 * @return input rotated left by rot bits */ template inline T rotl_var(T input, size_t rot) { return rot ? static_cast((input << rot) | (input >> (sizeof(T)*8 - rot))) : input; } /** * Bit rotation right, variable rotation amount * @param input the input word * @param rot the number of bits to rotate, must be between 0 and sizeof(T)*8-1 * @return input rotated right by rot bits */ template inline T rotr_var(T input, size_t rot) { return rot ? static_cast((input >> rot) | (input << (sizeof(T)*8 - rot))) : input; } #if defined(BOTAN_USE_GCC_INLINE_ASM) #if defined(BOTAN_TARGET_ARCH_IS_X86_64) || defined(BOTAN_TARGET_ARCH_IS_X86_32) template<> inline uint32_t rotl_var(uint32_t input, size_t rot) { asm("roll %1,%0" : "+r" (input) : "c" (static_cast(rot)) : "cc"); return input; } template<> inline uint32_t rotr_var(uint32_t input, size_t rot) { asm("rorl %1,%0" : "+r" (input) : "c" (static_cast(rot)) : "cc"); return input; } #endif #endif template BOTAN_DEPRECATED("Use rotl or rotl_var") inline T rotate_left(T input, size_t rot) { // rotl_var does not reduce return rotl_var(input, rot % (8 * sizeof(T))); } template BOTAN_DEPRECATED("Use rotr or rotr_var") inline T rotate_right(T input, size_t rot) { // rotr_var does not reduce return rotr_var(input, rot % (8 * sizeof(T))); } } BOTAN_FUTURE_INTERNAL_HEADER(scan_name.h) namespace Botan { /** A class encapsulating a SCAN name (similar to JCE conventions) http://www.users.zetnet.co.uk/hopwood/crypto/scan/ */ class BOTAN_PUBLIC_API(2,0) SCAN_Name final { public: /** * Create a SCAN_Name * @param algo_spec A SCAN-format name */ explicit SCAN_Name(const char* algo_spec); /** * Create a SCAN_Name * @param algo_spec A SCAN-format name */ explicit SCAN_Name(std::string algo_spec); /** * @return original input string */ const std::string& to_string() const { return m_orig_algo_spec; } BOTAN_DEPRECATED("Use SCAN_Name::to_string") const std::string& as_string() const { return this->to_string(); } /** * @return algorithm name */ const std::string& algo_name() const { return m_alg_name; } /** * @return number of arguments */ size_t arg_count() const { return m_args.size(); } /** * @param lower is the lower bound * @param upper is the upper bound * @return if the number of arguments is between lower and upper */ bool arg_count_between(size_t lower, size_t upper) const { return ((arg_count() >= lower) && (arg_count() <= upper)); } /** * @param i which argument * @return ith argument */ std::string arg(size_t i) const; /** * @param i which argument * @param def_value the default value * @return ith argument or the default value */ std::string arg(size_t i, const std::string& def_value) const; /** * @param i which argument * @param def_value the default value * @return ith argument as an integer, or the default value */ size_t arg_as_integer(size_t i, size_t def_value) const; /** * @return cipher mode (if any) */ std::string cipher_mode() const { return (m_mode_info.size() >= 1) ? m_mode_info[0] : ""; } /** * @return cipher mode padding (if any) */ std::string cipher_mode_pad() const { return (m_mode_info.size() >= 2) ? m_mode_info[1] : ""; } private: std::string m_orig_algo_spec; std::string m_alg_name; std::vector m_args; std::vector m_mode_info; }; // This is unrelated but it is convenient to stash it here template std::vector probe_providers_of(const std::string& algo_spec, const std::vector& possible) { std::vector providers; for(auto&& prov : possible) { std::unique_ptr o(T::create(algo_spec, prov)); if(o) { providers.push_back(prov); // available } } return providers; } } #if __cplusplus < 201402L #endif BOTAN_FUTURE_INTERNAL_HEADER(stl_compatability.h) namespace Botan { /* * std::make_unique functionality similar as we have in C++14. * C++11 version based on proposal for C++14 implemenatation by Stephan T. Lavavej * source: https://isocpp.org/files/papers/N3656.txt */ #if __cplusplus >= 201402L template constexpr auto make_unique(Args&&... args) { return std::make_unique(std::forward(args)...); } template constexpr auto make_unique(std::size_t size) { return std::make_unique(size); } #else namespace stlCompatibilityDetails { template struct _Unique_if { typedef std::unique_ptr _Single_object; }; template struct _Unique_if { typedef std::unique_ptr _Unknown_bound; }; template struct _Unique_if { typedef void _Known_bound; }; } // namespace stlCompatibilityDetails template typename stlCompatibilityDetails::_Unique_if::_Single_object make_unique(Args&&... args) { return std::unique_ptr(new T(std::forward(args)...)); } template typename stlCompatibilityDetails::_Unique_if::_Unknown_bound make_unique(size_t n) { typedef typename std::remove_extent::type U; return std::unique_ptr(new U[n]()); } template typename stlCompatibilityDetails::_Unique_if::_Known_bound make_unique(Args&&...) = delete; #endif } // namespace Botan #if defined(BOTAN_HAS_STREAM_CIPHER) #endif BOTAN_FUTURE_INTERNAL_HEADER(stream_mode.h) namespace Botan { #if defined(BOTAN_HAS_STREAM_CIPHER) class BOTAN_PUBLIC_API(2,0) Stream_Cipher_Mode final : public Cipher_Mode { public: /** * @param cipher underyling stream cipher */ explicit Stream_Cipher_Mode(StreamCipher* cipher) : m_cipher(cipher) {} size_t process(uint8_t buf[], size_t sz) override { m_cipher->cipher1(buf, sz); return sz; } void finish(secure_vector& buf, size_t offset) override { return update(buf, offset); } size_t output_length(size_t input_length) const override { return input_length; } size_t update_granularity() const override { return 1; } size_t minimum_final_size() const override { return 0; } size_t default_nonce_length() const override { return 0; } bool valid_nonce_length(size_t nonce_len) const override { return m_cipher->valid_iv_length(nonce_len); } Key_Length_Specification key_spec() const override { return m_cipher->key_spec(); } std::string name() const override { return m_cipher->name(); } void clear() override { m_cipher->clear(); reset(); } void reset() override { /* no msg state */ } private: void start_msg(const uint8_t nonce[], size_t nonce_len) override { if(nonce_len > 0) { m_cipher->set_iv(nonce, nonce_len); } } void key_schedule(const uint8_t key[], size_t length) override { m_cipher->set_key(key, length); } std::unique_ptr m_cipher; }; #endif } namespace Botan { /* * Get information describing the version */ /** * Get a human-readable string identifying the version of Botan. * No particular format should be assumed. * @return version string */ BOTAN_PUBLIC_API(2,0) std::string version_string(); /** * Same as version_string() except returning a pointer to a statically * allocated string. * @return version string */ BOTAN_PUBLIC_API(2,0) const char* version_cstr(); /** * Return a version string of the form "MAJOR.MINOR.PATCH" where * each of the values is an integer. */ BOTAN_PUBLIC_API(2,4) std::string short_version_string(); /** * Same as version_short_string except returning a pointer to the string. */ BOTAN_PUBLIC_API(2,4) const char* short_version_cstr(); /** * Return the date this version of botan was released, in an integer of * the form YYYYMMDD. For instance a version released on May 21, 2013 * would return the integer 20130521. If the currently running version * is not an official release, this function will return 0 instead. * * @return release date, or zero if unreleased */ BOTAN_PUBLIC_API(2,0) uint32_t version_datestamp(); /** * Get the major version number. * @return major version number */ BOTAN_PUBLIC_API(2,0) uint32_t version_major(); /** * Get the minor version number. * @return minor version number */ BOTAN_PUBLIC_API(2,0) uint32_t version_minor(); /** * Get the patch number. * @return patch number */ BOTAN_PUBLIC_API(2,0) uint32_t version_patch(); /** * Usable for checking that the DLL version loaded at runtime exactly * matches the compile-time version. Call using BOTAN_VERSION_* macro * values. Returns the empty string if an exact match, otherwise an * appropriate message. Added with 1.11.26. */ BOTAN_PUBLIC_API(2,0) std::string runtime_version_check(uint32_t major, uint32_t minor, uint32_t patch); /* * Macros for compile-time version checks */ #define BOTAN_VERSION_CODE_FOR(a,b,c) ((a << 16) | (b << 8) | (c)) /** * Compare using BOTAN_VERSION_CODE_FOR, as in * # if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,8,0) * # error "Botan version too old" * # endif */ #define BOTAN_VERSION_CODE BOTAN_VERSION_CODE_FOR(BOTAN_VERSION_MAJOR, \ BOTAN_VERSION_MINOR, \ BOTAN_VERSION_PATCH) } #endif // BOTAN_AMALGAMATION_H_ OSCAR-code-v1.3.1/oscar/SleepLib/thirdparty/botan_windows.h000066400000000000000000005257101417327530600235310ustar00rootroot00000000000000/* * Botan 2.18.2 Amalgamation * (C) 1999-2020 The Botan Authors * * Botan is released under the Simplified BSD License (see license.txt) */ #ifndef BOTAN_AMALGAMATION_H_ #define BOTAN_AMALGAMATION_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Build configuration for Botan 2.18.2 * * Automatically generated from * 'configure.py --amalgamation --os=mingw --cpu=generic --disable-shared --minimized-build --enable-modules=aes,gcm' * * Target * - Compiler: g++ -pthread -std=c++11 -D_REENTRANT -O3 * - Arch: generic * - OS: mingw */ #define BOTAN_VERSION_MAJOR 2 #define BOTAN_VERSION_MINOR 18 #define BOTAN_VERSION_PATCH 2 #define BOTAN_VERSION_DATESTAMP 20211025 #define BOTAN_VERSION_RELEASE_TYPE "release" #define BOTAN_VERSION_VC_REVISION "git:a44f1489239e80937ca67564ff103421e5584069" #define BOTAN_DISTRIBUTION_INFO "unspecified" /* How many bits per limb in a BigInt */ #define BOTAN_MP_WORD_BITS 32 #define BOTAN_INSTALL_PREFIX R"(/mingw)" #define BOTAN_INSTALL_HEADER_DIR R"(include/botan-2)" #define BOTAN_INSTALL_LIB_DIR R"(/mingw/lib)" #define BOTAN_LIB_LINK "" #define BOTAN_LINK_FLAGS "-pthread" #define BOTAN_SYSTEM_CERT_BUNDLE "/etc/ssl/cert.pem" #ifndef BOTAN_DLL #define BOTAN_DLL #endif /* Target identification and feature test macros */ #define BOTAN_TARGET_OS_IS_MINGW #define BOTAN_TARGET_OS_HAS_ATOMICS #define BOTAN_TARGET_OS_HAS_CERTIFICATE_STORE #define BOTAN_TARGET_OS_HAS_FILESYSTEM #define BOTAN_TARGET_OS_HAS_RTLGENRANDOM #define BOTAN_TARGET_OS_HAS_THREAD_LOCAL #define BOTAN_TARGET_OS_HAS_THREADS #define BOTAN_TARGET_OS_HAS_VIRTUAL_LOCK #define BOTAN_TARGET_OS_HAS_WIN32 #define BOTAN_BUILD_COMPILER_IS_GCC #define BOTAN_TARGET_ARCH_IS_GENERIC /* * Module availability definitions */ #define BOTAN_HAS_AEAD_GCM 20131128 #define BOTAN_HAS_AEAD_MODES 20131128 #define BOTAN_HAS_AES 20131128 #define BOTAN_HAS_BLOCK_CIPHER 20131128 #define BOTAN_HAS_CIPHER_MODES 20180124 #define BOTAN_HAS_CPUID 20170917 #define BOTAN_HAS_CTR_BE 20131128 #define BOTAN_HAS_ENTROPY_SOURCE 20151120 #define BOTAN_HAS_GHASH 20201002 #define BOTAN_HAS_HEX_CODEC 20131128 #define BOTAN_HAS_MODES 20150626 #define BOTAN_HAS_STREAM_CIPHER 20131128 #define BOTAN_HAS_UTIL_FUNCTIONS 20180903 /* * Local/misc configuration options (if any) follow */ /* * Things you can edit (but probably shouldn't) */ #if !defined(BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES) #if defined(BOTAN_NO_DEPRECATED) #define BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES private #else #define BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES public #endif #endif /* How much to allocate for a buffer of no particular size */ #define BOTAN_DEFAULT_BUFFER_SIZE 1024 /* * Total maximum amount of RAM (in KiB) we will lock into memory, even * if the OS would let us lock more */ #define BOTAN_MLOCK_ALLOCATOR_MAX_LOCKED_KB 512 /* * If BOTAN_MEM_POOL_USE_MMU_PROTECTIONS is defined, the Memory_Pool * class used for mlock'ed memory will use OS calls to set page * permissions so as to prohibit access to pages on the free list, then * enable read/write access when the page is set to be used. This will * turn (some) use after free bugs into a crash. * * The additional syscalls have a substantial performance impact, which * is why this option is not enabled by default. */ #if defined(BOTAN_HAS_VALGRIND) || defined(BOTAN_ENABLE_DEBUG_ASSERTS) #define BOTAN_MEM_POOL_USE_MMU_PROTECTIONS #endif /* * If enabled uses memset via volatile function pointer to zero memory, * otherwise does a byte at a time write via a volatile pointer. */ #define BOTAN_USE_VOLATILE_MEMSET_FOR_ZERO 1 /* * Normally blinding is performed by choosing a random starting point (plus * its inverse, of a form appropriate to the algorithm being blinded), and * then choosing new blinding operands by successive squaring of both * values. This is much faster than computing a new starting point but * introduces some possible corelation * * To avoid possible leakage problems in long-running processes, the blinder * periodically reinitializes the sequence. This value specifies how often * a new sequence should be started. */ #define BOTAN_BLINDING_REINIT_INTERVAL 64 /* * Userspace RNGs like HMAC_DRBG will reseed after a specified number * of outputs are generated. Set to zero to disable automatic reseeding. */ #define BOTAN_RNG_DEFAULT_RESEED_INTERVAL 1024 #define BOTAN_RNG_RESEED_POLL_BITS 256 #define BOTAN_RNG_AUTO_RESEED_TIMEOUT std::chrono::milliseconds(10) #define BOTAN_RNG_RESEED_DEFAULT_TIMEOUT std::chrono::milliseconds(50) /* * Specifies (in order) the list of entropy sources that will be used * to seed an in-memory RNG. */ #define BOTAN_ENTROPY_DEFAULT_SOURCES \ { "rdseed", "hwrng", "p9_darn", "getentropy", "dev_random", \ "system_rng", "proc_walk", "system_stats" } /* Multiplier on a block cipher's native parallelism */ #define BOTAN_BLOCK_CIPHER_PAR_MULT 4 /* * These control the RNG used by the system RNG interface */ #define BOTAN_SYSTEM_RNG_DEVICE "/dev/urandom" #define BOTAN_SYSTEM_RNG_POLL_DEVICES { "/dev/urandom", "/dev/random" } /* * This directory will be monitored by ProcWalking_EntropySource and * the contents provided as entropy inputs to the RNG. May also be * usefully set to something like "/sys", depending on the system being * deployed to. Set to an empty string to disable. */ #define BOTAN_ENTROPY_PROC_FS_PATH "/proc" /* * These paramaters control how many bytes to read from the system * PRNG, and how long to block if applicable. The timeout only applies * to reading /dev/urandom and company. */ #define BOTAN_SYSTEM_RNG_POLL_REQUEST 64 #define BOTAN_SYSTEM_RNG_POLL_TIMEOUT_MS 20 /* * When a PBKDF is self-tuning parameters, it will attempt to take about this * amount of time to self-benchmark. */ #define BOTAN_PBKDF_TUNING_TIME std::chrono::milliseconds(10) /* * If no way of dynamically determining the cache line size for the * system exists, this value is used as the default. Used by the side * channel countermeasures rather than for alignment purposes, so it is * better to be on the smaller side if the exact value cannot be * determined. Typically 32 or 64 bytes on modern CPUs. */ #if !defined(BOTAN_TARGET_CPU_DEFAULT_CACHE_LINE_SIZE) #define BOTAN_TARGET_CPU_DEFAULT_CACHE_LINE_SIZE 32 #endif /** * Controls how AutoSeeded_RNG is instantiated */ #if !defined(BOTAN_AUTO_RNG_HMAC) #if defined(BOTAN_HAS_SHA2_64) #define BOTAN_AUTO_RNG_HMAC "HMAC(SHA-384)" #elif defined(BOTAN_HAS_SHA2_32) #define BOTAN_AUTO_RNG_HMAC "HMAC(SHA-256)" #elif defined(BOTAN_HAS_SHA3) #define BOTAN_AUTO_RNG_HMAC "HMAC(SHA-3(256))" #elif defined(BOTAN_HAS_SHA1) #define BOTAN_AUTO_RNG_HMAC "HMAC(SHA-1)" #endif /* Otherwise, no hash found: leave BOTAN_AUTO_RNG_HMAC undefined */ #endif /* Check for a common build problem */ #if defined(BOTAN_TARGET_ARCH_IS_X86_64) && ((defined(_MSC_VER) && !defined(_WIN64)) || \ (defined(__clang__) && !defined(__x86_64__)) || \ (defined(__GNUG__) && !defined(__x86_64__))) #error "Trying to compile Botan configured as x86_64 with non-x86_64 compiler." #endif #if defined(BOTAN_TARGET_ARCH_IS_X86_32) && ((defined(_MSC_VER) && defined(_WIN64)) || \ (defined(__clang__) && !defined(__i386__)) || \ (defined(__GNUG__) && !defined(__i386__))) #error "Trying to compile Botan configured as x86_32 with non-x86_32 compiler." #endif /* Should we use GCC-style inline assembler? */ #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || \ defined(BOTAN_BUILD_COMPILER_IS_CLANG) || \ defined(BOTAN_BUILD_COMPILER_IS_XLC) || \ defined(BOTAN_BUILD_COMPILER_IS_SUN_STUDIO) #define BOTAN_USE_GCC_INLINE_ASM #endif /** * Used to annotate API exports which are public and supported. * These APIs will not be broken/removed unless strictly required for * functionality or security, and only in new major versions. * @param maj The major version this public API was released in * @param min The minor version this public API was released in */ #define BOTAN_PUBLIC_API(maj,min) BOTAN_DLL /** * Used to annotate API exports which are public, but are now deprecated * and which will be removed in a future major release. */ #define BOTAN_DEPRECATED_API(msg) BOTAN_DLL BOTAN_DEPRECATED(msg) /** * Used to annotate API exports which are public and can be used by * applications if needed, but which are intentionally not documented, * and which may change incompatibly in a future major version. */ #define BOTAN_UNSTABLE_API BOTAN_DLL /** * Used to annotate API exports which are exported but only for the * purposes of testing. They should not be used by applications and * may be removed or changed without notice. */ #define BOTAN_TEST_API BOTAN_DLL /* * Define BOTAN_GCC_VERSION */ #if defined(__GNUC__) && !defined(__clang__) #define BOTAN_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__ * 10 + __GNUC_PATCHLEVEL__) #else #define BOTAN_GCC_VERSION 0 #endif /* * Define BOTAN_CLANG_VERSION */ #if defined(__clang__) #define BOTAN_CLANG_VERSION (__clang_major__ * 10 + __clang_minor__) #else #define BOTAN_CLANG_VERSION 0 #endif /* * Define BOTAN_FUNC_ISA */ #if (defined(__GNUC__) && !defined(__clang__)) || (BOTAN_CLANG_VERSION > 38) #define BOTAN_FUNC_ISA(isa) __attribute__ ((target(isa))) #else #define BOTAN_FUNC_ISA(isa) #endif /* * Define BOTAN_WARN_UNUSED_RESULT */ #if defined(__GNUC__) || defined(__clang__) #define BOTAN_WARN_UNUSED_RESULT __attribute__ ((warn_unused_result)) #else #define BOTAN_WARN_UNUSED_RESULT #endif /* * Define BOTAN_MALLOC_FN */ #if defined(__ibmxl__) /* XLC pretends to be both Clang and GCC, but is neither */ #define BOTAN_MALLOC_FN __attribute__ ((malloc)) #elif defined(__GNUC__) #define BOTAN_MALLOC_FN __attribute__ ((malloc, alloc_size(1,2))) #elif defined(_MSC_VER) #define BOTAN_MALLOC_FN __declspec(restrict) #else #define BOTAN_MALLOC_FN #endif /* * Define BOTAN_DEPRECATED */ #if !defined(BOTAN_NO_DEPRECATED_WARNINGS) && !defined(BOTAN_IS_BEING_BUILT) && !defined(BOTAN_AMALGAMATION_H_) #if defined(__clang__) #define BOTAN_DEPRECATED(msg) __attribute__ ((deprecated(msg))) #define BOTAN_DEPRECATED_HEADER(hdr) _Pragma("message \"this header is deprecated\"") #define BOTAN_FUTURE_INTERNAL_HEADER(hdr) _Pragma("message \"this header will be made internal in the future\"") #elif defined(_MSC_VER) #define BOTAN_DEPRECATED(msg) __declspec(deprecated(msg)) #define BOTAN_DEPRECATED_HEADER(hdr) __pragma(message("this header is deprecated")) #define BOTAN_FUTURE_INTERNAL_HEADER(hdr) __pragma(message("this header will be made internal in the future")) #elif defined(__GNUC__) /* msg supported since GCC 4.5, earliest we support is 4.8 */ #define BOTAN_DEPRECATED(msg) __attribute__ ((deprecated(msg))) #define BOTAN_DEPRECATED_HEADER(hdr) _Pragma("GCC warning \"this header is deprecated\"") #define BOTAN_FUTURE_INTERNAL_HEADER(hdr) _Pragma("GCC warning \"this header will be made internal in the future\"") #endif #endif #if !defined(BOTAN_DEPRECATED) #define BOTAN_DEPRECATED(msg) #endif #if !defined(BOTAN_DEPRECATED_HEADER) #define BOTAN_DEPRECATED_HEADER(hdr) #endif #if !defined(BOTAN_FUTURE_INTERNAL_HEADER) #define BOTAN_FUTURE_INTERNAL_HEADER(hdr) #endif /* * Define BOTAN_NORETURN */ #if !defined(BOTAN_NORETURN) #if defined (__clang__) || defined (__GNUC__) #define BOTAN_NORETURN __attribute__ ((__noreturn__)) #elif defined (_MSC_VER) #define BOTAN_NORETURN __declspec(noreturn) #else #define BOTAN_NORETURN #endif #endif /* * Define BOTAN_THREAD_LOCAL */ #if !defined(BOTAN_THREAD_LOCAL) #if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_TARGET_OS_HAS_THREAD_LOCAL) #define BOTAN_THREAD_LOCAL thread_local #else #define BOTAN_THREAD_LOCAL /**/ #endif #endif /* * Define BOTAN_IF_CONSTEXPR */ #if !defined(BOTAN_IF_CONSTEXPR) #if __cplusplus >= 201703 #define BOTAN_IF_CONSTEXPR if constexpr #else #define BOTAN_IF_CONSTEXPR if #endif #endif /* * Define BOTAN_PARALLEL_FOR */ #if !defined(BOTAN_PARALLEL_FOR) #if defined(BOTAN_TARGET_HAS_OPENMP) #define BOTAN_PARALLEL_FOR _Pragma("omp parallel for") for #else #define BOTAN_PARALLEL_FOR for #endif #endif /* * Define BOTAN_FORCE_INLINE */ #if !defined(BOTAN_FORCE_INLINE) #if defined (__clang__) || defined (__GNUC__) #define BOTAN_FORCE_INLINE __attribute__ ((__always_inline__)) inline #elif defined (_MSC_VER) #define BOTAN_FORCE_INLINE __forceinline #else #define BOTAN_FORCE_INLINE inline #endif #endif /* * Define BOTAN_PARALLEL_SIMD_FOR */ #if !defined(BOTAN_PARALLEL_SIMD_FOR) #if defined(BOTAN_TARGET_HAS_OPENMP) #define BOTAN_PARALLEL_SIMD_FOR _Pragma("omp simd") for #elif defined(BOTAN_BUILD_COMPILER_IS_GCC) && (BOTAN_GCC_VERSION >= 490) #define BOTAN_PARALLEL_SIMD_FOR _Pragma("GCC ivdep") for #else #define BOTAN_PARALLEL_SIMD_FOR for #endif #endif namespace Botan { /** * Called when an assertion fails * Throws an Exception object */ BOTAN_NORETURN void BOTAN_PUBLIC_API(2,0) assertion_failure(const char* expr_str, const char* assertion_made, const char* func, const char* file, int line); /** * Called when an invalid argument is used * Throws Invalid_Argument */ BOTAN_NORETURN void BOTAN_UNSTABLE_API throw_invalid_argument(const char* message, const char* func, const char* file); #define BOTAN_ARG_CHECK(expr, msg) \ do { if(!(expr)) Botan::throw_invalid_argument(msg, __func__, __FILE__); } while(0) /** * Called when an invalid state is encountered * Throws Invalid_State */ BOTAN_NORETURN void BOTAN_UNSTABLE_API throw_invalid_state(const char* message, const char* func, const char* file); #define BOTAN_STATE_CHECK(expr) \ do { if(!(expr)) Botan::throw_invalid_state(#expr, __func__, __FILE__); } while(0) /** * Make an assertion */ #define BOTAN_ASSERT(expr, assertion_made) \ do { \ if(!(expr)) \ Botan::assertion_failure(#expr, \ assertion_made, \ __func__, \ __FILE__, \ __LINE__); \ } while(0) /** * Make an assertion */ #define BOTAN_ASSERT_NOMSG(expr) \ do { \ if(!(expr)) \ Botan::assertion_failure(#expr, \ "", \ __func__, \ __FILE__, \ __LINE__); \ } while(0) /** * Assert that value1 == value2 */ #define BOTAN_ASSERT_EQUAL(expr1, expr2, assertion_made) \ do { \ if((expr1) != (expr2)) \ Botan::assertion_failure(#expr1 " == " #expr2, \ assertion_made, \ __func__, \ __FILE__, \ __LINE__); \ } while(0) /** * Assert that expr1 (if true) implies expr2 is also true */ #define BOTAN_ASSERT_IMPLICATION(expr1, expr2, msg) \ do { \ if((expr1) && !(expr2)) \ Botan::assertion_failure(#expr1 " implies " #expr2, \ msg, \ __func__, \ __FILE__, \ __LINE__); \ } while(0) /** * Assert that a pointer is not null */ #define BOTAN_ASSERT_NONNULL(ptr) \ do { \ if((ptr) == nullptr) \ Botan::assertion_failure(#ptr " is not null", \ "", \ __func__, \ __FILE__, \ __LINE__); \ } while(0) #if defined(BOTAN_ENABLE_DEBUG_ASSERTS) #define BOTAN_DEBUG_ASSERT(expr) BOTAN_ASSERT_NOMSG(expr) #else #define BOTAN_DEBUG_ASSERT(expr) do {} while(0) #endif /** * Mark variable as unused. Takes between 1 and 9 arguments and marks all as unused, * e.g. BOTAN_UNUSED(a); or BOTAN_UNUSED(x, y, z); */ #define _BOTAN_UNUSED_IMPL1(a) static_cast(a) #define _BOTAN_UNUSED_IMPL2(a, b) static_cast(a); _BOTAN_UNUSED_IMPL1(b) #define _BOTAN_UNUSED_IMPL3(a, b, c) static_cast(a); _BOTAN_UNUSED_IMPL2(b, c) #define _BOTAN_UNUSED_IMPL4(a, b, c, d) static_cast(a); _BOTAN_UNUSED_IMPL3(b, c, d) #define _BOTAN_UNUSED_IMPL5(a, b, c, d, e) static_cast(a); _BOTAN_UNUSED_IMPL4(b, c, d, e) #define _BOTAN_UNUSED_IMPL6(a, b, c, d, e, f) static_cast(a); _BOTAN_UNUSED_IMPL5(b, c, d, e, f) #define _BOTAN_UNUSED_IMPL7(a, b, c, d, e, f, g) static_cast(a); _BOTAN_UNUSED_IMPL6(b, c, d, e, f, g) #define _BOTAN_UNUSED_IMPL8(a, b, c, d, e, f, g, h) static_cast(a); _BOTAN_UNUSED_IMPL7(b, c, d, e, f, g, h) #define _BOTAN_UNUSED_IMPL9(a, b, c, d, e, f, g, h, i) static_cast(a); _BOTAN_UNUSED_IMPL8(b, c, d, e, f, g, h, i) #define _BOTAN_UNUSED_GET_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, IMPL_NAME, ...) IMPL_NAME #define BOTAN_UNUSED(...) _BOTAN_UNUSED_GET_IMPL(__VA_ARGS__, \ _BOTAN_UNUSED_IMPL9, \ _BOTAN_UNUSED_IMPL8, \ _BOTAN_UNUSED_IMPL7, \ _BOTAN_UNUSED_IMPL6, \ _BOTAN_UNUSED_IMPL5, \ _BOTAN_UNUSED_IMPL4, \ _BOTAN_UNUSED_IMPL3, \ _BOTAN_UNUSED_IMPL2, \ _BOTAN_UNUSED_IMPL1, \ unused dummy rest value \ ) /* we got an one of _BOTAN_UNUSED_IMPL*, now call it */ (__VA_ARGS__) } namespace Botan { /** * @mainpage Botan Crypto Library API Reference * *
*
Abstract Base Classes
* BlockCipher, HashFunction, KDF, MessageAuthenticationCode, RandomNumberGenerator, * StreamCipher, SymmetricAlgorithm, AEAD_Mode, Cipher_Mode *
Public Key Interface Classes
* PK_Key_Agreement, PK_Signer, PK_Verifier, PK_Encryptor, PK_Decryptor *
Authenticated Encryption Modes
* @ref CCM_Mode "CCM", @ref ChaCha20Poly1305_Mode "ChaCha20Poly1305", @ref EAX_Mode "EAX", * @ref GCM_Mode "GCM", @ref OCB_Mode "OCB", @ref SIV_Mode "SIV" *
Block Ciphers
* @ref aria.h "ARIA", @ref aes.h "AES", @ref Blowfish, @ref camellia.h "Camellia", @ref Cascade_Cipher "Cascade", * @ref CAST_128 "CAST-128", @ref CAST_128 "CAST-256", DES, @ref DESX "DES-X", @ref TripleDES "3DES", * @ref GOST_28147_89 "GOST 28147-89", IDEA, KASUMI, Lion, MISTY1, Noekeon, SEED, Serpent, SHACAL2, SM4, * @ref Threefish_512 "Threefish", Twofish, XTEA *
Stream Ciphers
* ChaCha, @ref CTR_BE "CTR", OFB, RC4, Salsa20 *
Hash Functions
* BLAKE2b, @ref GOST_34_11 "GOST 34.11", @ref Keccak_1600 "Keccak", MD4, MD5, @ref RIPEMD_160 "RIPEMD-160", * @ref SHA_160 "SHA-1", @ref SHA_224 "SHA-224", @ref SHA_256 "SHA-256", @ref SHA_384 "SHA-384", * @ref SHA_512 "SHA-512", @ref Skein_512 "Skein-512", SM3, Streebog, Tiger, Whirlpool *
Non-Cryptographic Checksums
* Adler32, CRC24, CRC32 *
Message Authentication Codes
* @ref CBC_MAC "CBC-MAC", CMAC, HMAC, Poly1305, SipHash, ANSI_X919_MAC *
Random Number Generators
* AutoSeeded_RNG, HMAC_DRBG, Processor_RNG, System_RNG *
Key Derivation
* HKDF, @ref KDF1 "KDF1 (IEEE 1363)", @ref KDF1_18033 "KDF1 (ISO 18033-2)", @ref KDF2 "KDF2 (IEEE 1363)", * @ref sp800_108.h "SP800-108", @ref SP800_56C "SP800-56C", @ref PKCS5_PBKDF1 "PBKDF1 (PKCS#5), * @ref PKCS5_PBKDF2 "PBKDF2 (PKCS#5)" *
Password Hashing
* @ref argon2.h "Argon2", @ref scrypt.h "scrypt", @ref bcrypt.h "bcrypt", @ref passhash9.h "passhash9" *
Public Key Cryptosystems
* @ref dlies.h "DLIES", @ref ecies.h "ECIES", @ref elgamal.h "ElGamal" * @ref rsa.h "RSA", @ref newhope.h "NewHope", @ref mceliece.h "McEliece" and @ref mceies.h "MCEIES", * @ref sm2.h "SM2" *
Public Key Signature Schemes
* @ref dsa.h "DSA", @ref ecdsa.h "ECDSA", @ref ecgdsa.h "ECGDSA", @ref eckcdsa.h "ECKCDSA", * @ref gost_3410.h "GOST 34.10-2001", @ref sm2.h "SM2", @ref xmss.h "XMSS" *
Key Agreement
* @ref dh.h "DH", @ref ecdh.h "ECDH" *
Compression
* @ref bzip2.h "bzip2", @ref lzma.h "lzma", @ref zlib.h "zlib" *
TLS
* TLS::Client, TLS::Server, TLS::Policy, TLS::Protocol_Version, TLS::Callbacks, TLS::Ciphersuite, * TLS::Session, TLS::Session_Manager, Credentials_Manager *
X.509
* X509_Certificate, X509_CRL, X509_CA, Certificate_Extension, PKCS10_Request, X509_Cert_Options, * Certificate_Store, Certificate_Store_In_SQL, Certificate_Store_In_SQLite *
*/ using std::uint8_t; using std::uint16_t; using std::uint32_t; using std::uint64_t; using std::int32_t; using std::int64_t; using std::size_t; /* * These typedefs are no longer used within the library headers * or code. They are kept only for compatability with software * written against older versions. */ using byte = std::uint8_t; using u16bit = std::uint16_t; using u32bit = std::uint32_t; using u64bit = std::uint64_t; using s32bit = std::int32_t; #if (BOTAN_MP_WORD_BITS == 32) typedef uint32_t word; #elif (BOTAN_MP_WORD_BITS == 64) typedef uint64_t word; #else #error BOTAN_MP_WORD_BITS must be 32 or 64 #endif /* * Should this assert fail on your system please contact the developers * for assistance in porting. */ static_assert(sizeof(std::size_t) == 8 || sizeof(std::size_t) == 4, "This platform has an unexpected size for size_t"); } namespace Botan { /** * Allocate a memory buffer by some method. This should only be used for * primitive types (uint8_t, uint32_t, etc). * * @param elems the number of elements * @param elem_size the size of each element * @return pointer to allocated and zeroed memory, or throw std::bad_alloc on failure */ BOTAN_PUBLIC_API(2,3) BOTAN_MALLOC_FN void* allocate_memory(size_t elems, size_t elem_size); /** * Free a pointer returned by allocate_memory * @param p the pointer returned by allocate_memory * @param elems the number of elements, as passed to allocate_memory * @param elem_size the size of each element, as passed to allocate_memory */ BOTAN_PUBLIC_API(2,3) void deallocate_memory(void* p, size_t elems, size_t elem_size); /** * Ensure the allocator is initialized */ void BOTAN_UNSTABLE_API initialize_allocator(); class Allocator_Initializer { public: Allocator_Initializer() { initialize_allocator(); } }; /** * Scrub memory contents in a way that a compiler should not elide, * using some system specific technique. Note that this function might * not zero the memory (for example, in some hypothetical * implementation it might combine the memory contents with the output * of a system PRNG), but if you can detect any difference in behavior * at runtime then the clearing is side-effecting and you can just * use `clear_mem`. * * Use this function to scrub memory just before deallocating it, or on * a stack buffer before returning from the function. * * @param ptr a pointer to memory to scrub * @param n the number of bytes pointed to by ptr */ BOTAN_PUBLIC_API(2,0) void secure_scrub_memory(void* ptr, size_t n); /** * Memory comparison, input insensitive * @param x a pointer to an array * @param y a pointer to another array * @param len the number of Ts in x and y * @return 0xFF iff x[i] == y[i] forall i in [0...n) or 0x00 otherwise */ BOTAN_PUBLIC_API(2,9) uint8_t ct_compare_u8(const uint8_t x[], const uint8_t y[], size_t len); /** * Memory comparison, input insensitive * @param x a pointer to an array * @param y a pointer to another array * @param len the number of Ts in x and y * @return true iff x[i] == y[i] forall i in [0...n) */ inline bool constant_time_compare(const uint8_t x[], const uint8_t y[], size_t len) { return ct_compare_u8(x, y, len) == 0xFF; } /** * Zero out some bytes. Warning: use secure_scrub_memory instead if the * memory is about to be freed or otherwise the compiler thinks it can * elide the writes. * * @param ptr a pointer to memory to zero * @param bytes the number of bytes to zero in ptr */ inline void clear_bytes(void* ptr, size_t bytes) { if(bytes > 0) { std::memset(ptr, 0, bytes); } } /** * Zero memory before use. This simply calls memset and should not be * used in cases where the compiler cannot see the call as a * side-effecting operation (for example, if calling clear_mem before * deallocating memory, the compiler would be allowed to omit the call * to memset entirely under the as-if rule.) * * @param ptr a pointer to an array of Ts to zero * @param n the number of Ts pointed to by ptr */ template inline void clear_mem(T* ptr, size_t n) { clear_bytes(ptr, sizeof(T)*n); } // is_trivially_copyable is missing in g++ < 5.0 #if (BOTAN_GCC_VERSION > 0 && BOTAN_GCC_VERSION < 500) #define BOTAN_IS_TRIVIALLY_COPYABLE(T) true #else #define BOTAN_IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable::value #endif /** * Copy memory * @param out the destination array * @param in the source array * @param n the number of elements of in/out */ template inline void copy_mem(T* out, const T* in, size_t n) { static_assert(std::is_trivial::type>::value, ""); BOTAN_ASSERT_IMPLICATION(n > 0, in != nullptr && out != nullptr, "If n > 0 then args are not null"); if(in != nullptr && out != nullptr && n > 0) { std::memmove(out, in, sizeof(T)*n); } } template inline void typecast_copy(uint8_t out[], T in[], size_t N) { static_assert(BOTAN_IS_TRIVIALLY_COPYABLE(T), ""); std::memcpy(out, in, sizeof(T)*N); } template inline void typecast_copy(T out[], const uint8_t in[], size_t N) { static_assert(std::is_trivial::value, ""); std::memcpy(out, in, sizeof(T)*N); } template inline void typecast_copy(uint8_t out[], T in) { typecast_copy(out, &in, 1); } template inline void typecast_copy(T& out, const uint8_t in[]) { static_assert(std::is_trivial::type>::value, ""); typecast_copy(&out, in, 1); } template inline To typecast_copy(const From *src) noexcept { static_assert(BOTAN_IS_TRIVIALLY_COPYABLE(From) && std::is_trivial::value, ""); To dst; std::memcpy(&dst, src, sizeof(To)); return dst; } /** * Set memory to a fixed value * @param ptr a pointer to an array of bytes * @param n the number of Ts pointed to by ptr * @param val the value to set each byte to */ inline void set_mem(uint8_t* ptr, size_t n, uint8_t val) { if(n > 0) { std::memset(ptr, val, n); } } inline const uint8_t* cast_char_ptr_to_uint8(const char* s) { return reinterpret_cast(s); } inline const char* cast_uint8_ptr_to_char(const uint8_t* b) { return reinterpret_cast(b); } inline uint8_t* cast_char_ptr_to_uint8(char* s) { return reinterpret_cast(s); } inline char* cast_uint8_ptr_to_char(uint8_t* b) { return reinterpret_cast(b); } /** * Memory comparison, input insensitive * @param p1 a pointer to an array * @param p2 a pointer to another array * @param n the number of Ts in p1 and p2 * @return true iff p1[i] == p2[i] forall i in [0...n) */ template inline bool same_mem(const T* p1, const T* p2, size_t n) { volatile T difference = 0; for(size_t i = 0; i != n; ++i) difference |= (p1[i] ^ p2[i]); return difference == 0; } template size_t buffer_insert(std::vector& buf, size_t buf_offset, const T input[], size_t input_length) { BOTAN_ASSERT_NOMSG(buf_offset <= buf.size()); const size_t to_copy = std::min(input_length, buf.size() - buf_offset); if(to_copy > 0) { copy_mem(&buf[buf_offset], input, to_copy); } return to_copy; } template size_t buffer_insert(std::vector& buf, size_t buf_offset, const std::vector& input) { BOTAN_ASSERT_NOMSG(buf_offset <= buf.size()); const size_t to_copy = std::min(input.size(), buf.size() - buf_offset); if(to_copy > 0) { copy_mem(&buf[buf_offset], input.data(), to_copy); } return to_copy; } /** * XOR arrays. Postcondition out[i] = in[i] ^ out[i] forall i = 0...length * @param out the input/output buffer * @param in the read-only input buffer * @param length the length of the buffers */ inline void xor_buf(uint8_t out[], const uint8_t in[], size_t length) { const size_t blocks = length - (length % 32); for(size_t i = 0; i != blocks; i += 32) { uint64_t x[4]; uint64_t y[4]; typecast_copy(x, out + i, 4); typecast_copy(y, in + i, 4); x[0] ^= y[0]; x[1] ^= y[1]; x[2] ^= y[2]; x[3] ^= y[3]; typecast_copy(out + i, x, 4); } for(size_t i = blocks; i != length; ++i) { out[i] ^= in[i]; } } /** * XOR arrays. Postcondition out[i] = in[i] ^ in2[i] forall i = 0...length * @param out the output buffer * @param in the first input buffer * @param in2 the second output buffer * @param length the length of the three buffers */ inline void xor_buf(uint8_t out[], const uint8_t in[], const uint8_t in2[], size_t length) { const size_t blocks = length - (length % 32); for(size_t i = 0; i != blocks; i += 32) { uint64_t x[4]; uint64_t y[4]; typecast_copy(x, in + i, 4); typecast_copy(y, in2 + i, 4); x[0] ^= y[0]; x[1] ^= y[1]; x[2] ^= y[2]; x[3] ^= y[3]; typecast_copy(out + i, x, 4); } for(size_t i = blocks; i != length; ++i) { out[i] = in[i] ^ in2[i]; } } template void xor_buf(std::vector& out, const std::vector& in, size_t n) { xor_buf(out.data(), in.data(), n); } template void xor_buf(std::vector& out, const uint8_t* in, size_t n) { xor_buf(out.data(), in, n); } template void xor_buf(std::vector& out, const uint8_t* in, const std::vector& in2, size_t n) { xor_buf(out.data(), in, in2.data(), n); } template std::vector& operator^=(std::vector& out, const std::vector& in) { if(out.size() < in.size()) out.resize(in.size()); xor_buf(out.data(), in.data(), in.size()); return out; } } namespace Botan { template class secure_allocator { public: /* * Assert exists to prevent someone from doing something that will * probably crash anyway (like secure_vector where ~non_POD_t * deletes a member pointer which was zeroed before it ran). * MSVC in debug mode uses non-integral proxy types in container types * like std::vector, thus we disable the check there. */ #if !defined(_ITERATOR_DEBUG_LEVEL) || _ITERATOR_DEBUG_LEVEL == 0 static_assert(std::is_integral::value, "secure_allocator supports only integer types"); #endif typedef T value_type; typedef std::size_t size_type; secure_allocator() noexcept = default; secure_allocator(const secure_allocator&) noexcept = default; secure_allocator& operator=(const secure_allocator&) noexcept = default; ~secure_allocator() noexcept = default; template secure_allocator(const secure_allocator&) noexcept {} T* allocate(std::size_t n) { return static_cast(allocate_memory(n, sizeof(T))); } void deallocate(T* p, std::size_t n) { deallocate_memory(p, n, sizeof(T)); } }; template inline bool operator==(const secure_allocator&, const secure_allocator&) { return true; } template inline bool operator!=(const secure_allocator&, const secure_allocator&) { return false; } template using secure_vector = std::vector>; template using secure_deque = std::deque>; // For better compatibility with 1.10 API template using SecureVector = secure_vector; template std::vector unlock(const secure_vector& in) { return std::vector(in.begin(), in.end()); } template std::vector& operator+=(std::vector& out, const std::vector& in) { out.reserve(out.size() + in.size()); out.insert(out.end(), in.begin(), in.end()); return out; } template std::vector& operator+=(std::vector& out, T in) { out.push_back(in); return out; } template std::vector& operator+=(std::vector& out, const std::pair& in) { out.reserve(out.size() + in.second); out.insert(out.end(), in.first, in.first + in.second); return out; } template std::vector& operator+=(std::vector& out, const std::pair& in) { out.reserve(out.size() + in.second); out.insert(out.end(), in.first, in.first + in.second); return out; } /** * Zeroise the values; length remains unchanged * @param vec the vector to zeroise */ template void zeroise(std::vector& vec) { clear_mem(vec.data(), vec.size()); } /** * Zeroise the values then free the memory * @param vec the vector to zeroise and free */ template void zap(std::vector& vec) { zeroise(vec); vec.clear(); vec.shrink_to_fit(); } } namespace Botan { /** * Octet String */ class BOTAN_PUBLIC_API(2,0) OctetString final { public: /** * @return size of this octet string in bytes */ size_t length() const { return m_data.size(); } size_t size() const { return m_data.size(); } /** * @return this object as a secure_vector */ secure_vector bits_of() const { return m_data; } /** * @return start of this string */ const uint8_t* begin() const { return m_data.data(); } /** * @return end of this string */ const uint8_t* end() const { return begin() + m_data.size(); } /** * @return this encoded as hex */ std::string to_string() const; std::string BOTAN_DEPRECATED("Use OctetString::to_string") as_string() const { return this->to_string(); } /** * XOR the contents of another octet string into this one * @param other octet string * @return reference to this */ OctetString& operator^=(const OctetString& other); /** * Force to have odd parity */ void set_odd_parity(); /** * Create a new OctetString * @param str is a hex encoded string */ explicit OctetString(const std::string& str = ""); /** * Create a new random OctetString * @param rng is a random number generator * @param len is the desired length in bytes */ OctetString(class RandomNumberGenerator& rng, size_t len); /** * Create a new OctetString * @param in is an array * @param len is the length of in in bytes */ OctetString(const uint8_t in[], size_t len); /** * Create a new OctetString * @param in a bytestring */ OctetString(const secure_vector& in) : m_data(in) {} /** * Create a new OctetString * @param in a bytestring */ OctetString(const std::vector& in) : m_data(in.begin(), in.end()) {} private: secure_vector m_data; }; /** * Compare two strings * @param x an octet string * @param y an octet string * @return if x is equal to y */ BOTAN_PUBLIC_API(2,0) bool operator==(const OctetString& x, const OctetString& y); /** * Compare two strings * @param x an octet string * @param y an octet string * @return if x is not equal to y */ BOTAN_PUBLIC_API(2,0) bool operator!=(const OctetString& x, const OctetString& y); /** * Concatenate two strings * @param x an octet string * @param y an octet string * @return x concatenated with y */ BOTAN_PUBLIC_API(2,0) OctetString operator+(const OctetString& x, const OctetString& y); /** * XOR two strings * @param x an octet string * @param y an octet string * @return x XORed with y */ BOTAN_PUBLIC_API(2,0) OctetString operator^(const OctetString& x, const OctetString& y); /** * Alternate name for octet string showing intent to use as a key */ using SymmetricKey = OctetString; /** * Alternate name for octet string showing intent to use as an IV */ using InitializationVector = OctetString; } namespace Botan { /** * Represents the length requirements on an algorithm key */ class BOTAN_PUBLIC_API(2,0) Key_Length_Specification final { public: /** * Constructor for fixed length keys * @param keylen the supported key length */ explicit Key_Length_Specification(size_t keylen) : m_min_keylen(keylen), m_max_keylen(keylen), m_keylen_mod(1) { } /** * Constructor for variable length keys * @param min_k the smallest supported key length * @param max_k the largest supported key length * @param k_mod the number of bytes the key must be a multiple of */ Key_Length_Specification(size_t min_k, size_t max_k, size_t k_mod = 1) : m_min_keylen(min_k), m_max_keylen(max_k ? max_k : min_k), m_keylen_mod(k_mod) { } /** * @param length is a key length in bytes * @return true iff this length is a valid length for this algo */ bool valid_keylength(size_t length) const { return ((length >= m_min_keylen) && (length <= m_max_keylen) && (length % m_keylen_mod == 0)); } /** * @return minimum key length in bytes */ size_t minimum_keylength() const { return m_min_keylen; } /** * @return maximum key length in bytes */ size_t maximum_keylength() const { return m_max_keylen; } /** * @return key length multiple in bytes */ size_t keylength_multiple() const { return m_keylen_mod; } /* * Multiplies all length requirements with the given factor * @param n the multiplication factor * @return a key length specification multiplied by the factor */ Key_Length_Specification multiple(size_t n) const { return Key_Length_Specification(n * m_min_keylen, n * m_max_keylen, n * m_keylen_mod); } private: size_t m_min_keylen, m_max_keylen, m_keylen_mod; }; /** * This class represents a symmetric algorithm object. */ class BOTAN_PUBLIC_API(2,0) SymmetricAlgorithm { public: virtual ~SymmetricAlgorithm() = default; /** * Reset the state. */ virtual void clear() = 0; /** * @return object describing limits on key size */ virtual Key_Length_Specification key_spec() const = 0; /** * @return maximum allowed key length */ size_t maximum_keylength() const { return key_spec().maximum_keylength(); } /** * @return minimum allowed key length */ size_t minimum_keylength() const { return key_spec().minimum_keylength(); } /** * Check whether a given key length is valid for this algorithm. * @param length the key length to be checked. * @return true if the key length is valid. */ bool valid_keylength(size_t length) const { return key_spec().valid_keylength(length); } /** * Set the symmetric key of this object. * @param key the SymmetricKey to be set. */ void set_key(const SymmetricKey& key) { set_key(key.begin(), key.length()); } template void set_key(const std::vector& key) { set_key(key.data(), key.size()); } /** * Set the symmetric key of this object. * @param key the to be set as a byte array. * @param length in bytes of key param */ void set_key(const uint8_t key[], size_t length); /** * @return the algorithm name */ virtual std::string name() const = 0; protected: void verify_key_set(bool cond) const { if(cond == false) throw_key_not_set_error(); } private: void throw_key_not_set_error() const; /** * Run the key schedule * @param key the key * @param length of key */ virtual void key_schedule(const uint8_t key[], size_t length) = 0; }; } namespace Botan { /** * Different types of errors that might occur */ enum class ErrorType { /** Some unknown error */ Unknown = 1, /** An error while calling a system interface */ SystemError, /** An operation seems valid, but not supported by the current version */ NotImplemented, /** Memory allocation failure */ OutOfMemory, /** An internal error occurred */ InternalError, /** An I/O error occurred */ IoError, /** Invalid object state */ InvalidObjectState = 100, /** A key was not set on an object when this is required */ KeyNotSet, /** The application provided an argument which is invalid */ InvalidArgument, /** A key with invalid length was provided */ InvalidKeyLength, /** A nonce with invalid length was provided */ InvalidNonceLength, /** An object type was requested but cannot be found */ LookupError, /** Encoding a message or datum failed */ EncodingFailure, /** Decoding a message or datum failed */ DecodingFailure, /** A TLS error (error_code will be the alert type) */ TLSError, /** An error during an HTTP operation */ HttpError, /** A message with an invalid authentication tag was detected */ InvalidTag, /** An error during Roughtime validation */ RoughtimeError, /** An error when calling OpenSSL */ OpenSSLError = 200, /** An error when interacting with CommonCrypto API */ CommonCryptoError, /** An error when interacting with a PKCS11 device */ Pkcs11Error, /** An error when interacting with a TPM device */ TPMError, /** An error when interacting with a database */ DatabaseError, /** An error when interacting with zlib */ ZlibError = 300, /** An error when interacting with bzip2 */ Bzip2Error, /** An error when interacting with lzma */ LzmaError, }; //! \brief Convert an ErrorType to string std::string BOTAN_PUBLIC_API(2,11) to_string(ErrorType type); /** * Base class for all exceptions thrown by the library */ class BOTAN_PUBLIC_API(2,0) Exception : public std::exception { public: /** * Return a descriptive string which is hopefully comprehensible to * a developer. It will likely not be useful for an end user. * * The string has no particular format, and the content of exception * messages may change from release to release. Thus the main use of this * function is for logging or debugging. */ const char* what() const noexcept override { return m_msg.c_str(); } /** * Return the "type" of error which occurred. */ virtual ErrorType error_type() const noexcept { return Botan::ErrorType::Unknown; } /** * Return an error code associated with this exception, or otherwise 0. * * The domain of this error varies depending on the source, for example on * POSIX systems it might be errno, while on a Windows system it might be * the result of GetLastError or WSAGetLastError. For error_type() is * OpenSSLError, it will (if nonzero) be an OpenSSL error code from * ERR_get_error. */ virtual int error_code() const noexcept { return 0; } /** * Avoid throwing base Exception, use a subclass */ explicit Exception(const std::string& msg); /** * Avoid throwing base Exception, use a subclass */ Exception(const char* prefix, const std::string& msg); /** * Avoid throwing base Exception, use a subclass */ Exception(const std::string& msg, const std::exception& e); private: std::string m_msg; }; /** * An invalid argument was provided to an API call. */ class BOTAN_PUBLIC_API(2,0) Invalid_Argument : public Exception { public: explicit Invalid_Argument(const std::string& msg); explicit Invalid_Argument(const std::string& msg, const std::string& where); Invalid_Argument(const std::string& msg, const std::exception& e); ErrorType error_type() const noexcept override { return ErrorType::InvalidArgument; } }; /** * An invalid key length was used */ class BOTAN_PUBLIC_API(2,0) Invalid_Key_Length final : public Invalid_Argument { public: Invalid_Key_Length(const std::string& name, size_t length); ErrorType error_type() const noexcept override { return ErrorType::InvalidKeyLength; } }; /** * An invalid nonce length was used */ class BOTAN_PUBLIC_API(2,0) Invalid_IV_Length final : public Invalid_Argument { public: Invalid_IV_Length(const std::string& mode, size_t bad_len); ErrorType error_type() const noexcept override { return ErrorType::InvalidNonceLength; } }; /** * Invalid_Algorithm_Name Exception */ class BOTAN_PUBLIC_API(2,0) Invalid_Algorithm_Name final : public Invalid_Argument { public: explicit Invalid_Algorithm_Name(const std::string& name); }; /** * Encoding_Error Exception * * This exception derives from Invalid_Argument for historical reasons, and it * does not make any real sense for it to do so. In a future major release this * exception type will derive directly from Exception instead. */ class BOTAN_PUBLIC_API(2,0) Encoding_Error final : public Invalid_Argument { public: explicit Encoding_Error(const std::string& name); ErrorType error_type() const noexcept override { return ErrorType::EncodingFailure; } }; /** * A decoding error occurred. * * This exception derives from Invalid_Argument for historical reasons, and it * does not make any real sense for it to do so. In a future major release this * exception type will derive directly from Exception instead. */ class BOTAN_PUBLIC_API(2,0) Decoding_Error : public Invalid_Argument { public: explicit Decoding_Error(const std::string& name); Decoding_Error(const std::string& name, const char* exception_message); Decoding_Error(const std::string& msg, const std::exception& e); ErrorType error_type() const noexcept override { return ErrorType::DecodingFailure; } }; /** * Invalid state was encountered. A request was made on an object while the * object was in a state where the operation cannot be performed. */ class BOTAN_PUBLIC_API(2,0) Invalid_State : public Exception { public: explicit Invalid_State(const std::string& err) : Exception(err) {} ErrorType error_type() const noexcept override { return ErrorType::InvalidObjectState; } }; /** * A PRNG was called on to produce output while still unseeded */ class BOTAN_PUBLIC_API(2,0) PRNG_Unseeded final : public Invalid_State { public: explicit PRNG_Unseeded(const std::string& algo); }; /** * The key was not set on an object. This occurs with symmetric objects where * an operation which requires the key is called prior to set_key being called. */ class BOTAN_PUBLIC_API(2,4) Key_Not_Set : public Invalid_State { public: explicit Key_Not_Set(const std::string& algo); ErrorType error_type() const noexcept override { return ErrorType::KeyNotSet; } }; /** * A request was made for some kind of object which could not be located */ class BOTAN_PUBLIC_API(2,0) Lookup_Error : public Exception { public: explicit Lookup_Error(const std::string& err) : Exception(err) {} Lookup_Error(const std::string& type, const std::string& algo, const std::string& provider); ErrorType error_type() const noexcept override { return ErrorType::LookupError; } }; /** * Algorithm_Not_Found Exception * * @warning This exception type will be removed in the future. Instead * just catch Lookup_Error. */ class BOTAN_PUBLIC_API(2,0) Algorithm_Not_Found final : public Lookup_Error { public: explicit Algorithm_Not_Found(const std::string& name); }; /** * Provider_Not_Found is thrown when a specific provider was requested * but that provider is not available. * * @warning This exception type will be removed in the future. Instead * just catch Lookup_Error. */ class BOTAN_PUBLIC_API(2,0) Provider_Not_Found final : public Lookup_Error { public: Provider_Not_Found(const std::string& algo, const std::string& provider); }; /** * An AEAD or MAC check detected a message modification * * In versions before 2.10, Invalid_Authentication_Tag was named * Integrity_Failure, it was renamed to make its usage more clear. */ class BOTAN_PUBLIC_API(2,0) Invalid_Authentication_Tag final : public Exception { public: explicit Invalid_Authentication_Tag(const std::string& msg); ErrorType error_type() const noexcept override { return ErrorType::InvalidTag; } }; /** * For compatability with older versions */ typedef Invalid_Authentication_Tag Integrity_Failure; /** * An error occurred while operating on an IO stream */ class BOTAN_PUBLIC_API(2,0) Stream_IO_Error final : public Exception { public: explicit Stream_IO_Error(const std::string& err); ErrorType error_type() const noexcept override { return ErrorType::IoError; } }; /** * System_Error * * This exception is thrown in the event of an error related to interacting * with the operating system. * * This exception type also (optionally) captures an integer error code eg * POSIX errno or Windows GetLastError. */ class BOTAN_PUBLIC_API(2,9) System_Error : public Exception { public: System_Error(const std::string& msg) : Exception(msg), m_error_code(0) {} System_Error(const std::string& msg, int err_code); ErrorType error_type() const noexcept override { return ErrorType::SystemError; } int error_code() const noexcept override { return m_error_code; } private: int m_error_code; }; /** * An internal error occurred. If observed, please file a bug. */ class BOTAN_PUBLIC_API(2,0) Internal_Error : public Exception { public: explicit Internal_Error(const std::string& err); ErrorType error_type() const noexcept override { return ErrorType::InternalError; } }; /** * Not Implemented Exception * * This is thrown in the situation where a requested operation is * logically valid but is not implemented by this version of the library. */ class BOTAN_PUBLIC_API(2,0) Not_Implemented final : public Exception { public: explicit Not_Implemented(const std::string& err); ErrorType error_type() const noexcept override { return ErrorType::NotImplemented; } }; /* The following exception types are still in use for compatability reasons, but are deprecated and will be removed in a future major release. Instead catch the base class. */ /** * An invalid OID string was used. * * This exception will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) Invalid_OID final : public Decoding_Error { public: explicit Invalid_OID(const std::string& oid); }; /* The following exception types are deprecated, no longer used, and will be removed in a future major release */ /** * Self Test Failure Exception * * This exception is no longer used. It will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) Self_Test_Failure final : public Internal_Error { public: BOTAN_DEPRECATED("no longer used") explicit Self_Test_Failure(const std::string& err); }; /** * No_Provider_Found Exception * * This exception is no longer used. It will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) No_Provider_Found final : public Exception { public: BOTAN_DEPRECATED("no longer used") explicit No_Provider_Found(const std::string& name); }; /** * Policy_Violation Exception * * This exception is no longer used. It will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) Policy_Violation final : public Invalid_State { public: BOTAN_DEPRECATED("no longer used") explicit Policy_Violation(const std::string& err); }; /** * Unsupported_Argument Exception * * An argument that is invalid because it is not supported by Botan. * It might or might not be valid in another context like a standard. * * This exception is no longer used, instead Not_Implemented is thrown. * It will be removed in a future major release. */ class BOTAN_PUBLIC_API(2,0) Unsupported_Argument final : public Invalid_Argument { public: BOTAN_DEPRECATED("no longer used") explicit Unsupported_Argument(const std::string& msg) : Invalid_Argument(msg) {} }; template inline void do_throw_error(const char* file, int line, const char* func, Args... args) { throw E(file, line, func, args...); } } namespace Botan { /** * The two possible directions for cipher filters, determining whether they * actually perform encryption or decryption. */ enum Cipher_Dir : int { ENCRYPTION, DECRYPTION }; /** * Interface for cipher modes */ class BOTAN_PUBLIC_API(2,0) Cipher_Mode : public SymmetricAlgorithm { public: /** * @return list of available providers for this algorithm, empty if not available * @param algo_spec algorithm name */ static std::vector providers(const std::string& algo_spec); /** * Create an AEAD mode * @param algo the algorithm to create * @param direction specify if this should be an encryption or decryption AEAD * @param provider optional specification for provider to use * @return an AEAD mode or a null pointer if not available */ static std::unique_ptr create(const std::string& algo, Cipher_Dir direction, const std::string& provider = ""); /** * Create an AEAD mode, or throw * @param algo the algorithm to create * @param direction specify if this should be an encryption or decryption AEAD * @param provider optional specification for provider to use * @return an AEAD mode, or throw an exception */ static std::unique_ptr create_or_throw(const std::string& algo, Cipher_Dir direction, const std::string& provider = ""); /* * Prepare for processing a message under the specified nonce */ virtual void start_msg(const uint8_t nonce[], size_t nonce_len) = 0; /** * Begin processing a message. * @param nonce the per message nonce */ template void start(const std::vector& nonce) { start_msg(nonce.data(), nonce.size()); } /** * Begin processing a message. * @param nonce the per message nonce * @param nonce_len length of nonce */ void start(const uint8_t nonce[], size_t nonce_len) { start_msg(nonce, nonce_len); } /** * Begin processing a message. */ void start() { return start_msg(nullptr, 0); } /** * Process message blocks * * Input must be a multiple of update_granularity * * Processes msg in place and returns bytes written. Normally * this will be either msg_len (indicating the entire message was * processed) or for certain AEAD modes zero (indicating that the * mode requires the entire message be processed in one pass). * * @param msg the message to be processed * @param msg_len length of the message in bytes */ virtual size_t process(uint8_t msg[], size_t msg_len) = 0; /** * Process some data. Input must be in size update_granularity() uint8_t blocks. * @param buffer in/out parameter which will possibly be resized * @param offset an offset into blocks to begin processing */ void update(secure_vector& buffer, size_t offset = 0) { BOTAN_ASSERT(buffer.size() >= offset, "Offset ok"); uint8_t* buf = buffer.data() + offset; const size_t buf_size = buffer.size() - offset; const size_t written = process(buf, buf_size); buffer.resize(offset + written); } /** * Complete processing of a message. * * @param final_block in/out parameter which must be at least * minimum_final_size() bytes, and will be set to any final output * @param offset an offset into final_block to begin processing */ virtual void finish(secure_vector& final_block, size_t offset = 0) = 0; /** * Returns the size of the output if this transform is used to process a * message with input_length bytes. In most cases the answer is precise. * If it is not possible to precise (namely for CBC decryption) instead a * lower bound is returned. */ virtual size_t output_length(size_t input_length) const = 0; /** * @return size of required blocks to update */ virtual size_t update_granularity() const = 0; /** * @return required minimium size to finalize() - may be any * length larger than this. */ virtual size_t minimum_final_size() const = 0; /** * @return the default size for a nonce */ virtual size_t default_nonce_length() const = 0; /** * @return true iff nonce_len is a valid length for the nonce */ virtual bool valid_nonce_length(size_t nonce_len) const = 0; /** * Resets just the message specific state and allows encrypting again under the existing key */ virtual void reset() = 0; /** * @return true iff this mode provides authentication as well as * confidentiality. */ virtual bool authenticated() const { return false; } /** * @return the size of the authentication tag used (in bytes) */ virtual size_t tag_size() const { return 0; } /** * @return provider information about this implementation. Default is "base", * might also return "sse2", "avx2", "openssl", or some other arbitrary string. */ virtual std::string provider() const { return "base"; } }; /** * Get a cipher mode by name (eg "AES-128/CBC" or "Serpent/XTS") * @param algo_spec cipher name * @param direction ENCRYPTION or DECRYPTION * @param provider provider implementation to choose */ inline Cipher_Mode* get_cipher_mode(const std::string& algo_spec, Cipher_Dir direction, const std::string& provider = "") { return Cipher_Mode::create(algo_spec, direction, provider).release(); } } namespace Botan { /** * Interface for AEAD (Authenticated Encryption with Associated Data) * modes. These modes provide both encryption and message * authentication, and can authenticate additional per-message data * which is not included in the ciphertext (for instance a sequence * number). */ class BOTAN_PUBLIC_API(2,0) AEAD_Mode : public Cipher_Mode { public: /** * Create an AEAD mode * @param algo the algorithm to create * @param direction specify if this should be an encryption or decryption AEAD * @param provider optional specification for provider to use * @return an AEAD mode or a null pointer if not available */ static std::unique_ptr create(const std::string& algo, Cipher_Dir direction, const std::string& provider = ""); /** * Create an AEAD mode, or throw * @param algo the algorithm to create * @param direction specify if this should be an encryption or decryption AEAD * @param provider optional specification for provider to use * @return an AEAD mode, or throw an exception */ static std::unique_ptr create_or_throw(const std::string& algo, Cipher_Dir direction, const std::string& provider = ""); bool authenticated() const override { return true; } /** * Set associated data that is not included in the ciphertext but * that should be authenticated. Must be called after set_key and * before start. * * Unless reset by another call, the associated data is kept * between messages. Thus, if the AD does not change, calling * once (after set_key) is the optimum. * * @param ad the associated data * @param ad_len length of add in bytes */ virtual void set_associated_data(const uint8_t ad[], size_t ad_len) = 0; /** * Set associated data that is not included in the ciphertext but * that should be authenticated. Must be called after set_key and * before start. * * Unless reset by another call, the associated data is kept * between messages. Thus, if the AD does not change, calling * once (after set_key) is the optimum. * * Some AEADs (namely SIV) support multiple AD inputs. For * all other modes only nominal AD input 0 is supported; all * other values of i will cause an exception. * * @param ad the associated data * @param ad_len length of add in bytes */ virtual void set_associated_data_n(size_t i, const uint8_t ad[], size_t ad_len); /** * Returns the maximum supported number of associated data inputs which * can be provided to set_associated_data_n * * If returns 0, then no associated data is supported. */ virtual size_t maximum_associated_data_inputs() const { return 1; } /** * Most AEADs require the key to be set prior to setting the AD * A few allow the AD to be set even before the cipher is keyed. * Such ciphers would return false from this function. */ virtual bool associated_data_requires_key() const { return true; } /** * Set associated data that is not included in the ciphertext but * that should be authenticated. Must be called after set_key and * before start. * * See @ref set_associated_data(). * * @param ad the associated data */ template void set_associated_data_vec(const std::vector& ad) { set_associated_data(ad.data(), ad.size()); } /** * Set associated data that is not included in the ciphertext but * that should be authenticated. Must be called after set_key and * before start. * * See @ref set_associated_data(). * * @param ad the associated data */ template void set_ad(const std::vector& ad) { set_associated_data(ad.data(), ad.size()); } /** * @return default AEAD nonce size (a commonly supported value among AEAD * modes, and large enough that random collisions are unlikely) */ size_t default_nonce_length() const override { return 12; } virtual ~AEAD_Mode() = default; }; /** * Get an AEAD mode by name (eg "AES-128/GCM" or "Serpent/EAX") * @param name AEAD name * @param direction ENCRYPTION or DECRYPTION */ inline AEAD_Mode* get_aead(const std::string& name, Cipher_Dir direction) { return AEAD_Mode::create(name, direction, "").release(); } } namespace Botan { /** * This class represents a block cipher object. */ class BOTAN_PUBLIC_API(2,0) BlockCipher : public SymmetricAlgorithm { public: /** * Create an instance based on a name * If provider is empty then best available is chosen. * @param algo_spec algorithm name * @param provider provider implementation to choose * @return a null pointer if the algo/provider combination cannot be found */ static std::unique_ptr create(const std::string& algo_spec, const std::string& provider = ""); /** * Create an instance based on a name, or throw if the * algo/provider combination cannot be found. If provider is * empty then best available is chosen. */ static std::unique_ptr create_or_throw(const std::string& algo_spec, const std::string& provider = ""); /** * @return list of available providers for this algorithm, empty if not available * @param algo_spec algorithm name */ static std::vector providers(const std::string& algo_spec); /** * @return block size of this algorithm */ virtual size_t block_size() const = 0; /** * @return native parallelism of this cipher in blocks */ virtual size_t parallelism() const { return 1; } /** * @return prefererred parallelism of this cipher in bytes */ size_t parallel_bytes() const { return parallelism() * block_size() * BOTAN_BLOCK_CIPHER_PAR_MULT; } /** * @return provider information about this implementation. Default is "base", * might also return "sse2", "avx2", "openssl", or some other arbitrary string. */ virtual std::string provider() const { return "base"; } /** * Encrypt a block. * @param in The plaintext block to be encrypted as a byte array. * Must be of length block_size(). * @param out The byte array designated to hold the encrypted block. * Must be of length block_size(). */ void encrypt(const uint8_t in[], uint8_t out[]) const { encrypt_n(in, out, 1); } /** * Decrypt a block. * @param in The ciphertext block to be decypted as a byte array. * Must be of length block_size(). * @param out The byte array designated to hold the decrypted block. * Must be of length block_size(). */ void decrypt(const uint8_t in[], uint8_t out[]) const { decrypt_n(in, out, 1); } /** * Encrypt a block. * @param block the plaintext block to be encrypted * Must be of length block_size(). Will hold the result when the function * has finished. */ void encrypt(uint8_t block[]) const { encrypt_n(block, block, 1); } /** * Decrypt a block. * @param block the ciphertext block to be decrypted * Must be of length block_size(). Will hold the result when the function * has finished. */ void decrypt(uint8_t block[]) const { decrypt_n(block, block, 1); } /** * Encrypt one or more blocks * @param block the input/output buffer (multiple of block_size()) */ template void encrypt(std::vector& block) const { return encrypt_n(block.data(), block.data(), block.size() / block_size()); } /** * Decrypt one or more blocks * @param block the input/output buffer (multiple of block_size()) */ template void decrypt(std::vector& block) const { return decrypt_n(block.data(), block.data(), block.size() / block_size()); } /** * Encrypt one or more blocks * @param in the input buffer (multiple of block_size()) * @param out the output buffer (same size as in) */ template void encrypt(const std::vector& in, std::vector& out) const { return encrypt_n(in.data(), out.data(), in.size() / block_size()); } /** * Decrypt one or more blocks * @param in the input buffer (multiple of block_size()) * @param out the output buffer (same size as in) */ template void decrypt(const std::vector& in, std::vector& out) const { return decrypt_n(in.data(), out.data(), in.size() / block_size()); } /** * Encrypt one or more blocks * @param in the input buffer (multiple of block_size()) * @param out the output buffer (same size as in) * @param blocks the number of blocks to process */ virtual void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const = 0; /** * Decrypt one or more blocks * @param in the input buffer (multiple of block_size()) * @param out the output buffer (same size as in) * @param blocks the number of blocks to process */ virtual void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const = 0; virtual void encrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const { const size_t BS = block_size(); xor_buf(data, mask, blocks * BS); encrypt_n(data, data, blocks); xor_buf(data, mask, blocks * BS); } virtual void decrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const { const size_t BS = block_size(); xor_buf(data, mask, blocks * BS); decrypt_n(data, data, blocks); xor_buf(data, mask, blocks * BS); } /** * @return new object representing the same algorithm as *this */ virtual BlockCipher* clone() const = 0; virtual ~BlockCipher() = default; }; /** * Tweakable block ciphers allow setting a tweak which is a non-keyed * value which affects the encryption/decryption operation. */ class BOTAN_PUBLIC_API(2,8) Tweakable_Block_Cipher : public BlockCipher { public: /** * Set the tweak value. This must be called after setting a key. The value * persists until either set_tweak, set_key, or clear is called. * Different algorithms support different tweak length(s). If called with * an unsupported length, Invalid_Argument will be thrown. */ virtual void set_tweak(const uint8_t tweak[], size_t len) = 0; }; /** * Represents a block cipher with a single fixed block size */ template class Block_Cipher_Fixed_Params : public BaseClass { public: enum { BLOCK_SIZE = BS }; size_t block_size() const final override { return BS; } // override to take advantage of compile time constant block size void encrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const final override { xor_buf(data, mask, blocks * BS); this->encrypt_n(data, data, blocks); xor_buf(data, mask, blocks * BS); } void decrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const final override { xor_buf(data, mask, blocks * BS); this->decrypt_n(data, data, blocks); xor_buf(data, mask, blocks * BS); } Key_Length_Specification key_spec() const final override { return Key_Length_Specification(KMIN, KMAX, KMOD); } }; } BOTAN_FUTURE_INTERNAL_HEADER(aes.h) namespace Botan { /** * AES-128 */ class BOTAN_PUBLIC_API(2,0) AES_128 final : public Block_Cipher_Fixed_Params<16, 16> { public: void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void clear() override; std::string provider() const override; std::string name() const override { return "AES-128"; } BlockCipher* clone() const override { return new AES_128; } size_t parallelism() const override; private: void key_schedule(const uint8_t key[], size_t length) override; #if defined(BOTAN_HAS_AES_VPERM) void vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_NI) void aesni_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_POWER8) || defined(BOTAN_HAS_AES_ARMV8) || defined(BOTAN_HAS_AES_NI) void hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; #endif secure_vector m_EK, m_DK; }; /** * AES-192 */ class BOTAN_PUBLIC_API(2,0) AES_192 final : public Block_Cipher_Fixed_Params<16, 24> { public: void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void clear() override; std::string provider() const override; std::string name() const override { return "AES-192"; } BlockCipher* clone() const override { return new AES_192; } size_t parallelism() const override; private: #if defined(BOTAN_HAS_AES_VPERM) void vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_NI) void aesni_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_POWER8) || defined(BOTAN_HAS_AES_ARMV8) || defined(BOTAN_HAS_AES_NI) void hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; #endif void key_schedule(const uint8_t key[], size_t length) override; secure_vector m_EK, m_DK; }; /** * AES-256 */ class BOTAN_PUBLIC_API(2,0) AES_256 final : public Block_Cipher_Fixed_Params<16, 32> { public: void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void clear() override; std::string provider() const override; std::string name() const override { return "AES-256"; } BlockCipher* clone() const override { return new AES_256; } size_t parallelism() const override; private: #if defined(BOTAN_HAS_AES_VPERM) void vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void vperm_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_NI) void aesni_key_schedule(const uint8_t key[], size_t length); #endif #if defined(BOTAN_HAS_AES_POWER8) || defined(BOTAN_HAS_AES_ARMV8) || defined(BOTAN_HAS_AES_NI) void hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; void hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const; #endif void key_schedule(const uint8_t key[], size_t length) override; secure_vector m_EK, m_DK; }; } #if defined(BOTAN_BUILD_COMPILER_IS_MSVC) #include #endif BOTAN_FUTURE_INTERNAL_HEADER(bswap.h) namespace Botan { /** * Swap a 16 bit integer */ inline uint16_t reverse_bytes(uint16_t val) { #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || defined(BOTAN_BUILD_COMPILER_IS_CLANG) || defined(BOTAN_BUILD_COMPILER_IS_XLC) return __builtin_bswap16(val); #else return static_cast((val << 8) | (val >> 8)); #endif } /** * Swap a 32 bit integer */ inline uint32_t reverse_bytes(uint32_t val) { #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || defined(BOTAN_BUILD_COMPILER_IS_CLANG) || defined(BOTAN_BUILD_COMPILER_IS_XLC) return __builtin_bswap32(val); #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC) return _byteswap_ulong(val); #elif defined(BOTAN_USE_GCC_INLINE_ASM) && defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) // GCC-style inline assembly for x86 or x86-64 asm("bswapl %0" : "=r" (val) : "0" (val)); return val; #else // Generic implementation uint16_t hi = static_cast(val >> 16); uint16_t lo = static_cast(val); hi = reverse_bytes(hi); lo = reverse_bytes(lo); return (static_cast(lo) << 16) | hi; #endif } /** * Swap a 64 bit integer */ inline uint64_t reverse_bytes(uint64_t val) { #if defined(BOTAN_BUILD_COMPILER_IS_GCC) || defined(BOTAN_BUILD_COMPILER_IS_CLANG) || defined(BOTAN_BUILD_COMPILER_IS_XLC) return __builtin_bswap64(val); #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC) return _byteswap_uint64(val); #elif defined(BOTAN_USE_GCC_INLINE_ASM) && defined(BOTAN_TARGET_ARCH_IS_X86_64) // GCC-style inline assembly for x86-64 asm("bswapq %0" : "=r" (val) : "0" (val)); return val; #else /* Generic implementation. Defined in terms of 32-bit bswap so any * optimizations in that version can help. */ uint32_t hi = static_cast(val >> 32); uint32_t lo = static_cast(val); hi = reverse_bytes(hi); lo = reverse_bytes(lo); return (static_cast(lo) << 32) | hi; #endif } /** * Swap 4 Ts in an array */ template inline void bswap_4(T x[4]) { x[0] = reverse_bytes(x[0]); x[1] = reverse_bytes(x[1]); x[2] = reverse_bytes(x[2]); x[3] = reverse_bytes(x[3]); } } namespace Botan { /** * This class represents any kind of computation which uses an internal * state, such as hash functions or MACs */ class BOTAN_PUBLIC_API(2,0) Buffered_Computation { public: /** * @return length of the output of this function in bytes */ virtual size_t output_length() const = 0; /** * Add new input to process. * @param in the input to process as a byte array * @param length of param in in bytes */ void update(const uint8_t in[], size_t length) { add_data(in, length); } /** * Add new input to process. * @param in the input to process as a secure_vector */ void update(const secure_vector& in) { add_data(in.data(), in.size()); } /** * Add new input to process. * @param in the input to process as a std::vector */ void update(const std::vector& in) { add_data(in.data(), in.size()); } void update_be(uint16_t val); void update_be(uint32_t val); void update_be(uint64_t val); void update_le(uint16_t val); void update_le(uint32_t val); void update_le(uint64_t val); /** * Add new input to process. * @param str the input to process as a std::string. Will be interpreted * as a byte array based on the strings encoding. */ void update(const std::string& str) { add_data(cast_char_ptr_to_uint8(str.data()), str.size()); } /** * Process a single byte. * @param in the byte to process */ void update(uint8_t in) { add_data(&in, 1); } /** * Complete the computation and retrieve the * final result. * @param out The byte array to be filled with the result. * Must be of length output_length() */ void final(uint8_t out[]) { final_result(out); } /** * Complete the computation and retrieve the * final result. * @return secure_vector holding the result */ secure_vector final() { secure_vector output(output_length()); final_result(output.data()); return output; } std::vector final_stdvec() { std::vector output(output_length()); final_result(output.data()); return output; } template void final(std::vector& out) { out.resize(output_length()); final_result(out.data()); } /** * Update and finalize computation. Does the same as calling update() * and final() consecutively. * @param in the input to process as a byte array * @param length the length of the byte array * @result the result of the call to final() */ secure_vector process(const uint8_t in[], size_t length) { add_data(in, length); return final(); } /** * Update and finalize computation. Does the same as calling update() * and final() consecutively. * @param in the input to process * @result the result of the call to final() */ secure_vector process(const secure_vector& in) { add_data(in.data(), in.size()); return final(); } /** * Update and finalize computation. Does the same as calling update() * and final() consecutively. * @param in the input to process * @result the result of the call to final() */ secure_vector process(const std::vector& in) { add_data(in.data(), in.size()); return final(); } /** * Update and finalize computation. Does the same as calling update() * and final() consecutively. * @param in the input to process as a string * @result the result of the call to final() */ secure_vector process(const std::string& in) { update(in); return final(); } virtual ~Buffered_Computation() = default; private: /** * Add more data to the computation * @param input is an input buffer * @param length is the length of input in bytes */ virtual void add_data(const uint8_t input[], size_t length) = 0; /** * Write the final output to out * @param out is an output buffer of output_length() */ virtual void final_result(uint8_t out[]) = 0; }; } namespace Botan { /** * Struct representing a particular date and time */ class BOTAN_PUBLIC_API(2,0) calendar_point { public: /** The year */ uint32_t get_year() const { return year; } /** The month, 1 through 12 for Jan to Dec */ uint32_t get_month() const { return month; } /** The day of the month, 1 through 31 (or 28 or 30 based on month */ uint32_t get_day() const { return day; } /** Hour in 24-hour form, 0 to 23 */ uint32_t get_hour() const { return hour; } /** Minutes in the hour, 0 to 60 */ uint32_t get_minutes() const { return minutes; } /** Seconds in the minute, 0 to 60, but might be slightly larger to deal with leap seconds on some systems */ uint32_t get_seconds() const { return seconds; } /** * Initialize a calendar_point * @param y the year * @param mon the month * @param d the day * @param h the hour * @param min the minute * @param sec the second */ calendar_point(uint32_t y, uint32_t mon, uint32_t d, uint32_t h, uint32_t min, uint32_t sec) : year(y), month(mon), day(d), hour(h), minutes(min), seconds(sec) {} /** * Returns an STL timepoint object */ std::chrono::system_clock::time_point to_std_timepoint() const; /** * Returns a human readable string of the struct's components. * Formatting might change over time. Currently it is RFC339 'iso-date-time'. */ std::string to_string() const; BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES: /* The member variables are public for historical reasons. Use the get_xxx() functions defined above. These members will be made private in a future major release. */ uint32_t year; uint32_t month; uint32_t day; uint32_t hour; uint32_t minutes; uint32_t seconds; }; /** * Convert a time_point to a calendar_point * @param time_point a time point from the system clock * @return calendar_point object representing this time point */ BOTAN_PUBLIC_API(2,0) calendar_point calendar_value( const std::chrono::system_clock::time_point& time_point); } BOTAN_FUTURE_INTERNAL_HEADER(charset.h) namespace Botan { /** * Convert a sequence of UCS-2 (big endian) characters to a UTF-8 string * This is used for ASN.1 BMPString type * @param ucs2 the sequence of UCS-2 characters * @param len length of ucs2 in bytes, must be a multiple of 2 */ std::string BOTAN_UNSTABLE_API ucs2_to_utf8(const uint8_t ucs2[], size_t len); /** * Convert a sequence of UCS-4 (big endian) characters to a UTF-8 string * This is used for ASN.1 UniversalString type * @param ucs4 the sequence of UCS-4 characters * @param len length of ucs4 in bytes, must be a multiple of 4 */ std::string BOTAN_UNSTABLE_API ucs4_to_utf8(const uint8_t ucs4[], size_t len); /** * Convert a UTF-8 string to Latin-1 * If a character outside the Latin-1 range is encountered, an exception is thrown. */ std::string BOTAN_UNSTABLE_API utf8_to_latin1(const std::string& utf8); /** * The different charsets (nominally) supported by Botan. */ enum Character_Set { LOCAL_CHARSET, UCS2_CHARSET, UTF8_CHARSET, LATIN1_CHARSET }; namespace Charset { /* * Character set conversion - avoid this. * For specific conversions, use the functions above like * ucs2_to_utf8 and utf8_to_latin1 * * If you need something more complex than that, use a real library * such as iconv, Boost.Locale, or ICU */ std::string BOTAN_PUBLIC_API(2,0) BOTAN_DEPRECATED("Avoid. See comment in header.") transcode(const std::string& str, Character_Set to, Character_Set from); /* * Simple character classifier functions */ bool BOTAN_PUBLIC_API(2,0) is_digit(char c); bool BOTAN_PUBLIC_API(2,0) is_space(char c); bool BOTAN_PUBLIC_API(2,0) caseless_cmp(char x, char y); uint8_t BOTAN_PUBLIC_API(2,0) char2digit(char c); char BOTAN_PUBLIC_API(2,0) digit2char(uint8_t b); } } BOTAN_FUTURE_INTERNAL_HEADER(cpuid.h) namespace Botan { /** * A class handling runtime CPU feature detection. It is limited to * just the features necessary to implement CPU specific code in Botan, * rather than being a general purpose utility. * * This class supports: * * - x86 features using CPUID. x86 is also the only processor with * accurate cache line detection currently. * * - PowerPC AltiVec detection on Linux, NetBSD, OpenBSD, and macOS * * - ARM NEON and crypto extensions detection. On Linux and Android * systems which support getauxval, that is used to access CPU * feature information. Otherwise a relatively portable but * thread-unsafe mechanism involving executing probe functions which * catching SIGILL signal is used. */ class BOTAN_PUBLIC_API(2,1) CPUID final { public: /** * Probe the CPU and see what extensions are supported */ static void initialize(); static bool has_simd_32(); /** * Deprecated equivalent to * o << "CPUID flags: " << CPUID::to_string() << "\n"; */ BOTAN_DEPRECATED("Use CPUID::to_string") static void print(std::ostream& o); /** * Return a possibly empty string containing list of known CPU * extensions. Each name will be seperated by a space, and the ordering * will be arbitrary. This list only contains values that are useful to * Botan (for example FMA instructions are not checked). * * Example outputs "sse2 ssse3 rdtsc", "neon arm_aes", "altivec" */ static std::string to_string(); /** * Return a best guess of the cache line size */ static size_t cache_line_size() { return state().cache_line_size(); } static bool is_little_endian() { #if defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) return true; #elif defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) return false; #else return state().endian_status() == Endian_Status::Little; #endif } static bool is_big_endian() { #if defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) return true; #elif defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) return false; #else return state().endian_status() == Endian_Status::Big; #endif } enum CPUID_bits : uint64_t { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) // These values have no relation to cpuid bitfields // SIMD instruction sets CPUID_SSE2_BIT = (1ULL << 0), CPUID_SSSE3_BIT = (1ULL << 1), CPUID_SSE41_BIT = (1ULL << 2), CPUID_SSE42_BIT = (1ULL << 3), CPUID_AVX2_BIT = (1ULL << 4), CPUID_AVX512F_BIT = (1ULL << 5), CPUID_AVX512DQ_BIT = (1ULL << 6), CPUID_AVX512BW_BIT = (1ULL << 7), // Ice Lake profile: AVX-512 F, DQ, BW, IFMA, VBMI, VBMI2, BITALG CPUID_AVX512_ICL_BIT = (1ULL << 11), // Crypto-specific ISAs CPUID_AESNI_BIT = (1ULL << 16), CPUID_CLMUL_BIT = (1ULL << 17), CPUID_RDRAND_BIT = (1ULL << 18), CPUID_RDSEED_BIT = (1ULL << 19), CPUID_SHA_BIT = (1ULL << 20), CPUID_AVX512_AES_BIT = (1ULL << 21), CPUID_AVX512_CLMUL_BIT = (1ULL << 22), // Misc useful instructions CPUID_RDTSC_BIT = (1ULL << 48), CPUID_ADX_BIT = (1ULL << 49), CPUID_BMI1_BIT = (1ULL << 50), CPUID_BMI2_BIT = (1ULL << 51), #endif #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) CPUID_ALTIVEC_BIT = (1ULL << 0), CPUID_POWER_CRYPTO_BIT = (1ULL << 1), CPUID_DARN_BIT = (1ULL << 2), #endif #if defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) CPUID_ARM_NEON_BIT = (1ULL << 0), CPUID_ARM_SVE_BIT = (1ULL << 1), CPUID_ARM_AES_BIT = (1ULL << 16), CPUID_ARM_PMULL_BIT = (1ULL << 17), CPUID_ARM_SHA1_BIT = (1ULL << 18), CPUID_ARM_SHA2_BIT = (1ULL << 19), CPUID_ARM_SHA3_BIT = (1ULL << 20), CPUID_ARM_SHA2_512_BIT = (1ULL << 21), CPUID_ARM_SM3_BIT = (1ULL << 22), CPUID_ARM_SM4_BIT = (1ULL << 23), #endif CPUID_INITIALIZED_BIT = (1ULL << 63) }; #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) /** * Check if the processor supports AltiVec/VMX */ static bool has_altivec() { return has_cpuid_bit(CPUID_ALTIVEC_BIT); } /** * Check if the processor supports POWER8 crypto extensions */ static bool has_power_crypto() { return has_cpuid_bit(CPUID_POWER_CRYPTO_BIT); } /** * Check if the processor supports POWER9 DARN RNG */ static bool has_darn_rng() { return has_cpuid_bit(CPUID_DARN_BIT); } #endif #if defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) /** * Check if the processor supports NEON SIMD */ static bool has_neon() { return has_cpuid_bit(CPUID_ARM_NEON_BIT); } /** * Check if the processor supports ARMv8 SVE */ static bool has_arm_sve() { return has_cpuid_bit(CPUID_ARM_SVE_BIT); } /** * Check if the processor supports ARMv8 SHA1 */ static bool has_arm_sha1() { return has_cpuid_bit(CPUID_ARM_SHA1_BIT); } /** * Check if the processor supports ARMv8 SHA2 */ static bool has_arm_sha2() { return has_cpuid_bit(CPUID_ARM_SHA2_BIT); } /** * Check if the processor supports ARMv8 AES */ static bool has_arm_aes() { return has_cpuid_bit(CPUID_ARM_AES_BIT); } /** * Check if the processor supports ARMv8 PMULL */ static bool has_arm_pmull() { return has_cpuid_bit(CPUID_ARM_PMULL_BIT); } /** * Check if the processor supports ARMv8 SHA-512 */ static bool has_arm_sha2_512() { return has_cpuid_bit(CPUID_ARM_SHA2_512_BIT); } /** * Check if the processor supports ARMv8 SHA-3 */ static bool has_arm_sha3() { return has_cpuid_bit(CPUID_ARM_SHA3_BIT); } /** * Check if the processor supports ARMv8 SM3 */ static bool has_arm_sm3() { return has_cpuid_bit(CPUID_ARM_SM3_BIT); } /** * Check if the processor supports ARMv8 SM4 */ static bool has_arm_sm4() { return has_cpuid_bit(CPUID_ARM_SM4_BIT); } #endif #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) /** * Check if the processor supports RDTSC */ static bool has_rdtsc() { return has_cpuid_bit(CPUID_RDTSC_BIT); } /** * Check if the processor supports SSE2 */ static bool has_sse2() { return has_cpuid_bit(CPUID_SSE2_BIT); } /** * Check if the processor supports SSSE3 */ static bool has_ssse3() { return has_cpuid_bit(CPUID_SSSE3_BIT); } /** * Check if the processor supports SSE4.1 */ static bool has_sse41() { return has_cpuid_bit(CPUID_SSE41_BIT); } /** * Check if the processor supports SSE4.2 */ static bool has_sse42() { return has_cpuid_bit(CPUID_SSE42_BIT); } /** * Check if the processor supports AVX2 */ static bool has_avx2() { return has_cpuid_bit(CPUID_AVX2_BIT); } /** * Check if the processor supports AVX-512F */ static bool has_avx512f() { return has_cpuid_bit(CPUID_AVX512F_BIT); } /** * Check if the processor supports AVX-512DQ */ static bool has_avx512dq() { return has_cpuid_bit(CPUID_AVX512DQ_BIT); } /** * Check if the processor supports AVX-512BW */ static bool has_avx512bw() { return has_cpuid_bit(CPUID_AVX512BW_BIT); } /** * Check if the processor supports AVX-512 Ice Lake profile */ static bool has_avx512_icelake() { return has_cpuid_bit(CPUID_AVX512_ICL_BIT); } /** * Check if the processor supports AVX-512 AES (VAES) */ static bool has_avx512_aes() { return has_cpuid_bit(CPUID_AVX512_AES_BIT); } /** * Check if the processor supports AVX-512 VPCLMULQDQ */ static bool has_avx512_clmul() { return has_cpuid_bit(CPUID_AVX512_CLMUL_BIT); } /** * Check if the processor supports BMI1 */ static bool has_bmi1() { return has_cpuid_bit(CPUID_BMI1_BIT); } /** * Check if the processor supports BMI2 */ static bool has_bmi2() { return has_cpuid_bit(CPUID_BMI2_BIT); } /** * Check if the processor supports AES-NI */ static bool has_aes_ni() { return has_cpuid_bit(CPUID_AESNI_BIT); } /** * Check if the processor supports CLMUL */ static bool has_clmul() { return has_cpuid_bit(CPUID_CLMUL_BIT); } /** * Check if the processor supports Intel SHA extension */ static bool has_intel_sha() { return has_cpuid_bit(CPUID_SHA_BIT); } /** * Check if the processor supports ADX extension */ static bool has_adx() { return has_cpuid_bit(CPUID_ADX_BIT); } /** * Check if the processor supports RDRAND */ static bool has_rdrand() { return has_cpuid_bit(CPUID_RDRAND_BIT); } /** * Check if the processor supports RDSEED */ static bool has_rdseed() { return has_cpuid_bit(CPUID_RDSEED_BIT); } #endif /** * Check if the processor supports byte-level vector permutes * (SSSE3, NEON, Altivec) */ static bool has_vperm() { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) return has_ssse3(); #elif defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) return has_neon(); #elif defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) return has_altivec(); #else return false; #endif } /** * Check if the processor supports hardware AES instructions */ static bool has_hw_aes() { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) return has_aes_ni(); #elif defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) return has_arm_aes(); #elif defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) return has_power_crypto(); #else return false; #endif } /** * Check if the processor supports carryless multiply * (CLMUL, PMULL) */ static bool has_carryless_multiply() { #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) return has_clmul(); #elif defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) return has_arm_pmull(); #elif defined(BOTAN_TARGET_ARCH_IS_PPC64) return has_power_crypto(); #else return false; #endif } /* * Clear a CPUID bit * Call CPUID::initialize to reset * * This is only exposed for testing, don't use unless you know * what you are doing. */ static void clear_cpuid_bit(CPUID_bits bit) { state().clear_cpuid_bit(static_cast(bit)); } /* * Don't call this function, use CPUID::has_xxx above * It is only exposed for the tests. */ static bool has_cpuid_bit(CPUID_bits elem) { const uint64_t elem64 = static_cast(elem); return state().has_bit(elem64); } static std::vector bit_from_string(const std::string& tok); private: enum class Endian_Status : uint32_t { Unknown = 0x00000000, Big = 0x01234567, Little = 0x67452301, }; struct CPUID_Data { public: CPUID_Data(); CPUID_Data(const CPUID_Data& other) = default; CPUID_Data& operator=(const CPUID_Data& other) = default; void clear_cpuid_bit(uint64_t bit) { m_processor_features &= ~bit; } bool has_bit(uint64_t bit) const { return (m_processor_features & bit) == bit; } uint64_t processor_features() const { return m_processor_features; } Endian_Status endian_status() const { return m_endian_status; } size_t cache_line_size() const { return m_cache_line_size; } private: static Endian_Status runtime_check_endian(); #if defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) || \ defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY) || \ defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) static uint64_t detect_cpu_features(size_t* cache_line_size); #endif uint64_t m_processor_features; size_t m_cache_line_size; Endian_Status m_endian_status; }; static CPUID_Data& state() { static CPUID::CPUID_Data g_cpuid; return g_cpuid; } }; } namespace Botan { /** * Base class for all stream ciphers */ class BOTAN_PUBLIC_API(2,0) StreamCipher : public SymmetricAlgorithm { public: virtual ~StreamCipher() = default; /** * Create an instance based on a name * If provider is empty then best available is chosen. * @param algo_spec algorithm name * @param provider provider implementation to use * @return a null pointer if the algo/provider combination cannot be found */ static std::unique_ptr create(const std::string& algo_spec, const std::string& provider = ""); /** * Create an instance based on a name * If provider is empty then best available is chosen. * @param algo_spec algorithm name * @param provider provider implementation to use * Throws a Lookup_Error if the algo/provider combination cannot be found */ static std::unique_ptr create_or_throw(const std::string& algo_spec, const std::string& provider = ""); /** * @return list of available providers for this algorithm, empty if not available */ static std::vector providers(const std::string& algo_spec); /** * Encrypt or decrypt a message * @param in the plaintext * @param out the byte array to hold the output, i.e. the ciphertext * @param len the length of both in and out in bytes */ virtual void cipher(const uint8_t in[], uint8_t out[], size_t len) = 0; /** * Write keystream bytes to a buffer * @param out the byte array to hold the keystream * @param len the length of out in bytes */ virtual void write_keystream(uint8_t out[], size_t len) { clear_mem(out, len); cipher1(out, len); } /** * Encrypt or decrypt a message * The message is encrypted/decrypted in place. * @param buf the plaintext / ciphertext * @param len the length of buf in bytes */ void cipher1(uint8_t buf[], size_t len) { cipher(buf, buf, len); } /** * Encrypt a message * The message is encrypted/decrypted in place. * @param inout the plaintext / ciphertext */ template void encipher(std::vector& inout) { cipher(inout.data(), inout.data(), inout.size()); } /** * Encrypt a message * The message is encrypted in place. * @param inout the plaintext / ciphertext */ template void encrypt(std::vector& inout) { cipher(inout.data(), inout.data(), inout.size()); } /** * Decrypt a message in place * The message is decrypted in place. * @param inout the plaintext / ciphertext */ template void decrypt(std::vector& inout) { cipher(inout.data(), inout.data(), inout.size()); } /** * Resync the cipher using the IV * @param iv the initialization vector * @param iv_len the length of the IV in bytes */ virtual void set_iv(const uint8_t iv[], size_t iv_len) = 0; /** * Return the default (preferred) nonce length * If this function returns 0, then this cipher does not support nonces */ virtual size_t default_iv_length() const { return 0; } /** * @param iv_len the length of the IV in bytes * @return if the length is valid for this algorithm */ virtual bool valid_iv_length(size_t iv_len) const { return (iv_len == 0); } /** * @return a new object representing the same algorithm as *this */ virtual StreamCipher* clone() const = 0; /** * Set the offset and the state used later to generate the keystream * @param offset the offset where we begin to generate the keystream */ virtual void seek(uint64_t offset) = 0; /** * @return provider information about this implementation. Default is "base", * might also return "sse2", "avx2", "openssl", or some other arbitrary string. */ virtual std::string provider() const { return "base"; } }; } BOTAN_FUTURE_INTERNAL_HEADER(ctr.h) namespace Botan { /** * CTR-BE (Counter mode, big-endian) */ class BOTAN_PUBLIC_API(2,0) CTR_BE final : public StreamCipher { public: void cipher(const uint8_t in[], uint8_t out[], size_t length) override; void set_iv(const uint8_t iv[], size_t iv_len) override; size_t default_iv_length() const override; bool valid_iv_length(size_t iv_len) const override; Key_Length_Specification key_spec() const override; std::string name() const override; CTR_BE* clone() const override; void clear() override; /** * @param cipher the block cipher to use */ explicit CTR_BE(BlockCipher* cipher); CTR_BE(BlockCipher* cipher, size_t ctr_size); void seek(uint64_t offset) override; private: void key_schedule(const uint8_t key[], size_t key_len) override; void add_counter(const uint64_t counter); std::unique_ptr m_cipher; const size_t m_block_size; const size_t m_ctr_size; const size_t m_ctr_blocks; secure_vector m_counter, m_pad; std::vector m_iv; size_t m_pad_pos; }; } namespace Botan { /** * This class represents an abstract data source object. */ class BOTAN_PUBLIC_API(2,0) DataSource { public: /** * Read from the source. Moves the internal offset so that every * call to read will return a new portion of the source. * * @param out the byte array to write the result to * @param length the length of the byte array out * @return length in bytes that was actually read and put * into out */ virtual size_t read(uint8_t out[], size_t length) BOTAN_WARN_UNUSED_RESULT = 0; virtual bool check_available(size_t n) = 0; /** * Read from the source but do not modify the internal * offset. Consecutive calls to peek() will return portions of * the source starting at the same position. * * @param out the byte array to write the output to * @param length the length of the byte array out * @param peek_offset the offset into the stream to read at * @return length in bytes that was actually read and put * into out */ virtual size_t peek(uint8_t out[], size_t length, size_t peek_offset) const BOTAN_WARN_UNUSED_RESULT = 0; /** * Test whether the source still has data that can be read. * @return true if there is no more data to read, false otherwise */ virtual bool end_of_data() const = 0; /** * return the id of this data source * @return std::string representing the id of this data source */ virtual std::string id() const { return ""; } /** * Read one byte. * @param out the byte to read to * @return length in bytes that was actually read and put * into out */ size_t read_byte(uint8_t& out); /** * Peek at one byte. * @param out an output byte * @return length in bytes that was actually read and put * into out */ size_t peek_byte(uint8_t& out) const; /** * Discard the next N bytes of the data * @param N the number of bytes to discard * @return number of bytes actually discarded */ size_t discard_next(size_t N); /** * @return number of bytes read so far. */ virtual size_t get_bytes_read() const = 0; DataSource() = default; virtual ~DataSource() = default; DataSource& operator=(const DataSource&) = delete; DataSource(const DataSource&) = delete; }; /** * This class represents a Memory-Based DataSource */ class BOTAN_PUBLIC_API(2,0) DataSource_Memory final : public DataSource { public: size_t read(uint8_t[], size_t) override; size_t peek(uint8_t[], size_t, size_t) const override; bool check_available(size_t n) override; bool end_of_data() const override; /** * Construct a memory source that reads from a string * @param in the string to read from */ explicit DataSource_Memory(const std::string& in); /** * Construct a memory source that reads from a byte array * @param in the byte array to read from * @param length the length of the byte array */ DataSource_Memory(const uint8_t in[], size_t length) : m_source(in, in + length), m_offset(0) {} /** * Construct a memory source that reads from a secure_vector * @param in the MemoryRegion to read from */ explicit DataSource_Memory(const secure_vector& in) : m_source(in), m_offset(0) {} /** * Construct a memory source that reads from a std::vector * @param in the MemoryRegion to read from */ explicit DataSource_Memory(const std::vector& in) : m_source(in.begin(), in.end()), m_offset(0) {} size_t get_bytes_read() const override { return m_offset; } private: secure_vector m_source; size_t m_offset; }; /** * This class represents a Stream-Based DataSource. */ class BOTAN_PUBLIC_API(2,0) DataSource_Stream final : public DataSource { public: size_t read(uint8_t[], size_t) override; size_t peek(uint8_t[], size_t, size_t) const override; bool check_available(size_t n) override; bool end_of_data() const override; std::string id() const override; DataSource_Stream(std::istream&, const std::string& id = ""); #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) /** * Construct a Stream-Based DataSource from filesystem path * @param file the path to the file * @param use_binary whether to treat the file as binary or not */ DataSource_Stream(const std::string& file, bool use_binary = false); #endif DataSource_Stream(const DataSource_Stream&) = delete; DataSource_Stream& operator=(const DataSource_Stream&) = delete; ~DataSource_Stream(); size_t get_bytes_read() const override { return m_total_read; } private: const std::string m_identifier; std::unique_ptr m_source_memory; std::istream& m_source; size_t m_total_read; }; } namespace Botan { class BOTAN_PUBLIC_API(2,0) SQL_Database { public: class BOTAN_PUBLIC_API(2,0) SQL_DB_Error final : public Exception { public: explicit SQL_DB_Error(const std::string& what) : Exception("SQL database", what), m_rc(0) {} SQL_DB_Error(const std::string& what, int rc) : Exception("SQL database", what), m_rc(rc) {} ErrorType error_type() const noexcept override { return Botan::ErrorType::DatabaseError; } int error_code() const noexcept override { return m_rc; } private: int m_rc; }; class BOTAN_PUBLIC_API(2,0) Statement { public: /* Bind statement parameters */ virtual void bind(int column, const std::string& str) = 0; virtual void bind(int column, size_t i) = 0; virtual void bind(int column, std::chrono::system_clock::time_point time) = 0; virtual void bind(int column, const std::vector& blob) = 0; virtual void bind(int column, const uint8_t* data, size_t len) = 0; /* Get output */ virtual std::pair get_blob(int column) = 0; virtual std::string get_str(int column) = 0; virtual size_t get_size_t(int column) = 0; /* Run to completion */ virtual size_t spin() = 0; /* Maybe update */ virtual bool step() = 0; virtual ~Statement() = default; }; /* * Create a new statement for execution. * Use ?1, ?2, ?3, etc for parameters to set later with bind */ virtual std::shared_ptr new_statement(const std::string& base_sql) const = 0; virtual size_t row_count(const std::string& table_name) = 0; virtual void create_table(const std::string& table_schema) = 0; virtual ~SQL_Database() = default; }; } #if defined(BOTAN_TARGET_OS_HAS_THREADS) namespace Botan { template using lock_guard_type = std::lock_guard; typedef std::mutex mutex_type; typedef std::recursive_mutex recursive_mutex_type; } #else // No threads namespace Botan { template class lock_guard final { public: explicit lock_guard(Mutex& m) : m_mutex(m) { m_mutex.lock(); } ~lock_guard() { m_mutex.unlock(); } lock_guard(const lock_guard& other) = delete; lock_guard& operator=(const lock_guard& other) = delete; private: Mutex& m_mutex; }; class noop_mutex final { public: void lock() {} void unlock() {} }; typedef noop_mutex mutex_type; typedef noop_mutex recursive_mutex_type; template using lock_guard_type = lock_guard; } #endif namespace Botan { class Entropy_Sources; /** * An interface to a cryptographic random number generator */ class BOTAN_PUBLIC_API(2,0) RandomNumberGenerator { public: virtual ~RandomNumberGenerator() = default; RandomNumberGenerator() = default; /* * Never copy a RNG, create a new one */ RandomNumberGenerator(const RandomNumberGenerator& rng) = delete; RandomNumberGenerator& operator=(const RandomNumberGenerator& rng) = delete; /** * Randomize a byte array. * @param output the byte array to hold the random output. * @param length the length of the byte array output in bytes. */ virtual void randomize(uint8_t output[], size_t length) = 0; /** * Returns false if it is known that this RNG object is not able to accept * externally provided inputs (via add_entropy, randomize_with_input, etc). * In this case, any such provided inputs are ignored. * * If this function returns true, then inputs may or may not be accepted. */ virtual bool accepts_input() const = 0; /** * Incorporate some additional data into the RNG state. For * example adding nonces or timestamps from a peer's protocol * message can help hedge against VM state rollback attacks. * A few RNG types do not accept any externally provided input, * in which case this function is a no-op. * * @param input a byte array containg the entropy to be added * @param length the length of the byte array in */ virtual void add_entropy(const uint8_t input[], size_t length) = 0; /** * Incorporate some additional data into the RNG state. */ template void add_entropy_T(const T& t) { static_assert(std::is_standard_layout::value && std::is_trivial::value, "add_entropy_T data must be POD"); this->add_entropy(reinterpret_cast(&t), sizeof(T)); } /** * Incorporate entropy into the RNG state then produce output. * Some RNG types implement this using a single operation, default * calls add_entropy + randomize in sequence. * * Use this to further bind the outputs to your current * process/protocol state. For instance if generating a new key * for use in a session, include a session ID or other such * value. See NIST SP 800-90 A, B, C series for more ideas. * * @param output buffer to hold the random output * @param output_len size of the output buffer in bytes * @param input entropy buffer to incorporate * @param input_len size of the input buffer in bytes */ virtual void randomize_with_input(uint8_t output[], size_t output_len, const uint8_t input[], size_t input_len); /** * This calls `randomize_with_input` using some timestamps as extra input. * * For a stateful RNG using non-random but potentially unique data the * extra input can help protect against problems with fork, VM state * rollback, or other cases where somehow an RNG state is duplicated. If * both of the duplicated RNG states later incorporate a timestamp (and the * timestamps don't themselves repeat), their outputs will diverge. */ virtual void randomize_with_ts_input(uint8_t output[], size_t output_len); /** * @return the name of this RNG type */ virtual std::string name() const = 0; /** * Clear all internally held values of this RNG * @post is_seeded() == false */ virtual void clear() = 0; /** * Check whether this RNG is seeded. * @return true if this RNG was already seeded, false otherwise. */ virtual bool is_seeded() const = 0; /** * Poll provided sources for up to poll_bits bits of entropy * or until the timeout expires. Returns estimate of the number * of bits collected. */ virtual size_t reseed(Entropy_Sources& srcs, size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS, std::chrono::milliseconds poll_timeout = BOTAN_RNG_RESEED_DEFAULT_TIMEOUT); /** * Reseed by reading specified bits from the RNG */ virtual void reseed_from_rng(RandomNumberGenerator& rng, size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS); // Some utility functions built on the interface above: /** * Return a random vector * @param bytes number of bytes in the result * @return randomized vector of length bytes */ secure_vector random_vec(size_t bytes) { secure_vector output; random_vec(output, bytes); return output; } template void random_vec(std::vector& v, size_t bytes) { v.resize(bytes); this->randomize(v.data(), v.size()); } /** * Return a random byte * @return random byte */ uint8_t next_byte() { uint8_t b; this->randomize(&b, 1); return b; } /** * @return a random byte that is greater than zero */ uint8_t next_nonzero_byte() { uint8_t b = this->next_byte(); while(b == 0) b = this->next_byte(); return b; } /** * Create a seeded and active RNG object for general application use * Added in 1.8.0 * Use AutoSeeded_RNG instead */ BOTAN_DEPRECATED("Use AutoSeeded_RNG") static RandomNumberGenerator* make_rng(); }; /** * Convenience typedef */ typedef RandomNumberGenerator RNG; /** * Hardware_RNG exists to tag hardware RNG types (PKCS11_RNG, TPM_RNG, Processor_RNG) */ class BOTAN_PUBLIC_API(2,0) Hardware_RNG : public RandomNumberGenerator { public: virtual void clear() final override { /* no way to clear state of hardware RNG */ } }; /** * Null/stub RNG - fails if you try to use it for anything * This is not generally useful except for in certain tests */ class BOTAN_PUBLIC_API(2,0) Null_RNG final : public RandomNumberGenerator { public: bool is_seeded() const override { return false; } bool accepts_input() const override { return false; } void clear() override {} void randomize(uint8_t[], size_t) override { throw PRNG_Unseeded("Null_RNG called"); } void add_entropy(const uint8_t[], size_t) override {} std::string name() const override { return "Null_RNG"; } }; #if defined(BOTAN_TARGET_OS_HAS_THREADS) /** * Wraps access to a RNG in a mutex * Note that most of the time it's much better to use a RNG per thread * otherwise the RNG will act as an unnecessary contention point * * Since 2.16.0 all Stateful_RNG instances have an internal lock, so * this class is no longer needed. It will be removed in a future major * release. */ class BOTAN_PUBLIC_API(2,0) Serialized_RNG final : public RandomNumberGenerator { public: void randomize(uint8_t out[], size_t len) override { lock_guard_type lock(m_mutex); m_rng->randomize(out, len); } bool accepts_input() const override { lock_guard_type lock(m_mutex); return m_rng->accepts_input(); } bool is_seeded() const override { lock_guard_type lock(m_mutex); return m_rng->is_seeded(); } void clear() override { lock_guard_type lock(m_mutex); m_rng->clear(); } std::string name() const override { lock_guard_type lock(m_mutex); return m_rng->name(); } size_t reseed(Entropy_Sources& src, size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS, std::chrono::milliseconds poll_timeout = BOTAN_RNG_RESEED_DEFAULT_TIMEOUT) override { lock_guard_type lock(m_mutex); return m_rng->reseed(src, poll_bits, poll_timeout); } void add_entropy(const uint8_t in[], size_t len) override { lock_guard_type lock(m_mutex); m_rng->add_entropy(in, len); } BOTAN_DEPRECATED("Use Serialized_RNG(new AutoSeeded_RNG) instead") Serialized_RNG(); /* * Since 2.16.0 this is no longer needed for any RNG type. This * class will be removed in a future major release. */ explicit Serialized_RNG(RandomNumberGenerator* rng) : m_rng(rng) {} private: mutable mutex_type m_mutex; std::unique_ptr m_rng; }; #endif } namespace Botan { class RandomNumberGenerator; /** * Abstract interface to a source of entropy */ class BOTAN_PUBLIC_API(2,0) Entropy_Source { public: /** * Return a new entropy source of a particular type, or null * Each entropy source may require substantial resources (eg, a file handle * or socket instance), so try to share them among multiple RNGs, or just * use the preconfigured global list accessed by Entropy_Sources::global_sources() */ static std::unique_ptr create(const std::string& type); /** * @return name identifying this entropy source */ virtual std::string name() const = 0; /** * Perform an entropy gathering poll * @param rng will be provided with entropy via calls to add_entropy * @return conservative estimate of actual entropy added to rng during poll */ virtual size_t poll(RandomNumberGenerator& rng) = 0; Entropy_Source() = default; Entropy_Source(const Entropy_Source& other) = delete; Entropy_Source(Entropy_Source&& other) = delete; Entropy_Source& operator=(const Entropy_Source& other) = delete; virtual ~Entropy_Source() = default; }; class BOTAN_PUBLIC_API(2,0) Entropy_Sources final { public: static Entropy_Sources& global_sources(); void add_source(std::unique_ptr src); std::vector enabled_sources() const; size_t poll(RandomNumberGenerator& rng, size_t bits, std::chrono::milliseconds timeout); /** * Poll just a single named source. Ordinally only used for testing */ size_t poll_just(RandomNumberGenerator& rng, const std::string& src); Entropy_Sources() = default; explicit Entropy_Sources(const std::vector& sources); Entropy_Sources(const Entropy_Sources& other) = delete; Entropy_Sources(Entropy_Sources&& other) = delete; Entropy_Sources& operator=(const Entropy_Sources& other) = delete; private: std::vector> m_srcs; }; } BOTAN_FUTURE_INTERNAL_HEADER(gcm.h) namespace Botan { class BlockCipher; class StreamCipher; class GHASH; /** * GCM Mode */ class BOTAN_PUBLIC_API(2,0) GCM_Mode : public AEAD_Mode { public: void set_associated_data(const uint8_t ad[], size_t ad_len) override; std::string name() const override; size_t update_granularity() const override; Key_Length_Specification key_spec() const override; bool valid_nonce_length(size_t len) const override; size_t tag_size() const override { return m_tag_size; } void clear() override; void reset() override; std::string provider() const override; protected: GCM_Mode(BlockCipher* cipher, size_t tag_size); ~GCM_Mode(); static const size_t GCM_BS = 16; const size_t m_tag_size; const std::string m_cipher_name; std::unique_ptr m_ctr; std::unique_ptr m_ghash; private: void start_msg(const uint8_t nonce[], size_t nonce_len) override; void key_schedule(const uint8_t key[], size_t length) override; secure_vector m_y0; }; /** * GCM Encryption */ class BOTAN_PUBLIC_API(2,0) GCM_Encryption final : public GCM_Mode { public: /** * @param cipher the 128 bit block cipher to use * @param tag_size is how big the auth tag will be */ GCM_Encryption(BlockCipher* cipher, size_t tag_size = 16) : GCM_Mode(cipher, tag_size) {} size_t output_length(size_t input_length) const override { return input_length + tag_size(); } size_t minimum_final_size() const override { return 0; } size_t process(uint8_t buf[], size_t size) override; void finish(secure_vector& final_block, size_t offset = 0) override; }; /** * GCM Decryption */ class BOTAN_PUBLIC_API(2,0) GCM_Decryption final : public GCM_Mode { public: /** * @param cipher the 128 bit block cipher to use * @param tag_size is how big the auth tag will be */ GCM_Decryption(BlockCipher* cipher, size_t tag_size = 16) : GCM_Mode(cipher, tag_size) {} size_t output_length(size_t input_length) const override { BOTAN_ASSERT(input_length >= tag_size(), "Sufficient input"); return input_length - tag_size(); } size_t minimum_final_size() const override { return tag_size(); } size_t process(uint8_t buf[], size_t size) override; void finish(secure_vector& final_block, size_t offset = 0) override; }; } BOTAN_FUTURE_INTERNAL_HEADER(ghash.h) namespace Botan { /** * GCM's GHASH * This is not intended for general use, but is exposed to allow * shared code between GCM and GMAC */ class BOTAN_PUBLIC_API(2,0) GHASH final : public SymmetricAlgorithm { public: void set_associated_data(const uint8_t ad[], size_t ad_len); secure_vector BOTAN_DEPRECATED("Use other impl") nonce_hash(const uint8_t nonce[], size_t nonce_len) { secure_vector y0(GCM_BS); nonce_hash(y0, nonce, nonce_len); return y0; } void nonce_hash(secure_vector& y0, const uint8_t nonce[], size_t len); void start(const uint8_t nonce[], size_t len); /* * Assumes input len is multiple of 16 */ void update(const uint8_t in[], size_t len); /* * Incremental update of associated data */ void update_associated_data(const uint8_t ad[], size_t len); secure_vector BOTAN_DEPRECATED("Use version taking output params") final() { secure_vector mac(GCM_BS); final(mac.data(), mac.size()); return mac; } void final(uint8_t out[], size_t out_len); Key_Length_Specification key_spec() const override { return Key_Length_Specification(16); } void clear() override; void reset(); std::string name() const override { return "GHASH"; } std::string provider() const; void ghash_update(secure_vector& x, const uint8_t input[], size_t input_len); void add_final_block(secure_vector& x, size_t ad_len, size_t pt_len); private: #if defined(BOTAN_HAS_GHASH_CLMUL_CPU) static void ghash_precompute_cpu(const uint8_t H[16], uint64_t H_pow[4*2]); static void ghash_multiply_cpu(uint8_t x[16], const uint64_t H_pow[4*2], const uint8_t input[], size_t blocks); #endif #if defined(BOTAN_HAS_GHASH_CLMUL_VPERM) static void ghash_multiply_vperm(uint8_t x[16], const uint64_t HM[256], const uint8_t input[], size_t blocks); #endif void key_schedule(const uint8_t key[], size_t key_len) override; void ghash_multiply(secure_vector& x, const uint8_t input[], size_t blocks); static const size_t GCM_BS = 16; secure_vector m_H; secure_vector m_H_ad; secure_vector m_ghash; secure_vector m_nonce; secure_vector m_HM; secure_vector m_H_pow; size_t m_ad_len = 0; size_t m_text_len = 0; }; } namespace Botan { /** * Perform hex encoding * @param output an array of at least input_length*2 bytes * @param input is some binary data * @param input_length length of input in bytes * @param uppercase should output be upper or lower case? */ void BOTAN_PUBLIC_API(2,0) hex_encode(char output[], const uint8_t input[], size_t input_length, bool uppercase = true); /** * Perform hex encoding * @param input some input * @param input_length length of input in bytes * @param uppercase should output be upper or lower case? * @return hexadecimal representation of input */ std::string BOTAN_PUBLIC_API(2,0) hex_encode(const uint8_t input[], size_t input_length, bool uppercase = true); /** * Perform hex encoding * @param input some input * @param uppercase should output be upper or lower case? * @return hexadecimal representation of input */ template std::string hex_encode(const std::vector& input, bool uppercase = true) { return hex_encode(input.data(), input.size(), uppercase); } /** * Perform hex decoding * @param output an array of at least input_length/2 bytes * @param input some hex input * @param input_length length of input in bytes * @param input_consumed is an output parameter which says how many * bytes of input were actually consumed. If less than * input_length, then the range input[consumed:length] * should be passed in later along with more input. * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return number of bytes written to output */ size_t BOTAN_PUBLIC_API(2,0) hex_decode(uint8_t output[], const char input[], size_t input_length, size_t& input_consumed, bool ignore_ws = true); /** * Perform hex decoding * @param output an array of at least input_length/2 bytes * @param input some hex input * @param input_length length of input in bytes * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return number of bytes written to output */ size_t BOTAN_PUBLIC_API(2,0) hex_decode(uint8_t output[], const char input[], size_t input_length, bool ignore_ws = true); /** * Perform hex decoding * @param output an array of at least input_length/2 bytes * @param input some hex input * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return number of bytes written to output */ size_t BOTAN_PUBLIC_API(2,0) hex_decode(uint8_t output[], const std::string& input, bool ignore_ws = true); /** * Perform hex decoding * @param input some hex input * @param input_length the length of input in bytes * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return decoded hex output */ std::vector BOTAN_PUBLIC_API(2,0) hex_decode(const char input[], size_t input_length, bool ignore_ws = true); /** * Perform hex decoding * @param input some hex input * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return decoded hex output */ std::vector BOTAN_PUBLIC_API(2,0) hex_decode(const std::string& input, bool ignore_ws = true); /** * Perform hex decoding * @param input some hex input * @param input_length the length of input in bytes * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return decoded hex output */ secure_vector BOTAN_PUBLIC_API(2,0) hex_decode_locked(const char input[], size_t input_length, bool ignore_ws = true); /** * Perform hex decoding * @param input some hex input * @param ignore_ws ignore whitespace on input; if false, throw an exception if whitespace is encountered * @return decoded hex output */ secure_vector BOTAN_PUBLIC_API(2,0) hex_decode_locked(const std::string& input, bool ignore_ws = true); } BOTAN_FUTURE_INTERNAL_HEADER(loadstor.h) #if defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) #define BOTAN_ENDIAN_N2L(x) reverse_bytes(x) #define BOTAN_ENDIAN_L2N(x) reverse_bytes(x) #define BOTAN_ENDIAN_N2B(x) (x) #define BOTAN_ENDIAN_B2N(x) (x) #elif defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) #define BOTAN_ENDIAN_N2L(x) (x) #define BOTAN_ENDIAN_L2N(x) (x) #define BOTAN_ENDIAN_N2B(x) reverse_bytes(x) #define BOTAN_ENDIAN_B2N(x) reverse_bytes(x) #endif namespace Botan { /** * Byte extraction * @param byte_num which byte to extract, 0 == highest byte * @param input the value to extract from * @return byte byte_num of input */ template inline constexpr uint8_t get_byte(size_t byte_num, T input) { return static_cast( input >> (((~byte_num)&(sizeof(T)-1)) << 3) ); } /** * Make a uint16_t from two bytes * @param i0 the first byte * @param i1 the second byte * @return i0 || i1 */ inline constexpr uint16_t make_uint16(uint8_t i0, uint8_t i1) { return static_cast((static_cast(i0) << 8) | i1); } /** * Make a uint32_t from four bytes * @param i0 the first byte * @param i1 the second byte * @param i2 the third byte * @param i3 the fourth byte * @return i0 || i1 || i2 || i3 */ inline constexpr uint32_t make_uint32(uint8_t i0, uint8_t i1, uint8_t i2, uint8_t i3) { return ((static_cast(i0) << 24) | (static_cast(i1) << 16) | (static_cast(i2) << 8) | (static_cast(i3))); } /** * Make a uint64_t from eight bytes * @param i0 the first byte * @param i1 the second byte * @param i2 the third byte * @param i3 the fourth byte * @param i4 the fifth byte * @param i5 the sixth byte * @param i6 the seventh byte * @param i7 the eighth byte * @return i0 || i1 || i2 || i3 || i4 || i5 || i6 || i7 */ inline constexpr uint64_t make_uint64(uint8_t i0, uint8_t i1, uint8_t i2, uint8_t i3, uint8_t i4, uint8_t i5, uint8_t i6, uint8_t i7) { return ((static_cast(i0) << 56) | (static_cast(i1) << 48) | (static_cast(i2) << 40) | (static_cast(i3) << 32) | (static_cast(i4) << 24) | (static_cast(i5) << 16) | (static_cast(i6) << 8) | (static_cast(i7))); } /** * Load a big-endian word * @param in a pointer to some bytes * @param off an offset into the array * @return off'th T of in, as a big-endian value */ template inline T load_be(const uint8_t in[], size_t off) { in += off * sizeof(T); T out = 0; for(size_t i = 0; i != sizeof(T); ++i) out = static_cast((out << 8) | in[i]); return out; } /** * Load a little-endian word * @param in a pointer to some bytes * @param off an offset into the array * @return off'th T of in, as a litte-endian value */ template inline T load_le(const uint8_t in[], size_t off) { in += off * sizeof(T); T out = 0; for(size_t i = 0; i != sizeof(T); ++i) out = (out << 8) | in[sizeof(T)-1-i]; return out; } /** * Load a big-endian uint16_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint16_t of in, as a big-endian value */ template<> inline uint16_t load_be(const uint8_t in[], size_t off) { in += off * sizeof(uint16_t); #if defined(BOTAN_ENDIAN_N2B) uint16_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2B(x); #else return make_uint16(in[0], in[1]); #endif } /** * Load a little-endian uint16_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint16_t of in, as a little-endian value */ template<> inline uint16_t load_le(const uint8_t in[], size_t off) { in += off * sizeof(uint16_t); #if defined(BOTAN_ENDIAN_N2L) uint16_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2L(x); #else return make_uint16(in[1], in[0]); #endif } /** * Load a big-endian uint32_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint32_t of in, as a big-endian value */ template<> inline uint32_t load_be(const uint8_t in[], size_t off) { in += off * sizeof(uint32_t); #if defined(BOTAN_ENDIAN_N2B) uint32_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2B(x); #else return make_uint32(in[0], in[1], in[2], in[3]); #endif } /** * Load a little-endian uint32_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint32_t of in, as a little-endian value */ template<> inline uint32_t load_le(const uint8_t in[], size_t off) { in += off * sizeof(uint32_t); #if defined(BOTAN_ENDIAN_N2L) uint32_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2L(x); #else return make_uint32(in[3], in[2], in[1], in[0]); #endif } /** * Load a big-endian uint64_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint64_t of in, as a big-endian value */ template<> inline uint64_t load_be(const uint8_t in[], size_t off) { in += off * sizeof(uint64_t); #if defined(BOTAN_ENDIAN_N2B) uint64_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2B(x); #else return make_uint64(in[0], in[1], in[2], in[3], in[4], in[5], in[6], in[7]); #endif } /** * Load a little-endian uint64_t * @param in a pointer to some bytes * @param off an offset into the array * @return off'th uint64_t of in, as a little-endian value */ template<> inline uint64_t load_le(const uint8_t in[], size_t off) { in += off * sizeof(uint64_t); #if defined(BOTAN_ENDIAN_N2L) uint64_t x; typecast_copy(x, in); return BOTAN_ENDIAN_N2L(x); #else return make_uint64(in[7], in[6], in[5], in[4], in[3], in[2], in[1], in[0]); #endif } /** * Load two little-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written */ template inline void load_le(const uint8_t in[], T& x0, T& x1) { x0 = load_le(in, 0); x1 = load_le(in, 1); } /** * Load four little-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written * @param x2 where the third word will be written * @param x3 where the fourth word will be written */ template inline void load_le(const uint8_t in[], T& x0, T& x1, T& x2, T& x3) { x0 = load_le(in, 0); x1 = load_le(in, 1); x2 = load_le(in, 2); x3 = load_le(in, 3); } /** * Load eight little-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written * @param x2 where the third word will be written * @param x3 where the fourth word will be written * @param x4 where the fifth word will be written * @param x5 where the sixth word will be written * @param x6 where the seventh word will be written * @param x7 where the eighth word will be written */ template inline void load_le(const uint8_t in[], T& x0, T& x1, T& x2, T& x3, T& x4, T& x5, T& x6, T& x7) { x0 = load_le(in, 0); x1 = load_le(in, 1); x2 = load_le(in, 2); x3 = load_le(in, 3); x4 = load_le(in, 4); x5 = load_le(in, 5); x6 = load_le(in, 6); x7 = load_le(in, 7); } /** * Load a variable number of little-endian words * @param out the output array of words * @param in the input array of bytes * @param count how many words are in in */ template inline void load_le(T out[], const uint8_t in[], size_t count) { if(count > 0) { #if defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) typecast_copy(out, in, count); #elif defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) typecast_copy(out, in, count); const size_t blocks = count - (count % 4); const size_t left = count - blocks; for(size_t i = 0; i != blocks; i += 4) bswap_4(out + i); for(size_t i = 0; i != left; ++i) out[blocks+i] = reverse_bytes(out[blocks+i]); #else for(size_t i = 0; i != count; ++i) out[i] = load_le(in, i); #endif } } /** * Load two big-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written */ template inline void load_be(const uint8_t in[], T& x0, T& x1) { x0 = load_be(in, 0); x1 = load_be(in, 1); } /** * Load four big-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written * @param x2 where the third word will be written * @param x3 where the fourth word will be written */ template inline void load_be(const uint8_t in[], T& x0, T& x1, T& x2, T& x3) { x0 = load_be(in, 0); x1 = load_be(in, 1); x2 = load_be(in, 2); x3 = load_be(in, 3); } /** * Load eight big-endian words * @param in a pointer to some bytes * @param x0 where the first word will be written * @param x1 where the second word will be written * @param x2 where the third word will be written * @param x3 where the fourth word will be written * @param x4 where the fifth word will be written * @param x5 where the sixth word will be written * @param x6 where the seventh word will be written * @param x7 where the eighth word will be written */ template inline void load_be(const uint8_t in[], T& x0, T& x1, T& x2, T& x3, T& x4, T& x5, T& x6, T& x7) { x0 = load_be(in, 0); x1 = load_be(in, 1); x2 = load_be(in, 2); x3 = load_be(in, 3); x4 = load_be(in, 4); x5 = load_be(in, 5); x6 = load_be(in, 6); x7 = load_be(in, 7); } /** * Load a variable number of big-endian words * @param out the output array of words * @param in the input array of bytes * @param count how many words are in in */ template inline void load_be(T out[], const uint8_t in[], size_t count) { if(count > 0) { #if defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) typecast_copy(out, in, count); #elif defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) typecast_copy(out, in, count); const size_t blocks = count - (count % 4); const size_t left = count - blocks; for(size_t i = 0; i != blocks; i += 4) bswap_4(out + i); for(size_t i = 0; i != left; ++i) out[blocks+i] = reverse_bytes(out[blocks+i]); #else for(size_t i = 0; i != count; ++i) out[i] = load_be(in, i); #endif } } /** * Store a big-endian uint16_t * @param in the input uint16_t * @param out the byte array to write to */ inline void store_be(uint16_t in, uint8_t out[2]) { #if defined(BOTAN_ENDIAN_N2B) uint16_t o = BOTAN_ENDIAN_N2B(in); typecast_copy(out, o); #else out[0] = get_byte(0, in); out[1] = get_byte(1, in); #endif } /** * Store a little-endian uint16_t * @param in the input uint16_t * @param out the byte array to write to */ inline void store_le(uint16_t in, uint8_t out[2]) { #if defined(BOTAN_ENDIAN_N2L) uint16_t o = BOTAN_ENDIAN_N2L(in); typecast_copy(out, o); #else out[0] = get_byte(1, in); out[1] = get_byte(0, in); #endif } /** * Store a big-endian uint32_t * @param in the input uint32_t * @param out the byte array to write to */ inline void store_be(uint32_t in, uint8_t out[4]) { #if defined(BOTAN_ENDIAN_B2N) uint32_t o = BOTAN_ENDIAN_B2N(in); typecast_copy(out, o); #else out[0] = get_byte(0, in); out[1] = get_byte(1, in); out[2] = get_byte(2, in); out[3] = get_byte(3, in); #endif } /** * Store a little-endian uint32_t * @param in the input uint32_t * @param out the byte array to write to */ inline void store_le(uint32_t in, uint8_t out[4]) { #if defined(BOTAN_ENDIAN_L2N) uint32_t o = BOTAN_ENDIAN_L2N(in); typecast_copy(out, o); #else out[0] = get_byte(3, in); out[1] = get_byte(2, in); out[2] = get_byte(1, in); out[3] = get_byte(0, in); #endif } /** * Store a big-endian uint64_t * @param in the input uint64_t * @param out the byte array to write to */ inline void store_be(uint64_t in, uint8_t out[8]) { #if defined(BOTAN_ENDIAN_B2N) uint64_t o = BOTAN_ENDIAN_B2N(in); typecast_copy(out, o); #else out[0] = get_byte(0, in); out[1] = get_byte(1, in); out[2] = get_byte(2, in); out[3] = get_byte(3, in); out[4] = get_byte(4, in); out[5] = get_byte(5, in); out[6] = get_byte(6, in); out[7] = get_byte(7, in); #endif } /** * Store a little-endian uint64_t * @param in the input uint64_t * @param out the byte array to write to */ inline void store_le(uint64_t in, uint8_t out[8]) { #if defined(BOTAN_ENDIAN_L2N) uint64_t o = BOTAN_ENDIAN_L2N(in); typecast_copy(out, o); #else out[0] = get_byte(7, in); out[1] = get_byte(6, in); out[2] = get_byte(5, in); out[3] = get_byte(4, in); out[4] = get_byte(3, in); out[5] = get_byte(2, in); out[6] = get_byte(1, in); out[7] = get_byte(0, in); #endif } /** * Store two little-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word */ template inline void store_le(uint8_t out[], T x0, T x1) { store_le(x0, out + (0 * sizeof(T))); store_le(x1, out + (1 * sizeof(T))); } /** * Store two big-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word */ template inline void store_be(uint8_t out[], T x0, T x1) { store_be(x0, out + (0 * sizeof(T))); store_be(x1, out + (1 * sizeof(T))); } /** * Store four little-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word * @param x2 the third word * @param x3 the fourth word */ template inline void store_le(uint8_t out[], T x0, T x1, T x2, T x3) { store_le(x0, out + (0 * sizeof(T))); store_le(x1, out + (1 * sizeof(T))); store_le(x2, out + (2 * sizeof(T))); store_le(x3, out + (3 * sizeof(T))); } /** * Store four big-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word * @param x2 the third word * @param x3 the fourth word */ template inline void store_be(uint8_t out[], T x0, T x1, T x2, T x3) { store_be(x0, out + (0 * sizeof(T))); store_be(x1, out + (1 * sizeof(T))); store_be(x2, out + (2 * sizeof(T))); store_be(x3, out + (3 * sizeof(T))); } /** * Store eight little-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word * @param x2 the third word * @param x3 the fourth word * @param x4 the fifth word * @param x5 the sixth word * @param x6 the seventh word * @param x7 the eighth word */ template inline void store_le(uint8_t out[], T x0, T x1, T x2, T x3, T x4, T x5, T x6, T x7) { store_le(x0, out + (0 * sizeof(T))); store_le(x1, out + (1 * sizeof(T))); store_le(x2, out + (2 * sizeof(T))); store_le(x3, out + (3 * sizeof(T))); store_le(x4, out + (4 * sizeof(T))); store_le(x5, out + (5 * sizeof(T))); store_le(x6, out + (6 * sizeof(T))); store_le(x7, out + (7 * sizeof(T))); } /** * Store eight big-endian words * @param out the output byte array * @param x0 the first word * @param x1 the second word * @param x2 the third word * @param x3 the fourth word * @param x4 the fifth word * @param x5 the sixth word * @param x6 the seventh word * @param x7 the eighth word */ template inline void store_be(uint8_t out[], T x0, T x1, T x2, T x3, T x4, T x5, T x6, T x7) { store_be(x0, out + (0 * sizeof(T))); store_be(x1, out + (1 * sizeof(T))); store_be(x2, out + (2 * sizeof(T))); store_be(x3, out + (3 * sizeof(T))); store_be(x4, out + (4 * sizeof(T))); store_be(x5, out + (5 * sizeof(T))); store_be(x6, out + (6 * sizeof(T))); store_be(x7, out + (7 * sizeof(T))); } template void copy_out_be(uint8_t out[], size_t out_bytes, const T in[]) { while(out_bytes >= sizeof(T)) { store_be(in[0], out); out += sizeof(T); out_bytes -= sizeof(T); in += 1; } for(size_t i = 0; i != out_bytes; ++i) out[i] = get_byte(i%8, in[0]); } template void copy_out_vec_be(uint8_t out[], size_t out_bytes, const std::vector& in) { copy_out_be(out, out_bytes, in.data()); } template void copy_out_le(uint8_t out[], size_t out_bytes, const T in[]) { while(out_bytes >= sizeof(T)) { store_le(in[0], out); out += sizeof(T); out_bytes -= sizeof(T); in += 1; } for(size_t i = 0; i != out_bytes; ++i) out[i] = get_byte(sizeof(T) - 1 - (i % 8), in[0]); } template void copy_out_vec_le(uint8_t out[], size_t out_bytes, const std::vector& in) { copy_out_le(out, out_bytes, in.data()); } } BOTAN_FUTURE_INTERNAL_HEADER(mul128.h) namespace Botan { #if defined(__SIZEOF_INT128__) && defined(BOTAN_TARGET_CPU_HAS_NATIVE_64BIT) #define BOTAN_TARGET_HAS_NATIVE_UINT128 // Prefer TI mode over __int128 as GCC rejects the latter in pendantic mode #if defined(__GNUG__) typedef unsigned int uint128_t __attribute__((mode(TI))); #else typedef unsigned __int128 uint128_t; #endif #endif } #if defined(BOTAN_TARGET_HAS_NATIVE_UINT128) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) \ do { \ const uint128_t r = static_cast(a) * b; \ *hi = (r >> 64) & 0xFFFFFFFFFFFFFFFF; \ *lo = (r ) & 0xFFFFFFFFFFFFFFFF; \ } while(0) #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC) && defined(BOTAN_TARGET_CPU_HAS_NATIVE_64BIT) #include #pragma intrinsic(_umul128) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) \ do { *lo = _umul128(a, b, hi); } while(0) #elif defined(BOTAN_USE_GCC_INLINE_ASM) #if defined(BOTAN_TARGET_ARCH_IS_X86_64) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) do { \ asm("mulq %3" : "=d" (*hi), "=a" (*lo) : "a" (a), "rm" (b) : "cc"); \ } while(0) #elif defined(BOTAN_TARGET_ARCH_IS_ALPHA) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) do { \ asm("umulh %1,%2,%0" : "=r" (*hi) : "r" (a), "r" (b)); \ *lo = a * b; \ } while(0) #elif defined(BOTAN_TARGET_ARCH_IS_IA64) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) do { \ asm("xmpy.hu %0=%1,%2" : "=f" (*hi) : "f" (a), "f" (b)); \ *lo = a * b; \ } while(0) #elif defined(BOTAN_TARGET_ARCH_IS_PPC64) #define BOTAN_FAST_64X64_MUL(a,b,lo,hi) do { \ asm("mulhdu %0,%1,%2" : "=r" (*hi) : "r" (a), "r" (b) : "cc"); \ *lo = a * b; \ } while(0) #endif #endif namespace Botan { /** * Perform a 64x64->128 bit multiplication */ inline void mul64x64_128(uint64_t a, uint64_t b, uint64_t* lo, uint64_t* hi) { #if defined(BOTAN_FAST_64X64_MUL) BOTAN_FAST_64X64_MUL(a, b, lo, hi); #else /* * Do a 64x64->128 multiply using four 32x32->64 multiplies plus * some adds and shifts. Last resort for CPUs like UltraSPARC (with * 64-bit registers/ALU, but no 64x64->128 multiply) or 32-bit CPUs. */ const size_t HWORD_BITS = 32; const uint32_t HWORD_MASK = 0xFFFFFFFF; const uint32_t a_hi = (a >> HWORD_BITS); const uint32_t a_lo = (a & HWORD_MASK); const uint32_t b_hi = (b >> HWORD_BITS); const uint32_t b_lo = (b & HWORD_MASK); uint64_t x0 = static_cast(a_hi) * b_hi; uint64_t x1 = static_cast(a_lo) * b_hi; uint64_t x2 = static_cast(a_hi) * b_lo; uint64_t x3 = static_cast(a_lo) * b_lo; // this cannot overflow as (2^32-1)^2 + 2^32-1 < 2^64-1 x2 += x3 >> HWORD_BITS; // this one can overflow x2 += x1; // propagate the carry if any x0 += static_cast(static_cast(x2 < x1)) << HWORD_BITS; *hi = x0 + (x2 >> HWORD_BITS); *lo = ((x2 & HWORD_MASK) << HWORD_BITS) + (x3 & HWORD_MASK); #endif } } BOTAN_FUTURE_INTERNAL_HEADER(parsing.h) namespace Botan { /** * Parse a SCAN-style algorithm name * @param scan_name the name * @return the name components */ BOTAN_PUBLIC_API(2,0) std::vector parse_algorithm_name(const std::string& scan_name); /** * Split a string * @param str the input string * @param delim the delimitor * @return string split by delim */ BOTAN_PUBLIC_API(2,0) std::vector split_on( const std::string& str, char delim); /** * Split a string on a character predicate * @param str the input string * @param pred the predicate * * This function will likely be removed in a future release */ BOTAN_PUBLIC_API(2,0) std::vector split_on_pred(const std::string& str, std::function pred); /** * Erase characters from a string */ BOTAN_PUBLIC_API(2,0) BOTAN_DEPRECATED("Unused") std::string erase_chars(const std::string& str, const std::set& chars); /** * Replace a character in a string * @param str the input string * @param from_char the character to replace * @param to_char the character to replace it with * @return str with all instances of from_char replaced by to_char */ BOTAN_PUBLIC_API(2,0) BOTAN_DEPRECATED("Unused") std::string replace_char(const std::string& str, char from_char, char to_char); /** * Replace a character in a string * @param str the input string * @param from_chars the characters to replace * @param to_char the character to replace it with * @return str with all instances of from_chars replaced by to_char */ BOTAN_PUBLIC_API(2,0) BOTAN_DEPRECATED("Unused") std::string replace_chars(const std::string& str, const std::set& from_chars, char to_char); /** * Join a string * @param strs strings to join * @param delim the delimitor * @return string joined by delim */ BOTAN_PUBLIC_API(2,0) std::string string_join(const std::vector& strs, char delim); /** * Parse an ASN.1 OID * @param oid the OID in string form * @return OID components */ BOTAN_PUBLIC_API(2,0) std::vector BOTAN_DEPRECATED("Use OID::from_string(oid).get_components()") parse_asn1_oid(const std::string& oid); /** * Compare two names using the X.509 comparison algorithm * @param name1 the first name * @param name2 the second name * @return true if name1 is the same as name2 by the X.509 comparison rules */ BOTAN_PUBLIC_API(2,0) bool x500_name_cmp(const std::string& name1, const std::string& name2); /** * Convert a string to a number * @param str the string to convert * @return number value of the string */ BOTAN_PUBLIC_API(2,0) uint32_t to_u32bit(const std::string& str); /** * Convert a string to a number * @param str the string to convert * @return number value of the string */ BOTAN_PUBLIC_API(2,3) uint16_t to_uint16(const std::string& str); /** * Convert a time specification to a number * @param timespec the time specification * @return number of seconds represented by timespec */ BOTAN_PUBLIC_API(2,0) uint32_t BOTAN_DEPRECATED("Not used anymore") timespec_to_u32bit(const std::string& timespec); /** * Convert a string representation of an IPv4 address to a number * @param ip_str the string representation * @return integer IPv4 address */ BOTAN_PUBLIC_API(2,0) uint32_t string_to_ipv4(const std::string& ip_str); /** * Convert an IPv4 address to a string * @param ip_addr the IPv4 address to convert * @return string representation of the IPv4 address */ BOTAN_PUBLIC_API(2,0) std::string ipv4_to_string(uint32_t ip_addr); std::map BOTAN_PUBLIC_API(2,0) read_cfg(std::istream& is); /** * Accepts key value pairs deliminated by commas: * * "" (returns empty map) * "K=V" (returns map {'K': 'V'}) * "K1=V1,K2=V2" * "K1=V1,K2=V2,K3=V3" * "K1=V1,K2=V2,K3=a_value\,with\,commas_and_\=equals" * * Values may be empty, keys must be non-empty and unique. Duplicate * keys cause an exception. * * Within both key and value, comma and equals can be escaped with * backslash. Backslash can also be escaped. */ std::map BOTAN_PUBLIC_API(2,8) read_kv(const std::string& kv); std::string BOTAN_PUBLIC_API(2,0) clean_ws(const std::string& s); std::string tolower_string(const std::string& s); /** * Check if the given hostname is a match for the specified wildcard */ bool BOTAN_PUBLIC_API(2,0) host_wildcard_match(const std::string& wildcard, const std::string& host); } BOTAN_FUTURE_INTERNAL_HEADER(rotate.h) namespace Botan { /** * Bit rotation left by a compile-time constant amount * @param input the input word * @return input rotated left by ROT bits */ template inline constexpr T rotl(T input) { static_assert(ROT > 0 && ROT < 8*sizeof(T), "Invalid rotation constant"); return static_cast((input << ROT) | (input >> (8*sizeof(T) - ROT))); } /** * Bit rotation right by a compile-time constant amount * @param input the input word * @return input rotated right by ROT bits */ template inline constexpr T rotr(T input) { static_assert(ROT > 0 && ROT < 8*sizeof(T), "Invalid rotation constant"); return static_cast((input >> ROT) | (input << (8*sizeof(T) - ROT))); } /** * Bit rotation left, variable rotation amount * @param input the input word * @param rot the number of bits to rotate, must be between 0 and sizeof(T)*8-1 * @return input rotated left by rot bits */ template inline T rotl_var(T input, size_t rot) { return rot ? static_cast((input << rot) | (input >> (sizeof(T)*8 - rot))) : input; } /** * Bit rotation right, variable rotation amount * @param input the input word * @param rot the number of bits to rotate, must be between 0 and sizeof(T)*8-1 * @return input rotated right by rot bits */ template inline T rotr_var(T input, size_t rot) { return rot ? static_cast((input >> rot) | (input << (sizeof(T)*8 - rot))) : input; } #if defined(BOTAN_USE_GCC_INLINE_ASM) #if defined(BOTAN_TARGET_ARCH_IS_X86_64) || defined(BOTAN_TARGET_ARCH_IS_X86_32) template<> inline uint32_t rotl_var(uint32_t input, size_t rot) { asm("roll %1,%0" : "+r" (input) : "c" (static_cast(rot)) : "cc"); return input; } template<> inline uint32_t rotr_var(uint32_t input, size_t rot) { asm("rorl %1,%0" : "+r" (input) : "c" (static_cast(rot)) : "cc"); return input; } #endif #endif template BOTAN_DEPRECATED("Use rotl or rotl_var") inline T rotate_left(T input, size_t rot) { // rotl_var does not reduce return rotl_var(input, rot % (8 * sizeof(T))); } template BOTAN_DEPRECATED("Use rotr or rotr_var") inline T rotate_right(T input, size_t rot) { // rotr_var does not reduce return rotr_var(input, rot % (8 * sizeof(T))); } } BOTAN_FUTURE_INTERNAL_HEADER(scan_name.h) namespace Botan { /** A class encapsulating a SCAN name (similar to JCE conventions) http://www.users.zetnet.co.uk/hopwood/crypto/scan/ */ class BOTAN_PUBLIC_API(2,0) SCAN_Name final { public: /** * Create a SCAN_Name * @param algo_spec A SCAN-format name */ explicit SCAN_Name(const char* algo_spec); /** * Create a SCAN_Name * @param algo_spec A SCAN-format name */ explicit SCAN_Name(std::string algo_spec); /** * @return original input string */ const std::string& to_string() const { return m_orig_algo_spec; } BOTAN_DEPRECATED("Use SCAN_Name::to_string") const std::string& as_string() const { return this->to_string(); } /** * @return algorithm name */ const std::string& algo_name() const { return m_alg_name; } /** * @return number of arguments */ size_t arg_count() const { return m_args.size(); } /** * @param lower is the lower bound * @param upper is the upper bound * @return if the number of arguments is between lower and upper */ bool arg_count_between(size_t lower, size_t upper) const { return ((arg_count() >= lower) && (arg_count() <= upper)); } /** * @param i which argument * @return ith argument */ std::string arg(size_t i) const; /** * @param i which argument * @param def_value the default value * @return ith argument or the default value */ std::string arg(size_t i, const std::string& def_value) const; /** * @param i which argument * @param def_value the default value * @return ith argument as an integer, or the default value */ size_t arg_as_integer(size_t i, size_t def_value) const; /** * @return cipher mode (if any) */ std::string cipher_mode() const { return (m_mode_info.size() >= 1) ? m_mode_info[0] : ""; } /** * @return cipher mode padding (if any) */ std::string cipher_mode_pad() const { return (m_mode_info.size() >= 2) ? m_mode_info[1] : ""; } private: std::string m_orig_algo_spec; std::string m_alg_name; std::vector m_args; std::vector m_mode_info; }; // This is unrelated but it is convenient to stash it here template std::vector probe_providers_of(const std::string& algo_spec, const std::vector& possible) { std::vector providers; for(auto&& prov : possible) { std::unique_ptr o(T::create(algo_spec, prov)); if(o) { providers.push_back(prov); // available } } return providers; } } #if __cplusplus < 201402L #endif BOTAN_FUTURE_INTERNAL_HEADER(stl_compatability.h) namespace Botan { /* * std::make_unique functionality similar as we have in C++14. * C++11 version based on proposal for C++14 implemenatation by Stephan T. Lavavej * source: https://isocpp.org/files/papers/N3656.txt */ #if __cplusplus >= 201402L template constexpr auto make_unique(Args&&... args) { return std::make_unique(std::forward(args)...); } template constexpr auto make_unique(std::size_t size) { return std::make_unique(size); } #else namespace stlCompatibilityDetails { template struct _Unique_if { typedef std::unique_ptr _Single_object; }; template struct _Unique_if { typedef std::unique_ptr _Unknown_bound; }; template struct _Unique_if { typedef void _Known_bound; }; } // namespace stlCompatibilityDetails template typename stlCompatibilityDetails::_Unique_if::_Single_object make_unique(Args&&... args) { return std::unique_ptr(new T(std::forward(args)...)); } template typename stlCompatibilityDetails::_Unique_if::_Unknown_bound make_unique(size_t n) { typedef typename std::remove_extent::type U; return std::unique_ptr(new U[n]()); } template typename stlCompatibilityDetails::_Unique_if::_Known_bound make_unique(Args&&...) = delete; #endif } // namespace Botan #if defined(BOTAN_HAS_STREAM_CIPHER) #endif BOTAN_FUTURE_INTERNAL_HEADER(stream_mode.h) namespace Botan { #if defined(BOTAN_HAS_STREAM_CIPHER) class BOTAN_PUBLIC_API(2,0) Stream_Cipher_Mode final : public Cipher_Mode { public: /** * @param cipher underyling stream cipher */ explicit Stream_Cipher_Mode(StreamCipher* cipher) : m_cipher(cipher) {} size_t process(uint8_t buf[], size_t sz) override { m_cipher->cipher1(buf, sz); return sz; } void finish(secure_vector& buf, size_t offset) override { return update(buf, offset); } size_t output_length(size_t input_length) const override { return input_length; } size_t update_granularity() const override { return 1; } size_t minimum_final_size() const override { return 0; } size_t default_nonce_length() const override { return 0; } bool valid_nonce_length(size_t nonce_len) const override { return m_cipher->valid_iv_length(nonce_len); } Key_Length_Specification key_spec() const override { return m_cipher->key_spec(); } std::string name() const override { return m_cipher->name(); } void clear() override { m_cipher->clear(); reset(); } void reset() override { /* no msg state */ } private: void start_msg(const uint8_t nonce[], size_t nonce_len) override { if(nonce_len > 0) { m_cipher->set_iv(nonce, nonce_len); } } void key_schedule(const uint8_t key[], size_t length) override { m_cipher->set_key(key, length); } std::unique_ptr m_cipher; }; #endif } namespace Botan { /* * Get information describing the version */ /** * Get a human-readable string identifying the version of Botan. * No particular format should be assumed. * @return version string */ BOTAN_PUBLIC_API(2,0) std::string version_string(); /** * Same as version_string() except returning a pointer to a statically * allocated string. * @return version string */ BOTAN_PUBLIC_API(2,0) const char* version_cstr(); /** * Return a version string of the form "MAJOR.MINOR.PATCH" where * each of the values is an integer. */ BOTAN_PUBLIC_API(2,4) std::string short_version_string(); /** * Same as version_short_string except returning a pointer to the string. */ BOTAN_PUBLIC_API(2,4) const char* short_version_cstr(); /** * Return the date this version of botan was released, in an integer of * the form YYYYMMDD. For instance a version released on May 21, 2013 * would return the integer 20130521. If the currently running version * is not an official release, this function will return 0 instead. * * @return release date, or zero if unreleased */ BOTAN_PUBLIC_API(2,0) uint32_t version_datestamp(); /** * Get the major version number. * @return major version number */ BOTAN_PUBLIC_API(2,0) uint32_t version_major(); /** * Get the minor version number. * @return minor version number */ BOTAN_PUBLIC_API(2,0) uint32_t version_minor(); /** * Get the patch number. * @return patch number */ BOTAN_PUBLIC_API(2,0) uint32_t version_patch(); /** * Usable for checking that the DLL version loaded at runtime exactly * matches the compile-time version. Call using BOTAN_VERSION_* macro * values. Returns the empty string if an exact match, otherwise an * appropriate message. Added with 1.11.26. */ BOTAN_PUBLIC_API(2,0) std::string runtime_version_check(uint32_t major, uint32_t minor, uint32_t patch); /* * Macros for compile-time version checks */ #define BOTAN_VERSION_CODE_FOR(a,b,c) ((a << 16) | (b << 8) | (c)) /** * Compare using BOTAN_VERSION_CODE_FOR, as in * # if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,8,0) * # error "Botan version too old" * # endif */ #define BOTAN_VERSION_CODE BOTAN_VERSION_CODE_FOR(BOTAN_VERSION_MAJOR, \ BOTAN_VERSION_MINOR, \ BOTAN_VERSION_PATCH) } #endif // BOTAN_AMALGAMATION_H_ OSCAR-code-v1.3.1/oscar/SleepLib/thirdparty/miniz.c000066400000000000000000011516571417327530600220030ustar00rootroot00000000000000/************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC * All Rights Reserved. * * 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. * **************************************************************************/ #include "miniz.h" typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; #ifdef __cplusplus extern "C" { #endif /* ------------------- zlib-style API's */ mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) { mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; if (!ptr) return MZ_ADLER32_INIT; while (buf_len) { for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; } for (; i < block_len; ++i) s1 += *ptr++, s2 += s1; s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; } return (s2 << 16) + s1; } /* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */ #if 0 mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) { static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; mz_uint32 crcu32 = (mz_uint32)crc; if (!ptr) return MZ_CRC32_INIT; crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } return ~crcu32; } #else /* Faster, but larger CPU cache footprint. */ mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) { static const mz_uint32 s_crc_table[256] = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D }; mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr; while (buf_len >= 4) { crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; pByte_buf += 4; buf_len -= 4; } while (buf_len) { crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; ++pByte_buf; --buf_len; } return ~crc32; } #endif void mz_free(void *p) { MZ_FREE(p); } void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } void miniz_def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); } const char *mz_version(void) { return MZ_VERSION; } #ifndef MINIZ_NO_ZLIB_APIS int mz_deflateInit(mz_streamp pStream, int level) { return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); } int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) { tdefl_compressor *pComp; mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); if (!pStream) return MZ_STREAM_ERROR; if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR; pStream->data_type = 0; pStream->adler = MZ_ADLER32_INIT; pStream->msg = NULL; pStream->reserved = 0; pStream->total_in = 0; pStream->total_out = 0; if (!pStream->zalloc) pStream->zalloc = miniz_def_alloc_func; if (!pStream->zfree) pStream->zfree = miniz_def_free_func; pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); if (!pComp) return MZ_MEM_ERROR; pStream->state = (struct mz_internal_state *)pComp; if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) { mz_deflateEnd(pStream); return MZ_PARAM_ERROR; } return MZ_OK; } int mz_deflateReset(mz_streamp pStream) { if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR; pStream->total_in = pStream->total_out = 0; tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags); return MZ_OK; } int mz_deflate(mz_streamp pStream, int flush) { size_t in_bytes, out_bytes; mz_ulong orig_total_in, orig_total_out; int mz_status = MZ_OK; if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR; if (!pStream->avail_out) return MZ_BUF_ERROR; if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; orig_total_in = pStream->total_in; orig_total_out = pStream->total_out; for (;;) { tdefl_status defl_status; in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; if (defl_status < 0) { mz_status = MZ_STREAM_ERROR; break; } else if (defl_status == TDEFL_STATUS_DONE) { mz_status = MZ_STREAM_END; break; } else if (!pStream->avail_out) break; else if ((!pStream->avail_in) && (flush != MZ_FINISH)) { if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) break; return MZ_BUF_ERROR; /* Can't make forward progress without some input. */ } } return mz_status; } int mz_deflateEnd(mz_streamp pStream) { if (!pStream) return MZ_STREAM_ERROR; if (pStream->state) { pStream->zfree(pStream->opaque, pStream->state); pStream->state = NULL; } return MZ_OK; } mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) { (void)pStream; /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */ return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); } int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) { int status; mz_stream stream; memset(&stream, 0, sizeof(stream)); /* In case mz_ulong is 64-bits (argh I hate longs). */ if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; stream.avail_in = (mz_uint32)source_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; status = mz_deflateInit(&stream, level); if (status != MZ_OK) return status; status = mz_deflate(&stream, MZ_FINISH); if (status != MZ_STREAM_END) { mz_deflateEnd(&stream); return (status == MZ_OK) ? MZ_BUF_ERROR : status; } *pDest_len = stream.total_out; return mz_deflateEnd(&stream); } int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) { return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); } mz_ulong mz_compressBound(mz_ulong source_len) { return mz_deflateBound(NULL, source_len); } typedef struct { tinfl_decompressor m_decomp; mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits; mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; tinfl_status m_last_status; } inflate_state; int mz_inflateInit2(mz_streamp pStream, int window_bits) { inflate_state *pDecomp; if (!pStream) return MZ_STREAM_ERROR; if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR; pStream->data_type = 0; pStream->adler = 0; pStream->msg = NULL; pStream->total_in = 0; pStream->total_out = 0; pStream->reserved = 0; if (!pStream->zalloc) pStream->zalloc = miniz_def_alloc_func; if (!pStream->zfree) pStream->zfree = miniz_def_free_func; pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); if (!pDecomp) return MZ_MEM_ERROR; pStream->state = (struct mz_internal_state *)pDecomp; tinfl_init(&pDecomp->m_decomp); pDecomp->m_dict_ofs = 0; pDecomp->m_dict_avail = 0; pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; pDecomp->m_first_call = 1; pDecomp->m_has_flushed = 0; pDecomp->m_window_bits = window_bits; return MZ_OK; } int mz_inflateInit(mz_streamp pStream) { return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); } int mz_inflateReset(mz_streamp pStream) { inflate_state *pDecomp; if (!pStream) return MZ_STREAM_ERROR; pStream->data_type = 0; pStream->adler = 0; pStream->msg = NULL; pStream->total_in = 0; pStream->total_out = 0; pStream->reserved = 0; pDecomp = (inflate_state *)pStream->state; tinfl_init(&pDecomp->m_decomp); pDecomp->m_dict_ofs = 0; pDecomp->m_dict_avail = 0; pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; pDecomp->m_first_call = 1; pDecomp->m_has_flushed = 0; /* pDecomp->m_window_bits = window_bits */; return MZ_OK; } int mz_inflate(mz_streamp pStream, int flush) { inflate_state *pState; mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; size_t in_bytes, out_bytes, orig_avail_in; tinfl_status status; if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR; if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; pState = (inflate_state *)pStream->state; if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; orig_avail_in = pStream->avail_in; first_call = pState->m_first_call; pState->m_first_call = 0; if (pState->m_last_status < 0) return MZ_DATA_ERROR; if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; pState->m_has_flushed |= (flush == MZ_FINISH); if ((flush == MZ_FINISH) && (first_call)) { /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */ decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); pState->m_last_status = status; pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; if (status < 0) return MZ_DATA_ERROR; else if (status != TINFL_STATUS_DONE) { pState->m_last_status = TINFL_STATUS_FAILED; return MZ_BUF_ERROR; } return MZ_STREAM_END; } /* flush != MZ_FINISH then we must assume there's more input. */ if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; if (pState->m_dict_avail) { n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; } for (;;) { in_bytes = pStream->avail_in; out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); pState->m_last_status = status; pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); pState->m_dict_avail = (mz_uint)out_bytes; n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); if (status < 0) return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */ else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */ else if (flush == MZ_FINISH) { /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */ if (status == TINFL_STATUS_DONE) return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */ else if (!pStream->avail_out) return MZ_BUF_ERROR; } else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) break; } return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; } int mz_inflateEnd(mz_streamp pStream) { if (!pStream) return MZ_STREAM_ERROR; if (pStream->state) { pStream->zfree(pStream->opaque, pStream->state); pStream->state = NULL; } return MZ_OK; } int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) { mz_stream stream; int status; memset(&stream, 0, sizeof(stream)); /* In case mz_ulong is 64-bits (argh I hate longs). */ if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; stream.avail_in = (mz_uint32)source_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; status = mz_inflateInit(&stream); if (status != MZ_OK) return status; status = mz_inflate(&stream, MZ_FINISH); if (status != MZ_STREAM_END) { mz_inflateEnd(&stream); return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; } *pDest_len = stream.total_out; return mz_inflateEnd(&stream); } const char *mz_error(int err) { static struct { int m_err; const char *m_pDesc; } s_error_descs[] = { { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } }; mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc; return NULL; } #endif /*MINIZ_NO_ZLIB_APIS */ #ifdef __cplusplus } #endif /* This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. 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 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. For more information, please refer to */ /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC * All Rights Reserved. * * 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. * **************************************************************************/ #ifdef __cplusplus extern "C" { #endif /* ------------------- Low-level Compression (independent from all decompression API's) */ /* Purposely making these tables static for faster init and thread safety. */ static const mz_uint16 s_tdefl_len_sym[256] = { 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272, 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285 }; static const mz_uint8 s_tdefl_len_extra[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0 }; static const mz_uint8 s_tdefl_small_dist_sym[512] = { 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17 }; static const mz_uint8 s_tdefl_small_dist_extra[512] = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 }; static const mz_uint8 s_tdefl_large_dist_sym[128] = { 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 }; static const mz_uint8 s_tdefl_large_dist_extra[128] = { 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 }; /* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */ typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1) { mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_OBJ(hist); for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) { const mz_uint32 *pHist = &hist[pass << 8]; mz_uint offsets[256], cur_ofs = 0; for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; { tdefl_sym_freq *t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } } return pCur_syms; } /* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */ static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) { int root, leaf, next, avbl, used, dpth; if (n == 0) return; else if (n == 1) { A[0].m_key = 1; return; } A[0].m_key += A[1].m_key; root = 0; leaf = 2; for (next = 1; next < n - 1; next++) { if (leaf >= n || A[root].m_key < A[leaf].m_key) { A[next].m_key = A[root].m_key; A[root++].m_key = (mz_uint16)next; } else A[next].m_key = A[leaf++].m_key; if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); A[root++].m_key = (mz_uint16)next; } else A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); } A[n - 2].m_key = 0; for (next = n - 3; next >= 0; next--) A[next].m_key = A[A[next].m_key].m_key + 1; avbl = 1; used = dpth = 0; root = n - 2; next = n - 1; while (avbl > 0) { while (root >= 0 && (int)A[root].m_key == dpth) { used++; root--; } while (avbl > used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; } avbl = 2 * used; dpth++; used = 0; } } /* Limits canonical Huffman code table's max code size. */ enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) { int i; mz_uint32 total = 0; if (code_list_len <= 1) return; for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); while (total != (1UL << max_code_size)) { pNum_codes[max_code_size]--; for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } total--; } } static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) { int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_OBJ(num_codes); if (static_table) { for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; } else { tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; int num_used_syms = 0; const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; } pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); for (i = 1, j = num_used_syms; i <= code_size_limit; i++) for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); } next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); for (i = 0; i < table_len; i++) { mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; } } #define TDEFL_PUT_BITS(b, l) \ do \ { \ mz_uint bits = b; \ mz_uint len = l; \ MZ_ASSERT(bits <= ((1U << len) - 1U)); \ d->m_bit_buffer |= (bits << d->m_bits_in); \ d->m_bits_in += len; \ while (d->m_bits_in >= 8) \ { \ if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ d->m_bit_buffer >>= 8; \ d->m_bits_in -= 8; \ } \ } \ MZ_MACRO_END #define TDEFL_RLE_PREV_CODE_SIZE() \ { \ if (rle_repeat_count) \ { \ if (rle_repeat_count < 3) \ { \ d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ while (rle_repeat_count--) \ packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ } \ else \ { \ d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ packed_code_sizes[num_packed_code_sizes++] = 16; \ packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ } \ rle_repeat_count = 0; \ } \ } #define TDEFL_RLE_ZERO_CODE_SIZE() \ { \ if (rle_z_count) \ { \ if (rle_z_count < 3) \ { \ d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ while (rle_z_count--) \ packed_code_sizes[num_packed_code_sizes++] = 0; \ } \ else if (rle_z_count <= 10) \ { \ d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ packed_code_sizes[num_packed_code_sizes++] = 17; \ packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ } \ else \ { \ d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ packed_code_sizes[num_packed_code_sizes++] = 18; \ packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ } \ rle_z_count = 0; \ } \ } static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; static void tdefl_start_dynamic_block(tdefl_compressor *d) { int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; d->m_huff_count[0][256] = 1; tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); for (i = 0; i < total_code_sizes_to_pack; i++) { mz_uint8 code_size = code_sizes_to_pack[i]; if (!code_size) { TDEFL_RLE_PREV_CODE_SIZE(); if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); } } else { TDEFL_RLE_ZERO_CODE_SIZE(); if (code_size != prev_code_size) { TDEFL_RLE_PREV_CODE_SIZE(); d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; } else if (++rle_repeat_count == 6) { TDEFL_RLE_PREV_CODE_SIZE(); } } prev_code_size = code_size; } if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); } tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); TDEFL_PUT_BITS(2, 2); TDEFL_PUT_BITS(num_lit_codes - 257, 5); TDEFL_PUT_BITS(num_dist_codes - 1, 5); for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4); for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;) { mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); } } static void tdefl_start_static_block(tdefl_compressor *d) { mz_uint i; mz_uint8 *p = &d->m_huff_code_sizes[0][0]; for (i = 0; i <= 143; ++i) *p++ = 8; for (; i <= 255; ++i) *p++ = 9; for (; i <= 279; ++i) *p++ = 7; for (; i <= 287; ++i) *p++ = 8; memset(d->m_huff_code_sizes[1], 5, 32); tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); TDEFL_PUT_BITS(1, 2); } static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { mz_uint flags; mz_uint8 *pLZ_codes; mz_uint8 *pOutput_buf = d->m_pOutput_buf; mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; mz_uint64 bit_buffer = d->m_bit_buffer; mz_uint bits_in = d->m_bits_in; #define TDEFL_PUT_BITS_FAST(b, l) \ { \ bit_buffer |= (((mz_uint64)(b)) << bits_in); \ bits_in += (l); \ } flags = 1; for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) { if (flags == 1) flags = *pLZ_codes++ | 0x100; if (flags & 1) { mz_uint s0, s1, n0, n1, sym, num_extra_bits; mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); pLZ_codes += 3; MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); /* This sequence coaxes MSVC into using cmov's vs. jmp's. */ s0 = s_tdefl_small_dist_sym[match_dist & 511]; n0 = s_tdefl_small_dist_extra[match_dist & 511]; s1 = s_tdefl_large_dist_sym[match_dist >> 8]; n1 = s_tdefl_large_dist_extra[match_dist >> 8]; sym = (match_dist < 512) ? s0 : s1; num_extra_bits = (match_dist < 512) ? n0 : n1; MZ_ASSERT(d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); } else { mz_uint lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { flags >>= 1; lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { flags >>= 1; lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); } } } if (pOutput_buf >= d->m_pOutput_buf_end) return MZ_FALSE; *(mz_uint64 *)pOutput_buf = bit_buffer; pOutput_buf += (bits_in >> 3); bit_buffer >>= (bits_in & ~7); bits_in &= 7; } #undef TDEFL_PUT_BITS_FAST d->m_pOutput_buf = pOutput_buf; d->m_bits_in = 0; d->m_bit_buffer = 0; while (bits_in) { mz_uint32 n = MZ_MIN(bits_in, 16); TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); bit_buffer >>= n; bits_in -= n; } TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); return (d->m_pOutput_buf < d->m_pOutput_buf_end); } #else static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { mz_uint flags; mz_uint8 *pLZ_codes; flags = 1; for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) { if (flags == 1) flags = *pLZ_codes++ | 0x100; if (flags & 1) { mz_uint sym, num_extra_bits; mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); if (match_dist < 512) { sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist]; } else { sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; } MZ_ASSERT(d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); } else { mz_uint lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); } } TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); return (d->m_pOutput_buf < d->m_pOutput_buf_end); } #endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */ static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) { if (static_block) tdefl_start_static_block(d); else tdefl_start_dynamic_block(d); return tdefl_compress_lz_codes(d); } static int tdefl_flush_block(tdefl_compressor *d, int flush) { mz_uint saved_bit_buf, saved_bits_in; mz_uint8 *pSaved_output_buf; mz_bool comp_block_succeeded = MZ_FALSE; int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; d->m_pOutput_buf = pOutput_buf_start; d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; MZ_ASSERT(!d->m_output_flush_remaining); d->m_output_flush_ofs = 0; d->m_output_flush_remaining = 0; *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) { TDEFL_PUT_BITS(0x78, 8); TDEFL_PUT_BITS(0x01, 8); } TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in; if (!use_raw_block) comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */ if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) { mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; TDEFL_PUT_BITS(0, 2); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) { TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); } for (i = 0; i < d->m_total_lz_bytes; ++i) { TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); } } /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */ else if (!comp_block_succeeded) { d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; tdefl_compress_block(d, MZ_TRUE); } if (flush) { if (flush == TDEFL_FINISH) { if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } } } else { mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); } } } MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++; if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) { if (d->m_pPut_buf_func) { *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); } else if (pOutput_buf_start == d->m_output_buf) { int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); d->m_out_buf_ofs += bytes_to_copy; if ((n -= bytes_to_copy) != 0) { d->m_output_flush_ofs = bytes_to_copy; d->m_output_flush_remaining = n; } } else { d->m_out_buf_ofs += n; } } return d->m_output_flush_remaining; } #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES #ifdef MINIZ_UNALIGNED_USE_MEMCPY static mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p) { mz_uint16 ret; memcpy(&ret, p, sizeof(mz_uint16)); return ret; } static mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p) { mz_uint16 ret; memcpy(&ret, p, sizeof(mz_uint16)); return ret; } #else #define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p) #define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p) #endif static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) { mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s); MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; for (;;) { for (;;) { if (--num_probes_left == 0) return; #define TDEFL_PROBE \ next_probe_pos = d->m_next[probe_pos]; \ if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ return; \ probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ break; TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; } if (!dist) break; q = (const mz_uint16 *)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD2(q) != s01) continue; p = s; probe_len = 32; do { } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); if (!probe_len) { *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN); break; } else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len) { *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break; c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); } } } #else static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) { mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; const mz_uint8 *s = d->m_dict + pos, *p, *q; mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; for (;;) { for (;;) { if (--num_probes_left == 0) return; #define TDEFL_PROBE \ next_probe_pos = d->m_next[probe_pos]; \ if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ return; \ probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \ break; TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; } if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break; if (probe_len > match_len) { *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return; c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1]; } } } #endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */ #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN #ifdef MINIZ_UNALIGNED_USE_MEMCPY static mz_uint32 TDEFL_READ_UNALIGNED_WORD32(const mz_uint8* p) { mz_uint32 ret; memcpy(&ret, p, sizeof(mz_uint32)); return ret; } #else #define TDEFL_READ_UNALIGNED_WORD32(p) *(const mz_uint32 *)(p) #endif static mz_bool tdefl_compress_fast(tdefl_compressor *d) { /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */ mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) { const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); d->m_src_buf_left -= num_bytes_to_process; lookahead_size += num_bytes_to_process; while (num_bytes_to_process) { mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); memcpy(d->m_dict + dst_pos, d->m_pSrc, n); if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); d->m_pSrc += n; dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; num_bytes_to_process -= n; } dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break; while (lookahead_size >= 4) { mz_uint cur_match_dist, cur_match_len = 1; mz_uint8 *pCur_dict = d->m_dict + cur_pos; mz_uint first_trigram = TDEFL_READ_UNALIGNED_WORD32(pCur_dict) & 0xFFFFFF; mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; mz_uint probe_pos = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)lookahead_pos; if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((TDEFL_READ_UNALIGNED_WORD32(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) { const mz_uint16 *p = (const mz_uint16 *)pCur_dict; const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); mz_uint32 probe_len = 32; do { } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); if (!probe_len) cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) { cur_match_len = 1; *pLZ_code_buf++ = (mz_uint8)first_trigram; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); d->m_huff_count[0][(mz_uint8)first_trigram]++; } else { mz_uint32 s0, s1; cur_match_len = MZ_MIN(cur_match_len, lookahead_size); MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); cur_match_dist--; pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); #ifdef MINIZ_UNALIGNED_USE_MEMCPY memcpy(&pLZ_code_buf[1], &cur_match_dist, sizeof(cur_match_dist)); #else *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; #endif pLZ_code_buf += 3; *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; } } else { *pLZ_code_buf++ = (mz_uint8)first_trigram; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); d->m_huff_count[0][(mz_uint8)first_trigram]++; } if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } total_lz_bytes += cur_match_len; lookahead_pos += cur_match_len; dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE); cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; MZ_ASSERT(lookahead_size >= cur_match_len); lookahead_size -= cur_match_len; if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { int n; d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; } } while (lookahead_size) { mz_uint8 lit = d->m_dict[cur_pos]; total_lz_bytes++; *pLZ_code_buf++ = lit; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } d->m_huff_count[0][lit]++; lookahead_pos++; dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE); cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; lookahead_size--; if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { int n; d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; } } } d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; return MZ_TRUE; } #endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) { d->m_total_lz_bytes++; *d->m_pLZ_code_buf++ = lit; *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } d->m_huff_count[0][lit]++; } static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) { mz_uint32 s0, s1; MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); d->m_total_lz_bytes += match_len; d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); match_dist -= 1; d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3; *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; if (match_len >= TDEFL_MIN_MATCH_LEN) d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; } static mz_bool tdefl_compress_normal(tdefl_compressor *d) { const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left; tdefl_flush flush = d->m_flush; while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) { mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */ if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) { mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; src_buf_left -= num_bytes_to_process; d->m_lookahead_size += num_bytes_to_process; while (pSrc != pSrc_end) { mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++; } } else { while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) { mz_uint8 c = *pSrc++; mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; src_buf_left--; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) { mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); } } } d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) break; /* Simple lazy/greedy parsing state machine. */ len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) { if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) { mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; } if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1; } } else { tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); } if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) { cur_match_dist = cur_match_len = 0; } if (d->m_saved_match_len) { if (cur_match_len > d->m_saved_match_len) { tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); if (cur_match_len >= 128) { tdefl_record_match(d, cur_match_len, cur_match_dist); d->m_saved_match_len = 0; len_to_move = cur_match_len; } else { d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; } } else { tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0; } } else if (!cur_match_dist) tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) { tdefl_record_match(d, cur_match_len, cur_match_dist); len_to_move = cur_match_len; } else { d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; } /* Move the lookahead forward by len_to_move bytes. */ d->m_lookahead_pos += len_to_move; MZ_ASSERT(d->m_lookahead_size >= len_to_move); d->m_lookahead_size -= len_to_move; d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE); /* Check if it's time to flush the current LZ codes to the internal output buffer. */ if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) { int n; d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; } } d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; return MZ_TRUE; } static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) { if (d->m_pIn_buf_size) { *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; } if (d->m_pOut_buf_size) { size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); d->m_output_flush_ofs += (mz_uint)n; d->m_output_flush_remaining -= (mz_uint)n; d->m_out_buf_ofs += n; *d->m_pOut_buf_size = d->m_out_buf_ofs; } return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; } tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) { if (!d) { if (pIn_buf_size) *pIn_buf_size = 0; if (pOut_buf_size) *pOut_buf_size = 0; return TDEFL_STATUS_BAD_PARAM; } d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size; d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size; d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; d->m_out_buf_ofs = 0; d->m_flush = flush; if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf)) { if (pIn_buf_size) *pIn_buf_size = 0; if (pOut_buf_size) *pOut_buf_size = 0; return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); } d->m_wants_to_finish |= (flush == TDEFL_FINISH); if ((d->m_output_flush_remaining) || (d->m_finished)) return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) { if (!tdefl_compress_fast(d)) return d->m_prev_return_status; } else #endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ { if (!tdefl_compress_normal(d)) return d->m_prev_return_status; } if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) { if (tdefl_flush_block(d, flush) < 0) return d->m_prev_return_status; d->m_finished = (flush == TDEFL_FINISH); if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_OBJ(d->m_hash); MZ_CLEAR_OBJ(d->m_next); d->m_dict_size = 0; } } return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); } tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) { MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); } tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user; d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_hash); d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY; d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1; d->m_pIn_buf = NULL; d->m_pOut_buf = NULL; d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL; d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_dict); memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); return TDEFL_STATUS_OKAY; } tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) { return d->m_prev_return_status; } mz_uint32 tdefl_get_adler32(tdefl_compressor *d) { return d->m_adler32; } mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE; succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); MZ_FREE(pComp); return succeeded; } typedef struct { size_t m_size, m_capacity; mz_uint8 *m_pBuf; mz_bool m_expandable; } tdefl_output_buffer; static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) { tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; size_t new_size = p->m_size + len; if (new_size > p->m_capacity) { size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE; do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity); pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE; p->m_pBuf = pNew_buf; p->m_capacity = new_capacity; } memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size; return MZ_TRUE; } void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) { tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); if (!pOut_len) return MZ_FALSE; else *pOut_len = 0; out_buf.m_expandable = MZ_TRUE; if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL; *pOut_len = out_buf.m_size; return out_buf.m_pBuf; } size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) { tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); if (!pOut_buf) return 0; out_buf.m_pBuf = (mz_uint8 *)pOut_buf; out_buf.m_capacity = out_buf_len; if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0; return out_buf.m_size; } static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; /* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */ mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) { mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER; if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES; else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK; else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES; return comp_flags; } #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */ #endif /* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */ void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) { /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */ static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0; if (!pComp) return NULL; MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } /* write dummy header */ for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); /* compress image data */ tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } /* write real header */ *pLen_out = out_buf.m_size - 41; { static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x44, 0x41, 0x54 }; pnghdr[18] = (mz_uint8)(w >> 8); pnghdr[19] = (mz_uint8)w; pnghdr[22] = (mz_uint8)(h >> 8); pnghdr[23] = (mz_uint8)h; pnghdr[25] = chans[num_chans]; pnghdr[33] = (mz_uint8)(*pLen_out >> 24); pnghdr[34] = (mz_uint8)(*pLen_out >> 16); pnghdr[35] = (mz_uint8)(*pLen_out >> 8); pnghdr[36] = (mz_uint8)*pLen_out; c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); for (i = 0; i < 4; ++i, c <<= 8) ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); memcpy(out_buf.m_pBuf, pnghdr, 41); } /* write footer (IDAT CRC-32, followed by IEND chunk) */ if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); for (i = 0; i < 4; ++i, c <<= 8) (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); /* compute final size of file, grab compressed data buffer and return */ *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf; } void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) { /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */ return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); } #ifndef MINIZ_NO_MALLOC /* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ /* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ /* structure size and allocation mechanism. */ tdefl_compressor *tdefl_compressor_alloc() { return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); } void tdefl_compressor_free(tdefl_compressor *pComp) { MZ_FREE(pComp); } #endif #ifdef _MSC_VER #pragma warning(pop) #endif #ifdef __cplusplus } #endif /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC * All Rights Reserved. * * 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. * **************************************************************************/ #ifdef __cplusplus extern "C" { #endif /* ------------------- Low-level Decompression (completely independent from all compression API's) */ #define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) #define TINFL_MEMSET(p, c, l) memset(p, c, l) #define TINFL_CR_BEGIN \ switch (r->m_state) \ { \ case 0: #define TINFL_CR_RETURN(state_index, result) \ do \ { \ status = result; \ r->m_state = state_index; \ goto common_exit; \ case state_index:; \ } \ MZ_MACRO_END #define TINFL_CR_RETURN_FOREVER(state_index, result) \ do \ { \ for (;;) \ { \ TINFL_CR_RETURN(state_index, result); \ } \ } \ MZ_MACRO_END #define TINFL_CR_FINISH } #define TINFL_GET_BYTE(state_index, c) \ do \ { \ while (pIn_buf_cur >= pIn_buf_end) \ { \ TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \ } \ c = *pIn_buf_cur++; \ } \ MZ_MACRO_END #define TINFL_NEED_BITS(state_index, n) \ do \ { \ mz_uint c; \ TINFL_GET_BYTE(state_index, c); \ bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ num_bits += 8; \ } while (num_bits < (mz_uint)(n)) #define TINFL_SKIP_BITS(state_index, n) \ do \ { \ if (num_bits < (mz_uint)(n)) \ { \ TINFL_NEED_BITS(state_index, n); \ } \ bit_buf >>= (n); \ num_bits -= (n); \ } \ MZ_MACRO_END #define TINFL_GET_BITS(state_index, b, n) \ do \ { \ if (num_bits < (mz_uint)(n)) \ { \ TINFL_NEED_BITS(state_index, n); \ } \ b = bit_buf & ((1 << (n)) - 1); \ bit_buf >>= (n); \ num_bits -= (n); \ } \ MZ_MACRO_END /* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */ /* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */ /* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */ /* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ #define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ do \ { \ temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ if (temp >= 0) \ { \ code_len = temp >> 9; \ if ((code_len) && (num_bits >= code_len)) \ break; \ } \ else if (num_bits > TINFL_FAST_LOOKUP_BITS) \ { \ code_len = TINFL_FAST_LOOKUP_BITS; \ do \ { \ temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ } while ((temp < 0) && (num_bits >= (code_len + 1))); \ if (temp >= 0) \ break; \ } \ TINFL_GET_BYTE(state_index, c); \ bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ num_bits += 8; \ } while (num_bits < 15); /* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */ /* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */ /* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */ /* The slow path is only executed at the very end of the input buffer. */ /* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */ /* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ #define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ do \ { \ int temp; \ mz_uint code_len, c; \ if (num_bits < 15) \ { \ if ((pIn_buf_end - pIn_buf_cur) < 2) \ { \ TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ } \ else \ { \ bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ pIn_buf_cur += 2; \ num_bits += 16; \ } \ } \ if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ code_len = temp >> 9, temp &= 511; \ else \ { \ code_len = TINFL_FAST_LOOKUP_BITS; \ do \ { \ temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ } while (temp < 0); \ } \ sym = temp; \ bit_buf >>= code_len; \ num_bits -= code_len; \ } \ MZ_MACRO_END tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) { static const int s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; static const int s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; static const int s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; static const int s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; static const int s_min_table_sizes[3] = { 257, 1, 4 }; tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; TINFL_CR_BEGIN bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } } do { TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; if (r->m_type == 0) { TINFL_SKIP_BITS(5, num_bits & 7); for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } while ((counter) && (num_bits)) { TINFL_GET_BITS(51, dist, 8); while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } *pOut_buf_cur++ = (mz_uint8)dist; counter--; } while (counter) { size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } while (pIn_buf_cur >= pIn_buf_end) { TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); } n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; } } else if (r->m_type == 3) { TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); } else { if (r->m_type == 1) { mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); for (i = 0; i <= 143; ++i) *p++ = 8; for (; i <= 255; ++i) *p++ = 9; for (; i <= 279; ++i) *p++ = 7; for (; i <= 287; ++i) *p++ = 8; } else { for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } r->m_table_sizes[2] = 19; } for (; (int)r->m_type >= 0; r->m_type--) { int tree_next, tree_cur; tinfl_huff_table *pTable; mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } if ((65536 != total) && (used_syms > 1)) { TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); } for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) { mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) { tree_cur -= ((rev_code >>= 1) & 1); if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; } tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; } if (r->m_type == 2) { for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) { mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } if ((dist == 16) && (!counter)) { TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); } num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; } if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) { TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); } TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); } } for (;;) { mz_uint8 *pSrc; for (;;) { if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) { TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); if (counter >= 256) break; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } *pOut_buf_cur++ = (mz_uint8)counter; } else { int sym2; mz_uint code_len; #if TINFL_USE_64BIT_BITBUF if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } #else if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } #endif if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } counter = sym2; bit_buf >>= code_len; num_bits -= code_len; if (counter & 256) break; #if !TINFL_USE_64BIT_BITBUF if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } #endif if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } bit_buf >>= code_len; num_bits -= code_len; pOut_buf_cur[0] = (mz_uint8)counter; if (sym2 & 256) { pOut_buf_cur++; counter = sym2; break; } pOut_buf_cur[1] = (mz_uint8)sym2; pOut_buf_cur += 2; } } if ((counter &= 511) == 256) break; num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) { TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); } pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) { while (counter--) { while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; } continue; } #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES else if ((counter >= 9) && (counter <= dist)) { const mz_uint8 *pSrc_end = pSrc + (counter & ~7); do { #ifdef MINIZ_UNALIGNED_USE_MEMCPY memcpy(pOut_buf_cur, pSrc, sizeof(mz_uint32)*2); #else ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; #endif pOut_buf_cur += 8; } while ((pSrc += 8) < pSrc_end); if ((counter &= 7) < 3) { if (counter) { pOut_buf_cur[0] = pSrc[0]; if (counter > 1) pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur += counter; } continue; } } #endif while(counter>2) { pOut_buf_cur[0] = pSrc[0]; pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur[2] = pSrc[2]; pOut_buf_cur += 3; pSrc += 3; counter -= 3; } if (counter > 0) { pOut_buf_cur[0] = pSrc[0]; if (counter > 1) pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur += counter; } } } } while (!(r->m_final & 1)); /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */ TINFL_SKIP_BITS(32, num_bits & 7); while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) { --pIn_buf_cur; num_bits -= 8; } bit_buf &= (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */ if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } } TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); TINFL_CR_FINISH common_exit: /* As long as we aren't telling the caller that we NEED more input to make forward progress: */ /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */ if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS)) { while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) { --pIn_buf_cur; num_bits -= 8; } } r->m_num_bits = num_bits; r->m_bit_buf = bit_buf & (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) { const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; while (buf_len) { for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; } for (; i < block_len; ++i) s1 += *ptr++, s2 += s1; s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; } r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; } return status; } /* Higher level helper functions. */ void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) { tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; *pOut_len = 0; tinfl_init(&decomp); for (;;) { size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) { MZ_FREE(pBuf); *pOut_len = 0; return NULL; } src_buf_ofs += src_buf_size; *pOut_len += dst_buf_size; if (status == TINFL_STATUS_DONE) break; new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); if (!pNew_buf) { MZ_FREE(pBuf); *pOut_len = 0; return NULL; } pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; } return pBuf; } size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) { tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; } int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { int result = 0; tinfl_decompressor decomp; mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; if (!pDict) return TINFL_STATUS_FAILED; tinfl_init(&decomp); for (;;) { size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); in_buf_ofs += in_buf_size; if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) break; if (status != TINFL_STATUS_HAS_MORE_OUTPUT) { result = (status == TINFL_STATUS_DONE); break; } dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); } MZ_FREE(pDict); *pIn_buf_size = in_buf_ofs; return result; } #ifndef MINIZ_NO_MALLOC tinfl_decompressor *tinfl_decompressor_alloc() { tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); if (pDecomp) tinfl_init(pDecomp); return pDecomp; } void tinfl_decompressor_free(tinfl_decompressor *pDecomp) { MZ_FREE(pDecomp); } #endif #ifdef __cplusplus } #endif /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC * Copyright 2016 Martin Raiber * All Rights Reserved. * * 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. * **************************************************************************/ #ifndef MINIZ_NO_ARCHIVE_APIS #ifdef __cplusplus extern "C" { #endif /* ------------------- .ZIP archive reading */ #ifdef MINIZ_NO_STDIO #define MZ_FILE void * #else #include #if defined(_MSC_VER) || defined(__MINGW64__) static FILE *mz_fopen(const char *pFilename, const char *pMode) { FILE *pFile = NULL; fopen_s(&pFile, pFilename, pMode); return pFile; } static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) { FILE *pFile = NULL; if (freopen_s(&pFile, pPath, pMode, pStream)) return NULL; return pFile; } #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN mz_fopen #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 _ftelli64 #define MZ_FSEEK64 _fseeki64 #define MZ_FILE_STAT_STRUCT _stat64 #define MZ_FILE_STAT _stat64 #define MZ_FFLUSH fflush #define MZ_FREOPEN mz_freopen #define MZ_DELETE_FILE remove #elif defined(__MINGW32__) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello64 #define MZ_FSEEK64 fseeko64 #define MZ_FILE_STAT_STRUCT _stat #define MZ_FILE_STAT _stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #elif defined(__TINYC__) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftell #define MZ_FSEEK64 fseek #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #elif defined(__GNUC__) && defined(_LARGEFILE64_SOURCE) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen64(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello64 #define MZ_FSEEK64 fseeko64 #define MZ_FILE_STAT_STRUCT stat64 #define MZ_FILE_STAT stat64 #define MZ_FFLUSH fflush #define MZ_FREOPEN(p, m, s) freopen64(p, m, s) #define MZ_DELETE_FILE remove #elif defined(__APPLE__) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello #define MZ_FSEEK64 fseeko #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(p, m, s) freopen(p, m, s) #define MZ_DELETE_FILE remove #else #pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.") #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #ifdef __STRICT_ANSI__ #define MZ_FTELL64 ftell #define MZ_FSEEK64 fseek #else #define MZ_FTELL64 ftello #define MZ_FSEEK64 fseeko #endif #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #endif /* #ifdef _MSC_VER */ #endif /* #ifdef MINIZ_NO_STDIO */ #define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) /* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */ enum { /* ZIP archive identifiers and record sizes */ MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, /* ZIP64 archive identifier and record sizes */ MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, /* Central directory header record offsets */ MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8, MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16, MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, /* Local directory header offsets */ MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10, MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3, /* End of central directory offsets */ MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, /* ZIP64 End of central directory locator offsets */ MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ /* ZIP64 End of central directory header offsets */ MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 }; typedef struct { void *m_p; size_t m_size, m_capacity; mz_uint m_element_size; } mz_zip_array; struct mz_zip_internal_state_tag { mz_zip_array m_central_dir; mz_zip_array m_central_dir_offsets; mz_zip_array m_sorted_central_dir_offsets; /* The flags passed in when the archive is initially opened. */ uint32_t m_init_flags; /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */ mz_bool m_zip64; /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */ mz_bool m_zip64_has_extended_info_fields; /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */ MZ_FILE *m_pFile; mz_uint64 m_file_archive_start_ofs; void *m_pMem; size_t m_mem_size; size_t m_mem_capacity; }; #define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size #if defined(DEBUG) || defined(_DEBUG) || defined(NDEBUG) static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) { MZ_ASSERT(index < pArray->m_size); return index; } #define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)] #else #define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] #endif static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size) { memset(pArray, 0, sizeof(mz_zip_array)); pArray->m_element_size = element_size; } static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) { pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); memset(pArray, 0, sizeof(mz_zip_array)); } static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) { void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE; if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE; pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) { if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; } return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) { if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; } pArray->m_size = new_size; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) { return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); } static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) { size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE; if (n > 0) memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); return MZ_TRUE; } #ifndef MINIZ_NO_TIME static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date) { struct tm tm; memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; return mktime(&tm); } #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) { #ifdef _MSC_VER struct tm tm_struct; struct tm *tm = &tm_struct; errno_t err = localtime_s(tm, &time); if (err) { *pDOS_date = 0; *pDOS_time = 0; return; } #else struct tm *tm = localtime(&time); #endif /* #ifdef _MSC_VER */ *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); } #endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ #ifndef MINIZ_NO_STDIO #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime) { struct MZ_FILE_STAT_STRUCT file_stat; /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ if (MZ_FILE_STAT(pFilename, &file_stat) != 0) return MZ_FALSE; *pTime = file_stat.st_mtime; return MZ_TRUE; } #endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time) { struct utimbuf t; memset(&t, 0, sizeof(t)); t.actime = access_time; t.modtime = modified_time; return !utime(pFilename, &t); } #endif /* #ifndef MINIZ_NO_STDIO */ #endif /* #ifndef MINIZ_NO_TIME */ static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num) { if (pZip) pZip->m_last_error = err_num; return MZ_FALSE; } static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags) { (void)flags; if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!pZip->m_pAlloc) pZip->m_pAlloc = miniz_def_alloc_func; if (!pZip->m_pFree) pZip->m_pFree = miniz_def_free_func; if (!pZip->m_pRealloc) pZip->m_pRealloc = miniz_def_realloc_func; pZip->m_archive_size = 0; pZip->m_central_directory_file_ofs = 0; pZip->m_total_files = 0; pZip->m_last_error = MZ_ZIP_NO_ERROR; if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); pZip->m_pState->m_init_flags = flags; pZip->m_pState->m_zip64 = MZ_FALSE; pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE; pZip->m_zip_mode = MZ_ZIP_MODE_READING; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) { const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); mz_uint8 l = 0, r = 0; pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pE = pL + MZ_MIN(l_len, r_len); while (pL < pE) { if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) break; pL++; pR++; } return (pL == pE) ? (l_len < r_len) : (l < r); } #define MZ_SWAP_UINT32(a, b) \ do \ { \ mz_uint32 t = a; \ a = b; \ b = t; \ } \ MZ_MACRO_END /* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */ static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) { mz_zip_internal_state *pState = pZip->m_pState; const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; const mz_zip_array *pCentral_dir = &pState->m_central_dir; mz_uint32 *pIndices; mz_uint32 start, end; const mz_uint32 size = pZip->m_total_files; if (size <= 1U) return; pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); start = (size - 2U) >> 1U; for (;;) { mz_uint64 child, root = start; for (;;) { if ((child = (root << 1U) + 1U) >= size) break; child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]))); if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) break; MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; } if (!start) break; start--; } end = size - 1; while (end > 0) { mz_uint64 child, root = 0; MZ_SWAP_UINT32(pIndices[end], pIndices[0]); for (;;) { if ((child = (root << 1U) + 1U) >= end) break; child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])); if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) break; MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; } end--; } } static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs) { mz_int64 cur_file_ofs; mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; /* Basic sanity checks - reject files which are too small */ if (pZip->m_archive_size < record_size) return MZ_FALSE; /* Find the record by scanning the file from the end towards the beginning. */ cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); for (;;) { int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) return MZ_FALSE; for (i = n - 4; i >= 0; --i) { mz_uint s = MZ_READ_LE32(pBuf + i); if (s == record_sig) { if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) break; } } if (i >= 0) { cur_file_ofs += i; break; } /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */ if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size))) return MZ_FALSE; cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); } *pOfs = cur_file_ofs; return MZ_TRUE; } static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags) { mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0; mz_uint64 cdir_ofs = 0; mz_int64 cur_file_ofs = 0; const mz_uint8 *p; mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32; mz_uint64 zip64_end_of_central_dir_ofs = 0; /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */ if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); /* Read and verify the end of central directory record. */ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) { if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) { if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) { zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) { if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) { pZip->m_pState->m_zip64 = MZ_TRUE; } } } } } pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); if (pZip->m_pState->m_zip64) { mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (zip64_total_num_of_disks != 1U) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); /* Check for miniz's practical limits */ if (zip64_cdir_total_entries > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk; /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */ if (zip64_size_of_central_directory > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); cdir_size = (mz_uint32)zip64_size_of_central_directory; num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); } if (pZip->m_total_files != cdir_entries_on_this_disk) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pZip->m_central_directory_file_ofs = cdir_ofs; if (pZip->m_total_files) { mz_uint i, n; /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */ if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (sort_central_dir) { if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); /* Now create an index into the central directory file records, do some basic sanity checking on each record */ p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) { mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size; mz_uint64 comp_size, decomp_size, local_header_ofs; if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); if (sort_central_dir) MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && (ext_data_size) && (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX)) { /* Attempt to find zip64 extended information field in the entry's extra data */ mz_uint32 extra_size_remaining = ext_data_size; if (extra_size_remaining) { const mz_uint8 *pExtra_data; void* buf = NULL; if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > n) { buf = MZ_MALLOC(ext_data_size); if(buf==NULL) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size, buf, ext_data_size) != ext_data_size) { MZ_FREE(buf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } pExtra_data = (mz_uint8*)buf; } else { pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; } do { mz_uint32 field_id; mz_uint32 field_data_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { MZ_FREE(buf); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) { MZ_FREE(buf); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */ pZip->m_pState->m_zip64 = MZ_TRUE; pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; break; } pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; } while (extra_size_remaining); MZ_FREE(buf); } } /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */ if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) { if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1))) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); if (comp_size != MZ_UINT32_MAX) { if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); n -= total_header_size; p += total_header_size; } } if (sort_central_dir) mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); return MZ_TRUE; } void mz_zip_zero_struct(mz_zip_archive *pZip) { if (pZip) MZ_CLEAR_OBJ(*pZip); } static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) { mz_bool status = MZ_TRUE; if (!pZip) return MZ_FALSE; if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) { if (set_last_error) pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER; return MZ_FALSE; } if (pZip->m_pState) { mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; mz_zip_array_clear(pZip, &pState->m_central_dir); mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); #ifndef MINIZ_NO_STDIO if (pState->m_pFile) { if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) { if (MZ_FCLOSE(pState->m_pFile) == EOF) { if (set_last_error) pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED; status = MZ_FALSE; } } pState->m_pFile = NULL; } #endif /* #ifndef MINIZ_NO_STDIO */ pZip->m_pFree(pZip->m_pAlloc_opaque, pState); } pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; return status; } mz_bool mz_zip_reader_end(mz_zip_archive *pZip) { return mz_zip_reader_end_internal(pZip, MZ_TRUE); } mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags) { if ((!pZip) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_zip_type = MZ_ZIP_TYPE_USER; pZip->m_archive_size = size; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end_internal(pZip, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); return s; } mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags) { if (!pMem) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY; pZip->m_archive_size = size; pZip->m_pRead = mz_zip_mem_read_func; pZip->m_pIO_opaque = pZip; pZip->m_pNeeds_keepalive = NULL; #ifdef __cplusplus pZip->m_pState->m_pMem = const_cast(pMem); #else pZip->m_pState->m_pMem = (void *)pMem; #endif pZip->m_pState->m_mem_size = size; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end_internal(pZip, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); file_ofs += pZip->m_pState->m_file_archive_start_ofs; if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) return 0; return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); } mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) { return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0); } mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size) { mz_uint64 file_size; MZ_FILE *pFile; if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pFile = MZ_FOPEN(pFilename, "rb"); if (!pFile) return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); file_size = archive_size; if (!file_size) { if (MZ_FSEEK64(pFile, 0, SEEK_END)) { MZ_FCLOSE(pFile); return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); } file_size = MZ_FTELL64(pFile); } /* TODO: Better sanity check archive_size and the # of actual remaining bytes */ if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) { MZ_FCLOSE(pFile); return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); } if (!mz_zip_reader_init_internal(pZip, flags)) { MZ_FCLOSE(pFile); return MZ_FALSE; } pZip->m_zip_type = MZ_ZIP_TYPE_FILE; pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; pZip->m_pState->m_pFile = pFile; pZip->m_archive_size = file_size; pZip->m_pState->m_file_archive_start_ofs = file_start_ofs; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end_internal(pZip, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags) { mz_uint64 cur_file_ofs; if ((!pZip) || (!pFile)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); cur_file_ofs = MZ_FTELL64(pFile); if (!archive_size) { if (MZ_FSEEK64(pFile, 0, SEEK_END)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); archive_size = MZ_FTELL64(pFile) - cur_file_ofs; if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); } if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; pZip->m_pState->m_pFile = pFile; pZip->m_archive_size = archive_size; pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end_internal(pZip, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } #endif /* #ifndef MINIZ_NO_STDIO */ static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index) { if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files)) return NULL; return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); } mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) { mz_uint m_bit_flag; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); if (!p) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return MZ_FALSE; } m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0; } mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index) { mz_uint bit_flag; mz_uint method; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); if (!p) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return MZ_FALSE; } method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); if ((method != 0) && (method != MZ_DEFLATED)) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); return MZ_FALSE; } if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); return MZ_FALSE; } if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); return MZ_FALSE; } return MZ_TRUE; } mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) { mz_uint filename_len, attribute_mapping_id, external_attr; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); if (!p) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return MZ_FALSE; } filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); if (filename_len) { if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') return MZ_TRUE; } /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */ /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */ /* FIXME: Remove this check? Is it necessary - we already check the filename. */ attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8; (void)attribute_mapping_id; external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0) { return MZ_TRUE; } return MZ_FALSE; } static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data) { mz_uint n; const mz_uint8 *p = pCentral_dir_header; if (pFound_zip64_extra_data) *pFound_zip64_extra_data = MZ_FALSE; if ((!p) || (!pStat)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* Extract fields from the central directory record. */ pStat->m_file_index = file_index; pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); #ifndef MINIZ_NO_TIME pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); #endif pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); /* Copy as much of the filename and comment as possible. */ n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); pStat->m_comment_size = n; memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; /* Set some flags for convienance */ pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index); pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index); pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index); /* See if we need to read any zip64 extended information fields. */ /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */ if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX) { /* Attempt to find zip64 extended information field in the entry's extra data */ mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); if (extra_size_remaining) { const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); do { mz_uint32 field_id; mz_uint32 field_data_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2; mz_uint32 field_data_remaining = field_data_size; if (pFound_zip64_extra_data) *pFound_zip64_extra_data = MZ_TRUE; if (pStat->m_uncomp_size == MZ_UINT32_MAX) { if (field_data_remaining < sizeof(mz_uint64)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pStat->m_uncomp_size = MZ_READ_LE64(pField_data); pField_data += sizeof(mz_uint64); field_data_remaining -= sizeof(mz_uint64); } if (pStat->m_comp_size == MZ_UINT32_MAX) { if (field_data_remaining < sizeof(mz_uint64)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pStat->m_comp_size = MZ_READ_LE64(pField_data); pField_data += sizeof(mz_uint64); field_data_remaining -= sizeof(mz_uint64); } if (pStat->m_local_header_ofs == MZ_UINT32_MAX) { if (field_data_remaining < sizeof(mz_uint64)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pStat->m_local_header_ofs = MZ_READ_LE64(pField_data); pField_data += sizeof(mz_uint64); field_data_remaining -= sizeof(mz_uint64); } break; } pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; } while (extra_size_remaining); } } return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) { mz_uint i; if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) return 0 == memcmp(pA, pB, len); for (i = 0; i < len; ++i) if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) return MZ_FALSE; return MZ_TRUE; } static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) { const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); mz_uint8 l = 0, r = 0; pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pE = pL + MZ_MIN(l_len, r_len); while (pL < pE) { if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) break; pL++; pR++; } return (pL == pE) ? (int)(l_len - r_len) : (l - r); } static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex) { mz_zip_internal_state *pState = pZip->m_pState; const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; const mz_zip_array *pCentral_dir = &pState->m_central_dir; mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); const uint32_t size = pZip->m_total_files; const mz_uint filename_len = (mz_uint)strlen(pFilename); if (pIndex) *pIndex = 0; if (size) { /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */ /* honestly the major expense here on 32-bit CPU's will still be the filename compare */ mz_int64 l = 0, h = (mz_int64)size - 1; while (l <= h) { mz_int64 m = l + ((h - l) >> 1); uint32_t file_index = pIndices[(uint32_t)m]; int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); if (!comp) { if (pIndex) *pIndex = file_index; return MZ_TRUE; } else if (comp < 0) l = m + 1; else h = m - 1; } } return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); } int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) { mz_uint32 index; if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index)) return -1; else return (int)index; } mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex) { mz_uint file_index; size_t name_len, comment_len; if (pIndex) *pIndex = 0; if ((!pZip) || (!pZip->m_pState) || (!pName)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* See if we can use a binary search */ if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) && (pZip->m_zip_mode == MZ_ZIP_MODE_READING) && ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) { return mz_zip_locate_file_binary_search(pZip, pName, pIndex); } /* Locate the entry by scanning the entire central directory */ name_len = strlen(pName); if (name_len > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); comment_len = pComment ? strlen(pComment) : 0; if (comment_len > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); for (file_index = 0; file_index < pZip->m_total_files; file_index++) { const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; if (filename_len < name_len) continue; if (comment_len) { mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); const char *pFile_comment = pFilename + filename_len + file_extra_len; if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags))) continue; } if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) { int ofs = filename_len - 1; do { if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) break; } while (--ofs >= 0); ofs++; pFilename += ofs; filename_len -= ofs; } if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags))) { if (pIndex) *pIndex = file_index; return MZ_TRUE; } } return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); } mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { int status = TINFL_STATUS_DONE; mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; mz_zip_archive_file_stat file_stat; void *pRead_buf; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; tinfl_decompressor inflator; if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; /* A directory or zero length file */ if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) return MZ_TRUE; /* Encryption and patch files are not supported. */ if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); /* This function only supports decompressing stored and deflate. */ if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); /* Ensure supplied output buffer is large enough. */ needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; if (buf_size < needed_size) return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL); /* Read and parse the local directory entry. */ cur_file_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { /* The file is stored or the caller has requested the compressed data. */ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0) { if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); } #endif return MZ_TRUE; } /* Decompress the file either directly from memory or from a file input buffer. */ tinfl_init(&inflator); if (pZip->m_pState->m_pMem) { /* Read directly from the archive in memory. */ pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; read_buf_size = read_buf_avail = file_stat.m_comp_size; comp_remaining = 0; } else if (pUser_read_buf) { /* Use a user provided read buffer. */ if (!user_read_buf_size) return MZ_FALSE; pRead_buf = (mz_uint8 *)pUser_read_buf; read_buf_size = user_read_buf_size; read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } else { /* Temporarily allocate a read buffer. */ read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } do { /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */ size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { status = TINFL_STATUS_FAILED; mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); break; } cur_file_ofs += read_buf_avail; comp_remaining -= read_buf_avail; read_buf_ofs = 0; } in_buf_size = (size_t)read_buf_avail; status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); read_buf_avail -= in_buf_size; read_buf_ofs += in_buf_size; out_buf_ofs += out_buf_size; } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); if (status == TINFL_STATUS_DONE) { /* Make sure the entire file was decompressed, and check its CRC. */ if (out_buf_ofs != file_stat.m_uncomp_size) { mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); status = TINFL_STATUS_FAILED; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) { mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); status = TINFL_STATUS_FAILED; } #endif } if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return status == TINFL_STATUS_DONE; } mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) return MZ_FALSE; return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); } mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) { return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); } mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) { return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); } void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) { mz_uint64 comp_size, uncomp_size, alloc_size; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); void *pBuf; if (pSize) *pSize = 0; if (!p) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return NULL; } comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) { mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); return NULL; } if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); return NULL; } if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return NULL; } if (pSize) *pSize = (size_t)alloc_size; return pBuf; } void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) { if (pSize) *pSize = 0; return MZ_FALSE; } return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); } mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) { int status = TINFL_STATUS_DONE; mz_uint file_crc32 = MZ_CRC32_INIT; mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; mz_zip_archive_file_stat file_stat; void *pRead_buf = NULL; void *pWrite_buf = NULL; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; /* A directory or zero length file */ if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) return MZ_TRUE; /* Encryption and patch files are not supported. */ if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); /* This function only supports decompressing stored and deflate. */ if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */ cur_file_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); /* Decompress the file either directly from memory or from a file input buffer. */ if (pZip->m_pState->m_pMem) { pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; read_buf_size = read_buf_avail = file_stat.m_comp_size; comp_remaining = 0; } else { read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { /* The file is stored or the caller has requested the compressed data. */ if (pZip->m_pState->m_pMem) { if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) { mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); status = TINFL_STATUS_FAILED; } else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); #endif } cur_file_ofs += file_stat.m_comp_size; out_buf_ofs += file_stat.m_comp_size; comp_remaining = 0; } else { while (comp_remaining) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); status = TINFL_STATUS_FAILED; break; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); } #endif if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); status = TINFL_STATUS_FAILED; break; } cur_file_ofs += read_buf_avail; out_buf_ofs += read_buf_avail; comp_remaining -= read_buf_avail; } } } else { tinfl_decompressor inflator; tinfl_init(&inflator); if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); status = TINFL_STATUS_FAILED; } else { do { mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); status = TINFL_STATUS_FAILED; break; } cur_file_ofs += read_buf_avail; comp_remaining -= read_buf_avail; read_buf_ofs = 0; } in_buf_size = (size_t)read_buf_avail; status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); read_buf_avail -= in_buf_size; read_buf_ofs += in_buf_size; if (out_buf_size) { if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) { mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); status = TINFL_STATUS_FAILED; break; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); #endif if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) { mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); status = TINFL_STATUS_FAILED; break; } } } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); } } if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) { /* Make sure the entire file was decompressed, and check its CRC. */ if (out_buf_ofs != file_stat.m_uncomp_size) { mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); status = TINFL_STATUS_FAILED; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS else if (file_crc32 != file_stat.m_crc32) { mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); status = TINFL_STATUS_FAILED; } #endif } if (!pZip->m_pState->m_pMem) pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); if (pWrite_buf) pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); return status == TINFL_STATUS_DONE; } mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) return MZ_FALSE; return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); } mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) { mz_zip_reader_extract_iter_state *pState; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; /* Argument sanity check */ if ((!pZip) || (!pZip->m_pState)) return NULL; /* Allocate an iterator status structure */ pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state)); if (!pState) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); return NULL; } /* Fetch file details */ if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } /* Encryption and patch files are not supported. */ if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } /* This function only supports decompressing stored and deflate. */ if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED)) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } /* Init state - save args */ pState->pZip = pZip; pState->flags = flags; /* Init state - reset variables to defaults */ pState->status = TINFL_STATUS_DONE; #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS pState->file_crc32 = MZ_CRC32_INIT; #endif pState->read_buf_ofs = 0; pState->out_buf_ofs = 0; pState->pRead_buf = NULL; pState->pWrite_buf = NULL; pState->out_blk_remain = 0; /* Read and parse the local directory entry. */ pState->cur_file_ofs = pState->file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } pState->cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } /* Decompress the file either directly from memory or from a file input buffer. */ if (pZip->m_pState->m_pMem) { pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs; pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size; pState->comp_remaining = pState->file_stat.m_comp_size; } else { if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) { /* Decompression required, therefore intermediate read buffer required */ pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } } else { /* Decompression not required - we will be reading directly into user buffer, no temp buf required */ pState->read_buf_size = 0; } pState->read_buf_avail = 0; pState->comp_remaining = pState->file_stat.m_comp_size; } if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) { /* Decompression required, init decompressor */ tinfl_init( &pState->inflator ); /* Allocate write buffer */ if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (pState->pRead_buf) pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } } return pState; } mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) { mz_uint32 file_index; /* Locate file index by name */ if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) return NULL; /* Construct iterator */ return mz_zip_reader_extract_iter_new(pZip, file_index, flags); } size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size) { size_t copied_to_caller = 0; /* Argument sanity check */ if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf)) return 0; if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)) { /* The file is stored or the caller has requested the compressed data, calc amount to return. */ copied_to_caller = (size_t)MZ_MIN( buf_size, pState->comp_remaining ); /* Zip is in memory....or requires reading from a file? */ if (pState->pZip->m_pState->m_pMem) { /* Copy data to caller's buffer */ memcpy( pvBuf, pState->pRead_buf, copied_to_caller ); pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller; } else { /* Read directly into caller's buffer */ if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller) { /* Failed to read all that was asked for, flag failure and alert user */ mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); pState->status = TINFL_STATUS_FAILED; copied_to_caller = 0; } } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS /* Compute CRC if not returning compressed data only */ if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller); #endif /* Advance offsets, dec counters */ pState->cur_file_ofs += copied_to_caller; pState->out_buf_ofs += copied_to_caller; pState->comp_remaining -= copied_to_caller; } else { do { /* Calc ptr to write buffer - given current output pos and block size */ mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); /* Calc max output size - given current output pos and block size */ size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); if (!pState->out_blk_remain) { /* Read more data from file if none available (and reading from file) */ if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem)) { /* Calc read size */ pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining); if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail) { mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); pState->status = TINFL_STATUS_FAILED; break; } /* Advance offsets, dec counters */ pState->cur_file_ofs += pState->read_buf_avail; pState->comp_remaining -= pState->read_buf_avail; pState->read_buf_ofs = 0; } /* Perform decompression */ in_buf_size = (size_t)pState->read_buf_avail; pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); pState->read_buf_avail -= in_buf_size; pState->read_buf_ofs += in_buf_size; /* Update current output block size remaining */ pState->out_blk_remain = out_buf_size; } if (pState->out_blk_remain) { /* Calc amount to return. */ size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain ); /* Copy data to caller's buffer */ memcpy( (uint8_t*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy ); #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS /* Perform CRC */ pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy); #endif /* Decrement data consumed from block */ pState->out_blk_remain -= to_copy; /* Inc output offset, while performing sanity check */ if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size) { mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); pState->status = TINFL_STATUS_FAILED; break; } /* Increment counter of data copied to caller */ copied_to_caller += to_copy; } } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) ); } /* Return how many bytes were copied into user buffer */ return copied_to_caller; } mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState) { int status; /* Argument sanity check */ if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState)) return MZ_FALSE; /* Was decompression completed and requested? */ if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) { /* Make sure the entire file was decompressed, and check its CRC. */ if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size) { mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); pState->status = TINFL_STATUS_FAILED; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS else if (pState->file_crc32 != pState->file_stat.m_crc32) { mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); pState->status = TINFL_STATUS_FAILED; } #endif } /* Free buffers */ if (!pState->pZip->m_pState->m_pMem) pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf); if (pState->pWrite_buf) pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf); /* Save status */ status = pState->status; /* Free context */ pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState); return status == TINFL_STATUS_DONE; } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) { (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); } mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) { mz_bool status; mz_zip_archive_file_stat file_stat; MZ_FILE *pFile; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); pFile = MZ_FOPEN(pDst_filename, "wb"); if (!pFile) return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); if (MZ_FCLOSE(pFile) == EOF) { if (status) mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); status = MZ_FALSE; } #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) if (status) mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); #endif return status; } mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) return MZ_FALSE; return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); } mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags) { mz_zip_archive_file_stat file_stat; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); } mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) return MZ_FALSE; return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags); } #endif /* #ifndef MINIZ_NO_STDIO */ static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_uint32 *p = (mz_uint32 *)pOpaque; (void)file_ofs; *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n); return n; } mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) { mz_zip_archive_file_stat file_stat; mz_zip_internal_state *pState; const mz_uint8 *pCentral_dir_header; mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE; mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; mz_uint64 local_header_ofs = 0; mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32; mz_uint64 local_header_comp_size, local_header_uncomp_size; mz_uint32 uncomp_crc32 = MZ_CRC32_INIT; mz_bool has_data_descriptor; mz_uint32 local_header_bit_flags; mz_zip_array file_data_array; mz_zip_array_init(&file_data_array, 1); if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (file_index > pZip->m_total_files) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; pCentral_dir_header = mz_zip_get_cdh(pZip, file_index); if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir)) return MZ_FALSE; /* A directory or zero length file */ if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size)) return MZ_TRUE; /* Encryption and patch files are not supported. */ if (file_stat.m_is_encrypted) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); /* This function only supports stored and deflate. */ if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); if (!file_stat.m_is_supported) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); /* Read and parse the local directory entry. */ local_header_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS); local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); has_data_descriptor = (local_header_bit_flags & 8) != 0; if (local_header_filename_len != strlen(file_stat.m_filename)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (local_header_filename_len) { if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); goto handle_failure; } /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */ if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0) { mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); goto handle_failure; } } if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) { mz_uint32 extra_size_remaining = local_header_extra_len; const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p; if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); goto handle_failure; } do { mz_uint32 field_id, field_data_size, field_total_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); field_total_size = field_data_size + sizeof(mz_uint16) * 2; if (field_total_size > extra_size_remaining) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); if (field_data_size < sizeof(mz_uint64) * 2) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); goto handle_failure; } local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); found_zip64_ext_data_in_ldir = MZ_TRUE; break; } pExtra_data += field_total_size; extra_size_remaining -= field_total_size; } while (extra_size_remaining); } /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */ /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */ if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32)) { mz_uint8 descriptor_buf[32]; mz_bool has_id; const mz_uint8 *pSrc; mz_uint32 file_crc32; mz_uint64 comp_size = 0, uncomp_size = 0; mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4; if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s)) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); goto handle_failure; } pSrc = descriptor_buf; // avoid g++ warning/error has_id = (MZ_READ_LE32(pSrc) == MZ_ZIP_DATA_DESCRIPTOR_ID); pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf; file_crc32 = MZ_READ_LE32(pSrc); if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) { comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32)); uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64)); } else { comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32)); uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32)); } if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size)) { mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); goto handle_failure; } } else { if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size)) { mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); goto handle_failure; } } mz_zip_array_clear(pZip, &file_data_array); if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0) { if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0)) return MZ_FALSE; /* 1 more check to be sure, although the extract checks too. */ if (uncomp_crc32 != file_stat.m_crc32) { mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); return MZ_FALSE; } } return MZ_TRUE; handle_failure: mz_zip_array_clear(pZip, &file_data_array); return MZ_FALSE; } mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags) { mz_zip_internal_state *pState; uint32_t i; if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; /* Basic sanity checks */ if (!pState->m_zip64) { if (pZip->m_total_files > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (pZip->m_archive_size > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); } else { if (pZip->m_total_files >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); } for (i = 0; i < pZip->m_total_files; i++) { if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags) { mz_uint32 found_index; mz_zip_archive_file_stat stat; if (!mz_zip_reader_file_stat(pZip, i, &stat)) return MZ_FALSE; if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index)) return MZ_FALSE; /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */ if (found_index != i) return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); } if (!mz_zip_validate_file(pZip, i, flags)) return MZ_FALSE; } return MZ_TRUE; } mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr) { mz_bool success = MZ_TRUE; mz_zip_archive zip; mz_zip_error actual_err = MZ_ZIP_NO_ERROR; if ((!pMem) || (!size)) { if (pErr) *pErr = MZ_ZIP_INVALID_PARAMETER; return MZ_FALSE; } mz_zip_zero_struct(&zip); if (!mz_zip_reader_init_mem(&zip, pMem, size, flags)) { if (pErr) *pErr = zip.m_last_error; return MZ_FALSE; } if (!mz_zip_validate_archive(&zip, flags)) { actual_err = zip.m_last_error; success = MZ_FALSE; } if (!mz_zip_reader_end_internal(&zip, success)) { if (!actual_err) actual_err = zip.m_last_error; success = MZ_FALSE; } if (pErr) *pErr = actual_err; return success; } #ifndef MINIZ_NO_STDIO mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr) { mz_bool success = MZ_TRUE; mz_zip_archive zip; mz_zip_error actual_err = MZ_ZIP_NO_ERROR; if (!pFilename) { if (pErr) *pErr = MZ_ZIP_INVALID_PARAMETER; return MZ_FALSE; } mz_zip_zero_struct(&zip); if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0)) { if (pErr) *pErr = zip.m_last_error; return MZ_FALSE; } if (!mz_zip_validate_archive(&zip, flags)) { actual_err = zip.m_last_error; success = MZ_FALSE; } if (!mz_zip_reader_end_internal(&zip, success)) { if (!actual_err) actual_err = zip.m_last_error; success = MZ_FALSE; } if (pErr) *pErr = actual_err; return success; } #endif /* #ifndef MINIZ_NO_STDIO */ /* ------------------- .ZIP archive writing */ #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); } static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); } static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v) { mz_write_le32(p, (mz_uint32)v); mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32)); } #define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) #define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) #define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v)) static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_zip_internal_state *pState = pZip->m_pState; mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); if (!n) return 0; /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */ if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)) { mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); return 0; } if (new_size > pState->m_mem_capacity) { void *pNew_block; size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2; if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); return 0; } pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity; } memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); pState->m_mem_size = (size_t)new_size; return n; } static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) { mz_zip_internal_state *pState; mz_bool status = MZ_TRUE; if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) { if (set_last_error) mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return MZ_FALSE; } pState = pZip->m_pState; pZip->m_pState = NULL; mz_zip_array_clear(pZip, &pState->m_central_dir); mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); #ifndef MINIZ_NO_STDIO if (pState->m_pFile) { if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) { if (MZ_FCLOSE(pState->m_pFile) == EOF) { if (set_last_error) mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); status = MZ_FALSE; } } pState->m_pFile = NULL; } #endif /* #ifndef MINIZ_NO_STDIO */ if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); pState->m_pMem = NULL; } pZip->m_pFree(pZip->m_pAlloc_opaque, pState); pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; return status; } mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags) { mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0; if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) { if (!pZip->m_pRead) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); } if (pZip->m_file_offset_alignment) { /* Ensure user specified file offset alignment is a power of 2. */ if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); } if (!pZip->m_pAlloc) pZip->m_pAlloc = miniz_def_alloc_func; if (!pZip->m_pFree) pZip->m_pFree = miniz_def_free_func; if (!pZip->m_pRealloc) pZip->m_pRealloc = miniz_def_realloc_func; pZip->m_archive_size = existing_size; pZip->m_central_directory_file_ofs = 0; pZip->m_total_files = 0; if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); pZip->m_pState->m_zip64 = zip64; pZip->m_pState->m_zip64_has_extended_info_fields = zip64; pZip->m_zip_type = MZ_ZIP_TYPE_USER; pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; return MZ_TRUE; } mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) { return mz_zip_writer_init_v2(pZip, existing_size, 0); } mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags) { pZip->m_pWrite = mz_zip_heap_write_func; pZip->m_pNeeds_keepalive = NULL; if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) pZip->m_pRead = mz_zip_mem_read_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) return MZ_FALSE; pZip->m_zip_type = MZ_ZIP_TYPE_HEAP; if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) { if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) { mz_zip_writer_end_internal(pZip, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } pZip->m_pState->m_mem_capacity = initial_allocation_size; } return MZ_TRUE; } mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) { return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0); } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); file_ofs += pZip->m_pState->m_file_archive_start_ofs; if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) { mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); return 0; } return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); } mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) { return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0); } mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags) { MZ_FILE *pFile; pZip->m_pWrite = mz_zip_file_write_func; pZip->m_pNeeds_keepalive = NULL; if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) return MZ_FALSE; if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb"))) { mz_zip_writer_end(pZip); return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); } pZip->m_pState->m_pFile = pFile; pZip->m_zip_type = MZ_ZIP_TYPE_FILE; if (size_to_reserve_at_beginning) { mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_OBJ(buf); do { size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) { mz_zip_writer_end(pZip); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_ofs += n; size_to_reserve_at_beginning -= n; } while (size_to_reserve_at_beginning); } return MZ_TRUE; } mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags) { pZip->m_pWrite = mz_zip_file_write_func; pZip->m_pNeeds_keepalive = NULL; if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init_v2(pZip, 0, flags)) return MZ_FALSE; pZip->m_pState->m_pFile = pFile; pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; return MZ_TRUE; } #endif /* #ifndef MINIZ_NO_STDIO */ mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) { mz_zip_internal_state *pState; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (flags & MZ_ZIP_FLAG_WRITE_ZIP64) { /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */ if (!pZip->m_pState->m_zip64) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); } /* No sense in trying to write to an archive that's already at the support max size */ if (pZip->m_pState->m_zip64) { if (pZip->m_total_files == MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { if (pZip->m_total_files == MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); } pState = pZip->m_pState; if (pState->m_pFile) { #ifdef MINIZ_NO_STDIO (void)pFilename; return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); #else if (pZip->m_pIO_opaque != pZip) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) { if (!pFilename) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */ if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) { /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */ mz_zip_reader_end_internal(pZip, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); } } pZip->m_pWrite = mz_zip_file_write_func; pZip->m_pNeeds_keepalive = NULL; #endif /* #ifdef MINIZ_NO_STDIO */ } else if (pState->m_pMem) { /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */ if (pZip->m_pIO_opaque != pZip) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState->m_mem_capacity = pState->m_mem_size; pZip->m_pWrite = mz_zip_heap_write_func; pZip->m_pNeeds_keepalive = NULL; } /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */ else if (!pZip->m_pWrite) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* Start writing new files at the archive's current central directory location. */ /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */ pZip->m_archive_size = pZip->m_central_directory_file_ofs; pZip->m_central_directory_file_ofs = 0; /* Clear the sorted central dir offsets, they aren't useful or maintained now. */ /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */ /* TODO: We could easily maintain the sorted central directory offsets. */ mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets); pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; return MZ_TRUE; } mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) { return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0); } /* TODO: pArchive_name is a terrible name here! */ mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) { return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); } typedef struct { mz_zip_archive *m_pZip; mz_uint64 m_cur_archive_file_ofs; mz_uint64 m_comp_size; } mz_zip_writer_add_state; static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser) { mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) return MZ_FALSE; pState->m_cur_archive_file_ofs += len; pState->m_comp_size += len; return MZ_TRUE; } #define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2) #define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3) static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs) { mz_uint8 *pDst = pBuf; mz_uint32 field_size = 0; MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); MZ_WRITE_LE16(pDst + 2, 0); pDst += sizeof(mz_uint16) * 2; if (pUncomp_size) { MZ_WRITE_LE64(pDst, *pUncomp_size); pDst += sizeof(mz_uint64); field_size += sizeof(mz_uint64); } if (pComp_size) { MZ_WRITE_LE64(pDst, *pComp_size); pDst += sizeof(mz_uint64); field_size += sizeof(mz_uint64); } if (pLocal_header_ofs) { MZ_WRITE_LE64(pDst, *pLocal_header_ofs); pDst += sizeof(mz_uint64); field_size += sizeof(mz_uint64); } MZ_WRITE_LE16(pBuf + 2, field_size); return (mz_uint32)(pDst - pBuf); } static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) { (void)pZip; memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); return MZ_TRUE; } static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { (void)pZip; memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX)); return MZ_TRUE; } static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes, const char *user_extra_data, mz_uint user_extra_data_len) { mz_zip_internal_state *pState = pZip->m_pState; mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; size_t orig_central_dir_size = pState->m_central_dir.m_size; mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; if (!pZip->m_pState->m_zip64) { if (local_header_ofs > 0xFFFFFFFF) return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); } /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, (mz_uint16)(extra_size + user_extra_data_len), comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) { /* Try to resize the central directory array back into its original state. */ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } return MZ_TRUE; } static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) { /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */ if (*pArchive_name == '/') return MZ_FALSE; /* Making sure the name does not contain drive letters or DOS style backward slashes is the responsibility of the program using miniz*/ return MZ_TRUE; } static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) { mz_uint32 n; if (!pZip->m_file_offset_alignment) return 0; n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1)); } static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) { char buf[4096]; memset(buf, 0, MZ_MIN(sizeof(buf), n)); while (n) { mz_uint32 s = MZ_MIN(sizeof(buf), n); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_file_ofs += s; n -= s; } return MZ_TRUE; } mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) { return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0); } mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) { mz_uint16 method = 0, dos_time = 0, dos_date = 0; mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; size_t archive_name_size; mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; tdefl_compressor *pComp = NULL; mz_bool store_data_uncompressed; mz_zip_internal_state *pState; mz_uint8 *pExtra_data = NULL; mz_uint32 extra_size = 0; mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; mz_uint16 bit_flags = 0; if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; level = level_and_flags & 0xF; store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; if (pState->m_zip64) { if (pZip->m_total_files == MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { if (pZip->m_total_files == MZ_UINT16_MAX) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ } if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ } } if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_writer_validate_archive_name(pArchive_name)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); #ifndef MINIZ_NO_TIME if (last_modified != NULL) { mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date); } else { MZ_TIME_T cur_time; time(&cur_time); mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); } #endif /* #ifndef MINIZ_NO_TIME */ if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); uncomp_size = buf_size; if (uncomp_size <= 3) { level = 0; store_data_uncompressed = MZ_TRUE; } } archive_name_size = strlen(pArchive_name); if (archive_name_size > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); if (!pState->m_zip64) { /* Bail early if the archive would obviously become too large */ if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len + MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ } } if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) { /* Set DOS Subdirectory attribute bit. */ ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG; /* Subdirectories cannot contain data. */ if ((buf_size) || (uncomp_size)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); } /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */ if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if ((!store_data_uncompressed) && (buf_size)) { if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return MZ_FALSE; } local_dir_header_ofs += num_alignment_padding_bytes; if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } cur_archive_file_ofs += num_alignment_padding_bytes; MZ_CLEAR_OBJ(local_dir_header); if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { method = MZ_DEFLATED; } if (pState->m_zip64) { if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) { pExtra_data = extra_data; extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, bit_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += archive_name_size; if (pExtra_data != NULL) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += extra_size; } } else { if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += archive_name_size; } if (user_extra_data_len > 0) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += user_extra_data_len; } if (store_data_uncompressed) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += buf_size; comp_size = buf_size; } else if (buf_size) { mz_zip_writer_add_state state; state.m_pZip = pZip; state.m_cur_archive_file_ofs = cur_archive_file_ofs; state.m_comp_size = 0; if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); } comp_size = state.m_comp_size; cur_archive_file_ofs = state.m_cur_archive_file_ofs; } pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); pComp = NULL; if (uncomp_size) { mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR); MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); if (pExtra_data == NULL) { if (comp_size > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); MZ_WRITE_LE32(local_dir_footer + 8, comp_size); MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); } else { MZ_WRITE_LE64(local_dir_footer + 8, comp_size); MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) return MZ_FALSE; cur_archive_file_ofs += local_dir_footer_size; } if (pExtra_data != NULL) { extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, user_extra_data_central, user_extra_data_central_len)) return MZ_FALSE; pZip->m_total_files++; pZip->m_archive_size = cur_archive_file_ofs; return MZ_TRUE; } mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) { mz_uint16 gen_flags = MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = size_to_add, comp_size = 0; size_t archive_name_size; mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; mz_uint8 *pExtra_data = NULL; mz_uint32 extra_size = 0; mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; mz_zip_internal_state *pState; mz_uint64 file_ofs = 0; if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; level = level_and_flags & 0xF; /* Sanity checks */ if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; if ((!pState->m_zip64) && (uncomp_size > MZ_UINT32_MAX)) { /* Source file is too large for non-zip64 */ /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ pState->m_zip64 = MZ_TRUE; } /* We could support this, but why? */ if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_writer_validate_archive_name(pArchive_name)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); if (pState->m_zip64) { if (pZip->m_total_files == MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { if (pZip->m_total_files == MZ_UINT16_MAX) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ } } archive_name_size = strlen(pArchive_name); if (archive_name_size > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); if (!pState->m_zip64) { /* Bail early if the archive would obviously become too large */ if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024 + MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ } } #ifndef MINIZ_NO_TIME if (pFile_time) { mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date); } #endif if (uncomp_size <= 3) level = 0; if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) { return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += num_alignment_padding_bytes; local_dir_header_ofs = cur_archive_file_ofs; if (pZip->m_file_offset_alignment) { MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } if (uncomp_size && level) { method = MZ_DEFLATED; } MZ_CLEAR_OBJ(local_dir_header); if (pState->m_zip64) { if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) { pExtra_data = extra_data; extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, gen_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += archive_name_size; if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += extra_size; } else { if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += archive_name_size; } if (user_extra_data_len > 0) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += user_extra_data_len; } if (uncomp_size) { mz_uint64 uncomp_remaining = uncomp_size; void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); if (!pRead_buf) { return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!level) { while (uncomp_remaining) { mz_uint n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); if ((read_callback(callback_opaque, file_ofs, pRead_buf, n) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } file_ofs += n; uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); uncomp_remaining -= n; cur_archive_file_ofs += n; } comp_size = uncomp_size; } else { mz_bool result = MZ_FALSE; mz_zip_writer_add_state state; tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); if (!pComp) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } state.m_pZip = pZip; state.m_cur_archive_file_ofs = cur_archive_file_ofs; state.m_comp_size = 0; if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); } for (;;) { size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); tdefl_status status; tdefl_flush flush = TDEFL_NO_FLUSH; if (read_callback(callback_opaque, file_ofs, pRead_buf, in_buf_size)!= in_buf_size) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); break; } file_ofs += in_buf_size; uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); uncomp_remaining -= in_buf_size; if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque)) flush = TDEFL_FULL_FLUSH; status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? flush : TDEFL_FINISH); if (status == TDEFL_STATUS_DONE) { result = MZ_TRUE; break; } else if (status != TDEFL_STATUS_OKAY) { mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); break; } } pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); if (!result) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return MZ_FALSE; } comp_size = state.m_comp_size; cur_archive_file_ofs = state.m_cur_archive_file_ofs; } pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); } { mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); if (pExtra_data == NULL) { if (comp_size > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); MZ_WRITE_LE32(local_dir_footer + 8, comp_size); MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); } else { MZ_WRITE_LE64(local_dir_footer + 8, comp_size); MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) return MZ_FALSE; cur_archive_file_ofs += local_dir_footer_size; } if (pExtra_data != NULL) { extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, user_extra_data_central, user_extra_data_central_len)) return MZ_FALSE; pZip->m_total_files++; pZip->m_archive_size = cur_archive_file_ofs; return MZ_TRUE; } #ifndef MINIZ_NO_STDIO static size_t mz_file_read_func_stdio(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { MZ_FILE *pSrc_file = (MZ_FILE *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pSrc_file); if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pSrc_file, (mz_int64)file_ofs, SEEK_SET)))) return 0; return MZ_FREAD(pBuf, 1, n, pSrc_file); } mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) { return mz_zip_writer_add_read_buf_callback(pZip, pArchive_name, mz_file_read_func_stdio, pSrc_file, size_to_add, pFile_time, pComment, comment_size, level_and_flags, user_extra_data, user_extra_data_len, user_extra_data_central, user_extra_data_central_len); } mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) { MZ_FILE *pSrc_file = NULL; mz_uint64 uncomp_size = 0; MZ_TIME_T file_modified_time; MZ_TIME_T *pFile_time = NULL; mz_bool status; memset(&file_modified_time, 0, sizeof(file_modified_time)); #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) pFile_time = &file_modified_time; if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED); #endif pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); if (!pSrc_file) return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); MZ_FSEEK64(pSrc_file, 0, SEEK_END); uncomp_size = MZ_FTELL64(pSrc_file); MZ_FSEEK64(pSrc_file, 0, SEEK_SET); status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0); MZ_FCLOSE(pSrc_file); return status; } #endif /* #ifndef MINIZ_NO_STDIO */ static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, uint32_t ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start) { /* + 64 should be enough for any new zip64 data */ if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE); if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start)) { mz_uint8 new_ext_block[64]; mz_uint8 *pDst = new_ext_block; mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); mz_write_le16(pDst + sizeof(mz_uint16), 0); pDst += sizeof(mz_uint16) * 2; if (pUncomp_size) { mz_write_le64(pDst, *pUncomp_size); pDst += sizeof(mz_uint64); } if (pComp_size) { mz_write_le64(pDst, *pComp_size); pDst += sizeof(mz_uint64); } if (pLocal_header_ofs) { mz_write_le64(pDst, *pLocal_header_ofs); pDst += sizeof(mz_uint64); } if (pDisk_start) { mz_write_le32(pDst, *pDisk_start); pDst += sizeof(mz_uint32); } mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2)); if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if ((pExt) && (ext_len)) { mz_uint32 extra_size_remaining = ext_len; const mz_uint8 *pExtra_data = pExt; do { mz_uint32 field_id, field_data_size, field_total_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); field_total_size = field_data_size + sizeof(mz_uint16) * 2; if (field_total_size > extra_size_remaining) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } pExtra_data += field_total_size; extra_size_remaining -= field_total_size; } while (extra_size_remaining); } return MZ_TRUE; } /* TODO: This func is now pretty freakin complex due to zip64, split it up? */ mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index) { mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size; mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs; mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; size_t orig_central_dir_size; mz_zip_internal_state *pState; void *pBuf; const mz_uint8 *pSrc_central_header; mz_zip_archive_file_stat src_file_stat; mz_uint32 src_filename_len, src_comment_len, src_ext_len; mz_uint32 local_header_filename_size, local_header_extra_len; mz_uint64 local_header_comp_size, local_header_uncomp_size; mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; /* Sanity checks */ if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */ if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* Get pointer to the source central dir header and crack it */ if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index))) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS); src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS); src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len; /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */ if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); if (!pState->m_zip64) { if (pZip->m_total_files == MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */ if (pZip->m_total_files == MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL)) return MZ_FALSE; cur_src_file_ofs = src_file_stat.m_local_header_ofs; cur_dst_file_ofs = pZip->m_archive_size; /* Read the source archive's local dir header */ if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; /* Compute the total size we need to copy (filename+extra data+compressed data) */ local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size; /* Try to find a zip64 extended information field */ if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) { mz_zip_array file_data_array; const mz_uint8 *pExtra_data; mz_uint32 extra_size_remaining = local_header_extra_len; mz_zip_array_init(&file_data_array, 1); if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE)) { return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) { mz_zip_array_clear(pZip, &file_data_array); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } pExtra_data = (const mz_uint8 *)file_data_array.m_p; do { mz_uint32 field_id, field_data_size, field_total_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { mz_zip_array_clear(pZip, &file_data_array); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); field_total_size = field_data_size + sizeof(mz_uint16) * 2; if (field_total_size > extra_size_remaining) { mz_zip_array_clear(pZip, &file_data_array); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); if (field_data_size < sizeof(mz_uint64) * 2) { mz_zip_array_clear(pZip, &file_data_array); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */ found_zip64_ext_data_in_ldir = MZ_TRUE; break; } pExtra_data += field_total_size; extra_size_remaining -= field_total_size; } while (extra_size_remaining); mz_zip_array_clear(pZip, &file_data_array); } if (!pState->m_zip64) { /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */ /* We also check when the archive is finalized so this doesn't need to be perfect. */ mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) + pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64; if (approx_new_archive_size >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); } /* Write dest archive padding */ if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) return MZ_FALSE; cur_dst_file_ofs += num_alignment_padding_bytes; local_dir_header_ofs = cur_dst_file_ofs; if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */ if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining))))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); while (src_archive_bytes_remaining) { n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining); if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } cur_src_file_ofs += n; if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_dst_file_ofs += n; src_archive_bytes_remaining -= n; } /* Now deal with the optional data descriptor */ bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); if (bit_flags & 8) { /* Copy data descriptor */ if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir)) { /* src is zip64, dest must be zip64 */ /* name uint32_t's */ /* id 1 (optional in zip64?) */ /* crc 1 */ /* comp_size 2 */ /* uncomp_size 2 */ if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5); } else { /* src is NOT zip64 */ mz_bool has_id; if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID); if (pZip->m_pState->m_zip64) { /* dest is zip64, so upgrade the data descriptor */ const mz_uint32 *pSrc_descriptor = (const mz_uint32 *)((const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0)); const mz_uint32 src_crc32 = pSrc_descriptor[0]; const mz_uint64 src_comp_size = pSrc_descriptor[1]; const mz_uint64 src_uncomp_size = pSrc_descriptor[2]; mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID); mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32); mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size); mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size); n = sizeof(mz_uint32) * 6; } else { /* dest is NOT zip64, just copy it as-is */ n = sizeof(mz_uint32) * (has_id ? 4 : 3); } } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_src_file_ofs += n; cur_dst_file_ofs += n; } pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); /* Finally, add the new central dir header */ orig_central_dir_size = pState->m_central_dir.m_size; memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); if (pState->m_zip64) { /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */ const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len; mz_zip_array new_ext_block; mz_zip_array_init(&new_ext_block, sizeof(mz_uint8)); MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX); MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX); MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX); if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL)) { mz_zip_array_clear(pZip, &new_ext_block); return MZ_FALSE; } MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) { mz_zip_array_clear(pZip, &new_ext_block); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len)) { mz_zip_array_clear(pZip, &new_ext_block); mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size)) { mz_zip_array_clear(pZip, &new_ext_block); mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len)) { mz_zip_array_clear(pZip, &new_ext_block); mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } mz_zip_array_clear(pZip, &new_ext_block); } else { /* sanity checks */ if (cur_dst_file_ofs > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (local_dir_header_ofs >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size)) { mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } } /* This shouldn't trigger unless we screwed up during the initial sanity checks */ if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) { /* TODO: Support central dirs >= 32-bits in size */ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); } n = (mz_uint32)orig_central_dir_size; if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) { mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } pZip->m_total_files++; pZip->m_archive_size = cur_dst_file_ofs; return MZ_TRUE; } mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) { mz_zip_internal_state *pState; mz_uint64 central_dir_ofs, central_dir_size; mz_uint8 hdr[256]; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; if (pState->m_zip64) { if ((pZip->m_total_files > MZ_UINT32_MAX) || (pState->m_central_dir.m_size >= MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } central_dir_ofs = 0; central_dir_size = 0; if (pZip->m_total_files) { /* Write central directory */ central_dir_ofs = pZip->m_archive_size; central_dir_size = pState->m_central_dir.m_size; pZip->m_central_directory_file_ofs = central_dir_ofs; if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); pZip->m_archive_size += central_dir_size; } if (pState->m_zip64) { /* Write zip64 end of central directory header */ mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size; MZ_CLEAR_OBJ(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64)); MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */ MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs); if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; /* Write zip64 end of central directory locator */ MZ_CLEAR_OBJ(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr); MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1); if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; } /* Write end of central directory record */ MZ_CLEAR_OBJ(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size)); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs)); if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); #ifndef MINIZ_NO_STDIO if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); #endif /* #ifndef MINIZ_NO_STDIO */ pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE; pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; return MZ_TRUE; } mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize) { if ((!ppBuf) || (!pSize)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); *ppBuf = NULL; *pSize = 0; if ((!pZip) || (!pZip->m_pState)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (pZip->m_pWrite != mz_zip_heap_write_func) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_writer_finalize_archive(pZip)) return MZ_FALSE; *ppBuf = pZip->m_pState->m_pMem; *pSize = pZip->m_pState->m_mem_size; pZip->m_pState->m_pMem = NULL; pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; return MZ_TRUE; } mz_bool mz_zip_writer_end(mz_zip_archive *pZip) { return mz_zip_writer_end_internal(pZip, MZ_TRUE); } #ifndef MINIZ_NO_STDIO mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) { return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL); } mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr) { mz_bool status, created_new_archive = MZ_FALSE; mz_zip_archive zip_archive; struct MZ_FILE_STAT_STRUCT file_stat; mz_zip_error actual_err = MZ_ZIP_NO_ERROR; mz_zip_zero_struct(&zip_archive); if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) { if (pErr) *pErr = MZ_ZIP_INVALID_PARAMETER; return MZ_FALSE; } if (!mz_zip_writer_validate_archive_name(pArchive_name)) { if (pErr) *pErr = MZ_ZIP_INVALID_FILENAME; return MZ_FALSE; } /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */ /* So be sure to compile with _LARGEFILE64_SOURCE 1 */ if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) { /* Create a new archive. */ if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags)) { if (pErr) *pErr = zip_archive.m_last_error; return MZ_FALSE; } created_new_archive = MZ_TRUE; } else { /* Append to an existing archive. */ if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) { if (pErr) *pErr = zip_archive.m_last_error; return MZ_FALSE; } if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags)) { if (pErr) *pErr = zip_archive.m_last_error; mz_zip_reader_end_internal(&zip_archive, MZ_FALSE); return MZ_FALSE; } } status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); actual_err = zip_archive.m_last_error; /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */ if (!mz_zip_writer_finalize_archive(&zip_archive)) { if (!actual_err) actual_err = zip_archive.m_last_error; status = MZ_FALSE; } if (!mz_zip_writer_end_internal(&zip_archive, status)) { if (!actual_err) actual_err = zip_archive.m_last_error; status = MZ_FALSE; } if ((!status) && (created_new_archive)) { /* It's a new archive and something went wrong, so just delete it. */ int ignoredStatus = MZ_DELETE_FILE(pZip_filename); (void)ignoredStatus; } if (pErr) *pErr = actual_err; return status; } void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr) { mz_uint32 file_index; mz_zip_archive zip_archive; void *p = NULL; if (pSize) *pSize = 0; if ((!pZip_filename) || (!pArchive_name)) { if (pErr) *pErr = MZ_ZIP_INVALID_PARAMETER; return NULL; } mz_zip_zero_struct(&zip_archive); if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) { if (pErr) *pErr = zip_archive.m_last_error; return NULL; } if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index)) { p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); } mz_zip_reader_end_internal(&zip_archive, p != NULL); if (pErr) *pErr = zip_archive.m_last_error; return p; } void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) { return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL); } #endif /* #ifndef MINIZ_NO_STDIO */ #endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ /* ------------------- Misc utils */ mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip) { return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID; } mz_zip_type mz_zip_get_type(mz_zip_archive *pZip) { return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID; } mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num) { mz_zip_error prev_err; if (!pZip) return MZ_ZIP_INVALID_PARAMETER; prev_err = pZip->m_last_error; pZip->m_last_error = err_num; return prev_err; } mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip) { if (!pZip) return MZ_ZIP_INVALID_PARAMETER; return pZip->m_last_error; } mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip) { return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR); } mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip) { mz_zip_error prev_err; if (!pZip) return MZ_ZIP_INVALID_PARAMETER; prev_err = pZip->m_last_error; pZip->m_last_error = MZ_ZIP_NO_ERROR; return prev_err; } const char *mz_zip_get_error_string(mz_zip_error mz_err) { switch (mz_err) { case MZ_ZIP_NO_ERROR: return "no error"; case MZ_ZIP_UNDEFINED_ERROR: return "undefined error"; case MZ_ZIP_TOO_MANY_FILES: return "too many files"; case MZ_ZIP_FILE_TOO_LARGE: return "file too large"; case MZ_ZIP_UNSUPPORTED_METHOD: return "unsupported method"; case MZ_ZIP_UNSUPPORTED_ENCRYPTION: return "unsupported encryption"; case MZ_ZIP_UNSUPPORTED_FEATURE: return "unsupported feature"; case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: return "failed finding central directory"; case MZ_ZIP_NOT_AN_ARCHIVE: return "not a ZIP archive"; case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: return "invalid header or archive is corrupted"; case MZ_ZIP_UNSUPPORTED_MULTIDISK: return "unsupported multidisk archive"; case MZ_ZIP_DECOMPRESSION_FAILED: return "decompression failed or archive is corrupted"; case MZ_ZIP_COMPRESSION_FAILED: return "compression failed"; case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: return "unexpected decompressed size"; case MZ_ZIP_CRC_CHECK_FAILED: return "CRC-32 check failed"; case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: return "unsupported central directory size"; case MZ_ZIP_ALLOC_FAILED: return "allocation failed"; case MZ_ZIP_FILE_OPEN_FAILED: return "file open failed"; case MZ_ZIP_FILE_CREATE_FAILED: return "file create failed"; case MZ_ZIP_FILE_WRITE_FAILED: return "file write failed"; case MZ_ZIP_FILE_READ_FAILED: return "file read failed"; case MZ_ZIP_FILE_CLOSE_FAILED: return "file close failed"; case MZ_ZIP_FILE_SEEK_FAILED: return "file seek failed"; case MZ_ZIP_FILE_STAT_FAILED: return "file stat failed"; case MZ_ZIP_INVALID_PARAMETER: return "invalid parameter"; case MZ_ZIP_INVALID_FILENAME: return "invalid filename"; case MZ_ZIP_BUF_TOO_SMALL: return "buffer too small"; case MZ_ZIP_INTERNAL_ERROR: return "internal error"; case MZ_ZIP_FILE_NOT_FOUND: return "file not found"; case MZ_ZIP_ARCHIVE_TOO_LARGE: return "archive is too large"; case MZ_ZIP_VALIDATION_FAILED: return "validation failed"; case MZ_ZIP_WRITE_CALLBACK_FAILED: return "write calledback failed"; default: break; } return "unknown error"; } /* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */ mz_bool mz_zip_is_zip64(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState)) return MZ_FALSE; return pZip->m_pState->m_zip64; } size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState)) return 0; return pZip->m_pState->m_central_dir.m_size; } mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) { return pZip ? pZip->m_total_files : 0; } mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip) { if (!pZip) return 0; return pZip->m_archive_size; } mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState)) return 0; return pZip->m_pState->m_file_archive_start_ofs; } MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState)) return 0; return pZip->m_pState->m_pFile; } size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n) { if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n); } mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) { mz_uint n; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); if (!p) { if (filename_buf_size) pFilename[0] = '\0'; mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return 0; } n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); if (filename_buf_size) { n = MZ_MIN(n, filename_buf_size - 1); memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pFilename[n] = '\0'; } return n + 1; } mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) { return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL); } mz_bool mz_zip_end(mz_zip_archive *pZip) { if (!pZip) return MZ_FALSE; if (pZip->m_zip_mode == MZ_ZIP_MODE_READING) return mz_zip_reader_end(pZip); #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)) return mz_zip_writer_end(pZip); #endif return MZ_FALSE; } #ifdef __cplusplus } #endif #endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/ OSCAR-code-v1.3.1/oscar/SleepLib/thirdparty/miniz.h000066400000000000000000002040011417327530600217650ustar00rootroot00000000000000/* miniz.c 2.1.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing See "unlicense" statement at the end of this file. Rich Geldreich , last updated Oct. 13, 2013 Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). * Low-level Deflate/Inflate implementation notes: Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses approximately as well as zlib. Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory block large enough to hold the entire file. The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. * zlib-style API notes: miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in zlib replacement in many apps: The z_stream struct, optional memory allocation callbacks deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound inflateInit/inflateInit2/inflate/inflateReset/inflateEnd compress, compress2, compressBound, uncompress CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. Supports raw deflate streams or standard zlib streams with adler-32 checking. Limitations: The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but there are no guarantees that miniz.c pulls this off perfectly. * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by Alex Evans. Supports 1-4 bytes/pixel images. * ZIP archive API notes: The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to get the job done with minimal fuss. There are simple API's to retrieve file information, read files from existing archives, create new archives, append new files to existing archives, or clone archive data from one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), or you can specify custom file read/write callbacks. - Archive reading: Just call this function to read a single file from a disk archive: void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); The locate operation can optionally check file comments too, which (as one example) can be used to identify multiple versions of the same file in an archive. This function uses a simple linear search through the central directory, so it's not very fast. Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and retrieve detailed info on each file by calling mz_zip_reader_file_stat(). - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data to disk and builds an exact image of the central directory in memory. The central directory image is written all at once at the end of the archive file when the archive is finalized. The archive writer can optionally align each file's local header and file data to any power of 2 alignment, which can be useful when the archive will be read from optical media. Also, the writer supports placing arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still readable by any ZIP tool. - Archive appending: The simple way to add a single file to an archive is to call this function: mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); The archive will be created if it doesn't already exist, otherwise it'll be appended to. Note the appending is done in-place and is not an atomic operation, so if something goes wrong during the operation it's possible the archive could be left without a central directory (although the local file headers and file data will be fine, so the archive will be recoverable). For more complex archive modification scenarios: 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and you're done. This is safe but requires a bunch of temporary disk space or heap memory. 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), append new files as needed, then finalize the archive which will write an updated central directory to the original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a possibility that the archive's central directory could be lost with this method if anything goes wrong, though. - ZIP archive support limitations: No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. Requires streams capable of seeking. * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. * Important: For best perf. be sure to customize the below macros for your target platform: #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #define MINIZ_LITTLE_ENDIAN 1 #define MINIZ_HAS_64BIT_REGISTERS 1 * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). */ #pragma once /* Defines to completely disable specific portions of miniz.c: If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. */ /* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */ #define MINIZ_NO_STDIO /* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */ /* get/set file times, and the C run-time funcs that get/set times won't be called. */ /* The current downside is the times written to your archives will be from 1979. */ /*#define MINIZ_NO_TIME */ /* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */ /*#define MINIZ_NO_ARCHIVE_APIS */ /* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */ /*#define MINIZ_NO_ARCHIVE_WRITING_APIS */ /* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */ /*#define MINIZ_NO_ZLIB_APIS */ /* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */ /*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ /* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */ /*#define MINIZ_NO_MALLOC */ #if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) /* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ #define MINIZ_NO_TIME #endif #include #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) #include #endif #if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) /* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */ #define MINIZ_X86_OR_X64_CPU 1 #else #define MINIZ_X86_OR_X64_CPU 0 #endif #if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU /* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ #define MINIZ_LITTLE_ENDIAN 1 #else #define MINIZ_LITTLE_ENDIAN 0 #endif /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ #if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) #if MINIZ_X86_OR_X64_CPU /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #define MINIZ_UNALIGNED_USE_MEMCPY #else #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 #endif #endif #if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) /* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */ #define MINIZ_HAS_64BIT_REGISTERS 1 #else #define MINIZ_HAS_64BIT_REGISTERS 0 #endif #ifdef __cplusplus extern "C" { #endif /* ------------------- zlib-style API Definitions. */ /* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */ typedef unsigned long mz_ulong; /* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */ void mz_free(void *p); #define MZ_ADLER32_INIT (1) /* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */ mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); #define MZ_CRC32_INIT (0) /* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); /* Compression strategies. */ enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; /* Method */ #define MZ_DEFLATED 8 /* Heap allocation callbacks. Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. */ typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); typedef void (*mz_free_func)(void *opaque, void *address); typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); /* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */ enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 }; #define MZ_VERSION "10.1.0" #define MZ_VERNUM 0xA100 #define MZ_VER_MAJOR 10 #define MZ_VER_MINOR 1 #define MZ_VER_REVISION 0 #define MZ_VER_SUBREVISION 0 #ifndef MINIZ_NO_ZLIB_APIS /* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */ enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 }; /* Return status codes. MZ_PARAM_ERROR is non-standard. */ enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; /* Window bits */ #define MZ_DEFAULT_WINDOW_BITS 15 struct mz_internal_state; /* Compression/decompression stream struct. */ typedef struct mz_stream_s { const unsigned char *next_in; /* pointer to next byte to read */ unsigned int avail_in; /* number of bytes available at next_in */ mz_ulong total_in; /* total number of bytes consumed so far */ unsigned char *next_out; /* pointer to next byte to write */ unsigned int avail_out; /* number of bytes that can be written to next_out */ mz_ulong total_out; /* total number of bytes produced so far */ char *msg; /* error msg (unused) */ struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */ mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */ mz_free_func zfree; /* optional heap free function (defaults to free) */ void *opaque; /* heap alloc function user pointer */ int data_type; /* data_type (unused) */ mz_ulong adler; /* adler32 of the source or uncompressed data */ mz_ulong reserved; /* not used */ } mz_stream; typedef mz_stream *mz_streamp; /* Returns the version string of miniz.c. */ const char *mz_version(void); /* mz_deflateInit() initializes a compressor with default options: */ /* Parameters: */ /* pStream must point to an initialized mz_stream struct. */ /* level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */ /* level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */ /* (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */ /* Return values: */ /* MZ_OK on success. */ /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_PARAM_ERROR if the input parameters are bogus. */ /* MZ_MEM_ERROR on out of memory. */ int mz_deflateInit(mz_streamp pStream, int level); /* mz_deflateInit2() is like mz_deflate(), except with more control: */ /* Additional parameters: */ /* method must be MZ_DEFLATED */ /* window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */ /* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */ int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); /* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */ int mz_deflateReset(mz_streamp pStream); /* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */ /* Parameters: */ /* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ /* flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */ /* Return values: */ /* MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */ /* MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */ /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_PARAM_ERROR if one of the parameters is invalid. */ /* MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */ int mz_deflate(mz_streamp pStream, int flush); /* mz_deflateEnd() deinitializes a compressor: */ /* Return values: */ /* MZ_OK on success. */ /* MZ_STREAM_ERROR if the stream is bogus. */ int mz_deflateEnd(mz_streamp pStream); /* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */ mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); /* Single-call compression functions mz_compress() and mz_compress2(): */ /* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */ int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); /* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */ mz_ulong mz_compressBound(mz_ulong source_len); /* Initializes a decompressor. */ int mz_inflateInit(mz_streamp pStream); /* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */ /* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */ int mz_inflateInit2(mz_streamp pStream, int window_bits); /* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */ int mz_inflateReset(mz_streamp pStream); /* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */ /* Parameters: */ /* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ /* flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */ /* On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */ /* MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */ /* Return values: */ /* MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */ /* MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */ /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_DATA_ERROR if the deflate stream is invalid. */ /* MZ_PARAM_ERROR if one of the parameters is invalid. */ /* MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */ /* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */ int mz_inflate(mz_streamp pStream, int flush); /* Deinitializes a decompressor. */ int mz_inflateEnd(mz_streamp pStream); /* Single-call decompression. */ /* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */ int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); /* Returns a string description of the specified error code, or NULL if the error code is invalid. */ const char *mz_error(int err); /* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */ /* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */ #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES typedef unsigned char Byte; typedef unsigned int uInt; typedef mz_ulong uLong; typedef Byte Bytef; typedef uInt uIntf; typedef char charf; typedef int intf; typedef void *voidpf; typedef uLong uLongf; typedef void *voidp; typedef void *const voidpc; #define Z_NULL 0 #define Z_NO_FLUSH MZ_NO_FLUSH #define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH #define Z_SYNC_FLUSH MZ_SYNC_FLUSH #define Z_FULL_FLUSH MZ_FULL_FLUSH #define Z_FINISH MZ_FINISH #define Z_BLOCK MZ_BLOCK #define Z_OK MZ_OK #define Z_STREAM_END MZ_STREAM_END #define Z_NEED_DICT MZ_NEED_DICT #define Z_ERRNO MZ_ERRNO #define Z_STREAM_ERROR MZ_STREAM_ERROR #define Z_DATA_ERROR MZ_DATA_ERROR #define Z_MEM_ERROR MZ_MEM_ERROR #define Z_BUF_ERROR MZ_BUF_ERROR #define Z_VERSION_ERROR MZ_VERSION_ERROR #define Z_PARAM_ERROR MZ_PARAM_ERROR #define Z_NO_COMPRESSION MZ_NO_COMPRESSION #define Z_BEST_SPEED MZ_BEST_SPEED #define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION #define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY #define Z_FILTERED MZ_FILTERED #define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY #define Z_RLE MZ_RLE #define Z_FIXED MZ_FIXED #define Z_DEFLATED MZ_DEFLATED #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS #define alloc_func mz_alloc_func #define free_func mz_free_func #define internal_state mz_internal_state #define z_stream mz_stream #define deflateInit mz_deflateInit #define deflateInit2 mz_deflateInit2 #define deflateReset mz_deflateReset #define deflate mz_deflate #define deflateEnd mz_deflateEnd #define deflateBound mz_deflateBound #define compress mz_compress #define compress2 mz_compress2 #define compressBound mz_compressBound #define inflateInit mz_inflateInit #define inflateInit2 mz_inflateInit2 #define inflateReset mz_inflateReset #define inflate mz_inflate #define inflateEnd mz_inflateEnd #define uncompress mz_uncompress #define crc32 mz_crc32 #define adler32 mz_adler32 #define MAX_WBITS 15 #define MAX_MEM_LEVEL 9 #define zError mz_error #define ZLIB_VERSION MZ_VERSION #define ZLIB_VERNUM MZ_VERNUM #define ZLIB_VER_MAJOR MZ_VER_MAJOR #define ZLIB_VER_MINOR MZ_VER_MINOR #define ZLIB_VER_REVISION MZ_VER_REVISION #define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION #define zlibVersion mz_version #define zlib_version mz_version() #endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ #endif /* MINIZ_NO_ZLIB_APIS */ #ifdef __cplusplus } #endif #pragma once #include #include #include #include /* ------------------- Types and macros */ typedef unsigned char mz_uint8; typedef signed short mz_int16; typedef unsigned short mz_uint16; typedef unsigned int mz_uint32; typedef unsigned int mz_uint; typedef int64_t mz_int64; typedef uint64_t mz_uint64; typedef int mz_bool; #define MZ_FALSE (0) #define MZ_TRUE (1) /* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */ #ifdef _MSC_VER #define MZ_MACRO_END while (0, 0) #else #define MZ_MACRO_END while (0) #endif #ifdef MINIZ_NO_STDIO #define MZ_FILE void * #else #include #define MZ_FILE FILE #endif /* #ifdef MINIZ_NO_STDIO */ #ifdef MINIZ_NO_TIME typedef struct mz_dummy_time_t_tag { int m_dummy; } mz_dummy_time_t; #define MZ_TIME_T mz_dummy_time_t #else #define MZ_TIME_T time_t #endif #define MZ_ASSERT(x) assert(x) #ifdef MINIZ_NO_MALLOC #define MZ_MALLOC(x) NULL #define MZ_FREE(x) (void)x, ((void)0) #define MZ_REALLOC(p, x) NULL #else #define MZ_MALLOC(x) malloc(x) #define MZ_FREE(x) free(x) #define MZ_REALLOC(p, x) realloc(p, x) #endif #define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) #else #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) #endif #define MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U)) #ifdef _MSC_VER #define MZ_FORCEINLINE __forceinline #elif defined(__GNUC__) #define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__)) #else #define MZ_FORCEINLINE inline #endif #ifdef __cplusplus extern "C" { #endif extern void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); extern void miniz_def_free_func(void *opaque, void *address); extern void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); #define MZ_UINT16_MAX (0xFFFFU) #define MZ_UINT32_MAX (0xFFFFFFFFU) #ifdef __cplusplus } #endif #pragma once #ifdef __cplusplus extern "C" { #endif /* ------------------- Low-level Compression API Definitions */ /* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). */ #define TDEFL_LESS_MEMORY 0 /* tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): */ /* TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). */ enum { TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF }; /* TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. */ /* TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). */ /* TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. */ /* TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). */ /* TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) */ /* TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. */ /* TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. */ /* TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. */ /* The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). */ enum { TDEFL_WRITE_ZLIB_HEADER = 0x01000, TDEFL_COMPUTE_ADLER32 = 0x02000, TDEFL_GREEDY_PARSING_FLAG = 0x04000, TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, TDEFL_RLE_MATCHES = 0x10000, TDEFL_FILTER_MATCHES = 0x20000, TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 }; /* High level compression functions: */ /* tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). */ /* On entry: */ /* pSrc_buf, src_buf_len: Pointer and size of source block to compress. */ /* flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. */ /* On return: */ /* Function returns a pointer to the compressed data, or NULL on failure. */ /* *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */ /* The caller must free() the returned block when it's no longer needed. */ void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); /* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */ /* Returns 0 on failure. */ size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); /* Compresses an image to a compressed PNG file in memory. */ /* On entry: */ /* pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. */ /* The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. */ /* level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL */ /* If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). */ /* On return: */ /* Function returns a pointer to the compressed data, or NULL on failure. */ /* *pLen_out will be set to the size of the PNG image file. */ /* The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */ void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); /* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */ typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); /* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */ mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; /* TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). */ #if TDEFL_LESS_MEMORY enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; #else enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; #endif /* The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. */ typedef enum { TDEFL_STATUS_BAD_PARAM = -2, TDEFL_STATUS_PUT_BUF_FAILED = -1, TDEFL_STATUS_OKAY = 0, TDEFL_STATUS_DONE = 1 } tdefl_status; /* Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums */ typedef enum { TDEFL_NO_FLUSH = 0, TDEFL_SYNC_FLUSH = 2, TDEFL_FULL_FLUSH = 3, TDEFL_FINISH = 4 } tdefl_flush; /* tdefl's compression state structure. */ typedef struct { tdefl_put_buf_func_ptr m_pPut_buf_func; void *m_pPut_buf_user; mz_uint m_flags, m_max_probes[2]; int m_greedy_parsing; mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; tdefl_status m_prev_return_status; const void *m_pIn_buf; void *m_pOut_buf; size_t *m_pIn_buf_size, *m_pOut_buf_size; tdefl_flush m_flush; const mz_uint8 *m_pSrc; size_t m_src_buf_left, m_out_buf_ofs; mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; } tdefl_compressor; /* Initializes the compressor. */ /* There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. */ /* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */ /* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */ /* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */ tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); /* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */ tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); /* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */ /* tdefl_compress_buffer() always consumes the entire input buffer. */ tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); mz_uint32 tdefl_get_adler32(tdefl_compressor *d); /* Create tdefl_compress() flags given zlib-style compression parameters. */ /* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */ /* window_bits may be -15 (raw deflate) or 15 (zlib) */ /* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */ mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); #ifndef MINIZ_NO_MALLOC /* Allocate the tdefl_compressor structure in C so that */ /* non-C language bindings to tdefl_ API don't need to worry about */ /* structure size and allocation mechanism. */ tdefl_compressor *tdefl_compressor_alloc(void); void tdefl_compressor_free(tdefl_compressor *pComp); #endif #ifdef __cplusplus } #endif #pragma once /* ------------------- Low-level Decompression API Definitions */ #ifdef __cplusplus extern "C" { #endif /* Decompression flags used by tinfl_decompress(). */ /* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */ /* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */ /* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */ /* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */ enum { TINFL_FLAG_PARSE_ZLIB_HEADER = 1, TINFL_FLAG_HAS_MORE_INPUT = 2, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, TINFL_FLAG_COMPUTE_ADLER32 = 8 }; /* High level decompression functions: */ /* tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). */ /* On entry: */ /* pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. */ /* On return: */ /* Function returns a pointer to the decompressed data, or NULL on failure. */ /* *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */ /* The caller must call mz_free() on the returned block when it's no longer needed. */ void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); /* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */ /* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */ #define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); /* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */ /* Returns 1 on success or 0 on failure. */ typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; #ifndef MINIZ_NO_MALLOC /* Allocate the tinfl_decompressor structure in C so that */ /* non-C language bindings to tinfl_ API don't need to worry about */ /* structure size and allocation mechanism. */ tinfl_decompressor *tinfl_decompressor_alloc(void); void tinfl_decompressor_free(tinfl_decompressor *pDecomp); #endif /* Max size of LZ dictionary. */ #define TINFL_LZ_DICT_SIZE 32768 /* Return status. */ typedef enum { /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */ /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */ /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */ TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4, /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */ TINFL_STATUS_BAD_PARAM = -3, /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */ TINFL_STATUS_ADLER32_MISMATCH = -2, /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */ TINFL_STATUS_FAILED = -1, /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */ /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */ /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */ TINFL_STATUS_DONE = 0, /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */ /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */ /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */ TINFL_STATUS_NEEDS_MORE_INPUT = 1, /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */ /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */ /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */ /* so I may need to add some code to address this. */ TINFL_STATUS_HAS_MORE_OUTPUT = 2 } tinfl_status; /* Initializes the decompressor to its initial state. */ #define tinfl_init(r) \ do \ { \ (r)->m_state = 0; \ } \ MZ_MACRO_END #define tinfl_get_adler32(r) (r)->m_check_adler32 /* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */ /* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); /* Internal/private bits follow. */ enum { TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS }; typedef struct { mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; } tinfl_huff_table; #if MINIZ_HAS_64BIT_REGISTERS #define TINFL_USE_64BIT_BITBUF 1 #else #define TINFL_USE_64BIT_BITBUF 0 #endif #if TINFL_USE_64BIT_BITBUF typedef mz_uint64 tinfl_bit_buf_t; #define TINFL_BITBUF_SIZE (64) #else typedef mz_uint32 tinfl_bit_buf_t; #define TINFL_BITBUF_SIZE (32) #endif struct tinfl_decompressor_tag { mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; tinfl_bit_buf_t m_bit_buf; size_t m_dist_from_out_buf_start; tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; }; #ifdef __cplusplus } #endif #pragma once /* ------------------- ZIP archive reading/writing */ #ifndef MINIZ_NO_ARCHIVE_APIS #ifdef __cplusplus extern "C" { #endif enum { /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */ MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512 }; typedef struct { /* Central directory file index. */ mz_uint32 m_file_index; /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */ mz_uint64 m_central_dir_ofs; /* These fields are copied directly from the zip's central dir. */ mz_uint16 m_version_made_by; mz_uint16 m_version_needed; mz_uint16 m_bit_flag; mz_uint16 m_method; #ifndef MINIZ_NO_TIME MZ_TIME_T m_time; #endif /* CRC-32 of uncompressed data. */ mz_uint32 m_crc32; /* File's compressed size. */ mz_uint64 m_comp_size; /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */ mz_uint64 m_uncomp_size; /* Zip internal and external file attributes. */ mz_uint16 m_internal_attr; mz_uint32 m_external_attr; /* Entry's local header file offset in bytes. */ mz_uint64 m_local_header_ofs; /* Size of comment in bytes. */ mz_uint32 m_comment_size; /* MZ_TRUE if the entry appears to be a directory. */ mz_bool m_is_directory; /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */ mz_bool m_is_encrypted; /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */ mz_bool m_is_supported; /* Filename. If string ends in '/' it's a subdirectory entry. */ /* Guaranteed to be zero terminated, may be truncated to fit. */ char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; /* Comment field. */ /* Guaranteed to be zero terminated, may be truncated to fit. */ char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; } mz_zip_archive_file_stat; typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); struct mz_zip_internal_state_tag; typedef struct mz_zip_internal_state_tag mz_zip_internal_state; typedef enum { MZ_ZIP_MODE_INVALID = 0, MZ_ZIP_MODE_READING = 1, MZ_ZIP_MODE_WRITING = 2, MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 } mz_zip_mode; typedef enum { MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800, MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */ MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */ MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000, /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */ MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000, MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000 } mz_zip_flags; typedef enum { MZ_ZIP_TYPE_INVALID = 0, MZ_ZIP_TYPE_USER, MZ_ZIP_TYPE_MEMORY, MZ_ZIP_TYPE_HEAP, MZ_ZIP_TYPE_FILE, MZ_ZIP_TYPE_CFILE, MZ_ZIP_TOTAL_TYPES } mz_zip_type; /* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */ typedef enum { MZ_ZIP_NO_ERROR = 0, MZ_ZIP_UNDEFINED_ERROR, MZ_ZIP_TOO_MANY_FILES, MZ_ZIP_FILE_TOO_LARGE, MZ_ZIP_UNSUPPORTED_METHOD, MZ_ZIP_UNSUPPORTED_ENCRYPTION, MZ_ZIP_UNSUPPORTED_FEATURE, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, MZ_ZIP_NOT_AN_ARCHIVE, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, MZ_ZIP_UNSUPPORTED_MULTIDISK, MZ_ZIP_DECOMPRESSION_FAILED, MZ_ZIP_COMPRESSION_FAILED, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, MZ_ZIP_CRC_CHECK_FAILED, MZ_ZIP_UNSUPPORTED_CDIR_SIZE, MZ_ZIP_ALLOC_FAILED, MZ_ZIP_FILE_OPEN_FAILED, MZ_ZIP_FILE_CREATE_FAILED, MZ_ZIP_FILE_WRITE_FAILED, MZ_ZIP_FILE_READ_FAILED, MZ_ZIP_FILE_CLOSE_FAILED, MZ_ZIP_FILE_SEEK_FAILED, MZ_ZIP_FILE_STAT_FAILED, MZ_ZIP_INVALID_PARAMETER, MZ_ZIP_INVALID_FILENAME, MZ_ZIP_BUF_TOO_SMALL, MZ_ZIP_INTERNAL_ERROR, MZ_ZIP_FILE_NOT_FOUND, MZ_ZIP_ARCHIVE_TOO_LARGE, MZ_ZIP_VALIDATION_FAILED, MZ_ZIP_WRITE_CALLBACK_FAILED, MZ_ZIP_TOTAL_ERRORS } mz_zip_error; typedef struct { mz_uint64 m_archive_size; mz_uint64 m_central_directory_file_ofs; /* We only support up to UINT32_MAX files in zip64 mode. */ mz_uint32 m_total_files; mz_zip_mode m_zip_mode; mz_zip_type m_zip_type; mz_zip_error m_last_error; mz_uint64 m_file_offset_alignment; mz_alloc_func m_pAlloc; mz_free_func m_pFree; mz_realloc_func m_pRealloc; void *m_pAlloc_opaque; mz_file_read_func m_pRead; mz_file_write_func m_pWrite; mz_file_needs_keepalive m_pNeeds_keepalive; void *m_pIO_opaque; mz_zip_internal_state *m_pState; } mz_zip_archive; typedef struct { mz_zip_archive *pZip; mz_uint flags; int status; #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS mz_uint file_crc32; #endif mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs; mz_zip_archive_file_stat file_stat; void *pRead_buf; void *pWrite_buf; size_t out_blk_remain; tinfl_decompressor inflator; } mz_zip_reader_extract_iter_state; /* -------- ZIP reading */ /* Inits a ZIP archive reader. */ /* These functions read and validate the archive's central directory. */ mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags); #ifndef MINIZ_NO_STDIO /* Read a archive from a disk file. */ /* file_start_ofs is the file offset where the archive actually begins, or 0. */ /* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */ mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size); /* Read an archive from an already opened FILE, beginning at the current file position. */ /* The archive is assumed to be archive_size bytes long. If archive_size is < 0, then the entire rest of the file is assumed to contain the archive. */ /* The FILE will NOT be closed when mz_zip_reader_end() is called. */ mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags); #endif /* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */ mz_bool mz_zip_reader_end(mz_zip_archive *pZip); /* -------- ZIP reading or writing */ /* Clears a mz_zip_archive struct to all zeros. */ /* Important: This must be done before passing the struct to any mz_zip functions. */ void mz_zip_zero_struct(mz_zip_archive *pZip); mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); /* Returns the total number of files in the archive. */ mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip); MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); /* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */ size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); /* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */ /* Note that the m_last_error functionality is not thread safe. */ mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); const char *mz_zip_get_error_string(mz_zip_error mz_err); /* MZ_TRUE if the archive file entry is a directory entry. */ mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); /* MZ_TRUE if the file is encrypted/strong encrypted. */ mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); /* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */ mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); /* Retrieves the filename of an archive file entry. */ /* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */ mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); /* Attempts to locates a file in the archive's central directory. */ /* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ /* Returns -1 if the file cannot be found. */ int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); int mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); /* Returns detailed information about an archive file entry. */ mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); /* MZ_TRUE if the file is in zip64 format. */ /* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */ mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); /* Returns the total central directory size in bytes. */ /* The current max supported size is <= MZ_UINT32_MAX. */ size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); /* Extracts a archive file to a memory buffer using no memory allocation. */ /* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */ mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); /* Extracts a archive file to a memory buffer. */ mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); /* Extracts a archive file to a dynamically allocated heap buffer. */ /* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */ /* Returns NULL and sets the last error on failure. */ void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); /* Extracts a archive file using a callback function to output the file's data. */ mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); /* Extract a file iteratively */ mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size); mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState); #ifndef MINIZ_NO_STDIO /* Extracts a archive file to a disk file and sets its last accessed and modified times. */ /* This function only extracts files, not archive directory records. */ mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); /* Extracts a archive file starting at the current position in the destination FILE stream. */ mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags); mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags); #endif #if 0 /* TODO */ typedef void *mz_zip_streaming_extract_state_ptr; mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); uint64_t mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); uint64_t mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, uint64_t new_ofs); size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size); mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); #endif /* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */ /* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */ mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); /* Validates an entire archive by calling mz_zip_validate_file() on each file. */ mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags); /* Misc utils/helpers, valid for ZIP reading or writing */ mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr); mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr); /* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */ mz_bool mz_zip_end(mz_zip_archive *pZip); /* -------- ZIP writing */ #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS /* Inits a ZIP archive writer. */ /*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/ /*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/ mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags); mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags); #ifndef MINIZ_NO_STDIO mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags); mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags); #endif /* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */ /* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */ /* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */ /* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */ /* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */ /* the archive is finalized the file's central directory will be hosed. */ mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); /* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */ /* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */ /* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); /* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */ /* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */ mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len); /* Adds the contents of a file to an archive. This function also records the disk file's modified time into the archive. */ /* File data is supplied via a read callback function. User mz_zip_writer_add_(c)file to add a file directly.*/ mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len); #ifndef MINIZ_NO_STDIO /* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */ /* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); /* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */ mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len); #endif /* Adds a file to an archive by fully cloning the data from another archive. */ /* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */ mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index); /* Finalizes the archive by writing the central directory records followed by the end of central directory record. */ /* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */ /* An archive must be manually finalized by calling this function for it to be valid. */ mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); /* Finalizes a heap archive, returning a poiner to the heap block and its size. */ /* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */ mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize); /* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */ /* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */ mz_bool mz_zip_writer_end(mz_zip_archive *pZip); /* -------- Misc. high-level helper functions: */ /* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */ /* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */ /* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ /* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */ mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr); /* Reads a single file from an archive into a heap block. */ /* If pComment is not NULL, only the file with the specified comment will be extracted. */ /* Returns NULL on failure. */ void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags); void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr); #endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ #ifdef __cplusplus } #endif #endif /* MINIZ_NO_ARCHIVE_APIS */ OSCAR-code-v1.3.1/oscar/SleepLib/xmlreplay.cpp000066400000000000000000000371431417327530600210300ustar00rootroot00000000000000/* XML event recording/replay * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "xmlreplay.h" #include #include #include #include #include #include #include #include // Derive the filepath for the given substream ID relative to the parent stream. static QString substreamFilepath(QFile* parent, const QString & id) { Q_ASSERT(parent); QFileInfo info(*parent); QString path = info.canonicalPath() + QDir::separator() + info.completeBaseName() + "-" + id + ".xml"; return path; } // MARK: - // MARK: XML record/playback base classes const QString XmlRecorder::TAG = "xmlreplay"; XmlRecorder::XmlRecorder(QFile* stream, const QString & tag) : m_tag(tag), m_file(stream), m_xml(new QXmlStreamWriter(stream)), m_parent(nullptr) { prologue(); } XmlRecorder::XmlRecorder(QString & string, const QString & tag) : m_tag(tag), m_file(nullptr), m_xml(new QXmlStreamWriter(&string)), m_parent(nullptr) { prologue(); } // Protected constructor for substreams XmlRecorder::XmlRecorder(XmlRecorder* parent, const QString & id, const QString & tag) : m_tag(tag), m_file(nullptr), m_xml(nullptr), m_parent(parent) { Q_ASSERT(m_parent); m_xml = m_parent->addSubstream(this, id); if (m_xml == nullptr) { qWarning() << "Not recording" << id; static QString null; m_xml = new QXmlStreamWriter(&null); } prologue(); } // Initialize a child recording substream. QXmlStreamWriter* XmlRecorder::addSubstream(XmlRecorder* child, const QString & id) { Q_ASSERT(child); QXmlStreamWriter* xml = nullptr; if (m_file) { QString childPath = substreamFilepath(m_file, id); child->m_file = new QFile(childPath); if (child->m_file->open(QIODevice::WriteOnly | QIODevice::Append)) { xml = new QXmlStreamWriter(child->m_file); qDebug() << "Recording to" << childPath; } else { qWarning() << "Unable to open" << childPath << "for writing"; } } else { qWarning() << "String-based substreams are not supported"; // Maybe some day support string-based substreams: // - parent passes string to child // - on connectionClosed, parent asks recorder to flush string to stream } return xml; } XmlRecorder::~XmlRecorder() { epilogue(); delete m_xml; // File substreams manage their own file. if (m_parent && m_file) { delete m_file; } } // Close out a substream and return its parent. XmlRecorder* XmlRecorder::closeSubstream() { auto parent = m_parent; delete this; return parent; } void XmlRecorder::prologue() { Q_ASSERT(m_xml); m_xml->setAutoFormatting(true); m_xml->setAutoFormattingIndent(2); m_xml->writeStartElement(m_tag); // open enclosing tag } void XmlRecorder::epilogue() { Q_ASSERT(m_xml); m_xml->writeEndElement(); // close enclosing tag } void XmlRecorder::flush() { if (m_file) { if (!m_file->flush()) { qWarning().noquote() << "Unable to flush XML to" << m_file->fileName(); } } } XmlReplay::XmlReplay(QFile* file, const QString & tag) : m_tag(tag), m_file(file), m_pendingSignal(nullptr), m_parent(nullptr) { Q_ASSERT(file); QFileInfo info(*file); qDebug() << "Replaying from" << info.canonicalFilePath(); QXmlStreamReader xml(file); deserialize(xml); } XmlReplay::XmlReplay(QXmlStreamReader & xml, const QString & tag) : m_tag(tag), m_file(nullptr), m_pendingSignal(nullptr), m_parent(nullptr) { deserialize(xml); } // Protected constructor for substreams XmlReplay::XmlReplay(XmlReplay* parent, const QString & id, const QString & tag) : m_tag(tag), m_file(nullptr), m_pendingSignal(nullptr), m_parent(parent) { Q_ASSERT(m_parent); auto xml = m_parent->findSubstream(this, id); if (xml) { deserialize(*xml); delete xml; } else { qWarning() << "Not replaying" << id; } } // Initialize a child replay substream. QXmlStreamReader* XmlReplay::findSubstream(XmlReplay* child, const QString & id) { Q_ASSERT(child); QXmlStreamReader* xml = nullptr; if (m_file) { QString childPath = substreamFilepath(m_file, id); child->m_file = new QFile(childPath); if (child->m_file->open(QIODevice::ReadOnly)) { xml = new QXmlStreamReader(child->m_file); qDebug() << "Replaying from" << childPath; } else { qWarning() << "Unable to open" << childPath << "for reading"; } } else { qWarning() << "String-based substreams are not supported"; // Maybe some day support string-based substreams: // - when deserializing, use e.g. ConnectionEvents to cache the substream strings // - then return a QXmlStreamReader here using that string } return xml; } XmlReplay::~XmlReplay() { for (auto event : m_events) { delete event; } // File substreams manage their own file. if (m_parent && m_file) { delete m_file; } } // Close out a substream and return its parent. XmlReplay* XmlReplay::closeSubstream() { auto parent = m_parent; delete this; return parent; } void XmlReplay::deserialize(QXmlStreamReader & xml) { if (xml.readNextStartElement()) { if (xml.name() == m_tag) { deserializeEvents(xml); } else { qWarning() << "unexpected payload in replay XML:" << xml.name(); xml.skipCurrentElement(); } } } void XmlReplay::deserializeEvents(QXmlStreamReader & xml) { while (xml.readNextStartElement()) { QString type = xml.name().toString(); XmlReplayEvent* event = XmlReplayEvent::createInstance(type); if (event) { xml >> *event; // Add to list if (m_events.isEmpty() == false) { m_events.last()->m_next = event; } m_events.append(event); // Add to index const QString & id = event->id(); auto & events = m_eventIndex[type][id]; events.append(event); } else { xml.skipCurrentElement(); } } } // Queue any pending signals when a replay lock is released. void XmlReplay::processPendingSignals(const QObject* target) { if (m_pendingSignal) { XmlReplayEvent* pending = m_pendingSignal; m_pendingSignal = nullptr; // Dequeue the signal from the index; this may update m_pendingSignal. XmlReplayEvent* dequeued = getNextEvent(pending->tag(), pending->id()); if (dequeued != pending) { qWarning() << "triggered signal doesn't match dequeued signal!" << pending << dequeued; } // It is safe to re-cast this as non-const because signals are deferred // and cannot alter the underlying target until the const method holding // the lock releases it at function exit. pending->signal(const_cast(target)); } } // Update the positions at which to begin searching the index, so that only events on or after the given time are returned by getNextEvent. void XmlReplay::seekToTime(const QDateTime & time) { for (auto & type : m_eventIndex.keys()) { for (auto & key : m_eventIndex[type].keys()) { // Find the index of the first event on or after the given time. auto & events = m_eventIndex[type][key]; int pos; for (pos = 0; pos < events.size(); pos++) { auto & event = events.at(pos); // Random-access events should always start searching from position 0. if (event->randomAccess() || event->m_time >= time) { break; } } // If pos == events.size(), that means there are no more events of this type // after the given time. m_indexPosition[type][key] = pos; } } } // Find and return the next event of the given type with the given ID, or nullptr if no more events match. XmlReplayEvent* XmlReplay::getNextEvent(const QString & type, const QString & id) { XmlReplayEvent* event = nullptr; // Event handlers should always be wrapped in an XmlReplayLock, so warn if that's not the case. if (m_lock.tryLock()) { qWarning() << "XML replay" << type << "object not locked by event handler!"; m_lock.unlock(); } // Search the index for the next matching event (if any). if (m_eventIndex.contains(type)) { auto & ids = m_eventIndex[type]; if (ids.contains(id)) { auto & events = ids[id]; // Start at the position identified by the previous random-access event. int pos = m_indexPosition[type][id]; if (pos < events.size()) { event = events.at(pos); // TODO: if we're simulating the original timing, return nullptr if we haven't reached this event's time yet; // otherwise: events.removeAt(pos); } } } // If this is a random-access event, we need to update the index positions for all non-random-access events. if (event && event->randomAccess()) { seekToTime(event->m_time); } // If the event following this one is a signal (that replay needs to trigger), save it as pending // so that it can be emitted when the replay lock for this event is released. if (event && event->m_next && event->m_next->isSignal()) { Q_ASSERT(m_pendingSignal == nullptr); // if this ever fails, we may need m_pendingSignal to be a list m_pendingSignal = event->m_next; } return event; } // MARK: - // MARK: XML record/playback event base class void XmlReplayEvent::set(const QString & name, const QString & value) { if (!m_values.contains(name)) { m_keys.append(name); } m_values[name] = value; } void XmlReplayEvent::set(const QString & name, qint64 value) { set(name, QString::number(value)); } void XmlReplayEvent::setData(const char* data, qint64 length) { Q_ASSERT(usesData() == true); QByteArray bytes = QByteArray::fromRawData(data, length); m_data = bytes.toHex(' ').toUpper(); } QString XmlReplayEvent::get(const QString & name) const { if (!m_values.contains(name)) { qWarning().noquote() << *this << "missing attribute:" << name; } return m_values[name]; } QByteArray XmlReplayEvent::getData() const { Q_ASSERT(usesData() == true); if (m_data.isEmpty()) { qWarning().noquote() << "replaying event with missing data" << *this; QByteArray empty; return empty; // toUtf8() below crashes with an empty string. } return QByteArray::fromHex(m_data.toUtf8()); } void XmlReplayEvent::copyIf(const XmlReplayEvent* other) { // Leave the proposed event alone if there was no replay event. if (other == nullptr) { return; } // Do not copy timestamp. m_values = other->m_values; m_keys = other->m_keys; m_data = other->m_data; } void XmlReplayEvent::copy(const XmlReplayEvent & other) { copyIf(&other); // Copy the timestamp, as it is necessary for replaying substreams that use the timestamp as part of their ID. m_time = other.m_time; } void XmlReplayEvent::signal(QObject* target) { // Queue the signal so that it won't be processed before the current event returns to its caller. // (See XmlReplayLock below.) QMetaObject::invokeMethod(target, m_signal, Qt::QueuedConnection); } void XmlReplayEvent::write(QXmlStreamWriter & xml) const { // Write key/value pairs as attributes in the order they were set. for (auto key : m_keys) { xml.writeAttribute(key, m_values[key]); } if (!m_data.isEmpty()) { Q_ASSERT(usesData() == true); xml.writeCharacters(m_data); } } void XmlReplayEvent::read(QXmlStreamReader & xml) { QXmlStreamAttributes attribs = xml.attributes(); for (auto & attrib : attribs) { if (attrib.name() != "time") { // skip outer timestamp, which is decoded by operator>> set(attrib.name().toString(), attrib.value().toString()); } } if (usesData()) { m_data = xml.readElementText(); } else { xml.skipCurrentElement(); } } void XmlReplayEvent::record(XmlRecorder* writer) const { // Do nothing if we're not recording. if (writer != nullptr) { writer->lock(); writer->xml() << *this; writer->flush(); writer->unlock(); } } XmlReplayEvent::XmlReplayEvent() : m_time(QDateTime::currentDateTime()), m_next(nullptr), m_signal(nullptr) { } QHash & XmlReplayEvent::factories() { // Use a local static variable so that it is guaranteed to be initialized when registerClass and createInstance are called. static QHash s_factories; return s_factories; } bool XmlReplayEvent::registerClass(const QString & tag, XmlReplayEvent::FactoryMethod factory) { if (factories().contains(tag)) { qWarning() << "Event class already registered for tag" << tag; return false; } factories()[tag] = factory; return true; } XmlReplayEvent* XmlReplayEvent::createInstance(const QString & tag) { XmlReplayEvent* event = nullptr; XmlReplayEvent::FactoryMethod factory = factories().value(tag); if (factory == nullptr) { qWarning() << "No event class registered for XML tag" << tag; } else { event = factory(); } return event; } void XmlReplayEvent::writeTag(QXmlStreamWriter & xml) const { QDateTime time = m_time.toOffsetFromUtc(m_time.offsetFromUtc()); // force display of UTC offset #if QT_VERSION < QT_VERSION_CHECK(5,9,0) // TODO: Can we please deprecate support for Qt older than 5.9? QString timestamp = time.toString(Qt::ISODate); #else QString timestamp = time.toString(Qt::ISODateWithMs); #endif xml.writeAttribute("time", timestamp); // Call this event's overridable write method. write(xml); } QXmlStreamWriter & operator<<(QXmlStreamWriter & xml, const XmlReplayEvent & event) { xml.writeStartElement(event.tag()); event.writeTag(xml); xml.writeEndElement(); return xml; } QXmlStreamReader & operator>>(QXmlStreamReader & xml, XmlReplayEvent & event) { Q_ASSERT(xml.isStartElement() && xml.name() == event.tag()); QDateTime time; if (xml.attributes().hasAttribute("time")) { #if QT_VERSION < QT_VERSION_CHECK(5,9,0) // TODO: Can we please deprecate support for Qt older than 5.9? time = QDateTime::fromString(xml.attributes().value("time").toString(), Qt::ISODate); #else time = QDateTime::fromString(xml.attributes().value("time").toString(), Qt::ISODateWithMs); #endif } else { qWarning() << "Missing timestamp in" << xml.name() << "tag, using current time"; time = QDateTime::currentDateTime(); } event.m_time = time; // Call this event's overridable read method. event.read(xml); return xml; } XmlReplayEvent::operator QString() const { QString out; QXmlStreamWriter xml(&out); xml << *this; return out; } XmlReplayLock::XmlReplayLock(const QObject* obj, XmlReplay* replay) : m_target(obj), m_replay(replay) { if (m_replay) { // Prevent any triggered signal events from processing until the triggering lock is released. m_replay->lock(); } } XmlReplayLock::~XmlReplayLock() { if (m_replay) { m_replay->processPendingSignals(m_target); m_replay->unlock(); } } OSCAR-code-v1.3.1/oscar/SleepLib/xmlreplay.h000066400000000000000000000345141417327530600204740ustar00rootroot00000000000000/* XML event recording/replay * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef XMLREPLAY_H #define XMLREPLAY_H #include #include #include #include #include #include /* * XML recording base class * * While this can be used on its own via the public constructors, it is * typically used as a base class for a subclasses that handle specific * events. * * A single instance of this class can write a linear sequence of events to * XML, either to a string (for testing) or to a file (for production use). * * Sometimes, however, there is need for certain sequences to be treated as * separate, either due to multithreading (such recording as multiple * simultaneous connections), or in order to treat a certain excerpt (such * as data download that we might wish to archive) separately. * * These sequences are handled as "substreams" of the parent stream. The * parent stream will typically record a substream's open/close or start/ * stop along with its ID. The substream will be written to a separate XML * stream identified by that ID. Substreams are implemented as subclasses of * this base class. * * TODO: At the moment, only file-based substreams are supported. In theory * it should be possible to cache string-based substreams and then insert * them inline into the parent after the substream-close event is recorded. */ class XmlRecorder { public: static const QString TAG; // default tag if no subclass XmlRecorder(class QFile * file, const QString & tag = XmlRecorder::TAG); // record XML to the given file XmlRecorder(QString & string, const QString & tag = XmlRecorder::TAG); // record XML to the given string virtual ~XmlRecorder(); // write the epilogue and close the recorder XmlRecorder* closeSubstream(); // convenience function to close out a substream and return its parent inline QXmlStreamWriter & xml() { return *m_xml; } inline void lock() { m_mutex.lock(); } inline void unlock() { m_mutex.unlock(); } void flush(); protected: XmlRecorder(XmlRecorder* parent, const QString & id, const QString & tag); // constructor used by substreams QXmlStreamWriter* addSubstream(XmlRecorder* child, const QString & id); // initialize a child substream, used by above constructor const QString m_tag; // opening/closing tag for this instance QFile* m_file; // nullptr for non-file recordings QXmlStreamWriter* m_xml; // XML output stream QMutex m_mutex; // force one thread at a time to write to m_xml XmlRecorder* m_parent; // parent instance of a substream void prologue(); void epilogue(); }; /* * XML replay base class * * A single instance of this class caches events from a previously recorded * XML stream, either from a string (for testing) or from a file (for * production use). * * Unlike recording, the replay need not be strictly linear. In fact, the * implementation is designed to allow for limited reordering during replay, * so that minor changes to code should result in sensible replay until a * new recording can be made. * * There are two aspects to this reordering: * * First, events can be retrieved (and consumed) in any order, being * retrieved by type and ID (and then in order within that type and ID). * * Second, events that are flagged as random-access (see randomAccess below) * will cause the above retrieval to subsequently begin searching on or * after the random-access event's timestamp (except for other random-access * events, which are always searched from the beginning.) * * This allow non-stateful events to be replayed arbitrarily, and for * stateful events (such as commands sent to a device) to be approximated * (where subsequent data received matches the command sent). * * Furthermore, when events are triggered in the same order as they were * during recordering, the above reordering will have no effect, and the * original ordering will be replayed identically. * * See XmlRecorder above for a discussion of substreams. */ class XmlReplay { public: XmlReplay(class QFile * file, const QString & tag = XmlRecorder::TAG); // replay XML from the given file XmlReplay(QXmlStreamReader & xml, const QString & tag = XmlRecorder::TAG); // replay XML from the given stream virtual ~XmlReplay(); XmlReplay* closeSubstream(); // convenience function to close out a substream and return its parent //! \brief Retrieve next matching event of the given XmlReplayEvent subclass. template inline T* getNextEvent(const QString & id = "") { T* event = dynamic_cast(getNextEvent(T::TAG, id)); return event; } protected: XmlReplay(XmlReplay* parent, const QString & id, const QString & tag = XmlRecorder::TAG); // constructor used by substreams QXmlStreamReader* findSubstream(XmlReplay* child, const QString & id); // initialize a child substream, used by above constructor void deserialize(QXmlStreamReader & xml); void deserializeEvents(QXmlStreamReader & xml); class XmlReplayEvent* getNextEvent(const QString & type, const QString & id = ""); void seekToTime(const QDateTime & time); const QString m_tag; // opening/closing tag for this instance QFile* m_file; // nullptr for non-file replay QHash>> m_eventIndex; // type and ID-based index into the events, see discussion of reordering above QHash> m_indexPosition; // positions at which to begin searching the index, updated by random-access events QList m_events; // linear list of all events in their original order XmlReplayEvent* m_pendingSignal; // the signal (if any) that should be replayed as soon as the current event has been processed QMutex m_lock; // prevent signals from being dispatched while an event is being processed, see XmlReplayLock below XmlReplay* m_parent; // parent instance of a substream inline void lock() { m_lock.lock(); } inline void unlock() { m_lock.unlock(); } void processPendingSignals(const QObject* target); friend class XmlReplayLock; }; /* * XML replay event base class * * This class is used to represent a replayable event. An event is created * when performing any replayable action, and then recorded (via record()) * when appropriate. During replay, an event is retrieved from the XmlReplay * instance and its previously recorded result should be returned instead of * performing the original action. * * Subclasses are created as subclasses of the XmlReplayBase template (see * below), which handles their factory method and tag registration. * * Subclasses that should be retrieved by ID as well as type will need to * override id() to return the ID to use for indexing. * * Subclasses that represent signal events (rather than API calls) will need * to set their m_signal string to the name of the signal to be emitted, and * additionally override signal() if they need to pass parameters with the * signal. * * Subclasses that represent random-access events (see discussion above) * will need to override randomAccess() to return true. * * Subclasses whose XML contains raw hexadecimal data will need to override * usesData() to return true. Subclasses whose XML contains other data * (such as complex data types) will instead need to override read() and * write(). */ class XmlReplayEvent { public: XmlReplayEvent(); virtual ~XmlReplayEvent() = default; //! \brief Add the given key/value to the event. This will be written as an XML attribute in the order it added. void set(const QString & name, const QString & value); //! \brief Add the given key/integer to the event. This will be written as an XML attribute in the order it added. void set(const QString & name, qint64 value); //! \brief Add the raw data to the event. This will be written in hexadecimal as content of the event's XML tag. void setData(const char* data, qint64 length); //! \brief Get the value for the given key. QString get(const QString & name) const; //! \brief Get the raw data for this event. QByteArray getData() const; //! \brief True if there are no errors in this event, or false if the "error" attribute is set. inline bool ok() const { return m_values.contains("error") == false; } //! \brief Copy the result from the retrieved replay event (if any) into the current event. void copyIf(const XmlReplayEvent* other); //! \brief Record this event to the given XML recorder, doing nothing if the recorder is null. void record(XmlRecorder* xml) const; // Serialize this event to an XML stream. friend QXmlStreamWriter & operator<<(QXmlStreamWriter & xml, const XmlReplayEvent & event); // Deserialize this event's contents from an XML stream. The instance is first created via createInstance() based on the tag. friend QXmlStreamReader & operator>>(QXmlStreamReader & xml, XmlReplayEvent & event); // Write the tag's attributes and contents. void writeTag(QXmlStreamWriter & xml) const; //! \brief Return a string of this event as an XML tag. operator QString() const; // Subclassing support //! \brief Return the XML tag used for this event. Automatically generated for subclasses by template. virtual const QString & tag() const = 0; //! \brief Return the ID for this event, if applicable. Subclasses should override this if their events should be retrieved by ID. virtual const QString id() const { static const QString none(""); return none; } //! \brief True if this event represents a "random-access" event that should cause subsequent event searches to start after this event's timestamp. Subclasses that represent such a state change should override this method. virtual bool randomAccess() const { return false; } // Event subclass registration and instance creation typedef XmlReplayEvent* (*FactoryMethod)(); static bool registerClass(const QString & tag, FactoryMethod factory); static XmlReplayEvent* createInstance(const QString & tag); protected: static QHash & factories(); // registered subclass factory methods, arranged by XML tag //! \brief True this event contains raw data. Defaults to false, so subclasses that use raw data must override this. virtual bool usesData() const { return false; } //! \brief True if this event represents a signal event. Subclasses representing such events must set m_signal. inline bool isSignal() const { return m_signal != nullptr; } //! \brief Send a signal to the target object. Subclasses may override this to send signal arguments. virtual void signal(QObject* target); //! \brief Write any attributes or content needed specific to event. Subclasses may override this to support complex data types. virtual void write(QXmlStreamWriter & xml) const; //! \brief Read any attributes or content specific to this event. Subclasses may override this to support complex data types. virtual void read(QXmlStreamReader & xml); QDateTime m_time; // timestamp of event XmlReplayEvent* m_next; // next recorded event, used during replay to trigger signals that automatically fire after an event is processed const char* m_signal; // name of the signal to be emitted for this event, if any QHash m_values; // hash of key/value pairs for this event, written as attributes of the XML tag QList m_keys; // list of keys so that attributes will be written in the order they were set QString m_data; // hexademical string representing this event's raw data, written as contents of the XML tag // Copy the timestamp as well as the attributes. Used when creating substreams. void copy(const XmlReplayEvent & other); friend class XmlReplay; }; // Convenience template for serializing QLists to XML template QXmlStreamWriter & operator<<(QXmlStreamWriter & xml, const QList & list) { for (auto & item : list) { xml << item; } return xml; } // Convenience template for deserializing QLists from XML template QXmlStreamReader & operator>>(QXmlStreamReader & xml, QList & list) { list.clear(); while (xml.readNextStartElement()) { T item; xml >> item; list.append(item); } return xml; } /* * Intermediate parent class of concrete event subclasses. * * We use this extra CRTP templating so that concrete event subclasses * require as little code as possible: * * The subclass's tag and factory method are automatically generated by this * template. */ template class XmlReplayBase : public XmlReplayEvent { public: static const QString TAG; static const bool registered; virtual const QString & tag() const { return TAG; }; static XmlReplayEvent* createInstance() { Derived* instance = new Derived(); return static_cast(instance); } }; /* * Macro to define an XmlReplayEvent subclass's tag and automatically * register the subclass at global-initialization time, before main() */ #define REGISTER_XMLREPLAYEVENT(tag, type) \ template<> const QString XmlReplayBase::TAG = tag; \ template<> const bool XmlReplayBase::registered = XmlReplayEvent::registerClass(XmlReplayBase::TAG, XmlReplayBase::createInstance); /* * XML replay lock class * * An instance of this class should be created on the stack during any replayable * event. Exiting scope will release the lock, at which point any signals that * need to be replayed will be queued. * * Has no effect if events are not being replayed. */ class XmlReplayLock { public: //! \brief Temporarily lock the XML replay (if any) until exiting scope, at which point any pending signals will be sent to the specified object. XmlReplayLock(const QObject* obj, XmlReplay* replay); ~XmlReplayLock(); protected: const QObject* m_target; // target object to receive any pending signals XmlReplay* m_replay; // replay instance, or nullptr if not replaying }; #endif // XMLREPLAY_H OSCAR-code-v1.3.1/oscar/UpdaterWindow.ui000066400000000000000000000316351417327530600177430ustar00rootroot00000000000000 UpdaterWindow 0 0 589 416 OSCAR Updater :/icons/logo-sm.png:/icons/logo-sm.png 0 0 0 0 0 0 0 0 14 75 true A new version of $APP is available 100 100 :/icons/logo-lm.png true Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop Qt::Vertical 20 40 Version Information 0 Release Notes 0 0 0 0 0 Build Notes 0 0 0 0 0 0 0 QFrame::NoFrame QFrame::Plain true QFrame::Box QFrame::Sunken Maybe &Later Qt::Horizontal 40 20 &Upgrade Now 4 4 4 4 4 75 true Please wait while updates are downloaded and installed... 0 Updates 0 0 0 0 0 QAbstractItemView::NoEditTriggers true QAbstractItemView::NoSelection QAbstractItemView::SelectRows true false Component Version Size Progress Log 0 0 0 0 0 0 0 0 0 0 Downloading & Installing Updates Qt::Horizontal 40 20 &Finished OSCAR-code-v1.3.1/oscar/VERSION000066400000000000000000000002411417327530600156450ustar00rootroot00000000000000// Update the string below to set OSCAR's version and release status. // See https://semver.org/spec/v2.0.0.html for details on format. #define VERSION "1.3.1" OSCAR-code-v1.3.1/oscar/aboutdialog.cpp000066400000000000000000000127301417327530600176010ustar00rootroot00000000000000/* OSCAR AboutDialog Implementation * * Date created: 7/5/2018 * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the Source Directory. */ #include #include #include #include #include "version.h" #include "SleepLib/appsettings.h" #include "aboutdialog.h" #include "ui_aboutdialog.h" AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog) { ui->setupUi(this); ui->aboutText->setHtml(getAbout()); ui->creditsText->setHtml(getCredits()); ui->licenseText->setHtml(getLicense()); ui->relnotesText->setHtml(getRelnotes()); ui->versionLabel->setText(""); QString path = GetAppData(); // TODO: consider replacing gitrev below with a link or button to the System Information window QString text = /* gitrev + */ "

"+tr("Show data folder")+""; ui->infoLabel->setText(text); setWindowTitle(tr("About OSCAR %1").arg(getVersion().displayString())); setMinimumSize(QSize(400,400)); this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); connect(ui->closeButton, SIGNAL(clicked(bool)), this, SLOT(accept())); int idx=AppSetting->showAboutDialog(); if (idx<0) idx = 0; // start in about tab ui->tabWidget->setCurrentIndex(idx); } AboutDialog::~AboutDialog() { disconnect(ui->closeButton, SIGNAL(clicked(bool)), this, SLOT(accept())); delete ui; } /*************************************************** void AboutDialog::on_donateButton_clicked() { // QDesktopServices::openUrl(QUrl("http://sleepyhead.jedimark.net/donate.php")); QMessageBox::information(nullptr, STR_MessageBox_Information, tr("Donations are not yet implemented")); } ******************************************************/ QString AboutDialog::getFilename(QString name) { QString filename; QString language = AppSetting->language(); QString docRoot = appResourcePath() + "/Html/"; if (language == "en_US") { filename = docRoot+name+".html"; } else { filename = docRoot + name + "-" + language + ".html"; if ( ! QFile::exists(filename) ) filename = docRoot+name+".html"; } qDebug() << "Looking for " + filename; return filename; } QString transLink (QString text) { return text.replace("This page in other languages:", QObject::tr("This page in other languages:")); } QString AboutDialog::getAbout() { QString aboutFile = getFilename("about"); QFile clfile(aboutFile); QString text = tr("Sorry, could not locate About file."); if (clfile.open(QIODevice::ReadOnly)) { text = clfile.readAll(); text = transLink(text); } else qWarning() << "Could not open" << aboutFile << "for reading, error code" << clfile.error() << clfile.errorString(); // qDebug() << "Failed to open About file"; return text; } QString AboutDialog::getCredits() { QString creditsFile = getFilename("credits"); QFile clfile(creditsFile); QString text = tr("Sorry, could not locate Credits file."); if (clfile.open(QIODevice::ReadOnly)) { text = clfile.readAll(); text = transLink(text); } else { qWarning() << "Could not open" << creditsFile << "for reading, error code" << clfile.error() << clfile.errorString(); } return text; } QString AboutDialog::getRelnotes() { QString relNotesFile = getFilename("release_notes"); QFile clfile(relNotesFile); QString changeLog = tr("Sorry, could not locate Release Notes."); if (clfile.open(QIODevice::ReadOnly)) { //Todo, write XML parser and only show the latest.. //QTextStream ts(&clfile); changeLog = clfile.readAll(); } else qWarning() << "Could not open" << relNotesFile << "for reading, error code" << clfile.error() << clfile.errorString(); QString text = "" "" ""+tr("Release Notes")+"
" ""+tr("OSCAR %1").arg(getVersion())+"" "
"; if (getVersion().IsReleaseVersion() == false) { text += "

"+tr("Important:")+" " ""+tr("As this is a pre-release version, it is recommended that you back up your data folder manually before proceeding, because attempting to roll back later may break things.")+"


"; } text += changeLog; text += ""; text = transLink(text); return text; } QString AboutDialog::getLicense() { QString text; QString licenseFile = ":/docs/GPLv3-"+AppSetting->language(); if (!QFile::exists(licenseFile)) { ui->licenceLabel->setText(tr("To see if the license text is available in your language, see %1.").arg("https://www.gnu.org/licenses/translations.en.html")); ui->licenceLabel->setVisible(true); licenseFile = ":/docs/GPLv3-en_US"; } else { ui->licenceLabel->setVisible(false); } QFile file(licenseFile); if (file.open(QIODevice::ReadOnly)) { text = "
"+QString(file.readAll()).replace("\n","
")+"
"; } return text; } OSCAR-code-v1.3.1/oscar/aboutdialog.h000066400000000000000000000015701417327530600172460ustar00rootroot00000000000000/* OSCAR AboutDialog Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the Source Directory. */ #ifndef ABOUTDIALOG_H #define ABOUTDIALOG_H #include namespace Ui { class AboutDialog; } class AboutDialog : public QDialog { Q_OBJECT public: explicit AboutDialog(QWidget *parent = 0); ~AboutDialog(); /**************************************************** private slots: void on_donateButton_clicked(); ***************************************************/ private: QString getFilename(QString name); QString getAbout(); QString getCredits(); QString getLicense(); QString getRelnotes(); Ui::AboutDialog *ui; }; #endif // ABOUTDIALOG_H OSCAR-code-v1.3.1/oscar/aboutdialog.ui000066400000000000000000000151121417327530600174310ustar00rootroot00000000000000 AboutDialog 0 0 757 610 Dialog 10 QTabWidget::South QTabWidget::Triangular 0 &About true Release Notes true Credits true GPL License TextLabel true true 128 128 128 128 :/icons/logo-lg.png true 14 true OSCAR Qt::AlignCenter Qt::Horizontal [version string] Qt::AlignCenter Build Information Qt::AlignCenter true true Qt::Horizontal Qt::Vertical 20 40 Qt::Vertical QSizePolicy::Fixed 20 10 Qt::Horizontal Qt::Vertical QSizePolicy::Fixed 20 10 Close true OSCAR-code-v1.3.1/oscar/checkupdates.cpp000066400000000000000000000263121417327530600177530ustar00rootroot00000000000000/* CheckUpdates * * Copyright (c) 2011-2018 Mark Watkins * Copyright (c) 2020-2022 OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "checkupdates.h" #include "version.h" #include "mainwindow.h" extern MainWindow *mainwin; struct VersionInfo { QString group; // test or release QString platform; // All or Requested platform QString version; // version number QString urlInstaller; // URL for installer page QString notes; // any notes }; CheckUpdates::CheckUpdates(QWidget *parent) : QMainWindow(parent) { manager = new QNetworkAccessManager(this); } CheckUpdates::~CheckUpdates() { disconnect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinished(QNetworkReply *))); } QString platformStr() { static QString platform; #if defined(Q_OS_WIN64) platform="win64"; #elif defined(Q_OS_WIN) platform="win32"; #elif defined(Q_OS_MACOS) platform="macOS"; #elif defined(Q_OS_LINUX) platform="linux"; #else platform="unknown"; #endif return platform; } static const QString OSCAR_Version_File = "http://www.sleepfiles.com/OSCAR/versions/versions.xml"; static QString versionXML; /*! \fn readLocalVersions \brief Reads versions.xml from local OSCAR Data directory */ QString readLocalVersions() { // Connect and read XML file from disk QString filename = GetAppData() + "/versions.xml"; qDebug() << "Local version control file at" << filename; QFile file(filename); if(!file.open(QFile::ReadOnly | QFile::Text)) { qDebug() << "Cannot open local version control file" << filename << "-" << file.errorString() << "version check disabled"; return QString(); } QByteArray qba = file.readAll(); QFileDevice::FileError error = file.error(); file.close(); if (error != QFile::NoError) { qDebug() << "Error reading local version control file" << filename << "-" << file.errorString() << "version check disabled"; qDebug() << "versionXML" << versionXML; return QString(); } return QString(qba).toLower(); } /*! \fn GetVersionInfo \brief Extracts newer version info for this platform If returned versionInfo.version is empty, no newer version was found */ VersionInfo getVersionInfo (QString type, QString platform) { VersionInfo foundInfo; QXmlStreamReader reader(versionXML); if (reader.readNextStartElement()) { if (reader.name() == "oscar"){ //qDebug() << "expecting oscar, read" << reader.name(); while(reader.readNextStartElement()){ //qDebug() << "expecting group, read" << reader.name() << "with id" << reader.attributes().value("id").toString(); if(reader.name() == "group" && reader.attributes().hasAttribute("id")){ if (reader.attributes().value("id").toString() == type) { while(reader.readNextStartElement()) { //qDebug() << "expecting url or platform, read" << reader.name(); if (reader.name() == "installers") foundInfo.urlInstaller = reader.readElementText(); if (reader.name() == "platform") { QString plat=reader.attributes().value("id").toString(); //qDebug() << "expecting platform, read " << reader.name() << "with id" << reader.attributes().value("id").toString(); if ((plat == platform) || (plat == "all" && foundInfo.platform.length() == 0)) { foundInfo.platform = plat; while(reader.readNextStartElement()) { //qDebug() << "expecting version or notes, read" << reader.name(); if (reader.name() == "version") { QString fileVersion = reader.readElementText(); Version fv(fileVersion); if (!fv.IsValid()) { qWarning() << "Server file versions.xml contains an invalid version code" << fileVersion; } else { if (fv > getVersion()) foundInfo.version = fileVersion; // We found a more recent version } } else if (reader.name() == "notes") { foundInfo.notes = reader.readElementText(); } else reader.skipCurrentElement(); } } } } } else reader.skipCurrentElement(); } else reader.skipCurrentElement(); } } else { qWarning() << "Versions.xml file improperly formed --" << reader.errorString(); reader.raiseError(QObject::tr("New versions file improperly formed")); } } return foundInfo; } void CheckUpdates::compareVersions () { #ifndef NO_CHECKUPDATES // Get any more recent versions available VersionInfo releaseVersion = getVersionInfo ("release", platformStr()); VersionInfo testVersion = getVersionInfo ("test", platformStr()); msg = ""; if (!showTestVersion) testVersion.version = ""; if (testVersion.version.length() == 0 && releaseVersion.version.length() == 0) { if (showIfCurrent) { QString txt = getVersion().IsReleaseVersion()?QObject::tr("release"):QObject::tr("test version"); QString txt2 = QObject::tr("You are running the latest %1 of OSCAR").arg(txt); msg = txt2 + "

" + QObject::tr("You are running OSCAR %1").arg(getVersion()) + "

"; } } else { msg = QObject::tr("A more recent version of OSCAR is available"); msg += "

" + QObject::tr("You are running OSCAR %1").arg(getVersion()) + "

"; if (releaseVersion.version.length() > 0) { msg += "

" + QObject::tr("OSCAR %1 is available here.").arg(releaseVersion.version).arg(releaseVersion.urlInstaller) + "

"; } if (showTestVersion && (testVersion.version.length() > 0)) { msg += "

" + QObject::tr("Information about more recent test version %1 is available at %2").arg(testVersion.version).arg(testVersion.urlInstaller) + "

"; } } if (msg.length() > 0) { // Add elapsed time in test versions only // if (elapsedTime > 0 && !getVersion().IsReleaseVersion()) // msg += "

" + QString(QObject::tr("(Reading %1 took %2 seconds)")).arg("versions.xml").arg(elapsedTime) + "

"; msgIsReady = true; } AppSetting->setUpdatesLastChecked(QDateTime::currentDateTime()); return; #endif } void CheckUpdates::showMessage() { if (!msgIsReady) return; if (showIfCurrent) { if (checkingBox != nullptr) checkingBox->reset(); } QMessageBox msgBox; msgBox.setWindowTitle(QObject::tr("Check for OSCAR Updates")); msgBox.setTextFormat(Qt::RichText); msgBox.setText(msg); msgBox.exec(); msgIsReady = false; } void CheckUpdates::checkForUpdates(bool showWhenCurrent) { showIfCurrent = showWhenCurrent; showTestVersion = false; // If running a test version of OSCAR, try reading versions.xml from OSCAR_Data directory // and force display of any new test version if (!getVersion().IsReleaseVersion()) { showTestVersion = true; versionXML = readLocalVersions(); if (versionXML.length() > 0) { compareVersions(); elapsedTime = 0; checkingBox = nullptr; showMessage(); return; } } else { showTestVersion = AppSetting->allowEarlyUpdates(); } readTimer.start(); connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*))); manager->get(QNetworkRequest(QUrl(OSCAR_Version_File))); if (showIfCurrent) { checkingBox = new QProgressDialog (this); // checkingBox->setWindowModality(Qt::WindowModal); checkingBox->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); checkingBox->setWindowFlags(this->windowFlags() & ~Qt::WindowMinMaxButtonsHint); checkingBox->setLabelText(tr("Checking for newer OSCAR versions")); checkingBox->setMinimumDuration(500); checkingBox->setRange(0,0); checkingBox->setCancelButton(nullptr); checkingBox->setWindowTitle(getAppName()); checkingBox->exec(); } qDebug() << "Starting network request for" << OSCAR_Version_File; return; } void CheckUpdates::replyFinished(QNetworkReply *reply) { if (showIfCurrent) { if (checkingBox != nullptr) checkingBox->reset(); } if (reply->error() != QNetworkReply::NoError) { qWarning() << "Update Check Error:" << reply->errorString(); reply->deleteLater(); if (!showIfCurrent) { // for automatic checks, don't show anything return; } msg = QObject::tr("Unable to check for updates. Please try again later."); msgIsReady = true; } else { // qDebug() << reply->header(QNetworkRequest::ContentTypeHeader).toString(); // qDebug() << reply->header(QNetworkRequest::LastModifiedHeader).toDateTime().toString(); // qDebug() << reply->header(QNetworkRequest::ContentLengthHeader).toULongLong(); // qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // qDebug() << reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); versionXML = reply->readAll().toLower(); reply->deleteLater(); // Only calculate elapsed time for Help/Check for Updates // (Auto-update time would include profile opening time) if (showIfCurrent) { elapsedTime = readTimer.elapsed() / 1000.0; qDebug() << "Elapsed time to read versions.XML from web:" << elapsedTime << "seconds"; } else elapsedTime = 0; compareVersions(); } if (showIfCurrent) showMessage(); return; } OSCAR-code-v1.3.1/oscar/checkupdates.h000066400000000000000000000032511417327530600174150ustar00rootroot00000000000000/* Check for Updates * * Copyright (c) 2020-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef CHECKUPDATES_H #define CHECKUPDATES_H #include #include #include /*! \class CheckUpdates \brief Check-for-Updates Module for OSCAR This class handles the Check-for-Updates process in OSCAR: it does the network checks, parses the version.xml file, checks for any new updates, and advises the user if updates are available. */ class CheckUpdates : public QMainWindow { Q_OBJECT public: explicit CheckUpdates(QWidget *parent = 0); ~CheckUpdates(); //! Start the check void checkForUpdates(bool showWhenCurrent); //! See if running version is current and prepare message if not void compareVersions(); //! Show message to user, if it is available //! If shown, clear the "message ready" flag void showMessage(); protected slots: void replyFinished(QNetworkReply *reply); private: QNetworkAccessManager *manager; QTime readTimer; float elapsedTime; QString msg; // Message to show to user bool msgIsReady = false; // Message is ready to be displayed bool showIfCurrent = false; // show a message if running current release bool showTestVersion = false; // Show message if test version is available QProgressDialog * checkingBox;// Looking for updates message QNetworkReply *reply; }; #endif // CHECKUPDATES_H OSCAR-code-v1.3.1/oscar/common_gui.cpp000066400000000000000000000063051417327530600174440ustar00rootroot00000000000000/* Common GUI Functions Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include "Graphs/glcommon.h" // Flag Colors QColor COLOR_Hypopnea = Qt::blue; QColor COLOR_Obstructive = COLOR_Aqua; QColor COLOR_Apnea = Qt::darkGreen; QColor COLOR_CSR = COLOR_LightGreen; QColor COLOR_LargeLeak = COLOR_LightGray; QColor COLOR_Ramp = COLOR_LightBlue; QColor COLOR_ClearAirway = QColor("#b254cd"); QColor COLOR_RERA = COLOR_Gold; QColor COLOR_VibratorySnore = QColor("#ff4040"); QColor COLOR_FlowLimit = QColor("#404040"); QColor COLOR_SensAwake = COLOR_Gold; QColor COLOR_LeakFlag = QColor("#40c0c0"); // Qt::darkBlue; QColor COLOR_NRI = QColor("orange"); //COLOR_ClearAirway; QColor COLOR_ExP = Qt::darkCyan; QColor COLOR_PressurePulse = Qt::red; QColor COLOR_PulseChange = COLOR_LightGray; QColor COLOR_SPO2Drop = COLOR_LightBlue; QColor COLOR_UserFlag1 = QColor("#e0e0e0"); QColor COLOR_UserFlag2 = QColor("#c0c0e0"); // Chart Colors QColor COLOR_EPAP = Qt::blue; QColor COLOR_IPAP = Qt::red; QColor COLOR_IPAPLo = Qt::darkRed; QColor COLOR_IPAPHi = Qt::darkRed; QColor COLOR_Plethy = Qt::darkBlue; QColor COLOR_Pulse = Qt::red; QColor COLOR_SPO2 = Qt::blue; QColor COLOR_FlowRate = Qt::black; QColor COLOR_Pressure = Qt::darkGreen; QColor COLOR_RDI = COLOR_LightGreen; QColor COLOR_AHI = COLOR_LightGreen; QColor COLOR_Leak = COLOR_DarkMagenta; QColor COLOR_LeakTotal = COLOR_DarkYellow; QColor COLOR_MaxLeak = COLOR_DarkRed; QColor COLOR_Snore = COLOR_DarkGray; QColor COLOR_RespRate = COLOR_DarkBlue; QColor COLOR_MaskPressure = COLOR_Blue; QColor COLOR_PTB = COLOR_Gray; // Patient-Triggered Breathing QColor COLOR_MinuteVent = COLOR_Cyan; QColor COLOR_TgMV = COLOR_DarkCyan; QColor COLOR_TidalVolume = COLOR_Magenta; QColor COLOR_FLG = COLOR_DarkBlue; // Flow Limitation Graph QColor COLOR_IE = COLOR_DarkRed; // Inspiratory Expiratory Ratio QColor COLOR_Te = COLOR_DarkGreen; QColor COLOR_Ti = COLOR_DarkBlue; QColor COLOR_SleepStage = COLOR_Gray; //#include //#include //typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); //LPFN_ISWOW64PROCESS fnIsWow64Process; //BOOL IsWow64() //{ // BOOL bIsWow64 = FALSE; // //IsWow64Process is not available on all supported versions of Windows. // //Use GetModuleHandle to get a handle to the DLL that contains the function // //and GetProcAddress to get a pointer to the function if available. // fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( // GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); // if(NULL != fnIsWow64Process) // { // if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) // { // //handle error // } // } // return bIsWow64; //} OSCAR-code-v1.3.1/oscar/common_gui.h000066400000000000000000000071231417327530600171100ustar00rootroot00000000000000/* Common GUI Functions Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef COMMON_GUI_H #define COMMON_GUI_H #include #include // Internal graph identifiers -- must NOT be translated const QString STR_GRAPH_EventBreakdown = "EventBreakdown"; const QString STR_GRAPH_SleepFlags = "SF"; // aka Event Flags const QString STR_GRAPH_FlowRate = "FlowRate"; const QString STR_GRAPH_Pressure = "Pressure"; const QString STR_GRAPH_LeakRate = "Leak"; const QString STR_GRAPH_FlowLimitation = "FLG"; const QString STR_GRAPH_Snore = "Snore"; const QString STR_GRAPH_TidalVolume = "TidalVolume"; const QString STR_GRAPH_MaskPressure = "MaskPressure"; const QString STR_GRAPH_RespRate = "RespRate"; const QString STR_GRAPH_MinuteVent = "MinuteVent"; const QString STR_GRAPH_PTB = "PTB"; const QString STR_GRAPH_RespEvent = "RespEvent"; const QString STR_GRAPH_Ti = "Ti"; const QString STR_GRAPH_Te = "Te"; const QString STR_GRAPH_SleepStage = "SleepStage"; const QString STR_GRAPH_Inclination = "Inclination"; const QString STR_GRAPH_Orientation = "Orientation"; const QString STR_GRAPH_Motion = "Motion"; const QString STR_GRAPH_TestChan1 = "TestChan1"; const QString STR_GRAPH_TestChan2 = "TestChan2"; const QString STR_GRAPH_AHI = "AHI"; const QString STR_GRAPH_Weight = "Weight"; const QString STR_GRAPH_BMI = "BMI"; const QString STR_GRAPH_Zombie = "Zombie"; const QString STR_GRAPH_Sessions = "Sessions"; const QString STR_GRAPH_SessionTimes = "SessionTimes"; const QString STR_GRAPH_Usage = "Usage"; const QString STR_GRAPH_PeakAHI = "PeakAHI"; const QString STR_GRAPH_TAP = "TimeAtPressure"; const QString STR_GRAPH_Oxi_Pulse = "Pulse"; const QString STR_GRAPH_Oxi_SPO2 = "SPO2"; const QString STR_GRAPH_Oxi_Plethy = "Plethy"; const QString STR_GRAPH_Oxi_Perf = "Perf. Index"; const QString STR_GRAPH_Oxi_PulseChange = "PulseChange"; const QString STR_GRAPH_Oxi_SPO2Drop = "SPO2Drop"; //OXI_Pulse, OXI_SPO2, OXI_Perf, OXI_Plethy // Flag Colors extern QColor COLOR_Hypopnea; extern QColor COLOR_Obstructive; extern QColor COLOR_Apnea; extern QColor COLOR_CSR; extern QColor COLOR_LargeLeak; extern QColor COLOR_Ramp; extern QColor COLOR_ClearAirway; extern QColor COLOR_RERA; extern QColor COLOR_VibratorySnore; extern QColor COLOR_FlowLimit; extern QColor COLOR_SensAwake; extern QColor COLOR_LeakFlag; extern QColor COLOR_NRI; extern QColor COLOR_ExP; extern QColor COLOR_PressurePulse; extern QColor COLOR_PulseChange; extern QColor COLOR_SPO2Drop; extern QColor COLOR_UserFlag1; extern QColor COLOR_UserFlag2; // Chart Colors extern QColor COLOR_EPAP; extern QColor COLOR_IPAP; extern QColor COLOR_IPAPLo; extern QColor COLOR_IPAPHi; extern QColor COLOR_Plethy; extern QColor COLOR_Pulse; extern QColor COLOR_SPO2; extern QColor COLOR_FlowRate; extern QColor COLOR_Pressure; extern QColor COLOR_RDI; extern QColor COLOR_AHI; extern QColor COLOR_Leak; extern QColor COLOR_LeakTotal; extern QColor COLOR_MaxLeak; extern QColor COLOR_Snore; extern QColor COLOR_RespRate; extern QColor COLOR_MaskPressure; extern QColor COLOR_PTB; // Patient Triggered Breathing extern QColor COLOR_MinuteVent; extern QColor COLOR_TgMV; extern QColor COLOR_TidalVolume; extern QColor COLOR_FLG; // Flow Limitation Graph extern QColor COLOR_IE; // Inspiratory Expiratory Ratio extern QColor COLOR_Te; extern QColor COLOR_Ti; extern QColor COLOR_SleepStage; #endif // COMMON_GUI_H OSCAR-code-v1.3.1/oscar/cprogressbar.cpp000066400000000000000000000052531417327530600200050ustar00rootroot00000000000000/* OSCAR C[onditional] Progress Bar * * Copyright (C) 2019 Guy Scharf * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. * * The C[onditional] progress bar causes the progress bar window to show only if the task * is not half done in two seconds (number of seconds is variable). The CProgressBar can * thus be used in all situations in which a progress bar might be appropriate without * determining ahead of time whether the task will take long enough to warrant use of a progress bar. * * This is especially useful when even though the number of iterations is known, the time required * to perform each iteration is not known ahead of time. For example, reading summary files and * discovering that some need to be updated but others do not. */ #include #include "cprogressbar.h" CProgressBar::CProgressBar(QString title, QWidget * parent, long maxValue) { savedTitle = title; maxSteps = maxValue; progress = nullptr; showProgress = false; this->parent = parent; } void CProgressBar::start () { timeChecked = false; timer.start(); } void CProgressBar::setMaximum (long value) { maxSteps = value; } void CProgressBar::setTitle (QString title) { savedTitle = title; } void CProgressBar::setWidth (int width) { this->width = width; } void CProgressBar::add (long count) { numDone += count; // If timer not started, start it now. if (!timer.isValid()) timer.start(); // See if timer limit has passed if (!progress && !timeChecked && (timer.elapsed() > timerLimit)) { maxSteps -= numDone; // maxSteps now is number of steps remaining numDone = 1; // and we figure only one processed so far progress = new QProgressDialog(savedTitle, QString(), 0, maxSteps, parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint); progress->setWindowModality(Qt::WindowModal); progress->setValue(0); progress->setMinimumWidth(width); progress->show(); QCoreApplication::processEvents(); showProgress = true; } // Update progress bar if one is displayed and more than 1% different from last display if (showProgress) { int pctDone = (100 * numDone) / maxSteps; if (pctDone != lastPctDone) { lastPctDone = pctDone; progress->setValue(numDone); QCoreApplication::processEvents(); } } } void CProgressBar::close () { if (progress) { progress->setValue(maxSteps); } } OSCAR-code-v1.3.1/oscar/cprogressbar.h000066400000000000000000000030701417327530600174450ustar00rootroot00000000000000/* OSCAR C[onditional] Progress Bar * * Copyright (C) 2019 Guy Scharf * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. * * The C[onditional] progress bar causes the progress bar window to show only if the task * is not half done in two seconds (number of seconds is variable). The CProgressBar can * thus be used in all situations in which a progress bar might be appropriate without * determining ahead of time whether the task will take long enough to warrant use of a progress bar. * * This is especially useful when even though the number of iterations is known, the time required * to perform each iteration is not known ahead of time. For example, reading summary files and * discovering that some need to be updated but others do not. */ #ifndef CPROGRESSBAR_H #define CPROGRESSBAR_H #include #include class CProgressBar { public: CProgressBar(QString title, QWidget * parent, long maxValue); void setMaximum (long max); void add (long count); void setWidth (int width); void setTitle (QString title); void start (); void close (); private: bool showProgress = false; bool timeChecked = false; int lastPctDone = 0; long timerLimit = 2000; long maxSteps = 0; long numDone = 0; int width = 250; QString savedTitle; QWidget * parent = nullptr; QProgressDialog * progress = nullptr; QElapsedTimer timer; }; #endif // CPROGRESSBAR_H OSCAR-code-v1.3.1/oscar/csv.cpp000066400000000000000000000033711417327530600161030ustar00rootroot00000000000000/* OSCAR CSV Reader Implementation * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "csv.h" #include CSVReader::CSVReader(QIODevice & input, const QString & delim, const QString & comment) : m_stream(&input), m_delim(delim), m_comment(comment) { } void CSVReader::setFieldNames(QStringList & header) { m_field_names = header; } // This is a very simplistic reader that splits lines on the delimiter and truncates // lines after the comment sequence (if specified). It doesn't do any quote handling. // If that's ultimately necessary, either rewrite it or subclass it. // // For a public domain version that handles RFC 4180, see: // https://stackoverflow.com/questions/27318631/parsing-through-a-csv-file-in-qt/40229435#40229435 // bool CSVReader::readRow(QStringList & fields) { QString line; fields.clear(); // Read until the next non-empty/non-comment line. do { line = m_stream.readLine(); if (line.isNull()) { return false; } if (m_comment.isNull() == false) { line = line.section(m_comment, 0, 0); } } while (line.isEmpty()); fields = line.split(m_delim); return true; } bool CSVReader::readRow(QHash & row) { QStringList fields; row.clear(); if (!readRow(fields)) { return false; } if (fields.size() > m_field_names.size()) { qWarning() << "row has too many columns"; return false; } for (int i = 0; i < fields.size(); i++) { row[m_field_names.at(i)] = fields.at(i); } return true; } OSCAR-code-v1.3.1/oscar/csv.h000066400000000000000000000014201417327530600155410ustar00rootroot00000000000000/* OSCAR CSV Reader Implementation * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include class CSVReader { public: CSVReader(QIODevice & stream, const QString & delim=",", const QString & comment=QString()); virtual ~CSVReader() = default; QStringList readRow(); virtual bool readRow(QStringList & fields); // override this for more complicated processing void setFieldNames(QStringList & header); bool readRow(QHash & row); protected: QTextStream m_stream; QString m_delim; QString m_comment; QStringList m_field_names; }; OSCAR-code-v1.3.1/oscar/daily.cpp000066400000000000000000003134351417327530600164170ustar00rootroot00000000000000/* Daily Panel * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "daily.h" #include "ui_daily.h" #include "common_gui.h" #include "SleepLib/profiles.h" #include "SleepLib/session.h" #include "Graphs/graphdata_custom.h" #include "Graphs/gLineOverlay.h" #include "Graphs/gFlagsLine.h" #include "Graphs/gFooBar.h" #include "Graphs/gXAxis.h" #include "Graphs/gYAxis.h" #include "Graphs/gSegmentChart.h" #include "Graphs/gStatsLine.h" #include "Graphs/gdailysummary.h" #include "Graphs/MinutesAtPressure.h" extern MainWindow * mainwin; QString htmlLeftHeader; QString htmlLeftAHI; QString htmlLeftMachineInfo; QString htmlLeftSleepTime; QString htmlLeftIndices; QString htmlLeftPieChart = ""; QString htmlLeftNoHours = ""; QString htmlLeftStatistics; QString htmlLeftOximeter; QString htmlLeftMachineSettings; QString htmlLeftSessionInfo; QString htmlLeftFooter; extern ChannelID PRS1_PeakFlow; // This was Sean Stangl's idea.. but I couldn't apply that patch. inline QString channelInfo(ChannelID code) { return schema::channel[code].fullname()+"\n"+schema::channel[code].description()+"\n("+schema::channel[code].units()+")"; // return schema::channel[code].fullname()+"\n"+schema::channel[code].description() // + (schema::channel[code].units() != "0" ? "\n("+schema::channel[code].units()+")" : ""); } // Charts displayed on the Daily page are defined in the Daily::Daily constructor. They consist of some hard-coded charts and a table // of channel codes for which charts are generated. If the list of channel codes is changed, the graph order lists below will need to // be changed correspondingly. // // Note that "graph codes" are strings used to identify graphs and are not the same as "channel codes." The mapping between channel codes // and graph codes is found in schema.cpp. (What we here call 'graph cdoes' are called 'lookup codes' in schema.cpp.) // // // List here the graph codes in the order they are to be displayed. // Do NOT list a code twice, or Oscar will crash when the profile is closed! // // Standard graph order const QList standardGraphOrder = {STR_GRAPH_SleepFlags, STR_GRAPH_FlowRate, STR_GRAPH_Pressure, STR_GRAPH_LeakRate, STR_GRAPH_FlowLimitation, STR_GRAPH_Snore, STR_GRAPH_TidalVolume, STR_GRAPH_MaskPressure, STR_GRAPH_RespRate, STR_GRAPH_MinuteVent, STR_GRAPH_PTB, STR_GRAPH_RespEvent, STR_GRAPH_Ti, STR_GRAPH_Te, STR_GRAPH_SleepStage, STR_GRAPH_Inclination, STR_GRAPH_Orientation, STR_GRAPH_Motion, STR_GRAPH_TestChan1, STR_GRAPH_Oxi_Pulse, STR_GRAPH_Oxi_SPO2, STR_GRAPH_Oxi_Perf, STR_GRAPH_Oxi_Plethy, STR_GRAPH_AHI, STR_GRAPH_TAP }; // Advanced graph order const QList advancedGraphOrder = {STR_GRAPH_SleepFlags, STR_GRAPH_FlowRate, STR_GRAPH_MaskPressure, STR_GRAPH_TidalVolume, STR_GRAPH_MinuteVent, STR_GRAPH_Ti, STR_GRAPH_Te, STR_GRAPH_FlowLimitation, STR_GRAPH_Pressure, STR_GRAPH_LeakRate, STR_GRAPH_Snore, STR_GRAPH_RespRate, STR_GRAPH_PTB, STR_GRAPH_RespEvent, STR_GRAPH_SleepStage, STR_GRAPH_Inclination, STR_GRAPH_Orientation, STR_GRAPH_Motion, STR_GRAPH_TestChan1, STR_GRAPH_Oxi_Pulse, STR_GRAPH_Oxi_SPO2, STR_GRAPH_Oxi_Perf, STR_GRAPH_Oxi_Plethy, STR_GRAPH_AHI, STR_GRAPH_TAP }; // CPAP modes that should have Advanced graphs const QList useAdvancedGraphs = {MODE_ASV, MODE_ASV_VARIABLE_EPAP, MODE_AVAPS}; void Daily::setCalendarVisible(bool visible) { on_calButton_toggled(visible); } void Daily::setSidebarVisible(bool visible) { QList a; int panel_width = visible ? AppSetting->dailyPanelWidth() : 0; qDebug() << "Daily Left Panel Width is " << panel_width; a.push_back(panel_width); a.push_back(this->width() - panel_width); ui->splitter_2->setStretchFactor(1,1); ui->splitter_2->setSizes(a); ui->splitter_2->setStretchFactor(1,1); } Daily::Daily(QWidget *parent,gGraphView * shared) :QWidget(parent), ui(new Ui::Daily) { qDebug() << "Creating new Daily object"; ui->setupUi(this); ui->JournalNotesBold->setShortcut(QKeySequence::Bold); ui->JournalNotesItalic->setShortcut(QKeySequence::Italic); ui->JournalNotesUnderline->setShortcut(QKeySequence::Underline); // Remove Incomplete Extras Tab //ui->tabWidget->removeTab(3); ZombieMeterMoved=false; BookmarksChanged=false; lastcpapday=nullptr; setSidebarVisible(true); layout=new QHBoxLayout(); layout->setSpacing(0); layout->setMargin(0); layout->setContentsMargins(0,0,0,0); dateDisplay=new MyLabel(this); dateDisplay->setAlignment(Qt::AlignCenter); QFont font = dateDisplay->font(); font.setPointSizeF(font.pointSizeF()*1.3F); dateDisplay->setFont(font); QPalette palette = dateDisplay->palette(); palette.setColor(QPalette::Base, Qt::blue); dateDisplay->setPalette(palette); //dateDisplay->setTextFormat(Qt::RichText); ui->sessionBarLayout->addWidget(dateDisplay,1); // const bool sessbar_under_graphs=false; // if (sessbar_under_graphs) { // ui->sessionBarLayout->addWidget(sessbar,1); // } else { // ui->splitter->insertWidget(2,sessbar); // sessbar->setMaximumHeight(sessbar->height()); // sessbar->setMinimumHeight(sessbar->height()); // } ui->calNavWidget->setMaximumHeight(ui->calNavWidget->height()); ui->calNavWidget->setMinimumHeight(ui->calNavWidget->height()); QWidget *widget = new QWidget(ui->tabWidget); sessionbar = new SessionBar(widget); sessionbar->setMinimumHeight(25); sessionbar->setMouseTracking(true); connect(sessionbar, SIGNAL(sessionClicked(Session*)), this, SLOT(doToggleSession(Session*))); QVBoxLayout *layout2 = new QVBoxLayout(); layout2->setMargin(0); widget->setLayout(layout2); webView=new MyTextBrowser(widget); webView->setOpenLinks(false); layout2->insertWidget(0,webView, 1); layout2->insertWidget(1,sessionbar,0); // add the sessionbar after it. ui->tabWidget->insertTab(0, widget, QIcon(), tr("Details")); ui->graphFrame->setLayout(layout); //ui->graphMainArea->setLayout(layout); ui->graphMainArea->setAutoFillBackground(false); GraphView=new gGraphView(ui->graphFrame,shared); // qDebug() << "New GraphView object created in Daily"; // sleep(3); GraphView->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); GraphView->setEmptyImage(QPixmap(":/icons/logo-md.png")); snapGV=new gGraphView(GraphView); snapGV->setMinimumSize(172,172); snapGV->hideSplitter(); snapGV->hide(); scrollbar=new MyScrollBar(ui->graphFrame); scrollbar->setOrientation(Qt::Vertical); scrollbar->setSizePolicy(QSizePolicy::Maximum,QSizePolicy::Expanding); scrollbar->setMaximumWidth(20); ui->bookmarkTable->setColumnCount(2); ui->bookmarkTable->setColumnWidth(0,70); //ui->bookmarkTable->setEditTriggers(QAbstractItemView::SelectedClicked); //ui->bookmarkTable->setColumnHidden(2,true); //ui->bookmarkTable->setColumnHidden(3,true); GraphView->setScrollBar(scrollbar); layout->addWidget(GraphView,1); layout->addWidget(scrollbar,0); int default_height = AppSetting->graphHeight(); gGraph *GAHI = nullptr, // *TAP = nullptr, *SF = nullptr, *AHI = nullptr; // const QString STR_GRAPH_DailySummary = "DailySummary"; // gGraph * SG; // graphlist[STR_GRAPH_DailySummary] = SG = new gGraph(STR_GRAPH_DailySummary, GraphView, tr("Summary"), tr("Summary of this daily information"), default_height); // SG->AddLayer(new gLabelArea(nullptr),LayerLeft,gYAxis::Margin); // SG->AddLayer(new gDailySummary()); graphlist[STR_GRAPH_SleepFlags] = SF = new gGraph(STR_GRAPH_SleepFlags, GraphView, STR_TR_EventFlags, STR_TR_EventFlags, default_height); SF->setPinned(true); //============================================ // Create graphs from 'interesting' CPAP codes // // If this list of codes is changed, you must // also adjust the standard and advanced graph // order at the beginning of daily.cpp. //============================================ const ChannelID cpapcodes[] = { CPAP_FlowRate, CPAP_Pressure, CPAP_Leak, CPAP_FLG, CPAP_Snore, CPAP_TidalVolume, CPAP_MaskPressure, CPAP_RespRate, CPAP_MinuteVent, CPAP_PTB, PRS1_PeakFlow, CPAP_RespEvent, CPAP_Ti, CPAP_Te, /* CPAP_IE, */ ZEO_SleepStage, POS_Inclination, POS_Orientation, POS_Movement, CPAP_Test1 }; // Create graphs from the cpap code list int cpapsize = sizeof(cpapcodes) / sizeof(ChannelID); for (int i=0; i < cpapsize; ++i) { ChannelID code = cpapcodes[i]; graphlist[schema::channel[code].code()] = new gGraph(schema::channel[code].code(), GraphView, schema::channel[code].label(), channelInfo(code), default_height); // qDebug() << "Creating graph for code" << code << schema::channel[code].code(); } const ChannelID oximetercodes[] = { OXI_Pulse, OXI_SPO2, OXI_Perf, OXI_Plethy }; // Add graphs from the Oximeter code list int oxisize = sizeof(oximetercodes) / sizeof(ChannelID); //int oxigrp=p_profile->ExistsAndTrue("SyncOximetry") ? 0 : 1; // Contemplating killing this setting... for (int i=0; i < oxisize; ++i) { ChannelID code = oximetercodes[i]; graphlist[schema::channel[code].code()] = new gGraph(schema::channel[code].code(), GraphView, schema::channel[code].label(), channelInfo(code), default_height); } // Check for some impossible conditions if ( p_profile == nullptr ) { qDebug() << "In daily, p_profile is NULL"; return; } else if (p_profile->general == nullptr ) { qDebug() << "In daily, p_profile->general is NULL"; return; } // Decide whether we are using AHI or RDI and create graph for the one we are using if (p_profile->general->calculateRDI()) { AHI=new gGraph(STR_GRAPH_AHI, GraphView,STR_TR_RDI, channelInfo(CPAP_RDI), default_height); } else { AHI=new gGraph(STR_GRAPH_AHI, GraphView,STR_TR_AHI, channelInfo(CPAP_AHI), default_height); } graphlist[STR_GRAPH_AHI] = AHI; // Event breakdown graph graphlist[STR_GRAPH_EventBreakdown] = GAHI = new gGraph(STR_GRAPH_EventBreakdown, snapGV,tr("Breakdown"),tr("events"),172); gSegmentChart * evseg=new gSegmentChart(GST_Pie); evseg->AddSlice(CPAP_Hypopnea,QColor(0x40,0x40,0xff,0xff),STR_TR_H); evseg->AddSlice(CPAP_Apnea,QColor(0x20,0x80,0x20,0xff),STR_TR_UA); evseg->AddSlice(CPAP_Obstructive,QColor(0x40,0xaf,0xbf,0xff),STR_TR_OA); evseg->AddSlice(CPAP_ClearAirway,QColor(0xb2,0x54,0xcd,0xff),STR_TR_CA); evseg->AddSlice(CPAP_AllApnea,QColor(0x40,0xaf,0xbf,0xff),STR_TR_A); evseg->AddSlice(CPAP_RERA,QColor(0xff,0xff,0x80,0xff),STR_TR_RE); evseg->AddSlice(CPAP_NRI,QColor(0x00,0x80,0x40,0xff),STR_TR_NR); evseg->AddSlice(CPAP_FlowLimit,QColor(0x40,0x40,0x40,0xff),STR_TR_FL); evseg->AddSlice(CPAP_SensAwake,QColor(0x40,0xC0,0x40,0xff),STR_TR_SA); if (AppSetting->userEventPieChart()) { evseg->AddSlice(CPAP_UserFlag1,QColor(0xe0,0xe0,0xe0,0xff),tr("UF1")); evseg->AddSlice(CPAP_UserFlag2,QColor(0xc0,0xc0,0xe0,0xff),tr("UF2")); } GAHI->AddLayer(evseg); GAHI->setMargins(0,0,0,0); // Add event flags to the event flags graph gFlagsGroup *fg=new gFlagsGroup(); SF->AddLayer(fg); SF->setBlockZoom(true); SF->AddLayer(new gShadowArea()); SF->AddLayer(new gLabelArea(fg),LayerLeft,gYAxis::Margin); //SF->AddLayer(new gFooBar(),LayerBottom,0,1); SF->AddLayer(new gXAxis(COLOR_Text,false),LayerBottom,0,gXAxis::Margin); // Now take care of xgrid/yaxis labels for all graphs // The following list contains graphs that don't have standard xgrid/yaxis labels QStringList skipgraph; skipgraph.push_back(STR_GRAPH_EventBreakdown); skipgraph.push_back(STR_GRAPH_SleepFlags); // skipgraph.push_back(STR_GRAPH_DailySummary); skipgraph.push_back(STR_GRAPH_TAP); QHash::iterator it; for (it = graphlist.begin(); it != graphlist.end(); ++it) { if (skipgraph.contains(it.key())) continue; it.value()->AddLayer(new gXGrid()); } gLineChart *l; l=new gLineChart(CPAP_FlowRate,false,false); gGraph *FRW = graphlist[schema::channel[CPAP_FlowRate].code()]; // Then the graph itself FRW->AddLayer(l); // FRW->AddLayer(AddOXI(new gLineOverlayBar(OXI_SPO2Drop, COLOR_SPO2Drop, STR_TR_O2))); bool square=AppSetting->squareWavePlots(); gLineChart *pc=new gLineChart(CPAP_Pressure, square); graphlist[schema::channel[CPAP_Pressure].code()]->AddLayer(pc); // graphlist[schema::channel[CPAP_Pressure].code()]->AddLayer(AddCPAP(new gLineOverlayBar(CPAP_Ramp, COLOR_Ramp, schema::channel[CPAP_Ramp].label(), FT_Span))); pc->addPlot(CPAP_EPAP, square); pc->addPlot(CPAP_IPAPLo, square); pc->addPlot(CPAP_IPAP, square); pc->addPlot(CPAP_IPAPHi, square); pc->addPlot(CPAP_PressureSet, false); pc->addPlot(CPAP_EPAPSet, false); pc->addPlot(CPAP_IPAPSet, false); // Create Timea at Pressure graph gGraph * TAP2; graphlist[STR_GRAPH_TAP] = TAP2 = new gGraph(STR_GRAPH_TAP, GraphView, tr("Time at Pressure"), tr("Time at Pressure"), default_height); MinutesAtPressure * map; TAP2->AddLayer(map = new MinutesAtPressure()); TAP2->AddLayer(new gLabelArea(map),LayerLeft,gYAxis::Margin); TAP2->AddLayer(new gXAxisPressure(),LayerBottom,gXAxisPressure::Margin); TAP2->setBlockSelect(true); // Fill in the AHI graph if (p_profile->general->calculateRDI()) { AHI->AddLayer(new gLineChart(CPAP_RDI, square)); // AHI->AddLayer(AddCPAP(new AHIChart(QColor("#37a24b")))); } else { AHI->AddLayer(new gLineChart(CPAP_AHI, square)); } // this is class wide because the leak redline can be reset in preferences.. // Better way would be having a search for linechart layers in graphlist[...] gLineChart *leakchart=new gLineChart(CPAP_Leak, square); // graphlist[schema::channel[CPAP_Leak].code()]->AddLayer(AddCPAP(new gLineOverlayBar(CPAP_LargeLeak, COLOR_LargeLeak, STR_TR_LL, FT_Span))); leakchart->addPlot(CPAP_LeakTotal, square); leakchart->addPlot(CPAP_MaxLeak, square); // schema::channel[CPAP_Leak].setUpperThresholdColor(Qt::red); // schema::channel[CPAP_Leak].setLowerThresholdColor(Qt::green); graphlist[schema::channel[CPAP_Leak].code()]->AddLayer(leakchart); //LEAK->AddLayer(AddCPAP(new gLineChart(CPAP_Leak, COLOR_Leak,square))); //LEAK->AddLayer(AddCPAP(new gLineChart(CPAP_MaxLeak, COLOR_MaxLeak,square))); graphlist[schema::channel[CPAP_Snore].code()]->AddLayer(new gLineChart(CPAP_Snore, true)); graphlist[schema::channel[CPAP_PTB].code()]->AddLayer(new gLineChart(CPAP_PTB, square)); graphlist[schema::channel[PRS1_PeakFlow].code()]->AddLayer(new gLineChart(PRS1_PeakFlow, square)); graphlist[schema::channel[CPAP_Test1].code()]->AddLayer(new gLineChart(CPAP_Test1, square)); //graphlist[schema::channel[CPAP_Test2].code()]->AddLayer(new gLineChart(CPAP_Test2, square)); gLineChart *lc = nullptr; graphlist[schema::channel[CPAP_MaskPressure].code()]->AddLayer(new gLineChart(CPAP_MaskPressure, false)); graphlist[schema::channel[CPAP_RespRate].code()]->AddLayer(lc=new gLineChart(CPAP_RespRate, square)); graphlist[schema::channel[POS_Inclination].code()]->AddLayer(new gLineChart(POS_Inclination)); graphlist[schema::channel[POS_Orientation].code()]->AddLayer(new gLineChart(POS_Orientation)); graphlist[schema::channel[POS_Movement].code()]->AddLayer(new gLineChart(POS_Movement)); graphlist[schema::channel[CPAP_MinuteVent].code()]->AddLayer(lc=new gLineChart(CPAP_MinuteVent, square)); lc->addPlot(CPAP_TgMV, square); graphlist[schema::channel[CPAP_TidalVolume].code()]->AddLayer(lc=new gLineChart(CPAP_TidalVolume, square)); //lc->addPlot(CPAP_Test2,COLOR_DarkYellow,square); //graphlist[schema::channel[CPAP_TidalVolume].code()]->AddLayer(AddCPAP(new gLineChart("TidalVolume2", square))); graphlist[schema::channel[CPAP_FLG].code()]->AddLayer(new gLineChart(CPAP_FLG, true)); //graphlist[schema::channel[CPAP_RespiratoryEvent].code()]->AddLayer(AddCPAP(new gLineChart(CPAP_RespiratoryEvent, true))); // graphlist[schema::channel[CPAP_IE].code()]->AddLayer(lc=new gLineChart(CPAP_IE, false)); // this should be inverse of supplied value graphlist[schema::channel[CPAP_Te].code()]->AddLayer(lc=new gLineChart(CPAP_Te, false)); graphlist[schema::channel[CPAP_Ti].code()]->AddLayer(lc=new gLineChart(CPAP_Ti, false)); //lc->addPlot(CPAP_Test2,COLOR:DarkYellow,square); graphlist[schema::channel[ZEO_SleepStage].code()]->AddLayer(new gLineChart(ZEO_SleepStage, true)); // gLineOverlaySummary *los1=new gLineOverlaySummary(STR_UNIT_EventsPerHour,5,-4); // gLineOverlaySummary *los2=new gLineOverlaySummary(STR_UNIT_EventsPerHour,5,-4); // graphlist[schema::channel[OXI_Pulse].code()]->AddLayer(AddOXI(los1->add(new gLineOverlayBar(OXI_PulseChange, COLOR_PulseChange, STR_TR_PC,FT_Span)))); // graphlist[schema::channel[OXI_Pulse].code()]->AddLayer(AddOXI(los1)); // graphlist[schema::channel[OXI_SPO2].code()]->AddLayer(AddOXI(los2->add(new gLineOverlayBar(OXI_SPO2Drop, COLOR_SPO2Drop, STR_TR_O2,FT_Span)))); // graphlist[schema::channel[OXI_SPO2].code()]->AddLayer(AddOXI(los2)); graphlist[schema::channel[OXI_Pulse].code()]->AddLayer(new gLineChart(OXI_Pulse, square)); graphlist[schema::channel[OXI_SPO2].code()]->AddLayer(new gLineChart(OXI_SPO2, true)); graphlist[schema::channel[OXI_Perf].code()]->AddLayer(new gLineChart(OXI_Perf, false)); graphlist[schema::channel[OXI_Plethy].code()]->AddLayer(new gLineChart(OXI_Plethy, false)); // Fix me // gLineOverlaySummary *los3=new gLineOverlaySummary(STR_UNIT_EventsPerHour,5,-4); // graphlist["INTPULSE"]->AddLayer(AddCPAP(los3->add(new gLineOverlayBar(OXI_PulseChange, COLOR_PulseChange, STR_TR_PC,FT_Span)))); // graphlist["INTPULSE"]->AddLayer(AddCPAP(los3)); // graphlist["INTPULSE"]->AddLayer(AddCPAP(new gLineChart(OXI_Pulse, square))); // gLineOverlaySummary *los4=new gLineOverlaySummary(STR_UNIT_EventsPerHour,5,-4); // graphlist["INTSPO2"]->AddLayer(AddCPAP(los4->add(new gLineOverlayBar(OXI_SPO2Drop, COLOR_SPO2Drop, STR_TR_O2,FT_Span)))); // graphlist["INTSPO2"]->AddLayer(AddCPAP(los4)); // graphlist["INTSPO2"]->AddLayer(AddCPAP(new gLineChart(OXI_SPO2, true))); graphlist[schema::channel[CPAP_PTB].code()]->setForceMaxY(100); graphlist[schema::channel[OXI_SPO2].code()]->setForceMaxY(100); for (it = graphlist.begin(); it != graphlist.end(); ++it) { if (skipgraph.contains(it.key())) continue; it.value()->AddLayer(new gYAxis(),LayerLeft,gYAxis::Margin); it.value()->AddLayer(new gXAxis(),LayerBottom,0,gXAxis::Margin); } if (p_profile->cpap->showLeakRedline()) { schema::channel[CPAP_Leak].setUpperThreshold(p_profile->cpap->leakRedline()); } else { schema::channel[CPAP_Leak].setUpperThreshold(0); // switch it off } layout->layout(); QTextCharFormat format = ui->calendar->weekdayTextFormat(Qt::Saturday); format.setForeground(QBrush(COLOR_Black, Qt::SolidPattern)); ui->calendar->setWeekdayTextFormat(Qt::Saturday, format); ui->calendar->setWeekdayTextFormat(Qt::Sunday, format); Qt::DayOfWeek dow=firstDayOfWeekFromLocale(); ui->calendar->setFirstDayOfWeek(dow); ui->tabWidget->setCurrentWidget(widget); connect(webView,SIGNAL(anchorClicked(QUrl)),this,SLOT(Link_clicked(QUrl))); int ews=p_profile->general->eventWindowSize(); ui->evViewSlider->setValue(ews); ui->evViewLCD->display(ews); icon_on=new QIcon(":/icons/session-on.png"); icon_off=new QIcon(":/icons/session-off.png"); icon_up_down=new QIcon(":/icons/up-down.png"); icon_warning=new QIcon(":/icons/warning.png"); ui->splitter->setVisible(false); if (p_profile->general->unitSystem()==US_English) { ui->weightSpinBox->setSuffix(STR_UNIT_POUND); ui->weightSpinBox->setDecimals(0); ui->ouncesSpinBox->setVisible(true); ui->ouncesSpinBox->setSuffix(STR_UNIT_OUNCE); } else { ui->ouncesSpinBox->setVisible(false); ui->weightSpinBox->setDecimals(1); ui->weightSpinBox->setSuffix(STR_UNIT_KG); } GraphView->setEmptyImage(QPixmap(":/icons/logo-md.png")); GraphView->setEmptyText(STR_Empty_NoData); previous_date=QDate(); ui->calButton->setChecked(AppSetting->calendarVisible() ? true : false); on_calButton_toggled(AppSetting->calendarVisible()); GraphView->resetLayout(); GraphView->SaveDefaultSettings(); GraphView->LoadSettings("Daily"); connect(GraphView, SIGNAL(updateCurrentTime(double)), this, SLOT(on_LineCursorUpdate(double))); connect(GraphView, SIGNAL(updateRange(double,double)), this, SLOT(on_RangeUpdate(double,double))); connect(GraphView, SIGNAL(GraphsChanged()), this, SLOT(updateGraphCombo())); // Watch for focusOut events on the JournalNotes widget ui->JournalNotes->installEventFilter(this); // qDebug() << "Finished making new Daily object"; // sleep(3); } Daily::~Daily() { disconnect(GraphView, SIGNAL(updateCurrentTime(double)), this, SLOT(on_LineCursorUpdate(double))); disconnect(GraphView, SIGNAL(updateRange(double,double)), this, SLOT(on_RangeUpdate(double,double))); disconnect(GraphView, SIGNAL(GraphsChanged()), this, SLOT(updateGraphCombo())); disconnect(sessionbar, SIGNAL(sessionClicked(Session*)), this, SLOT(doToggleSession(Session*))); disconnect(webView,SIGNAL(anchorClicked(QUrl)),this,SLOT(Link_clicked(QUrl))); ui->JournalNotes->removeEventFilter(this); if (previous_date.isValid()) { Unload(previous_date); } // Save graph orders and pin status, etc... GraphView->SaveSettings("Daily"); delete ui; delete icon_on; delete icon_off; } void Daily::showEvent(QShowEvent *) { // qDebug() << "Start showEvent in Daily object"; // sleep(3); RedrawGraphs(); // qDebug() << "Finished showEvent Daily object"; // sleep(3); } void Daily::doToggleSession(Session * sess) { sess->setEnabled(!sess->enabled()); LoadDate(previous_date); mainwin->getOverview()->graphView()->dataChanged(); } void Daily::Link_clicked(const QUrl &url) { QString code=url.toString().section("=",0,0).toLower(); QString data=url.toString().section("=",1); int sid=data.toInt(); Day *day=nullptr; if (code=="togglecpapsession") { // Enable/Disable CPAP session day=p_profile->GetDay(previous_date,MT_CPAP); if (!day) return; Session *sess=day->find(sid, MT_CPAP); if (!sess) return; // int i=webView->page()->mainFrame()->scrollBarMaximum(Qt::Vertical)-webView->page()->mainFrame()->scrollBarValue(Qt::Vertical); sess->setEnabled(!sess->enabled()); // Reload day LoadDate(previous_date); // webView->page()->mainFrame()->setScrollBarValue(Qt::Vertical, webView->page()->mainFrame()->scrollBarMaximum(Qt::Vertical)-i); } else if (code=="toggleoxisession") { // Enable/Disable Oximetry session day=p_profile->GetDay(previous_date,MT_OXIMETER); if (!day) return; Session *sess=day->find(sid, MT_OXIMETER); if (!sess) return; // int i=webView->page()->mainFrame()->scrollBarMaximum(Qt::Vertical)-webView->page()->mainFrame()->scrollBarValue(Qt::Vertical); sess->setEnabled(!sess->enabled()); // Reload day LoadDate(previous_date); // webView->page()->mainFrame()->setScrollBarValue(Qt::Vertical, webView->page()->mainFrame()->scrollBarMaximum(Qt::Vertical)-i); } else if (code=="togglestagesession") { // Enable/Disable Sleep Stage session day=p_profile->GetDay(previous_date,MT_SLEEPSTAGE); if (!day) return; Session *sess=day->find(sid, MT_SLEEPSTAGE); if (!sess) return; sess->setEnabled(!sess->enabled()); LoadDate(previous_date); } else if (code=="togglepositionsession") { // Enable/Disable Position session day=p_profile->GetDay(previous_date,MT_POSITION); if (!day) return; Session *sess=day->find(sid, MT_POSITION); if (!sess) return; sess->setEnabled(!sess->enabled()); LoadDate(previous_date); } else if (code=="cpap") { day=p_profile->GetDay(previous_date,MT_CPAP); if (day) { Session *sess=day->machine(MT_CPAP)->sessionlist[sid]; if (sess && sess->enabled()) { GraphView->SetXBounds(sess->first(),sess->last()); } } } else if (code=="oxi") { day=p_profile->GetDay(previous_date,MT_OXIMETER); if (day) { Session *sess=day->machine(MT_OXIMETER)->sessionlist[sid]; if (sess && sess->enabled()) { GraphView->SetXBounds(sess->first(),sess->last()); } } } else if (code=="event") { QList list=ui->treeWidget->findItems(schema::channel[sid].fullname(),Qt::MatchContains); if (list.size()>0) { ui->treeWidget->collapseAll(); ui->treeWidget->expandItem(list.at(0)); QTreeWidgetItem *wi=list.at(0)->child(0); ui->treeWidget->setCurrentItem(wi); ui->tabWidget->setCurrentIndex(1); } else { mainwin->Notify(tr("No %1 events are recorded this day").arg(schema::channel[sid].fullname()),"",1500); } } else if (code=="graph") { qDebug() << "Select graph " << data; } else { qDebug() << "Clicked on" << code << data; } } void Daily::ReloadGraphs() { // qDebug() << "Start ReloadGraphs Daily object"; // sleep(3); GraphView->setDay(nullptr); ui->splitter->setVisible(true); QDate d; if (previous_date.isValid()) { d=previous_date; //Unload(d); } QDate lastcpap = p_profile->LastDay(MT_CPAP); QDate lastoxi = p_profile->LastDay(MT_OXIMETER); d = qMax(lastcpap, lastoxi); if (!d.isValid()) { d=ui->calendar->selectedDate(); } on_calendar_currentPageChanged(d.year(),d.month()); // this fires a signal which unloads the old and loads the new ui->calendar->blockSignals(true); ui->calendar->setSelectedDate(d); ui->calendar->blockSignals(false); Load(d); ui->calButton->setText(ui->calendar->selectedDate().toString(MedDateFormat)); graphView()->redraw(); // qDebug() << "Finished ReloadGraphs in Daily object"; // sleep(3); } void Daily::updateLeftSidebar() { if (webView && !htmlLeftHeader.isEmpty()) webView->setHtml(getLeftSidebar(true)); } void Daily::hideSpaceHogs() { if (AppSetting->calendarVisible()) { ui->calendarFrame->setVisible(false); } if (AppSetting->showPieChart()) { webView->setHtml(getLeftSidebar(false)); } } void Daily::showSpaceHogs() { if (AppSetting->calendarVisible()) { ui->calendarFrame->setVisible(true); } if (AppSetting->showPieChart()) { webView->setHtml(getLeftSidebar(true)); } } void Daily::on_calendar_currentPageChanged(int year, int month) { QDate d(year,month,1); int dom=d.daysInMonth(); for (int i=1;i<=dom;i++) { d=QDate(year,month,i); this->UpdateCalendarDay(d); } } void Daily::UpdateEventsTree(QTreeWidget *tree,Day *day) { tree->clear(); if (!day) return; tree->setColumnCount(1); // 1 visible common.. (1 hidden) QTreeWidgetItem *root=nullptr; QHash mcroot; QHash mccnt; int total_events=0; qint64 drift=0, clockdrift=p_profile->cpap->clockDrift()*1000L; quint32 chantype = schema::FLAG | schema::SPAN | schema::MINOR_FLAG; if (p_profile->general->showUnknownFlags()) chantype |= schema::UNKNOWN; QList chans = day->getSortedMachineChannels(chantype); // Go through all the enabled sessions of the day for (QList::iterator s=day->begin();s!=day->end();++s) { Session * sess = *s; if (!sess->enabled()) continue; // For each session, go through all the channels QHash >::iterator m; for (int c=0; c < chans.size(); ++c) { ChannelID code = chans.at(c); m = sess->eventlist.find(code); if (m == sess->eventlist.end()) continue; drift=(sess->type() == MT_CPAP) ? clockdrift : 0; // Prepare title for this code, if there are any events QTreeWidgetItem *mcr; if (mcroot.find(code)==mcroot.end()) { int cnt=day->count(code); if (!cnt) continue; // If no events than don't bother showing.. total_events+=cnt; QString st=schema::channel[code].fullname(); if (st.isEmpty()) { st=QString("Fixme %1").arg(code); } st+=" "; if (cnt==1) st+=tr("%1 event").arg(cnt); else st+=tr("%1 events").arg(cnt); QStringList l(st); l.append(""); mcroot[code]=mcr=new QTreeWidgetItem(root,l); mccnt[code]=0; } else { mcr=mcroot[code]; } // number of digits required for count depends on total for day int numDigits = ceil(log10(day->count(code)+1)); // Now we go through the event list for the *session* (not for the day) for (int z=0;zraw(o) > 0) s += QString(" (%3)").arg(m.value()[z]->raw(o)); a.append(s); QTreeWidgetItem *item=new QTreeWidgetItem(a); item->setData(0,Qt::UserRole,t); mcr->addChild(item); } } } } int cnt=0; for (QHash::iterator m=mcroot.begin();m!=mcroot.end();m++) { tree->insertTopLevelItem(cnt++,m.value()); } if (day->hasMachine(MT_CPAP) || day->hasMachine(MT_OXIMETER) || day->hasMachine(MT_POSITION)) { QTreeWidgetItem * start = new QTreeWidgetItem(QStringList(tr("Session Start Times"))); QTreeWidgetItem * end = new QTreeWidgetItem(QStringList(tr("Session End Times"))); tree->insertTopLevelItem(cnt++ , start); tree->insertTopLevelItem(cnt++ , end); for (QList::iterator s=day->begin(); s!=day->end(); ++s) { QDateTime st = QDateTime::fromMSecsSinceEpoch((*s)->first()); // Localtime QDateTime et = QDateTime::fromMSecsSinceEpoch((*s)->last()); // Localtime QTreeWidgetItem * item = new QTreeWidgetItem(QStringList(st.toString("HH:mm:ss"))); item->setData(0,Qt::UserRole, (*s)->first()); start->addChild(item); item = new QTreeWidgetItem(QStringList(et.toString("HH:mm:ss"))); item->setData(0,Qt::UserRole, (*s)->last()); end->addChild(item); } } //tree->insertTopLevelItem(cnt++,new QTreeWidgetItem(QStringList("[Total Events ("+QString::number(total_events)+")]"))); tree->sortByColumn(0,Qt::AscendingOrder); //tree->expandAll(); } void Daily::UpdateCalendarDay(QDate date) { QTextCharFormat nodata; QTextCharFormat cpaponly; QTextCharFormat cpapjour; QTextCharFormat oxiday; QTextCharFormat oxicpap; QTextCharFormat jourday; QTextCharFormat stageday; cpaponly.setForeground(QBrush(COLOR_Blue, Qt::SolidPattern)); cpaponly.setFontWeight(QFont::Normal); cpapjour.setForeground(QBrush(COLOR_Blue, Qt::SolidPattern)); cpapjour.setFontWeight(QFont::Bold); // cpapjour.setFontUnderline(true); oxiday.setForeground(QBrush(COLOR_Red, Qt::SolidPattern)); oxiday.setFontWeight(QFont::Normal); oxicpap.setForeground(QBrush(COLOR_Red, Qt::SolidPattern)); oxicpap.setFontWeight(QFont::Bold); stageday.setForeground(QBrush(COLOR_Magenta, Qt::SolidPattern)); stageday.setFontWeight(QFont::Bold); jourday.setForeground(QBrush(COLOR_DarkYellow, Qt::SolidPattern)); jourday.setFontWeight(QFont::Bold); nodata.setForeground(QBrush(COLOR_Black, Qt::SolidPattern)); nodata.setFontWeight(QFont::Normal); bool hascpap = p_profile->FindDay(date, MT_CPAP)!=nullptr; bool hasoxi = p_profile->FindDay(date, MT_OXIMETER)!=nullptr; bool hasjournal = p_profile->FindDay(date, MT_JOURNAL)!=nullptr; bool hasstage = p_profile->FindDay(date, MT_SLEEPSTAGE)!=nullptr; bool haspos = p_profile->FindDay(date, MT_POSITION)!=nullptr; if (hascpap) { if (hasoxi) { ui->calendar->setDateTextFormat(date, oxicpap); } else if (hasjournal) { ui->calendar->setDateTextFormat(date, cpapjour); } else if (hasstage || haspos) { ui->calendar->setDateTextFormat(date, stageday); } else { ui->calendar->setDateTextFormat(date, cpaponly); } } else if (hasoxi) { ui->calendar->setDateTextFormat(date, oxiday); } else if (hasjournal) { ui->calendar->setDateTextFormat(date, jourday); } else if (hasstage) { ui->calendar->setDateTextFormat(date, oxiday); } else if (haspos) { ui->calendar->setDateTextFormat(date, oxiday); } else { ui->calendar->setDateTextFormat(date, nodata); } // if (hasjournal) { // ui->calendar->setDateTextFormat(date, cpapjour); // } ui->calendar->setHorizontalHeaderFormat(QCalendarWidget::ShortDayNames); } void Daily::LoadDate(QDate date) { if (!date.isValid()) { qDebug() << "LoadDate called with invalid date"; return; } ui->calendar->blockSignals(true); if (date.month()!=previous_date.month()) { on_calendar_currentPageChanged(date.year(),date.month()); } ui->calendar->setSelectedDate(date); ui->calendar->blockSignals(false); on_calendar_selectionChanged(); } void Daily::on_calendar_selectionChanged() { QTimer::singleShot(0, this, SLOT(on_ReloadDay())); } void Daily::on_ReloadDay() { static volatile bool inReload = false; if (inReload) { qDebug() << "attempt to renter on_ReloadDay()"; } inReload = true; graphView()->releaseKeyboard(); QTime time; time_t unload_time, load_time, other_time; time.start(); this->setCursor(Qt::BusyCursor); if (previous_date.isValid()) { // GraphView->fadeOut(); Unload(previous_date); } unload_time=time.restart(); //bool fadedir=previous_date < ui->calendar->selectedDate(); ZombieMeterMoved=false; Load(ui->calendar->selectedDate()); load_time=time.restart(); //GraphView->fadeIn(fadedir); GraphView->redraw(); ui->calButton->setText(ui->calendar->selectedDate().toString(MedDateFormat)); ui->calendar->setFocus(Qt::ActiveWindowFocusReason); if (p_profile->general->unitSystem()==US_English) { ui->weightSpinBox->setSuffix(STR_UNIT_POUND); ui->weightSpinBox->setDecimals(0); ui->ouncesSpinBox->setVisible(true); ui->ouncesSpinBox->setSuffix(STR_UNIT_OUNCE); } else { ui->ouncesSpinBox->setVisible(false); ui->weightSpinBox->setDecimals(1); ui->weightSpinBox->setSuffix(STR_UNIT_KG); } this->setCursor(Qt::ArrowCursor); other_time=time.restart(); qDebug() << "Page change time (in ms): Unload ="<resetGraphOrder(true, advancedGraphOrder); else GraphView->resetGraphOrder(true, standardGraphOrder); } else if (type == 2) { // Advanced order GraphView->resetGraphOrder(true, advancedGraphOrder); } else { // type == 1, standard order GraphView->resetGraphOrder(true, standardGraphOrder); } // Enable all graphs (make them not hidden) for (int i=1;igraphCombo->count();i++) { // If disabled, emulate a click to enable the graph if (!ui->graphCombo->itemData(i,Qt::UserRole).toBool()) { // qDebug() << "resetting graph" << i; Daily::on_graphCombo_activated(i); } } // Mark all events as active for (int i=1;ieventsCombo->count();i++) { // If disabled, emulate a click to enable the event ChannelID code = ui->eventsCombo->itemData(i, Qt::UserRole).toUInt(); schema::Channel * chan = &schema::channel[code]; if (!chan->enabled()) { // qDebug() << "resetting event" << i; Daily::on_eventsCombo_activated(i); } } // Reset graph heights (and repaint) ResetGraphLayout(); } void Daily::graphtogglebutton_toggled(bool b) { Q_UNUSED(b) for (int i=0;isize();i++) { QString title=(*GraphView)[i]->title(); (*GraphView)[i]->setVisible(GraphToggles[title]->isChecked()); } GraphView->updateScale(); GraphView->redraw(); } QString Daily::getSessionInformation(Day * day) { QString html; if (!day) return html; html=""; html+=QString(""); html+=""; QFontMetrics FM(*defaultfont); // QRect r=FM.boundingRect('@'); // Machine * cpap = day->machine(MT_CPAP); QDateTime fd,ld; //bool corrupted_waveform=false; QString type; QHash::iterator mach_end = day->machines.end(); QHash::iterator mi; for (mi = day->machines.begin(); mi != mach_end; ++mi) { if (mi.key() == MT_JOURNAL) continue; html += "\n"; html+=QString("" "" "" "" "" ""); QList sesslist = day->getSessions(mi.key(), true); for (QList::iterator s=sesslist.begin(); s != sesslist.end(); ++s) { /* if (((*s)->type() == MT_CPAP) && ((*s)->settings.find(CPAP_BrokenWaveform) != (*s)->settings.end())) corrupted_waveform=true; */ fd=QDateTime::fromTime_t((*s)->first()/1000L); ld=QDateTime::fromTime_t((*s)->last()/1000L); int len=(*s)->length()/1000L; int h=len/3600; int m=(len/60) % 60; int s1=len % 60; Session *sess=*s; QString tooltip = tr("Click to %1 this session.").arg(sess->enabled() ? tr("disable") : tr("enable")); html+=QString("" "" "" "" "" "" "" ) .arg((*s)->session()) .arg(tr("%1 Session #%2").arg((*s)->machine()->loaderName()).arg((*s)->session(),8,10,QChar('0'))) .arg(tr("%1h %2m %3s").arg(h,2,10,QChar('0')).arg(m,2,10,QChar('0')).arg(s1,2,10,QChar('0'))) .arg((sess->enabled() ? "on" : "off")) .arg(fd.date().toString(Qt::SystemLocaleShortDate)) .arg(fd.toString("HH:mm:ss")) .arg(ld.toString("HH:mm:ss")); #ifdef SESSION_DEBUG for (int i=0; i< sess->session_files.size(); ++i) { html+=QString("").arg(sess->session_files[i].section("/",-1)); } #endif } } /* if (corrupted_waveform) { html+=QString("").arg(tr("One or more waveform record(s) for this session had faulty source data. Some waveform overlay points may not match up correctly.")); } */ html+="
"+tr("Session Information")+"
 
"; switch (mi.key()) { case MT_CPAP: type="cpap"; html+=tr("CPAP Sessions"); break; case MT_OXIMETER: type="oxi"; html+=tr("Oximetry Sessions"); break; case MT_SLEEPSTAGE: type="stage"; html+=tr("Sleep Stage Sessions"); break; case MT_POSITION: type="position"; html+=tr("Position Sensor Sessions"); break; default: type="unknown"; html+=tr("Unknown Session"); break; } html+="
"+STR_TR_On+""+STR_TR_Date+""+STR_TR_Start+""+STR_TR_End+""+tr("Duration")+"
%2
" #ifdef DITCH_ICONS "[%4]" #else "" #endif "%5%6%7%3
%1
%1
"; return html; } QString Daily::getMachineSettings(Day * day) { QString html; Machine * cpap = day->machine(MT_CPAP); if (cpap && day->hasEnabledSessions(MT_CPAP)) { html=""; html+=QString("").arg(tr("Machine Settings")); if (day->noSettings(cpap)) { html+="\n"; } else { html+=""; } /* } else if ((day->settingExists(CPAP_BrokenSummary))) { html+="
%1
"+tr("Please Note: All settings shown below are based on assumptions that nothing has changed since previous days.")+"
 
"+tr("Machine Settings Unavailable")+"

\n"; return html; } */ QMap other; Session * sess = day->firstSession(MT_CPAP); QHash::iterator it; QHash::iterator it_end; if (sess) { it_end = sess->settings.end(); it = sess->settings.begin(); } QMap first; CPAPLoader * loader = qobject_cast(cpap->loader()); ChannelID cpapmode = loader->CPAPModeChannel(); schema::Channel & chan = schema::channel[cpapmode]; first[cpapmode] = QString("

%1

%3") .arg(chan.label()) .arg(chan.description()) .arg(day->getCPAPModeStr()); // The order in which to present pressure settings, which will appear first. QVector first_channels = { cpapmode, CPAP_Pressure, CPAP_PressureMin, CPAP_PressureMax, CPAP_EPAP, CPAP_EPAPLo, CPAP_EPAPHi, CPAP_IPAP, CPAP_IPAPLo, CPAP_IPAPHi, CPAP_PS, CPAP_PSMin, CPAP_PSMax }; if (sess) for (; it != it_end; ++it) { ChannelID code = it.key(); if ((code <= 1) || (code == RMS9_MaskOnTime) || (code == CPAP_Mode) || (code == cpapmode) || (code == CPAP_SummaryOnly)) continue; schema::Channel & chan = schema::channel[code]; QString data; if (chan.datatype() == schema::LOOKUP) { int value = it.value().toInt(); data = chan.option(value); if (data.isEmpty()) { data = QString().number(value) + " " + chan.units();; } } else if (chan.datatype() == schema::BOOL) { data = (it.value().toBool() ? STR_TR_Yes : STR_TR_No); } else if (chan.datatype() == schema::DOUBLE) { data = QString().number(it.value().toDouble(),'f',2) + " "+chan.units(); } else if (chan.datatype() == schema::DEFAULT) { // Check for any options that override the default numeric display. int value = it.value().toInt(); data = chan.option(value); if (data.isEmpty()) { data = QString().number(it.value().toDouble(),'f',2) + " "+chan.units(); } } else { data = it.value().toString() + " "+ chan.units(); } if (code ==0xe202) // Format EPR relief correctly data = formatRelief(data); QString tmp = QString("

%1

%3") .arg(schema::channel[code].label()) .arg(schema::channel[code].description()) .arg(data); //qDebug() << QString::number( code, 16 ) << tmp; if (first_channels.contains(code)) { first[code] = tmp; } else { other[schema::channel[code].label()] = tmp; } } // Put the pressure settings in order. for (auto & code : first_channels) { if (first.contains(code)) html += first[code]; } // TODO: add logical (rather than alphabetical) ordering to this list, preferably driven by loader somehow for (QMap::iterator it = other.begin(); it != other.end(); ++it) { html += it.value(); } /* ChannelID pr_level_chan = NoChannel; ChannelID pr_mode_chan = NoChannel; ChannelID hum_stat_chan = NoChannel; ChannelID hum_level_chan = NoChannel; CPAPLoader * loader = dynamic_cast(cpap->machine->loader()); if (loader) { pr_level_chan = loader->PresReliefLevel(); pr_mode_chan = loader->PresReliefMode(); hum_stat_chan = loader->HumidifierConnected(); hum_level_chan = loader->HumidifierLevel(); } if ((pr_level_chan != NoChannel) && (cpap->settingExists(pr_level_chan))) { QString flexstr = cpap->getPressureRelief(); html+=QString("%1%2%3") .arg(schema::channel[pr_mode_chan].label()) .arg(schema::channel[pr_mode_chan].description()) .arg(flexstr); } if (cpap->settingExists(hum_level_chan)) { int humid=round(cpap->settings_wavg(hum_level_chan)); html+=QString(""+schema::channel[hum_level_chan].label()+"%1%2") .arg(schema::channel[hum_level_chan].description()) .arg(humid == 0 ? STR_GEN_Off : "x"+QString::number(humid)); } */ html+=""; html+="
\n"; } return html; } QString Daily::getOximeterInformation(Day * day) { QString html; Machine * oxi = day->machine(MT_OXIMETER); if (oxi && day->hasEnabledSessions(MT_OXIMETER)) { html=""; html+=QString("\n").arg(tr("Oximeter Information")); html+=""; html+="\n"; html+=""; // Include SpO2 and PC drops per hour of Oximetry data in case CPAP data is missing html+=QString("").arg(tr("SpO2 Desaturations")).arg(day->count(OXI_SPO2Drop)).arg((100.0/day->hours(MT_OXIMETER)) * (day->sum(OXI_SPO2Drop)/3600.0),0,'f',2).arg((day->count(OXI_SPO2Drop)/day->hours(MT_OXIMETER)),0,'f',2); html+=QString("").arg(tr("Pulse Change events")).arg(day->count(OXI_PulseChange)).arg((100.0/day->hours(MT_OXIMETER)) * (day->sum(OXI_PulseChange)/3600.0),0,'f',2).arg((day->count(OXI_PulseChange)/day->hours(MT_OXIMETER)),0,'f',2); html+=QString("").arg(tr("SpO2 Baseline Used")).arg(day->settings_wavg(OXI_SPO2Drop),0,'f',2); // CHECKME: Should this value be wavg OXI_SPO2 isntead? html+="
%1
 
"+oxi->brand()+" "+oxi->model()+"
 
%1: %2 (%3%) %4/h
%1: %2 (%3%) %4/h
%1: %2%
\n"; html+="
\n"; } return html; } QString Daily::getCPAPInformation(Day * day) { QString html; if (!day) return html; Machine * cpap = day->machine(MT_CPAP); if (!cpap) return html; MachineInfo info = cpap->getInfo(); html="\n"; QString tooltip=tr("Model %1 - %2").arg(info.modelnumber).arg(info.serial); tooltip=tooltip.replace(" "," "); html+="\n"; html+="\n"; if (day->noSettings(cpap)) { html+=QString("").arg(tr("(Mode and Pressure settings missing; yesterday's shown.)")); } html+="

"+info.brand+"
"+info.model+"

"; html+=tr("PAP Mode: %1").arg(day->getCPAPModeStr())+"
"; html+= day->getPressureSettings(); html+="
%1
\n"; html+="
\n"; return html; } QString Daily::getStatisticsInfo(Day * day) { if (day == nullptr) return QString(); Machine *cpap = day->machine(MT_CPAP); // *oxi = day->machine(MT_OXIMETER), // *pos = day->machine(MT_POSITION); int mididx=p_profile->general->prefCalcMiddle(); SummaryType ST_mid = ST_AVG; if (mididx==0) ST_mid=ST_PERC; if (mididx==1) ST_mid=ST_WAVG; if (mididx==2) ST_mid=ST_AVG; float percentile=p_profile->general->prefCalcPercentile()/100.0; SummaryType ST_max=p_profile->general->prefCalcMax() ? ST_PERC : ST_MAX; const EventDataType maxperc=0.995F; QString midname; if (ST_mid==ST_WAVG) midname=STR_TR_WAvg; else if (ST_mid==ST_AVG) midname=STR_TR_Avg; else if (ST_mid==ST_PERC) midname=STR_TR_Med; QString html; html+="\n"; html+=QString("\n").arg(tr("Statistics")); html+=QString("") .arg(STR_TR_Channel) .arg(STR_TR_Min) .arg(midname) .arg(QString("%1%2").arg(percentile*100,0,'f',0).arg(STR_UNIT_Percentage)) .arg(ST_max == ST_MAX?STR_TR_Max:tr("99.5%")); ChannelID chans[]={ CPAP_Pressure,CPAP_PressureSet,CPAP_EPAP,CPAP_EPAPSet,CPAP_IPAP,CPAP_IPAPSet,CPAP_PS,CPAP_PTB, PRS1_PeakFlow, CPAP_MinuteVent, CPAP_RespRate, CPAP_RespEvent,CPAP_FLG, CPAP_Leak, CPAP_LeakTotal, CPAP_Snore, /* CPAP_IE, */ CPAP_Ti,CPAP_Te, CPAP_TgMV, CPAP_TidalVolume, OXI_Pulse, OXI_SPO2, POS_Inclination, POS_Orientation, POS_Movement }; int numchans=sizeof(chans)/sizeof(ChannelID); int ccnt=0; EventDataType tmp,med,perc,mx,mn; for (int i=0;ichannelHasData(code)) continue; QString tooltip=schema::channel[code].description(); if (!schema::channel[code].units().isEmpty()) tooltip+=" ("+schema::channel[code].units()+")"; // if (!schema::channel[code].units().isEmpty() && schema::channel[code].units() != "0") // tooltip+=" ("+schema::channel[code].units()+")"; if (ST_max == ST_MAX) { mx=day->Max(code); } else { mx=day->percentile(code,maxperc); } mn=day->Min(code); perc=day->percentile(code,percentile); if (ST_mid == ST_PERC) { med=day->percentile(code,0.5); tmp=day->wavg(code); if (tmp>0 || mx==0) { tooltip+=QString("
"+STR_TR_WAvg+": %1").arg(tmp,0,'f',2); } } else if (ST_mid == ST_WAVG) { med=day->wavg(code); tmp=day->percentile(code,0.5); if (tmp>0 || mx==0) { tooltip+=QString("
"+STR_TR_Median+": %1").arg(tmp,0,'f',2); } } else if (ST_mid == ST_AVG) { med=day->avg(code); tmp=day->percentile(code,0.5); if (tmp>0 || mx==0) { tooltip+=QString("
"+STR_TR_Median+": %1").arg(tmp,0,'f',2); } } // QString oldtip = tooltip; tooltip.replace("'", "'"); // qDebug() << schema::channel[code].label() << "old tooltip" << oldtip << "; new tooltip" << tooltip ; html+=QString("
") .arg(schema::channel[code].label()) .arg(mn,0,'f',2) .arg(med,0,'f',2) .arg(perc,0,'f',2) .arg(mx,0,'f',2) .arg(tooltip); ccnt++; } if (GraphView->isEmpty() && ((ccnt>0) || (cpap && day->summaryOnly()))) { html+="\n"; html+=QString("").arg(""+STR_MessageBox_PleaseNote+" "+ tr("This day just contains summary data, only limited information is available.")); } else if (cpap) { html+=""; if ((cpap->loaderName() == STR_MACH_ResMed) || ((cpap->loaderName() == STR_MACH_PRS1) && (p_profile->cpap->resyncFromUserFlagging()))) { int ttia = day->sum(AllAhiChannels); int h = ttia / 3600; int m = ttia / 60 % 60; int s = ttia % 60; if (ttia > 0) { html+="").arg(QString().sprintf("%02i:%02i:%02i",h,m,s)); } } float hours = day->hours(MT_CPAP); if (p_profile->cpap->showLeakRedline()) { float rlt = day->timeAboveThreshold(CPAP_Leak, p_profile->cpap->leakRedline()) / 60.0; float pc = 100.0 / hours * rlt; html+="").arg(pc, 0, 'f', 3); } int l = day->sum(CPAP_Ramp); if (l > 0) { html+="").arg(l / 3600, 2, 10, QChar('0')).arg((l / 60) % 60, 2, 10, QChar('0')).arg(l % 60, 2, 10, QChar('0')); float v = (hours - (float(l) / 3600.0)); int q = v * 3600.0; html+="").arg(q / 3600, 2, 10, QChar('0')).arg((q / 60) % 60, 2, 10, QChar('0')).arg(q % 60, 2, 10, QChar('0')); // EventDataType hc = day->count(CPAP_Hypopnea) - day->countInsideSpan(CPAP_Ramp, CPAP_Hypopnea); // EventDataType oc = day->count(CPAP_Obstructive) - day->countInsideSpan(CPAP_Ramp, CPAP_Obstructive); //EventDataType tc = day->count(CPAP_Hypopnea) + day->count(CPAP_Obstructive); // EventDataType ahi = (hc+oc) / v; // Not sure if i was trying to be funny, and left on my replication of Devilbiss's bug here... :P // html+="").arg(ahi, 0, 'f', 2); } } html+="
%1
%1%2%3%4%5
%1%2%3%4%5
 
%1
 
"+tr("Total time in apnea") + QString("%1
"+tr("Time over leak redline")+ QString("%1%
"+tr("Total ramp time")+ QString("%1:%2:%3
"+tr("Time outside of ramp")+ QString("%1:%2:%3
"+tr("AHI excluding ramp")+ // QString("%1
\n"; html+="
\n"; return html; } QString Daily::getEventBreakdown(Day * cpap) { Q_UNUSED(cpap) QString html; html+="\n"; html+="
"; return html; } QString Daily::getSleepTime(Day * day) { //cpap, Day * oxi QString html; if (!day || (day->hours() < 0.0000001)) return html; html+="\n"; html+=""; int tt=qint64(day->total_time(MT_CPAP))/1000L; QDateTime date=QDateTime::fromTime_t(day->first()/1000L); QDateTime date2=QDateTime::fromTime_t(day->last()/1000L); int h=tt/3600; int m=(tt/60)%60; int s=tt % 60; html+=QString("\n") .arg(date.date().toString(Qt::SystemLocaleShortDate)) .arg(date.toString("HH:mm:ss")) .arg(date2.toString("HH:mm:ss")) .arg(QString().sprintf("%02i:%02i:%02i",h,m,s)); html+="
"+STR_TR_Date+""+tr("Start")+""+tr("End")+""+STR_UNIT_Hours+"
%1%2%3%4
\n"; // html+="
"; return html; } QString Daily::getPieChart (float values, Day * day) { // qDebug() << "Daily:getPieChart, values" << values; QString html = ""; if (values > 0) { // html += ""; html += QString("").arg(tr("Event Breakdown")); eventBreakdownPie()->setShowTitle(false); int w=155; int h=155; QPixmap pixmap=eventBreakdownPie()->renderPixmap(w,h,false); if (!pixmap.isNull()) { QByteArray byteArray; QBuffer buffer(&byteArray); // use buffer to store pixmap into byteArray buffer.open(QIODevice::WriteOnly); pixmap.save(&buffer, "PNG"); html += "\n"; } else { html += "\n"; } } else { bool gotsome = false; for (int i = 0; i < ahiChannels.size(); i++) gotsome = gotsome || day->channelHasData(ahiChannels.at(i)); // if ( day->channelHasData(CPAP_Obstructive) // || day->channelHasData(CPAP_AllApnea) // || day->channelHasData(CPAP_Hypopnea) // || day->channelHasData(CPAP_ClearAirway) // || day->channelHasData(CPAP_Apnea) if ( gotsome || day->channelHasData(CPAP_RERA) || day->channelHasData(CPAP_FlowLimit) || day->channelHasData(CPAP_SensAwake) ) { html += "\n"; } } html+="
 
%1
"+tr("Unable to display Pie Chart on this system")+"
\n"; html+="
\n"; return html; } // honorPieChart true - show pie chart if it is enabled. False, do not show pie chart QString Daily::getLeftSidebar (bool honorPieChart) { QString html = htmlLeftHeader + htmlLeftAHI + htmlLeftMachineInfo + htmlLeftSleepTime + htmlLeftIndices; // Include pie chart if wanted and enabled. if (honorPieChart && AppSetting->showPieChart()) html += htmlLeftPieChart; html += htmlLeftNoHours + htmlLeftStatistics + htmlLeftOximeter + htmlLeftMachineSettings + htmlLeftSessionInfo + htmlLeftFooter; return html; } QVariant MyTextBrowser::loadResource(int type, const QUrl &url) { if (type == QTextDocument::ImageResource && url.scheme().compare(QLatin1String("data"), Qt::CaseInsensitive) == 0) { static QRegularExpression re("^image/[^;]+;base64,.+={0,2}$"); QRegularExpressionMatch match = re.match(url.path()); if (match.hasMatch()) return QVariant(); } return QTextBrowser::loadResource(type, url); } void Daily::Load(QDate date) { qDebug() << "Daily::Load called for" << date.toString() << "using" << QApplication::font().toString(); qDebug() << "Setting App font in Daily::Load"; setApplicationFont(); dateDisplay->setText(""+date.toString(Qt::SystemLocaleLongDate)+""); previous_date=date; Day * day = p_profile->GetDay(date); Machine *cpap = nullptr, *oxi = nullptr, //*stage = nullptr, *posit = nullptr; if (day) { cpap = day->machine(MT_CPAP); oxi = day->machine(MT_OXIMETER); // stage = day->machine(MT_SLEEPSTAGE); posit = day->machine(MT_POSITION); } if (!AppSetting->cacheSessions()) { // Getting trashed on purge last day... // lastcpapday can get purged and be invalid if (lastcpapday && (lastcpapday!=day)) { for (QMap::iterator di = p_profile->daylist.begin(); di!= p_profile->daylist.end(); ++di) { Day * d = di.value(); if (d->eventsLoaded()) { if (d->useCounter() == 0) { d->CloseEvents(); } } } } } lastcpapday=day; // Clear the components of the left sidebar prior to recreating them htmlLeftAHI.clear(); htmlLeftMachineInfo.clear(); htmlLeftSleepTime.clear(); htmlLeftIndices.clear(); htmlLeftPieChart.clear(); htmlLeftNoHours.clear(); htmlLeftStatistics.clear(); htmlLeftOximeter.clear(); htmlLeftMachineSettings.clear(); htmlLeftSessionInfo.clear(); htmlLeftHeader = "" "" ""; if (day) { day->OpenEvents(); } GraphView->setDay(day); UpdateEventsTree(ui->treeWidget, day); // FIXME: // Generating entire statistics because bookmarks may have changed.. (This updates the side panel too) mainwin->GenerateStatistics(); snapGV->setDay(day); QString modestr; CPAPMode mode=MODE_UNKNOWN; QString a; bool isBrick=false; updateGraphCombo(); ui->eventsCombo->clear(); quint32 chans = schema::SPAN | schema::FLAG | schema::MINOR_FLAG; if (p_profile->general->showUnknownFlags()) chans |= schema::UNKNOWN; QList available; if (day) available.append(day->getSortedMachineChannels(chans)); ui->eventsCombo->addItem(*icon_up_down, tr("10 of 10 Event Types"), 0); // Translation used only for spacing for (int i=0; i < available.size(); ++i) { ChannelID code = available.at(i); schema::Channel & chan = schema::channel[code]; ui->eventsCombo->addItem(chan.enabled() ? *icon_on : * icon_off, chan.label(), code); ui->eventsCombo->setItemData(i, chan.fullname(), Qt::ToolTipRole); } setFlagText(); if (!cpap) { GraphView->setEmptyImage(QPixmap(":/icons/logo-md.png")); } if (cpap) { float hours=day->hours(MT_CPAP); if (GraphView->isEmpty() && (hours>0)) { // TODO: Eventually we should get isBrick from the loader, since some summary days // on a non-brick might legitimately have no graph data. bool gotsome = false; for (int i = 0; i < ahiChannels.size(); i++) gotsome = gotsome || p_profile->hasChannel(ahiChannels.at(i)); // if (!p_profile->hasChannel(CPAP_Obstructive) && !p_profile->hasChannel(CPAP_Hypopnea) && !p_profile->hasChannel(CPAP_AllApnea) && !p_profile->hasChannel(CPAP_ClearAirway)) { if (!gotsome) { GraphView->setEmptyText(STR_Empty_Brick); GraphView->setEmptyImage(QPixmap(":/icons/sadface.png")); isBrick=true; } else { GraphView->setEmptyImage(QPixmap(":/icons/logo-md.png")); } } mode=(CPAPMode)(int)day->settings_max(CPAP_Mode); modestr=schema::channel[CPAP_Mode].m_options[mode]; EventDataType ahi=day->count(AllAhiChannels); if (p_profile->general->calculateRDI()) ahi+=day->count(CPAP_RERA); ahi/=hours; if (hours>0) { htmlLeftAHI="\n"; // Show application font, for debugging font issues // QString appFont = QApplication::font().toString(); // htmlLeftAHI+=QString("").arg(appFont); htmlLeftAHI+=""; if (!isBrick) { ChannelID ahichan=CPAP_AHI; QString ahiname=STR_TR_AHI; if (p_profile->general->calculateRDI()) { ahichan=CPAP_RDI; ahiname=STR_TR_RDI; } htmlLeftAHI+=QString("\n") .arg("#F88017").arg(COLOR_Text.name()).arg(ahiname).arg(schema::channel[ahichan].fullname()).arg(ahi,0,'f',2); } else { htmlLeftAHI+=QString("\n") .arg("#F88017").arg(tr("This CPAP machine does NOT record detailed data")); } htmlLeftAHI+="\n"; htmlLeftAHI+="
%1

%3

  %5
%2
\n"; htmlLeftMachineInfo = getCPAPInformation(day); htmlLeftSleepTime = getSleepTime(day); htmlLeftIndices = "\n"; quint32 zchans = schema::SPAN | schema::FLAG; bool show_minors = false; // true; if (p_profile->general->showUnknownFlags()) zchans |= schema::UNKNOWN; if (show_minors) zchans |= schema::MINOR_FLAG; QList available = day->getSortedMachineChannels(zchans); EventDataType val; QHash values; for (int i=0; i < available.size(); ++i) { ChannelID code = available.at(i); schema::Channel & chan = schema::channel[code]; // if (!chan.enabled()) continue; QString data; float channelHours = hours; if (chan.machtype() != MT_CPAP) { // Use machine type hours (if available) rather than CPAP hours, since // might have Oximetry (for example) longer or shorter than CPAP channelHours = day->hours(chan.machtype()); if (channelHours <= 0) { channelHours = hours; } } if (chan.type() == schema::SPAN) { val = (100.0 / channelHours)*(day->sum(code)/3600.0); data = QString("%1%").arg(val,0,'f',2); } else if (code == CPAP_VSnore2) { // TODO: This should be generalized rather than special-casing a single channel here. val = day->sum(code) / channelHours; data = QString("%1").arg(val,0,'f',2); } else { val = day->count(code) / channelHours; data = QString("%1").arg(val,0,'f',2); } // TODO: percentage would be another useful option here for things like // percentage of patient-triggered breaths, which is much more useful // than the duration of timed breaths per hour. values[code] = val; QString tooltip=schema::channel[code].description(); tooltip.replace("'", "'"); QColor altcolor = (brightness(chan.defaultColor()) < 0.3) ? Qt::white : Qt::black; // pick a contrasting color htmlLeftIndices+=QString("") .arg(chan.defaultColor().name()) .arg(altcolor.name()) .arg(chan.fullname()) .arg(data) .arg(code) .arg(tooltip); } htmlLeftIndices+="
%3%4

"; htmlLeftPieChart = getPieChart((values[CPAP_Obstructive] + values[CPAP_Hypopnea] + values[CPAP_AllApnea] + values[CPAP_ClearAirway] + values[CPAP_Apnea] + values[CPAP_RERA] + values[CPAP_FlowLimit] + values[CPAP_SensAwake]), day); } else { // No hours htmlLeftNoHours+="\n"; if (!isBrick) { htmlLeftNoHours+="\n"; if (day->size()>0) { htmlLeftNoHours+=""; htmlLeftNoHours+=""; htmlLeftNoHours+="\n"; GraphView->setEmptyText(STR_Empty_NoSessions); } else { htmlLeftNoHours+=""; htmlLeftNoHours+="\n"; } } else { // machine is a brick htmlLeftNoHours+=""; htmlLeftNoHours+="\n"; htmlLeftNoHours+="\n"; } htmlLeftNoHours+="\n"; htmlLeftNoHours+="
 
"+tr("Sessions all off!")+"
"+tr("Sessions exist for this day but are switched off.")+"

"+tr("Impossibly short session")+"

"+tr("Zero hours??")+"

"+tr("BRICK :(")+"

"+tr("Sorry, this machine only provides compliance data.")+"
"+tr("Complain to your Equipment Provider!")+"
 
\n"; } } // if (!CPAP) else htmlLeftSleepTime = getSleepTime(day); if ((cpap && !isBrick && (day->hours()>0)) || oxi || posit) { htmlLeftStatistics = getStatisticsInfo(day); } else { if (cpap && day->hours(MT_CPAP)<0.0000001) { } else if (!isBrick) { htmlLeftStatistics =""; htmlLeftStatistics+=""; htmlLeftStatistics+=""; htmlLeftStatistics+=""; htmlLeftStatistics+=""; htmlLeftStatistics+=""; htmlLeftStatistics+=""; htmlLeftStatistics+="
"+tr("\"Nothing's here!\"")+"
"+tr("No data is available for this day.")+"
\n"; } } if (day) { htmlLeftOximeter = getOximeterInformation(day); htmlLeftMachineSettings = getMachineSettings(day); htmlLeftSessionInfo= getSessionInformation(day); } htmlLeftFooter =""; // SessionBar colors. Colors alternate. QColor cols[]={ COLOR_Gold, // QColor("light blue"), QColor("skyblue"), }; const int maxcolors=sizeof(cols)/sizeof(QColor); QList::iterator i; sessionbar->clear(); // clear sessionbar as some days don't have sessions if (cpap) { int c=0; for (i=day->begin();i!=day->end();++i) { Session * s=*i; if ((*s).type() == MT_CPAP) sessionbar->add(s, cols[c % maxcolors]); c++; } } //sessbar->update(); #ifdef DEBUG_DAILY_HTML QString name = GetAppData()+"/test.html"; QFile file(name); if (file.open(QFile::WriteOnly)) { const QByteArray & b = html.toLocal8Bit(); file.write(b.data(), b.size()); file.close(); } #endif webView->setHtml(getLeftSidebar(true)); ui->JournalNotes->clear(); ui->bookmarkTable->clearContents(); ui->bookmarkTable->setRowCount(0); QStringList sl; //sl.append(tr("Starts")); //sl.append(tr("Notes")); ui->bookmarkTable->setHorizontalHeaderLabels(sl); ui->ZombieMeter->blockSignals(true); ui->weightSpinBox->blockSignals(true); ui->ouncesSpinBox->blockSignals(true); ui->weightSpinBox->setValue(0); ui->ouncesSpinBox->setValue(0); ui->ZombieMeter->setValue(5); ui->ouncesSpinBox->blockSignals(false); ui->weightSpinBox->blockSignals(false); ui->ZombieMeter->blockSignals(false); ui->BMI->display(0); ui->BMI->setVisible(false); ui->BMIlabel->setVisible(false); ui->toggleGraphs->setVisible(false); ui->toggleEvents->setVisible(false); BookmarksChanged=false; Session *journal=GetJournalSession(date); if (journal) { bool ok; if (journal->settings.contains(Journal_Notes)) ui->JournalNotes->setHtml(journal->settings[Journal_Notes].toString()); if (journal->settings.contains(Journal_Weight)) { double kg=journal->settings[Journal_Weight].toDouble(&ok); if (p_profile->general->unitSystem()==US_Metric) { ui->weightSpinBox->setDecimals(1); ui->weightSpinBox->blockSignals(true); ui->weightSpinBox->setValue(kg); ui->weightSpinBox->blockSignals(false); ui->ouncesSpinBox->setVisible(false); ui->weightSpinBox->setSuffix(STR_UNIT_KG); } else { float ounces=(kg*1000.0)/ounce_convert; int pounds=ounces/16.0; double oz; double frac=modf(ounces,&oz); ounces=(int(ounces) % 16)+frac; ui->weightSpinBox->blockSignals(true); ui->ouncesSpinBox->blockSignals(true); ui->weightSpinBox->setValue(pounds); ui->ouncesSpinBox->setValue(ounces); ui->ouncesSpinBox->blockSignals(false); ui->weightSpinBox->blockSignals(false); ui->weightSpinBox->setSuffix(STR_UNIT_POUND); ui->weightSpinBox->setDecimals(0); ui->ouncesSpinBox->setVisible(true); ui->ouncesSpinBox->setSuffix(STR_UNIT_OUNCE); } double height=p_profile->user->height()/100.0; if (height>0 && kg>0) { double bmi=kg/(height*height); ui->BMI->setVisible(true); ui->BMIlabel->setVisible(true); ui->BMI->display(bmi); } } if (journal->settings.contains(Journal_ZombieMeter)) { ui->ZombieMeter->blockSignals(true); ui->ZombieMeter->setValue(journal->settings[Journal_ZombieMeter].toDouble(&ok)); ui->ZombieMeter->blockSignals(false); } if (journal->settings.contains(Bookmark_Start)) { QVariantList start=journal->settings[Bookmark_Start].toList(); QVariantList end=journal->settings[Bookmark_End].toList(); QStringList notes=journal->settings[Bookmark_Notes].toStringList(); ui->bookmarkTable->blockSignals(true); // Careful with drift here - apply to the label but not the // stored data (which will be saved if journal changes occur). qint64 clockdrift=p_profile->cpap->clockDrift()*1000L,drift; Day * dday=p_profile->GetDay(previous_date,MT_CPAP); drift=(dday!=nullptr) ? clockdrift : 0; bool ok; for (int i=0;ibookmarkTable->rowCount(); ui->bookmarkTable->insertRow(i); QTableWidgetItem *tw=new QTableWidgetItem(notes.at(i)); QTableWidgetItem *dw=new QTableWidgetItem(d.time().toString("HH:mm:ss")); dw->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); ui->bookmarkTable->setItem(i,0,dw); ui->bookmarkTable->setItem(i,1,tw); tw->setData(Qt::UserRole,st); tw->setData(Qt::UserRole+1,et); } // for (int i ui->bookmarkTable->blockSignals(false); } // if (journal->settings.contains(Bookmark_Start)) } // if (journal) } void Daily::UnitsChanged() { double kg; if (p_profile->general->unitSystem()==US_English) { kg=ui->weightSpinBox->value(); float ounces=(kg*1000.0)/ounce_convert; int pounds=ounces/16; float oz=fmodf(ounces,16); ui->weightSpinBox->setValue(pounds); ui->ouncesSpinBox->setValue(oz); ui->weightSpinBox->setDecimals(0); ui->weightSpinBox->setSuffix(STR_UNIT_POUND); ui->ouncesSpinBox->setVisible(true); ui->ouncesSpinBox->setSuffix(STR_UNIT_OUNCE); } else { kg=(ui->weightSpinBox->value()*(ounce_convert*16.0))+(ui->ouncesSpinBox->value()*ounce_convert); kg/=1000.0; ui->weightSpinBox->setDecimals(1); ui->weightSpinBox->setValue(kg); ui->ouncesSpinBox->setVisible(false); ui->weightSpinBox->setSuffix(STR_UNIT_KG); } } void Daily::clearLastDay() { lastcpapday=nullptr; } void Daily::Unload(QDate date) { if (!date.isValid()) { date = getDate(); if (!date.isValid()) { graphView()->setDay(nullptr); return; } } // Update the journal notes Session *journal = GetJournalSession(date); bool editorHasContent = !ui->JournalNotes->toPlainText().isEmpty(); // have a look as plaintext to see if really empty. if (!journal && editorHasContent) { journal = CreateJournalSession(date); } if (journal) { auto jit = journal->settings.find(Journal_Notes); if (jit != journal->settings.end()) { // we do have a journal_notes record if (editorHasContent) { const QString & html = ui->JournalNotes->toHtml(); if (jit.value() != html) { // has the content of it changed? jit.value() = html; journal->SetChanged(true); } } else { // empty, so don't need this notes setting anymore journal->settings.erase(jit); journal->SetChanged(true); } } else if (editorHasContent) { // Create the note journal->settings[Journal_Notes] = ui->JournalNotes->toHtml(); journal->SetChanged(true); } if (journal->IsChanged()) { journal->settings[LastUpdated] = QDateTime::currentDateTime(); journal->machine()->SaveSummaryCache(); journal->SetChanged(false); // save summary doesn't automatically do this } } UpdateCalendarDay(date); } void Daily::on_JournalNotesItalic_clicked() { QTextCursor cursor = ui->JournalNotes->textCursor(); if (!cursor.hasSelection()) cursor.select(QTextCursor::WordUnderCursor); QTextCharFormat format=cursor.charFormat(); format.setFontItalic(!format.fontItalic()); cursor.mergeCharFormat(format); //ui->JournalNotes->mergeCurrentCharFormat(format); } void Daily::on_JournalNotesBold_clicked() { QTextCursor cursor = ui->JournalNotes->textCursor(); if (!cursor.hasSelection()) cursor.select(QTextCursor::WordUnderCursor); QTextCharFormat format=cursor.charFormat(); int fw=format.fontWeight(); if (fw!=QFont::Bold) format.setFontWeight(QFont::Bold); else format.setFontWeight(QFont::Normal); cursor.mergeCharFormat(format); //ui->JournalNotes->mergeCurrentCharFormat(format); } void Daily::on_JournalNotesFontsize_activated(int index) { QTextCursor cursor = ui->JournalNotes->textCursor(); if (!cursor.hasSelection()) cursor.select(QTextCursor::WordUnderCursor); QTextCharFormat format=cursor.charFormat(); QFont font=format.font(); int fontsize=10; if (index==1) fontsize=15; else if (index==2) fontsize=25; font.setPointSize(fontsize); format.setFont(font); cursor.mergeCharFormat(format); } void Daily::on_JournalNotesColour_clicked() { QColor col=QColorDialog::getColor(COLOR_Black,this,tr("Pick a Colour")); //,QColorDialog::NoButtons); if (!col.isValid()) return; QTextCursor cursor = ui->JournalNotes->textCursor(); if (!cursor.hasSelection()) cursor.select(QTextCursor::WordUnderCursor); QBrush b(col); QPalette newPalette = palette(); newPalette.setColor(QPalette::ButtonText, col); ui->JournalNotesColour->setPalette(newPalette); QTextCharFormat format=cursor.charFormat(); format.setForeground(b); cursor.setCharFormat(format); } Session * Daily::CreateJournalSession(QDate date) { Machine *m = p_profile->GetMachine(MT_JOURNAL); if (!m) { m=new Machine(p_profile, 0); MachineInfo info; info.loadername = "Journal"; info.serial = m->hexid(); info.brand = "Journal"; info.type = MT_JOURNAL; m->setInfo(info); m->setType(MT_JOURNAL); p_profile->AddMachine(m); } Session *sess=new Session(m,0); qint64 st,et; Day *cday=p_profile->GetDay(date); if (cday) { st=cday->first(); et=cday->last(); } else { QDateTime dt(date,QTime(20,0)); st=qint64(dt.toTime_t())*1000L; et=st+3600000L; } sess->SetSessionID(st / 1000L); sess->set_first(st); sess->set_last(et); m->AddSession(sess, true); return sess; } Session * Daily::GetJournalSession(QDate date) // Get the first journal session { Day *day=p_profile->GetDay(date, MT_JOURNAL); if (day) { Session * session = day->firstSession(MT_JOURNAL); if (!session) { session = CreateJournalSession(date); } return session; } return nullptr; } void Daily::RedrawGraphs() { // setting this here, because it needs to be done when preferences change if (p_profile->cpap->showLeakRedline()) { schema::channel[CPAP_Leak].setUpperThreshold(p_profile->cpap->leakRedline()); } else { schema::channel[CPAP_Leak].setUpperThreshold(0); // switch it off } QFont appFont = QApplication::font(); dateDisplay->setFont(appFont); GraphView->redraw(); } void Daily::on_LineCursorUpdate(double time) { if (time > 1) { // use local time since this string is displayed to the user QDateTime dt = QDateTime::fromMSecsSinceEpoch(time, Qt::LocalTime); QString txt = dt.toString("MMM dd HH:mm:ss.zzz"); dateDisplay->setText(txt); } else dateDisplay->setText(QString(GraphView->emptyText())); } void Daily::on_RangeUpdate(double minx, double /*maxx*/) { if (minx > 1) { dateDisplay->setText(GraphView->getRangeString()); } else { dateDisplay->setText(QString(GraphView->emptyText())); } } void Daily::on_treeWidget_itemClicked(QTreeWidgetItem *item, int column) { Q_UNUSED(column); QDateTime d; if (!item->data(0,Qt::UserRole).isNull()) { qint64 winsize=qint64(p_profile->general->eventWindowSize())*60000L; qint64 t=item->data(0,Qt::UserRole).toLongLong(); double st=t-(winsize/2); double et=t+(winsize/2); gGraph *g=GraphView->findGraph(STR_GRAPH_SleepFlags); if (!g) return; if (strmin_x) { st=g->rmin_x; et=st+winsize; } if (et>g->rmax_x) { et=g->rmax_x; st=et-winsize; } GraphView->SetXBounds(st,et); } } void Daily::on_treeWidget_itemSelectionChanged() { if (ui->treeWidget->selectedItems().size()==0) return; QTreeWidgetItem *item=ui->treeWidget->selectedItems().at(0); if (!item) return; on_treeWidget_itemClicked(item, 0); } void Daily::on_JournalNotesUnderline_clicked() { QTextCursor cursor = ui->JournalNotes->textCursor(); if (!cursor.hasSelection()) cursor.select(QTextCursor::WordUnderCursor); QTextCharFormat format=cursor.charFormat(); format.setFontUnderline(!format.fontUnderline()); cursor.mergeCharFormat(format); //ui->JournalNotes->mergeCurrentCharFormat(format); } void Daily::on_prevDayButton_clicked() { if (previous_date.isValid()) { Unload(previous_date); } if (!p_profile->ExistsAndTrue("SkipEmptyDays")) { LoadDate(previous_date.addDays(-1)); } else { QDate d=previous_date; for (int i=0;i<90;i++) { d=d.addDays(-1); if (p_profile->GetDay(d)) { LoadDate(d); break; } } } } bool Daily::eventFilter(QObject *object, QEvent *event) { if (object == ui->JournalNotes && event->type() == QEvent::FocusOut) { // Trigger immediate save of journal when we focus out from it so we never // lose any journal entry text... if (previous_date.isValid()) { Unload(previous_date); } } return false; } void Daily::on_nextDayButton_clicked() { if (previous_date.isValid()) { Unload(previous_date); } if (!p_profile->ExistsAndTrue("SkipEmptyDays")) { LoadDate(previous_date.addDays(1)); } else { QDate d=previous_date; for (int i=0;i<90;i++) { d=d.addDays(1); if (p_profile->GetDay(d)) { LoadDate(d); break; } } } } void Daily::on_calButton_toggled(bool checked) { bool b=checked; ui->calendarFrame->setVisible(b); AppSetting->setCalendarVisible(b); if (!b) { ui->calButton->setArrowType(Qt::DownArrow); } else { ui->calButton->setArrowType(Qt::UpArrow); } } void Daily::on_todayButton_clicked() { if (previous_date.isValid()) { Unload(previous_date); } // QDate d=QDate::currentDate(); // if (d > p_profile->LastDay()) { QDate lastcpap = p_profile->LastDay(MT_CPAP); QDate lastoxi = p_profile->LastDay(MT_OXIMETER); QDate d = qMax(lastcpap, lastoxi); // } LoadDate(d); } void Daily::on_evViewSlider_valueChanged(int value) { ui->evViewLCD->display(value); p_profile->general->setEventWindowSize(value); int winsize=value*60; gGraph *g=GraphView->findGraph(STR_GRAPH_SleepFlags); if (!g) return; qint64 st=g->min_x; qint64 et=g->max_x; qint64 len=et-st; qint64 d=st+len/2.0; st=d-(winsize/2)*1000; et=d+(winsize/2)*1000; if (strmin_x) { st=g->rmin_x; et=st+winsize*1000; } if (et>g->rmax_x) { et=g->rmax_x; st=et-winsize*1000; } GraphView->SetXBounds(st,et); } void Daily::on_bookmarkTable_itemClicked(QTableWidgetItem *item) { int row=item->row(); qint64 st,et; qint64 clockdrift=p_profile->cpap->clockDrift()*1000L,drift; Day * dday=p_profile->GetDay(previous_date,MT_CPAP); drift=(dday!=nullptr) ? clockdrift : 0; QTableWidgetItem *it=ui->bookmarkTable->item(row,1); bool ok; // Add drift back in (it was removed in addBookmark) st=it->data(Qt::UserRole).toLongLong(&ok) + drift; et=it->data(Qt::UserRole+1).toLongLong(&ok) + drift; qint64 st2=0,et2=0,st3,et3; Day * day=p_profile->GetGoodDay(previous_date,MT_CPAP); if (day) { st2=day->first(); et2=day->last(); } Day * oxi=p_profile->GetGoodDay(previous_date,MT_OXIMETER); if (oxi) { st3=oxi->first(); et3=oxi->last(); } if (oxi && day) { st2=qMin(st2,st3); et2=qMax(et2,et3); } else if (oxi) { st2=st3; et2=et3; } else if (!day) return; if ((etet2)) { mainwin->Notify(tr("This bookmark is in a currently disabled area..")); return; } if (stet2) et=et2; GraphView->SetXBounds(st,et); GraphView->redraw(); } void Daily::addBookmark(qint64 st, qint64 et, QString text) { ui->bookmarkTable->blockSignals(true); QDateTime d=QDateTime::fromTime_t(st/1000L, Qt::LocalTime); int row=ui->bookmarkTable->rowCount(); ui->bookmarkTable->insertRow(row); QTableWidgetItem *tw=new QTableWidgetItem(text); QTableWidgetItem *dw=new QTableWidgetItem(d.time().toString("HH:mm:ss")); dw->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); ui->bookmarkTable->setItem(row,0,dw); ui->bookmarkTable->setItem(row,1,tw); qint64 clockdrift=p_profile->cpap->clockDrift()*1000L,drift; Day * day=p_profile->GetDay(previous_date,MT_CPAP); drift=(day!=nullptr) ? clockdrift : 0; // Counter CPAP clock drift for storage, in case user changes it later on // This won't fix the text string names.. tw->setData(Qt::UserRole,st-drift); tw->setData(Qt::UserRole+1,et-drift); ui->bookmarkTable->blockSignals(false); update_Bookmarks(); mainwin->updateFavourites(); } void Daily::on_addBookmarkButton_clicked() { qint64 st,et; GraphView->GetXBounds(st,et); QDateTime d=QDateTime::fromTime_t(st/1000L, Qt::LocalTime); addBookmark(st,et, tr("Bookmark at %1").arg(d.time().toString("HH:mm:ss"))); } void Daily::update_Bookmarks() { QVariantList start; QVariantList end; QStringList notes; QTableWidgetItem *item; for (int row=0;rowbookmarkTable->rowCount();row++) { item=ui->bookmarkTable->item(row,1); start.push_back(item->data(Qt::UserRole)); end.push_back(item->data(Qt::UserRole+1)); notes.push_back(item->text()); } Session *journal=GetJournalSession(previous_date); if (!journal) { journal=CreateJournalSession(previous_date); } journal->settings[Bookmark_Start]=start; journal->settings[Bookmark_End]=end; journal->settings[Bookmark_Notes]=notes; journal->settings[LastUpdated]=QDateTime::currentDateTime(); journal->SetChanged(true); BookmarksChanged=true; mainwin->updateFavourites(); } void Daily::on_removeBookmarkButton_clicked() { int row=ui->bookmarkTable->currentRow(); if (row>=0) { ui->bookmarkTable->blockSignals(true); ui->bookmarkTable->removeRow(row); ui->bookmarkTable->blockSignals(false); update_Bookmarks(); } mainwin->updateFavourites(); } void Daily::on_ZombieMeter_valueChanged(int action) { Q_UNUSED(action); ZombieMeterMoved=true; Session *journal=GetJournalSession(previous_date); if (!journal) { journal=CreateJournalSession(previous_date); } journal->settings[Journal_ZombieMeter]=ui->ZombieMeter->value(); journal->SetChanged(true); mainwin->updateOverview(); } void Daily::on_bookmarkTable_itemChanged(QTableWidgetItem *item) { Q_UNUSED(item); update_Bookmarks(); } void Daily::on_weightSpinBox_valueChanged(double arg1) { // This is called if up/down arrows are used, in which case editingFinished is // never called. So always call editingFinished instead Q_UNUSED(arg1); this->on_weightSpinBox_editingFinished(); } void Daily::on_weightSpinBox_editingFinished() { double arg1=ui->weightSpinBox->value(); double height=p_profile->user->height()/100.0; Session *journal=GetJournalSession(previous_date); if (!journal) { journal=CreateJournalSession(previous_date); } double kg; if (p_profile->general->unitSystem()==US_English) { kg=((arg1*pound_convert) + (ui->ouncesSpinBox->value()*ounce_convert)) / 1000.0; } else { kg=arg1; } if (journal->settings.contains(Journal_Weight)) { QVariant old = journal->settings[Journal_Weight]; if (old == kg && kg > 0) { // No change to weight - skip return; } } else if (kg == 0) { // Still zero - skip return; } if (kg > 0) { journal->settings[Journal_Weight]=kg; } else { // Weight now zero - remove from journal auto jit = journal->settings.find(Journal_Weight); if (jit != journal->settings.end()) { journal->settings.erase(jit); } } gGraphView *gv=mainwin->getOverview()->graphView(); gGraph *g; if (gv) { g=gv->findGraph(STR_GRAPH_Weight); if (g) g->setDay(nullptr); } if ((height>0) && (kg>0)) { double bmi=kg/(height * height); ui->BMI->display(bmi); ui->BMI->setVisible(true); ui->BMIlabel->setVisible(true); journal->settings[Journal_BMI]=bmi; } else { // BMI now zero - remove it auto jit = journal->settings.find(Journal_BMI); if (jit != journal->settings.end()) { journal->settings.erase(jit); } // And make it invisible ui->BMI->setVisible(false); ui->BMIlabel->setVisible(false); } if (gv) { g=gv->findGraph(STR_GRAPH_BMI); if (g) g->setDay(nullptr); } journal->SetChanged(true); mainwin->updateOverview(); } void Daily::on_ouncesSpinBox_valueChanged(int arg1) { // This is called if up/down arrows are used, in which case editingFinished is // never called. So always call editingFinished instead Q_UNUSED(arg1); this->on_weightSpinBox_editingFinished(); } void Daily::on_ouncesSpinBox_editingFinished() { // This is functionally identical to the weightSpinBox_editingFinished, so just call that this->on_weightSpinBox_editingFinished(); } QString Daily::GetDetailsText() { QTextDocument * doc = webView->document(); QString content = doc->toHtml(); return content; } void Daily::setGraphText () { int numOff = 0; int numTotal = 0; gGraph *g; for (int i=0;isize();i++) { g=(*GraphView)[i]; if (!g->isEmpty()) { numTotal++; if (!g->visible()) { numOff++; } } } ui->graphCombo->setItemIcon(0, numOff ? *icon_warning : *icon_up_down); QString graphText; if (numOff == 0) graphText = QObject::tr("%1 Graphs").arg(numTotal); else graphText = QObject::tr("%1 of %2 Graphs").arg(numTotal-numOff).arg(numTotal); ui->graphCombo->setItemText(0, graphText); } void Daily::setFlagText () { int numOff = 0; int numTotal = 0; for (int i=1; i < ui->eventsCombo->count(); ++i) { numTotal++; ChannelID code = ui->eventsCombo->itemData(i, Qt::UserRole).toUInt(); schema::Channel * chan = &schema::channel[code]; if (!chan->enabled()) numOff++; } ui->eventsCombo->setItemIcon(0, numOff ? *icon_warning : *icon_up_down); QString flagsText; if (numOff == 0) flagsText = (QObject::tr("%1 Event Types")+" ").arg(numTotal); else flagsText = QObject::tr("%1 of %2 Event Types").arg(numTotal-numOff).arg(numTotal); ui->eventsCombo->setItemText(0, flagsText); } void Daily::on_graphCombo_activated(int index) { if (index<0) return; gGraph *g; QString s; s=ui->graphCombo->currentText(); if (index > 0) { bool b=!ui->graphCombo->itemData(index,Qt::UserRole).toBool(); ui->graphCombo->setItemData(index,b,Qt::UserRole); ui->graphCombo->setItemIcon(index, b ? *icon_on : *icon_off); g=GraphView->findGraphTitle(s); g->setVisible(b); } ui->graphCombo->setCurrentIndex(0); setGraphText(); updateCube(); GraphView->updateScale(); GraphView->redraw(); } void Daily::updateCube() { //brick.. if ((GraphView->visibleGraphs()==0)) { ui->toggleGraphs->setVisible(false); // ui->toggleGraphs->setArrowType(Qt::UpArrow); // ui->toggleGraphs->setToolTip(tr("Show all graphs")); // ui->toggleGraphs->blockSignals(true); // ui->toggleGraphs->setChecked(true); // ui->toggleGraphs->blockSignals(false); if (ui->graphCombo->count() > 0) { GraphView->setEmptyText(STR_Empty_NoGraphs); } else { if (!p_profile->GetGoodDay(getDate(), MT_CPAP) && !p_profile->GetGoodDay(getDate(), MT_OXIMETER) && !p_profile->GetGoodDay(getDate(), MT_SLEEPSTAGE) && !p_profile->GetGoodDay(getDate(), MT_POSITION)) { GraphView->setEmptyText(STR_Empty_NoData); } else { if (GraphView->emptyText() != STR_Empty_Brick) GraphView->setEmptyText(STR_Empty_SummaryOnly); } } } else { ui->toggleGraphs->setVisible(false); // ui->toggleGraphs->setArrowType(Qt::DownArrow); // ui->toggleGraphs->setToolTip(tr("Hide all graphs")); // ui->toggleGraphs->blockSignals(true); // ui->toggleGraphs->setChecked(false); // ui->toggleGraphs->blockSignals(false); } } void Daily::on_toggleGraphs_clicked(bool /*checked*/) { //QString s; //QIcon *icon=checked ? icon_off : icon_on; if (ui->graphCombo == nullptr ) qDebug() << "ToggleGraphs clicked with null graphCombo ptr"; else qDebug() << "ToggleGraphs clicked with non-null graphCombo ptr"; return; /* for (int i=0;igraphCombo->count();i++) { s=ui->graphCombo->itemText(i); ui->graphCombo->setItemIcon(i,*icon); ui->graphCombo->setItemData(i,!checked,Qt::UserRole); } for (int i=0;isize();i++) { (*GraphView)[i]->setVisible(!checked); } updateCube(); GraphView->updateScale(); GraphView->redraw(); */ } void Daily::updateGraphCombo() { ui->graphCombo->clear(); gGraph *g; ui->graphCombo->addItem(*icon_up_down, tr("10 of 10 Graphs"), true); // Translation only to define space required for (int i=0;isize();i++) { g=(*GraphView)[i]; if (g->isEmpty()) continue; if (g->visible()) { ui->graphCombo->addItem(*icon_on,g->title(),true); } else { ui->graphCombo->addItem(*icon_off,g->title(),false); } } ui->graphCombo->setCurrentIndex(0); setGraphText(); updateCube(); } void Daily::on_eventsCombo_activated(int index) { if (index<0) return; if (index > 0) { ChannelID code = ui->eventsCombo->itemData(index, Qt::UserRole).toUInt(); schema::Channel * chan = &schema::channel[code]; bool b = !chan->enabled(); chan->setEnabled(b); ui->eventsCombo->setItemIcon(index,b ? *icon_on : *icon_off); } ui->eventsCombo->setCurrentIndex(0); setFlagText(); GraphView->redraw(); } void Daily::on_toggleEvents_clicked(bool /*checked*/) { QString s; //QIcon *icon=checked ? icon_on : icon_off; if (ui->toggleEvents == nullptr ) qDebug() << "ToggleEvents clicked with null toggleEvents ptr"; else qDebug() << "ToggleEvents clicked with non-null toggleEvents ptr"; return; /* ui->toggleEvents->setArrowType(checked ? Qt::DownArrow : Qt::UpArrow); ui->toggleEvents->setToolTip(checked ? tr("Hide all events") : tr("Show all events")); // ui->toggleEvents->blockSignals(true); // ui->toggleEvents->setChecked(false); // ui->toggleEvents->blockSignals(false); for (int i=0;ieventsCombo->count();i++) { // s=ui->eventsCombo->itemText(i); ui->eventsCombo->setItemIcon(i,*icon); ChannelID code = ui->eventsCombo->itemData(i).toUInt(); schema::channel[code].setEnabled(checked); } updateCube(); GraphView->redraw(); */ } //void Daily::on_sessionWidget_itemSelectionChanged() //{ // int row = ui->sessionWidget->currentRow(); // QTableWidgetItem *item = ui->sessionWidget->item(row, 0); // if (item) { // QDate date = item->data(Qt::UserRole).toDate(); // LoadDate(date); // qDebug() << "Clicked.. changing date to" << date; // // ui->sessionWidget->setCurrentItem(item); // } //} void Daily::on_splitter_2_splitterMoved(int, int) { int size = ui->splitter_2->sizes()[0]; if (size == 0) return; // qDebug() << "Left Panel width set to " << size; AppSetting->setDailyPanelWidth(size); } OSCAR-code-v1.3.1/oscar/daily.h000066400000000000000000000250331417327530600160560ustar00rootroot00000000000000/* Daily GUI Headers * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef DAILY_H #define DAILY_H #include #include #include #include #include #include #include #include #include #include #include #include "SleepLib/profiles.h" #include "mainwindow.h" #include "Graphs/gSummaryChart.h" #include "Graphs/gGraphView.h" #include "Graphs/gLineChart.h" #include "sessionbar.h" #include "mytextbrowser.h" namespace Ui { class Daily; } class MainWindow; /*! \class Daily \brief OSCAR's Daily view which displays the calendar and all the graphs relative to a selected Day */ class Daily : public QWidget { Q_OBJECT public: /*! \fn Daily() \brief Constructs a Daily object \param parent * (QObject parent) \param shared * Creates all the graph objects and adds them to the main gGraphView area for this tab. shared is not used for daily object, as it contains the default Context.. */ explicit Daily(QWidget *parent, gGraphView *shared); ~Daily(); /*! \fn ReloadGraphs() \brief Reload all graph information from disk and updates the view. */ void ReloadGraphs(); /*! \fn ResetGraphLayout() \brief Resets all graphs in the main gGraphView back to constant heights. */ void ResetGraphLayout(); /*! \fn ResetGraphOrder() \brief Resets all graphs in the main gGraphView back to their initial order. */ void ResetGraphOrder(int type = 0); /*! \fn updateLeftSidebar() /brief Updtes left sidebar to reflect changes in pie chart visibility */ void updateLeftSidebar(); /*! \fn graphView() \returns the main graphView area for the Daily View */ gGraphView *graphView() { return GraphView; } /*! \fn RedrawGraphs() \brief Calls updateGL on the main graphView area, redrawing the OpenGL area */ void RedrawGraphs(); /*! \fn LoadDate() \brief Selects a new day object, unloading the previous one, and loads the graph data for the supplied date. \param QDate date */ void LoadDate(QDate date); /*! \fn getDate() \brief Returns the most recently loaded Date \return QDate */ QDate getDate() { return previous_date; } /*! \fn UnitsChanged() \brief Called by Profile editor when measurement units are changed */ void UnitsChanged(); /*! \fn GetJournalSession(QDate date) \brief Looks up if there is a journal object for a supplied date \param QDate date \returns Session * containing valid Journal Session object or nullptr if none found. */ Session * GetJournalSession(QDate date); QString GetDetailsText(); /*! \fn eventBreakdownPie() \brief Returns a pointer to the Event Breakdown Piechart for the Report Printing module \returns gGraph * object containing this chart */ gGraph * eventBreakdownPie() { return graphlist["EventBreakdown"]; } void clearLastDay(); /*! \fn Unload(QDate date) \brief Saves any journal changes for the provided date. \param QDate date */ void Unload(QDate date=QDate()); void setSidebarVisible(bool visible); void setCalendarVisible(bool visible); void addBookmark(qint64 st, qint64 et, QString text); void hideSpaceHogs(); void showSpaceHogs(); QLabel * getDateDisplay(); //void populateSessionWidget(); public slots: void on_LineCursorUpdate(double time); void on_RangeUpdate(double minx, double maxx); private slots: void on_ReloadDay(); /*! \fn on_calendar_currentPageChanged(int year, int month); \brief Scans through all days for this month, updating the day colors for the calendar object \param int year \param int month */ void on_calendar_currentPageChanged(int year, int month); /*! \fn on_calendar_selectionChanged(); \brief Called when the calendar object is clicked. Selects and loads a new date, unloading the previous one. */ void on_calendar_selectionChanged(); /* Journal Notes edit buttons I don't want to document */ void on_JournalNotesItalic_clicked(); void on_JournalNotesBold_clicked(); void on_JournalNotesFontsize_activated(int index); void on_JournalNotesColour_clicked(); void on_JournalNotesUnderline_clicked(); void on_treeWidget_itemSelectionChanged(); /*! \fn on_nextDayButton_clicked(); \brief Step backwards one day (if possible) */ void on_prevDayButton_clicked(); /*! \fn on_nextDayButton_clicked(); \brief Step forward one day (if possible) */ void on_nextDayButton_clicked(); /*! \fn on_calButton_toggled(); \brief Hides the calendar and put it out of the way, giving more room for the Details area. */ void on_calButton_toggled(bool checked); /*! \fn on_todayButton_clicked(); \brief Select the most recent day. */ void on_todayButton_clicked(); /*! \fn Link_clicked(const QUrl &url); \brief Called when a link is clicked on in the HTML Details tab \param const QUrl & url */ void Link_clicked(const QUrl &url); void on_evViewSlider_valueChanged(int value); /*! \fn on_treeWidget_itemClicked(QTreeWidgetItem *item, int column); \brief Called when an event is selected in the Event tab.. Zooms into the graph area. \param QTreeWidgetItem *item \param int column */ void on_treeWidget_itemClicked(QTreeWidgetItem *item, int column); /*! \fn graphtogglebutton_toggled(bool) \brief Called to hide/show a graph when on of the toggle bottoms underneath the graph area is clicked \param bool button status */ void graphtogglebutton_toggled(bool); /*! \fn on_addBookmarkButton_clicked() \brief Current selected graph Area is added to Bookmark's list for this day's journal object. */ void on_addBookmarkButton_clicked(); /*! \fn on_removeBookmarkButton_clicked() \brief Currently selected bookmark is removed from this day's Bookmark list. */ void on_removeBookmarkButton_clicked(); /*! \fn on_bookmarkTable_itemClicked(QTableWidgetItem *item); \brief Called when a bookmark has been selected.. Zooms in on the area \param QTableWidgetItem *item */ void on_bookmarkTable_itemClicked(QTableWidgetItem *item); /*! \fn on_bookmarkTable_itemChanged(QTableWidgetItem *item); \brief Called when bookmarks have been altered.. Saves the bookmark list to Journal object. */ void on_bookmarkTable_itemChanged(QTableWidgetItem *item); /*! \fn on_ouncesSpinBox_valueChanged(int arg1); \brief Called when the zombie slider has been moved.. Updates the BMI dislpay and journal objects. Also Refreshes the Overview charts */ void on_ZombieMeter_valueChanged(int value); void on_graphCombo_activated(int index); void on_toggleGraphs_clicked(bool checked); /*! \fn on_weightSpinBox_editingFinished(); \brief Called when weight has changed.. Updates the BMI dislpay and journal objects. Also Refreshes the Overview charts */ void on_weightSpinBox_editingFinished(); /*! \fn on_ouncesSpinBox_editingFinished(); \brief Called when weights ounces component has changed.. Updates the BMI dislpay and journal objects. Also Refreshes the Overview charts */ void on_ouncesSpinBox_editingFinished(); void on_ouncesSpinBox_valueChanged(int arg1); void on_weightSpinBox_valueChanged(double arg1); void doToggleSession(Session *); void on_eventsCombo_activated(int index); void on_toggleEvents_clicked(bool checked); void updateGraphCombo(); void on_splitter_2_splitterMoved(int pos, int index); protected: virtual void showEvent(QShowEvent *); private: /*! \fn CreateJournalSession() \brief Create a new journal session for this date, if one doesn't exist. \param QDate date Creates a new journal Machine record if necessary. */ Session * CreateJournalSession(QDate date); /*! \fn update_Bookmarks() \brief Saves the current bookmark list to the Journal object */ void update_Bookmarks(); /*! \fn Load(QDate date) \brief Selects a new day object, loads it's content and generates the HTML for the Details tab \param QDate date */ void Load(QDate date); /*! \fn UpdateCalendarDay(QDate date) \brief Updates the calendar visual information, changing a dates color depending on what data is available. \param QDate date */ void UpdateCalendarDay(QDate date); /*! \fn UpdateEventsTree(QDate date) \brief Populates the Events tree from the supplied Day object. \param QTreeWidget * tree \param Day * */ void UpdateEventsTree(QTreeWidget * tree,Day *day); virtual bool eventFilter(QObject *object, QEvent *event); void updateCube(); void setGraphText(); void setFlagText(); QString getLeftAHI (Day * day); QString getSessionInformation(Day *); QString getMachineSettings(Day *); QString getStatisticsInfo(Day *); QString getCPAPInformation(Day *); QString getOximeterInformation(Day *); QString getEventBreakdown(Day *); QString getPieChart(float values, Day *); QString getIndicesAndPie(Day *, float hours, bool isBrick); QString getSleepTime(Day *); QString getLeftSidebar (bool honorPieChart); QHash graphlist; QHash GraphToggles; QVector GraphAction; QGLContext *offscreen_context; QList splitter_sizes; Ui::Daily *ui; QDate previous_date; QMenu *show_graph_menu; gGraphView *GraphView,*snapGV; MyScrollBar *scrollbar; QHBoxLayout *layout; QLabel *emptyToggleArea; QIcon * icon_on; QIcon * icon_off; QIcon * icon_up_down; QIcon * icon_warning; SessionBar * sessionbar; MyLabel * dateDisplay; MyTextBrowser * webView; Day * lastcpapday; gLineChart *leakchart; bool ZombieMeterMoved; bool BookmarksChanged; }; #endif // DAILY_H OSCAR-code-v1.3.1/oscar/daily.ui000066400000000000000000001351121417327530600162440ustar00rootroot00000000000000 Daily 0 0 968 452 0 0 0 0 0 255 255 255 255 255 255 255 255 255 127 127 127 170 170 170 0 0 0 255 255 255 0 0 0 255 255 255 255 255 255 0 0 0 255 255 255 255 255 220 0 0 0 0 0 0 255 255 255 255 255 255 255 255 255 127 127 127 170 170 170 0 0 0 255 255 255 0 0 0 255 255 255 255 255 255 0 0 0 255 255 255 255 255 220 0 0 0 127 127 127 255 255 255 255 255 255 255 255 255 127 127 127 170 170 170 127 127 127 255 255 255 127 127 127 255 255 255 255 255 255 0 0 0 255 255 255 255 255 220 0 0 0 Form 0 0 0 0 0 Qt::Horizontal Qt::Vertical 0 0 1 0 4 0 4 0 0 0 25 16777215 25 Go to the previous day QToolButton { background: transparent; border-radius: 8px; border: 2px solid transparent; } QToolButton:hover { border: 2px solid #456789; } QToolButton:pressed { border: 2px solid #456789; background-color: #89abcd; } :/icons/arrow-left.png:/icons/arrow-left.png Qt::ToolButtonIconOnly false Qt::NoArrow 0 0 Show or hide the calender Qt::LeftToRight QToolButton { border: 2px solid #aaaaaa; border-radius: 10px; background: white; } QToolButton:hover { border: 2px solid #456789; border-radius: 10px; } QToolButton:pressed { border: 2px solid #456789; border-radius: 10px; } true true Qt::ToolButtonTextBesideIcon true Qt::UpArrow 0 0 0 25 16777215 25 Go to the next day Qt::LeftToRight QToolButton { background: transparent; border-radius: 8px; border: 2px solid transparent; } QToolButton:hover { border: 2px solid #456789; } QToolButton:pressed { border: 2px solid #456789; background-color: #89abcd; } :/icons/arrow-right.png:/icons/arrow-right.png Qt::ToolButtonIconOnly false Qt::NoArrow 0 0 0 25 16777215 25 Go to the most recent day with data records QToolButton { background: transparent; border-radius: 8px; border: 2px solid transparent; } QToolButton:hover { border: 2px solid #456789; } QToolButton:pressed { border: 2px solid #456789; background-color: #89abcd; } :/icons/arrow-end.png:/icons/arrow-end.png Qt::ToolButtonIconOnly false 0 0 QFrame::StyledPanel QFrame::Raised 0 0 0 0 0 0 0 0 200 0 0 #qt_calendar_nextmonth { background: transparent; border-radius: 8px; } #qt_calendar_nextmonth:hover { border: 2px solid #456789; } #qt_calendar_nextmonth:pressed { border: 2px solid #456789; background: #89abcd; } #qt_calendar_prevmonth { background: transparent; border-radius: 8px; } #qt_calendar_prevmonth:hover { border: 2px solid #456789; } #qt_calendar_prevmonth:pressed { border: 2px solid #456789; background: #89abcd; } #qt_calendar_monthbutton { background: transparent; border: 2px solid transparent; border-radius: 8px; width: 6em; } #qt_calendar_monthbutton:hover { border: 2px solid #456789; } #qt_calendar_monthbutton:pressed { border: 2px solid #456789; background: #89abcd; } #qt_calendar_yearbutton { background: transparent; border-radius: 8px; } #qt_calendar_yearbutton:hover { border: 2px solid #456789; } #qt_calendar_yearbutton:pressed { border: 2px solid #456789; background: #89abcd; } Qt::Monday false QCalendarWidget::ShortDayNames QCalendarWidget::NoVerticalHeader true 0 1 0 0 true 1 true Events 0 0 0 0 0 QAbstractItemView::NoEditTriggers true true false false false 1 4 4 4 4 View Size 1 30 Qt::Horizontal QSlider::TicksAbove 2 0 0 3 QLCDNumber::Flat Notes 0 0 0 0 0 QFrame::StyledPanel QFrame::Raised 2 4 0 0 0 0 0 Journal Qt::Horizontal QSizePolicy::Maximum 342 20 true i true B true u color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 rgba(255, 0, 0, 255), stop:0.166 rgba(255, 255, 0, 255), stop:0.333 rgba(0, 255, 0, 255), stop:0.5 rgba(0, 255, 255, 255), stop:0.666 rgba(0, 0, 255, 255), stop:0.833 rgba(255, 0, 255, 255), stop:1 rgba(255, 0, 0, 255)) Color 0 0 85 16777215 Small 3 QComboBox::AdjustToContents Small Medium Big 0 0 QFormLayout::AllNonFixedFieldsGrow 4 4 2 0 2 2 0 0 true Zombie false I'm feeling ... Qt::AlignCenter 0 0 Weight If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value 1499.000000000000000 QSlider::groove:horizontal { border: 1px solid #999999; border-radius: 4px; height: 8px; /* the groove expands to the size of the slider by default. by giving it a height, it has a fixed size */ background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #ff6060, stop:1 #60ff60); margin: 2px 0; } QSlider::handle:horizontal { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f); border: 1px solid #5c5c5c; width: 18px; margin: -2px 0; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */ border-radius: 3px; } 10 5 5 Qt::Horizontal QSlider::TicksAbove 1 0 0 true Awesome Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 QFrame::NoFrame false 4 QLCDNumber::Flat 0 0 B.M.I. Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Bookmarks 4 4 4 4 Add Bookmark true QAbstractItemView::SingleSelection QAbstractItemView::SelectRows true true Starts Notes Remove Bookmark 0 1 0 0 0 0 0 0 QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Sunken 2 2 0 2 0 QToolButton { background: transparent; border-radius: 8px; border: 2px solid transparent; } QToolButton:hover { border: 2px solid #456789; } QToolButton:pressed { border: 2px solid #456789; background-color: #89abcd; } Flags true true Qt::ToolButtonTextBesideIcon false Qt::DownArrow 12 QLayout::SetMaximumSize QToolButton { background: transparent; border-radius: 8px; border: 2px solid transparent; } QToolButton:hover { border: 2px solid #456789; } QToolButton:pressed { border: 2px solid #456789; background-color: #89abcd; } Graphs true Qt::ToolButtonTextBesideIcon false Qt::DownArrow 0 0 Show/hide available graphs. QComboBox::AdjustToContents 20 20 false OSCAR-code-v1.3.1/oscar/docs/000077500000000000000000000000001417327530600155305ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/docs/0.0.gif000066400000000000000000000267641417327530600165330ustar00rootroot00000000000000GIF89a, "        !!! "#*!-*$#**+& , .,)/,0,43*334364373 :7;<<=1=;@>??@;@<@@A8PBAD92DCBFHHDJD JJKGLLALLMHkMQQRI^RRSQQTR"UQUQ XZ XZX\ZYZY[Rlbdc[ cabcccbd]nfg]gfjjkj kjklmplqjqsrrsrsv#uqxxy|zzzzz{q|y}Ӂ|. ׋! ,۔ ޜƛ& ߦ̥⮮/TͲTв ӳZεaѸ繼 غlӼt {"Ɉ9$ϔҜ#اܲ"ߺ# )4GHL&:PH*ZX̢.z` H2hL6pH:x̣> IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌,!,w- H`?~+ȰÇ#;pċ. ?{/I; 9ɉbʜC}V ˟ q󤾡 1r6d)Ԍ3XjxRQv~U7lٰgۺiVbwC{WXէ@xq⡋:5֫v ˙r?wZ- WΧSd]uqN lS-ìawA:Oqck4rCCC$_S}hDZ7R~~(~xΩ>NQG_N9__!,w- H`M+ȰÇ#,B36FL CD-(Qr(Rdɓ)RI&WeiO8PO= dSf9JdDN3Uɐ:m`USʪlȦOTq8E[[zߊ"O!/_Q#6vl%dc X1vF[yHqP;QuKYޘ9yU{`?REkY\q<+-*{"V/d8D -G!<6ꆣf)o> "^ AEU8mœO5Us)C\EU' wT2}{iy8\U`q_U9WUUTUw2o IS@>$OPRUzEU9IUECԠC&Uc:XDgzƀ 6$"-R DP?8RuD!['Q>QxVۅ.jCVff?b(QG?>ZP>-dq5jCD_v+CkAPUZP!) 9;) Uan>q _Ԧ_nCUT4CҡYӾe`e)Z'n]1D'U1Bds2~F?ŲRT)|Ա2 (̀BODž3UA?4tKET\wRDvqher9 e`}\PL0l*.[%o5CnP>%ِ7[: xUM{ߵTENSeZt 8DvTmzS)Q`=r+Ny O^;!AGSJ‚bVm>R&|}K<|TO -?P6O'Hcٽ}TIM @rqC*f8P#K?n<e$XC8,1ҠL~׾8yC %~"0& 7|! EXBB?yE! !,w- H`5br1+ȰÇ#;pċV#D% >~PM(QnH&S H͛2"Ϗ4БIΟ"[O J0EK>_,*U?fTv)/ɇʴ="UbcV+gK- p_X_\ۆ ܵ)hqRO'K US|06A!rS#%-Qeu.2#\s遲ŭO=DMA1>T#I9V |>tkD\~!ىx}j?s(5XFsQ CbU D;SNEr GyU$_2X'gG@DZi"V->"X1:ĄZ-ҦT5TcA' ݈3t'E YrSFX>VDwvQXjӆ fhDB\~GohrjH`iHi>YDIiO2h@obDUcCmbEZ4CbuRi"5,jː$jp:tVJ4-RR&+Q?E>=+HkĮH6OݮV}Yr۪V_Tp y"Iuc_9'E•ZYFըO6RڝS E)XL>DK݌Ue)D5ZRAԊ`%X9s\- E5R5D,XQuX^vC(DtUokϩ`vk檅64H1AEA Q\VHa6_?!н,XU`i~oOg m]ZZPe2 Uqc;{>&|Q·E, Q@џjDm/m̚} 땁W=1P=}DCC$hF⛶I 3!8\)R CւZ;@,]]eD#BA0_E'2 1*AԊ]5@Z'׹P4! A<Fj A10#`tޥ$A 1@F.0a/x>4@X1 !,w- H] *\ȰCJ*2jdOB AZĸQcǏ!)Zɲ"Y w])IsA)sY͐zjЍУC`ihϤ 18ĩSEKCV*Du n*R։cN:%ua\B=S"H2X${M1i{lqOǐNnhYu&傡N%H= ,1Bٞƭv壺IWV^9-иa= iϾ 2g&v0Fb~l^#z -ϵ蒙of.G-אs=Ex uG?C VPfC w{ u|( C((li/nC2PrXR Wi>ҤWG'kE$CBD`MnBQTstpGtV?>FbaMNsFi*D&M_fFq4 "TwgARf4-G٣IQS*`MDUYi+RIC2Txё8&I*P 5UwCSX<"MQV,My蓰)yB˦%uT[ڪZk kbC#ҤkAW8abS1 0y5-[mZS`>5؃wSRѹy=e`9~\Pc+2q0[ׄA2#.6Dm@ psaFKvBP:mPVuCN}/g-CcV6)\::r;Av[G5Df|>$'` J0JL7^xrtX!DN}MyGl99:ƖLGN:5g.س 5ȑesDx# L.oϽƜ`dd 7n׃ AP:~FP $8@!#>L&z7M%@*ua "0(#ma`YFz8QMC4#]LPo PÚ +Uia@P-C (P< g5_xguL?^t A@,mKHP: ,D, p (( " &, :ڊ ,@R"P)A`'8F fpE*N8j5y @Z(&kXvR,li&@Ӗ7ƣ/`}  Tsd6:$_,@@D? BP gdP,`f !34eG$3U}D JG%x*{;uH: c8-Qՙ=5_< ca@d|b%:z5\+~>5$.]czPdF~&֨KC\*пA*jF8DQlJ=HvQ-_WN>7Є [RV4U=*jrrvUnb PYr e@ Jt@(RD('PrE(@j )f @1 & Pm=oB <`Dc˃\ON>bK}ؤDPA"0:fa1Nr Q\JJI9 Ȗ*8-$hB3NIL+֌H24 ra@pI9G{qۇЍo4 xq恈0$ꁉ  _pevD(?n$NôV :č?4 S}lb Lu؛=Msz~)h oSM #ƫۛ>!&rf+R\;KP;<@U~ M HmqÁG^s"/yQp,c%@dsQFpX`خ#sT 0\H9T nN^sAfcC׷T>wA} @gs4C vnL޳Y- 2f%## 4Y3-$iXKיnb'a )&#ރ˜p ^ȰglUj65.UWO%R  teSĹ0MdK 0C gKHė B;cCc 0|1@4 %# w ¤`I zAK p qv}@ #I%%/J(Li[ ϰy3!i){A @` G+&8 +` pDR2ŒCh  %ȀI .O@ ?TFa? u zWpZ B'YE蠑 P@OF @O3@DQ9Y0TDWzY` ZpD"P ã]! ܀+ Ai$ 0TAP :HGI74QEv3 08Q@ }Ϡ`] PD,p*$TcQ!} ,@EϺHR@/eK,P 93м{"հ ynڛ'H|"R+~jQ*\! sd`ˠ.(@rs b, Dri1=NbZװkp` Ѭ,", r` 4PE<0 Vphs#>B/"fp7? 3g v3 s*FЃ BQ8Q0 V@/Fp8; JR`  *Ӏq V0A ҰR k}p-W !, V(8P ٥m(pUp۫25@`R$V׷> @ *ڹƴ4 RȀ V Y VPjL| B% 1# 6'g r@ Ȁ pI`d . x<0'4 ]P @Ĝ g4+>((.( ʀ 90R>.S Π3e;OSCAR-code-v1.3.1/oscar/docs/GPLv3-en_US000066400000000000000000001045131417327530600173610ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . OSCAR-code-v1.3.1/oscar/docs/PRS1 Data Format.odt000066400000000000000000000343561417327530600210430ustar00rootroot00000000000000PKB>^2 ''mimetypeapplication/vnd.oasis.opendocument.textPKB>Configurations2/statusbar/PKB>'Configurations2/accelerator/current.xmlPKB>Configurations2/floater/PKB>Configurations2/popupmenu/PKB>Configurations2/progressbar/PKB>Configurations2/toolpanel/PKB>Configurations2/menubar/PKB>Configurations2/toolbar/PKB>Configurations2/images/Bitmaps/PKB> content.xml7 uQh,tmop.&+追ySd䶌Me42aNZ̔N &pkP6Mpqǧԉʩa>)E&+Ebn -q'Z(|r$ޏ|O3^q7%e#3{b <+@ D:.MMȅB탌Ʃ]lū4#3I"f# ٘`Inۅemb Դx%f l# RT 7W53< =$"zvS_wy(Ԙ^Z~vs/pq'Ϧ>[#>^y{@D eq 9 /m>hYbM;xdSeS1mmPl,RkekOb뿯(mOI͢z ޚJXdn}^`ͨѫD܍(2:&e[q`e^LI#x7 Bl4 e,!hsM&055㳀yPri:DTW\&Ө.!co%[ 028ʤ[cMgc0c.9q6a|.}/zI1l0)4`Cݛ Dl.)?be lxɸ0&X1T('j$/=벃 ć, *GJ{q6XY<s$ Fˆ+ ^aCCxjxw2#ޡa/_l/\6D8C :h"MŌYcL<S@eJCpHD~A@) 遇)ΎAgriBK:O%hPtbcdY-x]49-_{_)G_^OG uw۹ԐTK)'VtlV>E;@&@54=bkgw\5jOfQCiZ0 SG k A# K5+.< k."c a hd"'ϒQrW6'%%<8T[ؼ\~ Uq WlYE fl뿱Md Ǭr{j#|$<~3aAT\8',Yo Zl>Wv a~fd Q41bMT('=}tNMh 8%X_ XZ6Cb$pK߲9p,'p 8ّ`p}[#C*P5_`X"܁rLF\mW>3бi!^A-DDwΈLlq$'[ﲔ~as$)['Br5A(laH*[N 58K8VwqCxH˙nl ,ynuv"ߧK#n*'B3q!kE @ÉLp!XYn$er}*CQ{@o<4bDAZxI܉:GF_۷n耎~~xx(pmc$"xCW)l!֫Ǯ W;t\@!v{@"3q;03U,=îbEܨ s /Uj#%e|̑rNa7D4nE+ (p[( &04S9,HBv 3)7DZuSD=~Mo([Th4K^XY*%p`o}n4$&)x{(!oeTژ׷n- ੋh뵡hx +(vl2DVW8_#*Z nQ6ʇ| t~%/w䇿[,|G=[nd$kȞwcx+E<0kǓ9f]3+O\:5-z 0enrh~)%_:C/_y en<:izV^o59+~S]t "g}-~-]LV/y=p)* O<$o&;$jUm}YG񬑜l{_K) BlzZѐΪ`vE7oq*c>y*5tJZ=~:|6 KEc[rkEؒG< &'{q/:afp%|xECEiv˖mGng73[bd-Dd Jd'9דS~6x!602]YǀRnWýU5=;===}Q9Zֺ/NYն K  +֬wցN:U+8 60QsǓoQ9@坜L\JE0 ,p~ ,v D4gxQ.?ނ;-'qf7\V_ 7iT>/*&u} L,,-U9)VoISB>jA#nK4Stu@v6rOu8^@ R<6tߩM%f 2gNOXcvx1; v2Nk} k[w/5~*5n ^۹*ف^;5{>ViV8̾^Po+XFOTE{ O07K(ÿVV$\O1(D=w?Eɺ)1M+ L{ 5Ek߀! f2M#0 ~̄'ɮM vWau_Tݒ*WwQ5_NN@)O6&4(aon!TJIXi,@|-gSvMK-yFZr'Q|B 2;sUwVţ_*tlubؐ12oCoVs勐@$^f7'..V =<1 manifest.rdfn0EwrfYdhjCP}]ҢUչ:8<{Όl[7?QF=O,-p*Q,Tk!ɲ C"[2b,#V`_A":q4v!Zl2 Kc~|' *S "tr5M }j"'.#*u15teJ]a2zPKB> styles.xml[[۸~0hd]f&3v,] 0v7˂(HʗD˒Gs4sxx+)e1b.+/ ӄnVޯ_y߿[e$˔%u<X`2KC\y5KKJ,2Y S;ir/RfD :]3%˩\fvgNV;=cS'EgOXY!IzZ B\jn3 bhjpU5/4Wj1D(%h~U N*݌@O |lޫtyRwnd>b#;_ԵT 'mnw>cUUM0Ս:0Y's=9˞"ighrSզG%WVlzt6rYᥨutԹ -yfj]Z+9Ɨ_EU naGZ D0\+-P#B@K~xlQ %OqRwoM03[AYNGEm'7%)+/b}N3GgaQ}FHo0Ŝ,ŜTD&!>r]fzq]^as?39avvg;KqjhP-8G(^4I7!Z/vdg;hwH/r ƒW3n\g.Jn4pT$,oۯ8Q. ɪ[l|_y*=QBgC#}QH?g|QX㻳[Fr o ,cGdJTvV۞Ė̖zQS¬w֕P(k]Ve`Oao@aEx9v m^Ԛ֋%Tz@ Gm"{!%"W'L SUp1'$sf\Er=HG.s-#8qK2W*Z]иgMxԃT{R'G@{BU _`8mÐZ%q:Yun7fR4(nHc]ƨءx(Ìu:IbJRExϐE SW:86xē}Г{E)8㸅9? */h+G."SC[R݉rh'%j8#܌@3ɦ04ԁjZujbm Zhyg-Bf>S*ӝ9s^`}UiϤB5c*؀=Y KJBGN[ϐ]-ei[6 ]a~|Zg-mr`IGXv$U_Nt38h}F 6= gFG?e#d(^[\4&@֥oqQL. O[2d U%KƘTL5;62vp _"9bK}T9b)H"Cˌ^˙Gb}xBҡlM~'?=Q7e][ڀ{t9*#ow)H Ƕ0jPrL6vt'z&^n{K9Bc{3%ڷ{Q|;Aʊ3Xp' C,(۸7n0W#C洛1R__ݩ3x*h? S4ݟFNߟOhj%vfPI:AxCrQȈ$S1{wPK22PKB>K3QBBmeta.xml Mark Watkins2011-07-07T16:25:472011-07-21T18:21:13Mark WatkinsPT5H59M16S35LibreOffice/3.3$Linux LibreOffice_project/330m19$Build-301PKB>& \Thumbnails/thumbnail.pngPNG  IHDRZpIDATx흍n* ]}u|M9 ?|V4;ƀI_|z#{rm~N.:-ObP?&n[h~Ʀ ?r'l3 c!jxn)VR˯+p&=r˲S4eKo<Ӛ/P3_qoHioHپN=50R(u7ݾ/1[a Qf^ a(`])]iX)g;}iOC ),~'qp'1?c5Sli :ًۗ[B6HӞس4ΕM[:o˼> PB@x[B_89Nrneҟ?CJPm\wJL*ĶbGN@kGdYVB8WϕJ.&og9TcY7Y vPP`v${A#|re5u)(B;fSr)zA1qSϿ8Umpd=KTy;`w{HYKҽBt9MgX"nG(]Buowh]h!\X =L&WHb >Q F_i|}d.uUiH;Z?u YPr,: z0ZnbfnbU`((;dm7yĎu(f%TN][Q7'vQϢNU`(@9PrtQ/T KYgYB Ǝi9999Һ /};]߉kYgipy+;03C|6V8TP=x> ;7:Xy{9Lў}hX[=Ѹ7Gc_ g(@9S=KzZ7epP 6y_IENDB`PKB> settings.xmlZ]s:}"B>&0  %훰ЍHrߕ )-jWgϮ+(Qgdӻi> >ɄP0LcT1Dvk ULs],]3a mkNYd!| f$Ri>/PMKjޮ('|l{UnURp:;Tk+ٟSn 9Y=G HekA޾ lI~c Mr[.LLr@<2<a}u~q]LpDeК19`(ɠnT ttdS\Fp 1KN`Ѷ#Pމ6N 6]W)_\#S*rQ'Cn;]9rqqU$`r`DG$i3fHpˣ@cKLM E͜^I??Tz壤0 bKՎ 4=(pe`/=SXQg Ҏu }Kg x&LcIG%3OehR96K1w=%3yU|G4-Tb?ݓ}6C ?2ؓ-ڇw,wH9"w*88wjHA͠|w כL%|,$E8" U6ͪ~ VړoZHU8\f/\2 J{"U=3np 2.h,B)>0c?J#P#XgŒ$ \I|Ҡٽ e.#W4JD3s:S \iCu:̤4< RieՐQS0 ݬ-&TxeG )8 )LAҞv 34!%OOD+{JG:[8[NF-$!X'2@, \jj'KX)M[aX*kB(Ж}(AyeU0ecaΧwh0*?WQkeU6zj$0& _ -JuXΥn9&wEHi" :_ع|^Ak9PQ|By?w;x"p2ߎTkG F]*ްVFJ2(At*3Z/jIwjwMETA-INF/manifest.xmlMn0=EmeUEV g,O iKζ{3˝CTζ? tC>V [.1Rs\TmRbc`lH6ΣL-5_QiPXb)ieV([JpIVq=Цe<\9۫!8R\C()Qc޺ d a<V^; ,';#749=Xecez/WUj\>bɓr*?WO"PKcc-PKB>^2 ''mimetypePKB>MConfigurations2/statusbar/PKB>'Configurations2/accelerator/current.xmlPKB>Configurations2/floater/PKB>Configurations2/popupmenu/PKB>:Configurations2/progressbar/PKB>tConfigurations2/toolpanel/PKB>Configurations2/menubar/PKB>Configurations2/toolbar/PKB>Configurations2/images/Bitmaps/PKB>՞0` Ucontent.xmlPKB>=<1 manifest.rdfPKB>22 styles.xmlPKB>K3QBB!meta.xmlPKB>& \&Thumbnails/thumbnail.pngPKB>wH! @-settings.xmlPKB>cc-2META-INF/manifest.xmlPKph4OSCAR-code-v1.3.1/oscar/docs/about.html000066400000000000000000000062141417327530600175330ustar00rootroot00000000000000 about

Welcome to OSCAR,
the Open Source CPAP Analysis Reporter

This software has been designed to assist you in reviewing the data produced by your CPAP devices and related equipment.

Volunteers translated it into many languages. Click the Help tab to change the displayed language.

Any Chromium-based browser like Google Chrome or Microsoft Edge Chromium currently offers the best automatic translation options.
You will find extensive help in the OSCAR Wiki. Open http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

For questions visit http://www.apneaboard.com or a local apnea forum.

PLEASE READ CAREFULLY


OSCAR is NOT a substitute for competent medical guidance from your Doctor.

Due to the lack of documentation released by manufacturers regarding file formats, the accuracy of data displayed in OSCAR can not in any way be guaranteed.

All reports generated are for PERSONAL USE ONLY and are intended to be as accurate as possible.

OSCAR reports are based on data reported by the CPAP machine. Acceptance of this data for compliance or other purposes is subject to the approval discretion of the reviewing agency.
The use of this software is entirely at your own risk. The authors will not be held liable for anything related to the use or misuse of this software.

OSCAR is free (as in freedom) software, released under the GNU Public License v3, and comes with no warranty, and without ANY claims to fitness for any purpose.

OSCAR is © 2019-2020 The OSCAR Team: a group of volunteer developers from the Apnea Community and members of multiple forums and various nationalities.

OSCAR is a derivative of the SleepyHead program which is copyright © 2011-2018, Mark Watkins.
OSCAR-code-v1.3.1/oscar/docs/about.xxxx000066400000000000000000000062141417327530600176060ustar00rootroot00000000000000 about

Welcome to OSCAR,
the Open Source CPAP Analysis Reporter

This software has been designed to assist you in reviewing the data produced by your CPAP devices and related equipment.

Volunteers translated it into many languages. Click the Help tab to change the displayed language.

Any Chromium-based browser like Google Chrome or Microsoft Edge Chromium currently offers the best automatic translation options.
You will find extensive help in the OSCAR Wiki. Open http://www.apneaboard.com/wiki/index.php/OSCAR_Help.

For questions visit http://www.apneaboard.com or a local apnea forum.

PLEASE READ CAREFULLY


OSCAR is NOT a substitute for competent medical guidance from your Doctor.

Due to the lack of documentation released by manufacturers regarding file formats, the accuracy of data displayed in OSCAR can not in any way be guaranteed.

All reports generated are for PERSONAL USE ONLY and are intended to be as accurate as possible.

OSCAR reports are based on data reported by the CPAP machine. Acceptance of this data for compliance or other purposes is subject to the approval discretion of the reviewing agency.
The use of this software is entirely at your own risk. The authors will not be held liable for anything related to the use or misuse of this software.

OSCAR is free (as in freedom) software, released under the GNU Public License v3, and comes with no warranty, and without ANY claims to fitness for any purpose.

OSCAR is © 2019-2020 The OSCAR Team: a group of volunteer developers from the Apnea Community and members of multiple forums and various nationalities.

OSCAR is a derivative of the SleepyHead program which is copyright © 2011-2018, Mark Watkins.
OSCAR-code-v1.3.1/oscar/docs/channels.xml000066400000000000000000000043701417327530600200510ustar00rootroot00000000000000 OSCAR-code-v1.3.1/oscar/docs/countries.txt000066400000000000000000000034471417327530600203140ustar00rootroot00000000000000Afghanistan Albania Algeria Andorra Angola Antigua & Deps Argentina Armenia Australia Austria Azerbaijan Bahamas Bahrain Bangladesh Barbados Belarus Belgium Belize Benin Bhutan Bolivia Bosnia Herzegovina Botswana Brazil Brunei Bulgaria Burkina Burundi Cambodia Cameroon Canada Cape Verde Central African Rep Chad Chile China Colombia Comoros Congo Congo {Democratic Rep} Costa Rica Croatia Cuba Cyprus Czech Republic Denmark Djibouti Dominica Dominican Republic East Timor Ecuador Egypt El Salvador Equatorial Guinea Eritrea Estonia Ethiopia Fiji Finland France Gabon Gambia Georgia Germany Ghana Greece Grenada Guatemala Guinea Guinea-Bissau Guyana Haiti Honduras Hungary Iceland India Indonesia Iran Iraq Ireland {Republic} Israel Italy Ivory Coast Jamaica Japan Jordan Kazakhstan Kenya Kiribati Korea North Korea South Kosovo Kuwait Kyrgyzstan Laos Latvia Lebanon Lesotho Liberia Libya Liechtenstein Lithuania Luxembourg Macedonia Madagascar Malawi Malaysia Maldives Mali Malta Marshall Islands Mauritania Mauritius Mexico Micronesia Moldova Monaco Mongolia Montenegro Morocco Mozambique Myanmar, {Burma} Namibia Nauru Nepal Netherlands New Zealand Nicaragua Niger Nigeria Norway Oman Pakistan Palau Panama Papua New Guinea Paraguay Peru Philippines Poland Portugal Qatar Romania Russian Federation Rwanda St Kitts & Nevis St Lucia Saint Vincent & the Grenadines Samoa San Marino Sao Tome & Principe Saudi Arabia Senegal Serbia Seychelles Sierra Leone Singapore Slovakia Slovenia Solomon Islands Somalia South Africa Spain Sri Lanka Sudan Suriname Swaziland Sweden Switzerland Syria Taiwan Tajikistan Tanzania Thailand Togo Tonga Trinidad & Tobago Tunisia Turkey Turkmenistan Tuvalu Uganda Ukraine United Arab Emirates United Kingdom United States Uruguay Uzbekistan Vanuatu Vatican City Venezuela Vietnam Yemen Zambia Zimbabwe OSCAR-code-v1.3.1/oscar/docs/credits.html000066400000000000000000000070161417327530600200570ustar00rootroot00000000000000 credits

Credits


OSCAR is a derivative of the SleepyHead program written by Mark Watkins, during the years 2011 to 2018. The current project is the combined effort of people from CPAPtalk.com, ApneaBoard.com, and other volunteers, starting in 2019.

OpenSource Libraries
OSCAR uses the OpenSource version of the Qt cross-platform toolkit available from http://qt.io which itself draws from many smaller open source libraries. You can read the individual licensing for many of these components that are used under the hood of OSCAR at https://doc.qt.io/qt-5/licenses-used-in-qt.html

Data formats
The CPAP device data formats are mostly undocumented. Getting them working in OSCAR involved a lot of investigation, together with a lot of SD card data samples, many patient users willing to put up with crashes and data issues, and plenty of help from fellow developers out there who shared in the workload of decoding data formats. Thanks to all of you who have helped in the fight to protect our right to keep our own data open and accessible!

The current OSCAR team consists of the following:
Fred Bonjour : Project Manager & Lead Tester, Phil Olynyk : Lead Developer, Arie Klerk : Translations Team Coordinator

Developers
Phil Olynyk (Lead Developer), GuyScharf, sawinglogz

Reporters
AlanE, BrandonA, Crimson Nape, foxfire, Heyns, jeremieb, jaswilliams, palerider, patl

Testers
Fred Bonjour (Lead Tester), Beej, DeepBreathing, Fastlane, GuyScharf, JJJ, LookingForward, Pollcat, Ruth Catrin, SarcasticDave94

Advisors
aviB, SkepticDoc, Sleeprider, SleepyProgrammer, srlevine1, LunaFerret, harre, mdhamptom, mitchcampbell, rtannerf

Translators
Arie Klerk (Translations Team Coordinator, Dutch), 1st.qwerty (Polish) delta (Romanian), drol (French), FaureCourtet (French), fossegrim (Norwegian), hearsay73 (Filipino), Heyns (African), jaswilliams (British), johanh (Finnish), koimark (Finnish), Lazer1234 (Swedish), Mac_Sheepcounter (German), Perchas (Spanish), refurbished (Polish), Ristraus (Brazilian Portugese), ShaunBlake (British), steffenreitz (German), tolnaiz (Hungarian), unidee (Finnish), untoutseul05 (French), yrnkrn (Hebrew).

Thank you very much for your continuous effort!

OSCAR is always looking for help: programmers, testers, or translators. If you are interested, please PM 'bonjour' on the Apnea Board Forum.


A special mention to the ApneaBoard for providing a development forum for OSCAR and for providing the primary download site for OSCAR at https:\\sleepfiles.com\OSCAR.

Also acknowledging ApneaBoard for their support of software for CPAP users for many years.

OSCAR-code-v1.3.1/oscar/docs/credits.xxxx000066400000000000000000000070161417327530600201320ustar00rootroot00000000000000 credits

Credits


OSCAR is a derivative of the SleepyHead program written by Mark Watkins, during the years 2011 to 2018. The current project is the combined effort of people from CPAPtalk.com, ApneaBoard.com, and other volunteers, starting in 2019.

OpenSource Libraries
OSCAR uses the OpenSource version of the Qt cross-platform toolkit available from http://qt.io which itself draws from many smaller open source libraries. You can read the individual licensing for many of these components that are used under the hood of OSCAR at https://doc.qt.io/qt-5/licenses-used-in-qt.html

Data formats
The CPAP device data formats are mostly undocumented. Getting them working in OSCAR involved a lot of investigation, together with a lot of SD card data samples, many patient users willing to put up with crashes and data issues, and plenty of help from fellow developers out there who shared in the workload of decoding data formats. Thanks to all of you who have helped in the fight to protect our right to keep our own data open and accessible!

The current OSCAR team consists of the following:
Fred Bonjour : Project Manager & Lead Tester, Phil Olynyk : Lead Developer, Arie Klerk : Translations Team Coordinator

Developers
Phil Olynyk (Lead Developer), GuyScharf, sawinglogz

Reporters
AlanE, BrandonA, Crimson Nape, foxfire, Heyns, jeremieb, jaswilliams, palerider, patl

Testers
Fred Bonjour (Lead Tester), Beej, DeepBreathing, Fastlane, GuyScharf, JJJ, LookingForward, Pollcat, Ruth Catrin, SarcasticDave94

Advisors
aviB, SkepticDoc, Sleeprider, SleepyProgrammer, srlevine1, LunaFerret, harre, mdhamptom, mitchcampbell, rtannerf

Translators
Arie Klerk (Translations Team Coordinator, Dutch), 1st.qwerty (Polish) delta (Romanian), drol (French), FaureCourtet (French), fossegrim (Norwegian), hearsay73 (Filipino), Heyns (African), jaswilliams (British), johanh (Finnish), koimark (Finnish), Lazer1234 (Swedish), Mac_Sheepcounter (German), Perchas (Spanish), refurbished (Polish), Ristraus (Brazilian Portugese), ShaunBlake (British), steffenreitz (German), tolnaiz (Hungarian), unidee (Finnish), untoutseul05 (French), yrnkrn (Hebrew).

Thank you very much for your continuous effort!

OSCAR is always looking for help: programmers, testers, or translators. If you are interested, please PM 'bonjour' on the Apnea Board Forum.


A special mention to the ApneaBoard for providing a development forum for OSCAR and for providing the primary download site for OSCAR at https:\\sleepfiles.com\OSCAR.

Also acknowledging ApneaBoard for their support of software for CPAP users for many years.

OSCAR-code-v1.3.1/oscar/docs/graphs.xml000066400000000000000000000034171417327530600175430ustar00rootroot00000000000000 OSCAR-code-v1.3.1/oscar/docs/release_notes.html000066400000000000000000000362721417327530600212600ustar00rootroot00000000000000 release_notes

Changes and fixes in OSCAR v1.1.0-rc-1
Portions of OSCAR are © 2019-2020 by The OSCAR Team

  • [fix] AHI and Usage bars in overview graphs now show the correct height when oximetry usage is significantly longer than CPAP usage.
  • [fix] Improved import of Philips Respironics settings. In particular:
    • The settings for PC, S, and S/T modes are now displayed correctly.
    • AVAPS settings are now displayed correctly, including target tidal volume.
    • Backup breath settings are now imported and displayed.
    • The settings for CPAP-Check and Auto-Trial modes are now displayed correctly.
  • [fix] Fix the reported duration for extremely long sessions on DreamStation CPAP and BiPAP devices
  • [new] Additional Philips Respironics devices tested and fully supported:
    • DreamStation Auto CPAP (500X120)
  • [fix] Fix regression in Welcome page pressure display
  • [fix] Several crashes have been fixed.

Changes and fixes in OSCAR v1.1.0-beta-2
Portions of OSCAR are © 2019-2020 by The OSCAR Team

  • [new] Add preliminary support for Viatom/Wellue pulse oximeters
  • [new] Add support for Dreem hypnograms
  • [new] Extensive reorganization of the ResMed loader to facilitate understanding and future improvements
  • [new] Additional Philips Respironics devices tested and fully supported:
    • DreamStation Auto CPAP (500X130)
  • [new] Add a menu option to create a zip from an SD card
  • [new] Ask where to save screenshots
  • [new] Alert users when unexpected Philips Respironics data is encountered during import
  • [fix] Add support for CPAP mode on DreamStation BiPAP S/T and AVAPS
  • [fix] Pinch-to-zoom now works as expected
  • [fix] The Philips Respironics loader now respects the "ignore old sessions" preference
  • [fix] Improved handling of discontinuous Philips Respironics data
  • [fix] Improved import of Philips Respironics flex and humidification settings
  • [fix] Fix incorrect display of tube diameter on some Philips Respironics devices
  • [fix] Fix missing "Bi-Flex" label for bi-level DreamStations
  • [fix] Fix crashes in ZEO loader
  • [fix] Fix several memory leaks

Changes and fixes in OSCAR v1.1.0-beta-1
Portions of OSCAR are © 2019 by The OSCAR Team

  • [new] Extensive overhaul of the Philips Respironics System One importer, resolving all previously reported issues.
  • [new] The following Philips Respironics devices are now tested and fully supported:
    • REMstar Plus (System One) (251P),
    • REMstar Pro (System One) (450P, 451P),
    • REMstar Auto (System One) (550P, 551P),
    • BiPAP Auto (System One) (750P),
    • BiPAP AutoSV Advanced System One (950P, 951P),
    • REMstar Pro (System One 60 Series) (460P, 461P),
    • REMstar Auto (System One 60 Series) (560P, 561P, 562P, 560PBT),
    • BiPAP Pro (System One 60 Series) (660P),
    • BiPAP Auto (System One 60 Series) (760P),
    • BiPAP autoSV Advanced (System One 60 Series) (960P, 961P),
    • BiPAP autoSV Advanced 30 (System One 60 Series) (960T),
    • BiPAP S/T 30 (System One 60 Series) (1061T),
    • BiPAP AVAPS 30 (System One 60 Series) (1160P),
    • DreamStation CPAP (200X110),
    • DreamStation CPAP Pro (400X110, 400X150),
    • DreamStation Go (400G110),
    • DreamStation Auto CPAP (500X110, 500X150),
    • DreamStation Go Auto (500G110, 502G150),
    • DreamStation BiPAP Pro (600X110),
    • DreamStation Auto BiPAP (700X110),
    • DreamStation BiPAP autoSV (900X110, 900X120),
    • DreamStation BiPAP S/T 30 (1030X110),
    • DreamStation BiPAP S/T 30 with AAM (1030X150),
    • DreamStation BiPAP AVAPS 30 (1130X110),
    • DreamStation BiPAP AVAPS 30 AE (1131X150)
    • Note: The settings for PC, S, and S/T modes are still displayed incorrectly.
  • [new] Update translation files and add new languages
  • [new] Allow user to reset graph order on the Daily page to Standard (for CPAP and APAP) or Advanced order (for ASV and AVAPS modes)
  • [new] Add preference setting to include the serial number on machine settings list
  • [fix] Place date, time, and Oscar version information in report footers
  • [fix] Update identification of ResMed S9 devices on the Welcome page
  • [fix] Correct formatting of event number in the Daily Events tab
  • [fix] Correct timezone offset for SomnoPose imports
  • [fix] Show a progress bar when setting the Overview range to a large number of days
  • [fix] Make session bars on the Daily page clearer by using a better color
  • [fix] Improve list of devices on the Statistics page
  • [fix] Report Pressure when IPAP data is missing
  • [fix] Implement Refresh button on the Profile page

Changes and fixes in OSCAR v1.1.0-testing-4
Portions of OSCAR are © 2019 by The OSCAR Team

  • [new] Windows installers support Oscar, Oscar 32-bit, Oscar (test) and Oscar 32-bit (test)
  • [fix] Release builds use a Settings key of OSCAR, Test builds use OSCAR-test, and Branch builds use OSCAR-branch. Default data directories are similarly named.
  • [fix] Overview chart problems when both CPAP and Oximetry data were included fixed
  • [fix] Translations updated for all languages. Spanish (Mexico) and Norwegian languages added
  • [fix] All languages now available on Mac
  • [fix] Date bar on the bottom of the Daily graph now in the local time when no line cursor displayed, and formatting improved
  • [fix] 100% zoom now works on the Overview page
  • [fix] View/Reset Graphs now enables all graphs and all event flags
  • [fix] Calendar date now formatted per national settings
  • [fix] Toggle buttons on the Daily page's session list and event and chart pulldowns changed to checkboxes

Changes and fixes in OSCAR v1.1.0-testing-3
NOTE: Translations have NOT yet been updated for these changes
Portions of OSCAR are © 2019 by The OSCAR Team

  • [new] DreamStation BiPAP S/T and AVAPS ventilators (1030X and 1130X) are now supported. The settings aren't yet displayed correctly, but therapy events and graphs should now display properly.
  • [new] View/Reset Graphs organizes graphs in their original order
  • [fix] Format dates for the national region as reported by the operating system
  • [fix] Improve progress bar for the Statistics cache update
  • [fix] Correct calculations of seven-day AHI and leak rate on the Welcome page
  • [fix] Clarify AHI and hours labels on Records tab of the right sidebar
  • [fix] Correct import error resulting in invalid elapsed times and impossibly high AHI values

Changes and fixes in OSCAR v1.1.0-testing-2
NOTE: Translations have NOT yet been updated for these changes
Portions of OSCAR are © 2019 by The OSCAR Team

  • [new] Show progress bar when the Statistics page has to refresh the cache with more than 180 days in it
  • [new] Romanian language is supported
  • [fix] Show commit id in the title bar for testing and beta builds
  • [fix] Show BMI label and calculated value only when both weight and height are non-zero
  • [fix] Improve BMI display in Daily/Notes
  • [fix] Correct profile dialog to show height is measured in cm, not meters, when units system is metric
  • [fix] Always show event indices on the Daily page even when some event flags are turned off

Changes and fixes in OSCAR v1.1.0-testing-1
NOTE: Translations have NOT yet been updated for these changes
Portions of OSCAR are © 2019 by The OSCAR Team

  • [new] Offer migration if a non-default directory is selected on first use
  • [new] Hold the SHIFT key down when starting OSCAR to use Software Graphics Engine
  • [new] Support for DreamStation BiPAP autoSV (900X) should now be complete
  • [new] Improved DreamStation support
  • [new] Improve oximeter import for CM550D+
  • [new] Disable Dark Mode on Mac
  • [new] Show hours/day constituting compliance on the Statistics page
  • [new] Better order of graphs on the Daily page for newly created profiles
  • [new] Help/System Information shows info about OSCAR, OS, and data location
  • [new] Move pie chart option from Preferences dialog to View menu
  • [new] Hide pie chart when capturing screen
  • [new] The --datadir option now allows fully qualified name on Windows
  • [fix] Fix some oximeter import issues
  • [fix] Use local time rather than UTC in oximeter import
  • [fix] Improve screen capture on Mac
  • [fix] Fix crashes in CPAP data rebuild, purging of a machine
  • [fix] Prevent crash if taking a screenshot before a profile is opened
  • [fix] Increase mask vent ranges
  • [fix] Correct session bar if no sessions are present
  • [fix] Correct months shown on the Statistics page monthly view
  • [fix] Compute compliance on the Statistics page based on total days, not days used
  • [fix] Paginate the Statistics report when printing
  • [fix] Fix "phantom date" (12/31/1969) on some ResMed imports
  • [fix] Default font substituted when a specified font is not valid
  • [fix] Change Preferences measurement units choice to Metric or English
  • [fix] Improve display of cmH2O numbers
  • [fix] Show graphics engine in title bar correctly
  • [fix] Adjust the size of the Preferences dialog to fit smaller screens
  • [fix] Label climate control as manual or auto correctly on ResMed import
  • [fix] Move less useful information from title bar to Help/System Information
  • [fix] Change "Prescription Settings" to "Machine Settings" message
  • [fix] Improve icons, especially smaller ones
  • [fix] Correct Mac menu issues
  • [fix] Improve messages in debug pane
  • [fix] Re-organize build instructions and other cleanup

Changes and fixes in OSCAR v1.0.1-r-1
Portions of OSCAR are © 2019 by The OSCAR Team

  • Disable multitasking to avoid a crash during pre-loading of summaries

Changes and fixes in OSCAR v1.0.0-beta-9
Portions of OSCAR are © 2019 by The OSCAR Team

  • Picked up the latest translation files
  • Changed logo to big O with text
  • Clean up odds and ends

Changes and fixes in OSCAR v1.0.0-beta-8
Portions of OSCAR are © 2019 by The OSCAR Team

  • Picked up the latest translation files
  • Fixed filename creation for about OSCAR docs
  • Removed display of I:E ratio from the Daily page
  • Changed some Mac build parameters

Changes and fixes in OSCAR v1.0.0-beta-7
Portions of OSCAR are © 2019 by The OSCAR Team

  • Use the Qt language files for button text
  • Use "Maximize" (with frame & menu) instead of "FullScreen"
  • Change Mac target platform to OS-X 10.12 Sierra

Changes and fixes in OSCAR v1.0.0-beta-6
Portions of OSCAR are © 2019 by The OSCAR Team

  • Added a progress bar during file migration
  • Raise the main window upon opening it

Changes and fixes in OSCAR v1.0.0-beta-5
Portions of OSCAR are © 2019 by The OSCAR Team

  • Moved the Help Index to a user-writable location
  • Clarified folder selection and Migration messages

Changes and fixes in OSCAR v1.0.0-beta-4
Portions of OSCAR are © 2019 by The OSCAR Team

  • Added code to migrate an old data directory to a new data directory
  • Removed "-d" startup option and "Change directory" file menu item. Use the "--datadir foldername" startup option to create or change the data directory.
  • Various fixes to tab and button visibility depending on profile and machine data presence.

Changes and fixes in OSCAR v1.0.0-beta-3
Portions of OSCAR are © 2019 by The OSCAR Team and members of the apnea community

  • The Donation and Update features have been removed
  • The Glossary, FAQ, and Wiki features are not yet implemented
  • The Daily page left panel will resize to a smaller size.
  • Oximeter import reports file errors in a message box
  • Fixed date and time problems with Oximeter import
  • Added support for new CMS50D+ firmware version 4.6
  • Reformatted the 'No Data' display on the Statistics page
  • Got the Help browser working - just stubs for now
  • Small fixes to build and debug problems

SleepyHead v1.1.0-unstable-0

  • Released under the GNU General Public License version 3
  • Copyright © 2011-2018 Mark Watkins
OSCAR-code-v1.3.1/oscar/docs/release_notes.xxxx000066400000000000000000000321721417327530600213260ustar00rootroot00000000000000 release_notes

Changes and fixes in OSCAR v1.1.0-rc-1

  • Portions of OSCAR are © 2019-2020 by The OSCAR Team
  • [fix] AHI and Usage bars in overview graphs now show the correct height when oximetry usage is significantly longer than CPAP usage.
  • [fix] Improved import of Philips Respironics settings. In particular:
    • The settings for PC, S, and S/T modes are now displayed correctly.
    • AVAPS settings are now displayed correctly, including target tidal volume.
    • Backup breath settings are now imported and displayed.
    • The settings for CPAP-Check and Auto-Trial modes are now displayed correctly.
  • [fix] Fix regression in Welcome page pressure display
  • [fix] Several crashes have been fixed.

Changes and fixes in OSCAR v1.1.0-beta-2

  • Portions of OSCAR are © 2019-2020 by The OSCAR Team
  • [new] Add preliminary support for Viatom/Wellue pulse oximeters
  • [new] Add support for Dreem hypnograms
  • [new] Extensive reorganization of the ResMed loader to facilitate understanding and future improvements
  • [new] Additional Philips Respironics devices tested and fully supported:
    • DreamStation Auto CPAP (500X130)
  • [new] Add a menu option to create a zip from an SD card
  • [new] Ask where to save screenshots
  • [new] Alert users when unexpected Philips Respironics data is encountered during import
  • [fix] Add support for CPAP mode on DreamStation BiPAP S/T and AVAPS
  • [fix] Pinch-to-zoom now works as expected
  • [fix] The Philips Respironics loader now respects the "ignore old sessions" preference
  • [fix] Improved handling of discontinuous Philips Respironics data
  • [fix] Improved import of Philips Respironics flex and humidification settings
  • [fix] Fix incorrect display of tube diameter on some Philips Respironics devices
  • [fix] Fix missing "Bi-Flex" label for bi-level DreamStations
  • [fix] Fix crashes in ZEO loader
  • [fix] Fix several memory leaks

Changes and fixes in OSCAR v1.1.0-beta-1

  • Portions of OSCAR are © 2019 by The OSCAR Team
  • [new] Extensive overhaul of the Philips Respironics System One importer, resolving all previously reported issues.
  • [new] The following Philips Respironics devices are now tested and fully supported:
    • REMstar Plus (System One) (251P),
    • REMstar Pro (System One) (450P, 451P),
    • REMstar Auto (System One) (550P, 551P),
    • BiPAP Auto (System One) (750P),
    • BiPAP AutoSV Advanced System One (950P, 951P),
    • REMstar Pro (System One 60 Series) (460P, 461P),
    • REMstar Auto (System One 60 Series) (560P, 561P, 562P, 560PBT),
    • BiPAP Pro (System One 60 Series) (660P),
    • BiPAP Auto (System One 60 Series) (760P),
    • BiPAP autoSV Advanced (System One 60 Series) (960P, 961P),
    • BiPAP autoSV Advanced 30 (System One 60 Series) (960T),
    • BiPAP S/T 30 (System One 60 Series) (1061T),
    • BiPAP AVAPS 30 (System One 60 Series) (1160P),
    • DreamStation CPAP (200X110),
    • DreamStation CPAP Pro (400X110, 400X150),
    • DreamStation Go (400G110),
    • DreamStation Auto CPAP (500X110, 500X150),
    • DreamStation Go Auto (500G110, 502G150),
    • DreamStation BiPAP Pro (600X110),
    • DreamStation Auto BiPAP (700X110),
    • DreamStation BiPAP autoSV (900X110, 900X120),
    • DreamStation BiPAP S/T 30 (1030X110),
    • DreamStation BiPAP S/T 30 with AAM (1030X150),
    • DreamStation BiPAP AVAPS 30 (1130X110),
    • DreamStation BiPAP AVAPS 30 AE (1131X150)
    • Note: The settings for PC, S, and S/T modes are still displayed incorrectly.
  • [new] Update translation files and add new languages
  • [new] Allow user to reset graph order on the Daily page to Standard (for CPAP and APAP) or Advanced order (for ASV and AVAPS modes)
  • [new] Add preference setting to include the serial number on machine settings list
  • [fix] Place date, time, and Oscar version information in report footers
  • [fix] Update identification of ResMed S9 devices on the Welcome page
  • [fix] Correct formatting of event number in the Daily Events tab
  • [fix] Correct timezone offset for SomnoPose imports
  • [fix] Show a progress bar when setting the Overview range to a large number of days
  • [fix] Make session bars on the Daily page clearer by using a better color
  • [fix] Improve list of devices on the Statistics page
  • [fix] Report Pressure when IPAP data is missing
  • [fix] Implement Refresh button on the Profile page

Changes and fixes in OSCAR v1.1.0-testing-4

  • Portions of OSCAR are © 2019 by The OSCAR Team
  • [new] Windows installers support Oscar, Oscar 32-bit, Oscar (test) and Oscar 32-bit (test)
  • [fix] Release builds use a Settings key of OSCAR, Test builds use OSCAR-test, and Branch builds use OSCAR-branch. Default data directories are similarly named.
  • [fix] Overview chart problems when both CPAP and Oximetry data were included fixed
  • [fix] Translations updated for all languages. Spanish (Mexico) and Norwegian languages added
  • [fix] All languages now available on Mac
  • [fix] Date bar on the bottom of the Daily graph now in the local time when no line cursor displayed, and formatting improved
  • [fix] 100% zoom now works on the Overview page
  • [fix] View/Reset Graphs now enables all graphs and all event flags
  • [fix] Calendar date now formatted per national settings
  • [fix] Toggle buttons on the Daily page's session list and event and chart pulldowns changed to checkboxes

Changes and fixes in OSCAR v1.1.0-testing-3 NOTE: Translations have NOT yet been updated for these changes

  • Portions of OSCAR are © 2019 by The OSCAR Team
  • [new] DreamStation BiPAP S/T and AVAPS ventilators (1030X and 1130X) are now supported. The settings aren't yet displayed correctly, but therapy events and graphs should now display properly.
  • [new] View/Reset Graphs organizes graphs in their original order
  • [fix] Format dates for the national region as reported by the operating system
  • [fix] Improve progress bar for the Statistics cache update
  • [fix] Correct calculations of seven-day AHI and leak rate on the Welcome page
  • [fix] Clarify AHI and hours labels on Records tab of the right sidebar
  • [fix] Correct import error resulting in invalid elapsed times and impossibly high AHI values

Changes and fixes in OSCAR v1.1.0-testing-2 NOTE: Translations have NOT yet been updated for these changes

  • Portions of OSCAR are © 2019 by The OSCAR Team
  • [new] Show progress bar when the Statistics page has to refresh the cache with more than 180 days in it
  • [new] Romanian language is supported
  • [fix] Show commit id in the title bar for testing and beta builds
  • [fix] Show BMI label and calculated value only when both weight and height are non-zero
  • [fix] Improve BMI display in Daily/Notes
  • [fix] Correct profile dialog to show height is measured in cm, not meters, when units system is metric
  • [fix] Always show event indices on the Daily page even when some event flags are turned off

Changes and fixes in OSCAR v1.1.0-testing-1 NOTE: Translations have NOT yet been updated for these changes

  • Portions of OSCAR are © 2019 by The OSCAR Team
  • [new] Offer migration if a non-default directory is selected on first use
  • [new] Hold the SHIFT key down when starting OSCAR to use Software Graphics Engine
  • [new] Support for DreamStation BiPAP autoSV (900X) should now be complete
  • [new] Improved DreamStation support
  • [new] Improve oximeter import for CM550D+
  • [new] Disable Dark Mode on Mac
  • [new] Show hours/day constituting compliance on the Statistics page
  • [new] Better order of graphs on the Daily page for newly created profiles
  • [new] Help/System Information shows info about OSCAR, OS, and data location
  • [new] Move pie chart option from Preferences dialog to View menu
  • [new] Hide pie chart when capturing screen
  • [new] The --datadir option now allows fully qualified name on Windows
  • [fix] Fix some oximeter import issues
  • [fix] Use local time rather than UTC in oximeter import
  • [fix] Improve screen capture on Mac
  • [fix] Fix crashes in CPAP data rebuild, purging of a machine
  • [fix] Prevent crash if taking a screenshot before a profile is opened
  • [fix] Increase mask vent ranges
  • [fix] Correct session bar if no sessions are present
  • [fix] Correct months shown on the Statistics page monthly view
  • [fix] Compute compliance on the Statistics page based on total days, not days used
  • [fix] Paginate the Statistics report when printing
  • [fix] Fix "phantom date" (12/31/1969) on some ResMed imports
  • [fix] Default font substituted when a specified font is not valid
  • [fix] Change Preferences measurement units choice to Metric or English
  • [fix] Improve display of cmH2O numbers
  • [fix] Show graphics engine in title bar correctly
  • [fix] Adjust the size of the Preferences dialog to fit smaller screens
  • [fix] Label climate control as manual or auto correctly on ResMed import
  • [fix] Move less useful information from title bar to Help/System Information
  • [fix] Change "Prescription Settings" to "Machine Settings" message
  • [fix] Improve icons, especially smaller ones
  • [fix] Correct Mac menu issues
  • [fix] Improve messages in debug pane
  • [fix] Re-organize build instructions and other cleanup

Changes and fixes in OSCAR v1.0.1-r-1

  • Portions of OSCAR are © 2019 by The OSCAR Team
  • Disable multitasking to avoid a crash during pre-loading of summaries

Changes and fixes in OSCAR v1.0.0-beta-9

  • Portions of OSCAR are © 2019 by The OSCAR Team
  • Picked up the latest translation files
  • Changed logo to big O with text
  • Clean up odds and ends

Changes and fixes in OSCAR v1.0.0-beta-8

  • Portions of OSCAR are © 2019 by The OSCAR Team
  • Picked up the latest translation files
  • Fixed filename creation for about OSCAR docs
  • Removed display of I:E ratio from the Daily page
  • Changed some Mac build parameters

Changes and fixes in OSCAR v1.0.0-beta-7

  • Portions of OSCAR are © 2019 by The OSCAR Team
  • Use the Qt language files for button text
  • Use "Maximize" (with frame & menu) instead of "FullScreen"
  • Change Mac target platform to OS-X 10.12 Sierra

Changes and fixes in OSCAR v1.0.0-beta-6

  • Portions of OSCAR are © 2019 by The OSCAR Team
  • Added a progress bar during file migration
  • Raise the main window upon opening it

Changes and fixes in OSCAR v1.0.0-beta-5

  • Portions of OSCAR are © 2019 by The OSCAR Team
  • Moved the Help Index to a user-writable location
  • Clarified folder selection and Migration messages

Changes and fixes in OSCAR v1.0.0-beta-4

  • Portions of OSCAR are © 2019 by The OSCAR Team
  • Added code to migrate an old data directory to a new data directory
  • Removed "-d" startup option and "Change directory" file menu item. Use the "--datadir foldername" startup option to create or change the data directory.
  • Various fixes to tab and button visibility depending on profile and machine data presence.

Changes and fixes in OSCAR v1.0.0-beta-3

  • Portions of OSCAR are © 2019 by The OSCAR Team and members of the apnea community
  • The Donation and Update features have been removed
  • The Glossary, FAQ, and Wiki features are not yet implemented
  • The Daily page left panel will resize to a smaller size.
  • Oximeter import reports file errors in a message box
  • Fixed date and time problems with Oximeter import
  • Added support for new CMS50D+ firmware version 4.6
  • Reformatted the 'No Data' display on the Statistics page
  • Got the Help browser working - just stubs for now
  • Small fixes to build and debug problems

SleepyHead v1.1.0-unstable-0

  • Released under the GNU General Public License version 3
  • Copyright © 2011-2018 Mark Watkins

OSCAR-code-v1.3.1/oscar/docs/schema.xml000066400000000000000000000036151417327530600175170ustar00rootroot00000000000000 OSCAR-code-v1.3.1/oscar/docs/script.js000066400000000000000000000042201417327530600173700ustar00rootroot00000000000000/* Borrowed from Michael Leigeber http://sixrevisions.com/tutorials/javascript_tutorial/create_lightweight_javascript_tooltip/ */ var tooltip=function(){ var id = 'tt'; var top = 3; var left = 3; var maxw = 300; var speed = 10; var timer = 20; var endalpha = 95; var alpha = 0; var tt,t,c,b,h; var ie = document.all ? true : false; return{ show:function(v,w){ if(tt == null){ tt = document.createElement('div'); tt.setAttribute('id',id); t = document.createElement('div'); t.setAttribute('id',id + 'top'); c = document.createElement('div'); c.setAttribute('id',id + 'cont'); b = document.createElement('div'); b.setAttribute('id',id + 'bot'); tt.appendChild(t); tt.appendChild(c); tt.appendChild(b); document.body.appendChild(tt); tt.style.opacity = 0; tt.style.filter = 'alpha(opacity=0)'; document.onmousemove = this.pos; } tt.style.display = 'block'; c.innerHTML = v; tt.style.width = w ? w + 'px' : 'auto'; if(!w && ie){ t.style.display = 'none'; b.style.display = 'none'; tt.style.width = tt.offsetWidth; t.style.display = 'block'; b.style.display = 'block'; } if(tt.offsetWidth > maxw){tt.style.width = maxw + 'px'} h = parseInt(tt.offsetHeight) + top; clearInterval(tt.timer); tt.timer = setInterval(function(){tooltip.fade(1)},timer); }, pos:function(e){ var u = ie ? event.clientY + document.documentElement.scrollTop : e.pageY; var l = ie ? event.clientX + document.documentElement.scrollLeft : e.pageX; tt.style.top = (u - h) + 'px'; tt.style.left = (l + left) + 'px'; }, fade:function(d){ var a = alpha; if((a != endalpha && d == 1) || (a != 0 && d == -1)){ var i = speed; if(endalpha - a < speed && d == 1){ i = endalpha - a; }else if(alpha < speed && d == -1){ i = a; } alpha = a + (i * d); tt.style.opacity = alpha * .01; tt.style.filter = 'alpha(opacity=' + alpha + ')'; }else{ clearInterval(tt.timer); if(d == -1){tt.style.display = 'none'} } }, hide:function(){ clearInterval(tt.timer); tt.timer = setInterval(function(){tooltip.fade(-1)},timer); } }; }(); OSCAR-code-v1.3.1/oscar/docs/startup_tips.txt000066400000000000000000000002241417327530600210300ustar00rootroot00000000000000You can reorder graphs by clicking and dragging on the title bar. SleepyHead project started out in March 2011, initially written in Python script. OSCAR-code-v1.3.1/oscar/docs/tooltips.css000066400000000000000000000031501417327530600201160ustar00rootroot00000000000000 OSCAR-code-v1.3.1/oscar/docs/tz.txt000066400000000000000000000076611417327530600167400ustar00rootroot00000000000000Pacific/Midway=(GMT-11:00) Midway Island, Samoa America/Adak=(GMT-10:00) Hawaii-Aleutian Etc/GMT+10=(GMT-10:00) Hawaii Pacific/Marquesas=(GMT-09:30) Marquesas Islands Pacific/Gambier=(GMT-09:00) Gambier Islands America/Anchorage=(GMT-09:00) Alaska America/Ensenada=(GMT-08:00) Tijuana, Baja California Etc/GMT+8=(GMT-08:00) Pitcairn Islands America/Los_Angeles=(GMT-08:00) Pacific Time (US & Canada) America/Denver=(GMT-07:00) Mountain Time (US & Canada) America/Chihuahua=(GMT-07:00) Chihuahua, La Paz, Mazatlan America/Dawson_Creek=(GMT-07:00) Arizona America/Belize=(GMT-06:00) Saskatchewan, Central America America/Cancun=(GMT-06:00) Guadalajara, Mexico City, Monterrey Chile/EasterIsland=(GMT-06:00) Easter Island America/Chicago=(GMT-06:00) Central Time (US & Canada) America/New_York=(GMT-05:00) Eastern Time (US & Canada) America/Havana=(GMT-05:00) Cuba America/Bogota=(GMT-05:00) Bogota, Lima, Quito, Rio Branco America/Caracas=(GMT-04:30) Caracas America/Santiago=(GMT-04:00) Santiago America/La_Paz=(GMT-04:00) La Paz Atlantic/Stanley=(GMT-04:00) Faukland Islands America/Campo_Grande=(GMT-04:00) Brazil America/Goose_Bay=(GMT-04:00) Atlantic Time (Goose Bay) America/Glace_Bay=(GMT-04:00) Atlantic Time (Canada) America/St_Johns=(GMT-03:30) Newfoundland America/Araguaina=(GMT-03:00) UTC-3, America/Montevideo=(GMT-03:00) Montevideo America/Miquelon=(GMT-03:00) Miquelon, St. Pierre America/Godthab=(GMT-03:00) Greenland America/Argentina/Buenos_Aires=(GMT-03:00) Buenos Aires America/Sao_Paulo=(GMT-03:00) Brasilia America/Noronha=(GMT-02:00) Mid-Atlantic Atlantic/Cape_Verde=(GMT-01:00) Cape Verde Is. Atlantic/Azores=(GMT-01:00) Azores Europe/Belfast=(GMT) Greenwich Mean Time : Belfast Europe/Dublin=(GMT) Greenwich Mean Time : Dublin Europe/Lisbon=(GMT) Greenwich Mean Time : Lisbon Europe/London=(GMT) Greenwich Mean Time : London Africa/Abidjan=(GMT) Monrovia, Reykjavik Europe/Amsterdam=(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna Europe/Belgrade=(GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague Europe/Brussels=(GMT+01:00) Brussels, Copenhagen, Madrid, Paris Africa/Algiers=(GMT+01:00) West Central Africa Africa/Windhoek=(GMT+01:00) Windhoek Asia/Beirut=(GMT+02:00) Beirut Africa/Cairo=(GMT+02:00) Cairo Asia/Gaza=(GMT+02:00) Gaza Africa/Blantyre=(GMT+02:00) Harare, Pretoria Asia/Jerusalem=(GMT+02:00) Jerusalem Europe/Minsk=(GMT+02:00) Minsk Asia/Damascus=(GMT+02:00) Syria Europe/Moscow=(GMT+03:00) Moscow, St. Petersburg, Volgograd Africa/Addis_Ababa=(GMT+03:00) Nairobi Asia/Tehran=(GMT+03:30) Tehran Asia/Dubai=(GMT+04:00) Abu Dhabi, Muscat Asia/Yerevan=(GMT+04:00) Yerevan Asia/Kabul=(GMT+04:30) Kabul Asia/Yekaterinburg=(GMT+05:00) Ekaterinburg Asia/Tashkent=(GMT+05:00) Tashkent Asia/Kolkata=(GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi Asia/Katmandu=(GMT+05:45) Kathmandu Asia/Dhaka=(GMT+06:00) Astana, Dhaka Asia/Novosibirsk=(GMT+06:00) Novosibirsk Asia/Rangoon=(GMT+06:30) Yangon (Rangoon) Asia/Bangkok=(GMT+07:00) Bangkok, Hanoi, Jakarta Asia/Krasnoyarsk=(GMT+07:00) Krasnoyarsk Asia/Hong_Kong=(GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi Asia/Irkutsk=(GMT+08:00) Irkutsk, Ulaan Bataar Australia/Perth=(GMT+08:00) Perth Australia/Eucla=(GMT+08:45) Eucla Asia/Tokyo=(GMT+09:00) Osaka, Sapporo, Tokyo Asia/Seoul=(GMT+09:00) Seoul Asia/Yakutsk=(GMT+09:00) Yakutsk Australia/Adelaide=(GMT+09:30) Adelaide Australia/Darwin=(GMT+09:30) Darwin Australia/Brisbane=(GMT+10:00) Brisbane Australia/Melbourne=(GMT+10:00) Melbourne Australia/Hobart=(GMT+10:00) Hobart Asia/Vladivostok=(GMT+10:00) Vladivostok Australia/Lord_Howe=(GMT+10:30) Lord Howe Island Etc/GMT-11=(GMT+11:00) Solomon Is., New Caledonia Asia/Magadan=(GMT+11:00) Magadan Pacific/Norfolk=(GMT+11:30) Norfolk Island Asia/Anadyr=(GMT+12:00) Anadyr, Kamchatka Pacific/Auckland=(GMT+12:00) Auckland, Wellington Etc/GMT-12=(GMT+12:00) Fiji, Kamchatka, Marshall Is. Pacific/Chatham=(GMT+12:45) Chatham Islands Pacific/Tongatapu=(GMT+13:00) Nuku Alofa Pacific/Kiritimati=(GMT+14:00) Kiritimati OSCAR-code-v1.3.1/oscar/exportcsv.cpp000066400000000000000000000405121417327530600173430ustar00rootroot00000000000000/* ExportCSV module implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include "SleepLib/profiles.h" #include "SleepLib/day.h" #include "exportcsv.h" #include "ui_exportcsv.h" #include "mainwindow.h" extern MainWindow *mainwin; ExportCSV::ExportCSV(QWidget *parent) : QDialog(parent), ui(new Ui::ExportCSV) { ui->setupUi(this); ui->rb1_Summary->setChecked(true); ui->quickRangeCombo->setCurrentIndex(0); this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); // Set Date controls locale to 4 digit years QLocale locale = QLocale::system(); QString shortformat = locale.dateFormat(QLocale::ShortFormat); if (!shortformat.toLower().contains("yyyy")) { shortformat.replace("yy", "yyyy"); } ui->startDate->setDisplayFormat(shortformat); ui->endDate->setDisplayFormat(shortformat); // Stop both calendar drop downs highlighting weekends in red QTextCharFormat format = ui->startDate->calendarWidget()->weekdayTextFormat(Qt::Saturday); format.setForeground(QBrush(Qt::black, Qt::SolidPattern)); ui->startDate->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format); ui->startDate->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format); ui->endDate->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format); ui->endDate->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format); Qt::DayOfWeek dow = firstDayOfWeekFromLocale(); ui->startDate->calendarWidget()->setFirstDayOfWeek(dow); ui->endDate->calendarWidget()->setFirstDayOfWeek(dow); // Connect the signals to update which days have CPAP data when the month is changed connect(ui->startDate->calendarWidget(), SIGNAL(currentPageChanged(int, int)), SLOT(startDate_currentPageChanged(int, int))); connect(ui->endDate->calendarWidget(), SIGNAL(currentPageChanged(int, int)), SLOT(endDate_currentPageChanged(int, int))); on_quickRangeCombo_activated(tr("Most Recent Day")); ui->rb1_details->clearFocus(); ui->quickRangeCombo->setFocus(); ui->exportButton->setEnabled(false); } ExportCSV::~ExportCSV() { delete ui; } void ExportCSV::on_filenameBrowseButton_clicked() { QString timestamp = QString("OSCAR_"); timestamp += p_profile->Get("UserName") + "_"; if (ui->rb1_details->isChecked()) { timestamp += tr("Details_"); } if (ui->rb1_Sessions->isChecked()) { timestamp += tr("Sessions_"); } if (ui->rb1_Summary->isChecked()) { timestamp += tr("Summary_"); } timestamp += ui->startDate->date().toString(Qt::ISODate); if (ui->startDate->date() != ui->endDate->date()) { timestamp += "_" + ui->endDate->date().toString(Qt::ISODate); } timestamp += ".csv"; QString name = QFileDialog::getSaveFileName(this, tr("Select file to export to"), p_pref->Get("{home}/") + timestamp, tr("CSV Files (*.csv)")); if (name.isEmpty()) { ui->exportButton->setEnabled(false); return; } if (!name.toLower().endsWith(".csv")) { name += ".csv"; } ui->filenameEdit->setText(name); ui->exportButton->setEnabled(true); } void ExportCSV::on_quickRangeCombo_activated(const QString &arg1) { QDate first = p_profile->FirstDay(); QDate last = p_profile->LastDay(); if (arg1 == tr("Custom")) { ui->startDate->setEnabled(true); ui->endDate->setEnabled(true); ui->startLabel->setEnabled(true); ui->endLabel->setEnabled(true); } else { ui->startDate->setEnabled(false); ui->endDate->setEnabled(false); ui->startLabel->setEnabled(false); ui->endLabel->setEnabled(false); if (arg1 == tr("Everything")) { ui->startDate->setDate(first); ui->endDate->setDate(last); } else if (arg1 == tr("Most Recent Day")) { ui->startDate->setDate(last); ui->endDate->setDate(last); } else if (arg1 == tr("Last Week")) { ui->startDate->setDate(last.addDays(-7)); ui->endDate->setDate(last); } else if (arg1 == tr("Last Fortnight")) { ui->startDate->setDate(last.addDays(-14)); ui->endDate->setDate(last); } else if (arg1 == tr("Last Month")) { ui->startDate->setDate(last.addMonths(-1)); ui->endDate->setDate(last); } else if (arg1 == tr("Last 6 Months")) { ui->startDate->setDate(last.addMonths(-6)); ui->endDate->setDate(last); } else if (arg1 == tr("Last Year")) { ui->startDate->setDate(last.addYears(-1)); ui->endDate->setDate(last); } } } void ExportCSV::on_exportButton_clicked() { QFile file(ui->filenameEdit->text()); if (!file.open(QFile::WriteOnly)) { qWarning() << "Could not open" << ui->filenameEdit->text() << "for writing, error code" << file.error() << file.errorString(); return; } QString header; const QString sep = ","; const QString newline = "\n"; // if (ui->rb1_details->isChecked()) { // fields.append(DumpField(NoChannel,MT_CPAP,ST_DATE)); // } else { // header=tr("DateTime")+sep+tr("Session")+sep+tr("Event")+sep+tr("Data/Duration"); // } else { // if (ui->rb1_Summary->isChecked()) { // header=tr("Date")+sep+tr("Session Count")+sep+tr("Start")+sep+tr("End")+sep+tr("Total Time")+sep+tr("AHI"); // } else if (ui->rb1_Sessions->isChecked()) { // header=tr("Date")+sep+tr("Session")+sep+tr("Start")+sep+tr("End")+sep+tr("Total Time")+sep+tr("AHI"); // } // } // fields.append(DumpField(NoChannel,MT_CPAP,ST_SESSIONS)); QList countlist, avglist, p90list, maxlist; for (int i = 0; i < ahiChannels.size(); i++) countlist.append(ahiChannels.at(i)); // countlist.append(CPAP_Hypopnea); // countlist.append(CPAP_Obstructive); // countlist.append(CPAP_Apnea); // countlist.append(CPAP_ClearAirway); // countlist.append(CPAP_AllApnea); countlist.append(CPAP_VSnore); countlist.append(CPAP_VSnore2); countlist.append(CPAP_RERA); countlist.append(CPAP_FlowLimit); countlist.append(CPAP_SensAwake); countlist.append(CPAP_NRI); countlist.append(CPAP_ExP); countlist.append(CPAP_LeakFlag); countlist.append(CPAP_UserFlag1); countlist.append(CPAP_UserFlag2); countlist.append(CPAP_PressurePulse); QVector statChannels = { CPAP_Pressure, CPAP_PressureSet, CPAP_IPAP, CPAP_IPAPSet, CPAP_EPAP, CPAP_EPAPSet, CPAP_FLG }; for (auto & chan : statChannels) { avglist.append(chan); p90list.append(chan); maxlist.append(chan); } float percentile=p_profile->general->prefCalcPercentile()/100.0; // Pholynyk, 18Aug2015 EventDataType percent = percentile; // was 0.90F // Not sure this section should be translateable.. :-/ if (ui->rb1_details->isChecked()) { header = tr("DateTime") + sep + tr("Session") + sep + tr("Event") + sep + tr("Data/Duration"); } else { if (ui->rb1_Summary->isChecked()) { header = tr("Date") + sep + tr("Session Count") + sep + tr("Start") + sep + tr("End") + sep + tr("Total Time") + sep + tr("AHI"); } else if (ui->rb1_Sessions->isChecked()) { header = tr("Date") + sep + tr("Session") + sep + tr("Start") + sep + tr("End") + sep + tr("Total Time") + sep + tr("AHI"); } for (int i = 0; i < countlist.size(); i++) { header += sep + schema::channel[countlist[i]].label() + tr(" Count"); } for (int i = 0; i < avglist.size(); i++) { header += sep + Day::calcMiddleLabel(avglist[i]); // Pholynyk, 18Aug2015 } for (int i = 0; i < p90list.size(); i++) { header += sep + QString("%1% ").arg(percent*100.0, 0, 'f', 0) + schema::channel[p90list[i]].label(); } for (int i = 0; i < maxlist.size(); i++) { header += sep + Day::calcMaxLabel(maxlist[i]); // added -- Pholynyk, 18Aug2015 } } header += newline; file.write(header.toLatin1()); QDate date = ui->startDate->date(); Daily *daily = mainwin->getDaily(); QDate daily_date = daily->getDate(); ui->progressBar->setValue(0); ui->progressBar->setMaximum(p_profile->daylist.count()); do { ui->progressBar->setValue(ui->progressBar->value() + 1); QApplication::processEvents(); Day *day = p_profile->GetDay(date, MT_CPAP); // Only export days with CPAP data. if (day) { QString data; if (ui->rb1_Summary->isChecked()) { QDateTime start = QDateTime::fromTime_t(day->first() / 1000L); QDateTime end = QDateTime::fromTime_t(day->last() / 1000L); data = date.toString(Qt::ISODate); data += sep + QString::number(day->size(), 10); data += sep + start.toString(Qt::ISODate); data += sep + end.toString(Qt::ISODate); // Given this is a CPAP specific report, just report CPAP hours int time = int(day->hours(MT_CPAP) * 3600L); int h = time / 3600; int m = int(time / 60) % 60; int s = int(time) % 60; data += sep + QString().sprintf("%02i:%02i:%02i", h, m, s); float ahi = day->calcAHI(); data += sep + QString::number(ahi, 'f', 3); for (int i = 0; i < countlist.size(); i++) { data += sep + QString::number(day->count(countlist.at(i))); } for (int i = 0; i < avglist.size(); i++) { float avg = day->calcMiddle(avglist.at(i)); data += sep + QString::number(avg); // Pholynyk, 11Aug2015 } for (int i = 0; i < p90list.size(); i++) { float p90 = day->percentile(p90list.at(i), percent); data += sep + QString::number(p90); // Pholynyk, 11Aug2015 } for (int i = 0; i < maxlist.size(); i++) { float max = day->calcMax(maxlist.at(i)); data += sep + QString::number(max); // added -- Pholynyk, 18Aug2015 } data += newline; file.write(data.toLatin1()); } else if (ui->rb1_Sessions->isChecked()) { for (int i = 0; i < day->size(); i++) { Session *sess = (*day)[i]; if (sess->type() != MT_CPAP) { continue; // Not every session in a day with CPAP data will be a CPAP session. } QDateTime start = QDateTime::fromTime_t(sess->first() / 1000L); QDateTime end = QDateTime::fromTime_t(sess->last() / 1000L); data = date.toString(Qt::ISODate); data += sep + QString::number(sess->session(), 10); data += sep + start.toString(Qt::ISODate); data += sep + end.toString(Qt::ISODate); int time = sess->length() / 1000L; int h = time / 3600; int m = int(time / 60) % 60; int s = int(time) % 60; data += sep + QString().sprintf("%02i:%02i:%02i", h, m, s); float ahi = sess->count(AllAhiChannels); //sess->count(CPAP_AllApnea) + sess->count(CPAP_Obstructive) + sess->count(CPAP_Hypopnea) // + sess->count(CPAP_Apnea) + sess->count(CPAP_ClearAirway); ahi /= sess->hours(); data += sep + QString::number(ahi, 'f', 3); for (int j = 0; j < countlist.size(); j++) { data += sep + QString::number(sess->count(countlist.at(j))); } for (int j = 0; j < avglist.size(); j++) { data += sep + QString::number(sess->calcMiddle(avglist.at(j))); // Pholynyk, 11Aug2015 } for (int j = 0; j < p90list.size(); j++) { data += sep + QString::number(sess->percentile(p90list.at(j), percent)); // Pholynyk, 11Aug2015 } for (int i = 0; i < maxlist.size(); i++) { data += sep + QString::number(sess->calcMax(maxlist.at(i))); // Pholynyk, 11Aug2015 } data += newline; file.write(data.toLatin1()); } } else if (ui->rb1_details->isChecked()) { QList all = countlist; all.append(avglist); for (int i = 0; i < day->size(); i++) { Session *sess = (*day)[i]; sess->OpenEvents(); QHash >::iterator fnd; for (int j = 0; j < all.size(); j++) { ChannelID key = all.at(j); fnd = sess->eventlist.find(key); if (fnd != sess->eventlist.end()) { //header="DateTime"+sep+"Session"+sep+"Event"+sep+"Data/Duration"; for (int e = 0; e < fnd.value().size(); e++) { EventList *ev = fnd.value()[e]; for (quint32 q = 0; q < ev->count(); q++) { data = QDateTime::fromTime_t(ev->time(q) / 1000L).toString(Qt::ISODate); data += sep + QString::number(sess->session()); data += sep + schema::channel[key].code(); data += sep + QString::number(ev->data(q), 'f', 2); data += newline; file.write(data.toLatin1()); } } } } if (daily_date != date) { sess->TrashEvents(); } } } } date = date.addDays(1); } while (date <= ui->endDate->date()); file.close(); ExportCSV::accept(); } void ExportCSV::UpdateCalendarDay(QDateEdit *dateedit, QDate date) { QCalendarWidget *calendar = dateedit->calendarWidget(); QTextCharFormat bold; QTextCharFormat cpapcol; QTextCharFormat normal; QTextCharFormat oxiday; bold.setFontWeight(QFont::Bold); cpapcol.setForeground(QBrush(Qt::blue, Qt::SolidPattern)); cpapcol.setFontWeight(QFont::Bold); oxiday.setForeground(QBrush(Qt::red, Qt::SolidPattern)); oxiday.setFontWeight(QFont::Bold); bool hascpap = p_profile->GetDay(date, MT_CPAP) != nullptr; bool hasoxi = p_profile->GetDay(date, MT_OXIMETER) != nullptr; //bool hasjournal=p_profile->GetDay(date,MT_JOURNAL)!=nullptr; if (hascpap) { if (hasoxi) { calendar->setDateTextFormat(date, oxiday); } else { calendar->setDateTextFormat(date, cpapcol); } } else if (p_profile->GetDay(date)) { calendar->setDateTextFormat(date, bold); } else { calendar->setDateTextFormat(date, normal); } calendar->setHorizontalHeaderFormat(QCalendarWidget::ShortDayNames); } void ExportCSV::startDate_currentPageChanged(int year, int month) { QDate d(year, month, 1); int dom = d.daysInMonth(); for (int i = 1; i <= dom; i++) { d = QDate(year, month, i); UpdateCalendarDay(ui->startDate, d); } } void ExportCSV::endDate_currentPageChanged(int year, int month) { QDate d(year, month, 1); int dom = d.daysInMonth(); for (int i = 1; i <= dom; i++) { d = QDate(year, month, i); UpdateCalendarDay(ui->endDate, d); } } OSCAR-code-v1.3.1/oscar/exportcsv.h000066400000000000000000000027371417327530600170170ustar00rootroot00000000000000/* ExportCSV Module Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef EXPORTCSV_H #define EXPORTCSV_H #include #include #include "SleepLib/machine_common.h" namespace Ui { class ExportCSV; } struct DumpField { DumpField() { code = NoChannel; mtype = MT_UNKNOWN; type = ST_CNT; } DumpField(ChannelID c, MachineType mt, SummaryType t) { code = c; mtype = mt; type = t; } DumpField(const DumpField ©) {code = copy.code; mtype = copy.mtype; type = copy.type; } ChannelID code; MachineType mtype; SummaryType type; }; /*! \class ExportCSV \brief Dialog for exporting SleepLib data in CSV Format This module still needs a lot of work */ class ExportCSV : public QDialog { Q_OBJECT public: explicit ExportCSV(QWidget *parent = 0); ~ExportCSV(); private slots: void on_filenameBrowseButton_clicked(); void on_quickRangeCombo_activated(const QString &arg1); void on_exportButton_clicked(); void startDate_currentPageChanged(int year, int month); void endDate_currentPageChanged(int year, int month); private: void UpdateCalendarDay(QDateEdit *dateedit, QDate date); Ui::ExportCSV *ui; QList fields; }; #endif // EXPORTCSV_H OSCAR-code-v1.3.1/oscar/exportcsv.ui000066400000000000000000000170771417327530600172100ustar00rootroot00000000000000 ExportCSV 0 0 521 254 Export as CSV :/icons/save.png:/icons/save.png Dates: 0 0 Resolution: Details Sessions Daily true Qt::Horizontal 40 20 Filename: Qt::Horizontal 40 20 Cancel Export 0 0 Start: QDateTimeEdit::DaySection true 0 0 End: QDateTimeEdit::DaySection true Qt::Horizontal 40 20 0 0 Quick Range: Most Recent Day Last Week Last Fortnight Last Month Last 6 Months Last Year Everything Custom ... :/icons/save.png:/icons/save.png Qt::Vertical 20 40 0 cancelButton clicked() ExportCSV reject() 377 230 260 126 OSCAR-code-v1.3.1/oscar/fonts/000077500000000000000000000000001417327530600157315ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/fonts/FreeSans.ttf000066400000000000000000016031401417327530600201630ustar00rootroot00000000000000 FFTMP9DGDEFVwMzGPOS0w$@ GSUBdP/OS/2sPVcmapѨc@fcvt RU$fpgmBSgasp glyfjT5head,6hhead$hmtx>loca!8tU>maxp 3 nameZ DP Ypost3Ԭprep^TAU_<   B2`ZB`du /]1 BGNU @  8,`CM|c4,,!y40MIM&(H2WM.W,+,f,", ,,#,+,.,%,&nnH-H2H2,M"O0YZcZ ,SdO,PAKL &[ &]0cU c@,,M,*,6,,(,,FB:DAF,F,$,6,ME",A N+dNHKz,4,,C, d,+Mr%-H(M.M^H2^^M\,A0WM'^Em(.e=e=e,` 0ZZZZGL & & & & &H_ UUUU [cC,*,*,*,*,*,*y",(,(,(,(A,$,F,$,$,$,$,$H2c,A,A,A,A+6,*,*,+0000Y,Z,(Z,(Z,(Z,(Z,( ,, ,, ,, ,,S,F', _^dvBO::,F@,P9,PD,PD,L,FL,FL,F.L,F &,$ &,$ &,$+(]ME]M;]M10"0"0"0"ccc U,AU,AU,AU,AU,AU,A  ccc,V0P,6,000O,64&Z/0c , yFBMO:M0A@,F &&5$&$J0,6[0"cMccUpA U,cc c  ",",@,6drdH2|YY  P PDLL F,* &,$U,AU,AU,AU,AU,A,(,*,* y" ,, ,,O: &,$ &,$c  5YY  ,,v[L,F,* y" c,,*Z,Z,({z &, &,$]M]M-U,U,A0"c S,Fc,*Z,( &,$ &,$ &,$ &,$ ,*,,,6 ,,,(,($("$"!,,", ,F,F,FB DcDAFAFAF,,F7F,$ $9MEMEMEMEMETqE"6"6",r"B ,  (4,/,/,/0 &F"".F:F,,/,/ DAD-5mM M &g(J11WWWMMOMMMMM3M3MMMMM\MdMMSM#MgMgMzM{M M M M MMsMOM9MMMM:MMN(((((HHMMM1M3M.MMGLM nM M M=&1=)2,+5w XU}>e9CC!2h:Qk|;nn1LnMsMV<qAM OFR 5ZtS &dOAKLvA &P[t(c 4 4  T0'$382T024$'&$ (8<'< "$k92#"l"2.; *$2"$2$D64M l49$,,69 &H0PPFP00d8SStPPe SPPFP,"P 0PPxP!NPP.PP0ce ]0Pr8>PSPIjPP*P(&&&FFZ#&$7F7FFjF.F&&-FAF&$a&BF0FFF F$F%&%&AF&$F-UFAF7F.F<* 0&& PGF P<FYPFM  OPF >*"PFPF GP:FPF"P(F0&0&c B L ~0 0v<0vPF/$*&P PF  P.FP;Fv<0ePwFP(&(& o"P#&/ /  0$c  P7FP7F.&& 0&& 0&&*$e e e r80FPFjPF0" &?$sPFFF<<F2FFFAFFTF#FI2A(<S- [(FBA-FFA-Fo<F-x-(MO5MMM MAA'F<#9A"AE#aF'A8F9#(FFF#)FF#8A(,AF#'A#AA& A!AHAoA(A(#-FtAB,#* FZM knEu&y>Fn:O:V&n?>_'o?`=:&&9X%o?m0>z3_=K$?98? N3^>#%<#><8+X6X8423,jjjrTrW>>>#9>kkmIdsXakk>>~Ikc,}  >1?=E#X <E X  Kk8c_4*O( 8A*2T|L!M,^G ,   e*qqxqvs?Z?q?X?d?N?e?e?"??mj,"R%RtD#s#e:+ O3} ?- Hy+w@ya/d  RtAM191`$yy(! m+ew_0\@eYZzuzvqpelrf]B-" !|!nn@M <L3 b obYfz #hYs lN;l?l%5 <, &-r)t.O_)  S} D P - - - P  :1_ ie [ ey ( $ }TT     Mv[  qlK<l<</<X<S<<}Pn<U<<Z<[PPK<o<YP<O<W<,<[<NP<T<P<TPx<Q<tKoxv<,*O,6O,6O,60Y,Y,Y,Y,Y,Z,(Z,(Z,(Z,(Z,(cZ ,,S,FS,FS,F,S,FO:O:O:,P:,P,P,PAKAFAKAFAKAFL,FL,FL,FL,F &,$ &,$ &,$ &,$[,6[,6]ME]M;]M;]M0"0"0"0"0"ccccU,AU,AU,AU,AU,A   ccc,F+*,*,*,*,,*,*,*,*,*,*,*,*Z,(Z,(Z,(Z,(,Z,(Z,(Z,(SN_9 &,$ &,$ &,$ &, &,$ &,$ &,$&5$&5$&5$&5$&5$U,AU,AUpAUpAUpAUpAUpA    T0T0T0T0T0T0T0T0''''''rco$$$$$$$$ UU,8BA"$"$"$"$"$"$w22222222_$$$$$$$$oT0T0''$$8"$"$22$$T0T0T0T0T0T0T0T0><U\C;$$$$$$$$PUKN$$$$$$$$jh5&)T0T0T0T0T0T0T0@MtMtM M $$$$$AkSRBM  xDQM 2222929222  1;""M $$$$$AAP.4MRMvM,dM.M.,,8K6kA@@@M0M1M/M1,&,&^2^FWssW P 111111x 11nb,|,M,;;#H 5NNyNXMBMB|,.0 (?;(z<;$<W1<<<<\<<<^^^^^^^^^^ ^ ^o^N^'^^E^^^^^^^^^^ ^ ^o^L^]^^^g&Y c,,AFG%[]o2e<6$c:"'Z[!6)2142wCP)0Bss,3lTY0(!g,<V):^j AX4RU1? 4 O"o,(2-)L)8:P:V&Cg(2 ,,P,P /(eEeeEeeeeEeeEeeeeEd,dBddd,P0YAKBBBB    BD,AF [-1[-    0-  |8# -|8{  ['-[,['D ,+Z7  H7H7H-H7H7-7i7i$)H(H2H2(H]HK 8KT[[((,ddd,__HKHK=K%H3H*H(H(H2H2H2HIHI*H2H2H(H(H-H---*H-H-H-H-HHH-H-H(H(H-H-H7H7H7H7HH6HH6(|4|P|B|B|E|E+++++LXL`L|>|>|>|>==W@>H(H(|B|B7'bH_I,,d;;II(((,,XXX77,,##########|||||||k,|#|#$#rBr"#S"#r"#S"#M7+777By<2"###==H(M.^LL)&NS'pA/iM' 24 G&-!# T 5 0MK/:w#2"5xFxY|O   } +8:8:r+++++KG]q!:!VwX X  ,1`?n?++!-Z-PP@@  nmq  Dh~3   gHk[[+yy+.VX_HjjezSCO O/Hhh0%%   .F:+O} ?--  +pq+t+ rq    ? q  x _CoJ$3* CGt:+ O3} ?- Hy+ yyWaa n2FHcq8|ULL' a\vuo?]c')B'iN)g'>>|)C|2bxjjrtJ**jUU  5]Jk5])u# = '!|||LLLU,LD([8c?z]s*U QY)*; b(.&bNE9EoE8c(EE9E3E[b& A  r H uK;,(v,/./6h[Q*^KXh[[u2 Bm+*%5)L\33\, NL42,G:<01,T$q0`.^)V2[/X1*K*Kb obYfz #hYs lN;l?p% a T 1 @ > [ B g RQ1rr{uojmugZ hZ]Cl a b _ a AAA AA:18K%z:P&h? 9R%i?%#H2<<<<:::O:V&n?_`= &&9X%m0z3_=?98?N3^>#%<#>O:&98?H#  a?\J.d>-a|,dqV 2C#I C oM}#pn_C1a\7777777H67777&77@7u77M7A7R777 77777b77,\, ~37ouz~asV_ ,J 9 M T r   ( 0 3 6 9 < B H M \ ^ t EMWY[]} d q !!! ! !!!!"!3!;!D!K!N!!!!!!!!!""" """" "."4">"I"M"P"b"e"k"s"}"""""""""## ###*##$#$i%K%l%%%%%%%%%&,&g&o'@** .6<>ADO $7Ptz~p|1Ya0  < P X     * 2 5 8 < > G K Y ^ f  HPY[]_ p t !!! ! !!!! !&!5!A!J!M!S!!!!!!!!""""""" "#"4"<"@"M"P"`"d"j"m"z"""""""""### #)##$"$`%%P%%%%%%%%%&,&`&i'@** .8>@CFsqgYQO93" #!  ~zj^\RLDA: pkEzxUVRE5 ` ~b~$377Potuzz~~0\ceagps|IM1VSY_ya ,0J  9$ < M] P To X rt                      ( * 0 2 3 5 6 8 9 < < > B G H K M Y \# ^ ^' f t( 7 : A B E [ b d i s v y z {                   EHM<PWBYYJ[[K]]L_}Ml d p q H t J e j x ~ !! !! ! ! ! ! !! !! !! ! !" !&!3 !5!; !A!D !J!K !M!N !S! !! !! !! !! !! !! !! !! "" "" "" "" $"" -"" /" " 1"#". 2"4"4 >"<"> ?"@"I B"M"M L"P"P M"`"b N"d"e Q"j"k S"m"s U"z"} \"" `"" j"" k"" v"" |"" "" "" "" ## ## # ## #)#* ## ## $"$# $`$i %%K %P%l %% %% :%% =%% >%% ?%% @%% A%% B%% D&,&, E&`&g F&i&o N'@'@ U** V* * [.. \68<>>#@A$CD&FO(23Qv       !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a rdei xpk vj N 0s Q Rgw  & $M :l|6cn .T J m} b  B: ' u   y  f|qxyzz}{g,K*PXJvY#?+X=YK*PX}Y ԰.-, /+\X G#Faj X db8!!Y!Y-,KRXE#Y!-,i @PX!@Y-,+X!#!zXYKRXXY!+XFvYXYYY-, \Z-,"PX \\Y-,$PX@\\Y-, 9/-KPXYF+X!YKRX!Y+\X E+D E++D E:++D E++D Ev++D Ey++D E ++D E+D E +Fv+D E +Fv+D E +Fv+D E+Fv+D E@+Fv+D E+Fv+DY NEJSSUUU^^,,,,X(<xhP\p< T L P  0 d $ <x8\x(|$@8(|tTXt(<<  @`| < ( < !!$!"""""# #$#<#T#$@$X$p$$$$$%%P%h%%%%%&&&&&&''D''''((((@))))))* *$*<*T**++ +8+P+h+,,(,@,X,p,,,- -$-<-T-.d.|...../ /$/</T/00 080P0h0001111122 282P2h22223P3h333334 4l4444455505`5x5555566 6T66666677,7t77788,8D8\89p99999:::0:H:`;,;;<>(>@>X>p>>??t?????@@@4@L@d@|@AAAB BXBBC<CCDDhDDEEEEF0FG GGGH4HxHIIxIIJTJK,KL LtLM8MN0NdNO OdOOPLPPQQpQRRDRSSLST(TTTUUPU`UxUUUUUVV V8VPVhVVVVVVWW0WPWhWWWWWX X$XDX\XtXYYYYYYZhZZ[ [$[<[T[l[[[[\0\H\`\\\\\\]],]D]\]t]]]]]^^^4^L^d^|^^^^^_ _$_<_T_l__````aaPahaabbbbbc c$c<cTclcccd4dHddeLef(ffg8ggh\hiij,jjk|kklTlllmHmmn n4noohoopDpq,q<qPqqrrLr|rsssst<ttuuLuvvpvvvvwwwxlxyyyxyyz zz{{{0{{|T|}d~~T X0D,<T|\<`t,@Tt\8Pdx$Ll(<Pd @` (<Pd<Xl(<\L| D(DXlX$8L`x <T@Th(<Xl,@Th|4hx0@Ph t DT t 8Php8X(x@ Hd$<Tl$ d8Ph<L\tTpLP$L0dhtP d@ 0@dtDx$x|l$,D\tDTd0Ph <|4Ì0Č<lŜ<Ɛ|DxDɄ,`ʔxth͔TΈ<tϴ4tЬ|ӌӨ,| dՠ Lք(@Xp׼t،ؤ$<Tlلٜٴ4Ldtڄڜڴ,D\tیۤ $ܬpLސ@߬\\D0dp40xT8\H@Px,Plph(x,t X0l$pT<<8d8Ph <TpP8l L4d<4Lt< l        8   X   8 t  <,pXL4l\$X,X0`$4(p|,Ht   !x!"P"h#T$$%|&4&|&' 'l((x)4))*h*+p+,,----.4.///00011p220233\34l4455`566X67,7d7778H889 9(:0:L:h::;;;0;H;`;x;;>>? ?X?@ @A$AB BxBC0CCCDDhDE`FFGHhI$IJxKKLXM(MN<NO0OPPQQRPSHST`TU4UVVVWdWX<XYYZZtZ[d[\\\]D]p]^^p^_T___`H`bc|cd de(ef$fhhijjkk|llm8mnTnoLoopLpxpqhqrrprs8sdttulvvvvvwwwxpxy8yzXz{`{|h|}X}~t~d h<T|d<x4T,@0h\lpHDH|0H`x0xpP0TH<0X T84LdLD8<(x (thPxŜ lƜ Tɀp8(P̐͜HϤllє$Ҍ ӔP4d4Pذ<ڰ۔ܼt,߄|LTpp|(XlXLTl `0X 8Ph(D\t,D\t4LdXp 8Ph(@Xp0H`x 8Xp0H`x(H`x 8Ph8Ph(@Xp0H`x   8 P h        @ X x       0 P h        0 H `       ( @ X p     0Ph(@Xp0H`x 8Ph(@Xp0H`x 8Ph(@Xp0H`x 8Ph(@Xp0H`x 8Ph(@Xp0H`x 8Ph(@Xp0H`x   8 P h      !!(!@!X!p!!!!!"""0"H"`"x"""""##,#D#\#t#####$$$4$L$d$|$$$$$% %$%<%T%l%%%%%%&&,&D&\&t&&&&'''('@'X'p'''''((($($($($($($($($($($($($($($($($($(@(P(`(|((((() )H)p))*8*x**++$+4+L+l+|+|+|+|+|+|+|+|+|,H-L-p--..P....//0000`000011t11222L223 3 3T333444d5x5556686`66666666677<7p7888d89L9x9999:0:D:X:l:::::::; ; ;4;H;\;4>x??@@AABBCCdCDHDETEFTFFGG4GPGGGGH4H`HIItIIIIJJHJxJJJJK LHMMN4OOOPQXRpRS(TTUV$VVW<WY\Z ZZ[<[L[`\t\\\^_P_`_`b8bLcxccccccd(d<dPdpdddeeeff(fHfhfffffgg8gPg`gxgggggh h8hHh`hhhhhhhii i0iHihiiiiijjj j0jhjjkk|kl4lPldllmm`mmmmnn$n8nTnpnnooTopphqq<qqrXrrrs`sst@tTt|tttuu@uTupuuuvwwwx x8x|xxxyyPyhyz(zxzz{H{d{{{|d|||}}~<~~<\Pd$h hTh`ht0Lh|(Dh|Hp<hD|4Pp  Hl(Hd@dtLl0t44P0p ,Hd  4H\(T  4Hd (D`|8h|4Pl,Tp8TpH8TpTp,Hd0<<Pd,@\xLhLDTl tL@t4@Č@Ŕƀ|Ȍ4t˰8,̀T$lXШ8Ѹ (Ҙ(ӔԈՈ|xtx@۸hߨ4d0hh`x@\t,l l <   lP`D|  !"#$\%%&'(l)*,+ +,\--.\//01D2$234456477t8@9:<:;<@<=>?d@X@ABClDEHEFdGGHIdIJKXKLMNpOOPQRtSPT TUXUVW0WXYZ[P\\]^`8a b(cd eXf(fghtiXjkHlmn4nopq`r<ssu,uvwxyz`{<|0|}~t\(@pD@T(<l84@XXdpL8pX 4h(|( p<p@ŜƀD4ʔ8հ֘dP(ڰt DH8D84x@$dDpLX|   (  D  H  H( LTd<`,@T\ h !8"##$ $|%%&&'d(()*d*++,@,-..//001l2@23p3445P56467@78@89t:4:x:;<;<<<|<==`=>@>>?D?@L@ABBBC,CD0DEXEFHFGHIJhK4KLM,NNOdOP`PQxR$RSTdULUVWXdYYYZ[[t[\h\]^^^_8_`x`aabbc\cxcdXdde e@eeeflfg ghgghDhiipijkk<klxlmXmnoop\pq8qrTsstDtuLuvpwwhwxTxypyz<z{ {||}T}~xPDL4|0(pH0H(Hd@x$x0T8X\ lhtX$`Dt 4H$Tt<x$$$`LXt\hd€\ü@`żtPǜdD`XL̸͘d τXа8є<ҔҰ @x0נDٔ$`ܠLt8Lt$Dhp4P `8 , 8Ph(@Xp0@XpT0Pht|$P000|0Tl $<Tl,D\t4LdL 8  (   l  l  <  $|Dl$tp<8HD 4x l\xd h !$!" ""#\#$t%&'X'(h()l))**<*|*+X++,H,--`-.x./</00X11X12223@334$45 5\55C!!!oN /C| #'#5'STRhh41 3#'73#'4]']'ooo3#3##7##7#537#537337#3$]jiu'L&|'L&erly$L$}#0| }DDDD!.4;%#5.=3&'&546753#.'%5>54&'oi;emO $]!Ge`;[gO@243"81ZzJ716[ $02"&46"32654&%3#2#"&46"32654&HcdedF)<<*)<;oBuBHceEGddF)<<*)<;dIEdeeF;*)<;)+;^'VdHEdedF;)*;;(+;4}*3%#"&5467.54626=3#>54&#"'322 ;D\qE]/"d\?2Eljp&;Y$]]$Y;&2 ##5#5353FF FFWmh 73#5>=#Wii#32#"& 3265+.@;!wqrvvJFWYM0ܐf[#5>73#f>:X? .R;"632!!>?654&#"2e~dA8u(Ro\`P< t\R6#B-Wu?47U9L $"#632#"'332654##5>54&L9Xesc@8mXHHEN% _OCxSEh[l'ODexMJKCK,A8A %!533##5GBAiiXު]4O6#!!632#".'332654&#"#;Hfk)E-%XrHSTG):Q5W+ip)/wXLO[%+%4>32#.#"632#""32654&+"3G?#Qk X >.LQ:i_zeARR>=PJC^R3bW38O~chSBD[WCGS. #67!5x^(|JϪ W%("&547.5462&"32654"32654z҅y6*ww*spCC87D9RQACSu:waz{av:"@1SjjS2@=32=<24M|NM?>&##"&'33267"&54632"2654&"3G@#Qk X >.LQBze=PJSRk^R3bW38O~chMXCGSTAE[n 7#5#5hhhhhhhhnm #53#5>=#hii#< hh\x&&4-75% -vEOO2oa!5!5aFFFF25-5v EOOM#54>54&#"#4632#5%45%Z%54%F:I8UolatZ%.J1-9 /7%B00=#3?RJswhhh"r5A332654&#"327#".5467>32#"'#"&546322654&#"SZ @hя؟Orpr|yTJBYVLn`BLF]eT&A[6(?b:#X}ꝏ C*kmbC54&+ova(=Z-eF?}/1>D?^r$;;p/T+49E04>32#.#"32673!".00GyL+_^Wj{mZ``!KwG/d2bjO4NNej3Ni`Y3!2#'32654&+YlpplRZe !!!!!dLRRRZC #!!!]t\LRR,$326=#5!#'#"&54>32#.#"$9^]&#&=4&+#!2'4&+326 +q:>]Pks6+LCJEh&0* ")8.IAC?e^>RH6<0m0%2>54/&54632#.#"#".'3V8N%x~X^WJY6BJQ(>gA)KN8%X+U;*)S#0%cswkGMD7*01`C9=. #7\<64Q##5!b]<yRRU3#"&533265(]]eUXdnoOOTJ!#33ddci !# #333ffhdhWPP # #33sqqov0vc #33]socG !!5!5ELfTRR5R@,#3#ggHH#/7,53#53ggHHS,I3# #IEzyE,PB!5B~22P#'`<䔔*)4632327#"&'#"&54>7>=4&#"26=Aad- #()T^O] -<$1&=8:<SEXeJ<6qIF)? %(MTG& &(-0I*^  -P,6 3632#"'#"32654&6S9ihx|hk;KBPPBDUSVZCoooY^p#.#"3273#"&54632T@4DMNEkTp^k}j[m\7;ob]j~`kb#5#"&54632"32654&J"O6jz{fl5DTTEBPP'E1+}Qp[\on[^o(%!3273#"&632!654&#"~+Sf#TvZnmJq&T=?RH(DiV`J@7,AYW###5354632&#"WSFFA: : D8DY8?E5R&("&5463253#"&'3326="32654&\|{db>M0_I\nUqT>#K!CNNDBLK}[LJKeV*TJX_a2,n^_mk^bmF4&#"#3>32#C)@NSS%K8LYSk70aP0'MBtB#7#5SST ii& 3#"'53265#5FS  &SS mG'%ii: 73##kgQSU޵PD#T'F!3>32>32#4&#"#4&#"#FM"K4a,%H4IOT3.3HT3.3HT J/*P,$KGwi27P9i27P9F3>32#4&#"#FM#Q:LZS:2@NT X70NAtk/8aP$ 2#"&6"32654&q}np}~oFPPFEQOMmmm\an6& 3632#"'"32654&6M;lhy{ea@BPPBDTSO^}NoooY^p&##"&5463253"32654&S9ihx|hk32ATTTM$C( Jg _;3"$732654&/.5462#&#"#"'z5E7C&+N]InbXf3<.3PNFuc/7-%" C>IWTMT+$# E:LZ#327#"&5#5353V'14GGS DF+(DA !#5#"&5332653K%P:L[S:2ANSI4,NA/8aP)  !#33[^^ W !# #333*_je^\kdfgh^e hh #'#373$a__~~^ & 3#"'5326?3Z)^ Y l KSh !!5!5EV9 JIKxI+,!#";#"&=4&'5>=463%$.R%.49",,"94A'EF&x'AE>E< E ;F>Ed,3#d<<S, 5326=47&=4&+532#%RR%/4:!++!:4A'x%&x'AE>E< E EK "#>3232653#"/&/571u5i'*bsNFKE%&?z3 J 4t673#5.546753#.8`TjW*euue*XcT9XEg tZmab wz ZYcV254'#53.54632#&#"&3C;?!h(+*9J+#580E8nQ"e5>,X@M ,> 7+'RD'B8.%B;V.&(791![{ 1W9FC'&%'#"''7&547'76327'"32654&:5(<5)380 687,5:(;97,>?,+?>65391+;9-7575:7)42/>V==*,> !#3##5#535#5333着XUUb3M33M3cBd,3#3#d<<<<oo++4?32654/.5467&5462#54&#"#".57654/=)+571+27,kaT5,&3 =0mkP.4'oII' E50&2&u"J12G33E`bO/70%"^'J6s)"4 H`*J0{'?=3m-<(d(#5!#5h hgggg%0#"&54632#.#"327 #"&54$ 32654(!@(VlmX#G -(9DE8V<ᢚ8@$fi2+]OHb|ߞ✞%/M#-!5%#"'#"&5467>=4#"#63232'3265@"/1808DYC";~zTS.9%6b33_,++4,11 , l^z /(-j 757757-jj5jjYzSSSSyYzSSSS(V w!#5!(FNw.8#58HH#.9#&54574&+#32'3254&+& #"&54$ 32654O"F΍K#(=<ᢚh/ -#%($ ~"*C# ߞ✞w.!5.FF 2#"&46"32654/>YY@=YYfN87&(8Y>@XZ|Y98&'87('2o ##5#5353!5FFFFFFF"#6323!>?654&$>@R`?! 3H:;1 F8R/ #:IL& / +B%"#632#"&5332654&'춮&.#?@K9GUFHO>,-*0 3@.)-,@8;L=IJD.++&'3# $\P-3#q<A$  %#"'#"'#33265332 !S;h6$SS:1ANS-1? SS/8aP)L)0O .546;####an^9@R@^e@JWKy'*32654#"'73632#"'< ! . *& &)B9/N%\8# +2"E #57>73#c6$ +>D.0W(/D!52"&546"2654&:DJKKKC'--N-,b33ZTOZZRQZ4@78@?79@.jm =Q #57>73#3#%#533##=c6$ +>:<:.CC>D.0W'x; 5d=L *#57>73#3#"#6323!>?654&c6$ +>:<:$>@R`?! 3H:;1D.0W' F8R/ #:IL& / +Q%)47"#632#"&5332654&'춮&%3#%#533##=.#?@K9GUFHO>,-*0 3@.):<:.CC>-,@8;L=IJD.++&'3# $5'x; 5d`' "] #k$#j$#f$w#|$p#j$#z$ %!#!!!!!!#Ug+n`dRRR(_0*7%2673!632#"'732654#"'7.'.54>32#.#"zZ_`! &)B90M. 8Xy܁!4@r!q3=HFYkbR"6$ f2zK*"ksD*"jsD*"fpD*"|qD*"jpD*"zlD"M0<C%3273#"&'&'#"&54>=4#"#632>3226=%!4&#"01e%TvY:^ 8e;L\*FGf$vp Tʀ/\6IpFZgG86&P@?R4K%iUa,':4TG&9$ N]R&,J@7pQe  ,('-GYW*5.54632#.#"3273#632#"'732654#"'5N&j[mT@4DMNEkTt\ %*B9/N.  8=P,b]7;oc]j^m!# +2!)%("kvH("jxH("f{H("juH"kA"j"f "j$*4>32&''7&'77"&"32654&$-<8'';a$U4,%Ja(Z*F+"||FPPFEQO@e<' (1-'(,/-%)#MLs>Rmmm\anF"|tQ$"kmR$"jnR$"fnR$"|nR$"jmR2 !5#5#5&hhhFFhhphh##"''7&54632732654&#"?,}og<= B0~oeA:\+?FP)EFPGIm>EJGsAA0m_B 4m_DA"klXA"jlXA"foXA"jnX&"jQ\6& 3632#"'"32654&6S8ifz{ea@BPPBDTSP}NoooY^p&"jV\L#i$*"inD#x$*"xnD3"&5467#'!#3#327 h8L3. KMcx7'H%0px4*$:',6 - L+3T4?632327327#".5467&'#"&54>763>=4#"26=Cad- LH&*:1%9QaO]%(@5)1%u:;QEXeJ<6qIF)?=7 - ,!( CMTG"3" N-0I*^  -P,0#j&"jnF0#f&"fcF0q#y&"y\F0#g&"ggFY#g'#qG #53!2#!326&+3YEE]kppkSCCSC-!3##5#"&546325#5353"32654&??JFbf|{ea@SDTTDBPO5;R|N5LnnlY\mZeO#i(("isHZe#x(("xrHZeo#y(("yqHZ3"&5467!!!!!!#327@8L3.=ds7'H%04*$:RRR,6 - (4.6%327#"&54>?#"&632!32?54&#"&)(&"&)?L  (pmJq~01e%T=?R)B,(0 - 0( J@6b4K%iAYWIZe#g(("g|H,cf @7 *&"fuJ,#x*&"xaJ,q#y*&"yXJ,#h*&#JS#f+F#f2K'!5%53!533##!##5w,]w],,]^,ZZHFLF4&#"##53533#>32#C)@NS??S%K8LYSk70aPX5LL50'MBt0i#|,%"| R#i,"i*#x,"x3"&5467#33278L3.^7(H%04*$:',6 - 3"&54673#327#5r8L-(S 0#H%0T4*!8 *6 - ii_s#y,^ #S  dr#-,B&1#ML #f-&&fO#g.:#gN: 73 #%#k0gS  F#j/@#jOP#g/9#gOP'\/DZ'\OP#y/D#yO( 7!!573{(PP]yLzR9M8u 7#573BBS??S3@392@2`L#j1F"jzQL#g1F#gQL#g1F"gtQ.s&\QL&%3#"'53232=#3.XMG @vXeT58Q;1jF&#"'532654&#"#3>32 &:2@NTM#Q:LZmG'/8aP X70N&N#i2$"ikR&#x2$"xlR&#}/2$#}R+-4>325!!!!!!5#".7327&#"+(=RM'y>HUNCu(MR<'])94};;|59'dXT6XLRRRJ^5QZIn?' xex'@n(*1%3273#"'#"&54632>32$"32654!4&#"01f#TvY>?q||q=e=Jq،PPFEQX&P@?R4K%iV`ggj28J@7pm^`ml^a3GYW]#j5EK"jU]#g5;A#gU]#g51P"gU0m#j6""jWV0m#f6""fVV0*mJ%2>54/&54632#.#"632#"'732654#"'7&#'.=3V8N%x~X^WJY6BJQ;wT&)B9/N.  ,YgX+U;*)S#0%cswkGMD7*01`C(ED+ # +2"(%E[64"*@732654&/.5462#&#"632#"'732654#"'7&'z5E7C&+N]InbXf3<.3PNF4.&O&)B9/N. /7-%" C>IWTMT+$# E:/N # +2"(%G0m#g6""gTV+Q"32654#"'7##5!##632#"' ! . * < &)B9/N%[RRy7# +2"%/32654#"'7.5#53533#327632#"'B ! . &(*GGSVV &)B9/N%S*$DDF/# +2"Q#g7#^WQ535#5!#3##x<]eHRRHe #3#327#"&=#535#5353VOO'14LLGGS DHF+(HDUi#|8A"|pXUO#i8A"ilXU#x8A"xlXU#z8A"zjXU#} 8A#}XU3#"&5467#"&53326533279L"'9,]eUXd]%44%H%04)/ pOOTJ4W9046 - A4  $327#"&547#5#"&533265#*$'>J`%P:L[S:2AN ; -2+>1I4,NA/8aP)#f8:#fZ #f<&"fV\ p#j<G#j="jZ]Gq#y="yP]G#g="gX]3##5354632&#"SFFA: :DY8?E5-!533#632#"'##5"3264&YS@ae{|fbFJ?$COPBDTTLL5N|R;X5m\Ylnn0* 0%2>54&+4+3%"5463!2#!#"3263J1>D?F{=G~=Z-eF?vaR1 R+49EM!1N$;;p/TB^r:OPpx6  "32654&!!632#"'BPPBDUS9ihx|hk;oooY^p3IVZCo732654&+332#!'@RQA]]dƀRR=@PhxfcË  "32654&3632#"'#'BPPBDUSS9ihx|hk;K]oooY^p VZCË0 &0(4>32632&#"#.#"32673!".00GyLHP _^Wj{mZ``!KwG/d2bjO4O;ORNNej3Ni`.,"632&#"#.#"3273#"&54632R T@4DMNEkTp^k}jT>ER7;ob]j~`k0L!%32654&+"5463!2#!#"3263glppl{=GoR1 Rǚ1N:OOo754!35!5!!".6;#"Ot=Y-] 8''8R'+EF:.)-,6  "3264&#5#"&546325!5!&FSUDBPPK;kh|xhi9p^Yooo3CZVI&;$ A6Ze !5!5!5!!5!sdLRR'R/'0m/".547&54632#.#";#"32>=3MQv<ix~X^WJY_?XlC`%N8;U+X6Y\+GG&}1-lcswkGMD7.[RT.)+47Ji5&C!!!!#"'532Zt\QH DL%RRG58Q&##"'53265#5354632&#"W &FFA: : DmG'DY8?E5R,.326=#5!#'#"&54>32632&#"#.#"$9^[cEgI>K57FA+#"&54&#"#3>3232>54&'7/714C)@NSS%K8LYI-F&5Hf1?';d+(/70aP0'MB/#1=+.t3&}; Pv:34632&#"73#:A: :kgQe8?E5޵PM #5333##|||T}}TSCCC,"273267#".' #'527.#76*:o0!! z`%6 !$5_LMFb\tMbR @ !#5#"'#"&533265332653S"K4a,%H4JT_/-3C_[3C_J/*P,$LFV27O: iO: &33##"'532Le}XiMG DL%T'OD58LF83>32#4&#"#FM#Q:LZS:2@NT X70NA3/8aP& #"&632.#"!26^pm ԌaVԐ}|Fu& ""2654&#"&63232>54'7t菎>lB @%k=򥤄΢V$$ 6+8]a$5w !"32654&7#"&63232654'FPPFEQO%\%np}~n8R mmm\an+8VEg# & #"2654&#"&632632#4&#"t菎qVT`SI^%&*X򥤄΢V8=gTM5i$8 #"32654&#4&#"#"&632632FPPFEQOHT"'#4np}~nT8@>F@mmm\an]M'*I|))I0#3264&++##"3263#"5463!2gCKKCq[]R1 {=GluDxD]q:O1No6&  ""32654&'632#"'#4632&#"BPPBDTS;lhy{ea@T>9 :oooY^p^}N?9>E[3264&+#332#&=4&#AIIA]]hxVI+q;=DxDxqct:+o.IAC?0m0%2>=3#".546?>54&#"#>32G;U+X%8NK)Ag>(QJB6YJW^X~x%N;46<\7# .=9C`10*7DMGkwsc%0#S)*"$%3#"&546?>54&#"#>2326sXcuFNP3.<3fXbnI]N+&C7E4ZL:E #$+TMTWI>C "%-7G 5!!!!5 fmTRRR&= "2654&#"&4632327#"5i%%2%$2II29B& $$$#IdHD8T'Gm&.5#53533#327#"'53265),GGSVV &+$DDmG'Q4>7!##+"327#"?2]+R16 {Z4+RytO3#327#"&5#5354632&#"VV'14GGA: :^RDF+(DY8?E&Q#5!#327#"&54'%]eUXd]X+8f SnoOOTJ Ao#5#"&5332653>54'J%K%P:L[S:2AN]+8kHI4,NA/8aP)  6U".5332>=4.'3N7MM(](('GF3 4)+l,aJ - '=a?8ciDA&'>323#.#"$  A-s]  iN  @cE"&$!#"'5326?3632&#")^ Yr)^l KU hAl KG3#!!5#537!5EMWfTCRRCR 3#!!57#537!5{SEV]} JCIKCI E7!5!#"'332654#52>Ef ,1|X Qj ҵRT +F,dyMAK  E3"3273#"&'454>7'5!! jQ X|1, f KAMyd,F+ TR q '5!!#"32673#"&546Ə܏0NEHHXmaJIKCKJMxeXc"6 )#"&#"327#"&543232654&+57!5!,aW=6Wb]Baa`\0aSH0Pb &E"LBO40+;KIJ"632'!!>7'57654&#"2e~W(A8u(I_S'`P< t\{O$MD#B-Wo|9"MB7U9L@#%#"&5332654.=#53533#x]hsO?H?H&66&GGSVV&66&]c|nD^8:)5=.DD-"&D6&  !>54&#"#3>32#O'? 7*haTT=_NBW%FGa;' h9$A6F"))P;%^N1aSDB&d,_d,"_n_2,33#3###535#53<<FfFCFfF|Y#?'Y#@'#@,GP#-,/P&#M,/D&w#MOLn#-1L&k#M1F&#M,Q#g$*"gpD"#g,"g&#g2$"gnRU#g8A"goXU#j"8qA!"qmdU#j"8vA#vU#j"8gA#gpU#j"8CA#CI( H)#j"$q*9"ql|#y"$q*"qn\ g#q"M#q,-3267#536=#5!3##'#"&54>32#.#"$9^M $5kL\nn^_mk^bmX(4$@2,}[LJ[3468TJ,#g*&"gZJO#g.:s#gfN&#&"&5467.54632327"2654&8L-)Nd0#H%0t菎4*"7Ξ԰eg*6 - p򥤄$#&"&5467.54632327"32654&8L.*\e~nq}qd0#H%0FPPFEQO4*"8vy*6 - mmm\an&#S#q$#"qk E#gy q"gU2&&gY#='Y#]'#],G,#v*&#vJ[D!>54&#"#3>32#O'? >#)k5__=_NB^#.S8d"+9$A6F"!*/!P;%^N,[EP.BL#C1F"CNQ#zb$@0v*#v #v"M#v=#v #v#$#D#$*#DZe#(#HZe#((#H{#>,z#=*#;,#/&#=2#R&#-2$#R]#!5A#|U]#5-Z#kUU#8#XU#8A#X0m&6\ "&V\j Q&7\ '\ W '"#632'>54.''67654&L9Xes1bI> @[{%LCxSEh[1O1{rJ(>WB\$) "K20<8Az("#4632'>54&''67654'5VbOL`"F52\:8 #ZE9.Ja,80PfXD(: #]4,$J "5E' )QS#g+Fs#gK(G!#"'5326=!5!5EL &)fTͽmG'JR5R( )5!5!!#"'53265u9E &KxIJmG'u#y$*"ylDZ1e&32654#"'7#!!!!!#632#"' ! . 'ds &)B9/N%URRR1# +2"(/832654#"'7.54632!3273632#"'!654&#" ! . 'esmJq~+Sf#TmT &)B9/N&T=?R%VzJ@7pH(DiR_1# +2"AYW&#q+$9"ql|&#q)$;"qr~&u#y2$"yiR&#q&$"qk\ S#q<&"qT\& 3#"'53265FS & mG'* DA$ #4&#"326"&5463253327#"&'OF?HUC@DKplZBS- #(+&JWySQxwvzPBL)? %+$"# ) 6  ""32654&4632&#"632#"'BPPBDUSA: :9ihx|hk;oooY^p3e8?E5VZC F  -%265.#"7#"''7&54632#.#"676328D-][mN800D0j[mT@4DMW)+-6 M,0I)*<6Ilb]7;obN0H  7&L $"32654&#"&546323327#"5 DTTEBPPY"O6jz{fl5S"' p[\on[^ox1+}Q&GmF $"32654&7#5#"&5463254632&#" DTTEBPPJ"O6jz{fl5A: :p[\on[^oE1+}Q8?E(747>32#"&'332767'.#"(qJmnZvT#fS+R?=Tp7@J`ViD(HDIWYA( H)(:5%!326%!&'&#"#>327332>7#"#"&'&T=?R+Sf#TvZd?YE '    -*O=E#mJqAYWH(DiV`>]T r-EB^J@7"#%3#"&547&5462#&#";#"326sXcuVAnbXf3<10>J+;@6E4ZLV&%KIWTMT+$!5L6%-7$":>732654&+532654&#"#>327332>7#".'&'#"'z5E6@;+J>01<3fXb[`7UE (!  -**">AVuc/7-%6L5!$+TMT0OT r-(SE:>K%&VLZ!(>32#"&547"32654&+532654&"az\p",*rcyoG?GO6=;+5)01?WI(5;-LZYsm_-%6L5!$+& 33##"'53265#53FSMM &NN HmG'#K1\|{db><9 0_I\nUn^_mk^bm^b2,}[S9>EJKeV*TJX& /"32654&"&54632>2&#"#"&'3326=CNNDBLKT\|{db<$> 0_I\nUqT>#Kn^_mk^bm}Y#. H%KeV*TJX_a2, !326=#53#'#"&54>32#.#"vMb.`,Lln$8^;]vV C?UVajUB=HV|#HO;'ZR3,k 254''47&'33". `4 ZZ&47L0#/(*/P^N`(F<+4  #/2632#4&#"#"&5467&"#>32>54&4:;5>`L2 (+<-.=^8)%.4LbE-! ,??Af@%6O)Zf)?.Y)T}mG?3'1gF3  K, F4&#"#4632&#">32#C)@NSA::%K8LYSk70aPe8?E60'MBtF&%4&#"#4632&#">32#"'53265C)@NSA: :%K8LY &k70aPe8?E50'MBmG'#5533##5#5TSPPSKii_HHB 327#"&5'14 UF+(  3#3#53# IIRR HHH|"#>32332653#"/#&~/571T 5i'*T"sNFK2 %&]533##"54>5"TT?*("Mq&$R(D& 3327#"DT& mF'GDqJ#"'732654#+57!#3!Yam5I7)EN TT cXexg.KCK=JF  P@ F8 !##"'#"&533265332653M"K4a,%H4IOT3.3HT3.3HT/*P,$KG27P9I27P9IF&*3>32>32#"'532654&#"#4&#"#FM"K4a,%H4IO &3.3HT3.3HT J/*P,$KG mG'27P9i27P9&3>32#4&#"#"'53265FM#Q:LZS:2@N & X70NAtk/8aPrmG'F&@3>32327#"54&#"#FM#Q:LZ& :2@NT X70NA('Gm/8aPF ##3mSl c c$ 2#"&6."!26q}np}~MN)N|NRZ[QFNXX$ "3275&#3#3!5#"&6325!FPPF_&$n7Np}~nN8Jmmbb LL&%/%4.#"326=332>%4>2#"'#"&c-EA*x3:7,T/3(4,G|{G,$<=!`0.b^`:T1 qsIc9!"85H5)JU=('=SL*Fh4ZZ98`EA  U EA 7>53#5#"'ETTTM$C( IJg'_;3E& 7>53327#"=#"'ETTT&  $C( IJg'Gm;3E8A #3>32ATTTM$C( Jg(_;3E&A327#"53>32ATT& M$C( Jg'Gmy_;3Tt 3#4 #4&'T T""mm!%%Ee !4&'#54 ""T %%!mmR6 #'##3254&+32RCgET,@![1*[wHL 0/#$+6   "(0332654&/.5462#&#"#"'327#""X5E7C&+N]InbXf3<.3PNFucN/& k/7-%" C>IWTMT+$# E:LZL'G& #"'532654632&#" &A: :^5mG'8?E&##"'53265#534632&#"W &FFA: :8DmG'@D-8?E5& 4#"5632327#"5_: :A& 5E?8'Gm& &3265&#"4>324632&#"&'#"&S#(0#<"#M/8 31A: :"SGVLg  *5 e8?E5 HpI W &#327#"&5#5353V'14GGS DF+(OD0 !#5#"&=#53533533#!326=K%P:L[IISSNN:2ANI4,NAxHHW/8aP "F *#".5467#5332>54&'531?';d>Af9& ?1nH5%N4,E'5H&};54&'7714SI-F&5Hf1?';d+(U/#1=+.t3&};54SNH9rB4+Ht &"_6)``KxIJ->5    q #"'332654+57!5!,YamXHHEN0 cXexMJKCKIJ4v )4&'#".54>32654+57!5!"3267&,Ya!%nI)</8  '0##(-G E cX H /2&&*5   KIJM$/"#632#>54&L9XesC8T_OCxSEh[9f,A8A/.#"#&'.54632=H:CO_T8CseJNA8A,pJf9[h/ 40+4>32#.#"32673!".500GyL+_^Wj{mZ``!KwG/d2bjO4NNej3Nh`1&&2kF "$#"&547&54632'";#"32654&nzcrVAp\~^3?10)5+;=6OG?ZLV&%KIW|/+$!5L6%-_msY[ *326=#53#'#"&54>32632&#"#.#"vMb.`,Lln$8^;c;N :V C?UVajUB=HV|#HO;'27E533,kF &!#54>323&'#"&73265&#"DS/8 31S"SGVLM#(0#<"#ii*5  HpI* :7 N0F| 333FT @L&F $"32654&54632&#"##"&54632 ETTEBPOU<9 S9ihx|hgp[\onZ^pI9>ES/"#6323##5#535>54&L9XesC8mmTii_OCxSEh[9fHHX,A8A/.#"3##5#535&'.54632=H:CO_iiTmm8CseJNA8A,XHHf9[h #"32654&%!!5#"&546323 DTTEBPP'E$"O6jz{fl5S!p[\on[^o?JIE1+}QI[q+7#"'732654#+57!#5#"&546323!%"32654&Yam5I7)EN J"O6jz{fl5SDTTEBPP cXexg.KCK=E1+}QJ p[\on[^o 7:"32654&"32>54##5!5#"&546323!354632 DTTEBPP &"ISNH"O6jz{fl5SrB4+HV#p[\on[^o   )6)``E1+}QJ->5'^1732654&/.5462#&#"#!"&5#53533#2H&+N]InbXf3<.3PNFvb05GGSVV6.$" C>IWTMT+$# E:LZ+(DD &#+#"'5326=#"&5#5353354632&#"#32 &s14GGSA: :S^5mG'C+(DY8?E7A#327&54632#.#"354632##5"'#"&5#5353"32654V a.j[mT@4DMNFB4+HSNHP: YM14GGS 7 DFib]7;ob]j->5=6)``*+(Di   90#"'532654&#"####5354632&#"3>32JF &:2@NTSFFA: :#Q:LZ58G'/8aP8DY8?E5RX70NDz(7#332654&/.5462#&#"#"TT+F7C&+N]InbXf3<.3PNFuc22&5-%! B>IWTMT+$# E:LZD- !!3ET$ JII_-/Z*)Z*)wh!#5!#!#5!#FFFFhCC߉CC:4&#"563232653##"&5g& C)@NSS%K8LYN'Gm(70aP!80'MB&^%327#"&=#"&54&#"563232653: :A%K8LY& C)@NS\5E?80'MB'Gm(70aP! 0hK) 0j)  }u 3#"'53265#5F6] 66eG. DD&U)Uf>=3327#"=#"'776 Y+0Cz.G&!(S \fZ);\w)1 1'  W #5433ii#<x&&4W 3#5>=#Wii#<x&&4W #"=<#ih4&&xC_<5OC "3"&546$44,,4$#45"#>32#5>54&2%9IEBK,$7>3+6-JKC<%B u*%*5.#"#5&'.54632(/&+3>7$,KBEIg03*%*u B%3#'#!c&MN&3>dM-O33#'#t_`?QO@``O2#'373_`?QO@O``LQw.q\P-vPCd#53HHt/qS7$C=#7jg"/qL"qg"3g~?"rz_!{_, B7533!5Fx۷FF BtM' nB %##5#53533ByFxyFxyyFyy B-!5B-FFU< 332673#".;T(0; $<4B" @sd#5hhhOC 2"&546"2654&%34H44$$3%$34$#4,93 "&546733278L3.F7'H%04*$:,6 - e?23273#"&#"#>[X :Jd :-%%h&%16N\3#%3#=q<q<䖖A:57332>7#"&'JE/# -*-NcKN&r-_UGID0 ?t):rfO)1V);[)65)( #!5H H( #!5!5HV 3H( #!5!HV nHj( #5!5!HV H3( )5!3bVHHH^3!HHO_HHf#3!HHOfI86gQ. &qcq1. 37dM&.7d#.b$/cL8znkn'kkl'jje cC|nAMA3##PHH#5#5HPH 8B53!53HHdd 8B!53BHHd$M!'&/76?M%33%h +00+!PCPvOfe|wq=us!5sHHUx&dydj4}#532654&#"'632,, %(04F@ 2&$3CzO3}Og1Ly#53HHLL&L=N#'##'`<=`<䖖U/#ycxU x1)G +2A 3#5265#X/)-I+/*,A #3"&=|-)/J*/+I+A8C8v3#5#53HH3SH5G!#5#FwC #0#53>54'%-I+8pT  &xs$853533SHSHSSH8##5#5.SHS-HSSH ##5#5353.SHSSHHSSHSSXdwM}U&-'03#"'053265SS &-mG'&X-50327#"=0& -y'Gm[Zy^jzqzrR 3+52505#d4.77x8|9kew.qIq9Ch3#YLLhC3#YLLZzPw!!75#}'D}}R4632632#4&#"#54&#"3%&'$3,,%34$3 ''7'7N __ `_ __ _GHGGHGG!FR5654&547w61%&h%%", g7 ,  eMcQCP\|2AXV[@Q/&,&Hɻ3#5#=S]WS#7&#"#>32733273#"C+ :-!%'>1  :J4G%16@T%hh4#y0#y|cF=,#|_| G  Y #5'67&'9 4.8+ A , -19% 5 !c:M'3 #y xuPQ~j#~P{$`& 57'5%3#'#鼼K!c&MN&uJ:;;=_/|%yJmR37''7'7/PQ5'./&4QPDT,CFFC,0Ib2>32#".1#"&54>32>7".#"2654&#"W   & 2$  $2 &!  *    1   "8   8" \(     &M(&     " 37#7#A%9?' e_9N267>73"&'.'33232653#"/&3C+WL**,"C,317 =A%*$FKE +); ?xNU+ 5!'7'7E%9YY9%E.?3@?3@9dDBQ:JHCQ<UL?Q:HRCQ:L XCQ;^FDQ:VXGCQ9XPK:Qm7j B@82>73#"=3 !1JL*OQO,nm sPDdlM  3#'#5!#5Za:SES USSSSV #5h hh&#ze '%5&'se"D'60"F$Oo%R6!#!6|`{5)3!bt{;Ze(G=S+&!5!#"&632%"2654&)Pt菎@R1VԂ򥤄d,O.3#3#tcxh'uK0L1Ac !5!!5!!5!S9gI"SsSaS&2P!#!#!^^0y[i3(^ )55!!!^tU#RRQ7 <%"&5463532##4&'>={|Q}{SoVWnVon_}yQQx|_vVk c r{ lUYq;4%##5#"&5;332657"^"zvZKWVWKZlaaQA7AQ7kb4!%>54&#"!53.546323!;D.qmiMOZhTL /MwAqnc#LH.ei,H #j' #j20G#>'"{BJ#D8+"F2"`R0G(4>353327#"&=&5#"4&#"3260 1S4o3H +%#_BxRBJFHN!CJ9%FRW E  8Nni[f2:)4>32#"&'#4&#"32654+53262*Q;_iho_;F!VH?4;)qAE2*?G7RE$dXw%_w$3?)jTJDJI: #5&#"'4632 V $.'v F #7~$-74632'5!! #".7";27>54&$v_    y =A1FRPD/ %RsHH%$/$8]gO[]]5Rb'$%#"547&546732#.#";#"267 mgffpf ch LV;@@..?AHrXPB_`Q?CO'%U/(;(/,(%'&>".5467#5!'654&&7L=+gdqED&L" .T6BH<@ℎ.A7aE!#J4&#"#4#"'632632'.>X  $CaR[Xn{lLH6SU.v8 7327#"&53 .,<@XQHE< !##373l>XXr < ".#76323267#".' #  6 !$0!! z`') R !0:JFb\<8"&'#332676=3#5+6XX 8)*=VP; "x##*17j>%%  Y>/%2'654&+".54>7.5467#5!#";#"jI'J#&8Ca1'3"6D, dt*@LXRnJd^F*C=^V"!78.H) K9+HFFT0+E@RxD$RR %27#"&5###5!#$ 0.,^G(@@ >0FT:FF28"4>32#"'#2654."20B>!r>D+Gc5X>\11410/1=_8% wlm&G\l8Q)*RpR*"J&4632#4&#"'>54&'.'."tc|[JB01(H;/H@0Z -(FI2.9vu_@F'L4.B%  -<,n P %p"N4>32!##"&732654&"",KO,@qsZ[=IOLLMo9RRf{|k[lZVpr "&5#5!#3272I1! #CQ3II+O 2 7327>53#".531S<# XjnEZ.XbHD<$~V:H7F.8 #"&5467;32+#54&+3267yZF*3CiNXyn X]OK] nZ$D!d;NiŘznOhf ,<+"&/#.##56323327<$=ah   B `Ã$ 3&hN<$*8l %##5"&53332653luVtZGUVUGZlljn6YQNQY$'%2>54'7#"'#"&54673>=3.4(4lP*F$<=!`0.b^`<4Pl3:7,T"55H/u"NFh4ZZj<)lIc7 "jF2"jhR$#L2"qR$#V6-0"34&47676323##".'33265C#s,! CC 1L24N, P>-AIGSJiC " 3'@*#HAeX8 &8SG+;^@v4 052>32&#"#54.'&4Df>! PK> '&'@2&`.&-`3ZQ3^E$. 9\xETp_+4 "]4#j]98!463532##5"&73"4&#269Xz?nJXzTa_b^_ae[uy8^M+xSk|oOSki$+%2>54'!3>=3%##"'#"&5467#5.4(4NM3:7,T#>#$<=!`0.b^`!>"55H/eeIc7 ߞF0y6Fh4ZZj(3F(  2733#"'5326=&5#7654&# cgjϐ &Ɣh:ka Tc}NqmG'OFc8*0!>3273327#"&5#7654&#"Z1;Ocgj-#/,6]1=Oh:%.+(9_e}NF9F.4>32#"'3263"#"&572654."90B>!r>D+Gc54)-1'-Dm>\11410/1=_8% wlm&GR4Rl3[\l8Q)*RpR*&a0%"4>3"3# 1S4CFFBN!CJ9%LVLBJVP[&|CP[&|jl2+53254+##5!#)J[ 54Rc9B]WE,? BBK9PPP9#jz0 .#"!!32673!".54>327^W^x lgZ``!KwG/0GyL+NNsR~ej3Ni`12bjO40m6d,#j,-S(!#0#52>=!32#'2>54&+,F.%ua 1>M@{Ch_T,^!DNteGtY^rR+48FS!!#3!332#'2>54&+^]1]|w` 1>M@L;uZ\rR+48F2#4+##5!#)J[]B]WE;9PPPn&jP#C [#xSy !#5#3!3P^v^y'$Pp%#!!!3 4.+32>p-Y=t] 8''8$FE+R.*,-Pp%P93#!!]tR"y %!!>=!3#5!#5KE*[PPR5hv ٥qчP[(  # 333 ##nEt]tEv]Dwb44D0m1%2>54&+532654&#"#>32#".'3V8N%`ClX?_YJW^X~xi(>gA)KN8%X+U;*).TR[.7DMGkwscl-1}9=. #7\<64P 33##PX}eXi['VP#xPn 3#33 #]]1oz|2M!R!#52>=!#,F.%^RDjaV-^"FPvgG'P0P+.2P!#!##^1^y'P^30&Q7 [3#733qqo{0-$0<032##5".54>;532>4.#" &CcF3'?6K._.K6?'3FcC& _0H5''5H0_0H5''5H0|7jH6W8( ^^ (8W6Hj7]%JdJ%z%JdJ%;Py %33#5!3#^CP^Rqчy8"#"&53;3#MX]L]]\A)9t'P %33!333^b^^R'yPy1)333333#o^^^CPyyq 3#5!32#'32654&+Dd@RQARxfcRR=@PP %#!33232654&+#pd]=@RQAm^cxfR=@Ph'Pp %#!33232654&+pd]=@RQAcxfR=@P* 3267!5!.#"#632# `Zgl x^W^_+LyG0/GwK! je~RsMO4Ojb21`iN3!P#"&'##33>32%"32654&v^]yr|}rq~|aŗ좡^ 463!###"&6;5#">vaI]oneqQ7>53"32654&w,oq}np}$F2?)/J1%S(:FPPFEQOFLU  ( N=mmm\anF %4+3265'4&+326'2+y[*1.;bb;.DRFE2.TGF+$,)P4Q"5!3#5!#573#W0"S@FnFݬ@4xx t;&H 73#'#5#'35kjSjk $#732654&+532654&#"#>2#"'|5E6@;+J>01<3fXbnAVuc/7-%6L5!$+TMTWIK%&VLZF 33##FTlSm ccF"xvF 73#'#`jS   !##0#52>5lST !2  b%F$ 33###FhhT2T FkCF 3353#5##FTTTT &RF !###FTT @F&S&F$ !###$tT L@& \&&;&2632#"&'##"&5463253"32654&!"32654&-dflmd1>#S?5egke^1S@CC@CFFdCBBC@CB^#+-&^lll\`nm^_lk]am [F$ 33333#5FTT=F @4x0 3;53#5#"&50T"TT;V B9Fn %33!333TTTL @F )333333#dTTT32#"&'33267#53.#"*m[j}k^pTkAM I:4@\\ck`~^TMIP;7F#4>32#".5##3%"32654& 1W7q}n7W2! ^TTJFPPFEQO51D8'&8H> יmmm\an 4>;#5##7.7;5#"F!@,TEgCR[[*1w/0L=F+$&&Cr&&jy]'4&#"##53533#>32#"'5>5B*BKSTTS *;#KZ7PP 5{\70bNT@EE@!JD?k@#(|\F|"j&%#3273#"&54632#.#"3_MAkTp^k}j[mT@4:I T^~`kb]7;PI$VFL "j&M  72>=!32+##%#32654&SqmkaF'B8||:81P,yCXEVa6,+(F1 !#5##335332'#32654&TTTqmkbk||;72 BXEW.))4&#"##53533#>32#B*BKSTTS *;#KZS\70bNT@EE@!JDF"jAF#C&"xD\F !#5#333:FTTxx @!#&53653653#&'6fKpdFGPRdFGPRd)EB&f[38~ٹꉕꉕo߾@ӽ 3#&3653653#&'^RkdRkdR^C#"+jwy<5*8l U0 #"&632.#"!26^pm ԌaVԐ}|Fu& 2#"&6."!26q}np}~MN)N|NRZ[QFNXX'{'* ''7' _' _A$''#7'57'573@87;?K7MCCBBc 53353#5#HHHcCBU+<`@ f4@C"#46323".}' @[QJFN (N>~ '$I]!)!;  Y& ]' ]Dy' ]y' ]3' ]F' ]f' ] ]33Uk "/<IT_3#5265##5463"37'654''&5477#"'7327''7632&#"#"&5335532#4&#X/)-YX/)-?3 ?3 >4"  >4" I+/*\I+/*JI+/*\I+/*>4" >4" ?3 !?3 X/)-ZX/)-Py 332673#".333#5##;U(0; $<4X}eCPKiB" @[qчVF- 333#5##332673#".FTl7#!#53533#32&]vaYY]\\1M+4^rFxxF| %4+3267#!#53533#32[)2[UF>>THHvphN* CS~FHHFKLP^ 654&+327'7+#!2KC62L$0]-lu<:2!6a@TM;lhys7U^poo@2?2CNO^P993#!53!]]t`Fmu353##FTT i@ > 3###535!!jj]KKtFYFR ~ 5!#3###5H6DDT>HLxFFPR93#!!32+532654&+]t\]]\40+55+RfZZfR/-'-/Fq 26=4&#"#!#>32+)C$*=3T'A9I>YLF079.[Vd L0'IFBM y%3#5### 333?@P]nEx]xJчDDwb44* %3#5#%#5# 35373JF!Sg0kSk@x*3g:%#"'53254&'.'332>54&+532654&#"#>32gv6L80%H!uX+U;8N%`ClX?_YJW^X~xi\x "7*4 - 6% t64*).TR[.7DMGkwscl-1"32732654&+532654&#"#>2#"'53254&'&'z5E6@;+J>01<3fXbnAV_S6L80%H!/7-%6L5!$+TMTWIK%&VDX%5*4 - 6&Pyn%3#5##33%IP,]]1oJчM2F %3#5#'#373@F$SS`@x P'#375373#%#0]]0FxvtF)*MR?F 7'#375373#'#1SS1FgkgF) 1k%gqt* 3#3 ###5353``kxdn]UU]oF;D)Fj 533#73 #%##5ASGGk0gS<..FjF> #5!3 ##Dkxdn]R;D #5!73 #%#̸ k0gSLPy%3#5#!#3!3CPQ^]v^JчL;F$ %3#5#5##335332+9)C$*=3TTA9I>YLF079.[Vd@ 0'IFBM0%0".54>32#.#"354632##"26=4&kIsC,0GyL+_^Wj{uiX48UuF^1-3Ni`12bjO4NN-R>9:[` 5 &("&54632#.#"354632##"2654&Wnj[mT@4DM;3J6,GWHT+%# }b]7;ob]kW,;1=>\`#"X2(03,#"'53254&'.54>32#.#"32673L80%H!Hm:$ 0GyL+_^Wj{mZ`` 5o*4 - 6&CUlL"2bjO4NNej$&3'#"'53254&'.54632#.#"3273jL80%H#Xfj[mT@4DMNEkT\N7o*4 - 6&qb]7;ob]j~Vh $yQ %3#5##5!#bCPP32'Y8?R01f#TvZj~PN3*$+: " c0L0# .MSW4K%iV`xU>9A-'!%+m&=0G"*3m9C#"'53254&'.5.54632#4&#";6763 !32673.#"L80%H!6_Y4fgM?7APC0IS,{jW^_(5d &C-Vuo*4 - 6&+XluSN];7 4*6BsKUMO$ !-9"f[&3;B#"'53254&'.'.54632#&#";>32!3273!4&#" L80%H#UdPN3*$+: " c0L0# ~01f#TeL6'Y8?Ro*4 - 6'jU>9A-'!%+m&=0G"4K%iM_#fMSWP#^' #x}#xPR2+5326=4&+#33\]]\40+55+]]kxdwfZZfR/--/;Fq %4&+#37372+5265qC)lSSkMLYYL)CY70 MBzBMI07 y!#52>=!3#5#,F.%GPURDjaV-^"FPvgGqч !3#5##0#52>5dS=FK !2  4x0)CXDG.PAAqQDPR!!#3!3+5326#^]v^^\40+5L;9ZfR/Fq 26=##3353+)*ATTTYLF07 BMPy!#3!33#5#$^]w]GPTL;qчF% 33533#5#5##FTT=FKT 4x=4#"#632>3226=%!4&#"01e%TvY88e;L\s3$vp Tʀ/\6;W1FZgG86&P@?R4K%iUak:4TGQN N]R&,&9VL0Qe  ,('-GYWP[#x|&"xr/ #7326?# !654&#"#632 &C-Vui/GwK{jW^_+LyG0!-9"f[h1`iN3eMO4Ojb /#j' "jp( #j&}#j0m#j~$"jJ Ey q 2Pg#iF"ivP#jF"jx.#j&"jw0&0#j7&"jm8*#j$"jQ [g#i&"iT [#j&"jV [#}&#} 8"#j0"jXPy9 73#5#!!CPPtJчRF| !#3#5#F6=FK LxP#jFl#j0m1%2>=3#".547&54632#.#";#"G;U+X%8NK)Ag>(ix~X^WJY_?XlC`%N;46<\7# .=9}1-lcswkGMD7.[RT.)*"#%3#"&547&5462#&#";#"326sXcuVAnbXf3<10>J+;@6E4ZLV&%KIWTMT+$!5L6%-7RI!#52>=!+53265,F.%^\40+5RDjaV-^"FPvgG'R\R/-q !+5265##52>5dSYL47 !2  BMI(b%&4$&T : ZPn3#37'373#'#]]ru2Ko}hu(z|β](K}U MF 7'373#'#'#qXu.`Z`u ljS qH.YOl F%2653'#"&53YrO_W;[ 1z^&:Ipv]De$$c#6"(Fy2#54.#"!!#4>aRp=_'O=1D&/_&>e-XlIRQ:LA)LE8];ZR4<&%##5#".54>3234&#";k_?_1:kLBd9# n^K5J*/@U8:MZ5$*:4TSq=RR_ #K:rO_ 8m+VmL^8HC"pvQR/F^<+Fy%26=3".53!!YsN_%=dDSr=_/ %:LovQQ9XR6!+XlL] 6"*2.32#4&'!#5332>54.#"3!54>32(4+{S!$YZZ ;[O:,GF"YE]E}Y:hH=! 37,BvI=Y-siQLud65}F %#33#@`OOMFy#54.'"!!4>32y_'O=9I& &>eDRp=9KBAM9W;ZR4-WlF 3"2=#".546;54&'#"#4>;23#E.4Y_5pQ8Q-Wx?U,1_.KL)i66p8^U8%AC''>7$iBAE:- :U+q\KNA|%"&=4673533#2675#"mjm\c`EHAE[po^~\oK~:O=QkFx4.#"#3>32#%I8Jg ^b y/Ts>^8MA45%:"-ZlKGF^333F_^F%2>=3#".=##33$#-+_Tp9k__ %II,`K,YjL@9KA #".3!2.#'#"&54>7'"=4&#"26v\^$ A~'8fC4Qkpk`Fy%".532>=3#5dTr>^$G2.C) _a \,XmL)4C'!">.)L$42&'"#.'%6,%' /t-g.0" 82O/8"'54632&'"&54672>=4."327&KD[_oteV I/(9OFA:$55?U)2#5 2*WG=BXo?c< ( "''K*>C+ (Y,+D%0#9A|23#4&'"#546.ja=QCK_q\@K@F3&'7672''"j+;?)/<  ?BwC,@<+XA 13/ `y^,G  9=C5/2f (L_><%2>53#".53\8J%j>qSRp=_NK BL7]MmV*-XlIXvo-&4#"&5332>54.+53254&#"#4>32)Bny](2 )##!5-X45G`.[?5T2! cOds{S!35!-= Ms:35,77"&31y ".'#53326=3o=&9%sN_< 'UmQJ^X!6")ovIjY(,467'5'"2>=3#".5(nRXK*=)`q;_8%MsTaS[R[ F2@FY%7A3Fy#4.'"#4>32y_'O=9I% ^&>eDRp=b9KBAL:c;ZR4-WlBG#%5>=4.'"547632G-JT-n61!GW_@Ks>c5$ 32t_,+'a^&>eDRp=bBV0 Z*+;ZR4-Wl-9674.#" 632#4&'!#5332654.#&'54>32 )*HL+0L, _(4+{S!$YZZDk>gI)n)>iB2K?:UH?\."3A5]thH=! 4793'4Y#GP<(%7F3##4.'"#4>32yKN\'O=9I% ^&>eDRp=Q9KBAL:c;ZR4-WlFy%2653#"&53YrO_:sTz^$GIpvbFh\0c(3E)A %".=332>=33#5_Tr>^ &:&.C) _Z \,XmLed !9)!">.)^$-g.%4.'4632#.'"#".53326 0M^`O6kHl4[VRJZ2Oa`O2qFnB+YHi(3'O9\i(5D&C>75$0)O7\z 2D> ?Fy#54.'"#4>32y_'O=9I% ^&>eDRp=yx9KBAL:c;ZR4-Wl<9(34>32#"&5332>54.#"'67&62654.#"O.bFX)Fsq]WI)-"#/$h#jJU[RxP1;!: 9<$e% kNb}w`?M6#%95A `@79(3FY33##F__Pu-'-%.54>7532#3674&'\<^E42D]=^;\L8!9@H4^,=/"^ob0,?tIGp>,77 (?vL54. =E2"n)OM0!3C=eϚz '?pI~5;5#"#".533'".546;32C/,..--Ym")flAc7# ]-48#ih_.j~0D ?+hGG_{$4-mp ;)X=pbOC_5 53'65=^35mfP9G"NL73X7iLA2>32327#"&#" 6=,0../8%x"0705PC L14>32#"533254#"# #E/:C4*U7%Hn721?/,3a)7e*=3'?3! TJQ=2TP7A !%2653#5#"'#"5332>53(E;XOT4t=cX$8()X*B_ZI$0[[h?@ A.19<F8%#54.#"!!#3632X &KXO323##4'"#3 !BLddXNGEXS   D>>6\_K A%2=3#5#".57!!WO:k$9% XQ/AaJU'!1BDG#8;7"&54632533#2674&#"Wrqf7GRc1HC:CACEAmu+6g;LTitKVtrFM %'5#33#&LVJwBA8"!!3632#4.#I_O323#2=#"aEBHYB=>:(? VNT%B^+)(PH $G^JC>XA79&7.A,"$81P=#"#mbYbb}_1O. '4 z hDfz%9KE"FQ;F8"#3632#4&'*-XX>]dOX4"H3GhaI?>F8 #3X9eF8%253#5#".5'##33XO9l1D$XX+?"JV%D7-}BF#$#"&54637&'4734.#"32"e}~P=U3;^+ZJBIy\Lvu*C=64(2@D8I1ce[mF8%2>53#"&'3 *-XX=_fKXG"H3, Eia ~F"#3632#4&'*-XX>]dOX4"H3GhaI?>#&42#5#"&54>74.'47332>54&#"  KVM D9dx1HJ>@D)HEF*: H9=R- _?3v4bH;0 -. PUs.J8Un}A8.23#4'"#3>8)=dQwXRB*)?2\ W$,747'7670!0%4&',@1>fh1':eb ()#1@2{T0'F],IT=A-4.54>;#"#5#"&'332>7u+3+-,MT6**T) !!AQX$*,: 6&@&$D%&+9O8G6-+2*8 2>53#"'7& X9I*{ !^X KF%273#5#"&54>;"_XPWOAQ%M3A_@1?FS]F49B-=7>#3(3.+53(E;XOT4t=cX(()X*B_Z,$0[[h%) A.19<&7#5;#"54>54.54>">54&A].7*kƅ%+%!0/!))4 9/%%(9.GB#," "?H0#2-$+L4.G) JB@!7(*@)79R&ERA72>4&#"#36323# GD:4XO%2>533#"&'3*-XV=_fKXG"H38 EiaG~A8&%253632#4.#"#5#"&53U.&/ LXpaHSTA8"#3632#4.#XO5#"&"32654.#wj\;Q "1J/0<'UA1)89Wiu5W.-8V-/sJ?:)>*/ ;),.;1-D?`f;V*ai7R+F 3#~ 6B A88)%2673632#4.#"##"&53M;T32'"254&umQ15VddQ E;%HG+4LE:@g}ABLLBFA47kvU_HfV^# %".54>32"32>54.+NL. 1C>!m"1E; 1/]<2/01 9pM>b;&wDh:$ +U9ka-U98S+*8o#+2"&54>;32##5#".533332654#'35"Ia!=I/>=Yl*:7-W/(9/!NW%DD5638PC*; ߆i<_9%7#JnlS-%!F ".53326=33#5+> V%*GHVO>-# `-+KDBR$Z5353Zdddee^dd &27"':'0AU+Y  "U&k']&&&zzn&W&&__}U3#GG3>Wd 3#GGGFn  :FX##5467'36=3>Q;f&,WPJ[sCVL(CJ)LLX%#73'.#52#BuQ3G36TC82X˧2C L,V9&X#!5!W  LL?:X #.'#53@{lWAZWXamtQ: Lw>X33>WX'(X##5!WW  LL?;X 4&+#!2#aJW\Wt=[X}a=:X%26=4&#"5632#"&53?Y Rzd[Zz5c:X3:WWX#&8X 2#4&+5iWaJXY7>=!53!!'.2W%,4 [W5V+, *bD?Rn(*@5?;X 2!4&+wxTX_X+@U@0:X4&+##532!53uWA:X:N{wAT L`L>8X3>W 3FX 2!534&#GqRVX^L(>Z=9X  "&5!24&+3265ڑtWWTWPL[[zvhrJN>ZY?$X737>737xYs# W +C:C  &!X5:9'LO98 X4&+;#"=32#N]y,@"(~}WtTD1Lsk?X*2+532>=4.+;'.=Aj=*(GqA753 [UWc#WKc/6 +3X!5!3>=3 f<&Fd0-W@jL 4'>8&X#53>7!5!#&l|>7$5#ooWlbL)Lr%X 2#4&+5uWO\XuitNJL<X6753+;2653#.53bWpFd x>WBW{R}W,YG^8\nLRXZW#{X4+#5265#5!2#$ityMI<yWtukLDM/Lqm>jX!3!3WWXX<`X3354.'33##:D! U ;!,kWHV,,54'* S*IC25^H7Z,MuG)5!5!5!ZXa #"&547'!5!73#".5'2632 tmM 8#&'f Z-=Z #}a%#"&547'!537373#""&5'2>33 tmb(M  6?'f #Z-=Z#} kq#532!5!4654&#yyB\oF(TS 1)Z a Q/k2!5!4654&#\oF(T 1)Z a Q/m".5463##'2654&#"2>+_UdY /\&44*&>9'X<_,EX!gOI14XY+6Ld !"&54632"3264@bz|`ay{1bNQ43J~WZ|}Y[zQK/2KHbV[s.54632'>K"2X ): 3_2@(HdfO"!#'#'!53547O$JzppssZ\r_ '!5!73#_J.DZ,>Z__ '!5!73_JZ,>%'!5!5jieO`ZS #/;U2#"&5462#"&54632#"&546'2#"&5462654&#"".546335'##" ! K" ! " ! K" ! h&44*&>9.2>*_Udj[ /> ! " ! " ! " ! "NI14XY+6LO)V;_?ObD,EX!ga(%#654#"%632Z\@DtaE\I;@ؕWHO]qZU;I@np)5!7pHfUJZCkv )##5!5#=SO^Xk?ks'545):5-AASXDfm(hj7&$ "&/.(# #xOaI8UZ-_Y+M Y :1 0#`n2"6E2632#"&+"''7.'&54676>54&#"3">=4&5):5-AAWT<  "dH*h "  7&$ "&/.(# 1#xO_J.2 H+".1` +M Y :1 0#`(2%#"&'!5%277327kF3C$&8,A(E,71D1Z)m+>CK@ %"&546?!5!'7327SdVF9? >"pA0(F)OL8F3Z!."$@ #"'!!32654'./;Vd%>  ?)F(0J""LOZ@3*$"$#!5!".54735'#"&'i)t6/i #% W||W Z8V|9_ SI>O] "J?<9!& 3"&'#53!'5#@F5AdGUdk`#532!5!4654&#{{n\oF(TR 1)Z a Q/!#"'!5!'#5!3/\  ;;ZZYck%"#"&54>732632&\ n69KN(Gn*i :((/C9Ee*lSJ*+99&T''7'77''7' &%Y(,$"5'=d+jA!577''7'77m &$Y(+#"5(>+jA!5,O3#3#,####s*/'K7K/'K7Kq5 2#"&54'2#"&546.! !n! "! ! ~ ! "* '7''7'78!8 8KK7^_`]_ 77'7!9 8 8KK7^_`]_3#'3#r####K}/'|KiK@KL[ 535#53#3HHIIL"< <" D 535#53#3 HHII"< <"Ov 2#"&546>" ! B ! "6{ 2"&546/537$/++U y!;9 2#"&467#'7yi.++-$ w";8jv 2#"&546>" !  ! "fq#53qbb]jv 2#"&546>" !  ! "Ov 2#"&546>" ! B ! " x 2#"&5472#"&546).! !7" ! V !  ! "w 2#"&54672#"&546?" ! ! "! ! " ! " #2#"&54632#"&546'2#"&546" ! " ! K" ! t ! " ! " ! " !4632#"&74632#"&2#"&546"! "! ," ! n! 0"! 0"E ! " %N,`H% RHf7!#7777''7''7''7''7'$"$$#"  ; ; ;> ? ? @ @ ( D C C C B @ B A V  )3533##5#6#"&542#"&2#"7#"432V'SR)U, , ,  OO%QQg, z- ,J'72673#"'&'3&IDI+FIoKM77G*!AICI-6N]02I+>M'7[TZ\T\?)'%%XQ23275#5354&#"#"&=3326323##"#"'"&'73254&#"'254'"'4>8P9T"/hm! o(8A]5DLGJ)J;.)5PC*,HG5; 3IeS32: hVJ,/eE.;-)5PC*,7AHI3;7\):Q9W@ II A*(<e@23275#5!####"#"'#".'73254&#"'22674'"'4>32.'"'46322674&5XN4*""&R* XN4"ZH39 "0d-'D",,5)NhkM62 ;$F_vE] `IIM ; ,R?8"-[ 8+Fe+4&'!5!##".'72654&+'326 ~Fr+7,/hK(N<9'!?*Ed072D0aHDEEIIC2A(U1OZ';PIL+&dd3&4LI$e=2'>54&'"#".'72654&+'32654&'!5!!6{325!5!!674'432")::PCJ7(J,b %-2 VF5|{F7 %,?>:R71C *?ԟ;oN8DFIII4 J)!-d$Ae>.54632675!5!#327#"&54>54&#"#54&#"sh2-AuX@Q+K_89'77'*(;);HFd'77'7%&4J2,)@@SDa73cIIf J20Q4+'!=)M;%;**>& )/"FU >{?'fB2{X72674'%#5!54&#"#"&=3326323#'>75##'& *J# m$75##'' *%U<1Y7 M7IIF3 ( @={#2674'%#5!#'>75##'#'7' *%U<1Y7 K;R:3(!/f# r&;A`7DRMGLK(.dF.;%8?Q=2-6BGN/;6\eU:A* I1"!0'M57 TJ MMHMI(;K:.)6PD*,6AHI3:6[eO@A*&I:J ]m #)<'P:3 <7.CI, 0+WD23275#53%7'73####"#"'#".'73254&#"'22674'"'4>:S9T"/h1d?UMGLJ)+`I1;r[SC*,7@HG5;6\eY6A*&I6$J 8MN#P;2 <50CI, 0+e<7267!5!!632'>74&#"#5#"&54>32."#"%H9I8]yFT.#7IH+@m"L2 % #/>(  IIP[9X0m1!,/' $`D'O:P4)4- eA%#".573275#5!##4>32&'"3275!#'32675#"&{";9eE9" < 2' HI#*  =L($D)0/$8X2(94D:Pq7)@PLD%$."-IIX1F: 7Λ&3::%:(3.D e#".54735#5!'# E.(; GH :DII$Ne).547#5!##5#".54547"327!;"6ENIIoW2=*(2.*Vx*IM*^I0%IIܼG 9&6-$P'(/Le']vDle%2675!5!##5"&547#5!#8e $lGIt5@NEiY&;,`:IIEFb-#46 uIIg)9PCHnX%*-g'ie0C%"#".546;5!5!##5#"'.'"'46322674&7##"3>3232C YN4"HI7T&-'D",,5)NέH 7. '&)E] `II|-F ?8"-[!8+.M ( e(6323275!5!##5#"'"'73254&#"pN*)'II3%D*Ur0INpK4 & II!2&V:Fv95 8e746;5!5!##"327#"&Ag68z)5+6(_3DV{lMmpIIL()A$V*te&732654'7#".546;5!5!##"s(1'FB96LzX6_<"`Hl$ %;#197'1*iP`.HS'De}II#ce/46;5!5!##32>2#".573254#""&1>cY6.>.,"9="4lRB$34P_-q H6yy"!`II1= I 4L(6MQ7."FX7p"e%.74>;5!5!##"&54632#"&>54#"1A+m)2YM>4#5 #:<g>(2.7@,hII>.?i'-4B,& *= L);31`e####"&=#5!26=#`GJMC@U*`[-$?YU>I)1V4e4>3!5!5!###"'.*1'\4GI?%4 !! 84+hIIr;>6 !*+04*e3%275#53##5".'3254&'"327"54>320rahHI!&*5eC)(15.B; "3+K^-II  0FH {)6.0Cs 0 eEAe&46;5!5!##"34'432#&'".*E4H!(F&,/b$In?' &?W`II+!C&1*M >B)93(Qe/%2675#53##5#".547.54632#54'"63"e9`HJ_r$: 7-5G3.CI(1:0S ư89II_ & ;*W*0HG(%0"R ES48e463!5!5!####"*JY8GJ B(%IIOX 8e']6Ke 7"&=#5!##5'275#Mh*IH*I<7;X@II'J7%-e'7"&=#5!!632'>54&#"#5'3275#Mh*D1E^{E"S955H2;-;8ۦX@II?WDV0j2)&&1#%-0e$%67!5!##5".54672&'"327'M HIA=5L#YF G8+'7.#$ IIܮ/)<0Eg PN,6vBe&235#53####"&546;54&#"#&546.IhHI b )4N Fc/7II c r!/0=e46;5#5!####"&%5# )^HI bg "II NpI,e4'#5!##5#"&'326275#9,HJ=Q`$)5LPD)A54)II5xd ,Q,30ZLe#"'.'732=!5!#dK6 :y3 "'0?;J>O3BT 6")3D[)-).IIe$7'7#"'.'732=!5!#~847K6 7b&3 "'0?;J>O5958#3BOx'6")3D[)-).IIe%73".54632675!5!##'47&'"s4F?%NN8cLYCBHGJ/b B&1./F$K3!@-Fe0E^GB|II`r*!!(. ,:e/;>75!5!#"'#"&5463232654&#"'.#"32>P >%A,RK^'(`HS\DWR:-"+(( I3)VV' `4kIIn 1;,UfY]gVQh'NK0+<  *Amu- e']Ve&'"327!5!##5#"&5467202,.!"B<^I@CGfWHFl9'( <!IIܮ4j?Dh e )&'"327#%72>7#".547#5##7E'(d+yP2./6@,28:.bMGM*!z8EW45( =(*6&5IGe 7"&=#5!##575#327'Mh*IH**:.EʚXAII'&,)e#)###'#"'.'732=!5!5#GK&D 2a,3 "'0?;J>&%,Iw.6")3D[)-).Ie57".547&546;5!5!#!"632'674&#"{Y=_R;AK#K&:!99!;,=09X{7K< *I-98K>&`II ; 7' .! :#53__O]K##".'732>54.546;#"?ZZ?bK)\A+3Z`.)?[[?>3 N +;470W2"'632#"33254k@ 6=S&7 rP\G wBxA!.:)'AS`]L527#".54632#.#"B6.9)(@R.SQ4a^(%42>7#".546;#" &)D;='E9aTC9 <*8%8IG6&A!/46;#"63>767#"32>7#".547&E9aTC.!"&)D <"  ')E=:$C{8IG6$ <* !  ;)6#0#$J;2673#"&'3q+FIoKLnG*!-6N]_L+cEX'54&#"#"'&=3327632E?# 8 'B96Cc1"-M5 :|Kt#'7LK=K;LL#%7'7H?1c>L7#-H###53'2673#"&'3MHMj3@HoLLlG1/H6.N\^L%1!\###53/54&#"#"&=332632MGMX? l&:B\5BH2 0'M58 M ###53'73MHM==YH:,###53%7'73MHMB1d>|RH7#^9'39!W 8b#53"&533265232>32#"'732654'"##".5732654'.#'2>54&#"'4>{RR}zFR96];P5" C7ECRPB:* %!+Q#(1G( /3(S<29{Q)(.%$>3' 8*YLTqqT6FE7ZAF& 2;2o\Ys1G0@/ .- -@!3HK2!/"3 G0 ()  (.2 6#5D6*2!5!&3,M2.#"676322(1(! 0!R +1$&;<>32&#"!0 !(1(A;&$1+ e']8l e']9 e']x:e']m?~ce']Dhe&E]Ee&N]O,e'] 2RDeH%"327#"32>7#".547&5467'#5'%&'"'>325!5!!7'432+ML>88O#7( &)B=<& +4)H+b"+$o?|B-&A-=8:% <*7#5$&>/C )=ϛ9lM7 S]-HH5J* -eZ.54632675!5!#32>7#"32>7#".'47&54>54&#"#54&#"pg0,?rV=O,Jhr4="00",1 '*H="'*E*4)";"10"3$%7G,"2=>SE\61`HHd L3*G-'+/ <* " =)1 8$&>$>-+6!$(%DR"0x@f77.5463267327#".54>54'"#54&'"J& /WA.=6,= %.%"$0&'9 '.'K'6%#.a/>4E*'$ #B(.#, # 9&34"3>'v:SfO7.54632>32327#"32>7#".'47&54>54'"#54&'"J& 0XA0= /&2'/'$#$$  + !8&.,'.'K'6%#.a/B(J*."?14 $,  .+*/;'34"3>'s'C;eE&P{Z] #"&546322654&#"i]^fi[\j:CB;6E@'aigc`kmG>:DF<>Cqe 2##"&'463254&#"M]I#9J\i8!a?"$5egRX7YFI^,/\'26Xe>32'#"'472>74&'"X}/Z}#72<eB;/0SN?5^#.qU4W7 +J4/q97D/de3%4&+'3254'"'>32'&545463226b6A,kpfO-'%66!@&W?P7osB/B1?KMDB  ;(T!"8!HX t0>*ENe#,#"&547.5467>74.'3254k_GE[eZD-?(d,CJX$9X]eDUiWI@OOCHW?L3 B $=]+L ) FKK9ef2#'#"&547327.54'%XML-5Og:D E.- ?I*n[_m"f0G=8^ee0;#"3274'432'"#"&'47.54632&#"V$HZ/@@0 @!'8C8" RjV43_BH!)G(+DIH)(7 V(1'#_K`2K(74&#"3L#fP +! $ WR"+ 2'^/e%7,H_ *  1BY%%/Y*#jQ) 2#"&54>32654&#"1I49:" (,!))A42F0 #n+",!E#53x ;'f(x#"&'3274632#".: @#,M0& !8GVN#h " +( 3254&#"'7#"&54632V&/"2*$;(1C9%5D&)!6*_1)@42&C"0 !-72654&#"7#"&54632'2654&#"7#"&54632 3%k4&(AA"D_ 3!o,.90@#(8c80$?3;'1080FB,$4@t.%!5!##5'#".'732654&#"'632>G3R4C;aA* 3{F(7?()/S<^2 [?33\~I:bi:=-'N< ek!{6&'##5'#".'732654&#"'632!5!533##4-3R4C;aA* 3{F(7?()/S<^>   3JJ3:\~I:bi:=-'N< ek9 [?3 M3 ?3!5!654.#"#"&54732632'&/32654&#"'>32nX $l6J0 42Q9HR@$M% ;+*9(EA1T&547#"'732754.#"'4>732>3'23!5!4&#&+"&=4673;63636G)..4# :G"3'!=2%3$ /\ J(c>.] !E1*$M' hkUUkr:d (!%#d1[22 &M*#,8=33 ,< %>#J23!32?67#".'32>54'#"&=#5!6=4&#"5473J-Bg' 'fP9^8)32R.(< "  7.>f- 7W6E3)2 (/!ZoD_|V'!jwU#3)*%( +-3 f01@7B23!327#".'&'32>54'#"&=!5!65654&'"5473h.D]2@S'AA#QE3313-$B'2"<>)&Bf- 7W7H3"p9u2J%VL^R#/ >*4S:3!f21X092675333##54&'#5&/67.'#"&547^O>393TT3 632$eo  L&*'"$pC; #8"6#'%y\3j;D+t`N8H4 21& G/3$ t5y;2>54.'&547#"'7>54.#"&54>:F55&8,7 F.6R^R6d,* , %* &-#gK>!+;%4 2J.1&6($*R8Mi*1+ %'/!9-#;02#.#"#"&547326324&#"327#"&546(23GG* RR,BI>F, #;3O9H_5A@1:'+Sz %/9#LYH#4/&#"#"&547;26324&#"327#"&54632654/&546?LW3b =TQ,2BAD. #7Z%\Z $*IhiIW(uG_A5@1/1+Tx"+'8k0:&9(D)!*"I>2#".'732654''254&#"&54632/ "2)(H75%)3 5Q]L#".'732654''2654#"&54632654.'&547QP5,"2)(H75%)3354&+#5.+"7zw6H0/")( ^03@ %+)()C`*.33.&N4#B%'7LfX/X/+33##5./>4'"&54732677W$Ld3TT3:3:*4(%1( &'=5FaV3R,1-45)*%/DJ+{,2533##&#"32632'>54#"#"&5469M.3CC3V^!F O",E**I '  W15HM3`7#)"!E( O  %1^.'27#'47#5!##5./7##"&& !&')AtO=3JN&H $ #a133AAZ (R8  :D>32#"'326?#".'732654'#"547&'7&#"325 I4 '.',59(#)ZJ>g@&3"5R0<3% =!o#>(5<$UE$)  W<P-JcM|B @ykAE.0,"4!W>&1 5.72>54.'&'#5!!2#{#bDLgc"L6: G5Yi 33=.(L.5@.+532654./#"&=#5!!;26722654'#"'%8/?R/."C`z;GWw\304*= "12 NC1  ;)3/)$7='3\2?^.33 @ZK&a.E7".547#5!!327&547".'32?#"&'&5332654'(3 KzK9<0 #BP2$*%,SAH3'B%"@(2.a1337'!>_;HTjN]X !!# B!-"')!3_x+F! ?E06'$&?"+675!5!33##5'&/.'#5.+"7py 63VV33?#q#)Ca,+3AX3= W2;V1:F\#"&'#4&#&+&547;636;24&#""327#".5463267>32'"2654&#"532654&0g,[3B^#H}/69 ":, %n  "#! *7%!/,=j7_-X83%'/(240gCL3E9 C<9*#S x $ $>Ab!>-4lxE49!,3%F'323!32654#"'632#"&5#5!654'".54736A/;d&!"-:c4,'d[[ #,!*aF636D$"1'6vS?'O3 2 / ) .T".3##"&54>7>54'#53&5472654'/2Ư2@fHFV2 ʬP6&):4I<"G0,N73Pr^HZD N=%3]G54#"'62,'x 35% *>.9d?'33q 6Y1&"!=8[?$327+"&54>32533##4&#"r, )F>)3& 3__3Y$5@?3^+1:('c3W+w=&U.&*&54>32#"./332654&#"%5!, 8&KcfS)E/(3XYBLF57va '" '{y$?+JV\IFa!w33033##5./67654&#"32727+"&54624\%jI3QQ3";,C&,n'B7) ? (V~Q8P)YX3I% 58<"*$3- 2DJ.'#5!!67&54Mc3Og,./j2"33!PO_ Lcb-5%"753##5./7&54632&#yR3GkMp^),*' yU-J1& DJ3VGK)[F=8Y325!5!##5.#"c4#4,b3P3B$&3"&D/F"f33BE--,   s*>32533##4/.''654&#"72767&#" n6F4UU4 $%.% # 3#43B1PE3:. "&!$ 9 ,+2&) Y.*3254&##5./67&'#5!!6;2#"' I<38b($fNr3>(69vK Zr7 #1?ZFA_ %M#6@33*8&1,'aNaP'% .%.+'675!5!##*9!~A3H''%Y.6b++33$X,GA.,0#".'32654'#"&54632&#"326%5!%`K9bC:"9%T,P"5R3O)4! K%3oX #!9 @s1Jm_:Q?3Z7?#00:233.!.'5#5!##54&#"#"&5475>NB93`#B2U)D>=79.&!"33C+P,],82N.!670/.+5!##5.')9Y KU?3)9?Y7;G&4#9C 233LCR F7""5j.#675!5!##5.+"754632#"&G3>09twF&A^6&33Y/, \'" .-;27#"&46326325!5!##4&#"4&#"L0&;GG;1.+&S.N3@'9#.%)90QR0+ 33Y&0)57#"'73>54&#"56;263533##4&#"327#" 4$I5N3QQ3Q$!, <X&)-1=8k_  $W3^s/6 +.&'#5!##5./6&'5-~"F3BdE*I;0|UL#A33@]G:%3(?+7qc{.(6325.'5#5!##5.#"#"'7326s@5"'2 B_853 :'30A,-84EK7"33N4?4j" E.#2&/32>54&#"&546'5!6lY&_:&: M$B0dY:o2[p!H9!(# $<"%*9+K33_ 4632#"&"i2".$5!&/32>54&#"&54635)-AY&_:&: M$B0d/3NK+o2[p!H9!(# $<"%*9+K{&'#53533##L2Rb  3YY3:3 M33###53'>32&#"B_Y3m_* DXV#BUe33:IdNJ&Ww)$23###53.#"'67."&546ц TT3ccf,[o -@Hi)m33Rv$ *!$%H*iL%'274'7&'#"&5463267&#"0 *$(0M9< */) ?%(BA9/"9S @$463'7&#"327'#"&B1. !+1( )3&0b)5?"!#&r/%)?+dsj2QG~8Q%) 7'?'/+dZi!j2QG~8l@~8/.7267#"&5467#5! CL^O<V[rD3  :fK$31n5q807467#534&+"&47327273633#3267#".GA-Y)*3,2 XLG>=0 C0H"Q/342H #4M3j@~  :9TD {'28>ISYk%#"&5467#5!3267&'#"'72327/&5477'67'67'654/7632&"'&'77&'#53533##BL^O<VZsD)B!*  $5G!,+!,4 0 !+s2Rb 3JJ3W:fK$31s6p #,*d3s #),%*u  44%+):3 M3 w4%/9?Vw%&'27#"/7'&477'67'677'654/62'&#"&'7267#"&5467#5!26323###5374&#"#"&547."*0  44K%,+!,342  !,OCM]P<VYsB&K /?hh4yy2@`S)$+*544 < u )+$* 4O#+' :gK#31s5q~;!,33$63$)7/Z'7+{({ 1/"'654.'&54632#".'732654+9.'!)0 J[g7_G:OC# & /8-.$Li 4 ,#H/5.PBTA4(84'  >426323###5374&"#"&547c)B&Gdd3yy3(B`T*4(,33$63!,7.1<%"&=#5!!>7#".'32>54' 4632#"& %. S(4wY=a9'3Q)=#   * 6(33[%p"YoIg]$$6-2(   4 a.+3"&5#5!!>54#"'6324632#".#4Z.M#"(Lj!- /733h]d# "mL_-!.)&+5!##5&/674632#"&eU I3C,=,E$Q(/(" !%&]23Lp/8B*5G# "fX '&Y=2675333##54&''75&/67.'#"&547HpC; #0O>393UU37ds42$eo  L&8!$/3$ ,8"6#'%y\3i=C+GT*8jU`N8H4 2!$& tyt2>54/&'#"'7>54.#"&54>32>54.'&547#"'732654&#"&5465,".]-b ,* , %) &-# :F55&8,7 F.6R^R6BC&C=$ .8(!*4F2!$19"#.*1+ &&0!9-K>!+;%4 2J.1&6($*R83W );" 3 %1   #&$,62654.5467#"'732654&#"&54632R=2GG2 )(0;/D7#!+45,$/",(  &6#'9 &-  #("24j2654.547#"'732654&#"&546322654/&5467#"'732654&#"&54632p>2GH2# Cm/D7!"+45-=].g  ((4/D7! "+/5{#/!,) .Z': &0   "("2eB/"4'#4  0': &/  # *AA 4&#"326'2#"&54>pA4>I@4O<8DR^IXncK*8. Ml,)%#"&546332654.'&547ls=.A?1   19O+$==* 1EE1Mc09/ M=4/!1 40 +0=b1#%#'&/&/33254./&'7^2a"HC39@!* R'u  H:''*"6E@~$@;#''=.V  *!&#-?,'2#"73>54&#"&54>&/;1!)BD$a3+mG$6O>$1: 3 #4X7A^/z C!*4#Dz%1= &,9C 262654'7>=&#"#"&5467.54>32\>s(>h%:r"B+%3~aGER'D@=&7/43(J/#C.@ $:O0", *1b8JL?'13M/%8 8&+61%23&5467."&546?6?&'>32#".'732654 Z #+) .! cI>jF+3=o@1BL2228* #3-(8.!(! A*ZwEo? Hm]E?`0;6363637&54&#"+"./57"#"'4632375' 89/"NF 6 l6(,x&CWL),*&v=g 4X:7 $  0"32>54&%#"#"'4'76326323;2?*-,;3N#dJ/%,$/-9/'   18#@>/#  JOg3"# >4%>5.'.547?654&#"3"&54632H('E6Q &1$&\xwR:)&:?G;7Kp I4%  - " *#/=pN` 3 0=+!7.258El: . '.+'675!5!##*9V$ %9!~A3@>e X,X.6b++33.!7.'77.+'675!5!##`:,n69!~A3H''%&&Q . I7 .6b++33$X,ba %.#529{M]M/b3=aOIf$ 3#2654'7#"&=#5354'80AA/F-,4_D99VV1$19F3Z/&R*."'<=sJXB3KNba 72673#w63/M]MOa=(K&$ %>75>54.#"&54>32%< 3@" '%*8(1[,Z :3+$'$. 4)34P: <1732>73#"&5463232654&#"&54>32#'"@8K}S5 3AR-2BY., " .9(7, ,!L;h7H# V]nѫ"D8#3(#3'(?=8N/' 73#"54#62i %*L9TG&7N#V.(7"&=4#563232>323#"&=4#"l + 2" *1&3 N?M?)" 34)51e+%72654.#"".=4>321K)! 2,# 1G$#G/98#`P(#3 $ "3 3 554<)"C.Qdt $326?63"#".'#"546326.,O+&?43473#"&53%/YHJX 7,( RlkS%0 wX 4632"&00!H $!&000'(m+82#"&54>4&#"3262#"&54>4&#"3262GC84F.e2&$ "!)C #+C86D 6k.  )("mD86DD8 ${&"3 ( 6%5CC8 #,| ) #'+h>26?53###"&54>5&'#"&54>54+5326766+;džAN/7 '$+$BJ;& G8\>]P "3[9'Ai JE>&9)H+&WI]7/$;"A /X8DaPB26?5!#####"&54>5&'#"&54>54+532>6+;@AN/7 '$+$BJ;& G8\YWP "3[9'AFi JE>&9)H+&WI]7/$;"A /X8 aJt05.#"!##"32>2#"&'67&'###53546323598G4׈iYK$>! c6\w@}}kc43 q-{L:bA!f# (asINagn@A:g)Q783567&'#5!4&#"#4>323####"32>2#"&-{MwZ2JC*@#B/am@_3LcPK$>! d/uA|T[gnAf>H&@;"g:A@>/!f# 'co@"5"5"4h"X/@3>7%!2#"!##".5#54&#"#326} xJ[J0C]=Z5%biMFu'L54W?2! DY- +Z= '9S26F@Q0NCA41 *H8*AXo#Dk64 +EC]Ic>+A 8[0OG(5h"!%87"&54632>7!5!##&''327.#"Pny]cn-p! C A3 .% jZ#  7H7rSXkyEAA!-%3,3M:3"UD'9 $ J#%2675!4.'#532!53##5#"'763%T )@E ikY ȇABcL%0@Z=1 AA54'#53232>54&54632'*ʉA$0EA 3:p <>2SDHJ&)4)! A"Bd#'KANE.J3A'+c 'TZz.}'074>327654&#!5#5!!32#&'#"&7327&#"<>*>\4+B:M/E"_6,E")Y^D]@;*L;KHY+)265-"AAv/.NM1mH4SL8(?L!4632!5!5!##".'#"5!32>F3#<’pZ1MG*9#]O!;1S.?6AAsOd?zY~~f3&.74>3!5!5!#!"632#".54>3&>54&'#;$ 6 -t+o&tnrn,  "54&#"#"&5467'32>7;4=q86:;kb 2 ; (K'' &% )3!W- g\'"'(l*";%=KAAATW2OQ? &8, - 3M  KA!4%2;5&'#5!#!"3!#327#"&'.'".54[ K l>2O+#! ;+)X  @=;xx7AAA$,@ )70  h%#"&'6%5!5!#"32>32zM>k<1&,SAOK#)/#>"! euC!AA )G/3L )%4&#"32>".54>75!5!!wWPu2K, \VT3/[<4(5,0A1 4$".4>32!5!5!#!632#"'&'#"254#"77%!(%EaG

h4:}YK"3# $ u(7AAA!1X-mL37:K}9@(,.76325.'#5!!&'#.#"32632#".&Z<; I3 QEG2$i!HGU Bn0 PiW0W AA#W < @5*P7 3-"N*#".4232654&#"#"543232765!5! 69x$2/6f(H@;mZ*LM?l") @Mq.?e(>% .5?=>8B5/AA"7>54'#5!##5#"!5275!0% `AXeb !Jh >,(,>AA=G!.R=,Q"4632!5!5!#!32632#".5#"[+#Si(~ )Z5Id6H>5RJAAoz0 .+*QdE7>54'#5!##5#"3270 g@Y_b >YEhB-+2AA=3.5fKQ<\)".54675!5!!5>54&&Ay!3%,.PR  3 wm]H1=A.B/[AAY+" A=1H]#732>73##5#".54654&+532vOW75͍@Lb(X@)L%*-H AE((  '7!5!5!#!">3274#"26wEl1#B2% *)A$?Ta!W+ZQ>90MO%;tAA>&!1 &G8;IB1)+*}!*!#5#".'>7.'#5!#!352675#Az&Q^ %5!I?>0m 9_Vv3E32/ (1AA?S-.;oJz.8!".543232>54'#".54>3265!5!#3267&#"*pP6`A(; -hi); $K4^HK-p^y!)1I,=Ij+!1'=*a-'+,JPaAA]:!WqO!&(<74>54&+532!3##!#"+"[giʊ@&)5YD?o?WEAB)Av"4'#5!####"&'76732=35!f `?''D'@9g+5s RAAi$5 m*O|Lq#K_x".54;5!5!#'26=#"t;eH~K7$LZ]i=lAAeYM?0&(=bb&*%2654&##'"3"&5467'#5!##!BZS>]\?T%/! \}iINMGj|_Q|{;[AGY^B%; >}][p AA qZXD 157#"&546322654&##'"3"&5467'#5!##!R3BZT>\\?T%/! \~jHMNHj|_Q{|*!& [AFZ^B%; >~\Zq AA sYXD+!5!5!#!"3!#327#".'.54> ?$'+N +6%8 ;*)H/"+)=xAA.0@.= 1*?5 4% #)%74>4'#5!##!#"&!54632#"&3/.$sA  )z$!Z]wpAAEtv!74>4'#5!##!#"&!53/.$sA  )z$Z]wpAAEtvt 325!5!##".54>;#"v!6U.]I~01;^ 7$1(LDG3`AAc.D"LF #*@]d 2#"&54}&2%57##5!#@dBFAA.#"3###535463298G4@}}kc43 L:_A@A:g*P6"B23###534&#"#4>;am@\\2JC*@#Cg:A@Af>H#@<$@h0#"'5327  Y_cUX^ (A('h0#"'5327#".'5327  Y_cUX^iY$G%cUX^ (A('' A(' z2#".'&#"547=}!h&=!4Z(J&x)$*3!#'|&4632.#"&5432.#"+#4hL=]j&%T*)%(")("2;#M:T\HP &  @2 !m".1&#"#".5432  -U $) 1++/-#4 9)#".54&#"&547232632#"'67&#"2] $I/a3E& C!+2+ 5/)?4&5$&K, 53& #?1&#il8# 10  $.%2675!4.'#532!53##5#"&'76#"546323%\*q  ik0' ɈAY"=" !32 /^) A[\A33X[0  !*7#"&54632746;5!5!####".73275#"_\Hv@7!5!#<^TA8,h32#"&547#".7"32>54 0W8eh  8M5w-I+ FW#4)29',3%e]')E8/aRb?(0$?2. ,p+#463 #"./&543232>54#"#"i C^!>;h2!^H*l{\%t4;7[5~H2=DJP1+2)[-3?YI.,t9 $ !*))+532654'5".54>4.5474&#"32SZ]`Uj"+GGJGG,!jUNPQN5ZX7Z@"%? ! 5%.C!!C.#3 "@%"@m<..<`3273#5#".54654&+532*$WOMe@@Lc7nCL%*=/<(8HFE&9'5&'&54632&54632#"&#"yH; ]#<`VJ9p1!AO5mY%47CHK @ -(/("=$7@0 G/V" ^F#7" q BO D DB )$< G,2#"54?>54&#"#"546Qi *"9(E*&u8:FhK*kJ66  ,z_!;4,*!$"4-^GN-C`+" 3=YX"4632!!32632#".547#"V*M-IS%/k4dmQ4@2TG@"6`jK1 #=]c j74632!&'.54632#"&#"#!32632#".5#"V* @KG @ !-.#`#K_%/kcR1@2TG%) 0H (*7 ZxB #>^wH!2#"54654'&#"#"&546(; $ ()=1M'%+&C!1#<I 5B#"&532>7 YHIY 8J7 UilR$1 4!&767&'#5!##"32>2#"&35.wZg75F;8L$>! a0nD-{VZgnAA . e# 'boќ *0<.#"#4>323##"&/47#532674&##326@^v7R/ @ &=kFTv8cbLEt'L4Ozy29Z,kaA6Fjn+=5%EO9&5ZW2AZn"Dk65 в$AAQb7[5 Jbnt2#".#"#.#"!# # &'45#53>32>#"&547#"&54632'2>54#"%3>74.##32>sI, %4\;@z  #N80$ )  !fT,// {yM" ) +\FMOdhihW,4$@tu4.?,^>+ ,;:) $($A! "! EkI]]9A]ELw=3353###"'#'32654+732# (/ kyssyi8%I9C$A*? 5"xDPcW( BRUD%, u1#32>=3353###"'#'32654+732#pp[)/ jyssvi8%I9C$A*;,X 5"xDPcW( BRUD%,  $)*726546;#"#"'47&546;#";#"(J8&d)_FCOX7!UL/I#,  ?0W CU>1!Y8GS6'X.! /246;#"#"'4>3&546;#";3265a4!c*&z^;PX!>UL#  8'-F1>5H2/ES'S7IS1?F $;<"3674&+'32654&+532#".5467.44@F1Z[('d3B> p>_5! |X42$= Sd B0AM*'V( !SK7k GX(6)Kq,D;346 #4&#"32674+'325&+532##"&5*pj'IH+P;8JHXYIHd3C_olme 3l!B-#@-'OSK=SK7$7 eJUfP62#"'732654.'#5'74&'"'4>327537&54S.L6fEX29#(<)1sNR#*O?*Y08]+AsP9OAB@OT12. 0%:OܠMd"c @Bdq&": I'r30:'s8:'t=:'r1; 's9;'t9; B.4>32&#"3%#".'732654&#'7"&G"9; M^ 6?f(  Ն-*)(^b-6KXb. P/% 'P8Y  D;/%*G3UH 232=3353###"&=&+7~!1>Ckkggi8J,Lg:+2=;g9JV?TJ)#34.#"5632#"./4'7326Jhh 3#L->vn /Q32"WE'9;-)( X ls.9. # AM9'732673#5#"&547.54632&#";#"n3nYg-,,L~}K 9?l.[JC*9'TNY.:747"&54>;#";#"27#"'4672#".32654&#"X#G(! J<)I&(?TA44 ~PxRp'"%'}H/D3"4 S4#X.\}Of0dEP ;/#&51346327#"&5467.#"32>5#".WF*U>3L $2;49((" .     /:(EOa):>+R _7NJ=f $($'0 X #JS=653##".'532654&#"#"'532>7.#"763632u, 76 ".4*,;;W*@-%6 0*.B$9CH3Q=He0ZYD7G(7_JMW')=/BW@X*a#353###"&'732654.#"'6gxjooj $.V5,o )PBG2&L"=kQ&E/%;[ /LK-? [&4&#"7633267#".54>0+:1C-K%C?&0c9wX+NJ->YZ>f c9'.91(XF*]&>)P8*>!#*2&#"#".5467&5462654&'"$@-)E[>YY>-JM*@b8# X77p?*NGgIVYCGnO)$4/AR)@N+=,%1;&7C#732654.5472&#"#"&'X j2:N@\]@<4.BdA\\A/LN)b}G2&"+<.[<#E05N'X!$ 54>54#"76323274632>XY>l2C/Kqh=XW=@-H/{:)?#!3 gU8);!(-9K9_2Ed)#3%#"'53674&#"76"&53327dooG]$H1$%(M-# 1%srO;T@h+bN99"Pa+5RՆe:CT%\ ".5463753##"327;F5$wRqq9D . 1T6QrlSF;{X40#5#.5467>54&#"#&54>32326734p,z-oTZ.1/5-a3 :%1H$TL[BB:o.""bS .%&06 , "13HR 8F+%7"327#"&547"&54632&#";)')LJB[gN;t[=Y=+,4* TTXgVJ/;+?MUDP461#5#".547.547654';#"326734p3$85%N9-b-< Z)1).~LG9,o4v ?,A,H)L &!RM'G+N "72654#"'463!53###".>%!QK"$HDSqO5G>7".=4+5323673#56O$%[<%"_rrF7%6+T,/ 5 _C64>32&#"3%2#"'#'&'732654.''7&\ 553b!>9>& 6gfkKCWb"KB  Q,  J( 'YU.LQ~5A"6&   8d"726=3353###"&54>32&#".;osoos'AA"l!77,.9IA/T)7O'sy9S) Lu(".54>32353###"&54354&'2"-&RN3RT,8@,kooi\:1X)$O6+-BNr: A*: s@.]<#4&#"5632353#5##"&543s#%=jppj.$8+bQ, X rj&;&`+'#5#"&546;2654+532"&#"32673+qW\k"`+EX7I|*  .& 3uq/t9x_(;UT@(2:7265&+7!2#;!"&/#5/>lIe3fG(V-%MT\D7k TU$#5#535327#".54632&#"po"6*7E9't)0"53 51-SJW&9a32&#"3654Mhs %!"RAV~ ?+ B2(XVHl#4=[k[=l+QO1 R F_#5#".54672&#32673pVYB[,jY5-'64+Wp,>*GC&P`N/9k*'K1#3"27#"&54>32#.'&+532>74&Kpoh,&1(>P!79f{5$5 fX {%#.%U3!9173267'"&=4&+5353#54:QOUm ,qqV(".`6 T$J&2353###;#"/#532654&+5&0#mppS9 + &F10 6%-1a T1U,*%T3"&547&543.'"32#"'53254&#"327)O+?.-?E]2M`5<1%YYBAM$J3~NOYw0&SP"*4$>AQ0%.B;!9(6[(+#53ggOe #46;#"#"&54323254/&=8*$6e6 :%Mn 0p6g50>$,S3- +$_*#a47W/<#3qq,^4>3'.#"#,JM)~3_ #*FUpKl8SZ)  `N,2#4.#"#&56>@{Hp0 XdC1A5&'*"<#'2#"&'732654#"'6I][KGh<71J7'H'%78YI D$3*"327#"&54>;)7v2=.CaXx$, 6)\-Q*_H%: Q\#+-46;#"27#"'32>7#"&547&G:dWD '=*'(O7?!    '(O^XDY9KH7=* .%=+V4/%(r #".'3327=#D&>22`-x)"-[=t#3` O'7#%7uh]Q"G_ #".'3327#3"F&=12a,y) oo.Z=f)#3#3_oo  '7#%7#3 ]P\MLoo:& 2#'32g !Js2#"&54#".'33674&#"#"'532?672#".'7326%3274&+73274&#"'63#".'* %? &5b>-K2*oM4v *#)5#Z-O)H<9$p]*" +R"4H`4a!"db;16b c3'#5'74&#"'4>327537&54S.QT56* e).6!!-#,rOU $U>.E#,O,AtS6<&8F, 8(!6_OݠMd!~c +2&I^J !: I"&5476;54.#"3265_B@X`=?# (78+'4_\EB@B\_&7 P9:JJ:*463227#"=.7"3>=4&hSNg+3,t/B #,.# #ANgI<#"d g (7+v.*.+]&7432654.#"'632327#"&5&XF7c =*=?Q0k ,7H2M^T[Q 'OiU  Z[J +#"'7326=4'732>54.#"'6Zr""d}]lk6OG-GG++51#9 cR="' lH`FL83(HV   P!""&547&/7'32654.#Ml}N`7[4$ M@b%"!&"L5]E_^Gp+"Lj.Ga-.%8?!%;#"&=#"&=4+5323273 "i!44E[P "$* 4nE TqT:T -"#!.47467.54632&'"327'"#'2654. B&.C`v+Y!P %o]o]fq-H@\]@3HA#ADN   Rb  +?-=NL#8*%".54632#"54732>"3674&j+JF)`CSf "6W9O @4+@ 0*#`, +)Q9HJiX=c]>%]%7 6)2&!,-3?h,'"&547%327_BI>0;AI+k5hVY4P1%1Qc)"%#53'"327#"&54632&>q(>5"#0.ns"0'Q[M7O&^toW RK B%2#"&5464&#"326%54&+'3254'"'632'&543226e0<.-0=As"%'!#%*g5A.lukU .3N`B&Y@R:qv7C.*J@,(5?-0=m342'/BKPDE4OCU$"8"JY x1 A@I?S> 2654&#"72#"&546(43)*43+;[[;<[[F8(*88*,4];32#"&"&546324&#"322T42,)3=2+)11)+Y@;[`6+@$,# 7_a5BV7`Y>;[a14)*21+*B*87V57X46T7b5aY=CV%5."8!WT`NV@=[\{B#&$?QLI6FXh20TZm,Y:1-:Ly A*0>s Y32654#"!3264>3233632!".54;#3254&#"#5##".543!654&#"#"&) 6' ai!0:,d7!5B/UR0< /"71{%k~WNmgH$.@14=3%&>/?QLB%;!h20TRK7 *777"- Zm,Y:1-:Ly 9(3EH[ IR_h4&#"26467&54632#4&#"632654.##".54632#"'#"&&#">2654.'%327&'-#+*B*#A]C08N9'O43F5%8 wQn"jsNlht9M`0=w<[AuBJ dvL7-RvV?~i73%#'*5KDvkЩ{fV66?I3/>5C /(GUt??AIa>[N&LX?#x2B+T&)-"LA[L 2"&546!2"&546###!#(P(d=<uX< )34>32+"3!!"546;2654&#"2#"&7"32654 +EF$VxC=Ns66ls;,.]TE6)$*5+-:g/90F#nSK417T(.G,CN6R26[ / 0'PZc4>32>;####54&#"632#"&%4>32+"3!!"546;2654&#"2#"&2654#"3254#"-A55O?#7%Y7H7&Q/@B2.E+FF$UxNt66/s;,2YTE6)$*5+-:b.!3;2@]+0( 27)e3@A+=-1=]k0F#pQIg7T(.F-CN6R26 00/8%%#"&5463!###"6324&#"326m"3+[tj*(ŁPq;3:5M6#11#BB.4-#K4L d:;k5+%xuj]E82M%:%%(  ]72654&#"2654&#"4632#"&547;#"#"54632>7"&546323254&#"632#"#-* *({# 7ѠVF7F 1G3BB$%H3H,-<+"6*,8UB*%Tx 0(7@H1;###(+ \Pq;3:5M6#11#(84+8;B d:;k5+%xuj]E82M '0%4>32>;####4&#"632#"&73254#"%4632#"&547;#"#"&54632#&#"32767"&546323254&#"632#"7"2654&-A55O?#7%Y7H7&Q/@B2.E=!3;2ѠVF7F 1G3BB$%H3JU:/)=<\C!1,8UB*%Tx 0(7@H1 0*>*('@]+0( 27se 3@A+=-1=]/8Pq;3:5M6#11#BB.4-#K4L d:;k5+%xuj]E82M%:%%(y /35"32="&546;5!#32#"'532654+a2wwS|2$n5JZ; $#*8`Vb+,GsBgO)D6Y9:O=)$[[DM %4&#"###!#63233!53>5&,76",;\765(,"."\)R6Q8[*7 Fs "&546;5!#3#75#"32#3S|1%b,a2wgO+B67[DMB&,Gw  ^32654&"4&"326"&54632632#!"32?632#"&'47#"&5463!2654&"#54&#"632T# ,(<*.F*0$ *PAI`33oWrngA]S8%PPY:MK4/?L;Kv_REYUzN7`B*H :KS&#&#Q#,(D*(`SI_|bb`n~6d?3`4L82BE/"1aGMRkJEq]ADf& R;8G C%32654&#"'463!#632#"$54?232654&#"##"632#"&6(*9;'@hl(8MWN &CP}GD).&=Ot2ZDSU<[t'0/( A;0h=!oUamS^5},O1kKAOB?]3!!6AS7"<HR\74632>3263!###"#"&547&#"#"&547&#"632#"&7"3254&32654''32654ď]L](tAGD:d*0BVPFL^!>9+_:%5,'9`a`5+%<|+))6RROrnJRROroINTJXBDMp-,;`&;17Y_1yAOOKo8Xb.zg*.80#&546;2654&+#"&546;5!#32+"35##"3254>Iev><+bKS|1%+Oat6.[8w.D:\XBP[DMgO+B6oRsϋ&1Bs&!#632+"#'546;2654&#"###'!DMvca6@FQW91- 776wQg}X. ?7\R;T'R*4@74632>3!###"#"&547&#"632#"&32654254&#"ƒYKS';<`6*dSCAT^5>d*0BVPFL^`5+%9~*6RROroINTJXBDMp Ko8Xb.z`&;-,;'3!3!67S2#!33464&#"3263X7HE6O`J5))/8Xc.UnS.>AY'5T332653!3!5#"56,H457`I&UF(@S7"K 1 !#57## ǐ66XBR8/%+"#'546;2654#"#4&#"#4632>328t4,7AFbq#774'-6F9(A@#b_ř"- "- ?8u>$L(:B R>T), 41=%4&#"632#"&5463232654#52#".=7'"32654&mOD~3JCX]BMfkg40KWp]/E#.=8*,97DywE9WC@Qz[rS7>jQ6f 45 7:-(10)(? @&274632>3!###"#4&#"632#"&%4&#"326 r3]H57C(17f8Su5LDQY>W^;'$;:*(5q:'*76RJ/7ZrP8TE=Ny)>>#'2. <%4&#"3262+;#"#"&546;#"32>3&5!3346P`J5))/3X7HE*8@+Q01:E,sm N4+776OY'5TY8Xc.Un01$C$=707 =&-&NS.>A0".74632!3!5>54&#"632#"&7"32654&udOQN8?9FvQCu -'BTUAO[(14+)37zFJb;1cDQqV;]CAPm5),73),9 1>W%32654+%"32654&%"37674&463232+#7!5>54&#"632#"&5463237"&&bst(14+)37$6O.)3Q@9P #k9F=4a=a9FvQCu -'BTUAO[{dOQN@>]5&C<5),73),9K(!(^ %n:jA3PB76C1cDQqV;]CAPlU{FJb;K >J74632>3232654'52#"54654&#"#4&#"632#"&732654&#" kBu M,IPM;PVlf\ 62087hGD~3JCX]BMfT8*,97%.=r,$".ZG*$H7cn%E6BB1/;DwE9WC@Qz(10)(?:MY%#!"#'5463!2654#"#4&#"!5>54&#"632#"&5463234632>32"32654&u107AF6cv"775'+w9FvQCu -'BTUAO[udOQNF9U$>#b`(14+)37Ś#*%- ?8u?#L(:@"1cDQqV;]CAPmTzFJb;>TH,L5),73),9  !#### 766RRL4&#"#&54632VSB`%C]mq7_rQ<4432#".54>32#4&"32654&/j0T@DIT>Uv3=`y7fu*7-)"9-Ôth;JVH=Nnhmq{ ]m!?-*1>"*= a /;"32654&4632#"&547"3&54632#"'&%"32654&$;8,+8C(ϨYXG>U9UPOVz8\A;cbnl)>:*.=C<#)11)$;ƨ`MENX>E/le^e8DAX^8Weuu3C*)58,&A/:D2'>54'&#"#"&547&#"632#"&546326"3254&32654'l`7*8CBW'$dSDAS`7?c (1AVPFL^Ə_GIf+56*a;4+(:b_y}H#o=V9;RPqoIRSKYADMoR},)6+*6`':18X`0sGQ  (4!####">32#".54>32#4&"32654&*76j0T@DIT>Uv3=`y7fu*7-)"9-6RRÔth;JVH=Nnhmq{ ]m!?-*1>"*=  /;E"32654&4632#"&547"3&54632#"'&%"32654&%!####$;8,+8C(ϨYXG>U9UPOVz8\A;cbnl)>:*.=CH76<#)11)$;ƨ`MENX>E/le^e8DAX^8Weuu3C*)58,&A6RR ` &2Q]%4632>3!###"#4&#"632#"&%4&#"326">32#".54>32#4&"32654&*r3]H57C(17f8Su5LDQY>W^;'$;:*(5j0T@DIT>Uv3=`y7fu*7-)"9-q:'*76RJ/7ZrP8TE=Ny)>>#'2.qÔth;JVH=Nnhmq{ ]m!?-*1>"*=v #"54632//1 @&274632>3!###"#4&#"632#"&%4&#"326 r3]H57C(17f8Su5LDQY>W^;'$;:*(5q:'*76RJ/7ZrP8TE=Ny)>>#'2. "%5#"32#3"&546;5!#!53!Va2wwS|1%32#"&/54>32327654&#"### C"QY{q!% ):ƠydoC6C)756$`]V٩ 8iG6 ,K DMsBnKR;&*74>;5!#!##5##5##"&732=#"%#3$%<54.5473vucPPH-?!0::0P 19?2!x{b0310IZ%BG+4N.%#(C, %+!07Z<0%%#"&5467635#"&=3;4&#"3260ke=54X P KbPWHJk\KJchoqc:`N%nQGPJGJQM<F'3%#"&54>7654.#"#>24&#"326Fmi/(6!-%1,PdN'++P\KQreHPmj|~g#;*$ &/',T>blfM@,I0Ka^OH^X<-9#"'.#"#.#"53.546326324&#"326pXP8$$ ()&$$v(V47LfWY7=jWlP>:=E@<@>Ys/XNH+"#[\2"  P0LYNN~T=YZ>=UR<"%#"&547332654&#"#&54632o P QLJEG;DEPy^Zyo ))!Nf]LJ:?M@]e^W<&%#"&54733254ᒐ&#"#4632*+fPPH4D<7#"&546326324&#"264&"326dg{ -@B]oLGg 0CbX{r9)*=:Z5>PTSADPd~zg-<M'YAJ[YF#Fl)2:*-6>AGRBAX`P-*%#654#"#"&=46326324&"26-1P1\43<322VQW$G01F$1&P$3:dABd9-TR11RT-[YVV>n]77]n<%%#"&54733254.#53254'5++gPQG0# \ADMP0!RNOpeI88CHW#32<qr< aFc,\<oA#654&#"#54#"#54#"#&'&'&!"53&54>32632632o/Q0/0,2AXW@BURPQsa^w>AYeLmrZ>TZA@V\P(%#"&54632327#"&#"6324&#"326cgPgT;@ -va?e\PXA?VSD@WasyfCeIP[>z]AZS?EZVP"%#654#"#54&#"#&54326321P1\34P43\1P1X3/d^yb5I55Iby^(LL<.%#"&547332654.#52654.5473{\ePSCAH//(`:8QQ8P(=F=(S+(Zdkb#BMMA(1<0*"'B, '3?/KT.%#"&547332765#"&547332654'5673sij P RK5--8b7;J(B_PV"6/+Qi|k0)!Lh#$b9)8A? M! 0 < H<9%#654&#"#4654'&#"#&5467>7673632632ASD480"%P.29+$ZE{z PNi` -A*J=VWlk_Uo;U"#L  ' F!K47t*ml64#X(,#K2/+$OnP%#"5332654.'3bPDUP`pVD$8+);<2>J#".547332654&'#"&5467.546322'4&#"3264&#"326&@X`51^W@'OPOpuIB4e=Bc9,)3]EBV!&.1'%15+75'%/+<%#"&547332654&#"#54#"#"&547332>32632')o P QLIC -"*P 34;P*%( +Z>LZNQo ))!Nf^K]15."> A47 $>N><7C#".547332654.+532654&#"#"&46326324&#"326A8opDvWT hSXi-$<9/>>0&> zU[~ZeC?3N`I<5#654&#"#4654'&#"#.#"5&546326325I.79/! P%$'9=LCBz|R PLni4sAqh\b6;dQh,c_aO54&53 iPTLJEA^Nd#P#I2')Pxh*01N`]L:_MD@AH1:I4 6 ="-%#"&5463254#"#4&#"56326324&#"26=zgibDX?!P1#A6IO&-]ROPbGJ^a`zldfn=pI^`G/"Z3<OXPo"-.#"#4&#"632#"&546326324&#"26oB#.P!?XDbig{OR]-(MI6~^JGb`aW!0G``Gp=nfdlz2Tb<<3HPOFGPN<w#.'#"&547332=#"&54632537'5&#"32wpn P PMOM;YU;E[PpppBD%5/!I/xm+*)!Pd9@9:E=\GHhO4!"P%#"&537324&#"32^h|P^^~PQ?BVT@?\thwv~XQA@Z<<I%#"/#"&'332654&#"57'&#"&546327654+53232654'7/eCW5a 8t$'~ #L'78DMQ9MTVH+7>-.? 5{G.!E$?>AM_a+Fl&(c*) 4<7#"&547332654춮춮.'&53$!?)EJ(YPU>9S4]a66ad0;JX4PVV/9#"&54>7>4646<54&#"#&546324'326c]+922&'&)C \BF<I2FgBD[HaoiZ%B-&  $48')*AK^k%.98Gm5(AL[0=%#"&547.#"#.#"53&54>326324&#"326lZMg1 -&BR_XVS C^[-YYk#T?ZkcM7,-":6C[00%'7-C!FO:dGEEtXAY`=QR"#"&54733254&#"#&54632dbQl,F,H74>-9FdFKeKcjaP>=5E7I/@A:-4!&.FTUJ2#"&547332>54򂂮&#"#&54632RoFF9*:> '.8.5@FiQFg5.>(0cQ@8==9G&&@0&<(2C)-6;5PUND1J ]V$0%#"&547#"&54632632$4&#"324&#"326Vg^QpJ,A>QeIG^:2QLP9/.13*/ 9@B?D>?9`ziQf(T>HZUF=.{^=D0*9D]lF?Q^'%#>54&#""&546326324&#"326SF&-:7@(nrv_Q=8SQcC=GCCDE>s])t89P429\wu\_t==nS=RXIFYg%#654&"#&54632;F;@xA;F;mVWkpSNv?[\>tPUnWvv+#"&547332>54&+53254Ȕ!*, $L:Us F D<, '((A&C*BC2CUF9NI%cT@55@32632632-.F.,(SF'+(-F"&a(^CRTqN:L!%V#>%TATMUKX+Dbhg4-5)le)<+ :Q8bIII) I`'%#"&5463254#"#&546324'&#"32\]Zrm\K5urFiSP]F(&-@>A@x_ruZ\y? R_]PX!!`CB[*%#"'&54632327#"&#"6324&#"326k^F:=&%-A@^u01}OIZCn:w[B^V@d##](%#654&#"#4654.#"#&54632632I2K2:&%.3) $>0[%+.#"&5473325#"&547332654'53273WgSr,F,E9CX4V=;QE($!&,aK<>jp_R@;5E9G) 4;>B9$)("/H@1%#654&#"#54&#"#&54>53632632IXW>Q)%W;SR9B.&53R=@%97)+7:Q<#".547332654#"#54&#"#"&547332>32632Qqm;mL@cEQWK$$<#:%1=&F&#*(!T89#n+[=:/)@CNiSJR4&--+4+;0-$,%$))#@J9.:#"&54733254#52654&#""&54632632%4&#"326xfhA VIz9A//[kjmPf53aDMm76>65C?=;5=5E8H0$5u:N=6%#654&#"#4&'&#"#.#"53.546326324F446.$ <'<6jT%u#VGj]2{[9AfZ(C:dI[=QDF8O"3aq9 eAf 'b1C ;,H[{+$Ok$#"&5473326=#"&5473326753a`So,F,H7D>>BJ\3F39/%C=5E8HWG2ZJPHEO0F% $/%#"&5463254&#"#54&#"56326324&#"32dX\mjZQ4$", 1 1?),C='P>9F;@??@A?Z}v][v:"-5-<22:=F)??T@?]^BC]*%#654&#"#4632#6324&#">KFKA9-S |Gi6#E95/Z6%#"&5467332654&+532654&'&'.5473Z9.05lE9FAAx~Qk=;?B;<(*!F'77'-< P4jr£XJ[eSOC,y??@;9?B?u=7.2518,#:v[]v{\yAS??)B^]]\%/%'#"&54733265#"&5463237'&#"26wYmQl,F,D9H<3Z=TU@J?=5E9GSKKP=?NP|~TonoY6#&78%#"&537324&#"2mZRl@{7ZunRdw`F`M#"/"'5326549'.#"&546327>54+53232654'7H/@&c+8VHM0*5.$P )11J9M#T5}$=,] 33<[.:OqB*@F#=.+)#+I$6779LI< 5!':$H;%#"&547332654&+53254+53264.5473pVdtFCM6L $SREDST 6LL6AYI:'$*/&(-RKfcNM.4;'<5=<. 1( &/(+ 1$&/ <X=E%#"'&'5767632327654'&'53254'&'532654/54#"6XUC_NC12+V-J3@3 P4E7E6/XmT/;8H)1X+"l[6lSy-W@; 6 :+ 6)7@E5D8#>?*z3^EKDl&'567&5462'4&"6lWPQV1?iXXi=3A6`6ff 56jHZZHj63993Z..x!#"&5467327654#"'7#5!632 (rN_$ 'D5NY$!* 4r/=YL'6)/4A1"W4:#"&54732654&+532654.54632'654#"hXL^*!A1>FT<--%X5LL5NF"#A1g+ W(3##"&5476;5#"5476323#3'54#"35#"32OUFN^2/S\\0/MHSOOOdr$FNNE$=5dxGUXMO,(HM(&MEn4H|kaf>u@5;<D 9F#z$*#zDOo\#y%6 #yEO\o#y%6E #yEO~o#qN%6g #qE0*#v*#vY_#y'"y^GY\#y'E#yWGY~#q'g#qYGY1"+32654#"'7#!2+632#"'32654&+ ! . '= &)B9/N"lppl%U1# +2")5#5632#"'732654#"'7.54632"32654&JA+ &)B9/N ! . 'gt{fl5DTTEBPP'E,*1# +2"(%V~}Qp[\on[^oY.#f'#fZGZe#q"(C(^"C^zZe#q"(v(Z#vvZ.e#f((#frHZ\e#|((E#|tHZ1e#x("xrZC_#y)\#y,I,g#q*&"qrJS]#y+F#yKKS\#y+F\#yoKSZ#j+F]#jK1&32654#"'7#3!3#!#632#"' ! . ']w]] &)B9/N%U;'L1# +2"1.4&#"#632#"'732654#"'7#3>32#C)@N &)B9/N ! . 'S%K8LYSk70aP1# +2"(%U0'MBtS=#x+F=#xpK\0#|,\ #|LN#j",v!;#vO#v.:#vVNO\#y.:\#yqNO~#q.:~#qsNP\#y/:\#yOP\g#q:\[#q;P~#q/~#qOP.#f/.#fOK#v(0F#v%PK_#y0F#y PK\#y0F\#yPL\#y1F"y{QL\#y1F\#ypQL~#q1F~#qrQL.#f1F.#fsQ&#|"2j'$#j&#|"2j$W#jt&#q"2C$Y"Chu&#q"2v$Y#vu[i#v36& #vS[i_#y36& #yS]_#y5EA"yU]\#y5;\A#yU]\g#q^;\P"i"_]~#q5~A#qU0mm#y6""yQV0Em#y6"E#yLV0m#v"6y2"^#yM0m#g"6y&"T#yP#0Emx#yf"E"yQgQ\#y7@"ytW\Q#y7E#yW~Q#q7gC#qW.Q#f7I#fWUF#j8AF #jnXUE#|8AE #|hXU#f8A #fcXU#|"8vA\#vx+U#q|"8jA>"jos-n#|9 "|VY\#y9 \ #yHY#C@:#CZ#v9:#vZZ#j8:#jZm#y4:#yZ\#y6:\ #yZ\#y;"yP[[#j;"jT[ \#y<&"yR\G#f="fT]\G#y=\ #yM]~G#q=~ #qO]F~#qqK-"jbW#zZ&"zR\*#^)D#yA\#y$*E#yD##$*#D#f"$vm*c#v6##f"$C "C)#f"$*1##f"$| *c#|r\#f*E"fp#x"$v*|#v#x"$C *#Ce#x"$@%*##x"$|:*q#|r\|#x*E"xnZ\e#y((E#yoHZe#+((#HZe#|(("|vHZ#f"(v~(h#v;#e#f"(C"CZ}#f"((:#Ze#f"(| (a#||Z\e#f(E"ftS#o,N#j_\#y,9\#yL&E#y2$E#yjR&#c2$#R&#f"2v$v#vI&#f"2C"C&#f"22$/#&#f"2|"$`#|p&E#f$E"fl&#jb$5"j}c&#kb$5"kVc&#gb$5#c&t#|b$5"|~c&E#yb$E5w#yjcUE#y8AE #yAXU#88A#XU#jqAo#jrU#kqAo"kmrU#'qAo# rUq#|qAo#|rUEX#yqAEo#yAr #C<&"C0\ \#y<& #y\ #2<&#\ u#|<&"|W\0G"w>0G"s>0G">>0G"F>0G"K>0G">>0Gk"q>0Gk"q>""&K"įV""ŜT"W"'"VB'"`B'",B'"4B'"8B'",B##?#x#.N"#<"ĭ#-"#9"Š#J"r DJ"tDJ"=DJ"ODJ"|DJ"[DJk"fDJk"xD#}%2"%8c"%e"Ĵ%a"%d"ţ%T"%U"%,"F8"F&"F&"ijF!"F("ŨFk"Fk"ƾF#}',#'+"'"ĭ'"'"Ţ'6V"'t2Z"'p$"gL$"rL$":L$"BL$"GL$":L#{-#~-"-"į-S"-m]"Ţ-w2"`R2"hR2"1R2":R2"@R2"4R2j"bR2k"bR#x2P"ī2"ţ20YW"2$#V$#V$#V$#V$#V$#V$j#V$j#V#6#{6"6"ī6>"6ee"Ş6TT"6{TV"6{0G#>0G"P>'#B'"=BJ#DJ"bD"F8"F$#L$"VL2#R2"XR$#V$#V09G"j09G"j09G"j09G"j09G"j09G"j09Gk"j09Gk"j/#|-#z##F#M#4T#,W#y 8"8"8"8"8"8"8k"9k"##A# F#!<#"?##T#/$U#2%$9#N$9#O$9#P$9#Q$9#R$9#S$9j#T$9j#U#V#W[#XY#Y#SZ&#s[T#d\V#g]0G"xn>0G"qo>09G"j^09G"j>09G"j_0G"|r>09G"j#xg#q"ֽ"1#~tA72>73#"=3} !1JL %*Oc2( tAB lG| yG#j8"b:"D9"cJ"|mD8""#"#mV"%0"%S\#%RA#BAh"E 9Gj' "xF"qF*"F6"F"|F*l&F*#x' g#i'-"'k$"'bDAs#QA"] 9Gl'2"x^R2"q^R2">R2">R28"~N28#N2"|aR2k"R\ #x2 g#q2+"ֺ25#w2#}/I"li  #'#5!#5A:aSES ISSSS"li  PC $9#j$9#V$9#k$#|V$9#""֟-<""-<"֧6F"6$4#6RP#vvAJ.8#58HH.81818!518HH8!58HH8K 4kA #54673]0-00e;D&N@ 356=#@]]00e{&M@h 7356505#A]^10he{&I @ #&=00]hM&{e0+ #54673#54673]0-00]0-00e;D&Nhe;D&N1. 356=#7356=#1]]00]]00e{&Mhe{&M/,h 7356=#7356=#/]]00]]00he{&Mhe{&M1. #&=3#&=00]00]hM&{ehM&{e&O ###5353XXRUR&O#3##5#53#5353XXRRR3R2, 2#"&4{hIJ34IJ52IIhF,0F|}Whs'h'hsth''hWKy  $.9C2#"&46"2654&%3#2#"&46"2654&%2#"&46"2654&?WX=>XX=%56J55(BuB?WX=>XX=%55J65B?WX=>XX=%55J65X@=XY|X<5%&55%&5F +X@=XY|X<5J65%&5XX=%55J65B?WX=>XX=%55J65?WX=>XX=%56J55(BuB?WX=>XX=%55J65X@=XY|X<5J65%&5X "05>32&KY9UV9YY@9229@Y#73#'#D>>{y> 6' I' J 5[A!5[ANP3#><>'y,!7;#"&=47.=46;#"%.49R.$94.%",,"V'AE>x&FE>EA'F; E <,!74&'5>=4&+532+53265m",,"%.49$.R94.%VE< E ;F'AE>EF&x>EA'M)#","M',"|&". #>7!5QJbFaJgcT{W0O ####532 mb@R@9^^LJJ@J 7".54>3!#33B`--`B 99RJDi>=hF@@V J %!53#5!2#3+99 B`--`RRJ@V@Ci?>iD(W" I?m 3##"=?hhi<#i hh4&&x;X %270#"&'5"5>32&KY9VU9YY9UV9YgY@9229@YY@9229@Y(W&  I<>' ''yy%3#"&/&#"#>32326+H>/-##5+O854$0 #91  .14 ;4X "05>32&KY9UV9YsY@9229@Y$a'&546276327632#"/#"/#"&54?&'#"&54?&'#"&463267'&546326q "." m! * $$ * "m "# m  * $$* m $$* !m "." m"* $$* "m "." n  <D'y'yy 1G& ' '  e<&yO'yO'yyg<'y'y'yw'yzyT<'y y<9 'yV'y&yy<)  'y& 3'yH'yaHya< 'y'yy<&y'y'yyD4 #""3265*LIZ/-inU[#7#5667nUEEQ #533##=м.CC>; 5dF#632#".'332654&#"#7.&.BRXE%98I/56.1#4"4wO?CU, G4./7(G$4>32#&#"632#"7"32654&!.)5D8?14&C=OTA*45'(308W1 ;4@SN/L;>Q1()64(+1G #>7#5G$=QR,hm4H*#"&547.54632&"2654"32654NVCDUN#M<=LJH++H+$V44*,4"H:IJ:G"'1@?2&$%%.%&..&%F$#"&'33265#"&54632"32654&F!.)5E9?14)?>NTA'30(*558V1 ;4@SN/K<>Q.5(+23')7,Mh ##5#5353L.... S!5S.. lS & Q Q)o! 3#.546$YY$199 @MLN! SK'8x3>32#54&#"#(24&1;6& *27n9# 3*$?4+D HE8{8Ft*Bu8Q J*F K*G L8G M*H N*F OM PV T7 QV To Reol SKLl SI(5T)47632327#"&'#"&54>7>=4&#"26="?A7=4< ' ($&'6-9A0'#4o0-)27. 0= 39K#3273#"&46323454&#"J5C7 M;GSUG0I 6()5 .-E8?ae0*$+:99L 72#"&46"32654&JQRHIQRH-44--43aYSaaa2H=>GGxr>RR={{9J;&'&#"#>32#"&'&7#3265C7 M;GSUG0I 6()5.-E8?ae0*$+:9&A $*0#7&'#7.54>?373#&'>73'&''O 7+#7<8&9];7,# 7BTk=C Uv .t"OiKU@C[|6Y/_fO8((<\;~- e_L;I~ 9)4>32#&#"3>32673#". (>fA!RZi?8M$C( TTS q@f<(d2bjO4܊fO_;3UJgg3Ni`,3###535#53!!3SOOOOB77B7R793#3#632327#"&#"'67#53&'#53.54632#&#"&}Y;?!h(+*9J+#580e|nQ"e5>,X@M ,> 7(62Z'B8.%BW>6!!791![{ 1W9FFr&*3>32>?3#4&'#5#54&#"#7FM"K4a,#D0<7>58T'#T73.3HTY'1 J/*P*$W[ I;wi+5*27P9K GG#&)##'##5#535#533333##5##3'##3'3]N----ZO33O32fO::@@7FF]FF]]]]lUu[N3264&+#327&'332654&/.54632#.#"#"'#"&5#+#!2353CKKCVX5E7C$-4Y?YYX&0'1S6PDucz2> 14CiK]-jt8LZ>3 +(DOjc]x'V525#'+.14!#'##'#53'#53333333#3#'7#!7#;'##%#3'W97W5iZF78X3KL>;HG6CF1_nn\_2NNKW.eRSdcRUd?>,'wqI?))J??$k0".'#73&547#73>32.#"!!!#3267>g@/S7O?/@l@c^3K,DsExD)E9G-AQ=;;9QA/CI&d_;;\k"Y+##533333###]77]F%xngH**HgQ#5575575#5!#7]<F]_F_f_F_RR]F]f"8' 3654&#"2654''#>7.54632#b5( (Ghq;'jC%ZK:P[DG}R\F(%3."tssPu@. (! K[J8ecaM]'!&-327!%#3#+##535#535!2)&+!654'Q%42<fF]4444-Ql]3%LM10 0=F0D0}@=+[DZ "+3#.'>=#53#'#5.54>7_Blu2:xGPOxk 52J4 @;V@7T2 +<753#.'>73'b;(6N0;lh_8CL=`oy;YP~hd"=H;;*64`sG9DanT> .!67 Z^O S$Mpb .!&'7! O_Y S bpM6m37NY3<"y !&'67 OAbbAO $ bM^^Mb "y )'y9'y9yB9F#jj2: 2#"544&#"3261?!!!#.Q4B VWzW2:4&#"62#"54 32ȱxeDeȯ(gwxv*!!.'7'>7!.'>7*$!#=##=#!$* *$!#>##>#!$,+.*#??#*-,,,,-*#??#*.+C #dL*m 4PZH~#!5HHP~H)_'y|'y|yB|0!#5!#GHGgpB" !&'6702y#dd#y2781#ww#1JO###J>GO>GONN   ~G I@0G {637''7'7)E F-!()!-F EI':==:'s:FJt"#>32327#"&=0#"&546767>?654&3267%3#2#654&#"3273#".546i(C dS?M3    6=#5l.11sN%"5Z::g>MC,&&=!1)Y%C!&8 +> CE1)4   60#=  =7%'E0  "('6C0/7e'"21s:FJz"#>32327#"&=0#"&546767>?654&3267%3#4632#654&#""&547332>54/.i(C dS?M3    6=#5l.11sN%"5Z::gS:IC&$-9D=4-lYFOG-*!1 8;>0 CE1)4   60#=  =7%''AZ4* ,  *%GU70  ("r&,3%,4>753#.'>733!5.4%G1JC> K K CI1G%/45n$GI3PP QC& Ob AC3GGO XFCWT)->P2#654&#"3273#".5463#2#"&5467>2>54&#"H>MC,&&=!1)Y%C!&8 +> ::\ET#CsDT$_'>"1-&="1E0   "('6C0/7e'"21%'SB5u&\SB3v&-0|&6B017'5B008Y-E3#2#654&#"3273#".546#7#"&5473326?3::>MC,&&=!1)Y%C!&8 +>y= #I/3;EC?' 3O 3C'E0   "('6C0/7e'"21.".) C "NA0mR(o"r),!t}326?623>3232676;2#"&54?#"#"&54632#"'32767#.5476?#"&54654#"+"54632%"67654#!'\&jaG14tIc9#U( <^$:9L@<.yAP9K0#!$ ?4d+H\  VJ3.)%4$ i'!&j3bP0@73%Տ@6FI?fE3K8 FD60TkY[)& @">YkyW963 kUpD+-Oi27&#"7>32&'654&#"327#"&54>73254&#"#"'63232654&54>7>58A � *-0Me4(,6 # +#$! &#.4   Q`K N (2$ %D0R  +' W,!  -"p)EG,,6 0#"0(9(  [n?A1M+ S+$!7)!83>32#654&#"#T9/T8?HPTI/&Dd=T,$C;W &/`RVN>32#654&#"#?3U/T8?HPTI/&Dd=TzVWT H,$C;W &/`R?HR3:M>74632"'326?&547>7632>54'4;2#"&>K(DO4_'M*3&B';"#W?Z4dAJ#+?GaZ4% { !?.l#)# !J8  GQY4>32#"327>3232676;2#".'+"54>;26?#"&"327&"67654%Y:!O`O7\gZ#,-ΰd=dnG1x" M|3'O/'fR A+EWP:V c}"0^:H@&U/5sK H16Lrm:)$]U=B/KD2& :Rz@-m%xC.3%0327#"&54>7'7>324&#">-S7**F?[8/5\ o%(?%$/=#+#+H%X$LI*\&v=W54&#"#"&547&536324'326*L2m2!'8 1B ?,0(,0/+(((2sJU9!.ZR3.<'BD%IX$-M/$7\30CG.iWDj&@z>X8YG!w 2>54'%32676;2#"&54?65##"&54632#"'326?46367&#"3267>=4>32#"&547>3272"#"&#"6dDnnp*"0>+ "K]/ " OQ4""J>U;P/#!# >3;=[<{j@7BF%?&  m1&:8=vJ2 $ m y@'U%E/(+0i(GD2#qHnX'$<" wU f,wZO4 615 K:D()*H)/?NV/  !H23273#".5>54&#.54632#"&5332>54&#"[),#  P42(9#!&%O[aMLW*L9-YU$&&/ NF)M+.EP70 16'Ev`4' )?PuYGD, ?FrbK.J!> U"(5264&/&54632#4&#"#".53##33#4-(lQSHLTC1."0&mX#G185 CFFyC^[Cb%6 S;EG@#'! X+* ?,'2>UjKU1f7!33@g($.G/M$.?$##5!##33#Dw6FyC^[C~77UjKU46 6tj"#".54>32#"&'"654&#"3"&54>3232732>54.#"#4>7.'6732654.9D0  1Z9FQ7S[06%*3& !8 .'++' 6!!  +J.S="1)! ;QJg %@j!E-! (2D8(.\=Bl 4Q-*A%!* .!<&%/ S7 / 4D7'n6  2[G %-%F O."mx74632"'3267>7&#"32>=472#"&547>327632#"&54632#"32>54'#"'#"&>54'632"327&"/"!$( B6;=IRL?yo96K:uA `5,7<" yVgm8'xQL5Any m5#7.54632677'"&464'327632"#".##"&">54&5467&)!!/2 LTs^V*!es!  &L'D1>A   V %P?$85BQJH CT!$N% 5 1aE 60!v$'* -$+-?#+A  Y   Aqk^1  +.?Q0(H&%#"&54>32326'>54&#"f ,n5;IZF%+p 3+ <6Q+&`m 39L=I^$ Af()1 &$*cC-Zc74>7&54>2#"&54632#"32654#"632#"'32>54#"#"5>32#"&4#"326-9WGazRe3&Y* !D-#S|+X<%3$?%3R.N=1Y6 a'N _1##>zJ216o+X P ]+J>TP JND3/.@:9^6 =n+/ "2OF< )S}74632#"'32>7+"5467>763267>32#"'"547#"&4>3232#"&#"32654&5463#"&>5&#")0#!$ +bV:@z R *&,3?&  $'#C!t 7(/gF`~TB? C@GW5A\{ p#4-;@"@;_K&%!=J;!.&A5,2 537!.n(>aM'M4MU!5{>"Gz#E !))ln7;>7676332>7>?6332676;2#"&547>7#"=>7#"&5463632#"'B: *Y:s IV<k "*1 v-/G-o  Gl'+-?IvF-47B@Ca"Z[4#"+g / @D##3#/2#654#"#"&547332654&/.546763#`J;H<=sc&9 5[3C#g&#*M1DM403>J $g$1[::K  ! B/&  2 !.&=6 )&4&  ,93 '(G)GE[#u# 'm{[#u"t 'mE_# K _# K"t 'm_# K"u 'm_# K" J 'mE`# L `# L" K 'mEa# N a# N"u 'ma# N" K 'ma" M# N 'mE"{ 'md,d#,,d#,,#,,d#9,9]#,9s#,#,9#,#,#,9d#;,;]#,;s#,#,;P/0&Y'K0BLBt#LLBR#L#LLB#YL  Y #LY h#L#LY F#L#L#LYB#[L [#L[h#L#L[DOFGFP  !&'672y#dd#y281#ww#1-; !#'67&'P81#ww#12y#dd#y21 75!&'7'6712y#dd#y281#ww#1-;x 367&'781#ww#1x2y#dd#y2!!.'7'>7!.'>7A82%6/^66^/6%28AA82%6/^66^/6%28DCF @6a00a6@ FCDDCF @6a00a6@ FC!%'>7.'>7.'7DCF @6a00a6@ FCDDCF @6a00a6@ FC]A82%6/^66^/6%28ABA82%6/^66^/6%28%0#654'327#0#(t1 X"qS@(Sq"X 1 &@ "+ &@003!&'7'67!#082y#dd#y281#ww#12#!&'67!254&# NTVI2y#dd#y2}i36G;DD1#ww#1Q#+-"3!&'7'67!"&54663i}2y#dd#y2IVT3+#Q1#ww#1DD;GZu3!'7!#7[nuTpoT !67r#y28v#1 %!&'!2y#1#v8b\ \@#_G 8@  - 8\  Hr@ D!  r@ X& V !!!.'>7QQHM)B\[[\B)M~5PP5b!QaVVaQ!b'7'>7'#'#H!QaVVaQ!b5PP5m)B\[[\B)MHQQ-'7!5!7'!5!'7)MHQQM)B\[[\!b5PP5b!QaVVa,<z.'7373!QaVVaQ!b5PP5)B\[[\B)MQQH!'7'7!.'>7!7'!8L(PXRRXP(LL(PXRRXP(xTToT}_"`^KK^`"__"`^KK^`"VVV'7!'>7'7.'77'7H!QaVVaQ!bb!QaVVaQ!bPPPP)B\[[\B)M(M)B\[[\B)M0QQQQ /6%#"#".'#".#"#52>32>3232>;&'7'6`w:<=92), (#7A>9;<9D= (#4g2y#dd#ygNc bP>I>"854Oc bPcc#1#ww# 73#3#KhxcMx'N+%2#"&54632.#"#>"32654&#@G3!ez_i:QL.> X k]@JP=>RR3R^hc~O83WbSGCW[DBSZT !5!5!5!!5!cdLRR'R# 32654 &#"3#"'#7&54632JVtWqnFi\טfMJrYטh*FJuIqjg[NsfY 33 ! &d)'h !#7! d3Y'q7h!".546;#!!;~Es\3m-IP4!FyRFF;Q)F7!33#3#;#"'#7&54677#?t7}B76Y^E7%u.p }AGFFF[tM b-74>;#"!!;#"'&-&5B1*6. l -9%qJ$7W1 6 B/62C 6j27g Ig7&3#7#537#537&'#5323&>7t7%2ZqD7[q><2DP?4 >/H@'tNQxF"<32&#"32>7#".'#"&54>32.#"32>d+)H!+.#  ! i)   &6$M,#g7"9|1Jd2>32#"&#.'#"&54>32>7.#"2654&#"  !"!'<N9 5*+(*9N;3-1    )11-1)       !0*5Y& Y5*0 = ! ^dH! !0Ee3VSZsS BLd " : :d V" :# : :_  6%654&/7#"'732=.546754632&#";P+%U$- 'N8%<>! ^(: BEdH! !0>H W&<  >/&/ zJYpEe3V ,6+9?A7.2")Q8?J,23 ' A8?::F/;1+#,!%! ?@3 7#7'&#"#>3273326=3#"/E/571*q@590'*NFK!%:E*$"t EJ(m & ' ?E( &7#7#537'&#"#>3273326=3#"'!!E;tT7/571J9@H590#(AiF$NFK,e !%:EvF2$&  ?2,#7#537#5!733#3!"#>32326=3#"/&E!=C3273326=3#"'3!!!E?c5@N/571`)@6 59024= FfF2NFK8Pk!%:E bFfFIp& ? ?nI,:7#7&#"#>327'&#"#>3273326=3#"'326=3#"/E\ /5715I/571]>@L 5905>590'*=,NFK_/NFK7o!%:E `$!%:E'35$"#>7>2#.'.267>73"&'.'3C[Z# Z[Z "Z[" [ZZ # +5  5+  +5  5+ 2o &  u2%!#7#537#53733#6K6*3K3*FFfFwwFf(Y & '& '% '(  %!#7#537#537#5!733#3# .K. (K( FmmFKFKF__FKFK- %!55% v8CC3EOO- %!55-5v8CCxEOO- %5% %5% vvEOOEOO- S ?367>73#.'.#"##7#>76?&'.'3323lKE+" %?&U5 "Z-FKC1# (D&Z8 #Z- +5\5+  +5Z5+ - 75%737'#?-&@K0hJ0K;/E|r,O;ZOhsApA-3#75?'57YK@]OjCCCCBj>-!!#7#5375?'5737TSK.KD>GKR$yElCCCC=OKfOtAT1.".#"'6323275% $YLF%()|!YPG(v.//P/0bEOO.".#"'6323275-5$YLF%()|!YPG(v.//P/0bEOO-752$7&$-|mTqgkxIfEs\OL@DJOZu- \C( [' ': \( \' ': ]-752?367&'#7&?-MK651\;!3R3`vGKRo "EK#)OTP<4OU7)-33"#756?.'567&PK[qeCK+3.Z8#2Q2^u$ $*EMe!'OQS<3OS7]7X!".546;#;~Fr\3'IX9DwRFB\2F7X bIX733#;#"'#7&546;7Xo-0BP? 0F99FbJ44FBFF߄6."3!!#7#537#53&+532654K@G^K0Oc=nyOK/F::F.F(F-tV( !&'67%3 &=33265/!RCffCR!pHHfdK&!fQaaQf!ƃba4,%!!!!,MGPH k|B:' '= kB:' '; lE7 k=-@E7 o|+ 33##5#53$264&"6  i66A66^M+ !5!$264&"6  ~/6^M+ 77''76  264&"''''╫'&&'+  '264&"6  H&`>&`JM+ 2#"&546$264&"6  &$$&%4$%(9MLq"!!#"F"F"D" v!l!#!5!F}&\F%!5!3FFFF\L"!!#xF"F"L$$ !!!!#nnF$FpF8>>-5%>H}>>7->H+~>!>' '\ |>%>' '` }!##3XXM = =%#".5332653M\^MRYaRL``LXS W. 2#"&546&$$&%4$%(@m 373'7@))j(ii(6||L}MM}> ?'%%5H%%ݚok Bk( #367&'!!#7#537ȏ7K53.\:$4S3axV5KYxbnc5 !u!'OSP=4OV7F77F(ELx( $33"!!#7#5375>?.'567&mK`h]UkvK#B=<75I)9V6f~#$EDF77F&.O06C>7OZ8YB:33#!!!!#7#537#!!K3:^']%K%,P'wsشfGHNFJJFNVcB:%!#7#537#53!5!733!3:~$K$+O.}y-K-4.FFHHF\GcHZZ\c' # 32654 &#"#"''7&54632JVtWq$[\טfa%aYטh*FJuIq#[g[b%bfYb!##bFFmHe##5eF'F_|333_FmFIf)533fFF4 4632&#"dH! !1ZsS BLd6;&#"'7325;%<>! ^&Ee3V;XI %"05>32&KY9UV9YY@9229@Y;XF ::#:;((:03#;;(((8T #57>7T_{RJ(R:b7(8z 3#(RR (JT #.'&53T.:R(JR{7b,8X X8X  ,KX! YX8MN#3#0LLX8 #0L X M 03# 5L78,O X8,  7 ,  X4;$#;U$  !"32654&'632#"'#57537BPPBDUS9ihx|hk;K??SBoooY^p~VZC2@2zN3@%53!53G0HHpp#!3##52672#"&546"32654&%54&#"#546322#"&546"32654'.sG/3)(0=T@BW=Z) Wҕ՘bTl+r4 QK.=.-97- ?ST@9P<Ւӗ!l\|c'-#2>M#67>32#"&54?32654&+532327654&#"72#"&546"32654'.66@&=PDQTC32"32654&2#"&546"32654'.3)19 L;LS>%C!M+en)67*&//ҕ՘bTl+r ^Q DSAE\'9nb<(1=.3C<15?EՒӗ!l\|c'-# %!#>7#72#"&546"32654'.8SYDmMҕ՘bTl+rH*yib\Ւӗ!l\|c'-#"-9H"&54767&54632'"32654&"2654&2#"&546"32654'.UUS/HN<;I)Q&..%&1.+)46P41&ҕ՘bTl+rnX72#"&546"32654'.-'=9"4*'>T.*R) ttT88#"3264%2#"&546x|ّԕ컵Ք՗j "32654.'2#"54$㖛f`ck2B?_ 654> 54.''#"547&'#"&5475#"&547&'&/.54>75&'67.5467.'>7'>54&'GSRL#q 08"5"( ;N$V&[&-2F >3'  x))[&V# ;,.'&". U_^T%  T`acMUTH)xnABp`L_ P #-(86C# =**rY-{/r2)T"5 ] ]#  04 T*1r0{,4G=:J-ۢP!LJ "   LMpB@n~YT"O$%3!56767#"&54>7#"I(,C$U,T 4R5K,FJHGKF-K5R4P* 8vPJ5-J=BkCBjB>J.5J#:.'.5463267632">54&#"#".'&\Qm<+U>U22F=W6E1='5?&DJF.84?/ 9KOY/?VK,6"8T;8pW/F4%KFR 7(/hX[c/398 /!*J"0 &'67>7.(8F!JE;;8-0>P<6jj1?0#8!5>7#"&54632&5462>32#" ".F--F/! O&@\S;""']]'  ;S\@n%%9%./$:#,"(_CA\ 82?ZZ?28 \AC_"OC%3.'32654.'3263!56767#"&54>7#"=!28"L2.@,740 ??;%@.K((,C$U,T 4R5K,FJHGKF-K5R?('t?.!7&//O09Z40='.?J4P* 8vPJ5-J=BkCBjB>J.5J#>32.'.54632\Y6=W5QTMQm<+U>w0JFT;6phiBKOY/?V"0 &'6(8F!J0>P<#/h%3.32654&#"654&#".#"32>!5>7#"&54632&5462>32#"!28"c`%G<$vLB44A2.+8!?D(.%  ".F--F/! O&@\S;""']]'  ;S\@n?('H538 l6&DF$e#A37G )(H%9%./$:#,"(_CA\ 82?ZZ?28 \AC_7 %#"&46323B,-BB-3"S%..J.-7"&46323#54'&BZBB-6 T&\%..J.-7mC88m7#"&4632#"&4632"F0/FF/:#F/0EE0:"ks&--L-'&--L-b75%#"&46325%#"&4632wF/0EE0:"F0/FF/:#=='&--L-ځ&--L-Bp #"577>324&#"326p;W_&(  FNW%'30;b,C|R0  VO7&'32.'>?/&54654'2>2%2367>7632327#"&'&/.'&547#"&'>7&'>;&5477654'4?&54?&472>54'#"&'#"32?632#"'%>7.#"327'&547.'&'6324654.'.#"3263232654&"#"'.'&'&'&'32?6=#"'>7>7&54672>7327"&+"327>32&#"54#"+654.'676794OgB/4PB6*'#C  / Fs3IJI    +b] i ),/KImL W!+a>E,' Y3q2VU U (%#   ([]DZ A'$04[,  ).P() -2>c`* f8''j)6 -M01$"" 8 9+ "# vpn #AYI[ o#,&g!!jV0D9'0*A   H ! E"   !   :## H% G " # O0:/B4G/93NF W  >FH    *{/ ) N !!" %5T Em*a &x18_ Mx'+26LL 32&54>32#53D!$MNHMP$MwR9?!H!57UB$,8& PP9 $ DYHH-3O@*,   +?_;>/%2 JLbm#%7'7&54637#'75H?1c>LK&II@7:/mke6$;B; AB&eLm!$"3###53%7'7&5463753=I$1MGM31c>LK&I0mke -).;7HH7#;Cf>&/173:>2>7#"&5467Y6   CL_bN\LD  :hj3!eb)4&#".547323267#".546&<(*2,2MS1 O_?/ C/G#^"5  !$ " +=;/QBx  :8QFY5QM'%  5&&_. $'2'32654&##5.+'676753VG E`\)H.2'[A!M)?E)#RDP(#$*GN70C33S{7253#5.#"327#".54>1G33 c$7B &"1(H$C%c.5L0 5$,@ '(f#2#".'732654&#"&546{ #-Uc#k3='I] <r4#53533##5./>54#"327"&54>32=>?&&3>>3?[C"/># .!-% !/ -;P3DD3,HB , :(&,*!.#'3##5.'&/753632&=654G4;E  491,$r^.31*1vZKW(=>E_/**"$*GD(#<%253#5&#".546V.H$33=_%&+><21_P *1(#CAB%m%'673#5.# ) JIg43Q+0   54'#"&543367B5oXO34P- (0" 0!'!C   LE;H_b@L3 4"668& #90.#"#"&5473263253#C PYM.9S 6 2833( W6-( -32 *ia%'63253#54&#".#"#"&54632 33**!" .9E%0 (  $*(-7,*@."*2654&##5.#'>75#5!#2#"'7%25"h <"3$4M(/RLJB5'$1@ wB'*&/5('33T.*'; -q7sl)33##5./>4'#"&5473267>;C3<<3 w/4D 5#'+!-$4CU>3+]2-.6-  &- 6 .,253#5&#">32'7>54&#"#"&546(G.33Iq%E!-8)A&2 J&U-.'$8, $&%8K!9 1!'2[>.(2705#'47#5!##5./7#"&M   V 9P3[6 6 4(L     33./@:&p.,547#5!2#"'3267#".573254'#"&72654#y6:1, +":#_5_>%34b6U)2*4'(+"ZMT3,#2940_yHjv1 :jI,=8%E.72654'&'"&'#5!#he97GW2Jb+  @33"558h.73265&'.5#5!#3}1rrF #c2!?$~<]'m33 9OP`.1C.#53>54.##"&=#5!!3232'".'32654'CB * $W3:J C$6> !5  m0;  3+R3< +733 -B ./467#5!#3"'327#".'732654'#"&l'f 3 $7=G:'>) 3PE($"("3J 33G* _57T<-0+!,m!675#5!33##5.'#5.#7Ps(3>>3 3l4] ] D 3 /T?3u?"Sc, >%:<O2#"&'#4.#&5473263254&#"27#"546327>"3254#"532654,!I"?3 4?54'#53&5472654'lF~c :'7G}v4'* 9*13.Q32*9*2=*S-8' 3?6+2%M7<=2Q#Q.$"&=#5!#267#"332654'$8!$^C*3 !:"-?6:!33}6K&4d82R<-G'!3.7"&5#5!#32654#"'632r2ER (I  & n5#33 |& (0 C %;2.#"37#".54>#> f$8C .- ;;-*Qk.5M/>.,6& mm-33##5./654&#"327#"&54632:+33::3 $ 3 !=-,;*8R?34# 7F 5!$05 W3$"753##5./7.54632&u  Vm:32L8B*"  d(6&4/! 053=252-"( ? #!z.'5#5!#6?#&54C+G1*k`CV? 3 *339 (&+/).1.&#"&546325!5!#<6!(E5<^3 v)2+M3K.K9#>3253##5&''654&#"72767&#"KK;\PƓ2 (+8"?!J_T3:  ( 5 & /.*#"'73254&##5./67&'#5!!532')3l*<&I8/. ,2$%=0g21%11@ /!%033(* #<7.675#5!##5.#"75WmI/3 q(0!Q $KxD!33?c : "..24632&#"32>7#".'32654'#"&'5!u '    %4O:-K0* 3h=(1%<)4 1  (>?P#3SC1 jv:-@:33. *4'#32%#5!##".#"327#"&54674\# U:ea,)#G7 $*4,753309)5))30#(L.!##5./67.+35b-3h)!=,V=.#DZ4.36/;)8 <*>A5QM'%  5&&.%25!5!##5&#".#"&54>326 O3&)+#$$$3 4"*-%Z33>?$7( "E$)/).0263253##5.'27#".'#"'72654&#''/ #!7j3# % 8- #/IK[334 0 +#,1!&30".!327'654.'"47#5!##".x/b1a4rk7 $%Y( MK$33-;X ,J.%#".'#"&'6732654'#5!!3254':(4 "<6 " $78$=",6(3!'@* 33$H8 ([\.%)&/32>54&#"&54632%5!) (z/,"2 5%"H!'.W([6'37 ("1 ,X332}.(2"&=#5!#>7#".'32654'4632"" 3 !/!-*0L) 3e7,=$Z$+7~33{CC#$9" 8NbC u@#!K$:.'7"&5#5!#32654#"'6324632#"&n -@Y0A+a_ 7 !33"g+ '$*@EM. #74632#"&&+5!##5&/675O /CA \532{ +/0P>(( B336Q )-m(E(5QM'%  5&&7.7.'7'675#5!##5.#"75D++DWmI/3 q(/!R $L9 0 6"D!33?c ; ",'233##5./32654'#"&4732>54'r=Y3ll33P]+ 4]?? 9/( 0GC=W3&-!?2 JJ3Z0H..'5!4.##5&/7'7632#"'7326Bpk/&3>(y&)djF4Lf< 33#nAW6P!4#>#=>G83, ;X.7.#'675#5!##5 /AT&yg4,~X:<*6g&+33: N6NFo.4&#"3"&54632#53## R! *5'72BC3[? (&/733tY.'463&'7#53##54&#""&5& 3>wC3]+ ' )(3 .>3 333oZ((.47#5!#'>54&N2;ACZ+3xK833G@EE0 )! .>B2'65.#"#4&#".547326324#"327#"&54>326%5!M*9!#)/3=TFRD )7=&Q7-%y# +SaU7uG' &(09+'{9F0/#)( Z63,/"2)/# L3D06`*.33.(L1#B%''5NJ Q4A.#"#"&547326325./>54'"&547326733##wCQLZ/EF<4686q4)$1( ,(74#"#"&54632533##yCM /E#.DG 6 28V^!F O",&"'*& N  WC9M.3CC3' --$1 *;) *<7#)"5"% $%1^5HM3.+=.##"&547326325./7#"&547#5!##27#p#l1D.*" KN &Hc<3&   34O:0% L3 2>] (R8,33 $ a0 +.-5&#"#".547326325#&'#5!!#2654.'o+( t "+.*" ^KL=7(3(Lg9 ;(.' P1e33=() "%+? 8 7<.KW.#"#".547326325򂂮./#"&=#5!!32&'#2654'#"'}C J0E#.136E$"1'-4TEQ.##"&547326325#"&54>7>54'!53&5473##2654'r   g 2C.)# 33=EV2 Q6& 00ƯE 3r7F<"G/P90% L3):ZD N= 3\H8J",O63o.BA!"x@TV>NAqY.>.#"#"&547326325#"&'332654'#"&=#5!!3267#DPLZ.9S 4 56*3m3b[;RR%(F^K3r' M@'. *32 +[3'&?.33S2lF&.:.#"#".547326325#"&5#5!!>54#"'632#p#y "+.)# ?D 3 .q,.3' :&.' (&3 M/733q \&"!<5>./!!4632#4&#"#"&54732632&#"327"&5͘6 3.*>L, Vx'&w  >%.39!-]B4]I/A*3$"m&$ -VU.GK.#"#".547326325#".'332654&#"&54>32#5!B Q/D#.DG 7 83%.":,'3  $2BLF57 + 8&Kc33Mvu' ,.), '<) *l0'F+R'*7;X/:\IFa!#'" '}>33B.#"#"&547326325&'&/67654&#"327#"&546233##xCL LZ.EG<46PB`,p'@7) ? (V~Q]$iJ4QQ4<( M@*+ .;) +BW68;"*$3- 2DJ=7Q(ZX3.+3".5472632&547'#5!!67&'" '0!1-Hh'#* d.e,-.(5*f@*/:!3:gV2"33!S|^P "55:.#"#"&547326325./7&54632&#"753##yENYM.EF 6 46FkMq_<+( $yR3yUwW2) W6), .<) +EFL)[E/(8 DJ3325!5!##{B OYM.DG 7 65"5- "!4>)b3|P3m' W6), *<) */:'' , '&D2;(f33c.H7326325.#"&54>323>54'!5!##5."#".5472*"  9; ))&)H2 $k>j3 k "*.'3 -" !2% ,,>33-&54  ;'0%   s1<.#"#"&547326324&''654&#"'>32533##267&#"CPLZ.EG 6 462$%.# n6F3VV3 # S34 ?2( M@!4 -<) +) W"&!$ $1PE3 ,+R)!.>.#"#"&547326325./67&'#5!!2#"'73254&##xCL LZ.EG<199b($fp3@&69tMh," K94F' M@$1 *;) *IA] %M#6@33)9&1,&bNIg2 1 #3=.,3726325.#"'675!5!##5.##".547  8"z@4#:/ #*.) 2" H,6`-+335 ;'+* (&3{I`!!.8<"32>7#.##".547326325#"&54632&%5! 9 !1- -!49;$1C1g7aD0;3%: 3 !]#"J5214B "U>0!+$+_33hA.FJ.#"#"&547326325#".'32654'#"&54632&#"3267#5!A MLZ-EG 8 38 %9bC:"9%T,P"5R3O)4! K%3!%33MXc' M@*+ .;) +w1Jm_:Q?3Z7?#00):2G #!9 D833=.18.#"#"&5473263254&#""&547.'5#5!##5|C H YM.EF :56`#/*U.C AML93?59.( W6+* *;) +5+P,`)%""33B73N.'5.#"#"&547326325./67.+5!##zENLZ.EG 8 46)9-9Ye n>4a/:R#-2) M@!4 .<) +;CR 89CG33bN/- /jM.#"#"&547326324&#"327#"'#"'732654&#"563263533##ENYM.EF 2 56P%!, <" 4#I5N3QQ32) W6), .<) +Ms/6 +:')01=8k_  $W3.*.5.##".547326325./67&'#5!##5&'o#g #*.)" BdE*g-~IF3LI;0{V2 ;'+* (&3 /]G:%R#A33b{(?+7q\.6?.#"#"&54732632.#"#"'73267.'5#5!##6325CPLZ.EF 7 29 :(20A,- B_853@5"'2n' M@*+ ';) * N4?4j" EK7"33a84E.FN726325.'#"&'6732654'#5!##5."#"#".5473254'}   2 "<8 " $7W33   k "*.*8$= '3!'@* 33"<4 ;'0% &'3w$H8%P.:>.#"#"&547326325&/32>54&#"&54632'#5!zDNYM.EG<64_:&9 VB0d/7kY&4y{' W6'. .<) *Uy79!(# )7#$*9,JZ9o2Zq!!33.)-4.#"#".547326325.#"'675!5!##'~CP/D#-DG<19:!~oA39V0 %& ,-&- /;( *%-6b++33@>e X,q.)18.##"&547326325.#"'675!5!##'.'7l$;.2D.)#: !~A3X_:,n6(Z%O:1$ (&3 o-6b++33v&Q . I7QHY*.M`e.#"#"&547326325.#.'474.#"#474'#5!#2#"'73254&#"#25".5#7#3FL LZ-EG 8 38Q*  R+&69'j$044"  ,4 38a?  S3 2( M@%0 .<) +h5F 1#2  Q 33Y.!1&/ &,*4\D8 ,0)U.D%'675765254.#675!5!##"'#7254&'#5./7&',EX.4"kP*-D !4XlGq' %+!O3)"M."M.(,"m ?   [X5-33. Q>" 5$,3#3Y%( )0" V67723##"'73>54'32654#"'632#"&547./675#5!4&#"#"547326c' 'Fy52\,0!K  Y8H%%P #'Ks93@<(2 DoW1 A/DRkAY)<3!7!H, F^iH8#Q*&%4[54'32654#"'632#67AC K55 .

 H&;$6 .*>,C"<6) 1&,)#.$&<4G:)1050*0! 33.LT2#".'732654&#"&5475.#'>75#5!#2#"'73>5654&#6'25"7!0 "D-7a:#32M*.;!"+ 4Q( PT'; 6&$ 5$1@ u!0'*2"B_OGP85)9+/5*33.'*';)#oq7O.SW%.#'67.'32654'#72654&#"&54632632#"'7654&#"#5!w#) JR, 1.(!   *433b= >!)0%,*@,G"A4) 1&,)#.$&<4G:)1050*0!&;:#33.Ya2#5.#'67.'732654&#"&5475./>75#5!#2#"'732654&#6'25"A K !0 3S+!"+.Q132M*.;!"+!/U( PT'; 6&$ 5$1@ u !0';2,0#! FVGGP85)9,05*33.'*';)#oq8F._c.#"#"&547326325#".'32654'#72654&#"&54632632#"'7654&#"#5!EOLZ.EG 8 46371('   *445ʅ) M@!4 .<) + .*>,C"<6) 1&,)#.$&<4G:)1050*0!&;33.NV7&54675./>75#5!#2#"'732654&#632#5&#"#"&5473263254#"'25"&+$*W( PT'; 6&$ 5$33)1@)L--" 6 '-7z1@ uw ?25*33.'*'; )#6&*#F2$6 / *"(<q8.ah.#"#"&547326325#".'732654&#"&5475.'"#'675#5!#2#"'73>54&#632#25"zCOLZ.EG<643;5^=%32M*-<%+1I(B'< 6&$ 5$ &34=<v' M@$1 *;) *&>[SGP86(4+-54633.'*';)#&>&iq7zT.7>&#".546325.#"'675!5!##"'72>54&+#0?^&+>,RH@zw9F0/")( \22)7! *3&#Cc-/6`*.33.(L1#B%'7L JX/M.8@65654&##2#"'74635.#'>75!5!##4&#""&'25"^*/5Y1[%'&$-/W(PEpV3B#''P1@ u3 "8@2T=8;$!"15&33@ )&q8T.EL726325.#"'675!5!##"'72>54&##4&#.#"#"&546x4& (@zw9F0/")( Z72,'(#-74'#5!##"'73254##5.#.'474.#"725.'#7#3c6 'j1"#4"  3Q*  R+&3)?!? FDS3 n 33Y)1&/ &E|5F 1#2 V$878L=0)x.HUZ&#"&546325.#.'474&#"#4>74'#5!##"'73254.##25'.5#7#3Gg6)KE#3O-Q*  RJ'6'j1"#4"  ,(3BV? +">S3 ?7/G'6:9[5F 15? !33Y)1&/ &  Um@8U)0). )-d25.'#2654&#"2#632#"/'#3'.'#5!##5.#"327#"&547.'#"&5463.'#"&54 '/"/<3M >) OS#C  fR<kI3m+  "4!&&+'!A)'*?  i=9)!* 5 < 338'^ /)0 -)#!&.!;A675!5!##5.#"#"&'732>7&'2##"'732>54'6!S395%,#5*!/ NS> BG # V._a]9idR'33l0#)3@ ,R.A/<'  <Z' K4&#"632%4>32533##54&#"632'>54#"#"&5467654#"#"&Zx=UBSB,>*y>3RR3S>`D . %' O$U:)E!?;|eL 5+1!rY3gPlh"% $)+&9G   .A25!!67&547'5&#">32'>54&"#"54>yPG.}e,7d-Iq09"*+ #& ! J8-.'3!wCq8Nmx72r<6& $'! $   -D+18{GQ4632533#632&5474&#"#5&#'675.#">32'654&"&25\GaJ1@23Cw2K"0!?)+ (  '$fJ 9,g-* 6PnM3UQUbCdE) Am@H< -03-1 (!  #"U' ) :%8{MW].#'67&#'675.#">32'654&"&54632533#632&5474&#"#5'=2SU*!9M7X2K"0!?)+ (  '$fJ 9\GaJ1@23  Jx2,f-0:.5@H< -03-1 (!  #"U' ) =6PnM3OWqFCdE)    -+9+{L2533##54#.#"#"&546326325&#"32632'7674#"#"&5469M.3CC2#- .%$ -07#)"!E( 6)%1^+{=2533##5&#""&54632&#"32632'7674#"#"&5469M.3CC3=_%"/>,.H$V^!F O",E**8+ N  W15HM3< *2(#C2107#)"!E( 6)%1^+o{:@4&#"3"&547&#"32632'7674#"#"&54632533##'32#R!  *&V^!F O",E**8+ N  WC9M.3C3l72l[? (&"=7#)"!E( 6)%1^5HM3t7l.927#'47#5!##5&#".546325./7##"&& !&')3fO=3=_%&+>,.H$JN&H $ #a0 133qP *2'#C21$AZ (R8.>DR#"'#'654&##5. #'76?4#""&5467&'#5!#572>747474+' ;Wi$:/3  ~#k%3'4 *nHU cK:d c 2'i ~x50%"*2  3FU&$!334F&Y.[am.#"#"&54732?6325. #'76?4#"".546?&'#5!##"'#'654&##572565474+];2QH* ?A  )5  ~$k&3" * -fU(:Xi$173hK;d c 22(W6%0 ,<)+!  3F U&  33%i ~x60$0{5H%.js47#5!25!##"'73254&+##5.#&/4>?54&#""&=47#"'3267#".573254'#"&72654#725'.5#7#36='3 3#  ,4 3Q*  H7$ %7#_5_>%34b6U)2)5'(- 8`B+">R3ZMT33Y 1&/ &,|5F     -;  #2940_yHjv1 ;iI,=9% ^B8U)0).KT.#"#"&'#".573276754'#"&547#5!25!##"'73254&+##2654#"&=47#"'326732?6325.#&/4>?54&#"725'.5#7#3A 6?W 5_>%35b5*2)45F'3 3#  ,4 3~())$y$ ":$_,C?38Q*  H748`B+">R3"'81Hjv1 54'"'#"'3267#".573254'#"&72654#63f *(<44Ld3TT387p4(0 %7#_5_>%34b6U)2)5'(- ZMT3A4?JJ>`V3R-1C*5)!#2940_yHjv1 ;iI*=9%1_g7>54&#"327#"&5467&'#5!##"'#67.4&54>7#"&'732654')58"%(2 k#@64 ( =MHk-4    $O3="-}$B">' 3!+22 %N!@%2kyHY+    R=.'; -+ .:CP47#5!25!##5./7##"'#"'3267#".573254'#"&72654#27#6B<4MNG%7#_5_>%34b6U)2)5'(- P!M82ZMT33A>] (R@#2940_yHjv1 ;iI,=9%/ $%4a.3<Il.#"#"&'#".57;67654'#"&547#5!25!##2654#27##"'3267326325./7##"D)EW5_>%34b6  .)2)56=<4'(- P!M82*%7#_HD? 2 46MNG2'?6Hjv1 ;i& !,=9+MT33)%/ $%4a#2940_U, +0>] (R."O2654#7567.'#"'326'47#5!##54&#"#"'#".573254'#"&('(+"?4<+/"O $. >":#684`#$%T3%#O5_>%34b6U)2)5%963M$3 !#0 9MT33 .!O,B%Hjv1 ;iI,=9nP.1:V47#5!##54&#""&54>3&'#".573254'#"&72654#7#"'3267y6hC3]+ '+(+5_>%34b6U)2)5'(+",*":#_ ZMT33s;Z(* / Hjv1 ;iI,=9%9 * #2940_7(  .!*07265&'&5#5!!2+&'52654/&/#2qoJAb!;'9(G"`&$,\4 i7k}=]$3&0m33=,'+J 3C8 $ 3H +.6AIO.+532654./#"'#&5#5!!;26722654'#"'3265&'5#_AmS."C`z;GVx _1u?AbD=#) NC1 1rrFe2]&S)$7='4[2@]Eg&0m33@ZK&z$~<]< /H +.GRZ`.#'67&+532654./#"'#&5#5!!;2672&'#2654'#"'3265&'5#G  JP, AYlhGVx _1u?AbD=#) N_AmS."!/4C1 1rrFe   > *0C0<4[2@]Eg&0m33@D2]&S)$$$K&z$~<]< /H7+.Q\dj.#"#".547326325&+532654./#"'#&5#5!!;2672&'#2654'#"'3265&'5#xC L55 .32>32#"&'#4&'#&'%4&#"32654&#"5326if0C GQN5+'W@5o "#!)=b*7%B3"- ,0g*^3=c}o H$5a/W2% $-b+ @33@?%  S x$ )!`QE]A' 4&7-gBO-yQ xE67"+3%.,5.#"&54632+&'#5!!#2>54.': +="6(K=+4K/+n+c=2-* 4#bDLg*9#7 /G =7<09N33=&(Yi $.#u%2654'"#".57"'327467#5!!327327.547#".'327#"'#".'&'73254'#"&( #~4 $7/%:)>! @l7J 22 7$:@C1cF 8!;& 3/L$&B!  1#4`M8>.K 33GQ =Gf%=#`O (!+(Z2Bm#-!@z)2K..M"67$.)%'2654'"#".57"'327467#5!!327327#5.#'67&547#".'327#"'#".'&'73254'#"&,J( #~4 $7/%:)>! @l7. 3Q,!K\2 7$:@C1cF 8!;& 3/L$&B# !  1#4`M8>.K 33GQ =,J73 *0K.`O (!+(Z2Bm#-!@z)2K..M"6G{.OU%47"';2?#5&##5654/67.5332654'#".5467#5!!3276 @.29 , +201#<9S(1$;# ,#2S;?C.9Up+' 0, S) r+".9L7  e38+CMg9%*) ,,*7*33!3# )T.1FO2#"&'#".'&'732654'#"5467!5!!63254#"';2654&#"&'32696)'P S,BQ0L-%)(7 =/e5}2M<^06@%1'B/D61K65k@$2N7> h @#/~&U33)P`CY5:232537632%32654'#"'732654&#"iF$cF.=",M("PG !"3)32#"'24&#"3254'#"'7;26&'32qz/34+44(#ve A1,?66c- @!<^H&4.1.&0"((.l2&?M5E/Q*  )|- *O!@r''' E4 *A!= ,B)2+5;HB=!7)-"?9LK6m( (6C)$ !0^v327#"./32>4'#"5?>5'.'#"&54632>32&'&=467+"'3254'#"'7332654#"> +!- & :]/2 h; !# ,I!  $8&P;+*8#>"#! H$%" X,#<-* .*)[yV/) N*0Pj9]s *B_>7  9 '.1X'(+%%. /^6?(W)++C.  - #8!1- $o%7'72654'#"&5432654&#"2#"'#74'#5&/67&#"#"&5473724&+"&54632>L7U.h*}1 )9d_80:)/4%!*449?[0 ! DT$,A?iP .m &;.))&E! K*+BM #< $ t\A-7/>+5 H/F\9,/oS&C8M; Bw. '6:>f$ 'G%#"'732654'#".=#5!>74#"&5473723!32654#"'632P#?*\.Lq!?6.)y[5G!* a3D/#;T *"))/&-( (*! 3 5 <1) $ / E3 37q9-$!B2H;'d.#"#"&54732?6325#"'732654'#".=#5!>74#"&5473723!32654#"'632#g>2RF* 7I $ .3(:\/KrD6.(\5G"* `4B0#;T (!")$3(V6)+ '30*#%* *" 3 5 <1+ $ / G1 37q9-$!B2L*.o 'I4&#"3".54632!32654#"'632#"&5#5!654'"&5473723##Q"    5'73;d&!"-:c4,'d[[3G!*a6AB3[@ (!/76D$"1'6vS?'O3 2 ?/) . F63t5n{FQ%"&=!5!632533##&#"2632'7674#"#"'#"&'332654'7&547#326!#C9M.4BB4T`"DL #,F)+8+ T 0E:qu30M0?32>54''#"=#5!#3267#"&'#".'732>54m !730)%&7 #R^1~ S-6aEH_1W/2MI</5 0 $ȇ975e;"2)%:(7#6333T]4BzH^0IJj !0)"o.=4&#"3"&54632!32?67#"&'332654'#"&=#5!##6Q"  *5&824 K(R6m3b[;RR%(C3[@ (&07-2l7?+[3'&?.33t ='L#".54>3253>74#"&5473723!32654&#"'632#"&=4&#"37 % :&#? [8E!*4/@b  "'KC/(h[B , 5#,6&.,c 5 ;1)  w 3I %"nO=(s_M h'm.#"#".54732?6325#"&=4&#"37#".54>3253>74#"&5473723!32654&#"'632#D6/E#.EF56;5/(h[B  % :&#? [8E!*4/@b  "'K(3c',.*+ *;)+>=(s_M- 5#,6&.,c 5 ;1)  w 3I %"n8F $U4A".546324.5473##"&54747654&#"3732654'y &E3lB99<8ͫwW6ca*#)Z[c2$ET"* 8%327 %N?i4#6K&/ %&)*.$ "/(" ='(5h49K #`v\ &# XKA>$ /E% 1 )FDZ,!.=46325!!32?67#".'332>54'#"&=&#"&7*.5L4 K .U6RL33 >GV80I$S%#40!(%7;n3-2l->2$Gd_&aM/ *<2#*@3;[h2f.#"#"&54732?6325#"32654'#"&54632654'.+"27+"&54>327#vC  L[.DG( 46%3i4#6K&/ %&)*.$ "/(" ='(5 %3c'LA(- .;)+ #`v\ &# XKA>$ /E% 1 )FDZ*417.IZ%"&=&#"&546325!!32?67#5.#"#".547.'332>54'#"'32?632(4/!(8)02L4 K3C K55 1A3 :FT?/I$SM4FmQ 325!!>54#"'632i 32ZB  &"2(2F` 36% ,./7#6cL0 5",@ H$3q 6X2&"!<Y?r;327+"&54>32533##5.#"37#"&54>3254&#"r, )F>)3& 3__3f[B L!1)1GY$-H>3^+1:('c3w!icM/=B+@ H$+w4?o{=%2#53##54&#"3"&5474&#"327+"&54>32533#;3C3Q" *$Y$-H, )F>)3& 3,,4633t6@ (&"3+w4<>3^+1:('c3J.04#"./732654''254&"&54>32%5! cM 71=71832%5!*"Mm 33R,!(v^7186u) &.X*Y?31 ZD.++  5 %09&h;$ #1U.=A.#"&546325#"&'&5/332654&#"&54>32#5!p +="6(KE#3M.73XYBLF57 + 8&Kc-(3ov*9#7 /I'69:V\IFa!#'" '}];c33E.J"&54632!5!##54&#"3".5463&'#"732654& +0& )1"@B \H2} #(/  ].  ,!.,  !1 7%f*336!U )+ #<*D%8o.@4&#"3"&54632!5!##&54>32#"&'&5/332654&#"6Q"  *5&82C3 + 8&KceT73XYBLF57[@ (&0733t'" '}]\|V\IFa!GU.LP!25#"&'&5/332654&#"&54>32#4&#.#"##"&5463265!["#73XYBLF57 + 8&KcA2,'(#-9+ &*/6-$F133{@2533##&#"2?632'7674#"&547'#5!#6746:M-3BB3T`!F  # "-F)*8' !) /d-n\\xZ17FM3`7#  )" F( 6' NkWG LcdY233{4[.:27#'47#>7&&547'#5!##5./7##"'&  3f2t2d-n <3KN $ a0 1DEPLcdY233A>] (RMrWG-..?6?'&547'&547'#5!!>7)'!#2")3!#)2>32TZ,k(N!* oZ8`Pc,H#"E (37>7#5&'R,!EZ  2F?2TZ,k(   (")3 % J+0G//1,H   + (37>7'&#"#&'7 8!#2  2hn,k( ')-X")3#F"W!- *Q 02,H !6- (332632'."33 FG! 4F0If.A;s3@32632&=474754'#567  JQ,!!2! 4F0If.A;."33s%   >!)0+wD}*33'I6* '0 <54'#"&5463367&547'5#5!!>?6765qVN43O- (0" 0!&"%   3%#)J &"y?H8G`b?M2 4"75& 0#)#33"!.M%25#"&54632"2?&547'5#5!!>?67#5&#""5473265!'$2&'4##< 3%#)J &"y*38 2:;K -1&%# 8)#33"! 5;,<*7/.j.#"#".54732?6325#"&'332>54'#"&543367&547'5#5!!>?>?>767#B K55 .3!"&547.=47##5!##5&#"27d%  2- *y%"V$3 ./MQ3U0  80  #z0 $sH5= MK0G33Ap  ' -Y507"753##5&#""&546325./7&54632&#yR3=`#.>-TFGkMp^),*' yU-J1& DJ3^P% )3'#CcLGK)[F=8Y4&+#3"&475./7&54632&#"75!###632R!  *GkMp^),*' #yC3yU-J1&v 72[? (&88GK)[F=8 DJ3t:L654'!53&/&5473##"&54>?654&#"7#"&5432654&'*!F 47! ΰT$4>./U9(-a <- .:8&.MC%!%30%16D<)%4*P23sv5S. G4- *%.$/^.$1kFsM20 /7T3;OA8   '+3k33H^HtBq}I 5Pb?,\F!a".: 0%"(Ee.[.#"#".547326325#".'732654'#".'&";27#"&546325!5!!327#C  /E#.EF :2965FsM20 /7T3;OA8   '+!:~QL3',.%0 &;) *+Bq}I 5Pb?,\F!a".: 0%"(3k33H^-,^.,%"&=&#"&46325!5!!>54#"'62Q,'<6!(9(5< 35%*>.@&vR3L33q 6Y1&"!=Y/.I4'!5!##".'732654&#"&5467.#"&546323>$u>t,+$N71[@43d/&$5I4$)/% )* )-0=!& >33-&3_"142JYG c}@1(7% "&!5  !253..W%.#'67.'732654&#"&5467.#"&54>323>54'!5!##!  JP*!"*6a?(4\=&$7G4$)/% () ))&)H2 $z>y*>3!  >"(0# MdZ! O#A0(7% "&"4  !2% ,,>33-&2 >5S,.L"&5467.#"&54>323>54'!5!##5&#"#"&5473>3254`C$=(+ ))&)H2 $k>j32: O 9J,8'#<(AH %"$P !2% ,,>33-&0'3*V02: >(11I).i#5.#"#"&54732?6325#".'732654&#"&5467.#"&54>323>54'!5!#"* >3C   LZ.EG  55)33-&2a).F%#&/532654#"'63. #"&54>323>54'!5!# %@(}"w66 `SaG1    ))&)H2 $k>j( +#A#m3<)H]     !2% ,,>33-&= 9y.$&547'5&#"&46325!5!!67d.<6!(9(5<e,.Lcb[23vR3L33!PUYy.5;"#5&#"&46325!5!!632#7. /6754&5'g3<6!(9(5<MN12 -cRTM>}*vR3L33o   +R#E :&.A3".547;26;2&547'5&#"&46325!5!!67&'#"# &/!24 i , d.<6!(9(5<e,./'5P@*81!3:b[23vR3L33!PUY .06#5&/67.#""&546325!5!#632&5455(3=e&=q9"4D$7?".W46#2D&2N;b!35:%&4)0")55*z33f;8lWq>;&!K.6<E.#'67&/67.#""&546325!5!#632&547#5'O+!?J3G&=q9"4D$7?".W46#2 5(3~D&22' J#'0?*635:%&4)0")55*z33f;8lWq>7&!    Y.HN&#"#"&547326325&/67.#""&546325!5!#632&547#=Y22#;]* ?A * '6=e&=q9"3C$"1?".We9 2 5(32LD&1<DL$3 8=* +b!35:%&4)5)55*z33f;8lSu>7eޱ6&p.1.#"&546325.#"&54>325!5!##W +>"5(LD$.J7"5- "!4#4,d1P3G*8#6/J'60C.9'& , '$E/F"e33uov.3:4&+#3"&475.#"&54>325!5!###632R!  *"4'"!4#4,b3C3v 72[? (&8.:& , '&D/F"f33t97.7D%4>3&'".#"&546323>54'!5!##54/&#""&#6'$;E )-07+' $C3)" '*ڏ.' ,, !25#>33o)((U0#,;  ..7"#"'727&#"+"&5463267.'5#5!##.'6325J $": <+,6"K (&0&T1IX`$J01O#,ESEU3:x "&'o)*&"33)<{0!B3'GR#"=74754&''65&#"'>3253>74#"&5473723!>54#"'6267&#"29d4T3 $% .# o5F[5E!*39 9f+<-/@)9 D $E9Z Y"&!? $1P 5 <0)  w 3 J&"!<4+E/" ,K~=I#"&/732654&#"&5475&''654&#"'>32533#2767&#"!0yAJ5$3 E:=)5I1,3e #K;\P0(+8"?=>;J^{XVF* A0!C &%NR  ( $!J_P3 & /y 5?727&#"63%&'4&#"'67654&#"'632533##5&''27&#"6)'kA+E!&/03W!) L&)PwZW5OQ1-&m9)4;Bt2H% *)E '4 ~K3.'`9G os*5:4&#"3"&5474&''654&#"'>32533##267&#"2#R!  *(2$%.# n6F3B42 # S34 ?R82j[? (&# W"&!$ $1PE3tI ,+R)!e7  s0>>32533##5&#".5463254/.''654&#"72767&#&#" n6F3VV3=_%%,>,OK $%.% # 3#2$ ?1PE3O *0($Cd. "&!$ 9 ,+2&(!  sFT7463263254/.''654&#"'>32533##4'5&#.#"""""&2767&#&#"YC/5% ( $%.# n6F3VV2,'(#-< # 3#2$ ?!$F1. "&!$ $1PE3 = &*/6 ,+2&(!.1;D>3267.'5#5!##.#"#"&'7267&/'654&#"72767&"76325Z.jZLUJ05 (2%$( ,%#' ")V[ X(E{)+&"33 EIe '$*& 5!+`E!#W23!52732#"'73254&#32654#"'632#"&=./67&'#5!654'"5473;{"="w/. ,2%=0')*G *n71l*<&I8Anf-% =,3(* #(69vK Zr8 #1? 8- -407X>0A_ %M#6@33*8&1,'aNaP'% .I'675!5!!327&5467+".527#"&'&'332654'#"/.', 26S _%+19/"#  -@%s$ 20:+.-7'%& .J-* 33., /hx+e s(4  31SZPEl."2("2.'5675#5!!67&54?67'&'Q\[p@f,. 4W59'}h<%2;33!4aQNI Qi "Z7I9&Z0.8>74732632&54?67'&/675#5!!67&#"#"&5S2)0x!0  4W59'}!@f,.)9q%>J\[ ,A,9B "Z7I9&Z082;33!4aQNI -[<%=.+2%&546?#5&/67./675!5!#636750_I-);"Y1Ky0%*"(T` $-E^w72+[uo);T&? *e#+33+W#T`l L d.+.+'675!5!##5&#".5462*H''%9!~A3=_%&+=ZH%$X,X.6b++33iP*1($B21.7=%#"&'332>54'#"&547.+"'675!5!##36?6'55oXN44O- (0" 0!&"9p(\g%,  S]D46326325.+'675!5!##4&#.#"""##"J(3' *9!~A2- 1%$.H''%0,.6b++33<)  +60$X,hA.SW%6325#".'32654'#"&54632&#"3267#4&#.#"""&546325!' (9bC:"9%T,P"5R3O)4! K%3!%(#2,'(".;B05XE1Jm_:Q?3Z7?#00):2G #!9 &R:* &*/7,$F33. P4'#32'4'#5!##".'732654&#"&5467.#";27##"&5467# 6: &f %#hQ1[A43'=[06H-#1)0! +%!4,("&T!-33543VD\1JXI V^A@1!>!&. #3+'8O[33##5./67654&#"32727+"'#".#";27""#"&546754'#5!6324'#32\%jI4QQ4";,C&,n'B7) ? D#G7 *& e)7?Q# '*:8P)YX3I% 58<"*$3J))3+'!-3JI4,#).1=&547'5#".#";27""#"&546754'#5!!674'#32nd.#G7 *& ee,.# '*:Lcb[2[))3+'!-33!Q~UY4,#).2>#5'.#"&546325"&#";7"&546754'#5!#4'#3224L"3 #QA"+c   && oM4#(&:B E1?7 /G%:B|X,#'4335*5* #, .-47254&#"#"&547.'5#5!##5&#""&5465.H$`#B2U)D>NB93=_%#.E-=79.!21C+P,],&!"33P *3(+;82Nbs Z2767&#&#"4'#32'4'#5!>32533##4/.''654.#"#".#";27""#"&54670)3#2$ >`# '*: e N$F3VV3 $%!/,#G7 *& (82&(84,#)T!-33 'E3:. "%" "9))3+'bs^kw.#"#"&54732?6324/.''654.#"#".#";27""#"&546754'#5!>32533##2767&#&#"4'#32C  LZ.EF 46 $%!/,#G7 *& e N$F3VV30)3#2$ >`# '*:2(M@%0 .<)+). "%" "9))3+'!-33 'E3(82&(84,#)`shv%263254/.''654.#"#".#";27""#"&546754'#5!>32533##4'5.#.#"""""&5462767&#&#"4'#32 2) ($%!/,#G7 *& e L#F3UU2- .$#.;DM % 3#1% !?\# '*:1. "' "9))3+'!-33&E3 % -3/7,%E--+2&(!54,#). J4'#32%467&'#5!!32732#"'73254&##5./7.#";"&5/ ;*d Z< ^r#%/"(M83'5!ll *" #33 O6GM^O7-&-=ZC;V -VK ,.Zg.#"#"&54732?6325./7.#";"&5467&'#5!!32732#"'73254&##4'#232F6LZ-EG  38'5!ln **d Z< ^r#%/"(M83V *';2(M@%0 .<)+2;V -VK , #33 O6GM^O7-&-=A'.NZ#"&'332>54'#"&547.#";27""#"&546754'#5!##"'36?6'4'#325pWN43O-'9 0!4*0" *& ea1$ # '*:NF9FbbAL2,$ 7$'/ #3+'!-3309-/ " \4,#).T`%25#"&54632"32?#".#";27""#"&546754'#5!##5&#""5473264'#32!'#7")2# F#G7 *& ea/3: 2 0DR# '*:Y -2%&# A))3+'!-330985;442B4,#)Z.kw&#"#"&547326325#"&'332>54'#"&547.#";27""#"&54674'#5!##"'3>7#4'#32\7<QLZ.FG 929/=N43O-'9    4*0" *& ea1$ !.5(3# '*:q: M@+* +9, +nbAL2,$   $'/ #3+'(-3309-/ 5F9;-A4,#).3?J%463&'#".#";27""#"&54674'#5!##54&#""&74'#327#A5&,#G7 *& eC3Y/ '*# '*:2D(&#))3+'(-33o] ()4,#)09 .>8op.4;%#53##54&#"3"&546754&#"#"&547.'5#5!#!530*C3Q"  *`#B2U)D>NB =79.5533t6@ (&/{C+P,],&!"3382N.JV%25#".#";27""#"&546754'#5!##54&#".#"""&54632674'#32 #G7 *& ea 3 *-#+& .9A).3l# '*:))3+'!-3309*$(  @-7,$G7 4,#)\.;B%254&#"#"&547.'5#5!##4&#.#"##"&5463265;`#B2U)D>NB92- .#%.;K'2( =79.C+P,],&!"337- ,4",07-,=082N(.>JS4'#5!##5.#"#"'73267.'#".#";27""#"&546?#325476325n e?53 :.10A,. 0;1$#G7 *& ˵ '*:D@5"'2!-33N+(Tj# F?9!-/))3+'U#)B4,74Eh(.Yen.#"#"&54732?632.#"#"'73267.'#".#";27""#"&546754'#5!###325476325A 6K\-EG 38 :.10A,. 0;1$#G7 *& e?53y '*:D@5"'2c'LA(- ,;)+N+(Tj# F?9!-/))3+'!-33m#)B4,74E[.;C3&54632635!5!#'654.'#5.+'67.#"&#"I!SK'; !u7J 4 :1-'X= !C" +@%"h8-i,L,=!  a33 (3"6!)& ) )K:0O;"_` ;.L46326325!5!##54&#"632'67&#""&5463254&#&#";"&=29- !"s3ZB!AH"+ '%A & 'bEAH"1@#!  %,a1G5&L33œ+\;+$'  %$1&!=QL  ^1,17,W3723!32654#"'632#"&=4/.#"&#"&546326325!5!>54#"&5473C =f*2N$, ,<!8QI(8#%"y{[5E!* z 3 H&"!8#Y:, . ^ ++N,=1&3h3 8 <0 (  U,u.#"#".54732?6325#"&=4/.#"&#"&546326325!5!>54#"&5473723!32654#"'632#@!L'F:.EG 8356$, ,<!8QI(8#%"y{[5E!*3C =f*233v& D4%0 ,<)*2:, . ^ ++N,=1&3h3 8 <0 (   z 3 H&"!8#LT6.B&54>326325!5!#>7#"&'332>54'#"5&#".#">$$3 4"(/& +"OzCRs9>-<)&:M8&)+#$G( " C #*/)$Y33 :<[rSk$&-#3,$*@i>?$7Y.b.#"#"&54732?6325#"&'332>54'#"5&#".#"&54>326325!5!#>7#@!6K\.9S29+,Rs9>-<)&:M8&)+#$$$3 4"(/& +"O33r&LA%0 *32+Sk$&-#3,$*@i>?$7( " C #*/)%Z33 :<[PF8.P32654&#"&5463254&#".#"3254'7#"&546326325!5!##".'NM8F40)H5 /+3''=. 1-=C0; .d?gQ326325!5!!67ge.&)+#$$$3 4"(/& e,.R]b[2|>?$7( " C #*/)%Z33!P`N.HM25!5!#632&=656=4'#5./75.#"'.#"&54>326: : ."332>7&#"3d/+3''=. 1-=C0; .d?3# Z#' # 3'C@"(D!/&9-%C 1 F33a ' $)E]  ,.Q&'.#"""""&54632632&/#5!!3273#"'73254&##5./67d--*"(:-(69vK ;P$ 5 K:/,: %kB-/6-,=0#%33*8&1,'aN %51 &$ 1 #2>ZFFX %.n.#"#"&54732?6325./6?&'.#"""""&54632632&/#5!!3273#"'73254&##E  LZ.EF46,: %@--*"(:-(69xM ;P$ 5 L:32(M@%0 ,<)+5FX %3B-/6-,=0#%33*8&1,'aN %52&$ 1 #2>o.I%2#53##54&#"3".54754&#"4&#;27#"&46326325!5!#84B3Q#    '@'." 0&;GG;1.+&S )5733t6? (!Y&&3))90QR0+ 33.I%25&#".#"&54>326325!5!##5&5&#".#"##"&546326*&)+#$$$3 4"(/& O3 */",%/8C(.3>?$7( " C #*/)%Z33W) B.9+$F7!.?H%"'.'27#".'#"'72654&#'326325!!'2654/&'\ %# % 8- '/ #!7l/"C  Y{+51-S%G34 0 +#,1 &30/IK[3=#$ 0]3* G.[f4.'#"=&'327#"&'#"'7254&+'632>325!!32632.+732>'2654+"'` _0N)&%  - ):(  W&2     &N.?b>g6Q"9mCP6D3*AYwKG% +11 &' ,A* N3< M81^(/K$Aw2<J(!-]32654&#"&5463254&#"327#"'#"#".'7&'7;>54&#"56;263533#Q&54&#"56;263533##5&#".546324&#"327#" 4#I5N3QQ3=_%&+>,.H$P%!, <X')-1=8k_  $W3P *3&$B21s/6 +oS%4&#"327#"'#"'7;>54&#"56;263533#2#53##54&#"3".546P%!, <" 4#I5N3&&54B3Q#    )5r/6 +:')-1=8k_  $W3:733t6? (!"\726324&#"327#"'#"'7;>54&#"56;263533##4'5&#.#"""&5461) (P%!, <" 4#I5N3QQ2- .$# .;C1s/6 +:')-1=8k_  $W3 > ,4/7,$F.8@327'654.'"47#5!#'2654&+"#5.+'767&x1b1a4JPc])/$  2'[A!)7\$ E%Y( MK$33+## P:Q(#;GN70$0#)#g.JT]"&547#".547#5!!>32'654#"#5&#"#".473263254'326?'654.'"1G$ $4 '' !0#"1230< S'-()T 0, ! a1WI # ,K$33+9) 8-##B2* 218#9+01S1':K -A*3F31B 6 5 +o< .'Fm'<@67'&/&'#5!654'"&5473723!32654#"'632#"&'.'7#')=[3G"*`4B0;c %!".:c4.r=Ũ4tG5/  3 1 ?/). D836C$"1'6vS %6Vgh'$U]f.#"#"&547326325#"&547"&547#5!4#"#"&547326323#32654'7#4&#"6'327CPLZ.DG<46JN*3^B<:NWJ0 zSE5g~M[M8,u#=3!N! !0"c' M@*+ ,;) ++3&:K37@+3F31<  4=:35'N6H"#9+/M +o< .'jm'[_.#"#"&547326325#"&'./67'&/&'#5!654'"&5473723!32654#"'632##B   L[.EF 046*+.r=!)=[3G"*`4B0;c %!".(!32va'LA(- 1 <) + %6V.5/  3 1 ?/). D836C$"1'+b'iT1;G654'#4>7'&5467'#5!&5473##"&7&/62654'  D%,;\P6&+   )>0cGEW;9)<&7F<&:3|1 )&N?3]G#x@TV6\8!q.$:AE%"&'#5./67&'#5!!>32'4&#"'67654&#"32%&'5(.g3AcG*g-~"# ,<+.1'-P$ D20f".H;0{UT]O$@\H:%R#A33%  >-5%13gh3. ( FDKG(>+7qc#ws IN2767&#&#"327'654.'"47#5!632533##4/.''654&#"#".%67#c # 3#2$ ?1b1a4F3VV3 $%-V) $" ,+2&(!B%Y( MK$3 E3:. "&!$ (5 ,y #sGT\ej.#"#"&54732?6324/.''654.#"'#".547#5!632533##2767&#&#"327'654.'"767#F7LZ.EG  38$%-X* $4F3UU30)3#1% ?1b1 '2(M@!4 .<)+). "'  )7 ,K$3E3(82&(E%Y(  .D27'74&#"26"&547#5!!632#"'732654##5&/67'x*\' )> :Ahq)diG4Ke$!  3>(a8^Nl>.=&7533#&(4#>">>E:%0 ;PnAW6=&.KS^.#"#"&547326325&/67'#"&547#5!!632#"'732654&#"#27'74&#"26wCNLZ.EG 8 46=(a8?:Ahq)dgH6Ke$!   2( M@!4 .<) +*V6=&=&7533#&(4#> @>E:%0 ;#/Nl>.:.4A327'654.'"47#5!##54&#""&5463&'#".7#x1b1a4lC3]+ '*5&3: $/%Y( MK$33oZ()!((- ,-<  3+#oR.)04%#53##54&#"3"&54675./67&'#5!#&'5y12C3Q"  *BdE*g-~"!3I;0|UL4433t6@ (&# ]G:%R#A33(?+7qc{}.I3254'"'3'2654&+"#5.+'767&'#"&'6?32654'4'#5!hVG?8&h8$=9)IY]&7  2'[A!)CO?"<8  $?E",l$H8$"5 N7P(&4GM80$:Q3  @*33.T^%&5467&'#"&'6732654'#5!#>32'654&#"#5&#"#".5473263254#"'3254'"'$1#."<8  ))!0#"231;V%7 (+2R 1,%C8$= # !H?3!3.33"5/ 8-#B2* #3*)A"8; /I$H8$v}.RXb.#"#".547326325.+'767&'#"&'6?32654'4'#5!#'2654&+"#3254'"'h:B/.) >>2,2'ZA!)CO?"<8  $)IY]&7  3VG?8&h8$=T' ;(*+ *;) +EN70$:Q3  @*33"5 N7P(&4IE",l$H8$S L254'#233##5./2654'#"&5473274'"&'#"'7327#5!2'!]  z*?>X3ll1:@g,Hd=A-*-  K10 <"1% $ ?2)>'   ,$C=W3&/?')I0&J 3/6::!)n3EdI'KS%2654#"'632#"&'5.#"#"'73267.'5#5!467474#"&5473723!25!6?a*/6a6 +4-!21@,%. B_2[8E!*a3DC '1`$RK&"!<9Z!-?4?4jOCK7%3 4 <1)  / C73n=EoIC$Y4&+3223#32654'7#"&54>7.'#"&'6732654'#5!4#"#"&547326%V9#=CE5g~3HH38+v#7.'#"&'6732654'#5!4#"#"&547326323#32654'7#4&+32EM/E#-EG 8 38@<*3 )/';"<8  #:NVK0 zSE5g~3HH38+v#=33 $V9#>c' ,.(- ,;) +3&!#=*3!A) 3F31B  4=:3#B419#9+/&%M+$H9*.>G#".'732654&#"&5467&'#"&'67326=.5#5!!3254&#&#"dT7&'#"&'6732654'4'#5!##3254&# JO+!%6b?(3-5N*5I41* +"2"<8  #7W&$"43f9#=B  >!)0NdZ ;PA.@1(7!& #,F3!A)33"#2 UK+$H8 ).JT"&5467&'#"&'6?32654'4'#5!##5&#"#".5473>3254'3254'"'`C$;'6"<8  $7W$30< O $7 ,5*#<(8$=EH%"#OH3  @*33"1 53*!/) 2:;&31$H8$h(.bk.#"#".54732?6325#".'732654&#"&5467&'#"&'67326=.5#5!##3254&#uD6/E#-EG (38)1[?43\>&%5I3$)2(5"<8  $7W% #">3[9#=c',.(- ,;)+b 2HZG S"@1(7%! E3 @* 33"!4 UO.$H8 *q&.7A%#&/532>54#"'67&'#"&'6732654'#5!#'3254'"'#J1|"v54!$0>!`G1z/"<8 #7W.&?8$=+3#B#m3,"I] |?3!E# 33"%7#$H8$. ?2654'#%3267#5!##5.#"&546325.'&'+"/P ' 4A-5 , /.;6325.'5#5!##5&#""&546325.#"#"'7326s@5"'2 B_853=_%#.>,-H% :(20A,-84EK7"33O *1)#C21N4?4j" Es Y2767&#&#"'3254'"'3#67632533##4/.''654.#"'#".'#"&'6?32654'4'#5!0)3#2$ >8$=`' B!F3VV3 $%-44 "<8  $7(82&(($H8$)E3:. "'  )(3  @*3sZgqv.#"#"&54732?6324/.''654.#"'#".'#"&'6?32654'4'#5!632533##2767&#&#"'3254'"'3#6E  LZ.EF 46 $%-44 "<8  $7!F3VV30)3#2$ >8$=`' 2(M@%0 .<)+). "'  )(3  @*3E3(82&(($H8$scq{%63254&/&''654&#"'#".'#"&'6?32654'4'#5!632533##4'5.#.#"""&546322767&#&#"'3254'"'3#6 ( %&14 "<8  $7H3UU2-1$# -3&'#".'#"&'6?32654'4'#".  8$=YC4\+ ' )(% / 4 "<8  $".0< 3$H8$33oZ (+" /!(3  @*o.C6325#53##54&#"3".54675.#"#"'73267.'5#5!#s@5"'2313/B3Q#     :(20A,- B_8!84E:433t6? (!, N4?4j" EK7"33.LV726325.'#"&'6?32654'4'#5!##54&#".#"""##"&5463254'"'10!2 "<8  $7W33** $.9B8$=6 '3  @*33"<(  %*(-7,$F$H8$.N6325.'5#5!##4&#.#"""##"&546326325.#"#"'7326s@5"'2 B_852- 1#!-.#""&546325&/32>54&#"&54632&'#5!K '7!&"/>,-H%z_:&: M$B0d/6lY&&3|"8 *2)#C21Tc09!(# $<#$*9+KZ9o2[p!*33I..22'654&#"&/32654&#"&546326%5!0:.;&A|Z&[,CXK&1++ Q?0a 5'v;1$:*.#a!Yr!G9!@3$=4 %4EF*-V33./=B4'#5!#2?&547#5.#&'>354&#"/46725'.5#7#3f%X9 0D3O+$1J5-+B?YG+DR1!#33]*/On=|6E!+;( h9 ;M52*=.O;27#"&'&/32>54&#"&546326326325!5!##4&#"4&#|/'-@ %CY&_:&: M$B0d/'V":0/+&+VN3@'." %*801+"[p!H9!(# $<#$*9+K4''0+ 33Y&&3)J.FJ%25&/32>54&#"&54632&'#4&#.#"##"&5463265!"z_:&: M$B0d/6lY&&2-+"#! .;J(2( Oc09!(# $<#$*9+KZ9o2[p!*7-  B"07,+?033W~P[g2533##&#"32?632'#".'32654&'#"&=#5!#26754>4632#"654#"+1;K-2AB3S^*= # ,&D(#p[=b7'/Z4(-CP4 %  Y&7, "b,!/0HP3 `:  7(>'WjIe[&$[H4 7!5+33" b (< 2i^ 7hT.,39.#'67.+"'675!5!##"'72>54&+#/P+!AU(w2zw9F0/")( \23)L Kf!*0D,4U6`*.33.(L1#B%'7L2JX/W)  +{1.#'675&#"32632'7674#"#"&54632533##yQ,!LeV^!F O",E**8+ N  WC9M.3CC3 *0L2o7#)"!E( 6)%1^5HM3N.,5.#'67./7##"&547#5!##27#OQ,!ES"q=N&HO=3& !&')3  J *0E-0B (R8,33SE $ #a0    U.#,%.#'7&'#5!!#'2>54.'$  JP,! c=2-* 33#bDLgC   >!)033=&(4GYi .EP&'#5.#'67&+532654./#"&=#5!!;26722654'#"'\# J3%80?R/."3O+!B]}GVx _104*= "12 NC1      <(3/)$3#'0F/U4[2@].33 @ZK&7a.M.#'67&547#".'32?#"&'&5332654'#".547#5!!32?#Q,!DY #0C( $*%*`4H3m9"@(1'2KzK9# 3!)0F../]X ! B!-"$,!@Rx+F! c6'$&1/a1337'!>_;HTZ'@7523!32654#"'632#5./7.5#5!654'"&5473G$B>.I/;d&!"-33  !&d[[3G!*a!#/%l32533##dQ,!MdY$-H, )F>)3& 3__3 *0L2c+w4<>3^+1:('c3.U.6:%.#'67&'&'&/332654&#"&54>32#5!`JQ,!-303XYBLF57 + 8&Kc503v  >!)0."V\IFa!#'" '}]Ai33[3833##5.#'67./67654&#"32727+"&54624\%jI4QQ4P,  (V~Q2JL8P)YX3`3 *0@*=@ 8<"*$3- 2DJe&.&,"##5!!632#7. /6754&5'g3HLP12  .cRUK>}* 33p   +R#E 9&-5 .5:5%"75!##5.'#5./7&54632&7675#0{&]#yg3C3GkMp^),*' yU-J1&3gw: O4  2 DJ3X/{ VGK)[F=8Y323>D'@s$k>j3  !$z 94 ))&)H2 #/">33-&5"*!L, !2% ,,.$,767.#"&54>325!5!##5.#7JfA&'"!4#4,b3P3Q,# KEN0?C , '&D/F"f333 *q    s%3.#'6754/.''654&#"'>32533##2767&#&#"P*!Md $%.# n6F3VV3 # 3#2$ ?"(0N0F. "&!$ $1PE3 ,+2&(!. ).5.+'675!5!##5.'#675s.{%]U9!~f4D3H''%3on: M6  1.6b++33X/z $X,JL+YA.=A%4632&#"3267#5.#'67.'32654'#"&'5!  J)4! K%3!%(#2M,!'*:_B*9%T,P"5R3OXG   #00:2G #!9 &R3!)0) LukDQ?3Z733.<7574'#32%#5!##5./67.#"327#"&54674G$Ex# '*:ea3  !'l$c +& #/&4,#)3309;"*#CG3*'(c .BGR.#".547326325./67.#";27"#"&54674'#5!##574'#32^;V%B6+@D7.4  !:Y$c  ,+ "a33kH#H{#  V:g' D5'/ /:, +1!*/7G3*#(-3309B.T"0)4, K 9h.',5.#'6754&#".547.'5#5!##'5x=79.S+!Jg`#B2U=4>NB93hO/82N +0L1C+P,L/&!"33m+}. J%55>754&#".547.'5#5!##5.#"#".54732?6325.#O/=79.0O2`#B2TB. ALG83B!6/E#-DH (47R,+}73N27 C+O,G1$$"335',.%0 .<)*+, .6.#'6754&#"4&#;27#"&46326325!5!##Q+!Md@'." 0&;GG;1.+&S.N35 *0O/QY&&3))90QR0+ 33;76754&#"327#"'#"'732654&#"5263533##5.#IhP%!, <" 4#I5N3QQ3Q,EM1s/6 +:')01=8i_  $W33 *&.*2757#5./67&'#"&'673265#5!!3254'D'@33  !(kK$"<8 " 78$=#/#"<"*$B]3!'O/33$H8".&/7675.#"#"'73267.'5#5!##5.#6325Ih :(20A,- B_853Q,@5"'2EM12N4?4j" EK7"333 *84E. 75%5!##5.#&/32>54&#"&54632675.{%]Hf4H"^Y&_:&: M$B0d/3gnq: M6 133X66 [p!H9!(# $<#$*9+KQ5L+'.26%.#'67&/32>54&#"&54632&'#5!KJR+!EZu_:&: M$B0d/6lY&&3  >!)0F/X,9!(# $<#$*9+KZ9o2[p!*33_#".'#5.#"#"&547326325./>54'"&547326733#3274#"3"54632K+( 4CQLZ.EG 7 4686q4)$1( ,(54'"&547326733#67O/_4CQLZ.EG 7 4686q4)$1( ,(?>54#"&546#0%s/O;5SKI/ &@b8M 6$,E-/()"9$ &[OYY6#80B:[o ;$YkGO *"%Q[%`;+1># 3 .F{`"&'#5.#"#"&54732?632&#"32632'7674#"#"&54632533#3274#"3"54632KT)3CYM.DG28V^!F O",E**8+ N  WC9M.3  6 *>0#$,96G##6'W6), *;)*<7#)"!E( 6)%1^5HM3G,X'$;(<{{W%&=47#5.#"#"&54732?632&#"32632'7674#"#"&54632533#67=/^3CYM.DG28V^!F O",E**8+ N  WC9M.3@7CB _! 6'W6), *;)*<7#)"!E( 6)%1^5HM3#`%+{j"&'#4'5.#.#"""&54632632&#"32632'7674#"#"&54632533#3274#"3"54632KT)2- .$# .;C/1) (V^!F O",E**8+ N  WC9M.3  6 *>0#$,96G##   ,4/7,$F17#)"!E( 6)%1^5HM3G,X'$;(<+{{a%&=47#4'5.#.#"""&54632632&#"32632'7674#"#"&54632533#67=/^2- .$# .;C/1) (V^!F O",E**8+ N  WC9M.3@7CB _!    ,4/7,$F17#)"!E( 6)%1^5HM3#`%.|.#"#"&=7332632'&547#".'32?#"&'&5332654'#".547#5!!32?3274#"3"54632#".'A t"?O2)/!y '$#0C( $*%*`4H3m9"@(1'2K(K9#  )7 *>0#$,A4   --4XA ,F4 2H']X ! B!-"$,!@Rx+F! c6'$&1/a1337'!#3X'$;(<-,Q ,M6A.q.#"#"&=7332632'&547#".'32?#"&'&5332654'#".547#5!!32?67&/&=47A t"?O2)/!y '$#0C( $*%*`4H3m9"@(1'2KLK9#  ,8/ ]--4XA ,F4 2H']X ! B!-"$,!@Rx+F! c6'$&1/a1337'! #`% 1T .KE/! K5A.IM#"/#4#"#"&547372&#"327#"&546323274#"3"546327!5!L*: .\ =K, V'&w !)7.  5* >0#$,#97F/r]I4<*3$#m'$#*8. ) 4X'$;(93.<@%&/&=47#4#"#"&547372&#"327#"&5463267!5!b/ ^.\ =K, V'&w !)7. @7?D>G .KE/! r]I4<*3$#m'$#*8. :L3h#"/#5.#"#"&54732?6325'./67654&#"32727+"&546233#3274#"3"54632K+9 4CLZ.EG (46A1]A,p'@7) ? (V~Q]$iJ4 )6 *  >0#%,94I/5(M@*+ .;)+BF11 8;"*$3- 2DJ=7Q(ZX3 3X'$;(<{[%&=47#5.#"#"&54732?6325'./67654&#"32727+"&546233#67=/^4CLZ.EG (46A1]A,p'@7) ? (V~Q]$iJ4@7CB _! g5(M@*+ .;)+BF11 8;"*$3- 2DJ=7Q(ZX3#`%.B%&#"3262&'#"&4632654#"#".5467#5!!32>F$ $%3@-   (0M9<("/24:3;-,9 !+0&0\LD4WE BA9908?JV!?-:#&K22O&7(/(.T#"./4&5&'#"+".547;26;2&547'#5!!673274#"3"54632A4)  .(5 P '0!1-$ i#* d.e,  6 *>0#%+9,Q1C^P @*/:!3:jS2"33!F+X'$;(<.E%&=47&'#"+".547;26;2&547'#5!!6767S/[ .(5 P '0!1-$ i#* d.%e, +8CB b! 1C^P @*/:!3:gV2"33! #`%5W%"&547326325./7&54632&#"75!#3274#"3"54632"&'#5.#"yyUDUF`.EF 3 :FkMq_<+( $y$  6 *>0#$,KT)3D = $#& $k>j ,-)=52$.6&6 o& )6')H2 >33,'2 (!4/&QE'+H,' " Q !2!+,,.=74632'.#"&54>323>54'!5!#&#"327'#"&;,  :1 ))&)H2 $k>j(#%" 'o"*E(39- !2% ,,>33-&> S'(.+4'!5!#'7".#"&54>323>$k>jd3b:D  ))&)H2 >33-&1,G (J8g,, !2% ,,.G7467&#"&54>323>54'!5!##"73254'#5>54&#"3&80?' ))&)H2 $k>j!D"4ZIj0(Wp2 89,O*51]$? . !2% ,,>33-&($0426BGC) 1(%'4 Q.c#"./&'&'#"+".547;26;2&547'5&#"&46325!5!!673274#"3"5462$K*)  /'5P &/!24 i , d.<6!(9(5<fe, -7 *>0F-96G5AUY @*81!3:b[23vR3L33! 3X'$;(: .T%&=47'&'#"+".547;26;2&547'5&#"&46325!5!!6767/\ /*2 P(. 24l$) d.<6!(9(5<!%e, +8K: d! 4@UY @*81!3:b[23vR3L33!  sXf#"&'#5.#"#"&54732?6324/.''654&#"'>32533#3274#"3"54632%2767&#&#"A4*)3C  LZ.EG 46 $%.# n6F3 )7 * >0#$, # 3#2$ ?9,Q##5(M@!4 -<)+). "&!$ $1PE33X'$;);Q ,+2&(! }sM[%&=47#5.#"#"&54732?6324/.''654&#"'>32533#672767&#&#"?/W3C  LZ.EG 46 $%.# n6F3C- # 3#2$ ?CB _! o5(M@!4 -<)+). "&!$ $1PE3#`%# ,+2&(! scq#"&'#4'5&#.#"""""&5463263254/.''654&#"'>32533#3274#"3"54632%2767&#&#"A4*)2,'(#-0#$, # 3#2$ ?9,Q## = &*/6-$F1. "&!$ $1PE33X'$;);Q ,+2&(! sXf%&=47#4'5&#.#"""""&5463263254/.''654&#"'>32533#672767&#&#"D/\2,'(#-0#$,A4))4#:/ #*.) H''2" H,6`-+333X'$;(;.,Q"!5 ;'+* (&3{$`!k.?H726325.+1'675!5!#67&=47#5.##".547  8"zqA1/Y4#:/ #*.) H''2" H,6`-+33 #`% 1T d! p5 ;'+* (&3{$`!N.Q5#"&'#4&#.#"#"&46326325.#'675!5!#3274#"3"54632g~E0oA4*)2- 1%$ .;J(3' *9!~g  !6 *>0#$,:$e8,Q##<)  +607X>0,.6b++33G-X'$;(<+.GP%&=47#4&#.#"""##"&46326325.+'675!5!#67/]2- 1%$.;J(3' *9!~D"TH''%1T d! <)  +607X>0,.6b++33 #`%$X,.RV#"/#.#"##".547;725#"&54632&#"32?3274#"3"54632%5!A49- -!  9;$1C1*m7aD0;3% 9/:81 )6 *  >0#%+79,Q/#"J5214B "U>0!+$+ 38883X'$;(:33.EI%&=47#.#"##".547;725#"&54632&#"32?675!n/^- -!  9;$1C1*m7aD0;3% 9/:81A6zň1T d! #"J5214B "U>0!+$+ 388E-Y"f33=.PW#"&'#5.#"#"&5473263254&#"#"&547.'5#5!#3274#"3"54632%5L)(*3C YM.EF :56`#/*U)D AML  !6 *>0#$, ?59.97F##;5(W6+* *;) +5+P,],%""33G-X'$;(<73N={.FM%&=47#5.#"#"&5473263254&#"#"&547.'5#5!#675=/[3C YM.EF :56`#/*U)D AML?5T?59.CB _! 5(W6+* *;) +5+P,],%""33#`%f73Nsx"&'#5.#"#"&54732?6324/.''654&#"#".#";27""#"&546754'#5!>32533#3274#"3"54632%2767&#&#"4'#32KT)3E)LZ.EF46 $%!/,#G7 *& eM#F3  -7 *>0#$,3#2$ ?[# '*:96G##5(M@%0 ,<)+). "' $":))3+'!-33 %E3 3X'$;(9O)!2&( 64,#)sl{%&=47#5.#"#"&547326324/.''654&#"#".#";27""#"&546754'#5!>32533#672767&#&#"4'#32/W3F6YM-DH :38 $% /,#G7 *& eK#F3E+3#1% !?Z# '*:1T d! o5)W6%0 ,<) +) * "' $":))3+'!-33 %E3#)!2&(!54,#)O..4@#"&'#5.+"'675!5!#3274#"3"5463254632#"&6A4*)3>$h )7 *>0#$,09twF&9,Q##Y/4^6&333X'$;(<- \'"'.#)5%&=47#5.+"'675!5!#67%54632#"&/X3>$@B/~9twF&CB _! Y/4^6&33#`% \'"y.%#".'732654'#"'732>54&'.#"632'>54##"&54632754&#".#"3254'7#"&546326325!5!# P+@uL.3Sz7%  #;%!, 4B  H"%!/+3''=. 1-=C0; .d?1d'  ):X}r% Ef$ - @I'D/   "C  "IDF(D!/&9-%C 1 F33DaJ[#"'732654+563632>76?67#"&'732654'#"'673264'.'32>7#"&(  `   M%%- )E$M9m%2Q!?"  '1"    1 G,A#=! ~"Y/Tj&/ *,d%P   ( + o#"&'#5.#"#"&547326324&#"327#"'#"'7;>54&#"56;263533#3274#"3"54632A4*)3E  YM.EF 2 56P%!, <" 4#I5N3  !6 *>0#$,9,Q##5)W6), .<) +Ms/6 +:')-1=8k_  $W3G-X'$;(<c%&=47#5.#"#"&547326324&#"327#"'#"'7;>54&#"56;263533#67J/V3E  YM.EF 2 56P%!, <" 4#I5N3C,>G ^! o5)W6), .<) +Ms/6 +:')-1=8k_  $W3x#"&'#4'5&#.#"""&546326324&#"327#"'#"'7;>54&#"56;263533#3274#"3"54632A4*)2- .$# .;C/1) (P%!, <" 4#I5N3  !6 *>0#$,9,Q## > ,4/7,$F1s/6 +:')-1=8k_  $W3G-X'$;(<n%&=47#4'5&#.#"""&546326324&#"327#"'#"'7;>54&#"56;263533#67R/]2- .$# .;C/1) (P%!, <" 4#I5N3A5CB _!  > ,4/7,$F1s/6 +:')-1=8k_  $W3#`%serz#".'#5.#"#"&54732?6324/.''654.#"'#".547#5!632533#3274#"3"54632%2767&#&#"327'654.'"767#K*) 3F7LZ.EG  38$%-X* $4F3  6 *>0#$,0)3#1% ?1b1 '96G5(M@!4 .<)+). "'  )7 ,K$3E3 1'X'$;(0)3#1% ?1b1 'CB b! r5(M@!4 .<)+). "'  )7 ,K$3E3#`%#(82&(E%Y(  . FP73267&#"&'#"&54632765'&5&'#"&'6?32654'4'#5!!3254'"'(# 8-$A71%-7'9X)"<6  $78$=  ""1 7);(&HM'-"$-' "! j3  @*33$H8$1.9C74632'.'#"&'6732654'#5!#&#"327'#"&3254'"':. #7"<8 #Zz%##" 'o:3#8$=&42.'3!E# 33" 4 X6G$H8$6.%/?&'#"&'6?32654'4'#5!#3254'"'͑Y)"<8  $7W)!d8$=j k3  @*33D6,G~8$H8$.MW#".'73254'#5>54&#"3&5467&'#"&'6?32654'4'#5!!3254'"'#."4ZI.PDM0)Wp278-F )2]17/"<8  $78$="/ ,527B%AYD) 1%'4NG >3  @*33$H8$\.Yb#"&'#5.#"#"&54732?632.#"#"'73267.'5#5!#3274#"3"54632%6325A4*)3C  LZ.EF29 :(20A,- B_8 )7 *>0#$,@5"'29,Q##Z6'M@*+ ';)* N4?4j" EK7"333X'$;(<84E\.NW%&=47#5.#"#"&54732?632.#"#"'73267.'5#5!#676325U/[3C  LZ.EF29 :(20A,- B_8c@5"'21T _! 36'M@*+ ';)* N4?4j" EK7"33#`%f84E;su"&'#5.#"#"&54732?6324/.''654.#"'#".'#"&'6?32654'4'#5!632533#3274#"3"54632%2767&#&#"'3254'"'3#6"KT)3E  LZ.EF 46 $%-44 "<8  $7!F3  6 *>0#$,0)3#2$ >8$=`' 96G##5(M@%0 .<)+). "'  )(3  @*3E3G,X'$;(8$=`' >G ^! p5(M@%0 .<)+). "'  )(3  @*3E3 (82&(($H8$6s#"/#4'5.#.#"""&5463263254&/&''654&#"'#".'#"&'6?32654'4'#5!632533#32>74#"3"54632%2767&#&#"'3254'"'3#6K*; 2-1$# -0#$, # 3#2$ !?8$=Z! 96G/ % +5/6-$F1'"),%(3  @*3 E3F,'#'$;(9O ,+2&(!+$H8$  sx%&/&=47#4'5.#.#"""&5463263254&/&''654&#"'#".'#"&'6?32654'4'#5!632533#672767&#&#"'3254'"'3#6/ W2-1$# -0#$,@5"'29,Q##:*  +6"06-+?0N4?4j" EK7"333X'$;(<84E.Zc%&/&=47#4&#.#"""##"&546326325.#"#"'73267.'5#5!#676325X/ ^2- 1#!-54''4&#"3&546327%5!w=3+!*W "h~G29S&+H$+#!&A[G6H&B225GDi&R $j|"F"!0&8%`'2-$02T5FKV!_33!.6:%47""./32>54&#"&54>32632'&5!  o\&D?[>&: L%/,#<(6k :+N@/L$Xs!U?!(# $=4!3 "*W6 39OW ,33N.,08#"&'#5.+'675!5!#3274#"3"54632%'5A4*)39!~g  !6 *>0#$,9V$ %9,Q##Y.6b++33G-X'$;(<7@>e X,&.!%-%&=47#5.+'675!5!#67''/X39!~?B/9V$ %>G _! Y.6b++33#`%@>e X,N.,4=#"&'#5.+'675!5!#3274#"3"54632.'75A4*)39!~g  !6 *>0#$,`:,n6H''%9,Q##Y.6b++33G-X'$;(<&Q . I7Q$X,)."*3%&=47#5.+'675!5!#67.'7/[39!~B?5`:,n6H''%CB _! Y.6b++33#`%o&Q . I7Q$X,.MQ#"&'#5.#"#".54732?6325.+'675!5!#3274#"3"54632%A4*)3C  /D#-DG 19:!~o 6 *>0#$,9V9,Q##4&,-&- /;(*%-6b++33X'$;(<7@.AEM%&=47#5.#"#".54732?6325.+'675!5!#67'A/]3C  /D#-DG 19:!~o6@9V$ %1T d! 4&,-&- /;(*%-6b++33 #`%@>e X,,T.*1'7675!5!##"'72>54&+#5.+"7zw9F0/")( \23@){({`*.33.(L1#B%'7LfX/JX/,w/'733##5./>4'"&5473267ZW$Ld3TT3:3:*4(%1( &'={({R5FaV3R,1-45)*%/DJ+,t{.'72533##&#"32632'7674#"#"&546WQ9M.3CC3V^!F O",E**8+ N  W{({5HM3`7#)"!E( 6)%1^,Q.*'727#'47#5!##5./7##"&3a& !&')3fO=3JN&H{({? $ #a0 133AAZ (R8 ,2 <F'7>32#"'326?#".'732654'#"547&'7&#"325k I4M.',59(#)\H>g@&3"5R0<3% =!o#>(5<$U{({TE)+) W<P-JcM|B @ykAE.0,"4!W>&1 5,. "'7%2>54.'&'#5!!#̦#bDLgc=2-* W{/{({Yi 33=&(0^,.9D'7.+532654./#"&=#5!!;26722654'#"'| %80?R/."C`z;GVx _104*= "12 NC1 {({ <(3/)$7='4[2@].33 @ZK&,.I'7".547#5!!32?&547#".'32?#"&'&5332654'zQ'2KzK9# 0 #0C( $*%*`4H3m9"@({({1/a1337'!>_;HTjN]X ! B!-"$,!@Rx+F! c6'$&,"+'7675!5!33##5'&'#5.+"7qpz63VV364?#q#){({a,+3@X3=$W2;V1,:H^'7#"&'#4&#&+&547;636;24&#""327#".546327>32'4&#"2654&#"53260g*\3B^#H}/69 ":, %n  "#! *5'2*,=35a.W83%'/{({40gBL3E9 C<9*#S x $ $>Ba1>-44'xE67!,3%,'5'723!32654#"'632#"&5#5!654'"&5473|6A/;d&!"-:c4,'d[[3G!*a{({\F636D$"1'6vS?'O3 2 ?/) .,T(4'73##"&54>7>54'#53&5472654'|/2Ư2@fHFV2 ʬP6&(:4I<"G0{({,N73Pr^HZD N=%3]G54#"'62|,'x 35% *>.9d{({?'33q 6Y1&"!=8[?,]('7327+"&54>32533##4&#"@, )F>)3& 3__3Y$-H{({5>3^+1:('c3W+w4,U.)-'7&54>32#"&'&5/332654&#"%5! + 8&KceT73XYBLF57v{({ '" '}]\|V\IFa!w33,]4'733##5./67654&#"32727+"&5462@\%jI4QQ4";,C&,n'B7)!> (V~Q{({S8P)YX3I% 58<"*$3- 2DJ,-.'7'&547'#5!!67d.Oe,.{({Lcb[2"33!Q~UY-,:5")'7"753##5./7&54632&U#yR3GkMp^),*' yU-J1&{({ DJ3VGK)[F=8Y325!5!##5.#"04#4,b3P3"4'"{({0&D/F"f33.:& ,  ,{s"0'7>32533##4/.''654&#"72767&#&#"] n6F3VV3 $%.% # 3#2$ ?{({O1PE3:. "&!$ 9 ,+2&(!,Y..'73254&##5./67&'#5!!6;2#"'q I<38b($fNr3>(69vK Zr8{({ #1?ZFA_ %M#6@33*8&1,'aNaP'% , .'7.+'675!5!##9!~A3H''%{({.6b++33$X,,A./3'74632&#"3267#".'32654'#"&'5!)4! K%3!%`K9bC:"9%T,P"5R3OX{({$#00):2G #!9 @s1Jm_:Q?3Z733].%'7.'5#5!##54&#"#"&5475@h>NB93`#B2U)D>=79.{({:&!"33C+P,],82N,J.%'7670/.+5!##5.'-9Y KU?3)9?Y7;G&4{({9C 233LCR F7""5j, .''7675!5!##5.+"754632#"&G3>09twF&{({^6&33Y/, \'",.3'7;27#"&46326325!5!##4&#"4&#c0&;GG;1.+&S.N3@'." {({)90QR0+ 33Y&&3),<'7#"'7;>54&#"56;263533##4&#"327#"q1 4#I5N3QQ3P%!, <{({')-1=8k_  $W3^s/6 +,D.'7&'#5!##5./6&'5&r-~"F3BdE*I;0|UL{({C#A33@]G:%3(?+7qc{,. ,'76325.'5#5!##5.#"#"'7326q@5"'2 B_853 :(20A,-{({84EK7"33N4?4j" E,.#''72&/32>54&#"&546'5!|6lY&_:&: M$B0d{({Z9o2[p!H9!(# $<#$*9+K33,J.3>'7"&=#5!!>7#".'32>54'4632#"&-%. R(4wY=a9'3.EN#)=#(0 {({6(33X(p"YoIg]$TY2$6-2(+4 ,.!/'7%"&5#5!!>54#"'6324632#".#4Z.M#"(Lj!- {({/733h]d# "mL_-!,9.!-'7&+5!##5&/674632#"&xeU I3C,=,E$Q(/(" !%&{({K]23Lp/8B*5G# "fX '&,#. '7'.+'675!5!##9V$ %9!~A3{({I@>e X,X.6b++33, . %'7%.'77.+'675!5!##`:,n69!~A3H''%{({&Q . I7 .6b++33$X,W.=A'&54632>32'654&#"#5&#"#".5473263254#"753$i.$. +"0$"531;V$6 ()R 3)%Cͫ #3g;- 9+#B2* "2+)A(2  /H33g/2#".'73254'#5>54&#"3&54>5e"4YJ.PDM0(Wp2 88-F )2]$5)g9)327B%AYC) 1%'4N!1 a,"2#".'732654&#"&546s0A\G+N8,3 2L*+>*+3,M9AM*?L?DN64*!.BaR*%&54632#5&#"#"&5473263254#"]*34'2  )M.,#1',7)^6&)# C4$6 3+"(= *%&54632#5&#"#"&5473763254#"f1<31; :I,8'/='$C 2f142*W.2: ;(10I <.#4'!5!##".#"&54>323>$k>j6:D  ))&)H2 >33-&"E,, !2% ,,R&#"&546323#YId6(KE#3M.33e7 /J'69:~'.#'673#bF(B,D<Md33 ) 2L/0/N0!'56753#5.'I"5v0{33  !&, )L!!n?.'25#"&54632"2?#5&#""547326!'# '4##>U$-- ]_sX'. 0',/#Fk'.##"&54732?6323#R    %,1D.*"$ B 33`   O90% %(1  }*"463263253#4&#."##"K'3' (32-+F% .BX=0e7- C",/P#".'732654'""""'732654&'.#"632'>54##"&54632&25F43aF83 T$   #7$%%*+3 < G#&5Y2 !#@:WfXq# -(>IL. ".B  !J)cr23####5.#"#"&547326324&#"327#"'#"'7;>54&#"56;26354&#"&5463.#"B4]XGJJ33E  YM.EF 2 56P%!, <" 4#I5N6?HHiϑf,[TMG )7eE3b5)W6), .<) +Ms/6 +:')-1=8k_  $z"$%HSu$#!.'26=33265#5!##"'67%30" C3X 64YA &)+""*2 FHm33:Q'P w.5%25!32>3247#5!##"'6732>54#"#".M,/'04?;mwXbpYB2 &@28K %'3:P%7(/(D4=?S633]dP$CJV?@ ;.$/726=&#"&46325!5!##"'>73265(<6!(8)5<;X65YB %0"B3\(evR3L33:Q'P*2 GG:.>I%26=4&#"4&#;27#"&46326325!5!##"'7673265M%@'." 0&;GG;1.+&SW 64YB & {0"C2)+Y&&3))90QR0+ 33:Q'P  q*2 GGmEP%26=4&#"327#"'#"'7;>54&#"56;263533##"'673265P%P%!, <" 4#I5N3X 64YB &0"C2)+s/6 +:')-1=8k_  $W3:Q'P q*2 GGm. (,7%5./67'&'#5!##"'?3265&'533265 ,$<!!E7.(X 74YA '(90`G8p3/#C30.,8(33;Q'P ' 3$  \g|+2FHJ.1;H%26=#".'#"&'6?32654'4'#5!##"'>?3254'"'33265Z%4 "<8  $W 65YB G8$=90"C2\+ (3  @*33:Q'P$H8$*4DJ:.-6A%26=.#"#"'73267.'5#5!##"'?632533265U% :(20A,- B_8X 64YA  @5"'230"C3*,N4?4j" EK7"33;Q'P 84Eq*2 GHl^.4#"'&/32>54&#"&54632326=!5!# 65.#Y&_:&: M$B0d/6l8 C3wWt;Q' [p!H9!(# $<#$*9+KZ9D-GH33K.+%>54#"#".547#5!!32>327028K %';m-/'04?  KAhV?@ ;)T622S%7(/(D4+'#I$923#32654'7#"&54>54#!5!4#"#"&547326E5g~2HH28,u#218#9+01S1'$T>CL3F31B 6 5c"274'7&'#"&5463267&#"5 2  "(0M9<("/"& " $-"BA99:Q. $#"./&'73274#"3"5462%5!K*) "  -6 *>0F-$96G 4 3X'$;(:33.7&=47'6753S/c F@m1T d! 3f33.'6325#.'5#5!##5.#"#"'7326!)*03U/+4/"*)6% &9i;(33bA,4,Z 9 a.$6325.'5#5!##5.#"#"'7326`# #$I'o3)!("+ 0/gn+33<699I *Q.#632.#"#"'73267.'5#5!#!)/"*)6% &3U/b*09RA,4,Z 9;(338e #53'46;5!5!##"327#"'&eSS$g68z)5,@7_3DV{jMNOMmpIIL(/I#V*tGGe'#5373264'7#"'&546;5!5!##"_SS);5EC96LxSkEI]Jl$ O)C$3n'1*iR^FI\Ie}II#F%"3267#"&5467'#5'%&'"'>325!5!'&54>32'"!!7'432+M(!'88OCF5(H+b"+SG?*.,8)D $Q|B-&#=8N50C )=ϛ9lM7 SDFH9H5$2 ; % JWH5J* -D_%"3267#"32>7#".547&5467'#5'%&'"'>325!5!'&54>32'"!!7'432+M  "88O#7( &)B=<& +5(H+b"+RH?*.+8)D $Q|B-& =8% <*7#5$%>0C )=ϛ9lM7 SCGH9H5$2 ; % JWH5J* -iL#53&54632'""#"&546;5!5!##"32632.'"'46322674&aaXN4*D!$R*O -s ZG i"0d-'D",,  )NZkM62 ;# F_v4`IIP; ,R?8"-[  8+|KZt#532653#"&53#'7DD%!'/D31E, $K=Ar$2??2 ;UG#532653"&53###53'73CC%-/FbE-4 MHM==Y@r$3?A1%H:L,043###53%7'7&54>212653"&537#533'=R7LMGM31c>L!&!-/FbE-3;CC'e%>BUHH7#*$3?A1%2@>5LJ'+'"#&'#%7'7&54>322653#"&537#53=D $R:- ?1c>O,7+,/D31E-*DCC$ %IY>J7#%1 $2??2&2@&Kk%#532653#"&53&54>32&'"7DD%!'/E21F- $wY 1#*4 #*(@r$2?@1 ZsI, : & #T*e9'3'#"'&547&546;5!5!#!"632'674&#"~ WY &fAK#K&:;8" ;,=0&`II ; 6&  .! > e *@#53'3.547#5!##"&54654&#"&5475!"327"2USS W*8#P @cs6Ua."MV#C"$!?Q|Z)O R_5<IIp5(DwM-iOMOaEptKO%tQ&37#"32>7#".'47&543254&#".54oF: K?184H&#1! #3;=8( &)D<9%:/\"8p'@43F>7^HH8v#  L2' <*7#+=h=.Ma;"R"0'>aW<D23275#53###"'3274'4632.'#".547&54632'4#"6*SFeFGKNF>&)b  *&2@,61/eAH5=DG:587x%zHHO /O"* )-.4!@*HC1C3;C/59'1&"W8<463!5#53##!"32?2'#"54632327654#""&%!5!+/MMI{@D!'5$a5{w&'; 0!7kEy]HHr63, $X,tB .# rHg%.547#5!5#!5!53###"=[PMBMI)6T8 M84HoHGa4($w.Xg!5!53##5'%&'"'>325#vvNG+b"+,MHG՜9lM7 Se2;463273267#5!##5#"&547'7''67.74#"6dA58GF\S*#: ^(FG"`/6A)P"U&-8.$10880D4J'?9;8,X5EHHm38B6&"UAx7'9 D* ++(xe"+4>3275#53##5'7''67&74#"6w*27GE32&#"4#"6v AMFI   .9'M= B -=*27GEd'-#*,1*9- 1Z"HHC  >*Z.+  9F;7&29/E3EF1"%2++ * e-#53'-32654.'5!5!5!####".5 >>-"C =  GG# :(:i<#D8Y!$%# qHHc(6./XrX e13275#"&'5#5!!632'>54&#"#5'%p;,78jKe)E@(C 8>E!S3   G,!;I"//U@HHA5E "pB.k.'&  7"e153275#"&'5#5!!632'>54&#"#5'%#53p;,78jKe)E@(C vE!S1   G,!",III"//U@HHA5E U.k.(%  7HEe7275#"&'5#5!##5'%5;4;/Ke)GG-")7"/GU@HHだ7&e&.##'7&#"#"'.'732=!5!25!6FG2ZI% A2 4_%11 @_9 G<Cl75M $/Ju&58%X!',(.H24eoe,!632'654&#"#"'.'732=!5!o#)<\8/7X1 ,+ ?- 41%,8M9 G<oqS84X/1K=&',+5(6!N$p',(.He=#"'.'732=!5!!3632'>74&#".547B; /[,11 @_9 G<z$:\B=.!)2IBq158%X!',(.HHmUbk' !,'N$0>Z*e;7267!5!!632'>54&#"#5#"&54?.54672'&#""H8H6ZwC!Q/4FG% q@/.XEJ',06*HHNX<]|/g0 ,tM? A )M+Ab N 4$'<e"L"&54?&'&5732=#5!##5#"'32675#"&54>32&'"32675^pR\; .-MG  O% 5W4%+7C;L"2*6K( Aq?*$!< ","-HHx + 4='A#31D8&8 E4 "?e #7"&'47%.547#5!##5#".547"327!;p%<D?FG=K:0G 8,)Sx'#K)G>)OM? A5&HH # 0%: '#NI]e,0747#5!#32675!5!##5#"&'4?#"&75CaW%;(3f#0]FG py3N5C;GGQW@HHzM !>*:A Ue.7267!5!#632&#"#5#"'&5467672'&#""HF7D")4FGI*?36-DJ')?*HHMX t#-1B$B1 N  #&9*qe<#"&54>32&'"3267#".'&57326=#5!!#3267q7C;L"2*6K2 A?7^@7#; .J65W4&+1D8&8 E3#"<#4QJ0; ","HH224='@$e##"'&54735#5!L!45'9456Hye'"32767#".547.547#5!#; 8+'6J8yM0G 6,*Dd'#K)B'%%TF 0%:- R!&HHIze!5!47#5!#32767#"'&}CaW%;&039$ ~>4'&HC;GGQ*.I{Q"!e-32654/5!5!5!##5#"&'47%5##"'&5c = **GG&p # 9(`Y!0>7 qHH]N?=*(6//@t&e!5!32654.'5!##"'&5, =   #C4`HY!$%# H(6"=3?ze=72654#"#"&546;5!5!##"263227*"'&'"'4632)M#K,p#\@(W%4 &`+Mh!)'S6+3.^HHJ'Qz+N?-oX"Pe,!5!632327#".'#"'&'7232654&#"BG+FU!"#   bG47'2/V(469/3H$IMLF[&9/=,/H (e'463!5!5!###"&'4?5#""'&).8 f(GG]& r!M?1L\>1HfHH,@ ?)_@^O4^s)e@%2>75#53##57#"&'4?#".'3254&#"37".54632)+8=eGG32#ry3bA(*-7)B:-= U9HZ- 8(HH*OM= ?) :.CG x*5-/C&#0=`DAe/46;5!5!##"34'432#'#"&'4?".)B4G)B+ N_ rFl>& -=U^HH+ B001c9>6?) e(92'CgG"&'4?#".547.54632#4.#"6;"3275#53##57p+.,@ 5,3K)-EH  #(6"9.Cs`FG&&I G=)J.% 21T)1GI& ',$DRoHH##OMje ##"&=#5!26=#j#P>H iH&GV s.e7#53!5!463!##"&eek>H i.cH&GV sJe27#"&=#5!#>5*FKf6;;X*U@HH"/1e 7"&=#5!#632&#"#5'3275#Kf)GD/43G.;,78U@HH>L &/""//uXe%!5!0#"&54632&#">7'27'R EgYCJ9!  8+&HDZGEc N},4s)e$#534&'46323##"&546;54#"11 J2#$_ (+0H' 38 *Ga o !2. "qe46;5#5!#3##"'& (\|/0% HHGO7:e327#"&'22654'#5!#=6 <'RI>Q^~)57Cl'/W!)Ud7v`,"6$HH+e 7275#"&'5#5!##5#"&'4?;4;/Jf)GG%r7"/GU@HHN?@)[e43275#"&'5#5!!632'>54&"#5#"&'47%5p;,78jJf)E@(C vE T7.GG r .I"//U@HHA5E U.h1)$>vM6?) "e !5!".54632>7"'67&'"Zʏ4$JN9aJX@%<+)g A 1*+HH:=(Ad2E]F%Gj4!+.+Fbe.:!5!>32#"'#"&5463232>54&#"#.#"32>N'ER ?,\&'^ESXEVO:* #0  G3(SS( H"/aR;:$W\gSJi'L% <:  )@js+be2>#53!5!>32#"'#"&5463232>54&#"#.#"32>#bbN'ER?,\&'^ESXEVO:* "1  G3(SS( CYH"/aR;:$W\gSKh'L% =9  )@js+uXe!5!267#"&54632'&#"3'R (+0EgYCJ')?HW/UZGEc N  #&9e)#%72>7#".5467#5!&#"32>V+wN0,.'N>27+)_nB&' 6DT24'B,) /* HHLL&0  Je 7"&=#53#'327'Kf)u*;,<$ǥU@HH{*"/)e "'&/732=!5!#3#B#D"D1V[9 F<J*0S& 5U',(.HHGQe-3!!3. 54>7.543!5!5!#!"w+zC0E<5#%" 'Q32"&'46?&/"'"#".#&}}]\#9,06 p%?$ */$H 9@;l8/oI D!21 e-%'32##5#"&'4?#"&54632&#"?!5!7+"FG&rEcZB G9!  s*7U]E?>) Kf=Ec M} H2e<4&'463235#53##5#"&'47%5##"&546;54#" J20"eFG  p_ (,1 ' 390HHI   >( ~(a o 2.e $"&'47%46;5#5!####"&%5#r & (\FH_^3@)NN?X HHOnFe+##5#"&'4?#"&54632'&#"327!5!FG&rEcYC G'& :,C;]E??) Jf=Fb M  +6;Hbh 94#">"&'47%.''7&547>325#53##59.5 8q 4u.C$ 11"%:d\MFG&(#'    ?*Q H9c4B3 +7>.HHM@ e8ES2675!5!!632'>54&#"#5#"&547&54672&#"%654.#"6322675"'#G9G9W54vC"P-!5EG1MFg87XD 7+6&370"! 5E7C#G-N2$HHoGP58C):Xm+[,&hi0W9:>*A:[ F.$'/8' h}G$- ,"09yeZ#533254#"#"&546;5!5!##3262632#'2674'"#5#"&54632&#"3275.'7xOO`1o 7"4v%Iyz'<U8&W=%<3HP:.49CG40;J^&" 21200aP41MKV NISd+ ^HHK5?Q R%=*] T,# (  7K'SHH$%eP!/AYW$3X24F# ]&c6:'" 9 nxe48%#"'#"&54735&'73274&#"#"'&546;5!5!#!2632##53G@+'j&:f(0ig J*/41Gki6T :#HOOq 6lU.;6{.#-19+ZHHIsKmxe5H#53%46;5!5!#!2632#5#"'&'47.547&'73254'"#"&"3275#"';xOO=1Gki6 U ;"H|>E4$%/40ih!J-`o<O~e:%4'5%5#"'#"'732654#"'6323275!5!##5#"'732FP("^B_e/`036e-0U%3 O[JPQ5+7-u7"GH HHi+*4D81889e1H"34'432#'#"&547#"&546?&546;5!5!#32?3267'"'A(C#+J`%wDG< ,L6'5B4sG>E-BGS" "7\-B Ex*!B)0o%JhJ0!G1,T=U^HH< %*1 + dS0f&e0747&54;5!5!##"632&#"327432'#"M"FdK( # ##+?K A %;?F('(<\THH ' J)IJ31g#y)}eQ"34'432#'#".547".54632'&#"3>33267'".546;5!5!#O(B+ ')Ta&' %P:A-< > l ;.;^. Xz:A6u<x*!B/0"TY7 0!%H*.@#&;  +L@,eo &A>#327'".546;5!5!# (B+ 13Z&.:r";",A  /'8FXz;A6=KAx*!B00"TXQ_N +# a' "3 1ua%&@>$f7* !>^HHi fe.7"&547&546;5!5!##57##"672'"327FZLMLVGGMe -EFd/)G\#F95O6(^HH=25# HD$;e'3"34'432#'#"&547&546;5!5!#'&#"326)B+ 11S H<;F/B4G"$'$&/%<x+ B00[\3T.A(,=U^HH{N#%.8,e0##"327#"327#".546;5&'&5476;5!5!,x(5]1A).+2]1AVu=uN`9,jIM315,#Y:3'!Z;3N(L/3Q:./D7(&^H9,eA4>;5#".546;5!5!##"327#"32674.'7#"&)'91 ;oFa8,x(5]1A MUUPJ;9 EV2Y%#5 A+L+5P^HH#Y:39*. + +8Y9eP4>;5!5!##"32674.'7#"32674.'7#"&54>;5"".)(;/h!&P5 9$%DUJ;9 CY1Y'91DU:B%5 ^HHX ,%%;!9X + )8YI#5 M Fnke4%#"'#"&54735&'73254&#"#"'&546;5!5!#!2632#G@+'j&:f(0ih J*/41Gki6T :#Hq 6lU.;6{.#-19+ZHHIs9VeT3254#""&546;5!5!##3262#"'323762#"'732654#""&547&'7Y`1o=4v%IVW'<U8&iO7:IM8& 'C)1`1/@=4vaC1B NISd+ ^HHK5>]A5'1( NI4, f-8Y 9VeNW46;5!5!##32?62#"&54632#"&546;5#"&'732654#""&>54#".IVW M8&1Yb@G(1JfL\SB#\R1`1:5=5u*1-XHH   5:'A2? $*.<'/BqR6D'j\  NI#N)#!e7!3##"&'4?35#3.54>7&43!5!5!###!"w(  \#B>Y5+A"DQ7"'4?&#"I,6!5J#9,4)8U=XK  $dAJz(y)=B0S:J5  "U 6 2% > )(&- ?#56#5j5:J;)^H)4s8$:Y-@P? ?_.0ieIR#!"632'654&#"#"&'#"&54>7467.546;5!5!&'26I,6!cL :-0:8U;XI  ^%A#7T  $FW+A? > L5  - #> $6 6"$!#PH! ?!#;)^H /6!:9e.7##5'7&'"'67&#""#"'&/732=!5!!632FG5DZCJ[  A2 "D!1V[9 G<Dl鱉) J N$/0S& 5U',(.HeVT+e=2>7#".5467&'&'&'".546;5!5!##"34'432#" '*E*Q=D7 Xz:B4G)B+GTD6 <*?+:E $ &A>#=U^HH+ B008U;6$  8e#5327#"'&'515%1D!A<HH Eye+!5!47#5!#3267#"&'#"&%#327>cCzW&:&6j |>+B ZK1O\%;&03()HC;GG R`@{R*E@Q*"Qe-5#%##5#"&54?#''&/732=!5!5FGB%t#D4_%1U\9 G<l @ ?) S%*Iu'5U',(.H4Z"#&54>323###53654'.#"\H' / )R=*MMHMOJ#!$f#A,0/FN!HH &/D5":\E$.#"#.54>323###5H8K 0 !H"E/AnIMMHMeDi"!,  32:MIHHI13###53654'.#"#.547632&54632&#"}MMHMPJ# 3J,$2j P7$0 6-A HH &.E.:&<#J&F 67 ;Pj+.#"#&54>32&54632'"3###5W8Ye+7E"I 55[>4[: D $QMHMg0A)>,.!$+? l=E558 ; $F\GG4R6#5357#"'&'76#&54676323###53654'.#"AA>(M P#323###5BBN(M :%#8K 0 !H"E/?nJMMHM"WdxE"3(&YDi"!, 32;MHHH9^%&#"3###53&54>32r!/ SMHMK &@)-%+*((a((-HH//2% '/2h#4'&#3###53&5467632O8FFSMHMKA7,06s^Y:f/O3(HH#(7^':J<f#'&#"3###53&5467632`tޝN=DSMHMK9>DSCfI "I'HH)1X)X5=2327#"'3274'4632.'#"&547&54632'4#"6*TIONF>bc  &2A- 9@UeBD9 '/;597(O >+O"',. /UEIC1C.@+7N&1"  e1!5!463!!"32?2'#"5463232>54#"".Ep&y>D!'a5{w&B 3/ .!0O$HG5 4+j!X+tC )#FL e!5!.547#5!#"B[P)6T8HM84HH4(#x.:e!5!'%&'"'>32vvR+bOJ+SLHt9m&'7 SCO#La(14632732?#"&547'7''6?&74#"6J-8FF\S*!:)D9"_/4D(OEU.7V<9,!1809/C4J'@96<-JCj38B4&%VAy6")9-:,,, +Ke"46327'7''6?.74#"6eA57HF<(ـHU.6U8. 10880D4j Q7^HH #v# L / h>-=''%|-Gm8Kk#53/"#'&54>32QQE #*(:+.)*+I< & #T*;I4* e37".547.543!5!5!#!"632'674''7&#"pw%1:<-@,:=9*@!>?&R9A<:-4;0(@c3;7M <^HH 9!B-WO+::(,"&Kk&54>32&'"Y 1#*4 #*(KsI, : & #T*!.#'#73^{aCҔ Q ###53'#532673#"&53MGN8YY1+5=]BC[<+(HV.*CTVA!, M '7###53[TZpMHM\T\H&547632&'"###53KX($9%0 $SVMHMfpI1 ; % JXIH'?'"#'&54>32###53C32&'"3###53654&#"#&eOJ= I/&4AMMHMOR= / /N)0W,(( ; I>%HH >N+;4 {1#534>32>32&'"3###53654&#"#& QQ%K2J= H.&0AOOGMPR= / /M)J)(0 ,)' ; I B%HH@L+;4|Kt#53#'7``FK=[;|K(t'"#'&54>32#'7(D $R9,-,7)tK=% % IY;H5$2 ;|K*t#53/"#'&547632#'7*QQC!%S9+*%$8%qK=K+ $G\:B=2 ;LL #53#%7'7``Z?1c>[ޚ7#LL='"#&'#%7'7&547632=DD3:+ !?1c>W %%8%$ Io5L7#"2 LL=#53#%7'7&54637"#/=bb?1c>MK&II2 ) :+[њ7#:B: Ca2 U&54>32'"###53'73Y 0$,C!%TMHM==YhsH + ;$H]KH:,!###53%7'7&547632&'"#&'MHMB1d>F '$:$5 $R9*(H7#1 ; % IY5.cLP"%#53'"3###53%7'7&54>7633'PRPI4 LMGM31c>L$%@'Lm CAVHH7#157eI##"327#'#7.547#'"'.'732=!5!#3>;5!5!x)4+?8^/BGb^{`4=.#D/[,13"BZ9 G<UN2,J'-E#S)bӓ&2P1%*Bq15;)Y#',(.HH=5LmHD #53/"3###53'73'&54>32PPD $P%MHM3=+-,8(I, $ HZHH:9I4%1 (3!%!!(?(_g14>325#!5!53##5"&'46?&/"'"#".#&(]\#9,01M}NG p%?$ */$| 9:HGi8/oI D!21  e<'3#5346;5!5!##3>32#".573254#""&WESS31> !& +'+#9=!4lRB$3=Wk'q H6y!BO# `II.3 I 4M'6MQ7.OS@p"#8e '346;5!5!##"327#"'&p Vg68z)5,@7_3DV{jMN!MmpIIL(0I#V*tGGe*'33254'7#"'.5476;5!5!##"M VV(2&96LxSkE102Fl$ !%;#j7'1*iR^F]4B52}II#ce5'346;5!5!##32?2#".573254#""& W{1>cY+R5+#9=!3mQC$3?Wj&q H6y!"!`II;5I 4M'6MQ7.PS@p"e*3'34>;5!5!##"&'4632#"'&>74#" VA+m)2YM>4#5 #:<hSR>&2."7@,hII>.?i%/5A-% )B)93e37267!5!!632'>54&#"#5'7.547672'&#""H8H6ZwC!Q/4FG-5?+-DJ')?*HHNX<]|/h0!+t9TY6@01 N  #&9ze(E%5#"&54>32&'"32675!#'326#".5732=#5!##5'l4A;L"3(6K)<'! 6V4&+n,8bD7" ; .-GG,1,D8(9 E4 ɘ* 4=':H"(?NKB$","-HH'9e##5'%##"'&54735#5!GG,!45'9韓8'456H?e2"327!0;'.547#5!##5'%#".548,)Sx+JK),*D?FG-"""" /<)B'#N&#-I! R!&HHc8  8&:4{e>#53#'#7.'73254#""&546;5!5!##327>32{PPL/1 ^{_-]E91W@oK6t%I{|' *%*6M0E&֓ :GG0.&zLm"8'^HH;2 H]e!%747#5!#327675!5!##5'7#"&!75CaW%;&88-0]FG,603N $ C;GGQ4+8HH{7~A 7te(G"327.54>75!"327654&#"%.547#5!##'#7.54-(1"xW6"C !,6)8"Mt| fK`^{a@Y* .o3+('* pO7a9A@]49HHm (|ZAԓ`LJe$%'-32654/5!5!5!####"'&5 -"C = **GG# :(`8Y!0>7 qHHd(6./?ze.@72674#"#"&546;5!5!##5'7/"'46327675##"3237632)L#K,p#FG-] `+.9N!)'`>E:'%S6+3.^HH鎦9 !z+N?.PX" 0ۥK e0%'-6323275!5!##5#"'#"'&'7232654&#",!hG+FU!$FH 2 bG56'2/V(469/3n9$IMHHIX&9/=,/H 7,e%##"327#'#7&'.5476;5!5!,x"-,@6^0AFb^{`N=4,/?,1/.G!S)aӓ 8]6A8>mH9e*#'#73"3254'7#"'.5476;5!5!#^{`Er%0':275ItQgF000EhǓK?",C!f6'0(hO]E^1A12yHH-Ve<#'#737#".'73254'"#"&546;5!5!##32>72K^{`C'3.2hOA#1W@oB6t%IVW'>,Ӕر(?& 5JO6.&zLm8%^HH% D- e+4#'#734>;5!5!##".547632#"'&>74#" ]{aD?* k(2UM)$1ObPgPQ6(1-Ӕ5@-fHH=,=g 17E5CNJKM#:31Re%'%####"&=#5!26=#-"GG}P325#!5!3XNG+bOJ+,MvМ9m&'7 SH)e36%2>75#53##7'7#".'3254&#"37".5463275)+8=eGG,894cA(*-6*B:-= U9G[-+ 8(HH}8|.DF x*5-/C&#0=aCA e'46;5!5!##"34'432#''%".)B4G)B+2_ ,Xz:-=U^HH+ B00V<9&A>g=%5#"&54>7.54632#54'"#"6;"3275#53##5' d//GM   -3K)-EH&9*5"B$Fo^FH- ?N3   U(2FI&#"P DRoHHs8,e%'-463!5!5!####"&,!Db,GGh8l&HHGV se%%'32##5'%#"&54632&#"?!5!7+"FG-V,EcZB G9!  s*7UX81f=Ga M} H <e+4&'463235#53##5'%5##"&546;54&#" J20"eFH+ ^ '0,380HHl8Ia o .,e$5#46;5#5!##5'%5##"&lɕ (\FH-_S. HH醞9/One-32>75#22654'#5!##5'%5#"&=6 >) ')57FI-!:P^~/W+   +t,"6$HHc8 3ve#"'&''7&'732=!5!#\I6 %18<,9 G<L1A858M8',(.HHe(".54632675!5!##5'75'4?&'"p4$JN9`KX@CD GG-/^ A 1*+H:=(Ac3E]FAxHHc9{Rp)!.+eG4#"6&'&547''6?.54632632'>54&#"'67&'"8.$1mL.=HV-6UA58GF] 57#4 )% 6),Y84(+++(}3PS?+ $)9,*089/D4G )% ; " #+R ' ;&= $i%.327'"&'5#535!##"327#"'&'#>;5p<+>"jJf),w*165G^1ARzgL6' uG,>#-)U@GGA/I.1T)rF2B!|-;ng+2327'"&'5#5%!##"32654'7#"'&'6;5p;,<$jJf) h%0';2DA75JuQiD)' 1`="/)U@HG@",C!258$1)hP[D(@{Vz7$h6#>;5327'"&'5#535!##"327##'#7.'uG, ;,<$jJf),w(4,@4]2A!S) ^{`Pv(!-( b@^O4^s`1HGO-? Ee/574>7&43!5!5!#!"!###"&=.5#2)DQ7&43!5!5!#!"!#5#"&5432&#"32675#.)DQ3m=1/"-P01FT?.le #'".547&=#5'l  (:FM; .j VL   * 5:f,DON Ee1"&'4?!3.54>7&43!5!5!#!"!#5\rBr%53H DQ?5#3.54>7&43!5!5!#!"w(  \  $Bl+52H!DQ5 B^HHNe277'7#"&=#5!#>5. Kf6;;WOTbU@HH"/e:#53%#5346;5!5!##32>32#".573254#""&SSSS31>+=,+":=!4lRB$3?Xj%q H6yOO# `II;5 I 4L(6MQ7.PS@p"e7#537#".547&546;5!5!#!"632'674&#"mSSY 9ZQ:AK#K&: :9!;,=09XbO7K6 *I-98K>&`II ; 8&).! >e*#53'46;5!5!##"34'432#/".SSE4H!(F',: b!In?' O?W`II+!C&1e>B)93#8e$#53'346;5!5!##"327#"'&eSS Vg68z)5,@7_3DV{jMNO!MmpIIL(/I#V*tGGe.#53'33264'7#"'.5476;5!5!##"USS VV(2&FB96LxSkE102Fl$ O!%;#3n'1*iR^F]4B52}II#ce:#53'346;5!5!##32>72#".57254#""&SS W{2>cY+?,+"9="4lRB$3>Wj'q H6yO!"!`II;5I 4M'6MQ7.NS@p"e.7#53'34>;5!5!##"&'4632#"'&>74#"SS VA+m)2YM>4$6 #9=hSR>&2.2O"7@,hII>.?i%/5A-% (= LLP);31%e.#53'346;5!5!##"34'432#&'".SSWE4H!(F',: b$In?' O 4?W`II+!C&1e>B)93e7#53'3'#"'&547&5476;5!5!#!"632'674&#"SS WY &fAKK&:;,=0;VlO 7K6#6m98K>`II ;).! ? e >#53'3#5346;5!5!##32>72#".573254#""&SS W`SS31>+=.+":=!4lRB$3@Xi%q H6yO!BO# `II;5I 3L(6MQ7.PS@p"$i )2327''3%"&'5#535!##"327#"'&'#>;5p<+>"@ WJf),w*165G^1ARzgL6' uG,>#-)Y!ͺU@GGA/I.1T)rF2B!|-;ng /8327''3%"&'5#5%!##"32654'7#"'&'676;5p;,<$@ WJf) h%0';2DA75JuQiD)' .G="/)Z!͹U@HG@",C!258$1)hP[D(@{1zEe39'3%4>7&5463!5!5!#!"!###"&=3.5#2 WD'*7&5463!5!5!#!"!#5 WrBr%53H D'*7&43!5!5!#!"!#5#"&54;#"'"32675#3. WDQ3m=1/"Uw1FT?1e$7#537"&=#5!#632&#"#5'3275#eeKf)GD/43G.;,78.cCU@HH>L &/""//&e%7#53!5!32654.'5!##"&'&5ee, =   # :(Eq0cHY!$%# H(6./ln?L)4>32&#"3!##"'73274&#'7&E!89!m5#73267#"&547.54632&#";#"u2:!8{7pS7#".'#732654+732k 69 j.wv21M* 8!*K9NO,?!eDh1&>!S'%+"'>74.'32>5"#".54632632&#"51k?Y"'    .8&UD(Q:/3l(1 -PF5(@XY'"%._"I1M^*:<(QPE75"&/73>54&#"#"'532>7'&#"7637632&   3*,937#"327.!/:*7B}$pp-H(UD9$UUk)7"&54637654&#"#&54632&#7>7i-C8^)3-_2W?ZPTI%"F2*3q SaQ  G$/3*1A\=EQ4>`B%'7267'.547&547654';#"-R:Df`g~Z!H4|{JGMGoC KAB(5OK  ! )QJ&Gm'463!##".%&#"7>^E; ]J >;$BXL&ARR.5C7'&''7&{>Jb&8\%U%;<|Y%OF L)*- NI_$7= : 7G(#3 gl:6$ %+-F4726=33#'&54>32&'".9lrqoY/STC^7P* It &74354.'.54>323#"&V(#-,%PK"4FA!@uih6L@_<" /9--+C>a;&TB:U++0 HI4&#"56323##"&543x '5"|ih;+5-`>*!V qeU#(:%_%%267#"&=46;265&+732"&#"4w UXq ]!(AW8Dx(  "(\2d8{Y';RT=~  %#574632&#"32>7#"'&Vj)0$31315(]GGQ)h O $G16K$ U JM$"&54>32&#>546;&#"a.0  e2PMY2KU!tFf3 K )<*X^kj$)}]74632&+3267#".nY+, 53(W UU4=,PaM.9i+`= -O/#.'&+'32>7'&'767+.54>32m`5f~638. '1#P 1%(:I;G)%HE*6Tb g5(U S 9'#A (? >2.:'K,%"&5754+73'3267'Nn-T|-%9O,^5R#"'-&23##;#"/#73265.+5%0"j6b- %D/, 5%+NH_R0R.!!(R&632&#";#"&".546776770&:[/$IcW~@$i;nU+/3Cb Q@$GP*DF$%DK=H \q34323#32?672'#"54>7654&#"#&dWtIFB ,?GLE6Fn",>;\#xG.K&)!K%;( 3 ]0}2>%&!#$".547#'!#"327<$DD2 vʐK@L\( +G.i1SSL4 2KV27'75.''4>)C& GM 5= );,>I3#ZKuCq +5&U -+7265'.54323265'.5467%&'&#"\5+4 B"+53n88/1(:'Lo+#,1/ WqK>c?,$ D.3 y     1<+f8$%= CE\"4632'%''7"&7"3654@cOPn[N4BQ$> Iy5GL4&#"5632#"'7326 '%'9BG 18D_-~BS&F<7;0H V $><#n3N8wRc$7"767'63.547.54>32&#";I+MPr* ^MS<%>>!d-*,H|?9 DeU:C2A)8%$5 S6?N +3267'7.'#732>54+732k 69 p)t+X 8% K9NV,?!`4 SS) D>S :SL'46327#"'767''74&#"327#"H9Aw4;(\ 6qPx-$ld5FYS F'-6Ls,>//P*? +77>7'7&'4637654&##&54632&#sE3*4o )-C8^)3-_2W?75 SJ%"4>TFM  G#03 )1A!?,EP/';#"3267'7&=47.547654'y8|{JG5,4s`+<~Z(0(s$QJ&HfE5)aA)H(J %'463!##"& '%'.#"76[H; ^JdX9%!X'&6@PM-5BX}Jo,"H#27'7'7654&+7h9gv5ɓ +,/O2U[Hgy R326=33##"'4632&#"'%il/8lnmm]\I+-+NX8(z5)ISQYTf JP'+74354&'.54>323##"&'%V4',.OC1PR+6?+ih7'&>+;%_;' .:->z ,?Lp9?*:U),/ewEp J23##"&54354&#"56'%fE6ih;-5,` #1 988fU#'9&^9)!V -eF]87265&+532632'74.#"+;#"./#5$1V2H 3",>>B;.I2dQ n (5( RR/-HO>]i<; ;J]R R 72#"&54626546;#"#"'47&546;#";#" !'H9$a( {^EBNW85*(.&`)%t^:IW);&#"3674&+'32654&+5322%,#'9[2! .LK( ,D?D~'XZ&%b1B> %!)#+5&DEa/Z;L@L$+T' QJ5h !GU @2#"&5464+532#"&54632#4'&#"3267'&+'32!!#G`0A9^֍nLS^&HF*9%7H 3XVG,&,$-g?QJ59FcFVdPCj`G:EM!?, #,& ,R?H&5432&"72#"&'73265&'#"&'73254''7&54>32.#"KR?&r&< ~W1n8$@$0b[fi=r Mc*^SE @0f B`3,@aM "Ge&&R+#M. E=R9;*=BW16I33#'P=,.+%2654&''7'7"&54>32&#"%2#"&'71>2RAbZ74.#"326?#"&UC,X?3ch  i) *B`'"  * FX>M^(:=*  *%ZYY2)&  _d0F265#"&'>74.#"327"&54>3273#".53Og ! .7?X8ZL A.H91q*OVO{J/hzpXJ b   F:0DUU@ I @F+-(9;)>'SM1)?TQ*a'46327#"'767''74&#"327#"H9Aw4;(\ 6qPx-$ld5FYS F'-6Ls,>//P*? @%2>54''7&#"#"'532>7'.#"763>32653#"'5"-H*"K9;T*=."3 5-.<'$6;'&-t'F!B-%,%4="9"6]HKV'&;.@BRW H 8 Cy#CA( R F<"&5467654'#"&54>7654&#"763"267#"3267zkbfg &y'{m3PIB765.#"76;#3272>54.##l+e#HYA=1?(Xk"A?[8M3H2:@%Uk:-"b5Nn5/3' 97! *D=2D@A1(5 8%i! Q8 'P:1''3JO     "!G4BO4>32&#"#"'#".5463&'67&5463&2654.'3254&'&'"I 74a>6Si2gJ$ l1PP*Fh4]&  uH! HK.)^"6Qd\C0U>).'I)g**  c(; ,,,< 2F)9" 4%!,@  #FP".5467&'73767'.'"#&'7632&#"#"'+.'732654#"*L@ 211o2MU .=?$n?+51=4cJ3-#"H3 ' bUO  :"%  #BQ1J#,FJ4#"#"'7>72.#"32#"32?&5432#"&54325&'&'7326S 5 (M2s ^hnhZ& OQIHL=9KGidGf0n4[D0_  L* ?() #,,!(  RAyPC{  BH1L37&54>367'.#3327&54;'&'#',*=#6&f)TD 4J6%'+.5467"&54>32&+;#"7674&5472 NZ<'$ ^y((=*S:m08 , *B$ I#'A7y' gTK:*-* S; I=#"D S)HO73267".54?654;7632#'&/#"&5467#".467327"&+*i .4'Q%# k'rL:J N-%5B W' >Co1YV +5%<.;*O ,, =#1I(G) )*(' Q(9,N2"+>1d+HNGN7< 8LI <54-<>@\4%&'#"&547327.54?65&/33274632NB')Q1\aN?2L>w *HG*g6e(AQp &2U,4~T) фyYn5645=JS!C.q( (:D;27c50"3%2#"'#'&'732>7'&''7'7&54632&5l% /bzbiHCT[219 )]Sj NKX.`E 'XT2GL{3@* ,$s0W3b@*BJ0%4#";!"/#53265&+7!2632#"'532kp(!/gK - (U/;j7Y/4 97!K>#?++[R0R,$LR:?!1`BNU O e=3632&'#327#".'#;!"&/#53265&+72;(@$tJr/PK@!  !)(2&N3iN (T/<k?)Nu/  E 8&([  RR,$LR2,%2#73267'.+532654&+5>;8GDPNSZV-"".1:g.u cp 6I4)CzG9[;(U%&V H?! 277>7'7.5467&+9E&9 ,3 iV04341 ZS= $C*Q]M.[(4%2>5#"&'7>7''7"&54>32#76732654#"(c>xiFT <X.W?M-WtlR70-(GP#/<9@{`=6#$> 9fZ;D'*5H<$C1K+O--:",b#+"327#"&547&54672.#"#5'7.?.2v4^n=WTD39Bh7 <Lz>*65N b\Q sA%#"&546354#"327#"&54>7"&56732&#";233#5#>61'0-%;= A"$C!B.8 17)@[%E' oo$!7")2$4L'Wv6++ ,`J*$! J&$ koO472&#"#32327653#'#"&546;254+27#".54>3.jd 68 '#$TV,83#@;.mm]1Mb3L_FRg`= "0X[C*+;3UJ G*& !2( (i$]I#,*YG^oH'=fA7]4*I" <2654&#"'&/#67#"&54?''7&54>32E% =!B= Hp]15L?ARpƨBd%M36P%c$%2.!' -6$1K#4P6I!,B U>_+M6YT.9@ $)!0&3 &84'37>'2#%'7"&54>327#"&''%oP :1YZq  .WAMfIKc{iD,%BU"D9+ $I<  ^BfZ;D'*5&-nL<7PTG>2#"'54>7"'4>54'76323463236grK)@.<=WW=jP"P'0ZJ;UU;F,I.u=7654.#"#&eUmmtHFA!+?HLE XG$ޱJu+*!;U U'U8265'.543253#53265'.5467%&'&#"R5+4 A"+53q6oo:G+5 9'Ll,!-1/ YqJ@a=,$ D.3  1;+f 9&%= CG\ !"365446323#5'%''7"&%> IdMQlZllN3AM+!6.H69CL4&#"5632#"'7326#5'%3'9BG 18D_-~BS&F<7n;2m0H V $><#n3N8OSRIq#)7"7673#5'63&'&547.54>32&#";J+KQmmr* ]''S<&=?!c,*,J|?: C7lU: !2A)8%$5 S6?N5%#5'7.'#732654+532+326735n)u"0%7")K9N!69qm(pS)7$S&8SM@+>`2 E'/77>73#5'7&'4637654&##&54632&#jE3*&z oo)-D8^)4-`1W@66 TJ##5;&FM G#03*1A!?,FO+/+;#"32673#5'7&=47.547654'o5|{JG5+np3q_+;} X(0(s*QJ&F%~E5)aA)F*I  %@ #%&#"7>%463!53#5'%5##"&FW&& [H:nn:!]LcYNH# APf[I!33CU273#5'7'54+7^8fxoo6ʏ,,.N1ZIfzR!326=3353#5'%5##"'4632&#"/32353#5'%5#"&T3&)3 0PC1PS+5?.imm;%h8L=d;' M>?z ,>Lo9?+:_xEpg),-"2353#5'%5##"&54354&#"56]E6ioo:i;-4,_ #288f>eF]j#&8'^9* V =7265&+5326323#7'7.#"+;#"/#5%1U3!"K;9ln; % 2cQ n/ (4'!RR ,ID ]i 0  ;J] R0R77>73#5'7.467&+w:C'9 mm-SKiU13441 0~S>V_ M+ 932654#"#5#"&'7>7''7"&54>32#76732673GP#/<n;d,DU BX/W?M-WthR7 +,(< nC-:",P&'>5#(<9fZ;D'*5H^,M6YT.;?/F!( H$4 $%3/!'.6%'.+8%3275%'7"&54632#53#5#"&''%74'37>IIJc|-XfTXs  mmbDU!C@Q :6%-BfZ;E&3=J;  aq=6PT9+%m''%L-Gd9N7&5467>7'.#3327&54;#"&'73>545'&#"'67&'#'lHC3g) $+ '=yMUP #'[HBk86S4&'! (#52L)281wEU  $' 8,AWU3) G.;H9<7])(7+(G 0&9 " %$"5 & "*432&#"#.֍ $&3:((zx : "(e&4E5#53'"#.54>32QQY*R9;-A&II+6gP:+)  :L@#'#73^{`D3?73267'3265'.'"&5754+7367&'>72&#"#"'&,8O$S@)LMA-Ri-)05nZA*(DX\ '1`A!7-.6A;+#^5R%(@K XC!")8 =;$873267'"&5754+73>54&#"76323267#"&'-%8PLRi- {F)92@*M#A?&54&#"763232767'72&32>5'.'%32767'eX;O\')77]{`89Pj-'24nZ?,'RA11FH-2 ;.D "(:'@( ēo^5R "(AKX6A2%(##.5432.#"#3Q9.*%O"onND.a ; 0=`,4>32&#"#. <-'(R:((z$' : -@]4E-4&#"76323267#'#7.54>)*92?-I$@?%;TT;h0d8[B^{`$?:!=WX=o a :'-70':G%Z7 ɓ*G.*= "[T3#.''#"&563232>7'.'&543253To$*@D5EW*\L] 0LT6  * Dk"=*$n S$$ 9Z3,2I4    @T4.'&#"#%38GUn07 N#B$;)~%,73265'.'&=472&#"#'#7&'&'X k1:N Kz(=<4.Bd^#/ 8>$]{`OD:H1' "'7[< +!)4,E)Ǔ ,& 3%2654&'"4672&#"#'#7.5467&5)OFhITlp^@-(FWFSS7":@%^{a2N, [47*:.#4;'6CFCL[C G5/H*Ɠ$0+AS%1#'#7.54>54#"7633274>32^{a3M- >XY>l4A/Kvc=XW=>/ 2${fE퓓&2..@ !2gS90:)$.9)+`-<   !2#"&4"#4>32'.s4$#1E n6jIGvJ+ if" "67 85.SO/$94(. %"#4>32632&#"#4.n !6^>.)#! w(*S&=`p!>H4#  y O .&Y79* -2#"44>32632&#".#"#.- !5^>XLC w( ) S%4W2-C nT *T!?G4#/5y O 0$[!30#*>2% 2#".5464.'&#"#%3"-+7FUn.; M+! B$;)~$ ".#"#4>3 632&FN+\w]o '?oIv$T"O"30#!?G4#y O *2"47".#"#4>3 632&\GM+\w]8S*o '?oIv$T*T8T"O!31# 59!?G4#y O 44.#"#&=632632&#"#N 4": UW)g ?54'" XRBV8T<;JC4IV)96( i=x)' 'L; ' <2654+'767+&'71E= 9&%/,-BhM`A,0)8 ;0-8=9514>324.'#;65'.1CQ-=mF*L+_80/; B# [/+01>.E94JN/ > 4T267#".5432#"2>#q ,v6=Y-D*N ,-t(q# 4632#"&#3Y'"!#2^e#)%`"#324&5432&!05Qڑ[*a'0"D m j P^!"#324&5432&2"4!!05PْY*Za'0"D m j P8 *TV= 2#"&546'7#%7#')1qe[N0&&%#E["#%7'7>;)!<[Nrd/"Gm1pE#6&LX! 4632#"&7"#%7'7>;="..""*W#A&<01^-u&"-+*"-Y<K&'#".533275432&#"{4 524^\$#?$%*!-^D} 6b2*#K&'#".533275432&#"{4 524^\$#?$%*!-^D} 6b2*#%!5!^72#".5464&#"326.;& . @p %()2>+- & /;j2)<'"%!5!=©^5B 62#"&5464>32&#"3%#"/732654&#'7"&'"#'$,#9;#n 9Ckkggi"I0Lg:&*32'"+2>;g)1"V>UkJ <2#"&546#34&#"5632#".54'7326h&,($*hh9CL->u9  .Q22" WB'9<*'1+1N X 9  :)<.! " ?LJ9 32#"&546'32673#5#"&547.54632&#";#")1)"62;"<}pqz;qU=nYe/*/L~}K')%"3'! ;@k-[JA*:':HV8@Q!F ?2#"&5462#"&5463265'.'&=472&#"#"&'( 5,"%-$j j2:N My'>:6/Bc=Pb/.LN*<)P( # "%).)!+G2& #'7\<')45N';<lB 82#"&54#"';2673#5#".'#73267'&/#732I>' &$L#:7k .upqx6$>)$8 *9P)" 10D fC9q1*3 U#  TN4Y <J2#"&546'47"&54>;#";#"27#"'4672#".32654.#"( '$5fX#G'! J<#I&)?TAM).S\PyQp!#!(=*" 1(!G/D3"5 T5#X.Y~Og;=v pV]Q@3'0 972#"&54627+.5467'."32>5#".546(')",$1gE-L #>*49*#y<(, /:(WB*)1*7'.#"7636321 '$5Xu%' C 66% ".4*+ ;;W*@.&6 6.-A%6AB"'" 1'"G#?6jHf0YWE8F&7_JMW&(>-@FSX*Fa +2#"&546353###"&'7327'.#"'6&1($**gyjopi{M*r )P N-L"=+*&!2+'lQ#_S:[ /&0;[ 12#"&5464&#"7633267#".54>)205+:2B-Ko^.c9wX*NK->YZ>R+&!(&# dQ8-:2'XE*\&>)Q7+>"" -:2#"&5464672&#"#".5467&52>54&'"' %,,4o_@-(FV FSR7-KM)@b8# X7724HfJTW)2))AMZD F57O($4/AR'3:'7C  22#"&546'3265'.'&=472&#"#"'&''0'$*: j2:N Lz'=<4/Ad^#/.LN*jU9Q)$#2+G2&!"'7\=+!)45N'7$ 52#"&5467 54>54#"7633274>32(!$)"5x>XY>l2C/Kvc,BMB,>/'%{Hcb*31'!9.@ !3 gS8)7(.9(7 `#8d (82#"&54%#3%#"'53674&#""76"&533267c>& '$-poE_$H1#&(M,$ %%jsM=(Xt`*" 1n`P 99!Oc*7Rԅe=&!QK"$*11 )ESqO+5G> *72#".546#3".=4+7323767W')  +rp+E&&[<%# (CI&)>' " (MO$,! T-0!  Y 4C ?2#"&5464>32&#"3%2#"'#'&'7;>7'&''7&d&')"* 553b!>9>& 6g|fkKCWb" ._ Q>*)1*,  J( 'ZU.HO~5A"'59e 172#"&546726=3353##'&54>32&#"|&()"*z/:osopr +M0311'$5(8@,joqh(dU'%8().%R M#6GC1"'%"2(! A*:   k\=" 48K/,A?c<' (72#"&5464&#"5632353#5##"&543O' )",B"&=kpqj;.8+b!)" 1*6, X rj$ &;&`+ 972#"&546#3267#".=46;2654+532"&#"N& )"*qp4wW\:PA. `+EX9G|& ",)" 1)A}3f9 ,P3';TU> W +2#"&546265&+7!2;!"./#5>)'/4.>lIeV;K'W*-'(!-%NT\D&H!] U TN 12#"&546%#3#572>7#".54632&#"[(,54popΣ6*7E9's*1"5342#)>(#$'!R W&9a32&'3654r)"$'$6rNgs %! 2QV~ ?+" )2(X*11'" VHm"45V]_8l+QO1 R ^6E`k (2#"&546#3267#".54672&#}& )"*po([ VYA\,kX4.*55)" 1)+b?+GD%Q_N,;lwK :72#"&546#3"27+.54>32#.'&+'32>74&R2165poh+'1(;L5/<fX z%0< V <*91%73267'2#"&5467"&5754+7353#54:QJ()'$5Un. pqV(".*<)2&" `6T$pM 332#"&54>2353###;#"/#7326=4&+5''!* %&1#mop S=. 'F1, *..   7&-5a T1U,!*U# A2#"&5467"&547&54;.'"".=7654&#";27s& "$65̋ O+@-.Oy#=WV8(C$5YB?L cIxMON34"%&"w3$SP %@H6Q  !%.B;)8R7\( )'%"5632#"'53254"&533267 >$!0NS#G0"&%NShpK<(YqWvQgH88#Md^,c3@T Y 5)1"&533267"5632#"'53254.#5'%3pqK;(Zf1"!0LU %>'!&%N #*p=Do(`3?V YQiG+0$Md "W#"&##"&5463267 +W t5#//  E<Y]=&%( f`w'27#".'./&54676;53#";(  ()  % ,5#   "! *( 4`' ,T326=3#"&5432#V-!;-6BkR$<$K4)5Di/EZ#".'6532=35!5!## D*#.)gdAV  B %MAA1|".'#"54632353'32=!/  <1B (G2*)3x.@*H4@x#"&'>753"&#"32>72F.&4qN-(ZA;%\+?J)&9-8\T!1>u$#".54232>54&#"#"5432327 LL?.S(d$).H3+)0C@,-=,<$% 2!: $ k[5654&#"&54>353&8 i<=20=32=O >"$  5nY)!RR$6O #".'3s,VjH.2tIv#"&##"&54632675! f r5"0- 8 4/H N9$ ( G;;Be #&'5327%326=3#"&5432#PcSVbfRZ\W,#;O2CjR&%$2)((B4)LAi/D  gd #".'5327'"&547332>2+iY$G%fQZ]3P 2$! ' 8)(Wf+)MP0/'&z#"&'35!q?#9-.L-X&9665$5!5!2#"&##"&546326788v  q5"0- 11l0013 N9$ ( GR2!5!5!326=3#"&5432#VeeM6!;P1HeR&11p11nJ2+dhLBo$E  @ "#".54632#"54654'&/A! "HQRG'!!#6' J:/TR1/*G$1X 4632#"&10% !$%% 1(00 *3Y327#"'#"54?654'&#"#".54632327"'7&'&54675!5!!5>54&#"cOT]cQP_>  (_ @JKAXZT]^ZM>>|^_|2%,(zBMl**=! 9""3 # L.3,KJ" 8##1aNAAO0G) @45Jfq?455X 4632"&#+5;+,00!H k@``YYE$!&00FAA@ =G[#"'5327'4&##3263>7%4>32!!#"&/47#5!.#"%"&547332>53@kL[]cVX^8Z,kaA6FzajJ" &=kFTv8BLEt'L4OG^v7R/ BUI-$3 V((A('7[5!WYA%EO9&5ZW2AZn"Dk65 в$Ajn+=5KX=I<.' BW &GQe#"'53275#"'5327'4&##3263>7%4>32!!#"&/47#5!.#"%"&5473326534iYP\cUX^lLY]cUX^,Z,kaA6FzajJ" &=kFTv8BLEt'L4OG^v7R/ BU&%)MV''A('V((A('7[5!WYA%EO9&5ZW2AZn"Dk65 в$Ajn+=5KX=&8 ?F BW AL"&5473326534&#"#326!2"'"!!#"#53267{BUI-+KWBY- +Z=v_6F@Cq\=Z5%WMFu'L5D ##$z0X=I<@E CV8[g5 41*H8*AXo#Dk64 !AA;8:Q'a&f'c''+'i''o'\''l'[''' 'Z ' }'z z'E''tx''t'Xh'/# T62#"&5426?5;#####"&54>=&'#"&54>54'4&+532676&6+;@@cAN/7 '$+$BJ;&  &8\>]p% "3[9'AFi JE>&:*C$ &WI]7/$;" 2#"&'67&'###535463235#&98F5̈^(8U?CK$>! c/nwu@}}kc52 |-{ %L:cA8'!f# 'bpLT[gn@A:g*P5 B62#"&543567&'#5!4&#"#4>323####"32>2#"&8&}-{MwZ2JC*@"C.am@_|:K$>! X:p+%/|T[gnAf>H$?<$g:A@8V!f# *_p@'&5z'&5z'&4]h'M&,'zh'!&,SX X4632"&26?5;+#+##"&54>=&'#"&54>54'4&+532676O00!H D6+;pWTTJ@ W/AN/7 '$+$BJ;&  &8\>]$!&00 "3[9'AFi JE>&:*C$ &WI]7/$;" %1 %,5% ''Z''W  ", %(E#(K#"&##"&5463267'7 D< r53/!45 @./1P4& Vk_B32=3#"&5432'7N3?:O3>gJ'Dr45p?M|{D9X-= a60('7'3270#".'.546;53#";035?  *  1#,",5"LuW    +"J#)%#####5354632&#"354632&#"WSSFFA: :A: : D88DY8?E5RY8?E5R ####53546;0#.#";SSFFA:EK: 8DY8?f5R####53546325&#"3SSFFA:M@:'8DY8?G5RD+#5&#"3######5354632&#"3546323DS*:SzSySFFA: :yA: Oi"5R88DY8?E5RY8??")######5354632&#"35463235&#"?SuSySFFA: :yA:.):'88DY8?E5RY8?͆5R&#327#"&5#5353##5354632&#"V'14GGSSFFA: : DF+(DDY8?E5AO3273#5#"&5##5#"&5332>75#".=3;.54>;#"{10_XPWOAQVT) !!ARX$*,: w9B-=3!!!32=3#5#".5#"#5#"&'332>7u+3+-,Q/=WO:k$9% 6**T) !!AQX$*,: 6&@&$BDGaJU'!1%&+9O8G6-+2*A8@4.54>3!632#4&'"##"#5#"&'332>7u+3+-,>]dOX49*-X6**T) !!AQX$*,: 6&@&$GhaI?>"H3l%&+9O8G6-+2*A84<3273#5#"&5#3#".=332>=#"&54>;%;5#"10_XPWOAQ\V=_6I%Xn*-eX%O;C= 0%FS]F4D8 E;C.~"H3O;,-2hA8F4.54>;3253#5#".5'###"#5#"&'332>7u+3+-,+@XO9l1D$X6**T) !!AQX$*,: 6&@&$BF"JV%D7-}l%&+9O8G6-+2*:X&52- 273#"&'3J GRC@OG*RIPPI%-8XX'%X%>53+533"4fW,?L:ꥋZNT<v3R0! L :@X##=47'3>7538}rAZWzFVW{p"zGx)qA&X#!5!W  LL?4X #.'!53:{lWAZ WXamtQ: Lw9X4&#!5!2#!5!265|Tbxy\WysBWLY[LVC%!#467>=!53!W$C;6 aW>MWS`12!/ Hᕙ1P+7.H?5X 2!4&#! o]X[X+7^@%X #.'!5{mWAZ>XamtQ: L#uX4&#!#525#5!2#[Pux<oWtESgdLzFL}a2!5353F FF<&<&<'&<&&:}FX&:FX&:FX'm:6X&X&&X&?:X&1X&3-(X&)=:X&5 X&F'@&8X&9X&%!&H0:X&C3FX&u=9X&/98 X&ME?X&ZF3X&>8&X&)%X&<X'#{X&>&:6&09&?&8HF#53>=3>Q;fhW):WD'DZ9#"35467>54&#"34632#3 !!8 #H=5"/$,888hg')# 17A-4'A{hg o7#3353!!#5#3&$u$ tm&oA}}/ f  !#5##3&'5!3#'353i Lkg%QppB-'d#www m~%E!flC)pk 5!# F( ? H5&'535&'5333#5'5e#Be E]`;`]!e#8!;W7X\f\ '2!#5##5##3&'5%3#&'353'353\laV!K1: g&P dP'j fpB,& b#ww[w3D m~'C\~O apllC)pje #3!!!!!OA~"L`~#Il~#YC("T b&'Ip%{|*##5##33533!#3!!{   !yJ  dyZe%%h]|]1  uH  kq fp|t-'.'5!3#&'53w   fq g(S!t  !][WQ!eS"673#&'!!567!#3!5'5!&F a&Nb&E[u b/'Ni&EV&N ekX 35##335&'5!3673#&'#=k 6=X ;8)B\8u 8nN6p)Lad:75&'535&'5333#5&'5!5&'535&'5333#5'5e#Be E]`]`]! Eq'>e!D]`[`]!e{#8!;V RX <&5":W RX\ %!#3!!!#3!!#H\TFWh[ ah#S cU &`g&?5!#'7"%7"''4 i'/;` ad<_- #&'#'#&'5o(O&i%pp%#T eQ bll"G #'5!#3!! 8&u M K 1k R&>7%37"''49)-6e@X$>/ J%##5##533533533##4 ;0*XXZ ;p Dd22^`_`^!!C O&'3!#3!!UX'.54#%%h47NP%4)|r-7&'5!.'5!.'5!g/W$"!W4Wd%(% % (& 3 (( C7%3V)-X$t|3!!!#$h56% &|t-.%!#'#&'5o%oq(S!h}kk}S  %73#&'!#3k _n&E `tj&N hepK'#3%#3#3#3#3l(& j '& 9P(& ~[/(& f#(&|p'Wl*W;\W\W@X 2j-X d%.:%'.'5!>7'67'67&'7"&#!567i> )2#W D. 0tR,-" 1  (&},  wT.'%v ''7237'"#'677{z|WUWX \ X|}z|W^!TX ^V}   7#3!!! `&  h'&d !7#3!!!!!#5&'53N^~X d~#Il~OW"$@aUg'Xj'&Iq%5Mmm$6ff8"!#3.'5%&''foX!k %   Rer)x&P i?X%r %   b iis'Lhd{:A%#5'535&'535&'5333#5&'535&'535&'5333'5!]!ee'>e!D]`[`]! Ee#Be E]`]`XcX\&5":W RX <#8!;W RO(&|,9 #&'5!9S'Y DtC##&'#'#&'57#3%!o(O&i%pp%#T! Zo eQ bll"G  a(c,,#'#&'5%##5##33533o%oq(S!` !y ! ! byZh}kk}S u    iq f`, &'5%#'##3!!%3S!Y%or'U bV(.}S S}kk i'U$|5!#73!!!# ^($m56L#&Sx-.N&< 7%3'5!#V)-^'X$Q  #'#&'5%3#33'co%pq'VU),[d)i}kk}T X%l)) ,&.##5#!!!!#333533%3R!z& WlZ&E`z\V'/R#u%  _&uc'&N iq hQ$%'#%3737&'5%#'#UV)-),xT!n%or'XX%T f}kk,"#'#&'57#3!!#33'o%oq(S! `!Z&h}kk}S  h' a)%^!'#5##335&'5!373U8'=k 6=X ;8),u 8nN6pX% C,'!!!!!#3!#3373#/B;.Wl'C&E d~ZP*,VR&:9_&u'J'&N kM a[$&|",33#7##5!#&#5'5'#&'5$l၃P!& ;4(V9l'p$L$'Q|w..S%=/VUfk%H~%D #&'#'#&'5#33' O(O&i%pp%#TZd! %GQ bll"Gfl( .#'#&'57#3!!o%oq(S! `"h}kk}S  h'!g'#'#&'5#33#3#3o&oq'VN_Z_#Ili}kk}T N)h'\h&&Iq%B#+5!#3#'#'33##33%3 c'H cl<^RkV(.Wj&o(g'T#}%:U%# 5!#%3%5!# ^'V%1^'SL%Q.!.#/#'&'57!!#7#'73#'53!!p%oq(R"l~V  b% a~!, xjllSL<GHF"  5!#&%3!!!#_'& $h56L!&T%r-/N# %'#-373%5!#UV9V(.9%1 ^(UL%S|%33#7##V%1T$i၃50&4N$Hs..73!'#%373337##zV9V'/9++,%j . &1QZ#Or/0 . Y %75337&'5!#5#%5!#&#5'5# ^#T $L% o(O@l&p ef"G%HeQPfkL(3'7##%3%#'5!0$h y !  &V(.]'u Lr/ |/ !  $UOkC#,%#5#/'73533'33###'33#VJ#I cJ(.])>jQ ^vv&IkUs e()f}%R%[g( %#5#%3533'#'5!NV(.]&~]'u }vUr e(&#OkA$-#5'#375'5!3#'#'33#33##]'!J Zt fl:_iQO Uawkl&p(h'k}%T#0&#7#5'#5&'57'##335&'5! p%oq(R"tM]tX bM(jllSW fZNXZg!#33#3#35!#N_Z_#Il* c'"N)h'\h&&Iq% WG87#37373#'#/# `*,9'/V9V! h[Q#  "7#333'5!#5#'%3'# `%nu ^'O!&8 yC h(vkSQ$,0 | A##'5!7#'#'33##33]'u fl;_NjOkl'p(h'N)}% &/5!#5##5##335335%5!# ^'% !z  !\z ^ ^(S$   t   frjkS%'#-373%#3!!wV9V(.9%1 `!UL% h'  #5'#5&'53!!!!#p%oq(R"_ 7l~V xjllSG (L<B 3#7##%3%3>$l yO!&V)-U),Bu. |/Q$.X&X%}#,4333##'##333#%5!#%3$Aقc&J% &$hL̓JL ^'V%1}C-.e(%!|t #SL%&,1:@KT]c5!#5##5##7##33533533&7#35#35#35&'#35#'35#35# ^'TLV! %   &%n]I]_^Jl76l{!- ^JKJlKI!- lLJ7RM<Q= &   |wDKDͅ F5L(DLu# F5L# L( ',5;FMS[c5!#5##5##5##33733733&7#35#37#3'&'#35#'35#35#%%3 ^(TLV "M&%n^I^_^Jl76l {!- @.JKI#lKIZlLJ 1%V%1RM<Q="[|wEKEͅ F5L(DLu# )"4L=L L%I*%%3'5!#%33#7#aV%1 ,E(V%1$ 6၃g&N$.=L%V;./i$W&7'#'7373'#'737375!#8%8%cd8&8#&c; Q!&6K6K7'Y0P(NW,7'#'7373'#'7373%#'#&'58%8%cd8&8#&c<o%oq'=86K6K7'Y0PD h}kk}<0G8!7537373#'#/#5##3J `+*8'/V8U!JJO Qp h]Q# UuY J27-37373#'#'##5##533533533##V)-G),8%1V9U?&4 TW] ;TXL%zJe&?Za]aa!!D '7"/  b. 5!# _(T'.'5!3#&'53w$  fq g(M't$!][WM! C7%3V)-X$  7'73'73y<il> kuy*Hty,F5!#673#&'!#3 _'&EYnO[T&N gY b7i#(7"&547'767>7&'7'6_I0B[R S_'N5'"FmB.3,=p$dJDyT>,>[N@+  0'Wqyu`Hd97P%'>54'#"54632'&'32J >@eq%=!Zg$V<5|P0$T>e'7R'6?&CD55-T6S Y_fT yj7P 4632&'#"%&'327$5 (VE~/%E,-8pI 1SKX3JP7 mP>32&''67''67&'7> 2*4H =* %;;yV%',JugTT~AI0 (7kO)#F57R.547#"&54732>7M-9/=1CC':L#9 (H+% 1,RTyX_\5'(/#2=*,  '3OW7UP267#"."'>32*Y1cn!A'%1N .'(2%0o<>I>6P'#"&'7632.'&'&'67'>\=][Y3 C<+ #4'p&?'X3_'EaB #b0S65 ?$ee_m) 7=ao-%&''7#52672>54'7#"&547 -&82/&0".#@[:0 (L`}>GTΉ|46tb725#-=7$1E;aZM^-U5;7 Ke "&54632654'7'67&''7&0Qr6fua 3S7+.Eq2n lN  fh$$.(>=[=78"&54732>767'672CF%5?,G;3I*18J.)>@(/' ,!*T:ӛ%7 7"&54>732632'>54#"-;Xmf 5c?G$%#3mB4)= b 0#_!( F 78.547376?'>'3JN'?CJ3?:B74.3_:?&30&NP&@8 "SK^78J2'54#"#"54326,8*-&B1$@&7-".7$EM7 dB2>7.'#"54>7#"5467'632>S.@` 9#5%"84 ~0" !"  $4:A({>3 *")$'/;_W_(bV\$%4H &! *# %I$% ,$ 7R?P 2#"&543254&#"pOK;5M6-O."-PD57#".'&'76--%5 ] (/<"l/'5 1LPKK19WDQ [J/g -ed78 ",52.'#"&54632>4#"%"327&$V_M+*1>TiE#+Y,=RX/M07& % hA7]C/!=uc";V38(-$ 5 -' P 3LXu 'x7P 462&#"6"3277>BM/ApC1~!Ȁm } 7k^>53#"&54747.1:6#1@,Ly6W37W?:X.kH$ /KsID78]'654#"'67632]1-(_D3,Qr-D64=EP<27# 7>77%)!5!+&7WL H[7P2'67654'"#"'732<.$    v XTP+# 0 - 6 7P9>32'>7#"54>7&#"#"'732632 LL6f* &0 O8, & Y " Y V$. $ go 5  ,  $   5 0 7Z>3267.#"7z$+)"h4WN,?98!!$&)M*g(P+7# 7>7'>7&(!5"*%%)!5!+&7XL G\WL H[7#, %>7'>7'>7y%)!5$($&(!5"*%%)!5!+&7T K QYXL G\WL H[7 2#"&546}!/*#-),".,! .+ ?$: s +P F $,=   ~    H _ 6}   V | HQ X  O x]  ] \k ^t$$ $Ho$T*GCopyleft 2002, 2003, 2005, 2008, 2009 Free Software Foundation.Copyleft 2002, 2003, 2005, 2008, 2009 Free Software Foundation.FreeSansFreeSansMediumMediumFontForge 2.0 : Free Sans : 9-5-2010FontForge 2.0 : Free Sans : 9-5-2010Free SansFree SansVersion $Revision: 1.256 $ Version $Revision: 1.256 $ FreeSansFreeSansGNUGNUhttps://savannah.gnu.org/projects/freefont/https://savannah.gnu.org/projects/freefont/The use of this font is granted subject to GNU General Public License.The use of this font is granted subject to GNU General Public License.http://www.gnu.org/copyleft/gpl.htmlhttp://www.gnu.org/copyleft/gpl.htmlThe quick brown fox jumps over the lazy dog.The quick brown fox jumps over the lazy dog.VaNormalJovencillo emponzoado de whisky, qu mala figurota exhibes.navadnoDovoljena je uporaba v skladu z licenco GNU General Public License.http://www.gnu.org/copyleft/gpl.html`erif bo za vajo spet kuhal doma e ~gance.1KG=K9 G0I0E N30 68;-1K; F8B@CA... 40, => D0;LH82K9 M:75<?;O@J!odmiana zwykBaMedioMittelZwlf Boxkmpfer jagen Victor quer ber den groen Sylter Deich.NormalPortez ce vieux whisky au juge blond qui fume.GemiddeldZweedse ex-VIP, behoorl3k gek op quantumfysica.oby ejnP2  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghjikmlnoqprsutvwxzy{}|~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~                           ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~        !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ softhyphenmicromiddotAmacronamacronAbreveabreveAogonekaogonek Ccircumflex ccircumflex Cdotaccent cdotaccentDcarondcaronDcroatEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflex Gdotaccent gdotaccent Gcommaaccent gcommaaccent Hcircumflex hcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonekIJij Jcircumflex jcircumflex Kcommaaccent kcommaaccent kgreenlandicLacutelacute Lcommaaccent lcommaaccentLcaronlcaronLdotldotNacutenacute Ncommaaccent ncommaaccentNcaronncaron napostropheEngengOmacronomacronObreveobreve Ohungarumlaut ohungarumlautRacuteracute Rcommaaccent rcommaaccentRcaronrcaronSacutesacute Scircumflex scircumflexTcedillatcedillaTcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflexZacutezacute Zdotaccent zdotaccentlongsuni0180uni0181uni0182uni0183uni0184uni0185uni0186uni0187uni0188uni0189uni018Auni018Buni018Cuni018Duni018Euni018Funi0190uni0191uni0193uni0194uni0195uni0196uni0197uni0198uni0199uni019Auni019Buni019Cuni019Duni019EObarOhornohornuni01A2uni01A3uni01A4uni01A5uni01A6uni01A7uni01A8uni01A9uni01AAuni01ABuni01ACuni01ADuni01AEUhornuhornuni01B1uni01B2uni01B3uni01B4uni01B5uni01B6Yoghuni01B8uni01B9uni01BAuni01BBuni01BEwynnuni01C0uni01C1uni01C2uni01C3uni01C4uni01C5uni01C6uni01C7uni01C8uni01C9uni01CAuni01CBuni01CCuni01CDuni01CEuni01CFuni01D0uni01D1uni01D2uni01D3uni01D4uni01D5uni01D6uni01D7uni01D8uni01D9uni01DAuni01DBuni01DCuni01DDuni01DEuni01DFuni01E0uni01E1uni01E2uni01E3uni01E4uni01E5Gcarongcaronuni01E8uni01E9uni01EAuni01EBuni01ECuni01EDuni01EEuni01EFuni01F0uni01F1uni01F2uni01F3uni01F4uni01F5Wynnuni01F8uni01F9 Aringacute aringacuteAEacuteaeacute Oslashacute oslashacuteuni0200uni0201uni0202uni0203uni0204uni0205uni0206uni0207uni0208uni0209uni020Auni020Buni020Cuni020Duni020Euni020Funi0210uni0211uni0212uni0213uni0214uni0215uni0216uni0217 Scommaaccent scommaaccentuni021Auni021Buni021Cuni021Duni021Euni021Funi0224uni0225 A_dotaccent a_dotaccentuni0228uni0229uni022Auni022Buni022Cuni022Duni022Euni022Funi0230uni0231uni0232uni0233dotlessjuni0250uni0251uni0252uni0253uni0254uni0255uni0256uni0257uni0258uni0259uni025Auni025Buni025Cuni025Duni025Euni025Funi0260uni0261uni0262uni0263uni0264uni0265uni0266uni0267uni0268uni0269uni026Auni026Buni026Cuni026Duni026Euni026Funi0270uni0271uni0272uni0273uni0274uni0275uni0276uni0277uni0278uni0279uni027Auni027Buni027Cuni027Duni027Euni027Funi0280uni0281uni0282uni0283uni0284uni0285uni0286uni0287uni0288uni0289uni028Auni028Buni028Cuni028Duni028Euni028Funi0290uni0291yoghuni0293uni0294uni0295uni0296uni0297uni0298uni0299uni029Auni029Buni029Cuni029Duni029Euni029Funi02A0uni02A1uni02A2uni02A3uni02A4uni02A5uni02A6uni02A7uni02A8uni02A9uni02AAuni02ABuni02ACuni02ADuni02AEuni02AFuni02B0uni02B1uni02B2uni02B3uni02B4uni02B5uni02B6uni02B7uni02B8primemod dblprimemod quoteleftmod apostrophe apostropherevuni02BE ringhalfleftuni02C0uni02C1uni02C2uni02C3uni02C4uni02C5verticallinemodmacronmodifier acutemodifier gravemodifierverticallinelowmod macronlowmodgravesubacutesubcolontriangularmodcolontriangularhalfmodringhalfrightcenteredringhalfleftcentered tackupmid tackdownmidplusmodminusmoduni02DEuni02DFuni02E0uni02E1uni02E2uni02E3uni02E4 toneextrahightonehightonemidtonelow toneextralowuni02EAuni02EBuni02ECuni02EDuni02EEuni02EFuni02F0uni02F1uni02F2uni02F3uni02F4uni02F5uni02F6uni02F7uni02F8uni02F9uni02FAuni02FBuni02FCuni02FDuni02FEuni02FF gravecomb acutecombuni0302 tildecombuni0304uni0305uni0306uni0307 diaeresiscomb hookabovecombuni030Auni030Buni030Cverticallineabovecmbdblverticallineabovecmb gravedblnospuni0310breveinvertedcmbcommaturnedabovecmb psilicomb dasiacombcommaaboverightcmbuni0316uni0317uni0318uni0319uni031Ahorncmbringlefthalfsubnospuni031Duni031Euni031Funi0320uni0321uni0322 dotbelowcombuni0324uni0325uni0326uni0327 ogonekcmblinevertsubnospuni032Auni032Buni032Cuni032Duni032Euni032Funi0330uni0331uni0332uni0333uni0334uni0335uni0336uni0337 slashlongnospringrighthalfsubnospuni033Auni033Bseagullbelowcmbuni033Duni033Euni033Funi0340uni0341uni0342uni0343diaeresistonosnospuni0345uni0346uni0347uni0348uni0349uni034Auni034Buni034Cuni034Duni034Euni034Funi0350uni0351uni0352uni0353uni0354uni0355uni0356uni0357uni0358uni0359uni035Auni035Buni035Cuni035Duni035Euni035Funi0360uni0361uni0362uni0363uni0364uni0365uni0366uni0367uni0368uni0369uni036Auni036Buni036Cuni036Duni036Euni036Funi0374uni0375 ypogegrammeniuni037Etonos dieresistonos Alphatonos anoteleia EpsilontonosEtatonos Iotatonos Omicrontonos Upsilontonos OmegatonosiotadieresistonosAlphaBetaGammauni0394EpsilonZetaEtaThetaIotaKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsi IotadieresisUpsilondieresis alphatonos epsilontonosetatonos iotatonosupsilondieresistonosalphabetagammadeltaepsilonzetaetathetaiotakappalambdauni03BCnuxiomicronrhosigma1sigmatauupsilonphichipsiomega iotadieresisupsilondieresis omicrontonos upsilontonos omegatonostheta1 UpsilonhookUpsilonhooktonosUpsilonhookdiaeresisphi1 pisymbolgreekuni03D7uni03F0rhosymbolgreekuni03f4uni03F5Iecyrillic_grave Iocyrillic Djecyrillic Gjecyrillic Ecyrillic Dzecyrillic Icyrillic Yicyrillic Jecyrillic Ljecyrillic Njecyrillic Tshecyrillic KjecyrillicIicyrillic_graveUshortcyrillic Dzhecyrillic Acyrillic Becyrillic Vecyrillic Gecyrillic Decyrillic Iecyrillic Zhecyrillic Zecyrillic IicyrillicIishortcyrillic Kacyrillic Elcyrillic Emcyrillic Encyrillic Ocyrillic Pecyrillic Ercyrillic Escyrillic Tecyrillic Ucyrillic Efcyrillic Khacyrillic Tsecyrillic Checyrillic Shacyrillic ShchacyrillicHardsigncyrillic YericyrillicSoftsigncyrillicEreversedcyrillic IUcyrillic IAcyrillic acyrillic becyrillic vecyrillic gecyrillic decyrillic iecyrillic zhecyrillic zecyrillic iicyrilliciishortcyrillic kacyrillic elcyrillic emcyrillic encyrillic ocyrillic pecyrillic ercyrillic escyrillic tecyrillic ucyrillic efcyrillic khacyrillic tsecyrillic checyrillic shacyrillic shchacyrillichardsigncyrillic yericyrillicsoftsigncyrillicereversedcyrillic iucyrillic iacyrilliciecyrillic_grave iocyrillic djecyrillic gjecyrillic ecyrillic dzecyrillic icyrillic yicyrillic jecyrillic ljecyrillic njecyrillic tshecyrillic kjecyrilliciicyrillic_graveushortcyrillic dzhecyrillic Omegacyrillic omegacyrillicuni0470uni0471 Fitacyrillic fitacyrillicOmegatitlocyrillicomegatitlocyrillic Otcyrillic otcyrillicthousandcyrillictitlocyrilliccmbpalatalizationcyrilliccmbdasiapneumatacyrilliccmbpsilipneumatacyrilliccmbuni0487uni0488uni0489uni048auni048buni048Cuni048Duni048Euni048F afii10050 afii10098uni0492uni0493uni0494uni0495uni0496uni0497uni0498uni0499uni049Auni049Buni049Cuni049Duni049Euni049Funi04A0uni04A1uni04A2uni04A3uni04A4uni04A5uni04A6uni04A7Haabkhasiancyrillichaabkhasiancyrillicuni04AAuni04ABuni04ACuni04ADuni04AEuni04AFuni04B0uni04B1uni04B2uni04B3uni04B4uni04B5uni04B6uni04B7uni04B8uni04B9uni04BAuni04BBuni04BCuni04BDuni04BEuni04BFuni04C0uni04C1uni04C2uni04C3uni04C4uni04C5uni04C6uni04C7uni04C8uni04C9uni04CAuni04CBuni04CCuni04CDuni04CEuni04CFuni04D0uni04D1uni04D2uni04D3uni04D4uni04D5uni04D6uni04D7uni04D8 afii10846uni04DAuni04DBuni04DCuni04DDuni04DEuni04DFDzeabkhasiancyrillicuni04E1uni04E2uni04E3uni04E4uni04E5uni04E6uni04E7uni04E8uni04E9uni04EAuni04EBuni04ECuni04EDuni04EEuni04EFuni04F0uni04F1uni04F2uni04F3uni04F4uni04F5uni04f6uni04f7uni04F8uni04F9uni0510uni0511uni0512uni0513uni051auni051buni051cuni051duni051euni051funi0531uni0532uni0533uni0534uni0535uni0536uni0537uni0538uni0539uni053Auni053Buni053Cuni053Duni053Euni053Funi0540uni0541uni0542uni0543uni0544uni0545uni0546uni0547uni0548uni0549uni054Auni054Buni054Cuni054Duni054Euni054Funi0550uni0551uni0552uni0553uni0554uni0555uni0556uni0559uni055Auni055Buni055Cuni055Duni055Euni055Funi0561uni0562uni0563uni0564 echarmenianuni0566uni0567uni0568uni0569uni056A iniarmenianuni056C xeharmenianuni056Euni056Funi0570uni0571uni0572uni0573 menarmenianuni0575 nowarmenianuni0577uni0578uni0579uni057Auni057Buni057Cuni057D vewarmenianuni057Funi0580uni0581uni0582uni0583uni0584uni0585uni0586uni0587uni0589uni058A afii57799 afii57801 afii57800 afii57802 hiriqhebrew afii57794 afii57795 patahhebrew qamatshebrew holamhebrew afii57796 dageshhebrew afii57839 afii57645 rafehebrew afii57842 shindothebrew sindothebrewsofpasuqhebrewupperdothebrew alefhebrew bethebrew gimelhebrew dalethebrewhehebrew vavhebrew zayinhebrew hethebrew tethebrew yodhebrewfinalkafhebrew kafhebrew lamedhebrewfinalmemhebrew memhebrewfinalnunhebrew nunhebrew samekhhebrew ayinhebrew finalpehebrewpehebrew tsadifinal tsadihebrew qofhebrew reshhebrew shinhebrew tavhebrew vavvavhebrew vavyodhebrew yodyodhebrew gereshhebrewgershayimhebrewuni0700uni0701uni0702uni0703uni0704uni0705uni0706uni0707uni0708uni0709uni070Auni070Buni070Cuni070Duni070Funi0710uni0711uni0712uni0713uni0714uni0715uni0716uni0717uni0718uni0719uni071Auni071Buni071Cuni071Duni071Euni071Funi0720uni0721uni0722uni0723uni0724uni0725uni0726uni0727uni0728uni0729uni072Auni072Buni072Cuni0730uni0731uni0732uni0733uni0734uni0735uni0736uni0737uni0738uni0739uni073Auni073Buni073Cuni073Duni073Euni073Funi0740uni0741uni0742uni0743uni0744uni0745uni0746uni0747uni0748uni0749uni074Auni0901 anusvaradevauni0903uni0904uni0905uni0906uni0907uni0908uni0909uni090Auni090Buni090Cuni090Duni090Euni090Funi0910uni0911uni0912uni0913uni0914uni0915uni0916uni0917uni0918uni0919uni091Auni091Buni091Cuni091Duni091Euni091Funi0920uni0921uni0922uni0923uni0924uni0925uni0926uni0927uni0928uni0929uni092Auni092Buni092Cuni092Duni092Euni092Funi0930uni0931uni0932uni0933uni0934uni0935uni0936uni0937uni0938uni0939 nuktadevauni093Duni093Euni093Funi0940uni0941uni0942uni0943uni0944ecandravowelsigndevauni0946uni0947uni0948uni0949uni094Auni094Buni094Cuni094Duni0950uni0951uni0952uni0953uni0954uni0958uni0959uni095Auni095Buni095Cuni095Duni095Euni095Funi0960uni0961uni0962uni0963uni0964uni0965uni0966uni0967uni0968uni0969uni096Auni096Buni096Cuni096Duni096Euni096Funi0970uni0971uni0972bn_candrabindu bn_anusvara bn_visargabn_abn_aabn_ibn_iibn_ubn_uubn_ribn_libn_ebn_aibn_obn_aubn_kabn_khabn_gabn_ghabn_ngabn_cabn_chabn_jabn_jhabn_nyabn_ttabn_tthabn_ddabn_ddhabn_nnabn_tabn_thabn_dabn_dhabn_nabn_pabn_phabn_babn_bhabn_mabn_yabn_rabn_labn_shabn_ssabn_sabn_habn_nukta bn_avagraha bn_aakaarbn_ikaar bn_iikaarbn_ukaar bn_uukaar bn_rikaar bn_rrikaarbn_ekaar bn_aikaarbn_okaar bn_aukaar bn_hasanta bn_half_ta bn_aumarkbn_rrabn_rhabn_yyabn_rribn_lli bn_likaar bn_llikaarbn_zerobn_onebn_twobn_threebn_fourbn_fivebn_sixbn_sevenbn_eightbn_nine bn_asamira bn_asamiba bn_rupeemark bn_rupeesign bn_currency1 bn_currency2 bn_currency3 bn_currency4bn_currencyless bn_currency16 bn_issharuni0A01uni0A02uni0A03uni0A05uni0A06uni0A07uni0A08uni0A09uni0A0Auni0A0Funi0A10uni0A13uni0A14uni0A15uni0A16uni0A17uni0A18uni0A19uni0A1Auni0A1Buni0A1Cuni0A1Duni0A1Euni0A1Funi0A20uni0A21uni0A22uni0A23uni0A24uni0A25uni0A26uni0A27uni0A28uni0A2Auni0A2Buni0A2Cuni0A2Duni0A2Euni0A2Funi0A30uni0A32uni0A33uni0A35uni0A36uni0A38uni0A39uni0A3Cuni0A3Euni0A3Funi0A40uni0A41uni0A42uni0A47uni0A48uni0A4Buni0A4Cuni0A4Duni0A59uni0A5Auni0A5Buni0A5Cuni0A5Euni0A66uni0A67uni0A68uni0A69uni0A6Auni0A6Buni0A6Cuni0A6Duni0A6Euni0A6Funi0A70uni0A71uni0A72uni0A73uni0A74uni0A81uni0A82uni0A83uni0A85uni0A86uni0A87uni0A88uni0A89uni0A8Auni0A8Buni0A8Duni0A8Funi0A90uni0A91uni0A93uni0A94uni0A95uni0A96uni0A97uni0A98uni0A99uni0A9Auni0A9Buni0A9Cuni0A9Duni0A9Euni0A9Funi0AA0uni0AA1uni0AA2uni0AA3uni0AA4uni0AA5uni0AA6uni0AA7uni0AA8uni0AAAuni0AABuni0AACuni0AADuni0AAEuni0AAFuni0AB0uni0AB2uni0AB3uni0AB5uni0AB6uni0AB7uni0AB8uni0AB9uni0ABCuni0ABDuni0ABEuni0ABFuni0AC0uni0AC1uni0AC2uni0AC3uni0AC4uni0AC5uni0AC7uni0AC8uni0AC9uni0ACBuni0ACCuni0ACDuni0AD0uni0AE0uni0AE6uni0AE7uni0AE8uni0AE9uni0AEAuni0AEBuni0AECuni0AEDuni0AEEuni0AEFuni0AF1uni0B82uni0B83uni0B85uni0B86uni0B87uni0B88uni0B89uni0B8Auni0B8Euni0B8Funi0B90uni0B92uni0B93uni0B94uni0B95uni0B99uni0B9Auni0B9Cuni0B9Euni0B9Funi0BA3uni0BA4uni0BA8uni0BA9uni0BAAuni0BAEuni0BAFuni0BB0uni0BB1uni0BB2uni0BB3uni0BB4uni0BB5uni0BB7uni0BB8uni0BB9uni0BBEuni0BBFuni0BC0uni0BC1uni0BC6uni0BC7uni0BC8uni0BCAuni0BCBuni0BCCuni0BCDuni0BD7uni0beauni0bebuni0becuni0beduni0bf1uni10A0uni10A1uni10A2uni10A3uni10A4uni10A5uni10A6uni10A7uni10A8uni10A9uni10AAuni10ABuni10ACuni10ADuni10AEuni10AFuni10B0uni10B1uni10B2uni10B3uni10B4uni10B5uni10B6uni10B7uni10B8uni10B9uni10BAuni10BBuni10BCuni10BDuni10BEuni10BFuni10C0uni10D0uni10D1uni10D2uni10D3uni10D4uni10D5uni10D6uni10D7uni10D8uni10D9uni10DAuni10DBuni10DCuni10DDuni10DEuni10DFuni10E0uni10E1uni10E2uni10E3uni10E4uni10E5uni10E6uni10E7uni10E8uni10E9uni10EAuni10EBuni10ECuni10EDuni10EEuni10EFuni10F0uni10F1uni10F2uni10F3uni10F4uni10F5uni10f9uni10fbuni1E00uni1E01 Bdotaccent bdotaccentuni1E04uni1E05uni1E06uni1E07uni1E08uni1E09 Ddotaccent ddotaccentuni1E0Cuni1E0Duni1E0Euni1E0Funi1E10uni1E11uni1E12uni1E13uni1E14uni1E15uni1E16uni1E17uni1E18uni1E19uni1E1Auni1E1Buni1E1Cuni1E1D Fdotaccent fdotaccentuni1E20uni1E21 Hdotaccent hdotaccentuni1E24uni1E25uni1E26uni1E27uni1E28uni1E29uni1E2Auni1E2Buni1E2Cuni1E2Duni1E2Euni1E2Funi1E30uni1E31uni1E32uni1E33uni1E34uni1E35uni1E36uni1E37uni1E38uni1E39uni1E3Auni1E3Buni1E3Cuni1E3Duni1E3Euni1E3F Mdotaccent mdotaccentuni1E42uni1E43uni1E44uni1E45uni1E46uni1E47uni1E48uni1E49uni1E4Auni1E4Buni1E4Cuni1E4Duni1E4Euni1E4Funi1E50uni1E51uni1E52uni1E53uni1E54uni1E55 Pdotaccent pdotaccent Rdotaccent rdotaccentuni1E5Auni1E5Buni1E5Cuni1E5Duni1E5Euni1E5F Sdotaccent sdotaccent Sdotbelow sdotbelowuni1E64uni1E65uni1E66uni1E67uni1E68uni1E69 Tdotaccent tdotaccent Tdotbelow tdotbelowuni1E6Euni1E6Funi1E70uni1E71uni1E72uni1E73uni1E74uni1E75uni1E76uni1E77uni1E78uni1E79uni1E7Auni1E7Buni1E7Cuni1E7Duni1E7Euni1E7FWgravewgraveWacutewacute Wdieresis wdieresisuni1E86uni1E87uni1E88uni1E89uni1E8Auni1E8Buni1E8Cuni1E8D Ydotaccent ydotaccentuni1E90uni1E91uni1E92uni1E93uni1E94uni1E95uni1E96uni1E97uni1E98uni1E99uni1e9auni1E9Buni1EA0uni1EA1uni1EA2uni1EA3uni1EA4uni1EA5uni1EA6uni1EA7uni1EA8uni1EA9uni1EAAuni1EABuni1EACuni1EADuni1EAEuni1EAFuni1EB0uni1EB1uni1EB2uni1EB3uni1EB4uni1EB5uni1EB6uni1EB7uni1EB8uni1EB9uni1EBAuni1EBBEtildeuni1EBDuni1EBEuni1EBFuni1EC0uni1EC1uni1EC2uni1EC3uni1EC4uni1EC5uni1EC6uni1EC7uni1EC8uni1EC9uni1ECAuni1ECBuni1ECCuni1ECDuni1ECEuni1ECFuni1ED0uni1ED1uni1ED2uni1ED3uni1ED4uni1ED5uni1ED6uni1ED7uni1ED8uni1ED9uni1EDAuni1EDBuni1EDCuni1EDDuni1EDEuni1EDFuni1EE0uni1EE1uni1EE2uni1EE3uni1EE4uni1EE5uni1EE6uni1EE7uni1EE8uni1EE9uni1EEAuni1EEBuni1EECuni1EEDuni1EEEuni1EEFuni1EF0uni1EF1Ygraveygraveuni1EF4uni1EF5uni1EF6uni1EF7uni1EF8uni1EF9uni1F00uni1F01uni1F02uni1F03uni1F04uni1F05uni1F06uni1F07uni1F08uni1F09uni1F0Auni1F0Buni1F0Cuni1F0Duni1F0Euni1F0Funi1F10uni1F11uni1F12uni1F13uni1F14uni1F15uni1F18uni1F19uni1F1Auni1F1Buni1F1Cuni1F1Duni1F20uni1F21uni1F22uni1F23uni1F24uni1F25uni1F26uni1F27uni1F28uni1F29uni1F2Auni1F2Buni1F2Cuni1F2Duni1F2Euni1F2Funi1F30uni1F31uni1F32uni1F33uni1F34uni1F35uni1F36uni1F37uni1F38uni1F39uni1F3Auni1F3Buni1F3Cuni1F3Duni1F3Euni1F3Funi1F40uni1F41uni1F42uni1F43uni1F44uni1F45uni1F48uni1F49uni1F4Auni1F4Buni1F4Cuni1F4Duni1F50uni1F51uni1F52uni1F53uni1F54uni1F55uni1F56uni1F57uni1F59uni1F5Buni1F5Duni1F5Funi1F60uni1F61uni1F62uni1F63uni1F64uni1F65uni1F66uni1F67uni1F68uni1F69uni1F6Auni1F6Buni1F6Cuni1F6Duni1F6Euni1F6Funi1F70uni1F71uni1F72uni1F73uni1F74uni1F75uni1F76uni1F77uni1F78uni1F79uni1F7Auni1F7Buni1F7Cuni1F7Duni1F80uni1F81uni1F82uni1F83uni1F84uni1F85uni1F86uni1F87uni1F88uni1F89uni1F8Auni1F8Buni1F8Cuni1F8Duni1F8Euni1F8Funi1F90uni1F91uni1F92uni1F93uni1F94uni1F95uni1F96uni1F97uni1F98uni1F99uni1F9Auni1F9Buni1F9Cuni1F9Duni1F9Euni1F9Funi1FA0uni1FA1uni1FA2uni1FA3uni1FA4uni1FA5uni1FA6uni1FA7uni1FA8uni1FA9uni1FAAuni1FABuni1FACuni1FADuni1FAEuni1FAFuni1FB0uni1FB1uni1FB2uni1FB3uni1FB4uni1FB6uni1FB7uni1FB8uni1FB9uni1FBAuni1FBBuni1FBClenisprosgegrammenipsili perispomenidialytikaperispomeniuni1FC2uni1FC3uni1FC4uni1FC6uni1FC7uni1FC8uni1FC9uni1FCAuni1FCBuni1FCC psilivaria psilioxiapsiliperispomeniuni1FD0uni1FD1uni1FD2uni1FD3uni1FD6uni1FD7uni1FD8uni1FD9uni1FDAuni1FDB dasiavaria dasiaoxiadasiaperispomeniuni1FE0uni1FE1uni1FE2uni1FE3uni1FE4uni1FE5uni1FE6uni1FE7uni1FE8uni1FE9uni1FEAuni1FEBuni1FECdialytikavaria dialytikaoxiavariauni1FF2uni1FF3uni1FF4uni1FF6uni1FF7uni1FF8uni1FF9uni1FFAuni1FFBuni1FFCoxiadasiaenquademquadenspaceemspacethreeperemspacefourperemspace sixperemspace figurespacepunctuationspace thinspace hairspacezerowidthspacezerowidthnonjoinerzerojoinuni200Euni200Funi2010uni2011 figuredash afii00208uni2016 underscoredbl quotereverseduni201Funi2023onedotenleadertwodotenleader hyphendot lineseparatorparagraphseparatorlrerlepdflrorlouni202Funi2031minuteseconduni2034uni2035uni2036uni2037caretuni203B exclamdbluni203Doverlineuni203Ftieuni2041asterismuni2043uni2045uni2046uni2047uni2048uni2049uni204Auni204Buni204Cuni204Duni204Euni204Fclosureuni2051uni2052uni2053uni2054uni2055uni2056uni2057uni2058uni2059uni205Auni205Buni205Cuni205Duni205Euni205Funi2060uni2061uni2062uni2063uni2064 zerosuperioruni2071 foursuperior fivesuperior sixsuperior sevensuperior eightsuperior ninesuperioruni207Auni207Buni207Cuni207Duni207Euni207F zeroinferior oneinferior twoinferior threeinferior fourinferior fiveinferior sixinferior seveninferior eightinferior nineinferioruni208Auni208Buni208Cuni208Duni208Euni2090uni2091uni2092uni2093uni2094 colonmonetarycruzeiroliramilluni20A6pesetauni20A8won afii57636dongEurouni20ADuni20AEuni20B0uni20B1uni20B2uni20B3uni20B4uni20B5leftharpoonaccentrightharpoonaccentuni20D2uni20D3uni20D6uni20D7uni20DBuni20DCuni20DDuni20DEuni20DFuni20E0uni20E1uni20E5uni20E6uni20E7uni20E8uni20E9uni20EAuni20EBuni20ECuni20EDuni20EEuni20EFuni20F0uni2100uni2101uni2103uni2104uni2105uni2106uni2107uni2109uni210Buni210Cuni210euni210funi2110Ifrakturuni2112 afii61289uni2114 afii61352uni2117 weierstrassuni211BRfrakturuni2120uni2121uni2126uni2127uni2128uni2129uni212Aangstromuni212Cuni212Duni212eescriptuni2130uni2131uni2132uni2133uni2135uni2136uni2137uni2138uni2139uni213auni213buni2141uni2142uni2143uni2144uni214auni214buni214duni214eonethird twothirdsuni2155uni2156uni2157uni2158uni2159uni215A oneeighth threeeighths fiveeighths seveneighthsuni215Funi2160uni2161uni2162uni2163uni2164uni2165uni2166uni2167uni2168uni2169uni216Auni216Buni216Cuni216Duni216Euni216Funi2170uni2171uni2172uni2173uni2174uni2175uni2176uni2177uni2178uni2179uni217Auni217Buni217Cuni217Duni217Euni217F arrowleftarrowup arrowright arrowdown arrowboth arrowupdn arrowupleft arrowuprightarrowdownright arrowdownleft arrowbarright arrowhookleftarrowhookrightcarriagereturnharpoonleftbarbuparrowleftbothalfharpoonupright harpoonupleftharpoonrightbarbuparrowrightbothalfharpoondownrightharpoondownleftharpoonrightleft arrowdblleft arrowdblup arrowdblright arrowdbldown arrowdblboth arrowdblbothvarrowsquiggleright universal existentialemptyset Delta.mathgradientelement notelementuni220Asuchthat notcontains ownersmalluni2210uni2213dotplus slashmath backslashmath asteriskmathuni2219 proportionalangledividesnotbarparallel notparallel logicaland logicalor intersectionunionuni222Cuni222Duni222E thereforesimilar reversedtildelazysinv wreathproduct notsimilaruni2242 asymptequalnotasymptequal congruentapproxnotequalnotapproxequaluni2249equivasymptotic approaches equivalence notidenticalmuchless muchgreaternotequivasymptoticnotless notgreater notlessequalnotgreaterequaluni2272uni2273precedesfollows precedesequal followsequal notprecedes notsucceedssubsetsuperset notsubset notsuperset reflexsubsetreflexsuperset notsubseteqlnotsuperseteqluni228C squareimagesquareoriginal subsetsqequalsupersetsqequalintersectionsqunionsq circleplusuni2296circlemultiply circledivide circledot turnstilelefttacklefttackdown perpendicular assertion truestate triangleright trianglelefttriangleftequaltriangrightequalnarylogicaland narylogicalornaryintersection naryuniondotmathuni22C6bowtiepreceedsnotequalfollowsnotequalnotsubsetsqequalnotsupersetsqequaluni2300 ceilingleft ceilingright floorleft floorright integraltp integralbtfrown slurbelow angleleft anglerightuni239Buni239Cuni239Duni239Euni239Funi23A0uni23A1uni23A2uni23A3uni23A4uni23A5uni23A6uni23AEblankbblankuni2460uni2461uni2462uni2463uni2464uni2465uni2466uni2467uni2468uni2469SF100000uni2501SF110000uni2503uni2504uni2505uni2506uni2507uni2508uni2509uni250Auni250BSF010000uni250Duni250Euni250FSF030000uni2511uni2512uni2513SF020000uni2515uni2516uni2517SF040000uni2519uni251Auni251BSF080000uni251Duni251Euni251Funi2520uni2521uni2522uni2523SF090000uni2525uni2526uni2527uni2528uni2529uni252Auni252BSF060000uni252Duni252Euni252Funi2530uni2531uni2532uni2533SF070000uni2535uni2536uni2537uni2538uni2539uni253Auni253BSF050000uni253Duni253Euni253Funi2540uni2541uni2542uni2543uni2544uni2545uni2546uni2547uni2548uni2549uni254Auni254BSF430000SF240000SF510000SF520000SF390000SF220000SF210000SF250000SF500000SF490000SF380000SF280000SF270000SF260000SF360000SF370000SF420000SF190000SF200000SF230000SF470000SF480000SF410000SF450000SF460000SF400000SF540000SF530000SF440000upblockuni2581uni2582uni2583dnblockuni2585uni2586uni2587blockuni2589uni258Auni258Blfblockuni258Duni258Euni258Frtblockltshadeshadedkshadeuni2594uni2595uni2596uni2597uni2598uni2599uni259Auni259Buni259Cuni259Duni259Euni259F filledboxH22073uni25A3uni25A4uni25A5H18543 filledrecttriangle triangleinv whitediamondcircle largecircleuni262Cspade heartopen diamondopenclubspadesuitwhiteheartdiamond clubsuitwhiteuni2669 musicalnotemusicalnotedbluni266Cuni266Duni266Euni266Funi2740uni2A00uni2A01uni2A02uni2A03uni2A04uni2A09uni2E17cresc_cyrillic dot_cyrillictitlo_cyrillic NameMe.E968 NameMe.E969 NameMe.96A bn_initekaar bn_initaikaarbn_reph bn_kaphala bn_nnaphala bn_taphala bn_thaphala bn_thaphala1 bn_dhaphala bn_naphala bn_below_ba bn_bhaphala bn_raphala bn_laphala bn_half_ka bn_half_kha bn_half_ga bn_half_gha bn_half_nga bn_half_ca bn_half_ca1 bn_half_cha bn_half_ja bn_half_jha bn_half_nya bn_half_tta bn_half_ttha bn_half_dda bn_half_ddha bn_half_nna bn_half_tha bn_half_dha bn_half_da bn_half_na bn_half_pa bn_half_pha bn_half_ba bn_half_bha bn_half_ma bn_half_ya bn_half_ra bn_half_la bn_half_sha bn_half_ssa bn_half_sa bn_half_ha bn_half_rra bn_half_rha bn_half_yyabn_half_asamirabn_half_asamiba bn_khaphala bn_phaphala bn_baphala1 bn_maphala bn_maphala1 bn_yaphalabn_k_rabn_k_ra1bn_kh_rabn_g_rabn_gh_rabn_c_rabn_ch_rabn_j_rabn_tt_ra bn_tth_rabn_dd_ra bn_ddh_rabn_t_rabn_t_ra1bn_th_rabn_d_rabn_dh_rabn_n_rabn_n_ra1bn_p_rabn_ph_rabn_b_rabn_bh_ra bn_bh_ra1bn_m_rabn_y_rabn_sh_rabn_ss_rabn_s_rabn_s_ra1bn_h_ra bn_asamir_ra bn_asamib_ra bn_k_ss_rabn_k_kabn_k_tta bn_k_tt_rabn_k_tabn_k_ta1 bn_k_t_ba bn_k_t_ba1 bn_k_t_ra bn_k_t_ra1 bn_k_t_ra2bn_k_nabn_k_mabn_k_labn_k_ssa bn_k_ss_nna bn_k_ss_mabn_k_sabn_g_gabn_g_dabn_g_dha bn_g_dh_babn_g_labn_g_nabn_g_mabn_gh_nabn_ng_ka bn_ng_k_ra bn_ng_k_ssa bn_ng_k_ss_ra bn_ng_khabn_ng_ga bn_ng_gha bn_ng_gh_rabn_ng_ma bn_ng_ma1bn_c_cabn_c_cha bn_c_ch_ba bn_c_ch_rabn_c_nyabn_c_nabn_j_ja bn_j_j_babn_j_jhabn_j_nyabn_ny_ca bn_ny_chabn_ny_ja bn_ny_jha bn_tt_tta bn_tt_tt_rabn_tt_mabn_dd_ga bn_dd_ddabn_dd_ma bn_nn_tta bn_nn_tt_ra bn_nn_ttha bn_nn_dda bn_nn_dda1 bn_nn_dd_ra bn_nn_dd_ra1 bn_nn_ddha bn_nn_nnabn_nn_mabn_t_ta bn_t_t_babn_t_thabn_t_nabn_t_mabn_t_ma1bn_t_labn_d_gabn_d_ghabn_d_da bn_d_d_ba bn_d_d_rabn_d_dha bn_d_dh_babn_d_nabn_d_bha bn_d_bh_ra bn_d_bh_ra1bn_d_mabn_dh_nabn_dh_mabn_n_tta bn_n_tt_ra bn_n_tthabn_n_dda bn_n_dd_ra bn_n_ddhabn_n_ta bn_n_t_ba bn_n_t_ra bn_n_t_ra1bn_n_thabn_n_da bn_n_d_ba bn_n_d_rabn_n_dha bn_n_dh_ba bn_n_dh_rabn_n_nabn_n_mabn_n_ma1bn_n_sabn_p_ttabn_p_tabn_p_pabn_p_mabn_p_nabn_p_labn_p_sa bn_ph_ttabn_ph_labn_b_jabn_b_da bn_b_d_rabn_b_dhabn_b_nabn_b_bhabn_b_labn_bh_labn_m_tabn_m_thabn_m_dabn_m_nabn_m_na1bn_m_pa bn_m_p_ra bn_m_p_labn_m_pha bn_m_ph_rabn_m_bha bn_m_bh_ra bn_m_bh_ra1bn_m_mabn_m_ma1bn_m_labn_m_la1bn_m_sa bn_m_s_rabn_l_kabn_l_gabn_l_tta bn_l_tt_rabn_l_dda bn_l_dd_rabn_l_tabn_l_dabn_l_dhabn_l_pabn_l_pha bn_l_ph_rabn_l_mabn_l_labn_sh_ca bn_sh_chabn_sh_tabn_sh_nabn_sh_mabn_sh_labn_ss_ka bn_ss_k_ra bn_ss_k_ra1 bn_ss_tta bn_ss_tta1 bn_ss_tt_ra bn_ss_tt_ra1 bn_ss_ttha bn_ss_nnabn_ss_pa bn_ss_p_ra bn_ss_pha bn_ss_ph_rabn_ss_ma bn_ss_ma1bn_s_ka bn_s_k_ra bn_s_k_ra1bn_s_khabn_s_tta bn_s_tta1 bn_s_tt_ra bn_s_tt_ra1bn_s_ta bn_s_t_ba bn_s_t_ra bn_s_t_ra1bn_s_thabn_s_nabn_s_na1bn_s_pa bn_s_p_ra bn_s_p_labn_s_pha bn_s_ph_rabn_s_mabn_s_ma1bn_s_labn_s_la1bn_h_nnabn_h_nabn_h_mabn_h_labn_h_la1bn_rr_gabn_k_babn_g_babn_gh_babn_c_babn_ch_babn_j_babn_tt_babn_dd_babn_nn_babn_t_babn_th_babn_d_babn_dh_ba bn_dh_ba1bn_n_babn_n_ba1bn_p_babn_b_babn_bh_babn_m_ba bn_m_b_rabn_m_ba1 bn_m_b_ra1bn_l_babn_sh_babn_s_babn_s_ba1bn_h_babn_h_ba1 bn_kh_r_ukaarbn_kh_r_uukaar bn_g_ukaar bn_g_r_ukaar bn_g_r_uukaar bn_g_l_ukaar bn_g_l_uukaar bn_j_r_ukaar bn_j_r_uukaar bn_t_r_ukaar bn_t_r_uukaar bn_th_r_ukaarbn_th_r_uukaar bn_d_ukaar bn_d_r_ukaar bn_d_r_uukaar bn_dh_r_ukaarbn_dh_r_uukaar bn_n_ukaar bn_n_uukaar bn_n_rikaar bn_n_t_ukaarbn_n_d_r_ukaarbn_n_d_r_uukaar bn_p_r_ukaar bn_p_r_uukaar bn_p_l_ukaar bn_p_l_uukaar bn_b_r_ukaar bn_b_r_uukaar bn_b_l_ukaar bn_b_l_uukaar bn_bh_r_ukaarbn_bh_r_uukaar bn_m_r_ukaar bn_m_r_uukaarbn_m_p_r_ukaarbn_m_p_r_uukaar bn_r_ukaar bn_r_uukaar bn_l_g_ukaar bn_sh_ukaar bn_sh_r_ukaarbn_sh_r_uukaar bn_sh_l_ukaarbn_sh_l_uukaarbn_ss_p_r_ukaarbn_ss_p_r_uukaar bn_s_ukaar bn_s_uukaar bn_s_rikaar bn_s_t_ukaar bn_s_r_ukaar bn_s_r_uukaarbn_s_p_r_ukaarbn_s_p_r_uukaarbn_s_p_l_ukaarbn_s_p_l_uukaar bn_s_l_ukaar bn_s_l_uukaar bn_h_ukaar bn_h_rikaarbn_asamir_ukaarbn_asamir_uukaarbn_asamib_ukaarbn_asamib_uukaarbn_asamib_r_ukaarbn_asamib_r_uukaar bn_k_hasanta bn_kh_hasanta bn_g_hasanta bn_gh_hasanta bn_ng_hasanta bn_c_hasanta bn_ch_hasanta bn_j_hasanta bn_jh_hasanta bn_ny_hasanta bn_tt_hasantabn_tth_hasanta bn_dd_hasantabn_ddh_hasanta bn_nn_hasanta bn_t_hasanta bn_th_hasanta bn_d_hasanta bn_dh_hasanta bn_n_hasanta bn_p_hasanta bn_ph_hasanta bn_b_hasanta bn_bh_hasanta bn_m_hasanta bn_y_hasanta bn_r_hasanta bn_l_hasanta bn_sh_hasanta bn_ss_hasanta bn_s_hasanta bn_h_hasanta bn_rr_hasanta bn_rh_hasanta bn_yy_hasantabn_asamir_hasantabn_asamib_hasanta bn_post_k_raglyph569glyph570glyph571glyph572glyph57487 bn_baphala bn_below_ba2glyph578glyph579glyph580glyph581glyph582glyph583 bn_sh_ra.001bn_yaphala.002bn_d_yabn_n_ya bn_la.001bn_sh_yabn_ss_yabn_s_yaglyph593bn_h_yaglyph595 bn_ss_tta.002glyph597glyph598 bn_uukaar.1glyph600A.002A.003uni091F_nuktadeva.nuktuni0920_nuktadeva.nuktglyph240glyph241uni0908_anusvaradeva.abvsuni0947_uni0901.abvsuni094B_uni0901.abvsglyph245glyph246glyph247uni0939_uni094D.halnglyph249uni091B_uni094D.halnuni0939_uni0944.blwsuni0915_uni094D_uni0937.akhnuni091C_uni094D_uni091E.akhnuni924_94D.half_924.presuni924_930_94D.blwf.vatuuni936_94D.half_91A.presuni936_930_94D.blwf.vatuuni936_94D.half_935.presuni95B_930_94D.blwf.vatuuni92B_930_94D.blwf.vatuuni95E_930_94D.blwf.vatuuni92A_930_94D.blwf.vatuuni938_930_94D.blwf.vatuuni0930_uni0941.blwsuni0930_uni0942.blwsuni915_94D.half_928.presuni0916_094D.half_0928.presuni0918_094D.half_0928.presglyph269uni0915_uni094D.halfuni0916_uni094D.halfuni0917_uni094D.halfuni0918_uni094D.halfuni091A_uni094D.halfglyph275uni091C_uni094D.halfuni091D_uni094D.halfuni091E_uni094D.halfuni0924_094D.half_0928.presuni0925_094D.half_0928.presuni0926_094D.half_0928.presuni0927_094D.half_0928.presuni0923_uni094D.halfuni0924_uni094D.halfuni0925_uni094D.halfuni926_94D.hlf2_926_94D.presuni0927_uni094D.halfuni0928_uni094D.halfglyph289uni092A_uni094D.halfuni092B_uni094D.halfuni092C_uni094D.halfuni092D_uni094D.halfuni092E_uni094D.halfuni092F_uni094D.halfuni092A_094D.half_0928.presglyph297uni0932_uni094D.halfuni0933_uni094D.halfuni0934_uni094D.halfuni0935_uni094D.halfuni0936_uni094D.halfuni0937_uni094D.halfuni0938_uni094D.halfuni0939_uni094D.halfglyph306uni092C_094D.half_0928.presglyph308uni092E_094D.half_0928.presuni0935_094D.half_0928.presuni0936_094D.half_0928.presuni0915_094D.half_0915.presuni0919_094D.half_0915.presuni0919_094D.half_0916.presuni0919_094D.half_0917.presuni0919_094D.half_0918.presuni091E_094D.half_091C.presuni0926_094D.half_0918.presuni0926_094D.half_0926.presuni0926_094D.half_0927.presuni0926_094D.half_092C.presuni0926_094D.half_092D.presuni0926_094D.half_092E.presuni0926_094D.half_092F.presuni0926_094D.half_0935.presuni091F_094D.half_091F.presuni091F_094D.half_0920.presuni0920_094D.half_0920.presglyph329glyph330glyph331uni0939_094D.half_092E.presuni0939_094D.half_092F.presuni0932_094D.half_0939.presglyph335u9_38_4D.hlf_24_4D_30prespresglyph337uni0930_uni094D_afii301.halfu91A_94D.hlf2_91A_94D.halfuni0938_0928_094D.half.presglyph341glyph342glyph343glyph344glyph345glyph346glyph347glyph348glyph349uni915_94D_937.akhn_94D.halfu91C_94D_91E.akhn_94D.halfglyph352u924_94D.half_930_94D.blwf.vatuglyph354glyph355glyph356glyph357glyph358glyph359uni939_930_94D.blwf.blwsuni0930_uni094D.rphfuni0930_uni094D.blwfuni093E_uni0901.abvsuni093E_anusvaradeva.abvsglyph365glyph366glyph367glyph368glyph369uni0947_anusvaradeva.abvsglyph371glyph372uni0948_anusvaradeva.abvsglyph374glyph375glyph376glyph377glyph378u9_38_4Dhalf1F_30_4Dblwfvtu2glyph380glyph381uni0928_094D.half_0928.presuni0919_uni094D.halfuni091F_uni094D.halfuni0920_uni094D.halfuni0921_uni094D.halfuni0922_uni094D.halfuni0926_uni094D.halfuni915_930_94D.blwf.vatuuni916_930_94D.blwf.presuni917_930_94D.blwf.presuni918_930_94D.blwf.vatuuni919_930_94D.blwf.vatuuni91A_930_94D.blwf.vatuuni91B_930_94D.blwf.vatuuni91C_930_94D.blwf.vatuuni91D_930_94D.blwf.vatuuni91E_930_94D.blwf.vatuuni91F_930_94D.blwf.vatuuni920_930_94D.blwf.vatuuni921_930_94D.blwf.vatuglyph402uni923_930_94D.blwf.vatuuni0924_uni094D_uni0930.presuni925_930_94D.blwf.vatuuni926_930_94D.blwf.vatuuni927_930_94D.blwf.vatuuni928_930_94D.blwf.vatuuni92C_930_94D.blwf.vatuuni92D_930_94D.blwf.vatuuni92E_930_94D.blwf.vatuuni92F_930_94D.blwf.vatuuni930_930_94D.blwf.vatuuni932_930_94D.blwf.vatuuni936_94D.hlf2_932_94D.presuni937_94D.half_91F.presuni937_94D.half_920.presglyph418glyph419glyph420uni936_94D.hlf2_928_94D.presu938_4D.hlff24_4D.30_4Dbfvtuprsu926_94D.half_92E_94D.half.presuni924_94D.hlf2_928_94D.presuni939_94D.half_923.presuni939_94D.half_932.presuni939_94D.half_935.presu92A_94D.half_924_94D.half.presuni939_94D.half_928.presu939_94D.half_92E_94D.half.presu92A_94D.half_930_94D.blwf.vatuuni0919_nuktadeva.nuktuni0939_nuktadeva.nuktglyph434uni91F_93C.nukt_94D.halnuni920_93C.nukt_94D.halnuni095C_uni094D.halnuni095D_uni094D.halnglyph439uni939_93C.nukt_94D.halnuni919_93C.nukt_94D.halnu937_94D.half_91F.pres_94D.halnu937_94D.half_920.pres_94D.halnu939_94D.half_923.pres_94D.halnu939_94D.half_928.pres_94D.halnu939_94D.half_932.pres_94D.halnu939_94D.half_935.pres_94D.halnuni092B_nuktadeva_uni094D.nuktuni091C_nuktadeva_uni094D.nuktuni0A95_uni0ACD.halfuni0A96_uni0ACD.halfuni0A97_uni0ACD.halfuni0A98_uni0ACD.halfuni0A9A_uni0ACD.halfuni0A9C_uni0ACD.halfuni0A9D_uni0ACD.halfuni0A9E_uni0ACD.halfuni0AA3_uni0ACD.halfuni0AA4_uni0ACD.halfuni0AA5_uni0ACD.halfuni0AA7_uni0ACD.halfuni0AA8_uni0ACD.halfuni0AAA_uni0ACD.halfuni0AAB_uni0ACD.halfuni0AAC_uni0ACD.halfuni0AAD_uni0ACD.halfuni0AAE_uni0ACD.halfuni0AAF_uni0ACD.halfuni0AB2_uni0ACD.halfuni0AB3_uni0ACD.halfuni0AB5_uni0ACD.halfuni0AB6_uni0ACD.halfuni0AB7_uni0ACD.halfuni0AB8_uni0ACD.halfuni0AB9_uni0ACD.halfuniA95_ACD_AB7.akhn_ACD.halfuAA4_ACD.half_AA4_ACD.half.presuniAA4_ACD.half_AB0_ACD.vatuuAA8_ACD.half_AA8.pres_ACD.presuAB6_ACD.half_AB0_ACD.blwf.vatuuA96_ACD.half_AB0_ACD.blwf.vatuuA97_ACD.half_AB0_ACD.blwf.vatuuA98_ACD.half_AB0_ACD.blwf.vatuuA9A_ACD.half_AB0_ACD.blwf.vatuuA9C_ACD.half_AB0_ACD.blwf.vatuuAA5_ACD.half_AB0_ACD.blwf.vatuuAA7_ACD.half_AB0_ACD.blwf.vatuuAA8_ACD.half_AB0_ACD.blwf.vatuuAAA_ACD.half_AB0_ACD.blwf.vatuuAAC_ACD.half_AB0_ACD.blwf.vatuuAAD_ACD.half_AB0_ACD.blwf.vatuuAAE_ACD.half_AB0_ACD.blwf.vatuuAB8_ACD.half_AB0_ACD.blwf.vatuuni0A87_uni0A82.abvsuni0A88_uni0A82.abvsuni0A89_uni0A82.abvsuni0A8A_uni0A82.abvsuniA95_ACD.half_A95.presuniA95_AB0_ACD.blwf.vatuuni0A9C_uni0ABE.pstsuni0A9C_uni0AC0.pstsuniA9C_AB0_ACD.blwf.vatuuniA9D_AB0_ACD.blwf.vatuuni0A9F_uni0ACD_uni0A9F.presuni0A9F_uni0ACD_uni0AA0.presuni0AA0_uni0ACD_uni0AA0.presuni0AA1_uni0ACD_uni0AA1.presuni0AA1_uni0ACD_uni0AA2.presuniAA6_AB0_ACD.blwf.vatuuni0AA6_uni0ACD_uni0AAE.presuni0AA6_uni0ACD_uni0AA6.presuni0AA6_uni0ACD_uni0AA7.presuni0AA6_uni0ACD_uni0AB5.presuniAAB_AB0_ACD.blwf.vatuuni0AB0_uni0AC1.blwsuni0AB0_uni0AC2.blwsnounicode_3_1_cduAB5_ACD.half_AB0_ACD.blwf.vatuuniAB6_ACD.half_AB5_ACD.presuniAB9_AB0_ACD.blwf.vatuuniAB9_ACD.half_AAE.presuniAB9_ACD.half_AAF.presuAB6_ACD.half_AA8.pres_ACD.presuniAB6_ACD.half_A9A_ACD.presuni0AA2_uni0ACD_uni0AA2.presuni0A95_uni0ACD_uni0AB7.akhnuniAA4_ACD.half_AA4.presuniAA4_AB0_ACD.blwf.vatuuniAA8_ACD.half_AA8.presuniAB6_AB0_ACD.blwf.vatuuniA96_AB0_ACD.blwf.vatuuniA97_AB0_ACD.blwf.vatuuniA98_AB0_ACD.blwf.vatuuniA9A_AB0_ACD.blwf.vatuuniAA5_AB0_ACD.blwf.vatuuniAA7_AB0_ACD.blwf.vatuuniAA8_AB0_ACD.blwf.vatuuniAAA_AB0_ACD.blwf.vatuuniAAC_AB0_ACD.blwf.vatuuniAAD_AB0_ACD.blwf.vatuuniAAE_AB0_ACD.blwf.vatuuniAB8_AB0_ACD.blwf.vatuuniAB5_AB0_ACD.blwf.vatuuniAB6_ACD.half_AB5.presuniAB6_ACD.half_AA8.presuniAB6_ACD.half_A9A.presglyph407glyph408glyph409uni0AB0_uni0ACD.rphfglyph411uni0AB0_uni0ACD.blwfuniAB7_ACD.half_AA0.presuniAB7_ACD.half_A9F.presuA_B7_CD.half_9F_B0_CD.blwfvatuuA_B7_CD.half_A0_B0_CDblwfvatuglyph417glyph421uniA9F_AB0_ACD.blwf.vatuuni0A9C_uni0ACD_uni0A9E.akhnglyph424uniAA1_AB0_ACD.blwf.vatuuniAA0_AB0_ACD.blwf.vatuuniAA2_AB0_ACD.blwf.vatuglyph428glyph429glyph430glyph431glyph432glyph433glyph435glyph436glyph437glyph438glyph440glyph441uni0AC7_uni0A82.abvsglyph443glyph444glyph445glyph446glyph447glyph448glyph449glyph450glyph451glyph452glyph453uni0A95_uni0ABC.nuktuni0A96_uni0ABC.nuktuni0A97_uni0ABC.nuktuni0A98_uni0ABC.nuktuni0A99_uni0ABC.nuktuni0A9A_uni0ABC.nuktuni0A9B_uni0ABC.nuktuni0A9C_uni0ABC.nuktuni0A9D_uni0ABC.nuktuni0A9E_uni0ABC.nuktuni0A9F_uni0ABC.nuktuni0AA0_uni0ABC.nuktuni0AA1_uni0ABC.nuktuni0AA2_uni0ABC.nuktuni0AA3_uni0ABC.nuktuni0AA4_uni0ABC.nuktuni0AA5_uni0ABC.nuktuni0AA6_uni0ABC.nuktuni0AA7_uni0ABC.nuktuni0AA8_uni0ABC.nuktuni0AAA_uni0ABC.nuktuni0AAB_uni0ABC.nuktuni0AAC_uni0ABC.nuktuni0AAD_uni0ABC.nuktuni0AAE_uni0ABC.nuktuni0AAF_uni0ABC.nuktuni0AB0_uni0ABC.nuktuni0AB2_uni0ABC.nuktuni0AB3_uni0ABC.nuktuni0AB5_uni0ABC.nuktuni0AB6_uni0ABC.nuktuni0AB7_uni0ABC.nuktuni0AB8_uni0ABC.nuktuni0AB9_uni0ABC.nuktuniAA3_ACD.half_AB0_ACD.vatuuni0AA3_uni0AB0_uni0ACD.vatuuni0A30_uni0A4D.blwfuni0A35_uni0A4D.blwfuni0A39_uni0A4D.blwfuni0A2F_uni0A4D.pstfglyph152glyph153glyph154glyph155glyph156glyph157uniA30_A4D.blwf_A41.blwsuniA39_A4D.blwf_A41.blwsnounicode_3_1_3glyph161uni0A30_A4D.blwf_A42.blwsuniA39_A4D.blwf_A42.blwsnounicode_3_1_2fnounicode_3_1_30uni0A28_uni0A42_uni0A70.abvsuni0A3E_uni0A02.abvsuni0A09_uni0A71.pstsuni0A0A_uni0A71.pstsuni0A13_uni0A71.pstsuni0A15_uni0A3C.nuktuni0A18_uni0A3C.nuktuni0A19_uni0A3C.nuktuni0A1A_uni0A3C.nuktuni0A1B_uni0A3C.nuktuni0A1D_uni0A3C.nuktuni0A1E_uni0A3C.nuktuni0A1F_uni0A3C.nuktuni0A20_uni0A3C.nuktuni0A22_uni0A3C.nuktuni0A23_uni0A3C.nuktuni0A24_uni0A3C.nuktuni0A25_uni0A3C.nuktuni0A26_uni0A3C.nuktuni0A27_uni0A3C.nuktuni0A28_uni0A3C.nuktuni0A2A_uni0A3C.nuktuni0A2C_uni0A3C.nuktuni0A2D_uni0A3C.nuktuni0A2E_uni0A3C.nuktuni0A2F_uni0A3C.nuktuni0A30_uni0A3C.nuktuni0A35_uni0A3C.nuktglyph194uni0A39_uni0A3C.nuktuni0A05_uni0A3C.nuktuni0A06_uni0A3C.nuktuni0A07_uni0A3C.nuktuni0A08_uni0A3C.nuktuni0A09_uni0A3C.nuktuni0A0A_uni0A3C.nuktuni0A0F_uni0A3C.nuktuni0A10_uni0A3C.nuktuni0A13_uni0A3C.nuktuni0A14_uni0A3C.nuktuni0A06_uni0A02.abvsglyph207uniA35_A4D.blwf_A41.blwsuniA35_A4D.blwf_A42.blwsuniA30_A4D.blwf_A4D.blwsuniA39_A4D.blwf_A4D.blwsuniA35_A4D.blwf_A4D.blwsffffiffluniFB05 m_n_armenian m_e_armenian m_i_armenian v_n_armenian m_x_armenianuniFB1DuniFB1EyodyodpatahhebrewayinaltonehebrewuniFB21uniFB22uniFB23uniFB24uniFB25uniFB26uniFB27uniFB28uniFB29shinshindothebrewshinsindothebrewshindageshshindothebrewshindageshsindothebrewalefpatahhebrewalefqamatshebrewalefdageshhebrewbetdageshhebrewgimeldageshhebrewdaletdageshhebrewhedageshhebrewvavdageshhebrewzayindageshhebrewtetdageshhebrewyoddageshhebrewfinalkafdageshhebrewkafdageshhebrewlameddageshhebrewmemdageshhebrewnundageshhebrewsamekhdageshhebrewpefinaldageshhebrewpedageshhebrewtsadidageshhebrewqofdageshhebrewreshdageshhebrewshindageshhebrewtavdageshhebrewvavholamhebrew betrafehebrew kafrafehebrew perafehebrewaleflamedhebrewuniFFFDuni10380uni10381uni10382uni10383uni10384uni10385uni10386uni10387uni10388uni10389uni1038auni1038buni1038cuni1038duni1038euni1038funi10390uni10391uni10392uni10393uni10394uni10395uni10396uni10397uni10398uni10399uni1039auni1039buni1039cuni1039duni1039funi103a0uni103a1uni103a2uni103a3uni103a4uni103a5uni103a6uni103a7uni103a8uni103a9uni103aauni103abuni103acuni103aduni103aeuni103afuni103b0uni103b1uni103b2uni103b3uni103b4uni103b5uni103b6uni103b7uni103b8uni103b9uni103bauni103bbuni103bcuni103bduni103beuni103bfuni103c0uni103c1uni103c2uni103c3uni103c8uni103c9uni103cauni103cbuni103ccuni103cduni103ceuni103cfuni103d0uni103d1uni103d2uni103d3uni103d4uni103d5uni10900uni10901uni10902uni10903uni10904uni10905uni10906uni10907uni10908uni10909uni1090auni1090buni1090cuni1090duni1090euni1090funi10910uni10911uni10912uni10913uni10914uni10915uni10916uni10917uni10918uni10919uni1091auni1091buni1091fB-./0^__`$%%&&''(efghhiijmnnoopst}~"#%&&''(12346789mntuwxxy } ~        ^ _ _ ` ` a b c d e i j j k l m m n v w w x                                                " # # $ & ' ' ( 5 6 6 7 = > > ? E F F G G H H I I J J K b c c d d e e f f g g h n o o p q r r s t u u v v w w x z { { | } ~ ~                              $ % & ' ' ( * + + , , - 4 5 S T T U Y Z Z [ o p p q z { { |                   KLLMijjkkllmmnnostuvwxxy{|0112, $  5 W  4DFLTzarmnbengbng2cyrldev2devageor"gjr2,grekJgujrTgur2rguruhebrlatnphnxsyrctml2ugarxpeo  #%),  #%),   (*-   (*-  !&+.  "&+. $' $'/aaltabvs"abvs(abvs.akhn4akhn:akhn@blwfFblwfLblwfRblwfXblws^blwsdblwsjblwspccmpvdlig|dligfrachalfhalfhalfhalnhalnhliginitligaliganuktnuktnuktpresprespresprespstfpstfpstspstspsts rephrphfrphfrphf"vatu(vatu.vatu60)('&%$#  .-" ! ,+*/   1dlt| $,4<DLT\dlt|. ,N`Bn~ *  HZx  4 b$$%(&*&V&n&'))"JT^hr|&0:DNXblviiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiGh",6@J^hr|t]u]v] ]]nw] 5] 6]x]y]]nz]{] ] @] h]89:<?BCDENR\ A g(V`jt~&0:DNXblv #$%'%&" UxfwxPGN"  CnZ DnA8? 6 , mxa nS  e eBHNTZ`flrx~++%'016 "(IOILOLII*  """oxa nS &0:DNXblv f g h i k l  n o p o J<FPZdnx",6@ x x x x xxxxxxxxxxxxxxxxxxx x!x"x#xGJLLNPUWY` bhUU +\fpz$.8BLVblv Un Vn Wn Xn n Yn [n \n ]n n n n n bn cn dn n fn gn in jn kn ln mn nn n n qn rn sn tn un vn wn xn n n n n n n  Y yn8=?KM\ C E# G I& Y Y) *%PZdnx",6@JT^hr| q r s t u v x y z { | } ~                        #"*     &Pf  '  "  '   '   '  "  '   '   ' *  foOo,odh)Xblv",6@JT^hr~:oZo[o\o]o=o>ovozoyo{oaxWo^oDo_o`oaoIobocodoYoeo(o)o*o+oax%ax-o.o/o0o1o2o3oMo'osRorQo4o GJLLNOQ_eegg  "!%  R!HR\fpz$.8BLV`jt~                         F          M  K            H  N  J  L           !8;<=>?@ABCDFGHIJKMNOPQSUY[wz c i w V>HR\fpz$.8BL  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o   4@Vb@xR?xQAxRCxTBxSTxT "HxdGxYFxXEx_9G$XZ "(SLxNdxgdiLhZ qQpR&xRx QRSTX  Xh $.@Z     8 QK E D : &, K X U F R Q C B C  &,28>D  m X R Q P O J I ;VV Q`P_ 9: U e x "@:DNZdv  J\n     nS  g 8 QK RK SK ?  g ^K EG _K E D : aK K  c oK zK |K \ }K $  g  q ~K IX G= C B   "(.4  m K X U F R Q  ; : 9 8 C B C $*06<BHNT  m X R Q P O J I ; e `K9:G[ U V X ] c d e f g i k m q t u v w x :d,NhT"T^J|Xz $       &,28>D          &,       $*06<                           $*06<          $*06<        "(.4:@FLRX^djpv| ( & % $ " !          "(.4 / . - , + * ) 1 0 &, 8 7 5 4 3 2 9 &,28>DJPV\bhnt L K I G E D C B A . @ ? = < ; :$*06<BHNTZ`fl Z Y X W V U T S R Q P O N M &, ` _ ^ ] \ [ $*06<BHNT n m l k j i h f d b a"(.4:@FLRX^djpv|      .   } | z x v t s q p         q s t u v y { | ~ Z &@"""&  KoJnknaD.  Pc Ob  BeS\:z  *4FPbl(:L^p"4FXbt                                                                                                                   y       :    ! . 8 @ N ` k x , "2 T$.858687888m8m8<=>?s "FPr 9%   %  % $      % :$ $   % = > a     ;$%   < >% b  >$ % +_ahilm *  333 <m;kNZ*6BN %  &  (  )  *  , &0:DNXblv An ?n n n n n  nnnnnnn n n>\xy 5 6 %PZdnx",6@JT^hr|                                      #*   7  8  %.|  :DFLTzarmnbengbng2cyrldev2devageorgjr2grekgujr gur2guru(hebr2latn>phnxNsyrcZtml2fugarrxpeo~     abvmPabvmVblwm\blwmbkernhkernnlfbdtmarkzmarkmarkmkmkmkmkrtbd      (0:BJRZbjrz  >26* &\&'/44R9=8=> P &,28>DJPV\bhntz "(.4:@FLRX^djpv|u}HuuQT6CTSCrn`=y66LT<nYiu!!!Q!!|!!!!!A!!!!6!!!!!!!X!W!!o!!!!;!!!vT!P!!]}G iikmpqv/EIK  _ 28>DJPV\bhnt].L7W.T+Z.^=@.<#:%_8c8 0&6 XXW{P r "(.4:@FLRX^djpv| $*06<BHNTZ`flrx~ &,28>DJPV\bhntzX:|bXXkX.W:lP1mXMW;8nX8l88naij:;S`xZNX8mclX88 p888l8888W8$=D]4567899HH;PR<WW?ZZ@^^AbeBggFqqGttHIMXZ  _`a&&g//h44i:;j==lAEm !#')*+ , -1 &,28>DJPV\bhntzK]XbT\\[( # "ZXYVWZ  Z"Fn  &,28>DJPV\X=aDo6x $(,28<DHLRX\j $6<NTfl~"4N`r$>DJPV\bhntz "(.HZlrx~'$ J '# X '  + 3  A     q w] `^  @ 0] @^   @  ] @^   F @v] @^     v] P^     N NSPKB~||N NSPKB~|zNgNK~W|zNZN}JB~|zN@NljKL~|zO 00O @X  O%UU./$43LR h o "(.4:@FLRX^djpv| $*06<BHNTZ`flrx~ &,28>DJPV\bhntO3}[XXk{Xp3eO4mXMW? nonfodj3;`x^NWX9mm?&?m eoW.#}o$%&'()*+,-./0123456789:;<=DFHJKLMNOPQRSTUVXZ\]HPQRVWZ^bcdegqrtvyz{| /12347:;=ABCDE__ #&()+- ~ : < @ A B CD$*06<BHNTZ`flrx~ &,28>DJPV\bhntz^OUR>X>RAUUUU/UU#U :U,X,S[XURUBB=X&U03>s=[T 2:.@  Yh  28RXy : >~ &,28>DJPV\bhntzSSS l SSSmb SS/<yvh^h^hbhhlphhM/hhjhhAhhjhKl5hkoUhQvU3hMkBkBRnR00b$= D]$ FLRX^djpv| ^Adn1;Q(<((((d(\4B "FLRX^djpv| [ $8+TKt  2::G`bhnopqx"(.|`C]%f. D &,28>DJPV\bhntz |yfDmJUUkM_wq)bg.&O L|Q6?.'303h00m_weR; idbD8<>BCDEINSTVW\tz N O P Q U \ ` e j p r s u bcden"(.Xtpk<&J #HNTZ`flrx~T ! *O;$&#0+). - #9TMx *   +:;G`bh78rst"(.zaw^^r "(.4:@FLRX^djpv| $*06<BHNTZ`flrx~ &,28>DJPV\bhntz "(.4:@FLxaejgdi9jjedce _eqehaXaed ciecgffegaggaeac d hcea egeiga>>5|7@^^ag[i_didieeYgaeagWi#aeg4cge.e:cc^d^gddigcgg^g;e>>A;D>>kjd^^gdaedaagda aeWchgaj^9ddaddd ceecg ddg|7@jigeadaa**+-388 ;< >E GGIPRTVX \\#tt$xz%( 5 6) : :+ = B, N Q2 U U6 \ \7 ` `8 e e9 j j: p p; r s< u u> ? S T W ] ^ e f g k l m n s t w y$%fghip~"(.4:@FLkUgXdGLi@8Xjjkkssuuzz~~ kkww~~jksuz~J J$$%%&'(())**--..//22 3344 55 66 77 9: ;;<< ==        "" $$ && 88 :: ??yy""ZZdd ff nn pp  s$$&&**--224466779:;;<<==DDFHII JJMM PQ RRSSTTUUVV WW XXYZ[[ \\]]         !! ""## $$%% &&++--//11338899::;;<< ==>> ??@@ EE [[ddee ffgg nnoo ppqq   X$%&'()*-./2345679:;<= "$&8:?y"Zdfnpb0 GDDEFHHIIJJKKNNPQRSUUVV WW XXYZ[[\\]]    !! ## %% ++--//113399<< >> @@ ''EE[[ee gg oo qq @DDFHIIJJMMPQRRSS TTUU VVWWXX YZ[[\\]]    !!##%%++ -- // 11 33 99<<>>@@22[[ eeggooqq\DEFHIJKNPQRSUVWXYZ[\] !#%+-/139<>@'E[egoqy$$%%&'))**.. //22334466779: ;; << == DD EF HH II JJKKNNPQRS UUVVWWXXYZ[[\\]]              !!""##$$%%--//3388 99:: ;; <<== >>?? @@22 ""## ''EEZZ[[ ddeeffggnnooppqq   mm}}     $%&')*./234679:;<=DEFHIJKNPQRSUVWXYZ[\]  !"#$%-/389:;<=>?@2"#'EZ[defgnopq mm}}      n$$&&**--224466779:;;<<==DDFHII JJMM PQ RRSS TTUU VV WW XX YZ[[ \\]]           !! ""## $$%% &&++ -- // 33 8899::;;<< ==>> ??@@ 22 ## EE [[ ddee ffgg nnoo ppqq   m}     .YņOSCAR-code-v1.3.1/oscar/help.cpp000066400000000000000000000237701417327530600162450ustar00rootroot00000000000000/* OSCAR Help Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include "SleepLib/common.h" #include "translation.h" #include "help.h" #include "ui_help.h" Help::Help(QWidget *parent) : QWidget(parent), ui(new Ui::Help) { ui->setupUi(this); QString helpRoot = appResourcePath() + "/Help/"; qDebug() << "Help root is " + helpRoot; QString helpIndex; // Use a path in AppData QCoreApplication::setOrganizationDomain("nightowlsoftware.ca"); auto path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); if (path.isEmpty()) qFatal("Cannot determine user data storage location"); QDir d{path}; if (d.mkpath(d.absolutePath())) { qDebug() << "Help index is in" << d.absolutePath(); helpIndex = d.absolutePath() + "/index.qhc"; } else { qDebug() << "Could not create path to index directory - " + d.absolutePath(); } QDir dir(helpRoot); QStringList nameFilters = QStringList("*.qch"); QStringList helpfiles = dir.entryList(nameFilters, QDir::Files | QDir::Readable); language = currentLanguage(); QString helpFile; for (const QString & file : helpfiles) { if (file.endsWith(language+".qch")) { helpFile = helpRoot+file; break; } } if (helpFile.isEmpty() && (language != DefaultLanguage)) { ui->languageWarningMessage->setText(tr("Help Files are not yet available for %1 and will display in %2.").arg(lookupLanguageName(language)).arg(lookupLanguageName(DefaultLanguage))); language = DefaultLanguage; for (const QString & file : helpfiles) { if (file.endsWith(language+".qch")) { helpFile = helpRoot+file; } break; } } if (helpFile.isEmpty()) { ui->languageWarningMessage->setText(tr("Help files do not appear to be present.")); // Still empty, install is dodgy // Copy en_US out of resource?? // For now I just don't care, if the user screws up.. tough } helpLoaded = false; // Delete the crappy qhc so we can generate our own. if (QFile::exists(helpIndex)) QFile::remove(helpIndex); helpEngine = new QHelpEngine(helpIndex); helpNamespace = "nightowlsoftware.ca.OSCAR_Guide"; if (!helpFile.isEmpty()) { if (!helpEngine->setupData()) { ui->languageWarningMessage->setText(tr("HelpEngine did not set up correctly")); qDebug() << "Help engine Setup Failed"; } else for (auto const& st : helpEngine->registeredDocumentations()) { if ( st == helpNamespace ) { qDebug() << "Already Registered" << helpFile; helpLoaded = true; ui->languageWarning->setVisible(false); } } if ( ! helpLoaded ) { if (helpEngine->registerDocumentation(helpFile)) { qDebug() << "Registered" << helpFile; helpLoaded = true; ui->languageWarning->setVisible(false); } else { ui->languageWarningMessage->setText(tr("HelpEngine could not register documentation correctly.")); qDebug() << helpEngine->error(); } } } helpBrowser = new HelpBrowser(helpEngine); tabWidget = new QTabWidget; tabWidget->setMaximumWidth(250); tabWidget->addTab(helpEngine->contentWidget(), tr("Contents")); tabWidget->addTab(helpEngine->indexWidget(), tr("Index")); resultWidget = new MyTextBrowser(this); resultWidget->setOpenLinks(false); tabWidget->addTab(resultWidget, tr("Search")); ui->splitter->insertWidget(0, tabWidget); ui->splitter->insertWidget(1, helpBrowser); ui->forwardButton->setEnabled(false); ui->backButton->setEnabled(false); if (!helpLoaded) { QString html = "


"+tr("No documentation available")+"

"; helpBrowser->setHtml(html); return; } else { QTimer::singleShot(50,this, SLOT(startup())); connect(helpBrowser, SIGNAL(forwardAvailable(bool)), this, SLOT(forwardAvailable(bool))); connect(helpBrowser, SIGNAL(backwardAvailable(bool)), this, SLOT(backwardAvailable(bool))); connect(helpEngine->contentWidget(), SIGNAL(linkActivated(QUrl)), helpBrowser, SLOT(setSource(QUrl))); connect(helpEngine->indexWidget(), SIGNAL(linkActivated(QUrl, QString)), helpBrowser, SLOT(setSource(QUrl))); connect(helpEngine->searchEngine(), SIGNAL(searchingFinished(int)), this, SLOT(on_searchComplete(int))); connect(helpEngine->searchEngine(), SIGNAL(indexingFinished()), this, SLOT(indexingFinished())); connect(resultWidget, SIGNAL(anchorClicked(QUrl)), this, SLOT(requestShowLink(QUrl))); searchReady = false; helpEngine->searchEngine()->reindexDocumentation(); helpBrowser->setSource(QUrl(QString("qthelp://%1/doc/index.html").arg(helpNamespace))); } } Help::~Help() { disconnect(resultWidget, SIGNAL(anchorClicked(QUrl)), this, SLOT(requestShowLink(QUrl))); disconnect(helpEngine->searchEngine(), SIGNAL(indexingFinished()), this, SLOT(indexingFinished())); disconnect(helpEngine->searchEngine(), SIGNAL(searchingFinished(int)), this, SLOT(on_searchComplete(int))); disconnect(helpEngine->contentWidget(), SIGNAL(linkActivated(QUrl)), helpBrowser, SLOT(setSource(QUrl))); disconnect(helpEngine->indexWidget(), SIGNAL(linkActivated(QUrl, QString)), helpBrowser, SLOT(setSource(QUrl))); disconnect(helpBrowser, SIGNAL(backwardAvailable(bool)), this, SLOT(backwardAvailable(bool))); disconnect(helpBrowser, SIGNAL(forwardAvailable(bool)), this, SLOT(forwardAvailable(bool))); delete helpEngine; delete ui; } void Help::startup() { helpEngine->contentWidget()->expandToDepth(0); } HelpBrowser::HelpBrowser(QHelpEngine* helpEngine, QWidget* parent): QTextBrowser(parent), helpEngine(helpEngine) { } QVariant HelpBrowser::loadResource(int type, const QUrl &name) { if (name.scheme() == "qthelp") { qDebug() << "Loading" << name.toString(); return QVariant(helpEngine->fileData(name)); } else // QString path = name.path(QUrl::FullyEncoded).replace("/./","/"); // if (!path.startsWith("/doc")) path="/doc"+path; // QString urlstr = "qthelp://"+helpNamespace+path; // QByteArray bytes = helpEngine->fileData(urlstr); // if (bytes.size()>0) return QVariant(bytes); if (type == QTextDocument::ImageResource && name.scheme().compare(QLatin1String(""), Qt::CaseInsensitive) == 0) { static QRegularExpression re("^image/[^;]+;base64,.+={0,2}$"); QRegularExpressionMatch match = re.match(name.path()); if (match.hasMatch()) return QVariant(); } return QTextBrowser::loadResource(type, name); } void Help::on_backButton_clicked() { helpBrowser->backward(); } void Help::on_forwardButton_clicked() { helpBrowser->forward(); } void Help::on_homeButton_clicked() { if (!helpLoaded) return; QByteArray index = helpEngine->fileData(QUrl(QString("qthelp://%1/doc/index.html").arg(helpNamespace))); helpBrowser->setHtml(index); } void Help::on_searchComplete(int count) { if (!searchReady) { QString html = "

"+tr("Please wait a bit.. Indexing still in progress")+"

"; helpBrowser->setText(html); return; } QHelpSearchEngine * search = helpEngine->searchEngine(); QVector results = search->searchResults(0, count); // ui->searchBar->blockSignals(true); ui->searchBar->setText(QString()); // ui->searchBar->blockSignals(false); QString html = ""; QString queryString; #if QT_VERSION < QT_VERSION_CHECK(5,9,0) QList results = search->query(); if (results.size()>0) { queryString=results[0].wordList.at(0); } #else queryString = search->searchInput(); #endif QString a = (results.size() == 0) ? tr("No") : QString("%1").arg(results.size()); html+=""+ tr("%1 result(s) for \"%2\"").arg(a).arg(queryString)+""; if (results.size()>0) html += " ["+tr("clear")+"]"; for (QHelpSearchResult & result : results) { QString title = result.url().toString().section("/",-1); html += QString("

%2: %3

").arg(result.url().toString()).arg(title).arg(result.snippet()); } html += ""; resultWidget->setText(html); tabWidget->setCurrentWidget(resultWidget); } void Help::on_searchBar_returnPressed() { if (!helpLoaded) { ui->searchBar->clear(); return; } QHelpSearchEngine * search = helpEngine->searchEngine(); QString str=ui->searchBar->text(); #if QT_VERSION < QT_VERSION_CHECK(5,9,0) QList query; query.push_back(QHelpSearchQuery(QHelpSearchQuery::FUZZY, QStringList(str))); search->search(query); #else search->search(str); #endif } void Help::indexingFinished() { searchReady = true; } void Help::forwardAvailable(bool b) { ui->forwardButton->setEnabled(b); } void Help::backwardAvailable(bool b) { ui->backButton->setEnabled(b); } void Help::requestShowLink(const QUrl & link) { if (link.toString() == "clear") { resultWidget->clear(); } else { helpBrowser->setSource(link); } } void Help::on_languageWarningCheckbox_clicked(bool checked) { ui->languageWarning->setVisible(!checked); } OSCAR-code-v1.3.1/oscar/help.h000066400000000000000000000031061417327530600157010ustar00rootroot00000000000000/* OSCAR Help Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef HELP_H #define HELP_H #include #include #include #include #include "mytextbrowser.h" namespace Ui { class Help; } class HelpBrowser : public QTextBrowser { public: HelpBrowser(QHelpEngine* helpEngine, QWidget* parent = 0); virtual QVariant loadResource(int type, const QUrl &url) Q_DECL_OVERRIDE; QString helpNamespace; private: QHelpEngine * helpEngine; }; class Help : public QWidget { Q_OBJECT public: explicit Help(QWidget *parent = 0); ~Help(); void print(QPrinter * printer) { helpBrowser->print(printer); } private slots: void on_backButton_clicked(); void on_forwardButton_clicked(); void on_homeButton_clicked(); void startup(); void on_searchBar_returnPressed(); void on_searchComplete(int results); void indexingFinished(); void forwardAvailable(bool b); void backwardAvailable(bool b); void requestShowLink(const QUrl & link); void on_languageWarningCheckbox_clicked(bool checked); private: Ui::Help *ui; QHelpEngine *helpEngine; QTabWidget * tabWidget; HelpBrowser * helpBrowser; MyTextBrowser * resultWidget; bool searchReady; QString helpNamespace; bool helpLoaded; QString language; }; #endif // HELP_H OSCAR-code-v1.3.1/oscar/help.ui000066400000000000000000000136111417327530600160710ustar00rootroot00000000000000 Help 0 0 848 602 0 0 Form 0 0 0 0 0 0 0 background: rgb(100, 100, 100); color: rgb(255,255,255); QFrame::StyledPanel QFrame::Raised 0 0 true Qt::Horizontal 40 20 Hide this message 0 0 4 8 4 8 4 :/icons/back.png:/icons/back.png 24 24 true :/icons/forward.png:/icons/forward.png 24 24 true :/icons/go-home.png:/icons/go-home.png 24 24 true 0 0 true Search Topic: Qt::Horizontal OSCAR-code-v1.3.1/oscar/help/000077500000000000000000000000001417327530600155305ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/help/help_en/000077500000000000000000000000001417327530600171425ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/help/help_en/OSCAR_Guide_en.qhp000066400000000000000000000105361417327530600223270ustar00rootroot00000000000000 nightowlsoftware.ca.OSCAR_Guide doc
default.css *.html OSCAR-code-v1.3.1/oscar/help/help_en/daily.html000066400000000000000000000004511417327530600211320ustar00rootroot00000000000000 daily

Exploring the Daily View

OSCAR-code-v1.3.1/oscar/help/help_en/default.css000066400000000000000000000000001417327530600212660ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/help/help_en/faq.html000066400000000000000000000051361417327530600206040ustar00rootroot00000000000000 faq

Frequently Asked Questions

Why doesn't OSCAR support [insert obscure/new device] yet?

Plenty of reasons.. Pick your favourite:

  • • Because the 5 people who own one can't supply enough data to see all possible event codes?
  • • Because it doesn't feature a flow waveform and would be boring?
  • • Because one unpaid developer can only hack, create, and maintain so many importers before they go insane?
  • • Because to get it right takes a really, really big number of developer man-hours that would be better spent on other parts of the program?
  • • Because the build quality perhaps is garbage and nobody should own one?
  • • Because nobody else is interested in helping hack the formats?
  • • Because I prefer hands on and rarely get to play with/hack on the hardware directly?
  • Why doesn't OSCAR let me generate compliance reports?

    Mainly, to avoid attracting the lawsuits that would inevitably come from offering this capability. Here are the primary reasons why I'm dead against it:

  • • It's far too easy to change the source code to fake compliance reports.
  • • Do you like the idea of sharing the road with truck drivers with an untreated sleep disorder who faked compliance data?
  • • Data Formats of CPAP machines in OSCAR had to be reverse engineered because manufacturers don't release documentation, and accuracy can't be guaranteed.
  • • To do it would require closing the sourcecode and establishing a relationship with manufacturers who have proven they care very little about data access rights.
  • This stuff is also the reason I never bothered hacking CPAP data Checksums... If they were public knowledge, people could alter SD data card content, which would not be cool.

    OSCAR-code-v1.3.1/oscar/help/help_en/gettingstarted.html000066400000000000000000000004511417327530600230600ustar00rootroot00000000000000 gettingstarted

    Getting Started

    OSCAR-code-v1.3.1/oscar/help/help_en/glossary.html000066400000000000000000002066231417327530600217040ustar00rootroot00000000000000 glossary

    Glossary of Sleep Disorder Terminology

    A-Flex

    An algorithm for adjusting CPAP pressure during the later stage of inspiration and during exhalation to improve patient comfort based on a user-defined gain setting.

    AHI

    Apnea/Hypopnea Index. A count of apnea and hypopnea events per hour.

    Apnea

    An apnea is indicated if there is an 80%/75% (Respironics/ResMed) reduction in airflow for 10 seconds compared to the average airflow over an extended period of several minutes or if there is no airflow detected for 10 seconds.

    Apnea/Clear Airway Apnea Detection the the Respironics System One.. An apnea is detected when there is an 80% reduction in airflow from a baseline for at least 10 seconds if there is no airflow detected for 10 seconds. During the apnea, one or more pressure test pulses are delivered by the device. The device evaluates the response of the patient to the test pulse(s) and assesses whether the apnea has occurred while the patient has a clear airway or an obstructed airway. The airway is determined to be clear if the pressure test pulse generates a significant amount of flow; otherwise the airway is determined to be obstructed.

    Apnea Detection guidelines per ResMed machines..Apnea...When the respiratory flow decreases by more than 75% for at least 10 seconds.

    ASV

    Adaptive Servo-Ventilation. Pertains to a low-pressure, electrically driven ventilator system with electronic pressure control. The device’s pressure controls are adjusted to deliver pressure support for patient ventilatory assistance. The device augments patient breathing by supplying pressurized air through a patient circuit. It senses the patient’s breathing effort by monitoring airflow in the patient circuit and adjusts its output to assist in inhalation and exhalation.

    Auto-CPAP

    Continuous Positive Airway Pressure (CPAP) that automatically titrates the pressure up and down based on the varying requirements of the patient.

    AVAPS

    Average Volume Assured Pressure Support therapy mode

    Average AHI

    The average AHI (Apnea/Hypopnea Index) is the total number of apneas and hypopneas that occurred during sleep divided by the number of therapy hours.

    Average Hours of Use

    The total number of hours the patient received therapy divided by the total days of use.

    Average Time in Large Leak Per Day

    Displays the average amount of time the patient spent with excessive air leakage that will compromise therapy. This could be the result of poor mask fitting.

    Bi-Flex

    A small amount of pressure relief (levels of 1, 2, or 3) applied during the latter stages of inspiration and during active exhalation (the beginning part of exhalation).

    Bi-Level

    Two different positive pressure levels (IPAP/EPAP). The dual pressure levels provide a more natural means of delivering pressure support therapy to the patient resulting in improved patient comfort. The pressure toggles between an inspiratory and an expiratory pressure during spontaneous breathing.

    BPM

    Breaths per Minute, or beats per minute in the context of Heart Rate/Pulse oximetry.

    C-Flex

    A small amount of pressure relief applied during active exhalation (the beginning part of exhalation).

    Cheyne-Stokes Respiration

    (pinched from Wikipedia, full link: Cheyne-Stokes respiration)

    Cheyne-Stokes respiration is an abnormal pattern of breathing characterized by progressively deeper and sometimes faster breathing, followed by a gradual decrease that results in a temporary stop in breathing called an apnea. The pattern repeats, with each cycle usually taking 30 seconds to 2 minutes. It is an oscillation of ventilation between apnea and hyperpnea with a crescendo-diminuendo pattern, and is associated with changing serum partial pressures of oxygen and carbon dioxide.

    Cheyne-Stokes respiration and periodic breathing are the two regions on a spectrum of severity of oscillatory tidal volume. The distinction lies in what we observe happening at the trough of ventilation: if there is apnea, we describe it as Cheyne-Stokes respiration (since apnea is a prominent feature in their original description); if there is only hypopnea (abnormally small but not absent breaths) then we call it periodic breathing. Physiologically and mathematically, the phenomena are less different than they appear, because breaths that are smaller than the anatomical dead space do not actually ventilate the lung and so - from the point of view of gas concentrations in an alveolus - the nadir of hypopnea in periodic breathing may be indistinguishable from apnea.

    These phenomena can occur during wakefulness or during sleep, where they are called the Central sleep apnea syndrome (CSAS).

    System One calls lumps this together with Periodic breathing..

    cm H2O

    Measurement unit of pressure; centimeters of water.

    Compliance

    The consistency and accuracy with which a patient follows the regimen prescribed by a physician or other health professional.

    Compliance Graph

    Provides a view of the patient's therapy usage and the patient's compliance.

    CPAP

    Continuous Positive Airway Pressure

    CSR

    Short for Cheyne-Stokes Respiration

    Daily Events Per Hour

    Number of events per hour for one night of therapy.

    Desaturation

    An indication that the patient's measured SpO2 is reduced by 3% or more.

    Diagnostic RDI

    Diagnostic Respiratory Disturbance Index. The total number of breathing events divided by the total sleep time without therapy.

    DME

    Distributor of Durable Medical Equipment

    EPAP

    Expiratory Positive Airway Pressure

    Exhaled Tidal Volume

    The amount of air passing out of the lungs for each breath.

    FL

    Flow Limitation is a partial obstruction of the airway as detected by a change in the shape of the flow signal.

    Flow Limitation Detection in Respironics System One..Auto Mode Only scoring only..Straight CPap or Straight BiPap modes does not score this event. The device looks for relative changes in the peak, flatness, roundness, or shape (skewness) of the inspiratory portion of the airflow waveform. These changes are observed both over a short period of time (groups of 4 breaths) and over a long period of time (several minutes). Statistical measures are used to help minimize false event detection while allowing the device to be sensitive to even small changes.

    Flow Limitation Index

    Changes in flow limitation are recorded as events. The Flow Limitation Index is calculated by the total number of flow limitation events per night divided by the hours of use. Note: The average is calculated by taking the total number of events divided by the number of therapy days. This can be used to indicate if there has been a significant degradation in the flow signal, resulting in a pressure increase. This value is only reported on auto pressure machines.

    GMT

    Greenwich Mean Time (time zone), also known as Universal Coordinated Time (UTC)

    Hours of Usage

    Shows patterns of use displayed by date.

    Hypopnea

    An hypopnea is indicated if there is approximately 40% reduction in airflow for a duration of between 10 and 60 seconds, compared to the average airflow over an extended period of several minutes. Following a reduction in airflow, the therapy device must see two recovery breaths in order to label the event as a potential hypopnea. (Respironics detection is 40% reduction and ResMed detection is 50% reduction)

    Hypopnea Index

    The Hypopnea Index is calculated by the total number of hypopnea events per night divided by the hours of use.

    Hypopnea Detection in the Respironics System One..A hyponea is detected when there is an approximately 40% reduction in airflow from a baseline for at least 10 seconds.

    Hypopnea Detection per ResMed guidelines...When the respiratory flow decreases to 50% for at least 10 seconds.

    IPAP

    Inspiratory Positive Airway Pressure

    Leak

    The amount of air leakage in the patient circuit.

    LL

    Large Leak

    LPM

    Liters per Minute..L/min

    MaP

    Minutes at Pressure

    Mean Pressure

    Average device pressure multiplied by the time at pressure divided by the total time in the device.

    Minute Vent

    The average minute ventilation (tidal volume x rate).

    NRAH Index

    Non-Responsive Apnea/Hypopnea Index. A non-responsive apnea/hypopnea flag is generated when a patient has apneas and or hypopneas that do not respond to increased pressure from a pressure therapy device. It is detected when the patient has at least 2 apneas and/or hypopneas, the pressure level of the therapy device increases at least 3 cm H2O, and the patient continues to have apneas and/or hypopneas. Total Events / Total Session Hours = Index.

    Obstructive Apnea

    Obstructive Apnea (OA) is a temporary cessation of airflow caused by a collapse of the airway either full or partial without an accompanying cessation of respiratory effort.

    Obstructive Apnea Index

    The Obstructive Apnea Index is calculated by the total number of Obstructive Apnea events per night divided by the hours of use.

    OSA

    Obstructive Sleep Apnea

    PB

    Periodic Breathing is a Respironics data feature defined as a persistent waning and waxing breathing pattrn which repeats itself between 30 and 100 seconds. The nadir of the breathing pattern is characterized by at least a 40% reduction in airflow from an established baseline flow. The pattern must be present for several minutes before it can be identified as periodic breathing. No therapy adjustments are made in response to periodic breathing.

    Patient-Triggered Breaths

    Breaths initiated by the patient.

    Peak Average Pressure

    The largest average CPAP Pressure in the date range.

    Pressure

    Pressure settings and average delivered pressures are indicated as colored lines on reports.

    90% & 95% Pressure reports

    90% Pressure.. PR System One..The pressure at which the device spent 90% of the time at OR below. 95% Pressure.. ResMed S9..The pressure at which the device spent 95% of the time at OR below.

    Pressure Pulses

    Data available on the Respironics System One machines. Small test probes or puffs of air to help the machine decide if the apnea event is obstructive in nature or clear airway in nature. This number can vary widely and is of no real critical consequence to therapy.

    Pressure Support

    Difference between IPAP and EPAP pressure on bi-level machines.

    Ramp

    During ramp time, a patient starts therapy at a pressure lower than the prescription. The pressure is incrementally increased over time while the patient is falling asleep.

    Ramp Time

    The time over which the pressure increases from the initial low-value, to the prescription value.

    RERA

    Respiratory Event Related Arousal... a sequence of breaths characterized by increasing respiratory effort leading to an arousal from sleep, but which does not meet criteria for an apnea or hypopnea.”

    RERA Detection in the Respironics System One data..Respiratory effort-related arousal..defined as an arousal from sleep that follows a 10 second or longer sequence of breaths that are characterized by increasing respiratory effort, but which does not meet criteria for an apenea or hypopnea. Snoring, though usually associated with this condition need not be present. The RERA algorithm monitors for a sequence of breaths that exhibit both a subtle reduction in airflow and progressive flow limitation. If this breath sequence is terminated by a sudden increase in airflow along with the absence of flow limitation, and the event does not meet the conditions for an apnea or hypopnea, a RERA is indicated.

    RDI

    Respiratory Disturbance Index

    REMstar Auto Flags

    Measurements recorded in 30 second intervals for the following measures: NR = Non-Responsive Apnea/Hypopnea event OA = Obstructive Apnea event H = Hypopnea event FL = Flow Limitation event S = Snoring event AHI = Apnea/Hypopnea Index (the sum of the Apneas and Hypopneas during the night divided by the number of therapy hours).

    RR

    Respiration Rate Frequency of breathing, expressed as the number of breaths per minute

    S/T

    Spontaneous/Timed therapy mode

    SD Card

    A SD Card (Secure Digital Card) is an integrated circuit which is housed in a compact, rugged plastic enclosure. SD Cards are designed to store data and to enable the transfer of data between devices equipped with SD Card slots.

    Session

    A length of time in which therapy has been delivered with breaks lasting no more than one hour.

    Sigh

    A breath that is delivered every 100 mandatory or assisted breaths at 150% of the normal volume.

    Sleep Therapy Flags

    Measurements recorded in 30 second intervals for the following measures: OA = Obstructive Apnea event H = Hypopnea event S = Snoring event AHI = Apnea/Hypopnea Index (the sum of the Apneas and Hypopneas during the night divided by the number of therapy hours).

    SmartCard

    A type of memory card inserted in some therapy devices that records the patient's device usage information. The SmartCard can be removed for easy download of the data into EncorePro.

    SmartCard Reader/Writer

    The SmartCard reader/writer is used to download compliance data from a SmartCard.

    Snore

    A loud upper airway breathing sound during sleep, without episodes of apnea.

    Snore detection in Respironics System One..Vibratory snore is detected when a specific frequency is detected during the inspiratory portion of the patient's breath. Vibratory snore is disabled at pressures greater than 16 cm H2O.

    Split Night

    A mode that enables the auto CPAP algorithm to be delayed by a pre-selectable time interval.

    Ti

    Ti...ResMed..Duration of inspiration (ie, the respiratory flow into the lungs), expressed in seconds (5 breath moving average).

    Ti Max...ResMed...Maximum inspiration time in seconds.

    Ti. Min..ResMed..Minimum inspiration time in seconds.

    Tidal Volume

    The amount of air passing in and out of the lungs for each breath (mL).

    Total AHI

    The sum of the Apneas and Hypopneas divided by the number of therapy hours.

    VAPS

    Volume Assured Pressure Support, breath by breath correction towards a target tidal volume.

    Vibratory Snore (VS) Index

    The Vibratory Snore Index is the total number of vibratory snoring events per night divided by the hours of use.

    VTE

    Estimated average exhaled tidal volume.


    OSCAR-code-v1.3.1/oscar/help/help_en/import.html000066400000000000000000000004401417327530600213400ustar00rootroot00000000000000 import

    Importing Data

    OSCAR-code-v1.3.1/oscar/help/help_en/index.html000066400000000000000000000026011417327530600211360ustar00rootroot00000000000000 index

    Contents

  • Introduction
  • Getting Started
  • Supported Machines
  • Importing CPAP Data
  • Exploring the Daily View
  • Exploring the Overview
  • Statistics and Trends
  • Glossary of Sleep Disorder Terminology

  • Open Source CPAP Analysis Reporter

    OSCAR has been specially designed to help assist you in exploring data produced by CPAP (or APAP/BiPAP/ASV/AVAPS/etc) ventilators.

    It provides advanced features such as flow waveform analysis to generate respiratory graphs, as well as custom event flagging capabilities.

    Note to Testers:This help browser is just a stub to test the QHelpEngine, proper content and translation linkages have not been written yet

    OSCAR-code-v1.3.1/oscar/help/help_en/overview.html000066400000000000000000000004521417327530600216770ustar00rootroot00000000000000 overview

    Exploring the Overview

    OSCAR-code-v1.3.1/oscar/help/help_en/oximetry.html000066400000000000000000000005761417327530600217200ustar00rootroot00000000000000 oximetry

    Using an Oximeter



    OSCAR-code-v1.3.1/oscar/help/help_en/reportingbugs.html000066400000000000000000000005701417327530600227240ustar00rootroot00000000000000 reportingbugs

    Reporting Bugs

    Software glitches are inevitable... All software has them.

    OSCAR-code-v1.3.1/oscar/help/help_en/statistics.html000066400000000000000000000004551417327530600222260ustar00rootroot00000000000000 statistics

    Statistics & Trends

    OSCAR-code-v1.3.1/oscar/help/help_en/supported.html000066400000000000000000000056011417327530600220570ustar00rootroot00000000000000 supported

    Supported CPAP Machines

    Not every model that looks like these is supported... Most are, yet some annoyingly have unique and yet unseen/unhacked data structures.
     

    ResMed


    ResMed S9 Family
    "Escape" models are bricks

    AirSense 10 Family

    AirCurve
     

    Philips Respironics


    System One

    System One 60 Series

    System One BIPAP/AutoSV

    DreamStation CPAP and ASV
       
     

    DeVilbiss


    Intellipap DV54

    DV64 Family
     
     

    Fisher & Paykel


    F & P Icon
    Last... and (*cough*) least.
       
    OSCAR-code-v1.3.1/oscar/help/help_en/tipsntricks.html000066400000000000000000000016301417327530600224050ustar00rootroot00000000000000 tipsntricks

    Tips & Tricks

    Not so obvious control features

  • • Select an area by dragging with left button down, use right button to pan that area
  • • Hold down alt key to select an area without zooming in, hit B key to bookmark (while still holding alt), click outside of the area to cancel
  • • Hold down ctrl key to zoom in with mousewheel.
  • • Hit Escape key to return to previous zoom level.
  • • Press F8 to show/hide the entire left panel in daily view
  • • Press F9 to show/hide calendar in daily view
  • • Press F10 to show/hide the right side navigation bar
  • OSCAR-code-v1.3.1/oscar/help/help_nl/000077500000000000000000000000001417327530600171515ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/help/help_nl/OSCAR_Guide_nl.qhp000066400000000000000000000105431417327530600223430ustar00rootroot00000000000000 nightowlsoftware.ca.OSCAR_Guide doc
    default.css *.html OSCAR-code-v1.3.1/oscar/help/help_nl/daily.html000066400000000000000000000004761417327530600211500ustar00rootroot00000000000000 daily

    Het verkennen van de dagelijkse weergave

    OSCAR-code-v1.3.1/oscar/help/help_nl/default.css000066400000000000000000000000001417327530600212750ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/help/help_nl/faq.html000066400000000000000000000043771417327530600206210ustar00rootroot00000000000000 faq

    Veel Gestelde Vragen

    Waarom ondersteunt OSCAR niet [voeg een obscuur/nieuw apparaat in]?

    Genoeg redenen .. Kies je favoriet:
    • Omdat de 5 mensen die er een bezitten niet genoeg gegevens kunnen leveren om alle mogelijke gebeurteniscodes te zien?
    • Omdat het geen stroomgolfvorm heeft en saai zou zijn?
    • Omdat één onbetaalde ontwikkelaar alleen zoveel importeurs kan hacken, maken en onderhouden voordat ze gek worden?
    • Omdat om het goed te krijgen een heel, heel groot aantal manuren voor ontwikkelaars nodig is dat beter besteed zou kunnen worden aan andere delen van het programma?
    • Omdat de bouwkwaliteit misschien afval is en niemand er een zou moeten bezitten?
    • Omdat niemand anders geïnteresseerd is om de formaten te helpen hacken?
    • Omdat ik de voorkeur geef aan hands-on en zelden direct met hardware kan spelen / hacken?

    Waarom kan ik met OSCAR geen therapietrouw rapporteren?

    Voornamelijk om te voorkomen dat u de rechtszaken aantrekt die onvermijdelijk zouden komen door het aanbieden van deze mogelijkheid. Hier zijn de belangrijkste redenen waarom ik er dood tegen ben:
    • Het is veel te gemakkelijk om de broncode te wijzigen in valse rapporten.
    • Vind je het een goed idee om de weg te delen met vrachtwagenchauffeurs met een onbehandelde slaapstoornis die nepgegevens vervalst?
    • Gegevensindelingen van CPAP-machines in OSCAR moesten worden gehackt omdat fabrikanten geen documentatie vrijgeven en nauwkeurigheid kan niet worden gegarandeerd.
    • Om dit te kunnen doen, zou de broncode moeten worden gesloten en een relatie moeten worden aangegaan met fabrikanten die hebben bewezen dat ze weinig om toegangsrechten voor gegevens geven.
    Dit spul is ook de reden dat ik nooit de moeite heb genomen om CPAP-gegevens te controleren Checksums ... Als ze algemeen bekend waren, zouden mensen de inhoud van de SD-datakaart kunnen wijzigen, wat niet cool zou zijn. OSCAR-code-v1.3.1/oscar/help/help_nl/gettingstarted.html000066400000000000000000000004551417327530600230730ustar00rootroot00000000000000 gettingstarted

    Ermee beginnen

    OSCAR-code-v1.3.1/oscar/help/help_nl/glossary.html000066400000000000000000000404721417327530600217110ustar00rootroot00000000000000 glossary

    Woordenlijst van terminologie van slaapstoornissen

    A-Flex

    Een algoritme voor het aanpassen van de CPAP-druk tijdens de latere fase van inspiratie en tijdens uitademing om het comfort van de patiënt te verbeteren op basis van een door de gebruiker gedefinieerde versterkingsinstelling.

    AHI

    Apneu / hypopneu-index. Een telling van apneu- en hypopneu-events per uur.

    Apneu

    Een apneu wordt aangegeven als de luchtstroom gedurende 10 seconden met 80% / 75% (Respironics / ResMed) wordt verminderd in vergelijking met de gemiddelde luchtstroom over een langere periode van enkele minuten of als er gedurende 10 seconden geen luchtstroom wordt gedetecteerd.
    Apneu / Clear Airway Apneu Detectie van het Respironics-systeem One .. Een apneu wordt gedetecteerd wanneer de luchtstroom vanaf een basislijn met 80% wordt verminderd gedurende ten minste 10 seconden als er gedurende 10 seconden geen luchtstroom wordt gedetecteerd. Tijdens de apneu worden door het apparaat een of meer druktestpulsen afgegeven. Het apparaat evalueert de reactie van de patiënt op de testpuls (sen) en beoordeelt of de apneu is opgetreden terwijl de patiënt een vrije luchtweg of een geblokkeerde luchtweg heeft. Er wordt bepaald dat de luchtweg vrij is als de druktestpuls een aanzienlijke hoeveelheid stroom genereert; anders wordt vastgesteld dat de luchtweg wordt geblokkeerd.
    Richtlijnen voor apneudetectie per ResMed-machine ... Apneu ... Als de ademhalingsstroom gedurende ten minste 10 seconden met meer dan 75% afneemt.

    ASV

    Adaptieve servo-ventilatie. Heeft betrekking op een lagedruk, elektrisch aangedreven ventilatorsysteem met elektronische drukregeling. De drukregelaars van het apparaat zijn aangepast om drukondersteuning te bieden voor beademingsondersteuning van de patiënt. Het apparaat verbetert de ademhaling van de patiënt door perslucht toe te voeren via een patiëntcircuit. Het detecteert de ademhalingsinspanning van de patiënt door de luchtstroom in het patiëntcircuit te bewaken en past de output aan om te helpen bij het in- en uitademen.

    Automatische CPAP

    Continuous Positive Airway Pressure (CPAP) die de druk automatisch op en neer titreert op basis van de verschillende vereisten van de patiënt.

    AVAPS

    Gemiddeld volume verzekerd druk Ondersteunt therapiemodus

    Gemiddelde AHI

    De gemiddelde AHI (Apneu / Hypopneu-index) is het totale aantal apneu en hypopneu dat tijdens de slaap optrad, gedeeld door het aantal therapie-uren.

    Gemiddelde gebruiksuren

    Het totale aantal uren dat de patiënt therapie kreeg gedeeld door het totale aantal gebruiksdagen.

    Gemiddelde tijd in grote lekkage per dag

    Geeft de gemiddelde tijd weer die de patiënt heeft doorgebracht met overmatige luchtlekkage die de therapie in gevaar kan brengen. Dit kan het gevolg zijn van een slechte maskerpassing.

    Bi-Flex

    Een kleine hoeveelheid drukvermindering (niveaus van 1, 2 of 3) toegepast tijdens de laatste inspiratiestadia en tijdens actieve uitademing (het begin van uitademing).

    BI-niveau

    Twee verschillende positieve drukniveaus (IPAP / EPAP). De dubbele drukniveaus bieden een natuurlijkere manier om drukondersteuningstherapie aan de patiënt toe te dienen, wat resulteert in verbeterd patiëntcomfort. De druk wisselt tijdens een spontane ademhaling tussen een inspiratoire en een uitademingsdruk.

    BPM

    Ademhalingen per minuut, of slagen per minuut in het kader van hartslag / pulsoximetrie.

    C-Flex

    Een kleine hoeveelheid drukvermindering tijdens actieve uitademing (het begin van uitademing).

    Cheyne-Stokes-ademhaling

    (geknepen van Wikipedia, volledige link: Cheyne-Stokes-ademhaling)
    De ademhaling van Cheyne-Stokes is een abnormaal ademhalingspatroon dat wordt gekenmerkt door een steeds dieper en soms snellere ademhaling, gevolgd door een geleidelijke afname die resulteert in een tijdelijke ademhalingsstop, apneu genaamd. Het patroon herhaalt zich, waarbij elke cyclus gewoonlijk 30 seconden tot 2 minuten duurt. Het is een oscillatie van ventilatie tussen apneu en hyperpneu met een crescendo-diminuendo patroon en wordt geassocieerd met veranderende partiële serumdruk van zuurstof en kooldioxide.
    Cheyne-Stokes-ademhaling en periodieke ademhaling zijn de twee regio's op een spectrum van ernst van oscillerend getijvolume. Het onderscheid ligt in wat we waarnemen bij de ventilatieopening: als er apneu is, beschrijven we het als Cheyne-Stokes-ademhaling (aangezien apneu een prominent kenmerk is in hun oorspronkelijke beschrijving); als er alleen hypopneu is (abnormaal kleine maar niet afwezige ademhalingen) dan noemen we dat periodieke ademhaling. Fysiologisch en wiskundig zijn de verschijnselen minder verschillend dan ze lijken, omdat ademhalingen die kleiner zijn dan de anatomische dode ruimte de long niet daadwerkelijk ventileren en dus - vanuit het oogpunt van gasconcentraties in een alveole - het dieptepunt van hypopneu in periodiek ademhaling kan niet te onderscheiden zijn van apneu.
    Deze verschijnselen kunnen optreden tijdens het wakker zijn of tijdens de slaap, waar ze het Central sleep apnea syndrome (CSAS) worden genoemd.
    System One noemt dit samen met periodieke ademhaling.

    cm H2O

    Meeteenheid van druk; centimeters water.

    Nakoming

    De consistentie en nauwkeurigheid waarmee een patiënt het door een arts of andere gezondheidswerker voorgeschreven regime volgt.

    Nalevingsgrafiek

    Geeft een beeld van het therapiegebruik van de patiënt en de therapietrouw van de patiënt.

    CPAP

    Continue positieve luchtwegdruk

    MVO

    Afkorting voor Cheyne-Stokes Respiration

    Dagelijkse evenementen per uur

    Aantal gebeurtenissen per uur voor één nacht therapie.

    Desaturatie

    Een indicatie dat de gemeten SpO2 van de patiënt met 3% of meer wordt verlaagd.

    Diagnostische RDI

    Diagnostische ademhalingsstoornisindex. Het totale aantal ademhalingsgebeurtenissen gedeeld door de totale slaaptijd zonder therapie.

    DME

    Verdeler van duurzame medische apparatuur

    EPAP

    Expiratoire positieve luchtwegdruk

    Uitgeademd ademvolume

    De hoeveelheid lucht die voor elke ademhaling uit de longen stroomt.

    FL

    Flow Limitation is een gedeeltelijke obstructie van de luchtweg zoals gedetecteerd door een verandering in de vorm van het stromingssignaal.
    Stroombeperkingsdetectie in Respironics-systeem One..Auto-modus Alleen scoren alleen..Rechte CPap- of Straight BiPap-modi scoren deze gebeurtenis niet. Het apparaat zoekt naar relatieve veranderingen in de piek, vlakheid, rondheid of vorm (scheefheid) van het inspiratoire deel van de luchtstroomgolfvorm. Deze veranderingen worden zowel over een korte periode (groepen van 4 ademhalingen) als over een lange periode (enkele minuten) waargenomen. Statistische maatregelen worden gebruikt om de detectie van valse gebeurtenissen te helpen minimaliseren, terwijl het apparaat gevoelig is voor zelfs kleine veranderingen.

    Stroombeperkingsindex

    Veranderingen in stroombeperking worden geregistreerd als gebeurtenissen. De stroombeperkingsindex wordt berekend door het totale aantal stroombeperkende gebeurtenissen per nacht gedeeld door de gebruiksuren. Opmerking: het gemiddelde wordt berekend door het totale aantal gebeurtenissen te nemen gedeeld door het aantal therapiedagen. Dit kan worden gebruikt om aan te geven of er een aanzienlijke verslechtering in het stromingssignaal is opgetreden, resulterend in een drukverhoging. Deze waarde wordt alleen gerapporteerd op autodrukmachines.

    GMT

    Greenwich Mean Time (tijdzone), ook wel bekend als Universal Coordinated Time (UTC)

    Uren van gebruik

    Toont gebruikspatronen weergegeven op datum.

    Hypopneu

    Een hypopneu is geïndiceerd als de luchtstroom gedurende ongeveer 10 tot 60 seconden met ongeveer 40% wordt verminderd, vergeleken met de gemiddelde luchtstroom over een langere periode van enkele minuten. Na een afname van de luchtstroom moet het therapieapparaat twee keer ademhalen om het voorval als een potentiële hypopneu te bestempelen. (Respironics-detectie is 40% reductie en ResMed-detectie is 50% reductie)

    Hypopneu-index

    De hypopneu-index wordt berekend door het totale aantal hypopneu-gebeurtenissen per nacht gedeeld door de gebruiksuren.
    Hypopneu-detectie in het Respironics-systeem One..Een hyponea wordt gedetecteerd wanneer de luchtstroom vanaf een basislijn met ongeveer 40% wordt verminderd gedurende ten minste 10 seconden.
    Hypopneu-detectie volgens de ResMed-richtlijnen ... Wanneer de ademhalingsstroom gedurende ten minste 10 seconden afneemt tot 50%.

    IPAP

    Inspiratoire positieve luchtwegdruk

    Lekken

    De hoeveelheid luchtlekkage in het patiëntcircuit.

    LL

    Groot lek

    LPM

    Liter per minuut..L / min

    Kaart

    Minuten bij druk

    Gemiddelde druk

    Gemiddelde apparaatdruk vermenigvuldigd met de tijd bij druk gedeeld door de totale tijd in het apparaat.

    Minute Vent

    De gemiddelde minuutventilatie (ademvolume x snelheid).

    NRAH-index

    Niet-responsieve apneu / hypopneu-index. Een niet-reagerende vlag voor apneu / hypopneu wordt gegenereerd wanneer een patiënt apneu en / of hypopneu heeft die niet reageren op verhoogde druk van een druktherapie-apparaat. Het wordt gedetecteerd wanneer de patiënt ten minste 2 apneu en / of hypopneu heeft, het drukniveau van het therapieapparaat stijgt met ten minste 3 cm H2O en de patiënt blijft apneu en / of hypopneu hebben. Totaal aantal gebeurtenissen / totaal aantal sessie-uren = Index.

    Obstructieve apneu

    Obstructieve apneu (OA) is een tijdelijke stopzetting van de luchtstroom veroorzaakt door een volledige of gedeeltelijke instorting van de luchtweg zonder een daarmee gepaard gaande stopzetting van de ademhalingsinspanning.

    Obstructieve apneu-index

    De obstructieve apneu-index wordt berekend door het totale aantal obstructieve apneu-gebeurtenissen per nacht gedeeld door de gebruiksuren.

    OSA

    Obstructieve slaapapneu

    PB

    Periodieke ademhaling is een gegevensfunctie van Respironics, gedefinieerd als een aanhoudende afnemende en wassende ademhalingspatroon die zichzelf herhaalt tussen 30 en 100 seconden. Het dieptepunt van het ademhalingspatroon wordt gekenmerkt door een vermindering van de luchtstroom met ten minste 40% ten opzichte van een vastgestelde basisstroom. Het patroon moet enkele minuten aanwezig zijn voordat het kan worden geïdentificeerd als periodieke ademhaling. Er worden geen therapie-aanpassingen gemaakt als reactie op periodieke ademhaling.

    Door de patiënt getriggerde ademhalingen

    Ademhalingen geïnitieerd door de patiënt.

    Piek gemiddelde druk

    De grootste gemiddelde CPAP-druk in het datumbereik.

    Druk

    Drukinstellingen en gemiddelde afgeleverde drukken worden in rapporten aangegeven als gekleurde lijnen.

    90% en 95% drukrapporten

    90% druk .. PR-systeem één .. De druk waarbij het apparaat 90% van de tijd op OF beneden heeft doorgebracht. 95% druk .. ResMed S9..De druk waarbij het apparaat 95% van de tijd op OF beneden heeft doorgebracht.

    Drukpulsen

    Gegevens beschikbaar op de Respironics System One-machines. Kleine testsondes of luchtwolken om de machine te helpen beslissen of de apneu-gebeurtenis obstructief van aard is of een zuivere luchtweg in de natuur. Dit aantal kan sterk variëren en is niet echt van cruciaal belang voor therapie.

    Drukondersteuning

    Verschil tussen IPAP- en EPAP-druk op machines met twee niveaus.

    Helling

    Tijdens de ramp-tijd begint een patiënt met therapie bij een lagere druk dan het recept. De druk wordt in de loop van de tijd stapsgewijs verhoogd terwijl de patiënt in slaap valt.

    Ramp tijd

    De tijd gedurende welke de druk toeneemt van de aanvankelijke lage waarde tot de voorgeschreven waarde.

    RERA

    Ademhalingsgebeurtenisgerelateerde opwinding ... een reeks ademhalingen die wordt gekenmerkt door toenemende ademhalingsinspanning die leidt tot opwinding uit de slaap, maar die niet voldoet aan de criteria voor apneu of hypopneu. "

    RERA-detectie in het Respironics-systeem Eén gegevens ... Ademhalingsgerelateerde opwinding als gevolg van ademhaling ... gedefinieerd als een opwinding uit de slaap die volgt op een ademhaling van 10 seconden of langer die wordt gekenmerkt door toenemende ademhalingsinspanning, maar die niet voldoet aan de criteria voor een apenea of hypopneu. Snurken, hoewel meestal geassocieerd met deze aandoening, hoeft niet aanwezig te zijn. Het RERA-algoritme controleert op een reeks ademhalingen die zowel een subtiele vermindering van de luchtstroom als een progressieve stroombeperking vertonen. Als deze ademhalingssequentie wordt beëindigd door een plotselinge toename van de luchtstroom samen met de afwezigheid van stroombeperking en de gebeurtenis niet voldoet aan de voorwaarden voor apneu of hypopneu, wordt een RERA geïndiceerd.

    RDI

    Index voor ademhalingsstoornissen

    REMstar Auto Flags

    Metingen geregistreerd in intervallen van 30 seconden voor de volgende metingen: NR = niet-responsieve apneu / hypopneu-gebeurtenis OA = obstructieve apneu-gebeurtenis H = hypopneu-gebeurtenis FL = stroombeperkingsgebeurtenis S = snurkende gebeurtenis AHI = apneu / hypopneu-index (de som van de apneu's en Hypopneus 's nachts gedeeld door het aantal therapie-uren).

    RR

    Ademhalingsfrequentie Ademhalingsfrequentie, uitgedrukt als het aantal ademhalingen per minuut

    S / T

    Spontane / getimede therapiemodus

    SD-kaart

    Een SD-kaart (Secure Digital Card) is een geïntegreerd circuit dat is ondergebracht in een compacte, robuuste plastic behuizing. SD-kaarten zijn ontworpen om gegevens op te slaan en de overdracht van gegevens mogelijk te maken tussen apparaten die zijn uitgerust met SD-kaartsleuven.

    Sessie

    Een tijdsperiode waarin therapie is gegeven met pauzes van niet meer dan een uur.

    Zucht

    Een ademhaling die elke 100 verplichte of ondersteunde ademhalingen wordt afgegeven op 150% van het normale volume.

    Slaaptherapie vlaggen

    Metingen geregistreerd met intervallen van 30 seconden voor de volgende metingen: OA = obstructieve apneu-gebeurtenis H = hypopneu-gebeurtenis S = snurkende gebeurtenis AHI = apneu / hypopneu-index (de som van de apneu's en hypopneus 's nachts gedeeld door het aantal therapie-uren).

    SmartCard

    Een type geheugenkaart dat in sommige therapieapparaten is geplaatst en die de gebruiksinformatie van het apparaat van de patiënt registreert. De SmartCard kan worden verwijderd om de gegevens eenvoudig in EncorePro te downloaden.

    SmartCard-lezer / -schrijver

    De SmartCard-lezer / -schrijver wordt gebruikt om nalevingsgegevens van een SmartCard te downloaden.

    Snurken

    Een luid ademhalingsgeluid van de bovenste luchtwegen tijdens de slaap, zonder afleveringen van apneu.
    Snurkdetectie in Respironics System One .. Trillend snurken wordt gedetecteerd wanneer een specifieke frequentie wordt gedetecteerd tijdens het inspiratoire deel van de ademhaling van de patiënt. Trillend snurken wordt uitgeschakeld bij een druk van meer dan 16 cm H2O.

    Split Nacht

    Een modus waarmee het automatische CPAP-algoritme kan worden vertraagd met een vooraf selecteerbaar tijdsinterval.

    Ti

    Ti ... ResMed..Duur van inspiratie (dat wil zeggen de ademhalingsstroom in de longen), uitgedrukt in seconden (5 ademhalend gemiddelde).
    Ti Max ... ResMed ... Maximale inspiratietijd in seconden.
    Ti. Min..ResMed..Minimale inspiratietijd in seconden.

    Getijdevolume

    De hoeveelheid lucht die in en uit de longen stroomt voor elke ademhaling (ml).

    Totaal AHI

    De som van de apneus en hypopneus gedeeld door het aantal therapie-uren.

    VAPS

    Volume verzekerde drukondersteuning, ademen door ademcorrectie naar een doelgetijdevolume.

    Vibratory Snore (VS) Index

    De Vibratory Snore Index is het totale aantal vibrerende snurkgebeurtenissen per nacht gedeeld door de gebruiksuren.

    VTE

    Geschat gemiddeld uitgeademd ademvolume. OSCAR-code-v1.3.1/oscar/help/help_nl/import.html000066400000000000000000000004521417327530600213520ustar00rootroot00000000000000 import

    Gegevens importeren

    OSCAR-code-v1.3.1/oscar/help/help_nl/index.html000066400000000000000000000026311417327530600211500ustar00rootroot00000000000000 index Nederlandse vertaling

    Inhoud


    Open Source CPAP-analyserapporter


    OSCAR is speciaal ontworpen om u te helpen bij het verkennen van gegevens die zijn geproduceerd door CPAP-ventilatoren (of APAP / BiPAP / ASV / AVAPS / enz.).

    Het biedt geavanceerde functies zoals analyse van stroomgolfvormen om ademhalingsgrafieken te genereren, evenals mogelijkheden voor het markeren van aangepaste gebeurtenissen.

    Opmerking voor testers: deze helpbrowser is slechts een beginnetje om de QHelpEngine te testen, de juiste inhoud en vertaalkoppelingen zijn nog niet geschreven OSCAR-code-v1.3.1/oscar/help/help_nl/overview.html000066400000000000000000000004601417327530600217050ustar00rootroot00000000000000 overview

    Het overzicht verkennen

    OSCAR-code-v1.3.1/oscar/help/help_nl/oximetry.html000066400000000000000000000004571417327530600217250ustar00rootroot00000000000000 oximetry

    Een oximeter gebruiken

    OSCAR-code-v1.3.1/oscar/help/help_nl/reportingbugs.html000066400000000000000000000005701417327530600227330ustar00rootroot00000000000000 reportingbugs

    Bugs melden


    Softwareproblemen zijn onvermijdelijk ... Alle software heeft ze. OSCAR-code-v1.3.1/oscar/help/help_nl/statistics.html000066400000000000000000000004611417327530600222320ustar00rootroot00000000000000 statistics

    Statistieken en trends

    OSCAR-code-v1.3.1/oscar/help/help_nl/supported.html000066400000000000000000000055721417327530600220750ustar00rootroot00000000000000 supported

    Ondersteunde Apparaten

    Niet elk model dat er zo uitziet, wordt ondersteund ... De meeste wel, maar sommige vervelende hebben nog onbekende datastructuren.
     

    ResMed


    ResMed S9 Family
    "Escape" models are bricks

    AirSense 10 Family

    AirCurve
     

    Philips Respironics


    System One

    System One 60 Series

    System One BIPAP/AutoSV

    DreamStation CPAP and ASV
       
     

    DeVilbiss


    Intellipap DV54

    DV64 Family
     
     

    Fisher & Paykel


    F & P Icon
    Last... and (*cough*) least.
       
    OSCAR-code-v1.3.1/oscar/help/help_nl/tipsntricks.html000066400000000000000000000021171417327530600224150ustar00rootroot00000000000000 tipsntricks

    tips & trucs

    Niet zo voor de hand liggende bedieningsfuncties

    • Selecteer een gebied door te slepen met de linkerknop omlaag, gebruik de rechterknop om dat gebied te pannen
    • Houd de Alt-toets ingedrukt om een gebied te selecteren zonder in te zoomen, druk op de B-toets om een bladwijzer te maken (terwijl je Alt nog ingedrukt houdt), klik buiten het gebied om te annuleren
    • Houd de Ctrl-toets ingedrukt om in te zoomen met het muiswiel.
    • Druk op de Escape-toets om terug te keren naar het vorige zoomniveau.
    • Druk op F8 om het hele linkerpaneel in de dagelijkse weergave te tonen / verbergen
    • Druk op F9 om de agenda in de dagelijkse weergave te tonen / verbergen
    • Druk op F10 om de rechter navigatiebalk te tonen / verbergen
    OSCAR-code-v1.3.1/oscar/help/images/000077500000000000000000000000001417327530600167755ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/help/images/aircurve.png000066400000000000000000001352031417327530600213270ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGD pHYs  IDATximYV={n͈H2G4.!#,F?'!BUJJhd!PIDDdtq۝v79{#ɈdJgqg͹|3 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa8of7{wl6|-C}1"֭;~?>wC\oF|ͯ7^1B* % }y8`,Ta| ^z{a}u0u~0x7ށFH%!##b=p]ߣ{??}ַ۴^}/׿~ } SUZA) Hbu#x=t]~pzv??ݻﷸE\7X,p~qs,0?:ݻp]ܸqogak}-~ _ʗPB$D>#""b"B:aеڮC/3;~3Xl5.//C<|Xh;Jnq}ܽsggg8;]{kwy|saQW5JC*)xӊA9 Z]av;ٿsɟ"狋\pyys<|績jBp;-,q` )PJB*t7o޽#?u 7W_B_9kh`4m!( Hk7u!"`:t]`~?񷿫OoZpuuIÇ%Vm -u Z T=ESt=A+3! R )1{uWM?;6 Jaɟv  /-t7D)!>,EdDJtC5Wx㍯{n\./bqK?CY, l-;ЉΆ`9DDBR@jҤR@I.Τ/?7}y> ߉ JAj)@H(MZ)T b "tY,xmۡzDKY|w^m\-quysөX,ٮ1sZX༃>8q'KRA)Q;Q! $t@ @H)$"y|{7R )W*9 !E>c:y:!8N <۶''{?_W2.K\]]puurvA?a{2v˷9~v߱oi} _1Zkϐ+hRqc%OaE1F DD1T*Lрv{m1ho[YkXz+\C?%V%ιF60Cg>D{G)=\m^ɮ2: PB^9RA* $JSRRȎt]CN#1)bB91_T2z%!>c!K)dzi@Hr.ep!9d 4U hcDU7!>O:8WEH)PS@Rb/!G@iJ#Ȇ0GӂDu*4mףm[g~g#?>^zjK\^^P=++ C{:;]`kQr . oo2t]ũ) Nb %c4@0Z#4lt}K)"(!9s!@o/}6z|/"4*N-ۅ9or";A{>(%*9Wdm⭷oĿ?~ls^]_z˫K:/:-;?"~42z^C! 9Jh/]/e E0voK7Db !BAA)ׄBS=;>ѥD X;_<;`[9x8Uːix 6?yҟ|{|/o Ҩ+20U 6PZA+J8,@Ad{_؋c_3]r:݃% =V)|3ߏ<...p~LYn6T^'=PsbB0?$+I9цR51ePBNd޻pQǠw>FHu 5v @#ӻ=U JkEC?CKppp]\K}? Hhrc>qUw拏x1D9 {4 TDiS^+ 􊠰s`aXoVX.X7ض ꣄B`d<  Y4}@ Qy6nߺv:%wsN^7fi(YI}l\1 JUj"R@q1U2n@׀✐=#%^Z l6-c9./Ϲc]>jYN3x <҉6&gM"B#8rٮu[m=ěob@c4N\Or<?-8{T qǧ's&nݼ FV{b$CfSpG 5D`XkTVh''sݶxRS@t} Wf d+1R9QH-zc׃Cv[`;/ 6e7Mqg-Uqg DN <}0 I8J 43&ǰa P. L' TRRi08::0NQJ36+f3()ѶT>R2Zx7ZKe}Qy*#YK%;:ٍ3;ܝF*i:<.{:Q9"AYbˑm!عquuxƓo:8{x9kqBg@i)OG D ;;YiKEDϧPjtPw)Cv0: r% $5"i% aPUu]iuQWJG9GªCtmMe-b X,VhR L NO5꺆`<=UʫF)#L RG%`S+Q KC $#)O_1"2/("ԫ3!Dnn?|p`V__1:v Kʖ\F61LlG P0d3/M"sEi &~<~uq).=.#5B'H2ft V9 b@IsQ{n8۷0!";RdRjRA)jIF.KS7ܸq{W:tQ;0s_"F#/j=IHT'mZ>=}p`׫pyq\-d#俛u2oO7d}5{xON Așr8ADB͘KI,@3jaNZnIl,_Yb@j<+kĿ) 0ѐ@-./q-ܽs1@N dg4b?c׾u./owޅ2FJÈSȻOR:\!0@ 87zrشBtLy`殥!yS F~ySꋁYuݒT,ڂ;(~-9b l&ߢH1 |N8:$~ȺC?d%ʱܠM;#30'b d#YDAWJ@5ۭo=z½{O21xU"2,фs|{FHK'~q#i,byS{gU}=z.90 Ob*FXň~)]JI&eD sK!}| cQX`\z+\]_/~55kic!QHsPR#_%Jraq7D8fĈ@Nԑ>Lbw z`xsLd*Y"]%GNg3? 훘sC™,]Ďtmb&=Rfmry+\^^kfoCe\ rH6dKA'Ks1GJ_S[ٴ"AEXIwڱ\̥0XG睅)BItrT*@:A]O+WCG#x4Lxs 8!=Hv|47 \v"rhB \__'i9:k\__ˤ`="#>7̤ _p 4*12mFwdGy#K"rN JJ6J@v=%#:݇a5XYǏ'8F՛(dDd윤OD;=2.\=QyG>2Wmh&S G `D%]3sii1EHQ|H:R9Ǝe8V Ӓ1"Fy G? LL}A E@K7xGWqNH6(Khc8/]1JPQ(Hh P8YvJF gLg,(J)Mlk˲]P'4 29"vC(ס([rG:1!TfX*$|}uso2kŒ ݏR$hvЋ1N)r_lR7$;ѡD>px*cqϻ+Onӄ 6za0#Ƥ60UtMW+ Cxw$h!.]ї eѹT"bۄe'JM(* %!"uϧN'h<.>VS-qR)Te]婟> 9#8ma^?qrvKrR8~W~]ψ|^E2x"X<cd*%р wAʝ74"%?:':\Xj1 @UU0Z2UUv`HL&F Møik a$XfS\d8#D1!${~zZh 9Say&㏩&GO縛D `qhփ(!K_ Hvc@MD(!$wX.Oepyu_gXn6\ @(KK.|#ΕF}wo/2\MvL Z4e&WJ%As*@jC$ ޥPj]Qo+C +B"}aF׵h[\*w0F!RkqB"PF#0b959W,F{K0QrT dyBo@iXvQx'вxf`b| hr$/!ǧS@ ]{'T~|"U?.~`Zv[2suy,WK[R% 5l{H6 AxvNM[y {ԭ+# rq8GѲ?xALGژ#hO uHRd!,z WG\U62}6ї=1SP`\^n6Mmy'+ KkEOK{tyvoJ.GdzL,7KGҏ G{vR(?`sW\Rdܦd2ɳ)7M꼅zt݀np6xشVzHIyaj͔dS.+4blu]gJH(%B:;ln"PyV:Jv {1d=ރ7$X,5S>hu)x I-v8pN?2H("1q>?_/@e6|>{'qiȲ4al+Q[u\4:#BxRs8_ 5C C&$\:0J(OLMC¢J榚bjm-ɍKRUe^oq\CHZxBT>@Up$A4pHJL3TU*bIr4@`6bCi[%t}'!t.k)e ]b>[#\cK9vjPWpW@ϗӀZJd#+"bZcm(C9z oD4D~I*sW2<cP=s$Ύ9 D9ԗJ&"Zz;車@3~8@YaVh%v_ kc~Ӟ6!"6 r>X,믿w~ Z~IM,Rg` ;оtsɞ(c2?$oxxfD܀anHmѷi^Fό4 :q]{^``YcYT='yHe f9jio[h)&u5_KlZ)CxV| RQ-23Sb:`6h !tRTP. ! }_>!ge@Da:H J7I:\ym٘rX S(b[yq6Bbp>s J_d|1 &E!S]l67~W)^5\_-Ѷ-`Q (PКnx }J$M\}/S)MRH U2teB" 0kn]b,ڮ͚TѦt=#adt1G 芸B)Bj8G!w9w>PR$"; )f%SJ NQަzFs"uw ;DJ@QDc40e"eyl KwS X07#")! pt<8FS` {RnLS8 6F1Mh5P>r,J`{*yӃ"3h3T?13z%ilVbK~QHkqI걤p 4ȧbd:l9g>2&׋Vc]V%lIqn=D>hZihpuťD X{62笠o!'\PSWt JǮH>gL)KRH:_"0ə]G't5Gpn^>LJjɐqgk%)GM8Tև@yщqՍHINq/SXlN~4P|z sDTH%Av1S(,SO) Gi]G;M;$~v粒 d( _JxyO g1(_Hx(ҶO4̈́'"1sC.!K%qVnܾcpzFc`]o{CB: %-O]nE+BooB/4#%?_TyN6Qt߅@u)IMd -Eh]֭iH80iXO!)m)L "%UZ;'dT곎1"169Rc̵ߗF^K )ǩs. ;pA}O5+Cd a y=G W!D@X+Scp x& EMRQ>X'+MuO >#^ ZSJ-ɍnpv;!}upyT9ċ<|S(*&a/HMQau= 5b(!$Fg ԒOaRS5J-D|JkH(%cMM<x=8gE1uU65֨Umi[([읷hSSy i**P:!'b82ݨ_?v( &!M$g"k)GmQP_Lv64Ѱ=xhBxv/0F=ލnJWr+ H0~J r@ztE:$\N) >Z.Lg֛MmF 'O5nH'rKǷ(r7=IJK}@$Ttń W@"rc~J"/%2(]Ҫ2+r ZS=](h"冷޴@kӓSH{ $2Uѳ/ѹ@68JdvCqk.5 |ҥ fС{1.;W@r7|_gTr,R^H<DY~JZl|h'e6)(U!Be ģf k- m4>8f)ܖp>\"u(Q&t}enA$>!Z܍FOrJ&C_gI)K%&p:| Ad +>Q.( ΅ "l,i1hkvRʖPUص0-"$LkP%cvkSFRГ,>S( V : l>wHv*Y BipqyKB; m@@j̨"$x1x&"kIV Rn#UeA]hEĦ@JT8#ϕ& N*Et۪HBpTySؐ n $sH)YJ xHV!X-ଃ{Xֹɡ Y6\"nv\y M8 ]D@~^.خc a/}zaStU¤|{ UP(%:#c #JTh /ɑ)HĨ TA@L%$rb^\RB`۵ *>{| {)"khٖ4̪F4%Ŭ8m4CK)PkKye";c Ts߽ 8u iW`4qCtIS!FPR]!@r^Գ%a>T|=skI{Gw1gItï4{PP`ay`9i`ÍSHp!><6gghIƈGQmTR Oӡ,60?׾@s-@,P'tr5'{j OQ5U_&u&϶ߠmB %Ej TԪ[霆h BeHCsm] `aqfRA@2&!"֡-$ΦαUަw,9:F`=5N9 )D.k0J<|p "R"ޥTq)S10薷tV LABP]](-\uK3(HĒ87oo6|mܾy 錄FNaΛ[( p4]Oϔ$K1ȾG 7~q*CI"O%> s&Z=;QpgO$"c);Nj0 Ҟ0B]7M mbqC8vI n:xxymKuF|%RYh 5SV{SUBv+C! p jSj*1L@UBw1γDe4LG(8XTZ-KY5H38$3RQ#+w8i9]# 5X 7| 6Z]jk\_^wGe4 zvjZk]y:0+'$P0ӓB \J" |'+e _#9&~,c'9Bc %|z/HI .BIJ-  4m[\]_}T 5&/>HC!h>pnN[n/l_|۶VR֜{k@Z1F+T|uM( UJh8i!QWz}rT}iJ1juuTqL>j1"Jm@2("6#oDLnI"! >L[H)`ZP Ԉa{7X.5֫5QI]gBePZc`ޠ9e2(u(4Ag'G+_lPTzV͊S>yreub ȱiMcʌ@>%S T FE xJzR!"UZ6p4RBp)  1Da* %(O@)_ϗ `M*nnQtZuJ3nH4&MLĦc`֜S^U0$% &"S&*%:x}"bZ YRRum7R)2mNd ׺D49@| gc{GepK]gw{c]Cb\aX~}bC¤IfPX,j)|[80NI#)I!Pȩ TLfB s&& gZQgp}Qx=g"J&~r)T>* 1]xNT@BsZ+DC[߉?<(~_|_!]$Zpyy =nݾ6\K'f]Txjqrz\suUpcQa2m$ ɤJC37(ee4;r+mAlC DeC{AjZlXd`DZAñmDO@;0qܐR)ulDJ׀ G WWzzvDa JK@' N>jv% i;@ fD=9y@.hK"_bDU#09"e4#ᜧ^~2_ 9c[ț% _/#!BȐ2ʜSxQ PQSb tJ!LŝMSԹT_%1W~{ˋ~TDfbR'G'h*s0{!Gs 6b>?@+ÕsCN%;$dtDL븆KUSR*o/xN;I$ o>ׁ$Mc* \Qt@Q'h馳np=vŴiǛR5c&0&P^zq+\\^]Gݒx6(&xx޾IpF7thd#pzBaԆ$8O,N\cej(sGDf&a\] EJ[;pAq/\Ijƹj F'ޓQ(I@t (H8XIv*w:7F8>e*xMj*qC |VAXJ}9YL&SMwZ-&(Thq}}ˋK_\jEoS(ONS9 ̷g8D/ G±#ʘidZfizYّqDO@h?5ͱ 7L'֞1p2yu8;;FT 7o'Њt*C*yXsww!0QBk8p2:0ǥ='Қ))|TP@O<<~믰Zq}ll,K_rrMz 4Ґ:0v` NsfR-)tC.s/181RIED0iGDufO9AJY+\x@c,ӽ^l6\]R禔d4xd!%CR=Nc#H;1XJ)@zMӰ5E_ OCCriwm]d\d^ ZLg3.O(cLƍ3xGFB;m sֲ`n1j|ᕔĻ0nʈs<֚[zD%Rr='UH\B=#̦5>{w^]׿bu;=#\_~Vy~*^b]#e.};GRءPj@=~hN:tv-MfP0'r]faPZؾ%\ ř ajMuS`m`.ϴ}^TTRb(0pCŌ=t"HC &@KM_ '+zwJl _-K '94qQwYdy0J>l!dRׄ.FEdA*D0H5CJf^J i ABbs]5R?_K W0>S+z@UL;}̏Mv duMӠL ]!Bq!9C`${߀wކa{'l`Zc2Hu)K&T@S?}UWg-9 τ4y~!pˬ4ĝAds"`$aR϶$rJS NF˔fdMM }0:54W3c $"&;2B|643UaZ[hbϏ--\v7*ج*jo*B*GTC6(z/ T;9$; ڵRmעsZ0[Vƿ#L > X,VpɸS׫tf,F].sF!x]RRYꃮuGGGd-͓r3`G=D(Xv:j˫5zGurУx# txxmT]rNM|vcL,gl4" &ܐ2s4Dr߀ !Ib{y^ `u~b|xmƓt;?"72$5d 'QC& 1ж}&(8K+Z<tl$ 9:{;S (g;pJDzj "_eZ.^mELA!IrrE@KsʅBҬBʔ3g'{3vc-4./ƛo,IdX$X':1v (V L ĪqڈWI zPa ]b^cޠCMִ9`,Vc#o!LbKN $j?{[9Z)hT\1PώqJ,bp\<pt\~j#x Uu E >1~ XAhD)Le $gF[4n*<_3HhZ)TBpfκ$ nlؖ  `RԼC%PJ6ͤ =htEѺw1ob_vQ. 7mtx1@&F#ܼĭZ, 7l{2KK%aB34,C98R,uG6k4C}uﶘiN(?H[.JiߥPCnsg<#A '~'q歿,E"!IMw=!gGY" UU:?`٬v F)@<jYe C+>d?DKIBT+Noyh“'sՁ?J LKNĤ '.E'ŢIv[V$͏S.Dp G" {t.+XIt˒^z;/k{D2w)cBOH)"@d؊_ӧ"U(F[/0l-b6\-0iH)Qzg<Ͷ|}xca's8/JAR!'c6tzbr& #8W_WUxL Oai'c꜈S1 0`6Ԥ8rR '?c@I}AzI=#80z_b!"zS ! ap}?p{o>a񀽞|EȺh۞bfXZ º2f8;=+ 0I@:"1Bj^l6fͧB3#2ttbƽ @qEnm@j) '2dQ)+H̭a 1O)a=^ J+(~̇6gGg//)g+FMfM˷Pxӡǃ8r邏 ;8>mD$'z9qD-Z*"X/@)H:,Β~G3M2uqc<:՟٫GR4)pw`(a1LCt2+Cko+( ̻{RBmjȱMD!G{w5٧V[F{I)f%(p~4)L9&2J91ivDc P'pcXO3%0o_*  CQ^ ] aVgSs}OjR@!~) t`D>9r=u(b$!%h:P)QM)ҠMKƜJu$hsTh[f V̦TAN^ʓI~_$Ja,b>ϳ"P `\:Xga,DÕ|61y |_t:CLhCO'dJ`y\ϭBD+8˒/Do{r /vn-`\@SXbd 8BTBP)) X!1P9tpen֘ͰYme4*IJN!fO;jÍlUJ&${iԐzxGI-ωГxɬ-e!I)KxR"u~ZNNO1u~KɡpzzfRayj'- 8uﯔuTmf ڶCUQ o% (1H+Ɏ&(<契#4{7X-LOVJ0}ŝ|Z*8ɹ=A2$L*"j1o 1զ) &MbMMf'w>g~5 F#rQ^J$?ا`ƛ'Z)Mz_ i0i*̧SgSTUq1~y,K L& SL@v}c00k}AՄmuwSN|ba%HCiq.|V r* 4 'U\ּY-*DT(J\J!i4^HRD'pCʏg}^{~HW}c6aq'Ԛ`҆H_>ŷ!JA)$l68=`,ʌFF%&ܽ*+!-?pge` +$@"t<RC%NJHh  Pأ{L& `J@c=l @NW|4?FB` x.AqYKLIsmzklm1 {)WUh#LX.\.y]c-0"(&ɸݫyť}<7(ñ.IWPt:i)!hX-iͶ x*)DfQ׷nݼC=,\P~npb`* w)H9UKvc6S\fwȠ1!qZzӗ%Y~p rw=frG 26wkiRq=" EW$+ Xd:'զ1OCc2 btҌzB% k/c>x4~P;?CyxJq2W"V6QcuJb>?o|p||O< HI^]K8]|NRkZ `|XT H~`߉vB4G 0 UgNMBRj8.RGE d+r)%1ij(N\ߒq*S\v!0?z9/R2KLu!!)LXW>2hR#jiR9: 𠦟G]E#CRRنY=ȧ{ @݃ܐ{376 5)M&4k"DϥP90ۗ:t۝x %(`XȰ3ؕ$Qyn_Fxp}@?XL8z3`iqzv _MDsL jCm}?`Z`4qS(-aAkR1|,K&LNu’0&"%ʎӂpC@2ӢZЬ*RON1?>fNtz8;=bD˭̧7|Yq+n"_HJ tٜ݀^"aYGTF&c2K0=gb>YrGĬAmTHz6{5~'=5oޣ Yɏ!GoE:D!`mK=ڶumKh~¯WWh9NNOwEnM&i3N@ d2Xڅ2(@ 'l @0y2&W ƅQ%+K. zH+2裇dqp]|K_`ڮ@Up/r8uhv5pX0?; @?8ybՀS+peC=g{G$BF"Y~>X:詜w0Iq" [Q|HcC6 f *_S.aiOf2 5|ڽs#A a 32T~sp">&` 9[u<.>ZkL ~,a PHjjELKE\TF6Wơ@=DIڷQ8 . X(0<_{b:IEOؗR2JP6XT6 a !Dup>G {n@ 6gS ǵSLDfB@Yni<$T<3@ڿQ4tV+H0Ƅ'}458]t.AX$ȨQRI܂I4+#eua 25t}y Q} wAO}~P5rJJ513>;K{HY{O` eTn:2ݹZmm>Mi\4s sw/HޱqJA|(8n4TD>RI!b-)\bz:5Jmqǭ[orOa) |k9tA\@q )iP@MiCJI_8*: >Ajdqnu֋Ϲ7`4Hrn0yn92q@%Un_ !f$_bDX' 1NGR^9;vlA=I3]`<ᓁ[}n XKUDC?-rv` I9E=1GB KR2Zܹ=0N 3~M,+HAbR)@$ SYlߣ.5u{ğ B (ל Ӑ|rUp=G']7Ý7qzrA0R[IfھCo Ӥ]A/Ht) "wlo=IZ2Mhr Vj;U,mΑRK(rڶEiM)$ay"IdS3:;sn_~a-^O}}S,\S_EJB8:>@01 Xa423`4!jXk_*^n^ FU7Hrc-5yxNJ;2`ͅbJ$5ei5]09-V V1qpOG]^@T!%\tVC7)r&w8!tm5hE3< J|6(z.(VoB:`Q)X/O#A0+ s3k'W\!xѼFCOeg}7NqfX*k'=fNNOqyu k : Sdz.z GY6 K)z1FP,k0"tӄcs1orj0q>fս @Pԏ/($tPR!HJ; ڮ!^xg?f0@CWH(c84pΎQWZH_Xqmz&Gl6,;p Iw@ @Sz :syZz9/a'1$RW U3pJǜT<>k $h QjoUڐ. 85Hk&<3b&xF ovt EF]kԪwntnEpnܸC{Z75q {ǂHZ:AfcZay*uCqSePPz;*]nnK 10(ɳ AhB̑fpY~jC%/iiҪ`HdɤFL0ϨqMHM="Ym[h 9qFL 8n'rbiIq? rג!y;Ă;iJI(`-EoiR4˪(pe}g}۶*+(BQ``6Rv="L/fM&^A?t:LX"Mpm62a#FPƱL)yr5c3 BC\_yЧ,H5yDO49Rt0^P鬮&_B( N|o: zͬp{9Z{Cb> ҈pGP੧+y@k@1:b \_^ciqrrf VDAzV5;9"$m[Њs$ɅTM> Bh꺁 з"X>==rDfl Ѓ4W Rx# ;m:*#1j[fh胵ěW@?) a2 ߮#,vߐMw`Hls癹~bXp06J q&q-Lg3Vkt] g=5mɤƴi a0XbHr:j Eߴ>~G#/<@zØ%X}350?V"&f({ ‘P̊RNYkE>В"/)gLc@,0x]^\[XnLhPW=0 =`y61 [hE>#D [xJH%k_Ń1OpM(awxF􄔅C0x$۝SNb+@0 lLiD(fJٙR4#l%ڟc7mel)8aQnK™{F]ش-"Xo\npXiBS B*E #L`( >K^iĮJ2d HOA="U$ Y95 '̐c !x5$ky}B~g7D.%#bKZt/K\\.ޱG|D=Y8Buk|цOhY<Ҕ..LSP54Kyi#q-L#\&+׿ut}90ϩ <Ð S)k44iijioӽQS`btQ\5S% dk )QCјN'@)e J 464U 1NOPμzS9ˮc}Y~x=˛ʊ0"+`T0~BB0VEꢤ.M?8?>֭;O|H lO_ H92|$G' =ZLC?иo2OUDdGl=JРƇ7SҋH*!ۊP8Cka1؀ji'XKf Uλ,7$IJHۧ0Ce7$oMat wQ}4~o} =1A(s3@65%Ox  #:;wQOӤ=9[өXI);sN)<>cHcJ*A 23=4diPJ*+:g1#__/?aMGH_?k%(mYB dFEUL'3ܺ5fBnpq脚,ABJ'% -X@sP<-hFv䧾݆DS(ƒ|>zz)d$^ i$zmQ. Dx^N4CړeΛʠ,m0.~v  5D# {)}s.bI59m!2VDt2TeB%St'Wh$69'b kz Pp EaZ1fH^vC@ӟa^܇<jT0JdHu}6Ի7 v75؉t^[TO hX* LJݗEN)KQ^ >=be$59d#jՍT}8r8g'h& "Bv,S-"WR#݂~6ed#!wMMw32 ;J-`yZz 6O[G6H MJw PbDlL䍻>F Q-r>"<* 4PW`<*22u,~!S$T39IDATT*--_m4 Gk;}Jܼy#Gv77|/ TvcrQ!m5f2$z`i(uA9h><~*7xsr=8GgMrMiٍ!$ќ'S4;FfZc{VM8Ą*Đꃉ1>sj5FWH V P.;ƿwUX1TyP4zp!I>34\hYy%7r)0| a]ΡHRteiM٘+J]~|~)g>hLl O%W4Jj *SU'3%ihF4gS$Em#|$賅< +됑^f)*XmIAm7Ȩ*SA"kje@ƬGQ}Ɂx߂okj,5G 6x J.G#ã&h}CxI.}qr&*Se`H0XPbE*F nV QDÿWj/4~w8ڔ>.>"=:Hq:}'&F޽۸yv Eydz{)=p?B'{zIŘefܾbiDFc?5WPBFUiL&5n<%IOM]uи/͓Eڄi`umH'G)YkIS,}[8}$}"g>!okq5xbLaLj2ӉTa!V !1- t= Ftusfi Ȁ!)G,( I瓛\RFMello{(I# sx3ZFd1IJqO4^pǁjliBǘ|ɬSQWY#e T`]>* xA{tEp<R\wvN6RH*]|˯^7Z^CU򗾂vj<0 >!oMZӇ" $Q-Xb p&g;/zhtWjg[<6oBZ+T D999n޸ 6fK1\s>0]1vc#R;DppmEoiրT%{ܪoBcHٙE CH쐥贡]cҴ_~,%iJBÀ=)heLx_~#s>G}70!TDZcGN1ij-1]n訉FMt h' `@:%9^J`r`Ǡ XP)g)mW??xܸyW-ke|ϧa!YK,&@C"3x1%Shc@S-wK}\ӝC<7& Qo hALgscLSuMӠgvw KN p87zQD#]@;]H;bjjA`kX--B 8==C4$0+CIޝ"Ifz2QaTΫU : i&ɆCO}38y/3'?=xsݻO__!TB@[*7x\/Va>38qzz[7o`>Ƣu-aӺ"p@c XZ&<>!R+T, KZR+0V7g;テÇX7N&x֋^Z`Y"/ k[f$SOa+Q / sò7*M1^XT˓S'N'899)14M>#8LbZ1#D`2lYt.{te;x˟Ƌ/~wIs/K~Lƅ;@J`m1 dzn)|ˁf G<)䡴V\o!chIA̷ϛfn6X\/!0NYYL Vk|i[<.lue1]d2A39%NxZ0&|!"RD[vg1 GiGV$c_+$w><8Lh& NqzzcsL&W)"-ü#b]Gctf ;4|%9Br$D"9FAmY_8XD D+E y&NuȺv5u))5ZNP&ޒG0}3WQhO<<>g=)|61Y,CimylM[P)Ť~2/: % bQ/(B(R!N߿S5q*hipT|?RJdӳ[X-خ[t}u ~@׷dQDu}>ߋ_| z''l蚚]D82`x}xp4?t^mL.win?H@D}%~ ð>E+{Gض[xy>~RIC K2t#K+3JIZ+)զbp=ҁ|RhPiɴlӓcǨ*S}ڔ%i^H]hhgѾ,jIN)$IeDHryb}ע먿̧!+$OX1'GGxSOjl=n߼chSq} Pbq(h6P`*+|ޠ1F d8·8G#U͵@"ރAЍH=WH>C( Rj:/es=z@ Б NzQ.{ de eM?=.?0E*d$Rlɱn^ d"ĐCUSE?~s8 '酦Ö,Ams$4Cllp= ~lC>+g{CRSwXPu֕BI1<ɻO`; ѣW0/k shl͆EH< ( @%p sɗw#!Yf(09:K\sw|)G<>T߳#L^Lf;J -E傀 >5ggޯ../;Z62\9F;ʢ7b=oD ֣MR7v 1Hӟ&52abYkF|VO-+ez?%'<˔($쉪UD߂cbz1+SO`:xbx0qppwJK?FD&3UCcXcQ5 Jg^u3`?OkU] ;lM-˧b _I&Jd/$jp|>%Pt;`3WR\;Ǧ,U(8pttӳ3xOS,Rsuզg4JA=^GeR %kZZl2i;ۭaHc6p-"Cw<sDэ[f 2ur!ǐ鷼9Z64EZpww{sqxr_7Y믡E^)8J;&F 3bVy;eժJR2`!!dy.ɊԴ"'5zD<ܤ7 piԣLM>)B7NX+N$ m$ HfZ/@fEɨs(TB?B R=;[Joccw'yl8|iClg"m R:1(q c()Pqm9xdƞ!{zVsXH:`ɇ e(<[BADv##8 }J`0`Za0(iJkV(PRTt-Ghmy6.=9@*#?Z((\: 1I]C*eZ˖%U+Hlr=֐$tz\cPm*xDC94X=%/~MJ Hl/R9{ >D ' j˸I)itֽ&zNFMd+0ś~%H8`Dq7զj j@Eه*)7*Ji D:5{n'nNz|^w5v-R) !TxGnBűp|(Lsy!=iH_@ Ou+? 5Pmp@S4&zzzfC\R 7`A~W< `4̗𫷿~J 69_=BEQB = ] kڊ"٘h4\f)".w oLbTAH]GA,EHIB/{['.x2h_+u!%%6[ Ըz$.8"G'&XUwح:8#㥦P(z%ư%'2AFؔ0*!MFJfVQ)7*fIhQlUYTHZil5 $3RRP8L9"8GpC:9=3+%()[I!&|DzS~+,ka% ^b*)%Kz|l~^Vx*8v -.`I 4ǪXAMb"+oTDq&bHY(RPjZkyJЩߺ__Nv:aӅ`P`4b, Y bmJfc\$l-f}S&Q"9{fu 6iccSsΒijAT3@p,fX,o1=a29p_J{xomz@\l-ٛ+/+_R*0֠(`3@Fv`Ct1)@HvTR'm sSd.+z+B 3 9@f̭ko%}HҐZB˂ SS 0x!S/]͌{ܩJc`9֠*45I}Z/0>;WWMx@W_KZ7%5!SY2&u`X>kS<<[L/ ڔ(H0AD=XzN;E%R&`z]]i/hGU V) R)БtO.P=Iׯ4.gึ6%)Aɝٜ׫yGjB)yI%<%1 6,LݠkS岮Wjp|| -LNsrvUXkQ=r4E)Be Ěsu( 5ΰ@,O lxuJ 9;Rw[ SDHg/7#YO/A$bL2KH뾌w{O"f "Rr :nšuoMz@? $JvMݤ]yx#.,94u:O^ͺң'CL/Xb%NN!l ~&>BKPh cY 鶴H!ĭZ904!TD:JpZ8ߤ"\SzЕ񢞼 AXX"bi9O5r#̕bLǞDJIk)]HibbT^d)Dr1cA7ĭxsJtZЩkO_U ںA,zefW/nq~1dr'l\>T+z "i~>\4ím1Ji,MrWPDhG4 8ky*)QfSEZ 8§"R*N|=q}H`/y@$"Dvym C"Z*Bs%h)!扂$M LhN6L(JRq#QuhM40G nUto5lB ^n.?%PH7jE6\:$u.8u&j:N6gkOd%2rM{$At̡:[Ȩ5Ͱmg2F8Crƙg& p-Bq',!v'!Xԓi:Ak Z1pãC_̱XrsEB7tʠ?(t^Q-D8I"O u^dĎ7 E&upKD :$ 3^A#wk $O7WRbhχm@Cl) !:;-jppx}HE:k-l¹6DdVFD̓I9 [`z$ X1hjj[֒8d*"%P  L/8<<>L&l~G:]Bkf Q7Bi&1eY`ϣEZ Ji $ =:14 d<ֶgoh| 'X,o\bz>KXa NN.7fUc;@YhZM5ePz( }3q(".ڐ/>q_֍d>&=!QPqE! lfIjH8 Q m|^9;Kkky EUv,ht4o Ds @ӭִ* ȴ_-3jM)6} c׶( Ѩc<[( k>xg^jZYX%M'pWzɉG)iŅ%NC $)b @$  q@  "2kϵBwH%C|L-82lDTɥ֊}2ꔄ:M ݠjTUU7)V*,p|0td5ΦL)K&S7DYh4jI;nŲn:j=P 9" oZ1(wETGn{ra磸_t+㺸ތƘH5|xR*A9Gnжsh;ߡt+*KѰOG8xr>K&_ۯyX6Cq@1^ڠ49fAJؘ<Tn1*CPJä v'm({FxExcE0 vO4O{wjRr:-X"W+7+Uh0p6=-L/f89ɔۏ^K|;uӢW((eޯ#jboC+BTԍvTwtt'hT#];&՞Dž>bԍQna?0VuBl^yhۆqvk h-Pm*6BÝ'͖X,o1] {rK&8>9[?1M$ v\gI@X}8,n:B2ԄW}R/dRQ1H^DQ79H "s5fWRroԨ6uc:Twqz6ry e/mū~1_(K{PMǂJQ&էTRŚ_${IsRw Etåю:˩d)ҊO|&5Z4M-#`Y^899lmgm^gsk~@߃^pCzlن'a*ߨ{__IQ(8Sm'ƄTl0`4δى1ע?uE?B"K'<}ctʷ-.O\bĚ_!zv}vD<[E cZz% zϜ82 t`]sd!-ϓr%VZ+lxtkMp~>kL/.qx);^0o7! !pGUh tts%G}4@pB^at=H =1"!G2ἧZ156Fkں^oXMא({S,dm=[br)<^-vp`o4@awg'N _<ZhEuSFeHrNB+xCiCr GNO(UfsD34m-BplzzFS)ƻ8?5Xg///Wn Vpɀ!)Zۂr#YI+:{ wuA[$nJQ1kMC՚uSs@tM/8>9hAl'W}UѨp:.v1,ijjy4A d|$ F_$PЅJEܳ("!{p40-TuT˯W{71^_pz3^pv6 1|k_hxApƬaNl9 zh].DLyi 8((ޢ'j/7A]:2lͺw[}Qtz71/qr:^d< < ~1굯5cP--: !eQ@7kCP6(Ti#(b#MӢ1 aL61 65ji3`'X^r"/\nğW*vЪhw)P>Px;(-aG_JuZbB`GW{״dL!xkoc>t]n3>xP|>_ Ǔ=@~B8aoBkH;@EQ¡/s'O&Om"!ǐ٥ڶuRJ vvp91f _A~hq %tCP[`?{_a8vh&ύ>ŪHu'i(;x`MՊfZKy =/#Ń??_uo,\ۢVK (ѢWJFR/M i)<,Z 9@|#2-0/H~/K:EYCpy~!io6U[͆2jSxnnc GYXŃ3x'F6߶]4 q7՛AZ% FUUhMz/p}W8>>š 07~/?hAP1;C쌆xzpm͆|UWhR }77`,xM8`ɫ>G}M'gO^`I ׷X\ar|,x?ݟx pXW/ueddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddC[.2IENDB`OSCAR-code-v1.3.1/oscar/help/images/airsense10.png000066400000000000000000002207641417327530600214700ustar00rootroot00000000000000PNG  IHDR\rfsRGB cHRMz&u0`:pQ< pHYs  YiTXtXML:com.adobe.xmp 1 L'Y@IDATx Wyy^{UW.uHw0dXq!''vfl1d&q&sL|bxClo$ԭ{Z^߫[% "!R* (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ |c`}} 0 `Ǐ9~γK[ڭXЫv( ӶW]rGJÚI +>z 1]Vӧ{gIw=w{'_Iv'4SK4Z Mݻvlテ7x|xǎ>]FP {4q05g~ _K?/W{^)p:# wAw{inzNc;ڻ}ϾMhxEC0PD/X\<; LH{FH`ȴa)akW0(UX9&!4>𼔶,lM+Xg|{~q3ʼTdۉ5f{|_㓟wvl>5913O \U=}{R-UeBZ'P)S2e,ߒ`tezyJSB(N?~58#pz@!SU64lɃx+Nϯ6Ai7 5߫hR-s/^e_+W~}~@27Yd`K[f|? ?;ds`>˼woB|kLvu~Gn~aھST&냆ZyGGp\j# gM懑00WAKF]'D;~d/s\ElI<;K[ghq}s2wU$p9SL; HA^@Q,w*v3?<{V/A_+I- e[fݜ?;2I^; Y^ `4 F3Td>'"vS-'O{z[D?ww}]Ƨ1"Lľ8G{$N|c-0BPlps@K[jz۽%AB9eխ fj52,p{bb:u;?yMf 4]?}F҈2&}e Zb)e8 09: `a @-5z5?7^?- &ry>hɷa6 WS^L ħ8Ǹs>#sUθ%FSAnZ݁ 6dh4w0^\F=o! Uo'O~OBd! 幡{\Nի** Bu#$`XlS1թu&3TZp 3_q?gZ^ѦC|Q#< X"5 t?sk_a1 3È[_< R& Oʲ2/?L?ݮByx L+8h,-ɪ"ƲmrMp|tD*y 6w*_&lׯQȧCu22sgLڃaHX!1B Ar+.60UAż +X)Gş@SodkdJٷ/]VoK33SV5旯!BLUu/6] yWy4/ux)%q( %}W!I[:W,Lo*ؑW&10'BK0'sAKq oRw^|/A D(HxzZ K5?mG cGUʡyqڎ K2  %0UN153k7K;OJ7?N7|<nM;v5[kV;jwflߟԨ7{ݡlGwa̦۶[mHiр VZks\nƆjZv`R=%Ӷԫ`dH6L+J¼I9K[*vQf]w bv'.,+ޛO]V[S`b8\oII갿^ВFۥ&z߫c4tah;6kӏ+#^SM :_:a:^X`bu4MgJf*hq.׆ o3 k7`pF-̦.2=OOOySҾE?9YcZoDXZ^]NEhQ1) `PьP/lXV#E|),/Η D-(X 0uApeE\e2AN)P9`B1#̑2Pfգ4BA&szJdt Y"ohzc BhНBj},aT(uvI;#LFQfD`'e`58o=ՄW$sб1 vKӟMڙg<'K^63ń&8YHsi0l0IU]quSAfz'ln|>*4w uJnQT]$+)d~pTÕm im G/$cbaOgȜ*,nL7ut^uVz x'$3(leKX+D[u+Ԩ󩁆<<>ym+/M/~Kҍޘ?xJTN!vp=#N!\kc2gHClcހ-nhTc}*[Zz 3F,&V,LXBc bAed@wtcL`"pp Q^rf2f!$,%6ianpҙ8]_Ǽ~!YV[>i:ϧS'NcGN fSۭc08 f9:i׫$`FmUűׄ8Xa~yesWh+&S0)`ɐR/3xq_xܨb+IҺ0—jPe٪YĩܯRc_*73A4ԕ({a54edhHj"oZ!3[L] 5,lbS"T ÎέcazAu2MWjA-:ZV\h "Q>GX;IeR dBB G p G=J*N%Sױʻiδxn%6Zl9ĎQɾۤzT<ٞ bӛRsδɀءNRc鮯J{O?I/~m_lj~ `_8O6?7GZ\ՕՅJu|4ިWz'ڑȱKձqa"M 9Ǯ?&?#"WvOS3eqI>f,D1'@x*Zp056@ M) ~8 @ӥذ2h(t.c(ŠRM- #]B’8e)7']iaNVS}&w۳i#=L,SH1(+Ă(䎟<?P29X">]gQ(BA\\^C+eu w&\J#r.~0<)x'SN>[nbN2M(g~sd_ncNMyڸLA44} F b4NJ=:YK&7+N8X2>heh'$QBl~}[N Ӈ[UGL1nC4L(}~ W7ei@+.|cv +/uqn^;ί6ZVsvmIjoqQ0DW݌Q&S_ff>͌JaL]_D1g@x8Jo f[1uc7j3b2|.vF9/ Jsq%M-Lk&=&p.зA>2&]%MC0&ܯ!.ХTy/./ffgVNCu[q27Hdխmv4-/\i.otۓ`6OZfAꜫŒ>LX BHZq``MUerleNBBdG('Ld›vRO'ӂƕeU-0(52WH xICPYGmy̥ AC#Ѧ" ]MUG5R7Z `_skK )AA2L zXԵ5xeCF½&Dا(u XwgNޕ>x.tZ"`9tۆ#o"=x+A+ojz<>u*y@z_HC0{DqmdEAϸnrz$veT*X>ݫлazmV}GjQ~noho{Wg9f \ٺZ{V5ۃɻ88+DL{,)1#FD*ZW>4|P ? X#9Ep*]2P =s`qCO2P0KF0y61Hv:4?grZ+jiO-țV1XGh ؄yTz} $b ^ .\A q_NI3>(kM{/dX6BHG>8VC}GZ2a{LCҭDڷ{_:qD$(~!p'ꐲP|/c_E v;=2jDm KPI=2w9ڌmJUP\—ݼzZ}̊0#6ږŭ痖7%oڭtӝ^^[YpbN,v!f5]6Hhk$-soL5+3.^d/-j2CͫRY' Mi'4!n:dA AP]1r-ڹC4!H>HrJ,/}_h>c\+.$k1 % ( .e"^Xkm2Sښ4!gW\mH]`6rZDQQ"j![mZ K1EIH&xy[}RAK(WQ {/HOFح-?n-]ټb ?7ͯP x ! mHo==ZH`À }tU>H\fr aʯ&!ض~~ח| F=K;W[}S_Xi--13ȯN\ +0m9n $h%ԗ70uYR3T$z%)DΫ|8@ B ĖMo 5"Q/ RIKF/!ZQ S]LְMkhnɈ1.m65 tAZ4ޡmpqM6^ j2yKiheQ=;g;Vhzkɱ4L p5;f Ōe ҭ дvwdlBF9TC(AA0yf\Ua6'LAP#(KZ\ܞ*Yx+a:2 ȁ;,%WƝ+eH ?}l:tw~%;ί>Zc1.xmgzW,I4ҥnc]h{[ 5_ܹroy3nkr&ua s%G_v1Ruo^!ͶPRIKlHnÄ'm &0f&$o"'>ZfEkdI5{7פ6#0Y0ьe62!P\v*4%9K M lk+$SAm, ԛ5HRc0py=ZT;ᥛQGjhx|٦SgR>vGb.64M`|,_>L_5&k"G -]ƦxB0$Gq5∀VK_=- DVV09\M RS7hF3X&>WYW;D:뢌+ ++˺& I0 ; pK|&HTtaf 5ƹ` 1#^沎}@MO}_S|y$n{g;ζݟĻv.//Ni_]]+@ye%3k#<9d!?n5EDɪnIir)%f};^K@"WS*(IW:3!f8L>w*T`qW^L]OQ k'8p MD`U!̡MU$"ZB01hw91؃=iK aV\7Nmuv –:v$$`˲B$zZXBR_k X$w_''N~4vbcl#H^auuy KJ7n I` iڴτB稼j!( 'GY'GPN61RL./qBZ[a= PhyI=~aZ*.Gr]ޘEF,ÇđVh6{҃gO}Oڿ!9IsnDXPX.Vi#=M^)hhؑ{_q~ܕwrj|W.;%r{Ӊ#4䕊f*,AsI+$:qwF*2LU%B#S 4^D(D!maڈ`L]uq*+s9< %bCSK .@``ċ#>Z\x#V*@q6ȅzڍ9*cc%dhdrS'_7[oW]Tļn60էII{ָ> ,D#wmkC&} 0m8x5isd>/jxs 2yVמqgla8ԧB!qַR: !ŬˤAdqd_ %,fDp(dsq2͜wqQ&,=ayQ!6n6iyy1*K{ADг[t:v|$̽CnN6?GfK vt [#| Xt+1;}gFa ֔A61fGX }SxS  B(,3 А Ȓm4S!2}zE1+Qԩi %W{t 3iVk9EVOˉh ]^]I{uOxn:rZa[V*Lby/u9ye3in1'C.RDnc$I]zI6:g!(0BvPV`|)XƇk| hqY6B|vY3]}Kxs?+ hq)$[ R6t5@`|&,`u֗IvO>kj@)g>s-0dn<x藹&ȍ̊-AG@h\M[νFѓrK?0T=~ s]p79s/~/g,I4GihډDǫ ~nH\}eh^xA^[K 8B @`C,2ZsG(:ZG\R*DχZ6DJ,j2^SHcnȂ`@$4PZäƽfCc{VZHxë́Hפ(:hAB] a#|ez{m'S&1!׮4V 3>oOc鳷ߕ3}<]u>ָ%>ˋM]Y] ",@jG)7M"]⮁qϜÚh$v&9J,:`&[ҧQ}$[hU ~d g¦`+_c9P\ҀDPNZbJmˠ _]ii9~͠Gg% i1T>L`Vn,u\Xcn-H KΑqfM.- 0~7/o^~g<k| lkޙ|y- UեsMĺ/ecR'nJ4%]ZeOޖ.sAz|q~)](}K6r^>dr$t7($"JDjZzE@RʔU@lyr2#w*p% 7: EjdI= 0~Ӊ\ڞs_4At1}o-_ʗ2-oM@2a&!‘̛K1 $6Ûۉ)J{EMpcZH:zýMFă'#IJ +##.dgw%9[W8w;G2q#'f%\abGHl/%\=PDke ՉtE ]C NL);&Źȧ@' }痚!Uv4òҼd!8v,lnQ2tx `CF}WsA.?=$Ξ9{q S}w[ `HR ܙ鞻N7UN?WE,=/9y:ԥ t[$0fQW\^2WX֜Gԙ5 H¥CZM . ۗbp2e4bw*qhh./$8;DsŠ_ބC13=9=C' 4֡5.,K`y̸ĒCmc $@wN$݊&?G0A҆0N nenĎК*wH@a RM@A1MckU-H?ߜ{-Vv\8fٕ VWs4CЭr0di_f(nJ1KC..{K"XjB)kAQ x_U j2 ]࡫Ȥ;\?3~.a e$ K]\pQ:pחm_5=ySng 5;į;K_w1~~&:k,]^/*)ĒԆB8J\T*FQ 7,rܶ7Z@WXh ~YL(( PP(N\Oco4֩A7@`})h.?1luwrj,'w~J 5I>>\Xu`nj!f#NxhZC\p#$^ɐE5u=ȒW* /Z Z S+Qcɭ%V&Xc `BckQ .cYVLAjF$cUe*~J=k9o yLۺaR XqU S`:hx>frҏ`̰vhbֽ+,᣹/3ځ9=CQUQ=@R-I|ࣀn[wxXDz_p c2Ho?ӏ^-$b5Z60gw-w|sz޳p2(=yOKl(w`b/qT 'o'PSVByF=4QwE2$dx#<ɥA+e84W;{o6Dι0γ'hC%+{lq  2Y# >}ڏj2Q%via+k GˠuS0wmU`u?MS?P2NOt5h!D_m#XSH6=g?jL/ed -3LL0<0do$ 1|Wr?A-gBD0)4BP_tk, >0s6W"+ļL?VC>DcN,R|A\Gh9kM +E^xmO`$/QGK*GoC*-iIT(ZҶ G~$&… v0\:^̩@`[yT&t٥ץ S!T~mt.ْJ+tm=LI]/I8IU+EbBHQ\VdF5'lǘAG2.Y6RT9"LK=ղpLrqOtҷx"WF3<kI[iKՠTݕ:8Ő= y6 *'ƒ0=g`Fmh43?Ш- p*15yΆas'P*hEFC8ZfwI|2W}0l1*6 n+4A“e.}(<D.(_dMpJ A W,-E$"@Θ5y&؂yCl׸A1!#๱[c$u9woG60Nˌ.kzCfbmM2æ1cn, A21%P!#n -,lIyߝgGؑbNLeY"A,N T"=윹r<9V4/p+)DFnVsCh#ctN?1זڄFwAw?jDVּX(ϞAZoҗ6/2@$?t5UIߵuqҶ馛>֨D]`M%Ps ͥyqMx&h%gРL! Zfh`!?@T tP2!ꚥ&>,TOIAmҚ(wZh(' &f^X$L',;Y1C "PlA2C0!јCo[B8Zc'3*a~aC X:eqѶ.¦&ʁ#D>$`ۂHY=Q@{Tz G`@qA߹I)^ܡŽ6bF#ZPcpFxqD)[VԘ32xu4l3-*F?}|EVo1)oQ1PCv%Z43b=ao `/ A٠p)[j 7pҟ f Fc{y/9qpE~fɴjǏ(]|t3?k׶ؒ^ `K,˂$nà2K[”[GuZn 'wiU!vp>BP y (.A\ʏOmDf TV]jd6ǟW["gv(Mp(]G L$SB`\r-ٖf]$|4K&nDi%pX0`J/w% ,O %,Ȭ[[Pi&hRHpP3,-TB"2 L̐/xџ$,iOKX7X8 Uxi*Chud\N .fD5,N Zp@;+VDyU\|e.{!0g[`$W6dq+_vBNWy,td|'m3Q0{W<2"NY0}?ξ9"'BXÖ"Bvd^pC߲mdi֥O;8| =OMc9?:8g|-qh4ˌ8\}#Ŧ*$[W;gI|&= 9J߈ y,%HD!,Y$XFD1\I_ɓI 3A<۲ HJ7& a! NK,V؃>{2}jNvEZ#΁殮$Koc^hpҀQcsۯ8s58M;کҵ=^LI4ʌ_fڡ ?nЌćn {Op1ZofJ=k2 >\&=nFînYFyjYSQg(F,mu21ht !5 $A10W9D{sq f^H|yv5L?iI zK.+*Ϳǎm,saT2k%6]u%HGY2{OlA6pQ_W1V71eH pT~txȇl?1 79a0|^S]2dIܺ&]qʴen; Skst}Ȃ @$9Q3Įd:!\840o O1:l1(M\c[~3yw=fJ̛>2LvD~z dDa, }ýtq_bS)9ъX k-1l?21HJ%PX@1x`ۧɫvn_H*P^ǃlH$YWo!ڌxi~fpڴ=[= {61hpنYѶ((!jMa^W>OvB?Ch>㜯Qq Qq0W Fi 0X \KM%vN';:&iwqG-'ANόS,gNF xu; |חHצ@0wUjRWØ {ía*u<3ao:KrGZ!?ρ 7ѻusJ p>u55]WG He2\`W)LX6ry&SI!-,HX6]c 'R /δcC:P:+x3+KA$6ހEX+ ,.J[eE(m2c#\7h2KV Iۤ_ilkPE:aA7z(y[qE pNMrdq߇&[8!bG Ca.Ȟc\#hii5N8HG+b^)HQo|w` ٹL}$6J>L4ZLX {N͜jk)ˬP]9cOGT i޼ Ye~A@GGT9SJ@Jp|Rj!4֔='qIl)!+zm:)7{l36qc I֋?7 1R-JJJ|; ie.4R ijx5iו 6 Smwvr s&U0'yy^E*?Aތ4g00lK2?nd_l8in*&p94 (qOlykCЫ?qB%l'IJRפ^ dH J.CQipl=8.N_UݎA۰ߝZU{Q$8N(,& GBX+ ʗKM[p<+bL!㙿OFyl^IU+SEQ̊bCc b@GwrEz0+If7ՈqWG XS,2=i M >8[fi>ƃ|HJE@=Ǟ*LL1) jr?Iޡ'hY5 Eq\.3)ZnCaIϣ^!΍+Q<߇*{  H1rb fZop`1B,+4[ΛDmPM_ՌB(#d @qũ匃! LDD]"IƐ(jdrdA)4tn 'I^ jf?#b0($x3@͠I .P @7ĥB3W.F<̥^Fyk3dՀGwO^@YIr S ubuH5%ff0EA7ݵz*_^;;u4ع7͓hhT!p݇6/5}q*Dt=`l:>Wi5R9ZزPЭqe@ ᦀciKI v$`yR߭a ,1!/i% LZ׆i%+.شbc2B`;) @;FA|aY AKB!46my#]}.ǐЧӒ@VpNJ!!-oIO4w"ֵEۿ@/z {iln;ӡYƩy+ B~DiƕCΓDVeH}qW6wρ:-'#H]hzPQinLxd|ɩ6E-d)8k+N ^ԣ\пx|0H1AvMAK'|2>c/Jt,;v|a$L?(dH \ Q `t$j]&jdLUXԩ\}.m}2:|(]sos|CivhfQj%݌]#aN`.&R,Lp@\H1ơdHdjG<4$5#=$vuUVq]OL~0w4J F1Qؘ 6Al{Ӷץ=Ye(a|Y+ۉR\DR L8YA !p@0!,[ufe50ǟ XS4ȸҲ`B32i dAsh1n[~>a/TR@IZ]65_WqXK&4Acy۶e]SG/ːbRd|TRA1yL.bVFs46Qcy `@(XBaDcEm&|M;vs'"nF1]׏Sa#'lz6djq5Χ(sb(?kդ״`pR D&8\ h˵'19S u#6֣ے 飄O_-y2mRk))ۈkni{6x*,$|S[z.)1Xe/ol+4cfOJ^&y=>Z/%l:~Pԟp n`,m-.I5y% b"pR:%Ǹcj2[`NOyt08滫e|@rr5CFۺN%.7`S_lґX1^KR78/~Z[XP" R7GK!x;{_r%V@&*y v 0;?>럴;Ić# 3OGC(UAd=9s::t?!KHw>\7ڇ[D[8CFX+J}D L 2G,) ksR˥@0#:*[B )Lf=q$n=6=8?K.*"NkI\ؕX xdYD^@Il8F&,-ݛ,ّhqX17.>, X2ɘ A;AXHAX"òIam0;6} oө"@x{0LpaN ##bZfDKhBY faAhznrţyp^Uά-,҅-wƶϸo[j!wr}L 6 0z~8l0BOK$r| AgDĊ_"p\{Mz=[7z1 i. )_v:~D:~`ӶSip7]㶽"W 5 p!!8<$.) 1-ZQPLY_!^ޓɌhh`AXOX8u7(d0o$f%VP^dosJyC{G,!hjH>Yrg(qҤDnĊKoxWSd{ mYqsXG/Ҫt$~+r;]4gtYbZ`huXpq&  be ^ yt,N 6v{v9,#X۹QNuRΓ ql4âr8K&E<o F>bL0ϢWnSG%)SqeO"y13i _.qP:+V C23>;WΖ\"̎HPBkAY1|0v#Rw2{r/}Kړ2<{"I5ϣ0mؽ_)I>vh$mY-0MLdLqu]:ˇg@j.sYNmjJu:A; nM:)Ie}^ x,BiiL-F!8rKBmWb>#x*e5!%`t4hd&R3tE8y_myWrD"ti՚ϠƂ@}W?"$b!U[]H6@A4숀.;F  =,1|#G&}k Z+/ G^󏴀i;/J!B󩯫?3AȚ n`;b +QoaWXRѿxvx/` ܃4u|?F[gQxdD_z1kr R%g$E9'05GsOG|g(4e& olXXN'^4gjVOyrL;<;wwE¹vvzxNn|^eŒ 3 ]hCS[OԦ7.dlsd H\-1~w7޳`5QaBzw䂕W: Gc^(~{NG>s M$6i\ XQUU&!CEv/FD{k+]ݞFN@ց59 4dZDo1I]G tber:%ӜNH8I[(Pip3h $@C";{t=_̀QVI]F#˴څ f$oujtLg)Ϸ<7ȮOȠ*HX"$Ĭ0pf2NQ]0)vHL&84&b#-xh2]Vp;8A I9N80-FYI+7_Ǘk1noZ" 3}IhL.S)}nn,L#L($ZYBGE٭;XkG(bD3mcif:]C߅[FUxa)G%(.b6qϾz"vjQzVݗNk\]+t&Z}nU2WU,f,߷8e-؞PN?8Fx YZX}+bz٩řek7o\!Ͼ ;<&P:U'Әԉz빙0.x.:^iD?Im@0CT \gxMJ/Qx!"zU+g1}~,%GHD2s.5}@B8zdL"#Q2MFjRBX't%Ԥ1m6w-[:/J@>~LӕpGxJmF/wmٞb֯{"L 2HhZ]nɍĐN@sqՄɰƨ=C݆繦]8 U[CieMvpM]2QjS 4}ueH9P8s YeAyE\&w"4RxSq'%-b,>,hJ4.b +ht2-usWZğ}xݨTK"Fm(dr4W-=#իx>psP;:K6!];-&(*>#2RwSa}7x ޢ{R_sPdU&h}(.;F:+#?`Ӑ~ )H+ g6OcEV*Sa46fg;fn%~rE :WW{j9yqN?׾31RffXES.<~p~|l4/383Tr<#lZشD12W܃?bn>~w%(@4Z(150X4eR5k]M07NKB'4i@nT4,ڍzE?CɬVY %5!;ivS hsG=?Srkڦ#,ѨI,΋zS*H}H-"J|-Pƴ|x񃟵ğ4 >lVB `F0B3l^b;;lcB W9;+IˎCr? 4\x#'=gh?pvniscT+\^cyz-O'9x|?ZFI.lJ0q G@,3 $CB\[9 MalMz5n5 17atjENnh]ipW!dTA2>Y39&] fԶ̮i4͜M9r*A &[fHIXgjR\ɼ}0OI$k-Kr< 'Ĝ%T_HoA]by?Dc1OCNʹpVMlR)^ӖaqK/&7I9Ks9w|SI6V<b'v7Rn=]q /#ҕjmW+BMw"Q`@ْ4"}kiڄEo}󜣡ہ&-&2Y8e}2NQ5#. m>uQ \)4tzծĜ"DuZB%M9T0c :@ m:T_;aJ}aM Dׇ#_xrdYSRߚBwpYBƯ3ˤͰ hIiN]%џ`|Vsiz2&ev=28Т\x'vxrmNyP69c .}Xff:L ;4DaDW)Y5s Z gޅ@'x=mF8f^pR~cwI{"H : *S`ri aDg\n#z;8kBV ڞO~ KVbqƒHlHCrG?vp]IG_Y)=BV+HZBxapHF:8¶ӏ $xGDgn8R;! BMa@S cv 1=e|j!%YPЏ;bC`&UM ɮ3sLES AbΧڪ:c=ΰ0nN=;]Avce"tau91ZEܟ-Sl4ui,uafldkEy D'=x^7-ɜMx5 /X47B. in!9  Tf6-v6-:U:Q7%5xɈdӎe{;V8s:BA`&*o^b;zfl4z[O*{;Ůrxr5:OVDTwf .'WV9#vz0;)N]g{237ȌUR,P_!+ _ԣ %ھN9*׵X`b Ʊ_ (T|s Ha %8 \ח1 b 3&@hfB,e5: cyA3Y2%Zm#}ĸ~C+Ҿڌ?{4hC@Zw|+J=|K^]-z JpFpg׬F:V Cd:"qn{ǟz= LiDh0Ul`)8ʕW;N f?[L#QGɤ8 eGB:+ ]xA, ̷K_W#V"'V Z1\G&FϞ2c 1xʕOYJЩ!lSf|彀IYڬ+-lF98XHXL݌@z{HٖO=;ow̓c~=`#pǬ%+c *6Ìsry&fDشlMZWZ9yӖ EǞx5,9 m0^.3v@*Zu܅oPs>{%dZ m;# masӜVeL`1~!B MRq]]:k!`2sn,n ͱba|3'/=q Lre3WLCyN_BXDȫ52z@ȞkLK=ϼLAg v!aoQ6DCF·Kzvx>n1l 5? #XCWkLG{z1ă{޽`*̔X-/( ma%qthH+PKX5NM'x.LÔ7,>!6JʳN'4nvGWFn ml9tт̯u3@vĹ8O#՘ytF7;]Uzvզ^2a1&rp4CrXR.P:.*jlj +JV#o`0`w0q\*c d"TɀPU%iKė6QH4ŜTc&&T"QqB-ɿoobޯܒMYU ?s)nrK 6][_%vz$jD.i "B!*NűD)8bs[fiikA:8mUy;AdOB/͸4h#crJ7[wnVfw?xܧI6 mᅫETYX^QCr CysG'_Yw.T%JB $.iJqQ:x-~Ei^p5NΚ:e2$=`ݭm x _AlALjZ*,J7kj`SQ'ϣ%i[ec@0!>*5M FiL7VBB% k;`4Ӱ\p\H'Dܵ* I֞lQ,nB :8\39T9|(͖2.GN3pL_Ej69#c eQ,VJxf'c*~6"b҃"&/% *<\E/J~)Λe;{ܾzݝGZa^E_BA FZc=,6fBYs;`'cpbx'e*10J0ju Lbѹ pH@w]*橭#fiQL\$J,p9 Z1Ϙs1\XNQA *> mL9K\!;MA,Fe3,Ao t* 8HL@A1k0'["VO.3H|/##NLKuk%Vv-0*C7RA]yO봳gr+FZ~?y"W'r_yʯ//rcv*[`td0u!\$С#݉J WTjJ+Ik0uZZVǀ:~.oy/V4.)/$ud!20/ec@\c4>dRed Dx 8zMd.u*y ' &q܊O \fG[~,k)膕}|Љi|byc#o=)Ə[K8l48E>1,?A?Xi5BggKm#NH,iCkYvS;/X3JBC3̹#ҽ}`ztb$lpIg?`J;?Z- f"J}=--̥:\@O$1d-gȕ23 1L֩㪣6'EdzC~qEִ+x"3&ZYž`H t9F);Q( 4EcmLo E`uU2?]^>g˅0?rE!gU4 l ΞϖcG$Ny2]D 3А]7F9}Բec<]A/ğWVji5xCP|MW<ъ6x2,%}.{KX@e[~kac,Wf':5mq~L7'^VPx<7I3u4mzzںnOi"p_- >)zrV7`@cc[ M"A&CE(%Gtu]%:e0tQkjyJEٱ DŨd.\_"ʺ~S8eDy^`C Z!,M1O1i;@X$jN͹ud.P [;D*kɒ%M͆%F0TNqYWJbCAc+|O-Np8t^ܓ0+L!?‹\7,_g.,!}]'A#,eܛ,8vgOrߒÎ`|xҽo$aZD7~)rkf#AZTb|@Ĝ"xOnhVws TJ:/wЎݐoTm~pK{la+tߋZDӇ>ϠC\RlJ ~Q:}kwsWRS"VP@qvy'$9TzTN݅PODjq# +u茎M m|@s5A1DL fj>9n?#x00hG- *4Yn% dz2ԁb hN=2zF Ƀ"::P-M 8 ~\/MGUjyh bпeA[$P)(8oU`8N ײ=`SpgP\GS8gh"hn47%Hdz/=3";Uĵ{jhr*}D ,):0c./&H_*6E6w?s?XֻV|76VTEiXJa9ribPn 'AQN!G"jH 3&Yx c*<\]ռ :~\p"Mw\ 7:#D-,rnh04}$5AL5Өl %S IssPԉE{s_R95{_xZ `Ȕ̉DlC`9~p>\cȡ;d~P3cN8ha>QŃsniFv-ĝ-"}&n9Ơyߕ9B߽.YNNF+:q'RmԼ@TFԕ`1/~ӭ,u| Qih{86$X_j߹j 2?~\̯u/,.̑wtho&ȁrԓOH+8d pM%ֹZU6}n9=7A7R}LH ))9͎?!k0@InZ4r#!5E3q.Z@*zoej%bY&@(*{;([T;:)ӸDsѕzQSkQ;2w iBh2&`I)= PdvMXlŹcT %xQӪIW-MutJ\zGIߥPC;ZƏ+WoocSN(!/>ı7WUU/X2srxܺu*7YNxMV@ DDT(vĀSJiVbL(@"+HˠU_߂XkSc,SeCr؂Hj=p:}:OJ @u;a(E~ hkkucsbeyYEx\?.f X]\я:(s ˗p1ϕ;KgјYBw2Op1 pOy2g tXU:@#,+wc]_Lڛ&@N6j5SA_S7[Lo3&Xﭴq{W(@kؤWdfR~ʼn}v̷Yb%hi \1w,+Uksk7 {^ ;[60X\$isxNqR^!AnSٯ,kИ`mwnƗ@ko}msSe֭ɻAT7 L16l s~~l/nyqp"\io]V>Xx$~AQ|}r2U8\* Bh;Zk b+4`GF; 1q2I3@~P(:Z*d֔}M)ƕĵ̖g0FiNXL0Akk##"1&zVz++$=ׁ!Xg9 ꐱ U*fCDYvFNPt(j`TLfWs BW%iӌS "wLÃM9Q/7PEݥmc*h(FFxvɄ}ClX#ƞYnBhxĦ8vUNeg%4@1Gv 1e"ܤUiJw(fi ?=A˘cG1 ^P TvmY "2y֢:$i#Ey VWلо]⏮[h& #};K+-d*G*n^+zHn0v}.ac߸PVn2?=_j93/_1e]ӎ:EWs"d G &ȱu4ᜃ:O I e.fUXl*2|rtrǎŤ0RH<>,L:NcRF]mD=t ڂ00KEѮh)y{hpvg(evm@_{trhlYc=XSOkA$ QKh45t%kI=tGxIiÿ_0Kd[oJpN]gswmd-0洩 Y1O =rl R.)&G4n7`vSyƟ?}p҂ /V#lz4.]J@zĒKǞ)Oen2Eߎՠ/o|l>i{US@Y\  ??kBcAIBf̤s$A PM* lsbZr+_ۊ.ePFbm>?(")\sLʨ=-'sC.D9zmU .HeƅW@Pί' "EZI~)# ^)/u9g !W׎ERG&%TJD:urĻWz6 Cad XbPfղ2@lWsOT*e|J>O`VL>JڦBv 7#e)CTFqo'C["4Hx/Vrc8ES0죈޸~^ԗǏf/|VW{VD*hvL|%o>o#Z*:Ui $)޽(]=8VK9)hXy^xg6's=w`' K(P9ty=Μ2ktj}3S:@'ӸF z@ui%OM~FB'!owDPGׯh]qACl@$M/&@"h?ǩ;K59|b>fiHDB?!p~;DJ) *[a PZhc2`n`1zZS+3%~h^i 4`!Q nd&~p. MybD0(uRCpqb +FV9cem9$gh쌅UL[ߥzXI.)h$9\f@Ϊ=LiٶsIcٚ 3ٳ}ocbjrCmn]+.޼Z9|?pΰskk's0fwi?-OZكYCGA H+lPV2aTH!r dI %dQ"5+)%y ^Ɣ!lpY +-Ԗ2+LYfgWr6XeH9[sAf*W#49rڼ:$6 2V7 4wCoMx. B("|/ NeNzHFubm>KZKCs- U >xչ1Mq So\6+C1X#ρ1KPLKԜzZڶۯ^,ǎ/,(5U%=xPabPʀ5t?^)7g >&[=`Ǿ)cGZ ،\bS+-*TlԹ{&퍅 @AMgPР0Ʊ XF^?!rPW?3׿~{f | ׯ/*|iyVV0~RηOG 3B10>4}ƜhMF#c& #jE%6)"DGc%zH°.E!,pߢա7^h!r 1#eK ,`dB$N}5SJ8/_s58Ne]FWp1XSrk\9/z dyWY|JTƏ>R%= p0QjW02׾SҏyaaBd0?hMq%,Q과y}Ӵf ,J])H;gܼWX4* 6W^8 ҏ_ sS˗.[X D]pHI4_2A{o?jZP ChMn9qyn6P5|k/P@hubgĉdfJN@ .b<:ҒkRa<*~k޽E|x9SO 083C0LT aqf{DiC"5f#&xEJA(Yoxy㲎spc%DvJfAstax^\p$q2sl/e@"Ȅȑo ;2FNf1.@'1z7\Im8r35|NZ 2AHNV pS<-[gexs-C~x(,d61iXpd/ѹ?rX~tYA ,B?q7< ؞QwO\}̸tHӶC"dچՂ3q!R̹0uqTħKo?SFbY׉D߃>v8C^YyKK zr|G*@ IeؑD;d'&~C2a|9SIE ?) n3pٴ7IZO GZL]$lѯ5ap!׀MzL!6jk4QꮔܼKgPyg he~-`+?uXDd31T?1y>2+RL&9" ?#YOùkw}KϜz\v@g~եYɼ Ӻ[x_>fn8^4)K JSG}Jv60e07#?sҘLJ]ט&$ԅE nJ݋w?Nz&}a:<2W&\ CZ4𓗠ۮ=餂5x|'N f΍qHsU(xy$.^gܬ $:k_ r}+̛AҚYJx|ybv?;6V-DI#Z7DsD{z9@>(ܹyt2Q%Ǧi3 1(j_+})*) PVSi3 Q9#P3^!P0[׋?ءmN)` getrnv*wn=㯐_ "=*""|u8~!<<:HҬ^~yýC]w% ~}aF}xCˢՐ{}y&1o"ՓĂ^SM0 UC}0B|f xD{3oYn:zaojgo:Dd@):241BІGgn 0)ɽ j$V0k }>bey,IK H,J3,V)Sٱɐ|zwR qa/Lj̥YSDA$s7n:q,'E^.[>۶{i5hL:| ǑXqI Dt=}t4G'dw G2{ xi':]K6oHy7oAr`(@ nIb>D)i]V@& /p8}RR_W"lC$hQ'㇗ΒiIC<;?2vڐF{LOBpLokƒ[qea~y啷(!0-]ϲ̖Ilu*'WobbjMʙxCd\փǍ1̖|ڰ~a^TI$(<&P>] 3 x^Kb|?iP~"M ʩ'(=|S,O>ÎKV,4љTUX0_~/H_\_};Nl9z56dFÜn 3@/p^eö1HMB@:njlsAid !dII Dff J bQI%/*a_xYJ,^+A0)uhX#`p ̱T@Ǎn'ڏ)K׿ BƛU벰Z"jc<`]kI.B}]ܢ ińJZ\H5% 4)hÚ! HF5.>I0} j !V&NB]ST@p(Tlx۠+iΥ\Y|IYʚ(!P=vgDZ^V>ϔ'F+3P4Ac۷ʇB]<1~|pLXN')0HD 0ۦ0,v2&QK+0]4mY>)(?!BZkcrT__c/pj|f:1#],%VY:7 .ޡNF4a?N`ށo:q­Sg]7DŗXa9cE@_ V\yg}H2:A fTk;KUvqi.)7DaYEI A[ r>}C;x9|A;LX!k8+bcxKK]ڇؓR>ORy0e9sewtd7;U #0]IzVlK|roQI]"29uzOFp(UVŤd#Uꔴ-תPL}E[ 6 يhb:I }6φGw& x# K\^/|3o~?#&AvZQJfGλ3Qbϝ$4$Bd^u^O0&orz)Ceq j<2<됀451$k R6n" 0N/ ZwIG԰Y»5w8\h}57V ' %{p8)yW'[lk j 4 QpO#d2ՁU,H/;*#}=b[0eT K]K #!Dp/weXY}1]!/W/O'^S`Y@GB~j̽{e(&I~:~ByoAfB L(ݸT>8{W#/j!#Ik5qғd߃/K7GD|8: yZ<#288dp`Yc KӾ&ra9d=(aMĽAK*SX:f9&xL*TITd]4o(wro!]uM;|o~ֻpGwIt ۑ?D.o]g &_o| qs$7MjY'C(TW|sߔPV֭Ah"(ы5@mgKx's0 %lFmv%ԛֽ ~tZ9L:ld!(_mlOgTҿI.a*SC|bj؄WFsPf*+0npBTyJ܌8_>(4#m]FwB}.%JyB5 |=IM1RW0q' VvsOG &lpGN/wym]Z8ly S$Ql#qZnFOzwoW/3ear{PXs{<\+<%s9Ҟ~_i2mCk/}V9YVW62Ё&eH:FC x] hԨUQ!鲄cCB$~ġ/|`P<'xrKR0sxNwY:2O~2 l0J5 '^Ws ^Q%FA u!'@S3m3 "7 ՕjPE;{{!@˰@җGJ˦j0E=mRJ>1y{j:*QBĨI/(50"\q@p%|.q Ո:hcNơ.j{^.D +*ugS炯nR*c(X -A&кQGoµ8ihK >$F˲ OC(v+Y#͹BlO{oȜ'Hvd<ӡa]qUI@8gFYW~\ZW =eWV\fWZ#u 6VɔKgO,iv*XK338;:kksDj^ ӫ5ze4:|K_23=[Ν>I33zt0ۿ.!nױEq:""WRry{w;JLd',ewBA2Wn[ԯ{ Bq A3?.s+c.GqHh= iÁJn6hbgz!UR~4)$MBlVV׾}C"ɔ.—hmcaiMzWlިfxfyp`Euz4h&²2a¬X;~0juVVwQ8#c'UցfΫj!(Gpn[Y-o&J WBWJ# #~[zA<!'hWc^JxhRTa$gq9nx.U~+ Z!tjRP,ӯ^\_q!n >{V BL;)EՊK՟~ե^E"g=%O?4'. VfLj,0K -g;+Ǐ,O?$?O`J/~ҁGWCcL Ou qiD֑YYṶI-iYFe+4`߄l3H%0_: XnNa)slr#66lCtA'M#˸"RcVBnN`ʳXV?xWR3E /8= y/c/Q ZVm- SdД z9{ qaHӇo=ͦ@IDATx%'ucf +3 H'P9,@:=vjq\sa$.l! , Y Z@㷨rr'B^إlRX2C<~8t$|GU' eoEe7;)(3[ZA R<|֪B\>Z(ƞxС}< 6F&HljH9x㧯)|g[]wwV2Pdi ؁=ԋ%l9,$ljƫ\?k{A.a)a8oZKMbЪhա6 )uH*cg6KKF?fq K:Zo7o.-8ȲActk\FڷhOU`d3$ j 9#e"̉SEs[!rUlh2/PE:B,-No[uUp)o +Qdm`泎JG ,- ,1Kټ\夵C_YV,+\Cmb-TPc~c:r:c[ , <ǰ-g /AF6}g5֜~Z۩P,iKl;UPT ٔSi-yI jZVHiqNpzGSĝN0{߁) U};{ǣkwNHKTR? W\WyS;/_aZbƑ9<8`43GV7nt].>GF} еENuv\j' (м0\).:jrhGT _> [c];E?84=飻I[m"Gؙv>6͔;LK+Wʗg8rr](괱;]1]?H\Xjp@4sC`!'UԘns/_-fN6W1mXD>W$P H7EÑ|z2iMwk wezݞ@LTdҐp9o6Qb'׀kôiٗ1#R8~,N-lLRe(Rʛ~qxBv1&ϛ\TSԵN@N\I4Ⱥ;"N .:V1V*Xzx[H%mg8FZË|?92vx~j^ء#;7"}DJ5"}@,M{`u AD36Z 44C+0zɶxVNNCqάcM /r^ܯGd{1pgr/R^ϕ~`XƩ~TeyfYzz~'hvXf̮-iMfBQI T M h>ʉ3=f,P؁')ADE-BR$Gh [Gx%0Hc_.IḴoan~4KHN_ʥ8_g33S;ˋeܠ]m Y$ N#&7˵mA$0݆'euTZ " G2y-oA-cb'O2~p N噓O*#<#0ow<'*Y[3fL/L]-:IS n$527*K4|q)'x0+ RQF"J$|_2 _njvAFFw_1 |]s3c:3V›!k@?,˶m'o!J"{SJi* C .d% Vy?Ic`f*.A:TfLJwDBӽ8D88qa<`j2mYBbnG+X'~sSY-'='%&XL1F utK ~JtVdD'uY~׭xԘv%Sw~szz7lǯ?]U|_~#Olw"&{4 {s.ֺ]ux|jN$`I,r.{! [998vTֈօ̭!"=xg_ݯr$\y_߃=LFڕѱXxܚ_>se'&nE ^emz"ksLEhQ8pcȷؔ?;M|0Zdĵ>]%zCP]o%3['cJ c|36&S,i]?wlvs;R#ef)D;ڴuW#+ mS"vO J@ Ag:J]ւb"Gbi)(ĮTX?Z\\zG';-~|q! ^3NLy)<!h}==NCS*>uӟ%ˆg~H?  i-h?׽o85;g^--m0bI%I -bt "q\P ;Xo^ʅ g+?.}Rh*Lj{so^Xy9SnM+(ƷʇQg]'Yk8bx+Զj52FA |fU)HUBQhbuH'0[ AǺ* 1[Z/(Y~5 xq,cû~Vs|aWNxӋ8(tp7.C'H !e|C堥8D0tVj"LΪ@u.ftA+;&V<!wx,[+.s{?p)GJ߾CFÍe6RR>!@ZL am%Y.c|jU6r}qљ9΍ӈOO}iޣ~ o/~gqAцcJ J :0Pu} ӱS-AV\ jD:sY]iz]pw.ƀhaLTADFx{ϗA/?hoV~/=x?>[*_^x.LdYV& ̱Gϯɘ$ z 1^H3i\sn3Dz%9=^|Q8*bJG&P RAs:yxQXZ1^p_鍸6Mks=iHm$VPi_jBFb_ĈU'|]3&+PWkrP&D-n`?)SS,wשׂLޟ:S?Y\١H-ob&w=O9 C)?$LJ0i@Jw)ϹTABoa;075\<>t<叿MN7~]ik$}/W_#ķa{ ]_)uzHạumFd HWBqݾ} 1-O{zy JN:zXTڼ "SBE07BZ 3 #U;N?j^<򌏛Ld+I-FqUGLNfj d4H:̯gٻg p@ZE٢"K"eر8IU_J*?R_J+I\cʉˑBKi6$H2f3=ӳΛ(4I =`f߻.{Yj9\/(Z*}w,k5عңz̈́`9VN~46L".Xȱ⌸1&O2.+R}tƠ5&OMm} 0RhSH%ٚqt篎8iO~-.$mGXUg,JD\t% +(192 e^@OzV\lj0j/}g^Mjkj-.Ϸ-UYC Ikq/e tCviUV i:Lx@ QdM]oQ AE잧ObfI} Iall0 GHO^WU3ɾZ,/ ib?Շ(AN\>)I#aRF9E!6s#ԓ 5^4@ !:Yd^X7nB,QVFyM&1x *[mss*{/jHB'>':h -*ub4p(eJ$sCj" d'bpx?& ڦf`UZ~!isߎ Z>: c>6s=m[/c ܓ`('fzn|gPzW7/П8^(ڮ&+rU./ǦpL잁YM hrӽhk ݑ}G<\xׯ]U<83,=[MEPO9HoGS/mqa .]:dd@=饗#(J⤾p$l:=LY]/'/rrJFCܥP1,80#0Ū0_&(h6U9CdXfeU+#PJuu nl;gm !>qw//͹Vq@DTG'iO*XROj'0} o {U:+tnŞADg#rɶI"eY{np9sm@6n9fM"1u,}<'Pb80l ܞELxZ `re1&K&ě K&$rPƣcLwHtڐ,%DꞺcGea"rK#=LPo*7d~pןȪk)f`,v+$#e (%AƼQKjX3zݠj.?(&Rnmt=.6T3BTCy2^f8ӺqyQy W^%ғI@z,Mz~~.id;rr7R ob"K2qiATmkǴ^g&dJ = h lm6ʏs}~xkS_JLb@s_7>߮,C'TFS.7Btiisj^ g9bMٝ67@85LX"u '.$VrYoJb,XG4SC Sy`i oYaӨPZ8{ԡS3B 9~^>ij[=eZM9CF]'c g}UWT]%.# |6|_1bЕuL0TW'NִRPX^CAq1e4-$DHYPmATb*WG7JD‹ n+ɱKA8rN.)R(e."C0dp$WjB,?b2Vk<s"qmg~nKd)8k#yMy_g2\Oݟy%6ȉܗԶt'%5©Mru=治hF> -jzRVh $]*i0 =}@.9~WAӇA%TԆ -L#uFR(4p HG!&SoK|IoQ-b{*ec7i^ Rn1J? WIb}u &(_*m]O9jw)Z69OG 6~_J=LBAs$TiX /ڀK`}1Z"!Tw0](Pc% g(:Tyڗ?4jďߝ K w8qs-'?0 OڇOmVePi\=\5y_LK0\wx8__}N7I>$`L]}͌He&URKB+H)g64U#ѿ};VU*vFVKtZRXhH[g?Kf}^5<ȃyiĄqӈ:=ܗz%UP/ 貼C\J . -ll% *ASxHP2YX#K0P\FIJ ȀB6PjPd\>B_{C A2/eb$>mnGs_U[VC3DMOUEĐK0Qj&c4RQGd:N.\`~^%.d ^tu"<N?e.&dmG1:ji'fd*՝/u K:`Bv>e 2M$)Ȟ?`C,D ̺c 9{a  asq gy/{k_~yt&žo%rDoZuȑɁ{&WSWYN鄸 l5FGt'Q brc 3AI qL:pVv6'ijf,k+1 92Mr̓c呌tTSsh}/OLL}5"ĬAt|AB'TاG-)IQ 3][c0ER`O.j ( E/ÃBKD"L ]@ $5w$2UE]JcXJmZ{^.$惈g rz|f;x"Vr*ƮOؑptʲ f6 mqR+sm/ٞ~pܻ!N3&$|n}݀D UpCbMZ*)\NMrwV\1 MA3j<2h'P7ddj@kxul57*OFF%M|wP3'W'zi:z:'|")͈d9G?Zߜt9ӏnNNO5H.R`yW0L8bG~n$x&ZR -2E"'1#⸱Ξϑgmĺt]PO[6z,%;&Qaϱut(1r2т}3JG'(ҏ0,$a).Hy/Ciz\n9>%8CF`kA:<RFL"X`҄2e^dPE/g)>(+s-dFQQGW7)7\`8~YkT[vxlwLO@쇐x4Tw9P|z!j{?sA0T'kvQTHs$Ħ$ ݎ072/[ ɋ Kh[)38 *ršΚ]ЙiJ< qU)TQx[g6] ma^.IOw_&?}᏾T rwv$夗uw=0Bn.%#,Kׯ%/;Vp,е]$ȃݙ!`jskHH ,/mpc٧'Zq/++lag8uky-t$?zl~W:+ {l-#i|dΠ@&@hν H0 evh-תF#2j+rՖ 'xŠ7z%1'Ak M#hn(O βt F . !QD2 y#l-+'2aJ".cA}>fl`VnET%u_9}lØy(kx/J"05 xjVe h>3R֦$|R:B/ 372ZdQ/16p&/:"S 1|U:څK,'t+^S\-&xz2)TS]gz9_O'zve\Eۧ>+0W wqBO;C«.&<nܓEV^}}'LTc2(K˖ ^l.%X䴠ƕ. zxM>:zÜbg^D.0;-1*:+}WZ[x?$q *h2'ljG|KΛ\h92 O9>s(vAig^)A,(5RMUS" @i6%-= cd ?8 $\ g[ mUc $!v+&Q+ x(#_*w@2ԎWV2jT0x\ )mr)xJyYh"AXp"F _L}K%hy/h!}+mѶ[ ՋٶA\Jiě{A}*-n"k)/It.W>p\*Eo]K]o}{Y3${yJ-0ٗ$9D`h%׮^rk.06q3 ~D瀃B&TuُPk}pU]j1orc֞Z XBR'k)ؑwxE@?ة1S뗡hXqlgJ4` OhHC͍HO{!F_&`vy&wCͮӀ]sdDR65 0@'-`̄F0 Lvg@*|P?N L5Cv8Zߨ&|ǒ;FKHs8 Ѱ8ږ̒6L M4VV zz0* Y%3*Log 8z=w^! B0Жmkmlo=Q=ysl>{t.1k-3*Kǟh,T8=.I$!NG *; 8>C c f̼@4NvS-BDQ&u3K 3uk*TC9cR˓Vq^T#$h@(<8҆,#)Or"sD58lGG pV:Tr8jN º58: jT)u}@D@,"XhC| &_^cՅE.XĬ*rKx# L.l)*I5JWq~06&/+//*#)6҅s,=''#Ӥi?Xݖ .$&=!Y_W{S m[!їf xrN"ݗ]bT!Ͷ˥bɎή϶7\.|%ZH^e@4ũGnp"JSJ'CK^\`!2"KyF |Y \zr" %A >eU.2fg?C\ %E/)ED{yҤ@LW3^K2H dnRWQCmDxsgO&iQ FDbDd^=DžD ɴ`(\rqv _Ju|0҉' AzxL.^Z&kI#Q r |Ԉ=ac#}*;I1 5ǎB$ 1&2ݜ%֓80^yvt 1.{&B3!KÌ5&g˔MxF'ɼ=¼N=$N|1gu9{lXgWֱ|~crddd.j>eȗ"lRnnn` \VF7|D':D;,0`8Jb*,u.ԓKĤ* 3Gk)2$&I:kFĉlG:,P Swځw1Ao7`6=B h951LrUDG<Ąse9@̒DGGUi "g2`҉dDat~YWnK~u׮:! Xpp?x Ƹe8Q7RIl3p:"0T+ma1 @ 꺿k9}IUx[Sjnh$ؾ0Le;VβRP0l`"aپd TjAws 02`e.-mSg)$Q mM}OC}Ip g` mneoJGϥ38Nwoh\SMC,5z\<#cJSSv!/jXOνddy"g(#& N&2D9dLD%a`@FvAAF{C ;Jjî)Ip/%K5h 3AQqkpݰANcFrZ(]Tq#"{Vsu1I[|5fR׎3aPѩI=3^ 8.r ?я~$y[HXC}ñ9.U#24VҏX3eBl-<&V+%S#ҭ;)t%'sinvaeڝ +jθ*TEr2 R^[`7=ݩ٫1q#:'+LwWX TB`9+M8A0y۟5n Cpi"&tXL7@8q1ƚurgS2B~A7ĸ<%naLש(4Yӭ)1P} 'LC%*s\j|߈ ki_R dv UVBҙҜf6i{@~t4V@T+P۩v>x%}!O}*޳'}mT'ًaVEƛra{pF4b$gK&gn H@LB'lSq:}/%8Zqdm\Uen^tnW>f߽VIk2&hU@Eط\WFM\lDL6rCԇO?Go:o-- `_lW|#4,69I,*JHul`Yp4 uoAna\J+Lu1-b%$ܱ]0Zy.F=bYBs!b t} A֣K&I, RI^N6X߶rQ] 3^ EF(p0{flziOEwL(L,[Ķ8}XrZX5_߀dq€`j[BoÓL↥:܁` =|ݳ(DgҠ 4Mð~-F0r`A ;ܓY/WkgC KMGD/jN\ (] 鮔]US t#ikvn#" 6a"H>p.ts+c9}B[nϑ#7;đNmS::j0tz=a@niܐj Cd[ab&](U U13 I  S:j&PD2B $qc T:TGi"R(& O'61a* 'FƄ)4;1IG<1d-O@&yivE_:byB3R('^!7B!``WgY[8O%fS? 64;QE+Kb}eNN^u\()6rhS-¤,@gq8cab2)BP5= ӎ9 Ν3F@}Hpf&.v|?wu*"lmk!}DHCG-H.XPvCj9Hg6.&-R@L"HWyGbi,0wޘx_ZfncaxTHʭª-ؠo SU%55W,*",AN!<&HSxNWN%LrI8ăBŗ GPze:E0]`$Q gOEvE86ӫ0i\N =cSE_kR$ %v?K2F$SZ!cLBǏʼ0s) mJxB\CBo0ؔ-"0uĺ*B8ߘVziNi{0î8ğ#S[# wn0W@,uRS&@,zM:ZYǁ-hi2N-o=f,mL'D-/sfd293-',{_:Ry4Jv⣏>CuVϣ~Rܾ\S"Ln"PtMc3"0Lwexuy^AJR{PIR.0,.,6HTbJ}uk8EXi,Kq!M`'@-EBj7\T]7;sU b[(ÇMHX(]qC;Z"n(K縩\ $MoBaC@RiDfǀYSWj{u|;XN ^Q͉9x5ODAGBs~A96o%BzW FC_)i}CpJ; +59;ɼ7B$#gOWI?4Rwװyݕz~Taz-;Ի dd?Xq|`zsfvf֤Wkl0HLXD,(̂%Jy+9kL(buHG}["őgب0*hʥmza#F8UKΞ+WRKu7!Önk; 0HM"PW'hWz#rP䯤2^U3v _X;A2X 6&b>`ۼ#m;*#>oSzL\e /}C4<{-fmhOC]=ɮ{rOil8xbmwzCCss߸6vÛmhVGRzZ)A t2.˹I3PmDrVL9}ll GEAرO: zL~gTX\|Dt8 l lJ #`DҘ6"0i/x M\-t{9*:qH\w^UUmQJM壒jGHIa BL Vnț#j-vm26ԲU6]\>r4MGv H3?UKƒM2֭}7p E7ѡkDLY$Izጮq]Z5j] ~.ߪ$R~xqG]# vGyif"yRn>PE` J:q:4Oj,05v(=;2a֣2 8\~9٫$~;IDAT! Ժ .D CXTobKS,ϪL3G_O[7>Hj+0u KjK7в asõ~%}sc.;ǒ(?ִlg/@r ˍ N 7 XL7Ԭ}ߤX;|7t<8|<#E*Z.aCIޗXv3Dy]=bVdW"1 ˥w5O\]#v* lkw&m鸒e6:"1j [ Am0 c8ӡoNQ8_j ^[dy FF繱&6`i sh,6SrL!"/Z_ݪF2Ft;NIὝr:hMHv!|&hi" ƧF#}aivB2Mn;?+`l݅lyhDosήK}Px^{I~GP¿v~G>EF_^^ZnjkXgqiE+V2G[\#)"9qUF۾5٘ e%C;6WvÈYZمLljp2P"SrI Yf\\c} @fvK.㞴]+ I@0^# HW_3Af`ʺ5%!#8@ߗL\FN7PKj99_A@ߪ$?"雽5~n1*5-B!g-:8:9v@^fss1CenVzFʸKMAju2R Ķ/,聯p4<3A?!w`žSql=3\>>rLJBБtw_x{~1оYx#mB_O_bѕ(\9:6z@J-cR&Ae)Ukı h'k 6o=㯅H C%PbtLcW[eC#^;{\*7a VKR[&e/BT͵+G9)%+dj40s`]xTv6TfHHJEQu6J=йR_kN'\~{|Z%ޝ]k+}u٩GBoE#H5AH?!uXw*oEKS?ʎ2*BoAP[Fs:ˢ0FV"15",5yr]ب[v@Ld(\VT^k0)+6| pN4. JX.&U,# 6f7 7O G cO(V1pxK">R޽CCBPjB<~;Nz/O`r& 該jQq@p tdG1sbA{kC<548t\9FTG--]SKoBw?-ƿXonh Qi,=d5%.~!i7$vubpI>4]QOṚ܉Ǵ]myjk2%9wЁˁ!.`% bBmSP6T%fmXā<<ݵsFDY^\ΌԊda9VeT,/aS/Q#㠛t.S>#-KWE{`dtN$rS~+0dla^mTV,_moSiV6wO_ijC{NPKgzz.;]n'3ݼ-UQ <7??7I[\`Ӊ'WE Ԕ`<8MUuUk7F ؍ƒ`Ʊ&܌r4rcZ(B!F:k,.׹4Iú"z0:c%uqvSSAנf {]M TօdIiaM* 9'јޕ5lc q:#fy +x#^]EL/aH_]b nWWE sOǑu7@2HNkj"_̒]<jRF|H PTU7>fVuź5ZDTSS"蒡l0XW tWaECQJÐ$xw .:UAFϏ)h[Wت]L]D+酗Ө:4ʬ!(@bOeO!Qpq~\37S'.Αb9%Kq߃I +xo}9g~7ݻ ~؍R__2>ETk ҩfԛ bK,]EE׫,0Y-aFHp]wSHkJkqm@,egF!oG5ȻH+P㈰d&ۦk_NBk5I6#K%,ѢB΃V ,+2֣u&em<4$64?$;|A:qte0E ,)ƹYN"Ry:70|f``_,\F:{ZqRzqDOOn\TJN8jr=*f[mW^?(`%6Y%t#(QE0 'a`ܱM:}Qr:PUC0#]H}T`K T^Hxy}j{lE*s|vX)WF5*EACY%}P~DI[3dr^͎GޅSs' cM0bp5^;y`yTo+tؿNz+#]x3%VVW#A >O!xk8L{TAuH\P8"}R/KO>P+\cmưsEn~h PU0h :V)aH <~h,P6|$]R޲91ʔ<J4썶A/H77kY8lv 'k0ԛ*{/*,I2Kl~7}05Be/O ={pً|'VZKOs |ĉ:r}Olk߉Ad:# Q>2QGFFUn?0͚Kz镪;TUEy{ڣGן@yK!b sO\_#h?ͯzOשfkޯy?FTBt,zQR۾.'t\!Py3/K&.;+:\hNMA5QKErk_=aڂ#+ -_@?3_>UHd֮OdC w .^A@,kW.ϖӛd :f>J}sBP)C%4nWĤNYKGb91BF;QI!*^p5Ed_+e>,\q ZEkhBbױh@~KK( ؔsYjOq̓%շ'L{ݷ[{f{ߊwwK 0~/_:D >jYVb0$~dev$Dמ6kdnlTbCq ,ԉh֙PhQٮҡJ/wn{"0Oփ:y9B!Y;@}0:{w fӭ 5\t#$\e'<[<<r8FUv٦tjX41 kOmEe"[X,l=pBidoOή}'}KO>y\O=݉\D;O?xGZ/cRgh0yKsj,j"f\$@5$mm F2j5%;<]OmHoE5G߼uzhs2ssY0#uNh݈nB7D=q(k22Kk[CrDϱɉ,ދ}%t=W8>!+^$9W(a.$ooNYG3g/=ffɩ6&zp~qv~* NxoF!pM %4.j H/m]KWБ.S@"EU=f^?{<Uz&ξ K< !v,q7 !W"kټsݽOO+#]Hd.Q ΁XO/^~L4߮T8N Ug>$]aãb BFBH4i$3R-"6X?ζDT=P^7Q/ul8uՐ_n̻)7"7a;ZXDU]v9+~IO<ELޙtw8p޾>iN<}fU}%[÷m^m08BȞW^9qpiI߱~]ڡrOrPuĨxsJqHtkW~{N{+}}g!6 ooc/| /?0z+H14pz͑V8xqW-r~\\(|䯃4e@*?MϾ-f"2Yij67G0 Ml*i%A{ZW_ЩbXpszkOxy, ƌ\OOON~~t-}}AW"? 2zƵWWrmk KJc7츤u@\Q,5Y`t]|u9cZk{ܷWO eah߷=~<&t|255A0T4trbG;=22w}-Mkky|[ PlSb0*.U+\ ƎjB|kAT p+&ONN>61q]&ON8~t;o'K^I 6; sHtq455L"0{wGx'F޻SC:{>[k?sB I [kO>Rś\C^L Mgm3G!tcׯ=6vS3#+kCݝ]?Wn5F}jb<ѳ@aOoiww|tx}c`ppY_=t"h&> 믲 !)[=g72|+n!kiqѾ?o^8yO8uhݵRcͤcT12J =WŞk{ 柫tR]ccc%j~-%vow w4:~-i<>ONΔ9tcnV3- 7vww:n7o as\ MjM|Ҟ=O\]m?Aw+qVcniib9{枖i+unV6+ەb 9t#k\[Z^=|WLh6 ow%xvեՍJF{g0̓ Έ}l ?Zp06v[hް;6= A vvH{E5+A -B@fR^ A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d!ʵvIENDB`OSCAR-code-v1.3.1/oscar/help/images/cms50f.png000066400000000000000000002132011417327530600205770ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGD pHYs  IDATxy]9[U]>Hmdc fG–\Bn.Yr$L,HB0 `0rulK֌fFվ9VόMlm}zO}o?Wĉ'y8v=ZٱٙT9z3~X_E:z׽_SjgnN5??oo.ĭ ;N"Ϝ9qM 6Ʈ)<X*Z&iaF 9#*Zc1IRA\O| gbbž:u 9%x'ӧOkR]Ex>)Dy%Ibt`E$8"c) 挱&4V_8'OMtę3g43ȓ'OSNiGpP?BPJ) TRJ8FZ# ĉF)D'VX$:Qh)RJ uIZ0q?$޵ V ɓj{N˕$U;J:߃q"Jb%r)$$!F#@ ױ#!mk"!I<8 {-6~s" ʸ﷞9sJk$zvr$ _u-nGeP.~PЎKc@(\E"v1(ЉJ !\'@)(Ra@H(5V`0Za0qF,qmӃn.=uyWNx(OOZ[cE rTRƂ뺸kS6$:CGhcMb4QhcMHb(!pURJ$czVlq$qc[#"h:!cր$:f1ZcLB$#'Ed #Qթ C8BH1#olk֨c;= ,HxLaa; B( b0Z'=!0dR$a"_"ZFX"51.Ղ^ŰՀX# 1&Zž>u]Fv;s |ñPo5 b\'SI@9#>LWIE \01DB)ZDỊqB9+PkwjR15l@0Uh()p$a mc\xh]CFX2Μ]wqK'NI>vJg";1 f'bB[Eb$:!cC#rC$yc04yp$AQ Ntp@irRy|&6՗,VB 6N5?'v5]ة@r~~^{_W9`0CdZxJ2>QEH 0VYGk+`4n5|i6:\[ޤ;4Mzl6Hޤ;$+4VU'0qB't6Һ(B E6A*O8Jȩbu;DstMvxI3O'O ҝv_96.1 9Ҏ;Rf?w=#9q%˷+&b  X[\ &t{]26d:EQLOWiu;J9+A </!<1(SuƄ!"m:ctbI *H-D)*&¿;n dZӨEʕ* ʹ.32RQTZ SSLLO#ʥ2qCߣ5{ ialp]?Ch J(AڍX8X`OJ@ 0h_{y[;pdLCL.@ A{0$_qX"I4ZmccU>4 Y6kěm~aDKIӋ"*<# rqiorz6=ONAG)խvŘ:ZF$iaqCU.B:XH~1wd. x o75Pb8!YYBH[$qy9u٩ ,ycaȕk U%9zx?Nd#wå =JsxU=!aD-;I@"#DPTpKɶpTƎ$4p0`oX.OҪmʵzl6 JD9L4/sCkWI} })aU3&~@;daNXgXmrYtItBp\vwuZ &RD BH!$cإ[ڄUaЮN ȿ-ߞCRNcϠ 3VbT=9|v&81'n@gqq~ -k[\^'?iak}> y/]Ё#4V-s]t֯ሄ\`{рJɱIڭ&&Z2a}d1! N*@8> BaaRk L0qn.^H@ |u2vv@5NKLVMnvXVŧ>1:f8`ceɉ"y%)d VWـ^K{kp8,ZǸKݢy8V9 p2*;13BGR$?V?ɓjPM =v! 8DQhzR.`bf4wL`}c^iiK/lDpui D,/>Uʁޱ2Qd%ˡ10Z1=Q"WqGNU *Ě~cY2K.,QZgjfP0">QO iAZp|p\M-*?'e4W"Ի.3v ![9X3tX_5&i^!ټzНõK7V0Vt,ѰCksZc 6">F84[!MHr0"š b!,{GVu9ʿ;N1* @onLL k+JcLC:N&W`i*{n;BEc*CQ[s}:acvOhfv˛p"å'ɻ\=wѫL"!KU.4r{(ʌEB~c꾕q]7MŶ!v/vH0̗ΔSm11V'Q~YAxY\q$alIi$q `#ٹ]iLecmbHj348"2솬.#\S$:W 91:.ű :!Mi,G8L.2j CJ"+ x P%D9#+ Rr `-б6":o3]i͵z8'1c\6 .IlX\WRgJTPT*\t6KlFFibͥ5).-ڳ3 zZ&c N7PO#6vi  X)B$'DB[ Ha@yhxiI xecu$]/ZrVIӢ/Q)Q&bCYʰYgxF^`Cחjl ;ZM ݭsXx6|Ƃ(֯-D"?yW2I9el+OP>t͵5<ܧ?KD$B=}$BAfb6{כ$9|hc6aCٸvLDR^.P̕Yitr$8ys8T>}P H!h:x"/v8qB>} PL._Yh8J%pȹ)Y8?$b):6A6<=E82g$t=KETe:us; n0J*:VP `\^n+'IL.wq,%+bGhqb1Fc Da˘P.^(t 8M|':ql>A.36NƔ%lZŞ}+n&_@v_<=I2Zcc1C٢571<Ilnw;=F~T^Gc"U DC-$q5C.1lc(@($ q<Ӊў e»xI Y} TfzkqC3MY#X 9ǥPPfy {fH]>O41F6.Kc/sI'Xkq*Aj4 >l.(zwAHV?C3$&]C2kp`4)1^29\/\WbEL"~ށ/ .^($`nnnXil,2^ڏ t4 tM\?<>IQ'joLvan IN*:|:"N(WqO6 !rAd79et;t{fum4baPbOᢃϢ\c@#H"acq>ųw;itJ{s EHX[abIXE^Ǡ`s&60֒+0R`lBVIL̎!A% ;,d2'q%ՙ)Jc{(OurXNIb *:F\ 6E]p#U] b\sk8a8}zvbgܜdn$aC2h zZl%[(R>f=\aBuvY( d=VT7X"[r j-|OriuڄaLQNk u4Vvݐ+ilh4khnqاiu +!qњ$zpsnl $!I8"GFsa Dii0 codWԶ\)  -]#I"-R1>5 S3Y\EdL63>EkB4ŧd謮dHP*ؼFաPS[`0ZY{}_ǰzq$(/5L2-D@HI %N"⻞"8hʓԶJP,߳A?ѬӪ NA#2Lɀ^m@m<*֮-1;> A8B5=rV hnowQQfjV3wEE$q |֐#k5t@1J: svW]Qpz. 26õ֥h[ 5 UE\7`ue 2OէI&_hK3 ?>E2(\baX>G\Ugm*VW,(V ,_[ (1[+ٳͥE(fm(N  ERL8y|?K주&֚A!I4AHX(GZTŋC'O*N=0c~W,vD¥K`5ak崅W/b<5E7`lJiҵUTJl]/P'G*8r`SNpI]"҆djwD1BMڍV+΀~FX$4kAkIP@9> ,\Ry)#q`g;Nl9kӷݻs6(dP^g r ) Kmu+us,N|HʰgEՌyl/S-z:`HAX -XKD6XRK\ff}eˣ_IE_ Eu-i9+%Wv lCG"N"^6ֶDcc;':9|o !~I/AL<T+ %W*RH2qKe50$ϰqi7LN1s[UtLGac PʳtIG $6ha}QGy?5 4BXZ>:21Yeޢh0>8Nǖ:aA:IGf(Mtv|RC~ X)h9H ! q3jHtD}l<•_•&1'7M_GŸ1{~a \V]Wc9 [gJqѩ7QKEն(qFKC$LbgYZY 3Ѹp"vvUˈ& {mZ4kaC~ˠEbZfnpҫoҭ8PJژvLq"[)%@P@JrGYb$c!,Z+4{/,75F80{g~ V*i(Io:vgp}Zf[kŻ.gnnN܄9 N1pz^h$*|VƧDhc(V+Ci2qӏQ8 zV0Bۦ׏mL?.Wb% >"lL:SbcerJ/qs=r~ O~kAZ<zk1q~=D,}G߁r8vxl\HBkϜ75-"cin-쿍:~ B]_%IBz:+p\-j5 I:kH#[}M^$|QܜT_W^] ~Rk6f&6k5Q9^7m:6:N6(A& 9/dq] l6# ٬tgJ7u׿>77W݋/6#ʕt5o'3q0Fgй)/O1a%zD0LZKpO3剏~b=\9ӓS,]]$ am~BA./\!Hff}mDW3%._|-_A'1$NTd#Mr<$Zhk )`xw}{q\ |߯>ozӛ$VfejS7ڪZ][זiMַ?/J׼5cS2A{3̫JtTr~Y 2&͙ٽO5bjv=/A+?{n=n X48i|?=|/C?IBzB(E|)B Q`5h85})9__{˛H'QrZmgqm=^[Zu\fak[ӧ_+'F_~OR#R-jE8Hubr}N}?W{$x v I7bLז5;>F9# "N /[XMXd"_<8|fE'pk͎d5 #qe SӸ'uxr #Bu2/'Y7G{hkQ_"sEp\@Ǒ,Iu(j ^7ܜ/[ZGNk7;,&Kϟ[R_Ϟ+sss:| 8}ɓR"{L}u}2sZfgB$^h~|f?ww$b# ԭh+ 庌MNruZfYF (fkiNK%hlԉ:|a}eNmJ2[+Q)kLwC63q/dj{QϠX FE!?K(7 buƌbך`HR#i˧8!]Ї>:tH|}۾'gfC:Xڊ/]]p?_9nl0>Yt?|s%VG*7u}ӏ̙3GqϜ9\#v˅vToSg;ƦxMfQck" tm<7G=SoQ(7d< _(? \1ϰ?$P?fٷCyzaDuY?a,a)=(2{`/;H\W\\AHQA& x>`vdXhב G`nnN74 ?{~F7Ғ|v? hrLy/al˷ ^k$Y ؘ2A1#NLP3S n|e\HTL\cT+lD._@ |<@#!(&ƈGan†z{*h#A9` )JbEJnb 0F)ltR—w#߈O>y;_k; Mv.<} ./%ym+Fj™3g?gÿ G)[p ^67;G~{{裏}s#4ՕO[. q YpkWp]vA/\b8)ㇰa?qh6>q!ѠO4}Ack2|{}8W{ӧ}yoXk6?$I7Pf,^[9Կx~w/Z(n[7_ñ(NޚdrQF)%(qt}y=Y^;8]^?. 3vzd3P~ C8~I}s 66.-2lo^5C(bki FRDEH)X_XƱ GtR zw\k>Bkڵ&6 V! "~!QqNL! R*rR:A;kJ=j.B8Y8 Ĝsiw׾] HvC>'~yQ>=/Ɵ'S\ &Zj"$% 71B0v3GK\p_:jK BD146L훥MggNTZZ4>FaLcilnR`l.\49áRRV{Q-z>N^C)q)=$!@PD8X'q\`8D MoqǏJQ0R q؝G=<ÿ9x.c ~[+Vp;3SgC2"Qٻ$I&cS77<$Ur`aJ %2P 4CJ{ɖK"~HubBXX94 1,X&ũg ;$ k:+$t&!$CX+Hb5k&aA9䨉;^* XDq`з+[\^Xz/9yA1䷶C}{Rgt [~4mޗ@-x6[SN=; 􉺫?v J3K ;Cz0?EhXĊ)".b>(k8X]"::EDe8hҭ ߩؐpN-[MLHK5=iw) ~ Ba D@II"MsIp9es3yx$̙tOylNm FhcڽsڼG?8ӧ%('NpN8aw~7䧭D Gɑc9>}4`܎NlN (z86lf1,YW?od  ?NOcv~z. #Ͱs 2/]T,gf%|G1Yl6Kcc//h5tZ-[˫xL \tY{** {+P-*d=c ֒@I 8š8a/+ Rc:>&$ vw(ֶ>iӧE(;ug#,)#r\ωko}OD;_}C·>4'ɓ+Ǎ ?}Fu&zyI.#_.ħ_66G1k .՛ikT=6ug9*sױ<ַ!r牷 .77~?0˫N|k uC~ϫ=[a0H28\ BX)1RbHu~+$Zs[l:-'_k+'Nhd<  ַQy{ONZ?7?'֖Wh;ahX֕w˾|_ ~GGy}gih]- GtnMQQuDž@ %UkRC1ֲAh@ |ۏcU8u\[e|vHЮ9vie>JUgm}=mmls#A=#fQN,[|H$:& ;8V[.a]ZN&BQUJFw}llĿ[c=r$|À_PLq7nU+kcP.\΃ KS aaF҉c:6*S&aLdi.-R tVfY8Bdg޷3 Kϐq0aEƚ pH~*nh^[b}IקU_a7$w46B[g,* 3('/"sL>VHa?m^y(NwoĸgE'm4[szc? կ_iӧOsΩsٳO=TuJH{8qG9*V+#գ?gΜ nMۻ>׽ I+F^0 6zvLU!DBM5۬m48;sA5}Xk~}(XO0iލ[pz3oc|'~\fn}_Kqb?Ǩk_o7}6!gFj PXLڭX:Z$"$> $DXIiHhsV/&&Ҏw>ՇʕB.N0 iv;OpkЎS^>V xyyRydޟ85!^Hs‹F7 ?=~P>`=}2@$FDZՑF[6$IBEDQ0 cu; Nz03ʱ!́Cw8@~v0ƕh'ai,apuKOHEl^b_PWao| fðFM%"B~8g> 8.JS, q.F{rE"+X&Qx!qb3:Q=+H$2aP ?j齣}l8"A#?&H*幎dYнw?~/]wrph q#/77؃,~iրi>$ >GD}3\ԇ1 K4>KC[Hz-)QJT5a4$Aw#r>$ڢjp''&I ea1Z0֢l4|},& /#yb'\G)Q+s;W_ѽ(pI)G^ڻǾu`<%|Ճ>ik # xI E&0qȗ;bNJ{߶>wɓԩS;wξ"%Zn]0gWE kڈFi?s/^GS/`bl\+8w:{_{Nn4hr{f&R2A&O`ݺf{]rBkVޜ!d G.?7 @(H9zazlЍ$ VkGq2 !~o@ףti4;]z!Z !1CE@~6~V9t 8}Zpb ]g 8I0Ha%bW&F{Ctܤ |L \_9g]y?/?~ܞ:ujG^Ho7rYg26N.f[=T8 F#Ķx}o VږTSY7HX~i 7}ܬa$GW^R-ڋ%Jƀ8!I$,(" C!ސ~._io۽~guek5_zY7V9+ŜQsǏ N7aFN"c֚8o1jyԩ' _ =ض?ҝb=?qQBq=?><77;Buq{7['fEu|pl>W秂oR.nP.&1ԩ[@/D; Ccf`HXo H[gi?;24۟s 3" AY#B|~W=:}C7lb8lphFgN )LNwtzc5:AGfض_6سgJ_'J%@q>s={=n5_sfnnN$|A̙3_7{;xqxhk1ۚT,$H{dnBE om@g 0LOjoM\ɄQ}!dմ7sl;H#?ʩ)sBkbH0kl~dP[߼zvӉZk]7C8?d| '23WVdElm^ G+DJ9ZS28q;(AJ&}0ZmEe7#uy[)`ףaFE*=*BDc-jѠ4!I I}k FD!L(0&HavGʦ 9sfGUz٠,\;vE!N:u׎"Nq[\\zޠ;C}6DyA`{'ɬT VDBj@H.n(%nIZ]G+iEK~Y>+n m0 >cSmB8vFA>h1 $#HD2=="L0CsssG*@Z9t0V`D^4DΝ{`w _O6Hb?;8R?]&SㅸAv s<׎Y뎄^=;/J8ȑ^Huk%yceOOwQ0@sݔ v<~ۚPM>\rZtR-atD32$=hkFIcmJ F۔ia$$Ɖ"15=yMow@4 s\,b"pHٸ#|!pɓypmv( \!ǡw~w~'#<#4O'zwRV wJX@)igɓMi!SaƠ޴܀&AZ$fٿssN޻inR}Ѿ7&GňFϯnuGVr&A%Em-5֠)))1b~5VXcJoxwJ#Bߑ#֊h0rgp]/}O:[k{߯\ˍ@mc=sCCeyѣ BN(TZs}K#h|4Oش1&U óm뫻HvS{vjJnF5#<7ڿx&E9ThoRo!$B)(墤Z#1|hcfD#46}#У^V81.B)$y} 1jF~&??|ft(Z'̑#={`n$/FS/AeQA7U9V.yLe17K 2Z$?m[٦Dhhu6iNQLGI8=m $cL :oməw8йhG0Ii}ۦf%PJ8 GQT+J $& 4X,[Kl4jd`bbVjۍ@-Z#1#)і(L,`'lh_RӧO'?~R-_bi~q?nk } in?pɓy45yx n $M|dҶW*+BiL:k!1X#B C?6"M71AHקNZCۣ#zaD?D 8&ZM3:&1vN&H˰<'`~q#0K〉 PR5cm`VWHӄo9kera-bon %d[?{:ǡNS=o/IN7=o 8;Ɂ>C? ?.nop uیh#0BDTsl>U$Ip#]yy`uLxL&O0=R_CQB!z͏4F6| =R)<3LLQgS{`rXe|@.%xJÞvz Xq4|nX6?1I90T˰#U+Ywi77•Jɨ$m4:6_RskN8ssrrb\J%ERf_\Viy;Pa?muT! Bk"w g&g02 AįƯ?M3>>uF@.[Ƒ )AnDGiHhJe8Ih6lđmG˧$ HBNi2k [[_Y3s39G;{2gSW%rA65;DtO 7E M4&ud3rxMU|SVk;s|_Q{7#ؑ#454\D17wZ*?~6/)ĉ'q@X8sɫtiz %T 3>Vxlnn. L{p̙`k-N?!<ϚBQYRKp6cӛ[z$ώkq\EsEVVq]LA$I>PBcbLOa8,zhcl6%gll~@I2F6OQA61Lngccfzhcb$)p`j4" O?$}>v/ww嶃3xRDߙΝ&I4NR23rj$5n6/7=|ŋnqnB+Al{F+ ssW۾7:읚~?@ x̹ZO3Q05Y|RaQCYͶo!(岹Gct= UQPR*VqLC];As<6L0 #Kg&Ja C>pkwp;LNLcHãZ'IL$0 @'c,#ϥ/MO!p|l)Ib_&A %A'!$ Z7xnZk+MV1cccs%Jq 1p,lrure4|##Aa"$S3<}iS?o?.-9q=:%MKT*G oMMy 1T"nVm0 el23կ/ `; ےPŧR, GZAtc\m/RiŊQw +QʣpN3۳KFj0!\pK?Kkr*nn5Z zFZ#x"l@mk4ձIRϵt00qCqN011Mצ79v02| $|<'њ0QS7| !`1Π<7u)\+!-nPr*&ӌ#gb_{ayu%N@;k&c69@P~{a̾ٲVɻ ϕ -RJ(BjQqi:j [gR8#vTF !ؔnbNMu|lReRe\q%acbͭU$YYD"tڬ\lljMmkVFPDIŠs]OD:I8!ƪS,AFce보v 0^GO45|V`Z4*>aZI$Ji !|;W ,!R"y]_,:uʌ3lnn6Nu$XqD{銃 X }X!֤efȍ!je.SLM(4R',Rž\Ͱg| ^QxiJE(b(%X!pkH!} 밲z 6J:h,&~A'qZXY4 6IQO~vTu6omv5^o8 bE{*{zp}!JB+M{+'6{+ß5oz+j򮢱N"[kKCJ+`aa z65 *I|c|lje uI.񠋒. ǧ$$p0[1G1Kt-:&W/]VĠYXkȑIĶg_'H; "Ff8J~ayqk7~}|V^+-?yuUo=L0(D!QlT$KKNqĹQ|Љsv|cߐeɲUI*;(D#vyޫ?Z}D$̜9׳+;#?n?W~37OͿŏm+k0}Mto=Mn}υ'Qxv,Zn`ƶ>Ь=s9@YFGŠ,IhF[VG("|P`,`ֱ PJy"[ !uЅB&Gۋڕw.L /lW~7 @ +XevBy0[ n@ gOvF x/ٷgx\k!֑{7k?+4}r'~_z24MkY=|j4dG#uV{#@5ƽ!ozsݯ8s^W'v~__C\䉍]-`>;Q2yN|/mp:"hP˗yݏ({nOWP{1tS=^{wyui>h̵|U CgA!0!E0 Z\ FvzqqdwsX2W~[R?[fy+̻>v^Wx㛾w{/spm@R0ўkqyc }ЗF\CFƓE~WʁgUhw7vϯAWww=7eVP&(TUICqHpҹBd+vۋq?u@IMQ4*@/MDsp~'3VI 2\Us s8~xryA7_Ih5v<:BQ;2 g;a3C%/ !6#g[e2( &{9DQ0| _~ ?GoK#quca~>x%;6 yՕ=|sYv" >y=j8h8ڃ[X#s-?xnd2/½udD7Փ}%ϴ}!ңo ^(j|pw8qGn rrkwRt-0Cɻ,/5(gu!ԃHk{T#jDQo'1a8B@ADu"ϭ xʪb;.ɈTܪ%sC[Yo^e;a _31EͥxMCgkK< LpDʬP n/6m _8}YB)}#={B,!$[\^fRL)t>pB 曗{"Q"גk`RȈZsdZ@`T\3ix=D/GHZ\m&LdwwF] @_C;b:ZQC}^=z;1[s7?ǁ.m~;aWkf͹O1 㾟DSFA5m&@*qw }|__+ ;)/.-IUG>5OH! )M`{ᑚfA+o8psg?wS `Yٓ^nA;RL.TAR8ưB)l[stM`bLGSo~RPHY92GzWĞ$c4Iy!EF`fcU({Cd~\("Jhvv׆>%3C >PY~8h@o;3Ç޻ǂ雿t#MYBi@ezdcA5FI""Sz2 qeu l@pS0,[CEmz%ǡ@M. {ψc^emTMje#-kUrY bR(PC_@KT1L9u2y4k]@/BM|`2v9k^v}{.ڴVhvhvitCQ5Q@dq&EM\)LILŅ,"m?y*~O꺘Ť7m]E 3QI^RD4??)fC* EHOe9xbW^g`ؿ' ]^ׇ1^' ̋fOR7VPj{ tb Q#`[(syʱ2)o:5!?!8bL MNH3VYƕ3ApW7v OG`¾S{df 0"@jV?#Ĉ 4uFI卵52k$R0r{L8Ʌ2H!Kăa]-@I)Ι[ZXy~7v륵 /D&"\x%hgif?lj;BD3۸dGb-pi jܔ%ZH .OW94oJ18ocѐޤd~7s: ָ|"_y!._8ʸobI_okU>Y}:ɓH-*eB}b UAH%M! tއx'p=uH\+;! #`!!RS$B*voo|tuSgWs P8t &'J.4l/z. Zt-W;ț<ʢ=9r!~8H=~o(K㧈8a|0 s&N'ؾ|y贻4: ̷8z ofaasrOo_G!ԍx˸OV4] זޥ=# AgVJ ?a̍ z((ˆԺ4QZ5`.]s ?x^3^7{=6Ȧl0Q"TZ^\s?|ر0v~qn_o_JE]*@KaE3/g|()JȂ,z{kC(6.t,y9ѸnD|IqKw 69{,-_/-w-VVUi..w`+|]åsiKـQLxm@ ן5J([ٴDw?1"bG`8 H/pX x7t JC]B6WlّBZ)wB z/J<L08,z/| Tւ(21`SE!5GTAϤ^] %cm72hu83;7}ƛh H{nØ*q7%hD5P叞|~Gowq\^[—gw ,CXP`V'fLLYǎ)70\zΟ/pZNH`V/z !qcn}_k>m} W@GlL.Y^ Գ{O{yP\U\Gw8t?ԧDвScŌKORuVxWRM( (GZ|$dГwxaeoP-R 4Z0pgˊ6wvi-zb2u{rp{dw3DCpe"}6}FEXrGdd >q3cʐ%uTp%|8n,+) !m9okբ$_HBx)=궛|nE2.+t@"@ؐxuH" eںsUU. 2SX Tj%^ [.kєS EKKx@犫O=C뎻կ3(&`J,6 T$imǴV,JE,O?"r)}s k%,0t=Z]ǿ*_أ<_M?[ 3YG3BZM'/qzQut0vB[P={뉀TZTY;+𯎽e$P& EW)1u#߂GV CS2ѱ (-^]87:w<IG 1G7Pz ?0,;xk?S>"ia ' _[pf^RzT_Գ>9xAV.0hҿ4b10 Jř\ÝW rn?A,IDATt#JiTnĀ&)5(!0K(, !6_!odRw Qք.BJ=00vi|k~C5L=Z)tDGD< K8NCcsJ秂BƈRW/<Ϳ>lqnfxTY5X0 sRC灙VjP>6z  \ƟMaaP?W8?ȁXZZ`anNNMӥYh{ؗ5QZh`T%v2flJ^̨ce}{ao nsgX|F7{PV5% jn5,!гp.dD_DduV80}ܽ="@%?QD\(!3M|P!&/`EFϩH3163)<uB Lc-Z6s)4ƕ װKMG"PFWt攔X%RpuP?J0JxS Ii*|Uǣn2:(f,//e#~}PZX]^a~as9:V<{ ZB#pA&&G%Y0MBӂ$*g/\TgA*2n<Dcݯ|қy쒄[^Y*z!B nco{5b]^L?Mo>ژt wjY> pK8VJTF !T$z$p'\|i0@PZ] SOg,R)Y;:Π]L^XZf7.R8Xm^ݷ/Seo&]f#9;K9) ?sKO_z}M{ia\wMFZg"dRrhdYY}2n;\^b:LoպV^|h1pESu%e0{>}]uZcʞۻs5FU3g{yԩSA x\~tkk?۳;% TѭFʄodՙ;Cj(JytEoB`_X%'(,䯛5&VW|Nvbn0Osτ,oϾUʪnYoQpAzIp"i&:)k&xcso*|rF+1(^HL]Lo{^{ċG f(+ZTx#b-2篎y;_˕[ucon=LJ9XH ` 3#5C2cZj(V7;lPӬi* c*˟ q;AcnGKAr6[[}沜~vƸ`b("(Gt7=K=5W=۞3~ S|1-.5A@D4!gARY9(LuDΕ`uRu3WԼ %z%վx &?zݞ奶`R1>pTd&n+ ^w.}Nzk*+>ںIy=G@'3L' BYnjxZ_DJy[^<=BQ7RJ$k@g|%%/~8" ߻H.5tZ}ޗv2*{.]ͳ9?ۏ[=< y2h bI'=HBSdk/PU.}Y@-,.j㝧*_%A_vs$n+$B ?߃Q&5-х]ֵ =B'Lc _$SP"|_i,ƛ@2tAn Gr䆃[] `h:x`<Á{ħ蓬N_m41LT~ftf/@"$' ֑t4 *濦L͵,]foxtr&x 4PL;"@HWNEVy}8q,:[ o=vb`Z9:qℼz\ל+ޡgn\> }j.5\l p3~ʌ{Vl8K IŒ< )'tU1TSkٿKUZ\57p6eʵi/\GOTZn WZclE^M_RV-_[+(M;?uzheTM00x,X#],B -ַ}wT|op瞕=sI9Qk\8ŋkz=O92df6Wr}ō. N|C26 ü_Ѓx:SaZ;ѩn x Z e>b6d95,, 9z14i^}H9wQį3x83E !O٥LqbjZl.d;Ÿ*T.kR~AiFܓ_7]Tħe*?HWbtمB%&@S>~TPVē(/2;a2`ӌ 0XMKȤr޿+?o}։'hnqC5xwvvv?SԩS׍ƀWYZBP `R4osZ Z*x5g pZucnT'N T+#g\/BJ-@ȵEMz?J5z?SOYroVJȣՓf T*|T~n+R5,oF%|ހHYVpAD$v-՝_Ї~]z/fsӌOcNn?yX]]nW??D~S_~CGR_٭އhs 7L9MFSz.x& .bLgt[sEjɘ)E$wsqaE BkMi`0d2 RW)W!Y/Y { L.v6Bĉ,:! l08P*`l >ڦ#z,DY 9u]fʳ+.~Oi*DOL'ɅT7{fп{Ο׽uUOsO5{9aҞ??'swJcѻ|:txH^xicO<^]WY;z1X'SҏT62빵<#vי"kTO7OZI0sz}w٢O n> մ᧤L1E{uH әu/p-0%41L\^ &cF*%{t:?177SO?Q2D5j/V0x!qZsBm߿~?w)!S7`E* "7:L;gmiݺ\Mc?j{'Ow7<?2=~k&H'v׶?|7OYVYƎO#dI!4GZID*\ t0yָZ>tVzL(TEVKhc$@m~T^xأ2Cqxu)TD9&k^g##Ğ)dZ1QJML]Ąހqr?8BL>כH$Y*=E/k?5F"(VA +]~oC˱Tl!ٯU>'?$X%sZL:AWg2L*30]?OosPxǢfe=@'Y4Pk6b}$N✊7QS h6"Zy~)\0>.)s(xLA* 2?d4B dMUMȚxvx$:/;2R*,4l&?\l&x!$[1XYTMYOhdJ"bXWM75L5N"hmm q&Oena+{Ax,HCeSt&&YLߌFdyp8Vg9mč'Y慐B+IE`@*O|KVxVzǹ3glhZEQ7ZH7bߠhy9<(EE8Hf ) Rj>i( rM~nWp ހ V3y!#㭷S:O~r 'w)_I|No;GssKۃA :s JMB(T=p `B@$Qz z*~s ,{l]Kiw"dJ 2j4`Xvw~N3އm'NHlrcaq~Z3RR+$j[wTe6+ZUck}8٢,x-_yP(TD1 "31u/i̸6L_]PIZ;=TU~wnRJTk暍o+/~W~}{߳߯[n{ ;to00;|X?< \Ie=24F5^CK;{7TjR1T1h`4`411 }FXkF&g Y~haØC&T3qAaoQ4 Z?3 Xg%< gmɅuC^(<"<謿{IlM7֪S={pY~P-RTγK-DLy”-Vxk:vVSUe( @Y,r9L[Iw{ W/.2D42>{~;yfRw`K*o BIGs8'l(m5[o?᩾ɭ[uMҸmZ6 F#&"rr ϛ9DAT&hp8?rw~Yٳh46PM1$P%!ߛo<ܜL'ۤB|Xg`g2= LQOO}׻74[ou^cqeWͲ.rR6K0XQe*#GqQ*NkcMaY763=+Da}t{oßYp dx= dY@~?zwW֙xv{5'>q>M<nן}_YlrǺ>:/0BcU,d iw;9(RnfPkN73W*CUY-wMPݬs 5y%gYNQ4) ~wvh<@qO]), eAPP8lJ)Hk4Co}+ ˟d,Ͽhx{ I`4s'-$,,%!:=zS:M|EO#\ :#@ 2vL&ChEO"IIZ- Uh&3.`T£&pƑmTs+,R NhY>{{oyz)]Aw˿ 'C^h?ER+ꚟ)ga&c<) K$ 64Q#+ Ȥ!"j)|uvEo{f\ )T<@)!$/\-R), DzD !pZ٢PֽG-mqĉ%;?#f[;˜]w?>bi=/Ivoox&1 ЀA~쓚 ^B/hW @Ut )1F2([ʰ8<|+yQ焓8Ar-jã_^ٷ!W7+!QBL)Tݐp .bT z.:7΅T;vVW|Bbr N.J|yhwjvuak֘lsg\͟x̅_SG Ka=ס qcF qf#[V{~Gy՝7~7;u:@Xc(˒ ͺ=BF{.>%_cqQQ {VVh-FfElr5ye XCˋllasO=iC 0[xr%~eTyCxC^眷'#q%ŋ<~x~'J `:2X7WQ4Kg0IZ-qabT am85VP4Z])Qۘ¤Mޅh SF3Ty@ HZ)FEYs-(띫/B,U)K54%HcVMt_/|k^9)K׶ͭɧ^_j 2SiAem@_XSnkcȥMlM4M{^5O~zO~y\>ty*S1)Kru^FQg':fؽ<:,/υ53 ̌Ce+LhBԷh6;\;pH)*pk>az~xڼ?@L LH cb\U*SŸ )ZiQ:# vVK4xF7&v4< ehCB)ֺ>'nBa$'%e8HV2u|u~F֏;;k7Vn=u?|)n~2u% kK=Z}"D(s&ޘ%vCpMo/տ•k={y/xgdtҔ0)1FN@)m Fwjkm]Գ"~?$Id*6{)2lLkljwyGݼo3 q· o0ucPLŸ,3W?|i~=+?{ޡA YGe UURUɄɤ,uj-FˋYNYdYA5ZkDY*2!BjuV`w(-(*!l?jyUJI%B3١dc &$eWL4b&b $:״[M]?>rݮA=fwc%)KKˊt,Kz*g#3aڲ>iȨ vwwQ&3[;y;eggc}+/q32tZkyA^FA^f$R,KLYի dz`7֨2S\@%p.._RB kJqKfo$ N`0jJиSHJ[ eYz&C_oTC1uɤ=KapW"0KRe2.ѤEBThLIPf)T]c)M,H[2N$dW77|v1`pesmcC8qL8q%w?[֌x3"J5!qnj .#;,^Ik 8t炘Cyf\4\u[#S}8 s+n+&2(ȚZSavMTuNW(m-Eb2z,䵗&w SlPh, ʙK.j"# /SIHH}2KyO^H|j<=2X_hўG6S]c&JiNM(6։ Oκ3*PFW324F77+tV w,TnwG+_W"@gF 9Z2sF ngh]dfTbdZQP qE;t?|ҩʽT~v= ly~|\Wd:"`CCJ3GIQRT}AT-P7 5#ֈn M^*"Z͹ŏ uQ[ 1&K!c3v}?K{!d=4I FK0U# %B# hZG}E,񘍭…~yqd4DV@ɒz4\)$YZ C(㆟VGQ^+ĠS,-pָc:KJC9)q*ƀ \|QSè]ҨG(PA@II&Jx$$R2/S %%J3D6L!DhPzpr. Fv'Jԟ]ϧH]|wzɘP΅.EHpԥ $ zeJqLo 6 t$ዔbHԑE b/E!XL#T8vԱv 8͍}NƮs\%qaP- 59() IQ˟ zx>`T,wȈsDnn\|WZ8-)6D4¡S>ry7`x9ǺgF#F@V&r|pGjrQ&d__9*G21%_$k&ɝF1;LMK0]Ygh&ubR7FW'~'~g'/Sv=ıcNJSNWV,9r&TJY<ŅTl\-< ޓIYR)TŊ'8lHQ)#^C$& S8*ē\ZO=Jg$c9>(RI pU*Mf b!A |),vL7)&HY,TDKz<>66SgcLj5yA2A}IԀd ,F O8ဦΊ3E'@Id [i RLj8=EICSfC\"̓B ͸k&7޴H*${苧uu.W2RӋNYHGrnMZBrS41)Beֹs<ǿ<o '?r9(2],zUk-yHB 1@ٌMPG+'$Q Dc._=Ï}3O|➛Ŗxb[;}U8+4YdSF^ҁԚF4!db4H=Ey`IQDPAAJEQ ү(@D.aS[@ 3ɳ(U(' J' ;jPF8P)R !%E-^<ėn/SBضecjp4DvP*j"dTȩbT( dݧnؤL52AMhI5]pO!:jh<b@pDCRIzn0s4S'd)QS,L~hFILg:%!\F>D+q7E CklF<{;v̷[ǫA@QM;!"xj!ɛ:ed*^ZY1Qz9|&e|&VA-$8SLIyVJ3M/L,Λyo&8tKއ*26 YPo53o8psLFJM֌Y{WVš51 A>ie&LxH ė CGsV"d5  g-*E{TsJ΅RγH`IL4tU4T2 #W*̮evT iD OMTȉO>4_W_ wĮ*O8!O8ajf3M(93 c:[KIT#}gFčkhk}mi)Mdz | pEfȨvyk?b71QQc@xY"XqimP-ݙ~c6?|c^coߛ;?;5VUP^зJ!Tx2I!PRI=O)]J'1Xx*hS`ɘQK@;n5AOJD("/'{i>/zҨkԒeiL0^g֎~̙v^=b!~~Td2~.ǹΎϊf=ywIcZ!(~\ıSJE^d4%^@3hQF\c"FHUN4Y $BY#Ge:d*ؠNA L1IS 2g>%ɓ'mνãIȏ؄퇺/NRIβ \TZH_R2"4yRCJ)T*T̗ 6=Rc*|%S (*L-{CFhmmG*B5f"BPb "ygp&gN'Y:sŋpa_- wμ#D{W2Y"j&JwN'%thJ*8d$M Ea ӏ~n!Rc0!v "GZkVѨ h„MDO6] m\#'`tqq{/? g3A:;{)]%~Błr3\czL U:5ʴH%V IH _ uo@(ˍ)i?|3d RQ]5U`*噛2b⸍!&iDF V8)VRO"KAK;71G%PF6[M lOy{=֛JqY-4]T8SRE,-<*:4tT>4ebNΜIpsjNhH" x "B#uCB 9[(%l"RGGR8u<hT~Xy|֬Dh2y*JGu'|Ba:jR&%:E<~\:rGyD=å=F{hȔH)m]9&m%T 7h-lu6hwȊfTeZ_t7aM(k%B ,Vhoֆ1Qd"dv%8#y3%ih)x~4ֺGܧ>w˦x6C?NppsU%LgagD!ȳNj2Ǝ8%QYVBJHE,GPY:*C-E4`=z=D"QDS9FA,jI9h(Q"Utz=V഑8E 8'"B#ynn&>IN~#?M8s;dE3ō9'`奢93)=؊j<@8ǨC50Fdyrg 9\d:D !jsN{ot&/-)D.`UHBP)PBŽN8.*mq>؅@hzk;0~6Jmg7wYY^O&*ƛR8)'XgBT dLLIGRs66@GV-!@z&>BT@qR ҸXIhV#hb} %ZF@EDZ!<٨ ޛʥQZ\OgC9k4r0?Knl JHu5F,JT3;rlUBDI/5*ӘQYkalI^*'9r4@8n5q*TI*(JYݫ;;kg>J=E!Y))2KPh!VjUjI(+N8 %BH/` B4({Ӱ'q`n_wre}>𶕏,>P*L6}uMh=< jӐJƚt9LUq<Cņ])gƴRץO@"ģ t ~'~ x^>xz`+dQtnߠjh*l */Y?!^AXfH^+H!o 2/(-v^J1;/>й+qCǵ;\>_[P+*74mJKw뤏phi&wyzGce“`0 䚴ٓ8J$Eq:9ț9 ,֛A$m(X)ZP(!hZ T %:Yx wzm`؛qpS{>vB~mm祶zy=H\ќ??RYt•#dƕcdTD"zhYFD.d5[|{h 's*8|)Ε )Իx+~8=)i 8.8&X]bNѣG]w'N݁'yr:w?:~\:<'N#"1vV#oY9ш|ڷ 9,јxT:H8-+S!&AQ(9h %n^/C.eD#ʤ?it32%;~u PjZO'.&iLJZEbo!1YBNDKͷ<~e,X&?}{,yG 43fEk2 OՐՠhhƃm-eIow ^D |9UQ5NXxDr,JI5=FE3h MIlNh3: >w:}q,j2)ht3lnS4Дɸ4]Sh9Ke-<ek"7!)!Bg{= q nQf6>g6Z.KEں+^:\={#7hgdFZr)h0H"Ib9KV?[/fS2L9Ĕ#Z9qfB5W3nSKu'Óä]Dhj v%TW"JYga'$h:A>Gy/&¬ $`V,lRiED W )4:z*B(QTt9^UjDYDox` 7#G^TYNxk-<3ȗ>0S=ܿp0a/11eƳ{ ~ B3~eYsk Nb]].O[;_v]e3ONq-Qgz"I`q~%EFYm̵;ut!;(hlSYa dx< m$G0fK8t'@-= Z͂vnK٢lii7 2-ɳ+"Z܍DZxBk~,bsT( 8BfŁǎK^K}%|Y9vԴὐ~բ0X&c ;(VmʥK hd][YƓq|rbX}k>8k9àVV8w/sd{]tV٧ fTÑP79{ޝ|დrc*,Rs]Pŝ6jIgnZ8)x%Z "G"yOYMTcZ4#Q'r#WRDF(-i4Ms8Q!S8D9y[# tlh6QJRDs8ԬRYLKz1]dRovK;+zwҝ[|T [J[,2\9@fMWZUjlѮ"ǡM^-9Η"刬 kPGb\yDi.vUZ9* p(fO_[>OD^+=[ₑƆA4ɪh".ؾ 8gZEYw&źdqĦuHlE P£pI}2gC O(yؿwL ]z~^H S6`iTiTeIB"Jd$A9Y}A^2 hT~r[/{Jyƾ[^ų2o3鮷29m/#px Z.yU,ίvp+YTe?EB*ɓ_Haaqcd[xf?ʛu}~R}-ԗPVPDr&IH#11:B ֋[ɸNyKOcD+gg<ϳ~sL&a&C:tVXo&pZ'[XX\:0i8 *j8 ut_@2C"8 w]|Y%@̨GAb{4F6)<+!b* X˸l^|\Y9i$~~ ?mw1Z[G 7{;w72Xw n>|+Oe<3b\VQ?EgMƣ>RgeVn|%Z/uuVR_wu™<>)+3>YudkL;t}TE,(:K dRUŤ Ddф( 9w a.0@4$) 9Y%.C*仳#9aG5*)M`b+#Hɵg4vh Cz>]/zwg"ןՙZ#|. XqVhݯzo\)Vykf`-eei- m,IVOfi&b 7luɔկ6|mll^A=:Í^u^UNJ@ʲ,.u!Bn  mZl[0AUUQ Tq \ 6"̐> MZfH?hT:U4h`Չss]:JsZyAn4h,-X^g"EsC|/":cƚ7k.BVV &Iz[-c9&㒬à1d\2m#Fʂ˗ȲA:4y_I.)u{G-uFQ{ ;>9|]{: A.Z)PRd&Sv*-H&-Y-' ]JJRIRۉ-kڢS䈤#&bfXz˷-]`hRda[ՅFPxgfZ"[g~q_Ѹ1w1u` q:{e4l ĨH%Y@`iM07zW?H#I%цdn(Zlm_>u޷qw qƲE.}qaND ;[Tm:iB0['Sp۱;g4c Ո>{הRIO:;LSp^J4e]L;O1'ÒȄ_~e |[Vl=5sEu>F~]_Vll|L7_un1[gE^d3 N]uZ&)kj[y1Cub¨ؑO[tXo0΄jxf?ao)IRz/v< fsj͍8ˠץͱ{0c1iqn;zeriΒ)]H+:= 2!kW7ȅ$O~w11=w^waxf+b+>{(9g oX-"`£l+1)X"H_OeYΤhQo-F=~y{\dƋtzv%31 ,b4eވxʴ2-L)=ńI9eRUôO &eɤbLmz>wwǎ.h<7á?|x\ҧ uIo{MpntS!mTu7sGaQ('L˚K.PNFln3S^et1ŘxeD'I6K0R;Mēw;/Vd16MEwF/gv 3e On1qxFpnAq8|J sk4ApdXZX2-4n0#) %T43M@\a7 ED&h:Z[ڠkK,̌uqkg0>s sO~!ZI75xv[x )kxd ΐv;<%3tzz pc*ɻ)7L"*1$'*,,5% @$Mmia8QmS$yF>H6>}5ߌWp^^cirG.jpбX/  8+X:zBl0X,`,z|88֡1m]dh6hkht i /quSlN;;y;}xsM]-_Lwvpe4̡ d>"r71w + \|CYN.Y>r7| e^2\:DS6G><.CMtO)MQI ,;w"뀌W hS]njs Z'CZ&@XBM24dyNuh1ڮ1, m5z RŮ2T CԄ7=#Ap9T1.JFExBQVUCh0UͤT5a\Li]3-kӒb4Qq# 25_F|ӏ+'NGo;_{=Cb2:R ˇ)F^R) K d2g9n=_zsKL6GE[` #:$$AF\d0j8@x-{L`$,[+ $>e46U*1 J$'[AGv6sA5D` mk=PL?#KY߾|7?  /y kZoؓdijZu͸6ax0Hq|* lX6^(#QE&BHln&-D@kL2hHF;g?SX__?y7sP/?O~]?wEFűIw\4p9L?3FWIx\0:CDL5Au;9 2v2\z);;$]^Ir2eiasuܐ`,1ƍ 'ˈ`Eðr'wm s')[\pnL BD07ЧQv%M))NMDwET$i) c!ZH3Q8yX6 `_p5ihhLUWuMQL)ʒb:(ӂdd:f23LN'LS6(PLj+a!'Ol]~|u~0?)]ȍw gm^NܑnpXmƦy}ˤ`_8p~~H;[>lcvy÷~7_d9|{<̗yPO?ˠקg_ 2ڱ}y,əLJvzdJ2,WmlwDEYM.mo7uɈqQIYR6u4 f&n%Z8!p:Na6b` "@--\OPI? pfJ;n)$B'9ɔ/r p\<ߩYsL^&%օs>*zϥ'xYtve3fo+|L.&αxo>[SW W,o~͋|{M}rowSY/ X:_߾pO;ٙ\+&o1wN?0f[!y8ܗhnla9",6-!IKP h∰HBkRĹ_kL /$3$t:!ezN173C4<&p.[Q7 IARVl^*IIG&iN9Xܵ.N^~pgc7N,%]n Y׍Z)F} n^a{Py֋-g٧z C.=)'…h|ٽz׸wRM`l_t72{x%1ZT&zz8Wu5~W_aHͦ7^I$ߋϧE[/"w# 4ƶ0ѐLʢ4zDGCbCkͰ7Ǡ76gL ;rJYWMM-vA[E% zLJR =I0*"Q xn wt|c7$tO}Ͻ]ތO'N5-m6G0"Lyc߇ʸa BEsF8i5MM@)XB¡Güa2ё(1aLEkaPq! 2 G<Ȓ,9S*,zvG #SNa2MԉyU&7 $gs_7m ;e+6wln c}b1k X?s۬M/L.>ۏo{ܝ?׿e/wX[݁_gZpF>JRxM  O4ߡT D)r}S7J9iJ8'0.L HElB$QcZmТdRe Bf$nJX^y_sO_>OM^x'O?[_O~ ׸osOpa}f>ZNJ˚@n;PTzwhNT:AYXqWf&,#/k Z$IHa*;]L]}2xgס* "Xtf`,B XC K~S#EN`LG&l)m~ݗRT~6h]ЀB)QHdŤo[fܩSj7OM?}>r{w3o/s*8akQ4擧[>My`pr885k~1/!)Tw`.ZJ%.9RtI8geJ51$x=FFQ͗$% sXll$DɓBDw eަ;;虇w+n?ӿmo}fni|`~aM 'sqrɫnϜD@VVV8HыOODqR i yC$iJC,4MOO$C&ZqHHP)nB `gh56(_a+IDATGFa`w>svI Ybޑ'[X0u[tst@7N:~/oD&E޹~wy? 9}9#ÞȾ29G"&qUmu Zkkdq2=s:J XT<= ETf<Iz]&`l?c['si 5HP%Zk,:r !Z!R8$I%iR!(C(vR}A=!{YV%ӽD+r//Mq;_<“_~g|EG]sOolLmodJݔF%C_ܞ49wzמлtۼ.'UV]8W [\ɼV_~W? 3R쁋zH*o~u]P^ wk5BsCZ2 vx5 kl< ,N(u4ui4Fku 8Pz,6i*kςy/Gl($UTDJu/X뢭Cg_WA܇>}_wo;~=n^&xP&MsҔcl]ThI=pEE17N*O5j.vYկez6t~rm*Mz壎mâsx='\$w|^>_x++:(bӏ kV|"7g},Jݠqބ3|txP' с'+}GyG۟ ~X恥wsmw9i_BA$~/?n+i2`,$T창;bgLO[gM`mm煿3-uY^>EXI@ajb -Cf4To_&E/ E ^iJ ͳNA0Bsh`QwsT`c(0 NHTl]®$p{"=UW[fghCAX2~?68zَ pŸt>dmv'KJs;ҵՍT7S[EϤe>zl{+oƆܤ72{U63Ο"P{qq C2>le׷mMIrZP\\шwvmA A@T i*i*Y>@#+d9k0RJcb}|{t œ'*"Ez*W_B++8)^WVVfҘ8,!aIϝ~Z]]r}}ݭM[ g|_Mc\cTfjF xBIo=:('yt: #jD f3yb>ҰVQahA0TK J{⺻tfǖwZbP.AԮAH_4`(řčدĉƆJ^9|WY9݂7wƾc.Y%eD8n?/L% f*VU9Ox2b<3xC&a b ~*޻FL:Gm9/J1ַpRh}#Gp A7 &W/Ypuï<>f k?QU-]cå^KY 8H"ue4;BEF-t:e<$*Js<` pX4 R顓]·iuՀ]wAҊ/0ZPO$bn|>tVkQ&XI28,ZxwS(*lh S Bbb-@C=ynNG' @)8+PEH$JLC<&̺>Cj%``r/E}u܊Wm8q3G1t.XIBn=f y\ka=VDhIg *V*wA&9Y#m>x8VC6HD>0 "Ul[2Po=8Ϥtou߯W~᠞O6Unj[aV-QY \pFIJ;]$s8<@=HdJΔy9zAC\!wBڎ+>Y3NDPkGh&UEAYWK `,x Hd4u#6Zf ӯ|DaIۓuRhk#1((KB vszn4Mhh%~R"+A`e9nu8;F>$娪ɴ`w~^Is8ҙk'y'siI՞U˖g~n@Yiڐ$I4 !R~r<1Ŕ.QJ:y(F`)ς3K!}X+l꺡kFcvwckkͫhwO펊߹qƭzVWWªhv:@Qa A"Ŷ$:!2 }jit ŷ(!'Q ^Y; R% ssyN"c /C|<^7I /% ZFhi%1{lnnq W/mr.]fsk;E[`>ݬ8l?Mw-8.bkuM)(ʊtB1-:> M}k-)0Z | @.ʓ$I|NtV$ %If$ijeϟ9S9nn ڊkVw?TֵC&@@H"oYkZ Fk꺦*VLE輣`_bZzhxm̬; R̄FIwSzdRBtz]i W=<8w:kkk khríp+^fݳfTWMT,u$m?R26U6\}@c)hd:e411LLeevyO$3!3IW&$MOimZ:...#T]e ޑ$ӥwɲ,,ۦ(G㇏.>яKuu_ȃ~G{z87L;Y<IDf&mbgɴ`7fx4bsgdJ]ULs޺4M|&I9g?}R[ Ɇbmw#mB$1[Ås7hi-G:G[X:4ۨLSOk\.}衇nˉ'O&OJejj;v* 懶4IQJ]@t F}8W u0Mcwwb'wz;4~Ovz=>BJlcgw#gx<>Ϟzn_̙3ĉ>Tŭp+ltE剈> ).( xǣNGa!MSʳD).^(qn>$IO{k_끭M1gz8x,8I;uꔻuuu5<oߌq+܊o(n`tý^ލ)'SVC]5Ed2mJY'e0eYT$Q$M !׵~d?_ N:%f?B u~E0? .H?s?9>tu$˂y`2Kd%;yZ}Zr|E^jʊXc ָVĭp+^Qܘ~?z{&;'64_Bt%@ %~wZO)v'Ox[+?iD|IENDB`OSCAR-code-v1.3.1/oscar/help/images/dreamstation.png000066400000000000000000001533001417327530600221770ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGDIDATxw$Iv ~M{YZLOO @.%ɣ#q[+xܑ+%b!==ŴJ0wTU݃>Yann's ۓ7z'tB'pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6&~7p@H;IBZsg-iTb'"$XkAPH0˜\) t@8ZcmRXkAjzp Zk)RB!΁T)B[gPJgWpbEi <&*9ݗ)eCF'*^9 R ߩ+@(H!%B(YcKRtZ8`y̚~k;.B.z- O;:"scY_?ZΑ1?BH.G]o9Fȑ@,&]BT1{Y6ֆg5RʡRjM %.0J/Nk ėnI~H{Z7kLZ֔Fqr>I CҫB:PQ)6jNϷo6'-0 )Z_snҫJ!NHfWףBD{p!{QQR!7bdᖔRa֒8)U"JRW>iBJJT*|zKSWkc[N1yH!ʻ[k/~ZD2aXB)_y&}o ?8pXkrR)4hVYYNgw4ZS.YX\̩TKȇfP"zWƍo#SF;swimL+++/P&k J ATFHoz;spsTzFUxg pƀ0*W^2|]\X^~A0x}}/>,ՙ:gN"*iřٿfSs3rB|2+/~PH\kzJ $NLKu/2zn=9j RO?$_%C’&)IڣTv0bMaf7g蹄FgZf՚ BG6[6eqO"茝~B#:aFC!%Ig^zRӋ̴fQaTɥD)7&/*=BLEtjV?g Ab:4MI{="'2{; xଏn;羦zJHyș|$/홯ZeZb{0ʤ~ȁS"B* )+7!3Ċ&IqPBHVWV)j&.b0* R)J Nru.(^b ()RT^At>ꖵc' ۈI~+/^i49|fkCo RLl}>|A)xa&h ΀+_ 1(!% c=KQ-N/R#h"30CaP4\ߣhFh$ `-I2;vRA0v+s:~Ŀp0 |÷20oRJ1K_zg_gNh΀ҙ>x}4) ovr 4qP kNf2]+cI0MR ^cQTR QJcX,DX8^jNFX r Y$YCkw?\*>UpCz(Z?F)"c ķp Hn{O}O̍՛gPo0/z!) b8&B%i 6Rv{8k}u+V "6`5HR8fggZJ9Ҥ9@IEQ .ssa8rذu@uk-Cϝ3 F 0: νGf2*/h?`]J煢y FiZo͛ks,Pk [v`7ip=s?zO3{=W +4pX,N)ta0Xj)Ivz€,@2f1J)06z0*M Od'Ae9H?fI82?0΂5$Qe09C+MTnQ)~T<+^J0FZ?'4}sYʵdI)?)r^.Ɵ

    vY>EZ 9 2u0>$I>Y$ jlS9580֛5XFH+Ɔ."nwa&8P RV +%( J1LR:T,2Md'xMXSH!^gWj0z*T|QIu !R' [^]_['^.\r#d;LpfLKqd?4|J9z)NPo EHbf|ݏX^! PRbc'ސ Hb>NC~kc%QMd/❊*1$ΐ:9^%aTjʕWèj2)7o0o1Zsg>?ENQ60s\&Y-a)r(cRRڔF9vo`XKH)Hdn}O^ gޣL$Z]Xj 6vk5t=bofx?32܁U6cu \.rrY5QI);m}7Л"!ZW~S?X^:MXظȋLxx{w2%9 " g$ G:ITM;p͵M`u`435gy}\_!wPd0E?<㖁24EN8mirr 7;BHty>2`a7z~RRDB/ B߉uuȧن//~Ja{.LZs M8sK5WT:֡VZF;#i% Wob3$IZ# k3LyM6oeT" * 1a(da4jRCYnCoVd7gcg@HH)|198m FcR~}L+ )Wj?Qo4~TB/"H^Z  -Uʫ/OQ)y{_XF%laR_뎃.=Q[1#@㓲oru7%)y ܻ<^kRrB;=^F(С$(&5@ThU+ !a%$lwT*}*A};WSˏS~eɲ.SkQb31Ħ}:ޙN{'͙כV*W-/}pIR֮]}x˟xZs_D?FBһdzӰ\U?9`&<_9&! H)^=bʋT+5.׫)Ab-+۬ H!Z{UZH$ hHb,hX,+ Rz߱Y}<ɠ?dk{j(̘YuJH1 JA0Xg{gWkߨkB\} &&!K/>O|ÐK.23;ADLq^31 훝u'[6\aΆZ@E 0}ڝ) ̡'R2Bģ>8e:FNV,9vC 3gDnI]U|V9yܿ@BC^64#8_jI1 )|Qbmo=R{Z353R:Mu>addjΡW_w6["9P/6viq;R8CW|'k-Τ$"ISsS:kncU:Fq5iZ.CtDObR$tT{fgX,+iF1%UGሶzGёzzr|җh֢Tl[kH 53T>Uo 0o2~!4}/~[3Qz abkc .M#4xD$2,7$}#Qa4qbr吘@"@jDyʚ&* JV;Cڝ6w0 zr > 0f+Rffl{;{|Xp~>:1]MDbk_߿}{? >-/!#{ym!E'& &“L1(80ItSG%Vݾ-KMƒB?qreNiTNbaIqnƤ)R*[`cB 5AXzI=*Nm6qO#,'gP:+ԗ_j}RrL|PVE3_{>~^zjUʓyg'wvC˝Ƭ±Ouz#tHN@z|1qc:PA4yCLj#JjN)(');mڝxiiQ@#("u6|q'QD /d? !n^¯~<ӤIʹ3N,ϼϟ}>az7`~qO~淿Uo}~|?1EEsa XtV@֭;ucR>APUDDl5FpBj3JX2ۛF1AA@}nfE)XgIe'lui+I=BF%*zZ% ?Σ;rv0}?IR{'xO2tUT j&F/|䎀^h`ov\ |wSkz?|Oۂy@<{?Me˘8pŒY.Ic(E%*pg/QH#5Tԧ˶j_Ћc)K%ʡFJAV+[4&MR8+2RBQ/"M%Ttz$j91펙TL4u."Zm駞gǦ) pjy ty>~RGx$74pA䥖 MW^W~e/|.s%=_fx}PbRm߿D_1ac4N&" #4!NStRí.#ک QJ3ԫg OKADȠCJ!y5+ 1Cz=z=$)Ǡ$rDZ)JI寒/J=+ ɉI^p7vOiMÇ3-yBCZ0I J!Jhf#MJb+k:>;JF Cá:T(^EHKhH*+ p:( u`nh71q BHFØ|'4ሙy..ϳԬ " TJɐQ\kRsY/5w_&%Tٟ0>~jZ~5 th"!5$()%#1R&/a$iZ0;$a0&[5@Q4(h"-,ш$MhTJTKR3~_N0/itv? 8>t{=fgiUʴAJDQ/(2Lb66ougNVjz':adL;[뿴v ̷@!ibyVnPmqz49 RDtn⚎j'_Y |2u*EJI ui%V $1%XbVdwg|n FIWKiʁ$ 9~=I}KVL}1Tu m^&J0 ->SޠVQW҄vMݡ2T!Z%k0ih0dkk( >'=|L' wl{Oo6ۻ{lo:]fUggo@AVZCej3YZ:3r(hMN`Ec$|8eHTh N:{b -%8*70P@dI<DJ*%|`U!J-@(jnn2=Oٽǻs&OFhO>O<<ϼRJ_9=IxMZF֒ X'#pS5:;1u$aOmM;luvmʥӋ4juϾz6RtivVKfAbK K ^\f~I٢YRB @ Q rOx >/3vN%~(8W@B &[qNb/+"TBюB*d &/f)"jBA(JR\qbS:wdAL~M\C˯+21yLaRW^O~xМA ooŚ-$(-+P!$ijpΑ1qj/8kf~}|}<&6[6ؼ6{RSTURcggtv(Ľ G6Řx`ХjL4MZ fggh4i[ QXB%@8SO{I5~6 LT9PbIjA3b g2٘phgc\fys2F甘&9voː]k_$7&:#*Gj4oSO>ɕ+WTDקK(X)&N)(-HӔ8H0Ơbc}U4gRRC֚xrt' @nn^{_ww=nmw=B!*FC:66AדJaMM!P*u@j_[ŷ FX 6*kȵbk-f[s,xӡV^.Q Bt>4<La`<B",t٬^ 3nܳ_9lN`- ,}Ap9}On_C*EK|BPVqIpwx#B1l:HPI3Bh4b{sϔP x 5q7>A{s[["PzL\7   ,t'E2:| 8Ba%1$)ᅦza@T K γМZi2hQ-kmtФ&a;yl3d L.d[#&ΗiE~FuO3v׮<^:ėuz4gg1& f*4gN T촻ZӨUZ+UWFUc%z x(M/ gk++6QB"Bf[MAlR0 X߸Euðߥ }JN<;RH(uQeXx||m3Bc4,t3耐mvnqUE@S*טYgfnFVlsZI%*Kfܔ:[H);:0޼qJ7PO-fvd@3zN0.2i[L3i0ۗ&@}~ɱNՍ_ƍTj5$]FΚQfA f )$Z8qt1aTR.Q*X%Ir)DV1 x$DKռ?ԳZ$,OUb{vWnn3z(hD<|=,kxa{gMvwi @jp.ڂ3P \( Ak<(;;\z @(9!XoN)uk(-)EzrcK,0טZQ"M'ŸP}T8HO3phġ>7m57~n1wL&9:|y: *I=nkJInb-%ApӡYF!i2* Yy:ao<' _{"a34-RUqY<,?`eamoNnNڄB2S*s%.^bekte{gmwt$qLCI fNjWo"uHP d^1xfjua)kE0R-h6h,-8D1LA\&!Bgz2n1I߼Xi&AB0v/N)&5iELZnؿSg?U/z+|K_"cZ3bm19"۴ޟbJވʛxHbcv6Ti)0f<I)'zSg+TEz#3ZަKJt)b10Kav]nYm6vv0..r-aq&[$=wwHuV6wH YaB*ZWnp\A81oX$f*RHhZ^aqqYf3VJG+a>løl&`?IR0 dPG}H1 6S#Br*ڦlHBz/fZYDHza*j*Q"(Q9F.{{{DQx8x4MM^>Hk~ex{%0ی2H* `k(l AR.Gk tYn+kCR`h;片]6ad k>jϓACg^`֠/XMWR"T;m׎Q2 pX˕ګ*:jVs%f[,fhj0c BnyB`9c>y`R[I ?4'ƾ=JI=~ _+RTV*~ 2YV;DVVB)hpC؄p@o4bѩ4 Zoff\U쏬]K˕s8I-Y~֞K܊=lAf"%3J9|G ㄝ67x}+ۣ(+Ѫܘ[9}d{_ 8A}cR,_y7hB u`eIQ #L2JIv&zG!BVZtD;mQD)0_| X yF=6[;dwg^MץIS ЄA@5Rh% bUzKgxW)2[8vlwvmP.EufgfaafŹyf j*( ^ ִ?Dvz[V} &o=Ua~y1y*vg@w"$ 5PS!͊b^GeȼԤaЏc4e0LIq(eI #f]Ę;agg[[;tvb}}D*eyU*J(u=Iq—0`Gc0uG#hV7)ZUSi6gY^gan- `tC=G:h$?qٗ /LZ.iu)`/ * e* Zr}bp֢$"RU*"N,u< C4XnPuX Υ(:*NfjZ4CG}!>7ɖ&a7ू@)|~~ZH4k%fehc I2J|.(MI(RMK4efpG/dDogksխ ׮R$ 7 DkRuIiR6Ť$#`:A[HR`Dw7y5aHVcfnOqzq r@ -Ry)C>5E~GJ4pၾH's|@4Z-J.%uB(PtCqLCRADTF5DŽuܥ$ 5QJtXހ^g|' ( ޷vżKx,*K}g z%nig}-)5uIJgX@!äE馇jI%֔J!5eYK׈Ӕ4%Ici_Զ3"(Ef!{xQ+%<̋E( qRHIh#./!̬R^[> r;\֦jdR_lj}o8R& ss\`8nss}k+l.:T*g> "Ғ(!esɞ07YIX28?[,.27 +DQ)C.flTrj*wa?TXk^oį|ܺBslYCP-GJ. *D0_꼤5q2xPvHDF(푡~~àߧ53J1o6f$!c{?78} Byj&Q3QKH2ϸ(>`϶*|#頢2O:7nlԨ2׬Ӫֈ*e S ZiegN F1{뛛X]k|9*26 (gOF ̊©qҋ-{>7n]|ٗ85W z{ b55:BI2bdcyP. >S8FyuRO``f%Ydesm^JQ+|I,PNIȪȬuE0(E) M *85 a2ԗ9VKaHmaG-nd}maOjH_Hg EuG/q PjõH%aS ,i6Yh2Skjs 2Oq*:Cx>ρ140V0aVJ]plv4A@Uf5JZFby@ H@[2I$ԚM*nL*7". (M>()A @i$DQȅ{.R \ɭV֤mM᳘okI$ Zt"|28d9Z0_5p' t@i;I:(6j#֚u7ݝ0"d _}MHʟ;~{138∂yPv) -H&d 9 Z(M$YVv:kk\_[%l#(7v۬_ʽK /,h(0DeHć!W'V^CaKe:{!aL/KAD\V(om AݝVnr`҄\!D>p$#A85a5Q !&)PZ#:_L X-h6Dy*KP5U }~~-oNӡ\.|4IKUH-to_QY:*QIsf90{Fʑ] $JD\B)Mz}JZSP7yv셇/\V$[} e">G!pqԾ(eg0q}h ׉~zŹyM RJI2SݏM9TB|R(fw0d.!`ZaQaRssֽ=K\y˨ή?P*om.J67Ƞ)!*KBIP R(İ]ļ"G2sQ֚^ SK ҥmBf^ )PZ3L⬰)>O TCGU33`ul֊]] lpRHG#Zs 'zYkpH{gp@#T0$>ow.rӡx'F ֖y#^CVrWo,yy3|{f9|9:AZui>#$q" B}(KaJ30vn EfYj6$7++Z"AѰGwg$CTK!JL:ڃ & Y!;TDZ3YX\R>'@ejc~a8ׄYsLPiE8c~NMLYoC[X53 HEWIX65Z |%b.O|~7x0.Hh }JQ +U$ ER4 *Q0_oЩE$f]ɧg07jܿCKi԰3/|[vЕ@@& 45NYCoE!qJ7h ?cuaq0L*a@ģ!nا"-eQJILRȰrrj)t@*˜!Q}V+dcØ 4eшE]Su^ @IBO?s0Je._ aIfGKc71rIKvƙìpP%rc'D2otYf'] J M..68#nl˯v6ӟL5"˴f*cX9CPI@Jl{Q@RBx4 @^gW˯FR-]?sa:~k]aMz6iw?E66U Qڗm"JFi R  Pc,tJ::5e!H#`@IB9|,R2 }t<^H10"4Cd( 2}GE:’j ЌG{F5_#3ɗ l$I@ 5|,{Gأ"Cp !R} J?͔BN9p)NJg)3&8^<=yNy76;X+qfD|"|]z Rcޕ.sAMoVye_8OF@ oӷnlϭ~fΜ].p,ܸWsKZh307tz)x@BF# F ~ Qn=JJT"Èa&5 $lUZ-o"̈ĤB!/)%HU(W* GC6vV"[ p+B_Μatgj-T^)sO!a姧FrK/ Pvj݌d6:09 RO6X9V>帗8=>c3q}& W__>ȃIrَcL9j/1g`{^:eP8&fƻZ6s:3dqVq C^Ǡ?טVs$N [/oΐ/nrkV3ȃq".M~5WyXX=.Q )GZk1>ȞIQXljHIET<3%=C@QfL@RGtum՘UӔ=;}X;lΞ;|;k!8kT{wd^G;'cw&8 8(c~Q^"{(V)&kM\QSd݉HVJdR1r(y9\`S1ZC侍yJ|Z6n qz{]^Ɨ^>I:6Μy7Z_c{_ /hCoB̶'xa1%جGj *FF(ЏSxX rX# ʥ AIU(Y-ZK{jmY-($xZk/;M򊬂7WŸL$rm' U>el~0?2G~)}a=㩧??E\;Q0JRgo4?$ Kuf!q> )QW*T`Ҕn)- ra~sFw}+?|7?G_˟ HtNCHR*' '\٨3S$I@r R*XҜߊ藭=^IWqGxŤ"qyImQ'hvDU 5H)M۝ n{3sL '$5}G-;1z6'b|bU3-(&uܘo]0=м05.3GZ'4$͊d"!#UX=0i,( wwii]A _EV7aΜ+<Nj/ƭ׳ɂ pw cJ -j 8g)i_YG5fR9%iP-xܭ܄8lWڷ HFwv]^ud-:)BdC2o44yx]Y6!8i)MBqiwGZF]n'.2|}@Cۡ%K'%\Bucz!шQ^U9M՞CTF蜣?Ï3?WX_/-.balE%_]JJjj5fMR>QZRZLTڔ^nJDRf4Zs\~6 çR([\8kpIH R#VAOLsNbNvS nJAQ=Qln7q.bԻWMr:~B!pvpscb,}e) OxajO3L!#4T7\kOi9YD7ڗ#jQ1t#FI0.^?S>e!8τڽ.}!_ o_ħ-F&5zշN߼z_ G}U%}pvy$aۧZ k0ҘDOe'oA @i {!љ]/&/D2(%k6ykܳ#?1v^#q DJxafϩVVB':it򹿸 3 o.f?_lji[VL-oMoWc@1U=+g$jF\;۔ˆ ,Gj-ߤÕ:9JQZfkngn{@c/þYmwi5ycdk",$Qe)(%XZKZ2#NR |/^_/_sJZon,;8٬(_~i6/WlAQV K%$Yij͹YJzg'!p}S9Mc?uIb%)>:(DK>c2>)֔Q}g<|3+5[H!hffF(1)Z=X[T}{;w6hniohҩYj,d.svc0NLkq[NM SƦ"3.IbY|4 0Y~CO1׬+ih|GFA/x&`R {>Kh(!j\B+5.{!gTc=m^8?HFߴN@?i2:3~)5LR*;} IڸS{zo;s PiX|rT+ #TGC$Q.Ymz)Z8aq ={;0 L ri~ΟQ&A|SW7k5(Gijh.Bx gK*hTʌI ]evVK:T~ Yn(85X{Vˬz䭡o  4>=mE hI|o;kqCϮ2rOq+M{'_+$*rW{˙-"T*Q*ת ${{&%;;{՛;e8קVL8Tn;9.rzP\TJIo0`'?˒3s,7^.Ӽpms 3TJaspOubMJq9oHEj_}l; Ӕ˧ &p/Իoȓ+|^_t;G-(eYʹ i5vۣwqsIL=U Gm_2_9hf7d(]QDsf׵Ze沑CĘ4ɌŁތob ͟pXby~fZ̮GCScť xk%}qdlgs|/SKlXrUc^%ϻdGxޘ{t1U$!CM&^SA^&6:4W}gsmH8*wLc~SeVqNCyߝ辌Lم8Ip} NUZsTḾ"E)loÝp/1x3jsg@)WȄ9vwdQ<ϵ-.,":YǠrzuāR爓[;Y;*W05)$4? M>B=ٝ8^WvtD\,g MMyт;?g?384yHr_316w{fy>_p.12F|ZaLnmrLQƷP.+vzC24fg;ը(M83Ro MFǣѨ9_?w?ucaǂ`{sRL\ 5)&",l24|71~4q+m7sХc _JYqz|Dq`o{y]*qs1H)Yb *xu&5xzMadscIҬt{!+;mj7suxM@+L%bݥ^1mQ&1*u゚Y OQTMI?ip8W(aB&t;' +$7SW՛e#oԤ8.4R;;lg -^>/b`Yuc,D6w߷.y~XI 32ol%6Mq3&ԫ!yߥsh{/{una᧌8se֤XcDpxZLJEjک~6txO`453#BI1iJbmX.~ ǜ yÏ673-$/cHHɸܴ4jsQP*KJ*o<~O(Cn~ ܠ٨h͒wJ-~ ˥E`E·; W)a>銽G&yȤXd%_\1'rT_:<;uAwRI6vT+eQHbyݷ9ѹIo0b+}O!Tr7Yty E`_vb'hmz}Subc9Mz~퓲|}%a+h?tߚqk&Fzsf'"e-\P=Nun{'F_cڙ59.4;x=̓/G#ϾOAL2q-7+q =;u Gq84Ojܗ)f^ y}QRkRꥐvR-p)Yp`-J^L7sKTx܅ MeܛM @A<E ? @U~/ \pȾ旇HغLE8>8<-'Q14)3|5i3t*s؉,(XQ ~ *wcsv6ҤpH4؟Ĕ)-76i5jHO k?e~#[TΙA pG 5kY-nsjqt R/Ʊ! ˜!u35XcR "Cq| qDF~aH)Hc-J-|_'1a &Cin5}#7_|n"205[vd?Qy;|>).yy5L?Jq==K7c^dAиʏ#Η YQ7~!*j5 &o qԪ~w32R  ퟻpoו ׁ8h4˯ ˆ~{;SGIA{q 6ViUY۫w^wN˳)r^KI"@+"wp(%?sPm-` reyj>(+L1sݾqNYec:X[\͊kQ9?g />L siaJH)gyX$TpS)q񭦉K Mn{Ow;{?I FH ʗve}s<47R% # u'߳_#9}(%QJd#wbGKm,չTf - N?l5V$_G+tE9aЇs(AA{?I#s_lQ/.{hξ#NScH3P.8>D?dW_{_[+.UyhR?Cuwe"[Mhyk7x3OTjJu[k dP82-ܔ#pGё_Lިcՠ/l7vkW^7YHD ꚅ& 3K̞EV!e>[> ws{;›qz}qVHd8nsߌ d…o_ NզѨjm_(ekwAlPNjbc4)(I@ $1:{z#۹^vXR39G.p=pH!xv lRZP;̗_B:Eg)_VUH턝74{:,t.R(5Z. L30&Z0ҼǛIz0 tכG7y_wEx9di@TK%t'^YދM@KTqS Ke.=JC9rY `?RN%.`gΎWcJUA _ZK@Z̵rSqo:t$6 w\}+7n/@9 x}ԪuLcz[b}?o%#u7!N XZi#k )e <,tѼrׁIJu'j"q<Ijis*W^}/d&Uy pTD2e,Vayeeʿ+!.M7Pt)ş=oDqXyz:=tX}G@[·Υ8׹1χ\Qۤir fKHiv4J94=0Py.M[K~ڷ>x?J~sO>c+W_{{-$IRsJ DZWA*=Y6-V=:q"x7SyT4%A!XG<!1AN%vn:xX޸G!:ҡ.]<ヴ;V6v\{/^EEmT{{4\S 4K2&H-*9NuVC*_5?c û\Wr0dIX.܌_&Wzm߼L8pi3*X:OV|j%hs;Llj0IC> F&u0Hbd!H1v&:Vԧ=B{G_;!)}v{[\}5x:4W\ \8) d^ C^%(x⩗YyĦ@O埛ȴ8EGC],:dxē_4][aΜͤf@^.^ FDaPnspX3]9>-?9#^8_2 vQ.3k>su%x¼wmԘ]?Y~wK$ ޖLӔ~C4s^R&lgA)!(`L2pɴ0BJ|Rc1PRر(D郠7t䢼͵0$IP))|GHb[[{\{&_ʯ<{k,T3 .{ 3ך6蛀^"ǎv-UQTX!ͼFFjNT&/lB臧Xaē$/եavT!1|IȞ{k//sjO#+il$pf .4Akx$0{[[0&7.xp#˩uUG'ӟR􇃩𓒒QbfM٘xo1^ZsJj_4e1YCJV;zM!LЀ@dPR AycBN9g8*n&I <ת4|# G ; /^\E^bx<-tjD CYc/aߓ9@p)BTqf=FFDeniԚT[|-},,.de&7|4MԓݓN2ԎK~MXR)DLmS= 5yņ|qw9#GNj`tɒ|L4b!2u>rU1}$IR|)s{ !2nlMy'zL`EoOĚǟP/|32GX[^`c_>w[bQ^|%}8]A&q19c""j>anB 8xrnodK{=pÎ 6.* B8!HR( FJ҇uX NǥG:ggx~ kkֿW~ 3UD:0N)7[Ut>,ÐE'8|cZ :t=ҬOX ݗw4Npevo(r795g6v:7p="M"O>#RL£6!-Je !) pM('S4e+wSHAysAX=6Ŧ1&RA*,$ !*&{ ˮ>:źV=IDATw{[2=3}JHbbٲe[RT*$I)Ebl˶X.[b"H03o%|{=34`އjL[r9d"!9Od4lPZM ᭟PlHGC＀]z_Γ8'Ñ6ݵ=KX\%P.Jցa)a[SLk:5| ʢrw_- XUfJ!IeH_j#4?9lS낀p(ci٩eqЯxc'>o>EjmPg/\l]Yl.\vSJ#+%V N&0s!j-!Lʨ0?Ν? q̪ZxH\igtX2ρ|>' 0B!,7IAX!e5<8fz2zQvakaJ;|ǽ+qxG/BX0=t:`"o I/ Y?{9p3h)M\Yr*WUs˻r>[xyE@ ܷ_D/s>oYtA, 1qTlZktzw>|ot[QDZk._CqQidۙZ2ۦfK vo:eдab  `3(EA긤NZX|}БO;& Ĺ \B@MJ )P[38ZXZ}u068Gގ`Bg/^DpxvV`K(E%Rhߥg@l>9;t?n1@wޫ)pd]O#KnǼ PT'Sܿ2%Kӏ`ye Yq?lN>%FEy6f:}(Qh ymTf5Ì'1۪=/yZf)a$2ԉ̉z;BtRDed.SA(&%-(^!$R!n)伱50Y V XPlGʼn p8,?oVmġE[]FweXIP&K/>>;?O`yie^̼Qp!5bKx"TOI4 e0??\fT~gu~ ϝCGKs7!wUkŋY;<It?\jSv~ mg/֬HpWߙVBU$R  ^x"XS)-, KKQ]0J_Eau , Iq"RE֥Apڦg,٢JݖDA9}yq:ΟWΝsO_(J8ރg. [6{SgRBV3W?8+na WZSsϨ_[qTr NLw0-jUBr _0MR6z,{S!h9` 9u]@QB&)L.`(T#Ftt̺ǘ;b`!ڋG@wb?Dc` E,tyO6-c=,eƏlwdE.u?4B/0n/ ͽ O/p|ʊ+er͗p)^-Fc4f0)]*,LBTECn[ڏppc :сgFc%0{A@`0LڄQ2%CB P怋r4B񁣼P.SH32tC)ɻ  'a݌?gnTK8V`ΰD;Xn~̸ gU}G\[# D cBwᜇN>Z[u!D0<X_?ŵ#8]cw>zmg2_k| Em&Cer[5.ڐދQF XG}"e@(`Μ^]wZul\ۘp,l4Y.h)6 YB vs)Jg0. .EY eA D8<>99f m>\aɄ=ַ*)̩Vc=nֽZ, Lz}@?"d6g.#5O׿lq4N{M˟EzOc{4C)-q6RIk1w#}Ӻށ!|L-bgbݟ[hj>3B@3 6L:jϕ؂QQ@+\rFD50,^ͥ.p.Z0@^il|3jvW IȊJJxS/``ߐ"o_Z;SO_`#{{.h/`f.58僇KXه Ijn~ږ5D^U>-U2E[> ]{δj'b#ϸ(|ᠮӍq7|s" ``"` XB6]Ѣ%I $к %a?@}PScu+Wtf*6H\qPm4qܢwO`0V_U]w]WA0`f 'N(2ce {JU5Y( oe 02ޱjVUwq̞Q>o6-umg`|̝L#λ|VMS|_e(hWCd]MJYC a,X@'2FLU0xQX4Y&v5 )>_&ȹj7ꁿG@@D)-e\Fq %w  RةsyuJēxFgz$-wimKM?j;*Z̓Yz-3!06sXS?3H6'8;]i5~wk(͉F$'m+6Yx] T(l%,tP\N=!3&h1AO8s@Lf`C)R ITmR :Kiq oG855#۰[( #TEhfoMJd-󈃘=X7c;>7q_x*f]uUo)ԽO?~">UK ?4Ȱou>}m(+`uw[ʼnp{pI,YȚ(Eʭ-zOJ"[&8pK1:Vjf<0v4i;wMD%*f@?gY(ZL1J(>OQLч'0 * yS'kȀ[XBC`_;\E x_>uwE)x4;]ZE ʊ켆,88|j p>_%,.+eǬʷ& J2Gr_xC8K*=J $AkW!;-9t'Oɻڡh/,L`) VHda t+&"eU̱+1.]CO \. 0䰾Y$`d(F#/I;VQ}\.."pa$.ճm!wju?b_D 2VpJk,YGL $7S*EƯP3Er/Wz0‚e0)Yt{ HP 휢}G70M(ƙ[,6ڥ+m70l*'.[hk+8q~>q5Ȭ kc%xuKV-&Uʜ)h|]VP CG b*+`b!?_-*/%vEUM |ن'J9G *Ą (oa2MQBkamlo9Pj[I|QrVbdۚ$@$o^J.]E",: KF]p(7v% H8g.l(cQ(IgY ֠ο/^_ >`ơpI8y k[2"a[m$szKÛ`4R0gQ P,p&(JD*ę p,E5k€0-J.\= NEo@s}6~(\uх-P,P(}<OۅA}~5I"/ Ne2YڔƏ/,th6GYj #|dhFYѠť%d%mIqøR6ysmzSfeb`3 1l׾L>եE;vϜYceR46Ƅa3Տe0.hW*zcagpdk\ Ѭsjf s!)0V؋S{=FԧxaƅՒp>.w5~ې#jC'LrpmB/U)Y%)HkݪXHf<HHf3R (~@MeLƐIɤ: YفY?L)! QU9>g!{GC!A!R0HHŹ}t—@}8q9CGO.pV~筿Yg&9O=. ѴWo*hR`LP\&E s $:eݡBwUP7EҋBZ\cQG_1G G_slZp[d!FQZwϝr܄zb-jiv=-/6Z? TP:.E&>k;c\Zʆu?P6QHT;4Ot9$.vpw}8z ?Ձ$`b7|Dk>@n,Ds.GeA̔^$D"g"XWh `\ &VY&؊I6>ab DX !BZ!Rf*ĩ[B +ҸY o~"ڭƣ]@;F Vw g&hmZ`Z&ҘLr(U^x G S< @i &<ڋ?>g>`!{q#2jmG c?e*DZ g/Ccu|H L&[q4ҽnh熫%H]Hihh<w.D@!HWFͪ]zGG~~yȟw1ZB߯ =#|:?XWB@B&ЃuJ"Iww8,-췣Hc-g)6KŔ[)Ms~HR tmT>]C$vNP(h@D ch9q};nX[x@-ʁ簉  9~'Pfuq 8j?}D{/T 3b Д?g \PA$%4DBi8j) "":`Bu?CY6B4 a*q3ѵ'Vv+g"44@rvO`Jj3cYwU5e:y?7i"PJUn8յWVLY*Ccs#lsPP83S3>~";P}d / i/4β-(;_P@QkJEN7&<-& DBD('Ъt &D-aRT8)Upߙ }:CO4KXApr2@4,8Պd7DWJ\Hc`4ͥklE3/SB$}g`{P)j7 ?q` !2^ȫԶQ `1Sg<汕PEֺ[B e*'pDinFoOĊe{9ԃc9߾h֕{Z.R (8Rq@jA[v nz^lD @ L+Y ~^( l}OiʹTfNcWP>k CRQʏq .pueT1"mXUR9F\Hk ;J!WdLs~8~P-0bڝΥ_\^avM =dSvFyD ,VQVvdht`7)JhU1\ y;klQpp|u}xm=̢n!k4QY[OjN ( 9ǪGc9.'NpFK$tY d`e2C2812RA9v?U#+`Ǜ] ߀Ҕ0FБ#fKpvG^6 wkkў66Px,@1l v޽?S;MNO70.Z[7+ksv;b{GcSʢ&ɜ挂]f8ܰ?/ Nc~8:*B0f`ifIJy}pF$%z2+sHI~]9l;GUW_NA?tc ~M0hb @KC8Mw/ωH4:'7[ii":@C;"gbvAuD3TllqCtn'кw(?qfUgA @1+(VUP+ U<[lX4LB}:hN K1 HΊ @cg)~|/ ؒK 8OSģAgEd(* G}& (c?Qf(r%UY0S{0XXkb) ML)=tkf7C +Ϟ!k5g̷&a0VR@pKj LtX}Dql n-)*9A>w?[pbK|6ׂ~?iQ6(Tr}}<8w|>+6Pe4r5cL +s@H` I(KSH)]POK!$&f/+UX8P=+_4n|mbqQT~vE=jg2B  p WiEu`v-([n1F}y !W _mD2TUcEs1y=1vg6[!Ǵk³9F?WfJ f,H R"R.ԔKCUǦ&1k4dnĊ$CZ=7ާW Eș8qF8P*KI^(i  "5%!(  Wx2 Qj(3lqJp]@& FT>c ʼXQ3 x 3Z?2x텅qk55PZLAVQ+N<"ܙnޑUzܿ@<7M}v;Ћx~/GM WBҴ0MB{ö ZZp@:ndym#KM*ȳVa=W)(k%oXI2FL@J^ފcf(peJBU& _[h]4qQU|~?q@k[eb:t=CMTG}+ݿ&8Ua 5-IN V q<Ԭz||/m,0 1.}{Mn\C؏ǻyhhQN9lojP,tCem*պ UWڐRvP4d"uzА`,JY, R.8ӧ@"֨4A*PEA6sK>+R ;ms8}ꏘѻ$&a ?=!eEdrq$R{ZfElvCl;e  C!*܂awA7p8ߚqLԯO]wo]b)<&h )LЖ!U#S(:xTT-=b2x8Ja JV6>WoYm֢l Ӂ=ЧF܅X(m6Q%%2" /Zh9*hdؐn4"$A. UP \8Sͧc7ؔ \VF)Y`0_knbC) cy 5 ƥ~ﻏnXXF_VDkjXV#|}?0+&CF/WE0_Ŕv°%E ۩ XkJPڢ{apOI$OTsqmMx,3ڝH 9 W@5:BmnǡS'UoGwtY,# p,&@=X ]4)& !,Z&otҬv,CȀ8~ؿ=qlQu ;x[Ch6g{, AA*$͠Pwc8_ԁq&|E`Ln 1sHf.L&KuNb;q4тyU1 2$ig -gG .9>Dv ki+kxǨ|׀t̂qK@xl"03A\,P,Ji)Y|)7N|cg,~,2J*g`IPʐ `u{DSn H'H$U1spP{-xI4\sLi,u f^+i3S TB( 2K;[E %6 L9R`iH=k`&#c(uRLVaP4Ks$K͂@ nV-Wtߴf6 EJ)=r_]]~, :q~MmR $I˫gПC\+?] &IBH(p2XP4YL}nk~?XK16E?Pщ/5To:}~lEeT#N qRN'(ƣTEiN,;]}V[Rq( M@$Y}Bi"95py@ i5He:Rn~Pq1VdRҦes#|qvSvq,d'hJn Vh-ʀԙ6$:iߤM".~D#Z,tZX:|os׎m7NWw0@OُKxwk/`0F L$ԲY+PѬ]i Aw*XrǠ_R0lBk]n2bJa25< E-~km¨[B✡i߰,yiAi·$uyi*VgQ ]*wZׂtiKe^g{saܬhDAB j5iL]yui uf{]<̌=!Ww"T0͟d4<˂l# i:J&$LSXBUY- VV[^ w*X9\-3s瑏l\]r5\RԓW@Ww&iCAZw@dg-nӬ-}߲(Ͽ3ckN<ʸgyD#R ʖ0h42I;j0Co`'ap qݧ~}Hf$oqyvV\$&Z?K[|0'HL>n}8:΂C"$ Y sZW;@ֱqw歧GC|5z|c>/E+at;Y\$>Sbb}*AU(LN4Xv!,a! 7 ;UAKP؁q٘ snd^cbq:1ػaiPJRu8 -хw^y{N 5ތ]YTFvMkv|36vcFExa]:DJHN>07EQcc `gj21f"?d3{ ט63rGt6^6HMקX" R0Szi`8tgԵ[@ VQ4G>W<1\`A٦7B seX#&ґ~RK2lc iI =x;./7VoZn{C]ƹNɅ}?_qjQ I$L.,*1!e6eD-J X>S? Sx?£y }kK X][lb37 0q7сEx-Y'p.](~#ٹd mkޡNe( *bkVѐKW $ Y4,EUZÂ2>x÷Bw* : VgOwo9d)MR$2zoc8c:jm4E~y-PȃG@}1POq_;8u|o᫏>? /amaik=\3 rCp $k_8*.hLٶ.Y:5:>KՑ@A^]Uמ>~Ƨ :>!pgaɺD9X!ٳ?tꟁoTy]{?5Ldmڐi9^ī=jA³FơMW^E&lrJQ;}7lw^c=gEp=DIiN#3 "|6Y:Br]hV4kTEZ, Vqݍqz1gE6ూ `|u%5C^Ƚz衻~a!Wuw)h4.ׯYkaKK/<NwY &i )M`w>2"M(Q*"+ֲ֚~ /L${8$w]W~ 7^B;X- Z0, z.:k&m$Y ml{;J]nw 1YTd+:OcQ*T0uÏCeP(jw1 VSCY]ơ#&3NJt -..w/W.D*?j,i"s׼O8ԅQ3 0\}?xgO٧΢hRB,6Je .dvPEV[*%nʺ\XOZD+1 K-B,b'`J*1Zk3^|mb'6]:R&=O`6>2xBv E1!di <GҍY&E|vVoE(`e}~cՍ 9<ēx+_^bv 'pR\(q}4T Z Ԏ`EL>Lc'vٟ/QńX֊L;[JFB j6.BCS̏e1H9 !di:Z8`dt|ʏ7j-X;tq \gm"MX !1c@ ^@hȊR(azArw/iU|kOk?gc$<N-"ↂFx[/kЖznVNĢ<%4şՊ" VP _g|C*0{}q^0o5>8 ,D{yJ+]Nh4v[P k-`0C޽ŕkW~=_n&|ߩS'g~L&Y٢̀eb@v$@ҔQ0\^XX;ҿ>@\w?+}|\Yx[/ǟsxÏ Lfϖ=bFVԽ 9 `}q'ˋKl'1p֕A*96p}w]o7f(t`׫7EkI#KąG<}ϟ}G|P[:~BoaFh4*S (&OtYkrlYҤ XJ 0B:m dD|T W"-͆ön_|/@]4 *Ԉ7HYT'kAuP<u8q_0 4{P}ݬk-ܷG>K\w>8wD¾c#P f u^ I2 V(*UטּvA8m,$cTeWb]}K0ný35hR8sோE ,JYxFS}ϴ8`{fUK޴ 5&Kݻ#7֯_?rͿ͊{W#A6CBn)Ł- ;\ !sZ)EAMh[RځMˬL"9,O}f*xDNa 5=y >[۬} GϗrgT X"~ciX)59- PhW>̅E{q]ɻ~9_{ -.VzYK*FQV#Ah"2<^Fd@ uBTo>Fg˗\}ؤ*W;:׭v*hGuPeΓR1ոu\p ^nC-`7Rzs^M1eK]XZ*܏×?3ioH0̛ Ff4kRC1Nx]%ߒD"I8|JFi1΅[&#|039ڕ<.v:koFS|# p_:d ` ľ]fm,qEQ@8x4,ý'j?b- oiZȺOS?k˹>s:guƨMJXlJd&F LHv3 "<>-_h@1#sz\ԄɌ%k.}?_.&lsDr4v2 W-y3 ӮyF:9B}8s# p v,ƗAwnMn R(WP^K }VYJ2n(* 1:xSǗocG<\ 9sp3lr/fZ.j_A M u [^G@@l 7w|>]LZ]25>˰\vBmVv;ǒ71Zb8 Ճlv =QGȀЉy8_Y2K1ǩSյoy L}dnUl(_zkX=e>k?I b#h4Ț 4#l][ш0 R2HөSe&,U3Z@$Mr FvP:=5]3U_yhkl-Ǔ{OG&&:C_1M$ o n\VPN++_.tVe|?1xxs{Ge&6y"md*.8XgD ֺ),,J' `K!Q@6i!xkePay V0ZA4 [=gJR0xɧBXJ!f ݧ¾:u;Venxe``%^N{;"k i0<9ldp4a:I u#0lwZ"  F `\>.$T>]<˸+Zx%i-D o`Yl^ڏSR!T--R1s䣌a4P˽6Z IڠyXěY `ׄLqc5ڝR]<-SNbssjb0u!FY R4MȔ|Au%0&d&'#h] 5Dy$+ǐo,PW_\cz/}3%&װ~E 7)*M$bAp}FKHx#no@vz=k]m-wA܊x1"Qh5?,~[]|-C}Q~=Ühd0K$h6H I]t0(M1@ W2ϡ@g(0R1X$䰰@9cx\ <Xp&]MSR90 뛘% @'ɞth5p[F 8UX%cǚ, *sni룏r4sb}Sol ,M 0S3 '`4_-&..l!|Y\=,Dkݕh.,# mw`%t>A>pבO&d=pLvWhA4{LC3P|ͿR I"pt;n|)/dY4fwW'zTsw k&+_-w֬rq|0NΌƥ ! Sfå% qA@k0*)`MIw`#l\ͫ e9L6H i( & 9v`2ot0V.Tw^N`'\\y"IR^?okUo5+7DkYw)I~Bm[W M' }/P`̀Mz__fחW5ب~cþm0ƅ(KpLL$h+Hi>Lg-'Kg#d ӄu?CjCfa4"dn!9N1Xס)c`ҹo:t !7_l(-"sC,K`<@3}7:Nwᅵx4k'1 01h`!$42Eb!i!m`BiDžtb8i tq io Ig"9@gj,#L/a JA+_͕23pLEvSo}`X-cDVz_Hl~M1w/v7G,@A8/Q% 5e-Ldցl ;K`͖$ǫ/ .c6|K/_@R:|7[AǍƖx+KueMyvpDKWG.S׮O<B) 3#&FfI 4aeV$4uQfv~@?5cjGy ֯o i4hhtRh;M ">VZ_OV:m误vDrw &,faEarZm6@)ߺ{Nl Gi!E>^꾿˅,zZ2WsZ2ɞYXً+eJ7 ?Tw-L$IV*lfT٘0 t٬- L1f@Ȑ!G[g.>N~qh.S `.5!sG d=k>9Y  /A=jf {h( 8EBX'VQ"#m-f~BP#O}d沣Pvx‘v^< o5]FgF߻M{Jv&Cr နIqL'ZOe֋eV歟^\±)hV&E心R-BnGǮMyji9g4_r׌N^a #_AHA&j.s[޺eNs\n(s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,?rP cIENDB`OSCAR-code-v1.3.1/oscar/help/images/dv64.png000066400000000000000000001710331417327530600202730ustar00rootroot00000000000000PNG  IHDR\rfKiTXtXML:com.adobe.xmp I: IDATxymYgӛzz=ҳFYK6f 4 KT*UILL`8JhrIH-<^w眽>1YHj5wuݾk}~߰vnmmݶvnmmݶvnmmݶvnmmݶvnmmݶvnmmݶvnmmnWzUK#Wx,/+=eo.w@KDRʨmx<oz2 ~l-;o*M J) `8iva</S)m7o3h2YXZXƓ\YFKpe2,㕶-B~2tDdȵx<^(MQi2lJiJA@hT~*,V FE)򝉒Q4L%Z"PY{؟U ="Nf!}IcfbMI!LX@VVسkf騛{ӟ>`g/FaӶ \+Z祔AidrKKKK[F\NYXXX\ƃn>Fa;B6CK4m FMSh8"4͐!P[0S)E%"0`g7B#0i!e  u3Р0X*GfA#*r[B2WEosgՔgecTasSEQBDD!볈bz +g03 `EABU!0:vǩ*nOO|ҹs/eC]w̛O9zCGOkޞp4\ Tb!j.J;rePQc!V+Q¨% JutI :Ip(a -fE[ b?S/+יГN/Ah|؟nШd<l"e@;WJ h&?sF$J hTI fKzN?o:fZqi7K|~?|w[ٷwRhX UG D+Om0u+hĪ \sh*8EZkuv| ѪbrJbq: (y5B¦[$N 0E=wO'x:>" &Xv*$FaPݒU*(LƕFބC{ffP![g b2ܘkQee̫i MF%U<hAs$ u[KR鲘[qaBj@Э3'5aO. Yb DOJTlM6 sm,VPРDž>v]W?vzB+jN>m׼暯i_l_H.! A@S(>5aý#] XWYcBd4I`B}~w mND!HzD4|@0np : %Fy5dn2FaO1_c "iy@hR4VahXҜ+jI"4͐Meepڙ2))]yV)D;nÞV~80t* Lgl@*{(c*YgƤ. ˉ{ ~J)>gR ]kfc&"c\tq"pSuؖCK+{ nYPcVx 'y\BpQߖ`ad,xp$ƔŵiA( I&UdB(~x@(*/ZD.\`H$(nuվDZdMTX\JXnWJB,(:[sc{ `0o~ǯ\wN4 J)X,m¤fvBN2D-$gJ & ʆAEuFȢ %ʧS#5:0ۋW(@*[G s1HO( Y(Zd~F-бH0z2͋ȕRE6i T6~N h.7|û~'~7 3]/LҀݭgRd^)$shZ,@@ܖ!:Q޶lxRP *Lx˗;Uc)-$) {;'FGWٳ _IʰESLs!7k>c d"iD-xL|[I)+F%[d,@Z,VIR GllAhV\ydX.eqMt4kkO?L&9oe:mũȶ(qhJZPpSf] m T9* 2`TtbdaM6so~HBhu3mQ`t 5sDC;'J$ jQ=%?FДE ׸/()#4(H{c4YKI61XJiBI!.\WhP STwm ^wݛ?k׸浆ETMй)TXFqSl;5+TODT`|}[hTx8̱9(=Ӭ*d-XeE(@ҪR #PSFS -QȮ}sRu$= v-y8MXЎɧ jڞ3 혠 /Y%i)%3D(Z$LFQ`۷]/'*B5ɟT y2f\U3aS6HpU!Гz=\@Hq^!$fYT0͹(pBI`9KhJ Bo>r+*JoB6Q!4vX x5^K(nmD.QKŹ64Ғi PFJm r>J/r|jC,Dr@ مue{!׾G?8p`]Z[ۅJ-dA+ ƚ&*uӁ`rԓhaWc3C@y 3UBvU"0fMйe%,Ca4eg!Ioht ʫ̨#L.^MTd[iV!SFH ϙc8#|om>Hb|jV<  иA;;f g %+dVXvY:`ѕcDчt=hZ~LJdaϴW=7Y\;s@"*!=L0WZ02X ز \$R> }ooӃ`jqy)푻vd4hAxӸ%FR`UodvsDwt,11>4&qsC_Hl Yl[n P?޾)a+͢j Jv"A_q`CX3l;fgg[!>:W<ɑq|=y[saȕK 2l$#O,a21G!rAGb9SaUjslk,}ح՟ϷaGEhޘ#KDɓ_=Uxxmwaae/TE-BU0Ǹ@'zC܆`\?KfeJGY"SStNvf>G<[up.B 7I&A( e7! geA t="bRUDOS4Q5%FHxD+䔩BƏYA;zmqM(BCm{8W+h47xY{TfHV%YF^!b@BEf79c[?֤Os=&j=) >OOyu.0؈ EϒZ6,hq/o!MQږ4P.ެBC9:`3/3 ӓ~-Ahŀ)PtӢ#苂 i|3#ڭ(2e#i暓',.}If~*h?s{k @隦gl4`Oض#{0bo63scΦYÕEHq!` LɄ6!U DY11D;Qz[1wCrjOZؚ"I2g\Ҙ{"R; n4Q⋞A-wh{o@Ӯ>ƫN_{9v PZl"`4b8]z&\5hOMkN&9u7|gY $Kcڞyr%cg .0-Z/ғ$L Mqc/ޭ˿xHهbN SF.QO+x)Mǂ@l8!W xbJ- μҕ+O=7u{k&IVkՊ@ P34kA&DR0--4HEɏY~:?O Xs&j1"ǒcׂN[Lu()(]*P:0dgͦ IDATd,<2TU2?ݬf SՕ))4!uV&cBX:駘o`ہ\8k(s+`uEj.H?$12-{{ᦛޚViZTZ/OVc$))dGlx%W,O% Rc,9EOZXddY"' m^""@* cO*=%_u슘 *b-lmC)AI1fT<~P]#p BMQ#no?xX%Qa۸aWgnv"^9N~#n喷!G~u]د!_F[Z,L<[(ink\;'JMp07D5" $=0 \1E++TElliX!=EnQ hi?]?2t'Ԉ`l#b:0+Ò"y:*eIc1%թ/AXa<¹'źpBQ]/Rg{}O>ӟWi+ }z*3;0c^g^)v NT@Lax(Gt98a -e3i};F4xS"a88[v):]`l0G)Gv jɩO`;rd-peTr ThǟQcN$¬w8QzBW- 6 39,yiyⳚsR4$c' Y_;t J@ odJN(i|=~痖᫴͠iwyO`ͦll6c@H1(t0mՂOS3@fT;(civ4.E!QJBCP }7acLQ ()jCX9܏s*Iu~M>o3!xCjE=7с,)绤M5X3Bd*Fה(@PK(92,S|3UǏ'_3RAKYv0xdJcڂp쿀܀U!o. _ ²5J,e""O we4*8к }3r!3(iH֌wȪJ||Kh9W}kOuʕgzɯBvV1B+B*48DnT2'. Nܠ 2,";WgZL _ZŐePXSU!pO:w_>P+ӑ\>VĐ8E(v;u-x/b'L5c5Wt 2G^CNJV7.;k[tTZ<#ˡ#G@ώ՘p+@Ui=ݾ= tLz,T9re?D/mp::R#4P2A΂7Hi梔ӖOVkUh xP1CF$"Fh\`U)430R+}WD[1;,Ŋ(3: >*Z$a9v)ށH8WEC&?9!Q+KZtC.nAϨ3V2eSi,J_@}*Lyi s 3J,A4%`p>Ea:Vء3bMr@{ߛAdbtgM}-fbKjUfCz|1/)̰ r^"NJR޵z>&Wl ۻ~}^Wx+V^;Ԯ=+f)`aMi}rr,z `N@c@`aaz A;f:LGJFUa%yPB>ݔٍ8vh4ڃWpk`ii͇ÇP3h=.' ~sю| fUW+G"MxBNt}MW .>N g:"8ԲABCY2,nD$1^L@}oV>w+1= Ca瞈8Js@ Dp#bqn]A\fN>#91؃SbYdD7=0==f؄ s|Q!%zML(NxIwN ׈Z>!BWҋo%J pw"mID],x%6~$ WXmέeerIǛ,)}i't_WFV$*dʞk&R0Q,0)U uYL`8Q(5Ka^1HN" Z3zKC`.F>m8 !g(c/ִaV` S&0n3#˅xeE%v[XG-M\J;W$T(5J {/E.]@ח_ Qx}:7zFA㨸 d'A3 - |wo l*׮b[T|Y$6̘Φl<~K`Ɠ(.8WeА-rZ FBd˨ 와04Ў{$6B /[9yٟ5$${l?70Z?T,x!%J {vUwDZR׏%tĈqROd>qHQoҭ#p4,f , (P cHKf;g}S:{PbeQfZ#\#}2Md/M* .a,KJL i:u 3 * G? Kˇ j.]g|&dft];^wI*O8h;5$%$.f_-'W{A/y^J ɇ+`"зj~a I4 P@R>>&rGctєZqޘʂӷlm ?@hR#7ށ2&P* fv_񠱝E3L0!CCPiՓX7 9j (%3HQj uOG>c<O^1km#Gh-OXUX|xQ g+pIU-9U'5#*ߴxrH8oGXvD-O\/+b zϰ'ov% j0hIy*=Z5a(E4[P<|ˑ|L3 8DPr̔qY(~ cMS2(.xv \L":rЁV|{-3_8" m9rhD+'4M4Rm,~ m>F|Ӥ3m[~i؟*| [HY< B<ǭ W i w}}ȞANm$[}m8Z& M Ia19=j]U>we#{4 R̕gPU$B*nLKW̔;{ϻ|@ LSNݴU}}@pA7ͪ4*l)pV:oCgN>f'fH+0EgS60UF-lovVvb40aPΙzfPt3N}޺l WX:)R2Ah? 6tn:x6?PEx6I6L' *TUB`A='倌jb}=V,Y nk03sx^OF`Fͺ~z]$UÒȽ tOd% |z0fSqey_T5۫߭19zǏ>LU`!ͻ N7$ӂwO|>J8*OLvN>m JS=zxayi {.~iJ\SOt5:k0)4(wVfdKKVBp_=Qrۣj1u˖@oY$X+g? p"S`&I^1& 6B?|p!*΢@ -GNNC aX檀#;2R48'hJ+DSgv!!T 1z~dЙ!_]1?Bđ#GNͻ<W >x{c|mo>t}¾dE۶D'4n};n4soZh^F~Qg;,GXR652|g^>HᛁHgI쾤n"U>eؔ OI 3O=zqosKz~?:ɽ vuUξxR-PkHųUvF+=0PvdK1nXxQP^> eK:pp?1JS7r{._MYWSFl>C@wX+c>2jݺV%k4)@2RͰu%]N "}Uj=!risؙǐcp#_?rCb )ݝrYsWVhf4dGT4Zjm '?8u`Nĝɷ> V,?ʿ%(O*],x@郼onu@7Mg؞Muo\| 0ce8} {V6vh/24~bhI,ރ 9?3?3…_ >ݼn3*6S jRc>LµU'(dSjfT&ee9`[ V/W&l&i,ݬUw20,7M֤"ҙ}2""pY{krYa:BZ,6Cu3}wae._|-D>\ܨlTA) +Wݳ`tJhl k **905NgX谾+z".]K/c}m[n>W99A9t@Z.KC`FCD4hf##8|u/{jehj踫owM}M]yfe0Љ c%g ة D -Q#bt7ۀXBUTb&ۺ*Sa(,RQH?O(EY%2UP6 Le&ܜI9Q?W,HRFφV`1 9c:X찱9l߸+X[X|ln3J3ՙOU)R0ܡv?~w_w).yOdH K̢$YPEbB};oOW7W4+hQ+a0`[L*ѭ.8v2k Wap-|h[ʺBb4WRt榘U5t +WM)-3$c" N“Ӫ,YU NA>h*X,XG%mPFhlAOObU~w`P+c:.^鰾6:.]<+`c}\ls 0odMpc],= )hncP* IDATԭ{,^򪻎+}!|⓿{ކk/<'EhɧA'оrq7Jrq5{;矿O~4_.1 Tf4ذKi, lG֠qGs#7vf0xZ "Bk`Xyg]k3"Uוє c"p0 P7UbJ.Xn M"W5&@cE Bu#V̻ͭΞOC$X 0*=gڬ`rGo {NՋbvԝ)Cv9x#Xy]ػgt\ 2-et  wZB!&, K>+W.߇/aCΖ}ۦ`8ahgD' 3F 5a,y-XkM 3޴<9>{{)'폝i0Y޻M߽I0ϓ.y .kHdia Y[RHf?^3Vģ?9ŽO2.otxasESlqjwN@t|Hi5"ҢY_S._4xX|T,"?|eʼn{t {NexUwkH(\Un&`8{ȑSZDkʞmb߁߽,MuX&q]&04ڹ'ը}Rb}p$(++'Gcq<&9૥* Ř[9G_O2 )ϝlUq0*He7[0fmV'-! ܼBLqij_xc#BFYR&()(Jp Å#8K ڳX>vQl,Sܘ;LױGNI߭szin 2ʔA @Ǐ{?/+B,/k]v*a2CbfdJi0жn^H*3*g;L@(k\ӈW_IjhUu D# fD2aZ%͕1w9._+Xߘ֊a;6 4.I}NӠTVJTHi:e<`/_B} H "gƛ 2#,.g0Z܇m>Qb:cѷa:e(XgΖpk/k2sGӊzknzӔcw{>Zಲ=z_||6{5癉Cnm1R߈fQUwIi Tg[LSx)2ʊ]XOq:ߑHhye9a{^=ivffMoaks k\| h. ߉~ M!nܭvS6D#:Y5n 8Ka<'x6!E-Ё>!)x!,--ӧ0۞ӟ}ݷ߀xǞ~.]Ҿ,EH]һ7-aOh{O܉Kcy>bS N 7FGIp3lLms?Yt/bu uZȈ~;P  BSZ|>ssp %TeUK+Դ1#@FF^xCMe؎$#Bw~w~+;I@꺍2=ϘFKpfu`;sǏ';! 0EgCC! P/ɡHD1TNb8B xkS\Z-\Yu]Y:6ױM[MׁnPЂ. Fހ?ӧނK߄78oExÕf?`hD&Yi Y-p6D`MS;pZbW8ss skq?ĕG~ ujPP Ϩ2ҳٿ-GCNZ΋\4|<8s4:|ϣ)㧎 w 3{-7_V@M)]ppgNpϓn_-/ǥ ZxA΢Mi܀K eef` {Ќb0XB;!h1CCh +hbl*V7;F '0wN4[pv(\v[~ s .]n|"w̶gE9 1+o@@WDaPxea8_-` x 6 U6*_g…5<||bOw@T+e D@b KGnĥGç8uf #|U)Le83 T)1\ùgoKp/԰rs *;ogy)̩ԩwބ?>;+{uqKiB[N/㙗pabHgןo_r6領fj'`eeed?hQ4hR uH'пn QxP 4~lԺEծ):lnl{olU/ 4-&G0\hNƇЌC3>vxa<tρ6̀Y'@-6(]ADŽH0H+SE , GoqV6/ v@p_xmi C#?YVm|Íoz[fu|-)jWj.b`5Ŭ@M^HnUT"ݼ`0@@PhAN?i{>m8,A J+Gvmot]E 0Z| <$TF̼s ֏VtZZ h>aKΕt5l=C'1{6"Nnv0P1۸3{_p@ѝmc{Šp<ȣa%Ң @Tp⚓9^.NDmB62=cq5x,3ƾwaa@iьi4Ctfa;¼] 00hZ mRJ#6QgR "߶h8B[4&,X `<C hnY9"wkfaL![%lMT ʠgt񶷽~w?"躋]:}6760f gb:{UX !ME6AOoIiD˥o\B}.CC\ubE-w8{1gx(gUD`Ƀ=0BDؾ kca),o,(gĠvrF {X[<[{Wo蹛MvHQmRH+b,F"#I,e Ác$H N$ H@A 8r<ñIf~Ýsci1a߽g5n}6rªx2~?^|c[o3"/k^Ҩ%VC~}ϡ_,2xuc=~_yvȆ!O҄Ju',ezOXw[hҏ\wCgRdE9^ͮm!$KAUd!n܌_ԜULrJmO>3g~/_}3\]{sL)#p-4cS[" gy@c4*sFuPw6,85k_atNTb{V@sp2cv@:}_QE`y`@08g~:| ūHy!<,68_CPljksQE9'}>x& kfF[Gَ_i8w3CCX-Y6ȦzUH "tDx#?{/߉# =aX)֋ , ^[y;Jډi몇Q @5ʢNixY2zSZ̅fO}( NGmf `;]gEG[.j@fFF $U8&Md)"u 1IV Jghk Hf4٤c%*2읦`bnw8<:wa{~( l!8Hwa0|Ɵōn?Gq縺 a<-X= Bjk#ՎZwKMEb+GmnWx͟6 V7pc,V}Ey9 ꈪQ +r=O;|ۆflm@TnTW 3[0kL[hnׄ)ʻP~=MOsu-PրRcIM$_`џyů|}70 ǔh H\j;3멨R^>_rBH#:Ts;ZΤ c齊!* 0K'f&&B3* )74EB@{Ny<NON@&XmB8忍W ~7s{HiR’D -C*3<(g` #rQŰ?sU/03ZdF-է lNԁK"+N\1j K2S#  aO2V#$k*; B&]zg_k-;U PdJC[\>x@[A|¢_q+= a bp@) :/ 3!c̥+SCҲJ9M ;:V0 4 jFU1mjuVŸ X7@@r ,E=JP/ޕ7t MPYkj5zmtd)\C^k?OWvBH1#zSb$T3` ș9 3\89\TZtʮElPT{6j5g fct#(P:?4D؃=X<awrqDD 8M|WHq:%E"C1#eͽ>[ `(zJ+/USQ7cH͵[_hi~i6(B׾ӌ̳`7z:u9 ǬePEX&(?K-I & m;'79_:~G~7?ٟ_Eeoz@Nz=Ō8aoG{yNQXH1" \_W 1ENTcݵBAaM c_GEH sw/aq!l[5 Vn!j),r{ 谳I#0FM^1fh؜$e93}ȟ{E~y9%ppcǃ&uB [* !cք D5AOMQ1 WIZ׶JMZ\ IU=gxcEx",iY{!8q,<$fagj rnĪoлE8$RǯgA9nm~ȖBE]o&#St= [ya%@lڗ,@-_!ߵnhޡU@l EhC6htʴ@M*D _|,%OF2GPU)1BHp.h BjC_xK4L%hTR"„iZ- NZhˆiBL/矟. f2YNR@#J~JzIS!Xfl֧nD6!hR[f5&2&*LʖT?vq~0 Orc C'((Z i=mYjIr/v4--siYX󥂺z/ʪqAA`sp^6HWv>pb5k`63&,ua"̋ylN mfnuMkAgQZqB9SڼQar/1akZ<"Vv ݣ -OU6SAVSFPSDj*2?k+7^{M S121"\ZMCzi}!4h-} ¶vznJYKFQԭvzG~6)t6Q* Lg}^kf2}0Ab1Ƚ%۲̵l̼b2ҞXdn{{"*Nz3uHflz%P篫J+ turמ-q~vU{4;EJVH'tJB&Ò\H`1"ib1,IXwϯ&B )\7|3n/c&ݘu )F0IS gUL:{'hZlEa5 h8 j`<" t))uTBld6Mg'۴GM4B1ee 0[ݤEV4C$-dBfPuyTd7h:5b^& %U޿2k{ I,X4ėgע]{#=Q8@@jgp1!XTr9c% |XtTR>?z\LH%u"sc8!N#4"vƏykwB+a ?mZk¤ڧT&kZę+2(?+ov2fW߶csA@3b:X;5W!,3f^RN*MSO=l0g]٬`++nս++O/.B}}f~D\ C8[Ь􉩖L[, ] DhBrmޫBB^! L1H:R"Cl b=${?O?/~:?;rn=أ?_z;9agg;8\-2sa\ 60u{R4R`eJmu |jji8+g9#8wX'*d^|Mt }9u֜lW=6C'=uv?rG:N^SdIS~5uE75%{ [84*lF]e$=g:W('֯XPDSOk6O)!3#" )I Ƥ'rlM;$,轴o'[7oݼ؄嗾_F#)T(! 4i*L,fF"RLpR[xAv أ3k"* ̴'7B+*,Qa2AW Cf6p1LX~YeaFܘ=LtnF}ln)[x8UWs yD\jJ X=E/ftXtl8}\Q%OE][O eנK(̃3kgd1YHPMljֱָ،,7XW^Nl?#?>o+KeθGY$@zn%Уi%пF}~f^64$ЋZێM0 ƣnIآe3%yyو])3F{KST55ݘ.׮wn J!{\}&LR-7 @|\PakPC ֿ}B\3gW3-jA D׼*saBNEGRi^bu@ l9L fgC9%pJH)a#B E˿b*XݢӔvB!|_??'y<(x/t+㶟qBN#A5߬AzRY YȖʽ:3]g^~Y(~ܬiԱYn(KVB4'8rr]W's/*Rzm!BayI-cD!l%&D1 m0Tg=bb:z|'?x\bJ̹ƑHiwv^6PFBYO|~D[\Y zh[<\D^=@?;5"hZ 8V5cDƎ,]ewm}ݝ! aL~&NSt_/B.O܎G8讃jRB 2\ɹ/M?ajE4_WAtPa dƐlS$Tt -Td1 L1LH1 ÿ́E~߻^ 4;F˚lKpX,O9_56GR\$kCMDs tus}fVk*BZoxQ=M9VW7h\7_Qjۚ`Xy3IϫQg\ɔ9YބAu&00#Ea#4a'q"ĭBDоׄ*9J0،WXwN:Lޚ~#G_d_ Ui *s`GH/%ųk1aLR`wWF v@$U2P,S;Z x@f.P*3XKS n| U`BQ4o8鸮GFP#9Օ}3E0펙!Q.j@S'$NpH„2\Gmg] )!e _cұG'%XtQW(=OZ.ʢQv4kR:(6]!&ל5}[k&lT0E;Ftm\Ulmueh.׊ӫƪkS_ޫڍmxgjJ9pyu=}(G+mϜ;۪ݷk8e9o%A ף&䘓.sZ1[Q "Ȣ9gpH) [!!E0K6T5wDR&l%B x:O>۝>ϔj1 Aq" ٖȶ!vj*h%՜I!OdwHjTMjBe@j~uM*RQ4@+`y MeI*#Ϋa(\ǰV_{˞ܹUh@ 0=z}桖oWk)*B ]w€j7ȫ BƜ銭!0.'Q4T'f x<~d1rMșKzo Uf}iGΟ0/ =0M9H͢wr1]pgGĿS%K!*a&0B /l͔s9@*W^ku?EW>feC,=lY1HA\f"V9\\X 5%߲tyk>E08\<9:^טt&Lsk,  sAhSn\|\hr.=FM QԌ.'J!y!'qh/}Y(Pj:r,7@X5 24 Tx0N,ub}S:|&&W;K얓@q3lz*:ꉳB1 T0q@L2gt9hP(  9Q|K%7}Z58hp+_6j"1[7cjL S[B@AJ-5 f܆bG]Jˍ6@R &MլTꑔ%1sj(z8=VӝIMwղK=^hNLEھ2M,8i#MDҚ y|`bX!m:nݺ t@5;~$ KQzg(:p臲S9=\!ʱLFV3:ԉC`(klUwP qCcC*Zra]=6Jͼokʞ0_x֍YɞU?8yMcjwAa0ld) U+d98#I;%5!%iF̺Z%pN&A[. 똔[3ۣL) 9!C@pyg*k,B6Mi^ IҢd]y-g0NZB.O ޛ " DdҾ<q9Ѓ2딒2ibGfF9$Bl UjVL0{Vi(N6'5m#vƹn-'PLNu@w&w^UmFZ_.EJe%G Zbs %4-)OhζZy]5h9@1MknGa:8-`tJ56,sv O-_ sEdAN+'&f(n3)4GYSY~+l%7Ϲ Z8#sҟ 2|g_e^4*3RH`U9Ob |r{{Y0L2-Raf 1#;J;#$1ʡS;Q%1>u6BdX?oZY 5kYHֻ29II|CRVQV}H*0CAtꕧ n%E)$lSBL%e`܌7) $\'tp;&U IDATG8!9К6N᪗Lr V m@LA%N -[n [b ;`L1[6Kf5x\9}!6܌ QQMտdZ+r 4H`%P=ET(hF^9k:'Đ0Q@R4'-B-ϩޓs9Sړ@ֶ2Lյ5T˔tkhT1T e:GsTe=-J s73A!,JB%sLR r,-rbyaJ!{30ϊcJM1EMawK!=j*, 0s]P')(IoWbEQX71}M0鸥c^. NqtoIh8kFc2T(Tv:H1n5SE bV& j˿gF&vs&7z1K%faJ%jjj0zizD[AA=7ڗȵE!E4ŧYC,u|f_*uM8EA p%ΊIW\)- )UpGI"b ɎbWKLSZ%(HE}(*sBiA TLxVU]QR1DB23B *'E5 J5PoԢID _w?BFѬ3.B!6ÐME Ǥ4Q T}o\$ $MB1IzoJ 1sh<ࢍs21٘>8{8y$#C )- ,w `cLc{ U PBTNVeֆE8PAr0F2t tzFd J:oD !SUa9'qDw;_Ob|Y {qqqxzrrM6w3$^Tb1)D#R8VA4LRfr{O/S5JRk ڗ\ْ?Rex/e,L9sFAc@N# 7!l:xwnȹE.r[+Qט05OCjܘIEZ5- D)GiTİsCqXd0(KLDcyӑ BW h}}HNQ!1%@\ї9KX]gJs%H-eT[:P3$?"LImM̲l?gk! uO@(g& _a:z31ZXmkf24aG^(󘶶b2*dB)ƒ:5_:i$i$ǨSmpF QFD8$Y0rZ*WDaG'.N0XPGp%p5&JRԃje ճZ20%&I Ċ,<'=[B􍩢*sV m&%8?}G8:>­[я ~! Aj.dnWGfJ5gCYgY4kaфf}nFEDXGQO^@sq攑 wu`pAb"NjK_'Lo&8*.⬞;Wli!F΍Meq V"h]SB"Kf2U{I퇣0qfGfp `"gu,ZU$:DNs-T^;Ǵ bzkf8xZU㨝n&@vx3bТkLjj&6fX $$(2\1Y&8RS-m88߿cͦv;H)cB ]jGL+Ɵt1GkNE6sBvB/_Jg;u5TJ[bM$4 N=b8^9yH S Xsd!aꗾ)v0aNN(suDZ8F떰C%)xcb1KΡrf3#L[\jՄ&İEN)Bh'p8"dJYY<6ڽU2yE ijr)A3E^q:jKLru҉cjɓ ?+$Aj]W#Fgj#XY)!+eus+cY}$9ELa)pSg'BxS&MrA\\Ƞwkrs-OR#7) }$^yxG:_[a^})7JgP"1_*sapD\#)&UnG/Sg 5[kofl7[ĐLu@3J FD^JDqux"XӉ\BPY)*YRƧ@"T3&[[{Tǽ/I21Oq~qifkX Vf" KRQ챼fB9$ Bp 3`{)P{vĊ|\ .~QmP='e^&FوWfR3 *fNJQЉBv J ]?H&E8GxtH)y"rkU͊B8aF0"U:8a$D!̘8x{W5y vtKK .I.Ȯ fY"] XOYnO^ѐG>O\BL`)<:Hbf;!Ą`.h]Bш 1&1,XӍ69|t9 |!猤ȃ:uG=8㺜sE*ē.Y&tFeDZ-ZډFmWR\F&peggg8<kV`r:a i酮v5n)"AN&2)Si $6bq BvR#@Y 6d prRqzSIhxUJ,ΖGdW "|z̃>bci%Ż~pqttsl6?IPacKfSVtfΌ2'Pz Ӕ1M (^}q89rѦ Ǫ裸-28$+>grԮ/k" 6pS j'x40` Arw i ~׶ !xHRӣ2'MUy툤R6'ӍQrI,i>7ПBJI;:Ss@!Z@AwQ6Pb%'-ͬB3rML vL"&FT+DW}Y yRĸ'8<<߿{xVEkڦ[#:8"Es\*gl-\LCH*6フo6 QM~>L|NҵS05t]l :q~qOpptCG*WZ, hj' _"dH9YE)&C~o%R>K؝$)JNx?t(قdg-2gt:Z@LXxW[fr$uύ5H[L'xr̶ t DJxK$zp; g]Γ5Leŷ3 NOOp_g8>9NON oS&fb!]rJH x Ol&J[N+YtŘc;Lcv1 /<K5Y&LLҎXdDiNB}6민)|:H,aK2&mF,9 9j݂=vo߂zX~h@n/oOc_*^:cj$rDmҌ@=~=7^>d=ɭQ!Z2Pu"$^~W͊"1{7uR+O4kZ sDt`Ro\ 3<d-D=)r9A{7]b$D%Ey9qY1H\>%A!H4l)bm OblW GV @ѝ{0aTEz;}w ͟aE!T1&,j>Hɉ{{X.׭˟g~vd^o%BK̙g=^H=^M4YspN\O2Ick7>i|9'i5DI1 ''{ݹ_\["p+ k"Vs+C҈LC6 S8!#rahtڨH=U1k'/ـ4#\ZNӇb&{q2Y9:B̵œl :}] 9p/}-8Mod#1:+edRI;< Eҋ)t(q̒稤rRLclFܼeUQj-u@5{v84:l̬%YZF|xp;w899/j#=&NP!( $;{B 1oLc2?/| 1掻-5Ccw1AGu@@Haw3KXtvv"*B2_x8_gb9` B |ttu~k0YGn:$^oZ$gF䴆D +|FݨCg6dM&v(GICR'@R$#Bp~qy m܎u}\0> goM1&bc|O"q;DkiH1)/Ko&n>`w&%\ANL!EKl.1]!ǭ ;˯g.,?H̱i槆IE2z-,=3bO~0u~Kc2ŝY/4!CՠY@¤^a)\n.ptx{wؿ8>9^Dsqٚ}㟚$9!a22) /}V)­ 6 Fs{{7ĭ߆[nwJe-vZ6x N^#ioWlhɭ9sBL#BH:8E|k[])L8;?1ΝwݿC~[ʿ]/"B?]X)1rDr)"La"fK_ ⭐_p> uRx'qqwcow;e"fmW\ad__b{ng;CxG?lܻ?:8U$X";Y`go@ ^|{~ pvRh$*f<$U*'Ij֩$GhIN S Xl9%_惐0B/_ʯ⢄sy;0DYqnjq Bf_Trb_Ў*'\? G=2K^}iaX.xpq܁X3ϾOmX.xj|88N.^D=4D|Nw<1_'T?w \q;Jҥsږ4g]H1MkUBEV„dŬ.A:#l_Ems}\ H})FIr4F$a1d\m#bvSI|`~9ݛowK 8=}w_ĸ9jaowaKa@Ș˟Ž;/|~<#HO N#־+՗rf[7j8~3?mׯo&x;np&/$P8&fKGq`Wz BeCbbL1A ZY;c|MuIɫVu8BP)AC!mvp˒k(ςҀ&3=Ǎ!ùx!\bwgۻ]ߡ&iu]'Qv=b>>|;ކg,_8b\hSTi 1vXw;d|_Wjfu̴e^xN [&$ ZR[RsXHV78Bg mK$YjX GNHZְS+uG )b1J˜c)<[Yk0*.ַ a  Wqv2C[7vJY G82%55C:<qkw( @9f=F ?-Dduw- Y!Si}1.!spp޷Cc!Q&֗菞l藨{8xd3qJhkŎ6.iHG߰ Ug%ܸue* cT 0VF+_kߏ:t1 Xʳ)O;ebRzva>_FVQ-2$%XX[b}Vdֲ\xp=Zrjk kL\ ̉zrg)_?MIDp0S="Ǡtعw)̮AE>:voUXU:or{u&qN8 .`+KKdΩp"& J,5cnt/iB@+ q\EVMnEX"NQ@J%󩴜䉺Պ]1=#ZK̎s|K&,Dka"?_ eஂ5X.** <O3[cGk]2 +&U1+ ͿDG秭 FnǦG: oX8tP0R=2:-fF $xD[g/c"|` ^ *Y_&8I|XGD &bzv$)Ȧ"cuX|`zv~<ϐt,ծA0e\.S, d|8zߎ1@K6a\A `oxv? e^`p蝷r?#|160 &"( XlqrS mI-bryUsYkuUx>c3DTW==1}vO/ֿ,ҭ{v-O` 7> 9h˥ LM9~." 乌/P _k9fjNtxPPWa>A^hvL7w?#nɶCl]&ND@R-ߕSR2ztȝKO2Zfu E 8"(89 !=z1:䔄dT2 @@^0&612,%PDk޺'~M~y$S(rBl]hCdH%=QfLfP7by<$ VFֹXr`b) cb_.ͩaM$!I~\#=6$UDU*lgL@̬ŘJUu1"! -nTv -!,o~c᠇%V2z](ҋADg{'˗7clɿ%"pJ%wPKg X"F}>Cܒ }Ggns`"ekl/c ʼLu|ʩ͊ #L5é\sZ mA8֤u3HXf)%K016y1<,zf%Mb\W@Z3d1++9>,w>} X,7q!h920pĕ팅.Z 45e.CL#K% SD3`Mp=E#-k-l8P/e~05u1DCaK7s* Ƞ6#ԋC|)ݨ\UߋKs9PGP#F 5pY{u,0F 3Э`e>o5 LVa|ǹgg73gapΊbdUtMȠl!xCEP<$G!iX!6/@cVd`#3pAxu%t_ cba5;0pind6CC1Z3CtbV58>4I7/C\wW/b֘=ymX+Ɯ\|٠(,A>47C`@Fd"gA 8-s8888b}WQ")ˀ3 #c1!axk" -9?>K(Hb$!Dv_re1K&p9y?,V/Uaxp{ozW!yF/V,HKTH|((A"&`Fr'=;G`@)KPd zqZEQVenQ|k<Ε 890zGyt@IZ SZ<YX@X YxllZ .3%H VXXG}k>Y]tv(Bg 3k7QG=Lvsvu=wS-!o$0%跠QOby'vO ARI)2JN!X^C$  n1ldl5l1ϗ? Ж@wK U<]*ZS?98No@#upJe/Q@]xfhQ+/|9d֤q_Y$XcPd(>xZܹw^pbdZ`CL"yozKUeLɂQf"q%jɁж A>`(^9ckuֲ~ȲEV[A.z{16rʉ _\<(BD tߒ BધYBF'_VF})qֈa+CmeYwd_ryA}ta؆XGtT\}қkKKBHk_Dz{f}@ a! =w_~|z!# EPğA24j@&Nky0-X}V^d]nspC9yxZ>VJo,2ZXmM 2d"be&'ݷ>.ijGk H* A @%%[]BDu4ERkJ2!\8]z}.y&3D,xbX]N&yFch. 4TLDxO@-dtHs3O#] +Oz eP"G ,Gfb2b0Zq}D4؏pzvN9CҦ4#A׿k=uNQR!Ӝa?(Bʧ!w@1\^ =Fk01 ` `kcu(EDAZ1x>l4?jY )HZݍJ{`zΕOXyè/੧6PK B Ix>*2m>@lW}bXӜ7?!s[ LȦJ4IE!-," c"6 ewUwh̄%ØKXkX,a-.]F  10/  fO߼}xfu6;S0ox3g%3(V W <{>s)eXGg,F# Fb5g_GzX,F=)>R|BؔYf0]PQ}0 X~j wsIc`J;X&Ўr3 - z^ysٹl <˱X.$[l{Wɠ_h8!b\{)Y)nvk*F_@Q>~_}yΊ":[2 XV L&y {k0 Dt:]wƼ `Fox+G 3H>XIʁ3800pa2| 1 L' p :BQ,P5>z!z@ 0?f `sp8Nr:?A=sFXUȫu'eLszC\ɘ kAps'%169a\B*K2K?1JVOa'ROYe:9 v=q~( =X>u@@6(EVu|9*}ᰏ vuD OgVeE.uГ Bd2@wa8,aGva3mr8 &[{"f=g>FVq+ؾ}|0ƠhOٌ) p_G=?dM@ zUd ;X?ï毡(Tv6 pcSUUb4v@:o~k㣫EYE(F" n)N~>iuY,XVYpPX]]}XbWYV*j&vƳi >ysDc m6Fu`ǥu7w l\y. gpv/A,e}ofEf+/|ۿO\6=cE'l똡t^@:4x>v8=9eN&6V65HI$ Ɉ0 P@4ơ߻ ~VUU$?(αgஐAb%:lnO#:.NOXΓu*_c}u6Vi(E:~G;15lee8XOgh3 GY!1>k o.(AGXACT0H)۪3B \AH+ZQUùV+V6.gلFΙ4%n.}7z|2>9YM 0`A@3h-sY 2]ڔWВw|>$`+l()˜il['MB@p*)XGBd(bkaumnX5y,6drnpgC 1rVY$V/i^MBaBh[{;2bÛ]Gvq$ueb1˲Zcl>ދBoC? bg46Ȳ* %Qr lk ER+pF+cEcI_Oct`ae[R{F"YξAAӻz@ Ed.CQd yvZ" 4vX3zZA믿Wr IDATx@Dӽ CLG`.@Ry(roZ`0]dPh@ (}=1a$2W,_HZB;Q)>b+&|~X FI l7a`lpi;So{FP+Q cqdN.W .cC??m=P^GUxKDξ*hm=@ƀ ryD^Z]hQx0^ZOɧJʹE`,b0J":IIQ@'Y(e, E,sB0rP3[~Vqpy1 vsp֠ eTz7C7_~;G4`o__\f>TBx@zay87 #t_HIJa|"! $_>EW1S*H<9AdK1I`{Q :TXdYI<କy8vGd>ބ- 8*g)OQL0k\,<zƢC2b&e2N4ש! ,C1F.6,@ Uyi_9kX۸KO`}m kXЯrS_k >ZaS ͒P}I XOOf"/ eY%0EF)CވQnX$Yp#=:O<*[G:7?@ao>_^ R̕B\c%)zVr86jc0ҥ'yio`}mÕ>2GQ(U]3oA$M C"rdC\O2mq&T1z@Sya_dXxTEp&Qwf5f~dvS[P3+y寏Կ{{qހ7 YhH?Бԃ{r'K  &ڶ'Xf`eBsFؼ6/?d5F#e* EZ~Xvcơ* YbJ֣nǠ*JubiEˁ hG/;Y`}G`gݝ U>`Kjb[\7|__<d{?pk&5X8XXX_PDȤA8 bee >źߕ970ҥ'p&ѯz_r䙃+lጵȬVG~IJWw "}n9g%DgՔSL1d3dsVcD ws4tC5C, G)>c8( ,@oWvOw{e3s2 Hǂ,=ݱED#V6DA԰1$HFX[ ~+2F\ڼK7/?pȑ| (:]lQ* RyG8 ҌA2 /9`CPnXŲ8{ y / KIL C"K}ct:Q wE Dgçϥ[!1 6caMhA>!"8CS8"{%'YYc4`8駯ɧxzek"-kC(8,N iԿA;7Zѧ;FPq_(y֚(kA؋/k|o9BILgMt(_yQ j/"bdU3iXm 2l} (E¢a~x7hxa8*rʌO,G^0q| Kpf"ҨcwɛDY, ShHT'_ItBpּ3SdCYT!bQ78OQ%NOqz:+KTyYs@90֢efQ0O +l<֊k#U%#-]ـ㤦&+7=~ tt_ p=CD@;u3&JDsklRgK788]:T kkkظO`}}B^n"2˴VqNRv%]L,cÏgܽ{e8::wb8`hp9~Iְo +Л p4z"h AZH5R} )E&$Q$3;Ec012ҥX_]ǥ5 +( 5 Jsyu)|l7h͠l$̘ 0YJ~,s5"k ~1f6=lCLSyzYcX◾m@ p.5gH( `2F 2+hAo ~7> L6 FU~~U"$h  sDد"s`_{DxqӇ"^z޵k{u]E.#`o{_G_\LӾ( F ЯJ=A@t[",7 b!pp.p\O?O\p0@]92޲|llv,(R9i(⬗W3<à죪Je.,ӧ)Ad0jviˮL qxzr r .p<]`I9s´X6|-r &bR: }aQD0PoB]1PI|Q/=bܴP<,L +['t%$J=e 9"=!0Kq5]i%l mdF) "a cb`g prg:4L@aqf1 a4(1ze!"q݋ Y1 |Sc41϶@T1p.0ptt~ܽ7Czt>lMgmǠI;vXAU%1&عbX[[C?@QyP9`TXRySR@윈Q0%Y:u69V)>ʲB;)uroA-8mǢ[X=EĦ>olv!57gPrCe$d*hǠʐ|}c >"Oky&Jɼ6 9LFCX반1_,0_AƄ^[o髯[6{d7?)QXQxZΓhC@4:H$Yb'ah>Ƌ7o[63?z ;xg?_ V](؂!>3ZF24dd!`CpMU Cx+r8k|SL;ғmc D!p= )i6> `++T Ec3,2 s=ȴsRv).uƴD9e\i*nA'9pf48P RмI8,s=5)*}$S5v pΝknzekKwvv7Ϸ^3`o/eι'NӜH8RBQ;wS7yؼ$(` ei=܍]?dr9D_zy{f{5foHJ2xmYt))Bnӝ߾}[orttti{`Bιz*QMg~X=DGIڪ0&L1yT]N:Ⓩo>XyO"l,-hڈ.tjѲ(PU%6-$zNU zNbkhRe -|rZJaЫaPڬOt s@]9& >1 M >H1z>ݸqoo}s;g[Dt_j!2,ȹT̷P Pl:G@Fִ  gA{Ӈ gâDY1*3#nݥZ8X).vv2LOf0[Ǐv*ֹ2xxzm)Z^tZ-'P"SgLMl$k 1Ie}ãQ~?QhĦYΎnݺڍ7_ܽպ×zC4: eDD^67`{]EAHQsy3P0,X Ygc~}4X=$L JIU,Ӄjcu]^Ϋ;w:>>.y'X;н{^ggq=H|$KkMA8m;Om.(3Ҁ&prPe.":Xym'`h`G!l6{ݭ7lo֭7X>KQ/<|\ X-ƉO[kD-5x^kk'rA΍I)&fX{Kjt%Љ t2 ^OL; ?ǹgn˄mga`=vW2jA IVZ!]` BX.듃;oߺyPtԭGprrNa/PB#|e5& ˏ&RH/k幻I&3M=(_ž3kb/pVvJQ`>a)RǷ(S(N iaL҆otR]@ 4Kcɉ)wΖCII!&JpC5ͮD> Մy@Qciy%2xzp{{{7igg̓鸋3IOO^^2"Ln'KbBd]Y[QVj_G4gAٍ"}vkM"_|c7Mԙsn@6SlRG#Ev}:caN AÓuXu浝c!ARrr z ANwH0'\0 m'ed#ʋ8rL )F!㽽?zckk5M}{Y1ͦ{E#,IhMɲJBmsMZz jc'8^獪l$=έe?tNVintaE̵T2xcX_nxSk^B|h02 #sBͧw޽z߹y{{Φ;`zd@sxtXvݥvRG"ZYP9=r!vt̺Spm)s&G {\L\()`7ۀt;>{h G"HMҖAC;ʍ7^;<<3wq_e/ԑLf^Eis)NU+B+)D@%:2*F&@?j!B4]!2+ࢵ#<dEk+Z :k;#F_^b|{k[oyo9vq>R^cQ}s'/|D'aBmtDVnߞmzmʩ1?,z||Jlۍ2gihoJ4ZcXo\z:8!%Rm=LRiдnk@.EwYtY _E7EEDlbǶ$˷IQ$c]̙T-:"93s(uҥmhZᒒ46(Y/AiyH2Z/\hR^GW+0!i3Fxc}@Aڅ?vsM t1kwMqvK#!4gt6 0YP ښPNkJr~}*d:iQa&g6u _s@v6?(-?5;ae9VN4+A@UA3;)ǔ\]풟K&)FC%:cCP0?&P2eBchb \*ZM4jد)'gv/^#Nf󚻵Y%똝hwڊ~Lf8/ 0"5BJ'8`N g{IFk,xf!ev{$M:!=zVݫ\J2 9[Jd`dϞ=;8x lutzPJhZg:r)8[[6}{o36)!d2N:Nztp8LR(K|? Vjl{y/T*uE\`x<x<x<x<eR2'IENDB`OSCAR-code-v1.3.1/oscar/help/images/fp_icon.png000066400000000000000000002211631417327530600211250ustar00rootroot00000000000000PNG  IHDR\rf MiCCPPhotoshop ICC profilexڝSwX>eVBl"#Ya@Ņ VHUĂ H(gAZU\8ܧ}zy&j9R<:OHɽH gyx~t?op.$P&W " R.TSd ly|B" I>ةآ(G$@`UR,@".Y2GvX@`B, 8C L0ҿ_pH˕͗K3w!lBa)f "#HL 8?flŢko">!N_puk[Vh]3 Z zy8@P< %b0>3o~@zq@qanvRB1n#Dž)4\,XP"MyRD!ɕ2 w ONl~Xv@~- g42y@+͗\LD*A aD@ $<B AT:18 \p` Aa!:b""aH4 Q"rBj]H#-r9\@ 2G1Qu@Ơst4]k=Kut}c1fa\E`X&cX5V5cX7va$^lGXLXC%#W 1'"O%zxb:XF&!!%^'_H$ɒN !%2I IkHH-S>iL&m O:ňL $RJ5e?2BQͩ:ZImvP/S4u%͛Cˤ-Кigih/t ݃EЗkw Hb(k{/LӗT02goUX**|:V~TUsU?y TU^V}FUP թU6RwRPQ__c FHTc!2eXBrV,kMb[Lvv/{LSCsfffqƱ9ٜJ! {--?-jf~7zھbrup@,:m:u 6Qu>cy Gm7046l18c̐ckihhI'&g5x>fob4ekVyVV׬I\,mWlPW :˶vm))Sn1 9a%m;t;|rtuvlp4éĩWggs5KvSmnz˕ҵܭm=}M.]=AXq㝧/^v^Y^O&0m[{`:>=e>>z"=#~~~;yN`k5/ >B Yroc3g,Z0&L~oL̶Gli})*2.QStqt,֬Yg񏩌;jrvgjlRlc웸xEt$ =sl3Ttcܢ˞w|/%ҟ3bKGD pHYs  tIME )n+v IDATxilU>d޻DU4! VI"YMKVݴq#;?::G8v0w1 2[`#TRj|t͛眽k흷pA!wœp_f޳}99999999999999999999_6|Pz]'d\~ok_kɟO׾ qo|c=o_NxǷ߃8q.]՛Wqpp 'y/~ћ2䞣9nby|18=w5)Ooi7@pAL%k;>֍:^=wfa,G/×^xߞ(n9zwǺ+^_x4?{ڙmwrr٣{dcw~ HYD  H9Y"%#H?%١k[aN0:9<:8 [&)s7m>N{>cx__Woe{>яΞ}… ~=^={e{~}Z!tcD R" dd AKX/@f5 *JpBb` whOwwLٳW=wy"6KA.A!XzIz)YD@ @v1%kd $|&1r_ IkN>VbMWL`3B* 8 ;Lpfool_U/?w}xl_ng2{Ip|!%41HKp"03 I>em%Y&V ,[`0@ԆHJrA$mЬ/VdL!ٯ$IJ~ d  &gw lwmv^<=ֻ>kw\|7<_,_:?^N IȮZ2>AjzdVdL E!VkVfA[l%<[?JC2jfCNj@a"@u񯯵Ne"D 0C lΞկ|ыE{Q Rq$H)! b{:.3U0DA,a(ξVቐ3,x;zoOM_7׿;AĬ3^n*=;I`Ѿxu>8ރ 8bH 8bZ4b 9G!I9 #A2Al 3(ke8+{}90ІM7Avv&3h|@J~qcªq}?B~r}ie<Dsmk4b "*ؓcg{ީoy}Oys|oC|b9)!dC 'D@{ 1 0 #RF 3fc&spsDgbe_(gh?X fw sh Ik0qSߠ9g }|rc1 H13CH)iXKITcDB+8?3ix[~uoG l_'|Σ㯿v[޸ի~"pL,ƖM<1 nj(bp\`8rDZa1' DG k@p 8_#AN1[.JҫCrbȽHb s`v`Gv؞̰ qx~9WhM+d)+P IJ8 N}+_rO_9xޗ7O&σ>裟-Gor㋆ 9&+'g`{`7Y7pch1Ǎ1 ~4p |Yp6T 1;VAcB)#匘UdJ"^HJMTDMbl:6f‰_8xB3i .? A=Ɇh[ L3pH"8ܝSo׽uOlM/|3w}s~<7o;svԵ 4l|@L¤*±' y4{7 8>j< &1 H Bv'Y(=nٰԄU'Q9Rl04U4DDkkK4&H"8apڀL'3 'jc>_xBVVYk0XqW%I[io_WǾzo??_Xߐr/p>?^.QU,A$q$QV{<[%) q8!9R9WXUtYrB@"b>C9z1-`Ab8cĘ'*l8D8 PT I9C`?AG vw V7qZ!Ee31 1g DpB#u_xם}r=>5c/\m٨ңXim3Bև@멸R}88<C4zFՑv@ 6 %׺:!ZqӞ )%a8ѾnH[ml ^+& \! 4FEP67;[[8=x9 [/HAI2ZрLMH:|f̓;[;7na?xc>O>7 1gs0Iub BE) \qD3D"8ݸ9bp|;{C1g qDzyߓZbP8Z)UG`zzDŽ<9!GZ6I2c _,py`"%q$P3hjzG4ؚmcN -V|0 ڞ(8H503i wsyzsݿ oo!L_Їw\yǫ6LeMQv2[+#j@u p9<_ġ?!uu@JrIvATU r6MQ`';1? @ޝ@@;Aa2RT,-QIe’5R ױc\B6G{H1"c={4lvw.t0D x$@*(@6\T5w߽ޯwm_o;ySQrd Q  h%<a+7nʵ/u;pp Y0Qhx , &Q:Ji}qx؁C["9BtsLHi"Pa@JY$T5(V!HNyL&ԑZ=!.x· p%"4$lxH5Μ>X XrL& *I[/b(1Wj#ӯʗȻ}ds皉yc{-_ȁ:vJ F# R?suI6=\; JNs\͛7d Kc5fA6':W=*NJp=輾fҲ]w#Ghmb̏Q/#T"b쓌#RHCDG lI/x m7Ah:!*fLZlӷ`{0c *R0F+SHIh_W~W?~#w IDD1 |༡N 26i!jGocB?}iO1#yRBJIM3IÈ_0%^bW#RԠP b!tڮCȑ;4 n Nmg`tX c߫x(WVB7;ozds$黖׏g]5ZkXw=s9l&5R4]iiLj ׮• rÀZa\Z~{gn<cp` L!7V9cҶtcu`5:%ظ* j@:J PSr!1&$%YHYa%c#RۥˈcD8"c? b1֖{0ivtlt [;;?S }0E '2gU5j w˴~̩[C?lK?S#;pdNf`WJ2b_@b`Tkh?,koŜ".8+|*V9(g ~@{8E3̱o$sMs அ <{Q];A$8 )e4-Ɣo1\ph (_gQ 9$@6z+A0@bNNt(JF#1##$oZ9q@qLHyDzq Y,Ũ{ &[[hM?s Ok[$X N#ͳ_ME[ 8=O_&|Gy?/?www-~#1+C@sFu )Z78\t\8HyD{}~DR{ @h}оkf6)]mqèAI C2(8oV^VјX I &AM y2Q _ALz.4Nbux$%ŌU^?OH#~TmBoҨ$M M1BΰwvvwPʊ;b?\MP͂Cwv~ޯzɏ~wREp_+$\vaXHqcTLAp"$t:ì;X-J&mzٰr+ 1cui XDCuBzIq56T3q YA$Σbp<= }a ǰ@8tg ӭ4)vඳ`5&I.򀊠-jMr3CpM?}g^W;OoG?z˯O>{;BqRV(,pA3e{ 9 x! j~@/~34mYz.v6#OZ0+5h/<P;"KO_ Yxs1l*"g!i=qaM&+@bp0m:L|Cqer? f': WFi&p! MཫgTw5xg;}N|7{[{z@W'g>!TcXmW*.ŕ&W.W C}zfd0_'~Ccu4G,Ny4]n6E3M:{4U_kec9&,)k> dLsQzSJ'pxBm{JSF| 3gRP FQ /Lэޜ~-hyІm [`LN6w+ R` ]gyrR6)LIP`N<$D㎟o-{B}a1.]CJ3!'fGMVj)_.MYI!60b>AY#B3[Y +InG7܅0yr|q+D;4mn{ ]7{1jYy B@JI/ Pm4zR\rU~X[b):Ly3())ŸH"5,AKp%E}@ hM۠k[4U|#4{BX˪kU P= f\Ǐ9G# CuQvH1`rY9ΰgEUӅKW~D?yoOny4۳m`D;bdXqL?Q_w"8<I<ldUIVszђ"#!/!๊zzT IDATv:Uhuhn-lo`ok[8mXδdX+/@y^Y[+% {(`[_+^*@8>BK*Ҋ6K" lԬ x18Nп-؏$?R6 'k8:2n'fP(>53b+rcN >4 ,ɢ LDŽ7U \cLv . ]9%PHRAؘmZTKu)au-E$!eY ski5jfΰdi7ŴmqDȀ32 c | `1 ',HK)|ɂZ/D!},d3>蓈i_z& XYlsYpcؚ\K7~ t>=NL1gx G1drR+֕+]eLHHy5`DXv VGGQCO Ek$i׈,bFM։Ama2`g{{;{؝mavhN-ǙkM~_ HH-{nKRHvIo_\;&ʍKٟ~UoZկb @P&<8 VkqĜtEf4^M7s8>Z?߅3ūRί>ԲpXcbh%cH mӜʁ'sE#` /8q]I(4TfB4n9a/rY/vIQĹ |YrJH) Eݤd:N.f"eun_rW9IHEx[9(Nd/PQejn6u_.Ge%4w#KҞ)o:q\ɈDΪ Fgrc9wW]}Gͯ.6L9h-; H#FDY;Ö̏?`~~d@ OŠRA!W +.N&::x0B+\_Q02i[mN8f3̶ۻ86ڶEfgkv+Xaf-};6(a`eix}ʌ[$ZN'l䤮,Yڊ}F8[ aՂ3v5.nV >=g DBJXx ]rq\z3޿O&|?<ћZtêm8 9CV粙{9Eg|ULgnPՍ}|<ɠL]G`VdT}e]YEDAU Rt/)`X-@5`ͤ$U1OZ~2bMΡt)nbLg[;ݭm4>_b8ȵV_=ÓJ=7l I/3Ң\:@t.JaYޠU t2Õ`c~1Љ'@BCvNEY%$ΖR2TVTDe rpۧup`c~~o__ImbT[miUb: AQoCA 7``Rl=m6Dma06m/W+ %$60д+#\`:i>S؛`{BE gT_؊ji@c Ht: (s1ۘ1{sz8tG ^{}# z1dYW }y[(GB{Y\Z6zQcGm=&0 vwNKbmLpyz;6/\r,\} 㨔NN@]d-BR80C CPƆgpLMPQMP o\촿KƣN4g=忱 boGF\Zb6Ws+F#]8ggfbCl#xb.:TgB9;sRZdu OT r9DiMS 2D\!_ ۞,Q:M ]=4x-|;w?~{ޓ6/|ӏc>_XS^Gt);Ӏr _w`LRL+Wd@gs'R `;ăCZޣ!gB8 8+n6{M&Jgz,Dkm`Ȁ?!ݚ]@[]IbaJDXJQltlx64?>YxE-((`-*"ܭ#dCDOրNҞ"1:mƤ.cDN1;[gpi̦۪ty\*,'v XLh2pC7wN"W݀_$ڢWVf k cB6谕Ѷ GTr sVF,}>3p." _5U*o63XtYWS m,,к*%8&U} Z1.8`#!u6xq˿o{xy q!Q T"XVaU1d=~B^ʄ"Z]\hvvwqFө}enbo4n9};;躶yh S6Q_v|6>K,j[lвf3rUgPn.EOX/ݺtTzIL]WoKTK8M gF[BD9 ݊\TX)hK#Uavԯo'?s{a"vZIJX+m)"dg{l'KuA!0NT zY] vr,)Ͷ=tҙߛHR 4\atD+|1lU.;{hoSP 1/fdd}+>TlV1QĎ jA7svfA{2K2 /CrTR]:4HwT̬^4:I`r:"w!G,ՖQɞ`h Aj4q\\A ;EW6/p.\|X]$݇Qw۷]izQXJi& sϪݧ c<[ۂm0V+Fìmqim9dC׳e." 9]ul}4)jTJ&ouvId_I&㷲9,d?g=2by3Qċ,VS]Y)Kob M9y 䅩ʍV`B fĩ?狵Y3 <3ذQ\xeR2f8"EZtʯoǾPM/ٹ/fœ%ղFm4eڜEb@WU0Iq 9cp![oÙӧ0-OQL`o`&e`@e-$Uu{MBTQ.fW.M T8-k'af+`!h[t8e柝)(س N'21:tT@&;NtMKC0k`} V=_ g"d&dVY$Z=8sY<}-!cD`Y{'N>v X?އ~T^O G ' G34 >Stpsr0a&31::# Dd(7aR+)p(WD'H0W+X^ŦR lg}Rzok\s4 U7b8 `8 w3) xFFM.# ц cK>HUZ3Ҭ$ *SuZcbމp 6Sph !Tu 0H.x̳C6qwƑX_d;q8 Jm[ib_Vo/x)\GKJKd)'hٝ* ] 9>1ȬK>,wf)`&-lÐ'BcCHj*64X{-7D@()Xi>uʬSkQچ:s:yEYdٱ vSM58Ù^,{1%4> Ml *fbmʭ8}o1cW8y{Lډ(qb[Wo|o޿ǹsܦ3xwf_B{f1 ª29wd m0$l)!oW~[䫎qwk9AۀXzԣ^U-K &ZIHlCaZ*X (' 01Xd62)2U_%`KːĄo Pԇ:)6fZXh5 er<&F2Cb;feq@u?wlQfj>!  Qni5PtL:W8gni<8>fYdAѦfBVAl2Í&Ww/:hR.1yaDi-F{\?:hޮR>~Ug rK$GרbXb.[N*Z&s8a/Yr EN9uppLPͷXig]Y~OsȚ] U5#)xy)u}XͲҕ ; ZTdJMcX삓eIplj0V'ra餅o (>D J::N i)gg{b CMEՋ𳽇@P$mWkJUzLb y8{=\=֦ZbWc^gkk:ZecO?.L>0$P4drb 6ީhH#mSirìT$b]1%ټ˘GxAM3 o 2- RNgXΏ_DxGˢ^F s P`9 aĐ |1Ǖת#b[ $B.#a#f ̾sQגLx#p:NẢ>T*] /Z:gjcp&Iz1Iy2TBr͂*Fe[F؂ dX9ݾT@ۏ~@WtȄ).yckHm?1q ,{ \z /~+ ao~fR8}JSe#Doٿ W؊VV&l1&]҈8  Qe΁pz4N³/bٯlדPQ"\{w8z]̉umϟ!&lzhY]0jG:~Gڟ]7Ls@IY=lT5e"x垥wpkIu`@2_i;L{mDZ3%9;oܻޡ6O^Kh \5FtG6>hVtzxgwbxr[As?[nW1ixR21Imf3xv8bLY3BJ}DJ \u[b WU7IP7"]bҴȒ0ש2 nLL_ThHF1WOZ҄"T/ږGAwpXTI.;q.zYsP)*`,e^[,Qšg*Rh,[gkD&; 7G>(|kq _Udkհzi&lM't4aēO}?; dK0n,L)UQbuŰH^{;[qU\?)_EAhV%pK39C6 |4Ǫ/ خa稘dd֬L`e!kDT; 98\񺎫8l'Wvg@5b^DVl V K#6"V"XвC56t2 IDATΣusVOfb2"eF > 8B%-AlhfB(u0ӟ_Wk4"}IJi- "^*4&c2x՟}ɧя~tgΕ+WRVFy2"5@ %cѯ0m:>Jntni%#'vX.C񡄵 :k 30mTcqBS#8yhl|Y_]ab+"U0Cl-stj(0*8|TSN*5 :#;l""%%vPQi=b؈0Tz", y~T=%o~ y/xͽa:%)Q Nh$|T?C[O6yWΦs7ɯeuݜs}]D̈쓙d(RK64*hP@Р*LF0` ( L[ ML29g)_{=o~Ww oQ 5l՘Q]mdރ3`V~KΠ؟ |\faQu!v-7VmҀ0k:>؇qeg{<ؙչV{i8HdAgAQY[5W(y4S^D6A.>x_"lVa?A>m&kkME)SH 5 )xqGlU l.) Jf3=BuE:,x"0Rf c˜tC b**2T7pE۞PE.nQr .h O`7?k<||>~{+W*UO Ճ G+hS4mUvD9bvşٟY yog|9!)gŅ⊿"Mekl:irn3ChCCT|5xpo:1|Iv5}ˣ11N ] BK"4: sxtf`U!L@NZ0VU(Rl{1Gmrǔ)V3L6+'5^ݙrF9. -&A,*?,/c- BPN~{?9~7]vj)>Z1Lɧ:ԍUq(%ᲆ|Cg١+?:#)ecBgr0`nr(*q-JPrЕJk5/Pij}X{d['jNL]ŠS-,ҝ=dR5i >kZ80:gas^,.ٗ{|0ﺺ ,͆%k#rE~ >qшI> K0OO>'C~/m!97S,\<@6 pZh~T'\z>%|o޺ a4@y]{[)zU@ʊLD0\&>Y k9)1hCHTgej#:%),t:"1GRf]BWvB41֡D,k8!;&UJ 'da1 g_IPcȗA0*, 㗟ϟ㽷w _A:'3Hʦ6~d(Y=eqzBh-dZ ՈmԻ1*6\d~ }9jt_& Qe,&Tޡ-8<ìmqٯܵ:3 9qe >> ^=д,2 pYzeL^aX~B!лBL6۞%KET"? ($䩧e[kzWu_S90Z$ȜG'aOkzф VZI$*g"Ozd'K dIJ >{|!mo~ַd\}pZl%Ӽ"J*̕Tf\]:DPE&w;k>rA#CzI?q{J:N98BcZcXb4ؓ3 Bʵ*txq>/x6]Ur<0T?Oy x' ddYDXp7^$ t*8IьUYƂvU(‘&Hb.Ա6^} ݼ 0f 3"S.U83h A~by6moVd g@8~ ~']jH!ʀOlF04lqiJDZǦKU},|}<9=O?KW˯>߂vRLs)>@Jnߞ iB0*律]xlc[yj\^NE8MEx0➳ =D"vuuW~Yeu6gi@ތ(2c=X|=j%0/ 3c6xvv/ǽ'<{xww # UE60#ʗ֦/>V9e[%, u;녯q+eմe T$h˶wymW '^q;tn'4=nݸnw>>%n^=ī/+;[*I`El#U?e@Y"rB9:?.%zۻiZ-yr)̹<Y(lCl _+W=#G>68Ź$*+M%$;$IH"sb>{p?EλoТO )z n_ jtvֲڦlTrVjW;B JYg^hA% C'//ܬӓ?x 7E6mgrSA^Yld^{S=08`^$f"fE>KZo~ģOqp_{Wᣒesf%G,ƊKlR&NQn"LfY@YX([eC2\皤KPJ8_/Ahɼd75(EzLУb$c)rxr^N.x)?z?x;xշp}sCEa U[JbW(2L tQFd`AbYTڦ8HHZA0(V.}?'w/z @-* MkJ+)s+뗾I!hnYlд$g,7k4!M/`ei۶s[-ONp#<>~'+o׮|I[ثs:K9Y@,7/ Va-C)Ev(tP'.Dſn= NFC=Z|猁F0֛m D^o}oVר<ƒ Q4t‚lTjk,gUCðfmbĘ &MqfrM /hoWoޣg  `ہ@aE'[/ƙ."Q~ ZP{T[8tDA)w2$:!z[sIu¹\%d$ЌK"j]ZBb'x؆LZ;pNZ[`4s0 14 ŭ.*IԲ 0:Qʌ!glҀ )rG??^?1<:}]z\֭d=K#X%}kH$-HiblYbgG{ 3Zsu-Y]eܺ4rӋs|){;+q`> XaDN&yb!q/6\X{8F]>6`R.R5ԐlY Œ~LTl#C9k(ҹaAWr#lRbs<8~c,K0'\ŷ|^ս]mNݥg WQy-Gm]{"%EZ7ouF1Vb@1hsg~~@Lh̖CH=֫u{>{gy[Dq#'5sNYLeqc۔c%:≮Yl|l3q^2pu-5^y} 9‘CJW_a`Ytq <>Ll>_934 Z 0Y)*xm3J Hq @WT+&py{(kASE(..qOVRƀÝ=5\? 5pP(E8}^ ٱ؂֢`&iAـzhjBM`2ڝ̓%+̗ YP 0%ç;&B{!|;zəSTTQӂ UJ3l+$FX 3V]iG!a޺$X7gO`LvZO=j!Mmqʡ5SbrU؝8a3n3Nۢ 4IL1 cJVnR&C$ߓ {t?/Y`|PюW3"r[@A_|q0|b# 狅DІ.u6\A'R%h6 r23Zܙ%L5[SpьWbts#XD>8F)aPY7v s2kSn~5 ȉ&>!V"Sh a_rqdzmde] $YK㜒CN81mc!Ƣ_#^{p 18slݕ`wcej8HKWuՑ+b3a7)g,6+,.k|rÝ`]`gawwv>7? `,\!ge見-SapV/`5=6)//qqrպ$F`޴k;ܼ3BlDfh/XM6+$[4dϪ@ŀ2ΙQhu欄I}!:(l?KmIDs[z9󖜬.ݢ8A O>}饗/0 aLFLZV=i9i̐>jZZy$q}kF8hF#\.h|1ɇ0֛5'gx=*A>-."-:хشIBr %rr8Dh7\ԗ R`GA.;,qWbɋPʚ&s_hRb" 뼥IK,y*朱Y{z-ԓM;PPiY[KP&upbV!dس\!0h&A=y~>^//3(8HgaS0dV6zʫC3ҾX*`'7}.t!"t،Y$ IDAT fA#Do!=M; ;e4M(}ET]{;}!G5-!̌sox.3amx[yh,D4QSW۵h~b8bL z ַ.6OVqd0 PL E#`C>hF)V Pe\bh`NXd,Fm9ьTږ b$ R vyO?r0I X*mZ^bg,Jf`{zDP &:\KƘ6h]}*=}IҀ ߆@drUlA !z8=;GH"x63u*,Z%|aK TVH!5) ƈb hcTcЭZf`VCŰjoOiD0Höz`Sw}.7 ][=,%"Al Y93RJX~Ħ_cYaYXR2Mu޹ՙ#gbw'L_慪<~riƜ' I´<(01chy\ӁKtRx ^AU\}A6lbD6؏, )1 &բYTVWn6؞C7 ,+'xes9~`d/<9[?f**VJ1 cCF/8_-l7X#qL 'ov3PWC{z(l!P@&ӹ8 Iո䳣'?~Z7^}1M{UhgA8gpS&pAo$9abup&aқl8$p..t3 b5/8 KRp]1 . <;Vcy I~m魿WEyi&j-7yڦAlfVJ4Dw9 V}za##R[i(Kr(dk`5b<5r!lfrGj̘rb0(e#f\?BD_%475?~7w2IAPҤȎt;o/_v?OobY'2_=ZNCئ9U"-&fAeo씡@ЏA=<.9f!1d=MZP82KMaw{{h_/Wxr uI>q-FHrғ9+wdH6?(RMC5l˰ %Cق$iz B ,|ިENulE-k\c!6. h`sdpgd(U_0wQB VVZ_ZZ_cm`*Rl߉j*f;GMHuKFN~rΖ ,6k5z3ڦAv1ћʳNuXYḎӢ5 |b13ڎ%L +o=9I2Տ^HFrBDvtMDl"ڶQ3Qh?o* }ѧͨeڬuB&TSi\2WHif <8bİ0J G4+WŶ^3K}X `ls&Κ Ț늯ľgS*%H p/XB#rN?e \ݿ??=^˅\!;*SJXR Վ!FUaWfVJPO )1gӈs6-fMD i8iд-vvߟcwg$%_. Pꃒ3i3 }JM4S/fmMסktQxι:KW Dg}LDzqZ몎IiMJmZR"Ix ǥ) ӈjjs..q\`3 9%.4:\rYӢ12a%Et9=>l%Ī e)ѐ^rx齣dcP`sHm3s|.qvtZ}'~^ ࣋ Z..cZ~AEhEl9^ +bk|R?UoN7C!7cd R@WΞLjW n6ǵ۷D_>|u?{yr$[O.zlŠ''c|bumĬh `vu*k5&,j3r*h#ɭ\wJlHy5K\'K,Cyp ]3)#BKքbgs$pA#qU4[!gid11kRVd9 0Ќu)JPT!F3:Kz ]__'_qvq "lJ**Q׫ gd] e~ZAE1*ΘXg1fQ:!p o ^y|$FpzKuY(494*b@Z]yhN^'k8贾[]6-@0W\7 X^%3pXo8>;ΗK,W+=iѶ!8g0=kܸ:FkJyX /P,Nf lL,,L!U,p3ہi  b౻|;}NzקWZ.<™ĵA3'Eg9K0$ŁȬ,0AC3؈BP͌d;{z r8[.qpD#>s \5:+þȓAv&3JƃwmD7vzӷM&ƪ֟?@qŦf# *񬜻l-Zloؒrޓ`[(9s7=\+ gX7n^Nhh-KV3cdݪEpJ8 R&B6I*[KRLPsJ5s/>{!*cZN9H|q{E4=$}PO̺ vÅ#Ba+6Al#>{r_#NjS>=02uܬk:f3h[8+{_Vގ9Èf1 ($/SM2m2 -#l.81bFDGhb+{4igAN pzYlXl 2%uɌUa<0I=\Lb%Cr,` dX*sA!S@"Qr9q7+,/1V#[% H&eTaޘ\Gp}hQ^*f_cX'վ7!'؟uجk\l,55x4EBvhۈYעm[f~KY?pF?ˡz qM\BI.3%]_,1#//G''x~~r13|ux!25={q0UTm, ()=ZrFł`TD_j[bUiOlh@!=妇s*hŸY m;oM_"M@r~g> _ `uLDr)1TL:a5䭗]8/-80N8:?rD3f1ܸyXTs/0Pd9#{8epC볠E_^ʦ8]v(`I8m^h=!8߬Y6TO䴒2RW "WOeDS (68)˪kE[8%dW)U66X%Ǧf0կ5ۇ70k4s%0ER /(g30JHHCC]n πB ]}]KN?D6b5m e5IXʌآkn[ 9Q9b`8YX! ,( MCtpA%=wZao dסqNclLeڟ]ui3ǗSasscp^3ǍitHb^|īaa<π~o9~'Ƚ_M}JK9 Nὕr,NF5$31zDܿww U!5;ylY4?%P`~MIs-N[z{vG}Ϟ\HVXY@N!lE\Ჯ:ْdMsnJu:v[ͮm⵿R&ؐ}@(a@"͈˔Y,MX 1X*l5Sy8jaF6xf6`7N!@%% 9ryg8=>cDM^&|s/s*vҐQ|-Rm;CfLJ_Nmx'Rh&0T;[TW 'fgp0~L<-(3iS\O^Tr1Ftm !{}8fSMJ7j_) ʜ T1='G8\O#?W^AӶ"Ccyn+:d1k.,}͖#"vX_ŬUB!S2zw#QX ggxq`YTLy5!{l1:F`mI$뼃ք㡏kx5fYٕF!d=Q~UeNU '$m%c F 'y JO󽏾-~޿7>eՙY Q(71%Y QƊ-A9٠Wzc84]E#wv~~a1vPW)'4gOqy0۝#{T*lc?އ~u/=r͔FneSyB=Lmar@Q[ u/4K=SoKM ;P8N˹z4*mV!u'=eYjgWӵ`~/\ۯ0,@hA:f(.*X2n nZKƆ3O2qCA价?e\g$TC!uOJX>=9Ҍp֡e>_6샅1ߝ}.{aڛqC{k_XuI,@o:(C&em PBi݊8}a!95R#!i.bAƼfFv??ۯpt}0,`[|I-[ Hi/ q䐝 cbTgʹRMNVG/ɅS.e0=g LVr^(9 a~<::«^ƭhcÑQ[v]]tIPt[~%v P]}xw=te>PDHl{x-@N[,q ܻsxpDo56!\;(@6x[_CaX=ś.66Of!۴(8VJXedЎ5hZsV<xPY7ʮIݚ`lHGʀKDA #Oƞ鞾TwUe}~ߵ|՞hu}z.o+,j*0a ,\Q 6X~dzhIU`}ǚ[>LQ|r7}g{7w4leSDeC.ҿiRܐa1 Mz5t8% (Kpq9׾5|"Zb llam}]Bٷs埘DfnQ-^\!X?f#8v2^sYO)*F`v&4~u$ |#Pr&<[[=wW\_n!C䷼OB]I\(인,٤*py3khC% FX?$N܄uQ萳ʌN{c CbzHMƪ/$F0V6V%1`DF _,㒝$TlO on 4M[|ۺb=vP M| l<F#c( Pbҡ9(MV.YضU u^W&F-E/vlpHL| A
ʍ eyjsQpk36. 5]Js|W[A{>닧~`P?4x&1a%0rڒI l܉YaW˜ĈnPYšp_^bb\7UI2ˊY-WOlef$hMO 7Ðqvz //Wow*NVF&~H#F`2 K FLY[⠺C͇QNlfv@ 3:a9236L"88>Ws_!B *v!<Lib}~qN <&[騡!vpY0\LSo()U`zK ”JfI cjU u!WL  l*B 2u[C\X&&iVgai1P\Xu{BUlzDYZ%iRwt~6clmcmCNO7C7b~@7oX(G#$JUp}DL:pCbOfk@N[_as\,a] ֜-mLlcƒ: xOT||vo<?~o? +p~=eW*V je!1S!%o"GYtuA`%LRizj#i${"Et#QlÆs~S!IzHD*"Ο4$eqZ7^ft1&zaԏv#aДm d3ðEnjB~&SKdTۻhPǧ}6ב lL~Лݐ9$e l 0Db"T4˥X!0zT0q]2l!\psgğSx66>íUT-uΕ H>6i7~>8dj*@'e1[YJ=p%5XxhYr1%UJ1u>%d(b!M:+C bۺІ'{d(̌IhI ]TRm-jՖDU@BX,}˯}#!b.d \^.)G.+([;X[⅗c6(3"3T.5j%BCA^b/E1&s 4Wml*V L% fp~{9-Z8 s53P2Jho=wǏqy9wwv7c5hR~>@LM[#ȐkuDMCֆ7'(H~/0PadKqg|=5r Ҿ t 2@sE%Z_N3I oM}EF]̡)Jm=wrٴ ,Mw> n =`urnϾ xD/tE  ߥӈM|i"7s1lZA" kkU]+ QZ4%>,F@ SQ_ضH[36 +s?-#\/&F!@J8 9C̔ 0ͬs g.9S=8NEƎg]/njIPLNB ֡RCt!%Ը1Xk)CKǙٜrvEh]eTVuӡ0kS|{?H ccc ?|5&]O|e.ww{k4li 4l#Gl) B'+gkӪdv>6ca!R/ 5>Qi н ۪7ӆz90;x-eG#±lr?# }?RuT_`=oSnc량)l{ijfr&g3oNHAtMPl)@8WE=XX%+g}RJH5e B?6GZ`̃ /$NਮnMyT>WTBL[}cCcss7o5<!l:vH[;F9~iE-*o*ތXJvN b,$WP!Kj*А T2*͸iT)ZDyΠ0{'VЦp}i:M/ M5_K1Ł\-*:x b eK4\Z"R=LR 49~yfh)ϘxSu.AJiAuR7 J[򖊕H.vQ{/LX\7HZoD:ܽD\IJb[l<}UTzjiF`TQ3iKRDo߃+,ZYU 8Ð-$u3_2,/f) P{;(!llo yxߥ"B7>K!Fʇ&l_Su.Z~z 898[vܰZZY %VKT}SQ N/X@0RvTC6jrHC kbm{-y`z_^"t-E 9kiϾ-ѯ`E ЮTe aӫ^~'Edg%/X Y{j{J`Qpkc$i" 2y}OΞ|v::uv~<r@>lkUp>V.^R@.l{[wYE2:J:[0 (-讀H >pe$]p}mkL ŀ7teȆv.~;4hƁAD@ DJmKfm/Heh5(Ĉ&*|jlL'֮+M:  898!O;wj+%5һ9 Cy:DI =[j#GyIkà>AQTQ^g8=9Ë/?&Z9iYof/x>N]~}loo:5I-.ol!MC-})&i}][;Vozbj+eYs-=lULglCIϫ&;MFΓǏ'0\֫=#+4% P@ܠqh wG 2>>yMǪv^7C$ڊ'B%{fgdQzoޅn}0`!nGE @\'恫r.zޡdDD[UD#Bx ͦꍘLpH)Uex{pzr |[qކ eKV+HmD5`.| @G&)TʔwDXZnR -FJ̵v+&E{r4ڣEpr~[K68 6rmm[UB-KjU:X30A4LZK&1 HjA16R| PJ-"$^[SPQN!j B $SBjFq1+xjժ P0XpPaڛkW.[fAQyXb1_`kg S9obs6Xll6j塮~, 6t}>55ꁬM_1Uw)Ѓ-6c@d"f7URUN.³ bex|ɍTZr ;]D tfOjIi @a{%bj"nU@FOaaCAϝ*QN>lO&_f1d4֟+ հd(54¦C-k:mf$a4Iw}r 1I4zJ"ŵ:0sWU c[U`Ūmp,ZZΆX+hRLnm>@ 5~^29uX3-q=~*/vyyml#}mFdMTT:EM IDATVEE,Ӈ/jst(JΓɬЦXˑ<6KF csrm|DLF}ھ j[A΄ICbpi8maqѠfƈCSe`[uڢP'oHAGªխc"I V&^*Kê7>"PP0ݘa{wIER@aXჷ?ѹ@ 6qW07r4sm y#& yRf!΢tUn : >s\m)5Mƣ700^җvP>Ēf 9\AVV1y[am肕DW{,-%}41odVcD }ك1Wf1SAG* .̕~˾ ,jh&t*6!P0y]\]MXi~r%G;KШ*D: 770̪"xc^z!|1>ֽJU ^e`HuK8k9dF N`~6!R\FQIPzkUaL.}XrP H0 J>n,y"3%j<\&\/V\i v,8I*ٕj=vB^ƚ3K4J\ 0dCp  k&zEuXdg ̦8$+7)ؼ.Yml@A[e1HDBZq4)ǪՀoS'> kjYڜWJ'K[VNp}c T/?x6 /=ڿ3<&F CIh MY \JATc"R; ft3=ޞήXj˳P󀽛;?oꫯ\FLVHr9jՒk9Ǥn 3-PWu܊ZcPrOH6C<& y U$D͐ Ri] J1N=UfRLD=PL8I\pE8x:됌~ol6]?6yp0%=RJRyNOp~zO|k. 0YTdd}۷jdA|gWnm94lGcuJΨntS /0o궤DՊ9'e*fc`Vṩ~eJdY:yz,ӆL= qŨBzS2o`2h6At{/?s$8?D I^ߚb6ױ}s_+6sY, M Um8-i(mK{I4R],ӞA6(r'gg"QVр:cP;\ T]TDc3`S,ЗST_ vk2[O `+:oL5`֩{`/JU=Ԩ}t4$<B*Ou*VJ-@Z=ͮ+-OׂJYZ W.aJ\^n֡t}\ a,\ Ggx`20ݘ`6vv.`2s*)C 4B^.\5(#7(*!-hPpa`SIʄPz2W+QzMtH{)RKuvi;z sHRwQTkiY~˨Djt![z3GIT' CE"CvdEK#K! S 8ykν[(&yj8m[ZǔRdADl2ŃK3JKo^JQ:65N2o^R SŲu"bU4%z73`I͖j\E &bOEn0RՊg h:z WyF77^}mmo8)}kj_/yّڐRg=o3& 34v]=,ܽ=JC|̕ctb"4dImݓ^lct'M5!(6Q8G<3/`Atv669Yu_?j#l &MTtJ@lʿ- >XZǍۻLjjE/J+ Y;7c~RDd#RL8"LSHy&6nnc?˸h-=[kAf')sbTOt:]l?Vx5E= 1L$xpjXʯx"Ӎ(Hːzl 0q|:(VXCIc4;C5j*$՟u^Q <`PxUt`wpL#Vt! N`dΦ>lK2ӎXe,ٕEnɊӮb;C يa;x =ii6]q1z^*2L-׹Km%X 2h/ .U.(I !&֥J3 kc,Y%>vR 2E_.ƈ{j|`%N/.ٟ=k9YNR:óԜP*V%4 [)(!GYyP_cՁ'֞,d (m % ŝ}sClV*.P$ٴ"?`(&\,h^Sm*TsM@S$[{WIhV?j`W Rz Y -̃`ۍ=`J @0#4)6K'$\ՠЃŊoӖH{pZR/;"ȹ J?d̬?i) 1 C۬Q$y}jŔԇ/U%v& v62&:m$uV%calkR3A+7ʏKeɄN\Vq2!Եɤ pl1Ɓϲ] M P4c-i99ѕxc&l7,EN4MniBơ R8'YHݖd C&fA[99:}{m[k!Y0W &] %./0+2 Uy` %WA ',z+HLqJsQfb22yxѯaش$-`L(?Qkug!``cCTN `dc"#z&ɷum;IV5/HJ_&Sy#R,)<6xo#u@"37Et* ^?6Ȭ*7e Pa;0b:,k| Mxc{dR%굗B$mWK̯pz6eA)X]-~UGL}oZdph+4 f%Z<@f6"'h?-xEp'4 h*>tsK +QnyX[,Yȅ:Նl{.|?lܽzyL5b me+[~zM01$ .Qٟ-;"T^?"6n7qLpb]n~٤g΋uu/V1u5א2  !Хmܼ[7wݿvw6[Cό CaE\b\\]6Y"48;1(E!ER2Ylbv[9@C5^vwӧ~_G <]MN#C[爽/Z;XM>t.zpbppՌBZ,\ 9wfS @~ ۮ", /݉LoҀbivu (!@J1Q TjւD'Po`alRe.8=>C7e~~qg [{bHX^_`걸\`~>jԨA8C.n߹;xi>[XCHѤ]`r+_Z,З%a ī`dPXa5I;2Kóo 8:b߆gVx7__9_!:gύ^T*}lh|>O RqZ?֧J \aSpTk(f>Bne 5XǴ,m{j}OW%zUWֿFjF-5匃JG9 #Rc 9H]l=Lfֿg !Dd"ZXۘ`sgܣd{ϯqur@+a ky wn;wp=ܽuwwnb6"ut)Ԁ}c~} \\\d~\R@YIDC.%PbssE b, #X|x1X$}C͌/fذR_hdWD !dq$D 0VMhĒmpmHvT-^2|=ր蔑|x]<ثisg_AO>8j#n[qswnŃxxܾ'C;:uYZbb P2"3s/qf]'QlE5Uz G k(.yF bWzy /?:x2):Vjod%Xh bvF29#h~p}(%{qAr\e%g.)t=(X_ 9(B+L W!l[Z7j(hugf՞'CEulxK]I1IM1_U6on:Q#FKVk\(eIBfX߻[qg^[{d25- B8P VK\]]GǧxrrIХ^lF.>,E o5CQ ګ zh}K6)#ap1 -I% Ř_/?_ok /TkB(Z sOZ\]f73魡%UϹ;ўOL~12LpaIDp74r*4Ĥ+ 0c0IJ\M~ݢR@b74&!&&+R SBb.Ǭԝ`XXz\^./Z\ ԭckgw޽x^ؿ{7lV+oEi"J)g88=1>:9)a0N2̢yXp1քdDKvrFAgE%TiVbvԙKUHVKabJ Rg?k˿G4fOSZJ{8CBe3Ťq srCuB^>Q IDAT  f]imbk!{["xЄFQmz!5|`B=$A]Y=g9PStF2l\u 8a[0C? ~c~G&t)ama-Ãx/y[{I7Ax.@*FXpNNGGЃt]n.p(ZƗ؂NFAoƭ=+;: BE'jbp>{bң'`\c~G~_߾'KȪ3FPE"4RtRݙ*bɀI@HLN ,%IS-2O$6X_es۠-&Y`^qZSDƠ΢\\T`*A荹`-Ni:k1 t`~9`~1rpR 4b`s&vn­[p}<;q-´(Xչb炳%.?8GGOqqqN6Y0QVdxFrmB2zM rLnۏ@-&X_aifWP13b=;UDzQ(Z( pWsK"TnsQ(B )!` +P^b>bOju6䌕p+׫/!@6E53RW _/.+Qntf`vغԨ" uG1_{ gxrR2/7,aLcalƅs]y)l5'Ȗfy wWs.7 cb^?|lы؜y020c\חK +Zr)b2.voƃ{/xp{{;Q.a͏COώӧxtt#gWX=bM:X[tm&FŽ/>0ɷ2j[ YB"u5[P x$1 2u"2ٞ8xvU05֧>_O_{tp@lm*iv{%.D{Akk)& &Ӏ2 nPـP`.PuH|nSc h v> P0r0ֱ{6ܾƒ}߿[76!]Bg )qW81>!q@o0&lmot DBn! ra$ƭy<\5;q0GVANU˞ g{yChFG ` OM5=+bNbM1zkS 'ܞafk.UMMeGxu{At FDƒDuF4+XXT"-1Ap}c3 CʡJTCFOcD ).Knhb$`0eA $jb ۏqyq2dAol 7o!^/۷qsgTWh6mUbgxtp#<9;b0L}i:Ť`BQՕ a-i> D~;Sdm0+r N# .vƓ .>l˞SJ*bϲ2sKerEJSRHm ;>/F[<u]-#hR`< ߩ #`*<5&ƨQcwE5@Bj84G_FTaPR\3\mnA&#Ͷt;fuP30̰6n ^_ċwt2YAf%7_\v<#\#Ztfݿdn!U23!l nv#a!AoClcQLZmVI.PH^&bOH,U3.6$ZDiȌ.r2=!#mVqA{?^__:>£8:; EMt .&cPP@DHadʚzlRlblb)R#M#6RD!֊44],f`:C7ؠO_vDD̈`Rb~ ?Ee:8,͠ƌ˫?G@^\񁡡 4 7XG7])fe^kϨ.D AHkI"%{؈O&NPdk`$;+?&gt#US:n޼ݽ]n(M۾{sݣ210&{1i:hK("vbPBWC<瘟_j~+,b*Ȕ'Sf3llobw}K%Rxh|EƺKP+)׭L$iڟ Tғp:@Hu[aHէmhc5<*Uϰ>?_&I*/'^؋a.+uݻ̊&tL2 uV V[x}YK%& گ//Jj5Oc|eg0BURH [}&vol6Dp~uGxr| \\#+HLjQn]P *6tFA)"%Ġ #l P5y%6"T˹+ ݣ&$"yp3z>bPKv%̏uFNp|5tk"L3Y$W"5v󏾨 +\4#Y*NI8dxozgIhi,&R9yd2ō-AGN.1GI갽y"`C #I݄bD"!&Hθ;マà PHH :ִ1Nhs _~wG:A[24Wh;g=נ XZ@0 `ǡSm)stta3wRt#SL|sɆAoZ?<;{rz9Y~6"۽''Z\A!}mpTx-:efBTi~6ek`)ITGYu"~D&~=9C "S\bZb{ Y1e&!JVR˞zK$sIJuI]0N0]b:\#o6O@,XcFuX[&gXO U\pZrq˫Ks\^^_]?u7;4hQˮG&i[20 Jt"኱u ;3ĄW-[L[ՒCDA9j'>7/ү׾cW" Ԥi$VDtglOmX+o.@W3@E|W)Eϡ `PB/ZhEMU J:X)dCe[А $0`K@d| *;`,p!lBҭJ & )01{7w1tKw]mhe5`Za{VK=.׸1)?3D1X,5t$AdS+^f,cc>}*?g$ҀVu:%u3"rTGCy8XÇkgb@$GyRۚS+L*_\YiS@8tB.)T5Q Qe+sM 8a0QȔB: LXcN|ͬ2At` ~! \4%lNװ6aXwzo`-@Eo{_˷fX_]zQ ia4b}s7nd.>xym|筷'?lj7`tF*M!spm{4)ڳKś!Z$ͤ&*sCEI%(y 4=ݳ"AC)?l___G|)5f4[& կ]?D9~błΘMpT(F9Syy[ 0F, CR(F0i+cʒ#)\UDC)$唜r˖e;ёK8ETI E$3{z}=绐K$@H}svKoDa{GY?HR"0"2jzV5 7w`$#b0jպFDkMɒ=6_q86Y!)EW<767/u wv ,,;N3gqz6iF iߴM a]b GZH h($X*A<8!(RɈ羰ߺj)ȑ(q1j3_{2JH1枯tzt4$~2pxcZ` '. ghO[bl:E x;]Ɲ^xNr'1R@dͥF h[36Wpp%v6h|yTMZTE8hà(..][t`*E,/ `\Ӳ اM<]EdI9C0ѧfH743PNM;EQ0J1[sӔ}x$YHQm2TM=?~ow}\ya1" @r|T@KnT(5 C:iD؄!|7)>`Č70sd$OhM$# J6%Nh)ыͮu D A[.$Op}yll ևC w`m P{ykEc[xoa a ZH_^" K=EI`:!![@!4ܴG6j k-rN'GtXU!j\]VL\. z~&(s1CHCh)d`Z7ؘzw_~ѣUw'!h|:rɊ*#Q@J!YU_gQ5-91P1khHfJ[Zt| |^$ܹkP+) ҄#}(̘$aa|/9DyR3,pT"8EV̕iJ/9 U)aBf \/may|!׆7xkZgIDIbo񘚁MTó~AXa];(ELKL 0BB*D()Bz!cc$FȨ } v]TF͑]R4&/?#xjەx=XθcOS-<>GUں maS!k!bMH| <BL定x-B հ\z:!AN*2A1VnF@#r  37(A E  5;&ctu\vkKX-"/K|]x6\\Yv6-wp5ܼu  nu (+C ZhEӔq5EޡkdZA)AME)f 9l%3)R3tpGab V'O`cg{+<+ 7im4P$\Ң={O¯_uhcDfO48@p t:ƅkC]5(ܻrD1t~Lu.visAE3]6hi&/$,+DG5oզ7")1#"6ZY3".!P5._K/0x졇m|1IwpvZ ް;74( @V֢nNW/]:q; m0(z ) : fOH =9{cJ=/ s52@ϱ2OckcUӤg3>ҝw[8Iv &?޳g~~}ue:Uò$=7WjZ+7nM4sv$d""B#qZXP;Fzgٸ5)H%"  E2~؈\E"19X|a9;ঐw.aPSI=U\Q&RJXﰹ .>I}N|Ï)f|Z4h/ qr3ǎamq)х<*4 xfp*k )޸r[m IDAT{(%r<yLJ@u:L{+(X.KfOi)wR7*gkzAN߉,~"m@" x#iLjO0LX;<ϱ:\pǠ=ped3BK(D tj7u<ܢWH3TzeXBdО*tA n TrMMX_a 9 {OMB "" QގjŐ.03ĩ3e4;OPR($eZzg7h~,WxЙ8Qo){wg/_ 7ot2IřFۚh-;hwiUk[R!SK+}Y&i^y{d8s31tEž(ofp-sՕE 1,@| 67o[AtE1 qq#P̰lo-{sN\W+}ĄԅRu/# ~ix8?_r9Lη@ހ>Ԣ%M9FsQ/@^FM@NqI#*#D:OD&mjh5I͈vxn~J3wmkk)\ fG}> bj[8Zj2]<vaJИ61_AFH &m>0D3G V ç+@އ $ mY,cp xJUJ%H9ٰZ;[0=LHd,=RGu,%^ MQ,D 3?>QOyCmm82;n50L0NQ5!^:\@Tlنo-B8;´&:m+ yZ"$xD#XjFZ$eGJ?z %K z&lKh:M|3̀fYB68+$Kְb4Ń}WB3"Fp'\ /y*ِFAc(kIˡˇqd+"NpC"%'>CZOA@`1|R%6L Γݑ-E| 7<\ޣnj %g <Ù{oA]׸~&pjE1?"ݜF*dB{k3У4pyzvj8nxIt18ău|Jca0>ERlBm |"*fIșgA"9hh~a'_wbG|%g|3 כ ,WʃgQ}]ͱ\:t@e!Z("B4!H>R@n:ǹx4J΄@ Oa|py$e!RS+k ʹB?UtK׮o 2cbl4ږK)jBHt|\.I` K6FCw=%$" KA66wv< 7C!$iQN'Lj^ʿPJsm'ZPxM9@j`nĕ qZ5n; >LVFaHF*7j{㙾+ ~ :7DQ܅\+OOД`IiI7hFP`iee^b|4ttʷ locuisSc6a$I+RC9h=6wwq  oZhu پx!$uI8ijg4Hx<G9o>:4+}D& b?*)%MGJU]*)F\H5Fzq=Ec@ڪHS'nL{`Wpi-ܠcTibM;Y#>BH+@ކ{1RpTG 3F)E,|gLq=KHG/}'f",0<_kI> 4A.5jEIml*O?k`]<1z?MDAt2q| 1.ta@CO*V d N汘7o!%uڳ$p*0cQ I]6 ɐKcC Ci-Q PڦE u}$mh-pC[WƳ_"7 ְ~߽xs Hqa2H#L$(EH;pRc>ǼMe߀Pz8?DT8O0{ Lk`sc{=ȵ4ʢDg@ 8<:i%e%ƐQ5-Ҹ8tqz{Z>}ޟs\~ϟ|酝 BDi":ˆ.8=FHVM@<%\BXHpIB7C.$ER&8b,1$f!!ϣx>M6x֤6\2@Iē<cey!6onbw{SGP8wFhdJ&։c~^*!b<xe?*]dKױ~) ǝg;c=~1Y%o1aAU`M%@+´{4EtXJ$_2e R;K_侏@i &֚S[L <3|<:dq g{?vo|o?~k4^wn-yH;fj<jj.wL %"CF ?Ke> |!KAH0X$ZJE 1[K0;~I)bXBC F6pW1$* r~pkIJc[TmZM e4n?~ /]+/9K(qg޸_y /R),]gqp)<6/~{;A<0>,˕B0v4#Ld!WD8qmx./R< }̢"ѝDQX[YʼncX^XD@8}11mDf2!sG? /|Ƿ N~t>˝xz5RhLD!.9KD 2] z҅q(F _2O"J$b$yv8*F$`Dݯ:HB"lџy.\_J2` JI Q{M8k~& Ad  Vzl 2n ~!^\/5)ѦYUP$u4ѕfbB"x˱ 6;ygxGd>e tdzFssw362c_,y b:Q*= EX $}@ap 1aRMd^Qbc( 76K፛\ e b0\*8u _~kr*0cC^n# LixPM~}ƶ]8%&'Bn &)mѴ-LR3yUgȹ<{oܹsO3%w;kO$0c!<ɕE/F~xJv񂤭b^_=9nJO Cf|ԫ̧'㎑uY&$$d`H7&T@wLHkd!10wfcB# }-Kx{ދ?.:duL&hl694p_hiaE,./ae8ĉu{%m& 1|df`VȕA <ChjUua%TXٱ"Yhf"2a= =%Α,8$eGfJUÅg˲gkG~臮}3]5!Df틷/}pW޺mA%=q'<}(2u@Pv|eTނ!Ģ^^q6otvDi$>* l*\MXpO Ls`sNBt[O Nu !7enBEܝݍ=m # 0eBH8k11:peeكL.8rbl(O!u[XG Y+(`'6o4-sbI$iXXHD"HA+$ )QK,DpPWW/w|Wo'?g߸r{A}3C_eՓ!iPY 9 F{Ç(9n:Ҹ?HB蹉ec'pu`ya ܥE 4YD5N~O菐pL_YXӫǰBȀ7gZ()6$cg;r p@LDY9wB)t]eN y"pe|ǶO:5Ӻ'VO>??%F)IFEw>Aa~>be axdE\2yoF@oJ&\_]AV. +90 $g0EMt>9xsnFsQ yZx@pF F(!G'@Hxgi [LHHm`r&+ bf( a 1WK1+R8)X<8BU~˫@oz 2Z;ekBQ|qai~V>=s|~9L{G0p.(}T (%`aǸip8c#:JB) )Q<"J5yf^.,_YꕿV^{$g7n_O?3OWyW@0;x"μ'&'1HDz΢agэU|GH,^}lR9 %5zE8֝G]p:hLJyhkw4 K瞏H4ʤL!" +97 SN8~"ȀLi = S*'4EB:)>66XK4^xbj-h %>f򫽲|u0XxzS'NOJ ""O=spBZ E)rcМ$@MֶȹѺ )ޘ#ZtmKeȠPJ`@ !:\ y?yNzsW^)[wm''W^j%fBnc.s| נAe|mӦKv z9 frQt}8j pZ 8w-.,`=jۢm[LuUd!)iъY,>H)JQd/ɸ=AJQ/88Ҋo :` -_{h+`Qҙ+KEKG'x샷g8K767ʶq.7c&|`r z)ϕnѴ9 |DH A^t%Bb{$ed8ʫu㭍Reb=\BkW[T,&Z5,$7b/z!$V(M%u^z8St كP@s@P{HIR 2m6V{Y}!w_yCm}3y'=m}|>yG?mؖ):/DogxnG *0fbKN S/3zQ(UB@rԇ[cdh\5yDK3+H-0梤y9y*F0@D`4KH= L;ֵp-g p Z (c @&;̊f=;,Hay|?p??Z|p$֌QtMYpTt`B``5ZȽfxDHIff^ Yѽ/lɁpX9Դ>IC^^iܐoSSB|$ZXG=$GHJ>"^)CH((`B<ť{yk:u;{lio´>-SHYyb*8mqpmݠ26VCy1iFA3> M?ץ,&^cDӎ0TN#qR.H!gpec{KJmI'xabAMvQ9ǎI$AȦ_~V}qiJ 8KB+R%@W>_CuUm0Nak;Q `J%{vD`2GbWi#)Ej$JA#L^):6huszRڅ֒">s/H 2 -iԗN}_~~oϽ+sϛ~l 1s,.b<`2{y-a|R uu8LT^!”MB{!ҵ#5੎#; $et]\G & 1#nx,ZBJ1S"ϞgQO=v΅ >nx;7?Wvck{֊"19VWWp|u 㦂E^@9J}% Vjل}H@׫Nʼ@Z乁u]-aG˖e"|>Xoƀ!Ё}2y84kEa>{^]wΝ;Ο^!O͝[?Ru!2ZC4մ)$polTyQ s@Tmx#mi`E(dFgj!P&ޱ se]_.KÕ r:e%|R~g>sf2^/]&7qYDz:!)$(^y\k(%$OAZ4SW9K7 $*)nssLϗe)?y?# k~7o[k۩RA FgPnPZhuR*˰!Ax!Bfe_^[Yyeyi5|^g/_x2zmR*6hA fԸt %,/.nTMX{Jvm]c|t༃Y%)9넀&7@Kq_/+_*O:;k~|֧V\ "| ˍ AhGG|TI ,#k(d218PlQ )?;;/[>Zk~ɭzjϽ‡y2>: 9}ܑ< s:19OӓPfŖZȧyWz#k~|WAo]_ogwL Rh%g/e B@P;k'6&bo+׼\ŕ\_XZwܻ姾?wk~)^?s?gVo7];ֻSJ*OI`yK=#=u8>y&R_z~i>>k7Twikkkkkkkkkkkkkkkkkk ;=\=IENDB`OSCAR-code-v1.3.1/oscar/help/images/intellipap.png000066400000000000000000002323501417327530600216510ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGD pHYs  IDATxgeu~k}ܵϧy/myU(x$a9%i쎉qiuFQHQ. $(z0 [0(˪J_f;f/H@y{=gܷy$G˿Sv=ۺ3eirĴR(WמK_C,ѿ|$-| ]n[x`Ƕ]Nvq3k$ ".@)_'{ O:?z~؋pxe~Cѣ_zY|ի\y}˫{lFfgf52cB 2%RRJ H)!ˊ z}'avaJ8n+ ?X>|X=Qn љe`ʊ=^?~tɅM5މ4AWBp)"A191@Ĉq(e"RX1>__w!~ȑꇽPuȿYnq[@dpA l6ݳp5hO=V⾥ù̊BNn+{vNq>"D4&hTbem/Mci~vD$ȯ `~D{Xg|~wC8W'p?I СC̙3',XYxݨ{yY,I&'f?CnkIZs]RA&LZ l8? b`x; _p!o~s_?< }w0k(ݔCKӰAv[=z +qRСC4f޲{[&hueӓH$W-IŘ,Y b1X^" 9k Ƙi¬Ux8<68Z|U1 DxOί?qH)EG9EHСC_UG|򓟔 WB7mڿNMqggshu'h6 @GeE FD "9."0hv2gr"o~[[qzk>. b4攓Às'p}/F$dW3=z_׵Y2* DYtsᨷmNLeB#M%iJ, (#$Ie )qI8 .@HRIJLh/A*%{o~C^tLeI4i6ĵPVFE^Wnz.^v_=?p,tW8xQoOX$$";-ڢ8veynnsÀ 7*['m҂\ 9c1CҊFVʲB^Z|(P%|xžrGCIЂ#LDRJ6zl8s8P "hD>ʱs܅O?q;˥g/|]8^ GyY1$QbZ `Ղt Rv@JѳBmK0ΣcٹtneHUIEeac8(GqB(cHӏV9 ~sΫQyBB%\LIJvSYRUJ#s5&b0D6Mt053-W}׽p׉OgW=G0 X>;~~۶sU$g[PJmZ2 A{.?e~27оnβ%؃Ke,|hh]JiE9"Ap[.B( uK9zBo5XL6ƀ$H)Q[GrFUې&RRAJUE!+x '?}k;w׮zWs(4JVȫa1z*ajbo-,5fC"0MfW103 !"ҕz(HYpH|$"p&XOB_% OA՘aoaj̔0:N (Pkΐ@(uIRﵡHAF0EˆK)11)|ޞU;٣8vصJ{8(6pEp.0=1s Y6Ug:;83֝3nB[`~Q>@(P`4C'DXx+䬺r׫= ,r YtZ>}V*P/C9ׂ&mi#f^CJuB,KHr'p AhA2 &yG"RwBHJ+cV߿A 4Rڍ@$)$qo RScn-1(1MJ1v(JjvͬiNt8¦owP'𧾎A|FHʪB%%1@uuτ81acra #BJxaǎ ymQ ׿w~>D? VDͥعvgSݮjR eU`a`0a4cT( |t܀䄞>G X0&N8 i>{MUnl;Hr+{ EQw^MCW]CHpR.G88OyN:H!BTHi{Bo;? -++Zw%K/+ epe*vcnzV {  Թw!MBD&k;j"n1޳\ 0f N6~(S|nVVJ5RJt pˋ@%ʪ!8NsuzF/* ~mݺ߆ HQxɦ44E\e4J)=(%1cZPR߂sUe2A*0?#UˣԧT*Ro<%FM~*r4Z=|?7?;wܾ}ޚđFL i頬JȪB%h7:hh7hfM$q8u~V _kgaդB}5޺!u&Ӑ0pD$nKAvRQi8J_eiu0L^1V[ *Ur[@Vvn*4 Й8VFJĻέ/>/?9"ϝ;WkKl=r˒Dm4s'arr  [4(`ֶ(ʺ!Ţ@T5أl`gkki,8̑\f)iu "% ZQ(ͮqGbL3' * :8)!oj(gmJԮ%Θ0jt,bG^|g]x⥥۠wy8l-M>#xýݿޏσ[أ6e8q): oq5a"yCJ!! }I9uv_f< t\FC=uUXt>7_s Xpsn[Ǡ4aRhXVD.C*j&wM5,!`{.0 %@UjPr4JqxؓG|ӷ9СClrrxIo{{޿m={wSS)Dp+ ܶ7ϛstLձ`}!y@-UXXi}LX¢%sn*(tHʗ{[[SMe֮v+ t:0!PUU1PMn.P eQjn0eLmAYU(p]V\W/F_ׯ|/>Kww(:x _я~t}۵m?ևv.m~a] N NWSUr 9sn͹W/@%. SmM^NA,Pl-TQR+ \[`Uy]U dU^ Ү|O]|%%BtEp8(._}Vo㇩|\ScwUw=;߸k hZ1G'"Rծwŭ6ԇ i 2:ʓqhE8@ݭet}ƜV]I[U&H KTAnX,:QuSKXlhTw\F-mЩA@AS"8 x0f ReU9ן$([t>DϥTK#]8Te3dC`tɯN1* |畱^!]ђ[U`K3 _iQ>BQ)_:y}SOl'n?8p=uoջvz; I(qbJIVc)Kp>?6ҭʺG5m0X^UVg ,V ~B.w]ۭXJ"0'ro8aXuk]7#J(8sո,hnՍ?Jq2X#nqʱNB菠5XX]_}թx.Xgg>|]#TPR:i^c7uYhUr *vkLs{, ո=NÌik9)u,RV`LΖUiR}ӶoTUU=. t^D]ep(Z fJ)Ea8D"q .P yQ`42MTB./}nۙta:zz7MLL!k6P4O-`)Ck=(Kg8!3j!0L`j,z^I3?s! .d"BԄ&+`LMñ{8KeaWbYiH28%j?| [x6Q~ 0dN@1h6`S\2-h ~?{NC;fz`ݭ؞P*XtTFxdbB @A3**KJSI)@t!xHןPJIJ#{ܿddJQΜ`: 8Jpeo ȸtppOҞ:*kVXe捬/k)*rdw](ypێD&zt'jl@kx~-SI Xg{w>&5ĻUg}ٻK޺ 7t>ez0iImS˹חu$\,@a%rmh>Ƨn\ ϭo2Ƕ?eVUqp};:]"F`pe WڋC fggG^{`;| ;aE}@ծ F۶mGz˯iq}#}cSc lvdxSs-y͘WUF =Kt`j :+s0Sk 8.#aҊ8fK [$I_0Y[?_X5t! DQ i "D(`pX\t1_~ pԟxCЁ?o~Gv;ض]**FRd,=5*iVAp j]`].^#Vإ $ t%0Jtmy4u+X8JF]:6)a.܆  UrT xxOpꇐyWvMҴoȲ*}(;پ=,~Nq:Iɖ#0*FeQ`Οկ_Ds& >Ǭk=v-=;mhZd3VWVY 05=.gikj\P%i3`\4~C^ ,>h56i *'̲ 9w1ȱ|qek*X?P̻Gۃ= U3{cWuSc>o{UØHY44c!취ϒ8rxJKX(SwߙPˬRcޡ]{vw܁@U$bE102Hp2Fy*)&uD9xl`C& Ÿ̰\<21pI.EQ@л,|z&4ɃS.'Ռ=T;V5tk|˩s+iRw}u}qxu[JV>qo^mytUQ,ʲRk+/?~3~'^'? [c_;~y;К JD,4OppXa)t]4-i%zU8ԇ֭"lV: d<0&"2ߥYz.r߀NY^V 7.1n+\梛/'F?GB4mϱfy`~usZ]EgU%PqJ)yPSA8cMQ2"G7no<$z\~3?Ziw}czG{,)j;vM)1ΐ%VosT&'&6[NrB=- [.nOrPP.uXa'^"rUoJ)GÅR:.Mꑙ!2vDYܛ[Yu{/ mv;aDHtu5A# {mC޽#_ -UD(QyymiI|[_ɛ|'Q |W<DOl#qbl뭎P!5W'xޝV6nm'\{.3[$Oc|;-F_Ie{F0[Ym?O;_\:И@ۖfVW0ǝ[k$-:V,gN7Ν =}ϾCw[017ek>63EXZ (sYoT?V7+30V.gO^8a;m=:kD&`=&Vp B Xbܹݨ+PbX p-g5[2T:kVg@1ٽߖ5MP`X'IZt[Iu_ϝ;WJX_G_>s|1cx8Ǻܦa֪ 0Cm%4)4d + 0? ۨvǺ(4+zjC[`XziRj7o8mtkoۯ]p )mΙs]6(Pwo`/uZ_qCUP+u"^A 7~{.d JqƛYV4;Jw\xR_{E/˿MLNŸ5k {ʔֱu(T)Qz&QBaߕdV0cM Ͻ7d'( I):VvZۮV?"n[-Yང9/-P.ܾӱ2&؝[yНs\AQ[80^k2wdcB,Iee __OJ(r YW~ᣟ~ٜܶ81SnjsPf]Kk}?zw1G(eUIRFrn @ XSRxQIA2 jt`" pyUM7goŲJ ^{dFl@ -/ |NfRgBT$K?]bySޚGH#A< >IYmϺ36_9^lfE@fąxƉ|g>~@tǼf #ǏsߝH og<\5nv)+`!u+0fwUlhtpoi{NqiHc ɝfS@: [8kYpN vFcNn8X=}n`(\f3`)vŌMk:o-t]zBvAx<# 2FABAQDUfBCϽp3/mQg?[x~x5Fcuonm\rygm>8 @Q(U,E1"!>궑ʤX uY Bp?^g9k<gEa6a`>T~u= ӊk&@aIMD̵3fƼ.l{͋P" qj3U`́~&s (0u)cqn+N8?J`\ /ѝ,-p0!X? KdprzƑK\ϱAaJsv&fzƦTnrsog(^ /TB$ b6"=O<:ҮD-]B{Jb7¿paM4tj!}2/H/7bʆz ?ՅAeQNpk=,Օt"e#C}еtk]eڽlI!ZPYW8۝?v8p@?C~`&m>#S"ʺS!v!]HUo`3;lW*BDP :c'ƈ> LВ1'=a* PB Xt, F<'!>C]: ߸9A. X; ~G=ܮ> AX}FR&1BZ= +!f%YZ4Z]͟>yvDŽ($ѣ]SBJ)AƧ}AA@ qU)Y@5F H }dI$N2@f*䠇u\8h8tDΚx4jP^5Ъ oJYJp-KʵJ,8y>/A_w6,"㚛&QS{JFZJ)G\1gSt%AeQ:P^AdԞ+iO P fg}ͶQˣo6b<<5;AURWv I뚑ͣe| D/>|ʲ f eQT7EBw1R@j֮[hU [DM;!p4AȹtЖ`VS vyJ证҄?"Ǵ+2A+9]$ÝeM7\Ll-3?cBx0 >J ݔ(b4GV6 \ tF{8o{@y3 QRHmV>cG>RBB+0;;vU%稬U>E455l[+pHR &x,˜qV^l vYn[e'U/PGYc]h/>'&9eڼH0_ǀz>mua*10强qfy!V1P> fS@1#IiڀlV FfZmTg=zj>ښZt'usUAG]ʢ}1."]ip`H:Ahʇ0 y.Dc! 78 JAhV22!K$n5i;ՍK>|ӯF"S}|3"$ ( `L/cqx0k'kkX_3DFl. wN-,{8l-q QpmMm_qLsǃ;[R@?a'4o(ͤ(QʪDQ2G (Beh4鼾ADF# }3N< PsCf`Y(„-04pm5 FNK_ݛ0GQ(FpQV+U  S3֒R(%I@RW\8nRhWc uD^,lfĆ @G&Qdj]ϩSu5C5*.W6Ҫs;u}MXE3Vj,F/Q4K-cfi¬wr6&4Z]}-N\;M6G`8y -UU}fxIegL_H2Ma<"c!Zb,'^66(u gՀKN  ^sSţ?6_رG@@E* eQB&H)!9WoY3P Ʋ -K ]O܈fkeJYb8Ņk1ڝy[a0_Z2q@|7 .C< 9Y DӻPVUYa&6} F UzG98#DQ88n膞6CT|^o/ᥳ'mjv\! D$M!BQ@̮ 9"c Ytx6FWI$䏽Wſ=+j@M FȋQlfÖ́er۶5oc|K b iW(ڻݕ1l)hJq),X#9q{eiV뼇eX" 'y^`8a}s+km1 ((g&Ins \Di)``Z`ق0n S X\Ox#HeC$F@Y>ϯ\Hy͔lM9*/aS# k+s;竇y77|q!:r+&3 f&"R{ZqUAUZk /MޘXeBݐ-k/^h6Z+E+wn9(Lw1mΔ*J'#A1%RAje nqQ~mz V#ZǘK[mܹIlݎ{ U-y9T!/rZq YD$x@q8RLsʏmOMM?Fogzرc+!f pk#7QUBP\mqA1sA97>3&8a; z/iPѣj{&eKwţ! Zܞ 8𾅕{N!IS-t'es8*Y T8\ng]\&$xJh=$i0!2 ^]GTc,P)im+)+W3x 8q$n./C$vl߆mD@k,A(P@JTQ1Ġ7ΠCBt'}7\ njKI6l$+  M+$w_7]!2\]ZByZ%J:SǪh?+:Z*]-Ee'tM3 SI "YQ+>M͏Q 8q5GToe3TI5ؤ9pGt |Z#MAlM"fr4jcŬ:9)AbArPG#hk\ξ> Gpv˲&okz`0,0L " q12iAݵ]薠Ta:r:~ϵK~ "t/%6z~:\7WF;vh"M!Q,!Bnʃ@sBu" Ж q>e_L) AB2I,Z Ě[`"V7AUBSQ8dxlm&11;Ν;1꾑f}H+2Z.eڴ4E1\^tTQ轺`J{ p 61)_bwԣǎ;8__önF,cY$I "a's_~nPf!nuaiCD%%8#^_  @.]TEgA-_\=Y*3/>W/#b1fc~LGaV`.L-,z_y-"ŚYFBGB[4Fa87)V\.5A |.6ftb4BeJD+Y!#p]yFV}W8U9Q) d{8GB3z\ƉqsyYbvfNqz O E^`4Q)4PygRzB.ʱJNU?lC&,ZF#P>PU!MV*g!Е @#' 8I+\6Vݻjhb%_6J۷o2x˛ bp>8Ly~(B(Ǒ$>>ez(?%<pnkZ6To*#8qce)]8ިKwݯa O@Q(r? )ˬ}'^Gh}x6וUp8}_TnC()Af4J.`\[9XԻV{Z9W'aB nvY -4uST4͐f z=Ԭe /̮a+jى2ayc ~\3tsF {vE7$2 4l{DB$s8#P[F[F)St(Tjg&h*F 8I$%181=>tQ X[_M\s381 ׀,fgggND"Hw \ 1"X,L w̩ogr®=?iw1Hf7$AL7o ?Û2#BvwM ,C(#T*9# 3,Nb8җO р2<6P EYj #,_YuTB!ݽ6\R\SushZ@e*d "$Ih4iwtjfi)R(ե8} _>?ſ>8q;s6XY)P[ߠze%*R=W&RJb9g ݉;v bT2!u1YNhe <}Y`9 0 ҊSVUYu6ʆLTmق`" ^LD|_KW|Q4$̦'))3.`(KՍ5LFq8PUʴ\,&/5[Ԏm~3ZsA`!C CI!M3?Dy@ ]|ߪ‹/Ѩăqgu=#:ܻzM*-: Y%*U279G&HD"nw]~=T(k+tW._+gҙ8w W$mػĻuPB!G CތKct׆*Mmց|姱0 vla4` 5la|g0;73OJfʇmlḟkUIU$r(2h~zӧÇ8|ŹsܾNS\Yj,IhdHbM ݂ d\9G6$M[ngjA'㫰 T*D=FFI$I23?[{[KkSǰ}nSSbNtGq^ , wVB)lKJ(UU@V6-tv:mzA3S~cW'q?.p HaܹgfXRSkb ([qۍ{QoCHxo51;ݡYL9yܯebumK7nb=<\_ tƴ$Bkrd]3ܽJEqTNZLC`Zu8v$8=}PE^`mcZ2FT%Jiy7 ZFTNpjdH-1y8R24C 1EȋFbm6MHa$XiO?_Ih] T`{9 vTjZ_zXJ!5>눖ֻ!/ AXv/spx9=1Mă{DV)s iD'c͍Z}XށN{au ۘS*K!P;?;#|4EػOt/<<E\T& =Hp7Ϳ`>EHY9bP4ڢ35Y<Ѓwo_~g?O@ǍF:¥mP׺n4O zXDAe )8nL*v |H|}8F~2BJoȻjY8Z|I2P H.0rAXcrhinp[@N2oGe7|Hm+WCdH,VDँ:9gYX[]õkWqE=/y <<^eKi*ueqסFw R*[⫝̸Y6̐ 1T,A!"aaVHJ8W}YX/_ Ǎq8~ra~m43HJ {4Ŷj"NbDf: MiG ]qCp}9(xdNBC0iAbD]xtx>8Akޠ8I0 piLt:h20l (Rge\z%%"fZ6ڝ: mIj;B $@os׮]écO藏O{;RXصsqdFIylDJӃ|loL0%?L&Y/~_԰~g $q 8ŷmtpǾHe[ۖj F5 M\Y=v!BL+%n:ȠeÖ'\}\x(>$f#nր@Y0Α$9}gGh "V=LpřxfU*)fVE#*2BjۜVX1N¨r=9c e(#p,IT!)x(͢&fg}n;X ?%]AJKA:TpYi82}~v $0e5\q^ĥgqIkeTC0d &N9q{_^pm2 Hދ]h7051C/n]xg@3opݶ0NE#Ӥ$ (ta-i6XZD$RAGJ,N DdoylC/2l4X^fW=>/cem33woϘyyXDq$5\݂”(1 xΝsgp3X_/1;Dg `:`ʹ qC>'uǥ5EmC"ZXw įa\:q9WW]&׽v033n4KS ׌b;3O?s;ka^sUSܶ~z @ €zF+ܛlbe8e]AIBGLW?|͵ Ə;СCȑ#X,KM4=)XzQI$ҵir B>.rY@pA8*Ls8\,E:J10̇ IBr(Ji)M3mcavܹg\ADl-1J ˳sHbz6@@QX]]ÍqEpgΞOcmEbvɉ9쟽|0Ϩu;#t{Zeѳk9'&Wo8ZDs)x_ k7rh<:s'v,,`vtY="Fn72}* V6oOOtlf4ty!84|];&!b =La BJ4;my_x><۬/0%"/Q%C7`SC*f@Wi:kN'/$8T޲` t] wxİ" lsR 7@m+/jaii Rxp3ō+رmVYHR*$}EQZS shk|"Μ;gOF@{\=ط72Jp7d޸RVG^<V4QI:75#B0"fam&4Ƚ܃];wann&'&h40PciFa<>(Sk*S\# ,oGN9s* e%%H7p垆]UU%61Dh(B)cz^x 6vBn@ ]g1.'#x>Ũ-V'hd lnn1=rz4ʑ%GUR*Ê5{ĉSg19A>UnRau}++t/ǹqYP@%/*=C YD mk U7Ժ Ky5nnb68sW#8׭*)ҕe#8ïoؽgazr FMۢ.'H ZZ ==f\&cn]o{'<.nۆF."8ކAu8ɝrvDam @h0ڍe\Z+됌jб7&m317IOi&ƴkѝ)C:%@o^= TI@vb]۞{9JZ܅ XJ2.-kk"i4 \ S  ]2\~ (*̉%(rVBl!/VzhVCrZ{tc nk˘߹ =70;3ɉN[i yxqImTl@)s=;0js҉0wݭ4 Ij9SƮ0pc 6n3vۇ] 035N4\-:$Y⌭EgڐYp3$41QR&)܏BUn8 SL3I~NiO6;֯ҹ%ܼuxi=cox#A/F3(I7C@O&'%iQG IH\]‰sWܩxe\4@"$UB ݸl*~*nb,&8 'O`zz 3Sޚzv{tE kINOeyHfY~ȱcn>ux-W2QQ(LہnHwYXCwc ͬ$.▻n2H̢ȴqM-rPzK!SdYjs`y6B#qr81AJϊ./ca~{wƥ+Wv f7p /6tm[Fq$l( 1s\_@1*tSv@&$׾-LNܾ` Hs6A!(9*URd 2H8:5-^|#SMjo [Vw$] J2"B;D'MIdFuZ6 *+nEQa7\s EqAI$IN~w`t hxIHim_ "tZmp_4:<o-46y E\wZo W_2֖[V}?oC v$3u6wLχ1&؊uM߃ qR yE|(JG¡ BBp1 (F}TUH0 lێ}{ۙp4&ֱn%ptmLNĽf?`E޲C݂BdbcE\YU[kuyA *lSfx,XɅGVʅ2t+@QjK)ƀh3n8>cwboc 2GQ$:&vnŞ] 5?۶Ia8] Nnnf#C$tATUUG#lnkKqy*._WqMllj)"&e ,]_:vZ0c2nүMf+DK/ ? $39q@f yҜGe#Iv\Ϝۿ|RWT &)90ϑ& JqRM::6di|4pi) &p͖nQ0wF"O,{1Qu1jN lnp8y.^V ~)%&&7_8up.0-k7Wۻ ~Z641Ss҇0DpmkPluBҭ.['7=lp}by!eYb96}0in#1=sTU(G11Mll0(+Jӹh +ױ~ ۶!˚ؿ{ϟ1Js- °zHs!u(7FKQ $IMJ R"m6s|ȑ#D?5`nn;wNL=9V@q kЛͧL+$1D'r-ĝP .agw;=gAUlgfˣ|7GfnBcuy33`/lhIv'P!ڷa VWV17x;ޅ|gSy/^Gq=`hZC9r͂T)PKznyB@1"`D 6vۀ^,ʕE,/+pn+PH@$bݞN`*ςTElvY# N̰}n WA^y8~  (M/`qVog` NR Vo/}[*=%Yf)3M)VS_l0Ɩ8J"S4 QiFgnɓ; a& \;g5BF:JDI+YTlj ID瘛à1518InfQ)AO=7 n29̆P `2:n.ҩKx%t pt'ڮ+]c3HvkhGir^¯ammBtmLML;EA@82>Sm._Mlūqs=c1Kx +,kTjQ`J , ZILOt t v܆viUx@@ G6 JxYLthdia!1DCܸy.^K/q,XFIC0J0#G*di d&x;J͌,BQVFX_>J)Z6Z =pDž+x3xe\!6%np2_v#쌞TV$kK9ac} 'NmITG(c6~FKay42=׮k8*mBgldرmNL/ɬ@+4aݞyX'w2ÉqF`/ƩVRܶ6#Mwm8sسw?ul6QJ40mbvf SS^:}^8/^> 8]81*a_1wx3!J!7VnFB$WV7{(}DdLP'PQ۱)5J~ cTXzfheےS F`o{+ట@(9[7H'zS ŏ8+ڈml.qzzCI4f08ץ*+4im 50Pr8B- `vv?~\LL1SĈYz"hN "[e0d Q#B7 1^ x[uc *\bÃ@]ӄ܃O[8N][C8EMIcmmX]]ťELuۚ0żcc[(4N|G HrQoÍ5y4dv`=Xص3s;dMa0,1K75.vj5&C2l@UX]YQ)SHdO *@ =! O7[`mSSl|gPllB3$R%n\;) c\ klOќ@A\{iW0HRlic}}זoD^ ]=ӯjZ}{hlۓ*&=s|fSt#Nt?=|f(,⢜'IZ}LY!MR-FC'vҰT0݂R&ra jIp;|t`al63iAYnGQ f"Ʉ./crr VSgNCvl1{&R!M3mCUUzD'g.q]سgfvCT`TH0pdinwM7|v9Rtgϝ((Ԭ:>}.aG\ e>" &H@6D4Е$UU &f'|1%Mڗ|AZЙElO"mřn(g$@fTTHJ*Lth7[8s4U[ jp0w-HE&,3hIx(ߕBDQضmF!^:w6nrh4}ܹ;s~ӳRasc4,PIM1vN kQ[, 8Kd޲ʪӧp nKWjH{R)&-)(A$PFPJ*GkP-If'&?`ccEUB1*zJ*8ElCD)Qp= Cb- 3pVӄ..D /`9LvҐ{v CbO4 T9[u}x?ˀ ޫH p/Dֱa T^^t$Em2y{FZMYc;-ˠ2TcBngz8Blv($氍e8 X%2Zm%b G# 贻vqy\F MS3o/_-(0 ^E! &t:h6ny.DQde?A)0ΉʥֱݝĜz͘˽e DQb\TFPG zF@ HB^o>U%QV% Z/7kBS sJzH#Iomjx2ݍ8JȼjfIzYW933 b WCa7&p+(m)x~- L[qÈGmrdES(szZ t'ۘE*"Fбfdz17*z=; ۆ$5 9օ~`7G6l纈)ύ0~*q~᠏m@R W`s 5{G#pn5151nFCs. PU%Vp \xg/\ًr&X5,-İ߇U? p)H D."=SٌWPD JʡP@jrD$ ݳP^]Z%ʪ!/JG9&'hd1'=jyuQq$& iǕ>mw U(\ ARVZe,Kv=P#'@cayXjMh$ir/ZӢZ-"BUwwqE6Iqqo-9ysYAֈD)Oݖ"nрA m-s60(rG\r IIsLr-{DCͯm"92_GVja|rĠޒsPP[G~1Sի ƙ fe#Gd a:uYakc bE9r#4s:c01 HS$:UΏw"BﯽkW. At)w -5H&9(5\ hY} FCpuUcgg;/ϗ8PW5`2bcc $5繯@cPV5'SܿwׯWͫ7q!KTHp:EkEM]+ 5;1WwJOZSAH\ -Rkc ޝu P)t4!C;I=8M2TutF_I ( ӵHM54ˆP@=]۠h)YBTB4]rml4y>_~q*` PIxQg:*;hJ(*QWH-JõEr+rԧ>_ "ඳ`2 iD(;;{(JK'hJy*r"R`zr߾M <%~Mb\7ooͷ[bh0*CRh6zx5V H)UQaP 76h8o]N4!ֈ,`㬧PBE%Cts4:eJ/cd2f;8>BV(S3H3 v])V%rm֎iuLt4 .:wƜG\;<|<*2hߗer$uhu+$+7뱟"Z&#oS9k4,_y핟(_ȣ}Y~^YatFJ=,sjIӓ&`,#)kMᛎAV[ cepq"ECl*$ϔk[Z,9\F>O[u@ 61[Wa溯I)dwnx ܼYe `@):PnD,Ʋ g-,ýcB\b0a80M[[w0Y[NRt]KӖM"^P:kJU е%LאB˫RijOtET[Ġ@FW ~Yd=hhXXӠKtM 6p2YtE6hvȳ۸p<<#C U!]&xCCw5!MY+C]!&Z!+%Ai h}5]תTx6} _O~S 9E #y=C"Ե)j$bM]h,S%P?UW" F_sWF ϡXiREB.l"gS(px|;[X[9M$ʱ=v[zTOA\`F*ȽgYE_RrnE;,8=.)^tvVۘǩJ/_~'_|>0p3I|AYH 3)Gc$Ev(9v`>!2#'ځJc0^@"U3XB./a](D ??4@E?rIZ`PRf88Y #llm`ss7orQb{kӟ_Ux5dl JI;V M]B){o>HDalC+m8\y-G#di8 2@ePsc(MPP d3 uzHZ.qvw L)eaA6mk4u.aY}wuI&Sba8)vTOgt z/}ٺs n!] uE{=WTk8,H.m3g+р Ng(9:Ά4h{1(cmbvr]a}-X8ܿ!M z2{8t"OG(+`41qۛ*rY}ܾqmRsQ )&PZ8|" !vY$Zh9 acWcw6`wkΟ# Te|qp|1(rQġ'wf`ݻXߘ}{phR rZW92ٵP؁\YKeY?vwp}W^i^* ЖqqPG2 G~3D:K`e% YpxxŢ "E\]G:}-rI1Q͑_@,qm<|,Μ9T|I5`ooƁx24i ʧ.8,h|k4N%)\| p27Gy%.vb}Zr_=t;w.XYyecM5$MY0DC#0"yj5kXK[mU, oNSE,1`wwr+Woas}db6qY^jِ50Le,S4m9G9;B}|&;P$!1j>Dž3888zH0qtt):%V)iv-2R#҄c`mXQKضMֽ0(J|*cuT?28T4c/ؑoQ+\ymܺ}?!Z- G{y*E v7 c{ A1P15ZM*X߼}׮]/Oi=$ Z?W]) Pm; ;Dž `wgwF~x4E!O34E(T!IzNA79|L.ԑʂ$2Q7DN"p{QG;_E Egjr Gc erP Fp 9x- (c1:-D#!mH,^dp M`o\} dQE'lg`rDXB;u~Ca2Y>1JKg GTjow!Ve>YcM^}Mg |O=~hv+K*JhAgl;k-v Bq?~꥽=UŋWڍ:I?usJ+!(\,P5QbiB2Wz)sTMMxG>15ddpr<)n`0C?O`ՙ 16 HT_7LMUY098<866qMq1@4X̗(gSyTkX!G!u(TU  +A;C9ضDYUXP ]aks6Q-u]׵岤RPd2cÓ{/>'DžccsE^b24ifTf9ItAؿ>W鿂Wew+W>^ Ҽ 5&"Kԍ0 yU7"'-iv%LWT#C%M( Ry G(F]WXKh=<'ދ'~alln`<XrTpxxiq]L4%fYR j\oc;kX,p}U㑇}cGޮn~1pP␣ҸVH*N >rX3,W猵,u`vmiPՕLwkWU+/^L/]tqR0b}46|qxbd_.oV~{:Em YdEhl(4K`GGPdk hR `>\xN0_.%`uZf ECXv4`8Bu "-| d܆P\RY#ĝڶ 1'Ǚ!I ZB} g*6C; kK7MZY^"1de@zth[㺺UW_ʪx];iYbP8}e[חt h #\y-}cxo~`MU`|6l>`0d堔g М%DH0LĞ^VLϺ <'!I| f8*4MhbZT h.On#ܻG.6:r9CU/gp]^my(1L3Й][Ú4vYҪOmCۥ˲h4d<)Zmij:nZlmI3; 4m59)qL7aAӑFkȴDk eãcܽ}e]}?|1)JQRX7 itc> 榯Xy[(_z]aއw1OfB|tpf2Y:J-9t L?h9f 8z\ta}Ԡ(sV+h{FŁŲB: Gk?6vΑy/ZW@۶8>9h2A(IjX&ԁҽSH$:Fo^PA8^K=;0E^ K4y c %EQ3d𝵰]G|LK06csmkP,xrVuD! xspsrΝP MnC`*LIoT53j=99 /#CMZg?'x;;(>TUbŐ*Yׇ0JT+-:XQDOU,1Npn޽ܹ}?n˹_ӂ6664M։VJawgD`>_b N&tQt uJ|Ղ.(r3(xP$%AdžBQE.*p%oY7b{=v>%;I)?%ɈgEk% -b>Gׯ`4`}mh5Lgu-LK~S4e6Fy=r[قsty@68<<%670PcMO$g` (O;k53Zlh͍u{dNSҸֵ8:>d2“O=g?a<pC Gs 6*pG;8R `폵``6D u!oɗUbcܹ{lͫWz|.pmpQ~;4˷Vfw{Oo"2]rr.a4F1^}U|p9\&|ZdibXxA %C][~_`1J6'\;ex<Uy4R`N߯eEhv?뫀 f Ukkx|,'A@Żw+p[wwyハ=<qXzATbrP+x 8]^]2Ns΃|=S8&tl-ښ@Ԙ/89;߿oE,˟wϿ ZRӀ߼V^976Aj+M uJ=?MIx~g?1L0-(]p`:"A!—@𱙇;fg $~{-4]U4aqEAKB낣;*䫛 U6,o^R [HRZ&6gxCX[fJͻ|iuLOΝ;udYh4!;jsQ*#A$\')Ypi@T%I lobog'qέuE΍If%޹+Wo^ yrz$;^qࡎEA$#A% O@3K0j]סkZt]I贮\1=b>PV?|@"%(+_ȋ_#=7z;;;X q[jܼsXLOc:44vi"ŰttlaMB9a1k,G<ÉkEZybm2G0=XG8. mc~{{Xp0qΆC'עOѽp8 uyb8sX]E*='r*~C@4_R],vMmUr4q*i˦~yծW^jgA3gֶRt1EU.u$hEsuH <=vdYeo /0I@T':4ճ4 OkH߫PBۥ,YE+E!(NuH yCbωl6ƣuubsgçG>t,nܾ>0lס,moa<3 F ,#s6 2z1 NZ ŜqcvҔa<籾,M}.hV+s]yJON_ ]Z>C*λsp=D߰}mGFߵ-MQU ij޽r&ӿo {_lkvg?N:MC*~<L :0ZK_ <_%Ds+oMSc>b}4B6 vI$V 4RB3@<HUOWPP~E!\-f (,"6>d3%Ng8sf|3%= dGΟǝ{HaEU &i10r+\a4kTU"/28r<’0MRll`q=$nܹ7`4yx]4>NzhɓQ"WH,жD"tiZ,%NOx*IG> @^,-\ dyC㏁k׮qy4$/ljOMPd@Ac w6, Q1+Iſ=?˥ aZ5 14 b=rg& ;C_ \~*>`2QKsZ"- R׹qepH=p ZUu|t|[}G3L%77Wer5MVNt U]סuŋɥKo[1SHrgk;:Ӣk:t-y,IQbAs0]{WO7d<ta1N1 PdMI4Ԁ(O]f* oiJpciin eU( *:v=? ҥ+o= ~ ٚ4p8.q|"BE(] l^b>2}} ;X_ڄ >M|vęp"\W.,LA1bm+:E\Ԧ{E10PxzC. Ɍ[C)KooUDU-Q.X.+MCmgv쌅\m: S+@ҥKNN}vmreU;:xʛKDɘl:h4F27 Dv~jcHrEP^Hn ^+o{w,95aŠ-r@"rh|0;8>gϞ}xC8{WQiYLJ&"B-i~%%eg-4hg`k{L ȳ,`3p60S2=eV=J&Ox:3讗=e=zM;> +]S 䞮9~6r9r efcJxt8Xk@y(n{g{R~j1&њ;Ӣ+Fuon"IRd,)L7_K_Z 9cO9̙3Xh2B1g {S?^_/-^ŹȨ9`=GsQzJ)R[JM&hou̙]"DZѥɄpiZTurYa,T5Iyh&qױ6xD_zwژD4p϶<>ϑ{)Cst`K/=GUEnvwv|C _4eY,IZ9oZ)PQӄeY|_{s/~ t >n]ۿv'rT%PZ)X2 I2$ ,Fݘqs U}/2>go8Z1du-cL&CiRNaR /:|XlU0^5û9Ȼ/xQjMoMb8ȉ܋ZNjw+)%M֐994|/_k7kQhZ` -iQU5j6nКE`TX_cᇰ6dh@^'t_qP% \GD.J$wCs%=q^>|uS#2AuhuUR\*n: |Kzr(ЀJ&Ӱo8y /`=i }_~w곣5J)}`l٢Ţ\[.>ԇѧ?_6kちdeUhzhBEI"D7 )D#O*0|ugثVMpP]2(ry{B}ZKMTU1ȇG>'>>cʲV) 'wZZΐ~&h8& ɠiTѹ UޒHsׅɿW|-]}a"I=>V7F[ㅲ{"@ƧU_?r )J7 AYю_U%1yrX;c`YtRo};g71]JYmbZŠz7ͷ~߿L8BŮ`,W= r`@3rVJHoP6!%|&/`eIijLg  5z|}e#/aKMӠi:r tm@`9, Mvc'[>BvVοoǁM4 yw=͐Zz\9y~TC0~+ϳauԞ+ bpږuYqߐcf< erFmj4u6˪N_G0c%&W{쵒:8=>u[ ȅJ, bLPèQ8<6|ڨVl0 _ __/WPoIM0;PڐW* XI*.!J!3Eh*AR[u1(6Yd`,ˉ)QT% "m8\a`oyUqCՋC*$|םP5.J3O<_ >Tv;Iyn'<23CS. , %37 ϧ5U*at cto]yg+povppppmwwEzvPNK5R4kP ͚ k\g;wPDŔ^3O:3 9Rvz0(CBZpZM8FݞWW%eISUD27E8Jn|K^mmZ }]=o\:\J"Q:0C:w\=>nDHKx]עaE傘]RQ9h丈T`1]kңk/~i}S"鋵ۻM8M(6tʡlČ+Qi8백9|#3gc0piP"CV<9qʓa5M&%&忟eU36K'ۭH3m &A/G1rSd  w1K9g=`L>ӝrD]n^YfnF?rf=~$#EQj ?z{^| F$ߞuc-j]ײ.?vJʦ , ,%AבT(};8v6Y1w_~8.#vpƹ.[7s=/R< MqugcgXSJ(c$p <S<b?8<:t:E~Ru;u r!4,60<6 .vViקrKyG'M It(d%ޕ+GG(,Q6tA|m=>E7`q [!{~Տ Z_" OoŻ}g|]('a^>% s,˒MbBǡ Fs}$#7>cL|k/yacOvAԋEPk4I0 8ӡ,Ɔ6>)nܸ|YuCz$Kଂ Ly vEgHzB4.9*=ޑΜ9\zՎEQ,g-1XIJ۝P 4rb0{w?Z`E1JݴB!u :EE4$iCIsw_Jd^'Fm%bP˶jLpfTDG>ZKt^c`4K}Jk5+sx(@߹xlw8{}o]=:DwM9*uUJueY*+:]=G ŐDY5B5`0.XC,,h֔Ugo_`9˗/:9w`8Ҵwâ*1P`@iэF#+xG߃^ b~MR!.K3A-.Xt>wVJIqߔ 5vrn3a dzjtZ!T5|D"zkF?,9~NC11Kߍ w1CϱŽ i"diiW \*KuK}1aևp.Ph!.po3Љ(@ !r)5Gr"SuXJ7UWk9 &4) S8RԸӴ &,B誔FuM(#ߖ k  & h|:VڄGuGLrZy7;KȌt(m60[ip8rFfE| ̵K~f:TM.|Lv.`?‹Wg7zg-Bi3$@ ZfUa/KTUx|€h'Fy>z: ϱO%T_0V8DY589yղtUYeY:) ?<|s̙7 U F)ŸeCQu4 MhoΟ|IȲGçԛ'WqMEBH!vzLQ G1)T41JN'} \qFG̽Psl,N2$:N(=*Dl*]E:g9{e2R( I:zRϏrwO?:9;ˁG9D'?sDa9:DYx\w]0~R׷]rA65U L I)n9е Bѧ]"5h'mkT ML=SYn뮬;<kݿm̙?>(pZd4Jе t@<0 GWa$ *A$EQ y/h\?"R$=q ($a95SߤsHǘ߽k0|$| @XL }F\/b2NO,e yCy( >y\$1\"cOIQȲR%AvL1Lv(<|Ԛh'&]2$-(!ߕ>AI*.aw8D\k#omKa}M_bLe/Nw;4U:Up, Yjt#'k\~%<ēxGʫ(΄ҝ +Ai!qb>ňZQWR>D L`i^K$%Sl;'6RxY(햢D\'ao*K1u2zۃvXһqkl?>iS.l<2|2&rN]^C:[1pKjÚ<$;uB 냕|o,)UA8'}۶EYHW:ɽ* ~cq֫:?kkYEd>Q9m8RIL98]klĵޝ78OO>OW'Li0WCȊuUbdh,J*n, :L\^}U|gkQGj/:Ty!䠜 V9DGyrz$D@kiyІ$l>1~QYh:yx֗$:r0hv^N?'iRlKtcd>;Xg/qS'Ș~@'! $%RrLS->ʇ> /3ƵW,j_)?OX)H3R| F77Mvu[] $CZ sap6".a]]@J+;ۀ[? YJmLn (yRhq5~ ?t&@תriGQmk|e6[e#Ahima$y 0sbYx˯|<,wv \iV`4b*a99 Aj:-Pg}.9 R퍽c|>/Q*hΝo#L7If cp~ct})~W!N؀·9/ }OQ-! %gNcQ% }4)% }Q겁5ID]mעn*4MRU߻qo88 ; Ғ m0,HT GN9g |pxpbf4A70p# RVʃ@1&n]W)YXm2b10#oH}G_XՆ!AZr{_{?3&Rƥw]C\iU2pv'c{e@<^oDus%hIw“B;yJ2B²\b>+^?ۥ&GfCϟP. eU NO sU]'4uONf7i_ۍ{.{W}Y&\/˲D6TIS&jrkx>_ 9"DCpƠj,wˑ@ ed*jQʮ8&uz FnzG.0“x\J^H-q ! Fђ9;CmBqHԳ\'H`d60,\ɹ107"cZi5Vʧ /QDd\[0a!] ވ1XgpqDhE?[ukҔAp]c3&鬻{m5okQb~sbQ8gsiqi x@oU oc} iqy{jn&|7THu~ (7!vwv՜; Á` ם @&%^xܜ5;|n~γd7oKyFQmimB2֓xLpc tcrhJeqP7woƆuHx/.]tf!I4!ߢ j;yAtac4 9Cܹq _y+xCki͉N I 5LfH j@wb/ Qy"/nw i}()-8] az& VT;RG`W1$=;*oC.D @[uN% $Chה& {6|^|d+d*PtGoBЫt }P MZrR@ǥxJ!VwN*Oll&t 9˘u]Um]@ڥK85C@?8g}DbC֘/FHa']uI}~'^{>am}9di@e$)s5M!^5,C1wO@kM Ou-yEJ򭵘LpW믽hȹ9w)nVN:<`e# sCκ14rzI24t]iy$ۆiMMwq-6#;JDol7l=JOR]ŀsxsg rt}Ο OtJ 鍠(v(0O)Ǩ ιt7aʥDs]kIIHѧEe]pZuʺ?Iߕ޹=;% kOQ_"]S>rc C(ϞwoKE}ZTY$Y6nU5L3F:m0ڶMJi\oEDg:~d^@SB"#zrvXy.{v)) Hq8`NUf<':2qor;܎0<`8"rt] 0ψA"SGK8R1(\+ޜ)D"-sƖ&;tƘŲ|.l6two~ zZ/K|dE*IرO!tb8p<p+4\k'Qۮ(XPA0n?=VGC'׵jΑBL> qҥU~;mvڛqz!ps r/Ӹg{H2ZI%>nFAA#s cZM`/@pn. i @.\hM'~N;:x؋/MU?jBJ:I Bp~0E3W~W“O~eYA^j!ʈa)&=oQKO'ᶱڮ BIvEg-:?2F#fFm' P9_2~Wy#'ynB<&:{8itl86'J yr0}-X[ @nqT6G8:>wpL$Ҍ&6| !X BڇGL sg[2n``a`Q k@PֵmM7F@nݺ׋,`ϪJ: S r 4bZyc>CAᙏ>[7oA%؀ !5XDTL=FA:BԀU5O&kR㿇ty8xOkJTd1\$upsH+Za1(Fs@0P+Ei !F ƣ14 p|xeG}rNFuwga s9'"p `g;1›(%+Zit񩯀]۹mUYo;Ku]W"۵tpEb^2GOGEM;Fۮ~g!w446 i88-WG{@ AIW~K/ < K Q~.F-$ a8Qk[Q+JYO5C9N&e|21w'0g5H N1q5\zhIrqc'd`3#Qķ eO9gm3~ (o),Mkl g̛'''G6Q7G@ܞ^?oy΂NJY@d՞ as9VmZ!KܼqǠ1,D@`[/X@q*L e >BJ`_niT^ h7^ U94t o$g" .mOp1hC낥5 -Zhopγ. 쌏*\.`HzM', k=]>D JtJ"Ҁ^*BSA|6 yĩhBYu㪶Mϼ< ٰ˟bo̰ ~ȴNg,4AD:sa7_<1=91s89RH&$+ Yo-o~׀1Q_e/13Ģ3\x Qx&\"cEK9?$^JkAG.12Rgi  h)8$ixdH!4DIDATݿ# :!;>]iW zg'Z^Ҭ#`U8T*0&VP_** 5ִm,r6|NwU/z'= r9hЕQ|TJxdYZ.1—el#~_Ѷ-"0)t$ιsghn"㣰"v肈13QNj)y#P_G9ra»p`%Ǖ)1rY!1 Ě1$bl *Ѱgsr @e6۩T('6nQoa)eɩ)* Wu\b w&.ݹs>OxD|2^|o`X|W&ŕiyJ&):cX/d} W| [[x}tAQ:Nz-Fs\ȾdX H~1QB\O&FxGF%`:mϒ6xnR~w `Њ@ײ9ws, =YFdd u1ãc,%1H4q8<$ :_w Bm-dr7Ygy^g f#7q1njUW*?} wW?n/oY0Нipyi *OZc<6Г8.kE΋HjRsI#IE'ӆ?3y87N2^*,9?EE"֑VJ"(EB:7he1=3D|>6>#BY 1AdXftBچ% <u"I/QغiQKdyM9_o|vKĎ"5P.,hS S;R #Lװ͍M FCXX,pt{w}LO+i$K)&o]\|/hHK=C_ҩ/NSyzi\iyR)jm:4?y㭟Q{t߃J R{{ܸ}P|nc҉1JHo Q4|q}XW$I0Y[^|}Yom+Wi.NӋ{tyӿ+w iK#/ȑI,9( TU ?g!}fk)A 3=0x"ݤ:*!!@&PCU8>>|>r@[׼{#MUr$NYI:ox^q/Y 4Pzx #8Y+17@# [ApkQءu]u_| Je_=ps4it yԏ1^w._x.\@SW( v,鸔#3zBx@-0ioᨥ.f"c-eOٕs^@j᪎EClg`[upڪA bkw[GN\8<<ݻp]߿lFuJДӯ)B ܎DJGMϙJk )Fϐ>GF/W9 uY3 b)KLDcPڵ*)RiU :ImpǧG+!;ft prRsE)7ɅygD*ެ9*4XS`avh~G?]Ot]-Hw:QgZ0i2Of(_7nɧcyruCN%K=.7 ;ܹf :x[P4=ePd=D'XEd'R/1f䄈%U N@b,ϑ%4Z#5brQbY-if8iiB`iKMfB_[D=Wzk&MC tE%qCHLC:v>Լ%7R@ۅaDXC]7Gmxu˛~| Nt_&KB۶]-B9C!>qdޘ+SXTjSd1:vv/\@uX`1_ųă9BΩJ9`|X\|:_8$h";/PҢ"> =EG{LbWc::gߒx;qbwYd㿬N:ED __i R#H3h8b>lml6ʺr4h_5Ruh4r!P~Q;ޑ 2vT|Ìxb0,%)4Ge{Βg]7hnUyyoTZ!Mxq Rxn҄n $6'EusଌL3J@kfMjۮ5D4t"%I/w|ZK0yst%JksEݲ-̾Rݷ0.\ؾp/*.yn4IRrtkďgPc,ihfY$ciY :DöA SjCNLG!z*am"'!I"!xpA>e8Im]KCt$R5=gZ=C>XgHܽ(Bޏvm EXca::s(8X)Zb^Tfv6o\y_Awo@ptPd98vQN)@YhpoY9U)Zp"R}_'rki Y1Kx}dEx Mlk K:Mۄ+!(U RPNqi,dZcڮCYdpT3KGewC3aI98n JߎZ*`XÃǯ]{6c`߲_`L,;Zk4!5YSD~R rr/MT#a=;ADJ5E1@^(En#;FEH8DF`[6b?Zt@iKԩxe4(&ZoW譀WK2Wd_R*LS8.}R9v<_9y!F6`ORe08yfEոw$EQXkmXrQ/}KaxG]t~'r;C)5u^JJ K]vJ[Of+^ܬEҔ[DO_Z蚕xzcd糑؊B7;Ct;0Q?g8NE&LwƉ$|D \'QcG)8!ul8oy bȯUZ?9&'t%$iS_c{< @_ñjR.!I9h r 4ЁY|]`Y``8 H<oj& 52ΗD mjMuqJ<8i *-j9Yz A\t Z+A?*+V^Y$}8(X1.N>i($X5I-| 8H*ѱԝae>>5|Auj\.'/^rm5~`mm@Mc*zyq ?DB~Ee9 (' yGk K 4lZiJI c<u1?TZC%8 A; t/ ڶKଊf H=^_@Dy#I~'[2U Iܹg#L:+r֚=g,Щ Jwfp.,|?F4Ep:GFA@SHF[(5M/}Ksܗ>u_!⡣ȎǓ?$9in\FeZOݕE%$&< u?6S EM7ZM&i* D [>W~G arڰЙ &.2+$a9VY ?b'tq)DW]'2T5)eBÑNL %8O] ato*D"!733&9qF/7n/~;ǏP\rwo @Uռ,9ԛv*,nsL RqDVTRGy~0?=ڏz6{0@*# U4 +"x [e5% ݿaqOvcsD~qʫ}ʿC2ABU.x48Iy{iXV1~? -K'E?.8%Uhm^vmx7AE)@]`'5,'; ]mM :y X|*8O< >Fᜑxƅ!A7zy8Dzy)gG>䃊BrHv -;/]"_ĉ LUƙ(qb'G n\pS*HrZE2A0#0`Uk`UFӃlZ!It{i2"-0NPoA!\IG4lsƊ<;hbNh*}}G:.%J[$ i:8|5' EuN+)T9zџ>S˗%=)x睩*Q[0irY1|-ːL&ǡ8ʿs[F]UP>Sm4(Z vp̀ 6a+^Ͻ ,=y|⇨s?h t{/jibP `@&<)(N$BxjSL q.[^D*~gM:c+ VGW+t"Y-R=`Red'hΪa.4 JJNJ|>}rN"961ܠ'{0QGG峽KJ@zwA_QL-Ïw1 됦iR5?}^z Qcz4r.|Xslzc$nA8^40U08|!w0uu8s&b( }5VJ:(Z eK:> k,uJuJgu*poÿ˿K/$w{W+86?t4|\qD]"Ggr ȒYA' ؀I;D]1/T s*nBBc_rt1 Ļ) Hm D5"|*Fs=(EMtG\'׋d=q9U"9W93xv`sd^({|2\t?yS*щVg:g:wkyA# ){¡x? Fvd}c:Bx1ƴ[ J{ym Å)DX}MeGJr2"H]@ʑ <9ɹH>_Y!ob sؤK"3Y-ٟ[ⓗU,$Id:/W8(WJu /@{~_ïOp28%I&@gȳ Y!Mlx" R3Aٙ$*C ثCE0.)La@N- vC9ջ ; QIbA>H4AD&Si ,Df\5'TYDF[j$spZ߀Yח QZ89o}WڝW>I&" Oe,MRXR1O|r͕ $ pښ"%4b#@hA'hsp1"%Zw&9}?ENG^ɲЙU)_yDc"t ]LH `#T^|aZucE-|w򽇒#)tfY2YdJ'lږO޹q?/{{{N=# /_x~wxgum&!jf9k4GLx gHpܪ:ѽW&tL%%A}zJ;fXdfyc=k\9q8*igOx 89?Zj;x7|4n+ڍ!O>.: :r ;sg7ljqBUc} V`dE'/K'ӟDn.]pi'"˭6! Bx2='5tN~|b",d~qC\9. P!a|`/\ҋjl  HC? Ȅχzf4_EDija.Xԡ:BF:C/m~tV+eTd 鴝/K/2O~|GQ0?/ǍJ4 H3if{"$t< 8(wÇ+xsRJE'` 佀P] Hp D34= &RjE^ A+ hEtYYN;׮/݄# ~\}# $بH}먴#"(UeF\;<8/o}_wy'Gr#Rzύ|j y=3Y&D*e$$A'ITL ^X4hG+o3."s>b8RCH"IXky<3ՏD:(- (a#9Pk saN;Aҽ@ ix]x(g!"BfMAB,Kk ~Z;Trlrg0ŽҥeN0]J^d-`|f,(tInGkGGP/ .1 @SJ0m֢w?~YŋӋ/^xƿt÷h$׻;(PwNHDeY@Y ?* ݀J\_l^3" a{0DE=#Uݠ{0 c]hž{·C &8wNB 8LFb¯"HEvE ;C-tWSw8*hXNRբ˓3x }=ߪ8rſyQKp@T, N?-SY\ie,DC%ڹؤ|ȵH 8_`R -=z. ( @g~i.J#b 1KOp," W 9J6*My`]'@:>$IDi*Lrܾg5_㻩 u /4?1 a~Xͅx`HHq@a L=^DO$c($oGڗ;$@{ ąNAtB ]Ɲ8)':Ǣ,Ks&k":J0l6奓~~Ï؏eϟ7/}|7Eh(j{{hEQӔp  ⩻&OI;}!IAFa }bds 'IUb/A̧3TUl7nS^onrC"7&:nPi҄`-F.2C@0809~\cvE|h )Cz=&EwP ww#6rPpt0j PCf|,xr>xtń$go6<9M XE꼜?to7A?<;(?r\8s,4ӁuBuBR! ̡"HR`P[+%##캸;І'!;R%9~̮lj39Ύ`/88p4ԱqbMȒ$sr*~Tr}~,;::WZk=~G; g%kmΤ q/52ʛ R)-{]8$f>}$Bht@AQ ")A}dr@aBPhI*G74FE z ``K\a`1g[A)eZi|j^^_=<3PC|o; ȿG~oԍR3&4\4Oz`T 74@.Mk HmP f'% " =Q8T^ԗgSYN@Ej·\[Yk :7§(_+㵋䥂0 PHmp6Yaf;BpƖ٠dmV\YђA'녩hwe1#Mmy#mnݵ8Ipt:<-q#/913=ݢ Ed*ǃ i-8pǧ.L]:/93?gDH4CgF:>PFa7#']mn?~[n'B P{R3#a䦇]^s-(D$1CK1oi_^^zcyj9H_y]< sx8iΞ=Z8 j퓳=dGLS *rPD`غ0 iYc|j'?QPc\B>2 y^/eYvwF}ն.`$C/6==F)e `N@+Qi-2M6IA`mF.;e!z0m@^$Tc-y$ ADBpZDJ"gN笩-|߿nGG-С&wl߮j­6 w:EUJ?0x,<1hTQ]%> ͈?3xIV~!Orp!S>s9WQe;B`a7$Ao«tNwr˘h2O3Rk)j%Au+n8LuFwX:f1bk$U5{e+,P`5s=0 6Iq[;^lix1Vu{p5?XAe'f=#ㆲ4xz$1sxl~qo?$~i+${0 PW' I[ߐipx6}}D2/3 v9[C#H;3ɬnILFF'.BD9Ϭ7;$FDs /3 ?qú.ג @ʧ2put.(e3 STKjEP\ye;pYTfș@e;ѹtr wk©rYo @TzT@ Z?iIa SoWb`SYSD#Q#鲝[8W .+2o7T,ȚjԣVTֈ gG^rgL$P`%ݬgj0RM5+} eJ 5m Z2Lx\}t^ѥ7$nl,! ľSplΥ16ol ZEt(;m@,ZuntlY(LK{)ߗ _iQty ML-D @rYaG 12Kj"4z.0!H\jQYEe5RT2d8<k&kJ,T#Zr.IP2Gh0)B>Siu)k,0YSlk,Y^J{l{Oe(PB$aunEGnBCp; 3B* d\n?8; -D}[oz?>[nea[`%%%X"r]ݻw`-ZDii)V}+ڱq>@#h՛;:zyٳgyfRRRرcfɒ%t:֭[V%Kyf4 O$*-SI:W2u@%x:[[yX,Gtthn,^QVBB$~ $HKqE`rR {uOGT\QBO𠬨0p0.ĩ@AP `ҐPga&~q#\ 6 +l%<>hwxhb{0.,o+ {@X=a^ XUwp-+>O/UXbSLzia<̝}q9~[a!~Nq/r2z$H!|89{K1+7G! P$#ZZZ}>wRSۚj4.dz}VUWan*+~(k1=5*<{N}JBt(ѣ=6 f=Yc&U=l| x񖔈.*kUwg7TN0_"r}Ē~ _ydH|rv{kў{5kְ"_,Q}l/ NDM Zݦm裏5HaBV]H$XĻy@d/OLg|c/EeJfX__ώ;TF#gd29r$۶m ˣшϝ;G3CY I9CwJ,P`x]QuQ1_4ũ֢锔6UAhiiaowNv˄Eoc`ds(E)1؅8ו1cOr;GtHNNVUGR)?lŨ?z*ߗ5?#>'ux+?'d}>9!JÑ'3@K$[5!l'8PhZ"[q \>͏0n"QFX-FqP-9};TqO&;hY6txI9 D^X$<3 [߹H[-yK_U1o8ҧ57:!׳c2h3j2U7$qecM5R4,Nnh1`?{JIypuN0qb Y2a-?O>l7cmcbߓJ+[xS>.(1F{s(qo$S_kA#>pJu*BRcp7E}VZ8/W#2-b/"/l# Xg nTr?W6N57w+Gluu>^E-f&^q |VCEVڱq,ȴeJ}pH6GO{^^ !H`6(kMc{'->սG$><Q&DRN ,160dsT a)(ahvq;>Iۏ;ɵDba1Zbugr^.N52m<2?iC,x*T#*}~PP SUD Z~ [ P5|DZ%tEXtdate:create2019-05-15T23:00:58-04:00i%tEXtdate:modify2019-05-15T23:00:58-04:00vIENDB`OSCAR-code-v1.3.1/oscar/help/images/oximeter.png000066400000000000000000001235411417327530600213450ustar00rootroot00000000000000PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYs  tIMEm IDATxwxy ZoӀ^;%6bM"[q{؎|{˱%R3Ao[k?6Hؤ,ׅ {>z*VUbX*VUbX*VUbX*VUbX*VUbX*VUbX*VUbX*VUbX*VUbX*VUbX*VUbX*VUVkʣ-+߲/? D̮RcNk-V !ڿ]|c A6Z ," A*@ZpV>K,֏54ڃ]q/)B#5.-!DB'(W)9Jd ME`yJS1XmVJR&֥V|O:6'a[j2;36BYCP,(#ET ҩ p$ E a-yߧ]ld|@.3ԋ0Ċ|e aF 0 B9 = f-G&LsKSZkNS3ӽ:^deIu"E) E @Zk,)1N i6b$]ˆ~E:ʅѾ;\O82V /FHA! G뗝s o|Xl5evQ 5&M8sqS/1uR|;[Af4!x+6 Ho6$J*$ )Z B``'+6JXJY'޳!{q>)"륙 v_WX%ȿc1` 8‚49p\k3{?1vKCVcK[pp-M28"C*@`2W1gΞṑ)jq?=b$J#@X!,!љ9z~-rOE.(l2Oᦝ፥{ϙĜ%W 6`F}~G1n(RP˻wL9 q28 OLW "L-8LUЉ=w?H܅ cc82[b Е_Te~vz! yHs Ǟ葧8}<Ɔ~ʠ\$q]F 8(܀38ajr)J_<@Y_ek#}ܰ5d݄Ya9ÎXdۚlL@J|qJ&<<ypVK Fx2PO\Z#qxO\ ?DXC&'BoV 乩Ǐӳ_Ώan8\gf@QġD]B \JS]1з+lV)|fgf8sK֐I}toߏN-j iA+5,F #J0cdm"JM)u^uMҙ}վ'&l屳w?tz1 s,Wmضm#RYenztP,&aa1Gx}x<lFqU N+p|7z}ބ$N`d~B7Ԉ 1$/قP M|D C+]/DY_ԥh ;nō|,V8rI.Ǵb&MUJo+{ٱΞ.&**k"|s=t:W ä-ciK|۷nޚzռǫ?¥91}/.\-͎X2Y\o=k7IH8O!!3:}ǟz#GNp8i\~=sd5gZ/ոf38af(97?WfqΧu[%\a(vQ(<"ˈ|4ktQdS`I!ӧI_ĺEYgn1&>ѷ5Ys7>̈́pFl(I)±h7H6ljp  Jbxߛ=pޮ^{J V3]O3OߣOO͗&YX:CFVk/ε{ XV89J+nN8ԋ8؃ZSpxkJhj˚8iґW<S "ݓaa#0LϳiHwr[n%(syf'M1>q >\k-j+{w,/WƑ.lP%*5ñGƑ'gqa pq Hl:n:?o^U϶8vwK$ #GI˴Rt2͗MA _d' A+gɎ8MV+y^g[ 6`bI:c.{3>$B'S1=Å1ZDHE5„Pgh2kw3*nCA#2k |%E@ 0 0@_xہ^iX%pj'/~ɧy#cc2@l':@kL͌X[F(b %='gy#߽y)nOxb#˶]!%aפ]> Gl *$ńy(ъk%O%iw̿gifc1ܭ\81}?[,y Iۑc߻ʍ?=t?C}z57p՛?A'[A\ɥxS|_3>> ;`xw~Ĭcδ9v<.Tx bO%DkJ8@Kr}:^=xߋ=|/c>3H󡯒lbP tvЪ)ZٔT"?~δ|+xSGyA6Z$%.\8E5Q4'#ٸfv\ZD&aeh\ 1 g('le*IlDS"m@-ԝ?L~o>{=_o1sNo7mc}7*L-QOCJ>]. ~?+>瞡ZAUkLdÀREnō7J&b"#58v"^jmIq )Rb29zzn#<>nÍﺊ,U-C&39TMLy >~Qr%zbzg(;]⑇|a9* uvفr.Qᐤ<'r2IuK瘾4Bǚ $ |''_$5~-a5_WJn)0-h~cc?3ӛΝ '\5箷\ջא4թI°P)8ϗ>AG9{ڤxNߠUi4NeGd3Çp]p?3m3 wLd i+.)iR"2>q# EG+fTUۯd2ާ[y5p'a{)J_ .Fr)<'C5Kl~quMczUz "r Qp{>ql' Bn[._ϮiK)<2"fRG_"?F8D E$ǐ~: K8˚ :P,NؽF)GyK߫_r()MP~{7%[ i:"|q_t)\4T Wp?xL|o12!&x.\ヌE9(8t3gx/?ω=L_OIi^|9,-P[^" Wtt{i5{$xɧ8s 1ZFB _Οf̳TXy }뮧G(58r7^}H9yWD՘X}}/<̦N r XCgQQk-3>;E3Gs9!s qTeWdzrwKt0ƠAIlkX;koxV ƅG&ްށhA8~&kzB8WB R GO =|o)p獻yngc*̌ b$pdr8cG>ʗs/NT; `پ&ٳi.92[Oԃu/9OI1'AcTC^-.9};=JH]7?*/9+ aAl* >6"|ݣIѸ1EGj$,/Қcg"ݶ噔羿_}/:(zC p0ze(4y5M/r!5__'‘W'꒙Fg5 o β8qh8:PLCh EHNئ׫1ԷDJW}%;zaK?G_]MWs.tYh@)Kyxɱ_l刓V+<QPT< "8C};4Bf/<}[p5d7^Q[ac=eZ y'}=@wb ,CE#3ph s/g>ŅLE').,>.RVu*A:cN7 ne'b{}'?5 5Oރ`]udVLU!n5ISMGGʝzXVyGi֪T*!OM:Z*-w2|[X{Ap4ճX@_B)Lq=U9e~d;ndyrQ;e߿rA_\.?Ɋ[4-w7lÐD|ϕhѱ&, :: m3 ?0d4I n牛 o}{}6כT ﶫrmU5X~= qʛw ~K?~2mtě(X_꣧QSGȡPn_sZX@|?x"q "2Tj38}:bnaHhn8n)ߍ@0:6ɓ>$ a-3@ceMZo`[( T'Fxpa6A̐XCڝ亷a2ԤA qmB}j=W)$rRuT^)y! ܅_?l7R9.Ú N"]"21k_wg;lbؑPI ٓ"б9 jAntoقE>0L˅s|='p'KZ m죻ug ]f[^OS|d4ÐV s^.㓶, ne;vk[/<=M d :@wKLKImjh"m`piBHiQ7\~ԏ?tl[^G"ԈPkERwmerDX$n&%Mʭ$I֚$I0j|3|%I Q&HCUq, simrZPz4_<~+b'އ܉!v.Y|9C7=6ι.ՄhA-06?_E2dR"*e;Nג@Aә( CМቿ O:yT)!V-7c}o79WP[LjL.4QBH /PQsϗ!Br% Z d7~/ \<ӧؤN&4Civ~"`[eq#$KXkvѪ/TٷoWJf=5v8_M8sPNִ7+b: xI+ap\B$)C Htֆ0lCݻ1N>1]j@U8& HBaИTYt0x2 >6}v8An CWyd{U\ه>]ӚX"1 S<$#S%Hمg b=0KaL2TPdGu|'?>57_buʺNo`]W'UcqiZ{km$/x.}>JDttttbfkA`ƞH#jNBq =/'J&,I8$/"Yb˚,wodZ 4jORr/N|/N}b=-d8JCB cBJ2bTF^8 {!VLM-Q Sm46&MSiO}CLOOJ$iҞJpH5qnX\׿+TJ2.=%e=rkg9pMp.r{>p Yf XF*KZPۢ$m?ü׿ )P}a'?N~$N!q 2д&Fq J/۶s(R*n6^8~y6GvFuZ# 7p׼Ƃ3i̜#_)cVIHE9-γ<9ET=Okgmlbۖ>vngEԾ}{_=A>/?}W9=Vs!W,')Ij3gm,B ( FJ-p 7oe7oua*dx˼ ]:e븤F`@z홰&MYU @i r+"V+xwoǥQ*D8J VfǕQDEJ%Z?2;3r\F| (E4q$abdH'ZH]anO?zXtgulwݛ>gqE&NhiB G{m؃{p>=q\8$V-,U&cj ZCcvffgX~=KKK|cex0~83?OLP"ob7\GSD/`՜D&^4W61 Ĵ}NN8;NX% )u¥XH+wQp4ϗmWG{>OSk4}%q bPN[(i+sR]K"zJ>[GF˜"PGYZ4tHVM|gjj|k dJ]RNDy-*qDȦe.ɷPU1s㋬' *_ys~'`|M "Ћ^+wpoΜ广 2A %8El֍$CVA ;t/ l!+ݻY4u8Em|˧c-ɭLr S/b%iۿ H$-y7/ ,M^]u =,b_$~6@G w-45F.\8~=y}_}tuu%HRRő $i0(c4ֶM,!%& (дW{F]A %mۻ\b|4,9?QkItDiq G}O|#\8S{=C"J&ؼŤ@E t0]{}tey 1{?IP-xd u&]֋0O~L~C!~$w3n kssSvs۸pcaLmZ*E?dpԦxvE@sҚd:qK"1!gA/\eqtxrV[<| Eyo"SSSLNNÇ_A&Zޓ鯞)AF bbRWXKc:Aa"@+D R4ڦVғ8 AXᇮ_k^{if/t}ZI.Ű4ibmHuB%ffefw´p)IȵfxO'A<Uq[ %֬`3u4a(:וm(gSrOc''ŇC8NK&ظi #Z]ƪ2DZPH@4c&咤 eD]v])SÇ; ՛>箙e]_9&"zs Ҭ|D*% a|\CH2"MRxOӬ,q_ug2LTWIJ"$& KDŽaH8^kp{WPYʺ.; ߙ{>t2X!i%%JHbĹ=l_獇J#CC ASY}33V$:vZ6 ,,8. hruג$)/<7t3ey_b \lCtkjA[s4F^DX&Av#rx- bqli?tufsxBK+<%\`Ϧ2~ k"Eʹ䲽=Im톇_~ןNRIbn{Nume1B Z6@& 3~%Itok5JZUc'.<"/!El@3u0vI)U&#. 2e?̷g>a1Ay bgѵ9AA F>e3DRdZrjuz oy[}-[r~hԗy_)f"#-I}s@aul᭔; 1+| EB6Nw_]7]IGE)JV5"ۿ_Ir+A|Ksп܀W1+IbM\O1%jHz8cK|$\2ٶ20s:fMe&'g*FVJR!K BR<n\]z38N/J :ZcRTB,#D:.IEr1̥;ڋh9 9: HS}xo8F|Sgzd<OQk5I@e֝tw$IrmѤi z׻x;N:w}7[n{ɓS֩\GT\;v]t]y#&4NOADhGp) )mw-Д58!qQ%LXYt\lEZIaneؼn FGmKA:Ԣ%.\'/;QWo~Gj[]"S<_&-b^Y d{\!H`uJxγ8H܌H|l)˖-q](kSSصKJ(34ӐD.R#L \6ÿq'*ĉhL`tDƤڐ A,%~#a#&˥X##p|uItp;7nسwKǞ~~ZyYH FcKOvCo_8Z&[az4 J"֭q ?Ooo(ZΥy}ȖceԦN!*琭 <藺fk &SȔ0_F1K^: Y +WT!x9ҰL-&&T8tTE@[T5yY\Xe׍7nHnN^y8гpĤ8^r0qZǞ=XY!TLB,,̳c6{sQK~^\@) O$,1WtE[Gc3Nqd6 ]kqr]ԅ_U%d Y//Mw"Jb>OFep:,մ d2.Wo:ځ2ƤX,:`VX, h>Fy}/?# Xj_  \-x:\r>ݽ%H^Z]Y щ>Fm~J B$Is\VBpX[`f|$Xi =,JBI2IZˤf-ۯUZ ?ۮ݊XЦEST!!p\^0c۶![\Oܗx _zB`cJH)wvuVׯ #8u=V17p=V{%7qA|>6mpϜ"9) ư0XÚ+D! U.9DYrš]ޝ<朎,ͅeS#4GEJBsc<<"Ff[Klɝ_hλ%ւ$i¹gkڎ{Imfڵ]ݢZj/wm)\"2LG@gok{(w]YfgWC1`iqBR$զB@w,s8C>Lk gNa~+cA(bHHSgg%)ov-WoМ:g'&m}lmv^;TϜ=Eښ*dv_QŠQR.Yz40Yʅ y\R۞"R.RIp:,\w.nr::F `7C "lrه+Mgh&Eg3k"4ht 21*Q`,XJ QEuF]lXM YG+LjYJ`^1@ 0nYglrr۷o't0҅.LЊZ m$l!$VPZa GiZ>' [<'2L(sDQiFkP~Չ0{If/? 2otkXAoO? qx 𘙙&)hh4:tF3g<Ξ9]y~w~O<@9e1QsitKύo@ƙ3$cKp#xrh7neZ:J6"MmqDK}rwb#OБA T=*hWANi%7`az gC*y^vl!гJs')tY,24GŴظa ,sqNS\ոO}Bsn7}Kva*hgs8^e0AE5fp]*7d|Ξ=B{#]:|ukr%(#1=5Br\o(ۋ]3wEd}ѺDu Aw> )l M=9H<"5S4.!+dMw)O)AY.JHVa\&ո| ۷ 0Jhc `i9!f6!N"M  ?ѧϦa7buHXj>Jy8"P`mP2ήAU,Jȕ"vtKiϰ4 aR.ٲmK U 9 X^no.\Wa y(džmki5RH!Q$*D@eE) 9ЭypzrLcQ)8%48) p)ٶu;bY:۶/_"ccl߾x< |3;r|X#QոADW뜚 pN^A3qNaNcdX#t؃׷c0&Ȳ<9Js44Q*<)3!DJEJT@t_BE!.V[f3X~lAyԩS Rʞ={*&Z?O2h1BƒhqHq@(C*PGu HHYX9mZўW%Wx7BI.+fe34 zY_f8C}z }ӬH.Rp |t|k7yUCLǣOOۋNv)MpJ4ՠi t]q;k]ײ01N+3^) J&C=2 qDp`r6 A;R)KxFSh4={ރjYtAQ,d*xҠ"IW-!"-\[#+o#J9cB $+@vt h'u㸄JBww'=h]%sIUY4b|0WSšE pӗO=ωp4O)~az!ΟTĢiʘv9>mkѩ&MP!M5Ǟ}{8w,4 >k8I܈0زCǞ{~K,y[ED3jcx&+]ny3"٘$ҚVm,IdR)3d<Ϡ=pDX6JZ j<qHtRik 8^ Dsh<ӷlbyp=u ړ`c "B9Q$[:mg[(+{Fc?~Hᠭ~iʇ])l8mZ'!e8v-Q,dY\^`Zc{Hx';}*7#mVDq$I1iH)wi.ҧ.p.|ٰ8=%<ǣ)QG^wc/rTKy'9} n C6^d""(),riI 7D'Nb*1hB:+z5lF߉U.$H @4O2;y")B &f%/1$n5n~.puwwbL m"\鯈]m]*Vh^v̿QXk*A&VbHZj,ZiDPdqse n>],!Y9.ȶi$V۵6+E~RvX:h%ZdStQY 9K˨@n:n,i"'ELUb%:E4$qLk#,]l޶ng>F/GE$fn ~@Zgzj뮽VbddwĤ3.Vl QqWMמR\`<&CƐ4RXr>a>|(a 1~Ny/"DN~@(Gr:K;4d{cwuo‘q,qTnFX(ڡyV|C4K13gΐ}M~$IRGc _;ߑ&KMR3,]C*Qi԰h%2EH]nDIUD(PJΪۗHZ0r"IRlP`%8N+|1Hi<Geh6LN1~z-] ٴ Q%p::JDQߓI +*bS(o9xÏB/m F+;5QHNR$U>HF*LH ff4Yc л\q W15O}<"Ƕ XQ#_BmйQnwJz"ˣ4&'2YBk׬ap)x*CFu7MA܅1I;yNvp?ttu۔FZ""ŘB&Mbcȗ;PV!D78j}oCu!Yd99$2'fbndvh%!Mb'"Ii, _$]#B#(ta/:ϋ@Al=#C4!}x]7cJ}v4~^`,B/%VxmceƷ·20ZWTZ:ɑsie/%1:_wKN:o!ߨA^rЧCw AR=ycgW%4"P@ 83YM6B g%#ڴ/v])ere7&U.Iuq@ 0,01z;9D\u 㗣QH>\Hk@(XYϣDel%v/N,▋mѡ,nH/ӳSmBiW)scD0)qsy]|RЈc T,MOҜ&:|gɻ.`<)69Mi@ֹ6IIr=kmWK6aw~[K|mwATTG<[{4,$AH% -N7x|),)AVƴ/K6#-횮HMʅZ/Z}(iiMb\P8~KP^KGo) aI%Bh+[(%X0B#S iq|<s XÝ7wţ->,J+CŨLN5\d^tw tx=ViN&pF̡|yw'^v:Ÿ# 5 z~VuWTvi$#8!m,m︂܂<8jғ˜vHop8ӽHItY??{lO~ ُD1:`tb((iv:J:vJnwʶB"Ca$dy ( I/\>Z8DJAou}X>lO1+[?]0ZsΝ?tW+goMq3y"|'ylI3 CAy0CqI R9!im'MԴæfeNdeXK pCEd<IxQbP!St&&Nⶩ&R)T: #6iw[!u)j1zBqd4' '872[Lc0K&s8l8:6(SD2}1B!:V it3!^P##U 7'){A) RB+\!V: ֻ}84AmK]-qrŷ"A_W A\scщ&Vdq" -axnfF%rA #u$Z8!I4PD]Hԉњ4]İ2ȯrߗ ZZT]hڋ[# O6n\Ǜp{w "DQԮq[)"DX¶s?4^"7w_jiXyOX+B$IwNѧ\AG\&"^<'7ͳeN $"4 Ioo}'NZc㤝RC*V52DR'hQA(P$IQp])+fJKHu fo_qq|L8 WG6x5$Rb-ڢ+%q3LR#Up(Fir\0{_$6",&q1SoX4E,u-٬= ltŏsBS_+pbØ?z_'SI򲐯w3 /%0L hhOWz:}q|нm/]Ξnp$6з //u n{[rŷHlhS3Tq-(/hK`%)Dy$Fk4 -/ExٜPJa|9@rnkk-nDzL/-Vg"O%YVr=wo/o"T9b\2QE18"\ódI\_ <(u JjK䂈+]-ýct#Q//RikxBd7㛫qI~^A$MS;vx&qs٪#иR2=Ctjr*h_#Iqʃ nɢGzލ}89Oض!XCԚ1r YܰJ.Ac<0bXB,zX%_4i}Xrk1m]lȗOK cJ`KJ4a+%jZ1jMB""%p-AO/nF]x]$&bbMrHj4&2s3Y\ \+-A V9X^5uqQ>Vfjojo\{[RW=$N6VX$N&=L X`hslm*rabc7vRٞl2E ^D&u9R+I&ҊCDq%ahvjC`jH!0ؕ%ж=FP,8\#W' *Z ]@0Cw݁9aB bti,)G )#VcDtPĭyʁkr?Ѝ)i:F(}٭xY_֢oUl~#qQ|3i|{)EJ]$w515=EY/uO{PYƱxoY}[4̟"9Q0 &IpI.cC#IN;i(D{Hru<)`%oVB VR3Ŷp%J]hg14JIcҘQ!c x ӺD\@ S{ sONH " H"(Yd˻-\rZֻ.I[MS@ E@ ` D}s13iq{p <۩M_oFjNq={j c^@$ #alATIŠMJ2F;3R:YS".7h=S*G8Yb?9/g."'%$"ѷrZ 2i4S6oa?~e7kK ءDn/_seʰ?/m' 9!7la8-P",ީƋ:B #x1B"} X{/ᤠ>;ɦG.ĥ8Y_f0pn}4Z0BU]9Dlwph ( uUZԣUvy)̪Acqrת~O^ARG~$q+$祼a'/;A}nAGV6meqi?"^ Q#EJ-0Q166<VR rqI(:pP7~r] (u첡cmh{.b|kC3e,ar {tǝ|8J+DIT $;K΅j\#t;]Zynr޽Fv؆3h/D9,;z괋rӅ`cV^ןvbyyy]:HM!T@!b!Msݍoc?tKLLN091WzA|א6g21EKNLB+,@+Q#x4) Hmr]B$3)Nf?paLr$]b<E/C'q8:$\@6yju|ɝ0 ;@s;J*҈$Iщpx$% GopZ|B'?& Q2b<ɓ=`i7#.{\}D*C*).<[S ԒnQ BtαZR5xu;b~ uLa1y qP˒= 4AII(B'353{W~^}jbsz!$:pER e*ơ]Cx# N! ;P<(/F8O{e<=G^@tнsV9.TKV`Ntq Ft;h@BDHDaqʠcaa780]vt7 @>S06tR֑ 1.% "͇!Z( ~kʻb?5AE]3gp?lwWQuھ6D[XMmRW,P #M'{O<_Q*k:?O+_L" L[IcZ*ٵkTa'R/֚Vdkk )qk-^o];A8;"R*VdYm۷SW4~aI*@(F!BGQC8M8YPDT`,B+D!fMn/w, _:A/< $ yԷ^ۃ7ccK! pagbXzs/Л;[^FDVΏU00xۮwm3xW}" 9$_?!*[}F pjjX  P\~ɓ%yO1 R/{x^ , ܔJb:!FC$Id\ ,dwt]ڋsɋlMӚ2P]L1_қ'=A& T$1:'/NHvrok/|{x붢fF%P: I^Ԓ͏-v91,YFhKn Y2I:=+/ن-Qj QéN7s: Xkɲ B}+'tui/0Di, b|ZKZd8]q UlSJwxq..<w"1Va O 1RG#IDO b涫ӗ"IOIdp's)Ne"V1JЫq%A.F("].&jy72֚[Wb@֣xL!(('LX 9A8xv3:+$27>|rٜ=_HqvvNv'J֞:UAbKlͱ.!Z訉/rH +XAZ!J00` 0#D0XZb" G6Ewi(f۴hlNe8"0t6G}E: E̩BGhxJך!BG$QL,%JXnxU|]8c>ʧZz+,q=?ؒ5!S1J6ܲ# "]vQz\BCSRB>O.:]I92w}Nf?s[[^}rF8r:-kt q4.i67B$,-"%m1OK*PX; )#CH+rmN>B%,%l.N"$YVP- :c8t5?QMй4Y (:,=S]oַF]RHEAYG_#}&agbj(I($HN!DD ¤ qꓐ/]gYkvx]y`0xYWpDJy9w?UR#"KSy;gxz!0$INc >@IVipoHk› ?o^#X x(;*8yyK .`aD262JtX'.LQ !:,:"GHAWN8w^/Vvn0rչ"EkSǩmŮe||D pyseXYrS | [p ; *_ΠKD'' )_+dQzEQ8Hi{?N;TW]P;XZm7_#ʾ|Ԡ$  :J.bWqOsp Wc8Wj5C:.Y.g8lcEF^Gi*!Vvȇ]:'N0 $ Nk 5 " MG ke^>th-h<@ppkZ+>>'0m(`SL V@!  *w/iL R:~e*4V*Vc(N(+Y0 XZZ@Jsgs BGW'}: ; -"3{ ѣ|c牕$qXZ2 "_ })N!* r+4|I=CM5OFb RawԴ#(*$c,$qB$LMqw_i+֡T2"X%>IgsG>J<š`$I4JbňR"QBƩ/" 扌HP&dD9$ `#:p~^=:IQ Rz8`yLAzt. hE M>. l2qh R1BGA;'4 dd QϪdź%|~7k/CDCJjxwBu_<¿Ҧ0zX#s ;9H/8e lBźI\9aK`7"%Jࡲ($*-QW"̍O45\9Sz|y"&[.z%tEh*Zk es ;RXPX]1:~>e O޳΢, :)yUۃw9Q2.Vh<}4\u<'IGؽ1M23C=!S$EyD:<ܡ$Jal +2W~Ô0S:Jx '(ΐcEq\˟U~l͢2vv[/®"ˉ>$l1dM_{/:¡ QY /"(QZi .| GH@H!abg0졁Z-%M#DJTI`:.ٹ6nuԚA:Zd @J_#\%%Pn0? N"ҤN-iNj-UI^CIUv!nXD*] V`ml~e_s˪iRFX'3N"삢q_GŨz]>&ISAnYVg!9sܾjNLIUI`HYzѭ%ə/6?=FTfRy_A*YM T% #pS2F%-R4P^2ld»uvORdYwOHQ>) HIT G9B]χEJ![$nc9=C$ UTkYU)/qs"ZH4̖qokqqϧy/B&ŋ9|c+UC (d3nP$A71"FXĘ Wn?p׿ZH>IxW\U_imTs+;)!79E◾ѓdsHDBz_- Y%ڄ G8_Ŋ\&iɇ(ڻY3>nUA:!X8BO+< L%룉[k% Y-{T*;g!Q㺖KiEZ! w@)6^v}[|388#ģϓ{$H3@Ȣd:iP` :6Mq`d " M^4U;k}.5X\|"DZ@RTTBB\hwxӕ%v=HGMv*pD+~b;(Y4>$73ZEnPޡ ϿgXi4!_Vc:AKaď[ƾVP\' a xx0ќwM?_ Z5D#1I"lݖpWQ%1>4$؄5 ^WpG} F;,;:V];p o"QlۋR_˭r{)LAcÂ<'"N@b2"Bj@+GU686zj}vNطzéaVsnD $)2;BVr2LqB6\d%0XqMv_~K~*8o}ӵkxe;y{:=9&|%'2ѡg)IJݱwa;- *´$Arں>5OyareXt lp ^zP"xJp#;ogۦ E1.b CR!Қ~~ͧpb(03r_!4Y񉉗8q[˅kì?(2IO, Ap2x/]G1mǻ_.n \~eu.a\u|GStu~\r4'>&i_QT%փHJ$fYE="Nq[@6$AV9QARM cޡaq2=$Rc[\~P<]_ї6cT #?ꨯu5vB"-/So4JYkBEYĕrla@bBuGY[o׿2cMtVc#1EN\K7+gaCVcf4HC^*Ej9* KڐFEg+b$K s|5]]wYd'7C"B5/iRCVv%Pdi 3pÛ;nD)Q* |j:Fc#:pEN8rSݥ6p0$JFk_7]zƛ|AiǍzc>u5{߰q{Uyhvv4 lB_`i$B*f#I'gq0!C")Ge搴=S-䨐#@;4ǧOnЫIdV3x<(iG@~|;xuo1z^ZrLrӛUW\E,=RF4I~p/8;{:նl69yr9-ss:xat3V tH6@)DkU:aX^9m|ʗ %K_SAPP8JhPARY[ l+nG;vi!/ϿȷuHt;ƶicH0I"c&$tL4jILrvnη^mDbѬ7?WK_<s R}OZWH)`n8sǏ03Ź9я06%~nGEZrkZ܄Ѫ4ٲ2DXݺ"t ➬w%CJkc{{-`n8y^`5n|0jy'u,ɓ sܑ=v'NAmV[Lc@oy-LC1DZk-SjQ3]oΛ&M)DD@'(Qy~Ғ*ħC&EX_`9.1;C$Vo|V>03;=_G2J_B"qx) d #ͳ>CQx&qZcc4'J(0j)Pc 0GD~C]xIkI"-&˲j`^rMoKQb$!@q T"cP:f$"Rc6k_2nl^% *ݹ"g0XF%Cn rŻ>U}9#(Sw'~ cqd[b fF\u1wv3f=[MZ('SS3u\ku=X?х'xj4;Kk|<!InU!Z{6H(U PC -^8IQo6Immp@oyn|;J-cEDGqmH8RB\ҒR{? ~JɲI-%26oĥaϮkS%T$#ꝊGO)D*?<凹SQęC)dq^{酁Kk&' |͛\n [T)v:ǨNU?I!|BvzԳYV})*B@5DH]9?K$)F3HH☢((h"/BC0(B*ć W{ FqsgI(8x ImvZm4RjБFoi,(e\?DO*zލvX5k"p1D/_{_V{)~!W^y>w-lGYU:zNLNl?c[kT+kqAB!P 3%%E瘘grrWj:vT­uGX/okDbQȍyMJpF`mȣji $()'3\|% CBev"u[bP@ iDxȍ qadO>q|0iQiۮ{#7&FhZm۷~hV%IPJ3<0XuE=7H*+SqpAMj,Og-BP",b[bʆ-Byɚ'VJ\|-e #R+`hsPjb ?X]Ü$QJ-M㸄{dT"gr.  ڃab1" bYR#|R _l)]ֹ抋JE1YP>0;;uغ9~_{+3[#$1t:AQo0]cb|89q8Ǐg-mC\%.߿dE Rd-ü9\yFQJkXZZCLOob۶Ci8AE) h8)EiD0ۀچDQ NG|eרZFo8ޓwyk]︙fpΑeu8~hzsabYkB[^/Ϥnb fQ:*BHȝn} HٺuZGX%L̪݊+?[h#TG.븲_n/AoOII٤nsA6o;Y.eutu|ڒ~h>fXU >|)Zg?/|g%cXJ yL׿wS9֤eLLLn?$1i4!)XeiEQR껨/+2 crN?`8dӦMweBQ9/U x0P5&!c*EζcCt116ơÇ9y$allASD:*iF+?ul&1+xĀW ~I,e!AXKl;^IK_XusmZB޳\z骞 U  Jf*χc;Fcjfz^2D 1ZJAe,1H(wBkde ڷ6;B&ZNBڪX8~M3(%XmoheȤDZťE1"EeCBORK/{ ib'M3Fܧu搳 ~8>P:噃 G>0"h]cj |b$wҋÙYOknzXkB|3@E?Zvo\|Ǐc0Xf-|\hKDn9*J򵖗G㧑RA MJ1aa $N~>j8.i52aϪ tFѰY t >wG'?L@@'q  纫/={ogff gsYB$^m/+־x(mdYF8?Ct%k!90GfGBU#|`Ot՜w9U Kh1(˜ǿVK/<@}s8**O1 Φ1v8T%hhqli?Y>yiw4Z-xK4nz]ro[La'KӼh-[^]^v`#|p0\nIӔf9sVvz~-|FTZi/1?Fs陰x̕ 'geڵ g UQ^gzw&ƟJJ}IN}p&t;񊐡0 o>I,L%D Ϡc|VsZ-{}}_Ntfe4^ 3qQڊp!bشe;Rt#ʠf{Qfl^.}.&g.}./xɉ o]@hT(Aks|b~7D9u֓9zs,FH[ⲋ/=p';nš!F?Ȳ__rXk'zu40iB$pYV r>pp];Ȳ8|p?-۷kyxk8py9,F^C8dvVfg \:gkmug5OH%GQ H:7OXB&,kyQO"kfhvbbbܬ~9Au'zm~zN^?Ďr0=rd?ȦM)Ǐܵ-E?*E{iy!វ^o`LHG%,9K½ݕ6Y!2N}_?_@f>N v7z;\ZRxkh5ǚ/MM~q^n7 uہk^͔2rj%;B%h`vv+Z)HIs%yeV*)wPQώ:\B}0i`} =|w];6# qLx$j=~~^n*hkDѐ[n-ɌFQ󉉉'W8wQQ43 i;0V5*} p+q}}blBJR:@<Hױ֠u>.g25_7^} $N1ol$[yƤEQu:ZZZ{OV;%tN>? >t+e˂+mC(| -*KJV@?TcˑcKdge︑.(4@Ixڬob|e'/rWTEoEv-s^Ր|8J\ڮڪvȒq_ IL HWCS_ǾOjo11d:F${nD{E:@E(E#N'Re)=ՕiUoRr,\;sAasv "~+|BZ 5z۹Uk,M6z_Wb ?3ƴs[XXU֙H `mwZbh(/FczG\w+܉5 5GkfGy_g}\.Vس"* =ޅfb(|"FN%NL7g<F{E5 R3L 7<əZ6y AeXo^TT?*YUUJ>SUuyXɟyoC+KSdj?U F^fY2zK Va2Ē D DHwR "UDUW=QG=<9 *O< uNjjYsڦ{il6H%Ih47/#wc̥vfVT"A0C8GURVBЕ,J<8~C8V8O&Ik333^a/ )m8j۟uƂlɊ!DQBJNZroCÜ#ҚQKkJ/߰ۆ2c̦v`Z5XP`}(9`0@E :S<Գ$ ϵ4z}+2 Yc`pݾFZ-A);sa0(sbnK`Ut:]u$IJMI~V}yll]7ۆƊv{Gqd$Hť.nAJ lJ"}^kyû>kmA`Ƙ9ǽ^KK')9 /Rq9֟Bwo: }9~~4<$w;~\ nÓexUWi"),0JI!8!K<`Xn-  LQ$c k ahhC8vW!pUgcH42R{K?oH`.BykqS[Minnթ nj!dF44XS(L䎯ac4xxzzAk>o :Q@!`c  Bކah 4z  Hƃ=``,k'&Fxg/,EC͡CR@/_Z7ym- l.0 BC> "Od?A:&p-ϕ.7r@P2"l2f>f,aay Zi\[OEjO3YLN|?_w}C 7L ę#2 Ā 6@R;g?CxذgXJqFFafXX^z "a pFa&1>-7{E٨#Wz2PDPȲ܆5X0,Ȼlhgc)+c/ yj>?i=r$H1h6h6kW+hV+`ІCae\DR@ɻ[C p4lBB M4@r|߳{ڟ{o$ }go{Uj5pg~`;$۲_ "͡5&f SQ@qQ э=l 6Xa<sK'S\#M\gVVv qC&M;߯K7@"$U$dKwq[}߃F~}ӸtU x=8w\`mayv?ETn2}R9[ aGqر^) !!~&: =p TctZ5QD%oNl `!hT{Lph(I-[^$ }/pws'~iq~ܹ>*,=oՖsO_|CYxyk/o%Y HÆ>+8Dp2{6 ص$IUV'.K_BymO÷'*h選o!;S?]^>ԃ#= Vnt^r]vrZ)i&D$ Z?eقko"X@pN^zyW'Z폖b jR6 6&4^'l+0x; ;fN>?͝V綍vv}ӽBGC,H&"N' m.-77[-] h軠a'TN !c\C*6ֱ !$B,A 4,@(5k1vo߂vߢe lz_  2ȞLϔڿ.o{'8vWo[[]~cecW6ԪE1`fpRzq wy7_'pt>fs3 fDd >|7G_'?V;ݛڝT1:v H) #ư a=-kE23Glop) W%M-wޠPqt_QzUbmuq$aِ=1PZC yb7Z -ACƒ]d`!(@&Kyջs}ݏÇÇdo\t8SchiqVo VאfI$&!ZY&JuW/0gOcOy;7Rܷͯߏ[np N_y]ZZJ#5  F p)vQ^EX */@ZP,QjqK~̀' t:m9s chuBC HH)fmWB0Z!ڋL`,h{E=pOw'x}0!"LwM/~ūn| @6Gx΅7/nt}u획 mQ$I4G>,rE1f$P5$|h<Ͷy^d. ӟ>tKWz9zA݇ ݻoxK\qu?m?0啍Zkyxb Z3 MB2LxinP@&=dkoåک8ǡ[|:P&xA.pߵLZaI>vL8Ἓ;Z.HXЏlc9ks(y66m ؓ:qI, VijiEj$Y~$`׮j < AyO~s{.seL>:<?]_W^rw/#{|[^ֺamc}:.`ueq%q%"!0l #lB୬.t3SF1j&14H*u8F B^_88+/lO o/eKO}kO=ommV/}in vjLH( Ahů3j#GvlXK)`2D )4G=F}[őC]\ݬ~k1ܡx>{^}E] ~)'5Rio+|9~:qQF3ڭ6R9 # )"Rh c `o@022 W j5sI)xb5̘yswg~~Ew|ѧx ,5HTcX ݳDCi!%16PVEHHk$(z!`r'{7_́[o3Qli0s??.[_u}cv45uShI$i]ě=l")ĎKxm 5=(vlۅ)X\dr7Xt$lM_)E,f`B'!~d F6^W&dO1E-Ȗ,FO"Vt:0Z[RF|6lqH X3ӨT{RrK8Re\RYp 5W퍮:g~Mozw#Sn|p=yE$ c {!OQRE>Fc&)mghpo:Szܘ|;~:>ɿ{:Wwz7n}v~׆HH) Ʉ|n6y ϝ1_ݕ3gyu p9 Mnڠ.0; /R!ýe.?CuFw ՂТJwǙOí4:.QkR"IbH!e Zk]0G8Tk f&042ր&DQ>4$ i&<IJq+C ~N>\{/5wSjCQjTx_mSl"d&"M3>vbyjH,HD#mhMfP0P彝gYOkDĎ{qчY$ P8SDNy^X`1G8p>qREN/^|7TAomb0 ɀFy͚:"/MPi^=cN$t07=o<8`!FLrQJ "ec.}O;-wOM={q YBYO1#*jh6GQk RC QjQ6? ;0P /=>)s2b)@R -Re]Z!OfkI|~#b#8)* 5sN\/h>(&x7@ h?gȄwB,$kHlsCd^N/- #-f9Rꒌ6Į2"U P ]a ;VgZŚe7 Td/.Ǟ~w3?nU"zd_FKqfVk 2uB1FmzIj;׉UvqN 1| rTYPJ"3e7v¤lAFr% {ҋ UȒMȿ Y[X0Z\EYt9{-xWSga՝lK׵Q/ز;A {=J^Qm6m{\Oj43"aAY$ēAb9k+6`!FDQ$ hh6Y )^ ҥ!"A rAƹta(IKyY!v4T|Z.'=a`B.wǸ˯p@! kU#GR߽>f ~ 1F]b2E݊-{Po!"ad8v9klce @&N%҉gzb¡(@ݙpNo9'^S]y\K6ШǘJ J nr,kk7KQ0e ?z16@JGl XAHRBya 5@(rW&:5VGÏ 3"g]1WJH`rl^Cc4Ƒ+F>DO$x3Sf!,lΌ"؀ %`>B[x-0C{2~ta199ڇ]{01>ӧHu sF;$8\ҡλXFQDϠR1Դm"Wa8J 2E W!~_ p~$ υ A6aFYV@J$AR8}#"DQ(PWbzvGxR.bmm q$$ 3FG :5:ZA;Vmb`xƵH RW`Ji2: "N7=r~ߺk\;Vw*IcV|#زu?}[&Q@t4}Qa]N=ʖŊ0eAOzG ЗZsla"JNMS 5+ؽm޽}vLVmY['w{n Z6fXB9Oނ?mn|ч8ZM\_ky$ K7 fc1flnmʢ•m"BV{)hel4NPjPMi^O044zഏ<˜ ,E{#?+)_&>k7(8 SWɛ/oott1msTVPװ"6HG)tfNQr{4D})ˣĥҖӻ!$\P.AR@iv`02߿w4֖zUL!–Yu!34J{wA1dt.BLJP:94P^<9 ׶䖼 \#% OcX `3MdU{KHFz}ݐiAbN t}k=,-ϣ>4'Z)V}&F C]1vtSjAdGQPO"OPKg[n?:g~ql2٩i̯.qw+)C_)y8^:h&$X4l# eX`sEg%$T$EA5cuQo$صm.ۇ}{bnn+FGQTc\jA(Qclu8k nA+Auy\#>122?j0PͶS.:,XnSs{䁲|?.IzyJ\Hnh#6V ihe3%(p)$fH! v#P[#y܉$1-ÈDN}h@<_>F'2;tÎ0l;y҆H?qYMx^94S{?{ GhcX58ɡz=v-r()RN䷘T!G~/k^rxËp`nZF#39;6cҍb0035{>iԓ,@FR+*>dJ ϜDZE=Page`5n)E -of+~RqK{;^b “ gy RZz'5jh4`f\A,JF03aY)a+CC,mH9ȱ4-X\l>,i R?EDHȍ^&I㕏|/>ID}RfC%wu?ԧ>ɻOhnN@C2ԑ$ JCHH"tH "z imtǷ ..\h 6,A6YF& ne݊\}[fh-Q($\j]p~!L #M3r##?E ըVPyˋH*M*UxR}a /#]?h\0pȕFmZ禨8n eIkKv{¦d S|1z jmT1Zkl݊HDPy6'0/%- >E@J%#P&,*$X'ϭj=~?7 w7r PwM0?OM$qRSZqv4S`a 4*UHi1 b!@̈-1]0|ێ6k'2XD"$@1Z]t>\o\FFGT[WvEx/7^%#[>j҅J^MݕȆw Xx+&WsXk%[S| 6ԢJѶlg{=u쑷09zCۦ 롡h;o+3h Fط;vl4jKh&*U g%3%KwچN!r5! A؀RDX_[Oc|bS,]K(ZsnHz8NjaiyvڱAVg}M~~^7ݙ*zi19$^r2N f2veOsU_t/Ď%rO R'ffxUqػ{7nns$#á|&xPt9Ϟ \bYʁ>yxK_?(&Ɔg9ZoCvwb:FZcpj#M#(B 8z{ԫ%d6d6u-pd#DRZ}!H 7!k'1+<8k;T>eL؁47H+}(c <8 S(Tl@F蔅$$>&yD( d3|)f) ]w<4Z~wo}[#߱7eo=c_~&RdYBE:rM|:׼}smd=RP^Lyffm4vM{~ؾcTc0ij!$\j"乶ᲔJ׽؃j}ȖUzͫqLΐRZyoqXcaLOo"WQI*ժ!ԷEl +&|_gg5 t ~ %d$GRmpumx F^, Qari{AHFYPt=7NBt럳=NT0* [Ea4qt˷|oow뮻43GO=QGd$D b %A؋I:h&J~zE lDdC$Sw0>;^}]w &eXXYA$ Q$l/:> $1Aiɰsԉ)n| 8gA4Bd޽;^*~{wlAG%`@ϞBL$+ػc/Q ՚ĉeXzLö.p;s)a6֦j{pm>o]nԖ'#~`DT@7kC_21jl, ` YK&F!of [qg>ngCj#JlS]+tzJKhc bxhj`51%'j ښ *h?8p= ƫ KfiWj,H1M\q SRAi㌅ژĵϽ rDR S ׇdIH$WEx\PaI]Tqp+JY$=9?wxwaqGm> 7?uܖN#39"ξt > BTPgXؾe?\q`څٙ4,<&ȁxEEMy#b{BEd)"Yu=?SOĉp Tz$QUujqAƙG/Nj_tS!WS7C{wۚ8"0 cl0u*ϟ얭W j ,Zx,^6lwm '!IAٗ. Vd3t!^@, _y4GIo ri7z"S'Zg.`qigac+D͙N<:_£m-׎@[MBzq3L +&*B%ʄ!#eq8l"^8vw~_{ۜ %,zMvn/13ϯI9WLJVCEHA4?B;݂"ݶ^~x+޽67ѱ1T*T]ղ#( -'sT+Hϐ1$A[0iqyΜʼn'pqltZ-AZAl iP؅;~رe*~eC1A+w/IX(\.µ\ʇ3+K8ԓؾ{?ƇrEz w|Ja7`}|Ifk\.70@-YL!ʨKÕHj\+ n=se,.,$)e*DXmgh 9oB0FIБk_2ܳ =kLK>w[=^l HPJXA^lVgDcݭ 2sۃroxF!q׷~NwfFF~nD$)"n(g<7S< gF`eVD l ?/;]vbfj Fu36 ڃQ~(4E1yT*TlWYk??SNqv,n 4j5̌7E aɳB2Ea__:~L܄$.u6~&X=qUux$ϥǞZL`bt*ڿhlz0eQړCLM|Sj:2Q @X 6A-*_@ .r rH0T \MF* %:Wcf 5=CuA2žRm'%}A. H/RA)DDxp~l߹[06:B bP6ܔ~VmT BI$CMB8w4Μ?ƹSZ\ I%HdzõҺCRc|F8p}ؿg-]H,F@`=׿{M#S:TgdI̸-sѯ#Nbm [m@团(h2enE5 r[q{:?ѵݮ!B4z `ʲ8V`A2 UힴA,}9D x$ֆM@%bnC LK&!gei8Ub|/lIF\: WF# KGww7bU@u^S5Qre\㒔C;ұ\gHs5\}Ncnn*Jy0lKA!^Q%j4!`e/.38~8ϝG^Gz5H 2jZ/%.KQ `'Zw-վy}y<կb jޓMĥe/)ӟG[QNb ?XM>6rTkUm3vD[O/mcS.*V'  I:)4 Q { A;7PE1 @hɶhU|V*R g2S$R诚+FyolC`^A{n!%MKS}7gy߷~zD_iV %">yz^~?7]x@ϟg9Ì+Oyn JAډx2LaNk_ngPDfPrU5T5$(Q:άg^zPql^3=TDY >"/gZ3 =Y4kWzoGipsibltvn عZ"<8WFw>٤)Ȳ N[=LO/yM e_ P/3|cumqi;y筗PT0٨4%378֣ ' o,7^;P8J5F->eڶc } I,>#D^66WUZJ 2eN;:RzhnTo־=T`رJ/K:۶ W+T*5ݹ \u5ێ"=hQ!NH*5$:TjMxL #Ob;$!k,'rc-mv#8y1VɊxf d:&"UzG>Hķl8p@J,ey-cNd*=sH9C}}_+wX0k,]J{>mʢ`awYKpe GZ!V!q%]s2\΢ +"4D@ H*6oUuUZ|i_mo6!ؖ<]J ;ݏ1C4>3ff#2vCk} /0/8<-*nq\j(\CdFiA_WG9f^+/ۍ1H;uV #7!MJII>9ɑanw >WwtbОv{B:uG4,,!]̈&&\񱩐u`GVQJ1x~uh#N"2nB6x,%V)Qq kM4̾ O'Rb1 7}d(adinZUkXkמػ}%H*Uĉ@2>{:0T"K3!QTPUQY6i#O/*I04Un=I$i+@R8egF, (Op*|K|[5GH J"1Kjր^#g0҈w|v}S$yx;ށXYw0hsQ̍O j5B)`SNks` I"%ΜG"FtNEOǹC`Bg${ Z!5rN&057p.;q"7p顗oR$w/~M(e ya\~.vuP7-(!x()-Ma0)w x[Q>&4TlO#)ŹUml %]q.?1LLMcfz3GVXn2@eT,,$Q)BseS`14( W\ !ExwAaf}ffx܅}wC[` `ۿz˟{t{_T/#Gƣ1;=L$h5ȔrynU[G1;5_~ahtVĖO\(4f5 h`Ϯw$lys0𮫐!, WQ6Ь ZCJ@0+ i^n{^yCkVPk6Fހ:iv7C!Wzs…]%T|ԓ=Q..Mb'mƮ쭃}NŠD1W3K-wԓoy[VB+Y֛h-#_5>ڊ?j-9,+O)j;hXbu}[F^wå_E᰷, [67fv`|!?~ \4F bĵqObvvSn6Q%޳)Ȇq!&Ɔb^E)F8%7bfyθ+7w}S=7H6~̶ڮS09CCh4X[:AvBjomk3H$Ǫ-@ *yGS2xTJp9bB Qћ`G8)ck@ȎbGq dm2EbQ+_GKZ9F:Cጲ(aRpXmDѭ[|JV0I$?zF?W]3(~L&6B–;qG{,nfhbPŇ\GyjZk_j|/"S`F,$ 5f6EGo,nfz |qt[ !i P0<1Vv^K4ɛJ@D9 CvkӤfd:s=x }.c> lE>ٸ-)N9DJYCեHSJIEA l87 'R=B46]P_'gH)mdl$ .߳W1\ڹ\z{I!BxY$/K{סZY:T4-Wj6_ 0ȔAX604w]+=W:={varbI ,.bumC& #Ȩ(^C.s!g\qn.p" ywΆ:W ˦ q b hD ?xN#Qtؼ`;8h|UaF(S(KOMxcBw=^&@~݀nRF*₁)Sm{Vjdh;Q.m}ifWbw34gŽ'?QP07pͫ^~۾W3 fcD޴SlF 'OG,l6tGW,qs%ҡ4HǟCa]JSv7#]tL I$%U|z-4&0e7\vy8x vlCQF Y9!FQđD%6y\φ_|˟C&m݂O? H[vK)Eq>̨X[[RH((QJA*V&!ϴ gN8<\|^D1D w{d^Q(.eD!"(Q,Gz߾vDxv{e I":U+-_G?g*R/ 8PJ(M);piDQ};p fR6{XA0ԇNk H"VC/s-f!G}P\4c$^~X\Z8Fmoނv}%Ѭ`$ nᕹ>>Vi^-hoeH D(ߎ)pålZa)#NoӧBZ" X8U\F )l@8q/* |͟ w|'-3zk|D1у!Gt@G"$?ɾ/<2gNkGӋ}ŗ%h1;7GGGйFDDJg'1.z lhn@PTꘚyYf[׬!W#gP`=/;N<$ 3)@1GU`hhq: ߵ ea0ArSop| gB?(287cAبC{~}H*J#-λz67;A;{nw!خ23eD-%bvɂo2! )Q9e$&T+ZP ԪGA e)KagJpӭ7J 䖘"<ҕOŬWӀaZ@]= w8cHe"A,@iY4^\H] _N8#19p;b34E9̶ܩvh@H0,""-BfIڱejAJcˌLNd˧2`,ll:U"9ܹsVPСC7444|1ưт >VQU 'H!3V e>\O12ģOUۑ+/ܘJzw .u8ݿ QLPq^/!IbıOO7zFnsՠhFq|>adJ!;[98Nsh4rPr(v(mWB=7xcw+DgN')=2NV3XPOI&>G=K"T.BʓEkb=~0^M 'ig" 2Jj\?X&"ԔO{m^k1"Ì-;1::=[Fpaag9@쇈ڶPDI6ƛM{ps$Pg?$>C/2Zm8Ĵ5.ބW5:q's簼E?Jj &F}vލmQo휅Ryny1= \y5׽knyPIb}gvXGlVEzD!cC4,G/| ơپhO<0p@U8+eo6+Mfx(cy.u9+V>:r/wcρ K)׮r9DrMx=A7""^ZÇö`9 *IwFm>>s bvv:4نSg$Afj&a̫%@N{>w}kx_4˛gR(˙L>=qGϞR vjdx5>9=wbرk$erQ>A%3D!k ="xi; \iXH26 *E?~eU-8}I4c{;ݝ%}DZcE4)g6vRoA?HA.ӫ" ,U^z#aP{8aEx1սҝ\ IK3ڻ+וBb81Skm,tÍk:$)x_Mwu敯"T>䝥 tZs$DyY5ְZÉR"<:i41^# ob\T 83# K+__z5_q7LB)lK')پȥ~p/R@i6`m`rvr A{9VV{lj/^5eQg"}0FdUXJ;wE$ىqnb۱#]v.̅S"R ! ?I:={J<$Rb^|}/v/~sX_[DDp* AvU1#%:=/|I_2Lo&c+a5"$"[ؔ3 (2(toJځl _(hS|L) 5u' :\@l{!b.(j` rgȲeb#&iϤFRAf 2?Vec sː!w} ;n^_eQB2AJ C,#kY}5 {cׅҦ(0JVU6z C,%^/G/kxc. p~f]|\xs"(#GcODk$qS]#o/clD]?n/`t4=#xL)yйVV5B1:;n'Jad"MET2tвa8k^z4PT8#7 veNLaiek#vlN_vi\zᇕŠl!$>'].%b7Yl!̣Ps~x&/jȰ"Um$jm % EyeL7bLMH@H"lp]a! c NL+1 Xr6Y(RK'^q% :D Er&+4 o avg4]q#a %I+>BB@ i .Tm"'(V0TS``t6X~Ε!lM"j#ߕZt/kh&BT@ڰ7^FU܃\+l2:*?M"@ʰG[Fǚˏ|i?}hߢYRIȳ =D"JÀ2WFM>RH\{^H~x2n~m᚝XoE 1L0BAa$b, z(*M0!vЈ`6조W{yepY@L&htU$%'/`{#mOwz(Y/1"!zc6 #Vπ=瀶j@=g Cƥb RI, -@QE%Ç}WbfrN}FIO$ܬ:R-{$ά)$hs8(>rWp"3:Lc7GN.h 5yx֙˩ 2=*[%h6d7viSK 6 أZEVɍUo < 3y@NR! ZJvܨy!op|jtm44K16{;pPkh7VǸ3$ d$DҦr$TjE ahu]w(KI{P־hX `1FA٭sh45kسskS`D>+ )J>0,cZahw~{(D+YX0zp]pV6PcqHH"$Z页BE"rdY~ۅZe۶MaE#À\2ZaTbX *jߌnyO(gwUe/ /88^7C5 e`e >rHڊ"~bKq;J`]t|%_/xֈ(xnVHh)\` N{,[ 4i:!|]nK) KSYgg!#@:ə)ZWQ$51:28ӐHD Ő:f'1>QAۅ@Q[,vz9jm{k1;5O,-/7oc"q &Yfp ^7z_\GV4LC݃}ndZRE^o#|3<*М {畣B7A0>FV?[פX@.xlAXE`(c)Um`+( `eʗ4sT8E*/bɈA"V`)6VqaV[@cjz۶&'!DVG$N - mw+VK"K{<Ƅ'P֖-jiC[w #CoRqr Qj[0@2c=V;mΛ&"MZ"F Ll1.d\L!w\HNhW4c XXu$AFr,/-byז S۰}>LMoEVX!O;Py~~f Rؽ //K/,\@"Q"]4@݂V ҕ퀐QC+\I"~?!a ](,a,"ʃH.fj|ǟz D:p 0u]Z}'xp@\Pt<f v#j:@-@>*&&G0<4TbZщQLo–m&"VVlP7 Ğ;1<2ByܡUJ_ Ue,-cvfSu 5*yV>u ?~{ʵ+:R_q2j VH҃o\•],&vf0B@"$b V^}{o ;U;t4_ cPv ن/B0)HHg=Qȸ9ƛ}mu)wY)hSFA;/gLeV8.0"g @ HQyθdX^\X_[ߺs`jf+8ˠ~:Ϡb"" k qɱÇ7I\R/} 2;-Fơ(0{lG0uKpCtyv@8pd vJ $Qd@Y^E1>ž}{i<#(KKQ8qp:mq+N^8D~vm4)Q7PVjm+:#שTkj=+;n ^ BJx n`my ~c=NYMB!cz~2A]?ܵ6/$ڹ-uh`r $v8H$$6Pk4G1rT~LaKS{c^h k7a? l0 * >‗vQR~"B}l!ۖr\vD1 y0ͫ$͗+,K{"B$j QۡL z9nDE )1"("hs-I"JCmoVӀw߿tusstH)*e8u @D[w¹3g059SR5k](t_-Jw$B T+]LOMO4^[ﲃHS)e)p#*DAA qdl4FFo~81]{](,"5~ۙMV4nƑȕARCVEQ2 y*whyfOk_:03Dӂb ]Va Ơ"Ņųh6GQ5,p? 廪T ߘI k=i;MAb<":CQkh.1hR6hBDBG j$y8O^Y°@1ɩĕ 'P3cmq$!)m6Jb$qdQdm XB HFI'/=&AZ [^qF;뱜-Bk9 :39~={GgLV")TYx'pm `'vIHs<-[&̶L){") XAB@Kd+K8~)|+_x_z,* adJN?*gt;uk<0HLf0%6c @pN_ܿ^7^Iډ]81A6bTBM%լRuo{ f&kiis9 Qf $1hIUaDu'N&rj}WD 9yuE@Wx{g tP)7?}paK;JSv=hFڮZ`a!$zQИ[P>@Ir:AP')% %(SР C$t6 8m]kt=)n׮]g;yo.ۤHcE[d!#L(S3ht:X][gq#0GS[D;TE st> %W:/ \|P~GU"/./c`vn.ٳpчvܩv}awܩ^=;䉧߱u˥Vih%1H>t#3SP;Yx;Nd:?%]?4i Eꐮkز}p(x/AK]"ϚAǻL{nBRϠuͷjVGvqI$8ɱ:FZ lY^ d9RIP)0 Hz2 ,V2 cc7i:~etڣ Jb rN%<݅D֛xzEa&7Sfݷbef4ObΡ04Cׅu B-nhA!֠@ :0RiH PEK]"D$oM ")BS 6fݿ>@|]m@m.w,,66q9[YBsrN ĵ6nތ{M'N"/ @ˆU.)i𧅿wA@G ױqz_ƛr6n݂<.o];0o)d}-rU@ IO_bOD|;+:;=}޷ .AV,7)@f3@HD3ȊYJ&jI\ZHE w(/,hCNݎRc I۞DL~7ޘ\~,tf! ߽xSnd ~q@o}IևBbvv#?Mxld6cGe*?9 @OQ ;K\%<P>X탛A{4ıv |`eqx)}/ G={w?r M0S/H&%(F*I0C2o᫖` Y!%i @VOEl GNq Dc3f{KٹM%GTT\b Fo9n*Z֓YVy 0+qǐ0%7]&Yi?Eu0a3 >)K~A!R@+t$/ 8`ѩ`lf ȳ{Ȝ@?P2bb՛Ql˺l=rmWɧVlՒg ûEQ:ۈc(l 5veRGe0P @j 8ӓS{o͛ED6k/ -؉ЊQOBB (Hay}i<^{#O#5 f\4˱O)UY)( dZkp@§Fsk}ycȢ]݇·Ӥ1o2pQ'#QCsdNa֭hqiD| &rzwT(q3|^YqJA(iF[Z_Vy*ۯ{멧뗾z' ZKזh4s#17zV#mo.A* s($wCEHqv=_ރ_RV1KK"`[0**jQ+6_o7Rgy\Wd gp!<#O"c'ly BjZ#Ԥ`̒"ƘK]޼XBisi-2+JM2I1;wOox#ɱHKBKb ݓ .(I"V(ԒPWR%؈m8?᨞!Gp;rtNikBg},=x?T.׋{7 d%_]ӊJ*Q~8Dqh#'w+ iVFc:h(Bn`%&`JET*\CL~<(MM#D^z0yQ 萰(@DPZC+ZNN_f[Ed݌5mZYOq$i)NɣtPCg)utvK⓻wݛ7LmG{}K-*Wa-~U6EIt&qoƃC- ^M@!K60*"GYWinpÛ߈M۷q*x=cERGxw׻8s;w߷,bj$xl#ᔱ9r'F 1FGV E&j7`m R(YQh4;^bҋ>29igb{k Xcrԛ LBe `?=g`^LMÓ kP)4C"Z#ӈ̅A"7@#E4 @jfQ 3βM@# Mq<ː4q)(,c[mDg.w [iFҌ-Aj?<˾ Oe893::6ܠ4KH6oS0;='8"D1:m _z/H*:iТ( ~޷u3||PM|Iʕ?JH%eup D@3 1HS:1:jI`jGkL6EP4;uh6눢JJ)zEtR 32i5Q4;I 됦)VpvaXQ;p;a:Di{n#=mBwk!D/NM~Ё{T˅زɡpzu¸L΢n's #bhEi+gQ:@`Z#",-bQoށ[(=$1+8g=:sc&Q(PF*4-v58Btʂ$a@ahEyd0QISҭ9/A$|30sSH? mrE&reO!3ulfTzZE $RJ#4 DI&J'?$֑U}XYI Y 9yP’~uE)ԥg7'G],q9[0g^ #,Ol54#dE&Q'̹RxLd~s(y#/A`d|A@Ek`#?@fd̗xW,L-If-B- (g b8WR*C4'6`Ӂք/Lal7j0TH@@( +}G',lH֝n6曋[nw'>/8wzKNz,t=o {Ӧ24UG:t<>^@-ҍm9>+9a@ J# CQ$8Fk( @ww߃3Y9vmQ,[Le%쥦RC x-^Eh+|&,,_ (Ѱ9Jj`L{3B4`JN)ߗu%uLMM#b9EF$ yk+aŨ!Hɐi4`L(' fq!TB:%58'MFHJ-YJnYAB"Ԣ[ %(}Hׅ 7"r !"e?{^-kn~~^ !V>?/NO2ӳ3 Ng z+aF' XlܴO<6f:5RS,C(}N1",lǧ6~0>=SYt{>8XhP[ɓ8oyq<~'5F1.%]2HH@)JA0 -m`ha1V0nHh$-$nɒItz=AaC8JsØ\,(dW0[cdBUH:رV]iq[>nxU'=nsK'OqEc}e)MмH,,μ ;W/"M}@H$IR0EH~EKBPB 5RZ+왧uPN lNҼ )/XcQCUCYa0C$-ɷRVk'XfU>z+p!ұJxW)z 8K)Xf;tyw 6`q݈d!moX烹Nx/?ؓ?+k++EYȝ"Uk,Vϣ62N,6mݎ3'NarrQʬBZB8Iɰ hd) 3tAK+ؼy<}~ȋˋ8qxI1 +3yOπOBV!+ F ҐMDU'5DQcOKdiZ! Q"B$qPe 7_0 ą.R- X&E[uYG?o>5]o `?'pÛat(:MXhRTN!jo܎߃s#܋47DИFjY >l9k t2:m@BC<}j 2$"@H+RՒQV#;TWWQXNHya R, EN'- vͭ@0/ )=)+:.>Ⱥ2Jg B,'G EV KW} B2hx<=9 qg/-r;uS$ ߣ}?M$un )g'Y(NI0$}eqƐ֠N(CQOPOb$Q_KΣ9I@# Jѳ@4%0Y zDS|Dk-"zwg&1c k ctח]>ktE3e茎Ov}7g}unغ?y Ǐu/.L #n(N{ـIBiXDU!/A^WX8˻,Rܻu' !t!hG,'qVGV48 aBn}s%heXj9͍ "X<\}4mGX#³Teo  C&XgD?f)LA8PFv#cM Z,3ȍ),S hDyfZ#4&6])-H z-.EQJ" Iqa %y~!p"7mS\JGFѱOMw_ lַ'ؼe%VH)YmXp)Qk4af<ؼaZ O_lxCtp{5{=((Ui=ҎfA0 :(B$4=lFYሜYF|r2,5 S ; n;5K( zȿ>{--W]H``1i?:, 9^ޱI2li}1GB ˖R{. IV# $)v{69iyNĜ4yt j틼(e8C_StLJBs`[*@0Jkh4>}tRz@cÿmw4r8Э(YiKNAΖd,oWn_[aAq# [C4͑ 4߷54uXezx%W(\n DC(x61;}n@e8 i'I0>4lDUBA^uׄ[~wJk$IVNvZi)? !fa-^Y^pzKF,<9y^pedjB@H⻽♴P aQpaɉ$fc juf6'}u' | {_I~Ļ#IoI?7PhG#tby}[ 4-ZZИ.˻|]`D3c11ԓ{{4&Te0 ;PN17Xb U hxbW7o*ekh7"$q0)+8s ~9zh=q 55EկIuW\߯ݵk曍~lhO>_?_x˻FIΤ[Q\0O?}w T·a𗠈L~nG%GT/j0DRaf7L`q k>> ˰N<-&H~ib=͌ KREyy`cI)!X_ Q'2N8nJ1^YPxv$U/厔tNjW8'6o-|:҄ཏ:q֋]*#$VhSQ ':#0&.E&GJtZ -09h^BPtwȍ=tT:a%rq)N^\v`)$xfm43X13VCPzw /,ԉ#feN 16>V{I~vnӦOtMwkȍv0٨g wƕ]/(J&;#=(mOc3el8xG5"j6594{VJBhkqH8gH[6ei:9G2:!N6 ՌQ-7s5wS09N!79Ҕ927c$q7qFBr{!LyߗphstJ. kk' B0BΆ@Ęۂ'!$ɞ4p tZ&j5 : aZ: rZCJ-'!ˆs*Gq»G?{ydcYɏ':uLHOٳ'ͩSGʂj4rn_yxg~?sGڵK:ur||\2z<?+ngMD/[v&/(fEO} sѨ#8BjZ 3Z#cEQG̳5=PAz҆9M)s [yKaAZSdj1C^9炍%,Cw4듅W!OSbY(` "xnlQs~ bɗ^e.@+gBǏcjf&*la]Jǭ8IX,>PHtFrȈ,Ŧ " !u!QsO!mWDcC9lJpFϞE+ q!:1* 5?iO:lj1>9zsI\;:2Tg|ַ;v{0??*lߪF۷_zj߭ǟ>K/ EOM>E!>D]G\!/2a&La=Q6;t ICQdeX]YF^cRnzEDZDx𐞿33&m iɊ`iq21Dqi@b-sӌFf8r)Ty:7@=iy־| /w )& !DMMG ~_|TF 0A-TiP _`0,sKMXY]CQGkȍChadрz+B!5PJR/XDWR:X Zj0&Z_8sro}uJA\ڑ]8abvY?5So}ׅe;61 ?v^˿'?Μ>m7lY=ȭ*~JS͛oc,QV&/,s؝0VtRw(<wC0B0@?$ h`\GCz=t{୥S-Mޢ0elui2[Oؑ@IpLS/e+J([p:-NmƢ`0$"Z(%MS57œoȢX!@VT"kk]yZəMIVd+j0y Wus7)#SP¡oI)M.9_ioeyRj#kO;/M7뫃wisU6&zܹ G~y ;)50tyQ Ffz0Y{O9u!ᄼh!kǝYٳo*RGǛoT(3k2t! PP#Jbf294ϑɓ]- I}]yigJ2ݕU[ύ /9l^Yybi+.o-g‹ߏaOqAk+MTWEPJĉLXHaXs֐:P34ذy$AR@O!D4qQa;v6@i|#0+a鹢dª^C\kBqT{jwMw =cѿ 'g=Hv׮g=t32:3/xYֻ:ĖK.#0>FD Ee^)9KG= w~ ttj{{9W9Ƀp b+$ <`=+;z??8pKͩ:Hҟ@݋]Zdߟ2!{OK~ڇ5@#zXYZ™#O3-4-DqG@լZ$S OyӶVs.p}s6]5EwEaUh9u^k3׿M7tϷ^,'g}-\{'<_9?,m@&M]YEslEatl3X[<-Fȡ 4ayceTa[QJΊ2phÂ%B@!8&F Y'H!̠g[[YV?*tݞ 1uoK}|xd9h[5{ދ ˉт%S5%( (CG <+ҶtX Pm  4 a(eAt} g`eq:1TZ=u]eNxGrƤQsְN +,@QXg mlڢX:jOCrZ=bnnW^מ|w98uj}4/E?X׎;={]G7"5^> !ژٴy܋~' $nQĵZ)sm媫𐊟3vO<=fOBAhը!"ˌ-HKgX_81d<)yy=ȋzO`6.ht{餯 oI>RT ?fY͊ݍso4G?ڗKϮ]Ȉ}1M='|p{׼=e堳KyCE\#5=lCՆ) lgVQr ([=hYe5KIG0*`k:FZ DGt^փsniG/ʾ'xDw(es}JѤSVCK$zt*hXO!al$?rTκ4l PP<:eAI  HWqiW(rQZѴW+wC$B"Mi0cEi3kM+:l2 I{tO}K֭[ssN|@qv~~r-z\YO/.-Moz{!v H, H4GF19){G[ϰh7O\:2MT^IfZIa $JKK:i!R4+i q`+Im㻻? wlU[}W.nO-U|Wb@?&1 J:xAB_e+7"<6 إ@"< H񬀝 { gZ ')9XOlB1;@Hys/S_D'[9GH# )rcaC ź ֥:TB4-0ADG_sF~_Els͗cy1u%L'7%/*1> 7n}O DXH=8P$r.jZz#V Z& P`Gz^N:8s(m8뭴\TL<;Jx>Dٍ~.E(*8bqA||g}j,g5Ҳ.RV>Q L1#DCzf:47I?+BB8^A-F hqL?qȊ*3^6T X^Ve0.2}S\HfQrQK>U{^nVw^t~k?nڏu.2@g|)}iOǩp#Gn7#/U59nq#chj5`-1yPifG Y }99 ((c(ށ{x @sHFq,ct<W<eK,5/ZwGhI[yޫ룱Y]ڊƉT:Ͻ'zEa^f_t%<$+]Ǡ֑xڊ%E @rgJ1D ,4?e $MD N+0=$@G%18s@Zt]?̚lEGTN4(lv>әע=]==X5qS*~"N(̡92~^}X[N`Ӌ(% G jI FBj4 jy&- 6$M#0 x?7qmϾW<$M) 5!J},///}y?Ş;׾SsPYnQH 4Gf V/`yyl'eAa m,0ȻV~RyO:ŴhٮlA߭#lZ8N[F4_19ωZ_TȾ:$WOpx\J'AG/r 'NdH25 c3^( Phb1:6(FOڊ=w\[>'"Txt=ɑɩ/M?}kr~OoWyT]ՓWȑ'eW\i$>/j!SՕ Z:6ná0=Ձuq͕qu{]w!DXY_Cw}dAi6v?ϹҵoDiUj@#;_$sekTx!AEw_Til>I6IQޙ:o OO@=iժI(_kR՘ol֐(B@nme F:hD;c3x;޵gYV'wXeUy׼:j' #D&IS paLN{~~?z f3XC'B]szzt֛g_Sl^/q^8JbjGKH N K ҵЊmaycSܟΨP̎t,$xbV(eC7'8f(juF&fdt⿿կƍ/"+Sa}|vرcݳg?>\vQcʇ"bdtKgbf߇1O0[PWr|XrUwo-^ςYs$2'›$,9/aቫPt''(}3x T )BWBx-XES@xQr_қ1U@ga)3o:Q"I. ]<;:]Pꌎɱf|j/6]?7ϸOgvڽWo[[Οɟ. ֖ Op 8HLmiǞ|g.·c(=ub#ҳP݃Y֛Z:Q*#0We->3܁ҰC=^ tWdo:j櫃WSӊe?O ʏH] &XNOTY"SRbYSHzZ3Ef.,sZQo}Ӧmxӛ޴z^~=7o޺S}=wwٍ6KJn=9 ֱ{Ϻo!Jm`+Uʐgxb1o_KN*%m@Mʡ9T=_YI<lX4<VsH1wZ%?ҕ"\!ȇaeDai4j+.]XpKINGG'&>mٱu CǽpI{477x*B t (FXp<|V3hMA%K$e@pKX@ug՝,,VKt7VJ6 Z7PJ2s+ Z$R s$,+.K_sW ؀ $LR$sk&jE_-U019ltrmzm?y >X+k~~^ۭ׼:LN͠(,TV<1PT#,vu~Y' .2bk'twB7g#Dv *cԳΖwz4)gNBWw˗LBJhd}ě_  */7d&M9@K☴Q貴o> -u4u\rܽS^~ɕ o8֝ ؉! v;_y׿o0ɕeJe ¸;o/Vɳ}0ѮSҫo4s[TV>~5Jn|NK1>gOI~TtY1/`y?%@(~Rcms +P%O%$$D@^sƮ.~^ bnn0#ϼz{WC*^nf|${?pן^y@Q0-^d'WFZ ՂbbϪW u7&*"cŝ3(|g1Ȉ+* !PY؝36Ag |4$x >Q% lP8,P@% &[_s'y&#ؖ^zkv[ϫ~VpW'lnEQH,tB*SbYy/~z JpQr [pǝ򈭆^'zk9ݦo/ <~͒px?OXw0接tEbg.^=!y'EN/z FҶ[KKguڽ$Lg7l_%~)w~ѿٰz!7|G>}__үVKSh8,]i ߵ>t g)pWC?-8JB=3G6ϺsGɨy2%4;!Y^B1U_ԍX'J IY[28Fjt4>Q7%h4H"KΪy('f>13_Oo);v|Kհz0;;k~gbկ1 !KKs Gz *B-)` mm։Wbt"X˔M'A>; \{pIS2s Ac;Wp[B~JzUh>5ෛ 4 $Qh"3+*lu:ش'6~jvvӧ<25yB={԰zA;x eo/JQK:9:d)O#{,,S C)'5$tkCV) !\iIZad} H65WRT:d$Y?pLu4 Ԓ,ΊSJ$FG|ns/|}k7|5Ñvl۶>x_vp׿yv9@lF$R(GΏI{IAV01Ԓjσ|>6yp$nv+$sp#T"TpD5s1aKg[#䠥Dh7%YY<#O?$.m3xҗӧ~#.z;.o0L~/Q"@4'il@˔TH3Gkџ$ʔE١2A^lΘ®.s*(0> ccS_6!Dy!G^40w~}ϼ=^j(ܽSǎB G@R*b 4 G[~F/T-d402h&$, iJw|hHWߏs~.yʍm3'O2;;I-)7hmW/.\#;o;gZ 2p4UmP u,ADž~A&-ћBukɣnmV<9==6]򱷾|j׮]ԩSye:p;vB}o|?u3fUR'yh@wvPB|?wΕ AOz|uq8 )BӱFJj H>j= !oK xz-ioݜ9v.=uf%/ygg6~_]/zs- a}zue?ᑏ~e?6gtl>iY.G7M{`e)ٕo9y;l%KtGSv I&Az|[alY1ML7B4 4k H={YtLFG'? no{zmOS~_cAQz[,^Yw݃w$9ɁdA_/9Ζ_*Zh "HᰶdN=V/ЭN[mټ#g㓟7M_[ Ą۽{EgXIh;.~S_7oEQ9N7~E#7m |Z;eЯQq"s#'#4uJQ@m͞=}^pD71lɩt&bW~̝;w;K~Xih; mxSgY% pqsڏ`fJ'eA q䀕~u`PPObq$z5wqp6mB^~O`||ɉݗ^s'7Qf/G?zw}Wn\Wi\XXƽ=z#avⰋt (v:%8ϻJ@]cIѪ@.;cΞ9*ٍ[1qF'LMLܺq}0^|z׮]f7^/ſw}W>կ~}KaENk_F[u @3l%y ƌ<ʃafvҬ/NvEv^O][16>yar|3sw&s󮛃Ga8E`__ozg>iCШ7a'O_8bEEoOޥAII:A^ XFzr +KOٵcFlr9FFF'>;5q7w:H:y^TO}w?%nfNۯ.2Q )/sD%@YH TR?]@^! 4lcm=}Y:+ZuF_{`lb|+_[Mcǎ!D|5U?BwmW39aꍺZY]_/xT_ b9FD!ڭMy nqY\؇(Qz/VM?0;ᓗ]q_qǎzǎj ~`6jԬ Эum_܃<+ .;Jըc3 +Kٓ~:U3-W019 ӛgg:I{}ؼwqHT+_RA 02֢0R(ԓ#&R5,?cϞ=bްjl|)#W'>vsW^9Wo̼a0;v`Ϟ=ΞYxצ D-dW!DHQ7'QFz-TySǏ gN ٞW]sĆ;''7%\s_2Ɠt!3oX? |y~_Ǯ}W'I|kwCGFem5$賴 ]>Z#V6_hM/ӛn`~X?t׀_W__q5'#O`t$QOh!.M*F: 51>):_o޸c{ eh97b?s?xG7nruD Ҭ vw^ 56:HFkidl΍[6// ;UW C0S7BB7??~뷿;>  đ{Gdi9a;ZtRnk_z-ƹ?f{^kXzj -J/Wvdi( |J*|q6\xmoY5kw9$7T^z7яn};c,>}>I:G^D)~7\x\z1 p_taf~.K9sNl6穞 xT@~Ν ?b/?R?5Xi3a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXַdRq_TfIENDB`OSCAR-code-v1.3.1/oscar/help/images/prs1_60s.png000066400000000000000000002044751417327530600210740ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGD pHYs.#.#x?vIDATxYmu}c9Wӟ͛M 6*Q%R`r8G0p8\(U([2EhJ(&I A$m"~s})*6qFƍ̼9c|yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyǿ.ٴS^P^U=(Z}++ l.n?k#`9nT1 5ׯ_J et;[+Q#2n'( 'sǿ߿o8sζma9qp.'ҘƔ!mr2_'8ץ伪ޠ*~PoLBDc% GLXDU2a A;_ؽ=~hcr'* 'ƟzೌIb:5y&ols; wfP-5P6MSgh;UeRWa^E奀 )B}SH\ݍ"bbDU"i ~n9#kYkvnmwƒ1X Lu )xhnooсpW׵cfWgu(r:'9S)(U[MΦd\=88Mgլ֞feUqBee\}\̲! fPulU@$ &b@""x0u@`ԯ(w`T7xׯ_72 W'_)yYϳ,˜dp !yibQU鴚J]êe%T \C[d,NV Tm6`fUaTX(TL:g2g :Lw]ܹ9,;jTޘN꿩#aƍKʲ,h<,y\r466r>Lf .w k!TBR.u5:7U0YUoU~i+!y!$ "DU1 LJD)(݉$UHAQF*Ap@Mlf8B| w#;F89& ' TF|xxȽ^Gܸ忙I< ?*˥xb[Uu88d(]"R.뺚MiyppT‡AnU,d AB\TnZ厈,HT/pR wPAJ1 PB㭯 &* *TϱaIc@*^UjR3Yz WnolTe4۶߀w<^#ǯA; ,P9"ʺ(t;ksk b4l<.Ϫ䴚5WpTeQKlA܇CaKĖ +EUDHW i UC @1 3 Ql0 4≨֠ %!|`;v/=0_21ʒx0ޓO94q+jڬ Mn "ma),+t 򺮋YyptT><=-fR8j.Qa`c DŁD1Q#LlHI'UhAƎ6s"2$($%@BBl1ŊAH1BJLJD Q謮[4C==e}t' U?ʐ0 !qާPR'^MQ7001PA(5:!+o;TԹyǏCtxxȻsnczlSxb`Une1ow.E/>XMO ,?JM=L&4m[mF~: ?Rg>ZoQp)w^U-*X^Վ43#End9W24x٤8˫+jPdyU,6cKN2c%Î,X6a63+ B*11"Fax;Gȉ/kR`]Q2GL1hHS0+*q@@ T a3VV/SOmThq||& @I5ޘ_ /|ߧ5Üu흵#ι,QƠ2[[֭Y{zz߿$rMU.|mohp6eYXVxaGGRvj)s. of[ږ}ƭD$ќYscla)MP6U1Nã8<>N墩+UEW\¸<7e"@Ɓl,330[!&6 b2lIR)_Ӵp/?RM}8sbJjo*" rӸ/ZP Ʈ$Nkd(DUU٫ha>eC ?ukدR=Y;f;s!, %?'~j.;ܟ.{`m68(Rxֺf9bv"Y.C̹r4֖[}7;O2]/1tnj1Se[e㘍>dy203j>4MZM RȲL윘egjm-J M[<>>)t6-u[k Ue>EQJT`rl5 @XXf2Ć &01 x %M2Çc M*vh,hQ=R5CҌE c7%PH&R|S^ qz]@bZ`Y)0))^v{TU5AWǺ`0Xܻw}~xxtwkFf*g|L@,KKdlYmD>*91J5\1SEJ]Zn[fp&bfNCL}88l~L2EqU`ܺ6Nd5rfâpdr!BӺl:ݽߐZD[*03[ke93-Iu|:l\8D0̀1c" ѡ0$;%xӣǏ8UPJL$A$>^&_*A {|EA4]wf]T S{Wv7o D j5FK%fp җ_|W)?tjĄ&!mۊ+ ceq"Yk3c8"(鵫:N{g't?~W]euВ?̺֚2jo.*.cآ`ˍq!2c\a[B )[r54;88ೳ!/'^^48Un-˃ÇlV^*6.+ll8'\s263N&e ,5Ll/rf"V1c{x."!] kxCߢ* XEbƳϩHğ44O ; UBHaD -̀j躎uL`cB D'AgU2vo?}*09r:W\icb9זMOSܤwժp,WVL۪#2 cWdR<|ʹ1y]LΪIaw6VժlϜuT{>_ݼ_!af 5"NEVmq 7ln(V/Ey69+O<;9-ϙ`A:Ym{^||=9;Ǹw͖ngw?ַU޻W\yZ $cK6P`22f` [R2l1,sYȷl *Y x3q*3[TwZCU1LUBBa;đ$P~Uc aFbqsI Pjw8%Yk1$C1haf JSNX(\rO^mcIv:c^o~_ ;fww΃ߕ#¦,{,S0M5 %3ʬu9E\H|Q&<:9)NNΊr<}$U*H<_//^|{.m_c"PkaAd Px/Et:+ONIyxxTUɬl$Ғ-ma,ge&fjW7}loȀ%v >+qLƹ4^c8cA!$1^9O4qVجohJY: !f6 GL))۾K!% N7@Ճ#(YS*p6%5D%( 3Z@9+&bYE" JBѠ9׾?~Վ.&̒mVjȧqqrxRGI9L63yI EQ͕BpDDDJ Qoe煝X>"2bf~#mƋ%aX)1cnU# qDH*-BtÌP7Ġk!"f@0{֣ݭoq~[ד {w3BQ@PDk=3^;wC"iJ C0e!,?xN0S?&T|]dڦG/G-WV*_s}G4ɭB%T82ỹrVP-fyy68>>-N&rX!h.E|&̷hDTH-N!J eg<3ߜlmhocpX,擳?>=wxpX|^mSk km! $gS#%sưQw3Y|S5wn2S=nV&0[ 0q"΁1ċW~W.r囎4G ¤O5:HQI4 Wxw" ԍǟÈ3į)8JQF#y*k1P2M@0؄|p$(k f%J`hD=& w>ާo=qyW;0,gƘ~ue?O_xv%falu˶tVg'8:9-N|WuS &AA Ym&8q`l CdpԒ()5uћ?]|~ͯ~us2NNNeլHDəlnͭ59B3U͈D`Ȩ^C`11u۪"!Wa%J|#t^|uAH$XЄD1[`w"[ G'("YxkL.镂7D(AB>;@~=j5 ~H:yhw$ukQ߻fLjJ)&3kYFAtPy?x#c¥iCLKk}Wƍ?RI((ݸ-KYքY.|19NO6&qyttRL&iV! hNL%d4pb%KVDY5O*lMNQid@l7m~߇sLҷ^Y ze#aKhFdɈߓ ԙx-z"R"ej4 Q8!ؐt|_5!V*"lL$BϧStI- h}x#;V`J_|! W d$#)#HLVϖu? Θ!Zؐ^zI{Jj5my^W/޺s4ak6UB.sՐ)( ^2q"UV!V YJ0D%DP5ƦR RO7~`wvʲW >4PFF &f"CJQ"D`h9SB0L"9rU+𧿀>wKǟ0-4֬ pif-K;Da,#OsUF4߂I]0B:@VAZx `fX6$NI"'?=5A;_2x8)ViiT5 b^`=y&UkPQB01yUH+&^ZU!P87}dKf?ҥKK/G- oBDH]dY o_×.?|xxeն |Sv}|6~[? /m_J$ *jc"6O`PG,m0ގO2/ SBE Րq&8f2P60Qdؐ13",W5}x獷M^;` (Dn M5Ml6V#iȚ6g(yDw{o]?.՟t-R~;h_Bl77kH OU vX)0L)uhʼn#HGfR%Qh`%eljЭQȭv/G"W˿_x6;8'Lejکze;Pb5HUU+(iXaJ\,C{u?c1!*g[,D4@L%QJD"EI47 c,qɾ*>!x4m 2E4-&g砺L:75û7w/7?Ayo5Jd)i)Z }x;f*A|( S !h&;uh  X(Q:De%8%x"BȆ)@`V(:;SOUB(`buEDG&I`ڌ o0S`2{}amV K[rM}M?$%koyMQxJ8`ę۴[&P^bhIYj)D$O@PJ$+e0q,!Pz6 Ll=vD[{ZD )4ƚҴ5hji1_,}l^coV}LOQ5M +o(1 w ǃ~ =Ad@z,6&&5Y{$Mwj;< XsAE<:jPANg@>hҌ. j@%b $-.XӀ +V %U kP< EY9\(J.K@>,+7~)ϧyWS\riD(z csng>3U!xg% FL2?̪D"β&> hA8̱71BZC$"8Xcl`mzmi&hmۢiXΦXsp3|iwqz6ml:=aO@!n|cxlzͭ bOZq.>q߉!0]2` ,ʰA^BH?XCb "HiD9"f(l x. FSP/XLƘ`5YPeޯ =D@k-<++dnlc*Dv8 xM``G%5`1BR!ma"w3O^mA6~mLNQN`kf(TjwƅٟGʟBۄ7@6 Pbh=5rB2NSvD$ ׊62XZNZ}iZX.WX,X5v&Wڵ T[Q(SdEFx{+xG?aw G&Wx@ m2wx⃟,>s8C\| &vĜ F&lJ3 H}ETbfqV$y{H>  Rr6j1fc4u @$Ou Ծ)[zV`AT#˗y KRGBX$06zM!':Ihf ZEΖ+ogeV$"q {lneۆ1M{pyEAmP1`g#׶Zټٚݣщ)YYfԹHHnl5&#pbr%" a]F &hсTN'bJ8 k3du1uQnںr9jDmjbri(6=\actYE5 UM`G숰5aU{oObkw&.\l-TK=:0q36o80bqMyѐ7Z$x,&_ đ@K,J~-DK@J9iSh_ϦMKmZI'^_;D"Wlw6#br3ͽعxe/Ӷġ| `C $! "B &??x黾{[^†x xvwF4JW4TMpŌ nĕȂۖ咧NnP}[z÷6os>VS#Mx祗.tZ٠ J޷eX6٬OS{v6|6!Y+>ghIJD{z TH X^cٞQNGUeIAQ&;"Y`uu.!€zzrrj_Z2g000m,3TE"ː^TDdtn=fRpI,3q1&{/iV8=>;㻷̦ pA$ݜ<n};W!J𾍓 "nđ:Tz_;ۮ 9l#eMՔYaދBC6҆r6r2F]7h|oZMzzH^"H/{?a|?g>i^(a8Vyo~ gЮ{+{1\[T g =5)KG(6)FSSeQagL|*sx;೷|-==={/#{hhllOg ^:.Ox5-޷y6F֐C K !dA ͲF$0mS%G|Dq+z(쬥.GQPҞD4 zbb|bjn},b4c4aW(dlҐЧ3`uKHj,/L1!.xx'GgX'w-y㇘܇dyc!Z> p{hCl-tYD6bknVJ(Q51kw'@#@ |q?xCXa>j, %j o~_YAbVq!cUFU-@*IiZxQKQ%` oO=~U2-,vh#O/ X'N\.MY\] uN*42[ ? /եËd=nREJ"B̆C"BaQ%A`Ȫ/'DqG"YeȺY.Mz|zl jַX , mFCC!vM#4/$H`ɦcD@x2H.^P2iUJ BmQ ^yػtY1j_W]e!Vip~}kXLQ-uhM;8.[fDUUG xD@GE|:0ȋ&4v8 gҡC)ڊ8u!ZQ: ! cYeb9kYW/q,ڶ2\{/qLw3ϰ=99ɗSp#tS$O/쇠PSՂ22"vD&jTE#/H;GjV菶0>ۨCdyU|6C<} ~| yf ۛ[w׮\{zW y /V899b6IyQId#,(5qMY$erQk!Rm(9v.|{;D&a9Fv|$ Ņ+ax K068C'DbLV辣O a&!ѥ%5(+ov[W@R,\B9J6hC Mhep;3{~/r'|#> h`eEetlcW}̛w^rW@# M 񂢨`m I b"8وx_VX2%VOq뱪c fsZ-'2 /r@vww/[3+A+^,萍l-6}-|1mNӳxw2_,/J%BY$DT6i])L̰f9DYy,WX,f&M&MX̦X-稃G2gЫrn 9`4Q!%9|C26F(21oX,P0!KXE/a!d]%A"(GYizED[x!,qq oWM)d6ͦjlFAWJUDdg':DN>~oXeq6K=lb>j9b>AQ!˸yYa0C=iûb Z~ =یV$>"M1W%u<)3 k!M Buflsp^UvD 4OMb.K/ k6b01?9xݿ<˱1 KL' ܻw.67pW!)mE$50==FVl&KF Iv⨺TK|?&!3AQ PRbxU~rԵ7 Ih, n\_|j)4wpz6W%Ș" MnoB)"+:ƲrO*1*WK,fS4V%Z{~hs#{0@*P9嵧 !A"Ę2! G8QA۴Xs$H b+rX ;V!1\p9Zh} )S(zh_6]51Bt 2 hVKmTx~]EQ(}FC,'MPQ#Р7;m\!{X5+IkA%Bbgmv&Z1qd 9U`ʊIJ1zC>_O8;Wkq<cogDblPA9 =,KrHB,i  ~ o]쵋bԻm]o^R3減.+E'6ˋx $zbT_-Vm 1 /nagkUU 50dgw{t>\B-Ru% 41@@h؇ 9 8o[rl10 p=,WKܽ'G}X^$N`DJNsfeKB_={~:G}T O%ZHQo#3TCB\vѶUaMs'k"]tbߴ Gh=@_1kAIl@L :~.!/O?oWp.v6G(>N100(;,y7;[hM]÷PJF XH Y(5A$@$ A58GJ܉ccذR@Ppo^|jL[<^yE^qCRz-`7,>~kovDdS͔Ԋ 3Y&(b701`-#+* l6j94V+/ ¥ml*3q,( QO@R{hCX3@Yֆ SP"89:j6nрgQZF^ tТ?;O/b2!}vW%9"fB4(U.5\}= F7<˴JXru k,qOgq?e&<9 ƺhh ѲK@)֤x#!z(ظ(Lbd3믾>hѝ DWմ'g'hj5g-Mlm`s v1L1/=v/_C]`WB5xf٢^<ɇB%K)]TEԗd +LO&׿S抁ƫX f9U!(+n͖PPa #4N~㊦;W 2vr MzE5'/a/Я*ycM$(56"EAv=jя$Csgd'%PiNl ,r`6a^1>Y V;(\ȭ 22\Md^2.G&-$(B73!8o~o1Go0B50+J(U~oC6,Ƨw1ڹ c ڶº, 6! ZcvDd L4BΛ26٬Ebτ! i%s +T|/@.)c0`Q0 3cܿul^`1>fPm čկK*C<bH^E]_&@аfƅ$\tt0VFIsbS](jsY!Ү4eXHǮWY0 @!-LkUKLL X+j(Mh0 hY|xjF22's% c c=y"n㣻X\p# oIACj$В@0#o;;xuTAŰ2(8m\yzro㓟YGe HP$B\ɚXFq@yHdQ要I Aa'w9~mOႠq,V =Jc X"?.:" >Y1jwF탃W"jqOl\fɱ'ΔI@`Ǖvotr^:X!'`r:igIIk9,<4֤2 lkLgX,MXNNLBvZdy AAQq!E`I1 Y8|LZ,pppՠJ& ǜHM7/'!WU(sE8*O} GX6@BhGCOG~w{|u?C%8^#/Ezm\[(\IW76c m[#"%2NVBf=^} ypzp6v `%4|ļ,P%>^c y0W}ONϦrmm;xߍ=,Q  3!HM$Jx܎D,1)I% VLIhVVg^K̽/KnxW)c ό_gE^ol WU&&u\}jJΛ 3,s(JO?=,f y>&]䦃J uZ$=HEz*6M(h}@ݴXj:A3;dFQ ѪA-<7z7mB_˳hIE e2-!ܿ`/UX2DZZ2ѤdE+-&`kw3bjC=MR%M>c %7b7aFRƯt+Z ![_:e=+)1a#NMfY/V1l,MyC`TXWWu?|Vk޻8B=,XX.sh Ӫ(7\ztѨ jFEUZT.c0aXbYȳ&yBLH[gQ\_Si) W`A, bOkP,эQc8,9x%]Aխ-֢D-2b(UdBPӨY`[t 7&};^at8yoi G{8;oV` D;6Kwq"+<`]G@9R4 5Bv:e bW@| (&(Zk<&A]bf^CU8 j[jbܿww}\v[;-l'1D0_>";-B{&4չ/'ZMմ(9 $&Js" n@1HZg6Kߪ^}ak)<-i[!`g} Asn?:::8= OQ$Cp"M NN!z8ˏ@XXZog8umIyobqp<"u&yQyaPUyP E/GV c`98E$??HDJk 7a\JYڤG6eQyhwc޵H l|*pm\0M\E ED_Ìlk}/nݙ κ-d2נ{}2 !H0$ۆBolU!7 ,Nc|6\1o-?I|DZ Z`F2ͷ^ X˘-({Ň?a4M3\xwEU8dYM݀;v*%@G8$pwj Diq (cpzz}}p{\v ˿?*0|_ֽ=Y ^Њ,fkke`X>C%$THy,3̦3lmn+s:xԵkq xh&=HN4i1;>iY?-z`8EV"/aᰇF0`B5?acc,2)Ė[ 5Y9+cy89< P,mCjYB{D#F!~oe.؄ɲF/i@t(9gom]ĕ(T-:8ߏ&[ortCjMy^10`6;A i:W Qxo|,z4*ҳ^ FQVN/>}fcl`({ z=q[r!,B9B5GRe?oS j5~x{UH5R"(]ҞAH.kBkN|]RHw^yҷ_yҼi.ͺME f}ϝT2k^26oM|>i] v`\&, A$ m&fɱ/yƾ}Ycݢ%ij`cns|ø8Ca|6A^X5 .\*Qc=?%ln^|ύhSXL k}Y8Lдm+s\yBn-2DmbkyQ/h2:&GVmWz.ڭ%òMSL  FvҀE"d}\o>W/K_/anP C,xǸς<3s'0_c{Wҵ'`A^,z'X8(ck׻KigPnN N %)֔"m%]:Q% @<6Pg^M5w7NK3ofYP\%fJu,uA}6^,"<>>. '*,*ٱEC 8]]/IflD{5IjnOql8_g1m¹UMN<*7_uB'vvv@?|UX-8>9 G`cAK,[;8EiȾLiazd#^DpLT$6QGjaR%fb>==ck.#ˌ HHeY&ND|Mvڟy`J/s9vj9idCAvdt:8>Q/]Vo׬60 GClln7oBGU0k"#$HI0.>tap06nOdMׇF݅s)-47h$( ftbvob6범to)dѸJ=Qjd:AWx{Ņ+a:`|z 7!%^qD/;cq|>.?8vv/_ul]u> u 2?rXZ6Ҙv"׋;.1M uX, V9ee wV|C>۷!@Sǟ^ eUA@ځq.bFm?/4^5<8:o_W-&g'Opey ɒWF-i䃈 OK2Y*Qi}$o] gDRND"hy4bkkIZ+.\ Nw.}R5s#-my/jp޽H( !"q1.u>q:=^kȲ *G1ؤ-=;,%_ȭ6|-tl,Ø( pzu`mXn1o5M;8i9}[<ŧId5ogF;n 5ݲ& $t)h~q&b6Ǭ,уh322Xq̧ \{ <86.)|¯xO y,^~ʷ^;n!.Ѭxke=8WVYk7 ](]j @>>JA2YjT&) L,L f0Vٔ/pZyU $L&?KX'g%f΋UB٬k ߿W6@ษў=fN}qAr@aXCQU}ڛX,{&mZc/8EKfa(D_T!Lѕn5K"HT]y^RmAl|Pl^օ+MI\ r)EsP$Fz@RQ7b1>#K|5\ .\ ! 4q4^ӓSl_cW`cgiMgXӣ`>6(2J(ĤhW+3dEh^gDm@56𝗾Y|haMw9A]"A%1p.9g?AEY.b@GnN*Z Zcp|p',a|Tlz{`=ӵ/Rr>"҃ݑTd89 ZTZ&i.\xov9Gh<5lWy{ ,{ 0x;XXKؼDӴxw,gE r,?-j?>cUX+![Wb0؀u9Njժl@]7m"Bxm@ ,1ZyU\{W=bA l=JZ֩F t9=-J\^|_p89@N"۔ Jr7"}C&5PBHTM0=9D'd^5e.]o_?G[օh^ysptӳ3,Eov_=}}골F;[ 7E0b99AɆ4ڄEO@Y>ҁd&/єft V/\qֽgS= $ bVb4V04Pj!ՖXt~~ o驘p߇oMy֭M8rhbizZ@d>>n} eBQoE4fwt._&.k4\͡@-# ~xdԩD>h|cH@U5(> 6!aU˺`X"/ yV 4$@w.=L^,irQa}"j,*`cJl,vvvqkعx-۷1^{22 5?(Κ&{w|6ɟOmGqvrNN~//etxB,VuFD0DftRI9N`)4Ɉ4-Ʌ}97Bԁmۆ^{M?U˿tM9mX!)T#zj ս{ղq9 uu}RZdF*^{oll`ww'Gx"K7_WovՖtٗGBcTy^]o5@}HD6VJۨۨQgy_np=8[Db!wy&Dzn}PtKMi-C;JtT!vx{:gAlpױul(aRMuEh*x(,qkb9{]:56ZI଄ϯ{ )349 [dyvyb[l  6+PT}ln#ћo; Ƞl*e^׿O4`9̧S<ӇwPOR >X1G?l7wVt"TR%8MK$]PqMh7ƍoH$D "2Eu{{mYd )wvvoo?SZ#kWM]NE,sdf\Fp@?vՔZ{m6U]{߻+W.M,5VUMr]J?g .՗!w VB4`_4DetQܘ# !!3(ȳL p"+(H -\/Ga]L_'PW W Ń).Wx߇ޏq5Ub6Nw0;9S- =E$ClO/Hjd\ygwLPj%{I`  kH3VS t767ćiX7Ţ=}n៞R۹l!f4CP>m x/Pn&B6$CfP}gS"% fִ`#KaiO1QU6s!eX:/^cx<|%?i\$&LJz m>QWTa}xS?I,&cT#m۸9T..\ `CS]ݻ=ԋ)o#h,Re>dB>ub 􌂣G))P hRA{$(Q$&b6$Jܫ=W(M[` 4 QtH`[Mzm۞Yvjomml{BB.p,޷0m gg#/}S/d ^)J  4`ئ9iFH)VARJH$Z =;(g'3.AD*Q9q},jr޹twXHbYkFI1!H!% ( mBb:-3"+| =$ALeZڦnotBXT9GG,AVb(2|36M,Ggyfwӽbq1YAD ŀh+| {ʕxGo|MӢu6+Cqrʘ RM&b:aW"XS&d,iզ))<k3gpmaADOnY QHXEE34vf1uۅ(XB L<ͯ㓿Kxzps LߍQIƟG ?_e~:}KbMCKk$b`OAa Rse[ g?+fX*$̶iGY+lYɄc3hk|Vh<ϡbCjvugJ$)o ז'844$Ni~ըBlՉ'zZkPLS]A\t92J-˼XƓ^s^iQ, U4HQSRJGD965X`ƅQ)rhC6ՔS`*k5 ȋmu-&);GdI6QZZ ɻl$Ew9 A EFFIx\N`,'M}M_:wy< 80^~}Q f؇'d$)c:YF/y(u ^ s$H4c)qR*@G,e G0 n>FkZ ΍g^8G<8>GGGl6 P?J)q02r*d`y9Zkmket>GDaiEz;+\Poj>8d2_E]WAHVP|8~I`!Ԟ'4<(Кw} W GaA9ʰlR%Sp#F51 ,E/:T2) Ȅ=G@C ;g7o}hkO=bd4KhB ,#|k}{!X !P'D6i'" Ȋ@]v7fmVH+|-zz}.Vgw9c'X޻ob}>!Pʁ cQp޳ILIB`RwU.UL J$ac,GHb~| b=ƒD% V$dwe$7swo{ -|W?4^p1z3elK:ku]ͻoݼ=s#EyC%)(aN!,^ a4a:)QdVVNfCq#ūi ؐ} NΑVXǘ #d8Y%f;ZD(9 T֒J+J(0QQn J0"(0P mx:Z3gp-<26%gbbơ ?׶{ o|KX,loXeMjϸDyeD7bCJ1#4ѷA4=YQ}$XK9EE % oiΝ.`b,HA <_Y}@f*޿\1Ar"R:Tu6U>tr3)ǭw]_V8\zu ӟ4>Ϫ۷^Mw}~ݻղC C˾+ ViaG.,GB Q2Cd0lxwJ"7KRpQƣ2 '|Ųtxۦ&R%( 6% J#745B7<`ur4Bs5H%}8uW w~EEi+8V$4YSYO;)}ZAó}xs1.1rRFv9%1PFOB5< F*zaʟ$CH,QW(=V"r&X-9KqǡDYQ+߾+MVfMOԺq9|%^tt4mĜ2B_l92ъMu@/@[Ad1 yl b1kWu-Ӄ <J *N*ӚJօ"b1pk*@96x |kF# @Q\#2ȲUY": F13RJx-) 4(<0~wo<8łàkHL>q|GǵGS %#H:zQnqHHt6DVY0IXBo6H DAhQY(RȴUz ^1G,WhаDP>w3G#FDE&܊+Mll“[r @AE J,)!ZvL6+h%LXs!NQ)LtnFj^5/G?;?7ڏwvvrW1+h'(}- #?Na4fJw)XiO4Rx!w{@Ch so(sP`@qR p£H<ŋ0E<{@ <$2Q[E#rR`40gLr&itb0+&c`Rf+ZP`X{ <) "|1* \9}}KPи!+G06CCMqhٝ# n tM>)<] NE9@1aPdFBtP))\piXilW_4iְIZ[(k[$LEh=8A"TQ 2XLWqJVݠp@pЉA)/š7flf\[o}|@AƤx;A2B*G.jUWVuYΧ}Y{ݯ|K;r'UUͿ2!环11bA+fy/bD(0- Ք,}pc8??G9\|D4մfx"E"D1` %SkY4 #K\/}e{h(!3 ̢(JX;a6a]jR9irDM4fc&igrx}RȠVLM$JKE~bڨF3/&n.vpU3(y/j5|O6Ezͷ' k" 6` <8laLBeiàat9!2k,_̮[܊"u6 "LfrD0Gd-5Qix0˳;{F q1k8["֑eɷnwC.o3ٯxOBf[f6mZEYתYv2zflS0Wx睷d̤JcL_u{րiX|EUUǜb*+CVa:ƃxrrL! bj .A^(h~iզɃS˸{uSc>go~~c^57viG(F'2R0ު6Fشe430@;CtXk GfP7=1 \r|&vƠ(%s VNDciN[i-Qi/G,n|pyޚGF #`W,҄Ejlz||2{キ>$˲:˺ ;>:?.J4Dc v%2E2KA\r-z灶{tzM lc,J"gƚ1 sq7ݢ3b3ĨQuzU|۝˘gКwWx[7q> r`BzOO.w~H#ęȫ8E f[ěsңc:Eb;&tjRЈe9n#|G14jl0R K TIXFiKQHiC' wP oޖ'D7l`"C`I:,1e7[/i$ɋ"l>"A*##JkbߟcᅬG_FwpzrbU_m#Q] cÔBp<2BJR.hToMhp0ϚDYH嗏_<p}w(,s̏Qh\O"6WM&U@u-^{Q6eA1L-vmF<8<1rؐhʼn@'\lU𼧙MDZN" ӆ6b|sT =| yL&9f]w;t}K0xO\-v{oz4`>b2.`Fz(t46QKm \(⡫b\W~}}gpUhx(j{px1QEAeǭ,YxM2^|Ӹ2$?1s^$dQ~_{__c{()7qќ3/|}[bgQ K)۽UU5J)-Č2lU٦XCd{mUT@z]~]?:Ca^E߻E۶scEt(Aڋ&FJ{LMFYZ\|Un,Jf9<wniPTR_iAڕ02 Wo6Z`, \1>X*+t]Ɲ;?c-vg8<ѥ}.׿e|__k}=!|߆ø{ To&J A脁-2*wd2>tڮBWuC(dP޾tmdK}{{;Ii(oEPve[Q,Dh' Iq`WW4qi*VdAI!!9R/FR亾Wɿħ~;{/o|Op7Om۹ > i|MpRe9hZt1%b<}N.Xi݇?h>o~ؚP!7dPfuUOnݾkJ-O*|^R2ܣ7߄E#1 T ̑=U1U 7A[Q86dQ5'k1tvv?]Ý_֛8;_aw>ۋw \WkKl{>8::C?Tr[d'Q4D@+q@iVĭK.ÏhYoj]V`Lf]kF~(TI*9&a1e&  IldǖְܝmRA\l ڌ;sk*K*љՅ19: %>0Z"4t&VhWKkxŏPU/wXR{12k5B@# !,I$9vmYf$Kܯ#1.ư]sO>?8r儲l!jr?|^{MCht.qgehjֵnr֝|&^m][sI]X(P|;TF怆 cT0lvQAtS#_kD(uII؇o>@9*d3J:{ կ~= xoo}{;BXlaJ쫘љS #}`'fpmLSd@wKv 8 G Y1%b"BH0Vb =CDmS.`"* J7`:I~L9lT4(__ /p&; k>0ɘ(ӡ8AfjuG~_7/~_o.{Stmk~\\`wmvkym!!S4"Er!F]dg8O}I_׫l>JRJ1mFgq{]uVmg(jm(4į' lW8;;hCTuU9^+-* 06ZF@BD* = ,Lb^YYz >zǟG'q^cfL-}Svul7皦wmnus_f&ƾGmϞ8RrBQ(geg"]ՈTq3'" m)Ϭ0xJ0$^ 89=Cz]:MӣZ:@ޙs 4Ok#уZQɃH 4ՄF ߟ~ }} b X4I"fâwWEՇC=UD={ePJ hL(QǤې͐%>tY(x+SL{-flir0J LNB 0@@t=ܿy5z#gٮXW0{/N8{, KDP$dkaIâc5 N#}2RQc2R! d"aSt6ݻ1*FMUu=BZx^F1WEh"ɋieyVj!:uzff }S;6smmD߯|Cv*kΟr7umyP8lRrzH[dʠ3GZRMG uI-$dH` '!j;*PhoA(o2}]`2E,`L3,gK*3 3dΗ7"lGͦq$yժF]W-{<>6 +֙O[2n,c{[T->W0ك6Hle|B 'Xܻ#O?wx v'Ef1KN'B*5"B59KVda#xy9 q0W.H"!#)|H5X:JEJ;e}/+Sc@dƲT6-zjB]5 B#΂6TeFn>6;;g>O|ʬs{zt4GW-Ki7o7nP4?N>}[j':2KG!fnrT'w) V) )Ŵ[R,*)a%lCW?FYM&3+I#Y,%<Ħk`$89(H9ڶcaEYߜuxkxCO㽷A f 3񞞿+zz6<-_?Qg [B%RSdy1BG}`]]im7Jm$KOJ L̯5ma]5B;W0LQoheO0OgڬWXc2ŭw~lXErEۋ{pbrB[u JDcBPlAEd*q ! JR!ɁK ^!QP_ha&y]zBԉB:RT]:7+u+39ȅ I6u&w2ɤtfӉ&t6]OFc"+o/=mSBz!)(hQxQ JŨl娟N&TYh%u F͞wzr<TDt蝓 Lla"/D?nؙv38eEּge;pxpo{r/{PƠ, uR2X d9TQJK 7kWqh3|巾ΡtuQum|Y 6C^T5x?[;7q9O+,;, {ʫ(Gz!{?zbC(s< ϼA{xu .xMfR# #&$ҋ$Z[œ DͲZV9`ِ9 NdP{ ¬Tbڑ<)ʁ &gSM\=3(*c^0@ !# _ji3-bt 3hxh,)FVg~6tSf噂:?vC|%{RJugggcw}J̴Mj<;Vӓ\+c [+ap`#%9fL ?@Yp-4Mx" FWtZ!&kQ`Hya n^y1?ʗZc6Ci3Prb7Xe4~_ċ|>ÿ\@ٜӓɣ蛕8(Ef36G;oC9R4=M۷Ope]Uم[E#3dEƳ9tf5 ﶖW#m4Y8|w~u,{쇘( <pL5Zm'DTIJ\Դ%G\%KzZ 1'u[kzdW5(lm\u=\߱&pA>H4Y擬ͷY&s}|=Ǻn$Y!r,6Jv-:;[itZE~}Sg_ٝ>*rZL/ '+^cɣ=r!b27oz^o&?)F4)RLM)i0pĭ10t:bs<89E^NG97.0`#Myh=pԅj{;FyW+ujt2f̞Gl1p9/5ûF:6)17⡫Wgq3[o|MMG<.P@n}7| h8^op1Ƴd*dF#[~px/1G)n$Z,,޹SmV(PLwQ#P\[^ omWb逾F_lCt+5LV)qV֟|ɁZ1IR=hL.<0&do ޱ@-zxײN"90ӈ%id2AEyxŗ! yf QU֫5QEQtRE"X;/7?O,v>1YUsZ߽_V^{MAQcHQpqlrfSݻ_g2Dٚ>Bbm$S,ߞI߿ c,ƓEI+H:Ej+F H(%<;kT&m r<(ſ'η{~|g0%vG{Șw'xC|#:RiJ3~h;@EdC) zk,{ )n65sho-~_]Ƴ`X;"+[xg1 JYHЉzl.R;t3@$OM+gY5,إ^)E@.BW #Bڧa2`4QW}HkHD)m5g{t]V ED CT_~S<}#LGO˺+KKL ?@Sm6 }=dZiIw9J{sv`EFmUbVrxۨ)nlb2gI4D&ʰ/J 45(ׁ|[%آ2sd[ }Tj\:<%ņ9<8ݠ:Ik7ljD6/$C \+<9'+ Shy1,|pݛ~yMo|Xט'kpu`\2#W]x2\B`T_ hzMcO>71dcw>X=8FAHCQ mCwm<v=L0"Ģ󮟁6w` xKxwIVd^n =!rq#eU5o a-ic!i$G (M~L:ƈ@"t}mw\d(Yð("}A@6b(%@ `Zo0w|-c) I,w\!( 0d\}71yY.f9bشh(E.+!Qf#VkĎ{ϱQ,ɒ)I=춾ma3 c3GfQΐ#Nr;(1uFeX+ckhkWŏ޽d[h@9=凱~pU_>ŭ'IWЭϰYO⫟=Lf;l-/jVl(/JȤY#]!,!hnLB8 bD*l 412b<%;5ٚ =BUB˾:}"Be|l0n)fy yV8@wbpL?4ͩk8l|q?v(G.D=:s6Ck(kb=M|cݴ{"45ہ(^L1uozt9E]Gg?vmjkr 6H,1%ܲbn5z|Oc2izEks.Бy!JHFwŒt,sU@-;gp|6 k2dv45S]콻THJAE覴#J1a<τQc@< ֝hMb:gܸ6ErrOBM?PfLgEnv{7߆ք~9xQla{Jd-!=ލkee`3"2ftt eQ{(;?wл}@E{QO?K.(HX.| y.aw >"+('G B7| J- @SZS71!4 `W9(}M )m(%g'O|Hdcl$%EmSrIZR|eaEYbT#'d,R,Uwg˦;yh>_|Σ_;]&kJwyg J{&۪(wbZ#D&R:2称J I$|j*8cww2Ghޗ,!#0GɁ ^6p@ɳ <&Y9lZcToi:\|y!/2#(oPȤkok1eEny\}vw V߇`oĞqGcV{X zj4lyw轁9"֧TX^B`)*:6"ĝ~_!IaKHQxOD4Ĕ;(T`-ISO%GamҰozk3W仮G ˁ(O&GȲJ+RJV:lkX[N{>=":ޟ71JWJYxx/g?vH|NS6:jo*}5Κ|)V: QbK#[/%=>zd:>޹hqYp%l7:8VJo[LMQTS ŝ6G9*{ E1ʚ*҅(b<̡s6èa2|ߐ, ^AE 2mQxY''0.>hϫD8c!n!Dt ϣ/> P9F.Y1׊g7ɑgaL&N@$JEdO'O}w?ҋwܹ1flSXqۜC~0ɽcܯ\tubWwQ.LFu\>)F2̬v$i0OcZ.ayVkN4x4N(kA =r 6;í{kɨ"ӓs(e9XQn@6P6/%2k@^9Bgmd" U+# $%0)N ]ףiZl6,kTUz|@vhAf(12[`s-4' oB$|P ]u{x_ADVX(?Hr;?(aϴ֛BŢsÐ{*W0Mb^7杛MU},1Faxe{!ird0Zcdc,YɸDgu"y9ŧ/ʍh'}JB7aJT,ɗm`1j@1ym{tC{v S&a)9Gb>?˗` ٜ%;>1,Ҡln̓׋mZp}ܽs}Kt7֛ ھrB[/ѷ[tZS~3Y.._{a:߅Ro]Ϝ̻dpKRO(Tl\[ M7?a(f!& $bf^[7;@!,ʢDQl.$I&QflF2Z1YZ^u|g>?. /| 䖏t~(u۶MUUɉ7Ƅ~G=@)೟ݍm ص|lzUf|gOv"?qч5G$Wt*DkT/~1w٤֮(҃g}͢ckb-VJkmۢw ; <8>rUcgw3aҭe A1)PPa}2˞;n0>箿p;m Tz\6W^y` J1mS]c_FӃjgg],ry6iygnz2Q23.@'(C7lV=>NOT|:F"J('-=QH ,c fihǠBD>+0qp>"D"D3"/ p%ހjhcCEcDMڠ6Cd'9o2 @F$> Jwjd[9H*[fz?`)}_ţ| w8ShC2/Ł 'Ԁ^\S+$x{ah44߈_@ߋ\H~Bg#ٺ?'@QRTdEFaoofZmmcέC߯} ˝駟zOϲn}eIڶ?'*|Fgףyg)kѵ5B_̛|c5E(t_}Pٕ4udg=h]=jÜ]  rLYuq)},ˌ!@gE6!!d&C%:JB+dy4dAûlk:4Q`^ 7c^65z-9_Zڒ^X/-$V|tZ*9R$v6W\wH析AU,qz}LiYx#:Ţ}Hn!ܓt`x7QCױڲ`W޴2T joJ>{Ai"4L"s?+W^=܉Y~,nZ7'NBc}4}TĪs}=O([oN*4:1,U#ؠ#F`ŃTM<-%WưzZ1KJeU" X͞p*Fh2 c`0; \8`pբ|, Z,+4u$&xK|{)!-<^ VLYަ!]7%5_quL6;(+{Cج`:[t&>/ Ew/B"i |>AA|<+XkQ/Ėe՚`dfi8<< ;=oc&4JΕgWzG??:8/l e>eak\?7W~,njxWOP4A.wU^Nn)/U$tX %JD[w KtRl 7DR-RꐌnH[N >TuvXjk1MֻKZd Ȑ쫐t%`bZwEK ! ]6 *l!?Ӳ!3>{Urz2~јNj{9i6lgp:bl^1 n嶧BQF'qh{5Άn`kH(HD8N'㰻3;{iҪrίkcgŨ8yϝ~٧Nbz[V>UuUEifMWՃ?'.J)k~tI}]ko\GEcY>ezJDJHD4>xl#;=ކfDVXka IH Ӹ+SmͰndŞ;EʭE9<EGeяIUѺ>6 | k]לO'|ǟ0Nݰ(rhSiN *݇Ex71iiB 2뜷 uIdxuȣ@Q_,An>Q)t]Ρ޴X.7]=f1NέKֹʫn5Mw]_@x8Ğ?ꫯg>t]0S'nuӮδ><0ҶE SzNZzm $K7=bP!%2[v2i<Ų,Be^)+[ q[Y-|:.teMߘWjf눺uNW?GQ_x R zGyo:EY }[zo˲*I |e޹b^|VWcx=+t}ĺ1aă0i9q8jk06)QFmt*Gcveޑs*LK%;!6i+sz.R8>R4 mLD~ Z *Թ譗JgÓ W$'5PY,"ẽ@~C^mz- 2?g|.\}W&7W󆲬 @of7E7MoԐ?xU|qve7yy7]W׮rz6˝2:kS I$揜{躈ꘞ ko9z]v IQo%-mcHo1LI`VuXm,Wcg,Z҈xם眗h%vY&7fL }4pk  )-6A` >Q%Y2YcC1P7!T%/ Kڂp @<L:ڑd=|D^C4[nkşCb04T6Fy6 d>H1ù(x:=,em:_4$oCW[/0=~'Upm@t.jjֵkj2=hҟbԊpB9[gp]Mbw;;N'8==fSm=Is?|9ۍ < r "mpZEcxK\X/.7vSpe0Kށ2v$N23$DT>r#mn9 )f$izDvP/$isۂ5veA@ `p&"d۞yA <Z!.kd^gq2G(RTy,,y,j]z~e>VM٩ٙͳTK**UetސvQ}۶7n/g^?賟}px-Zg̪MEb1k{U ZAV*N3+9v}"x,1Lp~vmZ/1A+-5^* P 4m\um9(2cL']/|qjK`]͘bK1~u Xk36;emmt\4%MtY⹕b R)+ׁD?-U"mGx|0aKF Z'^IAG˶=l`m#Cz{ !lX$zг&Vq4*bȭZ9=E[ oyQdgU&ϬYkVV14J1.\ubgg+!Oh19,;Dͦv]_'xi(PHQyɥXl ÕvQ0_ܽ{vƻ 孏YJgsg N)s>I+Ӭ iI ljz##l6fb1AP5jScд x4kzAK[U\'ɭ-I 7F(3<10?2J RXT0H^RTFӬ}u8Pu?X P +1u9V5ƽ{QNiPW ڶA>r! N/Qbu"$lR?1 ޒV3!'9&Kjw(V ~ۘAml#^L\ ޖFˀcG]@=dNB^QɈ>aDSJؔpk>Fh:9h p BYM7&7I%Z„[" Sf@=Dlv)AHw[]6xsr@)VV5 1&ֈSV\fLb1 fQ6t6[?Y1hj3)A[b ZRm>Lu''<IP Dݺ5\zzz߻;w}aLjmi°m~^2G3(rkhV#*֣3x. &@sS̬X78Tw]ch wquǸVEW2+踲FMY5:[.:D^; 0g'Z N:G_ducWݻo㛶DJ"x(%*1 gV٪ˆA"ؓ=Ā/+Wq=a0b ԐA@ŕ9"Kye=z0v#e楈- [TX!vBI8Pv%^>mNmiLK䊼:e`1X6~hm ;B"]5FE,3H!*tLkA1hڊTYֆ,f7!Rw{oBHKk* sFcm46&ϫ,RI7Ml4ꭽ⦅ݷdz'ׯ_^}O5"Rfjٕk==?o|8ߏeypRR0g}sDS7 ?"ϫzu`?.xQ)uaOq%-& /a4^ăwn@1WI~Ka2vM$ÃDM']S7s(`?`rRjqtZiٽk"{Ѷ-,C”d_̳yfHkMxBXTU އoR{_u]" KV]ZV5K鵎fc566|Kn7Dcu-97~;@:O{ źޜד~3$ p!ig~!>Z:aqsA\`xM`΂?cfvxCt(F}zjxWq~&6 Z["dlqVW i$}6107bO{}x.CH?g+%qaHnOAA3[ ;bTH\eYP ZlVH\,j,i7@{7}VDa5΍YZTƬ2*n-ѨpO_^Qxh7cմPM߬H^,v歛ƈ ".ðy_ QO$QPr[ɝH7Tz Ho,1 P ;x+&899'>"|7C<XWq() 0̺rĭr`%uISdZ 3:Q2AqlCT>^h,I|4c((SQ>-λVh"GE^ SsHP'Lf6jo!^k)ҭvm(bbXj,άQ祉g&\%4| hZ9f~:=srۿFJ}^ٿ~ {{' ;NmWj25x\:Ŝê.sH{xJbi﷊"$b HIJL|b xI=Nyl+`jc@:,2+͖ote¢/lnӰX_<rY??JH!7c壦骺z;/N<ۄƷV:u }L.7|hpI(HbzAF/ZoX֘&|u =t'kx۷%ܺuIn;=c[vQDE-E܃6JvfI`kم츔y^q^dc.B%qdZo`lY&5 Y] Dyab1w>䌎i[{W6 X[ץ,|۸6톌^кv]絩XǷ=~*`K ~4 >L^ou5ݻTvow|UMUȷ|R)-=' η~o.F]p1F =x<<$% vF"ݺ|oC@| >lD12d MD|<'CM v" `MВ& MޑQ)cAk#!e1TCc6&V1ҺԪfe VZ*ϰZQmL0u1 {OI;a|mܶ/n?S)ko *ǫ΢?&߬!()FE<1PP5x k 1Ho[gȖKBу(b1x'ƃ_/1|׾|_xYj1;N(Z^fHߞ$/ogͬWC\Mul5h6=(\?_|oVx?O|s1^Vu Sgޢ;7'Y*e4%pit[<=֋`m uT6'^ć?_U|?1-=P_Ѷ }a D >BlƢpgd9Jn-![';5dO1*H.C\(&X$W@ /{?eH=-RL}1ľڎ5yW5#p  r}$ӷ3*V=cq4IYg*fǩĵ`a\}jjS#T٬Y,]fӊ=[Zf?OIPw* O<|ʕK+/'>  hGWoc41F iYJcI8pIʷ D++GMD> JDSZ AXDzY RT-'(M>FD@ z +5Z5D*Qj jbh6Vkmi|ЏῳI~jd W3msRGTټ>88f[!QMT;Dͻq~SDsӌjCf%|ux!| D>$ ƭP&=8ܕġ ;&BĔ7*:IeW7@:$Q.2kBKH:M13)*PQ)5Hu.4V*]F+6.,ZʳQo de՞\;.m ٦s g?OSU8A}3f']1ʋN?ɧ_淞?ws#"H@MF.*1؍sLz̓HA()CE@֨R@=TC=j+VDa--3cVEfVeX*Ҧ u,tm5Lsuex"=qDUn_?.A@=:i"+kk7l6iv?x@rR!Hap -ުؒ&O+:ŪACm{ k"!n~)򋆕a ^KN\l  ߊ8HzPR28 >JQ$D2Q6h.]f. "7+z[1JWʪQEVur^?lz_v,_?zKD3?vq@͒]Ǖޙ{VUA  FP! {ae͐E-ڢ(I( UdA9(MP(E'srk7n7WŃzソu^p*ȹ ޹iMyjep(tHT1aY=H4=W_e.ڋbW4tK!oVϟ8\-ܽīœ_*g 5REd.6&\!ȋG(iE F)"%ṋ̔/3c VpÊ0l~rk#~j=< 6 3O}mm6h<ǿB2 yqD~~ 98]vt .=w{>)aV!F;&滻 /;,rۘUx(,0sGE>j4cD!B^9OPU-D = e6\2i{nPIљ;&+coVS4yuw^xy*u xOּ-0,}2vVn?wGeBxH#0KʢcL6ʠ= I,| dn⤐*+ȓILPA)!Aی艱İ9%ֳ49YK{<cn~ϭ"6< YdUql7`q?rt| 2Ͽ%\P]hF\Ey+QaXz<eb1?xo0x*x28 ,x5E sz#)z&߉JG@ 5k6f[Xsfq1ym`j9ݦСdy߱y]}X~?yj{ܑ!rogoŽf휷"<28+0Dww8Ky#`584@D'sun>ē18b~l<:.5 K6-T) :L*4@e`BPd!F=5p J"%mfʏ1{ L;\ 6 6p_zݻoW%o擳^OH9t}ggυd.-,OK[o23˟EEΊ; 8: \AS{eܔ{D O,FUUQhAMWT a-@mΕ!sYm:8#и;"-5O 0˃OO msH?&n?)̚ =(,sZ"g\hPQͧY_w9*L颬Bbq`bܻnD1 2Go&/s 2 0!c*y!x՞”^ML*mZCRgڲ4PAmnb:WV_f^o_Y m"NF}O=EfkvSyd[˄Ibf&%g;E6+1z:,C?|hX z/<a}9[0LÄ/Kc Dfb L @q ВgiF-6yjHMFs oK4f,jjswCSpU5Hֿgo^_1IڃO?%fE U]2Yk-ك",O`r& &נE2l@ƆCdƔx7[y+b(CFPmPRm *dt635tJo3`k24xq0~0KD N5? ҍL|e=ج.WR{q̳-io71ۆ9<wea6[G\@,JJDeJ0N`t`NF+e3=+rʘ$˕Yo5ˆ`ݖ'w:e_yz. loE?>~kM?}Q02f1w~FL 5(03lEl=hb/LxQOt]p%07 R@ bс:BBzt j2Fܜ,x&2QUhMbl7~o ɌǾ|"E,%/J{|úٴxY ~_Nn/o<^EQY+=B|?k\2,1ހX=e7`,:% ^-/ / >+^&" a@Pj ԩj`VܬYfmY&7Zެ͐#Jju˝]39ẉGW7p((m_~ѫ5])_2;/tLKVlS{KA/y8?p̖"&3w!9*HՋ* %04(de*UTP[Am p&rY^LYEk;b7Ņq(x* qu'+??-?;<@PO3kf C"Y i^WHђyHg hmXZd kL&tO?"Hmt.A3`jb_U oD'O u#‘іA:\2EqQ*9=9;ͪ ux0^N}ݹsG&?gVk[b7M_觯w0L׽ !n cbUUvx1 yiq^jP/B_8JXzUmܪJ BIEV9-6CPgzu~k3L5tO|U< @AçY f>x׏^Wo٫h͆CݎB 81 p1ؘ9ӆ`ip4JF!,y7nR-tO|<6 ݇ʶb7n_tx͗ˏ~vPDD*mg9gP }"(5Fy P v^q|+4*5ߑ(X1 hjq4Z[) WErmLɐlE7So0%0?XIZE9=ݙl$W3ϧorԫ/<|_&\a2lFa2 %y8z &[M;1[,G UBEX, 2`.T񵆺5\6+Tʖyu[kʲF<Lo鉟yc w\d|oo׼KD8dCks@ps yŋÞYM9?D+aR'b&!TЃhMIDAT uBFݮ ]ey.QeT(VY[kLGmp1zw3+/ ToN_{񺈿a^^.fndBJlp4* >hn(Jl%w^I 9Q3*0R**TrgnWٲAVԤ qgYGj0N+D$~egVYBD^7~GҘVCU=i#Dǃ!C"cbtv묻! %u l()Ԩ2 @Bj=h2ʣBG%JC$BkVE-rS-nڒ+CT)k6ud{x,`ڈ6s<ҽwU}o|J-tVUOcMk.V9rbdCf!#"Ix?BAN Rej %GԏL<: ^wX2cR\Ey"YEYӀ\o)!ٰwL;7nw*!g&- .V.{׍5}ɣo}d{L3 L&̋mPHWNP Q!A1a1#~ B'":jh<7e2[Mn8hȆ1=6']D[oY 90SSхO޸mc˺0z̈́ C'b:[U4TC,EVvʶS嶨fzZ;;N+)^7+-~HxǪݻKoóݻ8񊅽+<+룛zD6\ppq^U3ʪ˜jSز(LiȔ6X*Q4YcWF V+vq187?j ];ڂ{LG ^>+?|_}ŵ%W8:I'1~}I>G٩Kq>>JO*W_}#NᢺK>}lQUwov#-DED"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$:/B IENDB`OSCAR-code-v1.3.1/oscar/help/images/prs1_960.png000066400000000000000000001325111417327530600207710ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGD pHYscIDATxwmu>:a$%R"TJ#Q6DYf-{b%=*M[QUI*i%K.0cҘ4%&HH w;|}m~ l{9w~vkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkZ>ڭ7?}̾tNnR}w^^T!G2>kvz`oV׋V'b=ܬZt1x>?䯾~NoOJ֞?\?-X6\go\X,fX }H)a_7)r=䓺(ޯ6?8ϞX,]gk^謅sI( ,G#?_}F_X;[V'|rPU1;֋b|r!]k` .!X_ciadAB#o~Too>{ë];[ Kj^n_/X.O,/.7/gj>_Xg=@Bv' "tExڟjN֛n=x֫'.//^^,rA]X|D $7wЇ7? L(_^;];[o{Ukۻlbz|~L,5¹ |Y w)UmaUŧFtno5.Kj5r=b9_=\.}y9`v1\Xq:E$ڴquׄ M;yUsO?ywv `^%oVCer|~Y5[t_D=e䞤r;f d|o}>#LpzY>WoNN]]O>>:::\./߹vy1?\b60w Bh]O{KJ|f𳸞mD{C-Owq?_7K'[',1\ߣZf=qy]^./rjPѣ]DB5Ȭ< sɁJ@f"/[W!g!DN֣@v!)7|;kַCcX^'f{z2/zݠ5^+dn˭B=εVRD Ç[= x|Nps_?Y;[t={RnڬW[\WO,w`6_iZC-|̽_êx w_5_F;+;ûb /}q>!3ms/oUح>'xczrrlx|1պFZ<~"x "~ۗokcH>3S7 [л3= m|I v<^\͗\^.^:c`mD۶aEJu=DO9t^.k[=V*G%{晧>c@FLJqw_l޹^Ol޷\nX.{F7-:c$62ꄄy$/!D‹BN)86A%=Dn_ =W2o"P ]A ']| H@#${2.tr$U"|P&B?)7{?Ճ,׋ӷWd<[#GZ|G?QZ@{1[_ϞҿKߛ*\.mMղ\Mkτ-1 u]9ǂ_upYMf?(|C6O^ 9'W } bKc/- F _qkv@|I裏x]/}潟x}ggӳ\G|}'~'&/߶Z\__\\⭀7mּ<[7z}sjպu>X4!w$A$Mщ->ϸd=r+^HHœo @_)MJ/ozV]ǃ@ z|O _ {5[=O?j3hޱ^^w>o=?8sz^^`ڠm P( G#??H|OO֭m.OsqyӇOO/n]fX;y{ݟAܼ1?gc;Xoݶ&2s+bc2v7^K-|R+r+^< 9TVuz=N!"WXo׿{Qf7[>[˛wNN'͗X[XkὃVt9f@9@;և~gm{ˇ⽛=.N;=9yrr6sr&./WkPH{( | /gwm6u@+|jaϙugJ$+xYSO$\9UX/W2{]d#ߣ^[J /$U<_P"G˽fVͻ6[_x;'stzDα\mvh"ԼlmJL(zXepA* )9I넔.Y{6?P[9Jo==2 ]vm͗=^d/>Cчx_8?_ݮ~mֿ ?r6wlVシ]go=;ؿs8;ry`4^.ށg_x5O̖HM_a0;}?#PfOs֭}rz56u +EkL  ]DƝ {RhEkwB-fmj;B:6& g qe?PAqyhQ^ {XﺻnDŽOT;?ěp})lvruh^b6{r~󋋇OOOoܺu:8=aڠqIM]]ru;8Y^srey9{hYYdoyfҷopXAo/!ḿ{& :x_Bq ؚ 9r{~h 3>پ;.@g0gtܖ`;{d^)'|-O zxۀGvM?=o Jt^|ӓ{ϧnl ^Pr[(EэP [cd+aL @KSX>siz H ]Vp0~&l7^9W*Y4 z{ ӵB@e( BE[m J\'e 'E ?=^5}]~OmEes )Ǩݸ}'}+ůf'?񳿌vMt|YhKJy=$ md!†T%rX2_]tkcs;Yf!B@I wD }!MdDbz0Ẉ3\۬}Vo]n1'X!@O.^aIqL*yw|gƭ͗|xWgOKXKZ 8 Y2!I^JSP؟N`dl5x7\Y˞B_\*Ekc"3Ed}KM"siC2o۶OBϠWIdnjHw m|!ǒ}{X3zh2I'f0x1Cwx/|_ٴ!BRʲuJi8J 5Y烖 *u񍳘-V >Q/s[_!&zw6!躭pޓ̔H4mG*Vzœf(!D# D8'/z!@^^PH)x  zP;,DBxҳmJ: wH9tF9REYF˹{Y[/bI!$+ /wr=Y pNVGd}d[$/Z{Ȭ(D?lL Ee^zzoKdp32xD樼kBKEJjG@|0 N Ûd}W(z3.J Zˬ 6 ˅YI]/oaf:ۀ+fpyk-upzS׃BHHᝅwWZyY՗_/FmQ|. .m{J= tQRɋ{Yh@Q~C.|p>TǾy}wyT'ba kӇ!8,кs&Rbmq) kBy9O|RfJ׏?ୃYX`Lu5pZ:8cr^oܑU4Yg1P')7⽽,/Ǹg/hE%dZ'kJwuB(=NOsW%< 1yw0M_  8BրPpbM/pol´k- E tQ: /Kl% "o%ǺfǸ2k @KP-sc*2 yC Jgs/wa yT j4pΑ)v@a4*08Xgb[GP.ﶮ! Q '=C4L&d|v/%{"vOO'J/o{v}]ީ^|^z6NN1ж"Z-!P J_ȋKBFU f"{J!еJmjL~j;oLǔx>ڼ)IhJ@%8OJBiHEp0%zJ(_',a:E9'h@F:5|p}GNwzۇݚqe&Q!]m)+eˌ]Ќhz?vg^t߻ZϿo>۷oU/t ''g\;XV,r>?Fo4& l£mHY$RE$_g^ 0Y H7* s>s]>Ê_EW+ J9v[<#S)dQ)PHQ{'<D0{Ia.DxAo#*)H)?3xE"\'bC4܌lrKl@d7y<0(%fˆ _yA_  `^#3JGTX,pNH2B@KZ+e!$5y֤bBiXTMqrq:Aj <4W$VA)am\Xqh_ >gDvp !B'ANIA V2*#T HHHM8xa5x(*B8xf^NG++'KCBC>۷/]n& j;n;ڴ.PsE1M{uӢmIӵ(y㉗sC˂ 8cX)=("SsPJd MSG\}Cpq V}z TAH) є鈖N`KdxB@h 8NWgƒރW^ \ ~W/Su^ ?%E+!!&R( k! )LӢit]jޠ,5g۱YZh\ ?r~⥗W.i~NSwѶQog=xvzr;}Y}1[:2N@tgAO<հ@G&\.Pՠ* vC0˸X @BIEy(;OS:nvSLge+-aCݴΣmYr֠ds˙TՀ%*ϪO!ѯМ \z*y)%)a)eR@H 4 Fƃ ҔU/gs%\X,Whvpg|~ywאG>q(/|/BqgX44X+)bc兮ץ=u]CG0^Wd8A8=/K27W1#ʏEVtW{>xvyypvv@g2vypTbV;frݽs\1uT . ٦,J %B* %0֠3ZC]gt>(Ȃ'E7\O"8vjD@uRZÅ2̊~& u]<ۨ9p׻9ף`qqD " "\i!4T0PjA5h~d'@vvRJR-P(AD5qXxZ1uΞie!W9a*% r?Gs1tZ Lg`cg+|5|o1Xo6h@ @UQѾB/A~+)Dx?SP~#ׯJ@cGfA2A#qS"E1@ULFgZ#,zN CP B#K=!B5[b `A׵0mmaL t!1J$k| T}@YhZiXR$|/#6в@U:[39~d ߷9ك N@@D_ɤ drӗv]8 '‡t e|{5-:9BT"2 8Y:p[$XEzuyBG `xk..W"ED.vk<*gb0UU*Kxg``:9323%[ Z7[p@IːϧTDz]cZb\٬|qDYiBC(C}k+D'yD05p6]2s"*H'=7޾΅MwWD1rEgE,y?I>>y-GXhkMۢmNYKz`R)9#O9_xEPTԛ56XcuK^i.rʵ qU-זK =o/ֽ__^>{w+_ 78/bSs1}OR_)mĮ˚n δYkm$3VʔΥ4e׳0_ K4P á&M^~)k:_Aw8Y'#@sP2vk.u55aBv=]?$|蹳ug`;$.X$- F!&t!΃B%C51Zjh Dޤ{CF5ik*j t0xPN nnx+|]ϒBueA'T޽uBc7 /pw1p>  `N9HEC2RJ9 6eL (H6Ry;2VVptD]K8 @FB)h;RVdss~zN^ZXG6,`+Y48Z,֤/[  BM-%PRo6u0,ޕ"/:Y'y7CKߑg#1 aex_XĿE../0MPm > im)iOդf!%0Z!|_8^uj)rn޼g |Э,`3vˤr7lF@ߏFptmGz"x ?$Tšq>e8=Coꤔ|:$/wBsha:K"TJѣJciK~ތ"LbEY'tg|}"]YwM(0Q%n޼?:$.4̅#ˠR-8(=x2FAVjKQU%Lx{2I~Oa9 tu;RJ]}lb`@P}3J96e mS'Qt h[&8G&cxDO;ptDs <{VRymߖPuzr c Nbo:卙.jIS!@эCHx8`HL #kr/>o~ՐR ȒW|W)RBBGTzlKp"`^|n^PZ#4jvF_rzH!#tV|5q)ױ~k>S-x~7]n kyYLPUyso%%&P7-֫mg8T5iYcIP6GYB]|pr?|pgđG(?GZIȆOXH TPWH c:cS!OQ~׽d)jQ ~lU_RA Z[/Xسܾe]!=Y4OvI.w- HRIrZJZ)I5^y~60b8>˧2Q y{_S8F<%Gwgᇖ0֢@KJj\G9Ct8:d СI :*O!C9qj;δxVa#{Z=۞p > ~sI0k0h,ж-fE,|J 5qc>"!URU= *eWe9gje$lu"B >@ŋ퍞kjx> $$j1&eGDp0$;|?7UɼMtZS򭵵™ID^RvCpu{,y0\)爀0_pzzB FeBA5DYh@I&B=%!6# q P@娘Ϙˤ9+dʼn-& ,N/P񝭻2֓R \\̰7d4‹2+vegƪ0QaFgl̋ sEo ) ʼ|"IeRP~G1.m 'sީ >/y.aGUrIIuf5}s.*9x^7]8l5pMq]V!lj͹5):<{ܺMзoa٠T7oc:#4W U#O~MۇRWygqW=r}ؓK)k+#[K+[wύFM b9GGZ@pO$##Ҧd+xG2 Gp0sJI ىt'wR#~"%A{DG 1^u["qlX;"6d<}js5Zs9 j-OT׳Y= jm9&Syҳ"w,H*IaҰ֡Hq0q )%]ј:hx#)yTQѦdpqӏcFskb !> `x7)x|>'8<:@Q]ˋ9cVj;BL 0xc46!ԇ9G]l[tJEy4tQ!dRRV8y[v(Ң9DA7":HI¦d7=zVX qhӎ;;6Ǿ|pJN R]{ a)$&aZ]K~V5B  *+CZfk @s TN!   RG† r5Ywq3.BSVq%2Ep;>P˭cy󽓀+ *=Y' _v4:ٵ@ lgO{+q@QhX(Ȕg rܗO&L w/3#r]EP<)=?+𷒳!J)ZÛp&ЙZ 8*EAaJAjRq:r,%8-h5'# "C }{\7QM0C۫) 6ڒt Y/J={'xnp>W1 |v`omW'X/:LѓLJDŽ`73Yf;y TAxc҈#?#0}C iPJQ` BZ1݂%  zC (L , G/ qs1ݘB3*t[^JO^ug_{Cc0a4n(ƭk:5G,>jvmb0@L0 eR!Ϯǧ̴muT #C/Ɏs A?ߦ]URyU+( b}x Ge{C US B=zkNAs8xV|(J$oӉ!N,•Ю@)J UAPiB fyX X,.VJR,,ML5< 0PhP`dvHmo ,`Gcmɸ>뻷J*Z(K$S<e返$VjPXC+1h y5C9¹.'qMHoV sٯ=ҳm rB8/a֛߹Yt Z-q1[h8;qTI7;vF 8h@e#R2&.4zOD) " ! P h dv"մ"θ$pRs0WF#@JBE"ǵ$8"۰OZ/a;CǼR.ncgǭk(=eQ`:BK@M>KeUZLRXxKY-ُ o襅EP䲷duչswGS&]s ޏFUSb]USⰭ? /@4i:Mhu,oG4;CQi,f?@侬`WS($ |YhG"ƂRx㣅ׂI!0D bc @` H ![>Yis0qCpjI-)6 TLr{n+Fٍ(@= pH !ѭs<)*L|fq؊:a|op5Llv b*˯_~ۑٻ </>T3+hkYA %t44ke|(bgQ9e13.S&=]r_g9b ;L>PJҀB,jpBh=s#w'>}@N!BϴjZp9{=%l@2?֍+rY[R|LE;܆exk2vh:% (Vd';AĢ'nj3מ_fͅ|3ߴȣ9$+DڎuIeq .B K(ġm?VhY+,x?!BƄHbX`M<˳ #\/FO\s-UPaO]Yo2V"yJ~ e^uO|?SBaI|V45F۶8>k;,5Ue3zm~҃9ZPыE$w?'%фXQ9JUR2YBGpBtɃ "դ">^?st cxд& dNJ6YHuf*L,i:ٽ xʌ)^)2E: ]d{"@9K=)$#?Ý;~uΡ̫Ys>rVr>q"NQa`̉d1hy+׻ 6OyඔZM0 {r;X~*t ȭm;"DtB\~<Bvwpȝ$Mb!~͏P# ,V"SE%3HwEY,!'棔0.չtS=PGߙЗ2$U]]^K-2+ /eӠuAU~<0~Ovm#v CV JAh(@?on:ٲy@oxlù'Yɨdρ ~`/{x{<1evjlz=:2=&oy|?8:#!`OҏAi~~OsR WTvsIDq:q1C_fg"6y+ICb)p?D"eVA* ׍扑,)z,MPC I<j߄0[k>Ϙ~#E9}:4u9֛m[,5vLP~UVqfY*vS 3B)5 ROF>*Yhz5=%4gk((9nfQ& D&!kraJv"'Rh65\(Us7Y롩$QJ'呣P}+smyeb PŖpJpf@0q#8{AC"yXw?rK28lY^;m7$҈9o"g=~H ,(^Ojyt] N8=;g6f"dps27B(-M @a3ebW9 5S> EQb4a2b=b4JwJ|weeω<BGw4YMs 휃ueU,YÔ,ƊGimԗ)LV5MrQ]% J\{BG!:u=MqzZ>S=zpoz.nJDPWDP){\^^ξU&`_|q0(>x<>NØf3QjAU`"':@j4]ᰄ2lIx 4Tg-XAq Di.I"TEx}߸7p|txA5zr^z_ /mGh U 4|$zB8LF2vкnPDy vuO/H( 4u D6?>ۨw8V#=;UxGm֢!:{'%%H!nH|@p-E^/-* ^/gU* 5>_>~H-|&`Rᆈ?P`Aw(2!#9աW2Zxs7n܃Gy|;w=z[[ߊ|?p?~<[#|7{Cu뚇S$,_q5-$o~@teyЊ_=XQp><}oFo\-^גǪ0owDHȀd%A&qBc{8?Fo(o)m9p NF?;_f ~,,Xb,;Цs3{qMpq8[lޕ(%䆿Ck {9ݐ|GEg<׭_ 1|k},lϫ\ۺte|PBYH|^x7r>; =1ͱ^{aXqot+D$ܤ'2:zh]{)==Ī,XXa^MTb㝋n5Zyj0DUhh)/| ur;gsv'u n3Bcد0qdN|(3<5  :'g&5н03CUUJ -"T{"ꦼ>-Abڶ h/E}zQ|;`[ۿXyA!cL Ky 4}SN<-ΰ'N./N?y鿦 ~Cc]kf9Y]``˄#qc CWH**9sΜ&֎VJJg=Fä!1ppp4P%tY˜_WK+89AT9% J{n:tg}6/bXa<:m0P%!^"؟0B;)jJ>G-Zt %i\Fc:1InQVy&Z3$ L $#5nd%d[$Of:< ׵|5 sMO!DFBBGyh(lwf =݇ o~bv~~}m`Uűc|rDӴ{o`XlTR ֻq><=GUTPk*&po`9GZsjx#$GBԀ(Gt9 ttHӋOlk'˷?rͦFuN!lf" -X:cbipL\'>b$B (>h!QhC[7R,s׿{ x&IDK4-,?kyl]E_8E|'!Fx2r_Vߣ0& w|i6f5.z^ky?jGtØtЪAa,UxmUI):<>Uvx4=F*- [!P0w23lVtI)GF MV]9_|EY`27X-Z{}Ȣ8!Z*|dzErjmwpL B p!s׭1֠,+~vt̞M}eKx,4@^H"o-cdr Ӗt>dgE6KS nVث^r #P,ceW./N>*f+y|SUUN'PJk 'qMH=϶ZRUng mAboJ8y*𸑦RE8?-iKeIt +[ Sk1&?/EQa\ 騴^ZՐI;t8הft'(1b4`'rc:X/9"JO2]˜A")4bZ7ukd M cb w э>ihEZ '(M11p۝8=8H \9$\A*8= 2Otgm:ڦvz`lS 唚 Q#̾'A4ѻ=,(O* p}WK0TЪnaC zXK(6 |J?+{ٶBjPa2`4A*i^?ƶۧ^1@(L0MG NJfA`L?B.2+KM$=p&xBq^ |W}^l7"Jkz)c£TiܼT4L4 %qɇ}^"N]ԟA9a]rS"Y㐬H1yWNl]>OW'Lm;~KdSJ#Lw}`F ~E75ݹ.f0d׵M f!D]fpk4n}&|?[ڶwļJc2c8n6[`: 5M;%yO\* Dk 6uCG†LXS=+flV_ t(JItmGkO6>ށ)v.?v ruxiZtMi8G+"(X!hvpX{$}/`  %tqsBde.y pZ.<%>Χ t@0Lv껚 {/ЇRtn85ڦb1r]=5Τ"y} bɹ>uy Ǔ wΦj]n LK%R'88`P@`X*J'b]So|ʻjR7N!z缕IK=.t N;j $_X$|ف/c.|xEAMO];Cw.|X/vmҪ*7x4 MC%yA]y`,Re f7CBb0{5v-%M^AqPBLh=Sy:X!wmF^c0b0(mŋb俨JiLg0 k:4Ma򍥙e䮽J*e3eŒ , 5F!4<Ꜭmٛ2!\zrv|}{>3|ytOK^8)"!!cwuܡ82E=,ap62NpZK~]4S֘Mh4?`XBJ|0UDCHrK46g۠mH9 h 6ua:>Z)'ho* R Z+tu^YdJ+҉~2.'e ޙǍ(kҜk>wo}Q, 74"; % AaP. ކl}C</%k\JOleP2z3UhЄYlb'?O05fSYֱ`'yҜZ `s>vކkkLT+9I _l6+{)R4 ֫{E$׺2PGxI C,Sb Ecl#IȐ~RH% ~ -U. ~@!7[{O>vv0ԵDY۟b2Z:Z8Ot-Q}78A) i?cѴ-!B;a:_{_1HJkZtm qC5*IJyAzjE'P(BYhv]kI1G]rezD=WeQVj@Z0(Hl_B^wU.#pͦA۶N 50_y M炷kΝDk0 24lj0'1h9hM+X(]b<£Y鳜]7/mסkZzfDl *55$ȴ`Zd2`8{ْbJ*MӀ<]bp-h?BsJkl4M֝[lyCI@0D~F;o# #={ZaryCBLAKV>˼Ers`yrʋ`у>#Rrhi2RUa80ݛ1tiܜ T??O"\?gj8]P)g34>hy|DQ S u:ص9@4֧cs,47 d%-hpX.quc`Fc,+uzZh1{;L r1x4 :*-ptxdb0yprgƭ *]Pz]P%!DҊTuEA(d?&@b8Ts l8qǤH );T1t1#Y9롓ϊ˂͌wYZ6raa8`y(H8]rݐ,  w|dBSCJqr`0P)kљXXrP[+(iup9{)HYж-ڦI$%1f826e%@ʇUP4Th.Fp֠;jn 5QV8[ f- GCE[gxya!!O}e8Ht mg`ÍC,VKr1O  Zy`XnJc4 ܑևzTB~{8y/ArJW߃tu{S%aCXӡi67+4{&8㠊YԐJa:`(<B8 !45e DVXW;M;-黡0 πgRZq/ G1'?->,BCX1;ƓGqxY+ާywr% wdA &^2tͦfFSP E5`0D @H5vĉKBQ5Bgkx O&~_l[0jT7=8uxrShS*.JiZC1$qcI"`3Ե `w=yX8=@U 1Nt-%&LcJxW565Y߮EmmQ-F)[5pxOL2t5xRsm\1ϱ1_4]$XKCӶhC LAȄKA& VR)Âk :GH>}@)Y(>Xg昃vί;vA":HYXFRʹ`JR(FQ r)kC}^s)jj8d: O` e(M~5ZSн:dtQffoLJӟ~_ Ou*0(  Zhm0p~xɸ#)yClBp}sRI|Sc!uwAuGPDuhބ}v-͆9t]t1 P Teɮ7wS!\[ߠimK? (t$t~ xD K!$Ǽ$99 BPEnnf|SDlCB%3o#*s@#ڇx?C@| G%B!,U pj\JJ4mi/4t?CE k!m{F  ,L'??77\/GWP(uŝFzc+L&#͡Jd<7iHIYu`S-)qeShh@tuAt({#4uŜkQp-+8nIn%( d*4RJV4XlMP7 MSpXV%;*=hB#u6X Żxڒ;# 6(ui\pxRN"}=s5C_[flY'1 hIFDq=qPGgg~*Eݖ5UC9l֘50`r.wɺ.།!o>Qdc]tSrp eBf*): !V{كkۖyB[T 4gLסLRP"`S:US*3,EAdшhR*fk:9bU,_>ڛO'j+XNMB{FMSZY!ÝD6I. qa7f84-P$O< *|8FSlL)$H#圉 v ps1XoT;[vJAqQM {ʤ$uA-h ~=P.hf:<0[|0:miNY?QDaD?n|Q;!`9yBֵN3#uhM˹y8`8?( \W9sT H-0zڣ*SǢ 0'p8DxXWx3̖(!0.ow˟W,opiVJCxL.{,Lc_()"z{xLJ@X)C/uh,5ðBQhwNpppbvyp1IUJ%8@iPQ&<16 Otp6sDQR2ѧCv2PЙ[,`RPl#ה=Iυ,գS F4+9ZzI(x/u-qm=x0|s0ҭh| Xl.xR{7dn=o1!jHc;ȠP<&7'l#/A"3x}* HY/x`๗pX#cy`Osu Ko3{i#''ghoy=y*$EJ鄇8'Ͱ6u08Rc`2p7j{\̖pm,.rs  Xga< hH .Ddm]^д:cb*B^1peȈ{%J8R@2pT<9ǣPCkIETJXk)2ʮO;Bі>K2#--qc$/ݺMLM H>ۂ-'pR;JY 0΢4IDATZR)T1Fp;QSXއR8:VKE%P{c>.#hZEYVz`n܋8-#:ٱoW mJX`8ѝ"^`:`n!Dzxykp@JGM.c,,!4 Uh,+L/>is ܼj@;֛#8>|\ *=3 mMݠn:] |e&:Exg0OKК,4P2(beÓEWjf/Hc>(ka- qBDjJ ţAP5*cͭ۶7{8Z >ubc*]AΡmiJDžJJKT ZGuL2u 2"*cHK.ۛLt tcZkV 8g0cM_:傍 . %8S7VOƠ%U\EE s9t5 sMɘ%$QU% '89=/J}R`_)'YbuS2QI 24-lg`΄*HpQܕ- #VZ?:"Q"d%1ٜLAY]NR &(9-=JS Qw>"&)@% X^)". MWnʄPԒ O< g c'Rk(Ƀ,+?K1MdP0c^nj :tlĝ ~P;J+B|/|S|O-wo0gN6{{>=5J%)gJc|16j;Bk dB6X۶0%QZx<Ƥen "kz5PYeu kDN/%tFqH!hRL|GkᜣdP@u EaRRS=L+;Qj_r ѥVԧ d M/BV@`^CxGUQ.>3}CB!Ak Mv]hT!*EhRR(xT/!}^ը5cM1P% jAPْsuA575yBΝf˕B_OR<[}sW+wo䓿'k=JM%“ m`n0N\^@eY`/oFYV #v*IM"%κjj]m! v J<x(b;O^p (=UՉaYBIj%r !4&No93i1U-nɞePΣm p,qB<  KYB8k+f{pDS"ߐ3]1n(P#M ,KaU 2 -hPa;8dB)CeCrW5 s?޴ЌR B( 0cc4 +.SjX8t"D!Q<[c^D#Ha(T{:7hay麞d{y 8# CС I+(xKg:1mtӾZ&^+ pq2VԥA(GO0Tۋy drPjE[x-ܾufwZ~^)TU}sW "w}ԫOh:=Σ**L4_v-u/ݚiZ DqzS.cd$ׄ"-iTm t mѶ.kh }/3Ń J C@JhnQhAY`PqI> lkr-sBϹ('% AAxVd< ~DqFQ9wI<2B BUrRQzudUQ/ƣ>hCdL Y|$˪toÃC`4 3+F_% B"kxB+hXapOg: ǛUUYm`ĥ"~Tq<[RӨ2Jita2Ю5e謅j )-mF2$!OJ4b'Z6NZeH~Hg`<}0eϬؖgCknih%ӠW5q@QEvж 8d*873Ja)wp-4@CPynI,g?Ԡg S<)Ӛ^ t VJ(c$-%:u%%Y|Q ‚;RBz.m;j k4uE2Ϟ /YXR^E5 gZIEJUFaM@vqUx\4JָTL |١팅 p3N xjTʽ1'mybM6֕Z8,p8VZit]pxt0IG64,4 U0?v.fsnPa)B) #1!q-Ԁ4 ~9 gX.˺^ROvs?)jכNfszsj2 \Ѷ 6ӽ!7 )ҭP EQJT,1,ʢ.hK(LZvM gKLӴ0ȫ&ƝtU! E):PH*0y.V0缥Buk9 4\MOM1Dύv .VH)VB OДӚXYboUY0&X1EYL`֚8D)f@ 7ށ@YiL&8:<>)bB(ѨB!@Ml;''8z5 BQGye蕮7pE+m{"aIӷXwiŚ궵VS?aŮ{w$NԳ]IR Ơ*Q +TU`h CUz3H8MbJj &,p ,vZ 5 cO648=q BQ(FY(ՊL'5c4& VJ899)h.9 %cCBJcoJ&c"!|H(݃Б`^'']fqZF#^7{M_/oS{B* e1=(Ѵ-%n޳BK4pX1TJ:M*ƣ!{H ꌻXJ9G^Su*hBQUK} 'cJY`ȳf=a7 ;CaɠRCR$݆B450~벤!0D]ט͗_Mֽqm!Co@Y ~{{{ v O!cd ۧ 6hU IddrZuy1<s7^}x}7 hq2 z1fPteoݍuecƝQ5tpE\8wd|bu*Jh0 $>iQT dEyVFԶ($;zhIͭɢFw&mCo7խBeTRi4 y(qGIEF٪yG."Iy7zT'< K~wg@H m]"Ha ۠y,rxl,_xY~8ŷ&;ĥ<[?gCRGD^h4"1j2r 2!F8qA>CxQ&bªHP%@VcPR0A)(kx&2o5F:Cd4Ƶ[(9xoH7NkН&032ZNe9%߾o=>Bx#B[ژ2곴oPwwp5,KD;v~o@lNz׫ §t8ulh|<=iTR8. &;j<=ǖ~d:UU#Ms1VɢK<:}3vF }NZL&eb#%!M*08P98Q{%V0$u@(@* pF妩Q'aL]W(JؗnȔC5&+w:eVS[)uw"nNxm3AƗG\,z)ТBeФiDȀ=Čzq0:7$0F7nQ7-(^e8LF'aA!cppטLX,&M8`GC.Lo4{l8E7`CL|eeƻ;yaIhk\m4Aob9}̖ dYUl><g EYeYok!TMme0 E!uGAx օ}Um)*1]uI'xۢ,JUQ$TuںU;Zf;`N}>f %EzK!țs_g pM*CnsnZ9U(#r*GC/M P0F6ua N1&1'MgLXEQora]o\~$<?~iA|!" 'deR4㞇@H4mPBYh,nk:yQ, u 8ۚ9}ےVvt*\-!g[o߅`[z&73s/ѝ$ben }qjnLp>[AE(2vwq$'t.=H顪ĽWAdȈA ZGIÔ#ݻ B@0D/ ;q2 !Q%z)+dE4KTUj:+jL2qU7,]n)9@:KILږZze]cȲ iK)obuGUM۩ꔑZ)7&ɖuwjĺ $ѕ(^JFf{ʮ,x5a}}͔J%Q.f̼V0yC c[4꒬!(FGJ01qb4h9^ϥwo^y5&q&6&+Eֶؓn0׋oS0hH9GGI0 0F$ahN90t"P9Z EYbLLdW"/Q5%3k A5m/a'톖k#ԩ69uDx:-)',z`d=EAV6v'wݬu7, q܇Iv-H":r>*{q|-4}=kp&6~OO~w4}h4EH.\؅+Nl7#Tv] yEh Ig]7he]brB)oH«ZmV䜣n&Q̢ͦF&YJBRR zRzM0"}J2_Rd$$+Jc-ifw;0ׅV( Xa !jj|BQHVy]/3]޵\DŽ3zyՕXbp DfA!#QdUQiaX E%2~]!/Tc|=zC5e&`m ^m,s7 _g!2@Ei gƬ{>0bƅV׼&MŽ}忕iG:zƖ5BJ)"E dC^lp51BA\{CFi*J =5snu7~駟?,mcWGm֨ y^! ܼy{c/H9i2ϑ8/jӕWbz&{N t|h[o@\~{\gāi o}'ټI \7 =vOo›6ƙ[D-3o THf2bCű~p>g5pʫ:}<F#%EqnhbdYVjJjiN͹5>o[~cfTs2X9)͸pt XaNmݝv|iγD>N_akmF~a[dSW5Utxdqp?ޟW LmO>dكnsl맰%8=t"P YԌw]Gx;{W&'˓?Ev52D/Z%׹hٱ 4yl鎍g:&]'f %(0!:;p䴁mWfV.eM`թifʹ$Đș cLgSuVx\܅ ޑWn|x}:GK`i[1@b8`6?g}SJ:m=)a&Fn_wl6B͢ W"ݢkj/}"&Q^pt&<酾unS<)o@n9Yc2a|>8n5Udht*+^IvppSNN3_S_^'_So` !8}ʝ=ٸ+ekx"twʳ|wvQi, Ƀ,]po_@A)J4+P - uA-("uoClL) n :L7.?SR2nԟktg&Py]XGϊFUoDA{i+߻h#tKhxG |њf[ <(l`gg!J!#ciiL/x+1Co7߷(*54q#@\6j,sO8O\̓^g;B\ +/_wM<#WV׳uɩq0E' |20{iB $(2IӵgV3i OJc'so]4N9vͭ@o(,K 1SpIru{t&O<1/1ӻEU<Jg 6 SL9;K+h뢮@l䷖۱NⶻlkQkimw,C϶CF*Y''m^9}0^{7]gvs=3|BV8iJB24qh5D\? f1n9tWxocm5;L(7 yc3#;kg|1ILno/WYg0nX-q2`|2V$/}Ș>Zv soԧ'ݯy.>ar ;`z7;ct pIcmەj[emqttqX-oeys7nܸ&řC&~oʈ ,B=y?ڝxk%gS}s|k w[ܸqGwGժ.z.[7x;_O|/˿;Q5nܸ0̷oj(Mآ)03;rOa#0PUMUSh>x( ?׾r##_zq[oA)IcFQhOy;q<|J$śo7PSg]5=u$999IkCQXG?|78a:} UZZTA5Zk" 2Mu]Cޏ'SeڶF|_ug w&F!T(ã%5`AsUY=CӴih4cTu^dde]܏{׃ կ~~,?Jv߾}f޽}q4K_8I7h@Zoy˿|[o;[6ZZK/'޽{/yYz8IsͅkED /ŭw6?]Zo>c !D{{b%y|رEJ fMyG )XiktUOo÷={D{ɿ[6\Ϧ{o|wd'^d4bѾ9@` X,TfYȹ>mW qSw[}a%Z{U==OϺٛԼFvt~[*0ᾂ!hш?S?ӯ)ٳ|?;~0oϞ=[Wٻ&45)N,Kʸk-7 "Ƹ] Z@ pܨwOԏ|j߾}}m¦랷j(6mvY%7gY~1)YZkn[k|Է!3cc񱑨ֈW?[kxFX1+lz|;wWY\"YZkр֪q-;ڝ٢m~c,BM/WkZ 30ؿ%f`#lovy{ZݵS}Y_Gjt:59ozz c|.c] ?k`q@cŸcW1X#r`~ 6? >@Ї5Ծ14{y\ M;xJ!w>>wO1ſ_`0 @By%-N\S j pcM/xn{}QOZ~m*X$Nr_+CE[ZG w,6clEHqanT;:~5v*v⻗[޽{M裏V/?pcd<آ$1X+hK6Z!zrjT 4'g{t.?2Ie~gNdl(iy ;`1bbM(W#Rb=h115M.6;yj4OW!ǥm\kZB~c^2}M:x-ORP@Y "O3X0P'6=6c-j̸'~o{zm߱Fhn{m;K2m~@Hu!2cݯjӄZcI~ZBQN t3#|=t>s0ϧ%~f`1L 2BH B݀F–,S>pXm'LnՋnw]:o$5oR]#DDM&1҂f0miʟV*B6$MѲe`@]IR"9 c(WT5֒%kKjH*T7OR%n`v]ZDZlھO~dn7X46}N^SjHLJݱ֒ oiq(7YkQ'RJ pKx![d<'&$.:I%Ms V}}h4etp>iw?"+[niF ݽ^v;~aڼ!*}ytf)kF _7kl iFu%7'}{Al<,rmXou8??8zgNb\zUl߼jE,`X^\G#p=wۜ]zvnI2:O]3P/kIEc/Z|,G& w=߻?OUO8V珺RZ&&BB%fK-KҵvKunhrr?۷-oo;%g]qXkm:ܾAhv,7N^}ĥ=(j[d!T ol]Hי2sO}xʸ̹g9|0$tܹ^l.ںA:'N…n[g?~_(Rwg^ Y R qaU_{ F֚-Sbnj_̇SٳgO6w֊;Cx:(ʬysD?9JBMR4 r\e].ZSm5&P^§aF/_pT1ֆյUΞ?cG8z+KKSU'c]Y3yt#lu1#T58F*RQTAk { \L>G8O%2 vnQYo2Iuu\~3ժYPHW*22` ! 915]3#?zp#|Ex;ԉxCgoVB]*dZpXzޯ Uy=|g:9vrQw QuR,Ӵ:-Ν;DZx,^:<311NU*x~pdk1;v_Z"0 M5Ë]|%_,}Vy+^٭.bz<ƔSE4j%ݵinvv f 6ފ2vYhI]OK?5%Rabs㟯 M*ĩ8xI{S.ų/-6_nsAzuh4*"IRZkˬ-`J\EE1GqTk!85NMkm!SS19>ُsInx9zzO}S2+^*fg7s]Shzc B 3#w_s|Ǝmowћ]ߙrX|偓'=v@?2+Y/s= x:p"jD+?ß z܄N8B!O($Ys9t =JqgyUb xGڝ01Y""SחY^'MTTL%`%KS:6YbMNZ^RN⸆*yue l$ͻX_>q} .,/|SdylX>Ɣ,˲xzv À썛Xk#| Wo}OqG di>-YݐG/UkIB)م>ē/U{EDߐ֧=]p5E 3 UYA^v1e=BH5Ξ?˱cxГ,=W^7Fv(Q9SO¬A,g^mkMd(ҤCNi"")%R*:F}QL%IiPo 7HW$Wγu%Nlb t?qGqcs~]qU\~NSTYoGy{8zhd王ٹsfBIN,MF%h `ZYiJz_ B)RNoHLAm:# p{J18>͕W]}nvzKdtt|ɹ͌ BXC+\JAa\`Ra=|>?/  [o9w*$Q%3ZGνjOVc6!DAM+_S\qɕޱzåz6 ݧ֟ޥ/~o]P Rd-++:}>#FGX\Yկy 캈z:GO~ۺm'#S4aks2*g^hTJnfuiA B N= ܤk.%$҂4e,)@*,s3u}}[0>™S5O`\*);9s2QQkQ'K;.jg9R*)z A.pN@HG[1c"{@(\cZnyǙ"jB DQĹųR/hnr2h%t  u:I6?r~cGWr-[=vl#?}}nk1oJNftl=n 3@SSBP؅Vާ9~8O>@_}}u㯼gs |uy2ZI! Q t`yOSh1261kt9f__b`pO#| _aW'ݴYV@_QX_[aum"RǏ"Y`@E$#>uڈP.kP$*>$HkKK:qG6#-$I~vJK=@跽 ԾSkՕZ_[wTt9|+Fx 'üzw{Z]όuYnתUib$.k5YSUxӕ7݌ B߻RE\s5XA"' 0yY4[*BzP׏P<\{'X}{Py15vש(R.v+nLafY(Iiq?[7a.}gN3ŀJwd]e.(꫟ӧ9t6oW"=x/-q742dYޣQu ,z6biELmmd[q{2M~PȞt] ܸoM3^+t;Y7śԫt-H"LR9ֺL@"dYSy~>KW~`Ϟ=B#޽{ҒUܚ݂KǕJFmxJmq&*!E~HkNcURk+|gNɄ-e #5֭;olqޕQEz10=݃t9:'gjk޽{6=|cx;ߩ_-2RUMRkFiRYH5p|lp)|ez8sHGSTQ>..۔@I96:IjRZI©s8(_yqYZZR1<2"q%v1^ +й 44^JLX8?&n c=Pv #:3 x| *uqJc~ ЧaV]rfZdZ+&'hPҒ%]OEf2x,zLX]&EQ )A_Gz (c,Rz|R#yYԏgOE0]mWju$%ISk 2W[#\]8aKOqw%= uII֤U0ưā5&׹Se)CCs1+N4=snwwq%Sŭys罘O~t+Y\C8@u]8` ZzgH_K܉?NAzʳUҹ!D՘"yq DZ9J@_j%*$u(:X"岑gkץ)e ˪MOW)ntY)JI3'Ky͏|w =|.o}[ nA 7mmI-}X{uZ'2.B{LrP(СUp=LEE{اt {6~;m#)n\0iJއV{{Ν_Jq&'gA@&:YO^ˊ9{>T2^IE&9Ur $} jA݅gQ]-@7!ҫUnjTSk180L_ q1<(i""J)BY E\o"d4iw7@D*BEiͻ\}ճٽc73"R:_>m+j݁w[;kZKeG*涍y]Wra#yǞ=eNw3\˳kF5M2$;ŨPHQJEҋdɭP;$&֞R[VEf B"c1Y 4?8^ըTk Z6'OǾG42RYuOZ8fr|s.SV) 0E6s.0ŞEp`( (A RȖ6OؓO0O0k] B !:Nw'*_QQ֢Vm?Hk5TQVPRriO#FZM$t;kcQ:U<'RڝYb%R1JEj34:3oR6([? x.hLb)yl3 n21Y^'7_g !Uz-l߿mgnQ]'wBN-X jzU9ibΥPkEՇwn`˺-=Xkʬ}BZy<`IqRI~*,|X #.PS[.w[ӿ4KIojy ќ8vzQ\ak*2J8?YT' %8R0u" XZ] b;ed솁nO Љk<scPqDs!M:RX,thVv&,!:+$V V &ϑ@UJuF ӳl4H^_`zlYx@jSj*'MN!u/7 c5,lalٲFc i第9c.P^#JV(JŸ{!($jK^GLNo>}!"j 3># 7u!#H1<0L0;!Ge NNk,mcm'4'ԱH:]7P%JE(Ei%& av6maxd| >x$Dpx=`uݽ%J@Z}ڠiA-)Ƥ|%A[gŖsO nf`߾bŧ` [Ҍ4'FlQ0*lvu='|(`0T퍚()A]6;O,}cXc+U$YƩ3yo;> sxhf_n!R/(! ,K7m6gŌ5./lQ9sqI$YK+X,shc8~^$d2&4Duxp,bwJTof~y$ϨĿa",@F1i4JP6:yMYBit[D>zqAe}}e,uS"046mLOM180HR`#>띢z,rZ2 >ep DIK0|| 8P Ԧg?K~~7߬'Ľh|>v7v٫kCc קXYcܝ7T (ecpzo|1dZ^Q$BbIOt7='(~N&X(1@ זywqmI;$]`NY俶RIv!٨31:Ʊc9wXk]40>>RRk0D^"gX^^fiizVrEM7~(Jp!{ :Z[jI@ S $NrECPja^k DI҄ݢ^#OCI;h!F&0Ξ;CϟXKWTI[.ajj j*1)o߫7 XXBL0n)($z/k'=YAV遻os{ /@3Xk?yrI;{e\<>4h UcRk-zľ+EmRp/܅ oX 0Ċ@\?=&JG4!,0?RE1"12jZ_ZR5),ZgiBttVImtK& վA֚,.ca~}C@kJ!d2؈ͭ"|L)g9E:0W_oR:8Uў={7}n] V 44 SV 1(1e KM~{/wܗ{R/!Y/BN/=һ =T[| r-3!͍~:N:C|O|#\n}-۶mWh"X]]V۱*BF5$appAZ5.+K,;eTV!pc: رc7sLs'q5xaS:vtv7n8K8\,=%AY _څߣ=}A\0e7_RoN71|p!kQ4x̥D߻?@0.[~ZVaf9{%Y "-6\tgG^1dzH_fgN%Q(R*M5RaAΰGke.w\}啌On8}߸N>y'ݽKdh&}$(jMzx_k_&gѬu)$qbS7Ȓgۿlݴ7?WM=dxlHE  qq)o n=|^֖'0 \ YEޡ ""g&Yi _ۿM{W}78*),sԩcX*Q"SZL̲yv+S  PVee+R`RT=I{'BÇ^ -(JB4NQM()kq+?Tk؛`"|z)**ho}_)Mo>o>c}zu*-On8O ix4[o~CSɞUPq>8^8}k F] T*>Mr \{Uضŋ_p=/kg?I0>1GTF[<t, !R9Bͯ!pK8yg3՘Gβ)ykb FY[]Z 6g0==8jTY($pZHzʐJR=j Y'vqz\ % ՟;K/ı3A1 #>( Hkx 2oV~kw}eƼŗ*Uәd`\W"t[n\v[ hG;$Az, Y \HՁ!j&Zcc 8p }|&퉍>:^pu޾FʹgWǎ/2c]I[,tiӜ>{cGO"x+oz_O)B^ K.?n*ɵa~u\ |!xp+"]&\ɫ3^uAyu+Sr|~PƖqTQKW~޳g}}cYO?8i inH!"@a,ܴSV .M:Ơ lFpFYda"pP v,@B`trjI+p x!yCۙضWrGi[xr?_ 7,c`x͛XrWnr" ,en&*E^S'xkApn%t8npr\ysË_t#CNM166bVWWyG_ c,./enG^eo%+\f kmB)} Ȏu;+֚ŵ_~qYo ~l#H:]3nX+E .*zUi[allIWFf)1=ݡT^PpVX^18:@8??7߸ N׽X0Ε.$. &Kh.ʫh;̟^kLBd w"[QJze4B*ՇQ^-Dʝn~{]j<7/+o1gEFض7&7&?_zO~E=<16Eq,JJ!.220 edYjãR5V}~}-[QJt jGpk5qI&q\x0og{/{7K?~1<'a1đ"ӆom|߼Dt jk7mfY+˝gUjF[&X][euqJRzB g0[BEO>TyOT(&9zp˶lټ1Tg厁)e=esMN?dBY(bX]^s?bNcm%ISZdOG|J9È8/Y9FgՌ\&Js+ڽFRm:~xx j(oB[~β/cQYɃ`$ ۃ(YzIUJHI7i"bppiJe#:iV^Wr`|x2O<0_ܧaD4jyآL k2Yq*\e!u^W=J^gu}mz8ZQDv͖mgs.W^u 'ϜdSFcVXkD }fue-gx#=k-&*Bz-.?lVFp5LS䚫 cĕ5yN^鴫#?eeWa~9j/h&Vrk8ԫΞ?(gNdxdJ\e !?UeEV){Qj[Rt*$.EnVHSIT$)k2FW^k_ػfy-tun玟_73?ybE@ÿSqD0piZ1JJW:zT`-2DHǥIhI6G⁇?Ah]pq˛o^ֹY937|sRcm:ӀZL 0EJxU8YYob?{=^nA)O[26>A7Ir]ΩGI.>nG~S9u$NzN1m #j'ϜZ0=yN%ih4LOrW?^@Zx˗(>3reEth~Kع}C#tKz@7 QSӇo|%ktG3fZ[$AFE6mBr-_ށ 5Yh06:Noa: UmO~BDR"Un0T{hm@(,R@eD.$Vbpk&w3\3¾}i!mVzaGh6H<֨7(,`GecxVCz tNњ> ǶmM6*ԡ'YY^`,lݱ-8&M܆Nz[&M[lsLUb/wy(ǔ`yeQZu7AWIbdbH،E7Z-ؓL -$XqdVƩ4M0:$Y ƕ&w]EnH.X :Z8~,޻L++l"knZ&LRJpqFCcJlďW2\seJhc [ dp}~ڕM8y|s0:I\#g>'ߴ}3/{xW11:Lgš#Oz/㌎ IMT;sSbZ 6KGIeYN7Mo68s<|seP/NO׽董u/cqç}cVtVض2_giicf384Is`X*L'C.})fO<yx<}>y2<4FŜ>}طކr[2 0 !ykݺm`|r~7k.4ְ=ȼ;)h<b0 ^&BfgOgy,[v@Ȉ&c? -#&fqmg#x>i=7;fgfp"[D@%UHYD>Se?-X&] O0i3Z*/ ]lxuGw 0󼨑T;<8ӰB$FR%윌!MS]SBEQ1$FP#}-|&Gi!{?9sZfydMrd`=I~s_[<~ }q9~w ,ã! obrkO)Q NŔ$024ʶ]38:JѧW'>#(u3gyfs~ć˽)eVNI)o,yN(xԊa@DUZ<{c lټ?.XƇzviYo2s UVxk_*Ox]l9ǮtU2ΙX[^JO ETb/HR.]_]bhpz,KXvzN/366A_7c܏]\98 %JѼ8oSqJx/ӹAca~i0:>5ϺF^\6Ur\m"#op*=c]WeŮ x"ȍ!&D)J8vsqaXi@݁%VۢE=@ 3XmH'y p%5#$i8t'oY/җ{ vxO׋U:MWTtYY(9l(gyJItɍpͻ$Gs_[>w7f GO%/e|zGNm+n%b/0 CgE !J7AJ(M> 1EnD50]tӄN8FeVVW8$3ws\Sk6k#[5/lnDV08J̀&+F6oBH$0:2Ʃ'] $:t?tYYZAk۶bvvAJ5m-bgcGZ<+w( [ 5#/gn jvkTUgѮ݆ɽD=݄JEy!ZQ Q֔2]p@ D&[Uݲ1ONxk Em4F^,'!ykigطoi{}y`?##ERQh k\ ` ?ACă4KHZۘ&VgO|?{v3=9KTʫyч9v)Ԑhsλywnۺ [k$U2Q δPbߋ A{=ŭ7(Oޡ '3‚!7NO'aY43팼bi~E w^L鳇ݍ${Q}^f` QTsUre*Xj/7"j ̐(,|RsM_={,lnڐTeHS]M7t"0dyN2m y{ v ,lk/}3/^}o .O|!M'piYYzCL2y3,Ǹ㋟_bqsnݶ\g$yN+OTb k6bx ]'yf*bu&P E|gJ mj ـ-~BaY=A"Xf,hsmVi!g10id~qM$ IJk.O$$Y&R+4f+7p'^s Cۧ`brW035*ԬYrc *oCUg,twI L:LӦ޳/uRTj1л.K^ʱJ}tc:rIVjemqڵy~.lڲj׾r;rIOg`pNڥtQQDJk!*QD'Ii4j Mꆮ&RLyF M-MW8rWOrw]lۄTEgv+lސCU{ƧzAz'sƠ d*#rF{ o/}6&7q1Z1bhg9}*"y\usMi(=z74cm[{ lݾqpWڟ;w< WoRm{zMZC[zLU8QL}e`ӕh?31by^0OITM^Q֐Qxt~p#WXoBXp3T2"@KhxqM6u\H5I2BnOcmsaq8qe &;K0^2mBHbƖ&[Uqqd~'p RfT # 7$¡þ]HZI'q\rfg$#h7E*gɉORoԙ#c4k/z>'" AxM\! +E V.0ƢT쥳n9&HW"Hl#*~3C Z_E ؗzB>R_(r/Щqm־9"k<,n^o w|@>Oj_K[;"tE:|#3ΤEHԩc^yAUv[sf~~|ض}Y)+m1YRVי裯^Q5=˩8y-ι&馮W$(pvG댤F)lky ]:ۦgh"GP*{6Ԁ" fP=}yӖRX.ƣޡK;-nJ3:yƉSgy/+.KvssG{1R8q8>VHSc%(ʖ_ 9 gmVGF֛T3O)9.gqr|l߹QbQ{5 A |9`mr|?Y(СQ(.+q TcdůVbw?k74UgPJM8p8D(b<΢un;tyiz 69vfwX}7!5k0 ĉc(zML}}cO\^.\}i8צ)d+6H%BT#ѕr#j V:Kq/?X"sEi|B]+܌M|m`G?#R$}#*]-A"{RJN*jRsJRRmzv//̛mʈnedtOf߾166F$ beyufaqFrnyk`~uvrrzNΟ=E`vvV9cf0H:6AUXXYW\J4<7ёa?9lJ))6=u(S׎)T7`6T Ɍ EHe1z]s5/PαQ\a|p =dTE$Ij[sŗyv+}N.!ϲBnԙuHDia-|iZVx_~vle%ivnv^czPA_9rVa\o|TN:j-c 'E1Xo-darOǥ# זZ{zؽJ7}g0^d9Xƽ&w|H}xtur4w}㰀LgeUt Z=#D""GYV qGWLˁmҢ6JUƮ;+kT*T+1.3:O9CZ^&dF.(q &&7;v26>΋v,9C$YK#{غy3+kyNvLFyGy mǨ&6@ESl~L JA.>?#LUXKXorn~ivn&/޹%/ѭ,)]$hkiw;$i5׍nsy:j1>zvlz Ub(lhŵNZ!Fa`׽|oaLM5C |SP-$Ao=O3y1$QZrđxk҉dd^J j=Wk}` ^P.~yyǽO-7Z{Jmr ^$v=_U(_cl1CHאE1Z[*Γuօ_@p)n9=3h c&F0=}x׵I;)roa\bHG4uu.ϲaG&R,soչtS{)@ fYYk9BEMX]pSq5ȫ {1&۷ɝwǯm0n~mHzm]b]|_bfj })=`eZ"3V+W8etEJD8|)80+9L0}'yRsq\E AʲHp%WrW21=Mt^qցDҟvqHAa45,I-TDR(VDāS΍.qsohRXm]g3[6m[۪3eJ%":I~^cc:|9FFGY__uNJR.:GW9+k^P79725O}ӴϞd|rN9(V703[t}TMTkFGxؾu; &П-P*#P]a.aU3.%Kyަ- ӄ{IDATsg9|g`xj C@l]ָ֚gϽ-[3::NZso8Bq5Dk#uVQ†q1 R >q"- 9R9`?Q|[RH- HCz+:'/&:gco4ڛte-59zFE,8vH~GT⊫)?pyFd[VVh}V(HA;oy[__Zquj4^Mu=Bgi*Zvgw`#,CI:N]{Tˋx5/ءy n羀e Mq׾L3:<'hVyGhHiRi*LO4j[L)ZeK0*U)EXΝ[`mysxεavrv́bSbdt]. $*XZYFk  ?n]4)P~R 2(x#3A9azrx:D[zw~  Si6D7MI̥iJ$W%SbI\yRtbt,R]cbkuv;^Q]jJV^ShT*K(EqNF%)k+TWIsiZ-G4T5R{3fff,@YDֹPl$di@?z/?H_?KKѨIPmU,ws<޲?kә;[J2DaơmS_mFEDQ~՟$ڊC$@L*UԜ;ǵQ8T*ɭ{9{g=8g߅K xOs՟շ⻾GObt|. y+AqIiɽۺؼv;v>H)Uܥv !08Õ܂ճ8r ۍ,&Vm@$XZ\@';ځv]$q\V]ވu֠j]mI1d͘.'s0 #DYT6xZq\h;ӌ[lJbqa ׫ڨ@A&)z7V8a^l^߄Ƴ609..*%Z؜˽>֭[Ut8 Cx^(JJ 5Ю6:#x5֬߂O3憗/?XZra =UHJHCh;}kL,MFY IHB8 Wm n` CBkf ji~ HHh&\D_O*/yoccdAjFش|@-X^Kucnh,Or(6I& |!0>9^QZ\a^}-<'Aņ  8{4|/i$(M\=֩;ْ5{142FcBI*mVkLّdId ]8^P/RT7g|QSn gC5DH2lśkAI/4 Qn*K3$A.*~@p~I#/3p77'oo>zEQk<ҚgZzGP²ܭUʡ د T|s09DG s_zVjX\Z9&354uT*uw`djy,^FkGw6&1rӪ#cq#,<]$\"7p$p/O#I"eriyanF^=Flj'('19> `LW摦E\z.̓ gtr ^s֭߈Nve!PJA$V VHRxـnc-HnEZJpSl"oթ/d.5H%ɏ!KYeXF U(@6K}_]OAgr a/|w;~Q8~bO6;3GF'je}ݗW~S'Nayq(%Ta$YDhhjعs'QJyŋg01>FӧOqn>֭ۈitFF;NĩD_@ZLOšQ(@n2`r,jn[LK.czf7r-hXXX;ؾp*$y{e$dr\|a8 qm/K_֗a݆Mh6ZPIFE f3{ y^4+ |`^jVؼd?ap{(Q>9F X8p\N*nժ5bDjB&F^0L`L@J +> ϣx8F4YΠMt&Lڡ :e\K)| %D%8DE0Q4pOH 7b\HkrI:}~}A@yp\z}=wPQa XNR8Aڭ40<Μ?Űi ^>߰_w#Ñ1Ξ9`xIU-lW{} 4JẆ1Ѧܥ~ZLو '6y>4AGb 8{F(OAɱ/at:4y+[قKϠCRA@` ,\ELu!-ފuqۋoH s0O3FPAf6qYi6Қy%if̮Aݢhk>ӌ[hd쌝nW`aEd,*Ѫz )5ȖZ0 n=E0F?E(ɸ 7fe RBzez-xÃկx5^tM?>ZĠV-B)R)HZ T %92KwyЦii%`ѣ8!5^6[2JqM>p1<'2VPwJZa|*A AYG+pz= &yKPu3?7_k}!8]GԟGs΢_D/ #h@Cx/ ao A/ӨtUu?,^Wvr cO]fc:.ze4-e/_| W#c6Ƈ 8ٿ6l諾pxx)-tp"ys8qjLJPw[!\^h1"&oѧ'r3^-/B[,qi:o "XZGHJ{¡;0&,L[n{֭^q P)O{Vtk ;. ABKvI5Yφe1N1%X~5" ;f-X;aJ hR];ynQ} !**YN=Vd_XܷX~&&P$"Ve2T@'(HFn:l @9߿Y9Xlz @dP(Ky|CBuϛ.L87AhJm5.]?ߎxp5WaG@Rnʧln-1dk` nx^45{4Ç<7H 39]ln rI1i;ݠ6ϑ xLm-!0Sjw}uh#+dG\8!cZK!RcNc#xXqOƵkiæx yMI q}?caucyiϜ2`Y#U9 =`U[0;=FE pad+&J((u<t˭8U>)!,k,܉w\fk>Hɺr =.SAXl/uvF\?-7ނn \}5fWMsU*jUUL1M2VdBp!8m2?/"O3HyQq)7]@Zr )gJ1mQ*6h;|“8uyҊU80?j/2)Cß9^ -.-@ sC4O'6Ѩ.ajZ?inf֢;?}WakgV$\5Μu/I3`R"fga-8vd/8eᆆA (L] o?x+~ؽ~q\:v a" C:޳Ni_@khe12#lWEHSOD G{pEdn P=6[_2̮* `-Rs/ٷn ' Q Zk-])*bۧ1yMc[3$iwdg>kYJ|YȢ%+yUp] i~r߅WPJE$aƞSA`~4H>.k#:w z6Ma`q E'p~J7y+937W뮻rw`sAM*Q So "* {繁Ws pBHd)4AQ:RZKfƓÊB,y@rK;أZ9.s::tWڡӊ]G.wҴƉcS麛?}>CxK'Fqy:AbRh8Y*LQ317X-qU$!+na5LLz9;Va3`w!֓{@49 XOZ?PS $N[R!\Gc^EՂ1 ("\x x</"_hAB{ɍ:J)`fxvnnnΝKV>_J9=77w3^O6m4hN sKb+@NǏ쿣3ApE@!TAjxrA; aEsNE; Rk@;.Y&0 6b s\A/zKf]T*U.K9н]n3hwo)RRd;K|vhԋq$N9O'_jF G[!<-i!Eɭ/u!9q\dq=(ǁx%.ܲR ۫{Xfa ߃\iOߋ^hF`8`h~VBHsΨWҁ*vJi/q\DVƴ12`A8?(E9![Hk1:1ZM/pNj_xf:8y4} c~_-(Bfg]3(DD~YIMzA@;w{ZYE*c2)Q hX^ayy_|/ˋ-Q΍7o8&RTepdA ÉTNRsoQ `K$Yn( t%H~4Kq`GW+4?ltl''7 F&*!ȅgEacaw v\z9BuZTE%Sc%<1,jJbbCWcK6 0"(j.ne8r شjtZ5шIp עz#LiQZ" O=dB8 'M|$JOq2DKHC,} ܚTL?49q}tGG=oJ4:Q1kOJQ&_*ckn^9W/˨a\N@ L^e~Χ~1qȕ␒* aX^\EF5As8A8 |j{^<ڵSJqh4U^ԚxYܲs ~B~7`0tLOu<cG)&u/a$6^sٍF4I%4a-`dbAJ $ JkT&lnZ|K^YQ_r ,++cCn!vK, ۥ5),CIB="eu](ǃk*,G߇11z2|K_n6lXI!,*jvas@iސGAeTkhzINJ*aU NcB,O*Ǥ30۔4&^,iKNFZg5.?>;H=DIZu,Ce!;-wҬ$>y(N?+^Qc< ^ݞrŵB ",z <16EZwB<׃"9?Īnڼ^Z-##jAH ܤ^=X7>VN|y~5\#o|?@f!ߝ_pwdwy'oCw\o|I?a0cApnh l(e}Ziԛ h!wW@)0$=rظ~'&P7yd^1)K=Yz&?%PɺI2 DAş+Aӭ^cׇ j f:˃5\ǃwp9giѬq9yDAoqyNcȻ1+HkߐcҔa9B~"PcH(jn*M za^#ρzV>~ Wk}T+.t,*jP0UI'ܒxëC =ׁjI+p>=J4Q0d# *,=hAw$-Tx,HI|,!Kt1| $v<^߉\O?$ eyQخ )J'C؇TyR,u xՊATgKq\4F%=xO<ӰV]gZͪꎴEgt ^иl6?2;x?C]s͜زeݻw?q]wޙݗ/_z?EQGmt9 blnP7?? ߽NYZ"?wILbZ-aشi3f&gQh]b"+(jE C/0dE@ec ੧‰GpK^v{ SS7{KqF\K2x>v*uB:pBG] {A)Gta64}) x)¶SC'ڦ .I> ufCcJ&)0D>?LD}?z;xp/7ߊE\xY ,ق;n(A#j%XSŝ¶]cG2HTs|/Rp N=U:Uh'PmPk6wv՚):p]w8gLK9˃7?1=9舍XDQ (8Vx ,9n{Mؿ{' ΣV |Xst&p/g p6Pw4JjdoܬpIQ6vڜ@KwMɷt"pFjatdZ ʊ;EvZ[ zJí:A|-Rs\ƋO-C d(ưIrC+iD$,LJ]dzYѦmĈy(Ys=4#8y .^/BH8ZfNJbnOtl$&G;rzjLFVmuG&}ų577;NOswq^޽fv'Ϟ38 >D!Pq1? \󂛰g5PZ]A"ٯ݈TuhM,AI Ҏd'WFb>_|7Sp Vߣ)ƅHMY+0Y8 RR(ZJA:tkEE:s3K#Dkx*_5q]E ~6Ej%1=T ܘ!@3$5slELv'Gq I3.eA,M%1QU=\rD,MN>T iͽB1`\WKAƜ"RXҖՏc,,9mxG}I Eߧ_\\##裏`o~)52A^}o?<=3gZ6wݡϾ?y)kyG''F>b{V|@KȀHI7{vS[q$sW eA"zӌ2pرg Bl*!6lJ *U:Uj!&*)HGq=# &c{p=>؎qUGJ< ϜĴr|rlLOͮF+k5[v;w؏'i֗(w}ooa#gYnnjP)+tΑ% >ukc酷aMvGx>))5V2FV1/ZKNKN=d>hl+2<-#[Bt Ngq]ހd{4O|ffGhWLɩ5ֺZѿ׽n Թ;+v%߶u?e_eW 莍ύvgNa(BqOũ:i6QRniĖ[+0(Xk-)R攜 JdXA@q du]D̳ι$(f9W `c3<"R:.A K=uS|b /懢RzoAAe9dR>`JN{>Id uaF1$BEȍA$HW|4)2Ckf?cR zY! cqhIH1"\EruD.{XkEXZ#3 RX\X#>G[6̚n֬VhO[~׭ߵ :zלw?׏^ Xy x'2kr=ii}8a8~Zi9sBilp Q>_AX,"XkUo2*-rv.`&FM2`3R*)+\NJQ:ZGjrxGJA׃B5OUaVXKgP*% ("D1@N =xP&N.,$YtMMG^({|Ĉ>[İ E`:~D4%N_%t-1Dq]YS8*ZIR^ߊCCI,=b'*ߣ}(#1-J1Ue",BӪT8l`2K.bC'19|jWEE;򿚣wqw8SS_Nd*VFs7{ ?41>8z.hnRdĹq}*2BAв 5tePBkIs~a-`,Ax'+)(NJ@s.r68)FpK6Yz!Yw45 Ae &͐ 4ARy#4I@{a@qL0Ʋ:C36J iBBh*׵*L9J . ހ8ފQ"*P*I R./W9.jQ{\EG8rvډǁ XiƼb|tDMF~ihbf?~]wݕ !w/w+`P]tGdg06߈G߉~`Uוvc$j J+xXfn. c՞捨̹1\m*NKT y1H q*3ii1>=JyoxիA\w%\={,ԛ_x?vGDQ+a6m۶97{~Wj:}酑= !K{ /bu8;Oλ6cØ}ЎF6f5ߌ c:[Ni%Hw4u6(F7+QVUpwus_ei6`EZ `rb;1\1\%+#THKzFݧ>ijRn"Fӿ% \,TnE-/ h'Ј5ހe 3=2>[k]g8|b.pHYyKehIH(pvwtwO[ RDaĐќ8"AU3yf JuVfWAw!> dҲ`FC,bDiFU->m.kXZX=ȎO|j3mIBZ?4>0z^u.nn.λӻ|럽UO؏|ϯygM/ 4FaXb{8w֮]ukWcؼj:twgW$ւ&Vev_ss3[Ɂ8|ϧRi\ F AŶRεCp(Q鏢44_SN- ,_@yH9>fg|F=Ib$LI#nq fH&RɗKr!YR sW[1$J#٠!`_ Hb I3Azіd-W r|痠M 5 XPh7jp*Ii| SıhwUW6c-g5=:Zp37|-[ح[m0xKKo'OrZ!l%lx>u?bvfZ6Avylie ,8"Eiͬúqq.\yǠNՠ P%߁,0 PecjI;.,7= S7T(S{82 Hbs0IP&^&3xߤISJkԀ(:SlJёJp;s#eE$)$"abd AAKڰ]D>:];w̘쨵kj۫[+_ύgMװw z=Fj4hIҮ]>QbӦ-d}Cä]qe)ީ! )0W֊C- yp|u]gq[n]ׅ L}c\-  @PJl8ް5X$&5]lDazYaii)w죘 $^B,E,K#QDpI, $l3 rՖ@Ԧ1 D6(d?s|˖xm/.# [ajZ ˩z{CGGv.캙i3Ҫٵ WM֭?|?ÒY/`nnNe WS'#~O`]+yz|ӟ*T-vu4☚JGH"-͈## RZt \._v(lUp=ts M-%5 1O~Rb^zMXe,#(i4PW`-V}4o{ܹW=.#UN fZd0anʀ"d(/|?4qv55p ?:pJ %|߅l&qx.b\WZ"]-I=u-{AP'$=IY x'S7B$I0**T}Y(1~!K=I#2QPR"I3I<=R 9Ph)69lRD+p8Cd&\f/%d D_Zr}t[MT^ DgiԩعIç>Xf4j;2֮GJi'ֹ9w|جo/A4*Q 괛T"L&!$~FjZ.),`3rS*$t G/Z К~CR2D rr!R9N)(_y)X D? (Dfl` ?rˍ:^iPåtW+ ,9ЎLkdƠ׏؋jFxҞ=σT 8ryAxI MUk4_NOUfnn.{JUײw=pv=)UFQl u~k֮Sac51~bcVH7Hʙ[j pxAV+5KQYF iJV9 4 VbYdX2 /Xd&/4KFRR>BCaDI ICdIkssy[>Y!fSw k9O"ZiAEEf*` Y(U˽>o>o f DӬVĔR},Kqyٷ_>=y٬VY##hvFֺo77n|~F"0v3m %Nbxe6?{V-AK({ VM]kG[zf:hfcc;n;gn}=׏w}+wK]{#kQy$̰azj<,|u/$GSkk1OzKvdǁGR$$# E)oE/~VHCÌ1yN > IJYn$KӔ6SZAQ# )˾&1ҌN4M ˕ҤL!eҢ` 4zE^+t)MQ$WUn Nr+!6(s%ܔE!.\'?I|ϿNYu/pe~#<@٩ѩ,=5=.NEW]KNi=`ulbJĜ 8$)zi%XRГ$ (*O8_{OR2 eX ^9CD*`LBft[qԠ &ﰔHLN(BQ֭81=zǁ2={}|g_t;u]93g}w~_p =QoQ޿d}%+Yx޾¥ܵh%TB w g$OŘ];U 55|߃vT*>$$  We(Tp݀Ky6PL$raK0zzʞ$IlvZ~o'Cwbk75VEONO ԪgZ?|Qci/\_ _鞧}W''&.~(ks)Ȼt"n؀Ν;-ހT|jZb+g^q" /ŧJZ$Is^i1mht# Cs\V4#Dq\{Q2󈸛 5gy>@3;_)DQr_u3V&JT'2* __K޺w߁Kh?8ƺȪ7>F:">T]p bSxl>7/iNIFM5Áif I$Nuh ~0DCQ£:N dXHKI \ڤȳ R۟$a 07˻`,%D1[r$xn%q ! * W*dt<އ<?kld]A fk{w=@VfY_0d|bKW.Șsʒ]#QJW{ťKjj< )y̠t׭uYf36jqrr/TfrxVkj/ _z,ΟG><}FviQTd!&'q%[o}[CT_9h/:*;y,F42yzIiMn)r"q e∹E}(J]0)L?˨o!&@$"F)ɔ#VqvR4=s#@`t6dH1j~@y̍Kq~sra/=,e FF,r~?Dfry$܏4Kxfe (kL?ye)SwsdSs<"Tv%"V-$VXp5m(\DRᙼ1q #iB}H0q'( 9d#A 8FtI H>ahO >}>Is%(.~ܓ~ <4u\k54<~乗/xrx|.L13RT_E{dvF3>{?W}(?ȁ]O^pO:hRbrfAZAPx~n`K˶SʬifTn49cD1Ip0DgZ=?I)򼷼LӜ>/uE.e~AWCG%@H ͂d!%ՠ|/11fюTӽ^2Bݖ2܀IM*hy.$3go|hd쟽޸xO#5uVsP?z;Y`0g_B!B"L9 莌ؽ{^W| ( n[6YJ\<ϑ rC0\SoSQ" c1~  KZϤHl!]Pa #qP[prVV8 ){eA'_spp‰ǿ,+8NxZ6|FrK!QKieܿO>_;:05lzl|7p_wQ&w?T}}~>p;;{>a=ɱTUl)(B30N05Ż.hl=زb/6d&Gf-$Cddd4M'2NGz~Y'qD("}F48)}7aLCoG{,$*_Y@|iKFB1']tEw_ =R |E$GkR/ %fT*T*5tR }LvE=1Ab%-VMp ^{iF<ː9R!R&d'8FE\G0'Q8?#ȑf 80לF &!-D#KՀ,'1饄jɛϡD㡔"9TL1qB*Z .JCyxNē;w>qu:V4:[??|xSx5*n=́Ν;CI?^ɤ]Rt8ªi~շw؝0zSc<&)L#r~iDG'?p~Y!#f& CqHs}dI£&GZK.xjSRaF#4F0)\Ew{آ̧1RQxzj2xj)%pa<عs7|]eGmU3hqstc-o\^e v8__ /|t9xh6iAG;h]Ce=f}b/+Gy5~A K1óIcyyBvHM'nRNDH8 m2ID~|cJgd^!=9'2ٸH99<|ߦ]3|Jw||G,4EŐ**v8Q*18<} ܽ|iSm;隊'HGMϠj}xȡ6W rann3Sk`=NS]^߃y(!k^#۷㻾?Q5jI #&y$IAFDϘ86-~r"Nf`MF@0,9g  w8Kp GqX,!(>GHqh() ^u]Bk8'wgnd>jqjnnN0;\_077Eo};?pwPvʢvZhj]ŕaaF'PVmx7`aq,qay_Va$R%V+f^  K.6,.> R!-zlaAc-R_rf,;VxG HA~! ˭`dd8ì= r|=T!!pe :~@Yg*N]MON"wvw##Moz?x={Mo5x%0:67FG%DawZK˗133/| nlj /_aCN' [s-W؋gRQ0pxyNf/]}S tމR=U|!I+%ѐ&-za(B֠I@v,ùSgϓ1{ LԘF^hɪU?:{eSoxyS }+WHf vX,/-cfv;ƿǥ%r6 C3L3 Db4e^F|4N`sm&2&_%s@ f9\[P8S5*f e1ʓ )X`u5<-ݮR.ze; xQq7m,p=G//$؎nh Z0~*<#M[4M?MR>ax9!9?찈|5P<5 vyhI|nvڦQdݑ1Tk]6_?txχ*s~j۶m7|galVVJJSSK*0czb.^rl!NƊ$\73qo>5lSr rZ*h| W`S_'XS v,$MMN+DžRl= }a<@Z7jA>֪Im4u:77\z.+r_|M澇NzHvD<#q1T֫x/v}y~i ̻;}N?a^i _, 3_x0]NDր:HSri>u7 sNIaISAYg8vRBp_HASE0hwy! 59?v $8Te x'@+VPJbyyC?ݻ@uZ@Z5fj{SVoy/}x--[ep}9Ah~c> _~vؓ{l""(G KetF:H p=ʰܒ(GZ 1!cY)a AM1bEs))ʅhT@frYDQGp^xGR E{>?-YY7vV{˗_fvypcć#!ofȳSS8.^@kJ 6'60PlQJgxCH( <20YWrG_JxFQ(!RE[Z9p]ukxX5SK^|C^yȘ՛4鷼;p۾-ߺu백7\A/mヌL?ѣ&0YNj{š9WJJ ,ئҪ Ѐ [& @aksx'xdv4 NŮݻ{| 7c=VUl9򑱱ɟ|?,kW/ /}Q{,T7jUk5[t]OX C, YnSe\ż}UYޫ%8Ed9PV*DT8?Dg9Gێ]W_w5: |z8nLUwo_np=Wy~=j֭aÇժZj(M誫ƾvų]\ynlR"sDi(K{pPA9<)o>|CLq[VTFZuW=[{w3igzmwI}烏$C٨a_{=N>#T/vi&r~ >@0I\xCc;$$\i1N<;Y{jԭ:^sX*'F;S7glXi=pc۶mпx;׭Eb Tj|X#C4-Jas`ˮ"a:RHhDžd.G148jp]=2x?+0Gqy0Ҩ[uJ+lv3>7'GZivZ+~<&}CmyZiV&'bC s9@zJsOK,1 y*6ܕe],.\ ._8sRC@ik-$1[tV@* yrc9r|s8y S3W6+lYIڱVgO֮gE~?\_oy\E4Fc~nްQv4j%LO`9 ]~/inC @Кdsْ!Mb? v~ >0`݆)̮A%pluF6uhvmoB$u_Wssst׿  l#f k7u裨J)d6ZZ1S}j582>yD*5ՊoV鎌VkkO-o?)?\_y~=j۶S[n4GQw֟Ylxxa<{9^'^ؗN;KZRZu\,.-gأcbbliQF)k+uhm[ặ lʷ~;F\_P'=O>zj2e8}48F^Ņp~Q ۀJCHksfl8Aմ[&V`xouw}߹?e ҍW_Dqe;wɓ>&<&K<}g;z|~<?\j`9}{=yG'ͩhŅG[R<橪jbtd nh6?86xOo_=#p}3Ka-SSx{YwG>gqO6UMrG)ѵFx5W]{~|177sss֭[qk*b_w™w^:6q4Mnh[6JݶGWv۶m~P\~/Y*_vf~;⿛Ph_?՛:4k͏_sbhlݺ-P3\d={+߮V\ZA>5\LZu{J NOIENDB`OSCAR-code-v1.3.1/oscar/help/index.qhcp000066400000000000000000000010621417327530600175130ustar00rootroot00000000000000 help_en/OSCAR_Guide_en.qhp OSCAR_Guide.en_US.qch help_nl/OSCAR_Guide_nl.qhp OSCAR_Guide.nl.qch OSCAR_Guide.en_US.qch OSCAR-code-v1.3.1/oscar/icons/000077500000000000000000000000001417327530600157135ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/icons/OSCAR.icns000066400000000000000000010351611417327530600174470ustar00rootroot00000000000000icns:qic12PNG  IHDR@@iqsRGBDeXIfMM*i@@FQBIDATx[ pU>_BBBH $PV *Uъ-REP֥ZP2ݴZF*B[*F R;!CB}Ow}Ky9s)_f2O}#IQa(`EkB`Yŀ|L$P&mR. Tx}$[B+T&x G\ )iW Qr*Ujڥ hR J7&ʸ@1 f7<ȔjI9W%NJ꥾ Be*-uU?DޣO3&߼!\& x '⍯fMb}ԑD |& ?>{WMڠ,21v1X8H|QWs2` o#pZyewͬ։''q68 I P.5-Y$'%Y:`'4c$Cg)\ZO eb-f$bLPIt>]HɨWɹ4@w=o,AKU=Bo`h}_'#W 0Qa.'&4zΡr[B 0G'dU 0 ~â\[xRp[GiNU_3LVM_qT~ei.xöxyf6f1u0/]+n"i&8<}Xl9_ ;¶NIU. 0`=/+bJj@h|b$B m)eɡn%OĀgw돕ˊ9UV:d$2\c0Vꀹ$܄ZK(X\y"nN<'+a~ȳ 0FH-yRNh3 ɩcV~lش(z\绯 rAY /,b,&|vǀ+3f1xEk5u-{`\-a(8X*U#Ps;+яNǯ.q/hP 㶖vYY kӪ7XF{Lz%'/$pyyK@ʈ$]@'[`fO$)镮?~9nv9ri:gu\9%RbN2u#f$f_иXeUsqRU]'MKEW$3#C]Pi @~r@M `fAz /3 XJ >#\ӧ\Gu))G0ΩQ[JGZi2^wÆ sJN 졉TndBȪDSVAʫZph8=67CUUU}_ _@Xbb{C͋vXM+C b.02Q_Y{_O-6fVNQjH" Y2ᡎ89 : tkD̀T-֚iq ibi?FfPnM߷Juuݥst= g4іE!%?r͑Rj# ZL Tk$I=<ά|7`!z4mTW%Nf `&`fwc bٳG _ pSFMp9IXշha7igp,՜ӭ-<cMgϞҌ36?M8Ȝ?/pp%>ew/pwD&F') ,; [e@g餵W`(_sRa緾WwA% dGpBNFtsK| Um@+7$lIi@$  2K,&n [I c+ù#&FdG7RIENDB`ic07&bPNG  IHDR>asRGBDeXIfMM*iHw%IDATx]xUŶ^Bǐދ("ՋWrԋ^?A}b/ SU@@"H z5wΜg]NBX߷2f5k֬)'*==][.O嬁+pA|}%Ł`4 ʇOO(c@W(8SXXL&Vdž2 ~\߉p/ 0BT6٢kB2:֟pD)@ Ffydz`M`+ [9U Ȝ_\\L)`5d%/tfdz] HƇlݳ7ç.Ȳ]' dI9w!]V=%Rۇ"Nh)R JZ\+q JJ1Rad!ZHuzIl#\)(p95-j=ٛ]9ت."M!m@7#(?g Ac'd:lHlSV6-s(ZPB+mh84ӁA؂0!W G5O~+/ˋɃJttA _ӀS!. rZ|0K  a1o,pa[v|k,>N`ou@?y? a&5<BD\#(H)|ہ@×PVȱnhyf6n!W Q8Q ݳPէwAH)T0Jc JbZ\QXD*[ S/gy9r悜1'aBd1! 罖 2uO#o14{>ñKK˪R#>ZJaΞOOw-d1| {! CVvbs!Lq|@W@`=ju;J5JJ2"mgdC١ jI&J$^K"ȴ 0hx漴z{;7cei^8I^8s<3}q ;T5F y~ Bcq@ (3>@mKO]oz7޺(4uJ]JsqD29[m0IUyB0_h:GћU8&oc0tiY9V(fx4y~9)`ut'F~B0I hȨ& &|}1cdAdඕxJS8X\g^aTѪpFU UDGoSĩZ}Ee翚 (%<$YX])Hg۫u!n )% ¼ E;v㢯Ѱ,{PdxM924P,"9!`cz5Bp:M i$x_5E&χ'Ow~Y4Ѩ k$Ȝ>T%p3̋,އZ\WrD'w&MA\?>pFdJw.ݺ(N{yiuuG<<1_Uexm'nPvɵ+ ! f;6+酿Vו#| TQ^5!"#bE3W+@w Cg!:Jˠbח~X 9Jc@&t~8?V=/o[6ew7y ;GMvZ̧ ʈFݧԭ0 _>A&2E-rOu<2eA;Qz`'t$  0 P{GHA,7 cv>HBH{6-- lt_n#([H踫rG^ CD:[j-H|=~E˷v=npLu*g$ X*j>* U7Y}$HX#]ib |!g[*i5%HL_EWc@ߪ9NpY]i:١Z~npĎ՝kq>(+<3+M'q=xe@.6`ȌL!Zyg7rЃ=~7SQt^6,H=}ֿ`^Ƙ1V<Ȱٙ #xH0$jM䀺thy{UZ{vހk/b{wEX!CgB;=Zp.ٞ d]/v*H6bkN fpw(vi-Ά`Ffy8 )_[a4Wm,N#\9]vg<}Q zlu8I+I[lȮ*zY*FHPq`$|y[v+Gmt*-23^8ЅOOkyj k+l9 @[E,l.;RBL 0fѾFp zS3Du N(4` [V h[ ӲyN{(cKG2*?PϓYEok]L!?oUt`-7f| .xn[(^OhMNV MbkV%tEg|22U,F|5`tJ(VC{iv4U.Q,u *1摌^1Z^fmXYV\'T_V0J h uᵢAH<dRC]45ƅ.ۗ13H*-AU1Ԥ0磿Wl de+ #wMN@%D g cJ!"р!ZzUX<t置x0NC8V<V>M!1pҠO +A2^!)@,7KNԙJ 2_גSmO>yEAJ]?͙t魗>[^9lj(VNUkag8# п)LYwDaJ(x\.7VFu/=H)K<#37?fKЏ]+^1_eKUW0nH"2-LBZ.,7!(Xv+_Ҁ C xGth oVW q1̥hd8F N"mR!*~4t:z|Yi+koPwQٝ0>]|=_1KCe Wj*^4lšd `9TWt (jT81:=sZݰԙ)`9ta{. FSӐdAP6N0Z+W\•:$8 1٧VPW1cD2) ZJ,יjNºхS9O `b?* c%ꕭ0KaJTzfEѽ8|O&(qޑAVBW]|coLi* DeRNZ8*.D5q2zAbB'`ꆡ.=p?Qõ%Eꕭ{8PwDsKQ dbQG9ߠBN?#Uez,HU~T&:愍![cd͚5ҢEl/ϗ.]J'?ᅲ!C]]/]T&L /+=8:w2 vjƥO_RcR={H套^I&~׮]2`?~ :QZskʈ#D4wrOA4hw-lK"Vx sVi YX A{nQSѣG5\#'O{OΝ:KFoy;֘1c[n?qa~/Kիe݆`$2~?܋m۶Ɇ x?̞=[NprIy衇>>}Zv ԍ5Ѩ95 uTe|w%!!A3ݻKѢEeŎfڪU+)P ԩ#͚59s8E_*Q+W$/Ӣ65mTf̘, s / 'Ceִ_r#ga0!!˗/7ZNVZ[.i;wίIonmϟM6I߾}7iD- h9{3i*U60-谓|FbͯtwË? |R\9!uh޼CUk&|Ve˖[ꏲ-tl:hB8Zq삨+V @Gx[.Z 1W (`}aU⬞z|5kժUB'/HŃg8S-dKZQ',X Bf^إe1H;,=W KWa|$vEiܸq]UaTvV0]uUBL Nh?*&&04B8p@^WTɰ/j BY<ǵT m 1pMնA,8{QߙDo8J*kԨ@7T4 U]LKqQ==~-S2Ac-CIE>l\fPe_h9b #a%իge;u+7v nPpqM W.gf!>_ӉUFƍ*e2 .՞Bahj(iNs *}{ҼϩH"Q@xe:h>y:%E@۰m?ܼ"-ԍ9 '^CHS"җMd=aO>]1*Նd8 G9a+-% ~S1W_ ֣?qZU1!6r} iSSSW^U`081p8aQVB9%K l#ʕ2l0fXL>Qt3!ѫ2}c?KZZ,+Y(dz8Hs(V#E*#^Oyu<~'~-@GTciqD97B_֭e)̏J|XIP*&zaI/R0(\hPQ{OVQ'9ԩ  @FӠ$i=В%e1\0L,h7M8v%u$.8) DGNsB@a.GyHa# a/\J.:JlG, @[ƾN)K-̠|@@˱( &kєiy=ǃ"-` *2b]? 0 !3M3o-u ‡g sZVVpx">ꙢnB+ x;T68+@i(xT`+!^5`7C?`|-25u3}IJ f9>ZkcyvCqU|c9ĞD@'_ݠ5{14~VbO%5ME:#L{a*2+ְaC{XMt.pq%HNqevgB .`3p򪂯[@{5QALB &['Dg Lpn3}nMou(fi-\P8 O-Lʯ9^?5T\<@Z:,GTRcq:T +ZM3O:`8kp*`wnIN j׮mr-_J'P`!eB\عvP4l?{<''<+OƥKh6C_jNҠ8_k!=-iq$APB RiYRToNV$4\x]B(Zׯ7&Jbh) h0ٚ)p TwGqv*[G8]&\v봸h$R"-2)-Ȃ{J׊ڽ݄Ѭ+Z\؝(ÍF2)ͩ^B'tuZ\i|psgh+3o_O՘G>A2OC o [(#pҾ}{Z-Vaڶm[״(Jl߾(m2FJV|q k2݌jpe"e+ R3{.Nh馛$>Z ._s kX. EaJoXeVcHTXShv,\gt +pH%vCBĊn!  ӸȄqC# dwbdv{]SeR|#k iFmldW U<;xs܎U>\`BsݠrڨwNC:gVr9dQ\8m#9T3BHS=3 &^<:|y"Db8&}֭kbs 0߹sg3qDGyrHN w@sl/@_H[)gv±@k_(PP( `7o ~rgth>ic6mF~/g^ss'-v'2rkZQu, `:"[}g?o9"E$`YjoO&&:rduNaGƭw_BS눀EdGeИ=IJ+Opayyax"G:{?۫xqXSX|ИmbW0$OPbKyWW|K X.':;d~ YX"pжxFoL'r!IfxdLwkJ+'.7H8HkR\ƒ>ۧFu֘ϡCwj߈k` PٝQ&H)5BTŁ{~Z-@9$|xcUM[\q@aadE8t{kݙ$3GTߟ]-_)"@b>!KjM*~"0p;_NtpyK;Nm5צ}?ΜeLܝӈ|V1 qM#8iy:c1 ;ǹFJ.#fꖋUOW;1_;.cmsd]|0ݓӶ D8 V>NN9z?u|uGO`#'xA=Tn2([@ߊ1@cW>:-4҅厺qr24'0::olȾ =ye}zOj|޽l/ː9μ 4V%pwPﯶ8^DZv! 竖y-IX-풓CN݂M;{ǔk=Q|_TA(@A{".šGњv|ÓkREq&3_Gs3'^A˄j68̛K.P(1qQ m̯ m=סoRC70z-SU< 31Uk:nA 7kPTgS[p8zGL…RT5A]Z>yE;T.w̚r3srR'`% 2L.nM H-b ùB;O49A>y'@} [a"r@cIaw, NwҀ+7S<4tnwSx.5@5<{) E~Zv +Yud|}2ڄX/Ժʗ<9SL?0W+ȅ_R eSv_X!G~k Td8Hc.Wwt{3cM=ۀIB/@ | #cv䓣@bIrhIc*ځB , 休\u9~Ȗ !@fRv~\DdgAΞ"Sy3ʞ?]uMIENDB`ic13SPNG  IHDR\rfsRGBDeXIfMM*igI@IDATx]^# EP"`^{Wbr-ݫ(`WPzrevy|m6LfI23믿D2 L .jQ )1d(@ Y3 d kJ02P?~V@FLU=@6QSl {qz9TBU**Xi"*0W=CE(Z6B2óWCh4d'3)h&J& AJy$6"@XVx]1 "P*3Ngu!4C %Bsidd  Dd c$I̎BCm9/.Y,W23 Qd2bF#<{"ӧqDZRΙ8d? "@"")[9egBc `g'!L*KaP oP_XqJ ?83Byaֿ[[ĚM-Ċ [Zg؀uҥ )ڈw*-7U._Z+]JT)_FTPZTPFTB_bQrYQx9?)@0W=.%J4U tLJ}W#DnK$/(fbњ-bΪMbF_ν t@Q́ DE*D熕DDEsՑd!p0  AAh2 :\7B-.(~V|*1~hf}3 BenݛVh^EWQTÌ"{O~E f@BAl8Ծ7B/ZsMI+SV6y6 2 vM wj]k3|h0a$`&@ğ"!@`ʒ 3VioR|;W7VNs5qn:)Tۖt@!x0("c}L!>#}_}5ˍܟL])^}=oѝ )@ U uqk*|F@AIAz@8ϳӿ5nxbg8nu9=B듰pvg>/n˃ dd|Y9r}z-J1<+жnEq> ęBWpygBN1&H^?E( 9i DQ zU-}>- Dxg0YJY ApZg<ۼu YD(@yAw\wC ng@`<$)@(iw;?{ef&gL܅=WX/"<&@AxL _w/Kĕ+ouM%H7h!;ӡRӑa`U`ȑ+p.i}10T띄p1xau3SuU(SJ4Q^ԁ9m:E-\W)+:a#NUl̩P֛n* m3,dzaGaqnXzJ+lbr ܇4#cWA@ z_!}8[^I cv*z5*4$6 lbJ㬵⛂5b bbMb=XxĖcJNTp(`4/Nd 9 Gŵ>g@FMîiWCئ9]M+pe~@y?!N.-#eA)\;ػdgtٹaJ#4wx˙{Va%pg=8j2|A0icg %:G# G~yi?TǨ~.5Š>c&,rc;obY&@EBf<{"\Ⱥ/`xd:.#/sDVX\\EơID춢'8 Xp-ig?'NKōS/{64x~@:n2# _?q6@S@$Cc_jsۮ-\:GUrwh*ftb)*[/ޛHEUˊuٓ LIYtb,m:9_Ў o=4aEǿ7JmBfialƢ *,1 zF=*HS~zэhx9=^A(@Fp95 i/ /8@PPکi{V8Ӝ#mkN_id?ӢVyɭČ: :H+RqgpHGE-8YJm`Iʏ"ckU^t+sNx}FjͲw.AU[sZ^LXPF Ti;?ŨsX:ypS1Y Am.ϟ>X}MCd?Эܣh҇vR[8kwm| &DvƑ.h/.G/l)m|b>uj\ v yZxV}qݽ~elLjj՝ťzn22t~)? +r 7j<#'hfE"W\ k:)b̞mLn6N%(w>K_M<ǿ6-F_^{ދs)Gy+{OA- @%hߞ\jt_juA %|QEL*GM Ӭz%;j@5rJ<.C;w`&AI}wxD E2 goJ<^iAα3q Ӏsj:?^`;SD5Ҿiy'jJQ9O4;hykT*"Pϯ?bqF t1Q2$L?DmR@[/.zG va,(@U!m4!m{;?Q@ pgvoK/׽Ie)*QL2zMN @g=:>QO[h2iO`]u[#(⬶ag~LL`&\}!y@b R4DG ;tQ7R KLS hfidD @E 9Z_Z:J( Irry @~脸7kE@mx8'|BMӗm2$K~'J~OM#?3p8nAw ?[ (O= =Q p@}ID2TA_~<[QPQB,"T i,uāgl *{jhkzQNs< P iʸث1 "@Z7wզȿIU#"/(+ P .(y@G| @E8ߢ`>ZfoPL -襂( "+9 a/s=5J +ɷ {&1Ϭ@S݁>ٚ5R qp NG8nSzշE\ P ܆v%N %*R itSz}>7NjG1YEG)xZNסB9}ht/ϦCc&Q4-ۇbFe"c@3Q:OycX k:K4/f6 ҇^A5J$ o` HА g\_J8oVN{d0V  R>Q|\/d٣q:΍dJ]h.m'0;6hjx>PeQ l @ ܓ뷊.St5}^ @ЖZ.q9AF)ЯMԸ{E 6@YH`P( ,fy wjAyeP)prtNpYN{ 7QuёqFeZ2)p]_6a3,_Ҡ |]}𥳐sЧp,[CL @t0FfiaKk+A-/thAM2ȞuK2s$j@r 2 |WWX҅qrt|N \4ڰ30 (2iX&R=fIjd()@]cI!y Nwˢox*4D<;qQε*ʱ9>/< H?JMG6O,H2`"0VJޭnA@{d1~i'luJ\4f{ǎ ]@%6btXjf%'~[֌}r}Ł[" r O݂<_`qewn)kB;\)pE~A.9~D5N-1gLI,iŒ݌vϧ6l3Vӟ׳8g$lB'% oeb=~0Ւ_'o&dnl;L=*$0q+g tN:p%Ԝ!H;̹vKc:Y (7sT_ŗ[٧U,Ftn [l oliKvw0v2~G}c jH8BƖӴ^qϑ[s-NƮu~"x2RC3BG*?3~jgEčN}V͑_AVRNt? t vjZ1Y/&~f{5tظ%^H c@_K8QK pA@C' ӗoҡ@ՔgY? 52h^cG 57>q=n77ȯV8ߛ W*Aڑoy5XWu%nu~rDs4+:+HgNLB|W͗LK(X&f:9˔2uճ(ײJ/:V.&z8ۿ^K` L^GZ҅qD{=\GN&@aX!^; ;v@iggͻ>ĝx:K|\7x%#b '6q@f3J*a -e=ܮ0+o V B]^W@b5C8!1K3޵5VnAs㥖A2>su!|w9R9xQ0sg^nHWY6pSoF;`rMh:ܢú'Eq 2)́FJBڢfΔgNl4v#1q6pp6&'@$vvd u8 ց3E:`#Ր=`eNv 8=RoZr27[*ZA0ו̔,~٣lS*hAWjqj#]< *jZd}v.LNߓ9jobwVgGؙSr4A;;pv,ߠ?S:mcg-fR_FݻjMWXN9 !g<B^G B;g2'ȯ˓6b2 ,o_ƹ]9;H|U;v;1-t˔]+Kҕ`f]NIoyeVXd05y1ל7uf~47 e- Jmĕo NRQ^۾Z}vf\\nerT}W)]^k+*#t]s/ -Z`Ò.̈UVr$++ߑ._jc$89[Ur=kȺXdN@Xi>weZc7F͗u֞Y-aErW",@,cEnߊ_ Y>KU֛]z;n= ^M5HbWd bƗIy* G`i*cB5v^fȲ$T KYUnU%{h% 2C]~^Q1Ϡ3KYjbyL.vyYUڴ~FjNEkrf P`9꺊r;n/}8,+ ;:T(Bx!+hu_}u{ XWJ%՝u{&]ڄ6֭:U(e a^R`@M5SWQw nS H-^Nu# GDF딗3G8衖+4u:pwlK;IK?~5'9^;e޽|CxV:]R ^סyљ2-tVse~O9~rz0F[]!{iOmO42VeVִG8EgT!bdXkT0#%/@9|yuxHFX_ e 8j;AntrK!v(}uFsH05 vpP5/~ᖈϵsV%cIk3M t߭ꦉ\O9UbNu||UG1,ћlw&qQsUཚLM<)>ӛgKdݨ/㵬F&e~-2fj%af-Q~̅758nd;L9121B# с]鈷n#\]`f~eywu8FIG `ɺ-b;B;5od:;Gbɩ1%"zz˫NSpu&vf-W3 en ޏoJ/Ic!Q*ڬEiJ6Lbwa ͩi})묻:mN}/Z&9tK%o3қ.NWNZ- X3`y~M6.T"FNvy~+ݻd:V\$吊/$;ݴ[M &-.!5ip/SN39wnѲiYfj h7YS^fᑚӱ#ӱܲqzJT:]ږsYCriU|% fIO6oUDrĨ&+fdvj 9ArT5lcdȤJnt"yNk]hnb^tPG[Jf|m@c\։nJ T~X(; L'SsL)?uCS0q'எnI$[NH[]󴣉,98& %u*k xa4fzDu[t Knt1!.s:)0ե3.8 bvk{7 w)tYٶPjxZ:x.ZRpĵ ē&aSàΩ#gud}v#oش032He*s|NZ,l4>*lG𠌾۬ rп&UwsVj5m ulc7{QqdʨN%vR2TGgLBz :5:WFCi廕%ۍP9פĆ-z9/( ]l/8]݈eƋҰzښsTӎ9z rOGlf1Τ^nZySNsÑߋ(ߧnjW>ߓnͪoxl/8־},r'vKGޮ{whj'Qw1u^f4wIzc??4pvL@6|)9/gQQ23?9E%`~SRL2E'^YHZ{eđ̉ ZH\``N`,Uy16337 qN (i,iTqDFZٲG( ۮA13Rׁ٠iT"Zp|OCDNqq4#|g>Nve݈#d94#aF*ɸ 'gd@:/[A?^Wn䷐mƜ$ 5n /8r!7Ԍn?{d#N՜?q?q:+F^6nv<603/&-tNwt3#s^8ݙX{.ss ))gVD`vp++N^!3ȩ)qbbƯNè9YNz\`ί{#pc&X xJU^gR?袋D2e/ڷoo4͛tAF!/q馛Q͋Ȑ0?p#EJ!~i1|p߿ҥ8w6n8~vąg]3ijэpoQZWPZO JAH'q}għ~CQFtI$G~aN>${sH?i$cm.˓>X 0Dm&yK_}9r/-ˆ #_/yiqzyiOU)'jW*뵌HMIx1q-kzꉫZpK&73nTjd3f/(8@4jȘVIfuy穏"?3^{%Zli)O>3/,͊0!kIdMwcW_äU_8_X~ŭު6/r"ǎ+/_n+k[ ŋYf rP=$w5,(|gp,ocBgdp`3ƻ1,iㆈbWp 7|-GykpD!?hV|yL%qifu)y-Ͼ;4a?;E]dLPN6$%, Yf' 5 Xˬ]]LXlQNFGu{_kȸ~wqcqr9mڴI|'otīss6. #52)7z0lX6J =`ئϝza(F¶_uv>qٳ7,{^++•W^OV{.@Κ@'sz'39kr:(;6%tJNۺZ0э^9BKpjT T2MEidh}[P⏪,/>(B)-ˠ-܀LNi_5bwTnf,,XS':r/U@Eܨz`+WiӦ,^*q狂zu47o~k<‘vҥFBD+V4 pyBPU^{ר% p$rtKM5z9"~V#2ij&?"SFve@M\8nxn̘1M$^u4!P%p%p iӦ(AJO rY!d V즫=E|T!-ZZPة͝tk Ih{.Uv<N*yqQvȻ ԉ$7E4.:;̇I`C%o! /"X3߳.T<[z$ "2>B-Z0g̗d켒ʁS%]eZ2E#K6w_rx,d/ `;`SVup1OJj4ySj2o^Qfu7=FD|F\ 'p3EA1!HCu`혎R5^pU/Y`G760=8؈g^u( jB``%m#0ۃND{Gk 7>y!x(XէM{}V\"|/R=7E s k2B2!63mq.pJ3nzjW#e/9 d%:ӑA*H7 ڨhiי|w`Xid.|q8MB)BSpcM? +ܬ*Q0 vG}2:v~ͻ~$[Q34F܊WsZңE8i^oP+]߸ 3E(\ةFy)%br%Z' sp\gqD'pr ƆL>g8sq 8ѫ_Ny=tNPyZԅ^]*ƭОWͩ,;($` wn4Y2 )~0XyWc%NkZrI:[zi93p%A:sl'm^Y d0Jf"gfTQרC4;q7fx'DW5ŧ>'[P8spYB?rye-?;¿_P@Mjtƥ@<QH봋{oثO 3: 5)Hq.Idi4b[_Q.%~w4wgb&{ X.lDsw@Sv#ՄlT~x.AbcwO@a<4np:u~8CH^ٝ&F1^sp?~0@'w'0sI!5yD5mq::8q6O\8?Y;?=ӆhiJSP'Z[-?R#0 2U^H(eQק_>˻<;0 /đ74-7hH*D%[R?Z 0(w\)ȁN]rqweL@( !O[PD,jkk0 =*R l|{曵WO(gI7h} iΤ(D/.cݰ0kcF:LS' ȫ!Fp?7vƎk:- klnՕց)6:UY4v2}-?g ^ qLXi6/*gIu8HCV<ۓvkQ=o1j}B_DvӍ쒹܊^r?٢Jȵ w4lj?o fpsuW~( ݍ󌾴 Y<=(M@wㄴi&$|A֢m3}"rQJaЪMg@ .Uzc<_!,4|޳=Iդ3C}x,}ݠsHar԰'}k\D,;E}QjNc&>YWL[hB(u6J08YMr+n/K@*@ߠLa>qd3? }bl'q "5 Tӿ(7RSFzYgU*iÄ:*yMCAG9,+),*In'c/Ԝ3x\+q& $_R[8 |5:r֣-~ΝժvLm-!9a!.3c05)Iv`n"kvpΰ"׏01GD?4d)&`Y0L 3 Йs/{;4%-e׭@:4!<`aaȍEQ+Hn-%YGSl>CM5:{ tnd5DSYzIr:+h^=m.YI;Y@IzM81Q'q\Pq?x>џ i4xĖL҇bt@Y%7+pH>{B;$Û OUz\IDATv#2jl ,_DkAZ}a9tQą ij<$#>ݩf;T);܁Hz /7M@ه[oe$ ٘e<󌁎[8KJՈiX KUUFCvW܃|'W#,(@z>9Ita-Aѣ;O[: uKD:1PH1߆@&ͫ%}J6H;)^OL Zש _=tʺ/#E %Bj[t$w q L v(ņ-w]etXM?.EeE?^ɗw5qdr+E!2a> X&JJM/E"9 71.q@ p#M=$<᲎iJlh\7"O5k~rW@{xN& h_2BF|;|3ez`򍂎M2H/i/ǥڑnS,X:("5 OpH Qr`wS"ex;$̏~9ٍ#eh~u#oAK9HiLaHnTS+%fH/" %A2⊏sߙ)6G/6= ԙjpFC0EQPޫpH3ڨѩppWȖ}2^C&}s@Rpaa 3'` {d*qsSuRAWTR\ ԔH筢-|S&N޶wT逸}&_[' ΄DYjEmpL4l*5q?@Z CfAU ^-#mF4? ;S,w`OHJ)# %h3\nA8< KQM*g%f3GfQQU|WweBǧ]J0~ T. \PO6X !'o#qnuP!,v[ ݏژ˰Ol+f#~:?7 (Q _8Mpkś{рDp.˄˵H.q @籨Cx—VuGdmauDZN'*;,a6k%Z.N+*˖Uu)5J?bj1t M㜅{LǚZ 29"܎HmUS$;?`$Xq+v/Y*]uJ6 deNUl'ېhkoe u@OZɢ )#w,oFˋ0z g4~) pwZ0'&M^J_,-N:=}QC禞bP>#RQ'"܂`%kOK6s`ȠQf?/sgWnGb w!P@(JkCզYtI-!qs+i*Qp Nu<> Cl-@L |oxr hZx^ze}"/s'nIϋ^m |D25Z2qWł>zOZl)q|b0%[{ a0F"-3RHbF#\Qs׊ϥ<8)РjY }^)^to32 `ԑQexžc<ӗm)"q>22ډP֯]3k;A8 C Q>PspLjjLe(M:/QWnp?:~ٸ^d 7r_)(<"f\afiLVJ/5A;xtxnE -@3J M2@L.E8'px+~ [m5I;L}8p֓+Gisٖ0# *g pf ?-p^4x,65W WcO%.$ld>@)h'J 2JӀ\AGO܊< 9;yZÜ{s;UG_0CkN@8 @LT۠"km.X/MZ)&ƒ (3 bMêON%y,7QOӗ(5d /Y1# >g+F(;;m"|]ngA;,.) RFh@fxw?;;=Uv!H P c|*( PvPL!%O "OF&( @rK!4BP !3zd'_ a$2(B@>Ս_\>D^_vHPGIuBMAH)0p A JAEkp螣8;8 tSxvl^g!LCXA(_5SF(.(͒e(`@g(a@ Yu3 )135J2P>xV݌f d LFFU70S#QQ c%g(`ņXC!IENDB`ic08SPNG  IHDR\rfsRGBDeXIfMM*igI@IDATx]^# EP"`^{Wbr-ݫ(`WPzrevy|m6LfI23믿D2 L .jQ )1d(@ Y3 d kJ02P?~V@FLU=@6QSl {qz9TBU**Xi"*0W=CE(Z6B2óWCh4d'3)h&J& AJy$6"@XVx]1 "P*3Ngu!4C %Bsidd  Dd c$I̎BCm9/.Y,W23 Qd2bF#<{"ӧqDZRΙ8d? "@"")[9egBc `g'!L*KaP oP_XqJ ?83Byaֿ[[ĚM-Ċ [Zg؀uҥ )ڈw*-7U._Z+]JT)_FTPZTPFTB_bQrYQx9?)@0W=.%J4U tLJ}W#DnK$/(fbњ-bΪMbF_ν t@Q́ DE*D熕DDEsՑd!p0  AAh2 :\7B-.(~V|*1~hf}3 BenݛVh^EWQTÌ"{O~E f@BAl8Ծ7B/ZsMI+SV6y6 2 vM wj]k3|h0a$`&@ğ"!@`ʒ 3VioR|;W7VNs5qn:)Tۖt@!x0("c}L!>#}_}5ˍܟL])^}=oѝ )@ U uqk*|F@AIAz@8ϳӿ5nxbg8nu9=B듰pvg>/n˃ dd|Y9r}z-J1<+жnEq> ęBWpygBN1&H^?E( 9i DQ zU-}>- Dxg0YJY ApZg<ۼu YD(@yAw\wC ng@`<$)@(iw;?{ef&gL܅=WX/"<&@AxL _w/Kĕ+ouM%H7h!;ӡRӑa`U`ȑ+p.i}10T띄p1xau3SuU(SJ4Q^ԁ9m:E-\W)+:a#NUl̩P֛n* m3,dzaGaqnXzJ+lbr ܇4#cWA@ z_!}8[^I cv*z5*4$6 lbJ㬵⛂5b bbMb=XxĖcJNTp(`4/Nd 9 Gŵ>g@FMîiWCئ9]M+pe~@y?!N.-#eA)\;ػdgtٹaJ#4wx˙{Va%pg=8j2|A0icg %:G# G~yi?TǨ~.5Š>c&,rc;obY&@EBf<{"\Ⱥ/`xd:.#/sDVX\\EơID춢'8 Xp-ig?'NKōS/{64x~@:n2# _?q6@S@$Cc_jsۮ-\:GUrwh*ftb)*[/ޛHEUˊuٓ LIYtb,m:9_Ў o=4aEǿ7JmBfialƢ *,1 zF=*HS~zэhx9=^A(@Fp95 i/ /8@PPکi{V8Ӝ#mkN_id?ӢVyɭČ: :H+RqgpHGE-8YJm`Iʏ"ckU^t+sNx}FjͲw.AU[sZ^LXPF Ti;?ŨsX:ypS1Y Am.ϟ>X}MCd?Эܣh҇vR[8kwm| &DvƑ.h/.G/l)m|b>uj\ v yZxV}qݽ~elLjj՝ťzn22t~)? +r 7j<#'hfE"W\ k:)b̞mLn6N%(w>K_M<ǿ6-F_^{ދs)Gy+{OA- @%hߞ\jt_juA %|QEL*GM Ӭz%;j@5rJ<.C;w`&AI}wxD E2 goJ<^iAα3q Ӏsj:?^`;SD5Ҿiy'jJQ9O4;hykT*"Pϯ?bqF t1Q2$L?DmR@[/.zG va,(@U!m4!m{;?Q@ pgvoK/׽Ie)*QL2zMN @g=:>QO[h2iO`]u[#(⬶ag~LL`&\}!y@b R4DG ;tQ7R KLS hfidD @E 9Z_Z:J( Irry @~脸7kE@mx8'|BMӗm2$K~'J~OM#?3p8nAw ?[ (O= =Q p@}ID2TA_~<[QPQB,"T i,uāgl *{jhkzQNs< P iʸث1 "@Z7wզȿIU#"/(+ P .(y@G| @E8ߢ`>ZfoPL -襂( "+9 a/s=5J +ɷ {&1Ϭ@S݁>ٚ5R qp NG8nSzշE\ P ܆v%N %*R itSz}>7NjG1YEG)xZNסB9}ht/ϦCc&Q4-ۇbFe"c@3Q:OycX k:K4/f6 ҇^A5J$ o` HА g\_J8oVN{d0V  R>Q|\/d٣q:΍dJ]h.m'0;6hjx>PeQ l @ ܓ뷊.St5}^ @ЖZ.q9AF)ЯMԸ{E 6@YH`P( ,fy wjAyeP)prtNpYN{ 7QuёqFeZ2)p]_6a3,_Ҡ |]}𥳐sЧp,[CL @t0FfiaKk+A-/thAM2ȞuK2s$j@r 2 |WWX҅qrt|N \4ڰ30 (2iX&R=fIjd()@]cI!y Nwˢox*4D<;qQε*ʱ9>/< H?JMG6O,H2`"0VJޭnA@{d1~i'luJ\4f{ǎ ]@%6btXjf%'~[֌}r}Ł[" r O݂<_`qewn)kB;\)pE~A.9~D5N-1gLI,iŒ݌vϧ6l3Vӟ׳8g$lB'% oeb=~0Ւ_'o&dnl;L=*$0q+g tN:p%Ԝ!H;̹vKc:Y (7sT_ŗ[٧U,Ftn [l oliKvw0v2~G}c jH8BƖӴ^qϑ[s-NƮu~"x2RC3BG*?3~jgEčN}V͑_AVRNt? t vjZ1Y/&~f{5tظ%^H c@_K8QK pA@C' ӗoҡ@ՔgY? 52h^cG 57>q=n77ȯV8ߛ W*Aڑoy5XWu%nu~rDs4+:+HgNLB|W͗LK(X&f:9˔2uճ(ײJ/:V.&z8ۿ^K` L^GZ҅qD{=\GN&@aX!^; ;v@iggͻ>ĝx:K|\7x%#b '6q@f3J*a -e=ܮ0+o V B]^W@b5C8!1K3޵5VnAs㥖A2>su!|w9R9xQ0sg^nHWY6pSoF;`rMh:ܢú'Eq 2)́FJBڢfΔgNl4v#1q6pp6&'@$vvd u8 ց3E:`#Ր=`eNv 8=RoZr27[*ZA0ו̔,~٣lS*hAWjqj#]< *jZd}v.LNߓ9jobwVgGؙSr4A;;pv,ߠ?S:mcg-fR_FݻjMWXN9 !g<B^G B;g2'ȯ˓6b2 ,o_ƹ]9;H|U;v;1-t˔]+Kҕ`f]NIoyeVXd05y1ל7uf~47 e- Jmĕo NRQ^۾Z}vf\\nerT}W)]^k+*#t]s/ -Z`Ò.̈UVr$++ߑ._jc$89[Ur=kȺXdN@Xi>weZc7F͗u֞Y-aErW",@,cEnߊ_ Y>KU֛]z;n= ^M5HbWd bƗIy* G`i*cB5v^fȲ$T KYUnU%{h% 2C]~^Q1Ϡ3KYjbyL.vyYUڴ~FjNEkrf P`9꺊r;n/}8,+ ;:T(Bx!+hu_}u{ XWJ%՝u{&]ڄ6֭:U(e a^R`@M5SWQw nS H-^Nu# GDF딗3G8衖+4u:pwlK;IK?~5'9^;e޽|CxV:]R ^סyљ2-tVse~O9~rz0F[]!{iOmO42VeVִG8EgT!bdXkT0#%/@9|yuxHFX_ e 8j;AntrK!v(}uFsH05 vpP5/~ᖈϵsV%cIk3M t߭ꦉ\O9UbNu||UG1,ћlw&qQsUཚLM<)>ӛgKdݨ/㵬F&e~-2fj%af-Q~̅758nd;L9121B# с]鈷n#\]`f~eywu8FIG `ɺ-b;B;5od:;Gbɩ1%"zz˫NSpu&vf-W3 en ޏoJ/Ic!Q*ڬEiJ6Lbwa ͩi})묻:mN}/Z&9tK%o3қ.NWNZ- X3`y~M6.T"FNvy~+ݻd:V\$吊/$;ݴ[M &-.!5ip/SN39wnѲiYfj h7YS^fᑚӱ#ӱܲqzJT:]ږsYCriU|% fIO6oUDrĨ&+fdvj 9ArT5lcdȤJnt"yNk]hnb^tPG[Jf|m@c\։nJ T~X(; L'SsL)?uCS0q'எnI$[NH[]󴣉,98& %u*k xa4fzDu[t Knt1!.s:)0ե3.8 bvk{7 w)tYٶPjxZ:x.ZRpĵ ē&aSàΩ#gud}v#oش032He*s|NZ,l4>*lG𠌾۬ rп&UwsVj5m ulc7{QqdʨN%vR2TGgLBz :5:WFCi廕%ۍP9פĆ-z9/( ]l/8]݈eƋҰzښsTӎ9z rOGlf1Τ^nZySNsÑߋ(ߧnjW>ߓnͪoxl/8־},r'vKGޮ{whj'Qw1u^f4wIzc??4pvL@6|)9/gQQ23?9E%`~SRL2E'^YHZ{eđ̉ ZH\``N`,Uy16337 qN (i,iTqDFZٲG( ۮA13Rׁ٠iT"Zp|OCDNqq4#|g>Nve݈#d94#aF*ɸ 'gd@:/[A?^Wn䷐mƜ$ 5n /8r!7Ԍn?{d#N՜?q?q:+F^6nv<603/&-tNwt3#s^8ݙX{.ss ))gVD`vp++N^!3ȩ)qbbƯNè9YNz\`ί{#pc&X xJU^gR?袋D2e/ڷoo4͛tAF!/q馛Q͋Ȑ0?p#EJ!~i1|p߿ҥ8w6n8~vąg]3ijэpoQZWPZO JAH'q}għ~CQFtI$G~aN>${sH?i$cm.˓>X 0Dm&yK_}9r/-ˆ #_/yiqzyiOU)'jW*뵌HMIx1q-kzꉫZpK&73nTjd3f/(8@4jȘVIfuy穏"?3^{%Zli)O>3/,͊0!kIdMwcW_äU_8_X~ŭު6/r"ǎ+/_n+k[ ŋYf rP=$w5,(|gp,ocBgdp`3ƻ1,iㆈbWp 7|-GykpD!?hV|yL%qifu)y-Ͼ;4a?;E]dLPN6$%, Yf' 5 Xˬ]]LXlQNFGu{_kȸ~wqcqr9mڴI|'otīss6. #52)7z0lX6J =`ئϝza(F¶_uv>qٳ7,{^++•W^OV{.@Κ@'sz'39kr:(;6%tJNۺZ0э^9BKpjT T2MEidh}[P⏪,/>(B)-ˠ-܀LNi_5bwTnf,,XS':r/U@Eܨz`+WiӦ,^*q狂zu47o~k<‘vҥFBD+V4 pyBPU^{ר% p$rtKM5z9"~V#2ij&?"SFve@M\8nxn̘1M$^u4!P%p%p iӦ(AJO rY!d V즫=E|T!-ZZPة͝tk Ih{.Uv<N*yqQvȻ ԉ$7E4.:;̇I`C%o! /"X3߳.T<[z$ "2>B-Z0g̗d켒ʁS%]eZ2E#K6w_rx,d/ `;`SVup1OJj4ySj2o^Qfu7=FD|F\ 'p3EA1!HCu`혎R5^pU/Y`G760=8؈g^u( jB``%m#0ۃND{Gk 7>y!x(XէM{}V\"|/R=7E s k2B2!63mq.pJ3nzjW#e/9 d%:ӑA*H7 ڨhiי|w`Xid.|q8MB)BSpcM? +ܬ*Q0 vG}2:v~ͻ~$[Q34F܊WsZңE8i^oP+]߸ 3E(\ةFy)%br%Z' sp\gqD'pr ƆL>g8sq 8ѫ_Ny=tNPyZԅ^]*ƭОWͩ,;($` wn4Y2 )~0XyWc%NkZrI:[zi93p%A:sl'm^Y d0Jf"gfTQרC4;q7fx'DW5ŧ>'[P8spYB?rye-?;¿_P@Mjtƥ@<QH봋{oثO 3: 5)Hq.Idi4b[_Q.%~w4wgb&{ X.lDsw@Sv#ՄlT~x.AbcwO@a<4np:u~8CH^ٝ&F1^sp?~0@'w'0sI!5yD5mq::8q6O\8?Y;?=ӆhiJSP'Z[-?R#0 2U^H(eQק_>˻<;0 /đ74-7hH*D%[R?Z 0(w\)ȁN]rqweL@( !O[PD,jkk0 =*R l|{曵WO(gI7h} iΤ(D/.cݰ0kcF:LS' ȫ!Fp?7vƎk:- klnՕց)6:UY4v2}-?g ^ qLXi6/*gIu8HCV<ۓvkQ=o1j}B_DvӍ쒹܊^r?٢Jȵ w4lj?o fpsuW~( ݍ󌾴 Y<=(M@wㄴi&$|A֢m3}"rQJaЪMg@ .Uzc<_!,4|޳=Iդ3C}x,}ݠsHar԰'}k\D,;E}QjNc&>YWL[hB(u6J08YMr+n/K@*@ߠLa>qd3? }bl'q "5 Tӿ(7RSFzYgU*iÄ:*yMCAG9,+),*In'c/Ԝ3x\+q& $_R[8 |5:r֣-~ΝժvLm-!9a!.3c05)Iv`n"kvpΰ"׏01GD?4d)&`Y0L 3 Йs/{;4%-e׭@:4!<`aaȍEQ+Hn-%YGSl>CM5:{ tnd5DSYzIr:+h^=m.YI;Y@IzM81Q'q\Pq?x>џ i4xĖL҇bt@Y%7+pH>{B;$Û OUz\IDATv#2jl ,_DkAZ}a9tQą ij<$#>ݩf;T);܁Hz /7M@ه[oe$ ٘e<󌁎[8KJՈiX KUUFCvW܃|'W#,(@z>9Ita-Aѣ;O[: uKD:1PH1߆@&ͫ%}J6H;)^OL Zש _=tʺ/#E %Bj[t$w q L v(ņ-w]etXM?.EeE?^ɗw5qdr+E!2a> X&JJM/E"9 71.q@ p#M=$<᲎iJlh\7"O5k~rW@{xN& h_2BF|;|3ez`򍂎M2H/i/ǥڑnS,X:("5 OpH Qr`wS"ex;$̏~9ٍ#eh~u#oAK9HiLaHnTS+%fH/" %A2⊏sߙ)6G/6= ԙjpFC0EQPޫpH3ڨѩppWȖ}2^C&}s@Rpaa 3'` {d*qsSuRAWTR\ ԔH筢-|S&N޶wT逸}&_[' ΄DYjEmpL4l*5q?@Z CfAU ^-#mF4? ;S,w`OHJ)# %h3\nA8< KQM*g%f3GfQQU|WweBǧ]J0~ T. \PO6X !'o#qnuP!,v[ ݏژ˰Ol+f#~:?7 (Q _8Mpkś{рDp.˄˵H.q @籨Cx—VuGdmauDZN'*;,a6k%Z.N+*˖Uu)5J?bj1t M㜅{LǚZ 29"܎HmUS$;?`$Xq+v/Y*]uJ6 deNUl'ېhkoe u@OZɢ )#w,oFˋ0z g4~) pwZ0'&M^J_,-N:=}QC禞bP>#RQ'"܂`%kOK6s`ȠQf?/sgWnGb w!P@(JkCզYtI-!qs+i*Qp Nu<> Cl-@L |oxr hZx^ze}"/s'nIϋ^m |D25Z2qWł>zOZl)q|b0%[{ a0F"-3RHbF#\Qs׊ϥ<8)РjY }^)^to32 `ԑQexžc<ӗm)"q>22ډP֯]3k;A8 C Q>PspLjjLe(M:/QWnp?:~ٸ^d 7r_)(<"f\afiLVJ/5A;xtxnE -@3J M2@L.E8'px+~ [m5I;L}8p֓+Gisٖ0# *g pf ?-p^4x,65W WcO%.$ld>@)h'J 2JӀ\AGO܊< 9;yZÜ{s;UG_0CkN@8 @LT۠"km.X/MZ)&ƒ (3 bMêON%y,7QOӗ(5d /Y1# >g+F(;;m"|]ngA;,.) RFh@fxw?;;=Uv!H P c|*( PvPL!%O "OF&( @rK!4BP !3zd'_ a$2(B@>Ս_\>D^_vHPGIuBMAH)0p A JAEkp螣8;8 tSxvl^g!LCXA(_5SF(.(͒e(`@g(a@ Yu3 )135J2P>xV݌f d LFFU70S#QQ c%g(`ņXC!IENDB`ic04ARGB >0Y+H2 ^* ^%h MMZ##Z =^= $==== ======== == ==m3= =wm!b ! O!R E z {̉zzz~zz=^=zy====zyz===={yzqq=q=qq=q={yz==z|z==z|z= =uzzy}z ҁ ۃӅ=^=========qq=q=qq=q====== =с̀ic14PNG  IHDRxsRGBDeXIfMM*i @IDATx]տ.һt,*-=D&&gKQ[4FcXb  R{٥?]fΛwڛrw7s9sέo>0#0BbˣeF` ?#0@ ` CfF`Xg`FH!,pyȌ#0 0#0)DN:`F`F`F I!3#0,30#R8)Y`Ar)QFȻ>Q?򯡴#b2QT"PF݌RJ`(զ&{Td&ߜRPB=wQrvRԉ}>ٔ CPo6 =n`J7;w]J)תScmJ;q+P#I!픖R߸{Hk)a+%&F/X I(u%0U}նg*4(A` )A85+eF@l`-(I&`U)45MN< JS)A)@ 01fX0`EߎTmԀ?TL@6`(ͧ4soh ƀ@`J/,wy@ L'g'VXAgJ`kpZD,L@t32;PC}߅RϖgJ6v P`%h ,<XHޜ~?J)գABtJs)ͣZ)yQdG+|;QF#%k# ;J+w@`9*#/jjJ?dg\v'#*V@|vŻ rTRtNOk]{c tH*!9U/jU(*tR.!M; ڃPPF@3d>DJ +|4V=ĄvL{bbp'm-PcƵ]WoC@pAVR$}>~,`YE!1)˷-;b wz8 dM@Ph:5"T(կ"z4&ԫ,k]CԠ2TYD=9? oEz{`XYď'>kuwسiLL64GAѱaUQj $BxnIԁi9v5^Lwy==ܠ‡^J`GPN HVoIL_Y$_]&VV+Ht8J[UVH(\PS iGi"fAl~lӧ6 0! {3}a%\meg[ŷKLuLF M*K( N^װC@ϗS L: kXp  zaO =?^E|ZED{6#SaӚgӪbt☎E:9QZC~Hi%mD?ǜ[$%?]hxeYx+3?0FlY'_ܲ8{=1S\bǔ>'@!}#A4`@,zaբZ?>nw;s6dj|&K`;~ki`x*Xf"TO_R75o@0FqAU%[J)!((J8=/EzC<э@ ۼ8G=qAajpM0,$Q |p,qzS]JX#T= i |^wk% YPK\28>C"N ځ)"rL)FO~C'}NTRg_'ޘI,pL@.@^Mep͠&FX;>THPB%8{#)â?Pg8cp LBxVW\M@kO V J Gi6%&FlNγ?)G3)߭i+)AmqMAt&B&Q爃TS` Ɛקv5`F}Glq 1>K-Duzߠ"%,L`M(e=҉j(ſ]C~ۘkO&?iF'T)4R LdgDRK܌oHuJwƏퟬo>eb҄@L]دm4 bp#|(}M5B4ϾM,eB1,p뤕)kŚϯW gdrEҶ1EFeJ,`~$ Qc+S)GWΜOB21@v`bQJoSc\U` Vע,JP{wH11w`+#qi}>B-J_#@'Weh܋9~A%__}Z,ܰ×F(@j !#[" >AinVVEŪY}oPƫ"z)a6J j;ȨoXLPDF8/Ǵ˕CqJH(R gs .bzЃ>_D x&0kY*#yF+`!;OT C')=MB * @`?}~sє<[n).{}xo.L@$@L >N@ @H(L1 ZR#)a%O Ňx 3@pHAS z$l N$LX@ @wB~_H{E3#A`xS D4(N{ 4,$W#=LGW0X,ޥ0L tʕD^E!*яڕ+Ň7ΙoV]4gX̯(}1{Sy gld:by$v>򣲴@ڟOBKrJmT K//ग़ԗ3]K),ZMi5ֳI5Q[С0HM &ͻ-Ċj\Hی$(lOx!GFTݔXI<I#K [-&x߇η KB&%ъ]W!!F_zFՌRE1Ʉ3/Cfv61myDφbN Dy*\uD C=G\4(a/Ws^' /ִjoQ'_ZG޺_(ھJb%I|O|ZG[ !BrE zf{q cxi-J ckJ'Pr=BBTLe)moI/ck'ϤM;gd{."EBX`= аz"ЛOJ}nz`?._/| AžAM3j IߓeaeL;h^LyiM1J`#?oR8`&Jo `BL.A* ~D<E-2۬8nupaEN^+^U"b .^'-{w[(>?H?I/@g؇C0Q Nns c-uĽV?B^=մ]?Spxr:͆W7ExsMSQz_# Y%Җ u*~Zܠ ;SLBF1XyUϝVԽR[)DB0h" %#4q[LJr [)"05y}_0K=ԝOWga ز @< P¯PhL` @gC?G>=0J5)R(R_ttsуLF< 3։3@>zqfnI[&Y00P@XcF<+o/~@޸*7gxY1yk_H bQPS|zqg^!m%J@QJX9z? 7Ûb|4P n ץy_? s:Ɨ)*hQ0Oo'FwXL!J8]̝` 9dzӵ (ߕo>YaDˬ;So*.?J*&F 6 7b>(Lg=CpUX Tn˧')hKclJQg3sŬpM0 cU|Lz (-(rUb bfR;]E 'j>rQ6EXH4&^JүA{d hRUw\+qmxE`"ø%E^hQҎꇠ>|YƬvtGure]e! ^-R_PG,ۊmuym$ߜ,wzN cRVA g )K!x#[KB9l \"8 l$!Ae'!`*@K40FO׭*㞞'oI2]Hab&F J|h8bE¹ob y O <>8i]ݣGQBԫ>b/WVϞV`@FxF[SEfD`]gIt&OC ,$"NH-Ez`a~)OĿ3W%%c9Xʏg+(бikI xllׯ^A! EK>-+B"k&gwJZ1q̿ 袎31㪃GEʮS x[@n;/|P\L.T@ 8Vg@ PD(د?4c'lhտQ(~g<vFw7c߭@),g!@Y(+`6JZM~C#v!E}ѪN ӄ*#n/>NUW'wgV8tۜ ZF~2 &JK/1D2!j%FvUbatFƛzz!UYA1bhL;X& )oP+oF嶢"1[G4_]ETgOg`E*Uv]3R8Zw~:`1KaBJB`}%ޙ| ky” 3qF`i qDc:K)s뻦wA>0 d!@5&`VkKF|J̿pNуL@}#PO/$ق(> [wmHR/"`,(b, pI̿ V\u%7eaAMK:K0#P;Ej:B}^&`>m hWL:ՀBn~Żl;W.B>|lG /$;)*D/NK0 M: .,XLI?5uNzUĢ?$k]\H/wh!>ET6J-X$,hvp.zW#& SXLs屺C}B!f]Cԩ{ISGٶE%U&.r^t2彙$ h,d2=H(2K'cS)׿b_c Qz-YN$PҕƉLH!7&DS(MʟMC( #7wcYNa`a@Ou"$YNu> _z~?$e'$%/1ۇ7HL#7;mXsM\}tϏ:/?ZP0T,(+kjmb k&nʑb2c͘"p={j[0K@FT%Pun`3?IG)玑-MGQ[oݻp^GQ{؁=4.K6tzwȼ,|}7܅߸ \70R&1wv-&R<>>v..N}am7aНzb #_JnwʒeDKʞ?M4_`B@p:Wco{tSv^433.0L5`1` _) my v% dwY31@(WY̿' Sm5JoC WR'ߐ.!)4b?S2"fHL#{p!ABqŇ1Dp}t-u_Y0M9= 8BJ.~XG1cO5fWO$ iB `! |3s< {@1ⲱJ!߉~O˶`(FuQBN;z?0QCT`SXjyBHTpQMd/X]ڴ,w/>ǽFYaZmMf :ȟ,T))[~vXA>4kWdL#} |IE0\@F\=C;8 z&nPRě?m"񧺕+ɿ*8HG7.>ϴue1=@bZɆ%R"yTR3Wp[:rn`\"pHdʆe/sgy?]&bV c)B ~I9/N jǐ:N`A 4t̎|xrp jsQJ :mn`CK:΄ ?^.~ZlsIҵT 1~II.*Q_=ip?ʮ-0 T=ln 3Ku6NgRRR٪DLamYM Q0@w.c#Sre ̼l37 d'Y8S#$L }jJX޾abd#0&ك0$WwJM\B _7L骳15U`Ҁs4HP]7b3\"~MRR!b%L#$_8N¬<}J8I5%4࡟TG֔2&r+ \$7ow5%%Y%.PM&FH3&f*UEO(b+J b T @%k,t_Es,b@(7|B5T0WOITD 4aGP8g^?*AxZ{V!oFH)@57I٨Ն|OzNymj5#Wb@d s;KxdCxЃ`bҎG}`H'J-,+D 4At/yBi]I3G2#/,*W\1VX_[e0zxLcQϓH@?| L+평`3xP2e"SG= gS`?%_)Tb1m 5r'F \N^OХNƤon^A`X㠟*goT<Tr'F 721#`5ř{ƍ0}gգSAQK@G ~ÿO :S}WS#'[|bJW*'v3MPRk9(̅R*V(L# ?m5S&8%.hRtJXUba:|5F7mr`L$..%Mi"OH5O &y/OF|}ekg|tX NB; ۾h[%̹ߌ#/#H؏ʁ:]ų4Kl-梅ED:lo2rf{*KPM~khG|K)HJA䉍@?h~bl3,;5>sgF mkwE%Je [XWcEG7ySdw12'ܦnrF`\#E>\ pe~ghv @ C, FTGZ4y<#@-^ YM,ݴ3iy'#/XQX}E>6U1#PǷX `~,xSi e= *JmBTLq]#_#$ h Ya4hh^lJưDZ p-U [OA9F%wl)*CR@hȏH PJJW&p_-dbF @ p.kM?+w-˅Ѵ!Wd zo9q#;EYs0IDo(7[wt< .WA/D lVIvt?<#0%@ p\qY\Ksz)xJ'X'qr3@Y>( XB_,R ԟx[AF@oDd~QHȟ#)3#<EQI"p^cH9$$u;qTVl),cҨNJ6KڴxD Ws"Ʃ3 md@/,hxF&5KH9sVEjF3# C X '\|)toa_p OɊ1#d#;9ehON]ҳBa䉔@4*9 OW8eu*"!%iF>ͪ [$Xy1NgBXG<7mSXg7XNwHW†ʙ~gJ1ZSaes;2IDpɎK w+\Sm͋n<dF ,~}XcQ  Mb߾|hgd4. 8['Z%m01#%;v|N>Qp 9Y}SX?7ˉN3)E TBeK+~Iho@)JUʃNt&;0 0yvsƪbqmsrM$cKp\-m=QٷyuѤf~}bF +7 E;&b 왘J6?(+r&W;>1\`$Y21#s䞄a*i5&[(WcƜ 4=i,_qS?[;0? 3w!Rg)EϭiH>9ϥNdqȯ(Ngx пeuQ*{7T986]ʉ_O] ,:yڕw@0##0x[̓45P2C!N㛶bXЃ0Ӻ3F ^0i:po\MS<z erCb<,s]#й&D`َ  ] ?g+@:FjV(j'FqWF ݞsJY*.Q:iHEj"{WS1'nɂ-e~gP)B{J/?Ur}1ȁd-0@Ma\U$v#2=5{7T8ck))_s $ND>4C  ?w`Up2P)no(S(a iND~-:(fռ;0_ԫ,ՂpR<09;ptnvX8؂)ܸCL7?pT]Yf(hM F 5٦duNu >])MǏ ), [_ndzmND`?qxIaag&C2wF H,{"ZX[nk(%FasӒE)*1~Fa 3 CT$ ߷!n`E`d:6 9e cai`aK~N?8N?CH+sI &4 OS_ rtsC9`r:4"_,ڪ" 0Fl$0{SU_NS=,z 6C\ja$A%J01@١va4@hV6K-a:ԝ [>ZZ͏={ }H{n,#`1CwC(7b0 VOM0_/ÀF}Ұ/VEY`Wg`X gh^:H`J altA8(>lm-xQ'CacB٫k, )kfq7l j|`@%z/ڦ|7L^-*\RmRwT`XLjZÅ+r6G5 h3F }1#7 Z>4P`~`*0iWv3V4lSggEP TJ0t`=ۛQ ĄhY ; * !p@K,"h!p<2 r.(45p6;eu*B8t2},j&@g9/#q0Σ3l=ټcXQluq=hJ' Zχ N1`a %ÔxDm޸?[rAq RcZ1hf~c3dqըb! pcoH/b8#@G:IU[wcJF7ynƻli-o֯c'NJ(|e2n2@`;#9 3l%Wk? ?y5n V#`8 =T\1JG^ o*F "{+1KN6Zlq+ X!=ymVQK44=g%NJA`1%z9ˮO|/:K'c!z tj~ JhFpT^hLC< qmr8#|y1e -+0#5-ӦѪߪu+C1˱At"Npf1}EZ49uEx08JZݦt޸UC4h 0PP!*_Ƌ 1߼??9S{` Q#;i@WNA*ϯ-jW&N3 Ԃ3R@5(ج ]%f U5q[4'z8@v[ =WG0μo> c3VhPTWa Yah]':O:EY+{l&k 4*WA27VׇhwpbX àzXy]"KUTfS<\:ᙋ\r!wlUiclO! 7J\';">ѱ#h%pcv\>9sˍ(Kgl ~MWGnKLoBҝ.վ\2Gƴ1^xYۭ죚xQGd `$4Gf!a>sodcĪKP5?9iUZ ̶M9ϻXç^!(+0sWMA }_1]z*nKԊ1MR*=i/,p\a{)!~ٵ#Gh}0_52x#k]B]&Y-Q⫪s#!TgtVWԯøVe-'1_FnSP[. bne d^bm -#av}rODϋBƎ <)ܘ(ctXVedoYP{4s`e s{m0'8e۠W>Q.5)SE۔wa^] Ne7wQiR@f-I-S5_T3ngi@IDAT-Ue>+ ɘz1[ 5rl_En.8׿cDK`+r~Lc'ti̓b@@ &ԹSeȑ IgY"?aR8{/p+fX V+cU ;1puոncW.ed ϥ@Gqi<*ٱ[PmLЧ.m1h^"7z4rtL`n]@mU11; [/L/zmJ~e/R#neUmK(UKwI+t4y_mw׾լ&y4=&5yժ $2^@#ۓ\;~$u2%uf"W1c3&s9?׫f4m 6פ{6]ݲ*!8@N Rq lPG];xϕ;TU?sȥ~L(sEkU 7,3)AbegYK߹t2\ zaϏv h^ g4׏6e~kʛʖLj{B=:[8OW!VOjzzsn*տYjWJ}6 ̤ӯ)9tzc2 8ni= @a>QȣOu9FuM3s+&gRGx$k ?g67FBYYi[GJ(WiX#H%?4[NN)hP`­N/x#7x.(Ad2"qr?V0LuU>\alr[2yMm Otbf[n1$HgVfcTghx`USЌp{<|$Ӻ \1f:as90Ό&Njksu:mũO:BUIxwYKZ)ri[]~?H'ԷTX8aXQŗ+xc:$(Wy6#߱M0nVc+nc|3~xThcb~RdI_y~ i(o/]W|֪j+$xu08K>@醊Ͽ, PO+ǯ3 7Qg,v0 <!ԴwSW\b @| dϝ{?R$8fj=~|Z =ƆPU;3{=w4eoGةW벊/e@q$̽ #}?ӆBo4GKJIt @J|V#{ZKI>ӊVmۥWksu4 V:䲰WAHͪ/@TaR-VnjX:ԔKCHTUd^8` Ɔ%2q /+`'Zkwն}3; .0mEnU?U<߫! hյBrp$i(8xa4*{ LB/s#̧<0,M^6ƍm/q&!~N VWȯ׎Yk~_bXJU\JT TCR~l LNmUynQhƏ܏gfeL(iUX}^A)ߐ8#"UVo[ FuR /I 2SZ:#L[ ́.3D~}o- 7-YI"\P˗YY-^;Q<":@"[pt5QDtCJ=‹LJƋ P/Ӫ,gHa eWDGxҝ1-[y,7 9nQ:sePYeli; xkaH>Nнe_j%q o50'^'uk,e7n sWi+o!謬u|UhH:P8Gߪy+;T+|Z8Or\ JیEuŀ;u9g"**CJVUN+ J5XZF|13*T`:RW !$(UbEr6SPw4Dg;6yT[gي:|mԳj-G7?# AӲ`$k XE p%((ҵ[藗`>q.g/TU頶 Jwr`V{XyљX Xt[}:A`|XHWq\)\Bji˷O!pnU鹚[>^klupmtIU\tAFK+c6]&uձjȧ3Xt!:B`mkԴұȾ%([Hk,ltÚ+Qk]տdfƺLmN/mݕkw[1OcElfnsZWsdφN_ewԡ-L̓r؄BZ.EC0{"yu^iܴ{!Em[5KV_FX.w8rf|w/LhW 4݌7ed1M/ _dt/?jD@ۭGD#Ɖ~Bk'^X:/Qd悇 ^κN ѭO'?lqEڂH3^̫2~ :u*ȱeԵmTXEXg2> 5[ws!ر2_oTb@]Vu1gJ]A%[=:=-Эl3( V§OX,umװ0W-. f YZ!~Yt}z}A}VTSĿK S &`F 3-V>Ѫle:ˊ=\a:v.}ѩn>Wv$q#t?ǯwˆ-E=7$stG0Y kD3"^x=i<٘<8 r?3Vg$Ϭ lYig^Ë +k\`HO0~l/` \#s^oO0#U*-uRm#iPhea@ .w'/v-eoa@A(A0~xF?էSlv02$RY=QHj7?a+{[$ygkcpe hpıWv/+M]}E,О3eWs-/'7Z6$4ב( b(ƄO΢q閣M}(sjB%#l z79BKbbۀO7BT}i%b/h8]FYIfk`W1^E a0 4,Jݨu/FÂ2(L<(&>7|h5)˶sgt1h_YBRaSQ2a1ԽqU17}: Lv5G a4^+)TfTpy6z1296~{2>e|Fa4yH,Pg^ve1-7pHllso \'^ԩTh,ʿ!H!LX5+/bI U:VTnVr.O0@:u}v*pAS{s~k]e9mO/@W-QԯQǘQ_[F'BǒDa|$aEPjP#TXc!}~aXC T:ي?۸~Q߰8!$$h96'1Ql}$ħW拹gk%u*EiR~Kuݍ^QZ`Z0ƃ0p 0}$^l 7^R]@at'"L{o>Ҽ h@G_>BM@vZF@Zj-jpfж[\> X5WnmWp~FN TKPRO89ѨQzfv)F%>@dGyD1Bkzuw*ダ^O_xqg)lȑ;AbEWA/ .-OqlmW9c 1d|饗:K`hu)߽+,^z%1o<?^ 0@|iqwÆ +egq8ӌryjn>RߢE /Awygx@+W 8/ac{ /8xJ(Eb_|߿4iџVZ[Z\{gT:[!G!-Zd0Uq?OT~ 8V}я'x¸6eC ;πZɓ'fv?,?!&)~ԥ"5*;a[Vl'@>I;i%H[wDVrcxvxQ7bΜ91uw}~i,3nJ O>DnZuQ^ڠs9X)s(]\ ^~eرcŞ=1:0+V-<쳢YfeZ,=X]w%^2ct |K` 6 3suKlyq`5/ Nru֤I1~xQ}``%_i n j/`8S^xao1f )Hvto иF4V&_v<1L#`,rWZ4,+_ aiɒ%8e6<A@$徶 KJl\!~$xF.]EN\東* 1[|:<Sꇟ@<݃BR[oذk*LjooU:dDS!;4Ӱ!GJГO>ilu-m ~Cӥ?(n:1a9˿~{\>S`xH LCʦ6"$iO6͘O$3DG}Th,"\z xWXiK{_~ U9I2!4@7|74搜\J@J@6<8n_IQح[7OCߡ41\y~q%T~I'z[ m8)ET!vpZ@-U:c&s?s~A9uZMذ*jҩjT?E<`#tBaKI# ڍ7yxpM{Y$kI&WhM ~iJX8įS@g:!j8eIEŖ7.= pªD& ϠA@eR/w}W=I$#Ev]tɗm|WJZm۶8D‘ω 5t¸e%Ybb3WE;m%mv)ѮDY _9Yq0ZL [XFu::7t&ma@VZ IUPA`1+qKCF?p"ydv+H~ VGqD )L۷=d˖U) Mj=??UfT\g~U#RMuU-m :9[A=gPi^`XI ւ Gq&-i޼6Av NieP#¶I !A1 ~Ic`…~Wﹾ =W p( "@ICBKp VnkBL0OCĊ6(C*8,XP :(dQޣ}X ҈1kvs8_~C;&?Ch>+͂Nv bЅ %W#R 4a—iP9Ǘ:*ddq>L1Fyȑx&1AWiѢEq dp#?C`VY" 2FÅUnH- GYUZ:: z BXAZԩ.هvj7JZ/Wg_߱G*}x%[-]۠@q$]K}P(~uT[Ak0:-1[_Q.{hxoٖl`OX@O X|>%!*b:G{I_(/ZT mAW4ƀ80Hm=ٻ7#cI/WO% 'Y7Dn5y?ן/O ?}|#Iu|Y2$A B,[FB2¸OVGA*YծnC@5=+,ctR6֭[' \&qH nAMQVfc u˗/7m/FY\dCKk J7Q(Zl@H+xPPP"._>O4sU?xqAzL3- Ɓr k׮A4QNA`dDX yyyFQP!=V5+;[}V#ob %qT}Dɒ[S!f A/5e@33grnIථ\>]バ!O㉀ r'y{.HjjnO@'|ۖrh$XC&?o%aSGlq" kyy :ԨZǍ.p s"UAg&L0:Ϫ-낆IB7D<*1Os }c? {. i (IEacz{/L @hoٲe?pGhCٞuW+488׷~kTٹsg!Ff}Fe4q^}YlsBkjEϦn%`Qnp&@J~acz{d?ӃFʸmxDnmӦM(xh^Rk SrB!to @ le%4nhq&tą,w%$8\4uhvv #l(LD K14:P1+u XJs1JefmΰXR F ROř _\߇wmt|\}\E/YD<-<+}$*riQ7/ܫi̜0~)C!`44K)v3qUWU.59t?Zg_R뮻.&Kې&p˶]wU[n)K_GMC馛Jqƅe)QyQh,-1t 7L ϟdC@$U%_| CߨiĈOV*:+(6nk0Bf#l}ufsw8?.s2Ge^(]><4\xHz ee> 7YC|)pxxqbQ \/VS.#ZX@ `K|4p < bCnQZi)ܯx}BWV=؇}7k^Z?N+\V'h;8腝K-Og3}I t 1# w=k="-70ĂJA\ %+Jj:5 ,k~M 31: WD k0GS PBpc#|?2bj7]B}:mOspJXoO 5Kxk$3(rkFvzI@1%[9SkVdh%P;oAT`'j)@ҖTCuIQdvYr9U"9bxNńYOѿ̞r&zFQ0" 9K3zefzNuLWuuWU^ r]x|fRE=1kPC{x 9~! #=WP9`۫~4WSVn/^~*vp 9$;5jy낡 ڤt.ܜrF=`ƷK%'e"txn y={R?ĵ O \HHtj`Q\Ɖek㤡S6_4o#1S{똄i.ve\l8qwˉy|=ʅ`jAOR0mIls"m-^9qDG,KQ6~o"E%Bn֙1>w0S|nary\U9X@w( 32lQ'3=ȅ H<ą| vaAyPeʼnuqɺx!˻n]veuO҇yy*zu8vkH3~gRAвRdL(4*;D;d}pu *!6Bo eH*֦>ED/wWZW^Q :iSVp #w[Ri`fa QNn]M? M+gM G"`n6њqdHe ~=[9 ",W^y:GLpN㉀iTJ?C*pRT)aÆgǩ')6z!uWo4^|IuܕyE(ʸ`RE|O$mPy~?vX"-`9:Mm3Pq#MySaw(Bo(^r[跩b11>v՜_Aϸ:?^xaTgv5i\yR\ ] lM\l hEivdo믿ʅ/O[]&M ^Ha%.64鸤"w]ϱ@^''.Kz饗bV0u:1̊eCu6)2b#?u梎5܊"yEef=;!-0gspl0\X4ƠYx2v^В}d

    C:CB'i_TL&Qwdn-՗x]ɖđw~/A0t#=p t姓NMp9f U|[iRPfi'~K מ&<#p;?IRk'')m;5yS&FZmno9-Nc=9s1A_:!w}Na$cLH_"d0d.@Ң^((_`ݺu ovTJ&xn_;g]a?Ҥ`ʔ?F^D__?kIGl=m/k 4a)Y.;wh$Ƣ~ꈇA#/*(YX5ktQG<$Ǥ|}cNV)5̄>sRV܏v /RCe)LP2_LQ\jQvm^~ȑhЮ/SMa%];vi3^c{̹ΤӔWU!?4ZݾNX6|Zf*"gQ|fۘÏa؏C@$Fa+iQv" TaItǪw}~LN&ҘJhoob7/IrhVIw^ [#PG!;8߽XzKRT3FnH{HkʼnR{]qD\j8{6vs1|<+E^qRQc%D{Nu?a*w7rKzI0so;0#x's kTj|3S+ym%}s/?D 饿dRYi֭`w;c&F$uEm/f~J2@  t@וcB1@+XY7js=7b?^ܸqc\ZtZzso~֛qϘ1C͞eʷxc՜9~>]6wv(T>weR{L LPKp(x4#Gvndտ[[˹jx'G,._5cǎŸc(I"^[%?z*gJ jf{th}б Qi2%]R x[0V/_9-yD^᧟~rnRJ:l*$/8yv$5Jhx8twDwnoܸjӦqU~u]JuF7,K U׭<hW[AT@4谄Ō+Q N/h|ۭ,߫Wڭԩc-ge͉ @ʕdҭݡ׼z[1yǭPI0(IB"Ta PjUu=l&Cv1zl1kͦx5,E6)(x%"aٿ+V9CI]JHI2Tz pe%N=ʀ8Hd¡⠾_&h0<ADδi:u#c߾}_ԑ*i*Ɨ`}k-tM?z-(% ]t1-v6żA92IdL^n]eAK6;n8׳gO2Ww(T#u|1 k` hg@/cǝvꩧDxyeVL{m7Z"tLMѼyB&n,uI#ӿɝfSI)irXdv5(IlDA܈;4pr9sS,ve00RB޼yN!/ otk&]ځBkށeB =9:M4ImݺZP%2&Dg Eb>Tȥ$}? e]v3}nɗ(Q }mqމٱoS&8?`֬YCEgVѺ'J6" {נAn_({mU\h_p)1ٮICKi`Ze"Nnmr1F ʲ-@)S 0}.Уmb#@ |/عcƌq С%?+dVmBgcblT3'1&پI@a`[LM?;ų^>O(;4*hU&n 7Ta| K({UG.D?﹔ɨgBh+ 7d>\V[naڴiʼ7V/6lP'>7@P`0i۶2K+La*W+(#hkFeg |7dU/:HBdO&ڵQGUOFL x\YС0K ŴB݀ށDԠp&iH@ڳ[@Upb :/w4xsk gQnYGZ4efD=}$tlg_(gjD|޸HuY?93rk'I8^B' 0 ԗ&2iB~}t}]۶9hXM_8[j񋷣ԙ qIw'ObJ_Ν;@?(TRC{Is24〘4L$?e<0>h/(0nfBX`jV3+RɔDM6u7k:۸g2k`$*hj)f=~$mKh;۪U8Ѫ uݻ+.jB0 (X1w $)soDnnjӦM팵8 C%4ib$,^E~kʖ-t^t)2{lLwZjgajhT4 EsC RLx:mѾ6b@ *1L?'eJ}с4* 0hEGd)Uz??Ո', 8u467|8*m*_g jz5_U .tz3צ2۶ݡ_+D+D?|rtRxP%K7ӤN:)m~:G]4k[2HF2!Ȃ:ǀ|4!Q t+K4FigJڡ 5gZrꩧ2: IƎ[د_?[|W6M>(,z9~/VGD ?cg!cpX4BO7/Baw@9i9w:t|~\nР:##kaO>䰜|wMSbv#x<~bcUXM=vtY>~@ d1PJjo[c T9aLl2(ֻ9g1 ;qo)DbuhDET+p 1! X#f_Ck`I%R&c%!4hX=t^g|CUA2u}_M? 4gϘ{šy\C߿zо̚5KQ'/{!yi2 _"MweսqEbʧvn~ ƱW_9cBs ۳׬Y= C#5k6?ޭxzkn[aVkzQmk3 7O L@}&U18Mc?ӐK:gۡ.9A= !G"& ]8Tƍ7nzՓ"3(3 iAYPzq?f:H+wt;n>nܹsՒ%KAUXĚuggΜts΢ g4'7I)%_F)Sٳg;YvX"50?6/}0}S @AT8"h3Iq;R xј'.h:/ Lb4 .iN<Ĉ_v4ײJ>Ș曟0d_?AfLj]:B( @:tf~RuUzڟoZ+ɼ[nų; @-'pP~7/e@hH㥵r!ų~W)^?踘QiEr N, .IH.}t;Ҏ6lؠ"Q$%X_7L"pH7k.*U{n5g%`).& /Aߊe)(>cB}3cW)mߏh&RN@DŊ_ 8[)ċ!mIw? liF>߱$äp7qXQ-vUQu듾Uy61ěRM}Q*#1@EQ37Z^#9! CԡZ8&hm.BA6 ?Cfëh:񘍦Hq'F{wv\c0#4eJ^CZ1c!^ba.@?|@Q׮]>Kp)'JPeDgT֭Uz,8p:x7Mz4^ʑ=-[T,&5g.ͿE1l7Wub}?D ,wrL[eˊZpRԨr6RY[|-h}%*b6t4(#}u.S:sy_~Ժk-XT8TtҊuy623S pucf;_l* ԦM^ r gu-Z8/hDxziˁԊ}7`qw4.)gbX0w7$#kV h0ut6*&u9@wVFt6~Ie?ۋPHSq(Hi!͈mܿz2&]@52l?SuGb}xqX*S͋zq:u0M/E%AX_~pJ\ _C֤13~ 0@g'`(djwE7pjKVUM iIayetaq|*<D\jc3,+.4 ks=BOm#Fps=ٲ}S=$$#_w^+++K]z d^Ja_zߴZY e_t)lHLFӁy&UTpGAZfMRSO=\3r GjNRW"w5wriI^tEW]uJ(wygSnS\buĿNhNoWH6HQ}}T$/mKͮh}TS HԒru06?ҭ/F(3Z:T=裑5 #!C3f81xB (2ĊL%O/"h7EոKZ6RwL}"M7\YZQCwTAޥ1T<7uRO{vi+AƝ7?uCJGfLf,䦃L-G :a vfd4iv㇧eyr GW6tD1mU. @RSkO(&v>O \PIykJ".]5su@*q/͋C54]{L/~G/ 0?O"ψ)U\(`!`ǿTUYt>zAoH&Tluߤ*`ǿ.$ +l$iiwia n1-9&1N34Y&oNYAAW-d ?) ! 5 ulk*+ x|k.T׆LбԸT?#pZ۽wk)o(1w ΁` _^ʔ{47eHq[ E7#*Dn篣|3ڻZ~뎍HoHcI 4R!\_LQGD`Ӯ}Ks̐+ `=kcEt @'*2b+ UW]ε\ Lb%p *$A>gmE5/J[0j7bS(^_2/..MkU %B"e+";XO_li5Vs5<`dpG:v}x*>#ģNkL3/D\3H_ 0jF55VyQղmk@ @< *Yvz7\[G`lQ |C 㑸}4⌦G#7OH7 8ŀ$QZ ([HÍ+"/Ad\}@dw!N06$:5s:-{#: >k` 90:ѧ\2!,UoFiٙ0˭v]p?Y)u,Axmhj6Aܖ.0ސQXO b1`B?]U hX%۵\P <:w} ` ]sk(*5F`/d@`⁗{|܇DNѕ igjnZ 25v J߱׮s/W8a1>?0V,R1 Ш8jb @!@g?S ;֪* i] } LU1xIx Pu37mRpB!KQ@(_H߸U=N*!6Pt D!C@/- }vk_0IjT䲲F/(ZZhBP)p4*MM> AzE-eET0p ҏ׭H ` S UKX 'ؤE/U6)6nhKdOKs Ss*)UԫA$- TzS ZF޲,wykn*ԟX/l?$Sf?*NyבZ6`}?ms yCs jŭKowJiA s*e;@tbO7y'E1y0$8x-˨eC" ˷:K_MB@ \7'1 lyn_iHDKØ߼-_Uzߗ]u:y(Sɠ;;wo/lOpnQSj=9mO<(2-UJ R ^D8OPvhz{2V)@ =n2PejtSlN;0OѯGc}Sr|#uc|R2NWa__ad0@h5?D{q'K?vHʭ F&Xa2r|2Q\nzUj{y6JšGH`E4?a2Υ}xN+<|k:5xG]r('5R' _# ԣH6/k:bHP ̷֞G0P ċF_Bmru,E!@ k:pgB@Q?W[W?mYh'~Ѵ̵o0UĖx!y_>=R9eՈpv3j 6T{ەν(R=#EH7b^+ `0EoM ^R3}_֪+F-Qp'daܻYeMUNj$9?'V#gҕDjhQ&~&#( J\Fpţpn텖nڭ?7[l.,^[]S| xκts7+Ղv􉭪7ijϊU,Zj_΅_  j|x4$1/rIkqPW4C}&STHc4#}\ޘ1b\غJFt{;i^n0\63^@*^t7q ޜYc}B?ಮUxn]_`*ZOkԢ бzxQ^/{Ek3^BOAz1g`8<J dkK4.AS™LX{A~jw_B5ݤmt7M p2?05 HAsY4G 샪 ]G{^_طwLuQO;pMn<~(/ 'CF"]/Q.l2xL/:gd(>ozjǬo*-(խYuHW[~AOaLP2~c5^J}$@i@xx&u; w`O3z@tm(ҁd mY_ 2wdDUT<.>h'moEm?%䂀Dt(v<5\"i]K2jS@Bp""ƩQϰE;mƂG'}kn>ҳ0]?]?>) 0X2`C#уSXPf!@. ;5(`WムwgmrYkv#_N5pD;8g_vqۄl4FCjoVX(^r_YUrCrU0~T._WPCCΚ]j֚zzĈ3)N{dן~U GL񟙎:P 73(L م4ZV5VVի-:Cy5EԦ4[ O{sRwS?x)uZSuZ ? `[thЅfvc<( r" KR"**@t\LiG $QqU{!=*{@,?sNumݽOںW޺GQĨAQKӨJYbx$@Tή?u07H#1`\.&}q/7 @bԁ4ɓXEP-hEr,i1>%>x3 6 @]Gcb H%HpHbڗ A]tH@!}p͟-S"H77DIԃS'7VYT$ pEARlvc)m0DHH_<Q-uGF~" +Dv D:T>]L xG~<:!@56˒PT@.>BڧnLuB m;W]z; p> @.c[C !ڶuˈ} %7 ~DBvinԐu囁v4 Y0~|{'a҇u? Cn ๟ר{^8b'f:ayσ)_]?!=dx Pa哲4(t2RU#kej6 ABΔNi]U]׳N"CFHOcx- FB>A@xP=<(r4!aG-Q E>}@CxrxmuIZVd ?V0nI~,N0{`?AJHQ;でXJRL!A~"o]׳MI$pᏹ/.(>KS\$\##@)H7"GJ#c=uZ)GD(h /}RC XSC8zD Q}0އF`#F{ |8c#Ɋ@4zjN5ԹPmkdgo 3XO [0zoDÅH!% u@st} \HPPZ*EDjmbҫXE*5aAO@`i@)ZQϡv9GN_@J އ52iYYӡ:EdEj\DB} &zMD {=&:`@H)J޶W}6wzgzӲmj}x YU[#_,WYTOqXQk`?;xL(&!DHd RF Mһ8r !ɏ0БI+#QW -3N8 6!mV3WBo+yա^9u o\I*~"}:i?wa"&  0I@Ŕ\ )HØph| XCQ|Z~Z p27gJfz+' i/E9,?ޘr i>Gk VC4niHM y1V}̅ppPWw}RZ2pS^a7߭QE(UPaG&\o+臷^p4WO!fBTx:R a;e>縀a ߷; hq>ǝqxyҐW,[JխZbao_jUdT3?zO-/SE0qf`J1!f\z1}rY_eK;{Cs^{hS#?Hf\?bj+IL*Lp\;#]DkzHR}O&0mڭ6haÎ}j 9UyXGG]XyӦx6d*EdJX̱S';tu+c9NrBFbڎD.slF$ LR( g"3::xNH$lv3}4)&!A c cB'<870qhmsg"FGC0JYf#G֠چrwi+"dx ^>[*R9u#@LT[؂ /A D0*k(!QEjHOa}/FF|YHӐA Bpj BjTIsG\s1>@JaJuym11%m1By|/T)S|OzsIvAH$Ȥѐ؊f*4Kl LBY$(L'.ܵFڃDEHlkdBM`hI[mG.Hd(= àW ]#dJPm{R(>ޅsw^z$.?! O+A@b!`;X0#0BbˣeF` ?#0@ ` CfF`Xg`FH!,pyȌ#0 0#0)DN:`F`F`F I!3#0,30#R8)Y`Ar)QFȻ>Q?򯡴#b2QT"PF݌RJ`(զ&{Td&ߜRPB=wQrvRԉ}>ٔ CPo6 =n`J7;w]J)תScmJ;q+P#I!픖R߸{Hk)a+%&F/X I(u%0U}նg*4(A` )A85+eF@l`-(I&`U)45MN< JS)A)@ 01fX0`EߎTmԀ?TL@6`(ͧ4soh ƀ@`J/,wy@ L'g'VXAgJ`kpZD,L@t32;PC}߅RϖgJ6v P`%h ,<XHޜ~?J)գABtJs)ͣZ)yQdG+|;QF#%k# ;J+w@`9*#/jjJ?dg\v'#*V@|vŻ rTRtNOk]{c tH*!9U/jU(*tR.!M; ڃPPF@3d>DJ +|4V=ĄvL{bbp'm-PcƵ]WoC@pAVR$}>~,`YE!1)˷-;b wz8 dM@Ph:5"T(կ"z4&ԫ,k]CԠ2TYD=9? oEz{`XYď'>kuwسiLL64GAѱaUQj $BxnIԁi9v5^Lwy==ܠ‡^J`GPN HVoIL_Y$_]&VV+Ht8J[UVH(\PS iGi"fAl~lӧ6 0! {3}a%\meg[ŷKLuLF M*K( N^װC@ϗS L: kXp  zaO =?^E|ZED{6#SaӚgӪbt☎E:9QZC~Hi%mD?ǜ[$%?]hxeYx+3?0FlY'_ܲ8{=1S\bǔ>'@!}#A4`@,zaբZ?>nw;s6dj|&K`;~ki`x*Xf"TO_R75o@0FqAU%[J)!((J8=/EzC<э@ ۼ8G=qAajpM0,$Q |p,qzS]JX#T= i |^wk% YPK\28>C"N ځ)"rL)FO~C'}NTRg_'ޘI,pL@.@^Mep͠&FX;>THPB%8{#)â?Pg8cp LBxVW\M@kO V J Gi6%&FlNγ?)G3)߭i+)AmqMAt&B&Q爃TS` Ɛקv5`F}Glq 1>K-Duzߠ"%,L`M(e=҉j(ſ]C~ۘkO&?iF'T)4R LdgDRK܌oHuJwƏퟬo>eb҄@L]دm4 bp#|(}M5B4ϾM,eB1,p뤕)kŚϯW gdrEҶ1EFeJ,`~$ Qc+S)GWΜOB21@v`bQJoSc\U` Vע,JP{wH11w`+#qi}>B-J_#@'Weh܋9~A%__}Z,ܰ×F(@j !#[" >AinVVEŪY}oPƫ"z)a6J j;ȨoXLPDF8/Ǵ˕CqJH(R gs .bzЃ>_D x&0kY*#yF+`!;OT C')=MB * @`?}~sє<[n).{}xo.L@$@L >N@ @H(L1 ZR#)a%O Ňx 3@pHAS z$l N$LX@ @wB~_H{E3#A`xS D4(N{ 4,$W#=LGW0X,ޥ0L tʕD^E!*яڕ+Ň7ΙoV]4gX̯(}1{Sy gld:by$v>򣲴@ڟOBKrJmT K//ग़ԗ3]K),ZMi5ֳI5Q[С0HM &ͻ-Ċj\Hی$(lOx!GFTݔXI<I#K [-&x߇η KB&%ъ]W!!F_zFՌRE1Ʉ3/Cfv61myDφbN Dy*\uD C=G\4(a/Ws^' /ִjoQ'_ZG޺_(ھJb%I|O|ZG[ !BrE zf{q cxi-J ckJ'Pr=BBTLe)moI/ck'ϤM;gd{."EBX`= аz"ЛOJ}nz`?._/| AžAM3j IߓeaeL;h^LyiM1J`#?oR8`&Jo `BL.A* ~D<E-2۬8nupaEN^+^U"b .^'-{w[(>?H?I/@g؇C0Q Nns c-uĽV?B^=մ]?Spxr:͆W7ExsMSQz_# Y%Җ u*~Zܠ ;SLBF1XyUϝVԽR[)DB0h" %#4q[LJr [)"05y}_0K=ԝOWga ز @< P¯PhL` @gC?G>=0J5)R(R_ttsуLF< 3։3@>zqfnI[&Y00P@XcF<+o/~@޸*7gxY1yk_H bQPS|zqg^!m%J@QJX9z? 7Ûb|4P n ץy_? s:Ɨ)*hQ0Oo'FwXL!J8]̝` 9dzӵ (ߕo>YaDˬ;So*.?J*&F 6 7b>(Lg=CpUX Tn˧')hKclJQg3sŬpM0 cU|Lz (-(rUb bfR;]E 'j>rQ6EXH4&^JүA{d hRUw\+qmxE`"ø%E^hQҎꇠ>|YƬvtGure]e! ^-R_PG,ۊmuym$ߜ,wzN cRVA g )K!x#[KB9l \"8 l$!Ae'!`*@K40FO׭*㞞'oI2]Hab&F J|h8bE¹ob y O <>8i]ݣGQBԫ>b/WVϞV`@FxF[SEfD`]gIt&OC ,$"NH-Ez`a~)OĿ3W%%c9Xʏg+(бikI xllׯ^A! EK>-+B"k&gwJZ1q̿ 袎31㪃GEʮS x[@n;/|P\L.T@ 8Vg@ PD(د?4c'lhտQ(~g<vFw7c߭@),g!@Y(+`6JZM~C#v!E}ѪN ӄ*#n/>NUW'wgV8tۜ ZF~2 &JK/1D2!j%FvUbatFƛzz!UYA1bhL;X& )oP+oF嶢"1[G4_]ETgOg`E*Uv]3R8Zw~:`1KaBJB`}%ޙ| ky” 3qF`i qDc:K)s뻦wA>0 d!@5&`VkKF|J̿pNуL@}#PO/$ق(> [wmHR/"`,(b, pI̿ V\u%7eaAMK:K0#P;Ej:B}^&`>m hWL:ՀBn~Żl;W.B>|lG /$;)*D/NK0 M: .,XLI?5uNzUĢ?$k]\H/wh!>ET6J-X$,hvp.zW#& SXLs屺C}B!f]Cԩ{ISGٶE%U&.r^t2彙$ h,d2=H(2K'cS)׿b_c Qz-YN$PҕƉLH!7&DS(MʟMC( #7wcYNa`a@Ou"$YNu> _z~?$e'$%/1ۇ7HL#7;mXsM\}tϏ:/?ZP0T,(+kjmb k&nʑb2c͘"p={j[0K@FT%Pun`3?IG)玑-MGQ[oݻp^GQ{؁=4.K6tzwȼ,|}7܅߸ \70R&1wv-&R<>>v..N}am7aНzb #_JnwʒeDKʞ?M4_`B@p:Wco{tSv^433.0L5`1` _) my v% dwY31@(WY̿' Sm5JoC WR'ߐ.!)4b?S2"fHL#{p!ABqŇ1Dp}t-u_Y0M9= 8BJ.~XG1cO5fWO$ iB `! |3s< {@1ⲱJ!߉~O˶`(FuQBN;z?0QCT`SXjyBHTpQMd/X]ڴ,w/>ǽFYaZmMf :ȟ,T))[~vXA>4kWdL#} |IE0\@F\=C;8 z&nPRě?m"񧺕+ɿ*8HG7.>ϴue1=@bZɆ%R"yTR3Wp[:rn`\"pHdʆe/sgy?]&bV c)B ~I9/N jǐ:N`A 4t̎|xrp jsQJ :mn`CK:΄ ?^.~ZlsIҵT 1~II.*Q_=ip?ʮ-0 T=ln 3Ku6NgRRR٪DLamYM Q0@w.c#Sre ̼l37 d'Y8S#$L }jJX޾abd#0&ك0$WwJM\B _7L骳15U`Ҁs4HP]7b3\"~MRR!b%L#$_8N¬<}J8I5%4࡟TG֔2&r+ \$7ow5%%Y%.PM&FH3&f*UEO(b+J b T @%k,t_Es,b@(7|B5T0WOITD 4aGP8g^?*AxZ{V!oFH)@57I٨Ն|OzNymj5#Wb@d s;KxdCxЃ`bҎG}`H'J-,+D 4At/yBi]I3G2#/,*W\1VX_[e0zxLcQϓH@?| L+평`3xP2e"SG= gS`?%_)Tb1m 5r'F \N^OХNƤon^A`X㠟*goT<Tr'F 721#`5ř{ƍ0}gգSAQK@G ~ÿO :S}WS#'[|bJW*'v3MPRk9(̅R*V(L# ?m5S&8%.hRtJXUba:|5F7mr`L$..%Mi"OH5O &y/OF|}ekg|tX NB; ۾h[%̹ߌ#/#H؏ʁ:]ų4Kl-梅ED:lo2rf{*KPM~khG|K)HJA䉍@?h~bl3,;5>sgF mkwE%Je [XWcEG7ySdw12'ܦnrF`\#E>\ pe~ghv @ C, FTGZ4y<#@-^ YM,ݴ3iy'#/XQX}E>6U1#PǷX `~,xSi e= *JmBTLq]#_#$ h Ya4hh^lJưDZ p-U [OA9F%wl)*CR@hȏH PJJW&p_-dbF @ p.kM?+w-˅Ѵ!Wd zo9q#;EYs0IDo(7[wt< .WA/D lVIvt?<#0%@ p\qY\Ksz)xJ'X'qr3@Y>( XB_,R ԟx[AF@oDd~QHȟ#)3#<EQI"p^cH9$$u;qTVl),cҨNJ6KڴxD Ws"Ʃ3 md@/,hxF&5KH9sVEjF3# C X '\|)toa_p OɊ1#d#;9ehON]ҳBa䉔@4*9 OW8eu*"!%iF>ͪ [$Xy1NgBXG<7mSXg7XNwHW†ʙ~gJ1ZSaes;2IDpɎK w+\Sm͋n<dF ,~}XcQ  Mb߾|hgd4. 8['Z%m01#%;v|N>Qp 9Y}SX?7ˉN3)E TBeK+~Iho@)JUʃNt&;0 0yvsƪbqmsrM$cKp\-m=QٷyuѤf~}bF +7 E;&b 왘J6?(+r&W;>1\`$Y21#s䞄a*i5&[(WcƜ 4=i,_qS?[;0? 3w!Rg)EϭiH>9ϥNdqȯ(Ngx пeuQ*{7T986]ʉ_O] ,:yڕw@0##0x[̓45P2C!N㛶bXЃ0Ӻ3F ^0i:po\MS<z erCb<,s]#й&D`َ  ] ?g+@:FjV(j'FqWF ݞsJY*.Q:iHEj"{WS1'nɂ-e~gP)B{J/?Ur}1ȁd-0@Ma\U$v#2=5{7T8ck))_s $ND>4C  ?w`Up2P)no(S(a iND~-:(fռ;0_ԫ,ՂpR<09;ptnvX8؂)ܸCL7?pT]Yf(hM F 5٦duNu >])MǏ ), [_ndzmND`?qxIaag&C2wF H,{"ZX[nk(%FasӒE)*1~Fa 3 CT$ ߷!n`E`d:6 9e cai`aK~N?8N?CH+sI &4 OS_ rtsC9`r:4"_,ڪ" 0Fl$0{SU_NS=,z 6C\ja$A%J01@١va4@hV6K-a:ԝ [>ZZ͏={ }H{n,#`1CwC(7b0 VOM0_/ÀF}Ұ/VEY`Wg`X gh^:H`J altA8(>lm-xQ'CacB٫k, )kfq7l j|`@%z/ڦ|7L^-*\RmRwT`XLjZÅ+r6G5 h3F }1#7 Z>4P`~`*0iWv3V4lSggEP TJ0t`=ۛQ ĄhY ; * !p@K,"h!p<2 r.(45p6;eu*B8t2},j&@g9/#q0Σ3l=ټcXQluq=hJ' Zχ N1`a %ÔxDm޸?[rAq RcZ1hf~c3dqըb! pcoH/b8#@G:IU[wcJF7ynƻli-o֯c'NJ(|e2n2@`;#9 3l%Wk? ?y5n V#`8 =T\1JG^ o*F "{+1KN6Zlq+ X!=ymVQK44=g%NJA`1%z9ˮO|/:K'c!z tj~ JhFpT^hLC< qmr8#|y1e -+0#5-ӦѪߪu+C1˱At"Npf1}EZ49uEx08JZݦt޸UC4h 0PP!*_Ƌ 1߼??9S{` Q#;i@WNA*ϯ-jW&N3 Ԃ3R@5(ج ]%f U5q[4'z8@v[ =WG0μo> c3VhPTWa Yah]':O:EY+{l&k 4*WA27VׇhwpbX àzXy]"KUTfS<\:ᙋ\r!wlUiclO! 7J\';">ѱ#h%pcv\>9sˍ(Kgl ~MWGnKLoBҝ.վ\2Gƴ1^xYۭ죚xQGd `$4Gf!a>sodcĪKP5?9iUZ ̶M9ϻXç^!(+0sWMA }_1]z*nKԊ1MR*=i/,p\a{)!~ٵ#Gh}0_52x#k]B]&Y-Q⫪s#!TgtVWԯøVe-'1_FnSP[. bne d^bm -#av}rODϋBƎ <)ܘ(ctXVedoYP{4s`e s{m0'8e۠W>Q.5)SE۔wa^] Ne7wQiR@f-I-S5_T3ngi@IDAT-Ue>+ ɘz1[ 5rl_En.8׿cDK`+r~Lc'ti̓b@@ &ԹSeȑ IgY"?aR8{/p+fX V+cU ;1puոncW.ed ϥ@Gqi<*ٱ[PmLЧ.m1h^"7z4rtL`n]@mU11; [/L/zmJ~e/R#neUmK(UKwI+t4y_mw׾լ&y4=&5yժ $2^@#ۓ\;~$u2%uf"W1c3&s9?׫f4m 6פ{6]ݲ*!8@N Rq lPG];xϕ;TU?sȥ~L(sEkU 7,3)AbegYK߹t2\ zaϏv h^ g4׏6e~kʛʖLj{B=:[8OW!VOjzzsn*տYjWJ}6 ̤ӯ)9tzc2 8ni= @a>QȣOu9FuM3s+&gRGx$k ?g67FBYYi[GJ(WiX#H%?4[NN)hP`­N/x#7x.(Ad2"qr?V0LuU>\alr[2yMm Otbf[n1$HgVfcTghx`USЌp{<|$Ӻ \1f:as90Ό&Njksu:mũO:BUIxwYKZ)ri[]~?H'ԷTX8aXQŗ+xc:$(Wy6#߱M0nVc+nc|3~xThcb~RdI_y~ i(o/]W|֪j+$xu08K>@醊Ͽ, PO+ǯ3 7Qg,v0 <!ԴwSW\b @| dϝ{?R$8fj=~|Z =ƆPU;3{=w4eoGةW벊/e@q$̽ #}?ӆBo4GKJIt @J|V#{ZKI>ӊVmۥWksu4 V:䲰WAHͪ/@TaR-VnjX:ԔKCHTUd^8` Ɔ%2q /+`'Zkwն}3; .0mEnU?U<߫! hյBrp$i(8xa4*{ LB/s#̧<0,M^6ƍm/q&!~N VWȯ׎Yk~_bXJU\JT TCR~l LNmUynQhƏ܏gfeL(iUX}^A)ߐ8#"UVo[ FuR /I 2SZ:#L[ ́.3D~}o- 7-YI"\P˗YY-^;Q<":@"[pt5QDtCJ=‹LJƋ P/Ӫ,gHa eWDGxҝ1-[y,7 9nQ:sePYeli; xkaH>Nнe_j%q o50'^'uk,e7n sWi+o!謬u|UhH:P8Gߪy+;T+|Z8Or\ JیEuŀ;u9g"**CJVUN+ J5XZF|13*T`:RW !$(UbEr6SPw4Dg;6yT[gي:|mԳj-G7?# AӲ`$k XE p%((ҵ[藗`>q.g/TU頶 Jwr`V{XyљX Xt[}:A`|XHWq\)\Bji˷O!pnU鹚[>^klupmtIU\tAFK+c6]&uձjȧ3Xt!:B`mkԴұȾ%([Hk,ltÚ+Qk]տdfƺLmN/mݕkw[1OcElfnsZWsdφN_ewԡ-L̓r؄BZ.EC0{"yu^iܴ{!Em[5KV_FX.w8rf|w/LhW 4݌7ed1M/ _dt/?jD@ۭGD#Ɖ~Bk'^X:/Qd悇 ^κN ѭO'?lqEڂH3^̫2~ :u*ȱeԵmTXEXg2> 5[ws!ر2_oTb@]Vu1gJ]A%[=:=-Эl3( V§OX,umװ0W-. f YZ!~Yt}z}A}VTSĿK S &`F 3-V>Ѫle:ˊ=\a:v.}ѩn>Wv$q#t?ǯwˆ-E=7$stG0Y kD3"^x=i<٘<8 r?3Vg$Ϭ lYig^Ë +k\`HO0~l/` \#s^oO0#U*-uRm#iPhea@ .w'/v-eoa@A(A0~xF?էSlv02$RY=QHj7?a+{[$ygkcpe hpıWv/+M]}E,О3eWs-/'7Z6$4ב( b(ƄO΢q閣M}(sjB%#l z79BKbbۀO7BT}i%b/h8]FYIfk`W1^E a0 4,Jݨu/FÂ2(L<(&>7|h5)˶sgt1h_YBRaSQ2a1ԽqU17}: Lv5G a4^+)TfTpy6z1296~{2>e|Fa4yH,Pg^ve1-7pHllso \'^ԩTh,ʿ!H!LX5+/bI U:VTnVr.O0@:u}v*pAS{s~k]e9mO/@W-QԯQǘQ_[F'BǒDa|$aEPjP#TXc!}~aXC T:ي?۸~Q߰8!$$h96'1Ql}$ħW拹gk%u*EiR~Kuݍ^QZ`Z0ƃ0p 0}$^l 7^R]@at'"L{o>Ҽ h@G_>BM@vZF@Zj-jpfж[\> X5WnmWp~FN TKPRO89ѨQzfv)F%>@dGyD1Bkzuw*ダ^O_xqg)lȑ;AbEWA/ .-OqlmW9c 1d|饗:K`hu)߽+,^z%1o<?^ 0@|iqwÆ +egq8ӌryjn>RߢE /Awygx@+W 8/ac{ /8xJ(Eb_|߿4iџVZ[Z\{gT:[!G!-Zd0Uq?OT~ 8V}я'x¸6eC ;πZɓ'fv?,?!&)~ԥ"5*;a[Vl'@>I;i%H[wDVrcxvxQ7bΜ91uw}~i,3nJ O>DnZuQ^ڠs9X)s(]\ ^~eرcŞ=1:0+V-<쳢YfeZ,=X]w%^2ct |K` 6 3suKlyq`5/ Nru֤I1~xQ}``%_i n j/`8S^xao1f )Hvto иF4V&_v<1L#`,rWZ4,+_ aiɒ%8e6<A@$徶 KJl\!~$xF.]EN\東* 1[|:<Sꇟ@<݃BR[oذk*LjooU:dDS!;4Ӱ!GJГO>ilu-m ~Cӥ?(n:1a9˿~{\>S`xH LCʦ6"$iO6͘O$3DG}Th,"\z xWXiK{_~ U9I2!4@7|74搜\J@J@6<8n_IQح[7OCߡ41\y~q%T~I'z[ m8)ET!vpZ@-U:c&s?s~A9uZMذ*jҩjT?E<`#tBaKI# ڍ7yxpM{Y$kI&WhM ~iJX8įS@g:!j8eIEŖ7.= pªD& ϠA@eR/w}W=I$#Ev]tɗm|WJZm۶8D‘ω 5t¸e%Ybb3WE;m%mv)ѮDY _9Yq0ZL [XFu::7t&ma@VZ IUPA`1+qKCF?p"ydv+H~ VGqD )L۷=d˖U) Mj=??UfT\g~U#RMuU-m :9[A=gPi^`XI ւ Gq&-i޼6Av NieP#¶I !A1 ~Ic`…~Wﹾ =W p( "@ICBKp VnkBL0OCĊ6(C*8,XP :(dQޣ}X ҈1kvs8_~C;&?Ch>+͂Nv bЅ %W#R 4a—iP9Ǘ:*ddq>L1Fyȑx&1AWiѢEq dp#?C`VY" 2FÅUnH- GYUZ:: z BXAZԩ.هvj7JZ/Wg_߱G*}x%[-]۠@q$]K}P(~uT[Ak0:-1[_Q.{hxoٖl`OX@O X|>%!*b:G{I_(/ZT mAW4ƀ80Hm=ٻ7#cI/WO% 'Y7Dn5y?ן/O ?}|#Iu|Y2$A B,[FB2¸OVGA*YծnC@5=+,ctR6֭[' \&qH nAMQVfc u˗/7m/FY\dCKk J7Q(Zl@H+xPPP"._>O4sU?xqAzL3- Ɓr k׮A4QNA`dDX yyyFQP!=V5+;[}V#ob %qT}Dɒ[S!f A/5e@33grnIථ\>]バ!O㉀ r'y{.HjjnO@'|ۖrh$XC&?o%aSGlq" kyy :ԨZǍ.p s"UAg&L0:Ϫ-낆IB7D<*1Os }c? {. i (IEacz{/L @hoٲe?pGhCٞuW+488׷~kTٹsg!Ff}Fe4q^}YlsBkjEϦn%`Qnp&@J~acz{d?ӃFʸmxDnmӦM(xh^Rk SrB!to @ le%4nhq&tą,w%$8\4uhvv #l(LD K14:P1+u XJs1JefmΰXR F ROř _\߇wmt|\}\E/YD<-<+}$*riQ7/ܫi̜0~)C!`44K)v3qUWU.59t?Zg_R뮻.&Kې&p˶]wU[n)K_GMC馛Jqƅe)QyQh,-1t 7L ϟdC@$U%_| CߨiĈOV*:+(6nk0Bf#l}ufsw8?.s2Ge^(]><4\xHz ee> 7YC|)pxxqbQ \/VS.#ZX@ `K|4p < bCnQZi)ܯx}BWV=؇}7k^Z?N+\V'h;8腝K-Og3}I t 1# w=k="-70ĂJA\ %+Jj:5 ,k~M 31: WD k0GS PBpc#|?2bj7]B}:mOspJXoO 5Kxk$3(rkFvzI@1%[9SkVdh%P;oAT`'j)@ҖTCuIQdvYr9U"9bxNńYOѿ̞r&zFQ0" 9K3zefzNuLWuuWU^ r]x|fRE=1kPC{x 9~! #=WP9`۫~4WSVn/^~*vp 9$;5jy낡 ڤt.ܜrF=`ƷK%'e"txn y={R?ĵ O \HHtj`Q\Ɖek㤡S6_4o#1S{똄i.ve\l8qwˉy|=ʅ`jAOR0mIls"m-^9qDG,KQ6~o"E%Bn֙1>w0S|nary\U9X@w( 32lQ'3=ȅ H<ą| vaAyPeʼnuqɺx!˻n]veuO҇yy*zu8vkH3~gRAвRdL(4*;D;d}pu *!6Bo eH*֦>ED/wWZW^Q :iSVp #w[Ri`fa QNn]M? M+gM G"`n6њqdHe ~=[9 ",W^y:GLpN㉀iTJ?C*pRT)aÆgǩ')6z!uWo4^|IuܕyE(ʸ`RE|O$mPy~?vX"-`9:Mm3Pq#MySaw(Bo(^r[跩b11>v՜_Aϸ:?^xaTgv5i\yR\ ] lM\l hEivdo믿ʅ/O[]&M ^Ha%.64鸤"w]ϱ@^''.Kz饗bV0u:1̊eCu6)2b#?u梎5܊"yEef=;!-0gspl0\X4ƠYx2v^В}d

    C:CB'i_TL&Qwdn-՗x]ɖđw~/A0t#=p t姓NMp9f U|[iRPfi'~K מ&<#p;?IRk'')m;5yS&FZmno9-Nc=9s1A_:!w}Na$cLH_"d0d.@Ң^((_`ݺu ovTJ&xn_;g]a?Ҥ`ʔ?F^D__?kIGl=m/k 4a)Y.;wh$Ƣ~ꈇA#/*(YX5ktQG<$Ǥ|}cNV)5̄>sRV܏v /RCe)LP2_LQ\jQvm^~ȑhЮ/SMa%];vi3^c{̹ΤӔWU!?4ZݾNX6|Zf*"gQ|fۘÏa؏C@$Fa+iQv" TaItǪw}~LN&ҘJhoob7/IrhVIw^ [#PG!;8߽XzKRT3FnH{HkʼnR{]qD\j8{6vs1|<+E^qRQc%D{Nu?a*w7rKzI0so;0#x's kTj|3S+ym%}s/?D 饿dRYi֭`w;c&F$uEm/f~J2@  t@וcB1@+XY7js=7b?^ܸqc\ZtZzso~֛qϘ1C͞eʷxc՜9~>]6wv(T>weR{L LPKp(x4#Gvndտ[[˹jx'G,._5cǎŸc(I"^[%?z*gJ jf{th}б Qi2%]R x[0V/_9-yD^᧟~rnRJ:l*$/8yv$5Jhx8twDwnoܸjӦqU~u]JuF7,K U׭<hW[AT@4谄Ō+Q N/h|ۭ,߫Wڭԩc-ge͉ @ʕdҭݡ׼z[1yǭPI0(IB"Ta PjUu=l&Cv1zl1kͦx5,E6)(x%"aٿ+V9CI]JHI2Tz pe%N=ʀ8Hd¡⠾_&h0<ADδi:u#c߾}_ԑ*i*Ɨ`}k-tM?z-(% ]t1-v6żA92IdL^n]eAK6;n8׳gO2Ww(T#u|1 k` hg@/cǝvꩧDxyeVL{m7Z"tLMѼyB&n,uI#ӿɝfSI)irXdv5(IlDA܈;4pr9sS,ve00RB޼yN!/ otk&]ځBkށeB =9:M4ImݺZP%2&Dg Eb>Tȥ$}? e]v3}nɗ(Q }mqމٱoS&8?`֬YCEgVѺ'J6" {נAn_({mU\h_p)1ٮICKi`Ze"Nnmr1F ʲ-@)S 0}.Уmb#@ |/عcƌq С%?+dVmBgcblT3'1&پI@a`[LM?;ų^>O(;4*hU&n 7Ta| K({UG.D?﹔ɨgBh+ 7d>\V[naڴiʼ7V/6lP'>7@P`0i۶2K+La*W+(#hkFeg |7dU/:HBdO&ڵQGUOFL x\YС0K ŴB݀ށDԠp&iH@ڳ[@Upb :/w4xsk gQnYGZ4efD=}$tlg_(gjD|޸HuY?93rk'I8^B' 0 ԗ&2iB~}t}]۶9hXM_8[j񋷣ԙ qIw'ObJ_Ν;@?(TRC{Is24〘4L$?e<0>h/(0nfBX`jV3+RɔDM6u7k:۸g2k`$*hj)f=~$mKh;۪U8Ѫ uݻ+.jB0 (X1w $)soDnnjӦM팵8 C%4ib$,^E~kʖ-t^t)2{lLwZjgajhT4 EsC RLx:mѾ6b@ *1L?'eJ}с4* 0hEGd)Uz??Ո', 8u467|8*m*_g jz5_U .tz3צ2۶ݡ_+D+D?|rtRxP%K7ӤN:)m~:G]4k[2HF2!Ȃ:ǀ|4!Q t+K4FigJڡ 5gZrꩧ2: IƎ[د_?[|W6M>(,z9~/VGD ?cg!cpX4BO7/Baw@9i9w:t|~\nР:##kaO>䰜|wMSbv#x<~bcUXM=vtY>~@ d1PJjo[c T9aLl2(ֻ9g1 ;qo)DbuhDET+p 1! X#f_Ck`I%R&c%!4hX=t^g|CUA2u}_M? 4gϘ{šy\C߿zо̚5KQ'/{!yi2 _"MweսqEbʧvn~ ƱW_9cBs ۳׬Y= C#5k6?ޭxzkn[aVkzQmk3 7O L@}&U18Mc?ӐK:gۡ.9A= !G"& ]8Tƍ7nzՓ"3(3 iAYPzq?f:H+wt;n>nܹsՒ%KAUXĚuggΜts΢ g4'7I)%_F)Sٳg;YvX"50?6/}0}S @AT8"h3Iq;R xј'.h:/ Lb4 .iN<Ĉ_v4ײJ>Ș曟0d_?AfLj]:B( @:tf~RuUzڟoZ+ɼ[nų; @-'pP~7/e@hH㥵r!ų~W)^?踘QiEr N, .IH.}t;Ҏ6lؠ"Q$%X_7L"pH7k.*U{n5g%`).& /Aߊe)(>cB}3cW)mߏh&RN@DŊ_ 8[)ċ!mIw? liF>߱$äp7qXQ-vUQu듾Uy61ěRM}Q*#1@EQ37Z^#9! CԡZ8&hm.BA6 ?Cfëh:񘍦Hq'F{wv\c0#4eJ^CZ1c!^ba.@?|@Q׮]>Kp)'JPeDgT֭Uz,8p:x7Mz4^ʑ=-[T,&5g.ͿE1l7Wub}?D ,wrL[eˊZpRԨr6RY[|-h}%*b6t4(#}u.S:sy_~Ժk-XT8TtҊuy623S pucf;_l* ԦM^ r gu-Z8/hDxziˁԊ}7`qw4.)gbX0w7$#kV h0ut6*&u9@wVFt6~Ie?ۋPHSq(Hi!͈mܿz2&]@52l?SuGb}xqX*S͋zq:u0M/E%AX_~pJ\ _C֤13~ 0@g'`(djwE7pjKVUM iIayetaq|*<D\jc3,+.4 ks=BOm#Fps=ٲ}S=$$#_w^+++K]z d^Ja_zߴZY e_t)lHLFӁy&UTpGAZfMRSO=\3r GjNRW"w5wriI^tEW]uJ(wygSnS\buĿNhNoWH6HQ}}T$/mKͮh}TS HԒru06?ҭ/F(3Z:T=裑5 #!C3f81xB (2ĊL%O/"h7EոKZ6RwL}"M7\YZQCwTAޥ1T<7uRO{vi+AƝ7?uCJGfLf,䦃L-G :a vfd4iv㇧eyr GW6tD1mU. @RSkO(&v>O \PIykJ".]5su@*q/͋C54]{L/~G/ 0?O"ψ)U\(`!`ǿTUYt>zAoH&Tluߤ*`ǿ.$ +l$iiwia n1-9&1N34Y&oNYAAW-d ?) ! 5 ulk*+ x|k.T׆LбԸT?#pZ۽wk)o(1w ΁` _^ʔ{47eHq[ E7#*Dn篣|3ڻZ~뎍HoHcI 4R!\_LQGD`Ӯ}Ks̐+ `=kcEt @'*2b+ UW]ε\ Lb%p *$A>gmE5/J[0j7bS(^_2/..MkU %B"e+";XO_li5Vs5<`dpG:v}x*>#ģNkL3/D\3H_ 0jF55VyQղmk@ @< *Yvz7\[G`lQ |C 㑸}4⌦G#7OH7 8ŀ$QZ ([HÍ+"/Ad\}@dw!N06$:5s:-{#: >k` 90:ѧ\2!,UoFiٙ0˭v]p?Y)u,Axmhj6Aܖ.0ސQXO b1`B?]U hX%۵\P <:w} ` ]sk(*5F`/d@`⁗{|܇DNѕ igjnZ 25v J߱׮s/W8a1>?0V,R1 Ш8jb @!@g?S ;֪* i] } LU1xIx Pu37mRpB!KQ@(_H߸U=N*!6Pt D!C@/- }vk_0IjT䲲F/(ZZhBP)p4*MM> AzE-eET0p ҏ׭H ` S UKX 'ؤE/U6)6nhKdOKs Ss*)UԫA$- TzS ZF޲,wykn*ԟX/l?$Sf?*NyבZ6`}?ms yCs jŭKowJiA s*e;@tbO7y'E1y0$8x-˨eC" ˷:K_MB@ \7'1 lyn_iHDKØ߼-_Uzߗ]u:y(Sɠ;;wo/lOpnQSj=9mO<(2-UJ R ^D8OPvhz{2V)@ =n2PejtSlN;0OѯGc}Sr|#uc|R2NWa__ad0@h5?D{q'K?vHʭ F&Xa2r|2Q\nzUj{y6JšGH`E4?a2Υ}xN+<|k:5xG]r('5R' _# ԣH6/k:bHP ̷֞G0P ċF_Bmru,E!@ k:pgB@Q?W[W?mYh'~Ѵ̵o0UĖx!y_>=R9eՈpv3j 6T{ەν(R=#EH7b^+ `0EoM ^R3}_֪+F-Qp'daܻYeMUNj$9?'V#gҕDjhQ&~&#( J\Fpţpn텖nڭ?7[l.,^[]S| xκts7+Ղv􉭪7ijϊU,Zj_΅_  j|x4$1/rIkqPW4C}&STHc4#}\ޘ1b\غJFt{;i^n0\63^@*^t7q ޜYc}B?ಮUxn]_`*ZOkԢ бzxQ^/{Ek3^BOAz1g`8<J dkK4.AS™LX{A~jw_B5ݤmt7M p2?05 HAsY4G 샪 ]G{^_طwLuQO;pMn<~(/ 'CF"]/Q.l2xL/:gd(>ozjǬo*-(խYuHW[~AOaLP2~c5^J}$@i@xx&u; w`O3z@tm(ҁd mY_ 2wdDUT<.>h'moEm?%䂀Dt(v<5\"i]K2jS@Bp""ƩQϰE;mƂG'}kn>ҳ0]?]?>) 0X2`C#уSXPf!@. ;5(`WムwgmrYkv#_N5pD;8g_vqۄl4FCjoVX(^r_YUrCrU0~T._WPCCΚ]j֚zzĈ3)N{dן~U GL񟙎:P 73(L م4ZV5VVի-:Cy5EԦ4[ O{sRwS?x)uZSuZ ? `[thЅfvc<( r" KR"**@t\LiG $QqU{!=*{@,?sNumݽOںW޺GQĨAQKӨJYbx$@Tή?u07H#1`\.&}q/7 @bԁ4ɓXEP-hEr,i1>%>x3 6 @]Gcb H%HpHbڗ A]tH@!}p͟-S"H77DIԃS'7VYT$ pEARlvc)m0DHH_<Q-uGF~" +Dv D:T>]L xG~<:!@56˒PT@.>BڧnLuB m;W]z; p> @.c[C !ڶuˈ} %7 ~DBvinԐu囁v4 Y0~|{'a҇u? Cn ๟ר{^8b'f:ayσ)_]?!=dx Pa哲4(t2RU#kej6 ABΔNi]U]׳N"CFHOcx- FB>A@xP=<(r4!aG-Q E>}@CxrxmuIZVd ?V0nI~,N0{`?AJHQ;でXJRL!A~"o]׳MI$pᏹ/.(>KS\$\##@)H7"GJ#c=uZ)GD(h /}RC XSC8zD Q}0އF`#F{ |8c#Ɋ@4zjN5ԹPmkdgo 3XO [0zoDÅH!% u@st} \HPPZ*EDjmbҫXE*5aAO@`i@)ZQϡv9GN_@J އ52iYYӡ:EdEj\DB} &zMD {=&:`@H)J޶W}6wzgzӲmj}x YU[#_,WYTOqXQk`?;xL(&!DHd RF Mһ8r !ɏ0БI+#QW -3N8 6!mV3WBo+yա^9u o\I*~"}:i?wa"&  0I@Ŕ\ )HØph| XCQ|Z~Z p27gJfz+' i/E9,?ޘr i>Gk VC4niHM y1V}̅ppPWw}RZ2pS^a7߭QE(UPaG&\o+臷^p4WO!fBTx:R a;e>縀a ߷; hq>ǝqxyҐW,[JխZbao_jUdT3?zO-/SE0qf`J1!f\z1}rY_eK;{Cs^{hS#?Hf\?bj+IL*Lp\;#]DkzHR}O&0mڭ6haÎ}j 9UyXGG]XyӦx6d*EdJX̱S';tu+c9NrBFbڎD.slF$ LR( g"3::xNH$lv3}4)&!A c cB'<870qhmsg"FGC0JYf#G֠چrwi+"dx ^>[*R9u#@LT[؂ /A D0*k(!QEjHOa}/FF|YHӐA Bpj BjTIsG\s1>@JaJuym11%m1By|/T)S|OzsIvAH$Ȥѐ؊f*4Kl LBY$(L'.ܵFڃDEHlkdBM`hI[mG.Hd(= àW ]#dJPm{R(>ޅsw^z$.?! O+A@b!`;X"====T P d#̆81~S/"F Õvcaq Ͻğmcelܜfp|ztp{srz|~vzʂzyzzzzzʀzz|zzzπz===܁zz====܀z|zz====z{Āz====܁zz====zzq=q=q=q=q=zz======zz====zĀz==΂==z{z====zz====рzz====zz{zӀ==̓ ==zzz====zz{zzźzzzz}z{zzzz{z ȸ ͂Ɋˀπ݂րց===Հ====܀ր============݀q=q=q=q=q=݀======ӂ====΂==΂======Ѐ====ހӁ======̓ ==؀Ӏ====܉։͂ ic10,PNG  IHDR+sRGBDeXIfMM*i@IDATxle eeA:" XPQ(Eb'M,)%oDMb%.Ks% g673W ~AX#Rۆ`M$/P@p wl ۀڎfmpoz@H9HlEk5vPf~,B @@ =%C3 6з m~Q KL`^!RJ`^/H v-jP@?\}4HW (hzāej q@! @=H @ 4Qy]6o+B$ (P/uCq`n%" ( $F 1NĂ w7լۨ11IV,TCfj@ F"@[Q-ތ>@#xL`g€9%4P1t .ҥ' 4~vkmlo~K57S֩~?+?}j [4qmq/5 ɅƺcgK<7j$?& ij'o#Xh@%Q  ;wy 4 `_>cloPkF ml_ylR#6>Tk +6Պ Y;*a9%/اv?W:eFG1kij~f+&VR[€`@ !!I3 xA@g6o8طf Ynn6]alA ~^A,6)pvL^A$5E`?-(-/^{Sg,,?t_אTG|iX ۂj,2Ke7R!b c3c &iZK,։ ;"6&qo z cJgT3a` lP@J-s</˽-Yzm a`PZ;[mqEk7WUem Iۀ_KUo-'A ?7h$Gڕ "*TѤvu]1H[mj( &>4A0v'jLTcBp4 !xBd@ xN}ZVfNuv`ӳ!Ӄ mPo{y7Ekt~mfm}e ߊ.46*4/Ȯ i_T+@pxIӟr"VhTc/C`D<#J2 ,] k-Ej߾ 7]>NUm2{[Yύ~lm-ݛn9ۦPL$ػ]T*,%D[%jBl>6OB `p J P@; B7Wf3`H,%tPᏟW%l/yk63uO_l;AsmER Z"K+Of!(PO@&Ny#fmj9oKm d:\j|w+r@U[*觫zK lEAA"4% (@qS 4tgjèߎ[i{not8_ٲ-S~$!#P%YCŁwBmsjW/d IMR NU]!+ub@mP1@ U뤖ӄWǟr_3kgg@[r}nP+un"{ҕ-jn+H{Tc7&ea )NvJ fͱ ZTU;o'@fpO -u+9![֭=ʷ aiґ4Hk9Zjl/|kdx6g|+>%@ 0-KmTݳ=?*Mv:A&ٲ{9SZ׀ $ j嗀S;>O6]EE!q2Ki b zZA,ꓯ ?3o!_;A z4'ҥXNjD[)(;\Ij}fbtVPPw@F@q@ ؔZs5O~_ϴյ?k`C 86>vo/Ԯh[@: iت,`b䰞*CRB %)pe>%l?ٞ}ۯo?NT}3Wl* l#P>ȯ:0D vR:, *`^V {! Ϝ!$L_mw=ՎP+W UKkߌ5u2u)KCITi@ eMdEb[OЅz'7`ڸT @w A 2"4-oZ##D-TN\)TQq&ٴE d5n$mu_"6P\m:lBY{jOU ԟ@qS Ѐ@\BA-%`P9%Ow3Ϋ)K7h #͹ e8ppZG D'5!55&% (m )N@_ov\5;/acu"h !"`oG3TV/}ߎ[~ ʆ2@q^:,j&?ٻ4JԻU S+@ @H;@*o Vr:_'ϯ]A  FSV{V%5@@H@tПI PQg-aՆ-'RlQ d 0}2nY EA4Z B@)H NEHQ 5hCJLTȾyo0i%^"Ib@hH:q߿DHC*HD<}+{#8Yf۩X^\ykJ!@+OJŎ1 Oh/;<"k"A@&v |>`m \_f} O|\`|2_99 K: <{rFҽ辅SjD3@@&v  f͹_oᶷz_V2 1 N(ק\vo嫟z!im 8R  2@|6;1\@>T~v yu< Y~Km r#{eHbR>Md}]S9d @ > ."Nn?H16RͷE78/_;@ rlܿ&rӐ62P6>9Kj}&  w!@|6;1\D>DHjP+U%|p\\y}j_'S@@0!`ﶅr!xl2)MLVNM> ##M@ #OHqj窵U%8yܪ38/B@2_=ճTv!5N HKԫM@ !MoR;Em/5_6"+o,k@vFK< 'TJkσ9 |E>`x &0@|6;1\D>0[k3QY!jL3r7K7h@Mkn-T(Ejw NF\@@&v &2mjg>55PgozcL^_ @I R "qPF&v $HS'js6z 3W@P!`6!؟jT@ߍ{\}D&v #Gswکjh/^(SX^ | [ˑ=Jia/ݫf6}p~@H lbwb(PlM9DfV߳_keK J޿]\+Y"ٞ8OdN960ОD 'M@ iN@EoOT+T40i\ЕJ;-wj/黳m @ eG&v 4">_ܰE{n<Պ4BU3@1UZWٺřmׄUbϏ ˂59diUL\@Jӎ='<\8eJ'`ˁ^QG]}Ip*qY!gCEOdj.tm |'P3Wu~VJ޳TI26aJl׋˧dS$3zV=?:jO{tyvI >"lbwb(9}rjWu~D}X16o}[KZ,.׏E"rL\QF`"=-,u{f V{\}^ad@ > .BJ@rڙj#!"ЭEyD'b ecd@o@|6;1\@ݬ;՚y~B dCV$'`GKզ@ɱ} _3"- r7kj?90.T䰮%Ҭchm5=#C=H!5lbwb(H1kUy͌J2rzRݦPt(woZ?=jN-!s,|aJy{66!w@8\3\?5ϩJ^%/A%@&v sfNz/#`3m𛓨:'@ ā'7* eݚS'vxo"gCE 9kj;حYѽDFCw+vV'`*Mȧ"NG@ G3G$/۳Wܪȕ6{K >Olbwb(uf* KC_T^(IҜי B azVɫSWsTj*;'\qrJ_O]MԆܭf.Q1w%@|6;1\DYqO0!K.?Py8`D/ەvȵG/O*-T(Oݮ& @|d Pp}`bQmo-[\r0Etե]&},Vp_:-k6 g27m'W.̣s8}@F%=M@ >Uf?@mjH^''}\Q$D~DݳyeҺ8Q"tn<8~iCˤ#(W{©]SYx6R+jke^ > .|"rzڥjmՒ6tɿ#/K:/2HOvj"LR)/a?=Zg N͋S~\nV6IDh J0˂5SY ʆ@\tKHwҴ i88K:so [lbwb(؎>\jWmǿ#l_Q~Mrh Q3c\y\dTfPcږȓ;ˠb/Zs@/(I;  > .MvZ[½ʆjK d|]suf L`* iu8K@~A亃ۈmK2|/wȮ!'ⳉAࢎ>T:jg%e4y3֎"@t L_QnЭO~~ rڵD9BZ$[ɚ~V> @|6;1\(}~R;!Y vl:Kg!@D`-r{ KfePצuSlK/t؁bI[r8|HB; >^CijB5o˅#3n{Gw=(ī'vNw w"`'LptdO 'nA՟r'1@|6;19F\6m|l:K*tL2{ iLCQ%`Bf_# ߁{_ `~z'[5?!ٌH >]ߪcx߬Kob~t((/L6Pq@^jox}B0#im)#:Ɉ^5^30瀜,G  PDBS\v2jfiY* @/&\yfJ _$D ?4\sPdrB@B  ~!gC }`x‚*TY:2yYrŠVr6,@so\#W:O>Nѯy_$2oP{A[N: Dqw@|6;1+ƪ%O,SfD G;sޅө@L8驙`"%&ر,mn}jqT^@|6;1{ O2jIv?׈1BVr߱d'@;_&c_-6qt;JxԮOuLl >plbwb(2B{?C}`Xu$ @yQ|h;9 D@Hwe`hR:SBN+i>]W0]"# /ԖT-)ؖ>4KEvFrRf q՜BH&l1}MzTZfg1/kl> 1&K2A w2>ٟ-Oz; e}Swwy'*QQU@ TI*9yVw<):pj [kD~B ٴEqѤcY ~_^zߖ8rͿsK G~sD{^2KlH@ҹX\qD;):z@]!=8EOT*;T, 2lH=[FQ퉧I2iv-ڵ6mJЇA[-FK\EJK]2E4s;2$I @ yeYI]jHn |e!R~:yɴ{7MSL&@ <N@Ss}_>]*FwV".%l"5I @/-`e}ڃ[K!LXS4yqkm>a#r mQ>a݅%>7K6T3wG0TӻIWiJ A}}d`&fu8陵kluGHERfR>|$)QtUSV#V+ߜO`:ӈNҭ]o$5 .y,-~![!IPjau&  vzT6`~a-2 N{pj2'<142B 3}knV35U%2O]IKn=@ "`^8<{nRjV~2{UV\I9~(|]L_!P7BtZۆər&']@\'g낀J@Hd{Ȉ^LRuղN7m=tՈnO~%vڥj]۬S_%u.l`mjAٍ=;ȋ:#D ϝ@cn._]GU4ɈЈpXGQTyZ4n~P%Yj6?NoX)h 5G-џ~ I*;gɤfіشF?E*5$ UCh]~z?Iaa?.R  )|!*|#JD_P~1-OF{G!@).1u;5 [o+w^2I8?1:dJ1!@  k'ϞpT`2I=n{k ק,D؞,A7o?gR ];I/ߘT=H&z>ygGS W@N]V{T`& &n9$`~o}kqc;ѝ@pΌ>b_Ӗ1\gݢ0[ه! !@`e}?~Mb@ P"N OeH9-d6쯗ӗ. @ /ऽ*A6mK5GJǽ^-Д@QgD*ߡzٿ3:ҵYn8+H @ ?Nm? c_-_VvuQuhU~:x9{ݤ,cM Z:,>쀀??KlwOqH@IDATaߠe &$S.APD@ nJ$+6IN R2 @ 90wRD%/}M$9C\}_f !b ìs?N+pD9)5v;8IF\@@J s)=SZ & neFILtZ Ad7̿ gpԟ|$Wd@0v\Q=J}ɟLG`odn_w$& '=~@Z-l5[((-*r @0_unydMVK{'5D8Oߡ&=Z%/i. 4_o Og`JΝGO dɄІ+?EUU-|PߥIL@'O \ R_=]帀FndVӒ f?VT!/L^AF>>ݴ;u_/B$~j^6VO[ÿ;ofٞbuG d"1 {65mzrzbwׄw>n3 ] Di}B-r<ڲN =9}mq L|l/ .eOO\)??m ^D { )p?+eSG .] "# 9;&sz y?dn3Hޥ,tK0"A4[Hvh5׃6]2R6H]?Ԉ @@^hkt&g˷C㗹PMhl HOQ~Ɯ?." a{u6 ՕyYAI9J`KM,7ܴrcuͶ 0)}Uhպ8MkO_.w4>o/15 f_ni  pO`93db8tC/i"~7Y@~覬_P`ilU !@ mg^ii!*uz=)nOS?mتI f) IJ aȘ!@ = y='"@zv`jZ|RVpC-i"~5Y_sq(N#~t/WԺvoB 0C].;kU%k6nquaN?Z߾V6EjoX#?yRF>erm%غ@@o@QiU^Jn7Tߧ8LG@PgT}Ș \;R<匟_%G=2i2@`d2HhS ;Dtwß:}?P0@Soo]zjoRk Tɠ|#7ou>'̿Ϝ2@Ћ2cnlU.BwM{ڹHK 'wb*jR~rT1w=ԑ  8'`"z:#ŷ6oc.,퇉_Qwē3 >Q(6o9-,S=МG|6ϓ. Q!@؞+'n<:-ngp \?n9nL7ő&A 嫋{KNDF4@@\&|Z7)3wI#`fg?;MbM;}o&1i2@f҇H3-bsO|j:uu$s sˋ&<# @`*;gw&cB|}\~Dn~W} ~&Dw׭ևG&vcwHrIF*%7 i#{\CK }UkhrvBB>K }.jX^ivz !|~W3P#@@ ([M;6m#&+oqN? dV}XoTB*Wl4)}&p. !@1#{ɃU~N XQ|i2L>}z C:fC<۲Scn.qZR+A B 1{7?EVQ~R~DpC.C dHGՌXlK68MF| 4c><sAd@ ۿ\0XpGWUn I!G3t_QTͱY)^I a"Ӹ4tnH Y^>գ+ _x't x3TUhm.Qs‚*yjZ0 +@j ?GGU@lսW:Wq`O-BG9B VB8;fqK $Щ,W.*V 4~h'u'@8[L ply;8HC@A'PA#:~eL[чet_$]T @ xQ=Z >1^)CƿnaR@TVhֶIv_?[$ q$pNrț @J,?Kqt>N9i 'B| x29թoiEwRsB+- Z_0%B ) HU'`[qC8)GHx?,syLs -v|_t,9@2:6ɈЈ<0n?kkK&"~j vL/R+qyu*'I Aenv  8#:KQΨE;-5rse-NA8tJ-y"EU3 [g&hXpt)ɳ@  8!O3w鸤<{Zb4ڼi2D *b @D#{1h'p˛ drǧ٘ĜBL ĝ`X5GbyDd t/ϗz/ $K*HW (UR犝 ؗ,!hAGI4q˫[f9I+6snHl^bsJ @hZ%b+@r+5NV)#ˢ,?_5g iүmaS" @Kv+@Odl0?Yn X‡*<,li /pκUbwE8C;I@%(xK4sb<8nӆ׫9N ^Qx *?[!k<$@ #P%_ $BWʜU0 Kp8@\$ϒuYqۗyZY$eA 1ǩ#F47԰{T;XeިEɔMZ xNrEb[/OWRM =`uL85s K (%8IM }J DGtbatU˯|e,v6G#_c;!`{hF$6-KEa0NJ!@ ` T4͕[u4l)-t;nV1B@ *bf#ğ9rz{dZG ݮ} ر;4wt5 +@N[IU"7cn@,? I׃Ş'$ 78IB܀ ,OFt\C @ $"%`/zqNԋg }%ӈ9&>GtKffG'^f hۦPg3#U2~~UbuNnWRAG*'XkTs%$ls9IB܀ P$*T @;&ph|ӎ3HؼFyvӶk9MD|o x u9;IB܀ CWu7`@@|eYb (ukSW'>VԣHOpea j-vA/̖[|eӻL*慭Z (;EO5[l5`c_X/h^FEGyDfu1N|kyN7`9#K^y @ px. 5)K7C/sZǡ`D7Υfxi u=c竃Jv @Ms!mɌ\"Ao̗u@6GXW 5_p[jiaHH;qeҒ ;Ga!P%WJ3> |Һ87; 0ofwpgwi nBIe>G-Q6*ұIS @01,H1w*BEtN~E Uʛ.uv_o?9sfיSeeTIf @/8lfd]B w@Hשѱ y wAmK FUȌ-ɳVnr6grNt@HgoGbV$ tnm'{7 @%зMh[:wbxE+I䣃W=4~|8{]%4Z=H'֔@nNt+#xpR|c"Qؗ,V'3N^_"pS7jw( !Cr_)ӞQxvB;nR˦6Iwk6;IBܠ Ԉ\It⦈y[H@2J=eFch\s Ʀ Eu-ږ'ޚF>6DL VMeԞ @~}8~Iyo[L8[0P$ak @ zDU2-t5G~v jKN {{3!@?}[0||pW 2gq*'=4&/$ qS@ҸH@"p(aDPӹ[/VH3_Gh4βqJwo{׽6o=Fh*Z2®J xD,?Kң& <0nYI3m/N7qJ:4aj?r,k6;IB(P%LuG @N\^o-Nc'N8@|jardD\a:JeûȀeC @*;oHYn}s,^W!+R' 8%nfd+wHmښp|"@^v#aHN @ X.msRP2E?;qjۑpJmv6o{"Q~{*~m7B 0 0E 텲U2 ng~LB d5F @@\0UQs흽r|ёV ?n./un/#{ّ@ =?gd:eyq#Mz_fQ :/LK OP@%pNV < x:bUӸɡ:fG%r&; %$MI\9Is$@қCm][ eCm{ p\()hC$5s+qY&M RYʆ @)'p־-N +/OY%sVmUM]IvO=]TX#P+e6ėN(k,7 _ !@[e@#UٲFtTOa'jqx fF }InA @@*VMPrt޸I-iNwv%Of׌Z$J}ƍ䮣:" mڅ6T%VgMp@26Iz![g_gmέrdB*A ܧd@wc K NVR .u3RU^Cmkv-ڊ$@ |cwێL^Aޭ\[>3[Ļ@HXgoc6;:ireF6 @ яUգ\"3@Ⱦ<>\td c{5< 6@ ~sxt6u3ȌY#$qKi΢lgq;`%uO)x(wE,-#zJF d3j)b};u3oƍn'Ll˸HHH'bd1}*I=^]g3@1@U{xX|gە2bD$@$@$@$S -{]t/׹ y)9R4N^{52d7<^kb=+$  _7-~S@$֚~Sz-$l⏯v迏JLj'v^/+$   P tIJg`T*t:uֹ y)u9%˗s_ssdq36HHH L?k#-3@-G`}ex(~gRyz^,[+R#"ULj@Y T! #p{-xׁJ@8dB@x+.]s.+Kgt:J6HHH&){ͷ_>sP2 ?AfH~Nvu;OSǴ86HHH%0O[zv=KLo)u.[^ FUlvHlqX5;9@]?a" 6HHH 3r.h?S̵f1 j)?[Ν;N/&6HHH 8m>VL$Pŏ~S3Q  {[ &\zx1ٯF$@$@$@$1w^^q >oٯs9vhP5~߹򘲧V+!";ΙLF-%  5<.n HӧeL6jUƄ*P%A_9}Ycąsƕ`;HHHA[_Z6g\>:^"yWSZfȊhP6UT9 yL#tWۇ.?<ک(u$@$@$@!] jV:]8Q(0&υVtۯ߻yt 0HHHr@Κ*ƴriEW{fi[F߲2 U>Ε.r桯fgs<&SPaHHH@ 0[ \>_f^sARP1 剳] էN4 D$@$@$x@aOKpV4HWR?,_"_- t _z7~%G?F$  3;j=^-3d"(QyUW=Գg@hs]Ƶ7]y3?y 8OibdIHHH |w=M?CC:/z^ j4҆mJyڛNrAl 3_C-e)?n0ITca؊RO+c&tOlK+[gkHHHH @/\8tIn=$Zղ߬c!f%HHH |8og"~r߯%_|taIR|3I+zZ6HHHG`ɌN1ښx<8$v Î@@V}%i{V6HHHJ୧Nko}dQ@Pplb5˷gɗ mMʊ~v&    ,٘H:hc ) PkPWz&5`l- @. L$P#/*9ZYy9F>Y=z uijN,.;emcHHHHJ }1JSD`H?/>.Qj [eWwfq5jl- @^ \ruU}bpX 7j ,\gwuxwVö !@3:pxJ>1.EyFѧ| ᭋ'夷& A4c ?ߺ 4ŋ")8:LSzd@&l<[M$@$@$@9%@9xn# OGJ[.B@Uևod:|l7 @N ,3Ntro)F%eSh~V:*E=v '   \(Ze!7,_v|)!_Yrߗ]W-j\xHHH\'S1ʪ}Fo@#-Tּ y@\jQ~W5+9H- )1vpX$   P&@3eT?A~N's(y,Vޣ:[BϱM$@$@$@ ,N$PA`X&-s@C—*1I@ &xHHHH%oc4XM);q%),owg!K @g[{ˮ r5Z}z8ihU@漮74[g=VBh@'OM-U !dF>5Υ&pXJo IE*/I(}ز{@lqCϚ*&    \2L$PJ`h䈸_nkEyȚW@=ep?ϳy(q2}BHHHJ\HQ:~wݫIdʫ`WΌ[ڣ$pDښlD$@$@$@$\kr@`>!2٪CȧϷΎؽ5|gLTrcZRD6f- |J$@$@$@ N+~H o;$΄wg; $C4* Uo^լ(-(ZHHHH G-`3Ŏ ^b@9_rjå'wbJ6l8Գ_<;Pxω]EĶ¥/Qx-8\y @ಅ]wo +Cڼ_\TK)359/S$i>լ(}fJBgπe8[AxP,@9_w~BA0Y@X!IFג Μ)Ǝn1211Ef^ _\k\ ?v~;ȏAQ.@jDh @HHH ,'8u5{xrw;&iRz|Q&JJδҔ;Wc5[(_HkP (2dв9c`@)#=a3HHHjx)(' Sy< ˕P,Qػ6S_+:[Pm|_+U_f_-o"X:€}  -KN۾ y\>)&e-q͖7'?\F@e} ;.G5huiLg$@$@$`<=s\غL,[?9I23Uչҳh@%#L8"3 ,ܷNh? `E3e:$@$@$NJzpOVճ^G luXmj\֬?F@>}h0P+g~YLD \M,u "7,ET74"a^lyвzbos ,sGh@Rd&   Lh'OiwalQHsiu+gڱB@|*+S%vKÏo+x&s R؂z^uk!fiŶKB gJD$@$@$W0F6On@!1 K ]&#;tDyS+yHP?OU|.U'zfp1zyyHp$66i.  8JDA6Ex=aWR@>u@s9M92G_!{,h@%3m,t 킍# wGЁ A&4EU"QYC̓GF!Ud?Zt( {F-?G.% GFIW'`ȫ)9's=[qKOŲ<   2gSvN\h:*M6!΅ .G<:S<8^AdA$@$@$P <\6I>(7{wWJ: E_K;Zl(-;.)fZuVL$@$@$@lZYzaeMaď>ˎ6htߧ7Km{ٳ;qZV՗*l5˹zC`9l X"pމth 7<)5QSno:PPט*+MlnMk <_D勺f9 <~̐Tl¼pPrD9RJ\YJf) lj֦Qrx}_~&+zh#W]|P,q6dTƜHH&(&H[j>P bwV3$Dhkug P`[5"P];<$zϷ5IHH ,ڮ'HO˨L)H! 6)#bphę`6m{v}3]JПF 3;swv:eHAFQgKU7_67ڍ 찊 ^9|؎֮Z5~y% @MΥ@M89=>qX}3BCj)]eqU}Yqe^'dyYMQ:dXXŋHHH.Ϣ@]@9< ޾a՞#T̾ Q mTJwVL$5#dz uzO>+NI^H/!  ̠ܝ7$^8 ;4H! tf35`Nt+2߲)=~hcMxn߀3@EAT{x2Г2),NXPKmphx8*lT<s>Ӌn/u.Ƃ0`Ro\W8lPgA}b 2TZl'^ΛtLVX K`oYH-ן,._1u\5jJ!IҡזZbv.cAxj{So\\BQNsIdHf=s9'`uH؅$j "_{Khk;:33%Y$xSw5X@e1>`Wx]aE0&GS v[pI3&dO&3ĽkOo,KvD]:]k_gDPvH ȢɝbX{XeIvf<6xk" NײB(nO.>bʛ:/ˋo$s2Zp9$5DGծm䙾5.ý(51ͮ,`&ܓ|iX{~  hj`0)$ )#'i+nhNvI[z*!̚+K߼)׬=_Xc? rUCFۡ5t pڛ{i*gá ^ [=41I_ym6ԩ$=nSycF7Q1ԿYb11sEq50Xyqq/O:u8KDƇV ^,JzX'Wjc弯 YUd0txpO.lgan0[⫗ yp )8mza7wP۔03?KP`ROUL!H3TAte,ڕLjd34ێVL\_G`Þhc---mu b1^IǸec*yO10|ik/Q2O!^/=pƽ3 I:vqkxmP7P0T@JAP:"WUV9"yZHّ*wKRn7.4i;k6f #'ٓ X˂sQsi85A@#GhvJ0I2_T grLG `3dYB884jl Gb7Z!04rD>4R0b!@c憘U;^.DL&&QK>8ScO=;+^{X:ƒ2eYm&}]0Wl=U8:\0kw&ykqԫyn`r\iC #zlWk2Ahvco*.H "`V$iJF 6(e,QLh3Ǜ3,mQc*Au\ׯIB0Kr\ՎcKj~ Z *)u*<W-}mA6v'_03Eג2~|7z!gfv9_RmL@D_LN1*.)& )uvV6+xOzO u>\Y,;i^h'(ʋ m. 5Mo^H{yAKvXlrz@+rP:E`400k?N $clu(.SſO*쎇_O{z;gJYNϙrI0I8yJ;(v|hEIWC2֏)2T[th"ŵ1K-@j'tPl}Tgi`vU"|PQH^A!ɤD+&S\-2_>|i HY5iPL{ u2){-߄JIwےA*v(V'`V4N/K(Ṫd1u[AHd'Z&(rM]2{7%(R1\3M2JG[)c&L00k<>5k-t5Q v=G?ϋ+~;B-M}?L}F̆tocR廘d6luN3ύË2%I3mpJ; xrwJXK&I)jYZmjZXNmq8Uk4 %8KC *!aQhᚭt~y\%"(D={f2B4viΛeU^W5J ]\yJ-ޏ^3Z;?⮫"~f3@s:?Ò}n7Nnu2:eW%"ÄYcѤDu4%I~-ҩ5R|')?liv#4%wJmXHR>S0fZuTY`kME3*.އ8k0IGcJd.U?yjQR+ <]k}8|`ģ!OPj0i(O*<',n)29Y_mZ55n˰XKk&QHݮ)#Ȍ䪥+LDL1JK=Y玶x̝Ԧ@[^2fBX`@պT2O5x+tZ(H$$$h4pώ*\u\juPP^4mu_qF ʼF[d}qc2WkdpX٠3̵N> _9 Qx8䂍b4Y佬PV%h UUҵk+nI+ +qbUjlRo_uz#s,73i `?Ʒ]^JjT7%$3 ȣI$gW84es<+V_(BR0w8> DST2g(4vMNQzª?E^{Q)qfƧn+ϙ{InRSb#z0b{^ Lhi-@t?I&l|'HCZgaJ0lM4&X>596rEӚ+5iS43/5TN^ٿmyTكY3cG7|YkeslO%TWc6SKhu4F75 TJk.W{|d"-{š1.)a$|c$lA{(vCpVwrDmJ0L~e˫^&MZ|\ UŃ 6M)l(&hKXO+ @ @\pa>l0لE@CjV߳n5^:dJsDS#W/HMUWVVx^U#hu.4AޝIOL)h\~ C;"~KLxĎZC͡D>㷪3NxOsԔ0~{MZiix$?MY}O󻒔%ώ!/kȮ)ޭ}烩*)?cg42!u4m;MR`ɗ7 L@gԔHWD>l`_ϗ¤ 1t-3:3MLͲٔKT>A`!'ޤQjǩJ*LR5HSm 'lٱpɟ bM _U`\i^ۉV®+kNteH#BaT;`m"5)&[,SaӚѽ8Sz0C 0 dM"-~P i.CcFx#/̀0uؕL_yxXfFb];. SKSKL6iχKӕ4VIFJ1`?̙Mu X`2|` m(}@߁4i^2S 34^iҚv=)Giki\M&,ӼiНn;?[9>'b> 0ЦB؄-<`(\Kx BHL־/Sܳ13[v\-Z:YSt$j:tbҿ A};kWWF*+u%!􁁜xsc8Qw?zrs|̓S8@ʳI zi' ҈ϨM0F6Khݱ-^T \0 Etq  VQp(bo CK.(ߪgZZp fxL[(3cw?ڊUp@xi B{ATz<:s`wUY9_o_LXA:L:XJba A:&.+ł;T~Ӌ Uαza`*9n;yNm{}(7/:^9˝ ˎ@^/~csvƚC#@{GԴv¦݆{h7A|,&3ۨ<Fa\m^Q#SS&}egZ{;C& pLZ6 h65dZ e6*mCn<>/OZB}zɗ7<]g^+ `[`E֕W?bҡ[ش5 *KjcH "S^-zhu PPOÌiF-)1B,¡ `r7ڸ/d j? !% TC}#푡1m^׹lA ~wnlP1LIO#kKY>]%=~; l.0LN{,M=^4#XLOl95o9pL6Dpfתp-#GК}UV6yfv++uFs3 T'a{- B<,ymGkq߼FU:B[X5ݍIF}JI ,ftt,LS*`M8#5h!+5)q+=RyVj:TmU;KOvWU)y!P4l4:m:כq A.BN!,a.I`\glK:E&MjcqU vI>r>'I#si˲wm#<&m70!zTvm"8 Alr:NטQSf iV^YjQauǿ1cs'%S[&f¼/ ۬N`Њߪe]i;.հBMWi'5sL ^xŇR|QkR'I  ^q#"ItFe`)+av_(=FZ #N]T>@99հv7&&U#&8yEhv @&OsTWPJCi+M麟$lϤ!z7YT+hS;ur'0 I⑾H W4Ι4_X676`ڋfN+c8G2a"IP^$` Y1x}V)k!cЫ䋉|&l4WIǢ&&L&eqdX6 N'~b|H,vW_:Qx숚~PSs|vaL2 ?Schc $@P`-K@`x?JMSUu#+^q-b>(e9r[V{ze&?J^L?Di9 +ڏ! H@4ݵSڈ=ZtBer(sk ;vR*M-4*ɅQq=|~8|-D&%iFՆ~}י]UZ$bCz{:I_Aձ|t1_6HCzZ-ݴ$Vx@@-:ɏj+at`gRUXM4t6ۤq3^(T`fc{S!M\L~W{d3'L%"M[̊O- ku5*5(0AmKh*Z3G PX鵌%@%U>9?>lIh\mh@fSԗdA<??Mक़0YR\6p=M b"Y^vGovhTc/IesƦK4+jp,_@ha:cq*&]xoRkA5Pt;j-28>uL8nrS4J7r,t}<;GĮlLzkf֮v%4k48ZkIc-Gξ6j&Ce8)vfWcʯC4· \>rDh#fvOVn&D˳&VYmsW/̶?7~&K2{6.Pc("Ul;䛀wEtXiE jɉcҪ) IyA JWv#f z:7*K32GVX .mӇFvIi툥MPZ;Q]D$ѧ(tZسZN3)0)dZ ] 8]Y>#o$*j>( E.PˬV,v^9/O0T˧ZX~+466i2T>3jV%# } WNؤmI~$ӛ3Hq{L2. ٯ?ߓ ؠ:8"YyTF pB צ3)UGoi+#`<0r\5n]5TMk4;QVS6&Չ:\OW`#@yp4-$u5,`^ԿǜGyp=n|ԃM"J١9\xXdVɧݲz'vذѷ{EٔڦT T}:jjm}Q酅do֮xF,0q7Y 8I'!08vf@3ѯWwBP,)YVIX|xZcqPʀ0~OsΙj !b-]T9Vbw=,`0CPNt [HCb?q2TVL",W }TvNUjH>x`7 Fi|cU;jzxVWYzM˓( LܲR+VA͇Ztc4Rin/LV~MaT_8g 1XMg^$ZGR*JwYoQlrw6>'z6XRv3ޅ3t|հhjY184e[&[YKeV"7kڭ3~6h3%ǯW#ҥ^s.n [A_VÒ;x/6W9x`OigZyj} &&w*k^J[@IDATm{Vs+L:a>1Mj UIsVZm6g Z8 !0Y@ r"Nӌ\GF(P H QT>8CbasMW`Bq;O%LFu?wD>7oMST؄Uw]E58$LuQ׆I "pt9bT_}tJXٻDzX+Ab!= C7Vˋv 8 66Wa!v{P@k*J:appŖ;nv``R;$Ӹ}V>wlx]rң`> m8Y|A2Kqċ RqSYհ+ϙV>l}`R;5.~'zlw_Q%mv,d8v cjGv_;ªucq A@@@%Bg?Ւ :a`aKZm3I .ibX۸Il٦l pcs! 㰸l2k6'2ּ l,7Rz)vwv9:֪Q[Z6|?BO@ H[zu6cN&gl`V/K]@li4 tx}On-MI9!|un7A{՝W3l`S8wW,MTCB mI1 qQ\Em&mpˆFn!e~eLqԣݭ5lML ;1˯u~?N=8 $2P8~KGVx^>FŇ)szw1ƃ!b3(҇q/S xMNZ6gNyn8irNlXŜs®v?v1XGC`Y] !}]*N8E1la]!^cPÿIUK8EXu(S̗e?&\UEBuv[qpzm!E>N{/uAXB(Z:PZ&r}^W^FڟM %|;hI碨Fޟt= qoD{d!~ lUFy$Xq,Xd!|ڲiNۣ}d®?#,Ӝ[ /C.&Wkh'3_m3oAi \wcW6ݘc5WEar|Zz Z"hloapA#K@!N=ȋ 3M~uh4>l؝ƩY>G|郪v;X`cTؕ"{qra86!LDٶ#TJ!xZf~=>a1FhƩ }>*찯tv*^G`GP~-aj>Y pq3/t6<봩Q^܏Qd:=sMbjF77&-j%wdž- ?"`Ԏ?xu"TF&]pŃTa'w?EA- 7uܤ K}Å]_,;LlLk'뗉sѽ[5aTۏ',qSV>ܻ(½tFR{6aԠ0ߤ7F/_h-Nv`bY |IďdH< BK@ߖ/ྦྷ}qxp{_LYgdos%aعXqKO]φhfT~+,G&Xł&Tv<%+ƒ?l]} 6CT::jPBc=ۍ:l-AJdB->mԋH!+ϝfI`C j#6nr4d&`8~hՎxjfQ⨰M(st'wg)͋p-hFvj&rIIVBm\@)/BL8ڜy|7B!$h7^&TяbS,4jL00ūz߫L]L1'ʵVbK}BIW^Yh1*L#d)N(Ck9a;/ S%6hL*94w\_m2XȢ?u”RIU"CqܨV &[M|F}^[OTϑO$cQ(,jLC+2,fɶ@> 4Twމc DQ|S@zyTs݇9v|ۻTսh6ph#P )A>i0Mv`Y c-Z\wn>ևƚR ͒Ey[] @[IQkZQnҌVٚXe,*7;}a9kPc)bպVmT6(A,MsI^e8 >E/&T+W뎎*Lv!{窋f:;'gv9a2oR;ZY$| H8^U6ksP)y'oHzNTA-d 0Wnf Lji-.օ8D"-cۘbt Lث7nEz9 (t@{<z)ϪM+ʡɰ py[!# Sg-AĔ?BB SS$+_ωy9҆ ңc~\|?-h``2GIӪ2kn\1X< _vȪo| c-RX,F mO$@.}kpl`P>(|lD6 #5#hGX p$X ΠL ;3+&:Pv*'Z8v ]B,!Uwa7GWĢ/h ;T1;e})ɋuaW|$@8h;[O{XFROCT *NXA( to12³uox!3X%PPcJVfk_KY W4COP>ǣS` RaO-U>@d+$I&D{J~؏Aֆ/?#yHHHHUcZhq\N| ˎ tysF@~xv-;H$@$@$@$@jfI۔`qZni1׹ tlPOC1\+HHHH2"9IV@> iө~,̿|&͜@F     7 ̓4͞*XMPraFe    Ț|Xi^}(6QO>/]otxG=   ' ݻw[$M}2-㙽ba1a%)J ޽{Ŗ-[ľ} 0a8رc@ {nϞ=bÆ '7;S?Aq駋%KtoH#P)Ng Vq)0HgPƍmmm%؟-˛?c?gqB'N(OD̚5K\pb… uo~q뭷(OXb83Eq9g d[mӪ`}z C>eozӛij>[Q Ե?O|[n)̵<@U`u{]AC{ꪫG?Q|i@I~ B`eBsVf`WɧMﰀUF`xDv{_/z{{v_ŕW^)~ǃ ӟT\qé~^*nqE̳'\ o(a̞=_}}"YoڴI\}WZp_ 0}Y!w0iF^+p:g@3yx1 P\s5YuĻ.5 p48Ԃ-vl {Ys)ho9sɎ;J.6|E(9$n>J W^׋UV |5 N.̙=$2EkH3!fܜ h4A%:묂"@ sQ)x\Ц}wm/~B3\gNcՄ1FjyMK!~Uo/x57qOdA5'y{ ۶ >򑏈}c5%Go,_k׮ַk^󚊾C(u%gxz衪E~q`zyW D> x/Oh~,?\0q׊뿮8~zGG/E$@=anPOȨ@4@ܔUX-v! ,<]\2h)5띁lO2"pF {۷dm$v `qQ(y ᳰ3 E{f?Qx-;ieЎߺ;Eag8>b͚5Y >U TCy(A5h˖-+؅};Yr%x W>bwBVB ̿N8$ |i[@$?m"wQ/+MXx٢SnΕmY2<&oV zz!}]4-8'ƒpq5_B%ZP}@C\+.sL~$J#tIE~Yƒ3Tkᜭ<=ӟta~![U`.{e/{YD\e˖C@D@a/ oʏ\L44c0Jn?N%K3ǷTNf&@e ;0 $y(x8N9B.|F|k_+=O$JPW}]2@q??v֊} ,)> mciҥױ >L,D7?ӻ/c r[wƍ+pq7C`/̀uTlOl׊nno+䃲WjtY5pFа'Jʀ?XR{{{F1 8T To'.h'[J&»{q?~ |pWnE1R'O~0kP"Ftv-<=.صkWI'/H"ZEybZ,|&\lܹ: Ieq#JýJH!&NRQgB#{KH s&I~S?ѻnV=N)3G`7*c8?n~WW]zWؙo:A¥r;N@źњ.|_rY___a>ۃd`@|7wG?UNB%x?wIZiƌmщ D/<;n,Y'Lh;xX}hX$@1 4I 婘U9{oTiv+{wT-}zI10$A|ذa8կ.~̘1k--A T?Ҭe@. Lfx64P x^uC&SN ȡ?g}tgv,8{oD(OwqG By~ghC}k_a /({ժU[ڊ<-ܷM`SO=U$q\Z\V( ȊL0NJ9s=?묳Loy[ٳK@{ `2Cg͐uToS$ƞbp}Aؤ:Oz8GDo馊}مyʼn\py\0q{J)Π88XE;cE~B&+YIOnp n(xB[9Cg]TU6iP!TZW¸z#g_xC)_,qΆLtմ6mRV"3,WZRH/ע:DUal}:~r hH݅eq]2}(>$@fײW^sj6#>wHbʃ1gb[Yyv6JmCk{5;w.ij=dH^ o({챔ja1YYG+N>Xi|,^g:(#-K%gXrJ *(Lr~zNpAO3w'(&s{vggLWuuիTzgM> ,$C 0k~$75j U[~!&3ΰ^#o $@Mw+k%O(ph|WRWuK`#p:8 6#Y={bM>%;<_|O-JD`c%+?祗^j9E4 B=䓦РA"B'୽_~̓8!oi|ߓO=>}zy[kJuӦMUVV^m|a*  BaGlT]P ^ U.RiJ_~9P:ZSiȑoB"0o޼ =h<?{ n$>#&DŽr| $D#5o<-6q 'p h${1g2"5NUj׮]yj鼼Jm&!oڪ]w&TKQN'VrʏK/ #FYfn1Ŭljiƌ4{lc\k6؇DxtN.8hm/|CKy Lp)@jbQ'0/v\^J[bSD-GQFj; 'B ʹqD ]/>]Sv+H@XmG$qlq:4hifxߏkxS6`?./e@U[oHJ7 ~NKH?0n_}qtM8a7Nh iLC;03ѠAB #|y+ E`8Ykn{EW`Wu<Ȯz 0g^˾N sъ+Ndjݺ%/v^qjժt4/mx<mQ=jԨAW]u1+#ט*AS_V%$(IzRalZ$: 9^naoBE`΃tH<7nx;ӴA:@.`бcGKKX^[H~aGfs=į~+&`ϻtRc\r`-[(YYY9n4¼2eJF!*իW.B5;iD@ #Ajԩ,N]H==Şex{.utRR6L HuO?mqYRώ3Jӟbhj|`9U2dH"K$B}8alV<Ǐ'l87pE(O!mh,)|[op2v2-W\AXHgpB[PP`b| I"091_1BLj?3͚sZ|96h-a˗?n̒"0qDE4pp\N //BKdG`} D]!MGРw `?GTC>EnH<d^O ×r @#9Ĉo-X`;x`:SW\pT}`'x݇C5kƳ2 -zbv\S!Idm"jtdQUs̉4oҾ 'ʡ #'1uHױS'0AGor-96 -۵k-If'xT{v y3gZl FP vF©3T-7Gݨ(85-` 1)wqeiWnIa~%LM/؅ B6mAyGx4٥KP2nx㦰neu`)إe.]~Pt5H FxW=gr L8NЄf/[`׮]ASOhpɒ%%#`nZ@2'|r<&2BDKx7Rڵ'0fBrUkoڵB ڬR pMVՠkwGם1P;WC/p0]iر4k,7z d(17&L0f5i6 뼼<\v C #AI&Ƭ@\eSC &b|' ⨂վ}{0`@?G`Z%Ob &p8Y%ǥfXIXs&yh-oYZB#m3'3@*eݴP<[Y]͙@@(>[য়~"YSy0WZ T+e5A`}3}ENZjH(xI<SFiӦƬ@]w$sP_3--[9[M`6I"[=|ԫW/ߴi?/)U?Sm9YJP{wࡏ^ &iw"ٺ"P_ҁV|naf-B*s= fYfpBc10xفI'LDsL%q"K/dNϞ=m]=A=_~'2]Ax![NAyJ *甦5;Pct 6`"pGbG B74谛'tM7p:ײi r"K|צd* ~6̘1H"9T%D/ : 8 5 >`. SR@2"p[>"PB7036 9w6t3dc(q]i=tEAwN1+p /FBBc\gߢ-P֭3Yc!QG]r7@觚{an3Ց飏>2 "OM@@!p* N"KwW-B5(:t | GD?6Ճ9 2 X8'СCycʕo`Ӭ.g5$M̥?cz68i޼yr221__}1K3|2s4 3^Mm,ʤ rTvn ZVgB4f䖮|(:Yf;c*xf/~a j//lώ1"FI&YN̡ݦM-qclBS|jV ؜cC$y `Ft#wm BCfbn>px v;: 60sh3V6["5|TRjA z2yjرcrxH$4iig˧="dj@xsPەa:>ҭS{IEŋ[ѪUZ򃚁~u9AԨ> N;4j֬Ys!$KLc3gj+뭷Njc"疂ɀ;a9l|/ZڸqcK^3a_~sn6SPs׷o_M8t޲O99V]#s6₎rZXr%a#VCR(T?t6 #իW[g5N8t"d!)֭̎[-~ڵkG{nfKu3j_re#^ kї_~i Zyyy<&*e$P W~}Vh-Nfz_VCP(V4ծx3߆N.@`wM'Y]9ϐ!4g}x؝7oޜ=#1qqz`!`g_c*ɖT8$1lTMxI#F2}մ\Lȶmfjf8ӑ-rϟO6mұ;yʊk[u]/Yx 0XV@? LfݻiԩqDl`nݺ Ϊҙgx1W Um LPWò$b eMΪA2ÝV1ԩcyhժU" DQʕ+[袋,~&ƏOgG‰hJuaC[peطt,B:)I]i~K?k1 % c}6Afnde~QI&7<j(@b&~pFYW{!vBG0g'B i-vdp`x۪aoZ!gHg8Xrni7!aRnv@c]vwNy\%ECNjy]sO):2'@IDAT.DX9dI[fFOnj& `%].}ҙO8,,,tAg՟:<9 LN%@2AMCiE~#X>%Vʢ2m,q a;nGξ a.:N_onjժik߈0*tZ2|At P]}L$;Ul5_JM )RJv2AF5jXo1I`^bbRPCI"8]Ą†Tp-#7OkX6 /s}B8VifoՌʼnΪlu.z>TW!|gNh@NhՑ? eK.55_uf) ={X^lM<ٔ' DŽhũا =2z-]wh=v}"4%y"tXcUԹPm3f 8Ao۶)O& j¯N8);w/99 T]b'LLO P Z,:‹І >3uAtכtJ`C x7Yr`?kg}6xY%;NrlO<5K46fvsLNNBwބz#Yy\E`r*ԮEَFm`oC D S(uxbE{V9]QI,Nt%γ)q<`^2eYC%tcwl`Ŋ4e `sbZfM;'3J_R6&Q5J۩}QV a 7p|[Oa !a0 Uetww饗RVY}~20n:7oNNh<v1 Cv!xjI 5Ǒw-ؙٙ;xעԤ"%;;&AO7-Zdb':g:cB"n.]N}Xa ~c@&f{/ gF'X hDG(}ySe'əJ%3MѤ*L7l i;_*HeBc0uTZ`A S?H/eK3;nc1WmTOXf5 L0 0޼p(zM 08[#>rss-5k~R0Y=Q/_\LqmO1tPM@4 5$b@8'BeV2N;He `Euo\l;%:DwҾPL7l1ѢE B4#$G{ aNu_>*Fmʓq-[pj3Pbf5f̘ainY iaQbAWb8L+jW#ǧ`i;8Nl',@ M?lbv8LcCyDoIj gGI&"^N>@v8]UcF?`̒kw]FYz;!ji K9s&mڴx89W h3L]ML 3m?<X'?A;L% c)N/~y)ZpUWœZ&:M.~6vǕw-;Ze~ő1x;w}xZS}!楁H6gvaL$D4!Q' t=]pwpH2eCW,L cIN~v).n~C`vF=j՛4ԚqR/٠otԳh@K6g_v>aPz*(TM$ -9#AyXΗ|i>ϩcpN0=_wk[C"VvJ8oG\ f@zN N60>"*C-g[Lv1lb1I?gh*;{Xƪ~&~'8qmI1chŦL۷7霰ڙGy|}ͪfSn@6FLQrIp#zAڱ_O?8hѢE( 7pN NKӧO751Q)OD:u,+gzKAH 06mMK!M2zNI.i&pS wЁFq r-l)<ѵ^6bAZWqTN u,z`P#UU,,٘y'|ҽKបgy7 6믿ޔsN);:3O:^}Ukؘa撪/0i0իW/(,UoiXqr-,@FsF]T}a6f0NGaߎwg皽;z0Fm 9?&B^^$#L& .ZJݺuCASOa/Y<[={W\y[$V#pB?~Oo<-̟?k2V` 'd.A!r\)lhEQś3ϐщraۤ Is`Zb 699֞@LO)K+W?lNjnY4i-YP's!|MDȊ}i;  ).ʆhB7#ղ+F,ؼvjիWzH#ՠ"gO-!?Gh:^j&%K>kD #득ُ\_3LHv=.:n'llUGUL[ $#% tAj׮m1)Sԩ9 wɫq N==X:5la-Fo#ص^w$;Jwie8S%6F;o@ g)xƍiС7aHG}`_کU˔{*H0sP_5HO<``-[ҙgi ^svF= PG5]uT~"BeC[49r#LvB&`'o=qWtB gGx ` ?GԶ8tT "1bEp6cC;jI'u]G*aYÀ{{ʓ@¥;L1FJ 9MK^Rw!SH}G:a}ĄD%?VU+W sٰ yf:ލ̪NrCG68 +VmI[TY5\@1 ]Fm)ˣnU=3~tOPΌ3L`E$!$?H00<_pƬP]QD7:9T*8H?`Uu:?9A} anuS5f9A+Y陜y;̈,qWX*m֬mdE R%]I +eD[19:X3q$ Ep> +ީOl}]|IZ1+!]jʷv[KC ҬYL!Dޟ'2KBp h2SÆ C]\)0Xls@3:JI7wQާޓ0 +أ[V;tD{6T?C oW]ur-0g "!yiŦJa_ڔe8-"ҳ>Vz6Xܵ%`= ;~-=Ybذa0e@xfjZ*L]M/2fyTc- f\85/7Dvu@u I5kÍPu dNbqEuvvvq9cۼy3I-o GE-2UXJF0vsϵ@/L(%  |gԪU+z',d}v O"8AI2='DpHO*DpO(s#ɅuZ?ԩSPNb (L T?At饗³O+p8vNJz|&w}s=;6na'Tv BuY*~!@H,fLq$$ ,tS nUTL$Phѕ#3K$enD6yZ 33ƎkY,VP!i _α8+;̙)X8B&a'N {@Wa%Um͛ǴxN.uo"!Tl!V|>Gx?;a!C駟.")f}T,pAO+ct0?P LGo9v:6W{5j6-.>h@TBqE4<_~IթCR6~AtҺu;Aǥmڴ9)WvI}i4hKB@#'̶s8ҿoNKmK䤵؅۝2!G`]tjRŕQP:˕+G>MpH26h;v숅>MZr|BSH֭k̖kAX͛G6lK< gewy%+ޑњqn)2ehN~ϝ;7dnk*ի_#X7*sO=E; dqҩ_X0~_>qؑelJC.a- ;ZK (ٌ#peiJ@?;oMLр>G'o 9jg_ٝC$g qaqlD퉐B#p^|j!]8e#S4t$B@Oo39dqI~BO?ɾ4yd۪6u 'YX^K~1IvNL-[,ΰ<`wa;ؔ a}`/>, 68F"m=zoI-=a˟af3~ "oKAEs ' )>xb |B e' @v0n3o1 aߛsho08jkAXpDž _O_=5Кçc'l!^Ջ q2ox*X“:#!z;}׊|g=D=x9a^43/qs0:ڤo"lFmϟ#1浼Ȟ׼8gx3ul̳Yȴ-&LԱcGzeey\OJcX4ϯ#/00DӆF~szor}kxbJ›1=5]|VqM,lfNz*Mw^ }bbDZJ?ޘ_O`ƨo!N$1ޱ?ol"'NySoR%ƙ 9 Ҭ]7cex~o_ϏLcMa09@<ϧx ރӬUgO/9} 7b[' _z)W9} `c4O5k>K#|0p^}f =Vy<_%Z$x b3^}$c@z\cB#U(\hBoVZm'!F`b3a^ 哱Ӧ~hKrzKaQǂފq6U%~CavU’e?78AJp"}1a^l9tC\9buy?a[ #~56LXag&Hls 86,v2y5k|,埄9s.^R: holRpo\N޿ysËLF6;~{t1ZߊCH pdgx+40ox.YoЪ*Keyg,~Isyq,ŋt|3- 7mM0-c"{ݛ1Y89v?ߕlln8aӄ7;팍qI5+ތwkx`a;qgaɞ.8X' ̟M%r?{/cBT X;YʊTb bHI+'aNx~x0{W U  ̿O{+Y:$E- $ZfjTZװ K7w IO{"P ޟXmSF,x'#FGu# B|16d!6rE&q}˛Ƭx]w]TOψUN )[I II +8ě2E/SMJA'ؠ8ƪ^i^ܖlIA7<6KR̯y3œ 'wbKZ3FGoؘaR-5Yo63ww\zL ⼶k݆_:v9|?gxmq:0&CV;LTWX7 c~wu3)¼C*ܧ{Xo_*`Y4[oYzh5mx47vmRE.`Єe'==ŲFc]: m{HЯy=u?`C"=p'vРyq,6(EC|Rx/Lryw{1fc>)9,ҙ9*fd^fa!wATAI56z|v;equmܘ6'LM;Y.Xc`?9M۱ubNlP}1)~wFїh']}5>\dž:=rĉbĢ c'%0= ,KdfIӭ$S[6f0K}lr,Veۼj)9= -k#)l .'|<$k_ alL5/6k6ͫ6q"ɋPc^pNgk6yhk~`~~HxGwn:|p3 ^糦,O*FP3~ě2P]?&t*`/_W1#3lP(Ky`lxO(9`v8˳ f|#T3R-^k RҀ a}_@ !О7]곶shJFE0bfN /1Y=-zSCz V[Nﯼ2/w?썸=D"U*fA>%v[{lLaw8 NԒ5},?m.VM<žj֝/ZS UgkH81袋l7x7žEzMg[ e*૛,~, Uwp!n;4I겦/L}1f{-b! |\׋El+JHpQ|zfcMuY[uyӕx3@8ŸPZ8! HKs72x zU˩W^4 !A X*L_~-Ig@3'+B v_iׄҏ~(#J0щck|7bIRsrԥ+4={>#d)ЁqSXSQnZ8\.ytwleoUj; a,]>u#`هD_q4__%i^%4yZx _38Ô41qtF9LqC8o~_ɓ^< . U<sԱ_<-@L<GeyQ#~[?3Jܤ…^Hs1U3߲7ބB` 9o WJh󊤤 BmDQh;{J@$QH lM=+-I=ݩC4Yަ( 3sD6sTN EDL NgZT5qo|G0i݆PF', #}6Gv_5}wcE˻If-4ihMz{eAUVqCjv'y晑wY\\wD?QLwSzJ Gmv ldDIPȣ"¦/PMBC0;TTOʰǒc.Wc,}&sȑtxXww9B*b ګI+WƄKPZj1oJ$fiӦVlT @ ͛ga"z)jѢ)_A spHTXXHsUSbU4R/eYK V\6JZ));P2;ǫӫA5=c ,j~G]vʤ`nAT5kЖ-[@ Bs5 jԨS d'B0S%| &l  TU`085s8 N?Q*Uٮ'k̂~lxw6~Bus4ֶ3(Txuw1m5\cZP 'ϰU2r jEuz8r NfٸO]:HJ԰ґ}Z͡Xl#F0fɵ &tM <ӨsΦd? @2Nus &.l`&,|a|E\x80 e6mj w>3B^v6#D3gaNr7K8ü+p-!Gt5\֭E_Q xwh<)3))C NӊTDn?QW)"5Rbrxno.~LD<|1<6m"6V顇=zْ\!YJRUuň%:mܸx \B?[r!8D`twJC߱cGI4a)B"PB:ǝC|#%q1UZnNQ bLO?hҤI1?6" ,]>) ²uemRO`Rӧǜ#d-[?SժU3 a)!0Bx?U_n]977$W娥(Rc?jNrC@7~hر4eKM6| p7f[snꖲ"Uz]|_xᅖvQ޽GjPŸQh!<| /PLP?+-lxnl59\k駟&SWH:&@HUмysX,ٯ_b_|ARڵkK.$ݔ) `f20  4]m쪉BhqZg.7aY)v XtTʢy"vWʉ@Ga !~jaUVl!3==z4͙e63LN͘%H&6v4;PCXwy'(p 6 D(̑#GLL]_'g))C Ki~R(rcɭN'Y+8-*"EXtq<aÆ{Qƍr*WNfZ%o,%ݻw](j(2QFѺuXO B@<Öp8 ~-\fN e8tDWD9S 鍀j׮Fh/?dJhm`"KDlĖ/_nI&oPpF'Ȅ"83 TXv-={1W\qux^[jC-]9S7i9`3CXZԫa*ųRH8*}G_M6œO8C?"N_}ěnIT-H[ BiV%Az{&0^9ǑbRn$:VX-v$Rj:-."ߟF][oB^:x,\#26bW6=YqgDI /hqwWS֭Q z'_xBR/I^޺N[}{:RGh$_NP7}! f頫hOF۷o7uΊ66eJBpoB? B/!|Nc^{ꩧh"-|p\|y {LnyS Qwv0Ϧpeu:4ueZ)1"xFShas po>׿$\ FziƍƬ_#d)>?~߈^u*f%'c3Oի69 ԓpNDHP۝aBH XX9өE|vڳlYWꈾtlҤIE|˖-ca/ H g=HP4xߵˬE %=2JA&.\H;v43 %?pr=PzιG?<}g+UD&,WRڥrj~`\~Hȃ83Bڇ]x@@~c~-I߭C lTQ?=j(駟8hSiӦT89Vq Cz޽ijZLm۶Q Z*Y j|c(b耻2E-RYشfwEq*Yp>ڵk6B}3H*ozݖ-[fe7LFBH6 '4qJι^s~=h8~1qB:!pkj*T6.*dP ;Fg"H(`sX$xݮ'0fuHl:챚5nܘ *$=H8FD:YLB&M2=Ν;L鿗h.8me*$S4m4M׮]ڵkg2>k] o֠|qAy\ " `DhQI3"W^ybHB#Y5c{eM8I7OҞw~ Q&'pRժUżDE4љ̛7Iߘ"@IDAT d2K:t`ZZ{e%Aj*|@M 'eNj`].hx? vojqСCI׬Y3c=#ݻwg'i[zA[R `b"2i& #U?hG^~ _tB(JŴ6]PE`Ε 3@N6mذ?_"S,I#ШQ#kt^K~kq )h0ƪGv˦M,ͥ3…qL9aԫWϒU{dz)Um@qǥ+D`˕YDɁ/bKpIʕ+y睗|)>b ?r"z oƍe]fʓ _}}G^",p:cHǪԱp;9НҘ9(m @Qx; 9iNaAZp I3+V(&12 8g۲ehm֔' A `yfӣmڴ޽{Lh_NKt~{]sD`?9Q{%73@oG*:=CONzo;)EaÆMlҥMy"%5jXšZ I h0ԢE B?2N/(YH+Ǹy@ʺC@x2V4*}:D C Gl/6fggS߾}AEF xh7V^ތ%f Sɒ #`|Μ9g6mjc)d' \Ԯ2 Og(/E]"j4\֭md.j-f3@Mc}1aЪU4r!MTUTɨ˰b~ڵ7tS>{ia-4jժqK' ,~uJ\V?B!ҝ?$AWfZ=qMr'<5e}?cTXtſ~:Jgy^ׯ_#LLT8}AZ3!X mPBANXsH="H<\*唢v\yLܲ bp"FQ=>[ Dotl8hREBK50!%,HFݻ_rYSYlF6]NrQ^&w mo\6_2sCHPoذ1KxWLs !/8p,ig˧ 0U*_ޕʷxiQ/MtU*TP) O \ɗ@Mk.r TRB\xXB;Wm۶:3lT@xIv +[,ծ];ժyAvA< 80!A U WKN1­D`}Yl0}`EA@6TCK.O %x8VUm24?h` %#amZH D02e\4ޟgƛD׬"5wu?;09P13"(bpY e_D˭ߟ%6P$#9&NH7o61߾}))S@شVHH+WZ4Lʕ+G~hʳ<P4i-[RMy^%^ɫѯPM !)<"(;6_hbYR.6c3A}MH/>Cڽ{CRNN)O@2=4pZ۶mdg vpVbEKY"6<_"`mJ#pIm-Wk$h\C? ';QbHG >\ =UYp-;|饗L&&Pt`]x 8p^kё#GL&M\ `bN:Ddh~v)xA=wL:Gi3#$Cyʖ;͵ ;Z93RaX S.@HCiݺu&5kfm)$Tg8 8JfI"ؿ? e*7  TzTZ55;4qp"lmGk}1NQLp1W?ڥXl'M?"Zv! RE46g Hjy^ 8+7o^qb'ׂSz,L$QMꊝwVZf`E<߱N.լPr+ȏZGg&&_PP`ʓ W={¹n]@DRA[VVYlήjS$dkG#k% ́$Nt ΰ?#)wE6qo nřc!8!^/qX @-`MS ,nrssŨp%pbݨQ#-#L T WSw:& > |K <ؔJ)b ~~?wuZzSN]ka$/ğS>gwZ\E =_~h{nʔ)B (6Fƍ ZLFYf,BNrw+[:WJ,eN5D j K #73@7hEE(J*/B@kךiժhD^00)_UdB4TАkР'="BDv2tm*T?<[C+J +gzL;yZ+D 8õc 3M4T$#pZ"xA[_A`ʕ&|׮]M[tz4LTp+hC+`ZT7/ SO7HY@jv@oq~Z2?89 J^7uK:ZJPED"0qD!&vꩇdyF'Ϸa!"󬿿^-u@=_Ѷۊ_^)`:Jn jW:CHC@PIogqTK/iA X@ݻTN'֭kʓ ,|B tm%[<'Gvy{n> Z/9uB= NH?bq8G_D[T;+A@A@@ҜBNTPP@M65fɵ ˗/͛7[v!A U `†HeʔAn^~yɛCz&OkE*0T)A&Bv!A UBHI#A OM6muf8iE:Y[&^CenA@ )Y$ZL"~[27vii !A NnkРATbEc\ I!'2=&bbbDI"l2:|ixOUP83DR%=k=B8QHc'֝Cʑ @gUpQN3#-;oT-N/H0k|oWp p{AiD`PtzkeO%ӔAY=}.iD@"dB'h*z'h"lռHTT)H=G`ƌ& v!}v>|Iͥk6e0RAu6Yڥ:t[_FĻJEGV6(/U8 UK ZѣGTԱcGjҤ1Kv]Zj%U<$-&P^=c1B`˖-'45ke]O&?<͝Qfpw1(ϸ:!QCD!A -ZDVZ9BG%?g ?h "͙BFZ~ђ%K,T3O(xtj5j\7H! ? rjK$8! ZGs&)|l̙caÆҠdd_vT_~}KdG|Ĩ @n ˩FyjkR S@+e*u-?;;NϠ $om11:t(5n8ժyMv~?O85KDdD$zz:|A hTW#]= *e<\?LHqgNJuHNuQ]#vV?cI&Y*Z%O2$vڴiRdq 0т2V!OL T;{DX~eMTja *'~v0"o\PP G8j-q4qDSUTf͚$!$_Mf2=uI'$!$~h0ԶI&T'h@~~>.kZvŴr$ףz}A-O[ҍC!?俟6ѳ[ɛӪ\H0D`ݺu.ׯ%_2kZl*)/Xs Ad*~ho$ر#UP!)<'p?5 pwg] S۸#nAtpX񖓟 ׆ۻwzդ+iq)A53jwRU?llg;K"]`AEEb{;1D%]#* DłP v)[s7M3޾{)l;>eʔrs3 .h"n(ښJ#6￟p F؉YɄ |V}3~ZԑUFyCgZ [Fy& crT+NΥ9p:50~ _xL=dȐKxrB)O6ˠDMo߾A$҈IKΟz["x  g)Zd9tEp&VByص-9Z^ /U)$0eQ-R /as14rȰnF B5FEw7L.uo~VyyyXĴ$Ԭ`˖->]sX;YoW{3ԕ#@r*JΡPVaŲP}wM?SXCEEE$^C@ۈMSmJYT|G=,4p PϚ<*:t&AV$&H!Э n8[KFZ_$)g0{Aۺ? jDE{Hd>k۶b>;j Wڰa-]4ۇ]P% D@U 58nܸUތF8L@8>u . r'm455&38;Wxd?:`4&}MZB qp/JB LԳ9zKwq$!ڐ@ V\2tV;3HL@ V_g}6i`ztֺX:(HO ƪx8 4e!̃/Ztw^\* 2O}tV 6ہ F$[X?ĉÚ@h0$8p؜i*aĔNqS^$ >i ٳgS]"P5/'0WlyߋGS\cb0`I󆍞t&@[hNrJ /i[|Du a颋.m0Fr2{y.>f̘st]B'KqT5UUUѶmf޲ 9|)P*3P%k3!@yTuy&;Փd{+V}uîXhm.r_jy}āim0/$8SMO< 06@aaaVh)"ك8l1%+E9U1B/E2viP<RNUR.q,ϮG!H99qV# g'ha٤ݻSzzPݺukq.ٮrHvp"1oiٲeW/UƖB:Cj' pJ!0fst{Q<*Ʉ 3/,ueF˄4iRPUyOMM  !@|4y:*++  B7ZQٸq#-4u)>{1c6'!8$`:IGN_FW Q,|fA=(I ਆٛ teY&{v~k m=N@~_N6m%*H J@G*_/!*mgDӓO>f}{qTʴo>hq"SLb/b&Æ23ڋhzͥI-w @ @4 ,YV*++îシ HQI:4.# 6ԁ^[~mTz+K U}r/HJb:zs~0 1]nJu!1Ku 3Ʋj(oa/ZpV~$hX\UTmA0Z9mUy!DŻ<ރO6-j-%KJJM A@3IT֭ w#Գgϰ_G@ u% пL7okoV0鿮Sc= Sj$'QiGT@Yg~[3{PTyF +;vD/P9K<{%jk_͝;^y_/; $ NF#G 4Ho,4Ej7 >hD;IԄk|ILI89e_ؑAFksF^ )qޠ2O /fS@PWvx4HEEEZ-Qwk$lBNK=1 D ޣ?xXMb6mCଳ΢#<2&PtByyyA3 ѝ5krʠ t~IyGPU>u \2I+UG !x8Mjg׿Ԅl t I9mhM9۔)SeeeQ*aqU"G.w1{`Xe\p`})(|tGt5$fHkӆ}w xTVlgի[ԩSʅ} + & >iW_wp֭[TcczϥݢڰīC/)#-v"䉱R!_{AggH ď}"]v$INТI@+T[@ Y_$H"ϏT}:-]40t MI·7jZN'OW=ZDU%P6S_~487vth. Js*CN{(KT@YY(>'[q45kHT͖w?AqEU3l"H9Z {XÿD^{X$!֭bz|My5j 4(:.xGA@LZE ^y$B֒Ob`0 W/X<<4o#c1D @BQS*HB| @k9ӓl"Fy kmm>[T^^NƉ t:+g-"Zp!}AexߚٳEscS$aUX!&MHsʵh !*.<2v t`.0jY8LII>8l#9. -?Dt$6H '0pX"k$օ^X A ,L B1`A=PH H8L$" nsI6M|)%rX4 % :5(JS{x8TrJO20jYq3SLplaÌ0Є $N -' ^CLbxfɈ;3h4S* g'i&UHI4"/s5v&lI8p9D?~Z :lFm:imn\P;G{-(YIW/LJo5-.ܠ!yڵ}Ar"2rH@fffćޖ"MTWW8 L[#@(#F+l(@^zTO>kB@LLBLf$)eZ@P8m-!FwH>r_04-GtHYJ0'tޝƌz )hȃ5lBG-<8 I$BPΚ5|IU߫e~t @ 駟NbD-p7E&%Æ ^ 9]0lRpաttWLe S+e @`f\ %JnU;ul8} b'{'&9Y䙸9G}_,{1@@TEKv~Qk׮F,?~|'v8aK޽{ H@LBLL.P-â <8D%my/:>Cㆶ!dN g,#} &mذb Z" :(lЖ.]z8{|7%>ӟDL{%e"LMF+ &}O^K Q#н0&[B۸yu#Gv!˱>*:>tR9T@YSMāVBBp9xVGNmE0/h5"  24O||I鿄H 0~ӧOUڇ~MNgo喖w # fr? ̋3I~_H: M>뮠uGvO ))򗿐ů |}%eeeA?`A $b;D1 0 (@y6^i{νH  ') |Ξ=@,mx1wI" D eC@EC xr&&'͆94ZgbA115l#G \S\w}6r EI-'eƎK7ԲooVV{ ZNмw@: ~QLᓔo'Bzj3f+t5אLbs;bĈKxO/0M2g[{y9g!C9[ ,o=1"@hEzMw@h9|@b&Q$JJJ/?묳  $beu7O;Zfwʿ"Omۯ0ɞ3 8@<x0s)#-޷D}E<3$h<ӁZ% LN;VM@@GIP83ݴiH'㿣HV蘛L_ *d{B@@%E!B6'a?a"NhShS @uwSL9l?lB-4mh]|?٠`Μ9>'b/vibРAbx466d٘ER׎qT *Q%CkL'%Kx?qr {)3m{[P'%oP&0az5M}C/UL$xi{F٠]uUuJwr{6g Pl~ $ >MB>pBK]eGU-Bfْ tEP{hӰg+ Z' 3.FiIgWZSow1^8;q!I $ =8#%ŷQ鼁 _{T*A@;" G?9+ݑ'UIqJ7u#0D$j=z lz3} B=<'U7MZL@{r鏃 T <6Y`  # %<0葓ʨK^j糸xJ(M8pY =OrVڝ=628c *rP ?_0͗H"Ŵ*jq_[:/ӪP&<H   ' faJd|+ofՉ p?Y)8뀒4:r8R° jNx {/HRN=Rf@Cv#+<>x~ FwTJ(2pnufUaҬ$oDd)"k.!p O~^4 Av0*NcSP!   鷢V+&GqqZA~O>$tG.5(+חTY*QҵG{}Y3Kj7mE3&Q;w6JuP.q H   n ֬)N(xgy5Fn  Vo69*锛L]ReAl-}Sy N߯8| 1 fk+cwQUe]H`W5-.  $0cVx`iþ -PY4ƙ_tPT^ <#!(*5?U6+4 7,]HGN.DF&o|tPA @Ld 0"}Q"^$~SyhPYj5Dՠ   4*ZB(L@X@C'h* tC鼆$W9*Rz{L'JW 'p̵o?@@@@q':~E2<>6k:=DA( Ȼ=R\֮F ߻YJMd*n;TGvtvo͎[TCyqt:曹j(VnC#UmVKy    23`n%?>N;yiuT.YH;No5<9+LoC/݁)M0&X~|:x 1t#}4sV;UcWS#[˼r78K?yVEOSmdƺ{mJwyjD'6zagJMOV ?ilÞs)[@/~aŀm98۩9OY.[ۿ6SB `),[3~H`a] P& XTHXH3)\( &!08GϝY'yAGco‚ GA'u#gv#6Y~CHƲ릮H #B]5: '$'- ]ZNojLtdC(\v~`"  M>#[kgjZ66z xam>GQCseZzՐBRC g`50'п$<#9Kkg۹8lC=/Km [\Y֣``5v~X 7# -m_KtNoqVr[Y x^Zmu2f^x;ztu'Y$8W>MZgVR=qk׊H/7<9WqVNG&29TP% &}X(P@lJY57qmnڎYAm+^ώim@ |BZz|xJ|h:JV~<4"8zi]7u3'Q?f($[Y+wcȖc&_;j-bp=ZС(!6PU@@"0ާ5eQUCT)q2{6㴅p>|?+)x,D@ #nr봴~ap v}$xT  `>-.B2@z܎tn\B8_k#^'f޽^g` V`y<p2@k᧮h!pZlzat%ek:  oVN SԐo%[Qʡ{@=kLɍh)1^bnJ" mcUJ@WlTwARrڄ  `-C߼u7zJ9Udkς7j&/l6"{ ՔN=˹Hٳ^v~@[6xꑭL@U^  q{#3%iO1f<4掞1s8kŶ]ؙrRa8ťm-  hąoAlͿ }VL9'Їc;JMP lem ӫ IBS!h#SmbupT|vX7ab] ( @ JPf6,5%T`ygzZLyQ@@Z' Tc٬j&ҹ?6ӿ<OЃڀ m N>.强"^~7!>l@9`; H  -{eNzGW .tu/q_B{ ZC`@TkHtt1%VkߨB@ku'`_D"0#1ZiV;zj%ƴ+ ]2ab.TyJs:Vkר|~vO`'ײE4ZƑ$04qF{vv@X)>h$U7F5vٞp$ΝN%[7ӆzJG u@ '_Q+&0G67f㖃yws@ @d.n Vd9nVD#o4H `-IcY xY"t7vd?B% 0$/w'dٟ[%ӗFӵ6z 9]ŦV4 `C~uOg~z X6F瓰h!-@?B@`f_n Ghm}S.Ô Bپ9IFA@ /pDQc$k_nc?$*-1m<6[Yr˷ss^vK k^-MB"6 L A`^4  `l?Gi JD}Kϵrdcxq ,Zɵ^op\pNnFx ?3{OPsOx\d%[!QE nA4ftV v&a =B`2pt !p=䲹8:Zf)oTѪ:x#!A1\*d?~*H8g"[ʿf~o{ {A`p˴lS4gM&P #ޚc5Nh h$ b=:vU-75nڎ.fSLMOa8lt.:@Գ r[|;KH-l۵[wu74 XG@6?$tsB`o~۰3&wb,JI+p+??@`%}4!jm : K{o_[?^M;Q ؑFcO㧭! gd-qw|bpI7 9\ @D[oo|&[/8drڰ]L@ `T Vke{K9M\JKupKXB}@`{z* @3` _w# 4j M!`F' YkЩe_B:"#5q鿉[[Kk[;VPoOZcW|]taj XH$j=i1%TdF@PMҹsǘ6sy*1KuXGQa2pq9&wrq+ikƋA@O@c(=@@H'ukKd[y!6UB6!Ma@`"h JƖa6p9қP2 B>AO @"ڼ^"jFR<;ˣkkvxn:҉s @;R7͖,c n/*>ˋDA}v/B0 `(! $tCAeC ~yz *zPqxq ,ZSo! f2g@Ww!T1V" t9I2jh@`3oejɯH#Ʒ{Ez *zPy| @IbgL]1PF7k6:刃lRU=' y ؀`7=$&EOb83TD\ A .C`団nonbjzU' 2w|k_b 8Lh& jZ6#ٓ@|\/S{.GHB; 6~F98 ɯkhܿWSND %ZOZ֯Fz0~Ϊ^@ l{A <$i'1gIM@gQV a RXN0^-^!/+j; ȩs. Q!7 *6H# -H3e)%ar9䇭gTӳ7$@3ET %(& `x@W)jw&  /%ĚD秱6$3-QC =B`Tf7}4gқ7eZ[˘(HbphxH`nuϮZ8BSd=/r~=khm8\XF @, P no'95 t~kS# &"̀in% *)_ b9~/S2FPwl} قD9yVr;99洵i78w]*jڅ8A lRS I[: O;/5`iV,긑9Oĩ8XGY@B˄٠.M|vժt;+ 6 `k}S}jGuȀfW ;vv_'wS_h؏%o[z^[q8݊C`%QH$)W~ӵKb" 5`&t#Mn(-oHxˑ4W6i,s e t$3@`&m⛩8hĤ7>m)+i:hjhn*AtpGAc%Mƒ c߉|ɀ|ih;ꘓn)U\F-hӁ HfL7l2.0?޶C}wI$&ke@/UC얕O`uRhhLJNE_lib< 0][f+DU({ `FrA$*(*۽D_Ne/+jwwY ;zH!BHӋl"v BLvx VA1͵[(9r95԰1D&8c& HыOg,.-mIݲTV7m&9MfǺi?XѣX @+Aㆨ3P57[0_8~dmKޮ$}N D s&K ЖS$Ae4::&ߩ?:BES Vle!ق0f|,uIįрB' ^l:з8 I@Nl%<^R  r'ci|3@+C?S'Ȃ.<8䜨'~UCԫI T$*}@rZ֚}9ٗ R9c-eiV]2 .OtT$v$qPx6@``8,n/|5R1G 6h٦&gZzin &H `4-3(cFSܾ_۰ϛ{*?#3Ֆ 3{eӘy42j38kH  tCOC 7 5i˶vY6D; Ih IA% I&߿߷okhC= CI٬#8?'@@@oM4  @"<κhi$xuPquoSr@f$~П-Mǯ/%l%&߇1=s蔃lɞuL8͹Fv  ~ 8s^c\XH@O}6{֫m `>?׍xyl?p#/awHDd>?ɛ\V15r[9:ES  @eW! #:fA@O轜㜢נsggWL{@/h@@Ә>|CY*"u_ w@Z<9܉.I4_\GO6BPFΥ8񛉜 <~*x0`g,9W`KZˡl--KS   "ЯdΙ:^s|Fs  d^!c톞X ,*Out/پKϦ؊R\V?scqmHgq~ޗ﷼  0yB2|?H7| ȥԯ޳[SO~n-؋@\6ԯ$FuϦx?/?^D66[ (N塧5{ ^v3 8le?})Jx]OJ5g ?ۅ@!XA+'-WQ q8xκ4CsX#`5ւZڱ!1$'^QtV 8L׬? @ XA>!hR"bp3 9UJݾH/YK VkPy7$_TW7gog[+ F(ۅ@#8TH0I`?wp>AkEVǷ[ `H   6itA<߉l{?pp>@4P@A56!6K4o|H5~a7}ʂg]4iF  #xVIc5d㿓9=?m*Æ@ʂI=i:@D1 6[P.p; |a @@ HSyU6 e/Ս}$  `6&J_BfFЁXG N @@b%a:&Ӆo֬gh^!ʀA#&1CUM!DL2%iҶ{h/9i;FUiq>O{[ڄJy9** CB eA$4uh  MaY&Dvi+*x@>Aɥ%Tcc??T6 + 0x+A |m832 |=:*Δ@@@ ;{63Ra!w^A&[VkkT5З+_ТFڽ!bvA@ ٘OQ*|\N=xoc?AW簯  }@C`xt?eWp>a;k|j;5 y#g&ѝ2邾Md#羛$.0ђB9 @7ۃ Fn3bxC#C-F hH  n$ w~<:su`<70%?wWMC,| `0 <PLf a4ea-=={A@+F_  `6m iXe/_d[Q705rr O^A&[Vk[4!Qdn'My Mf,@pgi2%~;&R.ı߷?gm3 *L( ` KBu 1>pgP&߾^O.h7Xv  QHM# wTL:k M;y/ZӖmIH[Sv,4/!*@TOiݳ}mZb㲕c?y J( `CUmIr y`9_I M/`٦&wFـq9/J.yt*oO'0qt2Th, ГmAD4aK圏ٔZzZbv-S7}k~2rP[ߦCNS59K2?/}mfzD@ I 0 4?ƿg !x\ΦMD^RGsi6D0<:p$SEdݗS+&3s^!D@ @:?)@W"P~PZXH `: nv0 >G tMά?4wɢaVpK h0F6 P)O”`e;>3lE,mk :>Nږ~/l)穜sh6A,| `0 <PM 9IA 9 ;"0sVrvnv$a@$N |OQ* Ƞ|5~H"Q53@`e> P%FyBT?/Wn簂[s  +S /I޼SFW!ɦ1i]}p+4@LI; x~?y+eǁX( 7֋Cj$78&{ATcx4֖Kη3=4־5B@(EVchj'_p:Ot$9[ E3@߭iKPU  PHCX('X96OG@$* `̠ PԳ'1^- $k|]]/@@Tb/j}Sga* (M#9(j+99s K @` \-MCx?D }8[&a]3+Pu' @%>{ۥAߓ_p> xLMίpM6~E@Pp @9N J@(~ks>sy-I=`FZyO 0uq-}kx) & >Ž:~ ׳] e%[=M<_?X= 'V cwf q,~Ƀ9'r,~HvVwe끎Aڴ(4+I=$/_/|QCQ˅@`f 8?de$|6EٲmZf+X]f.J!eAb) q4)?՗O[ev2'YzM6~,^! @b'@ um]q1"\/@" Ź2Ζ5[v/[v/u;'6}X67@ `` ` Vvr?em(~96?E UD\ͻK"0`g$Pc]{jP` ı+ї,|*! _NS*~e&P.(m<87fe 0+Zh32Fg #_3@" @`}_?Y oE># D`G6ۧ86OoswpQ*oө#;K@v:jjb33fPF @A`PA~x€Me`c.Z' ~P@"4 ^Aqۏ~o˲ios8TS~z%Ha{FN8S~~aV,@ D@~% >s퀾;p nǁ n`a@]>?k5 K`7C>Jx 56IS~/Ul6;þHs w8md,.A| @# |A;qNl$&" ynZu-Z ,i Ĝ` F^h n 1 kA'?#9CnONy@myo#g}k9yk~Elx]xL@ &y硜;sl$BF#дK`ݶ>%0Dk@߱vp="l00'QJbo#߁C{b%/d͗ߋ**: )lsj /.'0t,€8w\9S=M*UwЪ>m,ϾEc_ D N?7rjÛ~/`.y))7wmw gr7G!ax bxq15Kj#%$ND` y8\Q' XM vO)Nl  ٯF?-Il峭a[}/r/|՗}S~.Z!% f,@Jy^p.8$vm@Ͷ]|sWF Qs jv;vmA&_eo%^7V/Ld>?3Y6m(_y՜p-?|$  `6&@L \X!r/w_Jԁ75,Wtր va k"Q56y9|/!gaϻ~~I~)~_KRC K5tg@`i đ`^Wm@K&= ⌰ j9T&jvu\<ˉ}]I\V׎l/jIpzr^ihǻhr_y=8#DCp{+} @`ߵ@Geh`@4 D3@4 } E{@M f9PF(lg̓|M"4rN]h ك [9yL㍺8Փr/{I}an :IDAT"|ǜd_D߫lAK7PC?]c 邀@`P@@ @@@B J޷ )"(;% heMI5#H2KXp n|!W sHݻ O Dw芃Cɉ)U29Sx|5Q/L}'|B/y wO dd/0;QǗ>a6>!aYNpJ?C@o  ` ,NA@@BՏ8b6 1` j$! O+g Z63b~0omv\6"?װ#Cwd>^ZP1L`$bKM4"x~XEOcMbfS|1(ءKS~#r9k"\ЉL?i9WjswieC5f v#S$- Q;yS!H"y$8P 'm)~&FlؗK)x 佄= $qH\:/"HLwBx݊b7/'1VC!{S ck32$&_>ʦ~#*_s^̹sg3$w  +1!Y^%7'د<DM@%h/,|ɫ9/   Z@@b& p 'q fi@NC7;| |   z   +Hs.\9 IN%6βq_y)οpo{  f @@ V Ds9vAg$ l.p*8O}_h  @`E@@t! N sٯ5v"pX*NITwį~G{/   8a0F-Dk) $+9,ֲ-,)$y/DX  eCO"Dg7pqQ,I{O"c{9Ʌ@gzҞ\$]@@@CF   `?qlž @@@%@h"._`L@@@@@@@EA@@@@@@@@@@@@@ "c       ~       x`1E<Ș"@xXdL@@@@@@ o@@@@@@<@,2      7       S)oǎ `w"aY/ @@ @ 0Ov" @l @ H @ @ @`d' @ @ @ @@@<ى @ @@ @ 0Ov" @l @ H @ @ @`d' @ @ @ @@@<ى @ @@ @ 0Ov" @l @ H @ @ @`d' @ @ @ @@@<ى @ @@ @ 0Ov" @l @ H @ @ @`d' @ @ @ @@ pIENDB`ic11\PNG  IHDR szzsRGBDeXIfMM*i  bIDATX W LG;7g))`RJU*ڦih+jRl5mŠbMֶXM41iMƶ*" TTyz;w?-$I0 0wVM!D;9ar^x3ۂhMwhNC'ӄW_4OO>U}h92d_~Gzh;ji@)ڦ>s5ҪvܢwTQ۟-z/Q) zlNWs匩XfjI}p:?oXnX@D7Sr>o@=; p[.w'#lX~}r pG(g˹hx9> ql==Z3]gT755]T{Y1JKmvw !5#1XYC7s6<%Μ6D!//EEEX`Ftaϝ3THd"x1[vVC<V^YY[bѢqHtww \UU%0- <I~Кku1^iӦ!))I^s~KKB32õI/(? ljS2'22Rd޹s'JJJ4"3xgua@#ϊ dqq1l&h__``{,811Qt̜8ePoi栍['?psM2)pŋ6o y=Hw#Un6|8/|QʋmSN3VF ;ppG) hgve c_vh  >#ZHQ"rC`BkjDOzy)4T:sF b@k@E Gz>Wc>& ]={quyGRa *$ 1O|lJOgʋfo߾(A+l 6tc<.DU}|\WUOylxU b~Jfzg (#Ag\Zz 0{.|\ e Dq+**P^^StA~/v!οwo/jP= `w,Fn7ۅ9deeiԝ@*wV AYqrALLk)|J-&_d~.V .3]V `k@5P#,D~eM9 Z,q$:*gYadײ°:G[tDUqJY݄W^׋{QБUJew5bsR|tr; ;s뀃m|S݅+DSZTk|vڬ_Tż\*V>k@ Q`Q-yѮR ZҚ4o&huSJ2vwgEؖpu9+W| Լ4'xCDe(пB,0f`9)d ]EM /I+'0txH@eYIENDB`info>bplist00 X$versionY$archiverT$topX$objects_NSKeyedArchiver Troot U$null WNS.keysZNS.objectsV$classTname_assetcatalog-referenceTicon  !"Z$classnameX$classes\NSDictionary!#XNSObject$)27ILQS[ahp{$OSCAR-code-v1.3.1/oscar/icons/README.txt000066400000000000000000000003051417327530600174070ustar00rootroot00000000000000These icons are pinched from my ubuntu box.. They need replacing.. Appologies to anyone if these are used in error, contact me and I will correct immediately.. jedimark(at)users.sourceforge.net OSCAR-code-v1.3.1/oscar/icons/aircurve.png000066400000000000000000001352031417327530600202450ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGD pHYs  IDATximYV={n͈H2G4.!#,F?'!BUJJhd!PIDDdtq۝v79{#ɈdJgqg͹|3 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa8of7{wl6|-C}1"֭;~?>wC\oF|ͯ7^1B* % }y8`,Ta| ^z{a}u0u~0x7ށFH%!##b=p]ߣ{??}ַ۴^}/׿~ } SUZA) Hbu#x=t]~pzv??ݻﷸE\7X,p~qs,0?:ݻp]ܸqogak}-~ _ʗPB$D>#""b"B:aеڮC/3;~3Xl5.//C<|Xh;Jnq}ܽsggg8;]{kwy|saQW5JC*)xӊA9 Z]av;ٿsɟ"狋\pyys<|績jBp;-,q` )PJB*t7o޽#?u 7W_B_9kh`4m!( Hk7u!"`:t]`~?񷿫OoZpuuIÇ%Vm -u Z T=ESt=A+3! R )1{uWM?;6 Jaɟv  /-t7D)!>,EdDJtC5Wx㍯{n\./bqK?CY, l-;ЉΆ`9DDBR@jҤR@I.Τ/?7}y> ߉ JAj)@H(MZ)T b "tY,xmۡzDKY|w^m\-quysөX,ٮ1sZX༃>8q'KRA)Q;Q! $t@ @H)$"y|{7R )W*9 !E>c:y:!8N <۶''{?_W2.K\]]puurvA?a{2v˷9~v߱oi} _1Zkϐ+hRqc%OaE1F DD1T*Lрv{m1ho[YkXz+\C?%V%ιF60Cg>D{G)=\m^ɮ2: PB^9RA* $JSRRȎt]CN#1)bB91_T2z%!>c!K)dzi@Hr.ep!9d 4U hcDU7!>O:8WEH)PS@Rb/!G@iJ#Ȇ0GӂDu*4mףm[g~g#?>^zjK\^^P=++ C{:;]`kQr . oo2t]ũ) Nb %c4@0Z#4lt}K)"(!9s!@o/}6z|/"4*N-ۅ9or";A{>(%*9Wdm⭷oĿ?~ls^]_z˫K:/:-;?"~42z^C! 9Jh/]/e E0voK7Db !BAA)ׄBS=;>ѥD X;_<;`[9x8Uːix 6?yҟ|{|/o Ҩ+20U 6PZA+J8,@Ad{_؋c_3]r:݃% =V)|3ߏ<...p~LYn6T^'=PsbB0?$+I9цR51ePBNd޻pQǠw>FHu 5v @#ӻ=U JkEC?CKppp]\K}? Hhrc>qUw拏x1D9 {4 TDiS^+ 􊠰s`aXoVX.X7ض ꣄B`d<  Y4}@ Qy6nߺv:%wsN^7fi(YI}l\1 JUj"R@q1U2n@׀✐=#%^Z l6-c9./Ϲc]>jYN3x <҉6&gM"B#8rٮu[m=ěob@c4N\Or<?-8{T qǧ's&nݼ FV{b$CfSpG 5D`XkTVh''sݶxRS@t} Wf d+1R9QH-zc׃Cv[`;/ 6e7Mqg-Uqg DN <}0 I8J 43&ǰa P. L' TRRi08::0NQJ36+f3()ѶT>R2Zx7ZKe}Qy*#YK%;:ٍ3;ܝF*i:<.{:Q9"AYbˑm!عquuxƓo:8{x9kqBg@i)OG D ;;YiKEDϧPjtPw)Cv0: r% $5"i% aPUu]iuQWJG9GªCtmMe-b X,VhR L NO5꺆`<=UʫF)#L RG%`S+Q KC $#)O_1"2/("ԫ3!Dnn?|p`V__1:v Kʖ\F61LlG P0d3/M"sEi &~<~uq).=.#5B'H2ft V9 b@IsQ{n8۷0!";RdRjRA)jIF.KS7ܸq{W:tQ;0s_"F#/j=IHT'mZ>=}p`׫pyq\-d#俛u2oO7d}5{xON Așr8ADB͘KI,@3jaNZnIl,_Yb@j<+kĿ) 0ѐ@-./q-ܽs1@N dg4b?c׾u./owޅ2FJÈSȻOR:\!0@ 87zrشBtLy`殥!yS F~ySꋁYuݒT,ڂ;(~-9b l&ߢH1 |N8:$~ȺC?d%ʱܠM;#30'b d#YDAWJ@5ۭo=z½{O21xU"2,фs|{FHK'~q#i,byS{gU}=z.90 Ob*FXň~)]JI&eD sK!}| cQX`\z+\]_/~55kic!QHsPR#_%Jraq7D8fĈ@Nԑ>Lbw z`xsLd*Y"]%GNg3? 훘sC™,]Ďtmb&=Rfmry+\^^kfoCe\ rH6dKA'Ks1GJ_S[ٴ"AEXIwڱ\̥0XG睅)BItrT*@:A]O+WCG#x4Lxs 8!=Hv|47 \v"rhB \__'i9:k\__ˤ`="#>7̤ _p 4*12mFwdGy#K"rN JJ6J@v=%#:݇a5XYǏ'8F՛(dDd윤OD;=2.\=QyG>2Wmh&S G `D%]3sii1EHQ|H:R9Ǝe8V Ӓ1"Fy G? LL}A E@K7xGWqNH6(Khc8/]1JPQ(Hh P8YvJF gLg,(J)Mlk˲]P'4 29"vC(ס([rG:1!TfX*$|}uso2kŒ ݏR$hvЋ1N)r_lR7$;ѡD>px*cqϻ+Onӄ 6za0#Ƥ60UtMW+ Cxw$h!.]ї eѹT"bۄe'JM(* %!"uϧN'h<.>VS-qR)Te]婟> 9#8ma^?qrvKrR8~W~]ψ|^E2x"X<cd*%р wAʝ74"%?:':\Xj1 @UU0Z2UUv`HL&F Møik a$XfS\d8#D1!${~zZh 9Say&㏩&GO縛D `qhփ(!K_ Hvc@MD(!$wX.Oepyu_gXn6\ @(KK.|#ΕF}wo/2\MvL Z4e&WJ%As*@jC$ ޥPj]Qo+C +B"}aF׵h[\*w0F!RkqB"PF#0b959W,F{K0QrT dyBo@iXvQx'вxf`b| hr$/!ǧS@ ]{'T~|"U?.~`Zv[2suy,WK[R% 5l{H6 AxvNM[y {ԭ+# rq8GѲ?xALGژ#hO uHRd!,z WG\U62}6ї=1SP`\^n6Mmy'+ KkEOK{tyvoJ.GdzL,7KGҏ G{vR(?`sW\Rdܦd2ɳ)7M꼅zt݀np6xشVzHIyaj͔dS.+4blu]gJH(%B:;ln"PyV:Jv {1d=ރ7$X,5S>hu)x I-v8pN?2H("1q>?_/@e6|>{'qiȲ4al+Q[u\4:#BxRs8_ 5C C&$\:0J(OLMC¢J榚bjm-ɍKRUe^oq\CHZxBT>@Up$A4pHJL3TU*bIr4@`6bCi[%t}'!t.k)e ]b>[#\cK9vjPWpW@ϗӀZJd#+"bZcm(C9z oD4D~I*sW2<cP=s$Ύ9 D9ԗJ&"Zz;車@3~8@YaVh%v_ kc~Ӟ6!"6 r>X,믿w~ Z~IM,Rg` ;оtsɞ(c2?$oxxfD܀anHmѷi^Fό4 :q]{^``YcYT='yHe f9jio[h)&u5_KlZ)CxV| RQ-23Sb:`6h !tRTP. ! }_>!ge@Da:H J7I:\ym٘rX S(b[yq6Bbp>s J_d|1 &E!S]l67~W)^5\_-Ѷ-`Q (PКnx }J$M\}/S)MRH U2teB" 0kn]b,ڮ͚TѦt=#adt1G 芸B)Bj8G!w9w>PR$"; )f%SJ NQަzFs"uw ;DJ@QDc40e"eyl KwS X07#")! pt<8FS` {RnLS8 6F1Mh5P>r,J`{*yӃ"3h3T?13z%ilVbK~QHkqI걤p 4ȧbd:l9g>2&׋Vc]V%lIqn=D>hZihpuťD X{62笠o!'\PSWt JǮH>gL)KRH:_"0ə]G't5Gpn^>LJjɐqgk%)GM8Tև@yщqՍHINq/SXlN~4P|z sDTH%Av1S(,SO) Gi]G;M;$~v粒 d( _JxyO g1(_Hx(ҶO4̈́'"1sC.!K%qVnܾcpzFc`]o{CB: %-O]nE+BooB/4#%?_TyN6Qt߅@u)IMd -Eh]֭iH80iXO!)m)L "%UZ;'dT곎1"169Rc̵ߗF^K )ǩs. ;pA}O5+Cd a y=G W!D@X+Scp x& EMRQ>X'+MuO >#^ ZSJ-ɍnpv;!}upyT9ċ<|S(*&a/HMQau= 5b(!$Fg ԒOaRS5J-D|JkH(%cMM<x=8gE1uU65֨Umi[([읷hSSy i**P:!'b82ݨ_?v( &!M$g"k)GmQP_Lv64Ѱ=xhBxv/0F=ލnJWr+ H0~J r@ztE:$\N) >Z.Lg֛MmF 'O5nH'rKǷ(r7=IJK}@$Ttń W@"rc~J"/%2(]Ҫ2+r ZS=](h"冷޴@kӓSH{ $2Uѳ/ѹ@68JdvCqk.5 |ҥ fС{1.;W@r7|_gTr,R^H<DY~JZl|h'e6)(U!Be ģf k- m4>8f)ܖp>\"u(Q&t}enA$>!Z܍FOrJ&C_gI)K%&p:| Ad +>Q.( ΅ "l,i1hkvRʖPUص0-"$LkP%cvkSFRГ,>S( V : l>wHv*Y BipqyKB; m@@j̨"$x1x&"kIV Rn#UeA]hEĦ@JT8#ϕ& N*Et۪HBpTySؐ n $sH)YJ xHV!X-ଃ{Xֹɡ Y6\"nv\y M8 ]D@~^.خc a/}zaStU¤|{ UP(%:#c #JTh /ɑ)HĨ TA@L%$rb^\RB`۵ *>{| {)"khٖ4̪F4%Ŭ8m4CK)PkKye";c Ts߽ 8u iW`4qCtIS!FPR]!@r^Գ%a>T|=skI{Gw1gItï4{PP`ay`9i`ÍSHp!><6gghIƈGQmTR Oӡ,60?׾@s-@,P'tr5'{j OQ5U_&u&϶ߠmB %Ej TԪ[霆h BeHCsm] `aqfRA@2&!"֡-$ΦαUަw,9:F`=5N9 )D.k0J<|p "R"ޥTq)S10薷tV LABP]](-\uK3(HĒ87oo6|mܾy 錄FNaΛ[( p4]Oϔ$K1ȾG 7~q*CI"O%> s&Z=;QpgO$"c);Nj0 Ҟ0B]7M mbqC8vI n:xxymKuF|%RYh 5SV{SUBv+C! p jSj*1L@UBw1γDe4LG(8XTZ-KY5H38$3RQ#+w8i9]# 5X 7| 6Z]jk\_^wGe4 zvjZk]y:0+'$P0ӓB \J" |'+e _#9&~,c'9Bc %|z/HI .BIJ-  4m[\]_}T 5&/>HC!h>pnN[n/l_|۶VR֜{k@Z1F+T|uM( UJh8i!QWz}rT}iJ1juuTqL>j1"Jm@2("6#oDLnI"! >L[H)`ZP Ԉa{7X.5֫5QI]gBePZc`ޠ9e2(u(4Ag'G+_lPTzV͊S>yreub ȱiMcʌ@>%S T FE xJzR!"UZ6p4RBp)  1Da* %(O@)_ϗ `M*nnQtZuJ3nH4&MLĦc`֜S^U0$% &"S&*%:x}"bZ YRRum7R)2mNd ׺D49@| gc{GepK]gw{c]Cb\aX~}bC¤IfPX,j)|[80NI#)I!Pȩ TLfB s&& gZQgp}Qx=g"J&~r)T>* 1]xNT@BsZ+DC[߉?<(~_|_!]$Zpyy =nݾ6\K'f]Txjqrz\suUpcQa2m$ ɤJC37(ee4;r+mAlC DeC{AjZlXd`DZAñmDO@;0qܐR)ulDJ׀ G WWzzvDa JK@' N>jv% i;@ fD=9y@.hK"_bDU#09"e4#ᜧ^~2_ 9c[ț% _/#!BȐ2ʜSxQ PQSb tJ!LŝMSԹT_%1W~{ˋ~TDfbR'G'h*s0{!Gs 6b>?@+ÕsCN%;$dtDL븆KUSR*o/xN;I$ o>ׁ$Mc* \Qt@Q'h馳np=vŴiǛR5c&0&P^zq+\\^]Gݒx6(&xx޾IpF7thd#pzBaԆ$8O,N\cej(sGDf&a\] EJ[;pAq/\Ijƹj F'ޓQ(I@t (H8XIv*w:7F8>e*xMj*qC |VAXJ}9YL&SMwZ-&(Thq}}ˋK_\jEoS(ONS9 ̷g8D/ G±#ʘidZfizYّqDO@h?5ͱ 7L'֞1p2yu8;;FT 7o'Њt*C*yXsww!0QBk8p2:0ǥ='Қ))|TP@O<<~믰Zq}ll,K_rrMz 4Ґ:0v` NsfR-)tC.s/181RIED0iGDufO9AJY+\x@c,ӽ^l6\]R禔d4xd!%CR=Nc#H;1XJ)@zMӰ5E_ OCCriwm]d\d^ ZLg3.O(cLƍ3xGFB;m sֲ`n1j|ᕔĻ0nʈs<֚[zD%Rr='UH\B=#̦5>{w^]׿bu;=#\_~Vy~*^b]#e.};GRءPj@=~hN:tv-MfP0'r]faPZؾ%\ ř ajMuS`m`.ϴ}^TTRb(0pCŌ=t"HC &@KM_ '+zwJl _-K '94qQwYdy0J>l!dRׄ.FEdA*D0H5CJf^J i ABbs]5R?_K W0>S+z@UL;}̏Mv duMӠL ]!Bq!9C`${߀wކa{'l`Zc2Hu)K&T@S?}UWg-9 τ4y~!pˬ4ĝAds"`$aR϶$rJS NF˔fdMM }0:54W3c $"&;2B|643UaZ[hbϏ--\v7*ج*jo*B*GTC6(z/ T;9$; ڵRmעsZ0[Vƿ#L > X,VpɸS׫tf,F].sF!x]RRYꃮuGGGd-͓r3`G=D(Xv:j˫5zGurУx# txxmT]rNM|vcL,gl4" &ܐ2s4Dr߀ !Ib{y^ `u~b|xmƓt;?"72$5d 'QC& 1ж}&(8K+Z<tl$ 9:{;S (g;pJDzj "_eZ.^mELA!IrrE@KsʅBҬBʔ3g'{3vc-4./ƛo,IdX$X':1v (V L ĪqڈWI zPa ]b^cޠCMִ9`,Vc#o!LbKN $j?{[9Z)hT\1PώqJ,bp\<pt\~j#x Uu E >1~ XAhD)Le $gF[4n*<_3HhZ)TBpfκ$ nlؖ  `RԼC%PJ6ͤ =htEѺw1ob_vQ. 7mtx1@&F#ܼĭZ, 7l{2KK%aB34,C98R,uG6k4C}uﶘiN(?H[.JiߥPCnsg<#A '~'q歿,E"!IMw=!gGY" UU:?`٬v F)@<jYe C+>d?DKIBT+Noyh“'sՁ?J LKNĤ '.E'ŢIv[V$͏S.Dp G" {t.+XIt˒^z;/k{D2w)cBOH)"@d؊_ӧ"U(F[/0l-b6\-0iH)Qzg<Ͷ|}xca's8/JAR!'c6tzbr& #8W_WUxL Oai'c꜈S1 0`6Ԥ8rR '?c@I}AzI=#80z_b!"zS ! ap}?p{o>a񀽞|EȺh۞bfXZ º2f8;=+ 0I@:"1Bj^l6fͧB3#2ttbƽ @qEnm@j) '2dQ)+H̭a 1O)a=^ J+(~̇6gGg//)g+FMfM˷Pxӡǃ8r邏 ;8>mD$'z9qD-Z*"X/@)H:,Β~G3M2uqc<:՟٫GR4)pw`(a1LCt2+Cko+( ̻{RBmjȱMD!G{w5٧V[F{I)f%(p~4)L9&2J91ivDc P'pcXO3%0o_*  CQ^ ] aVgSs}OjR@!~) t`D>9r=u(b$!%h:P)QM)ҠMKƜJu$hsTh[f V̦TAN^ʓI~_$Ja,b>ϳ"P `\:Xga,DÕ|61y |_t:CLhCO'dJ`y\ϭBD+8˒/Do{r /vn-`\@SXbd 8BTBP)) X!1P9tpen֘ͰYme4*IJN!fO;jÍlUJ&${iԐzxGI-ωГxɬ-e!I)KxR"u~ZNNO1u~KɡpzzfRayj'- 8uﯔuTmf ڶCUQ o% (1H+Ɏ&(<契#4{7X-LOVJ0}ŝ|Z*8ɹ=A2$L*"j1o 1զ) &MbMMf'w>g~5 F#rQ^J$?ا`ƛ'Z)Mz_ i0i*̧SgSTUq1~y,K L& SL@v}c00k}AՄmuwSN|ba%HCiq.|V r* 4 'U\ּY-*DT(J\J!i4^HRD'pCʏg}^{~HW}c6aq'Ԛ`҆H_>ŷ!JA)$l68=`,ʌFF%&ܽ*+!-?pge` +$@"t<RC%NJHh  Pأ{L& `J@c=l @NW|4?FB` x.AqYKLIsmzklm1 {)WUh#LX.\.y]c-0"(&ɸݫyť}<7(ñ.IWPt:i)!hX-iͶ x*)DfQ׷nݼC=,\P~npb`* w)H9UKvc6S\fwȠ1!qZzӗ%Y~p rw=frG 26wkiRq=" EW$+ Xd:'զ1OCc2 btҌzB% k/c>x4~P;?CyxJq2W"V6QcuJb>?o|p||O< HI^]K8]|NRkZ `|XT H~`߉vB4G 0 UgNMBRj8.RGE d+r)%1ij(N\ߒq*S\v!0?z9/R2KLu!!)LXW>2hR#jiR9: 𠦟G]E#CRRنY=ȧ{ @݃ܐ{376 5)M&4k"DϥP90ۗ:t۝x %(`XȰ3ؕ$Qyn_Fxp}@?XL8z3`iqzv _MDsL jCm}?`Z`4qS(-aAkR1|,K&LNu’0&"%ʎӂpC@2ӢZЬ*RON1?>fNtz8;=bD˭̧7|Yq+n"_HJ tٜ݀^"aYGTF&c2K0=gb>YrGĬAmTHz6{5~'=5oޣ Yɏ!GoE:D!`mK=ڶumKh~¯WWh9NNOwEnM&i3N@ d2Xڅ2(@ 'l @0y2&W ƅQ%+K. zH+2裇dqp]|K_`ڮ@Up/r8uhv5pX0?; @?8ybՀS+peC=g{G$BF"Y~>X:詜w0Iq" [Q|HcC6 f *_S.aiOf2 5|ڽs#A a 32T~sp">&` 9[u<.>ZkL ~,a PHjjELKE\TF6Wơ@=DIڷQ8 . X(0<_{b:IEOؗR2JP6XT6 a !Dup>G {n@ 6gS ǵSLDfB@Yni<$T<3@ڿQ4tV+H0Ƅ'}458]t.AX$ȨQRI܂I4+#eua 25t}y Q} wAO}~P5rJJ513>;K{HY{O` eTn:2ݹZmm>Mi\4s sw/HޱqJA|(8n4TD>RI!b-)\bz:5Jmqǭ[orOa) |k9tA\@q )iP@MiCJI_8*: >Ajdqnu֋Ϲ7`4Hrn0yn92q@%Un_ !f$_bDX' 1NGR^9;vlA=I3]`<ᓁ[}n XKUDC?-rv` I9E=1GB KR2Zܹ=0N 3~M,+HAbR)@$ SYlߣ.5u{ğ B (ל Ӑ|rUp=G']7Ý7qzrA0R[IfھCo Ӥ]A/Ht) "wlo=IZ2Mhr Vj;U,mΑRK(rڶEiM)$ay"IdS3:;sn_~a-^O}}S,\S_EJB8:>@01 Xa423`4!jXk_*^n^ FU7Hrc-5yxNJ;2`ͅbJ$5ei5]09-V V1qpOG]^@T!%\tVC7)r&w8!tm5hE3< J|6(z.(VoB:`Q)X/O#A0+ s3k'W\!xѼFCOeg}7NqfX*k'=fNNOqyu k : Sdz.z GY6 K)z1FP,k0"tӄcs1orj0q>fս @Pԏ/($tPR!HJ; ڮ!^xg?f0@CWH(c84pΎQWZH_Xqmz&Gl6,;p Iw@ @Sz :syZz9/a'1$RW U3pJǜT<>k $h QjoUڐ. 85Hk&<3b&xF ovt EF]kԪwntnEpnܸC{Z75q {ǂHZ:AfcZay*uCqSePPz;*]nnK 10(ɳ AhB̑fpY~jC%/iiҪ`HdɤFL0ϨqMHM="Ym[h 9qFL 8n'rbiIq? rג!y;Ă;iJI(`-EoiR4˪(pe}g}۶*+(BQ``6Rv="L/fM&^A?t:LX"Mpm62a#FPƱL)yr5c3 BC\_yЧ,H5yDO49Rt0^P鬮&_B( N|o: zͬp{9Z{Cb> ҈pGP੧+y@k@1:b \_^ciqrrf VDAzV5;9"$m[Њs$ɅTM> Bh꺁 з"X>==rDfl Ѓ4W Rx# ;m:*#1j[fh胵ěW@?) a2 ߮#,vߐMw`Hls癹~bXp06J q&q-Lg3Vkt] g=5mɤƴi a0XbHr:j Eߴ>~G#/<@zØ%X}350?V"&f({ ‘P̊RNYkE>В"/)gLc@,0x]^\[XnLhPW=0 =`y61 [hE>#D [xJH%k_Ń1OpM(awxF􄔅C0x$۝SNb+@0 lLiD(fJٙR4#l%ڟc7mel)8aQnK™{F]ش-"Xo\npXiBS B*E #L`( >K^iĮJ2d HOA="U$ Y95 '̐c !x5$ky}B~g7D.%#bKZt/K\\.ޱG|D=Y8Buk|цOhY<Ҕ..LSP54Kyi#q-L#\&+׿ut}90ϩ <Ð S)k44iijioӽQS`btQ\5S% dk )QCјN'@)e J 464U 1NOPμzS9ˮc}Y~x=˛ʊ0"+`T0~BB0VEꢤ.M?8?>֭;O|H lO_ H92|$G' =ZLC?иo2OUDdGl=JРƇ7SҋH*!ۊP8Cka1؀ji'XKf Uλ,7$IJHۧ0Ce7$oMat wQ}4~o} =1A(s3@65%Ox  #:;wQOӤ=9[өXI);sN)<>cHcJ*A 23=4diPJ*+:g1#__/?aMGH_?k%(mYB dFEUL'3ܺ5fBnpq脚,ABJ'% -X@sP<-hFv䧾݆DS(ƒ|>zz)d$^ i$zmQ. Dx^N4CړeΛʠ,m0.~v  5D# {)}s.bI59m!2VDt2TeB%St'Wh$69'b kz Pp EaZ1fH^vC@ӟa^܇<jT0JdHu}6Ի7 v75؉t^[TO hX* LJݗEN)KQ^ >=be$59d#jՍT}8r8g'h& "Bv,S-"WR#݂~6ed#!wMMw32 ;J-`yZz 6O[G6H MJw PbDlL䍻>F Q-r>"<* 4PW`<*22u,~!S$T39IDATT*--_m4 Gk;}Jܼy#Gv77|/ TvcrQ!m5f2$z`i(uA9h><~*7xsr=8GgMrMiٍ!$ќ'S4;FfZc{VM8Ą*Đꃉ1>sj5FWH V P.;ƿwUX1TyP4zp!I>34\hYy%7r)0| a]ΡHRteiM٘+J]~|~)g>hLl O%W4Jj *SU'3%ihF4gS$Em#|$賅< +됑^f)*XmIAm7Ȩ*SA"kje@ƬGQ}Ɂx߂okj,5G 6x J.G#ã&h}CxI.}qr&*Se`H0XPbE*F nV QDÿWj/4~w8ڔ>.>"=:Hq:}'&F޽۸yv Eydz{)=p?B'{zIŘefܾbiDFc?5WPBFUiL&5n<%IOM]uи/͓Eڄi`umH'G)YkIS,}[8}$}"g>!okq5xbLaLj2ӉTa!V !1- t= Ftusfi Ȁ!)G,( I瓛\RFMello{(I# sx3ZFd1IJqO4^pǁjliBǘ|ɬSQWY#e T`]>* xA{tEp<R\wvN6RH*]|˯^7Z^CU򗾂vj<0 >!oMZӇ" $Q-Xb p&g;/zhtWjg[<6oBZ+T D999n޸ 6fK1\s>0]1vc#R;DppmEoiրT%{ܪoBcHٙE CH쐥贡]cҴ_~,%iJBÀ=)heLx_~#s>G}70!TDZcGN1ij-1]n訉FMt h' `@:%9^J`r`Ǡ XP)g)mW??xܸyW-ke|ϧa!YK,&@C"3x1%Shc@S-wK}\ӝC<7& Qo hALgscLSuMӠgvw KN p87zQD#]@;]H;bjjA`kX--B 8==C4$0+CIޝ"Ifz2QaTΫU : i&ɆCO}38y/3'?=xsݻO__!TB@[*7x\/Va>38qzz[7o`>Ƣu-aӺ"p@c XZ&<>!R+T, KZR+0V7g;テÇX7N&x֋^Z`Y"/ k[f$SOa+Q / sò7*M1^XT˓S'N'899)14M>#8LbZ1#D`2lYt.{te;x˟Ƌ/~wIs/K~Lƅ;@J`m1 dzn)|ˁf G<)䡴V\o!chIA̷ϛfn6X\/!0NYYL Vk|i[<.lue1]d2A39%NxZ0&|!"RD[vg1 GiGV$c_+$w><8Lh& NqzzcsL&W)"-ü#b]Gctf ;4|%9Br$D"9FAmY_8XD D+E y&NuȺv5u))5ZNP&ޒG0}3WQhO<<>g=)|61Y,CimylM[P)Ť~2/: % bQ/(B(R!N߿S5q*hipT|?RJdӳ[X-خ[t}u ~@׷dQDu}>ߋ_| z''l蚚]D82`x}xp4?t^mL.win?H@D}%~ ð>E+{Gض[xy>~RIC K2t#K+3JIZ+)զbp=ҁ|RhPiɴlӓcǨ*S}ڔ%i^H]hhgѾ,jIN)$IeDHryb}ע먿̧!+$OX1'GGxSOjl=n߼chSq} Pbq(h6P`*+|ޠ1F d8·8G#U͵@"ރAЍH=WH>C( Rj:/es=z@ Б NzQ.{ de eM?=.?0E*d$Rlɱn^ d"ĐCUSE?~s8 '酦Ö,Ams$4Cllp= ~lC>+g{CRSwXPu֕BI1<ɻO`; ѣW0/k shl͆EH< ( @%p sɗw#!Yf(09:K\sw|)G<>T߳#L^Lf;J -E傀 >5ggޯ../;Z62\9F;ʢ7b=oD ֣MR7v 1Hӟ&52abYkF|VO-+ez?%'<˔($쉪UD߂cbz1+SO`:xbx0qppwJK?FD&3UCcXcQ5 Jg^u3`?OkU] ;lM-˧b _I&Jd/$jp|>%Pt;`3WR\;Ǧ,U(8pttӳ3xOS,Rsuզg4JA=^GeR %kZZl2i;ۭaHc6p-"Cw<sDэ[f 2ur!ǐ鷼9Z64EZpww{sqxr_7Y믡E^)8J;&F 3bVy;eժJR2`!!dy.ɊԴ"'5zD<ܤ7 piԣLM>)B7NX+N$ m$ HfZ/@fEɨs(TB?B R=;[Joccw'yl8|iClg"m R:1(q c()Pqm9xdƞ!{zVsXH:`ɇ e(<[BADv##8 }J`0`Za0(iJkV(PRTt-Ghmy6.=9@*#?Z((\: 1I]C*eZ˖%U+Hlr=֐$tz\cPm*xDC94X=%/~MJ Hl/R9{ >D ' j˸I)itֽ&zNFMd+0ś~%H8`Dq7զj j@Eه*)7*Ji D:5{n'nNz|^w5v-R) !TxGnBűp|(Lsy!=iH_@ Ou+? 5Pmp@S4&zzzfC\R 7`A~W< `4̗𫷿~J 69_=BEQB = ] kڊ"٘h4\f)".w oLbTAH]GA,EHIB/{['.x2h_+u!%%6[ Ըz$.8"G'&XUwح:8#㥦P(z%ư%'2AFؔ0*!MFJfVQ)7*fIhQlUYTHZil5 $3RRP8L9"8GpC:9=3+%()[I!&|DzS~+,ka% ^b*)%Kz|l~^Vx*8v -.`I 4ǪXAMb"+oTDq&bHY(RPjZkyJЩߺ__Nv:aӅ`P`4b, Y bmJfc\$l-f}S&Q"9{fu 6iccSsΒijAT3@p,fX,o1=a29p_J{xomz@\l-ٛ+/+_R*0֠(`3@Fv`Ct1)@HvTR'm sSd.+z+B 3 9@f̭ko%}HҐZB˂ SS 0x!S/]͌{ܩJc`9֠*45I}Z/0>;WWMx@W_KZ7%5!SY2&u`X>kS<<[L/ ڔ(H0AD=XzN;E%R&`z]]i/hGU V) R)БtO.P=Iׯ4.gึ6%)Aɝٜ׫yGjB)yI%<%1 6,LݠkS岮Wjp|| -LNsrvUXkQ=r4E)Be Ěsu( 5ΰ@,O lxuJ 9;Rw[ SDHg/7#YO/A$bL2KH뾌w{O"f "Rr :nšuoMz@? $JvMݤ]yx#.,94u:O^ͺң'CL/Xb%NN!l ~&>BKPh cY 鶴H!ĭZ904!TD:JpZ8ߤ"\SzЕ񢞼 AXX"bi9O5r#̕bLǞDJIk)]HibbT^d)Dr1cA7ĭxsJtZЩkO_U ںA,zefW/nq~1dr'l\>T+z "i~>\4ím1Ji,MrWPDhG4 8ky*)QfSEZ 8§"R*N|=q}H`/y@$"Dvym C"Z*Bs%h)!扂$M LhN6L(JRq#QuhM40G nUto5lB ^n.?%PH7jE6\:$u.8u&j:N6gkOd%2rM{$At̡:[Ȩ5Ͱmg2F8Crƙg& p-Bq',!v'!Xԓi:Ak Z1pãC_̱XrsEB7tʠ?(t^Q-D8I"O u^dĎ7 E&upKD :$ 3^A#wk $O7WRbhχm@Cl) !:;-jppx}HE:k-l¹6DdVFD̓I9 [`z$ X1hjj[֒8d*"%P  L/8<<>L&l~G:]Bkf Q7Bi&1eY`ϣEZ Ji $ =:14 d<ֶgoh| 'X,o\bz>KXa NN.7fUc;@YhZM5ePz( }3q(".ڐ/>q_֍d>&=!QPqE! lfIjH8 Q m|^9;Kkky EUv,ht4o Ds @ӭִ* ȴ_-3jM)6} c׶( Ѩc<[( k>xg^jZYX%M'pWzɉG)iŅ%NC $)b @$  q@  "2kϵBwH%C|L-82lDTɥ֊}2ꔄ:M ݠjTUU7)V*,p|0td5ΦL)K&S7DYh4jI;nŲn:j=P 9" oZ1(wETGn{ra磸_t+㺸ތƘH5|xR*A9Gnжsh;ߡt+*KѰOG8xr>K&_ۯyX6Cq@1^ڠ49fAJؘ<Tn1*CPJä v'm({FxExcE0 vO4O{wjRr:-X"W+7+Uh0p6=-L/f89ɔۏ^K|;uӢW((eޯ#jboC+BTԍvTwtt'hT#];&՞Dž>bԍQna?0VuBl^yhۆqvk h-Pm*6BÝ'͖X,o1] {rK&8>9[?1M$ v\gI@X}8,n:B2ԄW}R/dRQ1H^DQ79H "s5fWRroԨ6uc:Twqz6ry e/mū~1_(K{PMǂJQ&էTRŚ_${IsRw Etåю:˩d)ҊO|&5Z4M-#`Y^899lmgm^gsk~@߃^pCzlن'a*ߨ{__IQ(8Sm'ƄTl0`4δى1ע?uE?B"K'<}ctʷ-.O\bĚ_!zv}vD<[E cZz% zϜ82 t`]sd!-ϓr%VZ+lxtkMp~>kL/.qx);^0o7! !pGUh tts%G}4@pB^at=H =1"!G2ἧZ156Fkں^oXMא({S,dm=[br)<^-vp`o4@awg'N _<ZhEuSFeHrNB+xCiCr GNO(UfsD34m-BplzzFS)ƻ8?5Xg///Wn Vpɀ!)Zۂr#YI+:{ wuA[$nJQ1kMC՚uSs@tM/8>9hAl'W}UѨp:.v1,ijjy4A d|$ F_$PЅJEܳ("!{p40-TuT˯W{71^_pz3^pv6 1|k_hxApƬaNl9 zh].DLyi 8((ޢ'j/7A]:2lͺw[}Qtz71/qr:^d< < ~1굯5cP--: !eQ@7kCP6(Ti#(b#MӢ1 aL61 65ji3`'X^r"/\nğW*vЪhw)P>Px;(-aG_JuZbB`GW{״dL!xkoc>t]n3>xP|>_ Ǔ=@~B8aoBkH;@EQ¡/s'O&Om"!ǐ٥ڶuRJ vvp91f _A~hq %tCP[`?{_a8vh&ύ>ŪHu'i(;x`MՊfZKy =/#Ń??_uo,\ۢVK (ѢWJFR/M i)<,Z 9@|#2-0/H~/K:EYCpy~!io6U[͆2jSxnnc GYXŃ3x'F6߶]4 q7՛AZ% FUUhMz/p}W8>>š 07~/?hAP1;C쌆xzpm͆|UWhR }77`,xM8`ɫ>G}M'gO^`I ׷X\ar|,x?ݟx pXW/ueddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddC[.2IENDB`OSCAR-code-v1.3.1/oscar/icons/airsense10.png000066400000000000000000002207641417327530600204060ustar00rootroot00000000000000PNG  IHDR\rfsRGB cHRMz&u0`:pQ< pHYs  YiTXtXML:com.adobe.xmp 1 L'Y@IDATx Wyy^{UW.uHw0dXq!''vfl1d&q&sL|bxClo$ԭ{Z^߫[% "!R* (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ (0P`@ |c`}} 0 `Ǐ9~γK[ڭXЫv( ӶW]rGJÚI +>z 1]Vӧ{gIw=w{'_Iv'4SK4Z Mݻvlテ7x|xǎ>]FP {4q05g~ _K?/W{^)p:# wAw{inzNc;ڻ}ϾMhxEC0PD/X\<; LH{FH`ȴa)akW0(UX9&!4>𼔶,lM+Xg|{~q3ʼTdۉ5f{|_㓟wvl>5913O \U=}{R-UeBZ'P)S2e,ߒ`tezyJSB(N?~58#pz@!SU64lɃx+Nϯ6Ai7 5߫hR-s/^e_+W~}~@27Yd`K[f|? ?;ds`>˼woB|kLvu~Gn~aھST&냆ZyGGp\j# gM懑00WAKF]'D;~d/s\ElI<;K[ghq}s2wU$p9SL; HA^@Q,w*v3?<{V/A_+I- e[fݜ?;2I^; Y^ `4 F3Td>'"vS-'O{z[D?ww}]Ƨ1"Lľ8G{$N|c-0BPlps@K[jz۽%AB9eխ fj52,p{bb:u;?yMf 4]?}F҈2&}e Zb)e8 09: `a @-5z5?7^?- &ry>hɷa6 WS^L ħ8Ǹs>#sUθ%FSAnZ݁ 6dh4w0^\F=o! Uo'O~OBd! 幡{\Nի** Bu#$`XlS1թu&3TZp 3_q?gZ^ѦC|Q#< X"5 t?sk_a1 3È[_< R& Oʲ2/?L?ݮByx L+8h,-ɪ"ƲmrMp|tD*y 6w*_&lׯQȧCu22sgLڃaHX!1B Ar+.60UAż +X)Gş@SodkdJٷ/]VoK33SV5旯!BLUu/6] yWy4/ux)%q( %}W!I[:W,Lo*ؑW&10'BK0'sAKq oRw^|/A D(HxzZ K5?mG cGUʡyqڎ K2  %0UN153k7K;OJ7?N7|<nM;v5[kV;jwflߟԨ7{ݡlGwa̦۶[mHiр VZks\nƆjZv`R=%Ӷԫ`dH6L+J¼I9K[*vQf]w bv'.,+ޛO]V[S`b8\oII갿^ВFۥ&z߫c4tah;6kӏ+#^SM :_:a:^X`bu4MgJf*hq.׆ o3 k7`pF-̦.2=OOOySҾE?9YcZoDXZ^]NEhQ1) `PьP/lXV#E|),/Η D-(X 0uApeE\e2AN)P9`B1#̑2Pfգ4BA&szJdt Y"ohzc BhНBj},aT(uvI;#LFQfD`'e`58o=ՄW$sб1 vKӟMڙg<'K^63ń&8YHsi0l0IU]quSAfz'ln|>*4w uJnQT]$+)d~pTÕm im G/$cbaOgȜ*,nL7ut^uVz x'$3(leKX+D[u+Ԩ󩁆<<>ym+/M/~Kҍޘ?xJTN!vp=#N!\kc2gHClcހ-nhTc}*[Zz 3F,&V,LXBc bAed@wtcL`"pp Q^rf2f!$,%6ianpҙ8]_Ǽ~!YV[>i:ϧS'NcGN fSۭc08 f9:i׫$`FmUűׄ8Xa~yesWh+&S0)`ɐR/3xq_xܨb+IҺ0—jPe٪YĩܯRc_*73A4ԕ({a54edhHj"oZ!3[L] 5,lbS"T ÎέcazAu2MWjA-:ZV\h "Q>GX;IeR dBB G p G=J*N%Sױʻiδxn%6Zl9ĎQɾۤzT<ٞ bӛRsδɀءNRc鮯J{O?I/~m_lj~ `_8O6?7GZ\ՕՅJu|4ިWz'ڑȱKձqa"M 9Ǯ?&?#"WvOS3eqI>f,D1'@x*Zp056@ M) ~8 @ӥذ2h(t.c(ŠRM- #]B’8e)7']iaNVS}&w۳i#=L,SH1(+Ă(䎟<?P29X">]gQ(BA\\^C+eu w&\J#r.~0<)x'SN>[nbN2M(g~sd_ncNMyڸLA44} F b4NJ=:YK&7+N8X2>heh'$QBl~}[N Ӈ[UGL1nC4L(}~ W7ei@+.|cv +/uqn^;ί6ZVsvmIjoqQ0DW݌Q&S_ff>͌JaL]_D1g@x8Jo f[1uc7j3b2|.vF9/ Jsq%M-Lk&=&p.зA>2&]%MC0&ܯ!.ХTy/./ffgVNCu[q27Hdխmv4-/\i.otۓ`6OZfAꜫŒ>LX BHZq``MUerleNBBdG('Ld›vRO'ӂƕeU-0(52WH xICPYGmy̥ AC#Ѧ" ]MUG5R7Z `_skK )AA2L zXԵ5xeCF½&Dا(u XwgNޕ>x.tZ"`9tۆ#o"=x+A+ojz<>u*y@z_HC0{DqmdEAϸnrz$veT*X>ݫлazmV}GjQ~noho{Wg9f \ٺZ{V5ۃɻ88+DL{,)1#FD*ZW>4|P ? X#9Ep*]2P =s`qCO2P0KF0y61Hv:4?grZ+jiO-țV1XGh ؄yTz} $b ^ .\A q_NI3>(kM{/dX6BHG>8VC}GZ2a{LCҭDڷ{_:qD$(~!p'ꐲP|/c_E v;=2jDm KPI=2w9ڌmJUP\—ݼzZ}̊0#6ږŭ痖7%oڭtӝ^^[YpbN,v!f5]6Hhk$-soL5+3.^d/-j2CͫRY' Mi'4!n:dA AP]1r-ڹC4!H>HrJ,/}_h>c\+.$k1 % ( .e"^Xkm2Sښ4!gW\mH]`6rZDQQ"j![mZ K1EIH&xy[}RAK(WQ {/HOFح-?n-]ټb ?7ͯP x ! mHo==ZH`À }tU>H\fr aʯ&!ض~~ח| F=K;W[}S_Xi--13ȯN\ +0m9n $h%ԗ70uYR3T$z%)DΫ|8@ B ĖMo 5"Q/ RIKF/!ZQ S]LְMkhnɈ1.m65 tAZ4ޡmpqM6^ j2yKiheQ=;g;Vhzkɱ4L p5;f Ōe ҭ дvwdlBF9TC(AA0yf\Ua6'LAP#(KZ\ܞ*Yx+a:2 ȁ;,%WƝ+eH ?}l:tw~%;ί>Zc1.xmgzW,I4ҥnc]h{[ 5_ܹroy3nkr&ua s%G_v1Ruo^!ͶPRIKlHnÄ'm &0f&$o"'>ZfEkdI5{7פ6#0Y0ьe62!P\v*4%9K M lk+$SAm, ԛ5HRc0py=ZT;ᥛQGjhx|٦SgR>vGb.64M`|,_>L_5&k"G -]ƦxB0$Gq5∀VK_=- DVV09\M RS7hF3X&>WYW;D:뢌+ ++˺& I0 ; pK|&HTtaf 5ƹ` 1#^沎}@MO}_S|y$n{g;ζݟĻv.//Ni_]]+@ye%3k#<9d!?n5EDɪnIir)%f};^K@"WS*(IW:3!f8L>w*T`qW^L]OQ k'8p MD`U!̡MU$"ZB01hw91؃=iK aV\7Nmuv –:v$$`˲B$zZXBR_k X$w_''N~4vbcl#H^auuy KJ7n I` iڴτB稼j!( 'GY'GPN61RL./qBZ[a= PhyI=~aZ*.Gr]ޘEF,ÇđVh6{҃gO}Oڿ!9IsnDXPX.Vi#=M^)hhؑ{_q~ܕwrj|W.;%r{Ӊ#4䕊f*,AsI+$:qwF*2LU%B#S 4^D(D!maڈ`L]uq*+s9< %bCSK .@``ċ#>Z\x#V*@q6ȅzڍ9*cc%dhdrS'_7[oW]Tļn60էII{ָ> ,D#wmkC&} 0m8x5isd>/jxs 2yVמqgla8ԧB!qַR: !ŬˤAdqd_ %,fDp(dsq2͜wqQ&,=ayQ!6n6iyy1*K{ADг[t:v|$̽CnN6?GfK vt [#| Xt+1;}gFa ֔A61fGX }SxS  B(,3 А Ȓm4S!2}zE1+Qԩi %W{t 3iVk9EVOˉh ]^]I{uOxn:rZa[V*Lby/u9ye3in1'C.RDnc$I]zI6:g!(0BvPV`|)XƇk| hqY6B|vY3]}Kxs?+ hq)$[ R6t5@`|&,`u֗IvO>kj@)g>s-0dn<x藹&ȍ̊-AG@h\M[νFѓrK?0T=~ s]p79s/~/g,I4GihډDǫ ~nH\}eh^xA^[K 8B @`C,2ZsG(:ZG\R*DχZ6DJ,j2^SHcnȂ`@$4PZäƽfCc{VZHxë́Hפ(:hAB] a#|ez{m'S&1!׮4V 3>oOc鳷ߕ3}<]u>ָ%>ˋM]Y] ",@jG)7M"]⮁qϜÚh$v&9J,:`&[ҧQ}$[hU ~d g¦`+_c9P\ҀDPNZbJmˠ _]ii9~͠Gg% i1T>L`Vn,u\Xcn-H KΑqfM.- 0~7/o^~g<k| lkޙ|y- UեsMĺ/ecR'nJ4%]ZeOޖ.sAz|q~)](}K6r^>dr$t7($"JDjZzE@RʔU@lyr2#w*p% 7: EjdI= 0~Ӊ\ڞs_4At1}o-_ʗ2-oM@2a&!‘̛K1 $6Ûۉ)J{EMpcZH:zýMFă'#IJ +##.dgw%9[W8w;G2q#'f%\abGHl/%\=PDke ՉtE ]C NL);&Źȧ@' }痚!Uv4òҼd!8v,lnQ2tx `CF}WsA.?=$Ξ9{q S}w[ `HR ܙ鞻N7UN?WE,=/9y:ԥ t[$0fQW\^2WX֜Gԙ5 H¥CZM . ۗbp2e4bw*qhh./$8;DsŠ_ބC13=9=C' 4֡5.,K`y̸ĒCmc $@wN$݊&?G0A҆0N nenĎК*wH@a RM@A1MckU-H?ߜ{-Vv\8fٕ VWs4CЭr0di_f(nJ1KC..{K"XjB)kAQ x_U j2 ]࡫Ȥ;\?3~.a e$ K]\pQ:pחm_5=ySng 5;į;K_w1~~&:k,]^/*)ĒԆB8J\T*FQ 7,rܶ7Z@WXh ~YL(( PP(N\Oco4֩A7@`})h.?1luwrj,'w~J 5I>>\Xu`nj!f#NxhZC\p#$^ɐE5u=ȒW* /Z Z S+Qcɭ%V&Xc `BckQ .cYVLAjF$cUe*~J=k9o yLۺaR XqU S`:hx>frҏ`̰vhbֽ+,᣹/3ځ9=CQUQ=@R-I|ࣀn[wxXDz_p c2Ho?ӏ^-$b5Z60gw-w|sz޳p2(=yOKl(w`b/qT 'o'PSVByF=4QwE2$dx#<ɥA+e84W;{o6Dι0γ'hC%+{lq  2Y# >}ڏj2Q%via+k GˠuS0wmU`u?MS?P2NOt5h!D_m#XSH6=g?jL/ed -3LL0<0do$ 1|Wr?A-gBD0)4BP_tk, >0s6W"+ļL?VC>DcN,R|A\Gh9kM +E^xmO`$/QGK*GoC*-iIT(ZҶ G~$&… v0\:^̩@`[yT&t٥ץ S!T~mt.ْJ+tm=LI]/I8IU+EbBHQ\VdF5'lǘAG2.Y6RT9"LK=ղpLrqOtҷx"WF3<kI[iKՠTݕ:8Ő= y6 *'ƒ0=g`Fmh43?Ш- p*15yΆas'P*hEFC8ZfwI|2W}0l1*6 n+4A“e.}(<D.(_dMpJ A W,-E$"@Θ5y&؂yCl׸A1!#๱[c$u9woG60Nˌ.kzCfbmM2æ1cn, A21%P!#n -,lIyߝgGؑbNLeY"A,N T"=윹r<9V4/p+)DFnVsCh#ctN?1זڄFwAw?jDVּX(ϞAZoҗ6/2@$?t5UIߵuqҶ馛>֨D]`M%Ps ͥyqMx&h%gРL! Zfh`!?@T tP2!ꚥ&>,TOIAmҚ(wZh(' &f^X$L',;Y1C "PlA2C0!јCo[B8Zc'3*a~aC X:eqѶ.¦&ʁ#D>$`ۂHY=Q@{Tz G`@qA߹I)^ܡŽ6bF#ZPcpFxqD)[VԘ32xu4l3-*F?}|EVo1)oQ1PCv%Z43b=ao `/ A٠p)[j 7pҟ f Fc{y/9qpE~fɴjǏ(]|t3?k׶ؒ^ `K,˂$nà2K[”[GuZn 'wiU!vp>BP y (.A\ʏOmDf TV]jd6ǟW["gv(Mp(]G L$SB`\r-ٖf]$|4K&nDi%pX0`J/w% ,O %,Ȭ[[Pi&hRHpP3,-TB"2 L̐/xџ$,iOKX7X8 Uxi*Chud\N .fD5,N Zp@;+VDyU\|e.{!0g[`$W6dq+_vBNWy,td|'m3Q0{W<2"NY0}?ξ9"'BXÖ"Bvd^pC߲mdi֥O;8| =OMc9?:8g|-qh4ˌ8\}#Ŧ*$[W;gI|&= 9J߈ y,%HD!,Y$XFD1\I_ɓI 3A<۲ HJ7& a! NK,V؃>{2}jNvEZ#΁殮$Koc^hpҀQcsۯ8s58M;کҵ=^LI4ʌ_fڡ ?nЌćn {Op1ZofJ=k2 >\&=nFînYFyjYSQg(F,mu21ht !5 $A10W9D{sq f^H|yv5L?iI zK.+*Ϳǎm,saT2k%6]u%HGY2{OlA6pQ_W1V71eH pT~txȇl?1 79a0|^S]2dIܺ&]qʴen; Skst}Ȃ @$9Q3Įd:!\840o O1:l1(M\c[~3yw=fJ̛>2LvD~z dDa, }ýtq_bS)9ъX k-1l?21HJ%PX@1x`ۧɫvn_H*P^ǃlH$YWo!ڌxi~fpڴ=[= {61hpنYѶ((!jMa^W>OvB?Ch>㜯Qq Qq0W Fi 0X \KM%vN';:&iwqG-'ANόS,gNF xu; |חHצ@0wUjRWØ {ía*u<3ao:KrGZ!?ρ 7ѻusJ p>u55]WG He2\`W)LX6ry&SI!-,HX6]c 'R /δcC:P:+x3+KA$6ހEX+ ,.J[eE(m2c#\7h2KV Iۤ_ilkPE:aA7z(y[qE pNMrdq߇&[8!bG Ca.Ȟc\#hii5N8HG+b^)HQo|w` ٹL}$6J>L4ZLX {N͜jk)ˬP]9cOGT i޼ Ye~A@GGT9SJ@Jp|Rj!4֔='qIl)!+zm:)7{l36qc I֋?7 1R-JJJ|; ie.4R ijx5iו 6 Smwvr s&U0'yy^E*?Aތ4g00lK2?nd_l8in*&p94 (qOlykCЫ?qB%l'IJRפ^ dH J.CQipl=8.N_UݎA۰ߝZU{Q$8N(,& GBX+ ʗKM[p<+bL!㙿OFyl^IU+SEQ̊bCc b@GwrEz0+If7ՈqWG XS,2=i M >8[fi>ƃ|HJE@=Ǟ*LL1) jr?Iޡ'hY5 Eq\.3)ZnCaIϣ^!΍+Q<߇*{  H1rb fZop`1B,+4[ΛDmPM_ՌB(#d @qũ匃! LDD]"IƐ(jdrdA)4tn 'I^ jf?#b0($x3@͠I .P @7ĥB3W.F<̥^Fyk3dՀGwO^@YIr S ubuH5%ff0EA7ݵz*_^;;u4ع7͓hhT!p݇6/5}q*Dt=`l:>Wi5R9ZزPЭqe@ ᦀciKI v$`yR߭a ,1!/i% LZ׆i%+.شbc2B`;) @;FA|aY AKB!46my#]}.ǐЧӒ@VpNJ!!-oIO4w"ֵEۿ@/z {iln;ӡYƩy+ B~DiƕCΓDVeH}qW6wρ:-'#H]hzPQinLxd|ɩ6E-d)8k+N ^ԣ\пx|0H1AvMAK'|2>c/Jt,;v|a$L?(dH \ Q `t$j]&jdLUXԩ\}.m}2:|(]sos|CivhfQj%݌]#aN`.&R,Lp@\H1ơdHdjG<4$5#=$vuUVq]OL~0w4J F1Qؘ 6Al{Ӷץ=Ye(a|Y+ۉR\DR L8YA !p@0!,[ufe50ǟ XS4ȸҲ`B32i dAsh1n[~>a/TR@IZ]65_WqXK&4Acy۶e]SG/ːbRd|TRA1yL.bVFs46Qcy `@(XBaDcEm&|M;vs'"nF1]׏Sa#'lz6djq5Χ(sb(?kդ״`pR D&8\ h˵'19S u#6֣ے 飄O_-y2mRk))ۈkni{6x*,$|S[z.)1Xe/ol+4cfOJ^&y=>Z/%l:~Pԟp n`,m-.I5y% b"pR:%Ǹcj2[`NOyt08滫e|@rr5CFۺN%.7`S_lґX1^KR78/~Z[XP" R7GK!x;{_r%V@&*y v 0;?>럴;Ić# 3OGC(UAd=9s::t?!KHw>\7ڇ[D[8CFX+J}D L 2G,) ksR˥@0#:*[B )Lf=q$n=6=8?K.*"NkI\ؕX xdYD^@Il8F&,-ݛ,ّhqX17.>, X2ɘ A;AXHAX"òIam0;6} oө"@x{0LpaN ##bZfDKhBY faAhznrţyp^Uά-,҅-wƶϸo[j!wr}L 6 0z~8l0BOK$r| AgDĊ_"p\{Mz=[7z1 i. )_v:~D:~`ӶSip7]㶽"W 5 p!!8<$.) 1-ZQPLY_!^ޓɌhh`AXOX8u7(d0o$f%VP^dosJyC{G,!hjH>Yrg(qҤDnĊKoxWSd{ mYqsXG/Ҫt$~+r;]4gtYbZ`huXpq&  be ^ yt,N 6v{v9,#X۹QNuRΓ ql4âr8K&E<o F>bL0ϢWnSG%)SqeO"y13i _.qP:+V C23>;WΖ\"̎HPBkAY1|0v#Rw2{r/}Kړ2<{"I5ϣ0mؽ_)I>vh$mY-0MLdLqu]:ˇg@j.sYNmjJu:A; nM:)Ie}^ x,BiiL-F!8rKBmWb>#x*e5!%`t4hd&R3tE8y_myWrD"ti՚ϠƂ@}W?"$b!U[]H6@A4숀.;F  =,1|#G&}k Z+/ G^󏴀i;/J!B󩯫?3AȚ n`;b +QoaWXRѿxvx/` ܃4u|?F[gQxdD_z1kr R%g$E9'05GsOG|g(4e& olXXN'^4gjVOyrL;<;wwE¹vvzxNn|^eŒ 3 ]hCS[OԦ7.dlsd H\-1~w7޳`5QaBzw䂕W: Gc^(~{NG>s M$6i\ XQUU&!CEv/FD{k+]ݞFN@ց59 4dZDo1I]G tber:%ӜNH8I[(Pip3h $@C";{t=_̀QVI]F#˴څ f$oujtLg)Ϸ<7ȮOȠ*HX"$Ĭ0pf2NQ]0)vHL&84&b#-xh2]Vp;8A I9N80-FYI+7_Ǘk1noZ" 3}IhL.S)}nn,L#L($ZYBGE٭;XkG(bD3mcif:]C߅[FUxa)G%(.b6qϾz"vjQzVݗNk\]+t&Z}nU2WU,f,߷8e-؞PN?8Fx YZX}+bz٩řek7o\!Ͼ ;<&P:U'Әԉz빙0.x.:^iD?Im@0CT \gxMJ/Qx!"zU+g1}~,%GHD2s.5}@B8zdL"#Q2MFjRBX't%Ԥ1m6w-[:/J@>~LӕpGxJmF/wmٞb֯{"L 2HhZ]nɍĐN@sqՄɰƨ=C݆繦]8 U[CieMvpM]2QjS 4}ueH9P8s YeAyE\&w"4RxSq'%-b,>,hJ4.b +ht2-usWZğ}xݨTK"Fm(dr4W-=#իx>psP;:K6!];-&(*>#2RwSa}7x ޢ{R_sPdU&h}(.;F:+#?`Ӑ~ )H+ g6OcEV*Sa46fg;fn%~rE :WW{j9yqN?׾31RffXES.<~p~|l4/383Tr<#lZشD12W܃?bn>~w%(@4Z(150X4eR5k]M07NKB'4i@nT4,ڍzE?CɬVY %5!;ivS hsG=?Srkڦ#,ѨI,΋zS*H}H-"J|-Pƴ|x񃟵ğ4 >lVB `F0B3l^b;;lcB W9;+IˎCr? 4\x#'=gh?pvniscT+\^cyz-O'9x|?ZFI.lJ0q G@,3 $CB\[9 MalMz5n5 17atjENnh]ipW!dTA2>Y39&] fԶ̮i4͜M9r*A &[fHIXgjR\ɼ}0OI$k-Kr< 'Ĝ%T_HoA]by?Dc1OCNʹpVMlR)^ӖaqK/&7I9Ks9w|SI6V<b'v7Rn=]q /#ҕjmW+BMw"Q`@ْ4"}kiڄEo}󜣡ہ&-&2Y8e}2NQ5#. m>uQ \)4tzծĜ"DuZB%M9T0c :@ m:T_;aJ}aM Dׇ#_xrdYSRߚBwpYBƯ3ˤͰ hIiN]%џ`|Vsiz2&ev=28Т\x'vxrmNyP69c .}Xff:L ;4DaDW)Y5s Z gޅ@'x=mF8f^pR~cwI{"H : *S`ri aDg\n#z;8kBV ڞO~ KVbqƒHlHCrG?vp]IG_Y)=BV+HZBxapHF:8¶ӏ $xGDgn8R;! BMa@S cv 1=e|j!%YPЏ;bC`&UM ɮ3sLES AbΧڪ:c=ΰ0nN=;]Avce"tau91ZEܟ-Sl4ui,uafldkEy D'=x^7-ɜMx5 /X47B. in!9  Tf6-v6-:U:Q7%5xɈdӎe{;V8s:BA`&*o^b;zfl4z[O*{;Ůrxr5:OVDTwf .'WV9#vz0;)N]g{237ȌUR,P_!+ _ԣ %ھN9*׵X`b Ʊ_ (T|s Ha %8 \ח1 b 3&@hfB,e5: cyA3Y2%Zm#}ĸ~C+Ҿڌ?{4hC@Zw|+J=|K^]-z JpFpg׬F:V Cd:"qn{ǟz= LiDh0Ul`)8ʕW;N f?[L#QGɤ8 eGB:+ ]xA, ̷K_W#V"'V Z1\G&FϞ2c 1xʕOYJЩ!lSf|彀IYڬ+-lF98XHXL݌@z{HٖO=;ow̓c~=`#pǬ%+c *6Ìsry&fDشlMZWZ9yӖ EǞx5,9 m0^.3v@*Zu܅oPs>{%dZ m;# masӜVeL`1~!B MRq]]:k!`2sn,n ͱba|3'/=q Lre3WLCyN_BXDȫ52z@ȞkLK=ϼLAg v!aoQ6DCF·Kzvx>n1l 5? #XCWkLG{z1ă{޽`*̔X-/( ma%qthH+PKX5NM'x.LÔ7,>!6JʳN'4nvGWFn ml9tт̯u3@vĹ8O#՘ytF7;]Uzvզ^2a1&rp4CrXR.P:.*jlj +JV#o`0`w0q\*c d"TɀPU%iKė6QH4ŜTc&&T"QqB-ɿoobޯܒMYU ?s)nrK 6][_%vz$jD.i "B!*NűD)8bs[fiikA:8mUy;AdOB/͸4h#crJ7[wnVfw?xܧI6 mᅫETYX^QCr CysG'_Yw.T%JB $.iJqQ:x-~Ei^p5NΚ:e2$=`ݭm x _AlALjZ*,J7kj`SQ'ϣ%i[ec@0!>*5M FiL7VBB% k;`4Ӱ\p\H'Dܵ* I֞lQ,nB :8\39T9|(͖2.GN3pL_Ej69#c eQ,VJxf'c*~6"b҃"&/% *<\E/J~)Λe;{ܾzݝGZa^E_BA FZc=,6fBYs;`'cpbx'e*10J0ju Lbѹ pH@w]*橭#fiQL\$J,p9 Z1Ϙs1\XNQA *> mL9K\!;MA,Fe3,Ao t* 8HL@A1k0'["VO.3H|/##NLKuk%Vv-0*C7RA]yO봳gr+FZ~?y"W'r_yʯ//rcv*[`td0u!\$С#݉J WTjJ+Ik0uZZVǀ:~.oy/V4.)/$ud!20/ec@\c4>dRed Dx 8zMd.u*y ' &q܊O \fG[~,k)膕}|Љi|byc#o=)Ə[K8l48E>1,?A?Xi5BggKm#NH,iCkYvS;/X3JBC3̹#ҽ}`ztb$lpIg?`J;?Z- f"J}=--̥:\@O$1d-gȕ23 1L֩㪣6'EdzC~qEִ+x"3&ZYž`H t9F);Q( 4EcmLo E`uU2?]^>g˅0?rE!gU4 l ΞϖcG$Ny2]D 3А]7F9}Բec<]A/ğWVji5xCP|MW<ъ6x2,%}.{KX@e[~kac,Wf':5mq~L7'^VPx<7I3u4mzzںnOi"p_- >)zrV7`@cc[ M"A&CE(%Gtu]%:e0tQkjyJEٱ DŨd.\_"ʺ~S8eDy^`C Z!,M1O1i;@X$jN͹ud.P [;D*kɒ%M͆%F0TNqYWJbCAc+|O-Np8t^ܓ0+L!?‹\7,_g.,!}]'A#,eܛ,8vgOrߒÎ`|xҽo$aZD7~)rkf#AZTb|@Ĝ"xOnhVws TJ:/wЎݐoTm~pK{la+tߋZDӇ>ϠC\RlJ ~Q:}kwsWRS"VP@qvy'$9TzTN݅PODjq# +u茎M m|@s5A1DL fj>9n?#x00hG- *4Yn% dz2ԁb hN=2zF Ƀ"::P-M 8 ~\/MGUjyh bпeA[$P)(8oU`8N ײ=`SpgP\GS8gh"hn47%Hdz/=3";Uĵ{jhr*}D ,):0c./&H_*6E6w?s?XֻV|76VTEiXJa9ribPn 'AQN!G"jH 3&Yx c*<\]ռ :~\p"Mw\ 7:#D-,rnh04}$5AL5Өl %S IssPԉE{s_R95{_xZ `Ȕ̉DlC`9~p>\cȡ;d~P3cN8ha>QŃsniFv-ĝ-"}&n9Ơyߕ9B߽.YNNF+:q'RmԼ@TFԕ`1/~ӭ,u| Qih{86$X_j߹j 2?~\̯u/,.̑wtho&ȁrԓOH+8d pM%ֹZU6}n9=7A7R}LH ))9͎?!k0@InZ4r#!5E3q.Z@*zoej%bY&@(*{;([T;:)ӸDsѕzQSkQ;2w iBh2&`I)= PdvMXlŹcT %xQӪIW-MutJ\zGIߥPC;ZƏ+WoocSN(!/>ı7WUU/X2srxܺu*7YNxMV@ DDT(vĀSJiVbL(@"+HˠU_߂XkSc,SeCr؂Hj=p:}:OJ @u;a(E~ hkkucsbeyYEx\?.f X]\я:(s ˗p1ϕ;KgјYBw2Op1 pOy2g tXU:@#,+wc]_Lڛ&@N6j5SA_S7[Lo3&Xﭴq{W(@kؤWdfR~ʼn}v̷Yb%hi \1w,+Uksk7 {^ ;[60X\$isxNqR^!AnSٯ,kИ`mwnƗ@ko}msSe֭ɻAT7 L16l s~~l/nyqp"\io]V>Xx$~AQ|}r2U8\* Bh;Zk b+4`GF; 1q2I3@~P(:Z*d֔}M)ƕĵ̖g0FiNXL0Akk##"1&zVz++$=ׁ!Xg9 ꐱ U*fCDYvFNPt(j`TLfWs BW%iӌS "wLÃM9Q/7PEݥmc*h(FFxvɄ}ClX#ƞYnBhxĦ8vUNeg%4@1Gv 1e"ܤUiJw(fi ?=A˘cG1 ^P TvmY "2y֢:$i#Ey VWلо]⏮[h& #};K+-d*G*n^+zHn0v}.ac߸PVn2?=_j93/_1e]ӎ:EWs"d G &ȱu4ᜃ:O I e.fUXl*2|rtrǎŤ0RH<>,L:NcRF]mD=t ڂ00KEѮh)y{hpvg(evm@_{trhlYc=XSOkA$ QKh45t%kI=tGxIiÿ_0Kd[oJpN]gswmd-0洩 Y1O =rl R.)&G4n7`vSyƟ?}p҂ /V#lz4.]J@zĒKǞ)Oen2Eߎՠ/o|l>i{US@Y\  ??kBcAIBf̤s$A PM* lsbZr+_ۊ.ePFbm>?(")\sLʨ=-'sC.D9zmU .HeƅW@Pί' "EZI~)# ^)/u9g !W׎ERG&%TJD:urĻWz6 Cad XbPfղ2@lWsOT*e|J>O`VL>JڦBv 7#e)CTFqo'C["4Hx/Vrc8ES0죈޸~^ԗǏf/|VW{VD*hvL|%o>o#Z*:Ui $)޽(]=8VK9)hXy^xg6's=w`' K(P9ty=Μ2ktj}3S:@'ӸF z@ui%OM~FB'!owDPGׯh]qACl@$M/&@"h?ǩ;K59|b>fiHDB?!p~;DJ) *[a PZhc2`n`1zZS+3%~h^i 4`!Q nd&~p. MybD0(uRCpqb +FV9cem9$gh쌅UL[ߥzXI.)h$9\f@Ϊ=LiٶsIcٚ 3ٳ}ocbjrCmn]+.޼Z9|?pΰskk's0fwi?-OZكYCGA H+lPV2aTH!r dI %dQ"5+)%y ^Ɣ!lpY +-Ԗ2+LYfgWr6XeH9[sAf*W#49rڼ:$6 2V7 4wCoMx. B("|/ NeNzHFubm>KZKCs- U >xչ1Mq So\6+C1X#ρ1KPLKԜzZڶۯ^,ǎ/,(5U%=xPabPʀ5t?^)7g >&[=`Ǿ)cGZ ،\bS+-*TlԹ{&퍅 @AMgPР0Ʊ XF^?!rPW?3׿~{f | ׯ/*|iyVV0~RηOG 3B10>4}ƜhMF#c& #jE%6)"DGc%zH°.E!,pߢա7^h!r 1#eK ,`dB$N}5SJ8/_s58Ne]FWp1XSrk\9/z dyWY|JTƏ>R%= p0QjW02׾SҏyaaBd0?hMq%,Q과y}Ӵf ,J])H;gܼWX4* 6W^8 ҏ_ sS˗.[X D]pHI4_2A{o?jZP ChMn9qyn6P5|k/P@hubgĉdfJN@ .b<:ҒkRa<*~k޽E|x9SO 083C0LT aqf{DiC"5f#&xEJA(Yoxy㲎spc%DvJfAstax^\p$q2sl/e@"Ȅȑo ;2FNf1.@'1z7\Im8r35|NZ 2AHNV pS<-[gexs-C~x(,d61iXpd/ѹ?rX~tYA ,B?q7< ؞QwO\}̸tHӶC"dچՂ3q!R̹0uqTħKo?SFbY׉D߃>v8C^YyKK zr|G*@ IeؑD;d'&~C2a|9SIE ?) n3pٴ7IZO GZL]$lѯ5ap!׀MzL!6jk4QꮔܼKgPyg he~-`+?uXDd31T?1y>2+RL&9" ?#YOùkw}KϜz\v@g~եYɼ Ӻ[x_>fn8^4)K JSG}Jv60e07#?sҘLJ]ט&$ԅE nJ݋w?Nz&}a:<2W&\ CZ4𓗠ۮ=餂5x|'N f΍qHsU(xy$.^gܬ $:k_ r}+̛AҚYJx|ybv?;6V-DI#Z7DsD{z9@>(ܹyt2Q%Ǧi3 1(j_+})*) PVSi3 Q9#P3^!P0[׋?ءmN)` getrnv*wn=㯐_ "=*""|u8~!<<:HҬ^~yýC]w% ~}aF}xCˢՐ{}y&1o"ՓĂ^SM0 UC}0B|f xD{3oYn:zaojgo:Dd@):241BІGgn 0)ɽ j$V0k }>bey,IK H,J3,V)Sٱɐ|zwR qa/Lj̥YSDA$s7n:q,'E^.[>۶{i5hL:| ǑXqI Dt=}t4G'dw G2{ xi':]K6oHy7oAr`(@ nIb>D)i]V@& /p8}RR_W"lC$hQ'㇗ΒiIC<;?2vڐF{LOBpLokƒ[qea~y啷(!0-]ϲ̖Ilu*'WobbjMʙxCd\փǍ1̖|ڰ~a^TI$(<&P>] 3 x^Kb|?iP~"M ʩ'(=|S,O>ÎKV,4љTUX0_~/H_\_};Nl9z56dFÜn 3@/p^eö1HMB@:njlsAid !dII Dff J bQI%/*a_xYJ,^+A0)uhX#`p ̱T@Ǎn'ڏ)K׿ BƛU벰Z"jc<`]kI.B}]ܢ ińJZ\H5% 4)hÚ! HF5.>I0} j !V&NB]ST@p(Tlx۠+iΥ\Y|IYʚ(!P=vgDZ^V>ϔ'F+3P4Ac۷ʇB]<1~|pLXN')0HD 0ۦ0,v2&QK+0]4mY>)(?!BZkcrT__c/pj|f:1#],%VY:7 .ޡNF4a?N`ށo:q­Sg]7DŗXa9cE@_ V\yg}H2:A fTk;KUvqi.)7DaYEI A[ r>}C;x9|A;LX!k8+bcxKK]ڇؓR>ORy0e9sewtd7;U #0]IzVlK|roQI]"29uzOFp(UVŤd#Uꔴ-תPL}E[ 6 يhb:I }6φGw& x# K\^/|3o~?#&AvZQJfGλ3Qbϝ$4$Bd^u^O0&orz)Ceq j<2<됀451$k R6n" 0N/ ZwIG԰Y»5w8\h}57V ' %{p8)yW'[lk j 4 QpO#d2ՁU,H/;*#}=b[0eT K]K #!Dp/weXY}1]!/W/O'^S`Y@GB~j̽{e(&I~:~ByoAfB L(ݸT>8{W#/j!#Ik5qғd߃/K7GD|8: yZ<#288dp`Yc KӾ&ra9d=(aMĽAK*SX:f9&xL*TITd]4o(wro!]uM;|o~ֻpGwIt ۑ?D.o]g &_o| qs$7MjY'C(TW|sߔPV֭Ah"(ы5@mgKx's0 %lFmv%ԛֽ ~tZ9L:ld!(_mlOgTҿI.a*SC|bj؄WFsPf*+0npBTyJ܌8_>(4#m]FwB}.%JyB5 |=IM1RW0q' VvsOG &lpGN/wym]Z8ly S$Ql#qZnFOzwoW/3ear{PXs{<\+<%s9Ҟ~_i2mCk/}V9YVW62Ё&eH:FC x] hԨUQ!鲄cCB$~ġ/|`P<'xrKR0sxNwY:2O~2 l0J5 '^Ws ^Q%FA u!'@S3m3 "7 ՕjPE;{{!@˰@җGJ˦j0E=mRJ>1y{j:*QBĨI/(50"\q@p%|.q Ո:hcNơ.j{^.D +*ugS炯nR*c(X -A&кQGoµ8ihK >$F˲ OC(v+Y#͹BlO{oȜ'Hvd<ӡa]qUI@8gFYW~\ZW =eWV\fWZ#u 6VɔKgO,iv*XK338;:kksDj^ ӫ5ze4:|K_23=[Ν>I33zt0ۿ.!nױEq:""WRry{w;JLd',ewBA2Wn[ԯ{ Bq A3?.s+c.GqHh= iÁJn6hbgz!UR~4)$MBlVV׾}C"ɔ.—hmcaiMzWlިfxfyp`Euz4h&²2a¬X;~0juVVwQ8#c'UցfΫj!(Gpn[Y-o&J WBWJ# #~[zA<!'hWc^JxhRTa$gq9nx.U~+ Z!tjRP,ӯ^\_q!n >{V BL;)EՊK՟~ե^E"g=%O?4'. VfLj,0K -g;+Ǐ,O?$?O`J/~ҁGWCcL Ou qiD֑YYṶI-iYFe+4`߄l3H%0_: XnNa)slr#66lCtA'M#˸"RcVBnN`ʳXV?xWR3E /8= y/c/Q ZVm- SdД z9{ qaHӇo=ͦ@IDATx%'ucf +3 H'P9,@:=vjq\sa$.l! , Y Z@㷨rr'B^إlRX2C<~8t$|GU' eoEe7;)(3[ZA R<|֪B\>Z(ƞxС}< 6F&HljH9x㧯)|g[]wwV2Pdi ؁=ԋ%l9,$ljƫ\?k{A.a)a8oZKMbЪhա6 )uH*cg6KKF?fq K:Zo7o.-8ȲActk\FڷhOU`d3$ j 9#e"̉SEs[!rUlh2/PE:B,-No[uUp)o +Qdm`泎JG ,- ,1Kټ\夵C_YV,+\Cmb-TPc~c:r:c[ , <ǰ-g /AF6}g5֜~Z۩P,iKl;UPT ٔSi-yI jZVHiqNpzGSĝN0{߁) U};{ǣkwNHKTR? W\WyS;/_aZbƑ9<8`43GV7nt].>GF} еENuv\j' (м0\).:jrhGT _> [c];E?84=飻I[m"Gؙv>6͔;LK+Wʗg8rr](괱;]1]?H\Xjp@4sC`!'UԘns/_-fN6W1mXD>W$P H7EÑ|z2iMwk wezݞ@LTdҐp9o6Qb'׀kôiٗ1#R8~,N-lLRe(Rʛ~qxBv1&ϛ\TSԵN@N\I4Ⱥ;"N .:V1V*Xzx[H%mg8FZË|?92vx~j^ء#;7"}DJ5"}@,M{`u AD36Z 44C+0zɶxVNNCqάcM /r^ܯGd{1pgr/R^ϕ~`XƩ~TeyfYzz~'hvXf̮-iMfBQI T M h>ʉ3=f,P؁')ADE-BR$Gh [Gx%0Hc_.IḴoan~4KHN_ʥ8_g33S;ˋeܠ]m Y$ N#&7˵mA$0݆'euTZ " G2y-oA-cb'O2~p N噓O*#<#0ow<'*Y[3fL/L]-:IS n$527*K4|q)'x0+ RQF"J$|_2 _njvAFFw_1 |]s3c:3V›!k@?,˶m'o!J"{SJi* C .d% Vy?Ic`f*.A:TfLJwDBӽ8D88qa<`j2mYBbnG+X'~sSY-'='%&XL1F utK ~JtVdD'uY~׭xԘv%Sw~szz7lǯ?]U|_~#Olw"&{4 {s.ֺ]ux|jN$`I,r.{! [998vTֈօ̭!"=xg_ݯr$\y_߃=LFڕѱXxܚ_>se'&nE ^emz"ksLEhQ8pcȷؔ?;M|0Zdĵ>]%zCP]o%3['cJ c|36&S,i]?wlvs;R#ef)D;ڴuW#+ mS"vO J@ Ag:J]ւb"Gbi)(ĮTX?Z\\zG';-~|q! ^3NLy)<!h}==NCS*>uӟ%ˆg~H?  i-h?׽o85;g^--m0bI%I -bt "q\P ;Xo^ʅ g+?.}Rh*Lj{so^Xy9SnM+(ƷʇQg]'Yk8bx+Զj52FA |fU)HUBQhbuH'0[ AǺ* 1[Z/(Y~5 xq,cû~Vs|aWNxӋ8(tp7.C'H !e|C堥8D0tVj"LΪ@u.ftA+;&V<!wx,[+.s{?p)GJ߾CFÍe6RR>!@ZL am%Y.c|jU6r}qљ9΍ӈOO}iޣ~ o/~gqAцcJ J :0Pu} ӱS-AV\ jD:sY]iz]pw.ƀhaLTADFx{ϗA/?hoV~/=x?>[*_^x.LdYV& ̱Gϯɘ$ z 1^H3i\sn3Dz%9=^|Q8*bJG&P RAs:yxQXZ1^p_鍸6Mks=iHm$VPi_jBFb_ĈU'|]3&+PWkrP&D-n`?)SS,wשׂLޟ:S?Y\١H-ob&w=O9 C)?$LJ0i@Jw)ϹTABoa;075\<>t<叿MN7~]ik$}/W_#ķa{ ]_)uzHạumFd HWBqݾ} 1-O{zy JN:zXTڼ "SBE07BZ 3 #U;N?j^<򌏛Ld+I-FqUGLNfj d4H:̯gٻg p@ZE٢"K"eر8IU_J*?R_J+I\cʉˑBKi6$H2f3=ӳΛ(4I =`f߻.{Yj9\/(Z*}w,k5عңz̈́`9VN~46L".Xȱ⌸1&O2.+R}tƠ5&OMm} 0RhSH%ٚqt篎8iO~-.$mGXUg,JD\t% +(192 e^@OzV\lj0j/}g^Mjkj-.Ϸ-UYC Ikq/e tCviUV i:Lx@ QdM]oQ AE잧ObfI} Iall0 GHO^WU3ɾZ,/ ib?Շ(AN\>)I#aRF9E!6s#ԓ 5^4@ !:Yd^X7nB,QVFyM&1x *[mss*{/jHB'>':h -*ub4p(eJ$sCj" d'bpx?& ڦf`UZ~!isߎ Z>: c>6s=m[/c ܓ`('fzn|gPzW7/П8^(ڮ&+rU./ǦpL잁YM hrӽhk ݑ}G<\xׯ]U<83,=[MEPO9HoGS/mqa .]:dd@=饗#(J⤾p$l:=LY]/'/rrJFCܥP1,80#0Ū0_&(h6U9CdXfeU+#PJuu nl;gm !>qw//͹Vq@DTG'iO*XROj'0} o {U:+tnŞADg#rɶI"eY{np9sm@6n9fM"1u,}<'Pb80l ܞELxZ `re1&K&ě K&$rPƣcLwHtڐ,%DꞺcGea"rK#=LPo*7d~pןȪk)f`,v+$#e (%AƼQKjX3zݠj.?(&Rnmt=.6T3BTCy2^f8ӺqyQy W^%ғI@z,Mz~~.id;rr7R ob"K2qiATmkǴ^g&dJ = h lm6ʏs}~xkS_JLb@s_7>߮,C'TFS.7Btiisj^ g9bMٝ67@85LX"u '.$VrYoJb,XG4SC Sy`i oYaӨPZ8{ԡS3B 9~^>ij[=eZM9CF]'c g}UWT]%.# |6|_1bЕuL0TW'NִRPX^CAq1e4-$DHYPmATb*WG7JD‹ n+ɱKA8rN.)R(e."C0dp$WjB,?b2Vk<s"qmg~nKd)8k#yMy_g2\Oݟy%6ȉܗԶt'%5©Mru=治hF> -jzRVh $]*i0 =}@.9~WAӇA%TԆ -L#uFR(4p HG!&SoK|IoQ-b{*ec7i^ Rn1J? WIb}u &(_*m]O9jw)Z69OG 6~_J=LBAs$TiX /ڀK`}1Z"!Tw0](Pc% g(:Tyڗ?4jďߝ K w8qs-'?0 OڇOmVePi\=\5y_LK0\wx8__}N7I>$`L]}͌He&URKB+H)g64U#ѿ};VU*vFVKtZRXhH[g?Kf}^5<ȃyiĄqӈ:=ܗz%UP/ 貼C\J . -ll% *ASxHP2YX#K0P\FIJ ȀB6PjPd\>B_{C A2/eb$>mnGs_U[VC3DMOUEĐK0Qj&c4RQGd:N.\`~^%.d ^tu"<N?e.&dmG1:ji'fd*՝/u K:`Bv>e 2M$)Ȟ?`C,D ̺c 9{a  asq gy/{k_~yt&žo%rDoZuȑɁ{&WSWYN鄸 l5FGt'Q brc 3AI qL:pVv6'ijf,k+1 92Mr̓c呌tTSsh}/OLL}5"ĬAt|AB'TاG-)IQ 3][c0ER`O.j ( E/ÃBKD"L ]@ $5w$2UE]JcXJmZ{^.$惈g rz|f;x"Vr*ƮOؑptʲ f6 mqR+sm/ٞ~pܻ!N3&$|n}݀D UpCbMZ*)\NMrwV\1 MA3j<2h'P7ddj@kxul57*OFF%M|wP3'W'zi:z:'|")͈d9G?Zߜt9ӏnNNO5H.R`yW0L8bG~n$x&ZR -2E"'1#⸱Ξϑgmĺt]PO[6z,%;&Qaϱut(1r2т}3JG'(ҏ0,$a).Hy/Ciz\n9>%8CF`kA:<RFL"X`҄2e^dPE/g)>(+s-dFQQGW7)7\`8~YkT[vxlwLO@쇐x4Tw9P|z!j{?sA0T'kvQTHs$Ħ$ ݎ072/[ ɋ Kh[)38 *ršΚ]ЙiJ< qU)TQx[g6] ma^.IOw_&?}᏾T rwv$夗uw=0Bn.%#,Kׯ%/;Vp,е]$ȃݙ!`jskHH ,/mpc٧'Zq/++lag8uky-t$?zl~W:+ {l-#i|dΠ@&@hν H0 evh-תF#2j+rՖ 'xŠ7z%1'Ak M#hn(O βt F . !QD2 y#l-+'2aJ".cA}>fl`VnET%u_9}lØy(kx/J"05 xjVe h>3R֦$|R:B/ 372ZdQ/16p&/:"S 1|U:څK,'t+^S\-&xz2)TS]gz9_O'zve\Eۧ>+0W wqBO;C«.&<nܓEV^}}'LTc2(K˖ ^l.%X䴠ƕ. zxM>:zÜbg^D.0;-1*:+}WZ[x?$q *h2'ljG|KΛ\h92 O9>s(vAig^)A,(5RMUS" @i6%-= cd ?8 $\ g[ mUc $!v+&Q+ x(#_*w@2ԎWV2jT0x\ )mr)xJyYh"AXp"F _L}K%hy/h!}+mѶ[ ՋٶA\Jiě{A}*-n"k)/It.W>p\*Eo]K]o}{Y3${yJ-0ٗ$9D`h%׮^rk.06q3 ~D瀃B&TuُPk}pU]j1orc֞Z XBR'k)ؑwxE@?ة1S뗡hXqlgJ4` OhHC͍HO{!F_&`vy&wCͮӀ]sdDR65 0@'-`̄F0 Lvg@*|P?N L5Cv8Zߨ&|ǒ;FKHs8 Ѱ8ږ̒6L M4VV zz0* Y%3*Log 8z=w^! B0Жmkmlo=Q=ysl>{t.1k-3*Kǟh,T8=.I$!NG *; 8>C c f̼@4NvS-BDQ&u3K 3uk*TC9cR˓Vq^T#$h@(<8҆,#)Or"sD58lGG pV:Tr8jN º58: jT)u}@D@,"XhC| &_^cՅE.XĬ*rKx# L.l)*I5JWq~06&/+//*#)6҅s,=''#Ӥi?Xݖ .$&=!Y_W{S m[!їf xrN"ݗ]bT!Ͷ˥bɎή϶7\.|%ZH^e@4ũGnp"JSJ'CK^\`!2"KyF |Y \zr" %A >eU.2fg?C\ %E/)ED{yҤ@LW3^K2H dnRWQCmDxsgO&iQ FDbDd^=DžD ɴ`(\rqv _Ju|0҉' AzxL.^Z&kI#Q r |Ԉ=ac#}*;I1 5ǎB$ 1&2ݜ%֓80^yvt 1.{&B3!KÌ5&g˔MxF'ɼ=¼N=$N|1gu9{lXgWֱ|~crddd.j>eȗ"lRnnn` \VF7|D':D;,0`8Jb*,u.ԓKĤ* 3Gk)2$&I:kFĉlG:,P Swځw1Ao7`6=B h951LrUDG<Ąse9@̒DGGUi "g2`҉dDat~YWnK~u׮:! Xpp?x Ƹe8Q7RIl3p:"0T+ma1 @ 꺿k9}IUx[Sjnh$ؾ0Le;VβRP0l`"aپd TjAws 02`e.-mSg)$Q mM}OC}Ip g` mneoJGϥ38Nwoh\SMC,5z\<#cJSSv!/jXOνddy"g(#& N&2D9dLD%a`@FvAAF{C ;Jjî)Ip/%K5h 3AQqkpݰANcFrZ(]Tq#"{Vsu1I[|5fR׎3aPѩI=3^ 8.r ?я~$y[HXC}ñ9.U#24VҏX3eBl-<&V+%S#ҭ;)t%'sinvaeڝ +jθ*TEr2 R^[`7=ݩ٫1q#:'+LwWX TB`9+M8A0y۟5n Cpi"&tXL7@8q1ƚurgS2B~A7ĸ<%naLש(4Yӭ)1P} 'LC%*s\j|߈ ki_R dv UVBҙҜf6i{@~t4V@T+P۩v>x%}!O}*޳'}mT'ًaVEƛra{pF4b$gK&gn H@LB'lSq:}/%8Zqdm\Uen^tnW>f߽VIk2&hU@Eط\WFM\lDL6rCԇO?Go:o-- `_lW|#4,69I,*JHul`Yp4 uoAna\J+Lu1-b%$ܱ]0Zy.F=bYBs!b t} A֣K&I, RI^N6X߶rQ] 3^ EF(p0{flziOEwL(L,[Ķ8}XrZX5_߀dq€`j[BoÓL↥:܁` =|ݳ(DgҠ 4Mð~-F0r`A ;ܓY/WkgC KMGD/jN\ (] 鮔]US t#ikvn#" 6a"H>p.ts+c9}B[nϑ#7;đNmS::j0tz=a@niܐj Cd[ab&](U U13 I  S:j&PD2B $qc T:TGi"R(& O'61a* 'FƄ)4;1IG<1d-O@&yivE_:byB3R('^!7B!``WgY[8O%fS? 64;QE+Kb}eNN^u\()6rhS-¤,@gq8cab2)BP5= ӎ9 Ν3F@}Hpf&.v|?wu*"lmk!}DHCG-H.XPvCj9Hg6.&-R@L"HWyGbi,0wޘx_ZfncaxTHʭª-ؠo SU%55W,*",AN!<&HSxNWN%LrI8ăBŗ GPze:E0]`$Q gOEvE86ӫ0i\N =cSE_kR$ %v?K2F$SZ!cLBǏʼ0s) mJxB\CBo0ؔ-"0uĺ*B8ߘVziNi{0î8ğ#S[# wn0W@,uRS&@,zM:ZYǁ-hi2N-o=f,mL'D-/sfd293-',{_:Ry4Jv⣏>CuVϣ~Rܾ\S"Ln"PtMc3"0Lwexuy^AJR{PIR.0,.,6HTbJ}uk8EXi,Kq!M`'@-EBj7\T]7;sU b[(ÇMHX(]qC;Z"n(K縩\ $MoBaC@RiDfǀYSWj{u|;XN ^Q͉9x5ODAGBs~A96o%BzW FC_)i}CpJ; +59;ɼ7B$#gOWI?4Rwװyݕz~Taz-;Ի dd?Xq|`zsfvf֤Wkl0HLXD,(̂%Jy+9kL(buHG}["őgب0*hʥmza#F8UKΞ+WRKu7!Önk; 0HM"PW'hWz#rP䯤2^U3v _X;A2X 6&b>`ۼ#m;*#>oSzL\e /}C4<{-fmhOC]=ɮ{rOil8xbmwzCCss߸6vÛmhVGRzZ)A t2.˹I3PmDrVL9}ll GEAرO: zL~gTX\|Dt8 l lJ #`DҘ6"0i/x M\-t{9*:qH\w^UUmQJM壒jGHIa BL Vnț#j-vm26ԲU6]\>r4MGv H3?UKƒM2֭}7p E7ѡkDLY$Izጮq]Z5j] ~.ߪ$R~xqG]# vGyif"yRn>PE` J:q:4Oj,05v(=;2a֣2 8\~9٫$~;IDAT! Ժ .D CXTobKS,ϪL3G_O[7>Hj+0u KjK7в asõ~%}sc.;ǒ(?ִlg/@r ˍ N 7 XL7Ԭ}ߤX;|7t<8|<#E*Z.aCIޗXv3Dy]=bVdW"1 ˥w5O\]#v* lkw&m鸒e6:"1j [ Am0 c8ӡoNQ8_j ^[dy FF繱&6`i sh,6SrL!"/Z_ݪF2Ft;NIὝr:hMHv!|&hi" ƧF#}aivB2Mn;?+`l݅lyhDosήK}Px^{I~GP¿v~G>EF_^^ZnjkXgqiE+V2G[\#)"9qUF۾5٘ e%C;6WvÈYZمLljp2P"SrI Yf\\c} @fvK.㞴]+ I@0^# HW_3Af`ʺ5%!#8@ߗL\FN7PKj99_A@ߪ$?"雽5~n1*5-B!g-:8:9v@^fss1CenVzFʸKMAju2R Ķ/,聯p4<3A?!w`žSql=3\>>rLJBБtw_x{~1оYx#mB_O_bѕ(\9:6z@J-cR&Ae)Ukı h'k 6o=㯅H C%PbtLcW[eC#^;{\*7a VKR[&e/BT͵+G9)%+dj40s`]xTv6TfHHJEQu6J=йR_kN'\~{|Z%ޝ]k+}u٩GBoE#H5AH?!uXw*oEKS?ʎ2*BoAP[Fs:ˢ0FV"15",5yr]ب[v@Ld(\VT^k0)+6| pN4. JX.&U,# 6f7 7O G cO(V1pxK">R޽CCBPjB<~;Nz/O`r& 該jQq@p tdG1sbA{kC<548t\9FTG--]SKoBw?-ƿXonh Qi,=d5%.~!i7$vubpI>4]QOṚ܉Ǵ]myjk2%9wЁˁ!.`% bBmSP6T%fmXā<<ݵsFDY^\ΌԊda9VeT,/aS/Q#㠛t.S>#-KWE{`dtN$rS~+0dla^mTV,_moSiV6wO_ijC{NPKgzz.;]n'3ݼ-UQ <7??7I[\`Ӊ'WE Ԕ`<8MUuUk7F ؍ƒ`Ʊ&܌r4rcZ(B!F:k,.׹4Iú"z0:c%uqvSSAנf {]M TօdIiaM* 9'јޕ5lc q:#fy +x#^]EL/aH_]b nWWE sOǑu7@2HNkj"_̒]<jRF|H PTU7>fVuź5ZDTSS"蒡l0XW tWaECQJÐ$xw .:UAFϏ)h[Wت]L]D+酗Ө:4ʬ!(@bOeO!Qpq~\37S'.Αb9%Kq߃I +xo}9g~7ݻ ~؍R__2>ETk ҩfԛ bK,]EE׫,0Y-aFHp]wSHkJkqm@,egF!oG5ȻH+P㈰d&ۦk_NBk5I6#K%,ѢB΃V ,+2֣u&em<4$64?$;|A:qte0E ,)ƹYN"Ry:70|f``_,\F:{ZqRzqDOOn\TJN8jr=*f[mW^?(`%6Y%t#(QE0 'a`ܱM:}Qr:PUC0#]H}T`K T^Hxy}j{lE*s|vX)WF5*EACY%}P~DI[3dr^͎GޅSs' cM0bp5^;y`yTo+tؿNz+#]x3%VVW#A >O!xk8L{TAuH\P8"}R/KO>P+\cmưsEn~h PU0h :V)aH <~h,P6|$]R޲91ʔ<J4썶A/H77kY8lv 'k0ԛ*{/*,I2Kl~7}05Be/O ={pً|'VZKOs |ĉ:r}Olk߉Ad:# Q>2QGFFUn?0͚Kz镪;TUEy{ڣGן@yK!b sO\_#h?ͯzOשfkޯy?FTBt,zQR۾.'t\!Py3/K&.;+:\hNMA5QKErk_=aڂ#+ -_@?3_>UHd֮OdC w .^A@,kW.ϖӛd :f>J}sBP)C%4nWĤNYKGb91BF;QI!*^p5Ed_+e>,\q ZEkhBbױh@~KK( ؔsYjOq̓%շ'L{ݷ[{f{ߊwwK 0~/_:D >jYVb0$~dev$Dמ6kdnlTbCq ,ԉh֙PhQٮҡJ/wn{"0Oփ:y9B!Y;@}0:{w fӭ 5\t#$\e'<[<<r8FUv٦tjX41 kOmEe"[X,l=pBidoOή}'}KO>y\O=݉\D;O?xGZ/cRgh0yKsj,j"f\$@5$mm F2j5%;<]OmHoE5G߼uzhs2ssY0#uNh݈nB7D=q(k22Kk[CrDϱɉ,ދ}%t=W8>!+^$9W(a.$ooNYG3g/=ffɩ6&zp~qv~* NxoF!pM %4.j H/m]KWБ.S@"EU=f^?{<Uz&ξ K< !v,q7 !W"kټsݽOO+#]Hd.Q ΁XO/^~L4߮T8N Ug>$]aãb BFBH4i$3R-"6X?ζDT=P^7Q/ul8uՐ_n̻)7"7a;ZXDU]v9+~IO<ELޙtw8p޾>iN<}fU}%[÷m^m08BȞW^9qpiI߱~]ڡrOrPuĨxsJqHtkW~{N{+}}g!6 ooc/| /?0z+H14pz͑V8xqW-r~\\(|䯃4e@*?MϾ-f"2Yij67G0 Ml*i%A{ZW_ЩbXpszkOxy, ƌ\OOON~~t-}}AW"? 2zƵWWrmk KJc7츤u@\Q,5Y`t]|u9cZk{ܷWO eah߷=~<&t|255A0T4trbG;=22w}-Mkky|[ PlSb0*.U+\ ƎjB|kAT p+&ONN>61q]&ON8~t;o'K^I 6; sHtq455L"0{wGx'F޻SC:{>[k?sB I [kO>Rś\C^L Mgm3G!tcׯ=6vS3#+kCݝ]?Wn5F}jb<ѳ@aOoiww|tx}c`ppY_=t"h&> 믲 !)[=g72|+n!kiqѾ?o^8yO8uhݵRcͤcT12J =WŞk{ 柫tR]ccc%j~-%vow w4:~-i<>ONΔ9tcnV3- 7vww:n7o as\ MjM|Ҟ=O\]m?Aw+qVcniib9{枖i+unV6+ەb 9t#k\[Z^=|WLh6 ow%xvեՍJF{g0̓ Έ}l ?Zp06v[hް;6= A vvH{E5+A -B@fR^ A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d A @ 2d!ʵvIENDB`OSCAR-code-v1.3.1/oscar/icons/arrow-end.png000066400000000000000000000060361417327530600203240ustar00rootroot00000000000000PNG  IHDR<<:r cHRMz&u0`:pQ<bKGD IDATh[]Yk˹9g=xq%E@JTJUKJQd)QiRCQU"A"Ťmژĥe3gn纯>8xs笳o[߷Z>H&p6moAؾ7>Q.FtTZ|KߊE?bŧ2TPAՇGct9󛿞~c +1}į4_ŪA LsǝQ192 KtX?{T'NJN|uw5s6'Yf7^ݢqw97hsOt1;Oz>4|鳻. D5KiIx oL9؅40 c4OI溯O=tԟ>X>mM ͮG@ 4uWfV"fa2 /X(X&SlNh Ƃ8 HMX_`jfJNL䦍W_}憠rxHl̮b HMl@EEtg8ɑFJuLثBtՁkhV% .< RX J%贃J~ba{,{h#ykF`𳺿5TKB_b]`.]ӽ._>() E(l)-Q bG@ǰ-Xsj\gGȑg~ⵞ,R l<\^|6"VX( $efzNiXӑKz#LGsaEƕfv^o?g94ퟍ+Xl ('O}QP}HaxGpc`r |QQt@c!+4&5 [i8 I Sң_z맇x>y 6[UTv wƳM36BϾhkXJ%ĀuR&W(yς՚8I[0bɕ(V6@Z9՜>1e|޻6˶{_8WY^0J3^m/c ] >8HytҍjW@@886QC>q qY@6P"Wh/HF(r'' {5*.Y_u@XT{zPw#\pAJJk^юa9zεt ܦp~S:,lI4 A^j4@Bf̓evBb&)h2 -lM S?{]w+^VmW njNBɸ|-Lb/nHguvrqؕl aB1,0†`N,>)̻-.xx٬&w~rR ]lb|F`)A+h+ Hf2 . IVah䟾!XTtIV %gM6N)@b +gsS)0bcdepRǭ.![h)סAijŭŧs7Y#h&$4w0KP =rh l2ɗz_u+t^UhTaHO8Xף(|rڂ<$MW-/B5<+@p4P`Ăfb6"x00X'LJ[ZFyd^^qMhƒg?2ʓBuNP=lIL]ar4̳vy(nnMzn ߎ-Ⱦ?2۔չ׮zAX+ܙR4tk@`] 9d9l^6,weġQn;U4z809}rY>^.ԛ_{j׋p&y`#1}gBfcyV߂^fHon!q=sw2,qH8H]6y>hFc׮3|r:LQLVØ7F'R|0Ҝ{ZGH=9dTeʸӎ_ce ^L4R0R""JI?c>,R2HD+KD`dX9H-bJOj:A&S; }/p =~2̅gY9׮H2\ !ϧZ߅`B4$ [ۚvGmD+m,A X6,g4EqN[ L6! LZHE*=LeS %c.>Nsfk: -pPTBeڡCXt Q賜ϰA3:HEf,eͤMIYu)FgtW2 BH)ll.9ji[LLH'0̠`2o<FL8>,\e&24d>˝,vfX t y :wirЫ՞\Ԁv\Bu[R ERASM,C~я!+ "NX^ Y 89Њ Jt 5糮ۥq!SP EY8vƸE(0A /xB|E1ƈ~57:X6Y\HAc%%@s{)?r!b-lsU]qfמa<ؒϞ!:{Q+Lj06̿ Ӽx_rqJ5'M{`Mp(k'BH3I6'I6W5y0SS'qa$Y(Mݮ|@L$uei$ƀ@Y vK͓OWg&퓆Y^"f/(:?ֽ..qܝ#s|gZcIABvԝSeʛ(ZAg% j *U5I6rOen08"zfj*A?+AЮF ls׾Ȓi(2 UK}܈ 6`(QvXYX\`6!׺q?.E$AVfm3MY/F8A`uҝ8~׮ǟ\lDw[:{80wG^9y4x'Wu??eŗ]kf)zj:7h];<;H$N Q$8! Np($DQ)/`5zwvgvf]]f6,+7F/UW4~UW}5#\KtvwwwwAwod}Jp4(0(u6}kRW8QI)XA&D~⧀oS# 8} Cu &f?]ODaأ z&MfǛ*Lb^XzfvG!7nysN]^"E'hu$Ie}{h' IqFhzOV1 efo8Q=rևĞAIr(!ʺǞcDŽqDZ !K!W9<ó\*뙙?Dx ԃ'" d N0A 'm>&>B}vq ޤ~SobÆ = :@/).MjwM&78uMɳ Sؔij39?unpt,/哵3Ş̆TvƝo*[Æ14ɇa]BYPYLmU kT{)Ucr@W?w 쟞oL])SS)~J@aA琩|% LA@Ԕ$쟂r ,`1u&&㯴(8|U lCڤV2jm(::6Y2 cq h4kYTJ%$* L2 1 0aʆ"x%5B+vSp(̎~{3v@!(u7a0 H項0:G :!i6$ R -JHLLȌYB*x |ú)Tb kCRl @/>mKٍq` 4l i4Z r:US,$MC$#L C?"qZ$U.9E>EfQLto[hNe2,H,lKt :XcmӶ,,;40LF$HX:oEe"Љ %TZ_{&q]oDYID'FF'i^9AЅnUM0zdFLLTYn.72@)HA! !4G9OJp3"S'tˆv+y#p8stud/$ ȓ>5-Vmha[ GA Cz}=719DUEsu*wv~j 1@D>?󰺊2m@h-76fX[4䶍.~bLKMt>h /u\D_6L>S`]NA#R$Sr >48lS;@6X夁i.#;Z'Ϫ*_r^n*BȌLC/&G%3ɻj;U’1P O*/Gt~#  :\g 5'zr,Gر'x~~ ڄwڶn~ۿvJ,0̳oޮiV;/'s ?p|Gv{2/E\<\]q5oppʌIENDB`OSCAR-code-v1.3.1/oscar/icons/back.png000066400000000000000000000057061417327530600173310ustar00rootroot00000000000000PNG  IHDR@@iq cHRMz&u0`:pQ<bKGD pHYs B(xtIME (,fC 'IDATxZklΝ]+ؔ1$PYJ^ y!* JR&@QH)U)U6E!' %HD`BZ{׻wfgv-Ueu5wf;9YZֲe-k#l5OoN-҂_. v^ ׳Hu=F’ܑx:RnQ^4Al'K|r͡&BLd94d93E@:Alx/mmi:W=A4 iFW=NČ ɲH )d{lL!Bc#.d^jD`&6@ s;v>;sbGmf̝ Jn0ek{YN>cS䑬%aqw*,s΄q X7!mh=_XDDQ7~6 D6I:Ky=oCO/ '0G^_ߍ=wTWAacN5P E|mE$ pf@(?p7:zGUaV}p$kfRCNuC9}PrMmҷ[çO7E|Q1wrO\N*6x$Z/S@j†!n ɱ7=ޮ'nj{i\uڊe8pq;zAfg)%q{\ G`Ҫy͹_(ΝLϚvvJӛn{Uo8ܾmD9KO V<}/;mJL/X d)Z7C*j e0dƓ~5)ƻ c5y%ˠ}H02tssBzR7^S+̬oA&`1$nD?O78s ;C/mi-`lfq@uTd  X)Hw=Uێpi*p= U "6p'>DҀ/R@F,NZBCZLes-k5ދPRNo@xMjh?HɲMb_U.%w`-)+1{(iN:: 2tH|=z2'[|L565"h2OCŕyVN:RlsהmeZcv!̡uY~Rr%~_ k poL>)$IAVS}H#kYZֲ}{p;@\[E`>0/PJw~&f ]dJ` L/-!HU`T=/`!g $t"q$H drESM@7y0`H&a;X J ,fuKX!qb^oG /G_IDt@ hD1H H1D[1 B֥qHFoب$cJ6lUm:;a.y-B@M<=[jH상wExQL +88uw 8{.kx+Wuw&ܺMr0807IENDB`OSCAR-code-v1.3.1/oscar/icons/cms50f.png000066400000000000000000002132011417327530600175150ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGD pHYs  IDATxy]9[U]>Hmdc fG–\Bn.Yr$L,HB0 `0rulK֌fFվ9VόMlm}zO}o?Wĉ'y8v=ZٱٙT9z3~X_E:z׽_SjgnN5??oo.ĭ ;N"Ϝ9qM 6Ʈ)<X*Z&iaF 9#*Zc1IRA\O| gbbž:u 9%x'ӧOkR]Ex>)Dy%Ibt`E$8"c) 挱&4V_8'OMtę3g43ȓ'OSNiGpP?BPJ) TRJ8FZ# ĉF)D'VX$:Qh)RJ uIZ0q?$޵ V ɓj{N˕$U;J:߃q"Jb%r)$$!F#@ ױ#!mk"!I<8 {-6~s" ʸ﷞9sJk$zvr$ _u-nGeP.~PЎKc@(\E"v1(ЉJ !\'@)(Ra@H(5V`0Za0qF,qmӃn.=uyWNx(OOZ[cE rTRƂ뺸kS6$:CGhcMb4QhcMHb(!pURJ$czVlq$qc[#"h:!cր$:f1ZcLB$#'Ed #Qթ C8BH1#olk֨c;= ,HxLaa; B( b0Z'=!0dR$a"_"ZFX"51.Ղ^ŰՀX# 1&Zž>u]Fv;s |ñPo5 b\'SI@9#>LWIE \01DB)ZDỊqB9+PkwjR15l@0Uh()p$a mc\xh]CFX2Μ]wqK'NI>vJg";1 f'bB[Eb$:!cC#rC$yc04yp$AQ Ntp@irRy|&6՗,VB 6N5?'v5]ة@r~~^{_W9`0CdZxJ2>QEH 0VYGk+`4n5|i6:\[ޤ;4Mzl6Hޤ;$+4VU'0qB't6Һ(B E6A*O8Jȩbu;DstMvxI3O'O ҝv_96.1 9Ҏ;Rf?w=#9q%˷+&b  X[\ &t{]26d:EQLOWiu;J9+A </!<1(SuƄ!"m:ctbI *H-D)*&¿;n dZӨEʕ* ʹ.32RQTZ SSLLO#ʥ2qCߣ5{ ialp]?Ch J(AڍX8X`OJ@ 0h_{y[;pdLCL.@ A{0$_qX"I4ZmccU>4 Y6kěm~aDKIӋ"*<# rqiorz6=ONAG)խvŘ:ZF$iaqCU.B:XH~1wd. x o75Pb8!YYBH[$qy9u٩ ,ycaȕk U%9zx?Nd#wå =JsxU=!aD-;I@"#DPTpKɶpTƎ$4p0`oX.OҪmʵzl6 JD9L4/sCkWI} })aU3&~@;daNXgXmrYtItBp\vwuZ &RD BH!$cإ[ڄUaЮN ȿ-ߞCRNcϠ 3VbT=9|v&81'n@gqq~ -k[\^'?iak}> y/]Ё#4V-s]t֯ሄ\`{рJɱIڭ&&Z2a}d1! N*@8> BaaRk L0qn.^H@ |u2vv@5NKLVMnvXVŧ>1:f8`ceɉ"y%)d VWـ^K{kp8,ZǸKݢy8V9 p2*;13BGR$?V?ɓjPM =v! 8DQhzR.`bf4wL`}c^iiK/lDpui D,/>Uʁޱ2Qd%ˡ10Z1=Q"WqGNU *Ě~cY2K.,QZgjfP0">QO iAZp|p\M-*?'e4W"Ի.3v ![9X3tX_5&i^!ټzНõK7V0Vt,ѰCksZc 6">F84[!MHr0"š b!,{GVu9ʿ;N1* @onLL k+JcLC:N&W`i*{n;BEc*CQ[s}:acvOhfv˛p"å'ɻ\=wѫL"!KU.4r{(ʌEB~c꾕q]7MŶ!v/vH0̗ΔSm11V'Q~YAxY\q$alIi$q `#ٹ]iLecmbHj348"2솬.#\S$:W 91:.ű :!Mi,G8L.2j CJ"+ x P%D9#+ Rr `-б6":o3]i͵z8'1c\6 .IlX\WRgJTPT*\t6KlFFibͥ5).-ڳ3 zZ&c N7PO#6vi  X)B$'DB[ Ha@yhxiI xecu$]/ZrVIӢ/Q)Q&bCYʰYgxF^`Cחjl ;ZM ݭsXx6|Ƃ(֯-D"?yW2I9el+OP>t͵5<ܧ?KD$B=}$BAfb6{כ$9|hc6aCٸvLDR^.P̕Yitr$8ys8T>}P H!h:x"/v8qB>} PL._Yh8J%pȹ)Y8?$b):6A6<=E82g$t=KETe:us; n0J*:VP `\^n+'IL.wq,%+bGhqb1Fc Da˘P.^(t 8M|':ql>A.36NƔ%lZŞ}+n&_@v_<=I2Zcc1C٢571<Ilnw;=F~T^Gc"U DC-$q5C.1lc(@($ q<Ӊў e»xI Y} TfzkqC3MY#X 9ǥPPfy {fH]>O41F6.Kc/sI'Xkq*Aj4 >l.(zwAHV?C3$&]C2kp`4)1^29\/\WbEL"~ށ/ .^($`nnnXil,2^ڏ t4 tM\?<>IQ'joLvan IN*:|:"N(WqO6 !rAd79et;t{fum4baPbOᢃϢ\c@#H"acq>ųw;itJ{s EHX[abIXE^Ǡ`s&60֒+0R`lBVIL̎!A% ;,d2'q%ՙ)Jc{(OurXNIb *:F\ 6E]p#U] b\sk8a8}zvbgܜdn$aC2h zZl%[(R>f=\aBuvY( d=VT7X"[r j-|OriuڄaLQNk u4Vvݐ+ilh4khnqاiu +!qњ$zpsnl $!I8"GFsa Dii0 codWԶ\)  -]#I"-R1>5 S3Y\EdL63>EkB4ŧd謮dHP*ؼFաPS[`0ZY{}_ǰzq$(/5L2-D@HI %N"⻞"8hʓԶJP,߳A?ѬӪ NA#2Lɀ^m@m<*֮-1;> A8B5=rV hnowQQfjV3wEE$q |֐#k5t@1J: svW]Qpz. 26õ֥h[ 5 UE\7`ue 2OէI&_hK3 ?>E2(\baX>G\Ugm*VW,(V ,_[ (1[+ٳͥE(fm(N  ERL8y|?K주&֚A!I4AHX(GZTŋC'O*N=0c~W,vD¥K`5ak崅W/b<5E7`lJiҵUTJl]/P'G*8r`SNpI]"҆djwD1BMڍV+΀~FX$4kAkIP@9> ,\Ry)#q`g;Nl9kӷݻs6(dP^g r ) Kmu+us,N|HʰgEՌyl/S-z:`HAX -XKD6XRK\ff}eˣ_IE_ Eu-i9+%Wv lCG"N"^6ֶDcc;':9|o !~I/AL<T+ %W*RH2qKe50$ϰqi7LN1s[UtLGac PʳtIG $6ha}QGy?5 4BXZ>:21Yeޢh0>8Nǖ:aA:IGf(Mtv|RC~ X)h9H ! q3jHtD}l<•_•&1'7M_GŸ1{~a \V]Wc9 [gJqѩ7QKEն(qFKC$LbgYZY 3Ѹp"vvUˈ& {mZ4kaC~ˠEbZfnpҫoҭ8PJژvLq"[)%@P@JrGYb$c!,Z+4{/,75F80{g~ V*i(Io:vgp}Zf[kŻ.gnnN܄9 N1pz^h$*|VƧDhc(V+Ci2qӏQ8 zV0Bۦ׏mL?.Wb% >"lL:SbcerJ/qs=r~ O~kAZ<zk1q~=D,}G߁r8vxl\HBkϜ75-"cin-쿍:~ B]_%IBz:+p\-j5 I:kH#[}M^$|QܜT_W^] ~Rk6f&6k5Q9^7m:6:N6(A& 9/dq] l6# ٬tgJ7u׿>77W݋/6#ʕt5o'3q0Fgй)/O1a%zD0LZKpO3剏~b=\9ӓS,]]$ am~BA./\!Hff}mDW3%._|-_A'1$NTd#Mr<$Zhk )`xw}{q\ |߯>ozӛ$VfejS7ڪZ][זiMַ?/J׼5cS2A{3̫JtTr~Y 2&͙ٽO5bjv=/A+?{n=n X48i|?=|/C?IBzB(E|)B Q`5h85})9__{˛H'QrZmgqm=^[Zu\fak[ӧ_+'F_~OR#R-jE8Hubr}N}?W{$x v I7bLז5;>F9# "N /[XMXd"_<8|fE'pk͎d5 #qe SӸ'uxr #Bu2/'Y7G{hkQ_"sEp\@Ǒ,Iu(j ^7ܜ/[ZGNk7;,&Kϟ[R_Ϟ+sss:| 8}ɓR"{L}u}2sZfgB$^h~|f?ww$b# ԭh+ 庌MNruZfYF (fkiNK%hlԉ:|a}eNmJ2[+Q)kLwC63q/dj{QϠX FE!?K(7 buƌbך`HR#i˧8!]Ї>:tH|}۾'gfC:Xڊ/]]p?_9nl0>Yt?|s%VG*7u}ӏ̙3GqϜ9\#v˅vToSg;ƦxMfQck" tm<7G=SoQ(7d< _(? \1ϰ?$P?fٷCyzaDuY?a,a)=(2{`/;H\W\\AHQA& x>`vdXhב G`nnN74 ?{~F7Ғ|v? hrLy/al˷ ^k$Y ؘ2A1#NLP3S n|e\HTL\cT+lD._@ |<@#!(&ƈGan†z{*h#A9` )JbEJnb 0F)ltR—w#߈O>y;_k; Mv.<} ./%ym+Fj™3g?gÿ G)[p ^67;G~{{裏}s#4ՕO[. q YpkWp]vA/\b8)ㇰa?qh6>q!ѠO4}Ack2|{}8W{ӧ}yoXk6?$I7Pf,^[9Կx~w/Z(n[7_ñ(NޚdrQF)%(qt}y=Y^;8]^?. 3vzd3P~ C8~I}s 66.-2lo^5C(bki FRDEH)X_XƱ GtR zw\k>Bkڵ&6 V! "~!QqNL! R*rR:A;kJ=j.B8Y8 Ĝsiw׾] HvC>'~yQ>=/Ɵ'S\ &Zj"$% 71B0v3GK\p_:jK BD146L훥MggNTZZ4>FaLcilnR`l.\49áRRV{Q-z>N^C)q)=$!@PD8X'q\`8D MoqǏJQ0R q؝G=<ÿ9x.c ~[+Vp;3SgC2"Qٻ$I&cS77<$Ur`aJ %2P 4CJ{ɖK"~HubBXX94 1,X&ũg ;$ k:+$t&!$CX+Hb5k&aA9䨉;^* XDq`з+[\^Xz/9yA1䷶C}{Rgt [~4mޗ@-x6[SN=; 􉺫?v J3K ;Cz0?EhXĊ)".b>(k8X]"::EDe8hҭ ߩؐpN-[MLHK5=iw) ~ Ba D@II"MsIp9es3yx$̙tOylNm FhcڽsڼG?8ӧ%('NpN8aw~7䧭D Gɑc9>}4`܎NlN (z86lf1,YW?od  ?NOcv~z. #Ͱs 2/]T,gf%|G1Yl6Kcc//h5tZ-[˫xL \tY{** {+P-*d=c ֒@I 8š8a/+ Rc:>&$ vw(ֶ>iӧE(;ug#,)#r\ωko}OD;_}C·>4'ɓ+Ǎ ?}Fu&zyI.#_.ħ_66G1k .՛ikT=6ug9*sױ<ַ!r牷 .77~?0˫N|k uC~ϫ=[a0H28\ BX)1RbHu~+$Zs[l:-'_k+'Nhd<  ַQy{ONZ?7?'֖Wh;ahX֕w˾|_ ~GGy}gih]- GtnMQQuDž@ %UkRC1ֲAh@ |ۏcU8u\[e|vHЮ9vie>JUgm}=mmls#A=#fQN,[|H$:& ;8V[.a]ZN&BQUJFw}llĿ[c=r$|À_PLq7nU+kcP.\΃ KS aaF҉c:6*S&aLdi.-R tVfY8Bdg޷3 Kϐq0aEƚ pH~*nh^[b}IקU_a7$w46B[g,* 3('/"sL>VHa?m^y(NwoĸgE'm4[szc? կ_iӧOsΩsٳO=TuJH{8qG9*V+#գ?gΜ nMۻ>׽ I+F^0 6zvLU!DBM5۬m48;sA5}Xk~}(XO0iލ[pz3oc|'~\fn}_Kqb?Ǩk_o7}6!gFj PXLڭX:Z$"$> $DXIiHhsV/&&Ҏw>ՇʕB.N0 iv;OpkЎS^>V xyyRydޟ85!^Hs‹F7 ?=~P>`=}2@$FDZՑF[6$IBEDQ0 cu; Nz03ʱ!́Cw8@~v0ƕh'ai,apuKOHEl^b_PWao| fðFM%"B~8g> 8.JS, q.F{rE"+X&Qx!qb3:Q=+H$2aP ?j齣}l8"A#?&H*幎dYнw?~/]wrph q#/77؃,~iրi>$ >GD}3\ԇ1 K4>KC[Hz-)QJT5a4$Aw#r>$ڢjp''&I ea1Z0֢l4|},& /#yb'\G)Q+s;W_ѽ(pI)G^ڻǾu`<%|Ճ>ik # xI E&0qȗ;bNJ{߶>wɓԩS;wξ"%Zn]0gWE kڈFi?s/^GS/`bl\+8w:{_{Nn4hr{f&R2A&O`ݺf{]rBkVޜ!d G.?7 @(H9zazlЍ$ VkGq2 !~o@ףti4;]z!Z !1CE@~6~V9t 8}Zpb ]g 8I0Ha%bW&F{Ctܤ |L \_9g]y?/?~ܞ:ujG^Ho7rYg26N.f[=T8 F#Ķx}o VږTSY7HX~i 7}ܬa$GW^R-ڋ%Jƀ8!I$,(" C!ސ~._io۽~guek5_zY7V9+ŜQsǏ N7aFN"c֚8o1jyԩ' _ =ض?ҝb=?qQBq=?><77;Buq{7['fEu|pl>W秂oR.nP.&1ԩ[@/D; Ccf`HXo H[gi?;24۟s 3" AY#B|~W=:}C7lb8lphFgN )LNwtzc5:AGfض_6سgJ_'J%@q>s={=n5_sfnnN$|A̙3_7{;xqxhk1ۚT,$H{dnBE om@g 0LOjoM\ɄQ}!dմ7sl;H#?ʩ)sBkbH0kl~dP[߼zvӉZk]7C8?d| '23WVdElm^ G+DJ9ZS28q;(AJ&}0ZmEe7#uy[)`ףaFE*=*BDc-jѠ4!I I}k FD!L(0&HavGʦ 9sfGUz٠,\;vE!N:u׎"Nq[\\zޠ;C}6DyA`{'ɬT VDBj@H.n(%nIZ]G+iEK~Y>+n m0 >cSmB8vFA>h1 $#HD2=="L0CsssG*@Z9t0V`D^4DΝ{`w _O6Hb?;8R?]&SㅸAv s<׎Y뎄^=;/J8ȑ^Huk%yceOOwQ0@sݔ v<~ۚPM>\rZtR-atD32$=hkFIcmJ F۔ia$$Ɖ"15=yMow@4 s\,b"pHٸ#|!pɓypmv( \!ǡw~w~'#<#4O'zwRV wJX@)igɓMi!SaƠ޴܀&AZ$fٿssN޻inR}Ѿ7&GňFϯnuGVr&A%Em-5֠)))1b~5VXcJoxwJ#Bߑ#֊h0rgp]/}O:[k{߯\ˍ@mc=sCCeyѣ BN(TZs}K#h|4Oش1&U óm뫻HvS{vjJnF5#<7ڿx&E9ThoRo!$B)(墤Z#1|hcfD#46}#У^V81.B)$y} 1jF~&??|ft(Z'̑#={`n$/FS/AeQA7U9V.yLe17K 2Z$?m[٦Dhhu6iNQLGI8=m $cL :oməw8йhG0Ii}ۦf%PJ8 GQT+J $& 4X,[Kl4jd`bbVjۍ@-Z#1#)і(L,`'lh_RӧO'?~R-_bi~q?nk } in?pɓy45yx n $M|dҶW*+BiL:k!1X#B C?6"M71AHקNZCۣ#zaD?D 8&ZM3:&1vN&H˰<'`~q#0K〉 PR5cm`VWHӄo9kera-bon %d[?{:ǡNS=o/IN7=o 8;Ɂ>C? ?.nop uیh#0BDTsl>U$Ip#]yy`uLxL&O0=R_CQB!z͏4F6| =R)<3LLQgS{`rXe|@.%xJÞvz Xq4|nX6?1I90T˰#U+Ywi77•Jɨ$m4:6_RskN8ssrrb\J%ERf_\Viy;Pa?muT! Bk"w g&g02 AįƯ?M3>>uF@.[Ƒ )AnDGiHhJe8Ih6lđmG˧$ HBNi2k [[_Y3s39G;{2gSW%rA65;DtO 7E M4&ud3rxMU|SVk;s|_Q{7#ؑ#454\D17wZ*?~6/)ĉ'q@X8sɫtiz %T 3>Vxlnn. L{p̙`k-N?!<ϚBQYRKp6cӛ[z$ώkq\EsEVVq]LA$I>PBcbLOa8,zhcl6%gll~@I2F6OQA61Lngccfzhcb$)p`j4" O?$}>v/ww嶃3xRDߙΝ&I4NR23rj$5n6/7=|ŋnqnB+Al{F+ ssW۾7:읚~?@ x̹ZO3Q05Y|RaQCYͶo!(岹Gct= UQPR*VqLC];As<6L0 #Kg&Ja C>pkwp;LNLcHãZ'IL$0 @'c,#ϥ/MO!p|l)Ib_&A %A'!$ Z7xnZk+MV1cccs%Jq 1p,lrure4|##Aa"$S3<}iS?o?.-9q=:%MKT*G oMMy 1T"nVm0 el23կ/ `; ےPŧR, GZAtc\m/RiŊQw +QʣpN3۳KFj0!\pK?Kkr*nn5Z zFZ#x"l@mk4ձIRϵt00qCqN011Mצ79v02| $|<'њ0QS7| !`1Π<7u)\+!-nPr*&ӌ#gb_{ayu%N@;k&c69@P~{a̾ٲVɻ ϕ -RJ(BjQqi:j [gR8#vTF !ؔnbNMu|lReRe\q%acbͭU$YYD"tڬ\lljMmkVFPDIŠs]OD:I8!ƪS,AFce보v 0^GO45|V`Z4*>aZI$Ji !|;W ,!R"y]_,:uʌ3lnn6Nu$XqD{銃 X }X!֤efȍ!je.SLM(4R',Rž\Ͱg| ^QxiJE(b(%X!pkH!} 밲z 6J:h,&~A'qZXY4 6IQO~vTu6omv5^o8 bE{*{zp}!JB+M{+'6{+ß5oz+j򮢱N"[kKCJ+`aa z65 *I|c|lje uI.񠋒. ǧ$$p0[1G1Kt-:&W/]VĠYXkȑIĶg_'H; "Ff8J~ayqk7~}|V^+-?yuUo=L0(D!QlT$KKNqĹQ|Љsv|cߐeɲUI*;(D#vyޫ?Z}D$̜9׳+;#?n?W~37OͿŏm+k0}Mto=Mn}υ'Qxv,Zn`ƶ>Ь=s9@YFGŠ,IhF[VG("|P`,`ֱ PJy"[ !uЅB&Gۋڕw.L /lW~7 @ +XevBy0[ n@ gOvF x/ٷgx\k!֑{7k?+4}r'~_z24MkY=|j4dG#uV{#@5ƽ!ozsݯ8s^W'v~__C\䉍]-`>;Q2yN|/mp:"hP˗yݏ({nOWP{1tS=^{wyui>h̵|U CgA!0!E0 Z\ FvzqqdwsX2W~[R?[fy+̻>v^Wx㛾w{/spm@R0ўkqyc }ЗF\CFƓE~WʁgUhw7vϯAWww=7eVP&(TUICqHpҹBd+vۋq?u@IMQ4*@/MDsp~'3VI 2\Us s8~xryA7_Ih5v<:BQ;2 g;a3C%/ !6#g[e2( &{9DQ0| _~ ?GoK#quca~>x%;6 yՕ=|sYv" >y=j8h8ڃ[X#s-?xnd2/½udD7Փ}%ϴ}!ңo ^(j|pw8qGn rrkwRt-0Cɻ,/5(gu!ԃHk{T#jDQo'1a8B@ADu"ϭ xʪb;.ɈTܪ%sC[Yo^e;a _31EͥxMCgkK< LpDʬP n/6m _8}YB)}#={B,!$[\^fRL)t>pB 曗{"Q"גk`RȈZsdZ@`T\3ix=D/GHZ\m&LdwwF] @_C;b:ZQC}^=z;1[s7?ǁ.m~;aWkf͹O1 㾟DSFA5m&@*qw }|__+ ;)/.-IUG>5OH! )M`{ᑚfA+o8psg?wS `Yٓ^nA;RL.TAR8ưB)l[stM`bLGSo~RPHY92GzWĞ$c4Iy!EF`fcU({Cd~\("Jhvv׆>%3C >PY~8h@o;3Ç޻ǂ雿t#MYBi@ezdcA5FI""Sz2 qeu l@pS0,[CEmz%ǡ@M. {ψc^emTMje#-kUrY bR(PC_@KT1L9u2y4k]@/BM|`2v9k^v}{.ڴVhvhvitCQ5Q@dq&EM\)LILŅ,"m?y*~O꺘Ť7m]E 3QI^RD4??)fC* EHOe9xbW^g`ؿ' ]^ׇ1^' ̋fOR7VPj{ tb Q#`[(syʱ2)o:5!?!8bL MNH3VYƕ3ApW7v OG`¾S{df 0"@jV?#Ĉ 4uFI卵52k$R0r{L8Ʌ2H!Kăa]-@I)Ι[ZXy~7v륵 /D&"\x%hgif?lj;BD3۸dGb-pi jܔ%ZH .OW94oJ18ocѐޤd~7s: ָ|"_y!._8ʸobI_okU>Y}:ɓH-*eB}b UAH%M! tއx'p=uH\+;! #`!!RS$B*voo|tuSgWs P8t &'J.4l/z. Zt-W;ț<ʢ=9r!~8H=~o(K㧈8a|0 s&N'ؾ|y贻4: ̷8z ofaasrOo_G!ԍx˸OV4] זޥ=# AgVJ ?a̍ z((ˆԺ4QZ5`.]s ?x^3^7{=6Ȧl0Q"TZ^\s?|ر0v~qn_o_JE]*@KaE3/g|()JȂ,z{kC(6.t,y9ѸnD|IqKw 69{,-_/-w-VVUi..w`+|]åsiKـQLxm@ ן5J([ٴDw?1"bG`8 H/pX x7t JC]B6WlّBZ)wB z/J<L08,z/| Tւ(21`SE!5GTAϤ^] %cm72hu83;7}ƛh H{nØ*q7%hD5P叞|~Gowq\^[—gw ,CXP`V'fLLYǎ)70\zΟ/pZNH`V/z !qcn}_k>m} W@GlL.Y^ Գ{O{yP\U\Gw8t?ԧDвScŌKORuVxWRM( (GZ|$dГwxaeoP-R 4Z0pgˊ6wvi-zb2u{rp{dw3DCpe"}6}FEXrGdd >q3cʐ%uTp%|8n,+) !m9okբ$_HBx)=궛|nE2.+t@"@ؐxuH" eںsUU. 2SX Tj%^ [.kєS EKKx@犫O=C뎻կ3(&`J,6 T$imǴV,JE,O?"r)}s k%,0t=Z]ǿ*_أ<_M?[ 3YG3BZM'/qzQut0vB[P={뉀TZTY;+𯎽e$P& EW)1u#߂GV CS2ѱ (-^]87:w<IG 1G7Pz ?0,;xk?S>"ia ' _[pf^RzT_Գ>9xAV.0hҿ4b10 Jř\ÝW rn?A,IDATt#JiTnĀ&)5(!0K(, !6_!odRw Qք.BJ=00vi|k~C5L=Z)tDGD< K8NCcsJ秂BƈRW/<Ϳ>lqnfxTY5X0 sRC灙VjP>6z  \ƟMaaP?W8?ȁXZZ`anNNMӥYh{ؗ5QZh`T%v2flJ^̨ce}{ao nsgX|F7{PV5% jn5,!гp.dD_DduV80}ܽ="@%?QD\(!3M|P!&/`EFϩH3163)<uB Lc-Z6s)4ƕ װKMG"PFWt攔X%RpuP?J0JxS Ii*|Uǣn2:(f,//e#~}PZX]^a~as9:V<{ ZB#pA&&G%Y0MBӂ$*g/\TgA*2n<Dcݯ|қy쒄[^Y*z!B nco{5b]^L?Mo>ژt wjY> pK8VJTF !T$z$p'\|i0@PZ] SOg,R)Y;:Π]L^XZf7.R8Xm^ݷ/Seo&]f#9;K9) ?sKO_z}M{ia\wMFZg"dRrhdYY}2n;\^b:LoպV^|h1pESu%e0{>}]uZcʞۻs5FU3g{yԩSA x\~tkk?۳;% TѭFʄodՙ;Cj(JytEoB`_X%'(,䯛5&VW|Nvbn0Osτ,oϾUʪnYoQpAzIp"i&:)k&xcso*|rF+1(^HL]Lo{^{ċG f(+ZTx#b-2篎y;_˕[ucon=LJ9XH ` 3#5C2cZj(V7;lPӬi* c*˟ q;AcnGKAr6[[}沜~vƸ`b("(Gt7=K=5W=۞3~ S|1-.5A@D4!gARY9(LuDΕ`uRu3WԼ %z%վx &?zݞ奶`R1>pTd&n+ ^w.}Nzk*+>ںIy=G@'3L' BYnjxZ_DJy[^<=BQ7RJ$k@g|%%/~8" ߻H.5tZ}ޗv2*{.]ͳ9?ۏ[=< y2h bI'=HBSdk/PU.}Y@-,.j㝧*_%A_vs$n+$B ?߃Q&5-х]ֵ =B'Lc _$SP"|_i,ƛ@2tAn Gr䆃[] `h:x`<Á{ħ蓬N_m41LT~ftf/@"$' ֑t4 *濦L͵,]foxtr&x 4PL;"@HWNEVy}8q,:[ o=vb`Z9:qℼz\ל+ޡgn\> }j.5\l p3~ʌ{Vl8K IŒ< )'tU1TSkٿKUZ\57p6eʵi/\GOTZn WZclE^M_RV-_[+(M;?uzheTM00x,X#],B -ַ}wT|op瞕=sI9Qk\8ŋkz=O92df6Wr}ō. N|C26 ü_Ѓx:SaZ;ѩn x Z e>b6d95,, 9z14i^}H9wQį3x83E !O٥LqbjZl.d;Ÿ*T.kR~AiFܓ_7]Tħe*?HWbtمB%&@S>~TPVē(/2;a2`ӌ 0XMKȤr޿+?o}։'hnqC5xwvvv?SԩS׍ƀWYZBP `R4osZ Z*x5g pZucnT'N T+#g\/BJ-@ȵEMz?J5z?SOYroVJȣՓf T*|T~n+R5,oF%|ހHYVpAD$v-՝_Ї~]z/fsӌOcNn?yX]]nW??D~S_~CGR_٭އhs 7L9MFSz.x& .bLgt[sEjɘ)E$wsqaE BkMi`0d2 RW)W!Y/Y { L.v6Bĉ,:! l08P*`l >ڦ#z,DY 9u]fʳ+.~Oi*DOL'ɅT7{fп{Ο׽uUOsO5{9aҞ??'swJcѻ|:txH^xicO<^]WY;z1X'SҏT62빵<#vי"kTO7OZI0sz}w٢O n> մ᧤L1E{uH әu/p-0%41L\^ &cF*%{t:?177SO?Q2D5j/V0x!qZsBm߿~?w)!S7`E* "7:L;gmiݺ\Mc?j{'Ow7<?2=~k&H'v׶?|7OYVYƎO#dI!4GZID*\ t0yָZ>tVzL(TEVKhc$@m~T^xأ2Cqxu)TD9&k^g##Ğ)dZ1QJML]Ąހqr?8BL>כH$Y*=E/k?5F"(VA +]~oC˱Tl!ٯU>'?$X%sZL:AWg2L*30]?OosPxǢfe=@'Y4Pk6b}$N✊7QS h6"Zy~)\0>.)s(xLA* 2?d4B dMUMȚxvx$:/;2R*,4l&?\l&x!$[1XYTMYOhdJ"bXWM75L5N"hmm q&Oena+{Ax,HCeSt&&YLߌFdyp8Vg9mč'Y慐B+IE`@*O|KVxVzǹ3glhZEQ7ZH7bߠhy9<(EE8Hf ) Rj>i( rM~nWp ހ V3y!#㭷S:O~r 'w)_I|No;GssKۃA :s JMB(T=p `B@$Qz z*~s ,{l]Kiw"dJ 2j4`Xvw~N3އm'NHlrcaq~Z3RR+$j[wTe6+ZUck}8٢,x-_yP(TD1 "31u/i̸6L_]PIZ;=TU~wnRJTk暍o+/~W~}{߳߯[n{ ;to00;|X?< \Ie=24F5^CK;{7TjR1T1h`4`411 }FXkF&g Y~haØC&T3qAaoQ4 Z?3 Xg%< gmɅuC^(<"<謿{IlM7֪S={pY~P-RTγK-DLy”-Vxk:vVSUe( @Y,r9L[Iw{ W/.2D42>{~;yfRw`K*o BIGs8'l(m5[o?᩾ɭ[uMҸmZ6 F#&"rr ϛ9DAT&hp8?rw~Yٳh46PM1$P%!ߛo<ܜL'ۤB|Xg`g2= LQOO}׻74[ou^cqeWͲ.rR6K0XQe*#GqQ*NkcMaY763=+Da}t{oßYp dx= dY@~?zwW֙xv{5'>q>M<nן}_YlrǺ>:/0BcU,d iw;9(RnfPkN73W*CUY-wMPݬs 5y%gYNQ4) ~wvh<@qO]), eAPP8lJ)Hk4Co}+ ˟d,Ͽhx{ I`4s'-$,,%!:=zS:M|EO#\ :#@ 2vL&ChEO"IIZ- Uh&3.`T£&pƑmTs+,R NhY>{{oyz)]Aw˿ 'C^h?ER+ꚟ)ga&c<) K$ 64Q#+ Ȥ!"j)|uvEo{f\ )T<@)!$/\-R), DzD !pZ٢PֽG-mqĉ%;?#f[;˜]w?>bi=/Ivoox&1 ЀA~쓚 ^B/hW @Ut )1F2([ʰ8<|+yQ焓8Ar-jã_^ٷ!W7+!QBL)Tݐp .bT z.:7΅T;vVW|Bbr N.J|yhwjvuak֘lsg\͟x̅_SG Ka=ס qcF qf#[V{~Gy՝7~7;u:@Xc(˒ ͺ=BF{.>%_cqQQ {VVh-FfElr5ye XCˋllasO=iC 0[xr%~eTyCxC^眷'#q%ŋ<~x~'J `:2X7WQ4Kg0IZ-qabT am85VP4Z])Qۘ¤Mޅh SF3Ty@ HZ)FEYs-(띫/B,U)K54%HcVMt_/|k^9)K׶ͭɧ^_j 2SiAem@_XSnkcȥMlM4M{^5O~zO~y\>ty*S1)Kru^FQg':fؽ<:,/υ53 ̌Ce+LhBԷh6;\;pH)*pk>az~xڼ?@L LH cb\U*SŸ )ZiQ:# vVK4xF7&v4< ehCB)ֺ>'nBa$'%e8HV2u|u~F֏;;k7Vn=u?|)n~2u% kK=Z}"D(s&ޘ%vCpMo/տ•k={y/xgdtҔ0)1FN@)m Fwjkm]Գ"~?$Id*6{)2lLkljwyGݼo3 q· o0ucPLŸ,3W?|i~=+?{ޡA YGe UURUɄɤ,uj-FˋYNYdYA5ZkDY*2!BjuV`w(-(*!l?jyUJI%B3١dc &$eWL4b&b $:״[M]?>rݮA=fwc%)KKˊt,Kz*g#3aڲ>iȨ vwwQ&3[;y;eggc}+/q32tZkyA^FA^f$R,KLYի dz`7֨2S\@%p.._RB kJqKfo$ N`0jJиSHJ[ eYz&C_oTC1uɤ=KapW"0KRe2.ѤEBThLIPf)T]c)M,H[2N$dW77|v1`pesmcC8qL8q%w?[֌x3"J5!qnj .#;,^Ik 8t炘Cyf\4\u[#S}8 s+n+&2(ȚZSavMTuNW(m-Eb2z,䵗&w SlPh, ʙK.j"# /SIHH}2KyO^H|j<=2X_hўG6S]c&JiNM(6։ Oκ3*PFW324F77+tV w,TnwG+_W"@gF 9Z2sF ngh]dfTbdZQP qE;t?|ҩʽT~v= ly~|\Wd:"`CCJ3GIQRT}AT-P7 5#ֈn M^*"Z͹ŏ uQ[ 1&K!c3v}?K{!d=4I FK0U# %B# hZG}E,񘍭…~yqd4DV@ɒz4\)$YZ C(㆟VGQ^+ĠS,-pָc:KJC9)q*ƀ \|QSè]ҨG(PA@II&Jx$$R2/S %%J3D6L!DhPzpr. Fv'Jԟ]ϧH]|wzɘP΅.EHpԥ $ zeJqLo 6 t$ዔbHԑE b/E!XL#T8vԱv 8͍}NƮs\%qaP- 59() IQ˟ zx>`T,wȈsDnn\|WZ8-)6D4¡S>ry7`x9ǺgF#F@V&r|pGjrQ&d__9*G21%_$k&ɝF1;LMK0]Ygh&ubR7FW'~'~g'/Sv=ıcNJSNWV,9r&TJY<ŅTl\-< ޓIYR)TŊ'8lHQ)#^C$& S8*ē\ZO=Jg$c9>(RI pU*Mf b!A |),vL7)&HY,TDKz<>66SgcLj5yA2A}IԀd ,F O8ဦΊ3E'@Id [i RLj8=EICSfC\"̓B ͸k&7޴H*${苧uu.W2RӋNYHGrnMZBrS41)Beֹs<ǿ<o '?r9(2],zUk-yHB 1@ٌMPG+'$Q Dc._=Ï}3O|➛Ŗxb[;}U8+4YdSF^ҁԚF4!db4H=Ey`IQDPAAJEQ ү(@D.aS[@ 3ɳ(U(' J' ;jPF8P)R !%E-^<ėn/SBضecjp4DvP*j"dTȩbT( dݧnؤL52AMhI5]pO!:jh<b@pDCRIzn0s4S'd)QS,L~hFILg:%!\F>D+q7E CklF<{;v̷[ǫA@QM;!"xj!ɛ:ed*^ZY1Qz9|&e|&VA-$8SLIyVJ3M/L,Λyo&8tKއ*26 YPo53o8psLFJM֌Y{WVš51 A>ie&LxH ė CGsV"d5  g-*E{TsJ΅RγH`IL4tU4T2 #W*̮evT iD OMTȉO>4_W_ wĮ*O8!O8ajf3M(93 c:[KIT#}gFčkhk}mi)Mdz | pEfȨvyk?b71QQc@xY"XqimP-ݙ~c6?|c^coߛ;?;5VUP^зJ!Tx2I!PRI=O)]J'1Xx*hS`ɘQK@;n5AOJD("/'{i>/zҨkԒeiL0^g֎~̙v^=b!~~Td2~.ǹΎϊf=ywIcZ!(~\ıSJE^d4%^@3hQF\c"FHUN4Y $BY#Ge:d*ؠNA L1IS 2g>%ɓ'mνãIȏ؄퇺/NRIβ \TZH_R2"4yRCJ)T*T̗ 6=Rc*|%S (*L-{CFhmmG*B5f"BPb "ygp&gN'Y:sŋpa_- wμ#D{W2Y"j&JwN'%thJ*8d$M Ea ӏ~n!Rc0!v "GZkVѨ h„MDO6] m\#'`tqq{/? g3A:;{)]%~Błr3\czL U:5ʴH%V IH _ uo@(ˍ)i?|3d RQ]5U`*噛2b⸍!&iDF V8)VRO"KAK;71G%PF6[M lOy{=֛JqY-4]T8SRE,-<*:4tT>4ebNΜIpsjNhH" x "B#uCB 9[(%l"RGGR8u<hT~Xy|֬Dh2y*JGu'|Ba:jR&%:E<~\:rGyD=å=F{hȔH)m]9&m%T 7h-lu6hwȊfTeZ_t7aM(k%B ,Vhoֆ1Qd"dv%8#y3%ih)x~4ֺGܧ>w˦x6C?NppsU%LgagD!ȳNj2Ǝ8%QYVBJHE,GPY:*C-E4`=z=D"QDS9FA,jI9h(Q"Utz=V഑8E 8'"B#ynn&>IN~#?M8s;dE3ō9'`奢93)=؊j<@8ǨC50Fdyrg 9\d:D !jsN{ot&/-)D.`UHBP)PBŽN8.*mq>؅@hzk;0~6Jmg7wYY^O&*ƛR8)'XgBT dLLIGRs66@GV-!@z&>BT@qR ҸXIhV#hb} %ZF@EDZ!<٨ ޛʥQZ\OgC9k4r0?Knl JHu5F,JT3;rlUBDI/5*ӘQYkalI^*'9r4@8n5q*TI*(JYݫ;;kg>J=E!Y))2KPh!VjUjI(+N8 %BH/` B4({Ӱ'q`n_wre}>𶕏,>P*L6}uMh=< jӐJƚt9LUq<Cņ])gƴRץO@"ģ t ~'~ x^>xz`+dQtnߠjh*l */Y?!^AXfH^+H!o 2/(-v^J1;/>й+qCǵ;\>_[P+*74mJKw뤏phi&wyzGce“`0 䚴ٓ8J$Eq:9ț9 ,֛A$m(X)ZP(!hZ T %:Yx wzm`؛qpS{>vB~mm祶zy=H\ќ??RYt•#dƕcdTD"zhYFD.d5[|{h 's*8|)Ε )Իx+~8=)i 8.8&X]bNѣG]w'N݁'yr:w?:~\:<'N#"1vV#oY9ш|ڷ 9,јxT:H8-+S!&AQ(9h %n^/C.eD#ʤ?it32%;~u PjZO'.&iLJZEbo!1YBNDKͷ<~e,X&?}{,yG 43fEk2 OՐՠhhƃm-eIow ^D |9UQ5NXxDr,JI5=FE3h MIlNh3: >w:}q,j2)ht3lnS4Дɸ4]Sh9Ke-<ek"7!)!Bg{= q nQf6>g6Z.KEں+^:\={#7hgdFZr)h0H"Ib9KV?[/fS2L9Ĕ#Z9qfB5W3nSKu'Óä]Dhj v%TW"JYga'$h:A>Gy/&¬ $`V,lRiED W )4:z*B(QTt9^UjDYDox` 7#G^TYNxk-<3ȗ>0S=ܿp0a/11eƳ{ ~ B3~eYsk Nb]].O[;_v]e3ONq-Qgz"I`q~%EFYm̵;ut!;(hlSYa dx< m$G0fK8t'@-= Z͂vnK٢lii7 2-ɳ+"Z܍DZxBk~,bsT( 8BfŁǎK^K}%|Y9vԴὐ~բ0X&c ;(VmʥK hd][YƓq|rbX}k>8k9àVV8w/sd{]tV٧ fTÑP79{ޝ|დrc*,Rs]Pŝ6jIgnZ8)x%Z "G"yOYMTcZ4#Q'r#WRDF(-i4Ms8Q!S8D9y[# tlh6QJRDs8ԬRYLKz1]dRovK;+zwҝ[|T [J[,2\9@fMWZUjlѮ"ǡM^-9Η"刬 kPGb\yDi.vUZ9* p(fO_[>OD^+=[ₑƆA4ɪh".ؾ 8gZEYw&źdqĦuHlE P£pI}2gC O(yؿwL ]z~^H S6`iTiTeIB"Jd$A9Y}A^2 hT~r[/{Jyƾ[^ų2o3鮷29m/#px Z.yU,ίvp+YTe?EB*ɓ_Haaqcd[xf?ʛu}~R}-ԗPVPDr&IH#11:B ֋[ɸNyKOcD+gg<ϳ~sL&a&C:tVXo&pZ'[XX\:0i8 *j8 ut_@2C"8 w]|Y%@̨GAb{4F6)<+!b* X˸l^|\Y9i$~~ ?mw1Z[G 7{;w72Xw n>|+Oe<3b\VQ?EgMƣ>RgeVn|%Z/uuVR_wu™<>)+3>YudkL;t}TE,(:K dRUŤ Ddф( 9w a.0@4$) 9Y%.C*仳#9aG5*)M`b+#Hɵg4vh Cz>]/zwg"ןՙZ#|. XqVhݯzo\)Vykf`-eei- m,IVOfi&b 7luɔկ6|mll^A=:Í^u^UNJ@ʲ,.u!Bn  mZl[0AUUQ Tq \ 6"̐> MZfH?hT:U4h`Չss]:JsZyAn4h,-X^g"EsC|/":cƚ7k.BVV &Iz[-c9&㒬à1d\2m#Fʂ˗ȲA:4y_I.)u{G-uFQ{ ;>9|]{: A.Z)PRd&Sv*-H&-Y-' ]JJRIRۉ-kڢS䈤#&bfXz˷-]`hRda[ՅFPxgfZ"[g~q_Ѹ1w1u` q:{e4l ĨH%Y@`iM07zW?H#I%цdn(Zlm_>u޷qw qƲE.}qaND ;[Tm:iB0['Sp۱;g4c Ո>{הRIO:;LSp^J4e]L;O1'ÒȄ_~e |[Vl=5sEu>F~]_Vll|L7_un1[gE^d3 N]uZ&)kj[y1Cub¨ؑO[tXo0΄jxf?ao)IRz/v< fsj͍8ˠץͱ{0c1iqn;zeriΒ)]H+:= 2!kW7ȅ$O~w11=w^waxf+b+>{(9g oX-"`£l+1)X"H_OeYΤhQo-F=~y{\dƋtzv%31 ,b4eވxʴ2-L)=ńI9eRUôO &eɤbLmz>wwǎ.h<7á?|x\ҧ uIo{MpntS!mTu7sGaQ('L˚K.PNFln3S^et1ŘxeD'I6K0R;Mēw;/Vd16MEwF/gv 3e On1qxFpnAq8|J sk4ApdXZX2-4n0#) %T43M@\a7 ED&h:Z[ڠkK,̌uqkg0>s sO~!ZI75xv[x )kxd ΐv;<%3tzz pc*ɻ)7L"*1$'*,,5% @$Mmia8QmS$yF>H6>}5ߌWp^^cirG.jpбX/  8+X:zBl0X,`,z|88֡1m]dh6hkht i /quSlN;;y;}xsM]-_Lwvpe4̡ d>"r71w + \|CYN.Y>r7| e^2\:DS6G><.CMtO)MQI ,;w"뀌W hS]njs Z'CZ&@XBM24dyNuh1ڮ1, m5z RŮ2T CԄ7=#Ap9T1.JFExBQVUCh0UͤT5a\Li]3-kӒb4Qq# 25_F|ӏ+'NGo;_{=Cb2:R ˇ)F^R) K d2g9n=_zsKL6GE[` #:$$AF\d0j8@x-{L`$,[+ $>e46U*1 J$'[AGv6sA5D` mk=PL?#KY߾|7?  /y kZoؓdijZu͸6ax0Hq|* lX6^(#QE&BHln&-D@kL2hHF;g?SX__?y7sP/?O~]?wEFűIw\4p9L?3FWIx\0:CDL5Au;9 2v2\z);;$]^Ir2eiasuܐ`,1ƍ 'ˈ`Eðr'wm s')[\pnL BD07ЧQv%M))NMDwET$i) c!ZH3Q8yX6 `_p5ihhLUWuMQL)ʒb:(ӂdd:f23LN'LS6(PLj+a!'Ol]~|u~0?)]ȍw gm^NܑnpXmƦy}ˤ`_8p~~H;[>lcvy÷~7_d9|{<̗yPO?ˠקg_ 2ڱ}y,əLJvzdJ2,WmlwDEYM.mo7uɈqQIYR6u4 f&n%Z8!p:Na6b` "@--\OPI? pfJ;n)$B'9ɔ/r p\<ߩYsL^&%օs>*zϥ'xYtve3fo+|L.&αxo>[SW W,o~͋|{M}rowSY/ X:_߾pO;ٙ\+&o1wN?0f[!y8ܗhnla9",6-!IKP h∰HBkRĹ_kL /$3$t:!ezN173C4<&p.[Q7 IARVl^*IIG&iN9Xܵ.N^~pgc7N,%]n Y׍Z)F} n^a{Py֋-g٧z C.=)'…h|ٽz׸wRM`l_t72{x%1ZT&zz8Wu5~W_aHͦ7^I$ߋϧE[/"w# 4ƶ0ѐLʢ4zDGCbCkͰ7Ǡ76gL ;rJYWMM-vA[E% zLJR =I0*"Q xn wt|c7$tO}Ͻ]ތO'N5-m6G0"Lyc߇ʸa BEsF8i5MM@)XB¡Güa2ё(1aLEkaPq! 2 G<Ȓ,9S*,zvG #SNa2MԉyU&7 $gs_7m ;e+6wln c}b1k X?s۬M/L.>ۏo{ܝ?׿e/wX[݁_gZpF>JRxM  O4ߡT D)r}S7J9iJ8'0.L HElB$QcZmТdRe Bf$nJX^y_sO_>OM^x'O?[_O~ ׸osOpa}f>ZNJ˚@n;PTzwhNT:AYXqWf&,#/k Z$IHa*;]L]}2xgס* "Xtf`,B XC K~S#EN`LG&l)m~ݗRT~6h]ЀB)QHdŤo[fܩSj7OM?}>r{w3o/s*8akQ4擧[>My`pr885k~1/!)Tw`.ZJ%.9RtI8geJ51$x=FFQ͗$% sXll$DɓBDw eަ;;虇w+n?ӿmo}fni|`~aM 'sqrɫnϜD@VVV8HыOODqR i yC$iJC,4MOO$C&ZqHHP)nB `gh56(_a+IDATGFa`w>svI Ybޑ'[X0u[tst@7N:~/oD&E޹~wy? 9}9#ÞȾ29G"&qUmu Zkkdq2=s:J XT<= ETf<Iz]&`l?c['si 5HP%Zk,:r !Z!R8$I%iR!(C(vR}A=!{YV%ӽD+r//Mq;_<“_~g|EG]sOolLmodJݔF%C_ܞ49wzמлtۼ.'UV]8W [\ɼV_~W? 3R쁋zH*o~u]P^ wk5BsCZ2 vx5 kl< ,N(u4ui4Fku 8Pz,6i*kςy/Gl($UTDJu/X뢭Cg_WA܇>}_wo;~=n^&xP&MsҔcl]ThI=pEE17N*O5j.vYկez6t~rm*Mz壎mâsx='\$w|^>_x++:(bӏ kV|"7g},Jݠqބ3|txP' с'+}GyG۟ ~X恥wsmw9i_BA$~/?n+i2`,$T창;bgLO[gM`mm煿3-uY^>EXI@ajb -Cf4To_&E/ E ^iJ ͳNA0Bsh`QwsT`c(0 NHTl]®$p{"=UW[fghCAX2~?68zَ pŸt>dmv'KJs;ҵՍT7S[EϤe>zl{+oƆܤ72{U63Ο"P{qq C2>le׷mMIrZP\\шwvmA A@T i*i*Y>@#+d9k0RJcb}|{t œ'*"Ez*W_B++8)^WVVfҘ8,!aIϝ~Z]]r}}ݭM[ g|_Mc\cTfjF xBIo=:('yt: #jD f3yb>ҰVQahA0TK J{⺻tfǖwZbP.AԮAH_4`(řčدĉƆJ^9|WY9݂7wƾc.Y%eD8n?/L% f*VU9Ox2b<3xC&a b ~*޻FL:Gm9/J1ַpRh}#Gp A7 &W/Ypuï<>f k?QU-]cå^KY 8H"ue4;BEF-t:e<$*Js<` pX4 R顓]·iuՀ]wAҊ/0ZPO$bn|>tVkQ&XI28,ZxwS(*lh S Bbb-@C=ynNG' @)8+PEH$JLC<&̺>Cj%``r/E}u܊Wm8q3G1t.XIBn=f y\ka=VDhIg *V*wA&9Y#m>x8VC6HD>0 "Ul[2Po=8Ϥtou߯W~᠞O6Unj[aV-QY \pFIJ;]$s8<@=HdJΔy9zAC\!wBڎ+>Y3NDPkGh&UEAYWK `,x Hd4u#6Zf ӯ|DaIۓuRhk#1((KB vszn4Mhh%~R"+A`e9nu8;F>$娪ɴ`w~^Is8ҙk'y'siI՞U˖g~n@Yiڐ$I4 !R~r<1Ŕ.QJ:y(F`)ς3K!}X+l꺡kFcvwckkͫhwO펊߹qƭzVWWªhv:@Qa A"Ŷ$:!2 }jit ŷ(!'Q ^Y; R% ssyN"c /C|<^7I /% ZFhi%1{lnnq W/mr.]fsk;E[`>ݬ8l?Mw-8.bkuM)(ʊtB1-:> M}k-)0Z | @.ʓ$I|NtV$ %If$ijeϟ9S9nn ڊkVw?TֵC&@@H"oYkZ Fk꺦*VLE輣`_bZzhxm̬; R̄FIwSzdRBtz]i W=<8w:kkk khríp+^fݳfTWMT,u$m?R26U6\}@c)hd:e411LLeevyO$3!3IW&$MOimZ:...#T]e ޑ$ӥwɲ,,ۦ(G㇏.>яKuu_ȃ~G{z87L;Y<IDf&mbgɴ`7fx4bsgdJ]ULs޺4M|&I9g?}R[ Ɇbmw#mB$1[Ås7hi-G:G[X:4ۨLSOk\.}衇nˉ'O&OJejj;v* 懶4IQJ]@t F}8W u0Mcwwb'wz;4~Ovz=>BJlcgw#gx<>Ϟzn_̙3ĉ>Tŭp+ltE剈> ).( xǣNGa!MSʳD).^(qn>$IO{k_끭M1gz8x,8I;uꔻuuu5<oߌq+܊o(n`tý^ލ)'SVC]5Ed2mJY'e0eYT$Q$M !׵~d?_ N:%f?B u~E0? .H?s?9>tu$˂y`2Kd%;yZ}Zr|E^jʊXc ָVĭp+^Qܘ~?z{&;'64_Bt%@ %~wZO)v'Ox[+?iD|IENDB`OSCAR-code-v1.3.1/oscar/icons/cubeoximeter.png000066400000000000000000001242121417327530600211160ustar00rootroot00000000000000PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYs  tIME .jpIDATxwW}>Ka0E)Bup(k,|@OE_ 33t4J@VksAg^xߕ g}goۯ.KKlZQ朑>Jr ];FTf4q ϰ fNnbs2Mm&)ha `,mHTHRB$A(NXWmJF1\CW!kC/f? g-Y \rvzWmFd5݌-D:e} 6U 18 iA_ hnB X\VbCQ#G}` n䲵SWEi*O }T&}֯+s{r#Sii#X% G+I!qIhtReqcQfpbJXO&#jEA@ *OjAJ5c\ϧ'9~rPLHĺabgߢ>}'xu'^~B0{F{.N೏ǧ ذΪ-lP`/`r|(u(8bjfSO.M(jHa4izɓs>4t@f'6]LM IH$$VCbEQN~09_DH%4Q +SRm GgBp'4UbExO?T|? 0~؋׋lƃzfQj-]eu(4,N!_K&NчGT(G(R oD:E'DZ݉\5><e&Γ_ލWR\lGsBЊ[$&2tg \%[_ÚaRk]%J`X#16E3{&Ck fxM2_iz/?Ae ߙgQ6N̿Yc{j<8GuyɅjf&av8 sU/O.l5}?4G#CfBi! P#2@mW^38C_/}?2Ӕ+65r-+ѻr CCF5:R↋6Sr0`c0 hp`r]}|h"ZOva ;~F%ȿ;R3ġ_OOsXQt+\|;+Ds40Bnl}'Ǐ`lHw u*K%nBQa | Ckr\yLN293IP ;z 4&ftz#N2_[5LZ:Wosr7aEXAenry]7ٸ"73-a _}f>0EqH#vOyz#1͒9jMrɐ `iſY֖Orͽ<4Kul~&zkH׭f`U?ʍ I6UԗrلXיglB(0+7շ#9QWIĀ(,('IR\b BbL#IL9oKkϾ$ r ߙb^zo |nvmQ:+J\c-;#Cucḛo>{=Ny$iC)Jޔ2{>Dy0\ ܔ(J֪^)]2zbF{}>2X"mɗכ4ku4E.MQRmcp=c,~XMAX nr''+y\5;G&/oLdl.EAZLQI׮va}7ENX`brZ,u ۳{}yfxdp1W]bE!$Sq4kֹ\C|`ӭ(tx>3:Sad#GW$)P=[9"Iزi =ؓByZv '3O-mѨ%H+,F}xեiGOPi5,{p#;Yy+9?q q0Ɛ$ vR0) ^%SLWѺ$Sy/ɋ|L[a=sޭ?Y]W^*^y1'*/6(d3r=^>$:Fz%%;ݥ1WgEp.~W[,\p@uy/yˁÒ'`f*G88ky莵=4d/70X`C0x&NTkE!itP1"%mR3&SYt3]BGKQԧƨ/ap( P(EDb]!]L`H!@Y#R[dWTLxD^o%|g2uoyV>8ɑccL-ڋx織22ImaVa]jC<|/1v#YV),&,S"nQ?+[Lk/9V7aEs m-]=Ӊ4<Xqj :zz ~AL}˸C[ocvQ//qÜ{xը 3K'NCh)b6?EWjd限z%?GH]8]#tm՛/o`$jhDv=UJI5H)j()KNDG] f??y9[W+?໘7xIOP[ۇ˶en`>r 3S8% DfxǸ{xѯ08EJmy$DTK7Ib/b~㙧-=%zV#\GeZN:y~ͅdԱE^7DT??;(p 7|F}unf'{%c0{A!pc XΘ [MqgX{ 49vrV="RDM0!ZsXRVn#19}ByK](161S=}O<\.*! \Wn؞g9O\uy<"[FҏkeQ?'.TnD)l_ڎ&\]qbY'4jEe]G_j 0ԗc|{{ٙ->qBcAX\CBm&Mv( $& (]'X:NK3E-Y=؋*/j-^禩Ot:[A'] F\ɥxWO|)<s4B^,53p?yvwc_#8.(KהpL₟U 99S'7`{9ᯢ"b%O忾{‡~ MLH= )uthV*~>gZAUsLd+"rE}Oy"7^b.IYSy iWDD[R1xByLt=ld桓~ >2NN4tMW~|ާNNbwH#|qϗơ#ϢMs, jL6*;]&>׆W./c-!YBKJT𱈌OHBb %Y޿UeKX~?7^~zn//h>R*ǣ1."I<'CD mb\.λR9݄Mz€s<#CP*X,)f0ƠBX$Z(;;R-be@&TgQ]8JϊA[a M֤[qx[!dH/ص7scPҬ%DBGF{;ʗdfDZ3[{X[^d.{Jܗ] }Bs_ߺ (w#=ϧL?9ܑ>~7ԛd]T׉EfPi>扽俿WiQ,) f h9VJ~Z,dyk/Pɒ˗yOr T׋ic,UI$/7em$%jhZ2q35GfMנT4 '^M3Ax|#۸E82p۫AH[ 2U0^bDQUy]/?FQPB'!?:5>;kJ{7x_'!0(R%]i.-¯},>.XReZ&c%AwsC,7mnC6$EV3q9-s ;[\ݞC9"y}ws/-'OKi6Gxťij3R.}}t{i5nGG6D8 52d34es,zFsVP +5B*e?K^Bm\Ɵ| |ANv\WXAgQQk-163 E3GsaA>;ٳy8F*2:u4InaxgG{aɨ8Ze–yFeЛ,4}~nN|n9rA@+en@wwR]C r#yO|"_.*waAk*<װX#vVг _p]Ck yƱQmӽzѢ( q=MVd贌r_s~?:ɑ?Zez8m#E٘ ӣI*2tx>ﺇӘRvd7i?,68>nwqb%׸>@?+şGSxzIiPXb:l2䐗g^K<=} 2_ ylS~+|B Ch[>juw4)7c:ƴȕTY-EEZl ZW42zsr+4EbX/a}(ttFdiq$J0!5x9{0vIuƦׄzp dQy]y#k/jC,&ۍ+?tђ1 z5m JZDdIP,qD՗sy] RX 5R$g޻瞧l刓V+<QPT<"k,Jrk̅t\ 9'ľ׳zȮ]IiPQfgh-{M7wB(W09@faZ-AkI]}i}(3`HZUlH#M ;M[P%b~ B~G?}Sy7zhUr+qx0> RJ=[y% U9e(mvQ:yQ{j3bWD= ;8:yǫ0$2W!4!s%ZktK2kG  0;3l4I o戛 w}:mR7Ы8/cyZ,UV)b7qexwS^wKـ7 _ ad7u y-OgGXNIgH#u}8/ 3W2"B=O"1 "2Tj8Њ}:bv~ Hhn8n)o}[No>280H":1&J]Ҥ卌zk.B@u$OIi+Z\FL4h>MO,q޹)8RpcEJ3]]=\.b&Ay)b0aEd'_58Akm [-rε^>GGGRfNŶ 4y# #q  D׶W?/;Fhԫ|鿽xndǻjܶo"+n@:ebVxolNcaaH~:ׯGocMWб2`_@1p9vxKQgj BX|efv5CkWj59?Ї8sh!AXI\@'mUA[xut 0;g`k P5HR%rc'OPڃL|S}#N`E .<[@'t)k M>?Ɵq#_@4ؠ.*hMDj,,NQkUV3<4¶m;FFwtwwOOR(F)(Pcrز 6`G{tv7v $Wѳ"rfVSw5gI=H2!Y0ul?$Wda dMFm{*_W^rX>jn`- u-d8JCB cBZM:Ũ<pV+vN.QLNγQ Sm46&MSi|LMMJ$iBk`G"RMj\E$g+%TJ2.=%%]_=q5pA?M+{ɻ>p Yf XB*KZPۢG$o)BTO}}xH 4 QʻLMcqq b{捛8q$R*nF߽Ņ9V絏{!kij]:w܀ 1}~Vx܈L<:Vm ?Hu D@j -/haI ^U=Clg.6c~FOwsU5ְuכEY!W,')Ij=gmv} RD +mןëo"9ydHq2$ ֠l.l~k $y*!C,@vfJ/dnU1fZs_W0*6/S/p{O+'Il  \` Hc%򶭧tyW ={/3{hߏGlΣ)5qach\VQJyzfaq.׽y{ރ֚5<יO#)f] 1guWA)S`~^c{U'XF3"(ђ.X(I6WĶB*cc, UvX`dUsYn>6z Rc0;~,w 1{š4Z @i r+VK6xm}lj'"(E&X-£Ѭ㸒0RD3(E`RDIGJ&)FtJzHEvӿGp6^t-1FGɟI^|)9BLPkbmuW\GccDc՜@&^4֑1 Ĵ}NL03FX% )u¥XHKQȰ E!%/9m+J#B J9-(AŠP8Ӷ#( !Z!@X'H#E Z,!i/GFK쟘$PGYZ4tHVM|grrO|sϽi -1R¦Td<66D4Q@jJcQYws/NU̎-\Uǩ|T==H 65\@/rdKΧswpI8Ǖ W():dK6 e` ` Z&1_ l&~7۶mcff4Mp%-lQ[1X=)rWQ;<^lH#yzѵ<(ĸ)d !fan~ʼnK\Kfۙ-m=z/}˼oxIZxoThHRRő $i0(ctVXd{^0@q@0 \wng\bl 9?QkIt9Diqqᓟ(Ǐᔮ%w66=?a/d[L PJk^Nz_ KgJƎ1Y~.`( _̊>V\ {>qG?%&>ZSϠ? SPYj W-fg')aa~vs9v'Ork|#!dЦ]nUhZN\F6]Lmjk0͓p ub!R/-HLdK.UY8y(xrV[<| En"_4rHkOudd;{Q$X#,NPXH)PB"1Q+UI a'a +캝Tf0=ve}Jv*I;hC(&/8=G;]?^ϔp)Iȵy#:B bUzB#2#DZPH@4 c&F咤 eD]nVv_)3O||?$YJLNVjis^mmr,F:h+FKYT*qn"4I1J>fLEk?ͬ*?,-HuT IbRqLPݵXt1  vw1<٧H'6QR$N7àD'q\ߡ綝dƑ!Ae!, O>; Bf< $[']VZڑ!2ـYennS $kC={=ww~%rѭ%5w0tmi݋YDU;hhl%H 4?N+@6_'X2X.d<-sM3)R>t׼/&ݲ='?X/"CyT`*tmթnW-7=FX+*O j?\?[@15=Mbb  8IўmFIKcZ 7o ,MH%- ik=bR(kmxpssqC`w@}G9HB$ v6rί־a6KV>(h<G>Yv8 -$T8Y֬Yjrjuz ~;Z>s饗~^ּoQ_4)$%kW;(umdir#4c/ ld>O剅 l$q IN17Ko)wd)xR(P`-*un2:#Jٻ.$I)wL5#cŻ¥.s HfU>vuBb4ִS_ƺ#%K zٳ.9fhyc M`~% #(0HO"j}_ \p^/^&$B1X8$6$`h;Cۼ2]lJ.[xB* m;I- ͐1C<ҏHa>ѻ?M1~]/$Hbrq;Zy)\:VlŐC?;D<@-2ҷj;&^/Mi56t "359;:p\XlXQ>>4VO0s Wq4pHZ}{iN-(8܀W1+IbM\O1%jH=z8cKv|$\2enaaffIuuk)LLL&UʝYqB`xWZy[= cǧq^Fuk Ƥ IXBu\xn.bi~=wz EEZmP)z^*`<|CO2wI($U \zViIu6 h4avj7M5 o|#z[o 6rwo>UuL.#*KLG0x+d&4ODDiՏÔ)w-Д58!qQ? LXYt\lEZIan̅ۇ YjFG퓂tE9 sԛCCiOl޺c#$DYIY'}0[؁]/BŤ5" GnhhF<|9旀"+ٺZ+_w%6|#Qv)LUЉA(G5]}{vVvVF)sl޴qa<'OŻDOo/JI=N%R28Ie*z=k|In&wLC8AdǐـZ/׏. (!d-0w(8y'T(dT񐮃pɂI\M1O&rx5;X9PƘEZ*ŢG0&2 s'\Gޏ6(:d \Owo '(ҨרVhVCt⠤T Bh9H>I(g[ I#׮gPCZ"iPƒW>IkULQE2~;y Q+!g۹[Q4U\ ڴHtJ 2$c7ʪ[Hp6s xK-s?i]OKTrql;X ).6l 010"#\#lEhhZ/^;e |wq~rr(` 5W=|cv>IE8:¤1Yt9$WOȯ5EH$̈́R1G$:qT鞤x`uLtb%{j3`%L.Rb `=[?czzTGDdΝ4 <ȏs y_M!{R œ}r!K1Fz$S|FXsmC-B:m[|3!N~aaVwX^|rj-E[;hd C#NS %|wRJ ?#(Z$N9y|pq%v ~ݿ\ GR]Zd~~GQ]fh-neؽ8j o\GTlt z压{s>J&-T#qLl,MƧ&xǩ/,! Na^M!ﰸ8ŎVj5r-B a6lܰ>AObY*t,(q:MqV> %.Z7[ƦOȣ_ZKV:"!ᶓ TAUG,O=  lۺF (8*W12h:O ߉b;;VKId$tq'2 ,x%0r\j\}_> 0Բf1! dJ`Ni!gdz| |*2HӞ$-I1hDHnzXK&/)XIp,(PJ!h6zrxׯx4iy: ,==]L'I[{hmFcE |7 Ibϒ\ģYr-8qF& Jh:ƵGk6XMzb ,żφC >KT+Klܰo'No7 u&>O2;S!MU'L,̒l{BUW8k|fG#m%5] }[s5b q ) pRAmK1qWQ&AT.RF8R9C2nP8=rք]?OA!7ik-i2??_œ-[6Weh1BƒhqHq@(C*PGu HHYX9췢ݯJ.ghOL"U;gavjMQKVѽS3 einCc#S@$.V[HNT 8> K:v+Vyեk:z/_{1ab!%1FvS,k@0E-$%M5{-[iZ)zz{iN 2[ '&4MobJLj'㴦H'v&xU^CDF"`c+K,N9whQ*d{4(p ,"nqX;ԏN#8rٮ\N%:e76ȷSS˺u?#!jKv]K% 5)3SQ X FIb|)K%W%%RUqVxrO?iNC)Nd}Ԓ'%$1LOP(12Rr qIHR!OA-,( `b8qv?Y B:Ab!x\u+;gaaY2^ kBB\0 bi1iQ]|^)<JI0g0:5+ЌjX !'f84pnJCӪf& ĕC8I Cark?KqQ*'))O> `e{H~8)#m&Uo2nJz:m(BV>2_xZZZBʏS,>#MXT X'ўӈ46hmH%5y"BiB \K'0mKᠭ>5Z{.'6 >Z'!eĺA<{/R,dYXgZc$]OxfYX!Hݮf4Ǒ$Ƥ!^Y_>}[5׼իv_s<ʙzqi1]..(G}G}[o%2dY:L)"2kIN,J59HTk1g2IHgps%Tt #5;ʥ$fL#@bJAB*YKL*-Ak_W]%ݝBWˢfGm'em"F:ʩ#V^s9۷m|$enbF E7WXemdh$۵6m-B{_[C7.~2]TC<*PڼKk܉9I|IE&XE4$qLk#,;9*wܸng<',GEr'Y}p<)P֙+8zh{I)g\ZəS×J׹Q\'L8")}+ρ]|586:U '8w!ZtlG8A: twh4+4>ny\tZGb+0` a56Ƿ 671k.fgg~V [=cb qF;-S)P^F Q}D˲A&$J*"4Q#7 oDE[]^1kh8!-;[YsJ\٬3urm:>Š,4 Z¡顣DE@;4! #%R4CqJV;_<2$!2 l$f rB:Z@Y.m" ]nwq#m'9"J(!?"8?nj 0ȓV1loa,&Iq=QkUBuս4~}^sef9>EGWMl%K,v:= $MƐ/wBhnsaN:)o{ /չd~cggqt4F}l!;8@+ i\<IJcN8t~@( 9Ѕu,OR:Oҷͷ^F% ǎ= 'H #Xiȕ)v2kH|v/*v&N @< ~C 펌p1VhD@_7~]4B,Xn'@3G6ҿl"Ʒx駿5ZoGgxC|+%4"P@ňppBqukC g%#ڴ/u&D֊oܴSt,}a@a~w;' %|("GI``⒇kL D)1fpEXhIvlBi#+)'G*G@O-cZx[uOP^[I}C#6&PDC85Asn[ /d%ﺸU F6E5YWodP?(鴵lv8mB,ۋ{=AE=[Vy9Xn$Ny?|$(Id~f`|Ş&~/eQ%%טM2ˆ#-`mi6oպ}v=&$FEݻ85GooUt+T")iAAZ}m#%kҥxU9o0|KϡKBgڡ- x4M#>KEtvq Di^ ˽y۾t)bi׏v"XHEU3ȔK# -$B%X8ENǞ aY@ mvzU*thOn ȱ NͬU5BalGvMxCMshO!Ma5 -Bbi(Hu@ n7}i'F[I@H-J`PE`OBh`Mbu;W^;h7v H僔$:kv29vTw_&_#׳BVl7H,`Չx8NjM u/) "ut >Vdq" -Kaxv%<AGHNQ`$ѸBݼWܰ|5ij"h7[`)$r9&Nqbl99 ?1 m9o6B//?Ϊ~>K/YUk=GvR fAcI2^Thb!jW%4I݄Ķ IbFLP!`<7 N[M,`9:N8B`]"uS.e)8|CHטvdhftҗ~@$HI'͟$6՜q%:GV!s}FDaH{TD"8Qmx x I)ѩQ#ظJZ\ey5l<EQ;M"N!ս{!"#'77 \}Z7Њ:J]2³$aa7n@"RMLq v)56NaP)(K8$BhU#*4+uVH%@b ,;HT`rDƵl`QJ-[< HRmщ•ZXQ*1D$NrTq-A: HlDYLb ޠ0I}qY"ZY{`2A *1@_W_v%]}Ţ1m{A) IrZȗIؿJF0 ۷GNa綼UOO5A΀|iu9ɧ>KG`޸UB48>[ׅE =- j"cЄE&ZY P^!=Ж4NQ;R$Š\_$Fk4 .e{qB)@'z ZbvK ֤iJ\iB :\@4ItH6P@g|&ZB#Ś/Yt΍Ό!:HqO hm r5a[nd'Բ'm^U PG*El5h"Q#%]a&ѕiѭIl2 k`@;i&ic DǚREïUTAJ+|H% ds.bt#.Lmo ߉ߍ T)MS}/|奔KӔV'ȋIx+nR0?@oO;wӔ%fH3?[cTj3l~;+֭c͆N3{Lͥ9*s'IkT%[:AlL-h6j $ZJ4$x+]%+B,Բ!T/wS(ra{^] ˮeRA+MatIs>hAs4\P#SXG<(䲄 SqZQ_Q' oq,׎KI+e#[Yqq WّAQ|֚e-xz Fxпo@DlZi 8{{{'RR,YJ*T.x=3g{<%3 .ה8vaePsx-tm9h"LI\G cRFhCu<)`9nB˅LNۑbێw;(R_Aˮ4JIcҘQ!cx:A\G S^E{6QZ*tQ3M*f۝BZMi.G(~^!<%vr ѬtBAV hq[x˯ddm?Rt@ ޲Pu47SU-^qw|Ϸ8eAH^r|W0 '|glGVu C^~Q%"u8I?ÓgGb_504P$O2=>kpFL;pd@Ԋ06ƊvN\ն0`Shn{BU`m?u{xUv;eNH Bij'vbO17Nb8.:ܐrc;.8`_D1U LB vgrxާ̙yG͙sT/]}loq GS<˜V,x"E H!pt+#̮̆e1-ct& {ѳ$Gǒd #Q^qJ|ɜ!7 sktA K*;҈$IщpxctIJbltu.:}b|%n2Tad:[|_xta!E3TĻ@y8A’nx.K9bhIA9I9ȑ3?QMCC["W&.glljʵw]|{[^`h b]FFaݻ&mPb.GvP" mpk[0RPh쾐1 :'ӯ#11 Z٦}`5 l>PRIGOE n'dH萵Zx# tƦ޻?%T$&D T!kN~5W\;7_*,C+H딩:w}[=3hLT6r2 Z.[u9(MڻѨ6(%B"rSKtrKV b Uk9GXTHXuϽGZ8`D |hZ3 B(tM"fbK hg4gf|g{'4|SJȋbʑ&3^O"vSBŤQ"dX)' j|sO]koJ-@T uiꓟ᯿wL# ;EduAW j%͖k:ٰa-Ta'R/WwVd9wj )4M</n7!V.D7V3!)+]J)er&ےs/yŜVnI+eT[AGI Q)x"B>p+ *ބ8 ʀHR C":u&׾Bu[;w9Y" IQJb^z%>?ښSV)EJQN)q m#[_Wpr`L"MR暈 !Ȳ<ϩV:+p}q[^"7`(% bәhFqo"Ky`kX'hc#$^F X)-Qt#L $ ̸XH;we4fL] =NO`eZ0}C W C *;{e)rK<íM{601a(nHHK ,R H{h ªJ9fgg'y%㊓{>=p<;-鈁rGgqYLIKr"LҢ ch%x{,!-.p^p`ZidNc)t>kTWWZM'w3<=1d{fHP1:ב83E%"YA]qwm\7Q-k:Y#v.(g~~gXv#CDI ޠDA E;(Qt'd]"0%-lRK9ʢ~} ŧOemyp8_Ra׮=^ۥa`@:TrnNY?³w$IpCAJ"MSsxAZ`g&AxDc1-BDp*gr.fD0 ¶!.Ik26w(#UFl}V}?icZRa9/hB P g&R~}װ~(VW ha\4g;_o<~Jk61հ (.LR+ m*GzSpRpcKBk] Yyu =.g(f<_VE/enq w`$2]{vqߖ |T%+3I$, -td+ ]D' V8>>|5As9N8Xӡ^?@lPNet:3XFa Tq\:YA:,IգHE"#E:b۳*q碵@| Vş/|~kϢށѡAR%Q(!"B(U&^R#dZHJ s̥;s9m8@n{zT@11== pX99=cl޼c"I_:m`#HXSHq(%EBbX5X&:ɻ^(|={p zLJiSBabz˛usHzCvYBsEQB&S8S?pH&I0^;3웚FWF YJ:BwvZ!BI8H/H:-ZiʱBFeh&8?K*<#T-o3cdRI*22x!EXffd릹Sw]Ezu B] b'l߶?瘡0Jj9R(#$Bw/2ϐ>y(VPRYMT }+B%IqRN+(H>mVxxN+ {wY8!N#<2wS8Toĺ!  8o=`:${l<m4D*-@IE%!m U;1J/ƧvX'0^"Xc&5-Ff}(V$ RgX9T]7]P:=8WB>=;?d-B >ǟ %VTJC)(HL^Y>ghu'59aVpqyA{5(l#v6~!e?NݻٳD ,}.s8 ࡲ($arlji6K_;|>DMY=2B%!S$Ey@溠omoPேj.ks3T<0œ%<lߵ kF(Wb)?~|8_?p/(ES Ǣq{xkyVݬ;{W]}#DqXCIq>:& ҒHk9 != "Œ:vvJ4P:)&dJLSiJ7P%<t @J_#Ӛ]Bn[&iRTQE*$E8$anN!gDs">}-0̉{8~UH+2:ɖǟΉ:F,uzhɫ~Z{G#*YKzt 8BgѥH *7]5׭/}o}B#_GPʓ D w,BI>JiCGa: J{r!t s5ow5Xr<&ngApkRJrc{6~ l&X9܇T|!"ExGT`<"tÝVVı&DGrI9}P*5vjr-oota\ˌtw[zCV$$kUbFzAqj/w>z# W=TUЊFhuw<7YLg4H@K7 X'16ڶ#p# K$7VXA9)RGkpH";1rv}\IR!*1. Hlez]L%22v < 8܂3 h Vzt>!;dq<X[$!ylS1(1AyJT4oi*Ȍ#Ju/XqH9~K -\٫t )J{tuo`d{' ?̝l{U16TARD>࡬u)JpV&U B'H#9B*QFhUtf&fWw^ړqu)SBV2W,T3kĺL8Ny'!HbW<ʩЌ2z>lr"%q6w;)gn ?-=bpúTq)N)sO{Vzؿ K$D`dV ĥ2?cep9 wÞoB{O!\7 Ma4`1oSO]n w? $x1O2LJ"|%HJ #r%i2JW)QT071)GUbcl[?|\vD$3-$%5"W\UߝϝDd[~dU"~%H@vmBUJ#I)NJ\z 4dt=o^A:!X8aDumBIZh"*%Śգk{sr8œm=t\k)~ֽIs)f:o˩gW>6pvzdѝɼRDi#YdJ+4ÃRnjVWp`b "*;'ԕY >hvto6E*Hk$Gb6 =G~*zt%}!o$; R;s#Y!EpS(Nr!%Qg<|x]tEǂ<3(I駞uRE4q'wGx0'}} o^Z ] =}"~.pq ؂B*sFy =ۉD$H =ID5k;|*O>Ϗ_8 Wt:҆R.t~]Ǜ+F'IDATxGHu@dpa]d_LqDZ Pqp{} z',::VY '/!}Ѷ9<KOnr"3ɹr2>q2'kOK@N)hHs9zr5!ڹ7g\1UC$ 4a^q"LqB6d%.7Xq/7WmOFamX场n^r1׿B^w$:b {ͱ.#57z.?Ku,N dCRe1t^M<678 lIiZtzwbXtA0jpE[)92z֮Q{O 0+ws5yC5}N381LffpWcMͯS6NJX{y/cG|4('`/!/EfggI4H} FB!B}^H0Ʊv~gマ{8M\~EsT7q's;'#Kشi yt +\.}U`zBIIt-S{wN:\M drATʂp^(!o" +'CyO"56˹7p9`/]1AX*I۾p'=qMq(585\uY5ZC !9sك{u:R!lZ4ggTH%qއ5"jrWcs؅ !A ֡Sz%\i43RW^;:R?#AV)R^Sx@\Rw\  ߻^^w| LOQ{OdC$@P4O"Rr;CCʀ(i Ӯs/@)QLFj{0u4)1pKw錤O!J59\ 4#_ho9 _w(7]yTUFZABM &R1:<I < 8 앙CҊ֣xlЂ*$XiՁjÀ'3-s"E2+D8<D4sΣDQL^q7ҋm;vsrMA9Dt9EQ@B,iLqEg_G1&8i0|9xR~c~[|#u)V `5H7_O,=RF6Z|W}3 {G6o|Љq͹zVcrr1[صs,>p>}}(C N!%Z Æ" /*_*,}O!vk⨄+h{n ڢ,dP[i6ؾL))B m΋/n-[xW$1DZ#e1 $\@{H!^HnL6[`T;{R`m[so /nT#:YΆ~0XG'}G} u)%glFFW156Ƴ?1jҨ+HS!gӂwd& H !Άx#[T(NA4[NPa'spp]P&&cOA W1ۨ3Ǫ5'cnV46'CBB[ B}"Қ^:6jg\*L_c-Ji4vT>fMa3ys.eZFxI)>&G/e˖,䎄??y1faSg]PRasK"IM+Dh 8XGx.6 jiI, =>*!+] !BLF٢U3;,ʷ ^B.h6dpUxGO'QkXYjswBa]1#1TDnr0-2~ocE-%;H#):-16G g^'o"Yb5Vi:9rۮ.DtH_߳욀ĞGcq۶mcڵ bUyt<ԓH)t󞡡!jJ)_1(yx@Jbxbᢄ'AT_GO碏/@yEAbاd:I&''iZ!\Tq"9؝ sJBvڂSo1ym[SЫ_Ґgh ZaG˔RT>= }[ H4$)ʼnNQ|%U|;/}vE5>b;cv ~$lޡD<̳|>̮]K瀣I_zGYfM9bY9@x) dӈ#?G{qF} (E R <ow2D{q8>.ޅjQ5T&)zGEYE0YF`1CsAb$!ಀ@qrT"}P:uH8BKIO% ~wd?@3(' *ݙ<ݞE%.xy5sļ>TJ ) >;1F5:%`3b0Y@_#˲9(qynNԐ!tJycI'+l۶ZT3b+h_@>IQ a7T^ ryS&k7jȤm0w6Yf v+PJ!YKd*cY*}5l۶,R$M(Qg9_Fn2 r.צӲdYr24 9S抋T Px{asF-Z+[ƖϢ}DEb陝^f.Btb;q?ۏf> '}K=xR# sG;vPX1KL,4zd#!p[t]n8]EZuU qM"(&39ZYVXeL'G|ZKk<\-mqpڵDq@H))At$7/8uvJ)K$\fժuF6n8J crL''9xe%R o~[t;ԉd#=;Ɇ5pӃ>s=wL67G?[z5۶m Oy'RIۏRR}Pu&rrTp*|)x!vALK"$bRet6R)8&src06Zk$& >J"•gx^΃|*n,I9;w$I֮[GR*(}}}HD 'CU, ^/P:((&q_6RFxP'"xr$g _YL DweE(PBD}pEIxjj1XbwՂt^ݿ ya| v<Ȍ%yMJpF`mȣJi $VO<$#ivbeJi #X_ɉTPYk=:"*"$  '|rc0mCEX%ٲYogw1DKĕW^B8ݿ~3Ϝz c%˲x n{n#TxwBLnyî];dmV,UaDAg-B"lb ={B+& IxFeyȥ Bq`@jEtliJ6oҋ/We NFň(q\=2*TO3Y8DكX-Eb-Ý_nƧfZ)ڳ ֌V+3JE1|=زGQ/MRC[#$1aYR-U]cp`89p`?gժ6!_8+u\#(rHQp5<(Zs,e] J(lczzv1 G8*X;dufyN gtn0֚R4| O;^B*͚5k:š"vs`>aWe=Sn1.Wkm\EZQaΝZu3ۘ\%KK}}E'P|>pu-='>?ѳQJ^emt.{#z g3J??9X[A_;Rl6t:TI*&Mm0)r )dؿvʕ+T!L+΅0@v.vH1!:Vԍ=YB+5C;렣~v$'oH?4ʈ#Ů_?|6O_'ȱXs1BJI9G__ϣ9Ea"RmE{9a=V1(fX'omЍ*.k,:NN37|4_arb4@\efz3DZJ }Cqe4T uʒmBi_s_t8ɿAKAcffo7]@yT_;zqW_}5K\^#V¯CPT͡[˳XύmohEnhZDZRU)D|ѐ*{MYg4M֓ez%cN9p'Hsus4-@ts;'Oow?"h](Z,$m\-7o3N&D}.>U﫽_мrq]twqLLLE/5{?ye]a}۳Z:t-誢Po/l觑RacaD8Ix'-yq\4OлzY@O.9:d7}O~oyTkU xG]'7-.,޽zFFp6VN{-fgg_ja9\g]w(SSSe!,mhIs%dm&'ƨקZYoe7>'.ϻ `blYcQ19[8>:pt~jᑰyȕ ϳj*6l؀3nQfyAN9L ?B<-`t2jz|8E77 ̳=4.&M4>[M[nsNś6:~_)'9BJ*?r9A*k(ʘA9Ww_] DBr'Ly]a㨖s>E@+_iۈ4IR :&>oq٧[eI98?Ň>!v޽,rAGO9y{Ȳ~J!c=yB<*!hqߓ&*U]WghPp(zF7񕾺մUB0=GA33fki6[DQ/?׿e/?ͿR\NOcOJS&''_:@|ScF5lYpHTA2k[kyOJs.8U|zu8ٺ)fk:i,h W 6*I%*گ:wu1,>я266oo1==R t EO*g t+Ɩ ]c uh O/)&s [ 7Q*bq_fϾiʕ4gq* 8K};dW|fIc3338z%),%;EDW@: $1s[<,! A0hG./HS?"#36k+ o~1+/on9G'> R׃Gg0rlv1wB‚Y8;t (y~Hl{_;t\N֚fxo.X1_6vqymXֹ R*5FE 1$VbtC-}XDJͶ=1v9#fy;7 xӢZJpud֭G~3NgVLM}^pˬ~Pc[Rx, >ۗyq~XLqecphɊR{x-[{N8,8G^R᳟,_|1ZJ9K)~[$!48# 'BJɖ큈E%`xo 4Cm۟GfI!uFjʑMy܏޲ {qO}TUj9w ::0Q݁ }a:+_ )y{=wcv"SI? 8)b-|K_bƍ~XkIӴ<,M}-(ExnE7 Dm6w^>SLBz@>5l۾]v#RbV+)RxwT {9>333//UKl)?E\?3oChThf^|WHʽa8L YmRZ4u6~o%'Hti߿L9N Dk-Ri,fpd/' P+ГSnOGkM__+,Hy3v(AtG1y5dY4cLN\&I2S.Bt^35iW  s: PT%J,>v;' SS5~* u$IJ:K_I3wx{='HDy\q+1IS , (Ʋ޳<~7$MeyO;:ȱ^ܱ}fv&'M,'Rqݻyo߾󞰷C:rXܱ'qdshsg|/~lyY,;Hw'-,-'{ޯdӦ~l۶ c̲Z {GAw {k~ {,ŝwޱ'N;aXۓyq'읰wWʇ=a:kÞwOjrw {!d9,%d,d9-לN{9{=Y;acmO.ŝwޱw~ {yrߨXIENDB`OSCAR-code-v1.3.1/oscar/icons/daily.png000066400000000000000000002267121417327530600175350ustar00rootroot00000000000000PNG  IHDR?1 pHYsodzTXtAuthorM,VO,++G>tEXtRaw profile type exif exif 214 45786966000049492a000800000003003b0102000c000000320000006987040001000000 720000009d9c03001a0000003e000000000000004d61726b205761746b696e734d000000 61000000720000006b000000200000005700000061000000740000006b00000069000000 6e000000730000000000000004000390020013000000a80000000490020013000000bc00 000091920200020000003738000092920200020000003738000000000000323031383a30 343a31382030393a30333a303400323031383a30343a31382030393a30333a303400 ?"ZtEXtRaw profile type iptc iptc 26 50686f746f73686f7020332e30003842494d0404000000000000 i#tEXtRaw profile type xmp xmp 0 MN[ IDATx^}]䀉 "A0"EVzg}ڻYg9dWk{-K%1H$@@9 `rӱg$]z_zsL'"Ѱene-˲]>ǶL6Rho϶-[.Sݠ瑻.m7hjg ?z]qKw_ޮ{x<>?24F2m?%p.O.ʚW5gJ˝e_6[鵁fe%7)yޯVU(CTQr1ҏ;UVOy{;錛<.nOA|:+eF6ZFrd}lrH\|m>,e + PR ԾO|MYveYV,koo38_n :0 gg]ySC#mz?He}K6sɏ6'K/|>5997X[[o~gf&K/@"5; N>27lٲÇ=rSsO={*z~td>֛җʩ-ag45}:嚚2m bWn 4ohK͸7kq8baj=^>;=^Cz\^wsWn<+|Pw^!}l~@vo)Ͽ^QDMm{jjjHUUUp/rDzrf|õ;s~hgZZZܶkڡ+CgO^D}o\.߾}!LoXf5񰷷?ò677 ۦ93 ._mjky/]G+k^{1|̖uz$Ͼy 6cVeJ WRBG״5_}ڽ6r!SW^ɗ#jY:-1f\!"/AÑ4.fQw%ClJ4}_[y|V/R77Q*A+E?=y^].eahTMܝd`^#kniy[-$#IO`Iu5ϻ#pSC }= 6npm;. uk>o:Kg\'Nojnkbc=Uk6[^_ oo #i#wtkC.מ͈kySY G/PhdK_<3I^OUE6[Mlh,z:~ %e_Z_w9kUXe#ZQ~ zЪF ~*B'9K2LNӹwf===Bl=3#BE,ePe21yJ(BT6{MwVvn? ж3-YFȶP/ C5-}XW\i.#9E+F@Gg'E%RL }ƴD.+s\&"^ׅ\/tr>^syws|6M4Ѐ}*sFq> `mӀ)s؝ݟ@DT nIջg?]NΌ9}b;mDtYillDdB UoGNP-;f׋@@ F^>QņEpr@GJ]E)`׽^/jH1`/)D^f/_){h Pk$ibC{x`l@='p8AV';a㖪XJeRfЧ&:FK^ "%[?s9*VE(ř2EP\>I-1֣GjT뵼]ޱ+\L':Da Od@ٱj jD0.OcV==|ߵv#o)Ww}2}-ZYX!%+CW\K<(]-Tج4Q ʆйhퟝ~ea2 !ȥQ|WnKx(aRpBE\^_}I 9 yL4:w Zˆ?7\3]SU6\5dڿ'^76ߦM.o"`~3:y\<^30WZD'ռvuO fY>!Džu%K .W3#x]@V۷;s|ĢAT SIxS΅3g]FeL=Y@R#l)!WiW3=HtC *Mw7bnэ8ŵ[W_ѣ_sy-ƦD0hm"/f<e\4")74|l;J+ OCO^o<z}JexWXȧ T29/5k'Lyp2սCA\e&ހ 5wMJ%`p*+\c=Act S (xсTA.U:  P-u?eoOWK׵shxĉ#˗wz(=F|ժj Fm- ~ѯ\ DΕ*@y>u8ݳҵ!p?j/5gom\ڔ]]??7ae$,ClJƁ/m~5F[9rnhh;]we3EC.ʐ%*#`"S5F #P >]my|ctg,Ep ͹&2+mhD78fT& 6fҮkj8čT*, 93 ̺=-^tF"ۯ24Nb2rn33/֟}Toѫp'ɶ:_}c޻Q6*p=Aǎ _TYpl,iAGJoR \m6qo2̒Oƅ뙗uoTx+.E鬋*[v?D(Tc1xˮLmWFY-ȿTgh݉żAL'Vg/mO ںY3gN%ܯ_N'sxҶ7>8`{|ľ ~eu\ao) y۪IZ^?ju6ku]=WN>險&!U prz.]gZEL*, K@\qK%¤}M)Dg,2[ermElX(.İaMgWHoaɝة=ɔq!T^;7&%$ph&$-@l la{5FY*HĢAFPl̻| ,jyw xVYoɠK@x1\xE˱lS-Ov#dٮu{ag4_I/;1B~ݞzoVy wsȰ}(8%!wkIC 7-E2Qeqb{:9zk*~⩧ 1aBbͼcmj@̘!ߪy խs\2 <2*-?x ٶI6iA^vKqQ=U\B,N1+qRn(n S<:`<B ˾@(Dߋum?15\X,*d8:%2s@O{o֛䉼ŽL8@A4>b!HFdT/3׉AF ޴$E/es3p[W^-ie!þ@mtN$h& ҩ7:;+L^=TW_gX;nEqdG&X_`!/>stC  /z=T 7l޺#A@@UQb4^]5ِOyn(4k7M2 #+{zC D[vcxL]-d4_j?FB]uy#@ĉN."@"SPIr%tUWŠ!뒝MX bV tPWs Ulj=1Aǃ%yxH% &B%&yXR4E:4[v$[Z0 ̫kJ85 bm|.A{w( H|K4647n7i'[""8677қ2$*;꺻t8U/ni5r5e lL4ߢ*ou5]9=̚1O}hF#,v%fWw6 5]UB:@5C{+ɖ-:h~p%KqyxOWz" Qtްq+=5֭[wD,V%*n6 Õ 9QwTt@R@wPY3fӓk6Kx7v ~L\6Rd!wF% ;΄iJ V=W ?wWU(,OEBS7; Ez?/lY*/QeNMN]vl|NJU4, )?fY9[sةQ,<ضEbAD\8~.[peMQJ/y(RkQJKHS!PX G]"Ԫ",QF,% ĄOt^"<~- LG0ɓ FDޡh$&(jnZϢ >PW9֚`/AS+ze%'^XVa2YR&jS ;Ga+Bbe!%ױÑrf8:ߥ&)*`ѷ>z(QjqT-d#1]]_x1k 6p #/vZC>H}].Nu,ǻDkMA=xO]KiV`'BJuCRte8P ʋţ8@<_ PrvU6)D%/wj/dZXn)5SN\PHazt»*./!Hĕ[A3$B'&F67qW\008h+O^Q('^侘75CqU IDAT%%lxSjbw`kKۊˋ$_7+ܢW?~xxQ8[P9v쭶cήްv~JfT"j@P`NL _DݻwKt5Xd5u绻w .Z kll 8Q7o6w8":+Do=1eUyME p<*z0СvT|`m<)t]LIzzfR,o{["RMJϼ0vqp7- +: 7$iHP +//;n_ ԻPЈu㸪*Vw74^geq-O .#R,zΣzU K,뾜M^XE)qךp-*869kM(jD҈r bnE. ,'dr d=\{wJ&[q M Ĭk@iSsT< A #P>_>G~WtĨJ$;Nj~%@ zn*${MTrE-z;RʢرbATkYdDM bKbu6Ka9m&So_ִr8FT͗5 Z@1A 0UL &~qBuiIt@Ҥ'Gxlۯ V_!_SPv0 LaȉHcÛ<oM5TVE)6t*YT.7MpЎUM+F$yn5 瑣a 8%(pi8)bE297á4!iVjDŽ.Ѣ:j|Y6M$,a __%#eE)H" s֭(' aT>W>MC!DxJ,~&Gb]Xx p5Dez,8 1NY@ jՌE" 2`ugAptjI,Lǃ3sL_=H<*,ClġGVćS.df,YH,Hv4Qd’ x@A{v@-&km]`A8ޜy<Z3 PPD x}.Ip GhEH>4{M XTgPɾ|r{{ w陽1Y#i  A=0,/eYeMcJZZ[Y%bBh91S%3e^ 944HWYc9=04c"|Ic WG%SY+@绠:FQ,Cc:LyQǡ*4wԙvnbWmXsƚZf∅+r۴espU򱶶zg5Nổ#vm4鉥 <18|ز33!AB).Q .TҋMI@4~}\)Ίp٠?ڶL,rʞ" (92%NȐRsz,w6PUS"dҭƔ\_ѱReP.!ݖ,]}0^iU,^T}?JXY QҊ +M{zWL*"̹ BĿ}X]Mu~Z2e Nbo€3ɤ t+Tu[uRZ &Y;`zӹu˶ͼ(uFM:=Yl_Z)XڛniՍdkk[K 6R Z+on ~HH*A hjnjD^`U[c.o`ݺ5C˖-fWM(U**&9~hرc(Ȫ,D/+ow\5) @Ԅ=0)b 4 عiGQF@P+(6wlCe<:5J#"7Hgkjj 5 YZ(hUӁ@f!H){V_YWX.%<ߐ31t򱊓jzom D楖xI+nݱcC^o5@e\R4ߋ\d&kQx\:L߅ P N4;R QyoAƥqWU@NWQ"g"̺tVDILoRͥ_?* 3dlm4Zs.",g:u|pyY3an|EJ/8;o%Ǝ9ֲ1Q޶y&\~l8B=ݲe3SS|h{%QGݺuիW7nH ǹs,[nAG4zW[ܳgv PEܪnMԇRGtB8uġ'O>{Ky~/;o~>sgϞwuN]RSt3 ospc!ij_Ö]!MOd8ge][I]]e "TDSO<*k;(N!9st$-8R{׾Dr6 "~LOOh|Ӊi0NjUohC@KksщeTLkA݅ 7::^E::Ʊccc&YP$Bm)~@bŊǏ/_A$d+ :7ުş~s6d>I76MO:Cn,*gCQ6=]QSS?2<GG'fB֯݀VWAoL^Hx_f=&ԄՅS 'tJ؂VxG9APҸ$ @[ Wu!@DAzcJhQd8+ox =>GWtBzW^ dU@?F*q !06(ayEҖD sęB R,\ґ>[J7$eM6P#"E/O4-_DEf)-Q^Rȴ[d <Y(RG}Z{a")\{:ɠ ex"-po™FF Jp,c /môqzɱi,xD1qTt(NJ&:K8+TPOBh,>MMDž/رSCCI%]˻b~N}B~饗>?S|0/pw_}M9ŌL:Onڲe @҉f̒`on۶?1j{k6[:@Ø\FXlVGa}Mÿ?Ԁ?Hx)@@(JUKr:BMYqmW$秤|zjzpfTRWdaWvtXuDc~ #ԡp5\"%CuDG P ["uƒ=D|baAQ(e--* ai 0mhR#o͸NFTCUx ^u,U^=ݻog6%C?  DD F Y"J t7$7ļHm1lzA#a< 4[~=&,$9pc#Z"+m@6FFZ ,jZ9c_]v6x|y=w+ʟ\XygmMOg'&&6<@;ᥴa`+'-u|C@X o(.u"BjEy'J|RC]=Уe W@ Uo%~Z,&=EG:^ NfI5}i)ONMe3^D)U_7R1;5=JX-Ϗrjr'T[3*9_̭V^ "C;Ah@#!:PMJ fn~X|D%/ JΉ+Ͻl/nmEЗCe4sP@liӧ/_4kMOOI8{>Lb+8v"BT6>s;ڈ q`8ֲryqrf+k֮GtiͬlܹrqvF@hFD Q@Qr>Yf}o_μMKKoh#L2 Sng\ʶ^:BMPN'=:2p26sDwu]!_6¥9ʄ+hYfA0.ڷ Heãr"MDAgj L~鍰G ĵP+.. '@7@~s0|~Ӡ{>7l^R[!b=q7d E{4ŞV)W)*Q xj!D8ED Rĭv=Hh4i|<[yAp^p tF[j"2#契y-\ Wfph7;*Hi'Ҍ%NK/T˳z#^ tIE!@:G(Uc*;զ249!^{sYLK5z$~W~K_u-֛Q_-I|О?~ Cל(G'{a3reN/!o>ߓ1xD"EFm-M+{ї~7o L *@bb‚7MGCJ'o09N8zXGN,[uMstjhX.sK۔>Ôsb=Vl+s- w^&Qa& ʰ~ u$yZ5$sW. IDAT[)q}>7bLrxpˈC4A''c'dX.|'>lo[c?ɩ!ҩqIg3lyid/ fLeơ&+{_ܢvDd X#AEL4DHG2-jHJἂ%Wl1FW^|WxLovZ} X# (XcF#ok^X6b7JԩSxmyY=;?W:|6Q90 ]W{޶J'P(Y]gX']"zN'S!JT|@O߼;_Koj"+Y'Q= D_oSrvrA RRz"{m͐_.TG0]{\wCH:Q'h]烸9sZ]B fZ=tQ긮ʅ-)nhɷtsf-!mn7ߠ{|\Aя~pW'0: ̲i700}.xSEQɇB@DQ` {欙RwZ]YiIAj3eIe?cZn䂌S@**2S hK909#yXrJbk-JA^tSÒiw.; CxRgG/ ONihtbbIk7WyzEmjK$n{eͻL E*t:91Ab;vFtCRB?cl"/|:$eӦM#YL l.Z1o.69"Hvnbrz 5V&!U.B,տ{ATƐ5‷g`Z|@xhϴ ٔ)Q" Qi8[{?^LF׌HT 1 ϝ=[[_};7bQx9WEjD6`k+pBv׮][I! 8+3A5h1 dF"xáorU5A-Bu0 ُKl\ݿwsV\(3R˵]~# ЀO=s>|k_㽸ɀxpf0"hW_|EpHiR#B Rx?v|frZPsH, X&[TE-'hffvhtJ8MW9;u =r6xi]L`?7M:??…W:6XͮVIRt^AF(f}SXt^Xݘoz{ޅ x~IJV,6;+@T /-]zZ*NA畳Ld] , z9N'Y9wmX[^ {PpMnz衇/ːk"_o- ;Ysy>O!)_*q(,lљg\Jͯ:27PF=ݢp~c;~lO,k! >Oș􆫑N<è`Gu5m@?4`A68]vJ >Ӏ X*hzLw}S歌("Q3ltbHZ)Æu^=A2koqfUWWt]|y۲`(~plˇXX}ȲC-zy~' r>ߊ}T ^ebS-D"yZ*!XOXoV) *%"()9,}S-Kb_?g6׉Lqىt@,uTcǎ@ M@xh"a[.ߺclb[~䍗{/?7ko׺ 7=? 뗟bc X  ϔ@e[-%t}i`??_%X'?Ц[5T )k dhyM-yvǎ; 7EOKH^y?B,7 hٟi9 ̅:xI3ѥc.*"2xɧla^` 2o} B⽐|" B@])D=L"=z?3w'^<ys2015r-_ x?GW1#1?hK[a!W9R{J!h*XxiFX!JqP0ƺTAAr8UU-"gg5![M O-ca0)4+5*mL۲aG Z*`;1H(/z #=w.\›>+D"0=p;.% nWkeac=eEڤ$j1JEi3fboHe38Alq$gQf3)-IJڼt9ΌWWtK46Y2$ >؂ڶ t(@ >g`fzj[.hP4A(tYǏCFhLȰ~ `y+\d/PEFY[QۈF p /t 5+-jBQ/mD,IpF@^ @c'o 6'G'G5U1NbLrfbJ޽Wa-jM2XńB!BN )K_|MQRı&\ RcQQ|GL:z!fǸH׿1=s=uo5sB;ҳ+-+'BLO1[o+)_0|^ 9!eIdxÇ ʥK@M49n'R~Ʊ7+c Mg"Y\K/UI%xJi==9}ʾvs"f{g}DkkŔUeoT5G^m=k_¼ #3x}aDߕ[[_[ǩEV4T:3z1ֱc4kjl/OO'fpɡX|`=NRS9t=,tS_'dL `d th DѩDj69 G86gKa%+^vx(a흜hPT]Cp )΃#(5ZVM \sptfwggkPU|63 x:ׯY:=5d dMױH%K#q޼Dp*r4pI/Yµ+n-Qݠ(y@?0BA>#HȠP&x ٩Onú{̩+V^AZnI i>c l/@W$-L\Yii&R}^8Ή-G(b?!10YZR $mz/Οp"3R/MSb8-"MfCKdeq+]l4J9' tO {,: r;v?8vC U~3A DjruOqЉ:$Wҧdݸ>۟Wn9˩%+/yu#Vql-jYYB *.j-ld0/"ѐ[7e Y'>;+b6l@1SwA4$be0ďp6+|]rcA֬Y5eŮ_xj˦ԭSx|qJ|1\YʪsTJUW` q*C'7`r`.)lFZ4^ !SZ+E`|+DH//#k2GG|Bׇ>3޺'?( |?|yGGkTO2i98CŸ~噺A`(7O$֬^NB_UL9$A-D"6W0 nfpgB4G? ?l{si1'`i"RȍXfy͋ .,/A- V0lVUuB~#{el=Ñxڍw"ϯ] ^{"&x?(`i&{/Tw~ÂćoW? jL$j5/Gy'{9YwN2ޠ~ z{:㱒5r. /ءuW1{Cgb[iW{H݄VU[ jD"[s_|eyaU@kO??tmo5 ڌ$Ɛt,WW'FmE.b.t7׆W$D*f9ys>TG@%ཌgǞ~M('5ַ} Z6F"HMT?P&Q 5l,A'Kx,/5.,Ƴ>3վnGs~ݭ{+xܕ{.G]MW>tez5{f'+|0n[_?76FF&gk|/6ru67:kB((%XtƲop&dҀYXNc-c$tL."h臂BT::@1>4>OտVi {s;osD.E;>穡Ø0^-(\ta7 xjONgp,O<~R^Bhy&N_qEj'u\½q.aX" XDB({{@ȣ睏$/aL ,.b-32-UWݳ& _9:84~޹>+m3=e `(rVMCώf.d\ݑ}cU1o'<8Th1mvVSFniB~?E /Dm] yd} ZL֋WKM-17|:QNE)ގ& ɰE<ԏ:(o)6EKc;2,RdKNbt=h#{YKӐ+aK/i$ܺuXH8TO!aں; >ץB͢$g\Ý_*_H97E5{}WnZc sNͱ=9!ۅ# :WG2JRr_kK8յ7Ѹ<]g ^ [w~wsBY:<`g H>tj+.b१#K_`\9W;A򔤇W*CD# ;%TZOd9=)\յ59 ^O]PfaCUBp"J IgJ*pM"rC]1_:?}v-୭~ۗÜ3. D"~cz C #˺!٩|ۿGu6Dq Y UQ(]>!|h!z̢RW42jt.a: (Ol"|Z(@4ZN6l}r[be }K;ۭ;ntHs*$vz3VnնJZWueLFÊ yRsY 7qxAҶZ-L%C85ɩucwuEBI>0r91OE2|(d&٫|~$n}5qh22mKČ|RŹ޼hO'Ϛѵަ8.Pq~BO]@ e@>aE\J$G.vnimWK9*D&ľ}9.px okrI,H`U A pM8j.6r1<"E݋떬UHx-K`휇"CB3 {ܡXHl.k9[$ IDATOTO\l*od$^Jصoĺ|2?][˹BUU;>ǦW٫֪ƈۚ ؙlk`ߎXm39WWҦ'1?U!=?6٭M*Bn 8i JiZU3*Ux AE**%N-T*Z7UbyKB*(X;` Bco^ 43r7Ġ'($dMo: cZBq\*IC),G)PަkC9/DGK6)S7qNj"{lwmE"xՍcʤsѠ9jnæpmFJ #aN|:XkgXP>9D7ʈ8yQ2joj W"-̬~hR$MSLeu`CC3k.&3k KEe$ +P&9n̊h p@wSnnt.%UZ6,´u̚NXT8=DHPz[W% 'ؾ߶SxH^BUI ),,Pcp `nmIVo2{s;4Qoy;w<9O=sFO|ET8'q iscM6Rm6 Q6X'WүgîHpê5Eٺm_M5P_x55~c=qŦMSGohiچYD q[r[ksW_8pxw]˱/Υ5,{=K [CG/밙Kiym89G=V=dIr|A):fQ.!E:2/ه5C|fKgvhVOkRRNIU>= tR^D'L{Ngma1pcPܯ1*2,iw#{H$> ]T{LdP.zSEqZ:C1קv߸GK7P+ܵk_"9NJ|γؒv SLK '_ P"_e0T }@xq gUCXq1N~a@mDGv.7#~ɤZC>qtϝnUɱ$CN iP1>KωvYhbW8,C8G %F{dOi9mZbܐV~5P?}hq(Ko OL&zFƦ:;;;_rƯK2BbY$3ݐ1ByњGNM 9"D&$+JW'"2dZ㓮 ve cxaFЎ^C1w smis9TYI^/T(q7N?щթD&WZ59*Rx]zDviq&H`&DZ +7 ʆ A* J ШeB%`wcDj'4iR2FJ=M0DhHzH6? lQب` ]"n Ñt_iȐ߳7L#O+Ѿ`0+ARZAT\X~3xZ@{V_ȁ 1SѱiRm"pGG' ܗ|jGaP>:phMD^DreXEɐ Xrj3f¤|*k3F0<8ɓbmօA7# AMndiYj1K;4%u v 2vbȽ Lt1o'q]EBsbpn@"q1f6HdTC C&& ;g` !1d%a0"yeiD, غ5msEã86/&Jp_M涺U+ϗ?.]~. Vڷ/t`Bu{]9ueJ$GQTO~,3tUD8sDdZ@GB2t R^0 ÈxWQ (Il24ErT'"lQk2z^ b^&pн11nf'o)oPs^ 9X Xk.^;zML$+;}g{s ǟK]Nw&OW]uuDz%{wmIML6^w{GxsnҡFGa[r|QP*f+ G~E:ۀWx-?E;"2-C1%heA7*fI\a$1LS O:"4-bzHʇ 1 ?R@EepzHFY)yQ`oBs\Qt.;[KkkkI$NfHD$HēRewON BB"LNF#3 Tеt6;G'g2.ldoVF1| 3ZYү/"!l`,DD BgTu< #pPUbD.uT(/=v'd|!OhZ{NDy$[+ e %Sy-@ }O~8Oagj `1̑ЮD.֏/B۱MCA<dcJiu ,8U Ŀ t $χ>!)CXWZG0&':ubHjW|luYj+I|A:bҸ=0+,E X &^QU5OzƂ(\. S>$ g#.رp$p]TJ٩X"kۋ&0BWwD:YIgtHQDl4w^S5ra ;*GF!! *FsyNeXEt J9[;‰쪥-D͑oriQ6S*ԊY&O>cwO]{SGcC1ܼykpxsK浾(l9zWKVZxt<M\~5*/ⲙoˣ#=?&ҡK.K/TcCI2=͒;`uZ^Oa23$76^i#U3OP cзah cw3HDF xJ""!>h("Xxśۇd4 ِGj]wjlokkɟ Ng|UؗJ[ۮxl/pbt<5րw.WGK,%W-X4L.ώ^~p`R$W_w.Wag%MPPej2/N˗dHS.oƻcMWlypg$s\Ht,gPz5HDB?w t(0 #`iPDe ڝ5Nd96{sU9"Qbf#t}C<ΓY`Щ !')D4SM12s zTB:P4kr]q5[t뻎ljP|:Ǯ>"#5Q8ς-18/t Io:!&D"SM ]#^yN54DEm56a Ya5_{} O.&:6V7kx{BT_>tPT"6cIs*zM'ZL2I{w.7庡[nsTSKDJCޣC흪lX%K>95?0R[o]bL,~2_38VA#z[2hNJ቙x]$'' UDrj@aK3K/vv{kmj y /[WmHA k Mq'&9)[_P1 4 ݵ#86($QBYO_(bQ*/ LGXHMI NnmSo@0kr)Ű4QQԙ1zEv\~`TttWwo,62c;wqF3NSnj>{(M,qR1>8KGk[_z5v l_MLLN^bu+{W?Θ?0=!up0SSK}k]wUbLXve)_J. Əv8(5[wkiMC<h`Ĵ|ڕSD_.Bj2ޒµ: 'qrxQcQU-Tw V6 e99ӦUwM*bX 5zv,lɚYШlhi:*F.kzl:2'e 4 DwI~O tc矣/8=Φh6fC P]@SHT/ݰD&ڼH@jD5lrzdaA>+DVhklȤỎC6\2RHjknnS^8 U,{ϱY,)bN/pN;F$תLOZ*!. H"JEB00ME #UG VPwUE;_f\z%2o [Z=ro/2ZP롉V7 rT|f"mty,7zmAΩ|tt8 `yE w{ |xH3g99=!^ȱ$N8J]:+t^x豛*^J4E[ů1!ҕݺjpāXmgȱpbא. K=o$<[p*^ '<@\*T;I<'7T#MWZ=~]Vtzxִ!ֆ IDAT-F.`GŕPQiu[1"sL:ڳ;U"ܳHdfDp-W>Q F ^F=3y+9:v?Lzli'%./yٯO f93tndt)HXpՍWy1p-{jf[eiw"1>:纫/Z*PuVbkf@yfPzpy:y|A,ur[ pŃ6iz(5^{wz8lY}Qr K],orHy>;Z' ;]e-P60JBxP{)Jdaex,k$Uwٳc7?lڝ<-Zs=s:k9ոvT ʘ*.SgB;sQI?4آk'fI 0lVGowYVq/i=#=Mm-Ě{^? ֺjCil)iBX, 1h&d3cS3f݇'kbAiP*!+ʛRBO`[K*GZ 1_+rINWT:3;h;3K"mcɃwqf}m$N5咓bPL6.|4u{{H\BAȉwhpmEᵱ𿚷LZhG ύ]{m݁;9R֡ A(RQjBX'k5d(äE$J&W@}?7"5Bn]{K~$ I^3lLXbJJOV( pQLO +Yl6P3y*zE#hp|cD*9ۖAAYv58Y0>QgF8Խ'vw]5򖵶|* xgT((dvUm5MDa8S˥:s⶧ekJ I _g`llm-U&G%~X@FRݡco~Ӄb:2{ꩱ|õP".S_(u9BFtf:a$^ E֢ktҎ#$)͎d,9hl;{{属>Φ' ZK]mky "i}fAH|J}81del9m([l*zƾ/:\[>06 GbSmK֮^W BUk(3q'O8L>- MVK8=m.LەbZLLV2{n}~#m]w6*CisT,Q9kiE1+9GcKƵIwtͣO<ܴdt ۚn!+T51iA!2yՍTs9N=T{cv_{^9vmZW[(cUer*[0gsF%K .F T+~#3HThmK^w];"7ڹ˱kg]"ϧ.QKeQʫY|òx-#`= OtV M%ޭ`vʖ7V[+pc{:x٤i%k01մyt{^?J-q{|KW$9,Y1<8wѣw>zCGz=[hl9<A1tYh4L߸G׊XkvR!#wM7-pq''lצyԤ\k&JUFPXi^Kb8fEIr26U_F&qğf5Bv"_fP;9 kӦ+ͬh ^af#+u*ǛpFYx@kTaUI = \:SE67{&z01x$|qkMƜ s }LMYꅸ]4AWٴ5l37͵pr}%T;CύU᫣>NPSwMlBG[/$[g,3xUAqnk'DQ_Lm޹?O~?OIMa".yb>w19p vLJ8 R4#lH:qXva;:DTo'q$*RUD`0YQg?yt X3e@UuF깪rj)ĊAS$jeC%BUp$C6 7hBHZi[F#2egk%䌭ؾ͚vLlt Ԯ {$5Ċ3hMOMr|pB$t7kRVl._a?l) [c+K6}o(Qx{FueSPTdzꩧHs?N P`!$n-]./xp)ZUd+E( Q?ojz`'P!qX1}l摇8,SW_jhwRyGKY zTk!s4 S`rtm95T)–?T^!YwIrw0f|[}~r#w[XImTJz*0JUK^bGh]`HܠN(7*{94[3*Om沓g.H Spl?K3:HPn~Pŝ/Xrek{tDu=k݆+>iG}}Cl&|n2w0> #D:,D@s5W__o@Ҋ:U`U=QIn 3?|ϟltZ?ȣ3ڎk:tؖޚ7>fw|TSgz#3@:ر_@'.mܥHؕN}n wrPyFjPL|N&E[\:` .iƒ.LqKI/%S|e|z~64m|k{i?a7a!єNnKiԀ/hJSU'?сwg97VRz_C p\~ݚ00#? r{w[ycCcVj'\ z s™2dܤ%k/87o) '?GZ:NyKw_& cоR&n=8V|dEz:nUv봅#J- 9h2<-O?o \GAԧ>>J?AɯG`fkMMh/wuD,&/TF zP͗wKO] y):rt. [P/m&{n{[{^ñT:fh'>Th!5#+ywgJk;Ӈ{u g'wЪoL8彀plB̊QǖN^%A)`g~ HJ!;ˡfNTX<ٶ̎ܪuŁk8o_x]wݿn_$ɡ>70Q,zeԶWcM'B<d.rxɧ?53=:,1'잱c<:kؑe䯷քe$L>xk e;pV Q߼9_IVp}i)GV-;r{s'౞-ZFPJRr"ټ4ց\x[r' BFǢs M@bFŽ >B*.g[eU^7/ټYt1PEW {|2B`dԣDǚsbһ덚e+c]]⑫yU{BQ2mm+/oZkB6myoƗo 1_ڕ|'7OL8F|w fBٲsM^~qI}m >}x6@ cOn|-jM]LkS!h=05շmύnTrs긎-Txm\g}m[ܣ\u@*#&w2 ,Ty L1 6:PSNPwztox H PP9 ޔ q]L\JjMCU٩]Vq@uRs9', 'Hu΄|ɏ_?֡A$Dc5udTg(~zU@BMocH?}1>y 6vKٜ S3xﯛVv׾N.w E۾~pe5`Ry\a|J  Y$EKʎCJ6>{b-+l\CO>w1 ~"_Z9?IsSC\L>ciڷk'z'MAT9#t sy) &BNҤ9RI3L&2\\X:w.D{]W=LiM4ҳ}ލW-;u%WP1eNaUyYp/xkx{}W~%iyjeffuuG{5Κm(+Yg6R0u~fo]F׹?#  TOGER21g.syhn}s[ډs^@U1} `*SZI[s_<[E b_¦3* >"]EϷ%_za a)}MgsӹW-Mx_nbc.": ?Wts1(L%=EO_;͙d`ǤtĿ7ްC]pA26?kqt"/~ap:F! pir&SRG 3`9쑽W^J4/-޺Tͩ/yΝA[~׿ uՏM !OHy:Lt'Hxh,tB6iMy T#CcHžJ} dSMŃO-e&G2Hl<|z[sd[KUڑ,5zB/t{}#pN\Ix$r WWT" UxaZZ9+ s㪫6uuc) n"L]v&W{tjE{<ϊNWOkitr/[BSpd*Q; {x0pUv}>L*1;Wvi?|tϱHԏs+Xz^]hR-)|RF̩ F fv;GMVEʝ[E 55ʥY۱c[Y2$c>p0Cxl4\¶o9rpt@|۬+WXqۓ64k5ycl~ϖ-l/d--,L݂E9SJ`Y}b%J+l%ǡ|f)~hnTk89KU*FƹS=x91XqLST'CUv ֩`}c i֌uWrgS^|5W#ʑV@# pu5p'3\8/[ Dk$pe;P!yB!fkx!T ,QxvdZދ\$:cGJbƦD+^Gޮ5Z|;vw>[֝w͏\BVJzIHKև׮!liT̏0Pe뛕33][+mL%5Ա\B*tSOC&/E] ic~{IwߵҫgHNd>P O4jŦӷJNOrF+g^3`kwNރ3v⍳(t s_$z-.^̫+OJ$Wu#K_MNkF%k\?;K! # IDAT{BK BC"P%*LAWo;S1XPu*p&L15m avwCqa.m}J.YhmO*&3 FB:QDӍh ^כTTi!կ1d;lm݇y:V' xcĵkg&.RUa(%gXIU6 rqoVl*sL .i>"AO1j9K 5sBrO|4IӳNH+9B8H H*`>NW^ əf*=7RtfЖš/ yXϪ/} I[MJ|:Ԟ,j‰/ѳ@+s|6g>mͦpP8lqDB99e\ɵv{RN;Ӎ}A8\ E(AyOyjY4Upq`GaKa`!`G{+8H 5 U2kZZuTTB@=~ 0)WZ sBfhQ'Lg t-g6;VXDɾaώ9֘JW!b*|D՝HR7_ҜSvH:JUKGJp)2N@ x{Gd5q6Xp]@'h6՗6_]_<傩 g;L aQ eX-g{KfQJdl$FşƳ^.9L; ]XQR1{!BFK\7yt)!a9f% FewIv!PiZ'K?cS|MK'e,>3^'ee]Ȍra䙳'6=fj{k #\!pJl m{ASVL<)uM Oٵ`B}CCᰯeERګ Yrú(Ċb8;~ o)[-~1tS-fO^aBZnM^7T*o^rlUզ{C2 r|c+ l'fcHږ2v 6Io$1ddBo\LTlc HQGjVL,qti9\|6zeVMS~hl*umM. ,|LqZAbdE-MK`|YcəE8.DBCN@mEZxۀ/!X8(4$9jY5!B"Np"J"nyИFAB "~ޫ􇞋#d7D X%14)4cfxF䨔*l\pwz@TcdS'T-/HkLG$̞mv0dS"#&1>S{?j^(sNvB}W^o=񜻙D5@|)u]m7kg?s)L޺/e eYiVKꘋ >I*Ux6J d0lB)&TyNQK|=~wr 墉Zg^x#}R1P03Y~@o~~Ҿ19%8iB tRʌlE}f==a2e |>%}|!-DdY+FwTt*?Fj'(;$}}A u^tF XŗSowؾ|1bTԓ0/*N4 McQf   Zϗp}%2 pIߠ?T('Btu6 i #FG4d9P.fjvCYKtpnn鶰J 3G@4v2mӆ-UΥPy`(x|GeT2N{{{-[._̷4"Oc/Pm41S5a.mT̙{%TQ-*QZ0TnqY ]ʷG˷>u{W-QP\6j@(Sl5$:f>Ggrc~>rokoV+ aALy@ᤣ8EШI<υD(DV|68yhő"YnR{5o炕Swح.3$ea%MyGÙc6.(WKK/:>~u:c/Ԫ s];ywt:KA]bE_i6KEmz\ya+7@ HDg?1{ LWW.X.ej L3{ }FsPݾvZٹw76t4i6x!qT& AlDz'bnM>O==]-ڋoڱ.:sXSpmΆ\˦RM|>S _  kO\AΫb Ժe {Oos;:BEs,J#w^rjjc|tŦc\n8936>Z8ٷw0 d+';FR9Zk f~qC~Z֐L3l-޶m@k2q֏>عAgc#|12Q`zcp17A;v<ڭEH6zJﲜesYGGvKC6ִ9ɸT%o7gY1&'RU -~?읳ᤧwΥ5<ˍ7\SmCtvx&ӱ` UUgEcag'+'rl h; 4b!_)Cs8A0homc=Ckݺ8$Ƣ%i7/#"c]3fO⚪8 @[,pO&7\NZrt/nX'Gƀ҃"c=h$TPvm6I9.'f~ƫ/ߤK杯yYO0ZYq\T 6"AEJcȖ!'cc؅DOJDuM/)(qɹ'1'=1 , YBe.'-۷-Mb E/Ol2*xuM)rnG.MW g!h*Qȅ.Jtj7+b~s?y쑵6K%WŤzSA[K(XLω1 Txf4_UTP +0>a/Y/}=l6z~4Hv[H./T3SMvC- 嗝ryxzox+ v'67ٹt}ݡ+}z@ܶln0r~]+`IM,0n b޲s:Jy"2x >L|7Si"i09p*6q)UM \䜍MGouE).ۂ67[|Ųx:l㏛`VWÒ'~akNJuDȞP{M1;!~>屭-o"y|MC׏EoRN62&mCik^"H8β#gx"/>+$2DyWZ-v,h7%35,kS/k.9\6_M?}gGӹq9wݰ{.n /m=es\ڤXx? K]=.Լn|G~wzxܶʘA`tYv&JB"uD(.PK *>"g7SkbmNڟ?zͳ f#D c%vXdMZALU'Z#W~Eh+P̯ȣNV,!1#N+Eq 5 ħ!̻ `C٪44,F0SbTu(EzetMoߴ'/= N_D xL yfR(,gج齃@OU>B{Y&IO)g25vq џUtuYSE7qCLWjZvxysLqU= k43p& }~@{5Bgd_V^Y/*8]jHGwgwZ<rgu,]*۝:DIm誶K] l0RÔ4>U4w`#%_~#,Ay壿{[{q-Otjz"&6Ync.OpuªH q+bv:DpSR <=#;_Jkï7;?c!?ޮK7 C{k?=TvddgLMgy㛡rT,VS9khS[_DO UaGw;>;MlP*d8%OkU5$p\@n~{#l^ւ_}0,1>?SCc{cG #YÉCcg=%K3S_n{םz͘LdīHʿu!O}F*0n P@6u.kj# }{VaT(<ҩes~S4QPJ&jίBm.FBTƄ5ī`m"6\` 37ʘro]y[/nkw~,zo=!a/Dh;ouh͇B6[%?vm%PgQ_[/ȊagOk :O?!F!#Q#Iʋ_ Xʸ̈~IfK+믹Te~_S3B\N6L;$ 2)@!MK^Bnvjk5!ߣ}\w55ԇ{:OO 6NSYSP^C5/I梨UnV S y[ص:)f dq7?ܣL9-LP2N&qN46+](~&Yՙg0s76ER,Շ@EGx2[iIn XL}ؘ)7hb,Dʄ0|K!-Gn!NjǨ=L`Zu[;ŴnC)3m!p@ŵ-X:T-ZSf>y"c{%sTN_hɖ֝ݛGdd3˔3ŐPKAQ2t ymAk~t{ZH_ac+WO'u) 8*A95zqYQžϳݯ|OHixۂV ~㯾Q$m-.&'e@ThB+kCB~HǒW$9KAL5 239*n:e{`U;ǻ_b28 y{6^z e&!^+UY 2r҃lʔqV;):Q6MrνS ܌h25ԭNkIs&P6J''v_ow>ҽO=rj:'5ڲ5TɋW#(Ӣ!S̢+r /( մuW}&1.pD8 %%.{?l113ƜFbr"|K%48;2-fE /N޿F'AAg &>(_8^A'kג7ݜ]!U-b+?`Ӏޞ o3 n`P V_AD'Xn ԟl[SGCMS:GՀbC1Z~vwb.qܗb1ʇ*Cʁ b9}2yBzϱslu|]R6vJc5n[Z5 *p'ViXR \6:ܒhBqd4П7XIP6H̏^7eS(GA@%S*8{?G~ȿ ^'mpӁdW,="8 cBִ]l\` 4JbnlYĦc`++>봸TFkwFdžx3Cco0ͽ=3}oiWK >3w^oo,g.,R:OLQ/h{p7 SERH}/G$>C*ȳ|ֶtUgXNsOy7 $)*HH̸ASU\k"m*o?Ļla ْZLŻn_+mum b̙]Rn2"TULڄ*f{ַo)ekur f#Ifdk{,}mJ6bC`,X1m٦ę 5f&c;J`g(vpmNx絥o 5@C˝G.J92sMǶ@Sgz!:y|~*Je顟mou],#{e׳7⬯hB/<mn#oX!Sʬ(>%G_?O~vٿNIg _5@P,)q"=hg I^{HZ5mT:=voKI&H >ޜ#$(Z꣭1"& SX.T H{N9.a.N!hTX k%\SdQ6𬜷sr|<~J&/PYQggGWdGJ2f&" }WS5̔tHg"3.kȟn{ <}虢 O/%ԧE5q/'g3j&G&bz嫹tlME9L P%ț%fGRӺhDAY(ȴʻnLoyeʢ" Fz*Њ~H< ~O=6}2>2M&ƞ|ϾJtL p>`@óg,#$pYDO5 '0CێDX4@MMzo45Խ6"XOݒi$OtNrL^i^Iȉ ޴L)+JS QDc ~SN%^A(Xϒ1N{(ppkʧ2h>źǎ"=H,1=&'\#>-/`$M T`#PT;k;ݝ%RU&66Jb4'A " ZXW ѳ%dߍ#1]3>?@ /8@vk[>d% P2B=nڑS}rDln__P"bbJ襺zWd%5 _&( p"d\">VRQXc&Γ(NYW$Ap~xg⍏Θ+u5ZX{-@RB+ЀLq͹ &n?zx$vU}BQq~<%B@Ѧ`N&1[j<vz6"$!TT2 vIcbmyl66ycj ѤxZJ׆/~m /\/i4'@iA {8=Qh|/:Bf439QT[}IRU"hj~sKCktkA$0&E_b0__;7Ic$sjޢZGP 392N`X#R`۶w^TZ"悹h4+4gG=>5, dž6%9yF;hK@`X` =ɉ_(_ƙw.}~?:LMLEu)Mv[yJ&",f9~RWgSJɧGClqW. lt:ܾY'i{ oT{%Z֚:?? S6>8QBg S)ٓdʢN'毜L?~q PJ_tJ?У;uۯ*]2D4E(L99st”سuO5vpv5"? T9v*D>!`0j_,{lp8ŷ }3zPV^%$/?H|۩BtiSׅ:'gw4t83E/ [-V,j{/x&E8+̍{ney0^?HٯZ0( 6ͥ009aPƒ#o{u[玑X1:27쮯ESCzOK)3:p7]u*{'<з"*F_tF7zoO?j;m .OJQ&,iN$&їw7,c$R݃J?k5Ͳ? uQN1KQʈOݘϞ=O=ׂN?y7ϽL啦`}Shq$ّ7훚ܱY%Bﳨ[7gbW kS=6jj{fPJ6:5\u&+4vTxHSi -3i%oU):~ <ʓ;uY2Il 2- ̧nL׸kBD;wgL06蟞m\1wRޜ?q X|tdro~.z;%m\gXt/ *⺼H,]%cLDqrbK?55P1v?4)Ko,r ܮZ#;;v"c DI24H_@v|nlkOG!(SWx)Ejס|`a.Ji6LM_ڝ^GͭD!;xin?BdF\n\mTbvn8NLŧڃnK;4v4k/O\yf]<3 *H@"pJH(0B1sswtn͑+d^ym,'Ea:| 7>| [eHsP-yh;c Ӎ|,Q,`ڀ? j fw }$Ӗ< 4H kb|C W"j3'PIwFt Huvy}p=AҙR.n6)u#d* 6sh쀅hzĽ &=09/ x/ b6;:+ T.yF{x[>Tf> R^Z*A^|iJ\ /n9mF+G ʰ_HKFq)PRb^Z{$Yp`[Q?P`>_i@Y k]V]4rgc cjsc=i=rrbV+CfW]__JRh=Kw ݥ6`LAhc ) @4B84ԂvO_RSZu0˨\+/_mPD-^Xj.\ZO9Ulc@[|Fw@uFnArjZ+V : 3ҒP%Ly I(ˆ&|;([t9MNMhGrr;,NBlfcqd*_;XW!.gJ)Мrb#0⥋W_.C@91>!sP!YOJY4'0gV|+  .[0[ke~ r݃KlğT2.(=0x I4X ::Aj.׮kV j#:ŴM`"}NCE|'e?T_]'KnpTHV?x6ڻZ'?xm w?uAP$}[6/r?Яѕ 30UHn:ۃ`% +V᣺߈k1͖6mxt4]]f->\ÇDcrW A}a4l @Lw=Gi650>58LWL'Y.uzӶHh!(OW?k ׃ѫ0@uCìRdaT*EeN+bF}VbJT-?}j6ݲu˧>98c5Ϟ4=\ BW?Ο\vn$(qDx vfwY]܉Fu CJ皥!P[k&븹@;h"Jsn|F@3Ǜ7oS7t<${W'=}N;044ѡ_'oT[zxթ1Mrbg".@t|V`"`HtHd6xqV'X]a=_z|YL60פ ąSI--CWpm#c%qqkg 5tnhŷK2m[/]8k9:>UEڵ;wp:]mPꬓq߉ 7R%=N3dSk8u!8q@H:Zo9<lHގ{6|S< ְBgϞ&?16AD<#< bI3DBP`E ZsI'D577sLi9_~3Y9200 7#U,*ALQ1}v=<:<}4z޽@3N'B.oss#rHf^bsc` 7"gg+^`KL)q m%bʷmfG'rjrc PP=@jVx2pA-~?"M^+c0L-~[6q[?Liztś7wr8߁=l3< hjf] *E&\  K'N h;7P'd/<6o۶M$bŃ`B%L+@8~~ g#"2]RM@&S?w3z2Zza 7$ɓ'5*V@'^-k866,Z+[=w1Bё1^_ཞ[cvp??{b*`q>Cqh-쎧xȱ#xǣQ13 W.Wr~xTh}HkP&cG-NSIȳpg>0t~°YH zO?w|/\훞ɩ2аI j*ge$Y A P-Khrw$i` VXTѭfH \̟]K>QPY@A> @<857§f bP`"H>HLDkFL9nPEcrO 0iLOQmhsM٩q7:=Ti%x2Y,ZkN,&/;L{*Hʼ)ll% (:e.C8R1TJV=[we]b0ET.F&#HP&I3+}'''z{;ZLG{U"1_31>t]N}rmtbhFBC\a~>폎lǎ7-o(mVaߍ Udֹk5[5h*K*/+ZVǬhPzvk4޵YEw_{W?;z9p0:4`ΌM&'(d͹yk+otX-7|[lVuDo‘z͞cS3`hn!PWKjtx`p[gۙOO mӶNϳ|pz ˤ2IJcJ3!?O~uT)-Gqtt"N޸v=Q(oO@ťIUlX:}"^=b$ LהUKɟ0$ Oϐs</[ e1K8;5|W$mq) UXwAKuxdR[%=814ntLKr"$+wYm&fcLSVM]##|vmSfmꙊ1 %c71I|1DB~=$ dsu =u uAIEWոUDsv&ว= MXva<(RyXp~Ee bP&zlh<ZFG$'u "Ihd6NDg#`E+ޡb%,X8+0>D 7״un"R&asYM-P Z۠H-9XcsrG1vד# =qҩܖg1(@`ZzTKٻÇFN4CKɲ}r8M ZiR2ֶ63-;vm,lQ,kU~–.7Q§NuommD{>ʹ@LEO=*n OO[B; º24I@ Hq<_׵'W^ϞLC3ąO>Ol2DݮW%q-q N/gotosͷOF\bG@;~e}j&78}~'^Gdtܸ8׆jzhlOk._, Bx|S@z";0\k7.] 7, 7yfK>:MS%fCYxL;Hᚚf麷vh<#*m;\caiopUIaxc"@*3 -#G&aU"A _HQ/^ nފu#DJ bᵖ^t~ g|õc݄cJ g]L$i@GCŴVeeN"yvb{:#t$누CР㕁WCv*Q cP1s:c#$lzMd5%(t")IbC;:L{#&דr3ckL4jaQ\JQ[ Fµ/zvj`LRGЇGպyP+vfKaԲ{O>S|1`+Gt7~?41OZў;v3YyO~s93ib7-*LEd~|ONuz$}O]w ϝq R:\sLGvo}=x~݇z'۷޻ggp!f3S::uHO}[0N/d%]ʧѥ\O{ Vn»kw\pꆹ~tr>*2ogK*,z~.3AZ[/=hry!-507fSݛE(8\ ^lAL*e'& 129iIۡNcѹrdF1SnPtU`zhQOLq#sϔ9j|^L΅bvxb$FǃmݥbjAo*;-^KVx DC ]e (@u%AlU&zs*Wrϑo[̔7JF[ե sOQ0biyyP % o*lZ& ONEkq*J}{kZ6_m-%BOB.(fکL*&_(d/nL x&qO0JrjdsHC={AQurnYRynn?2_bUz]ɛ-|XK] <]m*bm6"t2[%l)=IѝhÜ8{oSZ4z$\ΖKw-|ѓԠ(K3~ oIT. K,Xk}˸5*p=_~eG ء*T{I~MB]&.5 anzZW{c~_͛ƦI2};wg ~ Z}!5kkmmo+Wa#p6%˼/%ᑢb, vcdۍfLw.xF6\^<墯ᐚ;w*^TkX'ƍxkY'Ha\cݧ6pzK|[lx2r(QcX~=g L:M `r' SjOڟz%F477$G^-vR]Tdt]A4 $ˤ.oDAR-W [cȗ,&C{6c?llZ63YWw˖F*t>TbsY{vYLɑM:@ ˩JFkU^UIwޟ|'fjUA_Qh+}힛Qy(#V!\Z⸒xNGTJ%.!T$)9͵ߨN]әSbGk[6Ccg h8LL>Xܼ!Rah6=bs{

    1m %c c=>?3vD4vaNAvZ[ 1FF5m[|| mu`sGJlUMaw SSCGMQ=a.h\kV]RDH|Up=0k;Q=zT派CC'*#YwoHS('_: `L" *|SLow?k~;+ҙg=?BI¦n^_on%d8it  OV%;Ɖ\z2.׎SD*4cE{'5;NZ7ݶ c?z@ѧdԲ2OGmV1H,b,m|CS3d\k/`dr| DL/*4<G"RBuJ#I2泒%->,ɪ\ 7` ?z m LO|j6NfGg7mpbc{>Բ}ofTh>HC^w&8l&K/Ly9yݵuά(ѝ;JRqxav'e:wMMj`8ݦ Rte;/U^銓t f\.ֶhCKJ@ tGH"+Ix -gY6$)rԚ1MA? u3b8莊/TU[[+B:ym7w4TIYcO=&j Hs]_5 $[=5"4K*b1 ՙ֘bd:FϧNv^1,s{_:2oM `-ٹ\1rq՞At˻'r4;ݷwd)6Xǟ8vX!`v3=OzqxԷ~e i9xi@0jN#tbrM4ObW76Bʫ7Z=A1W؊eFc`A2Q׮D7i[>u#M8 IDATEQ9!i"NQptjr#ţ,.Ga>c&r٭3cha5ǓNP+/ "?99AK!bi;!0<2::]b.ՆUg[yΝ/l$$6#n^S].TOi a[߭pTV;O)[WB9RJߺGP;x.B,Xartz]@]( 7<~ ]P .PHlaq8| NEH{oVt =_W=qLQZ\Wq%p0<ѽ`wN\ir.^ڛH%bxm؜L.G@&1Ҿ6d@/xC2GàXTwe ٩YI]?k 9*zdyV%sx>\\:y1RBۀ{ ~O9MXlkP>r֙wy7\J }eA8V}U҇$x.;lD?$!- ̻!Rw!tLImHZmFKCcM_QjN!\&4:C}=b^u˩ZK}`yre׹cq`_q)@afu4[JxtzM6@1UyF _&CR2E*vrސiI瀅C}#R9o\x֮-Q_JkcvnBup804W$zF8оu;hw;سr,YM6cϡcf!: ux]M(&ok[mJwJ$gZ@ z% lg$ H ?2 Y&dTntUj1;L"wj$gLnltPsY 2fpb2:p'FMf~v,9,,ɉ`Pﭾ9;_>U=`-lIr ʜf ]zR i!{b~xA\!t ]"}>X߷JE{ )TP f޺qOW_}^foM~WN~ͷ PZrtۥ -ǁZ:>8m> y8# ĉڎwg}I 6K,~ xLfqxx A!fEE~G(\579040NVwOq/zr&*2^E;Ç6 jҧG5G\Jd IA\r<ăjn[O#m*~v}tF_G-В-c# tw bB"=t&3(a s3zT:\\jwݵf)ьVTI*o|_k0#cLәLbEhKŹޛWj0ڽ bŗT\ b`ard(17?8s:\45Ԧ%XUXل5^ula|'eUDwBv[^*~b֪BzuS\=O}^*.w YNQ WKO[5Fhz[1RK@Ղ#){Ugqs_Mx8J-͍L~fuSW=!B޲o; UDKb ^I 3[7:R1m&)Pg:/s(a,>_S3; hYV,PVb<O]{v"2v9(hcX΃TCC]]}-ؾXҖRve=]8Kr)e0K~w: 9Ijf"rb`rc1@n&>F;Fx+Ăf Y⭪17\9GN1}k]Jd0.cy|5!/P c'y !Ĉ.g'q=(˽7| <{kKpGhoփG[-}VyI=;`0+xn( 6o=WE&nnٽS;WMA u-GtRf]@nnUcP篟;aah&_ttϖ $4{zB#Ÿ @~Q+oj)Sލ*m  [5@KyWM a qk߭h$\/~kٷ'aLʌSTlaTȎM^wg˖ݿ9+cU(- z(j0MZͶM֛z=B,}qMynL!V !$cN,&)yrbv~az|bs0L̸iyʕP+yj@:MP e($ScN_d~zf*s}7"m<^#@تPqD9jkrb Q)ox4[J3i8}Ch9R@@]ZG<sԊZa݉" ?P8 #FƖM6宙|R6屺1*P*W8)!,m}S\ܜI4;b-yGXzJOM5βK#*TKe7[8B]'1(=ppphTAΥ%p]3ԥSmzatۻwlBjZN?'=\2r=:7( *\Ц "3L55WzN;h! THˑ¾YR"^Hӄ5;:Z괴-U2|ZX@M. -9":tJGLf24'9wm߭@K}j]Gr])REjc0 cq$$4%՛f=DBuz*.osfX6ݳݵÜ.~&ݻOz5>j"en)bzdwwwo~z%>3%aJK%Sg K}A%_Oy:_Dx~@’BuI\ͥl>ad=x1Hج݊xlLO#!%x"Ir 4U)$?*2dFU)\QTf%¢tZl-) j̚Tpϔm-;Jޫ;UI$Cn'ɸZf\zBCj]YD/#G7ȎJZcRf;0(s:tz>>:6yHjf{jjjK$Rݿw?޿?%r^'0GLBj@zW6Af\:X-.3X 6݊*itSЎ~oXfo}[ n /$jᐫtI"~HP$4+OB\ԩOؘGM+b:5\"d鹾CCC;N}qΜ E299XmkA喁V @yC-0qnimCQCoNʁi4?ڵo0nr!`%̅pELzB Ҭ\A?p:2BqC$NŊ`?=ujSW//iFZw|l":7~%qJOK`5W: UVԘxq=--M:ѳ_te !ɜ!uE0Yioj1znD NL6Ε5H-gJZ@*P,G# K-Ȑw:,B,%J644U6 (jjAhmk8 7@crbNy{06Π}(.iR"ѯJV.$]d O#1@3f XZv '.-H<CVCMDdQt9@q&&kFR.Z/#p-VX.hSY$ԇJuu*bPNHdKgOESm۷u!n'Pn3p9( ɱlXÐ_f1!j/]$g <^ MZ_˾гO<$Ke`DUo%@zznx'N65MLOhSKSss#kDبAkGl$[  Bd Q Y+Q.dARQ0$&ƒ*$Qy >E lI=+z,“$#'@Mjx(UM TG\aP&Ÿ `jjF^%FS fTɕ}:0/qiVxB^Tel|ioo#@TONE.(a [a<& İ@9Idu|^|YC 󅍗a|qu]fTEBDe~eyZ xx|p\so4޽±O7c"@{1I@?dYl E_nX=;wV'B3.z@T3i9|D_3eZ2/FD}YUs+J Hi:݆Lk0r,㭭-`$=x1;>9j~dq9z E";5օjIҽ#[()O7H/Dji {pkh) T_d?| {bLpA/Q(U-uzh[o, ES@>UpՖ:?G_GQ-='UqҌ '<Mwo>WK]D1oES0^q8R6f0 RvMѤ)4 gR SQSiҺ1Ni pIDATLlg`4_rOE۪kݨ^'5EP,1[' XN_VT{/FSaCjdX$E21y}Yp8\.CZ!XQFZ>z:c7OXNLS, HJ1M^s s:wdΰ,un#ʹKtAkHt3 Ԏe6z+*GD@T j[3lK 0`_J\;[AcH+'W_S\pWCO?~9j=z惆M[Fnmb:=nG]]0MǏy؋NUNZ8Ӌc5??ѝBqp44h!1B5+l Nf_,wk{nXZiZ$#Y P Cz)t:ІtЇ>&ex0d0@BpbrDHx2zx5h/]D4YhѶ6 Gnj~8?6Zc3r'zz{v48v :hK*_. 륗+D~Pk<^ }7nv0q:2)Řc(VNї~ +7ch4r|lcVdw}'ܭ86K~c /)?KFs CAK.O]3'[u !NPAiE11Ld4ősv¿_uJ'a/[.QNx{\Ntm2TWxpPgv,\m͜]rٳg|;:x:zix|UqdUdgera3Yc:`kkA9tcmL8u攞b@۽tp?c;Gf=B 1F Cmxy<;Vx))K<E0f{\PلxxLPhWxn m$puˬ _ +6e;> 0?' v$8Vfg'1V3 KCЬ˅e?>ʩD"2 oo S+)vBbeSB B| 7pL&uvY LH[辞.7DZd"Is*s )&Il 4`TB-,i5]^_"%:LlB8N6_Hw1)FڳjbArϤ"Zp9_МTmkN^HO83IfGA=iSP a8G/Zex^_~9Uρ);zdjbDӕ$o"1_H?^uWTrNv61%xp:def,2FݵQVU.XRhS?4<܂5CoFGRVJ)В3Sӈo2yIgaRVZ@O:]P~ p;$DF^L}1op'E.ՉlK[*YhC{XZĥ_\ƱFFVBS8R1AC QU+T D:*T-3Id):ըmWui)"ۣjk,# Ej!~'AjoSIQDG)~X,x"_CP}>Z% 9CaYoOsw,Ts"Chk h:a@Ofehn*-+?JP>Zt$<ن8(`|AzAHx2I̛V#E6 s)ؒ2"U@>A+_T,єQr+P2܇r qUxd $cTXYԭy6L3\3}e 6tiֶ^ ՅD~hfыxX g?,y#v 淺.@BRӥ_`f<@KRq4booml)ч}vۗ Ŷ/ I3p+.HIENDB`OSCAR-code-v1.3.1/oscar/icons/dreamstation.png000066400000000000000000001533001417327530600211150ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGDIDATxw$Iv ~M{YZLOO @.%ɣ#q[+xܑ+%b!==ŴJ0wTU݃>Yann's ۓ7z'tB'pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6pB'6&~7p@H;IBZsg-iTb'"$XkAPH0˜\) t@8ZcmRXkAjzp Zk)RB!΁T)B[gPJgWpbEi <&*9ݗ)eCF'*^9 R ߩ+@(H!%B(YcKRtZ8`y̚~k;.B.z- O;:"scY_?ZΑ1?BH.G]o9Fȑ@,&]BT1{Y6ֆg5RʡRjM %.0J/Nk ėnI~H{Z7kLZ֔Fqr>I CҫB:PQ)6jNϷo6'-0 )Z_snҫJ!NHfWףBD{p!{QQR!7bdᖔRa֒8)U"JRW>iBJJT*|zKSWkc[N1yH!ʻ[k/~ZD2aXB)_y&}o ?8pXkrR)4hVYYNgw4ZS.YX\̩TKȇfP"zWƍo#SF;swimL+++/P&k J ATFHoz;spsTzFUxg pƀ0*W^2|]\X^~A0x}}/>,ՙ:gN"*iřٿfSs3rB|2+/~PH\kzJ $NLKu/2zn=9j RO?$_%C’&)IڣTv0bMaf7g蹄FgZf՚ BG6[6eqO"茝~B#:aFC!%Ig^zRӋ̴fQaTɥD)7&/*=BLEtjV?g Ab:4MI{="'2{; xଏn;羦zJHyș|$/홯ZeZb{0ʤ~ȁS"B* )+7!3Ċ&IqPBHVWV)j&.b0* R)J Nru.(^b ()RT^At>ꖵc' ۈI~+/^i49|fkCo RLl}>|A)xa&h ΀+_ 1(!% c=KQ-N/R#h"30CaP4\ߣhFh$ `-I2;vRA0v+s:~Ŀp0 |÷20oRJ1K_zg_gNh΀ҙ>x}4) ovr 4qP kNf2]+cI0MR ^cQTR QJcX,DX8^jNFX r Y$YCkw?\*>UpCz(Z?F)"c ķp Hn{O}O̍՛gPo0/z!) b8&B%i 6Rv{8k}u+V "6`5HR8fggZJ9Ҥ9@IEQ .ssa8rذu@uk-Cϝ3 F 0: νGf2*/h?`]J煢y FiZo͛ks,Pk [v`7ip=s?zO3{=W +4pX,N)ta0Xj)Ivz€,@2f1J)06z0*M Od'Ae9H?fI82?0΂5$Qe09C+MTnQ)~T<+^J0FZ?'4}sYʵdI)?)r^.Ɵ

    vY>EZ 9 2u0>$I>Y$ jlS9580֛5XFH+Ɔ."nwa&8P RV +%( J1LR:T,2Md'xMXSH!^gWj0z*T|QIu !R' [^]_['^.\r#d;LpfLKqd?4|J9z)NPo EHbf|ݏX^! PRbc'ސ Hb>NC~kc%QMd/❊*1$ΐ:9^%aTjʕWèj2)7o0o1Zsg>?ENQ60s\&Y-a)r(cRRڔF9vo`XKH)Hdn}O^ gޣL$Z]Xj 6vk5t=bofx?32܁U6cu \.rrY5QI);m}7Л"!ZW~S?X^:MXظȋLxx{w2%9 " g$ G:ITM;p͵M`u`435gy}\_!wPd0E?<㖁24EN8mirr 7;BHty>2`a7z~RRDB/ B߉uuȧن//~Ja{.LZs M8sK5WT:֡VZF;#i% Wob3$IZ# k3LyM6oeT" * 1a(da4jRCYnCoVd7gcg@HH)|198m FcR~}L+ )Wj?Qo4~TB/"H^Z  -Uʫ/OQ)y{_XF%laR_뎃.=Q[1#@㓲oru7%)y ܻ<^kRrB;=^F(С$(&5@ThU+ !a%$lwT*}*A};WSˏS~eɲ.SkQb31Ħ}:ޙN{'͙כV*W-/}pIR֮]}x˟xZs_D?FBһdzӰ\U?9`&<_9&! H)^=bʋT+5.׫)Ab-+۬ H!Z{UZH$ hHb,hX,+ Rz߱Y}<ɠ?dk{j(̘YuJH1 JA0Xg{gWkߨkB\} &&!K/>O|ÐK.23;ADLq^31 훝u'[6\aΆZ@E 0}ڝ) ̡'R2Bģ>8e:FNV,9vC 3gDnI]U|V9yܿ@BC^64#8_jI1 )|Qbmo=R{Z353R:Mu>addjΡW_w6["9P/6viq;R8CW|'k-Τ$"ISsS:kncU:Fq5iZ.CtDObR$tT{fgX,+iF1%UGሶzGёzzr|җh֢Tl[kH 53T>Uo 0o2~!4}/~[3Qz abkc .M#4xD$2,7$}#Qa4qbr吘@"@jDyʚ&* JV;Cڝ6w0 zr > 0f+Rffl{;{|Xp~>:1]MDbk_߿}{? >-/!#{ym!E'& &“L1(80ItSG%Vݾ-KMƒB?qreNiTNbaIqnƤ)R*[`cB 5AXzI=*Nm6qO#,'gP:+ԗ_j}RrL|PVE3_{>~^zjUʓyg'wvC˝Ƭ±Ouz#tHN@z|1qc:PA4yCLj#JjN)(');mڝxiiQ@#("u6|q'QD /d? !n^¯~<ӤIʹ3N,ϼϟ}>az7`~qO~淿Uo}~|?1EEsa XtV@֭;ucR>APUDDl5FpBj3JX2ۛF1AA@}nfE)XgIe'lui+I=BF%*zZ% ?Σ;rv0}?IR{'xO2tUT j&F/|䎀^h`ov\ |wSkz?|Oۂy@<{?Me˘8pŒY.Ic(E%*pg/QH#5Tԧ˶j_Ћc)K%ʡFJAV+[4&MR8+2RBQ/"M%Ttz$j91펙TL4u."Zm駞gǦ) pjy ty>~RGx$74pA䥖 MW^W~e/|.s%=_fx}PbRm߿D_1ac4N&" #4!NStRí.#ک QJ3ԫg OKADȠCJ!y5+ 1Cz=z=$)Ǡ$rDZ)JI寒/J=+ ɉI^p7vOiMÇ3-yBCZ0I J!Jhf#MJb+k:>;JF Cá:T(^EHKhH*+ p:( u`nh71q BHFØ|'4ሙy..ϳԬ " TJɐQ\kRsY/5w_&%Tٟ0>~jZ~5 th"!5$()%#1R&/a$iZ0;$a0&[5@Q4(h"-,ш$MhTJTKR3~_N0/itv? 8>t{=fgiUʴAJDQ/(2Lb66ougNVjz':adL;[뿴v ̷@!ibyVnPmqz49 RDtn⚎j'_Y |2u*EJI ui%V $1%XbVdwg|n FIWKiʁ$ 9~=I}KVL}1Tu m^&J0 ->SޠVQW҄vMݡ2T!Z%k0ih0dkk( >'=|L' wl{Oo6ۻ{lo:]fUggo@AVZCej3YZ:3r(hMN`Ec$|8eHTh N:{b -%8*70P@dI<DJ*%|`U!J-@(jnn2=Oٽǻs&OFhO>O<<ϼRJ_9=IxMZF֒ X'#pS5:;1u$aOmM;luvmʥӋ4juϾz6RtivVKfAbK K ^\f~I٢YRB @ Q rOx >/3vN%~(8W@B &[qNb/+"TBюB*d &/f)"jBA(JR\qbS:wdAL~M\C˯+21yLaRW^O~xМA ooŚ-$(-+P!$ijpΑ1qj/8kf~}|}<&6[6ؼ6{RSTURcggtv(Ľ G6Řx`ХjL4MZ fggh4i[ QXB%@8SO{I5~6 LT9PbIjA3b g2٘phgc\fys2F甘&9voː]k_$7&:#*Gj4oSO>ɕ+WTDקK(X)&N)(-HӔ8H0Ơbc}U4gRRC֚xrt' @nn^{_ww=nmw=B!*FC:66AדJaMM!P*u@j_[ŷ FX 6*kȵbk-f[s,xӡV^.Q Bt>4<La`<B",t٬^ 3nܳ_9lN`- ,}Ap9}On_C*EK|BPVqIpwx#B1l:HPI3Bh4b{sϔP x 5q7>A{s[["PzL\7   ,t'E2:| 8Ba%1$)ᅦza@T K γМZi2hQ-kmtФ&a;yl3d L.d[#&ΗiE~FuO3v׮<^:ėuz4gg1& f*4gN T촻ZӨUZ+UWFUc%z x(M/ gk++6QB"Bf[MAlR0 X߸Euðߥ }JN<;RH(uQeXx||m3Bc4,t3耐mvnqUE@S*טYgfnFVlsZI%*Kfܔ:[H);:0޼qJ7PO-fvd@3zN0.2i[L3i0ۗ&@}~ɱNՍ_ƍTj5$]FΚQfA f )$Z8qt1aTR.Q*X%Ir)DV1 x$DKռ?ԳZ$,OUb{vWnn3z(hD<|=,kxa{gMvwi @jp.ڂ3P \( Ak<(;;\z @(9!XoN)uk(-)EzrcK,0טZQ"M'ŸP}T8HO3phġ>7m57~n1wL&9:|y: *I=nkJInb-%ApӡYF!i2* Yy:ao<' _{"a34-RUqY<,?`eamoNnNڄB2S*s%.^bekte{gmwt$qLCI fNjWo"uHP d^1xfjua)kE0R-h6h,-8D1LA\&!Bgz2n1I߼Xi&AB0v/N)&5iELZnؿSg?U/z+|K_"cZ3bm19"۴ޟbJވʛxHbcv6Ti)0f<I)'zSg+TEz#3ZަKJt)b10Kav]nYm6vv0..r-aq&[$=wwHuV6wH YaB*ZWnp\A81oX$f*RHhZ^aqqYf3VJG+a>løl&`?IR0 dPG}H1 6S#Br*ڦlHBz/fZYDHza*j*Q"(Q9F.{{{DQx8x4MM^>Hk~ex{%0ی2H* `k(l AR.Gk tYn+kCR`h;片]6ad k>jϓACg^`֠/XMWR"T;m׎Q2 pX˕ګ*:jVs%f[,fhj0c BnyB`9c>y`R[I ?4'ƾ=JI=~ _+RTV*~ 2YV;DVVB)hpC؄p@o4bѩ4 Zoff\U쏬]K˕s8I-Y~֞K܊=lAf"%3J9|G ㄝ67x}+ۣ(+Ѫܘ[9}d{_ 8A}cR,_y7hB u`eIQ #L2JIv&zG!BVZtD;mQD)0_| X yF=6[;dwg^MץIS ЄA@5Rh% bUzKgxW)2[8vlwvmP.EufgfaafŹyf j*( ^ ִ?Dvz[V} &o=Ua~y1y*vg@w"$ 5PS!͊b^GeȼԤaЏc4e0LIq(eI #f]Ę;agg[[;tvb}}D*eyU*J(u=Iq—0`Gc0uG#hV7)ZUSi6gY^gan- `tC=G:h$?qٗ /LZ.iu)`/ * e* Zr}bp֢$"RU*"N,u< C4XnPuX Υ(:*NfjZ4CG}!>7ɖ&a7ू@)|~~ZH4k%fehc I2J|.(MI(RMK4efpG/dDogksխ ׮R$ 7 DkRuIiR6Ť$#`:A[HR`Dw7y5aHVcfnOqzq r@ -Ry)C>5E~GJ4pၾH's|@4Z-J.%uB(PtCqLCRADTF5DŽuܥ$ 5QJtXހ^g|' ( ޷vżKx,*K}g z%nig}-)5uIJgX@!äE馇jI%֔J!5eYK׈Ӕ4%Ici_Զ3"(Ef!{xQ+%<̋E( qRHIh#./!̬R^[> r;\֦jdR_lj}o8R& ss\`8nss}k+l.:T*g> "Ғ(!esɞ07YIX28?[,.27 +DQ)C.flTrj*wa?TXk^oį|ܺBslYCP-GJ. *D0_꼤5q2xPvHDF(푡~~àߧ53J1o6f$!c{?78} Byj&Q3QKH2ϸ(>`϶*|#頢2O:7nlԨ2׬Ӫֈ*e S ZiegN F1{뛛X]k|9*26 (gOF ̊©qҋ-{>7n]|ٗ85W z{ b55:BI2bdcyP. >S8FyuRO``f%Ydesm^JQ+|I,PNIȪȬuE0(E) M *85 a2ԗ9VKaHmaG-nd}maOjH_Hg EuG/q PjõH%aS ,i6Yh2Skjs 2Oq*:Cx>ρ140V0aVJ]plv4A@Uf5JZFby@ H@[2I$ԚM*nL*7". (M>()A @i$DQȅ{.R \ɭV֤mM᳘okI$ Zt"|28d9Z0_5p' t@i;I:(6j#֚u7ݝ0"d _}MHʟ;~{138∂yPv) -H&d 9 Z(M$YVv:kk\_[%l#(7v۬_ʽK /,h(0DeHć!W'V^CaKe:{!aL/KAD\V(om AݝVnr`҄\!D>p$#A85a5Q !&)PZ#:_L X-h6Dy*KP5U }~~-oNӡ\.|4IKUH-to_QY:*QIsf90{Fʑ] $JD\B)Mz}JZSP7yv셇/\V$[} e">G!pqԾ(eg0q}h ׉~zŹyM RJI2SݏM9TB|R(fw0d.!`ZaQaRssֽ=K\y˨ή?P*om.J67Ƞ)!*KBIP R(İ]ļ"G2sQ֚^ SK ҥmBf^ )PZ3L⬰)>O TCGU33`ul֊]] lpRHG#Zs 'zYkpH{gp@#T0$>ow.rӡx'F ֖y#^CVrWo,yy3|{f9|9:AZui>#$q" B}(KaJ30vn EfYj6$7++Z"AѰGwg$CTK!JL:ڃ & Y!;TDZ3YX\R>'@ejc~a8ׄYsLPiE8c~NMLYoC[X53 HEWIX65Z |%b.O|~7x0.Hh }JQ +U$ ER4 *Q0_oЩE$f]ɧg07jܿCKi԰3/|[vЕ@@& 45NYCoE!qJ7h ?cuaq0L*a@ģ!nا"-eQJILRȰrrj)t@*˜!Q}V+dcØ 4eшE]Su^ @IBO?s0Je._ aIfGKc71rIKvƙìpP%rc'D2otYf'] J M..68#nl˯v6ӟL5"˴f*cX9CPI@Jl{Q@RBx4 @^gW˯FR-]?sa:~k]aMz6iw?E66U Qڗm"JFi R  Pc,tJ::5e!H#`@IB9|,R2 }t<^H10"4Cd( 2}GE:’j ЌG{F5_#3ɗ l$I@ 5|,{Gأ"Cp !R} J?͔BN9p)NJg)3&8^<=yNy76;X+qfD|"|]z Rcޕ.sAMoVye_8OF@ oӷnlϭ~fΜ].p,ܸWsKZh307tz)x@BF# F ~ Qn=JJT"Èa&5 $lUZ-o"̈ĤB!/)%HU(W* GC6vV"[ p+B_Μatgj-T^)sO!a姧FrK/ Pvj݌d6:09 RO6X9V>帗8=>c3q}& W__>ȃIrَcL9j/1g`{^:eP8&fƻZ6s:3dqVq C^Ǡ?טVs$N [/oΐ/nrkV3ȃq".M~5WyXX=.Q )GZk1>ȞIQXljHIET<3%=C@QfL@RGtum՘UӔ=;}X;lΞ;|;k!8kT{wd^G;'cw&8 8(c~Q^"{(V)&kM\QSd݉HVJdR1r(y9\`S1ZC侍yJ|Z6n qz{]^Ɨ^>I:6Μy7Z_c{_ /hCoB̶'xa1%جGj *FF(ЏSxX rX# ʥ AIU(Y-ZK{jmY-($xZk/;M򊬂7WŸL$rm' U>el~0?2G~)}a=㩧??E\;Q0JRgo4?$ Kuf!q> )QW*T`Ҕn)- ra~sFw}+?|7?G_˟ HtNCHR*' '\٨3S$I@r R*XҜߊ藭=^IWqGxŤ"qyImQ'hvDU 5H)M۝ n{3sL '$5}G-;1z6'b|bU3-(&uܘo]0=м05.3GZ'4$͊d"!#UX=0i,( wwii]A _EV7aΜ+<Nj/ƭ׳ɂ pw cJ -j 8g)i_YG5fR9%iP-xܭ܄8lWڷ HFwv]^ud-:)BdC2o44yx]Y6!8i)MBqiwGZF]n'.2|}@Cۡ%K'%\Bucz!шQ^U9M՞CTF蜣?Ï3?WX_/-.balE%_]JJjj5fMR>QZRZLTڔ^nJDRf4Zs\~6 çR([\8kpIH R#VAOLsNbNvS nJAQ=Qln7q.bԻWMr:~B!pvpscb,}e) OxajO3L!#4T7\kOi9YD7ڗ#jQ1t#FI0.^?S>e!8τڽ.}!_ o_ħ-F&5zշN߼z_ G}U%}pvy$aۧZ k0ҘDOe'oA @i {!љ]/&/D2(%k6ykܳ#?1v^#q DJxafϩVVB':it򹿸 3 o.f?_lji[VL-oMoWc@1U=+g$jF\;۔ˆ ,Gj-ߤÕ:9JQZfkngn{@c/þYmwi5ycdk",$Qe)(%XZKZ2#NR |/^_/_sJZon,;8٬(_~i6/WlAQV K%$Yij͹YJzg'!p}S9Mc?uIb%)>:(DK>c2>)֔Q}g<|3+5[H!hffF(1)Z=X[T}{;w6hniohҩYj,d.svc0NLkq[NM SƦ"3.IbY|4 0Y~CO1׬+ih|GFA/x&`R {>Kh(!j\B+5.{!gTc=m^8?HFߴN@?i2:3~)5LR*;} IڸS{zo;s PiX|rT+ #TGC$Q.Ymz)Z8aq ={;0 L ri~ΟQ&A|SW7k5(Gijh.Bx gK*hTʌI ]evVK:T~ Yn(85X{Vˬz䭡o  4>=mE hI|o;kqCϮ2rOq+M{'_+$*rW{˙-"T*Q*ת ${{&%;;{՛;e8קVL8Tn;9.rzP\TJIo0`'?˒3s,7^.Ӽpms 3TJaspOubMJq9oHEj_}l; Ӕ˧ &p/Իoȓ+|^_t;G-(eYʹ i5vۣwqsIL=U Gm_2_9hf7d(]QDsf׵Ze沑CĘ4ɌŁތob ͟pXby~fZ̮GCScť xk%}qdlgs|/SKlXrUc^%ϻdGxޘ{t1U$!CM&^SA^&6:4W}gsmH8*wLc~SeVqNCyߝ辌Lم8Ip} NUZsTḾ"E)loÝp/1x3jsg@)WȄ9vwdQ<ϵ-.,":YǠrzuāR爓[;Y;*W05)$4? M>B=ٝ8^WvtD\,g MMyт;?g?384yHr_316w{fy>_p.12F|ZaLnmrLQƷP.+vzC24fg;ը(M83Ro MFǣѨ9_?w?ucaǂ`{sRL\ 5)&",l24|71~4q+m7sХc _JYqz|Dq`o{y]*qs1H)Yb *xu&5xzMadscIҬt{!+;mj7suxM@+L%bݥ^1mQ&1*u゚Y OQTMI?ip8W(aB&t;' +$7SW՛e#oԤ8.4R;;lg -^>/b`Yuc,D6w߷.y~XI 32ol%6Mq3&ԫ!yߥsh{/{una᧌8se֤XcDpxZLJEjک~6txO`453#BI1iJbmX.~ ǜ yÏ673-$/cHHɸܴ4jsQP*KJ*o<~O(Cn~ ܠ٨h͒wJ-~ ˥E`E·; W)a>銽G&yȤXd%_\1'rT_:<;uAwRI6vT+eQHbyݷ9ѹIo0b+}O!Tr7Yty E`_vb'hmz}Subc9Mz~퓲|}%a+h?tߚqk&Fzsf'"e-\P=Nun{'F_cڙ59.4;x=̓/G#ϾOAL2q-7+q =;u Gq84Ojܗ)f^ y}QRkRꥐvR-p)Yp`-J^L7sKTx܅ MeܛM @A<E ? @U~/ \pȾ旇HغLE8>8<-'Q14)3|5i3t*s؉,(XQ ~ *wcsv6ҤpH4؟Ĕ)-76i5jHO k?e~#[TΙA pG 5kY-nsjqt R/Ʊ! ˜!u35XcR "Cq| qDF~aH)Hc-J-|_'1a &Cin5}#7_|n"205[vd?Qy;|>).yy5L?Jq==K7c^dAиʏ#Η YQ7~!*j5 &o qԪ~w32R  ퟻpoו ׁ8h4˯ ˆ~{;SGIA{q 6ViUY۫w^wN˳)r^KI"@+"wp(%?sPm-` reyj>(+L1sݾqNYec:X[\͊kQ9?g />L siaJH)gyX$TpS)q񭦉K Mn{Ow;{?I FH ʗve}s<47R% # u'߳_#9}(%QJd#wbGKm,չTf - N?l5V$_G+tE9aЇs(AA{?I#s_lQ/.{hξ#NScH3P.8>D?dW_{_[+.UyhR?Cuwe"[Mhyk7x3OTjJu[k dP82-ܔ#pGё_Lިcՠ/l7vkW^7YHD ꚅ& 3K̞EV!e>[> ws{;›qz}qVHd8nsߌ d…o_ NզѨjm_(ekwAlPNjbc4)(I@ $1:{z#۹^vXR39G.p=pH!xv lRZP;̗_B:Eg)_VUH턝74{:,t.R(5Z. L30&Z0ҼǛIz0 tכG7y_wEx9di@TK%t'^YދM@KTqS Ke.=JC9rY `?RN%.`gΎWcJUA _ZK@Z̵rSqo:t$6 w\}+7n/@9 x}ԪuLcz[b}?o%#u7!N XZi#k )e <,tѼrׁIJu'j"q<Ijis*W^}/d&Uy pTD2e,Vayeeʿ+!.M7Pt)ş=oDqXyz:=tX}G@[·Υ8׹1χ\Qۤir fKHiv4J94=0Py.M[K~ڷ>x?J~sO>c+W_{{-$IRsJ DZWA*=Y6-V=:q"x7SyT4%A!XG<!1AN%vn:xX޸G!:ҡ.]<ヴ;V6v\{/^EEmT{{4\S 4K2&H-*9NuVC*_5?c û\Wr0dIX.܌_&Wzm߼L8pi3*X:OV|j%hs;Llj0IC> F&u0Hbd!H1v&:Vԧ=B{G_;!)}v{[\}5x:4W\ \8) d^ C^%(x⩗YyĦ@O埛ȴ8EGC],:dxē_4][aΜͤf@^.^ FDaPnspX3]9>-?9#^8_2 vQ.3k>su%x¼wmԘ]?Y~wK$ ޖLӔ~C4s^R&lgA)!(`L2pɴ0BJ|Rc1PRر(D郠7t䢼͵0$IP))|GHb[[{\{&_ʯ<{k,T3 .{ 3ך6蛀^"ǎv-UQTX!ͼFFjNT&/lB臧Xaē$/եavT!1|IȞ{k//sjO#+il$pf .4Akx$0{[[0&7.xp#˩uUG'ӟR􇃩𓒒QbfM٘xo1^ZsJj_4e1YCJV;zM!LЀ@dPR AycBN9g8*n&I <ת4|# G ; /^\E^bx<-tjD CYc/aߓ9@p)BTqf=FFDeniԚT[|-},,.de&7|4MԓݓN2ԎK~MXR)DLmS= 5yņ|qw9#GNj`tɒ|L4b!2u>rU1}$IR|)s{ !2nlMy'zL`EoOĚǟP/|32GX[^`c_>w[bQ^|%}8]A&q19c""j>anB 8xrnodK{=pÎ 6.* B8!HR( FJ҇uX NǥG:ggx~ kkֿW~ 3UD:0N)7[Ut>,ÐE'8|cZ :t=ҬOX ݗw4Npevo(r795g6v:7p="M"O>#RL£6!-Je !) pM('S4e+wSHAysAX=6Ŧ1&RA*,$ !*&{ ˮ>:źV=IDATw{[2=3}JHbbٲe[RT*$I)Ebl˶X.[b"H03o%|{=34`އjL[r9d"!9Od4lPZM ᭟PlHGC＀]z_Γ8'Ñ6ݵ=KX\%P.Jցa)a[SLk:5| ʢrw_- XUfJ!IeH_j#4?9lS낀p(ci٩eqЯxc'>o>EjmPg/\l]Yl.\vSJ#+%V N&0s!j-!Lʨ0?Ν? q̪ZxH\igtX2ρ|>' 0B!,7IAX!e5<8fz2zQvakaJ;|ǽ+qxG/BX0=t:`"o I/ Y?{9p3h)M\Yr*WUs˻r>[xyE@ ܷ_D/s>oYtA, 1qTlZktzw>|ot[QDZk._CqQidۙZ2ۦfK vo:eдab  `3(EA긤NZX|}БO;& Ĺ \B@MJ )P[38ZXZ}u068Gގ`Bg/^DpxvV`K(E%Rhߥg@l>9;t?n1@wޫ)pd]O#KnǼ PT'Sܿ2%Kӏ`ye Yq?lN>%FEy6f:}(Qh ymTf5Ì'1۪=/yZf)a$2ԉ̉z;BtRDed.SA(&%-(^!$R!n)伱50Y V XPlGʼn p8,?oVmġE[]FweXIP&K/>>;?O`yie^̼Qp!5bKx"TOI4 e0??\fT~gu~ ϝCGKs7!wUkŋY;<It?\jSv~ mg/֬HpWߙVBU$R  ^x"XS)-, KKQ]0J_Eau , Iq"RE֥Apڦg,٢JݖDA9}yq:ΟWΝsO_(J8ރg. [6{SgRBV3W?8+na WZSsϨ_[qTr NLw0-jUBr _0MR6z,{S!h9` 9u]@QB&)L.`(T#Ftt̺ǘ;b`!ڋG@wb?Dc` E,tyO6-c=,eƏlwdE.u?4B/0n/ ͽ O/p|ʊ+er͗p)^-Fc4f0)]*,LBTECn[ڏppc :сgFc%0{A@`0LڄQ2%CB P怋r4B񁣼P.SH32tC)ɻ  'a݌?gnTK8V`ΰD;Xn~̸ gU}G\[# D cBwᜇN>Z[u!D0<X_?ŵ#8]cw>zmg2_k| Em&Cer[5.ڐދQF XG}"e@(`Μ^]wZul\ۘp,l4Y.h)6 YB vs)Jg0. .EY eA D8<>99f m>\aɄ=ַ*)̩Vc=nֽZ, Lz}@?"d6g.#5O׿lq4N{M˟EzOc{4C)-q6RIk1w#}Ӻށ!|L-bgbݟ[hj>3B@3 6L:jϕ؂QQ@+\rFD50,^ͥ.p.Z0@^il|3jvW IȊJJxS/``ߐ"o_Z;SO_`#{{.h/`f.58僇KXه Ijn~ږ5D^U>-U2E[> ]{δj'b#ϸ(|ᠮӍq7|s" ``"` XB6]Ѣ%I $к %a?@}PScu+Wtf*6H\qPm4qܢwO`0V_U]w]WA0`f 'N(2ce {JU5Y( oe 02ޱjVUwq̞Q>o6-umg`|̝L#λ|VMS|_e(hWCd]MJYC a,X@'2FLU0xQX4Y&v5 )>_&ȹj7ꁿG@@D)-e\Fq %w  RةsyuJēxFgz$-wimKM?j;*Z̓Yz-3!06sXS?3H6'8;]i5~wk(͉F$'m+6Yx] T(l%,tP\N=!3&h1AO8s@Lf`C)R ITmR :Kiq oG855#۰[( #TEhfoMJd-󈃘=X7c;>7q_x*f]uUo)ԽO?~">UK ?4Ȱou>}m(+`uw[ʼnp{pI,YȚ(Eʭ-zOJ"[&8pK1:Vjf<0v4i;wMD%*f@?gY(ZL1J(>OQLч'0 * yS'kȀ[XBC`_;\E x_>uwE)x4;]ZE ʊ켆,88|j p>_%,.+eǬʷ& J2Gr_xC8K*=J $AkW!;-9t'Oɻڡh/,L`) VHda t+&"eU̱+1.]CO \. 0䰾Y$`d(F#/I;VQ}\.."pa$.ճm!wju?b_D 2VpJk,YGL $7S*EƯP3Er/Wz0‚e0)Yt{ HP 휢}G70M(ƙ[,6ڥ+m70l*'.[hk+8q~>q5Ȭ kc%xuKV-&Uʜ)h|]VP CG b*+`b!?_-*/%vEUM |ن'J9G *Ą (oa2MQBkamlo9Pj[I|QrVbdۚ$@$o^J.]E",: KF]p(7v% H8g.l(cQ(IgY ֠ο/^_ >`ơpI8y k[2"a[m$szKÛ`4R0gQ P,p&(JD*ę p,E5k€0-J.\= NEo@s}6~(\uх-P,P(}<OۅA}~5I"/ Ne2YڔƏ/,th6GYj #|dhFYѠť%d%mIqøR6ysmzSfeb`3 1l׾L>եE;vϜYceR46Ƅa3Տe0.hW*zcagpdk\ Ѭsjf s!)0V؋S{=FԧxaƅՒp>.w5~ې#jC'LrpmB/U)Y%)HkݪXHf<HHf3R (~@MeLƐIɤ: YفY?L)! QU9>g!{GC!A!R0HHŹ}t—@}8q9CGO.pV~筿Yg&9O=. ѴWo*hR`LP\&E s $:eݡBwUP7EҋBZ\cQG_1G G_slZp[d!FQZwϝr܄zb-jiv=-/6Z? TP:.E&>k;c\Zʆu?P6QHT;4Ot9$.vpw}8z ?Ձ$`b7|Dk>@n,Ds.GeA̔^$D"g"XWh `\ &VY&؊I6>ab DX !BZ!Rf*ĩ[B +ҸY o~"ڭƣ]@;F Vw g&hmZ`Z&ҘLr(U^x G S< @i &<ڋ?>g>`!{q#2jmG c?e*DZ g/Ccu|H L&[q4ҽnh熫%H]Hihh<w.D@!HWFͪ]zGG~~yȟw1ZB߯ =#|:?XWB@B&ЃuJ"Iww8,-췣Hc-g)6KŔ[)Ms~HR tmT>]C$vNP(h@D ch9q};nX[x@-ʁ簉  9~'Pfuq 8j?}D{/T 3b Д?g \PA$%4DBi8j) "":`Bu?CY6B4 a*q3ѵ'Vv+g"44@rvO`Jj3cYwU5e:y?7i"PJUn8յWVLY*Ccs#lsPP83S3>~";P}d / i/4β-(;_P@QkJEN7&<-& DBD('Ъt &D-aRT8)Upߙ }:CO4KXApr2@4,8Պd7DWJ\Hc`4ͥklE3/SB$}g`{P)j7 ?q` !2^ȫԶQ `1Sg<汕PEֺ[B e*'pDinFoOĊe{9ԃc9߾h֕{Z.R (8Rq@jA[v nz^lD @ L+Y ~^( l}OiʹTfNcWP>k CRQʏq .pueT1"mXUR9F\Hk ;J!WdLs~8~P-0bڝΥ_\^avM =dSvFyD ,VQVvdht`7)JhU1\ y;klQpp|u}xm=̢n!k4QY[OjN ( 9ǪGc9.'NpFK$tY d`e2C2812RA9v?U#+`Ǜ] ߀Ҕ0FБ#fKpvG^6 wkkў66Px,@1l v޽?S;MNO70.Z[7+ksv;b{GcSʢ&ɜ挂]f8ܰ?/ Nc~8:*B0f`ifIJy}pF$%z2+sHI~]9l;GUW_NA?tc ~M0hb @KC8Mw/ωH4:'7[ii":@C;"gbvAuD3TllqCtn'кw(?qfUgA @1+(VUP+ U<[lX4LB}:hN K1 HΊ @cg)~|/ ؒK 8OSģAgEd(* G}& (c?Qf(r%UY0S{0XXkb) ML)=tkf7C +Ϟ!k5g̷&a0VR@pKj LtX}Dql n-)*9A>w?[pbK|6ׂ~?iQ6(Tr}}<8w|>+6Pe4r5cL +s@H` I(KSH)]POK!$&f/+UX8P=+_4n|mbqQT~vE=jg2B  p WiEu`v-([n1F}y !W _mD2TUcEs1y=1vg6[!Ǵk³9F?WfJ f,H R"R.ԔKCUǦ&1k4dnĊ$CZ=7ާW Eș8qF8P*KI^(i  "5%!(  Wx2 Qj(3lqJp]@& FT>c ʼXQ3 x 3Z?2x텅qk55PZLAVQ+N<"ܙnޑUzܿ@<7M}v;Ћx~/GM WBҴ0MB{ö ZZp@:ndym#KM*ȳVa=W)(k%oXI2FL@J^ފcf(peJBU& _[h]4qQU|~?q@k[eb:t=CMTG}+ݿ&8Ua 5-IN V q<Ԭz||/m,0 1.}{Mn\C؏ǻyhhQN9lojP,tCem*պ UWڐRvP4d"uzА`,JY, R.8ӧ@"֨4A*PEA6sK>+R ;ms8}ꏘѻ$&a ?=!eEdrq$R{ZfElvCl;e  C!*܂awA7p8ߚqLԯO]wo]b)<&h )LЖ!U#S(:xTT-=b2x8Ja JV6>WoYm֢l Ӂ=ЧF܅X(m6Q%%2" /Zh9*hdؐn4"$A. UP \8Sͧc7ؔ \VF)Y`0_knbC) cy 5 ƥ~ﻏnXXF_VDkjXV#|}?0+&CF/WE0_Ŕv°%E ۩ XkJPڢ{apOI$OTsqmMx,3ڝH 9 W@5:BmnǡS'UoGwtY,# p,&@=X ]4)& !,Z&otҬv,CȀ8~ؿ=qlQu ;x[Ch6g{, AA*$͠Pwc8_ԁq&|E`Ln 1sHf.L&KuNb;q4тyU1 2$ig -gG .9>Dv ki+kxǨ|׀t̂qK@xl"03A\,P,Ji)Y|)7N|cg,~,2J*g`IPʐ `u{DSn H'H$U1spP{-xI4\sLi,u f^+i3S TB( 2K;[E %6 L9R`iH=k`&#c(uRLVaP4Ks$K͂@ nV-Wtߴf6 EJ)=r_]]~, :q~MmR $I˫gПC\+?] &IBH(p2XP4YL}nk~?XK16E?Pщ/5To:}~lEeT#N qRN'(ƣTEiN,;]}V[Rq( M@$Y}Bi"95py@ i5He:Rn~Pq1VdRҦes#|qvSvq,d'hJn Vh-ʀԙ6$:iߤM".~D#Z,tZX:|os׎m7NWw0@OُKxwk/`0F L$ԲY+PѬ]i Aw*XrǠ_R0lBk]n2bJa25< E-~km¨[B✡i߰,yiAi·$uyi*VgQ ]*wZׂtiKe^g{saܬhDAB j5iL]yui uf{]<̌=!Ww"T0͟d4<˂l# i:J&$LSXBUY- VV[^ w*X9\-3s瑏l\]r5\RԓW@Ww&iCAZw@dg-nӬ-}߲(Ͽ3ckN<ʸgyD#R ʖ0h42I;j0Co`'ap qݧ~}Hf$oqyvV\$&Z?K[|0'HL>n}8:΂C"$ Y sZW;@ֱqw歧GC|5z|c>/E+at;Y\$>Sbb}*AU(LN4Xv!,a! 7 ;UAKP؁q٘ snd^cbq:1ػaiPJRu8 -хw^y{N 5ތ]YTFvMkv|36vcFExa]:DJHN>07EQcc `gj21f"?d3{ ט63rGt6^6HMקX" R0Szi`8tgԵ[@ VQ4G>W<1\`A٦7B seX#&ґ~RK2lc iI =x;./7VoZn{C]ƹNɅ}?_qjQ I$L.,*1!e6eD-J X>S? Sx?£y }kK X][lb37 0q7сEx-Y'p.](~#ٹd mkޡNe( *bkVѐKW $ Y4,EUZÂ2>x÷Bw* : VgOwo9d)MR$2zoc8c:jm4E~y-PȃG@}1POq_;8u|o᫏>? /amaik=\3 rCp $k_8*.hLٶ.Y:5:>KՑ@A^]Uמ>~Ƨ :>!pgaɺD9X!ٳ?tꟁoTy]{?5Ldmڐi9^ī=jA³FơMW^E&lrJQ;}7lw^c=gEp=DIiN#3 "|6Y:Br]hV4kTEZ, Vqݍqz1gE6ూ `|u%5C^Ƚz衻~a!Wuw)h4.ׯYkaKK/<NwY &i )M`w>2"M(Q*"+ֲ֚~ /L${8$w]W~ 7^B;X- Z0, z.:k&m$Y ml{;J]nw 1YTd+:OcQ*T0uÏCeP(jw1 VSCY]ơ#&3NJt -..w/W.D*?j,i"s׼O8ԅQ3 0\}?xgO٧΢hRB,6Je .dvPEV[*%nʺ\XOZD+1 K-B,b'`J*1Zk3^|mb'6]:R&=O`6>2xBv E1!di <GҍY&E|vVoE(`e}~cՍ 9<ēx+_^bv 'pR\(q}4T Z Ԏ`EL>Lc'vٟ/QńX֊L;[JFB j6.BCS̏e1H9 !di:Z8`dt|ʏ7j-X;tq \gm"MX !1c@ ^@hȊR(azArw/iU|kOk?gc$<N-"ↂFx[/kЖznVNĢ<%4şՊ" VP _g|C*0{}q^0o5>8 ,D{yJ+]Nh4v[P k-`0C޽ŕkW~=_n&|ߩS'g~L&Y٢̀eb@v$@ҔQ0\^XX;ҿ>@\w?+}|\Yx[/ǟsxÏ Lfϖ=bFVԽ 9 `}q'ˋKl'1p֕A*96p}w]o7f(t`׫7EkI#KąG<}ϟ}G|P[:~BoaFh4*S (&OtYkrlYҤ XJ 0B:m dD|T W"-͆ön_|/@]4 *Ԉ7HYT'kAuP<u8q_0 4{P}ݬk-ܷG>K\w>8wD¾c#P f u^ I2 V(*UטּvA8m,$cTeWb]}K0ný35hR8sோE ,JYxFS}ϴ8`{fUK޴ 5&Kݻ#7֯_?rͿ͊{W#A6CBn)Ł- ;\ !sZ)EAMh[RځMˬL"9,O}f*xDNa 5=y >[۬} GϗrgT X"~ciX)59- PhW>̅E{q]ɻ~9_{ -.VzYK*FQV#Ah"2<^Fd@ uBTo>Fg˗\}ؤ*W;:׭v*hGuPeΓR1ոu\p ^nC-`7Rzs^M1eK]XZ*܏×?3ioH0̛ Ff4kRC1Nx]%ߒD"I8|JFi1΅[&#|039ڕ<.v:koFS|# p_:d ` ľ]fm,qEQ@8x4,ý'j?b- oiZȺOS?k˹>s:guƨMJXlJd&F LHv3 "<>-_h@1#sz\ԄɌ%k.}?_.&lsDr4v2 W-y3 ӮyF:9B}8s# p v,ƗAwnMn R(WP^K }VYJ2n(* 1:xSǗocG<\ 9sp3lr/fZ.j_A M u [^G@@l 7w|>]LZ]25>˰\vBmVv;ǒ71Zb8 Ճlv =QGȀЉy8_Y2K1ǩSյoy L}dnUl(_zkX=e>k?I b#h4Ț 4#l][ш0 R2HөSe&,U3Z@$Mr FvP:=5]3U_yhkl-Ǔ{OG&&:C_1M$ o n\VPN++_.tVe|?1xxs{Ge&6y"md*.8XgD ֺ),,J' `K!Q@6i!xkePay V0ZA4 [=gJR0xɧBXJ!f ݧ¾:u;Venxe``%^N{;"k i0<9ldp4a:I u#0lwZ"  F `\>.$T>]<˸+Zx%i-D o`Yl^ڏSR!T--R1s䣌a4P˽6Z IڠyXěY `ׄLqc5ڝR]<-SNbssjb0u!FY R4MȔ|Au%0&d&'#h] 5Dy$+ǐo,PW_\cz/}3%&װ~E 7)*M$bAp}FKHx#no@vz=k]m-wA܊x1"Qh5?,~[]|-C}Q~=Ühd0K$h6H I]t0(M1@ W2ϡ@g(0R1X$䰰@9cx\ <Xp&]MSR90 뛘% @'ɞth5p[F 8UX%cǚ, *sni룏r4sb}Sol ,M 0S3 '`4_-&..l!|Y\=,Dkݕh.,# mw`%t>A>pבO&d=pLvWhA4{LC3P|ͿR I"pt;n|)/dY4fwW'zTsw k&+_-w֬rq|0NΌƥ ! Sfå% qA@k0*)`MIw`#l\ͫ e9L6H i( & 9v`2ot0V.Tw^N`'\\y"IR^?okUo5+7DkYw)I~Bm[W M' }/P`̀Mz__fחW5ب~cþm0ƅ(KpLL$h+Hi>Lg-'Kg#d ӄu?CjCfa4"dn!9N1Xס)c`ҹo:t !7_l(-"sC,K`<@3}7:Nwᅵx4k'1 01h`!$42Eb!i!m`BiDžtb8i tq io Ig"9@gj,#L/a JA+_͕23pLEvSo}`X-cDVz_Hl~M1w/v7G,@A8/Q% 5e-Ldցl ;K`͖$ǫ/ .c6|K/_@R:|7[AǍƖx+KueMyvpDKWG.S׮O<B) 3#&FfI 4aeV$4uQfv~@?5cjGy ֯o i4hhtRh;M ">VZ_OV:m误vDrw &,faEarZm6@)ߺ{Nl Gi!E>^꾿˅,zZ2WsZ2ɞYXً+eJ7 ?Tw-L$IV*lfT٘0 t٬- L1f@Ȑ!G[g.>N~qh.S `.5!sG d=k>9Y  /A=jf {h( 8EBX'VQ"#m-f~BP#O}d沣Pvx‘v^< o5]FgF߻M{Jv&Cr နIqL'ZOe֋eV歟^\±)hV&E心R-BnGǮMyji9g4_r׌N^a #_AHA&j.s[޺eNs\n(s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,s02Ws,?rP cIENDB`OSCAR-code-v1.3.1/oscar/icons/dv64.png000066400000000000000000001710331417327530600172110ustar00rootroot00000000000000PNG  IHDR\rfKiTXtXML:com.adobe.xmp I: IDATxymYgӛzz=ҳFYK6f 4 KT*UILL`8JhrIH-<^w眽>1YHj5wuݾk}~߰vnmmݶvnmmݶvnmmݶvnmmݶvnmmݶvnmmݶvnmmnWzUK#Wx,/+=eo.w@KDRʨmx<oz2 ~l-;o*M J) `8iva</S)m7o3h2YXZXƓ\YFKpe2,㕶-B~2tDdȵx<^(MQi2lJiJA@hT~*,V FE)򝉒Q4L%Z"PY{؟U ="Nf!}IcfbMI!LX@VVسkf騛{ӟ>`g/FaӶ \+Z祔AidrKKKK[F\NYXXX\ƃn>Fa;B6CK4m FMSh8"4͐!P[0S)E%"0`g7B#0i!e  u3Р0X*GfA#*r[B2WEosgՔgecTasSEQBDD!볈bz +g03 `EABU!0:vǩ*nOO|ҹs/eC]w̛O9zCGOkޞp4\ Tb!j.J;rePQc!V+Q¨% JutI :Ip(a -fE[ b?S/+יГN/Ah|؟nШd<l"e@;WJ h&?sF$J hTI fKzN?o:fZqi7K|~?|w[ٷwRhX UG D+Om0u+hĪ \sh*8EZkuv| ѪbrJbq: (y5B¦[$N 0E=wO'x:>" &Xv*$FaPݒU*(LƕFބC{ffP![g b2ܘkQee̫i MF%U<hAs$ u[KR鲘[qaBj@Э3'5aO. Yb DOJTlM6 sm,VPРDž>v]W?vzB+jN>m׼暯i_l_H.! A@S(>5aý#] XWYcBd4I`B}~w mND!HzD4|@0np : %Fy5dn2FaO1_c "iy@hR4VahXҜ+jI"4͐Meepڙ2))]yV)D;nÞV~80t* Lgl@*{(c*YgƤ. ˉ{ ~J)>gR ]kfc&"c\tq"pSuؖCK+{ nYPcVx 'y\BpQߖ`ad,xp$ƔŵiA( I&UdB(~x@(*/ZD.\`H$(nuվDZdMTX\JXnWJB,(:[sc{ `0o~ǯ\wN4 J)X,m¤fvBN2D-$gJ & ʆAEuFȢ %ʧS#5:0ۋW(@*[G s1HO( Y(Zd~F-бH0z2͋ȕRE6i T6~N h.7|û~'~7 3]/LҀݭgRd^)$shZ,@@ܖ!:Q޶lxRP *Lx˗;Uc)-$) {;'FGWٳ _IʰESLs!7k>c d"iD-xL|[I)+F%[d,@Z,VIR GllAhV\ydX.eqMt4kkO?L&9oe:mũȶ(qhJZPpSf] m T9* 2`TtbdaM6so~HBhu3mQ`t 5sDC;'J$ jQ=%?FДE ׸/()#4(H{c4YKI61XJiBI!.\WhP STwm ^wݛ?k׸浆ETMй)TXFqSl;5+TODT`|}[hTx8̱9(=Ӭ*d-XeE(@ҪR #PSFS -QȮ}sRu$= v-y8MXЎɧ jڞ3 혠 /Y%i)%3D(Z$LFQ`۷]/'*B5ɟT y2f\U3aS6HpU!Гz=\@Hq^!$fYT0͹(pBI`9KhJ Bo>r+*JoB6Q!4vX x5^K(nmD.QKŹ64Ғi PFJm r>J/r|jC,Dr@ مue{!׾G?8p`]Z[ۅJ-dA+ ƚ&*uӁ`rԓhaWc3C@y 3UBvU"0fMйe%,Ca4eg!Ioht ʫ̨#L.^MTd[iV!SFH ϙc8#|om>Hb|jV<  иA;;f g %+dVXvY:`ѕcDчt=hZ~LJdaϴW=7Y\;s@"*!=L0WZ02X ز \$R> }ooӃ`jqy)푻vd4hAxӸ%FR`UodvsDwt,11>4&qsC_Hl Yl[n P?޾)a+͢j Jv"A_q`CX3l;fgg[!>:W<ɑq|=y[saȕK 2l$#O,a21G!rAGb9SaUjslk,}ح՟ϷaGEhޘ#KDɓ_=Uxxmwaae/TE-BU0Ǹ@'zC܆`\?KfeJGY"SStNvf>G<[up.B 7I&A( e7! geA t="bRUDOS4Q5%FHxD+䔩BƏYA;zmqM(BCm{8W+h47xY{TfHV%YF^!b@BEf79c[?֤Os=&j=) >OOyu.0؈ EϒZ6,hq/o!MQږ4P.ެBC9:`3/3 ӓ~-Ahŀ)PtӢ#苂 i|3#ڭ(2e#i暓',.}If~*h?s{k @隦gl4`Oض#{0bo63scΦYÕEHq!` LɄ6!U DY11D;Qz[1wCrjOZؚ"I2g\Ҙ{"R; n4Q⋞A-wh{o@Ӯ>ƫN_{9v PZl"`4b8]z&\5hOMkN&9u7|gY $Kcڞyr%cg .0-Z/ғ$L Mqc/ޭ˿xHهbN SF.QO+x)Mǂ@l8!W xbJ- μҕ+O=7u{k&IVkՊ@ P34kA&DR0--4HEɏY~:?O Xs&j1"ǒcׂN[Lu()(]*P:0dgͦ IDATd,<2TU2?ݬf SՕ))4!uV&cBX:駘o`ہ\8k(s+`uEj.H?$12-{{ᦛޚViZTZ/OVc$))dGlx%W,O% Rc,9EOZXddY"' m^""@* cO*=%_u슘 *b-lmC)AI1fT<~P]#p BMQ#no?xX%Qa۸aWgnv"^9N~#n喷!G~u]د!_F[Z,L<[(ink\;'JMp07D5" $=0 \1E++TElliX!=EnQ hi?]?2t'Ԉ`l#b:0+Ò"y:*eIc1%թ/AXa<¹'źpBQ]/Rg{}O>ӟWi+ }z*3;0c^g^)v NT@Lax(Gt98a -e3i};F4xS"a88[v):]`l0G)Gv jɩO`;rd-peTr ThǟQcN$¬w8QzBW- 6 39,yiyⳚsR4$c' Y_;t J@ odJN(i|=~痖᫴͠iwyO`ͦll6c@H1(t0mՂOS3@fT;(civ4.E!QJBCP }7acLQ ()jCX9܏s*Iu~M>o3!xCjE=7с,)绤M5X3Bd*Fה(@PK(92,S|3UǏ'_3RAKYv0xdJcڂp쿀܀U!o. _ ²5J,e""O we4*8к }3r!3(iH֌wȪJ||Kh9W}kOuʕgzɯBvV1B+B*48DnT2'. Nܠ 2,";WgZL _ZŐePXSU!pO:w_>P+ӑ\>VĐ8E(v;u-x/b'L5c5Wt 2G^CNJV7.;k[tTZ<#ˡ#G@ώ՘p+@Ui=ݾ= tLz,T9re?D/mp::R#4P2A΂7Hi梔ӖOVkUh xP1CF$"Fh\`U)430R+}WD[1;,Ŋ(3: >*Z$a9v)ށH8WEC&?9!Q+KZtC.nAϨ3V2eSi,J_@}*Lyi s 3J,A4%`p>Ea:Vء3bMr@{ߛAdbtgM}-fbKjUfCz|1/)̰ r^"NJR޵z>&Wl ۻ~}^Wx+V^;Ԯ=+f)`aMi}rr,z `N@c@`aaz A;f:LGJFUa%yPB>ݔٍ8vh4ڃWpk`ii͇ÇP3h=.' ~sю| fUW+G"MxBNt}MW .>N g:"8ԲABCY2,nD$1^L@}oV>w+1= Ca瞈8Js@ Dp#bqn]A\fN>#91؃SbYdD7=0==f؄ s|Q!%zML(NxIwN ׈Z>!BWҋo%J pw"mID],x%6~$ WXmέeerIǛ,)}i't_WFV$*dʞk&R0Q,0)U uYL`8Q(5Ka^1HN" Z3zKC`.F>m8 !g(c/ִaV` S&0n3#˅xeE%v[XG-M\J;W$T(5J {/E.]@ח_ Qx}:7zFA㨸 d'A3 - |wo l*׮b[T|Y$6̘Φl<~K`Ɠ(.8WeА-rZ FBd˨ 와04Ў{$6B /[9yٟ5$${l?70Z?T,x!%J {vUwDZR׏%tĈqROd>qHQoҭ#p4,f , (P cHKf;g}S:{PbeQfZ#\#}2Md/M* .a,KJL i:u 3 * G? Kˇ j.]g|&dft];^wI*O8h;5$%$.f_-'W{A/y^J ɇ+`"зj~a I4 P@R>>&rGctєZqޘʂӷlm ?@hR#7ށ2&P* fv_񠱝E3L0!CCPiՓX7 9j (%3HQj uOG>c<O^1km#Gh-OXUX|xQ g+pIU-9U'5#*ߴxrH8oGXvD-O\/+b zϰ'ov% j0hIy*=Z5a(E4[P<|ˑ|L3 8DPr̔qY(~ cMS2(.xv \L":rЁV|{-3_8" m9rhD+'4M4Rm,~ m>F|Ӥ3m[~i؟*| [HY< B<ǭ W i w}}ȞANm$[}m8Z& M Ia19=j]U>we#{4 R̕gPU$B*nLKW̔;{ϻ|@ LSNݴU}}@pA7ͪ4*l)pV:oCgN>f'fH+0EgS60UF-lovVvb40aPΙzfPt3N}޺l WX:)R2Ah? 6tn:x6?PEx6I6L' *TUB`A='倌jb}=V,Y nk03sx^OF`Fͺ~z]$UÒȽ tOd% |z0fSqey_T5۫߭19zǏ>LU`!ͻ N7$ӂwO|>J8*OLvN>m JS=zxayi {.~iJ\SOt5:k0)4(wVfdKKVBp_=Qrۣj1u˖@oY$X+g? p"S`&I^1& 6B?|p!*΢@ -GNNC aX檀#;2R48'hJ+DSgv!!T 1z~dЙ!_]1?Bđ#GNͻ<W >x{c|mo>t}¾dE۶D'4n};n4soZh^F~Qg;,GXR652|g^>HᛁHgI쾤n"U>eؔ OI 3O=zqosKz~?:ɽ vuUξxR-PkHųUvF+=0PvdK1nXxQP^> eK:pp?1JS7r{._MYWSFl>C@wX+c>2jݺV%k4)@2RͰu%]N "}Uj=!risؙǐcp#_?rCb )ݝrYsWVhf4dGT4Zjm '?8u`Nĝɷ> V,?ʿ%(O*],x@郼onu@7Mg؞Muo\| 0ce8} {V6vh/24~bhI,ރ 9?3?3…_ >ݼn3*6S jRc>LµU'(dSjfT&ee9`[ V/W&l&i,ݬUw20,7M֤"ҙ}2""pY{krYa:BZ,6Cu3}wae._|-D>\ܨlTA) +Wݳ`tJhl k **905NgX谾+z".]K/c}m[n>W99A9t@Z.KC`FCD4hf##8|u/{jehj踫owM}M]yfe0Љ c%g ة D -Q#bt7ۀXBUTb&ۺ*Sa(,RQH?O(EY%2UP6 Le&ܜI9Q?W,HRFφV`1 9c:X찱9l߸+X[X|ln3J3ՙOU)R0ܡv?~w_w).yOdH K̢$YPEbB};oOW7W4+hQ+a0`[L*ѭ.8v2k Wap-|h[ʺBb4WRt榘U5t +WM)-3$c" N“Ӫ,YU NA>h*X,XG%mPFhlAOObU~w`P+c:.^鰾6:.]<+`c}\ls 0odMpc],= )hncP* IDATԭ{,^򪻎+}!|⓿{ކk/<'EhɧA'оrq7Jrq5{;矿O~4_.1 Tf4ذKi, lG֠qGs#7vf0xZ "Bk`Xyg]k3"Uוє c"p0 P7UbJ.Xn M"W5&@cE Bu#V̻ͭΞOC$X 0*=gڬ`rGo {NՋbvԝ)Cv9x#Xy]ػgt\ 2-et  wZB!&, K>+W.߇/aCΖ}ۦ`8ahgD' 3F 5a,y-XkM 3޴<9>{{)'폝i0Y޻M߽I0ϓ.y .kHdia Y[RHf?^3Vģ?9ŽO2.otxasESlqjwN@t|Hi5"ҢY_S._4xX|T,"?|eʼn{t {NexUwkH(\Un&`8{ȑSZDkʞmb߁߽,MuX&q]&04ڹ'ը}Rb}p$(++'Gcq<&9૥* Ř[9G_O2 )ϝlUq0*He7[0fmV'-! ܼBLqij_xc#BFYR&()(Jp Å#8K ڳX>vQl,Sܘ;LױGNI߭szin 2ʔA @Ǐ{?/+B,/k]v*a2CbfdJi0жn^H*3*g;L@(k\ӈW_IjhUu D# fD2aZ%͕1w9._+Xߘ֊a;6 4.I}NӠTVJTHi:e<`/_B} H "gƛ 2#,.g0Z܇m>Qb:cѷa:e(XgΖpk/k2sGӊzknzӔcw{>Zಲ=z_||6{5癉Cnm1R߈fQUwIi Tg[LSx)2ʊ]XOq:ߑHhye9a{^=ivffMoaks k\| h. ߉~ M!nܭvS6D#:Y5n 8Ka<'x6!E-Ё>!)x!,--ӧ0۞ӟ}ݷ߀xǞ~.]Ҿ,EH]һ7-aOh{O܉Kcy>bS N 7FGIp3lLms?Yt/bu uZȈ~;P  BSZ|>ssp %TeUK+Դ1#@FF^xCMe؎$#Bw~w~+;I@꺍2=ϘFKpfu`;sǏ';! 0EgCC! P/ɡHD1TNb8B xkS\Z-\Yu]Y:6ױM[MׁnPЂ. Fހ?ӧނK߄78oExÕf?`hD&Yi Y-p6D`MS;pZbW8ss skq?ĕG~ ujPP Ϩ2ҳٿ-GCNZ΋\4|<8s4:|ϣ)㧎 w 3{-7_V@M)]ppgNpϓn_-/ǥ ZxA΢Mi܀K eef` {Ќb0XB;!h1CCh +hbl*V7;F '0wN4[pv(\v[~ s .]n|"w̶gE9 1+o@@WDaPxea8_-` x 6 U6*_g…5<||bOw@T+e D@b KGnĥGç8uf #|U)Le83 T)1\ùgoKp/԰rs *;ogy)̩ԩwބ?>;+{uqKiB[N/㙗pabHgןo_r6領fj'`eeed?hQ4hR uH'пn QxP 4~lԺEծ):lnl{olU/ 4-&G0\hNƇЌC3>vxa<tρ6̀Y'@-6(]ADŽH0H+SE , GoqV6/ v@p_xmi C#?YVm|Íoz[fu|-)jWj.b`5Ŭ@M^HnUT"ݼ`0@@PhAN?i{>m8,A J+Gvmot]E 0Z| <$TF̼s ֏VtZZ h>aKΕt5l=C'1{6"Nnv0P1۸3{_p@ѝmc{Šp<ȣa%Ң @Tp⚓9^.NDmB62=cq5x,3ƾwaa@iьi4Ctfa;¼] 00hZ mRJ#6QgR "߶h8B[4&,X `<C hnY9"wkfaL![%lMT ʠgt񶷽~w?"躋]:}6760f gb:{UX !ME6AOoIiD˥o\B}.CC\ubE-w8{1gx(gUD`Ƀ=0BDؾ kca),o,(gĠvrF {X[<[{Wo蹛MvHQmRH+b,F"#I,e Ác$H N$ H@A 8r<ñIf~Ýsci1a߽g5n}6rªx2~?^|c[o3"/k^Ҩ%VC~}ϡ_,2xuc=~_yvȆ!O҄Ju',ezOXw[hҏ\wCgRdE9^ͮm!$KAUd!n܌_ԜULrJmO>3g~/_}3\]{sL)#p-4cS[" gy@c4*sFuPw6,85k_atNTb{V@sp2cv@:}_QE`y`@08g~:| ūHy!<,68_CPljksQE9'}>x& kfF[Gَ_i8w3CCX-Y6ȦzUH "tDx#?{/߉# =aX)֋ , ^[y;Jډi몇Q @5ʢNixY2zSZ̅fO}( NGmf `;]gEG[.j@fFF $U8&Md)"u 1IV Jghk Hf4٤c%*2읦`bnw8<:wa{~( l!8Hwa0|Ɵōn?Gq縺 a<-X= Bjk#ՎZwKMEb+GmnWx͟6 V7pc,V}Ey9 ꈪQ +r=O;|ۆflm@TnTW 3[0kL[hnׄ)ʻP~=MOsu-PրRcIM$_`џyů|}70 ǔh H\j;3멨R^>_rBH#:Ts;ZΤ c齊!* 0K'f&&B3* )74EB@{Ny<NON@&XmB8忍W ~7s{HiR’D -C*3<(g` #rQŰ?sU/03ZdF-է lNԁK"+N\1j K2S#  aO2V#$k*; B&]zg_k-;U PdJC[\>x@[A|¢_q+= a bp@) :/ 3!c̥+SCҲJ9M ;:V0 4 jFU1mjuVŸ X7@@r ,E=JP/ޕ7t MPYkj5zmtd)\C^k?OWvBH1#zSb$T3` ș9 3\89\TZtʮElPT{6j5g fct#(P:?4D؃=X<awrqDD 8M|WHq:%E"C1#eͽ>[ `(zJ+/USQ7cH͵[_hi~i6(B׾ӌ̳`7z:u9 ǬePEX&(?K-I & m;'79_:~G~7?ٟ_Eeoz@Nz=Ō8aoG{yNQXH1" \_W 1ENTcݵBAaM c_GEH sw/aq!l[5 Vn!j),r{ 谳I#0FM^1fh؜$e93}ȟ{E~y9%ppcǃ&uB [* !cք D5AOMQ1 WIZ׶JMZ\ IU=gxcEx",iY{!8q,<$fagj rnĪoлE8$RǯgA9nm~ȖBE]o&#St= [ya%@lڗ,@-_!ߵnhޡU@l EhC6htʴ@M*D _|,%OF2GPU)1BHp.h BjC_xK4L%hTR"„iZ- NZhˆiBL/矟. f2YNR@#J~JzIS!Xfl֧nD6!hR[f5&2&*LʖT?vq~0 Orc C'((Z i=mYjIr/v4--siYX󥂺z/ʪqAA`sp^6HWv>pb5k`63&,ua"̋ylN mfnuMkAgQZqB9SڼQar/1akZ<"Vv ݣ -OU6SAVSFPSDj*2?k+7^{M S121"\ZMCzi}!4h-} ¶vznJYKFQԭvzG~6)t6Q* Lg}^kf2}0Ab1Ƚ%۲̵l̼b2ҞXdn{{"*Nz3uHflz%P篫J+ turמ-q~vU{4;EJVH'tJB&Ò\H`1"ib1,IXwϯ&B )\7|3n/c&ݘu )F0IS gUL:{'hZlEa5 h8 j`<" t))uTBld6Mg'۴GM4B1ee 0[ݤEV4C$-dBfPuyTd7h:5b^& %U޿2k{ I,X4ėgע]{#=Q8@@jgp1!XTr9c% |XtTR>?z\LH%u"sc8!N#4"vƏykwB+a ?mZk¤ڧT&kZę+2(?+ov2fW߶csA@3b:X;5W!,3f^RN*MSO=l0g]٬`++nս++O/.B}}f~D\ C8[Ь􉩖L[, ] DhBrmޫBB^! L1H:R"Cl b=${?O?/~:?;rn=أ?_z;9agg;8\-2sa\ 60u{R4R`eJmu |jji8+g9#8wX'*d^|Mt }9u֜lW=6C'=uv?rG:N^SdIS~5uE75%{ [84*lF]e$=g:W('֯XPDSOk6O)!3#" )I Ƥ'rlM;$,轴o'[7oݼ؄嗾_F#)T(! 4i*L,fF"RLpR[xAv أ3k"* ̴'7B+*,Qa2AW Cf6p1LX~YeaFܘ=LtnF}ln)[x8UWs yD\jJ X=E/ftXtl8}\Q%OE][O eנK(̃3kgd1YHPMljֱָ،,7XW^Nl?#?>o+KeθGY$@zn%Уi%пF}~f^64$ЋZێM0 ƣnIآe3%yyو])3F{KST55ݘ.׮wn J!{\}&LR-7 @|\PakPC ֿ}B\3gW3-jA D׼*saBNEGRi^bu@ l9L fgC9%pJH)a#B E˿b*XݢӔvB!|_??'y<(x/t+㶟qBN#A5߬AzRY YȖʽ:3]g^~Y(~ܬiԱYn(KVB4'8rr]W's/*Rzm!BayI-cD!l%&D1 m0Tg=bb:z|'?x\bJ̹ƑHiwv^6PFBYO|~D[\Y zh[<\D^=@?;5"hZ 8V5cDƎ,]ewm}ݝ! aL~&NSt_/B.O܎G8讃jRB 2\ɹ/M?ajE4_WAtPa dƐlS$Tt -Td1 L1LH1 ÿ́E~߻^ 4;F˚lKpX,O9_56GR\$kCMDs tus}fVk*BZoxQ=M9VW7h\7_Qjۚ`Xy3IϫQg\ɔ9YބAu&00#Ea#4a'q"ĭBDоׄ*9J0،WXwN:Lޚ~#G_d_ Ui *s`GH/%ųk1aLR`wWF v@$U2P,S;Z x@f.P*3XKS n| U`BQ4o8鸮GFP#9Օ}3E0펙!Q.j@S'$NpH„2\Gmg] )!e _cұG'%XtQW(=OZ.ʢQv4kR:(6]!&ל5}[k&lT0E;Ftm\Ulmueh.׊ӫƪkS_ޫڍmxgjJ9pyu=}(G+mϜ;۪ݷk8e9o%A ף&䘓.sZ1[Q "Ȣ9gpH) [!!E0K6T5wDR&l%B x:O>۝>ϔj1 Aq" ٖȶ!vj*h%՜I!OdwHjTMjBe@j~uM*RQ4@+`y MeI*#Ϋa(\ǰV_{˞ܹUh@ 0=z}桖oWk)*B ]w€j7ȫ BƜ銭!0.'Q4T'f x<~d1rMșKzo Uf}iGΟ0/ =0M9H͢wr1]pgGĿS%K!*a&0B /l͔s9@*W^ku?EW>feC,=lY1HA\f"V9\\X 5%߲tyk>E08\<9:^טt&Lsk,  sAhSn\|\hr.=FM QԌ.'J!y!'qh/}Y(Pj:r,7@X5 24 Tx0N,ub}S:|&&W;K얓@q3lz*:ꉳB1 T0q@L2gt9hP(  9Q|K%7}Z58hp+_6j"1[7cjL S[B@AJ-5 f܆bG]Jˍ6@R &MլTꑔ%1sj(z8=VӝIMwղK=^hNLEھ2M,8i#MDҚ y|`bX!m:nݺ t@5;~$ KQzg(:p臲S9=\!ʱLFV3:ԉC`(klUwP qCcC*Zra]=6Jͼokʞ0_x֍YɞU?8yMcjwAa0ld) U+d98#I;%5!%iF̺Z%pN&A[. 똔[3ۣL) 9!C@pyg*k,B6Mi^ IҢd]y-g0NZB.O ޛ " DdҾ<q9Ѓ2딒2ibGfF9$Bl UjVL0{Vi(N6'5m#vƹn-'PLNu@w&w^UmFZ_.EJe%G Zbs %4-)OhζZy]5h9@1MknGa:8-`tJ56,sv O-_ sEdAN+'&f(n3)4GYSY~+l%7Ϲ Z8#sҟ 2|g_e^4*3RH`U9Ob |r{{Y0L2-Raf 1#;J;#$1ʡS;Q%1>u6BdX?oZY 5kYHֻ29II|CRVQV}H*0CAtꕧ n%E)$lSBL%e`܌7) $\'tp;&U IDATG8!9К6N᪗Lr V m@LA%N -[n [b ;`L1[6Kf5x\9}!6܌ QQMտdZ+r 4H`%P=ET(hF^9k:'Đ0Q@R4'-B-ϩޓs9Sړ@ֶ2Lյ5T˔tkhT1T e:GsTe=-J s73A!,JB%sLR r,-rbyaJ!{30ϊcJM1EMawK!=j*, 0s]P')(IoWbEQX71}M0鸥c^. NqtoIh8kFc2T(Tv:H1n5SE bV& j˿gF&vs&7z1K%faJ%jjj0zizD[AA=7ڗȵE!E4ŧYC,u|f_*uM8EA p%ΊIW\)- )UpGI"b ɎbWKLSZ%(HE}(*sBiA TLxVU]QR1DB23B *'E5 J5PoԢID _w?BFѬ3.B!6ÐME Ǥ4Q T}o\$ $MB1IzoJ 1sh<ࢍs21٘>8{8y$#C )- ,w `cLc{ U PBTNVeֆE8PAr0F2t tzFd J:oD !SUa9'qDw;_Ob|Y {qqqxzrrM6w3$^Tb1)D#R8VA4LRfr{O/S5JRk ڗ\ْ?Rex/e,L9sFAc@N# 7!l:xwnȹE.r[+Qט05OCjܘIEZ5- D)GiTİsCqXd0(KLDcyӑ BW h}}HNQ!1%@\ї9KX]gJs%H-eT[:P3$?"LImM̲l?gk! uO@(g& _a:z31ZXmkf24aG^(󘶶b2*dB)ƒ:5_:i$i$ǨSmpF QFD8$Y0rZ*WDaG'.N0XPGp%p5&JRԃje ճZ20%&I Ċ,<'=[B􍩢*sV m&%8?}G8:>­[я ~! Aj.dnWGfJ5gCYgY4kaфf}nFEDXGQO^@sq攑 wu`pAb"NjK_'Lo&8*.⬞;Wli!F΍Meq V"h]SB"Kf2U{I퇣0qfGfp `"gu,ZU$:DNs-T^;Ǵ bzkf8xZU㨝n&@vx3bТkLjj&6fX $$(2\1Y&8RS-m88߿cͦv;H)cB ]jGL+Ɵt1GkNE6sBvB/_Jg;u5TJ[bM$4 N=b8^9yH S Xsd!aꗾ)v0aNN(suDZ8F떰C%)xcb1KΡrf3#L[\jՄ&İEN)Bh'p8"dJYY<6ڽU2yE ijr)A3E^q:jKLru҉cjɓ ?+$Aj]W#Fgj#XY)!+eus+cY}$9ELa)pSg'BxS&MrA\\Ƞwkrs-OR#7) }$^yxG:_[a^})7JgP"1_*sapD\#)&UnG/Sg 5[kofl7[ĐLu@3J FD^JDqux"XӉ\BPY)*YRƧ@"T3&[[{Tǽ/I21Oq~qifkX Vf" KRQ챼fB9$ Bp 3`{)P{vĊ|\ .~QmP='e^&FوWfR3 *fNJQЉBv J ]?H&E8GxtH)y"rkU͊B8aF0"U:8a$D!̘8x{W5y vtKK .I.Ȯ fY"] XOYnO^ѐG>O\BL`)<:Hbf;!Ą`.h]Bш 1&1,XӍ69|t9 |!猤ȃ:uG=8㺜sE*ē.Y&tFeDZ-ZډFmWR\F&peggg8<kV`r:a i酮v5n)"AN&2)Si $6bq BvR#@Y 6d prRqzSIhxUJ,ΖGdW "|z̃>bci%Ż~pqttsl6?IPacKfSVtfΌ2'Pz Ӕ1M (^}q89rѦ Ǫ裸-28$+>grԮ/k" 6pS j'x40` Arw i ~׶ !xHRӣ2'MUy툤R6'ӍQrI,i>7ПBJI;:Ss@!Z@AwQ6Pb%'-ͬB3rML vL"&FT+DW}Y yRĸ'8<<߿{xVEkڦ[#:8"Es\*gl-\LCH*6フo6 QM~>L|NҵS05t]l :q~qOpptCG*WZ, hj' _"dH9YE)&C~o%R>K؝$)JNx?t(قdg-2gt:Z@LXxW[fr$uύ5H[L'xr̶ t DJxK$zp; g]Γ5Leŷ3 NOOp_g8>9NON oS&fb!]rJH x Ol&J[N+YtŘc;Lcv1 /<K5Y&LLҎXdDiNB}6민)|:H,aK2&mF,9 9j݂=vo߂zX~h@n/oOc_*^:cj$rDmҌ@=~=7^>d=ɭQ!Z2Pu"$^~W͊"1{7uR+O4kZ sDt`Ro\ 3<d-D=)r9A{7]b$D%Ey9qY1H\>%A!H4l)bm OblW GV @ѝ{0aTEz;}w ͟aE!T1&,j>Hɉ{{X.׭˟g~vd^o%BK̙g=^H=^M4YspN\O2Ick7>i|9'i5DI1 ''{ݹ_\["p+ k"Vs+C҈LC6 S8!#rahtڨH=U1k'/ـ4#\ZNӇb&{q2Y9:B̵œl :}] 9p/}-8Mod#1:+edRI;< Eҋ)t(q̒稤rRLclFܼeUQj-u@5{v84:l̬%YZF|xp;w899/j#=&NP!( $;{B 1oLc2?/| 1掻-5Ccw1AGu@@Haw3KXtvv"*B2_x8_gb9` B |ttu~k0YGn:$^oZ$gF䴆D +|FݨCg6dM&v(GICR'@R$#Bp~qy m܎u}\0> goM1&bc|O"q;DkiH1)/Ko&n>`w&%\ANL!EKl.1]!ǭ ;˯g.,?H̱i槆IE2z-,=3bO~0u~Kc2ŝY/4!CՠY@¤^a)\n.ptx{wؿ8>9^Dsqٚ}㟚$9!a22) /}V)­ 6 Fs{{7ĭ߆[nwJe-vZ6x N^#ioWlhɭ9sBL#BH:8E|k[])L8;?1ΝwݿC~[ʿ]/"B?]X)1rDr)"La"fK_ ⭐_p> uRx'qqwcow;e"fmW\ad__b{ng;CxG?lܻ?:8U$X";Y`go@ ^|{~ pvRh$*f<$U*'Ij֩$GhIN S Xl9%_惐0B/_ʯ⢄sy;0DYqnjq Bf_Trb_Ў*'\? G=2K^}iaX.xpq܁X3ϾOmX.xj|88N.^D=4D|Nw<1_'T?w \q;Jҥsږ4g]H1MkUBEV„dŬ.A:#l_Ems}\ H})FIr4F$a1d\m#bvSI|`~9ݛowK 8=}w_ĸ9jaowaKa@Ș˟Ž;/|~<#HO N#־+՗rf[7j8~3?mׯo&x;np&/$P8&fKGq`Wz BeCbbL1A ZY;c|MuIɫVu8BP)AC!mvp˒k(ςҀ&3=Ǎ!ùx!\bwgۻ]ߡ&iu]'Qv=b>>|;ކg,_8b\hSTi 1vXw;d|_Wjfu̴e^xN [&$ ZR[RsXHV78Bg mK$YjX GNHZְS+uG )b1J˜c)<[Yk0*.ַ a  Wqv2C[7vJY G82%55C:<qkw( @9f=F ?-Dduw- Y!Si}1.!spp޷Cc!Q&֗菞l藨{8xd3qJhkŎ6.iHG߰ Ug%ܸue* cT 0VF+_kߏ:t1 Xʳ)O;ebRzva>_FVQ-2$%XX[b}Vdֲ\xp=Zrjk kL\ ̉zrg)_?MIDp0S="Ǡtعw)̮AE>:voUXU:or{u&qN8 .`+KKdΩp"& J,5cnt/iB@+ q\EVMnEX"NQ@J%󩴜䉺Պ]1=#ZK̎s|K&,Dka"?_ eஂ5X.** <O3[cGk]2 +&U1+ ͿDG秭 FnǦG: oX8tP0R=2:-fF $xD[g/c"|` ^ *Y_&8I|XGD &bzv$)Ȧ"cuX|`zv~<ϐt,ծA0e\.S, d|8zߎ1@K6a\A `oxv? e^`p蝷r?#|160 &"( XlqrS mI-bryUsYkuUx>c3DTW==1}vO/ֿ,ҭ{v-O` 7> 9h˥ LM9~." 乌/P _k9fjNtxPPWa>A^hvL7w?#nɶCl]&ND@R-ߕSR2ztȝKO2Zfu E 8"(89 !=z1:䔄dT2 @@^0&612,%PDk޺'~M~y$S(rBl]hCdH%=QfLfP7by<$ VFֹXr`b) cb_.ͩaM$!I~\#=6$UDU*lgL@̬ŘJUu1"! -nTv -!,o~c᠇%V2z](ҋADg{'˗7clɿ%"pJ%wPKg X"F}>Cܒ }Ggns`"ekl/c ʼLu|ʩ͊ #L5é\sZ mA8֤u3HXf)%K016y1<,zf%Mb\W@Z3d1++9>,w>} X,7q!h920pĕ팅.Z 45e.CL#K% SD3`Mp=E#-k-l8P/e~05u1DCaK7s* Ƞ6#ԋC|)ݨ\UߋKs9PGP#F 5pY{u,0F 3Э`e>o5 LVa|ǹgg73gapΊbdUtMȠl!xCEP<$G!iX!6/@cVd`#3pAxu%t_ cba5;0pind6CC1Z3CtbV58>4I7/C\wW/b֘=ymX+Ɯ\|٠(,A>47C`@Fd"gA 8-s8888b}WQ")ˀ3 #c1!axk" -9?>K(Hb$!Dv_re1K&p9y?,V/Uaxp{ozW!yF/V,HKTH|((A"&`Fr'=;G`@)KPd zqZEQVenQ|k<Ε 890zGyt@IZ SZ<YX@X YxllZ .3%H VXXG}k>Y]tv(Bg 3k7QG=Lvsvu=wS-!o$0%跠QOby'vO ARI)2JN!X^C$  n1ldl5l1ϗ? Ж@wK U<]*ZS?98No@#upJe/Q@]xfhQ+/|9d֤q_Y$XcPd(>xZܹw^pbdZ`CL"yozKUeLɂQf"q%jɁж A>`(^9ckuֲ~ȲEV[A.z{16rʉ _\<(BD tߒ BધYBF'_VF})qֈa+CmeYwd_ryA}ta؆XGtT\}қkKKBHk_Dz{f}@ a! =w_~|z!# EPğA24j@&Nky0-X}V^d]nspC9yxZ>VJo,2ZXmM 2d"be&'ݷ>.ijGk H* A @%%[]BDu4ERkJ2!\8]z}.y&3D,xbX]N&yFch. 4TLDxO@-dtHs3O#] +Oz eP"G ,Gfb2b0Zq}D4؏pzvN9CҦ4#A׿k=uNQR!Ӝa?(Bʧ!w@1\^ =Fk01 ` `kcu(EDAZ1x>l4?jY )HZݍJ{`zΕOXyè/੧6PK B Ix>*2m>@lW}bXӜ7?!s[ LȦJ4IE!-," c"6 ewUwh̄%ØKXkX,a-.]F  10/  fO߼}xfu6;S0ox3g%3(V W <{>s)eXGg,F# Fb5g_GzX,F=)>R|BؔYf0]PQ}0 X~j wsIc`J;X&Ўr3 - z^ysٹl <˱X.$[l{Wɠ_h8!b\{)Y)nvk*F_@Q>~_}yΊ":[2 XV L&y {k0 Dt:]wƼ `Fox+G 3H>XIʁ3800pa2| 1 L' p :BQ,P5>z!z@ 0?f `sp8Nr:?A=sFXUȫu'eLszC\ɘ kAps'%169a\B*K2K?1JVOa'ROYe:9 v=q~( =X>u@@6(EVu|9*}ᰏ vuD OgVeE.uГ Bd2@wa8,aGva3mr8 &[{"f=g>FVq+ؾ}|0ƠhOٌ) p_G=?dM@ zUd ;X?ï毡(Tv6 pcSUUb4v@:o~k㣫EYE(F" n)N~>iuY,XVYpPX]]}XbWYV*j&vƳi >ysDc m6Fu`ǥu7w l\y. gpv/A,e}ofEf+/|ۿO\6=cE'l똡t^@:4x>v8=9eN&6V65HI$ Ɉ0 P@4ơ߻ ~VUU$?(αgஐAb%:lnO#:.NOXΓu*_c}u6Vi(E:~G;15lee8XOgh3 GY!1>k o.(AGXACT0H)۪3B \AH+ZQUùV+V6.gلFΙ4%n.}7z|2>9YM 0`A@3h-sY 2]ڔWВw|>$`+l()˜il['MB@p*)XGBd(bkaumnX5y,6drnpgC 1rVY$V/i^MBaBh[{;2bÛ]Gvq$ueb1˲Zcl>ދBoC? bg46Ȳ* %Qr lk ER+pF+cEcI_Oct`ae[R{F"YξAAӻz@ Ed.CQd yvZ" 4vX3zZA믿Wr IDATx@Dӽ CLG`.@Ry(roZ`0]dPh@ (}=1a$2W,_HZB;Q)>b+&|~X FI l7a`lpi;So{FP+Q cqdN.W .cC??m=P^GUxKDξ*hm=@ƀ ryD^Z]hQx0^ZOɧJʹE`,b0J":IIQ@'Y(e, E,sB0rP3[~Vqpy1 vsp֠ eTz7C7_~;G4`o__\f>TBx@zay87 #t_HIJa|"! $_>EW1S*H<9AdK1I`{Q :TXdYI<କy8vGd>ބ- 8*g)OQL0k\,<zƢC2b&e2N4ש! ,C1F.6,@ Uyi_9kX۸KO`}m kXЯrS_k >ZaS ͒P}I XOOf"/ eY%0EF)CވQnX$Yp#=:O<*[G:7?@ao>_^ R̕B\c%)zVr86jc0ҥ'yio`}mÕ>2GQ(U]3oA$M C"rdC\O2mq&T1z@Sya_dXxTEp&Qwf5f~dvS[P3+y寏Կ{{qހ7 YhH?Бԃ{r'K  &ڶ'Xf`eBsFؼ6/?d5F#e* EZ~Xvcơ* YbJ֣nǠ*JubiEˁ hG/;Y`}G`gݝ U>`Kjb[\7|__<d{?pk&5X8XXX_PDȤA8 bee >źߕ970ҥ'p&ѯz_r䙃+lጵȬVG~IJWw "}n9g%DgՔSL1d3dsVcD ws4tC5C, G)>c8( ,@oWvOw{e3s2 Hǂ,=ݱED#V6DA԰1$HFX[ ~+2F\ڼK7/?pȑ| (:]lQ* RyG8 ҌA2 /9`CPnXŲ8{ y / KIL C"K}ct:Q wE Dgçϥ[!1 6caMhA>!"8CS8"{%'YYc4`8駯ɧxzek"-kC(8,N iԿA;7Zѧ;FPq_(y֚(kA؋/k|o9BILgMt(_yQ j/"bdU3iXm 2l} (E¢a~x7hxa8*rʌO,G^0q| Kpf"ҨcwɛDY, ShHT'_ItBpּ3SdCYT!bQ78OQ%NOqz:+KTyYs@90֢efQ0O +l<֊k#U%#-]ـ㤦&+7=~ tt_ p=CD@;u3&JDsklRgK788]:T kkkظO`}}B^n"2˴VqNRv%]L,cÏgܽ{e8::wb8`hp9~Iְo +Л p4z"h AZH5R} )E&$Q$3;Ec012ҥX_]ǥ5 +( 5 Jsyu)|l7h͠l$̘ 0YJ~,s5"k ~1f6=lCLSyzYcX◾m@ p.5gH( `2F 2+hAo ~7> L6 FU~~U"$h  sDد"s`_{DxqӇ"^z޵k{u]E.#`o{_G_\LӾ( F ЯJ=A@t[",7 b!pp.p\O?O\p0@]92޲|llv,(R9i(⬗W3<à죪Je.,ӧ)Ad0jviˮL qxzr r .p<]`I9s´X6|-r &bR: }aQD0PoB]1PI|Q/=bܴP<,L +['t%$J=e 9"=!0Kq5]i%l mdF) "a cb`g prg:4L@aqf1 a4(1ze!"q݋ Y1 |Sc41϶@T1p.0ptt~ܽ7Czt>lMgmǠI;vXAU%1&عbX[[C?@QyP9`TXRySR@윈Q0%Y:u69V)>ʲB;)uroA-8mǢ[X=EĦ>olv!57gPrCe$d*hǠʐ|}c >"Oky&Jɼ6 9LFCX반1_,0_AƄ^[o髯[6{d7?)QXQxZΓhC@4:H$Yb'ah>Ƌ7o[63?z ;xg?_ V](؂!>3ZF24dd!`CpMU Cx+r8k|SL;ғmc D!p= )i6> `++T Ec3,2 s=ȴsRv).uƴD9e\i*nA'9pf48P RмI8,s=5)*}$S5v pΝknzekKwvv7Ϸ^3`o/eι'NӜH8RBQ;wS7yؼ$(` ei=܍]?dr9D_zy{f{5foHJ2xmYt))Bnӝ߾}[orttti{`Bιz*QMg~X=DGIڪ0&L1yT]N:Ⓩo>XyO"l,-hڈ.tjѲ(PU%6-$zNU zNbkhRe -|rZJaЫaPڬOt s@]9& >1 M >H1z>ݸqoo}s;g[Dt_j!2,ȹT̷P Pl:G@Fִ  gA{Ӈ gâDY1*3#nݥZ8X).vv2LOf0[Ǐv*ֹ2xxzm)Z^tZ-'P"SgLMl$k 1Ie}ãQ~?QhĦYΎnݺڍ7_ܽպ×zC4: eDD^67`{]EAHQsy3P0,X Ygc~}4X=$L JIU,Ӄjcu]^Ϋ;w:>>.y'X;н{^ggq=H|$KkMA8m;Om.(3Ҁ&prPe.":Xym'`h`G!l6{ݭ7lo֭7X>KQ/<|\ X-ƉO[kD-5x^kk'rA΍I)&fX{Kjt%Љ t2 ^OL; ?ǹgn˄mga`=vW2jA IVZ!]` BX.듃;oߺyPtԭGprrNa/PB#|e5& ˏ&RH/k幻I&3M=(_ž3kb/pVvJQ`>a)RǷ(S(N iaL҆otR]@ 4Kcɉ)wΖCII!&JpC5ͮD> Մy@Qciy%2xzp{{{7igg̓鸋3IOO^^2"Ln'KbBd]Y[QVj_G4gAٍ"}vkM"_|c7Mԙsn@6SlRG#Ev}:caN AÓuXu浝c!ARrr z ANwH0'\0 m'ed#ʋ8rL )F!㽽?zckk5M}{Y1ͦ{E#,IhMɲJBmsMZz jc'8^獪l$=έe?tNVintaE̵T2xcX_nxSk^B|h02 #sBͧw޽z߹y{{Φ;`zd@sxtXvݥvRG"ZYP9=r!vt̺Spm)s&G {\L\()`7ۀt;>{h G"HMҖAC;ʍ7^;<<3wq_e/ԑLf^Eis)NU+B+)D@%:2*F&@?j!B4]!2+ࢵ#<dEk+Z :k;#F_^b|{k[oyo9vq>R^cQ}s'/|D'aBmtDVnߞmzmʩ1?,z||Jlۍ2gihoJ4ZcXo\z:8!%Rm=LRiдnk@.EwYtY _E7EEDlbǶ$˷IQ$c]̙T-:"93s(uҥmhZᒒ46(Y/AiyH2Z/\hR^GW+0!i3Fxc}@Aڅ?vsM t1kwMqvK#!4gt6 0YP ښPNkJr~}*d:iQa&g6u _s@v6?(-?5;ae9VN4+A@UA3;)ǔ\]풟K&)FC%:cCP0?&P2eBchb \*ZM4jد)'gv/^#Nf󚻵Y%똝hwڊ~Lf8/ 0"5BJ'8`N g{IFk,xf!ev{$M:!=zVݫ\J2 9[Jd`dϞ=;8x lutzPJhZg:r)8[[6}{o36)!d2N:Nztp8LR(K|? Vjl{y/T*uE\`x<x<x<x<eR2'IENDB`OSCAR-code-v1.3.1/oscar/icons/edit-find.png000066400000000000000000002075031417327530600202730ustar00rootroot00000000000000PNG  IHDRx cHRMz&u0`:pQ<bKGD pHYs  IDATxw$~}+_׶i3f޸7xx >4K`, rІ LȍenR!EhSK% Ѐ %ygkM{ʬ']U{{7S]UeV}?{z g$4 @7uhh  HhBU(Nt8`{|y[s|L|޿!#l~!ypx` c6;A P /x%߇Pп~8Bp!Ix@HQAAA̠Bz߃P UWj O|7Hgq #/#wzab&7Z+(%?2_{֋BʿkxWAH &g|{V"-D& 0NPWᜃRH8Mx X SPPP8E(B vxU^9l{lAEB^Cՠ=MHFhT`Bu&]bRxI$16#; /@5P_OM?Mӄa4-6 qn&o]xYFxp|׭B;t]eYhZh۰, 8tlqb_R`F Q #>-u%^pófCK˴}ѿo,B1ض ۶:E|N3 0Phf̍&_Oe" @}_0=UPPx _-kXdE3$5MCw'_ ݺem!ztPMPDDdɆc'f Ch&88c,8c>p;AHPDdED&}LD Y}-5 T-yR^?$w*2%|M:5F"\!Q9A3p.s x0{jb&iQt1|ăP q(PRQbp(6!m4ؗ _(z$,!5kFg :4J:C^\@ҟD9$_9g1O`9݋8/ `@s&Ih%]^Fߩ5 *, :Ty&@J)t]5v J)l6l6˘r>n8&6' `J :A!O:|fDK Mk k">?bbFAAP2|wwe$wKxׄH|GX;@AA3E5?T0Ok,t]t] t>d:Ph@6a`4{'4)(5΅p5yk|q| 5Z(}G879-,~R@h3wMG?/`͠qqdG~L.JAAL@9~CI)_YZ-z=mpsu7@9" vOQBilOsjT1@t 4,>x<^" }̳wu0pφg0m%Jw\m3"! +LT ̊ihZvz0 a2d_oFkFkz#a*^h55Dfžj qB; J0&6S1%dB)N4BƄ~w89h6r8:P+hHL@Bk g3 ̝-ͭ,v脃03;>@OKYd gb"@D6S.G(((,?[w]lnnn&IF" 3 ~ (c$F l f_q8&4JFQJ&%cCG%%%@8!`sBZGR8`.9t/`.2罀_ 8aݐyCh-i6pgÜEd@'pgf(~sP>P E?Q( ~]h4M q ~e`XBolhoCo PA(4JiPp`! ph4 ΰDڝF09y4黔iPB\]Ċ4'TG\@B볝nx e7/ 8!@)x."pgp*@ `<)ZdY JE$ H"I|a5*n-z=B0N1Na۶4ȯsڀlC3(?ap_$Xњ |#BJ04%S2&!$S$=KbL!'Qb|:_ @vh]a`a"'G 0`q"|0wgrwzvU-Aǁm1 *C@AṆ" (F/j>q"?-Ћ~Igt`ujoRFk@?f|IBbT~ew?p g ؋N|KF(5}or-8x"0Û=D*ZHg1V,Lӄ_˄;~[jvхu-s8TٙɅ~о#Qf‘k0\9}Sg÷G<{`p ܟÛe1 \w0D`*A4" 1#6W?};w[[[ `<c<'ס@M} !8 p NXGRvNsH[\S |>|gnX7-"p 4 u=n۶@ES| "WEf-?^/NL&6'+jtawl0LC08^g nA68S# ;͊B@Tj?}pdayuƶcodw o|o: 8 a`# >)MA Te!&H;w/&lNЪ +YlF hvwa,sy~`.&[Y?lY~Ė}ISgRhg# /' :yvL`P@b:C;WP"sH!$[%`:(( V.vZx{xqj› @`i=8#LH|@Q{mfD婧i E+hg$]ױ+WҥK0M8.#yG{:tj5a C_`zhq`F0h[;o0{A^5_,I-?1~t,Gy z{j`='g4el{.XصGxO!TVŒ"n$D-1Nl Y?ҎΝ;Wqt:MPsU/ ѽetzh,ql B_fi{[m-7tf~ZJ*\N^HceeJ P4NCפ#895qjl9xõ)Iq" />KgQQ~4 .ڵkc,nb2Fs+oGAab4u0=ftD? ?"*Y߯뺯|U:HL];4w-?"D;70o#0.ϝB*QE|VϢ7 #&._ׯckk b2`6XU9f2;ZS8v|N6#: kh{ 7cOig"Cx%*s|FO,Ũv^kmQhA2Pvnf=Nbʠ"-QA%&Olq 'Y҄nҥK{D%d55llGӆe ㉍94)QvvRgNH0_JDr:R~ʋ/'bacf߃۠A|]k@H%lpWp*x KXRt:t>ass3!},g DC;/bV B(SFi-- LZݎ7: =b*_a~Q+ c-׵ 6L}miXg mzH軡5 TGׇpf'/gӁ  }SPPXg;Tw:\r*6771bR?a @\뿀n 4z> GS^z"݆N2F?3{HTo' #w&A6u<ΈvQD7B| ABӃ"pc(y%?\kP$6]W_}~?Q?/ps l\.(!80>b}B&~񷶻?0tzFѫDf%J BI2g6@@%`Z{r&$@@,&B?8م)((#xp%A ^WDsG)-1V18V"6w`csfG#Lmn07 &27BvI_7py /dH >a8vMko ̛nA~oia6@6; ]>8*| މ/JAAae<_dOV1DwEl^B׃aNgxp3{ ͭj6FɬֿV XeX˴-` ȁ8k >9mK=AЅ[[)XcS-h{M$@ HNJ2@e((<x@'wt:\zo&avcrj_ZkFc{:68`GO1x Bwq 8(ᣮ S{rRi ǩ/yUFK(].)lTV]F#Y@І5 !|Q1nr &Z6|F@; "@y"!do6xocww7E>ip`67u%lmlq]mg[-o[o 4(5k漬?ύŋX )/s!YQt{ ƯkF.?sV^{:Z 5&B|ߏJao PA O-3IG߲,\z_pńuV{;cssib2'?bf#0G~^ۤi S)8_M!jcFjTsmMPr@AKI{=6L~KcAw^oow}ah:x-$ \&E+$f\kY<B_Z, W\o˗/'SQjX#so U>vnZ__ڬAT^tn$) *]r@. C6Ar{ 63躁~{6F/gEdG@uԛJ QPPX+UuouȥHk&eŹ/f6kk8w ڭ6){1_ j2{>J{^Qv[EMInzVrHI/B+؈R}Y<]dDPMvӠ7yrsZ0l6N5t{]8 `uX@~J QPPXEP 0jꫯ_2vww-}qiP7Mcܾsc:sCu0LJ6~ip 8`O E:^A%Pܿf`}N(Tk y  @I II44Mz9 g0u<;`EK@R鬀 \coX߶ KY$QĿih6r _q* ĚupVnG0h-AHZ:g6u`.?y3/yNV7u @A>OIEHԗ y$I/ F@4JL|b\!MóGf >{$ @ p]>`o"*Y#gX7GK.+_ ^}Ut:l6Uk8U4[M[`/=ivi׷Q2Z?Y*sJQ# *SKGtKR$eɌMG/eCHhxmKTU47ua62Llnl$sR\b@ljxE(((,gDQ,loo㭷[o˲ ~d~!v m 8۵_XB \o_v P62n1NyLe rd/ZЦ`询M&g] _b@yQj`.BW5PA ,JtV^{ _WoS5_ ˸x%Z-͛31&H7XAB CMɍKMUQ,/VœyL) 'W$~$; 3}L0,NGɊ.8#,Ή-BAA ڿeY0MFׯ_WU\v c<AuM xt]F#||&{ 4|.7ֿfatިDɊID! G[KBfH>H{$Qe@"M ;˸N>fhPn.XӖ1+<+~g ų@^l5H կ~o41q |z.Ɠ|Hh眕 I9RdyjA_.O8 {ǖ#d8|@qT1&`KЦi>mP{z nxpM~6h&80@#FފAG2b,a ap/V^B-< /!,#*EiD.]~ql64$sp%0p֧xqxt Z XŸK_j#XVGPz=90L1gH._D;,IH^Uy" Bm iiYmGd=Ciag{{a,ffiߧiqq ۶9U@)i&(d^M|K_Vwۘ麎{{ヌGOÝ6Gڦ/- ^AK2UB0,AS@Rd~IBS묢$ׄ@hu[~l{Fgמ0Njh5+VMֹPJyǁQ@/A*(*V"0H(hvxC?Ctlt:jjsq7^ W^AģG{x=8^0 ( _XB¿v^p^GR.,o~ːERST" yg iyf,?.M$"IM&%! ZMOX0ǹMh+~$%@ Z È+ .*i%?iܹsac:fL@06ppxwyܺ5ۘ?4~RHybh EZv]lmΏ @nrn@ zQ],^,ʌ;O1E 5t}fdp6=ܮv F 8W**(F#X" _~]t:NQ*$h&]z ۻ0޻o`4z@c4щw*hu*qm(9Jo8(2Lu"d!,9I)m#ʨ&)cwGX(eh@h8_OtF@ 2`6& c--J4y'ӁяK/xv,<2.p  :Thu>Il)32Ĭ(dTg.OdBāHU@Fya'1H`{l {JGeg&mcA` J+((T]}  @~DD߿t:~:^Oz҄@#y 0]\*:{Gphb2:w; ŸW<~pI4xu)TM:S7LTQeS+Y~@Fnn,{nUY< 1<ϋq9izu&WPP'ټ~L#?#xͰ\t:)4˘Nx>p25KtUnnYgǿ"Qk㞤PeR|?BAJsΚr/!h'o2h,Ȉ{Ssf ͟B%P4&m2Þكk~ \eYXyq ePP8<-` Iф7MNoccc#%e$xʫ>,:!m!CR֫h\!9 j3j^'S-bABBܵE?c% ,P.3T!4('!#h-QBwf>:| c<:fҔAOZ؝f Ji 0N:T,‰iq!_Tip… V 4f_E'{0N@F^e=𯄪csP7 gMU-"&+S r[UbRŽ$"?)[*@J?) |cMv~yg{ܾGC^@qkUnd\?G 8lێs2t^4oXAA2 wR_u4Mxנ: || [}‹.Mŋ*ߎ̄X14] Q?l 4B?/xk:0; 8w*O?>ـPL Cn6b؅Gs(fmEcVYZZPd Z\9*\]>ZVO R%i?^8aӟ?csb_|& bK61w{ηJp<ݦ+KBy]Xi)}'Xpu-&"e"$,!X0Ɔz߽zJ`km @iO:eV0n܌?U{RR%( Hw:x`Fmӯ.yyc|lV#'~%]#!fuu"3C5UQ'F0̜W[W6E/Zu6yN|$E{z K;.\ãc>#xoVM`s t0jb+@ш{)((gE.a`gg_gi/\G~d̃ղOh$&HpR&kX*17.{?mnHr@Gʫ,ii/A~!0,q<ϵ}߷w7:\| O؎i@L8%0  jOP"NIQGƛo _d q;.\ܸ1>׃ha802 _^Fo^D"??C6mi|"UsyxUEY-[-'.bah$9|ҽ27b[_CېL<u xa_ >#xi7@sg n7 0<nFx>,]ݍ}b<7@7se4=Ļ~GZ->0=L:=,Bm R}edneח}yBiLf{t_R'#s19w]v\{k?`cs.ޕR&677nuU kY߃ D"KpuXUd$@<9lž駷"c0qAowƷ"9Wk (_:LzV꾎ྴ¸e*?wQ?υ<${ |E Hf%.iBKB~9/޽v*VeR=Knll5ړ*((,O xaE_O[t a{i 74D)q:~*m^(W-_?.OK8k%Jg?@6H%#^h^g1G˻D/!rGK8iEAvw7wu4-yt aMY=UMt 샧ƭeI@Q௿(DTޏD'җ> D1JL 1#G/R||۱~t {^667`@۶1Lb7@666md>nNa [ .ڵku'RJbc2hCM xFZ%øx6`u"qzI5].4Ή/X"J (6_0IU!:B{qݙ8Fk/8իk_2`Dlt:h4q ~o-sW8ME'eV_/^l_<V ܼ1>u4`h:=JoՅօ YV/)e+J/XRN*H%1Kf䦚h}["";#1 HyO. FӁS.?TI/_bS^EzTɃ_y]QQT=RRVRa@djit/#yAbG%@q]ױmnXϽ8{mprO@-%nhFv:.ԞTAA!"?Y@4B+ŋsQ_N ԿO :M$E/EYQ9YV_Ulݓ1 7ϊHj 4.BRd^:VWAm|ϛ\~£WfԍI@ǁi@ۍE@ +4@-D}f36}c ۗS 8l`8 ETH+T[bmX>6ki2טɎ#9֏|UI"K%;/lBH .p| sulq~՗<ڵqc7@$zst:loo뉥 N>lav.]cV{wLlS[毀T&˾Jdg QZk&UGJ"I& (O2J*8?a Hy:BvAe~y=pm3B0y1Z=go(lj7}eBU\zRS!?Pt2O)E+WGsH@} M8: Gc#C _*lܴZEp?u/3dR5TP Hr4#?&\NSڿ!1lۙ;;7>*hh6cq` f3 4_JTPX A.H#+ 8_# hVwqm4-~éS!!*YG5/yϞ$:uԸ_\r JPÿ0:OҦ1 %yfxy`"^ 88=vq+x=Lg.w d+X))@;PAA~At:\x/_4Fm~bhtiS <~^`0q>n i5~P3:{f 3,I?9SGXN=I,7ZG'W4rz "$ 훧xJ+? $}o6'ɵ/uSÅ /T\ҷPn\j튁JTPX 'MJ$V W^vo_!n\ 7oހh6mnZQ_rI2_"y 'e1/~D5ֳckI "B'!IE$ߓL@:y: !Gɓ$,nr#I8cv8/\ZAu]8 l6h4ZjR'?w"odt:vz^ך@oCG;s,S2}49PT@2 vL%S X)5.Fɉ$ǟ#b}E,PӄcHS: q\Ƕml^yeF5wF$ Ht~Nv-} ]$(ŋiZ\_l/>'aв9<ja8q0>@hOt^A m⫬{FV}b&fc/N&GH DHCsOE%L_HiL^a `~Z.6|ߛkWL,lNo!]8(vBՊiP+(,$0pyR*O0wn߆0- G) ~/v#R?-/zUqT^NΨ۩g/GR&ex`aTbk1$1s l؎}]pNw9ݕt@dFN ۗPA3$?7p @x?x8p v'S*6RQQcr&~ktlt&Y$i+2'5ZLRO\F _ [Q؎kOgY٣)>Z57x9yq0,BAۅiQ[z28NT 7InqU?oh5;wn!6]؎> m t n#k:4iU[IڗHK~ڟv9%耜l&~*ReU f~i@*,1\m{64j|Jpȁ3llm ( 0XVF4߀7#=L麎 .^E@۷8:6')61lX|ҶlۆyqAT ’8){Q`KB`YADA$JChw?Fǣl7L˰ͿE\W$\.'d[^,2LƟp亹KqRgSu]gfvt_Sr}Z۠IKe DY.\nM#3=kF 1'8pXQ_Qb LʫDi^qBUX )y+ l$\$W NR:?}"8ӗ Il6,V N7)?m3M#/@5eVt:t:EI |;;;0Me2]!p\vAR_m$.z,R.dUb8KܓL^k_ 1>]sHm~`cO2"0Ӟtd X8ŋ| 6f iq1 ]ף.*PAa w5KSJ>ϝ;BH'#z;oo͆ Ӵ@)H#?Pغ^* :uV+>G@er6kH'!̵wdUI"$,M9ڮ8;;~9>Fu0*ij,X**( iDƘLh=:m 1p~w Az(E49Y^n@ռ+D 2H8x/܎IK: IeZ<d~!0O/vH!x]ulqNa;.:zdoAD8ui²,qO jk`8 &jfBp…Dt^p{A1NP xTr<Ҧۊ 5XF^>x%je3ybgԟx&A Җ,cY AX۲Lꋗ1h5Kr!G@4Xv-Z Q-:766`4 3pA]T[Cu់Jc9x{S Y_㾬OMhҀ($jeE ҃ԵHWK2%݂ DZ)ދ/ ,0@A@Ho *(|Fq4"7j]㲟EB`XMhFCɓ'v[p#]o)RYɸ[B_5T?^Pœ)X>^.kOoM roerxo-IP*p,c0μ qcu u! 1u]xMl6jj`ړ)(|qrމF#6v^)H8b8q4"!V 'OD6}T["w#Qґ/GCKl-)}.-[Li$Ks\u}676 k,4l\Oź @J)t]GAߏd_n 0LC4, eʗv(+/,d؁B?uOε,}Y2 $}Â?)% =? ;$Iz$0D2C빞繽^y8[A È A( FfND@ك}ǃ#8~dfμoQtFRSwa~/vW5?ZѵNv.SC'Ix_&p"7 /IAG@9_,=u[{6Vr0b+d,˂i&*64GA᳊u+3"^ @htq`0]M qʂ娞+~mNj}e>HuaI+C%ddyJdHe^)-PS $Eu׵X㸎i6\χa,W 0yx-8[9Σź ,z,M?4 ]00l58mX,u䕬::WY߲V• ,VNORUzɵ6/tO]rAl@Ay ^dA a-AZ*b(gMnA)M~ctt1 xbcyD"v$i:G>%IvbIYY@s|?n+ܶP]hPP}!xA@TۛRH!9 pa4u8q,:3\K K;4#┃$QKTSb>NfR2"!i?i A]҂50T*' рs}8cA^VKXMb+ D;Ϩú-/VmYݽ(qog 44 (yyef_VV[ÚvkĚjeq (wKCc铎|E<: !=PbS@>c,t41Eo%D$ hj` ռ"@$~T3t2A,ae>)nPn %?ֺL5vBY3mMsIBt92lw$q$TH_'sTiM2q}A@r*dsf_\P{Uu@꟰Z1Xаf z8]Qp@: Ah(nD,@[R !AFOfքtmYB-OꪦB6@4>h%dyGeZ(S>lI'eX\fE9 (qY0bAG{ @X'(ŦkEf%cqιiIxUI} NE?`6C{p]!`tub,?NM;)۱z~[A덱P݋VN最),t4sO* % _ӛ4:-06]@AAay/hhc4uDxXh<ׅe`#`,h5ek6Z7Jk.#y/]ƗY#R?,?}1`"pR] -(Bm;zT_?ڜxp?AUU}=:PUi J)뽰~Bd 3u[@*-/>/4ӴE/0T$d WؓFqr$}?3T+Bݝ s}x]Ƨ١KO5*%j5w5"Wn9D])c/O2"-,i>d96Gt]KA[4pq3 ԉ1E*KcFֽ$ryn^t.5 I@)V j2y5veI]2$ԠL8f()a,F} V$//" $9A 8 FVo?_=c| tѫu\ųf_1 J\vWg<ğ/>QG24iH@(% <׭WPcX08{(~3VUPP[ļ:Y!"?ja8u n>\l6W<xDr [ImT|:S PmMOG,*ɴI<. OcHw(5d@-@3u~^D ÀK_gg mE+@Q_0 H!OWk i*נ7NP; Cx@ҙ}#UT/M,-L4]um6 w#:ljo*Pg ^%o3U+pU,3E yՆUIy-rڧ.l5/g̗UJm%ȇ}ލmbN Xghs'c9 @`,ˊkxqwRP a>?8A߳. $%uVC~YsV -NcEA`N-;#а优QldNt]3(մp%@nRoyLӜo `PpC R (Heپ\\P0U4qo3 B(nV߄O=Gm>PPPX/ (kvVuav" |ߋ}CMOتCWk K܋_lC{LZS"{aɫ T ΐܠDOG_m/X@@ i! ]zH7Mi²,XJi,'IZxrBBAʻUm (7Mx8{& 4JwV32CUv/:]mNΗNΖW(\y+?&ԛ\*X|.8f9CDk RYeqǏ`tiXPuvF9\t:q)0a >X`Pxdž!8 aZ0 @]kB^ѕ/qe,Դ_Rz薊-BhU56?E3p+M4˲,q{ g=BV Fe!ضt ۶1ja*b+l6~ sr`@4b( ~2|"y𚑔| yޞeiyߔDHnHO~kzٰfMn`Bl6coYcNL&qS( B-Pf}6Ep i)Ck|^zg%uC\E\ 8#]F&|" ?WHY K*D4+r7yA) 1 l6cx`1%#4M4MPJ㬡Ì((:c*DY)M$T9!dD#U|RI_Um&Rln~r&~~~`hZhZǙ_( ʇB&*Ă?1f3 øV@Zc\@4mPJ`ܪޥEM\ :QjhӂrϕM˷ H;H =/9n kUձR@HkүiACX7xTp(Kt9&&)7)ET^\fB SBD5  y3 xxBP}|yd\Ċ!3pqe$'vP4V{DhBtXNF?{p`> .j`a4q~ ,l2h4 hdլ \!+䭤  YTŢWv[k]%$.7("ppNdҕ怈T:X4a`jS[g݄Pl!w Ѱf}6)l6l6ip81\71pgYA3u[_@V "m_1&xՀh6Lb XJmB1)༲e?0[#O'YAl᧡2ω@Jm>=Ih/ k T+3LpB\].@Fv7cX; xn`|/jel`61 @, 5nQ(#p8x<ҁyt۶aL3,0^DeϺt 3ZerQ(CiHG,A+~BJoZKL|~ Hgt)W>>$-Is"V X V1! yZjn3N~1txz5SJA)6.v+4qppDP& 4Lgc D&$3l:44me^Qe'bkj}$¾,KCNNS_2kgɍ-'iҪ[ʅ9@K@o@dטP v#7?7[N6:.ZV8 0 ,P&𭢓L& !cqI2 @&0n#4.@:u ?)>եS}%Zt1T=etI✧u4 'W@60C`bd0<^| \+lڽwnG |@Q_ш @Cр8<<IW _{R!qnu.|c2PS` PJv[UWRGZ A52c8Fj@*Xʤ.Pl &"#Gّx(H񪄦PC<7>“/3g$<{Pp{nx#h4 =a,,'UPPqL\#0 ⊀29 0 8,!iPMHzJ|%$\C["K%r- Q\a d H%˄s\7ȸb3Y$ 2>R]׍~?O{h_#GGGwUkYYSK:R+6I@I䦗T\"X@~m\|3dvR|?%Zho޼CA{/:Z::>&l1'ALd# M@*$}V1.frO`" H Ix uP=TQ`gBtxAQӗe)u.Tn|_!!PMӌ~ w `A]8Ӆ"A)d2?~hNB'E <GGGflAф燁FwIyҦ:Q&^c(y^lvgPcyg5YܷIc1w'QܸWy\<b^lDJ00 scccsI_Р|mF#gcc^sL&coo/E4?Xjb#ө~AyVX&cpak0L MˠMҼx7j']-dҙKE\e j}yΣ4s2P =q/` $"1qg HjI!/ @R'%A("Xf̝ȋe$ PJnfsޣ?\of|=zb6 ߩ=B$D qz"H?t@b4qô@jע_E%7.ݵOWI@m:Xf|εߞ4gNnkHY)(,yl{w}r5x&9p8ē'Op8.HoA*($gf¾cL&f5%BV_5% 7,!Mj ȁ\"y29&ᔝ:_j>D/h rQFI4 #*H6GV X[_J)ջNPg9Ǩ Q6؈^q<|݋c(ᯠ"ND(1a6i6k Bi"`jK2sATOrEز?&Ⳇ !4$R+^$$ HXsN_  !`07~+~ hG50"NUsl#paZã=l!g ֯GM]i_6%(LK q8^4`P$1kI}(9L-P)QY(=>aAllRlZ~r^./nb񟣣#e97RI|j$*O$S'v..aG !aVۺ}o|۔Za6Fx!lNeWPXNPKFacocxө ]7i5 %tQ}|kKSGjn_@/k[lBڀ_ xB\iHc"0y>{@Sw;.}Lݣjڅ[[[&677>߿?2+( A D}6mV|80xx J54-*%5J('2Ѹo@In_M(T\,q*l3 Y +J )윹z`[ ]oH\Slݠ\@n(&@a<n{n|{ߥ iϿc{{NmǩnRio AApxxX{Văsãǘ.t݀kLA`FskKVh&Ioו'o+PSszD p/?-q~| 1Mkq.ZlÇ'Ν;y~(((4 +1 @)iqRKc8q3}%+*R)J/Tg)pPz$c dE҅e$es a?JFS׍n|t2yT妴]㲿!8::Çq=?ZB%M&R<"Qa u 3 8>x"`Fچ}mDE9yܱxTty r /."Ncusz'<'sūZ(d^B4MӍN{owq+li&~lAt:ţGp=ܾ}D7PPP(YJF$ C4y>Wf8 34-w0\./\r-(O|Q+MZ:3ѝa3J㦩SR7r}fCyCOxJvv=;|=``cc#.ix!nݺi"*,*ꀅsL8,躞`{OmQ6u8СЌ+@@ 7޹ GhˬK`MnM@I0'OjE.%ozP/aqtf+4/ښ pyt:ݻwqm٦?^B%5*8>>Vs:@H) #_Xڿ˸UۦUk`~`k,H4YCGb2O[x1湇RB4Rj{磏c܀hľ J)'ZPPP"<,kGl`08h4N2៴p0 8^]# 3 RB5rJf,V}6VocO)_*k&,Q~l6^Ͽw?Cn)ywwn0.;Nej-DAAΒAdQF@d,+'Fq|ahk@R@`KLEjjujkf<+o2&;d}W\??<Z_B0 0}Gnl۵*-NnkYVO$ϝ;41Lppp{Ç2 \+((TYP8::l6eY90cܐThqQ0+`ԋOItH뾺5(#b~t-3Vdp`A2xjt"ѐjh6GwҦDiFn~?z>ĝ;w0NeGXWAAq_,k$s)hi25;M@ѡqt(!aeqT9rڸkA25洲Mz]a˼` .:'֚esR+@=Io7A(Zhlo|OK=rHjmz=z=`{{FwŃ0ծ g&@ +l`q0 0L`YVPNۇzhK_4U˴j7|e3 ױtZd>Ry~/ yg %B44L?pާm[=LDBA׋~a:beoBu< *0]#V0b08 zrHZܥNsڟ0fIIF_ͫ಑RxO I- sO iif?ɋS/N4FVkQտN91?~'OM~(((,_0(k$0PT( l6 27cb81s|ikaαA $r#,1-&[\??aI,(ZO%I^ '))(eY|z mY}ӿ,4̈́?occ#6G' I*("PO t]4 #ɬ 8~)F3 /RJ~pnڌ e $ba?=$˓N)xYC),]' YG~ 1PiMmʇ݇o[ӥoX7"f36G4.h*(1 `6a81V]>2?qp>RJR ,k'!j˒+o~wZbi+(痦V)[X>PNaB(4C7̀a?x;.=v677c?*pxokUxdoA*f8h̃8-?MJ)zXSsVTZ]r7$\FbHwNWHb@uݠֽu߼sz#pg֝IEoY&Z.677n:'OøW UWAT4oj8f+@F@t>Fw0y s12v I(p*r*~L'.Ba2+@$rWEv@ڏP) ?C׍ֽG_q7ΓeSFG-BB>zZ(m8881 / O* jAdȴ@P1ޓx3<lQ%0I rڸ'? [[q^>7 +isHFJ@E-~)%赛w~m6[uoFHGD¿,yp]$5k/JAAa%⊯.c-UY ˇ_~ @XG7Խr=xIܬr= 4t_v('*8\M { ’x kUFAj+@|じ`c~!3|QI@-Kd<`rDιR4;J($H(dE>0ݏnLl{{񃛽{ KʄdP!$[ۗQKIMb?*ߊ@t.ڰk8b<$]@P?G]0⟃o4PJh4bSd }&:K|~+2MX)G1&7Sbx򄴵U*`,EoˆHӉ!Mp1) M4mW%w}٣ڷV(/?VfF# 4-v F#x'P3l'jxqfD@-~G71v &zqH(x=kQ<{~Wm_k\^_oEVI,:5,r\wS}7E1˲d2`0`0t BY O;_KUeAXp(3 Lj~ 1mw )pߨF j4kYaJ16q+Ǐ\> i|W.x<-u]Ϥv ˲9mq:.x9 ggJQT }L&f8(0 νo`&8'3aYK@V@RXQb ;-}sM\zx1 ͟RfKؓ[/;1&%2`@BHgF7@T4hj`@;{AL" i }B!К M0A.s}H q W^q*V46nCkt" (H"&bF B"P/~O<;Q5l1?:.FG߲D`AEEAIעtc4m87|ꇮPPS'$ WNgin"` :a(R bWj8A9Z\!,A$=ru\@#jPpdß:޿Ea l6cm,ql#a:sNX )((250E4tL["R Z%iPo#[ xvgij̐k tO>Cwc=V|;$jG?5A9hko`pF{?z-ZdLvN'6E]l6wjxa1Q +Y#>|mD2&bKoX'%lÃ. MMCۃo9>SA~WWZBaL N^!G/ަ8BJ狎DZ?!%xro{w׿19?zNky rD"BU8<ɓ'x1:]5}VdzHoQbRDDw@"@+N&ONA;ZxR0{ {A)ڷyy+S K aaPJî;/MKr$D?Xjńh@D&mx1=z` 1'P* ৱ+@e$XϏd,3uD׵}ܭH xN/  ܎jqGqD:|^AA*>4v䑀"7@!؀0/qN/FKTn? |lv&5Kړ"9cbPGɭ-;r!'Ov.Mt4 Eŀ6f|R |߇88<<fY8i1((eLF(BiM `E.PM?4tz~(W-׈b9jk/)7~ /5$sH@Mݸ#޹gnY4 Mxaƍyw]mc2{4-+p x ~5.4Ji#U Q4F vvP_zd#JHBJI)X/Cxia@0ҟ2l|^[__$P QGmEA%c+-)pJx ?^ک[G$ JCL!779  1qݷN.F BQUBzC>+$35?EK@:@)iZbo}xdqd$!-=˙> < Lӊ*A "#iZ&; )1Hk_MBeiʠ\l^;L" (f!yџPN@IƉ  ܟyo]}ZA~KB^,ialj,islq-zCH PPX3&Û͇2&ǖ*1`u4 }l~Wt6o-$ BjhϏ"'Oެ Bܾs2t~xS{?60{e_^юywL @(&ڧ^AD 1-ehH @eM[RW-aU5PAae<X"3IAdk,C O@1`\ϻx9'ihx$e+=jqX:j>4kR24?u4<=Gfw`twM|*jyYi2vHĴp }>^’x & Z<2Q ֩(w?y]BԵ#B !9t |p| XAid#.#,Z"Y!]h4({ 7)('d e D?^/lfn"b4n؞}|,C  n۷ƱVYgY}ҟ|QJg{>wxx͈>/Dgd\yL=C?Nfxr6o~ľ&+ ~X%PI\!Q''Fl3Z򦦐Ȇd+1Z:Lj*T'3W +3TEKQ14FtRSu^7A~@+2J#.uG/yj\{G^.ooFj\u#&@(B }U~ A_i 7ܾJl Mf chBM T+D0.^$/ SvEQGjU)e?۠֊YY_g?7XW2*?۟\Iԉ;t䅫;WI=?h$*&n Q4(ʹ0#' (^6DD63Ln [yHt Es{u+ыĠ2u?T%j*:+o瘍vA_gGKKɓH3h5qߴuƿN%t퇋o7~wk0QDz ùs'2: 20hI̕襫Ôs ڴ")5B2T뢲 vV&)^2iUf{brOXzɥ,=qqc_&lve=&usk]'|\Kp!3?5=p 0l<2ANr !_zO]uLܩQ̀) V2 }^]*M%O;k[7Z} mHb3vJ e x7go4;: yB#7F)ի[QF''[~3Ml?d҈}R._Vs\zӨ^)h4܋Z-4=]ߧĉzö?<,mˏON#fVޅm..? \\bh4b844ѻQsGKh-JyyZa'-̵C`RL 74!65H~$4{Q񶫢kodЭ\:^>+pIan|F?_@Mb]{=՝Sg߽ٻ|"I5v_{nQv ^v  ~ு_[–0Ε99o$9rɑ;_Lֹۣ3ZkZZkn;cuhF.378MS#f+ (#'h7]>tIҕ#'cF[f>4|]bYΈe4$qnwYuُٻQoNH"/1~/"@@@'YupwaY {ɑ;Ai -QkZa[G[CX?҉G;z%\ex7UBנ~뎫y]0egA…if{Y˓QoOoп؎G;:؎'2HӋ=TxY. ԵҭЊ惕_(Q chTdNSF`d d4:Ҥ5G6r-& (?$׿ߜɨu6j1OӴ?|n7̱ "26@o W `jdKذ"9D]D]:Fg(e"jGqiH?ik}"V bi-}n&r?.?`97^ Q ZT&۵ATzJiB)5T0ZR]T$+_>ZaQ)H3FeY26&keYRLzdH6X%0.Y$fp*Pjcu= >M5p& M_b S;@|&MH\XPWQ [ږ|ch$V:A _(&]"M~]bn30_ݏsθu z. &I4 yVK~8=eee%O>Na9[;bӵV4S׹MO/˩OadLzvVE.}eg;3h4*M*%¼rA|Z!04!5"qfNX8+0=]V씣WӸr3 [CƟΚ}'Hj8 ߿o?miu_e>2Iϸ.R3C]hmrbk̄h2u+񻤵b͟v41 ţqLi ?WS#` 42NVwyaDB~;@ &&uZ icLE ȯU}/1~~a>B]nƿzałor&'p WpKDpV!`s܎<036+v`pGu#~7ү3q~_k׽o:ûVcM M~3wڔ&:Od)"@8yN\\:ˋOnbhF3g Οv~?oCwF~㶖v/7Kȿ2`Mcӽ%h02Eg"f:[B4pLȭ7Tg-c_71v5wЈoc듒wg-a$N˰:ʌF#g"fp1pVȤҮILYg(sNq0.q௕g%lQF~l7[9,>Ǻϵoizsn{'j+ozI״>icvV>錟_&Mw $Ե(1w[7kF4-:C'`-'N0 94 (u$װ4RbKX^Vf4KSc~XQ,&%v%mBAMM=iIk_o1'y0<' AzN`ŞD42ⓌZ֊eCc4)߰ǹ]S!oLr75#ZkOtue'ͳ:oo_z< XKtF1VN^7׼\o\ڊ/^;CYcϜI Fy5Ӟ?)/VgXԥ.n{`gSS|;c$w_.n&1v$x_կy~'9׿w`KD'"vc>o^/ Bֻo-O*SBFubOܞ NafZÿ־d' S XYYu} o}uN<ߨK3U# 6hKYpQSڎoMLsuۧMZf_ M3ҟ[ǙY8۔O.< >5n_Xu{'&̈́] IiS}Pd}WaF}n-u!9}K[} pj?` $QA_or7_S|~ }AD}(j zc(BO@ ׉Qo{q0b6*PQq Q8?;ۿoi_A;U}ʁw)Z¹DͰ!?Ej,ء3hDCPWr9M)ε84OO;-x󺗿=GBL!DdEj Yo[ʁ˞ajD~&l pjq'o1FQتbh"yXOӡn{ɟ9liy3//z\MZdQ9LCj 0\9~K6X=ps__ LBB+̗ۘ]E;}sg1h;}CI WBZ7$\u'9Q^=NjT& W=xs2І8K! sP&Ř m D.Jwݻ/[!F<g^7 ^13Qnf֨iߤP@]Hnn\-x틞ݐ0%' v9CFD63\9d>_/^pfx0xNcb~c:!Vki@]N@(pb͏*v_O*mhb`n xh6X=/ݝ!DυXE LG8aE"lBLB`3"`<M c=ハ+. D@ 5)$*&7rۤ!Ge {}L',R8Pe`;>!|ocM.|Z"``Z!0' vj"k|YiFAJD۹i͓FsdɐcQW@Dp N\Л|3_gD/|oTDN#.8g|u\{FEıZ" D@JR&Ǵ</ DzaˮV)GaCŠo9=I n/<ޙLKN!["$Obڭ+/CiR YIJfFּ h/Ҍ~aǑ2@`GOa盇Ra.Έʓ@kc]_m9ELa&Pa5AxIkmk"386߻|{3hc;~ kSQؚ!5;qJ qDQ.g0 Lh8`=ZkPV^"^ϡՊQ("FGUi H!IȒ$S6垀xZ &C1X]>7\D;a;qE6k5V@[Ǜ_fF3:D@]b`(6Ơ杯n^khbxF_yWĺ ? K`%*0E+T̠= {7\v ۉɗu#8s3j!J)4;?|\O`$hy뾛~("ͬ(Tz' nUxkbH1ģ$l٠1)dhA: {?t/]l¶#@3.`+fB28w!}> :q0%h7E2eny:Q~Ry@h5dґ}p! Q7?\+DۊAY]nguSF&:q0%(x_/$?Neb@6'(r`GCHIZz0w.=C?|xґ'/~?@$@=gyѺy7:O@V7ۈc(JVTJ9M T6!0 PZ<("SHHF0bC4%%Dk ;h8d]d:W=i™xA9ܐLm?#mMyQR|QnS "3 h<2Aʽ &R[j8Qa72O{v"Dr ;Ŵb M? Ɂ[0m8 2;Wz|Q\>70B(\-(& (BPt%T) ֠7 {)ڤ0A{Z_=kmi~™A80ee8 o*4w.Ji=xN3 LXSaP@:`-!B?`~6^{#(7* )2}@PBguU Ppِ,`R>t}_ D kqK8FAu" čB~8`2W]q9?u;x5EBȿh^ I\ހg FYL s{hw:JqG}ӗvG yaf(&qϏ_+  ]poSʛ4(~Y]=$d`TѶ}Cx1ig7Qv4{=wwg Έa\|;ˋ6(2C_׿='#o2kSQyƿf+?@krv4vt" e ɨO9в"`nEds1Hͱ,nػo$( Q2[-_VWW7޽h zUՔ|jl`5)/N6fA~uΓa,AyQ{:޽{EB X6FAnh/})sY/N M eTO$nj_Y~nV( ?)(HZR"b@+M"vĆ FV|/ӳ?og=g<16gU#p; ns*2AP& R$!`L%&=Cվs"Ɉa`3{;?WqɥM;/SN59UWbįDA ݽx1C`<@K#JCt4`2$Zl8`{zR ,9 k^s9F#*hyM~)Wr~.PMSB4= DH( VqyQk+eb:1(2INP*E E._Gp#&'ް (x>CU*R [(ހ/'@ia$*d ў`=x耈!@ { |Ƿ=] seq*_  p uO rj0jC } O =N <' 'r"Ji*Y:˜{匂o?$Ct"Az>5mc: ?8q2w[7O(Mh?b^{5o @P*4 X$W*J`,` v=+J "AjF }v;/(y8OH(67`2@@AO4NL` Cck[7ɊAYx-\b vyM6cc`RIQСݷ? a vDonNp:PQ oQzL(o>ZUqe}-7A_PX'2c"o* aPfD!ȫpSȒ>&KP=8nU::t^a;$4-G?\AȺuRx1D1q011P9 TrAm+<7Q]Lս3GLJ@%qAIlԳ%٨&C#"lF ۉ~ #pÇtDQR.y^$' z"i+b3 Vev|[ EW @"$0WFHlŤ#Is\pنAFA<N@)b A0YgۭjE@ &=2igdz@'EV%y <- wiP'&*"b!*KÓ,ua{ᄃI Nar=uD')µ\:/*M us.}Aw#@7纍>߼nV, ,rN`Kh}T!ߗ?F@ b H4V쌀N}B@3m(6h=&:O]5>*A'=\zu|AϔIpD|k*=N{V0'l 4f̗[ +_VLav1bqykA `LQXAq]<9%jZydžK'@XY)!B6$ Z|w"`Dp*P(؉enn[ly""On;1q/P+uen_Z1`bu)tDFd#QRv%{n>}ETPf<Ա%qDQDETz#Ѹѵγ=WKTL<'WN' 0x ƽԭc̊"`Q*C B fԍȔ" C -tv{o>]\ETS?o|qVH2A5Fźjb]T.k\)]W*<@Uuw,g]5gCtdBv-1R'@90BNrJg13}wpٕ׊8 ©ƿxm+ဢK7]@-/[ Ϻ"SF홶9^* ͂ ~,˼HahA|*:"ؼ!:&3lc~K<g"A88JC=HݶA TY PPZWemڛrw{5LgP^qq5k{NEwoZ3n87(mK'@ټvadϰ|˞"Dp*R-0I3z0>eps0g4hd*y S&< Fk\y,P&6'b!ҥc(`vϰ%9dzI 7K\v ET.z yhZEuЮ<rzl[TJ{*Ԫpހ J/Ay \1*DȔ" `]?p[w9nk(HR|0NuC]t=}{s/}xNH#@S/ǫ X^rny(@k%ж'Os/ƀVUO 0*ϭg@2D`ŀAU~FA˫]nN[vJ>MuSO(]p0Vm>H/rA%? wB35(P7# Hgyb`5&DI5Z"]Ni/^,{8g_?a}tu p۸t$߼I-0]HaH]9`!JO&36jSp@<nVeY2Z9h+䥗$˰Yx?&]_=o[0="A8X~xqyA!2\BU޵vW!g|<( yYw۠43.حy( 6Tu!a: I9xK;#cAm>ZjjKe]W`כ&R#̓ww EG3"\*|M5 _6ZUs*èKQWm7үgU [xA8XŖFA=]^P@TVa7׍+ tOtIH=$p!(^w@_Pض)Br10 Oص($: sO0\Eu=f 7\rw0p:n\8|,,,Њ[^B@+Qm`h/'=5M_~co/ 8p%6$X{BiVYTQ!6XyޘC.xL &A'&Km'fbu5o[@i+!K`tV]ٞ{n/1GB ]^q0rTXrDuφ{_8B1PY`Ch' fvAύ_]J^k 2{$BD*ؼ:S24i'@4?#ts9{:D< ,Wsnh4byev;%ߓƵ0U* ?3y5mAr\YPx#j,QՊKC!X4J`Rpd󙊯Oޛ>b@3c;Vr]wYO@Q@n#7*/ G&Xw)TCx" @&V= Hj⠗(HL6Z r#("#^@Χg̜'@i`pI Z|xxN! ™xpet:n NDW޻|&^$b Js`Bj>:ye=φlm !=e"yCt{o'A< In̻K'N}#o5P^~b{llیѹBiSm9&CVE:KM P@q"៚PPΣ5`KMghv|o˾u 8 gw7VY\\tM@/ø hTW`*{ޔ7Wsw;t Hf4Z"\ leFQ׿[揋'` ™ȟQ#뱲¹C]ʐ;Z.|{0D@+`W;_{}d/^o n͡ v" 68[_h 3_v/k_`~B 46 vdaaa!EP{k)Fc~L8:*L`|CQ"Zu)H*x"AfR!넠JcHz謿WyϹ_́nGu @3'M ?֚("ݳfhBVGcE ozU8B4t<,D BQ(ujZ(݂O鴣k4qo-ی$ p&s xp* (H5$RSd//D+"gDA(ȔFybRY9 Me;1T m7ðY|*aq0.,LCk8Dل@]`I,ӎ#ၧ}vs6!AtUxYLբә#ґ2&A0w? sPLvpL~c gxLp!E^WPQ `&dO6!@; zF#VVVnZz@(Kf\Nj ]|N+ޔEc" Bb er *jF+h2]w嶛-" -46 ZYYa~~\*X+MU1P?yP7km+1ۦM^V=):FYVФ]w~S" M8Y$,,6WPPmtO P9n (.fs|O@l# $י,}M{" m7 Y]]eqav*Pe8˧4^d5 D@St"`.] 46 Z]]eeݴJ '4j4TsF~`gD@H'|Е, ؊T>o<aP'.]{~_D JcU4Ze@^ˑ c]>ź-XT?Om3^N@90NZͅtd;1U( hGdY]{7!"A8pcyyq+ pO)RF/ѫ0y[y\{/VT{cg& QPp?Py-0^o{p l#T!'@i]|׿ @QPt]E!*@Uf0n @+od/@|3~:aP(Jf(i֨(*>-۾8Bڐ&D`ޤ}k食ID Xnssh*e k]( կ!?L_8xL=Ɖ< -Eh++ϑKeh37ϊ"AJjlb!;ӝP һ_=o@4mXCG (Q9T<9W ^۟iD TmTn[fG*@j>@&)~?NaUCL_1ގؿ_vU=n,$P}E/^ɁQcE@T@ܶ@g({EtxmND g@ Py^Y!LSq-DB`Sx`4z=pTs =x}. PJEQy'9@L:gշ}7?+"`AQPce⮼[.fl 0fDL$C4@8d F(cx"Ґm+t6L\޷m0%"Ailvnl VAqWGT/S/N 4 V xk:C ~>6_Amdaeu-е .fv RV( P/= ޹ƌmvo4ޅg@XzjfG7p5?OQd_(FlnMrm'M?ܹ/LE t6 {yv-PsoD%_1+.<[b~`t<1vϊB`ɁZGm(7`7 t`G{nA5AFA-6 RyN V&03/=WEU{ ƅf[]k`}3劘 2 7 Q|SD;4|6_޷C mTt 0Pd+1}oD߄TB5&iDz{/X9RL.@."(D@T"D FA[^R?Z;IU5x0"_7odq¨(@_xw'b"lԻ} @a46 -[E8\MgL"A6Gcn4X\yѿ([g&% |,ER b+POz^_ 98)(7:WG)D lFA'W0ư0OEv 0@9j/ bgu^x'?c$ ZfJH /*JjH^FEmR䟑bOhIb @a66  q9ee3E ?/ ]?*,mWT* icjE^#AO|O[hCd2oLDMߺ"AfGm[`\Ci/PUuޯGUy*I~8 0VZ]f_Sy\FydB iI.N} (6u&˞O?yo "AfK}QjvNT%v W ׆~1 r;ycO7鱆B@o[Wr*BAoyՏ @a6 F\^aa~NMZ[['_b n"+灨\DU^yGŜAE' {Vsb:.s"AfOch*㍂<_&rpSu+mՇX(zq@c BoO(Mo+Ѕxp9 PJ▝MPF^qMh @akWW;!. E =&OBwr_:~O< Cy7QS 8^  y.A΂ ɛ4Em*ϒD裟9cEAM OPXvٽ@*DE@_%0fU kUs`,9pB~@Y9㉌PW Ԉp9N9X+9P+k0dYRz9hgsG)D l-)] yq/?+@*;8OkJ='RPY/+Lƽ.@y}c 0{[am!| ^<+; ~>tw|v&* oX 8+gv '3 =0`rm7b7"S10Ɣ/[<ϟNn[ecVVJMum&k#{LlIP4Ձ]{@G=\p>-B p J}6cxГtZ-;MNCQf-U0+2pgK~uY>8v#~/ VMۨ PR0d&,?35]NL[e?h@d6Y0SZp*nwl  @a$,GIƒGD^f{ ֥{25"~pUL)t!Di)X^ ?(Met5T3T (otm* <<a XJE4!Q;OSv *eTDߜ>C @a{9 kX .ex(W㞕yJA=P夽݉7n%m]a2c K+<P X! L4I9+r"d`Wc92vwiD l?ǀ>w ~l6 h8/5_# L ' \qAP>f87,hޅ&@ِ2 _x*^ a"c]8[tZgYF;F"@ se+#ʂސ,X˓}~WW>n X1B(=3P x~jy>w̘C}k2J\8֑/ #/$0 zB< ! 9eC %㱇㉓;O.ł8O\M;ASk f̌a'֚N ~( 7yJQ} <'t]:D!v9Ő88&GMp EiZZ-8& 9b9DB"ANFskIib抹'4ZGAǽvh@75Iʶ/ ^0Ou+RR q10I<(KY=*;O 9O?(jE3iހSLP ©GlyY`rr\vD@ǯg ǍjLP{9kT F4]Pd8&z۠>0Zw&T*jlAS>Gkcy6V(Ȋ@׹gR>P 0TIq18.jD@ 2Ɵ5jaPP vMt{<%S(hZb%:o@C)U! @^zm4ߡGcKX  U~\Rb`ͯ' hJ)L`ØfȣdV+|@8*D ڸ㍂ +.]ETW4*/G.9!jī箕8&,&0jU ,p֐@wV? gi(\^0ߦj<߯PhxN@` J`(tÏoX^!Z/dIaL~8M0l/OL pf#`dvYn9O@ "oB}Њ'/i?jKG:Χ~=A++ĤOׯ{l:o=e'nx|$ Yio"@x w):[1JJ A5 ]c51@%@N (pC[~~v˾͵F ԝ&U./ p8d4<|vV? g6wbg{^;H Ze.@k(WT1Pt5]N@iܩ'PBE1M}gϪ>+ y@Ff wX}W7@.}CГǘmy3VQTxP\>?O}f>EiRdh zY\\D)E$9><?inDԅ|yI0E_kMĉ?~̆E ǁ^NfG?I.GAu,lzs׃5KKK|K0Ml4:AEE,Hۊ}J)N8ѣGY^^.DBz_A0pVVZsgTՐ?p#tACO|__⢋."Nz>uḮ -(+Q8r'OdeeUw  8>p;qnYOXi~ß43ÝwG>>pye28O;_z㸘7c$)`SO=JFiZxkBl -X᎕;{06 +ʲc`$@lj|ogňogserM#Pwp?IbF`41YYYaii~_,$IvAB>M|Cj;}Ͻ:<y_6?f/--}n v]hXዀ^M} ~bOk]}pz F(G`  lCMc'Wl@U*R :t~| z'*!4 Lpz7Q߹]eenKۭ0-0"A:7`{)yDMp Y׿u~w~/9aZ|!zu7wF?4J)avE?c Qs#A83໰gx1Zm.%jՕU _o\Ops%Ƶ>wۢ(XP ܨ%}z^ FImgAa~GQ(&3G}/~K|S}:Ӕe74Xw_;c8/q0B!LR"A8606(I#O dAyr`E_Z⮻ᓟ5_xLj۴V,lNLJf==?iw XYY 5%l2"A% lZ1p}ډm`WM.|_$wk:6+(>'A1_]]UI/T 0-V%'GCzoܷM#: vr,c4Ѐk )@A6}gB ;>lLYAփ=Ý4uO@Mb`weGnpX/IY|"Ar xVl c~ß &QfOvnKty~uFLT1 PA H 5MkU4W< ִ%Q3R  0"9 8q,wƛ67+&Zϣ?p#pqb ;Y|A6}[B }G?g`=n4?4g A)5s̒nMIM~?ߏ f@fyA69fI-oS0I(L5}w-k7;X^uͬ>K  ` [ f0SZ4'c6_7~jFiY}Af3`]V6 {LS){Fi:& ~hw,?? N:y[BkNֹM#ovn_ρY}~Afv3} a邛3n3u0q qN  ̚gIppp?)w;/u ~Y`x YvA>6k@g_lc}xoC>? +D [Ŷ W4?T'$pnd>"Aq bwF?gR9|('ow%ai7eAmk 'kP% La~x`>'IAmkn_4 x^9S !@Av]0]t g?O]ݺ?G5/$  lKAli$( Н_W}wo'4Gau? vr{xvX][o*G~ptN Oioމo9"AN*v()mu6}kۀ @YA6S#l&c(o2~WK۶Q?La>|1AAx5ؖ[N_:X7#`oov|c_PA x?8AA8XĎXa|r &  ǀ1FANWp!p%ppQol|}NE p>`x.[p!p lCPIQlC){LE p-XaV|3@8GeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(/٣IENDB`OSCAR-code-v1.3.1/oscar/icons/eye.png000066400000000000000000001153001417327530600172030ustar00rootroot00000000000000PNG  IHDRx cHRMz&u0`:pQ<bKGD pHYs  IDATxw|>lے-ƅb C$@I߽R 7!)7@ &JLIn*0cH\%}~̖ٙJ:o^F3<[9<EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQ"q_(aR ÁIV8` ٣$GSE` F`)^ X | ЋȆWܛEGc&:~80 2M`<0 n@#Poy|>e%ع/Y[M0#%Yd7Uq(1@Q FΙ2A`:‰@ n@0Ǩgm{!B@2{K$NcRm:Ee%Yzߩ0P @Q #?4h9{d(q[\3/'O%z |=iqI"yG0Ex{V~(@DT[1*p8P$6n#/!\w`gS{į]tlo+#4 "<oWv)ʀ@cGL&HUX<޽@P?o@foc@' AyU(JP 2 G ҖMK!2n6ѿ(pNy ψe⛿E E)3㚏Gb}1 ;*{L3k#$+$rM ! nE @qG r8B[6Ew?LwKw}IK#Gq 8 pLU$$w `"BDuk7/)۫(R"6駒/!;/Ly/@r^'_nO@fB@)""˿Z7\Q9*m>bQt{IHȾ=&猒{|GApx\!u^! Y*"ȟD.BwEP";(@A|/,,;#2#2?.K,ExM}@Q"2fAl0r ^Bp}(B?+ܣcJgK.y䑧~ gBQ3*%cF>DjQ79w\. H`~F?ti s ,_+X*"| (rp(t%>|q*&Dnw=.\* q4#OdY)1e˲䑿怌m5@3?}F 7yv=w[n6D@+'' #RW`eY'm7z& *U e>6%pBp|T qi/;o;Fb0HXnHX$5e%-K~7|MRP,nîwb0 = r G~܂#2sGC/YOe@"-~k%Pʀ}Am c{j[!@۔Gde>k~>h{>0500.i39~?;w5vh ,+Q*A2` =?m*(Z;:gw1mV:![|g,HXӯ_QPKچx:p> fp \#or0@ ,Y.v5 ]@GNA6{[ԧ m I I@+"cIEstwg䟻k9^fdq8!?O͎" HX $,Dº/\g (Eүhk:HzDc[@\g2)/e /"t}毆 3hD 2] f 2=o%bs܆?36#@?_Pz[Д^ܮѿ[$,D%jjkDº²[ ^T(f|Sg {QA ҳKebiM q"G"2 h, g|X9" ϙx +55 DDeY}>)JUtDH ۝o߲Y< r MK:(_SC S)¾(0N,N#po`^o&E@;Nco%<۲kjj K$shX@)*i6JwcQ{'oO,횷<]Sm7"2˲,.MtLT9/;FwF@rK$ED"qeMg~uqCRu6:p)t6'IHx/Υ]*̞qn#^"X("L`r5?w7&AK$si7@))*i/IRH}xP{~M,9XD>,A%2gh 44>DXߛXp qmm "GXw+J67kAg'A1M=+iWf8rekyF?:ߛ,KfZ*c5JXG_r:Ł?m=mZ @p& 7H\qa>w*JEոo/w@DZcO.~lu:Y e`%Y"KO$DE|F޿a@cykkj@Ӳ]w~_H9W C?1Jո~4,rvWѯjvIh[:ʲS%dF u2>mkʓy^i#P[[ u\2{W@);8 *rgنO~\ ?rcM"qegŒ95#dGylB v9a`eY?O@)g׃\ | =0 비0 Vt?Z93}];۲˲H$ RϭW'&Z疕 3aY?Kn<*P¨3+R|pת cwXUcqߜHXg%_I$&zqqD; )d҂0hךJ *2qPɌpP ]ǁkW{ v#%&ֹeN$)-o>CXxsjjjIJ~6}Q݇Ju@)#gp =y { @УBf-_+ӛJqߘ-KǙ(gyj1_]3;ϖ%NtX:qR}PȆ}ꁋ@oWH&,^IMSWLXrέI̶J[&mԝ9Bf8ۦYx%@ %uߟv_ܱ#rư CD EV:DzܚrnPf' s@_V!d29W &4\@F6s6D`LNE(nv(R4pU+?HX,⚚I@p!ADI`y@D~6aR(*ٰw;ȷ Ǫ}y@k'\5^jkO,? šbᘪWY9 1w%mLnΛ0I rT(1a@W޶T#~5Ji79'aY_q555df DkXAOR"g<]*7*H4]\$ Ls>H9`A fZ{1u2oX_S8JX_I$ɖI:U%D0^{VJ+e߿ud"aH$fD&T 1Voxz[}(?]͖%%7kjjFk$|'/gB@D\" -vWJeP0k*32.,. ~Kܶz:Yv>횚S,BRߵ,w  X$Sw?)G⢥~֑?E h[b XW̻1wm,볖e}6 Ȏs{3+YCu=e%H&]`.ijP2:xj@ W&)_}VښS9hNЩ%w!+"_Dbǎdp-*0*Zg|.7ɿ *=uk6KJ_yeY D;$韍)#V&8dY vх1LߵUEE ~f vTZ6 Y_ZG뿝!bPSS3RRk O5nO@vN~lL2\v39-(s-ݯ VVmp 2G~ ˲Ƙ vs AJs_Lq^2KݿYu+;7/˲ί(E|;bY@pa@,**!3/#0>葻ii{~ֱYu+z UMMMS}\.?O2!;!G< $L2yٓT T "J% C/@~ԱY *я/AGl?+r3 [G]Ljo!#ّ4+17wT]?`\Wd&[#T n\w](0ζ,[5S#}-H{p&kLF ڱ}=ƘxqR<*{MNn׏Edj4O{-"rs&q@?$ 0Ǝc0;5\xu?Jq4u$x+A_@`{綢j`VcsbT}|\HψM&Bz4`R!71Aؾc\ qqR8*0{50.wo9¥?w)J5r->-"'y9 x HeI}&cw(`\U [Hg"1Wvnw)Js=8kf iS9 a>&}8qCXԅc<Оg\ֹi7EOE@"#rc^/@:H!vl>7i̅'~F Ĉ{6WBFD;ounzNQ#32" =?ȍ۳F?_7# IbH{N>Fɏ ˆ{?ܽ𸟕v&@Efj{j~13#@s(l`]'Sԋ(e㾹۞+l esE/B@N$$؞#%C 5r?P޹yMZ?<hFH;0 2h`@:D:AlDXm"AEBD1aY|alߛYirB Hy<;QQ1t#W-.k,*G72]Df`_zA\4wZfz'Y@;o.e2 h%f ef8S_?'00$رc\O}α=q ~̈{ 2o J|nܴ@]%d9E8 oz(`z>G}- 7L1b{ B+{Wo{SM qFdn iL&IO{w/?*)#q17vG06,.ڴɸl0aG ."m-3ǽ? .ؕh&1,;" _t ND}IYX'/' 4cǎd2y U!*!)0ĽPsQQ3$=x߻6-,>2~988ͮ͒πQ8W d&ߛ ~( 0YH&" q*C@?bA>i~N rk/Ik kcƃF_; nwx B}iEde3ly_;w/;wু_g 7T}sݒH`2`Ld]ɤ"PO1tiOANnU e]_%>돌o9fv鋴(6=߾`~>.Qk'o^yWF0wXVcuxO`nJL&33I[dc1|"JP>t94L;޿ȸǃ\.#nf/$ >Ɓ(9#~lL]zt5&DaonSS7m$peY3EsfG&0m!c,dt@.dc H2IsWq*@@3|d R J 6k>j(p&"{0@N@ >p1 X //q ׷u$$uIgEp'8c ML؞ [U_ |Gm pNҮ/j6c(pIDNoyam{\!h{:=8Y_\t rӟ[LbY)qyWF 3g#ŀbAIJ s.gUĈ *d끋Ƒ_ :> *k޵ŭq[3q9 _h'7? D /ڨ|cpVV&<~ߪ~ 0)w@ұ-=c w60IsUW J>d/d dz̏y8w旞Ϫ1#mcx?0çOe $A;7JWϽrR믝{EcyawzQ(Ȼ8;k8 Hv% ~zE**H" P`#26ohJ%0 n~qY52fN'"r $ёژ"UoT/:]Hl-ǽ uI/*~Ηu{W D;38&{U/[_x6ܱ3ȌI:sJqQ~&?gI 'w9w[zEq`A=12|ȌF]/XO]^vK:ØՃ vw$ 4 KDX ^Yt!tjtVK!(."  E]9{ (a VDo[WD?᚟%LgdߏL /]de9sƯ'Cv r5[/`G Wp8omK1#; :Lņ2 qX&0{?"ұ?OoLsҀ0TiL`fL! w,g -ŲϼxO#pepLY8{e@_v8[.UPfT! {\"y 0}p-ݵ=/T+Vp7@[ ;c2y|i5qʹ|a #J{rMmBBkH^qM5+~J 2?G ^0w_N A0Ɯm!Ȩ0Æv|` || =/o˪\!]! Z^lc O,z(Vǟp "ƴw ,gπ`p/p ~!W|׉e}^i3ɬIIUǿ>@0@3#$ɮd2y1|w~K e@@6dF%|/6,ٯ ׈F?l"2ŷ7ε9g2QsXpU vp%"3¾rDOL^/@{E䊧Ur:*i̍L ?1I"qs케Ic.鎻**!~j,r zݭ?ᇎ..$##W{p] O,[H4A:FD>|DD>""ݐ6!9@ ?U7o/>&ZIJ??mS͂$ HHc.vO efؐR0]&Q&'( /vK:=MLF9L0;YMT|GK-_wY6 "|I\d@^GB YA]"\j|3WbI7w'H':0HϾcǎp~w4PPP& ٵRŻ_lcbCM{KfLv߇@`uOvk#V`:p:p0Mv,8AN>-"3Yg.@@x!+Dn"+*_Oc&kOwV X (m]ܳ ;ܽ}J 20lȮS  W;h~I ~U'#{4Eݏ (7錑"+bN, ~eOܓ}W߼5uJ:)m~|WtW P#H74WoWjGQPb (G>T\"ڇr13Tǁ'VJ t}ʙ#E4-k$Mkp be EgU\M cӕg3&:^hd UT?=/?%6WcUnD)ȅOb'D>jSr=9˂&"[&!~lgo~}ڏcHsmc @Y18xpNO4Is_q vQD,3yEI6&A~p ~jSLp{NpMd66޸Pb ` *| Lww8JgAجAF+lO@$%Ƙxj-T P6d@n oY!c&1qesO߻iYU,a> !"2 xBF dKX"{&=ozg -dv^IW wyv$xAfr05Nj wpzd1|e-*"TK{]bUF 5LLm;:ގBYuVuQ{L˄pȝ`HoW,3g,^¸3n/x}9mċdH rhN#23<; dg'Pcq?fҸ]9 @@v>VSq￵ j}q_RVup=."31}7 ]HwWUIg߳x Ǹy_g`YOz5~"+& C=Ƙi^*Bh۹.1JJD?Qa¨9w #kQJ˪W{F!]DdFz{h>@xgd<`&L#0sDud2+n|o@~#wO~zH& [t|  nK>1ͅdkEmXnܗU<`a9˲ P7/h3L&waLR`1N&( @k.pˎN7&f΍/݅U Mu;%F}~ j}n[;6iw/zdkףUia05nIvwZSxC/ykrM |zfy8fԞoljN{ FR%<~/aU7/x^1/LN7I 7=a:`1fkVt,!@Sȯ*ZrVf&@~^˕D [٪:_}}t^'K @i]$ѳnjßM&gdVwQ}yܯazl c1'm͑3Yb^4MBKd|aPYKczbK-‰yg`|v]k*׃Jc )슖"tk(x.Voɸ+*vH,N5 Go,=5V(Kq.=$hN&E^NL|qxɉ~ ߺ J_.Y(>F5|v]j}hvp-0z}-fc 9 4H"ț婖ږٔwy]=c֮)"7HFdj8~:a0@j|kgc &G3wq@Iv޹e]X)piS`R@چ&аz3}-`|1.HoCWaFYKI] OA%֒wW9%%n9=p{ SO/qS5Lc̍ Y'|Fd<1}ѿ{30+(-lca|X hQN࿿Йj6C}?)b|1G_C䄴g >_A   Ci[qY~b'Ik3'@Dy,_:S'Ƙ_HzIWʇ\etswwzm`:1-{soʲ2 3$4ÿQ|@]l[㾖r3嘉7dEX\Av>ҴĖa޽qSUviscw|@gѠ}l -KWǸ署xݧ_>ිL0."`2k]1|(#~?ρ;HO8)QحeWw8`?hX7(: h~OCKۼu>~@.qmt|n@ Q6+ #r0pFK.[G|1{Nqz[ 8v?)MKW^TM3 N:d29 0gucs[,޼/ OR4MkK"v5r[v|ASO\ WKhmx`TaS \/PR{A8i׉-M,Zؓƴ6w݀31)Uk }ҕ/_=S'1&U1Ч@; 5w pkd9]ײ%up|{Y׬{i@yhvpi|Ak@뽁CCkl޺ L}L^B=ޱUdi-Mt 孵ߌΪWiYLz];c83ȫV4(>og{L<`^V~#O+5~Af_?G>&{V丶VY7pF4NmK+L o]z"o]8(UJk~@ ceVaSz3$^cB^ [ڍoViUcF24=WܛKXcfݱlKB<۳ӄ"Q$HCicA#|4x \HJ]31 "c6bY~{< @CӁSI@ =>k<-jP7Jk6o]>\vAl} r {亸sH|tߨ>ۤ/^륝خZb]^mqߪW_=jq"r ll6<ހT8e K<~1LLjIo^>yW_Apnßjdg ހm#fXr.i}}vĆک?~'"x>5!F;U42g|أ> 晎u}=f3|V~6M~޵5x  p۴_|95= Z1?ǿ~Y__{n2trG۝]Y(mĽ'h}r3sFcs7 pp׌򌉟N? کgaWc|83ׅKGDjxׯ߲0Qp4XÁ(i|62a'{ Sg7O< r${#씴f9 Կc>|:Ԃ1 &mDmIg;cN¬-@uppMZ`dHcm'_ )7N 5wP|h7tQ(@M;~mCsi=3fkN6с[l!7lɠ1|uQ/@~ƹT^.]iгVC9aڈgڒ_5ܶ?W `c2k n>ٯF4Nor>g#u{ 0$dyij#"_q\*.f}v!kK 0~ (W~JޢO1ݮسw:NC$ 3]k_m sWkT-$ v8 "GȐ=z)ܳ7_)G7L >)$wsoY4k֖oƚ/ N{kMyCy_}$pS"~? F#ԕFeX^ RkoY6NN \7V !Ǥgzn i/w2U P_S}}N$?P~D_]}9>h)T-t>ry&yJ;0!؅G <9|fwpw49IOJuo2gr4$^|s|ܘ)okĞ3"] ]Y{'' Na_gAeܿ]˸o@UڝI~Qt*G4φ-V".3q|!> 3]! %k>@9}ENOhK:`~gYt5Dz.gx{GVz/y'IN7&ggq^Ou9A&; % w<ߪib}N' 5}OD@ %j []a"-S:o^Ŝb0 ޭϊ b% @߀?Ӽ\S7`1J]@㥷n.4&yzz w&}"{ 'ap;swf3yǯyc"k'a^.D@}!Nш+Ya"_zNϝƴQ )g})8U9LО̏.kcLwƵ(P+ԏow~ \Xsm`LNog뺜h܀~<6o@@}#,s;YVp-o ="}FP@E<^{wsƘ?d O&} }s]>11y 0kfx?YO@6d./~/ KKpfŽw|'9}ń)2( spֆ-oW{V<&z7kpW!{Az)C92oϷ2yhV c0TnY$ F?H d7sn )~wOsfE]Nn|(YP[NY_e!Ɔ-o Jd2[Q 째=dNP@q}:`>1mG)OwE `|@LTY5HS޻ceɹƘOcgfv]1wpn>qmuNt۰Utmqꍼ.ڙwMs旧9"Ie5Nv11}=g1Kll.^tԆ-o 7 Y ̌#&9*)M7X2̽qن-o h 0,"5`Έ̇I- {PQ" 9{4:+:4QcL*$`6g=&ܯOzpL Txȉ|We)0}_>꿒 5t/pBۧBDE2  g`Y޷R?jQVxL%|x9P&Q`(oktO@թcOj.uzI&]#>3T&i(= ߹VDYYo@ Qoݕx"٘^O 2mJ"L8fu9 +1hWE@-yayƭo dࢸ/,v~6 7P@U9t" 3ޏ T{c}K6׀{bY~۷0@xߚK`0 ِMG79 L}zIFLKRX:D|7ʉ$IRǀ'6n}k.9[=l ; }C;70k.q x=n9*]Nk 8fcq?4[sJ G=q&1qcR0fgz+ 1ۢpSH_s UfwU|SUT@D> |srHp?al 2qxbS:fA`hvzin͑*AخsX`޴s6m}M[~u2 *"`= +cF.--ܸ<|D9E"8 )cNܻލc=٧I` Ƙ Ƙ)eFEK2o,`$ fkT`]"~_Lvǁؿ}7N}j" 5ח O&?6m}M[(}*%=lEãuƌ8l+i{'P"} b/tvx53w\I 0`צFXX8`~3ƾO i?j!Pn(? 8ɻpJEM |qwwwt~+5~q_v1qXSC:çJPmVzjy1'KO_ٸ}i@|/ Z)0'copg!#4SH`h 18 > 17m}J^Rڃw~ emN4ɳ|m. xX06;7M[ V*C{QG?aCw"rODT5yۢX8a >ẻdXU8'{@pg6`L}eHy]m#]S sZ1Z`\fAv} '7!&0G=;kSO`!I7/Ola+oUdFol'f`A-=̨1f<#dŗX&`ol7nRn8T i!`0s Y iw/?[X I0 ȇDwg" *JBUӄU3n6TW O^dTX9\gQcoD HSvW3KaT_'^qO+CYo~~]oTw Q?>/ŹP%& T=MYB :7=_Unh~ȝ58A}NsɌ]ǦWbGo302`]&8e K{ ʱy\( Kn_28ڍDD0ܨ"5x}iڇ|F8 6y1D@r|DA"D{m:Hәs#2 2Wܳ ̡` <5?7v0x |aݾ؋^BN g"{ 1 nvakvЧڇ|vi@ӕ)QP>@~N"]D@΁ǻDS eaXݜ@2BMZ)+]{v`sϳ$v?p`Φ8g0.z5c{S6]8JE@ӆHǶaovึa6چt2Hm.v"6C ِ@;ea?O&~B =w^X>"֧ςY(&<(!|4b>3g`Nm 5}; 4pp}Lwj}ZGqE0XS[+icNJ0XbqɘQ{/&t:Ԍ&AGpһ}=K>^C)M"KMpsct]=&Fik:h wVg/%OFS a=`(zixsAƄqPS*;+]ӛ~]8! y.ݾ,w,UQ>@K1~S/5\1ClßwNz[ 6/g.4(PA7lJ*XP7=\VYL! {ɟR&R\m+蓣@$o>e3cL'/ >X(Q1vH84Ƭˈj2D\)Eb :_ e5=`z3ʜ`Gn<o@jt]+0Chl3[Yjx>_9u AF>ܷgy ߢK*CfX\'P"^QLƺnvwQ \'_Y$"lw5D ~;U kIWsE}ƴԧ~K,LQRLi?^b E3& DA y`G:bmE=Nz/8c+9 .:TgIw3%q1օPg>$i.߹=+ >=*(${CzoP~/l|sk5 ~ .WL]S[h5c7Q/{"$>Li?x2.5ٲn~=q~locw>B-Vг}qG\@ V,J$]fgSr__%E@`xB1R~|֤pNCU̔맴Rd) yECMr5:]=.l|1x Gu_r_AD^>|QE&a!Y`tcF]EN0 #LLgWP`JǦȕ -)\?`Gpk޲~T% O3_،77M?r2H'݅ۄ,Ҳ; qԾ@`"Bq)D@pHs.=}=׿}d1,5*0cGX3'|]10$,n,^JlԪŜ5Ş_n~_r$E_~GVy^4LWQ/$~~>jH {Q˹hB`QP `J~ 쏣οFA{=C0 VV0ƮJ"R49yK]|vٰϨr\Lv/]CnDg{%B܀"A !꿔=DDc61oQ~hBA[Cq0]׵}=m|*Wa> r  ^\*6Ei{⥝_vL O~n8W>:CD_9{[ I|⎿-WHӳ탻9`ǻ`$nܳC{-AN)kg >B+[p)/~H&y^p`|wpER)#:3&thϕF%>jO9Ř.w|wL<_6]񷒄^l`p~(%8&P: m",E-JkE@vHv!"DO0Aq!ԏT@3ws{Ø,5*ы{m~?2o.?KBl`-9H-&rQJ^ MdpSSc7ɓuvS 8#X+h?l1/8YRެgL/Lϓ7P9&>PLH ?P9熠r{Y`SN: aq"E7;E9Z0E 4=>|#nܳ9QΕo$*gj۹4x!J#l:6߀]ǂ>wFǨV;qu%1 sXPGZX%Nm'e)$<<tqߋFPʁ/g76Kߩ12_`V窄#n" D\!JAZË܇1KTvj;q/{357w} e_ |?(#,Nj1;{}Ȇ}5cc&Iw=,;*lj΋ /c_Gw]kTm'N5߂'Pu uc\k( z\hjBW,#"Mu;0+:.`Ɣ6zUGApȀ D>RuZr;i>PkLn;Hw.T)wq 00@@϶;z^Vog)չRG77|J9h eo -+=gO*BZ׼WZS!H~juK rQ _Xw>;2ҹi~lF|LeCأ3y" @edr"v=CwQc0 זT=ޟ |⃼Df㮾=?##Y 4>e&BM_01 Z`?(`֏4Nn Y) |!,;l.^KʔĀ==p{S720d}{z`Y @bm6w|<8x1޹]gyD@^Y2 |_V}]Y&pϤ}Wt_1 ` 1Q%ObB_ZH(} ppiY;g՟؈:7D6~&YED@⪅Gq0|hu._%Ln4N0=(QO?Kw20df\`0[ Ҹ@ B_lػEEbf\$1W}l},\^/&!֏n99M(?|7i%1>2"SdH XJ`^z= !M;5KƘ a4c2wmbR[be ܴ|c,s1f)uɭ9 ph_Iy~,,>A!zgw`!J{\E袁b,j(|aWc4}K]2bE@AeUņB+<cNGtSzXկ#Ƀn@t@t>dHI(PSz/@yZ/7<ӟ1O#+qnx*L4U'(\(&${dGO_XZ*0pX{G;R1[_wz?8Q/~-p֒ދ|t`sj ю(#M:=0n; ;!ogKC*t5ܟ`|" 8`JE 2kESy#H7Ie,P4ЙzG'xhJ%TT#Ŷdk@&?B(aLKzx뎮e'CkGwn߇ ;n[Vg򞆺qEac Bn(NI}y&Wt?~f|1@@h #{]=VkHLj=qDWuJ EIWt==D ٜ&E[ 1 ӗt=xKĠׅ~@LP.42Atz`=/V0( CM;c։P:`8L3:Λer \oNUVRGv=TF}x*p"g ~ѳ:I%u=X~A z6tEKE*4sưND| ?} ߓY6GEK:]? Cj"4Gw!rhgs:ۃ1Wg-zj9^%{j-b2J.D@ quQYݾj9ӁEVBٶqoDħ(QbRqO 8.gc +ckAZ"Ƶ}$p߃yײuw&:,Os͡-#&&cKwz+_}Ammے/&Zŏ;0hK][=0vc" LoܾcݶbUz-_P7^``* \V{↍[>Hܙ%`\4BWK>}wC{Q7h34inAw9")|Ks:hK ;wo&Br'pG|X ^sNsq%"\া%^fpuOV='F(oL\O1_bԖ!7gVØ]'v=lX(`[`O@e@ صvkVQkvL\("| Z+=r;k>jw_4k|G^ GW?aWA>QD8< +v\!D #{! Kvd뎮b*wKo*׎wʊz-ٶqocȃ6r"~{z+={;0&t{n}/EbfdhIN_CK3kkރ{ޭQQ`P}$G̨Kzs.;:Hy>,0:-?sEx`tﶕؕ H쉈cvB9D@c7* ͽ jظeqFs\QgQ„`0=7}͸0%"%~-mok޻ ےj-3AZ13-YR^+ عv̢m+q=ۖoٶq AdZ6 :}V}A,|c.Hgal3@n+ nG}T3FΙk-&8M_Xߗv=,|Hj<\" |7Tbh跀=vKo+@B⺇mm'`FnHʏxE# M[>|d=ԳF)Z.ӪhC\L9axsJUV- f3߀|ai׼g⾚R2))jnT^6n};2g /v}/MM21u ,ZTծ|7+17+}⾏jdXג,ʁz LZUWzZ>v8 H ;)ϽVn^6o]~4ԍxh'G$ֈx$X?ϮZ],鎳|'k{(ʘzTF~lfH#5,P(QBCw,z_P@  Z7n}mC3?x ɵ_ta6r,pp0H']f5$| Q~'Wju}#~sǮ^ #?;p9pF/.G KuV))e 1٘i!{ƭo~&g܎Ciukwb ֍(-M2{GCV+3 S , F'VoxzM# Rpo>Uvۣ2 O=|YC>? *J@C3_/{@M6n}1wSU\!pv^bवiv` 8 {ђSp=Ql ;zf̈# Cs=_t'~|~S o9v4BT+;K<8Ȏ@n\v`( S^o<^L.[߬jU)gbf ti {z46Sځey5ЙX شf3qW1q47"rϿ!=-ݏ_T[ UREp/]0ז}ߕ+*JHiE@6k` 8dF[7,Ț iz"cC0+)nƷ{6loS@Vn_uq_S, 4|Vw=B;)P:hucG tc! Y rҊǟd|1U x t04-c := L,@skǼջm_Rƌ8ltCn1{q-LJnG  Qr/.[;ꮪE@((q(@"zZ0cU ƌ88.Y1w|l/o)q-GJ&˜jw#o_e"Lۉ-l)nz<xĚ~-s}qQ1c\W,q-G%LSP}B[+ \l#o_QPF"@$Js]bfn_@#TxSE@~h}ӐI_F0-*x9{q-GMJq2МTek-e&(7r(*z^8} zE/)p\}1cݶR]fC7HžM-B_YA|%-*J|R ]<|ޠ لkP̩KzgX&֎~Y|x(T L@ﶕ[⾷ "ȪߔL\O⾷|% W" )x80w_XA? C@((C(%FݳuGglVvRy.%̮wAq\NF?dhӐ_\ [+͸ @N屓4m?>˅#TT '4uf@YDt1kǼݻm)3Z)F?(:A}nrݓJ3z5}?۾B??l}_]PPa"SW(~=SPJSSyXi~Hk㐉_d/VNLypuOVJbl ub/r?՘@Q }[Hy7e ^_;fIC؅=V}BO ?,h<೫?,4NoiߪW,س3\͕꧁ lv^ߺո(n {]k,Po@a;xF㐉?.Gdb;!2-,Bsw?vC@M m'hhS NSS_;U)N&5' #@/߫D- H+uJ qH?NTSnSFfW_;fȥ}AL[@M<7(#쑿HIIҊV}MĤWf=`Ϩ [޼7 rWyݘai_^mAs/ LKx仫sm]i\%wيo" *D@yB>p-o{\/0`?t₸Ҵ ;ph=]֥P@fek^A2WdW&2 \'+AUFC3nD9Q @F-ov?Bs3_ 4Vwm~﷜ ;ppTi7%L X \zSr49fl\n" L=/~v @@ҘJD f/XA$*ک8\sOofO}T-4% g(J KmVm $`@A^8$ǽUk6<\5=6" ),e q"\?8ૂVF-؆C9 iOAk @6򒲲&e"]{{,\d|w͆gm?)It Z#F@?vHDnΠ@(4l)ȼQE@p;w KQB =1ˀ;lנ[׏H9 C.] @?b[rmO5r.Ԥϥ^ n@GUW==vCخ%' +Q/\A[sp"=kl=.0uBGq8 㭹ϕt%mɵ=uDCU+jF'b-^Ypln_7C wk27sJ5`xreO<)KiT ZI#4Ms"*"0vZ !U73%Lhc }#(Ƙ5b/{ݚ?/r H > 76#|qպ'uX7 u6t \AE@^gN.!PaeDAecj}T?l.Zɛ&O'};:K ~Sj?O`X7^<Ȯj 웞fO" 6 8_$e?;6>dFCOqG?y8w>K) TH-i"@Zg6bu> A3!>/4,?rD ??}/h5Վ EslsӁ*<0$Proi1E#0cueHnE.j n|.pE#* m"dj^`B@yR `G]OtnI&D/_|N9|}LՎ HiE@GE@)q#[ g dz`#vLMϫ;1 LiϬZxzH AKF}tw#rX=,t:7=%}AP@?& fgWJT `T >Zf 00+0 d/z%Jz$A6!`߾ xIu!ܴ@GemD9l|v~ NJnްMM*3(Vl0hKk{Y6,n=2‚rr`tumZ` (R_ WP4w^.ӏP0hi- J/=6ly;>Rji>/O~k57r֚ 1~RJ ALRɁԴٺKQJMkg?yM@J- /]o`S P過08iHMCjmݡ%啁Ak׃D=}q',__2@"`(p0cHM#P3M7ԍ*Y p z9Rʉ (f9WyHM P%MW_jQɺ~fMJPd"Ƨi}kEqDi#[Y}d^y8oguEA E [/u̐g4$T;MKBkoG^ |qgWWsP_cf@H[P=K`iCk~ u~o.cu] \ڱٍt *_|tHMh(Ĩ7pk+QQ|RucZ@ $%NV=jDPހZ3@Q "x e(cs7l޺,cw)1@ eێX`]bqU,8u}/L~Qe|*@m;־^.0E@=p<ߐ֕TQP7Z"&w);eul|n^|T*#܀!5쉫ߔϨ}n90A쎍iERLs i4;:u$5 h-LA:6>~@R)>pުcӰK uG28n칩S>VVs-ŎuARv}.1>`)\WS=g~ 5%B: (Cs-¥E%&*ƺi#cHo7?nٰ卵5?2qPKS& od_)Jq0VH3/?ݰҟ8lczzP'2E~R]˝+4A -ed>3$o 왯}b@D0<|sTzX7 W }-o~L+( ޹y]Oɋ 4M7G H7=oۆ-440HٰD!FR~ROh%Ѓw:7=5HPBcݴI [eJWH1z۰eѦw #|ZI.X \ֹ[EDR6릍NiE86. 6lYU#NAv*+.HyMݤ PT(enc(ſ<ܱaˢsJ9ii{kF8 #Sj0_ܴ2v2QTƺiv{:~CKG 9B=yɄw_ܴ@QTTHWz)mҶOymlزhiK)- & Qɯe@ f5=ƮM 'T(%U4wr:E}j{6lYucaxrщ/3:=|K]JPBcݴ〯} ߦRPN?R?k<؋1 ?~] _ڬ.tPb# &p~o@EgssR.[|R?k<8D;)Ub^>17vm~A]JIQNcݴ3؅Sҝ_27Ie`>[K VZgz6#"s]_x=(PA7ޛy; - Etߠc/d^xUV+%/*nZ#p:ހ*}JY8 @3ozq0K(яf_)3*nd@}Xr?G}b ^H}krfAs^E9ը*UǀK6YJEPT%vA9;IpvX%Q_R3ˀV`xxXx 4bDl+9A_b􊽐ϕ]_.T(UMcVAwM:x dA `9 q)}f=-h S E@2.Hy6~v+/xT(ƺsa_[D;v;XؘFV[ Vyqa@y;v2ެ@ko0TD| }Q֥N"=AQ*~Ccf<#eWKH i v` ;@0sqb^MR UX Xn~IGJPug_N.J8R(5$Wӗ)};PdǨܨ Jv "T9a_u "NBt}7((\%m8pjBүi^|E`!_C ?BxMY Xᶵ_Z_Q>@4M |XI8JTvL}z>ceYs?Ww)[k{^z(%C2H8Si!P6-a-PksO' R"(=yJ< \JtRT(ƺ铀 O۬? R^K-ZUʀO!V_qW^75ו"P!qA&[1x-cWYMe=/5 ePX7}p&%#Ǔ7;<9ށ 0=/rJE2(i>{]s-T&F?&!P>@瑼͋ W+ʠnzK~Xܙ*~\ѯU@_o(W`!=5@Bud6pp= uYʀET7 n* Xر_sy;Y< Wu2Q(h۹ =t`#9,h" ij.K"<_T5*@vnηJ'H T27Oރ0K߭}/(R$uI:K,ހ9F_BO~z_]Շ+ʀ@gA,+"O %\oג׿xb]kW* 4*8 dti( +3WP2T 식L8 dHJYH@бfHf}mc:HQ *4,(=ӀvVFl!;L`> xb]:W"P(1TTl>dx ,! Yt__QJ EBrDDhMaTH_[63aY P~BS.Њ-ڀJbFngY,^:(a!&󢫱k@R ظwW @QÆ"@#P`:)  Զc s12zR7֤:A:R],N[l^e P(aCvPl ܭC~3гaˢMÇf߲(=*(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((J4{!KIENDB`OSCAR-code-v1.3.1/oscar/icons/forward.png000066400000000000000000000057741417327530600201020ustar00rootroot00000000000000PNG  IHDR@@iq cHRMz&u0`:pQ<bKGD pHYs B(xtIME )3- ]IDATxZypU}G@hE(\*NSPtQK[gJ-L;Tۙ:ڡŅK5 $"HĬ/9_8w^x 42'缛}}d-kYZֲxznf.PTo|X\a ~[c%Ec?Eϊ}{oXKdSG tu%E3#7' 5뚲`TtBkMZbm@<ӷ{@ PPm=-_9xާ,n \0X)(Vhݘ0j*4r_((0$K(%_ގ!agC]!U5kEIbJِlC)`'Z^yXp"k萢>Wu~{'Kx":hdob3a2<)\8LЁ{E XzDΠ)t p=z"MF[*L1 Wk hxx&}^  rG ˠ2G ؤTI*`x BvHiل3+@)FL<(h8ܛmNX7s< :M:" : H{@(ތH-'[WQ L'W7ɝbV0qٔ㧓>DjyUi+N) Aµw#b!DvDT `hb _dŠ XJT hޏqC˜1 w BV|Lm ~˂R,os6`su %#1i*F"^ .&6h`ƸPPm WT>:-h k >HH@yO@:LO<=ؐF:@F5:/7b*׃zqt|27LJ6\$7%uNHHTϤyq$k)M/.؂mCCħKH'@sސaN8ff:<\ٰ5Led.ns/ e;o:+EP4%bSښJ>.Kpdlt|=&1Jљ4`{/@ɘmꍯƛZh޾K_fzz eN0 8=%@FaZdd?|P1=-xo*;}QK>p|;\Nc$a@Q)%R}ho8%)&PDXh-kp *S|*{qi=5X @tvʛHZ$*kk(UnG܊Ů[yGmߪVة) gK)/0lF9N$!-VuhtHhb|B`jQFvLϨ%{_S 9e%8}Jd{MNHB{sԈѰ|~T*Jv9V\w=ȥBݰ>Q{,rj$ ]ʇo_k¢<arJD@~"L>`U[Q$'9^5P߈pLcdfĊrH.N1p"3'U2s]Wz'Opհ;GEXȑ#j́-th:RRsw@p7Q] 0mBL2vxmv}Ù} y|3QDτte\7TOk4.MxkJ})t$~F֓0]{ 4̌9 mӪ3!9ͺfjp1滣a{^H5Rds벖e-kYZrO}IENDB`OSCAR-code-v1.3.1/oscar/icons/fp_icon.png000066400000000000000000002363051417327530600200470ustar00rootroot00000000000000PNG  IHDR\rf pHYsod IDATx^콇wux9*1U")m*ؒz9Ŭ5km-Vw-KhfrF0j5" _w94gzlF1t/OxG_>ʳ8U=NPΆlF1B\ѯ]5g l^WxW])9p6_8S_vg#@+4bGt'E;zt8pٞ߱-B~XâtDT2>8Ѓ(;S.;SMN{ؾ灠‹[ius||262bK8::zȳG Tݿ8]k_}\>>cW=9qzq78S!wX2WA5 !G7-=pxp Aspt08h ;{}>:2:ڴ;nsj cQ=FFG5X37#R!щVv~zikWm-)=` ARAlEAY@) \|N{%ۛٝ\[_אָovv[[;;ۻ~xdxKHRwt,HA BR R@ѣ#9Gcc#͉d\[R()yGXJaLJtڝ;vɉɝ~wVNOONڝ̖\cYGzRVg8Su$~Ҳc5K>}p0ޑPtWW۫˫˫SK˓[;{{,}KB9:vaoxt><>#c'n[! в8ȱ3y`3mEkȑĠڭV{l;QOMM_\ܹx~qsۋ 3S+SS%ԧ㡎34[g <]}Yfok͛ay{zZ"Oߣ<⮽Gqw S@E58>l Tԉs29nM؟u f.]~wN=@:noaoa. ^㼄a~{{{neyufyyuf`><t` _+>v|P "@?G_|2@5ҲP^هP C"R#rُvvt~l,+Q! ~2P0P \T dx=:#ᵻww>v瞻t^v" 4pF`zvcz}(==ׯ]y)rA`8@wAQ YR6X;ɐe;m1,6&&]BtioAe(˧-}m" d cT +W*4VRQ` :ƪ>[\k ?X|ۯ/NMM.j'Dzv & Ư=|z`} o\5{νه͉:<>?l`e0!0EPcl5C"k5nۯG[bOwx$;/R9KEΫ{m$Nl۞Xh /' F+#A 32J+iP܎4qy?qsW^zaQ+n}ڙ &qə?R*Xݽsn]v{fEOACgYZDpۚ ~z(c>Ŭ#+ִ:z6}=F&5M|\}*ǐǁp耄\Rzf[!fEq8lBovkC?ʠ;9A)uix~GC>u&;(E!=wq y 'Q7\Iolv~1y$y XGKo޺}u ;s :'h|nsk{%߸=sC% } A^bmbN3ՁDZ|X.+k?pxHZȰ  ՏC'G,M⎤No%TF 'uG t.B%tfB;MSSR*aAH 941G=o}=es’&:u B&tdҗZ,OhxeZ%8S/H<ͭ-x]qs- 幭M|Y|i*RH2ۯ7 뻂v)oIpXuBC BW昲C) d694~0j]}zƥ ƥ:= [f7%0la4  R#f"OH8K &<N:[{s|ğO~gfbb% D+=Ov^7&soݚs}fb-LZBs@@B٧/VM<&7$,k$ 쏛×6A(@دd# ȁ%1YvmY ?$q9)ÀBs"jWC_QjHKoL'0%0+a?M7r Kqdt2U(%&`~R(s{475hyeS/^wqC e/WFB?OlK(_ߟSι7o-޼qkV$`0!MqiD&dӃȣ 4񃁲0YH|J9v-Pʗf{) >?p :+nsy8~ ?3i -N}.|;-v&) &І(]\B: iG6%qJm&ƥ f<77ی͞H4]7IS_7pC|*V-q.ӟ+_2)E9^gQ_ l<|=,sR^Ϯ<3ܙ[]_Ĕ?ԖgR2"$ A|hiJ%T #B"kجll6k;r% {ͱ|gC SŁuC <Ǔd4BRFNŅ(!< =BL"^7,9V6( } &k?rBM][B6fukS~[uߤ6: ?JnJo_ cDzCt\r#dyJ\4,/d~dd#QSUIKa @ @P"5R1!f*~335,L73R fY*snCJ(+\ L32$9("1w31{kܹ.M=jQQѳ?JӅoIŨdH/DW 82]Yfskz=1*b=d@f"bV#JkM? f !8v'$h(< rO. s`H@BuB*Bx0q[ ;o'pGG.[L=AhE7q##FRK /fgfqaqwZ2b<)>G?+3 ID'ǭ|@?۞&^|))[ =H#𬖠(/5iscҍ7/|'&a8MNo˜c KN6!'#'C$ܬm-5[[ξlAI;VHخllELBjH,<1}v+ E`C!=,$H ?dD=p}YeeD$#aUoW9 #mgr=! #.}谵/l6&fnvV{3FYÌ?0qB-99{}t~*OoڔPOp,{H @7sޡy'k?K}% ⽇vwgHUܼe3ugcɁbw *fXMhvKzҬonj#"a@q ;OR:iV?f4Cd-$ʥ sf^ 1ƔsN(kKѮʋ@OQd(*D5 ›<H)ax(+hs}Y"ifg鉦'-A hoa-c+"%Ӷ=UVO}oNL xΟ)(/WBƅ7n_胏/|vƹ꜃ޔ!m&gqt'}RRrl2m57{EI7|U++ #BPa6[8 o}JB {Ya Uj̑ߢ0[Xs@ cAC0&^9A!K#)]+˜l>AB~HZDo*y97 TbKP#JJH+GKaK)=|˿q'ɏ~ؙF "E|Ä_Dt\<2^>_Ï}`a`Fh_~sWxY-@)kL GgUE<"DI4k".=le呺ow'"z&hVL>Jicչ((:sxL?uU/o"d$PM5AROW X}¹q(a@َ/^̵w\-% 4)4伪H{@r"=Иl47Sl}~{R:(p_;;剫P8Q+;|4h#?'PshSϼ(O,Hz\µ7.~GݸhyyA:41%hO&eQFH҉(@o (! fp՞P.jV옩us[(x XLtU8 ~ %_BϏrH,Y9J0!eɰ:<\ G7tV}Q*[,7#%9 $$ͻwUrN_gLu ]wRI7\( SXBCa[Qm+H Rs&zWCP]p#=$OI/;J_pT$*A;a0h4-GoP#@νtn X{ $"|)B80pqX}~N+x RU0:iH)r JD%{J@O4`w@$`DX5u!E%N)Kra^n\Rp IK (t3R!/0W6>WN < ? k~}A`NyOD6ݏt U .  7\B~ &&Y{YE[GKͣe= 8J1 L$QjpleTJ8NЩ3j>mϾzW%D5Jx-}I vמ$Ǔ6H%1#h!ˀcծ!ST#)-8K @Pl/4 D0p}@|X$`zE v9mx{rp@wPJÆ^3AD+W?3 #/Z;oݿ*SXRds.}}oLmnbX`{D[ސ\Pמ&T1xů4ݺ0S`} vj?_|T 6(ȱ=Y&6Vx厾R,2>#z"Ȑõ äA! mla{xdt0(gS4 X # )'@-֝fq$#>AcV 7Z"LyJ4F%yi ړ*o@Vt,o@iWTBwਿqrc\GP(L344翪 o^VeZO))7p= !- `Zb-O)$}~Gu34DܵCܞ`:C!->0r"FI$Gh R:2Ioc(%s!] :Gx, H*f+j0^zSpF7΀:(AR@ E( (@x _A&}V7A BPT)̞ܡdjRF y c%'768n_wPnHԊ\J6^|34U񋷿ˏX^[AB1&eO7@zk?  @lTy΂" /@Qsyyp,@ ̱84Ê;6.O_+$]o辳W/F_?P:G,@ߋc>W„\C_#Ǽ6 .8R $/۩+WG"]`iO6~3?lLx7Ej fC6Q4EIP;hOoLD)MCF#!L N,\˟÷hKۃx@\k]߾0ĝFcOmZxTcf%,811iimH+exS/Z?3鸞,WK.j`}%InL|s7^[`vatCqx <6_/&]w ٰGm)2 )LkX IDATyG?IB/&hXamܾ뇘~Ag\*A,KFe7l-<~)FfkYoyTB悍 >$wTjiZ_OH l7^h ^ŗRiZ|^Tek?{_=k)+_ |EQO(`o I(%װXH}Ko6֛6ۊczWZd_qm{ӛT6z,>A.p:߱i|Vlؙ0NIsNs*.FWMMd' ЏT|%ޡ23R⽬D5%SCnr:ƘaU|Ľf $)-+~"3+$]tΚ |_͹htf"Zi!FLEaU4YpQ^ER?`H 1@>ٿFQyՖp$Qp`48Vii^m֭G-E/?gGZp/URSYŏ>yݧcA2 &/e_HOL8&'\|e"%o8ӏ"!i^>jܻj~႟V_dR.K\ߡ,l)͍,.e5=}xxT\]e'aA @J[jYvߝ"kC}" 3j=k g%`튕L@8i&yH9%\uiPC}~%#gRdnD)-Q|rm7n=O/ K/>_=K?5 v$lnty,C⬁G yuv qǞ=PDŽ׺v.}_ef OJsaU`>c,y8."qQz +3)N1]%ZR1'!ᢤۯR]_iSԬW󝐻2eo ҇_Wc5sΗ;qJXar(Xdʍ $$J8jbֹ+ @JPK(j7pM*?*ϾSM7_O~v''?ϗR <3 孇;񮬮N+~Bm2?5qC0"ՒG`5$@ˆ[ YWC2Ɩ[-ovd OA!y?j.]:_˩%@n̝{ugL2 &d@bgBVXR X!?֓RHj,>8 --7#H t}3Ihr,giR 2uȜM\Nd oOPme +P)N=9T]BNޏ+>?.(C "qJoѐ!H_urJit%xul%U`Oh/tr8Z1& \dEG?yd$„ǘ|M-BzLn?޾P1ß?j.'-_6$,))13oߙR_U,?S,tb?/!/jn*&)S^ȠpEƬ=%Wc>&M R9䓞Z_eǜ/O?$'hȅaeI'OH UT)H~,#/-_&%)ӟ^!ߟ?yzYoC|tw`;J{SЗe>GfB <p ̌Ь8Q6ܷN_>hrSA/y͐"_4Xق~׿ 9'JͶ΀B/gnX嚽 3'ÂL8 }q%v7 tWνǟGss3nP$8?zV_*5QDՍ潊;$u*~gsX@@ Wcp'rqG<F/? }զz71t%p#Ta]dx3dOd!$s,uè7ΓυX$:ބ`\y g¥JV.™@߸!V 7))oOݛYxj-= ?X_~\8+ʩ"#JQDw @1;_!*%wp.IvMhBZ#@:xt%$~I ,fΊ# `"6=M} BĠZ".#<3R(Pk_~zzeR}){ Oe"/-7!T cFGT{40𿤁"^k<pQ6TX|+"sèz YMӒeG]/}i|rrw60|HB7}V2*ӓ\s; bd鮻a;1 4>xzɬ{j굅~iUV}Sթ';!Rcvnj@P{&"pLAANվt&@m͜*:;3GKo3UktmkϽ!& j!4b :pK0;Q:U<K%< NE_E ;w'j{Bc+(O|յ0 *d\Iez^  9)o1Fӛil5>ZtkwsxʅW^C L9Ev>U}3h,A$& x=XBzs956! {)x)0 aSbU>oUQ }ؗrǟ<=4G)"W`܉"[Nj /SK'ZfMI`3Ss"Z yXƗ?k sU y*/ OMhv"Vx:!PϩՋU֝@z.Kk_xbcwմC%&y%cZ\ PFrA8f!By3h3;10ޤgt 22\hUF`+R`3^I;:ޘ}XrJ!=#@m>*Rcs,#p.q Q=~q.kN ǥʡ <tab2 3"iFmʯ=loDf)#gϟչOp&{jieer>$A$xX8{3JQ<X\%[2Qcbl@?>;-xNv(Ho}K+b8?/~>D*yd}AԑQ,f!5}9|Eas/֌3G4b]RQ@u챌lQT~QJls~+ iQڧ_ v/޵:Igr;N+j S pQHǍkfBWBmڻϊ|UQI+._㛯< |!; ?+ $q[H#9 ?O~^@׷uW>-<}%#]|{bї4/!ms ԭb,XdBtϳ8I?"qm Lջ?ݟFz2$P`ױ)gQ+؝*Ȃs*Ha!5 8IY<=XQXFs½@eȋBȸHs 7Մ D)+D\)ʪb\yxiq:m)5rot7w˯-./_\;:{V#Z|W=yPGo#4GYZ4W'@ 7&JʉD"!<kLRi}N8xz$ASuiI;fqn^+N%fÀߩ@b_pa`ĺ'6nAi *i'L & ҟГUhOyX f!Xz|)uDar Uk[PLJ }޸Lh'%jeFeh}x "~g\P>+(-I*$#?c^p+ 7|@᎙SZ?,y=M%E?(/bRpbTQգtU]MW~?W/_X:#m A x(@_'?]7a!I=Rpz^ Ē[?98X]ns D2 +܋f &d ڣ|HI& +GwPHʥZD~0$6'^=RSŽ;=J-}Qbܟ,F= ͫ¨>UNyF%yOhq\ 'ثyt^ @Pv\qL-Nܽ{/G?XVTW xC2"{.z>dik-""At,$ྗX{`*kYMfu[AIAHr/^ -/l=$ Cma;,Ö4(}JW&UwԎ]1+DŲ9* cUԀBb^½ lΕl/"‹$℣aVϾw5at< gV8)hB3Kd`I>UQQr֧4^In:5j*e ]7^[O%@`gYPTʔfnƒMg\&8G ,& I㰕RzDL2ËPXHlk,; :вP~q.<mъEDI&,͓<(P,ktQPBKmv+b, 9vJy((= Y( *(Kࣸ $p]*ҤNrP '`/&3\I+ctj]r~woh(F(PXCi$&'D$ k/ fD̙xdkF쪃չ'/]x0?[]ߓJiCƺCta "P>s(T_A-TۏvF!,@H[I Fp ٶxtC l9=ɜmX/]v3G86>IX,(/ [<*Y $˒ XTZ~<.#{4J;DOT)xIeFs#Xxz0zq%]4E6J<[2/{fW(? ޢj^qye-1{IgY@T>QPmgJn75]:,%&X*ܘ#7Xޔ(7r a&[YSqrj(+X@ ~ܘ0+ B9F TxGsF#DJ᫪K d҄v:GBO]#ڬ=֔ݞXaӚ{g;Pk )]kpXV6LάPYn;49gw.2i"T^GS>-+!̜HiD2I k>|܏ F0x(Qtews+_JuvK`1SϞ.xCZpTIY 2n ^~TER؊&Sj"8|q#xPFQP\JQv&b#C%44LL3$ICqp ^&}[I@^xQ\ZA&7c1i^_s…s&'QT < *h=Hq,/,Lkpj HT7Q|E+͖VYcTz^cO 18/@@5gUex(D -ClckBQv\Q8~`9JP׬dRnt`~:.vҩNQT[J'VH_s 1"fml7tφsRcKݓ bJ;X xgi+ MC>BS9 ona37]taBB1")EխM[*V|aN:m(YM',82 z,jj\5oe~I'}DD`ys=L5~4&( a>s+D׆;⤥@}Ƒߍm 8Ȳ5΋(<ˇs`9r]p) ܖ ʠZVG`|*G~fV\Cqu;1p)5웿n|JߣlVICI.pij٧oW_QVw"̼y[ڥ<$sDzftWzk7nAo񄷟gALݟ^Y_Q9r4֟6j`pc-"|XD 죁'N,Yt&FPpdL7D;0b+ a'CJE&ABavGHU2Ѧ0X 2 ﬔD[M̘TVb`w #[x9H$ %%qyMxSrb_&4ʠ9Z(ǖ⧟%=pf P[N.BQF(BXYsHrK@@3'z&E?xA'|Ohkͪ xy:8D P}Qv#5vz{5yvuɰVNO! cX L@zcXZz2@(y#.˝ Fby"geXv %w =e(^}-!CbY`u3G{Wsdo3R[g Maˮ@` -nD|o87X[hJXZ/v*$*Fiʉx@T&rqmRp-,0s&B9o~# Ob|/hBry#jQx $7!up^sOTyyoJѳy-]RnwND>M}>+/E=9Tl %Olk>%F΍#l0PNvP[d,&iʄA,=pX A !4zf%Ko7d%_[m{6owB4})O_.GAԛ"~7673x XQ:b$&2n h1ݾ6UZguc>g&ϖA@2 QgE;PJRoe3 Q'_!E?[c 7X PnyyYy!<0X"xe(O 4聱cis.\ApU!Jߞa`!VkcCL+A u:wͅ*RxdVX-኱ád|z H&VM$ \ے /5o敗^rD#(M`0MFxx1΅Xs(opooݽG<9|PH{->͝ɲVw,W7%8<g/PB]Y&®;"T / jn>/Q &s쟛˄rCZL,ө53g D} #BI"pjl22"uH۰.A#L 5󞋕U>]PZraEcOvIp/ʹp K* Ws'ڔ)Ѿn_Hub6wf§뗵ReҜ ~.owEJQqkq}0z4njo5o}{8QǕ5R]iI^gELJFhF{ozufzv1|{&ډ?5/>So]we}P t;?I&2TԲbN"tu/\qDݛZKAX6qC0L8 .dԳ}Oj>8Ȯ;5 ]杓a3 Q#[iT@WRyZD9hiW'rWx(@ms* Ml""}aw"Zw~4cz8SAqu8Smսr,|R "2z|ݜCHLah$>}f+7{M_SEgPQdl"^w=\EOSzhiRDBA 4zTcb/ܡ9o|}n~3W.ll77Qh(TuCa+=V@9&{. !HX0U}aو% KʘYX3,QM͆##5VnFMIܱ 5-\ &93d|wr[\q^][P(C<@ٖfGWC]XSW)wǣG)fU>vs'_~O5i GnBWшރգd,[r,æ~wvv^҉ G?OĸCvգ2 %@3;4*gdw@w ߐkG'*n8*5 ufA2vtroiF'! \Bs 7P OPW!O,?eý>sCՕ_y~W_ow$ؤ7&p o`]ˇxxA\K{~MmOi* `bEe{CbΧ=s[MVI#ܖSL%s@+E`24W.r w}9h?X٬IS:1nx*^'eK (0ږԿ-AthmV1E>j)1/:S-d-~%*%sBгȉ'ʩ{Ї(I]2Q8j5E&2A C۲!iܮO]Ucgȕ 1@m(+ӨCxde2|~fK#Tr [10BӐ`}~YIESfQRhNCgx%Ǐ>x~xXZ#46k#΍Я^Q{U} xcaXA@^A~Fs7tYJ#X8[|({Dp")ƧV8:OQ@ßUaDz|ms t"2͑:ARFed- #$IHBc.P&Cr _o"3H=>3ijKٳx2qP$'ZQzԋ࢜rf ;CsD(#ƣ#+ReE_6͇{vuߟ7W\T"ģ(&^%|\$M%#+:|4EBʪb\xk[zT>PBlnn]W̫jBb_-s.֭iRpj_{"!*G?n5_K͹9pQ;|.={ELVGI7k,xg"J|edҨVZv/9.ޠz54Y;9nmB6J"'x$lǍ\<sF5:Q %^Rq]ıH sm2ֹ%W,yDp\P!X.{,+:0vÁm!ga;?-zFoQ5k>\nsi I8ǂQj[*&sF#ӱD d0 Ke988\x/|%uH_=Psv}còK͑9B@(">^ ->%7Bp]@܋2ܰ\Kb'Oo^k>PFsyQI 0JT# 9VOT!ebKH='P{qzjQV ,.wgJDϥ(_joT(̦Έc8&u BYuY">n=J(݆͝¼Ӎ;Z^~UcBT5W r׮6~r4{r3g L“õ:尛}:jG1:]x6ĵ亇'͝,|ʕ ~ >!OK8(;uUBYb [mZ6޲\6֝==6e?@T`|4=b/'*%oו58ܼyW9/^1XINJrq~;LS8g"G"2. '}Te8L~&.<zjnM:-Β ϶,# IYsxq-[JkoH'H@) [* ~-eue@?XzOA3aUFa!z:و: ea&uN&WVgtTG@ @eÙ;M7u#LLL',㰋CAMeuFM;Q即&ɶVx}-0TڡyI, |5ŋ_\3e{^r3.Hŧ"Uz%ޖ~2ŢFIfa J>CFv%ayz7'dd;pNIJ$ِp$!|9/aG?>*5#֠3;S}LzFy*H3S,+Y~^U$n{~|kZ¾By7'EY9&ץ{$6ͧi{J!-JL*cjk (J`A3|'sXQx(BM,//k]TO[K[0Wy-H) l^ps >7,¬*3Bs Z9!9-E_ #m5PE@/3[sҫXwʸ0g%Dx*吉xvN"t x~}_C(7-"`(IՃKI܁K}\H;ݺzpWW/Bz>G,sAE0/dfX7пP|u* dDfr+ z}V mxjOKad@c˨[E!ߺ -B҆mx2 n[F@Q@}'z2RsVI"}y;,,57V>9/tVo] ϱ|г"b%#%`AE EFtZc9F -c2*EQ9z+ `0pަ.,V9VDž'TvnNsX`eK[@ebF= pZC(Y^]j>U2^(˯4N4=0$uiyqrii|!g94ߏ'Gt32 qQAQ! QVZҖ-OS4_BSOG| B{?XE;46 W O:i(٦If{w!$u#XOlFDaj`ZWR 5X`aCV~i-TB!0]);ۂKͭ{43Se5X kD&\Sme<FZW9 sNlK 3 oMN(([E-֎ϧ w4KBBU\ +d 81%\HJQ|[O H# OO6kexIeyRnb@ þ)FJ qr_te5Er+\dE*Iߘ_HGׁj*MAރe -Njg_RNoǔ'tYQ& u֩O\؞ +5#Բmv }$@(45n&rVLfΚs3j76=|]᧕rэ[zfUfs)2%ɔ+> Qr9^V/?wsACzE1Rg+њ,Hcq8+ U&6fDž Dzq٫RMެl5eo?z,Xg32 tErn\%>!B9 wH[q Ep֬>QvI.UٵF}]9J*]\(oاֿdL`RJ8q0pNIO RRPxBTY>(6b:4FAW8pE~X{oǸ8PHRTK K$g23][hSYZYqd[W^n.ϝk&ApFx9'=9 eʭ2(H&,LV8-)y'`ȳjE'6/ّ c9Z#-Az2~JNVJpEs{&6'*÷*ᓒ~@FcMt3+@E72a3}FSuD<6Fn,kS#(_O(|Z9?+Roysv;vTP于y*})܄sR sz*i\BE. P90$NxAQpVX/,edw4B_D +O07V/H9ŰecãH B^o,*JsSs:FT璚"EcZd`V%93f̓rGTc8 J#% tqWP8b9QH-7Z~z0Liyy8ǘ~gsgpbO%4sWn1`2'.5k|ͫ#8je>!,nm pBkޓrTq.KH/ E` ¬;֬rpGH쳭> DA3B;15obZa]G[k"WfgK YOȥ!NG)E\΋0ekj5RMH+wk}G +bA6zrO.!<@7f@rXmwrQIe4#Wɹ$ X:H*:DQMdTWC<'s\@@Fƴ=E1#PL G+N `YBkj m#fzW&nGWHg:LnQa ;LVsR=O}~8;!H5O09, uXv,玚 6?z\&cBe,XIY4 ]%5,z+'Bw%y[fYbE{{M ΃JIӉXy5F:8 M{AqVL{`HrLiU~F Ocbc~.p~9@:QɘCVxBAFY5SW˥y|L'e%Q.sf.]e_}4Q"d dsUA Y183FI5o_mоg6m ʲHPI\IIgKlyo Z놲E(&];К+FX0@SLzCy"HRS[8\j![ϩ-$q2!=C8&`8o$5D(2cX8SASh!cZCFAH}M{gM&Q}hGS?%+օ)b)nUx>b~ Y(<̔p[%"cU@IBxA<)]Jp*E=vjCIq "<˜s:e܆oUfNgWbV˫S:'#{[k kkYxbۛ=nUG})=Vo@pB}jyuuBKzѝ7ʷēI͂^S"./kd_K9=-2,JC2=)wm$H\k qA)t]Use˄1Ɋ=_uL (JJ?70>!-5-'.AUN<9Ǵ,;w EPVZcz2> V%(Faopm8y<7l}1 % $&%,9XJQu7g$B *%-tbW 8ϒ`eJRx7ZȭM2N-j |. h# qnJgmcH$r!= 0Z t}Jx=+Xp@ !&%i zrh;// ہo_ P,\]ם[!7lDY`Vc-6ϔ$ dPT0>)dae9? EEigiLiBߔ-q5yQD&On!1*),̊+|˰Ă4G^ h'' B'Q]}c&F|Vj=ϵ$BJ/.h rlFp=dkQQоD'Ğ]ޤ,LSREK OycW^CA!xrMg0V$,v(iDۛRZ_۶50QM tE>FzBL}qNE?±N~ooVS Baq cqz'XS]!%YrmVcFKXrfMvfyW2TP($%)qIɎ3}p8v8a+l2%EdfPy޵¾k$iTV<Z׮p'?F[I3Nl7stT3ϛTZ#3>|=Ws$*CzfKfm# cswTlc~" `t~n]]6kۈ 3~6zޛ*^&.u˼%q}z\qĹLQC"%\;C MXY1]'BM4IMh/f⥣z6 Z_5d8eE죯VQh_=\ Ů :c#w7=Q ; QA>,OOx6T`RF:w*RĶw2Q?zj޿H͓GRa +rnAã=0H Uض5 X3Ajd㩲*'tߥy)OٷsM`- {qBIbTb&]i YHxfgKk_^w0w,*(r:X=s 3| BĽ7c|GZ(8Ta^'YVAa|^M!݅T^zu/>=x#? 8h$GBxz LeZUү{M y1cUUo .;ӌfqq nvbMߓ3n xO`8C{$Vp-so:Mwa882wLYrE'7¨ESZ>QM>7QW4GZiWP'';(!E ߙ5 daa;ayo=G3[mgk3F930s^GҪ1=o4kU apB͘Q7S؅&¾=$NYz5E8NDljfO?_w [0d.@n-e-)׮H:|D-U3w]L41|jC#䡄ǚYP~+k Ǩ=H]=6ɢiG vIshkS,3Ԫ֪*yq&=R3:ʑF8ܹWor-0nYरj4USߑ7qDNF0>MTczP6Q?. Cq; PXݝT59IyG9~d@]A u}zOl>py lIc)Nǭ ܌Tg_G0*̡/M֬ L;s߅"x=J_|uvk1ȩF)r v $??=~<97Rp`0I@J]yօдP+kژ3?O E9V^U#]M&HA%;Y*]|4hRi==  Ú\ jklw]CשFnRu5=?"C_K{T(;N5}?{[yL¥YqjY̵5cR^l2, %O^5<[_f`u^HoϿH@ }v(tW5l9Dܢ0|Vb; aP%4M53ErHQgxcb M/WCyܠ6ȱEcLJ-QP2Ӟf"awGxPR,Gτe͍ԫO|Q9͘[9L:$Ad@i}GjI(~TQΝ`zmvx:KjШ"93"3 tڛìSz8DKJ}'?ќn$ρ *>h2GgO lkF@ݨCq,Lɕ`$=y-N&jZK/I4&J`-(1-qrm̑d}߽so7[k6_ʗ T[ P4XV J0>R ]lJwg*78-&khJ q׍t;_}G׏H&nH4#<ރ'Rs" {<^,vKoZԤ1bLJQTXjq`r' W?ILOUSIF(QG~1HHxI}5T`3n+SΥ~yxfJ/<蠟bP}މTggcY쵛&.ù*N? % 5F3[MJѮԙ9"ikTԌA3*-g^Op/I10AfP&hԵ#ߌ Z<",kͅpו)uĝLc ˯0l%y (P& L9$gX')]>]sh5ӟ>ʽ|;-\=Ӑvc=XU,|ƕȆ6[4HS$؉q,̎_h+yy__m̛4Yzrws;:bQ;s *"q64Uw2모kS%s.Dm$4\dî ckWSF}/('T̑GgMupQ- T&e,$"afmYjP8&[υGUţ۷ czLbNu@^7Bϥh_/jSڲg:G0T /HbɎ} 80gx3Ka8v`5HopTBg,R|%&DJcCYe0S`h?vB6R&"4+ivY8_~aȃ7{qHwNzJjO o:YǢj㥏 OnIvy}<<&ikf;d<R˫؆WUk`9`Zӕ_f+뉽aH}}d4N<Haazލ2H瓸*ŕޮĘ"ܧLa]ؾ[6u^!LC"*mixdv/6((08ZЀdǭ'I&Y5>tC[2 $̑@ĪD ]ǘLaP$' 4f]I\~ }OTAa3H<5C@ڄJw9dz+^2w&,dj㘓s/O}|f\=9A[NC_RKaW ]Z9 ͌;pbGM?^_s/sCz7 CZJK@L 8o0tU (ќ@ndKaTCco#nޤOFJ=R=V| 5 NJ!4խ2sTxiuy׳ڥ3vI3H=`q%ܣFPPp$bJ&8cg fy5xƔ-*Uzhg4W]5T%x*wZyc/}zo~u9;"_18鎥 TqhIxqZ2jl3>oAϫp&DR֮f>nk60=ƅr9h7oS!pR<1tayگZJTOy'.M-$0M3s G>xBʼI' 8aIJEL,HdԳzn[UE/9Hfh+ 6BBzp0ƎU3{WY@f^(A'60WIa~v/eGx5 4IܭH^ijsbwm$j.J]N8`X_8l /qBWʇtۉg )k(;i^ĵIX|Z+D!tZ{Fs>K*o8V;trX\MB!G&Sro%CD78!ٗẪ81)p dfx:2݅:c}uZG`v |zڃ=<8`85XW u.%YT: a3I 5i OʭCO#8yzpDBܗ Z̤zȂ&D_D!RFG+d&JH*AHpb<>RAjo"d7q 0'#} Nx%1(EK<7d(^_zc2]Oz-j?RN4 a$ LVޮa(F\2*B[y2+RgK* UZ3\XSAjz0pqDْ/>vvWT1 喊N(p_L}(AT3u.= F̶C0JE<:7<-ko8☳~2)G_ـ:yzA9$GU֑RiIhBXh+B)#K㬤S7iH cgQ| #UgܼZF=%6Q1_H@s}\2ii-^}o)ڍ̇z{,*- q5~OcHD%74pOp6_5XlvuǮUjpN~+RT!:6YX|ke,ՖڧH\ƯN$~PiĒ5AzOsia.?Fw2A?ŖL"qqv{v*UڨEVXv ^UBvI;mʱJ%k>`kcOu#378XH+d\ X.c B󀎄P_PWKyFsS2#sY'_~Bw@eJz48=ao++0;7|3>cO| *R`7xUCvJdx*--]!#JSfYda()Tǁ :jtܗ%xY ɩ2юxI.IFWۗW4nlv֛oSyVMۊ>nLX6 ᰑG2-ĪĬ2˼u]s&mhgbq_-¥6Cve|&5vaz4P셳Kf!޹]'ZRu1jwg \Q#IL F1V9 &C2q ̸'`X}= 7/c fj;0 m"G uP% HTTO W[liy+o_ga !hCf‘X7P8SZۏ5蕵# %e!hI?&"k ctc.Ȱz1)&Z4k҆Rn=;vm|ͫ0gNVMK! i&"ը$Nb,st`Àn|vk\9tj`>w!*@| Z 6~35Og\Ȍrڠ_J*m&-%}y uy KM3)~ fc/S:jeUи1e¸a}c[Ca'fz$ɉ=ydOk=JZiSBoM969za0E)  u _HӺaDU: i0z 90bp?;JB~ >%ob?gO.f7|''I9*݂v TTn-ZR{cQb3mPGcHsɿҭ\0Wi7='kҥsqG?n]j*0k>g}b;オ]xa|o6l 5EnE[ՑbN:s-F4&`LY25#Ay  vL,pRfC;Ǯ޸v2@w*>U|o:( :n2 0#+V-sV_+ta*t~ 'pa kA=Lm(>zZ%2v-jg-Y[: ׹yT%Ǵ lWٲu"䱕Ē{%ˉF?>6o_]:wަHkrnU/{.{l[5VJ[ V5 * 0xϼ ^P˩` v7K1>>"|{dų5W#iR?J@,w~[۱DѓgNn}]Jc~]{oi]]fRnς^ K#A>nF`˪mhؗ² &GEsN<}r,OŴ9 -ॿ d4K"X%='ؘ1Tk $?qcp9N.FK^y:cϢ. UK=EYֶeVFtvfCBU%m$M\Ӄ4bzobxU2L 4- Di=շQ# R[ g1Dm(s;SwŰohXMDI F'/NSf(,zoyc?i{eܓ';}_v/!MڮѢ\3j[]֚>tRAqqN-D+:L% WZ(~!rIzI,qZrʂ $0LIm*Xqø҆ͦEseB2; j9Myl=brN=8xXvٱw Ef>Õm~Çh|CO}~T \vXc; iBegf)hZӐjSi??N_8~5ӬF>AQ yƓCW]-UX-vf0E5!j| 6R\v eσ3׮. f*|!}c}E Q{,ᡜ2)j[=\NTkC 9lN4a5E6%TWI6Bawg 9Wᄶ@jlURl@0sz4j߰:`HK$sq;qKʗ'aƭg ]YI3 ᛲl⪾&@%23%x3Ƒ95nSFZW`Ye**3 ѿ}[CM7P 1F+c41Э}8^@2Zs ,'N *BB*ǟya5T\5gA$"_nv'U MqF-.ÀC-x߈sH=T( k?;d@W̯Ṅ[+0Z ?WBsF˱}NRĻ$4 "Su@ ꄁY@&p-q<Br&@U7 ,+!mFJrT;} $MNs5ƛ߀0In}Vw#) >˅e<2U}veeՈ G -zb!Pz%fKbo0R+ؑ#.! SOH{zl%Hrkk{KM4˅..eJmlObJ *uy:ܺ "}Lwy7LjD';O‰3!L2r*?n߾a|M5arUe z.pB`bY#!aN`YZԜ+`Vh7L|I>|ooNs|}:ѹ:uVNYMVtV:7;?u R_ !x C7C\^nakMs^/xXP u .W#^a~>,7D?̘8Qih|лQ[yx \.Tý;>\@GX0cMnCƳ'>x? vA`^dP[^i  ɣNj &3}+3؜ M IDAT9x,?.DfSk<q;ە/n?~/ވ_|MR;{*}q) |f > 7n*.M{-; a4W?@Ln 7^V9S@%ǢmB%l}Uy%&7ʏ}ȝs#0;Aػ{H9U@ʸ>boTYߕ* .e,g`JA8X w *1ڊtG17I904I5"8 '!G \7Hek7P`g#1(Uӯ@8 XicmԎ*$/Ro{w;#xO M#['=-Żg_ߋ1$q {IjRT٘=JDJ@淚 :ێ޼us/z2ϱB'"O^u#u9j??L(NjgW-V+J0¾rb Yߣ9d}jQA$[&?~r1Oɱta8-\yU 4g}vc!kOR*P"[ &fSU*4j6K|M#tR―gKO~'2{9m^a2R|$oSD+h~gׯS|k;u&{);]2Lh O6Vt}|  ?'TGD)é2(jpJzkՔ8)@V5Je:p½*#0Oȶn\({`A| MPǫxI |Yp5e_]wc\nzuM pLb1u4Uc/|bx0Ͼ$RpLEß~]r-[w #> a2 }}w;yF~l-3Hgc3#2V#i+;W~[ BaCk>A|MAY*[>A^8Xc-ߩ_5Y9ѢfF$K= (\,@xQON{ԁl~@Aw݋;qE"ļШPG%$Q˦Y("N ]x" F C] Xg| Alշvt]xDۭM.`Fev|55{dvR_v3M@JpIr\% ׿B3q9u2.J|J:U_C<~DtTdfD8P;z@x+/d~mh#@0gG}H<]dľyNԳgmEƢ|u(Km6~/(:m:-|H{/cAsqP4Ɣ@avV\qs )%#l [dC|V]l`|J4J-?/ܢCa܋|^ʩڛFdΑ@<Wnmˡ5x.Ȩ')94Kotr4}' zk |0(0rU!5NQ ]ێck6Nod#;s y{=jSC6B12fej^IllINKy c{֥zyVyr"ÜxbUPAڑUL51 5qi-\z `1fux j -*PS=5f'|9dVdS0z#Gi3 شΙ/Գ1zo#yC5Q k97r_ݥR};$j->U 7NGsw*Z-R]KtC&ЖD%`DqK6,B8\t8]c|15/d`mW$karawjm^>ꤝ}_JQ -Y#GI$sw!Xe*㞗QpA-qTZS_[oEςM<ѸV[Nȵɸ\*qlQ\7kdxh,meDqBlܑ4m`ľge-\3>+`';qmq(G0/jBeP;=ɸcs{bs?vU[v뮗3ǗNy-8{'țiG~N{%4svuPPE C0 p34XJx GsUM\!yTKyqyQ%4TM޷) I0 {tD I]UrjFCH8eHF4 $*vd܍w0! LF,8>АL҂ (ک8$0RkǗф\CMzZ'\3;Rkg_ ϱ[>y0-z9T#נnBrr"BsO=ɖB}:I (9ՉZ*ͿGhcd7C1jdyPo2@% =S0:8u9 2回/`)2qn~^4ExsK'˫=!00jq}Q-\-p6B!p8hƦfBw!.A)CZ&!=L~vT6!8i\_9c&\1>O>P5($JqBHD0W6RaJ&Zɀ+ݗF_|7.:l'wS=w~v=A;mñaثկnn$K(ͰS7G)DNZ?4Ck4fKOshsLJՐ+Hp~@g+9eD}Z(Kkۑ4KrZ X\5Y Cs!ag3D8 ihS&odއYIk!oJ!ؙl*[tOCOm~?_uF]-I͂0ؤԀc+2|1 ŰsH:2=H1;4g杏?lHcqU-b*J&ε鷀Q"N{,= 0%ɜNM^Ʊt,@+Sz0AFs kp>^vsT?N;2_Js_@MLж' k8 [%'MVN =}Q]@_ܾs;s_MKo v`P*̼/u ?(b csQ8Uv"R]%xbZ.U`Mq8O4`QMص-͙cpv@klQQdݝ6{_zcˆs;o%LsI-kr9Nf5#+0d2mHmT {<T)!㧲O" fȆ9A LπꌓO3L UhOǯP]DsAj\`H&^ksInKZY˿Rhh`r x}~oH6I=Z߉P`T4g\s/im7|O>܀Rs"rǏn8wjTd{f300H3VJ({N7#Dl{0LEw}+e($e4FT&_6~mg†np*dSb̟pWs\eLkݻjz #=W Z"uVG!yj[V69w;DVseao&:/"HyN\yޢZ uvMNrQ%g`ףKDBw/!R9mmW/_ؙH*;50@<`wTg=V@hGD-nkI\Eb'tA(@W<7#8z"$ï0%(OIR&Z#s'-ZLWóK&X5u`PY25ky5Z e]*V%aaoUjp}Kvn 6gl}dGM}#E.^!*lI\VP"sz3تCJnTkRyOnCa:Ɯ# QG Hh(ۛu`z򓭪DQYhcBe$?>O#q~@aװ0;,pMJ}iP&q* 8 ER[.NN@1 iPͥ)s `*MF4 jPr_-H&,lAoS/ ]?/O)?WXQ.Va?%fiw PyFQ^:^zJu[_g)kp\bmgv9XPpf8w#&B]dic˜|ugUseW B9{:xAOR#nk8*?=sBnލڟ6xad ξŒ{@ ͫs͂ ^nӃ#wȵ ylgׄ&}/ن{ `Fz˱ 0iO}h7o.u!;m7k>77._5; iuK⫑ch_8^a̾3̠Su$ـ ieeɧ &^EfJxTUsޗA k3ɽ=5xSm$|Q_4-2Cc~|GKYz玭eB$u[Vbp^gh@I97^݂0" BD;Ŋÿ2=G82ʈ7t oWWвs)#{~>fKZNw#뷣~ݼv= ~> LSkN'po;}6ҟ1 vB蒓qjO=2I3(*H1FE֖Q74ݺ]BxVg?W XώV@;aI)tVl QueW2tRipmk Y/mbpP,P(X˹kE]248&H0n IDAT@9}3O8ʸ\gbbX˝IYصqCwA%O[pWV!Q͛PbtEz:QUyǏf2Vq7hL>I$pLeaa%A&鷓k&'-TPY;Z 8? ސ*6p%]-V0`!AƳoηT+y +'Ko v@S'O;{豝G??viT]J:g0L0(YGx(ۖ~h }QaQ_P+P4Mҳmq/%_˗=rf .Ǒ&R ])ki{3f9n}/oϽزqV Y g.;-x+1*,c[^]A'm{|{Ց#4+TN<8< 5+> 5SȤu@U7WIL=On Ocpd!5ڌee ڍ;o[P*>zr4h.HƯ2n֥k??sу3iUIZ3;dV~ٙ`GU& QmsUiH)5WfOJeʟhxV}&XB+uWX24T8WuD~:X`x@F'Ss@+8֔9[ξQ593Z: 8k3SJbDV^?]e0g`sڍ^7my;%4NF5 -=>4@--!oZ|v칯yTYhaQ==d"Jdx-o;c*46ңoml=‘$M/{2W#E%ozkr_~*(+we|8[S5PrNR#p"+i9~8 T`^`;fmlf= ]Q9s6bM ؒ| I7 A5",@Sm bAWqӯ=|pZ!7^//Wχw~[ nLW]R;ɣ.!QF))~nO5JP%N@/lc|$uG.0+V9ށ"ʀi$94ǜ6,c_ǹσ_ۆ /Ie5GژQɹM!KTqˬ ?ˏ?wf)5B]dyD[ҵIMƛ Ib&1tv؎:};)Z@ۍ7lUsk<?уlj*0C'ctCiz= n'շIh{ r[KkI8 e^.~4 %lh]ӷIv% Q~I/Ϯݽ[o='P,uc4ؤc˰jfZgw5+mʍKb^|zo0=L*]-{[o~gSp,d0ը]Y<&Y~l@0ȽTB4}(Eb4J|Qah'I!ԫŤ+m*B(st^K HJQ}Xp7Jfh|b%x B @<CY~U϶rEF QFBqYƮk!)zcTK{\U^ՙgTl2RG! J N?A6?/akjhU|kCMSZ',\eDC}|2*?&CM@OT lvjl)vQͫ(Q4ksN~S+@ wc{ tFj|:B@ vxI$hᶺ2ղ}mƹF$e@t^k*]wrVбY(PjS4EXI!ZryI\=DuR>S/jc^}̣Tޮ%V<}>ۉ4`V[38c'6`wRhtֽV:уdq KS-;Q1 ц/-j3_emHDzV.Eњ!L:P`=;罷`}dCqc}>߽ǯ-4{ʊSF.z`X (nZ6 X_g:+ԋh,.L{'e*hcFxg|%Y4Gv9]Bڼ7эG<US;_˥չh{q-!Zygæ6 c 6;>ˣ0~k@1$/eU'2S,FN8 W4 ݾ{; fKrLV=]Oz+ Q|NǏБ_v7zq?ñiaJ |swם>$=ûSsp7US3r_65'd#u;nkh7n`EO0?ގnjoq"(nj"t6eYpƩݥy3PR=\[%mr/ʻⷅ!Jm%DQgD)]15% xdZd@ ת.Co:_B-,x ,]hG;*el F?Q_2Qp6BƀFN 󜵻{ hd|!>c4 B~ī{ TU&H2w45@T`kϴ9Swב4g¶OѲ=B{vppNl;լ?E WDo.|y{©CCWyxA$cWͮ_aB¦ryW 0!@>6jU髁 SB)]<-0aVZOqv xqVB4Ml=%Cm;o*Eu7 ,JOa`ץ|E~<{pͳᇸ{ev:g"yLwn i4~xk>OΜ<]8v…zYۊ!z|&[p97SeAɍP}7 d\,**hi~I ^ͶTb^> e^s_: L8HBϓ><};}??ձ@g:TrR[}ڬAVy% 5JaS?]sf1V J@5"FTkM*Quu}={Q>7ϼ/n?aʱ乭(Cs)zL/z&cCkwf::XgLoܛ<7T 1 N9K_|Uzݍi|+׷ /noomoOSgc<[9tll&m1|~= 4#s? MǦ̋Q6&Z"ȜxQJ 19&p,c_jy1Sڹ|kR;NEyph 7 +E_ Ϳ'~h3xT2H_}$5"+p3V͚Y#\p~a%Y756 xk{>3@^Rs2jP1]sخ =*.̱idfB AiC/bPW$4IkFYۚ7wyr i/TSM(֥Z!PhjDW`^x{p;w$5a/l^;];ҟ;2'ik$k)XƆ׷/]n BGSP&2)(J9|2[y_U{ FSQl."vӼ` . 68׊u%4~Ç)F/'!|/OȄ#`h3QJucMBk H?J NX(LL[zxt\/NnR^- noiMGWsK0 vW}>LXw 42E&ɱh9._In!H+>!tm1Rޮ;~Os8)*Gf33|0;okgGd*q+t]*[ׯmW}vjT['݄(T.ØR1 N@>k&"_`&R=8 1-f~Qm=˵j<6t_?;VMjJݷ޹~/|'^t7sHL2p=Xx9-1q_c<7n׮m_)Uv/Óǜ?k~L% ?KH5ބN ܩp|6;`QZ+bٰ,JE,UR^5yq=00rҘc0AH4__?sn߿Z8?q%R){f4]5(]eJ>#M 2=ɷ'NA43^Y*,آHŘD;^K|#^a+tQMix6WU=Fm6)ҩ7s8_#K> ujGsTuoID/_>Kw흘>-ɽfmjjHEZO6 O\U~diȺVkt}4jG^Pj5[P/w@(AaS fOM`7 /-ߴ_:y?߽ßO?Oe Nʯkz,/!So c4_ U'KuE4Ir7D$B%ߘ7:SVhҸ24 )hb]r͌T~%1D^Dބm⫙H7{RyV<+wC,~?6<*xq=;]g7Ξ|uؽ0zl]uX*b܈p-~9_\}y;gODun=6cMmæUIj:f~8:&~j|5!m94Ÿ~ Oషj)VkFQaMӣ?`6Ga7uwo듏=  (KAJ H]s$N<^.S܄"mHo VJo|񅌯k@R:~Ҍ#fn Qr#uYøA $e\6Q[t 5NIHU}{gLT,qȎ/ty?ZH$ĆG?!I '_^ ÿۥ0{aw''v3 n/o\>0G&"\SǷݨD`NuJ4ъ0cx#D6&{_ByF*4j-eb ў,36&#u۞0VjGbN/-^&4xg@7~G}rn5^#Mx9J+HX0Vk}TʃqP4`.f#ؐ.2i9],L+f u Esu"ImU.Ӭzxsj?6~[׃4`Q14 J;;x C8 7:2g;qؽj.Q[mƆF_͜!5@E)6V;힣 IDATʗ_&ޭ78{$YiMn {bF'g:唯_hh -=VW%ŅS_v/<ە m UXKh-$ZS*fc8 w;ã1nFZOp'ϿOܾwTS,[[&SM\I0~ՀTO0D!c\!='I}mdzb'^ilA{ϖT9`Z>i$aJUT;mAJAAMhIp.8`"4P&̰HjEp45v<δz蟰EWٳqb6x;YvΟΦ??@$U⺰$Z˽h-|}qI@D6 ՋfWBg򔒲@KDy, ^Ge5 ,@Qu`)Pg*J.]6vg>)('W;pɂh2J)ue=%E~Et? `jA6?W?{jb;0]ncqP?wԪAAAQZPbU9b9.@=uPJj@NapU}9\B&"#!wiT,"պpI&@: N8Ɉ;;ݭ22_\l+I,O.l;:)gχ7{4~ 1^|6ؿQLRLojub\UPBn׆ ZnYHf֒Y@22` 2IIJѥ؆e*q /kO,!?Mg{ vYq bFFBxH 9Y#2{ז8ʃ7bEQPHCJ Bhl< $SR˨bkNS Z }_oͪ]pSJ_m{ٳgN}JdmD*3Ư{ Hx%z{I!p,*)\"3ο܇pi:d Hh-$@yas;UnRwޏɂ^̆c1!NdN5WcN90%e|jtuM[+c04.O)Ɋ܃SseF\N' *hLCO ŬbtR.H/B$=|;dc) ae ҝES6l ljȭ$g9[']d9FrU7>Zò'C9N\&^n96[P8x"l=z:R|5.t2y?ͻt"1Kt1U9qs-\;7( N5(7_H"UW4hSI w3C \sHawKJ~(N?ov;m4wZ ƴր S}y{gΝ"zj@ \VV?]S^w4g>Yh,QrXBiᑔezLzʉZפJ֣Tܲ\`ee}A0ȶ?(lľq J8 FUݏܷ[az'&6c(8'f^dy&B0aa@#Ngeϧ й xW$db^Q[|@w2/4_?];>)O.p@3yrV\Q.Ò .ʒɷ+ep7,#d,>\1{%n Mȵp{4/ Ns/B燲k{dDrltsW~6Q_ww#g6~us 3c +O#rcJ(: {8Zh2݀D\T]e^;ׯx9Y➅[5˥b] ktǤ]CI_z=.,Z4g2d3=<d ,3=!y&q*ma'S:Zw?jPnt_|Ӄ_;;,~,c XٮF!zl n!%6hZ8+$M<ಡ8eR 8!a:4DG)a#Ǝl?p8MUjL}n|-aEh&ARhGhlhn7[zԕ!Vq/:ETBpSS US;L>b~kh7ӣH#"RDIrX҇[_Ճ+dW?@w%V+wzŗb*go91q \,v1HU鿖*MBMb;KؙcIo+K_aM!~!5,X$~u.=E;u3v; "Ang5gīkWkٹfr X e Np){ۄ-?b_WnxsyGSfxl9;UTae}'C)zӦp-xܖZ(ׯ 'D"KbG7G,n: kH^w2(AdYyQv؈$/3IQ0-/*ʐ(P-+{{(O~6.]lET18!_}5ywgcA-w> `ɳ\C>@NQ`-e(#u\4p,Q$58+/L +nnhM@= MF@ܟEB @g[5 pykNA< )J̈́Ys; yQƁZS $z{L;}ژO{ʠDo "< &G]W~,Q2|Y-O/yuBƪF&6[am( pЏPQ2g.'(đ<g16bEhądqH# {~sHHP P*as @v6Ēs~Dakh7TQ:@\'%J :*?\z4c6yg]ZGAfH݆ Z9|kO+aB>*ϑUÄ`HSL[v*"roP$1dyQ34= 4JǸ E3'2s4A;&\ {oC.u-**hGvnm!v^>~ߎ?ץ 0ЅG@x8`oA"N SsF Л=;+@!15V Gw3 пm7K>b Sn6#[! Y,3yО,!/|.#iƁ _>IFź1{_W_}Qи`Ȩz 4V˯sTG8HӀHuT4⢚;WR~)a&.-]קp~w&]l[*:AВ,- uuVb$@<U㒕Ϋ[SƋf9[!9 9I~\C?*P‚n!&}p7}~JjoZ d*ĭ̸3(l@ᒚnR(BIW+1d7N xjSV9VLw Qă$SzdmrWFLOԙ}a('>ϹU&NJq" JptυӹC9r1u!v˛&d#nsA/ϞkfݐuiFԱGe?zu *bR皫B 7#W:M UP5MK%P,DJR1aD'O[_+zJ L†է؇2W{ ]ܕg&@?¶f>-jǬZ}{ U/H ՞lNC]48ϵE.2^(^JI+B.dqH3q}?;ۧ܆~e\JAW99SP<G:=>J{8gHxI%< _?*`!L@Mog=}6"(>8c D"e9bȃ抒VxjNhVI퇝e3_J&i]u.c5LX[9?L!=5C2\*BrQ-bPwb n_o/6vS(>w^}}y /^[>j`]l4m\zEE@:ANsźVYY}R)75'%hT"-{}hJ1DK?IWvٴTz YXE Ģ,;h Aiz)hqK|m' h?ZR]ϫwY ţLU! и-9 h} N *P@mЮbYV逌 13PcV`0 Ҋ//"ϑ8.6&^ :E y !7*߭T0ٛ9yA':GJ:dMƫ?کt:S,c⺆/L|2tpN(u3/x1>NN6C X⓻hB\6Qu<9BHBP$d4"!N TŊn%)e;NG@,'21JH 'Zʛ?| _yUI>',bﺄA,rQ!fT !-: WK0G/y{=Z]|͵;7i\3{>%TF~Vդ 3Yvc5Q]P(^>i"xg8E&cxEKzK 1-"cmSAD(@vXJ[/ ɇǥ*"ZD/T"8X"z 7NT. a"(( (>sf#PO^4?zU5,eaP |ʆ̜yttQNIshЁٍ'Qăqhy5N}S8Yf6L-6*@){k+agz/j"sgU< Ξ5W(dCpb. XNNk"J8-).݉as)s-pF-(C3)r6XW/8~ .qr&ɩ(n)HAl IDAT<*oQdKy)30] U<+}y_}m \3IU6oIEx,oӵ;u<_%a(,{"PU?~H43PMwza5p-z8." imE[,~;ʌV&>ayV.Sբjqz{x-YO|[+"OW"`M0~5jn[#@A s>,cD)7+jJF}z?@ nՍ e >_6SvzK4Qܟ(DiB2> q]8i3|~Qyq8J%Ib"N+R[-!nq&u6sv?jL]\S1.OM*+.! Jg]0[KS2х6ڌ9G ?ڜx\. TY#n2c)ňg3LE}H=޼' D[wWX*ym$4dǠ8GyӀyz[/J5I_ @/*WfX@B !M'@тZe%cŔzD/JGXh R{1fq`}uZ֙g_\H )sp&>9 aG "I-+yG2-6dq슢I@_&QՀ-%K~U0k?=y/U5Bx"ehY(vl?^ e="(03ZhKf-byʅt 5/B2/O:*t[nLoOjՄO_$LK!YLf2ei Js~gѹUY]ti#Є -klzK~g6zcTkc-$FJn\T Yoa)><LJ%' }T_|ٓE9[ g*gn332r$%Odz(Q_sYwJ-?ǁϵ3By5 ,8lD9 G I)7D@Bi p@Y—~{z甐A,5o-hvȇ/_4pg[󙒪Y{mio( ?D6_ROoHǕ`@,ބTF9E;MT4-6W4;$h-cԞ6^#d#G X Jm]–-Ч{q{=Y}<x ! YĕKO=#$j3 p84:\SL3Z۶%P.ϒu-ڬ&C[SI~0/~&pm^Ab%;PֶҾv?WjSB( :T1)bǷ3I6"֤`zʝQÖ=P{b$W%!oaS߫& t] Oμ4M((ۈUF!waeUW?n+bM %Is7_$Le Bh)aOfn||exKiQHPq PYHy'ƚ m=ɾτ{(7x֪0ڵbym@yy~hM2gCcf̀b1"(XzR3(>£͐ztҭ'T2!^x=o Hh{ԫD,-Ib]ki:5х+'C1íX>pX,oOȑDW|~]m[qȍ}YsK> &\ LV GD#"а@VΠ>e(KOMC1ƜyxTH& 2$ ]oN܂xTu2 \zH4\?}? /_ь3BZݢT(r|vvty,] x#J?ڥ.K|yz~{+/Y$7q+ Ād,3v4 o9#KX~@.m|J /.FO)?QƳYE班r&놠Cxh:>]/l#?R-"'4˦O6b"QF|o"!YoQcc'jYhRvW;؍kʰj)e B:ֲ\{"X'G X~Z^< ;HK"V|_w !RK3 kdG^ߢh</. }@_&QH /: ,)͸Vf ~H<&:!b=MKbCEmG=MTD ZJXp1h 㟪M>9 tE-؅(b,C9Xp(niT R4,E._򫯛fI3 X~qx9z☐\YaXy {?bTQ^YoVéDH;fhrY~zꑮkI]dHG·1YϚte-nGvgo4vFߣI53MM2M⚘ض'.;lAL-9ޛo_Bês0Éqr}<@Eg.|^.-!X[ ǡJN0l0(X/J9p="'9Yf1J] +)B,9 Q'6._}MVNKa?lXJl]9,5Lñ,'uncp;Qk&/:jfݗ5|YF &{F@+@J347Yis?CZѵ)4w'ߔTs ˥vW9PVM]8qǏ{;pJzrpyډ!Zr c,Glt)Bn6_ȫd%F!(ц ˙<iB( .# EH-d.S]=+9k6ɉuAp`ٴiV5d-永չڂi#+\fMVb+*_woCݛWstbZ<1K:C/!N߯g Ȁ&4pBӃj>yFn!1LCݟ]HK\;Av>Zi&%oT\B),V\-\Ldŗg^6xy85Ɓ>)$i/pɟA[s]uN^3Jנk/Uo.{?,u4bc?yhڵʤV9n cX.N/o |> ?;5aE\v:5ZQ>X^s؍)6ld;8Vx-a\ thI:,gwQw`u[^5KXtB@ok/6KJYB@v)~XJ]vF?ߒgW~[v%$DYֹWVO=f 46{﫱(7V쒒NڮXA+ά= ½N:Pc#>l }Xz"'"#Ω꭮5Waۥ'zâ0hL*Bh{~oaqyTtO0'a1}Gy(?Ð Z|3􁹱vq%h'^ln\뎬P}YkOl5g/^VL9 ,2޼ۯ.5"};r`nF5?Kμ/kFbβ$+UrPXH ?ǍO$4Z^bJ#8E"EC/W@0wIu~EVet ֲ] }n~|d}o[ uS4wgYT27_>u {K:DWV0ŽG3<Ƨ@8|ˊm=%i*Y/ӛ ӏE7j(w:\W$珯\}K.crC3Gswi .:ˀG4~co\*<=1%?jߪy.5Po\EigW_k^>~~ s׵u*CNj]j$_~ϵ^ "]4Gxy(p"NYr)"S-Afґ J Q SKZk[YʉWO=z^FoKyY#$xWsniao=6V%!r_Rd-Xld8\bxZII&naƢ" b=z'ѕÂIlZ*,A\ ۈtD%)ABF (d:_q%K˲j L?f_>w5'?O6M;''e|ڂ͋;XTKY}NY>{":ؼt'Z9W\uP/.α}e A֧0=85]yJ9PY,6"W@=~Bϭ꘤/JJyչ\S]5RS׏8tI{>rQkzAawW=*r yĉwonnR0=/)*ˏ>Ԭ RzZ1ĜnWռ~HݼBBOq3Gb9BIK[Q7/_?1 &> $aF ??}rV~Տ-פ$Dط:(+˰j8^g5/kw+L\$+0bO[TpY~ȽTrK2ݼ›U]!unA| /e5w,`Ts+TŏՊ߸Jy~)2r; 1+ؒ zG|kVS{~rdONt]tkrSfuz]/ G~ ?> @' yKz˳VC>Aru", 'sbB<4p wOl|0Qj4l9;%Odf[]f$<]q<U弄l M ڦNJ;uUdzkb`e}XqYW|4{}`ڬJ0Hb⯬" dx8:S֋?a߿q%ySL̾8/#.!]0rٕy(T:ͽÍR=9uE$WQ}W){SZϴ(e2P>Ս7_{ȣskgGn:?eA? dfy)\; ID ׎5[qylK_h4wN6&QXAN&җƆeUpzV`F1VL%~1԰*_z8gҫ,7ܞjź~I}!K`"R`uLg5.Ĕh)aEjOs0@WRXŷ"y3Z|p"?(p= QTrVc9iX~?T+N6ձքe)'pѣ7GF)G!|2X׉`=9<(Z%8{הY:#1|y4`J~Zrb+_]RAA}wt(&-m+Ei^YQpN.A)IѕG N"u3hk,ڲ.N/=6IkOCW;2o ^g\տ}A )׋D{(K ^ބmݢu V#W6#Vll"RC IDAT=RT|!'R؊|y:Bط.+'c[ dc6'X7\p l:tX[GbNSK2G>(jH`E]}JQ*?I]#$HQ߾(q7 ],y6~|}Tpfm*Rzo~"֖ԡxȜbjJө!v&[BqKAb_3ʍB|}˿Ӥn =\`΁8(~3(~!}d(!f?|cMo|.IN2Q]"U-E@i:bs,QQa "ӰRZ[K9+7$ &&'e'sK*@nBJgC8vR9X`:sVZن+JӦ]o{gr#cKr͖MM))j~oZRCrx3=BU P@Ĭ`ۙ>Spl :E'|[Ґ{?xGXuXAq-v ~I}͸Z+(q\yRC%5y㊘kN.2Ğ}0 k( ] M3EE1<˿bIj4ϴ& usΠQWDtϊ,;ϭ* u)u %S3(ʉ_"8 GIr-E֔V4cyoa|llqzbrԼy)y,Hc٫#x}=υ( z|_K!.>ryc4,MS?VQ"tsaztl2;n,_CcoA=T̺tDos@sKͼ1o'ՕL4%]ar{"IrKPמ H+\#p'vfDuouz'GǖLN! aQ}xG)ϵ;(g=O?=^T^x-l*, !5_ g¯~:Vf[T` b͓+3 c7 3ƌ79G4J!Qm0PUZWcA8LwbaG/^^E]ܗFDmѕ,QL2ceOkOEqP:J~9WʃtЕs ]em t pUy+,*!lQ '`Uk?/DW_@  n!l8•ϼsXCWLWb(_lqmdzڥ W?Qw \h XoJcd?*H7h[@ÏT ( J^htvs k\PK߀^͍Tkχ1rpH+5/:ۣ\[q 6,!T[|T(5Z`Bv!~zqiCSc333S ?#?}):s| , dq`ca(R(3<&69pXXDRVGa`-a]7_] 0$Cކ׸ lw'9~_'SDCc|ix[]w'a/~gA7c^E(A: j-dفPq=}b[m^[ gzfazl"? 8V/;Pg?vʻou[kkݲ~LvH> ػeȧD L#m"y"LyWy"(FQ f4-.GW]IW,{GQMK cy;[E49_O ^h8?F1(7.$tH4)Ktle+"Oϭ؁uuu]XA_}̊Įm+ւÚmMqzKgY0F!BI:҆'Ldp_1sG/wx_qOC>[" Y9qj"5U{.>g)Cծ(BCؕ@BuFN8Rf__o[mW߳,W!ҁ% xG! 9#ՏCGBUeQ* tȑ_SnY]]V(X2&~m2m\BI 4wYqYj$d荴6[ `bkx̺7}:qx<)ՄUwH7~?KRZc PKE@AJ BFƞNDST5۷ӵ*F^~@y3%<796i UXӏ1&K \+(`Vauf&АjLL8{Ă B6?&pȊ1j0C쉵+l"c$КEw209X$[N}·g<D\l,<<_Ej+BH50);gD2N֔ؖ* gQSxN^eugX_ iv>JA< ,rpI3S|s$=1BU|݌mՊkXisM2mu rc m1ke^@\ =h 98"Ec ,w0AusR;Bk]/VNklY߻hLv:a m'W{%HM< z6:U=Vڳ/L.Vxnj|Zvg*?<Ͻ> ҈F _9/~o67t%xMkMMZaS: a!YU{ РؗNdD aF!BW4(ּ*ȱnaK~'S)芲{MW"*$n=V z1J޾KҳQkWs]k*^m, /Lu7%_~jN}eE )R}k)%}yI0>on`akDyxm[wK4Զ̔ ]\AXeZzZJtX}jpCeU5 nה%XvyNz<B}cR9O7r^QF(_[JF յ%Kֵ-_}oiBs&&gjnl_es(u=x%aI6ӟM]|qT[{ۛ!Q`EjAE tsbg/*7/@Nvq-Xf!bwU_>8ᴌB:做cZ@9QF!Szs A\Z888CNwkK9f+E x`?~bnxhprY{; q@YB4Bso>0W73!PMBۈ?+6{GiՁY`z7da{~ Ű&`$aI (PLF<tf0&4A$JASMxuK}JA~M)($;;Z4Tcx)ZR p%,N-`-Yw54Wqj^xnvvhq^u6GOta~m`auE0uuvjʝ5)V0 :!D׻3цX'p^Ba Hգ͆se-naeopI4W2^hH_%癮j$=z;7`dxx}e2Εvk hI|@I:w=\WG#[kqUh.)4)zvCb+YmM}:晔?v\m}G~[wolnQooUaXk7$!eЭqYrDX}H>$.Q4 eշZ"t,,ibL/(^aըlkhh[>2T_ޞ+Ϋo}w#G}7G7oߜTr9Fǰl89wzIp R Op SjB†dQd#ܱm *俏-YCD{Z$؋#蔿E=lZkZcb-;w;;sڨo}COn}Yy4p/>Z|_(Yw <\[DݺG[mR\`T̫ռ܀oCzʹd}=+}=%*z3ߓN]=˯<{m 禯^S>!^%t+6)!VJe૙:aK!9Yu%{{VvMYXTeZ+Bk9~j/\?n|]Z%@ ~?!^`SgN?1}nnJ`+\m+~xza~3aF*OG[Z^R}ujlbE5bA(aC.U'?\=\oό5`f7^۴I +q!9 ¯"fbUZq"btv5,l;2j?f>\ '<"pUXd660ۨ^W`OeP.yP{+dVvx2G; S{ ~<{Ov]߽S{ ~<{Ov]߽SDf:{_{+ GQ'"GB[cԹyvﷰv-CIENDB`OSCAR-code-v1.3.1/oscar/icons/go-home.png000066400000000000000000000051051417327530600177550ustar00rootroot00000000000000PNG  IHDR00W cHRMz&u0`:pQ<bKGD pHYs11(R IDATh]lw;ήM 04"|(06J B"!OUTH}C*MBޚR!D$KPB6xm'߳3DzѝYf={DoitJ;wVVU" 2j*xG"^ Ji(E͛Gj'dmHÕ a֜B4jj<6 G ;Z|͝RP__Ogw' єVsv8ġgiatddhdhZQ9?sE_m[vŽ4^m+}UUU(8z(uuu=mfDSZc ]ӽzFr#jy3&#Q;aG]m$`}͏<(7/t3gD*Vr(ǷpjJ֭rR)'܅*+eE5^cZ4/^BuS̞=rs(Y޽,[\l;RVY1q抲eYך0b >4g";$;$';D<}kVFflێl(tuwvPiHp8E}e[mloaSY}8{4ǘ6}zi] nƉذ48iki Vd׮As冲W@X iWNכ4*"/Z[ Fɺu괂l5!"ǖmۣ5ߗ5>I__|SfYdvќ=H֔ѡ1k_GĐ~XYaLsG*+[ġCfx|~/p%&UUk9߿$.\…KӚc#{VtkZ~/\8ǧ>?zVUرmY#4̓#4& " fWEH˯C<)4nb1~i?8ߗ;8}8YHe,CS,ƥ7Q}=Ѵl#'d^BF0x}IIGB"",t`ߖ+]9O ~+!>w)7v@4s]di,0m7YIv;УypD t]Gmڏe /'4|hwx@iI ` Y Gm{@tWx8b1:\C9RJȶkgEZmн:!#8x40|n i, j G\ ^7|5lQ UyR !+Z.Rh* q< uGӆ M54:"'F)1(h.`[nۯq+)!G;AC^LM`(b1nvu#~}qn a]վeC"Ynkw3`qAS Ђu'#3(!_-N I) 6؅  7]Hp]`6p䈠B0@f-|In؃]h\E$$"\|5.muvuሐm %,$w, v.D^X;b g $hÓ:h+dŚEl۵2 o9(69 VCFBкmDPC" `ΰ{L' BVEnd > JB,2P_eBOwΛRGԲ4Ffs\^pJV tx'9*@F,!@jqt0s>l)KZm"IENDB`OSCAR-code-v1.3.1/oscar/icons/help.png000066400000000000000000002456431417327530600173670ustar00rootroot00000000000000PNG  IHDRx cHRMz&u0`:pQ<bKGD pHYs  IDATxy%GY>TwsL$$a% I*VY- ;( ?/( *ld%BX &]oVwsϽL=n~y"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ƃ DDDLWD{@+U)4P~tu|1p.,inqp{ 494""0DT""6)>";5 4pnU=UJ;lp"Z`N ›}RX搥j2 [+evi@W_/?5oqn CDfBT""f=- Y‰ vA66s)M@WuXFvg%HCW~7e `Y!,4vkF |wO8~hT""6QXg|d*lKHSp8AwWV̥i М/u>)k΋jk@d| и!n>f{HDD)NR?$){?7+%@ y 'ݯ͔iר |`bfD "b|$l$OKa_pؚ'&JM¾IS+oXy6ˠoZ:׸Ft=לSS> stJf}ښ;^ˬ0.c _|1*ngd ""8?kNƩ D@#A>$~e$܃ˮUi2a^Qk#7;3wJ )4(?zT nW #b"Cq#~B"J|$WϪFPK[ |ǩuZ(4P7khC𝭻pӬ-"b#)G $*<$*< v$L3y X|-|֠{SH _)k\Z nc (8±~$JzO>Qqr"M03hPF]b@Dq})4 }?=6sNKuN[JB[,44BU+_Y~D4#US`GƄRwN }o ޯp~GpO"7br{t\]-4.W f}"*-@ o-P0_ v%P4٩߬ƭh]Y} T)Wx|zW3qx!*qN@>WŔʧ־DV})~ڢw`Y;Lvѭ 5g.P_)®YjDDD bWxdp~ީEσO֝mVHjUBmMukA4XmRPZ ti]7+Z7/k K ( m`A[{XcZś,<+DDDl8.݁- I~p,P }'/ x7Wk?V;@P݇n'|U67QA&7e肜WR Z0RжmתxPSs= @Ć+;qZ_A L4Zз ~u#]˾[Ćܦ(ş(2<r Y/IT{@;l>_a.Ӥ,H!b0e< ~tDlz'N)l$SC[bmF3|OwER6+%_u,NL8 ~snh!7`u(dŠU)hQ$W}@P?vPw]Z:\7"":#*놫wLRw3N` yjVDsJߙR.O|U+Vȋ ~Q9Xu` d#}L%QhR-8Jq%HJB@Cqw9uḅ#"ZݞΔPw?Ig]E_ xj'"6* Ex`Em _ lM?d.>B(۰;ɁwgQ"15\~YU?C)R~~.++p;B7=-WB< [Ou( @w$T?zIг\Rq],~IbBy1v "tR &P4xO4*S@T"֌/=Ʒ/S>ja_Y>{ nURA vA~:n>4:sV APBHA0כ]Ep;(Dud"ֆDL+˨'Osʮ u7JU`XI As_~ps҉B}xV:KJ@UEOHB e/}$E̻VvwoJõ]ކD+vbGxl'=;d(,FS_[6wRگ>tz $O|,ۨ}Wc `РxBk+CH A\ MʀHza/"":"*v ; 癨PW ~eCAz | (2]XߟnR~d]8FƦ[o[&AUOyH1m";@F7ԧu炾e Y@DD  ^땯+^R~~8Oҧ+T @b?I"} O֡m"ABK\D)(]h6AI! |OMWB'WxG7шpxfy.v}$}  W?D{־ӊOk?!ےZ Ӯ|?B`¿oiEk=NPxƀ) yH1h k "MMJ&xQ!(.ur&HGT"D,--$ oq_Cz:p+,}J$}mkTЇIQy(o?ןʣ'=GX@[PIK[XIBj/cڔ =hKJGs/+{3xS#!'$I";r nܹɉ(B$P*u,{ߺ*ЯO\_J{TïAk7k)t>&e108m.." eR QB4*J,?wkێD @YD)}W8"9-q,~'zDW>V~lP }jp_l<~ RۥZtrMYy ;:+EC`z!` 3 ̳ *up)WϕRD @N M`tKKARAϡs *Mms_ + d|mmZP&R\p Pg 2 M:Og-+ &E.8>@W*݉8\L/$9o?}ԣ~귖~-ЯF7%> I XA"Au9-Rx;]:H:2g @Aht.q(W;,hjWx)&|i"cD(YApY|0X"i0)T@% ?}I؋IS)ajyCE9 (@Y2@ ض+Аr(E-" %8J@-ֿPQXYYyZe/ONk>}Cr+%z־i-/D/XCj^<%mp)mpֻ۠6ep(;R ¾O; %B=Q W1K_xl_s7xRjF>GT",..eKzuK$=Tu2HWI٦$_?!A/Ykxŵo4S~t]sg7FIXM.>Qϕ} 24TsBR~(#K>6KـQ8J,^~]a!/ $.4I:ەPc&'W':XMJA`A0!2|$EEiw}f`e?fdDi=^)55_ +/}һx%_[՟?3'›c[&-4qSӄ;5`I׃ 6 XW `-%@Z2 xڠU .BI7/3%ـ#Q8,^e~ex.TotT 9!/ͽחR‡x8V > zehf@¾)?ofbC:JOre@@~ 77n0G0p;eY4M{k?cG\!<$w<}A /=m"}Bz@؏7n+*vu-@Ipx/"Ъ  L VKsƿ}T,'|!*Gek}gr`n׫|H$)D#?D% tf:Ĥ~6ykT&Ьۄ8 h  ["t-%ˀoO~>9kP)8y#*Gnc9wzދ,;nf7.|piR <~)#~jƝۄt!UЋM 7.1C_0|@!^6XL#,<1hNW .1I!;ߡ3xKGT{Vdj@5?n1kT m.&v`Lid#2 :H`~&Ʒ ۀg?w?ʸ$7(>XD0#,~~]q͉jA>c }P>SChOK]Ys ]X,_% (ʠtbxr 6AgO}x#/1=dp7o߾w,{Q;~ss | ¿JBg˺e77vNL qh'-(]((]ιQHjTY7?!@RER5RռHk%@8xͳVۯ.pe8{8SeoɲM] /D7v/xxm;a5MQOC)nklU!/t ! 0@ـw)w*} S/^|71pβusss޷x_HrK_`BMRvOǚj> q%O($a!3րQDsx3 P ]ߦc?n8|I ,. ^:YE7C?)@dM ]oo-uNAF`ԥ\!Z{ Tn慻`]P ꑴ.6?ZxRjo(8 wޓK,{^߸>%ml?p><:+ɩ6N?c2kQ6D9Ё?}]Q ҾoEf@R,zsLߦxRo$"lr۷^`p4Mg};Te!%r3 hsRɶM+Z2hݟU`7j7S@u`b'l  @ 7p(GE (5#K;?h< vc&EgYֹ;nI_84 KO* 5Mҟo l ]Ȏ!J(cx~cJ @gxq k<.! nZD`OMuZ䏀z@Ї: ~gF+?.L)tǠq U4kk )ZRE '\|&`?;j Gd9p뭷n=x[4}ܑ!i :X. =ƞ9-ʶ qu}۔ytckj޶MCxwBf`\vi)o2Mʝn[1.R3erd|^MiVWN=6+E^2@YLDl"۷ogezβ G7xݫ| JAS~N_}d> *U/[+v( qvc"(r`y|~n:Ml߿It׃Zq8f`CXfs;k n0vo@\@pu(3-E`%`* CmSp 8@0 e peWI^sn<> na&;+䓁zҍ旆sC0tFq;۶_Sԯ.vqy4ۈ.Zt=0|ʈ #.-K+A :<wZFv$[,;r _l'FWaq*fяc{W+6Qyr PǷtahC}v}2#f=(FVD`8pӒ$yQ#?2 PA>pH-YZ48]>1b05wٯ麡:4]C0k #3 Nt7B$)l@@}/|$6(#0#Qoᄃ@)~?j?sI(.~@61QwxIM3d:KD봭bj A1XP X1K) nRj߬ohBdf Ioӟ _PێmdE=ֱMֻt1cfK_K`mM,/^&ա^@`qN|IL8P1 oVm1MpcKo ;e$6P?֖662%7j^}6&S[ON.?6c;(=@FT,v^.B#^Z8NwW< ϋL(4=g}mI4}Ve1}W_²D00;2|\S޽.ne6&?Xvt!32]%@XZaJ(6A7ߒ4}V8-{8[:ta$]&wq5s50MlhgZdPRu}0B߳Ǧ:}E(@J}y@9чlb :gTCxsI_8;`n'Y+}^؎h޴o 6q߷Ak (i2YJWщ'!;¿N;xϔMidҶVpnZ<"(HRꥧ~T7wߋ6;l ]R ~ xjSD%`:[$yseN$ &,.x*FNC܈iQcs@DհOԦm߀Z<.1f@hM2%(sߨԈ18%M(bVO)+q p12oeMEڶ5I~kPk[F屘 ? \t()"NRi0?Mg'I4MQ hS6rfvZKw3ZS^3 0~ 8pMx# 2d"<3Etį<طg|du'$09Ɯt4xŸh^n>3 0}~naF#EFd6%?i/")!2S@e5?M(ׂt`-kdNߚ ǑAVꕬanVc{$J:X J6 Ǯn1JMIy5,Ū^_evKdˍ+pa%`\{!Ƅ 8tx>25"2k Rɲg)s{\@VWm^M7ZZmu+j 3)2W(@iqi+pJrp,. C0.+hzu8(Ba.PJnؤS,F\)?~y'm4|Xr;KphZpIkR :t^lU*/v9?i[˲ӕ-^'/RϜMn XXXxRiL_S< m5't]:pP;殆``RAH/Sh"0%&]$hfkWib+);;S+`/+!+R!V`yE?zsRFD` ,,,,Nzc1?ۏ]U I7)Z,~=jZKkHqVU5*|74@brX̹/2XC'?5 6ޮK&(*߾8tН$yWe? o$$@%cvu77ug% t+%.: [~ YѢU`c1c,memN:u"^ORJ}z78tЖ$I=VkFSG³0v!w?S\}AM'*>0ULJt;T'/) V05K"ؿxџwSLD(*F63ЂgY4ME}1o[V&ucK.]SmBފx͔;Ӣ"(gV}$ =G'/+$\lNM XYu65"֌8xP k%I*:O31pm<0$Á{]^- =sz`o4Cz; GT9p-~9P J$vbJ40č¿ 7ٌA X\\|RB#!νps@C7e,k_s%FuEj{n{& j۶:~nS @)el׸r9#uε36LO+}mw.M'smu_'_u=ǒyLMK%H}o9M,X.O}! ̀u#t;_ko Cy scfXXXS$6pDz80NJ5۝$Y öHx׶#ԋͤW@ +m? I8VV `B`uvRʶ7/-|ERPZmՑ"30#p𛿃Z."\[,{iFῙpO9XRwZpkOߨ4YBmU!squYo{qgֿpyм z,kg4_Y}ukD#bfCTvocwMU~}7(-VOۑQB?h|s5~@8?^TX0=(\)зB F+-gW >"f@)g7Y?nEl8y/_/)U`V, lGX[Aij>-mf(ʡt Trt]6gW|m{9t/[v>o(; T9A9*c p꿦C _uƙE]̀Tt[p\rћp/?I7'>qMĪ=[cȗta9u󄘆 @.'Y C4=˞Y?Z.k)}ݓ"3 0F9p /11*D;CI w'?-FH{yq>w)i[@;|2ieybg &@lSJzAz"&KR4G>XJcy8\_#Do72+i)DYv$PIdn= N>eַAq@?5M s'z=/1x|nYw|h: JZ._#xQW&\.RI5W@Bm_(H&yPHUOG@šTib tXf VGɞ+GOYf.݁G$$)T!MH>v ~Q]sIqOpYDKtCAr6PEXA*$3P+L;iA"cZWۂ.G}waTU5mhއR: C~i#9_GD;RY,pf\g()?AdHH>OFrV.yPwˬo?7| 5Bs׷OXyy_yyo ٗnuT>7Q6ʟd*KV, n&WۦuZIﴡ{ N4L|u\ϒX>_rFP' *?H, ;=(O"pvW }ѲBA(*Zv\U@{~! &Hsi[j܉V89F c? P 9&Uv$ݬyb{K3Ϛ" VV߾nkoַ8*_ۊO~(?\2I9dH_E"yCg(@Y8/˿ Яx޵D;[X6n`S`=g?e Qv; ^](x?Y/HgOkFqWO?\HJM8iVeG?}|+]$QUc.݁'ʼnqVU͗J<9$Eޝ ~`v~`qz(V4͍Q\*jK?oY#|Yۻ+̓ ^`i?0<C@. <3KQp\wmi{ 5QBA]x,>y~4_O [TOOijER07(} Hr}/w#m۬o(OGӕvq]@,|6j"Q8Z31`7žEEMM 6A1pQ 1Wp=wxvoQ9G|^ǵp8پ>[_ob=rjx@3qEY?zVOC6A9(7**Y"M2iD޿ B ߗ;ΧY.>g7 #؉¾ xx[U'βVb2Bdan"ֿb` 怺*JH,\T9φ`;Ǒ^+m:|#Q\2 o(jhhX> pDhS[W9}dAG"yd7WYŸU@ro 8Hl݊$sߎؽ?lKSw@m"ݯs@sjr5XL_YtogbB_B3 qIJp 6,`ω_gȲb\ v:42@dF$*#@[EWtG,w߭WhJk—֛(uGeM]]_PP6όFe<*On>POZF9`\XrsI2J tRpTp$y.bܦcqEYwZq(lś3ߡ\7ϪjWg~I+f_گ+ ]~yY90]^}\7B RY+4⧖7KB.CS U&Q~<8 Y0Ў2P宂PgU ͡f'$sp/i xˀՕ {7C7s6@ @a<,s>co{#B|^Oz|??Sok?#)$7_`RLiOIХp vFYl=pHm/ Wߵ,~#s&s(.L@SJEh{֠Hphq%И׌w* zM|xl5WK16^)D(ɰL s|ϩ62*@WJ@cn:k ;0(%)cU Gk{~'F6/i~[ʕ]^?>t3pߟuqHzW,ßC+*qJ Zbw|TГux}t p@ä[ڜk0kKZ]6ax^ڵ"P-+_c̕%L,ĉ/yp)6Fs+vKB1W@&Tqla 5Ղ߰EU HUfeWLsHzseK6?t@z_V_V2HQkZoqzȬ–ns;,`{B(_[D/Ԏ=G^_ [Wߤƪ-ʀ:6/jI'9SpM31'i -)}|+Sby\P) `GZ!(k`x8 e;pzw p<4!99 dQ%$DS k.-v4>bX@CTO,}J3]JκF׀X@2 R4}"b :RI@M{cA0\lÎ׼>Qx{ز2(8бԤY$DP4`{Zg Wi@kxUp@A% RVsk۔*EgU¨h}GcàSۇ3,v"?AokK @9Ar!nK3"{ ,b A"#YqԿ-(sW~ ˿BnyaogW<8ƯT <`& -%Ip*J j~>To+%rX|]17us&܃_؏` 'ϕ7~fGһ_ ߂ޢPY(:,Թ"4hfHW!F$#dYȵ h!_./WeL @'<+/|xߗ>[AC#qY,^J oQr>_ߞuwV /M*PI dPi$!L>~T2P 4/KS ϕ `Dۏ>2@euHS[ W@CR?` Phٲ}5PcI3FAk(EKdw*}x-{Ɖ `NSUƧĮ>v{-n7{6_9 \GCA) rR<͐h]D7.6ϹX?o,c@_@sWִ.Nq'G%e ,o~RRz}`PYC`et,\N*-(ZuR| OJ_UtzR~m}SXn|/)@7 {>Qdqe_^[ļ5a𐇢wso>bP kтiQ~p\`P?UHPi:qMpn^| B,лFX|o5>pr[R`N)8u#v(`4{)S`^;SƛJ5WB( Ч_"UR/*4U֊T߹[_gz7z=ܲ~c2GwX ޲F-0KmssXK֪m Wj>z;Tg JGj[O#G2 lCe [e+zi~~żPP;rP1j9z]22p1cme|ThTZ {}{ݷT2{wo/'cR Hm~IZNX(2^?qJcKE2[|ruTz G/YY[̝{+_ }O2oYy@|r5p)/ЬJ¯DP5(-r{&JI GɼjnU4|F^Hte}( +8 ]}+WY Jq.;\anʇJW@Vk9@]>0L-*mX\,]K"044ŮYw{V O?Q`|Y ˪࿞o}=Z' ˆ\?5;uw@98_dY;l1gþ?C/@ :ݎGK!j\k|6.V^z |-A;;[S/R.p&q U{J #`3E I(]c(sYd~A,+'$Eaʹܠ*jau)̇|7 M{s`J@,.[mhW]Z6ͺa.߁ӔPU_d$߂n+ϗT΀@hoPQX ^@$ß(}U\uؠ(J'o} 'ctQ|Qt.[`UCBW/B+޼ S#B/S-|xnbpܳ~w~Û;Ywő|}֣@WJyX7}{P~VYpmxr2OT%}SUConTb0G%NJ (yNz_Ǩ{ ='|O;pKy( F#$}RL#Z͇ΡVCӊ4_e࢑lg [U\+GxDS%h]^;Ko.w9|,͌-mە3ӰLr@4HPWp>* Frn0)|X.߁^D%U_+>Tw&CAX?&Bpbz_K?Ms",1ͻXemZyL8=~q iJBe2x\2«?{{vSh<5(Xvr -@D{c_ s{"Jb@V.(Oŀ@3V9e*MM&XGZ6Wܠt,Lm+`L \Y*]УZ|e& Rxdܾ.UyU^WW'MP kGBJJ3۷5ǿOPU,|x*νϬZkO)۱[K8- qXո~UsC7z9$<p۷cKDg{[mA]gZO$л'y,#s$I5 2o%Mekjv:3Um-ڜ]Mm#"0j$I-#L݀AYhyLe\ ,-[˘%S@M3:J!5%*oPs~Л H8`?EڟLR&Vj(\5 =߀SEQh4*]7݀+ќVZcY_U?~2_#hy_W0 } 1| 4l_*~7 hg> ~? CTӝ;YQW@&2VN:pX؁.vf(h+oF~9o9ܚju!~|俐s`~iY`G~Z hdfY֭w%UU Um}XK5^e\P|x7\O|&+Um`2.*V_)e/i^׫X \/? JPLp`bYZk(UM~_7\_Kh_4N>`jU1bt@ K`ubV` FqY$ljZJYmH̕J@6_ TZy犀IkD3bLӶ\9>)GK_]]*VVV0, nۆ%Yx˖__-P"+E1|PE@XkR`/‰x$T#*>W; { B Bp `b22TtdV͚iOK៚s6/!e¿7?57W;XtiߢE WA"("/]w;WЗp8t21gu=_+)~ yJx>X±@-սmO< %Tx\y9'l/N A X XiXbpT+ }lZ׶xVsnio|/C=ֿY/do}&/gvt R)ʸ#%v|ϭ,..bqqe}S/W]- VpqQ&k2pǞ{>]oG[o-]}- (TA'~,uƘ" '6 F!(c6- yxi?Έ_FFϕcS?Mz  P U P>?FP`ma^2@@r*J&VEV>cO\zmʯ;$1JJ i`PFͣWd_>?}HEg?|ɹ]2)OfH}cعك}/?O.Y"ƟowHr^6E 8"Vtз&΂0X@+ qÊX%1+8߻hY+]<%U՝VCfrJe [ӫKڨNAHCa{Q!"IK_MKm¾%]t9I8<_2`lt~KKKؿVVV7"6?qyOh0<qZ#%15@ێʁW,DwJ[HƐSNT-M@N >KaR]{Pk ;p%o淔 @lUu@%J?G6dl:@)9f;𵫁=p iwޥ}f߾}?'~'{WWW?aƊ0>#\ `q7 ז~sw4 }h`3L.x03Pp˵ Vf\Drƌ` @:T ҷϬ_M( p3_m?jEF g/"$߅Z ~&`|_g^s_ s7~?zӟ>K/Z9˲+HldKQ\}U)(10NwhiBc(Y h])ƀK\6/ꔾzH~UJ~8e XA,F7|z=b0UC±KO-}d~PM/(@S-Ϣ!6yP I{ʗho6Ὼj<ݻwbiiQQGpy[1KMlُ ZuR`8)gɗoiJױ7Z34 д.;LG +u_SgTeicJ@~vU6(<4n_*q,_UC&>~۔H/0/Oj\ߕQg J@hΗA3I/^04Dwޥ={v׮]O}Ӟ##GYV[^"DZe`̀1<i_)XLaQU4\3M @j(k% .pS8̕_)Iի_(0^7_B߮ G)fŸ3uN |?={l{L7}nW_!7qT<vuuta20?[Zi yGh y_N.@@z hpMY*C7,#puNy *Nt7:CJ6,jrׂ?NY^|;<(߬v.)SrW.mh~ BJܰ?ʈ5v#.} ǶuJ}P,@R#gWȪ|o X2%0_t Z?UkCO3&Om?lW3ܳilڂBה%ڥZ?p5O{ĺp%m̀ c>1ЧWQ^0z@YwWX+CRڄ))+@JLJa,@Rl ` N`_3U؉&.7Fr/'|Bl p*pgJ9߿5 xĦYg(b%(SK~ JHEBRY:LwSWrK'!`M4M 4&03'L/D ,~7em7ߒ T1]ۃ[ Yv9${v&( o.e]:INSXƁ'>J)+++X]]5ؔ83?P~.LXKX|ݫ ]h Iڜw os @7P!]PfL2U iy`:Fx3e}眀{NMY1Jh_oS(2(бM'OCڣ[z=` ~UDC e`?'_X(9/?DNF_4n蒋[_ > VVVl#(#6-<)=!AtRM}`?? o)<.Dt&dThcu}.[jXϬ5vXpP_(5X "ٙOksyC Zb۹1vYX`~k$wC#<ai9m ~މNڔ{a{Elpwk4"b-=+ lW,5|_zз=.P WiToo& }T7CCp (vF ]xѨf'TZfuP0 *ʰpo:XU_U_ee `9Oxi&@럂zjdC+~t!_6"/?)O'])/_| ~wce>EQEǗ>BFWK<PwAzn_r2,%0 .h+`@5 0n QuLpUq݀{gPPIUS$WYS\Qxa . ~.N] @)A1EFԿsh0N@\{O,8O:tF3<XNu`X8IM @ԅl @ L3 An S8c'XuJ1Fc;)6*?4PysBC)AQ*4~H1#Lۨ2` QNC 'K[p+_{QeDFAk6LtrH?']1O De5@VB7AҞ9=ptTDk(nM3ad8?J%`TMKË~ dɈ vhpi$:>ga]>(Ju%2MŰ(l1:@K_i vhN*bMk@) 뤔n @Ju``f$}Kl 8?.2/5l@( D?~D;Ym.\v\!/2F"XaO"'%POJ`E}smLjY3܋$yán߆[=wb֕(pZf GuA7n]fKnV/9ﵡyCöYd6ş$ H*i`2^ :nZ Q? ? e@3Y #q)zcXF 'b<M񟼎/k!sP\8F]|zp\ Ce {1n2nN-Y , H=y Yb$J^0q֣y]Ԩpm(?Fu ϲZB䫷V>{?TFcCgK?|z7*k)8Jϟ-K/~~`#ň̀A}<.I f~Ssg> Puu@tW"}\ c`x@;J /\C P5L$čVY ,p O󝓰1 pj$$-HӬ41Js H) J1pA~@ e~d`Sv/ذ~%_}̇ 7 H'ÿn{YXȁ獀d.X0а8S|& )Yvs&WcCwh#foGb@* IK@p1Rh1 o@܂@u}6N8t+[ `ϭ"@[C塿N罚4Oy{(}ZȤ9exŸY֟ fWӲrFDl6oR5࿜)L_^_*`_y""o `jȲ?*z!^}OZ~/hXJ>@W;e.(fVCћ?T^V?.3_mw(L'*Һ O$IY?׮PoN?ys>ŸV:-0_ܲƻ}8X*i/EHjEΫR%ED}XA!8`BguN˾L,pHa])@Y9(lSLh? DN2;Yh &O #}#iehZF۔?y6>_`}8^[Dđ//cp g3PPY"xdva2Ɋ fH&ۚ 0x.ά\QZ~yP5gx+ d^FxR8o=޵tTl)i?eIK}NԿ'1J7(ި?GG] 9UM`!!˄A mߵ#. S?U t+MnG 1Î ؜lTQ {_Qfbòu+)sg8_&WIEZiV)@:KiӾԿ2Mu/mOvh5@T xT ?q4ʏDp%-i,j1Zw-"eˌ+v@[@7@nrTܠ>`B @x|=6ٛ\ LՈȀ ٺ(U?>ء3R .M]hoj,BDy?4 CSQ_ j,MR /=874~n647Tt3S/?C B\" ojpo TP4jvx\M7sa\uQR/?$c'E °eUhhsڭQq 8otvc\nYxzcG:~L  rpR&Uh KG tKSg.I1^6Z5+-wZ'BWq&}e?mL8wl\Ғ P4WcJG? hH/Gۑёo;>Uif1/߿a,_}`uj~ao1)4Ər+0#ֱ(Yܸ1" mNэ wKVZz.Z@(@@I2*NAu( ^ ytJh 0#]%TԦS?! E ,m#2ُX,%;靈!ˆD0L޿.EKe[V5jޯh 4, H6H0`.NN/4'uǤݸF )PKs7+HyPk՘}m.|=;}B/_n>}4Vwj% m "LFgv+vO󝊈΁0r(\?+@nҥEi Ёair"@|5I(Gk piuǴ]?i+@Q˞ po`e_c$;lxd6d?c%B'*Wp~?6R"SZ{pŪnh0 vDar)=gI|uAV[JeaiZ:U @Jadoi+: j+ Z=?nSP_w4ZlIWkT L@7}A2= _h`E/LDDDtB{ln8J@ N&]d?Lwv 7sp`ʀ2qihA Ӈb0탻O둧DΘs{ԁ4EЌXV?No+) a PJ7p)=/G$ eG!lgnl-3x#D?"bN2H\:Exnt iʾ lU d"UU@hŸ[P mh}%cv-ؠ%%EiCS\ӑpʵ[ZufL ܷ\rE ,F 2B1_t9X.hF=w^9k}R` m)@_*/=B '( dv!4g? @!R]h5PX,r2 |Nm: (j_8rVA@`: S eHY =v\jr3OgۏYNS8];(}@vnoT""f ] Z7x(  3+E]|fc<>.Mjs܇pߎ,c׳%cz׷J~lDLKpkTBY׀I/o9C=㨄S+9/:‰uymxjupDDĺ`Qf |RnAj6@WjBJ@R,=y\x?g6%R< k}1k"BjGpv ]lӆ!(aCp=1}9kX"ZfB4 vi7j07Xh9]I$Y v%!tԛ&67]N(3+`v~Jv6 =؅cc!`Z/: 2s7+:#$H/(6CnOb&FGc1WݗWV-owX>mmY|pK?$%--@i,vn{lh ihȖvcDDĬ\>fՏ$M1oTo%+_雯?;!Lwq.<@)lPi|^tR3Nox)UEYc~]BE5!_ɑk赈)@K;T0)Mh<@J=cGpHP -`0: @;1 8q-w? 1 uMb/ HLP ;=8/%ލV?ufvM_B\^րV e5c4"""+7JM>N@,bY 8;v=qk!ʙREtt0 `&P\{9! ܠg smҹTxM]`jSB85|s?"""S=Qlt:Ƅɺ]$@Jm6}J;JS1`.z@yPaC5W\/q(\ly}y =ɇ[~Z8 Ը!c \YQII`faR)TԕxJ0E*a~oDQ^4 VUI1 $[c7jQ]߉FclR@aHFhQ> m! !.ϧ~x-qM*hJ?E[Krѥe X/ÿbJ\u˶ ' XSOC8}J~GTmֿ}fjS{4Hѓ+T0 >1d 8l6Ho##""6qQSGHh C&~֪ 4pɭT_FY)?g &B@d/S/M[Zn+JilWXG?"bBXHN;]$ Up$: w;@XKq]&:e'8TkYXS9i(n~n7TЗ?Hu% UƅjФ6)>me=fF4RUGwXDDzcA?~`2L~ X?3&UBX+z>r~A u 8.lɐU3OoE#AڕuRh FUwW;HJnh9EE9 @D&c˒_7Z pOi`3h[7QPh7z&ĤrVENN`p @YxcZi쮈iThi p=cx$/¿_x&a??ɻk @gsPh:FDlZh`Awju'@ݡo\˲3>ܛ߳2M|T1J֏KSvF"h?|2  ẊuJwv1W!'+=k赈ŮдU ( 0R"# *0мh:ֿv Ѡ@fa+&4_ip\v@(c^^?R`vE{hCgɔ^UrGơü2lJS_=}CҾ@#/uOntDcwzo 0~%/(efcb`M v ý-*&?{ uס4C8B_ZjGT ^܆%aT~Bkd i Y"b=1sBBh#'PRPu|DX5_Gm|%";L$g~H,+?2~QجRC gA;i .t=vܺ]^SzkULi ҧAxE@ rq h)16_^taZC Ep_f)Zp頖J1?*sx5m"pT&hM @_a!:w0m!cP۔1{ؤxJ?|5T Pҁ$֥Xwb lj*̅kpwg~:B'"gzBGRٷkA @ܖS산i_+MXSL8t P㝎'ECIRMxHEK8/Vs49~7^Zjq?0R34 @J.'J! mq}E$4 F} iyv90y`l.w7@^*]7.Gmjou덁l6Z}-1@tμH B-`wڐA3atm^U%N-x#$oRhaمBDE'6d÷ zU%W9&dC7?ر46[_ Ǽrk [4״lH1 1'կZӅfK wpF*7"""6a]O\shو&t=縇w2w!_c@zR8r| u{*+4šK5E7~LNp GLw2(Pj[e*EDDD;?1ArkL࿧c a߱}mn,tm^ XhY7g SfF .5]|+mٗp8t")QH<X0 gVC59AöjFI%݆궾nН:B@,@tHir*h@(1᭄|7낵ch8$ǟ)%IDMC̃p#"":aG]50>eLEFeS!Or0@tjnƉ\/W=ƾ]%RE >Mqa쐥REoA%d6,蘻M2/,=/#-PZDẤzm##:{;MUkh5'0`- tͷDGOa#߾"~):9Ou.XڄOmJ Vջ6@[JB֝4>-kBiާv\mu-$ڿ"FDD ~uV3|p?>1N&Y6r⥕^Cu0p0ť x=9SdqL {U*(09B|e_T&T' ot !9 44e*?5nPGS%U1Xduppʎ* !~'k@K~nvc XrM,MzKIhڤ|8_r MuM<JN5S+\tuC8*$o9c =&@AD.X[j%Y6 ]f_k][OKbDDNh 3dG6XgrKw$%d.$X @Wqg}DDlb4 E7~*I*cADMЯ˷:ҥ6?- 4([ Ҷo?H0"""6NDe*e B:wސm͉7,O𥲾x.qY@j<4vɃ;fpڃ< b5VoR]jA8Һv2 0# Jk@IWQ،x$nG۸@*! %uXM[hF0u>ֱ|gnQ=;NFiYs(_Ÿy ;.oSn d84^@@e)s'1 vjiP*ߺ!U0pA Dm\noa@f @n@ـʀ$HynQpso5Oڵf w4L5֡彵ܜ (Ȳr2@44ؔHcլҗ:@m+b t4)A @*c( H`YoI; d9}t\ ` nOU Z 9@*p J5Ӕ |`A ?>$7Ik챈uJ-Or6P)U8!1otw _h\-`x-^}LC\IЗAu*Pˁ Дmq|e8iG/%-]TOYԿI8a̻Xx윍w*|t?UY)TK'Z}'ׂq_/KL@cdA&YkϮYж1 Cf@N1Fa\hM];vaD-i4 0#J@W+ UTu*\bvB:(""b{껻*0iR3I(PJ$&Q:|.jݡ(mEs,sjZoLjH7e_ P Zؗo7u }juݶf@i$S*oꁁ$@O!pApjt ڪO*Ql0 )Ǡoh'D.~y4b;h,Xk u@ΘG ,cO87m|XF ,@2w[(_ΞvEDDLOSp WO+`% f$@!kw.4e# > oOA^2EҤQƜG E3.&Z8ZnwOs)WZ<M p :-""bmJu7ks,XI$5nJ7^:l1,o.@}<@ vO ơ{[ E9X0,3mFc_W?zx@c<%ImHElDG^NuS4N+ػ7|_FqLoO,U 00w ( FH}CJ0f)A Gnh4^lQq)s. 8dBRCSJg[병Oe; n1褴?TP&U..eUqR Чn*-PgsS&<19DI1OI!n4i^e6 ,",X Bڋ 8UvQH@J3T,\P[B;]41dŏk:% sNKJLBw"@ojҨSh, 9c 鄇i4m0H_I)!I)ES{14+T(=? R#"6>}Np@:/aiZ ziN!~*1O>6Pe5/)+$-֭QjLKsco"m\@(h2"SMp߳Hsx cBp6@Ȳ2?WLQ GT$PCR],PfV po|H[<9o 5{Z'd6! HXS `z ՠ6Y*2(O}2P[fx hMב @x6@@`@USL9< ?""" 8USD 0dUdq vbC ik_.,f :7>nA_YSSR0kV*rK{2 SLA}^ Bl@3pe@:>'B8gimu& LR@7*@aۜyu\DDĴ8Y{[Y40Hh2ZL[!@B:eA7m`hk˿6[N|dp't`Iu_| (-(}NS5 D[_WSi$ːze@jNr@;Is4bBl@~eH]SRV5np[$C4y+MI hp;{4FfD&W480PD̐5k>J/)L7+$7(F,NL'/p:5HAAofBП2-zmE))+kl_/l µא ijx ka[H `⚮m'jc>0T @Vu:` W 't|?"""+??RE.NnVt]Z>)ݏGX\koLS+3jW0:y`jt2Aww -.nV2 У"L@jR+{/""bXZWֿJ?3 @)p Ide\h,euo19f{}ŖEPL!ꩲ-%^* p(V\*~FOmJl4,'*N?RZpj({8H10"b81G(g@i* ;s^6iICց}c]M+=@+ty YnQ@`  ,[% go'Jˍ_yyD DZ}EڊsLA (l< 11>}NKhf2oҒ?5+ duQ{%}'=&S`7ݣ,Me]G_yyS%T@u )*9- ,% n*e#᩻l*mf8z(F ` nHaǿd~j-P$?G+=[(?0 i3T%pD9 a4?; |n#yfe/|7glj M\kU|fhX`Q|I :'yKL)LԽ_ E~fEͶK- Z0TgKGDl ί#s?7"+OH  %)0!r? / <'$/ՃLQnVvk.r /H@`h@) j'h?$1hK?~(l1Fgv?7_u,(TncN]?BI?E I(G I&݂1]u X]zkn \inQ[? @ 9\PTy_LP `_k>W(bkR&UJ>͕`ί @J$d)<{'c0;16 3kOST VᏰw(u7ObQC?gTu( '&tTLYe&L ``Dx1t dߩ7x1J  ޏT㷣kaI0"J2> x4ߧJ?nϠz:Cҫ#J!BRBFMw]'qSW}1U yԷL[nSh|G15j(. \ )ʧB's;8Iuţ=ĵn~ynH L)=O b5@K F1 ` y`rT@TB^PX6f 40ݗe4?"@u{jz.G`9&`~RNsu]0QDŽ89Ο;Q⓷i1?W=?Q:Mn?s8~z2ڹMr8Dp0R0 @@@5$ez@A I |@+ ?oj%@?<=t䃲Uظ竬ZJ@) H S0?*"(GLO'oUHU~ m'yB>m sŢo  `'V?NR @t9LID>Act>)PuaP*Z6^ѯ }a󗘾&@h0(.M ̀2!^EDPcm4EԿ #1~ K=&DC|D߽(x=pE5hD@<@KnQwL. uP4ܮw:`D *Na[IJm $;3_-L 4Hj 0w Ru0 Fo1%0"bZ xpLJ P Jį+@CU} FRHt$DZ;|">Pe~*|=ZBU|W(h* z4bM?v/e 4_/IMCV$ ;?wM`v5){+\ipo[n|V*k-yn,VphEO/Xvc_̝u_@hQ8QF [hޑ?#%zHݗsTc]K~u̙ P)0neSkWD8Ux+bs_{Ywi4 n`"Kf7{i+(.B>g?8MTh'.0" ө,ߐ(3&J/ 0?S ?Hzb4W^ *[wz*,cw8*pz ?'IR[}+i_Y4/Pϱ2- <2ttEvO0IRs&fA/9 IwQt@g SWHGS"Z(8kNf/dzn.j 4,`,@DDL?u JjWWӤ d8`b'E~}9ߖ665qA> qE/eot?C5c:)C@@5Z!(#PHA{oIQmϹsܙ;0CT%(#(欘Ӈ T #~=E0093swsn87vwu}ڻv<[ &_Pg.[^-a8<>.)\.IeQckFUq%eʀ_8t  7Ϙ\Ǐ!ywK O6f6LE*![1(Tx^_`ݶl92iI_gnc!k[HMn 0EҡO` Kz@6b N3{։[s,*atZ37#%YH_Aɀ: XJD ݳOc.Ch8.u<!j+jLV#5ZN%Ivd֖E`1JK;K" V'Q)pUR1h@йI.(^InQp߱Ky+2$^_ 6@jK:x!Ȧzau`΍ep')v3;|wh; *SM?@ *@=60@lXg~)?KSf2 W caPpk6&F@Thʀ[ IBv`^sCx܆Gc$޿!l@ SO3f0WsүXwe[ ?j@?{xƹd(,jèqy 2 I16Tf, ם e+0ڶX $ڧDnmh LrI| -,2w x?s6n|/y(dZY_)O=F ]´_gIs`8;I̹s]k9_;ҕgT>YUqU!+zh) c#?) F<@Qw+TkA  N"&(珒[u,EzI[@GJ@h, = ׮s𘕈jO;_+,|`; UO2fi[_غU{Lw)ǜ] w/ƾnQPa{?R()k5HVXVŔ@6 %.lX:Ϟlš5k^ *4X.xV\}G&ъ Z9%el -L5Tk:yFm{RQB׿)3@n7r-[OAs[av^] F_u6M(!.9 mԒ =O*3 MњM<Y0$@ ֡KB`cXzRaa峏!<\ ȅ@:/_]h [&;y_C((ȏ[~+xFTJc:3~[l|Rؖ߀4@۩ng@,"{*c$` BC?)r7[$ K?8r\] [R0#@Gt;` [ I2` +]FqR& \pמU7 ^qz;ޭdK`dwOǦIqUZ!`mV&,qKީzi06oIǨH, `|$om `j}zf2 P yḊ'h }z础&  Y(@`){VbpA5kּ(WH) & ,zp>?1?X70 Uh4M?(!Z~l^' ys_XTev9H $i[_>T]7*Xac X;ߔ~D@R03Z\NC7rK].Uu e'}X~`myكN{#M9J衂-Jzd*s C O31zꇤCK>˯س 2PE →\"P*Ƚmw)hHLaSWH$\-SNK m0bXrJ\:&@^  P럑(\:I, lJ,Q&a([,G*@Z!0 -+`)9) lW4]l}˰ESjAa>&ZQ^&mpMePw77Y µC:J#n;3^jZLLV7Uҡ ڑ`>(4^_Y Ucr&Z~0!U`҇)@euq$pQЧCZн 4alg?< _ҿ0p[߈ƲeQJV 9Fl%kc\m ˖dHfXz8nP;6bj0- [s6" hDYw@E&m( 'Ce@^L"+* `UִUl1p]q\Jږ|  :oa^ (ncG(kzt=Ӊ;?c8ɿc# SpR{l3#PQ0uжɟmΫK>?gݤi?F[ v%0HiIF#2, hT%Yʲ̭[tFv5 س˰P$!$PM \ XXO'EX J3.4ׇarJAdOVN<87 PpsjNe޿8]X *=Ndf*5+~ՠ6nF `EFCH$cr8'-=fPXU]89gPTPs#Et).ZCT;`F๕ ޠwG= ?ip˗'Ṉ('t.|Gۼj}8$:^Zwtm NC`tR EHW@^tU67,na' ^!ΑOjva}[yN Lw}a!l{6({wk J)_$c{yjj4MO_S=xR766AB# ':zd2j8Go+1=?0c`#a Ks?SZ4!K2|*Mp.K+\esF*D9PVT8ymA|Kq9'&~^P)GƁb`p쳀# $=.;l_ٙӢ8*kSeϯ*/ЁQ-ML~"Cy2:(P Jhye@]+ ׯclvklM7rŶl,I4"Z%@)s | ɋ{=f+_i6A^x:t0:TeTԕ1@dI.àbnS;3 d;ftvW X/Z@H=V"lC4Z$90jA0ծnlf껊UNã 7h/Y9T.O/nk1wȵ! *ré4!eJ)!f2N1;Af$t>< ضIXB EtCnWyCcDoSg}y:ٲCmPL KYӲY"`LfO#u^ X 9\<b&CYVi SLzxL&nFqY804Ʋ!/Lа@Mү) y1SgN0qV?Cmqp؅^zܲ|5z= >N, &Cyw$JqF2XPȼ Q\|!FU7 =ZAk'p*GFCF I=Ї/? FQ+.sK:R){SI_ ?Fo!ߨQb x"jg!D# DQRNU) (O! ȃp";3TDG=eٿ42APa o {={OLy_m-Wў O곓##[/!}0`'󹥲_&I?HqMqX(O:H  el@Fn^/c3KJީ <,z$|Y8 cjzF9Nh'}^z 0{y'ձ~ˀ4>1czi]?-~B'?NG7' ͦnב'lS,Yeo~] XPxV|''$h@/vL8&m$FPkp=3WQ1,M $9}5Svmwy' ðOm%CI $X[TIΣ;Etx%? R / wbIn2 j塀G@P.QHLR/,'le`WBK&~|qwpq9q)=߾ph4._)9xyC02lo5&C!*#0 \?Wx6P_{ 0VBJ peV4#Y,2ץTz]Lv^}pѽ|Ti_t?Jal!Ӏ眐FGvT)#D֯o}'no{wk.ROGcW_cO lRQ0k# "ڄ-FN15'?7j \ɛ Pwy"^y%8#$2,yiw}^_wqh՜mL?8u<ػ7!X:'6\|6f}Y^ZsJWNS߈zt*&C` Jae~8\ { @{Ը ƀ聂aahɹ.xsN W9/+b<<5o=wOg o?`x/)%o->o :I N޿66^~m=B{|-Y]K`0[.|_OI^_OB PM2j%7N9Ծ;ffXɟ^2.zaT?̹-_u^TUimc_-b@})fSoE eK`6H#wԹ6v2zgkZUηy|`{د&8br}UmF@AQG.V#1Q[Jcz}!zkwqal6LO<_xؿ&6 Kse۷ws&b?Aʼk:F4fē- M.; !w lp@=0Jqʀ|(ʐtaNj 8T/~kv@n6yp+{(L&M?Gۗ~h\ -KɟWH|5ϾI~ 8=y6%CWEfqZUs-Ri{K=LӘ1O*@ld@2@PjyB *Z,@ UD( Tm[%M%|$ la ?<:Ap}__5Sⷑ(_'jX^p)5_UylyK?O?cT *b *@;%4E]"G $F'F $`oP2+f8{I{[7|LKz}13wNm.X𵾾~7xy?G$j@9HnI߉#- IRX;z1c oœye@ YHʪ@Y"_\(afXPQ8C2N}^}:qR'Br{Æ%m~eZC6m"x^|w}n^_A?m^?_֫<> ={WBֵ=Gg ^+PA6F R#6[L1# nU-RmSȆ&]y..83PRjhPzޕ\%^ "'.~tJ|s) % 8T{ 0 `יgaa~W,Aq\0ߡh韗1?tS@h)6:CQ69q< ˱'.B RKD+c*3iܬ-cPΕ5WLiؾxCO~\ڋnbL4V?xBx"g,?44)EWKxѭ˱חQW-kɦX /dj *öo|k~G&C޿ev2_;^Fft?/v£.Eˠ 4!MȰ Gзo#XA}M4} 4M0 ޗ SRxRJ`aTnlX} 2,xW6;ÛLjN S@[1׷qoLUKp8-k E"wHvz.N鲀Hz@&"`}o O>sW~>)fp yRO?'UZ80>ƒ8Kt9́("~ =\H l~MA4\2w8dC.^.ޣwF_G5ȿIP}efz.(SxfH?'l> Ȇ &6 DdBB;,Y%h},Im76 }''$3!LҠ~ H/q?k^t }G/p v~J@ @QoP#ATD(% BAADp򯫱-c{s,pɧC|g@f~^!?L%`+!"DH)!d4 ¾$ hB6 , .>4>°$G K8a6P@:/|KvX ;t?v+ŸX4_pr>KeX,RQA_&d؀X{cm-Ú3_r+#iC韒<oL [KIRȬ$Ѐ@H < 1&F{)3ؿ0_H߸xoW27p󁫾G?LGFձ—g >~^qp̌k ˱D BBhR#?Q2#/Mlf.Ů're2 `bd8.|{R(c_w_!}<cϸ_ý*0a|{ 'pnC!D>ٲuMYr1d?!$ '@_n'@~^-\ Ge@bq's2l`DaXGqz,1w |)S. 0QtF#:P'@=0*7 ^ `B>U-P 7FcMC͜e__d<@4F >T!8Ng׷n#oz3%@Ymٿ0x R'H$>ܧ2-<9k@OxiϢ L#AHU F{[<׷Æx;,YzR ۪etpEn955!P'|yYf Ab~1hx`,{セs >8q:X lDkcFnohc۲1C ;cH, ҷѷ>x\4L_S_OFVc$H}16|@-aZ_pb@@Q&°a @P\oKP"CH&,Gˑ%X$D>hdئE6s{gB&L~B&q|'I=%:%B;hxwvuup&*%18񐍸J~Y.P `A_& F@ϳ|*#m+Ӏ׷snہOV\NŌ6R.tʀa$(\FE PRF#`D$BcآAҞ V* `a:r9FZطx¶])4OD?f~ߠca']ƀC^>PL 0h?0㪍m-htYkv9/A 4@+p#~|-{#`='I}~py/Z~3eR' @][y%W>f3h l|M(4 82Lgxz}yr8zkhE!naY]ZE"7w=B{6pO_#GvؗقU/o?0/0K9I {)kthP!B)L@X6lxBތև><EIQwUa9- 8^~y闆~a d( R#0y]AB]/O'.~?# 'lKAUTɎի3E&X^Z ȔaήAu5e|zľ4@i|f_=|g?7R`}5׿N!%p^|`Z> Io݈{}"Lw;6qbH((AJH j's)D%% @̍XDZr 0Y̔ ;v?ݸvkL|v`)Ko/}%RU<yNb_e6 `,z@>C.̼$tziYЌS> ^?O ǯ󀌱BdnD[qG~Lv0MUYK+ĉ`V+ppVxsED@N.\dIױb.@!! XR/^ߦk9)K${gdGBrMe_6zdv; vyL_oGB$z=d<~=HHeU̾/Tcx?ی0~OIى> ͗56i-هmz}ix"3p@)I緆џ'G<ʫswEnLsB+1|8inCi(@"Hd?n!d TRYnl,)s1|{w2 &D6 ҿI,.'Zxf9]*Z.x.|~ZAq>to]~*7_2jx(]nڵ/߀TBwm/b20)vEjq$¤"Q JEYJc(&<) Ȁ, a^B$?V^j߷* ɷ'/kĄ& 2䜱%]@1[31Y3! !8]0$ 2z :` F:j`ܼ${^V:rcd Df@u耄p 0B@ad^ pv` 0!FH_,Bg3—$N_/_}<^Ǔ/~|iӉB̺.̙øIȬ7!:&@t},P+56ȏgxQ_6oA yK^ļz_sa]kDrY!'B( %|^#ò$DާyɟIaAV I"_`)42Oϝ ~]x?wHR7님l9 q8I]iD3 qJ"v£E󀾾.cj2_!I-l m2%Hp]5RQy)+l/CzA#t׼]>jX?V=g )Ozr=pƜT [qoa!pqV/]$tBJ@$$9NT !@> _$O{`h?^ĀHE&J< 0Fc8_W2"W[V<(d 1kS4YPn *ڨ1יDcxyxf;s?S"P*SҧƋ(K*Ⱥo{K_>I?)ף(1 oxW|0=̢AZ:Vzt?ś ,Y|Eo4`zzضsU±)(YH#5z MB{YyPO.|.}g*'%i5^ C`d>`6gA2 ppbu/dPWۚ. .X]ăxHnySWbߣGy3֍j+Ćpκ n7ou3U$gm:…糆9uQ>/8 NFA[7HQ{U^P\ݸ`o[D-g"´W@f@BwU PPkb/G[/ÆD_, PErUs snlω?f -kfUo$"}t-u+!~_y#lR9d~@Um|%##!/?s~B qsx@COk@,!^; `ߏCCtG,UI {ĬtlY'R!T)ylYQ(.*6E@&[!T؈eϿ뷑=|o; ʀۧVrx ɟ?W` Gz$`<:1/ B'(Il>1g$xLd IsX_5Dy`=3$!RZ8y.B]I|>RoUn#3wҨd0*AX^_t`^@|%8Q!W^F2=/?%!SRuAar">'?ugl&Y J̛wfz G-[xf !5LOǣ~ !}2WC8D T[P y9`vgpU_ad3N6!"v m~ٺVQӶBwHۼg?%mK@iV~UnY>PideܞջE%?x0 ?~ߺw:bp^{ >xK.z;?h.Z݆LKz#`ӗZ6~r9e R9q\|]%~9IP[|sYf08yr ҧgk%7| 8c2D3w8s1ڎ;^tV/d:1o };@q )% _3=/Vf,zJ,!qaP2点+a|tOf6ͫȴJx6Sv՟2}%nްkC.NE?۷o0 ( =%DHWlUPe_Ap\ _.~`<p{U1n*wUkwܟx5 cx2<{& ̻K޺cǎ(rI' !VCOW?. ր@}@VZ5=A`X 2aW@FкܩK >0%xg;6\>̶N!!x;?NXNjRI[`jp|%`+;v츬h\ Bx#W8l`׮.Kf*&`ܦ N5@Y_{f؉PrnXQ3:=}u},>ʱyq|5OQ_߹sK,Y2ZЈv0 OҞ?Wzn6Y;N =m*@HUe/FFc٢p/J՞B׼ _{ y-gvυ[c["1?9p+;H7~Mdؾ}!R_axRH =zLZX5KU[A5/[?K=s6M] sX J`?Ogj +\ٳx ?H1K.]}7A0HU)1 GR\l[ݡs*_{nY^@znKQJ{F19BxkvW4<ص9ɟ|]Y+e ..w px$x'^`رcK_q3ˆP\Je[G0GrN-~] Gkx:\uc%j>j.c\b#N~\Gm޼^K6o<Wsu(FuvL2T5 @W ~@>2R+ XzNV2Ji\FuQWwtFcG/do۶m;nGaDI8wB}`$&l]% MeoIK&a}.}Yg,D&qWe;k5\}޹T'g,?_=3pRUMxs`۶mΤ@=p ?d]M浑RbU^m #yMε-Wzz=Ro~;pApQs,e/dg9lٲqn'@DžI)5rG}$kz/L~.^lƙΧrl>k3Yi]ݘ+q/ sм}`Ej/@5nAbꩃ 賭=U_>Ӽ^oow_+}SA0蕀It^yd*~]FU*eT^DŲ{P/K6߱y58ڬ떐HekcZ2님9Xtwر0/QuGt&`~RWBXԸ%y2-P%߷b8֝ wcko? H aԹ;7зRC(%.ʌ^Z]D 3 /ğm#˨leB0lݕv4 b6$WL ?nolsWBf#0,[Г.M^RʲU@_'Ju|u]]mκ~'He}:&!~e'~7&h_s7n KW:f+0ATZR6H;˔V6 M])Jd|eN/ݺsg_w?[`$]f݇\먷SݯvsysڀRO?Vۯdj멝QX|C7+d'Dlܸ(.D \ ,"%I{3PCu }؎ :D_^.c(m1~KUŸ&~}p9w~dǎ-_ܓ$`kS44u^'`^vp@A,Imm\f 0Aeymݶux'#SW1( Tu[*C SƓ L@]cǎ-[I7Z5FP~K/!Ëj&5+lmSI@},o?;gM%g:}Jm&I|])z4"Ko1OƍAFqM *YD ?d4׶*O?[κ}u^mҽ~=hKJ.\~aٶ^WwMQG<{8 SLVX=3[ֵE03@U$%ŷ+BKLl[7_yuIڥ(pL%$s%޾-3%S_#[`c?IwS(.ٶm')v;g6lذ4 ×H) .sjuks^%Bm/w:uL:PC_l[QЉzPC(k756k7wS",Y0k oOiݡn_m۶VX i֭[OB|.%*DHK,JӉ Njs1{8]]mMaewQ%m҇M!emA6@qm^sM^N/ٱc')`ك-[dłXsx|1''SpwjLງݴWm+4O_/%}=Dw\d{;>~'i7z70|B(`V&^t>phBQ2 LvX`Ƅ \El+vLB[mmi7įROW([_+C)uo? n{E;7z͛7/KYp5~c6'EF}x[j@~*V)"n,dorU-UTꏨP\5 j4Fhl .mllŋjZG3I6mڴT0 OJ-,0)I*OSEBU_t-k4͕J3$Ցr*ۏxyU9xB/—)~<22y"x 6Fz$A^P3/$K̗y k`Ҧ(6E>E2DڦO/Kj|ifvN5=t~'%w+:2!nל}-mk$KH߈B{ׯϐ``ʯoJ^:R _phhhژ!ׯ_Ai wID Z۶mUt၉(5 Pr{Zw{iT<%2dy]'*Fc$FJ ?[[@M7G?sR(0::zߴjժ)x(<:7f 6mt!PV5!ן u>Ի i~:5j M!Aۤ7g/ܧtC]f%lVw1ҷV/oHt{D o ?-cd]Y5 [E+VV50>>j]~z36l誇_}r r 08K]wxla 'mIS;۵Q+(G7;5 12y 8+_g ~iwS 8T, }waߝc M6-0 OlJ;O\BJzVE0Td{m٬cu} U"^>*,{!꒶ ~IҟsYoTB?M_I0]v s^W8<< o'c J) c[)ۈ#Enk&` <_8nctt룣oZ|6x``ڵF]ɁӖ xMz`RvgTs8ɬ~ݨtdX.VH#dJү5nV\.38d~+mĿ\y*_S5֯FtA Z[n}af00aÆK)?*|;w Ue`1 Pmڷuc%U:i';M7zd'l@>t[^?>R76J-Nߎx=RG6` 0s,]*@muj28 s~: ?7'vUƷy>@kItQ`!~E M#s? %~-iD끁|EQoi7f!֭[7$| U*APpԿ!Xy8DBf7Hb`~e"ULe8LuhϵomJՐ]KHV@>.Wt1|UW$e3׷E1x _ǒj|E###+WYoblذd!a.$[7/+C`|_y SW1d01#1QitNf]+6';ڭATuc26_ۈf8=~ϻK&3@zR@<샃~x+f ؈ߦ">lO$hWuNr$ڛqqXCc##g!AڵkI)R0~]ρZKS96-$dB$j#Oܕ2`qvZۦ8OݦJL!傷ϖI*7xmz JH@}}/xہp=Do!_!G0\UX h` j߼ zE϶C`e].3*I"u*h+4_/#}FtO|A`#}_E?7#2'unJ(c`$HKaݎ&10nݺG !s5iVuk%x WH"ki^@/ ),An@fa\NuTKkPˈ0`vSefm-SrWW+ ·. {e'C }vM95I4ܟv:ί(0p[KO߆vx`x`c͚5RʗJ)YfA{VW~/S>i8e.$, lBҖ`vI[W*ٖ.لUkT^5TeiVm6²zl~˟-Oe#On]i?4?.cc$>=3'XvR0c0]KAg2D W{' ɊǕ;B~6@9samF!{N!jɝG ^oAH3}.Ee2?moۮKXJ-^3? y{wI/Bh,ud>z[ P2EHjSW: P41P\i}sލQ`_MEzle_S!S'Ѥ@̮|ۜo ضو_TQm+\2?/<V'֬YWaB yi m۰'/ H%&$fRRB(@PJBBH(!TBJJ@ 'd(P$/ | {h+cXgҼ٬:PCP %8Dn߉tϼFrɟ C)ђ6bwc]]=|O?-2į)H0Ӗ‡܆k<s^{ApFq?A cۋN\ГȗsϟrȷQE. U:N U!FYq ՌתRv8ҬWjP4 PWcK>^ٯ'n}=얱j~(?DRb\g yosYf}Rʓ}}#k΅/ IH{C @{>THOp*5Fn IJ%S#Ə_u{*Υ}rܯTYH}(Tv˼|;qx|?7P6ϷwJ_? W>tvZk~CT_#wmC!ց)x~ߧoxKq?#^@MC#k#{ZA‚`*8^ܖ.YjP@4r!zk.}&~֤ǪerJ|9uBޜ~wo R[^XfN9IlsIY&~1F _k= pR!7 B`й!P@f hU ِ)/?j`Q;A+>Ssu@ПGyzpfSBOɬT%q>e+^@I",ԣ觷$oke{O n}cUc W<=j]ᦥ8ZoGr5JT/PPP00ၢ_0Ȝ{A3pp >f-n~>3bx `W8Ngо"pE>]Sɟzٸekڸջ}>G׸a)( V %A8z qCв-_7)FiЮFApJgWl {3PL ЮFEv^>ۿP Fdcr2qu7ώ1`oqzgw<}޾s(Pۺ ~?6? ~Fj(7#” ksiҟ(,?#b}'`l_ V`U w5Nhm zPU>ImCPZO[wOcǤᆥ$G 3tAj tLupX[rZ C&J f>Tgb]~yNOoxL9D p^*BMQ$|96g^?kWh{L1q))4ɀCVE mk!1Ҷ-(عKPa^=Sey}@R{n VDBSBۍuoU6UjBfyoBeo_;X;Uꐾt–Zwްwzx'!1NɁAio+X \2/ E%BיZM t(c`|>v$+K-okl2;vQ=߿6N1k+#OJp)VxxL#S|)I Q 'P Lz %~:ȯu"H`Un@0c6C$qU\*)./ cv§\됾и7O[xcF{I8NDž%@\μ*`$]iaPp{@,u~\dWyUq~>x~D)+S--[ֵ_Ic_rqiX?Ѿb/[f[TϷyt=VPA(#1x@Ap<@ P U@T,wmI_n2˺6HacQo\Rw\awa4Yoxxg802# qzU|6 ~)[1F^{oxxt\`'*aX })1\-PAhu~)b_[^+Nz'R#ABPq}nۏ,ORrܷ[)q3}ã'  0$~,\$`0RX.5lƀGآraszǮ& qB {Z{1{38&xj HM_!11稗 3B\7~1q=sIENDB`OSCAR-code-v1.3.1/oscar/icons/intellipap.png000066400000000000000000002323501417327530600205670ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGD pHYs  IDATxgeu~k}ܵϧy/myU(x$a9%i쎉qiuFQHQ. $(z0 [0(˪J_f;f/H@y{=gܷy$G˿Sv=ۺ3eirĴR(WמK_C,ѿ|$-| ]n[x`Ƕ]Nvq3k$ ".@)_'{ O:?z~؋pxe~Cѣ_zY|ի\y}˫{lFfgf52cB 2%RRJ H)!ˊ z}'avaJ8n+ ?X>|X=Qn љe`ʊ=^?~tɅM5މ4AWBp)"A191@Ĉq(e"RX1>__w!~ȑꇽPuȿYnq[@dpA l6ݳp5hO=V⾥ù̊BNn+{vNq>"D4&hTbem/Mci~vD$ȯ `~D{Xg|~wC8W'p?I СC̙3',XYxݨ{yY,I&'f?CnkIZs]RA&LZ l8? b`x; _p!o~s_?< }w0k(ݔCKӰAv[=z +qRСC4f޲{[&hueӓH$W-IŘ,Y b1X^" 9k Ƙi¬Ux8<68Z|U1 DxOί?qH)EG9EHСC_UG|򓟔 WB7mڿNMqggshu'h6 @GeE FD "9."0hv2gr"o~[[qzk>. b4攓Às'p}/F$dW3=z_׵Y2* DYtsᨷmNLeB#M%iJ, (#$Ie )qI8 .@HRIJLh/A*%{o~C^tLeI4i6ĵPVFE^Wnz.^v_=?p,tW8xQoOX$$";-ڢ8veynnsÀ 7*['m҂\ 9c1CҊFVʲB^Z|(P%|xžrGCIЂ#LDRJ6zl8s8P "hD>ʱs܅O?q;˥g/|]8^ GyY1$QbZ `Ղt Rv@JѳBmK0ΣcٹtneHUIEeac8(GqB(cHӏV9 ~sΫQyBB%\LIJvSYRUJ#s5&b0D6Mt053-W}׽p׉OgW=G0 X>;~~۶sU$g[PJmZ2 A{.?e~27оnβ%؃Ke,|hh]JiE9"Ap[.B( uK9zBo5XL6ƀ$H)Q[GrFUې&RRAJUE!+x '?}k;w׮zWs(4JVȫa1z*ajbo-,5fC"0MfW103 !"ҕz(HYpH|$"p&XOB_% OA՘aoaj̔0:N (Pkΐ@(uIRﵡHAF0EˆK)11)|ޞU;٣8vصJ{8(6pEp.0=1s Y6Ug:;83֝3nB[`~Q>@(P`4C'DXx+䬺r׫= ,r YtZ>}V*P/C9ׂ&mi#f^CJuB,KHr'p AhA2 &yG"RwBHJ+cV߿A 4Rڍ@$)$qo RScn-1(1MJ1v(JjvͬiNt8¦owP'𧾎A|FHʪB%%1@uuτ81acra #BJxaǎ ymQ ׿w~>D? VDͥعvgSݮjR eU`a`0a4cT( |t܀䄞>G X0&N8 i>{MUnl;Hr+{ EQw^MCW]CHpR.G88OyN:H!BTHi{Bo;? -++Zw%K/+ epe*vcnzV {  Թw!MBD&k;j"n1޳\ 0f N6~(S|nVVJ5RJt pˋ@%ʪ!8NsuzF/* ~mݺ߆ HQxɦ44E\e4J)=(%1cZPR߂sUe2A*0?#UˣԧT*Ro<%FM~*r4Z=|?7?;wܾ}ޚđFL i頬JȪB%h7:hh7hfM$q8u~V _kgaդB}5޺!u&Ӑ0pD$nKAvRQi8J_eiu0L^1V[ *Ur[@Vvn*4 Й8VFJĻέ/>/?9"ϝ;WkKl=r˒Dm4s'arr  [4(`ֶ(ʺ!Ţ@T5أl`gkki,8̑\f)iu "% ZQ(ͮqGbL3' * :8)!oj(gmJԮ%Θ0jt,bG^|g]x⥥۠wy8l-M>#xýݿޏσ[أ6e8q): oq5a"yCJ!! }I9uv_f< t\FC=uUXt>7_s Xpsn[Ǡ4aRhXVD.C*j&wM5,!`{.0 %@UjPr4JqxؓG|ӷ9СClrrxIo{{޿m={wSS)Dp+ ܶ7ϛstLձ`}!y@-UXXi}LX¢%sn*(tHʗ{[[SMe֮v+ t:0!PUU1PMn.P eQjn0eLmAYU(p]V\W/F_ׯ|/>Kww(:x _я~t}۵m?ևv.m~a] N NWSUr 9sn͹W/@%. SmM^NA,Pl-TQR+ \[`Uy]U dU^ Ү|O]|%%BtEp8(._}Vo㇩|\ScwUw=;߸k hZ1G'"Rծwŭ6ԇ i 2:ʓqhE8@ݭet}ƜV]I[U&H KTAnX,:QuSKXlhTw\F-mЩA@AS"8 x0f ReU9ן$([t>DϥTK#]8Te3dC`tɯN1* |畱^!]ђ[U`K3 _iQ>BQ)_:y}SOl'n?8p=uoջvz; I(qbJIVc)Kp>?6ҭʺG5m0X^UVg ,V ~B.w]ۭXJ"0'ro8aXuk]7#J(8sո,hnՍ?Jq2X#nqʱNB菠5XX]_}թx.Xgg>|]#TPR:i^c7uYhUr *vkLs{, ո=NÌik9)u,RV`LΖUiR}ӶoTUU=. t^D]ep(Z fJ)Ea8D"q .P yQ`42MTB./}nۙta:zz7MLL!k6P4O-`)Ck=(Kg8!3j!0L`j,z^I3?s! .d"BԄ&+`LMñ{8KeaWbYiH28%j?| [x6Q~ 0dN@1h6`S\2-h ~?{NC;fz`ݭ؞P*XtTFxdbB @A3**KJSI)@t!xHןPJIJ#{ܿddJQΜ`: 8Jpeo ȸtppOҞ:*kVXe捬/k)*rdw](ypێD&zt'jl@kx~-SI Xg{w>&5ĻUg}ٻK޺ 7t>ez0iImS˹חu$\,@a%rmh>Ƨn\ ϭo2Ƕ?eVUqp};:]"F`pe WڋC fggG^{`;| ;aE}@ծ F۶mGz˯iq}#}cSc lvdxSs-y͘WUF =Kt`j :+s0Sk 8.#aҊ8fK [$I_0Y[?_X5t! DQ i "D(`pX\t1_~ pԟxCЁ?o~Gv;ض]**FRd,=5*iVAp j]`].^#Vإ $ t%0Jtmy4u+X8JF]:6)a.܆  UrT xxOpꇐyWvMҴoȲ*}(;پ=,~Nq:Iɖ#0*FeQ`Οկ_Ds& >Ǭk=v-=;mhZd3VWVY 05=.gikj\P%i3`\4~C^ ,>h56i *'̲ 9w1ȱ|qek*X?P̻Gۃ= U3{cWuSc>o{UØHY44c!취ϒ8rxJKX(SwߙPˬRcޡ]{vw܁@U$bE102Hp2Fy*)&uD9xl`C& Ÿ̰\<21pI.EQ@л,|z&4ɃS.'Ռ=T;V5tk|˩s+iRw}u}qxu[JV>qo^mytUQ,ʲRk+/?~3~'^'? [c_;~y;К JD,4OppXa)t]4-i%zU8ԇ֭"lV: d<0&"2ߥYz.r߀NY^V 7.1n+\梛/'F?GB4mϱfy`~usZ]EgU%PqJ)yPSA8cMQ2"G7no<$z\~3?Ziw}czG{,)j;vM)1ΐ%VosT&'&6[NrB=- [.nOrPP.uXa'^"rUoJ)GÅR:.Mꑙ!2vDYܛ[Yu{/ mv;aDHtu5A# {mC޽#_ -UD(QyymiI|[_ɛ|'Q |W<DOl#qbl뭎P!5W'xޝV6nm'\{.3[$Oc|;-F_Ie{F0[Ym?O;_\:И@ۖfVW0ǝ[k$-:V,gN7Ν =}ϾCw[017ek>63EXZ (sYoT?V7+30V.gO^8a;m=:kD&`=&Vp B Xbܹݨ+PbX p-g5[2T:kVg@1ٽߖ5MP`X'IZt[Iu_ϝ;WJX_G_>s|1cx8Ǻܦa֪ 0Cm%4)4d + 0? ۨvǺ(4+zjC[`XziRj7o8mtkoۯ]p )mΙs]6(Pwo`/uZ_qCUP+u"^A 7~{.d JqƛYV4;Jw\xR_{E/˿MLNŸ5k {ʔֱu(T)Qz&QBaߕdV0cM Ͻ7d'( I):VvZۮV?"n[-Yང9/-P.ܾӱ2&؝[yНs\AQ[80^k2wdcB,Iee __OJ(r YW~ᣟ~ٜܶ81SnjsPf]Kk}?zw1G(eUIRFrn @ XSRxQIA2 jt`" pyUM7goŲJ ^{dFl@ -/ |NfRgBT$K?]bySޚGH#A< >IYmϺ36_9^lfE@fąxƉ|g>~@tǼf #ǏsߝH og<\5nv)+`!u+0fwUlhtpoi{NqiHc ɝfS@: [8kYpN vFcNn8X=}n`(\f3`)vŌMk:o-t]zBvAx<# 2FABAQDUfBCϽp3/mQg?[x~x5Fcuonm\rygm>8 @Q(U,E1"!>궑ʤX uY Bp?^g9k<gEa6a`>T~u= ӊk&@aIMD̵3fƼ.l{͋P" qj3U`́~&s (0u)cqn+N8?J`\ /ѝ,-p0!X? KdprzƑK\ϱAaJsv&fzƦTnrsog(^ /TB$ b6"=O<:ҮD-]B{Jb7¿paM4tj!}2/H/7bʆz ?ՅAeQNpk=,Օt"e#C}еtk]eڽlI!ZPYW8۝?v8p@?C~`&m>#S"ʺS!v!]HUo`3;lW*BDP :c'ƈ> LВ1'=a* PB Xt, F<'!>C]: ߸9A. X; ~G=ܮ> AX}FR&1BZ= +!f%YZ4Z]͟>yvDŽ($ѣ]SBJ)AƧ}AA@ qU)Y@5F H }dI$N2@f*䠇u\8h8tDΚx4jP^5Ъ oJYJp-KʵJ,8y>/A_w6,"㚛&QS{JFZJ)G\1gSt%AeQ:P^AdԞ+iO P fg}ͶQˣo6b<<5;AURWv I뚑ͣe| D/>|ʲ f eQT7EBw1R@j֮[hU [DM;!p4AȹtЖ`VS vyJ证҄?"Ǵ+2A+9]$ÝeM7\Ll-3?cBx0 >J ݔ(b4GV6 \ tF{8o{@y3 QRHmV>cG>RBB+0;;vU%稬U>E455l[+pHR &x,˜qV^l vYn[e'U/PGYc]h/>'&9eڼH0_ǀz>mua*10强qfy!V1P> fS@1#IiڀlV FfZmTg=zj>ښZt'usUAG]ʢ}1."]ip`H:Ahʇ0 y.Dc! 78 JAhV22!K$n5i;ՍK>|ӯF"S}|3"$ ( `L/cqx0k'kkX_3DFl. wN-,{8l-q QpmMm_qLsǃ;[R@?a'4o(ͤ(QʪDQ2G (Beh4鼾ADF# }3N< PsCf`Y(„-04pm5 FNK_ݛ0GQ(FpQV+U  S3֒R(%I@RW\8nRhWc uD^,lfĆ @G&Qdj]ϩSu5C5*.W6Ҫs;u}MXE3Vj,F/Q4K-cfi¬wr6&4Z]}-N\;M6G`8y -UU}fxIegL_H2Ma<"c!Zb,'^66(u gՀKN  ^sSţ?6_رG@@E* eQB&H)!9WoY3P Ʋ -K ]O܈fkeJYb8Ņk1ڝy[a0_Z2q@|7 .C< 9Y DӻPVUYa&6} F UzG98#DQ88n膞6CT|^o/ᥳ'mjv\! D$M!BQ@̮ 9"c Ytx6FWI$䏽Wſ=+j@M FȋQlfÖ́er۶5oc|K b iW(ڻݕ1l)hJq),X#9q{eiV뼇eX" 'y^`8a}s+km1 ((g&Ins \Di)``Z`ق0n S X\Ox#HeC$F@Y>ϯ\Hy͔lM9*/aS# k+s;竇y77|q!:r+&3 f&"R{ZqUAUZk /MޘXeBݐ-k/^h6Z+E+wn9(Lw1mΔ*J'#A1%RAje nqQ~mz V#ZǘK[mܹIlݎ{ U-y9T!/rZq YD$x@q8RLsʏmOMM?Fogzرc+!f pk#7QUBP\mqA1sA97>3&8a; z/iPѣj{&eKwţ! Zܞ 8𾅕{N!IS-t'es8*Y T8\ng]\&$xJh=$i0!2 ^]GTc,P)im+)+W3x 8q$n./C$vl߆mD@k,A(P@JTQ1Ġ7ΠCBt'}7\ njKI6l$+  M+$w_7]!2\]ZByZ%J:SǪh?+:Z*]-Ee'tM3 SI "YQ+>M͏Q 8q5GToe3TI5ؤ9pGt |Z#MAlM"fr4jcŬ:9)AbArPG#hk\ξ> Gpv˲&okz`0,0L " q12iAݵ]薠Ta:r:~ϵK~ "t/%6z~:\7WF;vh"M!Q,!Bnʃ@sBu" Ж q>e_L) AB2I,Z Ě[`"V7AUBSQ8dxlm&11;Ν;1꾑f}H+2Z.eڴ4E1\^tTQ轺`J{ p 61)_bwԣǎ;8__önF,cY$I "a's_~nPf!nuaiCD%%8#^_  @.]TEgA-_\=Y*3/>W/#b1fc~LGaV`.L-,z_y-"ŚYFBGB[4Fa87)V\.5A |.6ftb4BeJD+Y!#p]yFV}W8U9Q) d{8GB3z\ƉqsyYbvfNqz O E^`4Q)4PygRzB.ʱJNU?lC&,ZF#P>PU!MV*g!Е @#' 8I+\6Vݻjhb%_6J۷o2x˛ bp>8Ly~(B(Ǒ$>>ez(?%<pnkZ6To*#8qce)]8ިKwݯa O@Q(r? )ˬ}'^Gh}x6וUp8}_TnC()Af4J.`\[9XԻV{Z9W'aB nvY -4uST4͐f z=Ԭe /̮a+jى2ayc ~\3tsF {vE7$2 4l{DB$s8#P[F[F)St(Tjg&h*F 8I$%181=>tQ X[_M\s381 ׀,fgggND"Hw \ 1"X,L w̩ogr®=?iw1Hf7$AL7o ?Û2#BvwM ,C(#T*9# 3,Nb8җO р2<6P EYj #,_YuTB!ݽ6\R\SushZ@e*d "$Ih4iwtjfi)R(ե8} _>?ſ>8q;s6XY)P[ߠze%*R=W&RJb9g ݉;v bT2!u1YNhe <}Y`9 0 ҊSVUYu6ʆLTmق`" ^LD|_KW|Q4$̦'))3.`(KՍ5LFq8PUʴ\,&/5[Ԏm~3ZsA`!C CI!M3?Dy@ ]|ߪ‹/Ѩăqgu=#:ܻzM*-: Y%*U279G&HD"nw]~=T(k+tW._+gҙ8w W$mػĻuPB!G CތKct׆*Mmց|姱0 vla4` 5la|g0;73OJfʇmlḟkUIU$r(2h~zӧÇ8|ŹsܾNS\Yj,IhdHbM ݂ d\9G6$M[ngjA'㫰 T*D=FFI$I23?[{[KkSǰ}nSSbNtGq^ , wVB)lKJ(UU@V6-tv:mzA3S~cW'q?.p HaܹgfXRSkb ([qۍ{QoCHxo51;ݡYL9yܯebumK7nb=<\_ tƴ$Bkrd]3ܽJEqTNZLC`Zu8v$8=}PE^`mcZ2FT%Jiy7 ZFTNpjdH-1y8R24C 1EȋFbm6MHa$XiO?_Ih] T`{9 vTjZ_zXJ!5>눖ֻ!/ AXv/spx9=1Mă{DV)s iD'c͍Z}XށN{au ۘS*K!P;?;#|4EػOt/<<E\T& =Hp7Ϳ`>EHY9bP4ڢ35Y<Ѓwo_~g?O@ǍF:¥mP׺n4O zXDAe )8nL*v |H|}8F~2BJoȻjY8Z|I2P H.0rAXcrhinp[@N2oGe7|Hm+WCdH,VDँ:9gYX[]õkWqE=/y <<^eKi*ueqסFw R*[⫝̸Y6̐ 1T,A!"aaVHJ8W}YX/_ Ǎq8~ra~m43HJ {4Ŷj"NbDf: MiG ]qCp}9(xdNBC0iAbD]xtx>8Akޠ8I0 piLt:h20l (Rge\z%%"fZ6ڝ: mIj;B $@os׮]écO藏O{;RXصsqdFIylDJӃ|loL0%?L&Y/~_԰~g $q 8ŷmtpǾHe[ۖj F5 M\Y=v!BL+%n:ȠeÖ'\}\x(>$f#nր@Y0Α$9}gGh "V=LpřxfU*)fVE#*2BjۜVX1N¨r=9c e(#p,IT!)x(͢&fg}n;X ?%]AJKA:TpYi82}~v $0e5\q^ĥgqIkeTC0d &N9q{_^pm2 Hދ]h7051C/n]xg@3opݶ0NE#Ӥ$ (ta-i6XZD$RAGJ,N DdoylC/2l4X^fW=>/cem33woϘyyXDq$5\݂”(1 xΝsgp3X_/1;Dg `:`ʹ qC>'uǥ5EmC"ZXw įa\:q9WW]&׽v033n4KS ׌b;3O?s;ka^sUSܶ~z @ €zF+ܛlbe8e]AIBGLW?|͵ Ə;СCȑ#X,KM4=)XzQI$ҵir B>.rY@pA8*Ls8\,E:J10̇ IBr(Ji)M3mcavܹg\ADl-1J ˳sHbz6@@QX]]ÍqEpgΞOcmEbvɉ9쟽|0Ϩu;#t{Zeѳk9'&Wo8ZDs)x_ k7rh<:s'v,,`vtY="Fn72}* V6oOOtlf4ty!84|];&!b =La BJ4;my_x><۬/0%"/Q%C7`SC*f@Wi:kN'/$8T޲` t] wxİ" lsR 7@m+/jaii Rxp3ō+رmVYHR*$}EQZS shk|"Μ;gOF@{\=ط72Jp7d޸RVG^<V4QI:75#B0"fam&4Ƚ܃];wann&'&h40PciFa<>(Sk*S\# ,oGN9s* e%%H7p垆]UU%61Dh(B)cz^x 6vBn@ ]g1.'#x>Ũ-V'hd lnn1=rz4ʑ%GUR*Ê5{ĉSg19A>UnRau}++t/ǹqYP@%/*=C YD mk U7Ժ Ky5nnb68sW#8׭*)ҕe#8ïoؽgazr FMۢ.'H ZZ ==f\&cn]o{'<.nۆF."8ކAu8ɝrvDam @h0ڍe\Z+됌jб7&m317IOi&ƴkѝ)C:%@o^= TI@vb]۞{9JZ܅ XJ2.-kk"i4 \ S  ]2\~ (*̉%(rVBl!/VzhVCrZ{tc nk˘߹ =70;3ɉN[i yxqImTl@)s=;0js҉0wݭ4 Ij9SƮ0pc 6n3vۇ] 035N4\-:$Y⌭EgڐYp3$41QR&)܏BUn8 SL3I~NiO6;֯ҹ%ܼuxi=cox#A/F3(I7C@O&'%iQG IH\]‰sWܩxe\4@"$UB ݸl*~*nb,&8 'O`zz 3Sޚzv{tE kINOeyHfY~ȱcn>ux-W2QQ(LہnHwYXCwc ͬ$.▻n2H̢ȴqM-rPzK!SdYjs`y6B#qr81AJϊ./ca~{wƥ+Wv f7p /6tm[Fq$l( 1s\_@1*tSv@&$׾-LNܾ` Hs6A!(9*URd 2H8:5-^|#SMjo [Vw$] J2"B;D'MIdFuZ6 *+nEQa7\s EqAI$IN~w`t hxIHim_ "tZmp_4:<o-46y E\wZo W_2֖[V}?oC v$3u6wLχ1&؊uM߃ qR yE|(JG¡ BBp1 (F}TUH0 lێ}{ۙp4&ֱn%ptmLNĽf?`E޲C݂BdbcE\YU[kuyA *lSfx,XɅGVʅ2t+@QjK)ƀh3n8>cwboc 2GQ$:&vnŞ] 5?۶Ia8] Nnnf#C$tATUUG#lnkKqy*._WqMllj)"&e ,]_:vZ0c2nүMf+DK/ ? $39q@f yҜGe#Iv\Ϝۿ|RWT &)90ϑ& JqRM::6di|4pi) &p͖nQ0wF"O,{1Qu1jN lnp8y.^V ~)%&&7_8up.0-k7Wۻ ~Z641Ss҇0DpmkPluBҭ.['7=lp}by!eYb96}0in#1=sTU(G11Mll0(+Jӹh +ױ~ ۶!˚ؿ{ϟ1Js- °zHs!u(7FKQ $IMJ R"m6s|ȑ#D?5`nn;wNL=9V@q kЛͧL+$1D'r-ĝP .agw;=gAUlgfˣ|7GfnBcuy33`/lhIv'P!ڷa VWV17x;ޅ|gSy/^Gq=`hZC9r͂T)PKznyB@1"`D 6vۀ^,ʕE,/+pn+PH@$bݞN`*ςTElvY# N̰}n WA^y8~  (M/`qVog` NR Vo/}[*=%Yf)3M)VS_l0Ɩ8J"S4 QiFgnɓ; a& \;g5BF:JDI+YTlj ID瘛à1518InfQ)AO=7 n29̆P `2:n.ҩKx%t pt'ڮ+]c3HvkhGir^¯ammBtmLML;EA@82>Sm._Mlūqs=c1Kx +,kTjQ`J , ZILOt t v܆viUx@@ G6 JxYLthdia!1DCܸy.^K/q,XFIC0J0#G*di d&x;J͌,BQVFX_>J)Z6Z =pDž+x3xe\!6%np2_v#쌞TV$kK9ac} 'NmITG(c6~FKay42=׮k8*mBgldرmNL/ɬ@+4aݞyX'w2ÉqF`/ƩVRܶ6#Mwm8sسw?ul6QJ40mbvf SS^:}^8/^> 8]81*a_1wx3!J!7VnFB$WV7{(}DdLP'PQ۱)5J~ cTXzfheےS F`o{+ట@(9[7H'zS ŏ8+ڈml.qzzCI4f08ץ*+4im 50Pr8B- `vv?~\LL1SĈYz"hN "[e0d Q#B7 1^ x[uc *\bÃ@]ӄ܃O[8N][C8EMIcmmX]]ťELuۚ0żcc[(4N|G HrQoÍ5y4dv`=Xص3s;dMa0,1K75.vj5&C2l@UX]YQ)SHdO *@ =! O7[`mSSl|gPllB3$R%n\;) c\ klOќ@A\{iW0HRlic}}זoD^ ]=ӯjZ}{hlۓ*&=s|fSt#Nt?=|f(,⢜'IZ}LY!MR-FC'vҰT0݂R&ra jIp;|t`al63iAYnGQ f"Ʉ./crr VSgNCvl1{&R!M3mCUUzD'g.q]سgfvCT`TH0pdinwM7|v9Rtgϝ((Ԭ:>}.aG\ e>" &H@6D4Е$UU &f'|1%Mڗ|AZЙElO"mřn(g$@fTTHJ*Lth7[8s4U[ jp0w-HE&,3hIx(ߕBDQضmF!^:w6nrh4}ܹ;s~ӳRasc4,PIM1vN kQ[, 8Kd޲ʪӧp nKWjH{R)&-)(A$PFPJ*GkP-If'&?`ccEUB1*zJ*8ElCD)Qp= Cb- 3pVӄ..D /`9LvҐ{v CbO4 T9[u}x?ˀ ޫH p/Dֱa T^^t$Em2y{FZMYc;-ˠ2TcBngz8Blv($氍e8 X%2Zm%b G# 贻vqy\F MS3o/_-(0 ^E! &t:h6ny.DQde?A)0ΉʥֱݝĜz͘˽e DQb\TFPG zF@ HB^o>U%QV% Z/7kBS sJzH#Iomjx2ݍ8JȼjfIzYW933 b WCa7&p+(m)x~- L[qÈGmrdES(szZ t'ۘE*"Fбfdz17*z=; ۆ$5 9օ~`7G6l纈)ύ0~*q~᠏m@R W`s 5{G#pn5151nFCs. PU%Vp \xg/\ًr&X5,-İ߇U? p)H D."=SٌWPD JʡP@jrD$ ݳP^]Z%ʪ!/JG9&'hd1'=jyuQq$& iǕ>mw U(\ ARVZe,Kv=P#'@cayXjMh$ir/ZӢZ-"BUwwqE6Iqqo-9ysYAֈD)Oݖ"nрA m-s60(rG\r IIsLr-{DCͯm"92_GVja|rĠޒsPP[G~1Sի ƙ fe#Gd a:uYakc bE9r#4s:c01 HS$:UΏw"BﯽkW. At)w -5H&9(5\ hY} FCpuUcgg;/ϗ8PW5`2bcc $5繯@cPV5'SܿwׯWͫ7q!KTHp:EkEM]+ 5;1WwJOZSAH\ -Rkc ޝu P)t4!C;I=8M2TutF_I ( ӵHM54ˆP@=]۠h)YBTB4]rml4y>_~q*` PIxQg:*;hJ(*QWH-JõEr+rԧ>_ "ඳ`2 iD(;;{(JK'hJy*r"R`zr߾M <%~Mb\7ooͷ[bh0*CRh6zx5V H)UQaP 76h8o]N4!ֈ,`㬧PBE%Cts4:eJ/cd2f;8>BV(S3H3 v])V%rm֎iuLt4 .:wƜG\;<|<*2hߗer$uhu+$+7뱟"Z&#oS9k4,_y핟(_ȣ}Y~^YatFJ=,sjIӓ&`,#)kMᛎAV[ cepq"ECl*$ϔk[Z,9\F>O[u@ 61[Wa溯I)dwnx ܼYe `@):PnD,Ʋ g-,ýcB\b0a80M[[w0Y[NRt]KӖM"^P:kJU е%LאB˫RijOtET[Ġ@FW ~Yd=hhXXӠKtM 6p2YtE6hvȳ۸p<<#C U!]&xCCw5!MY+C]!&Z!+%Ai h}5]תTx6} _O~S 9E #y=C"Ե)j$bM]h,S%P?UW" F_sWF ϡXiREB.l"gS(px|;[X[9M$ʱ=v[zTOA\`F*ȽgYE_RrnE;,8=.)^tvVۘǩJ/_~'_|>0p3I|AYH 3)Gc$Ev(9v`>!2#'ځJc0^@"U3XB./a](D ??4@E?rIZ`PRf88Y #llm`ss7orQb{kӟ_Ux5dl JI;V M]B){o>HDalC+m8\y-G#di8 2@ePsc(MPP d3 uzHZ.qvw L)eaA6mk4u.aY}wuI&Sba8)vTOgt z/}ٺs n!] uE{=WTk8,H.m3g+р Ng(9:Ά4h{1(cmbvr]a}-X8ܿ!M z2{8t"OG(+`41qۛ*rY}ܾqmRsQ )&PZ8|" !vY$Zh9 acWcw6`wkΟ# Te|qp|1(rQġ'wf`ݻXߘ}{phR rZW92ٵP؁\YKeY?vwp}W^i^* ЖqqPG2 G~3D:K`e% YpxxŢ "E\]G:}-rI1Q͑_@,qm<|,Μ9T|I5`ooƁx24i ʧ.8,h|k4N%)\| p27Gy%.vb}Zr_=t;w.XYyecM5$MY0DC#0"yj5kXK[mU, oNSE,1`wwr+Woas}db6qY^jِ50Le,S4m9G9;B}|&;P$!1j>Dž3888zH0qtt):%V)iv-2R#҄c`mXQKضMֽ0(J|*cuT?28T4c/ؑoQ+\ymܺ}?!Z- G{y*E v7 c{ A1P15ZM*X߼}׮]/Oi=$ Z?W]) Pm; ;Dž `wgwF~x4E!O34E(T!IzNA79|L.ԑʂ$2Q7DN"p{QG;_E Egjr Gc erP Fp 9x- (c1:-D#!mH,^dp M`o\} dQE'lg`rDXB;u~Ca2Y>1JKg GTjow!Ve>YcM^}Mg |O=~hv+K*JhAgl;k-v Bq?~꥽=UŋWڍ:I?usJ+!(\,P5QbiB2Wz)sTMMxG>15ddpr<)n`0C?O`ՙ 16 HT_7LMUY098<866qMq1@4X̗(gSyTkX!G!u(TU  +A;C9ضDYUXP ]aks6Q-u]׵岤RPd2cÓ{/>'DžccsE^b24ifTf9ItAؿ>W鿂Wew+W>^ Ҽ 5&"Kԍ0 yU7"'-iv%LWT#C%M( Ry G(F]WXKh=<'ދ'~alln`<XrTpxxiq]L4%fYR j\oc;kX,p}U㑇}cGޮn~1pP␣ҸVH*N >rX3,W猵,u`vmiPՕLwkWU+/^L/]tqR0b}46|qxbd_.oV~{:Em YdEhl(4K`GGPdk hR `>\xN0_.%`uZf ECXv4`8Bu "-| d܆P\RY#ĝڶ 1'Ǚ!I ZB} g*6C; kK7MZY^"1de@zth[㺺UW_ʪx];iYbP8}e[חt h #\y-}cxo~`MU`|6l>`0d堔g М%DH0LĞ^VLϺ <'!I| f8*4MhbZT h.On#ܻG.6:r9CU/gp]^my(1L3Й][Ú4vYҪOmCۥ˲h4d<)Zmij:nZlmI3; 4m59)qL7aAӑFkȴDk eãcܽ}e]}?|1)JQRX7 itc> 榯Xy[(_z]aއw1OfB|tpf2Y:J-9t L?h9f 8z\ta}Ԡ(sV+h{FŁŲB: Gk?6vΑy/ZW@۶8>9h2A(IjX&ԁҽSH$:Fo^PA8^K=;0E^ K4y c %EQ3d𝵰]G|LK06csmkP,xrVuD! xspsrΝP MnC`*LIoT53j=99 /#CMZg?'x;;(>TUbŐ*Yׇ0JT+-:XQDOU,1Npn޽ܹ}?n˹_ӂ6664M։VJawgD`>_b N&tQt uJ|Ղ.(r3(xP$%AdžBQE.*p%oY7b{=v>%;I)?%ɈgEk% -b>Gׯ`4`}mh5Lgu-LK~S4e6Fy=r[قsty@68<<%670PcMO$g` (O;k53Zlh͍u{dNSҸֵ8:>d2“O=g?a<pC Gs 6*pG;8R `폵``6D u!oɗUbcܹ{lͫWz|.pmpQ~;4˷Vfw{Oo"2]rr.a4F1^}U|p9\&|ZdibXxA %C][~_`1J6'\;ex<Uy4R`N߯eEhv?뫀 f Ukkx|,'A@Żw+p[wwyハ=<qXzATbrP+x 8]^]2Ns΃|=S8&tl-ښ@Ԙ/89;߿oE,˟wϿ ZRӀ߼V^976Aj+M uJ=?MIx~g?1L0-(]p`:"A!—@𱙇;fg $~{-4]U4aqEAKB낣;*䫛 U6,o^R [HRZ&6gxCX[fJͻ|iuLOΝ;udYh4!;jsQ*#A$\')Ypi@T%I lobog'qέuE΍If%޹+Wo^ yrz$;^qࡎEA$#A% O@3K0j]סkZt]I贮\1=b>PV?|@"%(+_ȋ_#=7z;;;X q[jܼsXLOc:44vi"ŰttlaMB9a1k,G<ÉkEZybm2G0=XG8. mc~{{Xp0qΆC'עOѽp8 uyb8sX]E*='r*~C@4_R],vMmUr4q*i˦~yծW^jgA3gֶRt1EU.u$hEsuH <=vdYeo /0I@T':4ճ4 OkH߫PBۥ,YE+E!(NuH yCbωl6ƣuubsgçG>t,nܾ>0lס,moa<3 F ,#s6 2z1 NZ ŜqcvҔa<籾,M}.hV+s]yJON_ ]Z>C*λsp=D߰}mGFߵ-MQU ij޽r&ӿo {_lkvg?N:MC*~<L :0ZK_ <_%Ds+oMSc>b}4B6 vI$V 4RB3@<HUOWPP~E!\-f (,"6>d3%Ng8sf|3%= dGΟǝ{HaEU &i10r+\a4kTU"/28r<’0MRll`q=$nܹ7`4yx]4>NzhɓQ"WH,жD"tiZ,%NOx*IG> @^,-\ dyC㏁k׮qy4$/ljOMPd@Ac w6, Q1+Iſ=?˥ aZ5 14 b=rg& ;C_ \~*>`2QKsZ"- R׹qepH=p ZUu|t|[}G3L%77Wer5MVNt U]סuŋɥKo[1SHrgk;:Ӣk:t-y,IQbAs0]{WO7d<ta1N1 PdMI4Ԁ(O]f* oiJpciin eU( *:v=? ҥ+o= ~ ٚ4p8.q|"BE(] l^b>2}} ;X_ڄ >M|vęp"\W.,LA1bm+:E\Ԧ{E10PxzC. Ɍ[C)KooUDU-Q.X.+MCmgv쌅\m: S+@ҥKNN}vmreU;:xʛKDɘl:h4F27 Dv~jcHrEP^Hn ^+o{w,95aŠ-r@"rh|0;8>gϞ}xC8{WQiYLJ&"B-i~%%eg-4hg`k{L ȳ,`3p60S2=eV=J&Ox:3讗=e=zM;> +]S 䞮9~6r9r efcJxt8Xk@y(n{g{R~j1&њ;Ӣ+Fuon"IRd,)L7_K_Z 9cO9̙3Xh2B1g {S?^_/-^ŹȨ9`=GsQzJ)R[JM&hou̙]"DZѥɄpiZTurYa,T5Iyh&qױ6xD_zwژD4p϶<>ϑ{)Cst`K/=GUEnvwv|C _4eY,IZ9oZ)PQӄeY|_{s/~ t >n]ۿv'rT%PZ)X2 I2$ ,Fݘqs U}/2>go8Z1du-cL&CiRNaR /:|XlU0^5û9Ȼ/xQjMoMb8ȉ܋ZNjw+)%M֐994|/_k7kQhZ` -iQU5j6nКE`TX_cᇰ6dh@^'t_qP% \GD.J$wCs%=q^>|uS#2AuhuUR\*n: |Kzr(ЀJ&Ӱo8y /`=i }_~w곣5J)}`l٢Ţ\[.>ԇѧ?_6kちdeUhzhBEI"D7 )D#O*0|ugثVMpP]2(ry{B}ZKMTU1ȇG>'>>cʲV) 'wZZΐ~&h8& ɠiTѹ UޒHsׅɿW|-]}a"I=>V7F[ㅲ{"@ƧU_?r )J7 AYю_U%1yrX;c`YtRo};g71]JYmbZŠz7ͷ~߿L8BŮ`,W= r`@3rVJHoP6!%|&/`eIijLg  5z|}e#/aKMӠi:r tm@`9, Mvc'[>BvVοoǁM4 yw=͐Zz\9y~TC0~+ϳauԞ+ bpږuYqߐcf< erFmj4u6˪N_G0c%&W{쵒:8=>u[ ȅJ, bLPèQ8<6|ڨVl0 _ __/WPoIM0;PڐW* XI*.!J!3Eh*AR[u1(6Yd`,ˉ)QT% "m8\a`oyUqCՋC*$|םP5.J3O<_ >Tv;Iyn'<23CS. , %37 ϧ5U*at cto]yg+povppppmwwEzvPNK5R4kP ͚ k\g;wPDŔ^3O:3 9Rvz0(CBZpZM8FݞWW%eISUD27E8Jn|K^mmZ }]=o\:\J"Q:0C:w\=>nDHKx]עaE傘]RQ9h丈T`1]kңk/~i}S"鋵ۻM8M(6tʡlČ+Qi8백9|#3gc0piP"CV<9qʓa5M&%&忟eU36K'ۭH3m &A/G1rSd  w1K9g=`L>ӝrD]n^YfnF?rf=~$#EQj ?z{^| F$ߞuc-j]ײ.?vJʦ , ,%AבT(};8v6Y1w_~8.#vpƹ.[7s=/R< MqugcgXSJ(c$p <S<b?8<:t:E~Ru;u r!4,60<6 .vViקrKyG'M It(d%ޕ+GG(,Q6tA|m=>E7`q [!{~Տ Z_" OoŻ}g|]('a^>% s,˒MbBǡ Fs}$#7>cL|k/yacOvAԋEPk4I0 8ӡ,Ɔ6>)nܸ|YuCz$Kଂ Ly vEgHzB4.9*=ޑΜ9\zՎEQ,g-1XIJ۝P 4rb0{w?Z`E1JݴB!u :EE4$iCIsw_Jd^'Fm%bP˶jLpfTDG>ZKt^c`4K}Jk5+sx(@߹xlw8{}o]=:DwM9*uUJueY*+:]=G ŐDY5B5`0.XC,,h֔Ugo_`9˗/:9w`8Ҵwâ*1P`@iэF#+xG߃^ b~MR!.K3A-.Xt>wVJIqߔ 5vrn3a dzjtZ!T5|D"zkF?,9~NC11Kߍ w1CϱŽ i"diiW \*KuK}1aևp.Ph!.po3Љ(@ !r)5Gr"SuXJ7UWk9 &4) S8RԸӴ &,B誔FuM(#ߖ k  & h|:VڄGuGLrZy7;KȌt(m60[ip8rFfE| ̵K~f:TM.|Lv.`?‹Wg7zg-Bi3$@ ZfUa/KTUx|€h'Fy>z: ϱO%T_0V8DY589yղtUYeY:) ?<|s̙7 U F)ŸeCQu4 MhoΟ|IȲGçԛ'WqMEBH!vzLQ G1)T41JN'} \qFG̽Psl,N2$:N(=*Dl*]E:g9{e2R( I:zRϏrwO?:9;ˁG9D'?sDa9:DYx\w]0~R׷]rA65U L I)n9е Bѧ]"5h'mkT ML=SYn뮬;<kݿm̙?>(pZd4Jе t@<0 GWa$ *A$EQ y/h\?"R$=q ($a95SߤsHǘ߽k0|$| @XL }F\/b2NO,e yCy( >y\$1\"cOIQȲR%AvL1Lv(<|Ԛh'&]2$-(!ߕ>AI*.aw8D\k#omKa}M_bLe/Nw;4U:Up, Yjt#'k\~%<ēxGʫ(΄ҝ +Ai!qb>ňZQWR>D L`i^K$%Sl;'6RxY(햢D\'ao*K1u2zۃvXһqkl?>iS.l<2|2&rN]^C:[1pKjÚ<$;uB 냕|o,)UA8'}۶EYHW:ɽ* ~cq֫:?kkYEd>Q9m8RIL98]klĵޝ78OO>OW'Li0WCȊuUbdh,J*n, :L\^}U|gkQGj/:Ty!䠜 V9DGyrz$D@kiyІ$l>1~QYh:yx֗$:r0hv^N?'iRlKtcd>;Xg/qS'Ș~@'! $%RrLS->ʇ> /3ƵW,j_)?OX)H3R| F77Mvu[] $CZ sap6".a]]@J+;ۀ[? YJmLn (yRhq5~ ?t&@תriGQmk|e6[e#Ahima$y 0sbYx˯|<,wv \iV`4b*a99 Aj:-Pg}.9 R퍽c|>/Q*hΝo#L7If cp~ct})~W!N؀·9/ }OQ-! %gNcQ% }4)% }Q겁5ID]mעn*4MRU߻qo88 ; Ғ m0,HT GN9g |pxpbf4A70p# RVʃ@1&n]W)YXm2b10#oH}G_XՆ!AZr{_{?3&Rƥw]C\iU2pv'c{e@<^oDus%hIw“B;yJ2B²\b>+^?ۥ&GfCϟP. eU NO sU]'4uONf7i_ۍ{.{W}Y&\/˲D6TIS&jrkx>_ 9"DCpƠj,wˑ@ ed*jQʮ8&uz FnzG.0“x\J^H-q ! Fђ9;CmBqHԳ\'H`d60,\ɹ107"cZi5Vʧ /QDd\[0a!] ވ1XgpqDhE?[ukҔAp]c3&鬻{m5okQb~sbQ8gsiqi x@oU oc} iqy{jn&|7THu~ (7!vwv՜; Á` ם @&%^xܜ5;|n~γd7oKyFQmimB2֓xLpc tcrhJeqP7woƆuHx/.]tf!I4!ߢ j;yAtac4 9Cܹq _y+xCki͉N I 5LfH j@wb/ Qy"/nw i}()-8] az& VT;RG`W1$=;*oC.D @[uN% $Chה& {6|^|d+d*PtGoBЫt }P MZrR@ǥxJ!VwN*Oll&t 9˘u]Um]@ڥK85C@?8g}DbC֘/FHa']uI}~'^{>am}9di@e$)s5M!^5,C1wO@kM Ou-yEJ򭵘LpW믽hȹ9w)nVN:<`e# sCκ14rzI24t]iy$ۆiMMwq-6#;JDol7l=JOR]ŀsxsg rt}Ο OtJ 鍠(v(0O)Ǩ ιt7aʥDs]kIIHѧEe]pZuʺ?Iߕ޹=;% kOQ_"]S>rc C(ϞwoKE}ZTY$Y6nU5L3F:m0ڶMJi\oEDg:~d^@SB"#zrvXy.{v)) Hq8`NUf<':2qor;܎0<`8"rt] 0ψA"SGK8R1(\+ޜ)D"-sƖ&;tƘŲ|.l6two~ zZ/K|dE*IرO!tb8p<p+4\k'Qۮ(XPA0n?=VGC'׵jΑBL> qҥU~;mvڛqz!ps r/Ӹg{H2ZI%>nFAA#s cZM`/@pn. i @.\hM'~N;:x؋/MU?jBJ:I Bp~0E3W~W“O~eYA^j!ʈa)&=oQKO'ᶱڮ BIvEg-:?2F#fFm' P9_2~Wy#'ynB<&:{8itl86'J yr0}-X[ @nqT6G8:>wpL$Ҍ&6| !X BڇGL sg[2n``a`Q k@PֵmM7F@nݺ׋,`ϪJ: S r 4bZyc>CAᙏ>[7oA%؀ !5XDTL=FA:BԀU5O&kR㿇ty8xOkJTd1\$upsH+Za1(Fs@0P+Ei !F ƣ14 p|xeG}rNFuwga s9'"p `g;1›(%+Zit񩯀]۹mUYo;Ku]W"۵tpEb^2GOGEM;Fۮ~g!w446 i88-WG{@ AIW~K/ < K Q~.F-$ a8Qk[Q+JYO5C9N&e|21w'0g5H N1q5\zhIrqc'd`3#Qķ eO9gm3~ (o),Mkl g̛'''G6Q7G@ܞ^?oy΂NJY@d՞ as9VmZ!KܼqǠ1,D@`[/X@q*L e >BJ`_niT^ h7^ U94t o$g" .mOp1hC낥5 -Zhopγ. 쌏*\.`HzM', k=]>D JtJ"Ҁ^*BSA|6 yĩhBYu㪶Mϼ< ٰ˟bo̰ ~ȴNg,4AD:sa7_<1=91s89RH&$+ Yo-o~׀1Q_e/13Ģ3\x Qx&\"cEK9?$^JkAG.12Rgi  h)8$ixdH!4DIDATݿ# :!;>]iW zg'Z^Ҭ#`U8T*0&VP_** 5ִm,r6|NwU/z'= r9hЕQ|TJxdYZ.1—el#~_Ѷ-"0)t$ιsghn"㣰"v肈13QNj)y#P_G9ra»p`%Ǖ)1rY!1 Ě1$bl *Ѱgsr @e6۩T('6nQoa)eɩ)* Wu\b w&.ݹs>OxD|2^|o`X|W&ŕiyJ&):cX/d} W| [[x}tAQ:Nz-Fs\ȾdX H~1QB\O&FxGF%`:mϒ6xnR~w `Њ@ײ9ws, =YFdd u1ãc,%1H4q8<$ :_w Bm-dr7Ygy^g f#7q1njUW*?} wW?n/oY0Нipyi *OZc<6Г8.kE΋HjRsI#IE'ӆ?3y87N2^*,9?EE"֑VJ"(EB:7he1=3D|>6>#BY 1AdXftBچ% <u"I/QغiQKdyM9_o|vKĎ"5P.,hS S;R #Lװ͍M FCXX,pt{w}LO+i$K)&o]\|/hHK=C_ҩ/NSyzi\iyR)jm:4?y㭟Q{t߃J R{{ܸ}P|nc҉1JHo Q4|q}XW$I0Y[^|}Yom+Wi.NӋ{tyӿ+w iK#/ȑI,9( TU ?g!}fk)A 3=0x"ݤ:*!!@&PCU8>>|>r@[׼{#MUr$NYI:ox^q/Y 4Pzx #8Y+17@# [ApkQءu]u_| Je_=ps4it yԏ1^w._x.\@SW( v,鸔#3zBx@-0ioᨥ.f"c-eOٕs^@j᪎EClg`[upڪA bkw[GN\8<<ݻp]߿lFuJДӯ)B ܎DJGMϙJk )Fϐ>GF/W9 uY3 b)KLDcPڵ*)RiU :ImpǧG+!;ft prRsE)7ɅygD*ެ9*4XS`avh~G?]Ot]-Hw:QgZ0i2Of(_7nɧcyruCN%K=.7 ;ܹf :x[P4=ePd=D'XEd'R/1f䄈%U N@b,ϑ%4Z#5brQbY-if8iiB`iKMfB_[D=Wzk&MC tE%qCHLC:v>Լ%7R@ۅaDXC]7Gmxu˛~| Nt_&KB۶]-B9C!>qdޘ+SXTjSd1:vv/\@uX`1_ųă9BΩJ9`|X\|:_8$h";/PҢ"> =EG{LbWc::gߒx;qbwYd㿬N:ED __i R#H3h8b>lml6ʺr4h_5Ruh4r!P~Q;ޑ 2vT|Ìxb0,%)4Ge{Βg]7hnUyyoTZ!Mxq Rxn҄n $6'EusଌL3J@kfMjۮ5D4t"%I/w|ZK0yst%JksEݲ-̾Rݷ0.\ؾp/*.yn4IRrtkďgPc,ihfY$ciY :DöA SjCNLG!z*am"'!I"!xpA>e8Im]KCt$R5=gZ=C>XgHܽ(Bޏvm EXca::s(8X)Zb^Tfv6o\y_Awo@ptPd98vQN)@YhpoY9U)Zp"R}_'rki Y1Kx}dEx Mlk K:Mۄ+!(U RPNqi,dZcڮCYdpT3KGewC3aI98n JߎZ*`XÃǯ]{6c`߲_`L,;Zk4!5YSD~R rr/MT#a=;ADJ5E1@^(En#;FEH8DF`[6b?Zt@iKԩxe4(&ZoW譀WK2Wd_R*LS8.}R9v<_9y!F6`ORe08yfEոw$EQXkmXrQ/}KaxG]t~'r;C)5u^JJ K]vJ[Of+^ܬEҔ[DO_Z蚕xzcd糑؊B7;Ct;0Q?g8NE&LwƉ$|D \'QcG)8!ul8oy bȯUZ?9&'t%$iS_c{< @_ñjR.!I9h r 4ЁY|]`Y``8 H<oj& 52ΗD mjMuqJ<8i *-j9Yz A\t Z+A?*+V^Y$}8(X1.N>i($X5I-| 8H*ѱԝae>>5|Auj\.'/^rm5~`mm@Mc*zyq ?DB~Ee9 (' yGk K 4lZiJI c<u1?TZC%8 A; t/ ڶKଊf H=^_@Dy#I~'[2U Iܹg#L:+r֚=g,Щ Jwfp.,|?F4Ep:GFA@SHF[(5M/}Ksܗ>u_!⡣ȎǓ?$9in\FeZOݕE%$&< u?6S EM7ZM&i* D [>W~G arڰЙ &.2+$a9VY ?b'tq)DW]'2T5)eBÑNL %8O] ato*D"!733&9qF/7n/~;ǏP\rwo @Uռ,9ԛv*,nsL RqDVTRGy~0?=ڏz6{0@*# U4 +"x [e5% ݿaqOvcsD~qʫ}ʿC2ABU.x48Iy{iXV1~? -K'E?.8%Uhm^vmx7AE)@]`'5,'; ]mM :y X|*8O< >Fᜑxƅ!A7zy8Dzy)gG>䃊BrHv -;/]"_ĉ LUƙ(qb'G n\pS*HrZE2A0#0`Uk`UFӃlZ!It{i2"-0NPoA!\IG4lsƊ<;hbNh*}}G:.%J[$ i:8|5' EuN+)T9zџ>S˗%=)x睩*Q[0irY1|-ːL&ǡ8ʿs[F]UP>Sm4(Z vp̀ 6a+^Ͻ ,=y|⇨s?h t{/jibP `@&<)(N$BxjSL q.[^D*~gM:c+ VGW+t"Y-R=`Red'hΪa.4 JJNJ|>}rN"961ܠ'{0QGG峽KJ@zwA_QL-Ïw1 됦iR5?}^z Qcz4r.|Xslzc$nA8^40U08|!w0uu8s&b( }5VJ:(Z eK:> k,uJuJgu*poÿ˿K/$w{W+86?t4|\qD]"Ggr ȒYA' ؀I;D]1/T s*nBBc_rt1 Ļ) Hm D5"|*Fs=(EMtG\'׋d=q9U"9W93xv`sd^({|2\t?yS*щVg:g:wkyA# ){¡x? Fvd}c:Bx1ƴ[ J{ym Å)DX}MeGJr2"H]@ʑ <9ɹH>_Y!ob sؤK"3Y-ٟ[ⓗU,$Id:/W8(WJu /@{~_ïOp28%I&@gȳ Y!Mlx" R3Aٙ$*C ثCE0.)La@N- vC9ջ ; QIbA>H4AD&Si ,Df\5'TYDF[j$spZ߀Yח QZ89o}WڝW>I&" Oe,MRXR1O|r͕ $ pښ"%4b#@hA'hsp1"%Zw&9}?ENG^ɲЙU)_yDc"t ]LH `#T^|aZucE-|w򽇒#)tfY2YdJ'lږO޹q?/{{{N=# /_x~wxgum&!jf9k4GLx gHpܪ:ѽW&tL%%A}zJ;fXdfyc=k\9q8*igOx 89?Zj;x7|4n+ڍ!O>.: :r ;sg7ljqBUc} V`dE'/K'ӟDn.]pi'"˭6! Bx2='5tN~|b",d~qC\9. P!a|`/\ҋjl  HC? Ȅχzf4_EDija.Xԡ:BF:C/m~tV+eTd 鴝/K/2O~|GQ0?/ǍJ4 H3if{"$t< 8(wÇ+xsRJE'` 佀P] Hp D34= &RjE^ A+ hEtYYN;׮/݄# ~\}# $بH}먴#"(UeF\;<8/o}_wy'Gr#Rzύ|j y=3Y&D*e$$A'ITL ^X4hG+o3."s>b8RCH"IXky<3ՏD:(- (a#9Pk saN;Aҽ@ ix]x(g!"BfMAB,Kk ~Z;Trlrg0ŽҥeN0]J^d-`|f,(tInGkGGP/ .1 @SJ0m֢w?~YŋӋ/^xƿt÷h$׻;(PwNHDeY@Y ?* ݀J\_l^3" a{0DE=#Uݠ{0 c]hž{·C &8wNB 8LFb¯"HEvE ;C-tWSw8*hXNRբ˓3x }=ߪ8rſyQKp@T, N?-SY\ie,DC%ڹؤ|ȵH 8_`R -=z. ( @g~i.J#b 1KOp," W 9J6*My`]'@:>$IDi*Lrܾg5_㻩 u /4?1 a~Xͅx`HHq@a L=^DO$c($oGڗ;$@{ ąNAtB ]Ɲ8)':Ǣ,Ks&k":J0l6奓~~Ï؏eϟ7/}|7Eh(j{{hEQӔp  ⩻&OI;}!IAFa }bds 'IUb/A̧3TUl7nS^onrC"7&:nPi҄`-F.2C@0809~\cvE|h )Cz=&EwP ww#6rPpt0j PCf|,xr>xtń$go6<9M XE꼜?to7A?<;(?r\8s,4ӁuBuBR! ̡"HR`P[+%##캸;І'!;R%9~̮lj39Ύ`/88p4ԱqbMȒ$sr*~Tr}~,;::WZk=~G; g%kmΤ q/52ʛ R)-{]8$f>}$Bht@AQ ")A}dr@aBPhI*G74FE z ``K\a`1g[A)eZi|j^^_=<3PC|o; ȿG~oԍR3&4\4Oz`T 74@.Mk HmP f'% " =Q8T^ԗgSYN@Ej·\[Yk :7§(_+㵋䥂0 PHmp6Yaf;BpƖ٠dmV\YђA'녩hwe1#Mmy#mnݵ8Ipt:<-q#/913=ݢ Ed*ǃ i-8pǧ.L]:/93?gDH4CgF:>PFa7#']mn?~[n'B P{R3#a䦇]^s-(D$1CK1oi_^^zcyj9H_y]< sx8iΞ=Z8 j퓳=dGLS *rPD`غ0 iYc|j'?QPc\B>2 y^/eYvwF}ն.`$C/6==F)e `N@+Qi-2M6IA`mF.;e!z0m@^$Tc-y$ ADBpZDJ"gN笩-|߿nGG-С&wl߮j­6 w:EUJ?0x,<1hTQ]%> ͈WWU&3 §J V ɃA@ DH `dK(D$tI! 6P Ă&I,hW@[fSd?Mq EBGErɸhIENDB`OSCAR-code-v1.3.1/oscar/icons/logo-lg.png000066400000000000000000000331071417327530600177650ustar00rootroot00000000000000PNG  IHDRZρgAMA a cHRMz%u0`:o_FbKGD pHYs  5IIDATxy|Td,d@@"(hq)JEl[k݊Zm~ +j]֊K-.TVDE(BL@L&YLe x=;u "&&&`1&&] Sp&&:b DGL)81gb#LLt31Sp&&:b DGLM}Fdc4O;p>\SJ]| e?& St"$Ās!tÈV#H[EH4QSp2ZѕR@vWρ5?& ` .9 YiH]+= :6cR+x` N9W!ѕ1Q?;H+3r)\LE߱׉? 9f 芥@tF#=0MDg-> ?]Spm~ ?rz"EHDwYLIBz(z]FW6!YFW  h u7GiH "j^sgÌ!¢X1a n 縬4%bE@@ Bb6\͡pDX-,X['fwRU7;Ҽ"D%ܧ=4*/Cn{\Pۊe q4usX-hO"¢__8-E`OfWR@D:}lPhh+ =a m5JK|FOwಁh=*޼lSh3^g*yiT7CN$ZH-$׎TИeCݘbCwN>GyFo;)D73N4݆4v5Spntw\K,Lztcu7酜H{ꥆE U?8:8+'mTOཧo!R~~Ui|SEL΀,{nN7NtÁ׍"ߠ<[;gT2tZݛl Tk_T|bS)Pý!M ^>AKjQfz#S 2ܯ\Yـ1bv'r1)FnL3{3Htyz_H:W=U[u4I]M83?g)ܯK.* ~vDmȶ[{Fn53澙6Ir-C}y.j.f֢AZS{83NPve^^AT -~j*z֐ #ۇy.tZ)A Zry6ShMǷ CH;jHmB+LB~Fy-oB1h^R=?_3Z%51{cIt״,\e#W\-tӄ\s O1&,4HAF]<=_MeT>chf.Vk-""- uB?JX_عZӪ'/ǜ8IYw˨9s.FZ:yucMwQ_[M0b` 0VB܋Z|LUHY&j%Z .kιu!0ߚnrZ nVw܂L3eBq=r,L M jS3*@_6]faZN7Bket1]݌X3W0WW1',]UGsZzιgh<VTZT&\g㲚Β1j * PB!pׄkF*dlk͈ujc-[{ۤFe5I- 2BpEЭTKpZvZMg s2yrk%k e1CL3V0%݋SKp,ehil3ˤ5KgaF'[zjGz6>)d PCpkLΤyYLsrI4-[76Œ.1aF|Md5,G/-[wȜ] /5M!毭zܼ0 t5C-'Hh_y*H!6yxAbu'e/䪳.uo.e/4!'E)#6JLt2ހo]v1'EF:(q߲\ZU3meGIzVhTtXa];`=67-w)smZ~<"Цջ<.k[1F>泤%e_nעUE Ӓk+[)׵FMSPܿQ.eDsVTerjn8'!j|Z.\5BQߦJ'gsǵҪɴ;DIJЙR;>a4N{$xR M[m*5DK -L9YvrmTȓY OJ{o|W|S'b [#硌-l_ O3N7 ?c&D=ќѬRv|j%̈ Oxr=1\hVX#[D&j5/0(+֬S/݉dmFS%&?Hr/ٮ/DKM{o w\ v*~ *nn@X w1Z"hhMiu"ȉYn«x/3 佯h:rڻrۢ-tHc3Z=Jxiv::"^Z&RhAɼ\k[4dû|( Z}N\a}۪2ܲtoR!ۿ5=.k\}"ox'[{AQБ=w{m›ح )dbxZ(m*\p5FEvoX !/\oڈ0wCvqWY2gՙy|EJ(>ئD%rVqD}rxO9v]W (0MY502ohiV;KJj:g[l}[sck+[(5M!.x"_9+*+fJMͧmh㑖v'2!f֙ W+k,'ӫ-@:At 8O>oj>Z$h$00#㖔ԴK$@Vz#=Ι:o')qYYu`毭P梍U*K=SbPIhcUd|qY+szulW:cJO[W+OzF;.@ WoMSE$sJQ3Gz-%֍Ix/gE;˰Ow]# 詒#SXu`xT'/+I=*T{z~! e8w\8j/F['LU#ŸILa۶mw}[ΰ:̚5 &|yBS'ʕ+),,dXBk뺿@ e!_ R`ԩ̚5>g}V귰~zN;4oYg3<{9-[Ɣ)S袋?:u*=ӟ~%KԾ'r.e5\zhAo7p֬Y\QٱcÇ7M  1qD|InV\ԩSz+w}7^x!sK/%'闿%OlV\|I, W\qEcl6fI_^]ʄU.blܯro&ƍyɓV>b~Ǎ;jQ?~yW5jTǍ?@ @0n.LQپ};W_}uSK>ns'&"8ݼ̯,ZfY`{?߮^zb!y1 ٳ/c8 l67' [opUWuJ^'zb";*+aQVA+WFg„ ֪^;v0f̘_ٶm~ z*HVn߾}ѣŷO>l6vH.tY5kL6Wx8ihhSUj(-[Э[7w}UUGNxÁ׫(l۱lɎĠ>nU2''*.u~+R$xL6mݻwjRWWZ]WZE~~yKNȑ#}AT??s`RswҊkɜH%;C޽[ѭ[7DQ1Ź`uVz?Gn_@Rِhdddj^{5N'cǎfE. jH2*Q5vw* <8`0H(ފ]vq 7}|=BY'|P(øP?\{ի _«-Xdܨ{th` Kb-[ѣG-7q477'puבaKOvWR+wݺѠ_zM9+:/NImڄCQ.9ٜc90ZVP(Ij}!W_r =&LLۍd\Dݎ[|εP`/lݶqcGhX )JoA(="FZ'EO*Qjj8p ˖- yBetn(`v;~ ۷KGCnAmJÑ#NȁIp@k$W;jkGvYGX,l[g7vŸeK?v;g:d(=r @a!M|'kײmh]v\X^*>ѬA]6;߇A X.dkqłj, =z#e/ߵf޽fU}aTCz[ӧ_-^/@߼<,jE  FѣϥsEl6HKXV6n7Iח;6"}ydn?ah>ۦF!njlݲ1ZQل[z:Fg^KSN|طOAiՊpP}@D{ gewl,~1fX:d#FH{K, `mSر#ߣ (ۻ} Ǔ''W9/OڦQ[_~ǯ`סCzntrr a=Zo7敕+ٰs' Y.($ e(bݾc`2uOq~ߚqYCXqEdgS q43:(~Gj^^2]18)j薕@(ߐ<~ʩVԾS:ter˃B"5 J߫B(:<ma^#8q"#G-JKaRw8v\CC?|Al%$ 䁏,_Vk\!vӧOhI3!jqo /Z=l>jy. %mشiׯ$y XN]tFMƓ,3ree%>f \N HYSyj6kU-/333owdl"vS}'|g|/n\}pjVX< Bj0U]?~U#fHܹs -Fٹsȥ^/~ YPJZZMMMQM>S֬YÖ-[4>HPc^{-V`>HPCpӺyvz222v577spBMۓѣ]7x:,Xm6@ }QƎǣ݆) DpP>_(C9[輙JA裏l|͚,vɒ%qKݎ덚.oժU12 QWm,\#Y!F;JH"jΫZΨTs\+}]rsoɫ˫Wg?ٳg%:+Wf)TnO6lXjZTвrf+**ݿ|r$KFFrcn۶&nMCzzx4-B,QII w]V*UTP:nZTloj˙5km\z5}W^^? dΝ;ev7|E]{&u' B?vd{'%jw^Mrv͛7;4j}!y(XM=U16mtjޕ_ͭ[,>޽{5lŋv;t5vhС:_H}vفܹS=ظq# i7oj(8/pVH\'GţٲeK{hI]]pPm޼kw%^jkk8p`jZv'N-;wS!ݞF$f- @($cVkqZ}}=ǎY`X:8n Gԭlîgef߮Vh,PF2VwpQ7s%yf233u~dҹ}9r.u'4440s̖|۠da)۩V󦍐۽{w6lؠʀh;v Z;wrgrCq1\ٕM0 C$ZgF=کUyzT5Z9z >\ Ц޽[IS[[K n?|N9] K/.SYk#-2=ک4{E#+'bm۶1{A1l)?^z1d]?p@ufĪUtm~`0HYY\pAwj:O$ZOFD(+^3p6 ,tƽ!9a:,ݮĽGdeeaZK/[Wbz;A{-H|pZI&,|2d.ZSPP_|H!]\s?S|jwƌfe c_()HCZZ0Ӄ'XfM˃'&Mbر긶+VO? rM71i$ @YqynҽkRKxm>3x<74hcƌ駟nӵ~aÆt:ٷoeees@/0`wy'O?iH,%%%- 8)S{* G%&x6! TAJ6"UV2m4u]x1VK.6>O [4?ogiY`Z8+v_f3ŖRb;\nt@*˓k+t$1o9{f!5we ?jnނ{^Qvا6&S{gSt/e\-Huݏ@a,MR59` `j(ZQ[G<]"oi59^N0Bp y,uO -b4It֖k|Jպ{.EeOGu]u{D01$..ApHL Ķ\\\'zI9\sC,+*ϭ?҄i?qpyCVyH۵F7,F m7uqaSt}j%l EHΐ+ K'M_7UUr3IqA`@?C&7юMo5Zb;!,[T\-0IwBO*?R(oz[U*터lRIp n F=nu03fd4_.G[5'  ]mYmkÜ6H ju#E`bH7r<!(ݚG w,Ͷ# "]HUA  ^{_vs2w܍!Qywb|':-nnĴv7t,j=cw?6mɒꂃtFV"(b{n{oYRӐ 9扼vуAp )(8os߱lS?lv3%B5Ʊ vymOfP@r$OR'໠όh,Zw/O~z ]UxƠ7͹«UXDBϬ]j'WFWHxjZ cc N@)HXsuC:]ulU VMi֮Up3KnЭzҬ)S7ZwX|C6Q]Oq7dJ]6aݼ2+ ⩙CO@h!_CKj\BgnR",f]D1m : =mEVqs/ËT}Z鬯ذlDJ,Uft \QCH=ͷ> #20y%K5_.>棎cSEhBSmO,8lIZE/!QtAݝ䂌EBww ;_V|pW/+?u/\]b,*w^N]]pFGK}aђf ̰ap{:z:nKϲ7Y!I, 2|47h;t,(mm?Yv+ 4,XX>ʡTp6+_"em<_!,blP6 v¢E tsZ9N+.5\6AZEZ HJl, !!{7h D+"aQ zKN44'"i׀Zc*DxB㻀ZW<8Z Sh-=X׃Z$sR)2U02?H{@O+!H"{s<:⣖,H\2$w+ G]95FWd\bV p4Ҫ3A )N`;")8X%!YD; 'y iIMI`IPmPlbb]yDwL)81gb#LLt31Sp&&:b DGL)81gb#?g%tEXtdate:create2019-05-15T17:29:22-04:00%tEXtdate:modify2019-05-15T17:29:22-04:00)gIENDB`OSCAR-code-v1.3.1/oscar/icons/logo-lm.png000066400000000000000000000116771417327530600200030ustar00rootroot00000000000000PNG  IHDRddpTgAMA a cHRMz%u0`:o_FbKGD pHYs  IDATx{tU?yt<:I $<k= uaUFEEI$seft'˜5  ALH CywQNuk{NtnGuK$s9 ?2ps0؃ U si!Hx` D׸@S@8PCZ/n!Dp dB"Ls 7{=]@4B&kl2bսoπ?F!*ߑ6Ԙ{t"`pc8 .^BCۑ{a I^!. #Cݠc(WRflMJb"8) $+IR[W8tGv[jhuy%! ~ ._@` i(Y<Mafe.NnAlV|`$c70a =bf$N֘*B! OSD(4چ)-?X W lK/Mvf: 3ZBMPDu M㍖1AF:), Iu2?Œp9FcJ\0v]:uǬ<x ؍P=VLxWNߨ^R¤'*Y(Vf#AZC,2]Q%qVI8#ih"z3%d$F+.kdKPIѼt楓勛䭅&QKVCm& +jX(@Bw?͊J6w&yoT–\ q9D2nꤺ6rRLlai"y~$n:}ay,;=LzbM?J1?[ 3LEi끁Z=$d84>aUԵ|zg8"X\H=OO5lT-Wm힠ZM 7.a}^YzG+#Ky~%VV4/OYM>Rٓ|phQп M&31 w7`Ya%i ޴뢩~J9s p9)~VRIU %U-~m~ZVyx+,P{E3rE6+ zsp?c43#R K)9o ֧A K.K ںV)ѯٓ=9G֜TZf}|Y 72rypf'~{%y2!'J6[W9j(G0IȞ̈ zo ~%K|MxZ:`w@-ҊI(> 6m`ҥ󬫫ZHz(//Vk4NjH@-B1 n(q:<3[Fk֬޸QcL[ʕ+MKL6K;jRn6#.݇3oAAqqg}5֏Bʅ^Ct* HIyfΞ=fqc 'ꇰ34w~1E0 Wzz}Pdv,O#('6@ ԩSMihXM+5CPl>9#eT?Hd:^KI~anho={Μ*uG[͈4PO 5ƫ;Q k?_9ޣB-H7 w=9θoO9/[ٴr%If#OD׸q`sܣW:aFsU.[xɪ z=#2B#7+6?TO;yN,wt`OW &>^ &[錎h,^>2K1"477a2lK^2*++ٌIF=O^u?}n_9!!A7p2k`9m& ӋGV]t|%/JO}QSi(+̤Fzߚ}ώsCᩧ2O###}UjjjB =䓾4IjLF#KOXyM;Ow}mڍ((._G>_Fy O/5.Nk׆]HC= W7h3Ú49ƶ1/ʅkt֭TvltwsxʮSVHISM^iߊe핶6Y/Fƍ fk͔ao1]`GǃWUΔhv yϢ;2erGuFMf0j>|v5h.,, ]WQ|0haժU~?mlj@P)!xJ|TR[#> nHtҢm5B[i{/:Yټ_^; vXn$wM} |qgCnq a(cO%M񤅟f%H2cyY &gf>nT4 GMCIuCD|ȏҬX!ag]IuThOPc7]p#oJ !oj!\sYfyz,bD - 9#/kV77rPb(+ 7drCNC>aCP ! Cv&E> d=s f~[6ȯ"+iSkC$Dc8LiD Յ޿'Z0E  pW:tx9BF2ps0?>d']%tEXtdate:create2019-05-15T23:00:58-04:00i%tEXtdate:modify2019-05-15T23:00:58-04:00vIENDB`OSCAR-code-v1.3.1/oscar/icons/logo-md.png000066400000000000000000000106561417327530600177670ustar00rootroot00000000000000PNG  IHDR@@iqgAMA a cHRMz%u0`:o_FbKGD pHYs  IDATx՛{|Seǿ'I&Bi@{E%.G-xqƑ:.Gq7t 3RUDe]WtHq)Ѣ" h IfxsNoI+f'9y}n{@  =0<1  mY hneGI w`ӇOi<γSˁUsyoP plHlA X@*p#p0 Hɳ7549wunU3S?3xIV~!Orp!S>s9WQe;B`a7$Ao«tNwr˘h2O3Rk)j%Au+n8LuFwX:f1bk$U5{e+,P`5s=0 6Iq[;^lix1Vu{p5?XAe'f=#ㆲ4xz$1sxl~qo?$~i+${0 PW' I[ߐipx6}}D2/3 v9[C#H;3ɬnILFF'.BD9Ϭ7;$FDs /3 ?qú.ג @ʧ2put.(e3 STKjEP\ye;pYTfș@e;ѹtr wk©rYo @TzT@ Z?iIa SoWb`SYSD#Q#鲝[8W .+2o7T,ȚjԣVTֈ gG^rgL$P`%ݬgj0RM5+} eJ 5m Z2Lx\}t^ѥ7$nl,! ľSplΥ16ol ZEt(;m@,ZuntlY(LK{)ߗ _iQty ML-D @rYaG 12Kj"4z.0!H\jQYEe5RT2d8<k&kJ,T#Zr.IP2Gh0)B>Siu)k,0YSlk,Y^J{l{Oe(PB$aunEGnBCp; 3B* d\n?8; -D}[oz?>[nea[`%%%X"r]ݻw`-ZDii)V}+ڱq>@#h՛;:zyٳgyfRRRرcfɒ%t:֭[V%Kyf4 O$*-SI:W2u@%x:[[yX,Gtthn,^QVBB$~ $HKqE`rR {uOGT\QBO𠬨0p0.ĩ@AP `ҐPga&~q#\ 6 +l%<>hwxhb{0.,o+ {@X=a^ XUwp-+>O/UXbSLzia<̝}q9~[a!~Nq/r2z$H!|89{K1+7G! P$#ZZZ}>wRSۚj4.dz}VUWan*+~(k1=5*<{N}JBt(ѣ=6 f=Yc&U=l| x񖔈.*kUwg7TN0_"r}Ē~ _ydH|rv{kў{5kְ"_,Q}l/ NDM Zݦm裏5HaBV]H$XĻy@d/OLg|c/EeJfX__ώ;TF#gd29r$۶m ˣшϝ;G3CY I9CwJ,P`x]QuQ1_4ũ֢锔6UAhiiaowNv˄Eoc`ds(E)1؅8ו1cOr;GtHNNVUGR)?lŨ?z*ߗ5?#>'ux+?'d}>9!JÑ'3@K$[5!l'8PhZ"[q \>͏0n"QFX-FqP-9};TqO&;hY6txI9 D^X$<3 [߹H[-yK_U1o8ҧ57:!׳c2h3j2U7$qecM5R4,Nnh1`?{JIypuN0qb Y2a-?O>l7cmcbߓJ+[xS>.(1F{s(qo$S_kA#>pJu*BRcp7E}VZ8/W#2-b/"/l# Xg nTr?W6N57w+Gluu>^E-f&^q |VCEVڱq,ȴeJ}pH6GO{^^ !H`6(kMc{'->սG$><Q&DRN ,160dsT a)(ahvq;>Iۏ;ɵDba1Zbugr^.N52m<2?iC,x*T#*}~PP SUD Z~ [ P5|DZ%tEXtdate:create2019-05-15T23:00:58-04:00i%tEXtdate:modify2019-05-15T23:00:58-04:00vIENDB`OSCAR-code-v1.3.1/oscar/icons/logo-sm.png000066400000000000000000000016221417327530600177770ustar00rootroot00000000000000PNG  IHDRשgAMA a cHRMz%u0`:o_F#PLTEz%%z7r|nτEu|em}'F{̺$Q{}6h=== Prqqy{ذka9?{`U݈~ p{ti5z}r~tRNS <5CA= r6OԵbKGDH pHYs  tIME:06IDAT(UV0 EJe=QJ)p̲7YILɉw-YD䬁Q56Q3agFPK$)n eAAɍG gl<1t10!zzzӌ"============zzρzzzzӪ============ςzzdީZzzzӌ"============pzz{Ѕzzz============Ѧzzzڹ{zzz============zzzՑ,čzzz΀ ============zzz ׳bzzzӍ%============zzzzݨXzzzϒ:==================zzzzqݨXzzzҚJqq===qqqqqq===qqqqqqqq===qqqqqqqq===qqqqqq===zzzzdqzzzܥR============zzzzečzzzז6============ܻzzzzrܼ|zzч============zzz{čՓ/zzz============֓1zzzЅݾϣzzz===============ܻzzzz߬`\|zzς֔2zzzz ʙzzzwwzzzz֔2Tx֕3zzzzzzzzτyzzݦUݦUzzzzz˜XԎ$vzzzwʙfzzzzzzn߫\srzzzzzzzzzzzzz|~ӫ/ܻٜBfp |zzzzztp{΀ n9lĎڟR΁)mcel΀ז6eѧp"ϣ彄qlvčְL'N??(0` p[_ײŏxg߭acrϣhbrؚ=ӊ zzzzzzzЅ֕3ݨW4ɘӌ#zzzzzzzzzzzzzzz΀ ܢMFXЦЄzzzzzzw΀֕5yďʙǕ徂ٛBЅxzzzט9bڟGzzzzzzwٝMޫf{zz{rbцzzzzzx֔<ٝKzzzՑ,RЄzzzzzvďxzzρ&Ҋzzzzzwծ̜yzz~ݦUzzzzzwҥkzzz΀ ]zzzzzzȖי=zzzӍ$WЅzzzzzf============zzzziW͟zzzzzσ============ݬczzzzчzzzzz========================ȯzzzzӎ&zzzzzz========================ҋ zzzz]ީZzzzzzח8========================szzzz֔1Єzzzzzߴu===========================ͱzzzzz$zzzzzz=================================ZI1zzzzzÌǓzzzzz{========================zzzzzז6߬azzzzzЄ========================~zzzzσAڟFzzzzz֔1========================ςzzzzzӪՒ-zzzzzݧU===GA9===PE5цzzzzz潁Ԑ*zzzzzrܻ֕3zz֕3ʙݦU۪`ұӌ"fw‘֠NʙzʙsЉσчzzzzzfԐ*zzzzzǓσ֕3z֕3σܻfӪσσݦUݦUцzzzzzۢNՑ-zzzzzʚzʙfzڝDσzڝDڝDӌ"σσЄzzzzzۢL֖5zzzzz̞zʙӪzӌ"ӌ"ܻݦUwݦUӌ"wݦU΀ zzzzzܣOݧVzzzzzəzzݦUʙܻݦUwʙӌ"wݦUwӌ"|zzzzzިYzzzzzzÌσӌ"zʙݦUڝDfڝDڝDfӪݦUݦUwڝDzzzzzz{zzzzziӪzfڝDzȕzzzzzzѧzzzzzט9ܻ֕3zzσʙۡKzzzzz~f֓0zzzzσчzzzzzԏ(3zzzzvyzzzzzlzzzzx|ݥNzzzzzz?߬`zzzzρxzzzzzԎ' zzzzq˗ݧQzzzzzzۺtwzzzzwqzzzzzՒ.*Ҋzzzs؜FuzzzzzzAρ zzzklwzzzzznܻzzzzdުh|vzzzzzܤQXܻzzzzfҌ3xlwzzzzzٜB~Ӌ!zzznjٞPԲs|izzzzzzwxfzzzyje΁ٜErʛ˛ďyۢPщ#ngxzzzzzρӪ PܻڝDzzzzupbbddbbotzzzzzzzwh*zzzzzzzzzzzzzzzzӌ"=:tӍ$zzzzzzzؚ=t`*MAu???PNG  IHDR\rf IDATxwxTe?wfIo$-ttQQ,(Vņm]*UAAPW, (4Az H/grdf2$3w|s{ 0` 0` 0` 0` 0`C0cA`+!@(;@=`rw PS n; t@a@S;ޞb۝uNb( < 2#I`{A ҁ=)Nat rAS1`!qRUԑ:nl 2)PIw) "je 1VAJ ~p p!eVq-RUsv$$@LޱVƤshv bJǁAXeV%Dt>ߡj^1 dP]/sl1 $pApىߥ>6oAnb|}8ovjE:\v:z@ dan`tGZA!# 3k'x9Bўb^ߒڌfC߸ nډ;G)WT_80 J^G{yz)vVa WiAۙb[ɕ`N&iNGss8r ^m1CyDƥ{KAm)@7oNSUR5 7>S϶", o#e<$ưu/ʬ;shOxْ(ñ([;x`u6%6CڡVݸipDxAt<;=̢%UwDyqrWnɓw;rp~$0 J:>m< j D0J j8ғ )ȿo59ggn%Rߎ=Y )2Nzu $:B|.Z6=7 A {n鏫܉YYYˊ%k| V>YvA e?Jx+j yuG kT Yŵݧo"X<; u?ͩ_n_)cҒ#T7W/Ɲ#c n94M"@B;TM8ey̡jݎqbZ$_%z/W.#ȍ. U48!SS:QX{?mXvl=sly.p5`m`ܘO]7ğ~ِRĺ6Wc~Ks@h?G~rݘYEbɍƮ2vVr4z3wD#@n9Q7FͻΉcU i}~Ci\3]cO>p/]Elxnsg3ú+.^\7RSh= :sS%&º;z*PRmc}Jф/MʃK.m`p^2 (-%5{:3d^Cu#kA7Kc@H {]Ooɔ~Qr$D&ݝ/'侑|^d#W´Gui(~ 6m q(VEu%ܿ*[K ; jؙ[ɨ9hArd 0U˓N29m*yHC1$} 4DzrdE1%g>k/< UG$PRm#=row^:+ G3p~8Rc; l֙&pđ$c_!=g8~=ϫw?* 6G}X |}'=)'DQ< gǀrvo^a$4C;0 l4g].;NVJ,E1lQWRGvEW#l 7N՘.|[':48vAtd-G|c?ʐ3Fr8݌fI(FhB1 >Eפq+qNrn I5RNYwPjކDX37C-ϟ xe7MW}JJ xNo-dKF24 x 37 x)9YzHz*:G{nRWc5 /^эhKv<28Zj< +Uz6iD.qrʛ4x'UmNnbmF @N xV7 b$2>^j0}X']e-C`4jv;׀_0d݄?F 8^sp&LG"oδ kTVs⌕koZLg_ՑY\2jN`Th…: 0Ёɺ˟LJEp/"M +ր_=:$PfqYo{}ZM͌ah_qW")3*-5-8X\'X)OyR/XOt]-}Z"NJ5nm9Z4j H`kjb1 O0Vt1:ȇ{$cnׂnZP㱒Z2Th. 5(GIC>; iZQDQ("RokrTFT-Cafn,W `( yšRU'!o@yܯ'ꨮ+\a|B:.ąZ *̠07^U@ETA4 G?@oUtz;&arqo3UI b5`<"v lNĵ:95pίd~5`>;/,q }l:R(𺓰Ň{.Su)G~kV|O1s*e}f#6<5uцDeqh m:86,(HzOB%QcJf[E#d<0$!@jWK*Ƣ(& /E;s+K}[Q%3^ l-`k(GU48&C LU+[ TU6f8&ۗ0:]pXIA/%{Ye7AǤ>l?YDH.^݉]JtXYڌ2CZ:(Z4ZeN#qt'mv5 6 qXmHJjؐPB~7p|QYkGjd`U)~]hQ s7XͿwxRץG+(1w)IAz7R樳d3E~#Qժ?U}k8>8*|X=2NR`\f²|O1q{=!P):7@"Uuv +m?MǖOSbr']ݺuJ6afHy)qg\G֜9cbfQ!me-}hlmϩ䒷lmviuw\@Ǝx]M?ZOh(Į?5hu1#KfmFYcQj{F0gweGH{Sh^=2c5)%rJnÓB*u|4~* K]5՜8^Z)&d=uq:`tr @&@o+`iks(#:,+ol&5rv4jon7vB%i.dJg MO>ڪI*nrqtSoxV)d(x K>"0BM}pSFqͭ7<)Dx`[ @Nwqk'±0KO܍h2v_?vңv_?iZ-ywErWc I3uR/EQL'HVB8Pn[5txnjիX\e5'ڴzc8f;ao5m vң%іU6{2^Ҝ@(K55'3rL JNWCCi>Ưy-Xxf8}fBR!6 15)) O XsOVD6游(jm"yZG=qBPe]*Iqո],1VuMi[$ _L)˟1<)/->ЖuМ̚.Jij _|V# xW!j[ubW#kN0?<׾jӚg&$5xZ#@)&9S"Wh..K`~5(e=ZBlxB!ju B0kHiÓBjw-SR.廋},Uם#G w;/f\'l+{ϒ܆|$Sĉ:i[:uZavpO26=/)~Sh W!_w9' 7- 8@[1 ސmVA@vYٝXq/Uf/y-su'ng w͌>lL[zKn|uʑ'91|f2r&^O=,;^4sXRp< u9$jI!6ѢF߫1xdbYai\{תCrI8,@ON pGZn ]% StjᮼԈ{s7bk[,`3nHRQlxmGqAݑk.7f8cSR-o5hxR߳䊫ll&WkZ4. &Sԣ bEo}f9z LOk#WlK8xeσKvss7rCv IDAT7sq]zO-gBjIC>WSRw{d,LHM?9=(Gޛ?%E6Wά&sb%׍뷐"@kl䑣GhEj N3=ww@EM/pp9qHzĽ]8IJxt$liysqO iUټsf4.Jm! j8EX 8F":o[[{ݐ$PjM61W tq[N3jA2pcVvKmxۖ\L@LF C??XĴw. isUʗZp}==CdJ XLr?@RB5|5qڂg@]`e6`Y >LRţ~)ZkJk.-\}Yݫqtڕ[oUWLPP]vOKxLH@zA Bh T$޼y3 2ժ"X~=ˤ[ob\3g䡇⢋.wޡO>M7?ŋ2dݺuÊ޳JaJ#Ri5)?w%s/f3!Ch~YPP^M7ĉ'?]w5׎;x饗Xr%K,Cqwj:ks뭷zj @ZZJBnJW#b@vOYSZ~f3Vo߾] 邾x?z!Mɓ'5e=\Jٽ{7 .d͚5k„ iw}dž oREg[B}7Uק~ڠglTQ I 9`6K(zQ j؛v Gfʔ) 6Y`` \p5se۶mԨ~r-|Ͳ<lڴIq=ӫW/d6Ypﯕ8,$ ha? @ҡ'6HSؗ_7oW_}ka՞_~ᩧrMjUjkkꫯ?6̭Zضm[~ ߅_4 mNDJ0=O?ʸy j]4i(R]]^x9>|8: ?^5&L)"!"@ t7G`ii)s=deeQ^^?0{l%PXXHee*))<]?ydGJ`˖-\{]{y5XJݣ5 '.tƌ]饗:~;}:Y ezQ1h͊<{::n߾oc( j*R/~%Ar-`ƪq%->cqq>ϼy':ڳ9R($ꫯ68}GQQMHo*EHFk;] 1А!C\׮]i]څSRZZظ9 8ޠwޜ9sFq<ޕJMMo;[O 0 \+B_C"+p| }%^]1, :usݺuB9u/33Q)h{Q#{^}N[j o: bI'D$N'h$lv z}éU9\]z)ikgddBdwqIIIBBBBJP9eu=¤^ޯpG@PąZ Զ)q/ݻw矞讶b wYŢ(1} 6Q3}X,>ӧ</7Ȅ#. O޽{(9r!DE9A";wHhaKK;8m4?\&W[-GE`|p^$#C5>_~IOݮLDh&"fm(,,䡇.A +/ןutյ]ud[0L Du w~ϳ0SN%%%Ν;{}٬.{w 0uT ک%px 0@m82ԏ5TnsVvHzGqD0< 0k,233Y>ݣ[HM>.ѣ3>Lf:dzzO?Mvv6+~Ht:L/*QFC~~uJ~X)~ArDQSPXY) WgrDyEzfe߲`|<Ȓŋ9whS^s[l&>"ʢ"P]k#Ƕ"Y{? `2Am-TV:~;#./f3Alxl6q#5f>ȩ\vw|2k@kDc@!NqmL I}xƅKxwCT9Cfv6ٜ<}tڸ=|Eqʰ}/ݢwe|2:|{16WdfpB +N..YI!2 `)SGqo3{6:Hqa!H;qPv6::E/fvHNv\ 1|+NՊn'4x0ݻáCu@. | d a'5H[_E GoUU쩩OZneFz+c/w#G@TDelO]ڳ'pь>};~yIp_M%s1;nGyyd nvDDx8pdAQtXxsK/oa;Vxulg)G&fMCbZ-cgn%CbI&u C%%. 4 N/p`^شIxW(I-1bAAv̙^ya̙< >\jaDR  x-kNyMYC9,%%KΎ;}fW`m>  b`2<.V2i$fZ/Z9U(QP HZV]] w]p!?8f:=W^'$<<\17ٺu+k׮UefG@_};wlќ`%Jm DF=8VRX"""<:o;'P{@@رc iQe]ȑ#硯l5\Ð!CZ4gQ[O "e zAng~‘}'Ev Ov#GpB{U׵]Y{rO9xe5`ƍݻ6=zz'xqLs?.c\Rk tͻFJu)Pf _|1ݺuSw_HHn˂UUU1uT.r j>sTm/6t9sp}IJ3Ϩ6@"ܭ 8`ڠh XI*춟l.]3L&6ᅬ(u[{ӦMfMr5yW6]RJ-o:i@aa!ٲﻺRnLIb򨦿?QQQ)TQ%![_ƍIHHy/K_@H@LH̀%; 6LnVX^Z,ζ-[x]HNNv{jzjUMjLׯ5pXbT:AД'e6j{9E+R-wo9|0SLQ}\.QQQ|%]v@PSx|ɬ=~C8˄߿300PvZTO:UgEQئM>i+u.exUW>..;_vl6Kvڙ[ɱZUjA-ҰmtAN3@O2@V~J㧟~">>r^?п]kǎӥKUJ/w3޴VZY xκ1_ܔڽ>-f>̧(Jcٲe$''{UM\R6C-?4-H^JKȉ|`tA?WjkkG}$l6/|[$MKzʟ? qDU#u>IKլS/pjWtAtGgΜl6l $w7?鷐h. (T\ p5bvEA gSRЋ{-ZT(ᆱI]k z…{٥˗7ԓ4o޼$D>Is<+#гg& .v{"iRTTDee%{֘0v;/䚘1cƌ&ґDž-Eg5v *)-` M|BBB8\ -jr'h9cVɴk doh/ѯ_&_4IM~b;%ˠUmJѽĉ@Yq ]UoB+Z pvÇ9zEjؤG}4WXXXiѢE DC-Rsm)8ĉ` <ǂ jcC2xQFQR8EoOqwyMp\x -ȑ#G7pw5LQcO, ?jk.-@h#5meyp8q V>|xCE Wٳ5WZZZC|}YYr~ ' |KpAp2_ZSW­gN~.)YDԻE1x\dӱ3Dɍ= j'(]T_{urt9N>?Eq _=dj.2y{9u?> ks*(N-OmvاwQRB0б?)" ` <.8:7lLj9a7tj'{"Ÿ9؝g״ 8I?80aFt|^c LG~@ܮ~PVAT {k),&JmkN_?6!,;*ΐ@ [M 䖡Xrmw9?"BZ{3AoE? /5r; ̹O$Oa4_`hIl`CA~"$ W2.[ݮQ'CB T%6.[r??)cE5..!Οׁ8H`wrc,P24!&'D+pק(PP5wٗ#Š8B{(@ows>n3").3O7J9sA#ϵ-b/=Ájƽug3Eyn{V: KmםT<|_OO#)"@zfwp@(B텦}p廋s&`ij0G֜}%ٔ=ϵ{e7/>+_kDp0ی2&.97bwyH'Gv(pwm'V{H a}.%IZd=Sy{9XP.[T {Gpv/*eEߒϱZk6 ~e7r_rw6^.MsyY,&KB*e}f9+} 5ј: O,_KB4^6Uw&AyV; ;sD28!A]=ɡ9w*6d}R51-%v'>N+AY #>3mgtZq6ƱZ.㽝8Y:Z$@@&E2O$֓` 4@ "$@u7~㳃dpĆXQ88 |Uþ7@K`&p'=l"r*yog![NTpX4bkdq̀(& $x Gq41ME+h~=UgJٟ_MfQ ^SbC-t s0SD1$!5 Diqxc<tMN20uN+jdpl˩$jTڨ5H5 h1`"2L@:0K0}H GKG7ie@{%8 u6zNQ\eF~E"UuvکScSkȝ"X-&,&@af̈́YMt -*` &G: Xc@G$?8g|v:X b$p6AS35HmNVKX| ț w(m# ?J;{SK;j E I`t|Pr8y.t2Ty g>8qH4%,N0Qt{.(u z<^ހ 0` 0` 0` 0` 0`@;_,D8IENDB`OSCAR-code-v1.3.1/oscar/icons/mask.png000066400000000000000000001167651417327530600173740ustar00rootroot00000000000000PNG  IHDR^ W cHRMz&u0`:pQ<bKGD̿ pHYsgRtIME /s\IDATxweY 7Whg[g>nUW==y'ߏp(Nbt/Nj })׹}:bDXQA,vGt^qֶLKERgS3pAD4ZZ+ gkq;})TCC@fn_ueAÅQj4Hu#M\P  b1pXjH*J԰&\ձs>cLZR$0i/33ѷoW?);ZښjRY}(B$'Q8gde"6M!k3b'8]<\ GM1 cKp2d-$! oA'^E5,˜1٧#h}Q3 .˚f }˾fDK;d-2]+Q3D'ؒ'?d3L [5Ajn /$ X.)7qN_jYk8xïs~'P>ϱ\.}O$"#Բg/ cB,L>ͭ۴휾z w R(Dt4-\4Bu @寓=MO[Xn} +p.Vn'8]xX.b( L!dYb.$v,=D#f1+C(tqLgCOb7N06PVkӒHdӇ]326KOf7\(IH[Ӝ #c# *dЫ`U4QdvC8kS 5[YR($;)nތO7,Qih+Ld&(Zl5,mhYN o _xز5M0@R5-X2&FRuvݿ/>nl@7A1I55C8 ~'2 ࡉ$pQOP@C06݃H]dOiҌw#WpHgRu lhҐJ Hx): iN*3T Xf^0g=KerϺSu/J i;}ge5Mi)j:ƶşu N(4Ҷ%TӜ}ea^M]'oz .45d6Mt%bm}81uM LbDM}/׻]bݖ"L7XTEziIJeNq?_L{Z2ز$v]?*5 X)pdӚH&5.0d%c[ݸch{Ե%KqeuW;puijr:MI*#g]ғwX#.z M{>ᴎfybnktʃj7^_}a>X)ˆ9p4,96O 011k1MfKݲĺ euimAuw3:]mm-֤ C]D*PbNA-q$: es՝yME Kd&Ϊ3KDr֬{svbV5 ƎX-3g6qWx4ux{Nj5c5uHaMcC\Ͳ X7Yu;!mV1]ԓhڔ(5FjaSX%J]-nB9KhPM94.smxn1{qլo}U# m–ܜd(%}| NgLpD.60v}]k ,0pn30jW[K.ߟ-/rmNiRX`,`ٜw~dWn /2ɌC!x@U'r ,;e{lJ"-[wȏ'kRPض)Sh#2S^?pjW 5M%{XaѶl=.,[0B N\0q65rmaz}djZr\'=n?#:;+&Xc "s9缮Բs&K눇}xO#m}u?~ߥVEF0>w#orA;5{v]ЉBGlGrX!ӶaQ޹H g,YrVfysf'8U A&V74pE;dln ==adQr95|\ed( P;oEq~ȿn`D2E%2c#}Nx#ćfYSOزlLׇ|IJ5oy3~S} e!AnbVE0OwjWǮ'55HC]m{5MV~{->Yl_ib( eq@ǽ w{ծ]'NpH#CD> c#tIbTd"ٻ}!'%_GneJc j"{|?#*׉l~I5Ĝ0i⢏;&Ր ndu|yM"5IL-pQݚ3"R;w{宆]nxTGSȡOӺ T-6hA'?O#1 *kq8 !&|}nWr/.Q BXSK,6 ^lׁ5QxI: hXmFpaebd"/U?yAZ0Ffh$}\S}5D'/o^#V545.w6[/GO\}ci-;!7;٢kFK5V.sZ!63h[[{sJ{[B9JMe,3t^]޹lם`hRr/,h-X0sIQmntEǼ0'v)Q"[D^dS:L%v9'=6jDbEṟc/ӎnr]=c'FzZ\0q;AOlizXrmKM>9K[!"m~DW^ 1PMQkiZ -5'ݳ lÍĶ,rD_aA$Dw!@>ۮU-ˍ WJ>?FгzCGgsRI ũVe[6t݆ }*^>Єf"-p.{? mW`l0 =SҾAcEckz:Znx#W1E[̠*^@_.AnboA] *\N[UXsA:"C}k)1_rUm`PӔHJ:܆QS!J.خ:A.lICj6,k1XfYmFB: T:BSJQ߰.źD&V>9]-\5q]C"ڪ\u^àJ d⩾p%SHa o۽|N;rg^6e٢|<͖k,{y/WG3DOje4'$jbK~oȽ`w\*1.כV:20~Ⱥ.ZvCP #^W9 [5 +=c-}IثR^f@`Wdmzv >DzH7ꬾ>RYsB{p%>WeW-GP',|K>X)$2C r>U' qf8ZZLkG29[Ծ0 R_@I&!!kjvV!Aw3K,ʪy ##/qU_Gwi4Jn^`N~cf-賶] lےhk8l$sDlbش)YC|), ͥM ng\,aӳQ[u1mc=D^=vU.>* VKZiq`X*Gn avHKH)Xv[֜^&o0RjzW?WvܽX2@QL'(a|XSfZgmC5KZNtlE붼,:洮Lͼ z>+;=RW9F;dwAd+i20p %\(6qAgv66 j za0d[W'Xã/ Tn.+:.j𛻳v)&XXnAe>;޳^5E#<" e:?s9'UB>@($S-Y,*صjDb N+m[ramcgEN{W9,l>uz(801IjZ捝'v-;ZeWC1R9`b/i:wq_Ux;{o=g]#(Tɤ W3K-z AUk_Gb₽rٰ%mnqObQC=#y#}+kRH dvпj`]s'X+ri]d!ely }.yT RN*vyZnr>\c@y:BP20yŋ5׹BL40MS'M@#(vަ}^g1jd=.Zöު 5wzAlۃ׵x(,5 R%YU9#Ȗ\b=nIX몫hRF)rfa# à. Aw/r- (8F9A mbhhbͭOzkȊ^f9"XKSK9#]-bE(aO rzEnܷ!/y )}:fتꇽBt)ĪԾ7ҵeK[GKKa[.loGS:@q 5!.#qO[,P:.-z mm-CH,5F޴[Oi*AZ3䆚yb;&% uضuC mMeQ@>[ᔪǝͤك0PH5EhfKCD&Jl]Yrk#:kښS˼܏z}"~CI?]5'8S\p)rV-酈b[ݾ-Za P% ꤚ-۶ L{:?q蛄Ѳ!0z ?4d"u4U5S0KhHld;}oP `~x b-.kY !jb s~u4ӎ(+ߧt!>a7yLfyzL!Uwbk 2Man .3a!iI!]-X!mK|-W99>~ R&R BC-9g%,t±Tv h)~·lʚ{Ïm $\~xQ MB ;8 TK1{ JD[[rFƟn!?雮YY!P_M9-&TU8kRxLͺ9#@-UkIt4Ai&d@hK8D-k&"=UHiʱ\!_NNCpz!⹠N%qp}9;}ѵa Zr *rXӒmM׹w}0pRpd\ҳ! }IprI(EGII3y(|E(Gh̥գ^PX$3C!3ˬQpDM~Vw:pg![|ӆ2Om42L!0o`KQPh<P4h Mq?^/sZ c &ځ0Ur:%oz#Q P@Sg~) ›? ݉I&jV s }֗s7 wNjYG__ݜ9=jFy cE^dKG%Y=P;]QPO@W& lb;kP[rx&ZMs&"2 (u'@}7gY9tյ4}_]?ZqV7ʓxr#uA&.22=ch' rq 8gI`8iSw:g»V`YBݼUcyDO\s8 !rsFz2@݂,T"b-L}5uyvf'PuYwFF!094pN˂&&j&+͉\Ҳ%3$ ˁ00S@[cͯmHsB`!()ІtҀnK}5&))1!|"dy(t im7(j2-^+nNQ_&n]2rm3_feZd׊=iMm9C\ua}%51XʌBDPFbQXI_:=e*ʲ](+=iY5PC.1і;;/7xR':ݾɧD~zq8.cY_۴l{zjA٥0\>=WT pJOdӶv(&jf8U XBny%qn0/4ٹpRӗ$ dnmS,6kٚ_aW/z8ٞʞVGVpPyCT(LW.塑hYG͜-M-|bNpLJx>Ś&l3r̂~޿0ot>a5/ԍ4C'1 BOͲKEj}yH`yeЍ(%RSC*t^Ü-9:zЫG]Z9A_ſ1py'lFx(zʊT-Lr r[efuI)ضa>BeCn3Vk]ڶg*#C##aJh`" T&7 B+}%g8 xF(0n#i9ᤵ#7[<O uC s=ͩC 4C&8A$PԴm# j.˧IF=zE0#;}WM8ZC)uC# m]tc(ױf[ԍ4,X m]ow?N~O6 b^/hmPՒV3oc`hiyFZjȥK^CjO\0Q>(v9}5XݦL>&XVוkMe5mMF!IŶM,[8KgjJ$ab)QНDZڡZF, ^{Ks}bےWe-76 \l @MI,TP} ƺlJԐ;/t@dS/`I҇=[;JDmDKfXю2v#FNkLCo"{VV*(T0&}TWfVC9mGosC_c _eka"VF-DdM"DFgFZ6BY9ϊ4/%m,T4dPv\*i 阯[ ]ܢ[D~96I Bx7#%-FM]ΊBk"Ua[PϼT#,Yr4?WdXEՏv4R -K.X[:M?<8[=O닧42kklsU=h)> [,#=- Ms|9c.yvн -MZOpH>&5uA ȱXfQ2M)ˡȊm]5])Uۺ5yH* RMM$5 P0ݧp}p($r{|/??bZKtlK4 %&"eatkbREnf4X2R ˎXW۶nyL<%ǻ㖴C8׌TYp\7m8F0oH"eE-kb[a,X" GFVLm]uF ]5[cy<}fv9+U},#eLW%}W *ԫFO9DS77 s9а ކ<0JurhNq7Բf@S : $EhĂ-u#55cDThH,W\:t)OQ]PX1̲-]%$;^gvMBNf=4!s*aQ8`^ H#C=Q;;ӷ_'834)E5dZ.)Ţs# MXehSMfw:;kͥ7 '\H'`s 5gx7-UiꈝӑjꆖW&ӕ ë{KeEa4,.+cܲ&Zl}';: 2iyƇVuWZphXtV)Wꌹ?8>W]o-k0f!Z6ji qbd[#Qq۽|w^~ tE[֔[aû>sIbu7O-RVw^V4͉5/7eAg1&$V ,؆%˙yPdQM"5- SJf~ ^~lRm-x|])}*ĕӺNZ}WL;1gbCE\p%-s&6lj[6ғjZitsf˅;S}PtX7R3mNGCN#rm{Q#{K(ާ-1 &$o,LCfh9ImsNϷm,b:w|3^JTo:%r w $ .8M$еo_)spt4<>*1%z[rĂ}{_v#~f~3%uPmjr,, CW -ƚ:i2_/?]^H?਑H@ key)[2s -wmYoVKqF6,iJE!g 3:[\fqj^MP,P[ ]fߎ4Lt=w;<[ftdꡅSjF:ִs -MɟNldz3LvL4ZRc"GY1cHi/v~ &FtcK@I#o3մ` 8 '447 Icէ MBli" 04Ig _ mnwYq+O~X9nU݊oC@ 56HM+yP^&X]_Dz"?{OlJw͂|zWG\dtb-MX`Q@Mf䢎Ԃ)g5G+RӖrxq9Opڅȍ!C!PT3U%a2¥b}xp'ҔEB碁8ž՛}^Ϟ5ea;pHBZ9Nj9sͷSpY7Q3Ԟ6ǒuP΄ҁ\v} 4 w(aa_ejC(E%.y8VC)5P[1 N l;/~nm`d>Ԛ7iݫ|/tG<\PY$)keMUa:"LK+?ek.Lj}y#bB_n>#ENj߷Կ˞b)/Cy{#=Z\eP+Er.b^sĺ@j|8EhnWSՉ9=}6M Қ-e;9xh)أZV09&su|uB0sn_%>p5mUZ⎇a˝Jf5cy^9_~{)lZӒk ($CgF y5[V%br'߷ t°ka b}l\ߊg6ꚶ =&d̹O[싣w\HG j\;` Zb$S/ix'_53>BMNLLpܼ_{׮^A#wz`&Buk# LtUTY@/|Bm<,w $nh/o|CZglU~7zbfɦAгܼs>fؖR5/}_V#*{Ti9 Cن-_G_߃gӻҟ߲awфi=7@|\LMBDMM_%ѻ3ȧ撖cb[OCsxOO/ '*']p<)NJx~MS#iŖ9{ՓqB孊KɽNWs+V%:eYyn,;h=n*MPoUZ2zl'&!*+iβm/.+:EHC'a*WMBnk[l[2Ծ,Eٟ'ǬhS:CU͜p Jસҗv"s3IUs!|'> C9u;+;#otEBL˖z={YO ;+r*8#VWC)AoҿX7 rM2,+v1t]"*zyx7[VCWmӦp(7 B5KeJ@pMqv%b5ss}6??^ֆLZ;C*sVGaC{U I,VnKHs#iCy2c!7qK4-(֒3,;Q'p9di18L)?}+SDD9PO7mOGHGĶ {4ͻۮZ{PJdj hhky\Ͳf@bGŷZ2Sg g,¸|մc# t<`_h:kř0mTVҐ&6,9j9,ik ״䴊3 eYZS/ Ȝ9 oY>>释/RO<0Bo8Y:L8#sĒ^Jthqla=%  U ױ*co|^ ~ڲ &k*43n]ٯ9Ȁ"-9iDOԝHBa;t!Vݻ7> ;A5P2r!Hyߗ.w):So>>?4L+A2:hdpnOպf)`ݜ/ ;]ōPU35NIm='7䴗|=N_)a[[oZuٴf&:2}zZab\/.nW\U-#SHI en b{.9kiV`;36Qw1|(;KC. qoO3 hyԟ1o]>jukCIlYST]1g݂ykռI_+3$w_bUI|qhtChHDZ<\P͢Bywj倣z1t[yNHm bl~ӧW}v5?XXO9\:A>Y8d蒖}{^Q_֗}c'1 eO MsV_=4NBfNӹ>g]4ԖzĊy[ՓVhr.oˌ( SInM]WmS9oᨇW0xyAUey\^m8rto}Wq$^~&T5ȥU ՟ `H=k ` R.244TGۢ3_K؜p kh D>#]u\2␳ U'-kd9uR,Y-X (9 [ꖜ~ygOXڲ`3fV3+%4}nϵ9Gi΋Pm:H6v*6C)[>]HJ[cl]y/h9gOsE[䜁r6 5B!,ľj:BT1¿{w=&My{lE=uG44MlƏ#%&RIKWp?+OTqCKc YWhXdzW;oxpN9蠗9Nǜ:UKMflvmxQh8mjW V^]v kXבaX#TYGſ.8/k1Pԍm)4-K m:n~o~r}խf4/_e2H Ex-%6 Xм%SKrQx&qPq"9ӷp# seJ66V Pi6\W>zǩۜGK+M#!ѯXj˺Z~+O#xˣ|+/Y3Tw#MϠXCQ\WMJd*TLLj: t{[|15c~‡}Q/ $ Թ%xuE_]fقGM{-z k6ԡzWswX Fl ]O/^Q Ŗ mk%pel$S|OY2%gRypJp6oAQgPse4f{n:6^UjgBD;bKW>񈶶oR'a_Ah~EVlڰgd9[E2Vڟw2d9h>/n8r/yss*~G-M%o9ݴ. sя+v GWNIQ8i1iK̻ÊZ-SW 9SgS-P -v$nʩ@F')$2ëiQf~/RcAC+^݊nq?u;~GѷeNAb]=E9(׵ϖ_NC=G /_xџXqko{NJݴ LĕMqSŊ V|6=l$⧆">o= 3"ǚT3S[IXM$*i8:eRiGOQGjCfQ˃"SH5L,h Xv_)rD˶HǂI{Gu#k%u;]6'җh20-=Cy3U_4q%iD^u'ݯkEާt7_}9Eea%Lan`8L ꢾdOB:gy,CxA崪n✒e]enƣ9tܢeI\fI"gK[S7`ybpFͦ1kgζC~/9I]+rZaN@L|9v1T]9Erܪ2_cx$"]64@_Ͳ!v]DbToE[ygmˬ9"\?=2}V7Ŝ9{[ _u}5X˺mA|9w=8(MdR}-Ki?z{_? d=d0g/Q{ [ u D78W7d2VGa6ݸm$ wڬ8\`6אkfqQdQ!%sJǜKVEki0[ͅ^'B[M]|-uy}m]v2]}],'g.0 \R5czcO"z5Nͫ$v %QM7JΕuخ|=,mAT%$Ȧa)i9n_tS(f55 6˟XbGYv@n1TWSbsmyb&a05gA;ijXqR)xu؜¼%r޵7pP/鹊B$G3kc4ѵErà g5~iIO^]OzE?: u]I&? $UWp[ymJe[A,*B5L|ʊ喎nr#R5j2ˏQP]61`ӂȚqp6 58T2NB&2gI*'\_ojN([:雘3VwUr/cۖL,e[0Hr%#-mw;̧@oz/g5;b=5ۺ%e=ψߪNXm\>J('E4ʩTb ucev҆&:TUVOωe k.kSȚؒF衣!ؖ]1۴m[;0*अz]C mk@cHfNjZ-o,CJc]M$WW(pRu N#:Si/&5a kh$̲%rmjOCŀ1E&eN?wm}"CBy*݀dO+Q+C-̤*s-sXw'37iEl0=JV.Ga?/r&w.j^!52T,SEBZX ? _e4QbtΆ$gM:o)`Ω;jSůXKbm9cY/i@b]gN0zR%y,-68B)vt2MVNPr$..CbA9 }U^1kH]\nUpUUevN 3X;uNv|̈́Kk)Е bb'Pg#7recWK8`P咦Ecc11oݢjļ\ڎQUn<}ϖJ~okyE<(JymZ܉%?{hZʹKl_ ,B,?_9;ٰH45Ă Z%cmG *fТ+#C'Yy#ĂmyC{9& blj8YCFwRE&SnqC]IBNCT꭫]#;M"JXMnxY3ر]99vcbx0'*tmL}8<+'G;ܢg y uk: Bad pXd#5H-;U^ (ΉJܑ 4OUTܹ_|:Kv$ema^$A_YBpZ3YXSdžۮz o;^0"qK--C-y*eA3=|2LNdyL˚vH&ဉUE(r@`˾M=Uh8s )j*,̫bq׮ 9G9v5Š- ( Js*j՜іH/>Z!în= <}}bWH'v x OהƱvpq]*& C*ahzUGfT{3͘H\(rWLݹ)fl' K"+r+8gIfK<乕LkZ,j:Ife2EaȚ%5-k΅`fRgzYHV y4tujգ(lIk.2qfzY mٓ:韟5J*v*ɼ2[3'2 9%ղr௡pȦuGӧn='FP*}&FAa-ͧW]ıQS;l!FyQlY%M/wg}tW26\sADTro2I(x'B$_?ԱjNf }QQ VF3V2liihd^o<#g0zoq93NʖˁbZ.c#HlȆy={[W[y'2QKvV,2?*ر.k PʬM鉙la)J7eWv(̦;DS+;1 HmAkU^u# cŃ5g}sBOJ+3y*SDl[ttSugr~5CtYPh\iy 74X#aTDIT dfrLrWC.J`c&7 fDV59)uiG}o,"+q KV,Lߌպb۹7bS-%Z {m5 #C= `B,섴a,Rx~l&O,=X)Нi^F;1L;b U ՔA-fm> v2=Z"e|[_AmIc膩Ȇ=17{v<+Ɓn_7z/v֜sɖ5kVئ.\^meg#]*BQcY?0h5l"LxWx{;mC,1s.Pa;F)-=:w ojDoLӵM\@i]Et"5 NZ3aov5Jɓ;gӺxVՁD+H qfadP-C\OĿP*YX5ZVserUvkOm;nk"2؜7EVNyi;\褾P:XEU %Bp$VWhYqQ|"_TNXUBT&zl7rMۺm*ЍzTIP[Vr eF~ YvBץ?@hSS{iU&>V_xS#ӲKRu򇉋N߉ĞmE+.J@?Ne#u5̝X2u$jV>[;Al8,#q(DKt;=as g].p{+s5,Jԧc;L>X~Nʓۿ\,a(K&6RuUe~fvU& {t-iBo13ݟt3 Ņbݖ c'jK)6U(4 \ҕ)es:Xߞ.arvS J+ZIW(^VuԽ'>RQ-V"y"[|9A"̌JTDvT< e}ۧԆ%=>zFtϟ ¸-'({.օA}JA2vQa{|"u#Srr]_z){:5Ǐy]3'8[4IftyrCĖM9]mk9+rKݹko3ثЕHlj4Tp?^vڮq0eRi.Œ=r=67p 06S`8Sr>v͜`<섀}IRv_怑̭RaTE< fqĶsvm JVVwvvKѴ[.+1cCpz}FYvR+TU"Rkc 0}OUß4gюC1 y2jr^MlZeCxձ,Y]#'xG6kvbH{P >}^bX]o>x64욬cVܿcT?wR̕;w E(.*{wC-7T+Admkip0*NPIN~Jϼ%Mv}66dzx.ؚޯ]Dq^a8-'/ڇN;'wH={a` qP o;AeC|Іv͎$0X ĎeuEZNrSz sHy 5{زF,T$\kfNpt a.\xƽ٣uyܜE#Kg i;7aWl>PhU3dh*6r9^PLO$p91 ¤e{-o}#絴lyğҲ=;4g8ϯ`i]}wy|.6Rqrfɦڮtu d2")l:ork$R=˸d9LtuKA`j12<\(FzAOq[ %%.IKaaA݆%li+H3??]Zg`KLԍJh7|vvrc ̙[6mȖN_d쐣ZQdž=T3o)$JۡTۢiwոbֈZ@ DA]#C8싼CRzETNyP\}Ht ,RݰkJ TY81FU{s31z'<6筛*4Ml˻"|:{/J΃f2E`kFA9sHw&]Ȍ"HXj5[e2 A˚~oאfn5 Shw8RR[첊)%cZzY4eYdKcXf_X~l6-5,Ay9A;vq6ZaI hxN#!Uȍz"MKW?m_[—>T|XH* ޕ~zu(]Ø`0O2),tK,K6lz^-ؒi$μܖ95zhmo^KM! ߝ^;-)G˹.D9Kf0 BKT,wVHt«`9l*ɭj-^>*^ o2>bYcO&ݵ`h(`,}NI ۪5E[c#*SYZSPFj&&'X|w8ͿB $0מ@uyrw'}|uzalo0M ;% I Gɦ#5TʌClF"i@Zs& ՗hNw9V/}s-jy?ߊ7vtx aZi)ZUv%|%G,:KT\e- 3 3&Ja f#bXnIl)yW&gu" +9d*p2E-#RKѝx<= bk8НVi芬*;}w^Md^AeVx`cM7y4;2*Ȋ_tCT\"&zF&y?ík‹]iqz{1FI p鍾lo;^b#z"R-mnSoy dS$4Tf;¤N*ȝR='7Gl\`RҞLyhܪ=iMS;Cu3Bf[aGu_+5nWd~U ] sotRnQ'R[u_.0xYxMj#}u}M &No:,SHѽ!pWIiaa:T~l%a]60JP_O469+5^&/>'rY1M Jw?u/Z44g[n[]jM+*ZkA^:A.5Yb`wPia a}E[)R .5 X]P ؜j>?{q^+7LA)3u vC9[x8(l#[w)ߢ)h(5%FA6>īgyKϵM^`ރ$z4m]֒?4 5M]icTsInQCa'Z.h@SAU$s{lE?ᕾӆ?#{";’kb@$88^~=jhN^D3h @Q[qUz%%Y){e/u^3Vre9}#Kb%uij)ԌB/HszEyCX,h=r5mMi*QCz EYo'9:S_xi􌞄H9wk='t1*'픯$DM݇.Zϣ(I20P^@C( `Lc#=uItNtSɕucʫaEOO65L*Wʼn4w-E|^cmY0X'L.vIa| ZLot20V5Zw^Iv|X^]]=sg0 %ET!7Tz@'&DjrƁ!)  ݔ*LcYhMw=^kKmLE=.‰v8,-s973|;/_aU{m\R%KRb<|gIE$ɤꡢP[Yb+}ЊK궥)\+2 )a%Y}-T$2)8)Iv$ 1%0'exӽoΎx3u*~̟GbKJRc#[ Ud ./.WK Req^%F~iA.#xo?>/C] wr)[ =*)]jU qHݒ.)cE=#b{pZ숆s) *T,,D5ԧouÃ,ib-)qPnl+QxYǼūJF':y0Un`'x?|ВmZԓ7.4}W*DEs"=liB(B5X&*$dD] C[R }m"àj\b@/ kH Ei VOR1V( !e[~4D"pB]b,rvD#uXqo²" QI?2L;u]mXSQb#r]S⢆r`&kςu PWMdQ 8˖HZpV mQHA)+RN8ᜱ5/S54-s Z}Ju:6: Y5 ?zu=ݐp #!C`i4Qpd55cR\]]3hqOj"I=E(UG@ W#xN^khC(ggmy5F]a"r~MMyԃd2K8 :j-7D;MPͪXo8D'sCP'3mbNbj&9} 0It`z%hsˀp*P]-jv k|N;SL}V+C"}5',k,A"8oböTCbжLCbEi[ʞhMOOU>l@GiJJ+sPUւܢsj2M54?6 D8Y]$26fzf9U5Ƣ+t`X|WΪr϶?"`]$Ah[nq%2l"W7u,81&)\QGM_[I-\M>uZxXO q4qB)|e0 e":L=5+tBogJɚRƫeun 0oP^ehdbVbmkXsA'j"&@-tJ4Go~-5J1XS CܤQCc6/!/J)tBP3U! {=;ωDZ"ٻU ][!᪋lmc ˺wmKSbb-5tiseVS@lX$"'Rqu+s_"L=zB5H;vrGHBoXEF5f9>4Cg!ԓrt>-iS\:4gx!4albxY52 U~Kii{N4 uŁ"oX=l͠X60diXW:2m/ Vt C^D;CԉLC\g{JZagjh:3J偱H#D5iNB)0nPމ-IȟGm0~-FrrkzNO$5n^-"-bMvš}jM45lpQrTU--cYh {Tk`4*m.b,ʂ_Y5e(֔fpu\f]s"x vAm#5cFb}TJC]tkb[OnΒ#YJPSِOqc[ xRKj99bӐQ&"iXB],UDW55CYr 1ݰާvE'BBvbBfӆsY3vIݚcVױb!̲82wYc}؊Cnq@i7K i^Vk:ste=dRirz"5Bo(rfUo^=(\\)iq:Aygm='=ū?Tu5{]l>rc, h-9fΚ$( ,hP3gY YWd9/wXߚ:.Hu!Æ6e-+ep`^kan"֖([s2e0OA$7!U=ĝ^ҩPuF]ީ0xcռ+{M {qQS]XVZᘳHޝJva:Wݵ{7!3-yƲ"ː cG )pHņ@!V )Z,ɖ-:3dIv7{oUynS3\lRT?}޺y>Gb- ZjUy9nU54hK]7Ă;w|+XRsG3`#r&E~ǿ C}|M tAJOR-uKqg=O}f‚Kv4j8妊=w%SvM&.(i)wۂ*.s"-]M:o/k)a[^UB>."}]wOqUGmhT 1PRRp#(At0#ѡ8S0O|+*D96=e[=|NvcϲW.~+鮚DW_jx>'}gKl;mPSUd%ljagd`lo~w=ehKd!nqʪ}EeTt2+};2m6hZᱜ}kmk~<;*Ǚ-j@Q| kz;ʫKM-( N:SZܱ_\HD_"?PwUͻy7%zUDL;i QTҕs:$\=碾{i;av!8􊚺u;Ъcc=-w|l:Dz b{Sl8N6P S}R0 HlwToSm$9޿mhFc5K'jaP[p7?鬫z~ӳ0і7sʜ-#s[j7p]RV\*JR-.*3BW%,ʹHgn2s*,Xs$Xپ¬[מ}鶟1$ cѐӐf/OyI',tU"f-hs*lu["Mq("sn5Pvm&x\U$ f q9omWmC00604RTh{ ·sО5G"#;^us^? _񔾁1Gr-8c!2NNM]EJN^uW[dUShĊg񌷐:{qyl[cgAGDŁYp :!D򛄆UQArVإ~-wCCκd3HlOp=:^qKES$<}4VזּW$/r:2gSXHSN{ц}9jIMk_U504 HQqMїTaLSYa;1-aaW_z[$t d%@N]lז=\MW*9PpM[be /./_s*&bM#-aEWB`GObU5%0FLXѐWw_xo7ͯy‚J*)k۵:.[bX]`nԱdmݵCCضnՎkJNS +q`%ZR S;tY#8=)n+4`rM7lKh8"qiM*}P ¬XMD_lglֲ^&ӿ%c:r%99C;Jz3lزaWǜZe]ëuFuy7}J]55yE5NhXsʊs 9caW*Y(5SjGDe%i2'4 %M;MP5cLOe͊MbIPx'15u$ܸ+Ն=:#nٰ$g`U+Rm(/o`բv 彠kۖ>㼪X]՗=ꌾ׽Em9UaåElYPn`McI]Ƹ~OL=ִlۑ%kZA.XƦB=ӈX){ޞug{3<ᗃ.o5_}C*vt@9M׼ꂉ-by5$FEc<^R5#sbXz]5 *ǚyX;|?g?W YuPj0)ReO` 9֬= )pֿ wυ-4uGj>m@ ?Vr[]UؒCyA;=S8( Jj>⤱\ gX4, PeZCF?_Rjjhk(r>:𮗵VŊrF!AVzZPE zλoRΉjCy*V2RUU0tm+1iwRk /( AюO[tՖ#Nhy vyvȦyM7RdjB%~}ΕBTpF)SQ3fsH_`"S6›ƲuIV1焎W̻5U#OwM_Qms:㴶mCCΘ+~CT"}+>/䜧]gh+PbHrdꥒzjG~v1@Qe&W2g>la$qdΡ6 *ozqB-=ꜞEw ]{|/'W壒# bu=wuQ5/$8>ㄉ5:+kIm[֒zLnsڒCcZ<朑Ej@ $A/Hu7h{&j@#VNIDAT5#TXlb /QoRbE[AŹKz'>Zyyg=jh*)[WrTݏ7FeC];wZ7ڶEl涂l*$ZzθBơ(kD32Zپ[9kYQ*S^QgџUURcg7b߶M-hPN?G|'= R{y/luey'o6~s ´mނ yٓJ;崧m+hT$ʪ3bid<0.+`ІH[Xtƶ>O*uywU5+Qbc@3 (o5іv'#ϛSR0 㷾@}$i U UT|"rGvZw[UߺJ:r汨c·;2T 98#b?^u[SuE#=?%7g *ZԷzAE*3Zۍak=s>^f`)3HtU=]r:/gd*%ӱ&oPyV]HZ՝0"6v4S|ϓR=hOCq(+RSǡTʌZSm.@Ibmbplu'=n;T0h\s.zѶUAK؃y 29BYGQ;/o:RZ NImnES`.h6zN!)++)))q)0d8/gfi*2ePeuVLfNIR / bOYDɓ6|A5%G:rUжkl$5tk*g$Y|[RwmNG1Pu_{lHlμ [4rUM$A]E]?L,P4⎆#]Mg- t9(4AfHۼ[tt ?l+#+T4Զh޺Wl`'K"P6IJZ5pCǚim*|}KudImW]2dm@13(hNbډBPZ)biͦ Tr4c,;`VLdnKNؒĢlJ1r2czxBTcNaeL\k.ki[aϊk>.yR9]ݔhd$6P W eڋo.)ۑfN%5$%-> [PeVfWm5dY(CN~&F<@ogN\Cx\,WSþ#𘳆iA"QnID'8ܫNj)giX^Cd6v[]7빠״ص`$o`ξTA_AHM]G=yeS@lPKߗWa7-)yF5JU'MkN8p`l?}o5ANJ5M=HGq!g'idꃒ;3Iש;jY0+A/ye+zZ46N ΥߦB7Z&2}ANb$g$&,sc͜(zB,/10/T%^YO{ejnny9ǓnT>܀lX7cIw캤,rڦmUM=|ej$rde[顢-gB3Q1R5R4 oz*eg^Nj)khꪄ&ZNۑ#r>}Ǣ_~P%c?;[QC _ -#ytJ!3FJwj`CMr茡}LςLZqݴm߼L}$5QՌ$ʺb}efRWf8PQQjcdI켓>gyޏb᳗>uG)(t ݰ,a=>fofqDT Ncw uus*8rhY6T*۟y#mj=a#ӾTr0q33$],y֧=\0ٿ$`YnIV8L+Z ):XiFSwFu)H7tl7Pe*6CLM}ZnXm7lY ڧ`h<5a"bѓG|~ww-whc)Mj2yıL.t Fd/;'gSL(khbddMj˦3A8O)92 t[$ DvYzu5HA|g%_ÒNZfc-i'}IlGu=Yobm㊱$'&ARv;hAkv=q| 'vGђLȻbm%#c"/ w)):>fuӛơ9A[ecuA's:i)U+N:Z4>/20TKUL|O dL}Ce±^%+92TU 9S *cYHMEN&Rrۢ[kyK:a|;x{7Ӿ5fpT-lUQIθF>+[Xxi荽gI;"9Qs$m aH٪ArPYd9KZȞeH)14TJ[jP^Max(h_:']t%ؗ󄶶؜C5tWY{ ]tG.'l Dl+)I#fQ"U XAݓ 5-؂;zKs:R2֐4e;%dX-Ӂ}Uu'"^Hw(+u7=cg>)퉜9%g0 7䕂6(3}Ra^0_ `=Դlڴ2S=(tᒉ#qB;7gKw]y>lTCrH5:uvm/w_PU^ρ508; DdhW8'2 s4+ڲ΀=0`ن`[bP$n[?ND)D&&t]uuUQe A7ȎÄ=2ӘBdM++!7kBʅaڂihFRP;wr2NEu¾`b[he;3ݰo5u}54^vʼO[~~~%A~[REUU `I؀ cq5&و* Í#_"¨*V۔,Q0̔5zN|Ikp)kXty=o@gMw:m|w$ĩKS4…jpeY9Bs_ U% [c%"cUUZ*aK/Hf~KС r>Q39C=vLm ̜LWuM ;L|)gݱg4Y|&(Ls3HFJ%JF;(r`l@l;4q:Z;YB5#r:-yFXW쪹0KJ$8jHz99e#Z@erAӰ#DM YƳH+OHPuWAflĮ"o{ maU*RT}w 7m؊F)Я/1`nQ(h$;4"D5Uҡ[8g^n0/ |^}wlRwXMÒwK瞍fw$elW(qQ&; 1 D'@!0;mu9 ef֚8k4Td7uāW<TY+U/k㷋34ۙN8K" x:46&ShI+(T |TMPԱ뮶G&FJRsb N$E鉗N::M$X(Yb&Ra@dnJx3jpB4>c*A0(^Tْ:kKG_Im-φIeȀJ'N'.Y%3!<5#}8M.5ND/;:&jZʡk7fo.L~*+  .sc1*:Fʶe8jYrU-*PO ]|UR*6 ~6fI0PQ-RF}3QDǾ84 M -i]ӮUyUMG2;HC-F{q,vNH@ƴ o?S{;I1f~h&iw|d jfx\mBs9Ūh諙ӗW2_&oN]0hQu}Pd}Hh#82jq+4{Tc"C gsHlJM2wNQ$(۲e#;kd}k˃R7~}1Z^NƁ7 miD=e'MBC9 P͞*dnM(tja%M-;iӒrmmYa`4W&N׫>x4o-Vv{ \h7 osr SNqM$ }Ih6[V<=e ΋RPѵo{U]}oc69pN0 E{j q#fPbxS|p~dS~_GIϺPO5u8 ɋ/;"T> l,TT Wq6 M !V BAHdÉyGu4r#i"ч)0W]zRygB»"?MQGNIn)f@+BME$Qj,S ~qZa jkk.HQEMUQΝ0$L!lٙWp:Yf.3C)(w-!6kM?àw}9^Mɡ@fq NH3I)gQ\dPԼN8N2(H@IMG,Z|X ˱^idY_`94 5HD>Mh[ּ@G?@LϮ]m]955z3-DTTҐdih%)+if a49$TF>u$ )dzvOuM8h#e8ã0*EL`d$"ƻ$ӎ-$:zA g|FSVT@l/(SR +avͤA wiQx~}m}]C%u5 B3f,=dΙCikl^PW.VN=޵eN_D׮@?H8LBx_E5NebF)*+I 1IC<00ťzzMќ̱0Sydzp :^n̒50 (NcaJpY J.>Lpn[WȌj*axqaS*q{eRE(bH[jhXL6k W€6lv)d)(=,xRE %'\ k|P3Jy>q(P74?lx,03qxmwH^昞 UT\]kGRР0~DQQD\Zf|AݠW% ޮa\xS%` zIz'N{W)BMDB ~ ';u3j\ [0໺ŋfϝ[Y^nwvH) Eh1űR},7zKRʿV64x+~ 1S;{j;{3e>eٖMlp=j/--2!1MkT{:|K=%__=`0O>wr׳'O>h֏R)gk4J10@R J"):^*fϞFe3S{?ࣛO)%lH_{#+o~kp?O_z/ _?7wn?beu0Q+ò,H)QVJ!Q=)UL"0>>"X(~|Sa뻷9>%&s?o8&j6EPH_ T*K0 ɺEvl6/CJݸ۱PRkp ?JIH))%|D$)X hZ8a<;8D.vz84 CZo}a:t7]Y{D;I ,>«T*ˏ# h)Pj iC*%5p!EJe@)8t\x^ (f3BΟ?e.nSOP$'^?l&Gv A+((XJ==> T@)a:"B5t:[-8M-l&Nvv?=o!ΝH `X?2VVVrZ]dV%2JCEH ZHH!0P()J:oi2 e>=ZH)12R* aYqɟ:H@-%?|!ܶu+* flM ة$BB)D#pLdJ@p>!χNJgϝh|>Ϟ9P()mݦ(eX]% J0M f0˲ӡJ4x$Y9pg4J'B> D/^v]3J0^?KD8o:9E;\ӎNҖ 23 brpXZt0 PJdJ3 !JAHs2>>}z WLۅX\sn,l\"I)9r07|4 tZз74#b4|.t&˲p",_o2K3RyaTܤa&(S"V\2|6J;e8x(VRbi }pT.X2@hJK$Gc\!G @Ӵxh80::B,ۂBiB)H;uǘ i=%zh$p5[*%D[pɋRG84Z &H/T*L& 1p!eTpQi/^@ DƲ, !`l 8̈,4~mzi"+˶m[4|W%CrKɩ]YB}ߋx|֓!lB~$˲c v*D"'&`Y6((5t蜜[C&Gk4͸)LLL1]b0ZWyq{/\YHfqWsll6`0f"N!N#H-ۊte`1W| *1}̓'~>SSSRikgpBWhC őx7V`Zv'@BJ2 ,;J)8CX+JJq˘=7̉{}K"@fdğ9v+ur] qh[*Z<ڨ^%aL x;jGJuwrJJ%ٳ.Gө='kMУ$@066ܿ.abbTKpF[lɏ ΄Cc.t™a" "wa !`&>##8|j++=wVMnFAi  ymؾmǧ{Y,//arjkJc2(@$S)4 MEb;! y.Z-!C) 董p]?>, _} &U=NA~?}>8G}GK 2!\NT*+!ha4Rp=Y#k_zP*|+Sq&i{W8*Q|L@IMCo9D'Qqc }0Oa 7( B*E,^YDq}{!⥋8={ZfҙȈ4{l}vsVsyJoxrBI4E-,^]幹ѨTM7Gw1 !!lZ `EKF%'y@͑ .` (F3O]*OvvC;twi*zCiu#=vRO;ԕlm:i֎NuMoeוPNiۺ2EծWa1mzHp\v펋o,wԩNBv;dUTԼD+MLia,jS՗"{^vczjY~$ء^߇ɚEN]V{|"suڳmI. q7}:̏~qxBw7zqq}ḭ۞~If.wt]Nk2F.BP/~RIK[aGl8_';GYe>v:~]SgYSYuu2۵6ٶj v~d&lhN7yjwQҾ Nާ]NRY; 7?~Y$w F>ڰs6teBۆ~6knwcܺIn$cݸF^w{N_^zUVw?m駬&k\?"su鸴T8@$dYI퇢/?&w:}t ~cMۘ>GhڱPzܰ:ծM{rYuk:,iӗ-zܰc8_Ym"?ܺak[-UP il$w\7nu}memWO'tZ>߷lyG˯ßڣDuEMvX6ٲi\VVvt]]j)|[WE<.&}m ܒېiYu87\Yeƹ)\ʄdo7r/6}h[GtM߮x.|uVٴqqVV]YӇʺ&k\_}m+mx'hWgh[?>:l8W8+1*IڍNϪ}/\tniөqPY}vfo7/\@ JÍ,#7 ϙ4krvJ:)kqA럸OHpf]&~Py@yHp=ܲDqbf.pA({_l>?~|xM6M.ZeW\I[&Nc0(oNp9Eyk 7y)Sln9@#k39 `p .` U&\<fyیl]wI.D $@ $.39Es;#yoIG)'L/JmT]wKw]ʺ63N'O4unڴipђV.rJ2i }< nmN}';îS0Hp0Hp0Hp0HpFyC.39Es7>/bY?^&O62qȄ 6[6.\篻.e݉2Eβ`,z*N'O4unڴipђV.rJ2i }< nm=Ip=ή;'k[hY %,JM .(@RLh51f#;ldyN @Ր .@NKB$`#Ӧzk#=0rF?gs%\@  \O'tR_$MRFn% n0HpTcz ֒@=zG dul-0wn;#f9p#'+,gh*`UF&#yw4>F12w.AܺឲEiW*Vg"yHH7[n#b $MRp^]md|F[e9Y#G_f4JiD V&M2y[}Fv9#g6,2 .\ ?gnsQzoa$űV1~QmV]h'S&Ȩ8qW]Qܴyq%G}l^vYqzX jI YٿWăy=t`[ո /"^d}':7m4YhI+]r% n4u脾_6 +.$|t>Ip^yܢޫ7ڜGily\~I< nQ< ."^AE' dul7Ȼed=)ZF^J#wȌFf4rFN#N0hB;I~(lMF^#;ldyy&GYBBxիFn#;hd}̙cd\#<,X@7Nigy6ی$GeӪ vx&F#z7Ff6r)Y;Eh##on3g9P#'X|{@IԩSSO'|9`iOSj5ϲ:l?WMa _/fN4IfϞ-O=TAXFڟ-4MO Mjedʔ)ͱ"Wŋ˖[n2ܝ^z(R~+4mCCG*zp]CvyuH;*뭴+4yүkH~H~H<["(h^6tE ߏ+4m?bЅ>3AhWhN#дu*:3AhWhN#дcܨ\=j,Y͝3g,_<+=]&xc#[l~uϲz;̘uGm5k&>tY!QP~Ch%l=u2uҥKod͛'O?ts+^a䵯5l_.}O_v].&tS#o~N;5Oz&^="~2,Κ5yOf"\Mv[oWoVRod Lf}'`sHt4%&hܹOJv\dHHy$K#HB&Fݿkd9Fq22wmSl|{9`#o9E AT'e@1Bބ@B)7P \.X#i ['eIF^:#o}H`cqGe(!o4wa#{mdl#o($MRGh0hB˹@B˕Bw_#G@)_Ȕ)Fod݌xc5rl@0 GYVo{m5뮜e`xxrTEyhHyW$w-"&,ɧuW$r]-u:ɢ[,ylYOɪWɳ'?,`ظGRvaֱQGshBn?ۍ*"E&N4ׄwN;~~ мs@o^r#~u3SO.76FΑn~O.v]>~62Fv#ӧsO#b uEloxw5&O6fkvBGT?Hnlcd睍췟#WZS7aud]ەE=- MAd֑HnɵFr冑\:!O緁 ϞFh{Ǟ7y{츣}엪"h+:,+e;zF^ ֜9FTO9pC#Ӧzk#=9s{.&0=[#H.cd%r}XgLxgYj8vnF9c3zOd .Zз'xgWF319wa#{=n)@PiHpK_zBH([ n֍BӺQ6wvr7]h+^hQPn v4iП-Xic!7 M;VBԇuCoǑ|<`$uEr僝$)ƍ+$ߍF^i+4e6FMvr7]h+^hQPn TO{4 Mۏ ǏόBg"дnid'c#ȑ5rs72u'n 2\H w~/ӣ0Bgr^ɯ~GrqrK$W_} P_lۗo$b~7G;E %Q~#E 5G?FEDÑyg$KDpa}PM(`Zg#old-ּ'W7y{H~H|2#H-䢋XV&PzD?EGFDti$_WǡǍAF gk$<SOEկFr=|s$l$4Іōv=gd9x#~c?99#g#9@YvN?m䀟3FCm__ⷘW4t?iճ h&O6fFod݌xc5r|E1o9x[?7֟?ky+Gٲ^F;I' E g)tO;{?wV5wegrwv?Gy,?#9:裑uW$_B$L$^oo /\ ir,t.ɑfFch䔿9F>½eP7! ?~UZޟ=zzϑ\#DlY*\#o#я"7"Hn-kKYlR?i$v$=wDxq$ Q!7:V[쾻2rqF:񏾽f#FnRkz>zP/&y댼F>#b6rF8Y;>o߾kd۸x;'#sWwvnUL6!Ea>-#34vhvTv";`䮷Gr["\H>Q$"OUl"7}#hv Zme`FgGa❪?9kQ0iyժp{ iF^i@[GK/E FBmp#дn֍* Ӎ:{ད,:/9Er\a$No/x|5/q|coȏ _Nw@v߻#Ye$1Drͫ#bH.yy$1Ff^%:ӳ_L!9tKztV{Z~یl]w1EyHpK`IB7(R~7jyu#дnTQnIeoCzC]-?=c }7K||3t  ˶^&|ߓ?ghBU@ To$K$E$H7ɕWrj =O:o z8k޲'eUؾ4~HK$%sC TGqH;"wFFr|u,8"hb-8`sEDCk"&\&۴ї<0/="#O"ַ"mDruD[QPV~o?ݑ׎܄ #7f#;ldyyHrzqruSX-'дkI^xW]-v}iP;Ȟߏ5d_5r_}獜l;r;rGqdmh$Hh}_"|#ͣYguvf; (Ol0? \`6n8Sn=ԦKֻDy5rns<}~ kY=wuT16{o7DxH>I$ &Gu]>?3_r͎Re?!4oRPz&y]\jj{}m[mu6zx;m"gŭ"M\?y F߇il77>Ȟ{9ƵރNO iɩMOԦֹ(X~6poDnUyjSU6Kc;dJG)VG)2}pL/Xu׃ϑ\>n+VD|ϖFȧ|vxx6:]Q]G$5vWS[.|+ݿ+??$s4KU /D5o|#_sAa7Ɠ iz[{Zw[Yq-<^_Ͽpb$Z.aYxoy!nWz=5O׃&ׯ?Hn}c$KDr"Y~$ɿ߫'~:-x'N❴|'mܸqkw[Ek_$^aT  ˶Z&|ߓ?gh*Ƃ ~q'NfFْrԬ(aLn"/;$F&n=u%5Tqq^1/=? lB[#CxbصN[-tX{: 'tX!]Cu77*{ ]>,'-YH==RQ5k\G6 vG{ )tB;A0H[fEIyiԟ- rZ#_xw:RC)y&e~;^^@=(e.g=bwҪr!^ti$_W|Uzu$u$O?׾^?M ֻْ|\r&W[m&Otϲ#ܒT%:=RQב%䈘0ʑ$e'y-5ý?ܺӣO)jo~רN{:Y^Q VQztdNGU,Gu͖E=Cf/F[5\Qz3tYt|HZ;SG!LET;`yz G!ƢPkz=xr¹|,T?[ZtళdnnҗFN#/]?bTI/x晑 z**_׃r<.=4\]3z:S zܟ̥Pa]ܑq=8 ]j߲*Щ:<Ӿ4xQұOcd㍍ F.#ood,#nme;id쵗3rIcw|(ާA6E&Gg9:miYdκG/yyt[s>,5u;dMݕ.$PifY@oZo֝Zwj@\8yI<[(UUֱvixGu4TgYڲ]rôd޷ ׇ׷:s;vݩkrs=lOI޼h׼?x?9je+Z+#&2r{ՕQzm+Ӏy20ڞӺtf 7N[:|՘޸'"yx~9]u*``,'s#=]/v׉ם-n{uϨu'?,^cd1-TzӏǪZ{LoY5䵓fYVv(vE#sەdbl_tywyuOp"E,z{,ځXtӺ[l8y}Mm4:yuYfo͢,s{ -Jݫ۱OڿmigSN@E',nY8RQ^G럃; q@C0Q3=j CUפ'{ѡen˙.eQ]6;Ey;Es׋B֣E>֭')pL ϙ4N_IYIPurv^x?$.ZeW\\&vR2&0l(LJꮲ:Y1Aü.m.,`.O@ .`  5uy]\u9hsy`(m.tyf\@ u?jWP.\&nBE $@ $@[ =5tYэn#Tn߻vF6e˫鲤n#ve;+/~Y㬴Vp sߋ6j7~ݶ~ ڍ&qVpnmrn|7!nty{fF7.KvNٯ?k\^y*!\@['"@[qy::.KVvXn+Tggʫ6:я6:ݰgtY:"?Y:ދv2lC|mU Pyijޒ@\h[*wu~,nBj6E6]~qU6g&ԞP]:܆U:}/Ymɫmە+Ebuvڗ\rMyY܏)YXܭ2 nt_>[&4ޝ,ulcis9Nmhs9fż.:v66666v60$_mG[]jżn(Y|׶ZJ+fՕgN*Ge(vڰUq^|Ey]'$= >ڰˢm ʪn}AeYKYB6ڰuҮ_eHeYlQ:%KYmF|o.nt$̏tCMuⷹ:5Looj[UsZzϝӏ*?9lwV:ծ:n\ڬUvq n8.DXYǺvgns7hs{dK3m.mk }NOUhsNBQVtW9!-usFnsYiw>Q\nl盝ΆfqE)e>.}nEE9kE/E;[ ;>6Am?~~VX;k\:w\Ue5O6z5p?MmFۃv_?^_emӮ?>To:p6MB{G;Cy*wZWiB Kcn\vezepRp>Xv?./[[vӄ^aNThuqyh7ar Mg~Y˝>\itǹk;}hqYqǹ됴2y46|8w_*c뵑W48w_mӺ k]}~YN놕6iqPY+mW@ $PsU:-(/\:Y1"-A7vu@3AE?[>m.G۬n .`  .` D&v voM&/BUw.Z"?gn|ʕ# )No'N,<e@hY/e.$@ $@;(Yڍ6̏jI[*mxn)}_ylqxWrYëmcۛgNF8|H_4ˣ]y2n0HpKHc̨`xSwYvY]ϓsC "?ۍw#TGZ]y۟:VO󴵨vdDrM:Y&^~jSڸ>,ܚ }ڽLg×g}tsw}8?>:O{VϦKg_:) Mƾǹ|ߎ}O:4N]ny+ThX6Q  O[ֆ;+ZZ;jCV=yǹUtt3sC3דֶvjm睲Ӆ>_8kWָ|Xnڤ҆kjW>Ѯ|ZYwFgu# uj+=1O1[0{֑"޷[ewO?FQӯ'^ծ ygL\ !vvrKgb/5m'v; yTT4:v}ZYw?ΧLNʸo:Nv][B 骆QnW.KsvZ^i[{wvSTn5δCqptڪq?|EgA/W?mRe}@HpK֯phvݩ*׮QE̟t~u;Uo j[/Vu~uӮ*߬ߪp )MA[2Pۍo^7~}rm;.4^[6zq~yDž4Y|aV6t3M:ynۗ5?ve-;]ןV?Sn]y鴼KyuCmۮvEUo^YeS$*}Q:C;tF|t Wp|4ʎKN26qӆ+w?>meǥϒ5; MKƕ5OeӵF;iӹie.[.\UzBv,wmBT0MgwٺlBʦse:޾4ݎ k7B҆rcuF'xÿ̟rt3 Sw)LbD\p9sGhDv\:)k@]Ɂ gYhI+]r %6u#-!n<{Cy&1KZvy?"$ Ҥ3O *:ÅFv[ddxُ9I#se}$ @= z! !@= m,?!L8[_l>b?>djf\d-7 KrMP'ed(y"" @ .('( .(܎6FAU#?mgW.нfp"P+;_ndwC4r7c#G^OaF $@ @+<[wU է7|3r@|jB17گT=/Z0?{^:vY\>|;HVj>>E}DAyA.gϓ6QQ`MɓGOk=E' $@ $@ `hIyB/%pt_ڭy|gn|@/ü~ym杻%\~C\| bt=1F9FNWskN8["=?(~ Eʒ ԅ.#{y}p杻&8[ߡ~. #{-6;m䤟9F>|+O] u`(`8zl%#~Q2rO{#[E~PÈ"aA[V(Wwg+̺K#|?7rƟ#-4 زxlExl3bc l{zC4r7c#G^  Hp=,@Ԯz&4BI6`{SZ:ZIhY#l{22w%ȃ^"{ SyʆV,z՛fs۳ٿ% ?։gm֊nlzQ.k>k{vht{^ǻAk'Sgza_r_s hC{_7V/2;W핶teq3yzQ.SӞ7*H(Ѧ:.<#=+0}j貤8EgOыrYPۓ;CmiƧ7\ J(e\G OfJ Ip{ϟ'm<r$PteqUM'O[w,%&ˢy6Z&ġIKQw~AM'Osq @WtøE#<`ȯ9GF9ϗA`i'Uh^QP# uR-M Wa97xݯ6-F_3rFN}-Q>cd/9.#5rFN3 C_Y#גxEsgy8^|'^,^1^ Pz'+Pм GZ[(qHzV#gǍ#٫Bm ŰWF^-N= mB2kz\"-Fwcu`4lO L$AEBY$z2 UDBVh Wu1[@=0od%#~Q2rO{# LQB]?z?TN8-N<lj7q: s@{;E)NgE Hpv:ȫՁEI e;8`霢BߣC \lw9!#GI?5r|[~X+,}Ge֬Y2y nԩ|9x:k5w8N8y]'4=ye9N^AZGɲȑ ]HN?~7~7c#G#zQHI3dٲes5<3^{ɂ CPWӏ0rCӏ0|ƍa'~-7uI[s^>%PH;o޼qŊɣ :a?/wB]~QPn>G(BvyuBtBǍ* ӍBӺQPU"4 Mۏ+4mWhn"ph#N7 MFBQ$|Ӎ"wHnWnUfgvQP: }7WԪӧ'̙3G.ޔ{{c}HZ)~7ݣ&8뿊npTW! )SG5gΜюVnQBrH+4mWhZ7(N7,yܨP;+4E ߏP={=CG*BӻQ]xG*HE nWhN#дFUF^i(R~7(N7ߍ Ս* ӍiU賸QE$_~yZzzΒ%Kv#G4OܨP[ݨP;([ SHSO%NVZ\z[o?~Rv0$CvwFF^i(R~D>yu#дn)TE FUF^(Rn 5/ӽ괽h IYYVmKq}om^cKUФI䩧jM\TLJw9!#GI?5r|VMO7TG7A! ϭ&[ou|ժU2|yGH Ɩwݮ2FFS~a J>tKFi9F>7~kprm~ܹs˗/O$vȞ6#<`ȯ9F9ϳQ! 4&s6WQܺФj~ IpZV^=-yj =/sM:Gr/^,SLIuEFv_ddt99I#se P]IիzW;ң+VH QraPh'Sugu=eAhWeC䥗^ʌ"w(KhsB4]3WhZ72#{~~_lv #'ԏnZ Kp̙Zிzyu0v6#oǍ#~cǯB\{e˖%Go5՞zdx ntjΥlRr-u. Ip큞yZ=kK! USNM\$W0ܞCb.^XO)&MkN5l(׸q2@oB+7]3Ї̜)fzsbN;MG>,\=-y+Vp2 Пn׽m(>K.m ,f춛sbfsbN9E̙g6K PR(__;`P:}Nsb>f)P&\@ trY'eE$տ8-qSV_r סI*Hp?‹r`@=G]v0HpP_${ͭn'Snܮ,\$MRp^0Hp0Hp0Hp0HpKEQ(K/ n 1I\ƍ 0Hp0Hp0HpKmF@n,$%6Ac#tk 7 \@  \@  \@  \gm{vR_]p9s&jCTfż.m.S^eo։~醭?k\^N˒.։ϒ5^dnf+TwU~{4e[g'kj?+SV~̗^e;gKimR}Uu>iWjwT<v֔.@(<\UYmŰ|β>)GV{Y6}u\NBgs'mgh׆NT0VGrUI۬cw2V+~Y-掫~Yf>46\^mmjK^UlN߮\.B۬ӷ׾6Dmʣv6*~-MQm2ݐnHp 1@2ecUMQ6wm{UDQ6+uשE[oELEYoElE[$%8MָHjs?W-uCmj_Us]U:TZ]Y百SNWgV=yTzgy7<ӇdmWOuPND?,CԆzXs~_(e:!]ц}]}N~(nmUVv{ .Z_ڼц}]$v|R,E*Z0gUd)Yj3|.Gs]svL#-.`~nF+ͽթa~{'MV۪:|}~|6W6=aEgUdWy֮vuTvrf}^:G7Hp{pw!?mX;ӌuQF#k\nsQl[Ost2}NB;U6w:}܍ڝUϝ=U xuͱ nIF՝ƟVG7TtMh=э"2tf;t60+Jm.S7muusF/~(Y^F/~)vzla׭~9 n;qƲYӹb,ׯ~Nm]l_ins7:4,ƟnvezᆫݴiMzn;.?iW7PӺMj_hX[ޏv㲴+՛U->?mv?./[[qy:ܺc2iz֕6y'O+MInp n$6\lQM|oEՃe4A;D.w__ָ"af>ǚk82O/kv{YD{SþoָiVۍ:0HpLjn=.:o9W҄ʅrÕ5Ε5*gÕ5Ε5./z+k/k Sy^Ô?<4e *6Ε5ʪk[Gry k7B҆zlXa*keDžƧ]\SY\YNg#M|ieCaڍF /#.@E / ,,o@M2lj?YY.39lR/aYfzg6`^x; -i+W$UaP_oNpkwrL̳֣~iBe}Xt2U俓4?.,@`Z%6췬DZqcEC Z]&Ynh5Sk_ۄYk5I=6.-۟:Wcfh+y}t #,4#a9;FNHb !Ip7~ qNܤT_}톛28@5>ԏV^|30?~|kpm7zr\^{SO\q^-]W;} . Gm9" 2MA'S+2'{0o)@73>aC \ld;m䤟9$uk(ֵݿ;H-4::Ft?c Khqա߇SFvȬFS~ .xH>hΗsFC4r7##~[^V&(Or|#3?od/9FbsD0BP%si}1_L<9y4#{\cd[|#7rֳ㧍Y#b{㞊yj8_dD_9!rDN-g.EJo9p"~O䔕"gY 3.\TV8F_om6FzJ ;^$"}n98{L'EۄBzQ$vɒ%sU{.yfϞ-/n>Bw@o=|uQ]5nY[żltf׊_ϟocz*k=:n|Ql|(C5YOʼnxo9c?zik^\IQ1.V(kk_W]Mc^ Vu]oնZ6nW^ IpmBl2y瓄v;Oj+4i^YzxiFu^vei*S{=&ϼ.3݇7>K=zȽGO?w B\=rN$SLIjx2itrq:SyT'tO "z ߙ?4?1>/2-rؗEI?93 Ipӧ'5]ti\i;LU`4]7nI#^id FXfG=#'4rƟIt +6A+VH.0^jU[}&zZююZ;i7;i㝴S~Z߳@Q?P?n6r=# Kp;u`{P%>̚5+e] 5)vn1xُ9 #tݓ'H.6Gn_Eu"^^׃UB֛~;_fdwC4r7c#)*$$vƌ}p[iSz  C ,A#/@.Wu9+FNusOY}o9#*rS"j087]Eыm666͂9$Q].2:o9A)rEN]Or6 lQ=/Ίc#Hn-dі̟X/֗nڑOkq4   Lpⴌ{z.k\Ѵ^7_RHkFvqo7rhyUr=\ˋyF^i,yݨP;;Bӆ"pB(RNC֋6B+7 -D4Ff`eF13rJ#g* Eh:7(N7ʖ_/ׯMxXly>UTHO'߆dx҆l|"ӨBSO=u{f%&YlRi_Be E FBW9fw%ZupDn_1B׍* +4*ϑ2u4@=%L=ϞeBMDhZ? MۏPگ^%pFhZ7(N7YQE$6]lI~+=4Qu'xئֱ'EvRdE\.2{ȱ9"gQ_V ΣuxyuC.Gxy300H$ɝFr˞,d\~Z]'ָCOa#ß-eeV;^2"> r7DNiFg.okwZ-7 MPB\{A-ݎ?* ܨ#mndvV#gǍ#+ðn,]W|=?gdۍ#a_᥿@o~8 *#kksgd`Ǫ|NC9ooY'~tbV>jƳ'A]vxWҁTTWW@{'W>}'FHn98׌uH#"⾑ܸS$&e[D{v2bŊUw}?޶!krǻ{aS/Rf֍AF^iݨ$ap;*4d놪=y,JZn)w;/qeF^idi wn uîƏ.]WhZ7 MF^i; }?^hQPUjG:BPTQL0 IpkQ;b!t/̾0#+-N#{o# -~߸^U-B\o0Fȇ9+Fn?s ԉn@'`N!"0|ӏ}اD׋ ;|JdDQ"9"BlBebiFbvwtsȞ׈w! ?htSWuh/4(.;.9gT^;|22A߹"^߹R̿4 #@=FȾ9^#G|O9AaOsoYUSF Mһ :u7-a?3.2"#d䠻yqOƑi-|/.s`\vgˠϑ\Zd\[b+tN }n uts"H<$[dH~{$ 7]t#дn #S"Y6;"ݑ\H.}eݍBӺ^8[bnBi\1B˟u5H&(]WhZ7 M?mH;1FrḦwDcL B1B˺yKn5ꏿ#e?L h@% Xhd%F>|6rw3# `$5FȜ9)#sWFq2P $sk/m@QB˗U-=tv$Er,`$*S}Xӣo&:t3"Ht@$7緍d"M8{"`v0&ͿeӍ^y ^XzōBӺ׋/(W;p#2`M$@[N._~xneINm:=n]"}|歜Tgyu#дn Q=B̡BӺF.ԨNpId5@Q_H$C#uf$KfDr;"YzʲH#"⾑ܸSS7Gr٦GpQzQͨ,vr; lߞuΌcm~x[x[e¶H$(Mh%0B7ʦώy}Vt#}w! \(^::|Kw֬Yn*G:3l8dyfڝJ\H\H\H\O#mGh=-r"w,r"?(٭ELk,W>}'EHn+뷋wFr\1qNl{{^kd?:gSH;gYlYUh"/(iiVN Kz;{_E iſ59rOɵ7Er -n'[qyԓdePH;cƌQ;:ujȦE(kcVt (tOC { Gr.#j*AI-;tn͌wf4v:u a^$ˏtM$WenE6AoTK! n8Ai?+"#rWEJUnD;Ed"~{k-rD.٤ C韣Iq|~ ZIkgٗ6Gpu;UnrwʶoؽKQPnԕ\<|N$ɝ4+:=/cyGFBQPcy4#S"Y6;#ݑ\(N^P[;BӺWhNBtl6ty4 MƋ/6"?7jC?l6Qg%ڹԜ9sdI2N4tzU6mr^[:a_62MEգxH r];iҤTS&;cu8'O@yt}Kq}o5r}Fx?0rꯋ۩}~hّ{|$w'{ĉc86FxloWZϊߙ-e-&YюI^PA=UH[}={v*}+VH݉FvȬ+=# ~OFAU#=edwc>y W?]h`lﰨ3[]ajR/իW'_>k[y* xgGh\?"r"w"r˞"K\7VLl~ZMh-[&?̞=[k/9dI&%Vz4FO׊_(Ip[l9-|Gζe\C;y:XƏ|g:q덞on -uל2S϶=]0ߩ~[#Ʈ^NێvNDB;jNsIn%w;ns][˫7ΆZ9mWv\߳F-oMpNzSzgds=k(.H[qȗ®"m+͊Az,{ wDmoH反GzO'K! ̙3ǩS&I)Sd-L\efdmC! ŋesMn.]kvuKMг>,~` hs s 5ԟɳSOQ&]~ECZr\,GU>6]lUCW+"/q=#"Zd3jP'$zVM:5Ir^zV\eXJzh<%e#mHn!kɕoӓ d.꣐W;>}z\[޷TY4e=eЫB:*MBӄaYBUhjgk');hQ^0;N:ܭ7Y-[zƏ:QG c=Tɽkw n%P7E7M-)W_w޼y+GK^u'K! U'e˞ Ip@Ju͙3'yB\HJu^pdɒ@zv(5iҤPI^pzQ[-[7= `P }p~i5k֨#zOܩS&Q_o]'jR^Jo$~:=oWZ '><0=T}ꩧ&ϟ</]'=Xu[{&@q`襛y:գMn0n  3FvȾ9^#G|O9:-ROL쵸zͭeu @?t} `JO=kq5jK,?{ܗ? V)Q-󄁺U[o<sSΓ^9@yBMeZjk[%kpTdEfK.mmXzurZ2- u2X" \@ >=/Όc#ҁܴ[$$QZ% Yf"i.P[6MD/>4Le~AkU}pIpciXo/fż.m.C2]7ki~)7)*ۮVGp5tzU6mk@y8E0HpiPO$@[ uh7gqyu;]vmtv<ݶѩni'MYv,iȫ]Nm_8+mn|76mB\"=n ڍ_m!iívɯ?k6j7n6ߍ<:}fqyu;]iueYэn˒]oӇmWʹmH+5}?D8=/醭?k\^N˒.֩~ ՙ*~{NͶN7lYut:]vN`e}qN"]p;,6[>*mk)Z>>Y|EVvXgc*y>XJkK 9ON_V|2iXtBWyjkU?Go/sI9[RyrRϐ6?<;i[>C6Tu^wڦ*~$%/>;Jf 6}Z1:mџm6w\:m7Y 'TjhUlN5nsV[buvnwfݼe>G\':mSehE9m~Vy>mijs醬6weE[]– w/K۬n\:nC"\:Y1綿Nm/:~+e*:~+e+: -.ie(EWVWm1hsV:C국:֡ʪ?jWOuN=3}Փ={>NYu>$lzgӇv2}'Qgh6w/u2D.{^ n¤6{uCqhþ*[kuYmeָ6"i}h"?gYm.RYmy>"ۭuNRVQ,s9> e: nt,PvSG7z]mN Ӽm=iV֮^s'㳹"۝վvk:66uձu\>AۃN Q?nnsƺYf܍2ڜYҌubۚ}Bt:}ܩ*n2~fūouEwHpKb7z,4>Q\nڝE뱏n66faG\QnsiϭïKG7z6Cm΢G7z6Kcnymp_F5şΝWcf~ v:nre5OStmGסaY4p+՛7\MntiuN2PyGnPB~]|ެ:ly?,yiqy:ܺ?܆e婳4v?Siuym:>o~<0\vz?ڍsqyiBY8w_rO+5}:qxN.m\iqx:$Li}: ;_zm5;_[nBiW_h_mӺa o'kw;>TJƕ5}? .\N ʋ6mVk@HpKPǍ]]7rjVGQ6+5[$@ $.۽96m WYh|+W$rB2~3gNS &Nؿ E't2$@ $@ $c,tx(B"M/eCڰ]~ҤaQ+mxڽXZ{wINQ ܒִDTG`C4!~zڞd}~"E&n%ʺ~9fuRo~VQ `M$%kY动#D˻ ˩P9u\iBa / sLJ^=\@  .p|A#tseBIY˟*O^h>EKZʕ+7%/-OYg} z` .+u6 %:)k@_Y+k4WƿY&~oI5ʼ<.s- e׉_'eVit{Nqw8&,Ӝ&'m:ZIfǗWgL2n||Ksx̯2p?[k286sKZ/'4keeuhS㵾:Z;}󹖹oigdu9eq8R>wTfZeO\er.i:Z㈧Y/.,g{a>uYQ.敻,&-nEۆ;wtYtocitYtg7ˢluR.4םfԲz{4˄ő29E-ɶs5^w,&dl#t,ˎNh^ܡ$L94 z u؄ >\@  \@ ukna{=nr uJHu봲rqz饗 @=;n8 mPe$@ $%s{]&턦! *L ϙ~+$k4_ϛિ:p[Y:)8e֊ˬ*{J4;ӷiIoYigӖq72#mq#Ӭ3N9ܖeeh5>LAcd~?7erO\QH;_*|e7AќQךePR99ָ2ɸu2Zf.g_^28[nueqt=ףf;޾n.g߆W}όeQ.oRnueQ2,r:jġmXwZbů;],|/߳,ʌLo_,6Ei\uӮ8{-nԥ,in-2ɰ.z10oZ5,eo*7R>f-v=jE}Zįd?)OebL2ܖi6%e:^5FWф;eQM{qqZxcxx,8Bbϵ.ߝnmmjeYlgd^Eٴ|e1fLeΧs=uwYl-;M>[, wLx}nE\eQiLc;M7:H]NˢOG>[u'bN62M'ˢhdY _;i /|֝rٕ+Wr0Hp0Hp[l";MH L7o8ANZW6^oS` @ n x饗2́9H1'$欳Ĝ{ /pksM͒|$%Ю{>7n\f,$@ $@ `d>&yۂȦ.s0hHp>e+e6zדWOX[&3^y?5KP}0^֍&v9[{]l76PtlWf:AXw˚s$(LcGnQ;rNXGgG2.Ŝv}L/ϊf)7frv쓟l,gW_-zcsb>Q1\ K|3b/nΑ0v_;r]ƎR n (Jz饗24u#yk7wje 2uҺזW=NmtjiwhL*^Nׯk+.m׸̌62;eqi0#qOsb\x \s/>t̙#Ĝq\̧>%f粷qlj7QQf".r$%0$r7.3 \BbaF׬/ &fXW^Z2a|MWMӏ[[^V>ow*< jm'G5>2k,}zh͜93yt@z_=4"Ԏ9Q޹sGy?񉑣|P zэswʔ)ɣ^3gNh/$_~yZԒ%Kvi݁8^_ޟ^FOJѶZFs-RTS IpTdV;4iRs$u=Ujlo.FNV=+駏w^nY Ha$(?igfzvfVӟ^)[;^> TEZwMϵ(بz]~>>>%D! o.]*WcLڿ.*B mvR5[ Q<=rەZsɜv[+`Xq6$j;~@ɺs-֝(F[OB\߭l;:Bz׿53:jfNeޓyfCJ6qHxd'mcI{yk'm,EMNeq{q&#[麓[.Z/蓟c%:xfnj{2z4b VH'Lե34Ȯ ˌx%WO-_9QW"lxY۷5ڝ`.Y) `grVԲXQ5E,&uGlݞUGIﶡ[iEӯ SkQ`_b/#~BCaUZ9&5KwԣfقlkȹUn?)(Q[Dfr@$^&D*W߮]j^+V$zQ;S׈5Nc:na&N9 /"Cqշ#SL.,y~ PI6k 1=ٱvJGu2Rm4SW)0{kxy}ScF+QD%:fIIz'GTSdDMt;;Q͛'QvXZ]vY41=ͮQ&QM;]>-ou+jh;+َ/Z؎v3 oY5o^Ty:.mvJl$h̙3[IV[m%[ne܎WjЈ ۮtx"5o[:Y8Ezcՠ M>bI0| Ip'M<>3z>N~yQ];yIQezh2?H[Oneze+Dz v2{mGD5o~Syf{y|{"?kv; S*\ j\.*puM7&eP1]^ud/SeTv]"_W*o|Рsׯ:Ϯ_No}ˢ~첨}Q]HX1C̙o-N mw2ԺŚ{> Ȕgrz2nu=c#د(Q! USNM)Zjxhz Bŋӓܺ$W(Fr*REOT<(E{QG)Uƺӽ#Ρ:>][^CP{Hroz($U+VcZuv]3kոYTx% `8QWGIG)UZwrdYiA{ՄA{ CEБz|t2q͸ޫ^{wpxr׽N[*}.#=$^k\\>F Kp,Q^ہ(v u͒(yżb&1y!NPֽk<{Տz=S@O38~\mEsUG.3~Q=s!bN8<-Aj)`vbڋ[ 5j$^qZ5';~z+^!浯]#H T]db\@>~NZMh-[&?̞=;\K ZzuGQ< tGh:눬ȣ\;TIʤǤ0'3o)_L(=Z&mYl\Lu 'YxeѶG˄E^]n^x; -i+W,&~8=z;?5=Ww ;xN`#^h|º;0kY+0lghxetW2Z8e֎WF˵̲̚xegG+e(eFN2Zj^hxe4e|zNo\+%er$xeMFxetG2<\wy4!^}I4ǭ,:m -Ɵ%.k,:kROі\EwZo>,٦ֲ22ɲI$폿W-ˢYFdy7qzLal-l,ڜ\d.z[ˢ,n\lSkYiu3}Le17)b<,:2vY\6Xm8!Z;.\Gm,>eZbF{tY˫3Hr;#8ZfodYh.9<$NZ^VROK&#o;*pH 5YX$ˢmShYD:^Z,_x\`{l3ub n!(Z]7\܎Aw&{S0RoYuܲ @Μ93y:ujpVA*;~XB{$y饗2o-foY} ]{m^ظ?g6zy($]xL>=yɭ{[Mru0]qe|kMM nbŊ$[jUrVk7_ KpK}QY`}55sԲh'4L>Fiuxsz[oxYݦQbnո *u߿Q$YsE&@c$j'P2뗉z8W'###&W;w~T7\q8gkW^m;')P\{ֶk_'&&+KQ?0 2U.arJa,$@ ]'n39@/p0:Np탓b^ .`  .`  nh*iv|ᇅ`V'IώSN90 \@  \@  \@  \@  \@ 9sLZ~aa,$PJ@N90XHp0Hp0Hp0Hp0ķQUY6gΜ]*Y=ٻW~*jɓٻ\F.;55E BqC ?<4::kjP갮AúPjN\v%=?~z0Hp0HpwCAYjP갮AúPjNԙ0HpW ݾ/j5.Z$ u  .`  .`  nܧ,RhXNɫF':-#4mλr1iuu]iNEbuZH^6birOhj5nmB\w#>nZ ?m[O?B[w­?aV^NurQN̿v[>ĭ?a:-#oγ:~ntrEb|-ٟ~ѰXs7^Q^ -pIen@;M7Ųh\zmvVhE:ы:ia_4,Fo#e.U,E|FQ_HuB.Z_]Se4 ۿE~U*Ջҭz,lʫK խ9^u1TzFHp\KΫs]յOòUaRuYmsRe[u^ԭЪu]֩0[zWrIͯ;,dv .[^Yd=2p.[\TXu.j]:Yo}Eu?^;ڭS*]ꜷ>뼞{Qvp|UZ?֛:WuF?Yc]Wϭ?սz6z:W:6z:Wz60"hX,E)S܋zUou*EzիۺzZ޴S]3nNJ-3ϢĔoW̳)3ߢiŔ)tbmиoG/Y2mש z^}QF^Kä_myEqhvY5뺪:ˢa!nm2S+e.gUu.SUuYo=)E3zFs~B3GU:dv׹ikֵ_Nnu]yf=Χ*sysVzʬwQ꺮ժzx?zSjTUg=n~w?~>:Aۅ~z?ul׻hyfΝE(g\[טy-_:un|܉]4N;OWuP~α nEv>n~o_(SuJ'.c_(SuB'u͖aWXYʮs:ϝ?-޾э^׹ʨs=]F7z]^)vذ?Ss :z:OCGz +sb6O_;<-NifuSv;GvP"_oVCӍVe*OΛn|xe]ʄǎGaEZMhv|?,}1ݶ?,;-oa4iY1lU&mڕ7m?|~n֮Pit~~1ݶ?,-*g~-7^QT.oX20w?7NLY.cs!qtm**sݖ[ __+w[nY7qCZye\E{0Hp鲠XԹXgu  n]f]W:׋^~[>\~ƺt0Hp0ķQ!@o}ӟ'Of]o2罹1=+V.p @viKpmOPvC0Hp0Hpbs(lِog4l7N;)hXVㅆ󰑧haVL) X &E1au:tbRu}aDل.Fxcթ +,`DWl:>&XOh5aܺԷqQtBV0nNFl";nʺuQTN @>e#jv3K/s'}VӉy޽r!ereɦM8u_j*\q- ɓ'w\vjjW/ܚ5kdÆ k.Yx?~܌Sn-@~Z̸vGf~\Lp͛''N;vK6ܞ_ ^&zmU@ߔrUub{3,Mnu:!SJvZ)j~۳:˼ח)o߾ݼ]APZ;:ɵ곸!SLOF'juuݢ-gD:,Q￟wPp2 7 SZ]lyÿ`ٹsy@P[mGv#eWqD{ec)%IE]dl>|<`jƍYzfZmuK}u:3%egpq]x/wwꩧ{p~+_oVZ'tymO^;}eNʾ0̜(V- ^8w3.\f~ . .N+#e;I }kŊ~w1(%]~y'kottԼM[DXOC>%OEw@]vo~[Kwz8TZ3A/۷Iun^+-`6.[lWJi&H?->P*OgnO;@0ߟM[Mv3h8Ȉ]ּLz=U.Hp tI;RF~Svp-[l)%i6Twժ׿u\h'& ~ i$]tyݱc,X/I]5*1Q?uxRS^yET_YjQw-GD=CEqDgcR~ZPfG) ~rVC]I [EzD=ˢN՟Ե?fQw+j‹fP%vpPe;f^Wew"=sla~O:4˖$χ]}};G;_8YdS]̬e;Ąlܸ܇͝;W.B6yg ;vh1ߟ5HpV#wn7:HpqeHpK7D$|rTvpwi4e[ĉ7/TJfzeɡCL_uJIpm۷6m:˼ׯ[ۻwy@zY_&yZ 6NQw6nwOq{Mڸ_޼Xq6Wrڼ+$K.5`sY~҉n/ ŭg3.Ji&uvu bƵp=z4{{~zT3AU LP3fV#wn&ϬÈfqeh&ղ\6ei4W4LER;jn?h^'ԿbW&3?:b?;}'vìCJ锔Y0[̾}P-ͣݸqa֧CMj}W (U ͛7˲e˚ڹse]&O=T6VPMnum&IQ=/WDV6Vk~R ~UkZ}0;v4y{qٴiwyY1^-n>Qs^zI٘w޼y֞ݵk:t(@8=q"_'LŋM~Fr f\3_|,]Լ$Q{'>;NeN=Ԩf/'2?f^1])ؔ}OdS,/Q6^u'~!wn&}g># uh-VfSR3A|/2U;\}K'6va 3?N]; n0Я&9KHpy;ifnV"Lvp9/rTvp/Rپ}YQs&~5kd߾}Y5EMD~w?y' `15d] 6g>&~gcTO}JQ j-%DME;`P~ Oi Zj߿r.M9GLvp3߱M3sz_|~Ȭ$E1%{9jLPi e˚u\^z0Z;mP*QR[63H kODS3-j|"aF˺\jZbEar @ב^*!Sk׮PRܥKÇWVJjUzS׿u\ %VJIp/"4`*xW*Q?AͷwBc:𬨟$lLTJ3A1 0?h]?UeڥZ0ۂvpklmvpvpa!f%thwm$vtϙ׺]Ql͕~^i]2Un-mA;M i7+fC;lӈU =h7Wy\ jQ7mu|XSDg^CԻfca?ĭ\@=0 @5SvnQ3uMQ￟սPfOh gps5J]Y֛Dv^QSEB?8ZЙ?\~zQ;nu}LIQ/(QQ| +>U&$vv;EuGvzf_dc΅Mh_ u?¡~Z̸?(GuOOϳzeUMXT;e hmQ̬֡W`I^jmffglZ5}=ںVK6g+yum=ܪ'\3NVWvs>Ѩvp>eY^˺c>٦-aGL;Wqv ~:ܨ}Y[*\\G viU'3+"e[3f n,WA,6MѾcV+"um="jM}*Np>&M#\E5()tD/rvĄ,[L%_\ڂ dΝ=BE!(VJ؋.Hv!'N>|X.]*7nԛ+P u͏d#Gd~Aԛo0>^WYe\ԮD=gˢ< JIp׬Yc^we^-j֭3ug>TIB@k29g@0+̭%d$Lxbj]|= $GT(UhY/j\M0G7VQNzqQD?5JIp:,uX¼\PF?BXmAQO5uPԫzl,;gs;D=𐨽O:SQ.w~]Aڞ JIpׯ_o^}4::jlinSԃSDg^O}7 Eܓy W_;wnWLo/U;{ϭLZõ?(ShFPaXuXF -u DBw#VBeg+bʺ+T 1+T sL+E]wgGRԋ/:zLB݈*Q{Be(ShnQ) ݾ}?~$z:t_nٺpuO9RLBe!bz-#4ٌXF -u DBw#VBeg+bʺ+T 1+T֍vʻ+TߣL"bʺQݨ\35=@kfPfꃢvc$?PR}oC7;O_tA6VB ? SQWqLODM/'E=?%WDu1^Q$Wehrm;DyQ?;$#{/ $T#C6_#A'קAxdΩ+7a[oK=Oz, ~+ *%ɁsO jRDULV[oʚz mJaxUQ'ijإ!lkn A7GEkH) m_ (柵 %`vnWX!6:vR5igggSz5Qosv0JIp|32::* ,㮃PQDBo7bC)4^E5׉ڶ3vu谨#GD~6` [7UhY܈*۫(Shn uj:Be݈*ZDBehGBe=U 2F'(ݻ7Ͻ9~T'QۍX6fC*n)4^Znв+TWQ݈*FBuh7bʺ+T '~4b5hGn (ShnT-T7uTCv`)7:jz}4?dHw|~ŋW R0|B7VC0qL<'~tC ,~9 q< :$K.5!S_u*ynBu - ЍgʏAZ^7]) .0ԕDݰU-&}NK/:V6VPԵ׋q1Q'E𢨣GE}A6&񚚬zol,;jtK9&'w$D'OEws~xoL;wNݔ>)?7P)? P n/kNLQg+bʺQ5@^n :U)4}7bʺQPڍXn u _7b{̆P=܈*ۋug^~KU AZ^7HbgCGue Mߍ2?[+T֍4HVh1BWQ݈*FBup#nBw#V(Oh+T֏gM1Bp#Vl/"VU AZ^73-*܁1Q&j~Q{ioц8OmU7mgѶgo?=!,'Q {VҾ/ IysGyX$(eJ/E?~e%ufQ[oJ _7Ail,4Yu]qU1I@wB+7`:QwQ~Q_曢E6NM(@[/j ?(uЅߴ?H/c݆vرRLQۍXn uBt m7'ܨZn uj:Q݈*'(Sh~Z^D,5zr|HSr_X -g#k/%e M݈*FPY7(TO7P6pqB΍~+,nT-,n)n uNn)4^D? -}A(BeWeqc_)DE=D=^ɨgru'qyOVg@kEx;#=-gD?1m>juuݢ-gD:,QoT3`VPύX?7~K`_uף?uVQuz9Q/$ĉlM?~}vvV?ޜOOƕ-yʦ+[^ʖ"EB~DQ7* Q=.^dzàm&WnEU.5GyJ\M}Вx@UDϺ)6;\;d>RLQP܈*FPY7(TO7P6pqB΍~\٢{WB{@՛EmiQO5uPԫƷ_" n?a u~)e MߍFPY7bʺQGzjZwn@HhыP$q^gpk(NhܸrtR[?4u͏DY]zѴ/z_Œj+7aLԭ_Ԟ'D=_[X@T ė0a3+Vd]PkO׻1Q'E𢨣I٘v۷O. h6& qwddD&&&fsΕz*Nu? FBup#V,nH]ݺa~ѷnLn_~/"P$;v0:g>c:tH6l 'N0ì!rsj:+T֍Avꩧ"}Y9x,qBJΓ /мeժUΝ;+#˪hM}JQ j-%DM|/cX`)%՗!k:]xq㽾7WgqQ_$ҍMEXO>%OEf$LҥK/6 ,oG}-׊ڶCwzQC8"JIp׮]k4enڴI:,^ݾ}y@vNr?n본!SJv2Rܢ{5-[fJ;G7!F5 Punhv@=t곲~f콹êPunhv@=t{ncvYq쟙nO]z@B@(\FjQou˸]z|gˢNJ{ÇeŊ2o<+~ݺ?3~R}g?Yٯ\Uwz8Яҳ7gyHxN_u@]vҥr!s~ݺ@/IQlo}sU֫}}EȿĤ팫q Amw]g.h#"u bƵ򇇦]gXgu] \u5suXQ\ >UNpKDY_-[P)5;|(ܹsʎ;d&ׯ[\@u$VvyOCg:R\{/n8пJKP\D(w;o\@8 $ZXzuY^ .6d66:Np 6m'8[")e*rEщNMۭo\ n]hW'eZ)SѰX+W/XmgZz/fZ ['w~P?Wȫ!yVûOӏVpOhj5nmi51vݖqOhXNțvκ\zu;nˇu_4,Vl:WT>dÆ ٻ_$z3n"vn en"E~E4)VukutNhX,;v)kG^]\hYߍzm*l]<ٯ헧zi)k7TT?[EYٔW:[]sLPO_Qc 1^~2j$}JP.%y]չZۍaYΪ?FQ},}9 2:vVehUvTevڤV#޸վau:벽"u C˖lnue;YϺL>i6\V:E:%V˷zu[k_Q{׎vz::^-OYu.g:UTNnMn5݀5ㄆҏuԹXvuտs5zne[keչJeYkeչje[i$VlyM'|ZM'fݖNM+|HѸ3nˇm|;z1*QoNmm+e=Վ2]'$]&jvWEϫ/[GPo=A_UYO/o] qhvIOz_^)s9s0gziO)RUQ.s5꾞;7|$%0?կo'D;L]ôv:X6WSE^簲׳VfWuWVQ?֛:W:tucщ9 SƬo2SSSikS:e@3۶mӾ/e'ޡ>ffۼ(gp1 ɓ'+RvZ;u>;ۼСCꫯ'?I?-@8G8 @W+5!~<fC@# ~[}YyG䡇j4?~rMOʻkumӐ~#.L\wnq]~nE7b&ɹs.VK`u2u6&$ֳNSx0F7zNo2)Ν#箫LNm/|jg=e)T0 .VNa]rce>񩬿o-ٽŎ;.^`;yv1hlm15k-R;3q{c˕Fe|GV}0î߼Y^y5UdϞ'̸]I^-ߪLVYtxȤ,ٟuy\KIb.mr]&y_A~U߈垖$玴N1dN.I=u:J56cNi<[圑sF@ WcP?ǩC#_W_8/-p򢎻BY"W<r eq+_ :;ַ;#2qň81V_d|#2Y&% e4)³e~^{d3Ip2G)0=yGZ>4-oȄ\nlǤ\!GΎè0׈dRa|g?͕ =;nn]vM_Z>y7n_=+ݮ$2V`VdtL%ɢgEf ~$yJ<7rY]?s7zSߘ78"I͜y%^D֑":f3y@o[X.kغzFk3[GLɆ dٲ>yM@_bcr+;qvX(c?kd $SL/F^xe.o2S2~lI9)g,''\Iw;۲_](v=z4+ݦɛDB-:'1VN>dX&IhM&19vff_Jj.[$I>ϵ>SߘIҪd4-JIf/Z/'i:Jsd\=|)Ý҅Ixg.m\#9Fs6}9??2?8XP\T8xvNiB3hZY>9"_ ~xP78_d|j,YP5Yo%׎ȢKKW:_a2qYQV.L1;} +ilg /&moCOv_:-s6NY, _Ȇ}J`rjö|~'ϔXI˙\mL3q_IVK;ǎ'<;yI19$ûwc&g:El5yW׬:g Ϳ}ZnaboԱd7%F}w//Kyk+Wyhn_|Wr_ ѿngj{oN$vMį%~cq֝9]5IWIv&5I.S%˅HFdlyvٱ/g6[Z6ƙ%uZ![y˙^r!yΐ3<"y3Js.~ńѯ%Ɂ{Oi~sUredr yE׮HV7tvlwaɶ (N8A w&/Q86ݻWgQ#jyUZFrIB^dV_8tGK5ɜAݕs?wl݈LfT.sIPDmF,ol qsr%2C"/G4:(=:M;oJ\!/޵/t_d.\Z$ IPZ(ItL@YIt4YgWݯWLY29iJNܿXՕL/IXq-7gN~$/T`DVڎA<2_8gۦ0YMdϴO'4Ͽ]rɟIzdvaz8lygOQgbofSns_Kc WI]wuG?Q9 ~U h˨t|/,c۶ɧ?SO= ﹱKtBZ-KL,ٱÿx9U]\-\h$Ѽ:;lmlW9IYv֔ĹɮM-ҳib>_ r*b^% Ӣ꒝EOisb ̘yu@Z8~;f]^׿c=5ǪSV^6œA?ZAIMcݢi"kLwrvfǜ_2=[?>=e\lr\!rןl;vi0(2aa )g'{d&Y…N2ZXw_޸W,gmm۔ظu^v*ggdoK&"{?rf2A,^Cx:yvxΓ sDcCkg&ǥYlFʪ/g?s+\|(S􃤎-LYwۻ:m޼yr]w['OʚKs?N~eqlyc|}*d}7 Q5:&y:qiX$#hΘ$$m.w.sSDE &Dz6ҽկ"WI;w<'ʾ5{p}N$d%OM4oCgn18=\j{ߤ4ቻrٽX1Y"iߜmr$9y+9Wb$jVBcwrzfr=^Lޙ+,NvuK^2?RiL_̽l9a zsJʮ` mbAQ1~%d;vGF'6oG,ݑ?:ӜD/X:ggvxud:gmd/IQ~?3S0y)l2Ի6ߙ:e41eޮ!;=/Y!+r?|_>y?w1$dgR*%&A3gBݫ|\IɑL'E}bU߈k,{HRp}|?웇-_lCyݦnc%#ί!gg vz##2Zo@ O8wMO7줬qVV_>$I#pC$&;ɵJg{Kh6~G:8+ms\ٳG/e]Sn~OQ6ҎSÂ/\\90&o^ ;C}S.l˦=~ּ?yyENu] |.mk4\XΘ8 gL.6¯~+~Onvy7%ʥӉU_R}6ϴwF2Px7LU_+^"tϾ _7c,/9ŕ_L4 r֓]Kp=i3A`/mYÜcs˅ռf A. ;3k'K_6nhll08GՊ񧦦rJ:' _.>uAo6^"z]w_ٛwV'z$c:*Nц$n18*mR_l}1@03O}4ZL֥~jvޥ욕3͗.ogpu>αC %Kס929cpT~gpOK>YCGkpZ]isE$Cz .zngpQ $5k(P6\@  9?<ޜzD7NwIENDB`OSCAR-code-v1.3.1/oscar/icons/overview.png000066400000000000000000001457531417327530600203060ustar00rootroot00000000000000PNG  IHDRx cHRMz&u0`:pQ<bKGD pHYs  IDATx{-Iv"yﭮuouWuH/YBX ƒ%f$ٞhI^ 3`? yI `h@UtJ]]U]]Uu}珈Ȍ:yպe+#󜓿{K~ ~ӯ ]]SG]BW?1= $J 7MG*8@ L ƎvFt`\x`E&wpsq/E00vOn_B}0`y겇$ `b@A᷽F6 % dOEed 0 pKW!fо]G[d,c)$j $ 0sX.`_@@ oI|) ;PSsb9C%T`Jf(QJnHc. b!}0@i8yOEMLA%oGOX,c@+VkȕV,+IN90G5Wfw`8 ੧xUwpZeJm \͕[0=~vUL 'X,ax*sB<+Gf-!!ZE %$ %p.pu6pyqj3`%c#σ  A`yGǪd pEUj"8R3v_A" es3ܙ%l[ wz  lc$["~ `` $Dc$< [?sj@@Y㛿 GG O= ܸ *r 1ZFG|oҗTĺ,]zs_%pg >cՕγLJ୷Kα $ ƀ%L~ 0y `xiggje|ql=.GzSՐxPІ k%$X̂qp./ΰ G#(!л& կP{Ϥ޲TrttNPjD=i*4.J`愣#P:;1WK `=J/{9LA{q,\E[8w{p@H.2Cr1K#c""ye+_G2 I'20bw$? F&OjzVWWjzto𩪮Nߪ:dZ{*ŢP"ZGQ"ef[O @G!$[A@udF>D@ʯ9$V kPV'B{/\|8"P+ < "eHLÒ1De[ |j_5>EP+-@-P͂v z:7Vuf"S 3[WkUvRB6p-,21C;9ry !͟X,TړE\6 JW-n,8Scw]+wڎYtǏG:̲q /˞ `@\B*@}'ծ L{; & 4;+ tF\tc +'#`@D|fgQ:&=8NZ0h?򂆆[Z44QFB,rưd''0}Uz#0r ; vUEIzV@,(`\-KDA0L#V`(RRXju[NUuVpkIAL䘤_^?U¥$1 g!9p"F9J(X,Ֆtj{Xסud.-&l lZ1/L0}pI^ ]XJ@@R*?H2<8X?RÝ, A>.uZ\RHI)Ÿ%XqyMgpR̜E[c,{iUSMn @0L_(;f PW^<6oxͺv> I_aV 7fB@A)DՆV{[#rpzHjU v-,jдmжW%: hG@D^Pe(1v@@~W89YaywWi}< 60-R |f>; (AL8_HgiMx`@QI-KF ƶ2B@N`nd́vj>>,֫l+ 8&jzW5Ô`(x繼Ծ"I$(?qvS A0yǿ8Rnȼws :g>GնͿjȿ; Qk%F@@tw$8>{6\&d?qcTj|QҜ{; 0rR]gWezù.m5PAaүp@@"EZۭ3F}-kV;ZLk(ʯmE< y*B֦ZZ;ۢN]t~5Ar[mCp}+ A}&*jw:0Ej?ݝch)xےc[>e`a;@#E8IPK8D `8=}R%3v4 Ѿo0W]RO C."(49mp M@Xm*A8X ` @id,[u_:k\5]mˊzslzN^F AV+8Z# V̎S /EaKވBZ? X\"Z6@! i\7hRK>5y! `@<i 8VBv_hvuB: Z!W ''H3Il/8G%iBBT@@" i8I`6ODŽ @1A>/>GG pAW=;ǽQ6W< lcX qv4}[jU 䅀b x럔``vvOd}<5?gc ? `>l8" Rޛ&`*X#cUJ( z S{eL T%P!p@|X,,!cueУmv}&sBEJoI 5)\w">4uörG)[?KW}U ܷ&>t:v0$8:DR0⬡W}lG]] K# 0~b(թ Z+zXwY`~BHin# X1"@BǏR@]v>W g WX,qaz߷&nCj- J! `@|$WugFoD"5QiT! `@|X\heHt8`@sd@ζn</ `?X.2x/.!z1)ES 8H `6gȶ|J`Ƶn.T7]}85 :0 !U8`aY.*~x&84Qm8T `N Hc[+mw41\+ `^B@|a=T AcPb lL?J6`<0/l7" YB mV}`k  ^K=@ ȹW4e cE&  DJ31+~m}{X0  q#ON :oCIVX\#@uAǏTqE ÷mW%ǭW'@0 `@=1o;>=PѶ?7b@@@@+Hn,8aH{a d^yaz) $p.b+F3FMǝ[(=n@@|ya Пc8:8䊙0 `@X\A5G b?Iv[Dʹr  '[EVRߜ!K lc?c>k  R@ `h]>+[ k'E@@!L,~TqS@|*E1s7 8, `~$)ҏZc(>cطV Od! `@>j՚$Ȝ &dHemC d(  `~`)%::G /CF4 .@qq٭ d@%QiS-C W cO 1?BT@RRL@-⇟ ppa~!qeҊ1u_BAOp@^h @@Iٹ<;8t># +d@@ r{ʢI}:jk ?6,*@1MgoW+~9 p-BBE&8Ƭ)ʼ[M> `@<Ǘ2?ɬ. (!.^?(;JCG扫+R|R>&P]S73@Z6M "yM% /;jH~0!}ʜj4=DH>R@@P@<>Jo.\4LmC onZ`G40Ol6?\R^PES3 M }EzHTD@Ɛ I` Ǯ)T=Df$Q/v4*˙WGc/qpu!~@uCǏD|.CMSu?<]UL{q8` `+j/I2;W/;i/ IrkT)v@@kt}:T*0,80O|sWkXiXh]]Z" Xo I ' R0$"-@JSW8|y³iP @xd~kGCI_|pP+LG 5Fh+|1jùA%, $^s`&4p @4\O /vsɃGqחHo6yioO3 `;!I(K$rTX}@ӭZ`l%Vs_#{2C"sL@!,2.j3,H4U{m$_A[zvjd$1GP5dB2]1T2 ( 97$ݵ}lw= q<_mOZ+!`Y1q "!T;ZP82kGx ?JA'q6ނ!D-"cOo `5B"6?0ao|\U{ 4M!oܒB$I I]#"ߍ=>"I @I&H[b߉+ ( p̻VX`Ɂzo$'I !@DP6#h "y A'I}|,A ] hM߶l&ʖN!|b@ 9@UX>U Lߓ@@$,=I ! /f@B 5@H@5{=@Jm i D"l!HdnF @ȵb}:>n6$X'*P2q_w `HJ%8B}΀2䁙!Nt n! wcV@ܢEM czn KRJ߼1fC ˋ+*!P(@ 08}sx,޳o" lس6x ;Ǟ/0_P{D Wcnܩ$}>|8/O0Y%2qݳO+_Q2A-cH@u2$QUt'"$͢?,g !į]DYpFP"=  ` ! Rd@ƑM/JhxE(A D VsB(cO:hKώ=: `& Vi S_J<D-L5.?h9OGDC`!xN@|@qA6~pq,hӳvĵ`A7L%HTm]kvZɏ'r >/~+S-X}WѓcO A7H_g~c}|ɿj΀T Ln:cOhƞ]ucO A7^S('!P43/-D$7:˔=෎=* `ސ$ ItJ`N]Cx@NG`Bxz T!i8EQFc[(4"Gg-G {U@q_;~\+z@oL# fmg,(KM6 C !6t'@%}̀wȟ,'I\ АBv#Q8c"E0p&a;'Q`1 ?('@"߮c?\\^Sg$N& "c.ʒvԑ|8V(d{r8cj9c]/S|ɚ ` 3q B=Ng"a.@d `Z { U4 Pg&V];ƾȿ' $w)&!ñP?_nO&!~{˵ `. )%00AǞB @ 0IT,%vѽ?Cp L(3ny1 "^{ UC}H-bKSܶaoLYQ>; Fـ1~;bLDzq2Q ` Hț誕>4X?F L RJ9i`8e P@pE+x $~RR$!0}0 l6=* @A :0ۮ)p#`O$\v?,ǫriT!|e$&#E&O!}Ls];09H|߹{Uh` )Dxx|_v>v~.mΎ)CHq)`Zӎ @1@$*Phj<{{zLRc=d`t hBjh64&uv]U@AD B_{u@t՟$Y@.&u Ӌ Ηb^/$ pqo) "Rzd~Uv,%;S 8K|0@ .`v[ u|DH( yAa0B6 Ap8P[n?H*#`Q#PIh1hCUu"*ma98~1eiL hI p(o഍N=nEķ32N^{> @Zo6Z}9wNft,˧S`$׫Yh&PpD*&7hZY%|DDAz{>&CD@B9 X֟ALUuvqYΓ iC}i0D.Ή)s~7h& ώ=_ @ah` )dda;P|9%e!8OE8=fap xZ?ID$K x5w]~"B3 9@+)"n$RE{wS-TLκBYy ! SH $6.{Zçwu_^W!OPofjeu[7GRɧ6@ASX2s MȾJ|I&_Ϯ~rq]lڜ rRO ˋɧ6Nz#RBNR ۾ߤm~Uu5ޤ 8Iz+ CnB*i!ŃcOA8e A-ӆdoT飮g!2qJy: |++ƞ/pHx- PHTt񼯶T_g@aL٬D> |Nai pH X+nPşʉ_ۆ`f (H 1m6!ȾI ufQ4W~ 7s,F^X2 z߆NG~ +z$5$ 1zcv Ug6x ?ER>m`bxtuu54A8$%8Wie5Nm{\Qfgtqopw9asm7Y\UW8/_i &pu?N 'ΣZa"Un|[ 2(}v%!RŠB|o@JWnYc$9Sxfi p8 0q$`( ܋ aT~^R3\y۩,Swx *Yg Jp,-$i /Q<{:|5ò0A0 |NҰRXDBݖ8:oU[r-ԡa]w]60,l70F?iH'je |s#ȿ%@Œ @h` P Ȩ˲Jxުw߾nt+$]Z6 `Fi  `jD'VF@{jjW+}|/ho]v#pD3F@o* !"t@<) AWe HhG@[PoL=ФplNsϫp~.){8  )A1}c;<f I"l_@.`q{/--*VՕ+( Rf:jtA|gΪ"[o#˫٤6@#$1 jdv? >/ο0D31+vD-V9Ư14ӀE"٤6@vNUږ wT ЅY۶cq,aY!En`,'O<;Il= )u,*|-@o+}s_A -qt#w߾{F_ƒ)#1<SB24A84>_ @ Aa_Z&&*ߍ(lF zYN  DHy"$s_r/7%W5u>O%m ѷF8~cA~.N`j[`(DJoֶ"qZ۶oy_dӶA]aخ D\;q,{#  "2 `2Dؓh z%J21)3E0`;%FMr'pΙR Dp٬4$ {2s:zhPCg_Xm 8?AwF!o :qpk [ s;`:Q0;q* A H Md{z7=P`%/e8BadMY k ?At v5uk};ʶ?px Z`H h @`L86=ke_Ѷo @*xgxKNrE x)I]2e0 A W$* e 8q+λMU`uկo='p0tQpzW&j}wja(S׀'_=Ѥ7^>@ZRmLL<@M%€46sr ra^"G[f 2vyk˴8f @b^. A7x"2 xE0pkJrax_I~O|,pz 8:u_2!Xxey+y g,= ې1=9-l$Y?(4EPSlID4 YH޸W``}]|SB*)@O~i p!?FMϛ;-#6eX߂ϼxk|p|D `q $Gm}^0mka)&,[B+P6 d_G[`q@$VX 1.An?VX &W ~?KncA  npxM9jk1!PJcj}p +򛀧 }xk/r].W'4waΦYi% ]X='x%ΰc]*/#ROb @@@x@SoT謹W-<ɾէ[qWe6HM!ຕ T[ mw ҙޢX`ܢ"ܵ|AX)w/z88>_DDij`=IV5r,1_Ⱦ -}6<\U2W~Ͳ1+*؎|:L>dVZŹ/wn_\;;t@Gb1 X 1z`[t>]\X^=>;]?kSKcNGwYϻyy윑U1/~1R]b %CVH %vopk; |P<}Z#,#-lcA o/bj"p |o//ǿy8@2R55t%dpK1_8I$r㟾)Bp'yZsr0cJ-ϝw;_Y֜sE<6`TܮScꄆ FѤmgEU}f%婣nl)`.`)@Ϧ2j` W|`7~{(Lifv-{Uc{%dp[1n> @fx/0Hv$I&.kzX(m3 5&f Mn H.3#,rs5NO2pPf!@%cU G/g|ێ ͜o[Rf q` \sAsi|Ě̉2m .}U|B{*\Z]<Án0R?nRrROs@V=`kU+2Mu] ÃA s?A.J_?t2A)Pzhc}ruZgZ#{WRc|VH-$8Cs)J@qLVrdQNpzVxLN@aw՞X5~l@$XvL_{ ]k Ch#-C$oDF_ⲰU<0ra _N@R S~/&Y]Iq5p #erE_*XH79DD'N $ a`!D y臿wG2>FQZ$z'f^XB5cLY c×!fWёz$7Krr53lN!KcX+Zؿ^HH#`6<J=?iB@$WǞDW臾7:yu}IM=} |ZN}*n&;oIK}Nkq j_2`M7V*2dŋEFEPWT9D,"%yzŽzؓ \س.o&DaL}],8*ƙJb-v^@O@A&)'p/s~JmT})ZUjYe960A~cA gd30_-N^ ќܽ K!*9Rf DViR׭-P.8Kv{@ 7:p%\W J; AD?x CJ-ެM co|^d"fDsdmBo׊쭀x㻺xITvkA K\ֶ*K8$Nlu "XG!  u>&WzҲWz99<{ ihGb/8j J?[ie ci&; AD?! ^O{tlퟔ`Q軎o-D3pYh`"8*BŚ=<#CnDLD??PNLO{z &|C"߇`&/@ 1̞`xaB* r+T;P!`HG]..Uod$@Y0A{5t*5&S _ިN b&>CO5 >IwqؿY=%% B_LLcHr7IMOPj!x$%: AD?=":FsCni'&/qwh7\T24(MJ`2#/;Cس8%?.]U7PWF*̋Y Y>t p|]^; AzN&OnL $I1K\ >MA-Y|VJ%N`v%{~~>/O3~/\s)7eeA\mH*82V, A0& Af <gj!SMv{:Y 0*?7*@h4$tN{~pPrz] ;d Pp# ''fkX 14A)~.&g5WЬ~WR!D-%,:{pv*};uW־ܚ-'޿Emi84A!~h }1^q%X2 .NP݆}sSxn.0DH%:5}zXS_j*?3 ڜ>e2] 3J0RBk;!D p΁+m,H}Z{.Ny WmmjjW[ܪ1HoJDQ2` D??{f3 I+X0Щ{:G6Y-@>-iN~jK5$LmȿW6)ӀX2e7Hlo^ `,í+C1 xX%! ,Er=}og&PP(r6">TBH@HB*Qqi 0GΘ_& >D Q@iBUP8^o  ցJ҆ڌݞy%+ت)QK>+ EWoVğ:IlGx/8R+ׇL@>Z߶uطu5o% "`I@l&K <ѷӕیa##YWLmyez bih џ ,2D@Mp1daOc/ JgSS6P*:DHH"lٍHmFƎCЎVNŐ` bHN\qXC_Mg~{M H2fMG'@ñ'0i|pQ*mT6Ee>.֣PF%"TTu,TE\`?q,A$ bM3I=Jm1Z?^reǮ%!}ZP|0B ` wxLdmaV.R7+&32i {eBG92g,A "BKA Ђ%Be2pІ!z>GҖQh>8}lO\$Dk;! 'X%/=>RoZws ]+z_vi[}E9J+kmpy#XBJB$mLuB zLI?_cZԖMtS~TL&GD-\"`WkB[@*+Wn҈q~ģG[0~d8#&j; s=KEӦ  hB%_khdiQ/> 񳄓hx:4A$/Sj~VSWF{38> @BHwYGx  A@, H(uo&ޜ|i2N;] ۞W $(\h7F&X[b+bM_FeBU&F1-]t,@Bbxxk3$gpLjJ >m:CZgѷ` =n;}V# 3&ⰶA(37Vu7P ! #$  [Hݧ_ia՟p_GQG7ߏv$ߒX՘i qP[ LtV+k(בj_̳)okXpDc|ɀs2h9ioع1VUc7@ƥr/WKC VFgpD0d,& A&۬]oԪϋ̙ež.ݵʪ3~%  `JpraN'\kE[-@/@DӦFߞ\Ϻ<#M\vb;{sl" q$pPi 0I=_C &`\=7} ː@ FBP.]&Z`B )<&6Wm2KmUw׭xƮK|'%{JlI6 l]ʎ|LԠܷܮ&vhH p3-#,DE$Fi,8Vjخ6!V5mM`f2 _@A`7%. `$<{}# fB >iqPMU[Wh+d lgn1ap<"'"D %xC]e Ts_QV *2_iK_&Xsѕۭ DQ {54F,DDX'+p/_yV{|$W,uGmoN@q3m7orԖ)qV`qKuA ;vq8_R/H\jɿv̝m%$o"h{ C @OG`b~W=} } +(ǐ@ ICH y*%6o[boK`sLvtcn&ݰ} %e GG 9Jk: Xg/޶_a ״I QzTJ SPp!t=?M+`P d7TPP΁6Ls)3?YK"p+X_@Pq k cAp!W".=x84Z.?y ie7wSc|jIAsG'"5]U&·9Y6٩-!3,<3) UW7Ъrrls[JYUqy!7_~Mɟ+sw |"!XIrPi `n dv/'08sVLo`V=ox Y0ʿj~7YW!Vy8"qլHlhi\eUj3'vv!*GXo7s5_cW N,$Jlp?;?Y,D⪿e yu\)PTA^a4U|U{5|*[/2GHH5R*k[hCm]˔r䮎+pl* r *lyFŠ,o*g%?Aj׌J/̋+q>wh#ʮUjZ8 ` ֳ>eU?9.PWo((۫_Ewi" c 3$.6zi k#Dr/#yNPVB\;"<ĹRo=UeMQwrDtm.c@1':CV .і]eC mV6oa}=ߧ@9~prƲd@A?xwƞ)9Kϙ5YV+:ۨ\_P╪<}4}{DU)v,>Ba ˒s1$X 9g5߰_zXìml5$d'U<~&+0狣x[wUBk)T _29ms[ݘkВq||+]k)8ҝXX6l70*SEX,x8@=pp 7x.[g~o2*ޡ !tjM&1~uE)S[qĕ:!p߷,w8UӪߜvI6c,myUyco(YD >I{xKcOb(#O D?xȸWyZWvi|?Xu !}}3T4(f%"2s,_t> V>Dڔ8F &" ]ŒX syuxi C/c_\Uy!R) x[w~$ޟnM0~>UQ̬   9DAHKY _z% %V{ܶO}6=ocIHH2R«O>dL }y7[w(*mGm s+>X%zLcaߦtlh#űge]2{.rK5"~[(Ml0ko--,?aڬd_[g[m]"P)"fj:7”W>l; P N M؍Wu2 ( }DeD5]:QxDH}S8OÁ6x79OZ]2 K r~SGmŬmi/^w!.*,h"?;f%ǹMnW?_]ב+E؎xE0oگ<(Q8nl{%~WY€0*}T{\F'jW"vJ-?D;HʃLl0/`}C˓? `ȿNPsX/dCUGiÂP#iQ,uB "X| ["`$ߥjGu|k%/Dl_"1vi f#z\kG%z>9r/QL#H5>ˀ~~6-p410k}z:uMweEyuهf~$˛$$N<c B#O<ǎܻpI㻻g 4f]_Zֳ@|s4^7hED W~y޼\qi )|_z`˽w5U+}Դ 254ȵ'upDOSߴMeKS~l3D\ K"FPd!ځ~UNr߬ӡɔV=^'=mה,>gO Iv[ |5n3xsR{o 1@ B ߗ)H?+uK^;7m s#6}v63 2DoTW(+{饌JH_܋߳OtIS"Z]{܊LL`_~%_| ac,D8W`8 K`TX-5`` [Y[b1ZxX!a }[RQ ˈGaqʅ$ J  1@+aG `VK1ןQh Hu%t$Q `%>, 9)7ڑ8ʿu}꿬SRF8Ml0/LW !30,"cq'p#D`αDS,q88тD01$66HFX#F + ؒ$-I-Ы9 M0Y{ 6RrV1H]&%oMq́3-HY/'Fe% ڀf/( HNnU #LCkޏ?a $?a++[Hl `S/]j_#p,a`'X ND'5A2d1 $\hm 2e-IĤfChXBl!N qqDWB lտmvX|jz[{o4B+dF@Ƴ#< $s_?;wqchͪ_Ę!@@h`&_ 2"LWF q X Q @EIJ `8pu8iF]IB-ooAnN7 Q"(4?+3`!(.ake V|d6UUcEËĭ;L Ol0cop%"Mi;f7R 7fC@5[]ѫM -@ʭ|c}tPjr0| h,b6#[r4$ J]G"~`\@ɔ"]Kr}M%w IHܼM<F84 O.OW'>D+3"7o5&ю|EjoEѳ)7@`ZPN5.7ߌ'oGkbJkW[|X7Y:<省s$8`LUKR2oҶ 7hUH<@OFVU_)8GL ,H#]{cOaW=x⛾ pVgjuۚkrFv[/J^PvR d*]"eBjStMCxxs@"` (3sgdJzy݇&G&/kU/  AwM*oGxxK,( 'YB^ ;AjL-,O^Z鑭!P@d 0+a7q;8-@76e@[̑28!e{3%!%oA'&H Rׁ&GҘP=O~g Kyߌ+D;(hW/S,GE[`));G~ WL ]7px7>t2>up K au*@✫f17~eǩM4햙,g R^v0*IZ)WǞ>0vE 6=׀yX" ˄`ؚ{%oSJFQXbc,qb-d on2W,*̕eCƔ@dJB#+u%&m;Pv,7,h{# " XbW5ד]"d׵70zQ$zB^ ˀpNqGWYX|LaMR/`Bwl!T9c Ɣ٫R`_:F$ٶNpTr iIv> *WFV"y+)u(Bw5<9Zm5,U՗|ѠLq3U >:-' |<cWUP8g ɭ R?C;ZMK|ߤmMCkzOzV#ߘ]ϦJXadY `\@ˇ[xI.߸-=j\u^6fTpcx8T"U'e!NSR9ϰʼ;S:# X2 y&?qPk(L`\*}7\qi F.+l i]PKEy #X.ڻgTݳW T!I6U"c 8 >=ykV Pk8TRBEY)GcuF 8UVvU Xӆp|8=F~qi F6D|GpW`#|Y YQ Ϸ_%/lGdc9gm#`K< r ,9ob 8w_sYĵ$, sB]!ͱ ?qZ:+{UAH7ʕ>$q|1a~ ~_ڬ1Mŕ<< vEşY%ٚ(44#}1bH#AFB-uc[H T8`-@j=Vc{&~o2h3 @Y离)7wI룝7H0W+k!EcvyލJݔ|Uz[Wi ;cp-6P1`%/v`lk-/fIe7>ʇ rM+m䍒ӿ˪?-Bs@m%$Ο",& `GRO=}a\>{7XKDNqFnQ'F F eQW16HDEP䯅b}-*z̑}҄@y٤cOIe=i %@>uM1q1C݇(h£ˋk[~Wߦ5f8<%q\wW\]Q_l1-<2dWo7(#T@# Gg틡/Wg8p̲d@R&泱 q"JOܡ-P܏oܩw [HE*EHOsO^?A-Jc Y"wgTKVBC`VÌXAY JM<%.[X0\p1َM\w?j3^A-8#`uNyW/XK1t(>ưn@`]"qs0 `t"Ͽg8 Г_(ƗtQdZ?/qM.@ 619] _Zt]b]q1"[[3|юbRtc&T$ `.JDJ^_P.)PQ?c#rhv^3&r߶PRY *dV5dUic /^ϐmf-| µHl0@JÕHn'nw#A~]\ 61gBD^3=+9g-o)@. 1H5I`x;. xHf V]^j d/LC\1?}=mq"$$NHd!!c4{to5~RķQc,]euQ/؂uﶼ%bH}qhV~9\som `_V>&:Rjʫ!*/u ꚤ6_ $=Kl),PP]Kȿh[\d1([ q-@mcMW}~_cPmAmT<&c7kj.ST{ߖUY={6;eА p <^]]]Du`s[63PFB+=EXё:ÁbO$Vl<4wQO12v$ /Owaj<eȬwrF qzngE`IX'F .D( Ա%rd)"nu8U,p X03AJ}9-8[ UUQ2 oZ^"mt݇KdN?ebGOwr L{ñoLC$p#18Ʋ9.T^J D + R NpNBޅ3BIo[yI!S{{~SY%@g~\2|Y>{>Y O Zpv?C}W6Bt`hA[n|s3$RcKf]ePn*-/6)>k>料 ZR y^567diy@8K89n0 $^{ 4Y;*[ԡ)٣5`X!w!,b+dڂ9&%0Y, 8 PE3"~Qno:WsW,׳NNTpI`90 WB`#qۖ"(iտ%8Ԃ_`/bxi@?uoc9 gMXeCV@tdT޾)GyE 9V^ Dٶ⯼M< X ڤ6$1$(bdt#"ra Y]2xJMF5}@__#\xHuG}5ph"q J i(i=zZm$R"Fےׇ%~>}j2-R j>1`+ m0fN#TTw&X uL5$=N_%gOQM ~k`:g޸KF $нz?{;őX0CNGv4@JG莾l};Fu,Z0)0š;z=ƂX VTV^ep?'fnow| k#o( p.aD@S~b] Ќ^.7##g Yy%7"62Vq & *vC3y;y(ϝ)G@] XV&g4%mWV}0yIH `'coD7R.Pɿ:Ví 5.82-!I)#!OTN=QQv#I!!q6CX A$Nvdߋk,t d:)Уϝ?7,q(T8`*djerr@`3ojIsq ϪX,UE?ȭVI %HzaM $${MSNNĭ8 _;@~b:bA Rb%j͕Ӣ倥a53D׭}ucqycY8: pJl06B>\ HLEu~CT;]@`;rЧ:~Wf@r d gɍ(Kd|Ҕp Vm_UQ՟ }*jUWM{q61'@GS -PqZjzѽ0DߌZ.j b:Q6N1pB$SX(>z~<IUn2z'W]_0H|"ؓ~? |<!.Az5%{Z4N\WLSdo9~XՁ̖䈫@d5ކQ] {2_a[~:n){"` 7 WcOc LFHKsg˃Gmzs :A@c*B &4}Y5{cz9Crs`4i8`cgJeuTڬyhe]b]m~"[Z5VUAQK qzwTgǞX@BlOt'@Ek-բU߄_1pw%_ 68ZJdi 1#t(` 0%dB9$7vBBs ʗN589Q[Y| !q-T`#DdnD߷W!K++`dzpt׉+h g ۥH5 :O wWU2y)*OV>W@OBy**v89L 7a` u"nDhѯ mn|X9Kc\'үB!8T 2y_&(g}ɜA N=Ux\cisA\ig 0:6'>q/-BrJW=JLokw&A&1B@o&Of6L$@ N#` @jO٪k;S.+4s<ںgXYg/;uIym(5^p]*ꊧNN H`z~^W:'/x.}?d_MH4xI,{t$m9 M.k/SiCWy5Uq:{NkרzD]Q0"-I?,fsL_*tW}ڏV,51y{=x[}ii?t/̾Uu=+&GgJU>z-Qq$O A0<$.6ziI $1*:~>EpC~۱"W,u As-!~ml@Pqmo`oU鮳 sXuy Lp$-2YTÃjii R$nvVto3R58*07XXuB\H1)S3Do&+@*hH& W/⯪k+M֑>au[uTr[w@04 |o_uv>~`sW YP`P~]ǬH <8 K|}Uen!T~Uo@kM,b[vR \46더Dm}:/@F3Koۘ`ʤo˵ q$"; rss & 𿵦ckЧ:m <200k`i ?)|qc-}:u|ggx: 7P,>t:`( 7L O;'z:c#ã.ǔ ?*9o=2)H$>MW; ߴ~|;WƞĘW@@oQU~KFU)3Ԣ3Y -^ExOcec;RGrɔ 2рVuc?5%FjXIִ+M}O%]UJPe!>p%ؓtI/dn굦u]#N7 7ُ=n?1"~hG[u٪uiq6u5Ut }zu꼉tFG&}Ll0]K!-8yMZKM+ vbK鸙enKSIDATe@ 9p>^]R?+⧒(*Z<)|,mCḾʵ# 7) @<<>Z0YxtO^NHZ&zj~Md|EE~)P4jȱیjc8Grāؑ z]5u=Ԡr+QoM>>סǫ{"0ݷa6W! !}h NL.VvoBܴI|R+G tUU3,">I6. !p\45W;tbv mia̘a3V ~% NOlG!^s4cpɌk\n(5?PR_*X:]:+?CMmPڅ@8>1*`h\4H7qdu{=M4/b.7OR!vvQ0 ФmrSbA߮Q1' ?US~O0,Hk`zs*"X,E}i?h?U՛7wR{z.EJhZD HR>Lazz5jjo9^Wgs ٱ0I+,1k`>I^$a,ej( ?n YU8`X;8*n,_mzM1!4%08.D-zCј_zϞN}c,*78>A\^\^\4~b!_ސnԎ/uXo2k2h h&M. @a j !vTGcERgFGnADkhyp!> ,0hbZ]4>@bhnE&@T /m NWS!\g5g g:#6f(x촢7u?5Me٨J|UOWGBD)! |i @oJ3ԢIRȄux')wiJ:-0i@23p WkU<&jK]#kQ1` Y0(`(IB2}0>ٗT]6D&&#K@``Hz|%4E8Rd6t =.+~g %/O]`00$y` |i+íE$+$6kr6 X ৅c kVOYJ`-9%ؘm@TH3'4~X!e2ת8r祖% IJ# `>/ F $ 2f?z]IM3=ΰ芡u #eqMr`6z u6zdu;ߣաXuug[x鿙k`_h$f@*GiMkU_CYaop po˲ }\boVU&#Ph'JRwWW_\&>">ΊRӬ1]uohXk`&/n[G[+ oS4 wyRm05|#{L p-f [Wo S ~ ?d:9u?״_gvME! xZ6JP& vۦvrO[c::K׷ ~EhC_wm[p$Q&uumhd]AhK~6@5= «O=T`&$&؜ '$6Nq'@W1@@*}`is^]!9joKTWۺk@>; [~R"W<ЊlGO WE72@CC`R,a,m UՖk )㑴 JmƻFjr H8$# (3x-]Q0\헶}u[-:*v2HPGA?0(6I1Da'rgmը[Z&38Hm>)N'8Grkn.n/ӽ4vkXqղ"Ab4?1RCE-I:+d&Z1@e,1|D:2[d8iuZ55;8K8=Y ؓ f#/n !ImW K->pJ̶?Z]` QĐO2#}/ƠSg:jfyM[9>D*0X0,]]]X'ͩ7kK~}#2 bxcԥQ(  N#` @>`dW%KXr'QZUޖkٞXU_\!HJa%WӘ M$lp#eؗ;O.>l`&)B;킱}Bf[iU?i=>^%6~xs*M暃&3y)pbxtg0'>y/5 4F9w?EF.*w҄[M1 21l&Z=(}c-SXumT͸uϭ*b&EJ8e<A46#|y!Hx''@v4,J>} DK3>f&AfGy4-?W%>Ѕ ɇIM,kF` ePZ ||yP6TO!Բ_>\X ?3}> ]J M26E6>6TyԪM=Ū<뺾>[u뒑 2pXpepHƼD"KFSm߷55\.,ۦ/޷9U@#΀%GKu_G>mHdr6 XmrbcW=^U7wnvDBm,pIrX R,-?fvׄu욾,GX`Y"lcW>6اj2DɎ8[Q HtI>A>mZԶϛ(>j}QׅeybOB!>=$y}>潭RpcGO뤾~A x{#I +! 9ը_ _J>mܧΎ{!PwɊvvx6%LC!a y ? :&Zd߆ݣ('L`l7x,nd` - q#Ҏt'iD>m:R%ThrU_wP#X_#Hn9|@x{oy}~R ZkRdoo;G? Z.(0/c9Âs--@q>}ѦqLoQM>nѷ O-!p! pr@ =I$bzیN^8J`*+ ,~#D onL$1fyJ`XĐELJs]ǧMi6>>Qn.\Uݡo6~wΙm$a0 >? B $!+e@>սM, l:"闡?j1 }Y A<=w: @ yH|a L ^|Ky[mfC~涍&8 Y/ K @zES&"\т#V+N}gQ_+o=F]c캾Jz܊(`^>%D/o) P;=]HWozx~Y e%LVz۽OV*}MZgZ =;3Do׮t ձ'15OEH1dD߅f& lfg ?%$Un<}umX%ΘȐP0'mJi;4Ml~R7(Cql3#w.gNC*.b~&B)@@BEh?J) [0r[wO}Tb&G%^A`l?)kJڴ4Vmjʏp! zb0K|q;jt^ˑ\tjF`+ljVbX #]zy,̭Oon@-}fm篫w~],P@ | i N֛D vV mU 'P]cc?bEK?Ƶwr_Bd:Z:}iTUhv~u&lsb4FEx8YjI^|}Џ頾.e##g@qK kƾq:3f>wWԭ O&>VMBE`HЅyj~K|y#'VmWu+|T7мe;1|zm4S 1 eIXLr[' TV;KSŇ>Vck_$ksQ cOa+[ {kF.Cd"@@]1istĠBk缭c媚p%w}ko{w(lb@$Ƀ0E$lH:](^FhX $a$౵ e:p1H"֊[Qc&2t3EwUu%2ۤSTCᑔ! 36H%[tKȗ @2?-$&?1 ]+IMMPtJ`0`ߌ]跳nۮOcI?ıLvbi,x潭'$s.6z=5wMƇ!  K84=9Ŗf_5.kjۧvp{ ]A+,C\OC}/v }č[Ɵ',0UW)z[0٣U?wW9ٛ e:V[ǹ$Ae sLŇ=8%>v[gh98 ݄p M`$a `uWFQɿT ѸM" 8~pr,㞔2hJ0_xK$rLnJ5m{5}Y,5 82f=^C(g:m Ķg{}cbH)݃~-)*ndr%ݿw1$y BH.P??}kQ95?`B6) _W N}um=Ŷ;ReFGRL>r Qut#2uK_eEiMSu8]Q!Hٱ0e^ )_lcJV=HQy1H;>L;G L5w}_ԊDMơbSq @ _J֛Db+EM@jlv~ԪVm4d4FN*LMe>Ei] ୺¾ߠmq6Zsٮ," VȐ ho_ᓗ[c4͜w0`Ɂ#XI}5sl8}@5ed̊[ܦM "$eH\ ? !_D%{k# T_ 0;,u4KUlỤ2ґLotZ IWo~k"MXO;u+POx mȣϹy%N)s J~AyS"wIQ"hGH\+R zq#%6]ףf]$+@p/v]>P @+ [Wc{\N TCQ^O8$NQzؓ: D>K A]ǠV;ɡD__!`9E qs`jM<ߗvk}kT8mqZ1%v" f'1uI/$[hJn@(ۍ6&y UL]s#C6uX~fW-Is|ؽ 6cQC ? r 0 o458Vʇ1^#E]meဳix|#&s,dEQ!"m ~iէcS#=̩< H[ƫ @ [ʗr ܟͿ^kW34C\S@`|<}+gdM,V}lKm#l\'R8y5fSm*z޾*IHܼLPC{ zాv9!AXȾ@_>}CЪd 03T ,T")aBK8IYZ( X@;I];>PE8:8DH3DH9} 8VV )v&kVdVvW 6#lɝ|)PH`U(TT@v$MSQwA=s1$g rZWYYaXˊi*:Tc |WC*A!/kb!_ߒ!}t'lvSE 0[ԋm˻zGS,A,ٮ޿)/SۯfP/G\f+SA Ўjڑ?= a3LdH~EX_pdeuC> &b8.y37D8"b ?]qY>5ŵlA?2SqXiuxy8! 'OI)&$d} |3vcKDכ|^xњg u,M,G~ӹ J;)9gt߆t p|LtA17{9EVLp` (u v2G`rPF_C#_6Shl63,XDk?,q|qv4a^~xO^AsvO W_ x1ŘOVQ5@NG\qbֱoٰ9n _vQ EӧK &C &8o޷>[YogHvc\O C:!ք!t+`ơu95%~4x, FAńQB*A @Hz~+$Xw\>M989bJ`yg\iVo{]lf gV= 5a` -v #z~8$ )_ )#z߾Y,3o %)HTMmW@<i7iWT|xcgST*;bC~q/[%8Hd`xyK(*-w*_sqf`UJ5|;l2ѺO2ovL⧊fnoo6˱1' !CAt[lWS(:p)oTc}C@)s]V7t1eehn0Uv'!-[Z1:#nÕ=S!_NRG+>h޿\;o"c|r BSYKq+o袙 (+j Tr4jB^ S#o! pS|<&ӝGߪ> l76|Eњټ1@]wt= f-HϲJnjr/۵Q"pv@[pMXhcB0b,8D`좻+*G`7o]UΦ-=u 5 VԣZ#,P0ڸ WA-{XX7v1f+;v^" y^a_GYigPHJW sͻQD,z{Op A "IB8h R,D;{tT(s<`wA,YH=D{зӼ~sbmlpvCB>=$憃3%^7fZ{@W_{ Qvb`lN S4$gJ`e|1Z~N4]ZFumwOe"e,\IA =~6a ? Xo6ɾ+7 d&g;c7Ou)9go,/l3@q܆o9FG'pj a ?B(ҝWٝۯ#,`0%aڢ˧, Zf%70o{uMz%(/4-p>(~k÷pXÿ9E4XAoh - =QiwJ4|m,!?=;!pOH0G)_nIC')/:Ố  XlQT0|9gE,!O&Ү=|r?vaxZR?)5K#AB(A3$/=9MKP2Z{}ډh`"B& w8i#1.Mŏ)zLyG䙌ٶؾ)@[4-pg^.)P5 .O`@y!om.t}ʾ^z[tf@)0>Ү=< egP16%ߔ":H^{s 駄d' }=z]`O  `*'3gg 8XF)#5|QЇW<4Ͻ85}DZŷm[r-CpjuIIo#`C߆X:B/D 'efjXZ0,3jJs2W="wH6.yKu)(㫫 Ib # D":ϩh^vj>w^궧 8#B]f}_+jba!M"͕ܺɉ O47ⷚI_D; yBcOa&lWD>4@uko38obp_&b 1U-CGr+RlGIGDm_S!rMsRWwG|D'|9& ik#y&1cc!rEq1`];ԇmߺcSǮ/!q @w00!|7W$>Vݴ  4U]W?\Ws]їCu4f)vKʕ8IA "I=z ƞ y: +LiMΰOK1<dL.zfwts ~'҆* Mc$˚`JMgn4-q}_'A@"  H yw؄pe("M lo##OX$bG2|x9΀<҂:> qo);H$b)wHԐ䟀R($9!}&2X}g,Oc8p #}!Pɩ1\= lk$$"?JǨ,~dRͻEΧB,BW(953bWѹ\ *mCG NNL# a `'\+@&x+0pz_5:y$+u ` L!+`p/:[tv(`kυ{b8[9k[YnI Z(h:z d"j/8~۫x[o_I vUvM%IPǔCPfCF RW1>xsݵP w8N K*UmovL|_Hog_̍2,Ayؓ3pGHR*={Vr7DV7&hrt7Ha,d;1|q#u9CxF?9$$2b#vUdh޾W՝{!۷\Gvo=;OlS; } K!Yho( B)tmnroCiDY^a:Iě-_+ ;J"\{z@$"*jz0!9rr8AR<>^c ^0AN!LBYBu\;380L 9^ҁ p @H#@UAQAx1В#]Xg{W8=KN#eG 1 節ǂB>kdunxwf,0n$V;OldK6CP$HVCmPp2Y!M0&ưzNnUC?ݷ{gf'ݙ{שSd#֎RY`J^g HO"Ҕ^(~Hĝ_[P3 O>5Enhgȥŵ0IK%"$i5_B%wv뮭ue膿EtFat5m%/ dاl&da JSʹ" mH$4, ֦ f?4U$qD r߮t]{q,P,F WM\H'l[[DE5 2LD[-DKhWL) eJ-(dڌMCLd؀p">JFDb lD,4M1&"fJo>~Cзʨ'^lie4f-PHeߴ˓y,heKdpUv ,s~zK/p *܍c'ڄ6cf'0N – 9"`mO!Ċ`{j$P}vژ )Zv}6iM\[Ij3bg뗬g0sȑBq`)ڎU\|Nt? ͼt|ssm$F&"ʆZ1іB! =X{֤+֕c^h_KoלEqm?B1Mbu,"m-n`"a[OQ_%fJ'7';ρGsɜ=,Hk%]tmw%*^}$T4]B[F?A=S'Ϗ$Ft;eMje8zH"!?_:o5PkbJE~]nFRI>R,ܱk?q(:BT q fdž,A7}d;d`gsBƝLOIuʼncزqIz6|ߏE1:"ZM$*T/N9gq B-j4qٌ/yulXM< z1Fl-#i~`&IB-YhmTޭ용3gϸm{NmǶ'B\VmDo2>eSwA(fG$G,@ӹW oZG6b.e$A 55Al|!.zѾ7dn;_KXDѕA[Mix=ͦKJ9@Wm{}a`n֋|978[m&$P2R]^eWG%WMh4L>ujֽKȓyc72"tC׬n}3n̰EØt}Y`C1 !h/EExPVݰm pesbf1!*tu~@cN)#.+LN8V57ܲnW6/?}ǞI{5i6"* ޛEw,/ j,7.jsh3rZS#{,LHxCt3qj EFk.J\@Sm+p8y`_Po]yEn[lZ 0ȐLX%#R~z &m$Su` gwqY[=*S$to=.>&B~DjWo!@~&Bm秧6= caW~\,@L¿*{~^C؉ԂDBxhN$D }4 0S7,ܮ?B\m"2?c &-D$)߫Mo"| a{BD7Xf G8p,_x罶 ",D$=M[k^yPqk&f?-= qmm1VkwhNS>f]}.˛붮wpNbGEB_,wF4뺬%c@jJ`||NDu#G= mjb!.&'RSB\M1~F .` p/qX$ O)0<?97< RR (l0HWDnGN8ٽgpl>97!(E ? "/yVp8G$ 0d$9o>W3N8cQxge?ݑdR~z aN$xl6QIz*VUbX*VUbX*VUbX*VUbX*VUbX*VUbX*VUbX*VUbX*VUbX*VUbX*VUbX*VUVkʣ-+߲/? D̮RcNk-V !ڿ]|c A6Z ," A*@ZpV>K,֏54ڃ]q/)B#5.-!DB'(W)9Jd ME`yJS1XmVJR&֥V|O:6'a[j2;36BYCP,(#ET ҩ p$ E a-yߧ]ld|@.3ԋ0Ċ|e aF 0 B9 = f-G&LsKSZkNS3ӽ:^deIu"E) E @Zk,)1N i6b$]ˆ~E:ʅѾ;\O82V /FHA! G뗝s o|Xl5evQ 5&M8sqS/1uR|;[Af4!x+6 Ho6$J*$ )Z B``'+6JXJY'޳!{q>)"륙 v_WX%ȿc1` 8‚49p\k3{?1vKCVcK[pp-M28"C*@`2W1gΞṑ)jq?=b$J#@X!,!љ9z~-rOE.(l2Oᦝ፥{ϙĜ%W 6`F}~G1n(RP˻wL9 q28 OLW "L-8LUЉ=w?H܅ cc82[b Е_Te~vz! yHs Ǟ葧8}<Ɔ~ʠ\$q]F 8(܀38ajr)J_<@Y_ek#}ܰ5d݄Ya9ÎXdۚlL@J|qJ&<<ypVK Fx2PO\Z#qxO\ ?DXC&'BoV 乩Ǐӳ_Ώan8\gf@QġD]B \JS]1з+lV)|fgf8sK֐I}toߏN-j iA+5,F #J0cdm"JM)u^uMҙ}վ'&l屳w?tz1 s,Wmضm#RYenztP,&aa1Gx}x<lFqU N+p|7z}ބ$N`d~B7Ԉ 1$/قP M|D C+]/DY_ԥh ;nō|,V8rI.Ǵb&MUJo+{ٱΞ.&**k"|s=t:W ä-ciK|۷nޚzռǫ?¥91}/.\-͎X2Y\o=k7IH8O!!3:}ǟz#GNp8i\~=sd5gZ/ոf38af(97?WfqΧu[%\a(vQ(<"ˈ|4ktQdS`I!ӧI_ĺEYgn1&>ѷ5Ys7>̈́pFl(I)±h7H6ljp  Jbxߛ=pޮ^{J V3]O3OߣOO͗&YX:CFVk/ε{ XV89J+nN8ԋ8؃ZSpxkJhj˚8iґW<S "ݓaa#0LϳiHwr[n%(syf'M1>q >\k-j+{w,/WƑ.lP%*5ñGƑ'gqa pq Hl:n:?o^U϶8vwK$ #GI˴Rt2͗MA _d' A+gɎ8MV+y^g[ 6`bI:c.{3>$B'S1=Å1ZDHE5„Pgh2kw3*nCA#2k |%E@ 0 0@_xہ^iX%pj'/~ɧy#cc2@l':@kL͌X[F(b %='gy#߽y)nOxb#˶]!%aפ]> Gl *$ńy(ъk%O%iw̿gifc1ܭ\81}?[,y Iۑc߻ʍ?=t?C}z57p՛?A'[A\ɥxS|_3>> ;`xw~Ĭcδ9v<.Tx bO%DkJ8@Kr}:^=xߋ=|/c>3H󡯒lbP tvЪ)ZٔT"?~δ|+xSGyA6Z$%.\8E5Q4'#ٸfv\ZD&aeh\ 1 g('le*IlDS"m@-ԝ?L~o>{=_o1sNo7mc}7*L-QOCJ>]. ~?+>瞡ZAUkLdÀREnō7J&b"#58v"^jmIq )Rb29zzn#<>nÍﺊ,U-C&39TMLy >~Qr%zbzg(;]⑇|a9* uvفr.Qᐤ<'r2IuK瘾4Bǚ $ |''_$5~-a5_WJn)0-h~cc?3ӛΝ '\5箷\ջא4թI°P)8ϗ>AG9{ڤxNߠUi4NeGd3Çp]p?3m3 wLd i+.)iR"2>q# EG+fTUۯd2ާ[y5p'a{)J_ .Fr)<'C5Kl~quMczUz "r Qp{>ql' Bn[._ϮiK)<2"fRG_"?F8D E$ǐ~: K8˚ :P,NؽF)GyK߫_r()MP~{7%[ i:"|q_t)\4T Wp?xL|o12!&x.\ヌE9(8t3gx/?ω=L_OIi^|9,-P[^" Wtt{i5{$xɧ8s 1ZFB _Οf̳TXy }뮧G(58r7^}H9yWD՘X}}/<̦N r XCgQQk-3>;E3Gs9!s qTeWdzrwKt0ƠAIlkX;koxV ƅG&ްށhA8~&kzB8WB R GO =|o)p獻yngc*̌ b$pdr8cG>ʗs/NT; `پ&ٳi.92[Oԃu/9OI1'AcTC^-.9};=JH]7?*/9+ aAl* >6"|ݣIѸ1EGj$,/Қcg"ݶ噔羿_}/:(zC p0ze(4y5M/r!5__'‘W'꒙Fg5 o β8qh8:PLCh EHNئ׫1ԷDJW}%;zaK?G_]MWs.tYh@)Kyxɱ_l刓V+<QPT< "8C};4Bf/<}[p5d7^Q[ac=eZ y'}=@wb ,CE#3ph s/g>ŅLE').,>.RVu*A:cN7 ne'b{}'?5 5Oރ`]udVLU!n5ISMGGʝzXVyGi֪T*!OM:Z*-w2|[X{Ap4ճX@_B)Lq=U9e~d;ndyrQ;e߿rA_\.?Ɋ[4-w7lÐD|ϕhѱ&, :: m3 ?0d4I n牛 o}{}6כT ﶫrmU5X~= qʛw ~K?~2mtě(X_꣧QSGȡPn_sZX@|?x"q "2Tj38}:bnaHhn8n)ߍ@0:6ɓ>$ a-3@ceMZo`[( T'Fxpa6A̐XCڝ亷a2ԤA qmB}j=W)$rRuT^)y! ܅_?l7R9.Ú N"]"21k_wg;lbؑPI ٓ"б9 jAntoقE>0L˅s|='p'KZ m죻ug ]f[^OS|d4ÐV s^.㓶, ne;vk[/<=M d :@wKLKImjh"m`piBHiQ7\~ԏ?tl[^G"ԈPkERwmerDX$n&%Mʭ$I֚$I0j|3|%I Q&HCUq, simrZPz4_<~+b'އ܉!v.Y|9C7=6ι.ՄhA-06?_E2dR"*e;Nג@Aә( CМቿ O:yT)!V-7c}o79WP[LjL.4QBH /PQsϗ!Br% Z d7~/ \<ӧؤN&4Civ~"`[eq#$KXkvѪ/TٷoWJf=5v8_M8sPNִ7+b: xI+ap\B$)C Htֆ0lCݻ1N>1]j@U8& HBaИTYt0x2 >6}v8An CWyd{U\ه>]ӚX"1 S<$#S%Hمg b=0KaL2TPdGu|'?>57_buʺNo`]W'UcqiZ{km$/x.}>JDttttbfkA`ƞH#jNBq =/'J&,I8$/"Yb˚,wodZ 4jORr/N|/N}b=-d8JCB cBJ2bTF^8 {!VLM-Q Sm46&MSiO}CLOOJ$iҞJpH5qnX\׿+TJ2.=%e=rkg9pMp.r{>p Yf XF*KZPۢ$m?ü׿ )P}a'?N~$N!q 2д&Fq J/۶s(R*n6^8~y6GvFuZ# 7p׼Ƃ3i̜#_)cVIHE9-γ<9ET=Okgmlbۖ>vngEԾ}{_=A>/?}W9=Vs!W,')Ij3gm,B ( FJ-p 7oe7oua*dx˼ ]:e븤F`@z홰&MYU @i r+"V+xwoǥQ*D8J VfǕQDEJ%Z?2;3r\F| (E4q$abdH'ZH]anO?zXtgulwݛ>gqE&NhiB G{m؃{p>=q\8$V-,U&cj ZCcvffgX~=KKK|cex0~83?OLP"ob7\GSD/`՜D&^4W61 Ĵ}NN8;NX% )u¥XH+wQp4ϗmWG{>OSk4}%q bPN[(i+sR]K"zJ>[GF˜"PGYZ4tHVM|gjj|k dJ]RNDy-*qDȦe.ɷPU1s㋬' *_ys~'`|M "Ћ^+wpoΜ广 2A %8El֍$CVA ;t/ l!+ݻY4u8Em|˧c-ɭLr S/b%iۿ H$-y7/ ,M^]u =,b_$~6@G w-45F.\8~=y}_}tuu%HRRő $i0(c4ֶM,!%& (дW{F]A %mۻ\b|4,9?QkItDiq G}O|#\8S{=C"J&ؼŤ@E t0]{}tey 1{?IP-xd u&]֋0O~L~C!~$w3n kssSvs۸pcaLmZ*E?dpԦxvE@sҚd:qK"1!gA/\eqtxrV[<| Eyo"SSSLNNÇ_A&Zޓ鯞)AF bbRWXKc:Aa"@+D R4ڦVғ8 AXᇮ_k^{if/t}ZI.Ű4ibmHuB%ffefw´p)IȵfxO'A<Uq[ %֬`3u4a(:וm(gSrOc''ŇC8NK&ظi #Z]ƪ2DZPH@4c&咤 eD]v])SÇ; ՛>箙e]_9&"zs Ҭ|D*% a|\CH2"MRxOӬ,q_ug2LTWIJ"$& KDŽaH8^kp{WPYʺ.; ߙ{>t2X!i%%JHbĹ=l_獇J#CC ASY}33V$:vZ6 ,,8. hruג$)/<7t3ey_b \lCtkjA[s4F^DX&Av#rx- bqli?tufsxBK+<%\`Ϧ2~ k"Eʹ䲽=Im톇_~ןNRIbn{Nume1B Z6@& 3~%Itok5JZUc'.<"/!El@3u0vI)U&#. 2e?̷g>a1Ay bgѵ9AA F>e3DRdZrjuz oy[}-[r~hԗy_)f"#-I}s@aul᭔; 1+| EB6Nw_]7]IGE)JV5"ۿ_Ir+A|Ksп܀W1+IbM\O1%jHz8cK|$\2ٶ20s:fMe&'g*FVJR!K BR<n\]z38N/J :ZcRTB,#D:.IEr1̥;ڋh9 9: HS}xo8F|Sgzd<OQk5I@e֝tw$IrmѤi z׻x;N:w}7[n{ɓS֩\GT\;v]t]y#&4NOADhGp) )mw-Д58!qQ%LXYt\lEZIaneؼn FGmKA:Ԣ%.\'/;QWo~Gj[]"S<_&-b^Y d{\!H`uJxγ8H܌H|l)˖-q](kSSصKJ(34ӐD.R#L \6ÿq'*ĉhL`tDƤڐ A,%~#a#&˥X##p|uItp;7nسwKǞ~~ZyYH FcKOvCo_8Z&[az4 J"֭q ?Ooo(ZΥy}ȖceԦN!*琭 <藺fk &SȔ0_F1K^: Y +WT!x9ҰL-&&T8tTE@[T5yY\Xe׍7nHnN^y8гpĤ8^r0qZǞ=XY!TLB,,̳c6{sQK~^\@) O$,1WtE[Gc3Nqd6 ]kqr]ԅ_U%d Y//Mw"Jb>OFep:,մ d2.Wo:ځ2ƤX,:`VX, h>Fy}/?# Xj_  \-x:\r>ݽ%H^Z]Y щ>Fm~J B$Is\VBpX[`f|$Xi =,JBI2IZˤf-ۯUZ ?ۮ݊XЦEST!!p\^0c۶![\Oܗx _zB`cJH)wvuVׯ #8u=V17p=V{%7qA|>6mpϜ"9) ư0XÚ+D! U.9DYrš]ޝ<朎,ͅeS#4GEJBsc<<"Ff[Klɝ_hλ%ւ$i¹gkڎ{Imfڵ]ݢZj/wm)\"2LG@gok{(w]YfgWC1`iqBR$զB@w,s8C>Lk gNa~+cA(bHHSgg%)ov-WoМ:g'&m}lmv^;TϜ=Eښ*dv_QŠQR.Yz40Yʅ y\R۞"R.RIp:,\w.nr::F `7C "lrه+Mgh&Eg3k"4ht 21*Q`,XJ QEuF]lXM YG+LjYJ`^1@ 0nYglrr۷o't0҅.LЊZ m$l!$VPZa GiZ>' [<'2L(sDQiFkP~Չ0{If/? 2otkXAoO? qx 𘙙&)hh4:tF3g<Ξ9]y~w~O<@9e1QsitKύo@ƙ3$cKp#xrh7neZ:J6"MmqDK}rwb#OБA T=*hWANi%7`az gC*y^vl!гJs')tY,24GŴظa ,sqNS\ոO}Bsn7}Kva*hgs8^e0AE5fp]*7d|Ξ=B{#]:|ukr%(#1=5Br\o(ۋ]3wEd}ѺDu Aw> )l M=9H<"5S4.!+dMw)O)AY.JHVa\&ո| ۷ 0Jhc `i9!f6!N"M  ?ѧϦa7buHXj>Jy8"P`mP2ήAU,Jȕ"vtKiϰ4 aR.ٲmK U 9 X^no.\Wa y(džmki5RH!Q$*D@eE) 9ЭypzrLcQ)8%48) p)ٶu;bY:۶/_"ccl߾x< |3;r|X#QոADW뜚 pN^A3qNaNcdX#t؃׷c0&Ȳ<9Js44Q*<)3!DJEJT@t_BE!.V[f3X~lAyԩS Rʞ={*&Z?O2h1BƒhqHq@(C*PGu HHYX9mZўW%Wx7BI.+fe34 zY_f8C}z }ӬH.Rp |t|k7yUCLǣOOۋNv)MpJ4ՠi t]q;k]ײ01N+3^) J&C=2 qDp`r6 A;R)KxFSh4={ރjYtAQ,d*xҠ"IW-!"-\[#+o#J9cB $+@vt h'u㸄JBww'=h]%sIUY4b|0WSšE pӗO=ωp4O)~az!ΟTĢiʘv9>mkѩ&MP!M5Ǟ}{8w,4 >k8I܈0زCǞ{~K,y[ED3jcx&+]ny3"٘$ҚVm,IdR)3d<Ϡ=pDX6JZ j<qHtRik 8^ Dsh<ӷlbyp=u ړ`c "B9Q$[:mg[(+{Fc?~Hᠭ~iʇ])l8mZ'!e8v-Q,dY\^`Zc{Hx';}*7#mVDq$I1iH)wi.ҧ.p.|ٰ8=%<ǣ)QG^wc/rTKy'9} n C6^d""(),riI 7D'Nb*1hB:+z5lF߉U.$H @4O2;y")B &f%/1$n5n~.puwwbL m"\鯈]m]*Vh^v̿QXk*A&VbHZj,ZiDPdqse n>],!Y9.ȶi$V۵6+E~RvX:h%ZdStQY 9K˨@n:n,i"'ELUb%:E4$qLk#,]l޶ng>F/GE$fn ~@Zgzj뮽VbddwĤ3.Vl QqWMמR\`<&CƐ4RXr>a>|(a 1~Ny/"DN~@(Gr:K;4d{cwuo‘q,qTnFX(ڡyV|C4K13gΐ}M~$IRGc _;ߑ&KMR3,]C*Qi԰h%2EH]nDIUD(PJΪۗHZ0r"IRlP`%8N+|1Hi<Geh6LN1~z-] ٴ Q%p::JDQߓI +*bS(o9xÏB/m F+;5QHNR$U>HF*LH ff4Yc л\q W15O}<"Ƕ XQ#_BmйQnwJz"ˣ4&'2YBk׬ap)x*CFu7MA܅1I;yNvp?ttu۔FZ""ŘB&Mbcȗ;PV!D78j}oCu!Yd99$2'fbndvh%!Mb'"Ii, _$]#B#(ta/:ϋ@Al=#C4!}x]7cJ}v4~^`,B/%VxmceƷ·20ZWTZ:ɑsie/%1:_wKN:o!ߨA^rЧCw AR=ycgW%4"P@ 83YM6B g%#ڴ/v])ere7&U.Iuq@ 0,01z;9D\u 㗣QH>\Hk@(XYϣDel%v/N,▋mѡ,nH/ӳSmBiW)scD0)qsy]|RЈc T,MOҜ&:|gɻ.`<)69Mi@ֹ6IIr=kmWK6aw~[K|mwATTG<[{4,$AH% -N7x|),)AVƴ/K6#-횮HMʅZ/Z}(iiMb\P8~KP^KGo) aI%Bh+[(%X0B#S iq|<s XÝ7wţ->,J+CŨLN5\d^tw tx=ViN&pF̡|yw'^v:Ÿ# 5 z~VuWTvi$#8!m,m︂܂<8jғ˜vHop8ӽHItY??{lO~ ُD1:`tb((iv:J:vJnwʶB"Ca$dy ( I/\>Z8DJAou}X>lO1+[?]0ZsΝ?tW+goMq3y"|'ylI3 CAy0CqI R9!im'MԴæfeNdeXK pCEd<IxQbP!St&&Nⶩ&R)T: #6iw[!u)j1zBqd4' '872[Lc0K&s8l8:6(SD2}1B!:V it3!^P##U 7'){A) RB+\!V: ֻ}84AmK]-qrŷ"A_W A\scщ&Vdq" -axnfF%rA #u$Z8!I4PD]Hԉњ4]İ2ȯrߗ ZZT]hڋ[# O6n\Ǜp{w "DQԮq[)"DX¶s?4^"7w_jiXyOX+B$IwNѧ\AG\&"^<'7ͳeN $"4 Ioo}'NZc㤝RC*V52DR'hQA(P$IQp])+fJKHu fo_qq|L8 WG6x5$Rb-ڢ+%q3LR#Up(Fir\0{_$6",&q1SoX4E,u-٬= ltŏsBS_+pbØ?z_'SI򲐯w3 /%0L hhOWz:}q|нm/]Ξnp$6з //u n{[rŷHlhS3Tq-(/hK`%)Dy$Fk4 -/ExٜPJa|9@rnkk-nDzL/-Vg"O%YVr=wo/o"T9b\2QE18"\ódI\_ <(u JjK䂈+]-ýct#Q//RikxBd7㛫qI~^A$MS;vx&qs٪#иR2=Ctjr*h_#Iqʃ nɢGzލ}89Oض!XCԚ1r YܰJ.Ac<0bXB,zX%_4i}Xrk1m]lȗOK cJ`KJ4a+%jZ1jMB""%p-AO/nF]x]$&bbMrHj4&2s3Y\ \+-A V9X^5uqQ>Vfjojo\{[RW=$N6VX$N&=L X`hslm*rabc7vRٞl2E ^D&u9R+I&ҊCDq%ahvjC`jH!0ؕ%ж=FP,8\#W' *Z ]@0Cw݁9aB bti,)G )#VcDtPĭyʁkr?Ѝ)i:F(}٭xY_֢oUl~#qQ|3i|{)EJ]$w515=EY/uO{PYƱxoY}[4̟"9Q0 &IpI.cC#IN;i(D{Hru<)`%oVB VR3Ŷp%J]hg14JIcҘQ!c x ӺD\@ S{ sONH " H"(Yd˻-\rZֻ.I[MS@ E@ ` D}s13iq{p <۩M_oFjNq={j c^@$ #alATIŠMJ2F;3R:YS".7h=S*G8Yb?9/g."'%$"ѷrZ 2i4S6oa?~e7kK ءDn/_seʰ?/m' 9!7la8-P",ީƋ:B #x1B"} X{/ᤠ>;ɦG.ĥ8Y_f0pn}4Z0BU]9Dlwph ( uUZԣUvy)̪Acqrת~O^ARG~$q+$祼a'/;A}nAGV6meqi?"^ Q#EJ-0Q166<VR rqI(:pP7~r] (u첡cmh{.b|kC3e,ar {tǝ|8J+DIT $;K΅j\#t;]Zynr޽Fv؆3h/D9,;z괋rӅ`cV^ןvbyyy]:HM!T@!b!Msݍoc?tKLLN091WzA|א6g21EKNLB+,@+Q#x4) Hmr]B$3)Nf?paLr$]b<E/C'q8:$\@6yju|ɝ0 ;@s;J*҈$Iщpx$% GopZ|B'?& Q2b<ɓ=`i7#.{\}D*C*).<[S ԒnQ BtαZR5xu;b~ uLa1y qP˒= 4AII(B'353{W~^}jbsz!$:pER e*ơ]Cx# N! ;P<(/F8O{e<=G^@tнsV9.TKV`Ntq Ft;h@BDHDaqʠcaa780]vt7 @>S06tR֑ 1.% "͇!Z( ~kʻb?5AE]3gp?lwWQuھ6D[XMmRW,P #M'{O<_Q*k:?O+_L" L[IcZ*ٵkTa'R/֚Vdkk )qk-^o];A8;"R*VdYm۷SW4~aI*@(F!BGQC8M8YPDT`,B+D!fMn/w, _:A/< $ yԷ^ۃ7ccK! pagbXzs/Л;[^FDVΏU00xۮwm3xW}" 9$_?!*[}F pjjX  P\~ɓ%yO1 R/{x^ , ܔJb:!FC$Id\ ,dwt]ڋsɋlMӚ2P]L1_қ'=A& T$1:'/NHvrok/|{x붢fF%P: I^Ԓ͏-v91,YFhKn Y2I:=+/ن-Qj QéN7s: Xkɲ B}+'tui/0Di, b|ZKZd8]q UlSJwxq..<w"1Va O 1RG#IDO b涫ӗ"IOIdp's)Ne"V1JЫq%A.F("].&jy72֚[Wb@֣xL!(('LX 9A8xv3:+$27>|rٜ=_HqvvNv'J֞:UAbKlͱ.!Z訉/rH +XAZ!J00` 0#D0XZb" G6Ewi(f۴hlNe8"0t6G}E: E̩BGhxJך!BG$QL,%JXnxU|]8c>ʧZz+,q=?ؒ5!S1J6ܲ# "]vQz\BCSRB>O.:]I92w}Nf?s[[^}rF8r:-kt q4.i67B$,-"%m1OK*PX; )#CH+rmN>B%,%l.N"$YVP- :c8t5?QMй4Y (:,=S]oַF]RHEAYG_#}&agbj(I($HN!DD ¤ qꓐ/]gYkvx]y`0xYWpDJy9w?UR#"KSy;gxz!0$INc >@IVipoHk› ?o^#X x(;*8yyK .`aD262JtX'.LQ !:,:"GHAWN8w^/Vvn0rչ"EkSǩmŮe||D pyseXYrS | [p ; *_ΠKD'' )_+dQzEQ8Hi{?N;TW]P;XZm7_#ʾ|Ԡ$  :J.bWqOsp Wc8Wj5C:.Y.g8lcEF^Gi*!Vvȇ]:'N0 $ Nk 5 " MG ke^>th-h<@ppkZ+>>'0m(`SL V@!  *w/iL R:~e*4V*Vc(N(+Y0 XZZ@Jsgs BGW'}: ; -"3{ ѣ|c牕$qXZ2 "_ })N!* r+4|I=CM5OFb RawԴ#(*$c,$qB$LMqw_i+֡T2"X%>IgsG>J<š`$I4JbňR"QBƩ/" 扌HP&dD9$ `#:p~^=:IQ Rz8`yLAzt. hE M>. l2qh R1BGA;'4 dd QϪdź%|~7k/CDCJjxwBu_<¿Ҧ0zX#s ;9H/8e lBźI\9aK`7"%Jࡲ($*-QW"̍O45\9Sz|y"&[.z%tEh*Zk es ;RXPX]1:~>e O޳΢, :)yUۃw9Q2.Vh<}4\u<'IGؽ1M23C=!S$EyD:<ܡ$Jal +2W~Ô0S:Jx '(ΐcEq\˟U~l͢2vv[/®"ˉ>$l1dM_{/:¡ QY /"(QZi .| GH@H!abg0졁Z-%M#DJTI`:.ٹ6nuԚA:Zd @J_#\%%Pn0? N"ҤN-iNj-UI^CIUv!nXD*] V`ml~e_s˪iRFX'3N"삢q_GŨz]>&ISAnYVg!9sܾjNLIUI`HYzѭ%ə/6?=FTfRy_A*YM T% #pS2F%-R4P^2ld»uvORdYwOHQ>) HIT G9B]χEJ![$nc9=C$ UTkYU)/qs"ZH4̖qokqqϧy/B&ŋ9|c+UC (d3nP$A71"FXĘ Wn?p׿ZH>IxW\U_imTs+;)!79E◾ѓdsHDBz_- Y%ڄ G8_Ŋ\&iɇ(ڻY3>nUA:!X8BO+< L%룉[k% Y-{T*;g!Q㺖KiEZ! w@)6^v}[|388#ģϓ{$H3@Ȣd:iP` :6Mq`d " M^4U;k}.5X\|"DZ@RTTBB\hwxӕ%v=HGMv*pD+~b;(Y4>$73ZEnPޡ ϿgXi4!_Vc:AKaď[ƾVP\' a xx0ќwM?_ Z5D#1I"lݖpWQ%1>4$؄5 ^WpG} F;,;:V];p o"QlۋR_˭r{)LAcÂ<'"N@b2"Bj@+GU686zj}vNطzéaVsnD $)2;BVr2LqB6\d%0XqMv_~K~*8o}ӵkxe;y{:=9&|%'2ѡg)IJݱwa;- *´$Arں>5OyareXt lp ^zP"xJp#;ogۦ E1.b CR!Қ~~ͧpb(03r_!4Y񉉗8q[˅kì?(2IO, Ap2x/]G1mǻ_.n \~eu.a\u|GStu~\r4'>&i_QT%փHJ$fYE="Nq[@6$AV9QARM cޡaq2=$Rc[\~P<]_ї6cT #?ꨯu5vB"-/So4JYkBEYĕrla@bBuGY[o׿2cMtVc#1EN\K7+gaCVcf4HC^*Ej9* KڐFEg+b$K s|5]]wYd'7C"B5/iRCVv%Pdi 3pÛ;nD)Q* |j:Fc#:pEN8rSݥ6p0$JFk_7]zƛ|AiǍzc>u5{߰q{Uyhvv4 lB_`i$B*f#I'gq0!C")Ge搴=S-䨐#@;4ǧOnЫIdV3x<(iG@~|;xuo1z^ZrLrӛUW\E,=RF4I~p/8;{:նl69yr9-ss:xat3V tH6@)DkU:aX^9m|ʗ %K_SAPP8JhPARY[ l+nG;vi!/ϿȷuHt;ƶicH0I"c&$tL4jILrvnη^mDbѬ7?WK_<s R}OZWH)`n8sǏ03Ź9я06%~nGEZrkZ܄Ѫ4ٲ2DXݺ"t ➬w%CJkc{{-`n8y^`5n|0jy'u,ɓ sܑ=v'NAmV[Lc@oy-LC1DZk-SjQ3]oΛ&M)DD@'(Qy~Ғ*ħC&EX_`9.1;C$Vo|V>03;=_G2J_B"qx) d #ͳ>CQx&qZcc4'J(0j)Pc 0GD~C]xIkI"-&˲j`^rMoKQb$!@q T"cP:f$"Rc6k_2nl^% *ݹ"g0XF%Cn rŻ>U}9#(Sw'~ cqd[b fF\u1wv3f=[MZ('SS3u\ku=X?х'xj4;Kk|<!InU!Z{6H(U PC -^8IQo6Immp@oyn|;J-cEDGqmH8RB\ҒR{? ~JɲI-%26oĥaϮkS%T$#ꝊGO)D*?<凹SQęC)dq^{酁Kk&' |͛\n [T)v:ǨNU?I!|BvzԳYV})*B@5DH]9?K$)F3HH☢((h"/BC0(B*ć W{ FqsgI(8x ImvZm4RjБFoi,(e\?DO*zލvX5k"p1D/_{_V{)~!W^y>w-lGYU:zNLNl?c[kT+kqAB!P 3%%E瘘grrWj:vT­uGX/okDbQȍyMJpF`mȣji $()'3\|% CBev"u[bP@ iDxȍ qadO>q|0iQiۮ{#7&FhZm۷~hV%IPJ3<0XuE=7H*+SqpAMj,Og-BP",b[bʆ-Byɚ'VJ\|-e #R+`hsPjb ?X]Ü$QJ-M㸄{dT"gr.  ڃab1" bYR#|R _l)]ֹ抋JE1YP>0;;uغ9~_{+3[#$1t:AQo0]cb|89q8Ǐg-mC\%.߿dE Rd-ü9\yFQJkXZZCLOob۶Ci8AE) h8)EiD0ۀچDQ NG|eרZFo8ޓwyk]︙fpΑeu8~hzsabYkB[^/Ϥnb fQ:*BHȝn} HٺuZGX%L̪݊+?[h#TG.븲_n/AoOII٤nsA6o;Y.eutu|ڒ~h>fXU >|)Zg?/|g%cXJ yL׿wS9֤eLLLn?$1i4!)XeiEQR껨/+2 crN?`8dӦMweBQ9/U x0P5&!c*EζcCt116ơÇ9y$allASD:*iF+?ul&1+xĀW ~I,e!AXKl;^IK_XusmZB޳\z骞 U  Jf*χc;Fcjfz^2D 1ZJAe,1H(wBkde ڷ6;B&ZNBڪX8~M3(%XmoheȤDZťE1"EeCBORK/{ ib'M3Fܧu搳 ~8>P:噃 G>0"h]cj |b$wҋÙYOknzXkB|3@E?Zvo\|Ǐc0Xf-|\hKDn9*J򵖗G㧑RA MJ1aa $N~>j8.i52aϪ tFѰY t >wG'?L@@'q  纫/={ogff gsYB$^m/+־x(mdYF8?Ct%k!90GfGBU#|`Ot՜w9U Kh1(˜ǿVK/<@}s8**O1 Φ1v8T%hhqli?Y>yiw4Z-xK4nz]ro[La'KӼh-[^]^v`#|p0\nIӔf9sVvz~-|FTZi/1?Fs陰x̕ 'geڵ g UQ^gzw&ƟJJ}IN}p&t;񊐡0 o>I,L%D Ϡc|VsZ-{}}_Ntfe4^ 3qQڊp!bشe;Rt#ʠf{Qfl^.}.&g.}./xɉ o]@hT(Aks|b~7D9u֓9zs,FH[ⲋ/=p';nš!F?Ȳ__rXk'zu40iB$pYV r>pp];Ȳ8|p?-۷kyxk8py9,F^C8dvVfg \:gkmug5OH%GQ H:7OXB&,kyQO"kfhvbbbܬ~9Au'zm~zN^?Ďr0=rd?ȦM)Ǐܵ-E?*E{iy!វ^o`LHG%,9K½ݕ6Y!2N}_?_@f>N v7z;\ZRxkh5ǚ/MM~q^n7 uہk^͔2rj%;B%h`vv+Z)HIs%yeV*)wPQώ:\B}0i`} =|w];6# qLx$j=~~^n*hkDѐ[n-ɌFQ󉉉'W8wQQ43 i;0V5*} p+q}}blBJR:@<Hױ֠u>.g25_7^} $N1ol$[yƤEQu:ZZZ{OV;%tN>? >t+e˂+mC(| -*KJV@?TcˑcKdge︑.(4@Ixڬob|e'/rWTEoEv-s^Ր|8J\ڮڪvȒq_ IL HWCS_ǾOjo11d:F${nD{E:@E(E#N'Re)=ՕiUoRr,\;sAasv "~+|BZ 5z۹Uk,M6z_Wb ?3ƴs[XXU֙H `mwZbh(/FczG\w+܉5 5GkfGy_g}\.Vس"* =ޅfb(|"FN%NL7g<F{E5 R3L 7<əZ6y AeXo^TT?*YUUJ>SUuyXɟyoC+KSdj?U F^fY2zK Va2Ē D DHwR "UDUW=QG=<9 *O< uNjjYsڦ{il6H%Ih47/#wc̥vfVT"A0C8GURVBЕ,J<8~C8V8O&Ik333^a/ )m8j۟uƂlɊ!DQBJNZroCÜ#ҚQKkJ/߰ۆ2c̦v`Z5XP`}(9`0@E :S<Գ$ ϵ4z}+2 Yc`pݾFZ-A);sa0(sbnK`Ut:]u$IJMI~V}yll]7ۆƊv{Gqd$Hť.nAJ lJ"}^kyû>kmA`Ƙ9ǽ^KK')9 /Rq9֟By~}*"2@>ö/޻s#f||ǹ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||<|<|<|<|<|<|<|<|<|M !?qZHx{Ȇ8u7@!OX\\Aa܄`b,.]8nh#"Z-Y^ ?+/nı&C=֛oayy>Q?O3`sfS|Õ(-RY#+^ټ^l_C$0 nι7FFz88~ 8B~}FKˈv{kǖ!ڭE\vYG6p9t=8b}'O6`ai kDZM<8[|"sgׯ8vl.n߼'O#njq DApkW/2|54 ;fkW 3gw߽BAW\#?V_{#~7m`yi hZؼv{ [wpg}#8 ?E aF)l7n;wAݭ>~hg<`TMBH4[Lj"2YQY0 ЈcBזV/ ku@\B yE8uQGDyyϓ"B<ڸvc[boWy'z f@MhPU!gH`@H0,-oS9y1~⸵ |_1qvwqRIpqa#+R>?2&7_{ngbM $F$ aߴ9әӷ^ BhS6 5bUh^!|χ߇/C4-qC+;~ /߼um47p? V)-<@H1?l0)%8xܹ/W^׎Y\ H P  @.7kקl$L  1R HasA!*%92ۅy󃵅Eww~ĩX[=8n"76,^I%FF~3yt} fQ?v?qܺB Ry 0\@0_ch1T5B V204lWVS w)M 7!ν.֎!vqߓ`@h4:Z8?Q !/Jϻ(DEy0\|/\|/m޼loBR(@Sl%6bs3 ~]A0賑 bwh{1?>|׿p؇ @`]ARvʦǺJKp#`ہ(oDvp73s<.Vd~ D ~WgNB3ُ֐4zfhIH%Gy' ,L;ӰR@QLO$V&~ sy}, ~.8ΛIaXh6+Dv3n^v7.g/?v4Mn_ի =P4e Q~2Dafuaeu_2cGB\ Qq5{]5030@:uso-C DRHfhk(͵ZV`n @\C03+nӡk73 Ax橧~{$45ϖ$Ps)$%@ `~օ[Lg~' Y2D- )j$)G@,AK Hس%k<"M\_5эsIfV(nIE[t>l6G_zk7^]#H+ua7un~=~JO秧G^]<W*(DB * BH)di 6OU *uӰ=IR4Ix.)?E1 otueb#nVun~әyPjp?[kTnz¢`{Vہ];\ (MmR Z#o4Ȟ^ x|>8nG^gi?"딐4hҖJ hJ1`A5^-A^ 3I .׀H!8 M:13>5!Oۋ(rKNyy~ϥQ~T% yD+twJPyi}RT˵ܖzVw>vٿ^Vxf 陴W_Aăއ~ P(55x2s$*kz"(3V|.`]@H|RHB ))!1AH3C_ߢj.D[׷"2CD>y0ˑ@5@ mAO9SowSR,VN,Dv)HZ dL (J <I$VPE(& ]d5wonG7o^:+k^X\_?彽l^bozB~4MOsq__xcوd}'1-(GSvƞL2Ys#]k ւ$rYA.c~MBBkDv[4XH`@7o]V~ڕ?k_Ckk_?[XXX|! y{𬰱qZXh9[o+/W.Zk 1,5ȳ%atjiV}=Y | wvfsY 8GvЇ 4^| ?NW;mu) 6+QOÇL|Q-qi2Ϧu h h8TYPJD+'M9Q(O,bmlܸ܅")PZ+֭ɓֿ'ݙV|[( |cooom`iyΜ*]bm?T*Ǡ$BP|)xϡc5UB/**ԓ"M7޷a4B4Ot9Wi^*+ L&L&u,ix #b.3rB|(H!ш#hp5\vO:qO>ChBdHl4IkIt=h0~`ss{=40DG 36Rz,{(nD@I)@0i "WA#n Z+r(!D' 76Y(.  .ſ"}I<3HRهl^$nd;d|>.>Zw؄(VVH&"j3fY3<+Gh^x;2x79l[G+0xO7NgRzW^.XA؈Z3ܹ}EV3bB<5CWP hQۋ766$C9Vcg^QN#6kxcwbxJ7CG57UjN2߳(4\ċ].;<'vQO#mjt5Y 3D*Gߗ(BH!~7~olx~?[PGR> =˙>|_|7?) )dA¤̝ߨ\X+:5A[nBko&f݄||/ G%Z Eݹ.۞EZS>09#C6nwTH{,+y@jiVp$ʸ)6 FBHh x.\O~Oh8DQML =D~!$43x{_yyYANV%Iصxl,ܨiY4&(­[ţiఓ]Cp7?/zQDxⱳȆ)pd3" ٙ%GLoʀ2l+4&@T޲>d [M6<-A $ + e۸~o$O}O-//-A /l;?_4t=6C~@J_zvJdę~q2AF 'fg %<0z?_ "˔xخ{5Ozg4 d>]*" ,hXOI]Ƞ!k SLN:AC` 3ѪXYYj7ǞZ\\3ɤv 4[gn?Aog-bVɻϹ!B?6z4 tXaH`$Q2Bx-Q9 a+opG~d`/R ]m4`f~$ɐIE"<%c5-Oidu&jڌ;T?g]] =P%m =Ab2A^l4Bw69N:m$kt_|?]??, ~(@BkpƵ4-,W['@)EIͳ4$AY!M(]@!Ȁwk]QOᶏDNxX,Noj}):G{QcXP"C€Zh5 *v18$13w IŽ:0н?gmRQHaĖKj@#0x,8eTSCͣ`1RQPP"Ki(p(rk$CVAohz_̳^L qqWH1Ff~ܾqD ثUVʌȩ*U.{l N],G!|Kk2AI\f>.]C<$MTf)u8({U@,# M1#D52:8N5?q$afs]ک 2Hg V=k$PLO 䵫j-byiIQ#1voʛA N w7LC߽SO"R9- "+mrV[}jh4լДVX&(/:zn8,#өigIҵPd6}$I޾Sz3QJCЅF܈5~QPFk2<*Mk.~2dWT}A 6b Z+yS@1ϥ`6_iB)he4-xoEanߺoͿ6/Y>t# #$3ozk(繙/WiMWk@Qa;rfEIxzh \| ~G9JI0FP!i7A\ ] iF=Sf$GZwbF?/Q13W*غ;-J VyL* (hQ3(7)q}ׯᡇ8:Z6|fsғȋTQa3'!%_-?P:&,/)"GQ*sh7=VlNG╕uZCcG ~j3/\}iǖId{##R[^qY;kc3ljzmkt L:s F@az$ ՆflCݠV=#pSf+< 5 #Wl|a8^Јc*As|c=|ɿe7djH>$.{^s&"M= YB 0#Jk# ] =[Î |ZZu{)"][g?kAҚHXfvl ԛ][Uz<U.K=52`6 ZPN6`qeTLF:M9BP2~:.~c(H(Bů_׏K YrM!"W_}L!|Oi"Ia@i]E"6f |.3]@JSp.zTmP GjZ@BԻbqqKKK ^ ˂,T{GVvkebikY[qi:I:F1@3PǣJ0& K54A@аP }7blWw~;4GXY^;+W[ Y``wwAPyB!-vvٔQ8 S(Sާ>J(p0tNs77oƍ$\Hsu(OF0 &)~phu F>&"IQ0#Q`#rAڅ5BhQI#si?])fC+3g Yѧ dI,+l+03+Z! "\ro&>O /F%Ąh ʫ}ӧ6Ko~!˗/L+#xк.vww%J#p9Ah/x6 hZ;h lZk>E3`v2 }]ZXXZ03m&;D2Pt֙->1!X;j7:B3ZViQQrfD(`;3wX(2~ͪ8WV}ʔ U0Xʑ ,GeWp]bUQ89<̳2nVp} \rO<Zont;3@J,V_})͛Rl$ AM }WO=*:o";S°\~<z43 `X$.66n2ӏ~R0DE̸sJ ! j];dUc,q#Ai4Ef'4QO|WnJS FkAζY((4tUCLպ6y&67f&}=Sţ>3~g7^^zL\27ׅP+CjF` &v3#6<~NΑ;&uӼf 4t8A U`q^ q@wgpWou*ňKH ikjaJ4Yg=c#ZG&?F4x1N3g4[?$t7)۽qV=.ԥ?#38}T^x7==ON߰SA ˲xw0"Tb&ܥiJҌ&*bP($ #6;:g@D\6jyLfH@0JьELg5^ZT5fƱ5#sܺPN݀. b69+h79LjݵǐRfpr=6բ0*rӶ,`PY!K3P09б( \z Yp5Qŋtʟx衇FYh_}|7h)W'N#7n`$f =Ujd %݆R F Š0a gP!u[lgG=ͫnV/̤+ -f!0^[/8s:!F$gNpc,G-ObdFGmf%ۍm+Zm| Yٽ'+;^׵2 _[\|Q* 栘+U ýPȲ[N?8=Out:],. b @ko;7:ُTL!ax뭷>S S3!2CĵkWqa<ˡu@u#}y9ʌZ2Ɂ TX ˽+̘%8 dx=S!&sMͰJ537K ޽y6C2R0[7o!DQ@S%\L0ɉ;OܼLjx&:yr؆? 8)H z}A{>$A|yaN\9 i@{?]0?<L8|'>;I|s24'%ν}/t:<4p鞔p.(2]S$:v"UP-emUJ )%rm5,iM,,d$rt F;_(wIYzQ:W)GFX)`5pS1߰@cZ8:$~XcSx<;RՔ| y+ЈbM홉KC1 /^2A#IP9q1yV@dxNo9eڎ#Q~& xA]?,F.vǸq}…)e1L]!ygJrlj^~Dfd <<]X!NF<_gu}WuQ{f@ׯ]ñc& H0cT:kQOGHWK5}F߻-Qh?!JLSϝ S-WF7nbȳO EWu(| ߗ#8 kS:WWǧLgs64a Yn uֳZBD%ͪ$p?c}' )q;=AhƳN=r^ZPii>\@OF B86WM&1CWgk]=BhJӔLɝ-ku7AaE&&, )3F! zV"mNv"`iME 0FTT$ "K@Q@ =)vC5g9 ToM_ڙOܠ?LG}ΐse](f;nIP\g*zAR} pY/I#R`H6Wy$+QF]JҒ˾ǣ#o m{=Kl!n6pgsW\|4+pC ٱ,<0L20YojrU.e䣽H=k4Mq?~|Wt^2H^~eMO@%ao jZyRzY&tz]l޹7txҎ!+eum'A8@ #pE9[AT =v]lKEMKKK+ugX[?g$!̊6i=ypfƐ Y) \ <}z6my:$\0PRzH!Gh[#Z`xD 6_NJ8&{2(h&ˌMmkAM\YrJT<]zZVQll(<6HTFal6az҃Eu8@NuFğ1SLuv'eśtjR& ј$ gb]ZZ) Z nX q"n4,5&! [|`Br:UkLYvz,./؏gWm X\|mSkSDX_YYβ.01wS[o0 [76LV^(7fJU߉[hsљ=pVVیh#$x'+_C<)˺~-|4M֘ڠĨ;y\ TBE q8`؇$SVKn /|=>Esjb>QWn J["-y ]&H"#o&ә~|L=y2,ɻWS{A # ZאDF,~PKʄ)fč&0+ IMT֌^oEq|836T!.uBz67oppv+ЈCF'x~$I{EaO39BwI H}0pbUSkeIڢdH!=xaN>Vg[yt6 W!<"=fйɺ,$FJopI1kp#$ nwS\MV㩓$faK 3]1EѨ L"! D)aw0dSq0#y8C̹p_+ۇY|w= رcX?v ^Qљ,o # [XB+,be$IHU0IPhO|+>DaTtaƜ+Ҍt8@g| (6-ۻ[7{<;\@³bhSI:@]QnpI@) lk AOx-"5AHU1LZ3hDs]k-pñm9|c )HP˫ۛ8B?!2Gƈ=ӓ-0TYϨXLFÐF^]h Q+0#p:nu2Ԟehm .G f! d j-—<;КI'69 ||?>Gf \"i^a:0V¹3ꕫRٵյ~OC^(mJ(6NnoohoIώFcDܞ̞#2}DQà#Pvp*%*2JO"RF3W2WSN:mgBJ+e]FL3FkT4u< &H` N*{{X?~PjEI0ٶ qphx22U*i5/5(Qz߿ 0Y_xdR10ڱ5,uH=":I KMORJ_?y7o&z`dIO~3nA~2%:N`hԟ Ra5CZx7o[iYK m;q}I2|*MAhӨdpmN[Nt X^taoeCD۠VWE*T]RM0 >ock%!+1Q.乜l,KZk@yZeKe$s7wNG  yA)ؤqf{ 6;$S^`0Bų8zj֣kaKY$%1JN]?Tqyv0')QJC AH6o[8~8|aooʲ nN$ ^ǎP(6yC71 mFn\F._iSr]a'JlfL^}l޸յ5KJ)!2QkٞPHk w(x0S w@[Oշ]朝&yӺ:U'ueycueW^I20F\2SrZ`%3>\C#87;=(i,= ӝD@ޘoLW$!H#Q8rmp0)y> $VVkekEr} '4ۺRN['{73 _сnӺ_ w{A[^<Ԭ qPfeqZJVH5r }=B7PӘBaO " zI`~b2$!$ -"~`og h4#%%#,(w8)37o}qt07?>_'jZ3k"+1㦖R#B>[ jL>Mh݁aGy䗰|9=u>iN6>H |64iG!K9=t͖T2V>qom ,'?G? !I|uC ,0q:(nZt)W%({ظv;w6qOjVIp'#eWk qYXYYAFe#/A9_k@5lDDM{*tKuJ]V Uv/Ϙo[ݶT $ЈHR ׁt5SgIFZ}ӍF+F:|Be6AT:*v[&* 7,T=B4i>RO1:*;61P+APCx>}9|n|d^ID__Ǘ+?$M-iG`Rk174KCO&.ni|K__}h%?~+2C)x"gXt@%艮TֵJ)my`VUE9)f˨S^, x1zvt,{;GNmz䄟X[5 qzKF!.;H*7ZX_[G{Ay ~L *RZbKxPXs^ZZ*6o*gJU(AHyQp0Dg~"lZFyz+a swfI^E ҘV+6yٌ}_?']߅~YaRpB80 q|$6m DupJЂ 4<zcVz~G43C b0.bڛl߭ %$h@A@fD cD3|]q;&fC<#8s~iP q[: ZrNeoYbum YA ߓqm\ &MóieLi᠏`h x;wp֭BiLC/-,/S1 i]\Iqb˞R~_o}p-(Qn$'%]x8Μ9ݯ~3c( msU@x>zi[}-B$wj$i^y-YL-Ev UJFߤtx!%췉9{=IgV /ino [3H# WAufmںY=ȕ%xXFߋ$ OHlnnbu f,,.&F .* <-`D<V &S8+‘@KįtwFĜY7'.`>ud=F̬vƕ+W+x٧ܳaeeŪ]?z~qjbibNߐ<\|&,!}]xR")5Tk^"v239R1Z0qu#gn#At!,Tʕ`dJDԈ (O qvk n~<4܉~Jq&a&xAdy+5rqCQXIY慑Oy##(ֈ N>Q}ĉg(mG5 v LIP^PU  $i )%|>} .ǹsokXlSh[ >8zyΗ~D|J:{,D)v#hK`陫VÌnHawcdx|wәqiBˮ>|@vr̮ja kFgnЩ.DS] H)qт4VT[Bi ~wwxM3}SW=mnGz21gxDwFj@>`#3OE W "CULs8n Bw g#&64 ll\ 0D Օ'%o(yjر( <{="AlhRRH/MS$i I7|Zff?Y^Zn>17zV`¦,AVZ_{O>m\._~laeuCH@CvDʍy^UcнRis>fIDQOun@臣#tS4ҝq54|ӡtcN3&6QoR xr펩$)'2~Q:e9y!vvvO}YdECװr+$5+H/SAAb"bA l6`4 <ϑ)<Fr9t'UvX !S'˗j4M%TٛʤRHk\~hvxǟx O< d۸umH@zy©y" 26B^jǵ7GQXY^Fg8X9q#㍂bt~l(ɞ4b{6΅<ۏP88p]̴~}8T:LdcR= m!(Yya8K/<٬dXx @CPA5nayOxI`8م,.."a󱺲 !(DIK)D9}g^|O,o1ӴĀK5`*pU1gpd^vw뙌QV=i9A?CGEa>''ۇf :0:1ݣ:k>20u8RQ@GH:Gߘe&64Kx0ԘU+5޽.R9aKFgCz^ib'M,..cu )Q X B&VWWh@&f{L y`muG{v˯qÏC>ΨߔwyG@JP*XY;'OCq n߾`۷^,K@Fff_)[c7/M4m7z DF$X\\@IZIӋoGpcd_. T{"LIOj L zez;tԳAJ}H<ɣ<@7~>OA bJR9 vQPAZ iAXN<,bq Vz6vwvq7ol 2di">( t;ݱM+lVZb )0ZP,m)$h8.3|C.ܞUz]h;C>uxz}/@x]Z(ںkƆ4=%)Y!'ㄚل,$C,zA0!vwq}:>!{{`fDq( mڝ@ݗ)KlTc{fCYjmiǴ`1K=2xf p$MAFy(nX[ 2x7ԓOaqaZNrfdmQE#/ S5ӤXlMޓҊW,Eh/4vv;c0 WH K5y@o 0#ӕ4t:@1, )<)SRvKŠ67{p=w`0@E1C3`%A$^wS:æDtkWfRTJ!l4p$iooEߟ쨵q~K~iBC 瀵 (3)0$ bw<,w`|@$X__Ï=oy( $CQ E^旊'5Iոw5jx`;w^$iM]&=+kX=g}R t:]ܺy m;[zy{Č'{ [;[PJ! -XJRzk[8#F.-S7 OeL* J3a=eBY| )xu<ӈ#HC%Q*`K TzM郕œrcz EQ W=ݵ㩃CR$=/?w9č6$E2N#H/L*4Ie8rR:N+2 x;ӧON_‹/3߇ť\znyQ0PLKaf5&@&8W/Ġkے><6H$$Z&d64ʫh੧Nw#2]i;ndǀ MgG=߇ȵad/ 2ZiQxG \H )|N7oaii4=3(dOh]PJ11 tе\=lmm#M(2L4btWbBmr@RkW >jNdEJ\8rک]}q :ട\]Q(8^cEa;n_y??V*y pap/n 2ER߹W\WZ`I4|?zuymb6$W90~cߛ_6)<Ykb#x]&!6L!J2VFppMi8buw][8qرc凛e9:tc{vwNp~`)y6]. JHڻ_݄oD盜s!"a_/'3gtF^wp׬3 Cfv܌kvkqX4]8F*I)g3>=4Կny~^[ZhfY}ttuy;,p8~?wawM}DH=z|۷~i^* @> הREA6vvvyyAF/.pB2Y2LmWw 0桠dhF=nԘE^KD$q"Py~bR 'Oooh᳟,zݮŀΫQCZ_&+i`UA,+04~e⁅y} u,(XZ\5@|>C=ܼ? =lmn"B~):d\~9thpډSfҏt~ pͷ{>?a|Xq+\0MVF6Ξ5)EΎ;Zsi 2|A7؁@P(p$QEYmǑZG~^FKKxWA5rF|7*˽}tw,KA(ة'F?>4x}?(Jpe p$j!/4/0E΢шd bQ\(L^ws7o)&mA:*DMNiڽȳ=m MdLqJ#e Һʂ!]b}8xIYpNgia{{ w{ ȲCmKr\e+ h^ӴI6(p1`cǎKx|Aܾ}?w,i߈s(ϱF@Ý-iFj!I}tQFfIZM2-$bx=U͝f0& 6\#挟>tW\OjDףvreÖh$`̛()ee۔IB '>x~<3H }cycoowA)+9䞕2fշ>R^_A]I|@ ks/`s51#NOҳcR\h z}[] !bM(T,J97I{਑U)yшTfە22m4-lm ojڤ3m !Ձ(NҘ j<='%mU2b 7KW`}}q`hb#m M43HqiHO#U(SE8 S)eiV;ahWWWqIH)).z~!Iz=z=p$[G49as-df<xǠ kۛN>}K1& t4])5 f8d$ E3A)6l\~xL|{ Qsa𥇮* ޠ3ŵXUx^ A=RMsYH)e$Ii*B~g^NNYp@_2aU{6i̱c}Da|Ь}YbZC9Ïď|lلy6ӹN6(4W!1fǏC`HdAK+E- U ."R Bo42M' %"A)IDa R, nHC[6F@ـRdAĄ"+ aiAy.),@yTf$u߱ qau@3_`7YUK6J'jRk{MւV̵FK-֢*VsEQW`c܈KAzZI^]t:$-RL~C0u#xBp4"]2RZ--`C=:_ 5<ȣxGql7@kxB4 RjU.VvЪ@@h_Z =ځ>fQެ|8QE? e%,58\y I&Rz!%.f YiFQ#V +++8s yAR#Z0 cJ)rq^"2e^ dB)1l! CnDQ˪(nOsoU(f7T$`-W:"itY3izYnOD_w>|Cфl> P*ie ;EQfy6<Ц_RpR_oֺ Y1XQK0FtV4)`$:w刢EQEuqPpvCtW!a^vcWO8Mq8^n^,+W:yȝN+ C,..bqqn6h6ٱ +eU~LxI3أM6^הZ&+k߭sdif@!}aRzPg9E^* !f?gb|$X:k}@8FdF'JM 5 h6b<4)10U!(YH0cʌgΞAŦ䠚5=*ڊ:z}ZJC(P+bXcu,J]Sg}רthCWM+jʑ\ȚldYn;I9HŕBV BH8qd%gSجI Í۪Bm5ͬgY@"nFYgG܃50!RPe/OR?r^u"RRh"ϱav{_M^Eqwno~ԝ.^QӋKKP?ˊrK[-I.׺8U6$шb$q0"0 V g(^ L#@("^VBi $!ב& LA<0(F٬m45]_ohDZ[ڤ$,g{RU7;NO qU֚o׬? 5/7Ԩ p2VY[ i#)i .JHj@кT-CӒGd` x+tã"GQ(-,,.`QVO<yQ/t4 pH)7Z(9OV-l3ܞ`H{!+}9~Jo4f<PhTu4)OzoRE!XiVěo~3gϠ^@X][ũt:.a! hpB¦n ~d[ĥkOuD9 5ZTE<ُ_]L5B; MhC%+)ژx)2S7hh쮙Eu>DEU|&:(@l&XXX߃KK=e X]?|FW=UhQqSIC0Ֆ5&iހg,z?a8H(oCbL]y '(r[:2( ?q/^D a8n`eeնM/"MLGJiAF  2..\%X1 @M'ϓdh2&i K<ɍﮭTeuR뻛:I][_vZLfs-Ԝ-VBnNsXT MNV.GX{F ֔gn(灕5k tFxhVj}"ORI TJCJOgQa۶An X0 R2\.jNicţ'SQ(.xCZYjt~l'9nq1``JnIbooDZaٓIPpR" ֜ jiK19g~zW87+eDGĔ#ڝn:xB\͇09늢Z^8Ҥ*X?{_K (rR aj@[ fF@y@G4 Ha뾿`F#Z/foodMy^r]^fBM=MJ&lJmoT#iCQj><뷥'n)7~}`M'Ml^:Ĥ./~[?EA&Dq DZ)c Yz]5&*((Ԫך$I)O6JĞ4/4s0| č3b=WǨTNm*#ᜍu 2\&ekҰ=h!" > X]~򢰝i YmcS3g1kd Zi4 ,.H / = =Q{ʉ-wo?nZ  < K wSa4Mƴ2,@Gq7 ͌DPQ\ 4H{v;wzRl}9Vrbz=P }dyOL>~U"qc [G`KْFNb7m1j-5cDd1nܰI{:AUؠ亡 \*=T8Q,0Ok (]_F7{j7nh뚘dw߿Z.3Siy⎟\A|HYYY skS jacc;;8y$L69H0n/>n"Bلaqa'NV yQE$FDB Ȳ Q[,Y9*&|`֣PyPڛfTt VZh idFGFն, Ȧ v9F9@Lep ңJ]XXE c.`mT7Qu-띚h;T% ݡL杦IOjͽyh"?x/|}=|C;EE?e&n߾^[n:r,K(J9Şu4|z?{<T &r]a"ct:],/G0$GEQ@1Ƃ^^^]gY4pMj5̋a"UEiK`Z6_IӮʉ4AڨܸgˁjƙMTR]I8g*H1sdp&X#",}OZZ$$/}]H + 2qlA\k^Y]}/IDPEoZ(.q#J<J)4 G$2εDȲt=>EvPYPDOc Vi9YXmuܗϸ+R(j)43e1>@c2A.$/`eCvi2d&' @JH\yĆ<#KЁ0|KZ2q㧉e'v _ Pzڐ-mF1zu BMNVFZ$)f( J:E)3V “KKh !4#%|`Fj~>K`Ynp:$sVQOiF#v1 I>~C%mmӇH*R?ٱB϶6r%EMKx4ak ,4Hy( nK"Sr% 6蔣nC{gK/M}iY[oP hp.GBP4G H1DXJdMFFK8K>~ł%8A+tgܖ: Z :4Qjvl X #'$׎t;^pIv0t,> 0ڭ6ä䋻t*z7u]yw 8xzm)6771 ll9"'j}?HsQ%PքL][ɣ/..VWeEst=dYj" 0VVuH/y^flҸ!1-L{pJν=5CQ 'K ;LT4"LkN*tu,B1` /PR#nьj572kB foy90nv>&=O_q ^`PPvL{ʤY)"e`P=bNiQ]|U`1ew2fQ8mQ|"eHP \eca!, YBVVVtV7f$W+K #2 xX:H3;fd:PFeB6ԓiyZ8:YYG"/S֑چ].bY^,CQ0G3Fs'nEZx[jFߍ5ay0r0}yww #dv`0H{}tV[_V*~A"'`J_"BIVw0b M;W%!ƴHH@qeMf/bNðWH$)l^Uh(dVG0vJIòyvھ= NK$Kk84{`̄A86'cgqe6vNR2 @03 hJAx<AFaKHa0h7_j6tڵ:>5xWN;Њig7ߪ9x$I$iM~R:̀` V-(-<ߤ6Jb*9pL ۾(005 *v.z$* Hy!DI)?0-Ub Y0 Z? ߎ݌ЦgK\  L ) f A05 qAxpWV F_-ƽZ}pŒ rn޼[[[4 yHiٍ^}QPv#aG-J!FQ%VƍE+++pzfIwSfTo8U{ m{=(E(<4 5AUʜqlYZ&֎1TE!,7]i3,/ RRXoaK erM6\,Xis2xaam iy U jJeYY x^5vſ![̌0A?裏?Ngy^*AUF@e鏢l%V8B%%DY;; ať%(#%iLOD2[$tLjbn lhGW y a iF+l=ap]dP@FVK,?ТsA䃅F!dVH  <$!y\X@ v"@u#0E,//}0SyjC1;ֆh"% q'^nܺ͛0-#m\Xe7T5S?=iHr]1lj@Ki.|CΞ=3nD~_΅v%9;o.>IEɴG I[v:dbLZ]H k 29^}Sj85\a-CQ303(/iVPMpFu04z>ECtXm 8|ViUz|){۪n'~ٳݹso;wncooL]61 .udI51NJm6]}jM RBBƱ|h4F+0|sƳ~}=v#$iSFpVQŘ01ZUe0H< [K!]-bqq !{  c) YAHI)3_'O;soy~{{cngq0޵q{{{fzFkDIŇNg_H"2Y'VDHD䕽Hd8I`LC"Ar={ŭ~LsfΰgYyK@4AzɏoBݧݠqرgH(E>'(=ijD_7/NsSog"(/.8,PY6MfE_4,+cEN?c;y8X^{Sʃ02S1Ͳ[Y}/M[Y!Qgmͯ޺ynwn{*RV<pA(U>kޏH^o`ȑpg0wyB8_CKetWqd3O(VXаX]]D٤w 'kgQ]Z_i6(Zb׻eYtGEB0#_D AU|D~]=ܡa`skKKKAQGD{V.*ļHOAnVqGQG{Ũv@Dc:`eJvQ E?U!ȗEBmǟ\ш.KI:~8iIxM**=߭(~fcL)`[b2Zֿ|9b)zHF=UW軡Eȭ,w/).8)`TX[;dY0dpaqO?nI(enaL ~ABedP^B$X^^ϣ(za - &ѕ| v6;aλW9]B H:6aYt/?ħ\|5#^G\Ҟ;/5)tϥ'R#rٯ|)&s^h@re2o/&js+'qU$/{1Sqcmm vONуތD)Jkfs.2Bbf?v kdxT]E4ve!xwQ 8WO<*QAx1L)@)]uI"/(.MW;;]?4P1 (E\s33-7<3?o=?/tGX__{on$Rs;\?=JOi'Nj!0s. %>W lh"$ 'F|YUNy0vlDx;3=?zqqq#G\?AiFAzԙwOG B<5W@yۚwYW rcQ Ԫ?L#bĘeh ]XVD9R0e7pvu:CӅQ}GD}&`?9%ԋQ=.&NYJm#tPd";(n9JDUlCi֏M>R٘jV*>Gs83,{d.ϮJL{|HyoK`yJ7FUUB[EYLIs.iC7n;/ HJsP֌JF$lF&t C@GC^߇Iq>`}dqu9zp}$HX;E;,:&izx؏)[}!H[4=S?tdaymq#2Ѿ(  :(3\a3f&zY^KRgDx8Rz]jQlE0'cqǗ*i"~y󻤔Yp&oo!GDO߶:{hssI%bSؔ7?B<JbqJ*3f9&+ne?E)?ŕQRBQ[,ڭE7WW~g.-٭ޗL ynH]%fιs`8aTeIgg+lh$5)BR4L9wada( 8q[Ǿyࡸ s|A0CqBM Lu&u%Yf#o퉡^"W2/cNzswjqE4`|gH#{Sȩ\þbƅCՈ芨c{{NOzS?ڷI?wCʒJ @o`bMQ޾<n3ڼjC4ERh˦qy:ˏ}=z8vMɔvHr0 ~>~gNWΞq_2{s8mO@I2rZ`E[o?Z1Ɣrau0@J=?  B3x"3NK0!ECZz}mOz=Dƀa!@|o{淒8yk0Y9 jiz&N*D$ "cxgAA\ (HRXd"UAӘga (x*9`u#Gĉxرo޼a& xd">k=4\#"d"Rj׷_&h5/EqDdL1:JJ1V" }{.,UۉC"e.=tX kmiW8qv뽣oMsa]fviA!`~P ՘PDo1{൭Dm @OU^Od~!TiF\R\3J1{8<-24EVCve3\]]y`m[voR&q\>yS!0+~/?-{low`Și-ܾ?ɥ8NVFAǠ4HOa)UY_h. }[vV:7[(%fzJ)D,ࡃ9Tu)hmo&yȆ] C]=vJiPi'ƘmV N~iEG&qzVW,QZij]KS8W1GƯR36ؠ@{q k+R9ijk|c=#Ib)@Fc{43,Ff7nQyAvNƁU C(p8AYA2] ('pӸ81iaa[rGqDOb{= j5 N[v8`8أBA,ܽ; ݛۋh4j8`4PJsbYڋXYYA3>q0 َBCh2{=lnmި3( slFY7QK6qoqn6P?0{#<!L!GơCh5[h)>y%$qAf}=Ci5HDkes.i A@:?ơJQKB??.G^}Z&:29:KK`VXs^3n))bTKܞ1r#2簽pDgy+x" 7o2QEZ.UJ-qn" b{53G/ΜzO=y }9nnnB"2jzy_L 1a$i=N=O@0[ !@  @ B@@ !@  @ B@@ !@  @ B@@ !@  @ B@@ !@  !@  @ B@@ G?zKkIENDB`OSCAR-code-v1.3.1/oscar/icons/preferences.png000066400000000000000000000073711417327530600207320ustar00rootroot00000000000000PNG  IHDR@@iq cHRMz&u0`:pQ<bKGD pHYs B(xtIME ,*#ZIDATx{p\}ǿܻw/I^ieY+]ɖmeqqM 0NӔSiGeeqLH4@ %$$ =CCMMMey6 \/f9;;~WgνǙV>iceف^m0HRz4xoo{<'p1J!0L `֭o&j^YXLG"a(X hhjjX,W 1u|||1Auq=t7k!D< 0Ϩ EQݝ`r[<===\%X z=W x߬;Ϫ*ٳ"(%(eK*]יj]3!pXԩSO'O>wrppU||PJJCebt85+B>5 ۻeϞ2;;/3/ҳ$;<<@GGGG ɺ\ RUUE{{[,Rm5Mxp|>$ ̠f677M5 J>}b9NkDv{M)/3`dd ىm۶W~߮j(F5o.\PҲ8ӧOΝ;ˋ,xWk.$I,,,l6c8rAX_@=ZxBm@zppp.HDK{J)A@((\.bn7|rLǡԩSkb}}ؘ+ V;N+ _Y044t:~NR8KV`X@`ޙ+077&h8&6VPJ冗aj" $ZWCJ[oe5|C{vlL|-`0h4~_{{Ά*,LfD:}> t^@̮]~vC_ 00pu?]3Jjmm,K 0'NL&#dY<Ey0jۻwǎM@ wI[={ I+ja @_~pM;BDBE1Df~˟ۿ?JjcA245hyٹ *E$ \x&IIo_ /t8z-M|;ay=UŢ,caBl~\6 J)ij^|qFCZlںqxdҶrt] TWU*(] JAM&,2V,bqa+m1}bx-2%Eфs<5)J'u^L$l.C(y&ː2b͇2'Y ƍAзr;tM$Pvn_ A,Cf!%n׮{ZZN u`BԾys$I)0f%8plrҊ#q[|^]91<\((eM0S)v8m6p%8,*4L0Vs{osGυS@ 98M0ŒIحVu|&vw[³5OVD"(_WHe9S)Xfr#!P `Wpk[X v>/E;XXd*{A)xC1eZ&Mv$m ^}n|s2"OJnY>NqpˣB3TrPdVAx+*9 esߟQM&ڠ1n!!EOXQdB+Ay"x{ M+fH blV6E0.@W4B I>c--+ O a=!0 Ty Dz>5[Jn돭'%dӦE**=8,C KEA\DWWu< F\}BrPT^_@D>u8mѣ?{0(HccAc4j+)AP].M&p;+d1ċEՔqf67f2ٟÿ_WH$2AU[\>YQr8`Y_u8D*>K䖩w  ^!j+yIBQa5!iZBޜaKMKrT$A.}iz&РABT(P,aAblTxܹsɹBWW_pGr"fK&5t[;w7x~K2LLM-VN> 6 ǁ. uLêKI(!dO>7B6 EU־owt IO鋋9J|]w;6%RER)rEJ`L?RUuzHEob ̈́:UNgs9|*erK1f= / D$@8>$i9OjF3ĊShNS `q ,K*ky7uS7n!^IENDB`OSCAR-code-v1.3.1/oscar/icons/prs1.png000066400000000000000000001710151417327530600173130ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGD pHYs  IDATxy%WuUu;ݷQRk$@ H61γ qqy//`C88q$[`a 1 H-RÝ3V{Cm0`9[V߾ù^w}w5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X-~ݵwr~СC~Ç4X5Xߡuaqѳ|=_f "Z{wŋxwo: }9~~4<$w;~\ nÓexUWi"),0JI!8!K<`Xn-  LQ$c k ahhC8vW!pUgcH42R{K?oH`.BykqS[Minnթ nj!dF44XS(L䎯ac4xxzzAk>o :Q@!`c  Bކah 4z  Hƃ=``,k'&Fxg/,EC͡CR@/_Z7ym- l.0 BC> "Od?A:&p-ϕ.7r@P2"l2f>f,aay Zi\[OEjO3YLN|?_w}C 7L ę#2 Ā 6@R;g?CxذgXJqFFafXX^z "a pFa&1>-7{E٨#Wz2PDPȲ܆5X0,Ȼlhgc)+c/ yj>?i=r$H1h6h6kW+hV+`ІCae\DR@ɻ[C p4lBB M4@r|߳{ڟ{o$ }go{Uj5pg~`;$۲_ "͡5&f SQ@qQ э=l 6Xa<sK'S\#M\gVVv qC&M;߯K7@"$U$dKwq[}߃F~}ӸtU x=8w\`mayv?ETn2}R9[ aGqر^) !!~&: =p TctZ5QD%oNl `!hT{Lph(I-[^$ }/pws'~iq~ܹ>*,=oՖsO_|CYxyk/o%Y HÆ>+8Dp2{6 ص$IUV'.K_BymO÷'*h選o!;S?]^>ԃ#= Vnt^r]vrZ)i&D$ Z?eقko"X@pN^zyW'Z폖b jR6 6&4^'l+0x; ;fN>?͝V綍vv}ӽBGC,H&"N' m.-77[-] h軠a'TN !c\C*6ֱ !$B,A 4,@(5k1vo߂vߢe lz_  2ȞLϔڿ.o{'8vWo[[]~cecW6ԪE1`fpRzq wy7_'pt>fs3 fDd >|7G_'?V;ݛڝT1:v H) #ư a=-kE23Glop) W%M-wޠPqt_QzUbmuq$aِ=1PZC yb7Z -ACƒ]d`!(@&Kyջs}ݏÇÇdo\t8SchiqVo VאfI$&!ZY&JuW/0gOcOy;7Rܷͯߏ[np N_y]ZZJ#5  F p)vQ^EX */@ZP,QjqK~̀' t:m9s chuBC HH)fmWB0Z!ڋL`,h{E=pOw'x}0!"LwM/~ūn| @6Gx΅7/nt}u획 mQ$I4G>,rE1f$P5$|h<Ͷy^d. ӟ>tKWz9zA݇ ݻoxK\qu?m?0啍Zkyxb Z3 MB2LxinP@&=dkoåک8ǡ[|:P&xA.pߵLZaI>vL8Ἓ;Z.HXЏlc9ks(y66m ؓ:qI, VijiEj$Y~$`׮j < AyO~s{.seL>:<?]_W^rw/#{|[^ֺamc}:.`ueq%q%"!0l #lB୬.t3SF1j&14H*u8F B^_88+/lO o/eKO}kO=ommV/}in vjLH( Ahů3j#GvlXK)`2D )4G=F}[őC]\ݬ~k1ܡx>{^}E] ~)'5Rio+|9~:qQF3ڭ6R9 # )"Rh c `o@022 W j5sI)xb5̘yswg~~Ew|ѧx ,5HTcX ݳDCi!%16PVEHHk$(z!`r'{7_́[o3Qli0s??.[_u}cv45uShI$i]ě=l")ĎKxm 5=(vlۅ)X\dr7Xt$lM_)E,f`B'!~d F6^W&dO1E-Ȗ,FO"Vt:0Z[RF|6lqH X3ӨT{RrK8Re\RYp 5W퍮:g~Mozw#Sn|p=yE$ c {!OQRE>Fc&)mghpo:Szܘ|;~:>ɿ{:Wwz7n}v~׆HH) Ʉ|n6y ϝ1_ݕ3gyu p9 Mnڠ.0; /R!ýe.?CuFw ՂТJwǙOí4:.QkR"IbH!e Zk]0G8Tk f&042ր&DQ>4$ i&<IJq+C ~N>\{/5wSjCQjTx_mSl"d&"M3>vbyjH,HD#mhMfP0P彝gYOkDĎ{qчY$ P8SDNy^X`1G8p>qREN/^|7TAomb0 ɀFy͚:"/MPi^=cN$t07=o<8`!FLrQJ "ec.}O;-wOM={q YBYO1#*jh6GQk RC QjQ6? ;0P /=>)s2b)@R -Re]Z!OfkI|~#b#8)* 5sN\/h>(&x7@ h?gȄwB,$kHlsCd^N/- #-f9Rꒌ6Į2"U P ]a ;VgZŚe7 Td/.Ǟ~w3?nU"zd_FKqfVk 2uB1FmzIj;׉UvqN 1| rTYPJ"3e7v¤lAFr% {ҋ UȒMȿ Y[X0Z\EYt9{-xWSga՝lK׵Q/ز;A {=J^Qm6m{\Oj43"aAY$ēAb9k+6`!FDQ$ hh6Y )^ ҥ!"A rAƹta(IKyY!v4T|Z.'=a`B.wǸ˯p@! kU#GR߽>f ~ 1F]b2E݊-{Po!"ad8v9klce @&N%҉gzb¡(@ݙpNo9'^S]y\K6ШǘJ J nr,kk7KQ0e ?z16@JGl XAHRBya 5@(rW&:5VGÏ 3"g]1WJH`rl^Cc4Ƒ+F>DO$x3Sf!,lΌ"؀ %`>B[x-0C{2~ta199ڇ]{01>ӧHu sF;$8\ҡλXFQDϠR1Դm"Wa8J 2E W!~_ p~$ υ A6aFYV@J$AR8}#"DQ(PWbzvGxR.bmm q$$ 3FG :5:ZA;Vmb`xƵH RW`Ji2: "N7=r~ߺk\;Vw*IcV|#زu?}[&Q@t4}Qa]N=ʖŊ0eAOzG ЗZsla"JNMS 5+ؽm޽}vLVmY['w{n Z6fXB9Oނ?mn|ч8ZM\_ky$ K7 fc1flnmʢ•m"BV{)hel4NPjPMi^O044zഏ<˜ ,E{#?+)_&>k7(8 SWɛ/oott1msTVPװ"6HG)tfNQr{4D})ˣĥҖӻ!$\P.AR@iv`02߿w4֖zUL!–Yu!34J{wA1dt.BLJP:94P^<9 ׶䖼 \#% OcX `3MdU{KHFz}ݐiAbN t}k=,-ϣ>4'Z)V}&F C]1vtSjAdGQPO"OPKg[n?:g~ql2٩i̯.qw+)C_)y8^:h&$X4l# eX`sEg%$T$EA5cuQo$صm.ۇ}{bnn+FGQTc\jA(Qclu8k nA+Auy\#>122?j0PͶS.:,XnSs{䁲|?.IzyJ\Hnh#6V ihe3%(p)$fH! v#P[#y܉$1-ÈDN}h@<_>F'2;tÎ0l;y҆H?qYMx^94S{?{ GhcX58ɡz=v-r()RN䷘T!G~/k^rxËp`nZF#39;6cҍb0035{>iԓ,@FR+*>dJ ϜDZE=Page`5n)E -of+~RqK{;^b “ gy RZz'5jh4`f\A,JF03aY)a+CC,mH9ȱ4-X\l>,i R?EDHȍ^&I㕏|/>ID}RfC%wu?ԧ>ɻOhnN@C2ԑ$ JCHH"tH "z imtǷ ..\h 6,A6YF& ne݊\}[fh-Q($\j]p~!L #M3r##?E ըVPyˋH*M*UxR}a /#]?h\0pȕFmZ禨8n eIkKv{¦d S|1z jmT1Zkl݊HDPy6'0/%- >E@J%#P&,*$X'ϭj=~?7 w7r PwM0?OM$qRSZqv4S`a 4*UHi1 b!@̈-1]0|ێ6k'2XD"$@1Z]t>\o\FFGT[WvEx/7^%#[>j҅J^MݕȆw Xx+&WsXk%[S| 6ԢJѶlg{=u쑷09zCۦ 롡h;o+3h Fط;vl4jKh&*U g%3%KwچN!r5! A؀RDX_[Oc|bS,]K(ZsnHz8NjaiyvڱAVg}M~~^7ݙ*zi19$^r2N f2veOsU_t/Ď%rO R'ffxUqػ{7nns$#á|&xPt9Ϟ \bYʁ>yxK_?(&Ɔg9ZoCvwb:FZcpj#M#(B 8z{ԫ%d6d6u-pd#DRZ}!H 7!k'1+<8k;T>eL؁47H+}(c <8 S(Tl@F蔅$$>&yD( d3|)f) ]w<4Z~wo}[#߱7eo=c_~&RdYBE:rM|:׼}smd=RP^Lyffm4vM{~ؾcTc0ij!$\j"乶ᲔJ׽؃j}ȖUzͫqLΐRZyoqXcaLOo"WQI*ժ!ԷEl +&|_gg5 t ~ %d$GRmpumx F^, Qari{AHFYPt=7NBt럳=NT0* [Ea4qt˷|oow뮻43GO=QGd$D b %A؋I:h&J~zE lDdC$Sw0>;^}]w &eXXYA$ Q$l/:> $1Aiɰsԉ)n| 8gA4Bd޽;^*~{wlAG%`@ϞBL$+ػc/Q ՚ĉeXzLö.p;s)a6֦j{pm>o]nԖ'#~`DT@7kC_21jl, ` YK&F!of [qg>ngCj#JlS]+tzJKhc bxhj`51%'j ښ *h?8p= ƫ KfiWj,H1M\q SRAi㌅ژĵϽ rDR S ׇdIH$WEx\PaI]Tqp+JY$=9?wxwaqGm> 7?uܖN#39"ξt > BTPgXؾe?\q`څٙ4,<&ȁxEEMy#b{BEd)"Yu=?SOĉp Tz$QUujqAƙG/Nj_tS!WS7C{wۚ8"0 cl0u*ϟ얭W j ,Zx,^6lwm '!IAٗ. Vd3t!^@, _y4GIo ri7z"S'Zg.`qigac+D͙N<:_£m-׎@[MBzq3L +&*B%ʄ!#eq8l"^8vw~_{ۜ %,zMvn/13ϯI9WLJVCEHA4?B;݂"ݶ^~x+޽67ѱ1T*T]ղ#( -'sT+Hϐ1$A[0iqyΜʼn'pqltZ-AZAl iP؅;~رe*~eC1A+w/IX(\.µ\ʇ3+K8ԓؾ{?ƇrEz w|Ja7`}|Ifk\.70@-YL!ʨKÕHj\+ n=se,.,$)e*DXmgh 9oB0FIБk_2ܳ =kLK>w[=^l HPJXA^lVgDcݭ 2sۃroxF!q׷~NwfFF~nD$)"n(g<7S< gF`eVD l ?/;]vbfj Fu36 ڃQ~(4E1yT*TlWYk??SNqv,n 4j5̌7E aɳB2Ea__:~L܄$.u6~&X=qUux$ϥǞZL`bt*ڿhlz0eQړCLM|Sj:2Q @X 6A-*_@ .r rH0T \MF* %:Wcf 5=CuA2žRm'%}A. H/RA)DDxp~l߹[06:B bP6ܔ~VmT BI$CMB8w4Μ?ƹSZ\ I%HdzõҺCRc|F8p}ؿg-]H,F@`=׿{M#S:TgdI̸-sѯ#Nbm [m@团(h2enE5 r[q{:?ѵݮ!B4z `ʲ8V`A2 UힴA,}9D x$ֆM@%bnC LK&!gei8Ub|/lIF\: WF# KGww7bU@u^S5Qre\㒔C;ұ\gHs5\}Ncnn*Jy0lKA!^Q%j4!`e/.38~8ϝG^Gz5H 2jZ/%.KQ `'Zw-վy}y<կb jޓMĥe/)ӟG[QNb ?XM>6rTkUm3vD[O/mcS.*V'  I:)4 Q { A;7PE1 @hɶhU|V*R g2S$R诚+FyolC`^A{n!%MKS}7gy߷~zD_iV %">yz^~?7]x@ϟg9Ì+Oyn JAډx2LaNk_ngPDfPrU5T5$(Q:άg^zPql^3=TDY >"/gZ3 =Y4kWzoGipsibltvn عZ"<8WFw>٤)Ȳ N[=LO/yM e_ P/3|cumqi;y筗PT0٨4%378֣ ' o,7^;P8J5F->eڶc } I,>#D^66WUZJ 2eN;:RzhnTo־=T`رJ/K:۶ W+T*5ݹ \u5ێ"=hQ!NH*5$:TjMxL #Ob;$!k,'rc-mv#8y1VɊxf d:&"UzG>Hķl8p@J,ey-cNd*=sH9C}}_+wX0k,]J{>mʢ`awYKpe GZ!V!q%]s2\΢ +"4D@ H*6oUuUZ|i_mo6!ؖ<]J ;ݏ1C4>3ff#2vCk} /0/8<-*nq\j(\CdFiA_WG9f^+/ۍ1H;uV #7!MJII>9ɑanw >WwtbОv{B:uG4,,!]̈&&\񱩐u`GVQJ1x~uh#N"2nB6x,%V)Qq kM4̾ O'Rb1 7}d(adinZUkXkמػ}%H*Uĉ@2>{:0T"K3!QTPUQY6i#O/*I04Un=I$i+@R8egF, (Op*|K|[5GH J"1Kjր^#g0҈w|v}S$yx;ށXYw0hsQ̍O j5B)`SNks` I"%ΜG"FtNEOǹC`Bg${ Z!5rN&057p.;q"7p顗oR$w/~M(e ya\~.vuP7-(!x()-Ma0)w x[Q>&4TlO#)ŹUml %]q.?1LLMcfz3GVXn2@eT,,$Q)BseS`14( W\ !ExwAaf}ffx܅}wC[` `ۿz˟{t{_T/#Gƣ1;=L$h5ȔrynU[G1;5_~ahtVĖO\(4f5 h`Ϯw$lys0𮫐!, WQ6Ь ZCJ@0+ i^n{^yCkVPk6Fހ:iv7C!Wzs…]%T|ԓ=Q..Mb'mƮ쭃}NŠD1W3K-wԓoy[VB+Y֛h-#_5>ڊ?j-9,+O)j;hXbu}[F^wå_E᰷, [67fv`|!?~ \4F bĵqObvvSn6Q%޳)Ȇq!&Ɔb^E)F8%7bfyθ+7w}S=7H6~̶ڮS09CCh4X[:AvBjomk3H$Ǫ-@ *yGS2xTJp9bB Qћ`G8)ck@ȎbGq dm2EbQ+_GKZ9F:Cጲ(aRpXmDѭ[|JV0I$?zF?W]3(~L&6B–;qG{,nfhbPŇ\GyjZk_j|/"S`F,$ 5f6EGo,nfz |qt[ !i P0<1Vv^K4ɛJ@D9 CvkӤfd:s=x }.c> lE>ٸ-)N9DJYCեHSJIEA l87 'R=B46]P_'gH)mdl$ .߳W1\ڹ\z{I!BxY$/K{סZY:T4-Wj6_ 0ȔAX604w]+=W:={varbI ,.bumC& #Ȩ(^C.s!g\qn.p" ywΆ:W ˦ q b hD ?xN#Qtؼ`;8h|UaF(S(KOMxcBw=^&@~݀nRF*₁)Sm{Vjdh;Q.m}ifWbw34gŽ'?QP07pͫ^~۾W3 fcD޴SlF 'OG,l6tGW,qs%ҡ4HǟCa]JSv7#]tL I$%U|z-4&0e7\vy8x vlCQF Y9!FQđD%6y\φ_|˟C&m݂O? H[vK)Eq>̨X[[RH((QJA*V&!ϴ gN8<\|^D1D w{d^Q(.eD!"(Q,Gz߾vDxv{e I":U+-_G?g*R/ 8PJ(M);piDQ};p fR6{XA0ԇNk H"VC/s-f!G}P\4c$^~X\Z8Fmoނv}%Ѭ`$ nᕹ>>Vi^-hoeH D(ߎ)pålZa)#NoӧBZ" X8U\F )l@8q/* |͟ w|'-3zk|D1у!Gt@G"$?ɾ/<2gNkGӋ}ŗ%h1;7GGGйFDDJg'1.z lhn@PTꘚyYf[׬!W#gP`=/;N<$ 3)@1GU`hhq: ߵ ea0ArSop| gB?(287cAبC{~}H*J#-λz67;A;{nw!خ23eD-%bvɂo2! )Q9e$&T+ZP ԪGA e)KagJpӭ7J 䖘"<ҕOŬWӀaZ@]= w8cHe"A,@iY4^\H] _N8#19p;b34E9̶ܩvh@H0,""-BfIڱejAJcˌLNd˧2`,ll:U"9ܹsVPСC7444|1ưт >VQU 'H!3V e>\O12ģOUۑ+/ܘJzw .u8ݿ QLPq^/!IbıOO7zFnsՠhFq|>adJ!;[98Nsh4rPr(v(mWB=7xcw+DgN')=2NV3XPOI&>G=K"T.BʓEkb=~0^M 'ig" 2Jj\?X&"ԔO{m^k1"Ì-;1::=[Fpaag9@쇈ڶPDI6ƛM{ps$Pg?$>C/2Zm8Ĵ5.ބW5:q's簼E?Jj &F}vލmQo휅Ryny1= \y5׽knyPIb}gvXGlVEzD!cC4,G/| ơپhO<0p@U8+eo6+Mfx(cy.u9+V>:r/wcρ K)׮r9DrMx=A7""^ZÇö`9 *IwFm>>s bvv:4نSg$Afj&a̫%@N{>w}kx_4˛gR(˙L>=qGϞR vjdx5>9=wbرk$erQ>A%3D!k ="xi; \iXH26 *E?~eU-8}I4c{;ݝ%}DZcE4)g6vRoA?HA.ӫ" ,U^z#aP{8aEx1սҝ\ IK3ڻ+וBb81Skm,tÍk:$)x_Mwu敯"T>䝥 tZs$DyY5ְZÉR"<:i41^# ob\T 83# K+__z5_q7LB)lK')پȥ~p/R@i6`m`rvr A{9VV{lj/^5eQg"}0FdUXJ;wE$ىqnb۱#]v.̅S"R ! ?I:={J<$Rb^|}/v/~sX_[DDp* AvU1#%:=/|I_2Lo&c+a5"$"[ؔ3 (2(toJځl _(hS|L) 5u' :\@l{!b.(j` rgȲeb#&iϤFRAf 2?Vec sː!w} ;n^_eQB2AJ C,#kY}5 {cׅҦ(0JVU6z C,%^/G/kxc. p~f]|\xs"(#GcODk$qS]#o/clD]?n/`t4=#xL)yйVV5B1:;n'Jad"MET2tвa8k^z4PT8#7 veNLaiek#vlN_vi\zᇕŠl!$>'].%b7Yl!̣Ps~x&/jȰ"Um$jm % EyeL7bLMH@H"lp]a! c NL+1 Xr6Y(RK'^q% :D Er&+4 o avg4]q#a %I+>BB@ i .Tm"'(V0TS``t6X~Ε!lM"j#ߕZt/kh&BT@ڰ7^FU܃\+l2:*?M"@ʰG[Fǚˏ|i?}hߢYRIȳ =D"JÀ2WFM>RH\{^H~x2n~m᚝XoE 1L0BAa$b, z(*M0!vЈ`6조W{yepY@L&htU$%'/`{#mOwz(Y/1"!zc6 #Vπ=瀶j@=g Cƥb RI, -@QE%Ç}WbfrN}FIO$ܬ:R-{$ά)$hs8(>rWp"3:Lc7GN.h 5yx֙˩ 2=*[%h6d7viSK 6 أZEVɍUo < 3y@NR! ZJvܨy!op|jtm44K16{;pPkh7VǸ3$ d$DҦr$TjE ahu]w(KI{P־hX `1FA٭sh45kسskS`D>+ )J>0,cZahw~{(D+YX0zp]pV6PcqHH"$Z页BE"rdY~ۅZe۶MaE#À\2ZaTbX *jߌnyO(gwUe/ /88^7C5 e`e >rHڊ"~bKq;J`]t|%_/xֈ(xnVHh)\` N{,[ 4i:!|]nK) KSYgg!#@:ə)ZWQ$51:28ӐHD Ő:f'1>QAۅ@Q[,vz9jm{k1;5O,-/7oc"q &Yfp ^7z_\GV4LC݃}ndZRE^o#|3<*М {畣B7A0>FV?[פX@.xlAXE`(c)Um`+( `eʗ4sT8E*/bɈA"V`)6VqaV[@cjz۶&'!DVG$N - mw+VK"K{<Ƅ'P֖-jiC[w #CoRqr Qj[0@2c=V;mΛ&"MZ"F Ll1.d\L!w\HNhW4c XXu$AFr,/-byז S۰}>LMoEVX!O;Py~~f Rؽ //K/,\@"Q"]4@݂V ҕ퀐QC+\I"~?!a ](,a,"ʃH.fj|ǟz D:p 0u]Z}'xp@\Pt<f v#j:@-@>*&&G0<4TbZщQLo–m&"VVlP7 Ğ;1<2ByܡUJ_ Ue,-cvfSu 5*yV>u ?~{ʵ+:R_q2j VH҃o\•],&vf0B@"$b V^}{o ;U;t4_ cPv ن/B0)HHg=Qȸ9ƛ}mu)wY)hSFA;/gLeV8.0"g @ HQyθdX^\X_[ߺs`jf+8ˠ~:Ϡb"" k qɱÇ7I\R/} 2;-Fơ(0{lG0uKpCtyv@8pd vJ $Qd@Y^E1>ž}{i<#(KKQ8qp:mq+N^8D~vm4)Q7PVjm+:#שTkj=+;n ^ BJx n`my ~c=NYMB!cz~2A]?ܵ6/$ڹ-uh`r $v8H$$6Pk4G1rT~LaKS{c^h k7a? l0 * >‗vQR~"B}l!ۖr\vD1 y0ͫ$͗+,K{"B$j QۡL z9nDE )1"("hs-I"JCmoVӀw߿tusstH)*e8u @D[w¹3g059SR5k](t_-Jw$B T+]LOMO4^[ﲃHS)e)p#*DAA qdl4FFo~81]{](,"5~ۙMV4nƑȕARCVEQ2 y*whyfOk_:03Dӂb ]Va Ơ"Ņųh6GQ5,p? 廪T ߘI k=i;MAb<":CQkh.1hR6hBDBG j$y8O^Y°@1ɩĕ 'P3cmq$!)m6Jb$qdQdm XB HFI'/=&AZ [^qF;뱜-Bk9 :39~={GgLV")TYx'pm `'vIHs<-[&̶L){") XAB@Kd+K8~)|+_x_z,* adJN?*gt;uk<0HLf0%6c @pN_ܿ^7^Iډ]81A6bTBM%լRuo{ f&kiis9 Qf $1hIUaDu'N&rj}WD 9yuE@Wx{g tP)7?}paK;JSv=hFڮZ`a!$zQИ[P>@Ir:AP')% %(SР C$t6 8m]kt=)n׮]g;yo.ۤHcE[d!#L(S3ht:X][gq#0GS[D;TE st> %W:/ \|P~GU"/./c`vn.ٳpчvܩv}awܩ^=;䉧߱u˥Vih%1H>t#3SP;Yx;Nd:?%]?4i Eꐮkز}p(x/AK]"ϚAǻL{nBRϠuͷjVGvqI$8ɱ:FZ lY^ d9RIP)0 Hz2 ,V2 cc7i:~etڣ Jb rN%<݅D֛xzEa&7Sfݷbef4ObΡ04Cׅu B-nhA!֠@ :0RiH PEK]"D$oM ")BS 6fݿ>@|]m@m.w,,66q9[YBsrN ĵ6nތ{M'N"/ @ˆU.)i𧅿wA@G ױqz_ƛr6n݂<.o];0o)d}-rU@ IO_bOD|;+:;=}޷ .AV,7)@f3@HD3ȊYJ&jI\ZHE w(/,hCNݎRc I۞DL~7ޘ\~,tf! ߽xSnd ~q@o}IևBbvv#?Mxld6cGe*?9 @OQ ;K\%<P>X탛A{4ıv |`eqx)}/ G={w?r M0S/H&%(F*I0C2o᫖` Y!%i @VOEl GNq Dc3f{KٹM%GTT\b Fo9n*Z֓YVy 0+qǐ0%7]&Yi?Eu0a3 >)K~A!R@+t$/ 8`ѩ`lf ȳ{Ȝ@?P2bb՛Ql˺l=rmWɧVlՒg ûEQ:ۈc(l 5veRGe0P @j 8ӓS{o͛ED6k/ -؉ЊQOBB (Hay}i<^{#O#5 f\4˱O)UY)( dZkp@§Fsk}ycȢ]݇·Ӥ1o2pQ'#QCsdNa֭hqiD| &rzwT(q3|^YqJA(iF[Z_Vy*ۯ{멧뗾z' ZKזh4s#17zV#mo.A* s($wCEHqv=_ރ_RV1KK"`[0**jQ+6_o7Rgy\Wd gp!<#O"c'ly BjZ#Ԥ`̒"ƘK]޼XBisi-2+JM2I1;wOox#ɱHKBKb ݓ .(I"V(ԒPWR%؈m8?᨞!Gp;rtNikBg},=x?T.׋{7 d%_]ӊJ*Q~8Dqh#'w+ iVFc:h(Bn`%&`JET*\CL~<(MM#D^z0yQ 萰(@DPZC+ZNN_f[Ed݌5mZYOq$i)NɣtPCg)utvK⓻wݛ7LmG{}K-*Wa-~U6EIt&qoƃC- ^M@!K60*"GYWinpÛ߈M۷q*x=cERGxw׻8s;w߷,bj$xl#ᔱ9r'F 1FGV E&j7`m R(YQh4;^bҋ>29igb{k Xcrԛ LBe `?=g`^LMÓ kP)4C"Z#ӈ̅A"7@#E4 @jfQ 3βM@# Mq<ː4q)(,c[mDg.w [iFҌ-Aj?<˾ Oe893::6ܠ4KH6oS0;='8"D1:m _z/H*:iТ( ~޷u3||PM|Iʕ?JH%eup D@3 1HS:1:jI`jGkL6EP4;uh6눢JJ)zEtR 32i5Q4;I 됦)VpvaXQ;p;a:Di{n#=mBwk!D/NM~Ё{T˅زɡpzu¸L΢n's #bhEi+gQ:@`Z#",-bQoށ[(=$1+8g=:sc&Q(PF*4-v58Btʂ$a@ahEyd0QISҭ9/A$|30sSH? mrE&reO!3ulfTzZE $RJ#4 DI&J'?$֑U}XYI Y 9yP’~uE)ԥg7'G],q9[0g^ #,Ol54#dE&Q'̹RxLd~s(y#/A`d|A@Ek`#?@fd̗xW,L-If-B- (g b8WR*C4'6`Ӂք/Lal7j0TH@@( +}G',lH֝n6曋[nw'>/8wzKNz,t=o {Ӧ24UG:t<>^@-ҍm9>+9a@ J# CQ$8Fk( @ww߃3Y9vmQ,[Le%쥦RC x-^Eh+|&,,_ (Ѱ9Jj`L{3B4`JN)ߗu%uLMM#b9EF$ yk+aŨ!Hɐi4`L(' fq!TB:%58'MFHJ-YJnYAB"Ԣ[ %(}Hׅ 7"r !"e?{^-kn~~^ !V>?/NO2ӳ3 Ng z+aF' XlܴO<6f:5RS,C(}N1",lǧ6~0>=SYt{>8XhP[ɓ8oyq<~'5F1.%]2HH@)JA0 -m`ha1V0nHh$-$nɒItz=AaC8JsØ\,(dW0[cdBUH:رV]iq[>nxU'=nsK'OqEc}e)MмH,,μ ;W/"M}@H$IR0EH~EKBPB 5RZ+왧uPN lNҼ )/XcQCUCYa0C$-ɷRVk'XfU>z+p!ұJxW)z 8K)Xf;tyw 6`q݈d!moX烹Nx/?ؓ?+k++EYȝ"Uk,Vϣ62N,6mݎ3'NarrQʬBZB8Iɰ hd) 3tAK+ؼy<}~ȋˋ8qxI1 +3yOπOBV!+ F ҐMDU'5DQcOKdiZ! Q"B$qPe 7_0 ą.R- X&E[uYG?o>5]o `?'pÛat(:MXhRTN!jo܎߃s#܋47DИFjY >l9k t2:m@BC<}j 2$"@H+RՒQV#;TWWQXNHya R, EN'- vͭ@0/ )=)+:.>Ⱥ2Jg B,'G EV KW} B2hx<=9 qg/-r;uS$ ߣ}?M$un )g'Y(NI0$}eqƐ֠N(CQOPOb$Q_KΣ9I@# Jѳ@4%0Y zDS|Dk-"zwg&1c k ctח]>ktE3e茎Ov}7g}unغ?y Ǐu/.L #n(N{ـIBiXDU!/A^WX8˻,Rܻu' !t!hG,'qVGV48 aBn}s%heXj9͍ "X<\}4mGX#³Teo  C&XgD?f)LA8PFv#cM Z,3ȍ),S hDyfZ#4&6])-H z-.EQJ" Iqa %y~!p"7mS\JGFѱOMw_ lַ'ؼe%VH)YmXp)Qk4af<ؼaZ O_lxCtp{5{=((Ui=ҎfA0 :(B$4=lFYሜYF|r2,5 S ; n;5K( zȿ>{--W]H``1i?:, 9^ޱI2li}1GB ˖R{. IV# $)v{69iyNĜ4yt j틼(e8C_StLJBs`[*@0Jkh4>}tRz@cÿmw4r8Э(YiKNAΖd,oWn_[aAq# [C4͑ 4߷54uXezx%W(\n DC(x61;}n@e8 i'I0>4lDUBA^uׄ[~wJk$IVNvZi)? !fa-^Y^pzKF,<9y^pedjB@H⻽♴P aQpaɉ$fc juf6'}u' | {_I~Ļ#IoI?7PhG#tby}[ 4-ZZИ.˻|]`D3c11ԓ{{4&Te0 ;PN17Xb U hxbW7o*ekh7"$q0)+8s ~9zh=q 55EկIuW\߯ݵk曍~lhO>_?_x˻FIΤ[Q\0O?}w T·a𗠈L~nG%GT/j0DRaf7L`q k>> ˰N<-&H~ib=͌ KREyy`cI)!X_ Q'2N8nJ1^YPxv$U/厔tNjW8'6o-|:҄ཏ:q֋]*#$VhSQ ':#0&.E&GJtZ -09h^BPtwȍ=tT:a%rq)N^\v`)$xfm43X13VCPzw /,ԉ#feN 16>V{I~vnӦOtMwkȍv0٨g wƕ]/(J&;#=(mOc3el8xG5"j6594{VJBhkqH8gH[6ei:9G2:!N6 ՌQ-7s5wS09N!79Ҕ927c$q7qFBr{!LyߗphstJ. kk' B0BΆ@Ęۂ'!$ɞ4p tZ&j5 : aZ: rZCJ-'!ˆs*Gq»G?{ydcYɏ':uLHOٳ'ͩSGʂj4rn_yxg~?sGڵK:ur||\2z<?+ngMD/[v&/(fEO} sѨ#8BjZ 3Z#cEQG̳5=PAz҆9M)s [yKaAZSdj1C^9炍%,Cw4듅W!OSbY(` "xnlQs~ bɗ^e.@+gBǏcjf&*la]Jǭ8IX,>PHtFrȈ,Ŧ " !u!QsO!mWDcC9lJpFϞE+ q!:1* 5?iO:lj1>9zsI\;:2Tg|ַ;v{0??*lߪF۷_zj߭ǟ>K/ EOM>E!>D]G\!/2a&La=Q6;t ICQdeX]YF^cRnzEDZDx𐞿33&m iɊ`iq21Dqi@b-sӌFf8r)Ty:7@=iy־| /w )& !DMMG ~_|TF 0A-TiP _`0,sKMXY]CQGkȍChadрz+B!5PJR/XDWR:X Zj0&Z_8sro}uJA\ڑ]8abvY?5So}ׅe;61 ?v^˿'?Μ>m7lY=ȭ*~JS͛oc,QV&/,s؝0VtRw(<wC0B0@?$ h`\GCz=t{୥S-Mޢ0elui2[Oؑ@IpLS/e+J([p:-NmƢ`0$"Z(%MS57œoȢX!@VT"kk]yZəMIVd+j0y Wus7)#SP¡oI)M.9_ioeyRj#kO;/M7뫃wisU6&zܹ G~y ;)50tyQ Ffz0Y{O9u!ᄼh!kǝYٳo*RGǛoT(3k2t! PP#Jbf294ϑɓ]- I}]yigJ2ݕU[ύ /9l^Yybi+.o-g‹ߏaOqAk+MTWEPJĉLXHaXs֐:P34ذy$AR@O!D4qQa;v6@i|#0+a鹢dª^C\kBqT{jwMw =cѿ 'g=Hv׮g=t32:3/xYֻ:ĖK.#0>FD Ee^)9KG= w~ ttj{{9W9Ƀp b+$ <`=+;z??8pKͩ:Hҟ@݋]Zdߟ2!{OK~ڇ5@#zXYZ™#O3-4-DqG@լZ$S OyӶVs.p}s6]5EwEaUh9u^k3׿M7tϷ^,'g}-\{'<_9?,m@&M]YEslEatl3X[<-Fȡ 4ayceTa[QJΊ2phÂ%B@!8&F Y'H!̠g[[YV?*tݞ 1uoK}|xd9h[5{ދ ˉт%S5%( (CG <+ҶtX Pm  4 a(eAt} g`eq:1TZ=u]eNxGrƤQsְN +,@QXg mlڢX:jOCrZ=bnnW^מ|w98uj}4/E?X׎;={]G7"5^> !ژٴy܋~' $nQĵZ)sm媫𐊟3vO<=fOBAhը!"ˌ-HKgX_81d<)yy=ȋzO`6.ht{餯 oI>RT ?fY͊ݍso4G?ڗKϮ]Ȉ}1M='|p{׼=e堳KyCE\#5=lCՆ) lgVQr ([=hYe5KIG0*`k:FZ DGt^փsniG/ʾ'xDw(es}JѤSVCK$zt*hXO!al$?rTκ4l PP<:eAI  HWqiW(rQZѴW+wC$B"Mi0cEi3kM+:l2 I{tO}K֭[ssN|@qv~~r-z\YO/.-Moz{!v H, H4GF19){G[ϰh7O\:2MT^IfZIa $JKK:i!R4+i q`+Im㻻? wlU[}W.nO-U|Wb@?&1 J:xAB_e+7"<6 إ@"< H񬀝 { gZ ')9XOlB1;@Hys/S_D'[9GH# )rcaC ź ֥:TB4-0ADG_sF~_Els͗cy1u%L'7%/*1> 7n}O DXH=8P$r.jZz#V Z& P`Gz^N:8s(m8뭴\TL<;Jx>Dٍ~.E(*8bqA||g}j,g5Ҳ.RV>Q L1#DCzf:47I?+BB8^A-F hqL?qȊ*3^6T X^Ve0.2}S\HfQrQK>U{^nVw^t~k?nڏu.2@g|)}iOǩp#Gn7#/U59nq#chj5`-1yPifG Y }99 ((c(ށ{x @sHFq,ct<W<eK,5/ZwGhI[yޫ룱Y]ڊƉT:Ͻ'zEa^f_t%<$+]Ǡ֑xڊ%E @rgJ1D ,4?e $MD N+0=$@G%18s@Zt]?̚lEGTN4(lv>әע=]==X5qS*~"N(̡92~^}X[N`Ӌ(% G jI FBj4 jy&- 6$M#0 x?7qmϾW<$M) 5!J},///}y?Ş;׾SsPYnQH 4Gf V/`yyl'eAa m,0ȻV~RyO:ŴhٮlA߭#lZ8N[F4_19ωZ_TȾ:$WOpx\J'AG/r 'NdH25 c3^( Phb1:6(FOڊ=w\[>'"Txt=ɑɩ/M?}kr~OoWyT]ՓWȑ'eW\i$>/j!SՕ Z:6ná0=Ձuq͕qu{]w!DXY_Cw}dAi6v?ϹҵoDiUj@#;_$sekTx!AEw_Til>I6IQޙ:o OO@=iժI(_kR՘ol֐(B@nme F:hD;c3x;޵gYV'wXeUy׼:j' #D&IS paLN{~~?z f3XC'B]szzt֛g_Sl^/q^8JbjGKH N K ҵЊmaycSܟΨP̎t,$xbV(eC7'8f(juF&fdt⿿կƍ/"+Sa}|vرcݳg?>\vQcʇ"bdtKgbf߇1O0[PWr|XrUwo-^ςYs$2'›$,9/aቫPt''(}3x T )BWBx-XES@xQr_қ1U@ga)3o:Q"I. ]<;:]Pꌎɱf|j/6]?7ϸOgvڽWo[[Οɟ. ֖ Op 8HLmiǞ|g.·c(=ub#ҳP݃Y֛Z:Q*#0We->3܁ҰC=^ tWdo:j櫃WSӊe?O ʏH] &XNOTY"SRbYSHzZ3Ef.,sZQo}Ӧmxӛ޴z^~=7o޺S}=wwٍ6KJn=9 ֱ{Ϻo!Jm`+Uʐgxb1o_KN*%m@Mʡ9T=_YI<lX4<VsH1wZ%?ҕ"\!ȇaeDai4j+.]XpKINGG'&>mٱu CǽpI{477x*B t (FXp<|V3hMA%K$e@pKX@ug՝,,VKt7VJ6 Z7PJ2s+ Z$R s$,+.K_sW ؀ $LR$sk&jE_-U019ltrmzm?y >X+k~~^ۭ׼:LN͠(,TV<1PT#,vu~Y' .2bk'twB7g#Dv *cԳΖwz4)gNBWw˗LBJhd}ě_  */7d&M9@K☴Q貴o> -u4u\rܽS^~ɕ o8֝ ؉! v;_y׿o0ɕeJe ¸;o/Vɳ}0ѮSҫo4s[TV>~5Jn|NK1>gOI~TtY1/`y?%@(~Rcms +P%O%$$D@^sƮ.~^ bnn0#ϼz{WC*^nf|${?pן^y@Q0-^d'WFZ ՂbbϪW u7&*"cŝ3(|g1Ȉ+* !PY؝36Ag |4$x >Q% lP8,P@% &[_s'y&#ؖ^zkv[ϫ~VpW'lnEQH,tB*SbYy/~z JpQr [pǝ򈭆^'zk9ݦo/ <~͒px?OXw0接tEbg.^=!y'EN/z FҶ[KKguڽ$Lg7l_%~)w~ѿٰz!7|G>}__үVKSh8,]i ߵ>t g)pWC?-8JB=3G6ϺsGɨy2%4;!Y^B1U_ԍX'J IY[28Fjt4>Q7%h4H"KΪy('f>13_Oo);v|Kհz0;;k~gbկ1 !KKs Gz *B-)` mm։Wbt"X˔M'A>; \{pIS2s Ac;Wp[B~JzUh>5ෛ 4 $Qh"3+*lu:ش'6~jvvӧ<25yB={԰zA;x eo/JQK:9:d)O#{,,S C)'5$tkCV) !\iIZad} H65WRT:d$Y?pLu4 Ԓ,ΊSJ$FG|ns/|}k7|5Ñvl۶>x_vp׿yv9@lF$R(GΏI{IAV01Ԓjσ|>6yp$nv+$sp#T"TpD5s1aKg[#䠥Dh7%YY<#O?$.m3xҗӧ~#.z;.o0L~/Q"@4'il@˔TH3Gkџ$ʔE١2A^lΘ®.s*(0> ccS_6!Dy!G^40w~}ϼ=^j(ܽSǎB G@R*b 4 G[~F/T-d402h&$, iJw|hHWߏs~.yʍm3'O2;;I-)7hmW/.\#;o;gZ 2p4UmP u,ADž~A&-ћBukɣnmV<9==6]򱷾|j׮]ԩSye:p;vB}o|?u3fUR'yh@wvPB|?wΕ AOz|uq8 )BӱFJj H>j= !oK xz-ioݜ9v.=uf%/ygg6~_]/zs- a}zue?ᑏ~e?6gtl>iY.G7M{`e)ٕo9y;l%KtGSv I&Az|[alY1ML7B4 4k H={YtLFG'? no{zmOS~_cAQz[,^Yw݃w$9ɁdA_/9Ζ_*Zh "HᰶdN=V/ЭN[mټ#g㓟7M_[ Ą۽{EgXIh;.~S_7oEQ9N7~E#7m |Z;eЯQq"s#'#4uJQ@m͞=}^pD71lɩt&bW~̝;w;K~Xih; mxSgY% pqsڏ`fJ'eA q䀕~u`PPObq$z5wqp6mB^~O`||ɉݗ^s'7Qf/G?zw}Wn\Wi\XXƽ=z#avⰋt (v:%8ϻJ@]cIѪ@.;cΞ9*ٍ[1qF'LMLܺq}0^|z׮]f7^/ſw}W>կ~}KaENk_F[u @3l%y ƌ<ʃafvҬ/NvEv^O][16>yar|3sw&s󮛃Ga8E`__ozg>iCШ7a'O_8bEEoOޥAII:A^ XFzr +KOٵcFlr9FFF'>;5q7w:H:y^TO}w?%nfNۯ.2Q )/sD%@YH TR?]@^! 4lcm=}Y:+ZuF_{`lb|+_[Mcǎ!D|5U?BwmW39aꍺZY]_/xT_ b9FD!ڭMy nqY\؇(Qz/VM?0;ᓗ]q_qǎzǎj ~`6jԬ Эum_܃<+ .;Jըc3 +Kٓ~:U3-W019 ӛgg:I{}ؼwqHT+_RA 02֢0R(ԓ#&R5,?cϞ=bްjl|)#W'>vsW^9Wo̼a0;v`Ϟ=ΞYxצ D-dW!DHQ7'QFz-TySǏ gN ٞW]sĆ;''7%\s_2Ɠt!3oX? |y~_Ǯ}W'I|kwCGFem5$賴 ]>Z#V6_hM/ӛn`~X?t׀_W__q5'#O`t$QOh!.M*F: 51>):_o޸c{ eh97b?s?xG7nruD Ҭ vw^ 56:HFkidl΍[6// ;UW C0S7BB7??~뷿;>  đ{Gdi9a;ZtRnk_z-ƹ?f{^kXzj -J/Wvdi( |J*|q6\xmoY5kw9$7T^z7яn};c,>}>I:G^D)~7\x\z1 p_taf~.K9sNl6穞 xT@~Ν ?b/?R?5Xi3a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXְ5a kXַdRq_TfIENDB`OSCAR-code-v1.3.1/oscar/icons/prs1_60s.png000066400000000000000000002044751417327530600200120ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGD pHYs.#.#x?vIDATxYmu}c9Wӟ͛M 6*Q%R`r8G0p8\(U([2EhJ(&I A$m"~s})*6qFƍ̼9c|yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyǿ.ٴS^P^U=(Z}++ l.n?k#`9nT1 5ׯ_J et;[+Q#2n'( 'sǿ߿o8sζma9qp.'ҘƔ!mr2_'8ץ伪ޠ*~PoLBDc% GLXDU2a A;_ؽ=~hcr'* 'ƟzೌIb:5y&ols; wfP-5P6MSgh;UeRWa^E奀 )B}SH\ݍ"bbDU"i ~n9#kYkvnmwƒ1X Lu )xhnooсpW׵cfWgu(r:'9S)(U[MΦd\=88Mgլ֞feUqBee\}\̲! fPulU@$ &b@""x0u@`ԯ(w`T7xׯ_72 W'_)yYϳ,˜dp !yibQU鴚J]êe%T \C[d,NV Tm6`fUaTX(TL:g2g :Lw]ܹ9,;jTޘN꿩#aƍKʲ,h<,y\r466r>Lf .w k!TBR.u5:7U0YUoU~i+!y!$ "DU1 LJD)(݉$UHAQF*Ap@Mlf8B| w#;F89& ' TF|xxȽ^Gܸ忙I< ?*˥xb[Uu88d(]"R.뺚MiyppT‡AnU,d AB\TnZ厈,HT/pR wPAJ1 PB㭯 &* *TϱaIc@*^UjR3Yz WnolTe4۶߀w<^#ǯA; ,P9"ʺ(t;ksk b4l<.Ϫ䴚5WpTeQKlA܇CaKĖ +EUDHW i UC @1 3 Ql0 4≨֠ %!|`;v/=0_21ʒx0ޓO94q+jڬ Mn "ma),+t 򺮋YyptT><=-fR8j.Qa`c DŁD1Q#LlHI'UhAƎ6s"2$($%@BBl1ŊAH1BJLJD Q謮[4C==e}t' U?ʐ0 !qާPR'^MQ7001PA(5:!+o;TԹyǏCtxxȻsnczlSxb`Une1ow.E/>XMO ,?JM=L&4m[mF~: ?Rg>ZoQp)w^U-*X^Վ43#End9W24x٤8˫+jPdyU,6cKN2c%Î,X6a63+ B*11"Fax;Gȉ/kR`]Q2GL1hHS0+*q@@ T a3VV/SOmThq||& @I5ޘ_ /|ߧ5Üu흵#ι,QƠ2[[֭Y{zz߿$rMU.|mohp6eYXVxaGGRvj)s. of[ږ}ƭD$ќYscla)MP6U1Nã8<>N墩+UEW\¸<7e"@Ɓl,330[!&6 b2lIR)_Ӵp/?RM}8sbJjo*" rӸ/ZP Ʈ$Nkd(DUU٫ha>eC ?ukدR=Y;f;s!, %?'~j.;ܟ.{`m68(Rxֺf9bv"Y.C̹r4֖[}7;O2]/1tnj1Se[e㘍>dy203j>4MZM RȲL윘egjm-J M[<>>)t6-u[k Ue>EQJT`rl5 @XXf2Ć &01 x %M2Çc M*vh,hQ=R5CҌE c7%PH&R|S^ qz]@bZ`Y)0))^v{TU5AWǺ`0Xܻw}~xxtwkFf*g|L@,KKdlYmD>*91J5\1SEJ]Zn[fp&bfNCL}88l~L2EqU`ܺ6Nd5rfâpdr!BӺl:ݽߐZD[*03[ke93-Iu|:l\8D0̀1c" ѡ0$;%xӣǏ8UPJL$A$>^&_*A {|EA4]wf]T S{Wv7o D j5FK%fp җ_|W)?tjĄ&!mۊ+ ceq"Yk3c8"(鵫:N{g't?~W]euВ?̺֚2jo.*.cآ`ˍq!2c\a[B )[r54;88ೳ!/'^^48Un-˃ÇlV^*6.+ll8'\s263N&e ,5Ll/rf"V1c{x."!] kxCߢ* XEbƳϩHğ44O ; UBHaD -̀j躎uL`cB D'AgU2vo?}*09r:W\icb9זMOSܤwժp,WVL۪#2 cWdR<|ʹ1y]LΪIaw6VժlϜuT{>_ݼ_!af 5"NEVmq 7ln(V/Ey69+O<;9-ϙ`A:Ym{^||=9;Ǹw͖ngw?ַU޻W\yZ $cK6P`22f` [R2l1,sYȷl *Y x3q*3[TwZCU1LUBBa;đ$P~Uc aFbqsI Pjw8%Yk1$C1haf JSNX(\rO^mcIv:c^o~_ ;fww΃ߕ#¦,{,S0M5 %3ʬu9E\H|Q&<:9)NNΊr<}$U*H<_//^|{.m_c"PkaAd Px/Et:+ONIyxxTUɬl$Ғ-ma,ge&fjW7}loȀ%v >+qLƹ4^c8cA!$1^9O4qVجohJY: !f6 GL))۾K!% N7@Ճ#(YS*p6%5D%( 3Z@9+&bYE" JBѠ9׾?~Վ.&̒mVjȧqqrxRGI9L63yI EQ͕BpDDDJ Qoe煝X>"2bf~#mƋ%aX)1cnU# qDH*-BtÌP7Ġk!"f@0{֣ݭoq~[ד {w3BQ@PDk=3^;wC"iJ C0e!,?xN0S?&T|]dڦG/G-WV*_s}G4ɭB%T82ỹrVP-fyy68>>-N&rX!h.E|&̷hDTH-N!J eg<3ߜlmhocpX,擳?>=wxpX|^mSk km! $gS#%sưQw3Y|S5wn2S=nV&0[ 0q"΁1ċW~W.r囎4G ¤O5:HQI4 Wxw" ԍǟÈ3į)8JQF#y*k1P2M@0؄|p$(k f%J`hD=& w>ާo=qyW;0,gƘ~ue?O_xv%falu˶tVg'8:9-N|WuS &AA Ym&8q`l CdpԒ()5uћ?]|~ͯ~us2NNNeլHDəlnͭ59B3U͈D`Ȩ^C`11u۪"!Wa%J|#t^|uAH$XЄD1[`w"[ G'("YxkL.镂7D(AB>;@~=j5 ~H:yhw$ukQ߻fLjJ)&3kYFAtPy?x#c¥iCLKk}Wƍ?RI((ݸ-KYքY.|19NO6&qyttRL&iV! hNL%d4pb%KVDY5O*lMNQid@l7m~߇sLҷ^Y ze#aKhFdɈߓ ԙx-z"R"ej4 Q8!ؐt|_5!V*"lL$BϧStI- h}x#;V`J_|! W d$#)#HLVϖu? Θ!Zؐ^zI{Jj5my^W/޺s4ak6UB.sՐ)( ^2q"UV!V YJ0D%DP5ƦR RO7~`wvʲW >4PFF &f"CJQ"D`h9SB0L"9rU+𧿀>wKǟ0-4֬ pif-K;Da,#OsUF4߂I]0B:@VAZx `fX6$NI"'?=5A;_2x8)ViiT5 b^`=y&UkPQB01yUH+&^ZU!P87}dKf?ҥKK/G- oBDH]dY o_×.?|xxeն |Sv}|6~[? /m_J$ *jc"6O`PG,m0ގO2/ SBE Րq&8f2P60Qdؐ13",W5}x獷M^;` (Dn M5Ml6V#iȚ6g(yDw{o]?.՟t-R~;h_Bl77kH OU vX)0L)uhʼn#HGfR%Qh`%eljЭQȭv/G"W˿_x6;8'Lejکze;Pb5HUU+(iXaJ\,C{u?c1!*g[,D4@L%QJD"EI47 c,qɾ*>!x4m 2E4-&g砺L:75û7w/7?Ayo5Jd)i)Z }x;f*A|( S !h&;uh  X(Q:De%8%x"BȆ)@`V(:;SOUB(`buEDG&I`ڌ o0S`2{}amV K[rM}M?$%koyMQxJ8`ę۴[&P^bhIYj)D$O@PJ$+e0q,!Pz6 Ll=vD[{ZD )4ƚҴ5hji1_,}l^coV}LOQ5M +o(1 w ǃ~ =Ad@z,6&&5Y{$Mwj;< XsAE<:jPANg@>hҌ. j@%b $-.XӀ +V %U kP< EY9\(J.K@>,+7~)ϧyWS\riD(z csng>3U!xg% FL2?̪D"β&> hA8̱71BZC$"8Xcl`mzmi&hmۢiXΦXsp3|iwqz6ml:=aO@!n|cxlzͭ bOZq.>q߉!0]2` ,ʰA^BH?XCb "HiD9"f(l x. FSP/XLƘ`5YPeޯ =D@k-<++dnlc*Dv8 xM``G%5`1BR!ma"w3O^mA6~mLNQN`kf(TjwƅٟGʟBۄ7@6 Pbh=5rB2NSvD$ ׊62XZNZ}iZX.WX,X5v&Wڵ T[Q(SdEFx{+xG?aw G&Wx@ m2wx⃟,>s8C\| &vĜ F&lJ3 H}ETbfqV$y{H>  Rr6j1fc4u @$Ou Ծ)[zV`AT#˗y KRGBX$06zM!':Ihf ZEΖ+ogeV$"q {lneۆ1M{pyEAmP1`g#׶Zټٚݣщ)YYfԹHHnl5&#pbr%" a]F &hсTN'bJ8 k3du1uQnںr9jDmjbri(6=\actYE5 UM`G숰5aU{oObkw&.\l-TK=:0q36o80bqMyѐ7Z$x,&_ đ@K,J~-DK@J9iSh_ϦMKmZI'^_;D"Wlw6#br3ͽعxe/Ӷġ| `C $! "B &??x黾{[^†x xvwF4JW4TMpŌ nĕȂۖ咧NnP}[z÷6os>VS#Mx祗.tZ٠ J޷eX6٬OS{v6|6!Y+>ghIJD{z TH X^cٞQNGUeIAQ&;"Y`uu.!€zzrrj_Z2g000m,3TE"ː^TDdtn=fRpI,3q1&{/iV8=>;㻷̦ pA$ݜ<n};W!J𾍓 "nđ:Tz_;ۮ 9l#eMՔYaދBC6҆r6r2F]7h|oZMzzH^"H/{?a|?g>i^(a8Vyo~ gЮ{+{1\[T g =5)KG(6)FSSeQagL|*sx;೷|-==={/#{hhllOg ^:.Ox5-޷y6F֐C K !dA ͲF$0mS%G|Dq+z(쬥.GQPҞD4 zbb|bjn},b4c4aW(dlҐЧ3`uKHj,/L1!.xx'GgX'w-y㇘܇dyc!Z> p{hCl-tYD6bknVJ(Q51kw'@#@ |q?xCXa>j, %j o~_YAbVq!cUFU-@*IiZxQKQ%` oO=~U2-,vh#O/ X'N\.MY\] uN*42[ ? /եËd=nREJ"B̆C"BaQ%A`Ȫ/'DqG"YeȺY.Mz|zl jַX , mFCC!vM#4/$H`ɦcD@x2H.^P2iUJ BmQ ^yػtY1j_W]e!Vip~}kXLQ-uhM;8.[fDUUG xD@GE|:0ȋ&4v8 gҡC)ڊ8u!ZQ: ! cYeb9kYW/q,ڶ2\{/qLw3ϰ=99ɗSp#tS$O/쇠PSՂ22"vD&jTE#/H;GjV菶0>ۨCdyU|6C<} ~| yf ۛ[w׮\{zW y /V899b6IyQId#,(5qMY$erQk!Rm(9v.|{;D&a9Fv|$ Ņ+ax K068C'DbLV辣O a&!ѥ%5(+ov[W@R,\B9J6hC Mhep;3{~/r'|#> h`eEetlcW}̛w^rW@# M 񂢨`m I b"8وx_VX2%VOq뱪c fsZ-'2 /r@vww/[3+A+^,萍l-6}-|1mNӳxw2_,/J%BY$DT6i])L̰f9DYy,WX,f&M&MX̦X-稃G2gЫrn 9`4Q!%9|C26F(21oX,P0!KXE/a!d]%A"(GYizED[x!,qq oWM)d6ͦjlFAWJUDdg':DN>~oXeq6K=lb>j9b>AQ!˸yYa0C=iûb Z~ =یV$>"M1W%u<)3 k!M Buflsp^UvD 4OMb.K/ k6b01?9xݿ<˱1 KL' ܻw.67pW!)mE$50==FVl&KF Iv⨺TK|?&!3AQ PRbxU~rԵ7 Ih, n\_|j)4wpz6W%Ș" MnoB)"+:ƲrO*1*WK,fS4V%Z{~hs#{0@*P9嵧 !A"Ę2! G8QA۴Xs$H b+rX ;V!1\p9Zh} )S(zh_6]51Bt 2 hVKmTx~]EQ(}FC,'MPQ#Р7;m\!{X5+IkA%Bbgmv&Z1qd 9U`ʊIJ1zC>_O8;Wkq<cogDblPA9 =,KrHB,i  ~ o]쵋bԻm]o^R3減.+E'6ˋx $zbT_-Vm 1 /nagkUU 50dgw{t>\B-Ru% 41@@h؇ 9 8o[rl10 p=,WKܽ'G}X^$N`DJNsfeKB_={~:G}T O%ZHQo#3TCB\vѶUaMs'k"]tbߴ Gh=@_1kAIl@L :~.!/O?oWp.v6G(>N100(;,y7;[hM]÷PJF XH Y(5A$@$ A58GJ܉ccذR@Ppo^|jL[<^yE^qCRz-`7,>~kovDdS͔Ԋ 3Y&(b701`-#+* l6j94V+/ ¥ml*3q,( QO@R{hCX3@Yֆ SP"89:j6nрgQZF^ tТ?;O/b2!}vW%9"fB4(U.5\}= F7<˴JXru k,qOgq?e&<9 ƺhh ѲK@)֤x#!z(ظ(Lbd3믾>hѝ DWմ'g'hj5g-Mlm`s v1L1/=v/_C]`WB5xf٢^<ɇB%K)]TEԗd +LO&׿S抁ƫX f9U!(+n͖PPa #4N~㊦;W 2vr MzE5'/a/Я*ycM$(56"EAv=jя$Csgd'%PiNl ,r`6a^1>Y V;(\ȭ 22\Md^2.G&-$(B73!8o~o1Go0B50+J(U~oC6,Ƨw1ڹ c ڶº, 6! ZcvDd L4BΛ26٬Ebτ! i%s +T|/@.)c0`Q0 3cܿul^`1>fPm čկK*C<bH^E]_&@аfƅ$\tt0VFIsbS](jsY!Ү4eXHǮWY0 @!-LkUKLL X+j(Mh0 hY|xjF22's% c c=y"n㣻X\p# oIACj$В@0#o;;xuTAŰ2(8m\yzro㓟YGe HP$B\ɚXFq@yHdQ要I Aa'w9~mOႠq,V =Jc X"?.:" >Y1jwF탃W"jqOl\fɱ'ΔI@`Ǖvotr^:X!'`r:igIIk9,<4֤2 lkLgX,MXNNLBvZdy AAQq!E`I1 Y8|LZ,pppՠJ& ǜHM7/'!WU(sE8*O} GX6@BhGCOG~w{|u?C%8^#/Ezm\[(\IW76c m[#"%2NVBf=^} ypzp6v `%4|ļ,P%>^c y0W}ONϦrmm;xߍ=,Q  3!HM$Jx܎D,1)I% VLIhVVg^K̽/KnxW)c ό_gE^ol WU&&u\}jJΛ 3,s(JO?=,f y>&]䦃J uZ$=HEz*6M(h}@ݴXj:A3;dFQ ѪA-<7z7mB_˳hIE e2-!ܿ`/UX2DZZ2ѤdE+-&`kw3bjC=MR%M>c %7b7aFRƯt+Z ![_:e=+)1a#NMfY/V1l,MyC`TXWWu?|Vk޻8B=,XX.sh Ӫ(7\ztѨ jFEUZT.c0aXbYȳ&yBLH[gQ\_Si) W`A, bOkP,эQc8,9x%]Aխ-֢D-2b(UdBPӨY`[t 7&};^at8yoi G{8;oV` D;6Kwq"+<`]G@9R4 5Bv:e bW@| (&(Zk<&A]bf^CU8 j[jbܿww}\v[;-l'1D0_>";-B{&4չ/'ZMմ(9 $&Js" n@1HZg6Kߪ^}ak)<-i[!`g} Asn?:::8= OQ$Cp"M NN!z8ˏ@XXZog8umIyobqp<"u&yQyaPUyP E/GV c`98E$??HDJk 7a\JYڤG6eQyhwc޵H l|*pm\0M\E ED_Ìlk}/nݙ κ-d2נ{}2 !H0$ۆBolU!7 ,Nc|6\1o-?I|DZ Z`F2ͷ^ X˘-({Ň?a4M3\xwEU8dYM݀;v*%@G8$pwj Diq (cpzz}}p{\v ˿?*0|_ֽ=Y ^Њ,fkke`X>C%$THy,3̦3lmn+s:xԵkq xh&=HN4i1;>iY?-z`8EV"/aᰇF0`B5?acc,2)Ė[ 5Y9+cy89< P,mCjYB{D#F!~oe.؄ɲF/i@t(9gom]ĕ(T-:8ߏ&[ortCjMy^10`6;A i:W Qxo|,z4*ҳ^ FQVN/>}fcl`({ z=q[r!,B9B5GRe?oS j5~x{UH5R"(]ҞAH.kBkN|]RHw^yҷ_yҼi.ͺME f}ϝT2k^26oM|>i] v`\&, A$ m&fɱ/yƾ}Ycݢ%ij`cns|ø8Ca|6A^X5 .\*Qc=?%ln^|ύhSXL k}Y8Lдm+s\yBn-2DmbkyQ/h2:&GVmWz.ڭ%òMSL  FvҀE"d}\o>W/K_/anP C,xǸς<3s'0_c{Wҵ'`A^,z'X8(ck׻KigPnN N %)֔"m%]:Q% @<6Pg^M5w7NK3ofYP\%fJu,uA}6^,"<>>. '*,*ٱEC 8]]/IflD{5IjnOql8_g1m¹UMN<*7_uB'vvv@?|UX-8>9 G`cAK,[;8EiȾLiazd#^DpLT$6QGjaR%fb>==ck.#ˌ HHeY&ND|Mvڟy`J/s9vj9idCAvdt:8>Q/]Vo׬60 GClln7oBGU0k"#$HI0.>tap06nOdMׇF݅s)-47h$( ftbvob6범to)dѸJ=Qjd:AWx{Ņ+a:`|z 7!%^qD/;cq|>.?8vv/_ul]u> u 2?rXZ6Ҙv"׋;.1M uX, V9ee wV|C>۷!@Sǟ^ eUA@ځq.bFm?/4^5<8:o_W-&g'Opey ɒWF-i䃈 OK2Y*Qi}$o] gDRND"hy4bkkIZ+.\ Nw.}R5s#-my/jp޽H( !"q1.u>q:=^kȲ *G1ؤ-=;,%_ȭ6|-tl,Ø( pzu`mXn1o5M;8i9}[<ŧId5ogF;n 5ݲ& $t)h~q&b6Ǭ,уh322Xq̧ \{ <86.)|¯xO y,^~ʷ^;n!.Ѭxke=8WVYk7 ](]j @>>JA2YjT&) L,L f0Vٔ/pZyU $L&?KX'g%f΋UB٬k ߿W6@ษў=fN}qAr@aXCQU}ڛX,{&mZc/8EKfa(D_T!Lѕn5K"HT]y^RmAl|Pl^օ+MI\ r)EsP$Fz@RQ7b1>#K|5\ .\ ! 4q4^ӓSl_cW`cgiMgXӣ`>6(2J(ĤhW+3dEh^gDm@56𝗾Y|haMw9A]"A%1p.9g?AEY.b@GnN*Z Zcp|p',a|Tlz{`=ӵ/Rr>"҃ݑTd89 ZTZ&i.\xov9Gh<5lWy{ ,{ 0x;XXKؼDӴxw,gE r,?-j?>cUX+![Wb0؀u9Njժl@]7m"Bxm@ ,1ZyU\{W=bA l=JZ֩F t9=-J\^|_p89@N"۔ Jr7"}C&5PBHTM0=9D'd^5e.]o_?G[օh^ysptӳ3,Eov_=}}골F;[ 7E0b99AɆ4ڄEO@Y>ҁd&/єft V/\qֽgS= $ bVb4V04Pj!ՖXt~~ o驘p߇oMy֭M8rhbizZ@d>>n} eBQoE4fwt._&.k4\͡@-# ~xdԩD>h|cH@U5(> 6!aU˺`X"/ yV 4$@w.=L^,irQa}"j,*`cJl,vvvqkعx-۷1^{22 5?(Κ&{w|6ɟOmGqvrNN~//etxB,VuFD0DftRI9N`)4Ɉ4-Ʌ}97Bԁmۆ^{M?U˿tM9mX!)T#zj ս{ղq9 uu}RZdF*^{oll`ww'Gx"K7_WovՖtٗGBcTy^]o5@}HD6VJۨۨQgy_np=8[Db!wy&Dzn}PtKMi-C;JtT!vx{:gAlpױul(aRMuEh*x(,qkb9{]:56ZI଄ϯ{ )349 [dyvyb[l  6+PT}ln#ћo; Ƞl*e^׿O4`9̧S<ӇwPOR >X1G?l7wVt"TR%8MK$]PqMh7ƍoH$D "2Eu{{mYd )wvvoo?SZ#kWM]NE,sdf\Fp@?vՔZ{m6U]{߻+W.M,5VUMr]J?g .՗!w VB4`_4DetQܘ# !!3(ȳL p"+(H -\/Ga]L_'PW W Ń).Wx߇ޏq5Ub6Nw0;9S- =E$ClO/Hjd\ygwLPj%{I`  kH3VS t767ćiX7Ţ=}n៞R۹l!f4CP>m x/Pn&B6$CfP}gS"% fִ`#KaiO1QU6s!eX:/^cx<|%?i\$&LJz m>QWTa}xS?I,&cT#m۸9T..\ `CS]ݻ=ԋ)o#h,Re>dB>ub 􌂣G))P hRA{$(Q$&b6$Jܫ=W(M[` 4 QtH`[Mzm۞Yvjomml{BB.p,޷0m gg#/}S/d ^)J  4`ئ9iFH)VARJH$Z =;(g'3.AD*Q9q},jr޹twXHbYkFI1!H!% ( mBb:-3"+| =$ALeZڦnotBXT9GG,AVb(2|36M,Ggyfwӽbq1YAD ŀh+| {ʕxGo|MӢu6+Cqrʘ RM&b:aW"XS&d,iզ))<k3gpmaADOnY QHXEE34vf1uۅ(XB L<ͯ㓿Kxzps LߍQIƟG ?_e~:}KbMCKk$b`OAa Rse[ g?+fX*$̶iGY+lYɄc3hk|Vh<ϡbCjvugJ$)o ז'844$Ni~ըBlՉ'zZkPLS]A\t92J-˼XƓ^s^iQ, U4HQSRJGD965X`ƅQ)rhC6ՔS`*k5 ȋmu-&);GdI6QZZ ɻl$Ew9 A EFFIx\N`,'M}M_:wy< 80^~}Q f؇'d$)c:YF/y(u ^ s$H4c)qR*@G,e G0 n>FkZ ΍g^8G<8>GGGl6 P?J)q02r*d`y9Zkmket>GDaiEz;+\Poj>8d2_E]WAHVP|8~I`!Ԟ'4<(Кw} W GaA9ʰlR%Sp#F51 ,E/:T2) Ȅ=G@C ;g7o}hkO=bd4KhB ,#|k}{!X !P'D6i'" Ȋ@]v7fmVH+|-zz}.Vgw9c'X޻ob}>!Pʁ cQp޳ILIB`RwU.UL J$ac,GHb~| b=ƒD% V$dwe$7swo{ -|W?4^p1z3elK:ku]ͻoݼ=s#EyC%)(aN!,^ a4a:)QdVVNfCq#ūi ؐ} NΑVXǘ #d8Y%f;ZD(9 T֒J+J(0QQn J0"(0P mx:Z3gp-<26%gbbơ ?׶{ o|KX,loXeMjϸDyeD7bCJ1#4ѷA4=YQ}$XK9EE % oiΝ.`b,HA <_Y}@f*޿\1Ar"R:Tu6U>tr3)ǭw]_V8\zu ӟ4>Ϫ۷^Mw}~ݻղC C˾+ ViaG.,GB Q2Cd0lxwJ"7KRpQƣ2 '|Ųtxۦ&R%( 6% J#745B7<`ur4Bs5H%}8uW w~EEi+8V$4YSYO;)}ZAó}xs1.1rRFv9%1PFOB5< F*zaʟ$CH,QW(=V"r&X-9KqǡDYQ+߾+MVfMOԺq9|%^tt4mĜ2B_l92ъMu@/@[Ad1 yl b1kWu-Ӄ <J *N*ӚJօ"b1pk*@96x |kF# @Q\#2ȲUY": F13RJx-) 4(<0~wo<8łàkHL>q|GǵGS %#H:zQnqHHt6DVY0IXBo6H DAhQY(RȴUz ^1G,WhаDP>w3G#FDE&܊+Mll“[r @AE J,)!ZvL6+h%LXs!NQ)LtnFj^5/G?;?7ڏwvvrW1+h'(}- #?Na4fJw)XiO4Rx!w{@Ch so(sP`@qR p£H<ŋ0E<{@ <$2Q[E#rR`40gLr&itb0+&c`Rf+ZP`X{ <) "|1* \9}}KPи!+G06CCMqhٝ# n tM>)<] NE9@1aPdFBtP))\piXilW_4iְIZ[(k[$LEh=8A"TQ 2XLWqJVݠp@pЉA)/š7flf\[o}|@AƤx;A2B*G.jUWVuYΧ}Y{ݯ|K;r'UUͿ2!环11bA+fy/bD(0- Ք,}pc8??G9\|D4մfx"E"D1` %SkY4 #K\/}e{h(!3 ̢(JX;a6a]jR9irDM4fc&igrx}RȠVLM$JKE~bڨF3/&n.vpU3(y/j5|O6Ezͷ' k" 6` <8laLBeiàat9!2k,_̮[܊"u6 "LfrD0Gd-5Qix0˳;{F q1k8["֑eɷnwC.o3ٯxOBf[f6mZEYתYv2zflS0Wx睷d̤JcL_u{րiX|EUUǜb*+CVa:ƃxrrL! bj .A^(h~iզɃS˸{uSc>go~~c^57viG(F'2R0ު6Fشe430@;CtXk GfP7=1 \r|&vƠ(%s VNDciN[i-Qi/G,n|pyޚGF #`W,҄Ejlz||2{キ>$˲:˺ ;>:?.J4Dc v%2E2KA\r-z灶{tzM lc,J"gƚ1 sq7ݢ3b3ĨQuzU|۝˘gКwWx[7q> r`BzOO.w~H#ęȫ8E f[ěsңc:Eb;&tjRЈe9n#|G14jl0R K TIXFiKQHiC' wP oޖ'D7l`"C`I:,1e7[/i$ɋ"l>"A*##JkbߟcᅬG_FwpzrbU_m#Q] cÔBp<2BJR.hToMhp0ϚDYH嗏_<p}w(,s̏Qh\O"6WM&U@u-^{Q6eA1L-vmF<8<1rؐhʼn@'\lU𼧙MDZN" ӆ6b|sT =| yL&9f]w;t}K0xO\-v{oz4`>b2.`Fz(t46QKm \(⡫b\W~}}gpUhx(j{px1QEAeǭ,YxM2^|Ӹ2$?1s^$dQ~_{__c{()7qќ3/|}[bgQ K)۽UU5J)-Č2lU٦XCd{mUT@z]~]?:Ca^E߻E۶scEt(Aڋ&FJ{LMFYZ\|Un,Jf9<wniPTR_iAڕ02 Wo6Z`, \1>X*+t]Ɲ;?c-vg8<ѥ}.׿e|__k}=!|߆ø{ To&J A脁-2*wd2>tڮBWuC(dP޾tmdK}{{;Ii(oEPve[Q,Dh' Iq`WW4qi*VdAI!!9R/FR亾Wɿħ~;{/o|Op7Om۹ > i|MpRe9hZt1%b<}N.Xi݇?h>o~ؚP!7dPfuUOnݾkJ-O*|^R2ܣ7߄E#1 T ̑=U1U 7A[Q86dQ5'k1tvv?]Ý_֛8;_aw>ۋw \WkKl{>8::C?Tr[d'Q4D@+q@iVĭK.ÏhYoj]V`Lf]kF~(TI*9&a1e&  IldǖְܝmRA\l ڌ;sk*K*љՅ19: %>0Z"4t&VhWKkxŏPU/wXR{12k5B@# !,I$9vmYf$Kܯ#1.ư]sO>?8r儲l!jr?|^{MCht.qgehjֵnr֝|&^m][sI]X(P|;TF怆 cT0lvQAtS#_kD(uII؇o>@9*d3J:{ կ~= xoo}{;BXlaJ쫘љS #}`'fpmLSd@wKv 8 G Y1%b"BH0Vb =CDmS.`"* J7`:I~L9lT4(__ /p&; k>0ɘ(ӡ8AfjuG~_7/~_o.{Stmk~\\`wmvkym!!S4"Er!F]dg8O}I_׫l>JRJ1mFgq{]uVmg(jm(4į' lW8;;hCTuU9^+-* 06ZF@BD* = ,Lb^YYz >zǟG'q^cfL-}Svul7皦wmnus_f&ƾGmϞ8RrBQ(geg"]ՈTq3'" m)Ϭ0xJ0$^ 89=Cz]:MӣZ:@ޙs 4Ok#уZQɃH 4ՄF ߟ~ }} b X4I"fâwWEՇC=UD={ePJ hL(QǤې͐%>tY(x+SL{-flir0J LNB 0@@t=ܿy5z#gٮXW0{/N8{, KDP$dkaIâc5 N#}2RQc2R! d"aSt6ݻ1*FMUu=BZx^F1WEh"ɋieyVj!:uzff }S;6smmD߯|Cv*kΟr7umyP8lRrzH[dʠ3GZRMG uI-$dH` '!j;*PhoA(o2}]`2E,`L3,gK*3 3dΗ7"lGͦq$yժF]W-{<>6 +֙O[2n,c{[T->W0ك6Hle|B 'Xܻ#O?wx v'Ef1KN'B*5"B59KVda#xy9 q0W.H"!#)|H5X:JEJ;e}/+Sc@dƲT6-zjB]5 B#΂6TeFn>6;;g>O|ʬs{zt4GW-Ki7o7nP4?N>}[j':2KG!fnrT'w) V) )Ŵ[R,*)a%lCW?FYM&3+I#Y,%<Ħk`$89(H9ڶcaEYߜuxkxCO㽷A f 3񞞿+zz6<-_?Qg [B%RSdy1BG}`]]im7Jm$KOJ L̯5ma]5B;W0LQoheO0OgڬWXc2ŭw~lXErEۋ{pbrB[u JDcBPlAEd*q ! JR!ɁK ^!QP_ha&y]zBԉB:RT]:7+u+39ȅ I6u&w2ɤtfӉ&t6]OFc"+o/=mSBz!)(hQxQ JŨl娟N&TYh%u F͞wzr<TDt蝓 Lla"/D?nؙv38eEּge;pxpo{r/{PƠ, uR2X d9TQJK 7kWqh3|巾ΡtuQum|Y 6C^T5x?[;7q9O+,;, {ʫ(Gz!{?zbC(s< ϼA{xu .xMfR# #&$ҋ$Z[œ DͲZV9`ِ9 NdP{ ¬Tbڑ<)ʁ &gSM\=3(*c^0@ !# _ji3-bt 3hxh,)FVg~6tSf噂:?vC|%{RJugggcw}J̴Mj<;Vӓ\+c [+ap`#%9fL ?@Yp-4Mx" FWtZ!&kQ`Hya n^y1?ʗZc6Ci3Prb7Xe4~_ċ|>ÿ\@ٜӓɣ蛕8(Ef36G;oC9R4=M۷Ope]Uم[E#3dEƳ9tf5 ﶖW#m4Y8|w~u,{쇘( <pL5Zm'DTIJ\Դ%G\%KzZ 1'u[kzdW5(lm\u=\߱&pA>H4Y擬ͷY&s}|=Ǻn$Y!r,6Jv-:;[itZE~}Sg_ٝ>*rZL/ '+^cɣ=r!b27oz^o&?)F4)RLM)i0pĭ10t:bs<89E^NG97.0`#Myh=pԅj{;FyW+ujt2f̞Gl1p9/5ûF:6)17⡫Wgq3[o|MMG<.P@n}7| h8^op1Ƴd*dF#[~px/1G)n$Z,,޹SmV(PLwQ#P\[^ omWb逾F_lCt+5LV)qV֟|ɁZ1IR=hL.<0&do ޱ@-zxײN"90ӈ%id2AEyxŗ! yf QU֫5QEQtRE"X;/7?O,v>1YUsZ߽_V^{MAQcHQpqlrfSݻ_g2Dٚ>Bbm$S,ߞI߿ c,ƓEI+H:Ej+F H(%<;kT&m r<(ſ'η{~|g0%vG{Șw'xC|#:RiJ3~h;@EdC) zk,{ )n65sho-~_]Ƴ`X;"+[xg1 JYHЉzl.R;t3@$OM+gY5,إ^)E@.BW #Bڧa2`4QW}HkHD)m5g{t]V ED CT_~S<}#LGO˺+KKL ?@Sm6 }=dZiIw9J{sv`EFmUbVrxۨ)nlb2gI4D&ʰ/J 45(ׁ|[%آ2sd[ }Tj\:<%ņ9<8ݠ:Ik7ljD6/$C \+<9'+ Shy1,|pݛ~yMo|Xט'kpu`\2#W]x2\B`T_ hzMcO>71dcw>X=8FAHCQ mCwm<v=L0"Ģ󮟁6w` xKxwIVd^n =!rq#eU5o a-ic!i$G (M~L:ƈ@"t}mw\d(Yð("}A@6b(%@ `Zo0w|-c) I,w\!( 0d\}71yY.f9bشh(E.+!Qf#VkĎ{ϱQ,ɒ)I=춾ma3 c3GfQΐ#Nr;(1uFeX+ckhkWŏ޽d[h@9=凱~pU_>ŭ'IWЭϰYO⫟=Lf;l-/jVl(/JȤY#]!,!hnLB8 bD*l 412b<%;5ٚ =BUB˾:}"Be|l0n)fy yV8@wbpL?4ͩk8l|q?v(G.D=:s6Ck(kb=M|cݴ{"45ہ(^L1uozt9E]Gg?vmjkr 6H,1%ܲbn5z|Oc2izEks.Бy!JHFwŒt,sU@-;gp|6 k2dv45S]콻THJAE覴#J1a<τQc@< ֝hMb:gܸ6ErrOBM?PfLgEnv{7߆ք~9xQla{Jd-!=ލkee`3"2ftt eQ{(;?wл}@E{QO?K.(HX.| y.aw >"+('G B7| J- @SZS71!4 `W9(}M )m(%g'O|Hdcl$%EmSrIZR|eaEYbT#'d,R,Uwg˦;yh>_|Σ_;]&kJwyg J{&۪(wbZ#D&R:2称J I$|j*8cww2Ghޗ,!#0GɁ ^6p@ɳ <&Y9lZcToi:\|y!/2#(oPȤkok1eEny\}vw V߇`oĞqGcV{X zj4lyw轁9"֧TX^B`)*:6"ĝ~_!IaKHQxOD4Ĕ;(T`-ISO%GamҰozk3W仮G ˁ(O&GȲJ+RJV:lkX[N{>=":ޟ71JWJYxx/g?vH|NS6:jo*}5Κ|)V: QbK#[/%=>zd:>޹hqYp%l7:8VJo[LMQTS ŝ6G9*{ E1ʚ*҅(b<̡s6èa2|ߐ, ^AE 2mQxY''0.>hϫD8c!n!Dt ϣ/> P9F.Y1׊g7ɑgaL&N@$JEdO'O}w?ҋwܹ1flSXqۜC~0ɽcܯ\tubWwQ.LFu\>)F2̬v$i0OcZ.ayVkN4x4N(kA =r 6;í{kɨ"ӓs(e9XQn@6P6/%2k@^9Bgmd" U+# $%0)N ]ףiZl6,kTUz|@vhAf(12[`s-4' oB$|P ]u{x_ADVX(?Hr;?(aϴ֛BŢsÐ{*W0Mb^7杛MU},1Faxe{!ird0Zcdc,YɸDgu"y9ŧ/ʍh'}JB7aJT,ɗm`1j@1ym{tC{v S&a)9Gb>?˗` ٜ%;>1,Ҡln̓׋mZp}ܽs}Kt7֛ ھrB[/ѷ[tZS~3Y.._{a:߅Ro]Ϝ̻dpKRO(Tl\[ M7?a(f!& $bf^[7;@!,ʢDQl.$I&QflF2Z1YZ^u|g>?. /| 䖏t~(u۶MUUɉ7Ƅ~G=@)೟ݍm ص|lzUf|gOv"?qч5G$Wt*DkT/~1w٤֮(҃g}͢ckb-VJkmۢw ; <8>rUcgw3aҭe A1)PPa}2˞;n0>箿p;m Tz\6W^y` J1mS]c_FӃjgg],ry6iygnz2Q23.@'(C7lV=>NOT|:F"J('-=QH ,c fihǠBD>+0qp>"D"D3"/ p%ހjhcCEcDMڠ6Cd'9o2 @F$> Jwjd[9H*[fz?`)}_ţ| w8ShC2/Ł 'Ԁ^\S+$x{ah44߈_@ߋ\H~Bg#ٺ?'@QRTdEFaoofZmmcέC߯} ˝駟zOϲn}eIڶ?'*|Fgףyg)kѵ5B_̛|c5E(t_}Pٕ4udg=h]=jÜ]  rLYuq)},ˌ!@gE6!!d&C%:JB+dy4dAûlk:4Q`^ 7c^65z-9_Zڒ^X/-$V|tZ*9R$v6W\wH析AU,qz}LiYx#:Ţ}Hn!ܓt`x7QCױڲ`W޴2T joJ>{Ai"4L"s?+W^=܉Y~,nZ7'NBc}4}TĪs}=O([oN*4:1,U#ؠ#F`ŃTM<-%WưzZ1KJeU" X͞p*Fh2 c`0; \8`pբ|, Z,+4u$&xK|{)!-<^ VLYަ!]7%5_quL6;(+{Cج`:[t&>/ Ew/B"i |>AA|<+XkQ/Ėe՚`dfi8<< ;=oc&4JΕgWzG??:8/l e>eak\?7W~,njxWOP4A.wU^Nn)/U$tX %JD[w KtRl 7DR-RꐌnH[N >TuvXjk1MֻKZd Ȑ쫐t%`bZwEK ! ]6 *l!?Ӳ!3>{Urz2~јNj{9i6lgp:bl^1 n嶧BQF'qh{5Άn`kH(HD8N'㰻3;{iҪrίkcgŨ8yϝ~٧Nbz[V>UuUEifMWՃ?'.J)k~tI}]ko\GEcY>ezJDJHD4>xl#;=ކfDVXka IH Ӹ+SmͰndŞ;EʭE9<EGeяIUѺ>6 | k]לO'|ǟ0Nݰ(rhSiN *݇Ex71iiB 2뜷 uIdxuȣ@Q_,An>Q)t]Ρ޴X.7]=f1NέKֹʫn5Mw]_@x8Ğ?ꫯg>t]0S'nuӮδ><0ҶE SzNZzm $K7=bP!%2[v2i<Ų,Be^)+[ q[Y-|:.teMߘWjf눺uNW?GQ_x R zGyo:EY }[zo˲*I |e޹b^|VWcx=+t}ĺ1aă0i9q8jk06)QFmt*Gcveޑs*LK%;!6i+sz.R8>R4 mLD~ Z *Թ譗JgÓ W$'5PY,"ẽ@~C^mz- 2?g|.\}W&7W󆲬 @of7E7MoԐ?xU|qve7yy7]W׮rz6˝2:kS I$揜{躈ꘞ ko9z]v IQo%-mcHo1LI`VuXm,Wcg,Z҈xם眗h%vY&7fL }4pk  )-6A` >Q%Y2YcC1P7!T%/ Kڂp @<L:ڑd=|D^C4[nkşCb04T6Fy6 d>H1ù(x:=,em:_4$oCW[/0=~'Upm@t.jjֵkj2=hҟbԊpB9[gp]Mbw;;N'8==fSm=Is?|9ۍ < r "mpZEcxK\X/.7vSpe0Kށ2v$N23$DT>r#mn9 )f$izDvP/$isۂ5veA@ `p&"d۞yA <Z!.kd^gq2G(RTy,,y,j]z~e>VM٩ٙͳTK**UetސvQ}۶7n/g^?賟}px-Zg̪MEb1k{U ZAV*N3+9v}"x,1Lp~vmZ/1A+-5^* P 4m\um9(2cL']/|qjK`]͘bK1~u Xk36;emmt\4%MtY⹕b R)+ׁD?-U"mGx|0aKF Z'^IAG˶=l`m#Cz{ !lX$zг&Vq4*bȭZ9=E[ oyQdgU&ϬYkVV14J1.\ubgg+!Oh19,;Dͦv]_'xi(PHQyɥXl ÕvQ0_ܽ{vƻ 孏YJgsg N)s>I+Ӭ iI ljz##l6fb1AP5jScд x4kzAK[U\'ɭ-I 7F(3<10?2J RXT0H^RTFӬ}u8Pu?X P +1u9V5ƽ{QNiPW ڶA>r! N/Qbu"$lR?1 ޒV3!'9&Kjw(V ~ۘAml#^L\ ޖFˀcG]@=dNB^QɈ>aDSJؔpk>Fh:9h p BYM7&7I%Z„[" Sf@=Dlv)AHw[]6xsr@)VV5 1&ֈSV\fLb1 fQ6t6[?Y1hj3)A[b ZRm>Lu''<IP Dݺ5\zzz߻;w}aLjmi°m~^2G3(rkhV#*֣3x. &@sS̬X78Tw]ch wquǸVEW2+踲FMY5:[.:D^; 0g'Z N:G_ducWݻo㛶DJ"x(%*1 gV٪ˆA"ؓ=Ā/+Wq=a0b ԐA@ŕ9"Kye=z0v#e楈- [TX!vBI8Pv%^>mNmiLK䊼:e`1X6~hm ;B"]5FE,3H!*tLkA1hڊTYֆ,f7!Rw{oBHKk* sFcm46&ϫ,RI7Ml4ꭽ⦅ݷdz'ׯ_^}O5"Rfjٕk==?o|8ߏeypRR0g}sDS7 ?"ϫzu`?.xQ)uaOq%-& /a4^ăwn@1WI~Ka2vM$ÃDM']S7s(`?`rRjqtZiٽk"{Ѷ-,C”d_̳yfHkMxBXTU އoR{_u]" KV]ZV5K鵎fc566|Kn7Dcu-97~;@:O{ źޜד~3$ p!ig~!>Z:aqsA\`xM`΂?cfvxCt(F}zjxWq~&6 Z["dlqVW i$}6107bO{}x.CH?g+%qaHnOAA3[ ;bTH\eYP ZlVH\,j,i7@{7}VDa5΍YZTƬ2*n-ѨpO_^Qxh7cմPM߬H^,v歛ƈ ".ðy_ QO$QPr[ɝH7Tz Ho,1 P ;x+&899'>"|7C<XWq() 0̺rĭr`%uISdZ 3:Q2AqlCT>^h,I|4c((SQ>-λVh"GE^ SsHP'Lf6jo!^k)ҭvm(bbXj,άQ祉g&\%4| hZ9f~:=srۿFJ}^ٿ~ {{' ;NmWj25x\:Ŝê.sH{xJbi﷊"$b HIJL|b xI=Nyl+`jc@:,2+͖ote¢/lnӰX_<rY??JH!7c壦骺z;/N<ۄƷV:u }L.7|hpI(HbzAF/ZoX֘&|u =t'kx۷%ܺuIn;=c[vQDE-E܃6JvfI`kم츔y^q^dc.B%qdZo`lY&5 Y] Dyab1w>䌎i[{W6 X[ץ,|۸6톌^кv]絩XǷ=~*`K ~4 >L^ou5ݻTvow|UMUȷ|R)-=' η~o.F]p1F =x<<$% vF"ݺ|oC@| >lD12d MD|<'CM v" `MВ& MޑQ)cAk#!e1TCc6&V1ҺԪfe VZ*ϰZQmL0u1 {OI;a|mܶ/n?S)ko *ǫ΢?&߬!()FE<1PP5x k 1Ho[gȖKBу(b1x'ƃ_/1|׾|_xYj1;N(Z^fHߞ$/ogͬWC\Mul5h6=(\?_|oVx?O|s1^Vu Sgޢ;7'Y*e4%pit[<=֋`m uT6'^ć?_U|?1-=P_Ѷ }a D >BlƢpgd9Jn-![';5dO1*H.C\(&X$W@ /{?eH=-RL}1ľڎ5yW5#p  r}$ӷ3*V=cq4IYg*fǩĵ`a\}jjS#T٬Y,]fӊ=[Zf?OIPw* O<|ʕK+/'>  hGWoc41F iYJcI8pIʷ D++GMD> JDSZ AXDzY RT-'(M>FD@ z +5Z5D*Qj jbh6Vkmi|ЏῳI~jd W3msRGTټ>88f[!QMT;Dͻq~SDsӌjCf%|ux!| D>$ ƭP&=8ܕġ ;&BĔ7*:IeW7@:$Q.2kBKH:M13)*PQ)5Hu.4V*]F+6.,ZʳQo de՞\;.m ٦s g?OSU8A}3f']1ʋN?ɧ_淞?ws#"H@MF.*1؍sLz̓HA()CE@֨R@=TC=j+VDa--3cVEfVeX*Ҧ u,tm5Lsuex"=qDUn_?.A@=:i"+kk7l6iv?x@rR!Hap -ުؒ&O+:ŪACm{ k"!n~)򋆕a ^KN\l  ߊ8HzPR28 >JQ$D2Q6h.]f. "7+z[1JWʪQEVur^?lz_v,_?zKD3?vq@͒]Ǖޙ{VUA  FP! {ae͐E-ڢ(I( UdA9(MP(E'srk7n7WŃzソu^p*ȹ ޹iMyjep(tHT1aY=H4=W_e.ڋbW4tK!oVϟ8\-ܽīœ_*g 5REd.6&\!ȋG(iE F)"%ṋ̔/3c VpÊ0l~rk#~j=< 6 3O}mm6h<ǿB2 yqD~~ 98]vt .=w{>)aV!F;&滻 /;,rۘUx(,0sGE>j4cD!B^9OPU-D = e6\2i{nPIљ;&+coVS4yuw^xy*u xOּ-0,}2vVn?wGeBxH#0KʢcL6ʠ= I,| dn⤐*+ȓILPA)!Aی艱İ9%ֳ49YK{<cn~ϭ"6< YdUql7`q?rt| 2Ͽ%\P]hF\Ey+QaXz<eb1?xo0x*x28 ,x5E sz#)z&߉JG@ 5k6f[Xsfq1ym`j9ݦСdy߱y]}X~?yj{ܑ!rogoŽf휷"<28+0Dww8Ky#`584@D'sun>ē18b~l<:.5 K6-T) :L*4@e`BPd!F=5p J"%mfʏ1{ L;\ 6 6p_zݻoW%o擳^OH9t}ggυd.-,OK[o23˟EEΊ; 8: \AS{eܔ{D O,FUUQhAMWT a-@mΕ!sYm:8#и;"-5O 0˃OO msH?&n?)̚ =(,sZ"g\hPQͧY_w9*L颬Bbq`bܻnD1 2Go&/s 2 0!c*y!x՞”^ML*mZCRgڲ4PAmnb:WV_f^o_Y m"NF}O=EfkvSyd[˄Ibf&%g;E6+1z:,C?|hX z/<a}9[0LÄ/Kc Dfb L @q ВgiF-6yjHMFs oK4f,jjswCSpU5Hֿgo^_1IڃO?%fE U]2Yk-ك",O`r& &נE2l@ƆCdƔx7[y+b(CFPmPRm *dt635tJo3`k24xq0~0KD N5? ҍL|e=ج.WR{q̳-io71ۆ9<wea6[G\@,JJDeJ0N`t`NF+e3=+rʘ$˕Yo5ˆ`ݖ'w:e_yz. loE?>~kM?}Q02f1w~FL 5(03lEl=hb/LxQOt]p%07 R@ bс:BBzt j2Fܜ,x&2QUhMbl7~o ɌǾ|"E,%/J{|úٴxY ~_Nn/o<^EQY+=B|?k\2,1ހX=e7`,:% ^-/ / >+^&" a@Pj ԩj`VܬYfmY&7Zެ͐#Jju˝]39ẉGW7p((m_~ѫ5])_2;/tLKVlS{KA/y8?p̖"&3w!9*HՋ* %04(de*UTP[Am p&rY^LYEk;b7Ņq(x* qu'+??-?;<@PO3kf C"Y i^WHђyHg hmXZd kL&tO?"Hmt.A3`jb_U oD'O u#‘іA:\2EqQ*9=9;ͪ ux0^N}ݹsG&?gVk[b7M_觯w0L׽ !n cbUUvx1 yiq^jP/B_8JXzUmܪJ BIEV9-6CPgzu~k3L5tO|U< @AçY f>x׏^Wo٫h͆CݎB 81 p1ؘ9ӆ`ip4JF!,y7nR-tO|<6 ݇ʶb7n_tx͗ˏ~vPDD*mg9gP }"(5Fy P v^q|+4*5ߑ(X1 hjq4Z[) WErmLɐlE7So0%0?XIZE9=ݙl$W3ϧorԫ/<|_&\a2lFa2 %y8z &[M;1[,G UBEX, 2`.T񵆺5\6+Tʖyu[kʲF<Lo鉟yc w\d|oo׼KD8dCks@ps yŋÞYM9?D+aR'b&!TЃhMIDAT uBFݮ ]ey.QeT(VY[kLGmp1zw3+/ ToN_{񺈿a^^.fndBJlp4* >hn(Jl%w^I 9Q3*0R**TrgnWٲAVԤ qgYGj0N+D$~egVYBD^7~GҘVCU=i#Dǃ!C"cbtv묻! %u l()Ԩ2 @Bj=h2ʣBG%JC$BkVE-rS-nڒ+CT)k6ud{x,`ڈ6s<ҽwU}o|J-tVUOcMk.V9rbdCf!#"Ix?BAN Rej %GԏL<: ^wX2cR\Ey"YEYӀ\o)!ٰwL;7nw*!g&- .V.{׍5}ɣo}d{L3 L&̋mPHWNP Q!A1a1#~ B'":jh<7e2[Mn8hȆ1=6']D[oY 90SSхO޸mc˺0z̈́ C'b:[U4TC,EVvʶS嶨fzZ;;N+)^7+-~HxǪݻKoóݻ8񊅽+<+룛zD6\ppq^U3ʪ˜jSز(LiȔ6X*Q4YcWF V+vq187?j ];ڂ{LG ^>+?|_}ŵ%W8:I'1~}I>G٩Kq>>JO*W_}#NᢺK>}lQUwov#-DED"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$:/B IENDB`OSCAR-code-v1.3.1/oscar/icons/prs1_960.png000066400000000000000000001325111417327530600177070ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGD pHYscIDATxwmu>:a$%R"TJ#Q6DYf-{b%=*M[QUI*i%K.0cҘ4%&HH w;|}m~ l{9w~vkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkvkZ>ڭ7?}̾tNnR}w^^T!G2>kvz`oV׋V'b=ܬZt1x>?䯾~NoOJ֞?\?-X6\go\X,fX }H)a_7)r=䓺(ޯ6?8ϞX,]gk^謅sI( ,G#?_}F_X;[V'|rPU1;֋b|r!]k` .!X_ciadAB#o~Too>{ë];[ Kj^n_/X.O,/.7/gj>_Xg=@Bv' "tExڟjN֛n=x֫'.//^^,rA]X|D $7wЇ7? L(_^;];[o{Ukۻlbz|~L,5¹ |Y w)UmaUŧFtno5.Kj5r=b9_=\.}y9`v1\Xq:E$ڴquׄ M;yUsO?ywv `^%oVCer|~Y5[t_D=e䞤r;f d|o}>#LpzY>WoNN]]O>>:::\./߹vy1?\b60w Bh]O{KJ|f𳸞mD{C-Owq?_7K'[',1\ߣZf=qy]^./rjPѣ]DB5Ȭ< sɁJ@f"/[W!g!DN֣@v!)7|;kַCcX^'f{z2/zݠ5^+dn˭B=εVRD Ç[= x|Nps_?Y;[t={RnڬW[\WO,w`6_iZC-|̽_êx w_5_F;+;ûb /}q>!3ms/oUح>'xczrrlx|1պFZ<~"x "~ۗokcH>3S7 [л3= m|I v<^\͗\^.^:c`mD۶aEJu=DO9t^.k[=V*G%{晧>c@FLJqw_l޹^Ol޷\nX.{F7-:c$62ꄄy$/!D‹BN)86A%=Dn_ =W2o"P ]A ']| H@#${2.tr$U"|P&B?)7{?Ճ,׋ӷWd<[#GZ|G?QZ@{1[_ϞҿKߛ*\.mMղ\Mkτ-1 u]9ǂ_upYMf?(|C6O^ 9'W } bKc/- F _qkv@|I裏x]/}潟x}ggӳ\G|}'~'&/߶Z\__\\⭀7mּ<[7z}sjպu>X4!w$A$Mщ->ϸd=r+^HHœo @_)MJ/ozV]ǃ@ z|O _ {5[=O?j3hޱ^^w>o=?8sz^^`ڠm P( G#??H|OO֭m.OsqyӇOO/n]fX;y{ݟAܼ1?gc;Xoݶ&2s+bc2v7^K-|R+r+^< 9TVuz=N!"WXo׿{Qf7[>[˛wNN'͗X[XkὃVt9f@9@;և~gm{ˇ⽛=.N;=9yrr6sr&./WkPH{( | /gwm6u@+|jaϙugJ$+xYSO$\9UX/W2{]d#ߣ^[J /$U<_P"G˽fVͻ6[_x;'stzDα\mvh"ԼlmJL(zXepA* )9I넔.Y{6?P[9Jo==2 ]vm͗=^d/>Cчx_8?_ݮ~mֿ ?r6wlVシ]go=;ؿs8;ry`4^.ށg_x5O̖HM_a0;}?#PfOs֭}rz56u +EkL  ]DƝ {RhEkwB-fmj;B:6& g qe?PAqyhQ^ {XﺻnDŽOT;?ěp})lvruh^b6{r~󋋇OOOoܺu:8=aڠqIM]]ru;8Y^srey9{hYYdoyfҷopXAo/!ḿ{& :x_Bq ؚ 9r{~h 3>پ;.@g0gtܖ`;{d^)'|-O zxۀGvM?=o Jt^|ӓ{ϧnl ^Pr[(EэP [cd+aL @KSX>siz H ]Vp0~&l7^9W*Y4 z{ ӵB@e( BE[m J\'e 'E ?=^5}]~OmEes )Ǩݸ}'}+ůf'?񳿌vMt|YhKJy=$ md!†T%rX2_]tkcs;Yf!B@I wD }!MdDbz0Ẉ3\۬}Vo]n1'X!@O.^aIqL*yw|gƭ͗|xWgOKXKZ 8 Y2!I^JSP؟N`dl5x7\Y˞B_\*Ekc"3Ed}KM"siC2o۶OBϠWIdnjHw m|!ǒ}{X3zh2I'f0x1Cwx/|_ٴ!BRʲuJi8J 5Y烖 *u񍳘-V >Q/s[_!&zw6!躭pޓ̔H4mG*Vzœf(!D# D8'/z!@^^PH)x  zP;,DBxҳmJ: wH9tF9REYF˹{Y[/bI!$+ /wr=Y pNVGd}d[$/Z{Ȭ(D?lL Ee^zzoKdp32xD樼kBKEJjG@|0 N Ûd}W(z3.J Zˬ 6 ˅YI]/oaf:ۀ+fpyk-upzS׃BHHᝅwWZyY՗_/FmQ|. .m{J= tQRɋ{Yh@Q~C.|p>TǾy}wyT'ba kӇ!8,кs&Rbmq) kBy9O|RfJ׏?ୃYX`Lu5pZ:8cr^oܑU4Yg1P')7⽽,/Ǹg/hE%dZ'kJwuB(=NOsW%< 1yw0M_  8BրPpbM/pol´k- E tQ: /Kl% "o%ǺfǸ2k @KP-sc*2 yC Jgs/wa yT j4pΑ)v@a4*08Xgb[GP.ﶮ! Q '=C4L&d|v/%{"vOO'J/o{v}]ީ^|^z6NN1ж"Z-!P J_ȋKBFU f"{J!еJmjL~j;oLǔx>ڼ)IhJ@%8OJBiHEp0%zJ(_',a:E9'h@F:5|p}GNwzۇݚqe&Q!]m)+eˌ]Ќhz?vg^t߻ZϿo>۷oU/t ''g\;XV,r>?Fo4& l£mHY$RE$_g^ 0Y H7* s>s]>Ê_EW+ J9v[<#S)dQ)PHQ{'<D0{Ia.DxAo#*)H)?3xE"\'bC4܌lrKl@d7y<0(%fˆ _yA_  `^#3JGTX,pNH2B@KZ+e!$5y֤bBiXTMqrq:Aj <4W$VA)am\Xqh_ >gDvp !B'ANIA V2*#T HHHM8xa5x(*B8xf^NG++'KCBC>۷/]n& j;n;ڴ.PsE1M{uӢmIӵ(y㉗sC˂ 8cX)=("SsPJd MSG\}Cpq V}z TAH) є鈖N`KdxB@h 8NWgƒރW^ \ ~W/Su^ ?%E+!!&R( k! )LӢit]jޠ,5g۱YZh\ ?r~⥗W.i~NSwѶQog=xvzr;}Y}1[:2N@tgAO<հ@G&\.Pՠ* vC0˸X @BIEy(;OS:nvSLge+-aCݴΣmYr֠ds˙TՀ%*ϪO!ѯМ \z*y)%)a)eR@H 4 Fƃ ҔU/gs%\X,Whvpg|~ywאG>q(/|/BqgX44X+)bc兮ץ=u]CG0^Wd8A8=/K27W1#ʏEVtW{>xvyypvv@g2vypTbV;frݽs\1uT . ٦,J %B* %0֠3ZC]gt>(Ȃ'E7\O"8vjD@uRZÅ2̊~& u]<ۨ9p׻9ף`qqD " "\i!4T0PjA5h~d'@vvRJR-P(AD5qXxZ1uΞie!W9a*% r?Gs1tZ Lg`cg+|5|o1Xo6h@ @UQѾB/A~+)Dx?SP~#ׯJ@cGfA2A#qS"E1@ULFgZ#,zN CP B#K=!B5[b `A׵0mmaL t!1J$k| T}@YhZiXR$|/#6в@U:[39~d ߷9ك N@@D_ɤ drӗv]8 '‡t e|{5-:9BT"2 8Y:p[$XEzuyBG `xk..W"ED.vk<*gb0UU*Kxg``:9323%[ Z7[p@IːϧTDz]cZb\٬|qDYiBC(C}k+D'yD05p6]2s"*H'=7޾΅MwWD1rEgE,y?I>>y-GXhkMۢmNYKz`R)9#O9_xEPTԛ56XcuK^i.rʵ qU-זK =o/ֽ__^>{w+_ 78/bSs1}OR_)mĮ˚n δYkm$3VʔΥ4e׳0_ K4P á&M^~)k:_Aw8Y'#@sP2vk.u55aBv=]?$|蹳ug`;$.X$- F!&t!΃B%C51Zjh Dޤ{CF5ik*j t0xPN nnx+|]ϒBueA'T޽uBc7 /pw1p>  `N9HEC2RJ9 6eL (H6Ry;2VVptD]K8 @FB)h;RVdss~zN^ZXG6,`+Y48Z,֤/[  BM-%PRo6u0,ޕ"/:Y'y7CKߑg#1 aex_XĿE../0MPm > im)iOդf!%0Z!|_8^uj)rn޼g |Э,`3vˤr7lF@ߏFptmGz"x ?$Tšq>e8=Coꤔ|:$/wBsha:K"TJѣJciK~ތ"LbEY'tg|}"]YwM(0Q%n޼?:$.4̅#ˠR-8(=x2FAVjKQU%Lx{2I~Oa9 tu;RJ]}lb`@P}3J96e mS'Qt h[&8G&cxDO;ptDs <{VRymߖPuzr c Nbo:卙.jIS!@эCHx8`HL #kr/>o~ՐR ȒW|W)RBBGTzlKp"`^|n^PZ#4jvF_rzH!#tV|5q)ױ~k>S-x~7]n kyYLPUyso%%&P7-֫mg8T5iYcIP6GYB]|pr?|pgđG(?GZIȆOXH TPWH c:cS!OQ~׽d)jQ ~lU_RA Z[/Xسܾe]!=Y4OvI.w- HRIrZJZ)I5^y~60b8>˧2Q y{_S8F<%Gwgᇖ0֢@KJj\G9Ct8:d СI :*O!C9qj;δxVa#{Z=۞p > ~sI0k0h,ж-fE,|J 5qc>"!URU= *eWe9gje$lu"B >@ŋ퍞kjx> $$j1&eGDp0$;|?7UɼMtZS򭵵™ID^RvCpu{,y0\)爀0_pzzB FeBA5DYh@I&B=%!6# q P@娘Ϙˤ9+dʼn-& ,N/P񝭻2֓R \\̰7d4‹2+vegƪ0QaFgl̋ sEo ) ʼ|"IeRP~G1.m 'sީ >/y.aGUrIIuf5}s.*9x^7]8l5pMq]V!lj͹5):<{ܺMзoa٠T7oc:#4W U#O~MۇRWygqW=r}ؓK)k+#[K+[wύFM b9GGZ@pO$##Ҧd+xG2 Gp0sJI ىt'wR#~"%A{DG 1^u["qlX;"6d<}js5Zs9 j-OT׳Y= jm9&Syҳ"w,H*IaҰ֡Hq0q )%]ј:hx#)yTQѦdpqӏcFskb !> `x7)x|>'8<:@Q]ˋ9cVj;BL 0xc46!ԇ9G]l[tJEy4tQ!dRRV8y[v(Ң9DA7":HI¦d7=zVX qhӎ;;6Ǿ|pJN R]{ a)$&aZ]K~V5B  *+CZfk @s TN!   RG† r5Ywq3.BSVq%2Ep;>P˭cy󽓀+ *=Y' _v4:ٵ@ lgO{+q@QhX(Ȕg rܗO&L w/3#r]EP<)=?+𷒳!J)ZÛp&ЙZ 8*EAaJAjRq:r,%8-h5'# "C }{\7QM0C۫) 6ڒt Y/J={'xnp>W1 |v`omW'X/:LѓLJDŽ`73Yf;y TAxc҈#?#0}C iPJQ` BZ1݂%  zC (L , G/ qs1ݘB3*t[^JO^ug_{Cc0a4n(ƭk:5G,>jvmb0@L0 eR!Ϯǧ̴muT #C/Ɏs A?ߦ]URyU+( b}x Ge{C US B=zkNAs8xV|(J$oӉ!N,•Ю@)J UAPiB fyX X,.VJR,,ML5< 0PhP`dvHmo ,`Gcmɸ>뻷J*Z(K$S<e返$VjPXC+1h y5C9¹.'qMHoV sٯ=ҳm rB8/a֛߹Yt Z-q1[h8;qTI7;vF 8h@e#R2&.4zOD) " ! P h dv"մ"θ$pRs0WF#@JBE"ǵ$8"۰OZ/a;CǼR.ncgǭk(=eQ`:BK@M>KeUZLRXxKY-ُ o襅EP䲷duչswGS&]s ޏFUSb]USⰭ? /@4i:Mhu,oG4;CQi,f?@侬`WS($ |YhG"ƂRx㣅ׂI!0D bc @` H ![>Yis0qCpjI-)6 TLr{n+Fٍ(@= pH !ѭs<)*L|fq؊:a|op5Llv b*˯_~ۑٻ </>T3+hkYA %t44ke|(bgQ9e13.S&=]r_g9b ;L>PJҀB,jpBh=s#w'>}@N!BϴjZp9{=%l@2?֍+rY[R|LE;܆exk2vh:% (Vd';AĢ'nj3מ_fͅ|3ߴȣ9$+DڎuIeq .B K(ġm?VhY+,x?!BƄHbX`M<˳ #\/FO\s-UPaO]Yo2V"yJ~ e^uO|?SBaI|V45F۶8>k;,5Ue3zm~҃9ZPыE$w?'%фXQ9JUR2YBGpBtɃ "դ">^?st cxд& dNJ6YHuf*L,i:ٽ xʌ)^)2E: ]d{"@9K=)$#?Ý;~uΡ̫Ys>rVr>q"NQa`̉d1hy+׻ 6OyඔZM0 {r;X~*t ȭm;"DtB\~<Bvwpȝ$Mb!~͏P# ,V"SE%3HwEY,!'棔0.չtS=PGߙЗ2$U]]^K-2+ /eӠuAU~<0~Ovm#v CV JAh(@?on:ٲy@oxlù'Yɨdρ ~`/{x{<1evjlz=:2=&oy|?8:#!`OҏAi~~OsR WTvsIDq:q1C_fg"6y+ICb)p?D"eVA* ׍扑,)z,MPC I<j߄0[k>Ϙ~#E9}:4u9֛m[,5vLP~UVqfY*vS 3B)5 ROF>*Yhz5=%4gk((9nfQ& D&!kraJv"'Rh65\(Us7Y롩$QJ'呣P}+smyeb PŖpJpf@0q#8{AC"yXw?rK28lY^;m7$҈9o"g=~H ,(^Ojyt] N8=;g6f"dps27B(-M @a3ebW9 5S> EQb4a2b=b4JwJ|weeω<BGw4YMs 휃ueU,YÔ,ƊGimԗ)LV5MrQ]% J\{BG!:u=MqzZ>S=zpoz.nJDPWDP){\^^ξU&`_|q0(>x<>NØf3QjAU`"':@j4]ᰄ2lIx 4Tg-XAq Di.I"TEx}߸7p|txA5zr^z_ /mGh U 4|$zB8LF2vкnPDy vuO/H( 4u D6?>ۨw8V#=;UxGm֢!:{'%%H!nH|@p-E^/-* ^/gU* 5>_>~H-|&`Rᆈ?P`Aw(2!#9աW2Zxs7n܃Gy|;w=z[[ߊ|?p?~<[#|7{Cu뚇S$,_q5-$o~@teyЊ_=XQp><}oFo\-^גǪ0owDHȀd%A&qBc{8?Fo(o)m9p NF?;_f ~,,Xb,;Цs3{qMpq8[lޕ(%䆿Ck {9ݐ|GEg<׭_ 1|k},lϫ\ۺte|PBYH|^x7r>; =1ͱ^{aXqot+D$ܤ'2:zh]{)==Ī,XXa^MTb㝋n5Zyj0DUhh)/| ur;gsv'u n3Bcد0qdN|(3<5  :'g&5н03CUUJ -"T{"ꦼ>-Abڶ h/E}zQ|;`[ۿXyA!cL Ky 4}SN<-ΰ'N./N?y鿦 ~Cc]kf9Y]``˄#qc CWH**9sΜ&֎VJJg=Fä!1ppp4P%tY˜_WK+89AT9% J{n:tg}6/bXa<:m0P%!^"؟0B;)jJ>G-Zt %i\Fc:1InQVy&Z3$ L $#5nd%d[$Of:< ׵|5 sMO!DFBBGyh(lwf =݇ o~bv~~}m`Uűc|rDӴ{o`XlTR ֻq><=GUTPk*&po`9GZsjx#$GBԀ(Gt9 ttHӋOlk'˷?rͦFuN!lf" -X:cbipL\'>b$B (>h!QhC[7R,s׿{ x&IDK4-,?kyl]E_8E|'!Fx2r_Vߣ0& w|i6f5.z^ky?jGtØtЪAa,UxmUI):<>Uvx4=F*- [!P0w23lVtI)GF MV]9_|EY`27X-Z{}Ȣ8!Z*|dzErjmwpL B p!s׭1֠,+~vt̞M}eKx,4@^H"o-cdr Ӗt>dgE6KS nVث^r #P,ceW./N>*f+y|SUUN'PJk 'qMH=϶ZRUng mAboJ8y*𸑦RE8?-iKeIt +[ Sk1&?/EQa\ 騴^ZՐI;t8הft'(1b4`'rc:X/9"JO2]˜A")4bZ7ukd M cb w э>ihEZ '(M11p۝8=8H \9$\A*8= 2Otgm:ڦvz`lS 唚 Q#̾'A4ѻ=,(O* p}WK0TЪnaC zXK(6 |J?+{ٶBjPa2`4A*i^?ƶۧ^1@(L0MG NJfA`L?B.2+KM$=p&xBq^ |W}^l7"Jkz)c£TiܼT4L4 %qɇ}^"N]ԟA9a]rS"Y㐬H1yWNl]>OW'Lm;~KdSJ#Lw}`F ~E75ݹ.f0d׵M f!D]fpk4n}&|?[ڶwļJc2c8n6[`: 5M;%yO\* Dk 6uCG†LXS=+flV_ t(JItmGkO6>ށ)v.?v ruxiZtMi8G+"(X!hvpX{$}/`  %tqsBde.y pZ.<%>Χ t@0Lv껚 {/ЇRtn85ڦb1r]=5Τ"y} bɹ>uy Ǔ wΦj]n LK%R'88`P@`X*J'b]So|ʻjR7N!z缕IK=.t N;j $_X$|ف/c.|xEAMO];Cw.|X/vmҪ*7x4 MC%yA]y`,Re f7CBb0{5v-%M^AqPBLh=Sy:X!wmF^c0b0(mŋb俨JiLg0 k:4Ma򍥙e䮽J*e3eŒ , 5F!4<Ꜭmٛ2!\zrv|}{>3|ytOK^8)"!!cwuܡ82E=,ap62NpZK~]4S֘Mh4?`XBJ|0UDCHrK46g۠mH9 h 6ua:>Z)'ho* R Z+tu^YdJ+҉~2.'e ޙǍ(kҜk>wo}Q, 74"; % AaP. ކl}C</%k\JOleP2z3UhЄYlb'?O05fSYֱ`'yҜZ `s>vކkkLT+9I _l6+{)R4 ֫{E$׺2PGxI C,Sb Ecl#IȐ~RH% ~ -U. ~@!7[{O>vv0ԵDY۟b2Z:Z8Ot-Q}78A) i?cѴ-!B;a:_{_1HJkZtm qC5*IJyAzjE'P(BYhv]kI1G]rezD=WeQVj@Z0(Hl_B^wU.#pͦA۶N 50_y M炷kΝDk0 24lj0'1h9hM+X(]b<£Y鳜]7/mסkZzfDl *55$ȴ`Zd2`8{ْbJ*MӀ<]bp-h?BsJkl4M֝[lyCI@0D~F;o# #={ZaryCBLAKV>˼Ers`yrʋ`у>#Rrhi2RUa80ݛ1tiܜ T??O"\?gj8]P)g34>hy|DQ S u:ص9@4֧cs,47 d%-hpX.quc`Fc,+uzZh1{;L r1x4 :*-ptxdb0yprgƭ *]Pz]P%!DҊTuEA(d?&@b8Ts l8qǤH );T1t1#Y9롓ϊ˂͌wYZ6raa8`y(H8]rݐ,  w|dBSCJqr`0P)kљXXrP[+(iup9{)HYж-ڦI$%1f826e%@ʇUP4Th.Fp֠;jn 5QV8[ f- GCE[gxya!!O}e8Ht mg`ÍC,VKr1O  Zy`XnJc4 ܑևzTB~{8y/ArJW߃tu{S%aCXӡi67+4{&8㠊YԐJa:`(<B8 !45e DVXW;M;-黡0 πgRZq/ G1'?->,BCX1;ƓGqxY+ާywr% wdA &^2tͦfFSP E5`0D @H5vĉKBQ5Bgkx O&~_l[0jT7=8uxrShS*.JiZC1$qcI"`3Ե `w=yX8=@U 1Nt-%&LcJxW565Y߮EmmQ-F)[5pxOL2t5xRsm\1ϱ1_4]$XKCӶhC LAȄKA& VR)Âk :GH>}@)Y(>Xg昃vί;vA":HYXFRʹ`JR(FQ r)kC}^s)jj8d: O` e(M~5ZSн:dtQffoLJӟ~_ Ou*0(  Zhm0p~xɸ#)yClBp}sRI|Sc!uwAuGPDuhބ}v-͆9t]t1 P Teɮ7wS!\[ߠimK? (t$t~ xD K!$Ǽ$99 BPEnnf|SDlCB%3o#*s@#ڇx?C@| G%B!,U pj\JJ4mi/4t?CE k!m{F  ,L'??77\/GWP(uŝFzc+L&#͡Jd<7iHIYu`S-)qeShh@tuAt({#4uŜkQp-+8nIn%( d*4RJV4XlMP7 MSpXV%;*=hB#u6X Żxڒ;# 6(ui\pxRN"}=s5C_[flY'1 hIFDq=qPGgg~*Eݖ5UC9l֘50`r.wɺ.།!o>Qdc]tSrp eBf*): !V{كkۖyB[T 4gLסLRP"`S:US*3,EAdшhR*fk:9bU,_>ڛO'j+XNMB{FMSZY!ÝD6I. qa7f84-P$O< *|8FSlL)$H#圉 v ps1XoT;[vJAqQM {ʤ$uA-h ~=P.hf:<0[|0:miNY?QDaD?n|Q;!`9yBֵN3#uhM˹y8`8?( \W9sT H-0zڣ*SǢ 0'p8DxXWx3̖(!0.ow˟W,opiVJCxL.{,Lc_()"z{xLJ@X)C/uh,5ðBQhwNpppbvyp1IUJ%8@iPQ&<16 Otp6sDQR2ѧCv2PЙ[,`RPl#ה=Iυ,գS F4+9ZzI(x/u-qm=x0|s0ҭh| Xl.xR{7dn=o1!jHc;ȠP<&7'l#/A"3x}* HY/x`๗pX#cy`Osu Ko3{i#''ghoy=y*$EJ鄇8'Ͱ6u08Rc`2p7j{\̖pm,.rs  Xga< hH .Ddm]^д:cb*B^1peȈ{%J8R@2pT<9ǣPCkIETJXk)2ʮO;Bі>K2#--qc$/ݺMLM H>ۂ-'pR;JY 0΢4IDATZR)T1Fp;QSXއR8:VKE%P{c>.#hZEYVz`n܋8-#:ٱoW mJX`8ѝ"^`:`n!Dzxykp@JGM.c,,!4 Uh,+L/>is ܼj@;֛#8>|\ *=3 mMݠn:] |e&:Exg0OKК,4P2(beÓEWjf/Hc>(ka- qBDjJ ţAP5*cͭ۶7{8Z >ubc*]AΡmiJDžJJKT ZGuL2u 2"*cHK.ۛLt tcZkV 8g0cM_:傍 . %8S7VOƠ%U\EE s9t5 sMɘ%$QU% '89=/J}R`_)'YbuS2QI 24-lg`΄*HpQܕ- #VZ?:"Q"d%1ٜLAY]NR &(9-=JS Qw>"&)@% X^)". MWnʄPԒ O< g c'Rk(Ƀ,+?K1MdP0c^nj :tlĝ ~P;J+B|/|S|O-wo0gN6{{>=5J%)gJc|16j;Bk dB6X۶0%QZx<Ƥen "kz5PYeu kDN/%tFqH!hRL|GkᜣdP@u EaRRS=L+;Qj_r ѥVԧ d M/BV@`^CxGUQ.>3}CB!Ak Mv]hT!*EhRR(xT/!}^ը5cM1P% jAPْsuA575yBΝf˕B_OR<[}sW+wo䓿'k=JM%“ m`n0N\^@eY`/oFYV #v*IM"%κjj]m! v J<x(b;O^p (=UՉaYBIj%r !4&No93i1U-nɞePΣm p,qB<  KYB8k+f{pDS"ߐ3]1n(P#M ,KaU 2 -hPa;8dB)CeCrW5 s?޴ЌR B( 0cc4 +.SjX8t"D!Q<[c^D#Ha(T{:7hay麞d{y 8# CС I+(xKg:1mtӾZ&^+ pq2VԥA(GO0Tۋy drPjE[x-ܾufwZ~^)TU}sW "w}ԫOh:=Σ**L4_v-u/ݚiZ DqzS.cd$ׄ"-iTm t mѶ.kh }/3Ń J C@JhnQhAY`PqI> lkr-sBϹ('% AAxVd< ~DqFQ9wI<2B BUrRQzudUQ/ƣ>hCdL Y|$˪toÃC`4 3+F_% B"kxB+hXapOg: ǛUUYm`ĥ"~Tq<[RӨ2Jita2Ю5e謅j )-mF2$!OJ4b'Z6NZeH~Hg`<}0eϬؖgCknih%ӠW5q@QEvж 8d*873Ja)wp-4@CPynI,g?Ԡg S<)Ӛ^ t VJ(c$-%:u%%Y|Q ‚;RBz.m;j k4uE2Ϟ /YXR^E5 gZIEJUFaM@vqUx\4JָTL |١팅 p3N xjTʽ1'mybM6֕Z8,p8VZit]pxt0IG64,4 U0?v.fsnPa)B) #1!q-Ԁ4 ~9 gX.˺^ROvs?)jכNfszsj2 \Ѷ 6ӽ!7 )ҭP EQJT,1,ʢ.hK(LZvM gKLӴ0ȫ&ƝtU! E):PH*0y.V0缥Buk9 4\MOM1Dύv .VH)VB OДӚXYboUY0&X1EYL`֚8D)f@ 7ށ@YiL&8:<>)bB(ѨB!@Ml;''8z5 BQGye蕮7pE+m{"aIӷXwiŚ궵VS?aŮ{w$NԳ]IR Ơ*Q +TU`h CUz3H8MbJj &,p ,vZ 5 cO648=q BQ(FY(ՊL'5c4& VJ899)h.9 %cCBJcoJ&c"!|H(݃Б`^'']fqZF#^7{M_/oS{B* e1=(Ѵ-%n޳BK4pX1TJ:M*ƣ!{H ꌻXJ9G^Su*hBQUK} 'cJY`ȳf=a7 ;CaɠRCR$݆B450~벤!0D]ט͗_Mֽqm!Co@Y ~{{{ v O!cd ۧ 6hU IddrZuy1<s7^}x}7 hq2 z1fPteoݍuecƝQ5tpE\8wd|bu*Jh0 $>iQT dEyVFԶ($;zhIͭɢFw&mCo7խBeTRi4 y(qGIEF٪yG."Iy7zT'< K~wg@H m]"Ha ۠y,rxl,_xY~8ŷ&;ĥ<[?gCRGD^h4"1j2r 2!F8qA>CxQ&bªHP%@VcPR0A)(kx&2o5F:Cd4Ƶ[(9xoH7NkН&032ZNe9%߾o=>Bx#B[ژ2곴oPwwp5,KD;v~o@lNz׫ §t8ulh|<=iTR8. &;j<=ǖ~d:UU#Ms1VɢK<:}3vF }NZL&eb#%!M*08P98Q{%V0$u@(@* pF妩Q'aL]W(JؗnȔC5&+w:eVS[)uw"nNxm3AƗG\,z)ТBeФiDȀ=Čzq0:7$0F7nQ7-(^e8LF'aA!cppטLX,&M8`GC.Lo4{l8E7`CL|eeƻ;yaIhk\m4Aob9}̖ dYUl><g EYeYok!TMme0 E!uGAx օ}Um)*1]uI'xۢ,JUQ$TuںU;Zf;`N}>f %EzK!țs_g pM*CnsnZ9U(#r*GC/M P0F6ua N1&1'MgLXEQora]o\~$<?~iA|!" 'deR4㞇@H4mPBYh,nk:yQ, u 8ۚ9}ےVvt*\-!g[o߅`[z&73s/ѝ$ben }qjnLp>[AE(2vwq$'t.=H顪ĽWAdȈA ZGIÔ#ݻ B@0D/ ;q2 !Q%z)+dE4KTUj:+jL2qU7,]n)9@:KILږZze]cȲ iK)obuGUM۩ꔑZ)7&ɖuwjĺ $ѕ(^JFf{ʮ,x5a}}͔J%Q.f̼V0yC c[4꒬!(FGJ01qb4h9^ϥwo^y5&q&6&+Eֶؓn0׋oS0hH9GGI0 0F$ahN90t"P9Z EYbLLdW"/Q5%3k A5m/a'톖k#ԩ69uDx:-)',z`d=EAV6v'wݬu7, q܇Iv-H":r>*{q|-4}=kp&6~OO~w4}h4EH.\؅+Nl7#Tv] yEh Ig]7he]brB)oH«ZmV䜣n&Q̢ͦF&YJBRR zRzM0"}J2_Rd$$+Jc-ifw;0ׅV( Xa !jj|BQHVy]/3]޵\DŽ3zyՕXbp DfA!#QdUQiaX E%2~]!/Tc|=zC5e&`m ^m,s7 _g!2@Ei gƬ{>0bƅV׼&MŽ}忕iG:zƖ5BJ)"E dC^lp51BA\{CFi*J =5snu7~駟?,mcWGm֨ y^! ܼy{c/H9i2ϑ8/jӕWbz&{N t|h[o@\~{\gāi o}'ټI \7 =vOo›6ƙ[D-3o THf2bCű~p>g5pʫ:}<F#%EqnhbdYVjJjiN͹5>o[~cfTs2X9)͸pt XaNmݝv|iγD>N_akmF~a[dSW5Utxdqp?ޟW LmO>dكnsl맰%8=t"P YԌw]Gx;{W&'˓?Ev52D/Z%׹hٱ 4yl鎍g:&]'f %(0!:;p䴁mWfV.eM`թifʹ$Đș cLgSuVx\܅ ޑWn|x}:GK`i[1@b8`6?g}SJ:m=)a&Fn_wl6B͢ W"ݢkj/}"&Q^pt&<酾unS<)o@n9Yc2a|>8n5Udht*+^IvppSNN3_S_^'_So` !8}ʝ=ٸ+ekx"twʳ|wvQi, Ƀ,]po_@A)J4+P - uA-("uoClL) n :L7.?SR2nԟktg&Py]XGϊFUoDA{i+߻h#tKhxG |њf[ <(l`gg!J!#ciiL/x+1Co7߷(*54q#@\6j,sO8O\̓^g;B\ +/_wM<#WV׳uɩq0E' |20{iB $(2IӵgV3i OJc'so]4N9vͭ@o(,K 1SpIru{t&O<1/1ӻEU<Jg 6 SL9;K+h뢮@l䷖۱NⶻlkQkimw,C϶CF*Y''m^9}0^{7]gvs=3|BV8iJB24qh5D\? f1n9tWxocm5;L(7 yc3#;kg|1ILno/WYg0nX-q2`|2V$/}Ș>Zv soԧ'ݯy.>ar ;`z7;ct pIcmەj[emqttqX-oeys7nܸ&řC&~oʈ ,B=y?ڝxk%gS}s|k w[ܸqGwGժ.z.[7x;_O|/˿;Q5nܸ0̷oj(Mآ)03;rOa#0PUMUSh>x( ?׾r##_zq[oA)IcFQhOy;q<|J$śo7PSg]5=u$999IkCQXG?|78a:} UZZTA5Zk" 2Mu]Cޏ'SeڶF|_ug w&F!T(ã%5`AsUY=CӴih4cTu^dde]܏{׃ կ~~,?J 1 L'Y@IDATxU$I2@z REX_W]UZXWĺw׵ZW"$!!R&eҦϹW%7yz9sIdHB SfNB  dI='5aB\ggIII8|:dJm&y]A>x?;S\mΜ*vi55jwNϾ>/+55My3Tv޷*;G__kkYEEM2UmPt۷RlM4ν=jsei6tf |fhx8mdhX[WO_gdZia[GwknnW\+$!\><2Y^.I@rं?Y_dLz}}mi麻s)ii&ZI622\OFRR*!Nsd d?)HDuHkjnI_#V^;+ yWm9ٲ _7ۤrHe[lm}Zu鴒OL{'S HJ/yK6dA=_;-οtrE'N(TSXF̰V_R-ϖ`24Wh bCZvM,A*TkhM F%=j3yBd'R! t-]zwsWGW0*_ DV$I _>9o-]"~aQd^fGiWg1aH8$,$n_KM 0\:ޡg k6nliayyVXZhCr9B #,SAvFY:Ds`9pXyw1 RV:zHxV2&DBr઀GH 31$xs!*_)=3>.dXtϞ=7o޼Gk99V9Kr-7##2 X>r%=@afR rb\ZVj%w{2:ts6oٺx7^ lDBrz,",>ʖ>Vgjkm.QH}ͩ"Fn jG#.a+O6<11Ccs^8O8%0뤈gyt} (Ǵ6G;X9+'){222%H'Q")DH$ӣ/٥kݑ)mmsqv, G鱍׿VX.< Kpsty^UGo$%Z[e1Xlo ++/_t!=smٺ"RpD NW۱MCy]M3T́:`[IRp x΅QtB]jd2*Y$p ³"s8j7vM0ZZ萗u [Zڲ nm9<i ]b{͌vEz8cHaE8aE #*KhcOIa94p"<8sՔ#AuvI]P *OLHmʵ͏mVL4Ma'iMR/شW ?ڒ{p$C>}I°I@p3|l6yDsDk$/8CUL4= 3 Hk+O)Iڍ.i˻/s*FndZ,=hgOcct~2q_'=?JQ Y}~-r"QnnA6TfFJAwo͚qgɜ={Nm)C%vaFGb1OӤODH2d 蔏"Wi`F.fb4M, =Tw6}YŰGY2Q\AWyQ, A}87;yȱܙ5ӧ]t_3Fn࣍"=_!^lU vizƦeMMӣ!DdNGA #"$Q'^q#W_|a$v95R>s?:&]F@@zo^Q58Ȱ|Q!2$( , ~/d/8A@|\CƴZ~[vnZ3|Eq'>C3fZ-;zQd;S/yu|yH#=`LH^=@(u{cȰ},bD"aM# gs9.Z]aZ;{NO9[mfI3aH 'Olǎ\A)xC18i- 2wóh},dp;x=Ls-1%zH>/16SOHQ**"L=0 Ss^EW"^oBɪ"f&W=<$$,.˰"|5aX"gG{Kr^8#6w,"o?bIpGT$.mmi~F5,ᐂ$fuDu@4X3g>Ch>,h`tUI0y .YOfR!a#0 FfCOڪc_-~t,fH##| ;X$|}Xgbb 01/ 2(r@a hcb\6a0Fft|$u>{u6 {."sz$BPnBUNۥr}ʡȼt+I_O~`HDϽ؂s!NCUD٨?pB  a!ʃ\Ey"ϐnDzm 6=DAX$O~p11hr@\TҊp3{hV"Q2HchXoi[|Ǖ@# CqD^lv HwByGY bQS^,7 yPp_YcoO'>R1jZ}w_x6!3Oo膾y_`oߍtц@M}{?ZR/|ˋĤ/| x?FC/¸TW^ P@ b%v#=WDi; QO`2AT njCDɢ~W~Tjpن5:lRED3H2>u֖52+37,B@Z_'$ #maNl%'{BiGZJ!Ę6^cCtokPG)8ʊ̃+؞+GL=QY-EkOҋDCȨG 4}`r(O2pO^~g"z3_{ ~82˾o-\lG-^^B Y`~Ty"8 |b ͛7;tЪR;﫫?8U'Juu)>Kľ\+ sڲH"7MW#|r)SދD4O1qDE--%|pM"![A@K#FHI:KqPJ,bDL~2Gv,>M~4 7̏@1o$x=$mo-^_1':j_f\*6TO0I |(?վح_ 74Ӧ=>ntQ^ T 8GPBُ A*ݲejm1=\YAo/}?gμO?Y Em93,2w~f{ϗ xvR5gY={.]zOm9MMkrrru~|/~ SNrdo`&fr9 sOxxO)#~#B3}liCư5K@Х |GCh1{%("&01Y5j/fH~h>؏Wd8biE#s:\t=x3ySRRf:٧J{! eSW۞CN -Ѝښ_4s ec_ klOҲ bݸqfl:#Gؑ+::9z4C\N[>Dªkj~}P $~w#*vwXkwk:FTpeM-gM*v5NB,] \f{'S{eh{WgV|I{²ϬYWb>?k!R"^2~0sD*"8;>{"(3iC>/j5a3o;b}5&pwæO:?ijJpPXC" '2Tx @=e$_ ȰqombNUK9]hƬnկ|woM,)@D@X5FDuL5X[24[߻쮮;w\Y4]4*Q GR~U 0ɉG?׼W͛7kJ-p;0?eg:XRRDmmY=: ' 6 sPX3>"<2,@5iokKGAUfˋ /|+vwW\¬>ɨE_ /siՎH\RE}|GHz%H`B)g`[Gl=O^et9KMW)Q~h!Zmz38NՌOm^ QeO>kۀ3 ^fe:-覛ozgܵdv#"cS/ A^z$*I .S-ue?C$BZV;Ju̳&}aUa$#k, ~3ap1sW3f j=Ku:9/h'L F2:]Dt 0ANF)-..v9,sfv+ L#!nf"?$W"^xh?3n I] yd3 f;_&SbB_'pO;H,RCճ%88/5nm553T^Rw^:Ahyr*~J cŘTهt_UcMm:^Jjy(C`jaT$>3\~{`gGPM;Of;@3! ? bь'm-mSlt֖SW'bo_ԘB!g%%󲸀xDLpᠪZSuo5UVUY3׿m 6OpzkcC£ )eeH4Ǹ.AЙP6U.+NB61 D᳖2\x+l_g7]礼x*}Hf]/Sf^ƉsQG$8QvO:+'R;AEQFt!@fZ>PBQ#~Gs_s5POcq8a1އHCD/vJDU٦yD歶'lɒFF 8tGi3fؽݟo99٣bjg9)d}#~)Ei#h[f,{>}ӖMe[>sZO|#Caj4}uudM\a.ur#Gb3g̲]-.*.-c,=UDPs$b^MҾ4DqDJFI 9l\z6ml+W}HLRu,=q<{?&0# ;'],'Ć>t\u1/t@Pi4tpVv Œ̓ Ao뮽ΪK'O$`1 1ƆDBЏ@cO(WO}aǬl}T}?rEq9a(x?_&3jk_+ް .<38'd /m|H=e꽻y`]*1Pr i)֡&<9z3F0S# 9 ڒ`Ҏva!RBCABj` o砵 ($YrOÃ>N gKc}#ncXf=_[Ȉ\:M* շ@*ˉNHIOއy[c 8ʈ @olw ȱٟf2a`adCː`DubD57v[BO݌m8ozt A'C_ƒ ?~ nkoè$2rƌ Au:-8>߼r{KK6h$I 2Hab!;E^wsl??_FI˕Uo_9Ņ9 HzI!b㙺3 , t3*?T2byڛ=rtzA0̽<=ziy4(f5BDqAVXWY ba_%ľ[>.~+R% >Qz|B:qg%_̫ǟixbe'G{fOh?҄!O,_%:qxx{HO` 0k9t} -aUj*[xR'w^KзJ2tW}Q`^DxSltP7!QSZЇ_b1!W1'-訂|~u0tLwoqֵ+uDsfz/ꢃʄJPs "~ ?BDxWpEa0C=J,AQ$8H]S3C|׾mn2zOz{nq񽗣-]m̔a0SD$VnB^ΌCFe/:mIdBd'iͷ%K~[[t6Tg@JFfD<w>'ǻ865 O@PQq 8yG  ̊}uw%v[f$ .>" C= dӏ!1}R XNKb}1 |YvϽWQ HKp ŲOַ6y%_!S. x:9YH/A ab n P`"CL<aŃT^Nʒ]8>×XCP[yTG=0D,&ل Z,RvsmNnj?>&`ӵQf1꣞@0Āq Wmn.mئ>C˜.")G(4^-ϱXvrcqx(!.-xNx 3 a Z?_ ]*jn)x;B)H}T=HJ㝗T7wwm%q/O^I"O*.1oD yBM ?" 3 Ý_6a&F cP#SFDvrxSLfÀ`hkM,b׷tJu:yKYn!_¸񩥥d-%[vC}bS+7/}'GRΫzh܃Cݔ|Zyǰ?|&۴Q1*V:`03My]:uvo|͵MrR2:Ȑv 7SI1vX.+++͵}B >҃8|rfڜ׼vN9L*..έ@X^@hqK0 U@ _SXdC1"եou<7!on?َ;6=͛:!pɸ9QjAEY7 DN6y5.[%JU`4$)@_ϡ  LfW {xч]1c }#-DI Q#z!=?;8):Eܓ+?ʈĶl sKF, V tC>|t޾ÿrr{b?ϭ/Yf'7āj7v\AE:)V^aofozoD){jLbz.ŁkwO HHmS[OE0~D`/Z2cqLA`̑1ŨŁr`YYĞ@ (⳧B6 $. TI8I3NbzH5GrKdЅR{P/Ժh`շbu:ذer|orD؉? g}FZ(KdSxn{Da,`|,Uo "I,#'2utk{=pϏ;^ci*]z}P!r-RZHmgvڲ˅O#lJtڿֶwm{?~{uﵷ_,KZJ #Gg9B1 | ˴.iV:qzKBHL}5Nb6##&%Kۗ}s?J>. 0u5cED̹Ξ5˝)7$@[ ޮ<Ï=av1 l<:#=W ֪ȹxɘ=v}#O{x&/_zM2M㤙RA '9C} L" @-,*͛6gg~bP,v$A2;v4O;՗aN1sS_ w&Nсp9\gH#;Uy &@@JoZ֬䌊vt ?k T#U[hZCMϱ=hA^|iPRgRCQ̺ ̌s͙c~{lٲ宆1d1f;#ޛ.Riǐ5688PN`܇ Hj#!p8Dj4{7 Ά.|22Dt0S&>mZbZަ͚#6# iDzrrm56{\{o{6+ϲn$+ zʊS푇O'4a8d 8S!0=*z]!$cxcDAm%OlKPO#n:uw g P*@D +l tuxy~Wh>r ~3;r L:RT:Ȟ+ǝEO6wwPcx8{L8*`P#]p@A"ѹ@8C#cc"@>|'IZfcfuх|% `@C6{'hm" &/pܸ1[r͘Yc&@3cF^qyOڝwήj"AƼ\r^`.nkB==yb?c!| G%~rv'OjOԝ0S|Kb",͸5Q0DWǎеgmv@̔1l*{=2;ޑ<}cƍ;}}c5b]H1S&Kf|/aMX?L:w. '?355Վd ĄQn>Vo>W 6֙`3Z@&(>R ZP0@kBG*_he0h/h* CjI:as̔Q2U_O4GW@x_&&@vT@!;sv`dHŌ1aC`Zo%ScF8V1,/eg! lƀt"4kYuql)0fTX.W VR^kVi 'xmSZHTNq,C&=3lREqZ'knF[|* S}ƛmGl¯`L@b;a >0`a ѿtMXX8g؇o;8C:FPl2 !z/| j!V>4B ]$)G $Œ'h R*+0)Q@HŌ RP$1 'wi%N7wwoo:GH-D($dx׻i .}B0lH$~ZMy|h/h?5gx2&ЙuZZnG+@z wT&祐`$eV\O9ci,ilH1w@q.;<`G}X?m={Yf*Ɓ(U| [R39c;bmT,M8{Acˇ-׫[tŽ?PgvZ`l 5H<4V( t#~Ȩ < @+ ig:҆"| uBva1H%ZW_|Df=MJKK؆~H{:m:Xb"y%=00kٗԜqyG?)~8T{+f B2!&xd^m4kVT_~ ~ B R;YJ}\03&}T=DC*+? {0wDNy̔k]]"N x+ڧeo1I|Àe!- AX*!A<'VHOCчJuCAAM9&l Nyh_aMRc_ꭶzb*^1xHj`É8!?"lZ0i_ 61\80;:[k|cԺgRc3$UP?X/q@TG*f ߲E@_"]?M !=3\?Y"Ğ4( "#8'v"/_sq#i&|DȆcMbB%f"Mm}ݫ_+s]Td28_1+xZGF_uՕ$'B{XȊm*>+.!P6mڨ{TH= r0 DЉ#1[G$1r&p <P75>$Aڐu ybxPgp$uݾ2{\a?8Jx/]S@c:{&L3ƖXeUS}n-RCQHSR)cHԫ[qIB3ER:%5΢3ߴz'~0/ ܓ>兲=ffΜ)&xk?OZI7%x/ CD֒-zbֳ8'.4S/jf,)-uN?{vT@RxӋ:Slo 厎na%l]bH'MX8O `gرi1ZfV$ +>خ%B*{fn# ݺg  Tlgىlbx}|8HTf͙gKaDb*2 ;S?m{ygI6oX O`A;CY)_:i#Ϗ7V9b?],vgqPR˞8);ZaߐlV\Vb*H :3m0`筷~nF}i? g`#Lx326c~; \9Kwy]nݙ_$ IJ_?Sz{SSˍÃ#eZW!EܳB/8Y+8mhvo*KJL rt"~ A:V@ e}\XRlmc9قݭZ>j<$=@ֈg9ٟe^ ["4?2TU`Trgܳ!DKP?<_loިv4jQ'"az:Mj. }%}|v|x>xfPѦMPUU3&z1 Bـ, bB.\(ć쮻j'Q W㈏8$-L5~ R` vk 4I?y'\__`0l.. IY.QI%^l[fSLؠ)G,5L7"ݡ2[cFݽY6YFR܀QƆ&M ;6w)T۶=_%>f $!5p> q< 51!yX^djo|c_޻n/,,NV=8HII1ڷW\rs-#}}К4С Nq\gfȰM9O/%mS'_雴,){rDՎ8U hy)CT@"ZNŚ5-gylbZ4K1mѢEvX6v7`hBVa\Sh䥗D/M>t8dɩL隵~-WHt8Z C|c ݄< [|;"K}' f\@lDre/ߕr"uyn<R}Ƹd3Y1q?y?+ t{ +ď7$F ѣĊ{ʔrEjg٫_{rӶz+-ti/02z:Ktp\D$=%'/.$-o 6)>P6B?D.xaAX×PW)`jdR?tx5^?I0~/\$o| 'h"uC ak0;aV-K5Tڝ-o< Uf@ ,XvbvӋrO !K" ^dgb YȒ[pLNJ?GԖa!,}T_ۊwi}b M- \ 4 8!a|b\|ϪĮ]heN]nYRI`A!HFdC-˓a]wݥ+=-@И/?#>Ϥ`fzʢm6vɾ9j}E~Q .xe5kny< jfWxC>]`EV]=SR$dV/!1=oLbOHŴ\IÕtL0ಲ ŝ:AC_;n+))<ة$/^9A4 h!vb| $ RB0 (t[dI9?׮Y+}| K*%~M藛t먥>-5+S7NJ7kg |KQ;ꔎ48<m%d;Z:/\&Jc{CJ_qbCX[d`/$97_`@͌j94Zwo- -Wu,F$Fg̰ NOm=R-Nsu?B1mi=5Jɛ}hSۄ/}ZyڪV#b?яԇL<<,Qd#[85hMǸ '*o`I<Cd b0 ? UB^`Fzޓ>,꘡~}׽_~{$6%$ )  @ЧX:w:^l"D^J!/ &wsaWwhvH`ߵK3YY9Yer;-) }٢Dy@#b|xk.wuRdX<t(/&Fr2CX"RtIAˠ (3+!a .NuJeW7[6ZV; h[5+?0!#-x{a8.0w-{ɽe+++Yل {5ŧ좋.QW*&c})r\?,x=!}.mHGkzFxaO8&heƍx:WˁgNЗs'[H-h]vt{tnDWW/"wX܇gG1G1)'t^0Ayj~ʵWb_&YшV5-\y!"붠wG * O(#WVh'Ҏ6+՞'"!b{jƒ&RͺE#h@$]mQ*|egpXd蝾a%`A6ERfSp B/,Gݐ:#ۆٓ!BlDv{+P!W}tYWA>>OU5eg8LU }5K5!<-rղlaYsga# wƙ6 $?0N:oB0E',~Kg=C0K&1Bw;uD};٤GAcrcU`IAؐtc?1wu61qyg-{騺@UBT{: L$-1B0^POv A`r@ aϙk=ZJIĎŷ_3~DGR2vEۧn}K_\{^e N?8 Z.I31m3Vl6hiS|MԤV]YU#n-dx()H;1x׈%+G޸[ #Q?0 xo\מ1m[J|6& f #Ui[tdfmuo c{@^|GH&T _$=병 `l:E$;G#Ofe޵X*{e]v%0Z}eƏ K*%^Q@qɇzMbߩo5Q P%|[a}.wO%V=02Fy^:Gm$+Phi52$評bΥܾK(}e2袺tq Suv Mށ}H*Bપr}.9$zbG]O TP8L AFH =hpfsΛ7]bc]260ۄb\)'[yO?\o*KZy6L(_Fx2n10!xjk#.R8_% LT9M[Z{G@ODn/?Ze_a/|%bpp82=a5uHC MD$ZDd cNiE)ΐg536cu_Ct \kŋzҥemb eĵgw:~価4!uAt'WM#n R Љ&O/@Pň) #W.+v+˶ 9Ē 31f#ہ}.}`eaIN0̀ ]_) 3ǤIնj:Zq(M0N|m۶io" ~:Dv 2FdmwDQ-|ì"g#;\[DV__aԑ{ՉGŃb OQ&35=$ć$>1SJ雒Hŋ( Sd0cU$6`%Op-Ƒ =LPφAe߁(p ƒPeHJg*v/ӜuM|n?![ήJAfc̬XQ+*|w T@fD@8ic&`i3FPӣGOz2_́tZffL϶X @i)YrYO'`fn'˦Mi~4?Q6qD-`c_:4Z,Cf/M\2 rTgkKɹi`'oFk:>ff) 8ve'0UZX) y  Y$ yFr#<$yc G# ~|3"pO9sW&vpي|Gb}۷WVVJcqwLl 43jv)v>& ?ܞ\9ͷ߸8ުgy`+p@5  0K4cpg|YX9s&Ͽ֯HMZ p("? 7c6hEeœD*!NqgKX!.|,7&Mt" wH$X'vS٪+lyVT cOg`8WL& *f"P]zѮuyP˗fTn(_ :bGktN-~ɧ࠯UO%"'~? { ^V%AqE؃ Ϝv_ w%r 21#Dԗg/?`ً{ۼQٺ:v0(0ΩFkަge}=Ieq0ޑ00aDB($QS>y>%(p}|7s6o~[ޢf(A,d ~H]|/67ac1u8 O?{}!)ERGyl9ꩵkB8~# 7/e*l,P> lBG @uzz`b3'N\xƀ ӥc THh4e"P}$[}iܚhϴK_u \GsbrL5f[nZl0ٯ;HfY Ս8dd۔RYS[?beZ.,E KR$\K[&ev ɑb^]!LC8*S@aBq 0046,h$#G.2=6y[Dy^5I|fԕnm!lIsOb{>lJm3V=Əxvl 7&"X5ku6wإy}Ol[oU*1V7!.U'#i06g%|01b#Ґ|LJ+aw>˒y$0 8吏++3HAf2ȧ! 86{`|]gӆ~Iq* b̘+!$Ƒz'Mo}=z9նm3%%`:}TɤM/>!F A 8ΗoNэZ}#kI1L4?="^6`a,d`'t\?^A8FUTg, .g 3|8rQf9Mv@zU$[)d#wp뷿m_"|箻 e@z =`şwqv@1Ԉ˖P9F<.6CEPo@;2 rKϦEnP[jո)2Ac93p׿W^/K]*GYUǴaHc7Re8A9-1 ;t{$)/+@w]#z|DŅ1>nOوB31>_ ~h>ݏpD:AjQ 'ee^_qݒuW\# dWq pԮ[CrۧYV5$ttu|}DpB!UUrḿO\RWG@?^ViA{|HK:Ζ#p&ҭC6ʀ2-7NI& %BH:"i/_O|C?cwi6y|V!g֥Yn gx#C?v> 1lϔ.^fX1?`B̪bgH]8 er鞥%wp̞e_*{ۯJ 8 ft '+>b"b*9R)f?~.Q]i۷nw_{s߃:aKR( f~y>K#0'xF؏ra5gE@=!=X*]3oÆfXB> E+Ryy9CEeX![]S緻!VZj7(6ŲG2igR775DS)!pIFD=BJj2AKgls/[6o<+5pE4gqJ?x1d(c$6/v_u.o[^ƕwǵq0~vi!$.jhjï{[\;_x|[c{K( ,sb~sal`lgmӧ>(q_?5=)LGRjȶ5^k ^064/ϗYMl !l9Χu9nzybGDnݶ5Q7n8yzg&=O-| }d .kzN:wF۟-"Ys"Kzo%[Y`p>ykxһo^۪Eq짧3 };'wODzeM+ӫjZڇL6l2\iA3_VVBUy'N?vrl3MJmꝴΤ|(G?w}0^Bk5xzφ1qXrXԓ^\5k֭]ۣ>'MT̚43arv=Ǣ2f~z!  :F8kM /c{ǣ`9Yũy6b\z˘/B+/-3:'Nd/yc ft-XmpN2ͽv_HFv]Nദ33pPuQ_xʳ:6 GԳ_?Ww;vg=׵[~[_|OH:0fxɵO?)HXpfi fr1ޭz:a̔#^2T+M4y=Qᨪ UvHon9k]ًJ|%0H/| ٔK_}9Y;:?G;wm>V ~WLaճ\8k[ @0}Bvn>6Y;bYdUo1<#ë*`CvS4Pˏl{k1<;*_KGBH /o@ko]y>u U_g G: &vSe?ϳ+Oq\'gg7}îpyƖߧz]ZвbUm  6#Ҹr>XPIlJkywU!SzYW}&Ұ g\#v?5~gM9=b5ߓ饟w~e'<}_RpC׫c%Kį^;wz8WyQAb+K`d-ٿP*Ʒly7G4l>@x{j3uxr?o2w>LN{B/gP2G"fj7O0?_t /hV!×DXU0v$.>ձiMrlB8Z,u~Ӻϳp'yӛ}>lėVF鍣/7>.O\Ju^)r8*7:B?A$ƮJFcݏt?uf,N-4dA(esvMa fH}ovC6Oh\?>{;- y3eaf JqgV@wUB p >Gŗ͠4QyM~ *fN?G~q$u׆U5 C9Y9`6tᒞ,vڟ]K0=0d<i9a*sqUTC޹#@X` ( G 4$K.ƀD,oPsUb)/xBl(^7z.BOi1WBxkxZYuZϾNߵMٞ^/Sٮ᫗?LOӉ"m_|T}|}C.+tY`o}3vۭgb8>B8l4N=؏>*V<Ο0 =Yŷ) Ι~Kr{;ȐƁ&?sdӍ8lo Gr ]FVgq߹n]`{5yWnyڲ-hI(#YS;eV1>^; 2]N DCs&?p;jhŶzy|ϟ3~mzj^ٞ7F ;K%;0|=3| ]Ʒgy{,g|Ь~vO߻vN0ypQթ[ϗ6y>BuGcyN?z_9%I!/T{|3owgб*'.z=4H&TSfUZPqs0h\π/}7PYNAy:7W9 vcs_G9yGLe0vZR-rn9΂2:coN?3z{_)NgEoO}bzUz a9a)#R5`w [][ lwuW .A"A!&<q[(gƹxB2貺NS g>fq y#=ph%N'NK{swu踮}q??lBqz 8h_P omX_G3!g0&[ӽ7 6U{ 'H PG"6W?ƕ2߹0۷?ଛ^X# T罐DŽѐv튽7Ms٤]H/twrg>^|$o}7ov<^co>ONߟ6xt<]L}K_~靿4}#u`-$>QSAV1dugKycFI7J:@}!Ē]y83[os̆ڂ 1wN͚Upe4^8óգu}YP_h,Zk qܼ:K;8y/;㫻iZf|唯AǀiSRV F"oVh\ʿM([x2ub~=%x2HۓU2\%L%xfձaCppw(\tW55$Xe+6pltQȭ#~&{{҆n>M+=[9Ɔ%2.G"7o\*Ɖx.)MR3:'~B`Kp'>qOum:xGCcBwG 8ي4p`&e=3gW]q<\/ex=;u}MҘzf z?5\]|YZϮ]}kX;.U]~^MDߔ4(ۊ) Ss ]2H9 C;ף,;rDtc6F3BxVOx~" $OqԸ@D'†YB$ɝ/=CәI9X8 4,o'y)ׯt,Gc^R/+ɵ+WJ?GO߄PONI:%27w-束b0Ͻz?Ǭn+Ϥz쉧l~%eeay *@IDAT2&e@^# M,8Mjˤ_bܔGo:u% ˛2g,ڙ6Kىk٭ӧh\3m  *"CqAnV9;@ÜWIϊˠ1ZǗy8ZYW|l:k6!"lc=>w2 nZ0dTF$${]Fp)4Kn$ !ĸ2/pm&S2՛S.Կq>((.i&o8ׯTsT*O5}!*}B\5rrBꙓx#5! Rknhq}Mŗ7gkC&^.ZVuyh{_{?IYvD1ق˂@m }e8?.gJ*eLŁחAN!\v͠ʲjXl&!_L Ɨ:|ԋlh#hPկʼۦW}+ e :~baIc .<, h- !^iNa)\[bE8NWZy W]wu]Ǻ*<_DLHrs) rh5faPHCd5j* ^fVb(A}󙿺Wn;z2_V5vz1VTzʫ/g?:秦S9?;" n xR1? aOؘq݇mӟz&=D?VW"\5"lizsxrG[GD^Tv,._qq )TPܚRðk×0cZ<-o~K-jxN \wz}Գ{*]a'^/ pM#CS&fq|#?0C ;^\y'oil8whxMKWm.:vW^S_Ɏ vX[/Oæ` uD?t5(|ZW϶$?΍F@ /h*u7l\t*pKn9$5v6X<wk3xƠ7|nɱz39z?8v&8:~"Em,A!!:v^~|1db̭"dx4nk'_EY1*ϦG}4$ (R{?aƥs0iN@dc00 go~0c4s:tf1 YX#{,cf<Am-G<}x_ ͠Q>.|zͷܒm2UTjY4t gy57-]d,eG !űzԪE x !ݼWFʢt6]lq_Yt*3ɏǠ沨f1mAP'u|(Ly 2MCݷr V97{n3nw:}>ߝ%$sxNW16`^m$oxa:-gly&B;B <-5=aÙ3/gcu 9Y *c2b02>Sr͵cfu"C={6n\xDXQǝw܃ߟ7u|¼m{ǟ6#y'|SzhI;ok#tao½+MFK^jГW k[V'ۢ7$ +.<>$hޒiҫkeEԾO_}(ޗ‡u2o1|%9#qo+c͚+r":TZL Z xG% ml.F>m2iiJ\>%ȶZ0B`QPQixՂشkuЄmyamFr)J31vHg xgG?aGzgxv&}xA:t=kf!s ]J0]X'sB'wڹSW'IԙTpx1'>`Yu^%u1dOL;F*-94bՔ+3*zQY}eSI`˙r,}yi0pu\Pb)=y>N4 C 65%wQ 45^MOLA "ozݙ?`bsXYh,'`߾}Xl7pc*_^LOp2Z\AҞ OIoC ~GT/{jjAQh"+7!}w?e MU>OZqK!AWO-ĕ;L.^cVTNð- '޽cS;mn*fK#.aA3b;Hu6g''@cGGT?Z x ^1{g!JM4XɽF:+t}Y}KnȑؿNj>MXEF?3*`6ĊͷTSN! Nl܌tլNDy]wG x(?QQD5A{h7#}& #m*w'l:O3'BϪV7% 7WG}ЫbVmas:5y!unFl F0?wlg㙢Uqv{K= TSKemn<̮גZgi ڼw}vwݥA%BVDzCZJ6 ᓙqeYp$nltUz57޲+ӭԍ$ 3P0X!07c_u:0ok5;*_q={ {Z *]8n|z#mCoo}H9 {:9ƐpvjEVaE~𾂀pXu1^tJv^}sCۏvp+:reXT'vD]wqlVs G4˖JsM.;?ċo rv Ҩkw޷T=)Stm0ߚN,c'FL6 |V(]Lw_ b@N32Ckɂw!ȺǖN{?Ϯ-t vDZZWU)ԆN "@շu*-P''uǽCqmnh"%AqA!#ɖTA"-XO' cȥȍM^\aGp!FGnquڽ+B!'<H~<'n a}0_٪۴inPHDzN25vk~yB98f}˜0OT$FK~!iU! 1‹#&=v2Z"`ڹ=u"*sj84 Sgv~>+^fo? xd Ag,Qim Rp&_@!/[Eң[ gM$zqU)A@r<=zl4!XGn1.\^:hs۶JvmE5SY\!T[wW1;^1`w?{'_Zm ia$^Z8nѳ*}a4ڶzӱwhQOH* -)"(Kz6gx~ʮ|c"fn&ذ| ,F<nǓ`:+@?~B9pw!=kX,/6Tf EUzl!6j*_}R<10 Ö=.Y$̧* @ I|3<\iCV0NgGkN-s$=&xXƐ40`^rZΦx(x˘1b|p1Ri,DGp"DH 0Խa7^00'!hw"@8u1YF.jV Z्1ΘLAH'_ZtcJ\LL/D;;\N .+̊6>ƼAop'=M?eЛH4k΄SïhYT:_ZG4>#]_}ٓ(xN{*3-Sa婋L ّGwoآz 53wvgM<" Y=c|2jSς,c9"kiOeq}_ONWƷ;R4lI@K>q>v񼋱-\^ǔ-_Hl<$NNyYOޅ| vЗp񲜁p(CpÍwd̎ŷam޼9Cq]޽+Z %$"hMU@lWn/N7tK+&i?"0Z$Qc. ҋwOŲZ޽f^0M~)0{e3XS 1?C8L-yY 9'4ߡ_<EZuAXcp0GZQʯ0* o<,~;jÂUkV={~¼OeLhq<6[|U:5m?企 keGr,<|}/r\F9Vq-sbK>¹gǝ6xk׼OLd>Pp:BaJ6 0\ݾpg\50XiC1Ӄe¨hSY֯]:W/0@5WKtOBT>sb@ckZCL ͓۟lt]:ۀf{z?VyzG1e寘~WrrfMn3ň0u*kٕG4k wANlik.$C*3 ,ţq-f>ƴ'hzJH9{7*&7Az5>C׼S0W[$ I*)u Z<(r]ksO<7?7G#W^u3XaNMU)|{!dkcَ a/,ꗆ<2N~1 {yyT;߄30-J^+({V{ĉh.lvu(K{xdVm=mvc-|Z^s4=* nCHk6o~osÌ ٟϣV;q}0@ a0hŀue2g9=YMĈ^S7&.A!OpOfe9B`EA npWq V;Z{!oև"Yg^՚A%WCc-n=uSFԬ4A7d"mС7P o_孜 u~f8Ԕ) v5}33E4Yd0LM(sLѸ"׫ٜilHYh+ ;8#p)ѯ˵胿ZƬ9rzJ?]꾜U WUJ"A_ e}.ch`:u eє|u"A -1UF&RSU#ٽ5dͻ$6cnHC0e0}X?L3v 8/Pf>H#Bzw[KVe+pnY8LJXsŠu=쳏gÐoZjd#!#*tx Įbկ?G/-][㱇C㍷(k#ȨOlkHKS@`Cà.>1"S\T~JMcHa;٤4>ux>;Ƹzv=yÇ;8pHJ z+Ǥ[gǎ(A-V;ջ;v4[g⦬JhcAE(\iWS퓞L30jAWҝk~CHcbzv0.n0huO^]VګL-L#25D.6-^iLz&@貸z3zt90sM72(`ɺ~qHwy#p<_<2zq Z+T:剛rhH%PRF#>Xݴ yUAJuO4+BcL4) ؟M.]L#eiakeC4űWt-a5'+3,SǫVf09ulѽկ/),c jzft`__|_In[^ SI! MU8{>&6ۆWlB\gEl K?w`6oٌќ[X, s4H3Uu4g>֭[eӮ;,weˑyq(cP1Fːzk!Pҷ?DÃفҘB4Lmڶmk-6.h>Rma$? Υ3Rt H/γ"|NdvU/r.øv\ npHp-/Ѝ74}sk/fз뷗*u>1/,: 䁞^}XS1pԥ+i͉:az0CLFC /MC+4%g:>!\icȒ2=<<Ϫ?,gJ/P& c!8I0tS02r Vs)yB3 hmӫ^_WzVwσ^xyi\}͚U1v߶tQz4)UTc,w\ n+2vqd֕e[^ƣ.۶nk7ycyG2[c#G҃g+Pð37Deݖ1ӟDv_=ˣݸlz/糏^r'Pv / (}*] ̒g2upf n,w7FX-ZCرvG!M.&uU]`lɩ6ى7Az0Bj~0pU3x&5m|w~g:pρ4ޏ |˓4 hRR ̦GyQk!/ &24ZHtƷH㍫hDG#`:aB `fxУ$:8wl|%wLo|[2,7WM;F Y!iU9 *& M2WA1z BbQK-ۢorO6g~wڹ멨{1UbaQ\18q˶i#d8GLxgplߢQ0ލvG] geÐ=ء2u۳2cv85 )ʙDtѧ5#=v0|{;Q2yD+..pe[`SpZBMEzÛC7pNuU[う,})|3^{MLB N{(1>Y+R7_S{#Dwݑ1,F^&u BCMXmB̀4z@NAԗJ5a yr&*21gV/Qf{ǎ'C <3$6fux_}t]̎W^iSN!=JgO|#jO@=|L/cټys4k[˚3 _;lr'crH8ҮWe/>QfCg\g0Sz$x4֣`dH1#:?ev[ ;@?v=V9k֛_{Mgzg=0tTƫyl)ǣniGcb+3 {Ke gVg4]bp|yǴ(PYZm>KKSxî*k|p/lV]]"Wt&c,*-#l%I GҶ'k|d[p,r@ MM7a;n5 pA_dc`{+x'vQĊ8'#WR/F X/pj@Lo"@NXX\m'# 9ȼw#Fq:~ÕA܉ӟi7CSrcVe sWԍB~PIfc3KwrSfO.'੏?v׸I=T`l6ZY#$[Xg(z@hY,\*|!AOx3Wmf 0 sPy.fO)pZM^S\"d$jչʈ*nk:Gcܪ.Lb:u]/ocq!Z)Gӳ4yͱIz.^ҌïȐ9zf;8(7<2<ݚ0lQlNhh 35DΗjx7NȬߐ hȇ#Fo@ϲC6(/Z ܅_&=ֿ܂"3aXs--xDB7k OE,ݩ䲒rӵls3OWn ._е>Յ quњ4B诘t45Ar9B2gcjnsC Sve/Ɨ2RrݺEpz40 q{zEfDoG``D0 &ԡ}4Yr4jgD<*_teǯ}kRf%|;z."Zm= 0jj%f|y&M8hH cxj}#k;z\'3 /DNJ~au3'aBYdi]밍wa ϕ9pb6Nk4J*BukexchU߾\ qTf.:>\{wTCz 2VlAZdoLD:*26EE:NAOONb%^5׌xuS_&WeN /N|4<{dbK3v|m܊n47.Wx#!2B-AУBn6W=a }'OZ89PS^i}Myw50!rQVG}Œ4˲ gS%QSL1a_uy d6n m+z@ʔ̳eYW qW>pzO=kv!j4"? / >C[x& hzϩAΔ➨fA58ݔFpzIarf/&<W:?ys7$) a _ z6= '@08ˇDМFC. -b CG[Lvt}M/0'xEĵ!<4ȩ؂6eaFǻ%Hz#'?<\8cX^yvj?| 3-N'2t:? ?A6kYǻ̼%ns!qmDB,!79JPO=#!) !D0a_A3%"r 7bvψBJO<1iC7޴-zӰaZCܭܪxf k3 =Mb֭eWp‘_u g-plN#it[K clƸqb0:ELKGY歧.%Lk4 |9Qs=aDpC|kLjD`/"B+&ՙ"ZpÍ٪lo{уP͍8 W =2oCAYHj=߫ 6Qh.mN?)!y>cX iUi8\m >?d3Q7傣5i/-üG͏@Ngj|#8VLjh~,wLfu蟶u|;`f[48nvi4ׄ1W_vU$c',\YCRs#|ft38d\Dy7>Hk_E  o9 `LV#duɘx=]v?u䩬?%h1YYhgnݺ <ܳ@LȼP|GX2 ?Tb"Ό/3c•5_#rL:mC1~'oI9 F5='Ar #.]F?l/h]*qD:U;_^&޸ f0utٍxc}-5( Tc,4߼F1=k>Qrkn.Z\:!#P?XcV^G?0ݨS ^W?pw}/RK%z@ZqzQ~;(M=8mc«ɧMR/„1!m/ #UWn ά_Z4 lFξ ~hOj)Abf":UdfW>4x3aӦ6mvs~G~ꓥEBh+KvK~"F@±, w /VAQ5v mOg?;À\a=\8 }qayoC0}QL,/Ao}kIcY"k3Qy=`_p Xy vÕh B{d`{oޱ8FcZxVgA<b OEkClDT, CUVL G}K[.qZx\9#J 7T: k4p=ϣTG`Ss<% # hK18bh$=xO/M}9vˀMP-V~a@^k_hKV͔aIMqao@:vpLN=e'r ;hć S6]uv'K.q ؗsG/v= G;ϗ_nj1T\fv th@Ks\TD>yZ&a2 yqT Uc4i:x8s- L@[f_5_Y<=1Mb\VxHň~T*8ŐcA\f|: VA\I0\y&n>{r#jƐ\0*x҃;rz/Ę#ľP@+ս֖Nb}~4z}9D,]2-xYze{x: j8znYA!_C7|5<63'*Vǽٸ&0¨{.|ӝ\imH; NveܟѰ%ͺ!0+"~ E;z:tCYS a?,H9' U"Ў|*^${-󄒞jLzeD_ ?B@LQCݻKVEX>ˈO/d^B׮ߔ<'G#\ Q ;AE15 iq\AlwB?N*} 5~o =zxbgc y5b+҂ 1gc`jz"f MΘܽp)p3vy×YL&PSt,Yژ\X.xq1nc8඘8rgL.R;}"ZxgQہ/C_Wei'-> A;LY2fۂ'u5M5m"x%/LŎ=Ӂh 6G]:*Dvj d{7mmqh܅ߟ)ߴgf|HoUK4]mq0Q#skr|bŘB ;QdP" 2$h%ea<Md1xzdP[|*Q; XZWقյ/Ocf\iLa*>l[GgKAmu`ͳ{':twOM 2RoS|"ޡ#~ƽ=2YS<}itڝ-U|> xܾ~0֋E>uDŽ %Kp}\N"#^=)Hkr'o ۀzCHMlY5fN_|l񥲨zJc0-Q pP[j(6 N{ U:7u7{߁I]\oGg2{. F9"QkYE{`pj8ܮ_$}助 P0sy=u{`L"?<;\x9mfIVOf3"xr㡜 U?mYRO>!Tzd2&)!`zC ٬JO^s vâN@A{pHK igجʙ?ق~SUyvϾd,kEWW4ʜO"ZFMGlXt>fYVg۪O hK<۷nÊ)p>oLIfcAWeQ~0wu܊4{1a@ 7;#pRxEʓ/ MiFrퟺKW׹fܺT`hJ á!N1b{691n?FpgmWt h2^K.A,=ՍfiBX4̀!CK[OYQ.",z0Wf;nK%x"ch-R];^8?f`Oy׭Nd*-&#f ]KܿXfchq Bh5Ά(4tdu[g5rjŻ3;%HqETOJQߌWq5 qEMQ?M"\RٚNqYB9Fl 9բHFO~ W4H@w;wMLiKW i B6fVz&Y '%ZPv#!o6<,ʍsУA0YYMe(LA L\6e;ѡT$:OC~{=:X-aD@jq.Ck(02 AZPUY+4cy†= ?0kIwꁹ@ 5i]Q בנ]([ǡmG5A=ߟe|S6uth%rA궼YNRg/NftcrY Ҧb-}[_hK1u-jD ?"| b<Rޥh56=NkDVaEc\>Ce,6 ACd,EiD7q聩gZ؆-BZ IS%L".p4YksU@y/})R׊XA cnI3áo|/obԣ@?L}]ÈQaj/X8B1LU/-Mvh,#ֶmjf pKB jFJiX>|ٓ!8\ѧ5 e:G_.& Sz`]4CIO4m T`=>ǩձ,v„3>#1Y6/4U ңGDpu?Z-XZSGC"K^xOl~D='#7 |1 0f;vB$=D%jNa^ݼe;v쨆@]5r10.ӄT #kjP_;# +(80]/M_̝Gr 9V'ؤO: xWeژgt;ߪ s=/ *n`Wu__9U)jKv0aꦼA 5Qgq.0^> U16/4ݚ\ G[eN\ǻt:ɇ+Jr  .Kb&Ӂ&jZ[D`w>.%[,+_N1̲K0E`p:=ٯڟmy{k/oޙy]A  6SM:v6ft:ɏLg'#i3M,mfYjNDvĩk[vxE۱%9Hq>Ϲ^7༗nw=s=sKͅ؍m3tD`E[1/FfH4Įi8phf4btRvnUR(g OZyرa{paJEza2)y@$zų̶ƴWgџ/ZAi& R"7Tc\WGHW-خ/7PyoX #{Q #y-p.Iۧnz)曮m㨸8Ķ  )=F}d< "xs׷3ۍl dz8MեAaQAPUԬkm e{H /Kmj;)ov Lě8~=G˻cJ+R}I s뤡Sav '"3سc O}Ν=Tlщեxz?Gq۹Q,aQ Sb>Յ5Hb$ l·)C; @ Wm`] Hn>'{aEPOkZ0e)8g<k]`E?byh [iWGS;9x&a2C[YFb6_Xx5V -K4i̲HXC9dⰑn|ԝN&9bH+*8;ۿH*P1mZG6F;0V":MO:jLKV^D'B2HvH+R |Αw#l0` .#-+E#7tH3iOXqoc~O~EDG,u):R:ߐk2}ֽّ*ƺ"x;zScīoa>ै K }R3S2.i?r M/rTQ.kbыx3hȢTЅs}ؐs}m8Îϵmjtv^CP$1C?D_Yd$SJ$.#ߘ`*'dZi6lXNDC﹑k ֍l u*q N:~o{˗v׽ޱsĊ&ʆͭei64/˄ƿYM (\W0TSϺXkÿx/MAw޿:Ry:v] `tN/,tT[ + UF"/# dҫ%e ^³ <~p,ǟ8N]$PZr}kuX1} }Z}Q,LT);g)2℥oښX Q׀(TV*>|( 0*>=(Hteg܁ltVGn v %ײoq6x탉%73 ޠ<%*ƪ]©v欛d H e Pt@hy0@.5VaJn2%pDgU=.V4swcۻms_݆>Zua!15{򗠥GXw#|@ ~b^H<4o 2H?Q~K?s1,I*SR2*<%v@X`#p *E,EuCg|$+?"X: f7估cP*i%G_Gi;p.ru}wllGkyU)y.1tX19 Jw]S}~ ]𦭭y4z'+j ->9xsߡIx)oQIa@]晖:s0MfN^̶igfpLo;Gν`:\:1>8y4+)Lb{βrZK>SIDg;@x"h iC9zЯjS-gBl h,1Z<=7RЙФMPG##]WIx=B a!% ,Ol/%pApT,D"[QPH7C>҂7 VsmUɋ<@ ^*)7MFcE>[u&(<1+ū~YCG V9lK XG|{9eT pBPMAqÈ8Gd.qm`ZQb+ 5 ,zNg/s|`} H&<7 `x@Wyr}qDEVg7n _S ?&0/˒SIh"F?/ N8DQv`}-r1dw l<N٦3HTtv42Id(".>_^"E'=.#HebNâr^ACU7B؅ؓLJB?zK?F|ͣ~x+!A{Rʷ=SSoZuOTldG ooJWe} )2W@h~t tp^-TAd g<頻5mgk8r;QyQNG~[gY'~.` {a֭#Lkӡ^dok bHE X^78$aΉ{"m3(tH׽f|؟طo+zN`Gu7H.U(, C7s "/\"A]vsu{fηZKa:FIl"uh| wj*6Ѱ54ݕ mbmvq6 q8>Z.w0</:!`\$Niƾu o`?#}?W^7Flk;|=4+ :iDŽ|Y٩ v5{D @v=,߾K-Ķ[E)ڸp} L֛^?ϩ;C# df f= AS_t.P XeF,![?7Vy9w.7P$7Oa鞁sz8V{8(QSyINMLYv_z1:s'G6go >IWVG Ϳ etbw$[}QN XKCk'-NLo{w^ wlf eKē!P@n1e0R 8e-vlя"b=GF5 *1W_D0F%j吏HPx`Je]н7H'G~]l=#Jߛ{a20J ,I"^DqW?Bɧt8:e]R!gQNS^O?VjۄsD =9)B¥]2Xg ֘t+ *҄~J+$|Ht^+WT A{ `X;C'ENvnT8jrr5&C{{7cI(6Ը)); s7}gF%.6u[{Wf.W9q|:`€48Os-h>Ě0 ^pSx"K)zeݲ_/m[.(}M Wx*ڱz {dEUjo1 DbCASRDN&`\Ճm󻝑{kj%^y[gL ظAd4E5a:ݼ 'ѣC,yO b)X<}p`"ڟȶu`4*zAP?fε}Vr %nUN29)FJZtAXl:nRh/1d/>^at1CJ M7|aֱ; :x),m߳:əssL 1G;2ϑбfζ6ŭF4&`>N;ٱc_oS/8%.e7}aR^ՑLC_8"XL!#@\ֶuu8n÷OiS/#2J> VA. $ޛ֐H]Gz _h/Su.iEIJ.̣ԱE1=ּ+ݷ.cp.Lhn._k'sp:zVMđpHxuy q l7-W]pÏ>-4E`"رcmm0 9\Q-hSd" aZͨjAv(Zvù>!v5Qƌlں; ̎/q~^/" Eݼl0DhV&RGd'bX'|糄յ\(f}N/ޢC o0An嵦S-ssKo먒y0yHX8Ϡd 32,B חkYz椮eFa.BXzAvaKF-28/ '%H3::; ػكv;m^g=o,h/m9du'h\BeP t2=0Uڧa?G Q}̻p:lQz[3++SA׭V,3β$T!DxI@DgD(qğ@>ry[eP;t<%K32PPG JbWR(V'-Yϫu{SBSgLø e:tԳ* n80z~A"4gt"k?ꏼ՟[L]2t0{Z)s-N\lӝ d ~<7jMZ/ظ Ѽ@N@!7|*2k,VPjM*Iĥjo+2:&N6WGB)ޫ+o\<2d{AhջDs3$[eO^OoJzVsH/fi>Ȅ?Y"$e| qk/F%2]!kִޫQRʌ#J]3 @_j1ricKC2S'dV;=u{̳WxEZ⤷81HtM 3ž\چle1(Zpe_][6XX Np*&Ƌ7 ?,k.@TdQS&a&!Ǧ&> b7nq~^3£S0Sza[9.(v$"ntg[+{Jsy:5=IPdJ BLƺEꏰ[0|dz}_82 ˸^3w>x:=nBä:EҰ.ϟ=:;1Y=H0L#3s/[vG6 '9-Tn 8q }^Б :@mxn^wv/7lmXZ2!4Z,Koli|puc.UhH[p}_X&0Ber8" N Him#}J K;Fr ?OLgDagVA&u"%TwyqI]+;e1ID· d/Vb.3m8>),}WOҫ֝*xxF@̢S;?Z߿507eC7tA2J/=G-a1uQpKDjȶd=> oMx]O_{ ~3B7{?>pU5a]XYo0ؖ?{n3ܻʜ 89E&qqv3 ܭJ .7I"n)xﭕ!xV o2;pWkw3_ǖSz뮽q51ԑN@β5 UʓY,~z+y5~wc?Կy;s#'1+!)19: _a':[wl˰ݻ_zo]_nryK.!0 U'pp@ܑ}[t8EUɗqq:lcios,* )j87\>m*hl\Ϻ(ÿބٝ#`a%p)%\Q7Gta葛ldpG2[!+Q7-iGnW1N B"Ѹ<8]1{w8y[ϸS+2L_K[d9Ϋ$< g7$a{o##z?Rտ:2L gWK%T`s~$a!b>&s>+ӸyT_E CMUk jn=|4AV?mfOXww Y)qP(D&$s#uFrHN0HOV {GxJeR,+(q25(jox?s/gK0ML&; /6>ZZVyfWa璪!zb `<"9'oW>yl*~xOk&-ynBsqrX)nD}Z^Zp]Te3^`j"29&ZD_:ΪP:y4PxL#M0̀IDATY5Ɠb&5/ py{q-0|ak18I>7QR˶^͸|:vfȎ CvhD Gy qݠ .6Dr D?O0[~܋{C!e>ҙ ^󝄐qɟLf:K^ubly8,=I,yoz4Au e3{1[~'u*Y̞%Z2L}j~ @0/wmy4NZIhSNW=VO」D c(u\N%}!x5$nzوTs-&~/0çH,3s=,SR ~?@_0{ݕت~ćVf7 le|?5UЍ]zauvW45ODK0Y $߬DI]  uOQfٯwD4Lcp '^8 CXdaKgFrSWu|!RNKUׅg4NBN# IA8RG%D_'0zԳ.f}Ư_u1xo F b_ XV76{d; cKApp]w:@tQ[/X9qs`2(m@}ѯTTB܆R%l?`@YS$B\m(' 45]vpmp #8.M} :{ý`b  EU7Ԁ !}?-uXzE25;(DdzBrkGrM  ıHPvDH]i}2lX'eS#/}gT?1{2O|Σ=U7}Օ?^3H_t6Fr—EZg|AA7C]Wdn "-9,OcӚFOoz漢ɀͰыPlE2(1: qep&'p<꿶:[Jxy jĠTջǼ?8[ Q%ZͱcGֲ{1%Kv.07zV*+&U.":fyq3ޟ'eLg,'Ǻ滌" X8~NXO8Ty7bKxN7=[jHz) X,%~ 0d.?QM+P|Av4Pu,C`|!xggNn/n3/l 7Iӎ#kY?|W# {J:NͼLa{Y@+. J #b3M g <7h[Pu8y͸)זx##7_c>z:B 1+{Fyw5u#^ @6Ŭm\m`mc17y%_qfs>^K#?F5_29kƼiӷu7 ` yvܐc}^|!~ҫf>'|Zt (m)q-}@|ϾҞ3=^F[Y0*z>I 7 w_mg/~D_8=xa:)ye* 6p|x|hGx[ۘr^c<}"ұ`Fa>b@.7UGj>@q$ghsW= 16|ÇߡO:,{OU+P(DP',ǼDQ{{}W$_LgZg2sD'3:wGPW>2L뒩ӛW>Gq㨽/N)iL˹D(h%7 "Q?|=y}?:fbFv,;Ǻ:#,UܱzR\-mXm4ڇ;U4 /\ہL%aÎNk׷gӑG}" \}I"qHU-'ccYS"*Tu<_Ǝg3NI٫EHOV["Û6oټy2?ƺ# Okנ.$% XLr<(ͲrIJ> nzSߔ̣H$aD<3:4Uɧ3G 8c^fz)TFe.^߲RP{0_QXuVŎm{d߾.AJNY-` 8x-SM"2 5Z!r"u^w|2MD2.e8>ċ{no}Ҧ/WyF_d@NAeKohFZO5EyWgAcһߺH춻גxݵkfzo}Xf$a7Q]oI7qUse_$nx!p%k+oSTH +˫AÌ"|wd8DD7D $|MG$0ȯaU8#.`uMMP.jze4Mv]^+8BxmGtO`7%Fvժug~Ҟv:5Ga9ezzj f0Xj0ԴV8ꧢ'M,[6 u/#xmPSxtxgM=ԧ鮿ƺ?.k xÀR:HQ3Q[*7Eau{if鼏џ#m|p~;|%iu.=^ۆGX#VH给"LW=]շ8!Wuojldc C/S"3NVdB_xqM鼫5O/`3BUAv}~?ľ}=smc[-FJ3Gz"'!WQ^E[?3^B>B gz4ZA?/!|rX -> U3ϣ^F5R"eZ(X7W\p>_c0i0& & obhϊs_.r4#txO_F>׺E$.3JDk|oƯ %2OK1Q 3Xd&l4ȶSF)Sbϟ|Ѥ{7"wpzo/zd8_$> => gl60f`-f:)\z!yћh/_F0ԣY1'A, Mg=g>lHjgQguI.W "xJ~SXV\ҹ\(?׷d2 Ac{uZΉ)GTMP&8dfޠR{{~M$N4̸w\R!~GSkz"_őpE|TJ'wG1؏@y-%:ǵX'u3jy>I>}[q~WL_Ł+ϏutX1w)XOzb>rnF0#;Ы5*ܼFm X^iMsF``:֯mMo|''Q]zsinHt fUIS[)Y<\SgtڶmNq #l=n3ǸS==*%aFѽ5;z Xub9^rq|7㺋=VR?]އS&)7(r m* BッC{O={{*ʁIf|‹J`>.{687FKflt X ʻN Oq.Ъ<㳀nX|e~ ?M~ Z{w+.%ϙVx6p;l`Gakp+h꩕fB& / wJ'"L;;S_ "|wb;AFVҁ%Ĝk^y?(ig؋q=9s v]> !LLNS4ul Q%W#adjQm* 0(1 a[_|#? X ooX ce]>8 voo&;r.VQ؟D ߩ3zuJq2L\3[Fsw߾}p/p Uȹ4eF]Z'`~5v.`3 8L= @G7#hMH̡D"sm Fq)́ pX {dp7+DY"|+,ȞCO:]ێ?p^|O\ bdtE F:c޳%W/Ux5bf|qQ *^ofK3!gڐt<6-+taOJozt{[6W,i'G?01<ԝz5q&SPxuη\%к ȼ̑=v!)B A[:yOQCDW-% ѳs_烈w3[^:5NWOi$!a Jt-v&wex9]{8T"3ղHc셠6n̅{zƨR7+a7ߞ PAW>สH}50A#GQ'*;(> <@Sx`7YN[֙>;;yDx`AE(bՂE~/ ,eU!ruAGZ1 `PFֆs<,O oBP_&PTR B0֮n=O'gu3u~|C=4{ށ?g>AD2h GG; KoQ.~>FF66k,Fwǀ]W5nEH9Ns¸3%ڲ%΢U۽{K? rݿC>{oga~-g'aݭ[Zا˄Aey醙C%z-sҬ#%*ܥ]*@Cdp+oB P1bPs՗{x| oh=sJۛ;=o}#9Oa7ਮ}iq!* /q]"h\ ɝtqI֭[xLy.Ize)NS7A K} nuȏj<^A{4sgu2X%)MES!+3u++ꍓoլA8̈́yơ2UPӌ89qe=/tsuΜ> FL3Zkn2)8POOFE) BMh LT2sY^rD!YDm\ Cwh5SJXH$8ub$#V#μ [ɬ~Q$j'@wtwAMi'9ҍ>XuKQ%m`g[[SP2q QO2u&iˁ/$LRY;vijFA%x_%I}(*0ߏ/ 8(eN+MHߍBrI|9I#[8h0wN#hģ~]]i)9dW%]ic_1  ^ @D_@vBXqb8(1M SNMR&‚&pS/vFo3o 0"8^xŵ̾x 45J-s883׉ߢԠs/h0 u~c~:<ʛo{fUjHym2 AB〃t8`_AO`c=84n}yh12u?gioK2[ VutM˂@0RѲ2KQ㓓^9%tX񻛍Ak]1'WLw:y zʻ ,9E&h ^6@՘:!8_5KXn1Fa|frDn4h p]u+yKEWw~qj@zJɍex Sri7/Z~0ydBӱ)6K*>yp@ ag%"eA[B2T*A De"\@ඪsۿĶe{K Qޞe45z8PyEĜjP ❸֝,Rfљ}9FNi$  F"AwXX6WXI~L;lmNsT`}H":x @$KM(`͛8]ɹ1?wl!%ǣ@"eJTg U€GEV;`|YD #:~V[S_&^Giwg XByCX<\ (Cv~+`Söc.yZ"B ϯ;eӺl8)Z+4# !L6MX\$&Ӵݯ\sQqζo6T3En=uAbxbnA6ăfiNA, qNJ"&Q'}sYRX.T`!|rD۲Y`Ff|nKV~?"vo|ae6ij<+I vS{Ͻ+sK?q AP|TXE4 .b>R]·12L,̱1nr{[;y5? H6H3]iEh<,%$,8,WmmG܎'pEPp)+)sݳb<~a%#~-Ppp|޽~wOgM,d\ ]ÎMnju t$PtF0Z}w}cl'-j+Ji _kJ.{7=p7?[U3#CύCwe󮑚S2@f6|߾ =߻°t{N!6ꋧY{a t'O2D!CdwΧfv+vs{ߋ=ko]WVkwApv-/-7Ʌ>N3Yj=mQNf_ bKeFQ4l6smltZ ᶛ~믽q9rrrrrrrrrrrrrrrrrrrr>P }j,IENDB`OSCAR-code-v1.3.1/oscar/icons/refresh.png000066400000000000000000000016501417327530600200610ustar00rootroot00000000000000PNG  IHDR(-S cHRMz&u0`:pQ<PLTE $&?EF,02 $$'59;9>A/57W\]6;>#FFF@CC;?Atuy7=>477:::^^^BEH.46UUU~~~eik[_ah^l7;?WZ\rrrvvvnstegiKOPDIKOMT?CFFLMaef@CGQUVmqqruvԜ>CD^deino򓕗pssAGIlqprwvGMNQVWSXY068@EFcijglmCHI.4628:/57OTVMRTſ»ŹĿxxtRNS"<%=0*:>>8 OI L].BGlLUd^\JY`efQ+^yIa29ze .lm`e`EV]Fc2'c bKGDV IDATc`F&f$ZqrqV BB"bR \UuMm]P@YE5Z-:mz F&f]V6 vN.n= ^>}~@C+F6M  4yiӣcbA.OHQQqkzFfVEvvNn7EȾ-)-+6:VvIENDB`OSCAR-code-v1.3.1/oscar/icons/rms9.png000066400000000000000000001762241417327530600173270ustar00rootroot00000000000000PNG  IHDR\rf cHRMz&u0`:pQ<bKGD pHYs  IDATxwduލގs &s&E +d9H"dYe˲,*ْL0HJEywLGU S,vwSv߾}f޽}q4K_8I7h@Zoy˿|[o;[6ZZK/'޽{/yYz8IsͅkED /ŭw6?]Zo>c !D{{b%y|رEJ fMyG )XiktUOo÷={D{ɿ[6\Ϧ{o|wd'^d4bѾ9@` X,TfYȹ>mW qSw[}a%Z{U==OϺٛԼFvt~[*0ᾂ!hш?S?ӯ)ٳ|?;~0oϞ=[Wٻ&45)N,Kʸk-7 "Ƹ] Z@ pܨwOԏ|j߾}}m¦랷j(6mvY%7gY~1)YZkn[k|Է!3cc񱑨ֈW?[kxFX1+lz|;wWY\"YZkр֪q-;ڝ٢m~c,BM/WkZ 30ؿ%f`#lovy{ZݵS}Y_Gjt:59ozz c|.c] ?k`q@cŸcW1X#r`~ 6? >@Ї5Ծ14{y\ M;xJ!w>>wO1ſ_`0 @By%-N\S j pcM/xn{}QOZ~m*X$Nr_+CE[ZG w,6clEHqanT;:~5v*v⻗[޽{M裏V/?pcd<آ$1X+hK6Z!zrjT 4'g{t.?2Ie~gNdl(iy ;`1bbM(W#Rb=h115M.6;yj4OW!ǥm\kZB~c^2}M:x-ORP@Y "O3X0P'6=6c-j̸'~o{zm߱Fhn{m;K2m~@Hu!2cݯjӄZcI~ZBQN t3#|=t>s0ϧ%~f`1L 2BH B݀F–,S>pXm'LnՋnw]:o$5oR]#DDM&1҂f0miʟV*B6$MѲe`@]IR"9 c(WT5֒%kKjH*T7OR%n`v]ZDZlھO~dn7X46}N^SjHLJݱ֒ oiq(7YkQ'RJ pKx![d<'&$.:I%Ms V}}h4etp>iw?"+[niF ݽ^v;~aڼ!*}ytf)kF _7kl iFu%7'}{Al<,rmXou8??8zgNb\zUl߼jE,`X^\G#p=wۜ]zvnI2:O]3P/kIEc/Z|,G& w=߻?OUO8V珺RZ&&BB%fK-KҵvKunhrr?۷-oo;%g]qXkm:ܾAhv,7N^}ĥ=(j[d!T ol]Hי2sO}xʸ̹g9|0$tܹ^l.ںA:'N…n[g?~_(Rwg^ Y R qaU_{ F֚-Sbnj_̇SٳgO6w֊;Cx:(ʬysD?9JBMR4 r\e].ZSm5&P^§aF/_pT1ֆյUΞ?cG8z+KKSU'c]Y3yt#lu1#T58F*RQTAk { \L>G8O%2 vnQYo2Iuu\~3ժYPHW*22` ! 915]3#?zp#|Ex;ԉxCgoVB]*dZpXzޯ Uy=|g:9vrQw QuR,Ӵ:-Ν;DZx,^:<311NU*x~pdk1;v_Z"0 M5Ë]|%_,}Vy+^٭.bz<ƔSE4j%ݵinvv f 6ފ2vYhI]OK?5%Rabs㟯 M*ĩ8xI{S.ų/-6_nsAzuh4*"IRZkˬ-`J\EE1GqTk!85NMkm!SS19>ُsInx9zzO}S2+^*fg7s]Shzc B 3#w_s|Ǝmowћ]ߙrX|偓'=v@?2+Y/s= x:p"jD+?ß z܄N8B!O($Ys9t =JqgyUb xGڝ01Y""SחY^'MTTL%`%KS:6YbMNZ^RN⸆*yue l$ͻX_>q} .,/|SdylX>Ɣ,˲xzv À썛Xk#| Wo}OqG di>-YݐG/UkIB)م>ē/U{EDߐ֧=]p5E 3 UYA^v1e=BH5Ξ?˱cxГ,=W^7Fv(Q9SO¬A,g^mkMd(ҤCNi"")%R*:F}QL%IiPo 7HW$Wγu%Nlb t?qGqcs~]qU\~NSTYoGy{8zhd王ٹsfBIN,MF%h `ZYiJz_ B)RNoHLAm:# p{J18>͕W]}nvzKdtt|ɹ͌ BXC+\JAa\`Ra=|>?/  [o9w*$Q%3ZGνjOVc6!DAM+_S\qɕޱzåz6 ݧ֟ޥ/~o]P Rd-++:}>#FGX\Yկy 캈z:GO~ۺm'#S4aks2*g^hTJnfuiA B N= ܤk.%$҂4e,)@*,s3u}}[0>™S5O`\*);9s2QQkQ'K;.jg9R*)z A.pN@HG[1c"{@(\cZnyǙ"jB DQĹųR/hnr2h%t  u:I6?r~cGWr-[=vl#?}}nk1oJNftl=n 3@SSBP؅Vާ9~8O>@_}}u㯼gs |uy2ZI! Q t`yOSh1261kt9f__b`pO#| _aW'ݴYV@_QX_[aum"RǏ"Y`@E$#>uڈP.kP$*>$HkKK:qG6#-$I~vJK=@跽 ԾSkՕZ_[wTt9|+Fx 'üzw{Z]όuYnתUib$.k5YSUxӕ7݌ B߻RE\s5XA"' 0yY4[*BzP׏P<\{'X}{Py15vש(R.v+nLafY(Iiq?[7a.}gN3ŀJwd]e.(꫟ӧ9t6oW"=x/-q742dYޣQu ,z6biELmmd[q{2M~PȞt] ܸoM3^+t;Y7śԫt-H"LR9ֺL@"dYSy~>KW~`Ϟ=B#޽{ҒUܚ݂KǕJFmxJmq&*!E~HkNcURk+|gNɄ-e #5֭;olqޕQEz10=݃t9:'gjk޽{6=|cx;ߩ_-2RUMRkFiRYH5p|lp)|ez8sHGSTQ>..۔@I96:IjRZI©s8(_yqYZZR1<2"q%v1^ +й 44^JLX8?&n c=Pv #:3 x| *uqJc~ ЧaV]rfZdZ+&'hPҒ%]OEf2x,zLX]&EQ )A_Gz (c,Rz|R#yYԏgOE0]mWju$%ISk 2W[#\]8aKOqw%= uII֤U0ưā5&׹Se)CCs1+N4=snwwq%Sŭys罘O~t+Y\C8@u]8` ZzgH_K܉?NAzʳUҹ!D՘"yq DZ9J@_j%*$u(:X"岑gkץ)e ˪MOW)ntY)JI3'Ky͏|w =|.o}[ nA 7mmI-}X{uZ'2.B{LrP(СUp=LEE{اt {6~;m#)n\0iJއV{{Ν_Jq&'gA@&:YO^ˊ9{>T2^IE&9Ur $} jA݅gQ]-@7!ҫUnjTSk180L_ q1<(i""J)BY E\o"d4iw7@D*BEiͻ\}ճٽc73"R:_>m+j݁w[;kZKeG*涍y]Wra#yǞ=eNw3\˳kF5M2$;ŨPHQJEҋdɭP;$&֞R[VEf B"c1Y 4?8^ըTk Z6'OǾG42RYuOZ8fr|s.SV) 0E6s.0ŞEp`( (A RȖ6OؓO0O0k] B !:Nw'*_QQ֢Vm?Hk5TQVPRriO#FZM$t;kcQ:U<'RڝYb%R1JEj34:3oR6([? x.hLb)yl3 n21Y^'7_g !Uz-l߿mgnQ]'wBN-X jzU9ibΥPkEՇwn`˺-=Xkʬ}BZy<`IqRI~*,|X #.PS[.w[ӿ4KIojy ќ8vzQ\ak*2J8?YT' %8R0u" XZ] b;ed솁nO Љk<scPqDs!M:RX,thVv&,!:+$V V &ϑ@UJuF ӳl4H^_`zlYx@jSj*'MN!u/7 c5,lalٲFc i第9c.P^#JV(JŸ{!($jK^GLNo>}!"j 3># 7u!#H1<0L0;!Ge NNk,mcm'4'ԱH:]7P%JE(Ei%& av6maxd| >x$Dpx=`uݽ%J@Z}ڠiA-)Ƥ|%A[gŖsO nf`߾bŧ` [Ҍ4'FlQ0*lvu='|(`0T퍚()A]6;O,}cXc+U$YƩ3yo;> sxhf_n!R/(! ,K7m6gŌ5./lQ9sqI$YK+X,shc8~^$d2&4Duxp,bwJTof~y$ϨĿa",@F1i4JP6:yMYBit[D>zqAe}}e,uS"046mLOM180HR`#>띢z,rZ2 >ep DIK0|| 8P Ԧg?K~~7߬'Ľh|>v7v٫kCc קXYcܝ7T (ecpzo|1dZ^Q$BbIOt7='(~N&X(1@ זywqmI;$]`NY俶RIv!٨31:Ʊc9wXk]40>>RRk0D^"gX^^fiizVrEM7~(Jp!{ :Z[jI@ S $NrECPja^k DI҄ݢ^#OCI;h!F&0Ξ;CϟXKWTI[.ajj j*1)o߫7 XXBL0n)($z/k'=YAV遻os{ /@3Xk?yrI;{e\<>4h UcRk-zľ+EmRp/܅ oX 0Ċ@\?=&JG4!,0?RE1"12jZ_ZR5),ZgiBttVImtK& վA֚,.ca~}C@kJ!d2؈ͭ"|L)g9E:0W_oR:8Uў={7}n] V 44 SV 1(1e KM~{/wܗ{R/!Y/BN/=һ =T[| r-3!͍~:N:C|O|#\n}-۶mWh"X]]V۱*BF5$appAZ5.+K,;eTV!pc: رc7sLs'q5xaS:vtv7n8K8\,=%AY _څߣ=}A\0e7_RoN71|p!kQ4x̥D߻?@0.[~ZVaf9{%Y "-6\tgG^1dzH_fgN%Q(R*M5RaAΰGke.w\}啌On8}߸N>y'ݽKdh&}$(jMzx_k_&gѬu)$qbS7Ȓgۿlݴ7?WM=dxlHE  qq)o n=|^֖'0 \ YEޡ ""g&Yi _ۿM{W}78*),sԩcX*Q"SZL̲yv+S  PVee+R`RT=I{'BÇ^ -(JB4NQM()kq+?Tk؛`"|z)**ho}_)Mo>o>c}zu*-On8O ix4[o~CSɞUPq>8^8}k F] T*>Mr \{Uضŋ_p=/kg?I0>1GTF[<t, !R9Bͯ!pK8yg3՘Gβ)ykb FY[]Z 6g0==8jTY($pZHzʐJR=j Y'vqz\ % ՟;K/ı3A1 #>( Hkx 2oV~kw}eƼŗ*Uәd`\W"t[n\v[ hG;$Az, Y \HՁ!j&Zcc 8p }|&퉍>:^pu޾FʹgWǎ/2c]I[,tiӜ>{cGO"x+oz_O)B^ K.?n*ɵa~u\ |!xp+"]&\ɫ3^uAyu+Sr|~PƖqTQKW~޳g}}cYO?8i inH!"@a,ܴSV .M:Ơ lFpFYda"pP v,@B`trjI+p x!yCۙضWrGi[xr?_ 7,c`x͛XrWnr" ,en&*E^S'xkApn%t8npr\ysË_t#CNM166bVWWyG_ c,./enG^eo%+\f kmB)} Ȏu;+֚ŵ_~qYo ~l#H:]3nX+E .*zUi[allIWFf)1=ݡT^PpVX^18:@8??7߸ N׽X0Ε.$. &Kh.ʫh;̟^kLBd w"[QJze4B*ՇQ^-Dʝn~{]j<7/+o1gEFض7&7&?_zO~E=<16Eq,JJ!.220 edYjãR5V}~}-[QJt jGpk5qI&q\x0og{/{7K?~1<'a1đ"ӆom|߼Dt jk7mfY+˝gUjF[&X][euqJRzB g0[BEO>TyOT(&9zp˶lټ1Tg厁)e=esMN?dBY(bX]^s?bNcm%ISZdOG|J9È8/Y9FgՌ\&Js+ڽFRm:~xx j(oB[~β/cQYɃ`$ ۃ(YzIUJHI7i"bppiJe#:iV^Wr`|x2O<0_ܧaD4jyآL k2Yq*\e!u^W=J^gu}mz8ZQDv͖mgs.W^u 'ϜdSFcVXkD }fue-gx#=k-&*Bz-.?lVFp5LS䚫 cĕ5yN^鴫#?eeWa~9j/h&Vrk8ԫΞ?(gNdxdJ\e !?UeEV){Qj[Rt*$.EnVHSIT$)k2FW^k_ػfy-tun玟_73?ybE@ÿSqD0piZ1JJW:zT`-2DHǥIhI6G⁇?Ah]pq˛o^ֹY937|sRcm:ӀZL 0EJxU8YYob?{=^nA)O[26>A7Ir]ΩGI.>nG~S9u$NzN1m #j'ϜZ0=yN%ih4LOrW?^@Zx˗(>3reEth~Kع}C#tKz@7 QSӇo|%ktG3fZ[$AFE6mBr-_ށ 5Yh06:Noa: UmO~BDR"Un0T{hm@(,R@eD.$Vbpk&w3\3¾}i!mVzaGh6H<֨7(,`GecxVCz tNњ> ǶmM6*ԡ'YY^`,lݱ-8&M܆Nz[&M[lsLUb/wy(ǔ`yeQZu7AWIbdbH،E7Z-ؓL -$XqdVƩ4M0:$Y ƕ&w]EnH.X :Z8~,޻L++l"knZ&LRJpqFCcJlďW2\seJhc [ dp}~ڕM8y|s0:I\#g>'ߴ}3/{xW11:Lgš#Oz/㌎ IMT;sSbZ 6KGIeYN7Mo68s<|seP/NO׽董u/cqç}cVtVض2_giicf384Is`X*L'C.})fO<yx<}>y2<4FŜ>}طކr[2 0 !ykݺm`|r~7k.4ְ=ȼ;)h<b0 ^&BfgOgy,[v@Ȉ&c? -#&fqmg#x>i=7;fgfp"[D@%UHYD>Se?-X&] O0i3Z*/ ]lxuGw 0󼨑T;<8ӰB$FR%윌!MS]SBEQ1$FP#}-|&Gi!{?9sZfydMrd`=I~s_[<~ }q9~w ,ã! obrkO)Q NŔ$024ʶ]38:JѧW'>#(u3gyfs~ć˽)eVNI)o,yN(xԊa@DUZ<{c lټ?.XƇzviYo2s UVxk_*Ox]l9ǮtU2ΙX[^JO ETb/HR.]_]bhpz,KXvzN/366A_7c܏]\98 %JѼ8oSqJx/ӹAca~i0:>5ϺF^\6Ur\m"#op*=c]WeŮ x"ȍ!&D)J8vsqaXi@݁%VۢE=@ 3XmH'y p%5#$i8t'oY/җ{ vxO׋U:MWTtYY(9l(gyJItɍpͻ$Gs_[>w7f GO%/e|zGNm+n%b/0 CgE !J7AJ(M> 1EnD50]tӄN8FeVVW8$3ws\Sk6k#[5/lnDV08J̀&+F6oBH$0:2Ʃ'] $:t?tYYZAk۶bvvAJ5m-bgcGZ<+w( [ 5#/gn jvkTUgѮ݆ɽD=݄JEy!ZQ Q֔2]p@ D&[Uݲ1ONxk Em4F^,'!ykigطoi{}y`?##ERQh k\ ` ?ACă4KHZۘ&VgO|?{v3=9KTʫyч9v)Ԑhsλywnۺ [k$U2Q δPbߋ A{=ŭ7(Oޡ '3‚!7NO'aY43팼bi~E w^L鳇ݍ${Q}^f` QTsUre*Xj/7"j ̐(,|RsM_={,lnڐTeHS]M7t"0dyN2m y{ v ,lk/}3/^}o .O|!M'piYYzCL2y3,Ǹ㋟_bqsnݶ\g$yN+OTb k6bx ]'yf*bu&P E|gJ mj ـ-~BaY=A"Xf,hsmVi!g10id~qM$ IJk.O$$Y&R+4f+7p'^s Cۧ`brW035*ԬYrc *oCUg,twI L:LӦ޳/uRTj1л.K^ʱJ}tc:rIVjemqڵy~.lڲj׾r;rIOg`pNڥtQQDJk!*QD'Ii4j Mꆮ&RLyF M-MW8rWOrw]lۄTEgv+lސCU{ƧzAz'sƠ d*#rF{ o/}6&7q1Z1bhg9}*"y\usMi(=z74cm[{ lݾqpWڟ;w< WoRm{zMZC[zLU8QL}e`ӕh?31by^0OITM^Q֐Qxt~p#WXoBXp3T2"@KhxqM6u\H5I2BnOcmsaq8qe &;K0^2mBHbƖ&[Uqqd~'p RfT # 7$¡þ]HZI'q\rfg$#h7E*gɉORoԙ#c4k/z>'" AxM\! +E V.0ƢT쥳n9&HW"Hl#*~3C Z_E ؗzB>R_(r/Щqm־9"k<,n^o w|@>Oj_K[;"tE:|#3ΤEHԩc^yAUv[sf~~|ض}Y)+m1YRVי裯^Q5=˩8y-ι&馮W$(pvG댤F)lky ]:ۦgh"GP*{6Ԁ" fP=}yӖRX.ƣޡK;-nJ3:yƉSgy/+.KvssG{1R8q8>VHSc%(ʖ_ 9 gmVGF֛T3O)9.gqr|l߹QbQ{5 A |9`mr|?Y(СQ(.+q TcdůVbw?k74UgPJM8p8D(b<΢un;tyiz 69vfwX}7!5k0 ĉc(zML}}cO\^.\}i8צ)d+6H%BT#ѕr#j V:Kq/?X"sEi|B]+܌M|m`G?#R$}#*]-A"{RJN*jRsJRRmzv//̛mʈnedtOf߾166F$ beyufaqFrnyk`~uvrrzNΟ=E`vvV9cf0H:6AUXXYW\J4<7ёa?9lJ))6=u(S׎)T7`6T Ɍ EHe1z]s5/PαQ\a|p =dTE$Ij[sŗyv+}N.!ϲBnԙuHDia-|iZVx_~vle%ivnv^czPA_9rVa\o|TN:j-c 'E1Xo-darOǥ# זZ{zؽJ7}g0^d9Xƽ&w|H}xtur4w}㰀LgeUt Z=#D""GYV qGWLˁmҢ6JUƮ;+kT*T+1.3:O9CZ^&dF.(q &&7;v26>΋v,9C$YK#{غy3+kyNvLFyGy mǨ&6@ESl~L JA.>?#LUXKXorn~ivn&/޹%/ѭ,)]$hkiw;$i5׍nsy:j1>zvlz Ub(lhŵNZ!Fa`׽|oaLM5C |SP-$Ao=O3y1$QZrđxk҉dd^J j=Wk}` ^P.~yyǽO-7Z{Jmr ^$v=_U(_cl1CHאE1Z[*Γuօ_@p)n9=3h c&F0=}x׵I;)roa\bHG4uu.ϲaG&R,soչtS{)@ fYYk9BEMX]pSq5ȫ {1&۷ɝwǯm0n~mHzm]b]|_bfj })=`eZ"3V+W8etEJD8|)80+9L0}'yRsq\E AʲHp%WrW21=Mt^qցDҟvqHAa45,I-TDR(VDāS΍.qsohRXm]g3[6m[۪3eJ%":I~^cc:|9FFGY__uNJR.:GW9+k^P79725O}ӴϞd|rN9(V703[t}TMTkFGxؾu; &П-P*#P]a.aU3.%Kyަ- ӄ{IDATsg9|g`xj C@l]ָ֚gϽ-[3::NZso8Bq5Dk#uVQ†q1 R >q"- 9R9`?Q|[RH- HCz+:'/&:gco4ڛte-59zFE,8vH~GT⊫)?pyFd[VVh}V(HA;oy[__Zquj4^Mu=Bgi*Zvgw`#,CI:N]{Tˋx5/ءy n羀e Mq׾L3:<'hVyGhHiRi*LO4j[L)ZeK0*U)EXΝ[`mysxεavrv́bSbdt]. $*XZYFk  ?n]4)P~R 2(x#3A9azrx:D[zw~  Si6D7MI̥iJ$W%SbI\yRtbt,R]cbkuv;^Q]jJV^ShT*K(EqNF%)k+TWIsiZ-G4T5R{3fff,@YDֹPl$di@?z/?H_?KKѨIPmU,ws<޲?kә;[J2DaơmS_mFEDQ~՟$ڊC$@L*UԜ;ǵQ8T*ɭ{9{g=8g߅K xOs՟շ⻾GObt|. y+AqIiɽۺؼv;v>H)Uܥv !08Õ܂ճ8r ۍ,&Vm@$XZ\@';ځv]$q\V]ވu֠j]mI1d͘.'s0 #DYT6xZq\h;ӌ[lJbqa ׫ڨ@A&)z7V8a^l^߄Ƴ609..*%Z؜˽>֭[Ut8 Cx^(JJ 5Ю6:#x5֬߂O3憗/?XZra =UHJHCh;}kL,MFY IHB8 Wm n` CBkf ji~ HHh&\D_O*/yoccdAjFش|@-X^Kucnh,Or(6I& |!0>9^QZ\a^}-<'Aņ  8{4|/i$(M\=֩;ْ5{142FcBI*mVkLّdId ]8^P/RT7g|QSn gC5DH2lśkAI/4 Qn*K3$A.*~@p~I#/3p77'oo>zEQk<ҚgZzGP²ܭUʡ د T|s09DG s_zVjX\Z9&354uT*uw`djy,^FkGw6&1rӪ#cq#,<]$\"7p$p/O#I"eriyanF^=Flj'('19> `LW摦E\z.̓ gtr ^s֭߈Nve!PJA$V VHRxـnc-HnEZJpSl"oթ/d.5H%ɏ!KYeXF U(@6K}_]OAgr a/|w;~Q8~bO6;3GF'je}ݗW~S'Nayq(%Ta$YDhhjعs'QJyŋg01>FӧOqn>֭ۈitFF;NĩD_@ZLOšQ(@n2`r,jn[LK.czf7r-hXXX;ؾp*$y{e$dr\|a8 qm/K_֗a݆Mh6ZPIFE f3{ y^4+ |`^jVؼd?ap{(Q>9F X8p\N*nժ5bDjB&F^0L`L@J +> ϣx8F4YΠMt&Lڡ :e\K)| %D%8DE0Q4pOH 7b\HkrI:}~}A@yp\z}=wPQa XNR8Aڭ40<Μ?Űi ^>߰_w#Ñ1Ξ9`xIU-lW{} 4JẆ1Ѧܥ~ZLو '6y>4AGb 8{F(OAɱ/at:4y+[قKϠCRA@` ,\ELu!-ފuqۋoH s0O3FPAf6qYi6Қy%if̮Aݢhk>ӌ[hd쌝nW`aEd,*Ѫz )5ȖZ0 n=E0F?E(ɸ 7fe RBzez-xÃկx5^tM?>ZĠV-B)R)HZ T %92KwyЦii%`ѣ8!5^6[2JqM>p1<'2VPwJZa|*A AYG+pz= &yKPu3?7_k}!8]GԟGs΢_D/ #h@Cx/ ao A/ӨtUu?,^Wvr cO]fc:.ze4-e/_| W#c6Ƈ 8ٿ6l諾pxx)-tp"ys8qjLJPw[!\^h1"&oѧ'r3^-/B[,qi:o "XZGHJ{¡;0&,L[n{֭^q P)O{Vtk ;. ABKvI5Yφe1N1%X~5" ;f-X;aJ hR];ynQ} !**YN=Vd_XܷX~&&P$"Ve2T@'(HFn:l @9߿Y9Xlz @dP(Ky|CBuϛ.L87AhJm5.]?ߎxp5WaG@Rnʧln-1dk` nx^45{4Ç<7H 39]ln rI1i;ݠ6ϑ xLm-!0Sjw}uh#+dG\8!cZK!RcNc#xXqOƵkiæx yMI q}?caucyiϜ2`Y#U9 =`U[0;=FE pad+&J((u<t˭8U>)!,k,܉w\fk>Hɺr =.SAXl/uvF\?-7ނn \}5fWMsU*jUUL1M2VdBp!8m2?/"O3HyQq)7]@Zr )gJ1mQ*6h;|“8uyҊU80?j/2)Cß9^ -.-@ sC4O'6Ѩ.ajZ?inf֢;?}WakgV$\5Μu/I3`R"fga-8vd/8eᆆA (L] o?x+~ؽ~q\:v a" C:޳Ni_@khe12#lWEHSOD G{pEdn P=6[_2̮* `-Rs/ٷn ' Q Zk-])*bۧ1yMc[3$iwdg>kYJ|YȢ%+yUp] i~r߅WPJE$aƞSA`~4H>.k#:w z6Ma`q E'p~J7y+937W뮻rw`sAM*Q So "* {繁Ws pBHd)4AQ:RZKfƓÊB,y@rK;أZ9.s::tWڡӊ]G.wҴƉcS麛?}>CxK'Fqy:AbRh8Y*LQ317X-qU$!+na5LLz9;Va3`w!֓{@49 XOZ?PS $N[R!\Gc^EՂ1 ("\x x</"_hAB{ɍ:J)`fxvnnnΝKV>_J9=77w3^O6m4hN sKb+@NǏ쿣3ApE@!TAjxrA; aEsNE; Rk@;.Y&0 6b s\A/zKf]T*U.K9н]n3hwo)RRd;K|vhԋq$N9O'_jF G[!<-i!Eɭ/u!9q\dq=(ǁx%.ܲR ۫{Xfa ߃\iOߋ^hF`8`h~VBHsΨWҁ*vJi/q\DVƴ12`A8?(E9![Hk1:1ZM/pNj_xf:8y4} c~_-(Bfg]3(DD~YIMzA@;w{ZYE*c2)Q hX^ayy_|/ˋ-Q΍7o8&RTepdA ÉTNRsoQ `K$Yn( t%H~4Kq`GW+4?ltl''7 F&*!ȅgEacaw v\z9BuZTE%Sc%<1,jJbbCWcK6 0"(j.ne8r شjtZ5шIp עz#LiQZ" O=dB8 'M|$JOq2DKHC,} ܚTL?49q}tGG=oJ4:Q1kOJQ&_*ckn^9W/˨a\N@ L^e~Χ~1qȕ␒* aX^\EF5As8A8 |j{^<ڵSJqh4U^ԚxYܲs ~B~7`0tLOu<cG)&u/a$6^sٍF4I%4a-`dbAJ $ JkT&lnZ|K^YQ_r ,++cCn!vK, ۥ5),CIB="eu](ǃk*,G߇11z2|K_n6lXI!,*jvas@iސGAeTkhzINJ*aU NcB,O*Ǥ30۔4&^,iKNFZg5.?>;H=DIZu,Ce!;-wҬ$>y(N?+^Qc< ^ݞrŵB ",z <16EZwB<׃"9?Īnڼ^Z-##jAH ܤ^=X7>VN|y~5\#o|?@f!ߝ_pwdwy'oCw\o|I?a0cApnh l(e}Ziԛ h!wW@)0$=rظ~'&P7yd^1)K=Yz&?%PɺI2 DAş+Aӭ^cׇ j f:˃5\ǃwp9giѬq9yDAoqyNcȻ1+HkߐcҔa9B~"PcH(jn*M za^#ρzV>~ Wk}T+.t,*jP0UI'ܒxëC =ׁjI+p>=J4Q0d# *,=hAw$-Tx,HI|,!Kt1| $v<^߉\O?$ eyQخ )J'C؇TyR,u xՊATgKq\4F%=xO<ӰV]gZͪꎴEgt ^иl6?2;x?C]s͜زeݻw?q]wޙݗ/_z?EQGmt9 blnP7?? ߽NYZ"?wILbZ-aشi3f&gQh]b"+(jE C/0dE@ec ੧‰GpK^v{ SS7{KqF\K2x>v*uB:pBG] {A)Gta64}) x)¶SC'ڦ .I> ufCcJ&)0D>?LD}?z;xp/7ߊE\xY ,ق;n(A#j%XSŝ¶]cG2HTs|/Rp N=U:Uh'PmPk6wv՚):p]w8gLK9˃7?1=9舍XDQ (8Vx ,9n{Mؿ{' ΣV |Xst&p/g p6Pw4JjdoܬpIQ6vڜ@KwMɷt"pFjatdZ ʊ;EvZ[ zJí:A|-Rs\ƋO-C d(ưIrC+iD$,LJ]dzYѦmĈy(Ys=4#8y .^/BH8ZfNJbnOtl$&G;rzjLFVmuG&}ų577;NOswq^޽fv'Ϟ38 >D!Pq1? \󂛰g5PZ]A"ٯ݈TuhM,AI Ҏd'WFb>_|7Sp Vߣ)ƅHMY+0Y8 RR(ZJA:tkEE:s3K#Dkx*_5q]E ~6Ej%1=T ܘ!@3$5slELv'Gq I3.eA,M%1QU=\rD,MN>T iͽB1`\WKAƜ"RXҖՏc,,9mxG}I Eߧ_\\##裏`o~)52A^}o?<=3gZ6wݡϾ?y)kyG''F>b{V|@KȀHI7{vS[q$sW eA"zӌ2pرg Bl*!6lJ *U:Uj!&*)HGq=# &c{p=>؎qUGJ< ϜĴr|rlLOͮF+k5[v;w؏'i֗(w}ooa#gYnnjP)+tΑ% >ukc酷aMvGx>))5V2FV1/ZKNKN=d>hl+2<-#[Bt Ngq]ހd{4O|ffGhWLɩ5ֺZѿ׽n Թ;+v%߶u?e_eW 莍ύvgNa(BqOũ:i6QRniĖ[+0(Xk-)R攜 JdXA@q du]D̳ι$(f9W `c3<"R:.A K=uS|b /懢RzoAAe9dR>`JN{>Id uaF1$BEȍA$HW|4)2Ckf?cR zY! cqhIH1"\EruD.{XkEXZ#3 RX\X#>G[6̚n֬VhO[~׭ߵ :zלw?׏^ Xy x'2kr=ii}8a8~Zi9sBilp Q>_AX,"XkUo2*-rv.`&FM2`3R*)+\NJQ:ZGjrxGJA׃B5OUaVXKgP*% ("D1@N =xP&N.,$YtMMG^({|Ĉ>[İ E`:~D4%N_%t-1Dq]YS8*ZIR^ߊCCI,=b'*ߣ}(#1-J1Ue",BӪT8l`2K.bC'19|jWEE;򿚣wqw8SS_Nd*VFs7{ ?41>8z.hnRdĹq}*2BAв 5tePBkIs~a-`,Ax'+)(NJ@s.r68)FpK6Yz!Yw45 Ae &͐ 4ARy#4I@{a@qL0Ʋ:C36J iBBh*׵*L9J . ހ8ފQ"*P*I R./W9.jQ{\EG8rvډǁ XiƼb|tDMF~ihbf?~]wݕ !w/w+`P]tGdg06߈G߉~`Uוvc$j J+xXfn. c՞捨̹1\m*NKT y1H q*3ii1>=JyoxիA\w%\={,ԛ_x?vGDQ+a6m۶97{~Wj:}酑= !K{ /bu8;Oλ6cØ}ЎF6f5ߌ c:[Ni%Hw4u6(F7+QVUpwus_ei6`EZ `rb;1\1\%+#THKzFݧ>ijRn"Fӿ% \,TnE-/ h'Ј5ހe 3=2>[k]g8|b.pHYyKehIH(pvwtwO[ RDaĐќ8"AU3yf JuVfWAw!> dҲ`FC,bDiFU->m.kXZX=ȎO|j3mIBZ?4>0z^u.nn.λӻ|럽UO؏|ϯygM/ 4FaXb{8w֮]ukWcؼj:twgW$ւ&Vev_ss3[Ɂ8|ϧRi\ F AŶRεCp(Q鏢44_SN- ,_@yH9>fg|F=Ib$LI#nq fH&RɗKr!YR sW[1$J#٠!`_ Hb I3Azіd-W r|痠M 5 XPh7jp*Ii| SıhwUW6c-g5=:Zp37|-[ح[m0xKKo'OrZ!l%lx>u?bvfZ6Avylie ,8"Eiͬúqq.\yǠNՠ P%߁,0 PecjI;.,7= S7T(S{82 Hbs0IP&^&3xߤISJkԀ(:SlJёJp;s#eE$)$"abd AAKڰ]D>:];w̘쨵kj۫[+_ύgMװw z=Fj4hIҮ]>QbӦ-d}Cä]qe)ީ! )0W֊C- yp|u]gq[n]ׅ L}c\-  @PJl8ް5X$&5]lDazYaii)w죘 $^B,E,K#QDpI, $l3 rՖ@Ԧ1 D6(d?s|˖xm/.# [ajZ ˩z{CGGv.캙i3Ҫٵ WM֭?|?ÒY/`nnNe WS'#~O`]+yz|ӟ*T-vu4☚JGH"-͈## RZt \._v(lUp=ts M-%5 1O~Rb^zMXe,#(i4PW`-V}4o{ܹW=.#UN fZd0anʀ"d(/|?4qv55p ?:pJ %|߅l&qx.b\WZ"]-I=u-{AP'$=IY x'S7B$I0**T}Y(1~!K=I#2QPR"I3I<=R 9Ph)69lRD+p8Cd&\f/%d D_Zr}t[MT^ DgiԩعIç>Xf4j;2֮GJi'ֹ9w|جo/A4*Q 괛T"L&!$~FjZ.),`3rS*$t G/Z К~CR2D rr!R9N)(_y)X D? (Dfl` ?rˍ:^iPåtW+ ,9ЎLkdƠ׏؋jFxҞ=σT 8ryAxI MUk4_NOUfnn.{JUײw=pv=)UFQl u~k֮Sac51~bcVH7Hʙ[j pxAV+5KQYF iJV9 4 VbYdX2 /Xd&/4KFRR>BCaDI ICdIkssy[>Y!fSw k9O"ZiAEEf*` Y(U˽>o>o f DӬVĔR},Kqyٷ_>=y٬VY##hvFֺo77n|~F"0v3m %Nbxe6?{V-AK({ VM]kG[zf:hfcc;n;gn}=׏w}+wK]{#kQy$̰azj<,|u/$GSkk1OzKvdǁGR$$# E)oE/~VHCÌ1yN > IJYn$KӔ6SZAQ# )˾&1ҌN4M ˕ҤL!eҢ` 4zE^+t)MQ$WUn Nr+!6(s%ܔE!.\'?I|ϿNYu/pe~#<@٩ѩ,=5=.NEW]KNi=`ulbJĜ 8$)zi%XRГ$ (*O8_{OR2 eX ^9CD*`LBft[qԠ &ﰔHLN(BQ֭81=zǁ2={}|g_t;u]93g}w~_p =QoQ޿d}%+Yx޾¥ܵh%TB w g$OŘ];U 55|߃vT*>$$  We(Tp݀Ky6PL$raK0zzʞ$IlvZ~o'Cwbk75VEONO ԪgZ?|Qci/\_ _鞧}W''&.~(ks)Ȼt"n؀Ν;-ހT|jZb+g^q" /ŧJZ$Is^i1mht# Cs\V4#Dq\{Q2󈸛 5gy>@3;_)DQr_u3V&JT'2* __K޺w߁Kh?8ƺȪ7>F:">T]p bSxl>7/iNIFM5Áif I$Nuh ~0DCQ£:N dXHKI \ڤȳ R۟$a 07˻`,%D1[r$xn%q ! * W*dt<އ<?kld]A fk{w=@VfY_0d|bKW.Șsʒ]#QJW{ťKjj< )y̠t׭uYf36jqrr/TfrxVkj/ _z,ΟG><}FviQTd!&'q%[o}[CT_9h/:*;y,F42yzIiMn)r"q e∹E}(J]0)L?˨o!&@$"F)ɔ#VqvR4=s#@`t6dH1j~@y̍Kq~sra/=,e FF,r~?Dfry$܏4Kxfe (kL?ye)SwsdSs<"Tv%"V-$VXp5m(\DRᙼ1q #iB}H0q'( 9d#A 8FtI H>ahO >}>Is%(.~ܓ~ <4u\k54<~乗/xrx|.L13RT_E{dvF3>{?W}(?ȁ]O^pO:hRbrfAZAPx~n`K˶SʬifTn49cD1Ip0DgZ=?I)򼷼LӜ>/uE.e~AWCG%@H ͂d!%ՠ|/11fюTӽ^2Bݖ2܀IM*hy.$3go|hd쟽޸xO#5uVsP?z;Y`0g_B!B"L9 莌ؽ{^W| ( n[6YJ\<ϑ rC0\SoSQ" c1~  KZϤHl!]Pa #qP[prVV8 ){eA'_spp‰ǿ,+8NxZ6|FrK!QKieܿO>_;:05lzl|7p_wQ&w?T}}~>p;;{>a=ɱTUl)(B30N05Ż.hl=زb/6d&Gf-$Cddd4M'2NGz~Y'qD("}F48)}7aLCoG{,$*_Y@|iKFB1']tEw_ =R |E$GkR/ %fT*T*5tR }LvE=1Ab%-VMp ^{iF<ː9R!R&d'8FE\G0'Q8?#ȑf 80לF &!-D#KՀ,'1饄jɛϡD㡔"9TL1qB*Z .JCyxNē;w>qu:V4:[??|xSx5*n=́Ν;CI?^ɤ]Rt8ªi~շw؝0zSc<&)L#r~iDG'?p~Y!#f& CqHs}dI£&GZK.xjSRaF#4F0)\Ew{آ̧1RQxzj2xj)%pa<عs7|]eGmU3hqstc-o\^e v8__ /|t9xh6iAG;h]Ce=f}b/+Gy5~A K1óIcyyBvHM'nRNDH8 m2ID~|cJgd^!=9'2ٸH99<|ߦ]3|Jw||G,4EŐ**v8Q*18<} ܽ|iSm;隊'HGMϠj}xȡ6W rann3Sk`=NS]^߃y(!k^#۷㻾?Q5jI #&y$IAFDϘ86-~r"Nf`MF@0,9g  w8Kp GqX,!(>GHqh() ^u]Bk8'wgnd>jqjnnN0;\_077Eo};?pwPvʢvZhj]ŕaaF'PVmx7`aq,qay_Va$R%V+f^  K.6,.> R!-zlaAc-R_rf,;VxG HA~! ˭`dd8ì= r|=T!!pe :~@Yg*N]MON"wvw##Moz?x={Mo5x%0:67FG%DawZK˗133/| nlj /_aCN' [s-W؋gRQ0pxyNf/]}S tމR=U|!I+%ѐ&-za(B֠I@v,ùSgϓ1{ LԘF^hɪU?:{eSoxyS }+WHf vX,/-cfv;ƿǥ%r6 C3L3 Db4e^F|4N`sm&2&_%s@ f9\[P8S5*f e1ʓ )X`u5<-ݮR.ze; xQq7m,p=G//$؎nh Z0~*<#M[4M?MR>ax9!9?찈|5P<5 vyhI|nvڦQdݑ1Tk]6_?txχ*s~j۶m7|galVVJJSSK*0czb.^rl!NƊ$\73qo>5lSr rZ*h| W`S_'XS v,$MMN+DžRl= }a<@Z7jA>֪Im4u:77\z.+r_|M澇NzHvD<#q1T֫x/v}y~i ̻;}N?a^i _, 3_x0]NDր:HSri>u7 sNIaISAYg8vRBp_HASE0hwy! 59?v $8Te x'@+VPJbyyC?ݻ@uZ@Z5fj{SVoy/}x--[ep}9Ah~c> _~vؓ{l""(G KetF:H p=ʰܒ(GZ 1!cY)a AM1bEs))ʅhT@frYDQGp^xGR E{>?-YY7vV{˗_fvypcć#!ofȳSS8.^@kJ 6'60PlQJgxCH( <20YWrG_JxFQ(!RE[Z9p]ukxX5SK^|C^yȘ՛4鷼;p۾-ߺu백7\A/mヌL?ѣ&0YNj{š9WJJ ,ئҪ Ѐ [& @aksx'xdv4 NŮݻ{| 7c=VUl9򑱱ɟ|?,kW/ /}Q{,T7jUk5[t]OX C, YnSe\ż}UYޫ%8Ed9PV*DT8?Dg9Gێ]W_w5: |z8nLUwo_np=Wy~=j֭aÇժZj(M誫ƾvų]\ynlR"sDi(K{pPA9<)o>|CLq[VTFZuW=[{w3igzmwI}烏$C٨a_{=N>#T/vi&r~ >@0I\xCc;$$\i1N<;Y{jԭ:^sX*'F;S7glXi=pc۶mпx;׭Eb Tj|X#C4-Jas`ˮ"a:RHhDžd.G148jp]=2x?+0Gqy0Ҩ[uJ+lv3>7'GZivZ+~<&}CmyZiV&'bC s9@zJsOK,1 y*6ܕe],.\ ._8sRC@ik-$1[tV@* yrc9r|s8y S3W6+lYIڱVgO֮gE~?\_oy\E4Fc~nްQv4j%LO`9 ]~/inC @Кdsْ!Mb? v~ >0`݆)̮A%pluF6uhvmoB$u_Wssst׿  l#f k7u裨J)d6ZZ1S}j582>yD*5ՊoV鎌VkkO-o?)?\_y~=j۶S[n4GQw֟Ylxxa<{9^'^ؗN;KZRZu\,.-gأcbbliQF)k+uhm[ặ lʷ~;F\_P'=O>zj2e8}48F^Ņp~Q ۀJCHksfl8Aմ[&V`xouw}߹?e ҍW_Dqe;wɓ>&<&K<}g;z|~<?\j`9}{=yG'ͩhŅG[R<橪jbtd nh6?86xOo_=#p}3Ka-SSx{YwG>gqO6UMrG)ѵFx5W]{~|177sss֭[qk*b_w™w^:6q4Mnh[6JݶGWv۶m~P\~/Y*_vf~;⿛Ph_?՛:4k͏_sbhlݺ-P3\d={+߮V\ZA>5\LZu{J NOIENDB`OSCAR-code-v1.3.1/oscar/icons/sadface.png000066400000000000000000001273721417327530600200230ustar00rootroot00000000000000PNG  IHDRzz cHRMz&u0`:pQ<bKGD pHYs+IDATx}wE[wv6'dE(AAĜK0{b|FĄ(A$約yLٙPU]vy?꧟7T5CV_n@Vd&+YJ$ 7YJV2$YJV!MV IndHpd%Cd%+,d%+Yɐd&+YJ$ 7YJV2$YJV!MV IndH,]݀to߾}߾}knnnooD"~?AI0I8X, bEQEfXfs.GUUU Ç?~g]UUՍJ% 7ǃ~~ڵk:E)Q}zlcKps,ɾ}z%Kر0D=z?={feKp=ܪU~ ,_rss;::DQ4hЅ^_dᦛJKK //n޼9EQLE .(B( cE7}*ް$"$ I8(@$}^:k#FHճbBpӽEOޞUbYX6*0q{Pj H HK@rXÂDlAE*@s@* a ƒmA Z.tmV$ HD(7[zH4srr^6!;mڴ[oOGQJ7,t | ,X`={#q{ Xm BkS 9DbyXKs,.,G,+sX#$F꽑O翵piFr pK@:n HՋH:S!t:>/ÇO~d+c駟WW("P`XPC/%$RdZl;6:7*t+?"m]MM]-APecnEtiRoco;t:]|?bMH]]ݽ+-kPH#KE!(=͡'ZYJRN$Jfb svD5v6w47~ Rm&BhQǍw}M>=ˊIMFcow}6m2v#$MS`ڃ tV81faS9dX#8>j+V}{=s- B6mF_}άLJn2$>xM;m"Waȱ|Wn /M<>Z A*E[IJaZu8o`mzF8~|ѩHn.G?[oen=dCӑkvv6s΀]ENRXQpA̟). jF>`˃\Â1"€{i$$ 7i۷XlN.w{8.UsL'0 1l@n"Xs@F>^Vh=vq[Õ!瞛o9B"M7|_jÆ F :I=`o9rN*B #hL0YSK}]]zur,;N4f[o UpbYt;w*%"8Ge}mW s_17_C '+sCS0k m%hԼp8n?//K{긒,ܤLniǎJϷNhK+ήr]?=SKC]AT$ 7[tx z4#Iz[цe_hHnR 7ok{"Q:S|}'rpECrJeÏ/Xq/x'qym-m5OvB999ӟnM'IMRrnaҥX`fszq_;=DQх.A~SFyasC::o k:% 7&헿嫯*ISv`ur/O:6an̈́f,)A/8z2/Z0/$rkX _6mZJ"Y1,p{? 9*-/房W0gK3.c݃`11z0&QYID>A;xD8jԨ_|1ͅQ1y7_q?1жdL)j eD$ Q`":S'B ]Ӽ6ಡm@3|WJJJ яWp+ {8z-_u\bHg8 p»ؔo0#ɆwΦck[>ɳ =U?V;4(C݄ |vDLE BiE_Ҁ4}kQf U Kb\veO=TNNN,Pᢋ.Z|9,R3,YboJKu"d|zk7U*K_h@燚W4nD0426gK[d,=]w=E` P`.6cAL)Kn utcȈF )0MfDQL67`XvW uH? c9-˘1cz޽{Z<8k֬۷TZzYҽJc p0̱>~#;L! w$vwߝ DKn6xBOp_Wp'63 4\^*&g= b5HN(ŒCDl@'/ljky /G8j8p%KǹdSf͚ųeDoeV;.\kx?>1PV?gEkϷkvoL{ W^ynMraA KcШ@(Qd*&νo8py~ňaQTH7#G;º;&[,Sx;xދ.?91غxEןX AeB232d͑;2݇4) :#%2# mjrZPG;gyf:zX5ܬ_~ƌMMMly6ᲡOy'r<8,4m*!W d :ЂDh{w_6|}aGj^?jx{toj/͂uf}c{,1'E x1G,yHelP>b=Qň\@ :*bV>@-.X,{^d 't{ciӦ]!?0=ΟJAbOr#:c:Cs97$'}³y>pxhnn CO8'xOcwK͛Lvzo*h =?|di,D=h E*O~*ȫz񂛌Ӗfհ M°hŵ^=tv5\`QLsǎ瞻U]TcA׍`Oyr:aALIm14<~Taq8 L9c6Eـ![yAGQNsA|DzhJ=J-ˉ'XFngg;>SJ? I 4I/h~^pG1@GaVJn?C>S\\|ÇcR~p>mڴuֱ:af9cWna EN;qB0'9?=Xi!ZôHiNGqI톺v… /${7w>y'oru#c$Q{ǁ#ޏd*t_:F)kD,Yi?lr#}ʔzwn~?C 9ӓ_~kFAd0ȰwCCq#.23)O?FvVTK9JA:+AV^=`#}lq7'/"#O/e=[~J؆X0/Oh l@8\v JR(r:us&G";IdH__aM0ۃ,bŊ#Gn)D"s=wŌr2ل罷 { ~XC' Rch8-rLF (QŦA@ (V2CMpGe ,/.%d¥@< GcKx?2tCuweCOt1Im IF_.zwGEN`N/C[C~J̱6mL:*$_Emĉ4ɷ ޅ=c ;TmK9 Lt (, !C† ;KB?!?ymk)[ʠ=%qu2;P9.S EYJQD~-M"l'*rjh}vI2hhx-[v± $SN}G&N_=K_65jE#5ɅVSQdL怐M?@}6'nru?BnUNN΢E=TPn}(pӨ{=_3Jc. j^Sq INӵy TIGuQ qZ=M Tw]&nnvyz_ch:oRprb tI&|`BKREцmR8kvrP=nݚ?,n 76m=z4 r>x: d0m@ٿ@:FN3@b:IzmR8>jq7Deqꩧ~b52utt5ϞQbL%CsF8b9I>%IMP$B}GPV\BP  9!E9 gAsO*eUW)ePD.K5k91X" 8+dP5upSSSccY[JLqS=s-e9r/{( b܃y+F`@27qcuKkِ) (S#_kRR(S_0;-kQXE*ՑP!`x`.MS鰐{R=n 7׬YCȴbAR54pBH h,X0q8d64.mY"F*&3 8Qtg !TQQcmdl(njkkļu/.F-d M옠Y0#@S. s` YVLa mq&cE,,suCA[*agQvh(# 1)UBvd+f!qT&:Sk[q ,JY*Vs w^%/x>z+utyG6l@<5Ծ!xV¨da<;8P5FDyfё(̐~bfTj ]u8'4^y1С㉋峂/FwR"KgLgf1SK>IĹ> @2U]]]UUӂ.nG^W4O3*A灞gԉxvh?)L!uIǐN#\hnk!֨2-8 ݊[:4#m%IXS[1 #д ׌\O>yٲe]'N`73g$b dD fvQD?|Vd ÊK\P(gaYTsi9Q4CZѰ%QPy5Me.D̥WZBcCICmgb 0t+ԕH]Ѻ ?yo7/vg̘oK駟|rG}{Կ^VwAaac)-YgebŞ{bAوũ% ZKkЕj?t>'[d+{> SCHY MQ)sA߿cǎ_~95']7o뉧.xӋ;+{ " DVƔXU=Cy-my>A?1p!UM I!oTAr{u;QH8V1{kXhLš@,(O1s/OUCByv0ZuD_͛^Luuuc.?uvpy|R2V\B^9e{Z%5QٶL׏?N9X b:LtA7 S`a yW2IwR#0;۷믿-3 7mmm?pg@Q7O/V?*4RQ2&^h*=ȦR?W~B@@2"aH7{Şd޹=G#|Mއ$ Ʃ4=6J9yT9o6@Y)޻El+]͖sz="mKO, mCb@ʰ&bĀMO=*=^J\2Ԁ"P(;hkۃ৏ 6iJ`z8LcѝI_%Cpe˖KO?(}\!J6F%kBfuHEbaR䀡Hq?}<=tHF }yA"zQ)^\Pվ}O-&159B0\awtt}j)!꫉kjTd$c Dl{/\s2/Gח*- #N=?DQlKGUS.~@@НfJЉʧ)vH{coM5R W10(ST]͠ 5JsN466u]P277x#1}d}p l n`:$ŘTo=.\ sB*-nYN`Qpo;fn.tcP-E421Ф$2(jD#I$IO>d;?pt;vOaQb!,I4w7  XWN [bgw ~D#555w_GG 0$p~Z=N)RtMbKF;y5zo|vM**r}( ʝA l q *G&8/--,))1lCP7OHqF&RΠYf˕n^ovIɧpǥX<Y:S7Sl c [fv@mD>d\Z>s*U4i@t΀+͂7~;*v{+2/hB?ʈzsL>` ԆO>IQJ'0N&"3ւ* Zj%߿m۶իW7>7$[n?1ԐCʼʝŁ3C~3 6.x$ǁ:R xx(4g7 \+ۚo-w 85 siXlc+UH$"ߗ*yeվ[;Nd8ʹf`sBgfj^#?O>CƱx?B0O0ݧ@F Y6+ 0| U yEUgf%&߲' N[[ۗ_~k׮w{cOM,qzˆ96H&deEp5 gn28%faq*2"XgO -%4CoQ|U$rݏO2aN+I_9tТES>o7 P.6d1j0¿u@0(u?( A^,FAJc-6j|ocí*NE"mF MkV|WM]]ƍy#>9_߶zI#Pw}Lt5Mm+ԝi q,qbWi[8f7a#J年?z6^'"c$GygR){˱%tp}~ B3 Q45'Y+&ipu?L-_\W;Bap~& 4StJG@@UI,< ӒJSEN{mhSgBVez R U WeBNu̔|GiF{j8%*V>/JEФ@]P2ע@h2 BR>rH;M# cNF*JPJ!֘cap Ԅ`HU wƏʨHS.5.8Uԙ3(aÀb)CpZN?U=2z|6"z_1j&x)d^QBMN^"&Ǭb&SnT[P (R]2CJw`P?!kvb]NioհJ\Y#ZZZ}T|'D&t:dh(]hԄwa?jQgN 'ZgTX,[D,(?sxృ-,bT;2 (Sρ5+We3~^n7n]K*SYC2g}P:cHycA#M1ݘՑcmD%5hz-[Qfzg>MՙP#(b…)MSSӞ={q]nVa>PO[铌n;|:yKr!/-htz".x9]6'CeBIл5ˤQj)F\<hT*DKpR1.2{0~GIS0/"1"sĸY]J=+ajq=g5M /FGk.vPE+C`@>h@a>54ڥufi._:] S)5l)i(--d&l޼Y^Hgr\F}B6֙.X,S6灟9GpjiFǟnt=.tovuZLe3f4'8T$C?UoU@ԧ7$;}-/ƣޡڱg]F3 lzs aB;B#-OEJ 6t+W4$3Y t0i Z0bꤢd^9ٞE/_GēZZ&"(j'_"gb E07*?ouZҠC̘l9pX|k祕r_J) -Ɏt]LCoo=X#]=#YSA34& Ig4AÊ?jCWc ]WN[NͰ|mgMe~>}mC=r=UqT7 F?a=|G %/"Ɩ,(lUU藟¼nI6l`z,?"H[[[V؅o5)6('g~,U)cK0!kCYgؒ8SNW=:T?h֜)}F+γ;Ò<9}eņ̩(-VQ*NLM Bl"ner\9DAms۾}1C]|H nvءJ I#$j2A\z9JV xs`y;O=QL4VsP"8(u [K#d#S0>nTR1ytϜSXxG t5xeITtT?IY{[[ۊ+E;w~msF؊+.pCz9w10ik&f8,Ho]yE@#:BL9TMRѢ?M7[ƈ.Si=ٺu6T*4GpVT[4&Sz-N ÇnhۅD6ox'FlZ,E+n`ʪG Z =YVO58plwGv5a=t@t]yl¢,KqKfY~w+Z& Nv͒,0L,%Iڽ{7p<|tZON.)`EN9F?.Xo;A'/t̝nQzmVh1~\}**ޞg (4pp_Ӄ[> "ldl^llFLtA 1hY\҂"=!4 fP$ (Bc95ktiiΝ;h DvKq'qvB#4'c޽`D Mmm6)y#} :eT=s/)г8ed4% n!g}Hc"@$t5f6ƚјxz8a6bqdv9umbyXN#Ewl>sLYIJzy5_%'cj6O^p"nft5)F}!Zbz,g;o6*A|>"`ax$}gb ~E8RioHɂMӛ%PzT2>Yʌʠ8`nQDGswAEI ) h /~}n1`A.f%ud1jg Y[?#^JH8 8ppM =0x "aA !Al(3D "JJzVBe/ *{A7u*76EUڵ+//cp#b8V%-w;867N)_Hmm]uat^؁kj ` k.+A 1 m%톲rԫ8'J[A\IBL 4('' ӥC7D5>R[i6\][ZM3- 46Bc#޶>#$ "TTИh$aPtK*"MD"nVcp#[ 1hfJHl؄?,;i~܌5(C$<KG v;(GÇSy9/.Iݬl6"&$paNꦝfDLED0}9Ȟw 烽ҭwи1si-.&`?iF"mKL@ Sɕ Jemշ#}ٶSRkM0Fgx璀Beehҩ¼c| 0@ˏR Q44WP!ʔZ:q%T7mBh7RcXG05".ϵ /AjSNuɀev=7 .t}q]mS|i6F~ D_tcR4LO\=ޑUFIuT"AH#Eû#d$Gd6挙KҌ,r C/޽_:~]!Qc+g_ ѬoЏZۇEkw:Pvpan:*p2f|1Ҍ]?<aͰ` -mo-^nB[^n|}0wb{&MF,dZ cpdL.;eBacxИ]25J$52|Fo bi% sr1(Qn(i5H: *~  iG_A]=$JW/WK__ml5ǡ";<6]䵗j9!v;Bmc솶>eE w3"#eI3 yFIk+Tg`̎߾OcKN1!F ;Np8\TT:c즸.{#ڬ}6e$<0W,3O ӓGc_kӇzo34E QDy30??m~ .+Hvgukg/ǩbyP rCYxJKK14h1&ZiiZs\ kfnP$"=9ymJc%P}>A&MMMkA6&yU0QQdUhy,{Yթ[K( Eν{) Ɉ_$D8l4*bzHul!?[\urđG Gi?knO}Kh ~Ȕ}珿!%hgΒDŀ܈H\ Iq{2!hiG<7GUQw()bd)N5^n#CxNE!]X7bסÇ}x^w?޵bM!X/%w+= ! x ,@O/Ro[-FIJ^ wídӯzbbN9% p8n" >#WѤlQ$Azxؓ5V<9Xx8e0I s?s%<]YK7p(84+B:0Rw9K7U3U&Js/txuP_!υ*J٢X@_p__u>kp8xXE4ɃxCаaÌv0a1=*hd(I*uƌ4b6/{K)U.\2v(ˑziqϻ~uwyiuHhyO/㱏ş^#HW7VR ޑV ^c_rO.n-p}¼N DǿSՄY81c*޳g''T:=$Z1ZL2!fKV2 ͇5D|@oqxnkg9ӭ6;IXGVWFZF #n*b)5c*Pr.JQ~rB%?"; [Sis9еg::!VR~eˌcOj4bjr 0@6h*H05L C͒D<~ioS5ê\}_0O|!A"!3<&aA6Aq^@! 6!E" G5:ѪvĿ;lgٿy 9lԖ__c&s6ΠMjHp mmmF)S@~'>ڱy%ygR~C0` B0ve b;~3rϒtPdH&"W$4J*Jz&-\7È%ش,vk8feI!m p u~";4%5$6H7γ]w<\P>6XT.֩~nI,#!VdX=-N= int˥-'ΊbPfi_^089?> 3/f>ZxVu̘1&:$L6^Wܧ!BΩ3Os#Il8U#ug8V?,E/0f"6d*H ^ZU%XM4lKv Rx P&,p9^O=JbjƐfiRSK|R,ݜa[~ĉ&zƌvA$8}+֭4ʪwc" m&‹/{}LUJmH2n_S$Q20& cZPWw>m(gDXUMNdP(O Q8o~x_ uMR2!.ȵyLpN &V5IӔ%Xgu T`o_j7'&v{0v{9D~֯s+m Tg`Ć*>ϟi}+͍>R Zg1~gBBЬRD(18JaN4?`. J2VCk"cFWbxϛƁy YD>clڐ, yތQvD@赈>)6d*]KIya\ڛ?17n4'MlG{ZBw8`@)PĤ*"Vi)>Ŷhdp˚ .=. tMš DM6&`B$wR5 %zdV&/z Qzr+UXX# L*&(?G vq_HX܂gpIUW]~A+Owۅ(ͮ"c=Ԩ!,{ZU?[ͳH{BzRi FjOZt^#vLQM#P@S1Z=J8ECya3:/ *Dq$EtFJMxCh Í$I---_)S8oY+I͜9s7Z<ů1%y{NGڤ`@HF7@c֖Uhfc uO<%h@kqǐ\5)ϪBe]݂;'zы{YV泜n&O7 ͛R!\|t!ioKޓO>|ԨQjp +s-_d~1@֘ȃІc.Ϸ݊fly8_өjډqT8<63x3XyhhBXs+$#E{.wv{^ ORXpǢPDRguBLq|vUD[gΝ+*E  ho="V'Ԩ"q@KSߊ$,ˣ8>{IjGa0]fcYF@3֤Li_2'pOm"s˼IvzV?Ԗ͝;Wb~CTʴUӾH܉VH[@Y y5)Ҵ͵hO,"WP,ĖJN%V*(wRU# ɦ7QDW0fMAˑL6ZÙ*EY KpO7t! pYSCYJe FnC_}{(-- 555NJ즤LDײ70^`D Hp3-B.[<ף׹*%w#=ey"lj'=P++d0sip TXC50!'Ê^u/uqۭя;@P58֜$~{#F83Hn&8E=}OCa0ݶeNf[WNq*óB6ȋPw!mjEx +Uq}:br/ tXzF#&d9o/p;{ߜ#͑gy5F' 7T;r\H8>s 9Iw#'zOl!xOQMk||K`)BlᲉ>*+P 9֎Sv@"8*.xhTo%Cg !zXmL9TPC F k:Go uRTIa4Զ"5V.;˻mԉyE_j&|vo޼9I nFAܵ5 joe?O,ojQc+w>Cu o$Hq1;Z @Bo Lx5İ/x?w\"v5JRhCF.sqK` NmaH1yt,]" z?G2dlbHR7@ܯG-:Q5&O|I6g וֹ,wnB7UR^8 }\pڀ*?3/<^p6H%P1C ѡrJr14U-*,_[\zYY6,Օ7*{n~cu[mRqiL Ru#9O` 6OtgQ[QeGXITeh#>F+Z8E X!IXXcnv*WW!6*ko\Cֳ8:}!m;6m4o)˯.炙wC+Bݱ,@N\a_KzFԷX|(ֳP+Oĉsss IKj!4{l)nS^R>C|GwHd7E.>Ź.Ư 6D4%L̠zsQU= M9esMAtVzQҌekO6cQyN|'q"Z3uMR5X'6e?H$ruA*$5p?Q\SfF9r?D~Mly\31g໷ ʂ WXYthhN2Kd&>G&be0&S X,8Nx:6T +j_4 4rvt(++[f͵^ ͐!C*++>y׶ǀ'\qgiR^Bh5r>E= E*"O==%H59i>ƢBc`.Ш|m{CEݨOvR!ڪJ-Z J^ڤOr'2Mf~C3f̤I!2o^뉼MN/}<]›`lˢ ޻# H;DIq%^ eA&Rx?:S m%.':uQuB-i͢F1yA"\Juf5t踃)R(}Ầb"а'!Yu3#SF@yNSx%ٓV *j/$'Oӧϰa ub^rUԏ1Ц)!-'h4dGSׂ+# _xB:DчlA OiJqL{ͫ*aHuվ`?5.'MN>5k\z饐RI=ܔ?x/7tb[Yy0d DrdmI[9,Н(5bҚ,"ǃ,U W/{UW1 ;*\(q۷W_gtْz'xHpjOy(wz 5"`#(2ݻ:3٘qT1iDS1ܡ'28e 0K:XcfMzX;ss<62$f%H?殯¤&zW_z뭐jI ܌5v|׬_cA :"kR85Ħh?ST|G=>Aa42? !5XChϭ뢂wc<\f^Gu nj-&F}gd$-p?0#~#6.HHJWF6KB`{^X`WWHhCyicnt,/x:HCXs˱NXhRJ!%sbn!L(y.1 &M>*Za‹vwuA7>m/ v7} M^ ޿DZ gm.bIDAT,Hj0樮.K'"2T:$/my@+e9RDr}|X_J\KUVUƥ% ̙3kjj.bHnNM7D t|_ƴoxĠdX~Y\\<~'*זR$'b_)tL);0 .ؐ\$e=ej%э s3j(5/b C`ʞ ms9|vJlI/_DU'>^[2 +aXs&dS2ZkPizΝCqfAĈ87!‹YtTql}W))?!jyn5Qb d/))xߓ+v}ܖo셢T *%!㈚H)E]Ul#/ x@U>4Ha|q؈x!WDlšlıT=m~X#78Zҡqbe#)'q! ʪ04q'F̙3gΝ[nM&`7p8? N/R獼X&x3PM@v͌r+輗;ɜ+M9i|ijDiB90%dc}of`ȋk$}4^7C?Y\CY]]k 3"HYYYS$cAe9`C/q 4ađ!bv9=Q L<9`&9HF0Xˈb4jtS\XckXQ6&:Gsԝ$1l 8̝;vdY v(>c@7?1շ4m`WgLA2629D2HD^`^pb))Acma8z25 ڢ#kk)Z#kU`=P2nҷo߃KbP0>=\5D0#+9&e&9gPffQs&QQ*z6)%718 NY([RQb5\fmQD^ B!v_x5$b7Qy,bUi HyEc K@\]O5ِxnޜ2 *F1/+w97٢w@[CQ)?ef6ޏDj>Ak k5̃x=-TP>!"b a F~VZn$psg5*zZ;TpUH]6e&@ejhP:͉fzLm  ISN9eƌG2Qe l2j(w-̷ }+) $ ̚JZ [ǘۖWFt+ШW@Qtب`ʱ&x?N $T@+nL{$)Q#2O-[2%e70|3u"'粒}V Ky(sj>rS T; X15#NLȪ[;گwޚ5knݚْiv%%%?q!QIXbY`5( 4l@4GStf<ބ>LBw>$CVەth4n8a uPl4وxL9JGst :6/xxMzXs@ʃg^-&!PrT15̤7֤d=!Ok򓶙袋N… СC$. Ee 8&aÈCUs+^Y}Tab"P\QУلpN(AkB9ew̰v-$+PXCʙ  ]A{ިy^zӦM|ÓJe**zӟdZgۃ>lzyMZh# Qa>$rn4gU3/I?}ˮ?zUK+2?Ҡ FuڃʢM";q<%XMͩč|CÚʃoZn 9r۶mije9≥/联["ql"ya/"% +Hm 1bL6 QZ:!49֔<)7Cg@Ѩ0+Q(PCrH"`IoRH!;UO}e~GԆ!nTe9]|L,ҐC0hRf},PNl|D GgUcGNK UR2eenND >5j?ԓٳg_￯0;f)Wҷo楪Fq[}_]Yo]g,ȔoiݽTyڻϑdZp* [B\x|uq#.dbhXӷo-[<kL>/ rZP+;ѰfѫW~嗻@w83nć7I~q ee)DjWHj#e vfoA5$&2kÈubD]ڼ)F\T/+^ frHaTT7C~l*d_.vpuġ+((p8z' I2P(4dȐ{ZgV_ջw~ *a`b>fx_|A{mwtG;w5mBo+zb &u-jp>i('ʀ4TC2 Lc F?&Z<\rɲe˾ o #N 'O֒Zo%uI$jUfmUk mGјB}ij?RkuoDqꅩ*҇@7XApgkd:Tk@a֌;vʕO=T7p^{W_MiE=a$6'ͼP..#P@Gɂq[ N(8~2VT{v0+by@[G8 aX$!Y#ju;<\RR;?@ xYgl~+Ф[ 8!*|qimJ@ڀ'tTetG,{,9T'IQMa AR5 ۾rC+#/۷7n߻F;͛ǍG3 lŠ+z(]?0 &-Ï8c1X Rr1u!8zyFjX2TLPִ@t4ƜfE&k[Xўؗ)!nŊ]%(cpw>iT@U8sBbeqژ@%hówPp'DLI' 3tؘC\ \;X3((:7Dfp嗯Xbʕz"MN=TG<'r- (8/CMc@ zt  U{)Ptˎv7N}5\pʕ+[+$իO?t}, *W錻L"G ]FOb5Z!N=y$jF~ciMp&5miLZtC瞻jժO?Nn#*%K;<9(_0ZŶ/sĆb&[g(+

    䱎5p|<3i ǣⴠWT= GdnGF^MXӵxvy$E&>(" =~5Q5k?c]ve)n7߼ktgߏ/B elvIsZ{RcD&g07b2"Zr@:0b&Z%t3gΚ5k^xc6 n>7o-0*.0srW|i-&;yсHv;-G A9Om6C5 oL0mШ\tE_o9ydxXz3:::y3х} d{!S&^@9`t S}rΑnJΜى2> !ck!te-_?=zt=ߝ8شiԩS[[[ik" zS8t&^-)@gʩmuɌ6$ 4pUHum.~Xbɒ%cCO={L#1(fٴ4pd; uYonck7lذ{xr< xnIæ0~ogs;kot"HfM܇Dy6lBq[7ȶ$-WV1u lciC(ՠE$miEtK.3go7xc8f+V`UZRFMUeDEEThI2֙9h,&~a@4  L"hmzt`ܹ>;o޼$)ݻ/}1> ]xjFAJkCR9AO> -Q!01"kqDNm*$u +LF% >}{G4zhaX^z饾}) 8pڵrB14|u*)86$t B6~w_֗\;S_]lQN+1;jIbUܠvW.ֈxy>|s]lُknrʋ.8pBS1@1 d X0裏=xwt}-kLT;lذ{+'|9n2a„۷ϝ;7//c6yCՋa}g:Tw FaiJZdd$m2.ѱ'~J}Yg`͜9sO>ׯc dٍ\|͛n)Wze|믿Inrȑ_WK.mkk#vQSl%U@.gߥ _Ev;= m t"t8U?UQ0o-mJ@@\riX̙aÆ)S8^7HFpÒO>nx<---v٫o3U:4{z(Zt w,(+5i3(C34Iܿkm>A9jԨ?ӦMKso|H$q!p8[E4V>pG^L ӭ%7$QIYulh&nX6-ܗxʔ)+V}wl6c,poᆝ;w6662d۶mE, je6e_CkL#'ʀyꇖjq< {Ν;{OHVu'#Y/|m566hlc+$}* D]i;<5摞Vff'VNƏ|k[Z{ʥKoG-Y1&l6ʠ N.> Z1=Cc2^ &&0 љX?-m0PUU5v??_Ь% 7fdwug}_zU@%N㋮V wւR H4GCO֥ (((6mڪUO~h>,ܘkݲeK[[[8,%"䴢_).@î(nG>;13ޏ7&Qe kkStN>}̓~衇FսvLJn%K̟?=X4{:)C7 &/c~#q,^-Țg7J#2sj~Sp$iѢEwu$I555@"Bpȼ_.( a5R3 G _e`!_W5<X,guVKKKmmu!C(qJ]Q6fG-C}41dž04"]fXfΜmnX,j Qpbx'}QAm:QE4S O*3',u2&Z-pG_?C3Nn>mڴ暚w_2 Y!Hn"`pѢE<@KKKkk+*.UQSz;|Prܵ)l<};:v5%vO|\%@@/zܛOpD *)a ¦۸〵2nܸ޽{WӧOnj[ Ap9ٹs磏>hѢ#G~s&N q .Msv] ˦9]wvu|w_$3mzS(ZnW\qmUUUe~LKss+s8pj!?& 㶌t?(wZ*$kxI卖pWyз1Wdke#Fٳƍwu]z1+f$ 7]&6mz^xᅼ]EDrGlBUuxmBsroGUMHɻn-\H4'(I_WW{v5k<[9m@YYI'n2dHTp^xaŊGz-GmBee`2Sz8L2xSd5Uo]m`kc@[_$ B˛8q$I}ݔ)S~̞=j&qcYIJp]ȑ#/v]]]Mt߾}_. vi){Y-}, .KS4]yHmᣞQOiEZ/C&<[ n{̘19997oͽk=z$Ŭ$)Yvq>wݱcGYYY]]|vذ=DB$C;"K0NÊ-/92 m޼yȐ!͛;wa2WYѕ,t_9rȇ~{-_<DFd,*G߹siΝ;wSps OeذawuA 8_~@૯*//;w{gtu’,KD6lذlٲoo$>|cKv@t5iҤ &L2夓N췧 1,vZrʕ++''XGz 'TVVZڝ;wwҤI'N0aB6*,'Դjժof[lillp:R__ T핕eeev=7558ptذa'|rbxǁd۷o۶m۷o߲eK.))l$yֆM,///((u8cwȑ6lC:t!C;?Bݻwoݺu߾}G;zhmmmmmmsssaaa^^^V+B( F5(|>Q@E;1D"`ۣ_l6Qv$I`󵵵666ѣW^QF$ 7YP(TWWW[[{z @J mmmHd޽UUU(F?{RPPr\6f\.A KKKzQ^^^VVAG.YJV!zdHpd%Cd%+,d%+Yɐd&+YJ$ 7YJV2$YJV!MV IndHpd%Cd%+,d%+Yɐd&+YJ$6lIENDB`OSCAR-code-v1.3.1/oscar/icons/save.png000066400000000000000000000074231417327530600173650ustar00rootroot00000000000000PNG  IHDR>a cHRMz&u0`:pQ<bKGDIDATx]KFlcs=!IBBP 򒠏]ˆuy,HPcOn-@)UP.`*T Mȣ777lccx>j??aqa_m]9 z=a0@a0 s9 z=@E˲pןz9!mѓD^x|lV,(JQQ5 _[c_l팂 _p_zzEu-O3@2 Tb]d$;ZM| )ʧX 0pesW$/_̦o8S}%5vԖwk bs-р!/x? 5$3BP{<5 EDj-T7ޚ`r-%|7(?/o=ï< s#H_ϙ'xc!ukO+ /xoҥK`C( '3?~M0p>ưHwb2[*b8¶m8}t +|W4<. .`nYc6!s}uh 1,!`oo>޻c"_J$]+,/?''X-@eJt;RuIs)W9vגs$z%òX=%ْ* tϾxmPu#5- l銴YUSRU$ʯ4}0*ӧΜ4f9 V=5Vͱec.z<]Pև(*'Y1$ùӡ3IZ{H7 7t}巟%jH)qzSo3!UPǁ7;Sc,L9cCXêz<$`UqeW+f@:#| dJB8X@7x7ur/POPO!)EfRӠ2^v#c~m%63Q~+ʱ"C~"(>cזϘ|gQ[5DyeJVol QZp(l0RxϴN75A R#OWfQ\CSgIka0 +|8nߢr+׺h {^7@KճMmjPH)aBe:CVpvA} IZ1/0V;ȵvf em%yuQ$B{%`}c6 pR([ͬj( p݄w4!$=: ?{-Sܑ xZ'FG#~ w_oIJJ Ƴ0{<ݏm6͂tVZ GuS;'jeZpM)vwtJk: AgM =5cһ`PSCA9tf\7dP 9oJZmEw]4̠XBw(k(_>vCo{.;.|6J,ol½}ܸ9~sm wrv/P ۸8rd fvssxnnd>۴jᑍ9:l?СxdcӖj*+mɤ)]d2իW7n3qU0mGPY 'غ!:.*M×ޫ3 :F(D!CA! ՜HG#0B hm[>cuh TAIs OKVHsD<]1#X,J< ((p<]484^ O + /v/`ߡz 5Ѭ(.KuaH/Yj#8> +KÒKvO`1F}|BH{Gf_1_#Gs3'(\ Nd]U +AVuO`Ypl iĪw j- puDi!)P[jP"3de=,&Pe%Mѳ#1<ʄYW,!ڳy *)EY;v/\30lmКn<;"*iC"M +X ɥBY/,V뉻!$ç6jybBH%@rjR!t=PVfNZSXG$@f/9 %b[Eǧ#~WX'Z`Y (U6Ec"tލa:.xY|FZ E2ؿwfxˑ_!@pRiY&z Y%EGG׎nݺ|.W+\ww8f~ Ȉ.b1=ݎu"m;i.w8/~r0Xhb0 s9 z=a0@a0 s9 *6hIENDB`OSCAR-code-v1.3.1/oscar/icons/sdcard-lock.png000066400000000000000000002374541417327530600206260ustar00rootroot00000000000000PNG  IHDRx cHRMz&u0`:pQ<bKGD pHYs  IDATxw#י Oaz2'1(b(Q9z6Zl~]k{^߻ǛIi%.%֒vW(J aᐓs@.Tչ*n=<_~Sz~yfEM)!uRʎu]޽{~!r/5&soʱ5N377O<3<Ñ#GxW}a,kn[ᄂ+v(,4\(}Ξ=?7 ~a|ghhA-:F8fM_Nt f6xFsNv{^Ν;s=Ç9rm366F,nY.ߩ}vb?>Xy9<<<ݍ};v,رc;v#Go|{_~)ȅ`7r%nnOY^o'Gv!2<۷orN!yG64B(>3 }}}zVgrr6xgx70 --6v>h\w(q|0M;nz\ץT*iѬ w]z)>ѣG+^m7[tf+r=p=T=55ų>Ç+}FFFzB /g+h[] jlim۶K0==ͳ>3-F_DJQ/AjV3=wzB\K'=|Y-4a۶m~wKa`hh%Qkn_3D< S"m|Ite`ff#GO/|r\#zՈhV.@kd7 h޺UD?ifffxg+^x|gtt' V7@klv6-4 (AP?WEF^7K xZ˲( }  49rz8h˹e+hUYXXXk4 btt" >OWO?O??_tRl[i+&8mU"zizFx ^ h4륙(xgy'>8 'o.ZFi+# ЂAѬ(z׻x׻ŧ?i*O?4/"X b؆ovmZi23@hHE=zu6JlZک@kxةm5z+,RI;O4++* _|xяy饗8vlmhXڽ] lp5^Pf˲8f7Kh z"=ʱcr];~/5@kyh4o^`t>hy靶n"@h xxᇗyCҫD-zf]XlEرc<|c^{%AЋ}w"lE`& FY7G_| eZJWB̭& Ыj4 x Nth`ImRJr۟h4MoL$DJ@ZFDyG?^яyٳkjlv'lNN*1u9t;vXUH$7F ÇW<Q[= ۩Z F>9~aÇy{mK,+׾'=ZGFѬ>} O?tQ`L ֙FsL!ؾ};###bu:*D u/6 FsS/fggygW׿u|߯۶-_+i{Ν;ٿW "`nnGyZSh6Y9WUy >iFTՕ`K,|F3::@zh4`㌳rS@&04F`B8ΆQ*deFyK@\0Xh4-ZgfI62h4C |͜ٶm-4`B.ߕ–qni41ZT7뺛>BcئFh@\ЩHk4[ -46VFh6-6ͮ߈N4) }JFC ʺ䒹B7t4FS3 6I'D'FVG xY2h4&Z-(`}ɁahF ZNx6Vʼ_K;Mdf悪h4ZN(vzd`3e5`يh4MsЬ -4fk@lzi%4-h4 ZN-o WirܹnFh=nf.)]Hhz-4Ch4A @hz-Zd~~^  Ei,--mvs4FSq+-\._s)%iMh4MZl2BPEFhz-ڣ~zm05Fh]EFhNDh4Z6|BFZG-j A\h4Z8h+h4FZǦ"@h4B B@[Ql5BD MFj[MLMM8f7Ch4hf7 d+XMh4MZf7"4F[hh] `0MSh4C q Zd2ٞh4-e m7$`fOFѼ}2Tt49f7_h4hokB@F{h>lEtnFlmhf7WhGhFZRJf7cm_Mf7Uh4.J"@ F=hnBih4}Novz-4}2a>Fh#{A.<F}nFZ^~ lRFVB pBh4Gpgh4ZB 20ʅ6Ah:Zԅnbqh4-ڧ ؊XMh4M-z0= F(hǹizFZ4Fl5,lk(i4fh6t-6q]RIOh4M3GAuEL4Mu@h4fh>y$׳)%mL&I$XPka è;|^BFѬ4]J۶I$R)ln]WJ ^4U8Nc0==(DAI$ H$HzFѴ[nKӤiln8:oD#cEC0 |u]$۶mhyfggٵkך>yN8c}y5b&LbR)IٱcCCC B0< >Q,) 8RJJ\L&8@aal6TDXQ6(>4ͅ-A*"J9)e%G"r)%KKK `ii", !\if݇iQ5h4 aŠ}kڇ8xGPX(Pr$"}0 }raxW>/KFs0fX,P.T(BTLbH.0CTT*UW3 DOFZl=2uti]˲pg0 X,VmP\VѬ-&?0e9;vfi4-[.\<~FyFh4oAzlZF`־N j4E G(vyjaiޖh4$ZhQ_49fFhдzB'.h4Mmt~Fl}t!GO Ck 8y%*ab\w/D5Uh4[ 0#٭$*B̑#ٳg9u -%SUW]=^!QѼu@ar|G}iqz<}^5W_y}ꉔ>ug{J%bX%LHp pFY-4 ip+H)=*}!ZpK))ٽ{.nh7\ e޽p<)pveqINDVl<)e5G&]_ k#0u,/=jX,rKSmOW< RJbC?ߕv0|p<) ^/@)P`ANM຃pN0 ff3,s'e %o]w￟D<,1W^!Dz``pK 3 Þ fAM'/lNdgA^m)e>z#qZh6 󐮋Xa2G}vũx?LM¹y_3}hCt!s?mI84 益sz8> Aw5)\!BуS??9>r+$6seHo|-+F -Jtr}ko&Μ!uoK.Sx KK38|-2yN`/1eRLY6q LC W_Pr!Wl ^ׇ|>y|o߾/;fN <%KA|胡TA.ڦ+i(x=Ƈ!P翧)0 -&3yX4X/fwuW7!C}կ6(Br LlE%WP&J)@F1'l׶6#F5LJTx"JTr0  ߂v] S|Ym괟7瞄PPU8ÕPA0?xp[ܶ3}.o[ h54XWĩS+N o$M^;.N4 㼥zs5ؐS8 G uRWD@C ${LyoZ]AUoG͑uaYwN{`^a_7;xI 5K"99m} f.Y A"r;|@Le 'K;=h5)u ޴yJ7P(N g,$nw;x;L/?{:t-4M5"MoK  ~pi np1.ػM30S&a0 rSrc݊T7Wa51+;_rїH%՜AURҗ%F66!gGV!\IɁF1&y1mCJwF:&aVݮ] % 0W7*+D_{=Dr5f?~ঃkEF|5/zz )e*d2 ӿ_s»&>F8T@ZeuFr GOe5*9Af| GZL+gY,%dzK]5zN%Ubr\u&uoG(W/ ]@]|gG/;90 _0Pղ/j q\lS=wuUpzj$<3J|Y87 3`kmvL;; !Vl @ qv6\Nͳ뇑>ec ~@ Ţ;hHN|IDB`eaM܆tJUK& T7f_W4 ;F -c VLD-q.!~ӛ#>{S]0 pZ Q|yMY3ٜ5=n&Lb x/(KYB?'&spyJh5Fɤ8d|*62S穐<ӿ_k{?cgm[0n $?e}|W<<@O庍i % S pAl>*߃h^Hσ#L#^2T\^ }) )4Xub<c vg98 &rM#R}>~ƴdF 6ԡ'\bZ[ MWp]UU!54WXX\y7L&S#BŗrŌ.}~y 0|t[`˿#T,*?S yf2 %rg1l_M%?` ]v B B`&#aY' K12`b TBֵ9~G$1@Xx4 HѨ*l nq cl醙Njcc6:=V+ZF泇ԻG_XysYf%r˔2-A̯^}Lض/i14CJIXf~\BKRIK]tKb1{MUٰ6t HDƂj%9;3sľ62k>0_5+ؚ[Ƕֽ 4ld]S%8?sv6dȩl }o- JB| ''|SW20㢄0,|&RB.b~#Ǖ+]Yo'[T\ȕX! dקԼƊj,ۀm$Q3E\,%g (T<^K{/؞2SEha Usp <ĐU`(t=x9< 2 L# + mtam;1 jN/< Y-&y`xosǎKqb3xmsz%SpmS9Q~`Hˌ!e%ϣX.),6`Ǹ}srEU6@DH ;eL>t`*~9>{Sq|f+KeY9wSB2]ΰiX aʂm2/,t m\. hK ?Ќ &,AP yL&|6O8񥪜yla-4e%HFmPg?4lǟx; o4<'y}}}ѓ]ߘ㲁~ Sƿ2 "T ;L$._a>fٓ?_z_)co7c\C&_ [`>0rl-nԈkຫG;OL8_z!~3J2:cdhyʮQ}=˥C \P2 UV4aF%KI~vX21^@";?ފO> xaX*qu8z/vm,Lēi2Vƿ/1LY t&?KL_?6q/nη]oaiG|uK 8Zֻ!Nyځ߁,ë~#9u u~ǁyZKGQ'KNEeFŊ!=ˋ~~=)|ar gEkkVzl$2xiYIUM,y !x<εWmbY+?ac&0 R*xnD bg3Yl?}M5Qna'grrK<^@)rv~NATj:_":X%v7~:9Yr315/4(`r,v#O 6B=AʶW2u.l&_dvǩfh0X}eDM]Tl<{?>yҗN>7 w}wڱ3/2Ł8a! ! i൳⧆ƭqK??uE<-BRtŒSY'*b߁muE[B.w\2%*6uj{wK/42X"]3'OdrKp=CmxRbeZ0Lp`_%-FETeOOӓYw;h{ \\rC-!'GFBt'ᅯl >E%Wp)95"M2 S's\5*(iUy Ku )Ux ?ČB@mi7& S}^Bhbz8 (p]iݺhGTi/@3B'}꩎‹O?}3ds97!bxcq/bo& *+"sE<<|"ϯ-h|#WgO]j@;@}IgLqӵaIerVuؽG$c6VOs]8? ^La1S WDgKX]疬ͨr=Wm\ 2U<3!km_[yZkLaڡy;Aɩx,ƹ)~/};vlMMM/| GRJʮ_XVa ZIo a22W27f9 d |?S[вw39Zd@_=v}(v`PCeqE8=9S\uE,3Mad*ͻhRnf(Ɔ~t* E K\6 L]`#u c z4kӎ%`uHu*PK3Խ.%pNB2bU~u݉\U ͪ#z `vn68eKǩm|>ʢ+c!1'xבRN>>Ή 8TE ̹sq4RJXP,kEʥmOs`d40&a0Lӵ"WnRR<[PpRgt2A]NaTIx2aF_zd\݌ 0Y $JC;a UIx.wѺOJYJ.tt Fm]$mðRR Č'нηBTi[kͨ0 l2M,S6Q]+} |Z[(YxḫcE:nџTiz!uLo rS!7lfaw1T5\}=pxt<|J~6O}p733Y[w EfD}x"Pviۯ]f46MFF n`6liTєdÌ'r5Pre[5F<)%?qXa!!L`f ͲuУSLCSOo?g>ȨYSyfz6VÌa6ibYXyY"ɘE9N S^P4>zd9%E ƭ$ XUX9R}%!JT˗0^N5ƶbؖ`:#/F="ȴtZo]W]ZeL$=wMXč/9(>wr7M8")Ke# L 3Kjww pOjݣ_TZoP [ imR 5 G6uVz\/y>U)3P߾s\LnXbnQ20D$9L= s*b),_[^=6˕cĬq˦/I%4vCl »Si6:^Xf=Dn~`dpDqt~/8 ?Iy/Bfs`(?TiY 2 (:,Ӑ HBmKYgFy3f6C>vۊۤcׅ]q_Hpʠ/p8>$,dmT?ӟHɕTTW"MJH:/ׄj ۠<؎%9 }=gػMT̢?`տ 96LzcUC"V,qvJ<DKxR+pzvw9'PIB^n!c')SAcH`og!] @VGﰶs2~eZ +ueV+1-v.ηiԹ>RL6GJI* HR$DK/ ~0,UB@,V˅z *1$H7rGF' >cvx"E٠_'r|l.5!`.ߙ(`j`GQڝ0?{p,6pPrt##qpm% X_hVc꩔E 7^@Djcv ~Q kY1 ot@-DLy:[."rwW"vtޜ$Ps+x9(s:i.JUg(eVjGr ry}Ȑze ;\T(X^ՇVRۗO^Vӆ8\2W\)(Ge/]9hu!z1@Q ;-0pF+~,H@[,9JF! , fU'dDs;dS9. n! MY~kp_~ '`0W5K'=pxoMF䶺s+8= +F/!i5 [,1XPӗs{uz#(!kD \sK8GJÄ4a{ Ż` ڱb`1I K׃뚲0Xf0"⍌2O@6 XT"Qj 'Z<᳏ϱ{_o*S^c~Mȕ֝#5jfP}~fx^|ݜb),(k4挟]Tbb4 {Fa|!y:gs|YҦCuP[;/^ywv5{;, 찔+/(=ϟۑaeIR1)U3({aʐ!`<wa0Pq>dfm+~Zh"u(a,f2T7 sd D޶me]eu }tKu8foQH&W%Dme$ pd5:$aApO m(zMqo-=탰mP-ki.M 2^֗r3\ÕjF`ʢ?,=Up+/5wqd^_nZBC հI&Z}liD5CuIeee2XdrBr-k jiJf 1bހ!+1j5kWaL:-ʱ7'ⵖa970hϢ+)' /Մ *#/Dox3 + 5:iLqP? ϯ6r`|~mG_s{#}So_=Z Ԇ; h*0 5G߆êat!rnj;X*6Z'P Ȕݛ}Md߼PdfEsJ!7# ZsUbYtM4xw^?̐ F"<\k g^@9H_MTeS5$g/B@ua CRm>,%/< bEni`RZ.6a64dp ă Ai,CԔ>O48e]:#T?X TG!p[ʈK)Vυ/Ju: dەQ|-zn M`uΦOמiEI5ӈ[g}^I 'Y@c`8Pɥ I;3 iXudXy)x-m]}ΡʿղԄzGD/opt^EQ5Osyo]_8Lm>H'Xw]5[y ~!vpʥt㵥:s@u3f6㗍׃sS%~GϚ!(-/:hو7zm Di"|n !$ml)5Mݶ|In/P:Ȫq)[a: p,~Eńa׊ w u<_p9~ \"F|Hjf2y?f[g8N2VK,dʔ]x>*ݵZ*j#X1\gŻf7+s] n3?(-.]X=XDe҆~_TҋӧyW[\_ٳ &GMEɖP Խh2<"Ùfr[:c"<6RxRTKJE`Yq0?x/ϟ>WS.K,a/ lX {lʩ$Mr<^? ټ<%kH]Q*{s e~8Gw{oZh6_J|_b:d97sÃ$b j nH)y:zy'w0zU"ZFU:o `E*WT_B+2k>A*"몹*;Tbz)CEno(ع_ylb(pVK˜.WJ+"<~^<<<5r=LrK9Ӫ*`z '=k;hP5*NHBc yؙ"R *+kK xf>s>}zk/7G MC]H K˯o~#F"n0Я=!X )%};< Iu6a+jFaBYLC-eii${9X>~(w<#-.u@q ÉwYIu\ȿ{B'''y'Wr ķWUI@"MST j"'N N;3Ed`d|$eتx~fv)}elhcsC*s` L\i0c˅Opz~KpFZzzL}j8)KRS'2JEژ_C-z3b^0߇qH 7Kɶ~e nd/%u Uغb[V0_k2<1zYdk{m{_{M%[I )}Բ!qõrudn~d'.T"dQ7/>0`d~< AuPGW/t71gWJfʒOϼo;\C)t0ϸaAR~00Ie H\v/jYRZD'AP\Dˑi"R1I e~Vw~]WvhukկKel7ߛ>}K,ئٲ1_}A2}wFBl쒏RR z7g}HnvEv4v/VsKBpwBvOP}y4ۯbpwֻpgᏎm,E9(32Hŗׇ>eȧjmQY] f 2m~wᗥbQWs*=ܫhP<\J@vA)R>ȿB0o)Ҫ zN^`_y(WX'&A ˨[<;96U[VH0>j_~Ce1gcZ}əoa0SKȠKTW{O`Z IB2W|a DKTϫW@,-IvO fSS$dv\*.z^Pը1#4LSZq UES&+Lm6dQD>8=xfE 5MFyC({mGI !]$M"e8M@_ؗTzFj6Gk+}>,:k k)@YFB7AݿCJ{4[n,lU$geA|s1ґh eL\w/};I'ʋ TL6W,pvjS93=ɩ)UZX=s/>exGT87?*P%V })%eGXV  jYM@u.`Q._cf~UG}4A4J pf&JCJaT:Cxr{]BNZpPzhuBnwfHV ̽:أ-8mY|۸Kٹmdڵ5+q<=^rXI,{a.RU#`Mܤjl)f*=ʾGҨTgB`JMnoڸ/>WUV_ɆPǑ8|?Hr uu]^_A%!vvky wB@.=\J#|ۯ}ve.Y=Lrsȱg+ހf"2M8^~q{cj缒HXɘ[r!!}黔]S]dEpM^&W;W5邡Pt*"*Fd(_<צK&#VEol<wlj,F4 TקKʥ3ݭMkeh5!8$< qv-Ϩ q^~e޽As/Rq^P{+_؏Rdz!;fY|逸 v@9`c\ r.z.S~6.SS{*K<2VBHUny?~%aѿm L.G%j2k~2H.k* @tFdFWohP܊Z8+jtA|{l;bV1y>T~{K"2MK"2oD|20"̃h,=|em=xqRJ'+mj|(%?|>rT6J|]Lwq\RX7-$)ڿ 4veG&*0iӟTE5A=Gs!5 *qAez.#%y2<3ΉvLpovh$H2a?[5zK>%ǩZ.Opbz ~.ᆈ #xԍ_e%1bqz=vE5ˁ" fY?{ yH%_+& ҷUi6y1b@y8K(Ӓ^>ǧ-j m EW63e#iJE2\lfe<ϗ*|5oܫds-}1FRL.is%]xP U~CA3ثhEӛݎ5Q⏲Uy$$Eq=wo0 uP.xײltaPg9chtqW@} !xiNa{#Zs$`[\4{evP*Y(S'|"(90> 7\K*@åɷ)lzB&C1tP?R(Yu+ +#Nen~> +, y"T׭$S OT#}Z9rnjAK^5CZFI δ@"bN]6Yi$,gpzvxO߮qhvn⊪Wl ~ ܲZxKyM7'FMEh|)eq!cw/ C}klFUp|_U4AogO7\nVT_3@17ˠQp%W < & lvR}ƶP`lH1IJɤR\O| %r*?V>nj<x1Uׄ[^ 4&7xx a e EQXt`†>=.^`^|Kr,ɽ׏ߜ'ye2KYϳ0"^ CAB ɛJ -_2|drlD&+alUz~8ë#Z^& *MNgoz$, _؆`20a1 NWl~paGz%_qErź~nűU' }Dl0NO/C[/| &JO8pkk믿de~,3_kxu2X6$Z]Mu]|`yݐqվj彚6\fjX㖉f AorŢj=x4v&7^; s~+㔋J%.CVM^RuUg 01?8ӂ>j f{uop2֝=xR̺&KgORfu0_$?UsrLceϕxߘi,)QZ'-VW~Wݻ'aY}NQ(A ) 4ZĦȻ>mY###p!! kӳJT*siCoE/lݛ}I~3/'02[Y*Y*Ei) ;/J[*\@G ]o -LyzIzru fo2I—̓C}^N,,e?2:7ۙ2ۈ>x5: 88LMM3`6wfbb;vo>RfPƯK i _Rj94}x\;־[P{oo|)ns(92f/@IӰ\>{&& io,Y uC*nmy 6._ 4p sy ҩj;òEH5ؗWW5oT.ACXBLӬgff8۬Qidef^uj XK"k~dEE@2S1a|̄ȗ}htW\k?uciu5d?wՉ";$spE<3K9 !x 86w^%IɃ/VhF7 }X 'Gs['`zXyW ]Wr;y=;WcMFsm|;~~ГHAzN!//B:U., |j.rE~0_f8R4܏X![޾Bl&Np9q~lۮG87Ļ׳ZA~ZHtciK0@aDJvt,=gK%5*VdfvU{/R X̓{-uOɻ!rAB(s,-V[(SBE0 C?dNsl> 9SW )f/=xs "vٜ}J5@-hD0NuqOg4C a &?v>ol?޽$^ u2jP x[tIe^ߏ|_-=\ck`uڞ' av j;]? qt,fu7 /LDtLO@sy^9s&R29 ෱mp}s!>uMR+%\Hcz1C&W뮎 !Tɳje``~AȟH  2jv{_\.pյVҎ؉>1ntLu 1~~')lA帽#cXmzvԅD0& P^6o@г _ e26IĖ\~ ;Wywa|ˡPL,||V|`lpB#P٫DVv}lsZcM)S^v'T4d_϶3}t!D+7bAEZAT?ۍv&o>/Ɋa;%pm+?lM-dZ܇eA=798g@q(^38djԚL)78z <:3sF% Y@ڼ/6oxpnLC PpԨl!i71h0ԭ(lfnzPɖJ| ҿCaYy) Sw $v<1ne9n\ .7`T7:q˕E0DCx)y2XQU6gdç:wnymH<Gagt~}M-.y{M8_ OTgtjT&/@6:^di^ -"V<,gf檙퍮"׃' [ O×Jh hh~0|(ҫ1q~n?ܴW9?F{FT/N& ccIU o3BPYNRڱj#ꆣ6!ɜ(P2 6X2/"ܵUe6=σm&<9S/O1Heb ,֮e&Ӫ'_s$= 3}CqK_/_ϱӌ#Lϵ1NOŹ8'fD·.yFݣu!e×߄o~ 7/hQ ɺGk/K g4㋇Mp {TVb^=0W-St WT5TaȪ89VHlZ>l,$AwV+dhg^lѺ7|C-a"1HY& x}eWJ@X6Iv c~n!#9V&'P̘ J=)r=܏| 0zG^.N ŵ|<2̪4oo*uɶ$%Ix,gΞe{e7b^K٦57> :mG\4ib'~:3I6°pR.yۦ?d縚+uC5‰s[P<{~i|U0ˏ<1U3Y<&<_p.Y?4P0@.8e*|)a$Ī1fZTJ5^& Gzmp^ZW{CXrڑ< `l2y808!h\g g .;}Ww02•;kΧXrkO CT\ `m6cʶZtV<!-,bhhӧO|~k B0Ƕt|3Nl6p 4Kɛ+vj+'d|+~|K'&Z+ C=\'W'xmE)rz:@j~7z! !=y^}ec#{ c^<zz\4Pby8$Y,N*7 `_?\={ F02 )UOP,뽇G ]kī4nx|/Zv=0 s2gN\=؁hEF2ɬD_R]^n ?$K78+4MqMK OsGOf/?̱s80l\"Wؼ~ЮjG5@:ټJH`0\< g37߀"ƏqÄJ= ;ʐ,/owɢT2ɖL> %x,|wQ}0qK%E5Y %a( ɠT?DԈ;JxqW7\*B̯Z kU>d+P1QYx@!Dܼ~} cgߙc2c3Зd(V9s.0V XS8y|ʮ'%s[B1V J`3)6!MV&}6Mn3Cqēߗl_ݻo)M0A8`!g WQ;7`xP6#(IDAT%Cхh.Ú P{P=i_IyS3]T斑~b쫥+[t2gHP4OXH- m< ezj&؁>y0?rW_,x,S}*3=_F*Gj.c:O|IIJ} YOl|s;!Fhk\irJtN@tb-E{h&`03>05;wSXA~HZ=ʽ]5<ڱb|=!7j 2U;kD+D v/O6ذIB$ Y)qbDŝu9@ݛJSJ a((R^eK+)~ o*odUj甡5Akް"閭.R52ƚEsk^CMQQ/y%57L@-۟UtBO๔[s\@0#'JXwǝ7_:Y~wt%6)>n8\ /MmDҮaЊ(l@i6~3E@)G\m.+-wc,`iPF&ee^9}++ƙנcb0 nzhҜB{ /V!asxs$ ( sƴw{˄@0 %5ͮ/ })Tl~ 4IVp4 XذjqՆ4߮txJF)sfOx=!J,{5L% +mLidиBVte jR(w{Ffyepኽ<-Ƈ'M A:ab f k;LHJ'iw*h:>Xf t;l=f_~A é2"hwПL2JaAⶍeqmޭg[ Z`CD=5rVv бYETR7ϽR *??e(E*.v Пt]]<jbN=lSؖ2ꦡ`Ww&͇4ZP*+_t#4 HCͮ+9Jqr!DtlRKFDdWz_) =ig3hO,P$u=ʮGY,~RNÄ{j} vruj4?rh;\-<6yK-LBѻo&huNy6K{ф@OB>Ggof5+5zg|ѡNIӡ\vDo QS@U{~#/ KYw\Ң`joBfZ9<,wP$4oT:PЋ5&RߘPv+:~ fe[kFlẗ́n=a}eW`P|;hO*UHȯd[,hX߾P`2 Bg[*;s}25?OQsE[ǡ/: &ޛ,@U=qݯeN n㭚 @AYMKrösىQ_3X>zk5縖|7_1 1C_OsXd۠T"dIJ\<2}lglHռ_dG)` %0e.[l%w;<;ߨDy\WhdYL^!,ggaWْCSBޗ< Cr-1_ŀTy01ʢW5&$>5?o?,'ZȚBҫ"@A\BҠl7 -eW5o^* `0͚:͇m,?vYYg CV ^~R>{R/KIV8nBbbv R DzwU@}7jOk\[L/X̗y!s|.W*o`dd( _*)ϗO}A?y,F^HQIW,rrv^e Ddy$,Sqy-}8xG܁{W4- jHwpߒ PkE `aauջ"`5bغ N@0a`Uzab26f1ln1#jM)D>C{!,>&7^+cߝO/Aeo<=#{8KT"S(1N2{ǮٽS*cVkh,2W*$)8HK$e|ɒCYk"e͟iS,t oQmjZYPXS2Fhr-_,`CCCr24^){߾ɇּΈ7iD[9R|W"Yd:D%PvȠWڵ߫Г?~*ŏ0>W Ē =<ߥ,y&3qR $*߶"U~M%5wʰ//9Je ew}^rTp/oG'0;\x4)ڜr69bI{Oh FmM0=jX׵َXnT3[=FSo /ZH[moqz#t:?DeYMqdXm9a5ꌕnV0ͪƄ.!\ yD.(ϳT(Pr8B>B@TP.#Pt]d"6 7žUjЛ6߳"" ,Ibi}'\<ďw2;IbB,aR,;d1b.#Mm$c6DDl;H\O(9eWy2eϣT(%ϣ,xU>gϘzu u' 0񾽘1E?T,(c]@ Ԉ_.,[9\/+T{I$Q힆_8/}U0oBEG3ߣ% &u"|&#gZݶZަ}-|>yu͌ztAYiW|]41ۆ#r !|u,, .ec>g'[*;d-Y7qR"mf~m%a~ ߆J1Xa~/xCP;w$[K<}_)S 8^6f @>4qí{ `׈e  jG#ڰ5ͷYQ+嵬V.6|41nU_X6ߩP5Nփ"`Xq^mp7`RGo4͚yY{YJUF?Z.T52:;1_ރjj5V"f07q!/%LC2`| D1ZwS/KsyZK3F$%-ْږl:zvƟgk{l;3d9IGE[%DH@"u7 tM7V8G[U©ps[UoX*3ˮU^*i STlq"ܯp7WZH] rWiuseٍ C WWʫvU1s@iV f`;S{@@e,y/,~.kZL/6,4] %v:, &UtB:2e2)u!\yxYʶ>_U'?3 GKă9_Lv/)woWEA)EW (+؅9P˸G/诮n uMҵǢ3&=Q蓕M 0(V`bb4 ZBI٤h:H/@Tݖ!%+NIY&GH?ee\(R^'6G|ak@O? $kcrQE۟Hn2((S.Vu :U~Kz+I~df뽼)|"= [JZ/VgnovA٤i<{w8RRbjWYJﰴh4HS4Y?UT,f'Ryz~$/ɓǴ-3SHQ?K\fa()rzPbUub'ޕUP<80^4,W!3l%@mth:gYVB_jXnw@77 5tMGi#EƓr)W^&o R2RXTT]r_?v9P>0`)\T @כeJlKmQ H7́=8FLπI0 xҫfl|gyWRW@~fj8&@<~$ϩTWAF0ѕ~!(V% :AOz@LE)%Vʁ;ȪNzc47*!t6gH)ӱ2Q"WB5!xe~G0M3πN:&>5Pu h@ބ"ׅكiLkz@ NSUJ]u5_ /7Cjy,bTTE.]A([^'`8ӑDw ~^'uMo-e#[>#%|3Gy%br]H$mq Z%HT\'8gKDyꥶU~ayU~W֬4+PPXV絟V>3&MӔo]J>\@;HZe)~~&[!x1zFgŋwێ{Жr!HQV|%JOV"_Q &*Bƫ~̜*YBFEPPǪ>g}RQi-kY|ׇXk8Z;/`j|%hBclY|.`W73pIjRp e`/~avN,?<q'].AJ4WӪ=\$)0K{E !SI7o0}o^(;Io)%F7  du@HJ2r*𥶫 K*# Q+,nˊ@GUg4^dեUxJ)3B9PROneyLz }5 #8*ZKր|b >>,p860ҴLkο.llcǼ3s@M"QT92,媪䋬(]\Pu d,0T#L(E]u<̌Jɸ>TDe_ H+kۜ9s^zΡ5Hz`*6‡}hh:oe[3ٻ:G8Lo Wxqc{9~ht` P{yO!E_ DX( >>IPy՟p{@dm:9rˬkUxs@e@IA 9P^JB666iyϺI ʼ* xy~o[on͇WyKں>zCڂBo[︗GLƱ]>(/sQy ]Ru($hv].X]VzefNS?}Ѻ3W !~@pd@Jx_gòhdGíKKŻ"c[w_/w9AM煭-7o?|d2p\<_1KQ-<%$:Im)$4/ TImT)?EfyE[*|y$K \LE8߳qkv Ǹfv[JBƉ%lDH)~u?fں'^~g H!B g_:?zMYow)"mK)'VDyx k/"CQNb7*>7uv0lʿ O֯t}BiZ(t $Yf T&e?YNlm~$DrD[oΣG!8:z vF!pw\x;ޔ {`DY&g#j{r"e5ʥw~UY\(+UV˔S `}}˲0 N@ap…u4 T(߱iB C~[OWDlft~[ R#:v;w9¿~I^i!~tA:xV>“7re%'C^Z{A TL}AQI"n00{xfLUyeB8j&R$+X`Z|wΟ ڜl6?y?{ #$A -]W_\F|"Gnõ>rx!Gyk;_ QS<w[YYEm' {uU8\uډYYv"pHg|8meMB m'N58RrCO"_rvh`Z--o>\x]<Ĕ*9J7\@(G~y/D"k$/Ψݒm(sxyd;s]eJيUiip"12l$|+<́ U'Ri}G'/VXV/nZJB$WJ%I.Td-/_?Kf=zz>9gf%c% k-~RKJxhH/òc796$ttFKq):@&_rd(4EW4^Ux^v cU䕪R5R0P+ţȜt9P˸G/TGuXx<.lFv,!0M @.Px]ҋje˶ٴ^x%nx ) in9NN?tʲ%A o-,O1S՟_/<\FJ=պ*yUVہ|9#p+5 x)/+(2ubTiwЁ@>mNMX]]M :IFek}'O "Z+~xƶy ;o&S v>ĐH-- HZ/T R]3#vfm (k(ZnP ?,׋@-EHP}aFgm H%Qt{^B$ؑǶiۼwe;{#f/h+NE  F0cswoHOk/K᫅W?NF=9SeeK!:䳫\hWV `@wԩ NъZlZmyj!eз,)NVM S7x[8ApTo/"bm|5f{=5d)eeWmgR.ea𫀂<2~Y+s @-kUkR`;N.SJ ~썜ZX@vx[\%-lp!Ni AClS:1 RQdkEyWl`=d-RmOQ]}EV"uW($BXr5TG0AA/Kqճ3$? O_ʫWFCFp},.5 F"Hм^>QBUvubusWJ&|YSUU_e|cm>Ġ *Y-˨NN^92M`]v22)ެmb V0'BKa˱S17:/'^Ēws Ϻ;c(yM@VKݟS7~fe&Y6,UT @ PU]VPar=u*;n7X@jM񘦩N!@ S^ *q׌/%tt]gͲBi董|S4FKhӽP$5a0`I&iH /AH `jsx3WWX/nB<9ILJHhG] m 1ntyJ?u'ր)] F˩#vJ/!?Ǔٳm]\Oa@#dޖ_Ky[ dyE-$@WX@q%QC nGh&hR\WUyޅexVYqݻ6ŨԆx;>KAk:py4ılwt^q#&8p_Зd~ T RƁvkUOZ/nJT*rfTrOP]P ۭfc`ȶm%>OvF~D$W6X'%tr2#ݠ,P/!U|8) (Ҧ<F&o LCY~vR& 'S^J,]omwfe@'_Y#漠/[6sN%O-T5@m ~8ܒ?uV9bg^bgULV?Z/q5S(͢eW rXQ}wZoO(+q}٦*hVe{AŮR'ݢrRN2RŻ*u~_="#LBY:sPJ F@րBNi?(;>W#`h:#ӷeRIkK\82pȓ08{8jK˼SNaȯdQR%.aQi@׫kMŻ[e;4Eݣ. օ-BA%x<?Uo췺jcumUC \LTB$Nzu}eW ^ QYXnIEDm+/ %\b+S) i^QeCИ(;E%[ 4BUT[>Gf4C2(j3̗#C+x ɎcuUXb{)T(SONmSys[hύ_}ΎP@)|qyr|e2qtI|T_ `R29vȻTWjX^R]qeȚ"h7䷛ŊrKQ-Sv 8](X6H)oBx `sslj&sk,m^T/ܬx;[<[YMӨnLapb&ק/M3$g@JZ@F>+PݔvsedˑYX> G3_MgTR  (@z?|%!ğJSBױ, ȟTAnRf  @T-5U5GV Dc2H袪5"RbA` ?NP9ʪn ǒgs +f[Q9D<^ <]4 @i6'}u\,E7VZ * ͗C>L o SG)UUR]EҠB:nf|[= R@ ,!OQ@>w}aagǨ"'&4-5Qx!xţkx48hȺms)v;zt2kȒS7^?H[jRfH5* d0WeMx^uM^z%Ξ=KEӴ!__Qc,²,]`0_ڵk\|5677yW rZ-#F- NVEի Vd_DԻ,';FkAeV # ƶlr]3]~cFS tYh.>r ]OqL2^{"uReQBVVՒ"jTj'斨WTPk@0wl7]wEwѣG9vX7_MdmmpH/Lףsy4Mc4n# ^b=-/Iu8JpW~ߖ Jq x[B`((]mX+W6׹AI,[W  cI/((RWTE@b ϔQBJDU&i7X P5n,( $:sL)x5M4MΝ;CiZ,1^u666/_ŋlmmqy`6a|@ICȢ$%rYӃGdc W~d} Q໛<9gh p݉3A+$,zZ`ĮKQ0SP0#@33&]׹p??b0p%x k>|3gh4" >4- SܺEI ,Ntrd0 ޿c jǏKTI4 u`4W.]L-8|peC'V0s9qŸʄ5|嗶6-qj 昗n.$'8RrͲ-^y4gZi]LU*Z7ikU%R+ 5R/T4`(##MNLf-z$:*{)I8z[ov݇JlTVC= ?S>pF hBw*1<9X[s#ER `N;Hzο.z[ +RW[vFu edK!:VA_BV+$BrAǛszJFfRڙ7-o/Gñ¿w4i]EeX ?xU5'0m3jߧ9ӎLW"%ζZ|_tS+Bp !@=  l?AuUzI'b*B`T)J(ZuAdW%#Ef ]_mw$q͑pMnBoo]{c>{WvZg X}V'~^ۿ ]Lx^?w_QŐ7-,A*%PMN7\ |2YL=)T3_WQ)<*",/Qci5U(ְ 8SmVm- WTU|/h?$qwdpK!ɺmn ɾ$ B\@ehRrG}FGm351Њpeo !G[0VҎ6S)pPgRLpP+T(ecR:K($J|9@=tӯf;nC(`q= g;HJNU33{mbtCppg7??TM@SL2 $ۖC{@+O-. A,,x ELz2@aöxxOLK<n4ysg!Ol[Xţ"'EVU~* /`=J1 hJ=C=+{?{ށUwݛ(݄:er!O{}6 3G#4MC-0X߃K'麗vcepg;OV-/xSn m_$Ďu yڲuEzOpR/B|Sxs _ՔBv% UF˙B%&f;vU%ԑ)m5/\eF83 ]pHv/ ^}˟#BۘjiNeHTII c"!S{]-n8$ 9i8p9i<xe OȽ imˑ~C,_xE>j0~[씿±,em˝j)C;ˏ_Ga#NupzF t}0;a5qĖt9[Ötc.7jZB5)ys]2%S Z "\1|}4H5hp~3{J.e%ʗ(zV7Cb'A<|(擔HOGC5v+v S@D q8њSOFv=6Rѱ`xcܴUJ,)IɦtؔM)II_JRbもqH;Rr0hVö(x4тw+!96e%>HRV7dಔr9L+l_y+b)/ڕ}M/Q'rKQ(ː؆if_Wx% nDeᘦkRu+xʲx8;~ &ğ}+,OKSϐdMp$8!+@o5{uXY yaâB^fY|y8Hv'T/Oq۩ $*rjI<`_N&CFL "sai:/btt/fv,<̲e/(;*D@fka9 4 #M_u!2S/-؆:d*:RN?AOFoIP?z>C&+V~bVw48s-/@'Fn62?%JڝTӻk֫zP2>mtFth3B3u ˲ eHYRRx @t6Z匀| i6G|69\nołphrPax?I_ O[elzmhn{}:b:ch;~^z<|Oߛ$[Iȣ  p*`,zh'}vnDyB`| @sP?_vZJ ӫ|gz>)ZBڑV75SyJnq hLj}(=6 XIwS|' _G$ɰ@h7.q o8GBJ 47^6/y~~0@J=ZqC|  88 PJv͖s4q8ߏ~IkoNw`/\S>Gph)-?m϶+BfLy'.8Vߐ6_7O T+ ND,PP2 Du <*<2##On;D *Q1潘UJr}#e0?  P5 NYiJ7+Jۥwr733OHƾW`)@ 7=$(?lA{Pw48p4-!m9❭D8o<؉{-BWә``RlF{1X0؈x;tjwLsrJ];òASu5@ $| n¡PJFz@`d;JZAIX3w?T H`4:yoCP:u LLYNwg3,H1M#F_6GBGGT^ߚN c _T@l12>}o߻X<}v X hǟHynVw`2' 4MCk4+9 ?CGw6ƭ"jo4M=jBmA@YmO(P|kx>5Ug˸GUi cdp#[M $30F`Ifgn #MM^5e lzLʢ&Z.SHk:{wOqF+b*ĺ/qOokdwžX7 #;AэgC_]kiP>F eb4TYԿ8ρO˲4m۶儔ah61 ]]Q(re0 h;8{=#Y4`65vݔW [~@*Y[_3x6} &eb&x+.C _:TaG"Cx+ZX['ݮ}3#7l74{Cog$Q1rbӟ,o6`6ib[i)sV|VA? R4tMCu àhiVfK:WImg3{rfc"Ж!čL!'QR|y`ZV1o1LM] DҼ bxewa\w.fJqm{bO/t{8kF(+PQ "Iŧ ÌJRctm3\ DJ7Բ`0,˵F*ݳDVja身}iMC4 àj]Xj֢6P`Y4fDqw@dyDmGb q7/h?=p:,:RrݚmvBpq96FJ5Z ״d&K K` R7Z.ȵD&c:`vnE7VU]lA0G#@Q eX1{ٝh|?s<Ʋml)m˲}hD7hnnzu5up66C-LZ6;mFK VWe+ˌ1Ffh4jtt:a]xLA_ U~SUO8FIe{SFl <Ήp%k#;ŦNH1ua ?$ ]{IQ͎m;Sſ9  _}I1G Wm:Hma<Oi|)ql9 4_{ŗAG⫈} H]#I Xp B+'Op)BEb4 h6tX\\t@ YDH)q —31YPu;EJ O}s >t\汐4?)v0u/=;wj:`Lphy,ʽqGĂn>b-wv+ t;w8u/Ol/Vi&*|{8y2 /DVmκ׍)]d*)]qw_]K;sqˋIFj?@PJ,)i>r x&T{ߺb6P~P}+|нyY`# M&fGlYVo? 0 (/ؽ@gvп^lv|Ѿʈ?|=Yp?fӮ&T#Kcw;L/K41zn 5NʿねS_aumoS>xŅ,²,WVXΨ[ j?8u9mW'T;yPZՁA. b+nֿȾ`1@+h-6, ɫ H*wxy `/嵏=~ם,--nYXXȌL{Wܗ,̌"26?InHm~k=·Hc[>p@_|>U#\B1g_g1H!Xy۳s5:{=G9x%!2 v%`"A8ͦKs0#rT0i. 5 >*`턕xYHi u?-38\]cz%W塖b8+ ^k?1[nfۥD^fͪrw3P2<ǴP!S_ڴ,67{{e!`s m}?D}4 p|{"2e)-p˴6-OqhBpaڭ֔@;pss0Io x1v{-@Sɴ$Li;.xsZ E'+|M0XI ,f-wG,ۮ~t흔k. LiA"Z!MtS"@>KCK4 h_ch=":rK34~`JŹJFi /mY&m:xlCA۲ON{f@5 d;to4劏cIGvܪ5_7me&p Gox'vd O ]uwp(vNKr#v$^9$˂~?0')LB"H妯5f \7@C_zmmlݖU"ՃT2Qpv0E]N !Ppv8,-ÉsP7]vM8pr2s!7h@S(x& $!4i0}tbu Y| nth64/ ?-, oNdYdޚfϻFyT#\x"/~d|m(eZ,ﳗye, u!8; ޤn>:%s{E끔8&`FTq%J7B.Oĝ?)+y'܋ܮ7Sh<%[5Bй0q(Ɋ9KC3\2\Yba N‡yX:6F8H!=e9l/oIFKEj J|Gl6@ߺd4* FFhE{6N\ؖMiZ-VVWmJ :~;`FsԖ+|9m~AS:^mGAqH:Bmt!QM%B\ܖyW[$1 <%\A&x>~,)H[-t]wM7MGoWD4(/ڌq'nPBcRTf}$e߀088h}\l܃Ȭ̮ȫ@!x&t1f4F״i5\^QNav_o ˰ L5gHT;6(40 Eƃ5Ӣ/|ov{r&d_=yLooSͻB+0w͛e\,Ij$޽дuh,*Lg\f,sH,7_z݌VvҒ\ҟ0ynfkI/Ox ]VU<+q {wwݤ1j? aeO*ȉLCFCPlQvWF9}ZZԶU@M 4$B Q J X$fw>^6ヒKKMa Rw2HiSKse `:f%5w ?ܙsUrYzT>{"\=|%}1tL*( 1s9:.koZ0{!CBRf>_%yB)!%]*MCڔQCfQ5:?p rTYwb^| $xfbCd?֏b|R]Y I_|Çz+`8d0nJibA=BeU_geQ \[7??m{ epr)zA&`fǵ|mw3:,YfInP}UhDTۯ vBJx>%wNRIVrRAuO ^[:+++,z }Fá?#=sq*8u/ߝ.!ASuB!00i̠Y︅W︝C߿a?C ˨GN|喲 ;4r mi!pgVl_z^2q䡍 yшxeY豣9`6zգV(@ 8HfPKh>%){eaYv8OWW]l-i'I-w_W8CL)!߯__AUv6F?X_g82FcF1VP~9 v{bu%a+~*HdјӯL!%?1L [<$Wc3X[8~hdžRV/9P Mʩpp(@>ٮe"{}TyICզG*2-rd~px3{?8UW]6CʼYQ7gUd ̴MpW67'n01.x9 ]zBUAր!EHolKI#  zeѤ) ,c`؟N\4HmP[ 3;>)S)4c>rB AVy0ցHg"|mc\ ǻ-%`O7?>L5c8cY8`@⫴^}mmCuiMqD3ri + NU֎^th44u=Ȧh VB&{=؆#]tAL0QPP` _4uy)y sꆊ5!d}kU4|g*cs0]@ D=XA /%@])5I%BC2m._XѧrbϏH=`ڒhÅ3'8}dGBU`(~^3O=%wV?N>IDATΚ 7*وmB kБkl;7gNᬮvxlbY~N"fs{xAbcτ]JC`lQ:j>Z= u|˚Ng4rf;`rG{lm. Ѱ({QI|Qq@HF7^Ҳ[^Lm)~s4b0`:eo ^~~yi5]ş:| }7%N,q3\z6BM}aZi4J/԰\|.)(0\5 /+'H]Q݇GY~zߧ B`l&Y,=0+h"8YWeYn'/<Ee0*y"I;"LQW3'Ngx /^~a&~goA_Eqw/p~o2gt:+f~+ן[oGz@`ZX`8dyyN$`*bzݎ]}ۢG#dn-TYr+PTY t5M^eY>H P27̎ F|{:DG]%DVS2@DtvTd0o !b!MJ-tx8:v2u2اl96:W^:*+L^(SKCG7-?]tׯ?ͳ#,.tt:Xi,t,z'3s]sYO@}A:($|w*9.J!_GpwF@hI`N{21sH-0Յ@C8?9qy?h`2׏JLFnm=O}U 84U{-[:eqX:n 7@!Vv:mlb<JNu͸zq[7k x`Q z=[P5B/ g2H2 BmY1:GsGh?Hvr. ě.7xI-,>gp^⟆;<]o<m.B 4d*9>UyN2 !)gZq7Ыx֛y3\^fqanNh,..iAvw]˪U'[L76m+>E 8Ue>K%。ϑr@ױClȂ[,ِCG^$h^Ir^"$AP%!\]Mwhë`8V~_iy&iFGC .\?q2ڛE ٙCt#\bk|U^F~ضo5MiZɱ9`yx `D@+-%4o=q*?*w3"1϶/4Ss0 vm\];nct8WVX̱D %7׀٪ʿ n=ic3jb'%y%>EX肑rK5'NK({!W~!qE\[7ųsێv*;pЍ0 #*N$Re+ w}ԨԳ*̪PZMBi4|oy*m]&߭%yb|SYNwY&_LY&GJ{0pF]GeY羃D|9XflIsV\\H{19it1ހYC)Y\\dii -'s 96"l9JK-tG^` R-v`rYN];Ǐrdqq'_o)KF7Mq _GX[Y2*Q~@UW)i>DZy|4S'=Ko]>pja IJ#J=[:?_O, !GFb|S*5BS)aHf?PuB@s$:Ghd')6{) $Ă w-}`:LQG8kZ[wk7$im͍ ZAT(W5OeEq`cvYx킂R>+?l%WJ  y`vtNa!5W(*ìVd <{`ťȑF(O9 gm:?|)7?\i4i4\% 8ys洜Aj^<-iYq|2+kLj"HPAvi8- /1o5^2޳Xؤfhд䃞J̎j= q}0!I\wEznJցS ?ʑvw/g]3#?i?+igBz+$ B[di­68 + )ᘷ?m|ӍXh#zR6G~%v|$SNSPv&I4MxVUsGh? v0@elePo70 /h4*팁Qc8$,-%1}җV ezǫa)v4!pOuNFmWI먕`hlzmXplխ*J>L KJ͇;vrBj^"}W*<(3麛Gi4XEU*; ql)5O6ض{oVi:~ܡ ˮ LZdF=$!˯Д'Fcw&+.x"WUc~M/@=㖩kjPtAEt]EѼhk;(q{]CyҬ 6<2mG~mA؟yӰ%R9;d $a6O0H0ԛpuοʩWi&FnLT@m#^uOTXqKJ+8T{9]S~ Ӏ!^Tkx] xk +iB)`{d KJwwBwuY-K(]*0Ķ|HeZBQIXD@Hd7mH碸|b1`/pQtwfOXNg̒j}H)}Fb+7\ Lx_ < p[n78/_?;L|Ixh5" kVi;htII H.,SH*}JjSUV*b*2C%t.N]#a=B)R.M0?bGzSh[9ϪPdBނ%d2.)K+jHXKh+`-Az%Yse|˓M_V+xtUl`eYn'@")Ʌ0~@ prEru{,;`BvvW% #SJ#ײ2=HY"YSh&L2j.?~1Fk`0̬'$ f95Y|+@t7C_sfG/wKX) O@00}1$jff34< 0'4 z= UW'Lʹ hoѤu0՞)wM;tJL  /jdʃYrJl  ՃKtFuj 0YaA(Ye,tGv+Eąs{|ح=_m ѓpEѿvU$M1ϝp`Lު?-)+hK. wCұR0f*PV뢤h, SjRTP!{q^?l}k`ѠpZs0[}QiH0˧hS[?|ub`RJ5 _v޿dT5rs30_Od4)THc9kx4brkrf!RW1P;ƴ`sulz;wZڶ%\lG'xs0;Rqé 4%:c/1iց8ojH{s٠2&^KXY ,>K|> sG'ʢ2$8u,nbX_۝=DIA:pE,dkhIUbCy0&Dޡ;H~l/+%'!ϜwJZcQ@}4l EK4W4vVJ.&Ym]B Ro5Xv9vg7)9|$eY8(NA$U@UD%3O']Ե݊:#y$kIBy%Df3 XJvf3ʊ}.xڻ\dC|:XK_u[-?Oq@ddХhAF((Slُd]&qlW#J"+إd!Gp5$`>c)& -RZ!0t; FŤ<5T(d,5ߥ( }1ISk租6w*: 9) Ɋ9emdp6]Z֔ Q%!4nY J]olx)U`|5$p6oMњAߜ8hm I P@,DET5ӖfJ#LõM_iIJ(PfV:@/ +g݉ݪFB\zmfLsLBz) ؃fxZqq%'N>Hx<f)8u/|W;l, eQ -]yݺXU-0.rP)/9qr;1{.E9"q91oA6ƓVT:.h%WF(?ݍm# ;Mpm=ɭW}'U.7(%N&Tݡ"{=@Z S\5 ,Xp0g !%9:N<8 1ocfL:}|x|?R'>Z`تeV_o˜Kl?a_B^^+V:=B \-WY> (=9+ GF7/aX+ ¢D&s@q&/a}?GV'JD0.i;}ŧ8 R+[^iM?EP6vEZM- /v4` w\ <D&֏D^ȹދYy6SܢltnV*G}A 4L3JTR0plg&ڮ-DJ0̱T7+Vek"r G^>zva^@4MMC^[0cn& `&=hAc+l H_WOʲ[ɱh։,Gv-}zr翘);4+N;\0fDyrdF3w]a Ngȟw('4&.Op$έi4h64(`=(Gs B‚}š>p'}e˱Y &>P=返wNRJ!nwrG~ChPk8wPKI?yjx_(KzN *w9PIf1,H.ͫG1Z^Y p꺒Qurk _Ks0c5b϶I ؗr˄MS,) rH5ūe gfۥ2M uבk [pYZյUDNYz[;e~-)`l$%W]XlA@yrkBe!66wiIeS<ߴun_9AH`]p8cW%Ts}>>h/'l>8sB<md4a?D/HW5}0 `i%oe fB @23V R'H~i'Չ.#X' YgI7+p0q !_}lVJyQśgOxjY)9@We"'8GTIEHzx?/_ PGCmgMW̔"/0f/*GƟP.g{_Xe8Nde v3 <MDk4&ǏeO ěO#gGMTCwv/<0,MY֦0$CouS_rWeP擄l^?y"M?O,mGǿ8[fNjlqo V\WBeO3ߺ>cYWN#?&=w /]_:j}?KG U~u.0W4iWS&*F3ϺyrQ/<‚{Pxz⿕`]C `Ѿ#{;n@Pa90byq,J[>_~?y^~^B.r8m;h1}/ n%4N#+S fL6X:1vq\CYvտ׽ R:$^K8_{`N;L(,۠* 2(Hɱgm{ 41cXq?X܆~a-K3;WC]eX4'\o<CKkR'.+S[}.̑嵫>Lq]~a$sb~q[;rfKOۯ@ojIO) oX+_ф`ٲ mS+p(aԟfaW!1c`4NxASw&zx?J?,ƶq9 +jIDeReHz~?u O`M%oW¿h;};@hm?/tz=F5@mY)0(шfGԟa?[tً0e+EA09Qq(LX]L^O_HeXqP8x<}X74!wO=DRHf=L)5*2Bpb0b4E^mt?gX_eW#`kVi2/IFȔOVF@DvD@'/ϣ. BY%tJ&W1|$v{YC $cr28[5d_"V{nˠ$M&)$I U[iտ~w# t>MˢNf?| 5z )h -UUIsyݩƒyif13R钺BݐWdHMpI݅FdL|qBln|pdDFs0[ nyGjc P0)61cXa{x8tAQ ?/3z |ɯ n:+tt@Q.P?i2mx|u7@frl>dqj-;#Y ekxL4!(^tvfOuNu+'@!xfAn~h/`eY?גOeIݎJwI;i׿NK_߆'/ n? \1<m]QK5jkLs #_`)},%zc`ўN:9OfF%)f7RApش0@@wHt w%.m8jh+u;p# ~#N?!/lc\GEV\~e"eHc\ KK:88U8kYs0[d謃k`4@)B ,ɭxO [v8 ƣffVq8?qW?DȈO7KIYyVWe-r+ͥӧ8Ukrs/=wxm/$pp8d<lԁ7Ӽf'~g} wGV (4=Ym $pqp<g6.+fEWn&Rn[? M*)Ȣq},0C)RViMpLչTLf GBpr8QA@c_{tyVmុis7 ~ӌ?מB=j6ȸόMƊXj-˄͚bfVW3EQ?J(Fh_T^K_`Q3 MeƲ nVa!P$SǴ1=@x+m$zFal݆{s=t_E>(/}0-}!нU \,|3z`̕[o:-M8UiX//>GV@JH̚jCY]^'pA-ꯄ ƐӶĭpQ26M1cx`@ot?x?_|+;'/"tZ  0~&/2]gTzLTg]ym?{|++ѿ%o?IGhgi"8YW"qxTպAA)[q$a 3WF`Fc4Ms=i++OC>O7>/ n8"T: uljZ9/V{(C7e| =oxʿ?\ /=lOڷKJ1ZM&LIG­1P4Ns3,:yg,˿k~?SHF>zFf'k2()@mg̥O+*+\5n#M^B;YPJ ߜ$gJiP\\FzӐp竗I\z{ %|⟢Ec#c^{yXVyYgOJGGP{C$ (3vxHG^g!CF73ܔνDޑw }Id)?34O;X <qQзw#O#!qj9)3^mZTh" ]D6t^++,tc{&o\ʟ NH+un-9|$xX[@ k|\LR™,VS1m GrVZL`aCQwrY2~ҷ#1? o /s̐]&q`oX eU5=?DyV\w2+_emk`h4,Ariz=6z'WϿQK3sHQS ,d[pG'WT~ a9XWx;,/[Z-W7?vcmM쇾` H#َ9k$0sg(T|)zm }µE$9^L`8s:̄ zHx믣]V;4~8G̅~eKzAaʛҟ&դS-DTZ{pGFɒ%6\yY]m7_~ҿk琟r;̞j FtfR s@ԫ~t^B6<+p8Ĵ,Fޖ}kPf%;Mz5yw?{7En]--@F6%sjU2TE%²9¹72 .FCտTKϑC>H$c̞js@4J\4\l%+׭&z; `RLbkQTH`ji|U\gRK^Vbڽoe|4+_SF*+~4 y?gVO6NbfO:Oy <ӫفG+ Bqk4\ac8K[ VsޠpM66Ymg7wWOO\I`O5 l,w *vz|8},aZKoՕ. /۟o)힖#O? -9.dY+ㆄ*6“毰lc8h=ʪw&a9Kngt:LoS}Ӆ:]ơBz!+Ai]ļz^ `y{xYQSxWшM??'?nؓO<MQ_oM]}1-<;{mWzƜ(\|VWYv]4 )j Lc?582 ;dts0{54}| ^$$}% M\zk~bX^u}UR1B d4n!LS'Yw!|&阜)@Nl$ 2(mUb V.8F 3g"X! MN⣚=HR}c QI$,W%_n:p5}a 0ͭTbR)ÈO%%{%%7 [ ^8q++tmw_9EIӰ_, ]̞v{) "2ҪP-1M;q˼o \__g4}σXHǡﳵyW#Ǐs;Ƕ/gy@F8p :sE$c_cqPk4UQވw|ak{5t+ց]س=q02؍ ,RƳ,7Cpv"IPF91!H1'=j5)?[8z޽lN@^jֳb_F:ͩMKwNɾv)TKUJ)(!Xmt9v@9s~z=F!ʡ.ٳTl&:o_g+c},) _ -%Ȯ6+wxuk?C\]:t躎8Ag۽տ }ؘ:̞Fu /q12kxS)(Z $)hZ_Nk[?%4.鎉+@dx_ZZi<+XLu.z(.>©:TXR2m\v^`%*@N.3ʇgQl6BǶgy"\0U7I}`8Q{4`(RL|F`OiPE4#@?CK+@WX@\#qHIr/Z,k w{}հ7@@P{Y mNvT,Bd>cq>B)tplMl"t5ιB$} phS1 `~ ԄEOluڴ̶BJnc C/_]M.^䍵5dA^F#8Rn"mRhn= H>NNM$S(QگZo^N"Ũ9"tGt;&fE[} 0n C7K?M _E~ʄU z+`Vch7 _}5_Pbl!:úv7oqK(j6h4rM5HzY$|\:e/:?#P>R IENDB`OSCAR-code-v1.3.1/oscar/icons/sdcard.png000066400000000000000000002110141417327530600176600ustar00rootroot00000000000000PNG  IHDRx cHRMz&u0`:pQ<bKGD pHYs  IDATxw$Gyϙl&mVB $!$ Ɔc166cbkcm}1` HJVaBwI٭t~뭷 g~~lDO!Z򥘷%KNjG[7񝖅+, ,ѭc?cW !ln)*Q/E"DzP\K)'w}J[(4Ů+ʇiE,vNoUF7d5SN#,$aXBA{l!Ā7h = }+p&`۶m?LyF y&{ KY/&B$p֭[Wnݺ>V,ŮيqэYR{ڕ3^w1ݰWϼl`"<,̭u;c/*~ORͿn+yoZ3 ,a,W~ DGSrV忘(_|̓]@EbF=v.d/Xץ6{d" 7߼taB@f2P}be߷'n[PGx/ ղJA`rz\M !.y:y;ݔN  X}X$g XPXbwZ_b N z~- :דH҇ lݺ&b饀=gcNԯBԯb+^۲ee˖bI!G G/ A2{y 7^_tT;KFqIVT%E^{e-d+2,D`^ }N^@Eztg 褜N @XJ΢ݘEoyxۢE00* X}2b`NS2&mRtMj@BB/|u6ȓ+r$b79Ez, z#Zb9 2{[*. U$D <ȷRR&ІnI2:)Z?`"c+p1NեӲ2=,Vzb#Er;)w,H)榛nJ_ַ5>ۡ,^zmfвۭS;~΄ AK!F@'ewQ]#?馛Ng;X$TzU 6tG~'^K'DU/A,.XDܫ/e%԰TcKu餜fe)xP`kr/Pd;X1(Bp񖕒mۜ>}0Zisss8)}Xa0 #ר}:JiQez$@Jɍ7xeB(蔢R2??|ŠhzضM*; 0]і{w^xeF-z*A_W4J. o;\pA4kTヤ>bXK]7 t:`V8q_W JuT~dW_Eڄq;Sr|sc˖-d< #SN嚖E (((,5Y|Sbƍ/U8׽u?K ," PPPXxի^'?I~]יn"-@EF3" A~'˿5y O]vHH@t(P]AAabݺu|ƍXX4EzQê*Ћ+Jv̐C Ez J+((,U S?S=^x!333]+!BD"=E2vW2`H@4(У"Zd2ʯ vj)f@=D].U$>r\HXj}{:59H@8y6'WuePPPXR8_zL$?z t& K r ~J/z tJY+(((T ׽uo^(%(KBW}/MtaೊPPZAAA `jjjQb ec@M@[o[oH4~|^fc1A|;  y{" EI* X'O,0 PPPPw@Xq򭜥$@E" ?//-ZSo,p gU E 8jbW]AAAcx]n+?,2yمDAAAL{;v^@(n}PP_P3 VlSg̍};9khx g3^gPǠ^N+Wd˖-j \@(ЃPو+apvJ(УPK g.ҖO l5Bzilٲ.8 E*(((xk^bW㬀"B(ftz@PCPk jLE:npVPP8щe5V6"]zZݻ"j Enp g<֯_ە"2": `lllU$ ^WdP!:Xtu|>QPPP:ꪦw(X SAAA!:l͛#jm, VAhhf@AEzY㪫²Ů E ((((;^vj,=dE, /ىL&_ @@ f5ո>(AZ{bkj 1[AAA'jA(kB-ԸY(P/ 5v8MӰm{p@%)%qŮEQ " g!PPPPPP8 @SŮ? Pk eE@AAAAס@H6n+E E&T((((tg똪AYzgݗM0PPPPPXj 4i,Z&((((,yͳP`Qu"لn(]"VUPPPhj u@@9 *(((tj-Cz95VB((((tjL"= Evtb Th0XP/BkPg8qK%VPPPhjܬeXP/B}q1PPPPP84(p@)P@AAAAጀRA9ApH'6{`+VR:Dbq;JAAጃRC%z~0M;P(p5я~MS'ń"eY! @k(ɓϓJj9=C&&ף^;uvR_AAcP3֡@p?{w[BL+/xaz-ZPPPPX0(T3~Rqo{J:?qڮ@.c||lu@AAAPʿ}( @wo_pH)2A/dZhXE&ZR"޽{/ 0J)Y~=ÿ!XbR -C)A~w)n߹e](^[5M! '$HyU144PPPXPʿP|] ' _(x'yߞCB`[B9B8B&=~"!l8afMu0100P!SNQAz!0 ]C검@`6i֝7RAvMPJ+((((D"-`%e۪- LMM]033ѣGezz I$\ad;RAAAAaѠ@ h?QR:y= h&sUW7-v*((((tfsXy!B|C#NG^ "????Am۶@=m( ikvSk^ڨyfggG~4odd2riP3VqtB _WyONN^v-6l@i >|t:M,`hhn q'?֭[*X` %:hjㅂBWq6Y((!7?8|;Jzzq馛ٵkLvv'O>}whh}k\r%|CttRC6GN\xfr0>u?t L3C& WaXšxow%`/òvR1ũ@& I0 uL@U߀vC4W@&F*N'>^ʕ+K2rYz5;v+ooҗTd2>яreq7/ngwϖ40 tѓe6syя~m۶}X,Vr W_M:^n << 0,50aX+SpjU)BS ` Ґy L!ص.<Vp60u&N\JY lp 00{V$sušex"h@λ=bb;G4'@3Q iAhf0'aֆaҁW ՛]pC6i|S\{iR)*ref, qd2\u]|{cxxa>WMar~,<>*qRAB|tČ[+Z bzwgw%Wͫ`8CtdxlD{!\oMs&\kA܄Y/w"R1,ǵE'x8|pc,D,OC!뒥Y,-!_~8U#0u6WϹu_xNi>RΐL¹[v+ P`m/\+ثi :t۷rw^A6_O?+ 0=CNv9,zWa?G5{աѽW+e@Gx?-ALNC8aAW4q k#8Oba-CFUMBDճ[0=<; O[趟8ۖ@ hg}[/XPѣn>VN__lٲJ[Ĕg&,7aEq[Ft,c8‘vъOvY!tiBu0xP. u!if1Mkhp4iM|Ʀa;KoyMw?#aYt| Pcs[=_!ݝP$U3y4:z}nH/J@k#]+ qOnڭpf@6IofAu\.ǩS* !J|Z%-dCfff:Nb/yp0ͺWU Bsvacc;O @B+Maq 3a&u-p;6~PVK`8o41Ko)z 3"Kb%'\ SpfwYx>6N8~yeGx#c^C>ŒaRXjk~ЫފꢕϷ؎IJ&s|p?{9FBco]/s$YX3_~LcuL]k5ce8f01f8NZ֑}g=:f h X`hmf7O@3dcI20Wpם/nk<5;,sygY@"ȇw=s8G3)s)ض~tnLqԗ~n;.m(7;$qNLH'H8=@.r{JPX(kg`$R8_/| \y啬X8h, o~|+_axxדJ#wؖ21u&D}Ӻ$.ds9^.1a;\sN@\xi.l 9+sXZ}c̿߀iQ(yf?~Pt匎b4uRA"˗322B:݇X>O\'Ӽn4Sc|xa %Jq4˖-c``73Aʒus07_<ҹyֿFS9fr,J^nˁVVM~{4MgE[_Bvn1˓+vNOS9]+!BlNE۳gOu4OOI._䓟$=}}x;شiSvg?N'^VT?C ۶xvgϷxv#N3<3Ďw?W *bq֮]͛n s8ve}Z05_;5ߑ.; lHMì=H q9r@GrSm[/6pl ˱ʳLg]T]T,T4֥Ez MDJy\:8LH??r;esMmMӘ;*ϳ}T_7Gؑcf y}_?$ٸ60@G$IFFFd0Inzlj1Q|[VmI-Ư2fA0 ]Դrv|,ɩYfsXNR^`jrtۨg(xLs9mIeZ+VÄ#'%.rOB)p(NoaB oybT9׼w޽իWfRJN8v*y{+j{7?uxlC7Ы\%\2S\akaúЛ^iH&r9^:)=z+2z }<:+`t[{Pܭ[)4?pw6 `t&g`b.jm4Fl>M_`VU`Ɔ]AIճ!&ϱZRa&emrsSiD9nG>mٻ뮻Xn{. ~Ig0>>0::ZQVUM8n <sla}__U}nF.[q B k V(MX 4@3BM׶zsP߽gou<_IC:oH0,}-AfsNNl(@֍yw$qŸKwrVwstMRhHN[ڑL&fqnV|C"200PJ))yRǹx{CN?4#3Kc&FɆ@ƄՃTd~4^w4k+[:56c8B3\eSޜ0]dzV͕VXΪA׼,ݏp*7#WHkxY r?y=q}ˀujpbf wBxG&gR6gOl0<<\*"|:NLa1 D"d2[O.qQ1>>8?s?'> .";_OL>cs*5! X^ W{bް{N7! <NmRhF̵:Mdsҁyr i蚻߼}0Ny e{n^΁SX9fsٚ.:23s/OvT#]u6'`]*\pMy٩ƎӧOsAfffJVϳt4myNA:&H8d[n??}{199Y:7 袋?ͯ꯲m6G݌}1ݨ214]7]WCމ19cMXX4RqϺ)E|%qhFFKdËrUjN Ċ^a9a.7G*[5܀ENp?Çk\7\uU\z饥MӤ#uBp'ܓipu;x޶-I+D`.+!c}W-HH!iش,Oh}s|ItMeX+$ds7W&ؚ0'f$+fGa*H]Nf{G'Lr6qL-wlNE3Q'Bccc̰rJ; "$X7M~r8al޼M6q-055Ub 000@&th=y,?<˹LiZ3NQ4't-!7 GJu<_>T Cj %!suq#&^%P̃!/%;h( s!MtA߅p>I$C|q 8LJXjU{3Mϐ;5oVƾ,6еW7ekU}~Wt=+EmV|NuC]\A_8t9qŭq}@ڄUCzvTƣu~' X?s,1#Q8p4[yڽ{Ϥh / -Y'?ÅrS1F7v5jk(TJ'X?93ސ%NFk캮T"ĭqmތ?J188iI'̃C*c09\I2q6/=^?2|~-î)jeI qLz[{?zbp#8 "FvO®srjd[n8S'Obf czn{=0;_"E識㯹>z(\:׶i >|ɓ>[?( ziMJ!cYeH ("6ۦY$Ͻ4f MW4&?oQy^<2lLA6ϝɜp" ݐ{G u+_:a{ɲl#>}q#1F+wj-zd'1 'R 69 Ga΁:Ѷ~`WM^89 w> O$$#093* &y0 t+Gx≓]"Ka$ޑ*x4Bz z2LZm''s 6$4;4CiH>JwŽy>{CsMF N Lqſ5Q<"9=x=O-^,ZY^<;7ٴbDwp=jp^D47`Y0 WgjI2 |t~clN1MBC 81ذ~D @(QkC_w b5@Z!t4I@Vw~}g .T-E_6,h 8j/2۷ N<x<֭[ٰaVbhh~wv U( S+W]zZz-<')FwS@R[󐷋Dp$^8ï2ii'H=4RQA!t\gO/^cp F=w\_i} )Rqhr,. {G`n<2l#``}qa s.<>7/y9$P$ | pZDB6D\? ^x>}aUVg7q嗳b t]_4e39W<%@3Aӊ%L/lN7V.(4M^~&ٌMcU((N|뮻xgy;Ν;"07qD{UbYFICМpO⚼W QLisyX@[fYbo@¡Ӱ~% j]?c9|Y&,~\7XRv\߳i?{uMӦP?t,p}?T"yH&?q}Q;vT+i40ǚW[{^ u%p5[gYarxdN咟xfF2aןiio4e0t w蟄Iؽ>ڕZCMMObGɦK$# z; ָS K]>A+6t,f*0?c3i9| wޘ`08B3PKX"T</O>$L9==]r-lذLdll\.w<a0447M.R>L&K:P9RHlasp]-[N~0/M3;Ul%aֆcIBٵ`9&BbKG /!eS9^:٣0gy(Tu.%pƧ`z>P g,"i70W6[!;l`׆$l zMiOz8fm$K,&ƍWYҤ/8.яrTD ۶m2 oy[R}>b|o\v-dĸyL 9釃'm9_0oo~ʕr) I$4 1W{3KMh㱶zi>sgֿHC1 rC}p8YX4fff'jَK/\Y|9d4{<o333r96l./a{ JlkFJ kWx+?e0x9 1ظfxf?bD:uJ4Ƚ+!џiΌtyۏJ$ SUˠCqGNMe8= #ͅU*iNW:}Jwj !tf5gϞ=kT+BcjmݺC00 , MرcG3;;۱>kdt]]1i}9r(9GJO Nh0:1g!EA"7 [g,q4!4N\zCxu4lZ xY&׌`hٳo?57GU۳#sR8}6hhFJW4R[fggBTb "(RB:G&X60meV/ x-lsk;T Q&1YEKg:/h11tCV8@R\n[ָ!=hIb.9 p !N:w@}Q#_)kϞ=oZz XeYt:DtSs\`G T\ VsNKb#vr8pJc{F7 Rqxv xO΃@hIC!%pL#fNL`/]m;n@h 9P.7!HJyÞ={ob׫PUs%7Z{H5idN+COޚUü8[b}YE,?9w/)*lKѯᩆzՇ,%NOJ  )yr| i!9P2f$O$JQY70Ъaڃpަf Nl,ZR^gϞ~_-&Xbt]gƍ^|E?Κ5k*Ƕm{ uEvU LKW2)&)3^9S})060ؾq[HGJ+Ke<fC 7o=m' pUWe8)x(~v.v3TC|>[0ay۵K)+@wLjَw8(5)[[~]ŀ"=fSA{ݻ[i8lqM73l\O?}-> 3a$(m& 6G}>p$ a,'4\&lVP#܂YlM`BFy\'Mˆc?&[׏ ^`|h:!}1d'zSpj 'vzeJm>iϞ=?Zh&=/`+&˗\{qwկ~__nv6 !i~5,+'*P48{>Oxz5Wn;Ϥnt9; OuWۑռ4z`Կcpy6mF 'l9t`l`\nl~|<> } 8< /iF8(])Uon߳gϟٳg]K 0].޽{233SJ 4MԩS|_k׮5N!D[Wkק80~ áq8| c`6<}@h*QY/ _t~_4V ˠR? nv 8[ҝtj>8H%cqDd2rX Gp:/8vll߭N.ytpzA缌i#ҳ~#|,笐o:t7]s;sӤbOŽM%wI``ڑ*+,06΢'~;wxxqmڵtoAv~~;wկ~Ⱐe˖8vDg1֬qq2Cb19ȄFkG /<{l;!sph/h=t S}Gt~Ck`4h~w;N0*%a$z?wް+G:չr03{}fiXi_ \_/0 aaDг~baqЋ2vuWRthhtH=9P;اR)v؁eYiM uZy.`5 S|['XI c('a>b=dN kywo#1W韞tO5?r*j^:L;ؒNkU:-ˁ?{U=n'ǿ34q@ZNSf{XP*:SR,X"gپi5kiL&Vzw hBC;q9yŪ?z)&Čw#%l9TVc0v7as`OBvٶv\+@r 9wvo9 D):_({[ܐ[~e]~m}5y7X6HFi=4 %}pln>ǹk$bO ̅{ 3Lg1>4F,5+%!NeSz"kP`)"%b5~PyFbC ȭ-(be-hRCi} lߺ!vq_ 9#!%,q!W9}`̀^ z[P(Cw]Uq12҂BR}q@K$RԹZLzy_?V ÁȓL S'.4N380&8l\ w= o+olT$L"K~AeYLO__@k :u&/jjYl]K!ӗͯ[mȇud5N0@,яi&r4/IpX`NuyT;v"t8H;ϡ'NŋETȫsO0&\Uh ^:0_3siZI,4 Ų]L/ 6;[GO9b>7Dze$ p;hڦFOɠ(8f~(7j/<Dn`J|. +RSJ,bvvŮa(Y`CFq `nnl.m;$b]8ʮü4O8K4c11nz nYْÖD ֵlٵ` TRk)0tT2nT+ot$8|`_LX[7n`eK-de퉓RiM&/ eq?M.cԅDHRdYfe52\54{9CO砽Wqk*O6+<>`'x*z@Vʿ,Ix.>qau|;"o>?3_t*u!CCLL] t:iϻQ ihl0\syw,/yA/"x Sb$\5:zG~8g&ckuM4|"$F:Dm{ Ø G2st CCʾw4BٙãcOP Hd>I%q[+su髦j(cI2,I"cY KtҩDɉ33m?>SQJW[^Ty!Dp5{R&vQ6ˏ`?ܜ _׾U2BkPABd3Uu1o4Pb{6O<$r9~7~ 7L$aW*b Ww`YnR 5D= I:|\xVcZFOqRYo^BƨwC{g3A/ی|߯_{4I]:B`z콮El1Q[EgՒ Jœ9QPp($i03NICy7HRmo#ղO4xʬQ9BRߴ'[ ttZ,ΚmcHלn5MCfIg7_x,Y|Vu{s9oykd\fGxJ}Ah#P^~J&I&v3{ (JGzbXqXjW-(zg>? Tq>8;vvl5pOvo [b:Ax>?Np*{|Kտ) 6?P'Ky[_s3L;uՑq@h"<8Ae7*TIONNr+_ɻ~wnu!o cܾvrZEMٮ䲒lo /knuD~ mɯOw+4%c2+h֣fo'ϟYPٴ+ J"#*uCiYu,}k,@K\tᅜi|;ﺋL&S"_7]v؁c%PBf SڔZ͟gߑ2)4)YKQG*A҂fg-Ioz":s=FtM 7D:k_J%|]\8,[ ;L}7=߻G}V0 Ao.]"}T6rmNUWCu^fw#m'Jo(iO_2D;2}mW !( RD wKrկU_vs#7nmn^֬^oo!oCwm$vT{o򶵶[ mo X[N-/2Alhnj__n= **e:lNQMi!}Q%]Վҋ"RA?>7s׾ϼ'''Ƈ?Av>a3cbP&H7)%G@bSߏ@od2lH_|Q[|Y+ps{eDԚi MnWarDXf%F,<^o-( $偪E_EEJ}_T*`k!4a 7|S Lp?Hy-DX?CV;ѕ[q jn=?z Iޢ|@ۢZqBWw:k!J%5 Y v !H[n /'~D9Wڍ+&RR/E :Nl=:W DG,JmWFԚ(^ht)_LI0]PzuY1kP`)BH^zi_I㤒EfL/86s?~K問-CtBCT55Tv Tt_.CZ!ʖk|+-+Zy@Iq܊—DZ)~zDI؍YJ+"]"=BZX۶b$řì4!HAlSaVeYZ>pAݬ2Z#XGኪ%ݠ x6$Xljʿڍ,tPPPq\kK/(;롬kx,F:еW&L077W$7o;!4þuC~E6W5tp4/1c$9jWVGZ t!D)׷#S5B$(@ }mQ(?ګ_)mmi fL27?mضduކ1+(aYvTQת5(HQsU$'52L,"i2Fk(Ŧ+n ,o4P#I( Ej# mdRE6;e'X|M -<|۲Hopl5u)Pֵ#dg៸M46T?UV^c9 YhVA/BȚ?̯@7R +)?3}k*pўx."cF*M0Lr% -bD:vDuA2:FZ&6oX_~6nMsDZhD*&8Gra u&Q*M(? ؋Kɟ@R iX*R#Ny W.]uWFcGYT#Ȁr7@[VZ[WɈ F4h ;nvg@uSBOCq9l\wI M+?j]h탹|pXYAd- HAecbOjЀ Ќ!!uDWȡD goOFs}p@33黑IIP P/_=U(Qa(^rw&i/yW 5rJ?$M@"u4@JvUk.:(?hDHGcCd#ѕ}ќաUw#(S!n29YHT&H@]yM.r5@WRt\+o:(,jٵ:߭L]<TH6k\+@@ -_R-Ko~%PFօj k@r}2ےEVT9,) lT2ړx9H̚yA##ɓDM rF}@}B!CoLhnYNfu8MI@X1N@o^mДrp AڔȯmC`_K%>9Ol"fX( oܬjbQT#GD- ƚIjqh#]WӴs?,&3 X-f9#x$2c4+ #(v@{\cX$S};TʹQ݊lݨG4j [B y!JF_!TaԜM!Xu;M)"s/4ī`k"uyqe5]Ԋ_jU$ ^ULt?`^PЫ"L?LV9A^FG"3GS*.fXg+VJ""'4]!*P30 VeՙꕉB YBG>cjbY=bt/YSfT167!Bj4"ObF,+Ә`D' BQ8ecVJ>}OZWUIhPP`E+g4MhBeqpl۶]N` ۍ#*W/ n!p]DeFh 4hIvd$RT_mN^uZY(")$ZSzXZX Ҁ##.(EeHbxxrYLP?pëdH)l*E YJ(33s|0;pȑ7\s 6QS^|"/U^9Կ⍴݂r2 lLj' ٔuEC?&IERSG !*qKՂH7C<:xҀgP P`A?z8? C8=6_~6S)&X12 zu,!&l@ t]C/~%Tv/{ 3*˨tR vkzy)BFz$ޢt%yhK3D#"h4("o5%a!DPQ VV-RDyy', Q7-6fQ E".~TsD7|bׅSZZi ZZ}=$c1R[o/v*",[ KҤr"hSv)lDn|rٰϢM sSшA:{s>1Y#|"ġ"2"*0E( -֯ZUvd,F0gx}޹dz|6!Mt_I ͮXr{q6EGX/s>w;_ڞG Eci”m kKeJ82*D}P8)1X8>DHX)6eTd^@RDip|KICf /S${ͣtCHb|Gܵ`{2' `zfxt<Wn«/0DYopۆi;uɯ*=-yD% lD9u}*|JI]M*kkl 9:0I%j P`SW"ɤ\{%< R!qJՉ#G2rrxDzXo)eTj4Vu'l !n-(Blj#|gB#,W/9hkB&|꓈ *PxJ8BhpE2v_' pE"x6/[,'C\we W0kHB05="-/#U:(7AsZPխ>N5scmF"+Y+2-lb]!1AY2!knpط qS; ~dmP蠼U(|Ab>?4?~ǟ{=xG$8H89p(OʕO*mla&)4f'/\WlێT,@":f@IyɾELMmQG;d4VkS2#n~J54}895 j%~Qkuh.phWE&c`Ag*slTu@aɨ$ȗ/(l~e߈hJ?HumQW Ј8KGssqlǶBk8~?kYr+C03B@P*d?7td9W*WYoQ"F|m͵q&A~ɼ1NW\ QTKꑂC32#urH4\_8+n=T"(gرLM+)y)6b?]xeL]W;u-# [AjY9sy# :AaF*(G!\*T;XB=G,+pʼ(7Q󼂭4VeSU-tC&8 {<Ȇ"]E8p#z΀-V>ڑ` Awt;3O1 /uj44!w(ϝ͵i6SeywW֖?F'&ኵUbS yYf3u 6NBӕyL"|,<̾ 4s(O=Y@fg"-2[m"s"j;B S E\+JBAR.|Y(_f:P[H*D܎[R.4D %ų,LM**ƭ|>Eֳ>a*B y]wN~Ugիg}2 pmSGDZ>Yj3D3K4v7lsc2QzDhD [bzhiB4:uZ)J(Cf% @Ќ@rChHi^e+_xaMs߾}Keq(+vbsiC~5Yr'+ODѨP=[[Ӝ٨?K/Q OS71hDQl77 @7Qz9( Ezu@GQ=|@MK^9❛㜡!#|yV$-L,%E&0M+)aH$!ߔx=b>sp$ɓqfft-MN`#Vce md"K1,=McCz#@Y7&- Ȇ𧯺.Cҷ{=Y";*<Xr?G!pwX}|>υWsgE 2l]#C /82 `۠ D&jFQ]VwohcGrS8}{r iY.(֣.F#|_?꒺f@g]9D. VrP(->|q3E%H[(+P!(k`XW"Df#IFHAu}Fᡕo,E8;HK74p2UpX#{IDAT`r8R4 fmlYmp}jsb,[xm?H88ťYܮXvVָ2&.0 }tk5(`>rϽ5>,QIhLځI#yf~WEhӒ@^m_>ʅ^H={gtA_mmLN8aOL"f%"p g1(Dġ'02˧yRP]A;Xr(f Q% q4Cm  =|_"\D1 v\wF*4f3fUfbRXe!r9DB`(<&lJ|VOc?쾗Va%M(AAH@4Ǐcp`>ik;"su;#+D}٦ ˖lHӕ@יT+Ɋ}Qs 7;fL_dqR \; E"@ޮqe&JFDEe.A\phHmYY ,oI ,˝ :f,Vt ,bdא~u>` D@P40_E9-i4o}p#*j"L~ 4 ]\e^RA IS~cǑyW>2{V ҮK;H>͐Jo{R"K R6mbB*>S &|[KˊV90z l txjDDCaE"TQ˛%u{[ ,5x{m| ./*NH*-֘Eqp ;;ws8e+[›}8d4x<^V􆁸դ/8ܷCБbVmE e!t)͙v5.z:௛ҵ RÖ͈-1'^|hs*pbPBܶG]+v4L ,)) @נRmWh4st}Yyiim ,۶qir/F;wf,_B[8hN",a`JG@ nh5 ] MW{:8yiL^uy;]"‹nL Èvi?Tɭf﷬7w@YE"l H'pZP.Hj:Z0Xu4X65)\|U+0tw cbf"QqEnźNrO=]VkR %m!nCj/f}f8d@u ,; _m<#EV*gi+(p-Oԙ՗~FLW˥tW+EJ1h$ r.qڲ͑(̶ɭ;嗓4sTTto[(,x"Q6S7DQɀmJ2!XL2Poπ4bX,`<|C]#x(ԩ4BZ AkADAs-j@WК0JeQeKDMvZr]JbhEJvZ&*ϻ;Ruo٩բul!8ɀ_f4].2 c<#؇>FK"Dp= $AjYŞVE"ex'R:$²?g-]Jrh]Ћʿ%ve(gۊuET { 8~"4:DZo/TP I?JN۾cZXjf,AMQ6au ]GJ[zHU -IAEX(Tܦmi%@z 8Cod 0MU*'A%/`QDKv 1s%-#fLXnSe^UDQB<*ng u-QU2d:Y/q͡z9d,WcjBNNIޓр(X}؈$( @נd4s*D۪g:S">:J00MWvĒ;!UBVQ&*!U@kǶF6dއK>ʽ"NS>*8TD!Ht ,!8R4 GvC4"mw"ׯ'neypH'BO j}B[:.t ^S|>,b޲ހ}D:g}0)hfLikP`ɡ8TdFAj-f׮a~Ibn iHwg[ܢ V꥓{{a}+r,O$ʻ^y9ƍ_rb"``r/mk(*AiۈCɅx( @נRxޑL4]~/X huZj{BC9{QFo-Jz:d ͺR{+zbtIQ)zv_VaHW;@]$UIv(Aa=RM~Qg`;u5INZiSERB)h&0]h8S|"!ERd jHHK&݋\s{׽?D7OzV[ —*kN ,IH߯ư)k߱s!LŢݵZ& B<8dyEh@i`%J\jn=KWa8Nj}G~;k>w#Dʭp"^)@7RDY^hj Qiۜ8o'_8chQp]nIEx@ A-5<  ϣ ekk$ XW74(Jej;X,ˠ;%-ubבJ$CLj;NoYY) -/h 09 'd*xΒ:v4Y('~:Rf˻}K="hPHC]e.<"r@`@5`ۜ޹͝躾&w2ǡ`Y*mY8SI*E4!3u00 0 4ma WB47βS/,a \w-ƚ5X.rb2d5Qʿeqp2 ,]"K ůz@HRhgw8LZ\dO`u2Pw8F^KS |-` SShGLNa<1;qTiPۆ2Ii"74 v"L$J?f$ 8f,#h 8IJqC;5gt,x,88qSYu _Q]4iZtL:*w$7?O#k`/k0җ,W?M]0K ,5,Ma7[%xU$q l:@JHI(LL xyϿv%%뮕$ 9 / ;08@ntskV35 3!J(d2I2h.A> _4/p p6iK0 2st߾BCwޠ. õhk(*}7jh@oIHK H.O $@[|&w?5X#:QEruA>5{hfluk WzM.jَ wm]!x!O?v+4A7 Ihcfsz|1t5|.+W` JHR)4aTW#l_}Ϯ mˍ#%:rp?3!BYLv_L:V<;)XUH$pL~d-C&h钀zxx-Aq2ЂU]qeX߹[ [/w@P}Aq9z{N)ԁ"K u-CY+e10}&pudGJa?8qqt/qH{b8ccfSڼ͛^t?94TdhKyJx0Er?m.!0av.RAʽ@tߟTY%1 @L?فf28)84XtE4'ċ˖@RS)uעw$~4|,\Y=2 +tCR؈_ESDv̧֬fE"v+pe۠iHzim_G{9HҏSGA+6>ɲd'ڲdj2%NѪTWXgsuECBPsƁ,"B~p>F~ G /`Α>rmv,dGF[>ߏJňXxgە^~մ+hwCP NӐ|6@qY- 9feA{>Ǡ+[$%t޲ϧ9j PC"M2m+QX lD&S3ÈŘ?y?k*Ҟbq]to/OjdXHĖYwMCOs.mcYViIDw+Sq"`yY]7z" |Ş=RzkUW+!7{fb$-ٰa,g2VWS`*7RgJQ ^ AʿOzD{IP7^i [P`Ij4R:wDn2}"T,/XB*9l"?T5H^eR21<xhYGvE =zÍuAx;ʉeckv;KNPH8nߊomTTԡÙ[3y%Gy'̻^{ԭB€d6<,ƀ3s̼y3GT w0'FVƥqꚫм R͉IMr>[nR{ ?;!y u}W}r( 0>MYȩ}݃T2$<5 Nh6<`[EvIsynw[:DH@:#-fq_I=brbCCC\D2:F@IަVt$U Naf`E~>"B[D3wާa_i5Wu]mU`:!^+lʘXP=΢[8e':^h `$>ML7CyxNOm!Y*v6P1*lE*S'D$Ke A{> 46j 8|q?{\C@&~/^$P*!V7$IK!H_wSH_& |i%BEp]a~껋B(.(#6`򬭵ÝN?j8k.|NP}/ YϤpJR@هdeU7A(&NK3TmY`elE,S x+.,Z \'l/0)2Gud &b^d[/Ot:8סRbz.?[(5]RinB\Cl5;!T:)ތߋ Ipz1Ud>1?܈v}Q BJɒekn.̫_g=Y߉1MP: T5#Y= ! qŌϟW 8ڂ61^Vݣc k1Ec`uHڵ 0^q u'piqO>:t$}{"@D؄ʯ 'V.,%.Le `qsDAFya Ml!ShI((qA|4(._K"Gط캫OHrdAf% %'EQ*I@Nj +BƮ/lމIKn-,U$KD I$$:iQylPQC $&gF\1%ʊ|2&gѪGɄ : tblݻIY$lr.û`Ci6Rh $! 70p%c)FPފ+C>ggNs tJiMNkw?7 I)$7$oW#GN$L]S4w\Ta׿SYr^tKPEY*$x0p6{Bwr+[b&TZ 9R6]Sv'ʜ $@= pyփImò,ڹ+#IE2v-g«};vYJ* qq   DAQ 5<ڝOh=2@T_|=nUNAB!ƪ(P6lԄ]k@;GR Hjc'aG5/bkC2@I?=>7irQ0@(HW'BJf'q!Sd %"7`E  -'Cas88l]Vkq 5Ԇ{^z讯ZKȴv+65o0`y .v/x' IPCӁ8y1tmTf?~ %owOEG(\ 5W5:ynp_G* 9o 5gғf@hyD3xGګu `0v:' Ѫau"JʈV$0 45ŸÃ*V*mJXhw71>c6IB7͠0`@fUnwF6V ހ(_]2Z`n_פF6B‡!RQZ dHٙGW{E˜/pM6Iy{kr(gyd_XF΢ 2xv|tU(]ƹ}.B@W`qG*$,LNL?/aC[vv> \ZD+7k9oz`}Ar s`v*< fa%8.:Vt]y[PW3Q yq_,(&""iC(&i˗J|;ݣz9ڌ+N$7YV-/Q @ŕ߾&#~ Im.1l~ /`~aKKKRF~[I#ׁ&"RA6d,}!'Hi%`-7i6+\Օ3cйǀP)q)bB|Ίˌ)2?zɳdyXuD=(d @TwIeSV6VL?^ϔ 9=$:dY8xqǎ *nzsK<'`mm-_2`x]r~&ߤߠ0`@_*kÓq%Avze-JN|ηc $7A"yP|Ri(GZʤmuҙe .E(q٠@6DlX rԨ IcF,`@`BXh)`xE`шU6RFdI=%Y$ZUHYjIAbE"crYI~(Cr IҨbhbߓŋXXXʊUW"Btmܔm$  l!:rJXφX@\r,aⓟ%@v,d~Y,mQDVLN)@qJmdsz&[>$50a 9{ \"ZVv>_~Pg;\5d$ \%BH  $f> f  x!G=wЎ:s3gwL]TXGc0qms:ƕ&X% ,T{U6)UaLq $0PD`Yla 3g1Fv ̚} udVt; h@_aPۅĠ$q:xX\^jY,<|bwoԧoFw%O.)Չ/#鳢g&|t_2^q+@\ެ񑼶@xz-:x.`ye%""Jn>&ef4!C۠0@(ŎshĶ.YA|`}7aS6qEile2XrSꢬP˕UZ'-'XS[@>~+DDX¡EaaqLWs H+At%šU3ym? U11O5rSu:v 5*oML}ؿ&ЩQNeH*M Va Nk  cV- +0GvQY'E_€IϜyfgh4|+|)C+ܭsz޵wh9' J!^L:OJ;3;#dZVWWɁÇ11O~t򢺭Y \)oqd`J$ O.l*}.ŭT- & ۆPaΣXV :z$jP-V=̀AGenCN*ucDZhQG 0q;;**è@'fLKn4i! H==R )Qqe+D[ 嫓4@[ oQ钏o D3Ocv~'QZP=%)Of }vV! x:f$ވ;Y+kqXZX@cu5CX: jj\\F+˟F:l r/~l^fI״+uRDƋ߇@Eyjy,ϣl[}9QtpWwI@Me= (. TPyDMb?w_n8J:T~5aC0zquٔ.IZ<$!#bE\uu]B9Gn-+@'CLl9FaGs h۽m׭C]֝+hz_;Ք%Fa@7wR>+6OCcnaK˾OPDٗh*osb+v@_S?O-oV=EVLץS F:EUsm.nս"s4㮇3sXn4e|"<8 9.e&q‚U1_CŽvyCaf~_!A%2?IO;~_z!P:{e"6*$/6 ZgVnr3nx,!-%Gb {5ZT={|Çz^?EH@(l0X'7@%8]X}yҲ{0nvb ~͘쭰ut!ј9jq :<#"mE[.u* GPUHTLsx|?*B[|] I']<j 28xi0}n,=|rU` yh6XYZa췼~ Cx풵?s}Bhd^]e"@eW)b*.fD겅wra&Z1@w3;A{vJ7vCSfFy`A`dFař,,.Gց VWV{ډ1}]۷;rW+s*Y+@FR  gUuB@+9>#=[ YrT\viKhZr@ Ɇ\_4eN(b0`@9/ /7[ 41]w|0'QnN%_}5&L:׽މ4ڷ+@Rsֶt++ߞ mEɈxStSPqHGgLĀ2VpN}wn_q^ӭe& T270Hps?q"ڐ/FQ-充ԖWbgoà㏂L7of&]O`9,,-o LeU,-먼͘GQ?W \\)Rx~+@vM`( 5Haһ#*q 4\x\* )zQixǁhqʜd ^02: &Ӫȟ$7J^K!Ah_ Z xŮ p~3ssĄL[\SM-p`?F?a~E?l>{}:,Ӎْ4?Y1Nb2e3!Gr7 gLL*DQl:Or;73oEVa8J}aYaHI6|e͉@&1ןw`i΢?\"t:,-.l1pLoMgz<b)cE^b+LRT <$-WhjaZq&DÇvۻdp'G%%"0`@mʓt0xd@&<,ڵS哀KN,V=*NDXk6_68:w6v|s;Ah.k_$3&sU4>MY)2梫H{U1n0t .~GLHʼn@Jz=~6j!~nBFC dZH:*$PvMaoӘV0.Lu]nWn>#\QFלV,!=2U ZRX͐cBtuLJ"2jc5qWy$ &ͪe f 8EHϐ> @IILa;0sf籼 #dnjބ?tTެVMR?2nRN9O) ,:{*']% oî ފ>gɚj!p]wS[޳0Xw4Ԋf`AP5f$`?~%2s J db}7c.IA2mx͒IJ5JZ03M JF|D;H.Ñ7c價,J5[O Y}bQ$By2޵ܩStJ@C$ UZ ׌Z:C  *o~Ӹ87o'j> ]vX57>e,ꎐ^v@<,a] UYy}]V²T"۠զމs=3 C +@v |"y'~ݖO?ɟ$xx". 9N$ *mc όr[DJB5b-MdqRA$GiW@ddz W@RF' +Qǵ)ڣ#p''P ezB uM6lSG.KHA_a)P?IɄO}/K}s;v, *nc ",.vÎkqѪň_kDNqT&q!Qp]jM!=*8c>@2_.LMCZfW".}tbeI3_0`P3Q Yϙ) 50[ ee1ЁqXKq-8pv.>K 98hFACo-ؿV5 ͟jP*(;9/.ڮ7U QO) БLh+H9레U<waAPwgExT!r%R90G?"`a[['߃)ggvzBDZyWW`߼8~N^LMM(x_sN|VnUuK~29Fpj-Umm8< jrZAn~Ž^@ A)}!@A+@@MtpX tp33_?O?s/bvnIzo:duc7`͠V2gުdr. HsH:!I-usQ2sa= 1VkoJkjA0Ox2GXyx׭K~+:XhaJaB6&?Ub/SO,7 oܮͿ 9mwet;TKUhq>jWx,]viH.ݹc##@VlvO '< SBBYT Rh@(r:&K@<gC[o¥K]zA_Z[NGe2M=Tg1SDHs$*(ŅSeβ yKWZ Pa"Xلc@&i_yi3!0`<s/ %ʎ{ w[YK[X@@4 }`5(uETR9&)rԣon}YI ѭ? k;&<|cþZ͵_)j)+cl^ Z]6%UC61]--@=W$^ w __.83b~<"xW{ͫ㑔ZXH,hʸ F*#I]^y' "$9<ڮ]rR$ e۰l'A'NqQ8,J0`AoUip&w1e}O kFCQLy=$.byyQ~"phwFɸ=HT+@\+ _J',&*@8W`[VONy`*⤫&@^aVF'O_e0孑 QNb}HI@22I/@#G`@rOH: ;z\hf{RV6-8O56@UKyW]]\VJXt22} 'tp`; gd1ЮQԿoLpT%Cٌ&Й "n?մ ]̤teq[6(bsWBAVCV+4am'2` "1>zd_WM'39HQ/ ݹ(cY Qė wb,./tzhN' c#B3CR W7CU(-VBܟ(C}N"~固Yc OTwUUdRra{nШErVS{P@=4wF`4BݹNfba߅"ƺt3--e&8(KBHG$)}nK5Zm^<~fOZ&%KAڄ\$Dq  T0 Oh5[1I@-[1+@:(c, k- 2ޯ}Ӿn 69)E}VҋR(/rS$`p/؎QVQXnD :: II@F+7 Cz#n},ܿᢅ|0)>!t&3e\D"WI" d<`j17?FWIk{sYf5R&XHѺr5Dk \{~|)jctxZ 3vڶ BF$FDj,5( lvcҥ(loƷf/MҸbCsVNICsϷ:~ KKhT\.G v^2nJ\l( 8N)l+&>%,Q4Ѡ0``Pݔi3gA.—'&@/ΣCXn4t8@SS`/|vZ~*_>"*2䓃\d#_PM3J1ǁ3:_ ؏p`^,YJ'~ 箯2G/$@x=UG$ ˲ ^ ΟdV( ȑP=nwt/Hn6:YfpYS fɬd o20 \t .7##V**s, ~`~[mjBrDiI=dБM򔓼!$;}/a_HG;hϏY@η:q n4 ]f[O0@ q,/,Ă'vؿ&nRniPUx,I@ ϞӉ)鲑xh}_A.mB'+L@(X4((򕐏y[T&WPmo]">wTX;$/|>2`݋% Ulďb`+h8 {_jc^4GVF"4\y+bGZd#i5<&V^y:ލz ;#RYosIg@AHB ʁ![;V^16pmPdYJ 7`(^|"}+ Y~IR Ut:FHd YfgQY#tQ4Y@72kٿWJU|@=T9 FQة{H@z0` ʛ_poHvE]iʪ'R Y-k-۳l<~+`Ñ閊,|%&5KsPY c᝿_1TE2 03WWѾsN B"?G82/5Ҡo0`3-3Y4=!,/-eq74Db˱deo`%X41VtV ^ Z]ߤ0x#;X+qZ&zqi} C6}܍&:6LLOwG ;Ne, w7a$7ZIf[lsȃU%o| h Mʲ wBV GOl?,U$ yM>waB/[_\X_y0dtX_+X3Xg +p@KǤgUeW2ȅARNr=,Xz?ȳѡ!X1; ", έܾD~_g"~U Г C0(IǻG? Z>( !`CbbbO++WRx$g茐u۱!DrAa ]"ʔKDz͒fq;Hahko:/ JH#ߍLCʺ2JUhҶ @Il~IDI8İ{1{թ),[ 욌&M/AWpCWֽh`xGGMYtd TYgl7^pӗr/aGztS?(|ZD (nH1gŔ#y,YPnWFov0{[;UsEM[Be:8_t)xEsa1Ş4 Ga@?aDV|aH++<ãѽk ,, /u@&yM bzt!(,]X5uB XT9} qH+ܚoARt,7/*W_@'q#X$ɿd1~N)Y8k| qNB C"ʶ@HpcݛaO>?/&k9R_Vɫeggfk;wbhhUY e˰|Ӳ\B# QU.W|dQ||uKe:RU=!pݵ\ycSS:$XXMmx'O=dۉw0E\) @"x&_0 TCk&|{v4 [$y^4:+e|~`'X`|ۇ;VŠ0`Gi#r*!`GfN#pր];X|~*3e@N#<>~5كZJΘN<\ :ZVL |9]a`N D|QhFW*%χw`?#A}vBmjj=.%j LC wN`b,( ,NnF) 4M6i떀!0xqD^VLhI.!B^qzѱnc]<GA)0L3OmOxzo55Wcdd6c<;B:cз]x^ jhX0?]vel:vf"0mm6ilՙ\O ; H:!V9`ML`QO'; X4$Zm}epm_#pE@2}G@d(r+fG9@ldE@P[v[ѼXBDDBZE^$#1yvϞ{$/"ȲQZصص֎5W ڷl40r<R~VX0, mu4;1O`U6D*CJqy^DQqd}0|&MKY)$0Ԫڊ܂I0-B{g 0`۠_6~_ypjQY]EZEZwwL`<8Zv#`A\7s_SN`bDɯ~ t`G~JBjQeGZqJ?TDdXڨdR*t$?_c_ LĿSaIE U:+5 C 敓6;d D8`6*Jt&ZZ->[9~5iVYU,*OD玻>CKeO]/IN]e.F-GKh{{1x4ǂC~M IAvX@530`[AEf<"xq(g;GF#wq> FI>wû.+lt Hpn賵gy?fH.a· t\ɧgLM<σ m_*`NkBq![{k|?s\S%u3`Ak/BޫVF&e6OL '{|@o+ f{rA:y<@ rDxvrΈ`Gg+GE+ 0aX2EDFc:+xX6ǃѿn+\Y+@dm2u|q]??hX%+(% !`(&yO"`+t+@ Xd퇤4z7~Cz͓U@:\6-C;ʕT&Tde *y @+ з`'Nc#T %MV_+Qz)#>0~Jc迯0`Pы&\B2m.ţUAN]|CxvlURof[de>p?'7\J:}t?wf+͑7޹}   kB[)80^o2œ)nZAZhyx$pHLG>C+ >MMJ~`hew 8h* [gGѺ`>_~Yړ C__ P[zJccVqKpFDl_(RZ}mf{wZҗLc*ܤ6IliWhyp]i?{Ż󏟠ߊ_[ŏ?v8w6ے0AG)V=&Xסbjb##V6kF1<,0о_;GZ\\zNKT63DeS0.<"D U~?+K&!``FGP'7Cښ$=AߠeDXi K>V̼hكQ ūh߹^˟O^vw?HTB H6่ZbCuMN '_c+ܢi{0ҫ'Ą,O*hY 49ʣ0?y5\qZF٦z*g Zhn8k]荛e!9 :Y., 3|ʛqH AS*ښz_]F}W$Pa'½ 2AQ uF*`A0tX~0ʗcmnb^GeόA6ytnGxx5"MWNwΨLFHS̈́u܂W)RQ4%?`}q|?6"J\0* VB.,x1œ8\}` @/mT7J ȶݨ  L_v?p駊 E`4-fG+[8z\bqZ噼~Du $@P'->̜Q) 0C|[z4~Uh^ cdhh0@TA=D@<ûc#p▤=cWD؆.|ZnXĔ~pTyg ?:.0: G<놂%4'SY!b3Y@lS7jaVB/ b 00O`_^/uk5ؕJd}M,, tܯ SCg{SC߿;k^拯G045T+ p h1'o~ p 6gc'H@Fh5}!rS-Ӌ> ,-:u ؓ>#*}3,@MsN Iz'C|@NW!o7!ϗT{--`00bӷh~|ƕLM62ZmZ¶,XzޮA1<){߇'Cص8?q[In8#:κ@Q#$rȝrsD<.yA: 'LlwA@'w{΀,0 qbn{UXh_~9hnأVV*XlێXoĀ۬£Ue|czp4.;  T' \UoM!`3Utld'f9<Vk fl yv:> qs 8Gs :c)X*Xo16 8e`t@kk.^5<sa=u KvTWTUo_j7E@CUL~p?s"Zοa[q8kRcNJ3^8}d,c&߫b`O@V$'Q0`[eq$Oz" tPT8x]4: ڳ4<7>ŲNADVV5X%`vla =PNU`N푽!8fL?k$|k8HdN$M*iE\>ʼ8L1$d QLK'k< PA|1_͔i9$] i0=؏>:mXpkثFVk2RQ}  FirN a>0*|i~HE#>I'$JTwU5c&pb_ay?Fg Mȡ_ p$r;Y€^>H"Hs#[a#OVS.Dd[P$LKv[M$$@(wEI@|Dd% "% /8H H#AA YF IX`PDR, I4"[Cˮ8Nˊ..eEautt3$YdY:+?#dq˞jKJɒ#K%6#}'C z еTE$e曡䲄+nR.LZh&IyJ(2ɿT *cH?_ 9J!B\l1/3y7tUЫ@Z#HS;QƲѕ'| + 'V ~mu@8=r*sה7$Kᡯ6ɏTǦ3.WQ/%3 "Qez(\*1TvhH@mr\\(̚D@ dP>  `` ;z/;FTC">3(@$ tD~~2TFb@ JH*:y&:mPߗF>ٺL'dE64a%$i[F4@ G#n+0( j Lv\ e: # ቀ<OOc6Uv1(fE]>9)tH0ѴȊzL"Wu'FDV>A3e#QX\v ,%_<=3  Pkc7.O h*I( OhYl]nFQgW ) ޣU!-*$@ ,_ 悄Xw)d￾EG[DmV PQN ;p/# ~gW@ïtz EΥaH Be%7gɔQJ1#4W*63<[wƱ \,.wR(?QJN*-x3˦K |{@p,aVAs{IhIDAT~vQI?vv8꺻6R+?|˔u/]x#t*{nforT["Kvit0rdX?J#x;]EXβ"n}ȐG%YbllDh_0`PQ.cE)vp͟@Eۋj/``I}:Xh4l/σi"g=0VLlUDR=3ID6i^Q*fw!~]$@TlA\)>/)4G۔00KNa! Œ%k[k{]s;ѩx\X6%r%g0r._t|QoZa˔UI֎B$ ,-i"ry|$d*]bXNj(,?V67 W0b)?3Yo5\Om&7Rj6ْt@$TUfFwku^U*-)TVDDlEd;{O/-KRNy(T-a ! %QV:8#ߡ$'TgVlHebSeo4$ FoKd*n¸ϨW1 d1.aD9%AGcbVIJ%rW  QxJ/ʂ+ *$#yyI@g U̚yr4( =Q%^yeҕ['zDN4Z"Pc ΙN]\btTUNI4(@s(m+M^i#E^~6/ @r$.*$au'1"|u@и{$לg P~i{jAo코eoF"H8[VWe#C>AB JX ?fB'yY2trQl%lv+@~ (O hOȪ Id#NRUS \*˒I@&1~zNX.f~W׏R("D(Q ir(i߁Cɩ:Jv"e%p^UBkɭAJ)6 u1le'{'°HsBngC01 -JXU~LLt3-@q{>JdK#Ìvd$ѳ&lHI’_AT n+h3Mc_Pt{V=F-nXTis)zu"2;2b *H$txB$@_էo$@Ҟ$ER"2(Go#0l r=`ڸDQoɈF'y?9_Qa,.|r[4ȜT ҚdϫTCֆ$LwIɁ:l/_ ½0!}D? J:ye^H.\/VD,RȕG PU=? v/"H׃ğir#=/0%"0lY]~j~`pC ʅOԛ3WEI,A2< n C$YXDH*|e d(71!.iN[Fovу+@}_̓F2ҕ (5ŅgN/ [I$!! `rN=WY1&~" N Y{-Q.0`,+@ ɋ 4*P^$@h%'.GT MK iKzw6Zc00pZv: -օQb``5hzٍ`!PFa 2@h@c0x$&OGѣh69v ƘY hDt~`N8}с]!oPlsZ8c6Z":}$/XG:^W zYDD6Zmy'6Z^QoƞP__p‹.hʕNs[4&\27]SO=<~)** (N27YSS3ҍ(;裏dYΤ>,DT|-"Q(  cJcxcdq'TD K2Pr3+V\wuf*Ć?<={xpp0ǫTzX$P0$`\)=ay @"B%NJ\\")e.!@TV"I(+) Hd\bS(( $84^o86#-[vwq(c2 d nFD"[n[nٹs'{݃a Vr8\q`3$WxZX:XbCXkb7`< 3,wпdGWD 5^#; x Lc gdd"<)3g1I27#)o|dZʜ"򎁄xZszsjBV)M( OnM|֗7@\P$zB\=@0IR.?o!.gs,cp32u 7w}@jYI@ [XgW*B\;ɹ55)Rk4Ye !Q.٫3(oMlo1&@/տ}!- ~_x9ݘؔ1V?裿/6ol\B0,m򖹄QO655:lF88O؛cos0 EC{ J`hPiiի򓟌9Sf$򗿼m;"ZP: $gV:9=TCOd1VÃAv${@-z)'fFp›omM]w#d[<]v $O=Ż|C .yCv)LmPOX~nWɝ EHcVEwgqFL27[~k_{WltrO\X+fi6y LA )(lAF>~eow$]@Õ!jjjjlDd n "o׿>jrxtu T8wjwnKb_IĤDkI2l=X]'> >+/:A|'sG?rݶL27y^zꫯ޾}R"4 n_ 듀C@N[5+o,))ўLM^{+b۶mJM*ntF7GNi]:x8>2en 8:pGQ}4RG*R\.5\s7EE&G]|7n/3k"KgqZQKk2 b%+Nb@^7Gq``%'B?w;fQ&'ikk^z%n,s 'Mu[{M+dv* VQbFY N1zw|``[ ˡe!TVV?/:e nlJ k~Eˤcy6uB | fn& ق4j|;z &= i$Nn Kƻ{ٲeyϋeI&_~x|0:h&fc@1$j+`wJ(s̹ǖ*cpcM~]]]5;Yō*ߪQAdP^}@[&#=^p")=7t|N:'p}WUUe>27gϞ:=b{ߵ JW 4D:eD>¦~M6dcFv6wS;C%N|P(voo}[cܘK,ws뭷riֻˤ4 JT3<@H|6!8rP|bj$ʃ01lzOK\A*L>}˖-ߢ(?;w;|ܘO?}E .WsaU'wV6~4(C݄ |vlS#>T/82e={aO)2.IL*[pv-pW27Tn:Y-e kvfW~qlKu"d|fe7U*Kߜ@gSG'~1FR& ̶MM;1Q*cpC??'2e.)EuƼc+SM`X2x?d%#'O=#x޴]^$?_ %h WG>u]r//x 27we]#9Ѕ31'UV^F V2$S1p}ˍ̳g-F w,b_[+&;K>裥&w _}YӜ3*gv_Rqa%Yt&.Sq!`9jذF!C + (IAƷ<29%%%= 'P=Ts 76l8餓JӋ^2}ʚ>*:hPu@C`4xI('&G: ھH0D&$I^z7,I|. 7_/~ _:t6'T7!S{:_ѐ7lK~~rt]5#UX-nݑoɸlrhjjZfԩSsCO>p -[ﳳU,ˮINמ|xAghr:1Nxo0C ӭ" }~O(8bFsn_K/=<*s7}ѱv\' m+kgWCSRTqW"*lV/>c)cfS t+ V?XCr]|ŷr(#/?yUWf@_US~~LہړLchx(2pr8 lC3"C󂎦,{`\aQp8;찵kזG|^FQ+;`g[Sz-|'NLϵS n 49.j~^pG1@GcVZԎ/t! ͩ\n̙3-v!) \lYvǛˤTUIS\{4D:ce8򇙇>b kӦX9]!5tLp\sϹ瞛cO~͎;-ZSWM=iT|yvz_!akOZHw8H+>m t8R(WQe ܶ)pIco|㷿g{8ܼ+Wd/*pW#ׁ[z'7SyLA4 ~\ <} 3QU(h~|&q>J~JWロg\;BQ{d}zA,UԨ4s.;z%y5¦9s#@GNsrڣ<`EsJJJ^ٳgQ)5e3xgywxŮCS(;&0(pB"6Q &b%ƜPV<}L]vEwKی<}WIUCXr)PT/gf1)QҜpGKtà(hQLp_\+푾( qnc=v)F|v&,\?* +J5U{2wFk_~3Lmg;І+ ~VrZ ۶m-SD,yrg讕5Kǧ'v O:Q$5+@3~Vs'X#9Id,eNsA\tG(ekv?_yV( Ξ=)YgOKmccr~4~ى -򛐍 =Pm@QTiSG|B %&na9&Ϙ155^)E/bÄRa@qI XvlXx?.ٜ#Y.EuΑ(ǔHX_{v4ʡ 7:Ԅii烮gKx(Rff2Ł#g.6{-F4  "\Xx>|{#єSVVr|W\qE^Ɉ! 7x| aU}V՗Z y"5 pMA 7dQe>lX"NOHYd@8N(s(IҿCr|,K,ywhfT:ǗHBW:M5t򂭒ka8#離!!}`/=@Q DIن ՅQ|cSgB-p=Ss{6##$`O?~\.U{;W -I2ORc%_bF+'7ІXq#N$_|=@^QgyOC9$K.{hg}䥳KrR8S 0&1Vfԕ6p6T7#liHq>mh4Xf&ARB xrgr^xc9Gzp覛nQpS+OڝkAjSa8@34b[$D]v9?C nnvF gs+ VkLfhlp >+X QC43'U=h8g}BC n^|œN:Z?<^CkRݵD9Uī3ډ /dt@.CӕXGp x'qq8=n 2p駟Κ5._G<ٷ/9 C~(#֨IS0`1PP 1y.<^) qHa6#'cIxCq 'O&-u L+~}d.$2Jp7ˤcojf| hrW! %q62Nsɒ%/.\|FDܫ((rӰ)f~朆\1 0+g wמXMQȜ5!#c!/ˤsc+HhUs15ȤEMUR=a+ C9NiEԵ 8M4ӉHwb%-ooZE<G=ϑGGFF;q2gΩOϽ fԄ4/ ٚ6/퉧~lal!z24&`@y^u")=~!\xGP zippnqm@H$R}a8 ( 70m͛O;4u)ۣwR 0k2/ACp瞃Q v9a„X,Fh%|񂆡el kJjxWá;)4o BD:u!(i7=qJ;d7ukMd,9 3S@fԨl=S8vWANϖ$SO3g"vb "Ӌ4t5vCH;ӺNJX?=!/&jTͱ$v!c;:a`JMS}H CGZAE:3Je3qvlTx||I"x8kGF T6qLEK@x*fd1LT-MOjˎ:3U">w)VP3dog& 8= _ ~o OI*$U41lŊI&md.323ўY-lQny'Ľ $- ej޽$zi}Q_ =C%L~ֱ* ܨ=h e[@Yц9R ,u)MD7ԖnѮɌ}wdR鬯W"buu_.bOӖ v|r"Wf>'|ca*JmEӐ?1etFi8 C{B9Q*4ha0Fd3At{2"O1 ҏ+Cw]0gF~:HU}]Yz)!yuV!/AZ̘*JD!r;K }?^$az1(HW r6dksNBi$wN|h?7p8N<ܟ*z7Xlɒ%.a|tԢ[Sk(>ASDoJ=|M&/}?oN0 1JHm4Deb4plJCpV['x`Xu:`Tgr;F#Ćtq9cm@σ): Ac0awD",ruuuUU 6m9s[Yi(d:EtN yڜoW/φM$nuƓ?`HXZA&FIl|4tI )u7Z[\w#7ÂMgҪՎFNOO`R"#7{ۈ׹ .t^Nҏ3P!{iQḂ8ww~z=76'? 5J0dFCo,ί]oȞK>wO||櫚+sNqQ⊢<߲,?#:؎ ܜ})fTJM  AҘXcclNښL*RcJ%ۓ: 7RP"Ag_zHM(wn{&rӿK;&.@fK8}[W^m#Nft!6͑ @SNRtd'Lnڴ)Υe桇ڰaӋqUR  hT- KiO[޽8~sKұķ!hT,$\H;?#$?onM?Ar# fMeOgcE-D"5kD"b/rWWWm*m*N&'x"qFS#!oQvL3Y'7!s1?F.4SMg^Cx>-ϙhnuL:Y^?"cq|"N9hKΝ$MSEOmL֚@,hOX25/)[EUVVp8@ G E3ԝc}?S`}_M YBQ|-p 8߂"S'"_Ȼ $jU{4ӣ*EQL?Rv,_~9}Na@ _:s@-ntwG元U?`h~4TÔl9ݝ|>ioo`\U~vt+Aە,7YH 3XCTLf4D%y==GU>Ac~[ #>7abtc%t_Lm)K&aC% Tɪ,k֬ׯ/Ppͥ^Jt~qŎWY$ 'gx6LRk8LXÔu~yG1HSi W!\_ը?160I@nЧйG *Lvwwر#uD֮][% n>^zxjՔވ]\ە `FjBf|uHEҥIe<9`(RY"\B/r M8~yc^JJ_hվfN-15=Bp݂"'e=\F?aիWuI{=‘2kds 5G1f7hJ8 k4U|K$L rWKZ+Èn-9fѣxt&)|ǚƁ>%k'jA@0fJЉ?6sရ/>/!fҔ+auHl1XPfQ:-E*P' <[ mԇ^0"@Hrh ef45j"c3Kao/8ܼK۶m#ZPU\<ΰ%蔇< L\@ Su'aڰ7׾H]5hlQtóc N}. ՀLK 8@!~$2zZ`W_ĩ7|m)8\}E v~~lkO9}h"|Xc6 kôss$_ސHvB[BZQ Nj._IUWW744[%/|W2r@dЪ8|9T3h/!ۖH#I?T!Iz!=̐o}vIҊINF(}7SPVJ5N¨A$ V-N8*%_8&{c zeOOO,s:EEEvzÍLaXDNơhUj IVK6_^P<@ oʲ\UU%B(ܹsIa_:1}a.)'f`O-<@I\ov//FnJ /\Cƚ30zcFm[Q$ۜbc8=KDS%lť Ҷcӓ2.-[6WhFbC2I.VBFF+]!cQb\%mnRM [U YLh_٪&8HjoĒx1:ЖɋI:i!O.!L(RoU r(裏0M 74jb{ J݉4Ӳ!1 3(>zlQzp{ql#T! tww[-7ۻqu QVm XVM$8DQ$ r nȌ u b aLOqx8~ I0A},H'L۷/_yX,AH$OͯuYn!e_YԑWՙ|xhh})-04G)ȎKNpFª98z 3&a. +m~(믿N7̽ 7/I\q i5 3ܘ32SuhY;. n[# }-AlX YN Eӟz ] yFPf%6$X,{nu_8~wr"&p1曉IaĜVdt22"q8\QqFڍdEr@,)ictT1@$6!B,M֔02\@f=J"q|U,*˥ "mm~?E;ےƪLȦvYG;Tq\E^,FA`-26@r o@/}y%8u I*Mp.YeVH$Ij.ݞguoQ*o\$軾&dŦ6ZnI$ ʨq,bbgViY  {oēB[H*bhphyTCIu`0e)y믿^:@™%44!Q45'Xo$&ipy?L-߰"0w$v"2hfcSʌp['xhI:*y~gg:Qwyvn0?TG"nq}gŬ|Go!2y'WȻ4 %EhB`eax8a (^pTwdiJڌ%aO,(3 賥kq_ĝ6T,UC~=Yq|ie1vO9mSuX2^{ sx,DS}J˰0O78~ʜ}= 6W }UD"R'{Z<x`xf RߞhEw4 fBUkR!kISSCD`QP -m,E> Hj @˄P0_R?[늳 "Jr=SCD+&.NQuEzؒcLo?V>:,E" 5JWy dm9P+ڧs)fL0t1%Hs4V mʒ\O08 2~̝5&IblB\j&y;I-w/qL(%,ݕYlt ԑj֥!,'fA7#ޏgcA;f .=SGqhfK1R!t^tiԸ$3H&݃tmi<<޻ロi-inCSʦEZL9MQRD*9`Aⳤhz~|lCԧ (/EsPt:`o|.H /Ri ˋƔo8& >ϸg\0O 7XonqSw&q -dbt_~S8(`N^*J-qFip-=B U6{TN3};kIEt|՜2>Y{zzzzz8A駟EŒxN;T;V,^@s&Zm`6::&ĉy"DcJx?hHgD`sšꍳ܂_0bꠢ\>9;ԞMnڊ{1Zj\.m;9hӋ©->Oҡz48cm,S(Q3pQcmVICJ~yKS @Fѓ='.|ޡ^9Dm<#US0 6 A{3_&aQQѮ]t@`ƍ ߶ 1%OzMԛy>K /SV^cg NUڳO.4FƸ*EDo7G`{x h2(Ècُmzg, y'l/(a|_6Kڔu*/-򔕕\.EQ"HGGX(ڳgO(2}9ͳ> ,`$ \;5)6(gzt9psNrq=R|{窯{Ѭ9SvH_:S:$G?Ho]Uz% 9iMbjbCsUST`d`` fuu51x˖-#'1&L('veR"5\(bjw=G&=_ٮΥ E#qо6L˴GG`Fׂ991ە9vcv~qYj hdUW)&q_TV֣0^% L7۷o'.Yz\wv#g3VJY#unѿ<[ߒ>s2ǎ6庫2ШVsLW5d3T>af"<|{>1&Vy=EC[=iƒbVI5czVj)0Xz̓>2^нi'.q1Kܾ[3w{Z&z*sng!B>r|Tx\N Ӛ9Q=Ez6_86ݨ}&ZGY hdw,enB~Ƃʌ'N~uՄ~I5WHkNCYA;2҇՟~2Twdh*d^wdF#CՋy!w>% ȇ-OY8yگN3GCHH_!%4= dy.$@i( pY :vcRŢ;Rmt J_AIu^FmkӇH{9f.=$M7QZ=t=t_J3 \sQZjĉb3g!2j'I.z Pg~81BWy =}邪 vi5 <5:oӺTy RO5LAJ~[AW_$]}ߏ"T .mL@CS#52 O iHC(4FdgKkU:RWL(qH.(@$xsAؾ};nl*S?81=ċݬ6 #,Ys&3,]ۯ6+d ¸z4.kk8mpD#f3OjK U1!qBYn7)5'=,]b~.]da].--%S@ 7S""5XԐoÍ6m 26Wg-%0FMTiI7Zzy zr{4mޑF5%lj拎5o;:sJB y)NIp(J,cGangLl)ssjᲜu- áEk׼H;k!)"|3)4- Khe&̲in͒?eU@&x FSA(///..Yl`]~?1qx9H{"&(kS2fWy+tuk!#㻤ڕ# N0DAަ4 O@TڛBX0ġHaRӘLmJqZ: (J( vW^!{th+̫E>4YRXOXQX(auD31%FQC׵8*"L3 piL̪ Lj@2[] 4ijQ"%Z][^#&7c{p۾3% H=R zQ-"cS V0YWnLJU '[M0 Ӟpyh  `Wcҋ*gRDtNb1 yp8ZhG'5t !+9J^esȚEꫧ^E2gB,VE5h2 SPȒҒ*M{@fk?bRED B4v1D"`x2l޼. *;10yh"lL{8ըLOڤef0G߈IXo,2sjgqiLƊ"i("LuJMeM,**"Z^{WR\y,fIƥ6IfńL]ʜ%..j) 6}후Pp!u眚\N^5 !Dԧ, q n)UD -k ɲfI۲,Sw{ 8 z4}*ٍw& $UwP3%D?_]";b486*k/>()mS8=8={h6֠ =w)zVNR@^EîdM6| N 3<2SMʤsnf[ [c"WKc^甅C7/#6`)h-*[ 7d2HӋB' 7t/$>fd՝>x[j#j8|u[`ޣ%nhMc٭!(nC,bY1)&3x sWF-Shp3o7i->#cP]}Xْ߁qvo|dn`pC-eʢ؆m} <ߨey]L,LQ"lš) dq'9ƩF#Mq}Qru8w ,#OvjCņp)/!pNy^>e n8@L'pG 7V19ՎEj-k 6ӭ9ƵӳkJɄ&Q#dxͳd=.rj4F$܈ZbzOT+" MvvAۊ,׆`W1Dm~ { mLW412ȪRfV5x 6DOίL(%Í`qMkpC܊# P~u7G6cɇbEk=@ !7̈TuҘlj:?~lǧ&gi i9L%Rnk᦯vTqyrѳ0+P*Gb L?fg9QLJfIC/B[˓O8pе d@7eII8Fڵ b ij8֕_4l\o'fYF׷>]2TvqP:G0Io%k&# &6BApLd|U[nETyd"L6]X,s[SpHU`rya͵[z190/\OI+S۳4&RAO-#i9Z!-z5!`IvXa3p͐ )k 1LhĒiƛl]8pʢgiʲ.x7|ok &dutI&o:W _3-f嬙kNBlv#I:0p5vu?rY ,a>q= JͶ!~ 6Oopٍ( n-Z^pdGh2KQt>#g49 ;*_h.v(}vCZe>ʿTx}nm0`A.I>+v@SvcNd7wF{zl=3zdL>c2'm 2^^ j<6Ad2K_ts,vCnK~t aҙ0\bZeQOJ2U$Gp| cG=S$1J% ArkF " ʊzliW2ƌKIl= sCexK wPWץ{S4$btj 5QU6ALLj'YUrR?b{4HQ 1X An_w}52n+B+V7 |KOn˖7g2 =OARHA XH67j(7B:aCHM t]M8Ž/y?G ֟xx5! LŬ!ސr/sjYA ,JWHe*BtSh3hazvnHr>P=Í;F'yYze0t ',$guzeɌԟLzhӤ#}ALS Ox\YkrLKbUEBGȽ6;U(|re7yLٍP0#2[DinB@滆KϦ C|z "wD(%ֹLX۟_.xDD 7vcI:0ĴPR'>pPgeEOR/3bf2I P6^yikz9Y_[^eV+iRҖuXHESv#IMvC@\QMg%5 pr,d`Ҙd*DH&5T2bނI g bI9QwCPW")SZٶZ"FG2Vp/!wRx/lV&=>l6mc}M N6-Ш-0\t\pzp+w g/" PV!rx- epp&)))!$w4TINҢ䩠oKcCI8#qt` U͌i/`D|>=a 'Bq 2R䪍H>p(!iU5$0/RG@P^d) gt0T8"*>ʆ-m^ QIm96 QYT+nֳLԢ= ہvvj%(Z3mkpHL/u !-XhJz>UPmYʂJ VEP!C/ö |wtX[qh-[:zBك?ajKgKvlV^1c"(..(b݌?q "ɯ̨R+䇑pkf 4:d']Cp02! $M❖P ', 9-Î/]#>a0'T88 Po 7>]e2̘͔)SNuHpy+ m16GuZ"A/EK:-= H&*D< W 1 kF"{p:jYǃLho]OR?<U/!jN`L&pMkk+1]@3)ޡw;^q`XY.-۽/Ž6s,8Ɨ8@6;q ,,Q +g:Xnt%9cЦmLϬlwEow4%zՒH$ln5* 7x,T9e Ʒu$UyYGzGCW JIWRn 2dUE6(xgr6K=0O &-)Y)8{>HxӶՒZy ҙ|ltB_>g)Ƥ94orKd Ҭ*/%=it+m6m㎸ڣQ֫4)*m>gJ,!mɗgm8HwKV gqy>pډbnc\5ǛY0!g?\r1.|`Jv^3"2yT >F!M7>&L2&5e!sIY_CoV/ '3s3pc{vI0i^STTļHd@ii1;_&UiuyxX:@T2x5&ӀLp1k@MIF8CL iW7xb9t¬GV0=æ7<1q$gO,r zͰv6;ZIxF=2#1?*gx"" @UnH?~/3R=èsd`iR84ajt+h W0ZjCy?d>H!7=P P*iRf8E !Ż(A7Tr\9Z_G=62F1_ճ4RYe%+F𢞵1!j d)mm݇ӶNמja0U6ԝIs%w6 ʂNzkmVSF$IRPanf̘AL(ȯZqO1ufcBzXY 2XDc@VƎ4Gt4yCȣ1`Yf9-Ǧ#]94*d?h=$c*)ǖOkc_ږJ~yD"Q^^ξEQճ,QGEL/qVI&-.63ny%d[ G/#*7h nB1iksOřQ(0 6_e wYC%I*/k5&)//}K*̱ 7CS׉B'ԁuv+` "NjCLѬka&ְL6{11!%=g\,GA]DV=qNq'>7WRRR^^njsyʌIS.9GG6YIp҇TO *-$C~ 2 ۛ("7M*[3n&ϿŶF3kl ?1;̈́EyBjR(Ynn" $84F :^ar~l\;(jA[5!Mxڄ.PI~{kҜ(YġkʴMz?)[8$S; ,q[][l|E4@$I&̙3E>D ͛?_<ܣ[03φPbYo4c_&OAPL hHoQV'N_˴YHwDA? ~l)ׇ-z^xeY6UBYx11H8/Eg+?O/Xh+/7-{ _&afrO4/q VHW0&Ă Yus?7=09fSfp؉('rM&$LԛԞ #PրRWvR 1vf`B6j}ϛX B%0A&V3 `ZxjvUy>'Qm)Dre7---DtOXE[xnptWo_T[#bt~+&a&f03oʣ4&?LaC>Kq^`dg鄭 ++SgF>&qcPiټJku9MbH ~ l2 _?X$5?īoO*|#ؕ#jjNJEDt~~6ͤIWATHr;"Yeoքeb& STp ÂX:|QÇ N;k,N(.|.5u`BZ6ڠY&;7WJJţ&Ɨܲ= `&n[KlͲeˈ1k;ehBw~yk%!Iw]3-_c# q(3h)C1 -AP*DQk|N| ,!BCϧ*λ/IX <ٓ".@1> 9KmdT8&FDͅ^HLOiG=5 ʣ}n |=E[͛V{Izqit}$O4t[` !~T)&@#S:lhBj-%q r (c6ifK9ڦ,jeYcnZ[[@RjnݭH;}ex# cx ˖WC| h*'h}#&9|&S!Oo`XNyLUjӳXso%/ cc lq@*++J$w0a1/* v$LoR'\7K$:ټ7烚^%1l0 DM6"d)UڋmL =(X1/kyH7T OP,CbA0!-9HgNsBRýb |">vãIЂ&( V]?m slb)`cT0_BmW!ݗ\ppD,C,R2jT-Jy(@YNjطT? Z"/l{#/햐[Wz"Td$AYy4)p8tKۇ?>0Y4V:'z$*"p Xr:/@vMr,j*Am`!`F!:qYF{\$I#Z0y;'TM׶H\M0*QKĘR O t2)DSq&`* ШH 9 kIP:3YTH7Sc5FtۓB}YJ cfKv *w K^VVi|Xpaqq^]dGH˸X[Ot;}xlӁzGBpl{l 鎠ǰQLle (8<- E*,vV<e-m&d0e>j7za|zB1݀ˊSDsIn3e3N5gC= D?(#NN&<$9@)%NՖ+'NsӞ&~Fk 1Hڥ̤"H n$I:u1="Vak2=Dr^[b?Dx{׶I^t`ψ> ΀'bcH-z9y?t+$=G^;8F3S/eʯz l taxM:!W :WNpgu1@OLkXyИpDfio F M%%& \Mª<&c@㎗v,ؓL=#Dd5S FD@3$MF?7wwcID}agZjw+C%$-P8DE9Wkhk N-Tm1-Ds+~O*!Tn$lcV]YA@)ǀ2FK =/Os nګ 1kԝOR =&GȄd {irZN~zs'*QL?&&m|ޖӖ@0,TUUWQ (D%lt;t8q1#-A)F!F`h500(ϋž~!?|"T݊OL4p8~޹ r)*hO9ԣ2h! =0'8o~?/ u =`3[2kȚPWFu 2Ea=sʦTR "^@r/M&C:OAMQ3ڸ;y} 'r|{Wգd y{y)B4Ly!S'v 3מC(v ?XQ'įoxSB!2X;RNN8 %6q{*З!3M& dC5`ff*$EW2sXChgWr{,ԕH5iv}3Ib.pviyYf[ψ?L'ϷO(+庵p 36C:f &5q:C,q41;F @Bolx5/x?wܘ&vlRq1S2jhorzc {+yv'iDm|UWWVI"n_}_`D5&bLon4'80Nu_8)%$Xyhg 3yA*2R >gVcaX ~:XQ_D9.?#YUFm0ӓJ;6-u:&V |["S{{? (:-I&AK_-vs!N$?ܛ80c0W8mPh)2B_=OYnh%Rc#ќ" ˅OVbW,v〹ϣC;_Y(Q.G^h:{#~Bء".Oz47v["%" 6LUeU> &g [oT?gQđb^3Qh[h-ڻw*Ao7>~0щ%ɱ&ܟ~7{-mjjr:-zSS`U7+WODtG}B/ߡ?eKWao|z+f-JA̠zQr0暇># ډ5 AJm0S&E!'d )\oyqE֭jk1;w.]ծ xD}ݡe ӟʬǧ{-foPwv$cGbhT4AbM 4tDv2>2+dʸ5">G&bc0 $'#,OEx^gMph. Þphכ"}>9!&OLː76mZCC1]04p!B5g9{Rחn4ǧW_6JɓnsM hoM jdl>"؄i(c 4:_BQ$r7MIUIOq~AfRq;lV>>q=| [>YDbM*p7E L&c  i*́NA{;x NZ$ @o@M ccGIFdMaZYD|a ˼B~ 8o'&XSS8A>x_5:8AϚ!h߿ +8F18|b 'LPZZjM&P@;E]DL|,J_&Aa42{Cߐ mMhu=$50Ij*J7^("G*s.1yI&]jx< ׿&*r!D$U{x ErbXi1G- 6Ǜ~v},}!C|1]:u<C!IM4)w\aᐃȓ917֤锗m$-iqoql̗$CrݿO(4CZh[4~:]:7^`Vf/OGmɓiXɾoGVcu2Ipo/OY=WIDAT-P6u~t릁iLQ/^wc~y檫"a EN-8N47 k 3ǯ1-ɵ72ov(n LT xȠ 5|^pSb\1P7eCuv= 1_tH78cܿrǥjf"L9X>/@Uא![tL)`:\( n w(dj|sI+ `OI AOyIimSvtt|ٖ7Sdܸq.s(4k%7w^i7 26r뷆ς1OGXsmz1O{^ycdShN˦9w4U>nB!&az2'-phlo'G'_5ㇿƈ ȼ `헻Xs1XSSSwyY꜂FUUUaAJ8ƅP <_ K0d3c$AI|m-.#+tEqU 4Rw9z)4C;/2xE&qǐLxcMW?I>_uuex#08U4gV?nEtO2kפOv8 KSpvng?F䮰XvP ƈD#_\1]';Zd|76媞/?'~Kl.a$;l&}a?L &Xk߰aK'<+X9m;!a㤿_\@YNnN0 ya؛f6+tF@ p 'cmooJwB Ȳ\SSGhx^$ZMc_211 H't`46K43L"d ?rLea1=it. 80x]+K)%W-Q=M``%lq `!m}ɹwo5 \:DsJO(3>S1Gxq#TغN{2Xޚcu-L#I [~#'9 8b xdDŽlTБoMnv _4iJTTTj*F > &=b\$nҔuր M0/{PWa &8uw:Y4٤X$cbpAFhvSՋ w]ZQ], !~<>]?@(aXnbd /ɓ'bK/Fw +ܼK˗/7 ' (} R?O,MQD i. --c-ᄙk s Rrk|>h@xhFJ=wO)ЪP91 4ݺ?*#FHINYYY2\zumm-] P]]l2/4V#8bÆ 0}۷gJ]Sg5opPj-ʆJ#bj/0HYpugy'Չ$L1p:g!xZMY2h`߫^`y!˾0W(Gdt5ˉJԍE1͜9pZ[ZZW_mN(Qn>Ԣ~R0DzFQ ٌ i!!·_#|/E JyZ63s[&@VLUel1:>y؞aB\sb?;~Ski<̓ǎA=D'ek!nBwA7nܹs-8䓟yqŎk(lvT*#"?&_~d_ D us%3+N?)jWjo:T g`qޅ1@33w+oc{kJQWC-5~|hYX0XW%IGa:SN9Nzo}[ĥydধ8oSDP_2!5𪛪T+5#N%\Kboy9tӃ%;|_׌ fMp\Dwmy2} lG7L($k?](Msi{+|Ù%,کo02 =Hж$  ͳmشiIº򈛾2# W#p"!]-ᑥ9D)zǒҲ9Re)3\q3 ᷷&}Xib^?Z%#Mwm=\%>ր-0O.\q-)SկOt ʈ ^{%,1U*>>BD8̇o7tBG(>U q|0N訯ʊPO˨Z2C@^yAKףt (=~P]-)݀\XlKcK2ÄL#%0 ܾ3D^o{,oHTVVvǛnnޱ#7pnppx)<{v iS!\8?&aMA3_U&q0쿃 ;I4"t \Nׅ={PyPQ"oP0Fp81q< 7ir4SX>g秧{H5M)k6-[66mڴڒkiԩp.˽oGn\z5M9޿ɗZ4ĨR3pKə%MoBQew!}d'6Fq M` <G|Ԩ K|S5V8q"x^I:# 7pwyx'I&*؍c̖+C2 %1vWO V΀ܙ5$\hm^G3ߓ 5%UDPk 7 Ԩ n%3fHd)O.\~yٷo_jl[myiSCǦi8$s:'Ix55y IRW*\r}]Y|b#|0RPOv+,F l3CQƓ+GST5+PS"ҷ~V}TυšUSǤб\NsID\_J*=X|`I&V kʺO?tKXSNXlY~ha7)9_|E)M(YL!)x[Ά\bz<Q+rˡH$Pl7%tR >B@Qho~!X[X6vF~`# \^^~ꩧZbQO?1ny\x_-؎WLMbIxxKx3[M@( မ6`3mgΜgϞ+TUUE /._I'nꪫh8qNE(|elxp.}}D];Q[*ɟ=@E({gs/P 4`Pp 5J?L} < W\iuANZZZbŊ?^+⮻"Jx,cXjrB>bEVT0[c-b!z=AKcV'ee]^q4}a?_,퉏(0N;k͑8OɎWVV6eʔ;jWvammmW_}56dTÍ, ,ظq#-CSשueFqr!ca 9o|gg]w'jKqGBa`ԹKyJQ@T6%-E8R5)i 4 a8mAR[wwyV*L\rʔ)>?jɓ'2I‹5VP hc؃J<.p;F?t*rM'V;&Vʼn*Av k tƐM4t XCm5o|M'Emllgu9{/BрЀnfV}qUO܋3b KKh "d  ûnyOwrO#W M`G8e^-KBEn$ $9H"'qR8 *"|nTS"L'U;&T !ץj7mbL0NM͚]8H[9tΝ;ns$L>}ɒ%VړCn0˗/WiSƗ8^=\| 3!7 f͚ޮ(dCS^|+ @#XA8y h2cLFS0,eHt4@7$ӌ;T+539X;vx>Qg͚/Ζ---W^yeW2C n`߾}̊QGϬwfq,rhc :@";@:dX t2U2ѭd7`?1_Q}ٳg߿.\T-eee(.[l̙V";8"P$ O`dUAGؤF#?+u*賑ՙ5]afC2i0@ux2߼a5,;ca L6+J';pW^m8-( 6d>Rm&ygtY_9n0ꭴSd { Ar{w )S8pg^*>#:({5W>SpO=YgF$,mܳ֗ZE Ê6V|%Hdj 5@Gl*&"/מ褆5qT>쎝~z)1cF{{93a{[RRRZZZWW7"}D ]SOMOہO0L54f ݧ֭ M~`o5Rq ^|' Yfٳ'M^oMMMqqYgeBgnW_]bE4eܝC@^zݦJ{TErj[vkr289 FdF__~p8RfՕܝI&L +لx7WXDh9@/22ERhc$F1B Sq_g(G894"T#a}á*P }"'eYz]2"ɓcؗaXԒ|f~O?M9n:.ln kt0`Ũ#̀3e&-XLiHf_ +vLcMYYYuuu$ |>~_|Ş|6lpڵmʑTaFdbmEJlN6 _ Ezm>544$ɆN;ZY)a̙Wbca8||_gsp;OU|{~9،if }dSqwS~2x? a8R2*+޿}> K,ްZ?]v]zù@% [7|>H$qê]^WW$rќ\m;Ϯa'`! EjH) L>mժU---ֻO/7l2lXnc=v]waXǁn_^s"mufC^QDՉ>!-h";A:,Q|)xpwyg$ikk{衇b;wdu " t0 5?f:r#{ trJ &g}v}}Kmm,++;3G|( + zj8ET9n87K :@$;@2Ƅ<$Tyc!Eՙ:ujWWɓO>g0M81L0aŊöws 7F|?'TŔSDLzRMia(FY AN o0g6{~|8_ Ǐooo?ӧN ,immݿ%K.\{m#"wEQ 6$@ 裏B!ӂ +[PtXs:-lL^4]Lx?O2O=ݱ;¤Iy晶[fD3fٳgj[fH6mOlދ/1fМj1C@> tl%3K[>;Xg9TB&~Q92E0F1g;cI@IΔ7nϞ='p¼yrE;_3.=,TrB)|_xPp̠T4beLǠrdI@@-”)S:::&N|I ԈX^^/|!Q"cpP(tx͎;xx<F٪*{ŝ"\&zR] 2px w.h+cHbM~K  F7cչ`0xꩧKߙ2eJggGq1ы,O=Ԗ-[R^d2nݺwycZ!$"#YтZOm)L@uA(LOܒG&s:d c僁}(۪ UG"xӦMk֬D"84ۥ\$}*lBvxj #3@1#N!̜B2ƿ}O&qhxŊ3gI#Yx纺bY!6{G b,8FAhMx?ޠTʻ׽~GV{ꪬ<餓jkku L6mѢE/>&Xwy祗^e9;v<`0XLI@njhaFb&D px?Z:"&dtUI'4qָ}:YG_8L$iV8qb4 -7o^~y:uj{{̙3?K27c~)nݺh4W$axWC!Aob6Z@Zc n{[b}P\;%K̜93W555)1>>M ׬Ycd^ؾ}kӃ1QbӦ39cOC}|`3,2lSz(={E (L4i``SOmnn%9ASh.fV97xir7:|悫DsغO!9A7Ey"H ={GQUUU;z'Nlkk:uҥK?3lJ0} / ˲:&P',oݺ>hkk+**LC2g daJz(Yˤ5ō# ot&<Ҍ)2O73Io-$Se:r].WSS۷oĉsΝ:uj6K$iҤIuuu'xgl툼VdY~_yA7nb DN*=|BK8bGKsTx/P@`(?g0P>Jv很)$NT$e5\:t:̙3w܂hSa~ttIǏ/ܵiX,o(A:;;m۶u֞zWr&"$!<8P"R!c`8+8Nu竩QC.|g^f$ [nB<88}[ٳc)%U}}}QQQ(4iԩS[[[,9[R3dY>̙si927-}}}'|B%Hر#EyhMMM4 #}+##RYS477DGA&ML&-Z`1H x뭷6n(IR0,1>x={ގ1x<,Yᨬzd!4~ & [$I'N ,/^Bh4{ۢ(ژ޾w޶6_RRt:Sćs(ł ޲ǧPfww| ===EEEK,:uv"B.27#/drӦM,hI$ٻw޽{<REjŖLGQQQQQQ*7 |ꆆǏ:Օkll\dIʳ>&d nF`n믧mOJI<^3+as)l$I>v;Δ#H ԋ]]]]]]]UU5+N 0qDñwiӦ-^pQ'[~-[R2K-Rk```pp0 hv\ ,Q2D%%DTWWcSGEHMJ!j,p-m^󥈌+)))++)//=nҚ~YΝ{G s"cp3J%G6l$)GEQR388#He.=4bƸ!qn7BH$QEQt:!|>_qqq8qa;;;[[[? &t>k27]zzz>7P1Kjllx<,+++1CCEO7nܸgϞP(D̓9]رN;yYg -cCp8'lݺ(bGBRaAn@,:uӛGY19T%Bot:EQLs(뭮N6a,vfe nyQ=5*Evt ~>p߿_IM1|{׮]۶m;xp\xN{8Ί ǓH$RZ[['OxFuc27Qwww"HQQBHgDac"ukPnܸqc2Ms!`05s޽)E 1@pqݙ'qM0w:@ح@,!!c٠$;9E0_YWF@sUqǑ۶%" !~gםqS5FAm<cs-~#UuXksιrp;?5L9//-/bXL&}/}Bd3Ϩ~΋oNF#H"4M_^YeG>r:Jߏ~ ϝ,v_/e̴io.dl6N_o$I^ԉǖVV,ˎVkT2i ϝKO;,|>UUUU0 Rv?};rS`ai/*"W,ʓjmtddye w{T,mno˲"ۅW&eYT*~AhEY#kE4ju7x/J&EQBta!H,7Hi0o7gN,_.}zl$I=t?J&d"|٤(aVnr"N$Q/ȲZTIA^_X\|ѩɾ$eGGz>]*Y)IR_s_oNc:A~7u |R&Lr43=N4M@,+ce! Ce$Y((Ic]x[lG"2uCBZ |e8~8]|^O$p"I"J$H~$IL@HaeYt !C!˲X kfD(fY! æe|F)~85EaCRbf0 ]}zjZy~@n EQjh̴eZiȲyP4 8omo禦LڎFc}IXI|^_!٘>p BA]? L| 4M#X~˲puÏ.ĎH0cl2oF#a X*ɲ\7f,ʡP魭gΞz7o.LN7ۭH8R,M%Ͽ13}Vx) :n$7HT$YQ@QhKt^Je4S.sN[cH^/+33,ì}?rBQTTRU$4-A ~SNv$I0+δEq}c#k/e^H$FG~O˲D@BeYnۡ`841 ㊢Ȋ Ӏm0UX5 C4 B$I_Mqu]wlYAIieI$8kFQaʲa0p])0LUU]9Cmmo7sse IN íAQTP|xuӲB$I`gpD" UU|R}e!9ó8:8nYH4$I,hHEc`0 EQ擣` `BB'I UU-أm p!232YBeYcpW`Cќ"B|1t}@Lt> CgWt~!LF,B8AX zC70&:\4MI {`>NL1g+2LrH,D8EE1M 4, !W R4a6켡GsZ;[a[:pXa  u] fEYi:k;W[Hv[,tYqpB X=Q"`z/2yjafϝ;/: Hy^^x!"|Rn6fg2h4:77jlrFϝ;ww#4bD5l62 %ǵ`0j6MӶ;EӴJR&ذ""hV*bXPm{"0#CBԩSN'L$,@ 066FӴ,TF+p||>4"9[^^]4p8{s=i>J!AWU5JJ 쵵5 lm0 xR((4MWU B/_N<6u]J!T<== ,\\$eƅ TUyW\|N' p:͛7#ښ+~]{^"j޺GYkkkXL`dYnZ 0>>N$drX,8N$I~ zA 0L^/᭭- Ν;{p|>eYzNӴ,ˠ `0iio~òÇEx60DBV+{/#$I!V&eY<6F3;TUi.R0 b) ,,ˢϱ8kT0GGG-*J,if;PZi<6۰i!a7sߏ($X0`ٳN.3BR[m5$ qo'@bpَE >S6p%lPR:5m,ˏ>axSsf?dXOYrF~hYO9sf-X'hApN8~0 sv̢NC l~N!~4j>cY&B=i,M MNuŽx-AdvW~b[d3\& "|(V*"v^BY]͏*}~3ey;sL*-aP$L B4U𑃲VJ|aYo<hau~KjFG=s4oj븏JM󛣇>p#O; aXV]V޽o{׿䘢\ϟa[+ CFx1 U+r /X^v+RQU;{"2Em='xO3,s4Hvf|I ,|CD&eaVWQ-Ot;2B˥'q Yr w{H wzbG,hy'_YF3X]Zq}jfEQ͕ CY&q5CU <xBw aĬpx840Xivщ$e/ݪL#Fs496fXI"YU{1(A`c':e;Pۣۜ!pVVVVR\zJA k׮ n޼yUgw4|- ð/BL&{zH$v…VŲ ?ORoiکSBi {*< r<==}ʕsA' èll Ar*RD8iҥKdsssffW_d2)K/ۿBaP([o9rX,N3??l6N{g<$BP|r$|oz (<Vׯ_׮][YYz=|0r8._rt8{;C)*϶'׏D"P9rdkk BD"Ha,Bit"0MСCSSSA|U"B$I,rNMVOj#W=N'OE1ˑ$yĉ^'BO$ BneYsss333[[[ErX,V*ᰮD" 山v-b"x饗.p_bjvng{Op:ݝ}ݕJ͓'OM29ڻND  &&&VVVArC!gWC4;kYV>ORD^pZZ%I#:M9kEQqrqtr9p*駟e2NJ>G#t:NQl6b&iZU$izzT*QIvNieɲFM4MC8.d( p'b,t2 nwiiرcZnTU8kZjuddDUU0u]YXX(* n!4q(!{ Htfff<RR$VJ%?;;[*LdYVE1 CQ`0u=+4R("l.--)7&rh6 Hl6iaVWW'&&p?p~j&J(eYu`׫sss|AT:vVZ-x|0zoEQt:a`ៜ~:XiFQ,4M:8miX ð r0 !dTVDFFFb`0ڲ, ^AIp(2 0L<d2h<(e)n}>_"$lZV+J B j5t/˲(RU50 !&J^²,iB!\.(4n}Yݮ(<ϛ)IRRu]bX8z~/Iz%s\T |ѣ$q*KKK*loo , @ ( E@ OMM.Q~?˲@P(XZq$I~vۅmkEQ###8P(dDD^d2nqp!Dd[XXv7666" Z !IJ,0|___k6;;df8Eid/ 4d^'kZ C{hZ8(J2"I4M @Ei,f>qt`PVt]#`'Ǿ4i}e(dYV4 #H1 ۊ8p mIV,cYVUU@$"q |@^>|(_|qܞ ?4{vC;y(԰UA~<>d{ ok;;7liG{iww~ޙI{'= ܶΙW/$_ KV )P(ݮE+B^ pC+O%`f65M4M+iUUkv4 (F"peNNN޸q@`{f'O$AKNCӴk4/몪R$BӉF˩T*j5I]0i@jx@O}iUU}>d0 B$Ib1]>a(*t;sAG`S7 !0x fZ~(Y,r/~jYV4 !AT0,[W=,*TR8hBU2 aELzrr/..9rdiieY$~cY6xssJ|mN(|=lJ!eNFӴ O"iuӜ%lD값mΞOR>RDQUUe B^8.M֦Ci,ˮ@aЙe˲v4q\.9sqYYffccò@ ]?Ǐ`066V*JG,afр zբNsF ###v{uu5 zzVݸqc|| JӵZl~ǍF# yphiZ0 a$ n.k6W^D"ۂ v'|P(tf LTbfaaC]z$I SO=b`0iZTO?RFn6Ѐ^B!4y_^^>~΢|hI q FRpðIhqdYi:B B6]\\ ۍFJrYTM$JZ|>E8.LrG4I ?hkEQc#T_YYQ%"*IKKK7nܘ7ZYͦ_<|;8NQԐ @ ^j (<|>Y+J($AlC)ZaJJ@ pƍb-gQgoz#m vJ0$I^o~~lž[v+ˠCnmmR)88 `|j $kA( F#ʲFggg86Uʕ+|>JA_|ƍ/^4 ChRMtuW^1M^qt;^e-//ͳgϮ|> Z|> C[ݓ>S;w\zB TP(s=Dzz] Ö/|GFF^X,((e5^B3<qBhyyfWVWWW.Mfsdd䩧ro q${&i47oޜRUbg{1~7 Eqnn_}dd$_r%/~T|f ~qq^KӺ8qf._irܹsXlܿF{'s )L̮T*Xid2Gq4MKӱXa ~X.9|> `>|7HqT*U*ӧOR@ |>_*4Mx<zf|0Pz?qăyfZeYa`Dz,pe>|xoV ! Jc6 UGw۠DD0[voa:9u߅l 2 'E4mnh7`loĐFjC!|wlgY:v} EFמ4N0"-jD_3К&hpUZ-O$ KORUU'g';{v09vz:mDrCh`gXF?ZId,[ l:q#N{B!;]:6{i?uڵkӝN ÔJ%IFGGPQh4,@홄ai9qץ}ot'mp(@(\ RU(DQ\\\>  }{###$}YeW]#]@u{]#(O?z~)$.d.,,|W#TX!}*;mSeA u6h%kCF@sPAw=U EQpg+{ 3i M[wgYB!T ɓ'*;8L wb'Of]فQ`"ن I@`w#}?gq&pxBu]@<{Lh4}MӠ|4 Fr `>3g`0h;VE$x]L%r u]%viZFQa.]'!ڵkL&HȲ'BUH$R(4MA ieY66̍s*JNX,RخT*Z- Þzv],9;v=vn"\es(J;zhؠiٳ.=]Xl4BhPw'Npi  0~*`S8666`P#bkk+L};Y]]V<#Xe{ҥKJe09r$޼ys}}=HH,a{4n˲lT44~/25͵NH$,˂Cz?v ,g[YY0ii<ϻtJ躾Kh>z(svr)[[[j5ME4N]/xw~w:xC ؘ(ttw$IYt*vF0:xd2v`Px 2( L& _mwF' Ȳ,bq8pW*b`0f.#bmm󁪹{:N>t: 4ME\.Jr=W j @Q_\\! DA {>~)4M`08H${h8H4]9 s {={I$fӽ/$IǏCI{ԞD")I@䤦ix$5.Rzvj!bJtv]$aɮji*Kn;t<]:F~?9IRo' kz'I8 C)̃`o:0Q > { 6Ǚz!ᡧpU^AgfnJ^w岍µ=S+`~}`,1tEQW\8rȡChlMqeyrr !MNxA;_aȡIc#ؐziorg ]OwVկ~5??jFGG;,9rڵkEj5ϷdI8r`01c.9e!C ص>uBnIIl0bq}}`bnw:j*r.zԔ S),$vM F.cNs ]>Cȝ:X& a@b,f ;v *%F"`D GL\.gf&) T J#)uN5ՍҲ4r;! (K=bk |Wܩj`0%x<>66ǡ5\$al6 Ά}~ ,%ttUO&3FEBx<$#p~ָĮ^cK|=nqB:\l4q333e, inE.874ͱ1UUaj PBϟbjW_ Ï1#%I2d,;k9ZǏonn...=zgY랋=W';e2$I . fr*R=v(R577wԩ̇o^ P]ݫ2q\~j.}O)q 1|j\r`A*_ʟv6͉ 0~nN8O8׻dH~b eY4Ms0t,k|||rr*uyssBhv)ɖ@`jji(o`  r9B#7=ˣ Sgsv#ŢaxYY6„p6j($I|>HqhZZjr@Ah68 py!a>,Z-W*P/$]tnWP(u]EVFMw5pG>---A3e4GGGc ëngО s#_98pNC| TřZAPkd{{; H$Hjt:~rrruuLLL疁 c\u::55NLLJÇxKKKu}jjҥKeR)<#@,_|0 rz=n!СC[[[V lb0::Yb2VY CDJD"QVa!puLz*f+ ԊuI+F G#$ICAt]@ UUyO&@ ț@7`ňEqtqn7dYl6 [2˲q BP(By?1MC`˲677 &ϏB $-fHq&>I@R';1 cA$| A-F`?1pz`tܮy?:4tICa#  &|GCeRCRU5ϓ$Y#jLRo5,BiF"CmKR뚦MMMyUU40rn[__mccCӴlzwvPСCPnѣ[]],kjjիbYح8>999??̌Ky_Fpƍcccf3Bx/ !0zޱcǚf^P1X駟^zd2 Y#. Ne`tU.F0tI*,"[[[GLL&>/<2RvB!KhCbqdd$J%J>`{H` ! imsAZMtm$ z=UU,ہg-Ct#*ڭ |baD"_ aWX; C]fw *İGrՊX=v6Z2$ѭ5n?x8" 6EL= >f넇U.OILΰ+8FOb00w&g8LF`ڍm|{x](~GHӴ .LOOnGFFdYi0XiѣmXXXƍ`paa6 svk׮iHhZ[[[8G"Yϙ3gAq \ƍPv]y_[[ B$駟:0۪&e9M"tu=/---Rr>]*r6֖eY\lt:tڥ0t\ZZc clF$U*p84|BQ_!`FAqOoxPէ%vn=N#6/,v p]! >u] }5Il𓍧 _fCɾIWgڮ"+;|whT7 % 3wKm6=ݣ<-vm!I@'.(v::Gjv9U]JRb?4I87MSQQǯ_NQT$ی i,~|nvСSGէ}0Dˣ;{;TUuJ󝊟 =Vv\a>WWWw\Vٟ]tIӴX,dVVVhBP7B(vGt讅ş,vβЌu6 l'Gweee}}mEQod2?4 .|l6{MP<vܣʹ[[,r褻l=wRFGSHٹ߅Tqĉ`b|P(4>>>::JDDSթ);B]UǝwnSg\W'~N]ܷkC$5qI݇wz[5n9\ vqOv`0LK 0W xY0s ~[mHٞco F."CȇѾmh6`N7X z]uJixh=$+riCT*{{Holӑe9BԲ,H s2˲vXZanZ@s.a0W?!x  Y0(P,8^z˗/\":y~>J yW\;X,B)WaNyytUUaf߸qlJ666\{l 1,{O~UU=~x<(,fJzrw , nwcc5@5KϹC$%,KBTT*[ip*oN!0 *~Na\.z=P{ jl6֦ggg+$ImĄiV;H;evh騪qHQj4vyWY^dnr*F^DG(d2=]}j)9u}{{ss0 MӶ&'')&69#٫xW9XC XC>;, ;$)4$m468J ,8h]a2V(eiF.MKVP%vQu= ApPU.!Mvy}0o裏>u=jT*e0jeY/Ϝ9#TzVWWO<[oE"h4jڸ(kkk'Ol={v]OX,6 ]O>@O?f9??aӧ766|$9yIp3Ya,]!# +Wru}zzǏ{-`# -4V VK ATͮtL=0=vTUFn Qgt B *]bn] ! wG#خT57a3풉9-lx;;uh~8^Z=ۀD~߷!:ngM4MC5vX슔n`?؄v~{ $˲o;vl||\+Wr9Yas\[]]=|(0LRDOÖe?_:f04M͛PnyRV%I)JAmP**TyX,4}=aځz~w}EygVO>aR03<r 5eYr9s%vbqkkĉo6˲={nr[ իE}GGOOiyUl6[ׯ_7zꩳg ~j#Ν;wnA$0eBe>yVR JT,,,D'N1A>|vBQ|<?~SðF166VVu$L>S.VkrrraaX,b\jɓ0z:v®-pNJ &P8~z݆JӧaPdbbtA|>T `; mall 0y=ߵId2`bb0@ g T8$IAܨIlWaL&(@əDxh9 . /As4MCz &ڮaAUU `i,nYe whD@EQi(d#( 4=Ed4@8SvE;qR;4(LAaiCj,96;B!vQ5u\Jw [aj]u 0V3W0=]H}ۇd6&Sj8{$Ae8Ӂ~4{T`N.~$ |3fg@]ϼܤվ63aOp8 /CR TګV{Wyv& $-VF^bM'MNsNozիE6$LZ[-Y 8]<7ORދb;ѣh4 T*e0vWz=ȷqd8@Juۜdָ4|j} .S')=ɐn؇e֨[@2JjO?too`0(5͂ L&~? z&bjP(tFT4:pN&0`3`zssMsth1C eyffp k& FC~eZ ~r$M&R$Ij6}{?I|? @ Ky^!=sΤyp..."8%MAZp Í7f3nB!I^G`X,U m#rjF0(/}N=Nx6ٮ\.Gu"?0d)À :JlE%H$Lbz^JDQpFbî%4ovB(!3R)ԙL& XE'%u`3LVS-VFۍF)RIR:QH#L$I;;;8aήx<s{ॖNU**˭V yzh `<4qSr~:Vk2x֩8KRlB=ud\rPqܩ8vKKK,󻻻Vl6$6bټv_lXD"xŧ4uXlcc~AONRFpߴdMjuR uV$0. g<- LT*nV[CW^='*JXl4A"釠=PUDz pLa C ZN$&h4޸q P3nR~`jPXD(Q fYv|0 YVN~{ʡPn#vi.;d(sQ+;?=uPG79痢; efTj5ONN[4tvrŢRE:: nvCUzkZ4R MŒYmZE5[,U*%uǛ@,߿Rimm ~Fh4DQX,@'lX %t4FEl6kc^J%8zz X,"pFoNRVEt:;;;F#T]͆eYJ (۷Z2 o^o0HV NfvN.XDNR!`E; P9{EQOitnw^FvFv*nX,dt9@; &e(FqU_y# zU?(0~:Ru3:~bpLJš9;Wz?.@F9Sl6%IV4F2=uV *Zv:zN]R(%O#R*JjT' 4M0 s ~Y8R͡ :h̰ PT@bt:mfff(ّ(@AƆh \\FAhf r[nQEJXr>ho"`Yl6iBvt(eYшD`faVK<"bP``0͙G) bXlZ^wooHU(ؤ <_^x*=ܒ$Z301˗/…BJe$"a~FL&Q.J]Q(FKdlleY%9R{PCV!BǃFJBp\0E69L0pЋha<`$I$g7BV u0+ZpDB?d"Bot_%{eYھ塙`DuT*zU UJ Pn g&&<$0|>\H F;B=J"A$` `I&t}G Uu/Ӂ봡NmuHFZRM!Q0N0L(r\TFT*'&&N' 믿,ˮ)p8LIaAHdnnN|>/ZX,v!sMJ-t1c˗Qj CQG MQN,(DG:D)R(6^VZ^JEQ4 8 JdrBqiiB RD)UujGj8024Bt `2hi6Uqh:QQȅaIIHAz 3eÔԁll6Ȳ<66F('v;%I'O?eyvvd2np_j5Q$ID"orR,~nnReRI-vիϞ=CrJP-M\ĄNN$.qLht:\. `0pg6Ϡ)vTZ6;; AèX,W\ahRt ) 04fPeTew]zn>e9ZThRLdqZTz^P@{$%67Zz*˲Nnu߯T*m.8,x+(uhK.!SK:PF3 c4/] (mo|f$]=x)CRD872ح-T*J$zP UT! q@}@Pb5<+_ X#:f:ςԯNVz=q2- 6y"y `hؠCSw[_ʶhMwR݀({, 0HF`$#mdFMi}}o~@3gm?:yoD5cW-_n~Rwj@J}c`H0~68h^w|||vv9Y lXdek>_\\ !IFL&nzv遢^3D=\2D!ںxٍ6 {<ǿ_!N|z+Wzn2.__dO>.\XZZ* jڵk&l, AS .x*>,◤R/VZw{n?w:?>?^Z- yt:ׯ_=7o?zH/,,$/_~?r\$)L<DH$ ߿L&H$yw?v766j'|"r(z YIDAT 6N/--͵P(ɤf+ H… v{uu---qp8ɤ^sΗ_~mwwa .rP(w 4T*%ȱ>,u:U(_}ZN]zurrr߽{֭[Hn+7nt'Oܼysss'?~ZE͛V+K믿v:3336-=yΝ;px~~~{{?Zݻ@Ν;N͛l6{^;;;߿H@Q[ިz$)chj$HdـC5,?o~@ (^dYtR ͻJj<k-`_~_uJqFNNSTtZ-nT*Z|z ޺u+(={ G6"$aJ,WWWď~#T* vqt6u]0 Z n,#] ū$|, V7oxoGjrӧo59>5Drybّ1߉h4jFt!C %d[{c(%EpZ^8oZzR(<d`~ @1Gh:?wT׳ZdXȩ|' ?^׏t{4 ՟b$z{@XJ^լSq~|)@goot R(*bi n@,y zgg702,[VFd)L&Z%2Z +X}6')IRXIm4R;'?cH"Vݻw/X,R²l6vϱX'''A#L&cH{ _)!?e2t:]>W("dh4t: n)pTUxr\ɩdf6 )@5fT*(Fh4Axfi?cS}>\.#Fj3L-{  _V 4@[F#@T*UTŋSFc@Q0 6-#cH xyBh,*È4+Jxz=efߕJ%A/CNe ~:GWT ۆ*H~榊of0 SjZ|@wHvŁ 焎r$=?XXR:/dSZ$}T*XFl$+,$IJRx#tȲc٪xIENDB`OSCAR-code-v1.3.1/oscar/icons/svg/000077500000000000000000000000001417327530600165125ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/icons/svg/applications-viewers.svg000066400000000000000000000253331417327530600234110ustar00rootroot00000000000000 OSCAR-code-v1.3.1/oscar/icons/svg/back.svg000066400000000000000000000674231417327530600201470ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz Go Previous go previous left arrow pointer < OSCAR-code-v1.3.1/oscar/icons/svg/calendar.svg000066400000000000000000000432301417327530600210060ustar00rootroot00000000000000 image/svg+xml Calendar Jakub Steiner http://jimmac.musichall.cz calendar date time cal OSCAR-code-v1.3.1/oscar/icons/svg/close-window.svg000066400000000000000000000226771417327530600216630ustar00rootroot00000000000000 image/svg+xml OSCAR-code-v1.3.1/oscar/icons/svg/edit-find.svg000066400000000000000000000125671417327530600211110ustar00rootroot00000000000000 OSCAR-code-v1.3.1/oscar/icons/svg/emblem-marketing.svg000066400000000000000000000243411417327530600224570ustar00rootroot00000000000000 OSCAR-code-v1.3.1/oscar/icons/svg/forward.svg000066400000000000000000000173401417327530600207040ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz Go Next go next right arrow pointer > OSCAR-code-v1.3.1/oscar/icons/svg/gnome-dev-media-sdmmc.svg000066400000000000000000000251331417327530600232760ustar00rootroot00000000000000 image/svg+xml OSCAR-code-v1.3.1/oscar/icons/svg/media.svg000066400000000000000000000654031417327530600203220ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz Generic Flash Media flash memory removable photo Novell, Inc., Jakub Steiner OSCAR-code-v1.3.1/oscar/icons/svg/moon.svg000066400000000000000000000170711417327530600202110ustar00rootroot00000000000000 image/svg+xml Weather Clear (Night, 220) Frank Solensky weather clear night moon 220 OSCAR-code-v1.3.1/oscar/icons/svg/preferences.svg000066400000000000000000000464211417327530600215430ustar00rootroot00000000000000 image/svg+xml OSCAR-code-v1.3.1/oscar/icons/trophy.png000066400000000000000000000365731417327530600177640ustar00rootroot00000000000000PNG  IHDR>a cHRMz&u0`:pQ<bKGD pHYs  {Ν[NZ{ֆOS)87HHJh}.g-8sDZ9:0;.55$ DMc@Hī~Z9~:="@،bCIi2(FZkue`8F}I"mW٭LpZ '**A)(Z1Vj 7 -B`;$B)piNJI<' RE{eY, RA ]P({A+%=?(L &TIRTsiOF1%z v_z ~Mz= ;ǹΊq6tk- BıK~𲗘AvRr}.$ F ;BJrDJv w|EϊbKY⊫?6lط㛾/7Zu%Ϋ7D3hBGHX!D)BK#R`JSRX)ɛRGJRR B)Ox^ < |$K ݪXUrw +@OFB$*aR!EtH%$Ѹٙ*>Z3ި!Vh֚n a-#:=:`hR3o2v oռwTnDkI De_RVؼo\m(%fx[Z)Ù5o:1(Hf4$DccxT*%m4`{RJYu.ugmJ'E \UHR=' Don6|l!4&F89s+wZsxktnh6+@gRzN qaZ1-ĐhC'qLYܵJ0w^ș-2s$7"J^YP}0cӕ?+8@b'$ 8pA (}P\ ,`e 5,P:QQhTi6*lV*qT#k$I aMk{p%@`ۖ_Cv V:"CuRK0e"@H ɞW\L;Γ}uv88(2s;׆ p&N(D_+e·N)MR۳^7>Y%IBus&XcmvxjKË@ g .ѹBf̐ZKd#7}BHU8Y΁z }_-Lbl4BG26R(p@- t^:2UΦ&b6FZRak=tMe# {=jQRxKx^7׃RHхJY2m-z^ )R s?Xu0d*Bd8_}o rsi40;vwz!s~w7`2olb% H+ŵ:f곞pI<@Ӟ :f#FR:ڍ#і(9'r?З_g~y:x\.8W}{NCŵ(tRo +UܚU.b\E'P=?Ii3@4B!NI:q;oTϘYH@rzB`I:THaA̡1Buf7lE'[$XHΙ7Y}LB Dc%gg_͗mGςQ4lj0sVnؼ}ǻ׿ X+^ػ^yd@ :@x3b1ww?^'Ϳf[B6c@tl {Rfjсu GDf7jxTcm@nLqVKzJ?':8M3^=F@2=u Ntd Q+V \ep;k~S;^OAv[;S⿥`}RevNO2T28)!;Ӟ5Өb!̿kc☷q$=t5Rz~Qfup'c}C'h#lq=]RFWEOb͕BMm-oˀ|K o RBJ)=C|&9,VI10l1_JTk$XdK%+dtM!֜HYY(B8%='@ &6_%mFHL"Jplt~ \x m)B`{p8zy7—BforDwkmPu4q(kOV-J^(/Xl&w'1C@8I4Y #[:p٢sjVm [6kBc ǽy"T6h:sRimRm'2:TU*!P ޱ:|͛_ƒm",s860r5|-G$V/\q8=f謙yͲ00kRkiQ鰼R:qv\ d?A2,2!p#̓o +H<'khdn>r{G^S(d %aF@xһY0!iOŀ0ҀGnm> mxYXgֵ^gkҥSnyig?gjUjsrNyan?NIy3LفVgLgMyLw֦nnc-֘ue9k,fd7Uq(e{{2?.T{{O ~ҟ^,vQthaCH2PȚ5d %Cl-h9Djj|nȷ=kPcFOd笴6>EƼBĪkbћ"9o4Moct)P7T˅ϯX[.Y.^Bۿ6lpνi_OL:S9Vr XR'  k۝@p8ژ\L- ;^ so36-0@jsdW1}wu գ|*9GZlV>w?!v~GzEO6yc.Vk~@<]pWP, U5/fzMWc=vdN:! Iclu\Ķ'3O%1]kz8Ru`Z`TIt嫋Dϻ)mߝΕ8]j8QoT>0F~C@{"bY[~Nt_qt_aS408O*i3uȒC1&˜kbI .5]:'c-K;OkN:F:@%rrӂt Xg\[ZS>}gIPCF\ Oo8.߶;1$r8)@#\4T!uMnbu6:ЖGc;LJ@ bGGoTBv HU@Jۯ{*ύo}ox3vV;KeI`x?*wZY*SRۿQsx;;㗦pp8!UJcXbl&md ,h6E_4sJ!2;$tvY.D/-/z7L^Eώ=_ 񣈞ن7*׿/7š;+GzVt ]h۽N;u4AdC<!`7aP 6Eq$[)`mXc]*pIKt2_s'moïcgG!t8WhRCuĿso*noV!DgfîS_ %v{k ާ$ɴ6F4ώ /]ʾkdY77:W" @PI\!b$F'QfX6gC|̳.f 'b4?Mϓ{$sGpY&:ƶF]HSݷ_F7G;9]3?MMGl}%X#ӳM#ep壓PR$2f+o(by;:jjzr/.<z֐J^TuN&Jp6ʒQӄT\AK aU@ dz :>=p72ukYs"K:M1hb~ .9Ks?зG&4f&$Z2<525={INNZq{w\výC=^+LWgmGK @ə/X;{0pƀQRy<2kSÚ4p s!u82拌"WbUZHw1L-, `m6|$hu 1{tk:iH +-Bsݷ_{& ju=238Tgl|$ >Va\T&ԺVfm\y=[]q̿7u!,AҬ~ja'Or:&͸,.s.`iܾ0i~#DXNVuYڷ!a@aiBXNswMP]o*ڻdj~VR8AiGəƛSG  wߴk>,eI%]oXĮڼ8p_׋h|q<}N(C0tS*`:ƹF:Cg !ңCȌ@n<-\,h\B0-p8k3 KH+yӍIN!:{?=ۅo&؁ÙY;VQm4*6NlѴ#3L%et˩d)ea-N*'zFEn`ŪGBX}u E)40e!2 l]nu. ?sZYlY:M@&滁֦9~{w$5]uLTjʼnzðM;2[I&g+o/SS|];霞wdl>yahC҆5|504T'ѳ.𕈢Cwv L#5tt BcƉqKtyF+xP$ֵK@ TkA8b=HO #1H6k4 I+֤bUl.z#eۜ_ cJp9r N`c> qQD$aޯs^eTӍ׈cKk^O2S񁏘Sf/FLtҪ u;o,HQxl5׽08N*DN>(H .ypm Bm8iĘH \6eϏSZ+L cmZGkCĉ%I\V)cLIVƸbad*HC{O~Oisˋ:urWS[ viM#\kWvNƉvc\1+fO'}Q?O{rRFq鯬7T쳴ic}ȓeOh6:@m&6Ѷ#@h~Z>/gd~H8YL AXo-cwYk4zsta~[޺FY{B]icU.9'5B?Ko;'{U]hW\: ?МZ;2IbXhm\9eciw3O| g aO.!p"mru.sfOTZT$' i_ m+!bgmV-UIڴ$P ;SUrՈedH:wMzڙtFI;+FJ2dh S =s@9@-zp-nNlWkROWW(Wr}׼!9fϙe.Vlv_|p$߁xPY*urEZ+ߊ1G,Ign* ";[9 Ӓ!0^=ڈw-^)u(ʘt0& kID晶0вU?|, N`Ƙ8k&f3D D@kv%|>NH~,p>Qˬ+u5VᬓX#0ǹ>$$gI;9Ҫ_RH;\ɓgؖ)j ͐q‡Ès6jD '02I{ L|ί7]h'o<1l9I›湉V5ZQ,d'$IaH($Nb[o$aamIW8)KνqÀ|*IxY0Zj]RqfH4l5juh4 ˆ/4C?ƣTzL`1bחĄڔZ=  2kqj4(6C>:9DgN=fӞ=aG4EX" 5'w{ C5C\BA(Y;Fe fNz-/_( C8JYK9=# ?i̇,Ǖvc|*^~;"ˏ@Ab\cesͦ|="ˏRXIX#Zz9KgX|֪O- \ HQ*z%A6=x&'OI蒇ɹ_,gO=&1_ا !^b.`? %%~Gصg/^܁ڷ=3wshL`5*%qZcB΁IaBLb2?(,[/J7omsfϧa\|Ӭ]UXSi6NPqwRY)TV>#t&=>'?}Y]E졵kUpXhTV74 Hh640g6oaÆ\ B8w1ʧ?~v)4M8g@L1:I4w~{WOZʕe=c4#i5a8It^b&+ %RJI1CIs>P,服kxγFJ޽Gؾ} µypJ,c?k^}kvi.@!$:!4vK(jaVHE!(-xs70<_}h`R~k;\-qo,+O+(,ie[S%IBG$:&IҤ%R Vs.\{)W\cPjI|y&q&p!n'Esx[ :d̙EZJc"i$ k˦iS5N:ࡻH:. f矾dGvEJ/x}#WfMql3yM`-۱9leRv7j;1$IGkRITְq\]?XbL&S C yz$IQLYZ\.r#|20;ɁaD'GJx7[%O'9 J|w6mcp^0IYY2ϡ!N4a!Z79xp~ccc) Cp| p}?K^r p.*ݳ'߭m[;[8U|ngfSFy[6@l|k{zwDwL퓥8>%};鮻vr^K!Rd]s`['ַG&Ig_r Et4$ ^Lz9Q h։U)^_-\䳸A%S@k-ւP6@tYQ%ıelΝ}nk02>HzN C("(%?޶Isv9H.8cK%͛8n|_B xEz=j¶&Tz(ʮggME4 jZ+wvyx(N2q|bLb``4֨|> 墧2ӶvJ?-4p07@[˥HCr]]EʭISBq :ff41f޽S#J?Gn{K\Z6;D>V bE/$BJ L:rh]<0L(JCæ4jyf`jh^h/(af΃;`tV} ,b^e5r#oYOdweݓD|ܜ.olAHKɦu܍]4ٻwλ/vϒ.+- Xt&\Blxq'7gpS }xHFs2@ooK/9MVsyksQ>] tMWt{ W_r)hmf;FX Re)B.!:zwӹ[ :-B(iBZgot_}5kٸq%O1=='=-#_>ԝ \~H)3E)֥;뵌$j8@ RMR7C)/\wXt1IH~."w.JK )[אR!Dv~%}2TyhGq qdl>˿#r| lF[|JºՂ銢Tl8{ _n}?(!Zs6Ofk (3_ȡ9`n*gv.!$WV& yVA39|fшL5y ?v#ֺ r3tU~ R, I!8|8! N[t 0DmR9 Rx* [ByFVԾ'LH_BDSm?$%n- 1Fj֬Ƚ|#K!Kp֢< ͏Z*0B0lZ}>{vFV3O:oU4U Ydk+Zo_@"U e}0?Vp7DaT> .5 H /M%c &c1P褉s❵R~iKPB`L̪U򔧾.zI\K%5Ql |5 65[к bFƍr9Ob!}܀@}2wFtk7'Eb.`Vc]a]hˮ9txA3rqT V\ť?[n;&a#$}[\γ+G,ւ 9q=ya획`K[2bOiUaw|ɩ.\uCܳ}'R8R#:n+Dqsn;B{R'IJ 8f箇k.= E">}C8V_hc8m[̠͟nu_vk vLth,@G!ӍRތOx{> k-(K p]~i-:wEj&D4λk/~[og:i'nW_JiXgӹ=iXc\w?,z1z/F׸IbJ]z5" JEl=icBaCHDttvb||uk.AC4\< +YQdj:bll~_)ٹ!P*Yr.|?`ϞLOO/E/Y{H:w<^z?eߧPիc4ZJ=dйXnݻqP {k)bhj4yֳO.{eϞ=8p2FOV QOrf>?5`llu)wRcchkA(ӣh6 Qo~ 6'=#GC10Ow7 ,XԴf׮]<~m$m13ٱcVcl\ã!ش26`"}6Tq9x( gll1nP,طn:Bqz?Zo 8X@ x}vdj=R]T pq8 I,%%rgbo|F|?+/gvMR6h444ʲ~}s7R.5zh#G 78t$d_q~J<gpzذɩ#G5E KŢwܷ}?Y(CCEs5(A5eZD9!r>Q 9ox%^=@l&rTTBbܐ -IgZzsdz=DeQ~8q:]F^CZXYw漢4ETqˑs Ni{ \~"v]:&H u@hOistV+IENDB`OSCAR-code-v1.3.1/oscar/icons/up-down.png000066400000000000000000000002711417327530600200120ustar00rootroot00000000000000PNG  IHDR OGtEXtSoftwareAdobe ImageReadyqe< PLTEsxctRNS A7IDATxt ?Q1&M 0+T3Ij]?DFIENDB`OSCAR-code-v1.3.1/oscar/icons/warning.png000066400000000000000000000003121417327530600200620ustar00rootroot00000000000000PNG  IHDR ^tEXtSoftwareAdobe ImageReadyqe< PLTEf>tRNS AHIDATxڄA C?hB-#كz .E^Y@7JPN (Nxt*u*IENDB`OSCAR-code-v1.3.1/oscar/logger.cpp000066400000000000000000000174301417327530600165700ustar00rootroot00000000000000/* OSCAR Logger module implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "logger.h" #include "SleepLib/preferences.h" #include "version.h" #include QThreadPool * otherThreadPool = NULL; void MyOutputHandler(QtMsgType type, const QMessageLogContext &context, const QString &msgtxt) { Q_UNUSED(context) if (!logger) { fprintf(stderr, "Pre/Post: %s\n", msgtxt.toLocal8Bit().constData()); return; } QString msg, typestr; switch (type) { case QtWarningMsg: typestr = QString("Warning: "); break; case QtFatalMsg: typestr = QString("Fatal: "); break; case QtCriticalMsg: typestr = QString("Critical: "); break; default: typestr = QString("Debug: "); break; } msg = typestr + msgtxt; //+QString(" (%1:%2, %3)").arg(context.file).arg(context.line).arg(context.function); if (logger && logger->isRunning()) { logger->append(msg); } else { fprintf(stderr, "%s\n", msg.toLocal8Bit().constData()); } if (type == QtFatalMsg) { abort(); } } static QMutex s_LoggerRunning; void initializeLogger() { s_LoggerRunning.lock(); // lock until the thread starts running logger = new LogThread(); otherThreadPool = new QThreadPool(); bool b = otherThreadPool->tryStart(logger); if (b) { s_LoggerRunning.lock(); // wait until the thread begins running s_LoggerRunning.unlock(); // we no longer need the lock } #ifndef HARDLOG qInstallMessageHandler(MyOutputHandler); // NOTE: comment this line out when debugging a crash, otherwise the deferred output will mislead you. #endif if (b) { qDebug() << "Started logging thread"; } else { qWarning() << "Logging thread did not start correctly"; } } void LogThread::connectionReady() { strlock.lock(); connected = true; logTrigger.wakeAll(); strlock.unlock(); qDebug() << "Logging UI initialized"; } bool LogThread::logToFile() { if (m_logStream) { qWarning().noquote() << "Already logging to" << m_logFile->fileName(); return true; } QString debugLog = GetLogDir() + "/debug.txt"; rotateLogs(debugLog, 4); // keep a limited set of previous logs strlock.lock(); m_logFile = new QFile(debugLog); Q_ASSERT(m_logFile); if (m_logFile->open(QFile::ReadWrite | QFile::Text)) { m_logStream = new QTextStream(m_logFile); } logTrigger.wakeAll(); strlock.unlock(); if (m_logStream) { qDebug().noquote() << "Logging to" << debugLog; } else { qWarning().noquote() << "Could not open" << debugLog << "error code" << m_logFile->error() << m_logFile->errorString(); return false; } return true; } LogThread::~LogThread() { QMutexLocker lock(&strlock); Q_ASSERT(running == false); if (m_logStream) { delete m_logStream; m_logStream = nullptr; Q_ASSERT(m_logFile); delete m_logFile; m_logFile = nullptr; } } QString LogThread::logFileName() { if (!m_logFile) { return ""; } return m_logFile->fileName(); } void shutdownLogger() { if (logger) { logger->quit(); // The thread is automatically destroyed when its run() method exits. otherThreadPool->waitForDone(-1); // wait until that happens logger = NULL; } delete otherThreadPool; } LogThread * logger = NULL; void LogThread::append(QString msg) { QString tmp = QString("%1: %2").arg(logtime.elapsed(), 5, 10, QChar('0')).arg(msg); appendClean(tmp); } void LogThread::appendClean(QString msg) { fprintf(stderr, "%s\n", msg.toLocal8Bit().constData()); strlock.lock(); buffer.append(msg); logTrigger.wakeAll(); strlock.unlock(); } void LogThread::quit() { qDebug() << "Shutting down logging thread"; qInstallMessageHandler(0); // Remove our logger. strlock.lock(); running = false; // Force the thread to exit after its next iteration. logTrigger.wakeAll(); // Trigger the final flush. strlock.unlock(); // Release the lock so that the thread can complete. } void LogThread::run() { QMutexLocker lock(&strlock); running = true; s_LoggerRunning.unlock(); // unlock as soon as the thread begins to run do { logTrigger.wait(&strlock); // releases strlock while it waits while (connected && m_logFile && !buffer.isEmpty()) { QString msg = buffer.takeFirst(); if (m_logStream) { *m_logStream << msg << endl; } emit outputLog(msg); } } while (running); // strlock will be released when lock goes out of scope } QString GetLogDir() { static const QString LOG_DIR_NAME = "logs"; Q_ASSERT(!GetAppData().isEmpty()); // If GetLogDir gets called before GetAppData() is valid, this would point at root. QDir oscarData(GetAppData()); Q_ASSERT(oscarData.exists()); if (!oscarData.exists(LOG_DIR_NAME)) { oscarData.mkdir(LOG_DIR_NAME); } QDir logDir(oscarData.canonicalPath() + "/" + LOG_DIR_NAME); if (!logDir.exists()) { qWarning() << "Unable to create" << logDir.absolutePath() << "reverting to" << oscarData.canonicalPath(); logDir = oscarData; } Q_ASSERT(logDir.exists()); return logDir.canonicalPath(); } void rotateLogs(const QString & filePath, int maxPrevious) { if (maxPrevious < 0) { if (getVersion().IsReleaseVersion()) { maxPrevious = 1; } else { // keep more in testing builds maxPrevious = 4; } } // Build the list of rotated logs for this filePath. QFileInfo info(filePath); QString path = QDir(info.absolutePath()).canonicalPath(); QString base = info.baseName(); QString ext = info.completeSuffix(); if (!ext.isEmpty()) { ext = "." + ext; } if (path.isEmpty()) { qWarning() << "Skipping log rotation, directory does not exist:" << info.absoluteFilePath(); return; } QStringList logs; logs.append(filePath); for (int i = 0; i < maxPrevious; i++) { logs.append(QString("%1/%2.%3%4").arg(path).arg(base).arg(i).arg(ext)); } // Remove the expired log. QFileInfo expired(logs[maxPrevious]); if (expired.exists()) { if (expired.isDir()) { QDir dir(expired.canonicalFilePath()); //qDebug() << "Removing expired log directory" << dir.canonicalPath(); if (!dir.removeRecursively()) { qWarning() << "Unable to delete expired log directory" << dir.canonicalPath(); } } else { QFile file(expired.canonicalFilePath()); //qDebug() << "Removing expired log file" << file.fileName(); if (!file.remove()) { qWarning() << "Unable to delete expired log file" << file.fileName(); } } } // Rotate the remaining logs. for (int i = maxPrevious; i > 0; i--) { QFileInfo from(logs[i-1]); QFileInfo to(logs[i]); if (from.exists()) { if (to.exists()) { qWarning() << "Unable to rotate log:" << to.absoluteFilePath() << "exists"; continue; } //qDebug() << "Renaming" << from.absoluteFilePath() << "to" << to.absoluteFilePath(); if (!QFile::rename(from.absoluteFilePath(), to.absoluteFilePath())) { qWarning() << "Unable to rename" << from.absoluteFilePath() << "to" << to.absoluteFilePath(); } } } } OSCAR-code-v1.3.1/oscar/logger.h000066400000000000000000000023041417327530600162270ustar00rootroot00000000000000#ifndef LOGGER_H #define LOGGER_H #include #include #include #include #include #include void initializeLogger(); void shutdownLogger(); QString GetLogDir(); void rotateLogs(const QString & filePath, int maxPrevious=-1); void MyOutputHandler(QtMsgType type, const QMessageLogContext &context, const QString &msgtxt); class LogThread:public QObject, public QRunnable { Q_OBJECT public: explicit LogThread() : QRunnable() { running = false; logtime.start(); connected = false; m_logFile = nullptr; m_logStream = nullptr; } virtual ~LogThread(); void run(); void append(QString msg); void appendClean(QString msg); bool isRunning() { return running; } void connectionReady(); bool logToFile(); QString logFileName(); void quit(); QStringList buffer; QMutex strlock; QThreadPool *threadpool; signals: void outputLog(QString); protected: volatile bool running; QTime logtime; bool connected; class QFile* m_logFile; class QTextStream* m_logStream; QWaitCondition logTrigger; }; extern LogThread * logger; extern QThreadPool * otherThreadPool; #endif // LOGGER_H OSCAR-code-v1.3.1/oscar/main.cpp000066400000000000000000000751631417327530600162440ustar00rootroot00000000000000/* OSCAR Main * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifdef UNITTEST_MODE #include "tests/AutoTest.h" #endif #include #include #include #include #include #include #include #include #include #include #include "version.h" #include "logger.h" #include "mainwindow.h" #include "SleepLib/profiles.h" #include "translation.h" #include "SleepLib/common.h" #include "SleepLib/deviceconnection.h" #include #include // Gah! I must add the real darn plugin system one day. #include "SleepLib/loader_plugins/prs1_loader.h" #include "SleepLib/loader_plugins/cms50_loader.h" #include "SleepLib/loader_plugins/cms50f37_loader.h" #include "SleepLib/loader_plugins/md300w1_loader.h" #include "SleepLib/loader_plugins/zeo_loader.h" #include "SleepLib/loader_plugins/somnopose_loader.h" #include "SleepLib/loader_plugins/resmed_loader.h" #include "SleepLib/loader_plugins/intellipap_loader.h" #include "SleepLib/loader_plugins/icon_loader.h" #include "SleepLib/loader_plugins/sleepstyle_loader.h" #include "SleepLib/loader_plugins/weinmann_loader.h" #include "SleepLib/loader_plugins/viatom_loader.h" MainWindow *mainwin = nullptr; int numFilesCopied = 0; // Count the number of files in this directory and all subdirectories int countRecursively(QString sourceFolder) { QDir sourceDir(sourceFolder); if(!sourceDir.exists()) return 0; int numFiles = sourceDir.count(); QStringList dirs = sourceDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); for(int i = 0; i< dirs.count(); i++) { QString srcName = sourceFolder + QDir::separator() + dirs[i]; numFiles += countRecursively(srcName); } return numFiles; } bool copyRecursively(QString sourceFolder, QString destFolder, QProgressDialog& progress) { bool success = false; QDir sourceDir(sourceFolder); if(!sourceDir.exists()) return false; QDir destDir(destFolder); if(!destDir.exists()) destDir.mkdir(destFolder); QStringList files = sourceDir.entryList(QDir::Files); for(int i = 0; i< files.count(); i++) { QString srcName = sourceFolder + QDir::separator() + files[i]; QString destName = destFolder + QDir::separator() + files[i]; success = QFile::copy(srcName, destName); numFilesCopied++; if ((numFilesCopied % 20) == 1) { // Update progress bar every 20 files progress.setValue(numFilesCopied); QCoreApplication::processEvents(); } if(!success) { qWarning() << "copyRecursively: Unable to copy" << srcName << "to" << destName; return false; } } files.clear(); files = sourceDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); for(int i = 0; i< files.count(); i++) { QString srcName = sourceFolder + QDir::separator() + files[i]; QString destName = destFolder + QDir::separator() + files[i]; // qDebug() << "Copy from "+srcName+" to "+destName; success = copyRecursively(srcName, destName, progress); if(!success) return false; } return true; } bool processPreferenceFile( QString path ) { bool success = true; QString fullpath = path + "/Preferences.xml"; qDebug() << "Process " + fullpath; QFile fl(fullpath); QFile tmp(fullpath+".tmp"); QString line; fl.open(QIODevice::ReadOnly); tmp.open(QIODevice::WriteOnly); QTextStream instr(&fl); QTextStream outstr(&tmp); bool isSleepyHead = false; while (instr.readLineInto(&line)) { if (line.contains("")) // Is this SleepyHead or OSCAR preferences file? isSleepyHead = true; line.replace("SleepyHead","OSCAR"); if (isSleepyHead && line.contains("VersionString")) { int rtAngle = line.indexOf(">", 0); int lfAngle = line.indexOf("<", rtAngle); line.replace(rtAngle+1, lfAngle-rtAngle-1, "1.0.0-beta"); } outstr << line; } fl.remove(); success = tmp.rename(fullpath); return success; } bool processFile( QString fullpath ) { bool success = true; qDebug() << "Process " + fullpath ; QFile fl(fullpath); QFile tmp(fullpath+".tmp"); QString line; fl.open(QIODevice::ReadOnly); tmp.open(QIODevice::WriteOnly); QTextStream instr(&fl); QTextStream outstr(&tmp); while (instr.readLineInto(&line)) { if (line.contains("EnableMultithreading")) { if (line.contains("true")) { line.replace("true","false"); } } line.replace("SleepyHead","OSCAR"); outstr << line; } fl.remove(); success = tmp.rename(fullpath); return success; } bool process_a_Profile( QString path ) { bool success = true; qDebug() << "Entering profile directory " + path; QDir dir(path); QStringList files = dir.entryList(QStringList("*.xml"), QDir::Files); for ( int i = 0; success && (i(countDone - startTime); auto elapsedCopy = std::chrono::duration_cast(allDone - countDone); qDebug() << "Counting files took " << elapsedCount.count() << " microsecs"; qDebug() << "Migrating files took " << elapsedCopy.count() << " microsecs"; return success; } #ifdef UNITTEST_MODE int main(int argc, char* argv[]) { initializeStrings(); qDebug() << STR_TR_OSCAR + " " + getVersion(); AutoTest::run(argc, argv); } #else #ifndef Q_OS_LINUX // Due to a bug in Qt, creating multiple QApplication instances in a process // causes subsequent native file dialog boxes to hang on Fedora 35. // See https://bugreports.qt.io/browse/QTBUG-90616 // // Since Linux users can simply use the --legacy command-line argument, // we can remove the shift-key check that requires those multiple instances. bool shiftKeyPressedAtLaunch(int argc, char *argv[]) { // Reliably detecting the shift key requires a QGuiApplication instance, but // we need to create the real QApplication afterwards, so create a temporary // instance here. QGuiApplication* app = new QGuiApplication(argc, argv); Qt::KeyboardModifiers keymodifier = QGuiApplication::queryKeyboardModifiers(); delete app; return keymodifier == Qt::ShiftModifier; } #endif int main(int argc, char *argv[]) { QString homeDocs = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)+"/"; QCoreApplication::setApplicationName(getAppName()); QCoreApplication::setOrganizationName(getDeveloperName()); QCoreApplication::setOrganizationDomain(getDeveloperDomain()); QSettings settings; // If shift key was held down when OSCAR was launched, force Software graphics Engine (aka LegacyGFX) QString forcedEngine = ""; #ifndef Q_OS_LINUX // Shift key check is skipped on Linux due to a Qt bug, see comment at shiftKeyPressedAtLaunch(). if (shiftKeyPressedAtLaunch(argc, argv)){ settings.setValue(GFXEngineSetting, (unsigned int)GFX_Software); forcedEngine = "Software Engine forced by shift key at launch"; } #endif // This argument needs to be processed before creating the QApplication, // based on sample code at https://doc.qt.io/qt-5/qapplication.html#details for (int i = 1; i < argc; ++i) { if (!qstrcmp(argv[i], "--legacy")) { settings.setValue(GFXEngineSetting, (unsigned int)GFX_Software); forcedEngine = "Software Engine forced by --legacy command line switch"; } } #ifdef Q_OS_WIN bool oscarCrashed = false; if (settings.value("OpenGLCompatibilityCheck").toBool()) { oscarCrashed = true; } if (oscarCrashed) { settings.setValue(GFXEngineSetting, (unsigned int)GFX_Software); forcedEngine = "Software Engine forced by previous crash"; settings.remove("OpenGLCompatibilityCheck"); } #endif GFXEngine gfxEngine = (GFXEngine)qMin((unsigned int)settings.value(GFXEngineSetting, (unsigned int)GFX_OpenGL).toUInt(), (unsigned int)MaxGFXEngine); switch (gfxEngine) { case 0: // GFX_OpenGL QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); break; case 1: // GFX_ANGLE QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); break; case 2: // GFX_Software default: QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); } QApplication a(argc, argv); QStringList args = a.arguments(); #ifdef Q_OS_WIN // QMessageBox must come after the application is created. The graphics engine has to be selected before. if (oscarCrashed) { QMessageBox::warning(nullptr, STR_MessageBox_Error, QObject::tr("OSCAR crashed due to an incompatibility with your graphics hardware.") + "\n\n" + QObject::tr("To resolve this, OSCAR has reverted to a slower but more compatible method of drawing."), QMessageBox::Ok); } #endif initializeLogger(); // After initializing the logger, any qDebug() messages will be queued but not written to console // until MainWindow is constructed below. In spite of that, we initialize the logger here so that // the intervening messages show up in the debug pane. // // The only time this is really noticeable is when initTranslations() presents its language // selection QDialog, which waits indefinitely for user input before MainWindow is constructed. QString lastlanguage = settings.value(LangSetting, "").toString(); if (lastlanguage.compare("is", Qt::CaseInsensitive)) // Convert code for Hebrew from 'is' to 'he' lastlanguage = "he"; bool dont_load_profile = false; bool force_data_dir = false; bool changing_language = false; QString load_profile = ""; if (lastlanguage.isEmpty()) changing_language = true; for (int i = 1; i < args.size(); i++) { if (args[i] == "-l") dont_load_profile = true; // else if (args[i] == "-d") // force_data_dir = true; else if (args[i] == "--language") { changing_language = true; // reset to force language dialog settings.setValue(LangSetting,""); } // "--legacy" is handle above, as it needs to be processed before creating QApplication. else if (args[i] == "-p") QThread::msleep(1000); else if (args[i] == "--profile") { if ((i+1) < args.size()) load_profile = args[++i]; else { fprintf(stderr, "Missing argument to --profile\n"); exit(1); } } else if (args[i] == "--datadir") { // mltam's idea QString datadir, datadirwas ; if ((i+1) < args.size()) { datadirwas = datadir = args[++i]; bool havefullpath = false; if (datadir.length() >= 2) { havefullpath = (datadir.at(1) == QLatin1Char(':')) // Allow a Windows drive letter || (datadir.at(0) == '/') // or Linux full path || (datadir.at(0) == '\\'); } if (!havefullpath) datadir = homeDocs+datadir; settings.setValue("Settings/AppData", datadir); qDebug() << "--datadir" << datadirwas << "homeDocs:" << homeDocs << "appdata:" << datadir; // force_data_dir = true; } else { fprintf(stderr, "Missing argument to --datadir\n"); exit(1); } } } // end of for args loop qDebug().noquote() << "OSCAR starting" << QDateTime::currentDateTime().toString(); qDebug() << "APP-NAME:" << QCoreApplication::applicationName(); qDebug() << "APP-PATH:" << QCoreApplication::applicationDirPath(); qDebug() << "APP-RESOURCES:" << appResourcePath(); #ifdef QT_DEBUG QString relinfo = " debug"; #else QString relinfo = ""; #endif relinfo = "("+QSysInfo::kernelType()+" "+QSysInfo::currentCpuArchitecture()+relinfo+")"; relinfo = STR_AppName + " " + getVersion() + " " + relinfo; qDebug().noquote() << relinfo; qDebug().noquote() << "Built with Qt" << QT_VERSION_STR << "on" << getBuildDateTime(); addBuildInfo(relinfo); // immediately add it to the build info that's accessible from the UI SetDateFormat(); //////////////////////////////////////////////////////////////////////////////////////////// // Language Selection //////////////////////////////////////////////////////////////////////////////////////////// initTranslations(); initializeStrings(); // This must be called AFTER translator is installed, but before mainwindow is setup // QFontDatabase::addApplicationFont("://fonts/FreeSans.ttf"); // a.setFont(QFont("FreeSans", 11, QFont::Normal, false)); mainwin = new MainWindow; // Moved buildInfo calls to after translation is available as makeBuildInfo includes tr() calls QStringList info = makeBuildInfo(forcedEngine); for (int i = 0; i < info.size(); ++i) qDebug().noquote() << info.at(i); //////////////////////////////////////////////////////////////////////////////////////////// // OpenGL Detection //////////////////////////////////////////////////////////////////////////////////////////// getOpenGLVersion(); getOpenGLVersionString(); //bool opengl2supported = glversion >= 2.0; //bool bad_graphics = !opengl2supported; //bool intel_graphics = false; //#ifndef NO_OPENGL_BUILD //#endif /************************************************************************************* #ifdef BROKEN_OPENGL_BUILD Q_UNUSED(bad_graphics) Q_UNUSED(intel_graphics) const QString BetterBuild = "Settings/BetterBuild"; if (opengl2supported) { if (!settings.value(BetterBuild, false).toBool()) { QMessageBox::information(nullptr, QObject::tr("A faster build of OSCAR may be available"), QObject::tr("This build of OSCAR is a compatability version that also works on computers lacking OpenGL 2.0 support.")+"

    "+ QObject::tr("However it looks like your computer has full support for OpenGL 2.0!") + "

    "+ QObject::tr("This version will run fine, but a \"%1\" tagged build of OSCAR will likely run a bit faster on your computer.").arg("-OpenGL")+"

    "+ QObject::tr("You will not be bothered with this message again."), QMessageBox::Ok, QMessageBox::Ok); settings.setValue(BetterBuild, true); } } #else if (bad_graphics) { QMessageBox::warning(nullptr, QObject::tr("Incompatible Graphics Hardware"), QObject::tr("This build of OSCAR requires OpenGL 2.0 support to function correctly, and unfortunately your computer lacks this capability.") + "

    "+ QObject::tr("You may need to update your computers graphics drivers from the GPU makers website. %1"). arg(intel_graphics ? QObject::tr("(
    Intel's support site)") : "")+"

    "+ QObject::tr("Because graphs will not render correctly, and it may cause crashes, this build will now exit.")+"

    "+ QObject::tr("There is another build available tagged \"-BrokenGL\" that should work on your computer."), QMessageBox::Ok, QMessageBox::Ok); exit(1); } #endif ****************************************************************************************************************/ //////////////////////////////////////////////////////////////////////////////////////////// // Datafolder location Selection //////////////////////////////////////////////////////////////////////////////////////////// // bool change_data_dir = force_data_dir; // // bool havefolder = false; if (!settings.contains("Settings/AppData")) { // This is first time execution if ( settings.contains("Settings/AppRoot") ) { // allow for old AppRoot here - not really first time settings.setValue("Settings/AppData", settings.value("Settings/AppRoot")); } else { settings.setValue("Settings/AppData", homeDocs + getModifiedAppData()); // set up new data directory path } qDebug() << "First time: Setting " + GetAppData(); } QDir dir(GetAppData()); if ( ! dir.exists() ) { // directory doesn't exist, verify user's choice if ( ! force_data_dir ) { // unless they explicitly selected it by --datadir param if (QMessageBox::question(nullptr, STR_MessageBox_Question, QObject::tr("OSCAR will set up a folder for your data.")+"\n"+ QObject::tr("If you have been using SleepyHead or an older version of OSCAR,") + "\n" + QObject::tr("OSCAR can copy your old data to this folder later.")+"\n"+ QObject::tr("We suggest you use this folder: ")+QDir::toNativeSeparators(GetAppData())+"\n"+ QObject::tr("Click Ok to accept this, or No if you want to use a different folder."), QMessageBox::Ok | QMessageBox::No, QMessageBox::Ok) == QMessageBox::No) { // User wants a different folder for data bool change_data_dir = true; while (change_data_dir) { // Create or select an acceptable folder QString datadir = QFileDialog::getExistingDirectory(nullptr, QObject::tr("Choose or create a new folder for OSCAR data"), homeDocs, QFileDialog::ShowDirsOnly); if (datadir.isEmpty()) { // User hit Cancel instead of selecting or creating a folder QMessageBox::information(nullptr, QObject::tr("Exiting"), QObject::tr("As you did not select a data folder, OSCAR will exit.")+"\n"+ QObject::tr("Next time you run OSCAR, you will be asked again.")); return 0; } else { // We have a folder, see if is already an OSCAR folder QDir dir(datadir); QFile file(datadir + "/Preferences.xml"); QDir dirP(datadir + "/Profiles"); if (!file.exists() || !dirP.exists()) { // It doesn't have a Preferences.xml file or a Profiles directory in it if (dir.count() > 2) { // but it has more than dot and dotdot // Not a new directory.. nag the user. if (QMessageBox::question(nullptr, STR_MessageBox_Warning, QObject::tr("The folder you chose is not empty, nor does it already contain valid OSCAR data.") + "\n\n"+QObject::tr("Are you sure you want to use this folder?")+"\n\n" + datadir, QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { continue; // Nope, don't use it, go around the loop again } } } settings.setValue("Settings/AppData", datadir); qDebug() << "Changing data folder to" << datadir; change_data_dir = false; } } // the while loop } // user wants a different folder } // user used --datadir folder to select a folder } // The folder doesn't exist else qDebug() << "AppData folder already exists, so ..."; qDebug().noquote() << "Using " + GetAppData() + " as OSCAR data folder"; QString path = GetAppData(); addBuildInfo(QObject::tr("Data directory:") + " " + path + ""); QDir newDir(GetAppData()); #if QT_VERSION < QT_VERSION_CHECK(5,9,0) if ( ! newDir.exists() || newDir.count() == 0 ) { // directory doesn't exist yet or is empty, try to migrate old data #else if ( ! newDir.exists() || newDir.isEmpty() ) { // directory doesn't exist yet or is empty, try to migrate old data #endif if (QMessageBox::question(nullptr, QObject::tr("Migrate SleepyHead or OSCAR Data?"), QObject::tr("On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data") +"\n" + QObject::tr("Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data."), QMessageBox::Ok|QMessageBox::No, QMessageBox::Ok) == QMessageBox::Ok) { migrateFromSH( GetAppData() ); // doesn't matter if no migration } } // Make sure the data directory exists. if (!newDir.mkpath(".")) { QMessageBox::warning(nullptr, QObject::tr("Exiting"), QObject::tr("Unable to create the OSCAR data folder at")+"\n"+ GetAppData()); return 0; } // Make sure we can write to the data directory QFile testFile(GetAppData()+"/testfile.txt"); if (testFile.exists()) testFile.remove(); if (!testFile.open(QFile::ReadWrite)) { QString errMsg = QObject::tr("Unable to write to OSCAR data directory") + " " + GetAppData() + "\n\n" + QObject::tr("Error code") + ": " + QString::number(testFile.error()) + " - " + testFile.errorString() + "\n\n" + QObject::tr("OSCAR cannot continue and is exiting.") + "\n"; qCritical() << errMsg; QMessageBox::critical(nullptr, QObject::tr("Exiting"), errMsg); return 0; } else testFile.remove(); // Begin logging to file now that there's a data folder. if (!logger->logToFile()) { QMessageBox::warning(nullptr, STR_MessageBox_Warning, QObject::tr("Unable to write to debug log. You can still use the debug pane (Help/Troubleshooting/Show Debug Pane) but the debug log will not be written to disk.")); } /////////////////////////////////////////////////////////////////////////////////////////// // Initialize preferences system (Don't use p_pref before this point!) /////////////////////////////////////////////////////////////////////////////////////////// p_pref = new Preferences("Preferences"); p_pref->Open(); AppSetting = new AppWideSetting(p_pref); QString language = settings.value(LangSetting, "").toString(); AppSetting->setLanguage(language); // Set fonts from preferences file qDebug() << "App font before Prefs setting" << QApplication::font(); validateAllFonts(); setApplicationFont(); // one-time translate GraphSnapshots to ShowPieChart p_pref->Rename(STR_AS_GraphSnapshots, STR_AS_ShowPieChart); p_pref->Erase(STR_AppName); p_pref->Erase(STR_GEN_SkipLogin); #ifndef NO_CHECKUPDATES //////////////////////////////////////////////////////////////////////////////////////////// // Check when last checked for updates.. //////////////////////////////////////////////////////////////////////////////////////////// QDateTime lastchecked, today = QDateTime::currentDateTime(); bool check_updates = false; if (!getVersion().IsReleaseVersion()) { // If test build, force update autocheck, no more than 7 day interval, show test versions AppSetting->setUpdatesAutoCheck(true); AppSetting->setUpdateCheckFrequency(min(AppSetting->updateCheckFrequency(), 7)); AppSetting->setAllowEarlyUpdates(true); } if (AppSetting->updatesAutoCheck()) { int update_frequency = AppSetting->updateCheckFrequency(); int days = 1000; lastchecked = AppSetting->updatesLastChecked(); if (lastchecked.isValid()) { days = lastchecked.secsTo(today); days /= 86400; } if (days >= update_frequency) { check_updates = true; } } #endif Version settingsVersion = Version(AppSetting->versionString()); Version currentVersion = getVersion(); if (currentVersion.IsValid() == false) { // The defined version MUST be valid, otherwise comparisons between versions will fail. QMessageBox::critical(nullptr, STR_MessageBox_Error, QObject::tr("Version \"%1\" is invalid, cannot continue!").arg(currentVersion)); return 0; } if (currentVersion > settingsVersion) { AppSetting->setShowAboutDialog(1); // release_notes(); // check_updates = false; } else if (currentVersion < settingsVersion) { if (QMessageBox::warning(nullptr, STR_MessageBox_Error, QObject::tr("The version of OSCAR you are running (%1) is OLDER than the one used to create this data (%2).") .arg(currentVersion.displayString()) .arg(settingsVersion.displayString()) +"\n\n"+ QObject::tr("It is likely that doing this will cause data corruption, are you sure you want to do this?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) { return 0; } } AppSetting->setVersionString(getVersion()); //////////////////////////////////////////////////////////////////////////////////////////// // Register Importer Modules for autoscanner //////////////////////////////////////////////////////////////////////////////////////////// schema::init(); PRS1Loader::Register(); ResmedLoader::Register(); IntellipapLoader::Register(); SleepStyleLoader::Register(); FPIconLoader::Register(); WeinmannLoader::Register(); CMS50Loader::Register(); CMS50F37Loader::Register(); MD300W1Loader::Register(); ViatomLoader::Register(); // Begin logging device connection activity. QString connectionsLogDir = GetLogDir() + "/connections"; rotateLogs(connectionsLogDir); // keep a limited set of previous logs if (!QDir(connectionsLogDir).mkpath(".")) { qWarning().noquote() << "Unable to create directory" << connectionsLogDir; } QFile deviceLog(connectionsLogDir + "/devices.xml"); if (deviceLog.open(QFile::ReadWrite)) { qDebug().noquote() << "Logging device connections to" << deviceLog.fileName(); DeviceConnectionManager::getInstance().record(&deviceLog); } else { qWarning().noquote() << "Unable to start device connection logging to" << deviceLog.fileName(); } schema::setOrders(); // could be called in init... // Scan for user profiles Profiles::Scan(); Q_UNUSED(changing_language) Q_UNUSED(dont_load_profile) #ifndef NO_CHECKUPDATES if (check_updates) { mainwin->CheckForUpdates(false); } #endif mainwin->SetupGUI(); mainwin->show(); int result = a.exec(); DeviceConnectionManager::getInstance().record(nullptr); return result; } #endif // !UNITTEST_MODE OSCAR-code-v1.3.1/oscar/mainwindow.cpp000066400000000000000000002642561417327530600174770ustar00rootroot00000000000000/* OSCAR MainWindow Implementation * * Copyright (c) 2020-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common_gui.h" #include "version.h" // Custom loaders that don't autoscan.. #include #include #include #include #ifdef REMSTAR_M_SUPPORT #include #endif #include "logger.h" #include "mainwindow.h" #include "ui_mainwindow.h" #include "aboutdialog.h" #include "newprofile.h" #include "exportcsv.h" #include "SleepLib/schema.h" #include "Graphs/glcommon.h" #include "checkupdates.h" #include "SleepLib/calcs.h" #include "SleepLib/progressdialog.h" #include "SleepLib/importcontext.h" #include "reports.h" #include "statistics.h" #include "zip.h" #if QT_VERSION >= QT_VERSION_CHECK(5,4,0) #include #endif CheckUpdates *updateChecker; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); if (logger) { connect(logger, SIGNAL(outputLog(QString)), this, SLOT(logMessage(QString))); logger->connectionReady(); } // Bring window to top (useful when language is changed) this->activateWindow(); this->raise(); // Initialise oscar app registry stuff QSettings settings; // Load previous Window geometry restoreGeometry(settings.value("MainWindow/geometry").toByteArray()); // Nifty Notification popups in System Tray (uses Growl on Mac) if (QSystemTrayIcon::isSystemTrayAvailable() && QSystemTrayIcon::supportsMessages()) { qDebug() << "Using System Tray for Menu"; systray = new QSystemTrayIcon(QIcon(":/icons/logo-sm.png"), this); systray->show(); // seems to need the systray menu for notifications to work systraymenu = new QMenu(this); systray->setContextMenu(systraymenu); QAction *a = systraymenu->addAction(STR_TR_OSCAR + " " + getVersion().displayString()); a->setEnabled(false); systraymenu->addSeparator(); systraymenu->addAction(tr("&About"), this, SLOT(on_action_About_triggered())); // systraymenu->addAction(tr("Check for &Updates"), this, SLOT(on_actionCheck_for_Updates_triggered())); systraymenu->addSeparator(); systraymenu->addAction(tr("E&xit"), this, SLOT(close())); // systraymenu = nullptr; } else { // if not available, the messages will popup in the taskbar qDebug() << "No System Tray menues"; systray = nullptr; systraymenu = nullptr; } } bool setupRunning = false; QString MainWindow::getMainWindowTitle() { QString title = STR_TR_OSCAR + " " + getVersion().displayString(); #ifdef BROKEN_OPENGL_BUILD title += " ["+CSTR_GFX_BrokenGL+"]"; #endif return title; } void MainWindow::SetupGUI() { setupRunning = true; setWindowTitle(getMainWindowTitle()); #ifdef Q_OS_MAC ui->action_About->setMenuRole(QAction::AboutRole); ui->action_Preferences->setMenuRole(QAction::PreferencesRole); #endif ui->actionPrint_Report->setShortcuts(QKeySequence::Print); ui->action_Fullscreen->setShortcuts(QKeySequence::FullScreen); ui->actionLine_Cursor->setChecked(AppSetting->lineCursorMode()); ui->actionPie_Chart->setChecked(AppSetting->showPieChart()); ui->actionDebug->setChecked(AppSetting->showDebug()); ui->actionShow_Performance_Counters->setChecked(AppSetting->showPerformance()); overview = nullptr; daily = nullptr; prefdialog = nullptr; profileSelector = nullptr; welcome = nullptr; #ifdef NO_CHECKUPDATES ui->action_Check_for_Updates->setVisible(false); #endif ui->oximetryButton->setDisabled(true); ui->dailyButton->setDisabled(true); ui->overviewButton->setDisabled(true); ui->statisticsButton->setDisabled(true); ui->importButton->setDisabled(true); #ifdef helpless ui->helpButton->setVisible(false); #endif m_inRecalculation = false; m_restartRequired = false; // Initialize Status Bar objects QTextCharFormat format = ui->statStartDate->calendarWidget()->weekdayTextFormat(Qt::Saturday); format.setForeground(QBrush(Qt::black, Qt::SolidPattern)); Qt::DayOfWeek dow=firstDayOfWeekFromLocale(); ui->statStartDate->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format); ui->statStartDate->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format); ui->statStartDate->calendarWidget()->setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader); ui->statStartDate->calendarWidget()->setFirstDayOfWeek(dow); ui->statEndDate->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format); ui->statEndDate->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format); ui->statEndDate->calendarWidget()->setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader); ui->statEndDate->calendarWidget()->setFirstDayOfWeek(dow); ui->statEndDate->setVisible(false); ui->statStartDate->setVisible(false); // ui->reportModeRange->setVisible(false); int srm = 0; if (p_profile) { srm = p_profile->general->statReportMode(); } switch(srm) { case 0: ui->reportModeStandard->setChecked(true); break; case 1: ui->reportModeMonthly->setChecked(true); break; case 2: ui->reportModeRange->setChecked(true); ui->statEndDate->setVisible(true); ui->statStartDate->setVisible(true); break; default: if (p_profile) { p_profile->general->setStatReportMode(0); } break; } if (!AppSetting->showDebug()) { ui->logText->hide(); } #ifdef Q_OS_MAC //p_profile->appearance->setAntiAliasing(false); #endif //ui->action_Link_Graph_Groups->setChecked(p_profile->general->linkGroups()); first_load = true; profileSelector = new ProfileSelector(ui->tabWidget); ui->tabWidget->insertTab(0, profileSelector, STR_TR_Profile); // Profiles haven't been loaded here... profileSelector->updateProfileList(); // Start with the new Profile Tab ui->tabWidget->setCurrentWidget(profileSelector); // setting this to daily shows the cube during loading.. ui->tabWidget->setTabEnabled(1, false); // this should be the Statistics tab ui->toolBox->setCurrentIndex(0); bool b = AppSetting->rightSidebarVisible(); ui->action_Sidebar_Toggle->setChecked(b); ui->toolBox->setVisible(b); ui->actionShowPersonalData->setChecked(AppSetting->showPersonalData()); ui->actionPie_Chart->setChecked(AppSetting->showPieChart()); ui->actionDaily_Calendar->setChecked(AppSetting->calendarVisible()); on_tabWidget_currentChanged(0); #ifndef REMSTAR_M_SUPPORT ui->actionImport_RemStar_MSeries_Data->setVisible(false); #endif qsrand(QDateTime::currentDateTime().toTime_t()); QList a; int panel_width = AppSetting->rightPanelWidth(); a.push_back(this->width() - panel_width); a.push_back(panel_width); ui->mainsplitter->setSizes(a); ui->mainsplitter->setStretchFactor(0,1); ui->mainsplitter->setStretchFactor(1,0); QTimer::singleShot(50, this, SLOT(Startup())); ui->actionChange_Data_Folder->setVisible(false); ui->action_Frequently_Asked_Questions->setVisible(false); ui->actionReport_a_Bug->setVisible(false); // remove this once we actually implement it ui->actionExport_Review->setVisible(false); // remove this once we actually implement it #ifndef helpless help = new Help(this); ui->tabWidget->addTab(help, tr("Help Browser")); #endif setupRunning = false; } void MainWindow::logMessage(QString msg) { ui->logText->appendPlainText(msg); } void MainWindow::closeEvent(QCloseEvent * event) { Q_UNUSED(event); static bool runonce = false; if (!runonce) { if (AppSetting->removeCardReminder()) { Notify(QObject::tr("Don't forget to place your datacard back in your CPAP machine"), QObject::tr("OSCAR Reminder")); QThread::msleep(1000); QApplication::processEvents(); } schema::channel.Save(); if (p_profile) { CloseProfile(); } // Shutdown and Save the current User profile Profiles::Done(); // Save current window position QSettings settings; settings.setValue("MainWindow/geometry", saveGeometry()); // Trash anything allocated by the Graph objects DestroyGraphGlobals(); if (systraymenu) { delete systraymenu; systraymenu = nullptr; } if (systray) { delete systray; systray = nullptr; } disconnect(logger, SIGNAL(outputLog(QString)), this, SLOT(logMessage(QString))); shutdownLogger(); runonce = true; } else { qDebug() << "Qt is still calling closevent multiple times"; QApplication::processEvents(); } } MainWindow::~MainWindow() { delete ui; QCoreApplication::quit(); } void MainWindow::log(QString text) { logger->appendClean(text); } void MainWindow::EnableTabs(bool b) { ui->tabWidget->setTabEnabled(2, b); ui->tabWidget->setTabEnabled(3, b); ui->tabWidget->setTabEnabled(4, b); } void MainWindow::Notify(QString s, QString title, int ms) { if (title.isEmpty()) { title = STR_TR_OSCAR + " " + getVersion().displayString(); } if (systray) { QString msg = s; #ifdef Q_OS_UNIX // GNOME3's systray hides the last line of the displayed Qt message. // As a workaround, add an extra line to bump the message back // into the visible area. char *desktop = getenv("DESKTOP_SESSION"); if (desktop && !strncmp(desktop, "gnome", 5)) { msg += "\n"; } #endif systray->showMessage(title, msg, QSystemTrayIcon::Information, ms); } } QString getCPAPPixmap(QString mach_class) { QString cpapimage; if (mach_class == STR_MACH_ResMed) cpapimage = ":/icons/rms9.png"; else if (mach_class == STR_MACH_PRS1) cpapimage = ":/icons/prs1.png"; else if (mach_class == STR_MACH_Intellipap) cpapimage = ":/icons/intellipap.png"; return cpapimage; } //QIcon getCPAPIcon(QString mach_class) //{ // QString cpapimage = getCPAPPixmap(mach_class); // return QIcon(cpapimage); //} void MainWindow::PopulatePurgeMenu() { ui->menu_Rebuild_CPAP_Data->disconnect(ui->menu_Rebuild_CPAP_Data, SIGNAL(triggered(QAction*)), this, SLOT(on_actionRebuildCPAP(QAction *))); ui->menu_Rebuild_CPAP_Data->clear(); ui->menuPurge_CPAP_Data->disconnect(ui->menuPurge_CPAP_Data, SIGNAL(triggered(QAction*)), this, SLOT(on_actionPurgeMachine(QAction *))); ui->menuPurge_CPAP_Data->clear(); // Only allow rebuilding for CPAP for now, since that's the only thing that makes backups. QList machines = p_profile->GetMachines(MT_CPAP); for (int i=0; i < machines.size(); ++i) { Machine *mach = machines.at(i); addMachineToMenu(mach, ui->menu_Rebuild_CPAP_Data); } // Add any imported machine (except the built-in journal) to the purge menu. machines = p_profile->GetMachines(); for (int i=0; i < machines.size(); ++i) { Machine *mach = machines.at(i); if (mach->type() == MT_JOURNAL) { continue; } addMachineToMenu(mach, ui->menuPurge_CPAP_Data); } ui->menu_Rebuild_CPAP_Data->connect(ui->menu_Rebuild_CPAP_Data, SIGNAL(triggered(QAction*)), this, SLOT(on_actionRebuildCPAP(QAction*))); ui->menuPurge_CPAP_Data->connect(ui->menuPurge_CPAP_Data, SIGNAL(triggered(QAction*)), this, SLOT(on_actionPurgeMachine(QAction*))); } void MainWindow::addMachineToMenu(Machine* mach, QMenu* menu) { QString name = mach->brand(); if (name.isEmpty()) { name = mach->loaderName(); } name += " " + mach->model() + " " + mach->serial(); QAction * action = new QAction(name.replace("&","&&"), menu); action->setIconVisibleInMenu(true); action->setIcon(mach->getPixmap()); action->setData(mach->loaderName()+":"+mach->serial()); menu->addAction(action); } void MainWindow::firstRunMessage() { if (AppSetting->showAboutDialog() > 0) { AboutDialog * about = new AboutDialog(this); about->exec(); AppSetting->setShowAboutDialog(-1); about->deleteLater(); } } // QString GenerateWelcomeHTML(); bool MainWindow::OpenProfile(QString profileName, bool skippassword) { qDebug() << "Opening profile" << profileName; auto pit = Profiles::profiles.find(profileName); if (pit == Profiles::profiles.end()) return false; Profile * prof = pit.value(); if (p_profile) { if ((prof != p_profile)) { CloseProfile(); } else { // Already open return false; } } prof = profileSelector->SelectProfile(profileName, skippassword); // asks for the password and updates stuff in profileSelector tab if (!prof) { return false; } // TODO: Check profile password // Check Lockfile QString lockhost = prof->checkLock(); if (!lockhost.isEmpty()) { if (lockhost.compare(QHostInfo::localHostName()) != 0) { if (QMessageBox::warning(nullptr, STR_MessageBox_Warning, QObject::tr("There is a lockfile already present for this profile '%1', claimed on '%2'.").arg(prof->user->userName()).arg(lockhost)+"\n\n"+ QObject::tr("You can only work with one instance of an individual OSCAR profile at a time.")+"\n\n"+ QObject::tr("If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding."), QMessageBox::Cancel |QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Cancel) { return false; } } // not worried about localhost locks anymore, just silently drop it. prof->removeLock(); } p_profile = prof; ProgressDialog * progress = new ProgressDialog(this); progress->setMessage(QObject::tr("Loading profile \"%1\"...").arg(profileName)); progress->open(); QList machines = p_profile->GetMachines(MT_CPAP); if (machines.isEmpty()) { qDebug() << "No machines in profile"; } else { #ifdef LOCK_RESMED_SESSIONS for (QList::iterator it = machines.begin(); it != machines.end(); ++it) { QString mclass=(*it)->loaderName(); if (mclass == STR_MACH_ResMed) { qDebug() << "ResMed machine found.. locking OSCAR preferences to suit it's summary system"; // Have to sacrifice these features to get access to summary data. p_profile->session->setCombineCloseSessions(0); p_profile->session->setDaySplitTime(QTime(12,0,0)); p_profile->session->setIgnoreShortSessions(false); p_profile->session->setLockSummarySessions(true); p_profile->general->setPrefCalcPercentile(95.0); // 95% p_profile->general->setPrefCalcMiddle(0); // Median (50%) p_profile->general->setPrefCalcMax(1); // Dodgy max break; } } #endif } // Todo: move this to AHIWIndow check to profile Load function... if (p_profile->cpap->AHIWindow() < 30.0) { p_profile->cpap->setAHIWindow(60.0); } if (p_profile->p_preferences[STR_PREF_ReimportBackup].toBool()) { importCPAPBackups(); p_profile->p_preferences[STR_PREF_ReimportBackup]=false; } p_profile->LoadMachineData(progress); progress->setMessage(tr("Loading profile \"%1\"").arg(profileName)); // Show the logo? // QPixmap logo=QPixmap(":/icons/logo-md.png").scaled(64,64); // progress->setPixmap(logo); QApplication::processEvents(); ui->statStartDate->setDate(p_profile->FirstDay()); ui->statEndDate->setDate(p_profile->LastDay()); // Reload everything profile related if (daily) { qCritical() << "OpenProfile called with active Daily object!"; qDebug() << "Abandon opening Profile"; return false; } welcome = new Welcome(ui->tabWidget); ui->tabWidget->insertTab(1, welcome, tr("Welcome")); daily = new Daily(ui->tabWidget, nullptr); ui->tabWidget->insertTab(2, daily, STR_TR_Daily); daily->ReloadGraphs(); if (overview) { qCritical() << "OpenProfile called with active Overview object!"; qDebug() << "Abandon opening Profile"; return false; } overview = new Overview(ui->tabWidget, daily->graphView()); ui->tabWidget->insertTab(3, overview, STR_TR_Overview); overview->ReloadGraphs(); // Should really create welcome and statistics here, but they need redoing later anyway to kill off webkit ui->tabWidget->setCurrentIndex(AppSetting->openTabAtStart()); GenerateStatistics(); PopulatePurgeMenu(); AppSetting->setProfileName(p_profile->user->userName()); setWindowTitle(tr("%1 (Profile: %2)").arg(getMainWindowTitle()).arg(AppSetting->profileName())); QList oximachines = p_profile->GetMachines(MT_OXIMETER); // Machines of any type except Journal QList posmachines = p_profile->GetMachines(MT_POSITION); QList stgmachines = p_profile->GetMachines(MT_SLEEPSTAGE); bool noMachines = machines.isEmpty() && posmachines.isEmpty() && oximachines.isEmpty() && stgmachines.isEmpty(); ui->importButton->setDisabled(false); ui->oximetryButton->setDisabled(false); ui->dailyButton->setDisabled(noMachines); ui->overviewButton->setDisabled(noMachines); ui->statisticsButton->setDisabled(noMachines); ui->tabWidget->setTabEnabled(2, !noMachines); // daily, STR_TR_Daily); ui->tabWidget->setTabEnabled(3, !noMachines); // overview, STR_TR_Overview); ui->tabWidget->setTabEnabled(4, !noMachines); // statistics, STR_TR_Statistics); int srm = 0; if (p_profile) { srm = p_profile->general->statReportMode(); } switch (srm) { case 0: ui->reportModeStandard->setChecked(true); ui->statEndDate->setVisible(false); ui->statStartDate->setVisible(false); break; case 1: ui->reportModeMonthly->setChecked(true); ui->statEndDate->setVisible(false); ui->statStartDate->setVisible(false); break; case 2: ui->reportModeRange->setChecked(true); ui->statEndDate->setVisible(true); ui->statStartDate->setVisible(true); break; default: if (p_profile) { p_profile->general->setStatReportMode(0); } break; } progress->close(); delete progress; qDebug() << "Finished opening Profile"; if (updateChecker != nullptr) updateChecker->showMessage(); return true; } void MainWindow::CloseProfile() { if (updateChecker != nullptr) updateChecker->showMessage(); if (daily) { daily->Unload(); daily->clearLastDay(); // otherwise Daily will crash delete daily; daily = nullptr; } if (welcome) { delete welcome; welcome = nullptr; } if (overview) { delete overview; overview = nullptr; } if (p_profile) { p_profile->StoreMachines(); p_profile->UnloadMachineData(); p_profile->saveChannels(); p_profile->Save(); p_profile->removeLock(); p_profile = nullptr; } } #ifdef Q_OS_WIN void MainWindow::TestWindowsOpenGL() { #if (QT_VERSION >= QT_VERSION_CHECK(5,4,0)) && !defined(BROKEN_OPENGL_BUILD) // 1. Set OpenGLCompatibilityCheck=1 in registry. QSettings settings; settings.setValue("OpenGLCompatibilityCheck", true); // 2. See if OpenGL crashes the application: QOpenGLWidget* gl; gl = new QOpenGLWidget(ui->tabWidget); ui->tabWidget->insertTab(2, gl, ""); //qDebug() << __LINE__; QCoreApplication::processEvents(); // this triggers the SIGSEGV //qDebug() << __LINE__; // If we get here, OpenGL won't crash the application. ui->tabWidget->removeTab(2); delete gl; // 3. Remove OpenGLCompatibilityCheck from the registry upon success. settings.remove("OpenGLCompatibilityCheck"); #endif } #endif void MainWindow::Startup() { #ifdef Q_OS_WIN TestWindowsOpenGL(); #endif for (auto & loader : GetLoaders()) { loader->setParent(this); } QString lastProfile = AppSetting->profileName(); firstRunMessage(); if (Profiles::profiles.contains(lastProfile) && AppSetting->autoOpenLastUsed()) { if (OpenProfile(lastProfile)) { ui->tabWidget->setCurrentIndex(AppSetting->openTabAtStart()); if (AppSetting->autoLaunchImport()) { on_importButton_clicked(); } } } else { ui->tabWidget->setCurrentWidget(profileSelector); } } int MainWindow::importCPAP(ImportPath import, const QString &message) { if (!import.loader) { return 0; } ui->tabWidget->setCurrentWidget(welcome); QApplication::processEvents(); ProgressDialog * progdlg = new ProgressDialog(this); QPixmap image = import.loader->getPixmap(import.loader->PeekInfo(import.path).series); image = image.scaled(64,64); progdlg->setPixmap(image); progdlg->addAbortButton(); progdlg->setWindowModality(Qt::ApplicationModal); progdlg->open(); progdlg->setMessage(message); connect(import.loader, SIGNAL(updateMessage(QString)), progdlg, SLOT(setMessage(QString))); connect(import.loader, SIGNAL(setProgressMax(int)), progdlg, SLOT(setProgressMax(int))); connect(import.loader, SIGNAL(setProgressValue(int)), progdlg, SLOT(setProgressValue(int))); connect(progdlg, SIGNAL(abortClicked()), import.loader, SLOT(abortImport())); ImportUI importui(p_profile); ImportContext* ctx = new ProfileImportContext(p_profile); import.loader->SetContext(ctx); connect(ctx, &ImportContext::importEncounteredUnexpectedData, &importui, &ImportUI::onUnexpectedData); connect(import.loader, &MachineLoader::deviceReportsUsageOnly, &importui, &ImportUI::onDeviceReportsUsageOnly); connect(import.loader, &MachineLoader::deviceIsUntested, &importui, &ImportUI::onDeviceIsUntested); connect(import.loader, &MachineLoader::deviceIsUnsupported, &importui, &ImportUI::onDeviceIsUnsupported); int c = import.loader->Open(import.path); progdlg->setMessage(QObject::tr("Finishing up...")); QCoreApplication::processEvents(); ctx->Commit(); import.loader->SetContext(nullptr); delete ctx; if (c > 0) { Notify(tr("Imported %1 CPAP session(s) from\n\n%2").arg(c).arg(import.path), tr("Import Success")); } else if (c == 0) { Notify(tr("Already up to date with CPAP data at\n\n%1").arg(import.path), tr("Up to date")); } else { Notify(tr("Couldn't find any valid Machine Data at\n\n%1").arg(import.path),tr("Import Problem")); } disconnect(progdlg, SIGNAL(abortClicked()), import.loader, SLOT(abortImport())); disconnect(import.loader, SIGNAL(setProgressMax(int)), progdlg, SLOT(setProgressMax(int))); disconnect(import.loader, SIGNAL(setProgressValue(int)), progdlg, SLOT(setProgressValue(int))); disconnect(import.loader, SIGNAL(updateMessage(QString)), progdlg, SLOT(setMessage(QString))); progdlg->close(); delete progdlg; if (AppSetting->openTabAfterImport()>0) { ui->tabWidget->setCurrentIndex(AppSetting->openTabAfterImport()); } return c; } void MainWindow::updateOverview() { if (overview) overview->ReloadGraphs(); } void MainWindow::finishCPAPImport() { if (daily) daily->Unload(daily->getDate()); p_profile->StoreMachines(); QList machines = p_profile->GetMachines(MT_CPAP); for (Machine * mach : machines) { mach->saveSessionInfo(); mach->SaveSummaryCache(); } GenerateStatistics(); profileSelector->updateProfileList(); if (welcome) welcome->refreshPage(); if (overview) { overview->ReloadGraphs(); } if (daily) { // daily->populateSessionWidget(); daily->ReloadGraphs(); } if (AppSetting->openTabAfterImport()>0) { ui->tabWidget->setCurrentIndex(AppSetting->openTabAfterImport()); } } void MainWindow::importCPAPBackups() { // Get BackupPaths for all CPAP machines QList machlist = p_profile->GetMachines(MT_CPAP); QList paths; Q_FOREACH(Machine *m, machlist) { paths.append(ImportPath(m->getBackupPath(), lookupLoader(m))); } if (paths.size() > 0) { int c=0; Q_FOREACH(ImportPath path, paths) { c+=importCPAP(path, tr("Please wait, importing from backup folder(s)...")); } if (c>0) { finishCPAPImport(); } } } #ifdef Q_OS_UNIX # include # include # if defined(Q_OS_MAC) || defined(Q_OS_BSD4) # include # elif defined(Q_OS_HAIKU) // nothing needed # else # include # include # endif // Q_OS_MAC/BSD #endif // Q_OS_UNIX //! \brief Returns a list of drive mountpoints QStringList getDriveList() { QStringList drivelist; bool crostini_detected = false; #if QT_VERSION >= QT_VERSION_CHECK(5,4,0) #if defined(Q_OS_LINUX) #define VFAT "vfat" #elif defined(Q_OS_WIN) #define VFAT "FAT32" Q_UNUSED(crostini_detected) #elif defined(Q_OS_MAC) #define VFAT "msdos" Q_UNUSED(crostini_detected) #endif foreach (const QStorageInfo &storage, QStorageInfo::mountedVolumes()) { if (storage.isValid() && storage.isReady()) { #ifdef DEBUG_SDCARD if (storage.fileSystemType() != "tmpfs") { // Don't show all the Linux tmpfs mount points! qDebug() << "Device:" << storage.device(); qDebug() << " Path:" << storage.rootPath(); qDebug() << " Name:" << storage.name(); // ... qDebug() << " FS Type:" << storage.fileSystemType(); } #endif if (storage.fileSystemType() == VFAT) { qDebug() << "Adding" << storage.name() << "on" << storage.rootPath() << "to drivelist"; drivelist.append(storage.rootPath()); } else if (storage.fileSystemType() == "9p") { qDebug() << "Crostini filesystem 9p found"; crostini_detected = true; } } } #endif #if defined(Q_OS_LINUX) if (crostini_detected) { QString mntName("/mnt/chromeos/removable"); QDir mnt(mntName); qDebug() << "Checking for" << mntName; if ( mnt.exists() ) { qDebug() << "Checking Crostini removable folders"; QFileInfoList mntPts = mnt.entryInfoList(); foreach ( const auto dir, mntPts ) { qDebug() << "Adding" << dir.filePath() << "to drivelist"; drivelist.append(dir.filePath() ); } } else { drivelist.clear(); drivelist.append("CROSTINI"); } } #endif return drivelist; } extern MainWindow * mainwin; void ImportDialogScan::cancelbutton() { mainwin->importScanCancelled = true; hide(); } QList MainWindow::detectCPAPCards() { const int timeout = 20000; // twenty seconds QList detectedCards; importScanCancelled = false; QString lastpath = (*p_profile)[STR_PREF_LastCPAPPath].toString(); QListloaders = GetLoaders(MT_CPAP); QTime time; time.start(); // Create dialog ImportDialogScan popup(this) ;//, Qt::SplashScreen); QLabel waitmsg(tr("Please insert your CPAP data card...")); QProgressBar progress; QVBoxLayout waitlayout; popup.setLayout(&waitlayout); QHBoxLayout layout2; QIcon icon("://icons/sdcard.png"); QPushButton skipbtn(icon, tr("Choose a folder")); QPushButton cancelbtn(STR_MessageBox_Cancel); skipbtn.setMinimumHeight(40); cancelbtn.setMinimumHeight(40); waitlayout.addWidget(&waitmsg,1,Qt::AlignCenter); waitlayout.addWidget(&progress,1); waitlayout.addLayout(&layout2,1); layout2.addWidget(&skipbtn); layout2.addWidget(&cancelbtn); popup.connect(&skipbtn, SIGNAL(clicked()), &popup, SLOT(hide())); popup.connect(&cancelbtn, SIGNAL(clicked()), &popup, SLOT(cancelbutton())); progress.setValue(0); progress.setMaximum(timeout); progress.setVisible(true); // importScanCancelled = false; popup.show(); QApplication::processEvents(); // QString lastpath = (*p_profile)[STR_PREF_LastCPAPPath].toString(); do { // Rescan in case card inserted QStringList AutoScannerPaths = getDriveList(); if (AutoScannerPaths.contains("CROSTINI")) { // no Crostini removable drives found! if (( lastpath.size() > 0) && ( ! AutoScannerPaths.contains(lastpath))) { if (QFile(lastpath).exists() ) { AutoScannerPaths.insert(0, lastpath); } } else { QMessageBox::warning(nullptr, STR_MessageBox_Warning, QObject::tr("Chromebook file system detected, but no removable device found\n") + QObject::tr("You must share your SD card with Linux using the ChromeOS Files program")); break; // break out of the 20 second wait loop } } // AutoScannerPaths.push_back(lastpath); qDebug() << "Drive list size:" << AutoScannerPaths.size(); if ( (lastpath.size()>0) && ( ! AutoScannerPaths.contains(lastpath))) { if (QFile(lastpath).exists()) AutoScannerPaths.insert(0, lastpath); } Q_FOREACH(const QString &path, AutoScannerPaths) { // Scan through available machine loaders and test if this folder contains valid folder structure Q_FOREACH(MachineLoader * loader, loaders) { if (loader->Detect(path)) { detectedCards.append(ImportPath(path, loader)); qDebug() << "Found" << loader->loaderName() << "datacard at" << path; } QApplication::processEvents(); } } int el=time.elapsed(); progress.setValue(el); if (el > timeout) break; if ( ! popup.isVisible()) break; // needs a slight delay here for (int i=0; i<20; ++i) { QThread::msleep(50); QApplication::processEvents(); } } while (detectedCards.size() == 0); popup.hide(); popup.disconnect(&skipbtn, SIGNAL(clicked()), &popup, SLOT(hide())); popup.disconnect(&cancelbtn, SIGNAL(clicked()), &popup, SLOT(hide())); return detectedCards; } void MainWindow::on_action_Import_Data_triggered() { static bool in_import = false; if ( p_profile == nullptr ) { Notify(tr("No profile has been selected for Import."), STR_MessageBox_Busy); return; } if (m_inRecalculation) { Notify(tr("Access to Import has been blocked while recalculations are in progress."),STR_MessageBox_Busy); return; } if (in_import) { Notify(tr("Import is already running in the background."),STR_MessageBox_Busy); return; } in_import=true; ui->tabWidget->setCurrentWidget(welcome); QApplication::processEvents(); QList datacards = selectCPAPDataCards(tr("Would you like to import from this location?")); if (datacards.size() > 0) { importCPAPDataCards(datacards); } in_import=false; } QList MainWindow::selectCPAPDataCards(const QString & prompt, bool alwaysPrompt) { QList datacards = detectCPAPCards(); if (importScanCancelled) { datacards.clear(); return datacards; } QListloaders = GetLoaders(MT_CPAP); QTime time; time.start(); bool asknew = false; // TODO: This should either iterate over all detected cards and prompt for each, or it should only // provide the one confirmed card in the list. if (datacards.size() > 0) { MachineInfo info = datacards[0].loader->PeekInfo(datacards[0].path); QString infostr; if (!info.model.isEmpty()) { QString infostr2 = info.model+" ("+info.serial+")"; infostr = tr("A %1 file structure for a %2 was located at:").arg(info.brand).arg(infostr2); } else { infostr = tr("A %1 file structure was located at:").arg(datacards[0].loader->loaderName()); } if (alwaysPrompt || !p_profile->cpap->autoImport()) { QMessageBox mbox(QMessageBox::NoIcon, tr("CPAP Data Located"), infostr+"\n\n"+QDir::toNativeSeparators(datacards[0].path)+"\n\n"+ prompt, QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, this); mbox.setDefaultButton(QMessageBox::Yes); mbox.setButtonText(QMessageBox::No, tr("Specify")); QPixmap pixmap = datacards[0].loader->getPixmap(datacards[0].loader->PeekInfo(datacards[0].path).series).scaled(64,64); //QPixmap pixmap = QPixmap(getCPAPPixmap(datacards[0].loader->loaderName())).scaled(64,64); mbox.setIconPixmap(pixmap); int res = mbox.exec(); if (res == QMessageBox::Cancel) { // Give the communal progress bar back datacards.clear(); return datacards; } else if (res == QMessageBox::No) { //waitmsg->setText(tr("Please wait, launching file dialog...")); datacards.clear(); asknew = true; } } } else { //waitmsg->setText(tr("No CPAP data card detected, launching file dialog...")); asknew = true; } // TODO: Get rid of the reminder and instead validate the user's selection (using the loader detection // below) and loop until the user either cancels or selects a valid folder. // // It doesn't look like there's any way to implement such a programmatic filter within the file // selection dialog without resorting to a non-native dialog. if (asknew) { // popup.show(); mainwin->Notify(tr("Please remember to select the root folder or drive letter of your data card, and not a folder inside it."), tr("Import Reminder"),8000); QFileDialog w(this); QString folder; if (p_profile->contains(STR_PREF_LastCPAPPath)) { folder = (*p_profile)[STR_PREF_LastCPAPPath].toString(); } else { // TODO: Is a writable path really the best place to direct the user to find their SD card data? folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); } w.setDirectory(folder); w.setFileMode(QFileDialog::Directory); w.setOption(QFileDialog::ShowDirsOnly, true); w.setWindowTitle(tr("Find your CPAP data card")); // This doesn't work on WinXP #if defined(Q_OS_MAC) w.setOption(QFileDialog::DontUseNativeDialog,false); #elif defined(Q_OS_UNIX) w.setOption(QFileDialog::DontUseNativeDialog,false); #elif defined(Q_OS_WIN) // check the Os version.. winxp chokes w.setOption(QFileDialog::DontUseNativeDialog, true); #endif //#else // w.setOption(QFileDialog::DontUseNativeDialog, false); // QListView *l = w.findChild("listView"); // if (l) { // l->setSelectionMode(QAbstractItemView::MultiSelection); // } // QTreeView *t = w.findChild(); // if (t) { // t->setSelectionMode(QAbstractItemView::MultiSelection); // } //#endif if (w.exec() != QDialog::Accepted) { datacards.clear(); return datacards; } for (int i = 0; i < w.selectedFiles().size(); i++) { Q_FOREACH(MachineLoader * loader, loaders) { if (loader->Detect(w.selectedFiles().at(i))) { datacards.append(ImportPath(w.selectedFiles().at(i), loader)); break; } } } } return datacards; } void MainWindow::importCPAPDataCards(const QList & datacards) { bool newdata = false; int c = -1; for (int i = 0; i < datacards.size(); i++) { QString dir = datacards[i].path; MachineLoader * loader = datacards[i].loader; if (!loader) continue; if (!dir.isEmpty()) { c = importCPAP(datacards[i], tr("Importing Data")); qDebug() << "Finished Importing data" << c; if (c >= 0) { QDir d(dir.section("/",0,-1)); (*p_profile)[STR_PREF_LastCPAPPath] = d.absolutePath(); } if (c > 0) { newdata = true; } } } if (newdata) { finishCPAPImport(); PopulatePurgeMenu(); } } QMenu *MainWindow::CreateMenu(QString title) { QMenu *menu = new QMenu(title, ui->menubar); ui->menubar->insertMenu(ui->menu_Help->menuAction(), menu); return menu; } void MainWindow::on_action_Fullscreen_triggered() { if (ui->action_Fullscreen->isChecked()) { this->showMaximized(); } else { this->showNormal(); } } void MainWindow::setRecBoxHTML(QString html) { ui->recordsBox->setHtml(html); } void MainWindow::setStatsHTML(QString html) { ui->statisticsView->setHtml(html); } void MainWindow::updateFavourites() { QDate date = p_profile->LastDay(MT_JOURNAL); if (!date.isValid()) { return; } QString html = "" ""; do { Day *journal = p_profile->GetDay(date, MT_JOURNAL); if (journal) { if (journal->size() > 0) { Session *sess = journal->firstSession(MT_JOURNAL); if (!sess) { qWarning() << "null session for MT_JOURNAL first session"; } else { QString tmp; bool filtered = !bookmarkFilter.isEmpty(); bool found = !filtered; if (sess->settings.contains(Bookmark_Start)) { //QVariantList start=sess->settings[Bookmark_Start].toList(); //QVariantList end=sess->settings[Bookmark_End].toList(); QStringList notes = sess->settings[Bookmark_Notes].toStringList(); if (notes.size() > 0) { tmp += QString(""; } } if (found) { html += tmp; } } } } date = date.addDays(-1); } while (date >= p_profile->FirstDay(MT_JOURNAL)); html += "
    %2
    ") .arg(date.toString(Qt::ISODate)) .arg(date.toString(MedDateFormat)); tmp += ""; for (int i = 0; i < notes.size(); i++) { //QDate d=start[i].toDate(); QString note = notes[i]; if (filtered && note.contains(bookmarkFilter, Qt::CaseInsensitive)) { found = true; } tmp += "
  • " + note + "
  • "; } tmp += "
    "; ui->bookmarkView->setHtml(html); } void MainWindow::on_dailyButton_clicked() { if (daily) { ui->tabWidget->setCurrentWidget(daily); daily->RedrawGraphs(); } } void MainWindow::on_overviewButton_clicked() { if (overview) { ui->tabWidget->setCurrentWidget(overview); } } void MainWindow::aboutBoxLinkClicked(const QUrl &url) { QDesktopServices::openUrl(url); } void MainWindow::on_action_About_triggered() { AboutDialog * about = new AboutDialog(this); about->exec(); about->deleteLater(); } void MainWindow::on_actionDebug_toggled(bool checked) { AppSetting->setShowDebug(checked); logger->strlock.lock(); if (checked) { ui->logText->show(); } else { ui->logText->hide(); } // QApplication::processEvents(); logger->strlock.unlock(); } void MainWindow::on_action_Reset_Graph_Layout_triggered() { if (daily && (ui->tabWidget->currentWidget() == daily)) { daily->ResetGraphLayout(); } if (overview && (ui->tabWidget->currentWidget() == overview)) { overview->ResetGraphLayout(); } } /* void MainWindow::on_action_Reset_Graph_Order_triggered() { if (daily && (ui->tabWidget->currentWidget() == daily)) { daily->ResetGraphOrder(0); } if (overview && (ui->tabWidget->currentWidget() == overview)) { overview->ResetGraphOrder(0); } } */ void MainWindow::on_action_Standard_Graph_Order_triggered() { if (daily && (ui->tabWidget->currentWidget() == daily)) { daily->ResetGraphOrder(1); } if (overview && (ui->tabWidget->currentWidget() == overview)) { overview->ResetGraphOrder(1); } } void MainWindow::on_action_Advanced_Graph_Order_triggered() { if (daily && (ui->tabWidget->currentWidget() == daily)) { daily->ResetGraphOrder(2); } if (overview && (ui->tabWidget->currentWidget() == overview)) { overview->ResetGraphOrder(2); } } void MainWindow::on_action_Preferences_triggered() { if (!p_profile) { mainwin->Notify(tr("Please open a profile first.")); return; } if (m_inRecalculation) { mainwin->Notify(tr("Access to Preferences has been blocked until recalculation completes.")); return; } PreferencesDialog pd(this, p_profile); prefdialog = &pd; if (pd.exec() == PreferencesDialog::Accepted) { // Apply any updates from preference changes (notably fonts) setApplicationFont(); if (daily) { daily->RedrawGraphs(); } if (overview) { overview->ResetFont(); overview->RebuildGraphs(true); } if (welcome) welcome->refreshPage(); if (profileSelector) profileSelector->updateProfileList(); // These attempts to update calendar after a change to application font do NOT work, and I can find no QT documentation suggesting // that changing the font after Calendar is created is even possible. // qDebug() << "application font family set to" << QApplication::font().family() << "and font" << QApplication::font(); // ui->statStartDate->calendarWidget()->setFont(QApplication::font()); // ui->statStartDate->calendarWidget()->repaint(); } prefdialog = nullptr; } #include "oximeterimport.h" QDateTime datetimeDialog(QDateTime datetime, QString message); void MainWindow::on_oximetryButton_clicked() { if (p_profile) { OximeterImport oxiimp(this); oxiimp.exec(); PopulatePurgeMenu(); if (overview) overview->ReloadGraphs(); if (welcome) welcome->refreshPage(); } } // Called for automatic check for updates void MainWindow::CheckForUpdates(bool showWhenCurrent) { updateChecker = new CheckUpdates(this); #ifdef NO_CHECKUPDATES if (showWhenCurrent) QMessageBox::information(nullptr, STR_MessageBox_Information, tr("Check for updates not implemented")); #else updateChecker->checkForUpdates(showWhenCurrent); #endif } // Called for manual check for updates void MainWindow::on_action_Check_for_Updates_triggered() { CheckForUpdates(true); } bool toolbox_visible = false; void MainWindow::on_action_Screenshot_triggered() { setUpdatesEnabled(false); if (daily) daily->hideSpaceHogs(); toolbox_visible = ui->toolBox->isVisible(); ui->toolBox->hide(); setUpdatesEnabled(true); QTimer::singleShot(250, this, SLOT(DelayedScreenshot())); } void MainWindow::DelayedScreenshot() { // Make sure to scale for high resolution displays (like Retina) // qreal pr = devicePixelRatio(); auto screenshotRect = geometry(); auto titleBarHeight = QApplication::style()->pixelMetric(QStyle::PM_TitleBarHeight); auto pixmap = QApplication::primaryScreen()->grabWindow(QDesktopWidget().winId(), screenshotRect.left(), screenshotRect.top() - titleBarHeight, screenshotRect.width(), screenshotRect.height() + titleBarHeight); QString default_filename = "/screenshot-" + QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss") + ".png"; QString png_filepath; if (AppSetting->dontAskWhenSavingScreenshots()) { png_filepath = p_pref->Get("{home}/Screenshots"); QDir dir(png_filepath); if (!dir.exists()) { dir.mkdir(png_filepath); } png_filepath += default_filename; } else { QString folder = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + default_filename; png_filepath = QFileDialog::getSaveFileName(this, tr("Choose where to save screenshot"), folder, tr("Image files (*.png)")); if (png_filepath.isEmpty() == false && png_filepath.toLower().endsWith(".png") == false) { png_filepath += ".png"; } } // png_filepath will be empty if the user canceled the file selection above. if (png_filepath.isEmpty() == false) { qDebug() << "Saving screenshot to" << png_filepath; if (!pixmap.save(png_filepath)) { Notify(tr("There was an error saving screenshot to file \"%1\"").arg(QDir::toNativeSeparators(png_filepath))); } else { Notify(tr("Screenshot saved to file \"%1\"").arg(QDir::toNativeSeparators(png_filepath))); } } setUpdatesEnabled(false); if (daily) daily->showSpaceHogs(); ui->toolBox->setVisible(toolbox_visible); setUpdatesEnabled(true); } void MainWindow::on_actionView_Oximetry_triggered() { on_oximetryButton_clicked(); } void MainWindow::on_actionPrint_Report_triggered() { Report report; if (ui->tabWidget->currentWidget() == overview) { Report::PrintReport(overview->graphView(), STR_TR_Overview); } else if (ui->tabWidget->currentWidget() == daily) { Report::PrintReport(daily->graphView(), STR_TR_Daily, daily->getDate()); } else if (ui->tabWidget->currentWidget() == ui->statisticsTab) { Statistics::printReport(this); #ifndef helpless } else if (ui->tabWidget->currentWidget() == help) { help->print(&printer); // **** THIS DID NOT SURVIVE REFACTORING STATISTICS PRINT #endif } } void MainWindow::on_action_Edit_Profile_triggered() { NewProfile *newprof = new NewProfile(this); QString name = AppSetting->profileName(); newprof->edit(name); newprof->setWindowModality(Qt::ApplicationModal); newprof->setModal(true); newprof->exec(); qDebug() << newprof; delete newprof; } void MainWindow::on_action_CycleTabs_triggered() { int i; qDebug() << "Switching Tabs"; i = ui->tabWidget->currentIndex() + 1; if (i >= ui->tabWidget->count()) { i = 0; } ui->tabWidget->setCurrentIndex(i); } void MainWindow::on_actionOnline_Users_Guide_triggered() { // QDesktopServices::openUrl(QUrl("http://sleepyhead.sourceforge.net/wiki/index.php?title=OSCAR_Users_Guide")); // QMessageBox::information(nullptr, STR_MessageBox_Information, tr("The User's Guide is not yet available")); if (QMessageBox::question(nullptr, STR_MessageBox_Question, tr("The User's Guide will open in your default browser"), QMessageBox::Ok|QMessageBox::Cancel, QMessageBox::Ok) == QMessageBox::Ok ) QDesktopServices::openUrl(QUrl("https://www.apneaboard.com/wiki/index.php?title=OSCAR_Help")); } void MainWindow::on_action_Frequently_Asked_Questions_triggered() { // QDesktopServices::openUrl(QUrl("http://sleepyhead.sourceforge.net/wiki/index.php?title=Frequently_Asked_Questions")); QMessageBox::information(nullptr, STR_MessageBox_Information, tr("The FAQ is not yet implemented")); } void MainWindow::on_action_Rebuild_Oximetry_Index_triggered() { QVector invalid; QList machines = p_profile->GetMachines(MT_OXIMETER); qint64 f = 0, l = 0; int discard_threshold = p_profile->oxi->oxiDiscardThreshold(); Machine *m; for (int z = 0; z < machines.size(); z++) { m = machines.at(z); //m->sessionlist.erase(m->sessionlist.find(0)); QList valid = m->availableChannels(schema::ChanType::ALL); valid.removeAll(OXI_PulseChange); // Delete only these and recalculate. valid.removeAll(OXI_SPO2Drop); // For each Session for (QHash::iterator s = m->sessionlist.begin(); s != m->sessionlist.end(); s++) { Session *sess = s.value(); if (!sess) { continue; } sess->OpenEvents(); // For each EventList contained in session invalid.clear(); f = 0; l = 0; for (QHash >::iterator e = sess->eventlist.begin(); e != sess->eventlist.end(); e++) { // Discard any non data events. if (!valid.contains(e.key())) { // delete and push aside for later to clean up for (int i = 0; i < e.value().size(); i++) { delete e.value()[i]; } e.value().clear(); invalid.push_back(e.key()); } else { QVector newlist; for (int i = 0; i < e.value().size(); i++) { if (e.value()[i]->count() > (unsigned)discard_threshold) { newlist.push_back(e.value()[i]); } else { delete e.value()[i]; } } for (int i = 0; i < newlist.size(); i++) { EventList *el = newlist[i]; if (!f || f > el->first()) { f = el->first(); } if (!l || l < el->last()) { l = el->last(); } } e.value() = newlist; } } for (int i = 0; i < invalid.size(); i++) { sess->eventlist.erase(sess->eventlist.find(invalid[i])); } if (f) { sess->really_set_first(f); } if (l) { sess->really_set_last(l); } sess->m_cnt.clear(); sess->m_sum.clear(); sess->m_min.clear(); sess->m_max.clear(); sess->m_cph.clear(); sess->m_sph.clear(); sess->m_avg.clear(); sess->m_wavg.clear(); sess->m_valuesummary.clear(); sess->m_timesummary.clear(); sess->m_firstchan.clear(); sess->m_lastchan.clear(); sess->SetChanged(true); } } for (int i = 0; i < machines.size(); i++) { Machine *m = machines[i]; m->Save(); m->SaveSummaryCache(); } daily->LoadDate(daily->getDate()); overview->ReloadGraphs(); } void MainWindow::reloadProfile() { QString username = p_profile->user->userName(); int tabidx = ui->tabWidget->currentIndex(); CloseProfile(); OpenProfile(username); ui->tabWidget->setCurrentIndex(tabidx); } void MainWindow::RestartApplication(bool force_login, QString cmdline) { CloseProfile(); p_pref->Save(); QString apppath; #ifdef Q_OS_MAC // In Mac OS the full path of aplication binary is: // /myApp.app/Contents/MacOS/myApp // prune the extra bits to just get the app bundle path apppath = QApplication::instance()->applicationDirPath().section("/", 0, -3); QStringList args; args << "-n"; // -n option is important, as it opens a new process args << apppath; args << "--args"; // OSCAR binary options after this args << "-p"; // -p starts with 1 second delay, to give this process time to save.. if (force_login) { args << "-l"; } args << cmdline; if (QProcess::startDetached("/usr/bin/open", args)) { QApplication::instance()->exit(); } else { QMessageBox::warning(nullptr, STR_MessageBox_Error, tr("If you can read this, the restart command didn't work. You will have to do it yourself manually."), QMessageBox::Ok); } #else apppath = QApplication::instance()->applicationFilePath(); // If this doesn't work on windoze, try uncommenting this method // Technically should be the same thing.. //if (QDesktopServices::openUrl(apppath)) { // QApplication::instance()->exit(); //} else QStringList args; args << "-p"; if (force_login) { args << "-l"; } args << cmdline; //if (change_datafolder) { args << "-d"; } if (QProcess::startDetached(apppath, args)) { QApplication::instance()->exit(); // ::exit(0); } else { QMessageBox::warning(nullptr, STR_MessageBox_Error, tr("If you can read this, the restart command didn't work. You will have to do it yourself manually."), QMessageBox::Ok); } #endif } void MainWindow::on_actionPurge_Current_Day_triggered() { this->purgeDay(MT_CPAP); } void MainWindow::on_actionPurgeCurrentDayOximetry_triggered() { this->purgeDay(MT_OXIMETER); } void MainWindow::on_actionPurgeCurrentDaySleepStage_triggered() { this->purgeDay(MT_SLEEPSTAGE); } void MainWindow::on_actionPurgeCurrentDayPosition_triggered() { this->purgeDay(MT_POSITION); } void MainWindow::on_actionPurgeCurrentDayAllExceptNotes_triggered() { this->purgeDay(MT_UNKNOWN); } void MainWindow::on_actionPurgeCurrentDayAll_triggered() { this->purgeDay(MT_JOURNAL); } // Purge data for a given machine type. // Special handling: MT_JOURNAL == All data. MT_UNKNOWN == All except journal void MainWindow::purgeDay(MachineType type) { if (!daily) return; QDate date = daily->getDate(); qDebug() << "Purging data from" << date; daily->Unload(date); Day *day = p_profile->GetDay(date, MT_UNKNOWN); Machine *cpap = nullptr; if (!day) return; QList::iterator s; QList list; for (s = day->begin(); s != day->end(); ++s) { Session *sess = *s; if (type == MT_JOURNAL || (type == MT_UNKNOWN && sess->type() != MT_JOURNAL) || sess->type() == type) { list.append(*s); qDebug() << "Purging session from " << (*s)->machine()->loaderName() << " ID:" << (*s)->session() << "["+QDateTime::fromTime_t((*s)->session()).toString()+"]"; qDebug() << "First Time:" << QDateTime::fromMSecsSinceEpoch((*s)->realFirst()).toString(); qDebug() << "Last Time:" << QDateTime::fromMSecsSinceEpoch((*s)->realLast()).toString(); if (sess->type() == MT_CPAP) { cpap = day->machine(MT_CPAP); } } else { qDebug() << "Skipping session from " << (*s)->machine()->loaderName() << " ID:" << (*s)->session() << "["+QDateTime::fromTime_t((*s)->session()).toString()+"]"; } } if (list.size() > 0) { if (cpap) { QFile rxcache(p_profile->Get("{" + STR_GEN_DataFolder + "}/RXChanges.cache" )); rxcache.remove(); QFile sumfile(cpap->getDataPath()+"Summaries.xml.gz"); sumfile.remove(); } // m->day.erase(m->day.find(date)); QSet machines; for (int i = 0; i < list.size(); i++) { Session *sess = list.at(i); machines += sess->machine(); sess->Destroy(); // remove the summary and event files delete sess; } for (auto & mach : machines) { mach->SaveSummaryCache(); } if (cpap) { // save purge date where later import should start QDate pd = cpap->purgeDate(); if (pd.isNull() || day->date() < pd) cpap->setPurgeDate(day->date()); } } else { // No data purged... could notify user? return; } day = p_profile->GetDay(date, MT_UNKNOWN); Q_UNUSED(day); daily->clearLastDay(); daily->LoadDate(date); if (overview) overview->ReloadGraphs(); if (welcome) welcome->refreshPage(); GenerateStatistics(); } void MainWindow::on_actionRebuildCPAP(QAction *action) { ui->tabWidget->setCurrentWidget(welcome); // Daily view can't run during rebuild QApplication::processEvents(); QString data = action->data().toString(); QString cls = data.section(":",0,0); QString serial = data.section(":", 1); QList machines = p_profile->GetMachines(MT_CPAP); Machine * mach = nullptr; for (int i=0; i < machines.size(); ++i) { Machine * m = machines.at(i); if ((m->loaderName() == cls) && (m->serial() == serial)) { mach = m; break; } } if (!mach) return; QString bpath = mach->getBackupPath(); bool backups = (dirCount(bpath) > 0) ? true : false; if (backups) { if (QMessageBox::question(this, STR_MessageBox_Question, tr("Are you sure you want to rebuild all CPAP data for the following machine:\n\n") + mach->brand() + " " + mach->model() + " " + mach->modelnumber() + " (" + mach->serial() + ")\n\n" + tr("Please note, that this could result in loss of data if OSCAR's backups have been disabled."), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) { return; } } else { if (QMessageBox::question(this, STR_MessageBox_Warning, "

    "+STR_MessageBox_Warning+": "+tr("For some reason, OSCAR does not have any backups for the following machine:")+ "

    " + "

    "+mach->brand() + " " + mach->model() + " " + mach->modelnumber() + " (" + mach->serial() + ")

    "+ "

    "+tr("Provided you have made your own backups for ALL of your CPAP data, you can still complete this operation, but you will have to restore from your backups manually.")+"

    " + "

    "+tr("Are you really sure you want to do this?")+"

    ", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) { return; } } QString path = mach->getBackupPath(); MachineLoader *loader = lookupLoader(mach); purgeMachine(mach); // purge destroys machine record if (backups) { importCPAP(ImportPath(path, loader), tr("Please wait, importing from backup folder(s)...")); } else { if (QMessageBox::information(this, STR_MessageBox_Warning, tr("Because there are no internal backups to rebuild from, you will have to restore from your own.")+"\n\n"+ tr("Would you like to import from your own backups now? (you will have no data visible for this machine until you do)"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { on_action_Import_Data_triggered(); } else { } } if (overview) overview->ReloadGraphs(); if (daily) { daily->Unload(); daily->clearLastDay(); // otherwise Daily will crash daily->ReloadGraphs(); } if (welcome) welcome->refreshPage(); PopulatePurgeMenu(); GenerateStatistics(); p_profile->StoreMachines(); } void MainWindow::on_actionPurgeMachine(QAction *action) { QString data = action->data().toString(); QString cls = data.section(":",0,0); QString serial = data.section(":", 1); QList machines = p_profile->GetMachines(); Machine * mach = nullptr; for (int i=0; i < machines.size(); ++i) { Machine * m = machines.at(i); if ((m->loaderName() == cls) && (m->serial() == serial)) { mach = m; break; } } if (!mach) return; QString machname = mach->brand(); if (machname.isEmpty()) { machname = mach->loaderName(); } machname += " " + mach->model() + " " + mach->modelnumber(); if (!mach->serial().isEmpty()) { machname += QString(" (%1)").arg(mach->serial()); } QString backupnotice; QString bpath = mach->getBackupPath(); bool backups = (dirCount(bpath) > 0) ? true : false; if (backups) { backupnotice = "

    " + tr("Note as a precaution, the backup folder will be left in place.") + "

    "; } else { backupnotice = "

    " + tr("OSCAR does not have any backups for this machine!") + "

    " + "

    " + tr("Unless you have made your own backups for ALL of your data for this machine, " "you will lose this machine's data permanently!") + "

    "; } if (QMessageBox::question(this, STR_MessageBox_Warning, "

    "+STR_MessageBox_Warning+": " + tr("You are about to obliterate OSCAR's machine database for the following machine:

    ") + "

    " + machname + "

    " + backupnotice+ "

    "+tr("Are you absolutely sure you want to proceed?")+"

    ", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { qDebug() << "Purging" << machname; purgeMachine(mach); } } void MainWindow::purgeMachine(Machine * mach) { // detect backups daily->Unload(daily->getDate()); // Technicially the above won't sessions under short session limit.. Using Purge to clean up the rest. if (mach->Purge(3478216)) { mach->sessionlist.clear(); mach->day.clear(); QDir dir; QString path = mach->getDataPath(); path.chop(1); qDebug() << "path to machine" << path; p_profile->DelMachine(mach); delete mach; // remove the directory unless it's got unexpected crap in it.. bool deleted = false; if ( ! dir.rmdir(path)) { #ifdef Q_OS_WIN wchar_t* directoryPtr = (wchar_t*)path.utf16(); SetFileAttributes(directoryPtr, GetFileAttributes(directoryPtr) & ~FILE_ATTRIBUTE_READONLY); if (!::RemoveDirectory(directoryPtr)) { DWORD lastError = ::GetLastError(); qDebug() << "RemoveDirectory" << path << "GetLastError: " << lastError << "(Error 145 is expected)"; if (lastError == 145) { qDebug() << path << "remaining directory contents are" << QDir(path).entryList(); } } else { qDebug() << "Success on second attempt deleting folder with windows API " << path; deleted = true; } #else qWarning() << "Couldn't remove directory" << path; #endif } else { deleted = true; } if ( ! deleted) { qWarning() << "Leaving backup folder intact"; } PopulatePurgeMenu(); p_profile->StoreMachines(); } else { QMessageBox::warning(this, STR_MessageBox_Error, tr("A file permission error casued the purge process to fail; you will have to delete the following folder manually:") + "\n\n" + QDir::toNativeSeparators(mach->getDataPath()), QMessageBox::Ok, QMessageBox::Ok); if (overview) overview->ReloadGraphs(); if (daily) { daily->clearLastDay(); // otherwise Daily will crash daily->ReloadGraphs(); } if (welcome) welcome->refreshPage(); //GenerateStatistics(); return; } if (overview) overview->ReloadGraphs(); QFile rxcache(p_profile->Get("{" + STR_GEN_DataFolder + "}/RXChanges.cache" )); rxcache.remove(); if (daily) { daily->clearLastDay(); // otherwise Daily will crash daily->ReloadGraphs(); } if (welcome) welcome->refreshPage(); QApplication::processEvents(); } void MainWindow::keyPressEvent(QKeyEvent *event) { Q_UNUSED(event) //qDebug() << "Keypress:" << event->key(); } void MainWindow::on_action_Sidebar_Toggle_toggled(bool visible) { ui->toolBox->setVisible(visible); AppSetting->setRightSidebarVisible(visible); } void MainWindow::on_helpButton_clicked() { #ifndef helpless ui->tabWidget->setCurrentWidget(help); #else QMessageBox::information(nullptr, STR_MessageBox_Error, tr("No help is available.")); #endif } void MainWindow::on_actionView_Statistics_triggered() { ui->tabWidget->setCurrentWidget(ui->statisticsTab); } void MainWindow::on_tabWidget_currentChanged(int index) { Q_UNUSED(index); // QWidget *widget = ui->tabWidget->currentWidget(); } void MainWindow::on_filterBookmarks_editingFinished() { bookmarkFilter = ui->filterBookmarks->text(); updateFavourites(); } void MainWindow::on_filterBookmarksButton_clicked() { if (!bookmarkFilter.isEmpty()) { ui->filterBookmarks->setText(""); bookmarkFilter = ""; updateFavourites(); } } void MainWindow::recompressEvents() { QTimer::singleShot(0, this, SLOT(doRecompressEvents())); } void MainWindow::reprocessEvents(bool restart) { m_restartRequired = restart; QTimer::singleShot(0, this, SLOT(doReprocessEvents())); } void MainWindow::FreeSessions() { QDate first = p_profile->FirstDay(); QDate date = p_profile->LastDay(); Day *day; QDate current = daily->getDate(); do { day = p_profile->GetDay(date, MT_CPAP); if (day) { if (date != current) { day->CloseEvents(); } } date = date.addDays(-1); } while (date >= first); } void MainWindow::doRecompressEvents() { if (!p_profile) return; ProgressDialog progress(this); progress.setMessage(QObject::tr("Recompressing Session Files")); progress.setProgressMax(p_profile->daylist.size()); QPixmap icon = QPixmap(":/icons/logo-md.png").scaled(64,64); progress.setPixmap(icon); progress.open(); bool isopen; int idx = 0; for (Day * day : p_profile->daylist) { for (Session * sess : day->sessions) { isopen = sess->eventsLoaded(); // Load the events and summary if they aren't loaded already sess->LoadSummary(); sess->OpenEvents(); sess->SetChanged(true); sess->machine()->SaveSession(sess); if (!isopen) { sess->TrashEvents(); } } progress.setProgressValue(++idx); QApplication::processEvents(); } progress.close(); } void MainWindow::doReprocessEvents() { if (!p_profile) return; ProgressDialog progress(this); progress.setMessage("Recalculating summaries"); progress.setProgressMax(p_profile->daylist.size()); QPixmap icon = QPixmap(":/icons/logo-md.png").scaled(64,64); progress.setPixmap(icon); progress.open(); if (daily) { daily->Unload(); daily->clearLastDay(); // otherwise Daily will crash delete daily; daily = nullptr; } if (welcome) { delete welcome; welcome = nullptr; } if (overview) { delete overview; overview = nullptr; } for (Day * day : p_profile->daylist) { for (Session * sess : day->sessions) { bool isopen = sess->eventsLoaded(); // Load the events if they aren't loaded already sess->LoadSummary(); sess->OpenEvents(); // Destroy any current user flags.. sess->destroyEvent(CPAP_UserFlag1); sess->destroyEvent(CPAP_UserFlag2); sess->destroyEvent(CPAP_UserFlag3); // AHI flags sess->destroyEvent(CPAP_AHI); sess->destroyEvent(CPAP_RDI); if (sess->machine()->loaderName() != STR_MACH_PRS1) { sess->destroyEvent(CPAP_LargeLeak); } else { sess->destroyEvent(CPAP_Leak); } sess->SetChanged(true); sess->UpdateSummaries(); sess->machine()->SaveSession(sess); if (!isopen) { sess->TrashEvents(); } } } progress.close(); welcome = new Welcome(ui->tabWidget); ui->tabWidget->insertTab(1, welcome, tr("Welcome")); daily = new Daily(ui->tabWidget, nullptr); ui->tabWidget->insertTab(2, daily, STR_TR_Daily); daily->ReloadGraphs(); overview = new Overview(ui->tabWidget, daily->graphView()); ui->tabWidget->insertTab(3, overview, STR_TR_Overview); overview->ReloadGraphs(); // Should really create welcome and statistics here, but they need redoing later anyway to kill off webkit ui->tabWidget->setCurrentIndex(AppSetting->openTabAtStart()); GenerateStatistics(); PopulatePurgeMenu(); } void MainWindow::on_actionImport_ZEO_Data_triggered() { ZEOLoader zeo; importNonCPAP(zeo); } void MainWindow::on_actionImport_Dreem_Data_triggered() { DreemLoader dreem; importNonCPAP(dreem); } void MainWindow::on_actionImport_RemStar_MSeries_Data_triggered() { #ifdef REMSTAR_M_SUPPORT QFileDialog w; w.setFileMode(QFileDialog::ExistingFiles); w.setOption(QFileDialog::ShowDirsOnly, false); w.setOption(QFileDialog::DontUseNativeDialog, true); w.setNameFilters(QStringList("M-Series data file (*.bin)")); MSeriesLoader mseries; if (w.exec() == QFileDialog::Accepted) { QString filename = w.selectedFiles()[0]; if (!mseries.Open(filename, p_profile)) { Notify(tr("There was a problem opening MSeries block File: ") + filename); return; } Notify(tr("MSeries Import complete")); daily->LoadDate(daily->getDate()); } #endif } void MainWindow::on_actionSleep_Disorder_Terms_Glossary_triggered() { // QDesktopServices::openUrl(QUrl("http://sleepyhead.sourceforge.net/wiki/index.php?title=Glossary")); // QMessageBox::information(nullptr, STR_MessageBox_Information, tr("The Glossary is not yet implemented")); if (QMessageBox::question(nullptr, STR_MessageBox_Question, tr("The Glossary will open in your default browser"), QMessageBox::Ok|QMessageBox::Cancel, QMessageBox::Ok) == QMessageBox::Ok ) QDesktopServices::openUrl(QUrl("https://www.apneaboard.com/wiki/index.php?title=Definitions")); } /* void MainWindow::on_actionHelp_Support_OSCAR_Development_triggered() { // QDesktopServices().openUrl(QUrl("https://sleepyhead.jedimark.net/donate.php")); QMessageBox::information(nullptr, STR_MessageBox_Information, tr("Donations are not implemented")); } */ void MainWindow::on_actionChange_Language_triggered() { // if (QMessageBox::question(this,STR_MessageBox_Warning,tr("Changing the language will reset custom Event and Waveform names/labels/descriptions.")+"\n\n"+tr("Are you sure you want to do this?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { // return; // } RestartApplication(true, "--language"); } void MainWindow::on_actionChange_Data_Folder_triggered() { if (p_profile) { p_profile->Save(); p_profile->removeLock(); } p_pref->Save(); RestartApplication(false, "-d"); } void MainWindow::importNonCPAP(MachineLoader &loader) { QFileDialog w; w.setFileMode(QFileDialog::ExistingFiles); w.setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); w.setOption(QFileDialog::ShowDirsOnly, false); #if defined(Q_OS_WIN) // Windows can't handle Viatom name filter - use non-native for all non-CPAP loaders. w.setOption(QFileDialog::DontUseNativeDialog, true); #endif w.setNameFilters(loader.getNameFilter()); // Display progress if we have more than 1 file to load... ProgressDialog progress(this); if (w.exec() == QFileDialog::Accepted) { QStringList files = w.selectedFiles(); int size = files.size(); if (size > 1) { progress.setMessage(QObject::tr("Importing Sessions...")); progress.setProgressMax(size); progress.setProgressValue(0); progress.addAbortButton(); progress.setWindowModality(Qt::ApplicationModal); connect(&loader, SIGNAL(setProgressValue(int)), &progress, SLOT(setProgressValue(int))); connect(&progress, SIGNAL(abortClicked()), &loader, SLOT(abortImport())); progress.open(); QCoreApplication::processEvents(); } QString name = loader.loaderName(); ImportUI importui(p_profile); ImportContext* ctx = new ProfileImportContext(p_profile); loader.SetContext(ctx); connect(ctx, &ImportContext::importEncounteredUnexpectedData, &importui, &ImportUI::onUnexpectedData); connect(&loader, &MachineLoader::deviceReportsUsageOnly, &importui, &ImportUI::onDeviceReportsUsageOnly); connect(&loader, &MachineLoader::deviceIsUntested, &importui, &ImportUI::onDeviceIsUntested); connect(&loader, &MachineLoader::deviceIsUnsupported, &importui, &ImportUI::onDeviceIsUnsupported); int res = loader.Open(files); progress.setMessage(QObject::tr("Finishing up...")); QCoreApplication::processEvents(); ctx->Commit(); loader.SetContext(nullptr); delete ctx; if (size > 1) { disconnect(&loader, SIGNAL(setProgressValue(int)), &progress, SLOT(setProgressValue(int))); disconnect(&progress, SIGNAL(abortClicked()), &loader, SLOT(abortImport())); progress.close(); QCoreApplication::processEvents(); } if (res == 0) { Notify(tr("There was a problem opening %1 Data File: %2").arg(name, files[0])); return; } else if (res < size){ Notify(tr("%1 Data Import of %2 file(s) complete").arg(name).arg(res) + "\n\n" + tr("There was a problem opening %1 Data File: %2").arg(name, files[res]), tr("%1 Import Partial Success").arg(name)); } else { Notify(tr("%1 Data Import complete").arg(name)); } PopulatePurgeMenu(); if (overview) overview->ReloadGraphs(); if (welcome) welcome->refreshPage(); daily->LoadDate(daily->getDate()); } } void MainWindow::on_actionImport_Somnopose_Data_triggered() { SomnoposeLoader somno; importNonCPAP(somno); } void MainWindow::on_actionImport_Viatom_Data_triggered() { ViatomLoader viatom; importNonCPAP(viatom); } void MainWindow::GenerateStatistics() { QDate first = p_profile->FirstDay(); QDate last = p_profile->LastDay(); ui->statStartDate->setMinimumDate(first); ui->statStartDate->setMaximumDate(last); ui->statEndDate->setMinimumDate(first); ui->statEndDate->setMaximumDate(last); Statistics stats; QString htmlStats = stats.GenerateHTML(); QString htmlRecords = stats.UpdateRecordsBox(); bool brange = (p_profile->general->statReportMode() == 2); ui->statEndDate->setVisible(brange); ui->statStartDate->setVisible(brange); updateFavourites(); setStatsHTML(htmlStats); setRecBoxHTML(htmlRecords); } void MainWindow::JumpDaily() { qDebug() << "Set current Widget to Daily"; // sleep(3); ui->tabWidget->setCurrentWidget(daily); } void MainWindow::JumpOverview() { ui->tabWidget->setCurrentWidget(overview); } void MainWindow::JumpStatistics() { ui->tabWidget->setCurrentWidget(ui->statisticsTab); } void MainWindow::JumpImport() { on_importButton_clicked(); } void MainWindow::JumpOxiWizard() { on_oximetryButton_clicked(); } void MainWindow::on_statisticsButton_clicked() { if (p_profile) { ui->tabWidget->setCurrentWidget(ui->statisticsTab); } } void MainWindow::on_reportModeMonthly_clicked() { ui->statStartDate->setVisible(false); ui->statEndDate->setVisible(false); if (p_profile->general->statReportMode() != 1) { p_profile->general->setStatReportMode(1); GenerateStatistics(); } } void MainWindow::on_reportModeStandard_clicked() { ui->statStartDate->setVisible(false); ui->statEndDate->setVisible(false); if (p_profile->general->statReportMode() != 0) { p_profile->general->setStatReportMode(0); GenerateStatistics(); } } void MainWindow::on_reportModeRange_clicked() { ui->statStartDate->setVisible(true); ui->statEndDate->setVisible(true); if (p_profile->general->statReportMode() != 2) { p_profile->general->setStatReportMode(2); GenerateStatistics(); } } void MainWindow::on_statEndDate_dateChanged(const QDate &date) { p_profile->general->setStatReportRangeEnd(date); GenerateStatistics(); } void MainWindow::on_statStartDate_dateChanged(const QDate &date) { p_profile->general->setStatReportRangeStart(date); GenerateStatistics(); } void MainWindow::on_actionPurgeCurrentDaysOximetry_triggered() { if (!daily) return; QDate date = daily->getDate(); Day * day = p_profile->GetDay(date, MT_OXIMETER); if (day) { if (QMessageBox::question(this, STR_MessageBox_Warning, tr("Are you sure you want to delete oximetry data for %1"). arg(daily->getDate().toString(Qt::DefaultLocaleLongDate))+"

    "+ tr("Please be aware you can not undo this operation!"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) { return; } QList sessionlist=day->getSessions(MT_OXIMETER); QSet machines; for (auto & sess : sessionlist) { machines += sess->machine(); sess->Destroy(); delete sess; } // We have to update the summary cache for the affected machine(s), // otherwise OSCAR will still think this day has oximetry data at next launch. for (auto & mach : machines) { mach->SaveSummaryCache(); } if (daily) { daily->Unload(date); daily->clearLastDay(); // otherwise Daily will crash daily->ReloadGraphs(); } if (overview) overview->ReloadGraphs(); if (welcome) welcome->refreshPage(); } else { QMessageBox::information(this, STR_MessageBox_Information, tr("Select the day with valid oximetry data in daily view first."),QMessageBox::Ok); } } void MainWindow::on_importButton_clicked() { on_action_Import_Data_triggered(); } void MainWindow::on_actionLine_Cursor_toggled(bool b) { AppSetting->setLineCursorMode(b); if (ui->tabWidget->currentWidget() == daily) { daily->graphView()->timedRedraw(0); } else if (ui->tabWidget->currentWidget() == overview) { overview->graphView()->timedRedraw(0); } } void MainWindow::on_actionPie_Chart_toggled(bool visible) { AppSetting->setShowPieChart(visible); if (!setupRunning && daily) { daily->updateLeftSidebar(); // daily->ReloadGraphs(); } } void MainWindow::on_actionLeft_Daily_Sidebar_toggled(bool visible) { if (daily) daily->setSidebarVisible(visible); } void MainWindow::on_actionDaily_Calendar_toggled(bool visible) { if (daily) daily->setCalendarVisible(visible); } void MainWindow::on_actionShowPersonalData_toggled(bool visible) { AppSetting->setShowPersonalData(visible); if (!setupRunning) GenerateStatistics(); } #include "SleepLib/journal.h" void MainWindow::on_actionExport_Journal_triggered() { QString folder; folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); folder += QDir::separator() + tr("%1's Journal").arg(p_profile->user->userName()) + ".xml"; QString filename = QFileDialog::getSaveFileName(this, tr("Choose where to save journal"), folder, tr("XML Files (*.xml)")); BackupJournal(filename); } void MainWindow::on_actionShow_Performance_Counters_toggled(bool arg1) { AppSetting->setShowPerformance(arg1); } void MainWindow::on_actionExport_CSV_triggered() { ExportCSV ex(this); if (ex.exec() == ExportCSV::Accepted) { } } void MainWindow::on_actionExport_Review_triggered() { QMessageBox::information(nullptr, STR_MessageBox_Information, tr("Export review is not yet implemented")); } void MainWindow::on_mainsplitter_splitterMoved(int, int) { AppSetting->setRightPanelWidth(ui->mainsplitter->sizes()[1]); } void MainWindow::on_actionCreate_Card_zip_triggered() { QList datacards = selectCPAPDataCards(tr("Would you like to zip this card?"), true); for (auto & datacard : datacards) { QString cardPath = QDir(datacard.path).canonicalPath(); QString filename; QString prefix; // Loop until a valid folder is selected or the user cancels. Disallow the SD card itself! while (true) { // Note: macOS ignores this and points to OSCAR's most recently used directory for saving. QString folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); MachineInfo info = datacard.loader->PeekInfo(datacard.path); QString infostr; if (!info.modelnumber.isEmpty()) { infostr = datacard.loader->loaderName() + "-" + info.modelnumber + "-" +info.serial; } else { infostr = datacard.loader->loaderName(); } prefix = infostr; folder += "/" + infostr + ".zip"; filename = QFileDialog::getSaveFileName(this, tr("Choose where to save zip"), folder, tr("ZIP files (*.zip)")); if (filename.isEmpty()) { return; // aborted } // Try again if the selected filename is within the SD card itself. QString selectedPath = QFileInfo(filename).dir().canonicalPath(); if (selectedPath.startsWith(cardPath)) { if (QMessageBox::warning(nullptr, STR_MessageBox_Error, QObject::tr("Please select a location for your zip other than the data card itself!"), QMessageBox::Ok)) { continue; } } break; } if (!filename.toLower().endsWith(".zip")) { filename += ".zip"; } qDebug() << "Create zip of SD card:" << cardPath; ZipFile z; bool ok = z.Open(filename); if (ok) { ProgressDialog * prog = new ProgressDialog(this); // Very full cards can sometimes take nearly a minute to scan, // so display the progress dialog immediately. prog->setMessage(tr("Calculating size...")); prog->setWindowModality(Qt::ApplicationModal); prog->open(); // Build the list of files. FileQueue files; bool ok = files.AddDirectory(cardPath, prefix); // If there were any unexpected errors scanning the media, add the debug log to the zip. if (!ok) { qWarning() << "Unexpected errors when scanning SD card, adding debug log to zip."; QString debugLog = logger->logFileName(); files.AddFile(debugLog, prefix + "-debug.txt"); } prog->setMessage(tr("Creating zip...")); // Create the zip. ok = z.AddFiles(files, prog); z.Close(); } else { qWarning() << "Unable to open" << filename; } if (!ok) { QMessageBox::warning(nullptr, STR_MessageBox_Error, QObject::tr("Unable to create zip!"), QMessageBox::Ok); } } } void MainWindow::on_actionCreate_Log_zip_triggered() { QString folder; // Note: macOS ignores this and points to OSCAR's most recently used directory for saving. folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); folder += "/OSCAR-logs.zip"; QString filename = QFileDialog::getSaveFileName(this, tr("Choose where to save zip"), folder, tr("ZIP files (*.zip)")); if (filename.isEmpty()) { return; // aborted } if (!filename.toLower().endsWith(".zip")) { filename += ".zip"; } qDebug() << "Create zip of OSCAR diagnostic logs:" << filename; ZipFile z; bool ok = z.Open(filename); if (ok) { ProgressDialog * prog = new ProgressDialog(this); prog->setMessage(tr("Creating zip...")); // Build the list of files. FileQueue files; files.AddDirectory(GetLogDir(), "logs"); // Defer the current debug log to the end. QString debugLog = logger->logFileName(); QString debugLogZipName; int exists = files.Remove(debugLog, &debugLogZipName); if (exists) { files.AddFile(debugLog, debugLogZipName); } // Create the zip. ok = z.AddFiles(files, prog); z.Close(); } else { qWarning() << "Unable to open" << filename; } if (!ok) { QMessageBox::warning(nullptr, STR_MessageBox_Error, QObject::tr("Unable to create zip!"), QMessageBox::Ok); } } void MainWindow::on_actionCreate_OSCAR_Data_zip_triggered() { QString folder; // Note: macOS ignores this and points to OSCAR's most recently used directory for saving. folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); folder += "/" + STR_AppData + ".zip"; QString filename = QFileDialog::getSaveFileName(this, tr("Choose where to save zip"), folder, tr("ZIP files (*.zip)")); if (filename.isEmpty()) { return; // aborted } if (!filename.toLower().endsWith(".zip")) { filename += ".zip"; } qDebug() << "Create zip of OSCAR data folder:" << filename; QDir oscarData(GetAppData()); ZipFile z; bool ok = z.Open(filename); if (ok) { ProgressDialog * prog = new ProgressDialog(this); prog->setMessage(tr("Calculating size...")); prog->setWindowModality(Qt::ApplicationModal); prog->open(); // Build the list of files. FileQueue files; files.AddDirectory(oscarData.canonicalPath(), oscarData.dirName()); // Defer the current debug log to the end. QString debugLog = logger->logFileName(); QString debugLogZipName; int exists = files.Remove(debugLog, &debugLogZipName); if (exists) { files.AddFile(debugLog, debugLogZipName); } prog->setMessage(tr("Creating zip...")); // Create the zip. ok = z.AddFiles(files, prog); z.Close(); } else { qWarning() << "Unable to open" << filename; } if (!ok) { QMessageBox::warning(nullptr, STR_MessageBox_Error, QObject::tr("Unable to create zip!"), QMessageBox::Ok); } } #include "translation.h" void MainWindow::on_actionReport_a_Bug_triggered() { // QSettings settings; // QString language = settings.value(LangSetting).toString(); // // QDesktopServices::openUrl(QUrl(QString("https://sleepyhead.jedimark.net/report_bugs.php?lang=%1&version=%2&platform=%3").arg(language).arg(getVersion()).arg(PlatformString))); QMessageBox::information(nullptr, STR_MessageBox_Error, tr("Reporting issues is not yet implemented")); } void MainWindow::on_actionSystem_Information_triggered() { QString text = ""; // tr("OSCAR version:") + "
    "; QStringList info = getBuildInfo(); for (int i = 0; i < info.size(); ++i) text += info.at(i) + "
    "; QMessageBox::information(nullptr, tr("OSCAR Information"), text); } void MainWindow::on_profilesButton_clicked() { ui->tabWidget->setCurrentWidget(profileSelector); } void MainWindow::on_bookmarkView_anchorClicked(const QUrl &arg1) { on_recordsBox_anchorClicked(arg1); } void MainWindow::on_recordsBox_anchorClicked(const QUrl &linkurl) { QString link = linkurl.toString().section("=", 0, 0).toLower(); QString data = linkurl.toString().section("=", 1).toLower(); qDebug() << linkurl.toString() << link << data; if (link == "daily") { QDate date = QDate::fromString(data, Qt::ISODate); ui->tabWidget->setCurrentWidget(daily); QApplication::processEvents(); daily->LoadDate(date); } else if (link == "overview") { QString date1 = data.section(",", 0, 0); QString date2 = data.section(",", 1); QDate d1 = QDate::fromString(date1, Qt::ISODate); QDate d2 = QDate::fromString(date2, Qt::ISODate); overview->setRange(d1, d2); ui->tabWidget->setCurrentWidget(overview); } else if (link == "import") { // Don't run the importer directly from here because it destroys the object that called this function.. // Schedule it instead if (data == "cpap") QTimer::singleShot(0, mainwin, SLOT(on_importButton_clicked())); if (data == "oximeter") QTimer::singleShot(0, mainwin, SLOT(on_oximetryButton_clicked())); } else if (link == "statistics") { ui->tabWidget->setCurrentWidget(ui->statisticsTab); } } void MainWindow::on_statisticsView_anchorClicked(const QUrl &url) { on_recordsBox_anchorClicked(url); } OSCAR-code-v1.3.1/oscar/mainwindow.h000066400000000000000000000320101417327530600171210ustar00rootroot00000000000000/* OSCAR MainWindow Headers * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #ifndef BROKEN_OPENGL_BUILD #include #endif #include #include #include "daily.h" #include "overview.h" #include "welcome.h" #ifndef helpless #include "help.h" #endif #include "profileselector.h" #include "preferencesdialog.h" extern Profile *profile; QString getCPAPPixmap(QString mach_class); namespace Ui { class MainWindow; } /*! \mainpage OSCAR \section intro_sec Introduction Open Source CPAP Analysis Reporter (OSCAR) is a program derived from the SleepyHead program written by Mark Watkins. SleepyHead was a Cross-Platform Open-Source software for reviewing data from %CPAP machines, which are used in the treatment of Sleep Disorders. SleepyHead was created by Mark Watkins (JediMark), an Australian software developer. This document is an attempt to provide a little technical insight into OSCAR's program internals. \section project_info Further Information OSCAR is hosted on Gitlab with full source code available there. Help for users can be found in the OSCAR Help Wiki. Data structures are described in a OSCAR Data Information Wiki. The SleepyHead project was hosted on sourceforge, and it's original project page can be reached at http://sourceforge.net/projects/sleepyhead. There was also a SleepyHead Wiki containing further information. \section structure Program Structure OSCAR is written in C++ using Qt Toolkit library, and comprises of 3 main components \list \li The SleepLib Database, a specialized database for working with multiple sources of Sleep machine data. \li A custom designed, high performance and interactive OpenGL Graphing Library. \li and the main Application user interface. \endlist This document is still a work in progress, right now all the classes and sections are jumbled together. */ // * \section install_sec Installation extern QStatusBar *qstatusbar; //QString getCPAPPixmap(QString mach_class); class Daily; class Report; class Overview; /*! \class MainWindow \author Mark Watkins \brief The Main Application window for OSCAR */ class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); //! \brief Setup the rest of the GUI stuff void SetupGUI(); //! \brief Update the list of Favourites (Bookmarks) in the right sidebar. void updateFavourites(); //! \brief Update statistics report void GenerateStatistics(); //! \brief Create a new menu object in the main menubar. QMenu *CreateMenu(QString title); //! \brief Start the automatic update checker process void CheckForUpdates(bool showWhenCurrent); void EnableTabs(bool b); void CloseProfile(); bool OpenProfile(QString name, bool skippassword = false); /*! \fn Notify(QString s, QString title="OSCAR (version)", int ms=5000); \brief Pops up a message box near the system tray \param QString string \param title \param int ms Title is shown in bold string is the main message content to show ms = millisecond delay of how long to show popup Mac needs Growl notification system for this to work */ void Notify(QString s, QString title = "", int ms = 5000); // /*! \fn gGraphView *snapshotGraph() // \brief Returns the current snapshotGraph object used by the report printing system */ // gGraphView *snapshotGraph() { return SnapshotGraph; } //! \brief Returns the Daily Tab object Daily *getDaily() { return daily; } //! \brief Returns the Overview Tab object Overview *getOverview() { return overview; } void updateOverview(); /*! \fn void RestartApplication(bool force_login=false); \brief Closes down OSCAR and restarts it \param bool force_login If force_login is set, it will return to the login menu even if it's set to skip */ void RestartApplication(bool force_login = false, QString cmdline = QString()); void JumpDaily(); void JumpOverview(); void JumpStatistics(); void JumpImport(); void JumpOxiWizard(); void sendStatsUrl(QString msg) { on_recordsBox_anchorClicked(QUrl(msg)); } //! \brief Sets up recalculation of all event summaries and flags void reprocessEvents(bool restart = false); void recompressEvents(); //! \brief Internal function to set Records Box html from statistics module void setRecBoxHTML(QString html); //! \brief Internal function to set Statistics page html from statistics module void setStatsHTML(QString html); int importCPAP(ImportPath import, const QString &message); void finishCPAPImport(); void startImportDialog() { on_action_Import_Data_triggered(); } void log(QString text); bool importScanCancelled; void firstRunMessage(); public slots: //! \brief Recalculate all event summaries and flags void doReprocessEvents(); void doRecompressEvents(); protected: void closeEvent(QCloseEvent *) override; void keyPressEvent(QKeyEvent *event) override; private slots: /*! \fn void on_action_Import_Data_triggered(); \brief Provide the file dialog for selecting import location, and start the import process This is called when the Import button is clicked */ void on_action_Import_Data_triggered(); //! \brief Toggle Fullscreen (currently F11) void on_action_Fullscreen_triggered(); //! \brief Selects the Daily page and redraws the graphs void on_dailyButton_clicked(); //! \brief Selects the Overview page and redraws the graphs void on_overviewButton_clicked(); //! \brief Display About Dialog void on_action_About_triggered(); #ifdef Q_OS_WIN //! \brief Called on Windows to see whether the current OpenGL driver will cause the application to crash void TestWindowsOpenGL(); #endif //! \brief Called after a timeout to initiate loading of all summary data for this profile void Startup(); //! \brief Toggle the Debug pane on and off void on_actionDebug_toggled(bool arg1); //! \brief passes the ResetGraphLayout menu click to the Daily & Overview views void on_action_Reset_Graph_Layout_triggered(); //! \brief passes the ResetGraphOrder menu click to the Daily & Overview views //void on_action_Reset_Graph_Order_triggered(); //! \brief passes the ResetGraphOrder menu click to the Daily & Overview views void on_action_Standard_Graph_Order_triggered(); //! \brief passes the ResetGraphOrder menu click to the Daily & Overview views void on_action_Advanced_Graph_Order_triggered(); //! \brief Opens the Preferences Dialog, and saving changes if OK is pressed void on_action_Preferences_triggered(); //! \brief Opens and/or shows the Oximetry page void on_oximetryButton_clicked(); //! \brief Creates the CheckUpdates object that actually does the real check for updates void on_action_Check_for_Updates_triggered(); //! \brief Attempts to do a screenshot of the application window void on_action_Screenshot_triggered(); //! \brief This is the actual screenshot code.. It's delayed with a QTimer to give the menu time to close. void DelayedScreenshot(); //! \brief a slot that calls the real Oximetry tab selector void on_actionView_Oximetry_triggered(); //! \brief Passes the Daily, Overview & Oximetry object to Print Report, based on current tab void on_actionPrint_Report_triggered(); //! \brief Opens the Profile Editor void on_action_Edit_Profile_triggered(); //! \brief Selects the next view tab void on_action_CycleTabs_triggered(); //! \brief Opens the User Guide at the wiki in the welcome browser. void on_actionOnline_Users_Guide_triggered(); //! \brief Opens the Frequently Asked Questions at the wiki in the welcome browser. void on_action_Frequently_Asked_Questions_triggered(); /*! \fn void on_action_Rebuild_Oximetry_Index_triggered(); \brief This function scans over all oximetry data and reindexes and tries to fix any inconsistencies. */ void on_action_Rebuild_Oximetry_Index_triggered(); //! \brief Destroy the CPAP data for the currently selected day, so it can be freshly imported again void on_actionPurge_Current_Day_triggered(); void on_actionPurgeCurrentDayOximetry_triggered(); void on_actionPurgeCurrentDaySleepStage_triggered(); void on_actionPurgeCurrentDayPosition_triggered(); void on_actionPurgeCurrentDayAllExceptNotes_triggered(); void on_actionPurgeCurrentDayAll_triggered(); void on_action_Sidebar_Toggle_toggled(bool arg1); void on_helpButton_clicked(); void on_actionView_Statistics_triggered(); //void on_favouritesList_itemSelectionChanged(); //void on_favouritesList_itemClicked(QListWidgetItem *item); void on_tabWidget_currentChanged(int index); void on_filterBookmarks_editingFinished(); void on_filterBookmarksButton_clicked(); void on_actionImport_ZEO_Data_triggered(); void on_actionImport_Dreem_Data_triggered(); void on_actionImport_RemStar_MSeries_Data_triggered(); void on_actionSleep_Disorder_Terms_Glossary_triggered(); //void on_actionHelp_Support_OSCAR_Development_triggered(); void aboutBoxLinkClicked(const QUrl &url); void on_actionChange_Language_triggered(); void on_actionChange_Data_Folder_triggered(); void on_actionImport_Somnopose_Data_triggered(); void on_actionImport_Viatom_Data_triggered(); //! \brief Populates the statistics with information. void on_statisticsButton_clicked(); void on_reportModeMonthly_clicked(); void on_reportModeStandard_clicked(); void on_actionRebuildCPAP(QAction *action); void on_actionPurgeMachine(QAction *action); void on_reportModeRange_clicked(); void on_statEndDate_dateChanged(const QDate &date); void on_statStartDate_dateChanged(const QDate &date); void on_actionPurgeCurrentDaysOximetry_triggered(); void logMessage(QString msg); void on_importButton_clicked(); void on_actionLine_Cursor_toggled(bool arg1); void on_actionLeft_Daily_Sidebar_toggled(bool arg1); void on_actionDaily_Calendar_toggled(bool arg1); void on_actionPie_Chart_toggled(bool arg1); void on_actionExport_Journal_triggered(); void on_actionShow_Performance_Counters_toggled(bool arg1); void on_actionExport_CSV_triggered(); void on_actionExport_Review_triggered(); void on_mainsplitter_splitterMoved(int pos, int index); void on_actionCreate_Card_zip_triggered(); void on_actionCreate_Log_zip_triggered(); void on_actionCreate_OSCAR_Data_zip_triggered(); void on_actionReport_a_Bug_triggered(); void on_actionSystem_Information_triggered(); void on_profilesButton_clicked(); void reloadProfile(); void on_bookmarkView_anchorClicked(const QUrl &arg1); void on_recordsBox_anchorClicked(const QUrl &linkurl); void on_statisticsView_anchorClicked(const QUrl &url); void on_actionShowPersonalData_toggled(bool visible); private: QString getMainWindowTitle(); void importCPAPBackups(); QList detectCPAPCards(); QList selectCPAPDataCards(const QString & prompt, bool alwaysPrompt = false); void importCPAPDataCards(const QList & datacards); void addMachineToMenu(Machine* mach, QMenu* menu); void purgeDay(MachineType type); void importNonCPAP(MachineLoader &loader); // QString getWelcomeHTML(); void FreeSessions(); Ui::MainWindow *ui; Daily *daily; Overview *overview; ProfileSelector *profileSelector; Welcome * welcome; #ifndef helpless Help * help; #endif bool first_load; PreferencesDialog *prefdialog; QTime logtime; QSystemTrayIcon *systray; QMenu *systraymenu; // gGraphView *SnapshotGraph; QString bookmarkFilter; bool m_restartRequired; volatile bool m_inRecalculation; void PopulatePurgeMenu(); //! \brief Destroy ALL the CPAP data for the selected machine void purgeMachine(Machine *); int warnidx; QStringList warnmsg; QTimer wtimer; }; class ImportDialogScan:public QDialog { Q_OBJECT public: ImportDialogScan(QWidget * parent) :QDialog(parent, Qt::SplashScreen) { } virtual ~ImportDialogScan() { } public slots: virtual void cancelbutton(); }; #endif // MAINWINDOW_H OSCAR-code-v1.3.1/oscar/mainwindow.ui000066400000000000000000002774641417327530600173370ustar00rootroot00000000000000 MainWindow 0 0 1023 804 0 1 16777215 16777215 OSCAR :/icons/logo-sm.png:/icons/logo-sm.png 64 64 0 0 4 0 0 0 0 Qt::Horizontal QFrame::StyledPanel Qt::Vertical 0 0 16777215 16777215 0 0 0 255 255 255 255 255 255 255 255 255 127 127 127 170 170 170 0 0 0 255 255 255 0 0 0 255 255 255 255 255 255 0 0 0 255 255 255 255 255 220 0 0 0 0 0 0 255 255 255 255 255 255 255 255 255 127 127 127 170 170 170 0 0 0 255 255 255 0 0 0 255 255 255 255 255 255 0 0 0 255 255 255 255 255 220 0 0 0 127 127 127 255 255 255 255 255 255 255 255 255 127 127 127 170 170 170 127 127 127 255 255 255 127 127 127 255 255 255 255 255 255 0 0 0 255 255 255 255 255 220 0 0 0 true true QTabWidget::North QTabWidget::Rounded 0 false false false &Statistics 0 0 0 0 0 0 0 QFrame::StyledPanel QFrame::Raised 8 8 4 4 4 Report Mode Standard true Monthly Date Range true true Qt::Horizontal 40 20 true 163 190 255 163 190 255 163 190 255 163 190 255 163 190 255 163 190 255 163 190 255 163 190 255 163 190 255 12 QToolBox { icon-size: 24px; border-radius: 15px; background: rgb(163, 190, 255) } QToolBox::tab { color: #444444; background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #8181b1, stop: 1.0 #8393c3); } QToolBox::tab:pressed { font: italic; color: #ffffff; background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #8181b1, stop: 1.0 #8393c3); } QToolBox::tab:selected { font: italic; color: #ffffff; background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #8181b1, stop: 1.0 #8393c3); } QFrame::Box QFrame::Sunken 1 1 0 0 0 0 153 697 163 190 255 163 190 255 163 190 255 163 190 255 163 190 255 163 190 255 163 190 255 163 190 255 163 190 255 true background: rgb(163, 190, 255) :/icons/logo-sm.png:/icons/logo-sm.png Navigation 0 0 QToolButton { background: transparent; border: 2px solid transparent; border-radius: 30px; } QToolButton:hover { border: 2px solid #56789a; border-radius: 30px; } QToolButton:pressed { background-color: #8080ff; border: 2px solid #56789a; border-radius: 30px; } Profiles :/icons/go-home.png:/icons/go-home.png 64 64 Qt::ToolButtonTextUnderIcon true 0 0 QToolButton { background: transparent; border: 2px solid transparent; border-radius: 30px; } QToolButton:hover { border: 2px solid #56789a; border-radius: 30px; } QToolButton:pressed { background-color: #8080ff; border: 2px solid #56789a; border-radius: 30px; } Statistics :/icons/eye.png:/icons/eye.png 64 64 Qt::ToolButtonTextUnderIcon true 0 0 0 0 QToolButton { background: transparent; border: 2px solid transparent; border-radius: 30px; } QToolButton:hover { border: 2px solid #56789a; border-radius: 30px; } QToolButton:pressed { background-color: #8080ff; border: 2px solid #56789a; border-radius: 30px; } Daily :/icons/edit-find.png:/icons/edit-find.png 64 64 Qt::ToolButtonTextUnderIcon true 0 0 QToolButton { background: transparent; border: 2px solid transparent; border-radius: 30px; } QToolButton:hover { border: 2px solid #56789a; border-radius: 30px; } QToolButton:pressed { background-color: #8080ff; border: 2px solid #56789a; border-radius: 30px; } Overview :/icons/overview.png:/icons/overview.png 64 64 Qt::ToolButtonTextUnderIcon true 0 0 QToolButton { background: transparent; border: 2px solid transparent; border-radius: 30px; } QToolButton:hover { border: 2px solid #56789a; border-radius: 30px; } QToolButton:pressed { background-color: #8080ff; border: 2px solid #56789a; border-radius: 30px; } Oximetry :/icons/oximeter.png:/icons/oximeter.png 64 64 Qt::ToolButtonTextUnderIcon true 0 0 0 0 QToolButton { background: transparent; border: 2px solid transparent; border-radius: 30px; } QToolButton:hover { border: 2px solid #56789a; border-radius: 30px; } QToolButton:pressed { background-color: #8080ff; border: 2px solid #56789a; border-radius: 30px; } Import :/icons/sdcard.png:/icons/sdcard.png 64 64 false Qt::ToolButtonTextUnderIcon true Qt::NoArrow 0 0 QToolButton { background: transparent; border: 2px solid transparent; border-radius: 30px; } QToolButton:hover { border: 2px solid #56789a; border-radius: 30px; } QToolButton:pressed { background-color: #8080ff; border: 2px solid #56789a; border-radius: 30px; } Help :/icons/help.png:/icons/help.png 64 64 Qt::ToolButtonTextUnderIcon true Qt::Vertical QSizePolicy::Expanding 0 0 0 0 167 687 170 170 255 170 170 255 170 170 255 170 170 255 0 31 237 170 170 255 true :/icons/bookmark.png:/icons/bookmark.png Bookmarks 0 QLayout::SetNoConstraint 0 0 0 0 0 0 0 0 0 125 125 255 200 200 255 255 255 255 212 212 255 85 85 127 113 113 170 0 0 0 255 255 255 0 0 0 125 125 255 200 200 255 125 125 255 200 200 255 0 0 0 212 212 255 255 255 220 0 0 0 0 0 0 125 125 255 200 200 255 255 255 255 212 212 255 85 85 127 113 113 170 0 0 0 255 255 255 0 0 0 125 125 255 200 200 255 125 125 255 200 200 255 0 0 0 212 212 255 255 255 220 0 0 0 85 85 127 125 125 255 200 200 255 255 255 255 212 212 255 85 85 127 113 113 170 85 85 127 255 255 255 85 85 127 125 125 255 200 200 255 125 125 255 200 200 255 0 0 0 170 170 255 255 255 220 0 0 0 background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 rgba(125, 125, 255, 255), stop:1 rgba(200, 200, 255, 255)) QFrame::Box QFrame::Sunken 0 0 0 0 0 0 0 255 255 255 190 209 255 255 255 255 255 255 255 190 209 255 190 209 255 170 170 255 255 255 255 190 209 255 255 255 255 255 255 255 190 209 255 190 209 255 170 170 255 255 255 255 190 209 255 255 255 255 255 255 255 190 209 255 190 209 255 170 170 255 15 QLineEdit { border: 1px solid #6060e0; border-radius: 20px; color: white; background-color: rgb(190, 209, 255); } QLineEdit:selected { background-color: rgb(190, 209, 255); } 0 0 163 190 255 163 190 255 163 190 255 170 170 255 163 190 255 163 190 255 163 190 255 170 170 255 163 190 255 163 190 255 163 190 255 170 170 255 QToolButton { background: rgb(163, 190, 255); } QToolButton:hover { background: rgb(163, 190, 255); border: 2px solid #272727; border-radius: 10px; } QToolButton:pressed { background: #6060c0; border: 2px solid #272727; border-radius: 10px; } :/icons/refresh.png:/icons/refresh.png true background: rgb(163, 190, 255) <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8.25pt;"><br /></p></body></html> false 0 0 167 687 true :/icons/trophy.png:/icons/trophy.png Records 0 0 0 0 0 background: rgb(163, 190, 255) <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8.25pt;"><br /></p></body></html> false 0 0 1023 21 0 0 &File Exp&ort Data &View &Reset Graphs &Help Troubleshooting &Data &Advanced Purge Oximetry Data Purge ALL Machine Data Purge &Current Selected Day Rebuild CPAP Data &Import CPAP Card Data Shift+F2 &Preferences &Profiles Exit View &Daily Show Daily view F5 View &Overview Show Overview view F6 View &Welcome F4 - - - Ctrl+Tab true Use &AntiAliasing &About OSCAR true &Maximize Toggle Maximize window F11 true Show Debug Pane Reset Graph &Heights Reset sizes of graphs Take &Screenshot F12 O&ximetry Wizard F7 Print &Report &Edit Profile Online Users &Guide &Frequently Asked Questions &Automatic Oximetry Cleanup Change &User true true Right &Sidebar Show Right Sidebar F10 View S&tatistics View Statistics Show Statistics view F4 Import &ZEO Data Import &Dreem Data Import RemStar &MSeries Data Sleep Disorder Terms &Glossary Change &Language Change &Data Folder Import &Somnopose Data Import &Viatom/Wellue Data Current Days true Show &Line Cursor Ctrl+L true true Daily Sidebar Show Daily Left Sidebar F8 true true Daily Calendar Show Daily Calendar F9 Backup &Journal true Show Performance Information Create zip of CPAP data card Create zip of OSCAR diagnostic logs Create zip of all OSCAR data CSV Export Wizard Export for Review Report an Issue System Information true false Show &Pie Chart Show Pie Chart on Daily page F3 Standard Standard graph order, good for CPAP, APAP, Bi-Level Advanced Advanced graph order, good for ASV, AVAPS true true Show Personal Data true Check For &Updates true Purge Current Selected Day &CPAP &Oximetry &Sleep Stage &Position &All except Notes All including &Notes MyTextBrowser QTextBrowser
    mytextbrowser.h
    action_Exit triggered() MainWindow close() -1 -1 399 299 actionView_Daily triggered() dailyButton click() -1 -1 850 219 actionView_Overview triggered() overviewButton click() -1 -1 850 301 actionView_Statistics triggered() statisticsButton click() -1 -1 772 101
    OSCAR-code-v1.3.1/oscar/mytextbrowser.h000066400000000000000000000005251417327530600177110ustar00rootroot00000000000000#ifndef MYTEXTBROWSER_H #define MYTEXTBROWSER_H #include class MyTextBrowser:public QTextBrowser { Q_OBJECT public: MyTextBrowser(QWidget * parent):QTextBrowser(parent) {} virtual ~MyTextBrowser() {} virtual QVariant loadResource(int type, const QUrl &url) Q_DECL_OVERRIDE; }; #endif // MYTEXTBROWSER_H OSCAR-code-v1.3.1/oscar/newprofile.cpp000066400000000000000000000354761417327530600174750ustar00rootroot00000000000000/* Create New Profile Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include "SleepLib/profiles.h" #include "newprofile.h" #include "ui_newprofile.h" #include "mainwindow.h" #include "version.h" extern MainWindow *mainwin; NewProfile::NewProfile(QWidget *parent, const QString *user) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint), ui(new Ui::NewProfile) { ui->setupUi(this); if (user) ui->userNameEdit->setText(*user); // ui->userNameEdit->setText(getUserName()); QLocale locale = QLocale::system(); QString shortformat = locale.dateFormat(QLocale::ShortFormat); if (!shortformat.toLower().contains("yyyy")) { shortformat.replace("yy", "yyyy"); } ui->dobEdit->setDisplayFormat(shortformat); ui->dateDiagnosedEdit->setDisplayFormat(shortformat); m_firstPage = 0; ui->backButton->setEnabled(false); ui->nextButton->setEnabled(false); ui->stackedWidget->setCurrentIndex(0); on_cpapModeCombo_activated(0); m_passwordHashed = false; ui->heightEdit2->setVisible(false); ui->heightEdit->setDecimals(0); ui->heightEdit->setSuffix(STR_UNIT_CM); { // process countries list QFile f(":/docs/countries.txt"); f.open(QFile::ReadOnly); QTextStream cnt(&f); QString a; ui->countryCombo->clear(); ui->countryCombo->addItem(tr("Select Country")); do { a = cnt.readLine(); if (a.isEmpty()) { break; } ui->countryCombo->addItem(a); } while (1); f.close(); } { // timezone list QFile f(":/docs/tz.txt"); f.open(QFile::ReadOnly); QTextStream cnt(&f); QString a; ui->timezoneCombo->clear(); //ui->countryCombo->addItem("Select TimeZone"); do { a = cnt.readLine(); if (a.isEmpty()) { break; } QStringList l; l = a.split("="); ui->timezoneCombo->addItem(l[1], l[0]); } while (1); f.close(); } ui->versionLabel->setText(""); ui->textBrowser->setHtml(getIntroHTML()); } NewProfile::~NewProfile() { delete ui; } QString NewProfile::getIntroHTML() { return "" "" "

    " + tr("Welcome to the Open Source CPAP Analysis Reporter") + "

    " "

    " + tr("This software is being designed to assist you in reviewing the data produced by your CPAP machines and related equipment.") + "

    " "

    " + tr("OSCAR has been released freely under the GNU Public License v3, and comes with no warranty, and without ANY claims to fitness for any purpose.") + "

    " "

    " + tr("PLEASE READ CAREFULLY") + "

    " "

    " + tr("OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor.") + "

    " "

    " + tr("Accuracy of any data displayed is not and can not be guaranteed.") + "

    " "

    " + tr("Any reports generated are for PERSONAL USE ONLY, and NOT IN ANY WAY fit for compliance or medical diagnostic purposes.") + "

    " "

    " + tr("The authors will not be held liable for anything related to the use or misuse of this software.") + "

    " "
    " "

    " + tr("Use of this software is entirely at your own risk.") + "

    " "

    " + tr("OSCAR is copyright ©2011-2018 Mark Watkins and portions ©2019-2022 The OSCAR Team") + "

    " "
    " "" ""; } void NewProfile::on_nextButton_clicked() { const QString xmlext = ".xml"; int index = ui->stackedWidget->currentIndex(); switch (index) { case 0: if (!ui->agreeCheckbox->isChecked()) { return; } // Reload Preferences object break; case 1: if (ui->userNameEdit->text().trimmed().isEmpty()) { QMessageBox::information(this, STR_MessageBox_Error, tr("Please provide a username for this profile"), QMessageBox::Ok); return; } if (ui->genderCombo->currentIndex() == 0) { //QMessageBox::information(this,tr("Notice"),tr("You did not specify Gender."),QMessageBox::Ok); } if (ui->passwordGroupBox->isChecked()) { if (ui->passwordEdit1->text() != ui->passwordEdit2->text()) { QMessageBox::information(this, STR_MessageBox_Error, tr("Passwords don't match"), QMessageBox::Ok); return; } if (ui->passwordEdit1->text().isEmpty()) { ui->passwordGroupBox->setChecked(false); } } break; case 2: break; case 3: break; default: break; } int max_pages = ui->stackedWidget->count() - 1; if (index < max_pages) { index++; ui->stackedWidget->setCurrentIndex(index); } else { // Finish button clicked. QString username = ui->userNameEdit->text().trimmed(); if (QMessageBox::question(this, tr("Profile Changes"), tr("Accept and save this information?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { Profile *profile = Profiles::Get(username); if (!profile) { // No profile, create one. profile = Profiles::Create(username); } Profile &prof = *profile; profile->user->setFirstName(ui->firstNameEdit->text()); profile->user->setLastName(ui->lastNameEdit->text()); profile->user->setDOB(ui->dobEdit->date()); profile->user->setEmail(ui->emailEdit->text()); profile->user->setPhone(ui->phoneEdit->text()); profile->user->setAddress(ui->addressEdit->toPlainText()); if (ui->passwordGroupBox->isChecked()) { if (!m_passwordHashed) { profile->user->setPassword(ui->passwordEdit1->text().toUtf8()); } } else { prof.Erase(STR_UI_Password); } profile->user->setGender((Gender)ui->genderCombo->currentIndex()); profile->cpap->setDateDiagnosed(ui->dateDiagnosedEdit->date()); profile->cpap->setUntreatedAHI(ui->untreatedAHIEdit->value()); profile->cpap->setMode((CPAPMode)ui->cpapModeCombo->currentIndex()); profile->cpap->setMinPressure(ui->minPressureEdit->value()); profile->cpap->setMaxPressure(ui->maxPressureEdit->value()); profile->cpap->setNotes(ui->cpapNotes->toPlainText()); profile->doctor->setName(ui->doctorNameEdit->text()); profile->doctor->setPracticeName(ui->doctorPracticeEdit->text()); profile->doctor->setAddress(ui->doctorAddressEdit->toPlainText()); profile->doctor->setPhone(ui->doctorPhoneEdit->text()); profile->doctor->setEmail(ui->doctorEmailEdit->text()); profile->doctor->setPatientID(ui->doctorPatientIDEdit->text()); profile->user->setTimeZone(ui->timezoneCombo->itemData( ui->timezoneCombo->currentIndex()).toString()); profile->user->setCountry(ui->countryCombo->currentText()); profile->user->setDaylightSaving(ui->DSTcheckbox->isChecked()); UnitSystem us; if (ui->heightCombo->currentIndex() == 0) { us = US_Metric; } else if (ui->heightCombo->currentIndex() == 1) { us = US_English; } else { us = US_Metric; } if (profile->general->unitSystem() != us) { profile->general->setUnitSystem(us); if (mainwin && mainwin->getDaily()) { mainwin->getDaily()->UnitsChanged(); } } double v = 0; if (us == US_English) { // convert to metric v = (ui->heightEdit->value() * 30.48); v += ui->heightEdit2->value() * 2.54; } else { v = ui->heightEdit->value(); } profile->user->setHeight(v); //profile->user->setUserName(username); AppSetting->setProfileName(username); profile->Save(); if (mainwin) mainwin->GenerateStatistics(); this->accept(); } } if (index >= max_pages) { ui->nextButton->setText(tr("&Finish")); } else { ui->nextButton->setText(tr("&Next")); } ui->backButton->setEnabled(true); } void NewProfile::on_backButton_clicked() { ui->nextButton->setText(tr("&Next")); if (ui->stackedWidget->currentIndex() > m_firstPage) { ui->stackedWidget->setCurrentIndex(ui->stackedWidget->currentIndex() - 1); } if (ui->stackedWidget->currentIndex() == m_firstPage) { ui->backButton->setEnabled(false); } else { ui->backButton->setEnabled(true); } } void NewProfile::on_cpapModeCombo_activated(int index) { if (index == 0) { ui->maxPressureEdit->setVisible(false); } else { ui->maxPressureEdit->setVisible(true); } } void NewProfile::on_agreeCheckbox_clicked(bool checked) { ui->nextButton->setEnabled(checked); } void NewProfile::skipWelcomeScreen() { ui->agreeCheckbox->setChecked(true); ui->stackedWidget->setCurrentIndex(m_firstPage = 1); ui->backButton->setEnabled(false); ui->nextButton->setEnabled(true); } void NewProfile::edit(const QString name) { skipWelcomeScreen(); Profile *profile = Profiles::Get(name); if (!profile) { profile = Profiles::Create(name); } ui->userNameEdit->setText(name); ui->userNameEdit->setReadOnly(true); ui->firstNameEdit->setText(profile->user->firstName()); ui->lastNameEdit->setText(profile->user->lastName()); if (profile->contains(STR_UI_Password) && !profile->p_preferences[STR_UI_Password].toString().isEmpty()) { // leave the password box blank.. QString a = "******"; ui->passwordEdit1->setText(a); ui->passwordEdit2->setText(a); ui->passwordGroupBox->setChecked(true); m_passwordHashed = true; } ui->dobEdit->setDate(profile->user->DOB()); if (profile->user->gender() == Male) { ui->genderCombo->setCurrentIndex(1); } else if (profile->user->gender() == Female) { ui->genderCombo->setCurrentIndex(2); } else { ui->genderCombo->setCurrentIndex(0); } ui->heightEdit->setValue(profile->user->height()); ui->addressEdit->setText(profile->user->address()); ui->emailEdit->setText(profile->user->email()); ui->phoneEdit->setText(profile->user->phone()); ui->dateDiagnosedEdit->setDate(profile->cpap->dateDiagnosed()); ui->cpapNotes->clear(); ui->cpapNotes->appendPlainText(profile->cpap->notes()); ui->minPressureEdit->setValue(profile->cpap->minPressure()); ui->maxPressureEdit->setValue(profile->cpap->maxPressure()); ui->untreatedAHIEdit->setValue(profile->cpap->untreatedAHI()); ui->cpapModeCombo->setCurrentIndex((int)profile->cpap->mode()); on_cpapModeCombo_activated(profile->cpap->mode()); ui->doctorNameEdit->setText(profile->doctor->name()); ui->doctorPracticeEdit->setText(profile->doctor->practiceName()); ui->doctorPhoneEdit->setText(profile->doctor->phone()); ui->doctorEmailEdit->setText(profile->doctor->email()); ui->doctorAddressEdit->setText(profile->doctor->address()); ui->doctorPatientIDEdit->setText(profile->doctor->patientID()); ui->DSTcheckbox->setChecked(profile->user->daylightSaving()); int i = ui->timezoneCombo->findData(profile->user->timeZone()); ui->timezoneCombo->setCurrentIndex(i); i = ui->countryCombo->findText(profile->user->country()); ui->countryCombo->setCurrentIndex(i); UnitSystem us = profile->general->unitSystem(); i = (int)us - 1; if (i < 0) { i = 0; } ui->heightCombo->setCurrentIndex(i); double v = profile->user->height(); if (us == US_English) { // evil non-metric int ti = v / 2.54; int feet = ti / 12; int inches = ti % 12; ui->heightEdit->setValue(feet); ui->heightEdit2->setValue(inches); ui->heightEdit2->setVisible(true); ui->heightEdit->setDecimals(0); ui->heightEdit2->setDecimals(0); ui->heightEdit->setSuffix(STR_UNIT_FOOT); // foot ui->heightEdit2->setSuffix(STR_UNIT_INCH); // inches } else { // good wholesome metric ui->heightEdit->setValue(v); ui->heightEdit2->setVisible(false); ui->heightEdit->setDecimals(0); ui->heightEdit->setSuffix(STR_UNIT_CM); } } void NewProfile::on_passwordEdit1_editingFinished() { m_passwordHashed = false; } void NewProfile::on_passwordEdit2_editingFinished() { m_passwordHashed = false; } void NewProfile::on_heightCombo_currentIndexChanged(int index) { if (index == 0) { //metric ui->heightEdit2->setVisible(false); ui->heightEdit->setDecimals(0); ui->heightEdit->setSuffix(STR_UNIT_CM); double v = ui->heightEdit->value() * 30.48; v += ui->heightEdit2->value() * 2.54; ui->heightEdit->setValue(v); } else { //evil ui->heightEdit->setDecimals(0); ui->heightEdit2->setDecimals(0); ui->heightEdit->setSuffix(STR_UNIT_FOOT); ui->heightEdit2->setVisible(true); ui->heightEdit2->setSuffix(STR_UNIT_INCH); int v = ui->heightEdit->value() / 2.54; int feet = v / 12; int inches = v % 12; ui->heightEdit->setValue(feet); ui->heightEdit2->setValue(inches); } } void NewProfile::on_textBrowser_anchorClicked(const QUrl &arg1) { QDialog *dlg = new QDialog(this); dlg->setMinimumWidth(600); dlg->setMinimumHeight(500); QVBoxLayout *layout = new QVBoxLayout(); dlg->setLayout(layout); QTextBrowser *browser = new QTextBrowser(this); dlg->layout()->addWidget(browser); QPushButton *button = new QPushButton(tr("&Close this window"), browser); QFile f(arg1.toString().replace("qrc:", ":")); f.open(QIODevice::ReadOnly); QTextStream ts(&f); QString text = ts.readAll(); connect(button, SIGNAL(clicked()), dlg, SLOT(close())); dlg->layout()->addWidget(button); browser->setPlainText(text); dlg->exec(); delete dlg; } OSCAR-code-v1.3.1/oscar/newprofile.h000066400000000000000000000031251417327530600171240ustar00rootroot00000000000000/* Create New Profile Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef NEWPROFILE_H #define NEWPROFILE_H #include #include namespace Ui { class NewProfile; } /*! \class NewProfile \author Mark Watkins \brief Profile creation/editing wizard */ class NewProfile : public QDialog { Q_OBJECT public: explicit NewProfile(QWidget *parent = 0, const QString *user = 0); ~NewProfile(); //! \brief When used in edit mode, this skips the first page void skipWelcomeScreen(); //! \brief Open profile named 'name' for editing, loading all it's content void edit(const QString name); private slots: //! \brief Validate each step and move to the next page, saving at the end if requested. void on_nextButton_clicked(); //! \brief Go back to the previous wizard page void on_backButton_clicked(); void on_cpapModeCombo_activated(int index); void on_agreeCheckbox_clicked(bool checked); void on_passwordEdit1_editingFinished(); void on_passwordEdit2_editingFinished(); void on_heightCombo_currentIndexChanged(int index); void on_textBrowser_anchorClicked(const QUrl &arg1); private: QString getIntroHTML(); Ui::NewProfile *ui; bool m_editMode; int m_firstPage; bool m_passwordHashed; }; #endif // NEWPROFILE_H OSCAR-code-v1.3.1/oscar/newprofile.ui000066400000000000000000001050501417327530600173120ustar00rootroot00000000000000 NewProfile 0 0 667 450 Edit User Profile :/icons/logo-sm.png:/icons/logo-sm.png 8 8 8 8 0 0 QTextEdit::AutoNone about:blank false false I agree to all the conditions above. Qt::Horizontal 40 20 6 8 8 8 8 User Information false 6 8 8 8 8 User Name Very weak password protection and not recommended if security is required. Password Protect Profile false true false 8 8 8 8 Password QLineEdit::PasswordEchoOnEdit ...twice... QLineEdit::PasswordEchoOnEdit Locale Settings 8 8 8 8 6 0 0 DST Zone 0 0 TimeZone Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Qt::Vertical 20 40 0 0 Country Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 6 8 8 8 8 Personal Information (for reports) 6 6 8 8 8 8 First Name Last Name It's totally ok to fib or skip this, but your rough age is needed to enhance accuracy of certain calculations. D.O.B. true <html><head/><body><p>Biological (birth) gender is sometimes needed to enhance the accuracy of a few calculations, feel free to leave this blank and skip any of them.</p></body></html> Gender Male Female 0 0 Height Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 2 350.000000000000000 0 1.000000000000000 0 0 Metric English Contact Information QFormLayout::AllNonFixedFieldsGrow 6 6 8 8 8 8 Address Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Email Phone Qt::Vertical 20 40 6 8 8 8 8 CPAP Treatment Information QFormLayout::AllNonFixedFieldsGrow 6 6 8 8 8 8 Date Diagnosed true Untreated AHI 999.990000000000009 CPAP Mode CPAP APAP Bi-Level ASV RX Pressure true 6 8 8 8 8 Doctors / Clinic Information QFormLayout::AllNonFixedFieldsGrow 6 6 8 8 8 8 Doctors Name Qt::Horizontal Practice Name Patient ID Qt::Horizontal Address true Qt::Horizontal Phone Email Qt::Vertical 20 40 128 128 :/icons/logo-lg.png true 0 0 14 false true OSCAR Qt::AlignHCenter|Qt::AlignTop true Qt::AlignCenter Qt::Vertical 20 40 16 8 8 8 8 Qt::Horizontal 40 20 0 0 &Cancel false 0 0 &Back false 0 0 &Next false false cancelButton clicked() NewProfile reject() 425 373 288 197 OSCAR-code-v1.3.1/oscar/oscar.pro000066400000000000000000000441441417327530600164400ustar00rootroot00000000000000#------------------------------------------------- # # Project created by QtCreator 2011-06-20T22:05:30 # #------------------------------------------------- message(Platform is $$QMAKESPEC ) lessThan(QT_MAJOR_VERSION,5) { error("You need Qt 5.7 or newer to build OSCAR"); } if (equals(QT_MAJOR_VERSION,5)) { lessThan(QT_MINOR_VERSION,9) { message("You need Qt 5.9 to build OSCAR with Help Pages") DEFINES += helpless } lessThan(QT_MINOR_VERSION,7) { error("You need Qt 5.7 or newer to build OSCAR"); } } # get rid of the help browser, at least for now DEFINES += helpless QT += core gui network xml printsupport serialport widgets help contains(DEFINES, helpless) { QT -= help } DEFINES += QT_DEPRECATED_WARNINGS # Enable this to turn off Check for Updates feature # DEFINES += NO_CHECKUPDATES #OSCAR requires OpenGL 2.0 support to run smoothly #On platforms where it's not available, it can still be built to work #provided the BrokenGL DEFINES flag is passed to qmake (eg, qmake [specs] /path/to/OSCAR_QT.pro DEFINES+=BrokenGL) (hint, Projects button on the left) contains(DEFINES, NoGL) { message("Building with QWidget gGraphView to support systems without ANY OpenGL") DEFINES += BROKEN_OPENGL_BUILD DEFINES += NO_OPENGL_BUILD } else:contains(DEFINES, BrokenGL) { DEFINES += BROKEN_OPENGL_BUILD message("Building with QWidget gGraphView to support systems with legacy graphics") DEFINES-=BrokenGL } else { QT += opengl message("Building with regular OpenGL gGraphView") } DEFINES += LOCK_RESMED_SESSIONS CONFIG += c++11 CONFIG += rtti CONFIG -= debug_and_release contains(DEFINES, STATIC) { static { CONFIG += static QTPLUGIN += qgif qpng message("Static build.") } } TARGET = OSCAR unix:!macx:!haiku { TARGET.path=/usr/bin } TEMPLATE = app gitinfotarget.target = git_info.h gitinfotarget.depends = FORCE win32 { system("$$_PRO_FILE_PWD_/update_gitinfo.bat"); message("Updating gitinfo.h for Windows build") gitinfotarget.commands = "$$_PRO_FILE_PWD_/update_gitinfo.bat" } else { system("/bin/bash $$_PRO_FILE_PWD_/update_gitinfo.sh"); message("Updating gitinfo.h for non-Windows build") gitinfotarget.commands = "/bin/bash $$_PRO_FILE_PWD_/update_gitinfo.sh" } PRE_TARGETDEPS += git_info.h QMAKE_EXTRA_TARGETS += gitinfotarget !contains(DEFINES, helpless) { #Build the help documentation message("Generating help files"); qtPrepareTool(QCOLGENERATOR, qcollectiongenerator) command=$$QCOLGENERATOR $$PWD/help/index.qhcp -o $$PWD/help/index.qhc system($$command)|error("Failed to run: $$command") message("Finished generating help files"); } QMAKE_TARGET_PRODUCT = OSCAR QMAKE_TARGET_COMPANY = The OSCAR Team QMAKE_TARGET_COPYRIGHT = © 2019-2022 The OSCAR Team QMAKE_TARGET_DESCRIPTION = "OpenSource CPAP Analysis Reporter" _VERSION_FILE = $$cat(./VERSION) VERSION = $$section(_VERSION_FILE, '"', 1, 1) win32 { VERSION = $$section(VERSION, '-', 0, 0) } RC_ICONS = ./icons/logo.ico macx { QMAKE_TARGET_BUNDLE_PREFIX = "org.oscar-team" # QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.12 LIBS += -lz ICON = icons/OSCAR.icns } else:haiku { LIBS += -lz -lGLU DEFINES += _TTY_POSIX_ } else:unix { LIBS += -lX11 -lz -lGLU DEFINES += _TTY_POSIX_ } else:win32 { DEFINES += WINVER=0x0501 # needed for mingw to pull in appropriate dbt business...probably a better way to do this LIBS += -lsetupapi INCLUDEPATH += $$PWD INCLUDEPATH += $$[QT_INSTALL_PREFIX]/../src/qtbase/src/3rdparty/zlib if (*-msvc*):!equals(TEMPLATE_PREFIX, "vc") { LIBS += -ladvapi32 } else { # MingW needs this LIBS += -lz } if (*-msvc*) { CONFIG += precompile_header PRECOMPILED_HEADER = pch.h HEADERS += pch.h } CONFIG(release, debug|release) { contains(DEFINES, OfficialBuild) { QMAKE_POST_LINK += "$$PWD/../../scripts/release_tool.sh --testing --source \"$$PWD/..\" --binary \"$${OUT_PWD}/$${TARGET}.exe\"" } } } TRANSLATIONS = $$files($$PWD/../Translations/*.ts) TRANSLATIONS += $$files($$PWD/../Translations/qt/*.ts) qtPrepareTool(LRELEASE, lrelease) for(file, TRANSLATIONS) { qmfile = $$absolute_path($$basename(file), $$PWD/translations/) qmfile ~= s,.ts$,.qm, qmdir = $$PWD/translations !exists($$qmdir) { mkpath($$qmdir)|error("Aborting.") } qmout = $$qmfile command = $$LRELEASE -removeidentical $$file -qm $$qmfile system($$command)|error("Failed to run: $$command") TRANSLATIONS_FILES += $$qmfile } HTML_FILES = $$files($$PWD/../Htmldocs/*.html) #copy the Translation and Help files to where the test binary wants them message("Setting up Translations & Help Transfers") macx { !contains(DEFINES, helpless) { HelpFiles.files = $$files($$PWD/help/*.qch) HelpFiles.path = Contents/Resources/Help QMAKE_BUNDLE_DATA += HelpFiles } # Removed because we are not using QT's translation files # QtTransFiles.files = $$files($$[QT_INSTALL_TRANSLATIONS]/qt*.qm) # QtTransFiles.path = Contents/translations # QMAKE_BUNDLE_DATA += QtTransFiles TransFiles.files = $${TRANSLATIONS_FILES} TransFiles.path = Contents/Resources/translations QMAKE_BUNDLE_DATA += TransFiles HtmlFiles.files = $${HTML_FILES} HtmlFiles.path = Contents/Resources/html QMAKE_BUNDLE_DATA += HtmlFiles } else { !contains(DEFINES, helpless) { HELPDIR = $$OUT_PWD/Help HELP_FILES += $$PWD/help/*.qch } DDIR = $$OUT_PWD/Translations HTMLDIR = $$OUT_PWD/Html TRANS_FILES = $${TRANSLATIONS_FILES} win32 { TRANS_FILES_WIN = $${TRANS_FILES} TRANS_FILES_WIN ~= s,/,\\,g DDIR ~= s,/,\\,g !exists($$quote($$DDIR)): system(mkdir $$quote($$DDIR)) for(FILE,TRANS_FILES_WIN) { system(xcopy /y $$quote($$FILE) $$quote($$DDIR)) } HTML_FILES_WIN = $${HTML_FILES} HTML_FILES_WIN ~= s,/,\\,g HTMLDIR ~= s,/,\\,g !exists($$quote($$HTMLDIR)): system(mkdir $$quote($$HTMLDIR)) for(FILE,HTML_FILES_WIN) { system(xcopy /y $$quote($$FILE) $$quote($$HTMLDIR)) } !contains(DEFINES, helpless) { HELP_FILES_WIN = $${HELP_FILES} HELP_FILES_WIN ~= s,/,\\,g HELPDIR ~= s,/,\\,g !exists($$quote($$HELPDIR)): system(mkdir $$quote($$HELPDIR)) for(FILE,HELP_FILES_WIN) { system(xcopy /y $$quote($$FILE) $$quote($$HELPDIR)) } } } else { system(mkdir -p $$quote($$DDIR)) for(FILE,TRANS_FILES) { system(cp $$quote($$FILE) $$quote($$DDIR)) } system(mkdir -p $$quote($$HTMLDIR)) for(FILE,HTML_FILES) { system(cp $$quote($$FILE) $$quote($$HTMLDIR)) } !contains(DEFINES, helpless) { system(mkdir -p $$quote($$HELPDIR)) for(FILE,HELP_FILES) { system(cp $$quote($$FILE) $$quote($$HELPDIR)) } } } } lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,12) { unix { system("/bin/bash $$_PRO_FILE_PWD_/fix_5-12_UI_files.sh"); message("Fixing UI files for old QT versions") } } SOURCES += \ checkupdates.cpp \ common_gui.cpp \ cprogressbar.cpp \ daily.cpp \ exportcsv.cpp \ main.cpp \ mainwindow.cpp \ newprofile.cpp \ overview.cpp \ preferencesdialog.cpp \ # psettings.cpp \ reports.cpp \ sessionbar.cpp \ # updateparser.cpp \ version.cpp \ Graphs/gFlagsLine.cpp \ Graphs/gFooBar.cpp \ Graphs/gGraph.cpp \ Graphs/gGraphView.cpp \ Graphs/glcommon.cpp \ Graphs/gLineChart.cpp \ Graphs/gLineOverlay.cpp \ Graphs/gSegmentChart.cpp \ Graphs/gspacer.cpp \ Graphs/gStatsLine.cpp \ Graphs/gSummaryChart.cpp \ Graphs/gXAxis.cpp \ Graphs/gYAxis.cpp \ Graphs/layer.cpp \ SleepLib/calcs.cpp \ SleepLib/common.cpp \ SleepLib/day.cpp \ SleepLib/event.cpp \ SleepLib/machine.cpp \ SleepLib/machine_loader.cpp \ SleepLib/importcontext.cpp \ SleepLib/preferences.cpp \ SleepLib/profiles.cpp \ SleepLib/schema.cpp \ SleepLib/session.cpp \ SleepLib/loader_plugins/cms50_loader.cpp \ SleepLib/loader_plugins/dreem_loader.cpp \ SleepLib/loader_plugins/icon_loader.cpp \ SleepLib/loader_plugins/sleepstyle_loader.cpp \ SleepLib/loader_plugins/sleepstyle_EDFinfo.cpp \ SleepLib/loader_plugins/intellipap_loader.cpp \ SleepLib/loader_plugins/mseries_loader.cpp \ SleepLib/loader_plugins/prs1_loader.cpp \ SleepLib/loader_plugins/prs1_parser.cpp \ SleepLib/loader_plugins/prs1_parser_xpap.cpp \ SleepLib/loader_plugins/prs1_parser_vent.cpp \ SleepLib/loader_plugins/prs1_parser_asv.cpp \ SleepLib/loader_plugins/resmed_loader.cpp \ SleepLib/loader_plugins/resmed_EDFinfo.cpp \ SleepLib/loader_plugins/somnopose_loader.cpp \ SleepLib/loader_plugins/viatom_loader.cpp \ SleepLib/loader_plugins/zeo_loader.cpp \ SleepLib/thirdparty/botan_all.cpp \ zip.cpp \ SleepLib/thirdparty/miniz.c \ csv.cpp \ rawdata.cpp \ translation.cpp \ statistics.cpp \ oximeterimport.cpp \ SleepLib/deviceconnection.cpp \ SleepLib/xmlreplay.cpp \ SleepLib/serialoximeter.cpp \ SleepLib/loader_plugins/md300w1_loader.cpp \ Graphs/gSessionTimesChart.cpp \ Graphs/gPressureChart.cpp \ logger.cpp \ SleepLib/machine_common.cpp \ SleepLib/loader_plugins/weinmann_loader.cpp \ Graphs/gdailysummary.cpp \ Graphs/MinutesAtPressure.cpp \ SleepLib/journal.cpp \ SleepLib/progressdialog.cpp \ SleepLib/loader_plugins/cms50f37_loader.cpp \ profileselector.cpp \ SleepLib/appsettings.cpp \ SleepLib/loader_plugins/edfparser.cpp \ aboutdialog.cpp \ welcome.cpp !contains(DEFINES, helpless) { SOURCES += help.cpp } HEADERS += \ checkupdates.h \ common_gui.h \ cprogressbar.h \ daily.h \ exportcsv.h \ mainwindow.h \ newprofile.h \ overview.h \ preferencesdialog.h \ # psettings.h \ reports.h \ sessionbar.h \ # updateparser.h \ version.h \ VERSION \ Graphs/gFlagsLine.h \ Graphs/gFooBar.h \ Graphs/gGraph.h \ Graphs/gGraphView.h \ Graphs/glcommon.h \ Graphs/gLineChart.h \ Graphs/gLineOverlay.h \ Graphs/gSegmentChart.h\ Graphs/gspacer.h \ Graphs/gStatsLine.h \ Graphs/gSummaryChart.h \ Graphs/gXAxis.h \ Graphs/gYAxis.h \ Graphs/layer.h \ SleepLib/calcs.h \ SleepLib/common.h \ SleepLib/day.h \ SleepLib/event.h \ SleepLib/machine.h \ SleepLib/machine_common.h \ SleepLib/machine_loader.h \ SleepLib/importcontext.h \ SleepLib/preferences.h \ SleepLib/profiles.h \ SleepLib/schema.h \ SleepLib/session.h \ SleepLib/loader_plugins/cms50_loader.h \ SleepLib/loader_plugins/dreem_loader.h \ SleepLib/loader_plugins/icon_loader.h \ SleepLib/loader_plugins/sleepstyle_loader.h \ SleepLib/loader_plugins/sleepstyle_EDFinfo.h \ SleepLib/loader_plugins/intellipap_loader.h \ SleepLib/loader_plugins/mseries_loader.h \ SleepLib/loader_plugins/prs1_loader.h \ SleepLib/loader_plugins/prs1_parser.h \ SleepLib/loader_plugins/resmed_loader.h \ SleepLib/loader_plugins/resmed_EDFinfo.h \ SleepLib/loader_plugins/somnopose_loader.h \ SleepLib/loader_plugins/viatom_loader.h \ SleepLib/loader_plugins/zeo_loader.h \ SleepLib/thirdparty/botan_all.h \ SleepLib/thirdparty/botan_windows.h \ SleepLib/thirdparty/botan_linux.h \ SleepLib/thirdparty/botan_macos.h \ zip.h \ SleepLib/thirdparty/miniz.h \ csv.h \ rawdata.h \ translation.h \ statistics.h \ oximeterimport.h \ SleepLib/deviceconnection.h \ SleepLib/xmlreplay.h \ SleepLib/serialoximeter.h \ SleepLib/loader_plugins/md300w1_loader.h \ Graphs/gSessionTimesChart.h \ Graphs/gPressureChart.h \ logger.h \ SleepLib/loader_plugins/weinmann_loader.h \ Graphs/gdailysummary.h \ Graphs/MinutesAtPressure.h \ SleepLib/journal.h \ SleepLib/progressdialog.h \ SleepLib/loader_plugins/cms50f37_loader.h \ profileselector.h \ SleepLib/appsettings.h \ SleepLib/loader_plugins/edfparser.h \ aboutdialog.h \ welcome.h \ mytextbrowser.h \ git_info.h !contains(DEFINES, helpless) { HEADERS += help.h } FORMS += \ daily.ui \ overview.ui \ mainwindow.ui \ oximetry.ui \ preferencesdialog.ui \ newprofile.ui \ exportcsv.ui \ # UpdaterWindow.ui \ oximeterimport.ui \ profileselector.ui \ aboutdialog.ui \ welcome.ui !contains(DEFINES, helpless) { FORMS += help.ui } equals(QT_MAJOR_VERSION,5) { lessThan(QT_MINOR_VERSION,12) { FORMS += reports.ui } } RESOURCES += \ Resources.qrc OTHER_FILES += \ docs/index.html \ docs/schema.xml \ docs/graphs.xml \ docs/channels.xml \ docs/startup_tips.txt \ docs/countries.txt \ docs/tz.txt \ ../LICENSE.txt \ docs/tooltips.css \ docs/script.js \ ../update.xml \ docs/changelog.txt \ docs/intro.html \ docs/statistics.xml \ update_gitinfo.bat \ update_gitinfo.sh DISTFILES += \ ../README !contains(DEFINES, helpless) { DISTFILES += help/default.css \ help/help_en/daily.html \ help/help_en/glossary.html \ help/help_en/import.html \ help/help_en/index.html \ help/help_en/overview.html \ help/help_en/oximetry.html \ help/help_en/statistics.html \ help/help_en/supported.html \ help/help_en/gettingstarted.html \ help/help_en/tipsntricks.html \ help/help_en/faq.html \ help/help_nl/daily.html \ help/help_nl/faq.html \ help/help_nl/gettingstarted.html \ help/help_nl/glossary.html \ help/help_nl/import.html \ help/help_nl/index.html \ help/help_nl/overview.html \ help/help_nl/oximetry.html \ help/help_nl/statistics.html \ help/help_nl/supported.html \ help/help_nl/tipsntricks.html \ help/help_en/reportingbugs.html \ help/help_nl/OSCAR_Guide_nl.qhp \ help/help_en/OSCAR_Guide_en.qhp \ help/index.qhcp } # Always treat warnings as errors, even (especially!) in release QMAKE_CFLAGS += -Werror QMAKE_CXXFLAGS += -Werror gcc | clang { COMPILER_VERSION = $$system($$QMAKE_CXX " -dumpversion") COMPILER_MAJOR = $$split(COMPILER_VERSION, ".") COMPILER_MAJOR = $$first(COMPILER_MAJOR) message("$$QMAKE_CXX major version $$COMPILER_MAJOR") greaterThan(COMPILER_MAJOR, 10) : { QMAKE_CFLAGS += -Wno-error=stringop-overread QMAKE_CXXFLAGS += -Wno-error=stringop-overread message("Removing stringop-overread error") } } # Make deprecation warnings just warnings QMAKE_CFLAGS += -Wno-error=deprecated-declarations QMAKE_CXXFLAGS += -Wno-error=deprecated-declarations lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,9) { QMAKE_CFLAGS += -Wno-error=strict-aliasing QMAKE_CXXFLAGS += -Wno-error=strict-aliasing } # Create a debug GUI build by adding "CONFIG+=memdebug" to your qmake command memdebug { CONFIG += debug !win32 { # add memory checking on Linux and macOS debug builds QMAKE_CFLAGS += -g -Werror -fsanitize=address -fno-omit-frame-pointer -fno-common -fsanitize-address-use-after-scope lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,9) { QMAKE_CFLAGS -= -fsanitize-address-use-after-scope } QMAKE_CXXFLAGS += -g -Werror -fsanitize=address -fno-omit-frame-pointer -fno-common -fsanitize-address-use-after-scope lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,9) { QMAKE_CXXFLAGS -= -fsanitize-address-use-after-scope } QMAKE_LFLAGS += -fsanitize=address } } # Turn on unit testing by adding "CONFIG+=test" to your qmake command test { TARGET = test DEFINES += UNITTEST_MODE QT += testlib QT -= gui CONFIG += console debug CONFIG -= app_bundle !win32 { # add memory checking on Linux and macOS test builds QMAKE_CFLAGS += -Werror -fsanitize=address -fno-omit-frame-pointer -fno-common -fsanitize-address-use-after-scope lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,9) { QMAKE_CFLAGS -= -fsanitize-address-use-after-scope } QMAKE_CXXFLAGS += -Werror -fsanitize=address -fno-omit-frame-pointer -fno-common -fsanitize-address-use-after-scope lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,9) { QMAKE_CXXFLAGS -= -fsanitize-address-use-after-scope } QMAKE_LFLAGS += -fsanitize=address } SOURCES += \ tests/prs1tests.cpp \ tests/rawdatatests.cpp \ tests/resmedtests.cpp \ tests/sessiontests.cpp \ tests/versiontests.cpp \ tests/viatomtests.cpp \ tests/deviceconnectiontests.cpp \ tests/dreemtests.cpp \ tests/zeotests.cpp HEADERS += \ tests/AutoTest.h \ tests/prs1tests.h \ tests/rawdatatests.h \ tests/resmedtests.h \ tests/sessiontests.h \ tests/versiontests.h \ tests/viatomtests.h \ tests/deviceconnectiontests.h \ tests/dreemtests.h \ tests/zeotests.h } macx { app_bundle { # On macOS put a custom Info.plist into the bundle that disables dark mode on Mojave. QMAKE_INFO_PLIST = "../Building/MacOS/Info.plist.in" # Add the git revision to the Info.plist. Info_plist.target = Info.plist Info_plist.depends = $${TARGET}.app/Contents/Info.plist Info_plist.commands = $$_PRO_FILE_PWD_/../Building/MacOS/finalize_plist $$_PRO_FILE_PWD_ $${TARGET}.app/Contents/Info.plist QMAKE_EXTRA_TARGETS += Info_plist PRE_TARGETDEPS += $$Info_plist.target } # Add a dist-mac target to build the distribution .dmg. QMAKE_EXTRA_TARGETS += dist-mac dist-mac.commands = QT_BIN=$$[QT_INSTALL_PREFIX]/bin $$_PRO_FILE_PWD_/../Building/MacOS/create_dmg $${TARGET} $${TARGET}.app $$_PRO_FILE_PWD_/../Building/MacOS/README.rtfd $$_PRO_FILE_PWD_/../Building/MacOS/background.png dist-mac.depends = $${TARGET}.app/Contents/MacOS/$${TARGET} } OSCAR-code-v1.3.1/oscar/overview.cpp000066400000000000000000000572741417327530600171710ustar00rootroot00000000000000/* Overview GUI Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include //#include #include #include #include #include #include //#include #include "SleepLib/profiles.h" #include "overview.h" #include "ui_overview.h" #include "common_gui.h" #include "Graphs/gXAxis.h" #include "Graphs/gLineChart.h" #include "Graphs/gYAxis.h" #include "Graphs/gPressureChart.h" #include "cprogressbar.h" #include "mainwindow.h" extern MainWindow *mainwin; Overview::Overview(QWidget *parent, gGraphView *shared) : QWidget(parent), ui(new Ui::Overview), m_shared(shared) { ui->setupUi(this); // Set Date controls locale to 4 digit years QLocale locale = QLocale::system(); QString shortformat = locale.dateFormat(QLocale::ShortFormat); if (!shortformat.toLower().contains("yyyy")) { shortformat.replace("yy", "yyyy"); } ui->toggleVisibility->setVisible(false); /* get rid of tiny triangle that disables data display */ ui->dateStart->setDisplayFormat(shortformat); ui->dateEnd->setDisplayFormat(shortformat); Qt::DayOfWeek dow = firstDayOfWeekFromLocale(); ui->dateStart->calendarWidget()->setFirstDayOfWeek(dow); ui->dateEnd->calendarWidget()->setFirstDayOfWeek(dow); // Stop both calendar drop downs highlighting weekends in red QTextCharFormat format = ui->dateStart->calendarWidget()->weekdayTextFormat(Qt::Saturday); format.setForeground(QBrush(COLOR_Black, Qt::SolidPattern)); ui->dateStart->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format); ui->dateStart->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format); ui->dateEnd->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format); ui->dateEnd->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format); // Connect the signals to update which days have CPAP data when the month is changed connect(ui->dateStart->calendarWidget(), SIGNAL(currentPageChanged(int, int)), this, SLOT(dateStart_currentPageChanged(int, int))); connect(ui->dateEnd->calendarWidget(), SIGNAL(currentPageChanged(int, int)), this, SLOT(dateEnd_currentPageChanged(int, int))); QVBoxLayout *framelayout = new QVBoxLayout; ui->graphArea->setLayout(framelayout); QFrame *border = new QFrame(ui->graphArea); framelayout->setMargin(1); border->setFrameShape(QFrame::StyledPanel); framelayout->addWidget(border,1); /////////////////////////////////////////////////////////////////////////////// // Create the horizontal layout to hold the GraphView object and it's scrollbar /////////////////////////////////////////////////////////////////////////////// layout = new QHBoxLayout(border); layout->setSpacing(0); // remove the ugly margins/spacing layout->setMargin(0); layout->setContentsMargins(0, 0, 0, 0); border->setLayout(layout); border->setAutoFillBackground(false); /////////////////////////////////////////////////////////////////////////////// // Create the GraphView Object /////////////////////////////////////////////////////////////////////////////// GraphView = new gGraphView(ui->graphArea, m_shared, this); GraphView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); GraphView->setEmptyText(STR_Empty_NoData); // Create the custom scrollbar and attach to GraphView scrollbar = new MyScrollBar(ui->graphArea); scrollbar->setOrientation(Qt::Vertical); scrollbar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); scrollbar->setMaximumWidth(20); GraphView->setScrollBar(scrollbar); // Add the graphView and scrollbar to the layout. layout->addWidget(GraphView, 1); layout->addWidget(scrollbar, 0); layout->layout(); /////////////////////////////////////////////////////////////////////////////// // Create date display at bottom of page /////////////////////////////////////////////////////////////////////////////// dateLabel = new MyLabel(this); dateLabel->setAlignment(Qt::AlignVCenter); dateLabel->setText("[Date Widget]"); QFont font = dateLabel->font(); font.setPointSizeF(font.pointSizeF()*1.3F); dateLabel->setFont(font); QPalette palette = dateLabel->palette(); palette.setColor(QPalette::Base, Qt::blue); dateLabel->setPalette(palette); ui->dateLayout->addWidget(dateLabel,1); /////////////////////////////////////////////////////////////////////////////// // Rebuild contents /////////////////////////////////////////////////////////////////////////////// RebuildGraphs(false); ui->rangeCombo->setCurrentIndex(p_profile->general->lastOverviewRange()); icon_on = new QIcon(":/icons/session-on.png"); icon_off = new QIcon(":/icons/session-off.png"); icon_up_down = new QIcon(":/icons/up-down.png"); icon_warning = new QIcon(":/icons/warning.png"); GraphView->resetLayout(); GraphView->SaveDefaultSettings(); GraphView->LoadSettings("Overview"); //no trans GraphView->setEmptyImage(QPixmap(":/icons/logo-md.png")); connect(GraphView, SIGNAL(updateCurrentTime(double)), this, SLOT(on_LineCursorUpdate(double))); connect(GraphView, SIGNAL(updateRange(double,double)), this, SLOT(on_RangeUpdate(double,double))); connect(GraphView, SIGNAL(GraphsChanged()), this, SLOT(updateGraphCombo())); } Overview::~Overview() { disconnect(GraphView, SIGNAL(GraphsChanged()), this, SLOT(updateGraphCombo())); disconnect(GraphView, SIGNAL(updateRange(double,double)), this, SLOT(on_RangeUpdate(double,double))); disconnect(GraphView, SIGNAL(updateCurrentTime(double)), this, SLOT(on_LineCursorUpdate(double))); disconnect(ui->dateEnd->calendarWidget(), SIGNAL(currentPageChanged(int, int)), this, SLOT(dateEnd_currentPageChanged(int, int))); disconnect(ui->dateStart->calendarWidget(), SIGNAL(currentPageChanged(int, int)), this, SLOT(dateStart_currentPageChanged(int, int))); // Save graph orders and pin status, etc... GraphView->SaveSettings("Overview");//no trans delete ui; } void Overview::ResetFont() { QFont font = QApplication::font(); font.setPointSizeF(font.pointSizeF()*1.3F); dateLabel->setFont(font); } // Create all the graphs for the Overview page void Overview::CreateAllGraphs() { /////////////////////////////////////////////////////////////////////////////// // Add all the graphs // Process is to createGraph() to make the graph object, then add a layer // that provides the contents for that graph. /////////////////////////////////////////////////////////////////////////////// // Add graphs that are always included ChannelID ahicode = p_profile->general->calculateRDI() ? CPAP_RDI : CPAP_AHI; if (ahicode == CPAP_RDI) { AHI = createGraph("AHIBreakdown", STR_TR_RDI, tr("Respiratory\nDisturbance\nIndex")); } else { AHI = createGraph("AHIBreakdown", STR_TR_AHI, tr("Apnea\nHypopnea\nIndex")); } ahi = new gAHIChart(); AHI->AddLayer(ahi); UC = createGraph(STR_GRAPH_Usage, tr("Usage"), tr("Usage\n(hours)")); UC->AddLayer(uc = new gUsageChart()); STG = createGraph("New Session", tr("Session Times"), tr("Session Times"), YT_Time); stg = new gSessionTimesChart(); STG->AddLayer(stg); PR = createGraph("Pressure Settings", STR_TR_Pressure, STR_TR_Pressure + "\n(" + STR_UNIT_CMH2O + ")"); pres = new gPressureChart(); PR->AddLayer(pres); TTIA = createGraph("TTIA", tr("Total Time in Apnea"), tr("Total Time in Apnea\n(Minutes)")); ttia = new gTTIAChart(); TTIA->AddLayer(ttia); // Add graphs for all channels that have been marked in Preferences Dialog as wanting a graph QHash::iterator chit; QHash::iterator chit_end = schema::channel.channels.end(); for (chit = schema::channel.channels.begin(); chit != chit_end; ++chit) { schema::Channel * chan = chit.value(); if (chan->showInOverview()) { ChannelID code = chan->id(); QString name = chan->fullname(); if (name.length() > 16) name = chan->label(); // qDebug() << "Channel" << name << "type" << chan->type() << "machine type" << chan->machtype(); gGraph *G = createGraph(chan->code(), name, chan->description()); if ((chan->type() == schema::FLAG) || (chan->type() == schema::MINOR_FLAG)) { gSummaryChart * sc = new gSummaryChart(chan->code(), chan->machtype()); // gts was MT_CPAP sc->addCalc(code, ST_CPH, schema::channel[code].defaultColor()); G->AddLayer(sc); } else if (chan->type() == schema::SPAN) { gSummaryChart * sc = new gSummaryChart(chan->code(), MT_CPAP); sc->addCalc(code, ST_SPH, schema::channel[code].defaultColor()); G->AddLayer(sc); } else if (chan->type() == schema::WAVEFORM) { G->AddLayer(new gSummaryChart(code, chan->machtype())); } else if (chan->type() == schema::UNKNOWN) { gSummaryChart * sc = new gSummaryChart(chan->code(), MT_CPAP); sc->addCalc(code, ST_CPH, schema::channel[code].defaultColor()); G->AddLayer(sc); } } // if showInOverview() } // for chit WEIGHT = createGraph(STR_GRAPH_Weight, STR_TR_Weight, STR_TR_Weight, YT_Weight); weight = new SummaryChart("Weight", GT_LINE); weight->setMachineType(MT_JOURNAL); weight->addSlice(Journal_Weight, QColor("black"), ST_SETAVG); WEIGHT->AddLayer(weight); BMI = createGraph(STR_GRAPH_BMI, STR_TR_BMI, tr("Body\nMass\nIndex")); bmi = new SummaryChart("BMI", GT_LINE); bmi->setMachineType(MT_JOURNAL); bmi->addSlice(Journal_BMI, QColor("black"), ST_SETAVG); BMI->AddLayer(bmi); ZOMBIE = createGraph(STR_GRAPH_Zombie, STR_TR_Zombie, tr("How you felt\n(0-10)")); zombie = new SummaryChart("Zombie", GT_LINE); zombie->setMachineType(MT_JOURNAL); zombie->addSlice(Journal_ZombieMeter, QColor("black"), ST_SETAVG); ZOMBIE->AddLayer(zombie); } // Recalculates Overview chart info void Overview::RebuildGraphs(bool reset) { qint64 minx, maxx; if (reset) { GraphView->GetXBounds(minx, maxx); } GraphView->trashGraphs(true); // Remove all existing graphs CreateAllGraphs(); if (reset) { GraphView->resetLayout(); GraphView->setDay(nullptr); GraphView->SetXBounds(minx, maxx, 0, false); GraphView->resetLayout(); updateGraphCombo(); } } // Create an overview graph, adding it to the overview gGraphView object // param QString name The title of the graph // param QString units The units of measurements to show in the popup gGraph *Overview::createGraph(QString code, QString name, QString units, YTickerType yttype) { int default_height = AppSetting->graphHeight(); gGraph *g = new gGraph(code, GraphView, name, units, default_height, 0); gYAxis *yt; switch (yttype) { case YT_Time: yt = new gYAxisTime(true); // Time scale break; case YT_Weight: yt = new gYAxisWeight(p_profile->general->unitSystem()); break; default: yt = new gYAxis(); // Plain numeric scale break; } g->AddLayer(yt, LayerLeft, gYAxis::Margin); gXAxisDay *x = new gXAxisDay(); g->AddLayer(x, LayerBottom, 0, gXAxisDay::Margin); g->AddLayer(new gXGrid()); return g; } void Overview::on_LineCursorUpdate(double time) { if (time > 1) { // even though the generated string is displayed to the user // no time zone conversion is neccessary, so pass UTC // to prevent QT from automatically converting to local time QDateTime dt = QDateTime::fromMSecsSinceEpoch(time, Qt::LocalTime/*, Qt::UTC*/); QString txt = dt.toString("dd MMM yyyy (dddd)"); dateLabel->setText(txt); } else dateLabel->setText(QString(GraphView->emptyText())); } void Overview::on_RangeUpdate(double minx, double /* maxx */) { if (minx > 1) { dateLabel->setText(GraphView->getRangeInDaysString()); } else { dateLabel->setText(QString(GraphView->emptyText())); } } void Overview::ReloadGraphs() { GraphView->setDay(nullptr); updateCube(); on_rangeCombo_activated(ui->rangeCombo->currentIndex()); } void Overview::setGraphText () { int numOff = 0; int numTotal = 0; gGraph *g; for (int i=0;isize();i++) { g=(*GraphView)[i]; if (!g->isEmpty()) { numTotal++; if (!g->visible()) { numOff++; } } } ui->graphCombo->setItemIcon(0, numOff ? *icon_warning : *icon_up_down); QString graphText; if (numOff == 0) graphText = QObject::tr("%1 Charts").arg(numTotal); else graphText = QObject::tr("%1 of %2 Charts").arg(numTotal-numOff).arg(numTotal); ui->graphCombo->setItemText(0, graphText); } void Overview::updateGraphCombo() { ui->graphCombo->clear(); gGraph *g; ui->graphCombo->addItem(*icon_up_down, tr("10 of 10 Charts"), true); // Translation only to define space required for (int i = 0; i < GraphView->size(); i++) { g = (*GraphView)[i]; if (g->isEmpty()) { continue; } if (g->visible()) { ui->graphCombo->addItem(*icon_on, g->title(), true); } else { ui->graphCombo->addItem(*icon_off, g->title(), false); } } ui->graphCombo->setCurrentIndex(0); setGraphText(); updateCube(); } #if 0 void Overview::ResetGraphs() { QDate start = ui->dateStart->date(); QDate end = ui->dateEnd->date(); GraphView->setDay(nullptr); updateCube(); if (start.isValid() && end.isValid()) { setRange(start, end); } } void Overview::ResetGraph(QString name) { gGraph *g = GraphView->findGraph(name); if (!g) { return; } g->setDay(nullptr); GraphView->redraw(); } #endif void Overview::RedrawGraphs() { GraphView->redraw(); } void Overview::UpdateCalendarDay(QDateEdit *dateedit, QDate date) { QCalendarWidget *calendar = dateedit->calendarWidget(); QTextCharFormat bold; QTextCharFormat cpapcol; QTextCharFormat normal; QTextCharFormat oxiday; bold.setFontWeight(QFont::Bold); cpapcol.setForeground(QBrush(Qt::blue, Qt::SolidPattern)); cpapcol.setFontWeight(QFont::Bold); oxiday.setForeground(QBrush(Qt::red, Qt::SolidPattern)); oxiday.setFontWeight(QFont::Bold); bool hascpap = p_profile->FindDay(date, MT_CPAP) != nullptr; bool hasoxi = p_profile->FindDay(date, MT_OXIMETER) != nullptr; //bool hasjournal=p_profile->GetDay(date,MT_JOURNAL)!=nullptr; if (hascpap) { if (hasoxi) { calendar->setDateTextFormat(date, oxiday); } else { calendar->setDateTextFormat(date, cpapcol); } } else if (p_profile->GetDay(date)) { calendar->setDateTextFormat(date, bold); } else { calendar->setDateTextFormat(date, normal); } calendar->setHorizontalHeaderFormat(QCalendarWidget::ShortDayNames); } void Overview::dateStart_currentPageChanged(int year, int month) { QDate d(year, month, 1); int dom = d.daysInMonth(); for (int i = 1; i <= dom; i++) { d = QDate(year, month, i); UpdateCalendarDay(ui->dateStart, d); } } void Overview::dateEnd_currentPageChanged(int year, int month) { QDate d(year, month, 1); int dom = d.daysInMonth(); for (int i = 1; i <= dom; i++) { d = QDate(year, month, i); UpdateCalendarDay(ui->dateEnd, d); } } void Overview::on_dateEnd_dateChanged(const QDate &date) { qint64 d1 = qint64(QDateTime(ui->dateStart->date(), QTime(0, 10, 0)/*, Qt::UTC*/).toTime_t()) * 1000L; qint64 d2 = qint64(QDateTime(date, QTime(23, 0, 0)/*, Qt::UTC*/).toTime_t()) * 1000L; GraphView->SetXBounds(d1, d2); ui->dateStart->setMaximumDate(date); if (customMode) { p_profile->general->setCustomOverviewRangeEnd(date); } } void Overview::on_dateStart_dateChanged(const QDate &date) { qint64 d1 = qint64(QDateTime(date, QTime(0, 10, 0)/*, Qt::UTC*/).toTime_t()) * 1000L; qint64 d2 = qint64(QDateTime(ui->dateEnd->date(), QTime(23, 0, 0)/*, Qt::UTC*/).toTime_t()) * 1000L; GraphView->SetXBounds(d1, d2); ui->dateEnd->setMinimumDate(date); if (customMode) { p_profile->general->setCustomOverviewRangeStart(date); } } // Zoom to 100% button clicked or called back from 100% zoom in popup menu void Overview::on_zoomButton_clicked() { qint64 d1 = qint64(QDateTime(ui->dateStart->date(), QTime(0, 10, 0)/*, Qt::UTC*/).toTime_t()) * 1000L; // GTS why UTC? qint64 d2 = qint64(QDateTime(ui->dateEnd->date(), QTime(23, 0, 0)/*, Qt::UTC*/).toTime_t()) * 1000L; // Interesting: start date set to 10 min after midnight, ending at 11 pm GraphView->SetXBounds(d1, d2); } void Overview::ResetGraphLayout() { GraphView->resetLayout(); } void Overview::ResetGraphOrder(int type) { Q_UNUSED(type) GraphView->resetGraphOrder(false); ResetGraphLayout(); } // Process new range selection from combo button void Overview::on_rangeCombo_activated(int index) { ui->dateStart->setMinimumDate(p_profile->FirstDay()); // first and last dates for ANY machine type ui->dateEnd->setMaximumDate(p_profile->LastDay()); // Exclude Journal in calculating the last day QDate end = p_profile->LastDay(MT_CPAP); end = max(end, p_profile->LastDay(MT_OXIMETER)); end = max(end, p_profile->LastDay(MT_POSITION)); end = max(end, p_profile->LastDay(MT_SLEEPSTAGE)); QDate start; if (index == 0) { start = end.addDays(-6); } else if (index == 1) { start = end.addDays(-13); } else if (index == 2) { start = end.addMonths(-1).addDays(1); } else if (index == 3) { start = end.addMonths(-2).addDays(1); } else if (index == 4) { start = end.addMonths(-3).addDays(1); } else if (index == 5) { start = end.addMonths(-6).addDays(1); } else if (index == 6) { start = end.addYears(-1).addDays(1); } else if (index == 7) { // Everything start = p_profile->FirstDay(); } else if (index == 8 || index == 9) { // Custom // Validate save Overview Custom Range for first access. if (!p_profile->general->customOverviewRangeStart().isValid() || (!p_profile->general->customOverviewRangeEnd().isValid() ) || (index==9 /* New Coustom mode - to reset custom range to displayed date range*/) ) { // Reset Custom Range to current range displayed // on first initialization of this version of OSCAR // or on new custom Mode to reset range. qint64 istart,iend; GraphView->GetXBounds(istart , iend); start = QDateTime::fromMSecsSinceEpoch( istart ).date(); end = QDateTime::fromMSecsSinceEpoch( iend ).date(); p_profile->general->setCustomOverviewRangeStart(start); p_profile->general->setCustomOverviewRangeEnd(end); index=8; ui->rangeCombo->setCurrentIndex(index); } else if (customMode) { // last mode was custom. // Reset Custom Range to current range in calendar widget // Custom mode MUST be initialized to false when the Custom Instance is created. start = ui->dateStart->date(); end = ui->dateEnd->date(); p_profile->general->setCustomOverviewRangeStart(start); p_profile->general->setCustomOverviewRangeEnd(end); } else { // have a change in RangeCombo selection. Use last saved values. start = p_profile->general->customOverviewRangeStart() ; end = p_profile->general->customOverviewRangeEnd() ; } } if (start < p_profile->FirstDay()) { start = p_profile->FirstDay(); } customMode = (index == 8) ; ui->dateStartLabel->setEnabled(customMode); ui->dateEndLabel->setEnabled(customMode); ui->dateEnd->setEnabled(customMode); ui->dateStart->setEnabled(customMode); p_profile->general->setLastOverviewRange(index); // type of range in last use // Ensure that all summary files are available and update version numbers if required int size = start.daysTo(end); qDebug() << "Overview range combo from" << start << "to" << end << "with" << size << "days"; QDate dateback = end; CProgressBar * progress = new CProgressBar (QObject::tr("Loading summaries"), mainwin, size); for (int i=1; i < size; ++i) { progress->add(1); auto di = p_profile->daylist.find(dateback); dateback = dateback.addDays(-1); if (di == p_profile->daylist.end()) // Check for no Day entry continue; Day * day = di.value(); if (!day) continue; if (day->size() <= 0) continue; day->OpenSummary(); // This can be slow if summary needs to be updated to new version } progress->close(); delete progress; // first and last dates for ANY machine type setRange(start, end); } // Saves dates in UI, clicks zoom button, and updates combo box void Overview::setRange(QDate start, QDate end) { ui->dateEnd->blockSignals(true); ui->dateStart->blockSignals(true); ui->dateStart->setMaximumDate(end); ui->dateEnd->setMinimumDate(start); ui->dateStart->setDate(start); ui->dateEnd->setDate(end); ui->dateEnd->blockSignals(false); ui->dateStart->blockSignals(false); this->on_zoomButton_clicked(); // Click on zoom-out to 100% button updateGraphCombo(); } void Overview::on_graphCombo_activated(int index) { if (index < 0) { return; } if (index > 0 ) { gGraph *g; QString s; s = ui->graphCombo->currentText(); bool b = !ui->graphCombo->itemData(index, Qt::UserRole).toBool(); ui->graphCombo->setItemData(index, b, Qt::UserRole); if (b) { ui->graphCombo->setItemIcon(index, *icon_on); } else { ui->graphCombo->setItemIcon(index, *icon_off); } g = GraphView->findGraphTitle(s); g->setVisible(b); } ui->graphCombo->setCurrentIndex(0); updateCube(); setGraphText(); GraphView->updateScale(); GraphView->redraw(); } void Overview::updateCube() { if ((GraphView->visibleGraphs() == 0)) { ui->toggleVisibility->setArrowType(Qt::UpArrow); ui->toggleVisibility->setToolTip(tr("Show all graphs")); ui->toggleVisibility->blockSignals(true); ui->toggleVisibility->setChecked(true); ui->toggleVisibility->blockSignals(false); if (ui->graphCombo->count() > 0) { GraphView->setEmptyText(STR_Empty_NoGraphs); } else { GraphView->setEmptyText(STR_Empty_NoData); } } else { ui->toggleVisibility->setArrowType(Qt::DownArrow); ui->toggleVisibility->setToolTip(tr("Hide all graphs")); ui->toggleVisibility->blockSignals(true); ui->toggleVisibility->setChecked(false); ui->toggleVisibility->blockSignals(false); } } void Overview::on_toggleVisibility_clicked(bool checked) { gGraph *g; QString s; QIcon *icon = checked ? icon_off : icon_on; for (int i = 0; i < ui->graphCombo->count(); i++) { s = ui->graphCombo->itemText(i); ui->graphCombo->setItemIcon(i, *icon); ui->graphCombo->setItemData(i, !checked, Qt::UserRole); g = GraphView->findGraphTitle(s); g->setVisible(!checked); } updateCube(); GraphView->updateScale(); GraphView->redraw(); } OSCAR-code-v1.3.1/oscar/overview.h000066400000000000000000000103571417327530600166250ustar00rootroot00000000000000/* Overview GUI Headers * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef OVERVIEW_H #define OVERVIEW_H #include #ifndef BROKEN_OPENGL_BUILD #include #endif #include #include #include "SleepLib/profiles.h" #include "Graphs/gGraphView.h" #include "Graphs/gSummaryChart.h" #include "Graphs/gSessionTimesChart.h" namespace Ui { class Overview; } class Report; enum YTickerType { YT_Number, YT_Time, YT_Weight }; /*! \class Overview \author Mark Watkins \brief Overview tab, showing overall summary data */ class Overview : public QWidget { Q_OBJECT public: explicit Overview(QWidget *parent, gGraphView *shared = nullptr); ~Overview(); //! \brief Returns Overview gGraphView object containing it's graphs gGraphView *graphView() { return GraphView; } //! \brief Recalculates Overview chart info void ReloadGraphs(); //! \brief Resets font in date display void ResetFont(); //! \brief Recalculates Overview chart info, but keeps the date set //void ResetGraphs(); //! \brief Reset graphs to uniform heights void ResetGraphLayout(); /*! \fn ResetGraphOrder() \brief Resets all graphs in the main gGraphView back to their initial order. */ void ResetGraphOrder(int type); //! \brief Calls updateGL to redraw the overview charts void RedrawGraphs(); //! \brief Sets the currently selected date range of the overview display void setRange(QDate start, QDate end); /*! \brief Create an overview graph, adding it to the overview gGraphView object \param QString name The title of the graph \param QString units The units of measurements to show in the popup */ gGraph *createGraph(QString code, QString name, QString units = "", YTickerType yttype = YT_Number); gGraph *AHI, *AHIHR, *UC, *FL, *SA, *US, *PR, *LK, *NPB, *SET, *SES, *RR, *MV, *TV, *PTB, *PULSE, *SPO2, *NLL, *WEIGHT, *ZOMBIE, *BMI, *TGMV, *TOTLK, *STG, *SN, *TTIA; SummaryChart *bc, *sa, *us, *pr, *set, *ses, *ptb, *pulse, *spo2, *weight, *zombie, *bmi, *ahihr, *tgmv, *totlk; gSummaryChart * stg, *uc, *ahi, * pres, *lk, *npb, *rr, *mv, *tv, *nll, *sn, *ttia; //! \brief List of SummaryCharts shown on the overview page QVector OverviewCharts; //void ResetGraph(QString name); void RebuildGraphs(bool reset = true); public slots: void onRebuildGraphs() { RebuildGraphs(true); } //! \brief Resets view to currently shown start & end dates void on_zoomButton_clicked(); private slots: void updateGraphCombo(); //! \brief Resets the graph view because the Start date has been changed void on_dateStart_dateChanged(const QDate &date); //! \brief Resets the graph view because the End date has been changed void on_dateEnd_dateChanged(const QDate &date); //! \brief Updates the calendar highlighting when changing to a new month void dateStart_currentPageChanged(int year, int month); //! \brief Updates the calendar highlighting when changing to a new month void dateEnd_currentPageChanged(int year, int month); //void on_printDailyButton_clicked(); void on_rangeCombo_activated(int index); void on_graphCombo_activated(int index); void on_toggleVisibility_clicked(bool checked); void on_LineCursorUpdate(double time); void on_RangeUpdate(double minx, double maxx); void setGraphText (); private: void CreateAllGraphs(); Ui::Overview *ui; gGraphView *GraphView; MyScrollBar *scrollbar; QHBoxLayout *layout; gGraphView *m_shared; QIcon *icon_on; QIcon *icon_off; QIcon *icon_up_down; QIcon *icon_warning; MyLabel *dateLabel; bool customMode=false; //! \brief Updates the calendar highlighting for the calendar object for this date. void UpdateCalendarDay(QDateEdit *calendar, QDate date); void updateCube(); Day *day; // dummy in this case }; #endif // OVERVIEW_H OSCAR-code-v1.3.1/oscar/overview.ui000066400000000000000000000164661417327530600170220ustar00rootroot00000000000000 Overview 0 0 760 392 Form 0 0 0 0 0 16777215 45 QFrame::StyledPanel QFrame::Raised 4 4 0 4 0 Range: 0 0 Last Week Last Two Weeks Last Month Last Two Months Last Three Months Last 6 Months Last Year Everything Custom Snapshot Start: 0 0 true Qt::UTC End: 0 0 true Qt::UTC Reset view to selected date range QToolButton { background: transparent; border-radius: 8px; border: 2px solid transparent; } QToolButton:hover { border: 2px solid #456789; } QToolButton:pressed { border: 2px solid #456789; background-color: #89abcd; } ... :/icons/refresh.png:/icons/refresh.png Qt::Horizontal QSizePolicy::Fixed 4 20 Toggle Graph Visibility QToolButton { background: transparent; border-radius: 8px; border: 2px solid transparent; } QToolButton:hover { border: 2px solid #456789; } QToolButton:pressed { border: 2px solid #456789; background-color: #89abcd; } ... true true Qt::DownArrow Drop down to see list of graphs to switch on/off. QComboBox::AdjustToContents Graphs OSCAR-code-v1.3.1/oscar/oximeterimport.cpp000066400000000000000000001245461417327530600204070ustar00rootroot00000000000000/* Oximeter Import Wizard Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include "Graphs/gYAxis.h" #include "Graphs/gXAxis.h" #include "oximeterimport.h" #include "ui_oximeterimport.h" #include "SleepLib/calcs.h" #include "mainwindow.h" extern MainWindow * mainwin; #include "SleepLib/loader_plugins/cms50_loader.h" #include "SleepLib/loader_plugins/cms50f37_loader.h" #include "SleepLib/loader_plugins/md300w1_loader.h" Qt::DayOfWeek firstDayOfWeekFromLocale(); QList GetOxiLoaders(); OximeterImport::OximeterImport(QWidget *parent) : QDialog(parent), ui(new Ui::OximeterImport) { ui->setupUi(this); setWindowTitle(tr("Oximeter Import Wizard")); this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); ui->stackedWidget->setCurrentIndex(0); ui->retryButton->setVisible(false); ui->stopButton->setVisible(false); ui->saveButton->setVisible(false); ui->syncButton->setVisible(false); ui->chooseSessionButton->setVisible(false); oximodule = nullptr; importMode = IM_UNDEFINED; liveView = new gGraphView(this); liveView->setVisible(false); liveView->setShowAuthorMessage(false); QVBoxLayout * lvlayout = new QVBoxLayout; lvlayout->setMargin(0); lvlayout->addWidget(liveView); ui->liveViewFrame->setLayout(lvlayout); plethyGraph = new gGraph("Plethy", liveView, STR_TR_Plethy, STR_UNIT_Hz); plethyGraph->AddLayer(new gYAxis(), LayerLeft, gYAxis::Margin); plethyGraph->AddLayer(new gXAxis(), LayerBottom, 0, 20); plethyGraph->AddLayer(plethyChart = new gLineChart(OXI_Plethy)); plethyGraph->setVisible(true); plethyGraph->setRecMinY(0); plethyGraph->setRecMaxY(128); ui->calendarWidget->setFirstDayOfWeek(Qt::Sunday); QTextCharFormat format = ui->calendarWidget->weekdayTextFormat(Qt::Saturday); format.setForeground(QBrush(Qt::black, Qt::SolidPattern)); ui->calendarWidget->setWeekdayTextFormat(Qt::Saturday, format); ui->calendarWidget->setWeekdayTextFormat(Qt::Sunday, format); ui->calendarWidget->setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader); Qt::DayOfWeek dow=firstDayOfWeekFromLocale(); ui->calendarWidget->setFirstDayOfWeek(dow); ui->dateTimeEdit->setMinimumHeight(ui->dateTimeEdit->height() + 10); ui->syncCPAPGroup->setVisible(false); QVBoxLayout * layout = new QVBoxLayout; layout->setMargin(0); ui->sessBarFrame->setLayout(layout); sessbar = new SessionBar(this); sessbar->setSelectMode(true); sessbar->setMouseTracking(true); sessbar->setMinimumHeight(40); connect(sessbar, SIGNAL(sessionClicked(Session*)), this, SLOT(onSessionSelected(Session*))); layout->addWidget(sessbar, 1); dummyday = nullptr; session = nullptr; ELplethy = nullptr; pulse = spo2 = -1; ui->skipWelcomeCheckbox->setChecked(p_profile->oxi->skipOxiIntroScreen()); if (p_profile->oxi->skipOxiIntroScreen()) { ui->stackedWidget->setCurrentWidget(ui->importSelectionPage); ui->nextButton->setVisible(false); ui->informationButton->setVisible(true); } else { ui->stackedWidget->setCurrentWidget(ui->welcomePage); ui->nextButton->setVisible(true); ui->informationButton->setVisible(false); } ui->dateTimeEdit->setMinimumHeight(ui->dateTimeEdit->height()+10); setInformation(); ui->cms50DeviceName->setText(p_profile->oxi->defaultDevice()); int oxitype = p_profile->oxi->oximeterType(); ui->oximeterType->setCurrentIndex(oxitype); on_oximeterType_currentIndexChanged(oxitype); ui->cms50DeviceName->setEnabled(false); ui->cms50SyncTime->setChecked(p_profile->oxi->syncOximeterClock()); } OximeterImport::~OximeterImport() { if (dummyday != nullptr) { delete dummyday; } if (session != nullptr) { delete session; } if (ELplethy != nullptr) { delete ELplethy; } disconnect(sessbar, SIGNAL(sessionClicked(Session*)), this, SLOT(onSessionSelected(Session*))); delete ui; } void OximeterImport::on_nextButton_clicked() { int i = ui->stackedWidget->currentIndex(); i++; if (i >= ui->stackedWidget->count()) i = 0; switch (i) { case 0: ui->nextButton->setVisible(true); ui->nextButton->setText("&Start"); break; case 1: ui->nextButton->setVisible(false); ui->informationButton->setVisible(true); break; default: ui->informationButton->setVisible(true); ui->nextButton->setVisible(true); } ui->stackedWidget->setCurrentIndex(i); } void OximeterImport::updateStatus(QString msg) { qDebug() << "oximod - updateStatus to " << msg; ui->logBox->appendPlainText(msg); ui->directImportStatus->setText(msg); ui->liveStatusLabel->setText(msg); } SerialOximeter * OximeterImport::detectOximeter() { const int PORTSCAN_TIMEOUT=30000; const int delay=100; qDebug() << "oximod - Attempt to detect Oximeter"; ui->retryButton->setVisible(false); QList loaders; // GetOxiLoaders(); if (ui->oximeterType->currentIndex() == 0) { // CMS50F3.7 SerialOximeter * oxi = qobject_cast(lookupLoader(cms50f37_class_name)); loaders.push_back(oxi); } else if (ui->oximeterType->currentIndex() == 1) { // CMS50D+/E/F SerialOximeter * oxi = qobject_cast(lookupLoader(cms50_class_name)); loaders.push_back(oxi); } else if (ui->oximeterType->currentIndex() == 2) { // ChoiceMed SerialOximeter * oxi = qobject_cast(lookupLoader(md300w1_class_name)); loaders.push_back(oxi); } else return nullptr; updateStatus(tr("Scanning for compatible oximeters")); ui->progressBar->setMaximum(PORTSCAN_TIMEOUT); QTime time; time.start(); oximodule = nullptr; int elapsed=0; do { for (int i=0; i < loaders.size(); ++i) { SerialOximeter * oxi = loaders[i]; if (oxi->openDevice()) { oximodule = oxi; break; } } if (oximodule) break; QThread::msleep(delay); elapsed = time.elapsed(); ui->progressBar->setValue(elapsed); QApplication::processEvents(); if (!isVisible()) { return oximodule = nullptr; } } while (elapsed < PORTSCAN_TIMEOUT); if (!oximodule) { updateStatus(tr("Could not detect any connected oximeter devices.")); ui->retryButton->setVisible(true); return nullptr; } QString devicename = oximodule->getDeviceString(); if (devicename.isEmpty()) oximodule->loaderName(); updateStatus(tr("Connecting to %1 Oximeter").arg(devicename)); return oximodule; } void OximeterImport::on_directImportButton_clicked() { ui->informationButton->setVisible(false); ui->stackedWidget->setCurrentWidget(ui->directImportPage); qDebug() << "oximod - Direct Import button clicked" ; oximodule = detectOximeter(); if (!oximodule) return; if (p_profile->oxi->syncOximeterClock()) { oximodule->syncClock(); } QString model = oximodule->getModel(); QString user = oximodule->getUser(); QString devid = oximodule->getDeviceID(); if (oximodule->commandDriven()) { if (devid != ui->cms50DeviceName->text()) { if (ui->cms50CheckName->isChecked()) { mainwin->Notify(STR_MessageBox_Information, tr("Renaming this oximeter from '%1' to '%2'").arg(devid).arg(ui->cms50DeviceName->text())); oximodule->setDeviceID(ui->cms50DeviceName->text()); } else { QMessageBox::information(this, STR_MessageBox_Information, tr("Oximeter name is different.. If you only have one and are sharing it between profiles, set the name to the same on both profiles."), QMessageBox::Ok); } } } oximodule->resetDevice(); int session_count = oximodule->getSessionCount(); QDateTime startTime = QDateTime(); int duration = 0; qDebug() << "oximod - Session count: " << session_count; if (session_count > 1) { ui->stackedWidget->setCurrentWidget(ui->chooseSessionPage); ui->syncButton->setVisible(false); ui->chooseSessionButton->setVisible(true); ui->tableOxiSessions->clearContents(); QTableWidgetItem * item; ui->tableOxiSessions->setRowCount(session_count); ui->tableOxiSessions->setSelectionBehavior(QAbstractItemView::SelectRows); ui->tableOxiSessions->setColumnWidth(0,150); int h, m, s; for (int i=0; i< session_count; ++i) { duration = oximodule->getDuration(i); startTime = oximodule->getDateTime(i); h = duration / 3600; m = (duration / 60) % 60; s = duration % 60; item = new QTableWidgetItem(startTime.date().toString(Qt::SystemLocaleShortDate)+" "+startTime.time().toString("HH:mm:ss")); ui->tableOxiSessions->setItem(i, 0, item); // item->setData(Qt::UserRole+1, datetime); // item->setData(Qt::UserRole, i); // item->setData(Qt::UserRole+2, duration); item->setFlags(item->flags() & ~Qt::ItemIsEditable); item = new QTableWidgetItem(QString(). sprintf("%02i:%02i:%02i", h,m,s)); ui->tableOxiSessions->setItem(i, 1, item); item->setFlags(item->flags() & ~Qt::ItemIsEditable); item = new QTableWidgetItem(tr("\"%1\", session %2").arg(user).arg(i+1, 0)); ui->tableOxiSessions->setItem(i, 2, item); item->setFlags(item->flags() & ~Qt::ItemIsEditable); } selecting_session = true; ui->tableOxiSessions->selectRow(0); return; } else if (session_count > 0) { chosen_sessions.push_back(0); duration = oximodule->getDuration(0); startTime = oximodule->getDateTime(0); oximodule->setStartTime(startTime); qDebug() << "oximod - Session start time: " << startTime.toString(); } doImport(); } void OximeterImport::doImport() { qDebug() << "oximod - Starting doImport"; if (oximodule->commandDriven()) { if (chosen_sessions.size() == 0) { qDebug() << "oximod - Chosen session size is zero - quitting"; ui->connectLabel->setText("

    "+tr("Nothing to import")+"

    "); updateStatus(tr("Your oximeter did not have any valid sessions.")); ui->cancelButton->setText(tr("Close")); return; } ui->connectLabel->setText("

    "+tr("Waiting for %1 to start").arg(oximodule->getModel())+"

    "); updateStatus(tr("Waiting for the device to start the upload process...")); } else { ui->connectLabel->setText("

    "+tr("Select upload option on %1").arg(oximodule->loaderName())+"

    "); ui->logBox->appendPlainText(tr("You need to tell your oximeter to begin sending data to the computer.")); updateStatus(tr("Please connect your oximeter, enter it's menu and select upload to commence data transfer...")); } connect(oximodule, SIGNAL(updateProgress(int,int)), this, SLOT(doUpdateProgress(int,int))); oximodule->Open("import"); if (oximodule->commandDriven()) { int chosen = chosen_sessions.at(0); oximodule->getSessionData(chosen); } // Wait to start import streaming.. while (!oximodule->isImporting() && !oximodule->isAborted()) { // QThread::msleep(10); QApplication::processEvents(); if (!isVisible()) { disconnect(oximodule, SIGNAL(updateProgress(int,int)), this, SLOT(doUpdateProgress(int,int))); oximodule->abort(); break; } } if (!oximodule->isStreaming()) { disconnect(oximodule, SIGNAL(updateProgress(int,int)), this, SLOT(doUpdateProgress(int,int))); ui->retryButton->setVisible(true); ui->progressBar->setValue(0); oximodule->abort(); return; } ui->connectLabel->setText("

    "+tr("%1 device is uploading data...").arg(oximodule->loaderName())+"

    "); updateStatus(tr("Please wait until oximeter upload process completes. Do not unplug your oximeter.")); importMode = IM_RECORDING; // Can't abort this bit or the oximeter will get confused... ui->cancelButton->setVisible(false); connect(oximodule, SIGNAL(importComplete(SerialOximeter*)), this, SLOT(finishedImport(SerialOximeter*))); } void OximeterImport::finishedImport(SerialOximeter * oxi) { Q_UNUSED(oxi); qDebug() << "oximod - finished Import "; connect(oximodule, SIGNAL(importComplete(SerialOximeter*)), this, SLOT(finishedImport(SerialOximeter*))); ui->cancelButton->setVisible(true); disconnect(oximodule, SIGNAL(updateProgress(int,int)), this, SLOT(doUpdateProgress(int,int))); updateStatus(tr("Oximeter import completed..")); if (oximodule->oxisessions.size() > 1) { chooseSession(); } else { on_syncButton_clicked(); } } void OximeterImport::doUpdateProgress(int v, int t) { ui->progressBar->setMaximum(t); ui->progressBar->setValue(v); QApplication::processEvents(); } void OximeterImport::on_fileImportButton_clicked() { QString documentsFolder = (*p_profile)[STR_PREF_LastOximetryPath].toString(); if (documentsFolder.isEmpty()) documentsFolder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); qDebug() << "oximod - File Import button clicked"; QString filename = QFileDialog::getOpenFileName(nullptr , tr("Select a valid oximetry data file"), documentsFolder, tr("Oximetry Files (*.spo *.spor *.spo2 *.SpO2 *.dat)")); if (filename.isEmpty()) return; // Make sure filename dialog had time to close properly.. QApplication::processEvents(); qDebug() << "oximod - Chosen filename is " << filename; QList loaders = GetOxiLoaders(); bool success = false; oximodule = nullptr; Q_FOREACH(SerialOximeter * loader, loaders) { if (loader->Open(filename)) { success = true; oximodule = loader; break; } } if (!success) { QMessageBox::warning(this, STR_MessageBox_Warning, tr("No Oximetry module could parse the given file:")+QString("\n\n%1").arg(filename), QMessageBox::Ok); return; } qDebug() << "oximod - Using loader " << oximodule->loaderName(); ui->informationButton->setVisible(false); importMode = IM_FILE; if (oximodule->oxisessions.size() > 1) { chooseSession(); } else { // oximodule->setStartTime( ??? ); Nope, it was set in the loader module by the file import routime on_syncButton_clicked(); } QDir oxdir(filename.section("/",0,-1)); (*p_profile)[STR_PREF_LastOximetryPath] = oxdir.canonicalPath(); qDebug() << "oximod - Finished file import: Oximodule startTime is " << oximodule->startTime().toString("yyyy-MMM-dd HH:mm:ss"); } void OximeterImport::on_liveImportButton_clicked() { ui->informationButton->setVisible(false); qDebug() << "oximod - Live Import button clicked"; ui->stackedWidget->setCurrentWidget(ui->liveImportPage); ui->liveImportPage->layout()->addWidget(ui->progressBar); QApplication::processEvents(); liveView->setEmptyText(tr("Oximeter not detected")); liveView->setVisible(true); QApplication::processEvents(); SerialOximeter * oximodule = detectOximeter(); if (!oximodule) { updateStatus(tr("Couldn't access oximeter")); ui->retryButton->setVisible(true); ui->progressBar->setValue(0); return; } MachineInfo info = oximodule->newInfo(); Machine *mach = p_profile->CreateMachine(info); connect(oximodule, SIGNAL(updatePlethy(QByteArray)), this, SLOT(on_updatePlethy(QByteArray))); ui->liveConnectLabel->setText(tr("Live Oximetry Mode")); liveView->setEmptyText(tr("Starting up...")); updateStatus(tr("If you can still read this after a few seconds, cancel and try again")); ui->progressBar->hide(); liveView->update(); oximodule->Open("live"); ui->stopButton->setVisible(true); dummyday = new Day(); quint32 starttime = oximodule->startTime().toTime_t(); ti = qint64(starttime) * 1000L; start_ti = ti; session = new Session(mach, starttime); ELplethy = session->AddEventList(OXI_Plethy, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, 20); ELplethy->setFirst(start_ti); session->really_set_first(start_ti); session->setOpened(true); dummyday->addSession(session); plethyChart->setMinX(start_ti); plethyGraph->SetMinX(start_ti); liveView->setDay(dummyday); updateTimer.setParent(this); updateTimer.setInterval(50); updateTimer.start(); connect(&updateTimer, SIGNAL(timeout()), this, SLOT(updateLiveDisplay())); connect(ui->stopButton, SIGNAL(clicked()), this, SLOT(finishedRecording())); importMode = IM_LIVE; } void OximeterImport::finishedRecording() { qDebug() << "oximod - Finished Recording"; updateTimer.stop(); oximodule->closeDevice(); disconnect(&updateTimer, SIGNAL(timeout()), this, SLOT(updateLiveDisplay())); disconnect(ui->stopButton, SIGNAL(clicked()), this, SLOT(finishedRecording())); ui->stopButton->setVisible(false); ui->liveConnectLabel->setText(tr("Live Import Stopped")); liveView->setEmptyText(tr("Live Oximetry Stopped")); updateStatus(tr("Live Oximetry import has been stopped")); disconnect(oximodule, SIGNAL(updatePlethy(QByteArray)), this, SLOT(on_updatePlethy(QByteArray))); ui->syncButton->setVisible(true); plethyGraph->SetMinX(start_ti); liveView->SetXBounds(start_ti, ti, 0, true); plethyGraph->setBlockZoom(false); } void OximeterImport::on_retryButton_clicked() { qDebug() << "oximod - Retry button clicked"; if (ui->stackedWidget->currentWidget() == ui->directImportPage) { on_directImportButton_clicked(); } else if (ui->stackedWidget->currentWidget() == ui->liveImportPage) { on_liveImportButton_clicked(); } } void OximeterImport::on_stopButton_clicked() { qDebug() << "oximod - Stop button clicked"; if (oximodule) { oximodule->abort(); } } void OximeterImport::on_calendarWidget_clicked(const QDate &date) { qDebug() << "oximod - Calendar widget clicked " << date.toString("yyyy-MMM-dd"); if (ui->radioSyncCPAP->isChecked()) { qDebug() << "oximod - Syncing to a CPAP session"; Day * day = p_profile->GetGoodDay(date, MT_CPAP); sessbar->clear(); if (day) { QDateTime time=QDateTime::fromMSecsSinceEpoch(day->first(), Qt::LocalTime); sessbar->clear(); QList colors; colors.push_back("#ffffe0"); colors.push_back("#ffe0ff"); colors.push_back("#e0ffff"); QList::iterator i; int j=0; for (i=day->begin(); i != day->end(); ++i) { sessbar->add((*i),colors.at(j++ % colors.size())); } sessbar->setVisible(true); ui->sessbarLabel->setText(tr("%1 session(s) on %2, starting at %3").arg(day->size()).arg(time.date().toString(Qt::SystemLocaleLongDate)).arg(time.time().toString("hh:mm:ssap"))); sessbar->setSelected(0); ui->dateTimeEdit->setDateTime(time); } else { ui->sessbarLabel->setText(tr("No CPAP data available on %1").arg(date.toString(Qt::SystemLocaleLongDate))); qDebug() << "oximod - Using oximeter time " << oximodule->startTime().toString("yyyy-MMM-dd hh:mm:ssap") << "on date " << date.toString(Qt::SystemLocaleLongDate); ui->dateTimeEdit->setDateTime(QDateTime(date,oximodule->startTime().time())); } sessbar->update(); } else if (ui->radioSyncOximeter) { qDebug() << "oximod - Using oximeter date and time"; ui->sessbarLabel->setText(QString("%1").arg(date.toString(Qt::SystemLocaleLongDate))); ui->dateTimeEdit->setDateTime(QDateTime(date, ui->dateTimeEdit->dateTime().time())); } } void OximeterImport::on_calendarWidget_selectionChanged() { on_calendarWidget_clicked(ui->calendarWidget->selectedDate()); } void OximeterImport::onSessionSelected(Session * session) { QDateTime time=QDateTime::fromMSecsSinceEpoch(session->first(), Qt::LocalTime); qDebug() << "oximod - Selected session starts at " << time.toString("yyyy-MMM-dd hh:mm:ssap"); ui->dateTimeEdit->setDateTime(time); } void OximeterImport::on_sessionBackButton_clicked() { qDebug() << "oximod - Session Back button clicked"; int idx = (sessbar->selected()-1); if (idx >= 0) { sessbar->setSelected(idx); QDateTime datetime = QDateTime::fromMSecsSinceEpoch(sessbar->session(idx)->first(), Qt::LocalTime); ui->dateTimeEdit->setDateTime(datetime); sessbar->update(); } } void OximeterImport::on_sessionForwardButton_clicked() { qDebug() << "oximod - Session Forward button clicked"; int idx = (sessbar->selected()+1); if (idx < sessbar->count()) { sessbar->setSelected(idx); QDateTime datetime = QDateTime::fromMSecsSinceEpoch(sessbar->session(idx)->first(), Qt::LocalTime); ui->dateTimeEdit->setDateTime(datetime); sessbar->update(); } } void OximeterImport::on_radioSyncCPAP_clicked() { qDebug() << "oximod - Use CPAP Time button clicked"; on_calendarWidget_clicked(oximodule->startTime().date()); ui->syncCPAPGroup->setVisible(true); } void OximeterImport::on_radioSyncOximeter_clicked() { qDebug() << "oximod - Use OximeterTime button clicked"; ui->syncCPAPGroup->setVisible(false); if ( oximodule ) { if (oximodule->isStartTimeValid()) { qDebug() << "oximod - Oximeter time is " << oximodule->startTime().toString("yyyy-MMM-dd HH:mm:ssap"); ui->calendarWidget->setSelectedDate(oximodule->startTime().date()); ui->dateTimeEdit->setDateTime(oximodule->startTime()); } else { qDebug() << "oximod - Oximeter time is faked" << oximodule->startTime().toString("yyyy-MMM-dd HH:mm:ssap"); ui->calendarWidget->setSelectedDate(oximodule->startTime().date()); ui->dateTimeEdit->setDateTime(oximodule->startTime()); } } } void OximeterImport::on_updatePlethy(QByteArray plethy) { if (!session) { return; } int size = plethy.size(); quint64 dur = qint64(size) * 20L; ELplethy->AddWaveform(ti, plethy.data(), size, dur); ti += dur; } void OximeterImport::updateLiveDisplay() { if (!session) { return; } if (ui->showLiveGraphs->isChecked()) { qint64 sti = ti - 20000; plethyChart->setMinY(ELplethy->Min()); plethyChart->setMaxY(ELplethy->Max()); plethyGraph->SetMinY(ELplethy->Min()); plethyGraph->SetMaxY(ELplethy->Max()); plethyGraph->SetMinX(sti); plethyGraph->SetMaxX(ti); plethyGraph->setBlockZoom(true); ELplethy->setLast(ti); session->really_set_last(ti); //liveView->SetXBounds(sti, ti, 0, true); session->setMin(OXI_Plethy, ELplethy->Min()); session->setMax(OXI_Plethy, ELplethy->Max()); session->setLast(OXI_Plethy, ti); session->setCount(OXI_Plethy, ELplethy->count()); for (int i = 0; i < liveView->size(); i++) { (*liveView)[i]->SetXBounds(sti, ti); } liveView->updateScale(); liveView->redraw(); } if (!oximodule->oxirec) return; int size = oximodule->oxirec->size(); if (size > 0) { int i = oximodule->startTime().secsTo(QDateTime::currentDateTime()); int seconds = i % 60; int minutes = (i / 60) % 60; int hours = i / 3600; size--; bool datagood = (*(oximodule->oxirec))[size].pulse > 0; if (datagood & (pulse <= 0)) { QString STR_recording = tr("Recording..."); updateStatus(STR_recording); liveView->setEmptyText(STR_recording); if (!ui->showLiveGraphs->isChecked()) { liveView->redraw(); } } else if (!datagood & (pulse != 0)) { QString STR_nofinger = tr("Finger not detected"); updateStatus(STR_nofinger); liveView->setEmptyText(STR_nofinger); if (!ui->showLiveGraphs->isChecked()) { liveView->redraw(); } } pulse = (*(oximodule->oxirec))[size].pulse; spo2 = (*(oximodule->oxirec))[size].spo2; if (pulse > 0) { ui->pulseDisplay->display(QString().sprintf("%3i", pulse)); } else { ui->pulseDisplay->display("---"); } if (spo2 > 0) { ui->spo2Display->display(QString().sprintf("%2i", spo2)); } else { ui->spo2Display->display("--"); } ui->lcdDuration->display(QString().sprintf("%02i:%02i:%02i",hours, minutes, seconds)); } } void OximeterImport::on_cancelButton_clicked() { qDebug() << "oximod - Cancel button clicked"; if (oximodule && oximodule->isStreaming()) { oximodule->closeDevice(); oximodule->trashRecords(); } reject(); } void OximeterImport::on_showLiveGraphs_clicked(bool checked) { if (checked) { updateTimer.setInterval(50); } else { // Don't need to call the timer so often.. Save a little CPU.. updateTimer.setInterval(500); } plethyGraph->setVisible(checked); liveView->redraw(); } void OximeterImport::on_skipWelcomeCheckbox_clicked(bool checked) { p_profile->oxi->setSkipOxiIntroScreen(checked); } void OximeterImport::on_informationButton_clicked() { ui->stackedWidget->setCurrentWidget(ui->welcomePage); ui->nextButton->setVisible(true); ui->informationButton->setVisible(false); } void OximeterImport::on_syncButton_clicked() { qDebug() << "oximod - Sync button clicked"; if (oximodule == nullptr) { qCritical() << "oximod - OximeterImport::on_syncButton_clicked called when oximodule is null"; return; } qDebug() << "oximod Start Time is " << oximodule->startTime().toString("yyyy-MMM-dd HH.mm.ss") << "Duration: " << oximodule->getDuration(/* dummy */ 0 ); ui->stackedWidget->setCurrentWidget(ui->syncPage); ui->syncButton->setVisible(false); ui->saveButton->setVisible(true); QDate first = p_profile->FirstDay(); QDate last = p_profile->LastDay(); QDate oxidate = oximodule->startTime().date(); qDebug() << "oximod - start date is " << oxidate.toString("yyyy-MMM-dd"); if ((oxidate >= first) && (oxidate <= last)) { // TODO: think this through better.. do I need to pick the day before? ui->calendarWidget->setMinimumDate(first); ui->calendarWidget->setMaximumDate(last); ui->calendarWidget->setCurrentPage(oxidate.year(), oxidate.month()); ui->calendarWidget->setSelectedDate(oxidate); on_calendarWidget_clicked(oximodule->startTime().date()); } else { // No CPAP sessions to sync it to.. kill the CPAP sync option on_calendarWidget_clicked(last); ui->calendarWidget->setCurrentPage(oxidate.year(), oxidate.month()); ui->calendarWidget->setSelectedDate(oxidate); } ui->radioSyncOximeter->setChecked(true); on_radioSyncOximeter_clicked(); if (importMode == IM_LIVE) { // Live Recording ui->labelSyncOximeter->setText(tr("I want to use the time my computer recorded for this live oximetry session.")); } else if (!oximodule->isStartTimeValid()) { // Oximeter doesn't provide a clock ui->labelSyncOximeter->setText(tr("I need to set the time manually, because my oximeter doesn't have an internal clock.")); } } void OximeterImport::on_saveButton_clicked() { qDebug() << "oximod - Oximeter Save button clicked"; if (!oximodule) return; QVector * oxirec = nullptr; if (!oximodule->oxisessions.contains(oximodule->startTime())) { QMessageBox::warning(this, STR_MessageBox_Error, tr("Something went wrong getting session data"), QMessageBox::Ok); reject(); return; } oxirec = oximodule->oxisessions[oximodule->startTime()]; if (oxirec->size() < 10) { qDebug() << "oximod - oxirec size less than 10 - quitting"; return; } // this can move to SerialOximeter class process function... MachineInfo info = oximodule->newInfo(); Machine * mach = p_profile->CreateMachine(info); SessionID sid = ui->dateTimeEdit->dateTime().toUTC().toTime_t(); quint64 start = quint64(sid) * 1000L; if (!session) { session = new Session(mach, sid); session->really_set_first(start); } else { // Live recording... if (dummyday) { dummyday->removeSession(session); delete dummyday; dummyday = nullptr; } session->SetSessionID(sid); session->really_set_first(start); ELplethy->setFirst(start); session->setFirst(OXI_Plethy, start); quint64 duration = start + (ELplethy->count() * ELplethy->rate()); quint64 end = start + duration; session->setLast(OXI_Plethy, end); session->count(OXI_Plethy); session->Min(OXI_Plethy); session->Max(OXI_Plethy); } EventList * ELpulse = nullptr; EventList * ELspo2 = nullptr; EventList * ELperf = nullptr; quint16 lastpulse = 0; quint16 lastspo2 = 0; quint16 lastperf = 0; quint16 lastgoodpulse = 0; quint16 lastgoodspo2 = 0; quint16 lastgoodperf = 0; bool haveperf = oximodule->havePerfIndex(); quint64 ti = start; qint64 step = (importMode == IM_LIVE) ? oximodule->liveResolution() : oximodule->importResolution(); int size = oxirec->size(); // why was I skipping the first sample? not priming it anymore.. qDebug() << "oximod = Creating event list for pulse and O2 saturation"; for (int i=0; i < size; ++i) { OxiRecord * rec = &(*oxirec)[i]; if (rec->pulse > 0) { if (lastpulse == 0) { ELpulse = session->AddEventList(OXI_Pulse, EVL_Event); } if (lastpulse != rec->pulse) { if (lastpulse > 0) { ELpulse->AddEvent(ti, lastpulse); } ELpulse->AddEvent(ti, rec->pulse); } lastgoodpulse = rec->pulse; } else { // end section properly if (lastgoodpulse > 0) { ELpulse->AddEvent(ti, lastpulse); session->setLast(OXI_Pulse, ti); lastgoodpulse = 0; } } lastpulse = rec->pulse; if (rec->spo2 > 0) { if (lastspo2 == 0) { ELspo2 = session->AddEventList(OXI_SPO2, EVL_Event); } if (lastspo2 != rec->spo2) { if (lastspo2 > 0) { ELspo2->AddEvent(ti, lastspo2); } ELspo2->AddEvent(ti, rec->spo2); } lastgoodspo2 = rec->spo2; } else { // end section properly if (lastgoodspo2 > 0) { ELspo2->AddEvent(ti, lastspo2); session->setLast(OXI_SPO2, ti); lastgoodspo2 = 0; } } lastspo2 = rec->spo2; if (haveperf) { // Perfusion Index if (rec->perf > 0) { if (lastperf == 0) { ELperf = session->AddEventList(OXI_Perf, EVL_Event, 0.01f); } if (lastperf != rec->perf) { if (lastperf > 0) { ELperf->AddEvent(ti, lastperf); } ELperf->AddEvent(ti, rec->perf); } lastgoodperf = rec->perf; } else { // end section properly if (lastgoodperf > 0) { ELperf->AddEvent(ti, lastperf); session->setLast(OXI_Perf, ti); lastgoodperf = 0; } } lastperf = rec->perf; } ti += step; } ti -= step; if (ELpulse && (lastpulse > 0)) { ELpulse->AddEvent(ti, lastpulse); session->setLast(OXI_Pulse, ti); } if (ELspo2 && (lastspo2 > 0)) { ELspo2->AddEvent(ti, lastspo2); session->setLast(OXI_SPO2, ti); } if (haveperf && ELperf && lastperf > 0) { ELperf->AddEvent(ti, lastperf); session->setLast(OXI_Perf, ti); } if (haveperf) { session->first(OXI_Perf); session->last(OXI_Perf); session->count(OXI_Perf); session->Min(OXI_Perf); session->Max(OXI_Perf); } calcSPO2Drop(session); calcPulseChange(session); qDebug() << "oximod - Setting up machine and session"; mach->setModel(oximodule->getModel()); mach->setBrand(oximodule->getVendor()); session->first(OXI_Pulse); session->first(OXI_SPO2); session->last(OXI_Pulse); session->last(OXI_SPO2); session->first(OXI_PulseChange); session->first(OXI_SPO2Drop); session->last(OXI_PulseChange); session->last(OXI_SPO2Drop); session->cph(OXI_PulseChange); session->sph(OXI_PulseChange); session->cph(OXI_SPO2Drop); session->sph(OXI_SPO2Drop); session->count(OXI_Pulse); session->count(OXI_SPO2); session->count(OXI_PulseChange); session->count(OXI_SPO2Drop); session->Min(OXI_Pulse); session->Min(OXI_SPO2); session->Max(OXI_Pulse); session->Max(OXI_SPO2); session->really_set_last(ti); session->SetChanged(true); session->setOpened(true); qDebug() << "oximod - Adding session to machine"; mach->AddSession(session); qDebug() << "oximod - Saving machine"; mach->Save(); mach->SaveSummaryCache(); p_profile->StoreMachines(); mainwin->EnableTabs(true); // somebody has to do it... mainwin->getDaily()->LoadDate(mainwin->getDaily()->getDate()); mainwin->getOverview()->ReloadGraphs(); ELplethy = nullptr; session = nullptr; oximodule->trashRecords(); accept(); } void OximeterImport::chooseSession() { qDebug() << "oximod - Oximeter Choose Session called"; selecting_session = false; ui->stackedWidget->setCurrentWidget(ui->chooseSessionPage); ui->syncButton->setVisible(false); ui->chooseSessionButton->setVisible(true); QMap *>::iterator it; ui->tableOxiSessions->clearContents(); int row = 0; QTableWidgetItem * item; QVector * oxirec; ui->tableOxiSessions->setRowCount(oximodule->oxisessions.size()); ui->tableOxiSessions->setSelectionBehavior(QAbstractItemView::SelectRows); for (it = oximodule->oxisessions.begin(); it != oximodule->oxisessions.end(); ++it) { const QDateTime & key = it.key(); oxirec = it.value(); item = new QTableWidgetItem(key.toString(Qt::ISODate)); ui->tableOxiSessions->setItem(row, 0, item); item->setFlags(item->flags() & ~Qt::ItemIsEditable); long int duration = oxirec->size() * oximodule->importResolution() / 1000L; int h = duration / 3600; int m = (duration / 60) % 60; int s = duration % 60; item = new QTableWidgetItem(QString(). sprintf("%02i:%02i:%02i", h,m,s)); ui->tableOxiSessions->setItem(row, 1, item); item->setFlags(item->flags() & ~Qt::ItemIsEditable); item = new QTableWidgetItem(tr("Oximeter Session %1").arg(row+1, 0)); ui->tableOxiSessions->setItem(row, 2, item); item->setFlags(item->flags() & ~Qt::ItemIsEditable); row++; } ui->tableOxiSessions->selectRow(0); } void OximeterImport::on_chooseSessionButton_clicked() { qDebug() << "oximod - Chosen session clicked"; ui->chooseSessionButton->setVisible(false); QTableWidgetItem * item_0 = ui->tableOxiSessions->item(ui->tableOxiSessions->currentRow(),0); QTableWidgetItem * item_1 = ui->tableOxiSessions->item(ui->tableOxiSessions->currentRow(),1); if (!item_0 || !item_1) return; QDateTime datetime = item_0->data(Qt::DisplayRole).toDateTime(); oximodule->setStartTime(datetime); oximodule->setDuration(item_1->data(Qt::DisplayRole).toInt()); if (selecting_session) { ui->stackedWidget->setCurrentWidget(ui->directImportPage); chosen_sessions.push_back(ui->tableOxiSessions->currentRow()); // go back and start import doImport(); } else { on_syncButton_clicked(); } } void OximeterImport::setInformation() { QString html="" "" "

    " +tr("Welcome to the Oximeter Import Wizard")+"

    " "

    " +tr("Pulse Oximeters are medical devices used to measure blood oxygen saturation. During extended Apnea events and abnormal breathing patterns, blood oxygen saturation levels can drop significantly, and can indicate issues that need medical attention.")+"

    " "

    " +tr("OSCAR gives you the ability to track Oximetry data alongside CPAP session data, which can give valuable insight into the effectiveness of CPAP treatment. It will also work standalone with your Pulse Oximeter, allowing you to store, track and review your recorded data.")+"

    " "

    " +tr("OSCAR is currently compatible with Contec CMS50D+, CMS50E, CMS50F and CMS50I serial oximeters.
    (Note: Direct importing from bluetooth models is probably not possible yet)")+"

    " "

    " +tr("You may wish to note, other companies, such as Pulox, simply rebadge Contec CMS50's under new names, such as the Pulox PO-200, PO-300, PO-400. These should also work.")+"

    " "

    " +tr("It also can read from ChoiceMMed MD300W1 oximeter .dat files.")+"

    " "

    " ""+tr("Please remember:")+" " "" +tr("If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding!")+"

    " "

    " ""+tr("Important Notes:")+" " +tr("For OSCAR to be able to locate and read directly from your Oximeter device, you need to ensure the correct device drivers (eg. USB to Serial UART) have been installed on your computer. For more information about this, %1click here%2.").arg("").arg("")+"

    " "

    " +tr("Contec CMS50D+ devices do not have an internal clock, and do not record a starting time. If you do not have a CPAP session to link a recording to, you will have to enter the start time manually after the import process is completed.")+"

    " "

    " +tr("Even for devices with an internal clock, it is still recommended to get into the habit of starting oximeter records at the same time as CPAP sessions, because CPAP internal clocks tend to drift over time, and not all can be reset easily.")+"

    "; ui->textBrowser->setHtml(html); ui->textBrowser->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->textBrowser->setOpenExternalLinks(true); } void OximeterImport::on_oximeterType_currentIndexChanged(int index) { switch (index) { case 0: // New CMS50's version 7 protocol ui->directImportButton->setEnabled(true); ui->liveImportButton->setEnabled(false); ui->fileImportButton->setEnabled(true); ui->oldCMS50specific->setVisible(false); ui->newCMS50settingsPanel->setVisible(true); break; case 1: // Old CMS50's ui->directImportButton->setEnabled(true); ui->liveImportButton->setEnabled(true); ui->fileImportButton->setEnabled(true); ui->oldCMS50specific->setVisible(true); ui->newCMS50settingsPanel->setVisible(false); break; default: // ChoiceMMed oximeters, and others? ui->directImportButton->setEnabled(false); ui->liveImportButton->setEnabled(false); ui->fileImportButton->setEnabled(true); ui->oldCMS50specific->setVisible(false); ui->newCMS50settingsPanel->setVisible(false); } p_profile->oxi->setOximeterType(index); } void OximeterImport::on_cms50CheckName_clicked(bool checked) { ui->cms50DeviceName->setEnabled(checked); if (checked) { ui->cms50DeviceName->setFocus(); ui->cms50DeviceName->setCursorPosition(0); } } void OximeterImport::on_cms50SyncTime_clicked(bool checked) { p_profile->oxi->setSyncOximeterClock(checked); } void OximeterImport::on_cms50DeviceName_textEdited(const QString &arg1) { p_profile->oxi->setDefaultDevice(arg1); } void OximeterImport::on_textBrowser_anchorClicked(const QUrl &arg1) { QDesktopServices::openUrl(arg1); } OSCAR-code-v1.3.1/oscar/oximeterimport.h000066400000000000000000000052561417327530600200500ustar00rootroot00000000000000/* Oximeter Import Wizard Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef OXIMETERIMPORT_H #define OXIMETERIMPORT_H #include #include "Graphs/gGraphView.h" #include "Graphs/gLineChart.h" #include "SleepLib/serialoximeter.h" #include "sessionbar.h" namespace Ui { class OximeterImport; } enum OximeterImportMode { IM_UNDEFINED = 0, IM_LIVE, IM_RECORDING, IM_FILE }; class OximeterImport : public QDialog { Q_OBJECT public: explicit OximeterImport(QWidget *parent = 0); ~OximeterImport(); private slots: void on_nextButton_clicked(); void on_directImportButton_clicked(); void doUpdateProgress(int, int); void on_fileImportButton_clicked(); void on_liveImportButton_clicked(); void on_retryButton_clicked(); void on_stopButton_clicked(); void on_calendarWidget_clicked(const QDate &date); void on_calendarWidget_selectionChanged(); void onSessionSelected(Session * session); void on_sessionBackButton_clicked(); void on_sessionForwardButton_clicked(); void on_radioSyncCPAP_clicked(); void on_radioSyncOximeter_clicked(); void on_cancelButton_clicked(); void on_showLiveGraphs_clicked(bool checked); void on_skipWelcomeCheckbox_clicked(bool checked); void on_informationButton_clicked(); void on_syncButton_clicked(); void on_saveButton_clicked(); void chooseSession(); void on_chooseSessionButton_clicked(); void on_oximeterType_currentIndexChanged(int index); void on_cms50CheckName_clicked(bool checked); void on_cms50SyncTime_clicked(bool checked); void on_cms50DeviceName_textEdited(const QString &arg1); void on_textBrowser_anchorClicked(const QUrl &arg1); protected slots: void on_updatePlethy(QByteArray plethy); void finishedRecording(); void finishedImport(SerialOximeter*); void updateLiveDisplay(); protected: SerialOximeter * detectOximeter(); void updateStatus(QString msg); void doImport(); void setInformation(); private: Ui::OximeterImport *ui; SerialOximeter * oximodule; gGraphView * liveView; gGraph * plethyGraph; Session * session; Day * dummyday; gLineChart * plethyChart; SessionBar * sessbar; EventList * ELplethy; qint64 start_ti, ti; QTimer updateTimer; OximeterImportMode importMode; int pulse; int spo2; bool selecting_session; QList chosen_sessions; }; #endif // OXIMETERIMPORT_H OSCAR-code-v1.3.1/oscar/oximeterimport.ui000066400000000000000000002062461417327530600202400ustar00rootroot00000000000000 OximeterImport 0 0 1370 748 Dialog 12 12 12 12 0 0 Qt::Horizontal QSizePolicy::Fixed 200 20 24 Oximeter Import Wizard Qt::AlignCenter 4 255 255 255 120 154 188 255 255 255 120 154 188 255 255 255 120 154 188 255 255 255 120 154 188 255 255 255 120 154 188 255 255 255 120 154 188 255 255 255 120 154 188 255 255 255 120 154 188 255 255 255 120 154 188 QFrame { background: qlineargradient( x1:0 y1:0, x2:1 y2:0, stop:0 white, stop:1 #789abc); border: 1px solid gray; border-radius: 15px; } QFrame::StyledPanel QFrame::Raised 0 0 0 0 Qt::Vertical QSizePolicy::Preferred 20 10 border: 0px solid gray; border-radius: 0px; :/icons/oximeter.png false Qt::AlignHCenter|Qt::AlignTop Qt::Vertical 20 40 0 4 4 4 4 background: white; border: 1px solid gray; border-radius: 15px; QFrame::StyledPanel QFrame::Raised border: 0px; border-radius: 0px; QFrame::NoFrame 0 Qt::ScrollBarAlwaysOn QAbstractScrollArea::AdjustToContents QTextEdit::AutoAll QTextEdit::WidgetWidth 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'.Lucida Grande UI'; font-size:13pt;"><br /></p></body></html> true false Qt::Horizontal 40 20 Skip this page next time. QPushButton { background: qlineargradient( x1:0 y1:0, x2:1 y2:0, stop:0 white, stop:1 #eeeeee); border: 1px solid gray; border-radius: 10px; } QPushButton:hover { border: 2px solid #56789a; border-radius: 10px; background: white; } QPushButton:pressed { border: 2px solid #56789a; border-radius: 10px; background: qlineargradient( x1:0 y1:0, x2:1 y2:0, stop:0 white, stop:1 #cccccc); } 4 12 4 12 4 0 0 14 <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Please note: </span><span style=" font-style:italic;">First select your correct oximeter type from the pull-down menu below.</span></p></body></html> Qt::AlignHCenter|Qt::AlignTop true Qt::Vertical QSizePolicy::Fixed 0 10 0 0 18 QFrame::NoFrame Where would you like to import from? Qt::AlignCenter Qt::Vertical QSizePolicy::Fixed 20 10 0 Qt::Horizontal 40 20 <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">FIRST Select your Oximeter from these groups:</span></p></body></html> 12 QComboBox::AdjustToContents CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 CMS50D+/E/F, Pulox PO-200/300 ChoiceMMed MD300W1 Qt::Horizontal 40 20 0 0 11 true true CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. Qt::AlignCenter 0 0 true 4 4 4 4 <html><head/><body><p>If enabled, OSCAR will automatically reset your CMS50's internal clock using your computers current time.</p></body></html> Set device date/time <html><head/><body><p>Check to enable updating the device identifier next import, which is useful for those who have multiple oximeters lying around.</p></body></html> Set device identifier 0 0 100 16777215 <html><head/><body><p>Here you can enter a 7 character name for this oximeter.</p></body></html> nnnnnnn true 7 Qt::LogicalMoveStyle false Qt::Horizontal 40 20 <html><head/><body><p>This option will erase the imported session from your oximeter after import has completed. </p><p>Use with caution, because if something goes wrong before OSCAR saves your session, you can't get it back.</p></body></html> Erase session after successful upload Qt::Vertical QSizePolicy::Fixed 20 10 <html><head/><body><p>This option allows you to import (via cable) from your oximeters internal recordings.</p><p>After selecting on this option, old Contec oximeters will require you to use the device's menu to initiate the upload.</p></body></html> Import directly from a recording on a device :/icons/oximeter.png:/icons/oximeter.png 64 64 false Qt::Vertical QSizePolicy::Fixed 20 10 <html><head/><body><p>If you don't mind a being attached to a running computer overnight, this option provide a useful plethysomogram graph, which gives an indication of heart rhythm, on top of the normal oximetry readings.</p></body></html> Record attached to computer overnight (provides plethysomogram) :/icons/cms50f.png:/icons/cms50f.png 64 64 Qt::Vertical QSizePolicy::Fixed 20 10 <html><head/><body><p>This option allows you to import from data files created by software that came with your Pulse Oximeter, such as SpO2Review.</p></body></html> Import from a datafile saved by another program, like SpO2Review :/icons/save.png:/icons/save.png 64 64 false false Qt::Vertical QSizePolicy::Expanding 20 20 14 <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Reminder for CPAP users: </span><span style=" color:#fb0000;">Did you remember to import your CPAP sessions first?<br/></span>If you forget, you won't have a valid time to sync this oximetry session to.<br/>To a ensure good sync between devices, always try to start both at the same time.</p></body></html> Qt::AlignHCenter|Qt::AlignTop true Qt::Vertical 20 40 21 Please connect your oximeter device Qt::AlignCenter 15 If you can read this, you likely have your oximeter type set wrong in preferences. Qt::AlignCenter 0 0 21 Please connect your oximeter device, turn it on, and enter the menu Qt::AlignCenter 0 0 QFrame::StyledPanel QFrame::Raised Press Start to commence recording Qt::AlignCenter 0 0 QFrame::StyledPanel QFrame::Raised 6 6 6 6 6 true Show Live Graphs true Qt::Horizontal 40 20 true Duration Qt::AlignHCenter|Qt::AlignTop 200 50 QFrame::StyledPanel 8 true <html><head/><body><p>SpO<span style=" vertical-align:sub;">2</span> %</p></body></html> Qt::AlignHCenter|Qt::AlignTop 0 0 60 50 0 106 255 0 106 255 127 127 127 Qt::LeftToRight QFrame::StyledPanel 2 true Pulse Rate Qt::AlignHCenter|Qt::AlignTop 0 0 80 50 255 0 0 255 0 0 159 0 85 QFrame::StyledPanel false 3 0 0 21 Multiple Sessions Detected Qt::AlignCenter 0 0 Please choose which one you want to import into OSCAR Qt::AlignCenter true QAbstractItemView::SingleSelection QAbstractItemView::SelectRows true Start Time Duration Details 21 Import Completed. When did the recording start? Qt::AlignCenter 0 0 0 200 Day recording (normally would have) started 6 0 2 0 0 0 0 1 0 0 200 Oximeter Starting time 0 0 I want to use the time reported by my oximeter's built in clock. true 0 0 I started this oximeter recording at (or near) the same time as a session on my CPAP machine. true 0 0 0 0 <html><head/><body><p>OSCAR needs a starting time to know where to save this oximetry session to.</p><p>Choose one of the following options:</p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true 0 0 0 0 <html><head/><body><p>Note: Syncing to CPAP session starting time will always be more accurate.</p></body></html> true 0 0 Choose CPAP session to sync to: 6 6 6 6 Qt::AlignCenter 0 0 0 ... :/icons/back.png:/icons/back.png 0 40 16777215 40 QFrame::StyledPanel QFrame::Raised 0 0 ... :/icons/forward.png:/icons/forward.png Qt::Horizontal 40 20 0 0 14 You can manually adjust the time here if required: 0 0 40 QAbstractSpinBox::UpDownArrows HH:mm:ssap Qt::Horizontal 40 20 Qt::Vertical QSizePolicy::Expanding 20 40 QFrame::NoFrame QFrame::Raised Qt::Horizontal 40 20 0 0 160 0 &Information Page 0 0 160 0 &Cancel 0 0 160 0 &Retry 0 0 160 0 &Choose Session 0 0 160 0 &End Recording 0 0 160 0 &Sync and Save 0 0 160 0 &Save and Finish 0 0 160 0 &Start OSCAR-code-v1.3.1/oscar/oximetry.ui000066400000000000000000000340661417327530600170300ustar00rootroot00000000000000 Oximetry 0 0 791 373 Form 0 0 0 0 0 0 0 0 0 QFrame::StyledPanel QFrame::Raised 0 2 0 2 0 4 0 0 0 Date 0 0 d/MM/yy h:mm:ss AP 0 0 QToolButton { background: transparent; border-radius: 8px; border: 2px solid transparent; } QToolButton:hover { border: 2px solid #456789; } QToolButton:pressed { border: 2px solid #456789; background-color: #89abcd; } R&eset Qt::Horizontal QSizePolicy::Fixed 10 20 0 0 <html><head/><body><p>SpO<span style=" vertical-align:sub;">2</span></p></body></html> 0 0 85 0 255 85 0 255 118 118 117 QFrame::Sunken 3 QLCDNumber::Flat Qt::Horizontal QSizePolicy::Fixed 5 20 0 0 Pulse 0 0 255 0 0 255 0 0 118 118 117 QFrame::Box QFrame::Sunken 3 QLCDNumber::Flat Qt::Horizontal 40 20 0 0 QToolButton { background: transparent; border-radius: 8px; border: 2px solid transparent; } QToolButton:hover { border: 2px solid #456789; } QToolButton:pressed { border: 2px solid #456789; background-color: #89abcd; } ... :/icons/save.png:/icons/save.png 0 0 &Open .spo/R File false false 0 0 Serial &Import 0 0 &Start Live true QLayout::SetMinimumSize 0 0 Serial Port 0 0 120 0 0 0 &Rescan Ports Qt::Horizontal 40 20 OSCAR-code-v1.3.1/oscar/pch.h000066400000000000000000000020001417327530600155130ustar00rootroot00000000000000#if defined __cplusplus /* Add C++ includes here */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif OSCAR-code-v1.3.1/oscar/preferencesdialog.cpp000066400000000000000000001450561417327530600210000ustar00rootroot00000000000000/* OSCAR Preferences Dialog Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include //#include #include #include #include "preferencesdialog.h" #include "version.h" #include #include #include "ui_preferencesdialog.h" #include "SleepLib/machine_common.h" extern QFont *defaultfont; extern QFont *mediumfont; extern QFont *bigfont; extern MainWindow *mainwin; typedef QMessageBox::StandardButton StandardButton; typedef QMessageBox::StandardButtons StandardButtons; QHash channeltype; PreferencesDialog::PreferencesDialog(QWidget *parent, Profile *_profile) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint), ui(new Ui::PreferencesDialog), profile(_profile) { ui->setupUi(this); channeltype.clear(); channeltype[schema::FLAG] = tr("Flag"); channeltype[schema::MINOR_FLAG] = tr("Minor Flag"); channeltype[schema::SPAN] = tr("Span"); channeltype[schema::UNKNOWN] = tr("Always Minor"); bool haveResMed = false; QList machines = profile->GetMachines(MT_CPAP); // qDebug() << "Machile list size is" << machines.size(); if ( machines.size() > 0 ) { for (QList::iterator it = machines.begin(); it != machines.end(); ++it) { const QString & mclass=(*it)->loaderName(); if (mclass == STR_MACH_ResMed) { haveResMed = true; break; } } } else { if (QMessageBox::question(this, tr("No CPAP machines detected"), tr("Will you be using a ResMed brand machine?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes ) haveResMed = true; } #ifdef LOCK_RESMED_SESSIONS // Remove access to session splitting options and show ResMed users a notice instead ui->ResMedWarning->setText(tr("

    Please Note: OSCAR's advanced session splitting capabilities are not possible with ResMed machines due to a limitation in the way their settings and summary data is stored, and therefore they have been disabled for this profile.

    On ResMed machines, days will split at noon like in ResMed's commercial software.

    ")); ui->ResMedWarning->setVisible(haveResMed); if (haveResMed) { profile->forceResmedPrefs(); ui->sessionSplitWidget->setVisible(!haveResMed); ui->prefCalcMax->setEnabled(false); ui->prefCalcMiddle->setEnabled(false); ui->prefCalcPercentile->setEnabled(false); ui->showUnknownFlags->setEnabled(false); ui->calculateUnintentionalLeaks->setEnabled(false); ui->createSDBackups->setChecked(true); ui->createSDBackups->setEnabled(false); } ui->resmedPrefCalcsNotice->setVisible(haveResMed); #endif int gfxEngine=(int)currentGFXEngine(); int selIdx = 0; for (int j=0, i=0; i<=(int)MaxGFXEngine; ++i) { if (gfxEgnineIsSupported((GFXEngine) i)) { ui->gfxEngineCombo->addItem(GFXEngineNames[i], i); if (i==gfxEngine) { selIdx = j; } ++j; } } ui->gfxEngineCombo->setCurrentIndex(selIdx); ui->culminativeIndices->setEnabled(false); QLocale locale = QLocale::system(); QString shortformat = locale.dateFormat(QLocale::ShortFormat); if (!shortformat.toLower().contains("yyyy")) { shortformat.replace("yy", "yyyy"); } // Qt::DayOfWeek dow = firstDayOfWeekFromLocale(); // QTextCharFormat format = ui->startedUsingMask->calendarWidget()->weekdayTextFormat(Qt::Saturday); // format.setForeground(QBrush(Qt::black, Qt::SolidPattern)); if (profile == nullptr) { qCritical() << "Preferences dialog created without legit profile object"; return; } ui->tabWidget->setCurrentIndex(0); //i=ui->timeZoneCombo->findText((*profile)["TimeZone"].toString()); //ui->timeZoneCombo->setCurrentIndex(i); ui->showLeakRedline->setChecked(profile->cpap->showLeakRedline()); ui->leakRedlineSpinbox->setValue(profile->cpap->leakRedline()); ui->oxiDesaturationThreshold->setValue(schema::channel[OXI_SPO2].lowerThreshold()); ui->flagPulseAbove->setValue(schema::channel[OXI_Pulse].upperThreshold()); ui->flagPulseBelow->setValue(schema::channel[OXI_Pulse].lowerThreshold()); ui->spo2Drop->setValue(profile->oxi->spO2DropPercentage()); ui->spo2DropTime->setValue(profile->oxi->spO2DropDuration()); ui->pulseChange->setValue(profile->oxi->pulseChangeBPM()); ui->pulseChangeTime->setValue(profile->oxi->pulseChangeDuration()); ui->oxiDiscardThreshold->setValue(profile->oxi->oxiDiscardThreshold()); ui->eventIndexCombo->setCurrentIndex(profile->general->calculateRDI() ? 1 : 0); ui->automaticImport->setChecked(profile->cpap->autoImport()); // Skip extra dialogs, this needs to rename. ui->autoLoadLastUsed->setChecked(AppSetting->autoOpenLastUsed()); ui->timeEdit->setTime(profile->session->daySplitTime()); int val = profile->session->combineCloseSessions(); ui->combineSlider->setValue(val); ui->openingTabCombo->setCurrentIndex(AppSetting->openTabAtStart()); ui->importTabCombo->setCurrentIndex(AppSetting->openTabAfterImport()); if (val > 0) { ui->combineLCD->display(val); } else { ui->combineLCD->display(STR_TR_Off); } val = profile->session->ignoreShortSessions(); ui->IgnoreSlider->setValue(val); if (val > 0) { ui->IgnoreLCD->display(val); } else { ui->IgnoreLCD->display(STR_TR_Off); } ui->LockSummarySessionSplitting->setChecked(profile->session->lockSummarySessions()); ui->warnOnUntestedMachine->setChecked(profile->session->warnOnUntestedMachine()); ui->warnOnUnexpectedData->setChecked(profile->session->warnOnUnexpectedData()); // macOS default system fonts are not in QFontCombobox because they are "private": // See https://github.com/musescore/MuseScore/commit/0eecb165664a0196c2eee12e42fb273dcfc9c637 // The below makes sure any default system font is present in QFontComboBox. QString systemFontFamily = QFontDatabase::systemFont(QFontDatabase::GeneralFont).family(); if (-1 == ui->applicationFont->findText(systemFontFamily)) { ui->applicationFont->addItem(systemFontFamily); } // If the current font is the system font, setCurrentFont() won't work as intended, // so select the font by searching for its name, which will always work. ui->applicationFont->setCurrentIndex(ui->applicationFont->findText(QApplication::font().family())); //ui->applicationFont->setFont(QApplication::font()); ui->applicationFontSize->setValue(QApplication::font().pointSize()); ui->applicationFontBold->setChecked(QApplication::font().weight() == QFont::Bold); ui->applicationFontItalic->setChecked(QApplication::font().italic()); // ui->applicationFont->setEditable(false); ui->graphFont->setCurrentFont(*defaultfont); //ui->graphFont->setFont(*defaultfont); ui->graphFontSize->setValue(defaultfont->pointSize()); ui->graphFontBold->setChecked(defaultfont->weight() == QFont::Bold); ui->graphFontItalic->setChecked(defaultfont->italic()); // ui->graphFont->setEditable(false); ui->titleFont->setCurrentFont(*mediumfont); //ui->titleFont->setFont(*mediumfont); ui->titleFontSize->setValue(mediumfont->pointSize()); ui->titleFontBold->setChecked(mediumfont->weight() == QFont::Bold); ui->titleFontItalic->setChecked(mediumfont->italic()); // ui->titleFont->setEditable(false); ui->bigFont->setCurrentFont(*bigfont); //ui->bigFont->setFont(*bigfont); ui->bigFontSize->setValue(bigfont->pointSize()); ui->bigFontBold->setChecked(bigfont->weight() == QFont::Bold); ui->bigFontItalic->setChecked(bigfont->italic()); // ui->bigFont->setEditable(false); ui->lineThicknessSlider->setValue(AppSetting->lineThickness()*2.0); ui->resyncMachineDetectedEvents->setChecked(profile->cpap->resyncFromUserFlagging()); ui->useAntiAliasing->setChecked(AppSetting->antiAliasing()); ui->usePixmapCaching->setChecked(AppSetting->usePixmapCaching()); ui->useSquareWavePlots->setChecked(AppSetting->squareWavePlots()); // ui->enableGraphSnapshots->setChecked(AppSetting->graphSnapshots()); ui->graphTooltips->setChecked(AppSetting->graphTooltips()); ui->allowYAxisScaling->setChecked(AppSetting->allowYAxisScaling()); ui->includeSerial->setChecked(AppSetting->includeSerial()); ui->monochromePrinting->setChecked(AppSetting->monochromePrinting()); ui->autoLaunchImporter->setChecked(AppSetting->autoLaunchImport()); #ifndef NO_CHECKUPDATES ui->updateCheckEvery->setValue(AppSetting->updateCheckFrequency()); ui->allowEarlyUpdates->setChecked(AppSetting->allowEarlyUpdates()); ui->test_invite->setVisible(false); if (!getVersion().IsReleaseVersion()) { // Test version ui->automaticallyCheckUpdates->setVisible(false); ui->allowEarlyUpdates->setVisible(false); ui->updateCheckEvery->setMaximum(7); } else { // Release version ui->updateCheckEvery->setMaximum(90); ui->always_look_for_updates->setVisible(false); if (!AppSetting->allowEarlyUpdates()) { ui->test_invite->setVisible(true); ui->allowEarlyUpdates->setVisible(false); } } #else ui->automaticallyCheckUpdates_GroupBox->setVisible(false); #endif int s = profile->cpap->clockDrift(); int m = (s / 60) % 60; int h = (s / 3600); s %= 60; ui->clockDriftHours->setValue(h); ui->clockDriftMinutes->setValue(m); ui->clockDriftSeconds->setValue(s); ui->skipEmptyDays->setChecked(profile->general->skipEmptyDays()); ui->showUnknownFlags->setChecked(profile->general->showUnknownFlags()); // ui->enableMultithreading->setChecked(AppSetting->multithreading()); ui->enableMultithreading->setVisible(false); ui->removeCardNotificationCheckbox->setChecked(AppSetting->removeCardReminder()); ui->dontAskWhenSavingScreenshotsCheckbox->setChecked(AppSetting->dontAskWhenSavingScreenshots()); ui->cacheSessionData->setChecked(AppSetting->cacheSessions()); ui->preloadSummaries->setChecked(profile->session->preloadSummaries()); ui->animationsAndTransitionsCheckbox->setChecked(AppSetting->animations()); ui->complianceCheckBox->setChecked(profile->cpap->showComplianceInfo()); ui->complianceHours->setValue(profile->cpap->complianceHours()); ui->prefCalcMiddle->setCurrentIndex(profile->general->prefCalcMiddle()); ui->prefCalcMax->setCurrentIndex(profile->general->prefCalcMax()); float f = profile->general->prefCalcPercentile(); ui->prefCalcPercentile->setValue(f); ui->tooltipTimeoutSlider->setValue(AppSetting->tooltipTimeout()); on_tooltipTimeoutSlider_valueChanged(ui->tooltipTimeoutSlider->value()); //ui->tooltipMS->display(profile->general->tooltipTimeout()); ui->scrollDampeningSlider->setValue(AppSetting->scrollDampening() / 10); on_scrollDampeningSlider_valueChanged(ui->scrollDampeningSlider->value()); bool bcd = profile->session->backupCardData(); ui->createSDBackups->setChecked(bcd); ui->compressSDBackups->setEnabled(bcd); ui->compressSDBackups->setChecked(profile->session->compressBackupData()); ui->compressSessionData->setChecked(profile->session->compressSessionData()); ui->ignoreOlderSessionsCheck->setChecked(profile->session->ignoreOlderSessions()); ui->ignoreOlderSessionsDate->setDate(profile->session->ignoreOlderSessionsDate().date()); ui->graphHeight->setValue(AppSetting->graphHeight()); #ifndef NO_CHECKUPDATES ui->automaticallyCheckUpdates->setChecked(AppSetting->updatesAutoCheck()); ui->updateCheckEvery->setValue(AppSetting->updateCheckFrequency()); if (AppSetting->updatesLastChecked().isValid()) { RefreshLastChecked(); } else { ui->updateLastChecked->setText(tr("Never")); } #endif ui->overlayFlagsCombo->setCurrentIndex(AppSetting->overlayType()); ui->overviewLinecharts->setCurrentIndex(AppSetting->overviewLinechartMode()); ui->ahiGraphWindowSize->setEnabled(false); ui->ahiGraphWindowSize->setValue(profile->cpap->AHIWindow()); ui->ahiGraphZeroReset->setChecked(profile->cpap->AHIReset()); ui->customEventGroupbox->setChecked(profile->cpap->userEventFlagging()); ui->apneaDuration->setValue(profile->cpap->userEventDuration()); ui->apneaFlowRestriction->setValue(profile->cpap->userEventRestriction()); ui->apneaDuration2->setValue(profile->cpap->userEventDuration2()); ui->apneaFlowRestriction2->setValue(profile->cpap->userEventRestriction2()); ui->userEventDuplicates->setChecked(profile->cpap->userEventDuplicates()); ui->userEventDuplicates->setVisible(false); ui->showUserFlagsInPie->setChecked(AppSetting->userEventPieChart()); bool b; ui->calculateUnintentionalLeaks->setChecked(b=profile->cpap->calculateUnintentionalLeaks()); ui->maskLeaks4Slider->setValue(profile->cpap->custom4cmH2OLeaks()*10.0); ui->maskLeaks20Slider->setValue(profile->cpap->custom20cmH2OLeaks()*10.0); ui->maskLeaks4Label->setText(QString("%1 %2").arg(profile->cpap->custom4cmH2OLeaks(), 5, 'f', 1).arg(STR_UNIT_LPM)); ui->maskLeaks20Label->setText(QString("%1 %2").arg(profile->cpap->custom20cmH2OLeaks(), 5, 'f', 1).arg(STR_UNIT_LPM)); /* QLocale locale=QLocale::system(); QString shortformat=locale.dateFormat(QLocale::ShortFormat); if (!shortformat.toLower().contains("yyyy")) { shortformat.replace("yy","yyyy"); }*/ chanFilterModel = new MySortFilterProxyModel(this); chanModel = new QStandardItemModel(this); chanFilterModel->setSourceModel(chanModel); chanFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); chanFilterModel->setFilterKeyColumn(0); ui->chanView->setModel(chanFilterModel); InitChanInfo(); waveFilterModel = new MySortFilterProxyModel(this); waveModel = new QStandardItemModel(this); waveFilterModel->setSourceModel(waveModel); waveFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); waveFilterModel->setFilterKeyColumn(0); ui->waveView->setModel(waveFilterModel); InitWaveInfo(); ui->waveView->setSortingEnabled(true); ui->chanView->setSortingEnabled(true); ui->waveView->sortByColumn(0, Qt::AscendingOrder); ui->chanView->sortByColumn(0, Qt::AscendingOrder); } #include class SpinBoxDelegate : public QItemDelegate { public: SpinBoxDelegate(QObject *parent = 0):QItemDelegate(parent) {} virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; virtual void setEditorData(QWidget *editor, const QModelIndex &index) const; virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; virtual void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const; }; QWidget *SpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const { QDoubleSpinBox *editor = new QDoubleSpinBox(parent); //editor->setMinimum(0); //editor->setMaximum(100.0); return editor; } void SpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { double value = index.model()->data(index, Qt::EditRole).toDouble(); QDoubleSpinBox *spinBox = static_cast(editor); spinBox->setMinimum(-9999999.0); spinBox->setMaximum(9999999.0); spinBox->setValue(value); } void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QDoubleSpinBox *spinBox = static_cast(editor); spinBox->interpretText(); double value = spinBox->value(); model->setData(index, value, Qt::EditRole); } void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const { editor->setGeometry(option.rect); } #include class ComboBoxDelegate : public QItemDelegate { public: ComboBoxDelegate(QObject *parent = 0):QItemDelegate(parent) {} virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; virtual void setEditorData(QWidget *editor, const QModelIndex &index) const; virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; virtual void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const; }; QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const { QComboBox *combo = new QComboBox(parent); QHash::iterator it; for (it = channeltype.begin(); it != channeltype.end(); ++it) { if (it.key() == schema::UNKNOWN) continue; combo->addItem(it.value()); } //editor->setMinimum(0); //editor->setMaximum(100.0); return combo; } void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QString value = index.model()->data(index, Qt::EditRole).toString(); QComboBox *combo = static_cast(editor); combo->setCurrentText(value); } void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QComboBox *combo = static_cast(editor); model->setData(index, combo->currentText(), Qt::EditRole); } void ComboBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const { editor->setGeometry(option.rect); } void PreferencesDialog::InitChanInfo() { QHash toprows; chanModel->clear(); toplevel.clear(); toprows.clear(); QStringList headers; headers.append(tr("Name")); headers.append(tr("Color")); headers.append(tr("Overview")); headers.append(tr("Flag Type")); headers.append(tr("Label")); headers.append(tr("Details")); chanModel->setHorizontalHeaderLabels(headers); ui->chanView->setColumnWidth(0, 200); ui->chanView->setColumnWidth(1, 40); ui->chanView->setColumnWidth(2, 60); ui->chanView->setColumnWidth(3, 100); ui->chanView->setColumnWidth(4, 100); ui->chanView->setSelectionMode(QAbstractItemView::SingleSelection); ui->chanView->setSelectionBehavior(QAbstractItemView::SelectItems); chanModel->setColumnCount(6); QStandardItem *hdr = nullptr; QMap Section; Section[MT_CPAP] = tr("CPAP Events"); Section[MT_OXIMETER] = tr("Oximeter Events"); Section[MT_POSITION] = tr("Positional Events"); Section[MT_SLEEPSTAGE] = tr("Sleep Stage Events"); Section[MT_UNCATEGORIZED] = tr("Unknown Events"); QMap::iterator it; QHash::iterator ci; for (it = Section.begin(); it != Section.end(); ++it) { toplevel[it.key()] = hdr = new QStandardItem(it.value()); hdr->setEditable(false); QList list; list.append(hdr); for (int i=0; i<5; i++) { QStandardItem *it = new QStandardItem(); it->setEnabled(false); list.append(it); } chanModel->appendRow(list); } ui->chanView->setAlternatingRowColors(true); // ui->graphView->setFirstColumnSpanned(0,daily->index(),true); // Crashes on windows.. Why do I need this again? ComboBoxDelegate * combobox = new ComboBoxDelegate(ui->waveView); ui->chanView->setItemDelegateForColumn(3,combobox); int row = 0; for (ci = schema::channel.names.begin(); ci != schema::channel.names.end(); ci++) { schema::Channel * chan = ci.value(); if ((chan->type() == schema::DATA) || (chan->type() == schema::SETTING) || chan->type() == schema::WAVEFORM) continue; QList items; QStandardItem *it = new QStandardItem(chan->fullname()); it->setCheckable(true); it->setCheckState(chan->enabled() ? Qt::Checked : Qt::Unchecked); it->setEditable(true); it->setData(chan->id(), Qt::UserRole); // Dear translators: %1 is a unique ascii english string used to indentify channels in the code, I'd like feedback on how this goes.. // It's here in case users mess up which field is which.. it will always show the Channel Code underneath in the tooltip. it->setToolTip(tr("Double click to change the descriptive name the '%1' channel.").arg(chan->code())); items.push_back(it); it = new QStandardItem(); it->setBackground(QBrush(chan->defaultColor())); it->setEditable(false); it->setData(chan->defaultColor().rgba(), Qt::UserRole); it->setToolTip(tr("Double click to change the default color for this channel plot/flag/data.")); it->setSelectable(false); items.push_back(it); it = new QStandardItem(QString()); it->setToolTip(tr("Whether this flag has a dedicated overview chart.")); it->setCheckable(true); it->setCheckState(chan->showInOverview() ? Qt::Checked : Qt::Unchecked); it->setTextAlignment(Qt::AlignCenter); it->setData(chan->id(), Qt::UserRole); items.push_back(it); schema::ChanType type = chan->type(); it = new QStandardItem(channeltype[type]); it->setToolTip(tr("Here you can change the type of flag shown for this event")); it->setEditable(type != schema::UNKNOWN); items.push_back(it); it = new QStandardItem(chan->label()); it->setToolTip(tr("This is the short-form label to indicate this channel on screen.")); it->setEditable(true); items.push_back(it); it = new QStandardItem(chan->description()); it->setToolTip(tr("This is a description of what this channel does.")); it->setEditable(true); items.push_back(it); MachineType mt = chan->machtype(); if (chan->type() == schema::UNKNOWN) mt = MT_UNCATEGORIZED; row = toprows[mt]++; toplevel[mt]->insertRow(row, items); } for(QHash::iterator i = toplevel.begin(); i != toplevel.end(); ++i) { if (i.value()->rowCount() == 0) { chanModel->removeRow(i.value()->row()); } } ui->chanView->expandAll(); ui->chanView->setSortingEnabled(true); } void PreferencesDialog::InitWaveInfo() { QHash toprows; waveModel->clear(); machlevel.clear(); toprows.clear(); QStringList headers; headers.append(tr("Name")); headers.append(tr("Color")); headers.append(tr("Overview")); headers.append(tr("Lower")); headers.append(tr("Upper")); headers.append(tr("Label")); headers.append(tr("Details")); waveModel->setHorizontalHeaderLabels(headers); ui->waveView->setColumnWidth(0, 200); ui->waveView->setColumnWidth(1, 40); ui->waveView->setColumnWidth(2, 60); ui->waveView->setColumnWidth(3, 50); ui->waveView->setColumnWidth(4, 50); ui->waveView->setColumnWidth(5, 100); ui->waveView->setSelectionMode(QAbstractItemView::SingleSelection); ui->waveView->setSelectionBehavior(QAbstractItemView::SelectItems); waveModel->setColumnCount(7); QStandardItem *hdr = nullptr; QMap Section; Section[MT_CPAP] = tr("CPAP Waveforms"); Section[MT_OXIMETER] = tr("Oximeter Waveforms"); Section[MT_POSITION] = tr("Positional Waveforms"); Section[MT_SLEEPSTAGE] = tr("Sleep Stage Waveforms"); QMap::iterator it; for (it = Section.begin(); it != Section.end(); ++it) { machlevel[it.key()] = hdr = new QStandardItem(it.value()); hdr->setEditable(false); QList list; list.append(hdr); for (int i=0; i<6; i++) { QStandardItem *it = new QStandardItem(); it->setEnabled(false); list.append(it); } waveModel->appendRow(list); } ui->waveView->setAlternatingRowColors(true); // ui->graphView->setFirstColumnSpanned(0,daily->index(),true); // Crashes on windows.. Why do I need this again? QHash::iterator ci; SpinBoxDelegate * spinbox = new SpinBoxDelegate(ui->waveView); ui->waveView->setItemDelegateForColumn(3,spinbox); ui->waveView->setItemDelegateForColumn(4,spinbox); int row = 0; for (ci = schema::channel.names.begin(); ci != schema::channel.names.end(); ci++) { schema::Channel * chan = ci.value(); if (chan->type() != schema::WAVEFORM) continue; QList items; QStandardItem *it = new QStandardItem(chan->fullname()); it->setCheckable(true); it->setCheckState(chan->enabled() ? Qt::Checked : Qt::Unchecked); it->setEditable(true); it->setData(chan->id(), Qt::UserRole); it->setToolTip(tr("Double click to change the descriptive name this channel.")); items.push_back(it); it = new QStandardItem(); it->setBackground(QBrush(chan->defaultColor())); it->setEditable(false); it->setData(chan->defaultColor().rgba(), Qt::UserRole); it->setToolTip(tr("Double click to change the default color for this channel plot/flag/data.")); it->setSelectable(false); items.push_back(it); it = new QStandardItem(); it->setCheckable(true); it->setCheckState(chan->showInOverview() ? Qt::Checked : Qt::Unchecked); it->setEditable(true); it->setData(chan->id(), Qt::UserRole); it->setToolTip(tr("Whether a breakdown of this waveform displays in overview.")); items.push_back(it); it = new QStandardItem(QString::number(chan->lowerThreshold(),'f',1)); it->setToolTip(tr("Here you can set the lower threshold used for certain calculations on the %1 waveform").arg(chan->fullname())); it->setEditable(true); items.push_back(it); it = new QStandardItem(QString::number(chan->upperThreshold(),'f',1)); it->setToolTip(tr("Here you can set the upper threshold used for certain calculations on the %1 waveform").arg(chan->fullname())); it->setEditable(true); items.push_back(it); it = new QStandardItem(chan->label()); it->setToolTip(tr("This is the short-form label to indicate this channel on screen.")); it->setEditable(true); items.push_back(it); it = new QStandardItem(chan->description()); it->setToolTip(tr("This is a description of what this channel does.")); it->setEditable(true); items.push_back(it); row = toprows[chan->machtype()]++; machlevel[chan->machtype()]->insertRow(row, items); } for(QHash::iterator i = machlevel.begin(); i != machlevel.end(); ++i) { if (i.value()->rowCount() == 0) { waveModel->removeRow(i.value()->row()); } } ui->waveView->expandAll(); ui->waveView->setSortingEnabled(true); } PreferencesDialog::~PreferencesDialog() { delete ui; } bool PreferencesDialog::Save() { bool recompress_events = false; bool recalc_events = false; bool needs_restart = false; bool needs_reload = false; if (ui->ahiGraphZeroReset->isChecked() != profile->cpap->AHIReset()) { recalc_events = true; } if (ui->useSquareWavePlots->isChecked() != AppSetting->squareWavePlots()) { needs_reload = true; } if ((profile->session->daySplitTime() != ui->timeEdit->time()) || (profile->session->combineCloseSessions() != ui->combineSlider->value()) || (profile->session->ignoreShortSessions() != ui->IgnoreSlider->value())) { needs_reload = true; } if (profile->session->lockSummarySessions() != ui->LockSummarySessionSplitting->isChecked()) { needs_reload = true; } if (AppSetting->userEventPieChart() != ui->showUserFlagsInPie->isChecked()) { // lazy.. fix me needs_reload = true; } if ((GFXEngine)ui->gfxEngineCombo->currentData().toUInt() != currentGFXEngine()) { setCurrentGFXEngine((GFXEngine)ui->gfxEngineCombo->currentData().toUInt()); needs_restart = true; } int rdi_set = profile->general->calculateRDI() ? 1 : 0; if (rdi_set != ui->eventIndexCombo->currentIndex()) { //recalc_events=true; needs_reload = true; } if ((profile->general->prefCalcMiddle() != ui->prefCalcMiddle->currentIndex()) || (profile->general->prefCalcMax() != ui->prefCalcMax->currentIndex()) || (profile->general->prefCalcPercentile() != ui->prefCalcPercentile->value())) { needs_reload = true; } if (profile->cpap->leakRedline() != ui->leakRedlineSpinbox->value()) { needs_reload = true; } if (profile->cpap->userEventFlagging() && (profile->cpap->userEventDuration() != ui->apneaDuration->value() || profile->cpap->userEventDuration2() != ui->apneaDuration2->value() || profile->cpap->userEventDuplicates() != ui->userEventDuplicates->isChecked() || profile->cpap->userEventRestriction2() != ui->apneaFlowRestriction2->value() || profile->cpap->userEventRestriction() != ui->apneaFlowRestriction->value())) { recalc_events = true; } // Restart if turning user event flagging on/off if (profile->cpap->userEventFlagging() != ui->customEventGroupbox->isChecked()) { // if (profile->cpap->userEventFlagging()) { // Don't bother recalculating, just switch off needs_reload = true; //} else recalc_events = true; } if (profile->session->compressSessionData() != ui->compressSessionData->isChecked()) { recompress_events = true; } if (recompress_events) { if (profile->countDays(MT_CPAP, profile->FirstDay(), profile->LastDay()) > 0) { if (QMessageBox::question(this, tr("Data Processing Required"), tr("A data re/decompression proceedure is required to apply these changes. This operation may take a couple of minutes to complete.\n\nAre you sure you want to make these changes?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { return false; } } else { recompress_events = false; } } if (recalc_events) { if (profile->countDays(MT_CPAP, profile->FirstDay(), profile->LastDay()) > 0) { if (QMessageBox::question(this, tr("Data Reindex Required"), tr("A data reindexing proceedure is required to apply these changes. This operation may take a couple of minutes to complete.\n\nAre you sure you want to make these changes?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { return false; } } else { recalc_events = false; } } else if (needs_restart) { if (QMessageBox::question(this, tr("Restart Required"), tr("One or more of the changes you have made will require this application to be restarted, in order for these changes to come into effect.\n\nWould you like do this now?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { return false; } } schema::channel[OXI_SPO2].setLowerThreshold(ui->oxiDesaturationThreshold->value()); schema::channel[OXI_Pulse].setLowerThreshold(ui->flagPulseBelow->value()); schema::channel[OXI_Pulse].setUpperThreshold(ui->flagPulseAbove->value()); AppSetting->setUserEventPieChart(ui->showUserFlagsInPie->isChecked()); profile->session->setLockSummarySessions(ui->LockSummarySessionSplitting->isChecked()); profile->session->setWarnOnUntestedMachine(ui->warnOnUntestedMachine->isChecked()); profile->session->setWarnOnUnexpectedData(ui->warnOnUnexpectedData->isChecked()); AppSetting->setOpenTabAtStart(ui->openingTabCombo->currentIndex()); AppSetting->setOpenTabAfterImport(ui->importTabCombo->currentIndex()); AppSetting->setAllowYAxisScaling(ui->allowYAxisScaling->isChecked()); AppSetting->setIncludeSerial(ui->includeSerial->isChecked()); AppSetting->setMonochromePrinting(ui->monochromePrinting->isChecked()); AppSetting->setGraphTooltips(ui->graphTooltips->isChecked()); AppSetting->setAntiAliasing(ui->useAntiAliasing->isChecked()); AppSetting->setUsePixmapCaching(ui->usePixmapCaching->isChecked()); AppSetting->setSquareWavePlots(ui->useSquareWavePlots->isChecked()); // AppSetting->setGraphSnapshots(ui->enableGraphSnapshots->isChecked()); AppSetting->setLineThickness(float(ui->lineThicknessSlider->value()) / 2.0); profile->general->setSkipEmptyDays(ui->skipEmptyDays->isChecked()); AppSetting->setTooltipTimeout(ui->tooltipTimeoutSlider->value()); AppSetting->setScrollDampening(ui->scrollDampeningSlider->value() * 10); profile->general->setShowUnknownFlags(ui->showUnknownFlags->isChecked()); AppSetting->setMultithreading(ui->enableMultithreading->isChecked()); AppSetting->setRemoveCardReminder(ui->removeCardNotificationCheckbox->isChecked()); AppSetting->setDontAskWhenSavingScreenshots(ui->dontAskWhenSavingScreenshotsCheckbox->isChecked()); AppSetting->setCacheSessions(ui->cacheSessionData->isChecked()); profile->session->setPreloadSummaries(ui->preloadSummaries->isChecked()); AppSetting->setAnimations(ui->animationsAndTransitionsCheckbox->isChecked()); profile->cpap->setShowLeakRedline(ui->showLeakRedline->isChecked()); profile->cpap->setLeakRedline(ui->leakRedlineSpinbox->value()); profile->cpap->setShowComplianceInfo(ui->complianceCheckBox->isChecked()); profile->cpap->setComplianceHours(ui->complianceHours->value()); if (ui->graphHeight->value() != AppSetting->graphHeight()) { AppSetting->setGraphHeight(ui->graphHeight->value()); mainwin->getDaily()->ResetGraphLayout(); mainwin->getOverview()->ResetGraphLayout(); } profile->general->setPrefCalcMiddle(ui->prefCalcMiddle->currentIndex()); profile->general->setPrefCalcMax(ui->prefCalcMax->currentIndex()); profile->general->setPrefCalcPercentile(ui->prefCalcPercentile->value()); profile->general->setCalculateRDI((ui->eventIndexCombo->currentIndex() == 1)); profile->session->setBackupCardData(ui->createSDBackups->isChecked()); profile->session->setCompressBackupData(ui->compressSDBackups->isChecked()); profile->session->setCompressSessionData(ui->compressSessionData->isChecked()); profile->session->setCombineCloseSessions(ui->combineSlider->value()); profile->session->setIgnoreShortSessions(ui->IgnoreSlider->value()); profile->session->setDaySplitTime(ui->timeEdit->time()); profile->session->setIgnoreOlderSessions(ui->ignoreOlderSessionsCheck->isChecked()); profile->session->setIgnoreOlderSessionsDate(ui->ignoreOlderSessionsDate->date()); int s = ui->clockDriftHours->value() * 3600 + ui->clockDriftMinutes->value() * 60 + ui->clockDriftSeconds->value(); profile->cpap->setClockDrift(s); AppSetting->setOverlayType((OverlayDisplayType)ui->overlayFlagsCombo->currentIndex()); AppSetting->setOverviewLinechartMode((OverviewLinechartModes)ui->overviewLinecharts->currentIndex()); profile->oxi->setSpO2DropPercentage(ui->spo2Drop->value()); profile->oxi->setSpO2DropDuration(ui->spo2DropTime->value()); profile->oxi->setPulseChangeBPM(ui->pulseChange->value()); profile->oxi->setPulseChangeDuration(ui->pulseChangeTime->value()); profile->oxi->setOxiDiscardThreshold(ui->oxiDiscardThreshold->value()); profile->cpap->setAHIWindow(ui->ahiGraphWindowSize->value()); profile->cpap->setAHIReset(ui->ahiGraphZeroReset->isChecked()); profile->cpap->setAutoImport(ui->automaticImport->isChecked()); // delete me??? AppSetting->setAutoOpenLastUsed(ui->autoLoadLastUsed->isChecked()); profile->cpap->setUserEventFlagging(ui->customEventGroupbox->isChecked()); profile->cpap->setResyncFromUserFlagging(ui->resyncMachineDetectedEvents->isChecked()); profile->cpap->setUserEventDuration(ui->apneaDuration->value()); profile->cpap->setUserEventRestriction(ui->apneaFlowRestriction->value()); profile->cpap->setUserEventDuration2(ui->apneaDuration2->value()); profile->cpap->setUserEventRestriction2(ui->apneaFlowRestriction2->value()); profile->cpap->setUserEventDuplicates(ui->userEventDuplicates->isChecked()); if ((ui->calculateUnintentionalLeaks->isChecked() != profile->cpap->calculateUnintentionalLeaks()) || (fabs((ui->maskLeaks4Slider->value()/10.0)-profile->cpap->custom4cmH2OLeaks())>.1) || (fabs((ui->maskLeaks20Slider->value()/10.0)-profile->cpap->custom20cmH2OLeaks())>.1)) { recalc_events = true; } profile->cpap->setCalculateUnintentionalLeaks(ui->calculateUnintentionalLeaks->isChecked()); profile->cpap->setCustom4cmH2OLeaks(double(ui->maskLeaks4Slider->value()) / 10.0f); profile->cpap->setCustom20cmH2OLeaks(double(ui->maskLeaks20Slider->value()) / 10.0f); AppSetting->setAutoLaunchImport(ui->autoLaunchImporter->isChecked()); #ifndef NO_CHECKUPDATES AppSetting->setUpdatesAutoCheck(ui->automaticallyCheckUpdates->isChecked()); AppSetting->setUpdateCheckFrequency(ui->updateCheckEvery->value()); AppSetting->setAllowEarlyUpdates(ui->allowEarlyUpdates->isChecked()); #endif (*p_pref)["Fonts_Application_Name"] = ui->applicationFont->currentText(); (*p_pref)["Fonts_Application_Size"] = ui->applicationFontSize->value(); (*p_pref)["Fonts_Application_Bold"] = ui->applicationFontBold->isChecked(); (*p_pref)["Fonts_Application_Italic"] = ui->applicationFontItalic->isChecked(); (*p_pref)["Fonts_Graph_Name"] = ui->graphFont->currentText(); (*p_pref)["Fonts_Graph_Size"] = ui->graphFontSize->value(); (*p_pref)["Fonts_Graph_Bold"] = ui->graphFontBold->isChecked(); (*p_pref)["Fonts_Graph_Italic"] = ui->graphFontItalic->isChecked(); (*p_pref)["Fonts_Title_Name"] = ui->titleFont->currentText(); (*p_pref)["Fonts_Title_Size"] = ui->titleFontSize->value(); (*p_pref)["Fonts_Title_Bold"] = ui->titleFontBold->isChecked(); (*p_pref)["Fonts_Title_Italic"] = ui->titleFontItalic->isChecked(); (*p_pref)["Fonts_Big_Name"] = ui->bigFont->currentText(); (*p_pref)["Fonts_Big_Size"] = ui->bigFontSize->value(); (*p_pref)["Fonts_Big_Bold"] = ui->bigFontBold->isChecked(); (*p_pref)["Fonts_Big_Italic"] = ui->bigFontItalic->isChecked(); validateAllFonts(); // Make sure fonts are valid and reset to default values if not // Changes to the application font will be set by the caller (mainwindow.cpp) // Set other fonts for use by graph etc. pages defaultfont = new QFont(((*p_pref)["Fonts_Graph_Name"]).toString()); defaultfont->setPointSize(((*p_pref)["Fonts_Graph_Size"]).toInt()); defaultfont->setWeight(((*p_pref)["Fonts_Graph_Bold"]).toBool() ? QFont::Bold : QFont::Normal); defaultfont->setItalic(((*p_pref)["Fonts_Graph_Italic"]).toBool()); mediumfont = new QFont(((*p_pref)["Fonts_Title_Name"]).toString()); mediumfont->setPointSize(((*p_pref)["Fonts_Title_Size"]).toInt()); mediumfont->setWeight(((*p_pref)["Fonts_Title_Bold"]).toBool() ? QFont::Bold : QFont::Normal); mediumfont->setItalic(((*p_pref)["Fonts_Title_Italic"]).toBool()); bigfont = new QFont(((*p_pref)["Fonts_Big_Name"]).toString()); bigfont->setPointSize(((*p_pref)["Fonts_Big_Size"]).toInt()); bigfont->setWeight(((*p_pref)["Fonts_Big_Bold"]).toBool() ? QFont::Bold : QFont::Normal); bigfont->setItalic(((*p_pref)["Fonts_Big_Italic"]).toBool()); saveChanInfo(); saveWaveInfo(); //qDebug() << "TODO: Save channels.xml to update channel data"; p_pref->Save(); profile->Save(); if (recompress_events) { mainwin->recompressEvents(); } else if (recalc_events) { // send a signal instead? mainwin->reprocessEvents(needs_restart); } else if (needs_reload) { QTimer::singleShot(0, mainwin, SLOT(reloadProfile())); } else if (needs_restart) { mainwin->RestartApplication(); } else { mainwin->getDaily()->LoadDate(mainwin->getDaily()->getDate()); // Save early.. just in case.. mainwin->getDaily()->graphView()->SaveSettings("Daily"); mainwin->getOverview()->graphView()->SaveSettings("Overview"); } return true; } void PreferencesDialog::saveChanInfo() { // Change focus to force save of any open editors.. ui->channelSearch->setFocus(); int toprows = chanModel->rowCount(); bool ok; for (int i=0; i < toprows; i++) { QStandardItem * topitem = chanModel->item(i,0); if (!topitem) continue; int rows = topitem->rowCount(); for (int j=0; j< rows; ++j) { QStandardItem * item = topitem->child(j, 0); if (!item) continue; ChannelID id = item->data(Qt::UserRole).toUInt(&ok); schema::Channel & chan = schema::channel[id]; if (chan.isNull()) continue; chan.setEnabled(item->checkState() == Qt::Checked ? true : false); chan.setFullname(item->text()); chan.setDefaultColor(QColor(topitem->child(j,1)->data(Qt::UserRole).toUInt())); chan.setShowInOverview(topitem->child(j,2)->checkState() == Qt::Checked); QString ts = topitem->child(j,3)->text(); schema::ChanType type = schema::MINOR_FLAG; for (QHash::iterator it = channeltype.begin(); it!= channeltype.end(); ++it) { if (it.value() == ts) { type = it.key(); break; } } chan.setType(type); chan.setLabel(topitem->child(j,4)->text()); chan.setDescription(topitem->child(j,5)->text()); } } } void PreferencesDialog::saveWaveInfo() { // Change focus to force save of any open editors.. ui->waveSearch->setFocus(); int toprows = waveModel->rowCount(); bool ok; for (int i=0; i < toprows; i++) { QStandardItem * topitem = waveModel->item(i,0); if (!topitem) continue; int rows = topitem->rowCount(); for (int j=0; j< rows; ++j) { QStandardItem * item = topitem->child(j, 0); if (!item) continue; ChannelID id = item->data(Qt::UserRole).toUInt(&ok); schema::Channel & chan = schema::channel[id]; if (chan.isNull()) continue; chan.setEnabled(item->checkState() == Qt::Checked ? true : false); chan.setFullname(item->text()); chan.setDefaultColor(QColor(topitem->child(j,1)->data(Qt::UserRole).toUInt())); chan.setShowInOverview(topitem->child(j,2)->checkState() == Qt::Checked); chan.setLowerThreshold(topitem->child(j,3)->text().toDouble()); chan.setUpperThreshold(topitem->child(j,4)->text().toDouble()); chan.setLabel(topitem->child(j,5)->text()); chan.setDescription(topitem->child(j,6)->text()); } } } void PreferencesDialog::on_combineSlider_valueChanged(int position) { if (position > 0) { ui->combineLCD->display(position); } else { ui->combineLCD->display(STR_TR_Off); } } void PreferencesDialog::on_IgnoreSlider_valueChanged(int position) { if (position > 0) { ui->IgnoreLCD->display(position); } else { ui->IgnoreLCD->display(STR_TR_Off); } } #ifndef NO_CHECKUPDATES #include "mainwindow.h" extern MainWindow *mainwin; void PreferencesDialog::RefreshLastChecked() { ui->updateLastChecked->setText(AppSetting->updatesLastChecked().toString(Qt::SystemLocaleLongDate)); } #endif MySortFilterProxyModel::MySortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { } bool MySortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (source_parent == qobject_cast (sourceModel())->invisibleRootItem()->index()) { // always accept children of rootitem, since we want to filter their children return true; } return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } // Might still be useful.. //void PreferencesDialog::on_resetGraphButton_clicked() //{ // QString title = tr("Confirmation"); // QString text = tr("Are you sure you want to reset your graph preferences to the defaults?"); // StandardButtons buttons = QMessageBox::Yes | QMessageBox::No; // StandardButton defaultButton = QMessageBox::No; // // Display confirmation dialog. // StandardButton choice = QMessageBox::question(this, title, text, buttons, defaultButton); // if (choice == QMessageBox::No) { // return; // } // gGraphView *views[3] = {0}; // views[0] = mainwin->getDaily()->graphView(); // views[1] = mainwin->getOverview()->graphView(); // // Iterate over all graph containers. // for (unsigned j = 0; j < 3; j++) { // gGraphView *view = views[j]; // if (!view) { // continue; // } // // Iterate over all contained graphs. // for (int i = 0; i < view->size(); i++) { // gGraph *g = (*view)[i]; // g->setRecMaxY(0); // FIXME: should be g->reset(), but need other patches to land. // g->setRecMinY(0); // g->setVisible(true); // } // view->updateScale(); // } // resetGraphModel(); // ui->graphView->update(); //} void PreferencesDialog::on_createSDBackups_toggled(bool checked) { if (profile->session->backupCardData() && !checked) { QList mach = p_profile->GetMachines(MT_CPAP); bool haveS9 = false; for (int i = 0; i < mach.size(); i++) { if (mach[i]->loaderName() == STR_MACH_ResMed) { haveS9 = true; break; } } if (haveS9 && QMessageBox::question(this, tr("This may not be a good idea"), tr("ResMed S9 machines routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution).") + tr(" If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back.") + tr(" If you need to conserve disk space, please remember to carry out manual backups.") + tr(" Are you sure you want to disable these backups?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { ui->createSDBackups->setChecked(true); return; } } if (!checked) { ui->compressSDBackups->setChecked(false); } ui->compressSDBackups->setEnabled(checked); } void PreferencesDialog::on_okButton_clicked() { if (Save()) { accept(); } } void PreferencesDialog::on_scrollDampeningSlider_valueChanged(int value) { if (value > 0) { ui->scrollDampDisplay->setText(QString("%1%2").arg(value * 10).arg(STR_UNIT_ms)); } else { ui->scrollDampDisplay->setText(STR_TR_Off); } } void PreferencesDialog::on_tooltipTimeoutSlider_valueChanged(int value) { ui->tooltipTimeoutDisplay->setText(QString("%1%2").arg(double(value)/1000.0,0,'f',1).arg(STR_UNIT_s)); } void PreferencesDialog::on_resetChannelDefaults_clicked() { if (QMessageBox::question(this, STR_MessageBox_Warning, QObject::tr("Are you sure you want to reset all your channel colors and settings to defaults?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { schema::resetChannels(); saveWaveInfo(); InitChanInfo(); } } void PreferencesDialog::on_createSDBackups_clicked(bool checked) { if (!checked && p_profile->session->backupCardData()) { if (QMessageBox::question(this, STR_MessageBox_Warning, tr("Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found.\n\n") + tr("Are you really sure you want to do this?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { // do nothing } else { ui->createSDBackups->setChecked(true); } } } void PreferencesDialog::on_channelSearch_textChanged(const QString &arg1) { chanFilterModel->setFilterFixedString(arg1); } void PreferencesDialog::on_chanView_doubleClicked(const QModelIndex &index) { if (index.column() == 1) { QColorDialog a; if (!(index.flags() & Qt::ItemIsEnabled)) return; quint32 color = index.data(Qt::UserRole).toUInt(); a.setCurrentColor(QColor((QRgb)color)); if (a.exec() == QColorDialog::Accepted) { quint32 cv = a.currentColor().rgba(); chanFilterModel->setData(index, cv, Qt::UserRole); chanFilterModel->setData(index, a.currentColor(), Qt::BackgroundRole); } } } void PreferencesDialog::on_waveSearch_textChanged(const QString &arg1) { waveFilterModel->setFilterFixedString(arg1); } void PreferencesDialog::on_resetWaveformChannels_clicked() { if (QMessageBox::question(this, STR_MessageBox_Warning, QObject::tr("Are you sure you want to reset all your waveform channel colors and settings to defaults?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { schema::resetChannels(); saveChanInfo(); // reset clears EVERYTHING, so have to put these back in case they cancel. InitWaveInfo(); } } void PreferencesDialog::on_waveView_doubleClicked(const QModelIndex &index) { if (index.column() == 1) { QColorDialog a; if (!(index.flags() & Qt::ItemIsEnabled)) return; quint32 color = index.data(Qt::UserRole).toUInt(); a.setCurrentColor(QColor((QRgb)color)); if (a.exec() == QColorDialog::Accepted) { quint32 cv = a.currentColor().rgba(); waveFilterModel->setData(index, cv, Qt::UserRole); waveFilterModel->setData(index, a.currentColor(), Qt::BackgroundRole); } } } void PreferencesDialog::on_maskLeaks4Slider_valueChanged(int value) { ui->maskLeaks4Label->setText(tr("%1 %2").arg(value/10.0f, 5,'f',1).arg(STR_UNIT_LPM)); } void PreferencesDialog::on_maskLeaks20Slider_valueChanged(int value) { ui->maskLeaks20Label->setText(tr("%1 %2").arg(value/10.0f, 5,'f',1).arg(STR_UNIT_LPM)); } void PreferencesDialog::on_calculateUnintentionalLeaks_toggled(bool) { } OSCAR-code-v1.3.1/oscar/preferencesdialog.h000066400000000000000000000060341417327530600204350ustar00rootroot00000000000000/* OSCAR Preferences Dialog Headers * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef PREFERENCESDIALOG_H #define PREFERENCESDIALOG_H #include #include #include #include #include #include #include "SleepLib/profiles.h" namespace Ui { class PreferencesDialog; } /*! \class MySortFilterProxyModel \brief Enables the Graph tabs view to be filtered */ class MySortFilterProxyModel: public QSortFilterProxyModel { Q_OBJECT public: MySortFilterProxyModel(QObject *parent = 0); protected: //! \brief Simply extends filterAcceptRow to scan children as well bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; }; /*! \class PreferencesDialog \brief OSCAR's Main Preferences Window This provides the Preferences form and logic to alter Preferences for OSCAR */ class PreferencesDialog : public QDialog { Q_OBJECT public: explicit PreferencesDialog(QWidget *parent, Profile *_profile); ~PreferencesDialog(); //! \brief Save the current preferences, called when Ok button is clicked on. bool Save(); #ifndef NO_CHECKUPDATES //! \brief Updates the date text of the last time updates where checked void RefreshLastChecked(); #endif private slots: void on_combineSlider_valueChanged(int value); void on_IgnoreSlider_valueChanged(int value); //void on_genOpWidget_itemActivated(QListWidgetItem *item); void on_createSDBackups_toggled(bool checked); void on_okButton_clicked(); void on_scrollDampeningSlider_valueChanged(int value); void on_tooltipTimeoutSlider_valueChanged(int value); void on_createSDBackups_clicked(bool checked); void on_resetChannelDefaults_clicked(); void on_channelSearch_textChanged(const QString &arg1); void on_chanView_doubleClicked(const QModelIndex &index); void on_waveSearch_textChanged(const QString &arg1); void on_resetWaveformChannels_clicked(); void on_waveView_doubleClicked(const QModelIndex &index); void on_maskLeaks4Slider_valueChanged(int value); void on_maskLeaks20Slider_valueChanged(int value); void on_calculateUnintentionalLeaks_toggled(bool arg1); private: void InitChanInfo(); void InitWaveInfo(); void saveChanInfo(); void saveWaveInfo(); QHash toplevel; QHash machlevel; Ui::PreferencesDialog *ui; Profile *profile; QHash m_new_colors; QStringList importLocations; QStringListModel *importModel; MySortFilterProxyModel * chanFilterModel; QStandardItemModel *chanModel; MySortFilterProxyModel * waveFilterModel; QStandardItemModel *waveModel; }; #endif // PREFERENCESDIALOG_H OSCAR-code-v1.3.1/oscar/preferencesdialog.ui000066400000000000000000004057731417327530600206400ustar00rootroot00000000000000 PreferencesDialog Qt::ApplicationModal 0 0 942 726 0 0 145 0 Preferences :/icons/preferences.png:/icons/preferences.png true true 0 0 0 0 0 0 2 &Import 4 4 4 4 4 0 0 Session Splitting Settings 4 4 4 4 4 0 0 [ResMed warning message] true true 0 0 0 0 0 0 Combine Close Sessions Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Minutes Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Multiple sessions closer together than this value will be kept on the same day. 0 240 10 30 0 Qt::Horizontal false false QSlider::TicksAbove 10 QFrame::Box 5 QLCDNumber::Flat Ignore Short Sessions Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Minutes Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 4 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> 90 5 Qt::Horizontal QSlider::TicksAbove 5 QFrame::Box QLCDNumber::Flat 4 Day Split Time Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Sessions starting before this time will go to the previous calendar day. QAbstractSpinBox::UpDownArrows 12 0 0 2000 1 1 Qt::Horizontal QSizePolicy::Minimum 10 20 <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Qt::Horizontal 40 20 0 0 Session Storage Options 4 9 4 0 0 4 true Changing SD Backup compression options doesn't automatically recompress backup data. true Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Compress SD Card Backups (slower first import, but makes backups smaller) true The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. true This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Compress Session Data (makes OSCAR data smaller, but day changing slower.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Create SD Card Backups during Import (Turn this off at your own peril!) 0 0 0 Do not import sessions older than: Sessions older than this date will not be imported true true 2099 12 31 1970 1 2 QDateTimeEdit::DaySection dd MMMM yyyy true Qt::Horizontal 40 20 0 0 Memory and Startup Options Bypass the login screen and load the most recent User Profile Auto-Launch CPAP Importer after opening profile Qt::Vertical 20 40 <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> Keep Waveform/Event data in memory <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> Pre-Load all summary data at startup Automatically load last used profile on start-up <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> Import without asking for confirmation <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered &CPAP 4 4 4 4 4 20 QLayout::SetMinimumSize 5 5 5 5 0 0 CPAP Clock Drift 4 4 4 4 4 0 0 <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> true -9999 9999 Minutes -59 59 0 -99 Hours Seconds 0 0 This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Calculate Unintentional Leaks When Not Present true Your masks vent rate at 20 cmH2O pressure 300 550 10 470 Qt::Horizontal QSlider::TicksAbove 20 0 0 80 0 QFrame::Box 48 l/m true Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Your masks vent rate at 4 cmH2O pressure 120 240 1 10 201 Qt::Horizontal false QSlider::TicksAbove 4 cmH2O Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 20 cmH2O Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 80 0 QFrame::Box 20 l/m true Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Note: A linear calculation method is used. Changing these values requires a recalculation. true 0 0 Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Custom CPAP User Event Flagging false true 4 9 4 4 4 s 10.000000000000000 % 10.000000000000000 This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Resync Machine Detected Events (Experimental) Qt::Vertical 20 40 #2 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Flow Restriction 0 0 true <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true 0 0 Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. % 10.000000000000000 Show in Event Breakdown Piechart Duration of airflow restriction s 1.000000000000000 10.000000000000000 #1 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Allow duplicates near machine events. 0 0 Event Duration QLayout::SetMinimumSize 5 5 0 0 General CPAP and Related Settings Show flags for machine detected events that haven't been identified yet. Enable Unknown Events Channels AHI/Hour Graph Time Window Compliance defined as 0 0 Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. minutes 5 999 60 false Whether to show the leak redline in the leak graph Flag leaks over threshold Preferred major event index Reset the counter to zero at beginning of each (time) window. Zero Reset 0 0 User definable threshold considered large leak l/min 1 0 0 Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. hours 1 8.000000000000000 4.000000000000000 0 0 AHI RDI 0 0 Changes to the following settings needs a restart, but not a recalc. Preferred Calculation Methods For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. % 1 99 0 0 Middle Calculations Maximum Calcs Upper Percentile <html><head/><body><p>Cumulative Indices</p></body></html> Median is recommended for ResMed users. Median Weighted Average Normal Average 0 0 140 0 <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> True Maximum 99% Percentile Combined Count divided by Total Hours Time Weighted average of Indice Standard average of indice Median 0 0 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::Vertical 20 40 &Oximetry 4 2 2 2 2 Oximetry Settings false 4 8 4 4 5 0 0 Other oximetry options % bpm Small chunks of oximetry data under this amount will be discarded. s 300 Discard segments under Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter bpm <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</p></body></html> Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Flag Pulse Rate Below Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Flag Pulse Rate Above Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Flag rapid changes in oximetry stats 0 0 <html><head/><body><p>SpO<span style=" vertical-align:sub;">2</span></p></body></html> Minimum duration of drop in oxygen saturation s 0 Sudden change in Pulse Rate of at least this amount bpm 0 1.000000000000000 Minimum duration of pulse change event. s 0 0 0 Pulse Percentage drop in oxygen saturation % 0 1.000000000000000 Qt::Vertical 20 40 0 0 300 0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method do </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Events 4 0 0 0 0 Search 0 0 0 0 Reset &Defaults 0 0 <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Waveforms 4 0 0 0 0 Search 0 0 0 0 Reset &Defaults 0 0 <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter &General 0 0 General Settings 4 4 4 4 4 Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Enable Multithreading Show Remove Card reminder notification on OSCAR shutdown Always save screenshots in the OSCAR Data folder Qt::Vertical Qt::Horizontal Graphics Engine (Requires Restart) Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. true 0 0 Check For Updates false 0 0 You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. true false false false Automatically check for updates 0 0 Check for new version every How often OSCAR should check for updates. 90 0 0 days. Qt::Horizontal 40 20 0 0 Last Checked For Updates: 0 0 TextLabel Qt::Horizontal 40 20 If you are interested in helping test new features and bugfixes early, click here. I want to be notified of test versions. (Advanced users only please.) 0 0 If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR true true Qt::Vertical 20 40 &Appearance 4 2 2 2 2 0 0 Graph Settings On Opening Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> Profile Profile Welcome Daily Overview Statistics Switch Tabs Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter No change Welcome Daily Overview Statistics After Import Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Graph Height Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Overlay Flags Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Line Thickness Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 The visual method of displaying waveform overlay flags. Standard Bars Top Markers The pixel thickness of line plots 2 8 1 Qt::Horizontal QSlider::TicksBelow Tooltip Timeout Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Scroll Dampening Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter How long you want the tooltips to stay visible. 1000 30000 100 500 5000 Qt::Horizontal QSlider::TicksBelow 1000 0 0 60 0 QFrame::Box TextLabel Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Graph Tooltips Bar Tops Line Chart 0 0 Default display height of graphs in pixels 50 600 10 180 <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> 25 1 5 Qt::Horizontal false QSlider::TicksBelow 1 0 0 60 0 QFrame::Box TextLabel Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Overview Linecharts Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Other Visual Settings Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Use Anti-Aliasing Makes certain plots look more "square waved". Square Wave Plots Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Use Pixmap Caching <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> Animations && Fancy Stuff Daily view navigation buttons will skip over days without data records Skip over Empty Days Whether to allow changing yAxis scales by double clicking on yAxis labels Allow YAxis Scaling Whether to include machine serial number on machine settings changes report Include Serial Number Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Qt::Vertical 20 40 Qt::Horizontal Fonts (Application wide settings) false 0 4 0 4 0 0 true Font 0 0 true Size 0 0 true Bold Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 true Italic 0 0 Application 80 16777215 6 30 10 0 0 Qt::LeftToRight 0 0 0 0 Graph Text 80 16777215 6 40 11 0 0 0 0 0 0 Graph Titles 80 16777215 6 40 14 0 0 0 0 0 0 Big Text 80 16777215 6 72 18 0 0 0 0 0 0 true Details Qt::Vertical 20 40 &Cancel &Ok Qt::Horizontal 750 20 cancelButton clicked() PreferencesDialog reject() 757 605 286 274 OSCAR-code-v1.3.1/oscar/profileselector.cpp000066400000000000000000000473621417327530600205210ustar00rootroot00000000000000/* Profile Selector Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include "profileselector.h" #include "ui_profileselector.h" #include "SleepLib/profiles.h" #include "daily.h" #include "overview.h" #include "statistics.h" #include "mainwindow.h" #include "newprofile.h" #include "version.h" extern MainWindow * mainwin; MySortFilterProxyModel2::MySortFilterProxyModel2(QObject *parent) : QSortFilterProxyModel(parent) { } bool MySortFilterProxyModel2::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QModelIndex index0 = sourceModel()->index(sourceRow, 0, sourceParent); QModelIndex index1 = sourceModel()->index(sourceRow, 1, sourceParent); QModelIndex index2 = sourceModel()->index(sourceRow, 2, sourceParent); QModelIndex index5 = sourceModel()->index(sourceRow, 5, sourceParent); return (sourceModel()->data(index0).toString().contains(filterRegExp()) || sourceModel()->data(index1).toString().contains(filterRegExp()) || sourceModel()->data(index2).toString().contains(filterRegExp()) || sourceModel()->data(index5).toString().contains(filterRegExp()) ); } ProfileSelector::ProfileSelector(QWidget *parent) : QWidget(parent), ui(new Ui::ProfileSelector) { ui->setupUi(this); model = nullptr; proxy = nullptr; showDiskUsage = false; // in case I want to preference it later on_diskSpaceInfo_linkActivated(showDiskUsage ? "show" : "hide"); ui->versionLabel->setText(""); ui->diskSpaceInfo->setVisible(false); QItemSelectionModel * sm = ui->profileView->selectionModel(); if (sm) connect(sm, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), this, SLOT(on_selectionChanged(QModelIndex,QModelIndex))); ui->buttonEditProfile->setEnabled(false); ui->buttonOpenProfile->setEnabled(false); } ProfileSelector::~ProfileSelector() { QItemSelectionModel * sm = ui->profileView->selectionModel(); disconnect(sm, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), this, SLOT(on_selectionChanged(QModelIndex,QModelIndex))); delete ui; } const Qt::GlobalColor openProfileHighlightColor = Qt::darkGreen; void ProfileSelector::updateProfileList() { QItemSelectionModel * sm = ui->profileView->selectionModel(); if (sm) disconnect(sm, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), this, SLOT(on_selectionChanged(QModelIndex,QModelIndex))); QString name; int w=0; if (proxy) delete proxy; if (model) delete model; const int columns = 6; model = new QStandardItemModel(0, columns, this); model->setHeaderData(0, Qt::Horizontal, tr("Profile")); model->setHeaderData(1, Qt::Horizontal, tr("Ventilator Brand")); model->setHeaderData(2, Qt::Horizontal, tr("Ventilator Model")); model->setHeaderData(3, Qt::Horizontal, tr("Other Data")); model->setHeaderData(4, Qt::Horizontal, tr("Last Imported")); model->setHeaderData(5, Qt::Horizontal, tr("Name")); ui->profileView->setStyleSheet("QHeaderView::section { background-color:lightgrey }"); int row = 0; // int sel = -1; QFontMetrics fm(ui->profileView->font()); //#if QT_VERSION < QT_VERSION_CHECK(5,11,0) // not sure when this was fixed QPalette palette = ui->profileView->palette(); palette.setColor(QPalette::Highlight, "#3a7fc2"); palette.setColor(QPalette::HighlightedText, "white"); ui->profileView->setPalette(palette); //#endif ui->profileView->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->profileView->setFocusPolicy(Qt::NoFocus); // ui->profileView->setSelectionMode(QAbstractItemView::NoSelection); ui->profileView->setSelectionBehavior(QAbstractItemView::SelectRows); ui->profileView->setSelectionMode(QAbstractItemView::SingleSelection); QMap::iterator pi; for (pi = Profiles::profiles.begin(); pi != Profiles::profiles.end(); pi++) { Profile *prof = pi.value(); name = pi.key(); // if (AppSetting->profileName() == name) { // sel = row; // } Machine * mach = prof->GetMachine(MT_CPAP); // only interested in last cpap machine... if (!mach) { qDebug() << "Couldn't find machine info for" << name; } model->insertRows(row, 1, QModelIndex()); // Problem: Can't access profile details until it's loaded. QString usersname; if (!prof->user->lastName().isEmpty()) { usersname = QString("%1, %2").arg(prof->user->lastName()).arg(prof->user->firstName()); } model->setData(model->index(row, 0, QModelIndex()), name); model->setData(model->index(row, 0, QModelIndex()), name, Qt::UserRole+2); model->setData(model->index(row, 5, QModelIndex()), usersname); if (mach) { model->setData(model->index(row, 1, QModelIndex()), mach->brand()); model->setData(model->index(row, 2, QModelIndex()), mach->model()); model->setData(model->index(row, 4, QModelIndex()), mach->lastImported().toString(Qt::SystemLocaleShortDate)); } QBrush bg = QColor(Qt::black); QFont font = QApplication::font(); if (prof == p_profile) { bg = QBrush(openProfileHighlightColor); font.setBold(true); } for (int i=0; isetData(model->index(row, i, QModelIndex()), bg, Qt::ForegroundRole); //model->setData(model->index(row, i, QModelIndex()), font, Qt::FontRole); } QRect rect = fm.boundingRect(name); if (rect.width() > w) w = rect.width(); // Profile fonts arern't loaded yet.. Using generic font. //item->setFont(font); //model->appendRow(item); row++; } w+=20; // ui->profileView->setMinimumWidth(w); if ( row == 0 ) { ui->profileInfoLabel->setText(tr("You must create a profile")); } proxy = new MySortFilterProxyModel2(this); proxy->setSourceModel(model); proxy->setSortCaseSensitivity(Qt::CaseInsensitive); ui->profileView->setModel(proxy); QHeaderView *headerView = ui->profileView->horizontalHeader(); headerView->setStretchLastSection(true); headerView->setSectionResizeMode(QHeaderView::Stretch); sm = ui->profileView->selectionModel(); connect(sm, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), this, SLOT(on_selectionChanged(QModelIndex,QModelIndex))); } void ProfileSelector::updateProfileHighlight(QString name) { QFont font = QApplication::font(); font.setBold(false); QBrush bg = QColor(Qt::black); for (int row=0;row < model->rowCount(); row++) { for (int i=0; icolumnCount(); i++) { QModelIndex idx = model->index(row, i, QModelIndex()); model->setData(idx, bg, Qt::ForegroundRole); model->setData(idx, font, Qt::FontRole); } } bg = QBrush(openProfileHighlightColor); font.setBold(true); for (int row=0;row < proxy->rowCount(); row++) { QModelIndex idx = proxy->index(row, 0, QModelIndex()); if (proxy->data(idx).toString().compare(name)==0) { on_selectionChanged(idx, QModelIndex()); for (int i=0; icolumnCount(); i++) { idx = proxy->index(row, i, QModelIndex()); proxy->setData(idx, bg, Qt::ForegroundRole); proxy->setData(idx, font, Qt::FontRole); } break; } } } Profile *ProfileSelector::SelectProfile(QString profname, bool skippassword=false) { auto pit = Profiles::profiles.find(profname); if (pit == Profiles::profiles.end()) return nullptr; Profile * prof = pit.value(); if (prof != p_profile) { if (prof->user->hasPassword() && !skippassword) { QDialog dialog(this, Qt::Dialog); QLineEdit *e = new QLineEdit(&dialog); e->setEchoMode(QLineEdit::Password); dialog.connect(e, SIGNAL(returnPressed()), &dialog, SLOT(accept())); dialog.setWindowTitle(tr("Enter Password for %1").arg(profname)); dialog.setMinimumWidth(300); QVBoxLayout *lay = new QVBoxLayout(); dialog.setLayout(lay); lay->addWidget(e); int tries = 0; bool succeeded = false; do { e->setText(""); if (dialog.exec() != QDialog::Accepted) { break; } tries++; if (prof->user->checkPassword(e->text())) { succeeded = true; break; } else { if (tries < 3) { QMessageBox::warning(this, STR_MessageBox_Error, tr("You entered an incorrect password"), QMessageBox::Ok); } else { QMessageBox::warning(this, STR_MessageBox_Error, tr("Forgot your password?")+"\n"+tr("Ask on the forums how to reset it, it's actually pretty easy."), QMessageBox::Ok); } } } while (tries < 3); if (!succeeded) return nullptr; } // Unselect everything in ProfileView updateProfileHighlight(profname); } return prof; } void ProfileSelector::on_profileView_doubleClicked(const QModelIndex &index) { QModelIndex idx = proxy->index(index.row(), 0, QModelIndex()); QString profname = proxy->data(idx, Qt::UserRole+2).toString(); //if (SelectProfile(profname)) { mainwin->OpenProfile(profname); //} } void ProfileSelector::on_profileFilter_textChanged(const QString &arg1) { QRegExp regExp("*"+arg1+"*", Qt::CaseInsensitive, QRegExp::Wildcard); proxy->setFilterRegExp(regExp); } // Clear filter list void ProfileSelector::on_resetFilterButton_clicked() { ui->profileFilter->clear(); } void ProfileSelector::on_buttonOpenProfile_clicked() { if (ui->profileView->currentIndex().isValid()) { QString name = proxy->data(proxy->index(ui->profileView->currentIndex().row(), 0, QModelIndex()), Qt::UserRole+2).toString(); mainwin->OpenProfile(name); } } void ProfileSelector::on_buttonEditProfile_clicked() { if (ui->profileView->currentIndex().isValid()) { QString name = proxy->data(proxy->index(ui->profileView->currentIndex().row(), 0, QModelIndex()), Qt::UserRole+2).toString(); qDebug() << "Editing" << name; Profile * prof = Profiles::profiles[name]; //SelectProfile(name); // may not be necessary... NewProfile *newprof = new NewProfile(this); newprof->edit(name); newprof->setWindowModality(Qt::ApplicationModal); newprof->setModal(true); if (newprof->exec() != NewProfile::Rejected) { QString usersname; if (!prof->user->lastName().isEmpty()) { usersname = QString("%1, %2").arg(prof->user->lastName()).arg(prof->user->firstName()); } proxy->setData(proxy->index(ui->profileView->currentIndex().row(), 5, QModelIndex()), usersname); //updateProfileList(); if (prof == p_profile) updateProfileHighlight(name); } delete newprof; } else { QMessageBox::information(this, STR_MessageBox_Information, tr("Select a profile first"), QMessageBox::Ok); } } void ProfileSelector::on_buttonNewProfile_clicked() { if (p_profile) mainwin->CloseProfile(); NewProfile *newprof = new NewProfile(this); newprof->skipWelcomeScreen(); newprof->setWindowModality(Qt::ApplicationModal); newprof->setModal(true); if (newprof->exec() == NewProfile::Accepted) { p_profile = Profiles::Get(AppSetting->profileName()); if (p_profile != nullptr) { QString name = p_profile->user->userName(); p_profile = nullptr; mainwin->OpenProfile(name, true); // open profile, skipping the already entered password } else { qWarning() << AppSetting->profileName() << "yielded a null profile"; p_profile=nullptr; } updateProfileList(); } delete newprof; } void ProfileSelector::on_buttonDestroyProfile_clicked() { if (ui->profileView->currentIndex().isValid()) { QString name = proxy->data(proxy->index(ui->profileView->currentIndex().row(), 0, QModelIndex()), Qt::UserRole+2).toString(); Profile * profile = Profiles::profiles[name]; QString path = profile->Get(PrefMacro(STR_GEN_DataFolder)); if (path == (GetAppData() + "/Profiles/")) { QMessageBox::warning(this, STR_MessageBox_Error, tr("The selected profile does not appear to contain any data and cannot be removed by OSCAR"), QMessageBox::Ok); return; } bool verified = true; if (profile->user->hasPassword()) { QDialog dialog(this, Qt::Dialog); QLineEdit *e = new QLineEdit(&dialog); e->setEchoMode(QLineEdit::Password); dialog.connect(e, SIGNAL(returnPressed()), &dialog, SLOT(accept())); dialog.setWindowTitle(tr("Enter Password for %1").arg(name)); dialog.setMinimumWidth(300); QVBoxLayout *lay = new QVBoxLayout(); dialog.setLayout(lay); lay->addWidget(e); int tries = 0; do { e->setText(""); if (dialog.exec() != QDialog::Accepted) { break; } tries++; if (profile->user->checkPassword(e->text())) { verified = true; break; } else { if (tries < 3) { QMessageBox::warning(this, STR_MessageBox_Error, tr("You entered an incorrect password"), QMessageBox::Ok); } else { QMessageBox::warning(this, STR_MessageBox_Error, tr("If you're trying to delete because you forgot the password, you need to either reset it or delete the profile folder manually."), QMessageBox::Ok); } } } while (tries < 3); if (!verified) return; } QDialog confirmdlg; QVBoxLayout layout(&confirmdlg); QLabel message(QString(""+STR_MessageBox_Warning+": "+tr("You are about to destroy profile '%1'.")+"

    "+tr("Think carefully, as this will irretrievably delete the profile along with all backup data stored under
    %2.")+"

    "+tr("Enter the word DELETE below (exactly as shown) to confirm.")).arg(name).arg(path), &confirmdlg); layout.insertWidget(0,&message,1); QLineEdit lineedit(&confirmdlg); layout.insertWidget(1, &lineedit, 1); QHBoxLayout layout2; layout.insertLayout(2,&layout2,1); QPushButton cancel(QString("&Cancel"), &confirmdlg); QPushButton accept(QString("&Delete Profile"), &confirmdlg); layout2.addWidget(&cancel); layout2.addStretch(1); layout2.addWidget(&accept); confirmdlg.connect(&cancel, SIGNAL(clicked()), &confirmdlg, SLOT(reject())); confirmdlg.connect(&accept, SIGNAL(clicked()), &confirmdlg, SLOT(accept())); confirmdlg.connect(&lineedit, SIGNAL(returnPressed()), &confirmdlg, SLOT(accept())); if (confirmdlg.exec() != QDialog::Accepted) return; if (lineedit.text().compare(tr("DELETE"))!=0) { QMessageBox::information(NULL, tr("Sorry"), tr("You need to enter DELETE in capital letters."), QMessageBox::Ok); return; } qDebug() << "Deleting Profile" << name; if (profile == p_profile) { // Shut down if active mainwin->CloseProfile(); } Profiles::profiles.remove(name); if (!path.isEmpty()) { if (!removeDir(path)) { QMessageBox::information(this, STR_MessageBox_Error, tr("There was an error deleting the profile directory, you need to manually remove it.")+QString("\n\n%1").arg(path), QMessageBox::Ok); } qDebug() << "Delete" << path; QMessageBox::information(this, STR_MessageBox_Information, tr("Profile '%1' was succesfully deleted").arg(name),QMessageBox::Ok); } updateProfileList(); } } QString ProfileSelector::formatSize(qint64 size) { QStringList units = { tr("Bytes"), tr("KB"), tr("MB"), tr("GB"), tr("TB"), tr("PB") }; int i; double outputSize = size; for (i=0; idiskSpaceSummaries(); qint64 sizeEvents = profile->diskSpaceEvents(); qint64 sizeBackups = profile->diskSpaceBackups(); html += "" "" "" "" "
    "+tr("Summaries:")+""+formatSize(sizeSummaries)+"
    "+tr("Events:")+""+formatSize(sizeEvents)+"
    "+tr("Backups:")+""+formatSize(sizeBackups)+"
    "; } return html; } void ProfileSelector::on_diskSpaceInfo_linkActivated(const QString &link) { QString html; if (link == "show") { html += ""+tr("Hide disk usage information")+""+getProfileDiskInfo(p_profile); showDiskUsage = true; } else { html += ""+tr("Show disk usage information")+""; showDiskUsage = false; } ui->diskSpaceInfo->setText(html); } void ProfileSelector::on_selectionChanged(const QModelIndex &index, const QModelIndex &) { bool enabled = false; if (index.isValid()) { QString name = proxy->data(proxy->index(index.row(), 0, QModelIndex()), Qt::UserRole+2).toString(); auto prof = Profiles::profiles.find(name); if (prof != Profiles::profiles.end()) { enabled = true; QString html = QString(); Profile * profile = prof.value(); if (!profile->user->lastName().isEmpty() && !profile->user->firstName().isEmpty()) { html += tr("Name: %1, %2").arg(profile->user->lastName()).arg(profile->user->firstName())+"
    "; } if (!profile->user->phone().isEmpty()) { html += tr("Phone: %1").arg(profile->user->phone())+"
    "; } if (!profile->user->email().isEmpty()) { html += tr("Email: %1").arg(profile->user->email())+"
    "; } if (!profile->user->address().isEmpty()) { html += "
    "+tr("Address:")+"
    "+profile->user->address().trimmed().replace("\n","
    ")+"
    "; } if (html.isEmpty()) { html += tr("No profile information given")+"
    "; } ui->diskSpaceInfo->setVisible(true); ui->profileInfoGroupBox->setTitle(tr("Profile: %1").arg(name)); ui->profileInfoLabel->setText(html); if (showDiskUsage) { // ugly, but uh.... ui->diskSpaceInfo->setText(""+tr("Hide disk usage information")+""+getProfileDiskInfo(prof.value())); } } else { ui->diskSpaceInfo->setText("Something went wrong"); } } ui->buttonOpenProfile->setEnabled(enabled); ui->buttonEditProfile->setEnabled(enabled); } OSCAR-code-v1.3.1/oscar/profileselector.h000066400000000000000000000033551417327530600201600ustar00rootroot00000000000000/* Profile Selector Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef PROFILESELECTOR_H #define PROFILESELECTOR_H #include #include #include #include "SleepLib/profiles.h" namespace Ui { class ProfileSelector; } class MySortFilterProxyModel2:public QSortFilterProxyModel { Q_OBJECT public: MySortFilterProxyModel2(QObject *parent = 0); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; }; class ProfileSelector : public QWidget { Q_OBJECT public: explicit ProfileSelector(QWidget *parent = 0); ~ProfileSelector(); void updateProfileList(); Profile *SelectProfile(QString profname, bool skippassword); void updateProfileHighlight(QString name); private slots: void on_profileView_doubleClicked(const QModelIndex &index); void on_profileFilter_textChanged(const QString &arg1); void on_buttonOpenProfile_clicked(); void on_buttonEditProfile_clicked(); void on_buttonNewProfile_clicked(); void on_buttonDestroyProfile_clicked(); void on_diskSpaceInfo_linkActivated(const QString &link); void on_selectionChanged(const QModelIndex ¤t, const QModelIndex &previous); void on_resetFilterButton_clicked(); private: QString getProfileDiskInfo(Profile *profile); QString formatSize(qint64 size); Ui::ProfileSelector *ui; QStandardItemModel *model; MySortFilterProxyModel2 *proxy; bool showDiskUsage; }; #endif // PROFILESELECTOR_H OSCAR-code-v1.3.1/oscar/profileselector.ui000066400000000000000000000243111417327530600203410ustar00rootroot00000000000000 ProfileSelector 0 0 912 696 Form Filter: Reset filter to see all profiles QToolButton { background: transparent; border-radius: 8px; border: 2px solid transparent; } QToolButton:hover { border: 2px solid #456789; } QToolButton:pressed { border: 2px solid #456789; background-color: #89abcd; } ... :/icons/refresh.png:/icons/refresh.png Qt::ClickFocus Qt::CustomContextMenu QAbstractItemView::NoEditTriggers true QAbstractItemView::NoSelection QAbstractItemView::SelectRows true false true false 250 0 QFrame::StyledPanel QFrame::Raised Qt::Horizontal QSizePolicy::Preferred 40 20 128 128 :/icons/logo-lg.png true Qt::AlignCenter Qt::Horizontal QSizePolicy::Preferred 40 20 13 true OSCAR Qt::AlignCenter false Version Qt::AlignCenter Qt::Horizontal &Open Profile :/icons/forward.png:/icons/forward.png &Edit Profile Qt::Horizontal &New Profile 0 0 true Profile: None Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 false Please select or create a profile... Qt::AutoText Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true true false Qt::Vertical QSizePolicy::Expanding 20 339 Destroy Profile OSCAR-code-v1.3.1/oscar/rawdata.cpp000066400000000000000000000105241417327530600167310ustar00rootroot00000000000000/* QIODevice wrapper for reading raw binary data * * Copyright (c) 2021-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "rawdata.h" #include #include #include RawDataFile::RawDataFile(QFile & file) : RawDataDevice(file, QFileInfo(file).canonicalFilePath()) { } RawDataDevice::RawDataDevice(QIODevice & device, QString name) : m_device(device), m_name(name) { connect(&m_device, SIGNAL(channelReadyRead(int)), this, SLOT(onChannelReadyRead(int))); connect(&m_device, SIGNAL(readyRead()), this, SLOT(onReadyRead())); connect(&m_device, SIGNAL(readChannelFinished()), this, SLOT(onReadChannelFinished())); connect(&m_device, SIGNAL(aboutToClose()), this, SLOT(onAboutToClose())); if (m_device.isOpen()) { open(m_device.openMode()); } } RawDataDevice::~RawDataDevice() { disconnect(&m_device, SIGNAL(channelReadyRead(int)), this, SLOT(onChannelReadyRead(int))); disconnect(&m_device, SIGNAL(readyRead()), this, SLOT(onReadyRead())); disconnect(&m_device, SIGNAL(readChannelFinished()), this, SLOT(onReadChannelFinished())); disconnect(&m_device, SIGNAL(aboutToClose()), this, SLOT(onAboutToClose())); } void RawDataDevice::onAboutToClose() { emit aboutToClose(); } void RawDataDevice::onChannelReadyRead(int channel) { qWarning() << "RawDataDevice::onChannelReadyRead untested"; emit channelReadyRead(channel); } void RawDataDevice::onReadChannelFinished() { qWarning() << "RawDataDevice::onReadChannelFinished untested"; emit readChannelFinished(); } void RawDataDevice::onReadyRead() { qWarning() << "RawDataDevice::onReadyRead untested"; emit readyRead(); } bool RawDataDevice::waitForReadyRead(int msecs) { return m_device.waitForReadyRead(msecs); } bool RawDataDevice::open(QIODevice::OpenMode mode) { bool ok = false; if (mode & QIODevice::WriteOnly) { // RawDataDevice is intended only for importing external data formats. // Use QDataStream for writing/reading internal data. // TODO: Revisit this if we wrap device connections in a RawDataDevice. qWarning() << "RawDataDevice does not support writing. Use QDataStream."; } else { if (m_device.openMode() == mode) { ok = QIODevice::open(mode); // If the device is already opened, mark the raw device as opened. } else if (m_device.open(mode)) { mode = m_device.openMode(); // Copy over any flags set by the device, e.g. unbuffered. ok = QIODevice::open(mode); } } setErrorString(m_device.errorString()); return ok; } void RawDataDevice::close() { m_device.close(); QIODevice::close(); setErrorString(m_device.errorString()); } void RawDataDevice::syncTextMode(void) { // Sadly setTextModeEnabled() isn't virtual in QIODevice, // so we have to sync the setting before read/write/peek. if (isTextModeEnabled() != m_device.isTextModeEnabled()) { m_device.setTextModeEnabled(isTextModeEnabled()); } } qint64 RawDataDevice::readData(char *data, qint64 maxSize) { syncTextMode(); qint64 result = m_device.read(data, maxSize); // note that readData is also used by peek, so pos may diverge setErrorString(m_device.errorString()); return result; } qint64 RawDataDevice::writeData(const char */*data*/, qint64 /*len*/) { syncTextMode(); // This method is required in order to create a concrete instance of QIODevice, // but we should never be writing raw data. qWarning() << name() << "writing not supported"; setErrorString("RawDataDevice does not support writing."); return -1; } bool RawDataDevice::seek(qint64 pos) { bool ok = m_device.seek(pos); if (ok) { QIODevice::seek(pos); setErrorString(m_device.errorString()); } return ok; } bool RawDataDevice::isSequential() const { bool is_sequential = m_device.isSequential(); Q_ASSERT(is_sequential == false); // Before removing this, add tests to RawDataTests to confirm that sequential devices work! return is_sequential; } bool RawDataDevice::canReadLine() const { return m_device.canReadLine() || QIODevice::canReadLine(); } qint64 RawDataDevice::size() const { return m_device.size(); } OSCAR-code-v1.3.1/oscar/rawdata.h000066400000000000000000000031421417327530600163740ustar00rootroot00000000000000/* QIODevice wrapper for reading raw binary data * * Copyright (c) 2021-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef RAWDATA_H #define RAWDATA_H #include #include // Wrap an arbitrary QIODevice with a name (and TODO: endian-aware decoding functions), // passing through requests to the underlying device. class RawDataDevice : public QIODevice { Q_OBJECT public: RawDataDevice(QIODevice & device, QString name); virtual ~RawDataDevice(); public: virtual bool isSequential() const; virtual bool open(QIODevice::OpenMode mode); virtual void close(); virtual qint64 size() const; virtual bool seek(qint64 pos); virtual bool canReadLine() const; virtual bool waitForReadyRead(int msecs); protected: virtual qint64 readData(char *data, qint64 maxSize); virtual qint64 writeData(const char *data, qint64 len); QIODevice & m_device; QString m_name; public: QString name() const { return m_name; } private: void syncTextMode(); protected slots: void onAboutToClose(); void onChannelReadyRead(int); void onReadChannelFinished(); void onReadyRead(); public: // TODO: add get/set endian, read16/read32/reads16/reads32, tests }; // Convenience class for wrapping files, using their canonical path as the device name. class RawDataFile : public RawDataDevice { Q_OBJECT public: RawDataFile(class QFile & file); }; #endif // RAWDATA_H OSCAR-code-v1.3.1/oscar/reports.cpp000066400000000000000000000574541417327530600170210ustar00rootroot00000000000000/* Reports/Printing Module * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include "reports.h" #include "mainwindow.h" #include "common_gui.h" #include "SleepLib/progressdialog.h" #include "version.h" extern MainWindow *mainwin; Report::Report() { } void Report::PrintReport(gGraphView *gv, QString name, QDate date) { if (!gv) { return; } Session *journal = nullptr; //QDate d=QDate::currentDate(); int visgraphs = gv->visibleGraphs(); if (visgraphs == 0) { mainwin->Notify(QObject::tr("There are no graphs visible to print")); return; } bool print_bookmarks = false; if (name == STR_TR_Daily) { QVariantList book_start; journal = mainwin->getDaily()->GetJournalSession(mainwin->getDaily()->getDate()); if (journal && journal->settings.contains(Bookmark_Start)) { book_start = journal->settings[Bookmark_Start].toList(); if (book_start.size() > 0) { if (QMessageBox::question(mainwin, STR_TR_Bookmarks, QObject::tr("Would you like to show bookmarked areas in this report?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { print_bookmarks = true; } } } } QPrinter *printer; bool aa_setting = AppSetting->antiAliasing(); bool force_antialiasing = aa_setting; printer = new QPrinter(QPrinter::HighResolution); #ifdef Q_OS_LINUX QString username = p_profile->Get(QString("_{") + QString(STR_UI_UserName) + "}_"); printer->setPrinterName("Print to File (PDF)"); printer->setOutputFormat(QPrinter::PdfFormat); QString filename = p_pref->Get("{home}/") + name + username + date.toString(Qt::ISODate) + ".pdf"; printer->setOutputFileName(filename); #endif printer->setPrintRange(QPrinter::AllPages); printer->setOrientation(QPrinter::Portrait); printer->setFullPage(false); // This has nothing to do with scaling printer->setNumCopies(1); printer->setPageMargins(10, 10, 10, 10, QPrinter::Millimeter); QPrintDialog dialog(printer); #ifdef Q_OS_MAC // QTBUG-17913 QApplication::processEvents(); #endif if (dialog.exec() != QDialog::Accepted) { delete printer; return; } QPainter painter(printer); ProgressDialog progress(mainwin); progress.setMessage(QObject::tr("Printing %1 Report").arg(name)); QPixmap icon = QPixmap(":/icons/logo-md.png").scaled(64,64); progress.setPixmap(icon); progress.open(); GLint gw; gw = 2048; // Rough guess.. No GL_MAX_RENDERBUFFER_SIZE in mingw.. :( //QSizeF pxres=printer->paperSize(QPrinter::DevicePixel); QRect prect = printer->pageRect(); float ratio = float(prect.height()) / float(prect.width()); float virt_width = gw; float virt_height = virt_width * ratio; painter.setWindow(0, 0, virt_width, virt_height); painter.setViewport(0, 0, prect.width(), prect.height()); painter.setViewTransformEnabled(true); QFont report_font = *defaultfont; QFont medium_font = *mediumfont; QFont title_font = *bigfont; float normal_height = 30; //fm2.ascent(); report_font.setPixelSize(normal_height); medium_font.setPixelSize(40); title_font.setPixelSize(90); painter.setFont(report_font); //QFontMetrics fm2(*defaultfont); qDebug() << "Printer Resolution is" << virt_width << "x" << virt_height; const int graphs_per_page = 6; float full_graph_height = (virt_height - (normal_height * graphs_per_page)) / float(graphs_per_page); QString title = QObject::tr("%1 Report").arg(name); painter.setFont(title_font); int top = 0; QRectF bounds = painter.boundingRect(QRectF(0, top, virt_width, 0), title, QTextOption(Qt::AlignHCenter | Qt::AlignTop)); painter.drawText(bounds, title, QTextOption(Qt::AlignHCenter | Qt::AlignTop)); top += bounds.height() + 90 / 2.0; painter.setFont(report_font); int maxy = 0; if (AppSetting->showPersonalData() && !p_profile->user->firstName().isEmpty()) { QString userinfo = STR_TR_Name + QString(":\t %1, %2\n"). arg(p_profile->user->lastName()). arg(p_profile->user->firstName()); userinfo += STR_TR_DOB + QString(":\t%1\n"). arg(p_profile->user->DOB().toString(Qt::SystemLocaleShortDate)); if (!p_profile->doctor->patientID().isEmpty()) { userinfo += STR_TR_PatientID + QString(":\t%1\n").arg(p_profile->doctor->patientID()); } userinfo += STR_TR_Phone + QString(":\t%1\n").arg(p_profile->user->phone()); userinfo += STR_TR_Email + QString(":\t%1\n").arg(p_profile->user->email()); if (!p_profile->user->address().isEmpty()) { userinfo += "\n" + STR_TR_Address + QString(":\n%1").arg(p_profile->user->address()); } QRectF bounds = painter.boundingRect(QRectF(0, top, virt_width, 0), userinfo, QTextOption(Qt::AlignLeft | Qt::AlignTop)); painter.drawText(bounds, userinfo, QTextOption(Qt::AlignLeft | Qt::AlignTop)); if (bounds.height() > maxy) { maxy = bounds.height(); } } Machine *cpap = nullptr, *oxi = nullptr; int graph_slots = 0; Day * day = p_profile->GetGoodDay(mainwin->getDaily()->getDate(), MT_CPAP); if (day) cpap = day->machine(MT_CPAP); if (name == STR_TR_Daily) { QString cpapinfo = date.toString(Qt::SystemLocaleLongDate) + "\n\n"; if (cpap) { time_t f = day->first(MT_CPAP) / 1000L; time_t l = day->last(MT_CPAP) / 1000L; int tt = qint64(day->total_time(MT_CPAP)) / 1000L; int h = tt / 3600; int m = (tt / 60) % 60; int s = tt % 60; cpapinfo += STR_TR_MaskTime + QObject::tr(": %1 hours, %2 minutes, %3 seconds\n").arg(h).arg(m).arg(s); cpapinfo += STR_TR_BedTime + ": " + QDateTime::fromTime_t(f).time().toString("HH:mm:ss") + " "; cpapinfo += STR_TR_WakeUp + ": " + QDateTime::fromTime_t(l).time().toString("HH:mm:ss") + "\n\n"; cpapinfo += STR_TR_Machine + ": "; cpapinfo += cpap->brand() + " " + cpap->model(); QString mn = cpap->modelnumber(); if (!mn.isEmpty()) { cpapinfo += " (" + mn + ")"; } cpapinfo += "\n"; cpapinfo += STR_TR_Mode + ": " + day->getCPAPModeStr() + "\n"; cpapinfo += day->getPressureSettings() + "\n"; QString pressurerelief = day->getPressureRelief(); if (pressurerelief.compare(STR_TR_None)) { cpapinfo += /*QObject::tr("Pressure Relief")+": "+ */day->getPressureRelief() + "\n"; } float ahi = day->count(AllAhiChannels); if (p_profile->general->calculateRDI()) { ahi += day->count(CPAP_RERA); } float hours = day->hours(MT_CPAP); ahi /= hours; float csr = (100.0 / hours) * (day->sum(CPAP_CSR) / 3600.0); //float pb = (100.0 / hours) * (day->sum(CPAP_PB) / 3600.0); float uai = day->count(CPAP_Apnea) / hours; float oai = day->count(CPAP_Obstructive) / hours; float ai = day->count(CPAP_AllApnea) / hours; float hi = (day->count(CPAP_ExP) + day->count(CPAP_Hypopnea)) / hours; float cai = day->count(CPAP_ClearAirway) / hours; float rei = day->count(CPAP_RERA) / hours; float vsi = day->count(CPAP_VSnore) / hours; if (day->channelHasData(CPAP_VSnore2)) { // PRS1 puts its 2-minute VS count in a different channel rather than reporting each incident. vsi = day->sum(CPAP_VSnore2) / hours; } float fli = day->count(CPAP_FlowLimit) / hours; // float sai = day->count(CPAP_SensAwake) / hours; float nri = day->count(CPAP_NRI) / hours; float lki = day->count(CPAP_LeakFlag) / hours; float exp = day->count(CPAP_ExP) / hours; int piesize = (2048.0 / 8.0) * 1.3; // 1.5" in size //float fscale=font_scale; //if (!highres) // fscale=1; QString stats; painter.setFont(medium_font); if (p_profile->general->calculateRDI()) { stats = QObject::tr("RDI\t%1\n").arg(ahi, 0, 'f', 2); } else { stats = QObject::tr("AHI\t%1\n").arg(ahi, 0, 'f', 2); } QRectF bounds = painter.boundingRect(QRectF(0, 0, virt_width, 0), stats, QTextOption(Qt::AlignRight)); painter.drawText(bounds, stats, QTextOption(Qt::AlignRight)); mainwin->getDaily()->eventBreakdownPie()->setShowTitle(false); mainwin->getDaily()->eventBreakdownPie()->setMargins(0, 0, 0, 0); QPixmap ebp; if (ahi > 0) { ebp = mainwin->getDaily()->eventBreakdownPie()->renderPixmap(piesize, piesize, true); } else { ebp = QPixmap(":/icons/smileyface.png"); } if (!ebp.isNull()) { painter.drawPixmap(virt_width - piesize, bounds.height(), piesize, piesize, ebp); } mainwin->getDaily()->eventBreakdownPie()->setShowTitle(true); cpapinfo += "\n\n"; painter.setFont(report_font); //bounds=painter.boundingRect(QRectF((virt_width/2)-(virt_width/6),top,virt_width/2,0),cpapinfo,QTextOption(Qt::AlignLeft)); bounds = painter.boundingRect(QRectF(0, top, virt_width, 0), cpapinfo, QTextOption(Qt::AlignHCenter)); painter.drawText(bounds, cpapinfo, QTextOption(Qt::AlignHCenter)); int ttop = bounds.height(); stats = QObject::tr("AI=%1 HI=%2 CAI=%3 ").arg(oai, 0, 'f', 2).arg(hi, 0, 'f', 2).arg(cai, 0, 'f', 2); if (cpap->loaderName() == STR_MACH_PRS1) { // FIXME: PB / CSR are now separate flags stats += QObject::tr("REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%%") .arg(rei, 0, 'f', 2).arg(vsi, 0, 'f', 2) .arg(fli, 0, 'f', 2).arg(csr, 0, 'f', 2); } else if (cpap->loaderName() == STR_MACH_ResMed) { stats += QObject::tr("UAI=%1 ").arg(uai, 0, 'f', 2); } else if (cpap->loaderName() == STR_MACH_Intellipap) { stats += QObject::tr("NRI=%1 LKI=%2 EPI=%3") .arg(nri, 0, 'f', 2).arg(lki, 0, 'f', 2).arg(exp, 0,'f', 2); } else if (cpap->loaderName() == STR_MACH_SleepStyle) { stats += QObject::tr("AI=%1 ").arg(ai, 0, 'f', 2); } bounds = painter.boundingRect(QRectF(0, top + ttop, virt_width, 0), stats, QTextOption(Qt::AlignHCenter)); painter.drawText(bounds, stats, QTextOption(Qt::AlignHCenter)); ttop += bounds.height(); if (journal) { stats = ""; if (journal->settings.contains(Journal_Weight)) { stats += STR_TR_Weight + QString(" %1 "). arg(weightString(journal->settings[Journal_Weight].toDouble())); } if (journal->settings.contains(Journal_BMI)) { stats += STR_TR_BMI + QString(" %1 "). arg(journal->settings[Journal_BMI].toDouble(), 0, 'f', 2); } if (journal->settings.contains(Journal_ZombieMeter)) { stats += STR_TR_Zombie + QString(" %1/10 "). arg(journal->settings[Journal_ZombieMeter].toDouble(), 0, 'f', 0); } if (!stats.isEmpty()) { bounds = painter.boundingRect(QRectF(0, top + ttop, virt_width, 0), stats, QTextOption(Qt::AlignHCenter)); painter.drawText(bounds, stats, QTextOption(Qt::AlignHCenter)); ttop += bounds.height(); } ttop += normal_height; if (journal->settings.contains(Journal_Notes)) { QTextDocument doc; doc.setHtml(journal->settings[Journal_Notes].toString()); stats = doc.toPlainText(); //doc.drawContents(&painter); // doesn't work as intended.. bounds = painter.boundingRect(QRectF(0, top + ttop, virt_width, 0), stats, QTextOption(Qt::AlignHCenter)); painter.drawText(bounds, stats, QTextOption(Qt::AlignHCenter)); bounds.setLeft(virt_width / 4); bounds.setRight(virt_width - (virt_width / 4)); QPen pen(Qt::black); pen.setWidth(4); painter.setPen(pen); painter.drawRect(bounds); ttop += bounds.height() + normal_height; } } if (ttop > maxy) { maxy = ttop; } } else { bounds = painter.boundingRect(QRectF(0, top + maxy, virt_width, 0), cpapinfo, QTextOption(Qt::AlignCenter)); painter.drawText(bounds, cpapinfo, QTextOption(Qt::AlignCenter)); if (maxy + bounds.height() > maxy) { maxy = maxy + bounds.height(); } } } else if (name == STR_TR_Overview) { QDateTime first = QDateTime::fromTime_t((*gv)[0]->min_x / 1000L); QDateTime last = QDateTime::fromTime_t((*gv)[0]->max_x / 1000L); QString ovinfo = QObject::tr("Reporting from %1 to %2"). arg(first.date().toString(Qt::SystemLocaleShortDate)). arg(last.date().toString(Qt::SystemLocaleShortDate)); QRectF bounds = painter.boundingRect(QRectF(0, top, virt_width, 0), ovinfo, QTextOption(Qt::AlignHCenter)); painter.drawText(bounds, ovinfo, QTextOption(Qt::AlignHCenter)); if (bounds.height() > maxy) { maxy = bounds.height(); } } /*else if (name == STR_TR_Oximetry) { QString ovinfo = QObject::tr("Reporting data goes here"); QRectF bounds = painter.boundingRect(QRectF(0, top, virt_width, 0), ovinfo, QTextOption(Qt::AlignHCenter)); painter.drawText(bounds, ovinfo, QTextOption(Qt::AlignHCenter)); if (bounds.height() > maxy) { maxy = bounds.height(); } }*/ top += maxy; graph_slots = graphs_per_page - ((virt_height - top) / (full_graph_height + normal_height)); bool first = true; QStringList labels; QVector graphs; QVector start, end; qint64 savest, saveet; gGraph *g; gv->GetXBounds(savest, saveet); for (int i=0;i < gv->size(); i++) { g = (*gv)[i]; if (g->isEmpty() || !g->visible()) continue; if (g->group() == 0) { savest = g->min_x; saveet = g->max_x; break; } } qint64 st = savest, et = saveet; bool lineCursorMode = AppSetting->lineCursorMode(); AppSetting->setLineCursorMode(false); if (name == STR_TR_Daily) { if (!print_bookmarks) { for (int i = 0; i < gv->size(); i++) { g = (*gv)[i]; if (g->isEmpty()) { continue; } if (!g->visible()) { continue; } QString label = ""; if (!g->isSnapshot() && (g->name() == schema::channel[CPAP_FlowRate].code())) { st = day->first(); et = day->last(); // If the current selection is more than a minute smaller than the whole night: if ((et - st) - (g->max_x - g->min_x) > 60000) { // First draw the flow waveform in the context of the entire day (matching the flags chart) start.push_back(st); end.push_back(et); graphs.push_back(g); labels.push_back(QObject::tr("Entire Day's Flow Waveform")); // And add a label to the selected flow graph below label = QObject::tr("Current Selection"); } } start.push_back(g->min_x); end.push_back(g->max_x); graphs.push_back(g); labels.push_back(label); } } else { const QString EntireDay = QObject::tr("Entire Day"); if (journal) { if (journal->settings.contains(Bookmark_Start)) { QVariantList st1 = journal->settings[Bookmark_Start].toList(); QVariantList et1 = journal->settings[Bookmark_End].toList(); QStringList notes = journal->settings[Bookmark_Notes].toStringList(); gGraph *flow = (*gv)[schema::channel[CPAP_FlowRate].code()], *spo2 = (*gv)[schema::channel[OXI_SPO2].code()], *pulse = (*gv)[schema::channel[OXI_Pulse].code()]; if (cpap && flow && !flow->isEmpty() && flow->visible()) { labels.push_back(EntireDay); start.push_back(day->first(MT_CPAP)); end.push_back(day->last(MT_CPAP)); graphs.push_back(flow); } if (oxi && spo2 && !spo2->isEmpty() && spo2->visible()) { labels.push_back(EntireDay); start.push_back(day->first(MT_OXIMETER)); end.push_back(day->last(MT_OXIMETER)); graphs.push_back(spo2); } if (oxi && pulse && !pulse->isEmpty() && pulse->visible()) { labels.push_back(EntireDay); start.push_back(day->first(MT_OXIMETER)); end.push_back(day->last(MT_OXIMETER)); graphs.push_back(pulse); } for (int i = 0; i < notes.size(); i++) { if (flow && !flow->isEmpty() && flow->visible()) { labels.push_back(notes.at(i)); start.push_back(st1.at(i).toLongLong()); end.push_back(et1.at(i).toLongLong()); graphs.push_back(flow); } if (spo2 && !spo2->isEmpty() && spo2->visible()) { labels.push_back(notes.at(i)); start.push_back(st1.at(i).toLongLong()); end.push_back(et1.at(i).toLongLong()); graphs.push_back(spo2); } if (pulse && !pulse->isEmpty() && pulse->visible()) { labels.push_back(notes.at(i)); start.push_back(st1.at(i).toLongLong()); end.push_back(et1.at(i).toLongLong()); graphs.push_back(pulse); } } } } for (int i = 0; i < gv->size(); i++) { gGraph *g = (*gv)[i]; if (g->isEmpty()) { continue; } if (!g->visible()) { continue; } if ((g->name() != schema::channel[CPAP_FlowRate].code()) && (g->name() != schema::channel[OXI_SPO2].code()) && (g->name() != schema::channel[OXI_Pulse].code())) { start.push_back(st); end.push_back(et); graphs.push_back(g); labels.push_back(""); } } } } else { for (int i = 0; i < gv->size(); i++) { gGraph *g = (*gv)[i]; if (g->isEmpty()) { continue; } if (!g->visible()) { continue; } start.push_back(st); end.push_back(et); graphs.push_back(g); labels.push_back(""); // date range? } } int pages = ceil(float(graphs.size() + graph_slots) / float(graphs_per_page)); progress.setProgressMax(graphs.size()); int page = 1; int gcnt = 0; for (int i = 0; i < graphs.size(); i++) { if ((top + full_graph_height + normal_height) > virt_height) { top = 0; gcnt = 0; first = true; if (page > pages) { break; } if (!printer->newPage()) { qWarning("failed in flushing page to disk, disk full?"); break; } } if (first) { QDateTime timestamp = QDateTime::currentDateTime(); QString footer = QString("%1 %2 %3").arg(timestamp.toString(MedDateFormat+" hh:mm")) .arg(STR_TR_OSCAR) .arg(getVersion()); QRectF bounds = painter.boundingRect(QRectF(0, virt_height, virt_width, normal_height), footer, QTextOption(Qt::AlignHCenter)); painter.drawText(bounds, footer, QTextOption(Qt::AlignHCenter)); QString pagestr = QObject::tr("Page %1 of %2").arg(page).arg(pages); QRectF pagebnds = painter.boundingRect(QRectF(0, virt_height, virt_width, normal_height), pagestr, QTextOption(Qt::AlignRight)); painter.drawText(pagebnds, pagestr, QTextOption(Qt::AlignRight)); first = false; page++; } gGraph *g = graphs[i]; if (!g->isSnapshot()) { g->SetXBounds(start[i], end[i]); } g->deselect(); QString label = labels[i]; if (!label.isEmpty()) { //label+=":"; top += normal_height / 3; QRectF bounds = painter.boundingRect(QRectF(0, top, virt_width, 0), label, QTextOption(Qt::AlignHCenter)); //QRectF pagebnds=QRectF(0,top,virt_width,normal_height); painter.drawText(bounds, label, QTextOption(Qt::AlignHCenter)); top += bounds.height(); } else { top += normal_height / 2; } AppSetting->setAntiAliasing(force_antialiasing); int tmb = g->m_marginbottom; g->m_marginbottom = 0; //painter.beginNativePainting(); //g->showTitle(false); int hhh = full_graph_height - normal_height; QPixmap pm2 = g->renderPixmap(virt_width, hhh, 1); QImage pm = pm2.toImage(); //fscale); pm2.detach(); //g->showTitle(true); //painter.endNativePainting(); g->m_marginbottom = tmb; AppSetting->setAntiAliasing(aa_setting); if (!pm.isNull()) { painter.drawImage(QRect(0, top, pm.width(), pm.height()), pm); //painter.drawImage(0,top,virt_width,full_graph_height-normal_height,pm); } top += full_graph_height; gcnt++; progress.setProgressValue(i); QApplication::processEvents(); } gv->SetXBounds(savest, saveet); painter.end(); progress.close(); delete printer; AppSetting->setLineCursorMode(lineCursorMode); } OSCAR-code-v1.3.1/oscar/reports.h000066400000000000000000000015621417327530600164530ustar00rootroot00000000000000/* Reports Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef REPORTS_H #define REPORTS_H #include "Graphs/gGraphView.h" class Report { public: Report(); /*! \fn void PrintReport gGraphView *gv,QString name, QDate date=QDate::currentDate()); \brief Prepares a report using gGraphView object, and sends to a created QPrinter object \param gGraphView *gv GraphView Object containing which graph set to print \param QString name Report Title \param QDate date */ static void PrintReport(gGraphView *gv, QString name, QDate date = QDate::currentDate()); }; #endif // REPORTS_H OSCAR-code-v1.3.1/oscar/reports.ui000066400000000000000000000021621417327530600166360ustar00rootroot00000000000000 Report 0 0 549 338 Form 0 0 -14 Qt::LeftToRight about:blank QWebView QWidget
    QWebView
    OSCAR-code-v1.3.1/oscar/scripts/000077500000000000000000000000001417327530600162675ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/scripts/build_number000066400000000000000000000000021417327530600206510ustar00rootroot000000000000001 OSCAR-code-v1.3.1/oscar/scripts/fetchUpstream.sh000077500000000000000000000000721417327530600214370ustar00rootroot00000000000000#!/bin/bash git fetch upstream git merge upstream/master OSCAR-code-v1.3.1/oscar/sessionbar.cpp000066400000000000000000000151231417327530600174560ustar00rootroot00000000000000/* SessionBar Graph Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include "sessionbar.h" SBSeg::SBSeg() { session = nullptr; color = QColor(); highlight = false; } SBSeg::SBSeg(Session *sess, QColor col) { session = sess; color = col; highlight = false; } //SBSeg::SBSeg(const SBSeg & a) //{ // session=(Session *)a.session; // color=a.color; // highlight=a.highlight; //} SessionBar::SessionBar(QWidget *parent) : QWidget(parent) { timer.setParent(this); m_selectIDX = -1; m_selectColor = Qt::red; m_selectMode = false; } //SessionBar::SessionBar(const SessionBar & copy) // :QWidget(this) //{ // timer.setParent(this); // QVector::const_iterator i; // for (i=copy.segments.begin();i!=copy.segments.end();++i) { // segments.push_back(*i); // } //} SessionBar::~SessionBar() { } void SessionBar::updateTimer() { if (!underMouse()) { QVector::iterator i; for (i = segments.begin(); i != segments.end(); ++i) { (*i).highlight = false; } } else { timer.singleShot(50, this, SLOT(updateTimer())); } update(); } Session * SessionBar::session(int idx) { if (idx >= segments.size()) { qCritical() << "SessionBar::session called with out of range index"; return nullptr; } return segments[idx].session; } SegType SessionBar::min() { if (segments.isEmpty()) { return 0; } QVector::iterator i = segments.begin(); SegType min = (*i).session->first(); i++; qint64 val; for (; i != segments.end(); ++i) { val = (*i).session->first(); if (min > val) { min = val; } } return min; } SegType SessionBar::max() { if (segments.isEmpty()) { return 0; } QVector::iterator i = segments.begin(); SegType max = (*i).session->last(); i++; qint64 val; for (; i != segments.end(); ++i) { val = (*i).session->last(); if (max < val) { max = val; } } return max; } QColor brighten(QColor, float f); void SessionBar::mousePressEvent(QMouseEvent *ev) { SegType mn = min(); SegType mx = max(); if (mx < mn) { return; } SegType total = mx - mn; double px = double(width() ) / double(total); double sx, ex; QVector::iterator i; int cnt = 0; for (i = segments.begin(); i != segments.end(); ++i) { Session *sess = (*i).session; sx = double(sess->first() - mn) * px; ex = double(sess->last() - mn) * px; if (ex > width()) { ex = width(); } //ex-=sx; if ((ev->x() >= sx) && (ev->x() < ex) && (ev->y() > 0) && (ev->y() < height())) { m_selectIDX = cnt; emit sessionClicked((*i).session); break; } cnt++; } if (timer.isActive()) { timer.stop(); } timer.singleShot(50, this, SLOT(updateTimer())); } void SessionBar::mouseMoveEvent(QMouseEvent *ev) { SegType mn = min(); SegType mx = max(); if (mx < mn) { return; } SegType total = mx - mn; double px = double(width() - 5) / double(total); double sx, ex; QVector::iterator i; for (i = segments.begin(); i != segments.end(); ++i) { SBSeg &seg = *i; sx = double(seg.session->first() - mn) * px; ex = double(seg.session->last() - mn) * px; if (ex > width() - 5) { ex = width() - 5; } //ex-=sx; if ((ev->x() > sx) && (ev->x() < ex) && (ev->y() > 0) && (ev->y() < height())) { seg.highlight = true; } else { seg.highlight = false; } } if (timer.isActive()) { timer.stop(); } timer.singleShot(50, this, SLOT(updateTimer())); } void SessionBar::paintEvent(QPaintEvent *) { QPainter painter(this); QRect rect(1, 1, width() - 2, height() - 2); painter.drawRect(rect); SegType mn = min(); SegType mx = max(); if (mx < mn) { return; } SegType total = mx - mn; double px = double(width() - 5) / double(total); double sx, ex; QVector::iterator i; //QRect selectRect; int cnt = 0; for (i = segments.begin(); i != segments.end(); ++i) { SBSeg &seg = *i; qint64 mm = seg.session->first(), MM = seg.session->last(), L = MM - mm; sx = double(mm - mn) * px; ex = double(MM - mn) * px; if (ex > width() - 5) { ex = width() - 5; } ex -= sx; int len = L / 1000L; int h = len / 3600; int m = (len / 60) % 60; //int s=len % 60; QString msg = tr("%1h %2m").arg((short)h, 1, 10, QChar('0')).arg((short)m, 1, 10, QChar('0')); //painter.setBrush(QBrush((*i).color); QRect segrect(3 + sx, 3, ex, height() - 6); if (seg.session->enabled()) { QLinearGradient linearGrad(QPointF(0, 0), QPointF(0, height() / 2)); linearGrad.setSpread(QGradient::ReflectSpread); QColor col = seg.color; if (m_selectMode && (cnt == m_selectIDX)) { // col = m_selectColor; } else if (seg.highlight) { col = brighten(col); } linearGrad.setColorAt(0, col); linearGrad.setColorAt(1, brighten(col)); QBrush brush(linearGrad); painter.fillRect(segrect, brush); } else { if (seg.highlight) { QColor col = QColor("#f0f0f0"); painter.fillRect(segrect, col); } //msg="Off"; } QRect rect = painter.boundingRect(segrect, Qt::AlignCenter, msg); if (rect.width() < segrect.width()) { painter.setPen(Qt::black); painter.drawText(segrect, Qt::AlignCenter, msg); } if (m_selectMode && (cnt == m_selectIDX)) { painter.setPen(QPen(m_selectColor, 3)); } else { painter.setPen(QPen(Qt::black, 1)); } painter.drawRect(segrect); cnt++; } if (!cnt) { QString msg = tr("No Sessions Present"); QRect rct = painter.boundingRect(this->rect(), Qt::AlignCenter, msg); painter.setPen(Qt::black); painter.drawText(rct, Qt::AlignCenter, msg); } } OSCAR-code-v1.3.1/oscar/sessionbar.h000066400000000000000000000033351417327530600171250ustar00rootroot00000000000000/* SessionBar Graph Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef SESSIONBAR_H #define SESSIONBAR_H #include #include #include #include #include "SleepLib/session.h" typedef qint64 SegType; class SBSeg { public: SBSeg(); SBSeg(Session *sess, QColor col); // SBSeg(const SBSeg & a); QColor color; bool highlight; Session *session; }; class SessionBar : public QWidget { Q_OBJECT public: SessionBar(QWidget *parent = 0); // // Q_DECLARE_METATYPE requires a copy-constructor // SessionBar(const SessionBar &); virtual ~SessionBar(); void clear() { segments.clear(); m_selectIDX = -1; } void add(Session *sess, QColor col) { if (sess) { segments.push_back(SBSeg(sess, col)); } } void setSelectMode(bool b) { m_selectMode = b; } void setSelectColor(QColor col) { m_selectColor = col; } int count() { return segments.size(); } int selected() { return m_selectIDX; } Session * session(int idx); void setSelected(int idx) { m_selectIDX = idx; } protected slots: void updateTimer(); signals: void sessionClicked(Session *sess); protected: void paintEvent(QPaintEvent *event); void mouseMoveEvent(QMouseEvent *); void mousePressEvent(QMouseEvent *); SegType min(); SegType max(); QVector segments; QTimer timer; int m_selectIDX; bool m_selectMode; QColor m_selectColor; }; //Q_DECLARE_METATYPE(SessionBar) #endif OSCAR-code-v1.3.1/oscar/statistics.cpp000066400000000000000000002033171417327530600175040ustar00rootroot00000000000000/* Statistics Report Generator Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include #include "mainwindow.h" #include "statistics.h" #include "cprogressbar.h" #include "SleepLib/common.h" #include "version.h" extern MainWindow *mainwin; // HTML components that make up Statistics page and printed report QString htmlReportHeader = ""; // Page header QString htmlReportHeaderPrint = ""; // Page header QString htmlUsage = ""; // CPAP and Oximetry QString htmlMachineSettings = ""; // Machine (formerly Rx) changes QString htmlMachines = ""; // Machines used in this profile QString htmlReportFooter = ""; // Page footer QString resizeHTMLPixmap(QPixmap &pixmap, int width, int height) { QByteArray byteArray; QBuffer buffer(&byteArray); // use buffer to store pixmap into byteArray buffer.open(QIODevice::WriteOnly); pixmap.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation).save(&buffer, "PNG"); return QString("logo"); } QString formatTime(float time) { int hours = time; int seconds = time * 3600.0; int minutes = (seconds / 60) % 60; //seconds %= 60; return QString().sprintf("%02i:%02i", hours, minutes); //,seconds); } QDataStream & operator>>(QDataStream & in, RXItem & rx) { in >> rx.start; in >> rx.end; in >> rx.days; in >> rx.ahi; in >> rx.rdi; in >> rx.hours; QString loadername; in >> loadername; QString serial; in >> serial; MachineLoader * loader = GetLoader(loadername); if (loader) { rx.machine = p_profile->lookupMachine(serial, loadername); } else { qDebug() << "Bad machine object" << loadername << serial; rx.machine = nullptr; } in >> rx.relief; in >> rx.mode; in >> rx.pressure; QList list; in >> list; rx.dates.clear(); for (int i=0; iFindDay(date, MT_CPAP); } in >> rx.s_count; in >> rx.s_sum; return in; } QDataStream & operator<<(QDataStream & out, const RXItem & rx) { out << rx.start; out << rx.end; out << rx.days; out << rx.ahi; out << rx.rdi; out << rx.hours; out << rx.machine->loaderName(); out << rx.machine->serial(); out << rx.relief; out << rx.mode; out << rx.pressure; out << rx.dates.keys(); out << rx.s_count; out << rx.s_sum; return out; } void Statistics::loadRXChanges() { QString path = p_profile->Get("{" + STR_GEN_DataFolder + "}/RXChanges.cache" ); QFile file(path); if (!file.open(QFile::ReadOnly)) { qDebug() << "Could not open" << path << "for reading, error code" << file.error() << file.errorString(); return; } QDataStream in(&file); in.setByteOrder(QDataStream::LittleEndian); quint32 mag32; if (in.version() != QDataStream::Qt_5_0) { } in >> mag32; if (mag32 != magic) { return; } quint16 version; in >> version; in >> rxitems; } void Statistics::saveRXChanges() { QString path = p_profile->Get("{" + STR_GEN_DataFolder + "}/RXChanges.cache" ); QFile file(path); if (!file.open(QFile::WriteOnly)) { qWarning() << "Could not open" << path << "for writing, error code" << file.error() << file.errorString(); return; } QDataStream out(&file); out.setByteOrder(QDataStream::LittleEndian); out.setVersion(QDataStream::Qt_5_0); out << magic; out << (quint16)0; out << rxitems; } bool rxAHILessThan(const RXItem * rx1, const RXItem * rx2) { return (double(rx1->ahi) / rx1->hours) < (double(rx2->ahi) / rx2->hours); } void Statistics::updateRXChanges() { // Set conditional progress bar. CProgressBar * progress = new CProgressBar (QObject::tr("Updating Statistics cache"), mainwin, p_profile->daylist.count()); // Clear loaded rx cache rxitems.clear(); // Read the cache from disk loadRXChanges(); QMap::iterator di; QMap::iterator it; QMap::iterator it_end = p_profile->daylist.end(); QMap::iterator ri; QMap::iterator ri_end = rxitems.end(); quint64 tmp; // Scan through each daylist in ascending date order for (it = p_profile->daylist.begin(); it != it_end; ++it) { const QDate & date = it.key(); Day * day = it.value(); progress->add (1); // Increment progress bar Machine * mach = day->machine(MT_CPAP); if (mach == nullptr) continue; if (day->first() == 0) { // Ignore invalid dates qDebug() << "Statistics::updateRXChanges ignoring day with first=0"; continue; } bool fnd = false; // Scan through pre-existing rxitems list and see if this day is already there. ri_end = rxitems.end(); for (ri = rxitems.begin(); ri != ri_end; ++ri) { RXItem & rx = ri.value(); // Is it date between this rxitems entry date range? if ((date >= rx.start) && (date <= rx.end)) { if (rx.dates.contains(date)) { // Already there, abort. fnd = true; break; } // First up, check if fits in date range, but isn't loaded for some reason // Need summaries for this, so load them if not present. day->OpenSummary(); // Get list of Event Flags used in this day QList flags = day->getSortedMachineChannels(MT_CPAP, schema::FLAG | schema::MINOR_FLAG | schema::SPAN); // Generate the pressure/mode/relief strings QString relief = day->getPressureRelief(); QString mode = day->getCPAPModeStr(); QString pressure = day->getPressureSettings(); // Do this days settings match this rx cache entry? if ((rx.relief == relief) && (rx.mode == mode) && (rx.pressure == pressure) && (rx.machine == mach)) { // Update rx cache summaries for each event flag for (int i=0; i < flags.size(); i++) { ChannelID code = flags.at(i); rx.s_count[code] += day->count(code); rx.s_sum[code] += day->sum(code); } // Update AHI/RDI/Time counts tmp = day->count(AllAhiChannels); rx.ahi += tmp; rx.rdi += tmp + day->count(CPAP_RERA); rx.hours += day->hours(MT_CPAP); // Add this date to RX cache rx.dates[date] = day; rx.days = rx.dates.size(); // and we are done fnd = true; break; } else { // In this case, the day is within the rx date range, but settings doesn't match the others // So we need to split the rx cache record and insert the new record as it's own. RXItem rx1, rx2; // So first create the new cache entry for current day we are looking at. rx1.start = date; rx1.end = date; rx1.days = 1; // Only this days AHI/RDI counts tmp = day->count(AllAhiChannels); rx1.ahi = tmp; rx1.rdi = tmp + day->count(CPAP_RERA); // Sum and count event flags for this day for (int i=0; i < flags.size(); i++) { ChannelID code = flags.at(i); rx1.s_count[code] = day->count(code); rx1.s_sum[code] = day->sum(code); } //The rest of this cache record for this day rx1.hours = day->hours(MT_CPAP); rx1.relief = relief; rx1.mode = mode; rx1.pressure = pressure; rx1.machine = mach; rx1.dates[date] = day; // Insert new entry into rx cache rxitems.insert(date, rx1); // now zonk it so we can reuse the variable later //rx1 = RXItem(); // Now that's out of the way, we need to splitting the old rx into two, // and recalculate everything before and after today // Copy the old rx.dates, which contains the list of Day records QMap datecopy = rx.dates; // now zap it so we can start fresh rx.dates.clear(); rx2.end = rx2.start = rx.end; rx.end = rx.start; // Zonk the summary data, as it needs redoing rx2.ahi = 0; rx2.rdi = 0; rx2.hours = 0; rx.ahi = 0; rx.rdi = 0; rx.hours = 0; rx.s_count.clear(); rx2.s_count.clear(); rx.s_sum.clear(); rx2.s_sum.clear(); // Now go through day list and recalculate according to split for (di = datecopy.begin(); di != datecopy.end(); ++di) { // Split everything before date if (di.key() < date) { // Get the day record for this date Day * dy = rx.dates[di.key()] = p_profile->GetDay(di.key(), MT_CPAP); // Update AHI/RDI counts tmp = dy->count(AllAhiChannels); rx.ahi += tmp; rx.rdi += tmp + dy->count(CPAP_RERA); // Get Event Flags list QList flags2 = dy->getSortedMachineChannels(MT_CPAP, schema::FLAG | schema::MINOR_FLAG | schema::SPAN); // Update flags counts and sums for (int i=0; i < flags2.size(); i++) { ChannelID code = flags2.at(i); rx.s_count[code] += dy->count(code); rx.s_sum[code] += dy->sum(code); } // Update time sum rx.hours += dy->hours(MT_CPAP); // Update the last date of this cache entry // (Max here should be unnessary, this should be sequential because we are processing a QMap.) rx.end = di.key(); //qMax(di.key(), rx.end); } // Split everything after date if (di.key() > date) { // Get the day record for this date Day * dy = rx2.dates[di.key()] = p_profile->GetDay(di.key(), MT_CPAP); // Update AHI/RDI counts tmp = dy->count(AllAhiChannels); rx2.ahi += tmp; rx2.rdi += tmp + dy->count(CPAP_RERA); // Get Event Flags list QList flags2 = dy->getSortedMachineChannels(MT_CPAP, schema::FLAG | schema::MINOR_FLAG | schema::SPAN); // Update flags counts and sums for (int i=0; i < flags2.size(); i++) { ChannelID code = flags2.at(i); rx2.s_count[code] += dy->count(code); rx2.s_sum[code] += dy->sum(code); } // Update time sum rx2.hours += dy->hours(MT_CPAP); // Update start and end //rx2.end = qMax(di.key(), rx2.end); // don't need to do this, the end won't change from what the old one was. // technically only need to capture the first?? rx2.start = qMin(di.key(), rx2.start); } } // Set rx records day counts rx.days = rx.dates.size(); rx2.days = rx2.dates.size(); // Copy the pressure/mode/etc settings, because they haven't changed. rx2.pressure = rx.pressure; rx2.mode = rx.mode; rx2.relief = rx.relief; rx2.machine = rx.machine; // Insert the newly split rx record rxitems.insert(rx2.start, rx2); // hmmm. this was previously set to the end date.. that was a silly plan. fnd = true; break; } } } if (fnd) continue; // already in rx list, move onto the next daylist entry // So in this condition, daylist isn't in rx cache, and doesn't match date range of any previous rx cache entry. // Need to bring in summaries for this day->OpenSummary(); // Get Event flags list QList flags3 = day->getSortedMachineChannels(MT_CPAP, schema::FLAG | schema::MINOR_FLAG | schema::SPAN); // Generate pressure/mode/`strings QString relief = day->getPressureRelief(); QString mode = day->getCPAPModeStr(); QString pressure = day->getPressureSettings(); // Now scan the rxcache to find the most previous entry, and the right place to insert QMap::iterator lastri = rxitems.end(); for (ri = rxitems.begin(); ri != ri_end; ++ri) { // RXItem & rx = ri.value(); // break after any date newer if (ri.key() > date) break; // Keep this.. we need the last one. lastri = ri; } // lastri should no be the last entry before this date, or the end if (lastri != rxitems.end()) { RXItem & rx = lastri.value(); // Does it match here? if ((rx.relief == relief) && (rx.mode == mode) && (rx.pressure == pressure) && (rx.machine == mach) ) { // Update AHI/RDI tmp = day->count(AllAhiChannels); rx.ahi += tmp; rx.rdi += tmp + day->count(CPAP_RERA); // Update event flags for (int i=0; i < flags3.size(); i++) { ChannelID code = flags3.at(i); rx.s_count[code] += day->count(code); rx.s_sum[code] += day->sum(code); } // Update hours rx.hours += day->hours(MT_CPAP); // Add day to this RX Cache rx.dates[date] = day; rx.end = date; rx.days = rx.dates.size(); fnd = true; } } if (!fnd) { // Okay, couldn't find a match, create a new rx cache record for this day. RXItem rx; rx.start = date; rx.end = date; rx.days = 1; // Set AHI/RDI for just this day tmp = day->count(AllAhiChannels); rx.ahi = tmp; rx.rdi = tmp + day->count(CPAP_RERA); // Set counts and sums for this day for (int i=0; i < flags3.size(); i++) { ChannelID code = flags3.at(i); rx.s_count[code] = day->count(code); rx.s_sum[code] = day->sum(code); } rx.hours = day->hours(); // Store settings, etc.. rx.relief = relief; rx.mode = mode; rx.pressure = pressure; rx.machine = mach; // add this day to this rx record rx.dates.insert(date, day); // And insert into rx record into the rx cache rxitems.insert(date, rx); } } // Store RX cache to disk saveRXChanges(); // Now do the setup for the best worst highlighting QList list; ri_end = rxitems.end(); for (ri = rxitems.begin(); ri != ri_end; ++ri) { list.append(&ri.value()); ri.value().highlight = 0; } std::sort(list.begin(), list.end(), rxAHILessThan); if (list.size() >= 4) { list[0]->highlight = 1; // best list[1]->highlight = 2; // best int ls = list.size() - 1; list[ls-1]->highlight = 3; // best list[ls]->highlight = 4; } else if (list.size() >= 2) { list[0]->highlight = 1; // best int ls = list.size() - 1; list[ls]->highlight = 4; } else if (list.size() > 0) { list[0]->highlight = 1; // best } // Close the progress bar progress->close(); delete progress; } // Statistics constructor is responsible for creating list of rows that will on the Statistics page // and skeletons of column 1 text that correspond to each calculation type. // Actual column 1 text is combination of skeleton for the row's calculation time and the text of the row. // Also creates "machine" names for machine types. Statistics::Statistics(QObject *parent) : QObject(parent) { rows.push_back(StatisticsRow(tr("CPAP Statistics"), SC_HEADING, MT_CPAP)); rows.push_back(StatisticsRow("", SC_DAYS, MT_CPAP)); rows.push_back(StatisticsRow("", SC_COLUMNHEADERS, MT_CPAP)); rows.push_back(StatisticsRow(tr("CPAP Usage"), SC_SUBHEADING, MT_CPAP)); rows.push_back(StatisticsRow(tr("Average Hours per Night"), SC_HOURS, MT_CPAP)); rows.push_back(StatisticsRow(tr("Compliance (%1 hrs/day)"), SC_COMPLIANCE, MT_CPAP)); rows.push_back(StatisticsRow(tr("Therapy Efficacy"), SC_SUBHEADING, MT_CPAP)); rows.push_back(StatisticsRow("AHI", SC_AHI, MT_CPAP)); rows.push_back(StatisticsRow("AllApnea", SC_CPH, MT_CPAP)); rows.push_back(StatisticsRow("Obstructive", SC_CPH, MT_CPAP)); rows.push_back(StatisticsRow("Hypopnea", SC_CPH, MT_CPAP)); rows.push_back(StatisticsRow("Apnea", SC_CPH, MT_CPAP)); rows.push_back(StatisticsRow("ClearAirway", SC_CPH, MT_CPAP)); rows.push_back(StatisticsRow("FlowLimit", SC_CPH, MT_CPAP)); rows.push_back(StatisticsRow("FLG", SC_90P, MT_CPAP)); rows.push_back(StatisticsRow("RERA", SC_CPH, MT_CPAP)); rows.push_back(StatisticsRow("SensAwake", SC_CPH, MT_CPAP)); rows.push_back(StatisticsRow("CSR", SC_SPH, MT_CPAP)); rows.push_back(StatisticsRow(tr("Leak Statistics"), SC_SUBHEADING, MT_CPAP)); rows.push_back(StatisticsRow("Leak", SC_WAVG, MT_CPAP)); rows.push_back(StatisticsRow("Leak", SC_90P, MT_CPAP)); rows.push_back(StatisticsRow("Leak", SC_ABOVE, MT_CPAP)); rows.push_back(StatisticsRow(tr("Pressure Statistics"), SC_SUBHEADING, MT_CPAP)); rows.push_back(StatisticsRow("Pressure", SC_WAVG, MT_CPAP)); rows.push_back(StatisticsRow("Pressure", SC_MIN, MT_CPAP)); rows.push_back(StatisticsRow("Pressure", SC_MAX, MT_CPAP)); rows.push_back(StatisticsRow("Pressure", SC_90P, MT_CPAP)); rows.push_back(StatisticsRow("PressureSet", SC_WAVG, MT_CPAP)); rows.push_back(StatisticsRow("PressureSet", SC_MIN, MT_CPAP)); rows.push_back(StatisticsRow("PressureSet", SC_MAX, MT_CPAP)); rows.push_back(StatisticsRow("PressureSet", SC_90P, MT_CPAP)); rows.push_back(StatisticsRow("EPAP", SC_WAVG, MT_CPAP)); rows.push_back(StatisticsRow("EPAP", SC_MIN, MT_CPAP)); rows.push_back(StatisticsRow("EPAP", SC_MAX, MT_CPAP)); rows.push_back(StatisticsRow("EPAPSet", SC_WAVG, MT_CPAP)); rows.push_back(StatisticsRow("EPAPSet", SC_MIN, MT_CPAP)); rows.push_back(StatisticsRow("EPAPSet", SC_MAX, MT_CPAP)); rows.push_back(StatisticsRow("IPAP", SC_WAVG, MT_CPAP)); rows.push_back(StatisticsRow("IPAP", SC_90P, MT_CPAP)); rows.push_back(StatisticsRow("IPAP", SC_MIN, MT_CPAP)); rows.push_back(StatisticsRow("IPAP", SC_MAX, MT_CPAP)); rows.push_back(StatisticsRow("IPAPSet", SC_WAVG, MT_CPAP)); rows.push_back(StatisticsRow("IPAPSet", SC_90P, MT_CPAP)); rows.push_back(StatisticsRow("IPAPSet", SC_MIN, MT_CPAP)); rows.push_back(StatisticsRow("IPAPSet", SC_MAX, MT_CPAP)); rows.push_back(StatisticsRow("", SC_HEADING, MT_OXIMETER)); // Just adds some space rows.push_back(StatisticsRow(tr("Oximeter Statistics"), SC_HEADING, MT_OXIMETER)); rows.push_back(StatisticsRow("", SC_DAYS, MT_OXIMETER)); rows.push_back(StatisticsRow("", SC_COLUMNHEADERS, MT_OXIMETER)); rows.push_back(StatisticsRow(tr("Blood Oxygen Saturation"), SC_SUBHEADING, MT_CPAP)); rows.push_back(StatisticsRow("SPO2", SC_WAVG, MT_OXIMETER)); rows.push_back(StatisticsRow("SPO2", SC_MIN, MT_OXIMETER)); rows.push_back(StatisticsRow("SPO2Drop", SC_CPH, MT_OXIMETER)); rows.push_back(StatisticsRow("SPO2Drop", SC_SPH, MT_OXIMETER)); rows.push_back(StatisticsRow(tr("Pulse Rate"), SC_SUBHEADING, MT_CPAP)); rows.push_back(StatisticsRow("Pulse", SC_WAVG, MT_OXIMETER)); rows.push_back(StatisticsRow("Pulse", SC_MIN, MT_OXIMETER)); rows.push_back(StatisticsRow("Pulse", SC_MAX, MT_OXIMETER)); rows.push_back(StatisticsRow("PulseChange", SC_CPH, MT_OXIMETER)); // These are for formatting the headers for the first column int percentile=trunc(p_profile->general->prefCalcPercentile()); // Pholynyk, 10Mar2016 char perCentStr[20]; snprintf(perCentStr, 20, "%d%% %%1", percentile); // calcnames[SC_UNDEFINED] = ""; calcnames[SC_MEDIAN] = tr("%1 Median"); calcnames[SC_AVG] = tr("Average %1"); calcnames[SC_WAVG] = tr("Average %1"); calcnames[SC_90P] = tr(perCentStr); // this gets converted to whatever the upper percentile is set to calcnames[SC_MIN] = tr("Min %1"); calcnames[SC_MAX] = tr("Max %1"); calcnames[SC_CPH] = tr("%1 Index"); calcnames[SC_SPH] = tr("% of time in %1"); calcnames[SC_ABOVE] = tr("% of time above %1 threshold"); calcnames[SC_BELOW] = tr("% of time below %1 threshold"); machinenames[MT_UNKNOWN] = STR_TR_Unknown; machinenames[MT_CPAP] = STR_TR_CPAP; machinenames[MT_OXIMETER] = STR_TR_Oximetry; machinenames[MT_SLEEPSTAGE] = STR_TR_SleepStage; // { MT_JOURNAL, STR_TR_Journal }, // { MT_POSITION, STR_TR_Position }, } // Get the user information block for displaying at top of page QString Statistics::getUserInfo () { if (!AppSetting->showPersonalData()) return ""; QString address = p_profile->user->address(); address.replace("\n", "
    "); QString userinfo = ""; if (!p_profile->user->firstName().isEmpty()) { userinfo = tr("Name: %1, %2").arg(p_profile->user->lastName()).arg(p_profile->user->firstName()) + "
    "; if (!p_profile->user->DOB().isNull()) { userinfo += tr("DOB: %1").arg(p_profile->user->DOB().toString(MedDateFormat)) + "
    "; } if (!p_profile->user->phone().isEmpty()) { userinfo += tr("Phone: %1").arg(p_profile->user->phone()) + "
    "; } if (!p_profile->user->email().isEmpty()) { userinfo += tr("Email: %1").arg(p_profile->user->email()) + "

    "; } if (!p_profile->user->address().isEmpty()) { userinfo += tr("Address:")+"
    "+address; } } while (userinfo.length() > 0 && userinfo.endsWith("
    ")) // Strip trailing newlines userinfo = userinfo.mid(0, userinfo.length()-4); return userinfo; } const QString table_width = "width='100%'"; // Create the page header in HTML. Includes everything from through QString Statistics::generateHeader(bool onScreen) { QString html = QString(""); html += "Oscar Statistics Report"; html += "" "" "" "" ""; //leftmargin=0 topmargin=5 rightmargin=0>"; QPixmap logoPixmap(":/icons/logo-lg.png"); // html += "
    " html += "
    " "" "" "" "" "" "
    " + getUserInfo() + "" "" + STR_TR_OSCAR + "   
    " "" + QObject::tr("Usage Statistics") + "   " "
    " + resizeHTMLPixmap(logoPixmap,80,80)+"   
    " "
    " "
    "; return html; } // HTML for page footer QString Statistics::generateFooter(bool showinfo) { QString html; if (showinfo) { html += "
    "; QDateTime timestamp = QDateTime::currentDateTime(); html += tr("This report was prepared on %1 by OSCAR %2").arg(timestamp.toString(MedDateFormat + " hh:mm")) .arg(getVersion()) + "
    " + tr("OSCAR is free open-source CPAP report software"); html += "
    "; } html += ""; return html; } // Calculate AHI for a period as total # of events / total hours used // Add RERA if calculating RDI instead of just AHI EventDataType calcAHI(QDate start, QDate end) { EventDataType val = 0; for (int i = 0; i < ahiChannels.size(); i++) val += p_profile->calcCount(ahiChannels.at(i), MT_CPAP, start, end); // (p_profile->calcCount(CPAP_Obstructive, MT_CPAP, start, end) // + p_profile->calcCount(CPAP_AllApnea, MT_CPAP, start, end) // + p_profile->calcCount(CPAP_Hypopnea, MT_CPAP, start, end) // + p_profile->calcCount(CPAP_ClearAirway, MT_CPAP, start, end) // + p_profile->calcCount(CPAP_Apnea, MT_CPAP, start, end)); if (p_profile->general->calculateRDI()) { val += p_profile->calcCount(CPAP_RERA, MT_CPAP, start, end); } EventDataType hours = p_profile->calcHours(MT_CPAP, start, end); if (hours > 0) { val /= hours; } else { val = 0; } return val; } // Calculate flow limits per hour EventDataType calcFL(QDate start, QDate end) { EventDataType val = (p_profile->calcCount(CPAP_FlowLimit, MT_CPAP, start, end)); EventDataType hours = p_profile->calcHours(MT_CPAP, start, end); if (hours > 0) { val /= hours; } else { val = 0; } return val; } // Calculate ...(what are these?) EventDataType calcSA(QDate start, QDate end) { EventDataType val = (p_profile->calcCount(CPAP_SensAwake, MT_CPAP, start, end)); EventDataType hours = p_profile->calcHours(MT_CPAP, start, end); if (hours > 0) { val /= hours; } else { val = 0; } return val; } // Structure for recording Prescription Changes (now called Machine Settings Changes) struct RXChange { RXChange() { highlight = 0; machine = nullptr; } RXChange(const RXChange ©) { first = copy.first; last = copy.last; days = copy.days; ahi = copy.ahi; fl = copy.fl; mode = copy.mode; min = copy.min; max = copy.max; ps = copy.ps; pshi = copy.pshi; maxipap = copy.maxipap; machine = copy.machine; per1 = copy.per1; per2 = copy.per2; highlight = copy.highlight; weighted = copy.weighted; pressure_string = copy.pressure_string; pr_relief_string = copy.pr_relief_string; } QDate first; QDate last; int days; EventDataType ahi; EventDataType fl; CPAPMode mode; QString pressure_string; QString pr_relief_string; EventDataType min; EventDataType max; EventDataType ps; EventDataType pshi; EventDataType maxipap; EventDataType per1; EventDataType per2; EventDataType weighted; Machine *machine; short highlight; }; struct UsageData { UsageData() { ahi = 0; hours = 0; } UsageData(QDate d, EventDataType v, EventDataType h) { date = d; ahi = v; hours = h; } UsageData(const UsageData ©) { date = copy.date; ahi = copy.ahi; hours = copy.hours; } QDate date; EventDataType ahi; EventDataType hours; }; bool operator <(const UsageData &c1, const UsageData &c2) { if (c1.ahi < c2.ahi) { return true; } if ((c1.ahi == c2.ahi) && (c1.date > c2.date)) { return true; } return false; } struct Period { Period() { } Period(QDate start, QDate end, QString header) { this->start = start; this->end = end; this->header = header; } Period(const Period & copy) { start=copy.start; end=copy.end; header=copy.header; } QDate start; QDate end; QString header; }; const QString heading_color="#ffffff"; const QString subheading_color="#e0e0e0"; //const int rxthresh = 5; // Sort machines by first day of use bool machineCompareFirstDay(Machine* left, Machine *right) { return left->FirstDay() > right->FirstDay(); } QString Statistics::GenerateMachineList() { QList cpap_machines = p_profile->GetMachines(MT_CPAP); QList oximeters = p_profile->GetMachines(MT_OXIMETER); QList mach; std::sort(cpap_machines.begin(), cpap_machines.end(), machineCompareFirstDay); std::sort(oximeters.begin(), oximeters.end(), machineCompareFirstDay); mach.append(cpap_machines); mach.append(oximeters); QString html; if (mach.size() > 0) { html += "

    "; html += QString(""); html += ""; html += ""; html += QString("") .arg(STR_TR_Brand) .arg(STR_TR_Model) .arg(STR_TR_Serial) .arg(tr("First Use")) .arg(tr("Last Use")); html += ""; Machine *m; for (int i = 0; i < mach.size(); i++) { m = mach.at(i); if (m->type() == MT_JOURNAL) { continue; } //qDebug() << "Machine" << m->brand() << "series" << m->series() << "model" << m->model() << "model number" << m->modelnumber(); QDate d1 = m->FirstDay(); QDate d2 = m->LastDay(); QString mn = m->modelnumber(); html += QString("") .arg(m->brand()) .arg(m->model() + (mn.isEmpty() ? "" : QString(" (") + mn + QString(")"))) .arg(m->serial()) .arg(d1.toString(MedDateFormat)) .arg(d2.toString(MedDateFormat)); } html += "
    " + tr("Machine Information") + "
    %1%2%3%4%5
    %1%2%3%4%5
    "; html += "
    "; } return html; } QString Statistics::GenerateRXChanges() { // Generate list only if there are CPAP machines QList cpap_machines = p_profile->GetMachines(MT_CPAP); if (cpap_machines.isEmpty()) return ""; // do the actual data sorting... updateRXChanges(); QString ahitxt; bool rdi = p_profile->general->calculateRDI(); if (rdi) { ahitxt = STR_TR_RDI; } else { ahitxt = STR_TR_AHI; } QString html = "

    "; html += QString(""); html += ""; html += ""; // QString extratxt; // QString tooltip; QStringList hdrlist; hdrlist.push_back(STR_TR_First); hdrlist.push_back(STR_TR_Last); hdrlist.push_back(tr("Days")); hdrlist.push_back(ahitxt); hdrlist.push_back(STR_TR_FL); hdrlist.push_back(STR_TR_Machine); hdrlist.push_back(tr("Pressure Relief")); hdrlist.push_back(STR_TR_Mode); hdrlist.push_back(tr("Pressure Settings")); html+=""; for (int i=0; i < hdrlist.size(); ++i) { html+=QString(" ").arg(hdrlist.at(i)); } html+=""; html += ""; // html += ""; // html += ""; // html += ""; QMapIterator it(rxitems); it.toBack(); while (it.hasPrevious()) { it.previous(); const RXItem & rx = it.value(); QString color; if (rx.highlight == 1) { color = "#c0ffc0"; } else if (rx.highlight == 2) { color = "#e0ffe0"; } else if (rx.highlight == 3) { color = "#ffe0e0"; } else if (rx.highlight == 4) { color = "#ffc0c0"; } else { color = ""; } QString datarowclass; if (rx.highlight == 0) datarowclass="class=datarow"; html += QString("") .arg(color) .arg(rx.start.toString(Qt::ISODate)) .arg(rx.end.toString(Qt::ISODate)) .arg(datarowclass); double ahi = rdi ? (double(rx.rdi) / rx.hours) : (double(rx.ahi) /rx.hours); double fli = double(rx.count(CPAP_FlowLimit)) / rx. hours; QString machid = QString("").arg(rx.machine->model()) .arg(rx.machine->modelnumber()); if (AppSetting->includeSerial()) machid = QString("").arg(rx.machine->model()) .arg(rx.machine->modelnumber()) .arg(rx.machine->serial()); html += QString("").arg(rx.start.toString(MedDateFormat))+ QString("").arg(rx.end.toString(MedDateFormat))+ QString("").arg(rx.days)+ QString("").arg(ahi, 0, 'f', 2)+ QString("").arg(fli, 0, 'f', 2)+ machid + QString("").arg(formatRelief(rx.relief))+ QString("").arg(rx.mode)+ QString("").arg(rx.pressure)+ ""; } html+="
    " + tr("Changes to Machine Settings") + "
    %1
    "; // html += QString("") + // tr("Efficacy highlighting ignores prescription settings with less than %1 days of recorded data."). // arg(rxthresh) + QString("
    "); // html += "
    %1 (%2)%1 (%2) [%3]%1%1%1%1%1%1%1%1
    "; return html; } // Report no data available QString Statistics::htmlNoData() { QString html = "
    "; html += QString( "


    " + tr("No data found?!?") + "

    "+ "

    logo

    " "

    "+tr("Oscar has no data to report :(")+"

    "); return html; } // Get RDI or AHI text depending on user preferences QString Statistics::getRDIorAHIText() { if (p_profile->general->calculateRDI()) { return STR_TR_RDI; } return STR_TR_AHI; } // Create the HTML for CPAP and Oximetry usage QString Statistics::GenerateCPAPUsage() { QList cpap_machines = p_profile->GetMachines(MT_CPAP); QList oximeters = p_profile->GetMachines(MT_OXIMETER); QList mach; mach.append(cpap_machines); mach.append(oximeters); // Go through all CPAP and Oximeter machines and see if any data is present bool havedata = false; for (int i=0; i < mach.size(); ++i) { int daysize = mach[i]->day.size(); if (daysize > 0) { havedata = true; break; } } QString html = ""; // If we don't have any data, return HTML that says that and we are done if (!havedata) { return ""; } // Find first and last days with valid CPAP data QDate lastcpap = p_profile->LastGoodDay(MT_CPAP); QDate firstcpap = p_profile->FirstGoodDay(MT_CPAP); // Get dates for standard report (last week, month, 6 months, year) QDate cpapweek = lastcpap.addDays(-6); QDate cpapmonth = lastcpap.addDays(-29); QDate cpap6month = lastcpap.addMonths(-6); QDate cpapyear = lastcpap.addMonths(-12); // but not before the first available date of course if (cpapweek < firstcpap) { cpapweek = firstcpap; } if (cpapmonth < firstcpap) { cpapmonth = firstcpap; } if (cpap6month < firstcpap) { cpap6month = firstcpap; } if (cpapyear < firstcpap) { cpapyear = firstcpap; } QString ahitxt = getRDIorAHIText(); // Prepare top of table html += "
    "; html += ""; // Compute number of monthly periods for a monthly rather than standard time distribution int number_periods = 0; if (p_profile->general->statReportMode() == STAT_MODE_MONTHLY) { int firstMonth = firstcpap.month(); int lastMonth = lastcpap.month(); int years = lastcpap.year() - firstcpap.year(); lastMonth += (12 * years); // handle time extending to next year number_periods = lastMonth - firstMonth + 1; if (number_periods < 1) { qDebug() << "*** Begin" << firstcpap << "beginMonth" << firstMonth << "lastMonth" << lastMonth << "periods" << number_periods; number_periods = 1; } qDebug() << "Number of months for stats (trim to 12 max)" << number_periods; // But not more than one year if (number_periods > 12) { number_periods = 12; } // } else if (p_profile->general->statReportMode() == STAT_MODE_RANGE) { } QDate last = lastcpap, first = lastcpap; QList periods; bool skipsection = false;; // Loop through all rows of the Statistics report for (QList::iterator i = rows.begin(); i != rows.end(); ++i) { StatisticsRow &row = (*i); QString name; if (row.calc == SC_HEADING) { // All sections begin with a heading last = p_profile->LastGoodDay(row.type); first = p_profile->FirstGoodDay(row.type); // Clear the periods (columns) periods.clear(); if (p_profile->general->statReportMode() == STAT_MODE_STANDARD) { periods.push_back(Period(last,last,tr("Most Recent"))); periods.push_back(Period(qMax(last.addDays(-6), first), last, tr("Last Week"))); periods.push_back(Period(qMax(last.addDays(-29),first), last, tr("Last 30 Days"))); periods.push_back(Period(qMax(last.addMonths(-6), first), last, tr("Last 6 Months"))); periods.push_back(Period(qMax(last.addMonths(-12), first), last, tr("Last Year"))); } else if (p_profile->general->statReportMode() == STAT_MODE_MONTHLY) { QDate l=last,s=last; periods.push_back(Period(last,last,tr("Last Session"))); //bool done=false; int j=0; do { s=QDate(l.year(), l.month(), 1); if (s < first) { //done = true; s = first; } if (p_profile->countDays(row.type, s, l) > 0) { periods.push_back(Period(s, l, s.toString("MMMM"))); j++; } l = s.addDays(-1); } while ((l > first) && (j < number_periods)); for (; j < number_periods; ++j) { periods.push_back(Period(last,last, "")); } } else { // STAT_MODE_RANGE first = p_profile->general->statReportRangeStart(); last = p_profile->general->statReportRangeEnd(); periods.push_back(Period(first,last,first.toString(MedDateFormat)+" -
    "+last.toString(MedDateFormat))); } int days = p_profile->countDays(row.type, first, last); skipsection = (days == 0); if (days > 0) { html+=QString("
    "). arg(heading_color).arg(periods.size()+1).arg(row.src); } continue; } // Bypass this entire section if no data is present if (skipsection) continue; if (row.calc == SC_AHI) { name = ahitxt; } else if (row.calc == SC_HOURS) { name = row.src; } else if (row.calc == SC_COMPLIANCE) { name = QString(row.src).arg(p_profile->cpap->m_complianceHours); } else if (row.calc == SC_COLUMNHEADERS) { html += QString("").arg(tr("Details")); for (int j=0; j < periods.size(); j++) { html += QString("").arg(periods.at(j).start.toString(Qt::ISODate)).arg(periods.at(j).end.toString(Qt::ISODate)).arg(periods.at(j).header); } html += ""; continue; } else if (row.calc == SC_DAYS) { QDate first=p_profile->FirstGoodDay(row.type); QDate last=p_profile->LastGoodDay(row.type); QString & machine = machinenames[row.type]; int value=p_profile->countDays(row.type, first, last); if (value == 0) { html+=QString("").arg(periods.size()+1). arg(tr("No %1 data available.").arg(machine)); } else if (value == 1) { html+=QString("").arg(periods.size()+1). arg(tr("%1 day of %2 Data on %3") .arg(value) .arg(machine) .arg(last.toString(MedDateFormat))); } else { html+=QString("").arg(periods.size()+1). arg(tr("%1 days of %2 Data, between %3 and %4") .arg(value) .arg(machine) .arg(first.toString(MedDateFormat)) .arg(last.toString(MedDateFormat))); } continue; } else if (row.calc == SC_SUBHEADING) { // subheading.. html+=QString(""). arg(subheading_color).arg(periods.size()+1).arg(row.src); continue; } else if (row.calc == SC_UNDEFINED) { continue; } else { ChannelID id = schema::channel[row.src].id(); if ((id == NoChannel) || (!p_profile->channelAvailable(id))) { continue; } name = calcnames[row.calc].arg(schema::channel[id].fullname()); } QString line; int np = periods.size(); int width; int dataWidth = 14; int headerWidth = 30; if (p_profile->general->statReportMode() == STAT_MODE_MONTHLY) { dataWidth = 6; headerWidth = 22; } line += QString("").arg(headerWidth).arg(name); for (int j=0; j < np; j++) { width = j < np-1 ? dataWidth : 100 - (headerWidth + dataWidth*(np-1)); line += QString(""; } html += line; html += ""; } html += "
    %3
    %1%3
    %2
    %2
    %2
    %3
    %2").arg(width); if (!periods.at(j).header.isEmpty()) { line += row.value(periods.at(j).start, periods.at(j).end); } else { line +=" "; } line += "
    "; html += "
    "; return html; } // Create the HTML that will be the Statistics page. QString Statistics::GenerateHTML() { htmlReportHeader = generateHeader(true); htmlReportHeaderPrint = generateHeader(false); htmlReportFooter = generateFooter(true); htmlUsage = GenerateCPAPUsage(); if (htmlUsage == "") { return htmlReportHeader + htmlNoData() + htmlReportFooter; } htmlMachineSettings = GenerateRXChanges(); htmlMachines = GenerateMachineList(); QString htmlScript = ""; return htmlReportHeader + htmlUsage + htmlMachineSettings + htmlMachines + htmlScript + htmlReportFooter; } // Print the Statistics page on printer void Statistics::printReport(QWidget * parent) { QPrinter printer(QPrinter::ScreenResolution); // ScreenResolution required for graphics sizing #ifdef Q_OS_LINUX printer.setPrinterName("Print to File (PDF)"); printer.setOutputFormat(QPrinter::PdfFormat); QString name = "Statistics"; QString datestr = QDate::currentDate().toString(Qt::ISODate); QString filename = p_pref->Get("{home}/") + name + "_" + p_profile->user->userName() + "_" + datestr + ".pdf"; printer.setOutputFileName(filename); #endif printer.setPrintRange(QPrinter::AllPages); printer.setOrientation(QPrinter::Portrait); printer.setFullPage(false); // Print only on printable area of page and not in non-printable margins printer.setNumCopies(1); QMarginsF minMargins = printer.pageLayout().margins(QPageLayout::Millimeter); printer.setPageMargins(fmax(10,minMargins.left()), fmax(10,minMargins.top()), fmax(10,minMargins.right()), fmax(12,minMargins.bottom()), QPrinter::Millimeter); QMarginsF setMargins = printer.pageLayout().margins(QPageLayout::Millimeter); qDebug () << "Min margins" << minMargins << "Set margins" << setMargins << "millimeters"; // Show print dialog to user and allow them to change settings as desired QPrintDialog pdlg(&printer, parent); if (pdlg.exec() == QPrintDialog::Accepted) { QTextDocument doc; QSizeF printArea = printer.pageRect(QPrinter::Point).size(); QSizeF originalPrintArea = printArea; printArea.setWidth(printArea.width()*2); // scale up for better font appearance printArea.setHeight(printArea.height()*2); doc.setPageSize(printArea); // Set document to print area, in pixels, removing default 2cm margins qDebug() << "print area (points)" << originalPrintArea << "Enlarged print area" << printArea << "paper size" << printer.paperRect(QPrinter::Point).size(); // Determine appropriate font and font size QFont font = QFont("Helvetica"); float fontScalar = 12; float printWidth = printArea.width(); if (printWidth > 1000) printWidth = 1000 + (printWidth - 1000) * 0.90; // Increase font for wide paper (landscape), but not linearly float pointSize = (printWidth / fontScalar) / 10.0; font.setPointSize(round(pointSize)); // Scale the font doc.setDefaultFont(font); qDebug() << "Enlarged printer font" << font << "printer default font set" << doc.defaultFont(); doc.setHtml(htmlReportHeaderPrint + htmlUsage + htmlReportFooter + htmlMachineSettings + htmlMachines + htmlReportFooter); // Dump HTML for use with HTML4 validator // QString html = htmlReportHeaderPrint + htmlUsage + htmlMachineSettings + htmlMachines + htmlReportFooter; // qDebug() << "Html:" << html; doc.print(&printer); } } QString Statistics::UpdateRecordsBox() { QString html = "" "Machine Statistics Panel" ""; Machine * cpap = p_profile->GetMachine(MT_CPAP); if (cpap) { QDate first = p_profile->FirstGoodDay(MT_CPAP); QDate last = p_profile->LastGoodDay(MT_CPAP); ///////////////////////////////////////////////////////////////////////////////////// /// Compliance and usage information ///////////////////////////////////////////////////////////////////////////////////// int totaldays = p_profile->countDays(MT_CPAP, first, last); int compliant = p_profile->countCompliantDays(MT_CPAP, first, last); float comperc = (100.0 / float(totaldays)) * float(compliant); html += ""+tr("CPAP Usage")+"
    "; html += tr("Days Used: %1").arg(totaldays) + "
    "; html += tr("Low Use Days: %1").arg(totaldays - compliant) + "
    "; html += tr("Compliance: %1%").arg(comperc, 0, 'f', 1) + "
    "; ///////////////////////////////////////////////////////////////////////////////////// /// AHI Records ///////////////////////////////////////////////////////////////////////////////////// if (p_profile->session->preloadSummaries()) { const int show_records = 5; QMultiMap::iterator it; QMultiMap::iterator it_end; QMultiMap ahilist; int baddays = 0; for (QDate date = first; date <= last; date = date.addDays(1)) { Day * day = p_profile->GetGoodDay(date, MT_CPAP); if (!day) continue; float ahi = day->calcAHI(); if (ahi >= 5) { baddays++; } ahilist.insert(ahi, date); } html += tr("Days AHI of 5 or greater: %1").arg(baddays) + "

    "; if (ahilist.size() > (show_records * 2)) { it = ahilist.begin(); it_end = ahilist.end(); html += ""+tr("Best AHI")+"
    "; for (int i=0; (i").arg(it.value().toString(Qt::ISODate)) +tr("Date: %1 AHI: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "
    "; } html += "
    "; html += ""+tr("Worst AHI")+"
    "; it = ahilist.end() - 1; it_end = ahilist.begin(); for (int i=0; (i").arg(it.value().toString(Qt::ISODate)) +tr("Date: %1 AHI: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "
    "; } html += "
    "; } ///////////////////////////////////////////////////////////////////////////////////// /// Flow Limitation Records ///////////////////////////////////////////////////////////////////////////////////// ahilist.clear(); for (QDate date = first; date <= last; date = date.addDays(1)) { Day * day = p_profile->GetGoodDay(date, MT_CPAP); if (!day) continue; float val = 0; if (day->channelHasData(CPAP_FlowLimit)) { val = day->calcIdx(CPAP_FlowLimit); } else if (day->channelHasData(CPAP_FLG)) { // Use 90th percentile val = day->calcPercentile(CPAP_FLG); } ahilist.insert(val, date); } int cnt = 0; if (ahilist.size() > (show_records * 2)) { it = ahilist.begin(); it_end = ahilist.end(); html += ""+tr("Best Flow Limitation")+"
    "; for (int i=0; (i").arg(it.value().toString(Qt::ISODate)) +tr("Date: %1 FL: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "
    "; } html += "
    "; html += ""+tr("Worst Flow Limtation")+"
    "; it = ahilist.end() - 1; it_end = ahilist.begin(); for (int i=0; (i 0) { html += QString("").arg(it.value().toString(Qt::ISODate)) +tr("Date: %1 FL: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "
    "; cnt++; } } if (cnt == 0) { html+= ""+tr("No Flow Limitation on record")+"
    "; } html += "
    "; } ///////////////////////////////////////////////////////////////////////////////////// /// Large Leak Records ///////////////////////////////////////////////////////////////////////////////////// ahilist.clear(); for (QDate date = first; date <= last; date = date.addDays(1)) { Day * day = p_profile->GetGoodDay(date, MT_CPAP); if (!day) continue; float leak = day->calcPON(CPAP_LargeLeak); ahilist.insert(leak, date); } cnt = 0; if (ahilist.size() > (show_records * 2)) { html += ""+tr("Worst Large Leaks")+"
    "; it = ahilist.end() - 1; it_end = ahilist.begin(); for (int i=0; (i 0) { html += QString("").arg(it.value().toString(Qt::ISODate)) +tr("Date: %1 Leak: %2%").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "
    "; cnt++; } } if (cnt == 0) { html+= ""+tr("No Large Leaks on record")+"
    "; } html += "
    "; } ///////////////////////////////////////////////////////////////////////////////////// /// CSR Records ///////////////////////////////////////////////////////////////////////////////////// cnt = 0; if (p_profile->hasChannel(CPAP_CSR)) { ahilist.clear(); for (QDate date = first; date <= last; date = date.addDays(1)) { Day * day = p_profile->GetGoodDay(date, MT_CPAP); if (!day) continue; float leak = day->calcPON(CPAP_CSR); ahilist.insert(leak, date); } if (ahilist.size() > (show_records * 2)) { html += ""+tr("Worst CSR")+"
    "; it = ahilist.end() - 1; it_end = ahilist.begin(); for (int i=0; (i 0) { html += QString("").arg(it.value().toString(Qt::ISODate)) +tr("Date: %1 CSR: %2%").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "
    "; cnt++; } } if (cnt == 0) { html+= ""+tr("No CSR on record")+"
    "; } html += "
    "; } } if (p_profile->hasChannel(CPAP_PB)) { ahilist.clear(); for (QDate date = first; date <= last; date = date.addDays(1)) { Day * day = p_profile->GetGoodDay(date, MT_CPAP); if (!day) continue; float leak = day->calcPON(CPAP_PB); ahilist.insert(leak, date); } if (ahilist.size() > (show_records * 2)) { html += ""+tr("Worst PB")+"
    "; it = ahilist.end() - 1; it_end = ahilist.begin(); for (int i=0; (i < show_records) && (it != it_end); ++i, --it) { if (it.key() > 0) { html += QString("").arg(it.value().toString(Qt::ISODate)) +tr("Date: %1 PB: %2%").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "
    "; cnt++; } } if (cnt == 0) { html+= ""+tr("No PB on record")+"
    "; } html += "
    "; } } } else { html += "
    "+tr("Want more information?")+"
    "; html += ""+tr("OSCAR needs all summary data loaded to calculate best/worst data for individual days.")+"

    "; html += ""+tr("Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available.")+"

    "; } ///////////////////////////////////////////////////////////////////////////////////// /// Sort the RX list to get best and worst settings. ///////////////////////////////////////////////////////////////////////////////////// QList list; QMap::iterator ri_end = rxitems.end(); QMap::iterator ri; for (ri = rxitems.begin(); ri != ri_end; ++ri) { list.append(&ri.value()); ri.value().highlight = 0; } std::sort(list.begin(), list.end(), rxAHILessThan); if (list.size() >= 2) { html += ""+tr("Best RX Setting")+"
    "; const RXItem & rxbest = *list.at(0); html += QString("").arg(rxbest.start.toString(Qt::ISODate)).arg(rxbest.end.toString(Qt::ISODate)) + tr("Date: %1 - %2").arg(rxbest.start.toString(Qt::SystemLocaleShortDate)).arg(rxbest.end.toString(Qt::SystemLocaleShortDate)) + "
    "; html += QString("%1").arg(rxbest.machine->model()) + "
    "; html += QString("Serial: %1").arg(rxbest.machine->serial()) + "
    "; html += tr("AHI: %1").arg(double(rxbest.ahi) / rxbest.hours, 0, 'f', 2) + "
    "; html += tr("Total Hours: %1").arg(rxbest.hours, 0, 'f', 2) + "
    "; html += QString("%1").arg(rxbest.pressure) + "
    "; html += QString("%1").arg(formatRelief(rxbest.relief)) + "
    "; html += "
    "; html += ""+tr("Worst RX Setting")+"
    "; const RXItem & rxworst = *list.at(list.size() -1); html += QString("").arg(rxworst.start.toString(Qt::ISODate)).arg(rxworst.end.toString(Qt::ISODate)) + tr("Date: %1 - %2").arg(rxworst.start.toString(Qt::SystemLocaleShortDate)).arg(rxworst.end.toString(Qt::SystemLocaleShortDate)) + "
    "; html += QString("%1").arg(rxworst.machine->model()) + "
    "; html += QString("Serial: %1").arg(rxworst.machine->serial()) + "
    "; html += tr("AHI: %1").arg(double(rxworst.ahi) / rxworst.hours, 0, 'f', 2) + "
    "; html += tr("Total Hours: %1").arg(rxworst.hours, 0, 'f', 2) + "
    "; html += QString("%1").arg(rxworst.pressure) + "
    "; html += QString("%1").arg(formatRelief(rxworst.relief)) + "
    "; } } html += ""; return html; } QString StatisticsRow::value(QDate start, QDate end) { const int decimals=2; QString value; float days = p_profile->countDays(type, start, end); float percentile=p_profile->general->prefCalcPercentile()/100.0; // Pholynyk, 10Mar2016 EventDataType percent = percentile; // was 0.90F // Handle special data sources first if (calc == SC_AHI) { value = QString("%1").arg(calcAHI(start, end), 0, 'f', decimals); } else if (calc == SC_HOURS) { value = QString("%1").arg(formatTime(p_profile->calcHours(type, start, end) / days)); } else if (calc == SC_COMPLIANCE) { float c = p_profile->countCompliantDays(type, start, end); // float p = (100.0 / days) * c; float realDays = qAbs(start.daysTo(end)) + 1; float p = (100.0 / realDays) * c; value = QString("%1%").arg(p, 0, 'f', 0); } else if (calc == SC_DAYS) { value = QString("%1").arg(p_profile->countDays(type, start, end)); } else if ((calc == SC_COLUMNHEADERS) || (calc == SC_SUBHEADING) || (calc == SC_UNDEFINED)) { } else { // ChannelID code=channel(); EventDataType val = 0; QString fmt = "%1"; if (code != NoChannel) { switch(calc) { case SC_AVG: val = p_profile->calcAvg(code, type, start, end); break; case SC_WAVG: val = p_profile->calcWavg(code, type, start, end); break; case SC_MEDIAN: val = p_profile->calcPercentile(code, 0.5F, type, start, end); break; case SC_90P: val = p_profile->calcPercentile(code, percent, type, start, end); break; case SC_MIN: val = p_profile->calcMin(code, type, start, end); break; case SC_MAX: val = p_profile->calcMax(code, type, start, end); break; case SC_CPH: val = p_profile->calcCount(code, type, start, end) / p_profile->calcHours(type, start, end); break; case SC_SPH: fmt += "%"; val = 100.0 / p_profile->calcHours(type, start, end) * p_profile->calcSum(code, type, start, end) / 3600.0; break; case SC_ABOVE: fmt += "%"; val = 100.0 / p_profile->calcHours(type, start, end) * (p_profile->calcAboveThreshold(code, schema::channel[code].upperThreshold(), type, start, end) / 60.0); break; case SC_BELOW: fmt += "%"; val = 100.0 / p_profile->calcHours(type, start, end) * (p_profile->calcBelowThreshold(code, schema::channel[code].lowerThreshold(), type, start, end) / 60.0); break; default: break; }; } if ((val == std::numeric_limits::min()) || (val == std::numeric_limits::max())) { value = "Err"; } else { value = fmt.arg(val, 0, 'f', decimals); } } return value; } OSCAR-code-v1.3.1/oscar/statistics.h000066400000000000000000000132351417327530600171470ustar00rootroot00000000000000/* Statistics Report Generator Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef SUMMARY_H #define SUMMARY_H #include #include #include #include #include #include #include "SleepLib/schema.h" #include "SleepLib/machine.h" //! \brief Type of calculation on one statistics row enum StatCalcType { SC_UNDEFINED=0, SC_COLUMNHEADERS, SC_HEADING, SC_SUBHEADING, SC_MEDIAN, SC_AVG, SC_WAVG, SC_90P, SC_MIN, SC_MAX, SC_CPH, SC_SPH, SC_AHI, SC_HOURS, SC_COMPLIANCE, SC_DAYS, SC_ABOVE, SC_BELOW }; /*! \struct StatisticsRow \brief Describes a single row on the statistics page */ struct StatisticsRow { StatisticsRow() { calc=SC_UNDEFINED; } StatisticsRow(QString src, QString calc, QString type) { this->src = src; this->calc = lookupCalc(calc); this->type = lookupType(type); } StatisticsRow(QString src, StatCalcType calc, MachineType type) { this->src = src; this->calc = calc; this->type = type; } StatisticsRow(const StatisticsRow ©) { src=copy.src; calc=copy.calc; type=copy.type; } QString src; StatCalcType calc; MachineType type; //! \brief Looks up calculation type for this row StatCalcType lookupCalc(QString calc) { if (calc.compare("avg",Qt::CaseInsensitive)==0) { return SC_AVG; } else if (calc.compare("w-avg",Qt::CaseInsensitive)==0) { return SC_WAVG; } else if (calc.compare("median",Qt::CaseInsensitive)==0) { return SC_MEDIAN; } else if (calc.compare("90%",Qt::CaseInsensitive)==0) { return SC_90P; } else if (calc.compare("min", Qt::CaseInsensitive)==0) { return SC_MIN; } else if (calc.compare("max", Qt::CaseInsensitive)==0) { return SC_MAX; } else if (calc.compare("cph", Qt::CaseInsensitive)==0) { return SC_CPH; } else if (calc.compare("sph", Qt::CaseInsensitive)==0) { return SC_SPH; } else if (calc.compare("ahi", Qt::CaseInsensitive)==0) { return SC_AHI; } else if (calc.compare("hours", Qt::CaseInsensitive)==0) { return SC_HOURS; } else if (calc.compare("compliance", Qt::CaseInsensitive)==0) { return SC_COMPLIANCE; } else if (calc.compare("days", Qt::CaseInsensitive)==0) { return SC_DAYS; } else if (calc.compare("heading", Qt::CaseInsensitive)==0) { return SC_HEADING; } else if (calc.compare("subheading", Qt::CaseInsensitive)==0) { return SC_SUBHEADING; } return SC_UNDEFINED; } //! \brief Look up machine type MachineType lookupType(QString type) { if (type.compare("cpap", Qt::CaseInsensitive)==0) { return MT_CPAP; } else if (type.compare("oximeter", Qt::CaseInsensitive)==0) { return MT_OXIMETER; } else if (type.compare("sleepstage", Qt::CaseInsensitive)==0) { return MT_SLEEPSTAGE; } return MT_UNKNOWN; } ChannelID channel() { return schema::channel[src].id(); } QString value(QDate start, QDate end); }; //! \class Prescription (Machine) setting class RXItem { public: RXItem() { machine = nullptr; ahi = rdi = 0; highlight = 0; hours = 0; } RXItem(const RXItem & copy) { start = copy.start; end = copy.end; days = copy.days; s_count = copy.s_count; s_sum = copy.s_sum; ahi = copy.ahi; rdi = copy.rdi; hours = copy.hours; machine = copy.machine; relief = copy.relief; mode = copy.mode; pressure = copy.pressure; dates = copy.dates; highlight = copy.highlight; } RXItem& operator=(const RXItem&) = default; inline quint64 count(ChannelID id) const { QHash::const_iterator it = s_count.find(id); if (it == s_count.end()) return 0; return it.value(); } inline double sum(ChannelID id) const{ QHash::const_iterator it = s_sum.find(id); if (it == s_sum.end()) return 0; return it.value(); } QDate start; QDate end; int days; QHash s_count; QHash s_sum; quint64 ahi; quint64 rdi; double hours; Machine * machine; QString relief; QString mode; QString pressure; QMap dates; short highlight; }; class Statistics : public QObject { Q_OBJECT public: explicit Statistics(QObject *parent = 0); void loadRXChanges(); void saveRXChanges(); void updateRXChanges(); QString GenerateHTML(); QString UpdateRecordsBox(); static void printReport(QWidget *parent = nullptr); protected: QString getUserInfo(); QString getRDIorAHIText(); QString htmlNoData(); QString generateHeader(bool showheader); QString generateFooter(bool showinfo=true); QString GenerateMachineList(); QString GenerateRXChanges(); QString GenerateCPAPUsage(); // Using a map to maintain order QList rows; QMap calcnames; QMap machinenames; QMap rxitems; QList record_best_ahi; QList record_worst_ahi; signals: public slots: }; #endif // SUMMARY_H OSCAR-code-v1.3.1/oscar/tests/000077500000000000000000000000001417327530600157425ustar00rootroot00000000000000OSCAR-code-v1.3.1/oscar/tests/AutoTest.h000066400000000000000000000030011417327530600176550ustar00rootroot00000000000000// From https://qtcreator.blogspot.com/2009/10/running-multiple-unit-tests.html #ifndef AUTOTEST_H #define AUTOTEST_H #include #include #include #include namespace AutoTest { typedef QList TestList; inline TestList& testList() { static TestList list; return list; } inline bool findObject(QObject* object) { TestList& list = testList(); if (list.contains(object)) { return true; } foreach (QObject* test, list) { if (test->objectName() == object->objectName()) { return true; } } return false; } inline void addTest(QObject* object) { TestList& list = testList(); if (!findObject(object)) { list.append(object); } } inline int run(int argc, char *argv[]) { int ret = 0; foreach (QObject* test, testList()) { ret += QTest::qExec(test, argc, argv); } return ret; } } template class Test { public: QSharedPointer child; Test(const QString& name) : child(new T) { child->setObjectName(name); AutoTest::addTest(child.data()); } }; #define DECLARE_TEST(className) static Test t(#className); #define TEST_MAIN \ int main(int argc, char *argv[]) \ { \ return AutoTest::run(argc, argv); \ } #endif // AUTOTEST_H OSCAR-code-v1.3.1/oscar/tests/deviceconnectiontests.cpp000066400000000000000000000152501417327530600230530ustar00rootroot00000000000000/* Device Connection Unit Tests * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "deviceconnectiontests.h" #include "SleepLib/deviceconnection.h" // TODO: eventually this should move to serialoximeter.h #include "SleepLib/loader_plugins/cms50f37_loader.h" #include void DeviceConnectionTests::testSerialPortInfoSerialization() { QString serialized; // With VID and PID const QString tag = R"()"; SerialPortInfo info = SerialPortInfo(tag); Q_ASSERT(info.isNull() == false); Q_ASSERT(info.portName() == "cu.SLAB_USBtoUART"); Q_ASSERT(info.systemLocation() == "/dev/cu.SLAB_USBtoUART"); Q_ASSERT(info.description() == "CP210x USB to UART Bridge Controller"); Q_ASSERT(info.manufacturer() == "Silicon Labs"); Q_ASSERT(info.serialNumber() == "0001"); Q_ASSERT(info.hasVendorIdentifier()); Q_ASSERT(info.hasProductIdentifier()); Q_ASSERT(info.vendorIdentifier() == 0x10C4); Q_ASSERT(info.productIdentifier() == 0xEA60); serialized = info; Q_ASSERT(serialized == tag); // Without VID or PID const QString tag2 = R"()"; SerialPortInfo info2 = SerialPortInfo(tag2); Q_ASSERT(info2.isNull() == false); Q_ASSERT(info2.portName() == "cu.Bluetooth-Incoming-Port"); Q_ASSERT(info2.systemLocation() == "/dev/cu.Bluetooth-Incoming-Port"); Q_ASSERT(info2.description() == "incoming port - Bluetooth-Incoming-Port"); Q_ASSERT(info2.manufacturer() == ""); Q_ASSERT(info2.serialNumber() == ""); Q_ASSERT(info2.hasVendorIdentifier() == false); Q_ASSERT(info2.hasProductIdentifier() == false); serialized = info2; Q_ASSERT(serialized == tag2); // Empty const QString tag3 = R"()"; SerialPortInfo info3 = SerialPortInfo(tag3); Q_ASSERT(info3.isNull() == true); serialized = info3; Q_ASSERT(serialized == tag3); } void DeviceConnectionTests::testSerialPortScanning() { QString string; DeviceConnectionManager & devices = DeviceConnectionManager::getInstance(); devices.record(string); auto list1 = SerialPortInfo::availablePorts(); auto list2 = SerialPortInfo::availablePorts(); devices.record(nullptr); // string now contains the recorded XML. qDebug().noquote() << string; devices.replay(string); Q_ASSERT(list1 == SerialPortInfo::availablePorts()); Q_ASSERT(list2 == SerialPortInfo::availablePorts()); Q_ASSERT(list2 == SerialPortInfo::availablePorts()); // replaying past the recording should return the final state devices.replay(nullptr); // turn off replay auto list3 = SerialPortInfo::availablePorts(); // Test file-based recording/playback QTemporaryFile recording; Q_ASSERT(recording.open()); devices.record(&recording); list1 = SerialPortInfo::availablePorts(); list2 = SerialPortInfo::availablePorts(); devices.record(nullptr); recording.seek(0); devices.replay(&recording); Q_ASSERT(list1 == SerialPortInfo::availablePorts()); Q_ASSERT(list2 == SerialPortInfo::availablePorts()); Q_ASSERT(list2 == SerialPortInfo::availablePorts()); // replaying past the recording should return the final state devices.replay(nullptr); // turn off replay list3 = SerialPortInfo::availablePorts(); } #define ENABLE 0 #if ENABLE static void testDownload(const QString & loaderName) { SerialOximeter * oxi = qobject_cast(lookupLoader(loaderName)); Q_ASSERT(oxi); if (oxi->openDevice()) { bool open = true; oxi->resetDevice(); int session_count = oxi->getSessionCount(); qDebug() << session_count << "sessions"; for (int i = 0; i < session_count; i++) { qDebug() << i+1 << oxi->getDateTime(i) << oxi->getDuration(i); oxi->Open("import"); if (oxi->commandDriven()) { oxi->getSessionData(i); while (!oxi->isImporting() && !oxi->isAborted()) { //QThread::msleep(10); QCoreApplication::processEvents(); } while (oxi->isImporting() && !oxi->isAborted()) { //QThread::msleep(10); QCoreApplication::processEvents(); } } open = oxi->openDevice(); // annoyingly import currently closes the device, so reopen it } if (open) { oxi->closeDevice(); } } oxi->trashRecords(); } #endif void DeviceConnectionTests::testOximeterConnection() { CMS50F37Loader::Register(); // Initialize main event loop to initialize threads and enable signals and slots. int argc = 1; const char* argv = "test"; QCoreApplication app(argc, (char**) &argv); #if ENABLE DeviceConnectionManager & devices = DeviceConnectionManager::getInstance(); /* QString string; devices.record(string); // new API QString portName = "cu.SLAB_USBtoUART"; { QScopedPointer conn(devices.openConnection("serial", portName)); Q_ASSERT(conn); Q_ASSERT(devices.openConnection("serial", portName) == nullptr); } { QScopedPointer conn(devices.openSerialPortConnection(portName)); Q_ASSERT(conn); Q_ASSERT(devices.openSerialPortConnection(portName) == nullptr); } // legacy API SerialPort port; port.setPortName(portName); if (port.open(QSerialPort::ReadWrite)) { qDebug() << "port opened"; port.close(); } devices.record(nullptr); qDebug().noquote() << string; */ QFile out("test.xml"); Q_ASSERT(out.open(QFile::ReadWrite)); devices.record(&out); QFile file("cms50f37.xml"); if (!file.exists()) { qDebug() << "Recording oximeter connection"; Q_ASSERT(file.open(QFile::ReadWrite)); devices.record(&file); testDownload(cms50f37_class_name); devices.record(nullptr); file.close(); } qDebug() << "Replaying oximeter connection"; Q_ASSERT(file.open(QFile::ReadOnly)); devices.replay(&file); testDownload(cms50f37_class_name); devices.replay(nullptr); file.close(); #endif } OSCAR-code-v1.3.1/oscar/tests/deviceconnectiontests.h000066400000000000000000000010021417327530600225060ustar00rootroot00000000000000/* Device Connection Unit Tests * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "tests/AutoTest.h" class DeviceConnectionTests : public QObject { Q_OBJECT private slots: void testSerialPortInfoSerialization(); void testSerialPortScanning(); void testOximeterConnection(); }; DECLARE_TEST(DeviceConnectionTests) OSCAR-code-v1.3.1/oscar/tests/dreemtests.cpp000066400000000000000000000053321417327530600206300ustar00rootroot00000000000000/* Dreem Unit Tests * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "dreemtests.h" #include "sessiontests.h" #define TESTDATA_PATH "./testdata/" static DreemLoader* s_loader = nullptr; static QString dreemOutputPath(const QString & inpath, int sid, const QString & suffix); void DreemTests::initTestCase(void) { p_profile = new Profile(TESTDATA_PATH "profile/", false); schema::init(); DreemLoader::Register(); s_loader = dynamic_cast(lookupLoader(dreem_class_name)); } void DreemTests::cleanupTestCase(void) { delete p_profile; p_profile = nullptr; } // ==================================================================================================== static void parseAndEmitSessionYaml(const QString & path) { qDebug() << path; if (s_loader->openCSV(path)) { int count = 0; Session* session; while ((session = s_loader->readNextSession()) != nullptr) { QString outpath = dreemOutputPath(path, session->session(), "-session.yml"); SessionToYaml(outpath, session, true); delete session; count++; } if (count == 0) { qWarning() << "no sessions found"; } s_loader->closeCSV(); } else { qWarning() << "unable to open file"; } } void DreemTests::testSessionsToYaml() { static const QString root_path = TESTDATA_PATH "dreem/input/"; QDir root(root_path); root.setFilter(QDir::NoDotAndDotDot | QDir::Dirs); root.setSorting(QDir::Name); for (auto & dir_info : root.entryInfoList()) { QDir dir(dir_info.canonicalFilePath()); dir.setFilter(QDir::Files | QDir::Hidden); dir.setNameFilters(QStringList("*.csv")); dir.setSorting(QDir::Name); for (auto & fi : dir.entryInfoList()) { parseAndEmitSessionYaml(fi.canonicalFilePath()); } } } // ==================================================================================================== QString dreemOutputPath(const QString & inpath, int sid, const QString & suffix) { // Output to dreem/output/DIR/FILENAME(-session.yml, etc.) QFileInfo path(inpath); QString basename = path.baseName(); QString foldername = path.dir().dirName(); QDir outdir(TESTDATA_PATH "dreem/output/" + foldername); outdir.mkpath("."); QString filename = QString("%1-%2%3") .arg(basename) .arg(sid, 8, 10, QChar('0')) .arg(suffix); return outdir.path() + QDir::separator() + filename; } OSCAR-code-v1.3.1/oscar/tests/dreemtests.h000066400000000000000000000010701417327530600202700ustar00rootroot00000000000000/* Dreem Unit Tests * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef DREEMTESTS_H #define DREEMTESTS_H #include "AutoTest.h" #include "../SleepLib/loader_plugins/dreem_loader.h" class DreemTests : public QObject { Q_OBJECT private slots: void initTestCase(); void testSessionsToYaml(); void cleanupTestCase(); }; DECLARE_TEST(DreemTests) #endif // DREEMTESTS_H OSCAR-code-v1.3.1/oscar/tests/prs1tests.cpp000066400000000000000000000407241417327530600204250ustar00rootroot00000000000000/* PRS1 Unit Tests * * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "prs1tests.h" #include "sessiontests.h" #include "../SleepLib/loader_plugins/prs1_loader.h" #include "../SleepLib/loader_plugins/prs1_parser.h" #include "../SleepLib/importcontext.h" #define TESTDATA_PATH "./testdata/" //#define TEST_OSCAR_CALCS static PRS1Loader* s_loader = nullptr; static void iterateTestCards(const QString & root, void (*action)(const QString &)); static QString prs1OutputPath(const QString & inpath, const QString & serial, const QString & basename, const QString & suffix); static QString prs1OutputPath(const QString & inpath, const QString & serial, int session, const QString & suffix); void PRS1Tests::initTestCase(void) { p_profile = new Profile(TESTDATA_PATH "profile/", false); #ifdef TEST_OSCAR_CALCS p_pref = new Preferences("Preferences"); p_pref->Open(); AppSetting = new AppWideSetting(p_pref); #endif schema::init(); PRS1Loader::Register(); s_loader = dynamic_cast(lookupLoader(prs1_class_name)); } void PRS1Tests::cleanupTestCase(void) { delete p_profile; p_profile = nullptr; #ifdef TEST_OSCAR_CALCS delete p_pref; #endif } // ==================================================================================================== extern PRS1ModelInfo s_PRS1ModelInfo; void PRS1Tests::testMachineSupport() { QHash tested = { { "ModelNumber", "550P" }, { "Family", "0" }, { "FamilyVersion", "3" }, }; QHash supported = { { "ModelNumber", "700X999" }, { "Family", "0" }, { "FamilyVersion", "6" }, }; QHash unsupported = { { "ModelNumber", "550P" }, { "Family", "0" }, { "FamilyVersion", "9" }, }; Q_ASSERT(s_PRS1ModelInfo.IsSupported(5, 3)); Q_ASSERT(!s_PRS1ModelInfo.IsSupported(5, 9)); Q_ASSERT(!s_PRS1ModelInfo.IsSupported(9, 9)); Q_ASSERT(s_PRS1ModelInfo.IsTested("550P", 0, 2)); Q_ASSERT(s_PRS1ModelInfo.IsTested("550P", 0, 3)); Q_ASSERT(s_PRS1ModelInfo.IsTested("760P", 0, 4)); Q_ASSERT(s_PRS1ModelInfo.IsTested("700X110", 0, 6)); Q_ASSERT(!s_PRS1ModelInfo.IsTested("700X999", 0, 6)); Q_ASSERT(s_PRS1ModelInfo.IsTested(tested)); Q_ASSERT(!s_PRS1ModelInfo.IsTested(supported)); Q_ASSERT(s_PRS1ModelInfo.IsSupported(tested)); Q_ASSERT(s_PRS1ModelInfo.IsSupported(supported)); Q_ASSERT(!s_PRS1ModelInfo.IsSupported(unsupported)); } // ==================================================================================================== void parseAndEmitSessionYaml(const QString & path) { qDebug() << path; ImportContext* ctx = new ProfileImportContext(p_profile); s_loader->SetContext(ctx); ctx->connect(ctx, &ImportContext::importEncounteredUnexpectedData, [=]() { qWarning() << "*** Found unexpected data"; }); // This mirrors the functional bits of PRS1Loader::OpenMachine. // TODO: Refactor PRS1Loader so that the tests can use the same // underlying logic as OpenMachine rather than duplicating it here. QStringList paths; QString propertyfile; int sessionid_base; sessionid_base = s_loader->FindSessionDirsAndProperties(path, paths, propertyfile); bool supported = s_loader->CreateMachineFromProperties(propertyfile); if (!supported) { qWarning() << "*** Skipping unsupported machine!"; return; } Machine* m = ctx->m_machine; s_loader->ScanFiles(paths, sessionid_base); // Each session now has a PRS1Import object in m_MLtasklist QList::iterator i; while (!s_loader->m_MLtasklist.isEmpty()) { ImportTask* task = s_loader->m_MLtasklist.takeFirst(); // Run the parser PRS1Import* import = dynamic_cast(task); bool ok = import->ParseSession(); // Emit the parsed session data to compare against our regression benchmarks Session* session = import->session; QString outpath = prs1OutputPath(path, m->serial(), session->session(), "-session.yml"); #ifdef TEST_OSCAR_CALCS session->s_events_loaded = true; session->UpdateSummaries(); #endif SessionToYaml(outpath, session, ok); delete session; delete task; } delete ctx; } void PRS1Tests::testSessionsToYaml() { iterateTestCards(TESTDATA_PATH "prs1/input/", parseAndEmitSessionYaml); } // ==================================================================================================== static QString dur(qint64 msecs) { qint64 s = msecs / 1000L; int h = s / 3600; s -= h * 3600; int m = s / 60; s -= m * 60; return QString("%1:%2:%3") .arg(h, 2, 10, QChar('0')) .arg(m, 2, 10, QChar('0')) .arg(s, 2, 10, QChar('0')); } static QString byteList(QByteArray data, int limit=-1) { int count = data.size(); if (limit == -1 || limit > count) limit = count; int first = limit / 2; int last = limit - first; #if 1 // Optimized after profiling regression tests. QString s; s.reserve(3 * limit + 4); // "NN " for each byte + possible "... " in the middle const unsigned char* b = (const unsigned char*) data.constData(); for (int i = 0; i < first; i++) { s.append(QString::asprintf("%02X ", b[i])); } if (limit < count) { s.append(QStringLiteral("... ")); } for (int i = count - last; i < count; i++) { s.append(QString::asprintf("%02X ", b[i])); } s.resize(s.size() - 1); // remove trailing space #else // Unoptimized original, slows down regression tests. QStringList l; for (int i = 0; i < first; i++) { l.push_back(QString( "%1" ).arg((int) data[i] & 0xFF, 2, 16, QChar('0') ).toUpper()); } if (limit < count) l.push_back("..."); for (int i = count - last; i < count; i++) { l.push_back(QString( "%1" ).arg((int) data[i] & 0xFF, 2, 16, QChar('0') ).toUpper()); } QString s = l.join(" "); #endif return s; } void ChunkToYaml(QTextStream & out, PRS1DataChunk* chunk, bool ok) { // chunk header out << "chunk:" << '\n'; out << " at: " << hex << chunk->m_filepos << '\n'; out << " parsed: " << ok << '\n'; out << " version: " << dec << chunk->fileVersion << '\n'; out << " size: " << chunk->blockSize << '\n'; out << " htype: " << chunk->htype << '\n'; out << " family: " << chunk->family << '\n'; out << " familyVersion: " << chunk->familyVersion << '\n'; out << " ext: " << chunk->ext << '\n'; out << " session: " << chunk->sessionid << '\n'; out << " start: " << ts(chunk->timestamp * 1000L) << '\n'; out << " duration: " << dur(chunk->duration * 1000L) << '\n'; // hblock for V3 non-waveform chunks if (chunk->fileVersion == 3 && chunk->htype == 0) { out << " hblock:" << '\n'; QMapIterator i(chunk->hblock); while (i.hasNext()) { i.next(); out << " " << (int) i.key() << ": " << i.value() << '\n'; } } // waveform chunks if (chunk->htype == 1) { out << " intervals: " << chunk->interval_count << '\n'; out << " intervalSeconds: " << (int) chunk->interval_seconds << '\n'; out << " interleave:" << '\n'; for (int i=0; i < chunk->waveformInfo.size(); i++) { const PRS1Waveform & w = chunk->waveformInfo.at(i); out << " " << i << ": " << w.interleave << '\n'; } out << " end: " << ts((chunk->timestamp + chunk->duration) * 1000L) << '\n'; } // header checksum out << " checksum: " << hex << chunk->storedChecksum << '\n'; if (chunk->storedChecksum != chunk->calcChecksum) { out << " calcChecksum: " << hex << chunk->calcChecksum << '\n'; } // data bool dump_data = true; if (chunk->m_parsedData.size() > 0) { dump_data = false; out << " events:" << '\n'; for (auto & e : chunk->m_parsedData) { QString name = e->typeName(); if (name == "raw" || name == "unknown") { dump_data = true; } QMap contents = e->contents(); if (name == "setting" && contents.size() == 1) { out << " - set_" << contents.firstKey() << ": " << contents.first() << '\n'; } else { out << " - " << name << ":" << '\n'; // Always emit start first if present if (contents.contains("start")) { out << " " << "start" << ": " << contents["start"] << '\n'; } for (auto & key : contents.keys()) { if (key == "start") continue; out << " " << key << ": " << contents[key] << '\n'; } } } } if (dump_data || !ok) { out << " data: " << byteList(chunk->m_data, 100) << '\n'; } // data CRC out << " crc: " << hex << chunk->storedCrc << '\n'; if (chunk->storedCrc != chunk->calcCrc) { out << " calcCrc: " << hex << chunk->calcCrc << '\n'; } out << '\n'; } void parseAndEmitChunkYaml(const QString & path) { bool FV = false; // set this to true to emit family/familyVersion for this path qDebug() << path; ImportContext* ctx = new ProfileImportContext(p_profile); s_loader->SetContext(ctx); QHash> written; QStringList paths; QString propertyfile; int sessionid_base; sessionid_base = s_loader->FindSessionDirsAndProperties(path, paths, propertyfile); bool supported = s_loader->CreateMachineFromProperties(propertyfile); if (!supported) { qWarning() << "*** Skipping unsupported machine!"; return; } Machine* m = ctx->m_machine; // This mirrors the functional bits of PRS1Loader::ScanFiles. QDir dir; dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); int size = paths.size(); // for each p0/p1/p2/etc... folder for (int p=0; p < size; ++p) { dir.setPath(paths.at(p)); if (!dir.exists() || !dir.isReadable()) { qWarning() << dir.canonicalPath() << "can't read directory"; continue; } QFileInfoList flist = dir.entryInfoList(); // Scan for individual .00X files for (int i = 0; i < flist.size(); i++) { QFileInfo fi = flist.at(i); QString inpath = fi.canonicalFilePath(); bool ok; if (fi.fileName() == ".DS_Store") { continue; } QString ext_s = fi.fileName().section(".", -1); if (ext_s.toUpper().startsWith("B")) { // .B01, .B02, etc. ext_s = ext_s.mid(1); } int ext = ext_s.toInt(&ok); if (!ok) { // not a numerical extension qInfo() << inpath << "unexpected filename"; continue; } QString session_s = fi.fileName().section(".", 0, -2); int sessionid = session_s.toInt(&ok, sessionid_base); if (!ok) { // not a numerical session ID qInfo() << inpath << "unexpected filename"; continue; } // Create the YAML file. QString suffix = QString(".%1-chunks.yml").arg(ext, 3, 10, QChar('0')); QString outpath = prs1OutputPath(path, m->serial(), sessionid, suffix); QFile file(outpath); // Truncate the first time we open this file, to clear out any previous test data. // Otherwise append, allowing session chunks to be split among multiple files. if (!file.open(QFile::WriteOnly | (written.contains(outpath) ? QFile::Append : QFile::Truncate))) { qDebug() << outpath; Q_ASSERT(false); } QTextStream out(&file); // keep only P1234568/Pn/00000000.001 QStringList pathlist = QDir::toNativeSeparators(inpath).split(QDir::separator(), QString::SkipEmptyParts); QString relative = pathlist.mid(pathlist.size()-3).join(QDir::separator()); bool first_chunk_from_file = true; // Parse the chunks in the file. QList chunks = s_loader->ParseFile(inpath); for (int i=0; i < chunks.size(); i++) { PRS1DataChunk * chunk = chunks.at(i); if (i == 0 && FV) { qWarning() << QString("F%1V%2").arg(chunk->family).arg(chunk->familyVersion); FV=false; } // Only write unique chunks to the file. if (written[outpath].contains(chunk->hash()) == false) { if (first_chunk_from_file) { out << "file: " << relative << '\n'; first_chunk_from_file = false; } bool ok = true; // Parse the inner data. switch (chunk->ext) { case 0: ok = chunk->ParseCompliance(); break; case 1: ok = chunk->ParseSummary(); break; case 2: ok = chunk->ParseEvents(); break; case 5: break; // skip flow/pressure waveforms case 6: break; // skip oximetry data default: qWarning() << relative << "unexpected file type"; break; } // Emit the YAML. ChunkToYaml(out, chunk, ok); written[outpath] += chunk->hash(); } delete chunk; } file.close(); } } delete ctx; p_profile->removeMachine(m); delete m; } void PRS1Tests::testChunksToYaml() { iterateTestCards(TESTDATA_PATH "prs1/input/", parseAndEmitChunkYaml); } // ==================================================================================================== QString prs1OutputPath(const QString & inpath, const QString & serial, int session, const QString & suffix) { QString basename = QString("%1").arg(session, 8, 10, QChar('0')); return prs1OutputPath(inpath, serial, basename, suffix); } QString prs1OutputPath(const QString & inpath, const QString & serial, const QString & basename, const QString & suffix) { // Output to prs1/output/FOLDER/SERIAL-000000(-session.yml, etc.) QDir path(inpath); QStringList pathlist = QDir::toNativeSeparators(inpath).split(QDir::separator(), QString::SkipEmptyParts); pathlist.pop_back(); // drop serial number directory pathlist.pop_back(); // drop P-Series directory QString foldername = pathlist.last(); QDir outdir(TESTDATA_PATH "prs1/output/" + foldername); outdir.mkpath("."); QString filename = QString("%1-%2%3") .arg(serial) .arg(basename) .arg(suffix); return outdir.path() + QDir::separator() + filename; } void iterateTestCards(const QString & root, void (*action)(const QString &)) { QDir dir(root); dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs); dir.setSorting(QDir::Name); QFileInfoList flist = dir.entryInfoList(); // Look through each folder in the given root for (auto & fi : flist) { QStringList machinePaths = s_loader->FindMachinesOnCard(fi.canonicalFilePath()); // Tests should be run newest to oldest, since older sets tend to have more // complete data. (These are usually previously cleared data in the Clear0/Cn // directories.) The machines themselves will write out the summary data they // remember when they see an empty folder, without event or waveform data. // And since these tests (by design) overwrite existing output, we want the // earlier (more complete) data to be what's written last. // // Since the loader itself keeps only the first set of data it sees for a session, // we want to leave its earliest-to-latest ordering in place, and just reverse it // here. for (auto i = machinePaths.crbegin(); i != machinePaths.crend(); i++) { action(*i); } } } OSCAR-code-v1.3.1/oscar/tests/prs1tests.h000066400000000000000000000011161417327530600200620ustar00rootroot00000000000000/* PRS1 Unit Tests * * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef PRS1TESTS_H #define PRS1TESTS_H #include "AutoTest.h" class PRS1Tests : public QObject { Q_OBJECT private slots: void initTestCase(); void testMachineSupport(); void testChunksToYaml(); void testSessionsToYaml(); // void test2(); void cleanupTestCase(); }; DECLARE_TEST(PRS1Tests) #endif // PRS1TESTS_H OSCAR-code-v1.3.1/oscar/tests/rawdatatests.cpp000066400000000000000000000202201417327530600211500ustar00rootroot00000000000000/* Raw Data Unit Tests * * Copyright (c) 2021-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "rawdatatests.h" #include "rawdata.h" #include // Check QIODevice interface for consistency. void RawDataTests::testQIODeviceInterface() { // Create sample data. static const int DATA_SIZE = 256; QByteArray data(DATA_SIZE, 0); for (int i = 0; i < data.size(); i++) { data[i] = (DATA_SIZE-1) - i; } QBuffer qio(&data); // Create raw data wrapper. RawDataDevice raw_instance(qio, "sample"); Q_ASSERT(raw_instance.name() == "sample"); QIODevice & raw(raw_instance); // cast to its generic interface for accurate testing // Connect signals for testing. _RawDataTestSignalSink sink; connect(&raw, SIGNAL(channelReadyRead(int)), &sink, SLOT(onChannelReadyRead(int))); connect(&raw, SIGNAL(readyRead()), &sink, SLOT(onReadyRead())); connect(&raw, SIGNAL(readChannelFinished()), &sink, SLOT(onReadChannelFinished())); connect(&raw, SIGNAL(aboutToClose()), &sink, SLOT(onAboutToClose())); // Open Q_ASSERT(raw.isOpen() == qio.isOpen()); Q_ASSERT(raw.isReadable() == qio.isReadable()); Q_ASSERT(raw.isWritable() == qio.isWritable()); Q_ASSERT(raw.isWritable() == false); Q_ASSERT(raw.isSequential() == qio.isSequential()); Q_ASSERT(raw.openMode() == qio.openMode()); Q_ASSERT(raw.open(QIODevice::ReadWrite) == false); Q_ASSERT(raw.open(QIODevice::ReadOnly) == true); Q_ASSERT(raw.isOpen() == qio.isOpen()); Q_ASSERT(raw.isReadable() == qio.isReadable()); Q_ASSERT(raw.isWritable() == qio.isWritable()); Q_ASSERT(raw.isWritable() == false); Q_ASSERT(raw.isSequential() == qio.isSequential()); Q_ASSERT(raw.openMode() == qio.openMode()); // waitForReadyRead and ready signals Q_ASSERT(raw.waitForReadyRead(10000) == false); //Q_ASSERT(sink.m_channelReadyRead != -1); //Q_ASSERT(sink.m_readyRead == true); // Channels Q_ASSERT(raw.readChannelCount() == qio.readChannelCount()); for (int i = 0; i < raw.readChannelCount(); i++) { raw.setCurrentReadChannel(i); Q_ASSERT(raw.currentReadChannel() == i); Q_ASSERT(raw.currentReadChannel() == qio.currentReadChannel()); } // Text mode // Text mode is pretty awful, it just drops all \x0D, even without a trailing \x0A. Q_ASSERT(raw.isTextModeEnabled() == false); Q_ASSERT(raw.isTextModeEnabled() == qio.isTextModeEnabled()); raw.setTextModeEnabled(true); Q_ASSERT(raw.isTextModeEnabled() == true); raw.peek(1); // force a sync of text mode Q_ASSERT(raw.isTextModeEnabled() == qio.isTextModeEnabled()); raw.setTextModeEnabled(false); raw.peek(1); // force a sync of text mode Q_ASSERT(raw.isTextModeEnabled() == qio.isTextModeEnabled()); // seek/pos/getChar/ungetChar/readAll/atEnd // skip() is 5.10 or later, so we don't use or test it char ch; int pos = raw.pos(); Q_ASSERT(raw.pos() == qio.pos() - 1); // peek (above) only retracts raw's position after reading qio Q_ASSERT(raw.getChar(&ch) == true); Q_ASSERT(raw.pos() == qio.pos()); raw.ungetChar(ch); Q_ASSERT(raw.pos() == pos); Q_ASSERT(raw.pos() == qio.pos() - 1); // ungetChar only affects raw's buffer/position Q_ASSERT(ch == data[0]); Q_ASSERT(raw.size() == qio.size()); Q_ASSERT(raw.seek(16) == true); Q_ASSERT(raw.pos() == 16); Q_ASSERT(raw.pos() == qio.pos()); Q_ASSERT(raw.atEnd() == qio.atEnd()); // Check boundary conditions at end of device. Q_ASSERT(raw.seek(255) == true); Q_ASSERT(raw.getChar(&ch) == true); Q_ASSERT(raw.pos() == qio.pos()); Q_ASSERT(raw.atEnd() == true); Q_ASSERT(raw.atEnd() == qio.atEnd()); Q_ASSERT(raw.bytesAvailable() == qio.bytesAvailable()); raw.ungetChar(ch); Q_ASSERT(raw.atEnd() == false); Q_ASSERT(raw.atEnd() != qio.atEnd()); Q_ASSERT(raw.bytesAvailable() == qio.bytesAvailable() + 1); Q_ASSERT(raw.reset() == true); Q_ASSERT(raw.pos() == 0); Q_ASSERT(raw.pos() == qio.pos()); QByteArray all = raw.readAll(); Q_ASSERT(all == data); Q_ASSERT(raw.atEnd() == qio.atEnd()); Q_ASSERT(raw.bytesAvailable() == qio.bytesAvailable()); // canReadLine Q_ASSERT(raw.canReadLine() == qio.canReadLine()); raw.seek(255 - 0x0A); Q_ASSERT(raw.canReadLine() == true); Q_ASSERT(raw.canReadLine() == qio.canReadLine()); Q_ASSERT(raw.getChar(&ch) == true); Q_ASSERT(ch == 0x0A); Q_ASSERT(raw.canReadLine() == false); Q_ASSERT(raw.canReadLine() == qio.canReadLine()); raw.ungetChar(ch); Q_ASSERT(raw.canReadLine() == true); Q_ASSERT(raw.canReadLine() != qio.canReadLine()); // readLine x2 Q_ASSERT(raw.reset() == true); Q_ASSERT(raw.canReadLine() == qio.canReadLine()); char line[DATA_SIZE+1]; // plus trailing null int length = raw.readLine(line, sizeof(line)); pos = raw.pos(); raw.reset(); char line2[DATA_SIZE+1]; // plus trailing null int length2 = qio.readLine(line2, sizeof(line2)); Q_ASSERT(length == length2); Q_ASSERT(strcmp(line, line2) == 0); raw.reset(); QByteArray raw_readLine = raw.readLine(); raw.reset(); Q_ASSERT(raw_readLine == qio.readLine()); // read & peek x2 Q_ASSERT(raw.reset() == true); length = raw.read(line, 128); Q_ASSERT(length == 128); Q_ASSERT(raw.pos() == 128); Q_ASSERT(raw.pos() == qio.pos()); Q_ASSERT(memcmp(data.constData(), line, 128) == 0); Q_ASSERT(raw.pos() == 128); length2 = raw.peek(line2, 128); Q_ASSERT(raw.pos() == 128); Q_ASSERT(length == 128); Q_ASSERT(raw.pos() == qio.pos() - length); // peek only retracts raw's position after reading qio Q_ASSERT(memcmp(data.constData()+128, line2, 128) == 0); raw.reset(); QByteArray raw_read = raw.read(128); Q_ASSERT(length == 128); Q_ASSERT(raw.pos() == 128); Q_ASSERT(raw.pos() == qio.pos()); Q_ASSERT(raw_read == data.mid(0, 128)); Q_ASSERT(raw.pos() == 128); QByteArray raw_peek = raw.peek(128); Q_ASSERT(raw.pos() == 128); Q_ASSERT(length == 128); Q_ASSERT(raw.pos() == qio.pos() - 128); // peek only retracts raw's position after reading qio Q_ASSERT(raw_peek == data.mid(128, 128)); raw.reset(); // Transactions // These exist solely within raw and don't pass through to the underlying device. Q_ASSERT(raw.isTransactionStarted() == false); raw.startTransaction(); Q_ASSERT(raw.isTransactionStarted() == true); raw_peek = raw.read(128); Q_ASSERT(raw.pos() == 128); raw.rollbackTransaction(); Q_ASSERT(raw.isTransactionStarted() == false); Q_ASSERT(raw.pos() == 0); raw.startTransaction(); Q_ASSERT(raw.isTransactionStarted() == true); raw_read = raw.read(128); Q_ASSERT(raw.pos() == 128); raw.commitTransaction(); Q_ASSERT(raw.isTransactionStarted() == false); Q_ASSERT(raw.pos() == 128); // Close raw.close(); Q_ASSERT(raw.isOpen() == qio.isOpen()); Q_ASSERT(sink.m_aboutToClose); //Q_ASSERT(sink.m_readChannelFinished); // Unimplemented/untested: // bytesToWrite // currentWriteChannel // setCurentWriteChannel // putChar // waitForBytesWritten // write x3 // writeChannelCount // bytesWritten signal // channelBytesWritten signal } _RawDataTestSignalSink::_RawDataTestSignalSink() : QObject() { m_channelReadyRead = -1; m_readyRead = false; m_readChannelFinished = false; m_aboutToClose = false; } void _RawDataTestSignalSink::onAboutToClose() { m_aboutToClose = true; } void _RawDataTestSignalSink::onChannelReadyRead(int channel) { m_channelReadyRead = channel; } void _RawDataTestSignalSink::onReadChannelFinished() { m_readChannelFinished = true; } void _RawDataTestSignalSink::onReadyRead() { m_readyRead = true; } // TODO: Test sequential devices when we have a test case. // TODO: Test waitForReadySignal when we have a test case. // TODO: Test readyRead/channelReadyRead/onReadChannelFinished signals when we have a test case. OSCAR-code-v1.3.1/oscar/tests/rawdatatests.h000066400000000000000000000014771417327530600206320ustar00rootroot00000000000000/* Raw Data Unit Tests * * Copyright (c) 2021-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "tests/AutoTest.h" class RawDataTests : public QObject { Q_OBJECT private slots: void testQIODeviceInterface(); }; DECLARE_TEST(RawDataTests) class _RawDataTestSignalSink : public QObject { Q_OBJECT friend RawDataTests; private slots: void onAboutToClose(); void onChannelReadyRead(int); void onReadChannelFinished(); void onReadyRead(); private: _RawDataTestSignalSink(); bool m_aboutToClose; int m_channelReadyRead; bool m_readChannelFinished; bool m_readyRead; }; // Do not declare this as a test to be executed. OSCAR-code-v1.3.1/oscar/tests/resmedtests.cpp000066400000000000000000000067011417327530600210140ustar00rootroot00000000000000/* ResMed Unit Tests * * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "resmedtests.h" #include "sessiontests.h" #define TESTDATA_PATH "./testdata/" static ResmedLoader* s_loader = nullptr; static void iterateTestCards(const QString & root, void (*action)(const QString &)); static QString resmedOutputPath(const QString & inpath, int session, const QString & suffix); void ResmedTests::initTestCase(void) { p_profile = new Profile(TESTDATA_PATH "profile/", false); p_profile->session->setBackupCardData(false); p_pref = new Preferences("Preferences"); p_pref->Open(); AppSetting = new AppWideSetting(p_pref); schema::init(); ResmedLoader::Register(); s_loader = dynamic_cast(lookupLoader(resmed_class_name)); } void ResmedTests::cleanupTestCase(void) { delete AppSetting; delete p_profile; p_profile = nullptr; delete p_pref; } // ==================================================================================================== static QString s_currentPath; static void emitSessionYaml(ResmedLoader* /*loader*/, Session* session) { // Emit the parsed session data to compare against our regression benchmarks QString outpath = resmedOutputPath(s_currentPath, session->session(), "-session.yml"); SessionToYaml(outpath, session, true); } static void parseAndEmitSessionYaml(const QString & path) { qDebug() << path; // TODO: Refactor Resmed so that passing callbacks and using static globals isn't // necessary for testing. Both are used for now in order to introduce the minimal // set of changes into the Resmed loader needed for testing. s_currentPath = path; s_loader->OpenWithCallback(path, emitSessionYaml); } void ResmedTests::testSessionsToYaml() { iterateTestCards(TESTDATA_PATH "resmed/input/", parseAndEmitSessionYaml); } // ==================================================================================================== QString resmedOutputPath(const QString & inpath, int session, const QString & suffix) { // Output to resmed/output/FOLDER/000000(-session.yml, etc.) QDir path(inpath); QStringList pathlist = QDir::toNativeSeparators(inpath).split(QDir::separator(), QString::SkipEmptyParts); QString foldername = pathlist.last(); QDir outdir(TESTDATA_PATH "resmed/output/" + foldername); outdir.mkpath("."); QString filename = QString("%1%2") .arg(session) .arg(suffix); return outdir.path() + QDir::separator() + filename; } void iterateTestCards(const QString & root, void (*action)(const QString &)) { QDir dir(root); dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs); dir.setSorting(QDir::Name); QFileInfoList flist = dir.entryInfoList(); // Look through each folder in the given root for (auto & fi : flist) { // If it contains a DATALOG folder, it's a ResMed SD card QDir datalog(fi.canonicalFilePath() + QDir::separator() + "DATALOG"); if (datalog.exists()) { // Confirm that it contains the file that the ResMed loader expects QFileInfo idfile(fi.canonicalFilePath() + QDir::separator() + "Identification.tgt"); if (idfile.exists()) { action(fi.canonicalFilePath()); } } } } OSCAR-code-v1.3.1/oscar/tests/resmedtests.h000066400000000000000000000011361417327530600204560ustar00rootroot00000000000000/* ResMed Unit Tests * * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef RESMEDTESTS_H #define RESMEDTESTS_H #include "AutoTest.h" #include "../SleepLib/loader_plugins/resmed_loader.h" class ResmedTests : public QObject { Q_OBJECT private slots: void initTestCase(); //void testChunksToYaml(); void testSessionsToYaml(); void cleanupTestCase(); }; DECLARE_TEST(ResmedTests) #endif // RESMEDTESTS_H OSCAR-code-v1.3.1/oscar/tests/sessiontests.cpp000066400000000000000000000301061417327530600212140ustar00rootroot00000000000000/* Session Testing Support * * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include "sessiontests.h" static QString ts(qint64 msecs) { // TODO: make this UTC so that tests don't vary by where they're run return QDateTime::fromMSecsSinceEpoch(msecs).toString(Qt::ISODate); } static QString hex(int i) { return QString("0x") + QString::number(i, 16).toUpper(); } static QString dur(qint64 msecs) { qint64 s = msecs / 1000L; int h = s / 3600; s -= h * 3600; int m = s / 60; s -= m * 60; return QString("%1:%2:%3") .arg(h, 2, 10, QChar('0')) .arg(m, 2, 10, QChar('0')) .arg(s, 2, 10, QChar('0')); } #define ENUMSTRING(ENUM) case ENUM: s = #ENUM; break static QString eventListTypeName(EventListType t) { QString s; switch (t) { ENUMSTRING(EVL_Waveform); ENUMSTRING(EVL_Event); default: s = hex(t); qDebug() << "EVL" << qPrintable(s); } return s; } // ChannelIDs are not enums. Instead, they are global variables of the ChannelID type. // This allows definition of different IDs within different loader plugins, while // using Qt templates (such as QHash) that require a consistent data type for their key. // // Ideally there would be a central ChannelID registry class that could be queried // for names, make sure there aren't duplicate values, etc. For now we just fill the // below by hand. #define CHANNELNAME(CH) if (i == CH) { s = #CH; break; } extern ChannelID PRS1_Mode; extern ChannelID PRS1_TimedBreath, PRS1_HumidMode, PRS1_TubeTemp; extern ChannelID PRS1_FlexLock, PRS1_TubeLock, PRS1_RampType; extern ChannelID PRS1_BackupBreathMode, PRS1_BackupBreathRate, PRS1_BackupBreathTi; extern ChannelID PRS1_AutoTrial, PRS1_EZStart, PRS1_RiseTime, PRS1_RiseTimeLock; extern ChannelID PRS1_PeakFlow; extern ChannelID PRS1_VariableBreathing; extern ChannelID RMS9_EPR, RMS9_EPRLevel, RMS9_Mode, RMS9_SmartStart, RMS9_HumidStatus, RMS9_HumidLevel, RMS9_PtAccess, RMS9_Mask, RMS9_ABFilter, RMS9_ClimateControl, RMS9_TubeType, RMS9_Temp, RMS9_TempEnable, RMS9_RampEnable; static QString settingChannel(ChannelID i) { QString s; do { CHANNELNAME(CPAP_Mode); CHANNELNAME(CPAP_Pressure); CHANNELNAME(CPAP_PressureMin); CHANNELNAME(CPAP_PressureMax); CHANNELNAME(CPAP_EPAP); CHANNELNAME(CPAP_IPAP); CHANNELNAME(CPAP_PS); CHANNELNAME(CPAP_EPAPLo); CHANNELNAME(CPAP_EPAPHi); CHANNELNAME(CPAP_IPAPLo); CHANNELNAME(CPAP_IPAPHi); CHANNELNAME(CPAP_PSMin); CHANNELNAME(CPAP_PSMax); CHANNELNAME(CPAP_RampTime); CHANNELNAME(CPAP_RampPressure); CHANNELNAME(CPAP_RespRate); CHANNELNAME(CPAP_TidalVolume); CHANNELNAME(OXI_Pulse); // PRS1-specific channels CHANNELNAME(PRS1_Mode); CHANNELNAME(PRS1_FlexMode); CHANNELNAME(PRS1_FlexLevel); CHANNELNAME(PRS1_HumidStatus); CHANNELNAME(PRS1_HumidMode); CHANNELNAME(PRS1_TubeTemp); CHANNELNAME(PRS1_HumidLevel); CHANNELNAME(PRS1_HumidTargetTime); CHANNELNAME(PRS1_MaskResistLock); CHANNELNAME(PRS1_MaskResistSet); CHANNELNAME(PRS1_TimedBreath); CHANNELNAME(PRS1_HoseDiam); CHANNELNAME(PRS1_AutoOn); CHANNELNAME(PRS1_AutoOff); CHANNELNAME(PRS1_MaskAlert); CHANNELNAME(PRS1_ShowAHI); CHANNELNAME(PRS1_FlexLock); CHANNELNAME(PRS1_TubeLock); CHANNELNAME(PRS1_RampType); CHANNELNAME(PRS1_BackupBreathMode); CHANNELNAME(PRS1_BackupBreathRate); CHANNELNAME(PRS1_BackupBreathTi); CHANNELNAME(PRS1_AutoTrial); CHANNELNAME(PRS1_EZStart); CHANNELNAME(PRS1_RiseTime); CHANNELNAME(PRS1_RiseTimeLock); // ZEO-specific channels CHANNELNAME(ZEO_Awakenings); CHANNELNAME(ZEO_MorningFeel); CHANNELNAME(ZEO_TimeInWake); CHANNELNAME(ZEO_TimeInREM); CHANNELNAME(ZEO_TimeInLight); CHANNELNAME(ZEO_TimeInDeep); CHANNELNAME(ZEO_TimeToZ); CHANNELNAME(ZEO_ZQ); // Resmed-specific channels CHANNELNAME(RMS9_EPR); CHANNELNAME(RMS9_EPRLevel); CHANNELNAME(RMS9_Mode); CHANNELNAME(RMS9_SmartStart); CHANNELNAME(RMS9_HumidStatus); CHANNELNAME(RMS9_HumidLevel); CHANNELNAME(RMS9_Temp); CHANNELNAME(RMS9_TempEnable); CHANNELNAME(RMS9_ABFilter); CHANNELNAME(RMS9_PtAccess); CHANNELNAME(RMS9_ClimateControl); CHANNELNAME(RMS9_Mask); CHANNELNAME(RMS9_RampEnable); s = hex(i); qDebug() << "setting channel" << qPrintable(s); } while(false); return s; } static QString eventChannel(ChannelID i) { QString s; do { CHANNELNAME(CPAP_Obstructive); CHANNELNAME(CPAP_AllApnea); CHANNELNAME(CPAP_Hypopnea); CHANNELNAME(CPAP_PB); CHANNELNAME(CPAP_LeakTotal); CHANNELNAME(CPAP_Leak); CHANNELNAME(CPAP_LargeLeak); CHANNELNAME(CPAP_IPAP); CHANNELNAME(CPAP_EPAP); CHANNELNAME(CPAP_PS); CHANNELNAME(CPAP_IPAPLo); CHANNELNAME(CPAP_IPAPHi); CHANNELNAME(CPAP_RespRate); CHANNELNAME(CPAP_PTB); CHANNELNAME(PRS1_TimedBreath); CHANNELNAME(PRS1_PeakFlow); CHANNELNAME(CPAP_MinuteVent); CHANNELNAME(CPAP_TidalVolume); CHANNELNAME(CPAP_ClearAirway); CHANNELNAME(CPAP_FlowLimit); CHANNELNAME(CPAP_Snore); CHANNELNAME(CPAP_VSnore); CHANNELNAME(CPAP_VSnore2); CHANNELNAME(CPAP_NRI); CHANNELNAME(CPAP_RERA); CHANNELNAME(OXI_Pulse); CHANNELNAME(OXI_SPO2); CHANNELNAME(PRS1_BND); CHANNELNAME(CPAP_MaskPressureHi); CHANNELNAME(CPAP_FlowRate); CHANNELNAME(CPAP_Test1); CHANNELNAME(CPAP_Test2); CHANNELNAME(CPAP_PressurePulse); CHANNELNAME(CPAP_Pressure); CHANNELNAME(PRS1_VariableBreathing); CHANNELNAME(CPAP_PressureSet); CHANNELNAME(CPAP_IPAPSet); CHANNELNAME(CPAP_EPAPSet); CHANNELNAME(POS_Movement); CHANNELNAME(ZEO_SleepStage); // Resmed-specific channels CHANNELNAME(CPAP_Apnea); CHANNELNAME(CPAP_MaskPressure); CHANNELNAME(CPAP_Te); CHANNELNAME(CPAP_Ti); CHANNELNAME(CPAP_IE); CHANNELNAME(CPAP_FLG); CHANNELNAME(CPAP_AHI); CHANNELNAME(CPAP_TgMV); // Calculated channels CHANNELNAME(CPAP_RDI); s = hex(i); qDebug() << "event channel" << qPrintable(s); } while(false); return s; } static QString intList(EventStoreType* data, int count, int limit=-1) { if (limit == -1 || limit > count) limit = count; int first = limit / 2; int last = limit - first; QStringList l; for (int i = 0; i < first; i++) { l.push_back(QString::number(data[i])); } if (limit < count) l.push_back("..."); for (int i = count - last; i < count; i++) { l.push_back(QString::number(data[i])); } QString s = "[ " + l.join(",") + " ]"; return s; } static QString intList(quint32* data, int count, int limit=-1) { if (limit == -1 || limit > count) limit = count; int first = limit / 2; int last = limit - first; QStringList l; for (int i = 0; i < first; i++) { l.push_back(QString::number(data[i] / 1000)); } if (limit < count) l.push_back("..."); for (int i = count - last; i < count; i++) { l.push_back(QString::number(data[i] / 1000)); } QString s = "[ " + l.join(",") + " ]"; return s; } void SessionToYaml(QString filepath, Session* session, bool ok) { QFile file(filepath); if (!file.open(QFile::WriteOnly | QFile::Truncate)) { qDebug() << filepath; Q_ASSERT(false); } QTextStream out(&file); out << "session:" << '\n'; out << " id: " << session->session() << '\n'; out << " start: " << ts(session->first()) << '\n'; out << " end: " << ts(session->last()) << '\n'; out << " valid: " << ok << '\n'; if (!session->m_slices.isEmpty()) { out << " slices:" << '\n'; for (auto & slice : session->m_slices) { QString s; switch (slice.status) { case MaskOn: s = "mask on"; break; case MaskOff: s = "mask off"; break; case EquipmentOff: s = "equipment off"; break; default: s = "unknown"; break; } out << " - status: " << s << '\n'; out << " start: " << ts(slice.start) << '\n'; out << " end: " << ts(slice.end) << '\n'; } } qint64 total_time = 0; if (session->first() != 0) { Day day; day.addSession(session); total_time = day.total_time(); day.removeSession(session); } out << " total_time: " << dur(total_time) << '\n'; out << " settings:" << '\n'; // We can't get deterministic ordering from QHash iterators, so we need to create a list // of sorted ChannelIDs. QList keys = session->settings.keys(); std::sort(keys.begin(), keys.end()); for (QList::iterator key = keys.begin(); key != keys.end(); key++) { QVariant & value = session->settings[*key]; QString s; if ((QMetaType::Type) value.type() == QMetaType::Float) { s = QString::number(value.toFloat()); // Print the shortest accurate representation rather than QVariant's full precision. } else { s = value.toString(); } out << " " << settingChannel(*key) << ": " << s << '\n'; } out << " events:" << '\n'; keys = session->eventlist.keys(); std::sort(keys.begin(), keys.end()); for (QList::iterator key = keys.begin(); key != keys.end(); key++) { out << " " << eventChannel(*key) << ": " << '\n'; // Note that this is a vector of lists QVector &ev = session->eventlist[*key]; int ev_size = ev.size(); if (ev_size == 0) { continue; } EventList &e = *ev[0]; // Multiple eventlists in a channel are used to account for discontiguous data. // See CoalesceWaveformChunks for the coalescing of multiple contiguous waveform // chunks and ParseWaveforms/ParseOximetry for the creation of eventlists per // coalesced chunk. // // This can also be used for other discontiguous data, such as PRS1 statistics // that are omitted when breathing is not detected. for (int j = 0; j < ev_size; j++) { e = *ev[j]; out << " - count: " << (qint32)e.count() << '\n'; if (e.count() == 0) continue; out << " first: " << ts(e.first()) << '\n'; out << " last: " << ts(e.last()) << '\n'; out << " type: " << eventListTypeName(e.type()) << '\n'; out << " rate: " << e.rate() << '\n'; out << " gain: " << e.gain() << '\n'; out << " offset: " << e.offset() << '\n'; if (!e.dimension().isEmpty()) { out << " dimension: " << e.dimension() << '\n'; } out << " data:" << '\n'; out << " min: " << e.Min() << '\n'; out << " max: " << e.Max() << '\n'; out << " raw: " << intList((EventStoreType*) e.m_data.data(), e.count(), 100) << '\n'; if (e.type() != EVL_Waveform) { out << " delta: " << intList((quint32*) e.m_time.data(), e.count(), 100) << '\n'; } if (e.hasSecondField()) { out << " data2:" << '\n'; out << " min: " << e.min2() << '\n'; out << " max: " << e.max2() << '\n'; out << " raw: " << intList((EventStoreType*) e.m_data2.data(), e.count(), 100) << '\n'; } } } file.close(); } OSCAR-code-v1.3.1/oscar/tests/sessiontests.h000066400000000000000000000006501417327530600206620ustar00rootroot00000000000000/* Session Testing Support * * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef SESSIONTESTS_H #define SESSIONTESTS_H #include "../SleepLib/session.h" void SessionToYaml(QString filepath, Session* session, bool ok); #endif // SESSIONTESTS_H OSCAR-code-v1.3.1/oscar/tests/versiontests.cpp000066400000000000000000000035751417327530600212300ustar00rootroot00000000000000/* Version Unit Tests * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "versiontests.h" #include "version.h" void VersionTests::testCurrentVersion() { qDebug() << getVersion(); // If this fails, it means that the defined VERSION isn't valid and needs fixing! Q_ASSERT(getVersion().IsValid()); } void VersionTests::testPrecedence() { // This is the list of precedence examples from the Semantic Version documentation: Q_ASSERT(Version("1.0.0-alpha") < Version("1.0.0-alpha.1")); Q_ASSERT(Version("1.0.0-alpha.1") < Version("1.0.0-alpha.beta")); Q_ASSERT(Version("1.0.0-alpha.beta") < Version("1.0.0-beta")); Q_ASSERT(Version("1.0.0-beta") < Version("1.0.0-beta.2")); Q_ASSERT(Version("1.0.0-beta.2") < Version("1.0.0-beta.11")); Q_ASSERT(Version("1.0.0-beta.11") < Version("1.0.0-rc.1")); Q_ASSERT(Version("1.0.0-rc.1") < Version("1.0.0")); Q_ASSERT(Version("1.0.0-alpha+001") == Version("1.0.0-alpha+002")); Q_ASSERT(Version("1.0.0+20130313144700") == Version("1.0.0+20200313144700")); Q_ASSERT(Version("1.0.0-beta+exp.sha.5114f85") == Version("1.0.0-beta+exp.sha.00000000")); // This is the list of precedence that we expect to work correctly as of 1.1.0: Q_ASSERT(Version("1.0.1-r1") < Version("1.1.0-testing-1")); Q_ASSERT(Version("1.1.0-testing-1") < Version("1.1.0-testing-4")); Q_ASSERT(Version("1.1.0-testing-4") < Version("1.1.0-beta-1")); Q_ASSERT(Version("1.1.0-beta-1") < Version("1.1.0-beta-2")); Q_ASSERT(Version("1.1.0-beta-2") < Version("1.1.0-rc.1")); Q_ASSERT(Version("1.1.0-rc.1") < Version("1.1.0-rc.2")); Q_ASSERT(Version("1.1.0-rc.2") < Version("1.1.0")); Q_ASSERT(Version("1.1.0-rc.2") < Version("1.2.0")); } OSCAR-code-v1.3.1/oscar/tests/versiontests.h000066400000000000000000000006561417327530600206720ustar00rootroot00000000000000/* Version Unit Tests * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "tests/AutoTest.h" class VersionTests : public QObject { Q_OBJECT private slots: void testCurrentVersion(); void testPrecedence(); }; DECLARE_TEST(VersionTests) OSCAR-code-v1.3.1/oscar/tests/viatomtests.cpp000066400000000000000000000045611417327530600210360ustar00rootroot00000000000000/* Viatom Unit Tests * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "viatomtests.h" #include "sessiontests.h" #define TESTDATA_PATH "./testdata/" static ViatomLoader* s_loader = nullptr; static QString viatomOutputPath(const QString & inpath, const QString & suffix); void ViatomTests::initTestCase(void) { p_profile = new Profile(TESTDATA_PATH "profile/", false); schema::init(); ViatomLoader::Register(); s_loader = dynamic_cast(lookupLoader(viatom_class_name)); } void ViatomTests::cleanupTestCase(void) { delete p_profile; p_profile = nullptr; } // ==================================================================================================== static void parseAndEmitSessionYaml(const QString & path) { qDebug() << path; Session* session = s_loader->ParseFile(path); if (session != nullptr) { QString outpath = viatomOutputPath(path, "-session.yml"); SessionToYaml(outpath, session, true); delete session; } } void ViatomTests::testSessionsToYaml() { static const QString root_path = TESTDATA_PATH "viatom/input/"; QDir root(root_path); root.setFilter(QDir::NoDotAndDotDot | QDir::Dirs); root.setSorting(QDir::Name); for (auto & dir_info : root.entryInfoList()) { QDir dir(dir_info.canonicalFilePath()); dir.setFilter(QDir::Files | QDir::Hidden); dir.setNameFilters(s_loader->getNameFilter()); dir.setSorting(QDir::Name); for (auto & fi : dir.entryInfoList()) { parseAndEmitSessionYaml(fi.canonicalFilePath()); } } } // ==================================================================================================== QString viatomOutputPath(const QString & inpath, const QString & suffix) { // Output to viatom/output/DIR/FILENAME(-session.yml, etc.) QFileInfo path(inpath); QString basename = path.fileName(); QString foldername = path.dir().dirName(); QDir outdir(TESTDATA_PATH "viatom/output/" + foldername); outdir.mkpath("."); QString filename = QString("%1%2") .arg(basename) .arg(suffix); return outdir.path() + QDir::separator() + filename; } OSCAR-code-v1.3.1/oscar/tests/viatomtests.h000066400000000000000000000010771417327530600205020ustar00rootroot00000000000000/* Viatom Unit Tests * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef VIATOMTESTS_H #define VIATOMTESTS_H #include "AutoTest.h" #include "../SleepLib/loader_plugins/viatom_loader.h" class ViatomTests : public QObject { Q_OBJECT private slots: void initTestCase(); void testSessionsToYaml(); void cleanupTestCase(); }; DECLARE_TEST(ViatomTests) #endif // VIATOMTESTS_H OSCAR-code-v1.3.1/oscar/tests/zeotests.cpp000066400000000000000000000052741417327530600203360ustar00rootroot00000000000000/* ZEO Unit Tests * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "zeotests.h" #include "sessiontests.h" #define TESTDATA_PATH "./testdata/" static ZEOLoader* s_loader = nullptr; static QString zeoOutputPath(const QString & inpath, int sid, const QString & suffix); void ZeoTests::initTestCase(void) { p_profile = new Profile(TESTDATA_PATH "profile/", false); schema::init(); ZEOLoader::Register(); s_loader = dynamic_cast(lookupLoader(zeo_class_name)); } void ZeoTests::cleanupTestCase(void) { delete p_profile; p_profile = nullptr; } // ==================================================================================================== static void parseAndEmitSessionYaml(const QString & path) { qDebug() << path; if (s_loader->openCSV(path)) { int count = 0; Session* session; while ((session = s_loader->readNextSession()) != nullptr) { QString outpath = zeoOutputPath(path, session->session(), "-session.yml"); SessionToYaml(outpath, session, true); delete session; count++; } if (count == 0) { qWarning() << "no sessions found"; } s_loader->closeCSV(); } else { qWarning() << "unable to open file"; } } void ZeoTests::testSessionsToYaml() { static const QString root_path = TESTDATA_PATH "zeo/input/"; QDir root(root_path); root.setFilter(QDir::NoDotAndDotDot | QDir::Dirs); root.setSorting(QDir::Name); for (auto & dir_info : root.entryInfoList()) { QDir dir(dir_info.canonicalFilePath()); dir.setFilter(QDir::Files | QDir::Hidden); dir.setNameFilters(QStringList("*.csv")); dir.setSorting(QDir::Name); for (auto & fi : dir.entryInfoList()) { parseAndEmitSessionYaml(fi.canonicalFilePath()); } } } // ==================================================================================================== QString zeoOutputPath(const QString & inpath, int sid, const QString & suffix) { // Output to zeo/output/DIR/FILENAME(-session.yml, etc.) QFileInfo path(inpath); QString basename = path.baseName(); QString foldername = path.dir().dirName(); QDir outdir(TESTDATA_PATH "zeo/output/" + foldername); outdir.mkpath("."); QString filename = QString("%1-%2%3") .arg(basename) .arg(sid, 8, 10, QChar('0')) .arg(suffix); return outdir.path() + QDir::separator() + filename; } OSCAR-code-v1.3.1/oscar/tests/zeotests.h000066400000000000000000000010521417327530600177710ustar00rootroot00000000000000/* ZEO Unit Tests * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef ZEOTESTS_H #define ZEOTESTS_H #include "AutoTest.h" #include "../SleepLib/loader_plugins/zeo_loader.h" class ZeoTests : public QObject { Q_OBJECT private slots: void initTestCase(); void testSessionsToYaml(); void cleanupTestCase(); }; DECLARE_TEST(ZeoTests) #endif // ZEOTESTS_H OSCAR-code-v1.3.1/oscar/translation.cpp000066400000000000000000000211761417327530600176510ustar00rootroot00000000000000/* Multilingual Support files * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SleepLib/common.h" #include "translation.h" QHash langNames; QString currentLanguage() { QSettings settings; return settings.value(LangSetting).toString(); } QString lookupLanguageName(QString language) { auto it = langNames.find(language); if (it != langNames.end()) { return it.value(); } return language; } void initTranslations() { // Add any languages with need for a special character set to this list langNames["ar"] = "\xd8\xb9\xd8\xb1\xd8\xa8\xd9\x8a"; langNames["bg"] = "\xd0\xb1\xd1\x8a\xd0\xbb\xd0\xb3\xd0\xb0\xd1\x80\xd1\x81\xd0\xba\xd0\xb8"; langNames["el"] = "\xce\x95\xce\xbb\xce\xbb\xce\xb7\xce\xbd\xce\xb9\xce\xba\xce\xac"; langNames["en_UK"] = "English (UK)"; langNames["es"] = "Español"; langNames["es_MX"] = "Español (Mexico)"; langNames["fi"] = "Suomen kieli"; langNames["fr"] = "Français"; langNames["he"] = "\xd7\xa2\xd7\x91\xd7\xa8\xd7\x99\xd7\xaa"; langNames["hu"] = "Magyar nyelv"; langNames["ko"] = "\xed\x95\x9c\xea\xb5\xad\xec\x96\xb4"; langNames["nl"] = "Nederlands"; langNames["pt"] = "Português"; langNames["pt_BR"] = "Português (Brazil)"; langNames["ro"] = "Românește"; langNames["tr"] = "Türkçe"; langNames["ru"] = "\xd1\x80\xd1\x83\xd1\x81\xd1\x81\xd0\xba\xd0\xb8\xd0\xb9"; langNames["th"] = "\xe0\xb8\xa0\xe0\xb8\xb2\xe0\xb8\xa9\xe0\xb8\xb2\xe0\xb9\x84\xe0\xb8\x97\xe0\xb8\xa2"; langNames["zh_CN"] = "\xe5\x8d\x8e\xe8\xaf\xad\xe7\xae\x80\xe4\xbd\x93\xe5\xad\x97 \x2d \xe4\xb8\xad\xe5\x9b\xbd"; langNames["zh_TW"] = "\xe8\x8f\xaf\xe8\xaa\x9e\xe6\xad\xa3\xe9\xab\x94\xe5\xad\x97 \x2d \xe8\x87\xba\xe7\x81\xa3"; langNames[DefaultLanguage]="English (US)"; QHash langFiles; QHash langPaths; langFiles[DefaultLanguage] = "English (US).en_US.qm"; QSettings settings; QString language = settings.value(LangSetting).toString(); QString inbuiltPath = ":/translations"; QStringList inbuilt(DefaultLanguage); QDir dir(inbuiltPath); dir.setFilter(QDir::Files); dir.setNameFilters(QStringList("*.qm")); qDebug() << "number of built-in *.qm files" << dir.count(); QFileInfoList list = dir.entryInfoList(); for (const auto & fi : list) { if (fi.fileName().indexOf("oscar_qt", 0) == 0) // skip files named QT... These are supplemental files for QT strings continue; QString code = fi.fileName().section('.', 1, 1); if (!langNames.contains(code)) langNames[code]=fi.fileName().section('.', 0, 0); inbuilt.push_back(code); langFiles[code] = fi.fileName(); langPaths[code] = inbuiltPath; } std::sort(inbuilt.begin(), inbuilt.end()); qDebug() << "Inbuilt Translations:" << QString(inbuilt.join(", ")).toLocal8Bit().data(); QString externalPath = appResourcePath() +"/Translations"; dir.setPath(externalPath); dir.setFilter(QDir::Files); dir.setNameFilters(QStringList("*.qm")); list = dir.entryInfoList(); qDebug() << "number of external *.qm files" << dir.count(); // Scan through available translations, and add them to the list QStringList extratrans, replaced; int numExternal = 0; for (const auto & fi : list) { if (fi.fileName().indexOf("oscar_qt", 0) == 0) // skip files named QT... These are supplemental files for QT strings continue; QString code = fi.fileName().section('.', 1, 1); numExternal++; if(!langNames.contains(code)) langNames[code] = fi.fileName().section('.', 0, 0); if (inbuilt.contains(code)) replaced.push_back(code); else extratrans.push_back(code); langFiles[code] = fi.fileName(); langPaths[code] = externalPath; } std::sort(replaced.begin(), replaced.end()); std::sort(extratrans.begin(), extratrans.end()); qDebug() << "Number of external translations is" << numExternal; if (replaced.size()>0) qDebug() << "Overridden Tranlsations:" << QString(replaced.join(", ")).toLocal8Bit().data(); if (extratrans.size()>0) qDebug() << "Extra Translations:" << QString(extratrans.join(", ")).toLocal8Bit().data(); if (language.isEmpty() || !langNames.contains(language)) { QDialog langsel(nullptr, Qt::CustomizeWindowHint | Qt::WindowTitleHint); QFont font; font.setPointSize(20); langsel.setFont(font); langsel.setWindowTitle("Language / Taal / Sprache / Langue / \xe8\xaf\xad\xe8\xa8\x80 / ... "); QHBoxLayout lang_layout(&langsel); QLabel img; img.setPixmap(QPixmap(":/icons/logo-lg.png")); QPushButton lang_okbtn("->", &langsel); // hard coded non translatable QVBoxLayout layout1; QVBoxLayout layout2; layout2.setMargin(6); lang_layout.setContentsMargins(4,4,4,4); lang_layout.setMargin(6); layout2.setSpacing(6); QListWidget langlist; lang_layout.addLayout(&layout1); lang_layout.addLayout(&layout2); layout1.addWidget(&img); layout2.addWidget(&langlist, 1); langlist.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); int row = 0; for (QHash::iterator it = langNames.begin(); it != langNames.end(); ++it) { const QString & code = it.key(); const QString & name = it.value(); if (!langFiles.contains(code) || langFiles[code].isEmpty()) continue; QListWidgetItem *item = new QListWidgetItem(name); item->setData(Qt::UserRole, code); langlist.insertItem(row++, item); // Todo: Use base system language code if available. if (code.compare(DefaultLanguage) == 0) { langlist.setCurrentItem(item); } } langlist.sortItems(); layout2.addWidget(&lang_okbtn); langsel.connect(&langlist, SIGNAL(itemDoubleClicked(QListWidgetItem*)), &langsel, SLOT(close())); langsel.connect(&lang_okbtn, SIGNAL(clicked()), &langsel, SLOT(close())); langsel.raise(); langsel.exec(); langsel.disconnect(&lang_okbtn, SIGNAL(clicked()), &langsel, SLOT(close())); langsel.disconnect(&langlist, SIGNAL(itemDoubleClicked(QListWidgetItem*)), &langsel, SLOT(close())); language = langlist.currentItem()->data(Qt::UserRole).toString(); settings.setValue(LangSetting, language); } QString langname=langNames[language]; QString langfile=langFiles[language]; QString langpath=langPaths[language]; if (language.compare(DefaultLanguage) != 0) { // Install QT translation files QString qtLang = language.left(2); if ( qtLang.compare("zh") == 0 ) // QT-supplied translation files have both _CN and _TW, but are the same for our purposes qtLang.append("_CN"); QString qtLangFile = "qt_" + qtLang + ".qm"; if (!QFileInfo(qtLangFile).exists()) { qtLang = qtLang.left(2); // Undo QT suffix for zh; we don't use that for our file qtLangFile = "oscar_qt_" + qtLang + ".qm"; } qDebug() << "Loading" << langname << "QT translation" << qtLangFile.toLocal8Bit().data() << "from" << langpath.toLocal8Bit().data(); QTranslator * qtranslator = new QTranslator(); if (!qtLangFile.isEmpty() && !qtranslator->load(qtLangFile, langpath)) { qWarning() << "Could not load QT translation" << qtLangFile << "reverting to english :("; } qApp->installTranslator(qtranslator); // Install OSCAR translation files qDebug() << "Loading" << langname << "OSCAR translation" << langfile.toLocal8Bit().data() << "from" << langpath.toLocal8Bit().data(); QTranslator * translator = new QTranslator(); if (!langfile.isEmpty() && !translator->load(langfile, langpath)) { qWarning() << "Could not load OSCAR translation" << langfile << "reverting to english :("; } qApp->installTranslator(translator); } else { qDebug() << "Using default language" << language.toLocal8Bit().data(); } } OSCAR-code-v1.3.1/oscar/translation.h000066400000000000000000000011251417327530600173060ustar00rootroot00000000000000/* Multilingual Support header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef TRANSLATION_H #define TRANSLATION_H #include const QString DefaultLanguage = "en_US"; const QString LangSetting = "Settings/Language"; void initTranslations(); QString currentLanguage(); QString lookupLanguageName(QString language); #endif // TRANSLATION_H OSCAR-code-v1.3.1/oscar/update_gitinfo.bat000077500000000000000000000026051417327530600202770ustar00rootroot00000000000000@echo off setlocal EnableDelayedExpansion set DIR=%~dp0 :::set DIR=\oscar\oscar-code\oscar\ cd %DIR% :: Check git in path and git directory can be found where /Q git.exe if errorlevel 1 goto GitFail git rev-parse --git-dir >nul 2>&1 if errorlevel 1 goto GitFail for /f %%i in ('git rev-parse --abbrev-ref HEAD') do set GIT_BRANCH=%%i if "%GIT_BRANCH%"=="HEAD" set GIT_BRANCH= for /f %%i in ('git rev-parse --short HEAD') do set GIT_REVISION=%%i git diff-index --quiet HEAD -- if %errorlevel%==0 goto GitTag set GIT_REVISION=%GIT_REVISION%-plus set GIT_TAG= goto GitDone :GitTag for /f %%i in ('git describe --exact-match --tags') do set GIT_TAG=%%i goto GitDone :GitFail :GitDone @echo Update_gtinfo.bat: GIT_BRANCH=%GIT_BRANCH%, GIT_REVISION=%GIT_REVISION%, GIT_TAG=%GIT_TAG% echo // This is an auto generated file > %DIR%git_info.new if "%GIT_BRANCH%"=="" goto DoRevision echo #define GIT_BRANCH "%GIT_BRANCH%" >> %DIR%git_info.new :DoRevision if "%GIT_REVISION%"=="" goto DoTag echo #define GIT_REVISION "%GIT_REVISION%" >> %DIR%git_info.new :DoTag if "%GIT_TAG%"=="" goto DefinesDone echo #define GIT_TAG "%GIT_TAG%" >> %DIR%git_info.new :DefinesDone fc %DIR%git_info.h %DIR%git_info.new 1>nul 2>nul && del /q %DIR%git_info.new || goto NewFile goto AllDone :NewFile echo Updating %DIR%git_info.h move /y %DIR%git_info.new %DIR%git_info.h :AllDone endlocal OSCAR-code-v1.3.1/oscar/update_gitinfo.sh000077500000000000000000000020371417327530600201420ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd $DIR git rev-parse --git-dir &>/dev/null if [ $? -eq 0 ]; then GIT_BRANCH=`git rev-parse --abbrev-ref HEAD` [ "$GIT_BRANCH" == "HEAD" ] && GIT_BRANCH="" # not really a branch GIT_REVISION=`git rev-parse --short HEAD` $(git diff-index --quiet HEAD --) if [ $? -ne 0 ]; then GIT_REVISION="${GIT_REVISION}-plus" # uncommitted changes else # only use the tag if clean GIT_TAG=`git describe --exact-match --tags 2>/dev/null` fi fi echo // This is an auto generated file > $DIR/git_info.h.new [ -n "$GIT_BRANCH" ] && echo "#define GIT_BRANCH \"$GIT_BRANCH\"" >> $DIR/git_info.h.new [ -n "$GIT_REVISION" ] && echo "#define GIT_REVISION \"$GIT_REVISION\"" >> $DIR/git_info.h.new [ -n "$GIT_TAG" ] && echo "#define GIT_TAG \"$GIT_TAG\"" >> $DIR/git_info.h.new if diff $DIR/git_info.h $DIR/git_info.h.new &> /dev/null; then rm $DIR/git_info.h.new else echo Updating $DIR/git_info.h mv $DIR/git_info.h.new $DIR/git_info.h fi OSCAR-code-v1.3.1/oscar/updateparser.cpp000066400000000000000000000262001417327530600200030ustar00rootroot00000000000000/* UpdateParser Implementation (Autoupdater component) * * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include "updateparser.h" Update::Update() { size = 0; } Update::Update(const Update ©) { type = copy.type; version = copy.version; platform = copy.platform; date = copy.date; filename = copy.filename; url = copy.url; hash = copy.hash; size = copy.size; notes = copy.notes; unzipped_path = copy.unzipped_path; } Update::Update(QString _type, QString _version, QString _platform, QDate _date) { type = _type; version = _version; platform = _platform; date = _date; size = 0; } bool UpdateParser::startDocument() { inRelease = false; inUpdate = false; inNotes = false; inUpdateNotes = false; release = nullptr; update = nullptr; return true; } bool UpdateParser::endElement(const QString &namespaceURI, const QString &localName, const QString &qName) { Q_UNUSED(namespaceURI) Q_UNUSED(localName) QString name = qName.toLower(); if (name == "release") { inRelease = false; release = nullptr; } else if (inRelease && name == "update") { inUpdate = false; update = nullptr; } else if (inUpdate && name == "notes") { inUpdateNotes = false; } else if (inRelease && name == "notes") { inNotes = false; } return true; } bool UpdateParser::characters(const QString &ch) { if (inUpdateNotes) { update->notes = ch; } else if (inNotes) { release->notes[platform] = ch; } return true; } UpdateStatus lookupUpdateStatus(QString stat) { UpdateStatus status = UPDATE_TESTING; if (stat == "testing") { status = UPDATE_TESTING; } else if (stat == "beta") { status = UPDATE_BETA; } else if (stat == "stable") { status = UPDATE_STABLE; } else if (stat == "critical") { status = UPDATE_CRITICAL; } return status; } bool UpdateParser::startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts) { Q_UNUSED(namespaceURI) Q_UNUSED(localName) QString name = qName.toLower(); if (inRelease && name == "update") { QString ver, type; UpdateStatus updatestatus = UPDATE_TESTING; for (int i = 0; i < atts.count(); i++) { if (atts.localName(i) == "type") { type = atts.value(i).toLower(); } if (atts.localName(i) == "version") { ver = atts.value(i).toLower(); } if (atts.localName(i) == "platform") { platform = atts.value(i).toLower(); } if (atts.localName(i) == "release_date") { release_date = atts.value(i); } if (atts.localName(i) == "status") { updatestatus = lookupUpdateStatus(atts.value(i).toLower()); } } QDate date = QDate::fromString(release_date, "yyyy-MM-dd"); if (!date.isValid()) { date = QDate::currentDate(); } release->updates[platform].push_back(Update(type, ver, platform, date)); update = &release->updates[platform][release->updates[platform].size() - 1]; update->status = updatestatus; inUpdate = true; } else if (inRelease && name == "info") { QString tmp = atts.value("url"); if (tmp.isEmpty()) { return false; } release->info_url = tmp; } else if (inUpdate && name == "file") { for (int i = 0; i < atts.count(); i++) { if (atts.localName(i) == "name") { update->filename = atts.value(i); } if (atts.localName(i) == "size") { bool ok; update->size = atts.value(i).toLongLong(&ok); //if (!ok) return false; } if (atts.localName(i) == "url") { update->url = atts.value(i); } if (atts.localName(i) == "hash") { update->hash = atts.value(i).toLower(); } } } else if (inUpdate && name == "notes") { inUpdateNotes = true; } else if (inRelease && name == "notes") { platform = ""; if (atts.count() >= 1) { if (atts.localName(0) == "platform") { platform = atts.value(0); } } inNotes = true; } else if (name == "release") { inRelease = true; QString codename; UpdateStatus status = UPDATE_TESTING; for (int i = 0; i < atts.count(); i++) { if (atts.localName(i) == "version") { version = atts.value(i).toLower(); } if (atts.localName(i) == "codename") { codename = atts.value(i); } if (atts.localName(i) == "status") { status = lookupUpdateStatus(atts.value(i).toLower()); } } releases[version] = Release(version, codename, status); release = &releases[version]; if (version > latest_version) { latest_version = version; } } return true; } bool UpdateParser::endDocument() { /*for (QHash::iterator r=releases.begin();r!=releases.end();r++) { Release & rel=r.value(); qDebug() << "New Version" << r.key() << rel.codename << rel.notes; for (QHash::iterator u=rel.files.begin();u!=rel.files.end();u++) { Update & up=u.value(); qDebug() << "Platform:" << u.key() << up.filename << up.date; } }*/ return true; } ///////////////////////////////////////////////////////////////////// // Updates Parser implementation ///////////////////////////////////////////////////////////////////// UpdatesParser::UpdatesParser() { } QString UpdatesParser::errorString() const { return QObject::tr("%1\nLine %2, column %3") .arg(xml.errorString()) .arg(xml.lineNumber()) .arg(xml.columnNumber()); } bool UpdatesParser::read(QIODevice *device) { xml.setDevice(device); if (xml.readNextStartElement()) { if (xml.name() == "Updates") { // && xml.attributes().value("version") == "1.0") readUpdates(); } else { xml.raiseError(QObject::tr("Could not parse Updates.xml file.")); } } return !xml.error(); } void UpdatesParser::readUpdates() { if (!xml.isStartElement() || (xml.name() != "Updates")) { qWarning() << "UpdatesParser::readUpdates() condition check failed"; } // Q_ ASSERT(xml.isStartElement() && xml.name() == "Updates"); while (xml.readNextStartElement()) { if (xml.name().compare(QLatin1String("PackageUpdate"),Qt::CaseInsensitive)==0) { readPackageUpdate(); } else { qDebug() << "Skipping Updates.xml tag" << xml.name(); xml.skipCurrentElement(); } } } void UpdatesParser::readPackageUpdate() { if (!xml.isStartElement() || (xml.name().compare(QLatin1String("PackageUpdate"),Qt::CaseInsensitive)!=0)) { qWarning() << "UpdatesParser::readPackageUpdate() condition check failed"; return; } package = PackageUpdate(); while (xml.readNextStartElement()) { if (xml.name().compare(QLatin1String("Name"),Qt::CaseInsensitive)==0) { package.name = xml.readElementText().toLower(); } else if (xml.name().compare(QLatin1String("DisplayName"),Qt::CaseInsensitive)==0) { package.displayName = xml.readElementText(); } else if (xml.name().compare(QLatin1String("Description"),Qt::CaseInsensitive)==0) { package.description = xml.readElementText(); } else if (xml.name().compare(QLatin1String("Version"),Qt::CaseInsensitive)==0) { package.versionString = xml.readElementText(); } else if (xml.name().compare(QLatin1String("ReleaseDate"),Qt::CaseInsensitive)==0) { package.releaseDate = QDate().fromString(xml.readElementText(), "yyyy-MM-dd"); } else if (xml.name().compare(QLatin1String("Default"),Qt::CaseInsensitive)==0) { package.defaultInstall = xml.readElementText().compare("true") == 0; } else if (xml.name().compare(QLatin1String("ForcedInstallation"),Qt::CaseInsensitive)==0) { package.forcedInstall = xml.readElementText().compare("true") == 0; } else if (xml.name().compare(QLatin1String("Script"),Qt::CaseInsensitive)==0) { package.script = xml.readElementText(); } else if (xml.name().compare(QLatin1String("Dependencies"),Qt::CaseInsensitive)==0) { package.dependencies = xml.readElementText().split(","); } else if (xml.name().compare(QLatin1String("UpdateFile"),Qt::CaseInsensitive)==0) { for (int i=0; i * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef UPDATEPARSER_H #define UPDATEPARSER_H #include #include #include #include enum UpdateStatus { UPDATE_TESTING = 0, UPDATE_BETA, UPDATE_STABLE, UPDATE_CRITICAL }; /*! \struct Update \brief Holds platform specific information about an individual updates */ class Update { public: Update(); Update(const Update ©); Update(QString _type, QString _version, QString _platform, QDate _date); QString type; QString version; QString platform; UpdateStatus status; QDate date; QString filename; QString url; QString hash; qint64 size; QString notes; QString unzipped_path; }; /*! \struct Release \brief Holds information about an individual major release */ struct Release { Release() {} Release(const Release & /*copy*/) = default; Release(QString ver, QString code, UpdateStatus stat) { version = ver; codename = code; status = stat; } QString version; QString codename; UpdateStatus status; QString info_url; QHash notes; // by platform QHash > updates; }; Q_DECLARE_METATYPE(Update *) /*! \class UpdateParser \brief SAX XML parser for update.xml */ class UpdateParser: public QXmlDefaultHandler { public: bool startDocument(); bool endElement(const QString &namespaceURI, const QString &localName, const QString &name); bool characters(const QString &ch); bool startElement(const QString &namespaceURI, const QString &localName, const QString &name, const QXmlAttributes &atts); bool endDocument(); QString latest() { return latest_version; } QHash releases; private: Update *update; Release *release; QString version, platform; QString release_date; QString latest_version; bool inRelease; bool inUpdate; bool inNotes; bool inUpdateNotes; }; class PackageUpdate { public: PackageUpdate() {} PackageUpdate(const PackageUpdate & /*copy*/) = default; QString name; QString displayName; QString description; QString versionString; QDate releaseDate; bool defaultInstall; QString installScript; QStringList dependencies; QString script; bool forcedInstall; QStringList downloadArchives; QHash license; QString sha1; unsigned int compressedSize; unsigned int uncompressedSize; QString os; }; /*! \class UpdatesParser \brief New SAX XML parser for QT Installer Frameworks Updates.xml */ class UpdatesParser { public: UpdatesParser(); bool read(QIODevice *device); QString errorString() const; QHash packages; private: void readUpdates(); void readPackageUpdate(); QXmlStreamReader xml; PackageUpdate package; QString currentTag; }; #endif // UPDATEPARSER_H OSCAR-code-v1.3.1/oscar/version.cpp000066400000000000000000000164711417327530600170020ustar00rootroot00000000000000/* Version management * * Copyright (c) 2019-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "version.h" #include "VERSION" #include "git_info.h" #include // Initialize the Version instance with build metadata, if any. #ifdef GIT_REVISION #ifdef GIT_BRANCH #define BUILD_METADATA "+" GIT_BRANCH "-" GIT_REVISION #else #define BUILD_METADATA "+" GIT_REVISION #endif #else #define BUILD_METADATA "" #endif static const Version s_Version(VERSION BUILD_METADATA); // Technically this is the date and time that version.cpp was compiled, but since // it gets recompiled whenever git_info.h changes, it's about as close as we can // come without forcing recompiles every single build. static const QString s_BuildDateTime = __DATE__ " " __TIME__; QString getPrereleaseSuffix() { QString suffix; if (getVersion().IsReleaseVersion() || getVersion().PrereleaseType().compare("rc") == 0) { // No suffix for release or rc versions. suffix = ""; } else { #ifdef GIT_TAG // If this commit has a tag, then it's a full testing (alpha/beta/etc.) release. // Put preferences/data in "-test". suffix = "-test"; #else // Otherwise it's a development build, which will be identified by its branch in most cases. #ifdef GIT_BRANCH suffix = "-" GIT_BRANCH; #else #ifdef GIT_REVISION // If we've checked out an older version, we're in a headless state and not on any branch. // If the older version was a previous testing release, it should be tagged, in which case // it's treated as a testing release above. // // Otherwise this is probably being used for regression testing an older build. suffix = "-" GIT_REVISION; #else // In theory someone might try to build a prerelease from a tarball, so we don't have any // revision information. Just put it in an "-unreleased" sandbox. suffix = "-unreleased"; #endif #endif #endif } return suffix; } const QString & getBuildDateTime() { return s_BuildDateTime; } const Version & getVersion() { return s_Version; } // Alternate formatting of the version string for display or logging const QString Version::minimalString() const { return toString().section("+", 0, 0); } const QString Version::displayString() const { if (IsReleaseVersion()) return minimalString(); else return toString(); } // This is application-specific interpretation of the prerelease data. const QString Version::PrereleaseType() const { // Extract the first identifier QString type = mPrerelease.section(".", 0, 0); // Remove any "-2", etc. that's included in the first identifier rather than as a dot-separated identifier type = type.section("-", 0, 0); return type.toLower(); } // Deal with non-Semantic-Versioning numbers used before 1.1.0-beta-2 to make sure they // will have proper (lower) precedence compared to later versions. // // TODO: THIS CAN PROBABLY BE REMOVED AFTER THE RELEASE OF 1.1.0, since the release // version will take precedence over all 1.1.0 prereleases, as well as 1.0.1 of any // release status. // // Right now we just need to make sure that 1.1.0-beta versions take precedence over // 1.1.0-testing. void Version::FixLegacyVersions() { if (mIsValid) { // Replace prerelease "testing" with "alpha" for backwards compatibility with 1.1.0-testing-* // versions: otherwise "testing" would take precedence over "beta". mPrerelease.replace("testing", "alpha"); // Technically the use of "r1" in "1.0.1-r1" could also be corrected, as the code // will incorrectly consider that release version to be a prerelease, but it doesn't // matter because 1.1.0 and later will take precedence either way. } } // =================================================================================================== // Version class for parsing and comparing version strings as specified by Semantic Versioning 2.0.0 // See https://semver.org/spec/v2.0.0.html Version::Version(const QString & version_string) : mString(version_string), mIsValid(false) { ParseSemanticVersion(); FixLegacyVersions(); } Version::operator const QString &() const { return toString(); } const QString & Version::toString() const { return mString; } // Parse a version string as specified by Semantic Versioning 2.0.0. void Version::ParseSemanticVersion() { // Use a C++11 raw string literal to keep the regular expression (mostly) legible. static const QRegularExpression re(R"(^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$)"); QRegularExpressionMatch match = re.match(mString); while (match.hasMatch()) { bool ok; mMajor = match.captured("major").toInt(&ok); if (!ok) break; mMinor = match.captured("minor").toInt(&ok); if (!ok) break; mPatch = match.captured("patch").toInt(&ok); if (!ok) break; mPrerelease = match.captured("prerelease"); mBuild = match.captured("buildmetadata"); mIsValid = true; break; } // If we ever encounter any really old version whose version isn't valid, its // major version will be 0, so it will correctly be considered older than // valid versions. } // Compare two version instances in accordance with Semantic Versionin 2.0.0 precedence rules. int Version::Compare(const Version & a, const Version & b) { int diff; diff = a.mMajor - b.mMajor; if (diff) return diff; diff = a.mMinor - b.mMinor; if (diff) return diff; diff = a.mPatch - b.mPatch; if (diff) return diff; // Version numbers are equal, now check prerelease status: if (a.IsReleaseVersion() && b.IsReleaseVersion()) return 0; // A pre-release version has lower prededence than a release version. diff = a.IsReleaseVersion() - b.IsReleaseVersion(); if (diff) return diff; // Both are prerelease versions, compare them: // The prerelease version may contain a series of dot-separated identifiers, // each of which is compared. QStringList ap = a.mPrerelease.split("."); QStringList bp = b.mPrerelease.split("."); int max = qMin(ap.size(), bp.size()); for (int i = 0; i < max; i++) { bool a_is_num, b_is_num; int ai = ap[i].toInt(&a_is_num); int bi = bp[i].toInt(&b_is_num); // Numeric identifiers always have lower precedence than non-numeric. diff = b_is_num - a_is_num; if (diff) return diff; if (a_is_num) { // Numeric identifiers are compared numerically. diff = ai - bi; if (diff) return diff; } else { // Non-numeric identifiers are compared lexically. diff = ap[i].compare(bp[i]); if (diff) return diff; } } // A larger set of pre-release fields has higher precedence (if the above were equal). diff = ap.size() - bp.size(); // We ignore build metadata in comparing semantic versions. return diff; } OSCAR-code-v1.3.1/oscar/version.h000066400000000000000000000036071417327530600164440ustar00rootroot00000000000000/* Version.h * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #ifndef VERSION_H #define VERSION_H #include class Version { friend class VersionTests; public: Version(const QString & version_string); const QString PrereleaseType() const; bool IsReleaseVersion() const { return mPrerelease.isEmpty(); } bool IsValid() const { return mIsValid; } bool operator==(const Version & b) const { return Compare(*this, b) == 0; } bool operator!=(const Version & b) const { return Compare(*this, b) != 0; } bool operator<(const Version & b) const { return Compare(*this, b) < 0; } bool operator>(const Version & b) const { return Compare(*this, b) > 0; } //!brief Returns the full version string, including all metadata, used in reports operator const QString &() const; //!brief Returns the full version string, including all metadata, used in reports const QString & toString() const; //!brief Returns the version string to display in the UI, without build metadata if a release version const QString displayString() const; //!brief Returns the version string without any build metadata const QString minimalString() const; protected: const QString mString; bool mIsValid; int mMajor, mMinor, mPatch; QString mPrerelease, mBuild; void ParseSemanticVersion(); void FixLegacyVersions(); static int Compare(const Version & a, const Version & b); }; //!brief Get the current version of the application. const Version & getVersion(); //!brief Get the date and time of the application was built. const QString & getBuildDateTime(); QString getPrereleaseSuffix(); #endif // VERSION_H OSCAR-code-v1.3.1/oscar/welcome.cpp000066400000000000000000000333511417327530600167440ustar00rootroot00000000000000/* Welcome page Implementation * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of Source Code. */ #include #include "welcome.h" #include "ui_welcome.h" #include "mainwindow.h" extern MainWindow * mainwin; Welcome::Welcome(QWidget *parent) : QWidget(parent), ui(new Ui::Welcome) { ui->setupUi(this); pixmap.load(":/icons/mask.png"); refreshPage(); } Welcome::~Welcome() { delete ui; } void Welcome::refreshPage() { bool b; const auto & mlist = p_profile->GetMachines(MT_CPAP); b = mlist.size() > 0; QList oximachines = p_profile->GetMachines(MT_OXIMETER); QList posmachines = p_profile->GetMachines(MT_POSITION); QList stgmachines = p_profile->GetMachines(MT_SLEEPSTAGE); bool noMachines = mlist.isEmpty() && posmachines.isEmpty() && oximachines.isEmpty() && stgmachines.isEmpty(); bool showCardWarning = noMachines; // The SDCard warning does not need to be seen anymore for people who DON'T use ResMed S9's.. show first import and only when S9 is present for (auto & mach :mlist) { if (mach->brand().contains(STR_MACH_ResMed) && mach->series().contains("S9")) showCardWarning = true; } ui->S9Warning->setVisible(showCardWarning); if (!b) { qDebug() << "No machines in Profile"; // sleep(3); ui->cpapIcon->setPixmap(pixmap); } b = !noMachines; // Copy application font to tool buttons ui->importButton->setFont(QApplication::font()); ui->dailyButton->setFont(QApplication::font()); ui->overviewButton->setFont(QApplication::font()); ui->statisticsButton->setFont(QApplication::font()); ui->oximetryButton->setFont(QApplication::font()); // Enable buttons that might be disabled ui->dailyButton->setEnabled(b); ui->oximetryButton->setEnabled(true); // Import features always enabled ui->overviewButton->setEnabled(b); ui->statisticsButton->setEnabled(b); ui->importButton->repaint(); ui->dailyButton->repaint(); ui->overviewButton->repaint(); ui->statisticsButton->repaint(); ui->oximetryButton->repaint(); mainwin->EnableTabs(b); ui->cpapInfo->setHtml(GenerateCPAPHTML()); ui->oxiInfo->setHtml(GenerateOxiHTML()); } void Welcome::on_dailyButton_clicked() { mainwin->JumpDaily(); } void Welcome::on_overviewButton_clicked() { mainwin->JumpOverview(); } void Welcome::on_statisticsButton_clicked() { mainwin->JumpStatistics(); } void Welcome::on_oximetryButton_clicked() { mainwin->JumpOxiWizard(); } void Welcome::on_importButton_clicked() { mainwin->JumpImport(); } extern EventDataType calcAHI(QDate start, QDate end); extern EventDataType calcFL(QDate start, QDate end); QString Welcome::GenerateCPAPHTML() { auto cpap_machines = p_profile->GetMachines(MT_CPAP); auto oximeters = p_profile->GetMachines(MT_OXIMETER); QList mach; mach.append(cpap_machines); mach.append(oximeters); bool havecpapdata = false; bool haveoximeterdata = false; for (auto & mach : cpap_machines) { int daysize = mach->day.size(); if (daysize > 0) { havecpapdata = true; break; } } for (auto & mach : oximeters) { int daysize = mach->day.size(); if (daysize > 0) { haveoximeterdata = true; break; } } QString html = QString("")+ // "" "" "" ""; Machine * cpap = nullptr; if (!havecpapdata && !haveoximeterdata) { html += "

    " + tr("It would be a good idea to check File->Preferences first,") + "
    " + tr("as there are some options that affect import.")+"

    " + "

    " + tr("Note that some preferences are forced when a ResMed machine is detected") + "

    " + "

    " + tr("First import can take a few minutes.") + "

    "; } else { QDate date = p_profile->LastDay(MT_CPAP); Day *day = p_profile->GetDay(date, MT_CPAP); if (havecpapdata && day) { cpap = day->machine(MT_CPAP); } if (day && (cpap != nullptr)) { QString cpapimage = cpap->getPixmapPath(); ui->cpapIcon->setPixmap(QPixmap(cpapimage)); html+= ""+tr("The last time you used your %1...").arg(cpap->brand()+" "+cpap->model())+"
    "; int daysto = date.daysTo(QDate::currentDate()); QString daystring; if (daysto == 1) daystring += tr("last night"); else if (daysto == 2) daystring += tr("1 day ago"); else daystring += tr("%2 days ago").arg(daysto-1); html += tr("was %1 (on %2)").arg(daystring).arg(date.toString(Qt::SystemLocaleLongDate)) + "
    "; EventDataType hours = day->hours(MT_CPAP); html += "
    "; int seconds = int(hours * 3600.0) % 60; int minutes = int(hours * 60) % 60; int hour = hours; QString timestr = tr("%1 hours, %2 minutes and %3 seconds").arg(hour).arg(minutes).arg(seconds); const EventDataType compliance_min = p_profile->cpap->m_complianceHours; // 4.0; if (hours > compliance_min) html += tr("Your machine was on for %1.").arg(timestr)+"
    "; else html += tr("You only had the mask on for %1.").arg(timestr)+"
    "; int averagedays = 7; // how many days to look back QDate starttime = date.addDays(-averagedays); QDate endtime = date.addDays(-1); // EventDataType ahi = (day->count(CPAP_AllApnea) + day->count(CPAP_Obstructive) + day->count(CPAP_Hypopnea) + day->count(CPAP_ClearAirway) + day->count(CPAP_Apnea)) / hours; EventDataType ahi = day->count(AllAhiChannels) / hours; EventDataType ahidays = calcAHI(starttime, endtime); const QString under = tr("under"); const QString over = tr("over"); const QString close = tr("reasonably close to"); const QString equal = tr("equal to"); QString comp; if ((ahi < ahidays) && ((ahidays - ahi) >= 0.1)) { comp = under; } else if ((ahi > ahidays) && ((ahi - ahidays) >= 0.1)) { comp = over; } else if ((fabs(ahi - ahidays) >= 0.01) ) { comp = close; } else { comp = equal; } html += tr("You had an AHI of %1, which is %2 your %3 day average of %4.").arg(ahi,0,'f',2).arg(comp).arg(averagedays).arg(ahidays,0,'f',2); html += "
    "; CPAPMode cpapmode = (CPAPMode)(int)day->settings_max(CPAP_Mode); ChannelID pressChanID = day->getPressureChannelID(); // Get channel id for pressure that we should report double perc= p_profile->general->prefCalcPercentile(); // When CPAP_PressureSet and CPAP_IPAPSet have data (used for percentiles, etc.) // CPAP_Pressure and CPAP_IPAP are their corresponding settings channels. ChannelID pressSettingChanID; if (pressChanID == CPAP_PressureSet) { pressSettingChanID = CPAP_Pressure; } else if (pressChanID == CPAP_IPAPSet) { pressSettingChanID = CPAP_IPAP; } else { pressSettingChanID = pressChanID; } ChannelID epapDataChanID = CPAP_EPAP; if (day->channelHasData(CPAP_EPAPSet)) { epapDataChanID = CPAP_EPAPSet; } if (pressChanID == NoChannel) { qWarning() << "Unable to find pressure channel for welcome summary!"; } if (cpapmode == MODE_CPAP) { pressSettingChanID = CPAP_Pressure; // DreamStation ventilators report EPAP/IPAP data, but the setting is Pressure EventDataType pressure = day->settings_max(pressSettingChanID); qDebug() << pressSettingChanID << pressure; html += tr("Your CPAP machine used a constant %1 %2 of air") .arg(pressure) .arg(schema::channel[pressChanID].units()); } else if (cpapmode == MODE_APAP) { EventDataType pressure = day->percentile(pressChanID, perc/100.0); html += tr("Your pressure was under %1 %2 for %3% of the time.") .arg(pressure) .arg(schema::channel[pressChanID].units()) .arg(perc); } else if (cpapmode == MODE_BILEVEL_FIXED) { // pressSettingChanID = CPAP_IPAP; // EventDataType ipap = day->settings_max(pressSettingChanID); // EventDataType epap = day->settings_min(CPAP_EPAP); html += tr("Your machine used a constant %1-%2 %3 of air.") .arg(day->validPressure(day->settings_min(CPAP_EPAP))) .arg(day->validPressure(day->settings_max(CPAP_IPAP))) .arg(schema::channel[CPAP_IPAP].units()); } else if (cpapmode == MODE_BILEVEL_AUTO_FIXED_PS) { EventDataType ipap = day->percentile(pressChanID, perc/100.0); EventDataType epap = day->percentile(epapDataChanID, perc/100.0); html += tr("Your machine was under %1-%2 %3 for %4% of the time.") .arg(epap) .arg(ipap) .arg(schema::channel[pressChanID].units()) .arg(perc); } else if (cpapmode == MODE_ASV || cpapmode == MODE_AVAPS){ EventDataType ipap = day->percentile(pressChanID, perc/100.0); EventDataType epap = qRound(day->settings_wavg(CPAP_EPAP)); html += tr("Your EPAP pressure fixed at %1 %2.") .arg(epap) .arg(schema::channel[epapDataChanID].units())+"
    "; html += tr("Your IPAP pressure was under %1 %2 for %3% of the time.") .arg(ipap) .arg(schema::channel[pressChanID].units()) .arg(perc); } else if (cpapmode == MODE_ASV_VARIABLE_EPAP || cpapmode == MODE_BILEVEL_AUTO_VARIABLE_PS){ EventDataType ipap = day->percentile(pressChanID, perc/100.0); EventDataType epap = day->percentile(epapDataChanID, perc/100.0); html += tr("Your EPAP pressure was under %1 %2 for %3% of the time.").arg(epap).arg(schema::channel[epapDataChanID].units()).arg(perc)+"
    "; html += tr("Your IPAP pressure was under %1 %2 for %3% of the time.").arg(ipap).arg(schema::channel[pressChanID].units()).arg(perc); } html += "
    "; //EventDataType lat = day->timeAboveThreshold(CPAP_Leak, p_profile->cpap->leakRedline())/ 60.0; //EventDataType leaks = 1.0/hours * lat; EventDataType leak = day->wavg(CPAP_Leak); EventDataType leakdays = p_profile->calcWavg(CPAP_Leak, MT_CPAP, starttime, endtime); if ((leak < leakdays) && ((leakdays - leak) >= 0.1)) { comp = under; } else if ((leak > leakdays) && ((leak - leakdays) >= 0.1)) { comp = over; } else if ((fabs(leak - leakdays) >= 0.01) ) { comp = close; } else { comp = equal; } html += tr("Your average leaks were %1 %2, which is %3 your %4 day average of %5.").arg(leak,0,'f',2).arg(schema::channel[CPAP_Leak].units()).arg(comp).arg(averagedays).arg(leakdays,0,'f',2); html += "
    "; } else { html += "

    "+tr("No CPAP data has been imported yet.")+"

    "; } } html += ""; return html; } QString Welcome::GenerateOxiHTML() { auto oximeters = p_profile->GetMachines(MT_OXIMETER); bool haveoximeterdata = false; for (auto & mach : oximeters) { int daysize = mach->day.size(); if (daysize > 0) { haveoximeterdata = true; break; } } QString html = QString("")+ // "" "" "" ""; if (haveoximeterdata) { QDate oxidate=p_profile->LastDay(MT_OXIMETER); int daysto = oxidate.daysTo(QDate::currentDate()); html += "

    "+QObject::tr("Most recent Oximetry data: %1 ").arg(oxidate.toString(Qt::SystemLocaleLongDate)).arg(oxidate.toString(Qt::ISODate)); if (daysto == 1) html += QObject::tr("(last night)"); else if (daysto == 2) html += QObject::tr("(1 day ago)"); else html += QObject::tr("(%2 days ago)").arg(oxidate.daysTo(QDate::currentDate())); html+="

    "; ui->oxiIcon->setVisible(true); ui->oxiInfo->setVisible(true); } else { html += "

    "+QObject::tr("No oximetry data has been imported yet.")+"

    "; ui->oxiIcon->setVisible(false); ui->oxiInfo->setVisible(false); } html += ""; return html; } OSCAR-code-v1.3.1/oscar/welcome.h000066400000000000000000000015301417327530600164030ustar00rootroot00000000000000/* Welcome page Header * * Copyright (c) 2019-2022 The OSCAR Team * Copyright (c) 2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of Source Code. */ #ifndef WELCOME_H #define WELCOME_H #include namespace Ui { class Welcome; } class Welcome : public QWidget { Q_OBJECT public: explicit Welcome(QWidget *parent = 0); ~Welcome(); void refreshPage(); private slots: void on_dailyButton_clicked(); void on_overviewButton_clicked(); void on_statisticsButton_clicked(); void on_oximetryButton_clicked(); void on_importButton_clicked(); private: QString GenerateCPAPHTML(); QString GenerateOxiHTML(); QPixmap pixmap; Ui::Welcome *ui; }; #endif // WELCOME_H OSCAR-code-v1.3.1/oscar/welcome.ui000066400000000000000000000517431417327530600166040ustar00rootroot00000000000000 Welcome 0 0 965 750 Form Qt::Horizontal 40 20 12 0 0 0 0 Qt::Vertical 20 40 Qt::Horizontal 40 20 128 128 128 128 :/icons/logo-lg.png true Qt::AlignCenter Qt::Horizontal 40 20 15 true Welcome to the Open Source CPAP Analysis Reporter Qt::AlignCenter 11 What would you like to do? Qt::AlignCenter 10 Qt::Horizontal 40 20 QToolButton { background: transparent; border: 2px solid transparent; border-radius: 10px; } QToolButton:hover { border: 2px solid #56789a; border-radius: 10px; } QToolButton:pressed { background-color: #8080ff; border: 2px solid #56789a; border-radius: 10px; } CPAP Importer :/icons/sdcard.png:/icons/sdcard.png 100 100 Qt::ToolButtonTextUnderIcon true QToolButton { background: transparent; border: 2px solid transparent; border-radius: 10px; } QToolButton:hover { border: 2px solid #56789a; border-radius: 10px; } QToolButton:pressed { background-color: #8080ff; border: 2px solid #56789a; border-radius: 10px; } Oximetry Wizard :/icons/cms50f.png:/icons/cms50f.png 100 100 Qt::ToolButtonTextUnderIcon true QToolButton { background: transparent; border: 2px solid transparent; border-radius: 10px; } QToolButton:hover { border: 2px solid #56789a; border-radius: 10px; } QToolButton:pressed { background-color: #8080ff; border: 2px solid #56789a; border-radius: 10px; } Daily View :/icons/daily.png:/icons/daily.png 100 100 Qt::ToolButtonTextUnderIcon true QToolButton { background: transparent; border: 2px solid transparent; border-radius: 10px; } QToolButton:hover { border: 2px solid #56789a; border-radius: 10px; } QToolButton:pressed { background-color: #8080ff; border: 2px solid #56789a; border-radius: 10px; } Overview :/icons/overview-page.png:/icons/overview-page.png 100 100 Qt::ToolButtonTextUnderIcon true QToolButton { background: transparent; border: 2px solid transparent; border-radius: 10px; } QToolButton:hover { border: 2px solid #56789a; border-radius: 10px; } QToolButton:pressed { background-color: #8080ff; border: 2px solid #56789a; border-radius: 10px; } Statistics :/icons/statistics.png:/icons/statistics.png 100 100 Qt::ToolButtonTextUnderIcon true Qt::Horizontal 40 20 QFrame::StyledPanel QFrame::Raised 2 0 0 128 128 128 128 :/icons/mask.png true 0 1 16777215 150 10 border: 1px solid; border-radius:20px; background-color: #ffffd0; Qt::ScrollBarAlwaysOff Qt::ScrollBarAlwaysOff QAbstractScrollArea::AdjustToContents <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Segoe UI'; font-size:10pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8.25pt;"><br /></p></body></html> 0 0 128 128 128 128 :/icons/oximeter.png true Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 1 16777215 150 10 border: 1px solid; border-radius:20px; background-color: #ffffd0; QAbstractScrollArea::AdjustToContents QFrame::StyledPanel QFrame::Raised Qt::Horizontal 40 20 0 0 40 40 40 40 :/icons/sdcard-lock.png true <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">ResMed S9 SDCards need to be locked </span><span style=" font-weight:600; color:#ff0000;">before inserting into your computer.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Some operating systems write index files to the card without asking, which can render your card unreadable by your cpap machine.</span></p></body></html> false 0 8 0 0 40 40 40 40 :/icons/sdcard-lock.png true Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter -1 Qt::Horizontal 40 20 Qt::Vertical 20 40 Qt::Horizontal 40 20 OSCAR-code-v1.3.1/oscar/win_icon.rc000066400000000000000000000000631417327530600167320ustar00rootroot00000000000000IDI_ICON1 ICON DISCARDABLE "./icons/bob-v3.0.ico" OSCAR-code-v1.3.1/oscar/zip.cpp000066400000000000000000000251751417327530600161200ustar00rootroot00000000000000/* OSCAR ZIP archive creation * Provides a Qt-convenient wrapper around miniz, see https://github.com/richgel999/miniz * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include "zip.h" #include #include #include #include "SleepLib/progressdialog.h" static const quint64 PROGRESS_SCALE = 1024; // QProgressBar only holds an int, so report progress in KiB. // Static functions to abstract the details of miniz from the primary logic. static void* zip_init(); static bool zip_open(void* ctx, QFile & file); static bool zip_add(void* ctx, const QString & archive_name, const QByteArray & data, const QDateTime & modified); static void zip_close(void* ctx); static void zip_done(void* ctx); ZipFile::ZipFile() { m_ctx = zip_init(); } ZipFile::~ZipFile() { Close(); zip_done(m_ctx); } bool ZipFile::Open(const QString & filepath) { m_file.setFileName(filepath); bool ok = m_file.open(QIODevice::WriteOnly); if (!ok) { qWarning() << "Could not open" << m_file.fileName() << "for writing, error code" << m_file.error() << m_file.errorString(); // qWarning() << "unable to open" << m_file.fileName(); return false; } ok = zip_open(m_ctx, m_file); return ok; } void ZipFile::Close() { if (m_file.isOpen()) { zip_close(m_ctx); m_file.close(); } } bool ZipFile::AddDirectory(const QString & path, ProgressDialog* progress) { return AddDirectory(path, "", progress); } bool ZipFile::AddDirectory(const QString & path, const QString & prefix, ProgressDialog* progress) { bool ok; FileQueue queue; queue.AddDirectory(path, prefix); ok = AddFiles(queue, progress); return ok; } bool ZipFile::AddFiles(FileQueue & queue, ProgressDialog* progress) { bool ok; // Exclude the zip file that's being created (if it happens to be in the list). queue.Remove(QFileInfo(m_file).canonicalFilePath()); qDebug().noquote() << "Adding" << queue.toString(); m_abort = false; m_progress = 0; if (progress) { progress->addAbortButton(); progress->setWindowModality(Qt::ApplicationModal); progress->open(); connect(this, SIGNAL(setProgressMax(int)), progress, SLOT(setProgressMax(int))); connect(this, SIGNAL(setProgressValue(int)), progress, SLOT(setProgressValue(int))); connect(progress, SIGNAL(abortClicked()), this, SLOT(abort())); } // Always emit, since the caller may have configured and connected a progress dialog manually. emit setProgressValue(m_progress/PROGRESS_SCALE); emit setProgressMax((queue.byteCount() + queue.dirCount())/PROGRESS_SCALE); QCoreApplication::processEvents(); for (auto & entry : queue.files()) { ok = AddFile(entry.path, entry.name); if (!ok || m_abort) { break; } } if (progress) { disconnect(progress, SIGNAL(abortClicked()), this, SLOT(abort())); disconnect(this, SIGNAL(setProgressMax(int)), progress, SLOT(setProgressMax(int))); disconnect(this, SIGNAL(setProgressValue(int)), progress, SLOT(setProgressValue(int))); progress->close(); progress->deleteLater(); } if (!ok) { qWarning().noquote() << "Unable to create" << m_file.fileName(); Close(); m_file.remove(); } else if (aborted()) { qDebug().noquote() << "User canceled zip creation."; Close(); m_file.remove(); } else { qDebug().noquote() << "Created" << m_file.fileName() << m_file.size() << "bytes"; } return ok; } bool ZipFile::AddFile(const QString & path, const QString & name) { if (!m_file.isOpen()) { qWarning() << m_file.fileName() << "has not been opened for writing"; return false; } QFileInfo fi(path); QByteArray data; QString archive_name = name; if (archive_name.isEmpty()) archive_name = fi.fileName(); if (fi.isDir()) { archive_name += "/"; m_progress += 1; } else { // Open and read file into memory. QFile f(path); if (!f.open(QIODevice::ReadOnly)) { qWarning() << path << "can't open"; return false; } data = f.readAll(); m_progress += data.size(); } //qDebug() << "attempting to add" << archive_name << ":" << data.size() << "bytes"; bool ok = zip_add(m_ctx, archive_name, data, fi.lastModified()); emit setProgressValue(m_progress/PROGRESS_SCALE); QCoreApplication::processEvents(); return ok; } // ================================================================================================== bool FileQueue::AddDirectory(const QString & path, const QString & prefix) { QDir dir(path); if (!dir.exists() || !dir.isReadable()) { qWarning() << dir.canonicalPath() << "can't read directory"; #if defined(Q_OS_MACOS) // If this is a directory known to be protected by macOS "Full Disk Access" permissions, // skip it but don't consider it an error. static const QSet s_macProtectedDirs = { ".fseventsd", ".Spotlight-V100", ".Trashes" }; if (s_macProtectedDirs.contains(dir.dirName())) { return true; } #endif return false; } QString base = prefix; if (base.isEmpty()) base = dir.dirName(); // Add directory entry bool ok = AddFile(dir.canonicalPath(), base); if (!ok) { return false; } dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden); dir.setSorting(QDir::Name); QFileInfoList flist = dir.entryInfoList(); for (auto & fi : flist) { QString canonicalPath = fi.canonicalFilePath(); QString relative_path = base + "/" + fi.fileName(); if (fi.isSymLink()) { qWarning() << "skipping symlink" << canonicalPath << fi.symLinkTarget(); } else if (fi.isDir()) { // Descend and recurse ok &= AddDirectory(canonicalPath, relative_path); } else { // Add the file to the zip ok &= AddFile(canonicalPath, relative_path); } // Don't stop in our tracks when we hit an error. } return ok; } bool FileQueue::AddFile(const QString & path, const QString & prefix) { QFileInfo fi(path); QString canonicalPath = fi.canonicalFilePath(); QString archive_name = prefix; if (archive_name.isEmpty()) archive_name = fi.fileName(); if (fi.isDir()) { m_dir_count++; } else if (fi.exists()) { m_file_count++; m_byte_count += fi.size(); } else { qWarning() << "file doesn't exist" << canonicalPath; return false; } Entry entry = { canonicalPath, archive_name }; m_files.append(entry); QCoreApplication::processEvents(); return true; } int FileQueue::Remove(const QString & path, QString* outName) { QFileInfo fi(path); QString canonicalPath = fi.canonicalFilePath(); int removed = 0; QMutableListIterator i(m_files); while (i.hasNext()) { Entry & entry = i.next(); if (entry.path == canonicalPath) { if (outName) { // If the caller cares about the name, it will most likely be re-added later rather than skipped. *outName = entry.name; } else { qDebug().noquote() << "skipping file:" << path; } if (fi.isDir()) { m_dir_count--; } else { m_file_count--; m_byte_count -= fi.size(); } i.remove(); removed++; } } if (removed > 1) { qWarning().noquote() << removed << "copies found in zip queue:" << path; } return removed; } const QString FileQueue::toString() const { return QString("%1 directories, %2 files, %3 bytes").arg(m_dir_count).arg(m_file_count).arg(m_byte_count); } // ================================================================================================== // Static functions to abstract the details of miniz from the primary logic. #include "SleepLib/thirdparty/miniz.h" // Callback for miniz to write compressed data static size_t zip_write(void *pOpaque, mz_uint64 /*file_ofs*/, const void *pBuf, size_t n) { if (pOpaque == nullptr) { qCritical() << "null pointer passed to ZipFile::Write!"; return 0; } QFile* file = (QFile*) pOpaque; size_t written = file->write((const char*) pBuf, n); if (written < n) { qWarning() << "error writing to" << file->fileName(); } return written; } static void* zip_init() { mz_zip_archive* pZip = new mz_zip_archive(); // zero-initializes struct pZip->m_pWrite = zip_write; return pZip; } static void zip_done(void* ctx) { Q_ASSERT(ctx); mz_zip_archive* pZip = (mz_zip_archive*) ctx; delete pZip; } static bool zip_open(void* ctx, QFile & file) { Q_ASSERT(ctx); mz_zip_archive* pZip = (mz_zip_archive*) ctx; pZip->m_pIO_opaque = &file; bool ok = mz_zip_writer_init_v2(pZip, 0, MZ_ZIP_FLAG_CASE_SENSITIVE); if (!ok) { mz_zip_error mz_err = mz_zip_get_last_error(pZip); qWarning() << "unable to initialize miniz writer" << MZ_VERSION << mz_zip_get_error_string(mz_err); } return ok; } static bool zip_add(void* ctx, const QString & archive_name, const QByteArray & data, const QDateTime & modified) { Q_ASSERT(ctx); mz_zip_archive* pZip = (mz_zip_archive*) ctx; // Add to .zip time_t last_modified = modified.toTime_t(); // technically deprecated, but miniz expects a time_t bool ok = mz_zip_writer_add_mem_ex_v2(pZip, archive_name.toLocal8Bit(), data.constData(), data.size(), nullptr, 0, // no comment MZ_DEFAULT_COMPRESSION, 0, 0, // not used when compressing data &last_modified, nullptr, 0, // no user extra data nullptr, 0 // no user extra data central ); if (!ok) { mz_zip_error mz_err = mz_zip_get_last_error(pZip); qWarning() << "unable to add" << archive_name << ":" << data.size() << "bytes" << mz_zip_get_error_string(mz_err); } return ok; } static void zip_close(void* ctx) { Q_ASSERT(ctx); mz_zip_archive* pZip = (mz_zip_archive*) ctx; mz_zip_writer_finalize_archive(pZip); mz_zip_writer_end(pZip); } OSCAR-code-v1.3.1/oscar/zip.h000066400000000000000000000045401417327530600155560ustar00rootroot00000000000000/* OSCAR ZIP archive creation * Provides a Qt-convenient wrapper around miniz, see https://github.com/richgel999/miniz * * Copyright (c) 2020-2022 The OSCAR Team * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #include #include #include #include class ProgressDialog; class ZipFile : public QObject { Q_OBJECT public: ZipFile(); virtual ~ZipFile(); bool Open(const QString & filepath); bool AddDirectory(const QString & path, ProgressDialog* progress=nullptr); // add a directory and recurse bool AddDirectory(const QString & path, const QString & archive_name, ProgressDialog* progress=nullptr); // add a directory and recurse bool AddFiles(class FileQueue & queue, ProgressDialog* progress=nullptr); // add a fixed list of files bool AddFile(const QString & path, const QString & archive_name); // add a single file void Close(); bool aborted() const { return m_abort; } public slots: void abort() { m_abort = true; } signals: void setProgressMax(int max); void setProgressValue(int val); protected: void* m_ctx; QFile m_file; bool m_abort; quint64 m_progress; }; class FileQueue { struct Entry { QString path; QString name; }; QList m_files; int m_dir_count; int m_file_count; quint64 m_byte_count; public: FileQueue() : m_dir_count(0), m_file_count(0), m_byte_count(0) {} ~FileQueue() = default; //!brief Remove a file from the queue, return the number of instances removed. int Remove(const QString & path, QString* outName=nullptr); //!brief Recursively add a directory and its contents to the queue along with the prefix to be used in an archive. bool AddDirectory(const QString & path, const QString & prefix=""); //!brief Add a file to the queue along with the name to be used in an archive. bool AddFile(const QString & path, const QString & archive_name=""); inline int dirCount() const { return m_dir_count; } inline int fileCount() const { return m_file_count; } inline quint64 byteCount() const { return m_byte_count; } const QList & files() const { return m_files; } const QString toString() const; }; OSCAR-code-v1.3.1/preferencesdialog.ui000066400000000000000000004057661417327530600175330ustar00rootroot00000000000000 PreferencesDialog Qt::ApplicationModal 0 0 942 651 0 0 145 0 Preferences :/icons/preferences.png:/icons/preferences.png true true 0 0 0 0 0 0 0 &Import 4 4 4 4 4 0 0 Session Splitting Settings 4 4 4 4 4 0 0 [ResMed warning message] true true 0 0 0 0 0 0 Combine Close Sessions Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Minutes Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Multiple sessions closer together than this value will be kept on the same day. 0 240 10 30 0 Qt::Horizontal false false QSlider::TicksAbove 10 QFrame::Box 5 QLCDNumber::Flat Ignore Short Sessions Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Minutes Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 4 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sessions shorter in duration than this will not be displayed<span style=" font-style:italic;">.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-style:italic;"></p></body></html> 90 5 Qt::Horizontal QSlider::TicksAbove 5 QFrame::Box QLCDNumber::Flat 4 Day Split Time Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Sessions starting before this time will go to the previous calendar day. QAbstractSpinBox::UpDownArrows 12 0 0 2000 1 1 Qt::Horizontal QSizePolicy::Minimum 10 20 <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, you won't see problems with this very often.</p></body></html> Don't Split Summary Days (Warning: read the tooltip!) Qt::Horizontal 40 20 0 0 Session Storage Options 4 9 4 0 0 4 true Changing SD Backup compression options doesn't automatically recompress backup data. true Compress ResMed (EDF) backups to save disk space. Backed up EDF files are stored in the .gz format, which is common on Mac & Linux platforms.. OSCAR can import from this compressed backup directory natively.. To use it with ResScan will require the .gz files to be uncompressed first.. Compress SD Card Backups (slower first import, but makes backups smaller) true The following options affect the amount of disk space OSCAR uses, and have an effect on how long import takes. true This makes OSCAR's data take around half as much space. But it makes import and day changing take longer.. If you've got a new computer with a small solid state disk, this is a good option. Compress Session Data (makes OSCAR data smaller, but day changing slower.) This maintains a backup of SD-card data for ResMed machines, ResMed S9 series machines delete high resolution data older than 7 days, and graph data older than 30 days.. OSCAR can keep a copy of this data if you ever need to reinstall. (Highly recomended, unless your short on disk space or don't care about the graph data) Create SD Card Backups during Import (Turn this off at your own peril!) 0 0 0 Do not import sessions older than: 165 0 Sessions older than this date will not be imported true true 2099 12 31 1970 1 2 QDateTimeEdit::DaySection dd MMMM yyyy true Qt::Horizontal 40 20 0 0 Memory and Startup Options Bypass the login screen and load the most recent User Profile Auto-Launch CPAP Importer after opening profile Qt::Vertical 20 40 <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> Keep Waveform/Event data in memory <html><head/><body><p>Makes starting OSCAR a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> Pre-Load all summary data at startup Automatically load last used profile on start-up <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> Import without asking for confirmation <html><head/><body><p>Provide an alert when importing data from any machine model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested machine <html><head/><body><p>Provide an alert when importing data that is somehow different from anything previously seen by OSCAR developers.</p></body></html> Warn when previously unseen data is encountered &CPAP 4 4 4 4 4 20 QLayout::SetMinimumSize 5 5 5 5 0 0 CPAP Clock Drift 4 4 4 4 4 0 0 <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> true -9999 9999 Minutes -59 59 0 -99 Hours Seconds 0 0 This calculation requires Total Leaks data to be provided by the CPAP machine. (Eg, PRS1, but not ResMed, which has these already) The Unintentional Leak calculations used here are linear, they don't model the mask vent curve. If you use a few different masks, pick average values instead. It should still be close enough. Calculate Unintentional Leaks When Not Present true Your masks vent rate at 20 cmH2O pressure 300 550 10 470 Qt::Horizontal QSlider::TicksAbove 20 0 0 80 0 QFrame::Box 48 l/m true Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Your masks vent rate at 4 cmH2O pressure 120 240 1 10 201 Qt::Horizontal false QSlider::TicksAbove 4 cmH2O Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 20 cmH2O Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 80 0 QFrame::Box 20 l/m true Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Note: A linear calculation method is used. Changing these values requires a recalculation. true 0 0 Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the machine missed. This option must be enabled before import, otherwise a purge is required. Custom CPAP User Event Flagging false true 4 9 4 4 4 s 10.000000000000000 % 10.000000000000000 This experimental option attempts to use OSCAR's event flagging system to improve machine detected event positioning. Resync Machine Detected Events (Experimental) Qt::Vertical 20 40 #2 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Flow Restriction 0 0 true <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true 0 0 Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. % 10.000000000000000 Show in Event Breakdown Piechart Duration of airflow restriction s 1.000000000000000 10.000000000000000 #1 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Allow duplicates near machine events. 0 0 Event Duration QLayout::SetMinimumSize 5 5 0 0 General CPAP and Related Settings Show flags for machine detected events that haven't been identified yet. Enable Unknown Events Channels AHI/Hour Graph Time Window Compliance defined as 0 0 Adjusts the amount of data considered for each point in the AHI/Hour graph. Defaults to 60 minutes.. Highly recommend it's left at this value. minutes 5 999 60 50 false Whether to show the leak redline in the leak graph Flag leaks over threshold Preferred major event index Reset the counter to zero at beginning of each (time) window. Zero Reset 0 0 User definable threshold considered large leak L/min 1 0 0 Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. hours 1 8.000000000000000 4.000000000000000 0 0 AHI RDI 0 0 Changes to the following settings needs a restart, but not a recalc. Preferred Calculation Methods For consistancy, ResMed users should use 95% here, as this is the only value available on summary-only days. % 1 99 0 0 Middle Calculations Maximum Calcs Upper Percentile Culminative Indices Median is recommended for ResMed users. Median Weighted Average Normal Average 0 0 140 0 <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html> True Maximum 99% Percentile Combined Count divided by Total Hours Time Weighted average of Indice Standard average of indice Median 0 0 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed machines do not support changing these settings.</p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::Vertical 20 40 &Oximetry 4 2 2 2 2 Oximetry Settings false 4 8 4 4 5 0 0 Other oximetry options % bpm Small chunks of oximetry data under this amount will be discarded. s 300 Discard segments under Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter bpm Flag SPO2 Desaturations Below Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Flag Pulse Rate Below Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Flag Pulse Rate Above Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Flag rapid changes in oximetry stats 0 0 SPO2 Minimum duration of drop in oxygen saturation s 0 Sudden change in Pulse Rate of at least this amount bpm 0 1.000000000000000 Minimum duration of pulse change event. s 0 0 0 Pulse Percentage drop in oxygen saturation % 0 1.000000000000000 Qt::Vertical 20 40 0 0 300 0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.84158pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncing Oximetry and CPAP Data</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data imported from SpO2Review (from .spoR files) or the serial import method does </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> have the correct timestamp needed to sync.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (using a serial cable) is one way to acheive an accurate sync on CMS50 oximeters, but does not counter for CPAP clock drift.</span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If you start your Oximeters recording mode at </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">the same time you start your CPAP machine, you can now also achieve sync. </span></p> <p align="justify" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The serial import process takes the starting time from last nights first CPAP session. (Remember to import your CPAP data first!)</span></p></body></html> Events 4 0 0 0 0 Search 0 0 0 0 Reset &Defaults 0 0 <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Waveforms 4 0 0 0 0 Search 0 0 0 0 Reset &Defaults 0 0 <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter &General 0 0 General Settings 4 4 4 4 4 Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. Enable Multithreading Show Remove Card reminder notification on OSCAR shutdown Always save screenshots in the OSCAR Data folder Qt::Vertical Qt::Horizontal Graphics Engine (Requires Restart) Try changing this from the default setting (Desktop OpenGL) if you experience rendering problems with OSCAR's graphs. true 0 0 Check For Updates false 0 0 You are using a test version of OSCAR. Test versions check for updates automatically at least once every seven days. You may set the interval to less than seven days. true 50 false false false Automatically check for updates 0 0 Check for new version every How often OSCAR should check for updates. 90 0 0 days. Qt::Horizontal 40 20 0 0 Last Checked For Updates: 0 0 TextLabel Qt::Horizontal 40 20 If you are interested in helping test new features and bugfixes early, click here. I want to try experimental and test builds. (Advanced users only please.) 0 0 If you would like to help test early versions of OSCAR, please see the Wiki page about testing OSCAR. We welcome everyone who would like to test OSCAR, help develop OSCAR, and help with translations to existing or new languages. https://www.sleepfiles.com/OSCAR true true Qt::Vertical 20 40 &Appearance 4 2 2 2 2 0 0 Graph Settings On Opening Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter <html><head/><body><p>Which tab to open on loading a profile. (Note: It will default to Profile if OSCAR is set to not open a profile on startup)</p></body></html> Profile Profile Welcome Daily Overview Statistics Switch Tabs Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter No change Welcome Daily Overview Statistics After Import Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Graph Height Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Overlay Flags Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Line Thickness Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 The visual method of displaying waveform overlay flags. Standard Bars Top Markers The pixel thickness of line plots 2 8 1 Qt::Horizontal QSlider::TicksBelow Tooltip Timeout Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Scroll Dampening Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter How long you want the tooltips to stay visible. 1000 30000 100 500 5000 Qt::Horizontal QSlider::TicksBelow 1000 0 0 60 0 QFrame::Box TextLabel Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Graph Tooltips Bar Tops Line Chart 0 0 Default display height of graphs in pixels 50 600 10 180 <html><head/><body><p>This makes scrolling when zoomed in easier on sensitive bidirectional TouchPads</p><p>50ms is recommended value.</p></body></html> 25 1 5 Qt::Horizontal false QSlider::TicksBelow 1 0 0 60 0 QFrame::Box TextLabel Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Overview Linecharts Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Other Visual Settings Anti-Aliasing applies smoothing to graph plots.. Certain plots look more attractive with this on. This also affects printed reports. Try it and see if you like it. Use Anti-Aliasing Makes certain plots look more "square waved". Square Wave Plots Pixmap caching is an graphics acceleration technique. May cause problems with font drawing in graph display area on your platform. Use Pixmap Caching <html><head/><body><p>These features have recently been pruned. They will come back later. </p></body></html> Animations && Fancy Stuff Daily view navigation buttons will skip over days without data records Skip over Empty Days Whether to allow changing yAxis scales by double clicking on yAxis labels Allow YAxis Scaling Whether to include machine serial number on machine settings changes report Include Serial Number Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) Qt::Vertical 20 40 Qt::Horizontal Fonts (Application wide settings) false 0 4 0 4 0 0 75 true Font 0 0 75 true Size 0 0 75 true Bold Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 75 true Italic 0 0 Application 80 16777215 6 30 10 0 0 Qt::LeftToRight 0 0 0 0 Graph Text 80 16777215 6 40 11 0 0 0 0 0 0 Graph Titles 80 16777215 6 40 14 0 0 0 0 0 0 Big Text 80 16777215 6 72 18 0 0 0 0 0 0 75 true Details Qt::Vertical 20 40 &Cancel &Ok Qt::Horizontal 750 20 cancelButton clicked() PreferencesDialog reject() 757 605 286 274 OSCAR-code-v1.3.1/utf8.sh000077500000000000000000000004671417327530600147250ustar00rootroot00000000000000#!/bin/bash TO="UTF-8"; FILE=$1 FROM=$(file -i $FILE | cut -d'=' -f2) if [[ $FROM = "binary" ]]; then echo "Skipping binary $FILE..." exit 0 fi iconv -f $FROM -t $TO -o $FILE.tmp $FILE; ERROR=$? if [[ $ERROR -eq 0 ]]; then echo "Converting $FILE..." mv -f $FILE.tmp $FILE else echo "Error on $FILE" fi